mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-07-02 04:51:30 +08:00
refactor: 统一组件样式,优化圆角和阴影效果,提升视觉一致性
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -2740,6 +2740,7 @@ export default {
|
||||
resubscribeTv: '正在重新订阅 {name} 第 {season} 季...',
|
||||
season: '第 {season} 季',
|
||||
noData: '没有已完成的订阅',
|
||||
noDataHint: '完成的订阅会显示在这里',
|
||||
},
|
||||
siteUserData: {
|
||||
title: '站点用户数据',
|
||||
|
||||
@@ -2741,6 +2741,7 @@ export default {
|
||||
resubscribeTv: '正在重新訂閱 {name} 第 {season} 季...',
|
||||
season: '第 {season} 季',
|
||||
noData: '沒有已完成的訂閱',
|
||||
noDataHint: '完成的訂閱會顯示在這裡',
|
||||
},
|
||||
siteUserData: {
|
||||
title: '站點用戶數據',
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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') }}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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') }}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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') }}
|
||||
|
||||
Reference in New Issue
Block a user