import { z } from "zod"; import { ReactNode } from "react"; import { Globe, Users, Mail, FileText, Server, HardDrive, Shield, Settings } from "lucide-react"; import { FieldConfig, SectionConfig } from "@/types/admin-panel"; import { ConfigItemType } from "@/hooks/use-site-config"; // 1) 使用 zod 定义“通用配置”结构与规则 export const commonConfigSchema = z.object({ site: z.object({ name: z.string().min(2, "网站名称至少2个字符").max(50, "网站名称最多50个字符"), description: z.string().max(200, "网站描述最多200个字符").optional().or(z.literal("")), keywords: z.string().optional().or(z.literal("")), url: z.string().url("请输入有效的站点URL").optional().or(z.literal("")), logo: z.string().url("请输入有效的Logo地址").optional().or(z.literal("")), icp: z.string().optional().or(z.literal("")), icp_url: z.string().url("请输入有效的备案链接").optional().or(z.literal("")), color_style: z.enum(["light", "dark", "auto"]).default("light"), }), user: z.object({ default_avatar: z.string().url("请输入有效的头像URL").optional().or(z.literal("")), default_role: z.enum(["user", "editor", "admin"]).default("user"), register_invite_code: z.boolean().default(false), register_email_verification: z.boolean().default(false), open_login: z.boolean().default(true), open_reset_password: z.boolean().default(true), }), email: z.object({ smtp_host: z.string().optional().or(z.literal("")), smtp_port: z.number().int().min(1).max(65535).default(465), smtp_user: z.string().optional().or(z.literal("")), smtp_password: z.string().optional().or(z.literal("")), smtp_from: z.string().email("请输入有效的发信地址").optional().or(z.literal("")), smtp_from_name: z.string().optional().or(z.literal("")), smtp_from_email: z.string().email("请输入有效的发信邮箱").optional().or(z.literal("")), system_template: z.string().default("default"), }), blog: z.object({ default_author: z.string().optional().or(z.literal("")), default_category: z.string().optional().or(z.literal("")), default_tag: z.string().optional().or(z.literal("")), open_comment: z.boolean().default(true), }), logging: z.object({ level: z.enum(["error", "warn", "info", "debug"]).default("info"), max_files: z.number().int().min(1).max(1000).default(10), max_file_size: z.number().int().min(1).max(10240).default(10), }), cache: z.object({ ttl: z.number().int().min(1).max(31_536_000).default(3600), max_size: z.number().int().min(1).max(1_048_576).default(1024), }), switch: z.object({ open_register: z.boolean().default(true), open_login: z.boolean().default(true), open_reset_password: z.boolean().default(true), open_comment: z.boolean().default(true), open_like: z.boolean().default(true), open_share: z.boolean().default(true), open_view: z.boolean().default(true), }) }); // 2) 字段元数据定义 type Meta = Omit & { defaultValue?: any }; const makeField = (id: string, meta: Meta, value?: any): FieldConfig => ({ id, ...meta, value: value ?? meta.defaultValue }); // 3) 分组图标映射 const categoryIcons: Record = { site: , user: , email: , blog: , logging: , cache: , switch: , other: , }; // 4) 分组标题映射 const categoryTitles: Record = { site: "网站信息", user: "用户设置", email: "邮件设置", blog: "博客设置", logging: "日志设置", cache: "缓存设置", switch: "功能开关", other: "其他配置", }; // 5) 已知字段的元数据(用于生成表单) const knownFieldsMeta: Record = { // site "site.name": { label: "网站名称", type: "input", validation: { required: true, minLength: 2, maxLength: 50 } }, "site.description": { label: "网站描述", type: "textarea", rows: 3 }, "site.keywords": { label: "关键词", type: "input", description: "逗号分隔,如:blog,tech,ai" }, "site.url": { label: "站点URL", type: "url" }, "site.logo": { label: "Logo地址", type: "url" }, "site.icp": { label: "ICP备案号", type: "input" }, "site.icp_url": { label: "备案链接", type: "url" }, "site.color_style": { label: "配色风格", type: "select", options: [ { label: "浅色", value: "light" }, { label: "深色", value: "dark" }, { label: "自动", value: "auto" } ] }, // user "user.default_avatar": { label: "默认头像URL", type: "url" }, "user.default_role": { label: "默认角色", type: "select", options: [ { label: "用户", value: "user" }, { label: "编辑", value: "editor" }, { label: "管理员", value: "admin" } ] }, "user.register_invite_code": { label: "注册需邀请码", type: "switch" }, "user.register_email_verification": { label: "注册需邮箱验证", type: "switch" }, "user.open_login": { label: "开启登录", type: "switch" }, "user.open_reset_password": { label: "开启重置密码", type: "switch" }, // email "email.smtp_host": { label: "SMTP 主机", type: "input" }, "email.smtp_port": { label: "SMTP 端口", type: "number", min: 1, max: 65535 }, "email.smtp_user": { label: "SMTP 用户名", type: "input" }, "email.smtp_password": { label: "SMTP 密码", type: "password" }, "email.smtp_from": { label: "发信地址", type: "email" }, "email.smtp_from_name": { label: "发信人名称", type: "input" }, "email.smtp_from_email": { label: "发信邮箱", type: "email" }, "email.system_template": { label: "系统模板", type: "input" }, // blog "blog.default_author": { label: "默认作者", type: "input" }, "blog.default_category": { label: "默认分类", type: "input" }, "blog.default_tag": { label: "默认标签", type: "input" }, "blog.open_comment": { label: "开启评论", type: "switch" }, // logging "logging.level": { label: "日志级别", type: "select", options: [ { label: "错误", value: "error" }, { label: "警告", value: "warn" }, { label: "信息", value: "info" }, { label: "调试", value: "debug" } ] }, "logging.max_files": { label: "最大文件数", type: "number", min: 1, max: 1000 }, "logging.max_file_size": { label: "单文件大小(MB)", type: "number", min: 1, max: 10240 }, // cache "cache.ttl": { label: "TTL(秒)", type: "number", min: 1, max: 31536000 }, "cache.max_size": { label: "最大容量(MB)", type: "number", min: 1, max: 1048576 }, // switch "switch.open_register": { label: "开放注册", type: "switch" }, "switch.open_login": { label: "开放登录", type: "switch" }, "switch.open_reset_password": { label: "开放重置密码", type: "switch" }, "switch.open_comment": { label: "开放评论", type: "switch" }, "switch.open_like": { label: "开放点赞", type: "switch" }, "switch.open_share": { label: "开放分享", type: "switch" }, "switch.open_view": { label: "开放浏览", type: "switch" }, }; // 6) 根据 valueType 推断字段类型 function inferFieldType(valueType: string, value: any): FieldConfig["type"] { const vt = valueType.toLowerCase(); if (vt === "boolean" || vt === "bool") return "switch"; if (vt === "number" || vt === "int" || vt === "integer" || vt === "float" || vt === "double") return "number"; if (vt === "email") return "email"; if (vt === "url") return "url"; if (vt === "password") return "password"; if (vt === "json" || vt === "object" || vt === "array") return "textarea"; if (typeof value === "string" && value.length > 100) return "textarea"; return "input"; } // 7) 解析配置值 function parseConfigValue(value: string | null | undefined, valueType: string): any { if (value == null) return ""; const vt = valueType.toLowerCase(); try { if (vt === "number" || vt === "int" || vt === "integer") return Number(value); if (vt === "float" || vt === "double") return parseFloat(value); if (vt === "bool" || vt === "boolean") return value === "true" || value === "1"; if (vt === "json" || vt === "object" || vt === "array") return value; // 保持字符串用于 textarea return value; } catch { return value; } } // 8) 根据 configs 动态生成分组 export function buildSectionsFromConfigs(configs: ConfigItemType[]): SectionConfig[] { const groupMap: Record> = {}; for (const config of configs) { const [category] = config.key.split("."); const group = category || "other"; if (!groupMap[group]) { groupMap[group] = []; } // 解析值 const parsedValue = parseConfigValue(config.value, config.valueType); // 检查是否有预定义的元数据 const knownMeta = knownFieldsMeta[config.key]; if (knownMeta) { // 使用预定义的元数据 groupMap[group].push({ config, field: makeField(config.key, knownMeta, parsedValue) }); } else { // 动态推断字段类型 const inferredType = inferFieldType(config.valueType, parsedValue); const field: FieldConfig = { id: config.key, label: config.description || config.key, type: inferredType, value: parsedValue, description: config.description ? undefined : `配置键: ${config.key}`, rows: inferredType === "textarea" ? 3 : undefined, }; groupMap[group].push({ config, field }); } } // 转换为 SectionConfig[] return Object.entries(groupMap) .filter(([_, items]) => items.length > 0) .map(([group, items]) => ({ id: `common-${group}`, title: categoryTitles[group] || `${group} 配置`, icon: categoryIcons[group] || categoryIcons.other, fields: items.map(item => item.field), })); } // 9) 将 zod 校验错误转换为 AdminPanel 的错误映射 export function zodErrorsToAdminErrors(result: z.SafeParseReturnType): Record { if (result.success) return {}; const errors: Record = {}; for (const issue of result.error.issues) { const path = issue.path.join("."); if (path) { errors[path] = issue.message; } } return errors; } export type CommonConfig = z.infer;