From 4e167df0f00824bb0357bf80b446e6bd6ef3ab9f Mon Sep 17 00:00:00 2001 From: ZhangAo Date: Fri, 4 Sep 2026 17:31:36 +0800 Subject: [PATCH] 20260904173136 --- ...management-index-重构-面板自治_3c0e8f17.md | 266 ++++ code/fms/ai-plans/TASK1.md | 126 ++ code/fms/fms-vue/mm1.yaml | 40 - .../public/module-management-layout-demo.html | 313 ----- .../fms-module-common/fieldUtils.js | 50 + .../queryUtils.js} | 50 +- .../fms-module-edit/FmsModuleEditModal.vue | 7 +- .../fms-module-list/FmsModuleListPage.vue | 4 +- .../fms-module-list/FmsModuleTable.vue | 2 +- .../fms-module-list/FmsQueryControl.vue | 7 +- .../fms-module-list/FmsQueryToolbar.vue | 2 +- .../src/components/fms-module-list/index.js | 1 - code/fms/fms-vue/src/router/index.js | 7 - code/fms/fms-vue/src/utils/dataChanges.js | 46 +- .../module/menu-management/MenuBasicPanel.vue | 160 +++ .../menu-management/MenuPowerLibraryPanel.vue | 191 +++ .../menu-management/MenuPowersPanel.vue | 196 +++ .../module/menu-management/MenuTreePanel.vue | 262 ++++ .../views/module/menu-management/index.vue | 1082 ++++------------- .../src/views/module/menu-management/utils.js | 90 ++ .../module-management/ModuleAutoCodePanel.vue | 123 +- .../module-management/ModuleBasicPanel.vue | 85 +- .../module-management/ModuleEditPanel.vue | 44 +- .../module-management/ModuleFieldsPanel.vue | 199 ++- .../module-management/ModuleI18nPanel.vue | 2 +- .../ModuleListConfigPanel.vue | 13 +- .../ModuleQueryConfigPanel.vue | 50 +- .../module-management/ModuleTreePanel.vue | 232 ++++ .../design-editor/FormDesignEditor.vue | 111 +- .../design-editor/TableDesignEditor.vue | 55 +- .../common/useStructureEditing.js | 4 +- .../design-editor/form/FormInspector.vue | 4 +- .../design-editor/table/TableInspector.vue | 4 +- .../views/module/module-management/index.vue | 947 +++++---------- .../views/module/module-management/utils.js | 89 ++ code/fms/fms-vue/src/views/tmp-dnd-panel.vue | 48 - .../views/menu-unsaved-node-switch.spec.js | 12 +- code/fms/历史记录选择器配置设计讨论.md | 619 ++++++++++ code/fms/字段权限与字段个性化设计.md | 159 +++ code/fms/开发规范.md | 83 +- 40 files changed, 3471 insertions(+), 2314 deletions(-) create mode 100644 code/fms/.codebuddy/plans/module-management-index-重构-面板自治_3c0e8f17.md create mode 100644 code/fms/ai-plans/TASK1.md delete mode 100644 code/fms/fms-vue/mm1.yaml delete mode 100644 code/fms/fms-vue/public/module-management-layout-demo.html create mode 100644 code/fms/fms-vue/src/components/fms-module-common/fieldUtils.js rename code/fms/fms-vue/src/components/{fms-module-list/moduleListUtils.js => fms-module-common/queryUtils.js} (67%) create mode 100644 code/fms/fms-vue/src/views/module/menu-management/MenuBasicPanel.vue create mode 100644 code/fms/fms-vue/src/views/module/menu-management/MenuPowerLibraryPanel.vue create mode 100644 code/fms/fms-vue/src/views/module/menu-management/MenuPowersPanel.vue create mode 100644 code/fms/fms-vue/src/views/module/menu-management/MenuTreePanel.vue create mode 100644 code/fms/fms-vue/src/views/module/menu-management/utils.js create mode 100644 code/fms/fms-vue/src/views/module/module-management/ModuleTreePanel.vue delete mode 100644 code/fms/fms-vue/src/views/tmp-dnd-panel.vue create mode 100644 code/fms/历史记录选择器配置设计讨论.md create mode 100644 code/fms/字段权限与字段个性化设计.md diff --git a/code/fms/.codebuddy/plans/module-management-index-重构-面板自治_3c0e8f17.md b/code/fms/.codebuddy/plans/module-management-index-重构-面板自治_3c0e8f17.md new file mode 100644 index 00000000..ec68eed7 --- /dev/null +++ b/code/fms/.codebuddy/plans/module-management-index-重构-面板自治_3c0e8f17.md @@ -0,0 +1,266 @@ +--- +name: module-management-index-重构-面板自治 +overview: 重构 fms-vue 模块管理页:将 index.vue(1600 行)中散落的面板数据回写回调与业务逻辑下放到各面板组件,index 只持有 drafts/originals 两份数据,通过 v-model 与面板双向绑定,保存时统一 diff。目标是将 index.vue 精简到 500 行以内。 +todos: + - id: diff-foundation + content: 改造 utils/dataChanges.js:diffRows 增加剔除 _ / v_ 前缀字段,抽出 stripInternalKeys 供 diffRows 与 hasUnsavedChanges 共用 + status: completed + - id: model-table-form-panels + content: 模式 A/C 改造表格类与表单类面板:ModuleFieldsPanel、ModuleQueryConfigPanel、ModuleBasicPanel、ModuleAutoCodePanel 改 defineModel,去掉 local 副本、watch 同步与虚拟字段剥离 + status: completed + dependencies: + - diff-foundation + - id: model-designers + content: 用 [subagent:code-explorer] 与 [skill:lsp-code-analysis] 定位并改造 FormDesignEditor、TableDesignEditor 及壳组件 ModuleListConfigPanel、ModuleEditPanel 为 defineModel,useStructureEditing 只换写入口 + status: completed + dependencies: + - model-table-form-panels + - id: index-rewire + content: 重接 index.vue:全部面板改 v-model 双向绑定,删除 4 个 proxy 回调,新增分组引用清理、自动编码只读、i18n reconcile 三个跨面板 watch + status: completed + dependencies: + - model-designers + - id: sync-fields-down + content: 将 buildSyncPlan、applySyncPlan、syncFields 迁入 ModuleFieldsPanel,同步按钮移至字段面板工具栏,index 相应删除调用与按钮 + status: completed + dependencies: + - index-rewire + - id: extract-module-tree + content: 新建 useModuleTree.js 抽出模块树的加载、新增、删除、拖拽排序与右键菜单逻辑,index.vue 改为解构接入 + status: completed + dependencies: + - sync-fields-down + - id: verify-regression + content: 编译校验与关键流程回归:新建模块保存、字段同步、分组增删、自动编码、i18n、模块拖拽排序、切换模块脏检查 + status: completed + dependencies: + - extract-module-tree +--- + +## 用户要求 + +对 `fms-vue/src/views/module/module-management/index.vue`(1600 行)进行职责解耦重构: + +- **index.vue 只保留两个核心状态**:`drafts`(草稿数组对象)与 `originals`(基线数组对象),保存时执行一次 diff 即可。 +- **各面板自治**:字段、列表、编辑、查询、自动编码、多语言、基础信息各自的编辑逻辑与数据写入,下沉到各自组件内部,通过 `v-model` 双向绑定自己那份数据,不再由父级中转。 + +## 产品概述 + +模块管理页面(左侧模块树 + 右侧分节配置面板)的内部架构重构,属纯前端逻辑层改造,页面功能与视觉表现保持不变,仅"同步字段"按钮从顶部通栏移入字段定义面板工具栏。 + +## 核心特性 + +- **数据所有权下沉**:7 个配置面板通过 `defineModel` 直接读写 `drafts` 中属于自己的那一份数据,父级不再提供 proxy 回调。 +- **保存时统一 diff**:index.vue 保留 `drafts` / `originals` 两份快照,`handleSave` 时按主表 `diffRow`、子表 `diffRows`、i18n `buildI18nChangeRequest` 生成差异请求。 +- **跨面板联动改为响应式**:分组删除清理引用、自动编码字段置只读、受管 i18n 键补齐,由事件回调改为父级 `watch` 幂等联动。 +- **字段同步内聚**:`syncFields` / `buildSyncPlan` / `applySyncPlan` 整体移入 `ModuleFieldsPanel`,同步按钮随行迁至字段面板工具栏。 +- **树逻辑外移**:模块树的加载、新增、删除、拖拽排序、右键菜单(约 250 行)抽为 `useModuleTree.js`。 + +## 预期效果 + +`index.vue` 由 1600 行降至 500 行以内,仅剩编排职责(树 + 草稿加载 + 保存编排 + 3 个跨面板 watch + 模板接线);面板组件各自内聚、可独立测试。 + +## 技术栈 + +沿用项目现有技术栈,不引入新依赖: + +- Vue 3 ` - - diff --git a/code/fms/fms-vue/src/components/fms-module-common/fieldUtils.js b/code/fms/fms-vue/src/components/fms-module-common/fieldUtils.js new file mode 100644 index 00000000..309803fc --- /dev/null +++ b/code/fms/fms-vue/src/components/fms-module-common/fieldUtils.js @@ -0,0 +1,50 @@ +// 模块数据驱动列表/编辑的「字段展示」工具:解析下拉选项、格式化日期等。 +// 供 fms-module-list / fms-module-edit 等按模块元数据渲染的组件共用; +// 查询条件构建(buildQueryCondition 等)见 queryUtils.js。 +import { isEmptyValue } from './queryUtils' + +const padDatePart = (number) => String(number).padStart(2, '0') + +export function parseOptions(field) { + if (!field?.b_options) return { optionConfig: {}, options: [] } + try { + const parsed = JSON.parse(field.b_options) + let optionConfig = {} + let source = [] + if (Array.isArray(parsed)) { + source = parsed + } else if (parsed && typeof parsed === 'object') { + optionConfig = parsed + if (Array.isArray(parsed.options)) { + source = parsed.options + } else if (Array.isArray(parsed.items)) { + source = parsed.items + } + } + const options = source.map((option) => { + if (option && typeof option === 'object') { + const value = option.value ?? option.key ?? option.id + return { ...option, value, label: String(option.label ?? option.name ?? value ?? '') } + } + return { value: option, label: String(option ?? '') } + }) + return { optionConfig, options } + } catch { + return { optionConfig: {}, options: [] } + } +} + +export function formatDateValue(value, withTime = false) { + if (isEmptyValue(value)) return '' + if (value && typeof value.format === 'function') { + return value.format(withTime ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD') + } + const text = String(value).replace('T', ' ') + if (/^\d{4}-\d{2}-\d{2}/.test(text)) return withTime ? text.slice(0, 19) : text.slice(0, 10) + const date = new Date(value) + if (Number.isNaN(date.getTime())) return text + const datePart = `${date.getFullYear()}-${padDatePart(date.getMonth() + 1)}-${padDatePart(date.getDate())}` + return withTime + ? `${datePart} ${padDatePart(date.getHours())}:${padDatePart(date.getMinutes())}:${padDatePart(date.getSeconds())}` + : datePart +} diff --git a/code/fms/fms-vue/src/components/fms-module-list/moduleListUtils.js b/code/fms/fms-vue/src/components/fms-module-common/queryUtils.js similarity index 67% rename from code/fms/fms-vue/src/components/fms-module-list/moduleListUtils.js rename to code/fms/fms-vue/src/components/fms-module-common/queryUtils.js index d372d834..768db44a 100644 --- a/code/fms/fms-vue/src/components/fms-module-list/moduleListUtils.js +++ b/code/fms/fms-vue/src/components/fms-module-common/queryUtils.js @@ -1,9 +1,9 @@ -// 数据驱动列表的共享工具函数,避免在各组件间重复实现查询/格式化逻辑。 +// 模块数据驱动列表的「查询条件」工具:构建查询 SQL 片段、解析查询输入/默认值。 +// 供 fms-module-list / fms-module-edit 等按模块元数据渲染的组件共用; +// 展示格式化(parseOptions / formatDateValue)见 fieldUtils.js。 export const identifierRe = /^[A-Za-z_][A-Za-z0-9_]*$/ -const padDatePart = (number) => String(number).padStart(2, '0') - export function escapeSqlText(value) { return String(value).replaceAll("'", "''") } @@ -18,35 +18,6 @@ export function isEmptyValue(value) { return value === undefined || value === null || value === '' } -export function parseOptions(field) { - if (!field?.b_options) return { optionConfig: {}, options: [] } - try { - const parsed = JSON.parse(field.b_options) - let optionConfig = {} - let source = [] - if (Array.isArray(parsed)) { - source = parsed - } else if (parsed && typeof parsed === 'object') { - optionConfig = parsed - if (Array.isArray(parsed.options)) { - source = parsed.options - } else if (Array.isArray(parsed.items)) { - source = parsed.items - } - } - const options = source.map((option) => { - if (option && typeof option === 'object') { - const value = option.value ?? option.key ?? option.id - return { ...option, value, label: String(option.label ?? option.name ?? value ?? '') } - } - return { value: option, label: String(option ?? '') } - }) - return { optionConfig, options } - } catch { - return { optionConfig: {}, options: [] } - } -} - export function isRangeQuery(item) { return ( item.config.b_operator === 'between' || @@ -160,18 +131,3 @@ export function buildQueryCondition(item, value) { } return `${field} = ${literal}` } - -export function formatDateValue(value, withTime = false) { - if (isEmptyValue(value)) return '' - if (value && typeof value.format === 'function') { - return value.format(withTime ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD') - } - const text = String(value).replace('T', ' ') - if (/^\d{4}-\d{2}-\d{2}/.test(text)) return withTime ? text.slice(0, 19) : text.slice(0, 10) - const date = new Date(value) - if (Number.isNaN(date.getTime())) return text - const datePart = `${date.getFullYear()}-${padDatePart(date.getMonth() + 1)}-${padDatePart(date.getDate())}` - return withTime - ? `${datePart} ${padDatePart(date.getHours())}:${padDatePart(date.getMinutes())}:${padDatePart(date.getSeconds())}` - : datePart -} diff --git a/code/fms/fms-vue/src/components/fms-module-edit/FmsModuleEditModal.vue b/code/fms/fms-vue/src/components/fms-module-edit/FmsModuleEditModal.vue index 856350e3..4d6ecbe0 100644 --- a/code/fms/fms-vue/src/components/fms-module-edit/FmsModuleEditModal.vue +++ b/code/fms/fms-vue/src/components/fms-module-edit/FmsModuleEditModal.vue @@ -35,11 +35,8 @@ import { } from '@/components/ui' import { loadDataApi, loadDataBySqlApi, nextIdApi, saveObjectApi } from '@/services/api' import { diffRow } from '@/utils/dataChanges' -import { - identifierRe, - isEmptyValue, - parseOptions, -} from '@/components/fms-module-list/moduleListUtils' +import { identifierRe, isEmptyValue } from '@/components/fms-module-common/queryUtils' +import { parseOptions } from '@/components/fms-module-common/fieldUtils' const props = defineProps({ /** 受控打开 */ diff --git a/code/fms/fms-vue/src/components/fms-module-list/FmsModuleListPage.vue b/code/fms/fms-vue/src/components/fms-module-list/FmsModuleListPage.vue index 61688f84..97387e81 100644 --- a/code/fms/fms-vue/src/components/fms-module-list/FmsModuleListPage.vue +++ b/code/fms/fms-vue/src/components/fms-module-list/FmsModuleListPage.vue @@ -10,9 +10,9 @@ import { buildQueryCondition, defaultQueryValue, escapeSqlText, - parseOptions, requireModuleId, -} from './moduleListUtils' +} from '@/components/fms-module-common/queryUtils' +import { parseOptions } from '@/components/fms-module-common/fieldUtils' import FmsModuleTable from './FmsModuleTable.vue' import FmsQueryToolbar from './FmsQueryToolbar.vue' diff --git a/code/fms/fms-vue/src/components/fms-module-list/FmsModuleTable.vue b/code/fms/fms-vue/src/components/fms-module-list/FmsModuleTable.vue index b7e2d4f4..a63da4ba 100644 --- a/code/fms/fms-vue/src/components/fms-module-list/FmsModuleTable.vue +++ b/code/fms/fms-vue/src/components/fms-module-list/FmsModuleTable.vue @@ -3,7 +3,7 @@ import { computed, h, ref, shallowRef, useSlots, watch } from 'vue' import { Checkbox } from '@/components/ui' import FmsTable from '@/components/fms-table/FmsTable.vue' import { useI18nStore } from '@/stores/i18n' -import { formatDateValue, parseOptions } from './moduleListUtils' +import { formatDateValue, parseOptions } from '@/components/fms-module-common/fieldUtils' const props = defineProps({ fields: { type: Array, default: () => [] }, diff --git a/code/fms/fms-vue/src/components/fms-module-list/FmsQueryControl.vue b/code/fms/fms-vue/src/components/fms-module-list/FmsQueryControl.vue index 2fa12b76..8f6499c4 100644 --- a/code/fms/fms-vue/src/components/fms-module-list/FmsQueryControl.vue +++ b/code/fms/fms-vue/src/components/fms-module-list/FmsQueryControl.vue @@ -1,7 +1,12 @@ + + + + diff --git a/code/fms/fms-vue/src/views/module/menu-management/MenuPowerLibraryPanel.vue b/code/fms/fms-vue/src/views/module/menu-management/MenuPowerLibraryPanel.vue new file mode 100644 index 00000000..47b74d00 --- /dev/null +++ b/code/fms/fms-vue/src/views/module/menu-management/MenuPowerLibraryPanel.vue @@ -0,0 +1,191 @@ + + + + + diff --git a/code/fms/fms-vue/src/views/module/menu-management/MenuPowersPanel.vue b/code/fms/fms-vue/src/views/module/menu-management/MenuPowersPanel.vue new file mode 100644 index 00000000..0a34eb10 --- /dev/null +++ b/code/fms/fms-vue/src/views/module/menu-management/MenuPowersPanel.vue @@ -0,0 +1,196 @@ + + + + + diff --git a/code/fms/fms-vue/src/views/module/menu-management/MenuTreePanel.vue b/code/fms/fms-vue/src/views/module/menu-management/MenuTreePanel.vue new file mode 100644 index 00000000..ff72c89e --- /dev/null +++ b/code/fms/fms-vue/src/views/module/menu-management/MenuTreePanel.vue @@ -0,0 +1,262 @@ + + + + + diff --git a/code/fms/fms-vue/src/views/module/menu-management/index.vue b/code/fms/fms-vue/src/views/module/menu-management/index.vue index 0cb28d24..8fa6d8f6 100644 --- a/code/fms/fms-vue/src/views/module/menu-management/index.vue +++ b/code/fms/fms-vue/src/views/module/menu-management/index.vue @@ -1,47 +1,28 @@