feat(layout): 菜单支持国际化
This commit is contained in:
@@ -3,7 +3,25 @@ import { createGetRoutes, router } from '@/plugins/router-plugin';
|
|||||||
import type { MenuOption } from 'naive-ui';
|
import type { MenuOption } from 'naive-ui';
|
||||||
import { RouterLink, type RouteRecordRaw } from 'vue-router';
|
import { RouterLink, type RouteRecordRaw } from 'vue-router';
|
||||||
import IconMenuRounded from '~icons/material-symbols/menu-rounded';
|
import IconMenuRounded from '~icons/material-symbols/menu-rounded';
|
||||||
import { useAppStore } from '../../stores/app-store';
|
import { useAppStore } from '@/stores/app-store';
|
||||||
|
|
||||||
|
import zhCN from '@/pages/_page-title-locales/zh-CN';
|
||||||
|
import enUS from '@/pages/_page-title-locales/en-US';
|
||||||
|
|
||||||
|
const { t, te } = useI18n({
|
||||||
|
inheritLocale: true,
|
||||||
|
useScope: 'local',
|
||||||
|
missing: (locale, key) => {
|
||||||
|
consola.warn(`菜单翻译缺失: locale=${locale}, key=${key}`);
|
||||||
|
return key;
|
||||||
|
},
|
||||||
|
fallbackRoot: true,
|
||||||
|
messages: {
|
||||||
|
'zh-CN': zhCN,
|
||||||
|
'en-US': enUS,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
// 路由转换为菜单树的辅助函数
|
// 路由转换为菜单树的辅助函数
|
||||||
function convertRoutesToMenuOptions(routes: Readonly<RouteRecordRaw[]>): MenuOption[] {
|
function convertRoutesToMenuOptions(routes: Readonly<RouteRecordRaw[]>): MenuOption[] {
|
||||||
const menuMap = new Map<string, MenuOption>();
|
const menuMap = new Map<string, MenuOption>();
|
||||||
@@ -31,7 +49,9 @@ function convertRoutesToMenuOptions(routes: Readonly<RouteRecordRaw[]>): MenuOpt
|
|||||||
// 构建菜单树
|
// 构建菜单树
|
||||||
for (const route of validRoutes) {
|
for (const route of validRoutes) {
|
||||||
const pathSegments = route.path.split('/').filter(Boolean);
|
const pathSegments = route.path.split('/').filter(Boolean);
|
||||||
const text = route.meta?.title || route.name;
|
const routeName = route.name as string;
|
||||||
|
const text = te(routeName) ? t(routeName) : route.meta?.title || routeName;
|
||||||
|
|
||||||
const menuOption: MenuOption = {
|
const menuOption: MenuOption = {
|
||||||
label: () => (route.meta?.link === false ? text : <RouterLink to={route}>{text}</RouterLink>),
|
label: () => (route.meta?.link === false ? text : <RouterLink to={route}>{text}</RouterLink>),
|
||||||
key: route.path,
|
key: route.path,
|
||||||
@@ -72,8 +92,8 @@ function convertRoutesToMenuOptions(routes: Readonly<RouteRecordRaw[]>): MenuOpt
|
|||||||
const routes = createGetRoutes(router)();
|
const routes = createGetRoutes(router)();
|
||||||
const menuOptions = computed(() => convertRoutesToMenuOptions(routes));
|
const menuOptions = computed(() => convertRoutesToMenuOptions(routes));
|
||||||
|
|
||||||
console.debug('原始路由:', JSON.stringify(routes, null, 0));
|
// console.debug('原始路由:', JSON.stringify(routes, null, 0));
|
||||||
console.debug('转换后的菜单:', JSON.stringify(menuOptions.value, null, 0));
|
// console.debug('转换后的菜单:', JSON.stringify(menuOptions.value, null, 0));
|
||||||
|
|
||||||
const menuInstRef = useTemplateRef('menuInstRef');
|
const menuInstRef = useTemplateRef('menuInstRef');
|
||||||
const selectedKey = ref('');
|
const selectedKey = ref('');
|
||||||
|
|||||||
@@ -7,7 +7,12 @@ const appStore = useAppStore();
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<AdminLayout :is-mobile="appStore.isMobile" v-model:sider-collapse="appStore.sidebarCollapsed">
|
<AdminLayout
|
||||||
|
:footer-visible="!false"
|
||||||
|
:tab-visible="false"
|
||||||
|
:is-mobile="appStore.isMobile"
|
||||||
|
v-model:sider-collapse="appStore.sidebarCollapsed"
|
||||||
|
>
|
||||||
<template #header>
|
<template #header>
|
||||||
<BaseLayoutHeader />
|
<BaseLayoutHeader />
|
||||||
</template>
|
</template>
|
||||||
@@ -19,9 +24,6 @@ const appStore = useAppStore();
|
|||||||
<template #sider>
|
<template #sider>
|
||||||
<BaseLayoutSider />
|
<BaseLayoutSider />
|
||||||
</template>
|
</template>
|
||||||
<div class="bg-yellow-100/28 dark:bg-yellow-900/28 text-yellow-900 dark:text-yellow-100 p-4">
|
|
||||||
4#GlobalMenu
|
|
||||||
</div>
|
|
||||||
<!-- <div>GlobalContent</div> -->
|
<!-- <div>GlobalContent</div> -->
|
||||||
<router-view v-slot="{ Component }">
|
<router-view v-slot="{ Component }">
|
||||||
<transition name="fade" mode="out-in">
|
<transition name="fade" mode="out-in">
|
||||||
@@ -30,8 +32,10 @@ const appStore = useAppStore();
|
|||||||
</router-view>
|
</router-view>
|
||||||
<!-- <div>ThemeDrawer</div> -->
|
<!-- <div>ThemeDrawer</div> -->
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<div class="bg-red-100/28 dark:bg-red-900/28 text-red-900 dark:text-red-100 h-full">
|
<div
|
||||||
5#GlobalFooter
|
class="bg-red-100/28 dark:bg-red-900/28 text-red-900 dark:text-red-100 h-full flex items-center justify-center"
|
||||||
|
>
|
||||||
|
GlobalFooter
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</AdminLayout>
|
</AdminLayout>
|
||||||
|
|||||||
12
src/pages/_page-title-locales/en-US.ts
Normal file
12
src/pages/_page-title-locales/en-US.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import type { RouteLocalizationFlags } from '@/plugins/router-plugin';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
Root: 'Root',
|
||||||
|
$Path: '$Path',
|
||||||
|
Demos: 'Demos',
|
||||||
|
DemosApiDemo: 'API Demo',
|
||||||
|
DemosCounterDemo: 'Counter Demo',
|
||||||
|
DemosI18nDemo: 'i18n Demo',
|
||||||
|
DemosWebsocketDemo: 'WebSocket Demo',
|
||||||
|
Home: 'Home',
|
||||||
|
} satisfies RouteLocalizationFlags;
|
||||||
12
src/pages/_page-title-locales/zh-CN.ts
Normal file
12
src/pages/_page-title-locales/zh-CN.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import type { RouteLocalizationFlags } from '@/plugins/router-plugin';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
Root: '根 (Gēn)',
|
||||||
|
$Path: '$Path',
|
||||||
|
Demos: '示例演示',
|
||||||
|
DemosApiDemo: 'API 调用示例',
|
||||||
|
DemosCounterDemo: '点击计数器',
|
||||||
|
DemosI18nDemo: '国际化示例',
|
||||||
|
DemosWebsocketDemo: 'WebSocket 示例',
|
||||||
|
Home: '首页',
|
||||||
|
} satisfies RouteLocalizationFlags;
|
||||||
@@ -2,9 +2,7 @@
|
|||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
|
|
||||||
definePage({
|
definePage({
|
||||||
meta: {
|
meta: {},
|
||||||
title: 'API 调用示例',
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// ========== API 模块 ==========
|
// ========== API 模块 ==========
|
||||||
|
|||||||
@@ -3,9 +3,7 @@ import { ref } from 'vue';
|
|||||||
import { NButton } from 'naive-ui';
|
import { NButton } from 'naive-ui';
|
||||||
|
|
||||||
definePage({
|
definePage({
|
||||||
meta: {
|
meta: {},
|
||||||
title: '点击计数器',
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// ========== 计数器模块 ==========
|
// ========== 计数器模块 ==========
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
definePage({
|
definePage({
|
||||||
meta: {
|
meta: {},
|
||||||
title: '国际化示例',
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
const { t, locale } = useI18n();
|
const { t, locale } = useI18n();
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
definePage({ meta: { title: '示例演示', link: false } });
|
definePage({ meta: { link: false } });
|
||||||
</script>
|
</script>
|
||||||
<template><div>此页面文件仅用于在侧边栏菜单中显示示例演示分组标题</div></template>
|
<template><div>此页面文件仅用于在侧边栏菜单中显示示例演示分组标题</div></template>
|
||||||
|
|||||||
@@ -2,9 +2,7 @@
|
|||||||
import { ref, onUnmounted, computed, nextTick } from 'vue';
|
import { ref, onUnmounted, computed, nextTick } from 'vue';
|
||||||
|
|
||||||
definePage({
|
definePage({
|
||||||
meta: {
|
meta: {},
|
||||||
title: 'WebSocket 示例',
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// ========== WebSocket 模块 ==========
|
// ========== WebSocket 模块 ==========
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { DataLoaderPlugin } from 'unplugin-vue-router/data-loaders';
|
|||||||
import { setupLayouts } from 'virtual:meta-layouts';
|
import { setupLayouts } from 'virtual:meta-layouts';
|
||||||
// import { createGetRoutes, setupLayouts } from 'virtual:generated-layouts';
|
// import { createGetRoutes, setupLayouts } from 'virtual:generated-layouts';
|
||||||
import { createRouter, createWebHistory } from 'vue-router';
|
import { createRouter, createWebHistory } from 'vue-router';
|
||||||
|
import type { RouteNamedMap } from 'vue-router/auto-routes';
|
||||||
import { routes, handleHotUpdate } from 'vue-router/auto-routes';
|
import { routes, handleHotUpdate } from 'vue-router/auto-routes';
|
||||||
|
|
||||||
const setupLayoutsResult = setupLayouts(routes);
|
const setupLayoutsResult = setupLayouts(routes);
|
||||||
@@ -63,6 +64,7 @@ declare module 'vue-router' {
|
|||||||
|
|
||||||
export { router, setupLayoutsResult };
|
export { router, setupLayoutsResult };
|
||||||
export { createGetRoutes } from 'virtual:meta-layouts';
|
export { createGetRoutes } from 'virtual:meta-layouts';
|
||||||
|
export type RouteLocalizationFlags = Record<keyof RouteNamedMap, string>;
|
||||||
|
|
||||||
if (__DEV__) Object.assign(globalThis, { router });
|
if (__DEV__) Object.assign(globalThis, { router });
|
||||||
// This will update routes at runtime without reloading the page
|
// This will update routes at runtime without reloading the page
|
||||||
|
|||||||
@@ -11,6 +11,14 @@ export function install({ app }: { app: import('vue').App<Element> }) {
|
|||||||
createI18n({
|
createI18n({
|
||||||
legacy: false, // you must set `false`, to use Composition API
|
legacy: false, // you must set `false`, to use Composition API
|
||||||
locale: navigator.language,
|
locale: navigator.language,
|
||||||
|
fallbackRoot: false,
|
||||||
|
// flatJson: true,
|
||||||
|
missing: (locale, key /* , instance, type */) => {
|
||||||
|
consola.warn(`缺少国际化内容: locale='${locale}', key='${key}'`);
|
||||||
|
return `[${key}]`;
|
||||||
|
},
|
||||||
|
missingWarn: !true,
|
||||||
|
fallbackWarn: !true,
|
||||||
messages,
|
messages,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -138,6 +138,9 @@ export default defineConfig(async (configEnv) => {
|
|||||||
|
|
||||||
if (packageName) {
|
if (packageName) {
|
||||||
// 根据包名分组
|
// 根据包名分组
|
||||||
|
if (packageName.includes('consola')) {
|
||||||
|
return 'consola';
|
||||||
|
}
|
||||||
if (packageName.includes('naive-ui')) {
|
if (packageName.includes('naive-ui')) {
|
||||||
return 'naive-ui';
|
return 'naive-ui';
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user