"use client"; import React, { useState } from "react"; import { gql, useMutation } from "@apollo/client"; import { Settings, Globe, Palette, Shield, Users, Database, Mail, FileText, Server, HardDrive, Lock, User, ToggleLeft, RefreshCw, Download, Upload, Save, CheckCircle, AlertCircle } from "lucide-react"; import { AdminPanelConfig } from "@/types/admin-panel"; import { commonConfigSchema, zodErrorsToAdminErrors } from "@/lib/config-zod"; import { SiteOpsConfigType } from "@/types/site-config"; import { ConfigItemType } from "@/hooks/use-site-config"; import { UpdateConfig } from "@/types/config"; import { Button } from "@/components/ui/button"; import { toast } from "sonner"; // GraphQL Mutation const UPDATE_CONFIG_BATCH = gql` mutation UpdateConfigBatch($input: [UpdateConfig!]!) { updateConfigBatch(input: $input) } `; // 创建基于后端数据的动态管理面板配置(带Mutation) export function createDynamicAdminConfigWithMutation( data?: SiteOpsConfigType, onExport?: () => Promise, onImport?: (file: File) => Promise, configs?: ConfigItemType[] ): AdminPanelConfig { // 从后端数据获取初始值(安全访问嵌套属性) const getInitialValues = () => { if (!data) return {}; return { // Site 配置 'site.name': data.site?.info?.name || "MMAP System", 'site.description': "", 'site.keywords': "", 'site.url': "/", 'site.logo': data.site?.brand?.logo_url || "/images/logo.png", 'site.copyright': "", 'site.icp': "", 'site.icp_url': "", 'site.color_style': (data.site?.brand?.dark_mode_default ? 'dark' : 'light'), // User 配置 'user.default_avatar': "/images/avatar.png", 'user.default_role': 'user', 'user.register_invite_code': data.ops?.features?.invite_code_required ?? false, 'user.register_email_verification': data.ops?.features?.email_verification ?? false, 'user.open_login': true, 'user.open_reset_password': true, // Email 配置 'email.smtp_host': "", 'email.smtp_port': 465, 'email.smtp_user': "", 'email.smtp_password': "", 'email.smtp_from': "", 'email.smtp_from_name': "", 'email.smtp_from_email': "", 'email.system_template': "default", // Blog 配置 'blog.default_author': "", 'blog.default_category': "", 'blog.default_tag': "", 'blog.open_comment': true, // Logging 配置 'logging.level': 'info', 'logging.max_files': 10, 'logging.max_file_size': 10, // Cache 配置 'cache.ttl': 3600, 'cache.max_size': 1024, // Switch 配置 'switch.open_register': data.ops?.features?.registration_enabled ?? true, 'switch.open_login': true, 'switch.open_reset_password': true, 'switch.open_comment': true, 'switch.open_like': true, 'switch.open_share': true, 'switch.open_view': true }; }; return { header: { title: "系统配置管理", description: "管理站点配置、运营设置和系统参数", breadcrumbs: [ { label: "首页", href: "/" }, { label: "管理中心", href: "/admin" }, { label: "系统配置" } ], actions: [ { id: "refresh", label: "刷新数据", icon: , variant: "outline", onClick: () => window.location.reload() }, { id: "export", label: "导出配置", icon: , variant: "outline", onClick: onExport || (() => console.log("导出配置")) }, { id: "import", label: "导入配置", icon: , variant: "outline", onClick: () => { const input = document.createElement('input'); input.type = 'file'; input.accept = '.json'; input.onchange = (e) => { const file = (e.target as HTMLInputElement).files?.[0]; if (file && onImport) { onImport(file); } }; input.click(); } } ] }, tabs: [ // 内容配置 (Site + Blog) { id: "content", title: "内容配置", icon: , sections: [ { id: "site-basic", title: "站点信息", description: "网站基本信息和设置", icon: , fields: [ { id: "site.name", label: "网站名称", description: "显示在浏览器标题栏的网站名称", type: "input", value: data?.site?.info?.name || "MMAP System", placeholder: "请输入网站名称", validation: { required: true, minLength: 2, maxLength: 50 } }, { id: "site.description", label: "网站描述", description: "网站的简要描述信息", type: "textarea", rows: 3, value: "", placeholder: "请输入网站描述" }, { id: "site.keywords", label: "网站关键词", description: "SEO相关的关键词,用逗号分隔", type: "input", value: "", placeholder: "请输入关键词,用逗号分隔" }, { id: "site.url", label: "网站地址", description: "网站的主域名地址", type: "input", value: "/", placeholder: "请输入网站地址" } ] }, { id: "site-brand", title: "品牌设置", description: "网站品牌形象和主题配置", icon: , fields: [ { id: "site.logo", label: "Logo地址", description: "网站Logo图片的URL地址", type: "input", value: data?.site?.brand?.logo_url || "/images/logo.png", placeholder: "请输入Logo URL" }, { id: "site.color_style", label: "颜色风格", description: "网站的颜色主题风格", type: "select", value: (data?.site?.brand?.dark_mode_default ? 'dark' : 'light'), options: [ { label: "浅色主题", value: "light" }, { label: "深色主题", value: "dark" } ] } ] }, { id: "site-legal", title: "法律信息", description: "网站的法律相关信息", icon: , fields: [ { id: "site.copyright", label: "版权信息", description: "网站的版权声明", type: "input", value: "", placeholder: "请输入版权信息" }, { id: "site.icp", label: "ICP备案号", description: "网站的ICP备案号码", type: "input", value: "", placeholder: "请输入ICP备案号" }, { id: "site.icp_url", label: "ICP备案链接", description: "ICP备案查询链接", type: "input", value: "", placeholder: "请输入ICP备案链接" } ] }, { id: "blog-defaults", title: "博客设置", description: "博客功能的默认配置", icon: , fields: [ { id: "blog.default_author", label: "默认作者", description: "博客文章的默认作者", type: "input", value: "", placeholder: "请输入默认作者" }, { id: "blog.default_category", label: "默认分类", description: "博客文章的默认分类", type: "input", value: "", placeholder: "请输入默认分类" }, { id: "blog.default_tag", label: "默认标签", description: "博客文章的默认标签", type: "input", value: "", placeholder: "请输入默认标签" }, { id: "blog.open_comment", label: "开放评论", description: "是否允许用户对博客文章进行评论", type: "switch", value: true } ] } ] }, // 用户管理 (User + Switch) { id: "user", title: "用户管理", icon: , sections: [ { id: "user-defaults", title: "默认设置", description: "用户相关的默认配置", icon: , fields: [ { id: "user.default_avatar", label: "默认头像", description: "新用户的默认头像图片", type: "input", value: "/images/avatar.png", placeholder: "请输入默认头像URL" }, { id: "user.default_role", label: "默认角色", description: "新用户注册后的默认角色", type: "select", value: 'user', options: [ { label: "普通用户", value: "user" }, { label: "VIP用户", value: "vip" }, { label: "管理员", value: "admin" } ] } ] }, { id: "user-registration", title: "注册设置", description: "用户注册相关的配置", icon: , fields: [ { id: "user.register_invite_code", label: "需要邀请码", description: "注册时是否需要邀请码", type: "switch", value: data?.ops?.features?.invite_code_required ?? false }, { id: "user.register_email_verification", label: "需要邮箱验证", description: "注册后是否需要验证邮箱", type: "switch", value: data?.ops?.features?.email_verification ?? false } ] }, { id: "user-access", title: "访问控制", description: "用户访问和登录相关设置", icon: , fields: [ { id: "user.open_login", label: "开放登录", description: "是否允许用户登录", type: "switch", value: true }, { id: "user.open_reset_password", label: "开放重置密码", description: "是否允许用户重置密码", type: "switch", value: true } ] }, { id: "user-features", title: "功能开关", description: "用户相关功能的开关配置", icon: , fields: [ { id: "switch.open_register", label: "开放注册", description: "是否允许新用户注册", type: "switch", value: data?.ops?.features?.registration_enabled ?? true }, { id: "switch.open_comment", label: "开放评论", description: "是否允许用户进行评论", type: "switch", value: true }, { id: "switch.open_like", label: "开放点赞", description: "是否允许用户进行点赞", type: "switch", value: true }, { id: "switch.open_share", label: "开放分享", description: "是否允许用户分享内容", type: "switch", value: true }, { id: "switch.open_view", label: "开放查看", description: "是否允许用户查看内容", type: "switch", value: true } ] } ] }, // 邮件配置 { id: "email", title: "邮件配置", icon: , sections: [ { id: "email-smtp", title: "SMTP设置", description: "邮件服务器的SMTP配置", icon: , fields: [ { id: "email.smtp_host", label: "SMTP主机", description: "SMTP服务器地址", type: "input", value: "", placeholder: "请输入SMTP主机地址" }, { id: "email.smtp_port", label: "SMTP端口", description: "SMTP服务器端口号", type: "number", value: 465, validation: { required: true, min: 1, max: 65535 } }, { id: "email.smtp_user", label: "SMTP用户名", description: "SMTP服务器登录用户名", type: "input", value: "", placeholder: "请输入SMTP用户名" }, { id: "email.smtp_password", label: "SMTP密码", description: "SMTP服务器登录密码", type: "password", value: "", placeholder: "请输入SMTP密码" } ] }, { id: "email-sender", title: "发件人设置", description: "邮件发件人相关信息", icon: , fields: [ { id: "email.smtp_from", label: "发件人地址", description: "系统发送邮件的发件人地址", type: "email", value: "", placeholder: "请输入发件人邮箱" }, { id: "email.smtp_from_name", label: "发件人姓名", description: "系统发送邮件的发件人姓名", type: "input", value: "", placeholder: "请输入发件人姓名" }, { id: "email.smtp_from_email", label: "发件人邮箱", description: "系统发送邮件的发件人邮箱", type: "email", value: "", placeholder: "请输入发件人邮箱" } ] }, { id: "email-templates", title: "邮件模板", description: "邮件模板相关配置", icon: , fields: [ { id: "email.system_template", label: "系统模板", description: "系统邮件的默认模板", type: "select", value: "default", options: [ { label: "默认模板", value: "default" }, { label: "简洁模板", value: "simple" }, { label: "企业模板", value: "enterprise" } ] } ] } ] }, // 系统配置 (Logging + Cache) { id: "system", title: "系统配置", icon: , sections: [ { id: "logging-level", title: "日志级别", description: "系统日志的级别设置", icon: , fields: [ { id: "logging.level", label: "日志级别", description: "系统记录日志的最低级别", type: "select", value: 'info', options: [ { label: "调试", value: "debug" }, { label: "信息", value: "info" }, { label: "警告", value: "warn" }, { label: "错误", value: "error" }, { label: "致命", value: "fatal" } ] } ] }, { id: "logging-files", title: "日志文件管理", description: "日志文件的管理配置", icon: , fields: [ { id: "logging.max_files", label: "最大文件数", description: "保留的日志文件最大数量", type: "number", value: 10, validation: { required: true, min: 1, max: 100 } }, { id: "logging.max_file_size", label: "最大文件大小(MB)", description: "单个日志文件的最大大小", type: "number", value: 10, validation: { required: true, min: 1, max: 10240 } } ] }, { id: "cache-settings", title: "缓存设置", description: "系统缓存的配置参数", icon: , fields: [ { id: "cache.ttl", label: "缓存TTL(秒)", description: "缓存的生存时间,单位为秒", type: "number", value: 3600, validation: { required: true, min: 1, max: 86400 } }, { id: "cache.max_size", label: "最大缓存大小(MB)", description: "缓存的最大内存占用", type: "number", value: 1024, validation: { required: true, min: 1, max: 10000 } } ] } ] } ], // 配置选项 autoSave: false, // 禁用自动保存,使用手动保存 validateOnChange: true, onValidate: (values) => { const result = commonConfigSchema.safeParse(values); return zodErrorsToAdminErrors(result); } }; } // 配置更新Hook export function useConfigUpdate() { const [updateConfigBatch, { loading, error, data }] = useMutation(UPDATE_CONFIG_BATCH); const updateConfigs = async (configs: Record) => { try { // 将配置对象转换为UpdateConfig数组 const updateConfigs: UpdateConfig[] = Object.entries(configs).map(([key, value]) => ({ key, value: String(value), // 直接转换为字符串,避免JSON.stringify添加双引号 description: undefined, category: undefined, is_editable: true })); const result = await updateConfigBatch({ variables: { input: updateConfigs } }); if (result.data?.updateConfigBatch === "successed") { toast.success("配置更新成功!"); return true; } else { toast.error("配置更新失败"); return false; } } catch (err) { console.error("更新配置失败:", err); toast.error("配置更新失败,请检查网络连接"); return false; } }; return { updateConfigs, loading, error, data }; } // 配置保存按钮组件 export function ConfigSaveButton({ onSave, loading = false, disabled = false }: { onSave: () => void; loading?: boolean; disabled?: boolean; }) { return ( ); } // 配置状态指示器 export function ConfigStatusIndicator({ status, message }: { status: 'idle' | 'loading' | 'success' | 'error'; message?: string; }) { if (status === 'idle') return null; return (
{status === 'loading' && ( <> 保存中... )} {status === 'success' && ( <> 保存成功 )} {status === 'error' && ( <> {message || '保存失败'} )}
); } // 完整的配置管理面板组件 export function DynamicAdminConfigPanel({ data, onExport, onImport }: { data?: SiteOpsConfigType; onExport?: () => Promise; onImport?: (file: File) => Promise; }) { const [formValues, setFormValues] = useState>({}); const [validationErrors, setValidationErrors] = useState>({}); const [saveStatus, setSaveStatus] = useState<'idle' | 'loading' | 'success' | 'error'>('idle'); const [errorMessage, setErrorMessage] = useState(''); const { updateConfigs, loading } = useConfigUpdate(); // 生成配置 const config = createDynamicAdminConfigWithMutation(data, onExport, onImport); // 初始化表单值 React.useEffect(() => { const initialValues = config.tabs.reduce((acc, tab) => { tab.sections.forEach(section => { section.fields.forEach(field => { acc[field.id] = field.value; }); }); return acc; }, {} as Record); setFormValues(initialValues); }, [config]); // 处理字段值变化 const handleFieldChange = (fieldId: string, value: any) => { setFormValues(prev => ({ ...prev, [fieldId]: value })); // 清除该字段的验证错误 if (validationErrors[fieldId]) { setValidationErrors(prev => { const newErrors = { ...prev }; delete newErrors[fieldId]; return newErrors; }); } }; // 验证表单 const validateForm = () => { const errors: Record = {}; config.tabs.forEach(tab => { tab.sections.forEach(section => { section.fields.forEach(field => { const value = formValues[field.id]; // 必填字段验证 if (field.validation?.required && (!value || value === '')) { errors[field.id] = '此字段为必填项'; return; } // 最小长度验证 if (field.validation?.minLength && typeof value === 'string' && value.length < field.validation.minLength) { errors[field.id] = `最少需要 ${field.validation.minLength} 个字符`; return; } // 最大长度验证 if (field.validation?.maxLength && typeof value === 'string' && value.length > field.validation.maxLength) { errors[field.id] = `最多允许 ${field.validation.maxLength} 个字符`; return; } // 数值范围验证 if (field.validation?.min !== undefined && typeof value === 'number' && value < field.validation.min) { errors[field.id] = `最小值不能小于 ${field.validation.min}`; return; } if (field.validation?.max !== undefined && typeof value === 'number' && value > field.validation.max) { errors[field.id] = `最大值不能大于 ${field.validation.max}`; return; } }); }); }); setValidationErrors(errors); return Object.keys(errors).length === 0; }; // 保存配置 const handleSave = async () => { if (!validateForm()) { setSaveStatus('error'); setErrorMessage('请检查表单验证错误'); return; } setSaveStatus('loading'); setErrorMessage(''); try { const success = await updateConfigs(formValues); if (success) { setSaveStatus('success'); // 3秒后重置状态 setTimeout(() => setSaveStatus('idle'), 3000); } else { setSaveStatus('error'); setErrorMessage('保存失败,请重试'); } } catch (error) { setSaveStatus('error'); setErrorMessage('保存过程中发生错误'); } }; // 渲染字段 const renderField = (field: any) => { const value = formValues[field.id] ?? field.value; const error = validationErrors[field.id]; const commonProps = { id: field.id, value: value, onChange: (e: any) => handleFieldChange(field.id, e.target.value), className: `w-full ${error ? 'border-red-500' : ''}`, placeholder: field.placeholder }; switch (field.type) { case 'input': return (
{field.description && (

{field.description}

)} {error && (

{error}

)}
); case 'textarea': return (