/** * 文案库 API(#1811 v2) * CRUD + 搜索/分类/分页;后端未就绪时使用 mock 数据(SCRIPTS_API_MOCK=true) */ import apiClient from "../client" import type { ScriptItem, ScriptListParams, ScriptListResponse, ScriptUpsertRequest, ScriptCategory, } from "./types" /** * 是否启用 mock。 * #1894:文案库接口已上线,默认 false 走真实 API; * 通过 SCRIPTS_API_MOCK=true 环境变量可本地开启 mock 调试(行为同 POINTS_API_MOCK)。 */ export const SCRIPTS_API_MOCK = (process.env.SCRIPTS_API_MOCK as string | undefined) === "true" // ==================== Mock 数据 ==================== const MOCK_CATEGORIES: ScriptCategory[] = [ "promo", "vlog", "knowledge", "story", "emotion", "other", ] const SAMPLE_TITLES: Record = { promo: ["新品上市限时特惠", "618大促开场", "品牌故事宣传片"], vlog: ["周末citywalk记录", "打工人的一天", "探店vlog"], knowledge: ["3分钟学会XX", "冷知识科普", "行业深度解读"], story: ["励志小故事", "情感故事一则", "反转剧情"], emotion: ["深夜emo时刻", "治愈系文案", "朋友圈金句"], other: ["通用开场白", "节日祝福", "万能结尾"], } const SAMPLE_TAGS = ["热门", "新品", "节日", "情感", "干货", "搞笑", "治愈", "励志"] function genMockScripts(): ScriptItem[] { const items: ScriptItem[] = [] const now = Date.now() let idx = 0 for (const cat of MOCK_CATEGORIES) { const titles = SAMPLE_TITLES[cat] for (let i = 0; i < titles.length; i++) { idx++ const title = titles[i] const content = `这是一条【${cat}】分类下的示例文案,标题为「${title}」。\n\n正文可以包含多段落,每段对应一个片段(segments)。\n\n此为 mock 数据,后端接口就绪后会自动切换为真实数据。` const segments = content.split(/\n\n+/).filter(Boolean) const tagCount = 1 + (idx % 3) const tags: string[] = [] for (let t = 0; t < tagCount; t++) { tags.push(SAMPLE_TAGS[(idx + t) % SAMPLE_TAGS.length]) } items.push({ id: `mock_${idx}`, title, content, segments, tags, title_text: title, title_category: cat, title_config: { font: "default", color: "#ffffff", stroke: "#000000", position: (["top", "center", "bottom"] as const)[idx % 3], size: 48, bold: idx % 2 === 0, italic: false, }, char_count: content.length, use_count: Math.floor(Math.random() * 50), created_at: new Date(now - idx * 86400_000 * 2).toISOString(), updated_at: new Date(now - idx * 86400_000).toISOString(), }) } } return items } const MOCK_SCRIPTS = genMockScripts() // ==================== 真实 API ==================== /** 获取文案列表(支持分页/搜索/分类) */ export async function getScripts(params: ScriptListParams = {}): Promise { if (SCRIPTS_API_MOCK) { const page = params.page ?? 1 const pageSize = params.page_size ?? 20 let items = [...MOCK_SCRIPTS] if (params.keyword) { const kw = params.keyword.toLowerCase() items = items.filter( (s) => s.title.toLowerCase().includes(kw) || s.content.toLowerCase().includes(kw), ) } if (params.category && params.category !== "all") { items = items.filter((s) => s.title_category === params.category) } if (params.tag) { items = items.filter((s) => s.tags?.includes(params.tag as string)) } const total = items.length const start = (page - 1) * pageSize const pageItems = items.slice(start, start + pageSize) return new Promise((r) => setTimeout(() => r({ items: pageItems, total, page, page_size: pageSize }), 200), ) } const res = await apiClient.get("/scripts", { params }) return res.data } /** 获取单条文案详情 */ export async function getScript(id: string): Promise { if (SCRIPTS_API_MOCK) { const item = MOCK_SCRIPTS.find((s) => s.id === id) return new Promise((r) => setTimeout(() => r(item ?? MOCK_SCRIPTS[0]), 120)) } const res = await apiClient.get(`/scripts/${id}`) return res.data } /** 新建文案 */ export async function createScript(data: ScriptUpsertRequest): Promise { if (SCRIPTS_API_MOCK) { const segments = data.segments && data.segments.length > 0 ? data.segments : data.content.split(/\n\n+/).filter(Boolean) const item: ScriptItem = { id: `mock_${Date.now()}`, ...data, segments, char_count: data.content.length, use_count: 0, tags: data.tags ?? [], created_at: new Date().toISOString(), updated_at: new Date().toISOString(), } MOCK_SCRIPTS.unshift(item) return new Promise((r) => setTimeout(() => r(item), 200)) } const res = await apiClient.post("/scripts", data) return res.data } /** 更新文案 */ export async function updateScript(id: string, data: ScriptUpsertRequest): Promise { if (SCRIPTS_API_MOCK) { const idx = MOCK_SCRIPTS.findIndex((s) => s.id === id) const segments = data.segments && data.segments.length > 0 ? data.segments : data.content.split(/\n\n+/).filter(Boolean) const updated: ScriptItem = { ...MOCK_SCRIPTS[idx], ...data, segments, char_count: data.content.length, tags: data.tags ?? MOCK_SCRIPTS[idx]?.tags ?? [], updated_at: new Date().toISOString(), } if (idx >= 0) MOCK_SCRIPTS[idx] = updated return new Promise((r) => setTimeout(() => r(updated), 200)) } const res = await apiClient.put(`/scripts/${id}`, data) return res.data } /** 删除文案 */ export async function deleteScript(id: string): Promise { if (SCRIPTS_API_MOCK) { const idx = MOCK_SCRIPTS.findIndex((s) => s.id === id) if (idx >= 0) MOCK_SCRIPTS.splice(idx, 1) return new Promise((r) => setTimeout(r, 150)) } await apiClient.delete(`/scripts/${id}`) } /** 复制文案(返回新副本) */ export async function duplicateScript(id: string): Promise { const orig = await getScript(id) const copy = await createScript({ title: `${orig.title}(副本)`, content: orig.content, segments: orig.segments, tags: orig.tags, title_text: orig.title_text, title_category: orig.title_category, title_config: orig.title_config, }) return copy }