20260724173425

This commit is contained in:
oneao committed 2026-07-24 17:34:26 +08:00
1 parent 4f40ad9f11
commit b33c743236
27 files changed
+1310 -437

No files matched your search

@@ -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();
<div v-if="treeData.length === 0" class="mm-tree-empty">
<a-empty description="暂无模块" />
</div>
<a-dropdown
v-else
:trigger="['contextmenu']"
:menu="contextMenuItems"
>
<div style="flex:1;min-height:0;overflow:hidden;display:flex;flex-direction:column;">
<a-dropdown v-else :trigger="['contextmenu']" :menu="contextMenuItems">
<div
style="flex: 1; min-height: 0; overflow: hidden; display: flex; flex-direction: column"
>
<FmsTree
class="mm-tree"
:tree-data="treeData"
@@ -502,9 +543,6 @@ loadTree();
<div class="mm-header">
<a-space>
<a-button type="primary" :loading="configSaving" @click="saveAll">保存</a-button>
<a-popconfirm title="确定删除该模块?" @confirm="onDeleteNodeById(selectedModuleId)">
<a-button danger>删除模块</a-button>
</a-popconfirm>
</a-space>
<div class="mm-header-center">
<a-space size="middle">
@@ -523,20 +561,22 @@ loadTree();
</a-space>
</div>
<span class="mm-header-title">{{ selectedModule.b_name || selectedModule.b_code }}</span>
<span class="mm-header-code">{{ selectedModule.b_code || '(未设置编码)' }}</span>
<span class="mm-header-code">{{ selectedModule.b_code || "(未设置编码)" }}</span>
<span class="mm-header-type">
<a-tag>{{ selectedModule.b_module_type === 'directory' ? '目录' : selectedModule.b_module_type === 'page' ? '页面' : '数据' }}</a-tag>
<a-tag>{{
selectedModule.b_module_type === "directory"
? "目录"
: selectedModule.b_module_type === "page"
? "页面"
: "数据"
}}</a-tag>
</span>
</div>
<!-- Tab 面板 -->
<div class="mm-body">
<a-tabs v-model:active-key="activeTab" class="mm-tabs">
<a-tab-pane
v-for="tab in visibleTabs"
:key="tab.key"
:tab="tab.tab"
>
<a-tab-pane v-for="tab in visibleTabs" :key="tab.key" :tab="tab.tab">
<template v-if="configLoading">
<div class="mm-loading"><a-spin /></div>
</template>
@@ -596,6 +636,7 @@ loadTree();
/>
<ModuleI18nPanel
v-else-if="tab.key === 'i18n'"
ref="i18nPanelRef"
:module-id="String(selectedModule.b_id)"
:loading="configSaving"
/>