☰
进销存可视化看板实践:从图表选型到交互设计的完整复盘
2026/10/7 17:39:54 网站建设 项目流程

最近给朋友的一家商贸公司收拾后台,他们仓库里管着上千个SKU,采购、销售、库存全挤在Excel里。月初对账能把人对到怀疑人生,老板要的“这个月卖得怎么样”没人能三句话讲清楚。所以新系统里,我坚持加了一个“可视化看板”模块,这也是我第一次正经做商品进销存系统的交互式图表。做完之后最大的感受是:图表本身不难,难的是让图表真的回答业务问题。这篇就当是初体验的复盘,把需求拆解、图表选型、交互设计、接口联调还有踩过的坑一块儿捋一捋,给准备做同类功能的兄弟一个参考。

项目背景很简单:采购入库有单据,销售出库有单据,库存表天天在变,但老板看报表只看三个问题——库存压了多少钱、哪些商品卖不动、哪些商品该补货。原来的表格系统这些数都有,就是看不出趋势和异常,一个数字错了要翻三层表格才能定位。所以我这次的目标不是做漂亮的“大屏”,而是做能交互、能下钻、能定位问题的经营看板,把进销存数据变成一眼能看懂的东西。

1. 看板功能规划与数据口径

1.1 先弄清楚老板到底想看什么

做可视化之前,我以为难点在选图表库、调色、做动效,真开工了才发现,最花时间的居然是“确定指标口径”。说白了,就是每个数字怎么算、取哪张表的数据、含不含税、退换货算不算进去。这些不在一开始定死,后面每个图表都可能返工。

我第一版直接奔着“酷炫”去,做了十几个图表,什么雷达图、热力图全往上堆。结果朋友看了一眼说:“这一屏我看不懂,你就告诉我库存超了没有。”所以第二版我做了减法,把看板收敛成四个核心区域:库存总览、销售趋势、品类结构、商品排行,再配一个低库存和高库存的预警列表。这四块基本覆盖了进销存系统里最常被问的问题——仓库里有什么、卖得怎么样、什么好卖、什么该补。

这里有个很实用的经验:跟业务方确认口径的时候,不要只问“销售额怎么算”,要拿着商品明细问“七天无理由退货的订单算不算销售”“预售的订金算不算收入”。这些对照表做出来之后,前后端开发、产品、业务各拿一份,比口头对齐靠谱一百倍。

1.2 图表模块怎么拆

我当时把整个看板拆成两层:第一层是“总览层”,放销售总额、采购总额、库存总额、动销率这几个关键卡片,再配两三个大趋势图;第二层是“明细层”,点总览上的任意一个卡片或者趋势图上的某一天,能自动钻到对应的商品列表和单据列表。这个设计的好处是老板日常只需要看第一屏,发现问题后再点进去定位,不会一上来就被几百行的表格淹没。

模块拆分还有一个隐藏好处:图表之间的数据依赖被切断了。销售趋势图挂了,不影响库存预警列表展示;后端接口也有独立的缓存,不会因为一个慢查询拖垮整个看板。我见过不少项目把所有的可视化数据塞到一个大接口里,前端拿回来再自己拆,一旦数据量上来,首屏loading能转十秒,体验非常差。

2. 图表选型:每个指标用对图

2.1 销售趋势:折线图配时间轴

销售趋势我选了折线图,这个基本没有争议。折线图天生适合展示连续时间维度上的变化,走势、拐点、季节性一眼就能看出来。但有个细节要注意:折线图横轴的时间粒度,必须跟后端查询的粒度完全一致。前端显示“每日”“每周”“每月”,后端接口就要支持对应的聚合维度,否则切换粒度的时候,前端拿到的还是原来的汇总数据,图会直接画错。

实现的时候,我用的是ECharts的折线图,xAxis的type可以根据时间粒度切换,日粒度用category,月粒度也用category,只是后端返回的label不同。这里不要偷懒用time类型,因为进销存数据不是均匀采样的,周末可能没有销售,time类型会把空日期也画成0值,干扰判断。用category类型,只显示实际有数据的时间点,趋势看上去更真实。

还有一点关于面积图:如果销售数据波动不大,可以给折线加一个透明渐变面积,视觉上更有“量级感”;但如果同一张图里要对比“销售额”和“订单数”两组数据,就别用面积了,叠加起来会糊成一团。我当时还加了一个平滑曲线,结果被朋友吐槽“数据被美化了”,后来改回折线,让拐点锐利一些,反而不容易误导人。

2.2 品类结构:环形图比饼图更好

品类销售占比,第一反应是饼图,但实际做下来环形图更合适。环形图中间的空白区域可以放总销售额或者总占比,信息密度更高;涉及多个品类时,环形图的色块面积也更直观,不会像饼图那样挤在一起。

这里有个容易踩的坑:如果某个品类销售额占比低于某个小数值,图例还是会在,只是扇区小到看不见,鼠标悬停也很难选中。我们后来在接口层就做了处理,占比小于1%的品类统一合并成“其他”,前端图例里只显示较大品类。这样既保留了整体分布,又不会让图表变成“一坨细碎的色带”。

配色方面,我一开始直接用了ECharts默认色板,好几个颜色肉眼区分度很差,尤其是蓝色和绿色偏暗的时候。后来我换成了固定的业务配色:销售用暖色系,库存用冷色系,预警用红色系。这个习惯延续到后面所有图表,老板看久了颜色就知道什么类型的信息在变化,比任何图例都管用。

2.3 商品排行:条形图比柱状图更顺手

商品销售排行,很多新手会默认用纵向柱状图,但商品名称通常很长,纵向柱状图的x轴标签会挤成一坨斜线,根本看不清。我后来统一改成了横向条形图,商品名称放在y轴,销售额放在x轴,标签空间大了不止一倍,按金额排序后谁是销冠、谁是吊车尾,一眼扫完。

这里还要注意排序逻辑不要写反。我看过一些后台,销售额从高到低排,但图例顺序却从低到高,导致“第一名”永远在图表底部,非常反直觉。所以我在前端里定了一条规矩:排行类图表一律按数值降序排列,并且展示前10名,后面加一个“查看全部”跳转到明细列表。进销存排行,本质上是为了识别重点商品,不是真的为了让你数完所有商品。

Top N的场景里还有一种情况,就是并列排名。有两个商品销售额完全一样时,后端如果只按金额排序,顺序会不稳定,比如第一次A在B前面,第二次B在A前面,用户会以为图表“抽风”。我后来在排序字段里加了第二个条件——按商品编码升序,保证并列时结果始终稳定。

2.4 库存预警:状态比图形更管用

库存预警这部分,我用的是表格加状态色,没有强行上图表。很多可视化方案喜欢把库存预警画成仪表盘,一个半圆表盘显示“库存健康度”,说实话对实际业务帮助不大。仓库管理需要的是“哪些商品要补货”“哪些商品积压了”,不是“整体健康指数”。

我做的预警列表是这样的:低于安全库存的商品标红,高于最高库存限制的标橙,正常区间的不显示或默认灰显。每一项都带“库存天数”这个指标——当前库存除以最近30天平均日销,算出来的数字直接告诉老板这批货还够卖几天。这个比单纯看库存绝对值有用得多,因为库存绝对值1000件到底多不多,得看它每天卖多少。

预警规则在系统里做成可配置的,每种商品有自己的安全库存和最高库存,后台可以按SKU单独调,也可以按分类批量设置。这里前端要做的是只展示后端算好的“预警结果”,不要在浏览器里自己判断,否则每次规则变更都要改一次前端代码,维护成本太高。

3. 交互细节:让图表会说话

3.1 全局时间筛选器和粒度切换

交互可视化跟纯报表最大的区别,就是用户能主动改变视角。我在看板右上角放了一个全局时间筛选器,支持“最近7天”“最近30天”“本季度”“本年”几个常用范围,还放了一个粒度切换按钮,日/周/月可以随时切。所有图表都监听这个筛选器的变化,重新拉数据后刷新。

这里的实现思路是:把筛选条件放进一个统一的状态对象里,比如{ dateRange: ['2024-01-01', '2024-01-31'], granularity: 'month' },任何一个图表组件只关心这个状态,不关心筛选器UI在哪里。Vue里我用了一个简单的provide/inject,React里可以用Context,本质上就是让图表订阅同一个“数据源状态”。

一开始我犯过一个错:把时间筛选写死在某一个图表组件内部,结果其他图表没法联动更新。后来不得不重构,把所有筛选条件提升到看板顶层,再往下传。这个教训值一次返工。

3.2 点击联动:从趋势图钻到明细

交互可视化的另一个重点是“钻取”。我实现的联动是:销售趋势图上,用户点击某个月的柱子,下方商品排行就自动切换成“该月销冠排行”,库存预警列表也切换成“该月动销数据”。这个功能在老板问“上个月到底什么卖得好”的时候特别有用,不用再手动拉一堆筛选条件。

ECharts的点击事件是通过chart.on('click', params => {})拿到的,params里面包含横轴时间点、系列名、数值。拿到时间点后,我把它拼进查询参数,重新调后端接口。这里要给接口预留startDate和endDate参数,前端只需要改这两个值。

联动还有一个细节:点击之后要给用户一个明确的“当前状态”反馈,比如被点击的柱子高亮,其他柱子变淡,筛选条件同步显示在页面上。否则用户点了一下没反应,或者数据变了但不知道为什么变,会觉得系统不稳定。

3.3 tooltip和横轴标签的打磨

进销存系统里,图表的tooltip不是鼠标悬停显示数值那么简单。我复写了绝大部分tooltip的formatter,把单位、符号、关键计算逻辑都塞进去。比如库存总览的tooltip,除了显示“库存金额”,还会加一行“较上周变动:+12.3%”;销售趋势图的tooltip,则显示“订单数”和“客单价”,让看的人不用来回对比就能拿到完整信息。

横轴标签也值得花时间。日粒度展示时,如果横轴跨了90天,默认每个点都显示日期,标签会重叠。我做了按刻度跳显,只显示每7天的标签,其余留空;切换成月粒度时,标签显示“2024-01”“2024-02”这种短格式。这些看起来是细节,实际体验差别非常大,尤其是给老板演示的时候,标签糊成一团很尴尬。

y轴的单位格式也要统一。金额类数值超过1万,我显示成“1.2万”,超过1亿显示“1.05亿”,用格式化函数统一处理,不能有的图上万有的图直接显示一长串数字。后端返回的是分还是元,在接口文档里写死,前端格式化之前先除以100,避免因为单位不一致导致图表数字偏差。

3.4 空数据和加载态:不画错的图比画好看的图重要

做进销存可视化,空数据是家常便饭。新店刚开业还没有销售记录,某个月份没有任何采购出入库,筛选时间段内完全没有数据……如果接口返回空数组,ECharts默认会画一个空坐标系,横纵轴还在,但没有任何内容。更坑的是,有些图表会默认显示一条数值为0的线,让老板误以为销量暴跌到零。

我的处理方案是:前端在拿到空数组后,不用ECharts渲染,而是直接渲染一个“暂无数据”的占位图,同时把横纵轴隐藏。这样至少不会给出错误的视觉暗示。另外,如果图表是“部分缺失”,比如有的月份有数据,有的没有,后端在聚合时就要把缺失月份补成null而不是0,前端遇到null直接断线,不要连到点,不要当成0值。

加载态也不能忽视。每个图表从点击筛选到数据回来,可能需要几百毫秒。我做了统一的loading效果,每个图表组件在请求期间显示一个轻量的转圈,数据到齐后再整体渲染。一开始图省事,只在页面顶部加一个全局loading,结果用户改了筛选之后整个页面白屏,体验很差。

4. 前后端联调的关键环节

4.1 后端聚合接口怎么设计才能支撑图表

图表数据和明细数据不一样,图表要的是“聚合结果”,不是列表。后端如果直接把明细表吐给前端,让前端用JS做sum、groupBy,数据量小的时候没感觉,数据量大了浏览器直接卡死。所以凡是图表要用的数,我都让后端在SQL里聚合完,前端只负责展示。

我设计了几个专用接口:销售趋势接口按时间粒度返回汇总、品类占比接口返回分类汇总、商品排行接口返回Top N、库存总览接口返回库存金额和预警数量。每个接口的返回结构尽量扁平,比如销售趋势就长这样:

{ "code": 0, "data": [ { "date": "2024-01-01", "salesAmount": 12345.00, "orderCount": 86 }, { "date": "2024-01-02", "salesAmount": 9876.50, "orderCount": 71 } ] }

接口名是dashboard.salesTrend,接收startDate、endDate、granularity、storeId四个参数。storeId是为了后续多门店扩展预留的,这次单门店用默认值,但字段先留着,省得后期改接口。

4.2 前端拿到数据后的二次加工

后端聚合得很好,前端依然要做一些必要的加工。第一是格式化,金额、数量、百分比都要统一格式,这个前面已经提过。第二是排序,后端在SQL里已经排好序,但前端图表组件有时会默认按照系列名排序,我需要强制按数据顺序映射。

第三是计算派生指标。比如动销率、库存天数、环比变化这些,可以在后端算,也可以在前端算。我的原则是:需要业务规则判断的(比如库存预警等级),必须在后端算;纯展示类的(比如环比百分比),可以在前端算。因为预警等级和业务规则绑定,后端改规则前端不用动;而环比只是(本月 - 上月) / 上月这种数学运算,前端算省一次请求。

这里要注意除法坑:环比基数是0的时候,百分数会变成infinity。我在前端对这种情况做了兜底,基数为0时展示“新增”而不是“+infinity%”。还有金额精度,JS用浮点数算钱容易出精度问题,我统一用“分”做整数运算,展示时才转成元,这样不会出现“0.1 + 0.2 = 0.30000000000000004”。

4.3 刷新策略和数据缓存怎么做合理

进销存系统的看板数据,不需要跟交易事务保持实时同步。我设置了两个刷新机制:进入看板页面时拉一次最新数据,然后每5分钟自动轮询一次。5分钟这个值我是照抄了后台运营的经验——太频繁后端扛不住,太慢了老板看到的“今日销售额”会显得不新鲜。

轮询还有一个好处:自动纠正其他操作导致的数据偏差。比如有人在前台开了一张销售单,库存减了,看板5分钟内会自动更新。我用的是setInterval配合页面可见性监听,tab切到后台时就暂停轮询,切回来再立刻刷新,省资源又能保证数据相对新鲜。

缓存方面,后端给图表接口加了Redis缓存,key按时间范围+粒度+storeId拼接,过期时间设为60秒。这样同一个图表快速切换筛选条件时,接口可以命缓存的,不会每次都打MySQL。当然缓存时间不能太长,否则用户改了商品资料之后看板迟迟不变,容易误以为系统出问题了。

5. 常见问题与排查技巧

5.1 ECharts容器大小和resize的坑

做可视化图表一定会遇到的经典问题:图表在页面加载时容器宽度是0,或者容器被折叠/隐藏,初始化后图表就渲染不出来或宽度不对。我遇到的场景是看板Tab切换,从“库存”Tab切到“销售”Tab时,销售图经常显示成一行细线。

原因是ECharts在init的时候读取了容器尺寸,容器当时是隐藏的,读取到0,后面就算容器显示了,图表也不会自动重绘。解决方案是:在Tab切换完成、容器真正显示之后,调用chart.resize();如果容器是动态宽度的,还要配合window.resize事件做监听。更稳妥的做法是,只在容器可见时初始化图表,而不是在组件mounted时无脑init。我现在封装图表组件时,多了一个visibleprop,容器不可见时直接返回一个占位div,不做init。

5.2 图例和数据的颜色对不上

有一次用户反馈说,品类占比图里“饮料”是红色,但同一个商品在商品排行图里“饮料”又变成了绿色。原因是图表库的默认色板是自动分配的,每次渲染都按照数据顺序重新分配颜色,同一个品类在不同图表里颜色不一致。

解决这个问题我用了一个统一色板映射:后端返回品类时带一个categoryCode,前端维护一张品类编码 -> 固定颜色的映射表,不在表里的用默认灰色。这样同一个品类在任何图表里都是同一个颜色,老板看习惯之后甚至不需要看图例,看到红色就知道是饮料区。分类不多的时候这个方法很简单,分类很多就要做成配置化了。

5.3 指标口径对不齐,跟财务对不上账

这是进销存系统做可视化最容易炸雷的地方。销售金额到底是含税还是不含税?退货单是冲减销售额还是单独一列?采购入库的成本价是加权平均还是先进先出?我第一版用“含税销售额”,财务那边报表示口径是“不含税”,结果老板拿着我的看板和财务报表对比,差了十几个点,差点以为系统出了bug。

排查过程很曲折,最后发现就是口径问题。解决方案是把口径做成系统级配置,配置项写清楚每个指标的计算规则,而且图表标题旁边加了一个信息图标,鼠标悬停显示“销售金额=已支付订单金额合计,不含税费”。这样业务方自己就能看懂,不用每次找开发对口径。

5.4 loading过多导致页面卡顿

看板页面一般至少五六个图表,如果每个图表独立发请求、独立渲染,首屏会同时发起六七个请求,接口慢的时候浏览器并发连接数打满,后面的请求全部排队,页面就会卡顿。我发现这个问题是在一个网络较慢的环境下实测,加载时间居然到了8秒。

后来我做了两件事:一是给请求分组,首屏先拉“库存总览”和“销售趋势”两个核心图表,其他图表等这两个返回后再拉,让用户最先看到最重要的内容;二是给每次请求做一个简单的防抖合并,用户快速切换筛选条件时,只取最后一次条件发起请求,避免疯狂刷新图表。做了这两步之后,页面加载从8秒降到3秒左右,体验好了非常多。

5.5 避坑速查表

问题现象原因解决
折线图横轴日期错乱周末显示为0time类型轴自动补点改用category轴,只显示实际数据日期
图例颜色不稳定同一品类颜色不同色板自动分配建立品类颜色映射表
图表在Tab切换后变窄容器隐藏时init图表生成时容器宽度为0Tab切换后再init,并监听resize
金额精度异常0.30000000000004浮点运算用“分”做整数计算,展示时转元
环比显示infinity上月基数为0JS除零判断基数,为0时显示“新增”
数据为空仍画线无数据时显示0值空数组未处理返回null,前端断线并展示空态
Top N顺序不稳定并列排名顺序跳变仅按金额排序追加第二个排序字段

这条速查表我打印了一份贴在公司白板上,后面其他同事做报表模块也照着查,省了不少沟通成本。很多可视化的问题看起来是“显示不对”,根子其实在数据层和生命周期管理,不是图表库本身的问题。

做这套商品进销存的交互可视化图表,我最大的体会是:图表库只是最后一道工序,真正的功夫在需求拆解、数据口径、接口设计和交互细节上。“初体验”就是踩坑的过程,这个标题起得很实在。如果你正准备给自己的系统加可视化看板,建议先拿一张纸,把老板最常问的五个问题写下来,再对应到图表,不要从图表库的示例开始倒推功能。数据能回答生意上的问题,图表才有存在的意义。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询