20260730231110
This commit is contained in:
1 parent
4929b78434
commit
318fa0d9e1
23 files changed
+4026
-506
No files matched your search
@@ -1,5 +1,5 @@
|
||||
<script setup>
|
||||
import { computed, ref, watch } from "vue";
|
||||
import { computed, onBeforeUnmount, ref, watch } from "vue";
|
||||
import { message, Modal } from "antdv-next";
|
||||
import FmsLoadingOverlay from "@/components/FmsLoadingOverlay.vue";
|
||||
import FmsTree from "@/components/FmsTree.vue";
|
||||
@@ -40,6 +40,7 @@ const selectedModuleId = ref(null);
|
||||
const skipLoad = ref(false);
|
||||
const expandTimer = ref(null);
|
||||
const activeTab = ref("basic");
|
||||
let expandDelayResolve = null;
|
||||
function buildTreeContextMenu(node) {
|
||||
const items = [];
|
||||
if (!node) return { items };
|
||||
@@ -329,7 +330,8 @@ async function onTreeDrop(info) {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (nodes[i].children && insertIntoTree(nodes[i].children, dropNodeKey, nodes[i])) return true;
|
||||
if (nodes[i].children && insertIntoTree(nodes[i].children, dropNodeKey, nodes[i]))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -371,10 +373,23 @@ async function onTreeDrop(info) {
|
||||
}
|
||||
|
||||
// ---- 模块配置加载 ----
|
||||
async function loadModuleConfig(moduleId) {
|
||||
function waitForNextPaint() {
|
||||
if (typeof requestAnimationFrame !== "function") return Promise.resolve();
|
||||
return new Promise((resolve) => {
|
||||
requestAnimationFrame(() => {
|
||||
requestAnimationFrame(resolve);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function loadModuleConfig(moduleId, options = {}) {
|
||||
const id = String(moduleId);
|
||||
const applyReady = options.applyReady;
|
||||
if (options.waitForPaint) await waitForNextPaint();
|
||||
if (String(selectedModuleId.value) !== id) return;
|
||||
|
||||
configLoading.value = true;
|
||||
try {
|
||||
const id = String(moduleId);
|
||||
const moduleType = findNode(id)?.b_module_type;
|
||||
let moduleResponse;
|
||||
let fieldsResponse;
|
||||
@@ -413,6 +428,8 @@ async function loadModuleConfig(moduleId) {
|
||||
moduleResponse = await loadDataApi("s_module", `b_id = ${id}`);
|
||||
}
|
||||
|
||||
if (String(selectedModuleId.value) !== id) return;
|
||||
if (applyReady && !(await applyReady)) return;
|
||||
if (String(selectedModuleId.value) !== id) return;
|
||||
|
||||
const original = {
|
||||
@@ -448,6 +465,23 @@ function clearExpandTimer() {
|
||||
clearTimeout(expandTimer.value);
|
||||
expandTimer.value = null;
|
||||
}
|
||||
if (expandDelayResolve) {
|
||||
expandDelayResolve(false);
|
||||
expandDelayResolve = null;
|
||||
}
|
||||
}
|
||||
|
||||
function waitForExpandAnimation() {
|
||||
return new Promise((resolve) => {
|
||||
expandDelayResolve = (ready) => {
|
||||
resolve(ready);
|
||||
};
|
||||
expandTimer.value = setTimeout(() => {
|
||||
expandTimer.value = null;
|
||||
expandDelayResolve = null;
|
||||
resolve(true);
|
||||
}, 300);
|
||||
});
|
||||
}
|
||||
|
||||
function onTreeSelect(keys, info) {
|
||||
@@ -481,15 +515,15 @@ function onTreeSelect(keys, info) {
|
||||
expandedTreeKeys.value = expandedKeys.filter((item) => item !== key);
|
||||
selectedModuleId.value = key;
|
||||
} else {
|
||||
// 展开:立即高亮选中节点,动画完毕后再加载数据
|
||||
// 展开:立即高亮,下一帧发请求;结果回写最多等到展开动画结束。
|
||||
expandedTreeKeys.value = [...expandedKeys, key];
|
||||
skipLoad.value = true;
|
||||
selectedModuleId.value = key; // 立即高亮,watch 中 skipLoad=true 跳过加载
|
||||
expandTimer.value = setTimeout(() => {
|
||||
expandTimer.value = null;
|
||||
loadedModuleId.value = null;
|
||||
void loadModuleConfig(key);
|
||||
}, 300);
|
||||
selectedModuleId.value = key;
|
||||
loadedModuleId.value = null;
|
||||
void loadModuleConfig(key, {
|
||||
waitForPaint: true,
|
||||
applyReady: waitForExpandAnimation(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1056,9 +1090,7 @@ async function syncFields() {
|
||||
}
|
||||
// 多语言面板中移除旧字段对应的受管翻译行(_managedId 格式为 field:<b_id>)
|
||||
if (i18nPanelRef.value) {
|
||||
const managedIds = new Set(
|
||||
Array.from(removedFieldIds).map((id) => `field:${id}`),
|
||||
);
|
||||
const managedIds = new Set(Array.from(removedFieldIds).map((id) => `field:${id}`));
|
||||
i18nPanelRef.value.removeByManagedIds(managedIds);
|
||||
}
|
||||
}
|
||||
@@ -1151,192 +1183,196 @@ async function syncFields() {
|
||||
|
||||
// ---- 初始化 ----
|
||||
void loadTree().catch((e) => message.error("模块树加载失败:" + (e.message || e)));
|
||||
|
||||
onBeforeUnmount(clearExpandTimer);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="mm-page">
|
||||
<a-splitter class="mm-splitter" lazy>
|
||||
<a-splitter-panel :default-size="260" :min="210" :max="440" collapsible>
|
||||
<!-- 左侧模块树 -->
|
||||
<aside class="mm-sidebar">
|
||||
<div class="mm-tree-stage">
|
||||
<div v-if="treeLoading" class="mm-tree-empty">
|
||||
<a-spin />
|
||||
</div>
|
||||
<FmsTree
|
||||
v-else
|
||||
:disabled="configSaving"
|
||||
:tree-data="treeData"
|
||||
v-model:expanded-keys="expandedTreeKeys"
|
||||
:selected-keys="selectedModuleId ? [selectedModuleId] : []"
|
||||
:context-menu="buildTreeContextMenu"
|
||||
:context-menu-disabled="busy"
|
||||
@select="onTreeSelect"
|
||||
@menu-click="onTreeMenuClick"
|
||||
>
|
||||
<template #title="node">
|
||||
<span class="mm-tree-node">
|
||||
<span>{{ node.title }}</span>
|
||||
</span>
|
||||
</template>
|
||||
</FmsTree>
|
||||
</div>
|
||||
</aside>
|
||||
</a-splitter-panel>
|
||||
|
||||
<a-splitter-panel :min="720">
|
||||
<!-- 右侧详情 -->
|
||||
<main class="mm-main">
|
||||
<FmsLoadingOverlay :loading="configLoading">
|
||||
<template v-if="selectedModule">
|
||||
<!-- 页面头部 -->
|
||||
<div class="mm-header">
|
||||
<a-space>
|
||||
<a-button
|
||||
type="primary"
|
||||
:loading="configSaving"
|
||||
:disabled="configLoading || configSyncing"
|
||||
@click="saveAll"
|
||||
>保存</a-button
|
||||
>
|
||||
<a-button
|
||||
v-if="selectedModule.b_module_type === 'data'"
|
||||
:loading="configSyncing"
|
||||
:disabled="syncDisabled"
|
||||
@click="syncFields"
|
||||
>
|
||||
同步字段
|
||||
</a-button>
|
||||
</a-space>
|
||||
<div class="mm-header-center">
|
||||
<a-space size="middle">
|
||||
<span>启用</span>
|
||||
<a-checkbox
|
||||
:checked="drafts.module?.b_canuse === 1"
|
||||
:disabled="busy"
|
||||
@update:checked="(v) => onSetField('b_canuse', v ? 1 : 0)"
|
||||
/>
|
||||
<a-divider type="vertical" />
|
||||
<span>显示菜单</span>
|
||||
<a-checkbox
|
||||
:checked="drafts.module?.b_canmenu === 1"
|
||||
:disabled="busy || drafts.module?.b_module_type === 'data'"
|
||||
@update:checked="(v) => onSetField('b_canmenu', v ? 1 : 0)"
|
||||
/>
|
||||
<template v-if="drafts.module?.b_module_type === 'data'">
|
||||
<a-divider type="vertical" />
|
||||
<span>自动编码</span>
|
||||
<a-checkbox
|
||||
:checked="autoCodeEnabled"
|
||||
:disabled="busy"
|
||||
@update:checked="setAutoCodeEnabled"
|
||||
/>
|
||||
</template>
|
||||
</a-space>
|
||||
<a-splitter-panel :default-size="260" :min="210" :max="440" collapsible>
|
||||
<!-- 左侧模块树 -->
|
||||
<aside class="mm-sidebar">
|
||||
<div class="mm-tree-stage">
|
||||
<div v-if="treeLoading" class="mm-tree-empty">
|
||||
<a-spin />
|
||||
</div>
|
||||
<FmsTree
|
||||
v-else
|
||||
:disabled="configSaving"
|
||||
:tree-data="treeData"
|
||||
v-model:expanded-keys="expandedTreeKeys"
|
||||
:selected-keys="selectedModuleId ? [selectedModuleId] : []"
|
||||
:context-menu="buildTreeContextMenu"
|
||||
:context-menu-disabled="busy"
|
||||
@select="onTreeSelect"
|
||||
@menu-click="onTreeMenuClick"
|
||||
>
|
||||
<template #title="node">
|
||||
<span class="mm-tree-node">
|
||||
<span>{{
|
||||
appStore.t(node.b_i18n, node.b_name || node.b_code || node.title)
|
||||
}}</span>
|
||||
</span>
|
||||
</template>
|
||||
</FmsTree>
|
||||
</div>
|
||||
</aside>
|
||||
</a-splitter-panel>
|
||||
|
||||
<!-- 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"
|
||||
:force-render="tab.key === 'i18n'"
|
||||
>
|
||||
<ModuleBasicPanel
|
||||
v-if="tab.key === 'basic'"
|
||||
:module-id="String(selectedModule.b_id)"
|
||||
:data="drafts.module"
|
||||
:parent-name="parentModuleName"
|
||||
:loading="busy"
|
||||
@update="(v) => onUpdate('module', v)"
|
||||
@i18n-saved="onI18nSaved"
|
||||
/>
|
||||
<ModuleFieldsPanel
|
||||
v-else-if="tab.key === 'fields'"
|
||||
:module-id="String(selectedModule.b_id)"
|
||||
:module-code="selectedModule?.b_code || ''"
|
||||
:fields="drafts.fields"
|
||||
:loading="busy"
|
||||
@update="onFieldsUpdate"
|
||||
@i18n-saved="onI18nSaved"
|
||||
/>
|
||||
<ModuleListConfigPanel
|
||||
v-else-if="tab.key === 'list'"
|
||||
:module-id="String(selectedModule.b_id)"
|
||||
:list-config="drafts.listConfig"
|
||||
:edit-config="drafts.editConfig"
|
||||
:query-config="drafts.queryConfig"
|
||||
:fields="drafts.fields"
|
||||
:groups="drafts.groups"
|
||||
:referenced-group-ids="referencedGroupIds"
|
||||
:loading="busy"
|
||||
@update="(v) => onUpdate('listConfig', v)"
|
||||
@groups-saved="onGroupsSaved"
|
||||
@sync-edit="(v) => onUpdate('editConfig', v)"
|
||||
@sync-query="(v) => onUpdate('queryConfig', v)"
|
||||
/>
|
||||
<ModuleEditConfigPanel
|
||||
v-else-if="tab.key === 'edit'"
|
||||
:module-id="String(selectedModule.b_id)"
|
||||
:edit-config="drafts.editConfig"
|
||||
:list-config="drafts.listConfig"
|
||||
:query-config="drafts.queryConfig"
|
||||
:fields="drafts.fields"
|
||||
:groups="drafts.groups"
|
||||
:referenced-group-ids="referencedGroupIds"
|
||||
:loading="busy"
|
||||
@update="(v) => onUpdate('editConfig', v)"
|
||||
@groups-saved="onGroupsSaved"
|
||||
@sync-list="(v) => onUpdate('listConfig', v)"
|
||||
@sync-query="(v) => onUpdate('queryConfig', v)"
|
||||
/>
|
||||
<ModuleQueryConfigPanel
|
||||
v-else-if="tab.key === 'query'"
|
||||
:module-id="String(selectedModule.b_id)"
|
||||
:query-config="drafts.queryConfig"
|
||||
:fields="drafts.fields"
|
||||
:loading="busy"
|
||||
@update="(v) => onUpdate('queryConfig', v)"
|
||||
/>
|
||||
<ModuleAutoCodePanel
|
||||
v-else-if="tab.key === 'auto-code'"
|
||||
:module-id="String(selectedModule.b_id)"
|
||||
:auto-code="drafts.autoCode"
|
||||
:fields="drafts.fields"
|
||||
:loading="busy"
|
||||
@update="onAutoCodeUpdate"
|
||||
/>
|
||||
<ModulePowersPanel
|
||||
v-else-if="tab.key === 'powers'"
|
||||
:module-id="String(selectedModule.b_id)"
|
||||
:module-code="selectedModule?.b_code || ''"
|
||||
:powers="drafts.powers"
|
||||
:loading="busy"
|
||||
@update="(v) => onUpdate('powers', v)"
|
||||
@i18n-saved="onI18nSaved"
|
||||
/>
|
||||
<ModuleI18nPanel
|
||||
v-else-if="tab.key === 'i18n'"
|
||||
:ref="setI18nPanelRef"
|
||||
:module-id="String(selectedModule.b_id)"
|
||||
:i18n-items="i18nItems"
|
||||
:loading="busy"
|
||||
/>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
</template>
|
||||
<a-splitter-panel :min="720">
|
||||
<!-- 右侧详情 -->
|
||||
<main class="mm-main">
|
||||
<FmsLoadingOverlay :loading="configLoading">
|
||||
<template v-if="selectedModule">
|
||||
<!-- 页面头部 -->
|
||||
<div class="mm-header">
|
||||
<a-space>
|
||||
<a-button
|
||||
type="primary"
|
||||
:loading="configSaving"
|
||||
:disabled="configLoading || configSyncing"
|
||||
@click="saveAll"
|
||||
>保存</a-button
|
||||
>
|
||||
<a-button
|
||||
v-if="selectedModule.b_module_type === 'data'"
|
||||
:loading="configSyncing"
|
||||
:disabled="syncDisabled"
|
||||
@click="syncFields"
|
||||
>
|
||||
同步字段
|
||||
</a-button>
|
||||
</a-space>
|
||||
<div class="mm-header-center">
|
||||
<a-space size="middle">
|
||||
<span>启用</span>
|
||||
<a-checkbox
|
||||
:checked="drafts.module?.b_canuse === 1"
|
||||
:disabled="busy"
|
||||
@update:checked="(v) => onSetField('b_canuse', v ? 1 : 0)"
|
||||
/>
|
||||
<a-divider type="vertical" />
|
||||
<span>显示菜单</span>
|
||||
<a-checkbox
|
||||
:checked="drafts.module?.b_canmenu === 1"
|
||||
:disabled="busy || drafts.module?.b_module_type === 'data'"
|
||||
@update:checked="(v) => onSetField('b_canmenu', v ? 1 : 0)"
|
||||
/>
|
||||
<template v-if="drafts.module?.b_module_type === 'data'">
|
||||
<a-divider type="vertical" />
|
||||
<span>自动编码</span>
|
||||
<a-checkbox
|
||||
:checked="autoCodeEnabled"
|
||||
:disabled="busy"
|
||||
@update:checked="setAutoCodeEnabled"
|
||||
/>
|
||||
</template>
|
||||
</a-space>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 未选中模块 -->
|
||||
<div v-else class="mm-empty">
|
||||
<a-result status="info" title="请选择模块" sub-title="从左侧树选择一个模块查看详情" />
|
||||
</div>
|
||||
</FmsLoadingOverlay>
|
||||
</main>
|
||||
</a-splitter-panel>
|
||||
<!-- 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"
|
||||
:force-render="tab.key === 'i18n'"
|
||||
>
|
||||
<ModuleBasicPanel
|
||||
v-if="tab.key === 'basic'"
|
||||
:module-id="String(selectedModule.b_id)"
|
||||
:data="drafts.module"
|
||||
:parent-name="parentModuleName"
|
||||
:loading="busy"
|
||||
@update="(v) => onUpdate('module', v)"
|
||||
@i18n-saved="onI18nSaved"
|
||||
/>
|
||||
<ModuleFieldsPanel
|
||||
v-else-if="tab.key === 'fields'"
|
||||
:module-id="String(selectedModule.b_id)"
|
||||
:module-code="selectedModule?.b_code || ''"
|
||||
:fields="drafts.fields"
|
||||
:loading="busy"
|
||||
@update="onFieldsUpdate"
|
||||
@i18n-saved="onI18nSaved"
|
||||
/>
|
||||
<ModuleListConfigPanel
|
||||
v-else-if="tab.key === 'list'"
|
||||
:module-id="String(selectedModule.b_id)"
|
||||
:list-config="drafts.listConfig"
|
||||
:edit-config="drafts.editConfig"
|
||||
:query-config="drafts.queryConfig"
|
||||
:fields="drafts.fields"
|
||||
:groups="drafts.groups"
|
||||
:referenced-group-ids="referencedGroupIds"
|
||||
:loading="busy"
|
||||
@update="(v) => onUpdate('listConfig', v)"
|
||||
@groups-saved="onGroupsSaved"
|
||||
@sync-edit="(v) => onUpdate('editConfig', v)"
|
||||
@sync-query="(v) => onUpdate('queryConfig', v)"
|
||||
/>
|
||||
<ModuleEditConfigPanel
|
||||
v-else-if="tab.key === 'edit'"
|
||||
:module-id="String(selectedModule.b_id)"
|
||||
:edit-config="drafts.editConfig"
|
||||
:list-config="drafts.listConfig"
|
||||
:query-config="drafts.queryConfig"
|
||||
:fields="drafts.fields"
|
||||
:groups="drafts.groups"
|
||||
:referenced-group-ids="referencedGroupIds"
|
||||
:loading="busy"
|
||||
@update="(v) => onUpdate('editConfig', v)"
|
||||
@groups-saved="onGroupsSaved"
|
||||
@sync-list="(v) => onUpdate('listConfig', v)"
|
||||
@sync-query="(v) => onUpdate('queryConfig', v)"
|
||||
/>
|
||||
<ModuleQueryConfigPanel
|
||||
v-else-if="tab.key === 'query'"
|
||||
:module-id="String(selectedModule.b_id)"
|
||||
:query-config="drafts.queryConfig"
|
||||
:fields="drafts.fields"
|
||||
:loading="busy"
|
||||
@update="(v) => onUpdate('queryConfig', v)"
|
||||
/>
|
||||
<ModuleAutoCodePanel
|
||||
v-else-if="tab.key === 'auto-code'"
|
||||
:module-id="String(selectedModule.b_id)"
|
||||
:auto-code="drafts.autoCode"
|
||||
:fields="drafts.fields"
|
||||
:loading="busy"
|
||||
@update="onAutoCodeUpdate"
|
||||
/>
|
||||
<ModulePowersPanel
|
||||
v-else-if="tab.key === 'powers'"
|
||||
:module-id="String(selectedModule.b_id)"
|
||||
:module-code="selectedModule?.b_code || ''"
|
||||
:powers="drafts.powers"
|
||||
:loading="busy"
|
||||
@update="(v) => onUpdate('powers', v)"
|
||||
@i18n-saved="onI18nSaved"
|
||||
/>
|
||||
<ModuleI18nPanel
|
||||
v-else-if="tab.key === 'i18n'"
|
||||
:ref="setI18nPanelRef"
|
||||
:module-id="String(selectedModule.b_id)"
|
||||
:i18n-items="i18nItems"
|
||||
:loading="busy"
|
||||
/>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 未选中模块 -->
|
||||
<div v-else class="mm-empty">
|
||||
<a-result status="info" title="请选择模块" sub-title="从左侧树选择一个模块查看详情" />
|
||||
</div>
|
||||
</FmsLoadingOverlay>
|
||||
</main>
|
||||
</a-splitter-panel>
|
||||
</a-splitter>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
Reference in new issue
Block a user