20260723225202
This commit is contained in:
1 parent
5f73b54802
commit
4d118a0037
22 files changed
+1403
-259
No files matched your search
@@ -1,6 +1,6 @@
|
||||
<script setup>
|
||||
import { computed, h, ref, watch } from "vue";
|
||||
import { message } from "antdv-next";
|
||||
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";
|
||||
@@ -20,6 +20,26 @@ const treeLoading = ref(false);
|
||||
const selectedModuleId = ref(null);
|
||||
const skipLoad = ref(false);
|
||||
const activeTab = ref("basic");
|
||||
const contextNode = ref(null);
|
||||
const contextMenuOpen = ref(false);
|
||||
|
||||
const contextMenuItems = computed(() => {
|
||||
const node = contextNode.value;
|
||||
const items = [];
|
||||
if (!node) return { items };
|
||||
if (node.b_module_type === "directory") {
|
||||
items.push(
|
||||
{ label: "添加目录", key: "add-directory" },
|
||||
{ label: "添加页面", key: "add-page" },
|
||||
{ label: "添加数据", key: "add-data" },
|
||||
);
|
||||
} else if (node.b_module_type === "page") {
|
||||
items.push({ label: "添加数据", key: "add-data" });
|
||||
}
|
||||
if (items.length) items.push({ type: "divider" });
|
||||
items.push({ label: "删除", key: "delete", danger: true });
|
||||
return { items, onClick: onContextMenuClick };
|
||||
});
|
||||
|
||||
// 原始数据(加载时保存,用于 diff)
|
||||
const originals = ref({
|
||||
@@ -49,6 +69,21 @@ const configSaving = ref(false);
|
||||
// 当前选中模块对象(从 drafts.module 获取)
|
||||
const selectedModule = computed(() => drafts.value.module);
|
||||
|
||||
// 上级模块名称(从模块树解析 b_parent_id 对应的名称,用于基本信息展示)
|
||||
const parentModuleName = computed(() => {
|
||||
const pid = selectedModule.value?.b_parent_id;
|
||||
if (!pid) return "";
|
||||
const find = (nodes) => {
|
||||
for (const n of nodes || []) {
|
||||
if (String(n.b_id) === String(pid)) return n.b_name || n.b_code;
|
||||
const child = find(n.children);
|
||||
if (child) return child;
|
||||
}
|
||||
return "";
|
||||
};
|
||||
return find(treeData.value);
|
||||
});
|
||||
|
||||
// 可见 Tab
|
||||
const visibleTabs = computed(() => {
|
||||
const type = selectedModule.value?.b_module_type;
|
||||
@@ -62,10 +97,12 @@ const visibleTabs = computed(() => {
|
||||
{ key: "query", tab: "查询配置", icon: "Search" },
|
||||
);
|
||||
}
|
||||
tabs.push(
|
||||
{ key: "powers", tab: "权限配置", icon: "Lock" },
|
||||
{ key: "i18n", tab: "多语言", icon: "Translation" },
|
||||
);
|
||||
if (type === "page") {
|
||||
tabs.push({ key: "powers", tab: "权限配置", icon: "Lock" });
|
||||
}
|
||||
if (type !== "directory") {
|
||||
tabs.push({ key: "i18n", tab: "多语言", icon: "Translation" });
|
||||
}
|
||||
return tabs;
|
||||
});
|
||||
|
||||
@@ -210,21 +247,43 @@ async function onAddModule(parentId, moduleType = "directory") {
|
||||
const addModuleMenu = {
|
||||
items: [
|
||||
{ key: "directory", label: "目录" },
|
||||
{ key: "page", label: "页面" },
|
||||
{ key: "data", label: "数据" },
|
||||
],
|
||||
onClick: ({ key }) => onAddModule(null, key),
|
||||
};
|
||||
|
||||
// ---- 删除模块 ----
|
||||
async function onDeleteModule() {
|
||||
const moduleId = selectedModuleId.value;
|
||||
if (!moduleId) return;
|
||||
// ---- 右键菜单 ----
|
||||
function onTreeRightClick({ node }) {
|
||||
contextNode.value = node;
|
||||
}
|
||||
|
||||
function onContextMenuClick({ key }) {
|
||||
contextMenuOpen.value = false;
|
||||
const node = contextNode.value;
|
||||
contextNode.value = null;
|
||||
if (!node) return;
|
||||
|
||||
if (key.startsWith("add-")) {
|
||||
onAddModule(node.key, key.replace("add-", ""));
|
||||
} else if (key === "delete") {
|
||||
Modal.confirm({
|
||||
title: `确定删除「${node.title}」?`,
|
||||
content: "此操作不可恢复",
|
||||
okText: "删除",
|
||||
cancelText: "取消",
|
||||
okButtonProps: { danger: true },
|
||||
onOk: () => onDeleteNodeById(node.key),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function onDeleteNodeById(moduleId) {
|
||||
try {
|
||||
await saveObjectApi([{ table: "b_module", key_field: "b_id", deletes: [{ b_id: moduleId }] }]);
|
||||
message.success("模块已删除");
|
||||
selectedModuleId.value = null;
|
||||
drafts.value.module = null;
|
||||
if (selectedModuleId.value === moduleId) {
|
||||
selectedModuleId.value = null;
|
||||
drafts.value.module = null;
|
||||
}
|
||||
await loadTree();
|
||||
} catch (e) {
|
||||
message.error("删除失败:" + (e.message || e));
|
||||
@@ -412,23 +471,28 @@ loadTree();
|
||||
<div v-if="treeData.length === 0" class="mm-tree-empty">
|
||||
<a-empty description="暂无模块" />
|
||||
</div>
|
||||
<FmsTree
|
||||
<a-dropdown
|
||||
v-else
|
||||
class="mm-tree"
|
||||
:tree-data="treeData"
|
||||
:selected-keys="selectedModuleId ? [selectedModuleId] : []"
|
||||
:field-names="{ title: 'title', key: 'key', children: 'children' }"
|
||||
@select="onTreeSelect"
|
||||
:trigger="['contextmenu']"
|
||||
:menu="contextMenuItems"
|
||||
>
|
||||
<template #title="{ key, title }">
|
||||
<span class="mm-tree-node">
|
||||
<span>{{ title }}</span>
|
||||
<span class="mm-tree-node-actions">
|
||||
<a-button type="text" @click.stop="onAddModule(key)">+</a-button>
|
||||
</span>
|
||||
</span>
|
||||
</template>
|
||||
</FmsTree>
|
||||
<div style="flex:1;min-height:0;overflow:hidden;display:flex;flex-direction:column;">
|
||||
<FmsTree
|
||||
class="mm-tree"
|
||||
:tree-data="treeData"
|
||||
:selected-keys="selectedModuleId ? [selectedModuleId] : []"
|
||||
:field-names="{ title: 'title', key: 'key', children: 'children' }"
|
||||
@select="onTreeSelect"
|
||||
@right-click="onTreeRightClick"
|
||||
>
|
||||
<template #title="node">
|
||||
<span class="mm-tree-node">
|
||||
<span>{{ node.title }}</span>
|
||||
</span>
|
||||
</template>
|
||||
</FmsTree>
|
||||
</div>
|
||||
</a-dropdown>
|
||||
</aside>
|
||||
|
||||
<!-- 右侧详情 -->
|
||||
@@ -438,9 +502,9 @@ loadTree();
|
||||
<div class="mm-header">
|
||||
<a-space>
|
||||
<a-button type="primary" :loading="configSaving" @click="saveAll">保存</a-button>
|
||||
<a-popconfirm title="确定删除该模块?" @confirm="onDeleteModule">
|
||||
<a-button danger>删除模块</a-button>
|
||||
</a-popconfirm>
|
||||
<a-popconfirm title="确定删除该模块?" @confirm="onDeleteNodeById(selectedModuleId)">
|
||||
<a-button danger>删除模块</a-button>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
<div class="mm-header-center">
|
||||
<a-space size="middle">
|
||||
@@ -481,6 +545,7 @@ loadTree();
|
||||
v-if="tab.key === 'basic'"
|
||||
:module-id="String(selectedModule.b_id)"
|
||||
:data="drafts.module"
|
||||
:parent-name="parentModuleName"
|
||||
:loading="configSaving"
|
||||
@update="(v) => onUpdate('module', v)"
|
||||
/>
|
||||
@@ -524,6 +589,7 @@ loadTree();
|
||||
<ModulePowersPanel
|
||||
v-else-if="tab.key === 'powers'"
|
||||
:module-id="String(selectedModule.b_id)"
|
||||
:module-code="selectedModule?.b_code || ''"
|
||||
:powers="drafts.powers"
|
||||
:loading="configSaving"
|
||||
@update="(v) => onUpdate('powers', v)"
|
||||
@@ -585,7 +651,7 @@ loadTree();
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: auto;
|
||||
padding: 8px 0;
|
||||
padding: 8px 10px;
|
||||
}
|
||||
|
||||
.mm-tree-empty {
|
||||
@@ -602,15 +668,7 @@ loadTree();
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.mm-tree-node-actions {
|
||||
display: none;
|
||||
flex: none;
|
||||
}
|
||||
|
||||
.mm-tree-node:hover .mm-tree-node-actions {
|
||||
display: flex;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.mm-main {
|
||||
@@ -687,6 +745,11 @@ loadTree();
|
||||
:deep(.ant-tabs-tabpane) {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
:deep(.ant-tabs-body),
|
||||
:deep(.ant-tabs-body-top) {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.mm-loading {
|
||||
|
||||
Reference in new issue
Block a user