20260726225220

This commit is contained in:
oneao committed 2026-07-26 22:52:21 +08:00
1 parent e3c7c6f8da
commit a8478d60ed
170 files changed
+6786 -1678

No files matched your search

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