fix(frontend): settings页面分tab拆分

This commit is contained in:
shaw
2026-03-04 16:59:57 +08:00
parent 59879b7fa7
commit f8de2bdedc
3 changed files with 280 additions and 143 deletions

View File

@@ -3534,6 +3534,15 @@ export default {
settings: { settings: {
title: 'System Settings', title: 'System Settings',
description: 'Manage registration, email verification, default values, and SMTP settings', description: 'Manage registration, email verification, default values, and SMTP settings',
tabs: {
general: 'General',
security: 'Security',
users: 'Users',
gateway: 'Gateway',
email: 'Email',
},
emailTabDisabledTitle: 'Email Verification Not Enabled',
emailTabDisabledHint: 'Enable email verification in the Security tab to configure SMTP settings.',
registration: { registration: {
title: 'Registration Settings', title: 'Registration Settings',
description: 'Control user registration and verification', description: 'Control user registration and verification',
@@ -3543,7 +3552,7 @@ export default {
emailVerificationHint: 'Require email verification for new registrations', emailVerificationHint: 'Require email verification for new registrations',
emailSuffixWhitelist: 'Email Domain Whitelist', emailSuffixWhitelist: 'Email Domain Whitelist',
emailSuffixWhitelistHint: emailSuffixWhitelistHint:
'Only email addresses from the specified domains can register (for example, @qq.com, @gmail.com)', "Only email addresses from the specified domains can register (for example, {'@'}qq.com, {'@'}gmail.com)",
emailSuffixWhitelistPlaceholder: 'example.com', emailSuffixWhitelistPlaceholder: 'example.com',
emailSuffixWhitelistInputHint: 'Leave empty for no restriction', emailSuffixWhitelistInputHint: 'Leave empty for no restriction',
promoCode: 'Promo Code', promoCode: 'Promo Code',

View File

@@ -3703,6 +3703,15 @@ export default {
settings: { settings: {
title: '系统设置', title: '系统设置',
description: '管理注册、邮箱验证、默认值和 SMTP 设置', description: '管理注册、邮箱验证、默认值和 SMTP 设置',
tabs: {
general: '通用设置',
security: '安全与认证',
users: '用户默认值',
gateway: '网关服务',
email: '邮件设置',
},
emailTabDisabledTitle: '邮箱验证未启用',
emailTabDisabledHint: '请在「安全与认证」选项卡中启用邮箱验证后,再配置 SMTP 设置。',
registration: { registration: {
title: '注册设置', title: '注册设置',
description: '控制用户注册和验证', description: '控制用户注册和验证',
@@ -3712,7 +3721,7 @@ export default {
emailVerificationHint: '新用户注册时需要验证邮箱', emailVerificationHint: '新用户注册时需要验证邮箱',
emailSuffixWhitelist: '邮箱域名白名单', emailSuffixWhitelist: '邮箱域名白名单',
emailSuffixWhitelistHint: emailSuffixWhitelistHint:
'仅允许使用指定域名的邮箱注册账号(例如 @qq.com, @gmail.com', "仅允许使用指定域名的邮箱注册账号(例如 {'@'}qq.com, {'@'}gmail.com",
emailSuffixWhitelistPlaceholder: 'example.com', emailSuffixWhitelistPlaceholder: 'example.com',
emailSuffixWhitelistInputHint: '留空则不限制', emailSuffixWhitelistInputHint: '留空则不限制',
promoCode: '优惠码', promoCode: '优惠码',

View File

@@ -8,6 +8,26 @@
<!-- Settings Form --> <!-- Settings Form -->
<form v-else @submit.prevent="saveSettings" class="space-y-6"> <form v-else @submit.prevent="saveSettings" class="space-y-6">
<!-- Tab Navigation -->
<div class="sticky top-0 z-10 overflow-x-auto scrollbar-hide">
<nav class="settings-tabs">
<button
v-for="tab in settingsTabs"
:key="tab.key"
type="button"
:class="['settings-tab', activeTab === tab.key && 'settings-tab-active']"
@click="activeTab = tab.key"
>
<span class="settings-tab-icon">
<Icon :name="tab.icon" size="sm" />
</span>
<span>{{ t(`admin.settings.tabs.${tab.key}`) }}</span>
</button>
</nav>
</div>
<!-- Tab: Security Admin API Key -->
<div v-show="activeTab === 'security'" class="space-y-6">
<!-- Admin API Key Settings --> <!-- Admin API Key Settings -->
<div class="card"> <div class="card">
<div class="border-b border-gray-100 px-6 py-4 dark:border-dark-700"> <div class="border-b border-gray-100 px-6 py-4 dark:border-dark-700">
@@ -146,7 +166,10 @@
</div> </div>
</div> </div>
</div> </div>
</div><!-- /Tab: Security Admin API Key -->
<!-- Tab: Gateway Stream Timeout -->
<div v-show="activeTab === 'gateway'" class="space-y-6">
<!-- Stream Timeout Settings --> <!-- Stream Timeout Settings -->
<div class="card"> <div class="card">
<div class="border-b border-gray-100 px-6 py-4 dark:border-dark-700"> <div class="border-b border-gray-100 px-6 py-4 dark:border-dark-700">
@@ -284,7 +307,10 @@
</template> </template>
</div> </div>
</div> </div>
</div><!-- /Tab: Gateway Stream Timeout (continued below with Claude Code & Scheduling) -->
<!-- Tab: Security Registration, Turnstile, LinuxDo -->
<div v-show="activeTab === 'security'" class="space-y-6">
<!-- Registration Settings --> <!-- Registration Settings -->
<div class="card"> <div class="card">
<div class="border-b border-gray-100 px-6 py-4 dark:border-dark-700"> <div class="border-b border-gray-100 px-6 py-4 dark:border-dark-700">
@@ -618,7 +644,10 @@
</div> </div>
</div> </div>
</div> </div>
</div><!-- /Tab: Security Registration, Turnstile, LinuxDo -->
<!-- Tab: Users -->
<div v-show="activeTab === 'users'" class="space-y-6">
<!-- Default Settings --> <!-- Default Settings -->
<div class="card"> <div class="card">
<div class="border-b border-gray-100 px-6 py-4 dark:border-dark-700"> <div class="border-b border-gray-100 px-6 py-4 dark:border-dark-700">
@@ -757,7 +786,10 @@
</div> </div>
</div> </div>
</div> </div>
</div><!-- /Tab: Users -->
<!-- Tab: Gateway Claude Code, Scheduling -->
<div v-show="activeTab === 'gateway'" class="space-y-6">
<!-- Claude Code Settings --> <!-- Claude Code Settings -->
<div class="card"> <div class="card">
<div class="border-b border-gray-100 px-6 py-4 dark:border-dark-700"> <div class="border-b border-gray-100 px-6 py-4 dark:border-dark-700">
@@ -814,7 +846,10 @@
</div> </div>
</div> </div>
</div> </div>
</div><!-- /Tab: Gateway Claude Code, Scheduling -->
<!-- Tab: General -->
<div v-show="activeTab === 'general'" class="space-y-6">
<!-- Site Settings --> <!-- Site Settings -->
<div class="card"> <div class="card">
<div class="border-b border-gray-100 px-6 py-4 dark:border-dark-700"> <div class="border-b border-gray-100 px-6 py-4 dark:border-dark-700">
@@ -957,147 +992,6 @@
</div> </div>
</div> </div>
<!-- SMTP Settings - Only show when email verification is enabled -->
<div v-if="form.email_verify_enabled" class="card">
<div
class="flex items-center justify-between border-b border-gray-100 px-6 py-4 dark:border-dark-700"
>
<div>
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">
{{ t('admin.settings.smtp.title') }}
</h2>
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
{{ t('admin.settings.smtp.description') }}
</p>
</div>
<button
type="button"
@click="testSmtpConnection"
:disabled="testingSmtp"
class="btn btn-secondary btn-sm"
>
<svg v-if="testingSmtp" class="h-4 w-4 animate-spin" fill="none" viewBox="0 0 24 24">
<circle
class="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
stroke-width="4"
></circle>
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
></path>
</svg>
{{
testingSmtp
? t('admin.settings.smtp.testing')
: t('admin.settings.smtp.testConnection')
}}
</button>
</div>
<div class="space-y-6 p-6">
<div class="grid grid-cols-1 gap-6 md:grid-cols-2">
<div>
<label class="mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ t('admin.settings.smtp.host') }}
</label>
<input
v-model="form.smtp_host"
type="text"
class="input"
:placeholder="t('admin.settings.smtp.hostPlaceholder')"
/>
</div>
<div>
<label class="mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ t('admin.settings.smtp.port') }}
</label>
<input
v-model.number="form.smtp_port"
type="number"
min="1"
max="65535"
class="input"
:placeholder="t('admin.settings.smtp.portPlaceholder')"
/>
</div>
<div>
<label class="mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ t('admin.settings.smtp.username') }}
</label>
<input
v-model="form.smtp_username"
type="text"
class="input"
:placeholder="t('admin.settings.smtp.usernamePlaceholder')"
/>
</div>
<div>
<label class="mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ t('admin.settings.smtp.password') }}
</label>
<input
v-model="form.smtp_password"
type="password"
class="input"
:placeholder="
form.smtp_password_configured
? t('admin.settings.smtp.passwordConfiguredPlaceholder')
: t('admin.settings.smtp.passwordPlaceholder')
"
/>
<p class="mt-1.5 text-xs text-gray-500 dark:text-gray-400">
{{
form.smtp_password_configured
? t('admin.settings.smtp.passwordConfiguredHint')
: t('admin.settings.smtp.passwordHint')
}}
</p>
</div>
<div>
<label class="mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ t('admin.settings.smtp.fromEmail') }}
</label>
<input
v-model="form.smtp_from_email"
type="email"
class="input"
:placeholder="t('admin.settings.smtp.fromEmailPlaceholder')"
/>
</div>
<div>
<label class="mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ t('admin.settings.smtp.fromName') }}
</label>
<input
v-model="form.smtp_from_name"
type="text"
class="input"
:placeholder="t('admin.settings.smtp.fromNamePlaceholder')"
/>
</div>
</div>
<!-- Use TLS Toggle -->
<div
class="flex items-center justify-between border-t border-gray-100 pt-4 dark:border-dark-700"
>
<div>
<label class="font-medium text-gray-900 dark:text-white">{{
t('admin.settings.smtp.useTls')
}}</label>
<p class="text-sm text-gray-500 dark:text-gray-400">
{{ t('admin.settings.smtp.useTlsHint') }}
</p>
</div>
<Toggle v-model="form.smtp_use_tls" />
</div>
</div>
</div>
<!-- Purchase Subscription Page --> <!-- Purchase Subscription Page -->
<div class="card"> <div class="card">
<div class="border-b border-gray-100 px-6 py-4 dark:border-dark-700"> <div class="border-b border-gray-100 px-6 py-4 dark:border-dark-700">
@@ -1309,6 +1203,168 @@
</div> </div>
</div> </div>
</div><!-- /Tab: General -->
<!-- Tab: Email -->
<div v-show="activeTab === 'email'" class="space-y-6">
<!-- Email disabled hint - show when email_verify_enabled is off -->
<div v-if="!form.email_verify_enabled" class="card">
<div class="p-6">
<div class="flex items-start gap-3">
<Icon name="mail" size="md" class="mt-0.5 flex-shrink-0 text-gray-400 dark:text-gray-500" />
<div>
<h3 class="font-medium text-gray-900 dark:text-white">
{{ t('admin.settings.emailTabDisabledTitle') }}
</h3>
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
{{ t('admin.settings.emailTabDisabledHint') }}
</p>
</div>
</div>
</div>
</div>
<!-- SMTP Settings - Only show when email verification is enabled -->
<div v-if="form.email_verify_enabled" class="card">
<div
class="flex items-center justify-between border-b border-gray-100 px-6 py-4 dark:border-dark-700"
>
<div>
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">
{{ t('admin.settings.smtp.title') }}
</h2>
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
{{ t('admin.settings.smtp.description') }}
</p>
</div>
<button
type="button"
@click="testSmtpConnection"
:disabled="testingSmtp"
class="btn btn-secondary btn-sm"
>
<svg v-if="testingSmtp" class="h-4 w-4 animate-spin" fill="none" viewBox="0 0 24 24">
<circle
class="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
stroke-width="4"
></circle>
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
></path>
</svg>
{{
testingSmtp
? t('admin.settings.smtp.testing')
: t('admin.settings.smtp.testConnection')
}}
</button>
</div>
<div class="space-y-6 p-6">
<div class="grid grid-cols-1 gap-6 md:grid-cols-2">
<div>
<label class="mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ t('admin.settings.smtp.host') }}
</label>
<input
v-model="form.smtp_host"
type="text"
class="input"
:placeholder="t('admin.settings.smtp.hostPlaceholder')"
/>
</div>
<div>
<label class="mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ t('admin.settings.smtp.port') }}
</label>
<input
v-model.number="form.smtp_port"
type="number"
min="1"
max="65535"
class="input"
:placeholder="t('admin.settings.smtp.portPlaceholder')"
/>
</div>
<div>
<label class="mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ t('admin.settings.smtp.username') }}
</label>
<input
v-model="form.smtp_username"
type="text"
class="input"
:placeholder="t('admin.settings.smtp.usernamePlaceholder')"
/>
</div>
<div>
<label class="mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ t('admin.settings.smtp.password') }}
</label>
<input
v-model="form.smtp_password"
type="password"
class="input"
:placeholder="
form.smtp_password_configured
? t('admin.settings.smtp.passwordConfiguredPlaceholder')
: t('admin.settings.smtp.passwordPlaceholder')
"
/>
<p class="mt-1.5 text-xs text-gray-500 dark:text-gray-400">
{{
form.smtp_password_configured
? t('admin.settings.smtp.passwordConfiguredHint')
: t('admin.settings.smtp.passwordHint')
}}
</p>
</div>
<div>
<label class="mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ t('admin.settings.smtp.fromEmail') }}
</label>
<input
v-model="form.smtp_from_email"
type="email"
class="input"
:placeholder="t('admin.settings.smtp.fromEmailPlaceholder')"
/>
</div>
<div>
<label class="mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ t('admin.settings.smtp.fromName') }}
</label>
<input
v-model="form.smtp_from_name"
type="text"
class="input"
:placeholder="t('admin.settings.smtp.fromNamePlaceholder')"
/>
</div>
</div>
<!-- Use TLS Toggle -->
<div
class="flex items-center justify-between border-t border-gray-100 pt-4 dark:border-dark-700"
>
<div>
<label class="font-medium text-gray-900 dark:text-white">{{
t('admin.settings.smtp.useTls')
}}</label>
<p class="text-sm text-gray-500 dark:text-gray-400">
{{ t('admin.settings.smtp.useTlsHint') }}
</p>
</div>
<Toggle v-model="form.smtp_use_tls" />
</div>
</div>
</div>
<!-- Send Test Email - Only show when email verification is enabled --> <!-- Send Test Email - Only show when email verification is enabled -->
<div v-if="form.email_verify_enabled" class="card"> <div v-if="form.email_verify_enabled" class="card">
<div class="border-b border-gray-100 px-6 py-4 dark:border-dark-700"> <div class="border-b border-gray-100 px-6 py-4 dark:border-dark-700">
@@ -1367,6 +1423,7 @@
</div> </div>
</div> </div>
</div> </div>
</div><!-- /Tab: Email -->
<!-- Save Button --> <!-- Save Button -->
<div class="flex justify-end"> <div class="flex justify-end">
@@ -1424,6 +1481,16 @@ import {
const { t } = useI18n() const { t } = useI18n()
const appStore = useAppStore() const appStore = useAppStore()
const adminSettingsStore = useAdminSettingsStore() const adminSettingsStore = useAdminSettingsStore()
type SettingsTab = 'general' | 'security' | 'users' | 'gateway' | 'email'
const activeTab = ref<SettingsTab>('general')
const settingsTabs = [
{ key: 'general' as SettingsTab, icon: 'home' as const },
{ key: 'security' as SettingsTab, icon: 'shield' as const },
{ key: 'users' as SettingsTab, icon: 'user' as const },
{ key: 'gateway' as SettingsTab, icon: 'server' as const },
{ key: 'email' as SettingsTab, icon: 'mail' as const },
]
const { copyToClipboard } = useClipboard() const { copyToClipboard } = useClipboard()
const loading = ref(true) const loading = ref(true)
@@ -1975,4 +2042,56 @@ onMounted(() => {
.default-sub-delete-btn { .default-sub-delete-btn {
@apply h-[42px]; @apply h-[42px];
} }
/* ============ Settings Tab Navigation ============ */
.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
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);
}
@media (min-width: 640px) {
.settings-tabs {
@apply flex;
}
}
.settings-tab {
@apply relative flex flex-1 items-center justify-center gap-2
whitespace-nowrap rounded-xl px-4 py-2.5
text-sm font-medium
text-gray-500 dark:text-dark-400
transition-all duration-200 ease-out;
}
.settings-tab:hover:not(.settings-tab-active) {
@apply text-gray-700 dark:text-gray-300;
background: rgb(0 0 0 / 0.03);
}
:root.dark .settings-tab:hover:not(.settings-tab-active) {
background: rgb(255 255 255 / 0.04);
}
.settings-tab-active {
@apply text-primary-600 dark:text-primary-400;
background: linear-gradient(135deg, rgba(20, 184, 166, 0.08), rgba(20, 184, 166, 0.03));
box-shadow: 0 1px 2px rgba(20, 184, 166, 0.1);
}
:root.dark .settings-tab-active {
background: linear-gradient(135deg, rgba(45, 212, 191, 0.12), rgba(45, 212, 191, 0.05));
box-shadow: 0 1px 3px rgb(0 0 0 / 0.25);
}
.settings-tab-icon {
@apply flex h-7 w-7 items-center justify-center rounded-lg
transition-all duration-200;
}
.settings-tab-active .settings-tab-icon {
@apply bg-primary-500/15 text-primary-600
dark:bg-primary-400/15 dark:text-primary-400;
}
</style> </style>