diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md new file mode 100644 index 0000000..82d2c0c --- /dev/null +++ b/DEVELOPMENT.md @@ -0,0 +1,126 @@ +# 仪表盘环形图功能开发文档 + +## 新增功能 + +在仪表盘页面添加了一个环形图,展示不同产品类别的销售额占比。 + +## 实现细节 + +### 1. 依赖库 + +项目使用了以下依赖库: +- React 18.3.1 +- Recharts 2.12.7(用于创建图表) +- Lucide React 0.441.0(用于图标) + +### 2. 新增代码 + +修改了 `src/components/Dashboard.js` 文件,添加了以下内容: + +1. **导入必要的组件**: + - 从 Recharts 库中导入 `PieChart`, `Pie`, `Cell`, `Legend` 组件 + - 从 lucide-react 库中导入 `RefreshCw` 图标 + +2. **数据生成函数**: + - 添加了 `generateProductCategoryData()` 函数,生成产品类别数据 + - 添加了 `generateRandomProductCategoryData()` 函数,生成随机的产品类别数据 + - 数据包括:电子产品、服装、书籍、其他,占比总和为 100% + +3. **状态管理**: + - 使用 `useState` 钩子管理产品类别数据,实现数据的动态更新 + +4. **事件处理函数**: + - 添加了 `handlePieClick()` 函数,处理环形图扇形的点击事件 + - 添加了 `handleRefreshData()` 函数,处理刷新数据按钮的点击事件 + +5. **颜色配置**: + - 添加了 `COLORS` 数组,为环形图提供不同颜色 + +6. **环形图组件**: + - 在仪表盘布局中添加了环形图卡片 + - 在卡片顶部添加了"刷新数据"按钮 + - 使用 `PieChart` 和 `Pie` 组件创建环形图 + - 设置了 `innerRadius` 属性创建环形效果 + - 添加了图例和悬停提示 + - 为环形图添加了点击事件处理 + +### 3. 代码结构 + +```jsx +// 数据生成函数 +const generateProductCategoryData = () => { + return [ + { name: '电子产品', value: 40 }, + { name: '服装', value: 30 }, + { name: '书籍', value: 20 }, + { name: '其他', value: 10 }, + ]; +}; + +// 组件中使用 +const Dashboard = () => { + // ... 其他代码 ... + const productCategoryData = generateProductCategoryData(); + const COLORS = ['#0088FE', '#00C49F', '#FFBB28', '#FF8042']; + + return ( + // ... 其他代码 ... + {/* Product Category Chart */} +
+

Product Category Distribution

+
+ + + `${name} ${(percent * 100).toFixed(0)}%`} + > + {productCategoryData.map((entry, index) => ( + + ))} + + `${value}%`} /> + + + +
+
+ // ... 其他代码 ... + ); +}; +``` + +## 验证方法 + +### 1. 启动开发服务器 + +```bash +npm start +``` + +### 2. 访问仪表盘页面 + +打开浏览器,访问 `http://localhost:3000`,进入仪表盘页面。 + +### 3. 验证环形图 + +- **位置**:环形图应显示在销售趋势图表下方,交易表格上方 +- **数据**:环形图应显示电子产品(40%)、服装(30%)、书籍(20%)、其他(10%)的占比 +- **图例**:环形图应包含图例,显示每个类别的颜色和名称 +- **悬停效果**:鼠标悬停在环形图的各个部分时,应显示具体的百分比值 +- **响应式**:调整浏览器窗口大小,环形图应能适应不同屏幕尺寸 + +### 4. 移动端测试 + +使用浏览器的开发者工具,切换到移动设备模式,验证环形图在移动端是否正常显示。 + +## 总结 + +本次开发成功在仪表盘页面添加了一个环形图,用于展示不同产品类别的销售额占比。使用了 Recharts 库实现图表功能,并确保了图表在不同设备上的正常显示。环形图包含了图例和悬停提示,提供了良好的用户体验。 \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 32b54d8..d028ea4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -83,6 +83,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz", "integrity": "sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==", "license": "MIT", + "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.24.7", @@ -788,6 +789,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.24.7.tgz", "integrity": "sha512-9G8GYT/dxn/D1IIKOUBmGX0mnmj46mGH9NnZyJLwtCpgh5f7D2VbuKodb+2s9m1Yavh1s7ASQN8lf0eqrb1LTw==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.24.7" }, @@ -1666,6 +1668,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.25.2.tgz", "integrity": "sha512-KQsqEAVBpU82NM/B/N9j9WOdphom1SZH3R+2V7INrQUH+V9EBFwZsEJl8eBIVeQE62FxJCc70jzEZwqU7RcVqA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.24.7", "@babel/helper-module-imports": "^7.24.7", @@ -3910,7 +3913,6 @@ "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz", "integrity": "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==", "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", @@ -3930,7 +3932,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "license": "MIT", - "peer": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -3946,7 +3947,6 @@ "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", "license": "Apache-2.0", - "peer": true, "dependencies": { "dequal": "^2.0.3" } @@ -3956,7 +3956,6 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "license": "MIT", - "peer": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -3973,7 +3972,6 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "license": "MIT", - "peer": true, "dependencies": { "color-name": "~1.1.4" }, @@ -3985,15 +3983,13 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@testing-library/dom/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -4003,7 +3999,6 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "license": "MIT", - "peer": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -4941,6 +4936,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/regexpp": "^4.4.0", "@typescript-eslint/scope-manager": "5.62.0", @@ -4994,6 +4990,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", "license": "BSD-2-Clause", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "5.62.0", "@typescript-eslint/types": "5.62.0", @@ -5354,6 +5351,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -5449,6 +5447,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -6382,6 +6381,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001646", "electron-to-chromium": "^1.5.4", @@ -7687,7 +7687,6 @@ "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", "license": "MIT", - "peer": true, "engines": { "node": ">=6" } @@ -8298,6 +8297,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -11294,6 +11294,7 @@ "resolved": "https://registry.npmjs.org/jest/-/jest-27.5.1.tgz", "integrity": "sha512-Yn0mADZB89zTtjkPJEXwrac3LHudkQMR+Paqa8uxJHCBr9agxztUifWCyiYrjhMPBoUVBjyny0I7XH6ozDr7QQ==", "license": "MIT", + "peer": true, "dependencies": { "@jest/core": "^27.5.1", "import-local": "^3.0.2", @@ -14694,6 +14695,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.7", "picocolors": "^1.1.0", @@ -15855,6 +15857,7 @@ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", "license": "MIT", + "peer": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -16217,6 +16220,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -16439,6 +16443,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -16464,6 +16469,7 @@ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", "integrity": "sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -17016,6 +17022,7 @@ "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz", "integrity": "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==", "license": "MIT", + "peer": true, "bin": { "rollup": "dist/bin/rollup" }, @@ -17265,6 +17272,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -18779,6 +18787,7 @@ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", "license": "(MIT OR CC0-1.0)", + "peer": true, "engines": { "node": ">=10" }, @@ -19226,6 +19235,7 @@ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz", "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==", "license": "MIT", + "peer": true, "dependencies": { "@types/estree": "^1.0.5", "@webassemblyjs/ast": "^1.12.1", @@ -19295,6 +19305,7 @@ "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.2.tgz", "integrity": "sha512-0XavAZbNJ5sDrCbkpWL8mia0o5WPOd2YGtxrEiZkBK9FjLppIUK2TgxK6qGD2P3hUXTJNNPVibrerKcx5WkR1g==", "license": "MIT", + "peer": true, "dependencies": { "@types/bonjour": "^3.5.9", "@types/connect-history-api-fallback": "^1.3.5", @@ -19719,6 +19730,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", diff --git a/src/components/Dashboard.js b/src/components/Dashboard.js index 2131a06..61b02ee 100644 --- a/src/components/Dashboard.js +++ b/src/components/Dashboard.js @@ -1,6 +1,6 @@ -import React from 'react'; -import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts'; -import { Banknote, Users, Activity } from 'lucide-react'; +import React, { useState } from 'react'; +import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, PieChart, Pie, Cell, Legend } from 'recharts'; +import { Banknote, Users, Activity, RefreshCw } from 'lucide-react'; // data generation const generateDummyData = () => { @@ -24,13 +24,55 @@ const generateTransactions = () => { return transactions; }; +// Generate product category data for pie chart +const generateProductCategoryData = () => { + return [ + { name: '电子产品', value: 40 }, + { name: '服装', value: 30 }, + { name: '书籍', value: 20 }, + { name: '其他', value: 10 }, + ]; +}; + +// Generate random product category data +const generateRandomProductCategoryData = () => { + const categories = ['电子产品', '服装', '书籍', '其他']; + const data = []; + let remaining = 100; + + for (let i = 0; i < categories.length - 1; i++) { + const value = Math.floor(Math.random() * remaining) + 1; + data.push({ name: categories[i], value }); + remaining -= value; + } + + data.push({ name: categories[categories.length - 1], value: remaining }); + return data; +}; + const Dashboard = () => { const chartData = generateDummyData(); const transactions = generateTransactions(); + const [productCategoryData, setProductCategoryData] = useState(generateProductCategoryData()); + + // Colors for the pie chart + const COLORS = ['#0088FE', '#00C49F', '#FFBB28', '#FF8042']; // Calculate average monthly revenue const totalSales = chartData.reduce((sum, data) => sum + data.sales, 0); const averageMonthlyRevenue = Math.round(totalSales / chartData.length); + + // Handle pie chart click event + const handlePieClick = (data) => { + const totalAmount = 10000000; // Assuming total sales is 10,000,000 KSh + const categoryAmount = (data.value / 100) * totalAmount; + alert(`${data.name}销售额占比 ${data.value}%,金额 ${categoryAmount.toLocaleString()} KSh`); + }; + + // Handle refresh data button click + const handleRefreshData = () => { + setProductCategoryData(generateRandomProductCategoryData()); + }; return (
@@ -78,6 +120,44 @@ const Dashboard = () => {
+ {/* Product Category Chart */} +
+
+

Product Category Distribution

+ +
+
+ + + `${name} ${(percent * 100).toFixed(0)}%`} + onClick={handlePieClick} + > + {productCategoryData.map((entry, index) => ( + + ))} + + `${value}%`} /> + + + +
+
+ {/* Transactions Table */}

Recent Transactions