This commit is contained in:
wintsa 2026-05-06 14:51:57 +08:00
commit bbc8692e7f
8 changed files with 309 additions and 0 deletions

12
index.html Normal file
View File

@ -0,0 +1,12 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>AG Chart Service</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

24
package.json Normal file
View File

@ -0,0 +1,24 @@
{
"name": "agchart-service",
"version": "0.1.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite --host 0.0.0.0",
"build": "tsc -b && vite build",
"preview": "vite preview --host 0.0.0.0"
},
"dependencies": {
"@vitejs/plugin-react": "^5.0.0",
"ag-charts-community": "^13.2.1",
"ag-charts-react": "^13.2.1",
"vite": "^7.0.0",
"typescript": "^5.8.3",
"react": "^19.1.0",
"react-dom": "^19.1.0"
},
"devDependencies": {
"@types/react": "^19.1.8",
"@types/react-dom": "^19.1.6"
}
}

104
src/App.tsx Normal file
View File

@ -0,0 +1,104 @@
import { useMemo, useState } from 'react';
import { AgCharts } from 'ag-charts-react';
import type { AgChartOptions } from 'ag-charts-community';
const revenueData = [
{ month: '1月', revenue: 146, orders: 92, margin: 36 },
{ month: '2月', revenue: 158, orders: 101, margin: 39 },
{ month: '3月', revenue: 171, orders: 117, margin: 43 },
{ month: '4月', revenue: 185, orders: 126, margin: 45 },
{ month: '5月', revenue: 193, orders: 133, margin: 47 },
{ month: '6月', revenue: 221, orders: 148, margin: 52 },
];
function App() {
const [chartType, setChartType] = useState<'bar' | 'line'>('bar');
const chartOptions = useMemo<AgChartOptions>(
() => ({
title: {
text: '业务趋势',
},
subtitle: {
text: '收入、订单与毛利表现',
},
data: revenueData,
series: [
{
type: chartType,
xKey: 'month',
yKey: 'revenue',
yName: '收入',
fill: '#2563eb',
stroke: '#2563eb',
},
{
type: chartType,
xKey: 'month',
yKey: 'orders',
yName: '订单',
fill: '#059669',
stroke: '#059669',
},
{
type: chartType,
xKey: 'month',
yKey: 'margin',
yName: '毛利',
fill: '#d97706',
stroke: '#d97706',
},
],
axes: [
{
type: 'category',
position: 'bottom',
},
{
type: 'number',
position: 'left',
title: {
text: '数值',
},
},
],
legend: {
position: 'bottom',
},
}),
[chartType],
);
return (
<main className="app-shell">
<section className="toolbar" aria-label="图表设置">
<div>
<h1>AG Chart Service</h1>
<p>React </p>
</div>
<div className="segmented-control" aria-label="切换图表类型">
<button
className={chartType === 'bar' ? 'active' : ''}
type="button"
onClick={() => setChartType('bar')}
>
</button>
<button
className={chartType === 'line' ? 'active' : ''}
type="button"
onClick={() => setChartType('line')}
>
线
</button>
</div>
</section>
<section className="chart-panel" aria-label="业务趋势图表">
<AgCharts options={chartOptions} />
</section>
</main>
);
}
export default App;

13
src/main.tsx Normal file
View File

@ -0,0 +1,13 @@
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { AllCommunityModule, ModuleRegistry } from 'ag-charts-community';
import App from './App';
import './styles.css';
ModuleRegistry.registerModules([AllCommunityModule]);
createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
</StrictMode>,
);

116
src/styles.css Normal file
View File

@ -0,0 +1,116 @@
:root {
color: #172033;
background: #f4f7fb;
font-family:
Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
sans-serif;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
min-width: 320px;
min-height: 100vh;
}
button {
font: inherit;
}
.app-shell {
display: grid;
grid-template-rows: auto minmax(360px, 1fr);
gap: 18px;
width: min(1180px, calc(100vw - 32px));
min-height: 100vh;
margin: 0 auto;
padding: 24px 0;
}
.toolbar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
}
.toolbar h1 {
margin: 0;
font-size: 28px;
line-height: 1.15;
}
.toolbar p {
margin: 6px 0 0;
color: #5b6578;
font-size: 14px;
}
.segmented-control {
display: inline-grid;
grid-template-columns: repeat(2, minmax(72px, 1fr));
min-width: 160px;
padding: 4px;
border: 1px solid #cfd7e6;
border-radius: 8px;
background: #ffffff;
}
.segmented-control button {
min-height: 34px;
border: 0;
border-radius: 6px;
color: #4a5568;
background: transparent;
cursor: pointer;
}
.segmented-control button.active {
color: #ffffff;
background: #1f4ed8;
}
.chart-panel {
min-height: 520px;
padding: 16px;
border: 1px solid #d9e1ee;
border-radius: 8px;
background: #ffffff;
}
.chart-panel > div {
height: 100%;
}
@media (max-width: 640px) {
.app-shell {
width: min(100vw - 20px, 1180px);
grid-template-rows: auto minmax(420px, 1fr);
padding: 16px 0;
}
.toolbar {
align-items: stretch;
flex-direction: column;
}
.toolbar h1 {
font-size: 24px;
}
.segmented-control {
width: 100%;
}
.chart-panel {
min-height: 460px;
padding: 10px;
}
}

21
tsconfig.json Normal file
View File

@ -0,0 +1,21 @@
{
"compilerOptions": {
"target": "ES2022",
"useDefineForClassFields": true,
"lib": ["DOM", "DOM.Iterable", "ES2022"],
"allowJs": false,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "ESNext",
"moduleResolution": "Bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}

10
tsconfig.node.json Normal file
View File

@ -0,0 +1,10 @@
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "Bundler",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]
}

9
vite.config.ts Normal file
View File

@ -0,0 +1,9 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
server: {
port: 5173,
},
});