
最近在给公司的进销存系统做数据升级业务方提了一个很朴素的需求“报表能不能做成能点的图表”以前这类系统里全是表格——进货单、销售单、库存明细一张张往下排。对账没问题但想看趋势、找异常、做经营决策就全靠人工在Excel里折腾。我花了两周时间把商品进销存系统的交互可视化图表从零搭了起来包括看板设计、统计口径梳理、图表选型、交互落地和性能调优。这篇文章就把整个过程完整复盘一遍适合正在做ERP、进销存、供应链看板的前后端开发、产品经理和数据分析师参考也适合想搞明白“图表到底解决什么问题”的业务负责人。刚接到需求时我下意识觉得“这不就是换个展示方式嘛”真正动手才发现一套能用的交互图表远不是把ECharts插件堆上去那么简单。它牵扯到三件事搞清楚业务方到底要看什么数据、把统计口径统一成一个标准、再把交互设计做到“看的人不用问别人就能看懂异常”。这篇文章按这个顺序展开最后附上我踩过的坑和排查清单可以直接照着用。1. 进销存里的数据到底哪些值得画成图进销存系统听起来复杂本质就三件事买进来、卖出去、剩多少。采购入库单记录“进”销售出库单记录“出”库存表记录“余”。这三类单据在业务量大的时候一天少则几十笔多则上万笔全堆在表格里看得人头大。业务方嘴里说的“好看一点”真实翻译其实是“我要一眼看出这个月赚没赚钱、哪个商品在积压、该不该补货、采购成本有没有涨”。搞清楚这个需求才轮到选图表库和写代码。1.1 从业务流程倒推看板需求做图表第一步不是打开代码编辑器而是回到业务流程里问清楚三个问题谁在看看完要做什么决定数据从哪来我分别找了采购、销售、仓管和财务聊了一圈发现四个角色关心的东西完全不一样。销售盯的是趋势和头部商品哪个卖得好、哪个在掉队采购关心的是进价变化和库存水位什么时候该补货、哪家供应商涨价了仓管最怕的是货堆在仓库里发霉库龄超过三个月没人管财务则只认毛利和周转率库存压了多少资金必须心里有数。把这些原始诉求翻译成指标才进入“画什么图”的阶段。这一步很容易被省略但它是整套图表的根省略的结果通常是做出一堆花花绿绿却没人看的图业务看完还得回去翻表格。我当时的做法是列一张“业务诉求—指标—图表”的对照表比如仓管想知道哪些商品三个月没动过对应的就是“库龄分布”用柱状图按0-30天、30-60天、60-90天、90天以上分段展示。再比如财务想看库存压了多少钱对应“库存金额TOP10品类”用横向条形图一眼扫过去就知道资金集中在哪里。需求不对齐后面全是白忙。1.2 一张图讲清楚 vs 一张表讲清楚这里得先给可视化泼一盆冷水不是所有数据都适合画成图。表格的强项是精确查询和核对比如查某张单据的状态、核对某个账户余额、对账时逐笔查看明细这些场景用表格最高效。图表的强项是看趋势、比重、分布和异常比如“这周销量比上周涨了还是跌了”“哪个品类撑起了八成销售额”“库存是不是越积越多”。判断标准其实很简单想回答“是多少”用表格想回答“怎么变化、什么结构、哪里异常”用图表。进销存里的单据流水、对账明细老老实实留在表格里销售额趋势、库存分布、毛利占比这些分析型数据做成图表才有意义。那段时间我们收到最多的无效需求就是“把这个明细表也做成图”我基本都劝回去了——一张几百行的流水表做成图信息密度反而暴跌用户根本看不出所以然。别为了可视化而可视化这是我看过太多失败项目之后最想强调的一点。1.3 初版图表清单六张图覆盖日常经营决策第一版我选了六张图分别对应销售、库存、采购三个模块。选图的原则只有一条每张图都必须能回答一个决策问题回答不了的直接砍掉。下面是初版的图表清单后面所有迭代都基于这个框架延伸。图表名称图表类型核心指标能回答什么问题销售趋势图折线图日/周销售额、订单量生意在涨还是跌活动有没有效果品类销售结构环形图各类目销售额占比收入靠哪个品类结构是否健康商品销售TOP10条形图销售额、销量谁是爆款谁该被淘汰库存余量分布柱状图当前库存量、库存金额货压在哪个仓库、哪个品类库存周转趋势组合图周转次数、周转天数库存有没有越积越多采购金额趋势折线图采购金额、平均进价采购成本是否异常上涨这六张图合在一起就是一套完整的经营看板销售看结果库存看资金占用采购看成本变化。做完这个清单我才意识到所谓“进销存可视化”本质是把进、销、存三个环节的数据放到同一个时间轴上让经营问题自己暴露出来。2. 技术选型与数据准备别让图表死在半路图纸有了接下来是工具和数据。这一部分如果没做好图表上线就是灾难——不是画不出来就是画出来的数值跟业务对不上然后被业务部门追着问“你这数据哪来的”。技术选型要综合考虑团队熟悉度、交互复杂度和社区支持数据准备则要重点解决统计口径不一致的问题这比写图表代码难得多。2.1 图表库怎么选ECharts、AntV、Highcharts 的取舍图表库选择上我把市面主流的几个方案都对比了一遍。ECharts是Apache开源的项目中文文档友好社区案例极其丰富交互组件最全dataZoom缩放、下钻联动、自适应布局都内置了5.x版本在渲染性能上提升非常明显还会根据场景自动选择Canvas或SVG渲染。AntV G2更偏统计图表语法适合做数据探索型产品可定制性强但上手门槛稍高团队不熟的话会拖慢进度。Highcharts老牌稳定兼容性好但商业使用有授权费用对预算敏感的中小团队不太友好。Chart.js体积小、上手快但只适合基础图表复杂的联动和钻取实现起来很吃力。D3.js几乎什么都能做但什么都要自己写开发成本最高一般项目没必要上。我的选择是ECharts 5理由很直接进销存看板要的折线图、柱状图、饼图、组合图、下钻联动ECharts全部自带遇到问题一搜一大把解决方案。而且和Vue、React的封装组件都很成熟团队上手成本低。中小团队的进销存项目选它性价比最高。如果你不做特别复杂的定制可视化ECharts几乎不会让你失望。2.2 数据接口设计口径统一是半条命图表做得再炫数据错了就是灾难。我和后端同学定了一条铁律表格和图表必须共用同一套统计服务严禁前端拿原始单据自己算也不允许后端另起一套统计逻辑。为了达成这个目标统计接口从一开始就设计成参数化而不是每个图表配一个硬编码接口。GET /api/v1/analytics/sales-trend { granularity: day, startDate: 2024-05-01, endDate: 2024-05-31, dimensions: [category], metrics: [gmv, order_cnt] }维度参数决定“按什么切”指标参数决定“返回什么数”时间范围统一控制。这样设计的好处是新的图表需求往往只需要组合已有参数后端不用每次重写SQL。比如要新增“按品牌看销售额”只需要把dimensions改成“brand”接口层基本不用动。返回结构里有两个特别容易踩的坑。第一日期必须做连续序列没有交易的天也要补零。很多统计服务只返回有销售记录的日期直接用这种数据画折线图中间会断掉一大截趋势完全没法看。第二金额字段统一用整数分存储、展示层再转成元避免浮点误差。曾经因为有人用前端JS把金额相加算出来的结果多了几分钱财务那边直接打了半小时电话过来问这个教训记忆犹新。2.3 关键指标计算的坑毛利、周转率、库存天数别算错进销存的指标看着简单细究全是坑。毛利等于实收金额减去销售成本但成本按移动加权平均还是月末一次加权结果能差出不少钱周转率有人按销售数量算有人按销售金额算分子分母口径不一致数字天差地别。所以所有指标必须在接口文档里写死口径并且在代码里加注释说明。指标统一口径示例常见分歧销售额(GMV)实收金额剔除退款和取消含不含税、含不含运费订单量支付成功的订单数是否剔除售后退款订单毛利实收金额 - 销售成本成本采用加权平均还是先进先出库存周转次数销售成本 / 平均库存分子按成本还是按售价库存周转天数平均库存 / 日均销售成本窗口期取30天还是90天周转天数这个指标我特别想提醒一句计算窗口不同同一批数据的答案完全不一样。业务问“我们的库存能卖几天”你得先跟财务对齐口径再把这个口径写进接口文档和前端tooltip里否则上线之后就会变成业务部门吵架的素材。我当时为这个指标和财务对了两轮最后统一成“近30天日均销售成本”作为分母并把这个口径直接标注在图表标题下方后续再也没有收到过质疑。3. 交互图表落地实录从静态图到能点能钻的仪表盘数据和服务理顺之后进入前端实现阶段。这一章是实际操作记录会贴出关键代码也会把当时踩过的坑一并写出来。3.1 第一版静态图表的搭建过程第一步先把一张销售趋势折线图跑通确认从接口到渲染整条链路没问题。我用的是ECharts 5加Vue 3初始化核心就两件事拿到数据、配置option。import * as echarts from echarts; const chart echarts.init(document.getElementById(salesTrend)); const dates res.data.map(item item.date); const values res.data.map(item item.gmv); const option { tooltip: { trigger: axis }, grid: { left: 70, right: 30, top: 40, bottom: 40 }, xAxis: { type: category, data: dates }, yAxis: { type: value, axisLabel: { formatter: (v) (v / 10000).toFixed(1) 万 } }, series: [{ name: 销售额, type: line, smooth: true, data: values, areaStyle: { opacity: 0.08 } }] }; chart.setOption(option);这里有几个细节直接影响使用体验。折线加了smooth和半透明的areaStyle视觉上比干巴巴的纯折线更容易跟踪趋势业务方反馈说“感觉更直观”。Y轴做了万元格式化不然销售额动辄几十万甚至上百万坐标轴标签会挤成一团。还有一个容易被忽略的是grid边距左侧留到70像素是为了容纳纵向轴标签如果写太小数值大一点的月份标签会被裁掉一半看起来非常业余。tooltip用trigger: axis而不是默认的item这样鼠标在图表上横向滑动时会同时显示当日所有系列的数据比只显示单个点好用得多。如果之后加了订单量系列只需要在series数组里再塞一项tooltip会自动跟着扩展不需要额外配置。3.2 让图表“活”起来时间范围联动、下钻、Tab切换静态图表能看但不好用业务方真正要的是“交互”。交互设计的第一原则是减少操作路径想对比上周的销售数据不应该让用户自己去翻日历而是做一个全局时间筛选器放在看板顶部所有图表随筛选条件自动刷新。这个组件我用了带快捷选项的日期范围选择器预设“近7天”“近30天”“本季度”“本年”业务人员基本不需要输入具体日期点点就完事。下钻是对“概览—定位—明细”链路的落地。点饼图的一个扇区就弹当前品类的商品销售排行点折线图的一个数据点就展开当天的订单明细。实现的核心逻辑是拿当前选中的维度值作为新接口的筛选条件再请求一次更细粒度的数据而不是在前端过滤已经加载的数据——后者受限于首屏数据量数据一多就会卡死。categoryPieChart.on(click, (params) { const category params.name; axios.get(/api/v1/analytics/category-top, { params: { category, startDate, endDate } }).then(res { categoryTopModal.show(res.data); }); });下钻这里有个细节我吃了亏弹层里的图表在关闭时一定要调用dispose()销毁否则多次打开关闭内存和事件监听会越积越多页面越来越卡。我第一版没做销毁连续点开十几个品类弹窗之后整个看板的交互明显变迟钝排查半天才定位到这个原因。合适的交互还包括Tab切换。我把六张图分成“销售分析”“库存分析”“采购分析”三个页签默认展示销售三张图通过切换让关注不同业务的角色快速进入自己的工作界面。一开始有同事建议把六张图全部放到一个页面上说“一眼全看到”实际试过之后发现信息密度过高每个图都变小反而看不清趋势。分组展示是更好的选择。3.3 移动端和字体缩放被忽略的交互细节第一版完成后运营同事在手机上打开看板发现图表缩成一团几乎没法看。ECharts默认在窄屏下不会自动适配有两件事必须处理一是图表容器必须有明确的宽度和高度用百分比设置时要保证父容器有高度二是监听窗口尺寸变化并调用resize否则图表会被拉伸变形或者留白。const resizeHandler debounce(() chart.resize(), 200); window.addEventListener(resize, resizeHandler);debounce后面跟的200毫秒延时很有必要。如果没有防抖浏览器窗口拉伸时会连续触发几十次resize重绘低端设备上会出现明显卡顿。另一个移动端细节是tooltip的触发方式默认的hover在触屏上基本无效要把触发改为点击或者在移动端把trigger配置为axis配合长按。对于长周期销售趋势我建议在移动端把dataZoom显示为底部滑块让用户滑动查看不同时间段。如果一段连续60天的销售曲线全部挤在手机屏上波动细节根本看不清加个滑块或者直接按周粒度聚合体验会好非常多。这类问题不涉及复杂技术但直接影响图表“有没有人愿意用”。4. 常见问题与排查技巧这套系统上线之后遇到的问题比我预想的多挑几个典型的写出来可以当排查手册用。4.1 数据不刷新、缓存卡住现象是库存明明出库了折线图却纹丝不动。一开始怀疑图表代码写错了后来排查发现两个原因。第一聚合统计数据走了定时任务每半小时刷新一次数据延迟属于设计如此不是故障。第二浏览器对统计接口做了HTTP缓存后端接口没有设置Cache-Control导致前端拿到的其实是旧响应。解决思路分两步。统计类接口默认设置Cache-Control: no-cache让浏览器每次带条件请求去做验证实时性要求高的页面用前端轮询比如30秒刷一次或者用WebSocket推送。不过这里更重要的沟通是和业务约定时效性预期日报级别的图表半小时刷一次完全够用没必要给每个图都上秒级实时。有过一次我花一天把轮询做成WebSocket推送结果业务方说“其实半小时看一次就够了”从那之后我先问时效性再做技术方案。4.2 图表卡顿与大数据量渲染优化当销售趋势图拉到一年时间跨度、数据点超过一两千个时ECharts渲染会明显变慢拖动和tooltip反馈都有延迟。我亲测有效的方案有三个。折线图开启sampling: lttb降采样。这是一种保留趋势轮廓的降采样算法会丢掉不重要的点实测几千个数据点时肉眼几乎看不出趋势差异但渲染性能提升很明显。多图表同时存在的看板不要一进入就全部渲染改成懒加载滚动到视口附近再初始化首屏秒开。长时间不用的图表一定要调用dispose()释放资源这个前面下钻部分也提到过不销毁的话页面迟早会被拖垮。4.3 图表和表格数字对不上“表格显示3500图表显示3200”是上线第一周被吐槽最多的问题。排查步骤其实很固定。第一步确认图表和表格是不是同一个统计服务因为我们系统里历史原因存在两套统计逻辑前端不小心接了旧的那套。第二步看时间范围是否一致尤其跨月、跨周的时候有的模块用的是自然周有的用业务周日期对不上数字自然对不上。第三步核对指标口径退款算不算、含税还是不含税。最后我们做了一个很笨但很有效的办法在图表tooltip里直接标注口径说明前端看板顶部也加一行小字写着“金额为实收销售额已剔除退款”。这个做法上线后关于对不上的投诉直接少了八成——很多所谓的数据对不上其实是看的人不知道口径怎么定义的。4.4 常见问题速查表症状可能原因解决方案折线图日期断点无交易日期未补零生成连续时间序列LEFT JOIN补零切换时间范围后图不刷新接口被HTTP缓存或组件未重新渲染接口设no-cachesetOption前清空旧数据窗口尺寸变化后图表变形缺少resize监听容器尺寸变化时调用chart.resize()手机端tooltip点不动默认hover触发不适用触屏改为item触发或配置底部dataZoom图表和表格数字对不上数据源或口径不一致统一统计服务tooltip标注口径页面越来越卡图表未dispose、数据点过多及时销毁图表、开启降采样、懒加载整套图表上线后最有成就感的不是图表多炫而是业务同事说了一句“我现在能看出问题在哪了”。我个人做完这个项目最大的感受是进销存的交互可视化图表真正的难点从来不在图表库的API而在于对业务指标的理解、数据口径的统一以及让图表真正嵌入到每天的工作流里。图表只是把已经存在的业务逻辑翻译成视觉语言翻译得准不准取决于你翻译之前对业务的理解够不够深。如果你刚开始做这块先别急着画图回去问自己三个问题这张图给谁看看完他要做什么决策数据从哪里来、口径是什么把这三个问题想清楚一套图表的核心就已经完成大半了。后续再根据业务反馈迭代你会发现自己做的图表会越来越“被需要”而不是躺在看板上吃灰。