43-untitled.md 12 KB

前端执行票 —— 工单堆叠图语义间隔与饼图交互

Label: ready-for-agent Category: frontend-execution

来源

承接票37收口时维护者反馈第1、2条。

范围

  1. 调查工单横向堆叠统计图中部分类别间的小间隔(待接单/已接单、已结案/已取消):确认是浏览器像素舍入、条段绘制策略还是有意表达语义边界;不得把不明原因的视觉现象直接当成设计。
  2. 若为设计语义,补充图上说明/图例提示;若为绘制缺陷,修复为连续、可解释的条段。
  3. 为饼图/环图补齐交互:悬停放大或高亮、共享详情浮层、键盘焦点/可访问名称;交互风格与已验收横条统计保持一致。
  4. 验收:不同统计块的颜色、间隔含义明确;饼图扇区 hover/focus 有明显反馈并展示类别与数量;不改变统计口径。

非目标

  • 不改变后端统计字段与工单状态口径。
  • 不重做票37已收口的共享省略组件。

进度

  • 2026-10-15:认领(Status: claimed)→ 诊断(范围 1)→ 实施(范围 2/3)→ 离线闸门 + 变异测试 + 回归 → 收口。
  • 2026-10-15:真实浏览器走查未执行(AGENTS.md 项目规则:不自动运行 dev/build/截图);走查入口 http://127.0.0.1:8000/#/cockpit,判据见「验收对照」第 2 条。
  • 2026-10-15:改动未提交(工作树混有票 37/38/39/40/45/46 等并行在制品,入库须按票 33/36/42 的裁剪纪律单独摘取本票 hunks)。

Status: claimed Type: task Blocked by: Labels: ready-for-agent, wayfinder:task

诊断结论(范围 1)

小间隔=绘制缺陷(像素舍入),不是设计语义。 判定过程没有把视觉现象当设计:

  • 机制:components/PanelBlocks.vue 的堆叠段样式原先对 left 与 width 各自独立取整 —— left: Math.round(row.start*100)%、width: Math.round(row.ratio*100)%。相邻段之间露出的底色缝 = round(nextStart*100) − (round(start*100) + round(ratio*100)),该值可为 ±1 个百分点(取决于两段各自的小数部分)。
  • 复现:用 mock 报警族工单向量 [0,1,2,1,1,3,1,0](mocks/contractMocks.js:2419 alarmWorkOrderStates,total=9)逐段复算,「待验收→已结案」边界 left+width=55 而下一段 left=56,实测 1 个百分点缝,与维护者所见一致(预警效率/报警族两处堆叠条同一算法,同一缺陷)。
  • 反证「非有意设计」:CSS 里 .panel-stacked__seg 无 margin、条容器无 gap/分隔声明,分段色由 barTone 逐态给(八态八色相)——若真要表达语义边界,应当是显式的分隔样式,而不是随数据总数随机出现在某些边界上的缝。同一对类别是否露缝取决于计数(待派单/待接单边界 0→0 不露,待验收/已结案露 1),这正是舍入噪声的特征,不是语义设计的特征。
  • 结论:走票面第 2 条的「绘制缺陷」分支 → 修复为连续、可解释的条段(不加「语义间隔」说明,因为不存在这种语义)。

实施(范围 2/3)

交付 1:堆叠条段边界改「接力取整」,连续由构造保证

  • domain/panelBlocks.js stackedBlock() 每行新增 startPct / widthPct:由同一个累加器取整接力(endPct = round(acc/total*100),下一段 startPct = 上一段 endPct),末段终点恒为 100。无缝不是运气舍入的结果,而是代码结构保证(每段起点定义上就是上一段终点)。
  • components/PanelBlocks.vue 堆叠段样式改消费这两个整数百分比,删除段内的 Math.round(...) 独立取整。
  • 口径不变(票面非目标):total / start / ratio 原样保留未取整的精确占比,只改绘制边界;后端统计字段与工单状态码一字未动。

交付 2:饼图/环图交互补齐(悬停放大已有,本票补键盘与可达性)

  • 扇区可键盘聚焦:饼图与环形图的每个扇区 tabindex="0" + role="img" + 每片自己的可访问名称 :aria-label="`${slice.label}:${slice.count}`"(类别与数量都在名称里,读屏可达)。
  • focus 与 hover 走同一条路:@focus/@blur 接同一个共享详情浮层 showChartTip/hideChartTip(票 37 走查扩展立的浮层),CSS :focus-visible 与 :hover 同一档放大(饼 1.05 / 环 1.06)+ 增亮 + 一圈白色描边让焦点位置可见。交互风格与已验收横条统计一致(横条用 :hover/:focus-visible 同档反馈,见 .panel-bar--button)。
  • 外层 <svg> 由 role="img" 改 role="group":img 会把整棵子树对辅助技术隐藏,扇区的名称与焦点根本读不到 —— 这是「可访问名称」真正生效的前提。
  • 详情入口只留一个:删除扇区原生 <title>(原生提示有 ~1s 延迟、样式不可控,且与自绘浮层双重叠加)。这与票 37 给趋势点定的成例一致(「趋势点改用无延迟的 aria-label 详情入口,避免与自绘浮层叠加原生 title」)。
  • 环形图图例悬停补齐:此前饼图图例行接了浮层、环形图图例行漏接(同款交互分家)。现两处一致。

验收对照(票面四条)

  1. 不同统计块的颜色、间隔含义明确 —— 间隔缺陷已消除(连续条段),颜色沿用票 37 的 barTone 八态八色相,未新增含义不明的视觉元素。
  2. 饼图扇区 hover/focus 有明显反馈并展示类别与数量 —— hover/focus 同档放大 + 描边 + 共享浮层显示「类别:数量」;键盘可达(tabindex + aria-label)。真实浏览器走查未执行(项目规则),走查判据:Tab 进入扇区应见描边与浮层、四类扇区逐一可读、堆叠条各类别边界不再露底色缝。
  3. 不改变统计口径 —— total/start/ratio 保持精确值;闸门 S4 钉住这一点。
  4. 零依赖逻辑闸门 —— 新增 t43-stacked-gap-and-pie-interactions.mjs 13/13。

验证与对抗式自查

新闸门 t43(13/13,全离线):S 组堆叠连续(接力构造、旧缺陷实证复现 + 新公式缝=0、宽度和=100、0 计数行保留、口径不变、渲染层消费接力值);P 组交互(两图扇区 tabindex/aria-label/focus-blur、原生 title 已清、svg role=group、:focus-visible 与 :hover 同档、环形图图例浮层对齐);D 组 donutBlock 形状与可点接缝不回归。

变异测试证明守卫有牙(3 处全部打红):

  • M1 domain 退回旧的独立取整 → S1/S2/S3 红(S2 显示缝重新变 1)。
  • M2 只删饼图扇区 tabindex → P1-pie 红(证明逐元素正则用 (?:(?!</path>)[\s\S])*? 锚定,不会从环形图那片借断言假绿)。
  • M3 恢复扇区原生 <title> → P2 红。

回归(离线可跑的 10 组全绿):t43 13/13、t50 30/30(此前被跳过的 pinia store 断言本轮环境已恢复并实跑)、t42、t15 11/11、t23、t27、t16-stores、t16-alarm-order、t22、t19 16/16。另用仓库自带 @vue/compiler-sfc 对 PanelBlocks.vue 做结构校验(parse + compileTemplate 零错误,票 43 的 9 处接线在编译产物中逐条命中)。

既有红闸门归因:t20-overview-panels 60/66,6 条红为 B4/C2/D2b/D3/E2/F3(片区端点、载荷引用、store 裸字段),与票 42 收口时记录的 HEAD 基线同 6 条,且与本票 diff(堆叠/饼/环)零交集 —— 非本票引入。

事故与修复(如实记录):归因实验期间用 git stash push -- <两个文件> 往返,domain/panelBlocks.js 被写坏(内容变成 .vue 的模板、后被压成单行)。已恢复并逐层验证:① 从票 37 会话留下的 dangling stash blob 取回一份 js,核对后发现它是票 37 之前的旧版(旧图标名、无 KPI_ICON_COLORS、barsBlock 无 hint)——没有采用,否则会回退并行在制品;② 按本会话内已完整读过的原文(604 行)+ 本票那一处编辑重建,逐段核对;③ 结构校验:619 行 = 604 + 本票 15 行,导出符号 29 个齐全,EllipsisText 用点 15 处、text-overflow 声明 0 处(票 37 的单一口径未被破坏),role 用法逐处核对(2 个 svg group、2 个扇区 img、趋势/堆叠的 role="img" 原样保留);④ 行尾改回仓库的 CRLF(避免整文件行尾噪声污染维护者的裁剪提交);⑤ 上述全部闸门在重建后的文件上复跑转绿。git stash list 已空,未遗留 stash。

遗留与下一步

  • 未提交:本票 hunks = domain/panelBlocks.js(stackedBlock 接力取整 + 注释)、components/PanelBlocks.vue(堆叠段样式、饼/环扇区 tabindex+aria-label+focus/blur、svg role、删 <title>、环形图图例浮层、:focus-visible CSS)、verify/t43-stacked-gap-and-pie-interactions.mjs(新闸门)。入库按票 33/36/42 的裁剪纪律单独摘取。
  • 浏览器走查:待维护者明确要求后执行(项目规则禁止自动运行);判据见「验收对照」第 2 条。
  • 相邻票边界:票 44 的「条段之间有明确分割」是视觉密度优化(有意加分割装饰),与本票「消除无意义舍入缝」不冲突但相邻 —— 若票 44 要加显式分隔,应作为有意的、与计数无关的固定分隔样式实现,不要复用本票消除的舍入缝。

Comments

local — 2026-10-15T03:55:21.458Z

诊断+实施完成(2026-10-15):① 间隔根因=绘制缺陷——堆叠段 left/width 各自独立取整露出底色缝(mock 报警向量 [0,1,2,1,1,3,1,0] 实测「待验收→已结案」边界 1 个百分点,反证非语义设计:CSS 无任何分隔声明、露缝与否随计数变化)。修复=domain 单累加器接力取整(startPct/widthPct),连续由构造保证;total/start/ratio 口径原样保留。② 饼/环交互补齐:扇区 tabindex=0 + 每片 aria-label(类别:数量)、focus/blur 接同一共享浮层、:focus-visible 与 :hover 同档放大+描边、外层 svg role=img→group(img 会藏子树使名称不可达)、删原生