指标层解决了“怎么算”,展示层要解决“谁来看、怎么看、看完能不能对得上”
前几篇聊了指标定义和指标引擎。按理说,口径已经在引擎里统一了,页面应该只负责展示。
但现实是:指标层算得再对,用户还是会在两个页面看到不同的数字。
原因通常不在引擎,而在展示层——每个页面自己拼参数、自己算环比、自己处理汇总行、自己决定“空值显示成 0 还是横杠”。这些看起来是小事,加在一起就是口径的第二次分叉。
这篇聊聊统计平台的展示层该怎么设计。
一、展示层的四个职责
一个报表后台,剥开看只有四件事:
| 职责 | 解决的问题 |
|---|---|
| 页面组织 | 路由、菜单、权限、面包屑、页签 |
| 请求层 | 参数、凭证、错误码、登录态、响应解包 |
| 口径共用件 | 汇总行、环比、缺失值、下钻条件、导出条件 |
| 图表与明细 | 趋势、占比、表格、下钻、导出 |
大部分团队的精力都花在第四项,但真正决定“数字一致”的是第三项。
二、页面是壳,组件是业务
成熟的后台里,大部分页面文件不到十行,只做一件事:挂载对应的业务组件。
1 | 路由(壳) |
这样组织的理由不是“结构好看”,而是三个实际问题:
- 权限、菜单、面包屑可以统一处理:路由层拿到路径,统一判断权限、高亮菜单、生成面包屑;
- 组件可以复用:同一个报表组件可以出现在独立页面、页签、弹窗、下钻页里;
- 路由变更不影响业务:改 URL、拆页面、加子路径,业务组件不用动。
反过来,如果把筛选、请求、渲染全写在页面文件里,每加一个入口就要复制一份,口径立刻分叉。
三、请求层:所有接口只有一个出口
所有页面都走同一个请求层,请求层只做几件事:
- 拼查询参数,丢掉空值;
- 注入登录凭证;
- 统一处理业务错误码;
- 登录态失效时刷新,刷新失败才跳登录;
- 解包响应,业务层只拿数据。
这里有几个容易被忽略的细节:
第一,响应解包只做一次。 业务码非零就抛错,不要让每个页面自己判断“code 是不是 0”。判断一次和判断一百次,出错概率差一百倍。
第二,登录态刷新要用单例。 多个请求同时失效时,只发一次刷新;否则后发的刷新会把先换到的新凭证冲掉,用户被踢下线。
第三,错误要按业务码分流。 同样是“请求冲突”,有的是“重复提交”,有的是“需要人工确认”,HTTP 状态表达不了,必须看业务码。
第四,解包时不能假设数据形状。 后端返回空对象、空数组、字段缺失,页面都应该能降级,而不是整页白屏。
四、口径共用件:数字一致的最后一道闸
这是展示层最重要的部分,也是最容易被抄来抄去的地方。
所谓“口径共用件”,就是把所有页面都会用到的展示逻辑抽出来,只维护一份:
- 汇总行:固定标识、固定置顶、固定加粗,所有报表同一套规则;
- 环比:窗口只认后端返回的值,前端不自己算;
- 缺失值:无数据、未分组、未归属,统一显示规则;
- 下钻条件:从哪一行点进去,带什么条件,由共用件决定;
- 导出条件:导出的筛选必须和页面完全一致。
为什么强调“只维护一份”?
因为报表页面的逻辑高度相似,复制粘贴是最自然的做法。但只要抄了第二份,就会出现:
- A 页面汇总行把“未分组”算进去了,B 页面没算;
- A 页面环比用自然日,B 页面用滚动窗口;
- A 页面下钻带上了筛选条件,B 页面漏了一个。
用户不会去读实现,只会说“你们这个数不对”。
原则只有一句:口径只此一份。抄第二份,就等于埋一个两页数字看起来不一致的雷。
五、图表:颜色、空值和环比都不能随手画
图表看起来是“画出来就行”,其实每个细节都在影响用户对数字的理解。
颜色要跟实体绑定,而不是跟顺序绑定。 同一个渠道,今天排第一、明天排第三,颜色都应该一样。按数组下标取色,用户会以为“渠道变了”。
空值不能画成 0。 环比没有数据时,如果按 0 画,趋势线会突然砸到谷底,看起来像“业务崩了”。正确做法是断开线段,或者标注“无对比数据”。
占比图要处理“其他”。 分组太多时,只展示前 N 个,剩下的合并成“其他”,并且保证“其他”能点开看到明细。
趋势图的粒度要统一。 按天、按周、按月,X 轴的刻度、标签格式、空档处理都要一致,不要一个页面按天补零、另一个页面跳过空档。
比率和金额不要共用一根轴。 百分比放右轴,金额放左轴,否则比例会被金额压成一条直线。
六、表格:排序、汇总、下钻都有讲究
报表表格通常比图表复杂,因为它承载了“可解释”的责任。
几个约定:
- 汇总行固定置顶,不参与排序,不参与分页;
- 排序只在同层兄弟之间生效,不要跨层级排序;
- 无数据行默认隐藏,但要有开关能显示出来;
- 阈值上色要统一色档,不能这个页面红黄绿、那个页面蓝橙紫;
- 列配置可持久化,用户调过的列顺序、显隐,下次打开还在;
- 数值列可点,点开是明细,并且明细条件与指标一致。
表格的“可解释”还体现在:点开明细后,条数和金额要能和指标对上。对不上时,宁可明确提示“该指标暂不支持明细”,也不要给一张看起来像、实际条件不同的表。
七、下钻:上下文怎么传,决定了体验
下钻有两个常见做法:
- 把条件放在 URL 参数里;
- 把条件存在会话存储里。
两者各有问题:
- 放 URL:刷新、分享没问题,但页签系统通常按精确路径匹配,带了参数会导致菜单高亮、页签判断错位;
- 放会话存储:页签不受影响、刷新不丢,但分享链接会丢上下文。
更稳妥的做法是混合:路由只带“下钻到哪个维度”,具体筛选条件放会话存储,并在页面上明确显示“当前下钻条件”,让用户知道自己在看什么。
另外两条约束:
- 下钻只能带指标支持的条件;不支持的指标不要给下钻入口;
- 下钻后的返回,要回到原来的滚动位置和筛选状态,而不是回到页面顶部。
八、导出:和页面同条件、异步、可追踪
导出是报表的“最后一公里”,也是最容易和页面不一致的地方。
三条原则:
- 导出必须复用页面的筛选条件,不能在导出接口里重新拼一遍;
- 大导出走异步任务:提交后返回任务标识,用户去下载中心取,避免请求超时;
- 导出列要有白名单,敏感字段不能因为“顺手”被带出去。
流式导出时,还要处理“跑一半失败”的情况:文件末尾要有结束标记,下载方才能区分“完整文件”和“半截文件”。
九、性能与体验:几个真实的坑
页签缓存和图表测量。 很多后台用“页签缓存”保留页面状态,隐藏的页签会被设成不显示。这时图表容器宽高为 0,图表库要么渲染空白,要么报警告。解法是监听容器尺寸,量到有效宽高再渲染。
防抖与请求竞态。 筛选条件变化很快时,旧请求可能晚于新请求返回。要用请求序号或取消机制,只认最后一次。
分页与游标。 明细数据量大时,偏移量分页越翻越慢,还可能重复。用游标分页,只能往后翻,但稳定。
缓存边界。 指标字典、店铺列表这类低频数据可以缓存;报表数字不要在前端缓存,参数一变就必须重新请求。
懒加载。 页签、下钻弹窗、图表,按需加载,不要首屏把所有报表都请求一遍。
十、常见坑
- 两个页面各自算环比:跨月、今天没过完,结果一定不同;
- 汇总行不固定:排序后汇总行跑到中间,用户以为看错了;
- 空值画成 0:趋势图出现假暴跌;
- 颜色按顺序分配:换一个时间区间,同一个渠道换了颜色;
- 下钻条件放 URL:页签高亮错乱;
- 导出重新拼条件:导出的数和页面不一致;
- 响应解包不判形状:一个空对象打挂整页;
- 登录态刷新并发:用户被莫名踢下线;
- 隐藏页签里渲染图表:宽高为 0,图表空白;
- 前端缓存报表数据:切回页面看到旧数字。
十一、结语
展示层不是“把接口数据画出来”,而是把口径稳定地呈现给用户。
页面壳、请求层、口径共用件、图表与明细,四层各司其职,数字才不会在最后一公里分叉。