Files
workspace/code/fms/.codebuddy/plans/FmsTable_内置单元格编辑_a4f723e8.md
T
2026-07-26 22:52:21 +08:00

9.0 KiB
Raw Blame History

name, overview, todos
name overview todos
FmsTable 内置单元格编辑 将当前散落在 ModulePowersPanel.vue 的“单行/单元格编辑”逻辑(row._editing 协议 + handleAreaSelectionChange + col._onEditEnd)下沉到 FmsTable 内,通过 editable: boolean prop 控制启用,组件自闭环;现有唯一调用方 ModulePowersPanel 同步改写为新 API。
id content status
refactor-utils 重构 createEditableCellRenderer.js 与 createCellRenderer.js,使 createInputCell 通过闭包读取 editingState,resolveCustomCell 根据 ctx.editable 决定是否注入 input customCell completed
id content status dependencies
enhance-fmstable 在 FmsTable.vue 新增 editable prop、editingState 内部 shallowRef、internalData + watch、handleAreaSelectionChange 处理器与 change 事件 emit,将 StkTable 的 dataSource 指向 internalData completed
refactor-utils
id content status dependencies
refactor-powers-panel 改写 ModulePowersPanel.vue:移除 handleEditEnd / handleAreaSelectionChange 函数,列定义移除 _onEditEnd 与 onChange,模板改为 :editable="true" + @change="onCellChange",清理无效的 :area-selection 与 @area-selection-change completed
enhance-fmstable

Product Overview

将当前散落在 ModulePowersPanel.vue 的"单元格编辑"逻辑(row._editing 协议 + handleAreaSelectionChange + col._onEditEnd)下沉到 FmsTable 内部。组件自闭环,通过 editable: boolean prop 控制是否启用单元格编辑。

Core Features

  • 新增 FmsTable.editable: boolean prop(默认 false),type:"input" 列在该 prop 为 true 时才进入可编辑态
  • FmsTable 内部维护 editingState: { rowKey, col } | null(shallowRef),不挂在 row 上,零数据污染,父组件保存前无需清理
  • FmsTable 内部监听 @area-selection-change(鼠标点击 / 键盘方向键统一入口),自动把命中单元格设为编辑格
  • 内部 watch editingState → 浅拷贝内部 dataSource → 触发 stk-table 重渲染让 customCell 重新求值
  • 退出编辑(Enter / Esc / Blur)时 emit("change", row, col, newValue) 给父组件
  • 沿用现有 createInputCell 的"非编辑零 VNode / 原生 input / 自动 focus.select()" 行为
  • ModulePowersPanel.vue 同步改写为新 API:删除 handleEditEnd / handleAreaSelectionChange / 列定义中的 _onEditEnd / onChange,模板改为 :editable="true" + @change="onCellChange"
  • 其他 7 个 FmsTable 调用方(dashboard / TableDemo / i18n-type / I18nPanel / Fields / ListConfig / EditConfig / QueryConfig)完全不受影响——editable 默认 false,与现状一致

Tech Stack

  • Vue 3 <script setup> + shallowRef + watch
  • 复用 stk-table-vue 已有的 area-selection 与 customCell 机制
  • 无新增依赖、无新增组件库、无 composable 文件

Implementation Approach

核心策略:把"编辑态"从 row 对象上的 _editing 字段迁移到 FmsTable 内部的 shallowRef;customCell 通过闭包读取 editingState;通过内部 dataSource 浅拷贝触发 stk-table 重渲染。

数据流(一次完整编辑)

stk-table area-selection-change(ranges)
  ↓
FmsTable.handleAreaSelectionChange(ranges)
  ├─ !props.editable → return
  ├─ ranges 为空 / 列非 input / 行越界 → editingState = null
  ├─ editingState 已指向同一 row+col → return(避免方向键连按重渲染)
  └─ editingState = { rowKey, col } → watch → internalData = [...internalData] → 重渲染
       ↓
customCell({ row, cellValue }) 重跑
  ├─ editing 不匹配 → 返回纯文本字符串(零 VNode 开销)
  └─ 匹配 → 返回原生 <input ref=focus.select()>
       ├─ onInput: row[col.dataIndex] = e.target.value
       └─ onBlur / Enter / Esc → notifyChange(row, col, newValue)
             ├─ emit("change", row, col, newValue) → 父 onCellChange → emitUpdate
             └─ editingState = null → watch → 重渲染 → 文本回退

关键设计决策

  1. editable: boolean(表格级):type:"input" 列在 editable=true 时由 resolveCustomCell 注入自定义 customCell;false 时回退到 stk-table 默认文本渲染,零运行时开销
  2. editingState 用 shallowRef,不挂 row:避免"保存前清理 _editing 字段"的脏数据风险;customCell 通过闭包读 ctx.getEditing()
  3. 只 emit change(row, col, newValue):进入编辑不发事件(语义冗余);退出编辑统一把"新值"冒泡给父组件用于脏数据判定
  4. 内部 internalData 浅拷贝触发重渲染:复用 stk-table 的 dataSource 响应式机制;editingState 变更 → watch → 引用变更 → 重渲染
  5. 复用现有 createInputCell 行为:Enter=不退出(允许多行编辑后续扩展)/ Esc=退出 / Blur=退出 / 自动 focus().select()
  6. 沿用硬编码 :area-selection(keyboard:true / ctrl:false / shift:false),不动——本次删除 ModulePowersPanel 上无效的覆盖赋值属清理

性能保证

  • non-editing 单元格仍然"返回字符串,零 VNode"(createInputCell 早返回)
  • editingState 用 shallowRef,watch 仅在状态变更时触发
  • 方向键连按同一目标时早 return,避免每按一次重渲染整表
  • checkbox / actions 渲染器继续用 WeakMap 缓存,不受影响

Implementation Notes

  • props.dataSource 外部引用变更(含父组件 localPowers = [...localPowers])会同步触发 watch;editingState 内的 rowKey 是字符串,与引用解耦,引用变更不影响编辑态判定
  • props.editable 由 true → false 动态切换时,displayColumns 重算 → resolveCustomCell 返回无 customCell 的 col → stk-table 回退到默认文本渲染
  • ModulePowersPanel 的 localPowers ref / addRow / deleteRow / batchDelete 行为不变(仍是父组件编辑副本),仅事件链路从"列 onChange"改为"@change"
  • 删除 :area-selection="true"(被硬编码覆盖,无效)和 @area-selection-change(下沉的入口)属"现状无效 prop 清理",不改变运行时行为

Directory Structure

fms-vue/src/
├── components/fms-table/
│   ├── FmsTable.vue                          [MODIFY] 新增 editable prop + editingState 内部状态 +
│   │                                                   handleAreaSelectionChange + change emit,
│   │                                                   dataSource 改为内部 shallowRef 触发重渲染
│   └── utils/
│       ├── createCellRenderer.js             [MODIFY] resolveCustomCell(col, ctx) 接 ctx,
│   │                                                   editable=false 时不注入 input customCell
│       └── createEditableCellRenderer.js     [MODIFY] createInputCell(col, ctx) 通过闭包读
│                                                            editingState;onExit→ctx.onExit(row, val)
└── views/module-management/components/
    └── ModulePowersPanel.vue                 [MODIFY] 删 handleEditEnd/handleAreaSelectionChange;
                                                        列定义移除 _onEditEnd / onChange;
                                                        模板加 editable + @change="onCellChange",
                                                        删 :area-selection / @area-selection-change

关键代码契约

FmsTable 关键改动点

位置 改动
props (L9-22) 新增 editable: { type: Boolean, default: false }
defineEmits (L24) 新增 "change"
新增 editingState = shallowRef(null) 编辑态内部 ref
新增 internalData = shallowRef([]) + 两个 watch dataSource 同步 + 编辑态触发浅拷贝重渲染
displayColumns (L41-55) 改为 props.columns.map(col => resolveCustomCell(col, ctx)),ctx 每次重算以追踪 props.editable
新增 handleAreaSelectionChange(ranges) 校验 editable / ranges / row / col,写入 editingState
新增 notifyChange(row, col, val) emit change + 清空 editingState
<StkTable> :data-source (L120) 由 dataSource 改为 internalData

utils 新签名

// createEditableCellRenderer.js
createInputCell(col, ctx)
//   ctx = { getEditing, onExit(row, col, val), getRowKey }

// createCellRenderer.js
resolveCustomCell(col, ctx)
//   editable=false → type:"input" 列不注入 customCell(stk-table 默认文本)
//   editable=true  → 注入 createInputCell(col, ctx)

ModulePowersPanel 改动要点

  • 删除 handleEditEnd、handleAreaSelectionChange 两个函数(约 L100-141)
  • 列定义移除 _onEditEnd: handleEditEnd 与 onChange: () => emitUpdate()(L200-203 共 4 处)
  • 模板 <FmsTable> 加 editable 与 @change="onCellChange",移除无效的 :area-selection 与 @area-selection-change
  • 新增 function onCellChange(row, col, val) { emitUpdate() }
  • localPowers ref / addRow / deleteRow / batchDelete 逻辑不变