仓储首页看板口径.md 32 KB

仓储首页看板口径

依据 2026-10-22 的 V2.3/V2.4 变更记录实施;随附文档虽然标为 V2.2,正文中旧版的六卡、库位地图和五类动态说明不再适用。

  • 首页为四格单行待办、五张 KPI(编号 01、02、03、04、06)、近 30 天出入库柱状与净趋势、分类横条,以及库龄环图、预警 TOP5、四类最近作业动态。
  • 仓库筛选只用已授权仓库;默认显示全部基本单位,但数量混合单位时显示“—”并提示用户选择单位,不默认套用列表第一单位。库存数量、分类、库龄均扣锁。
  • 库存总量、库存金额及库存分类分布的“查看台账”和图表点击均打开现有 /warehouseManagement/stockLedger 列表;首页不再提供自建台账明细视图。分类横条点击会带 categoryLevelId(分类层级 id)打开台账并预选左侧树节点,口径见 D2。
  • 呆滞占比按批次数,不按金额;库存周转按数量快照计算并剔除调拨出库。C2 没有核实的移动加权不含税成本源时显示“—”,切勿把入库价估值当成账面金额。
  • 库存总量卡片的「较上周」=本期数量 − 7 天前日末同刻快照(后端 weekOverWeekQuantity/weekOverWeekUnit/comparisonBaseAt),仅单一基本单位且两侧快照齐全时才有值;缺基准、跨单位或后端未上线该字段时显示 comparisonReason(缺省“暂无上周同时点可比快照”),差值不得以 0 代替。
  • 多基本单位只在卡片内按单位平铺小计(如“个 12,000 · kg 3,400”),不跨基本单位加总;单位数超过 3 个折叠为前 3 个并注明其余单位数;units 缺省或只有 1 个单位时不渲染小计行。
  • 锁定数量单列展示:单一时显示“其中锁定 X 个”,多单位时逐个单位附锁定数;lockedQuantity 为 0 是真实零按“0”显示,为 null 表示口径缺失显示“—”。
  • 文档中 B2 计数指向出库单,但下钻指定 V14 入总库申请,两类单据数据源不同。代码暂按文档保留“待出库单”计数与 V14 跳转;待业务确认后再统一口径。
  • 安全库存预警的三色分级、缺口量/缺口金额、影响生产工单、防重复下单与超储调拨预填口径见 A1。预警可用量与其它 KPI 不同源(含待检),共享片段 batchRows/costRows 不得改动,理由写在 A1。

当前只做 Vue 模板/脚本、MyBatis XML 与差异静态校验;实际数据对账和浏览器布局仍需在有授权数据的环境核验。

D1 出入库动态图

  • 横轴窗口=近 30 天滚动窗口(含今日,共 30 天),卡片副标必须写明窗口名与起止日期,不得显示成自然月或固定周期。
  • 柱体=数量,Y 轴取基本单位;后端 /wms/index/home-summary 的 trend 只返回有作业的日期,缺的日期前端补 null、绝不用 0 冒充。
  • 无作业日=当日入库与出库都无数据;只要任一方向有数据(含真实的 0)就不算无作业日。无作业日不画柱,给浅灰 markArea 底纹,hover 提示「该日无作业」,净趋势在该日断线。
  • 净趋势=当日前端重算的「入库量 − 出库量」,紫色虚线 #9B7DF7,绑右侧第二 Y 轴「单日净差」,与左侧数量轴分开,读数不混用刻度。
  • 连续同号风险:取窗口末尾最近连续同号段(末尾无作业日跳过),>= 2 天时提示——连续为正=积压风险(橙)、连续为负=缺货风险(红);0(持平)与中间的无作业日都打断连续段,没有连续段不提示。
  • 点击柱体与无作业日都带该日 startTime=00:00:00/endTime=23:59:59 打开 V4 出入库动态;无作业日列表为空态,但日期照带。
  • 卡片右上角「查看动态 ›」跳系统菜单真实路由 /warehouseManagement/inboundRequests(出入库申请,独立页签),不是首页内嵌的 ioapp 视图、也不是自建的 V4 动态列表:HomeOverview.vue 发 $emit('navigate', '/warehouseManagement/inboundRequests', { warehouseId }),由 dashboard.vue 的 @navigate="navigate" 走 $router.push。只带当前仓库,状态/时间/页签全部交给目标页自己的默认值(search.vue 的 defaultWhere.status='' 即全部状态,默认入库页签);不得带 dashboard=1,否则会误开看板的批量收货勾选列。V4 仍由「最近作业动态 › 全部动态」、趋势柱体与无作业日、以及调拨行的「查看单据」进入,入口不丢。
  • 口径实现集中在 dashboard-utils.js:TREND_WINDOW_DAYS、trendWindowDates、trendQuantity、isIdleTrendDay、trendNetOf、buildTrendWindow、trendStreak、trendRisk,由 scripts/check-warehouse-dashboard.cjs 静态断言守护。

D2 库存分类分布图

  • 卡片副标固定写明「按物料分类 TOP8(前 7 + 其他兜底)· 按基本单位」;只做数量口径,不做「数量/金额」切换。
  • 行数据来自后端 categories:{ id, name, quantity, ratio }。id 是物料分类根层级 t_main_category_level.id(字符串),可直接作为 V2 台账的 categoryLevelId;「其他」聚合行的 id 固定为字符串 'other',不是任何真实分类层级。
  • 点击非「其他」条形:HomeOverview.categorySelect(row) 发 open('stockLedger', { categoryLevelId: row.id }),dashboard.vue 的 open() 把 extra 经 cleanQuery 合并进路由 query(path 仍是 /warehouseManagement/stockLedger),台账页据此选中左侧树并过滤右侧列表。
  • 点击「其他」:只打开台账、不带分类,绝不把聚合兜底行下钻成具体物料。
  • 台账接受 query.categoryLevelId:树加载完成后在 treeList 中递归查找 id 匹配的节点,$nextTick 后 treeRef.setCurrentKey(id) 并展开祖先;current 置为 { id, data } 的形状,同时满足 item-list 的 current.id(首屏 datasource)与 current.data?.id(reload)两种读取。
  • 降级策略:树里找不到该 id(分类停用、无权限、树接口异常)时不报错、不白屏,清空选中回到台账默认的「不过滤」,并打印一条 [wms-stock-ledger] 说明日志;台账页日志前缀不得复用首页的 [wms-home]。
  • tooltip 口径:分类名 / 在库数量(单位取首页「基本单位」筛选 props.unit,为空时不加单位后缀)/ 占比%,并提示「点击进入台账」;占比或数量缺失显示「—」,不以 0 冒充。
  • 断言守护:scripts/check-warehouse-dashboard.cjs 覆盖「非 other 行带 categoryLevelId」「other 行不带」「open('stockLedger', extra) 把 query 传给 router」「台账页含 categoryLevelId 处理与递归查找」「tooltip 单位取 props.unit」。

A1 安全库存预警

本节固化安全库存预警改造后的唯一口径。后端实现在 WmsHomeMapper.xml 的 alertRows 片段;前端只做归一、配色兜底与文案,不复算任何阈值。回归守卫见 scripts/check-warehouse-dashboard.cjs 的 F1 断言块。

触发条件与短路顺序

alertRows 对每个「(仓库,物料)」行按下列顺序判定,命中即停:

  1. unknown(待核实):没有唯一的有效阈值配置(缺精确仓库配置、同仓同物料存在多条、阈值或基本单位不可核实),或可用量为负(锁定量超过余额)。
  2. critical(缺料):可用量 < 安全库存 secure_inventory。
  3. warning(偏低):安全库存 ≤ 可用量 < 安全库存 × K1。
  4. excess(超储):可用量 > COALESCE(最大库存, 安全库存 × K2)。
  5. normal(正常):其余全部。
  • 系数:K1=偏低系数 wms.home.alert.warning-ratio(默认 1.2)、K2=超储兜底系数 wms.home.alert.excess-ratio(默认 3)。两者由 WmsHomeAlertRatioConfig 解析后以 Mapper 参数 #{warningRatio}/#{excessRatio} 下发,非法值回落默认并打 WARN。前端不得内置 1.2/3——写死会让改系数后前后端静默分叉。
  • 最低库存 min_inventory 只作展示列,不参与任何触发判定。用最低库存判缺料是本次废弃的旧口径,不得回退。
  • 超储兜底只在最大库存为空时生效:配了最大库存时超储线就是最大库存本身,COALESCE 的第二项不会被用到;把 K2 当成「最大库存之外再加一层」是错的。

可用量口径(与 batchRows 不同源,必须长期并存)

  • 预警可用量在 alertRows 内一个独立的 av 子查询里算:SUM(stock_count_base) − SUM(COALESCE(lock_count_base,0)),按(仓库,物料)聚合,批次过滤条件与预警行集一致。含待检、未放行批次,不套 pre_warehousing/quality_control 过滤。
  • 其它 KPI(库存总量、分类分布、库龄、周转)走 batchRows 投影的 availableQuantity,定义是 available_count_base − COALESCE(lock_count_base,0),会把未放行/待检批次减掉。
  • 为什么两套口径要并存:预警判的是「这个物料还能不能发料」,锁定量才是真实占用,未放行批次仍在库里、后续会放行,把它当成不存在会漏报缺料;库存/库龄口径回答的是「现在可动用的量」,必须排除未放行。两者目的不同,强行统一只会让一边算错。
  • 护栏:batchRows 与 costRows 片段不得改动。 batchRows 的 availableQuantity 被库存总量、分类分布、库龄、周转等四个既有消费者同时读取,costRows 是库存金额(C2)的单价定义;为了预警去改这两处,会静默改掉四个 KPI 的数字且没有任何报错。预警需要不同口径时只能新增独立子查询,不能复用或改写共享片段。
  • 单价侧相反:costUnitPriceRows(从 costRows 抽出)与库存金额共享同一定义,缺价时保持 NULL,不兜底成 0。
  • 断言:F1-(5) 直接读 WmsHomeMapper.xml,校验 batchRows 的 available_count_base-COALESCE(b.lock_count_base,0) 与 alertRows 的 SUM(...)−SUM(...) 同时存在,且 alertRows 内不得出现 available_count_base(出现即意味着预警复用了排除未放行的口径)。

缺口量与缺口金额

  • 缺口量 gapQuantity = GREATEST(安全库存 − 可用量, 0),只在 critical/warning 行有意义,其余状态为 0。
  • 缺口金额 gapAmount = gapQuantity × 移动加权不含税单价(单价取 costUnitPriceRows,与库存金额同源)。
  • 缺价语义:单价不可用时 gapAmount 为 NULL 并下发 gapAmountReason,前端显示「金额待补价」且打一条去重日志,绝不用 0 冒充——0 会被读成「缺口不值钱」,直接误导补货优先级。真实 0 照实显示 0.00 元;底层格式化层对缺失给「—」、对 0 给 0.00。
  • 行文案统一由 alertGapText 生成:缺口 <数量><单位> / <金额>,缺价/缺口量缺失分别落到「金额待补价」与「—」。

排序与 TOP5

  • 预警列表与首页卡片共用同一条 ORDER BY gapAmount IS NULL, gapAmount DESC, FIELD(status,'critical','warning','excess','normal','unknown'), id:缺口金额倒序、缺价行(NULL)排最后、再按状态严重度、最后 id 稳定收尾(分页时同额同状态的行不会跳位)。显式写 gapAmount IS NULL 是为了让排序意图不依赖 MySQL DESC 的隐式 NULL 规则。
  • 首页卡片只展示前 ALERT_TOP_LIMIT = 5 条;超出部分由 buildAlertRows 返回 overflow 并在控制台 warn 一次,total 仍是后端原始条数,右上角「全部 N 项」取 alertCount。
  • 前端不重排:dashboard-utils 的比较器只用于核对后端顺序、偏离时 warn,不改变渲染顺序——清单是分页的,页内重排会让「第 2 页第一条」错位。

影响生产工单

  • 口径:预警物料作为 BOM 子件,经 BOM 主表/明细关联到成品,再关联 t_mes_work_order;未完工定义固定为 deleted=0 AND status IN (4 待生产, 5 生产中, 7 已延期),已完工(6)/待下达(8)/暂停(9)/终止(10) 都不算影响生产,不得放宽。
  • 汇总条 alertWorkOrderCount=全部缺料(critical)行关联工单跨行去重后的计数(同一张工单被多个缺料物料影响只算一张);算不出时为 null + alertWorkOrderCountReason,前端显示「—」并给原因,绝不当 0。
  • 行级 workOrderCount 用 CASE WHEN status='critical' THEN ... END:非缺料行为 NULL 而不是 0(0 会被读成「确定没有工单受影响」)。
  • 下钻:点「影响生产工单」按 level=critical 打开缺料清单,保证数字与清单同源。

已生成申请(防重复下单台账)

  • 行级 purchaseRequestCode/purchaseRequestId 来自 t_wms_dashboard_command(command_type='PURCHASE',command_key = warehouseId:categoryId),business_id 用 CAST AS CHAR——大整数不得进 JS number。
  • 列表侧 detail.vue 的 alertActionOf 优先级:unknown→「核对阈值」(不给补货入口,阈值不成立时补货只会被后端拒绝);excess→「发起调拨」;有 purchaseRequestCode→「打开采购需求」,绝不出现「生成采购申请」;critical/warning 且无单号→「生成采购申请」;normal→不给动作。
  • 弹层侧 DashboardActions.vue 的 purchaseLocked 三重拦截:数量输入禁用、提交按钮禁用并改文案「已生成申请,禁止重复提交」、submit 内再拦一次并打 [wms-home] warn;后端 hasReplenishmentAlert 与新缺料口径同源,是最后一道。

超储调拨预填

  • 超储行的闭环是把多的调给缺的:目标仓库=缺口最大的缺料行所在仓库(只认 critical/warning,按 gapAmount 降序、缺价行最后,再按 gapQuantity 降序),目标库区=该仓第一个启用库区,并显式标注「默认库区,请核对」。
  • 无缺料行时目标仓库与库区留空并给提示,不拿超储行自己的仓库/库区冒充目标。

补货与阈值弹层

  • 补货默认数量=positiveQuantity(row.gapQuantity):非正数留空,不得用安全库存或最大库存冒充缺口量。
  • 阈值弹层:最高库存非必填,留空提交 maxInventory: null;后端 insert/update 均 jdbcType=DECIMAL 无条件写,留空写 NULL(为空即走 ×K2 兜底)。

汇总数字口径

  • alertCount 只含 critical + warning + excess;alertUnknownCount 单列且不计入 alertCount(卡片显示「待核实 X 项(不计入 N 项)」)。
  • alertGapAmount = Σ gapAmount;全缺价时后端给 null + alertGapAmountReason,前端显示「—」。
  • 前端不用可见行数自算合计:自己加总只能得到「当前可见行」的合计,与清单页条数、缺价排除规则立刻分叉。F1-(1) 用「alerts 给 3 行但 alertCount 为 null → 显示「—」而不是 3」守住这条。

上线前必须核对的数据库前置项

  • t_main_category_wms.max_inventory 必须允许 NULL。本口径依赖「最大库存为空 → 按安全库存 × K2 兜底」;列若是 NOT NULL,留空保存会直接失败,超储兜底这条分支永远触发不到。
  • 本会话无数据库连接,该列的 NULL 属性未能在本环境验证,上线前必须在目标库核对:

    SHOW COLUMNS FROM t_main_category_wms LIKE 'max_inventory';
    
  • 判读:Null 必须是 YES。若为 NO,先做列可空迁移——用 SHOW CREATE TABLE t_main_category_wms 取现有精度与注释后,再执行形如 ALTER TABLE t_main_category_wms MODIFY COLUMN max_inventory DECIMAL(24,4) NULL COMMENT '最高库存,为空表示未配置(超储判定按安全库存×K2 兜底)'; 的变更,否则「留空保存」整条不可用。

  • 顺带核对:secure_inventory/min_inventory 的存在与可空性(阈值缺失会落到 unknown);t_wms_dashboard_command 上 (tenant_id, command_type, command_key) 是否有可用索引(防重复下单的查询走这条路)。

断言守护清单

F1 断言块覆盖:statusLabel/color 优先级与三色兜底(normal 不造色)、缺价「金额待补价」而 0→「0.00 元」、TOP5 截断与 overflow、状态不复算、汇总条全 null→「—」且不自算合计、alertActionOf 五分支(含「有单号绝不出现生成采购申请」「unknown 不给补货入口」)、卡片模板守卫(三色点、影响生产工单、已生成申请、金额待补价)、screens.alerts 文案守卫(含待检/偏低系数/超储兜底,且不得再出现「排除未放行」)、后端 SQL 静态守卫(critical 判定行不得引用 minInventory、必须用 secureInventory、K1/K2 走参数、gapAmount DESC、batchRows 与 av 子查询两条护栏)。后端仓库不在同级目录时该组打印 SKIP 并说明原因,不判失败。

E3 最近作业动态

本节固化首页「最近作业动态」卡片的唯一口径。数据由 /wms/index/home-summary 的 activities 下发,取数在后端 WmsHomeMapper.xml 的 recentActivities(WmsHomeMapper.xml:226-248),前端归一在 dashboard-utils.js 的 E3 段(dashboard-utils.js:597-925),模板在 HomeOverview.vue 的 feed 块(HomeOverview.vue:118-157)。

聚合范围与数据来源

  • 只聚合四类:入库单、出库单(同为 activityType=io,按 direction 1/2 区分,dashboard-utils.js:767-773)、调拨单(activityType=transfer)、盘点任务(activityType=inventory,取自 t_wms_plan_order,WmsHomeMapper.xml:240-246)。入/出/调拨行来自 ioRows 共享片段(WmsHomeMapper.xml:177-202)。后端没给的类型不许在前端补一条。
  • 「旧版五类动态说明不再适用」是本文件开头第 3 行的既有结论(「正文中旧版的六卡、库位地图和五类动态说明不再适用」)。含义有两层:不许按旧口径补第五类(质检、审批之类),也不许把旧五类的取数沿用过来;四类就是当前契约。
  • 调拨的识别写在 SQL 里(WmsHomeMapper.xml:235-236:出库方向 biz_type=2 或入库方向 biz_type=15),前端只认后端给的 activityType,不得自己按 businessType 再猜一遍——两套判据迟早分叉。
  • 时间窗:盘点分支只取近 30 天(WmsHomeMapper.xml:246),入/出/调拨沿用 ioRows 的 30 天滚动窗(WmsHomeMapper.xml:196-197),刷新时由服务层清空 startTime/endTime/unit 免得被首页筛选改写(WmsIndexServiceImpl.java:1547-1550)。

已知限制:定义里的部分状态在当前契约下不可达

  • ioRows 把「已完成」硬编码成主单审核通过(AND o.verify_status=2,WmsHomeMapper.xml:190)。该片段同时被 D1 趋势(WmsHomeMapper.xml:221)、V4 明细 io 页签(WmsHomeMapper.xml:817)、E3(WmsHomeMapper.xml:237)共用,是「已完成」的唯一定义点(WmsHomeMapper.xml:166-171)。因此入/出/调拨行恒为「已完成/绿」(dashboard-utils.js:748-749),盘点只有 status 0/1/2 → 待执行/进行中/已完成(dashboard-utils.js:740-744)。
  • 结论:状态定义里的「待质检/待审批/已驳回」以及「超时角标」在当前契约下不可达。 它们只存在于色表(dashboard-utils.js:611-618)与兜底分支(dashboard-utils.js:751-756),而兜底只在后端新增了未识别 activityType 且同时下发中文状态词时才生效。今天唯一可能被标超时的行是盘点 status=0「待执行」(dashboard-utils.js:829-830)。
  • 前端已做前向兼容:状态词表 + neutral 中性回落(dashboard-utils.js:608-619、621-626),后端一旦扩展取数即可直接生效;但不得为了凑出橙色/红色在前端伪造状态——伪造的「待审批」会让用户去催一个不存在的审批,比不显示更糟。
  • 未来若要覆盖这些状态,必须新增 E3 专用查询,绝不能改 ioRows。 动共享片段等于静默改掉 D1 的柱子和 V4 明细的数字,且一条报错都不会有;同理调拨剔除只能留在 trend 语句(WmsHomeMapper.xml:212-213),不能下沉进片段。

四段结构与对应纯函数

  • ①时间(formatActivityTime,dashboard-utils.js:711-720):今日 今日 HH:mm(:717)、昨日 昨日(:718)、更早 MM-DD(:719);时间非法或缺失一律 —(:713)并排到最后(:872),绝不用「现在」兜底——那会把一条陈年流水顶到列表最前面。hover 给完整时间(activityTimeTitle,:724-730)。
  • ②单据类型与单号(activityDocTypeOf,dashboard-utils.js:762-775;模板 HomeOverview.vue:142-151):类型名固定「入库单/出库单/调拨单/盘点任务」,未识别回落「作业单」而不是猜成四类之一(猜错会让用户点进错误的单据类型);单号缺失显示「单号待核实」(HomeOverview.vue:150)。单号段单独可点,用 @click.prevent.stop(HomeOverview.vue:148)——少了 .stop 会冒泡到整行的 @click(HomeOverview.vue:138),一次点击触发两次下钻。
  • ③动作描述(activityActionTextOf,dashboard-utils.js:785-815):按类型套模板,见下一节。
  • ④状态(activityStatusOf,dashboard-utils.js:737-757):渲染在 HomeOverview.vue:153,超时角标同格追加。

动作描述模板的两条硬约束

  • 盘点行不得渲染数量。 后端盘点分支把 itemCount 写成占位常量 1(WmsHomeMapper.xml:241,该行第 4 个投影值),它既不是盘点明细行数也不是盘点数量;当数量渲染会得到「盘点 1 项」这种伪造数字。前端对 inventory 直接返回 null(dashboard-utils.js:691),盘点只拼「盘点 · 库区(· 进度 xx%)」(:798-814)。盘点行的 description 是状态词(服务层按 status 拼「已完成/执行中/待处理」,WmsIndexServiceImpl.java:1576-1578),属于第四段,绝不能当动作文案(dashboard-utils.js:793-796),否则一行会读成「单号 → 已完成」。
  • 数量缺失不写「0 项」,进度为 0 或缺失不拼「0%」。 activityItemCount 只认有限且 >0 的数(dashboard-utils.js:688-694),activityProgressOf 对 0、负数、非数字一律返回空串(:699-707)。0 会被读成「这一单没有任何明细」「一点都没做」,与「不知道」是两件事;与 C2 缺价、E1 unknown 同一条铁律——不可核实就省略,绝不用 0 冒充。

状态着色

  • 五档:done 绿 / waiting 橙 / doing 青 / danger 红 / neutral 灰(dashboard-theme.scss:155-159,基础色见 :142-144)。文案与颜色解耦:颜色只认 ACTIVITY_STATUS_STYLES(dashboard-utils.js:608-619),改配色不用改文案,模板里也不许判业务状态。
  • neutral 的用途:未识别状态的兜底,文案「状态待核实」(dashboard-utils.js:625-626)。为什么不能用 done 或 waiting:done 是绿,会被直接读成「已完成」这个业务结论;waiting 是橙,会被读成「在等谁处理」,还会被套上超时角标(:829-830),等于替后端凭空造出一个待办。灰只表达「这里有个状态,前端不认识」,与 E1 库龄 unknown、F1 预警「待核实」是同一种视觉语言;样式见 dashboard-theme.scss:159。
  • 推导规则(唯一来源,组件与模板不得再内联判断):盘点按 status 0/1/2 → 待执行/进行中/已完成(dashboard-utils.js:740-744);调拨恒「已执行」done(:748);出入库恒「已完成」done(:749)。
  • 必须先按 activityType 分流再判 status。 io/transfer 的 status 恒为 NULL(WmsHomeMapper.xml:234),而 Number(null) === 0,先判 status 会把「已完成」显示成「待执行」(dashboard-utils.js:735-736)。未识别的类型或状态值只 warn 一次并回落中性灰(:745-746、:750、:756),不得静默当成 done。

超时角标

  • 只有两条同时成立才标:状态是橙色「待*」(tone === 'waiting')且事件时间早于 now−24h(buildActivityItem,dashboard-utils.js:827-830);阈值 ACTIVITY_OVERDUE_HOURS = 24(:603)。
  • 24 小时与待办条同源:待办条的 overdue 就是 DATE_ADD(a.create_time, INTERVAL 24 HOUR) < NOW()(WmsHomeMapper.xml:509,入总库申请分支同式 :532;盘点行固定不标,:558)。不许在 E3 另立 12h/48h——同一屏上的两个「超时」不能是两个定义。
  • done / doing / danger 不标(dashboard-utils.js:827):绿/青/红套「超时」没有业务含义,别给已完成的单据戴超时帽子。
  • 纯函数计算、幂等:由 operateTime 与 now 现算(:828-830),重复求值不会因为「已经标过一次」而漂移;不得做成「标一次就记住」的有状态标记。

排序、条数与去重

  • 去重:同一单据(orderId,缺失回落 id,两者都没有用行下标兜底)只留事件时间最新一条(buildActivityFeed,dashboard-utils.js:853-867);同刻不覆盖(:865-866)。后端已做 GROUP BY orderId(WmsHomeMapper.xml:238),但前端不能假设这个口径永远不变。
  • 排序:事件时间倒序;同刻按行 key 倒序(dashboard-utils.js:868-879),因此顺序确定、可复现——稳定排序才能让刷新后行不跳位。解析不出时间的行排最后(:872)。
  • 条数:默认 5 条(ACTIVITY_LIMIT = 5,dashboard-utils.js:601;前端截断 :880;后端 LIMIT #{q.limit} 取同一值,WmsHomeMapper.xml:247 与 WmsIndexServiceImpl.java:1546)。双保险的理由:前端还要按 orderId 再去重一次,去重后条数可能变少;后端口径若调整也可能变多,只靠后端 LIMIT 保不住「卡片正好 5 条」这个视觉契约(:598-600)。多出来的条数进 overflow(:881),不静默丢弃。
  • 「全部动态 ›」下钻 V4 出入库动态:HomeOverview.vue:128 发 open('io'),dashboard.vue:84-101 落到 io 屏(dashboard-screens.js:8)。整行与单号点击走 openActivity(HomeOverview.vue:701-705)→ dashboard.vue:214-216 按 activityType 分流:盘点→盘点工单详情、调拨→V4 带单号、io→入/出库单据详情;缺 orderId 时提示核实,不许硬跳到列表首页。

刷新与高亮

  • 30 秒轮询:dashboard.vue:170 的 setInterval(() => this.refreshPart('activities'), 30*1000),只刷 activities 这一片,不整页重算(:164-172)。
  • 新事件只加 is-new 类做 3 秒渐隐(HomeOverview.vue:135 + dashboard-theme.scss:162-163),JS 只负责到点摘类(HomeOverview.vue:679-682)。不移动 DOM、不调用任何滚动 API:30 秒轮询里主动 scrollIntoView/scrollTop 会把用户正在看的位置顶走(HomeOverview.vue:668-669、dashboard-theme.scss:160-161)。
  • 首屏不整屏高亮:diffActivityIds 在 prev 为空(首次加载)时返回空数组(dashboard-utils.js:919),HomeOverview.vue:670-673 因此天然不触发——整屏闪一下等于什么都没强调。
  • 差分只按行 key 比较,绝不按数组引用(dashboard-utils.js:920-924):activities 是计算属性,每次求值都返回新数组,引用比较会让 30 秒轮询把同一批行全判成新增、整屏反复闪。
  • 连续两批新增重置定时器:HomeOverview.vue:675 先 clearTimeout 再起新的 3 秒,只保留最后一轮,避免先到的一批提前被摘掉;组件销毁时同一处清理(:685-688)。

空态

  • 无数据时固定文案「今日暂无作业动态」(HomeOverview.vue:156),禁止空白、禁止只画一个空列表;有 activityReason 时追加「 · 原因」(同处),原因由数据侧给,前端不自造。
  • 注意:后端目前不下发 activityReason(WmsHomeMapper.xml:226-248 无此列,服务层也未拼),该字段实际只在刷新失败时被写入错误信息(dashboard.vue:158、:161)。所以今天的空态基本就是这一句话,不许为它编造「网络异常」之类的原因。

排查日志清单

  • [wms-home] E3 最近作业动态已解析(dashboard-utils.js:907):模块级快照去重(:642、:902-906),只在「总条数/去重数/四类分布/待*数/超时数」任一变化时打一条;每次都打会把控制台刷满、反而看不见真实变化。
  • [wms-home] E3 最近作业动态状态未识别(:636):按「类别 + 值」去重,同一句 warn 只打一次(:631-637),是发现后端口径漂移的第一信号。
  • [wms-home] E3 最近作业动态下钻(HomeOverview.vue:703):只记 id / activityType / orderNo / direction。
  • [wms-home] E3 最近作业动态新增事件(HomeOverview.vue:677):只记新增 id 与条数。
  • 禁止打印整行数据或整包响应(dashboard-utils.js:641):日志只放聚合数字与关键字段,不 console.log(row)、不打印 summary;长列表会淹掉控制台,单号也是敏感数据。日志前缀必须是 [wms-home],其它页面不得复用。

断言守护

E3 的关键口径由 scripts/check-warehouse-dashboard.cjs 的 E3 断言块守护,写法与同文件里 D1、E1、F1 各块一致(check-warehouse-dashboard.cjs:554、:804、结尾 F1-(5) 段)。被守护的关键点:

  • 时间四段文案、「—」兜底、不得出现「现在」兜底;
  • 盘点不渲染数量、数量缺失不写「0 项」、进度 0 或缺失不拼「0%」;
  • 状态推导先按 activityType 分流再判 status,Number(null)===0 的错位必须锁死;
  • 五档 tone 与 neutral 兜底、未识别不得落成 done;
  • 超时只标 waiting 且超过 24h,done/doing/danger 不标;
  • 去重、事件时间倒序、同刻 key 倒序、5 条截断与 overflow;
  • diffActivityIds 首屏返回空、按 id 比较不按引用;
  • 模板守卫:@click.prevent.stop、is-new 类、空态文案;
  • 后端 SQL 静态守卫:ioRows 的 verify_status=2 唯一门槛、盘点分支 itemCount 常量、LIMIT 与 24h 口径。

不写死断言条数:断言块会随口径增补继续加,条数写进文档立刻过期,以脚本实际执行为准。

未验证边界

  • 浏览器实际渲染未核验:.feed-doc 是 inline-flex + text-overflow:ellipsis(dashboard-theme.scss:148),而 ellipsis 作用于块容器的行内内容,对 flex 容器里的裸文本节点({{ item.docType }},HomeOverview.vue:143)未必生效;超长单号(.feed-no,dashboard-theme.scss:149-150)在 overflow:hidden 下可能被硬切而没有省略号。当前只有静态断言,真实宽度下的表现必须在浏览器里看一眼。
  • 真实 summary.activities 数据未对账:本环境无数据库连接,四类分布、itemCount、status 的实际取值只能靠联调确认,上述推断全部来自 Mapper 与前端代码。
  • 已知次要不一致(防御性路径,当前后端不可达):activityActionTextOf(dashboard-utils.js:785-814)在 activityType=io 且 direction 既非 1 也非 2 时把动作词兜底成「入库」,而 activityDocTypeOf(:762-774)对同一情况兜底成「出入库单」并 warn 一次。ioRows 的 direction 取自 t_wms_snapshot.out_type,正常数据非空,只有历史脏值可能命中;命中时同一行会读成「出入库单 RK… · 入库 N 项」。若以后要覆盖,应让两处共用一个方向判定助手,不要各自兜底。
  • progressText 生产环境恒为空:后端 recentActivities 不下发进度字段(WmsHomeMapper.xml:226-248),前端是防御性读取(dashboard-utils.js:696-707)。因此「进度 xx%」今天不会出现,只有后端补了进度列才生效——不得为了让它显示而在前端算一个进度。