feat(glass): 插件卡片改为染色玻璃

将插件卡片与市场卡片头部的实色背景改为变量驱动的染色层:
图标主色只作为透光色相,玻璃主题下叠加吸收遮罩与镜面高光,
并按 frosted/tinted 材质与透明度调整强度;文件夹卡片渐变底
同步降低不透明度让卡片本体玻璃透出。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
jxxghp
2026-07-28 13:52:50 +08:00
co-authored by Claude Opus 5
parent 8a4ca2482c
commit 8d86bd6e92
4 changed files with 107 additions and 20 deletions
+8 -10
View File
@@ -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"
+8 -10
View File
@@ -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"
+16
View File
@@ -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));
}
+75
View File
@@ -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;