# 代码审查问题清单 > 审查范围:`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) | 功能缺失 |