refactor: 统一组件样式,优化圆角和阴影效果,提升视觉一致性

This commit is contained in:
jxxghp
2026-06-07 19:49:15 +08:00
parent e239c0c5ea
commit c9ebf23977
17 changed files with 144 additions and 54 deletions

View File

@@ -346,7 +346,7 @@ function stopDrag() {
justify-content: center;
background-color: transparent;
cursor: col-resize;
inline-size: 4px;
inline-size: 1px;
transition: background-color 0.2s ease;
user-select: none;
}

View File

@@ -109,7 +109,14 @@ function submitSettings() {
</script>
<template>
<VDialog v-if="visible" v-model="visible" width="35rem" class="settings-dialog" scrollable :fullscreen="!display.mdAndUp.value">
<VDialog
v-if="visible"
v-model="visible"
width="35rem"
class="settings-dialog"
scrollable
:fullscreen="!display.mdAndUp.value"
>
<VCard class="settings-card">
<VCardItem class="settings-card-header">
<VCardTitle>
@@ -195,6 +202,7 @@ function submitSettings() {
.setting-item {
position: relative;
overflow: hidden;
border-radius: var(--app-surface-radius);
background-color: rgba(var(--v-theme-surface-variant), 0.3);
cursor: pointer;
padding-block: 10px;

View File

@@ -90,7 +90,7 @@ function closeDialog() {
/>
</VToolbar>
<VDialogCloseBtn @click="closeDialog" />
<VList v-if="plugins.length > 0" lines="two">
<VList v-if="plugins.length > 0" class="plugin-search-list" lines="two">
<VVirtualScroll :items="plugins">
<template #default="{ item }">
<VListItem @click="emit('open-plugin', item)">
@@ -130,4 +130,8 @@ function closeDialog() {
</VCard>
</VDialog>
</template>
<style lang="scss" scoped>
.plugin-search-list {
border-radius: 0 !important;
}
</style>

View File

@@ -129,7 +129,7 @@ const filteredSites = computed(() => {
<div
v-bind="props"
:class="[
'site-checkbox-wrapper pa-2 pa-sm-3 rounded-lg d-flex align-center',
'site-checkbox-wrapper pa-2 pa-sm-3 d-flex align-center',
{
'site-selected': selectedSites.includes(site.id),
'site-hover': isHovering && !selectedSites.includes(site.id),
@@ -193,8 +193,11 @@ const filteredSites = computed(() => {
<style scoped>
.site-checkbox-wrapper {
border: 1px solid rgba(var(--v-theme-on-surface), 0.08);
border-radius: var(--app-surface-radius);
cursor: pointer;
transition: transform 0.2s ease, background-color 0.2s ease;
transition:
transform 0.2s ease,
background-color 0.2s ease;
}
.site-checkbox-wrapper:hover {

View File

@@ -216,11 +216,50 @@ function getMediaTypeText(type: string | undefined) {
</VVirtualScroll>
</VInfiniteScroll>
</VList>
<VCardText v-if="historyList.length === 0 && isRefreshed" class="text-center">{{
t('dialog.subscribeHistory.noData')
}}</VCardText>
<VCardText v-if="historyList.length === 0 && isRefreshed" class="subscribe-history-empty">
<VIcon class="subscribe-history-empty__icon" icon="mdi-sync" size="30" />
<div class="subscribe-history-empty__headline">
{{ t('dialog.subscribeHistory.noData') }}
</div>
<div class="subscribe-history-empty__description">
{{ t('dialog.subscribeHistory.noDataHint') }}
</div>
</VCardText>
</VCard>
<!-- 进度框 -->
<ProgressDialog v-if="progressDialog" v-model="progressDialog" :text="progressText" />
</VDialog>
</template>
<style scoped>
.subscribe-history-empty {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 0.75rem;
min-block-size: 13rem;
padding-block: 2.5rem !important;
padding-inline: 1.5rem !important;
text-align: center;
}
.subscribe-history-empty__icon {
color: rgba(var(--v-theme-on-surface), 0.32);
}
.subscribe-history-empty__headline {
color: rgba(var(--v-theme-on-surface), 0.9);
font-size: 1.15rem;
font-weight: 600;
line-height: 1.4;
}
.subscribe-history-empty__description {
color: rgba(var(--v-theme-on-surface), 0.6);
font-size: 0.92rem;
line-height: 1.65;
max-inline-size: 25rem;
}
</style>

View File

@@ -29,8 +29,7 @@ const display = useDisplay()
const { appMode } = usePWA()
// 计算列表可用高度
// componentOffset = FileToolbar(48) + FileList操作栏(40) + VCard边距(4) = 92
const { availableHeight: listAvailableHeight } = useAvailableHeight(89, 300)
const { availableHeight: listAvailableHeight } = useAvailableHeight(100, 300)
// 输入参数
const inProps = defineProps({

View File

@@ -4,7 +4,6 @@ import type { FileItem } from '@/api/types'
import { useDisplay } from 'vuetify'
import type { AxiosRequestConfig, AxiosInstance } from 'axios'
import { useI18n } from 'vue-i18n'
import { usePWA } from '@/composables/usePWA'
import { useAvailableHeight } from '@/composables/useAvailableHeight'
// 国际化
@@ -12,16 +11,13 @@ const { t } = useI18n()
const display = useDisplay()
const { appMode } = usePWA()
type TreeRow =
| { type: 'root'; key: string; level: number }
| { type: 'loading'; key: string; path: string; level: number }
| { type: 'directory'; key: string; dir: FileItem; level: number }
// 计算列表可用高度
// componentOffset = FileToolbar(48) = 48
const { availableHeight } = useAvailableHeight(48, 300)
const { availableHeight } = useAvailableHeight(58, 300)
// 输入参数
const props = defineProps({
@@ -326,7 +322,6 @@ onMounted(async () => {
flex-direction: column;
flex-shrink: 0;
border-radius: 0 !important;
background: rgb(var(--v-table-header-background));
block-size: 100%;
box-shadow: none !important;
inline-size: 240px;

View File

@@ -2790,6 +2790,7 @@ export default {
resubscribeTv: 'Resubscribing {name} Season {season}...',
season: 'Season {season}',
noData: 'No completed subscriptions',
noDataHint: 'Completed subscription history will be displayed here',
},
siteUserData: {
title: 'Site User Data',

View File

@@ -2740,6 +2740,7 @@ export default {
resubscribeTv: '正在重新订阅 {name} 第 {season} 季...',
season: '第 {season} 季',
noData: '没有已完成的订阅',
noDataHint: '完成的订阅会显示在这里',
},
siteUserData: {
title: '站点用户数据',

View File

@@ -2741,6 +2741,7 @@ export default {
resubscribeTv: '正在重新訂閱 {name} 第 {season} 季...',
season: '第 {season} 季',
noData: '沒有已完成的訂閱',
noDataHint: '完成的訂閱會顯示在這裡',
},
siteUserData: {
title: '站點用戶數據',

View File

@@ -148,6 +148,15 @@ html[data-theme-shadow='high'] {
transition: border-color 0.2s ease, border-radius 0.2s ease, box-shadow 0.2s ease;
}
.app-surface-static {
border-radius: var(--app-surface-radius) !important;
box-shadow: var(--app-surface-shadow) !important;
}
.app-surface-static:hover {
box-shadow: var(--app-surface-shadow) !important;
}
// 底部导航卡片原本就是胶囊形态,不参与主题圆角切换。
.footer-nav-card.v-card {
--app-surface-radius: 9999px;
@@ -183,21 +192,52 @@ html[data-theme-shadow='high'] {
}
// 统一文本输入、下拉框、文本域等有框表单控件的圆角,显式 rounded 控件保留特殊形态。
.v-field:not(.v-field--variant-plain, .v-field--variant-underlined, .v-field--rounded, [class^='rounded-'], [class*=' rounded-']) {
.v-field:not(
.v-field--variant-plain,
.v-field--variant-underlined,
.v-field--rounded,
[class^='rounded-'],
[class*=' rounded-']
) {
border-radius: var(--app-field-radius);
transition: border-radius 0.2s ease, box-shadow 0.2s ease;
}
.v-btn:not(.v-btn--variant-plain, .v-btn--variant-text) {
// 自定义字段圆角时outlined 字段带左侧 inner icon 需要同步放大左侧描边弧度。
.v-field:not(
.v-field--variant-plain,
.v-field--variant-underlined,
.v-field--rounded,
[class^='rounded-'],
[class*=' rounded-']
).v-field--variant-outlined.v-field--prepended {
--app-field-outline-start-size: max(12px, calc(var(--app-field-radius) + 8px));
.v-field__outline__start {
flex-basis: var(--app-field-outline-start-size);
}
.v-field__outline__notch {
max-inline-size: calc(100% - var(--app-field-outline-start-size));
}
}
.v-btn:not(.v-btn--variant-plain, .v-btn--variant-text, .v-btn--flat) {
box-shadow: var(--app-surface-shadow) !important;
transition: box-shadow 0.2s ease;
}
.v-btn:not(.v-btn--rounded, [class^='rounded-'], [class*=' rounded-']) {
.v-btn:not(.v-btn--rounded, .v-btn--flat, .v-btn--icon, [class^='rounded-'], [class*=' rounded-']) {
border-radius: var(--app-surface-radius);
transition: border-radius 0.2s ease;
}
.v-btn-group:not(.v-btn-group--variant-plain, .v-btn-group--variant-text) {
border-radius: var(--app-surface-radius);
box-shadow: var(--app-surface-shadow) !important;
transition: box-shadow 0.2s ease, border-radius 0.2s ease;
}
// 只给外层 surface 加边框和阴影,卡片内部的列表、表格等子组件保持平面,避免层级噪声。
.v-card .v-list:not(.app-surface),
.v-card .v-sheet:not(.app-surface),
@@ -246,6 +286,7 @@ html[data-theme-skin='bordered'] {
// 设置项强调卡片:复用通知模板入口的弱强调条、轻渐变与悬浮反馈。
.app-card-colorful {
overflow: hidden;
border-radius: var(--app-surface-radius);
background:
linear-gradient(
135deg,
@@ -254,8 +295,9 @@ html[data-theme-skin='bordered'] {
rgba(var(--v-theme-surface), 0) 76%
),
rgba(var(--v-theme-surface), var(--app-card-surface-opacity));
box-shadow: var(--app-card-rest-shadow);
box-shadow: var(--app-surface-shadow);
color: rgb(var(--v-theme-on-surface));
transition: border-color 0.2s ease, box-shadow 0.2s ease, transform 0.2s ease;
--app-card-accent-rgb: var(--v-theme-primary);
--app-card-accent-end-rgb: var(--app-card-accent-rgb);
@@ -268,8 +310,6 @@ html[data-theme-skin='bordered'] {
--app-surface-border: 1px solid rgba(var(--app-card-accent-rgb), var(--app-card-border-opacity));
--app-surface-hover-shadow: var(--app-card-hover-shadow);
--app-surface-shadow: var(--app-card-rest-shadow);
transition: border-color 0.2s ease, box-shadow 0.2s ease, transform 0.2s ease;
}
.app-card-colorful::before {
@@ -459,10 +499,11 @@ html[data-theme="transparent"] .app-card-colorful,
padding-block: 4.5rem;
padding-inline: 1rem;
}
}
.Vue-Toastification__toast {
border-radius: 8px;
}
.Vue-Toastification__toast {
border-radius: var(--app-surface-radius);
box-shadow: var(--app-overlay-shadow);
}
// 对话框样式
@@ -623,11 +664,6 @@ html[data-theme="transparent"] .app-card-colorful,
color: rgba(var(--v-theme-on-surface), var(--v-high-emphasis-opacity));
}
.Vue-Toastification__toast {
border-radius: var(--app-surface-radius);
box-shadow: var(--app-overlay-shadow);
}
.backdrop-blur {
--tw-backdrop-blur: blur(8px)!important;
@@ -794,7 +830,6 @@ html[data-theme="transparent"] .app-card-colorful,
// 弹出层样式
.v-overlay__content .v-list{
overflow: hidden;
border-radius: var(--app-surface-radius);
backdrop-filter: blur(6px);
background-color: rgb(var(--v-theme-surface), 0.9);

View File

@@ -24,7 +24,9 @@ const route = useRoute()
const Draggable = defineAsyncComponent(() => import('vuedraggable').then(module => module.default))
const PluginAppCard = defineAsyncComponent(() => import('@/components/cards/PluginAppCard.vue'))
const PluginFolderCreateDialog = defineAsyncComponent(() => import('@/components/dialog/PluginFolderCreateDialog.vue'))
const PluginMarketSettingDialog = defineAsyncComponent(() => import('@/components/dialog/PluginMarketSettingDialog.vue'))
const PluginMarketSettingDialog = defineAsyncComponent(
() => import('@/components/dialog/PluginMarketSettingDialog.vue'),
)
const ProgressDialog = defineAsyncComponent(() => import('@/components/dialog/ProgressDialog.vue'))
const PluginSearchDialog = defineAsyncComponent(() => import('@/components/dialog/PluginSearchDialog.vue'))
@@ -1643,8 +1645,8 @@ function onDragStartPlugin(evt: any) {
<div>
<VPageContentTitle v-if="installedFilter" :title="t('plugin.filter', { name: installedFilter })" />
<LoadingBanner v-if="!isRefreshed" class="mt-12" />
<VAlert v-if="sortMode" color="warning" variant="tonal" class="mb-4">
<div class="d-flex flex-wrap align-center justify-space-between gap-2">
<VAlert v-if="sortMode" color="warning" variant="tonal" class="mb-4 py-0 app-surface-static">
<div class="d-flex flex-wrap align-center justify-space-between gap-2 py-5">
<span>{{ t('common.sortModeHint') }}</span>
<VBtn variant="tonal" color="error" @click="sortMode = false">
{{ t('common.exit') }}

View File

@@ -204,7 +204,7 @@ onMounted(loadDownloadDirectories)
</script>
<template>
<div class="file-browser-view app-surface">
<div class="file-browser-view app-surface-static">
<FileBrowser
v-if="operItem"
:storages="storages"

View File

@@ -18,7 +18,9 @@ import { useBackground } from '@/composables/useBackground'
import { useGlobalSettingsStore } from '@/stores'
import { openSharedDialog } from '@/composables/useSharedDialog'
const TransferHistoryDeleteDialog = defineAsyncComponent(() => import('@/components/dialog/TransferHistoryDeleteDialog.vue'))
const TransferHistoryDeleteDialog = defineAsyncComponent(
() => import('@/components/dialog/TransferHistoryDeleteDialog.vue'),
)
// i18n
const { t } = useI18n()
@@ -33,8 +35,7 @@ const { appMode } = usePWA()
const { useProgressSSE } = useBackground()
// 计算列表可用高度
// componentOffset = VCardItem搜索栏(68) + VDivider(1) + 分页栏(40) + VCard边距(2) = 111
const { availableHeight } = useAvailableHeight(125, 300)
const { availableHeight } = useAvailableHeight(135, 300)
// 提示框
const $toast = useToast()
@@ -1209,7 +1210,6 @@ onUnmounted(() => {
/>
</div>
</Teleport>
</template>
<style lang="scss">

View File

@@ -410,8 +410,8 @@ useDynamicButton({
</div>
</div>
<VAlert v-if="sortMode" color="warning" variant="tonal" class="mb-4">
<div class="d-flex flex-wrap align-center justify-space-between gap-2">
<VAlert v-if="sortMode" color="warning" variant="tonal" class="mb-4 py-0 app-surface-static">
<div class="d-flex flex-wrap align-center justify-space-between gap-2 py-5">
<span>{{ t('common.sortModeHint') }}</span>
<VBtn variant="tonal" color="error" @click="sortMode = false">
{{ t('common.exit') }}

View File

@@ -31,7 +31,12 @@ let progressDialogController: ReturnType<typeof openSharedDialog> | null = null
// 打开订阅日历共享进度弹窗。
function openProgressDialog() {
progressDialogController?.close()
progressDialogController = openSharedDialog(ProgressDialog, { text: `${t('common.loading')} ...` }, {}, { closeOn: false })
progressDialogController = openSharedDialog(
ProgressDialog,
{ text: `${t('common.loading')} ...` },
{},
{ closeOn: false },
)
}
// 关闭订阅日历共享进度弹窗。
@@ -85,7 +90,10 @@ async function eventsHander(subscribe: Subscribe) {
} else {
// 调用API查询集信息
const params = subscribe.episode_group ? { episode_group: subscribe.episode_group } : undefined
const episodes: TmdbEpisode[] = await api.get(`tmdb/${subscribe.tmdbid}/${subscribe.season}`, params ? { params } : undefined)
const episodes: TmdbEpisode[] = await api.get(
`tmdb/${subscribe.tmdbid}/${subscribe.season}`,
params ? { params } : undefined,
)
interface EpisodeInfo {
title: string
@@ -160,8 +168,8 @@ onActivated(() => {
<template>
<FullCalendar :options="calendarOptions">
<template #eventContent="arg">
<div class="hidden md:block overflow-hidden">
<VCard>
<div class="hidden md:block">
<VCard class="app-surface">
<div class="d-flex justify-space-between flex-nowrap flex-row">
<div class="ma-auto">
<VImg
@@ -434,12 +442,6 @@ onActivated(() => {
background-color: transparent !important;
}
.v-application .fc .fc-popover {
border-radius: 6px;
box-shadow: 0 4px 14px -4px var(--v-shadow-key-umbra-opacity), 0 4px 8px -4px var(--v-shadow-key-penumbra-opacity),
0 4px 8px -4px var(--v-shadow-key-ambient-opacity);
}
.v-application .fc .fc-popover .fc-popover-header,
.v-application .fc .fc-popover .fc-popover-body {
padding: 0.5rem;

View File

@@ -572,8 +572,8 @@ defineExpose({
</VCard>
</div>
<VAlert v-if="sortMode" color="warning" variant="tonal" class="mb-4 mx-2">
<div class="d-flex flex-wrap align-center justify-space-between gap-2">
<VAlert v-if="sortMode" color="warning" variant="tonal" class="mb-4 mx-2 py-0 app-surface-static">
<div class="d-flex flex-wrap align-center justify-space-between gap-2 py-5">
<span>{{ t('common.sortModeHint') }}</span>
<VBtn variant="tonal" color="error" @click="sortMode = false">
{{ t('common.exit') }}