mirror of
				https://github.com/soybeanjs/soybean-admin.git
				synced 2025-11-04 15:53:43 +08:00 
			
		
		
		
	feat(projects): 增加i18n支持翻译菜单,tab,title
This commit is contained in:
		
							
								
								
									
										12
									
								
								src/App.vue
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								src/App.vue
									
									
									
									
									
								
							@@ -13,14 +13,26 @@
 | 
				
			|||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script setup lang="ts">
 | 
					<script setup lang="ts">
 | 
				
			||||||
 | 
					import { watch } from 'vue';
 | 
				
			||||||
 | 
					import { useRoute } from 'vue-router';
 | 
				
			||||||
import { dateZhCN, zhCN } from 'naive-ui';
 | 
					import { dateZhCN, zhCN } from 'naive-ui';
 | 
				
			||||||
 | 
					import { useI18n } from 'vue-i18n';
 | 
				
			||||||
import { subscribeStore, useThemeStore } from '@/store';
 | 
					import { subscribeStore, useThemeStore } from '@/store';
 | 
				
			||||||
import { useGlobalEvents } from '@/composables';
 | 
					import { useGlobalEvents } from '@/composables';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const theme = useThemeStore();
 | 
					const theme = useThemeStore();
 | 
				
			||||||
 | 
					const { locale, t } = useI18n();
 | 
				
			||||||
 | 
					const route = useRoute();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
subscribeStore();
 | 
					subscribeStore();
 | 
				
			||||||
useGlobalEvents();
 | 
					useGlobalEvents();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					watch(
 | 
				
			||||||
 | 
					  () => locale.value,
 | 
				
			||||||
 | 
					  () => {
 | 
				
			||||||
 | 
					    document.title = route.meta.i18nTitle ? t(route.meta.i18nTitle) : route.meta.title;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style scoped></style>
 | 
					<style scoped></style>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,7 +9,7 @@
 | 
				
			|||||||
              v-if="theme.header.crumb.showIcon"
 | 
					              v-if="theme.header.crumb.showIcon"
 | 
				
			||||||
              class="inline-block align-text-bottom mr-4px text-16px"
 | 
					              class="inline-block align-text-bottom mr-4px text-16px"
 | 
				
			||||||
            />
 | 
					            />
 | 
				
			||||||
            <span>{{ breadcrumb.label }}</span>
 | 
					            <span>{{ breadcrumb.i18nTitle ? t(breadcrumb.i18nTitle) : breadcrumb.label }}</span>
 | 
				
			||||||
          </span>
 | 
					          </span>
 | 
				
			||||||
        </n-dropdown>
 | 
					        </n-dropdown>
 | 
				
			||||||
        <template v-else>
 | 
					        <template v-else>
 | 
				
			||||||
@@ -19,7 +19,9 @@
 | 
				
			|||||||
            class="inline-block align-text-bottom mr-4px text-16px"
 | 
					            class="inline-block align-text-bottom mr-4px text-16px"
 | 
				
			||||||
            :class="{ 'text-#BBBBBB': theme.header.inverted }"
 | 
					            :class="{ 'text-#BBBBBB': theme.header.inverted }"
 | 
				
			||||||
          />
 | 
					          />
 | 
				
			||||||
          <span :class="{ 'text-#BBBBBB': theme.header.inverted }">{{ breadcrumb.label }}</span>
 | 
					          <span :class="{ 'text-#BBBBBB': theme.header.inverted }">{{
 | 
				
			||||||
 | 
					            breadcrumb.i18nTitle ? t(breadcrumb.i18nTitle) : breadcrumb.label
 | 
				
			||||||
 | 
					          }}</span>
 | 
				
			||||||
        </template>
 | 
					        </template>
 | 
				
			||||||
      </n-breadcrumb-item>
 | 
					      </n-breadcrumb-item>
 | 
				
			||||||
    </template>
 | 
					    </template>
 | 
				
			||||||
@@ -33,6 +35,7 @@ import { routePath } from '@/router';
 | 
				
			|||||||
import { useRouteStore, useThemeStore } from '@/store';
 | 
					import { useRouteStore, useThemeStore } from '@/store';
 | 
				
			||||||
import { useRouterPush } from '@/composables';
 | 
					import { useRouterPush } from '@/composables';
 | 
				
			||||||
import { getBreadcrumbByRouteKey } from '@/utils';
 | 
					import { getBreadcrumbByRouteKey } from '@/utils';
 | 
				
			||||||
 | 
					import { t } from '@/locales';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
defineOptions({ name: 'GlobalBreadcrumb' });
 | 
					defineOptions({ name: 'GlobalBreadcrumb' });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,6 +20,7 @@ import { useRoute } from 'vue-router';
 | 
				
			|||||||
import type { MenuOption } from 'naive-ui';
 | 
					import type { MenuOption } from 'naive-ui';
 | 
				
			||||||
import { useRouteStore, useThemeStore } from '@/store';
 | 
					import { useRouteStore, useThemeStore } from '@/store';
 | 
				
			||||||
import { useRouterPush } from '@/composables';
 | 
					import { useRouterPush } from '@/composables';
 | 
				
			||||||
 | 
					import { translateMenuLabel } from '@/utils';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
defineOptions({ name: 'HeaderMenu' });
 | 
					defineOptions({ name: 'HeaderMenu' });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -28,7 +29,7 @@ const routeStore = useRouteStore();
 | 
				
			|||||||
const theme = useThemeStore();
 | 
					const theme = useThemeStore();
 | 
				
			||||||
const { routerPush } = useRouterPush();
 | 
					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 activeKey = computed(() => (route.meta?.activeMenu ? route.meta.activeMenu : route.name) as string);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function handleUpdateMenu(_key: string, item: MenuOption) {
 | 
					function handleUpdateMenu(_key: string, item: MenuOption) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,6 +7,7 @@ import ThemeMode from './theme-mode.vue';
 | 
				
			|||||||
import UserAvatar from './user-avatar.vue';
 | 
					import UserAvatar from './user-avatar.vue';
 | 
				
			||||||
import SystemMessage from './system-message.vue';
 | 
					import SystemMessage from './system-message.vue';
 | 
				
			||||||
import SettingButton from './setting-button.vue';
 | 
					import SettingButton from './setting-button.vue';
 | 
				
			||||||
 | 
					import ToggleLang from './toggle-lang.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export {
 | 
					export {
 | 
				
			||||||
  MenuCollapse,
 | 
					  MenuCollapse,
 | 
				
			||||||
@@ -17,5 +18,6 @@ export {
 | 
				
			|||||||
  ThemeMode,
 | 
					  ThemeMode,
 | 
				
			||||||
  UserAvatar,
 | 
					  UserAvatar,
 | 
				
			||||||
  SystemMessage,
 | 
					  SystemMessage,
 | 
				
			||||||
  SettingButton
 | 
					  SettingButton,
 | 
				
			||||||
 | 
					  ToggleLang
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										33
									
								
								src/layouts/common/global-header/components/toggle-lang.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/layouts/common/global-header/components/toggle-lang.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,33 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					  <hover-container class="w-40px h-full">
 | 
				
			||||||
 | 
					    <n-dropdown :options="options" trigger="hover" :value="language" @select="handleSelect">
 | 
				
			||||||
 | 
					      <icon-cil:language class="text-18px outline-transparent" />
 | 
				
			||||||
 | 
					    </n-dropdown>
 | 
				
			||||||
 | 
					  </hover-container>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts" setup>
 | 
				
			||||||
 | 
					import { ref } from 'vue';
 | 
				
			||||||
 | 
					import { useI18n } from 'vue-i18n';
 | 
				
			||||||
 | 
					import { localStg } from '@/utils';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const { locale } = useI18n();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const language = ref<I18nType.langType>(localStg.get('lang') || 'zh-CN');
 | 
				
			||||||
 | 
					const options = [
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    label: '中文',
 | 
				
			||||||
 | 
					    key: 'zh-CN'
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    label: 'English',
 | 
				
			||||||
 | 
					    key: 'en'
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
 | 
					const handleSelect = (key: string) => {
 | 
				
			||||||
 | 
					  language.value = key as I18nType.langType;
 | 
				
			||||||
 | 
					  locale.value = key;
 | 
				
			||||||
 | 
					  localStg.set('lang', key as I18nType.langType);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					<style scoped></style>
 | 
				
			||||||
@@ -11,6 +11,7 @@
 | 
				
			|||||||
      <github-site />
 | 
					      <github-site />
 | 
				
			||||||
      <full-screen />
 | 
					      <full-screen />
 | 
				
			||||||
      <theme-mode />
 | 
					      <theme-mode />
 | 
				
			||||||
 | 
					      <toggle-lang />
 | 
				
			||||||
      <system-message />
 | 
					      <system-message />
 | 
				
			||||||
      <setting-button v-if="showButton" />
 | 
					      <setting-button v-if="showButton" />
 | 
				
			||||||
      <user-avatar />
 | 
					      <user-avatar />
 | 
				
			||||||
@@ -32,7 +33,8 @@ import {
 | 
				
			|||||||
  SettingButton,
 | 
					  SettingButton,
 | 
				
			||||||
  SystemMessage,
 | 
					  SystemMessage,
 | 
				
			||||||
  ThemeMode,
 | 
					  ThemeMode,
 | 
				
			||||||
  UserAvatar
 | 
					  UserAvatar,
 | 
				
			||||||
 | 
					  ToggleLang
 | 
				
			||||||
} from './components';
 | 
					} from './components';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
defineOptions({ name: 'GlobalHeader' });
 | 
					defineOptions({ name: 'GlobalHeader' });
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,11 +1,7 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
  <router-link :to="routeHomePath" class="flex-center w-full nowrap-hidden">
 | 
					  <router-link :to="routeHomePath" class="flex-center w-full nowrap-hidden">
 | 
				
			||||||
    <system-logo class="text-32px text-primary" />
 | 
					    <system-logo class="text-32px text-primary" />
 | 
				
			||||||
    <h2
 | 
					    <h2 v-show="showTitle" class="pl-8px text-16px font-bold text-primary transition duration-300 ease-in-out">
 | 
				
			||||||
      v-show="showTitle"
 | 
					 | 
				
			||||||
      class="pl-8px text-16px font-bold text-primary transition duration-300 ease-in-out"
 | 
					 | 
				
			||||||
      @click="toggleLocal"
 | 
					 | 
				
			||||||
    >
 | 
					 | 
				
			||||||
      {{ t('message.system.title') }}
 | 
					      {{ t('message.system.title') }}
 | 
				
			||||||
    </h2>
 | 
					    </h2>
 | 
				
			||||||
  </router-link>
 | 
					  </router-link>
 | 
				
			||||||
@@ -13,7 +9,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<script setup lang="ts">
 | 
					<script setup lang="ts">
 | 
				
			||||||
import { routePath } from '@/router';
 | 
					import { routePath } from '@/router';
 | 
				
			||||||
import { t, setLocale } from '@/locales';
 | 
					import { t } from '@/locales';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
defineOptions({ name: 'GlobalLogo' });
 | 
					defineOptions({ name: 'GlobalLogo' });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -25,12 +21,6 @@ interface Props {
 | 
				
			|||||||
defineProps<Props>();
 | 
					defineProps<Props>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const routeHomePath = routePath('root');
 | 
					const routeHomePath = routePath('root');
 | 
				
			||||||
 | 
					 | 
				
			||||||
let flag = true;
 | 
					 | 
				
			||||||
function toggleLocal() {
 | 
					 | 
				
			||||||
  flag = !flag;
 | 
					 | 
				
			||||||
  setLocale(flag ? 'en' : 'zh-CN');
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style scoped></style>
 | 
					<style scoped></style>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -26,7 +26,9 @@ import { useRoute } from 'vue-router';
 | 
				
			|||||||
import { useAppStore, useRouteStore, useThemeStore } from '@/store';
 | 
					import { useAppStore, useRouteStore, useThemeStore } from '@/store';
 | 
				
			||||||
import { useRouterPush } from '@/composables';
 | 
					import { useRouterPush } from '@/composables';
 | 
				
			||||||
import { useBoolean } from '@/hooks';
 | 
					import { useBoolean } from '@/hooks';
 | 
				
			||||||
 | 
					import { translateMenuLabel } from '@/utils';
 | 
				
			||||||
import { GlobalLogo } from '@/layouts/common';
 | 
					import { GlobalLogo } from '@/layouts/common';
 | 
				
			||||||
 | 
					import { t } from '@/locales';
 | 
				
			||||||
import { MixMenuCollapse, MixMenuDetail, MixMenuDrawer } from './components';
 | 
					import { MixMenuCollapse, MixMenuDetail, MixMenuDrawer } from './components';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
defineOptions({ name: 'VerticalMixSider' });
 | 
					defineOptions({ name: 'VerticalMixSider' });
 | 
				
			||||||
@@ -45,13 +47,13 @@ function setActiveParentRouteName(routeName: string) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const firstDegreeMenus = computed(() =>
 | 
					const firstDegreeMenus = computed(() =>
 | 
				
			||||||
  routeStore.menus.map(item => {
 | 
					  routeStore.menus.map(item => {
 | 
				
			||||||
    const { routeName, label } = item;
 | 
					    const { routeName, label, i18nTitle } = item;
 | 
				
			||||||
    const icon = item?.icon;
 | 
					    const icon = item?.icon;
 | 
				
			||||||
    const hasChildren = Boolean(item.children && item.children.length);
 | 
					    const hasChildren = Boolean(item.children && item.children.length);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
      routeName,
 | 
					      routeName,
 | 
				
			||||||
      label,
 | 
					      label: i18nTitle ? t(i18nTitle) : label,
 | 
				
			||||||
      icon,
 | 
					      icon,
 | 
				
			||||||
      hasChildren
 | 
					      hasChildren
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
@@ -88,7 +90,7 @@ const activeChildMenus = computed(() => {
 | 
				
			|||||||
  routeStore.menus.some(item => {
 | 
					  routeStore.menus.some(item => {
 | 
				
			||||||
    const flag = item.routeName === activeParentRouteName.value && Boolean(item.children?.length);
 | 
					    const flag = item.routeName === activeParentRouteName.value && Boolean(item.children?.length);
 | 
				
			||||||
    if (flag) {
 | 
					    if (flag) {
 | 
				
			||||||
      menus.push(...(item.children || []));
 | 
					      menus.push(...translateMenuLabel((item.children || []) as App.GlobalMenuOption[]));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    return flag;
 | 
					    return flag;
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -21,7 +21,7 @@ import { useRoute } from 'vue-router';
 | 
				
			|||||||
import type { MenuOption } from 'naive-ui';
 | 
					import type { MenuOption } from 'naive-ui';
 | 
				
			||||||
import { useAppStore, useRouteStore, useThemeStore } from '@/store';
 | 
					import { useAppStore, useRouteStore, useThemeStore } from '@/store';
 | 
				
			||||||
import { useRouterPush } from '@/composables';
 | 
					import { useRouterPush } from '@/composables';
 | 
				
			||||||
import { getActiveKeyPathsOfMenus } from '@/utils';
 | 
					import { getActiveKeyPathsOfMenus, translateMenuLabel } from '@/utils';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
defineOptions({ name: 'VerticalMenu' });
 | 
					defineOptions({ name: 'VerticalMenu' });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -31,7 +31,7 @@ const theme = useThemeStore();
 | 
				
			|||||||
const routeStore = useRouteStore();
 | 
					const routeStore = useRouteStore();
 | 
				
			||||||
const { routerPush } = useRouterPush();
 | 
					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 activeKey = computed(() => (route.meta?.activeMenu ? route.meta.activeMenu : route.name) as string);
 | 
				
			||||||
const expandedKeys = ref<string[]>([]);
 | 
					const expandedKeys = ref<string[]>([]);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -19,7 +19,7 @@
 | 
				
			|||||||
          class="inline-block align-text-bottom text-16px"
 | 
					          class="inline-block align-text-bottom text-16px"
 | 
				
			||||||
        />
 | 
					        />
 | 
				
			||||||
      </template>
 | 
					      </template>
 | 
				
			||||||
      {{ item.meta.title }}
 | 
					      {{ item.meta.i18nTitle ? t(item.meta.i18nTitle) : item.meta.title }}
 | 
				
			||||||
    </AdminTab>
 | 
					    </AdminTab>
 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
  <context-menu
 | 
					  <context-menu
 | 
				
			||||||
@@ -36,6 +36,7 @@
 | 
				
			|||||||
import { computed, nextTick, reactive, ref, watch } from 'vue';
 | 
					import { computed, nextTick, reactive, ref, watch } from 'vue';
 | 
				
			||||||
import { AdminTab } from '@soybeanjs/vue-materials';
 | 
					import { AdminTab } from '@soybeanjs/vue-materials';
 | 
				
			||||||
import { useTabStore, useThemeStore } from '@/store';
 | 
					import { useTabStore, useThemeStore } from '@/store';
 | 
				
			||||||
 | 
					import { t } from '@/locales';
 | 
				
			||||||
import { ContextMenu } from './components';
 | 
					import { ContextMenu } from './components';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
defineOptions({ name: 'TabDetail' });
 | 
					defineOptions({ name: 'TabDetail' });
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,12 +1,14 @@
 | 
				
			|||||||
import type { App } from 'vue';
 | 
					import type { App } from 'vue';
 | 
				
			||||||
import { createI18n } from 'vue-i18n';
 | 
					import { createI18n } from 'vue-i18n';
 | 
				
			||||||
 | 
					import { localStg } from '@/utils';
 | 
				
			||||||
import messages from './lang';
 | 
					import messages from './lang';
 | 
				
			||||||
import type { LocaleKey } from './lang';
 | 
					import type { LocaleKey } from './lang';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const i18n = createI18n({
 | 
					const i18n = createI18n({
 | 
				
			||||||
  locale: 'zh-CN',
 | 
					  locale: localStg.get('lang') || 'zh-CN',
 | 
				
			||||||
  fallbackLocale: 'en',
 | 
					  fallbackLocale: 'en',
 | 
				
			||||||
  messages
 | 
					  messages,
 | 
				
			||||||
 | 
					  legacy: false
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function setupI18n(app: App) {
 | 
					export function setupI18n(app: App) {
 | 
				
			||||||
@@ -18,5 +20,5 @@ export function t(key: string) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function setLocale(locale: LocaleKey) {
 | 
					export function setLocale(locale: LocaleKey) {
 | 
				
			||||||
  i18n.global.locale = locale;
 | 
					  i18n.global.locale.value = locale;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,6 @@
 | 
				
			|||||||
import type { Router } from 'vue-router';
 | 
					import type { Router } from 'vue-router';
 | 
				
			||||||
import { useTitle } from '@vueuse/core';
 | 
					import { useTitle } from '@vueuse/core';
 | 
				
			||||||
 | 
					import { t } from '@/locales';
 | 
				
			||||||
import { createPermissionGuard } from './permission';
 | 
					import { createPermissionGuard } from './permission';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
@@ -15,7 +16,7 @@ export function createRouterGuard(router: Router) {
 | 
				
			|||||||
  });
 | 
					  });
 | 
				
			||||||
  router.afterEach(to => {
 | 
					  router.afterEach(to => {
 | 
				
			||||||
    // 设置document title
 | 
					    // 设置document title
 | 
				
			||||||
    useTitle(to.meta.title);
 | 
					    useTitle(to.meta.i18nTitle ? t(to.meta.i18nTitle) : to.meta.title);
 | 
				
			||||||
    // 结束 loadingBar
 | 
					    // 结束 loadingBar
 | 
				
			||||||
    window.$loadingBar?.finish();
 | 
					    window.$loadingBar?.finish();
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,7 +10,8 @@ const dashboard: AuthRoute.Route = {
 | 
				
			|||||||
      meta: {
 | 
					      meta: {
 | 
				
			||||||
        title: '分析页',
 | 
					        title: '分析页',
 | 
				
			||||||
        requiresAuth: true,
 | 
					        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: {
 | 
					      meta: {
 | 
				
			||||||
        title: '工作台',
 | 
					        title: '工作台',
 | 
				
			||||||
        requiresAuth: true,
 | 
					        requiresAuth: true,
 | 
				
			||||||
        icon: 'icon-park-outline:workbench'
 | 
					        icon: 'icon-park-outline:workbench',
 | 
				
			||||||
 | 
					        i18nTitle: 'message.routes.dashboard.workbench'
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  ],
 | 
					  ],
 | 
				
			||||||
  meta: {
 | 
					  meta: {
 | 
				
			||||||
    title: '仪表盘',
 | 
					    title: '仪表盘',
 | 
				
			||||||
    icon: 'mdi:monitor-dashboard',
 | 
					    icon: 'mdi:monitor-dashboard',
 | 
				
			||||||
    order: 1
 | 
					    order: 1,
 | 
				
			||||||
 | 
					    i18nTitle: 'message.routes.dashboard.dashboard'
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,7 +7,6 @@ import { localStg } from '@/utils';
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
export function getTabRouteByVueRoute(route: RouteRecordNormalized | RouteLocationNormalizedLoaded) {
 | 
					export function getTabRouteByVueRoute(route: RouteRecordNormalized | RouteLocationNormalizedLoaded) {
 | 
				
			||||||
  const fullPath = hasFullPath(route) ? route.fullPath : route.path;
 | 
					  const fullPath = hasFullPath(route) ? route.fullPath : route.path;
 | 
				
			||||||
 | 
					 | 
				
			||||||
  const tabRoute: App.GlobalTabRoute = {
 | 
					  const tabRoute: App.GlobalTabRoute = {
 | 
				
			||||||
    name: route.name,
 | 
					    name: route.name,
 | 
				
			||||||
    fullPath,
 | 
					    fullPath,
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										2
									
								
								src/typings/route.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								src/typings/route.d.ts
									
									
									
									
										vendored
									
									
								
							@@ -31,6 +31,8 @@ declare namespace AuthRoute {
 | 
				
			|||||||
  interface RouteMeta<K extends AuthRoute.RoutePath> {
 | 
					  interface RouteMeta<K extends AuthRoute.RoutePath> {
 | 
				
			||||||
    /** 路由标题(可用来作document.title或者菜单的名称) */
 | 
					    /** 路由标题(可用来作document.title或者菜单的名称) */
 | 
				
			||||||
    title: string;
 | 
					    title: string;
 | 
				
			||||||
 | 
					    /** 用来支持多国语言 如果i18nTitle和title同时存在优先使用i18nTitle */
 | 
				
			||||||
 | 
					    i18nTitle?: string;
 | 
				
			||||||
    /** 路由的动态路径(需要动态路径的页面需要将path添加进范型参数) */
 | 
					    /** 路由的动态路径(需要动态路径的页面需要将path添加进范型参数) */
 | 
				
			||||||
    dynamicPath?: AuthRouteUtils.GetDynamicPath<K>;
 | 
					    dynamicPath?: AuthRouteUtils.GetDynamicPath<K>;
 | 
				
			||||||
    /** 作为单级路由的父级路由布局组件 */
 | 
					    /** 作为单级路由的父级路由布局组件 */
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										2
									
								
								src/typings/storage.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								src/typings/storage.d.ts
									
									
									
									
										vendored
									
									
								
							@@ -18,5 +18,7 @@ declare namespace StorageInterface {
 | 
				
			|||||||
    themeSettings: Theme.Setting;
 | 
					    themeSettings: Theme.Setting;
 | 
				
			||||||
    /** 多页签路由信息 */
 | 
					    /** 多页签路由信息 */
 | 
				
			||||||
    multiTabRoutes: App.GlobalTabRoute[];
 | 
					    multiTabRoutes: App.GlobalTabRoute[];
 | 
				
			||||||
 | 
					    /** 本地语言缓存 */
 | 
				
			||||||
 | 
					    lang: I18nType.langType;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										3
									
								
								src/typings/system.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								src/typings/system.d.ts
									
									
									
									
										vendored
									
									
								
							@@ -242,6 +242,7 @@ declare namespace App {
 | 
				
			|||||||
    routePath: string;
 | 
					    routePath: string;
 | 
				
			||||||
    icon?: () => import('vue').VNodeChild;
 | 
					    icon?: () => import('vue').VNodeChild;
 | 
				
			||||||
    children?: GlobalMenuOption[];
 | 
					    children?: GlobalMenuOption[];
 | 
				
			||||||
 | 
					    i18nTitle?: string;
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /** 面包屑 */
 | 
					  /** 面包屑 */
 | 
				
			||||||
@@ -252,6 +253,7 @@ declare namespace App {
 | 
				
			|||||||
    routeName: string;
 | 
					    routeName: string;
 | 
				
			||||||
    hasChildren: boolean;
 | 
					    hasChildren: boolean;
 | 
				
			||||||
    icon?: import('vue').Component;
 | 
					    icon?: import('vue').Component;
 | 
				
			||||||
 | 
					    i18nTitle?: string;
 | 
				
			||||||
    options?: import('naive-ui/es/dropdown/src/interface').DropdownMixedOption[];
 | 
					    options?: import('naive-ui/es/dropdown/src/interface').DropdownMixedOption[];
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -300,6 +302,7 @@ declare namespace App {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
declare namespace I18nType {
 | 
					declare namespace I18nType {
 | 
				
			||||||
 | 
					  type langType = 'en' | 'zh-CN';
 | 
				
			||||||
  interface Schema {
 | 
					  interface Schema {
 | 
				
			||||||
    system: {
 | 
					    system: {
 | 
				
			||||||
      title: string;
 | 
					      title: string;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -59,7 +59,8 @@ function transformBreadcrumbMenuToBreadcrumb(menu: App.GlobalMenuOption, rootPat
 | 
				
			|||||||
    label: menu.label as string,
 | 
					    label: menu.label as string,
 | 
				
			||||||
    routeName: menu.routeName,
 | 
					    routeName: menu.routeName,
 | 
				
			||||||
    disabled: menu.routePath === rootPath,
 | 
					    disabled: menu.routePath === rootPath,
 | 
				
			||||||
    hasChildren
 | 
					    hasChildren,
 | 
				
			||||||
 | 
					    i18nTitle: menu.i18nTitle
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
  if (menu.icon) {
 | 
					  if (menu.icon) {
 | 
				
			||||||
    breadcrumb.icon = menu.icon;
 | 
					    breadcrumb.icon = menu.icon;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,5 @@
 | 
				
			|||||||
import { useIconRender } from '@/composables';
 | 
					import { useIconRender } from '@/composables';
 | 
				
			||||||
 | 
					import { t } from '@/locales';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * 将权限路由转换成菜单
 | 
					 * 将权限路由转换成菜单
 | 
				
			||||||
@@ -18,7 +19,8 @@ export function transformAuthRouteToMenu(routes: AuthRoute.Route[]): App.GlobalM
 | 
				
			|||||||
        key: routeName,
 | 
					        key: routeName,
 | 
				
			||||||
        label: meta.title,
 | 
					        label: meta.title,
 | 
				
			||||||
        routeName,
 | 
					        routeName,
 | 
				
			||||||
        routePath: path
 | 
					        routePath: path,
 | 
				
			||||||
 | 
					        i18nTitle: meta.i18nTitle
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      icon: meta.icon,
 | 
					      icon: meta.icon,
 | 
				
			||||||
      localIcon: meta.localIcon,
 | 
					      localIcon: meta.localIcon,
 | 
				
			||||||
@@ -33,6 +35,28 @@ export function transformAuthRouteToMenu(routes: AuthRoute.Route[]): App.GlobalM
 | 
				
			|||||||
  return globalMenu;
 | 
					  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
 | 
					 * 获取当前路由所在菜单数据的paths
 | 
				
			||||||
 * @param activeKey - 当前路由的key
 | 
					 * @param activeKey - 当前路由的key
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user