import express from "express"; import u from "@/utils"; import { error, success } from "@/lib/responseFormat"; import fs from "fs"; import path from "path"; import { validateFields } from "@/middleware/middleware"; import { z } from "zod"; const router = express.Router(); // 编辑视觉手册 export default router.post( "/", validateFields({ name: z.string(), images: z.array(z.string()), data: z.array( z.object({ label: z.string(), value: z.string(), data: z.string(), }), ), }), async (req, res) => { try { const { name, images, data } = req.body as { name: string; images: string[]; data: { label: string; value: string; data: string }[]; }; if (/^\d+$/.test(name)) { res.status(400).send(error("名称不能为纯数字")); return; } const mainPath = u.getPath(["skills", "art_prompts", name]); if (!fs.existsSync(mainPath)) { return res.status(400).send(error("视觉手册不存在")); } // 字段映射表(与 getVisualManual 保持一致) const DATA_MAP: { value: string; subDir?: string }[] = [ { value: "README" }, { value: "prefix" }, { value: "art_character", subDir: "art_prompt" }, { value: "art_character_derivative", subDir: "art_prompt" }, { value: "art_prop", subDir: "art_prompt" }, { value: "art_prop_derivative", subDir: "art_prompt" }, { value: "art_scene", subDir: "art_prompt" }, { value: "art_scene_derivative", subDir: "art_prompt" }, { value: "art_storyboard", subDir: "art_prompt" }, { value: "art_storyboard_video", subDir: "art_prompt" }, { value: "director_planning", subDir: "driector_skills" }, { value: "director_storyboard_table", subDir: "driector_skills" }, ]; // 根据 DATA_MAP 构建 value -> subDir 的映射 const SUB_DIR_MAP = new Map(DATA_MAP.map(({ value, subDir }) => [value, subDir ?? ""])); // 合法的 value 值集合,用于校验 const VALID_KEYS = new Set(DATA_MAP.map(({ value }) => value)); for (const item of data) { if (!VALID_KEYS.has(item.value)) continue; const subDir = SUB_DIR_MAP.get(item.value)!; const dirArr = subDir ? [mainPath, subDir] : [mainPath]; const filePath = u.getPath([...dirArr, `${item.value}.md`]); const fileDir = path.dirname(filePath); // 目录不存在时递归创建 if (!fs.existsSync(fileDir)) { fs.mkdirSync(fileDir, { recursive: true }); } fs.writeFileSync(filePath, item.data, "utf-8"); } const ossImagesDir = u.getPath(["oss", name]); let existingFiles: string[] = []; try { const allFiles = fs.readdirSync(ossImagesDir); existingFiles = allFiles.filter((f) => /\.(png|jpe?g|gif|webp|svg)$/i.test(f)); } catch {} const retainedFileNames = new Set(images.filter((item) => item.startsWith("http")).map((url) => path.basename(new URL(url).pathname))); for (const file of existingFiles) { if (!retainedFileNames.has(file)) { await u.oss.deleteFile(`${name}/${file}`); } } for (const item of images) { if (!item.startsWith("http")) await u.oss.writeFile(`${name}/${u.uuid()}.jpg`, item); } res.status(200).send(success()); } catch (err) { res.status(500).send({ error: String(err) }); } }, );