fix: 调整主题定制器和对话框样式,优化用户体验

This commit is contained in:
jxxghp
2026-06-02 21:06:24 +08:00
parent 200500a060
commit 57224e15fb
5 changed files with 43 additions and 54 deletions

View File

@@ -47,7 +47,7 @@ export default defineComponent({
// 当弹窗刚打开时,记录当前的滚动状态
if (!wasDialogOpen && isDialogOpen.value) {
wasScrolledBeforeDialog.value = scrollDistance.value > 0
wasScrolledBeforeDialog.value = scrollDistance.value > 10
}
}
@@ -160,6 +160,8 @@ export default defineComponent({
</script>
<style lang="scss">
/* stylelint-disable no-descending-specificity */
@use '@configured-variables' as variables;
@use '@layouts/styles/placeholders';
@use '@layouts/styles/mixins';
@@ -257,8 +259,8 @@ export default defineComponent({
&.layout-vertical-nav-collapsed .layout-vertical-nav:not(.overlay-nav) {
.nav-header {
justify-content: center;
padding-inline: 0;
margin-inline: 0;
padding-inline: 0;
}
.app-logo {
@@ -312,8 +314,8 @@ export default defineComponent({
}
.layout-navbar {
border-block-end: 1px solid rgba(var(--v-theme-on-surface), 0.08);
background: rgb(var(--v-theme-background));
border-block-end: 1px solid rgba(var(--v-theme-on-surface), 0.08);
inline-size: 100%;
max-inline-size: none;
padding-inline: 0;
@@ -324,15 +326,15 @@ export default defineComponent({
border-radius: 0 !important;
background: transparent !important;
inline-size: 100%;
max-inline-size: variables.$layout-boxed-content-width;
margin-inline: auto;
max-inline-size: variables.$layout-boxed-content-width;
padding-inline: 1.5rem;
}
.layout-page-content {
inline-size: 100%;
max-inline-size: variables.$layout-boxed-content-width;
margin-inline: auto;
max-inline-size: variables.$layout-boxed-content-width;
padding-inline: 1rem;
}
@@ -371,8 +373,8 @@ export default defineComponent({
.v-theme--transparent .layout-wrapper.layout-horizontal-nav-active .layout-navbar {
backdrop-filter: none !important;
background: transparent !important;
box-shadow: none !important;
border-block-end-color: rgba(var(--v-theme-on-surface), 0.04);
box-shadow: none !important;
}
html[data-theme='transparent'] .layout-wrapper.layout-horizontal-nav-active .navbar-content-container,
@@ -431,11 +433,11 @@ export default defineComponent({
.nav-section-title,
.nav-link > a,
.nav-item-icon {
color: rgba(231, 227, 252, 0.78) !important;
color: rgba(231, 227, 252, 78%) !important;
}
.nav-link > a:hover {
background-color: rgba(231, 227, 252, 0.06);
background-color: rgba(231, 227, 252, 6%);
}
.nav-link > .router-link-exact-active {
@@ -458,8 +460,7 @@ export default defineComponent({
.layout-page-content {
// display: flex;
// 使用 clip 替代 hidden避免 Chrome 144+ 滚动锁定问题
overflow-x: clip;
overflow-y: auto;
overflow: clip auto;
.page-content-container {
inline-size: 100%;

View File

@@ -10,6 +10,7 @@ import { usePWA } from '@/composables/usePWA'
import { useI18n } from 'vue-i18n'
import { useTheme } from 'vuetify'
import { VDialog, VNavigationDrawer } from 'vuetify/components'
import { useDisplay } from 'vuetify'
const props = withDefaults(
defineProps<{
@@ -31,6 +32,7 @@ const { isCustomized, resetSettings, setLayout, setPrimaryColor, setSemiDarkMenu
const { appMode } = usePWA()
const { t } = useI18n()
const { global: globalTheme } = useTheme()
const display = useDisplay()
const defaultPrimaryColor = themeCustomizerPrimaryColors[0].value
const drawer = computed({
@@ -41,11 +43,12 @@ const drawer = computed({
const customizerContainer = computed(() => (appMode.value ? VDialog : VNavigationDrawer))
const customizerContainerProps = computed(() => {
if (appMode.value) {
if (appMode.value && display.mdAndDown.value) {
return {
class: 'theme-customizer-dialog-overlay',
maxWidth: 420,
scrim: true,
fullscreen: !display.mdAndUp.value,
scrollable: true,
}
}
@@ -55,6 +58,7 @@ const customizerContainerProps = computed(() => {
scrim: false,
temporary: true,
width: 420,
persistent: false,
}
})
@@ -129,16 +133,11 @@ async function handleResetSettings() {
</script>
<template>
<component
:is="customizerContainer"
v-model="drawer"
v-bind="customizerContainerProps"
>
<component :is="customizerContainer" v-model="drawer" v-bind="customizerContainerProps">
<div class="theme-customizer-panel" :class="{ 'theme-customizer-panel--dialog': appMode }">
<div class="theme-customizer-header">
<div class="theme-customizer-header py-5 px-4">
<div>
<h2 class="theme-customizer-title">{{ t('theme.customizer.title') }}</h2>
<p class="theme-customizer-subtitle">{{ t('theme.customizer.subtitle') }}</p>
</div>
<div class="theme-customizer-header-actions">
<VBadge color="error" dot :model-value="showResetBadge" location="top end" offset-x="2" offset-y="2">
@@ -167,7 +166,6 @@ async function handleResetSettings() {
<div
v-for="color in themeCustomizerPrimaryColors"
:key="color.value"
type="button"
class="theme-customizer-color-option"
:class="{ 'is-active': settings.primaryColor === color.value }"
:aria-label="t('theme.customizer.usePrimaryColor', { color: color.name })"
@@ -177,7 +175,7 @@ async function handleResetSettings() {
</div>
<div
type="button"
v-if="!appMode"
class="theme-customizer-color-option theme-customizer-color-option--picker"
:class="{
'is-active': !themeCustomizerPrimaryColors.some(color => color.value === settings.primaryColor),
@@ -201,7 +199,6 @@ async function handleResetSettings() {
<div
v-for="theme in themeOptions"
:key="theme.value"
type="button"
class="theme-customizer-card-option"
:class="{ 'is-active': settings.theme === theme.value }"
@click="setTheme(theme.value)"
@@ -216,7 +213,6 @@ async function handleResetSettings() {
<div
v-for="skin in skinOptions"
:key="skin.value"
type="button"
class="theme-customizer-preview-option"
:class="{ 'is-active': settings.skin === skin.value }"
@click="setSkin(skin.value)"
@@ -260,7 +256,6 @@ async function handleResetSettings() {
<div
v-for="layout in layoutOptions"
:key="layout.value"
type="button"
class="theme-customizer-preview-option"
:class="{ 'is-active': settings.layout === layout.value, 'is-disabled': appMode }"
@click="handleLayoutChange(layout.value)"
@@ -287,11 +282,13 @@ async function handleResetSettings() {
</template>
<style lang="scss">
/* stylelint-disable no-descending-specificity */
.theme-customizer-drawer {
position: fixed !important;
z-index: 12000 !important;
border-inline-start: 1px solid rgba(var(--v-theme-on-surface), 0.08) !important;
block-size: 100dvh !important;
border-inline-start: 1px solid rgba(var(--v-theme-on-surface), 0.08) !important;
box-shadow: -2px 0 6px rgba(0, 0, 0, 10%) !important;
inset-block: 0 !important;
inset-inline-end: 0 !important;
@@ -321,16 +318,11 @@ async function handleResetSettings() {
border: 1px solid rgba(var(--v-theme-on-surface), 0.08);
border-radius: 16px;
background: rgb(var(--v-theme-surface));
block-size: auto;
box-shadow: 0 8px 22px rgba(0, 0, 0, 12%);
inline-size: min(420px, calc(100vw - 32px));
max-block-size: 85vh;
max-block-size: 85dvh;
block-size: 100dvh;
}
.theme-customizer-panel--dialog .theme-customizer-body {
max-block-size: calc(85vh - 104px);
max-block-size: calc(85dvh - 104px);
block-size: calc(100dvh - 80px - env(safe-area-inset-bottom) - env(safe-area-inset-top));
}
.theme-customizer-drawer.v-theme--transparent,
@@ -363,11 +355,9 @@ html[data-theme='transparent'] .v-overlay__content:has(.theme-customizer-panel--
.theme-customizer-header {
display: flex;
align-items: flex-start;
align-items: center;
justify-content: space-between;
gap: 16px;
padding-block: 28px 26px;
padding-inline: 32px 24px;
}
.theme-customizer-title {
@@ -378,14 +368,6 @@ html[data-theme='transparent'] .v-overlay__content:has(.theme-customizer-panel--
line-height: 1.2;
}
.theme-customizer-subtitle {
color: rgba(var(--v-theme-on-surface), var(--v-medium-emphasis-opacity));
font-size: 1rem;
line-height: 1.35;
margin-block: 8px 0;
margin-inline: 0;
}
.theme-customizer-header-actions {
display: flex;
align-items: center;

View File

@@ -61,19 +61,21 @@ const visible = computed({
</template>
<style scoped>
/* stylelint-disable selector-pseudo-class-no-unknown */
.system-health-dialog-card {
display: flex;
flex-direction: column;
overflow: hidden;
flex-direction: column;
}
.system-health-dialog-body {
/* 弹窗正文本身不滚动,滚动只交给健康检查结果列表。 */
display: flex;
overflow: hidden !important;
flex: 1 1 auto;
block-size: min(42rem, calc(100dvh - 8rem - env(safe-area-inset-top) - env(safe-area-inset-bottom)));
min-block-size: 0;
overflow: hidden !important;
}
:global(.v-dialog--fullscreen) .system-health-dialog-body {

View File

@@ -1,3 +1,5 @@
/* stylelint-disable scss/at-rule-no-unknown */
/* stylelint-disable no-descending-specificity */
// 公共样式 - 所有主题都需要
@tailwind base;
@tailwind components;
@@ -80,21 +82,18 @@ html[data-theme-skin='bordered'] {
}
.layout-vertical-nav,
.navbar-content-container,
.footer-nav-card {
border: 1px solid rgba(var(--v-theme-on-surface), 0.1) !important;
}
.navbar-content-container {
border-block-end: 1px solid rgba(var(--v-theme-on-surface), 0.1) !important;
}
.layout-vertical-nav {
border-block: 0 !important;
border-inline-start: 0 !important;
}
.navbar-content-container {
border-block-start: 0 !important;
border-radius: 0 0 10px 10px;
background-color: rgba(var(--v-theme-surface), 0.82);
}
}
// 应用类信息卡片:固定右侧媒体槽位,避免图片被左侧文字挤压变形。
@@ -118,6 +117,7 @@ html[data-theme-skin='bordered'] {
rgba(var(--v-theme-surface), var(--app-card-surface-opacity)) !important;
box-shadow: var(--app-card-rest-shadow) !important;
color: rgb(var(--v-theme-on-surface));
--app-card-accent-rgb: var(--v-theme-primary);
--app-card-accent-end-rgb: var(--app-card-accent-rgb);
--app-card-accent-start-opacity: 0.025;
@@ -126,6 +126,7 @@ html[data-theme-skin='bordered'] {
--app-card-hover-border-opacity: 0.16;
--app-card-stripe-opacity: 0.22;
--app-card-surface-opacity: 0.92;
transition: border-color 0.2s ease, box-shadow 0.2s ease, transform 0.2s ease;
}
@@ -163,8 +164,9 @@ html[data-theme-skin='bordered'] {
html[data-theme="transparent"] .app-card-colorful,
.v-theme--transparent .app-card-colorful {
backdrop-filter: blur(var(--transparent-blur, 10px));
border: 0 !important;
backdrop-filter: blur(var(--transparent-blur, 10px));
--app-card-accent-start-opacity: 0.018;
--app-card-accent-end-opacity: 0.01;
--app-card-border-opacity: 0;

View File

@@ -1,3 +1,5 @@
/* stylelint-disable scss/at-rule-no-unknown */
// 透明主题专用样式
html[data-theme="transparent"] {
// 定义透明度变量
@@ -39,6 +41,7 @@ html[data-theme="transparent"] {
// 设置页彩色卡片保留透明主题的玻璃质感,只叠加极轻的图标主色。
.app-card-colorful {
border: 0 !important;
background:
linear-gradient(
135deg,
@@ -47,7 +50,6 @@ html[data-theme="transparent"] {
rgba(var(--v-theme-surface), 0) 76%
),
rgba(var(--v-theme-surface), var(--transparent-opacity-light)) !important;
border: 0 !important;
}
// 工具栏