「产品经理用 Claude 实现产品」系列 · 第16篇
产品经理最擅长用数据说话。以前你做周报时手动从 Excel 拉数据做图表,现在自己的产品里就有实时更新的数据看板。本篇用 Claude 接入 ECharts,实现需求状态分布饼图、趋势折线图、优先级分布柱状图,把「需求管理平台」变成真正的产品数据驾驶舱。
一、前言:产品经理最熟悉的工作就是做数据汇报
每周一的产品周会------你打开 PPT,翻出上周的数据,一项一项填进图表里。
总需求数多少、新增多少、完成率多少......你用 Excel 拖来拉去,复制粘贴,调颜色,花了1个小时做了一张本来5分钟就应该有答案的图。
如果你的工具自己会统计、会出图,会实时更新,你每周节省的不只是1小时。
这篇我们做的事,对产品经理来说太熟悉了:做数据看板。区别是:以前你用 Excel 手工做,现在让你的产品自动生成。
💡 本系列全程使用 weelinking 访问 Claude,国内可稳定使用
二、后端准备:统计数据接口
做数据看板,第一步是想清楚:我要看哪些数据?
作为产品经理,这个问题你最有发言权。
2.1 定义统计指标
我们的「需求管理平台」需要以下统计数据:
概览指标(数字卡片):
- 需求总数
- 本周新增数(本周一到今天)
- 进行中的需求数
- 整体完成率
分布数据(图表):
- 各状态分布(待评审/开发中/测试中/已完成/已关闭)
- 各优先级分布(P0/P1/P2/P3)
- 过去 30 天每日新增趋势
2.2 实现统计接口
你: 帮我实现统计接口
GET /api/stats,数据库用 SQLite(better-sqlite3)。需要返回:
overview:总需求数、本周新增数(从本周一 00:00 开始)、进行中数量(status 为 'developing' 或 'testing' 的)、完成率(status='completed' 的数量 / 总数量)
statusDistribution:按状态分组,返回每种状态的数量,格式[{ status: 'pending', count: 12, label: '待评审' }]
priorityDistribution:按优先级分组,返回每种优先级的数量和其中已完成的数量(用于计算完成率),格式[{ priority: 'P0', total: 5, completed: 3 }]
dailyTrend:过去30天,每天新增的需求数量,格式[{ date: '2024-03-01', count: 3 }],没有数据的日期也要包含(count: 0)
Claude 生成的接口核心代码:
javascript
// src/controllers/statsController.js
const db = require('../models/db');
const getStats = (req, res) => {
// 1. 总览数据
const total = db.prepare('SELECT COUNT(*) as count FROM requirements WHERE deletedAt IS NULL').get();
const thisMonday = new Date();
thisMonday.setDate(thisMonday.getDate() - thisMonday.getDay() + 1);
thisMonday.setHours(0, 0, 0, 0);
const weekNew = db.prepare(
'SELECT COUNT(*) as count FROM requirements WHERE createdAt >= ? AND deletedAt IS NULL'
).get(thisMonday.toISOString());
const inProgress = db.prepare(
"SELECT COUNT(*) as count FROM requirements WHERE status IN ('developing', 'testing') AND deletedAt IS NULL"
).get();
const completed = db.prepare(
"SELECT COUNT(*) as count FROM requirements WHERE status = 'completed' AND deletedAt IS NULL"
).get();
const completionRate = total.count > 0
? Math.round((completed.count / total.count) * 100)
: 0;
// 2. 状态分布
const statusLabels = {
pending: '待评审',
developing: '开发中',
testing: '测试中',
completed: '已完成',
closed: '已关闭'
};
const statusDist = db.prepare(
'SELECT status, COUNT(*) as count FROM requirements WHERE deletedAt IS NULL GROUP BY status'
).all();
const statusDistribution = statusDist.map(item => ({
...item,
label: statusLabels[item.status] || item.status
}));
// 3. 优先级分布
const priorityDist = db.prepare(`
SELECT priority,
COUNT(*) as total,
SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) as completed
FROM requirements
WHERE deletedAt IS NULL
GROUP BY priority
ORDER BY priority
`).all();
// 4. 每日趋势(过去30天)
const last30Days = [];
for (let i = 29; i >= 0; i--) {
const d = new Date();
d.setDate(d.getDate() - i);
last30Days.push(d.toISOString().split('T')[0]);
}
const dailyCounts = db.prepare(`
SELECT DATE(createdAt) as date, COUNT(*) as count
FROM requirements
WHERE createdAt >= datetime('now', '-30 days')
AND deletedAt IS NULL
GROUP BY DATE(createdAt)
`).all();
const dailyMap = Object.fromEntries(dailyCounts.map(r => [r.date, r.count]));
const dailyTrend = last30Days.map(date => ({
date,
count: dailyMap[date] || 0
}));
res.json({
success: true,
data: {
overview: {
total: total.count,
weekNew: weekNew.count,
inProgress: inProgress.count,
completionRate
},
statusDistribution,
priorityDistribution: priorityDist,
dailyTrend
}
});
};
产品经理视角: 这段 SQL 查询,就是你以前在 Excel 里用 COUNTIF、SUMIF 做的事------只是现在换成了数据库语言,Claude 帮你写。
三、接入 ECharts
3.1 为什么选 ECharts
市面上的图表库很多,我们选 ECharts 的原因:
| 图表库 | 优势 | 适合场景 |
|---|---|---|
| ECharts(百度) | 功能最全,中文文档友好 | 数据看板、复杂图表 |
| Ant Design Charts | 和 Antd 集成最好 | Antd 项目快速接入 |
| Recharts | 轻量,React 友好 | 简单图表 |
| Chart.js | 通用,文档丰富 | 基础图表 |
我们选 ECharts ------ 功能最丰富,产品经理做汇报最常见的图它都有。
3.2 安装配置
你: 帮我在 React 项目里接入 ECharts。安装
echarts和echarts-for-react,创建一个通用的Chart组件,接收option(ECharts 配置)和style(宽高)作为 props,组件要支持图表自适应容器宽度(使用echarts-for-react的onChartReady回调注册 resize 监听)。
jsx
// src/components/Chart.jsx
import ReactECharts from 'echarts-for-react';
const Chart = ({ option, style = { height: 300 }, loading = false }) => {
return (
<ReactECharts
option={option}
style={style}
showLoading={loading}
opts={{ renderer: 'canvas' }}
notMerge={true}
lazyUpdate={true}
/>
);
};
export default Chart;
四、数据概览卡片
先做最重要的4个指标卡片------这是 dashboard 的"第一眼"。
你: 帮我做4个统计卡片组件,展示:总需求数、本周新增、进行中、完成率。
每个卡片样式:白色背景、圆角、阴影、内边距24px。分为两行:上行是标题(14px,灰色)和一个小图标(emoji);下行是数字(32px,加粗,黑色)和同比(可选,绿色↑/红色↓)。
4个卡片水平排列,适应不同屏幕宽度(移动端2列)。
jsx
// src/components/StatsOverview.jsx
const StatsCard = ({ title, value, icon, suffix, trend }) => (
<div className="stats-card">
<div className="stats-card__header">
<span className="stats-card__title">{title}</span>
<span className="stats-card__icon">{icon}</span>
</div>
<div className="stats-card__body">
<span className="stats-card__value">
{value}
{suffix && <span className="stats-card__suffix">{suffix}</span>}
</span>
{trend && (
<span className={`stats-card__trend ${trend > 0 ? 'up' : 'down'}`}>
{trend > 0 ? '↑' : '↓'} {Math.abs(trend)}%
</span>
)}
</div>
</div>
);
const StatsOverview = ({ data }) => (
<div className="stats-overview">
<StatsCard title="需求总数" value={data.total} icon="📋" />
<StatsCard title="本周新增" value={data.weekNew} icon="✨" />
<StatsCard title="进行中" value={data.inProgress} icon="🚀" />
<StatsCard title="完成率" value={data.completionRate} suffix="%" icon="✅" />
</div>
);
五、需求状态分布------饼图
你: 用 ECharts 实现需求状态分布饼图:
- 甜甜圈样式(环形饼图,内半径50%,外半径70%)
- 中心显示总需求数
- 颜色:待评审=橙色、开发中=蓝色、测试中=紫色、已完成=绿色、已关闭=灰色
- 图例在右侧,每条图例显示名称和数量
- 悬浮时显示 tooltip:名称、数量、占比
- 动画:从底部顺时针展开(animationEasing: 'cubicInOut')
javascript
// 状态分布饼图的 ECharts option
const getStatusChartOption = (data) => ({
tooltip: {
trigger: 'item',
formatter: '{b}: {c} ({d}%)'
},
legend: {
orient: 'vertical',
right: 10,
top: 'center',
formatter: (name) => {
const item = data.find(d => d.label === name);
return `${name} ${item?.count || 0}`;
}
},
series: [{
type: 'pie',
radius: ['50%', '70%'],
center: ['40%', '50%'],
avoidLabelOverlap: false,
label: {
show: true,
position: 'center',
formatter: () => `总计\n${data.reduce((s, d) => s + d.count, 0)}`,
fontSize: 16,
fontWeight: 'bold'
},
emphasis: {
label: { show: true, fontSize: 18 }
},
data: data.map(item => ({
value: item.count,
name: item.label,
itemStyle: {
color: {
'待评审': '#F59E0B',
'开发中': '#3B82F6',
'测试中': '#8B5CF6',
'已完成': '#10B981',
'已关闭': '#9CA3AF'
}[item.label]
}
})),
animationType: 'scale',
animationEasing: 'cubicInOut'
}]
});
产品经理的感慨: "这不就是我每周截图给老板看的那个图吗?以前要手动拉数据,现在实时自动生成。"
六、需求趋势------折线图
你: 用 ECharts 实现过去30天需求新增趋势折线图:
- X 轴:日期(只显示每5天一个刻度,格式 MM/DD)
- Y 轴:数量(整数,从0开始)
- 折线:蓝色,线宽2px,有数据点(直径4px)
- 区域填充:蓝色渐变,从上到下透明度从0.3降到0
- 悬浮 tooltip:显示日期和数量
- 加一条7日移动平均线(橙色,虚线),帮助看趋势而不是看抖动
javascript
// 趋势折线图的 ECharts option
const getTrendChartOption = (data) => {
const dates = data.map(d => d.date.slice(5)); // 只取 MM-DD 部分
const counts = data.map(d => d.count);
// 计算7日移动平均
const movingAvg = counts.map((_, i) => {
if (i < 6) return null;
const slice = counts.slice(i - 6, i + 1);
return (slice.reduce((a, b) => a + b, 0) / 7).toFixed(1);
});
return {
tooltip: {
trigger: 'axis',
formatter: (params) => {
const date = params[0].axisValue;
const count = params[0].value;
const avg = params[1]?.value;
return `${date}<br/>新增:${count} 个${avg ? `<br/>7日均值:${avg}` : ''}`;
}
},
xAxis: {
type: 'category',
data: dates,
axisLabel: {
interval: 4, // 每5天显示一个
formatter: (val) => val
}
},
yAxis: {
type: 'value',
minInterval: 1
},
series: [
{
name: '每日新增',
type: 'line',
data: counts,
smooth: true,
lineStyle: { color: '#3B82F6', width: 2 },
itemStyle: { color: '#3B82F6' },
areaStyle: {
color: {
type: 'linear', x: 0, y: 0, x2: 0, y2: 1,
colorStops: [
{ offset: 0, color: 'rgba(59, 130, 246, 0.3)' },
{ offset: 1, color: 'rgba(59, 130, 246, 0)' }
]
}
}
},
{
name: '7日均值',
type: 'line',
data: movingAvg,
smooth: true,
lineStyle: { color: '#F59E0B', width: 2, type: 'dashed' },
itemStyle: { color: '#F59E0B' },
symbol: 'none'
}
]
};
};
七、优先级分布------柱状图
你: 用 ECharts 实现优先级分布柱状图:
- X 轴:P0、P1、P2、P3
- Y 轴:数量
- 每个优先级显示两根柱子:总数(蓝色,半透明)和已完成(绿色,实心),叠放展示
- 柱子上方显示完成率(绿色文字,百分比)
- 颜色约定:P0=红色、P1=橙色、P2=蓝色、P3=灰色,柱子颜色与优先级对应
javascript
const getPriorityChartOption = (data) => ({
tooltip: {
trigger: 'axis',
axisPointer: { type: 'shadow' },
formatter: (params) => {
const priority = params[0].axisValue;
const item = data.find(d => d.priority === priority);
const rate = item ? Math.round(item.completed / item.total * 100) : 0;
return `${priority}<br/>总数:${item?.total || 0}<br/>已完成:${item?.completed || 0}<br/>完成率:${rate}%`;
}
},
xAxis: {
type: 'category',
data: ['P0', 'P1', 'P2', 'P3']
},
yAxis: { type: 'value', minInterval: 1 },
series: [
{
name: '总数',
type: 'bar',
data: data.map(d => ({
value: d.total,
itemStyle: {
color: { P0: '#FEE2E2', P1: '#FEF3C7', P2: '#DBEAFE', P3: '#F3F4F6' }[d.priority],
borderColor: { P0: '#EF4444', P1: '#F59E0B', P2: '#3B82F6', P3: '#9CA3AF' }[d.priority],
borderWidth: 1
}
}))
},
{
name: '已完成',
type: 'bar',
barGap: '-100%', // 叠加在总数柱子上
data: data.map(d => ({
value: d.completed,
itemStyle: {
color: { P0: '#EF4444', P1: '#F59E0B', P2: '#3B82F6', P3: '#9CA3AF' }[d.priority]
}
})),
label: {
show: true,
position: 'top',
formatter: (params) => {
const item = data[params.dataIndex];
const rate = item ? Math.round(item.completed / item.total * 100) : 0;
return `${rate}%`;
},
color: '#10B981',
fontWeight: 'bold'
}
}
]
});
八、让图表可以交互
8.1 时间范围选择
你: 在趋势折线图上方,加一个时间范围选择器(按钮组):7天、30天、90天。
点击后,向后端传递
?days=7(或30/90)参数,后端重新查询对应天数的数据,前端图表更新。选中的按钮有蓝色背景,默认选30天。
jsx
const TrendChart = () => {
const [days, setDays] = useState(30);
const [data, setData] = useState([]);
useEffect(() => {
fetchTrend(days).then(setData);
}, [days]);
return (
<div>
<div className="chart-actions">
{[7, 30, 90].map(d => (
<button
key={d}
className={`btn-period ${days === d ? 'active' : ''}`}
onClick={() => setDays(d)}
>
{d}天
</button>
))}
</div>
<Chart option={getTrendChartOption(data)} />
</div>
);
};
8.2 数据导出
你: 在数据统计页面右上角,加一个"导出数据"按钮,点击后下载一个 CSV 文件,包含当前时间段内所有需求的关键字段:ID、标题、状态、优先级、创建人、创建时间、完成时间。
纯前端实现,不需要调接口,直接从已有的需求列表数据生成 CSV,用 Blob 和 a 标签下载。
javascript
const exportToCSV = (data, filename = 'requirements.csv') => {
const headers = ['ID', '标题', '状态', '优先级', '创建人', '创建时间'];
const rows = data.map(item => [
item.id,
`"${item.title.replace(/"/g, '""')}"`, // 处理标题里可能有逗号
item.status,
item.priority,
item.creatorName,
new Date(item.createdAt).toLocaleDateString('zh-CN')
]);
const csv = [headers.join(','), ...rows.map(r => r.join(','))].join('\n');
const blob = new Blob(['\uFEFF' + csv], { type: 'text/csv;charset=utf-8' }); // \uFEFF 解决中文乱码
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
a.click();
URL.revokeObjectURL(url);
};
九、组装统计看板页面
你: 帮我组装统计看板页面
src/pages/Dashboard.jsx:布局:
- 顶部:4个概览卡片(一排)
- 中间左:需求状态饼图(占一半宽度)
- 中间右:优先级分布柱状图(占一半宽度)
- 底部:趋势折线图(全宽,含时间范围选择器)
页面打开时统一请求
/api/stats,用 Promise.all 并发获取所有数据,全部加载完成前显示骨架屏。
jsx
// src/pages/Dashboard.jsx
const Dashboard = () => {
const [stats, setStats] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchAll = async () => {
try {
const res = await api.get('/stats');
setStats(res.data);
} catch (e) {
message.error('数据加载失败');
} finally {
setLoading(false);
}
};
fetchAll();
}, []);
if (loading) return <DashboardSkeleton />;
return (
<div className="dashboard">
<StatsOverview data={stats.overview} />
<div className="chart-row">
<div className="chart-half">
<Chart option={getStatusChartOption(stats.statusDistribution)} style={{ height: 320 }} />
</div>
<div className="chart-half">
<Chart option={getPriorityChartOption(stats.priorityDistribution)} style={{ height: 320 }} />
</div>
</div>
<TrendChart />
</div>
);
};
十、这就是你的数据驾驶舱
做完这一切,打开统计看板页面的那一刻:
实时更新的总需求数、本周新增趋势、各优先级的完成情况------全部自动生成,不需要你手动拉数据,不需要打开 Excel,不需要做 PPT。
以前你花一小时准备的产品周报数据,现在刷新一下页面,5秒就全有了。
这才是技术型 PM 的价值:不只是会提需求,而是能让产品自己"说话"。
十一、总结与下期预告
🎯 本篇核心要点
1. 数据看板从"想清楚要看什么"开始。 作为产品经理,你比任何人都清楚需要哪些指标。Claude 负责实现,你负责定义。
2. 后端 SQL 聚合查询 = 你在 Excel 里的 COUNTIF/SUMIF。 换了工具,逻辑没变。
3. ECharts option 交给 Claude 生成就好。 图表样式太多参数,不用背,用自然语言描述你想要的效果,Claude 帮你转成配置。
4. 时间范围可切换 + 数据导出 = 一个完整的数据工具。 这两个功能让你的看板从"展示用"变成"分析用"。
5. 骨架屏 + 并发请求 = 更好的加载体验。 数据加载期间不让用户看白屏,是产品设计的基本功。
📌 记住这句话
以前花一小时手动做的产品周报,现在刷新一下,5秒就全有了。这就是让产品"自己说话"的意义。
📣 下期预告
第17篇:《部署上线------从 localhost 到全世界》
统计看板做完了,「需求管理平台」的全部功能也完成了!
下一篇是最激动人心的时刻------部署上线。
从"在自己电脑上跑"到"发个链接给任何人都能访问",这一步对产品经理来说有特殊意义:你终于要亲手完成一次"上线"了。Vercel 部署前端、Railway 部署后端,全程免费,Claude 帮你准备配置文件。
💡 本系列全程使用 weelinking 访问 Claude,国内可稳定使用
📎 配套资源
📋 数据看板实现清单
□ 后端统计接口
□ GET /api/stats 接口实现
□ 总览数据(总数/本周新增/进行中/完成率)
□ 状态分布数据
□ 优先级分布数据(含已完成数)
□ 过去N天每日趋势数据(空日期补0)
□ 前端图表
□ ECharts + echarts-for-react 安装完成
□ 通用 Chart 组件(支持自适应宽度)
□ 概览卡片(4个指标)
□ 状态分布饼图(甜甜圈样式)
□ 趋势折线图(含7日移动平均线)
□ 优先级分布柱状图(含完成率)
□ 交互功能
□ 时间范围切换(7/30/90天)
□ 图表 tooltip 信息完整
□ 数据导出 CSV 功能
□ 体验优化
□ 加载时有骨架屏
□ 数据获取失败有错误提示
□ 移动端图表可正常显示
📋 ECharts 常用图表提示词模板
饼图:
用 ECharts 实现[名称]饼图:
- 样式:[普通饼图/甜甜圈形](内半径X%,外半径Y%)
- 数据格式:[{ name: '标签', value: 数值 }]
- 颜色:[每个分类对应的颜色]
- 图例位置:[右侧/底部/顶部]
- tooltip:显示[名称和数量/名称和占比]
- 中心显示:[总数/空]
折线图:
用 ECharts 实现[名称]折线图:
- X 轴:[日期/类别]数据
- Y 轴:[数量],从0开始,整数
- 折线颜色:[颜色],线宽[N]px
- 是否填充区域:[是/否],填充颜色[颜色]
- 是否平滑:[是/否]
- tooltip:显示[X值和Y值]
柱状图:
用 ECharts 实现[名称]柱状图:
- X 轴分类:[类别列表]
- 系列:[系列1(颜色)、系列2(颜色)]
- 排列方式:[并排/堆叠]
- 柱子上是否显示数值/百分比标签
- tooltip:显示[X值和Y值]
数字卡片:
帮我实现统计数字卡片组件:
- 显示:大数字(32px,加粗)+ 小标题(14px,灰色)+ 图标(emoji)
- 可选:趋势指示(↑N%或↓N%)
- 样式:白色背景、圆角12px、阴影、内边距24px
- 多个卡片水平排列,响应式(移动端2列)
🌟 如果这篇文章对你有帮助,请点赞👍 收藏⭐ 关注🔔
你的支持是我持续更新的最大动力!
💬 评论区聊聊:你在现在的工作中,有哪个数据是你每周都要手动整理的?这篇能帮你解放吗?
📌 系列导航: 产品经理用 Claude 实现产品 · 系列目录
⏪ 上一篇: 第15篇:UI/UX 打磨------产品经理的审美终于能自己实现
⏩ 下一篇: 第17篇:部署上线------从 localhost 到全世界
💡 本系列全程使用 weelinking 访问 Claude,国内可稳定使用🚀 整个系列的核心理念:你不需要变成程序员,你只需要从"找人做"变成"自己能做"。