20260728225443

This commit is contained in:
oneao committed 2026-07-28 22:54:43 +08:00
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>