mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-09-06 16:16:42 +08:00
优化 App 组件,添加全局设置注入,更新主题属性处理逻辑,增强背景图片获取功能,并实现图片地址的动态计算。重构 Footer 组件,简化菜单状态管理,提升导航体验。调整应用中心页面,增加分组标题,优化列表项的内边距,提升整体可用性和视觉效果。
This commit is contained in:
+17
-4
@@ -10,8 +10,11 @@ let themeValue = localStorage.getItem('theme') || 'light'
|
|||||||
const autoTheme = checkPrefersColorSchemeIsDark() ? 'dark' : 'light'
|
const autoTheme = checkPrefersColorSchemeIsDark() ? 'dark' : 'light'
|
||||||
globalTheme.name.value = themeValue === 'auto' ? autoTheme : themeValue
|
globalTheme.name.value = themeValue === 'auto' ? autoTheme : themeValue
|
||||||
|
|
||||||
|
// 从 provide 中获取全局设置
|
||||||
|
const globalSettings: any = inject('globalSettings')
|
||||||
|
|
||||||
// 更新data-theme属性以便CSS选择器能正确匹配
|
// 更新data-theme属性以便CSS选择器能正确匹配
|
||||||
function updateHtmlThemeAttribute(themeName) {
|
function updateHtmlThemeAttribute(themeName: string) {
|
||||||
document.documentElement.setAttribute('data-theme', themeName)
|
document.documentElement.setAttribute('data-theme', themeName)
|
||||||
// 确保body元素也有相同的主题属性,以便更好地选择弹出窗口
|
// 确保body元素也有相同的主题属性,以便更好地选择弹出窗口
|
||||||
document.body.setAttribute('data-theme', themeName)
|
document.body.setAttribute('data-theme', themeName)
|
||||||
@@ -30,9 +33,8 @@ let backgroundRotationTimer: NodeJS.Timeout | null = null
|
|||||||
async function fetchBackgroundImages() {
|
async function fetchBackgroundImages() {
|
||||||
try {
|
try {
|
||||||
backgroundImages.value = await api.get('/login/wallpapers')
|
backgroundImages.value = await api.get('/login/wallpapers')
|
||||||
console.log('获取背景图片成功:', backgroundImages.value)
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('获取背景图片失败:', e)
|
console.error(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,6 +49,17 @@ function startBackgroundRotation() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 计算图片地址
|
||||||
|
function getImgUrl(url: string) {
|
||||||
|
// 使用图片缓存
|
||||||
|
if (globalSettings.GLOBAL_IMAGE_CACHE)
|
||||||
|
return `${import.meta.env.VITE_API_BASE_URL}system/cache/image?url=${encodeURIComponent(url)}`
|
||||||
|
// 如果地址中包含douban则使用中转代理
|
||||||
|
if (url.includes('doubanio.com'))
|
||||||
|
return `${import.meta.env.VITE_API_BASE_URL}system/img/0?imgurl=${encodeURIComponent(url)}`
|
||||||
|
return url
|
||||||
|
}
|
||||||
|
|
||||||
// 监听主题变化
|
// 监听主题变化
|
||||||
watch(
|
watch(
|
||||||
() => globalTheme.name.value,
|
() => globalTheme.name.value,
|
||||||
@@ -131,7 +144,7 @@ onUnmounted(() => {
|
|||||||
:key="index"
|
:key="index"
|
||||||
class="background-image"
|
class="background-image"
|
||||||
:class="{ 'active': index === activeImageIndex }"
|
:class="{ 'active': index === activeImageIndex }"
|
||||||
:style="{ backgroundImage: `url(${imageUrl})` }"
|
:style="{ backgroundImage: `url(${getImgUrl(imageUrl)})` }"
|
||||||
></div>
|
></div>
|
||||||
<!-- 全局磨砂层 -->
|
<!-- 全局磨砂层 -->
|
||||||
<div class="global-blur-layer"></div>
|
<div class="global-blur-layer"></div>
|
||||||
|
|||||||
@@ -7,86 +7,50 @@ const appMode = inject('pwaMode') && display.mdAndDown.value
|
|||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|
||||||
// 过滤出底部菜单项(排除电影和电视剧,因为我们会合并它们)
|
// 根据当前路径获取匹配的菜单路径
|
||||||
|
function getMenuPathFromRoute(path: string): string {
|
||||||
|
const matchedMenu = SystemNavMenus.find(menu => menu.footer === true && path.startsWith(menu.to))
|
||||||
|
return matchedMenu ? matchedMenu.to : '/apps'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 当前选中的菜单,初始值基于当前路由
|
||||||
|
const currentMenu = ref<string>(getMenuPathFromRoute(route.path))
|
||||||
|
|
||||||
|
// 过滤出底部菜单项
|
||||||
const footerMenus = computed(() => {
|
const footerMenus = computed(() => {
|
||||||
return SystemNavMenus.filter(menu => menu.footer === true)
|
return SystemNavMenus.filter(menu => menu.footer === true)
|
||||||
})
|
})
|
||||||
|
|
||||||
// 为每个底部菜单创建激活状态
|
// 监听路由变化来更新currentMenu
|
||||||
const activeState = computed(() => {
|
|
||||||
const activeStates: Record<string, boolean> = {}
|
|
||||||
|
|
||||||
footerMenus.value.forEach(menu => {
|
|
||||||
const pathKey = menu.to.replace(/\//g, '_')
|
|
||||||
activeStates[pathKey] = route.path.startsWith(menu.to)
|
|
||||||
})
|
|
||||||
|
|
||||||
return activeStates
|
|
||||||
})
|
|
||||||
|
|
||||||
// 更多按钮的激活状态
|
|
||||||
const moreActiveState = computed(() => {
|
|
||||||
return !Object.values(activeState.value).some(v => v)
|
|
||||||
})
|
|
||||||
|
|
||||||
// 用于动画的状态和方法
|
|
||||||
const indicator = ref<HTMLElement | null>(null)
|
|
||||||
const activeButton = ref<HTMLElement | null>(null)
|
|
||||||
|
|
||||||
// 更新指示器位置的方法
|
|
||||||
const updateIndicatorPosition = async () => {
|
|
||||||
await nextTick()
|
|
||||||
const activeEl = document.querySelector('.footer-nav-btn-active') as HTMLElement
|
|
||||||
if (activeEl && indicator.value) {
|
|
||||||
// 获取按钮的完整尺寸和位置信息
|
|
||||||
const rect = activeEl.getBoundingClientRect()
|
|
||||||
const parentRect = indicator.value.parentElement!.getBoundingClientRect()
|
|
||||||
|
|
||||||
// 计算相对于父容器的位置
|
|
||||||
const relativeLeft = rect.left - parentRect.left
|
|
||||||
|
|
||||||
// 设置指示器宽度和位置
|
|
||||||
indicator.value.style.width = `${rect.width}px`
|
|
||||||
indicator.value.style.left = `${relativeLeft}px`
|
|
||||||
|
|
||||||
activeButton.value = activeEl
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 监听路由变化
|
|
||||||
watch(
|
watch(
|
||||||
() => route.path,
|
() => route.path,
|
||||||
async () => {
|
newPath => {
|
||||||
updateIndicatorPosition()
|
currentMenu.value = getMenuPathFromRoute(newPath)
|
||||||
},
|
},
|
||||||
{ immediate: false },
|
{ immediate: false },
|
||||||
)
|
)
|
||||||
|
|
||||||
// 在组件挂载后初始化指示器位置
|
|
||||||
onMounted(() => {
|
|
||||||
updateIndicatorPosition()
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Teleport to="body">
|
<Teleport v-if="appMode" to="body">
|
||||||
<div class="footer-nav-container">
|
<div class="footer-nav-container">
|
||||||
<VCard class="footer-nav-card border" rounded="pill">
|
<VCard class="footer-nav-card border" rounded="pill">
|
||||||
<VCardText class="footer-card-content">
|
<VCardText class="footer-card-content">
|
||||||
<!-- 添加指示器 -->
|
<!-- 添加指示器 -->
|
||||||
<div ref="indicator" class="nav-indicator"></div>
|
<div ref="indicator" class="nav-indicator"></div>
|
||||||
<VBtnToggle class="footer-btn-group" :mandatory="false">
|
<VBtnToggle class="footer-btn-group" :mandatory="false" v-model="currentMenu">
|
||||||
<!-- 遍历底部菜单项 -->
|
<!-- 遍历底部菜单项 -->
|
||||||
<VBtn
|
<VBtn
|
||||||
v-for="menu in footerMenus"
|
v-for="menu in footerMenus"
|
||||||
:key="menu.to"
|
:key="menu.to"
|
||||||
:to="menu.to"
|
:to="menu.to"
|
||||||
variant="plain"
|
:variant="currentMenu === menu.to ? 'text' : 'plain'"
|
||||||
:ripple="false"
|
|
||||||
color="primary"
|
color="primary"
|
||||||
|
:ripple="false"
|
||||||
class="footer-nav-btn"
|
class="footer-nav-btn"
|
||||||
rounded="pill"
|
rounded="pill"
|
||||||
:class="{ 'footer-nav-btn-active': activeState[menu.to.replace(/\//g, '_')] }"
|
:class="{ 'footer-nav-btn-active': currentMenu === menu.to }"
|
||||||
|
:value="menu.to"
|
||||||
>
|
>
|
||||||
<div class="btn-content">
|
<div class="btn-content">
|
||||||
<VIcon :icon="menu.icon" size="24"></VIcon>
|
<VIcon :icon="menu.icon" size="24"></VIcon>
|
||||||
@@ -96,13 +60,14 @@ onMounted(() => {
|
|||||||
|
|
||||||
<!-- 更多按钮 -->
|
<!-- 更多按钮 -->
|
||||||
<VBtn
|
<VBtn
|
||||||
variant="plain"
|
:variant="currentMenu === '/apps' ? 'text' : 'plain'"
|
||||||
:ripple="false"
|
|
||||||
color="primary"
|
color="primary"
|
||||||
|
:ripple="false"
|
||||||
to="/apps"
|
to="/apps"
|
||||||
rounded="pill"
|
rounded="pill"
|
||||||
class="footer-nav-btn"
|
class="footer-nav-btn"
|
||||||
:class="{ 'footer-nav-btn-active': moreActiveState }"
|
:class="{ 'footer-nav-btn-active': currentMenu === '/apps' }"
|
||||||
|
value="/apps"
|
||||||
>
|
>
|
||||||
<div class="btn-content">
|
<div class="btn-content">
|
||||||
<VIcon icon="mdi-dots-horizontal" size="24"></VIcon>
|
<VIcon icon="mdi-dots-horizontal" size="24"></VIcon>
|
||||||
@@ -122,7 +87,7 @@ onMounted(() => {
|
|||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
z-index: 9998;
|
z-index: 1999;
|
||||||
padding-bottom: calc(6px + env(safe-area-inset-bottom, 0px));
|
padding-bottom: calc(6px + env(safe-area-inset-bottom, 0px));
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@@ -143,17 +108,6 @@ onMounted(() => {
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-indicator {
|
|
||||||
position: absolute;
|
|
||||||
height: 48px;
|
|
||||||
background-color: rgba(var(--v-theme-primary), 0.1);
|
|
||||||
border-radius: 100px;
|
|
||||||
z-index: 1;
|
|
||||||
top: 6px;
|
|
||||||
transition: all 0.3s cubic-bezier(0.25, 0.8, 0.5, 1);
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer-btn-group {
|
.footer-btn-group {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -39,6 +39,9 @@ onMounted(() => {
|
|||||||
<VContainer>
|
<VContainer>
|
||||||
<!-- 遍历所有分组 -->
|
<!-- 遍历所有分组 -->
|
||||||
<div v-for="(apps, header) in appGroups" :key="header" class="mb-3">
|
<div v-for="(apps, header) in appGroups" :key="header" class="mb-3">
|
||||||
|
<VListSubheader class="ps-1">
|
||||||
|
{{ header }}
|
||||||
|
</VListSubheader>
|
||||||
<!-- 分组内容 - 使用卡片包装 -->
|
<!-- 分组内容 - 使用卡片包装 -->
|
||||||
<VCard variant="flat" class="settings-section-card">
|
<VCard variant="flat" class="settings-section-card">
|
||||||
<VList lines="one" class="settings-list">
|
<VList lines="one" class="settings-list">
|
||||||
@@ -94,7 +97,7 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.settings-list-item {
|
.settings-list-item {
|
||||||
padding: 12px 16px;
|
padding: 8px 12px;
|
||||||
transition: background-color 0.2s;
|
transition: background-color 0.2s;
|
||||||
|
|
||||||
&:not(:last-child) {
|
&:not(:last-child) {
|
||||||
|
|||||||
Reference in New Issue
Block a user