192 lines
9.2 KiB
TypeScript
192 lines
9.2 KiB
TypeScript
import { z } from "zod";
|
||
import { ReactNode } from "react";
|
||
import { Globe, Users, Mail, FileText, Server, HardDrive, Shield } from "lucide-react";
|
||
import { FieldConfig, SectionConfig } from "@/types/admin-panel";
|
||
|
||
// 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) 最小化配置,用于生成 FieldConfig(避免重复手写 FieldConfig)
|
||
type Meta = Omit<FieldConfig, "id" | "value"> & { defaultValue?: any };
|
||
|
||
const makeField = (id: string, meta: Meta): FieldConfig => ({ id, ...meta, value: meta.defaultValue });
|
||
|
||
const sectionIcons: Record<string, ReactNode> = {
|
||
site: <Globe className="h-5 w-5" />,
|
||
user: <Users className="h-5 w-5" />,
|
||
email: <Mail className="h-5 w-5" />,
|
||
blog: <FileText className="h-5 w-5" />,
|
||
logging: <Server className="h-5 w-5" />,
|
||
cache: <HardDrive className="h-5 w-5" />,
|
||
switch: <Shield className="h-5 w-5" />,
|
||
};
|
||
|
||
const sectionTitles: Record<string, string> = {
|
||
site: "网站信息",
|
||
user: "用户设置",
|
||
email: "邮件设置",
|
||
blog: "博客设置",
|
||
logging: "日志设置",
|
||
cache: "缓存设置",
|
||
switch: "功能开关",
|
||
};
|
||
|
||
// 3) 通用配置的字段元数据(用于生成表单)
|
||
export const commonFieldsMeta: Array<{ id: string; meta: Meta }> = [
|
||
// site
|
||
{ id: "site.name", meta: { label: "网站名称", type: "input", validation: { required: true, minLength: 2, maxLength: 50 } } },
|
||
{ id: "site.description", meta: { label: "网站描述", type: "textarea", rows: 3 } },
|
||
{ id: "site.keywords", meta: { label: "关键词", type: "input", description: "逗号分隔,如:blog,tech,ai" } },
|
||
{ id: "site.url", meta: { label: "站点URL", type: "url" } },
|
||
{ id: "site.logo", meta: { label: "Logo地址", type: "url" } },
|
||
{ id: "site.icp", meta: { label: "ICP备案号", type: "input" } },
|
||
{ id: "site.icp_url", meta: { label: "备案链接", type: "url" } },
|
||
{
|
||
id: "site.color_style", meta: {
|
||
label: "配色风格", type: "select", options: [
|
||
{ label: "浅色", value: "light" },
|
||
{ label: "深色", value: "dark" },
|
||
{ label: "自动", value: "auto" }
|
||
]
|
||
}
|
||
},
|
||
// user
|
||
{ id: "user.default_avatar", meta: { label: "默认头像URL", type: "url" } },
|
||
{
|
||
id: "user.default_role", meta: {
|
||
label: "默认角色", type: "select", options: [
|
||
{ label: "用户", value: "user" },
|
||
{ label: "编辑", value: "editor" },
|
||
{ label: "管理员", value: "admin" }
|
||
]
|
||
}
|
||
},
|
||
{ id: "user.register_invite_code", meta: { label: "注册需邀请码", type: "switch" } },
|
||
{ id: "user.register_email_verification", meta: { label: "注册需邮箱验证", type: "switch" } },
|
||
{ id: "user.open_login", meta: { label: "开启登录", type: "switch" } },
|
||
{ id: "user.open_reset_password", meta: { label: "开启重置密码", type: "switch" } },
|
||
// email
|
||
{ id: "email.smtp_host", meta: { label: "SMTP 主机", type: "input" } },
|
||
{ id: "email.smtp_port", meta: { label: "SMTP 端口", type: "number", min: 1, max: 65535 } },
|
||
{ id: "email.smtp_user", meta: { label: "SMTP 用户名", type: "input" } },
|
||
{ id: "email.smtp_password", meta: { label: "SMTP 密码", type: "password" } },
|
||
{ id: "email.smtp_from", meta: { label: "发信地址", type: "email" } },
|
||
{ id: "email.smtp_from_name", meta: { label: "发信人名称", type: "input" } },
|
||
{ id: "email.smtp_from_email", meta: { label: "发信邮箱", type: "email" } },
|
||
{ id: "email.system_template", meta: { label: "系统模板", type: "input" } },
|
||
// blog
|
||
{ id: "blog.default_author", meta: { label: "默认作者", type: "input" } },
|
||
{ id: "blog.default_category", meta: { label: "默认分类", type: "input" } },
|
||
{ id: "blog.default_tag", meta: { label: "默认标签", type: "input" } },
|
||
{ id: "blog.open_comment", meta: { label: "开启评论", type: "switch" } },
|
||
// logging
|
||
{
|
||
id: "logging.level", meta: {
|
||
label: "日志级别", type: "select", options: [
|
||
{ label: "错误", value: "error" },
|
||
{ label: "警告", value: "warn" },
|
||
{ label: "信息", value: "info" },
|
||
{ label: "调试", value: "debug" }
|
||
]
|
||
}
|
||
},
|
||
{ id: "logging.max_files", meta: { label: "最大文件数", type: "number", min: 1, max: 1000 } },
|
||
{ id: "logging.max_file_size", meta: { label: "单文件大小(MB)", type: "number", min: 1, max: 10240 } },
|
||
// cache
|
||
{ id: "cache.ttl", meta: { label: "TTL(秒)", type: "number", min: 1, max: 31536000 } },
|
||
{ id: "cache.max_size", meta: { label: "最大容量(MB)", type: "number", min: 1, max: 1048576 } },
|
||
// switch
|
||
{ id: "switch.open_register", meta: { label: "开放注册", type: "switch" } },
|
||
{ id: "switch.open_login", meta: { label: "开放登录", type: "switch" } },
|
||
{ id: "switch.open_reset_password", meta: { label: "开放重置密码", type: "switch" } },
|
||
{ id: "switch.open_comment", meta: { label: "开放评论", type: "switch" } },
|
||
{ id: "switch.open_like", meta: { label: "开放点赞", type: "switch" } },
|
||
{ id: "switch.open_share", meta: { label: "开放分享", type: "switch" } },
|
||
{ id: "switch.open_view", meta: { label: "开放浏览", type: "switch" } },
|
||
];
|
||
|
||
// 4) 根据元数据分组生成 Section 列表(供 dynamic-admin-config 使用)
|
||
export function buildCommonSectionsFromMeta(): SectionConfig[] {
|
||
const groupMap: Record<string, FieldConfig[]> = {};
|
||
for (const { id, meta } of commonFieldsMeta) {
|
||
const [group] = id.split(".");
|
||
if (!groupMap[group]) groupMap[group] = [];
|
||
groupMap[group].push(makeField(id, meta));
|
||
}
|
||
return Object.entries(groupMap).map<SectionConfig>(([group, fields]) => ({
|
||
id: `common-${group}`,
|
||
title: sectionTitles[group] || group,
|
||
icon: sectionIcons[group],
|
||
fields,
|
||
}));
|
||
}
|
||
|
||
// 5) 将 zod 校验错误转换为 AdminPanel 的错误映射
|
||
export function zodErrorsToAdminErrors(result: z.SafeParseReturnType<any, any>): Record<string, string> {
|
||
if (result.success) return {};
|
||
const errors: Record<string, string> = {};
|
||
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<typeof commonConfigSchema>;
|
||
|