20260803225403

This commit is contained in:
oneao committed 2026-08-03 22:54:03 +08:00
1 parent efd5a2bc93
commit e17bbe9db3
40 files changed
+5516 -3910

No files matched your search

@@ -1,7 +1,7 @@
<script setup>
import { computed, h, onMounted, ref, watch } from "vue";
import { useRoute, useRouter } from "vue-router";
import { Database, House, PanelLeftClose, PanelLeftOpen, Settings } from "@lucide/vue";
import { Folder, House, PanelLeftClose, PanelLeftOpen } from "@lucide/vue";
import { useAppStore } from "@/stores/app";
import { usePermissionStore } from "@/stores/permissions";
@@ -9,103 +9,88 @@ const route = useRoute();
const router = useRouter();
const appStore = useAppStore();
const permissionStore = usePermissionStore();
const menuIcon = (icon) => () => h(icon, { size: 16, strokeWidth: 1.8 });
const openKeys = ref(["base", "system"]);
const openKeys = ref([]);
const effectiveCollapsed = computed(() => appStore.sidebarCollapsed);
function moduleLabel(moduleId, path, fallback) {
const module = permissionStore.modules.find(
(item) => item.b_code === moduleId || item.b_route === path,
const menuIcon = (icon) => () => h(icon, { size: 16, strokeWidth: 1.8 });
function buildFunctionMenu() {
const nodes = permissionStore.functionNodes.filter(
(item) => Number(item.b_canmenu) === 1 && permissionStore.canAccessFunctionNode(item.b_id),
);
return appStore.t(module?.b_i18n, fallback);
}
function directoryLabel(fallback) {
const module = permissionStore.modules.find(
(item) => item.b_module_type === "directory" && item.b_name === fallback,
);
return appStore.t(module?.b_i18n, fallback);
}
function canAccessMenu(item) {
if (item.moduleId) return permissionStore.canAccess(item.moduleId);
return true;
}
const menuItems = computed(() => {
const basePages = [
{
key: "/base/othercompany",
label: moduleLabel("base_othercompany", "/base/othercompany", "合作伙伴"),
moduleId: "base_othercompany",
},
{
key: "/base/contact",
label: moduleLabel("v_b_contact", "/base/contact", "联系人"),
moduleId: "v_b_contact",
},
{
key: "/base/otherdata",
label: moduleLabel("base_otherdata", "/base/otherdata", "其他数据"),
moduleId: "base_otherdata",
},
].filter(canAccessMenu);
const systemPages = [
{
key: "/system/module-management",
label: moduleLabel("m_metadata", "/system/module-management", "模块管理"),
moduleId: "m_metadata",
},
{
key: "/system/i18n-type",
label: moduleLabel("b_i18n_type", "/system/i18n-type", "多语言类型"),
moduleId: "b_i18n_type",
},
{
key: "/system/login-log",
label: moduleLabel("d_login_log", "/system/login-log", "登录日志"),
moduleId: "d_login_log",
},
{
key: "/system/user",
label: moduleLabel("b_user", "/system/user", "用户管理"),
moduleId: "b_user",
},
].filter(canAccessMenu);
const items = [
{
key: "/dashboard",
icon: menuIcon(House),
label: appStore.t("common.dashboard", "工作台"),
},
];
if (basePages.length)
items.push({
key: "base",
icon: menuIcon(Database),
label: directoryLabel("基础数据"),
children: basePages,
const menuById = new Map();
const roots = [];
for (const node of nodes) {
if (node.b_node_type === "entry" && !node.b_route) continue;
menuById.set(String(node.b_id), {
key: node.b_node_type === "directory" ? `function:${node.b_id}` : node.b_route,
label: appStore.t(node.b_i18n, node.b_name),
icon: node.b_node_type === "directory" ? menuIcon(Folder) : undefined,
children: node.b_node_type === "directory" ? [] : undefined,
sort: Number(node.b_xh) || 0,
nodeId: String(node.b_id),
parentId: node.b_parent_id == null ? "" : String(node.b_parent_id),
});
if (systemPages.length)
items.push({
key: "system",
icon: menuIcon(Settings),
label: directoryLabel("系统管理"),
children: systemPages,
});
return items;
});
const selectedKeys = computed(() => [route.meta.menuKey || route.path]);
watch(
() => route.path,
(path) => {
if (path.startsWith("/base") && !openKeys.value.includes("base"))
openKeys.value = [...openKeys.value, "base"];
if (path.startsWith("/system") && !openKeys.value.includes("system"))
openKeys.value = [...openKeys.value, "system"];
}
for (const item of menuById.values()) {
const parent = menuById.get(item.parentId);
if (parent?.children) parent.children.push(item);
else roots.push(item);
}
const sortItems = (items) => {
items.sort((left, right) => left.sort - right.sort || left.nodeId.localeCompare(right.nodeId));
for (const item of items) {
if (item.children) sortItems(item.children);
delete item.sort;
delete item.nodeId;
delete item.parentId;
}
};
sortItems(roots);
return roots;
}
const menuItems = computed(() => [
{
key: "/dashboard",
icon: menuIcon(House),
label: appStore.t("common.dashboard", "工作台"),
},
{ immediate: true },
);
...buildFunctionMenu(),
]);
const selectedKeys = computed(() => [route.path]);
function syncOpenKeys(path) {
const entry = permissionStore.functionNodes.find(
(item) => item.b_node_type === "entry" && String(item.b_route || "") === String(path || ""),
);
if (!entry) return;
const byId = new Map(permissionStore.functionNodes.map((item) => [String(item.b_id), item]));
const keys = [];
let parentId = entry.b_parent_id == null ? "" : String(entry.b_parent_id);
while (parentId) {
keys.unshift(`function:${parentId}`);
const parent = byId.get(parentId);
parentId = parent?.b_parent_id == null ? "" : String(parent.b_parent_id);
}
openKeys.value = [...new Set([...openKeys.value, ...keys])];
}
watch(() => route.path, syncOpenKeys, { immediate: true });
function navigate({ key }) {
if (typeof key === "string" && key.startsWith("/")) router.push(key);
}
onMounted(() => permissionStore.load().catch(() => {}));
onMounted(async () => {
try {
await permissionStore.load();
syncOpenKeys(route.path);
} catch {
// The route guard owns authentication and error reporting.
}
});
</script>
<template>
@@ -140,9 +125,12 @@ onMounted(() => permissionStore.load().catch(() => {}));
: appStore.t('common.collapseSidebar', '收起侧栏')
"
placement="right"
><a-button type="text" class="sider-collapse" @click="appStore.toggleSidebar"
><PanelLeftOpen v-if="effectiveCollapsed" /><PanelLeftClose v-else /></a-button
></a-tooltip>
>
<a-button type="text" class="sider-collapse" @click="appStore.toggleSidebar">
<PanelLeftOpen v-if="effectiveCollapsed" />
<PanelLeftClose v-else />
</a-button>
</a-tooltip>
</div>
</a-layout-sider>
</template>
@@ -167,19 +155,6 @@ onMounted(() => permissionStore.load().catch(() => {}));
margin: 0;
border-radius: 0;
line-height: 36px;
transition: padding-inline-start 0.2s ease;
}
:deep(.ant-menu-inline-collapsed) {
.ant-menu-title-content {
display: none !important;
}
.ant-menu-item,
.ant-menu-submenu-title {
padding-inline-start: 20px !important;
}
.ant-menu-item-icon {
vertical-align: middle;
}
}
}
.brand {