mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-09-07 00:36:41 +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" />
|
<VIcon :icon="props.icon" class="me-2" />
|
||||||
{{ props.title }}
|
{{ props.title }}
|
||||||
</VCardTitle>
|
</VCardTitle>
|
||||||
<VCardSubtitle v-if="props.subtitle">{{ props.subtitle }}</VCardSubtitle>
|
|
||||||
<VDialogCloseBtn v-model="visible" />
|
<VDialogCloseBtn v-model="visible" />
|
||||||
</VCardItem>
|
</VCardItem>
|
||||||
<VDivider />
|
<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 {
|
function getMobileCacheItemKey(item: TorrentCacheItem, index: number): string {
|
||||||
return item.hash || [item.domain, item.title, index].join('-')
|
return item.hash || [item.domain, item.title, index].join('-')
|
||||||
@@ -401,6 +415,21 @@ watch([titleFilter, siteFilter], () => {
|
|||||||
<template #default="{ item, index, itemRef }">
|
<template #default="{ item, index, itemRef }">
|
||||||
<article :ref="itemRef" :key="getMobileCacheItemKey(item, index)" class="cache-mobile-card">
|
<article :ref="itemRef" :key="getMobileCacheItemKey(item, index)" class="cache-mobile-card">
|
||||||
<div class="cache-mobile-card__poster">
|
<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
|
<VImg
|
||||||
v-if="item.poster_path"
|
v-if="item.poster_path"
|
||||||
:src="item.poster_path"
|
:src="item.poster_path"
|
||||||
@@ -425,18 +454,6 @@ watch([titleFilter, siteFilter], () => {
|
|||||||
<span v-if="getMobileMediaMeta(item)">{{ getMobileMediaMeta(item) }}</span>
|
<span v-if="getMobileMediaMeta(item)">{{ getMobileMediaMeta(item) }}</span>
|
||||||
</div>
|
</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">
|
<div class="cache-mobile-card__meta">
|
||||||
<span>{{ getMobileResourceMeta(item) }}</span>
|
<span>{{ getMobileResourceMeta(item) }}</span>
|
||||||
<strong>{{ formatFileSize(item.size) }}</strong>
|
<strong>{{ formatFileSize(item.size) }}</strong>
|
||||||
@@ -714,7 +731,7 @@ watch([titleFilter, siteFilter], () => {
|
|||||||
block-size: 100%;
|
block-size: 100%;
|
||||||
inline-size: 100%;
|
inline-size: 100%;
|
||||||
min-block-size: 0;
|
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);
|
background: var(--cache-mobile-page-bg);
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
}
|
}
|
||||||
@@ -820,12 +837,13 @@ watch([titleFilter, siteFilter], () => {
|
|||||||
background: var(--cache-mobile-surface-bg);
|
background: var(--cache-mobile-surface-bg);
|
||||||
box-shadow: 0 10px 30px rgba(var(--v-theme-on-surface), 0.07);
|
box-shadow: 0 10px 30px rgba(var(--v-theme-on-surface), 0.07);
|
||||||
gap: 14px;
|
gap: 14px;
|
||||||
grid-template-columns: 72px minmax(0, 1fr) 32px;
|
grid-template-columns: 72px minmax(0, 1fr);
|
||||||
margin-block-end: 12px;
|
margin-block-end: 12px;
|
||||||
padding: 14px;
|
padding: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cache-mobile-card__poster {
|
.cache-mobile-card__poster {
|
||||||
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -837,6 +855,14 @@ watch([titleFilter, siteFilter], () => {
|
|||||||
inline-size: 72px;
|
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 {
|
.cache-mobile-card__content {
|
||||||
min-inline-size: 0;
|
min-inline-size: 0;
|
||||||
}
|
}
|
||||||
@@ -847,6 +873,7 @@ watch([titleFilter, siteFilter], () => {
|
|||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
line-height: 1.35;
|
line-height: 1.35;
|
||||||
overflow-wrap: anywhere;
|
overflow-wrap: anywhere;
|
||||||
|
padding-inline-end: 34px;
|
||||||
white-space: normal;
|
white-space: normal;
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
@@ -854,8 +881,8 @@ watch([titleFilter, siteFilter], () => {
|
|||||||
.cache-mobile-card__main {
|
.cache-mobile-card__main {
|
||||||
margin-block-start: 6px;
|
margin-block-start: 6px;
|
||||||
color: rgba(var(--v-theme-on-surface), 0.88);
|
color: rgba(var(--v-theme-on-surface), 0.88);
|
||||||
font-size: 18px;
|
font-size: 15px;
|
||||||
font-weight: 800;
|
font-weight: 700;
|
||||||
line-height: 1.32;
|
line-height: 1.32;
|
||||||
overflow-wrap: anywhere;
|
overflow-wrap: anywhere;
|
||||||
white-space: normal;
|
white-space: normal;
|
||||||
@@ -865,25 +892,14 @@ watch([titleFilter, siteFilter], () => {
|
|||||||
.cache-mobile-card__main span {
|
.cache-mobile-card__main span {
|
||||||
margin-inline-start: 6px;
|
margin-inline-start: 6px;
|
||||||
color: rgba(var(--v-theme-on-surface), 0.58);
|
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-size: 14px;
|
||||||
font-weight: 600;
|
font-weight: 500;
|
||||||
gap: 8px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.cache-mobile-card__meta {
|
.cache-mobile-card__meta {
|
||||||
display: flex;
|
display: grid;
|
||||||
align-items: flex-end;
|
align-items: end;
|
||||||
justify-content: space-between;
|
grid-template-columns: minmax(0, 1fr) auto;
|
||||||
margin-block-start: 8px;
|
margin-block-start: 8px;
|
||||||
color: rgba(var(--v-theme-on-surface), 0.62);
|
color: rgba(var(--v-theme-on-surface), 0.62);
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
@@ -898,17 +914,18 @@ watch([titleFilter, siteFilter], () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.cache-mobile-card__meta strong {
|
.cache-mobile-card__meta strong {
|
||||||
flex: 0 0 auto;
|
|
||||||
color: rgba(var(--v-theme-on-surface), 0.62);
|
color: rgba(var(--v-theme-on-surface), 0.62);
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
|
text-align: end;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cache-mobile-card__menu {
|
.cache-mobile-card__menu {
|
||||||
align-self: center;
|
position: absolute;
|
||||||
color: rgba(var(--v-theme-on-surface), 0.5);
|
color: rgba(var(--v-theme-on-surface), 0.5);
|
||||||
justify-self: end;
|
inset-block-start: 8px;
|
||||||
|
inset-inline-end: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cache-mobile-load-state,
|
.cache-mobile-load-state,
|
||||||
@@ -960,7 +977,7 @@ html[data-theme='transparent'] .cache-mobile-page,
|
|||||||
}
|
}
|
||||||
|
|
||||||
.cache-mobile-card {
|
.cache-mobile-card {
|
||||||
grid-template-columns: 64px minmax(0, 1fr) 28px;
|
grid-template-columns: 64px minmax(0, 1fr);
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import api from '@/api'
|
|||||||
import type { ScheduleInfo } from '@/api/types'
|
import type { ScheduleInfo } from '@/api/types'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { useBackground } from '@/composables/useBackground'
|
import { useBackground } from '@/composables/useBackground'
|
||||||
import { useTheme } from 'vuetify'
|
|
||||||
|
|
||||||
// 移动端任务卡片视觉配置。
|
// 移动端任务卡片视觉配置。
|
||||||
type SchedulerMobileVisual = {
|
type SchedulerMobileVisual = {
|
||||||
@@ -23,7 +22,6 @@ type SchedulerMobileVisualRule = SchedulerMobileVisual & {
|
|||||||
// 国际化
|
// 国际化
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const { useDataRefresh } = useBackground()
|
const { useDataRefresh } = useBackground()
|
||||||
const theme = useTheme()
|
|
||||||
|
|
||||||
// 提示框
|
// 提示框
|
||||||
const $toast = useToast()
|
const $toast = useToast()
|
||||||
@@ -31,10 +29,6 @@ const $toast = useToast()
|
|||||||
// 定时服务列表
|
// 定时服务列表
|
||||||
const schedulerList = ref<ScheduleInfo[]>([])
|
const schedulerList = ref<ScheduleInfo[]>([])
|
||||||
|
|
||||||
// 当前是否为透明主题,用于限制透明适配只作用于本组件内部节点。
|
|
||||||
const isTransparentTheme = computed(() => theme.name.value === 'transparent')
|
|
||||||
const isDarkTheme = computed(() => theme.current.value.dark && !isTransparentTheme.value)
|
|
||||||
|
|
||||||
// 移动端任务图标按后端 job id 优先匹配,避免列表顺序变化导致图标看起来随机。
|
// 移动端任务图标按后端 job id 优先匹配,避免列表顺序变化导致图标看起来随机。
|
||||||
const schedulerMobileVisualRules: SchedulerMobileVisualRule[] = [
|
const schedulerMobileVisualRules: SchedulerMobileVisualRule[] = [
|
||||||
{ ids: ['cookiecloud'], names: ['CookieCloud'], icon: 'mdi-cloud-sync-outline', color: '#3f8cff', rgb: '63, 140, 255' },
|
{ ids: ['cookiecloud'], names: ['CookieCloud'], icon: 'mdi-cloud-sync-outline', color: '#3f8cff', rgb: '63, 140, 255' },
|
||||||
@@ -229,32 +223,18 @@ const { loading: schedulerLoading } = useDataRefresh(
|
|||||||
</tbody>
|
</tbody>
|
||||||
</VTable>
|
</VTable>
|
||||||
|
|
||||||
<div
|
<div v-else-if="!schedulerLoading" class="desktop-scheduler-empty">
|
||||||
v-else-if="!schedulerLoading"
|
|
||||||
class="desktop-scheduler-empty"
|
|
||||||
:class="{ 'scheduler-empty--transparent': isTransparentTheme }"
|
|
||||||
>
|
|
||||||
<VIcon icon="mdi-timer-off-outline" size="48" />
|
<VIcon icon="mdi-timer-off-outline" size="48" />
|
||||||
<p>{{ t('setting.scheduler.noService') }}</p>
|
<p>{{ t('setting.scheduler.noService') }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div v-else class="desktop-scheduler-empty">
|
||||||
v-else
|
|
||||||
class="desktop-scheduler-empty"
|
|
||||||
:class="{ 'scheduler-empty--transparent': isTransparentTheme }"
|
|
||||||
>
|
|
||||||
<VProgressCircular indeterminate color="primary" size="22" width="2" />
|
<VProgressCircular indeterminate color="primary" size="22" width="2" />
|
||||||
<p>{{ t('common.loadingText') }}</p>
|
<p>{{ t('common.loadingText') }}</p>
|
||||||
</div>
|
</div>
|
||||||
</VCard>
|
</VCard>
|
||||||
|
|
||||||
<div
|
<div class="mobile-scheduler-view d-md-none">
|
||||||
class="mobile-scheduler-view d-md-none"
|
|
||||||
:class="{
|
|
||||||
'mobile-scheduler-view--dark': isDarkTheme,
|
|
||||||
'mobile-scheduler-view--transparent': isTransparentTheme,
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<div v-if="mobileSchedulerCards.length" class="mobile-scheduler-list">
|
<div v-if="mobileSchedulerCards.length" class="mobile-scheduler-list">
|
||||||
<article
|
<article
|
||||||
v-for="{ scheduler, visual, statusText, statusVariant } in mobileSchedulerCards"
|
v-for="{ scheduler, visual, statusText, statusVariant } in mobileSchedulerCards"
|
||||||
@@ -291,11 +271,7 @@ const { loading: schedulerLoading } = useDataRefresh(
|
|||||||
</article>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div v-else-if="!schedulerLoading" class="mobile-scheduler-empty">
|
||||||
v-else-if="!schedulerLoading"
|
|
||||||
class="mobile-scheduler-empty"
|
|
||||||
:class="{ 'scheduler-empty--transparent': isTransparentTheme }"
|
|
||||||
>
|
|
||||||
<VIcon icon="mdi-timer-off-outline" size="44" />
|
<VIcon icon="mdi-timer-off-outline" size="44" />
|
||||||
<p>{{ t('setting.scheduler.noService') }}</p>
|
<p>{{ t('setting.scheduler.noService') }}</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -325,18 +301,10 @@ const { loading: schedulerLoading } = useDataRefresh(
|
|||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.scheduler-empty--transparent {
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mobile-scheduler-view {
|
.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%;
|
min-block-size: 100%;
|
||||||
padding: 12px 18px calc(22px + env(safe-area-inset-bottom));
|
padding: 12px 18px calc(22px + env(safe-area-inset-bottom));
|
||||||
background: var(--scheduler-mobile-view-bg);
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mobile-scheduler-list {
|
.mobile-scheduler-list {
|
||||||
@@ -351,8 +319,9 @@ const { loading: schedulerLoading } = useDataRefresh(
|
|||||||
padding: 18px;
|
padding: 18px;
|
||||||
border: 0;
|
border: 0;
|
||||||
border-radius: var(--app-surface-radius);
|
border-radius: var(--app-surface-radius);
|
||||||
background: var(--scheduler-mobile-card-bg);
|
background: rgb(var(--v-theme-surface));
|
||||||
box-shadow: var(--scheduler-mobile-card-shadow);
|
backdrop-filter: none;
|
||||||
|
box-shadow: none;
|
||||||
column-gap: 14px;
|
column-gap: 14px;
|
||||||
grid-template-columns: 62px minmax(0, 1fr) auto;
|
grid-template-columns: 62px minmax(0, 1fr) auto;
|
||||||
}
|
}
|
||||||
@@ -405,11 +374,11 @@ const { loading: schedulerLoading } = useDataRefresh(
|
|||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 6px 14px;
|
padding: 5px 10px;
|
||||||
border-radius: 999px;
|
border-radius: 999px;
|
||||||
background: rgba(var(--v-theme-on-surface), 0.06);
|
background: rgba(var(--v-theme-on-surface), 0.06);
|
||||||
color: rgba(var(--v-theme-on-surface), 0.62);
|
color: rgba(var(--v-theme-on-surface), 0.62);
|
||||||
font-size: 14px;
|
font-size: 12px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
@@ -475,19 +444,10 @@ const { loading: schedulerLoading } = useDataRefresh(
|
|||||||
gap: 10px;
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mobile-scheduler-view--transparent {
|
html[data-theme='transparent'] .mobile-scheduler-card,
|
||||||
--scheduler-mobile-card-bg: rgba(var(--v-theme-surface), var(--transparent-opacity-light, 0.2));
|
.v-theme--transparent .mobile-scheduler-card {
|
||||||
--scheduler-mobile-card-shadow: none;
|
background: rgba(var(--v-theme-surface), var(--transparent-opacity-light, 0.2));
|
||||||
--scheduler-mobile-view-bg: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mobile-scheduler-view--transparent .mobile-scheduler-card {
|
|
||||||
backdrop-filter: blur(var(--transparent-blur, 10px));
|
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) {
|
@media (max-width: 480px) {
|
||||||
@@ -519,8 +479,8 @@ const { loading: schedulerLoading } = useDataRefresh(
|
|||||||
}
|
}
|
||||||
|
|
||||||
.mobile-scheduler-status {
|
.mobile-scheduler-status {
|
||||||
padding: 6px 11px;
|
padding: 5px 9px;
|
||||||
font-size: 13px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mobile-scheduler-run-btn {
|
.mobile-scheduler-run-btn {
|
||||||
|
|||||||
Reference in New Issue
Block a user