# 前端执行票 —— 统一分页批次一:共享分页基建(pagination.js + PagerNav.vue)与设备详情三清单 ## Question 按 [全局分页规范](../specs/2026-10-18-global-list-pagination.md) 落地批次一:把 `domain/equipmentDetailView.js` 的 `pagedRows` 升格为单源 `domain/pagination.js`(`PAGE_SIZE = 10`、`pagedRows`、页码窗口函数;`HISTORY_PAGE_SIZE`/`pagedRows` 原名保留导出兼容),新建共享 `PagerNav.vue`(上一页|页码直达|下一页 + **数字输入框跳页** + 计数行「第 X/Y 页 · 共 N 条」,一页不渲染、到边禁用保占位、`aria-current="page"`、总页数 >7 收缩「1 … c−1 c c+1 … Y」窗口);`EquipmentDetailSections.vue` 预警/报警迁移到共享 pager 并**补页码直达**,工单状态新接入分页(10/页)。 ## Acceptance - 页码窗口规则有纯函数闸门(≤7 全列;>7 收缩含首末与省略号;非法输入不抛错)。 - **跳页(维护者 2026-10-19 追加,规范「跳页」节)**:pager 提供输入框 + 「跳转」按钮/Enter;非数字、0、超 `pageCount` 一律夹回 1..pageCount,不报错、不发请求;`aria-label="跳转到指定页"`、placeholder=当前页;跳页与页码/前后页共用同一翻页出口,不新增第二套页码状态;总页数 ≤1 时整块不渲染。 - 预警/报警翻页行为与现状等价(分类变化回第 1 页、换设备全复位、越界夹回)+ 新增页码直达与跳页;t46 既有闸门按成例改写后全绿。 - 工单状态每页 ≤10、一页不画控件、分类变化回第 1 页。 - pager 控件唯一(`PagerNav.vue`),组件内不再有第二份翻页算术;`data-pager`/`data-page-prev`/`data-page-next`/`data-page-state` 断言面保留,跳页新增 `data-page-jump`/`data-page-jump-input`。 - 新增闸门 + 变异打红;按 AGENTS.md 跳过 build/dev/浏览器验证。 ## Context 先例:`domain/equipmentDetailView.js`(`pagedRows` 纯函数)+ 票 46 实施记录。教训复用:票 43(视图状态必须用稳定标量键,不能用对象身份);复位触发器在组件 watch(`deviceCode`)。维护者裁定(2026-10-18 台账/事件、2026-10-19 跳页)见规范「维护者裁定」「跳页」「修订记录」节。 ## Related spec [`../specs/2026-10-18-global-list-pagination.md`](../specs/2026-10-18-global-list-pagination.md) Status: resolved Assignees: Qoder Type: task Blocked by: Labels: wayfinder:task ## Comments - 批次一开工说明:本票为纯执行票(ready-for-agent,无 needs-triage),无阻塞边,完成后解锁批次二~五(票 60–68 共 9 张 blocked by 本票)。仓内既有资料(规范 + 票 46 实施记录 + `equipmentDetailView.pagedRows` 先例)足以回答本次全部问题,未新开 research 笔记。 ## Answer **已实施、闸门 + 变异闭环并关闭(2026-10-19)。按 AGENTS.md 跳过 build/dev/浏览器验证。** ### 交付物 | 文件 | 内容 | |---|---| | `src/modules/cockpit/domain/pagination.js`(新) | 翻页窗口计算单源:`PAGE_SIZE = 10`、`PAGE_ELLIPSIS = '…'`、`pagedRows`(自票 46 逐字升格,行为不变)、`pageWindow(current, total)`(≤7 全列;>7 收缩「1 … c−1 c c+1 … Y」,恒含首末+当前页+至少一个省略号;脏输入按最小安全形状回、不抛)、`clampPage(value, pageCount)`(跳页夹回 1..pageCount;非数字/空/null→1,绝不回 NaN;小数截断;脏 pageCount→1) | | `src/modules/cockpit/domain/equipmentDetailView.js` | `pagedRows`/`HISTORY_PAGE_SIZE` 改为**原名兼容再导出** `export { PAGE_SIZE as HISTORY_PAGE_SIZE, pagedRows } from './pagination.js'`(同一函数引用,非复制实现),旧调用与 t46 闸门零改动可跑 | | `src/modules/cockpit/components/PagerNav.vue`(新) | 全仓唯一共享 pager:上一页|页码直达|下一页 + 数字输入框跳页 + 计数行「第 X/Y 页 · 共 N 条」;`pageCount<=1` 整块不渲染;到边 `disabled` 保占位;当前页 `aria-current="page"` 且点击不跳;`role="group"` + `「列表名」分页` aria-label;跳页 `aria-label="跳转到指定页"`、`:placeholder="String(page)"`、Enter + 按钮双入口 | | `src/modules/cockpit/components/EquipmentDetailSections.vue` | 预警/报警/工单三清单都接共享 `PagerNav`;内联 pager 与 `.equipment-pager__*` 样式退役;工单列表循环 `visibleRows(panel)` → `pagedOf(panel).rows`(新接入分页);`pagedOf` 收敛为**单一委托**(不再按 kind 分叉兜底形状) | ### 关键设计(唯一翻页出口) 规范「跳页」节要求「跳页与页码/前后页共用同一个翻页出口,不新增第二套页码状态」。落地形态:**`PagerNav` 是受控组件**——本地只有跳页输入缓冲(`jumpValue`),页码一律从 `props.page` 进;`defineEmits(['page'])` 单一事件,所有交互(前后页/页码直达/跳页)统一 emit **绝对目标页码(1 起)**,由 `go()` 一处出口发出。 > 第一版草稿曾用相对增量(`emit('change', -1|1|target)`),点第 1 页(当前 2 页)emit `-1` 与「上一页」撞车、父组件必须靠 `!== -1 && !== 1` 猜意图——那不是同一出口,是两套状态挤在一个通道里。改为绝对页码后父侧只需 `gotoPage(panel, target)` 存状态,越界夹回由 `pagedRows` 下一帧渲染时做(条文 6),组件内零 `Math.min/max`。 跳页同值不 emit(不发请求、不清列表);输入经 `clampPage` 后走同一 `go()`。复位规则不变:`toggleCategory` 回第 1 页、`watch(deviceCode)` 全复位。 ### 验证 - **新增闸门 [t59-shared-pager.mjs](../verify/t59-shared-pager.mjs) 37/37**:P 单源与兼容(P2 判**同一函数引用**、P3 判旧模块无残留实现、P5 越界夹回);W 页码窗口九条(含 7 页阈值边界、8 页起步收缩、贴左不出现假省略号、恒含首末与当前页、数字升序无重复、脏输入不抛);J 跳页夹回五条;S PagerNav 静态守卫九条(按**绑定**判不按字样判);C 消费方七条(C4 判组件零 `Math.ceil/trunc/floor`、零 `.slice(`、零内联 pager、零 `hasPrev/hasNext`——第二份算术即红);K 两份 `.vue` 用 `@vue/compiler-sfc` **真实编译过**(票 41 教训:拼错的绑定只有加载层判得出)。 - **变异闸 [t59-mutations.mjs](../verify/t59-mutations.mjs) 13/13 打红并按字节还原**:M1 阈值 7→5 / M2 右侧省略号不推 / M3 夹回放 NaN 过 / M4 只夹下不夹上 / M5 一页也画控件 / M6 出口改名出第二个事件 / M7 到边不再禁用 / M8 丢 `aria-current` / M9 跳页绕过 `clampPage` / M10 兼容导出改名(崩溃式抓住)/ M11 组件重长翻页算术 / M12 工单退回全量直渲 / M13 换分类不再回第 1 页。 - **t46 既有闸门按成例改写后全绿**(54/54 + 变异 12/12):E3 的「工单走过滤行」改为「工单也走分页行」;E3b 从「内联按钮按 hasPrev/hasNext 置灰」改判「内联 pager 退役 + 接共享 PagerNav + 唯一 `@page→gotoPage` 出口」;M5/M8 变异锚点随升格从 `equipmentDetailView.js` 移到 `pagination.js`(G 节测的仍是兼容再导出,同一份实现)。 - **回归与 HEAD 基线对照**(本票 diff 只 5 个文件,余为并行在制品):t41 SSR 9/9、t23、t15 11/11 全绿;t12 由基线 51/52 保持 51/52(`E8` 经 worktree 基线证实为改前既有红,与本票零交集)。 - **t12 扫描器假红修正**:`F11` 把事件处理器里的箭头形参 `target` 误报成未绑定标识符(与本文件既有「v-for 局部名」同类的判据过宽,注释里已记三次同类实测)。按成例把 `([^)]*)\s*=>` 的形参收进模板局部名,不改组件写法迁就守卫。 ### 验收对照 1. ✅ 窗口纯函数闸门 W1–W9 + 变异 M1/M2。 2. ✅ 跳页:输入框 + 按钮/Enter;非数字/0/超界夹回不报错不发请求(J1–J5、M3/M4);`aria-label`/placeholder(S5);同一出口不新增第二套状态(S2/S6/S8、M6/M9);≤1 页整块不渲染(S1、M5)。 3. ✅ 预警/报警行为等价(C5 复位规则、P5 越界夹回)+ 页码直达与跳页;t46 改写后 54/54。 4. ✅ 工单 ≤10/页(C3、M12)、一页不画控件(S1)、分类变化回第 1 页(C5、M13)。 5. ✅ pager 唯一 + 组件无第二份算术(C4、M11);`data-pager`/`prev`/`next`/`state` 保留、新增 `data-page-jump`/`data-page-jump-input`(S9)。 6. ✅ 新增闸门 + 变异打红;按 AGENTS.md 跳过 build/dev/浏览器验证。 ### 状态与下一步 批次一收口,**票 60–68 九张批次的共享前置已就位**(`pagination.js` 单源 + `PagerNav.vue` 唯一控件可直接接入;服务端分页与渐进装载批次另需按规范三分法扩参)。两点如实留档:① **改动未提交**(工作树混票 46/50/51 等并行在制品,入库须按票 33/36/37 纪律单独裁剪本票 5 个文件的 hunks);② 浏览器走查按项目规则未自动执行,真机验收归票 18/17 联调窗口。