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 }}
@@ -19,7 +19,9 @@
class="inline-block align-text-bottom mr-4px text-16px"
:class="{ 'text-#BBBBBB': theme.header.inverted }"
/>
- {{ 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 @@
-
+
{{ t('message.system.title') }}
@@ -13,7 +9,7 @@
diff --git a/src/layouts/common/global-sider/components/vertical-mix-sider/index.vue b/src/layouts/common/global-sider/components/vertical-mix-sider/index.vue
index abc73b46..5d32079e 100644
--- a/src/layouts/common/global-sider/components/vertical-mix-sider/index.vue
+++ b/src/layouts/common/global-sider/components/vertical-mix-sider/index.vue
@@ -26,7 +26,9 @@ import { useRoute } from 'vue-router';
import { useAppStore, useRouteStore, useThemeStore } from '@/store';
import { useRouterPush } from '@/composables';
import { useBoolean } from '@/hooks';
+import { translateMenuLabel } from '@/utils';
import { GlobalLogo } from '@/layouts/common';
+import { t } from '@/locales';
import { MixMenuCollapse, MixMenuDetail, MixMenuDrawer } from './components';
defineOptions({ name: 'VerticalMixSider' });
@@ -45,13 +47,13 @@ function setActiveParentRouteName(routeName: string) {
const firstDegreeMenus = computed(() =>
routeStore.menus.map(item => {
- const { routeName, label } = item;
+ const { routeName, label, i18nTitle } = item;
const icon = item?.icon;
const hasChildren = Boolean(item.children && item.children.length);
return {
routeName,
- label,
+ label: i18nTitle ? t(i18nTitle) : label,
icon,
hasChildren
};
@@ -88,7 +90,7 @@ const activeChildMenus = computed(() => {
routeStore.menus.some(item => {
const flag = item.routeName === activeParentRouteName.value && Boolean(item.children?.length);
if (flag) {
- menus.push(...(item.children || []));
+ menus.push(...translateMenuLabel((item.children || []) as App.GlobalMenuOption[]));
}
return flag;
});
diff --git a/src/layouts/common/global-sider/components/vertical-sider/components/vertical-menu.vue b/src/layouts/common/global-sider/components/vertical-sider/components/vertical-menu.vue
index b5932cc0..8f5c4e09 100644
--- a/src/layouts/common/global-sider/components/vertical-sider/components/vertical-menu.vue
+++ b/src/layouts/common/global-sider/components/vertical-sider/components/vertical-menu.vue
@@ -21,7 +21,7 @@ import { useRoute } from 'vue-router';
import type { MenuOption } from 'naive-ui';
import { useAppStore, useRouteStore, useThemeStore } from '@/store';
import { useRouterPush } from '@/composables';
-import { getActiveKeyPathsOfMenus } from '@/utils';
+import { getActiveKeyPathsOfMenus, translateMenuLabel } from '@/utils';
defineOptions({ name: 'VerticalMenu' });
@@ -31,7 +31,7 @@ const theme = useThemeStore();
const routeStore = useRouteStore();
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);
const expandedKeys = ref([]);
diff --git a/src/layouts/common/global-tab/components/tab-detail/index.vue b/src/layouts/common/global-tab/components/tab-detail/index.vue
index 50c200f5..828ecc42 100644
--- a/src/layouts/common/global-tab/components/tab-detail/index.vue
+++ b/src/layouts/common/global-tab/components/tab-detail/index.vue
@@ -19,7 +19,7 @@
class="inline-block align-text-bottom text-16px"
/>
- {{ 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