mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-08-09 23:53:31 +08:00
Refine mobile cache and scheduler layouts
This commit is contained in:
@@ -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 />
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user