Files
web-table/docs/superpowers/specs/2026-06-24-record-detail-context-menu-design.md
Misaka_Company 207bc6a7a1 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.
2026-06-24 11:09:14 +08:00

6.6 KiB
Raw Blame History

生产数据表 —— 右键查看完整记录(记录详情 Modal

  • 日期2026-06-24
  • 状态:设计(待评审)
  • 关联需求:生产部需求 ③「长内容字段完整显示」

1. 背景与问题

生产数据表字段多(约 60 列)、表格极宽(总宽 6000px+)。当前所有列 ellipsis: true src/app/page.tsx:159),长内容被 ... 截断,仅靠浏览器原生 title 气泡显示全文。 长规格类字段(技术参数 / 新参数 / 缺件明细 / 特殊要求 / 备注 等)难以完整查看。

需求 ①(行高亮)与 ②(字段筛选)已在代码中实现,本次只做 ③。

2. 目标

在不破坏现有「左键点击锁行」「列筛选」交互的前提下,提供一种零冲突、彻底的方式查看 某条记录的全部字段完整内容。

3. 方案概述

接管数据行的右键菜单:在数据行上右键 → 弹出自定义菜单(屏蔽浏览器默认菜单)→ 点「查看完整记录」→ 打开 Modal将该记录的所有数据字段完整铺开展示(含当前被 隐藏的列)。表格内联仍保持 ellipsisModal 作为「完整查看」入口。

4. 非目标YAGNI

  • 不改内联单元格的截断行为(仍是 ellipsis)。
  • v1 右键菜单仅一项「查看完整记录」;不做「复制整行 / 锁定行」等扩展项(后续可加)。
  • 不做列宽自适应、不做单元格自动换行(表格已过宽,会破坏紧凑度)。
  • 不做悬浮 Tooltip 预览(右键 Modal 已覆盖该诉求)。

5. 详细设计

5.1 交互流程

  1. 鼠标在任一数据行上右键 → 阻止浏览器默认菜单,在光标处弹出自定义菜单, 仅含「查看完整记录」。
  2. 点击该菜单项 → 打开记录详情 Modal同时关闭右键菜单。
  3. 右键菜单在以下情况关闭:选中菜单项、点击页面其它处、滚动、按 Esc、在别处再次右键。
  4. 表头 / 空白区域右键不弹本菜单(无法定位「哪条记录」)。

5.2 右键菜单(受控、行级作用域)

实现策略:通过 TableonRow.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 Modalopen={!!detailRecord}onCancel 关闭,destroyOnClose
  • 宽度:约 900px居中。
  • 标题:记录详情:${主标识}。主标识取值优先级: 生产订单号总排号ID(取第一个非空字段,否则显示「(未命名)」)。
  • 内容antd Descriptionscolumn={2}borderedsize="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

const [contextMenu, setContextMenu] =
  useState<{ record: DataRow; x: number; y: number } | null>(null);
const [detailRecord, setDetailRecord] = useState<DataRow | null>(null);

onRow 返回值在现有 onClick(锁行)基础上新增 onContextMenu

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.tsxv1 体量小),或抽成 record-detail-modal.tsx —— 视实现时 page.tsx 行数决定,避免单文件过大。

6. 边界情况

  • 实时刷新Modal/菜单持有快照,不受数据刷新影响。
  • 空值字段:渲染
  • 超长单字段(如多行技术参数):pre-wrap + break-allDescriptions 项自动撑高, Modal 内滚动。
  • 表头 / 空白右键:不触发本菜单(onRow 仅作用于数据行)。
  • 右键后行被筛选隐藏Modal 已持快照,仍可正常查看。
  • 主标识缺失:标题回退到「(未命名)」。

7. 测试

项目已用 vitestsrc/app/table-filters.test.ts)。沿用该模式,对可纯函数化的逻辑 写单元测试UI 行为以浏览器手验为准:

  • pickRecordTitle:生产订单号优先 > 总排号 > ID > 「(未命名)」;各字段为空时的回退。
  • formatCellValuenull / undefined / 空串 / 纯空白 → ;普通值原样字符串化; 数字 0false 等非空值不误判为空。

UI 验证清单(浏览器):数据行右键弹菜单且屏蔽默认菜单;表头/空白右键无菜单;菜单项打开 Modal 且内容为该行全部字段完整展示长内容换行不截断Esc/点击外部关闭菜单与 Modal 锁行(左键)与右键互不干扰。

8. 待实现时核对

  • antd v6Dropdown(受控 + trigger=[] + overlayStyle fixed 定位)、ModalDescriptions 的实际 API 与类型(ColumnsType / DataRow 已有)。
  • 受控 Dropdown 定位若不符合预期,切换到 5.2 的兜底自定义浮层方案。