--- name: 模块管理迁移计划 overview: 将 fms-vue2(antdv)的模块管理页面迁移到 fms-vue(shadcn-vue + reka-ui + tailwindcss),核心面板包括:基本信息、字段定义、权限配置、多语言。沿用 originals+drafts+diff 数据流模式,复用通用数据 API。 todos: - id: create-tool-functions content: 创建工具函数:dataChanges.ts(diffRows/tableChange/allocateTemporaryIds/cloneData/hasChanges)、tempId.ts(createTempId)、i18n.ts(validateModuleCode/buildModuleI18nKey 等),TypeScript 化旧系统代码 status: completed - id: create-types-and-service content: 创建类型定义 types/module.ts 和 API 服务 services/moduleManagement.ts(封装 loadTree/loadModuleConfig/saveModule/deleteModule/syncFields) status: completed - id: create-loading-overlay content: 创建 FmsLoadingOverlay.vue 加载遮罩组件 status: completed - id: rewrite-index-page content: 重写 index.vue 主页面:左侧树(FmsTree + 搜索 + 右键菜单 + 拖拽分隔线)+ 右侧详情(Header + section nav + 面板切换)+ 数据加载/保存/删除/字段同步完整逻辑 status: completed dependencies: - create-tool-functions - create-types-and-service - create-loading-overlay - id: create-basic-panel content: 使用 [skill:shadcn-vue] 创建 ModuleBasicPanel.vue:基于 shadcn-vue Form 组件(vee-validate + zod)构建表单,按模块类型动态显示字段,含图标选择器 status: completed dependencies: - create-tool-functions - create-types-and-service - id: create-fields-powers-i18n-panels content: 创建 ModuleFieldsPanel.vue(StkTable + 同步字段)、ModulePowersPanel.vue(StkTable + 增删改)、ModuleI18nPanel.vue(StkTable + 受管键 + I18nQuickSetModal),从旧系统迁移完整逻辑 status: completed dependencies: - create-tool-functions - create-types-and-service --- ## 用户需求 将 fms-vue2 旧系统的模块管理页面迁移到 fms-vue 新系统,实现模块元数据的管理功能。 ## 核心功能 ### 1. 模块树管理 - 左侧树形面板展示所有启用模块(s_module 表),支持虚拟根节点 - 支持按模块名称/编码搜索过滤 - 右键菜单:添加目录、添加页面、添加数据分类、添加数据、删除节点 - 删除节点前校验无子节点,级联删除所有子表数据 - 左侧面板宽度可拖拽调整(210px ~ 440px) - 点击树节点加载右侧配置详情 ### 2. 基本信息面板(所有模块类型) - 表单字段:b_code(编码)、b_name(名称)、b_module_type(类型:目录/页面/数据分类/数据)、b_parent_id(上级模块,只读展示父名称)、b_xh(排序号) - 目录/页面可见:b_icon(图标选择)、b_route(路由,页面必填)、b_canmenu(显示菜单开关) - 数据可见:b_viewtable(查询视图,必填)、b_savetable(保存表)、b_keyfield(主键字段) - 模块类型变更时自动清空不适用的字段 - Header 区域:模块名称 + 类型标签 + 启用开关 + 显示菜单开关 + 保存按钮 ### 3. 字段定义面板(data 类型) - 表格展示字段列表(b_field 字段名、b_name 显示名、b_type 控件类型、b_canuse 启用) - 工具栏:同步字段按钮(调用 describeApi 拉视图列,与现有字段合并) - 支持行内编辑、上移/下移、删除行 - 同步逻辑:视图已有列保留原配置、新增列追加默认行、已删列移除并清理关联配置 ### 4. 权限配置面板(page 类型) - 表格展示操作权限列表(b_code 权限编码、b_name 权限名称、b_canuse 启用) - 支持上移/下移、新增行、删除行 ### 5. 多语言面板(非 directory 类型) - 表格展示翻译条目(b_key 多语言键、各语言列的值) - 受管键(模块/字段/权限的自动生成键)只读展示 - 非受管键支持手动编辑 - 支持快速设置弹窗 ### 6. 统一保存 - 点击保存按钮,通过 diffRows 比较 drafts 与 originals 计算增量 - 子表临时负数 ID 通过 allocateTemporaryIds 批量替换为真实雪花 ID - 调用 saveObjectApi 在同一事务中批量提交所有表的变更 - 保存成功后更新 originals 快照、刷新树节点、刷新权限数据 ## 技术栈 - 前端框架:Vue 3 + TypeScript + Vite - UI 组件:shadcn-vue(reka-ui)+ Tailwind CSS - 表单验证:vee-validate + zod - 表格:stk-table-vue(StkTable) - 状态管理:Pinia - Toast:vue-sonner - 图标:lucide-vue - API 层:复用现有 api.ts(loadDataApi / saveObjectApi / nextIdApi / describeApi / loadDataBySqlApi) ## 实现方案 ### 整体策略 从旧系统完整迁移核心逻辑,适配新系统的 TypeScript + shadcn-vue + tailwindcss 技术栈。复用新系统已有的 FmsTree 树组件和 buildTree/sortTree 工具,新建缺失的工具函数和面板组件。 ### 数据流架构 沿用旧系统 originals + drafts + diff 模式: ```mermaid flowchart TD A[loadTree: loadDataApi s_module] --> B[buildTree 构建树结构] B --> C[用户点击树节点] C --> D[loadModuleConfig: 按模块类型并行加载子表] D --> E[originals = 加载的快照] E --> F[drafts = cloneData originals] F --> G[子面板编辑 drafts] G --> H[用户点击保存] H --> I[diffRows 比较 drafts vs originals] I --> J[allocateTemporaryIds 替换临时负数ID] J --> K[saveObjectApi 批量提交] K --> L[originals = cloneData drafts] L --> M[刷新树节点 + 权限数据] ``` ### 关键设计决策 1. **TypeScript 化**:所有工具函数和组件使用 TypeScript 编写,类型安全 2. **表单方案**:使用 shadcn-vue 的 Form 组件(vee-validate + zod),参考 login 页面的使用模式 3. **表格方案**:使用 stk-table-vue 的 StkTable,保持与新系统一致 4. **临时 ID**:子表新增行使用前端临时负数 ID,保存时通过 allocateTemporaryIds 批量替换 5. **模块类型路由**:section nav 根据 b_module_type 动态显示面板入口,使用 v-if 切换而非路由跳转 6. **权限控制**:通过 permissionStore.canPower('m_metadata', actionCode) 控制按钮可用性 ### 文件拆分原则 - index.vue:主协调层(树交互、数据加载/保存、面板切换、权限校验),目标控制在 400 行以内 - 各面板组件:独立文件,通过 props 接收数据、emit 通知变更 - 工具函数:按职责拆分到 utils/ 下独立文件 - 类型定义:统一放在 types/ 目录 ## 实现细节 ### 性能考量 - 树加载:一次性加载全部 s_module 行(b_canuse = 1),前端 buildTree 构建,无需多次请求 - 配置加载:按模块类型并行请求(Promise.all),非 data 类型只加载必需表 - 保存:一次 saveObjectApi 调用包含所有表的变更,事务保证一致性 - diffRows 比较:使用 Map 按 b_id 索引,O(n) 复杂度 - 树搜索:前端 filterTree 递归过滤,无需后端请求 ### 日志与错误处理 - 加载失败:toast.error 提示用户,不清空已有数据 - 保存失败:toast.error 提示,drafts 不回退(保留用户编辑) - 权限不足:按钮 disabled,不显示 toast ### 兼容性 - b_id 始终序列化为字符串 - boolean 列使用 int 0/1 - API 请求格式与后端 DataController 完全一致 - 不修改任何通用端点 ## 目录结构 ``` fms-vue/src/ ├── utils/ │ ├── dataChanges.ts # [NEW] 数据变更工具。导出 cloneData、diffRows、allocateTemporaryIds、remapForeignKeys、tableChange、hasChanges 函数。TypeScript 化旧系统 dataChanges.js,适配新系统 API 类型。 │ ├── tempId.ts # [NEW] 临时 ID 生成。导出 createTempId 函数,生成会话内唯一的负数字符串 ID("-1"、"-2"…)。TypeScript 化旧系统 tempId.js。 │ └── i18n.ts # [NEW] 多语言工具。导出 validateModuleCode、buildModuleI18nKey、buildModuleItemI18nKey、syncModuleItemI18nKeys、groupI18nRows、mergeI18nDefaults、consolidatePivotRows 函数。TypeScript 化旧系统 utils/i18n.js。 ├── types/ │ └── module.ts # [NEW] 模块管理类型定义。定义 ModuleData、ModuleField、ModulePower、I18nRow 等接口,以及 SectionName、ModuleType 等类型。 ├── services/ │ └── moduleManagement.ts # [NEW] 模块管理 API 服务。封装 loadTree、loadModuleConfig、saveModule、deleteModule、syncFields 等业务方法,内部调用 api.ts 通用端点。 ├── views/system/module-management/ │ ├── index.vue # [MODIFY] 主页面。完整重写:左侧树 + 可拖拽分隔线 + 右侧 Header + section nav + 面板区。实现树交互、数据加载/保存、面板切换、权限校验。 │ └── components/ │ ├── ModuleBasicPanel.vue # [NEW] 基本信息面板。使用 shadcn-vue Form 组件(vee-validate + zod)构建表单,根据模块类型动态显示/隐藏字段。支持图标选择器(复用项目已有的 lucideIcons 工具)。 │ ├── ModuleFieldsPanel.vue # [NEW] 字段定义面板。使用 StkTable 展示字段列表,支持行内编辑、上移/下移、删除。工具栏包含"同步字段"按钮(调用 describeApi)。 │ ├── ModulePowersPanel.vue # [NEW] 权限配置面板。使用 StkTable 展示权限列表,支持上移/下移、新增行、删除行。 │ ├── ModuleI18nPanel.vue # [NEW] 多语言面板。使用 StkTable 展示翻译条目(b_key + 各语言列),支持受管键只读展示、非受管键行内编辑。包含 I18nQuickSetModal 快速设置弹窗。 │ ├── I18nQuickSetModal.vue # [NEW] 多语言快速设置弹窗。使用 Dialog 组件,表单输入各语言翻译值,提交后合并到 i18n 草稿中。 │ └── FmsLoadingOverlay.vue # [NEW] Loading 遮罩层组件。使用 shadcn-vue 的 Skeleton 或自定义 spinner,覆盖在内容区域上方显示加载状态。新系统中不存在此组件。 ``` ## 关键代码结构 ### 类型定义(types/module.ts) ```typescript export type ModuleType = 'directory' | 'page' | 'data_category' | 'data'; export interface ModuleData { b_id: string; b_code: string; b_name: string; b_module_type: ModuleType; b_parent_id: string | null; b_i18n: string | null; b_xh: number; b_icon: string | null; b_route: string | null; b_viewtable: string | null; b_savetable: string | null; b_keyfield: string | null; b_canmenu: number; b_canuse: number; [key: string]: unknown; } export interface ModuleField { b_id: string; b_module_id: string; b_field: string; b_name: string; b_i18n: string | null; b_type: string; b_default_value: string | null; b_options: string | null; b_canuse: number; [key: string]: unknown; } export interface ModulePower { b_id: string; b_module_id: string; b_code: string; b_name: string; b_i18n: string | null; b_xh: number; b_canuse: number; [key: string]: unknown; } export interface Originals { module: ModuleData | null; fields: ModuleField[]; powers: ModulePower[]; } export type SectionName = 'module' | 'fields' | 'powers'; ``` ### dataChanges 核心函数签名(utils/dataChanges.ts) ```typescript export function cloneData(value: T): T; export function diffRows(current: T[], previous: T[]): { inserts: T[]; updates: T[]; deletes: { b_id: string }[] }; export function tableChange(table: string, changes: { inserts?: unknown[]; updates?: unknown[]; deletes?: unknown[] }): SaveRequest; export function hasChanges(request: SaveRequest): boolean; export async function allocateTemporaryIds(rowGroups: unknown[][]): Promise>; export function remapForeignKeys(rows: Record[], fields: string[], mapping: Map): void; ``` ## Agent Extensions ### Skill - **shadcn-vue** - 用途:在构建 Form 表单组件和 Dialog 弹窗时,查询 shadcn-vue 组件的 API 文档、props/slots/events 定义 - 预期结果:确保 Form、FormField、FormItem、Dialog 等组件的使用方式与 shadcn-vue 官方文档一致 ### SubAgent - **code-explorer** - 用途:在实现过程中需要快速定位旧系统面板组件的具体实现细节时,用于搜索和读取旧系统代码 - 预期结果:获取 ModuleFieldsPanel、ModulePowersPanel、ModuleI18nPanel 的完整实现逻辑作为迁移参考