Add design spec: right-click record detail modal
Design for production dept requirement 3 (long-content full display): right-click a data row to open a custom context menu -> open a Modal showing all fields of that record fully. Inline ellipsis unchanged.
This commit is contained in:
@@ -0,0 +1,140 @@
|
||||
# 生产数据表 —— 右键查看完整记录(记录详情 Modal)
|
||||
|
||||
- 日期:2026-06-24
|
||||
- 状态:设计(待评审)
|
||||
- 关联需求:生产部需求 ③「长内容字段完整显示」
|
||||
|
||||
## 1. 背景与问题
|
||||
|
||||
生产数据表字段多(约 60 列)、表格极宽(总宽 6000px+)。当前所有列 `ellipsis: true`
|
||||
(`src/app/page.tsx:159`),长内容被 `...` 截断,仅靠浏览器**原生 title 气泡**显示全文。
|
||||
长规格类字段(技术参数 / 新参数 / 缺件明细 / 特殊要求 / 备注 等)难以完整查看。
|
||||
|
||||
需求 ①(行高亮)与 ②(字段筛选)已在代码中实现,本次只做 ③。
|
||||
|
||||
## 2. 目标
|
||||
|
||||
在不破坏现有「左键点击锁行」「列筛选」交互的前提下,提供一种零冲突、彻底的方式查看
|
||||
某条记录的全部字段完整内容。
|
||||
|
||||
## 3. 方案概述
|
||||
|
||||
接管数据行的右键菜单:在**数据行**上右键 → 弹出自定义菜单(屏蔽浏览器默认菜单)→
|
||||
点「查看完整记录」→ 打开 Modal,将该记录的**所有数据字段**完整铺开展示(含当前被
|
||||
隐藏的列)。表格内联仍保持 `ellipsis`,Modal 作为「完整查看」入口。
|
||||
|
||||
## 4. 非目标(YAGNI)
|
||||
|
||||
- 不改内联单元格的截断行为(仍是 `ellipsis`)。
|
||||
- v1 右键菜单**仅一项**「查看完整记录」;不做「复制整行 / 锁定行」等扩展项(后续可加)。
|
||||
- 不做列宽自适应、不做单元格自动换行(表格已过宽,会破坏紧凑度)。
|
||||
- 不做悬浮 Tooltip 预览(右键 Modal 已覆盖该诉求)。
|
||||
|
||||
## 5. 详细设计
|
||||
|
||||
### 5.1 交互流程
|
||||
|
||||
1. 鼠标在任一**数据行**上右键 → 阻止浏览器默认菜单,在光标处弹出自定义菜单,
|
||||
仅含「查看完整记录」。
|
||||
2. 点击该菜单项 → 打开记录详情 Modal;同时关闭右键菜单。
|
||||
3. 右键菜单在以下情况关闭:选中菜单项、点击页面其它处、滚动、按 Esc、在别处再次右键。
|
||||
4. 表头 / 空白区域右键**不弹**本菜单(无法定位「哪条记录」)。
|
||||
|
||||
### 5.2 右键菜单(受控、行级作用域)
|
||||
|
||||
实现策略:通过 `Table` 的 `onRow.onContextMenu` 在**数据行**上捕获右键事件,记录
|
||||
`{ record, x, y }` 到状态;表头/空白不触发 `onRow`,故天然不会误弹。
|
||||
|
||||
菜单浮层采用「受控定位」渲染:
|
||||
|
||||
- 优先方案:antd `Dropdown` 受控(`open` 由状态驱动,`trigger={[]}` 完全受控,
|
||||
`overlayStyle={{ position: 'fixed', left, top }}`),复用 antd 菜单样式与内置的
|
||||
点击外部 / Esc 关闭逻辑。
|
||||
- 兜底方案:若 antd v6 受控 Dropdown 定位有兼容问题,改用一个 `position: fixed` 的
|
||||
自定义样式浮层 + 全局 click/scroll/keydown(Esc) 监听关闭。
|
||||
- 实现时须按 `AGENTS.md` 要求核对 antd v6 的 `Dropdown` / `Modal` / `Descriptions` API
|
||||
与 `node_modules/antd` 实际版本一致后再编码。
|
||||
|
||||
### 5.3 记录详情 Modal
|
||||
|
||||
- 组件:antd `Modal`,`open={!!detailRecord}`,`onCancel` 关闭,`destroyOnClose`。
|
||||
- 宽度:约 900px,居中。
|
||||
- 标题:`记录详情:${主标识}`。主标识取值优先级:
|
||||
`生产订单号` → `总排号` → `ID`(取第一个非空字段,否则显示「(未命名)」)。
|
||||
- 内容:antd `Descriptions`,`column={2}`、`bordered`、`size="small"`,按数据列原始
|
||||
顺序遍历**全部数据字段**生成 `<Descriptions.Item label={列名}>{值}</...>`。
|
||||
- 当字段数多导致超高时,Modal 内容区纵向滚动。
|
||||
|
||||
### 5.4 字段值渲染
|
||||
|
||||
- 纯函数 `formatCellValue(value)`:`null` / `undefined` / trim 后为空 → 渲染 `—`
|
||||
(全角破折号,便于区分「空值」与「未渲染」);其余 → `String(value)`。
|
||||
- 值样式:`whiteSpace: 'pre-wrap'`、`wordBreak: 'break-all'`,长内容自动换行撑高,
|
||||
完整可见不截断。
|
||||
|
||||
### 5.5 状态变更(`page.tsx`)
|
||||
|
||||
新增两个 state:
|
||||
|
||||
```ts
|
||||
const [contextMenu, setContextMenu] =
|
||||
useState<{ record: DataRow; x: number; y: number } | null>(null);
|
||||
const [detailRecord, setDetailRecord] = useState<DataRow | null>(null);
|
||||
```
|
||||
|
||||
`onRow` 返回值在现有 `onClick`(锁行)基础上新增 `onContextMenu`:
|
||||
|
||||
```ts
|
||||
onRow={(record) => ({
|
||||
onClick: () => setLockedRowKey((prev) => nextLockedRow(prev, String(record.ID))),
|
||||
onContextMenu: (e) => {
|
||||
e.preventDefault();
|
||||
setContextMenu({ record, x: e.clientX, y: e.clientY });
|
||||
},
|
||||
})}
|
||||
```
|
||||
|
||||
菜单项点击 → `setDetailRecord(contextMenu.record); setContextMenu(null);`。
|
||||
Modal 关闭 → `setDetailRecord(null)`。
|
||||
|
||||
> Modal 持有打开时刻的 `record` 快照,即使后台数据实时刷新也不会被冲掉。
|
||||
|
||||
### 5.6 涉及文件
|
||||
|
||||
| 文件 | 改动 |
|
||||
|------|------|
|
||||
| `src/app/page.tsx` | 新增 context-menu / detail-modal 状态与渲染;扩展 `onRow` |
|
||||
| `src/app/record-detail.ts`(新增) | 纯函数:`pickRecordTitle(record, columns)`、`formatCellValue(value)` |
|
||||
| `src/app/record-detail.test.ts`(新增) | 上述纯函数的单元测试(vitest) |
|
||||
|
||||
> 详情 Modal 与右键菜单浮层可直接内联在 `page.tsx`(v1 体量小),或抽成
|
||||
> `record-detail-modal.tsx` —— 视实现时 `page.tsx` 行数决定,避免单文件过大。
|
||||
|
||||
## 6. 边界情况
|
||||
|
||||
- **实时刷新**:Modal/菜单持有快照,不受数据刷新影响。
|
||||
- **空值字段**:渲染 `—`。
|
||||
- **超长单字段**(如多行技术参数):`pre-wrap` + `break-all`,Descriptions 项自动撑高,
|
||||
Modal 内滚动。
|
||||
- **表头 / 空白右键**:不触发本菜单(`onRow` 仅作用于数据行)。
|
||||
- **右键后行被筛选隐藏**:Modal 已持快照,仍可正常查看。
|
||||
- **主标识缺失**:标题回退到「(未命名)」。
|
||||
|
||||
## 7. 测试
|
||||
|
||||
项目已用 vitest(`src/app/table-filters.test.ts`)。沿用该模式,对**可纯函数化**的逻辑
|
||||
写单元测试,UI 行为以浏览器手验为准:
|
||||
|
||||
- `pickRecordTitle`:生产订单号优先 > 总排号 > ID > 「(未命名)」;各字段为空时的回退。
|
||||
- `formatCellValue`:null / undefined / 空串 / 纯空白 → `—`;普通值原样字符串化;
|
||||
数字 `0`、`false` 等非空值不误判为空。
|
||||
|
||||
UI 验证清单(浏览器):数据行右键弹菜单且屏蔽默认菜单;表头/空白右键无菜单;菜单项打开
|
||||
Modal 且内容为该行全部字段完整展示;长内容换行不截断;Esc/点击外部关闭菜单与 Modal;
|
||||
锁行(左键)与右键互不干扰。
|
||||
|
||||
## 8. 待实现时核对
|
||||
|
||||
- antd v6:`Dropdown`(受控 + `trigger=[]` + `overlayStyle` fixed 定位)、`Modal`、
|
||||
`Descriptions` 的实际 API 与类型(`ColumnsType` / `DataRow` 已有)。
|
||||
- 受控 Dropdown 定位若不符合预期,切换到 5.2 的兜底自定义浮层方案。
|
||||
Reference in New Issue
Block a user