mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-09-05 07:28:37 +08:00
refine settings card layouts
This commit is contained in:
@@ -101,19 +101,21 @@ function onClose() {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<VCard variant="tonal" @click="openRuleInfoDialog">
|
<VCard variant="tonal" class="app-card-shell" @click="openRuleInfoDialog">
|
||||||
<span class="absolute top-3 right-12">
|
<span class="absolute top-3 right-12">
|
||||||
<IconBtn>
|
<IconBtn>
|
||||||
<VIcon class="cursor-move" icon="mdi-drag" />
|
<VIcon class="cursor-move" icon="mdi-drag" />
|
||||||
</IconBtn>
|
</IconBtn>
|
||||||
</span>
|
</span>
|
||||||
<VDialogCloseBtn @click="onClose" />
|
<VDialogCloseBtn @click="onClose" />
|
||||||
<VCardText class="flex justify-space-between align-center gap-3">
|
<VCardText class="app-card-summary app-card-summary--double-action app-card-summary--title-subtitle">
|
||||||
<div class="align-self-start">
|
<div class="app-card-summary__content">
|
||||||
<h5 class="text-h6 mb-1">{{ props.rule.name }}</h5>
|
<h5 class="app-card-summary__title text-h6">{{ props.rule.name }}</h5>
|
||||||
<div class="text-body-1 mb-3">{{ props.rule.id }}</div>
|
<div class="app-card-summary__subtitle text-body-1">{{ props.rule.id }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="app-card-summary__media" aria-hidden="true">
|
||||||
|
<VImg :src="filter_svg" contain class="app-card-summary__image" />
|
||||||
</div>
|
</div>
|
||||||
<VImg :src="filter_svg" cover class="mt-7" max-width="3rem" />
|
|
||||||
</VCardText>
|
</VCardText>
|
||||||
</VCard>
|
</VCard>
|
||||||
<VDialog
|
<VDialog
|
||||||
|
|||||||
@@ -195,7 +195,7 @@ watch(
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<VCard variant="tonal" :width="props.width" :height="props.height">
|
<VCard variant="tonal" class="app-card-shell" :width="props.width" :height="props.height">
|
||||||
<VDialogCloseBtn @click="onClose" />
|
<VDialogCloseBtn @click="onClose" />
|
||||||
<VCardItem>
|
<VCardItem>
|
||||||
<VTextField
|
<VTextField
|
||||||
|
|||||||
@@ -252,6 +252,7 @@ onUnmounted(() => {
|
|||||||
<VCard
|
<VCard
|
||||||
v-bind="hover.props"
|
v-bind="hover.props"
|
||||||
variant="tonal"
|
variant="tonal"
|
||||||
|
class="app-card-shell"
|
||||||
@click="openDownloaderInfoDialog"
|
@click="openDownloaderInfoDialog"
|
||||||
:class="{ 'transition transform-cpu duration-300 -translate-y-1': hover.isHovering }"
|
:class="{ 'transition transform-cpu duration-300 -translate-y-1': hover.isHovering }"
|
||||||
>
|
>
|
||||||
@@ -261,9 +262,9 @@ onUnmounted(() => {
|
|||||||
<VIcon class="cursor-move" icon="mdi-drag" />
|
<VIcon class="cursor-move" icon="mdi-drag" />
|
||||||
</IconBtn>
|
</IconBtn>
|
||||||
</span>
|
</span>
|
||||||
<VCardText class="flex justify-space-between align-center gap-4">
|
<VCardText class="app-card-summary app-card-summary--double-action">
|
||||||
<div class="align-self-start flex-1">
|
<div class="app-card-summary__content">
|
||||||
<div class="flex items-center">
|
<div class="app-card-summary__title-row">
|
||||||
<VBadge
|
<VBadge
|
||||||
v-if="props.downloader.default && props.downloader.enabled"
|
v-if="props.downloader.default && props.downloader.enabled"
|
||||||
dot
|
dot
|
||||||
@@ -271,18 +272,21 @@ onUnmounted(() => {
|
|||||||
color="success"
|
color="success"
|
||||||
class="me-1"
|
class="me-1"
|
||||||
/>
|
/>
|
||||||
<span class="text-h6">{{ downloader.name }}</span>
|
<span class="app-card-summary__title text-h6">{{ downloader.name }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="downloaderDict[downloader.type] && props.downloader.enabled" class="mt-1 flex flex-wrap text-sm">
|
<div
|
||||||
<span class="me-2">{{ `↑ ${formatFileSize(upload_rate, 1)}/s ` }}</span>
|
v-if="downloaderDict[downloader.type] && props.downloader.enabled"
|
||||||
<span>{{ `↓ ${formatFileSize(download_rate, 1)}/s` }}</span>
|
class="app-card-summary__meta text-sm"
|
||||||
|
>
|
||||||
|
<span class="app-card-summary__meta-item">{{ `↑ ${formatFileSize(upload_rate, 1)}/s` }}</span>
|
||||||
|
<span class="app-card-summary__meta-item">{{ `↓ ${formatFileSize(download_rate, 1)}/s` }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="!downloaderDict[downloader.type]" class="mt-1 flex flex-wrap text-sm">
|
<div v-else-if="!downloaderDict[downloader.type]" class="app-card-summary__subtitle text-sm">
|
||||||
<span class="me-2">自定义下载器</span>
|
自定义下载器
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="h-20">
|
<div class="app-card-summary__media" aria-hidden="true">
|
||||||
<VImg :src="getIcon" cover class="mt-8 me-3" max-width="3rem" min-width="3rem" />
|
<VImg :src="getIcon" contain class="app-card-summary__image" />
|
||||||
</div>
|
</div>
|
||||||
</VCardText>
|
</VCardText>
|
||||||
</VCard>
|
</VCard>
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ onMounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<VCard variant="tonal">
|
<VCard variant="tonal" class="app-card-shell">
|
||||||
<span class="absolute top-3 right-12">
|
<span class="absolute top-3 right-12">
|
||||||
<IconBtn>
|
<IconBtn>
|
||||||
<VIcon class="cursor-move" icon="mdi-drag" />
|
<VIcon class="cursor-move" icon="mdi-drag" />
|
||||||
@@ -53,7 +53,7 @@ onMounted(() => {
|
|||||||
</span>
|
</span>
|
||||||
<VDialogCloseBtn @click="onClose" />
|
<VDialogCloseBtn @click="onClose" />
|
||||||
<VCardItem>
|
<VCardItem>
|
||||||
<VCardTitle>{{ t('filterRule.priority') }} {{ props.pri }}</VCardTitle>
|
<VCardTitle class="pr-8">{{ t('filterRule.priority') }} {{ props.pri }}</VCardTitle>
|
||||||
<VRow>
|
<VRow>
|
||||||
<VCol>
|
<VCol>
|
||||||
<VAutocomplete
|
<VAutocomplete
|
||||||
|
|||||||
@@ -205,22 +205,24 @@ function onClose() {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<VCard variant="tonal" @click="opengroupInfoDialog">
|
<VCard variant="tonal" class="app-card-shell" @click="opengroupInfoDialog">
|
||||||
<span class="absolute top-3 right-12">
|
<span class="absolute top-3 right-12">
|
||||||
<IconBtn>
|
<IconBtn>
|
||||||
<VIcon class="cursor-move" icon="mdi-drag" />
|
<VIcon class="cursor-move" icon="mdi-drag" />
|
||||||
</IconBtn>
|
</IconBtn>
|
||||||
</span>
|
</span>
|
||||||
<VDialogCloseBtn @click="onClose" />
|
<VDialogCloseBtn @click="onClose" />
|
||||||
<VCardText class="flex justify-space-between align-center gap-3">
|
<VCardText class="app-card-summary app-card-summary--double-action app-card-summary--title-subtitle">
|
||||||
<div class="align-self-start">
|
<div class="app-card-summary__content">
|
||||||
<h5 class="text-h6 mb-1">{{ props.group.name }}</h5>
|
<h5 class="app-card-summary__title text-h6">{{ props.group.name }}</h5>
|
||||||
<div class="text-body-1 mb-3">
|
<div class="app-card-summary__subtitle text-body-1">
|
||||||
<span v-if="!props.group.category">{{ props.group.media_type || t('common.all') }}</span>
|
<span v-if="!props.group.category">{{ props.group.media_type || t('common.all') }}</span>
|
||||||
<span v-else>{{ props.group.category }}</span>
|
<span v-else>{{ props.group.category }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<VImg :src="filter_group_svg" cover class="mt-10" max-width="3rem" />
|
<div class="app-card-summary__media" aria-hidden="true">
|
||||||
|
<VImg :src="filter_group_svg" contain class="app-card-summary__image" />
|
||||||
|
</div>
|
||||||
</VCardText>
|
</VCardText>
|
||||||
</VCard>
|
</VCard>
|
||||||
<VDialog
|
<VDialog
|
||||||
|
|||||||
@@ -199,21 +199,27 @@ onMounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<VCard variant="tonal" @click="openMediaServerInfoDialog">
|
<VCard variant="tonal" class="app-card-shell" @click="openMediaServerInfoDialog">
|
||||||
<VDialogCloseBtn @click="onClose" />
|
<VDialogCloseBtn @click="onClose" />
|
||||||
<VCardText class="flex justify-space-between align-center gap-3">
|
<VCardText class="app-card-summary app-card-summary--single-action">
|
||||||
<div class="align-self-start flex-1">
|
<div class="app-card-summary__content">
|
||||||
<div class="text-h6 mb-1">{{ mediaserver.name }}</div>
|
<div class="app-card-summary__title text-h6">{{ mediaserver.name }}</div>
|
||||||
<div v-if="mediaServerDict[mediaserver.type] && mediaserver.enabled" class="text-sm mt-5 flex flex-wrap">
|
<div
|
||||||
<span v-for="item in infoItems" :key="item.title" class="me-2 mb-1">
|
v-if="mediaServerDict[mediaserver.type] && mediaserver.enabled"
|
||||||
<VIcon rounded :icon="item.avatar" class="me-1" />{{ item.amount }}
|
class="grid min-h-6 grid-cols-3 gap-2 text-sm text-medium-emphasis"
|
||||||
|
>
|
||||||
|
<span v-for="item in infoItems" :key="item.title" class="flex min-w-0 items-center">
|
||||||
|
<VIcon rounded :icon="item.avatar" class="me-1 shrink-0" />
|
||||||
|
<span class="truncate">{{ item.amount }}</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="!mediaServerDict[mediaserver.type]" class="text-sm mt-5 flex flex-wrap">
|
<div v-else-if="!mediaServerDict[mediaserver.type]" class="app-card-summary__subtitle text-sm">
|
||||||
<span class="me-2 mb-1">自定义媒体服务器</span>
|
自定义媒体服务器
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<VImg :src="getIcon" class="mt-8 me-3 max-h-12" max-width="3rem" min-width="3rem" />
|
<div class="app-card-summary__media" aria-hidden="true">
|
||||||
|
<VImg :src="getIcon" contain class="app-card-summary__image" />
|
||||||
|
</div>
|
||||||
</VCardText>
|
</VCardText>
|
||||||
</VCard>
|
</VCard>
|
||||||
|
|
||||||
|
|||||||
@@ -153,22 +153,24 @@ function onClose() {
|
|||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<VCard variant="tonal" @click="openNotificationInfoDialog">
|
<VCard variant="tonal" class="app-card-shell" @click="openNotificationInfoDialog">
|
||||||
<span class="absolute top-3 right-12">
|
<span class="absolute top-3 right-12">
|
||||||
<IconBtn>
|
<IconBtn>
|
||||||
<VIcon class="cursor-move" icon="mdi-drag" />
|
<VIcon class="cursor-move" icon="mdi-drag" />
|
||||||
</IconBtn>
|
</IconBtn>
|
||||||
</span>
|
</span>
|
||||||
<VDialogCloseBtn @click="onClose" />
|
<VDialogCloseBtn @click="onClose" />
|
||||||
<VCardText class="flex justify-space-between align-center gap-3">
|
<VCardText class="app-card-summary app-card-summary--double-action">
|
||||||
<div class="align-self-start">
|
<div class="app-card-summary__content">
|
||||||
<div class="flex items-center">
|
<div class="app-card-summary__title-row">
|
||||||
<VBadge v-if="props.notification.enabled" dot inline color="success" class="me-1" />
|
<VBadge v-if="props.notification.enabled" dot inline color="success" class="me-1" />
|
||||||
<span class="text-h6">{{ props.notification.name }}</span>
|
<span class="app-card-summary__title text-h6">{{ props.notification.name }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-body-1 mb-3">{{ notificationTypeNames[notification.type] }}</div>
|
<div class="app-card-summary__subtitle text-body-1">{{ notificationTypeNames[notification.type] }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="app-card-summary__media" aria-hidden="true">
|
||||||
|
<VImg :src="getIcon" contain class="app-card-summary__image" />
|
||||||
</div>
|
</div>
|
||||||
<VImg :src="getIcon" cover class="mt-7 me-1" max-width="3rem" />
|
|
||||||
</VCardText>
|
</VCardText>
|
||||||
</VCard>
|
</VCard>
|
||||||
|
|
||||||
|
|||||||
@@ -216,11 +216,17 @@ onMounted(() => {
|
|||||||
<div v-if="cardProps.site?.is_active" class="site-status-indicator" :class="statColor"></div>
|
<div v-if="cardProps.site?.is_active" class="site-status-indicator" :class="statColor"></div>
|
||||||
|
|
||||||
<!-- 主体部分 -->
|
<!-- 主体部分 -->
|
||||||
<div class="relative flex-1 flex flex-col p-3 z-1">
|
<div class="relative z-1 flex flex-1 flex-col p-3 pr-12">
|
||||||
<!-- 顶部:图标和站点名称 -->
|
<!-- 顶部:图标和站点名称 -->
|
||||||
<div class="flex items-center mb-1">
|
<div class="mb-1 flex min-w-0 items-center gap-2">
|
||||||
<!-- 站点图标 -->
|
<!-- 站点图标 -->
|
||||||
<VAvatar tile rounded="lg" size="32" class="me-2" :class="{ 'cursor-move': display.mdAndUp.value }">
|
<VAvatar
|
||||||
|
tile
|
||||||
|
rounded="lg"
|
||||||
|
size="32"
|
||||||
|
class="shrink-0"
|
||||||
|
:class="{ 'cursor-move': display.mdAndUp.value }"
|
||||||
|
>
|
||||||
<VImg :src="siteIcon" class="w-full h-full" :alt="cardProps.site?.name" cover>
|
<VImg :src="siteIcon" class="w-full h-full" :alt="cardProps.site?.name" cover>
|
||||||
<template #placeholder>
|
<template #placeholder>
|
||||||
<div class="w-full h-full">
|
<div class="w-full h-full">
|
||||||
@@ -231,11 +237,11 @@ onMounted(() => {
|
|||||||
</VAvatar>
|
</VAvatar>
|
||||||
|
|
||||||
<!-- 站点名称和特性图标 -->
|
<!-- 站点名称和特性图标 -->
|
||||||
<div class="flex-1 min-w-0 flex items-center">
|
<div class="flex min-w-0 flex-1 items-center gap-2">
|
||||||
<h3 class="text-lg font-semibold leading-tight truncate">{{ cardProps.site?.name }}</h3>
|
<h3 class="min-w-0 flex-1 truncate text-lg font-semibold leading-tight">{{ cardProps.site?.name }}</h3>
|
||||||
|
|
||||||
<!-- 站点特性图标 -->
|
<!-- 站点特性图标 -->
|
||||||
<div class="flex items-center gap-2 ml-auto mr-10">
|
<div class="ml-auto flex shrink-0 items-center gap-2">
|
||||||
<div v-if="cardProps.site?.limit_interval" class="hover:bg-primary/8 transition-colors">
|
<div v-if="cardProps.site?.limit_interval" class="hover:bg-primary/8 transition-colors">
|
||||||
<VIcon icon="mdi-speedometer" size="16" color="primary" class="opacity-85 hover:opacity-100" />
|
<VIcon icon="mdi-speedometer" size="16" color="primary" class="opacity-85 hover:opacity-100" />
|
||||||
</div>
|
</div>
|
||||||
@@ -254,7 +260,7 @@ onMounted(() => {
|
|||||||
|
|
||||||
<!-- 中间部分:网址 -->
|
<!-- 中间部分:网址 -->
|
||||||
<div class="my-3">
|
<div class="my-3">
|
||||||
<div class="text-sm text-medium-emphasis truncate" @click.stop="openSitePage">
|
<div class="min-w-0 truncate text-sm text-medium-emphasis" @click.stop="openSitePage">
|
||||||
{{ cardProps.site?.url }}
|
{{ cardProps.site?.url }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -48,6 +48,133 @@ html.v-overlay-scroll-blocked body {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 应用类信息卡片:固定右侧媒体槽位,避免图片被左侧文字挤压变形
|
||||||
|
.app-card-shell {
|
||||||
|
block-size: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-card-summary {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: stretch;
|
||||||
|
justify-content: flex-start;
|
||||||
|
overflow: hidden;
|
||||||
|
min-block-size: 7.5rem;
|
||||||
|
block-size: 7.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-card-summary__content {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
display: flex;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
min-inline-size: 0;
|
||||||
|
row-gap: 0.25rem;
|
||||||
|
padding-block: 0.25rem 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-card-summary__title-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
min-inline-size: 0;
|
||||||
|
column-gap: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-card-summary__subtitle,
|
||||||
|
.app-card-summary__meta-item {
|
||||||
|
min-inline-size: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-card-summary__title {
|
||||||
|
display: -webkit-box;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
min-inline-size: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: normal;
|
||||||
|
word-break: break-word;
|
||||||
|
line-height: 1.35;
|
||||||
|
max-block-size: calc(1.35em * 2);
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
line-clamp: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-card-summary__title-row .app-card-summary__title {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-card-summary__meta {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
min-inline-size: 0;
|
||||||
|
min-block-size: 1.5rem;
|
||||||
|
column-gap: 0.5rem;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-card-summary--single-action .app-card-summary__content {
|
||||||
|
padding-inline-end: 3.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-card-summary--double-action .app-card-summary__content {
|
||||||
|
padding-inline-end: 5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-card-summary--title-subtitle {
|
||||||
|
padding-block: 0.75rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-card-summary--title-subtitle .app-card-summary__content {
|
||||||
|
justify-content: space-between;
|
||||||
|
block-size: 100%;
|
||||||
|
padding-block: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-card-summary--title-subtitle .app-card-summary__title {
|
||||||
|
flex: 0 1 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-card-summary--title-subtitle .app-card-summary__subtitle {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-card-summary__media {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
justify-content: flex-end;
|
||||||
|
pointer-events: none;
|
||||||
|
filter: brightness(1.35) saturate(1.05);
|
||||||
|
inset-block-end: 0.75rem;
|
||||||
|
inset-inline-end: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-card-summary--single-action .app-card-summary__media,
|
||||||
|
.app-card-summary--double-action .app-card-summary__media {
|
||||||
|
inset-inline-end: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-card-summary__image {
|
||||||
|
flex-shrink: 0;
|
||||||
|
inline-size: 3.5rem;
|
||||||
|
min-inline-size: 3.5rem;
|
||||||
|
max-inline-size: 3.5rem;
|
||||||
|
block-size: 3.5rem;
|
||||||
|
min-block-size: 3.5rem;
|
||||||
|
max-block-size: 3.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-card-summary__image .v-img__img {
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
// Toast通知样式
|
// Toast通知样式
|
||||||
.Vue-Toastification__container {
|
.Vue-Toastification__container {
|
||||||
z-index: 2500;
|
z-index: 2500;
|
||||||
|
|||||||
Reference in New Issue
Block a user