From 9b8ed2dcc90dec6c847b1921cd6719f4f40c62d7 Mon Sep 17 00:00:00 2001
From: haoxinyan <19103470692@163.com>
Date: Sat, 4 Apr 2026 18:11:16 +0800
Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E7=8E=AF=E5=BD=A2?=
=?UTF-8?q?=E5=9B=BE=E5=8F=8A=E7=82=B9=E5=87=BB=E5=BC=B9=E7=AA=97=E3=80=81?=
=?UTF-8?q?=E5=88=B7=E6=96=B0=E6=95=B0=E6=8D=AE=E5=8A=9F=E8=83=BD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
DEVELOPMENT.md | 126 ++++++++++++++++++++++++++++++++++++
package-lock.json | 32 ++++++---
src/components/Dashboard.js | 86 +++++++++++++++++++++++-
3 files changed, 231 insertions(+), 13 deletions(-)
create mode 100644 DEVELOPMENT.md
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