feat: add theme radius customization

This commit is contained in:
jxxghp
2026-06-05 23:06:10 +08:00
parent a73c28c1f7
commit f3c524b6b5
34 changed files with 231 additions and 52 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "moviepilot", "name": "moviepilot",
"version": "2.13.5", "version": "2.13.6",
"private": true, "private": true,
"type": "module", "type": "module",
"bin": "dist/service.js", "bin": "dist/service.js",
+1 -1
View File
@@ -178,7 +178,7 @@ const instructions = computed(() => {
position: fixed; position: fixed;
z-index: 1000; z-index: 1000;
border: 1px solid rgba(var(--v-border-color), var(--v-border-opacity)); border: 1px solid rgba(var(--v-border-color), var(--v-border-opacity));
border-radius: 12px; border-radius: var(--app-surface-radius);
background: rgb(var(--v-theme-surface)); background: rgb(var(--v-theme-surface));
box-shadow: 0 4px 20px rgba(0, 0, 0, 10%); box-shadow: 0 4px 20px rgba(0, 0, 0, 10%);
inset-block-end: 5rem; inset-block-end: 5rem;
+117 -1
View File
@@ -4,6 +4,7 @@ import {
themeCustomizerPrimaryColors, themeCustomizerPrimaryColors,
useThemeCustomizer, useThemeCustomizer,
type ThemeCustomizerLayout, type ThemeCustomizerLayout,
type ThemeCustomizerRadius,
type ThemeCustomizerShadow, type ThemeCustomizerShadow,
type ThemeCustomizerSkin, type ThemeCustomizerSkin,
type ThemeCustomizerTheme, type ThemeCustomizerTheme,
@@ -34,6 +35,7 @@ const {
resetSettings, resetSettings,
setLayout, setLayout,
setPrimaryColor, setPrimaryColor,
setRadius,
setSemiDarkMenu, setSemiDarkMenu,
setShadow, setShadow,
setSkin, setSkin,
@@ -175,6 +177,40 @@ const shadowOptions = computed<
}, },
]) ])
const radiusOptions = computed<
Array<{
previewRadius: string
title: string
value: ThemeCustomizerRadius
}>
>(() => [
{
previewRadius: '0',
title: t('theme.customizer.radiusSquare'),
value: 'square',
},
{
previewRadius: '4px',
title: t('theme.customizer.radiusSmall'),
value: 'small',
},
{
previewRadius: '8px',
title: t('theme.customizer.radiusDefault'),
value: 'default',
},
{
previewRadius: '12px',
title: t('theme.customizer.radiusLarge'),
value: 'large',
},
{
previewRadius: '16px',
title: t('theme.customizer.radiusExtra'),
value: 'extra',
},
])
const layoutOptions = computed<Array<{ icon: string; title: string; value: ThemeCustomizerLayout }>>(() => [ const layoutOptions = computed<Array<{ icon: string; title: string; value: ThemeCustomizerLayout }>>(() => [
{ title: t('theme.customizer.layoutVertical'), value: 'vertical', icon: 'mdi-dock-left' }, { title: t('theme.customizer.layoutVertical'), value: 'vertical', icon: 'mdi-dock-left' },
{ title: t('theme.customizer.layoutCollapsed'), value: 'collapsed', icon: 'mdi-dock-window' }, { title: t('theme.customizer.layoutCollapsed'), value: 'collapsed', icon: 'mdi-dock-window' },
@@ -186,6 +222,7 @@ const showLayoutSection = computed(() => !appMode.value)
const hasAppModeCustomization = computed(() => { const hasAppModeCustomization = computed(() => {
return ( return (
settings.value.primaryColor !== defaultPrimaryColor || settings.value.primaryColor !== defaultPrimaryColor ||
settings.value.radius !== 'default' ||
settings.value.shadow !== 'none' || settings.value.shadow !== 'none' ||
settings.value.skin !== 'default' || settings.value.skin !== 'default' ||
settings.value.theme !== 'auto' settings.value.theme !== 'auto'
@@ -228,6 +265,7 @@ async function handleResetSettings() {
// App 模式共享定制器,但保留桌面导航相关偏好,只重置 App 侧可调整的外观设置。 // App 模式共享定制器,但保留桌面导航相关偏好,只重置 App 侧可调整的外观设置。
await setPrimaryColor(defaultPrimaryColor) await setPrimaryColor(defaultPrimaryColor)
await setRadius('default')
await setShadow('none') await setShadow('none')
await setSkin('default') await setSkin('default')
await setTheme('auto') await setTheme('auto')
@@ -348,6 +386,31 @@ async function handleResetSettings() {
<VDivider class="mt-7" /> <VDivider class="mt-7" />
<h3 class="theme-customizer-section-title">{{ t('theme.customizer.radius') }}</h3>
<div class="theme-customizer-preview-grid theme-customizer-preview-grid--radius">
<div
v-for="radius in radiusOptions"
:key="radius.value"
class="theme-customizer-preview-option"
:class="{ 'is-active': settings.radius === radius.value }"
@click="setRadius(radius.value)"
>
<span
class="theme-customizer-radius-scene"
:style="{ '--theme-customizer-radius-preview': radius.previewRadius }"
>
<span class="theme-customizer-radius-scene__card">
<span class="theme-customizer-radius-scene__badge" />
<span class="theme-customizer-radius-scene__line" />
<span class="theme-customizer-radius-scene__line theme-customizer-radius-scene__line--short" />
</span>
</span>
<span>{{ radius.title }}</span>
</div>
</div>
<VDivider class="mt-7" />
<h3 class="theme-customizer-section-title">{{ t('theme.customizer.shadow') }}</h3> <h3 class="theme-customizer-section-title">{{ t('theme.customizer.shadow') }}</h3>
<div class="theme-customizer-preview-grid theme-customizer-preview-grid--shadow"> <div class="theme-customizer-preview-grid theme-customizer-preview-grid--shadow">
<div <div
@@ -469,7 +532,7 @@ async function handleResetSettings() {
.theme-customizer-panel--dialog { .theme-customizer-panel--dialog {
overflow: hidden; overflow: hidden;
border: 1px solid rgba(var(--v-theme-on-surface), 0.08); border: 1px solid rgba(var(--v-theme-on-surface), 0.08);
border-radius: 16px; border-radius: var(--app-surface-radius);
background: rgb(var(--v-theme-surface)); background: rgb(var(--v-theme-surface));
block-size: var(--theme-customizer-viewport-height, 100dvh); block-size: var(--theme-customizer-viewport-height, 100dvh);
max-block-size: var(--theme-customizer-viewport-height, 100dvh); max-block-size: var(--theme-customizer-viewport-height, 100dvh);
@@ -703,6 +766,10 @@ html[data-theme='transparent'] .v-overlay__content:has(.theme-customizer-drawer)
grid-template-columns: repeat(2, minmax(0, 1fr)); grid-template-columns: repeat(2, minmax(0, 1fr));
} }
.theme-customizer-preview-grid--radius {
grid-template-columns: repeat(auto-fit, minmax(92px, 1fr));
}
.theme-customizer-preview-option { .theme-customizer-preview-option {
align-items: flex-start; align-items: flex-start;
padding: 0; padding: 0;
@@ -715,6 +782,7 @@ html[data-theme='transparent'] .v-overlay__content:has(.theme-customizer-drawer)
box-shadow: none !important; box-shadow: none !important;
.theme-customizer-mini-layout, .theme-customizer-mini-layout,
.theme-customizer-radius-scene,
.theme-customizer-shadow-scene { .theme-customizer-shadow-scene {
border-width: 2px; border-width: 2px;
border-color: rgb(var(--v-theme-primary)); border-color: rgb(var(--v-theme-primary));
@@ -803,6 +871,54 @@ html[data-theme='transparent'] .v-overlay__content:has(.theme-customizer-drawer)
} }
} }
.theme-customizer-radius-scene {
position: relative;
display: block;
overflow: hidden;
border: 1px solid rgba(var(--v-theme-on-surface), 0.12);
border-radius: 10px;
background:
linear-gradient(180deg, rgba(var(--v-theme-on-surface), 0.02), rgba(var(--v-theme-on-surface), 0.05)),
rgb(var(--v-theme-surface));
block-size: 90px;
inline-size: 100%;
min-inline-size: 0;
}
.theme-customizer-radius-scene__card {
position: absolute;
display: flex;
flex-direction: column;
border: 1px solid rgba(var(--v-theme-on-surface), 0.08);
border-radius: var(--theme-customizer-radius-preview);
background: rgb(var(--v-theme-surface));
gap: 8px;
inset: 16px;
padding-block: 12px;
padding-inline: 14px;
}
.theme-customizer-radius-scene__badge,
.theme-customizer-radius-scene__line {
display: block;
border-radius: 999px;
background: rgba(var(--v-theme-on-surface), 0.1);
}
.theme-customizer-radius-scene__badge {
block-size: 8px;
inline-size: 42%;
min-inline-size: 28px;
}
.theme-customizer-radius-scene__line {
block-size: 7px;
}
.theme-customizer-radius-scene__line--short {
inline-size: 66%;
}
.theme-customizer-shadow-scene { .theme-customizer-shadow-scene {
position: relative; position: relative;
display: block; display: block;
+2 -3
View File
@@ -75,7 +75,6 @@ async function deleteDownload() {
<VCard <VCard
:key="props.info?.hash" :key="props.info?.hash"
class="downloading-card flex flex-col h-full overflow-hidden" class="downloading-card flex flex-col h-full overflow-hidden"
rounded="lg"
min-height="150" min-height="150"
> >
<template #image> <template #image>
@@ -133,11 +132,11 @@ async function deleteDownload() {
// 外层壳承载主题阴影,避免 VCard 的圆角裁切层影响阴影外扩。 // 外层壳承载主题阴影,避免 VCard 的圆角裁切层影响阴影外扩。
.downloading-card-shadow-shell { .downloading-card-shadow-shell {
position: relative; position: relative;
border-radius: 8px; border-radius: var(--app-surface-radius);
block-size: 100%; block-size: 100%;
box-shadow: var(--app-surface-shadow); box-shadow: var(--app-surface-shadow);
inline-size: 100%; inline-size: 100%;
transition: box-shadow 0.2s ease; transition: border-radius 0.2s ease, box-shadow 0.2s ease;
} }
@media (hover: hover) { @media (hover: hover) {
+2 -2
View File
@@ -137,7 +137,7 @@ function handleDropToFolder(event: DragEvent) {
&.sortable-ghost { &.sortable-ghost {
border: 2px dashed #2196f3; border: 2px dashed #2196f3;
border-radius: 16px; border-radius: var(--app-surface-radius);
background: rgba(33, 150, 243, 10%); background: rgba(33, 150, 243, 10%);
opacity: 0.3; opacity: 0.3;
} }
@@ -151,7 +151,7 @@ function handleDropToFolder(event: DragEvent) {
&.drag-over { &.drag-over {
border: 2px dashed #2196f3; border: 2px dashed #2196f3;
border-radius: 16px; border-radius: var(--app-surface-radius);
box-shadow: 0 0 20px rgba(33, 150, 243, 50%); box-shadow: 0 0 20px rgba(33, 150, 243, 50%);
transform: scale(1.02); transform: scale(1.02);
} }
-1
View File
@@ -255,7 +255,6 @@ onMounted(() => {
:ripple="false" :ripple="false"
variant="flat" variant="flat"
elevation="0" elevation="0"
rounded="lg"
:hover="!cardProps.sortable" :hover="!cardProps.sortable"
@click="handleCardClick" @click="handleCardClick"
> >
+6 -3
View File
@@ -404,7 +404,7 @@ function handleCardClick() {
<VHover> <VHover>
<template #default="hover"> <template #default="hover">
<div <div
class="w-full h-full rounded-lg relative" class="subscribe-card-shell w-full h-full relative"
:class="{ :class="{
'transition transform-cpu duration-300 -translate-y-1': hover.isHovering && !props.sortable, 'transition transform-cpu duration-300 -translate-y-1': hover.isHovering && !props.sortable,
'outline-dotted outline-pink-500 outline-2': props.batchMode && props.selected, 'outline-dotted outline-pink-500 outline-2': props.batchMode && props.selected,
@@ -419,7 +419,6 @@ function handleCardClick() {
'subscribe-card-paused': subscribeState === 'S', 'subscribe-card-paused': subscribeState === 'S',
'cursor-move': props.sortable, 'cursor-move': props.sortable,
}" }"
rounded="lg"
min-height="150" min-height="150"
@click="handleCardClick" @click="handleCardClick"
:ripple="!props.batchMode && !props.sortable" :ripple="!props.batchMode && !props.sortable"
@@ -594,10 +593,14 @@ function handleCardClick() {
position: relative; position: relative;
} }
.subscribe-card-shell {
border-radius: var(--app-surface-radius);
}
.subscribe-card-pending-tint::after { .subscribe-card-pending-tint::after {
position: absolute; position: absolute;
z-index: 3; z-index: 3;
border-radius: 8px; border-radius: inherit;
box-shadow: inset 0 0 48px rgba(56, 189, 248, 40%); // sky-400 box-shadow: inset 0 0 48px rgba(56, 189, 248, 40%); // sky-400
content: ''; content: '';
inset: 0; inset: 0;
+1 -2
View File
@@ -94,7 +94,7 @@ function doDelete() {
<VHover> <VHover>
<template #default="hover"> <template #default="hover">
<div <div
class="w-full h-full rounded-lg overflow-hidden" class="w-full h-full overflow-hidden"
:class="{ :class="{
'transition transform-cpu duration-300 -translate-y-1': hover.isHovering, 'transition transform-cpu duration-300 -translate-y-1': hover.isHovering,
}" }"
@@ -103,7 +103,6 @@ function doDelete() {
v-bind="hover.props" v-bind="hover.props"
:key="props.media?.id" :key="props.media?.id"
class="flex flex-col h-full" class="flex flex-col h-full"
rounded="0"
min-height="150" min-height="150"
@click="showForkSubscribe" @click="showForkSubscribe"
> >
@@ -102,7 +102,6 @@ function doDelete() {
:class="{ :class="{
'workflow-share-card--hovering': hover.isHovering, 'workflow-share-card--hovering': hover.isHovering,
}" }"
rounded="lg"
min-height="150" min-height="150"
:style="{ background: gradientStyle }" :style="{ background: gradientStyle }"
@click="showForkWorkflow" @click="showForkWorkflow"
@@ -196,7 +196,7 @@ function submitSettings() {
position: relative; position: relative;
overflow: hidden; overflow: hidden;
border: 1px solid rgba(var(--v-theme-on-surface), 0.1); border: 1px solid rgba(var(--v-theme-on-surface), 0.1);
border-radius: 8px; border-radius: var(--app-surface-radius);
background-color: rgba(var(--v-theme-surface-variant), 0.3); background-color: rgba(var(--v-theme-surface-variant), 0.3);
cursor: pointer; cursor: pointer;
padding-block: 10px; padding-block: 10px;
@@ -118,7 +118,7 @@ function submitOrder() {
position: relative; position: relative;
overflow: hidden; overflow: hidden;
border: 1px solid rgba(var(--v-theme-primary), 0.3); border: 1px solid rgba(var(--v-theme-primary), 0.3);
border-radius: 8px; border-radius: var(--app-surface-radius);
background-color: rgba(var(--v-theme-primary), 0.08); background-color: rgba(var(--v-theme-primary), 0.08);
cursor: grab; cursor: grab;
padding-block: 10px; padding-block: 10px;
+2 -2
View File
@@ -278,7 +278,7 @@ async function doDelete() {
position: relative; position: relative;
overflow: hidden; overflow: hidden;
border: 1px solid rgba(var(--v-border-color), var(--v-border-opacity)); border: 1px solid rgba(var(--v-border-color), var(--v-border-opacity));
border-radius: 8px; border-radius: var(--app-surface-radius);
background-color: rgba(var(--v-theme-surface), 0.8); background-color: rgba(var(--v-theme-surface), 0.8);
block-size: 280px; block-size: 280px;
inline-size: 240px; inline-size: 240px;
@@ -290,7 +290,7 @@ async function doDelete() {
.vue-flow__node { .vue-flow__node {
border: 1px solid rgba(var(--v-border-color), var(--v-border-opacity)); border: 1px solid rgba(var(--v-border-color), var(--v-border-opacity));
border-radius: 8px; border-radius: var(--app-surface-radius);
font-size: 10px; font-size: 10px;
&:hover { &:hover {
@@ -116,7 +116,7 @@ const colorTheme = computed(() => {
<style scoped> <style scoped>
.offline-dialog { .offline-dialog {
border-radius: 16px; border-radius: var(--app-surface-radius);
} }
.status-icon-wrapper { .status-icon-wrapper {
@@ -505,7 +505,7 @@ onMounted(() => {
.plugin-market-list-wrap { .plugin-market-list-wrap {
flex: 1; flex: 1;
border: 1px solid rgba(var(--v-border-color), var(--v-border-opacity)); border: 1px solid rgba(var(--v-border-color), var(--v-border-opacity));
border-radius: 8px; border-radius: var(--app-surface-radius);
background: rgba(var(--v-theme-surface), 0.72); background: rgba(var(--v-theme-surface), 0.72);
min-block-size: 0; min-block-size: 0;
overflow-y: auto; overflow-y: auto;
@@ -563,7 +563,7 @@ onMounted(() => {
display: flex; display: flex;
flex: 1; flex: 1;
border: 1px solid rgba(var(--v-border-color), var(--v-border-opacity)); border: 1px solid rgba(var(--v-border-color), var(--v-border-opacity));
border-radius: 8px; border-radius: var(--app-surface-radius);
background: rgba(var(--v-theme-surface), 0.72); background: rgba(var(--v-theme-surface), 0.72);
min-block-size: 0; min-block-size: 0;
overflow: hidden; overflow: hidden;
+4 -4
View File
@@ -1914,7 +1914,7 @@ onUnmounted(() => {
.preview-note { .preview-note {
border: 1px solid rgba(var(--v-border-color), var(--v-border-opacity)); border: 1px solid rgba(var(--v-border-color), var(--v-border-opacity));
border-radius: 1rem; border-radius: var(--app-surface-radius);
color: rgb(var(--v-theme-error)); color: rgb(var(--v-theme-error));
font-size: 0.875rem; font-size: 0.875rem;
line-height: 1.5; line-height: 1.5;
@@ -1932,7 +1932,7 @@ onUnmounted(() => {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
border: 1px solid rgba(var(--v-border-color), var(--v-border-opacity)); border: 1px solid rgba(var(--v-border-color), var(--v-border-opacity));
border-radius: 1rem; border-radius: var(--app-surface-radius);
gap: 0.375rem; gap: 0.375rem;
min-inline-size: 0; min-inline-size: 0;
padding-block: 0.875rem; padding-block: 0.875rem;
@@ -1959,7 +1959,7 @@ onUnmounted(() => {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
border: 1px solid rgba(var(--v-border-color), var(--v-border-opacity)); border: 1px solid rgba(var(--v-border-color), var(--v-border-opacity));
border-radius: 0.75rem; border-radius: var(--app-surface-radius);
gap: 0.75rem; gap: 0.75rem;
padding-block: 0.875rem; padding-block: 0.875rem;
padding-inline: 1rem; padding-inline: 1rem;
@@ -2052,7 +2052,7 @@ onUnmounted(() => {
flex: 0 0 auto; flex: 0 0 auto;
flex-direction: column; flex-direction: column;
border: 1px solid rgba(var(--v-border-color), var(--v-border-opacity)); border: 1px solid rgba(var(--v-border-color), var(--v-border-opacity));
border-radius: 1rem; border-radius: var(--app-surface-radius);
margin-block-end: 1.5rem; margin-block-end: 1.5rem;
margin-inline: 1.5rem; margin-inline: 1.5rem;
min-block-size: 0; min-block-size: 0;
+1 -1
View File
@@ -698,7 +698,7 @@ onMounted(() => {
display: flex; display: flex;
overflow: hidden; overflow: hidden;
flex-direction: column; flex-direction: column;
border-radius: 16px !important; border-radius: var(--app-surface-radius) !important;
box-shadow: 0 12px 40px rgba(0, 0, 0, 12%) !important; box-shadow: 0 12px 40px rgba(0, 0, 0, 12%) !important;
} }
+1 -1
View File
@@ -394,7 +394,7 @@ watch(selectedFile, async newFile => {
.upload-zone { .upload-zone {
padding: 2rem; padding: 2rem;
border: 2px dashed #ccc; border: 2px dashed #ccc;
border-radius: 8px; border-radius: var(--app-surface-radius);
text-align: center; text-align: center;
transition: all 0.3s ease; transition: all 0.3s ease;
} }
+1 -1
View File
@@ -719,7 +719,7 @@ onMounted(() => {
.site-resource-card__meta-item { .site-resource-card__meta-item {
border: 1px solid rgba(var(--v-border-color), calc(var(--v-border-opacity) * 0.7)); border: 1px solid rgba(var(--v-border-color), calc(var(--v-border-opacity) * 0.7));
border-radius: 0.6rem; border-radius: var(--app-surface-radius);
background: rgba(var(--v-theme-surface), 0.78); background: rgba(var(--v-theme-surface), 0.78);
min-block-size: 0; min-block-size: 0;
padding-block: 0.55rem; padding-block: 0.55rem;
@@ -392,7 +392,7 @@ onMounted(() => {
.stat-card { .stat-card {
padding: 16px; padding: 16px;
border: 1px solid var(--v-border-color); border: 1px solid var(--v-border-color);
border-radius: 8px; border-radius: var(--app-surface-radius);
background: var(--v-theme-surface); background: var(--v-theme-surface);
min-inline-size: 100px; min-inline-size: 100px;
text-align: center; text-align: center;
@@ -602,7 +602,7 @@ const isMacOS = computed(() => {
flex-direction: column; flex-direction: column;
padding: 16px; padding: 16px;
border: 1px solid rgb(var(--v-theme-primary)); border: 1px solid rgb(var(--v-theme-primary));
border-radius: 8px; border-radius: var(--app-surface-radius);
background-color: rgb(var(--v-theme-surface)); background-color: rgb(var(--v-theme-surface));
box-shadow: 0 8px 24px rgba(var(--v-shadow-key-umbra-color), 0.32); box-shadow: 0 8px 24px rgba(var(--v-shadow-key-umbra-color), 0.32);
gap: 14px; gap: 14px;
@@ -656,7 +656,7 @@ const isMacOS = computed(() => {
.vue-flow__minimap { .vue-flow__minimap {
overflow: hidden; overflow: hidden;
border: 1px solid rgba(var(--v-border-color), var(--v-border-opacity)); border: 1px solid rgba(var(--v-border-color), var(--v-border-opacity));
border-radius: 8px; border-radius: var(--app-surface-radius);
background-color: rgba(var(--v-theme-surface), 0.8); background-color: rgba(var(--v-theme-surface), 0.8);
box-shadow: 0 4px 15px rgba(var(--v-shadow-key-umbra-color), 0.1); box-shadow: 0 4px 15px rgba(var(--v-shadow-key-umbra-color), 0.1);
inset-block-end: 20px; inset-block-end: 20px;
@@ -687,7 +687,7 @@ const isMacOS = computed(() => {
// 自定义节点样式 // 自定义节点样式
.vue-flow__node { .vue-flow__node {
border: 1px solid rgba(var(--v-border-color), var(--v-border-opacity)); border: 1px solid rgba(var(--v-border-color), var(--v-border-opacity));
border-radius: 12px; border-radius: var(--app-surface-radius);
&:hover { &:hover {
box-shadow: 0 8px 16px rgba(var(--v-shadow-key-umbra-color), 0.15) !important; box-shadow: 0 8px 16px rgba(var(--v-shadow-key-umbra-color), 0.15) !important;
+1 -1
View File
@@ -778,7 +778,7 @@ onUnmounted(() => {
<template> <template>
<div> <div>
<VCard class="d-flex flex-column w-full h-full rounded-t-0" :class="{ 'rounded-s-0': showTree }"> <VCard class="d-flex flex-column w-full h-full">
<div v-if="!loading" class="flex"> <div v-if="!loading" class="flex">
<IconBtn v-if="display.mdAndUp.value"> <IconBtn v-if="display.mdAndUp.value">
<VIcon v-if="showTree" icon="mdi-file-tree" @click="switchFileTree(false)" /> <VIcon v-if="showTree" icon="mdi-file-tree" @click="switchFileTree(false)" />
+1 -1
View File
@@ -253,7 +253,7 @@ onMounted(async () => {
</script> </script>
<template> <template>
<VCard class="file-navigator rounded-e-0 rounded-t-0" v-if="!isMobile" :height="`${availableHeight}px`"> <VCard class="file-navigator" v-if="!isMobile" :height="`${availableHeight}px`">
<VVirtualScroll :items="visibleTreeRows" :item-height="32" class="tree-container"> <VVirtualScroll :items="visibleTreeRows" :item-height="32" class="tree-container">
<template #default="{ item }"> <template #default="{ item }">
<div <div
+2 -2
View File
@@ -610,7 +610,7 @@ onMounted(() => {
.filter-toolbar-card { .filter-toolbar-card {
overflow: hidden; overflow: hidden;
border: 1px solid rgba(var(--v-theme-on-surface), 0.08); border: 1px solid rgba(var(--v-theme-on-surface), 0.08);
border-radius: 8px; border-radius: var(--app-surface-radius);
background: rgba(var(--v-theme-surface), 0.82); background: rgba(var(--v-theme-surface), 0.82);
} }
@@ -759,7 +759,7 @@ onMounted(() => {
@media (width <= 600px) { @media (width <= 600px) {
.filter-toolbar-card { .filter-toolbar-card {
border-radius: 8px; border-radius: var(--app-surface-radius);
} }
.filter-buttons-grid { .filter-buttons-grid {
+19 -2
View File
@@ -24,6 +24,7 @@ export const themeCustomizerPrimaryColors = [
] as const ] as const
export type ThemeCustomizerLayout = 'collapsed' | 'horizontal' | 'vertical' export type ThemeCustomizerLayout = 'collapsed' | 'horizontal' | 'vertical'
export type ThemeCustomizerRadius = 'default' | 'extra' | 'large' | 'small' | 'square'
export type ThemeCustomizerShadow = 'none' | 'low' | 'medium' | 'high' export type ThemeCustomizerShadow = 'none' | 'low' | 'medium' | 'high'
export type ThemeCustomizerSkin = 'bordered' | 'default' export type ThemeCustomizerSkin = 'bordered' | 'default'
export type ThemeCustomizerTheme = 'auto' | 'dark' | 'light' | 'purple' | 'transparent' export type ThemeCustomizerTheme = 'auto' | 'dark' | 'light' | 'purple' | 'transparent'
@@ -31,6 +32,7 @@ export type ThemeCustomizerTheme = 'auto' | 'dark' | 'light' | 'purple' | 'trans
export interface ThemeCustomizerSettings { export interface ThemeCustomizerSettings {
layout: ThemeCustomizerLayout layout: ThemeCustomizerLayout
primaryColor: string primaryColor: string
radius: ThemeCustomizerRadius
semiDarkMenu: boolean semiDarkMenu: boolean
shadow: ThemeCustomizerShadow shadow: ThemeCustomizerShadow
skin: ThemeCustomizerSkin skin: ThemeCustomizerSkin
@@ -41,6 +43,7 @@ type VuetifyThemeApi = ReturnType<typeof useTheme>
const defaultPrimaryColor = themeCustomizerPrimaryColors[0].value const defaultPrimaryColor = themeCustomizerPrimaryColors[0].value
const validLayouts: ThemeCustomizerLayout[] = ['vertical', 'collapsed', 'horizontal'] const validLayouts: ThemeCustomizerLayout[] = ['vertical', 'collapsed', 'horizontal']
const validRadii: ThemeCustomizerRadius[] = ['square', 'small', 'default', 'large', 'extra']
const validShadows: ThemeCustomizerShadow[] = ['none', 'low', 'medium', 'high'] const validShadows: ThemeCustomizerShadow[] = ['none', 'low', 'medium', 'high']
const validSkins: ThemeCustomizerSkin[] = ['default', 'bordered'] const validSkins: ThemeCustomizerSkin[] = ['default', 'bordered']
const validThemes: ThemeCustomizerTheme[] = ['auto', 'light', 'dark', 'purple', 'transparent'] const validThemes: ThemeCustomizerTheme[] = ['auto', 'light', 'dark', 'purple', 'transparent']
@@ -67,6 +70,7 @@ function getDefaultThemeCustomizerSettings(): ThemeCustomizerSettings {
return { return {
layout: 'vertical', layout: 'vertical',
primaryColor: defaultPrimaryColor, primaryColor: defaultPrimaryColor,
radius: 'default',
semiDarkMenu: false, semiDarkMenu: false,
shadow: 'none', shadow: 'none',
skin: 'default', skin: 'default',
@@ -82,6 +86,9 @@ function normalizeThemeCustomizerSettings(settings: Partial<ThemeCustomizerSetti
? (settings.layout as ThemeCustomizerLayout) ? (settings.layout as ThemeCustomizerLayout)
: fallback.layout, : fallback.layout,
primaryColor: isHexColor(settings.primaryColor) ? settings.primaryColor.toUpperCase() : fallback.primaryColor, primaryColor: isHexColor(settings.primaryColor) ? settings.primaryColor.toUpperCase() : fallback.primaryColor,
radius: validRadii.includes(settings.radius as ThemeCustomizerRadius)
? (settings.radius as ThemeCustomizerRadius)
: fallback.radius,
semiDarkMenu: typeof settings.semiDarkMenu === 'boolean' ? settings.semiDarkMenu : fallback.semiDarkMenu, semiDarkMenu: typeof settings.semiDarkMenu === 'boolean' ? settings.semiDarkMenu : fallback.semiDarkMenu,
shadow: validShadows.includes(settings.shadow as ThemeCustomizerShadow) shadow: validShadows.includes(settings.shadow as ThemeCustomizerShadow)
? (settings.shadow as ThemeCustomizerShadow) ? (settings.shadow as ThemeCustomizerShadow)
@@ -161,17 +168,19 @@ export function applyPrimaryColorToVuetify(color: string, themeApi: VuetifyTheme
localStorage.setItem('materio-initial-loader-color', color) localStorage.setItem('materio-initial-loader-color', color)
} }
/** 布局、阴影、皮肤和局部菜单风格只依赖根节点属性,CSS 可以在不刷新页面的情况下即时响应。 */ /** 布局、圆角、阴影、皮肤和局部菜单风格只依赖根节点属性,CSS 可以在不刷新页面的情况下即时响应。 */
export function applyThemeCustomizerRootSettings( export function applyThemeCustomizerRootSettings(
settings: Pick<ThemeCustomizerSettings, 'layout' | 'semiDarkMenu' | 'shadow' | 'skin'>, settings: Pick<ThemeCustomizerSettings, 'layout' | 'radius' | 'semiDarkMenu' | 'shadow' | 'skin'>,
) { ) {
if (!isBrowser()) return if (!isBrowser()) return
document.documentElement.setAttribute('data-theme-layout', settings.layout) document.documentElement.setAttribute('data-theme-layout', settings.layout)
document.documentElement.setAttribute('data-theme-radius', settings.radius)
document.documentElement.setAttribute('data-theme-semi-dark-menu', String(settings.semiDarkMenu)) document.documentElement.setAttribute('data-theme-semi-dark-menu', String(settings.semiDarkMenu))
document.documentElement.setAttribute('data-theme-shadow', settings.shadow) document.documentElement.setAttribute('data-theme-shadow', settings.shadow)
document.documentElement.setAttribute('data-theme-skin', settings.skin) document.documentElement.setAttribute('data-theme-skin', settings.skin)
document.body.setAttribute('data-theme-layout', settings.layout) document.body.setAttribute('data-theme-layout', settings.layout)
document.body.setAttribute('data-theme-radius', settings.radius)
document.body.setAttribute('data-theme-semi-dark-menu', String(settings.semiDarkMenu)) document.body.setAttribute('data-theme-semi-dark-menu', String(settings.semiDarkMenu))
document.body.setAttribute('data-theme-shadow', settings.shadow) document.body.setAttribute('data-theme-shadow', settings.shadow)
document.body.setAttribute('data-theme-skin', settings.skin) document.body.setAttribute('data-theme-skin', settings.skin)
@@ -235,6 +244,7 @@ export function isDefaultThemeCustomizerSettings(settings: ThemeCustomizerSettin
const defaults = normalizeThemeCustomizerSettings({ const defaults = normalizeThemeCustomizerSettings({
layout: 'vertical', layout: 'vertical',
primaryColor: defaultPrimaryColor, primaryColor: defaultPrimaryColor,
radius: 'default',
semiDarkMenu: false, semiDarkMenu: false,
shadow: 'none', shadow: 'none',
skin: 'default', skin: 'default',
@@ -244,6 +254,7 @@ export function isDefaultThemeCustomizerSettings(settings: ThemeCustomizerSettin
return ( return (
settings.layout === defaults.layout && settings.layout === defaults.layout &&
settings.primaryColor === defaults.primaryColor && settings.primaryColor === defaults.primaryColor &&
settings.radius === defaults.radius &&
settings.semiDarkMenu === defaults.semiDarkMenu && settings.semiDarkMenu === defaults.semiDarkMenu &&
settings.shadow === defaults.shadow && settings.shadow === defaults.shadow &&
settings.skin === defaults.skin && settings.skin === defaults.skin &&
@@ -282,6 +293,10 @@ export function useThemeCustomizer() {
return updateSettings({ primaryColor: color }) return updateSettings({ primaryColor: color })
} }
function setRadius(radius: ThemeCustomizerRadius) {
return updateSettings({ radius })
}
function setTheme(theme: ThemeCustomizerTheme) { function setTheme(theme: ThemeCustomizerTheme) {
return updateSettings({ theme }) return updateSettings({ theme })
} }
@@ -306,6 +321,7 @@ export function useThemeCustomizer() {
await updateSettings({ await updateSettings({
layout: 'vertical', layout: 'vertical',
primaryColor: defaultPrimaryColor, primaryColor: defaultPrimaryColor,
radius: 'default',
semiDarkMenu: false, semiDarkMenu: false,
shadow: 'none', shadow: 'none',
skin: 'default', skin: 'default',
@@ -339,6 +355,7 @@ export function useThemeCustomizer() {
resetSettings, resetSettings,
setLayout, setLayout,
setPrimaryColor, setPrimaryColor,
setRadius,
setSemiDarkMenu, setSemiDarkMenu,
setShadow, setShadow,
setSkin, setSkin,
+1 -2
View File
@@ -200,7 +200,7 @@ function resolveDynamicMenuItemTitle(item: DynamicButtonMenuItem) {
<Teleport v-if="appMode && showNav" to="body"> <Teleport v-if="appMode && showNav" to="body">
<div class="footer-nav-container"> <div class="footer-nav-container">
<TransitionGroup name="footer-nav" tag="div" class="footer-nav-group"> <TransitionGroup name="footer-nav" tag="div" class="footer-nav-group">
<VCard key="main-nav" elevation="3" class="footer-nav-card border" rounded="pill"> <VCard key="main-nav" elevation="3" class="footer-nav-card border">
<VCardText class="footer-card-content"> <VCardText class="footer-card-content">
<!-- 添加指示器 --> <!-- 添加指示器 -->
<div ref="indicator" class="nav-indicator"></div> <div ref="indicator" class="nav-indicator"></div>
@@ -248,7 +248,6 @@ function resolveDynamicMenuItemTitle(item: DynamicButtonMenuItem) {
key="dynamic-btn" key="dynamic-btn"
elevation="3" elevation="3"
class="footer-nav-card dynamic-btn-card border" class="footer-nav-card dynamic-btn-card border"
rounded="pill"
> >
<VCardText class="footer-card-content"> <VCardText class="footer-card-content">
<!-- 各页面的动态按钮 --> <!-- 各页面的动态按钮 -->
+1 -1
View File
@@ -733,7 +733,7 @@ function handleBackdropClick(event: MouseEvent) {
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
border-radius: 12px; border-radius: var(--app-surface-radius);
block-size: 120px; block-size: 120px;
cursor: pointer; cursor: pointer;
gap: 4px; gap: 4px;
+1 -1
View File
@@ -281,7 +281,7 @@ onMounted(() => {
display: flex; display: flex;
align-items: center; align-items: center;
padding: 10px; padding: 10px;
border-radius: 12px; border-radius: var(--app-surface-radius);
background-color: rgb(var(--v-theme-surface-variant)); background-color: rgb(var(--v-theme-surface-variant));
transition: all 0.2s ease; transition: all 0.2s ease;
+6
View File
@@ -168,6 +168,12 @@ export default {
skins: 'Framework', skins: 'Framework',
skinDefault: 'Default', skinDefault: 'Default',
skinBordered: 'Bordered', skinBordered: 'Bordered',
radius: 'Corners',
radiusSquare: 'Square',
radiusSmall: 'Small',
radiusDefault: 'Default',
radiusLarge: 'Large',
radiusExtra: 'Extra Large',
shadow: 'Shadows', shadow: 'Shadows',
shadowNone: 'Flat', shadowNone: 'Flat',
shadowLow: 'Soft', shadowLow: 'Soft',
+6
View File
@@ -168,6 +168,12 @@ export default {
skins: '框架', skins: '框架',
skinDefault: '默认', skinDefault: '默认',
skinBordered: '边框', skinBordered: '边框',
radius: '圆角',
radiusSquare: '直角',
radiusSmall: '小圆角',
radiusDefault: '默认',
radiusLarge: '大圆角',
radiusExtra: '超大圆角',
shadow: '阴影', shadow: '阴影',
shadowNone: '无阴影', shadowNone: '无阴影',
shadowLow: '柔和', shadowLow: '柔和',
+6
View File
@@ -168,6 +168,12 @@ export default {
skins: '框架', skins: '框架',
skinDefault: '默認', skinDefault: '默認',
skinBordered: '邊框', skinBordered: '邊框',
radius: '圓角',
radiusSquare: '直角',
radiusSmall: '小圓角',
radiusDefault: '默認',
radiusLarge: '大圓角',
radiusExtra: '超大圓角',
shadow: '陰影', shadow: '陰影',
shadowNone: '無陰影', shadowNone: '無陰影',
shadowLow: '柔和', shadowLow: '柔和',
+3 -3
View File
@@ -1388,7 +1388,7 @@ onUnmounted(() => {
.search-progress-card { .search-progress-card {
padding: 16px; padding: 16px;
border: 1px solid rgba(var(--v-theme-primary), 0.18); border: 1px solid rgba(var(--v-theme-primary), 0.18);
border-radius: 8px; border-radius: var(--app-surface-radius);
backdrop-filter: blur(10px); backdrop-filter: blur(10px);
background: linear-gradient(135deg, rgba(var(--v-theme-primary), 0.08), transparent 42%), rgb(var(--v-theme-surface)); background: linear-gradient(135deg, rgba(var(--v-theme-primary), 0.08), transparent 42%), rgb(var(--v-theme-surface));
inline-size: 100%; inline-size: 100%;
@@ -1456,7 +1456,7 @@ onUnmounted(() => {
.search-skeleton-list { .search-skeleton-list {
overflow: hidden; overflow: hidden;
border: 1px solid rgba(var(--v-theme-on-surface), 0.08); border: 1px solid rgba(var(--v-theme-on-surface), 0.08);
border-radius: 8px; border-radius: var(--app-surface-radius);
background: rgb(var(--v-theme-surface)); background: rgb(var(--v-theme-surface));
} }
@@ -1546,7 +1546,7 @@ onUnmounted(() => {
.resource-list-container { .resource-list-container {
padding: 8px; padding: 8px;
border: 1px solid rgba(var(--v-theme-on-surface), 0.08); border: 1px solid rgba(var(--v-theme-on-surface), 0.08);
border-radius: 12px; border-radius: var(--app-surface-radius);
} }
.resource-list { .resource-list {
+34 -4
View File
@@ -32,8 +32,9 @@ html.v-overlay-scroll-blocked body {
} }
} }
// 全局卡片阴影 token卡片统一不使用投影避免透明主题和密集布局下出现脏边 // 全局卡片外观 token圆角和阴影在主题定制器中即时切换
html { html {
--app-surface-radius: 8px;
--app-shadow-rgb: 15, 23, 42; --app-shadow-rgb: 15, 23, 42;
--app-card-rest-shadow: none; --app-card-rest-shadow: none;
--app-card-hover-shadow: none; --app-card-hover-shadow: none;
@@ -47,6 +48,22 @@ html {
--app-surface-hover-shadow: none; --app-surface-hover-shadow: none;
} }
html[data-theme-radius='square'] {
--app-surface-radius: 0;
}
html[data-theme-radius='small'] {
--app-surface-radius: 4px;
}
html[data-theme-radius='large'] {
--app-surface-radius: 12px;
}
html[data-theme-radius='extra'] {
--app-surface-radius: 16px;
}
html[data-theme='dark'], html[data-theme='dark'],
html[data-theme='purple'], html[data-theme='purple'],
html[data-theme='transparent'] { html[data-theme='transparent'] {
@@ -114,8 +131,9 @@ html[data-theme-shadow='high'] {
// 统一系统内卡片阴影显式覆盖 Vuetify elevation 或局部卡片默认投影 // 统一系统内卡片阴影显式覆盖 Vuetify elevation 或局部卡片默认投影
.v-card, .v-card,
.v-application .v-card.v-card[class] { .v-application .v-card.v-card[class] {
border-radius: var(--app-surface-radius) !important;
box-shadow: var(--app-surface-shadow) !important; box-shadow: var(--app-surface-shadow) !important;
transition: box-shadow 0.2s ease; transition: border-radius 0.2s ease, box-shadow 0.2s ease;
} }
@media (hover: hover) { @media (hover: hover) {
@@ -183,7 +201,7 @@ html[data-theme-skin='bordered'] {
.app-card-colorful { .app-card-colorful {
overflow: hidden; overflow: hidden;
border: 1px solid rgba(var(--app-card-accent-rgb), var(--app-card-border-opacity)) !important; border: 1px solid rgba(var(--app-card-accent-rgb), var(--app-card-border-opacity)) !important;
border-radius: 8px !important; border-radius: var(--app-surface-radius) !important;
background: background:
linear-gradient( linear-gradient(
135deg, 135deg,
@@ -724,6 +742,8 @@ html[data-theme="transparent"] .app-card-colorful,
// 弹出层样式 // 弹出层样式
.v-overlay__content .v-list{ .v-overlay__content .v-list{
overflow: hidden;
border-radius: var(--app-surface-radius) !important;
backdrop-filter: blur(6px); backdrop-filter: blur(6px);
background-color: rgb(var(--v-theme-surface), 0.9) !important; background-color: rgb(var(--v-theme-surface), 0.9) !important;
box-shadow: none !important; box-shadow: none !important;
@@ -731,6 +751,7 @@ html[data-theme="transparent"] .app-card-colorful,
} }
.v-overlay__content .v-card:not(.bg-primary){ .v-overlay__content .v-card:not(.bg-primary){
border-radius: var(--app-surface-radius) !important;
backdrop-filter: blur(8px); backdrop-filter: blur(8px);
background-color: rgb(var(--v-theme-surface), 0.95) !important; background-color: rgb(var(--v-theme-surface), 0.95) !important;
box-shadow: none !important; box-shadow: none !important;
@@ -772,6 +793,7 @@ html[data-theme="transparent"] .app-card-colorful,
} }
.v-dialog--fullscreen > .v-overlay__content { .v-dialog--fullscreen > .v-overlay__content {
border-radius: 0 !important;
box-shadow: none !important; box-shadow: none !important;
} }
@@ -784,13 +806,21 @@ html[data-theme="transparent"] .app-card-colorful,
} }
.v-dialog > .v-overlay__content > .v-card, .v-dialog > .v-overlay__content > .v-card,
.v-dialog > .v-overlay__content > .v-sheet,
.v-dialog > .v-overlay__content > form > .v-card,
.v-dialog > .v-overlay__content > form > .v-sheet,
.v-bottom-sheet > .v-bottom-sheet__content.v-overlay__content > .v-card, .v-bottom-sheet > .v-bottom-sheet__content.v-overlay__content > .v-card,
.v-menu > .v-overlay__content > .v-card, .v-menu > .v-overlay__content > .v-card,
.v-menu > .v-overlay__content > .v-list { .v-menu > .v-overlay__content > .v-list {
border-radius: var(--app-surface-radius) !important;
box-shadow: var(--app-overlay-shadow) !important; box-shadow: var(--app-overlay-shadow) !important;
} }
.v-dialog--fullscreen > .v-overlay__content > .v-card { .v-dialog--fullscreen > .v-overlay__content > .v-card,
.v-dialog--fullscreen > .v-overlay__content > .v-sheet,
.v-dialog--fullscreen > .v-overlay__content > form > .v-card,
.v-dialog--fullscreen > .v-overlay__content > form > .v-sheet {
border-radius: 0 !important;
box-shadow: none !important; box-shadow: none !important;
} }
+1 -1
View File
@@ -817,7 +817,7 @@ onUnmounted(() => {
display: flex; display: flex;
align-items: stretch; align-items: stretch;
border: 1px solid rgba(var(--v-theme-on-surface), 0.04); border: 1px solid rgba(var(--v-theme-on-surface), 0.04);
border-radius: 8px; border-radius: var(--app-surface-radius);
gap: 0.75rem; gap: 0.75rem;
min-inline-size: 0; min-inline-size: 0;
padding-block: 0.5rem; padding-block: 0.5rem;
+1 -1
View File
@@ -244,7 +244,7 @@ onMounted(getModules)
.progress-card { .progress-card {
padding: 20px; padding: 20px;
border-radius: 12px; border-radius: var(--app-surface-radius);
margin: 16px; margin: 16px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white; color: white;