"use client" import * as React from "react" import { useTranslations } from "next-intl" import { toast } from "sonner" import { Loader2 } from "lucide-react" import { getAdminSystemSettingsAction, saveAdminSystemSettingsAction, } from "@/modules/settings/actions-system-settings" import { Button } from "@/shared/components/ui/button" import { SchoolInfoCard, type SchoolInfoValues } from "@/modules/settings/components/admin-school-info-card" import { SecurityPolicyCard, type SecurityPolicyValues } from "@/modules/settings/components/admin-security-policy-card" import { FileUploadCard, type FileUploadValues } from "@/modules/settings/components/admin-file-upload-card" import { NotificationConfigCard, type NotificationConfigValues } from "@/modules/settings/components/admin-notification-config-card" import { BrandConfigCard } from "@/modules/settings/components/brand-config-card" interface AdminSettingsFormValues { schoolInfo: SchoolInfoValues securityPolicy: SecurityPolicyValues fileUpload: FileUploadValues notificationConfig: NotificationConfigValues } const DEFAULT_VALUES: AdminSettingsFormValues = { schoolInfo: { schoolName: "", schoolCode: "", schoolPhone: "", schoolEmail: "", schoolAddress: "", schoolDescription: "", }, securityPolicy: { passwordMinLength: 8, sessionTimeout: 60, requireSpecialChar: true, requireUppercase: false, forcePasswordChange: true, }, fileUpload: { maxFileSize: 10, allowedTypes: "jpg,png,pdf,docx,xlsx,pptx", }, notificationConfig: { notifyNewUser: true, notifyScheduleChange: true, notifyAnnouncement: false, }, } /** * 管理员系统设置视图 * * 通过 Server Actions 加载和保存系统设置,数据持久化到 system_settings 表。 * 4 个 Card:学校信息 / 安全策略 / 文件上传 / 通知配置。 * 所有文本通过 settings.admin.* i18n 键获取。 */ export function AdminSettingsView(): React.ReactElement { const t = useTranslations("settings.admin") const [values, setValues] = React.useState(DEFAULT_VALUES) const [loadedValues, setLoadedValues] = React.useState(DEFAULT_VALUES) const [loading, setLoading] = React.useState(true) const [saving, setSaving] = React.useState(false) React.useEffect(() => { let cancelled = false async function load(): Promise { try { const result = await getAdminSystemSettingsAction() if (!cancelled && result.success && result.data) { setValues(result.data) setLoadedValues(result.data) } } catch { // 加载失败时使用默认值 } finally { if (!cancelled) setLoading(false) } } void load() return () => { cancelled = true } }, []) // dirty 检测:当前值与加载值不一致时为 dirty const isDirty = React.useMemo( () => JSON.stringify(values) !== JSON.stringify(loadedValues), [values, loadedValues], ) const handleSave = async (e: React.FormEvent): Promise => { e.preventDefault() if (!isDirty) return setSaving(true) try { const result = await saveAdminSystemSettingsAction(values) if (result.success) { toast.success(t("saveSuccess")) setLoadedValues(values) } else { toast.error(result.message || t("saveFailure")) } } catch { toast.error(t("saveFailure")) } finally { setSaving(false) } } const handleReset = (): void => { setValues(loadedValues) } const updateSchoolInfo = (key: keyof SchoolInfoValues, value: string): void => { setValues((prev) => ({ ...prev, schoolInfo: { ...prev.schoolInfo, [key]: value } })) } const updateSecurityPolicy = ( key: keyof SecurityPolicyValues, value: number | boolean ): void => { setValues((prev) => ({ ...prev, securityPolicy: { ...prev.securityPolicy, [key]: value } })) } const updateFileUpload = ( key: keyof FileUploadValues, value: number | string ): void => { setValues((prev) => ({ ...prev, fileUpload: { ...prev.fileUpload, [key]: value } })) } const updateNotificationConfig = ( key: keyof NotificationConfigValues, value: boolean ): void => { setValues((prev) => ({ ...prev, notificationConfig: { ...prev.notificationConfig, [key]: value } })) } if (loading) { return (
) } return (

{t("title")}

{t("description")}

{/* audit-P2-6: 品牌配置(独立表单 + 独立保存,不嵌入主表单以防嵌套 form) */}
) }