mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-09-08 09:16:58 +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 type { Plugin } from '@/api/types'
|
||||
import { getLogoUrl } from '@/utils/imageUtils'
|
||||
import { getDominantColor } from '@/@core/utils/image'
|
||||
import { getCardAccentRgbFromImage } from '@/composables/useCardAccentColor'
|
||||
import { isNullOrEmptyObject } from '@/@core/utils'
|
||||
import { formatDownloadCount } from '@/@core/utils/formatters'
|
||||
import { useToast } from 'vue-toastification'
|
||||
@@ -35,8 +35,8 @@ const $toast = useToast()
|
||||
|
||||
const createConfirm = useConfirm()
|
||||
|
||||
// 背景颜色
|
||||
const backgroundColor = ref('#28A9E1')
|
||||
// 卡片头部染色所用的图标主色(CSS 变量可直接消费的 RGB 通道值)
|
||||
const accentRgb = ref('40, 169, 225')
|
||||
|
||||
// 图片对象
|
||||
const imageRef = ref<any>()
|
||||
@@ -76,8 +76,8 @@ function closeInstallProgress() {
|
||||
async function imageLoaded() {
|
||||
isImageLoaded.value = true
|
||||
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"
|
||||
:height="props.height"
|
||||
@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="{
|
||||
'app-hover-lift-card--hovering': hover.isHovering,
|
||||
}"
|
||||
:style="{ '--plugin-card-accent-rgb': accentRgb }"
|
||||
>
|
||||
<div
|
||||
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%)`"
|
||||
>
|
||||
<div class="plugin-card__banner flex-grow">
|
||||
<VCardText class="px-2 pt-2 pb-0">
|
||||
<VCardTitle
|
||||
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 type { Plugin } from '@/api/types'
|
||||
import { getLogoUrl } from '@/utils/imageUtils'
|
||||
import { getDominantColor } from '@/@core/utils/image'
|
||||
import { getCardAccentRgbFromImage } from '@/composables/useCardAccentColor'
|
||||
import { formatDownloadCount } from '@/@core/utils/formatters'
|
||||
import { useDisplay } from 'vuetify'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
@@ -40,8 +40,8 @@ const { t } = useI18n()
|
||||
// 显示器宽度
|
||||
const display = useDisplay()
|
||||
|
||||
// 背景颜色
|
||||
const backgroundColor = ref('#28A9E1')
|
||||
// 卡片头部染色所用的图标主色(CSS 变量可直接消费的 RGB 通道值)
|
||||
const accentRgb = ref('40, 169, 225')
|
||||
|
||||
// 图片对象
|
||||
const imageRef = ref<any>()
|
||||
@@ -98,8 +98,8 @@ watch(
|
||||
async function imageLoaded() {
|
||||
isImageLoaded.value = true
|
||||
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"
|
||||
:height="props.height"
|
||||
@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="{
|
||||
'app-hover-lift-card--hovering': hover.isHovering && !props.sortable,
|
||||
'cursor-move': props.sortable,
|
||||
}"
|
||||
:style="{ '--plugin-card-accent-rgb': accentRgb }"
|
||||
:ripple="!props.sortable"
|
||||
>
|
||||
<div
|
||||
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%)`"
|
||||
>
|
||||
<div class="plugin-card__banner flex-grow">
|
||||
<VCardText class="px-2 pt-2 pb-0">
|
||||
<VCardTitle
|
||||
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));
|
||||
}
|
||||
|
||||
// 插件卡片头部染色层:普通主题保持原有实色底+压暗遮罩,玻璃主题只替换色层与材质。
|
||||
.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-template-columns: repeat(auto-fill, minmax(20rem, 1fr));
|
||||
}
|
||||
|
||||
@@ -760,6 +760,81 @@ html[data-theme='glass'] {
|
||||
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 {
|
||||
-webkit-backdrop-filter: var(--glass-surface-backdrop-filter) !important;
|
||||
|
||||
Reference in New Issue
Block a user