mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-09-06 08:06:43 +08:00
feat(glass): 插件卡片改为染色玻璃
将插件卡片与市场卡片头部的实色背景改为变量驱动的染色层: 图标主色只作为透光色相,玻璃主题下叠加吸收遮罩与镜面高光, 并按 frosted/tinted 材质与透明度调整强度;文件夹卡片渐变底 同步降低不透明度让卡片本体玻璃透出。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
import api from '@/api'
|
import api from '@/api'
|
||||||
import type { Plugin } from '@/api/types'
|
import type { Plugin } from '@/api/types'
|
||||||
import { getLogoUrl } from '@/utils/imageUtils'
|
import { getLogoUrl } from '@/utils/imageUtils'
|
||||||
import { getDominantColor } from '@/@core/utils/image'
|
import { getCardAccentRgbFromImage } from '@/composables/useCardAccentColor'
|
||||||
import { isNullOrEmptyObject } from '@/@core/utils'
|
import { isNullOrEmptyObject } from '@/@core/utils'
|
||||||
import { formatDownloadCount } from '@/@core/utils/formatters'
|
import { formatDownloadCount } from '@/@core/utils/formatters'
|
||||||
import { useToast } from 'vue-toastification'
|
import { useToast } from 'vue-toastification'
|
||||||
@@ -35,8 +35,8 @@ const $toast = useToast()
|
|||||||
|
|
||||||
const createConfirm = useConfirm()
|
const createConfirm = useConfirm()
|
||||||
|
|
||||||
// 背景颜色
|
// 卡片头部染色所用的图标主色(CSS 变量可直接消费的 RGB 通道值)
|
||||||
const backgroundColor = ref('#28A9E1')
|
const accentRgb = ref('40, 169, 225')
|
||||||
|
|
||||||
// 图片对象
|
// 图片对象
|
||||||
const imageRef = ref<any>()
|
const imageRef = ref<any>()
|
||||||
@@ -76,8 +76,8 @@ function closeInstallProgress() {
|
|||||||
async function imageLoaded() {
|
async function imageLoaded() {
|
||||||
isImageLoaded.value = true
|
isImageLoaded.value = true
|
||||||
const imageElement = imageRef.value?.$el.querySelector('img') as HTMLImageElement
|
const imageElement = imageRef.value?.$el.querySelector('img') as HTMLImageElement
|
||||||
// 从图片中提取背景色
|
// 从图标中提取主色,作为卡片头部染色玻璃的色相来源
|
||||||
backgroundColor.value = await getDominantColor(imageElement)
|
accentRgb.value = await getCardAccentRgbFromImage(imageElement, '#28A9E1')
|
||||||
}
|
}
|
||||||
|
|
||||||
// 计算图标路径
|
// 计算图标路径
|
||||||
@@ -236,15 +236,13 @@ onUnmounted(() => {
|
|||||||
:width="props.width"
|
:width="props.width"
|
||||||
:height="props.height"
|
:height="props.height"
|
||||||
@click="showPluginDetail"
|
@click="showPluginDetail"
|
||||||
class="app-hover-lift-card flex flex-col h-full"
|
class="plugin-card app-hover-lift-card flex flex-col h-full"
|
||||||
:class="{
|
:class="{
|
||||||
'app-hover-lift-card--hovering': hover.isHovering,
|
'app-hover-lift-card--hovering': hover.isHovering,
|
||||||
}"
|
}"
|
||||||
|
:style="{ '--plugin-card-accent-rgb': accentRgb }"
|
||||||
>
|
>
|
||||||
<div
|
<div class="plugin-card__banner flex-grow">
|
||||||
class="flex-grow"
|
|
||||||
:style="`background: linear-gradient(rgba(0, 0, 0, 0.6) 0%, rgba(0, 0, 0, 0.5) 100%), linear-gradient(${backgroundColor} 0%, ${backgroundColor} 100%)`"
|
|
||||||
>
|
|
||||||
<VCardText class="px-2 pt-2 pb-0">
|
<VCardText class="px-2 pt-2 pb-0">
|
||||||
<VCardTitle
|
<VCardTitle
|
||||||
class="text-white px-2 pb-0 text-lg text-shadow whitespace-nowrap overflow-hidden text-ellipsis"
|
class="text-white px-2 pb-0 text-lg text-shadow whitespace-nowrap overflow-hidden text-ellipsis"
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { useConfirm } from '@/composables/useConfirm'
|
|||||||
import api from '@/api'
|
import api from '@/api'
|
||||||
import type { Plugin } from '@/api/types'
|
import type { Plugin } from '@/api/types'
|
||||||
import { getLogoUrl } from '@/utils/imageUtils'
|
import { getLogoUrl } from '@/utils/imageUtils'
|
||||||
import { getDominantColor } from '@/@core/utils/image'
|
import { getCardAccentRgbFromImage } from '@/composables/useCardAccentColor'
|
||||||
import { formatDownloadCount } from '@/@core/utils/formatters'
|
import { formatDownloadCount } from '@/@core/utils/formatters'
|
||||||
import { useDisplay } from 'vuetify'
|
import { useDisplay } from 'vuetify'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
@@ -40,8 +40,8 @@ const { t } = useI18n()
|
|||||||
// 显示器宽度
|
// 显示器宽度
|
||||||
const display = useDisplay()
|
const display = useDisplay()
|
||||||
|
|
||||||
// 背景颜色
|
// 卡片头部染色所用的图标主色(CSS 变量可直接消费的 RGB 通道值)
|
||||||
const backgroundColor = ref('#28A9E1')
|
const accentRgb = ref('40, 169, 225')
|
||||||
|
|
||||||
// 图片对象
|
// 图片对象
|
||||||
const imageRef = ref<any>()
|
const imageRef = ref<any>()
|
||||||
@@ -98,8 +98,8 @@ watch(
|
|||||||
async function imageLoaded() {
|
async function imageLoaded() {
|
||||||
isImageLoaded.value = true
|
isImageLoaded.value = true
|
||||||
const imageElement = imageRef.value?.$el.querySelector('img') as HTMLImageElement
|
const imageElement = imageRef.value?.$el.querySelector('img') as HTMLImageElement
|
||||||
// 从图片中提取背景色
|
// 从图标中提取主色,作为卡片头部染色玻璃的色相来源
|
||||||
backgroundColor.value = await getDominantColor(imageElement)
|
accentRgb.value = await getCardAccentRgbFromImage(imageElement, '#28A9E1')
|
||||||
}
|
}
|
||||||
|
|
||||||
// 显示更新日志
|
// 显示更新日志
|
||||||
@@ -573,17 +573,15 @@ watch(
|
|||||||
:width="props.width"
|
:width="props.width"
|
||||||
:height="props.height"
|
:height="props.height"
|
||||||
@click="handleCardClick"
|
@click="handleCardClick"
|
||||||
class="app-hover-lift-card flex flex-col h-full"
|
class="plugin-card app-hover-lift-card flex flex-col h-full"
|
||||||
:class="{
|
:class="{
|
||||||
'app-hover-lift-card--hovering': hover.isHovering && !props.sortable,
|
'app-hover-lift-card--hovering': hover.isHovering && !props.sortable,
|
||||||
'cursor-move': props.sortable,
|
'cursor-move': props.sortable,
|
||||||
}"
|
}"
|
||||||
|
:style="{ '--plugin-card-accent-rgb': accentRgb }"
|
||||||
:ripple="!props.sortable"
|
:ripple="!props.sortable"
|
||||||
>
|
>
|
||||||
<div
|
<div class="plugin-card__banner flex-grow">
|
||||||
class="flex-grow"
|
|
||||||
:style="`background: linear-gradient(rgba(0, 0, 0, 0.6) 0%, rgba(0, 0, 0, 0.5) 100%), linear-gradient(${backgroundColor} 0%, ${backgroundColor} 100%)`"
|
|
||||||
>
|
|
||||||
<VCardText class="px-2 pt-2 pb-0">
|
<VCardText class="px-2 pt-2 pb-0">
|
||||||
<VCardTitle
|
<VCardTitle
|
||||||
class="text-white px-2 pb-0 text-lg text-shadow whitespace-nowrap overflow-hidden text-ellipsis"
|
class="text-white px-2 pb-0 text-lg text-shadow whitespace-nowrap overflow-hidden text-ellipsis"
|
||||||
|
|||||||
@@ -1338,6 +1338,22 @@ html[data-theme='transparent'].transparent-glass-realtime .v-theme--transparent
|
|||||||
grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr));
|
grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 插件卡片头部染色层:普通主题保持原有实色底+压暗遮罩,玻璃主题只替换色层与材质。
|
||||||
|
.plugin-card__banner {
|
||||||
|
position: relative;
|
||||||
|
-webkit-backdrop-filter: var(--plugin-card-banner-backdrop-filter, none);
|
||||||
|
backdrop-filter: var(--plugin-card-banner-backdrop-filter, none);
|
||||||
|
background-image: var(--plugin-card-banner-scrim), var(--plugin-card-banner-tint);
|
||||||
|
transition: background-image 0.2s ease;
|
||||||
|
|
||||||
|
--plugin-card-accent-rgb: 40, 169, 225;
|
||||||
|
--plugin-card-banner-scrim: linear-gradient(rgba(0, 0, 0, 60%) 0%, rgba(0, 0, 0, 50%) 100%);
|
||||||
|
--plugin-card-banner-tint: linear-gradient(
|
||||||
|
rgb(var(--plugin-card-accent-rgb)) 0%,
|
||||||
|
rgb(var(--plugin-card-accent-rgb)) 100%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
.grid-downloading-card {
|
.grid-downloading-card {
|
||||||
grid-template-columns: repeat(auto-fill, minmax(20rem, 1fr));
|
grid-template-columns: repeat(auto-fill, minmax(20rem, 1fr));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -760,6 +760,81 @@ html[data-theme='glass'] {
|
|||||||
backdrop-filter: var(--glass-surface-backdrop-filter);
|
backdrop-filter: var(--glass-surface-backdrop-filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 插件卡片头部改为染色玻璃:图标主色只作为透光色相,压暗遮罩换成玻璃吸收与高光。
|
||||||
|
.plugin-card__banner {
|
||||||
|
border-block-end: 1px solid var(--glass-border);
|
||||||
|
|
||||||
|
--plugin-card-banner-scrim: linear-gradient(
|
||||||
|
rgba(11, 19, 34, calc(0.34 - var(--glass-transparency, 0.5) * 0.14)) 0%,
|
||||||
|
rgba(11, 19, 34, calc(0.46 - var(--glass-transparency, 0.5) * 0.16)) 100%
|
||||||
|
);
|
||||||
|
--plugin-card-banner-tint: linear-gradient(
|
||||||
|
135deg,
|
||||||
|
rgba(var(--plugin-card-accent-rgb), 0.42) 0%,
|
||||||
|
rgba(var(--plugin-card-accent-rgb), 0.24) 58%,
|
||||||
|
rgba(var(--plugin-card-accent-rgb), 0.12) 100%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 卡片主体已经承担实时背景采样,头部只叠加静态色层与镜面高光。
|
||||||
|
.plugin-card__banner::before {
|
||||||
|
position: absolute;
|
||||||
|
background-image: var(--glass-sheen);
|
||||||
|
box-shadow: inset 0 1px 0 var(--glass-highlight);
|
||||||
|
content: '';
|
||||||
|
inset: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plugin-card__banner > * {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 磨砂材质透光更强,染色需要同步减弱以免头部压过卡片本体。
|
||||||
|
&[data-glass-appearance='frosted'] .plugin-card__banner {
|
||||||
|
--plugin-card-banner-scrim: linear-gradient(
|
||||||
|
rgba(11, 19, 34, calc(0.24 - var(--glass-transparency, 0.5) * 0.12)) 0%,
|
||||||
|
rgba(11, 19, 34, calc(0.34 - var(--glass-transparency, 0.5) * 0.14)) 100%
|
||||||
|
);
|
||||||
|
--plugin-card-banner-tint: linear-gradient(
|
||||||
|
135deg,
|
||||||
|
rgba(var(--plugin-card-accent-rgb), 0.32) 0%,
|
||||||
|
rgba(var(--plugin-card-accent-rgb), 0.18) 58%,
|
||||||
|
rgba(var(--plugin-card-accent-rgb), 0.08) 100%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 色调材质本身带主色语义,头部染色向主色收敛保持整页同一色温。
|
||||||
|
&[data-glass-appearance='tinted'] .plugin-card__banner {
|
||||||
|
--plugin-card-banner-tint: linear-gradient(
|
||||||
|
135deg,
|
||||||
|
color-mix(in srgb, rgba(var(--plugin-card-accent-rgb), 0.42) 74%, rgba(var(--v-theme-primary), 0.42)) 0%,
|
||||||
|
color-mix(in srgb, rgba(var(--plugin-card-accent-rgb), 0.24) 74%, rgba(var(--v-theme-primary), 0.24)) 58%,
|
||||||
|
rgba(var(--plugin-card-accent-rgb), 0.12) 100%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 文件夹卡片保留用户自选渐变作为色相,只降低不透明度让卡片本体的玻璃透出来。
|
||||||
|
.plugin-folder-card__bg {
|
||||||
|
opacity: calc(0.5 - var(--glass-transparency, 0.5) * 0.14);
|
||||||
|
}
|
||||||
|
|
||||||
|
.plugin-folder-card__bg::after {
|
||||||
|
position: absolute;
|
||||||
|
background-image: var(--glass-sheen);
|
||||||
|
box-shadow: inset 0 1px 0 var(--glass-highlight);
|
||||||
|
content: '';
|
||||||
|
inset: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plugin-folder-card__overlay {
|
||||||
|
background: linear-gradient(
|
||||||
|
rgba(11, 19, 34, calc(0.34 - var(--glass-transparency, 0.5) * 0.14)) 0%,
|
||||||
|
rgba(11, 19, 34, calc(0.46 - var(--glass-transparency, 0.5) * 0.16)) 100%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// 捷径菜单的嵌套卡片会被通用弹层模糊覆盖,需要显式跟随玻璃材质。
|
// 捷径菜单的嵌套卡片会被通用弹层模糊覆盖,需要显式跟随玻璃材质。
|
||||||
.shortcut-card-hover-area > .v-card {
|
.shortcut-card-hover-area > .v-card {
|
||||||
-webkit-backdrop-filter: var(--glass-surface-backdrop-filter) !important;
|
-webkit-backdrop-filter: var(--glass-surface-backdrop-filter) !important;
|
||||||
|
|||||||
Reference in New Issue
Block a user