20260728225443
This commit is contained in:
1 parent
5ec7aadef9
commit
f6b79ea09a
30 files changed
+1541
-1120
No files matched your search
@@ -12,22 +12,52 @@ const permissionStore = usePermissionStore();
|
||||
const menuIcon = (icon) => () => h(icon, { size: 16, strokeWidth: 1.8 });
|
||||
const openKeys = ref(["system"]);
|
||||
const effectiveCollapsed = computed(() => appStore.sidebarCollapsed);
|
||||
function moduleLabel(moduleId, path, fallback) {
|
||||
const module = permissionStore.modules.find(
|
||||
(item) => item.b_code === moduleId || item.b_route === path,
|
||||
);
|
||||
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 systemPages = [
|
||||
{ key: "/system/module-management", label: "模块管理", moduleId: "m_metadata" },
|
||||
{ key: "/system/i18n-type", label: "多语言类型", moduleId: "b_i18n_type" },
|
||||
{ key: "/system/login-log", label: "登录日志", moduleId: "d_login_log" },
|
||||
{
|
||||
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",
|
||||
},
|
||||
].filter(canAccessMenu);
|
||||
const items = [{ key: "/dashboard", icon: menuIcon(House), label: "工作台" }];
|
||||
const items = [
|
||||
{
|
||||
key: "/dashboard",
|
||||
icon: menuIcon(House),
|
||||
label: appStore.t("common.dashboard", "工作台"),
|
||||
},
|
||||
];
|
||||
if (systemPages.length)
|
||||
items.push({
|
||||
key: "system",
|
||||
icon: menuIcon(Settings),
|
||||
label: "系统管理",
|
||||
label: directoryLabel("系统管理"),
|
||||
children: systemPages,
|
||||
});
|
||||
return items;
|
||||
@@ -59,7 +89,7 @@ onMounted(() => permissionStore.load().catch(() => {}));
|
||||
>
|
||||
<div class="brand" :class="{ 'brand--collapsed': effectiveCollapsed }">
|
||||
<div class="brand__mark">F</div>
|
||||
<strong>FMS 管理系统</strong>
|
||||
<strong>{{ appStore.t("common.appName", "FMS 管理系统") }}</strong>
|
||||
</div>
|
||||
<div class="sider-menu">
|
||||
<a-menu
|
||||
@@ -72,7 +102,13 @@ onMounted(() => permissionStore.load().catch(() => {}));
|
||||
/>
|
||||
</div>
|
||||
<div class="sider-footer">
|
||||
<a-tooltip :title="effectiveCollapsed ? '展开侧栏' : '收起侧栏'" placement="right"
|
||||
<a-tooltip
|
||||
:title="
|
||||
effectiveCollapsed
|
||||
? appStore.t('common.expandSidebar', '展开侧栏')
|
||||
: 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>
|
||||
|
||||
@@ -13,10 +13,12 @@ import {
|
||||
} from "@lucide/vue";
|
||||
import { theme } from "antdv-next";
|
||||
import { useAppStore } from "@/stores/app";
|
||||
import { usePermissionStore } from "@/stores/permissions";
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const appStore = useAppStore();
|
||||
const permissionStore = usePermissionStore();
|
||||
const { token } = theme.useToken();
|
||||
const headerRef = ref(null);
|
||||
const trackRef = ref(null);
|
||||
@@ -34,6 +36,7 @@ watch(
|
||||
appStore.addVisitedTab({
|
||||
key: route.fullPath,
|
||||
label: getTabLabel(),
|
||||
i18nKey: getTabI18nKey(),
|
||||
closable: route.fullPath !== "/dashboard",
|
||||
});
|
||||
}
|
||||
@@ -74,6 +77,18 @@ function getTabLabel() {
|
||||
return route.meta.title;
|
||||
}
|
||||
|
||||
function getTabI18nKey() {
|
||||
if (route.path === "/dashboard") return "common.dashboard";
|
||||
const module = permissionStore.modules.find(
|
||||
(item) => item.b_code === route.meta.moduleId || item.b_route === route.path,
|
||||
);
|
||||
return module?.b_i18n || "";
|
||||
}
|
||||
|
||||
function getDisplayTabLabel(item) {
|
||||
return appStore.t(item.i18nKey, item.label);
|
||||
}
|
||||
|
||||
function closeTab(targetKey) {
|
||||
if (typeof targetKey !== "string" || targetKey === "/dashboard") return;
|
||||
|
||||
@@ -226,7 +241,7 @@ function toggleMaximize() {
|
||||
@click="changeTab(item.key)"
|
||||
@keydown="handleTabKeydown($event, index)"
|
||||
>
|
||||
<span>{{ item.label }}</span>
|
||||
<span>{{ getDisplayTabLabel(item) }}</span>
|
||||
</button>
|
||||
<button
|
||||
v-if="item.closable"
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
<script setup>
|
||||
import { computed, h, ref, watch } from "vue";
|
||||
import { computed, h, onMounted, ref, watch } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { message } from "antdv-next";
|
||||
import { Bell, ChevronDown, Languages, LogOut, Settings, UserRound } from "@lucide/vue";
|
||||
import { useAppStore } from "@/stores/app";
|
||||
import { useAuthStore } from "@/stores/auth";
|
||||
import { usePermissionStore } from "@/stores/permissions";
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const appStore = useAppStore();
|
||||
const authStore = useAuthStore();
|
||||
const permissionStore = usePermissionStore();
|
||||
const profileOpen = ref(false);
|
||||
const logoutOpen = ref(false);
|
||||
const menuIcon = (icon) => () => h(icon, { size: 16, strokeWidth: 1.8 });
|
||||
@@ -16,35 +19,74 @@ const menuIcon = (icon) => () => h(icon, { size: 16, strokeWidth: 1.8 });
|
||||
const breadcrumbItems = computed(() => {
|
||||
const items = [];
|
||||
|
||||
if (route.path === "/dashboard") return [{ label: "工作台" }];
|
||||
if (route.meta.section) items.push({ label: route.meta.section });
|
||||
if (route.meta.title) items.push({ label: route.meta.title });
|
||||
if (route.path === "/dashboard") {
|
||||
return [{ label: appStore.t("common.dashboard", "工作台") }];
|
||||
}
|
||||
if (route.meta.section) {
|
||||
const directory = permissionStore.modules.find(
|
||||
(item) => item.b_module_type === "directory" && item.b_name === route.meta.section,
|
||||
);
|
||||
items.push({ label: appStore.t(directory?.b_i18n, route.meta.section) });
|
||||
}
|
||||
if (route.meta.title) {
|
||||
const module = permissionStore.modules.find(
|
||||
(item) => item.b_code === route.meta.moduleId || item.b_route === route.path,
|
||||
);
|
||||
items.push({ label: appStore.t(module?.b_i18n, route.meta.title) });
|
||||
}
|
||||
|
||||
return items;
|
||||
});
|
||||
|
||||
const languageMenu = computed(() => ({
|
||||
items: [
|
||||
{ key: "zh-CN", label: "简体中文" },
|
||||
{ key: "en", label: "English" },
|
||||
],
|
||||
onClick: ({ key }) => appStore.setLocale(key),
|
||||
items: appStore.languages.map((item) => ({
|
||||
key: String(item.b_code),
|
||||
label: item.b_name || item.b_code,
|
||||
})),
|
||||
onClick: async ({ key }) => {
|
||||
try {
|
||||
await appStore.setLocale(key);
|
||||
} catch (error) {
|
||||
message.error("切换语言失败:" + (error?.message || error));
|
||||
}
|
||||
},
|
||||
selectable: true,
|
||||
selectedKeys: [appStore.locale],
|
||||
}));
|
||||
|
||||
const userMenu = {
|
||||
const userMenu = computed(() => ({
|
||||
items: [
|
||||
{ key: "profile", icon: menuIcon(UserRound), label: "个人资料" },
|
||||
{ key: "settings", icon: menuIcon(Settings), label: "账户设置" },
|
||||
{
|
||||
key: "profile",
|
||||
icon: menuIcon(UserRound),
|
||||
label: appStore.t("common.profile", "个人资料"),
|
||||
},
|
||||
{
|
||||
key: "settings",
|
||||
icon: menuIcon(Settings),
|
||||
label: appStore.t("common.accountSettings", "账户设置"),
|
||||
},
|
||||
{ type: "divider" },
|
||||
{ key: "logout", icon: menuIcon(LogOut), label: "退出登录", danger: true },
|
||||
{
|
||||
key: "logout",
|
||||
icon: menuIcon(LogOut),
|
||||
label: appStore.t("common.logout", "退出登录"),
|
||||
danger: true,
|
||||
},
|
||||
],
|
||||
onClick: handleUserMenu,
|
||||
};
|
||||
}));
|
||||
|
||||
const displayName = computed(() => authStore.user?.name || authStore.user?.id || "当前用户");
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
await appStore.loadLanguages();
|
||||
} catch (error) {
|
||||
message.error("加载语言配置失败:" + (error?.message || error));
|
||||
}
|
||||
});
|
||||
|
||||
watch(
|
||||
() => appStore.locale,
|
||||
(locale) => document.documentElement.setAttribute("lang", locale),
|
||||
@@ -72,15 +114,30 @@ function confirmLogout() {
|
||||
</a-breadcrumb>
|
||||
|
||||
<div class="toolbar-actions">
|
||||
<a-tooltip title="切换语言" placement="bottom">
|
||||
<a-dropdown :menu="languageMenu" :trigger="['click']" placement="bottomRight">
|
||||
<a-button type="text" class="toolbar-action" aria-label="切换语言">
|
||||
<a-tooltip :title="appStore.t('common.switchLanguage', '切换语言')" placement="bottom">
|
||||
<a-dropdown
|
||||
:menu="languageMenu"
|
||||
:disabled="
|
||||
appStore.languagesLoading || appStore.translationLoading || !appStore.languages.length
|
||||
"
|
||||
:trigger="['click']"
|
||||
placement="bottomRight"
|
||||
>
|
||||
<a-button
|
||||
type="text"
|
||||
class="toolbar-action"
|
||||
:aria-label="appStore.t('common.switchLanguage', '切换语言')"
|
||||
>
|
||||
<template #icon><Languages /></template>
|
||||
</a-button>
|
||||
</a-dropdown>
|
||||
</a-tooltip>
|
||||
<a-tooltip title="通知提醒" placement="bottom">
|
||||
<a-button type="text" class="toolbar-action" aria-label="通知提醒">
|
||||
<a-tooltip :title="appStore.t('common.notifications', '通知提醒')" placement="bottom">
|
||||
<a-button
|
||||
type="text"
|
||||
class="toolbar-action"
|
||||
:aria-label="appStore.t('common.notifications', '通知提醒')"
|
||||
>
|
||||
<a-badge dot><Bell /></a-badge>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
|
||||
Reference in new issue
Block a user