diff --git a/code/fms/CODEBUDDY.md b/code/fms/CODEBUDDY.md index 6efb1f85..aa199c26 100644 --- a/code/fms/CODEBUDDY.md +++ b/code/fms/CODEBUDDY.md @@ -41,7 +41,7 @@ pnpm fmt:check # check formatting There is **no lint or test script** in the frontend. ### Build/test discipline (from `开发规范.md`) -Do **not** run build/test/start yourself after finishing a change. Only build, test, or start services when the user explicitly asks. When delivering, state that build/test were not executed. +Do **not** run build or start services yourself after finishing a change — only when the user explicitly asks. Running tests to verify changes is allowed (e.g. backend `./mvnw test`, frontend `pnpm fmt:check`). When delivering, state whether tests were run (and their results) and that no build was executed. ## Backend architecture diff --git a/code/fms/fms-vue/src/App.vue b/code/fms/fms-vue/src/App.vue index 9d556e9f..1d3571c8 100644 --- a/code/fms/fms-vue/src/App.vue +++ b/code/fms/fms-vue/src/App.vue @@ -1,7 +1,5 @@ diff --git a/code/fms/fms-vue/src/views/module-management/components/ModuleListConfigPanel.vue b/code/fms/fms-vue/src/views/module-management/components/ModuleListConfigPanel.vue index 10b4df79..f2255e92 100644 --- a/code/fms/fms-vue/src/views/module-management/components/ModuleListConfigPanel.vue +++ b/code/fms/fms-vue/src/views/module-management/components/ModuleListConfigPanel.vue @@ -27,7 +27,10 @@ watch( ); function emitUpdate() { - emit("update", localList.value.map((r) => ({ ...r }))); + emit( + "update", + localList.value.map((r) => ({ ...r })), + ); } const availableFields = computed(() => @@ -106,7 +109,14 @@ const columns = [ title: "操作", width: 60, align: "center", - actions: [{ type: "danger", label: "删除", confirm: { title: "确定删除该记录?", okText: "删除" }, onClick: (row) => deleteRow(row) }], + actions: [ + { + type: "danger", + label: "删除", + confirm: { title: "确定删除该记录?", okText: "删除" }, + onClick: (row) => deleteRow(row), + }, + ], }, ]; diff --git a/code/fms/fms-vue/src/views/module-management/components/ModulePowersPanel.vue b/code/fms/fms-vue/src/views/module-management/components/ModulePowersPanel.vue index 0cc69de7..2a2bef2d 100644 --- a/code/fms/fms-vue/src/views/module-management/components/ModulePowersPanel.vue +++ b/code/fms/fms-vue/src/views/module-management/components/ModulePowersPanel.vue @@ -1,7 +1,11 @@ @@ -88,8 +193,17 @@ const columns = [
- + +
+ @@ -121,3 +235,54 @@ const columns = [ width: 100%; } + + + diff --git a/code/fms/fms-vue/src/views/module-management/components/ModuleQueryConfigPanel.vue b/code/fms/fms-vue/src/views/module-management/components/ModuleQueryConfigPanel.vue index 3cd3b9e5..9add15f5 100644 --- a/code/fms/fms-vue/src/views/module-management/components/ModuleQueryConfigPanel.vue +++ b/code/fms/fms-vue/src/views/module-management/components/ModuleQueryConfigPanel.vue @@ -38,7 +38,10 @@ watch( ); function emitUpdate() { - emit("update", localQuery.value.map((r) => ({ ...r }))); + emit( + "update", + localQuery.value.map((r) => ({ ...r })), + ); } const availableFields = computed(() => @@ -111,7 +114,14 @@ const columns = [ title: "操作", width: 60, align: "center", - actions: [{ type: "danger", label: "删除", confirm: { title: "确定删除该记录?", okText: "删除" }, onClick: (row) => deleteRow(row) }], + actions: [ + { + type: "danger", + label: "删除", + confirm: { title: "确定删除该记录?", okText: "删除" }, + onClick: (row) => deleteRow(row), + }, + ], }, ]; diff --git a/code/fms/fms-vue/src/views/module-management/index.vue b/code/fms/fms-vue/src/views/module-management/index.vue index 77cc6f6e..7bec3469 100644 --- a/code/fms/fms-vue/src/views/module-management/index.vue +++ b/code/fms/fms-vue/src/views/module-management/index.vue @@ -3,7 +3,13 @@ import { computed, h, ref, watch } from "vue"; import { message, Modal } from "antdv-next"; import FmsTree from "@/components/FmsTree.vue"; import { loadDataApi, saveObjectApi, nextIdApi, describeApi } from "@/services/api"; -import { cloneData, diffRows, allocateTemporaryIds, tableChange, hasChanges } from "@/utils/dataChanges"; +import { + cloneData, + diffRows, + allocateTemporaryIds, + tableChange, + hasChanges, +} from "@/utils/dataChanges"; import { createTempId } from "@/utils/tempId"; import { Plus } from "@lucide/vue"; import ModuleBasicPanel from "./components/ModuleBasicPanel.vue"; @@ -65,6 +71,7 @@ const drafts = ref({ const configLoading = ref(false); const configSaving = ref(false); +const i18nPanelRef = ref(null); // 当前选中模块对象(从 drafts.module 获取) const selectedModule = computed(() => drafts.value.module); @@ -245,9 +252,7 @@ async function onAddModule(parentId, moduleType = "directory") { } const addModuleMenu = { - items: [ - { key: "directory", label: "目录" }, - ], + items: [{ key: "directory", label: "目录" }], onClick: ({ key }) => onAddModule(null, key), }; @@ -265,6 +270,13 @@ function onContextMenuClick({ key }) { if (key.startsWith("add-")) { onAddModule(node.key, key.replace("add-", "")); } else if (key === "delete") { + if (hasChildNode(node.key)) { + Modal.warning({ + title: "无法删除", + content: "该节点下还有子节点,请先删除子节点后再删除当前节点。", + }); + return; + } Modal.confirm({ title: `确定删除「${node.title}」?`, content: "此操作不可恢复", @@ -276,7 +288,31 @@ function onContextMenuClick({ key }) { } } +function findNode(key) { + const walk = (nodes) => { + for (const n of nodes || []) { + if (String(n.b_id) === String(key)) return n; + const child = walk(n.children); + if (child) return child; + } + return null; + }; + return walk(treeData.value); +} + +function hasChildNode(moduleId) { + const node = findNode(moduleId); + return !!node?.children?.length; +} + async function onDeleteNodeById(moduleId) { + if (hasChildNode(moduleId)) { + Modal.warning({ + title: "无法删除", + content: "该节点下还有子节点,请先删除子节点后再删除当前节点。", + }); + return; + } try { await saveObjectApi([{ table: "b_module", key_field: "b_id", deletes: [{ b_id: moduleId }] }]); message.success("模块已删除"); @@ -329,6 +365,12 @@ async function saveAll() { } } + // 多语言:由各子面板收集变更,统一在此保存 + if (i18nPanelRef.value) { + const i18nReq = await i18nPanelRef.value.buildSaveRequest(); + if (i18nReq) saveReqs.push(i18nReq); + } + if (saveReqs.length === 0) { message.info("没有需要保存的变更"); return; @@ -338,6 +380,7 @@ async function saveAll() { // 更新 originals originals.value = cloneData(drafts.value); + i18nPanelRef.value?.markSaved(); message.success("保存成功"); await loadTree(); } catch (e) { @@ -393,9 +436,7 @@ async function syncFields() { const viewColumnSet = new Set(columns.map((c) => c.name.toLowerCase())); // 已有字段映射(按字段名) - const existingMap = new Map( - existingFields.map((f) => [f.b_field?.toLowerCase(), f]), - ); + const existingMap = new Map(existingFields.map((f) => [f.b_field?.toLowerCase(), f])); const mergedFields = []; @@ -434,7 +475,9 @@ async function syncFields() { if (hasChanges(req)) { await allocateTemporaryIds([req.inserts || [], req.updates || [], req.deletes || []]); await saveObjectApi([req]); - message.success(`字段同步完成:${columns.length} 列,新增 ${req.inserts?.length || 0},停用 ${req.deletes?.length || 0}`); + message.success( + `字段同步完成:${columns.length} 列,新增 ${req.inserts?.length || 0},停用 ${req.deletes?.length || 0}`, + ); } else { message.info("字段已是最新,无需同步"); } @@ -471,12 +514,10 @@ loadTree();
- -
+ +
保存 - - 删除模块 -
@@ -523,20 +561,22 @@ loadTree();
{{ selectedModule.b_name || selectedModule.b_code }} - {{ selectedModule.b_code || '(未设置编码)' }} + {{ selectedModule.b_code || "(未设置编码)" }} - {{ selectedModule.b_module_type === 'directory' ? '目录' : selectedModule.b_module_type === 'page' ? '页面' : '数据' }} + {{ + selectedModule.b_module_type === "directory" + ? "目录" + : selectedModule.b_module_type === "page" + ? "页面" + : "数据" + }}
- + @@ -596,6 +636,7 @@ loadTree(); /> diff --git a/code/fms/fms-vue/src/views/system/i18n-type/index.vue b/code/fms/fms-vue/src/views/system/i18n-type/index.vue index 4778bc72..192d89b7 100644 --- a/code/fms/fms-vue/src/views/system/i18n-type/index.vue +++ b/code/fms/fms-vue/src/views/system/i18n-type/index.vue @@ -64,7 +64,14 @@ function createActionsCell() { children.push( h( Button, - { type: "link", size: "small", onClick: (e) => { e.stopPropagation(); openEdit(row); } }, + { + type: "link", + size: "small", + onClick: (e) => { + e.stopPropagation(); + openEdit(row); + }, + }, "编辑", ), ); @@ -97,10 +104,28 @@ function createActionsCell() { const columns = [ { dataIndex: "b_code", title: "简称 / 编码", width: 150 }, { dataIndex: "b_name", title: "名称", width: 160 }, - { dataIndex: "b_default", title: "默认", width: 80, align: "center", customCell: createDefaultCell() }, - { dataIndex: "b_canuse", title: "是否启用", width: 100, align: "center", customCell: createCanuseCell() }, + { + dataIndex: "b_default", + title: "默认", + width: 80, + align: "center", + customCell: createDefaultCell(), + }, + { + dataIndex: "b_canuse", + title: "是否启用", + width: 100, + align: "center", + customCell: createCanuseCell(), + }, { dataIndex: "b_xh", title: "排序", width: 80, align: "center" }, - { dataIndex: "actions", title: "操作", width: 130, align: "center", customCell: createActionsCell() }, + { + dataIndex: "actions", + title: "操作", + width: 130, + align: "center", + customCell: createActionsCell(), + }, ]; /* ---------- 行内开关即时保存 ---------- */ @@ -157,10 +182,10 @@ function openEdit(row) { modalMode.value = "edit"; editingId.value = row.b_id; editingDefault.value = row.b_default === 1; - form.value = { - b_code: row.b_code, - b_name: row.b_name, - b_xh: row.b_xh ?? 0, + form.value = { + b_code: row.b_code, + b_name: row.b_name, + b_xh: row.b_xh ?? 0, defaultOn: row.b_default === 1, canuseOn: row.b_canuse === 1, }; @@ -240,10 +265,7 @@ async function saveForm() { > - + diff --git a/code/fms/开发规范.md b/code/fms/开发规范.md index 292f3ae6..a767b9bc 100644 --- a/code/fms/开发规范.md +++ b/code/fms/开发规范.md @@ -3,9 +3,10 @@ ## 一、构建与测试 - **不要过多封装** -- 完成功能后不主动执行构建和测试。 -- 只有用户明确要求时,才执行构建、测试或启动服务。 -- 交付时说明本次未执行构建和测试。 +- 完成功能后不主动执行构建。 +- 允许执行测试以验证改动(如后端 `./mvnw test`、前端 `pnpm fmt:check` 及页面功能验证)。 +- 只有用户明确要求时,才执行构建或启动服务。 +- 交付时说明本次是否执行了测试及测试结果,并说明未执行构建。 ## 二、后端接口