# 前端执行票 —— 工单堆叠图语义间隔与饼图交互 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`)。 - **外层 `` 由 `role="img"` 改 `role="group"`**:`img` 会把整棵子树对辅助技术隐藏,扇区的名称与焦点根本读不到 —— 这是「可访问名称」真正生效的前提。 - **详情入口只留一个**:删除扇区原生 ``(原生提示有 ~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](../verify/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 红**(证明逐元素正则用 `(?:(?!)[\s\S])*?` 锚定,不会从环形图那片借断言假绿)。 - M3 恢复扇区原生 `` → **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 会藏子树使名称不可达)、删原生 <title> 收敛单一详情入口(票 37 趋势点成例)、环形图图例悬停与饼图对齐。验证:t43 新闸门 13/13、变异 M1/M2/M3 全部打红、10 组离线回归全绿(t50 30/30)、@vue/compiler-sfc 结构校验 9 项全过。t20 60/66 的 6 条红=票 42 记录的 HEAD 既有失败,与本票零交集。事故如实记录:归因实验的 git stash 往返写坏了 panelBlocks.js,已按本会话完整读取的原文+本票编辑重建(619=604+15 行、导出 29 符号齐、EllipsisText 15 用点、CRLF 归一),全部闸门在重建文件上复跑转绿。浏览器走查已获用户授权,但被并行会话在制品 mapService.js 的半完成语法错误阻断(Vite 模块图编译失败),用户指示等其修复后重试。改动未提交(混有并行在制品,按票 33/36/42 裁剪纪律单独摘取)。 ## Answer **间隔=绘制缺陷(`left`/`width` 各自独立取整露出的底色缝),不是语义边界;已改为 domain 单累加器接力取整,连续由构造保证,统计口径未变。饼图/环图补齐键盘焦点(`tabindex` + 每片 `aria-label` 携带类别与数量)、focus 与 hover 同档放大 + 同一个共享详情浮层、外层 svg 改 `role="group"` 使名称可达、删原生 `<title>` 收敛为单一详情入口、环形图图例悬停与饼图对齐。t43 闸门 13/13 + 3 处变异全部打红;10 组离线回归全绿;t20 的 6 条红经票 42 HEAD 基线对照证实为既有失败。改动未提交(混有并行在制品);浏览器走查按项目规则未自动执行。**