mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-05-30 21:00:43 +08:00
删除语言和主题切换组件,整合相关功能至用户个人资料组件中
This commit is contained in:
@@ -1,64 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { SUPPORTED_LOCALES, SupportedLocale } from '@/types/i18n'
|
||||
import { setI18nLanguage, getCurrentLocale } from '@/plugins/i18n'
|
||||
|
||||
// 当前语言
|
||||
const currentLocale = ref<SupportedLocale>(getCurrentLocale())
|
||||
|
||||
// 支持的语言列表
|
||||
const locales = computed(() => {
|
||||
return Object.entries(SUPPORTED_LOCALES).map(([key, locale]) => ({
|
||||
value: key as SupportedLocale,
|
||||
title: locale.title,
|
||||
flag: locale.flag,
|
||||
icon: `flag-${key.split('-')[0]}`,
|
||||
}))
|
||||
})
|
||||
|
||||
// 切换语言
|
||||
async function changeLocale(locale: SupportedLocale) {
|
||||
try {
|
||||
await setI18nLanguage(locale)
|
||||
currentLocale.value = locale
|
||||
// 刷新页面
|
||||
window.location.reload()
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 获取当前语言图标
|
||||
const getCurrentIcon = computed(() => {
|
||||
const locale = locales.value.find(l => l.value === currentLocale.value)
|
||||
return locale?.flag || '🌐'
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VMenu class="locale-menu" scrim>
|
||||
<template v-slot:activator="{ props }">
|
||||
<IconBtn v-bind="props">
|
||||
<span class="text-xl">{{ getCurrentIcon }}</span>
|
||||
</IconBtn>
|
||||
</template>
|
||||
<VList>
|
||||
<div class="px-2">
|
||||
<VListItem
|
||||
v-for="locale in locales"
|
||||
:key="locale.value"
|
||||
@click="changeLocale(locale.value)"
|
||||
:active="currentLocale === locale.value"
|
||||
class="mb-1"
|
||||
>
|
||||
<template #prepend>
|
||||
<span class="text-xl me-2">{{ locale.flag }}</span>
|
||||
</template>
|
||||
<VListItemTitle>{{ locale.title }}</VListItemTitle>
|
||||
<template #append v-if="currentLocale === locale.value">
|
||||
<VIcon icon="mdi-check" color="primary" size="small" />
|
||||
</template>
|
||||
</VListItem>
|
||||
</div>
|
||||
</VList>
|
||||
</VMenu>
|
||||
</template>
|
||||
@@ -1,184 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { useDisplay, useTheme } from 'vuetify'
|
||||
import type { ThemeSwitcherTheme } from '@layouts/types'
|
||||
import api from '@/api'
|
||||
import { checkPrefersColorSchemeIsDark } from '@/@core/utils'
|
||||
import { useToast } from 'vue-toast-notification'
|
||||
import { saveLocalTheme } from '../utils/theme'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
// 显示器宽度
|
||||
const display = useDisplay()
|
||||
|
||||
const props = defineProps<{
|
||||
themes: ThemeSwitcherTheme[]
|
||||
}>()
|
||||
|
||||
const { name: themeName, global: globalTheme } = useTheme()
|
||||
|
||||
const savedTheme = ref(localStorage.getItem('theme') ?? themeName)
|
||||
|
||||
const currentThemeName = ref(savedTheme.value)
|
||||
const getNextThemeName = () => {
|
||||
const currentIndex = props.themes.findIndex(t => t.name === currentThemeName.value)
|
||||
const nextIndex = (currentIndex + 1) % props.themes.length
|
||||
return props.themes[nextIndex].name
|
||||
}
|
||||
|
||||
const $toast = useToast()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
// 自定义CSS弹窗
|
||||
const cssDialog = ref(false)
|
||||
|
||||
// 自定义 CSS
|
||||
const customCSS = ref('')
|
||||
|
||||
// 编辑器主题
|
||||
const editorTheme = computed(() => (currentThemeName.value === 'light' ? 'github' : 'monokai'))
|
||||
|
||||
// 更新主题
|
||||
function updateTheme() {
|
||||
const autoTheme = checkPrefersColorSchemeIsDark() ? 'dark' : 'light'
|
||||
const theme = currentThemeName.value === 'auto' ? autoTheme : currentThemeName.value
|
||||
globalTheme.name.value = theme
|
||||
// 保存原始主题设置,而不是计算后的值
|
||||
savedTheme.value = currentThemeName.value
|
||||
// 保存主题到本地
|
||||
saveLocalTheme(currentThemeName.value, globalTheme)
|
||||
// 刷新页面
|
||||
location.reload()
|
||||
}
|
||||
|
||||
// 切换主题
|
||||
function changeTheme(theme: string) {
|
||||
let nextTheme = theme
|
||||
if (!theme) nextTheme = getNextThemeName()
|
||||
currentThemeName.value = nextTheme
|
||||
// 保存主题到服务端
|
||||
try {
|
||||
api.post('/user/config/Layout', {
|
||||
theme: nextTheme,
|
||||
})
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
// 监听系统主题变化
|
||||
try {
|
||||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', updateTheme)
|
||||
} catch (e) {
|
||||
console.error('当前设备不支持监听系统主题变化')
|
||||
}
|
||||
|
||||
// 查询当前主题的图标
|
||||
const getThemeIcon = computed(() => {
|
||||
const theme = props.themes.find(t => t.name === currentThemeName.value)
|
||||
return theme?.icon ?? 'mdi-circle'
|
||||
})
|
||||
|
||||
// 监听设置主题变化
|
||||
watch(
|
||||
() => currentThemeName.value,
|
||||
() => updateTheme(),
|
||||
)
|
||||
|
||||
// 获取自定义 CSS
|
||||
async function getCustomCSS() {
|
||||
try {
|
||||
const result: { [key: string]: any } = await api.get('system/setting/UserCustomCSS')
|
||||
if (result && result.success && result.data?.value) {
|
||||
customCSS.value = result.data?.value ?? ''
|
||||
if (customCSS.value) {
|
||||
const style = document.createElement('style')
|
||||
style.innerHTML = result.data?.value ?? ''
|
||||
document.head.appendChild(style)
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 保存自定义 CSS
|
||||
async function saveCustomCSS() {
|
||||
cssDialog.value = false
|
||||
try {
|
||||
const result: { [key: string]: any } = await api.post('system/setting/UserCustomCSS', customCSS.value, {
|
||||
headers: {
|
||||
'Content-Type': 'text/plain',
|
||||
},
|
||||
})
|
||||
|
||||
if (result.success) $toast.success('自定义CSS保存成功,请刷新页面生效!')
|
||||
} catch (e) {
|
||||
console.error('保存自定义 CSS 到服务端失败')
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getCustomCSS()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VMenu v-if="props.themes" class="theme-menu" scrim>
|
||||
<template v-slot:activator="{ props }">
|
||||
<IconBtn v-bind="props">
|
||||
<VIcon :icon="getThemeIcon" />
|
||||
</IconBtn>
|
||||
</template>
|
||||
<VList>
|
||||
<div class="px-2">
|
||||
<VListItem
|
||||
v-for="theme in props.themes"
|
||||
:key="theme.name"
|
||||
@click="changeTheme(theme.name)"
|
||||
:active="currentThemeName === theme.name"
|
||||
class="mb-1"
|
||||
>
|
||||
<template #prepend>
|
||||
<VIcon :icon="theme.icon" />
|
||||
</template>
|
||||
<VListItemTitle>{{ theme.title }}</VListItemTitle>
|
||||
<template #append v-if="currentThemeName === theme.name">
|
||||
<VIcon icon="mdi-check" color="primary" size="small" />
|
||||
</template>
|
||||
</VListItem>
|
||||
<VDivider class="my-2" />
|
||||
<VListItem @click="cssDialog = true">
|
||||
<template #prepend>
|
||||
<VIcon icon="mdi-palette" />
|
||||
</template>
|
||||
<VListItemTitle>{{ t('theme.custom') }}</VListItemTitle>
|
||||
</VListItem>
|
||||
</div>
|
||||
</VList>
|
||||
</VMenu>
|
||||
<!-- 自定义 CSS -- -->
|
||||
<VDialog v-if="cssDialog" v-model="cssDialog" max-width="50rem" scrollable :fullscreen="!display.mdAndUp.value">
|
||||
<VCard>
|
||||
<VCardItem>
|
||||
<VCardTitle>
|
||||
<VIcon icon="mdi-palette" class="me-2" />
|
||||
自定义主题风格
|
||||
</VCardTitle>
|
||||
<VDialogCloseBtn @click="cssDialog = false" />
|
||||
</VCardItem>
|
||||
<VDivider />
|
||||
<VAceEditor v-model:value="customCSS" lang="css" :theme="editorTheme" class="w-full min-h-[30rem]" />
|
||||
<VDivider />
|
||||
<VCardText class="text-center">
|
||||
<VBtn @click="saveCustomCSS" class="w-1/2">
|
||||
<template #prepend>
|
||||
<VIcon icon="mdi-content-save" />
|
||||
</template>
|
||||
保存
|
||||
</VBtn>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VDialog>
|
||||
</template>
|
||||
@@ -3,7 +3,6 @@ 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 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'
|
||||
@@ -82,8 +81,6 @@ onMounted(() => {
|
||||
<ShortcutBar v-if="superUser" />
|
||||
<!-- 👉 Notification -->
|
||||
<UserNofification />
|
||||
<!-- 👉 Theme & Language -->
|
||||
<NavbarActions />
|
||||
<!-- 👉 UserProfile -->
|
||||
<UserProfile />
|
||||
</div>
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
<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">
|
||||
<!-- 主题切换 -->
|
||||
<ThemeSwitcher :themes="themes" />
|
||||
<!-- 语言切换 -->
|
||||
<LocaleSwitcher class="me-2" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -8,6 +8,12 @@ import ProgressDialog from '@/components/dialog/ProgressDialog.vue'
|
||||
import UserAuthDialog from '@/components/dialog/UserAuthDialog.vue'
|
||||
import { useAuthStore, useUserStore } from '@/stores'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useDisplay, useTheme } from 'vuetify'
|
||||
import { SUPPORTED_LOCALES, SupportedLocale } from '@/types/i18n'
|
||||
import { checkPrefersColorSchemeIsDark } from '@/@core/utils'
|
||||
import { getCurrentLocale, setI18nLanguage } from '@/plugins/i18n'
|
||||
import { saveLocalTheme } from '@/@core/utils/theme'
|
||||
import type { ThemeSwitcherTheme } from '@layouts/types'
|
||||
|
||||
// 认证 Store
|
||||
const authStore = useAuthStore()
|
||||
@@ -15,6 +21,8 @@ const authStore = useAuthStore()
|
||||
const userStore = useUserStore()
|
||||
// 国际化
|
||||
const { t } = useI18n()
|
||||
// 显示器
|
||||
const display = useDisplay()
|
||||
|
||||
// 确认框
|
||||
const createConfirm = useConfirm()
|
||||
@@ -31,6 +39,18 @@ const siteAuthDialog = ref(false)
|
||||
// 重启确认对话框
|
||||
const restartDialog = ref(false)
|
||||
|
||||
// 自定义CSS弹窗
|
||||
const cssDialog = ref(false)
|
||||
|
||||
// 主题菜单是否显示
|
||||
const showThemeMenu = ref(false)
|
||||
|
||||
// 语言菜单是否显示
|
||||
const showLanguageMenu = ref(false)
|
||||
|
||||
// 自定义CSS
|
||||
const customCSS = ref('')
|
||||
|
||||
// 执行注销操作
|
||||
function logout() {
|
||||
// 清除登录状态信息
|
||||
@@ -82,13 +102,172 @@ const superUser = computed(() => userStore.superUser)
|
||||
const userName = computed(() => userStore.userName)
|
||||
const avatar = computed(() => userStore.avatar || avatar1)
|
||||
const userLevel = computed(() => userStore.level)
|
||||
|
||||
// 主题相关功能
|
||||
const { name: themeName, global: globalTheme } = useTheme()
|
||||
const savedTheme = ref(localStorage.getItem('theme') ?? themeName)
|
||||
const currentThemeName = ref(savedTheme.value)
|
||||
|
||||
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',
|
||||
},
|
||||
]
|
||||
|
||||
// 编辑器主题
|
||||
const editorTheme = computed(() => (currentThemeName.value === 'light' ? 'github' : 'monokai'))
|
||||
|
||||
// 更新主题
|
||||
function updateTheme() {
|
||||
const autoTheme = checkPrefersColorSchemeIsDark() ? 'dark' : 'light'
|
||||
const theme = currentThemeName.value === 'auto' ? autoTheme : currentThemeName.value
|
||||
globalTheme.name.value = theme
|
||||
// 保存原始主题设置,而不是计算后的值
|
||||
savedTheme.value = currentThemeName.value
|
||||
// 保存主题到本地
|
||||
saveLocalTheme(currentThemeName.value, globalTheme)
|
||||
// 刷新页面
|
||||
location.reload()
|
||||
}
|
||||
|
||||
// 切换主题
|
||||
function changeTheme(theme: string) {
|
||||
currentThemeName.value = theme
|
||||
showThemeMenu.value = false
|
||||
// 保存主题到服务端
|
||||
try {
|
||||
api.post('/user/config/Layout', {
|
||||
theme,
|
||||
})
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
// 获取自定义 CSS
|
||||
async function getCustomCSS() {
|
||||
try {
|
||||
const result: { [key: string]: any } = await api.get('system/setting/UserCustomCSS')
|
||||
if (result && result.success && result.data?.value) {
|
||||
customCSS.value = result.data?.value ?? ''
|
||||
if (customCSS.value) {
|
||||
const style = document.createElement('style')
|
||||
style.innerHTML = result.data?.value ?? ''
|
||||
document.head.appendChild(style)
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 保存自定义 CSS
|
||||
async function saveCustomCSS() {
|
||||
cssDialog.value = false
|
||||
try {
|
||||
const result: { [key: string]: any } = await api.post('system/setting/UserCustomCSS', customCSS.value, {
|
||||
headers: {
|
||||
'Content-Type': 'text/plain',
|
||||
},
|
||||
})
|
||||
|
||||
if (result.success) $toast.success(t('theme.customCssSaveSuccess'))
|
||||
} catch (e) {
|
||||
console.error(t('theme.customCssSaveFailed'))
|
||||
}
|
||||
}
|
||||
|
||||
// 监听主题变化
|
||||
watch(
|
||||
() => currentThemeName.value,
|
||||
() => updateTheme(),
|
||||
)
|
||||
|
||||
// 监听系统主题变化
|
||||
try {
|
||||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', updateTheme)
|
||||
} catch (e) {
|
||||
console.error(t('theme.deviceNotSupport'))
|
||||
}
|
||||
|
||||
// 语言相关功能
|
||||
const currentLocale = ref<SupportedLocale>(getCurrentLocale())
|
||||
|
||||
// 支持的语言列表
|
||||
const locales = computed(() => {
|
||||
return Object.entries(SUPPORTED_LOCALES).map(([key, locale]) => ({
|
||||
value: key as SupportedLocale,
|
||||
title: locale.title,
|
||||
flag: locale.flag,
|
||||
icon: `flag-${key.split('-')[0]}`,
|
||||
}))
|
||||
})
|
||||
|
||||
// 切换语言
|
||||
async function changeLocale(locale: SupportedLocale) {
|
||||
showLanguageMenu.value = false
|
||||
try {
|
||||
await setI18nLanguage(locale)
|
||||
currentLocale.value = locale
|
||||
// 刷新页面
|
||||
window.location.reload()
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 获取当前语言图标
|
||||
const getCurrentIcon = computed(() => {
|
||||
const locale = locales.value.find(l => l.value === currentLocale.value)
|
||||
return locale?.flag || '🌐'
|
||||
})
|
||||
|
||||
// 获取当前主题图标
|
||||
const getThemeIcon = computed(() => {
|
||||
const theme = themes.find(t => t.name === currentThemeName.value)
|
||||
return theme?.icon || 'mdi-laptop'
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
getCustomCSS()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VAvatar class="cursor-pointer ms-3" color="primary" variant="tonal">
|
||||
<VImg :src="avatar" />
|
||||
|
||||
<VMenu activator="parent" width="230" location="bottom end" offset="14px" class="user-menu" scrim>
|
||||
<VMenu
|
||||
activator="parent"
|
||||
width="15rem"
|
||||
location="bottom end"
|
||||
offset="14px"
|
||||
class="user-menu"
|
||||
:close-on-content-click="true"
|
||||
scrim
|
||||
>
|
||||
<VList class="pt-0">
|
||||
<!-- 👉 User Avatar & Name -->
|
||||
<VListItem class="py-4" bg-color="primary" bg-opacity="0.05">
|
||||
@@ -131,6 +310,80 @@ const userLevel = computed(() => userStore.level)
|
||||
<VListItemTitle>{{ t('user.siteAuth') }}</VListItemTitle>
|
||||
</VListItem>
|
||||
|
||||
<!-- 👉 主题设置 - 使用嵌套菜单 -->
|
||||
<VMenu location="end" offset-x min-width="200" v-model="showThemeMenu" :close-on-content-click="true">
|
||||
<template v-slot:activator="{ props: menuProps }">
|
||||
<VListItem v-bind="menuProps" class="mb-1 rounded-lg" hover>
|
||||
<template #prepend>
|
||||
<VIcon :icon="getThemeIcon" />
|
||||
</template>
|
||||
<VListItemTitle>
|
||||
{{ themes.find(t => t.name === currentThemeName)?.title || t('common.theme') }}
|
||||
</VListItemTitle>
|
||||
<template #append>
|
||||
<VIcon icon="mdi-chevron-right" size="small" />
|
||||
</template>
|
||||
</VListItem>
|
||||
</template>
|
||||
<VList>
|
||||
<VListItem
|
||||
v-for="theme in themes"
|
||||
:key="theme.name"
|
||||
@click="changeTheme(theme.name)"
|
||||
:active="currentThemeName === theme.name"
|
||||
class="mb-1"
|
||||
>
|
||||
<template #prepend>
|
||||
<VIcon :icon="theme.icon" />
|
||||
</template>
|
||||
<VListItemTitle>{{ theme.title }}</VListItemTitle>
|
||||
<template #append v-if="currentThemeName === theme.name">
|
||||
<VIcon icon="mdi-check" color="primary" size="small" />
|
||||
</template>
|
||||
</VListItem>
|
||||
<VListItem @click="cssDialog = true">
|
||||
<template #prepend>
|
||||
<VIcon icon="mdi-palette" />
|
||||
</template>
|
||||
<VListItemTitle>{{ t('theme.custom') }}</VListItemTitle>
|
||||
</VListItem>
|
||||
</VList>
|
||||
</VMenu>
|
||||
|
||||
<!-- 👉 语言设置 - 使用嵌套菜单 -->
|
||||
<VMenu location="end" offset-x min-width="200" v-model="showLanguageMenu" :close-on-content-click="true">
|
||||
<template v-slot:activator="{ props: menuProps }">
|
||||
<VListItem v-bind="menuProps" class="mb-1 rounded-lg" hover>
|
||||
<template #prepend>
|
||||
<span class="me-4">{{ getCurrentIcon }}</span>
|
||||
</template>
|
||||
<VListItemTitle>
|
||||
{{ locales.find(l => l.value === currentLocale)?.title || t('common.language') }}
|
||||
</VListItemTitle>
|
||||
<template #append>
|
||||
<VIcon icon="mdi-chevron-right" size="small" />
|
||||
</template>
|
||||
</VListItem>
|
||||
</template>
|
||||
<VList>
|
||||
<VListItem
|
||||
v-for="locale in locales"
|
||||
:key="locale.value"
|
||||
@click="changeLocale(locale.value)"
|
||||
:active="currentLocale === locale.value"
|
||||
class="mb-1"
|
||||
>
|
||||
<template #prepend>
|
||||
<span class="text-xl me-2">{{ locale.flag }}</span>
|
||||
</template>
|
||||
<VListItemTitle>{{ locale.title }}</VListItemTitle>
|
||||
<template #append v-if="currentLocale === locale.value">
|
||||
<VIcon icon="mdi-check" color="primary" size="small" />
|
||||
</template>
|
||||
</VListItem>
|
||||
</VList>
|
||||
</VMenu>
|
||||
|
||||
<!-- 👉 FAQ -->
|
||||
<VListItem href="https://wiki.movie-pilot.org" target="_blank" class="mb-1 rounded-lg" hover>
|
||||
<template #prepend>
|
||||
@@ -163,6 +416,7 @@ const userLevel = computed(() => userStore.level)
|
||||
</VMenu>
|
||||
<!-- !SECTION -->
|
||||
</VAvatar>
|
||||
|
||||
<!-- 重启进度框 -->
|
||||
<ProgressDialog v-if="progressDialog" v-model="progressDialog" :text="t('app.restarting')" />
|
||||
<!-- 用户认证对话框 -->
|
||||
@@ -192,4 +446,34 @@ const userLevel = computed(() => userStore.level)
|
||||
<VDialogCloseBtn @click="restartDialog = false" />
|
||||
</VCard>
|
||||
</VDialog>
|
||||
|
||||
<!-- 自定义 CSS -->
|
||||
<VDialog v-if="cssDialog" v-model="cssDialog" max-width="50rem" scrollable :fullscreen="!display.mdAndUp.value">
|
||||
<VCard>
|
||||
<VCardItem>
|
||||
<VCardTitle>
|
||||
<VIcon icon="mdi-palette" class="me-2" />
|
||||
{{ t('theme.custom') }}
|
||||
</VCardTitle>
|
||||
<VDialogCloseBtn @click="cssDialog = false" />
|
||||
</VCardItem>
|
||||
<VDivider />
|
||||
<VAceEditor v-model:value="customCSS" lang="css" :theme="editorTheme" class="w-full min-h-[30rem]" />
|
||||
<VDivider />
|
||||
<VCardText class="text-center">
|
||||
<VBtn @click="saveCustomCSS" class="w-1/2">
|
||||
<template #prepend>
|
||||
<VIcon icon="mdi-content-save" />
|
||||
</template>
|
||||
{{ t('common.save') }}
|
||||
</VBtn>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VDialog>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.v-list-item__prepend {
|
||||
min-inline-size: 24px !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -21,6 +21,8 @@ export default {
|
||||
create: 'Create',
|
||||
saving: 'Saving',
|
||||
reset: 'Reset',
|
||||
theme: 'Theme',
|
||||
language: 'Language',
|
||||
},
|
||||
mediaType: {
|
||||
movie: 'Movie',
|
||||
@@ -36,6 +38,9 @@ export default {
|
||||
transparent: 'Transparent',
|
||||
purple: 'Purple',
|
||||
custom: 'Custom Theme',
|
||||
customCssSaveSuccess: 'Custom CSS saved successfully, please refresh the page to take effect!',
|
||||
customCssSaveFailed: 'Failed to save custom CSS to server',
|
||||
deviceNotSupport: 'Current device does not support monitoring system theme changes',
|
||||
},
|
||||
app: {
|
||||
moviepilot: 'MoviePilot',
|
||||
@@ -43,7 +48,6 @@ export default {
|
||||
subscribeMovie: 'Movie Subscription',
|
||||
subscribeTv: 'TV Subscription',
|
||||
settings: 'Settings',
|
||||
language: 'Language',
|
||||
selectLanguage: 'Select Language',
|
||||
logout: 'Logout',
|
||||
restarting: 'Restarting...',
|
||||
@@ -137,7 +141,8 @@ export default {
|
||||
},
|
||||
words: {
|
||||
title: 'Word Lists',
|
||||
description: 'Custom recognition words, custom production/subtitle groups, custom placeholders, file organization block words',
|
||||
description:
|
||||
'Custom recognition words, custom production/subtitle groups, custom placeholders, file organization block words',
|
||||
},
|
||||
about: {
|
||||
title: 'About',
|
||||
@@ -937,9 +942,10 @@ export default {
|
||||
season: 'Season {number}',
|
||||
title: 'Title',
|
||||
description: 'Description',
|
||||
descriptionHint: 'Add a description about this subscription. Search terms, recognition words, etc. will be included in the share by default',
|
||||
descriptionHint:
|
||||
'Add a description about this subscription. Search terms, recognition words, etc. will be included in the share by default',
|
||||
shareUser: 'Share User',
|
||||
shareUserHint: 'Sharer\'s nickname',
|
||||
shareUserHint: "Sharer's nickname",
|
||||
confirmShare: 'Confirm Share',
|
||||
shareSuccess: '{name} shared successfully!',
|
||||
shareFailed: '{name} share failed: {message}!',
|
||||
@@ -960,7 +966,8 @@ export default {
|
||||
title: 'RClone Configuration',
|
||||
filePath: 'rclone config file path',
|
||||
fileContent: 'rclone config file content',
|
||||
defaultContent: '# Please fill in your rclone config file content here \n# Please refer to https://rclone.org/docs/ \n# Storage node name must be: MP',
|
||||
defaultContent:
|
||||
'# Please fill in your rclone config file content here \n# Please refer to https://rclone.org/docs/ \n# Storage node name must be: MP',
|
||||
complete: 'Complete',
|
||||
},
|
||||
alistConfig: {
|
||||
@@ -1030,7 +1037,7 @@ export default {
|
||||
title: 'Update Site Cookie',
|
||||
checkHint: 'Checking login status, please wait...',
|
||||
confirmUpdateTitle: 'Confirm Update',
|
||||
confirmUpdateMessage: 'Do you want to update this site\'s cookie with the local cookie?',
|
||||
confirmUpdateMessage: "Do you want to update this site's cookie with the local cookie?",
|
||||
processing: 'Processing...',
|
||||
success: 'Cookie updated successfully',
|
||||
failed: 'Failed to update cookie',
|
||||
@@ -1229,7 +1236,8 @@ export default {
|
||||
mediaCategoryHint: 'Specify category name, leave empty for auto-recognition',
|
||||
customWords: 'Custom Recognition Words',
|
||||
customWordsHint: 'Recognition words only used for this subscription',
|
||||
customWordsPlaceholder: 'Block word\nReplaced word => Replacement word\nPrefix <> Suffix >> Episode offset (EP)\nReplaced word => Replacement word && Prefix <> Suffix >> Episode offset (EP)\nReplacement word supports format: { tmdbid/doubanid=xxx;type=movie/tv;s=xxx;e=xxx } to directly specify TMDBID/Douban ID recognition, where s, e are season and episode numbers (optional)',
|
||||
customWordsPlaceholder:
|
||||
'Block word\nReplaced word => Replacement word\nPrefix <> Suffix >> Episode offset (EP)\nReplaced word => Replacement word && Prefix <> Suffix >> Episode offset (EP)\nReplacement word supports format: { tmdbid/doubanid=xxx;type=movie/tv;s=xxx;e=xxx } to directly specify TMDBID/Douban ID recognition, where s, e are season and episode numbers (optional)',
|
||||
cancelSubscribe: 'Cancel Subscription',
|
||||
save: 'Save',
|
||||
cancelSubscribeConfirm: 'Are you sure you want to cancel the subscription?',
|
||||
@@ -1409,9 +1417,11 @@ export default {
|
||||
otpEnableSuccess: 'Two-factor authentication enabled successfully!',
|
||||
otpEnableFailed: 'Failed to enable OTP: {message}!',
|
||||
authenticatorApp: 'Authenticator App',
|
||||
authenticatorAppDescription: 'Use an authenticator app like Google Authenticator, Microsoft Authenticator, Authy, or 1Password to scan the QR code. It will generate a 6-digit code for you to enter below.',
|
||||
secretKeyTip: 'If you\'re having trouble with the QR code, select manual entry in your app and enter the code above.',
|
||||
authenticatorAppDescription:
|
||||
'Use an authenticator app like Google Authenticator, Microsoft Authenticator, Authy, or 1Password to scan the QR code. It will generate a 6-digit code for you to enter below.',
|
||||
secretKeyTip:
|
||||
"If you're having trouble with the QR code, select manual entry in your app and enter the code above.",
|
||||
enterVerificationCode: 'Enter verification code to confirm enabling two-factor authentication',
|
||||
avatarFormatTip: 'JPG, PNG, GIF, WEBP formats allowed, maximum size 800KB.',
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ export default {
|
||||
create: '新建',
|
||||
saving: '保存中',
|
||||
reset: '重置',
|
||||
theme: '主题',
|
||||
language: '语言',
|
||||
},
|
||||
mediaType: {
|
||||
movie: '电影',
|
||||
@@ -36,6 +38,9 @@ export default {
|
||||
transparent: '透明',
|
||||
purple: '幻紫',
|
||||
custom: '自定义主题',
|
||||
customCssSaveSuccess: '自定义CSS保存成功,请刷新页面生效!',
|
||||
customCssSaveFailed: '保存自定义CSS到服务端失败',
|
||||
deviceNotSupport: '当前设备不支持监听系统主题变化',
|
||||
},
|
||||
app: {
|
||||
moviepilot: 'MoviePilot',
|
||||
|
||||
@@ -21,6 +21,8 @@ export default {
|
||||
create: '新建',
|
||||
saving: '保存中',
|
||||
reset: '重置',
|
||||
theme: '主題',
|
||||
language: '語言',
|
||||
},
|
||||
mediaType: {
|
||||
movie: '電影',
|
||||
@@ -36,6 +38,9 @@ export default {
|
||||
transparent: '透明',
|
||||
purple: '幻紫',
|
||||
custom: '自定義主題',
|
||||
customCssSaveSuccess: '自定義CSS保存成功,請刷新頁面生效!',
|
||||
customCssSaveFailed: '保存自定義CSS到服務端失敗',
|
||||
deviceNotSupport: '當前設備不支持監聽系統主題變化',
|
||||
},
|
||||
app: {
|
||||
moviepilot: 'MoviePilot',
|
||||
|
||||
Reference in New Issue
Block a user