mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-05-26 18:59:44 +08:00
添加国际化支持:引入 vue-i18n,更新多个组件以支持语言切换和文本翻译
This commit is contained in:
@@ -3,18 +3,20 @@ import VerticalNavSectionTitle from '@/@layouts/components/VerticalNavSectionTit
|
||||
import VerticalNavLayout from '@layouts/components/VerticalNavLayout.vue'
|
||||
import VerticalNavLink from '@layouts/components/VerticalNavLink.vue'
|
||||
import Footer from '@/layouts/components/Footer.vue'
|
||||
import NavbarThemeSwitcher from '@/layouts/components/NavbarThemeSwitcher.vue'
|
||||
import NavbarActions from '@/layouts/components/NavbarActions.vue'
|
||||
import UserNofification from '@/layouts/components/UserNotification.vue'
|
||||
import SearchBar from '@/layouts/components/SearchBar.vue'
|
||||
import ShortcutBar from '@/layouts/components/ShortcutBar.vue'
|
||||
import UserProfile from '@/layouts/components/UserProfile.vue'
|
||||
import { useUserStore } from '@/stores'
|
||||
import { SystemNavMenus } from '@/router/menu'
|
||||
import { getNavMenus } from '@/router/i18n-menu'
|
||||
import { NavMenu } from '@/@layouts/types'
|
||||
import { useDisplay } from 'vuetify'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const display = useDisplay()
|
||||
const appMode = inject('pwaMode')
|
||||
const { t } = useI18n()
|
||||
|
||||
// 用户 Store
|
||||
const userStore = useUserStore()
|
||||
@@ -39,7 +41,9 @@ const systemMenus = ref<NavMenu[]>([])
|
||||
|
||||
// 根据分类获取菜单列表
|
||||
const getMenuList = (header: string) => {
|
||||
return SystemNavMenus.filter((item: NavMenu) => item.header === header && (superUser || !item.admin))
|
||||
// 使用国际化菜单
|
||||
const menus = getNavMenus()
|
||||
return menus.filter((item: NavMenu) => item.header === header && (superUser || !item.admin))
|
||||
}
|
||||
|
||||
// 返回上一页
|
||||
@@ -49,11 +53,11 @@ function goBack() {
|
||||
|
||||
onMounted(() => {
|
||||
// 获取菜单列表
|
||||
startMenus.value = getMenuList('开始')
|
||||
discoveryMenus.value = getMenuList('发现')
|
||||
subscribeMenus.value = getMenuList('订阅')
|
||||
organizeMenus.value = getMenuList('整理')
|
||||
systemMenus.value = getMenuList('系统')
|
||||
startMenus.value = getMenuList(t('menu.start'))
|
||||
discoveryMenus.value = getMenuList(t('menu.discovery'))
|
||||
subscribeMenus.value = getMenuList(t('menu.subscribe'))
|
||||
organizeMenus.value = getMenuList(t('menu.organize'))
|
||||
systemMenus.value = getMenuList(t('menu.system'))
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -74,10 +78,10 @@ onMounted(() => {
|
||||
<SearchBar />
|
||||
<!-- 👉 Spacer -->
|
||||
<VSpacer />
|
||||
<!-- 👉 Theme & Language -->
|
||||
<NavbarActions />
|
||||
<!-- 👉 Shortcuts -->
|
||||
<ShortcutBar v-if="superUser" />
|
||||
<!-- 👉 Theme -->
|
||||
<NavbarThemeSwitcher />
|
||||
<!-- 👉 Notification -->
|
||||
<UserNofification />
|
||||
<!-- 👉 UserProfile -->
|
||||
@@ -91,7 +95,7 @@ onMounted(() => {
|
||||
<VerticalNavSectionTitle
|
||||
v-if="discoveryMenus.length > 0"
|
||||
:item="{
|
||||
heading: '发现',
|
||||
heading: t('menu.discovery'),
|
||||
}"
|
||||
/>
|
||||
<VerticalNavLink v-for="item in discoveryMenus" :item="item" />
|
||||
@@ -99,7 +103,7 @@ onMounted(() => {
|
||||
<VerticalNavSectionTitle
|
||||
v-if="subscribeMenus.length > 0"
|
||||
:item="{
|
||||
heading: '订阅',
|
||||
heading: t('menu.subscribe'),
|
||||
}"
|
||||
/>
|
||||
<VerticalNavLink v-for="item in subscribeMenus" :item="item" />
|
||||
@@ -107,7 +111,7 @@ onMounted(() => {
|
||||
<VerticalNavSectionTitle
|
||||
v-if="organizeMenus.length > 0"
|
||||
:item="{
|
||||
heading: '整理',
|
||||
heading: t('menu.organize'),
|
||||
}"
|
||||
/>
|
||||
<VerticalNavLink v-for="item in organizeMenus" :item="item" />
|
||||
@@ -115,7 +119,7 @@ onMounted(() => {
|
||||
<VerticalNavSectionTitle
|
||||
v-if="systemMenus.length > 0"
|
||||
:item="{
|
||||
heading: '系统',
|
||||
heading: t('menu.system'),
|
||||
}"
|
||||
/>
|
||||
<VerticalNavLink v-for="item in systemMenus" :item="item" />
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
<script setup lang="ts">
|
||||
import { SystemNavMenus } from '@/router/menu'
|
||||
import { getNavMenus } from '@/router/i18n-menu'
|
||||
import { useDisplay } from 'vuetify'
|
||||
import { NavMenu } from '@/@layouts/types'
|
||||
|
||||
const display = useDisplay()
|
||||
const appMode = inject('pwaMode') && display.mdAndDown.value
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
// 获取导航菜单
|
||||
const navMenus = computed(() => getNavMenus())
|
||||
|
||||
// 根据当前路径获取匹配的菜单路径
|
||||
function getMenuPathFromRoute(path: string): string {
|
||||
const matchedMenu = SystemNavMenus.find(menu => menu.footer === true && path.startsWith(menu.to))
|
||||
return matchedMenu ? matchedMenu.to : '/apps'
|
||||
const matchedMenu = navMenus.value.find((menu: NavMenu) => menu.footer === true && path.startsWith(menu.to as string))
|
||||
return matchedMenu ? (matchedMenu.to as string) : '/apps'
|
||||
}
|
||||
|
||||
// 当前选中的菜单,初始值基于当前路由
|
||||
@@ -18,7 +22,7 @@ const currentMenu = ref<string>(getMenuPathFromRoute(route.path))
|
||||
|
||||
// 过滤出底部菜单项
|
||||
const footerMenus = computed(() => {
|
||||
return SystemNavMenus.filter(menu => menu.footer === true)
|
||||
return navMenus.value.filter((menu: NavMenu) => menu.footer === true)
|
||||
})
|
||||
|
||||
// 监听路由变化来更新currentMenu
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: '* * * * *',
|
||||
default: '',
|
||||
},
|
||||
items: {
|
||||
type: Array as PropType<{ title: string; icon: string }[]>,
|
||||
type: Array as PropType<{ title: string; icon: string; tab: string }[]>,
|
||||
default: () => [],
|
||||
},
|
||||
})
|
||||
@@ -76,8 +76,8 @@ onUnmounted(() => {
|
||||
v-for="(item, index) in items"
|
||||
:key="index"
|
||||
class="header-tab"
|
||||
:class="{ 'active': currentValue === item.title }"
|
||||
@click="currentValue = item.title"
|
||||
:class="{ 'active': currentValue === item.tab }"
|
||||
@click="currentValue = item.tab"
|
||||
>
|
||||
<VIcon v-if="item.icon" :icon="item.icon" size="small" class="header-tab-icon" />
|
||||
<span>{{ item.title }}</span>
|
||||
|
||||
45
src/layouts/components/NavbarActions.vue
Normal file
45
src/layouts/components/NavbarActions.vue
Normal file
@@ -0,0 +1,45 @@
|
||||
<script setup lang="ts">
|
||||
import type { ThemeSwitcherTheme } from '@layouts/types'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import LocaleSwitcher from '@/@core/components/LocaleSwitcher.vue'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const themes: ThemeSwitcherTheme[] = [
|
||||
{
|
||||
name: 'auto',
|
||||
title: t('theme.auto'),
|
||||
icon: 'mdi-laptop',
|
||||
},
|
||||
{
|
||||
name: 'light',
|
||||
title: t('theme.light'),
|
||||
icon: 'mdi-weather-sunny',
|
||||
},
|
||||
{
|
||||
name: 'dark',
|
||||
title: t('theme.dark'),
|
||||
icon: 'mdi-weather-night',
|
||||
},
|
||||
{
|
||||
name: 'purple',
|
||||
title: t('theme.purple'),
|
||||
icon: 'mdi-brightness-4',
|
||||
},
|
||||
{
|
||||
name: 'transparent',
|
||||
title: t('theme.transparent'),
|
||||
icon: 'mdi-gradient-horizontal',
|
||||
},
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="d-flex align-center">
|
||||
<!-- 语言切换 -->
|
||||
<LocaleSwitcher class="me-2" />
|
||||
|
||||
<!-- 主题切换 -->
|
||||
<ThemeSwitcher :themes="themes" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -7,11 +7,14 @@ import api from '@/api'
|
||||
import ProgressDialog from '@/components/dialog/ProgressDialog.vue'
|
||||
import UserAuthDialog from '@/components/dialog/UserAuthDialog.vue'
|
||||
import { useAuthStore, useUserStore } from '@/stores'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
// 认证 Store
|
||||
const authStore = useAuthStore()
|
||||
// 用户 Store
|
||||
const userStore = useUserStore()
|
||||
// 国际化
|
||||
const { t } = useI18n()
|
||||
|
||||
// 确认框
|
||||
const createConfirm = useConfirm()
|
||||
@@ -153,7 +156,7 @@ const userLevel = computed(() => userStore.level)
|
||||
<template #prepend>
|
||||
<VIcon icon="mdi-logout" />
|
||||
</template>
|
||||
退出登录
|
||||
{{ t('app.logout') }}
|
||||
</VBtn>
|
||||
</div>
|
||||
</VList>
|
||||
@@ -161,7 +164,7 @@ const userLevel = computed(() => userStore.level)
|
||||
<!-- !SECTION -->
|
||||
</VAvatar>
|
||||
<!-- 重启进度框 -->
|
||||
<ProgressDialog v-if="progressDialog" v-model="progressDialog" text="正在重启 ..." />
|
||||
<ProgressDialog v-if="progressDialog" v-model="progressDialog" :text="t('app.restarting')" />
|
||||
<!-- 用户认证对话框 -->
|
||||
<UserAuthDialog v-if="siteAuthDialog" v-model="siteAuthDialog" @done="siteAuthDone" @close="siteAuthDialog = false" />
|
||||
<!-- 重启确认对话框 -->
|
||||
@@ -173,14 +176,18 @@ const userLevel = computed(() => userStore.level)
|
||||
<VIcon size="x-large" icon="mdi-alert" />
|
||||
</VAvatar>
|
||||
<div class="ms-3">
|
||||
<p class="font-weight-bold text-xl text-high-emphasis">确认重启系统吗?</p>
|
||||
<p>重启后,您将被注销并需要重新登录。</p>
|
||||
<p class="font-weight-bold text-xl text-high-emphasis">{{ t('app.confirmRestart') }}</p>
|
||||
<p>{{ t('app.restartTip') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</VCardItem>
|
||||
<VCardActions class="mx-auto">
|
||||
<VBtn variant="tonal" color="secondary" class="px-5" @click="restartDialog = false">取消</VBtn>
|
||||
<VBtn variant="elevated" color="error" @click="restart" prepend-icon="mdi-restart" class="px-5"> 确定 </VBtn>
|
||||
<VBtn variant="tonal" color="secondary" class="px-5" @click="restartDialog = false">{{
|
||||
t('common.cancel')
|
||||
}}</VBtn>
|
||||
<VBtn variant="elevated" color="error" @click="restart" prepend-icon="mdi-restart" class="px-5">{{
|
||||
t('common.confirm')
|
||||
}}</VBtn>
|
||||
</VCardActions>
|
||||
<VDialogCloseBtn @click="restartDialog = false" />
|
||||
</VCard>
|
||||
|
||||
Reference in New Issue
Block a user