Refine mobile cache and scheduler layouts

This commit is contained in:
jxxghp
2026-06-28 12:46:47 +08:00
parent 7574719e04
commit e692a91113
3 changed files with 67 additions and 91 deletions

View File

@@ -73,7 +73,6 @@ const bodyClasses = computed(() => [
<VIcon :icon="props.icon" class="me-2" />
{{ props.title }}
</VCardTitle>
<VCardSubtitle v-if="props.subtitle">{{ props.subtitle }}</VCardSubtitle>
<VDialogCloseBtn v-model="visible" />
</VCardItem>
<VDivider />

View File

@@ -287,6 +287,20 @@ function getMediaTypeColor(type: string): string {
}
}
/** 获取移动端类型角标使用的 MediaCard 同款颜色类。 */
function getMobileMediaTypeChipClass(type: string): string {
switch (type) {
case 'movie':
case t('setting.cache.mediaType.movie'):
return 'border-blue-500 bg-blue-600'
case 'tv':
case t('setting.cache.mediaType.tv'):
return 'bg-indigo-500 border-indigo-600'
default:
return 'border-purple-600 bg-purple-600'
}
}
/** 生成移动端缓存卡片的稳定渲染键。 */
function getMobileCacheItemKey(item: TorrentCacheItem, index: number): string {
return item.hash || [item.domain, item.title, index].join('-')
@@ -401,6 +415,21 @@ watch([titleFilter, siteFilter], () => {
<template #default="{ item, index, itemRef }">
<article :ref="itemRef" :key="getMobileCacheItemKey(item, index)" class="cache-mobile-card">
<div class="cache-mobile-card__poster">
<VChip
v-if="item.media_type"
variant="elevated"
size="small"
:class="getMobileMediaTypeChipClass(item.media_type)"
class="cache-mobile-card__type bg-opacity-80 text-white font-bold"
>
{{
item.media_type === 'movie'
? t('setting.cache.mediaType.movie')
: item.media_type === 'tv'
? t('setting.cache.mediaType.tv')
: item.media_type
}}
</VChip>
<VImg
v-if="item.poster_path"
:src="item.poster_path"
@@ -425,18 +454,6 @@ watch([titleFilter, siteFilter], () => {
<span v-if="getMobileMediaMeta(item)">{{ getMobileMediaMeta(item) }}</span>
</div>
<div class="cache-mobile-card__chips">
<VChip v-if="item.media_type" :color="getMediaTypeColor(item.media_type)" size="x-small" variant="tonal">
{{
item.media_type === 'movie'
? t('setting.cache.mediaType.movie')
: item.media_type === 'tv'
? t('setting.cache.mediaType.tv')
: item.media_type
}}
</VChip>
</div>
<div class="cache-mobile-card__meta">
<span>{{ getMobileResourceMeta(item) }}</span>
<strong>{{ formatFileSize(item.size) }}</strong>
@@ -714,7 +731,7 @@ watch([titleFilter, siteFilter], () => {
block-size: 100%;
inline-size: 100%;
min-block-size: 0;
padding: calc(18px + env(safe-area-inset-top)) 16px calc(18px + env(safe-area-inset-bottom));
padding: calc(8px + env(safe-area-inset-top)) 16px calc(18px + env(safe-area-inset-bottom));
background: var(--cache-mobile-page-bg);
gap: 16px;
}
@@ -820,12 +837,13 @@ watch([titleFilter, siteFilter], () => {
background: var(--cache-mobile-surface-bg);
box-shadow: 0 10px 30px rgba(var(--v-theme-on-surface), 0.07);
gap: 14px;
grid-template-columns: 72px minmax(0, 1fr) 32px;
grid-template-columns: 72px minmax(0, 1fr);
margin-block-end: 12px;
padding: 14px;
}
.cache-mobile-card__poster {
position: relative;
display: flex;
overflow: hidden;
align-items: center;
@@ -837,6 +855,14 @@ watch([titleFilter, siteFilter], () => {
inline-size: 72px;
}
.cache-mobile-card__type {
position: absolute;
z-index: 1;
inset-block-end: 5px;
inset-inline-start: 50%;
transform: translateX(-50%);
}
.cache-mobile-card__content {
min-inline-size: 0;
}
@@ -847,6 +873,7 @@ watch([titleFilter, siteFilter], () => {
font-weight: 700;
line-height: 1.35;
overflow-wrap: anywhere;
padding-inline-end: 34px;
white-space: normal;
word-break: break-word;
}
@@ -854,8 +881,8 @@ watch([titleFilter, siteFilter], () => {
.cache-mobile-card__main {
margin-block-start: 6px;
color: rgba(var(--v-theme-on-surface), 0.88);
font-size: 18px;
font-weight: 800;
font-size: 15px;
font-weight: 700;
line-height: 1.32;
overflow-wrap: anywhere;
white-space: normal;
@@ -865,25 +892,14 @@ watch([titleFilter, siteFilter], () => {
.cache-mobile-card__main span {
margin-inline-start: 6px;
color: rgba(var(--v-theme-on-surface), 0.58);
font-size: 16px;
font-weight: 600;
}
.cache-mobile-card__chips {
display: flex;
align-items: center;
flex-wrap: wrap;
margin-block-start: 8px;
color: rgba(var(--v-theme-on-surface), 0.62);
font-size: 14px;
font-weight: 600;
gap: 8px;
font-weight: 500;
}
.cache-mobile-card__meta {
display: flex;
align-items: flex-end;
justify-content: space-between;
display: grid;
align-items: end;
grid-template-columns: minmax(0, 1fr) auto;
margin-block-start: 8px;
color: rgba(var(--v-theme-on-surface), 0.62);
font-size: 14px;
@@ -898,17 +914,18 @@ watch([titleFilter, siteFilter], () => {
}
.cache-mobile-card__meta strong {
flex: 0 0 auto;
color: rgba(var(--v-theme-on-surface), 0.62);
font-size: 14px;
font-weight: 700;
text-align: end;
white-space: nowrap;
}
.cache-mobile-card__menu {
align-self: center;
position: absolute;
color: rgba(var(--v-theme-on-surface), 0.5);
justify-self: end;
inset-block-start: 8px;
inset-inline-end: 8px;
}
.cache-mobile-load-state,
@@ -960,7 +977,7 @@ html[data-theme='transparent'] .cache-mobile-page,
}
.cache-mobile-card {
grid-template-columns: 64px minmax(0, 1fr) 28px;
grid-template-columns: 64px minmax(0, 1fr);
padding: 12px;
}

View File

@@ -4,7 +4,6 @@ import api from '@/api'
import type { ScheduleInfo } from '@/api/types'
import { useI18n } from 'vue-i18n'
import { useBackground } from '@/composables/useBackground'
import { useTheme } from 'vuetify'
// 移动端任务卡片视觉配置。
type SchedulerMobileVisual = {
@@ -23,7 +22,6 @@ type SchedulerMobileVisualRule = SchedulerMobileVisual & {
// 国际化
const { t } = useI18n()
const { useDataRefresh } = useBackground()
const theme = useTheme()
// 提示框
const $toast = useToast()
@@ -31,10 +29,6 @@ const $toast = useToast()
// 定时服务列表
const schedulerList = ref<ScheduleInfo[]>([])
// 当前是否为透明主题,用于限制透明适配只作用于本组件内部节点。
const isTransparentTheme = computed(() => theme.name.value === 'transparent')
const isDarkTheme = computed(() => theme.current.value.dark && !isTransparentTheme.value)
// 移动端任务图标按后端 job id 优先匹配,避免列表顺序变化导致图标看起来随机。
const schedulerMobileVisualRules: SchedulerMobileVisualRule[] = [
{ ids: ['cookiecloud'], names: ['CookieCloud'], icon: 'mdi-cloud-sync-outline', color: '#3f8cff', rgb: '63, 140, 255' },
@@ -229,32 +223,18 @@ const { loading: schedulerLoading } = useDataRefresh(
</tbody>
</VTable>
<div
v-else-if="!schedulerLoading"
class="desktop-scheduler-empty"
:class="{ 'scheduler-empty--transparent': isTransparentTheme }"
>
<div v-else-if="!schedulerLoading" class="desktop-scheduler-empty">
<VIcon icon="mdi-timer-off-outline" size="48" />
<p>{{ t('setting.scheduler.noService') }}</p>
</div>
<div
v-else
class="desktop-scheduler-empty"
:class="{ 'scheduler-empty--transparent': isTransparentTheme }"
>
<div v-else class="desktop-scheduler-empty">
<VProgressCircular indeterminate color="primary" size="22" width="2" />
<p>{{ t('common.loadingText') }}</p>
</div>
</VCard>
<div
class="mobile-scheduler-view d-md-none"
:class="{
'mobile-scheduler-view--dark': isDarkTheme,
'mobile-scheduler-view--transparent': isTransparentTheme,
}"
>
<div class="mobile-scheduler-view d-md-none">
<div v-if="mobileSchedulerCards.length" class="mobile-scheduler-list">
<article
v-for="{ scheduler, visual, statusText, statusVariant } in mobileSchedulerCards"
@@ -291,11 +271,7 @@ const { loading: schedulerLoading } = useDataRefresh(
</article>
</div>
<div
v-else-if="!schedulerLoading"
class="mobile-scheduler-empty"
:class="{ 'scheduler-empty--transparent': isTransparentTheme }"
>
<div v-else-if="!schedulerLoading" class="mobile-scheduler-empty">
<VIcon icon="mdi-timer-off-outline" size="44" />
<p>{{ t('setting.scheduler.noService') }}</p>
</div>
@@ -325,18 +301,10 @@ const { loading: schedulerLoading } = useDataRefresh(
font-size: 15px;
}
.scheduler-empty--transparent {
background: transparent;
}
.mobile-scheduler-view {
--scheduler-mobile-card-bg: rgba(var(--v-theme-surface), 0.94);
--scheduler-mobile-card-shadow: none;
--scheduler-mobile-view-bg: transparent;
min-block-size: 100%;
padding: 12px 18px calc(22px + env(safe-area-inset-bottom));
background: var(--scheduler-mobile-view-bg);
background: transparent;
}
.mobile-scheduler-list {
@@ -351,8 +319,9 @@ const { loading: schedulerLoading } = useDataRefresh(
padding: 18px;
border: 0;
border-radius: var(--app-surface-radius);
background: var(--scheduler-mobile-card-bg);
box-shadow: var(--scheduler-mobile-card-shadow);
background: rgb(var(--v-theme-surface));
backdrop-filter: none;
box-shadow: none;
column-gap: 14px;
grid-template-columns: 62px minmax(0, 1fr) auto;
}
@@ -405,11 +374,11 @@ const { loading: schedulerLoading } = useDataRefresh(
display: inline-flex;
align-items: center;
justify-content: center;
padding: 6px 14px;
padding: 5px 10px;
border-radius: 999px;
background: rgba(var(--v-theme-on-surface), 0.06);
color: rgba(var(--v-theme-on-surface), 0.62);
font-size: 14px;
font-size: 12px;
font-weight: 600;
line-height: 1;
white-space: nowrap;
@@ -475,19 +444,10 @@ const { loading: schedulerLoading } = useDataRefresh(
gap: 10px;
}
.mobile-scheduler-view--transparent {
--scheduler-mobile-card-bg: rgba(var(--v-theme-surface), var(--transparent-opacity-light, 0.2));
--scheduler-mobile-card-shadow: none;
--scheduler-mobile-view-bg: transparent;
}
.mobile-scheduler-view--transparent .mobile-scheduler-card {
html[data-theme='transparent'] .mobile-scheduler-card,
.v-theme--transparent .mobile-scheduler-card {
background: rgba(var(--v-theme-surface), var(--transparent-opacity-light, 0.2));
backdrop-filter: blur(var(--transparent-blur, 10px));
box-shadow: none;
}
.mobile-scheduler-view--dark {
--scheduler-mobile-card-bg: rgba(var(--v-theme-surface), 0.82);
}
@media (max-width: 480px) {
@@ -519,8 +479,8 @@ const { loading: schedulerLoading } = useDataRefresh(
}
.mobile-scheduler-status {
padding: 6px 11px;
font-size: 13px;
padding: 5px 9px;
font-size: 12px;
}
.mobile-scheduler-run-btn {