Files
workspace/code/fms/问题.md
T
2026-08-26 17:31:31 +08:00

115 lines
8.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 代码审查问题清单
> 审查范围:`ModuleEditConfigPanel.vue`(4510 行)、`index.vue`、`FieldGroupManager.vue`、`FmsTable.vue`
> 日期:2026-08-26
---
## 一、功能性 bug(会真实出错)
### 1. 子表预览弹窗没有打开入口
**P1**
- `previewOpen` 定义于 `ModuleEditConfigPanel.vue:611`,弹窗渲染于 1998–2087 行
- 全文件检索不到任何设置 `previewOpen = true` 的地方,也没有「预览」按钮
- 弹窗 `:open` 永远为 false → **子表预览功能不可达**
- `tableColumnRows`/`previewGroupHeader`/列宽拖拽手柄那一整套 WYSIWYG 预览全被闲置
### 2. `libGroup`/`columnGridGroup` 分组配置不一致(跨分组拖拽可能失效)
- 字段库:`libGroup = { name: 'form', pull: 'clone', put: false }`(1461 行)
- 子表列网格:`columnGridGroup = { name: 'form' }`(1464 行)→ 子表列之间可以互相移动
- **主表画布网格:内联 `:group="{ name: 'form' }"`(2165 行),默认 `pull: true`** → 字段库(`put: false`)往主表画布放 → 会被 SortableJS 拒绝
- 主表模式其实有自己的 `onLibDragEnd`(1368 行)兜底逻辑,注释也说"源=字段库,目标列表只收到 add/sort"
- 这是一个「看起来会被拒、实际靠 onLibDragEnd 兜底」的双轨实现——逻辑相当绕,一旦某个 onMove/onStart/onEnd 时序变化就会暴露。**主表拖入的可靠性完全押在 sortable 事件顺序上**,比较脆弱。
### 3. 分组标题重命名有 blur/点击竞态(很可能被覆盖丢改动)
- `commitEditGroupTitle` 同时绑定在输入框 `@blur` 和确认按钮 `@mousedown.prevent`(1806、1816、1824 行)
- 在按钮上按下鼠标 → `@mousedown.prevent` 阻止了默认行为,不会先触发 blur,顺序基本 OK
- 真正的坑是:**直接点「删除分组」或拖拽到其它分组时**,输入框先失焦提交、随后 `updateGroups`/`syncList` 把 props.groups 换成新数组,但 `groupTitleDraft` 只在 commit 里清。整体靠时序兜底,多步操作间容易丢改动(详见第 11 条联动问题)。
### 4. 表格模式下列宽与 `b_height` 语义混乱
**P0**
- `formDataColumns`(315 行)里 `b_height` 是「文本域高度」,但 `b_height` 会被 `normalizedRow` 统一 `Number()` 化(160 行);表格模式 `tableDataColumns` 没有 b_height 列
- **`b_width` 在 diff/save 时会被完整提交**——它既不是后端字段也不是 UI 控件,只是一次拖拽调宽的临时状态,随保存写库会造成脏数据(后端 `s_field_edit` 若没有该列,`update` 会因"字段不存在"报错或写进多余列)
- **栅格宽度口径不一致**:主表设计视图用「24 栅格」,但 `COLSPAN_OPTIONS`(428 行)写的是 `整行 24/24 / 2/3 行 16/24 / 半行 12/24…`——文本写着 24 栅格,但 `spanOf` 支持 1~24,`b_colspan` 默认 24。如果后端 `b_colspan` 存的是 12 制(计划文档 §10.2 说"12 栅格"),前端按 24 制渲染会导致整行永远多一倍宽。需要和后端表结构核对,目前前后端口径不一致的风险很高。
---
## 二、交互体验问题
### 5. 字段库「点击=添加」与「拖拽=添加」语义不一致
**P1**
- 点击字段库项 → `selectField`(862 行):**已加入的字段再点击会被移除**(867 行)
- 拖拽字段到画布 → 已在表单中会被 `Message.warning` 拒绝(1386 行)
- 同一动作两种结果,用户容易误删字段。建议点击已加入项改为「定位选中」,把移除收敛到 Delete/删除按钮。
### 6. 拖拽意图判定在窄卡片上极易误判
- `updateDragIntent`(959 行)按「鼠标到卡片四条边的归一化最近距离」决定插入方向(1024-1030 行)
- 字段卡片通常只有 1/2~1/4 行宽(约 150~300px),宽高比很大,**左右方向几乎总是胜出** → 用户想「拖到下一行」时总是落在同行前后
- 加上 hover 自身只给上/下(998-1009 行),两种判定阈值不一致,操作学习成本高、误操作率高
### 7. 拖拽期间 `dragLine` 只清不锁
- Esc 取消只清 `dragLine`/`dragOverSection`(125-129 行),SortableJS 的拖拽会话仍继续
- **中途 Esc 后继续移动,`onDragShadowMove` 会重新计算 intent**,表现为"取消失效"
### 8. 撤销栈是「操作级」而非「原子级」,且深度受限
- `pushHistory` 只压 `snapshotRows`(浅拷贝数组),相邻重复快照去重(99 行)
- `onColspanInput`(1503)每次 input 事件压栈 → 连续输入数字产生 N 个撤销点
- `onWidthInput`(533)同样逐键压栈
- 列宽拖拽 `startColumnResize` 压一次但 `onColumnResizeMove` 每帧改 `row.b_width`,mouseup 时才 emit → **撤销栈里的中间宽度和最终宽度不一致**,Ctrl+Z 会跳到拖拽中间值
- 这些组合下 30 条 HISTORY_LIMIT 很快就耗尽,且撤销点不直观
### 9. 删除字段的「撤销提示条」有 8 秒超时但无进度
- `undoTimer` 8 秒后清 `lastDeleted`(1539 行),提示条消失**且无倒计时/动画提示**
- 若用户晚于 8 秒想恢复,只能靠 Ctrl+Z(且此时 Ctrl+Z 撤销的是整表快照,会连带恢复已删除的字段布局,可能更糟)
### 10. 分组方向(左右/上下)开关被隐藏
- `toggleLayout` 定义在 `FieldGroupManager.vue:111`,但只有「分组管理」弹窗里每个节点有该按钮(280-283 行)
- 主画布上只有 hover 才出现的分组删除按钮,**画布上没有任何分组方向切换入口**——用户必须打开弹窗才能调左右布局,体验割裂
### 11. 删除分组直接改动 listConfig/editConfig,但不进撤销栈
- `deleteGroupDirect`(760 行)会改 `props.listConfig`(`syncList`)并把字段移未分组
- 但这些改动**不推入 history**,Ctrl+Z 无法回退分组删除本身(只能回退字段归属)
---
## 三、一致性与可维护性
### 12. `formDirection`/`globalDirection` 死代码
**P0**
- `formDirection = computed(() => globalDirection.value)`(644 行)引用的 `globalDirection` 全文件不存在
- **该 computed 会在首次求值时抛 ReferenceError**,任何路径访问 `formDirection` 都会崩
- 目前没人引用它所以没炸,但这是颗定时炸弹
### 13. 大量字段/状态重复定义
- `FIELD_TYPE_LABELS`(416)在 `libraryGroups`(667)和 `controlTypeLabel`(468)各用一次,类型集散落在 `placeholderOf`(481)、`defaultWidthOf`(512)、`SAMPLE_TEXT`(628)、`field-control` 模板(2254-2298)四处,新增类型要改五个地方
- 属性面板(主表 2527、子表 2385)几乎整段重复,只有宽度/高度两处不同
- `COLSPAN_OPTIONS` 只在数据视图用(363 行 `editor: { type: 'select', options: () => COLSPAN_OPTIONS }`),**设计视图属性面板用的是 `mm-fdesign__span-control` 数字输入框**,两处口径(24 制 vs 数字 1-24)分开维护
### 14. `placeholderOf` 只读字段被忽略
- `placeholderOf`(481 行)返回默认占位符,但 `placeholder` 在画布控件上是**只读展示**(`tabindex="-1"` + `pointer-events:none`)
- 属性面板里根本没有占位符编辑入口(只有数据视图的 FmsTable 有 `b_placeholder` 列)
- 即**设计视图里无法设置占位符**——功能存在但 UI 缺失
### 15. 快捷键在「预览(数据视图)」视图下静默失效
- `onDesignerKeydown` 要求 `viewMode === 'design'`(1569 行),但数据视图的 FmsTable 里 Ctrl+C/V/Z 会被浏览器/表格接管
- 设计视图的复制/撤销/Delete 快捷键全不生效,且没有任何提示(帮助浮层只在设计视图头部)
- 用户切到数据视图按 Delete 想删字段会直接没反应
### 16. 无保存时「重载」与「切换」的确认文案不一致
- `index.vue` 用两个不同的弹窗:重载用通用 `showConfirm`(236-244),切换用专门的 `switchConfirm`(332-341、1221-1236)
- 两个弹窗行为基本一致但代码/文案/按钮布局重复
- 切换弹窗缺「保存并切换失败时的错误提示」路径:`onSwitchSave` 里 `saveModule()` 返回 false 时直接留在弹窗,没有给用户反馈为什么失败
---
## 建议优先级
| 优先级 | 事项 | 理由 |
|--------|------|------|
| P0 | 修 `formDirection`/`globalDirection` 死代码(#12) | 一旦有人引用就崩 |
| P0 | 统一 24/12 栅格口径,核对 `b_colspan` 后端字段(#4) | 前后端不一致=整行渲染错误 |
| P1 | 给子表预览加入口或移除死代码(#1) | 功能不可达 |
| P1 | 撤销栈改「原子操作」+ 修复列宽拖拽中间态(#8) | 直接影响日常编辑 |
| P1 | 字段库点击/拖拽语义统一(#5) | 防误删 |
| P2 | 拖拽意图判定、Esc 取消锁定、删除分组入撤销栈等(#6/#7/#11) | 打磨体验 |
| P2 | 占位符编辑入口、分组方向画布入口、数据视图快捷键(#14/#10/#15) | 功能缺失 |