'use client'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { useEffect, useMemo, useState } from 'react'; import { EmptyState } from '@/components/feedback/empty-state'; import { ErrorState } from '@/components/feedback/error-state'; import { InlineMessage } from '@/components/feedback/inline-message'; import { LoadingState } from '@/components/feedback/loading-state'; import { TurnstileWidget } from '@/components/forms/turnstile-widget'; import { useAuth } from '@/components/providers/auth-provider'; import { PageHeader } from '@/components/layout/page-header'; import { AppCard } from '@/components/ui/app-card'; import { StatusBadge } from '@/components/ui/status-badge'; import { sendEmailVerification } from '@/features/auth/api/auth'; import { getPublicStatus } from '@/features/auth/api/public'; import { bindEmail, bindWeChat, generateAccessToken, getOptions, getSettingsProfile, updateOption, updateSelf, } from '@/features/settings/api/settings'; import type { OptionItem, UpdateSelfPayload } from '@/features/settings/types'; import { CodeBlock, PrimaryButton, ResourceField, ResourceInput, ResourceTextarea, SecondaryButton, ToggleField, } from '@/features/shared/components/resource-primitives'; import { formatDateTime } from '@/lib/utils/date'; const settingsQueryKey = ['settings', 'options'] as const; const defaultSystemFields = { ServerAddress: '', PasswordLoginEnabled: true, PasswordRegisterEnabled: true, EmailVerificationEnabled: false, GitHubOAuthEnabled: false, WeChatAuthEnabled: false, TurnstileCheckEnabled: false, RegisterEnabled: true, SMTPServer: '', SMTPPort: '587', SMTPAccount: '', SMTPToken: '', GitHubClientId: '', GitHubClientSecret: '', WeChatServerAddress: '', WeChatServerToken: '', WeChatAccountQRCodeImageURL: '', TurnstileSiteKey: '', TurnstileSecretKey: '', }; const defaultOperationFields = { AgentHeartbeatInterval: '30000', AgentSyncInterval: '30000', NodeOfflineThreshold: '120000', AgentUpdateRepo: 'Rain-kl/ATSFlare', GlobalApiRateLimitNum: '300', GlobalApiRateLimitDuration: '180', GlobalWebRateLimitNum: '300', GlobalWebRateLimitDuration: '180', UploadRateLimitNum: '50', UploadRateLimitDuration: '60', DownloadRateLimitNum: '50', DownloadRateLimitDuration: '60', CriticalRateLimitNum: '100', CriticalRateLimitDuration: '1200', ServerAddress: '', }; const defaultOtherFields = { Notice: '', SystemName: '', HomePageLink: '', About: '', Footer: '', }; const defaultProfileFields: UpdateSelfPayload = { username: '', display_name: '', password: '', }; type FeedbackState = { tone: 'info' | 'success' | 'danger'; message: string; }; type SettingsTab = 'personal' | 'operation' | 'system' | 'other'; function getErrorMessage(error: unknown) { return error instanceof Error ? error.message : '请求失败,请稍后重试。'; } function optionsToMap(options: OptionItem[] | undefined) { return (options ?? []).reduce>( (accumulator, option) => { accumulator[option.key] = option.value; return accumulator; }, {}, ); } function toBoolean(value: string | undefined, fallback: boolean) { if (value === undefined) { return fallback; } return value === 'true'; } function normalizeServerUrl(value: string) { return value.trim().replace(/\/+$/, ''); } function formatDurationLabel(value: string) { const milliseconds = Number.parseInt(value, 10); if (Number.isNaN(milliseconds)) { return value; } if (milliseconds >= 60000) { return `${milliseconds / 60000} 分钟`; } return `${milliseconds / 1000} 秒`; } function formatSecondsLabel(value: string) { const seconds = Number.parseInt(value, 10); if (Number.isNaN(seconds)) { return value; } if (seconds >= 3600 && seconds % 3600 === 0) { return `${seconds / 3600} 小时`; } if (seconds >= 60 && seconds % 60 === 0) { return `${seconds / 60} 分钟`; } return `${seconds} 秒`; } async function copyToClipboard(value: string) { await navigator.clipboard.writeText(value); } export function SettingsPage() { const queryClient = useQueryClient(); const { refreshUser, user } = useAuth(); const [activeTab, setActiveTab] = useState('personal'); const [feedback, setFeedback] = useState(null); const [busyKey, setBusyKey] = useState(null); const [profileFields, setProfileFields] = useState(defaultProfileFields); const [systemFields, setSystemFields] = useState(defaultSystemFields); const [operationFields, setOperationFields] = useState( defaultOperationFields, ); const [otherFields, setOtherFields] = useState(defaultOtherFields); const [accessToken, setAccessToken] = useState(''); const [wechatCode, setWeChatCode] = useState(''); const [emailAddress, setEmailAddress] = useState(''); const [emailCode, setEmailCode] = useState(''); const [emailTurnstileToken, setEmailTurnstileToken] = useState(''); const isRoot = (user?.role ?? 0) >= 100; const publicStatusQuery = useQuery({ queryKey: ['public-status'], queryFn: getPublicStatus, }); const profileQuery = useQuery({ queryKey: ['settings', 'profile'], queryFn: getSettingsProfile, }); const optionsQuery = useQuery({ queryKey: settingsQueryKey, queryFn: getOptions, enabled: isRoot, }); useEffect(() => { if (profileQuery.data) { setProfileFields({ username: profileQuery.data.username, display_name: profileQuery.data.display_name || '', password: '', }); setEmailAddress(profileQuery.data.email || ''); } }, [profileQuery.data]); useEffect(() => { const publicStatus = publicStatusQuery.data; if (!publicStatus) { return; } setSystemFields((previous) => ({ ...previous, ServerAddress: publicStatus.server_address || previous.ServerAddress, GitHubClientId: publicStatus.github_client_id || previous.GitHubClientId, WeChatAccountQRCodeImageURL: publicStatus.wechat_qrcode || previous.WeChatAccountQRCodeImageURL, TurnstileSiteKey: publicStatus.turnstile_site_key || previous.TurnstileSiteKey, })); setOtherFields((previous) => ({ ...previous, SystemName: publicStatus.system_name || previous.SystemName, HomePageLink: publicStatus.home_page_link || previous.HomePageLink, Footer: publicStatus.footer_html || previous.Footer, })); setOperationFields((previous) => ({ ...previous, ServerAddress: publicStatus.server_address || previous.ServerAddress, })); }, [publicStatusQuery.data]); useEffect(() => { if (!optionsQuery.data) { return; } const optionMap = optionsToMap(optionsQuery.data); setSystemFields({ ServerAddress: optionMap.ServerAddress ?? '', PasswordLoginEnabled: toBoolean(optionMap.PasswordLoginEnabled, true), PasswordRegisterEnabled: toBoolean( optionMap.PasswordRegisterEnabled, true, ), EmailVerificationEnabled: toBoolean( optionMap.EmailVerificationEnabled, false, ), GitHubOAuthEnabled: toBoolean(optionMap.GitHubOAuthEnabled, false), WeChatAuthEnabled: toBoolean(optionMap.WeChatAuthEnabled, false), TurnstileCheckEnabled: toBoolean(optionMap.TurnstileCheckEnabled, false), RegisterEnabled: toBoolean(optionMap.RegisterEnabled, true), SMTPServer: optionMap.SMTPServer ?? '', SMTPPort: optionMap.SMTPPort ?? '587', SMTPAccount: optionMap.SMTPAccount ?? '', SMTPToken: '', GitHubClientId: optionMap.GitHubClientId ?? '', GitHubClientSecret: '', WeChatServerAddress: optionMap.WeChatServerAddress ?? '', WeChatServerToken: '', WeChatAccountQRCodeImageURL: optionMap.WeChatAccountQRCodeImageURL ?? '', TurnstileSiteKey: optionMap.TurnstileSiteKey ?? '', TurnstileSecretKey: '', }); setOperationFields({ AgentHeartbeatInterval: optionMap.AgentHeartbeatInterval ?? '30000', AgentSyncInterval: optionMap.AgentSyncInterval ?? '30000', NodeOfflineThreshold: optionMap.NodeOfflineThreshold ?? '120000', AgentUpdateRepo: optionMap.AgentUpdateRepo ?? 'Rain-kl/ATSFlare', GlobalApiRateLimitNum: optionMap.GlobalApiRateLimitNum ?? '300', GlobalApiRateLimitDuration: optionMap.GlobalApiRateLimitDuration ?? '180', GlobalWebRateLimitNum: optionMap.GlobalWebRateLimitNum ?? '300', GlobalWebRateLimitDuration: optionMap.GlobalWebRateLimitDuration ?? '180', UploadRateLimitNum: optionMap.UploadRateLimitNum ?? '50', UploadRateLimitDuration: optionMap.UploadRateLimitDuration ?? '60', DownloadRateLimitNum: optionMap.DownloadRateLimitNum ?? '50', DownloadRateLimitDuration: optionMap.DownloadRateLimitDuration ?? '60', CriticalRateLimitNum: optionMap.CriticalRateLimitNum ?? '100', CriticalRateLimitDuration: optionMap.CriticalRateLimitDuration ?? '1200', ServerAddress: optionMap.ServerAddress ?? publicStatusQuery.data?.server_address ?? '', }); setOtherFields({ Notice: optionMap.Notice ?? '', SystemName: optionMap.SystemName ?? '', HomePageLink: optionMap.HomePageLink ?? '', About: optionMap.About ?? '', Footer: optionMap.Footer ?? '', }); }, [optionsQuery.data, publicStatusQuery.data?.server_address]); const accessTokenMutation = useMutation({ mutationFn: generateAccessToken, onSuccess: (token) => { setAccessToken(token); setFeedback({ tone: 'success', message: '访问令牌已重置,并已在当前页面展示。', }); }, onError: (error) => { setFeedback({ tone: 'danger', message: getErrorMessage(error) }); }, }); const tabs = useMemo( () => [ { key: 'personal' as const, label: '个人设置', description: '更新个人资料、绑定账号与访问令牌。', }, ...(isRoot ? [ { key: 'operation' as const, label: '运维设置', description: 'Agent 参数、更新仓库与接口限流设置。', }, { key: 'system' as const, label: '系统设置', description: '登录注册、SMTP、OAuth 与风控开关。', }, { key: 'other' as const, label: '其他设置', description: '公告、关于与品牌信息。', }, ] : []), ], [isRoot], ); const runBusyAction = async (key: string, action: () => Promise) => { setBusyKey(key); setFeedback(null); try { await action(); } catch (error) { setFeedback({ tone: 'danger', message: getErrorMessage(error) }); } finally { setBusyKey(null); } }; const saveOptionEntries = async ( entries: Array<[string, string]>, successMessage: string, ) => { for (const [key, value] of entries) { await updateOption(key, value); } await queryClient.invalidateQueries({ queryKey: settingsQueryKey }); await queryClient.invalidateQueries({ queryKey: ['public-status'] }); setFeedback({ tone: 'success', message: successMessage }); }; const handleProfileSave = () => { void runBusyAction('profile', async () => { await updateSelf({ username: profileFields.username.trim(), display_name: profileFields.display_name.trim(), password: profileFields.password, }); await Promise.all([ queryClient.invalidateQueries({ queryKey: ['settings', 'profile'] }), refreshUser(), ]); setProfileFields((previous) => ({ ...previous, password: '' })); setFeedback({ tone: 'success', message: '个人资料已更新。' }); }); }; const handleEmailVerification = () => { if (!emailAddress.trim()) { setFeedback({ tone: 'danger', message: '请输入要绑定的邮箱地址。' }); return; } if (publicStatusQuery.data?.turnstile_check && !emailTurnstileToken) { setFeedback({ tone: 'info', message: '请先完成人机验证。' }); return; } void runBusyAction('email-send', async () => { await sendEmailVerification( emailAddress.trim(), emailTurnstileToken || undefined, ); setFeedback({ tone: 'success', message: '验证码已发送,请检查邮箱。' }); }); }; const handleBindEmail = () => { if (!emailAddress.trim() || !emailCode.trim()) { setFeedback({ tone: 'danger', message: '请输入邮箱地址和验证码。' }); return; } void runBusyAction('email-bind', async () => { await bindEmail(emailAddress.trim(), emailCode.trim()); setEmailCode(''); await Promise.all([ queryClient.invalidateQueries({ queryKey: ['settings', 'profile'] }), refreshUser(), ]); setFeedback({ tone: 'success', message: '邮箱已绑定。' }); }); }; const handleBindWeChat = () => { if (!wechatCode.trim()) { setFeedback({ tone: 'danger', message: '请输入微信验证码。' }); return; } void runBusyAction('wechat-bind', async () => { await bindWeChat(wechatCode.trim()); setWeChatCode(''); await Promise.all([ queryClient.invalidateQueries({ queryKey: ['settings', 'profile'] }), refreshUser(), ]); setFeedback({ tone: 'success', message: '微信账号已绑定。' }); }); }; const handleToggleOption = ( key: keyof typeof systemFields, nextValue: boolean, ) => { setSystemFields((previous) => ({ ...previous, [key]: nextValue })); void runBusyAction(`toggle-${key}`, async () => { await saveOptionEntries([[key, String(nextValue)]], '系统开关已更新。'); }); }; const renderTabContent = () => { if (profileQuery.isLoading || publicStatusQuery.isLoading) { return ; } if (profileQuery.isError) { return ( ); } if (publicStatusQuery.isError) { return ( ); } const publicStatus = publicStatusQuery.data; const profile = profileQuery.data; if (!publicStatus || !profile) { return ( ); } if (activeTab === 'personal') { return (
{busyKey === 'profile' ? '保存中...' : '保存资料'} } >
setProfileFields((previous) => ({ ...previous, username: event.target.value, })) } placeholder="请输入用户名" /> setProfileFields((previous) => ({ ...previous, display_name: event.target.value, })) } placeholder="请输入显示名称" /> setProfileFields((previous) => ({ ...previous, password: event.target.value, })) } placeholder="请输入新密码" />
accessTokenMutation.mutate()} disabled={accessTokenMutation.isPending} > {accessTokenMutation.isPending ? '生成中...' : '重置令牌'} } >

当前角色

已绑定邮箱

{profile.email || '未绑定'}

{accessToken ? (
{accessToken} void copyToClipboard(accessToken)} > 复制令牌
) : ( )}

GitHub 账号

当前状态: {profile.github_id ? `已绑定 ${profile.github_id}` : '未绑定'}

window.open( `https://github.com/login/oauth/authorize?client_id=${publicStatus.github_client_id}&scope=user:email`, '_blank', 'noopener,noreferrer', ) } disabled={ !publicStatus.github_oauth || !publicStatus.github_client_id } > {publicStatus.github_oauth ? '绑定 GitHub' : '未启用 GitHub OAuth'}

微信账号

当前状态: {profile.wechat_id ? `已绑定 ${profile.wechat_id}` : '未绑定'}

{publicStatus.wechat_login && publicStatus.wechat_qrcode ? ( // eslint-disable-next-line @next/next/no-img-element 微信绑定二维码 ) : null} setWeChatCode(event.target.value)} placeholder="请输入微信验证码" /> {busyKey === 'wechat-bind' ? '绑定中...' : '绑定微信'}

邮箱地址

当前状态: {profile.email ? `已绑定 ${profile.email}` : '未绑定'}

setEmailAddress(event.target.value)} placeholder="请输入邮箱地址" /> setEmailCode(event.target.value)} placeholder="请输入邮箱验证码" /> {publicStatus.turnstile_check ? ( publicStatus.turnstile_site_key ? ( setEmailTurnstileToken(token)} onExpire={() => setEmailTurnstileToken('')} onError={() => setEmailTurnstileToken('')} /> ) : ( ) ) : null}
{busyKey === 'email-send' ? '发送中...' : '发送验证码'} {busyKey === 'email-bind' ? '绑定中...' : '绑定邮箱'}
); } if (!isRoot) { return ( ); } if (optionsQuery.isLoading) { return ; } if (optionsQuery.isError) { return ( ); } if (activeTab === 'operation') { return (
window.open( '/swagger/index.html', '_blank', 'noopener,noreferrer', ) } > 打开接口文档 void runBusyAction('operation-intervals', async () => { const heartbeat = Number.parseInt( operationFields.AgentHeartbeatInterval, 10, ); const sync = Number.parseInt( operationFields.AgentSyncInterval, 10, ); const offline = Number.parseInt( operationFields.NodeOfflineThreshold, 10, ); if (Number.isNaN(heartbeat) || heartbeat < 5000) { throw new Error('心跳间隔不能小于 5000 毫秒。'); } if (Number.isNaN(sync) || sync < 5000) { throw new Error('同步间隔不能小于 5000 毫秒。'); } if (Number.isNaN(offline) || offline < 10000) { throw new Error('离线阈值不能小于 10000 毫秒。'); } await saveOptionEntries( [ ['AgentHeartbeatInterval', String(heartbeat)], ['AgentSyncInterval', String(sync)], ['NodeOfflineThreshold', String(offline)], ], 'Agent 运行参数已保存。', ); }) } disabled={busyKey === 'operation-intervals'} > {busyKey === 'operation-intervals' ? '保存中...' : '保存运行参数'}
} >
setOperationFields((previous) => ({ ...previous, AgentHeartbeatInterval: event.target.value, })) } /> setOperationFields((previous) => ({ ...previous, AgentSyncInterval: event.target.value, })) } /> setOperationFields((previous) => ({ ...previous, NodeOfflineThreshold: event.target.value, })) } />
void runBusyAction('operation-repo', async () => { await saveOptionEntries( [ [ 'AgentUpdateRepo', operationFields.AgentUpdateRepo.trim(), ], ], 'Agent 更新仓库已保存。', ); }) } disabled={busyKey === 'operation-repo'} > {busyKey === 'operation-repo' ? '保存中...' : '保存更新仓库'} } > setOperationFields((previous) => ({ ...previous, AgentUpdateRepo: event.target.value, })) } placeholder="Rain-kl/ATSFlare" />

服务端版本

{publicStatus.version}

Server 启动时间

{formatDateTime(new Date(publicStatus.start_time * 1000))}

运行模式

静态导出 + Go Server 托管

版本入口

点击顶栏“版本”可检查更新、查看 Release Notes,并直接触发服务端升级。

); } if (activeTab === 'system') { return (
handleToggleOption('PasswordLoginEnabled', checked) } disabled={busyKey === 'toggle-PasswordLoginEnabled'} /> handleToggleOption('PasswordRegisterEnabled', checked) } disabled={busyKey === 'toggle-PasswordRegisterEnabled'} /> handleToggleOption('EmailVerificationEnabled', checked) } disabled={busyKey === 'toggle-EmailVerificationEnabled'} /> handleToggleOption('GitHubOAuthEnabled', checked) } disabled={busyKey === 'toggle-GitHubOAuthEnabled'} /> handleToggleOption('WeChatAuthEnabled', checked) } disabled={busyKey === 'toggle-WeChatAuthEnabled'} /> handleToggleOption('TurnstileCheckEnabled', checked) } disabled={busyKey === 'toggle-TurnstileCheckEnabled'} /> handleToggleOption('RegisterEnabled', checked) } disabled={busyKey === 'toggle-RegisterEnabled'} />
void runBusyAction('system-core', async () => { await saveOptionEntries( [ [ 'ServerAddress', normalizeServerUrl(systemFields.ServerAddress), ], ['SMTPServer', systemFields.SMTPServer.trim()], ['SMTPPort', systemFields.SMTPPort.trim()], ['SMTPAccount', systemFields.SMTPAccount.trim()], ['SMTPToken', systemFields.SMTPToken.trim()], ], '通用与 SMTP 设置已保存。', ); }) } disabled={busyKey === 'system-core'} > {busyKey === 'system-core' ? '保存中...' : '保存通用设置'} } >
setSystemFields((previous) => ({ ...previous, ServerAddress: event.target.value, })) } placeholder="https://yourdomain.com" />
setSystemFields((previous) => ({ ...previous, SMTPServer: event.target.value, })) } placeholder="smtp.qq.com" /> setSystemFields((previous) => ({ ...previous, SMTPPort: event.target.value, })) } placeholder="587" /> setSystemFields((previous) => ({ ...previous, SMTPAccount: event.target.value, })) } placeholder="name@example.com" /> setSystemFields((previous) => ({ ...previous, SMTPToken: event.target.value, })) } placeholder="请输入新的 SMTP 凭证" />
void runBusyAction('system-integrations', async () => { await saveOptionEntries( [ [ 'GitHubClientId', systemFields.GitHubClientId.trim(), ], [ 'GitHubClientSecret', systemFields.GitHubClientSecret.trim(), ], [ 'WeChatServerAddress', normalizeServerUrl( systemFields.WeChatServerAddress, ), ], [ 'WeChatServerToken', systemFields.WeChatServerToken.trim(), ], [ 'WeChatAccountQRCodeImageURL', systemFields.WeChatAccountQRCodeImageURL.trim(), ], [ 'TurnstileSiteKey', systemFields.TurnstileSiteKey.trim(), ], [ 'TurnstileSecretKey', systemFields.TurnstileSecretKey.trim(), ], ], '第三方集成设置已保存。', ); }) } disabled={busyKey === 'system-integrations'} > {busyKey === 'system-integrations' ? '保存中...' : '保存集成设置'} } >
setSystemFields((previous) => ({ ...previous, GitHubClientId: event.target.value, })) } /> setSystemFields((previous) => ({ ...previous, GitHubClientSecret: event.target.value, })) } /> setSystemFields((previous) => ({ ...previous, WeChatServerAddress: event.target.value, })) } /> setSystemFields((previous) => ({ ...previous, WeChatServerToken: event.target.value, })) } /> setSystemFields((previous) => ({ ...previous, WeChatAccountQRCodeImageURL: event.target.value, })) } /> setSystemFields((previous) => ({ ...previous, TurnstileSiteKey: event.target.value, })) } /> setSystemFields((previous) => ({ ...previous, TurnstileSecretKey: event.target.value, })) } />
void runBusyAction('operation-rate-limit', async () => { const entries = [ [ 'GlobalApiRateLimitNum', operationFields.GlobalApiRateLimitNum, ], [ 'GlobalApiRateLimitDuration', operationFields.GlobalApiRateLimitDuration, ], [ 'GlobalWebRateLimitNum', operationFields.GlobalWebRateLimitNum, ], [ 'GlobalWebRateLimitDuration', operationFields.GlobalWebRateLimitDuration, ], [ 'UploadRateLimitNum', operationFields.UploadRateLimitNum, ], [ 'UploadRateLimitDuration', operationFields.UploadRateLimitDuration, ], [ 'DownloadRateLimitNum', operationFields.DownloadRateLimitNum, ], [ 'DownloadRateLimitDuration', operationFields.DownloadRateLimitDuration, ], [ 'CriticalRateLimitNum', operationFields.CriticalRateLimitNum, ], [ 'CriticalRateLimitDuration', operationFields.CriticalRateLimitDuration, ], ] as const; for (const [key, rawValue] of entries) { const parsedValue = Number.parseInt(rawValue, 10); if (Number.isNaN(parsedValue) || parsedValue <= 0) { throw new Error(`${key} 必须为大于 0 的整数。`); } if (key.endsWith('Duration') && parsedValue > 1200) { throw new Error(`${key} 不能超过 1200 秒。`); } } await saveOptionEntries( entries.map(([key, value]) => [ key, String(Number.parseInt(value, 10)), ]), '限流设置已保存。', ); }) } disabled={busyKey === 'operation-rate-limit'} > {busyKey === 'operation-rate-limit' ? '保存中...' : '保存限流设置'} } >

全局 API 限流

作用于 `/api` 下的通用请求。

setOperationFields((previous) => ({ ...previous, GlobalApiRateLimitNum: event.target.value, })) } /> setOperationFields((previous) => ({ ...previous, GlobalApiRateLimitDuration: event.target.value, })) } />

全局 Web 限流

作用于页面和静态资源请求,过低会更容易触发 429。

setOperationFields((previous) => ({ ...previous, GlobalWebRateLimitNum: event.target.value, })) } /> setOperationFields((previous) => ({ ...previous, GlobalWebRateLimitDuration: event.target.value, })) } />

上传 / 下载限流

用于文件上传与下载接口,建议保留相对严格的阈值。

setOperationFields((previous) => ({ ...previous, UploadRateLimitNum: event.target.value, })) } /> setOperationFields((previous) => ({ ...previous, UploadRateLimitDuration: event.target.value, })) } /> setOperationFields((previous) => ({ ...previous, DownloadRateLimitNum: event.target.value, })) } /> setOperationFields((previous) => ({ ...previous, DownloadRateLimitDuration: event.target.value, })) } />

敏感接口限流

用于登录、注册、验证码、重置密码和 OAuth 等接口。

setOperationFields((previous) => ({ ...previous, CriticalRateLimitNum: event.target.value, })) } /> setOperationFields((previous) => ({ ...previous, CriticalRateLimitDuration: event.target.value, })) } />
); } return (
void runBusyAction('other-brand', async () => { await saveOptionEntries( [ ['Notice', otherFields.Notice], ['SystemName', otherFields.SystemName.trim()], ['HomePageLink', otherFields.HomePageLink.trim()], ['Footer', otherFields.Footer], ], '公告与品牌设置已保存。', ); }) } disabled={busyKey === 'other-brand'} > {busyKey === 'other-brand' ? '保存中...' : '保存基础信息'} } >
setOtherFields((previous) => ({ ...previous, SystemName: event.target.value, })) } placeholder="ATSFlare" /> setOtherFields((previous) => ({ ...previous, HomePageLink: event.target.value, })) } placeholder="https://example.com" /> setOtherFields((previous) => ({ ...previous, Notice: event.target.value, })) } placeholder="可在此编写首页公告内容" /> setOtherFields((previous) => ({ ...previous, Footer: event.target.value, })) } placeholder="留空则使用默认页脚" />
void runBusyAction('other-about', async () => { await saveOptionEntries( [['About', otherFields.About]], '关于页内容已保存。', ); }) } disabled={busyKey === 'other-about'} > {busyKey === 'other-about' ? '保存中...' : '保存关于内容'} } >
setOtherFields((previous) => ({ ...previous, About: event.target.value, })) } placeholder="在这里编写关于 ATSFlare 的介绍内容" className="min-h-48" />
); }; return (
{feedback ? ( ) : null}
{tabs.map((tab) => ( ))}
{renderTabContent()}
); }