diff --git a/src/App.vue b/src/App.vue index a801c855..38294b4c 100644 --- a/src/App.vue +++ b/src/App.vue @@ -13,14 +13,26 @@ diff --git a/src/layouts/common/global-header/components/global-breadcrumb.vue b/src/layouts/common/global-header/components/global-breadcrumb.vue index e24ab689..be2e257a 100644 --- a/src/layouts/common/global-header/components/global-breadcrumb.vue +++ b/src/layouts/common/global-header/components/global-breadcrumb.vue @@ -9,7 +9,7 @@ v-if="theme.header.crumb.showIcon" class="inline-block align-text-bottom mr-4px text-16px" /> - {{ breadcrumb.label }} + {{ breadcrumb.i18nTitle ? t(breadcrumb.i18nTitle) : breadcrumb.label }} @@ -33,6 +35,7 @@ import { routePath } from '@/router'; import { useRouteStore, useThemeStore } from '@/store'; import { useRouterPush } from '@/composables'; import { getBreadcrumbByRouteKey } from '@/utils'; +import { t } from '@/locales'; defineOptions({ name: 'GlobalBreadcrumb' }); diff --git a/src/layouts/common/global-header/components/header-menu.vue b/src/layouts/common/global-header/components/header-menu.vue index e19cd64c..e67550c1 100644 --- a/src/layouts/common/global-header/components/header-menu.vue +++ b/src/layouts/common/global-header/components/header-menu.vue @@ -20,6 +20,7 @@ import { useRoute } from 'vue-router'; import type { MenuOption } from 'naive-ui'; import { useRouteStore, useThemeStore } from '@/store'; import { useRouterPush } from '@/composables'; +import { translateMenuLabel } from '@/utils'; defineOptions({ name: 'HeaderMenu' }); @@ -28,7 +29,7 @@ const routeStore = useRouteStore(); const theme = useThemeStore(); const { routerPush } = useRouterPush(); -const menus = computed(() => routeStore.menus as App.GlobalMenuOption[]); +const menus = computed(() => translateMenuLabel(routeStore.menus as App.GlobalMenuOption[])); const activeKey = computed(() => (route.meta?.activeMenu ? route.meta.activeMenu : route.name) as string); function handleUpdateMenu(_key: string, item: MenuOption) { diff --git a/src/layouts/common/global-header/components/index.ts b/src/layouts/common/global-header/components/index.ts index f358911a..3443505e 100644 --- a/src/layouts/common/global-header/components/index.ts +++ b/src/layouts/common/global-header/components/index.ts @@ -7,6 +7,7 @@ import ThemeMode from './theme-mode.vue'; import UserAvatar from './user-avatar.vue'; import SystemMessage from './system-message.vue'; import SettingButton from './setting-button.vue'; +import ToggleLang from './toggle-lang.vue'; export { MenuCollapse, @@ -17,5 +18,6 @@ export { ThemeMode, UserAvatar, SystemMessage, - SettingButton + SettingButton, + ToggleLang }; diff --git a/src/layouts/common/global-header/components/toggle-lang.vue b/src/layouts/common/global-header/components/toggle-lang.vue new file mode 100644 index 00000000..d5e8e2f9 --- /dev/null +++ b/src/layouts/common/global-header/components/toggle-lang.vue @@ -0,0 +1,33 @@ + + + + diff --git a/src/layouts/common/global-header/index.vue b/src/layouts/common/global-header/index.vue index c25dd1c6..4c66c4c0 100644 --- a/src/layouts/common/global-header/index.vue +++ b/src/layouts/common/global-header/index.vue @@ -11,6 +11,7 @@ + @@ -32,7 +33,8 @@ import { SettingButton, SystemMessage, ThemeMode, - UserAvatar + UserAvatar, + ToggleLang } from './components'; defineOptions({ name: 'GlobalHeader' }); diff --git a/src/layouts/common/global-logo/index.vue b/src/layouts/common/global-logo/index.vue index a5a25b29..d4c592c2 100644 --- a/src/layouts/common/global-logo/index.vue +++ b/src/layouts/common/global-logo/index.vue @@ -1,11 +1,7 @@ - {{ item.meta.title }} + {{ item.meta.i18nTitle ? t(item.meta.i18nTitle) : item.meta.title }} { // 设置document title - useTitle(to.meta.title); + useTitle(to.meta.i18nTitle ? t(to.meta.i18nTitle) : to.meta.title); // 结束 loadingBar window.$loadingBar?.finish(); }); diff --git a/src/router/modules/dashboard.ts b/src/router/modules/dashboard.ts index 3243b743..82a863c0 100644 --- a/src/router/modules/dashboard.ts +++ b/src/router/modules/dashboard.ts @@ -10,7 +10,8 @@ const dashboard: AuthRoute.Route = { meta: { title: '分析页', requiresAuth: true, - icon: 'icon-park-outline:analysis' + icon: 'icon-park-outline:analysis', + i18nTitle: 'message.routes.dashboard.analysis' } }, { @@ -20,14 +21,16 @@ const dashboard: AuthRoute.Route = { meta: { title: '工作台', requiresAuth: true, - icon: 'icon-park-outline:workbench' + icon: 'icon-park-outline:workbench', + i18nTitle: 'message.routes.dashboard.workbench' } } ], meta: { title: '仪表盘', icon: 'mdi:monitor-dashboard', - order: 1 + order: 1, + i18nTitle: 'message.routes.dashboard.dashboard' } }; diff --git a/src/store/modules/tab/helpers.ts b/src/store/modules/tab/helpers.ts index d5833625..87ed2158 100644 --- a/src/store/modules/tab/helpers.ts +++ b/src/store/modules/tab/helpers.ts @@ -7,7 +7,6 @@ import { localStg } from '@/utils'; */ export function getTabRouteByVueRoute(route: RouteRecordNormalized | RouteLocationNormalizedLoaded) { const fullPath = hasFullPath(route) ? route.fullPath : route.path; - const tabRoute: App.GlobalTabRoute = { name: route.name, fullPath, diff --git a/src/typings/route.d.ts b/src/typings/route.d.ts index 0235dbe4..a40004f7 100644 --- a/src/typings/route.d.ts +++ b/src/typings/route.d.ts @@ -31,6 +31,8 @@ declare namespace AuthRoute { interface RouteMeta { /** 路由标题(可用来作document.title或者菜单的名称) */ title: string; + /** 用来支持多国语言 如果i18nTitle和title同时存在优先使用i18nTitle */ + i18nTitle?: string; /** 路由的动态路径(需要动态路径的页面需要将path添加进范型参数) */ dynamicPath?: AuthRouteUtils.GetDynamicPath; /** 作为单级路由的父级路由布局组件 */ diff --git a/src/typings/storage.d.ts b/src/typings/storage.d.ts index 892a7167..7d343a52 100644 --- a/src/typings/storage.d.ts +++ b/src/typings/storage.d.ts @@ -18,5 +18,7 @@ declare namespace StorageInterface { themeSettings: Theme.Setting; /** 多页签路由信息 */ multiTabRoutes: App.GlobalTabRoute[]; + /** 本地语言缓存 */ + lang: I18nType.langType; } } diff --git a/src/typings/system.d.ts b/src/typings/system.d.ts index cb88b031..f9b1936e 100644 --- a/src/typings/system.d.ts +++ b/src/typings/system.d.ts @@ -242,6 +242,7 @@ declare namespace App { routePath: string; icon?: () => import('vue').VNodeChild; children?: GlobalMenuOption[]; + i18nTitle?: string; }; /** 面包屑 */ @@ -252,6 +253,7 @@ declare namespace App { routeName: string; hasChildren: boolean; icon?: import('vue').Component; + i18nTitle?: string; options?: import('naive-ui/es/dropdown/src/interface').DropdownMixedOption[]; }; @@ -300,6 +302,7 @@ declare namespace App { } declare namespace I18nType { + type langType = 'en' | 'zh-CN'; interface Schema { system: { title: string; diff --git a/src/utils/router/breadcrumb.ts b/src/utils/router/breadcrumb.ts index 2955f5d4..19640264 100644 --- a/src/utils/router/breadcrumb.ts +++ b/src/utils/router/breadcrumb.ts @@ -59,7 +59,8 @@ function transformBreadcrumbMenuToBreadcrumb(menu: App.GlobalMenuOption, rootPat label: menu.label as string, routeName: menu.routeName, disabled: menu.routePath === rootPath, - hasChildren + hasChildren, + i18nTitle: menu.i18nTitle }; if (menu.icon) { breadcrumb.icon = menu.icon; diff --git a/src/utils/router/menu.ts b/src/utils/router/menu.ts index b3eb5dc7..abed1766 100644 --- a/src/utils/router/menu.ts +++ b/src/utils/router/menu.ts @@ -1,4 +1,5 @@ import { useIconRender } from '@/composables'; +import { t } from '@/locales'; /** * 将权限路由转换成菜单 @@ -18,7 +19,8 @@ export function transformAuthRouteToMenu(routes: AuthRoute.Route[]): App.GlobalM key: routeName, label: meta.title, routeName, - routePath: path + routePath: path, + i18nTitle: meta.i18nTitle }, icon: meta.icon, localIcon: meta.localIcon, @@ -33,6 +35,28 @@ export function transformAuthRouteToMenu(routes: AuthRoute.Route[]): App.GlobalM return globalMenu; } +/** + * 翻译菜单 + * @param menus + * @returns + */ +export function translateMenuLabel(menus: App.GlobalMenuOption[]): App.GlobalMenuOption[] { + const globalMenu: App.GlobalMenuOption[] = []; + menus.forEach(menu => { + let menuChildren: App.GlobalMenuOption[] | undefined; + if (menu.children && menu.children.length > 0) { + menuChildren = translateMenuLabel(menu.children); + } + const menuItem: App.GlobalMenuOption = { + ...menu, + children: menuChildren, + label: menu.i18nTitle ? t(menu.i18nTitle) : menu.label + }; + globalMenu.push(menuItem); + }); + return globalMenu; +} + /** * 获取当前路由所在菜单数据的paths * @param activeKey - 当前路由的key