refactor(frontend): 将备份和数据管理页面合并为设置页的标签页

将独立的 /admin/backup 和 /admin/data-management 页面整合到设置页,
作为「备份」和「Sora 存储」标签页,减少侧边栏条目,集中管理配置。

- 移除 BackupView 和 DataManagementView 的 AppLayout 包装
- 在 SettingsView 中以子组件形式嵌入,使用 v-show 切换标签
- 删除独立路由和侧边栏菜单入口
- 备份/数据标签页下隐藏主保存按钮(各自有独立保存)
- 优化标签栏样式适配7个标签,PC端支持细滚动条
- 清理未使用的图标组件和 i18n 键
This commit is contained in:
shaw
2026-03-14 20:22:39 +08:00
parent b9c31fa7c4
commit 616930f9d3
7 changed files with 55 additions and 91 deletions

View File

@@ -1,5 +1,4 @@
<template>
<AppLayout>
<div class="space-y-6">
<!-- S3 Storage Config -->
<div class="card p-6">
@@ -275,13 +274,11 @@
</div>
</transition>
</teleport>
</AppLayout>
</template>
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import AppLayout from '@/components/layout/AppLayout.vue'
import { adminAPI } from '@/api'
import { useAppStore } from '@/stores'
import type { BackupS3Config, BackupScheduleConfig, BackupRecord } from '@/api/admin/backup'

View File

@@ -1,5 +1,4 @@
<template>
<AppLayout>
<div class="space-y-6">
<div class="card p-6">
<div class="mb-4 flex flex-wrap items-center justify-between gap-3">
@@ -183,13 +182,11 @@
</div>
</Transition>
</Teleport>
</AppLayout>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import AppLayout from '@/components/layout/AppLayout.vue'
import type { SoraS3Profile } from '@/api/admin/settings'
import { adminAPI } from '@/api'
import { useAppStore } from '@/stores'

View File

@@ -9,7 +9,7 @@
<!-- Settings Form -->
<form v-else @submit.prevent="saveSettings" class="space-y-6">
<!-- Tab Navigation -->
<div class="sticky top-0 z-10 overflow-x-auto scrollbar-hide">
<div class="sticky top-0 z-10 overflow-x-auto settings-tabs-scroll">
<nav class="settings-tabs">
<button
v-for="tab in settingsTabs"
@@ -1649,8 +1649,18 @@
</div>
</div><!-- /Tab: Email -->
<!-- Tab: Backup -->
<div v-show="activeTab === 'backup'">
<BackupSettings />
</div>
<!-- Tab: Data Management -->
<div v-show="activeTab === 'data'">
<DataManagementSettings />
</div>
<!-- Save Button -->
<div class="flex justify-end">
<div v-show="activeTab !== 'backup' && activeTab !== 'data'" class="flex justify-end">
<button type="submit" :disabled="saving" class="btn btn-primary">
<svg v-if="saving" class="h-4 w-4 animate-spin" fill="none" viewBox="0 0 24 24">
<circle
@@ -1692,6 +1702,8 @@ import GroupBadge from '@/components/common/GroupBadge.vue'
import GroupOptionItem from '@/components/common/GroupOptionItem.vue'
import Toggle from '@/components/common/Toggle.vue'
import ImageUpload from '@/components/common/ImageUpload.vue'
import BackupSettings from '@/views/admin/BackupView.vue'
import DataManagementSettings from '@/views/admin/DataManagementView.vue'
import { useClipboard } from '@/composables/useClipboard'
import { useAppStore } from '@/stores'
import { useAdminSettingsStore } from '@/stores/adminSettings'
@@ -1706,7 +1718,7 @@ const { t } = useI18n()
const appStore = useAppStore()
const adminSettingsStore = useAdminSettingsStore()
type SettingsTab = 'general' | 'security' | 'users' | 'gateway' | 'email'
type SettingsTab = 'general' | 'security' | 'users' | 'gateway' | 'email' | 'backup' | 'data'
const activeTab = ref<SettingsTab>('general')
const settingsTabs = [
{ key: 'general' as SettingsTab, icon: 'home' as const },
@@ -1714,6 +1726,8 @@ const settingsTabs = [
{ key: 'users' as SettingsTab, icon: 'user' as const },
{ key: 'gateway' as SettingsTab, icon: 'server' as const },
{ key: 'email' as SettingsTab, icon: 'mail' as const },
{ key: 'backup' as SettingsTab, icon: 'database' as const },
{ key: 'data' as SettingsTab, icon: 'cube' as const },
]
const { copyToClipboard } = useClipboard()
@@ -2378,9 +2392,38 @@ onMounted(() => {
}
/* ============ Settings Tab Navigation ============ */
/* Scroll container: thin scrollbar on PC, auto-hide on mobile */
.settings-tabs-scroll {
scrollbar-width: thin;
scrollbar-color: transparent transparent;
}
.settings-tabs-scroll:hover {
scrollbar-color: rgb(0 0 0 / 0.15) transparent;
}
:root.dark .settings-tabs-scroll:hover {
scrollbar-color: rgb(255 255 255 / 0.2) transparent;
}
.settings-tabs-scroll::-webkit-scrollbar {
height: 3px;
}
.settings-tabs-scroll::-webkit-scrollbar-track {
background: transparent;
}
.settings-tabs-scroll::-webkit-scrollbar-thumb {
background: transparent;
border-radius: 3px;
}
.settings-tabs-scroll:hover::-webkit-scrollbar-thumb {
background: rgb(0 0 0 / 0.15);
}
:root.dark .settings-tabs-scroll:hover::-webkit-scrollbar-thumb {
background: rgb(255 255 255 / 0.2);
}
.settings-tabs {
@apply inline-flex min-w-full gap-1 rounded-2xl
border border-gray-100 bg-white/80 p-1.5 backdrop-blur-sm
@apply inline-flex min-w-full gap-0.5 rounded-2xl
border border-gray-100 bg-white/80 p-1 backdrop-blur-sm
dark:border-dark-700/50 dark:bg-dark-800/80;
box-shadow: 0 1px 3px rgb(0 0 0 / 0.04), 0 1px 2px rgb(0 0 0 / 0.02);
}
@@ -2392,8 +2435,8 @@ onMounted(() => {
}
.settings-tab {
@apply relative flex flex-1 items-center justify-center gap-2
whitespace-nowrap rounded-xl px-4 py-2.5
@apply relative flex flex-1 items-center justify-center gap-1.5
whitespace-nowrap rounded-xl px-2.5 py-2
text-sm font-medium
text-gray-500 dark:text-dark-400
transition-all duration-200 ease-out;
@@ -2420,7 +2463,7 @@ onMounted(() => {
}
.settings-tab-icon {
@apply flex h-7 w-7 items-center justify-center rounded-lg
@apply flex h-6 w-6 items-center justify-center rounded-lg
transition-all duration-200;
}