🎨 统一后台分页与设置页布局
This commit is contained in:
@@ -97,7 +97,7 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
pageSizeOptions: () => [10, 20, 50, 100]
|
pageSizeOptions: () => [5, 10, 20, 50, 100]
|
||||||
})
|
})
|
||||||
|
|
||||||
defineEmits<{
|
defineEmits<{
|
||||||
|
|||||||
@@ -81,6 +81,8 @@ const legacyForeverExpiresAt = '2099-12-31T23:59'
|
|||||||
const detailPolicyDownloadLimit = 5
|
const detailPolicyDownloadLimit = 5
|
||||||
const hourInMilliseconds = 60 * 60 * 1000
|
const hourInMilliseconds = 60 * 60 * 1000
|
||||||
const dayInMilliseconds = 24 * 60 * 60 * 1000
|
const dayInMilliseconds = 24 * 60 * 60 * 1000
|
||||||
|
const adminPageSizeOptions = [5, 10, 20, 50, 100] as const
|
||||||
|
const defaultAdminPageSize = 10
|
||||||
|
|
||||||
type DetailPolicyActionOption = {
|
type DetailPolicyActionOption = {
|
||||||
action: AdminFilePolicyAction
|
action: AdminFilePolicyAction
|
||||||
@@ -167,8 +169,10 @@ const normalizePresetChoice = <T extends string>(
|
|||||||
|
|
||||||
const normalizePresetSize = (value: unknown) => {
|
const normalizePresetSize = (value: unknown) => {
|
||||||
const normalizedSize = Number(value)
|
const normalizedSize = Number(value)
|
||||||
if (!Number.isFinite(normalizedSize)) return 10
|
const pageSize = Number.isFinite(normalizedSize) ? Math.trunc(normalizedSize) : defaultAdminPageSize
|
||||||
return Math.min(Math.max(Math.trunc(normalizedSize), 1), 100)
|
return adminPageSizeOptions.includes(pageSize as (typeof adminPageSizeOptions)[number])
|
||||||
|
? pageSize
|
||||||
|
: defaultAdminPageSize
|
||||||
}
|
}
|
||||||
|
|
||||||
const buildDefaultViewPresetParams = (): AdminFileViewPresetParams => ({
|
const buildDefaultViewPresetParams = (): AdminFileViewPresetParams => ({
|
||||||
@@ -178,7 +182,7 @@ const buildDefaultViewPresetParams = (): AdminFileViewPresetParams => ({
|
|||||||
health: 'all',
|
health: 'all',
|
||||||
sortBy: 'created_at',
|
sortBy: 'created_at',
|
||||||
sortOrder: 'desc',
|
sortOrder: 'desc',
|
||||||
size: 10
|
size: defaultAdminPageSize
|
||||||
})
|
})
|
||||||
|
|
||||||
const normalizeViewPresetParams = (params: unknown): AdminFileViewPresetParams => {
|
const normalizeViewPresetParams = (params: unknown): AdminFileViewPresetParams => {
|
||||||
@@ -218,7 +222,7 @@ export function useAdminFiles() {
|
|||||||
const summary = ref<AdminFileSummary>(emptySummary())
|
const summary = ref<AdminFileSummary>(emptySummary())
|
||||||
const params = ref<AdminFileListParams & { total: number }>({
|
const params = ref<AdminFileListParams & { total: number }>({
|
||||||
page: 1,
|
page: 1,
|
||||||
size: 10,
|
size: defaultAdminPageSize,
|
||||||
total: 0,
|
total: 0,
|
||||||
keyword: '',
|
keyword: '',
|
||||||
status: 'all',
|
status: 'all',
|
||||||
@@ -311,7 +315,7 @@ export function useAdminFiles() {
|
|||||||
health: params.value.health || 'all',
|
health: params.value.health || 'all',
|
||||||
sortBy: params.value.sortBy || 'created_at',
|
sortBy: params.value.sortBy || 'created_at',
|
||||||
sortOrder: params.value.sortOrder || 'desc',
|
sortOrder: params.value.sortOrder || 'desc',
|
||||||
size: params.value.size || 10
|
size: params.value.size || defaultAdminPageSize
|
||||||
})
|
})
|
||||||
|
|
||||||
const buildBuiltInViewPreset = (
|
const buildBuiltInViewPreset = (
|
||||||
@@ -1204,8 +1208,7 @@ export function useAdminFiles() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handlePageSizeChange = async (size: number) => {
|
const handlePageSizeChange = async (size: number) => {
|
||||||
if (!Number.isFinite(size) || size < 1) return
|
params.value.size = normalizePresetSize(size)
|
||||||
params.value.size = Math.min(Math.trunc(size), 100)
|
|
||||||
params.value.page = 1
|
params.value.page = 1
|
||||||
markViewPresetDirty()
|
markViewPresetDirty()
|
||||||
await loadFiles()
|
await loadFiles()
|
||||||
|
|||||||
@@ -89,10 +89,10 @@
|
|||||||
<div class="flex h-full min-h-0 min-w-0 flex-1 flex-col">
|
<div class="flex h-full min-h-0 min-w-0 flex-1 flex-col">
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<header
|
<header
|
||||||
class="shadow-md border-b transition-colors duration-300 h-16"
|
class="h-14 border-b shadow-md transition-colors duration-300 lg:hidden"
|
||||||
:class="[isDarkMode ? 'bg-[#1a2930] border-[#31454d]' : 'bg-[#f2f6f5] border-[#c8d7da]']"
|
:class="[isDarkMode ? 'bg-[#1a2930] border-[#31454d]' : 'bg-[#f2f6f5] border-[#c8d7da]']"
|
||||||
>
|
>
|
||||||
<div class="flex items-center justify-between h-16 px-4">
|
<div class="flex h-14 items-center justify-between px-4">
|
||||||
<button @click="toggleSidebar" class="lg:hidden">
|
<button @click="toggleSidebar" class="lg:hidden">
|
||||||
<MenuIcon class="w-6 h-6" :class="[isDarkMode ? 'text-gray-400' : 'text-gray-600']" />
|
<MenuIcon class="w-6 h-6" :class="[isDarkMode ? 'text-gray-400' : 'text-gray-600']" />
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -707,7 +707,7 @@
|
|||||||
:current-page="historyPage"
|
:current-page="historyPage"
|
||||||
:page-size="historyPageSize"
|
:page-size="historyPageSize"
|
||||||
:total="historyRows.length"
|
:total="historyRows.length"
|
||||||
:page-size-options="[5, 10, 20, 50]"
|
:page-size-options="[5, 10, 20, 50, 100]"
|
||||||
@page-change="historyPage = $event"
|
@page-change="historyPage = $event"
|
||||||
@page-size-change="handleHistoryPageSizeChange"
|
@page-size-change="handleHistoryPageSizeChange"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -31,59 +31,50 @@ onMounted(() => {
|
|||||||
<template>
|
<template>
|
||||||
<div class="p-6">
|
<div class="p-6">
|
||||||
<div
|
<div
|
||||||
class="sticky top-0 z-20 -mx-6 -mt-6 mb-6 border-b px-6 py-4 backdrop-blur"
|
class="mb-6 flex flex-col gap-3 lg:flex-row lg:items-end lg:justify-between"
|
||||||
:class="[isDarkMode ? 'border-gray-700 bg-gray-900/85' : 'border-gray-200 bg-gray-50/90']"
|
|
||||||
>
|
>
|
||||||
<div class="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
|
<div>
|
||||||
<div>
|
<h2 class="text-2xl font-bold" :class="[isDarkMode ? 'text-white' : 'text-[#2d4650]']">
|
||||||
<h2 class="text-2xl font-bold" :class="[isDarkMode ? 'text-white' : 'text-gray-800']">
|
{{ t('admin.settings.title') }}
|
||||||
{{ t('admin.settings.title') }}
|
</h2>
|
||||||
</h2>
|
<p
|
||||||
<p
|
class="mt-1 text-sm"
|
||||||
class="mt-1 text-sm"
|
:class="[isDirty ? 'text-amber-500' : isDarkMode ? 'text-gray-400' : 'text-[#586f75]']"
|
||||||
:class="[isDirty ? 'text-amber-500' : isDarkMode ? 'text-gray-400' : 'text-gray-500']"
|
>
|
||||||
>
|
{{ isDirty ? t('manage.settings.unsavedChanges') : t('manage.settings.allChangesSaved') }}
|
||||||
{{
|
</p>
|
||||||
isDirty ? t('manage.settings.unsavedChanges') : t('manage.settings.allChangesSaved')
|
</div>
|
||||||
}}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex flex-wrap items-center gap-3">
|
<div class="flex flex-wrap items-center gap-2">
|
||||||
<BaseButton
|
<BaseButton
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
:loading="isRefreshing"
|
:loading="isRefreshing"
|
||||||
:disabled="isSaving || isDirty"
|
:disabled="isSaving || isDirty"
|
||||||
:title="
|
:title="isDirty ? t('manage.settings.refreshBlocked') : t('manage.settings.refreshConfig')"
|
||||||
isDirty ? t('manage.settings.refreshBlocked') : t('manage.settings.refreshConfig')
|
@click="refreshConfig"
|
||||||
"
|
>
|
||||||
@click="refreshConfig"
|
<template #icon>
|
||||||
>
|
<RefreshCwIcon class="mr-2 h-4 w-4" />
|
||||||
<template #icon>
|
</template>
|
||||||
<RefreshCwIcon class="mr-2 h-4 w-4" />
|
{{ isRefreshing ? t('manage.settings.refreshing') : t('manage.settings.refreshConfig') }}
|
||||||
</template>
|
</BaseButton>
|
||||||
{{
|
|
||||||
isRefreshing ? t('manage.settings.refreshing') : t('manage.settings.refreshConfig')
|
|
||||||
}}
|
|
||||||
</BaseButton>
|
|
||||||
|
|
||||||
<BaseButton
|
<BaseButton :loading="isSaving" :disabled="!isDirty || isRefreshing" @click="submitConfig">
|
||||||
:loading="isSaving"
|
<template #icon>
|
||||||
:disabled="!isDirty || isRefreshing"
|
<SaveIcon class="mr-2 h-4 w-4" />
|
||||||
@click="submitConfig"
|
</template>
|
||||||
>
|
{{ isSaving ? t('manage.settings.saving') : t('manage.settings.saveChanges') }}
|
||||||
<template #icon>
|
</BaseButton>
|
||||||
<SaveIcon class="mr-2 h-4 w-4" />
|
|
||||||
</template>
|
|
||||||
{{ isSaving ? t('manage.settings.saving') : t('manage.settings.saveChanges') }}
|
|
||||||
</BaseButton>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="space-y-6 rounded-lg shadow-md p-6"
|
class="theme-2026-card theme-2026-card-hover space-y-8 rounded-[30px] border p-6 shadow-md transition-all duration-200 hover:-translate-y-0.5 hover:shadow-lg"
|
||||||
:class="[isDarkMode ? 'bg-gray-800 bg-opacity-70' : 'bg-white']"
|
:class="[
|
||||||
|
isDarkMode
|
||||||
|
? 'border-[#40545c] bg-[#202f36]/90 hover:border-[#7199a8] hover:bg-[#263941] hover:shadow-black/25'
|
||||||
|
: 'border-[#d7e3e5] bg-[#f8fbfb] hover:border-[#8fb2bf] hover:shadow-[#a9bdc2]/70'
|
||||||
|
]"
|
||||||
>
|
>
|
||||||
<!-- 基本设置 -->
|
<!-- 基本设置 -->
|
||||||
<section class="space-y-4">
|
<section class="space-y-4">
|
||||||
@@ -714,4 +705,10 @@ onMounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<style scoped></style>
|
<style scoped>
|
||||||
|
:deep(input:not([type='checkbox'])),
|
||||||
|
:deep(select),
|
||||||
|
:deep(textarea) {
|
||||||
|
border-radius: 30px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user