diff --git a/apps/web/src/api/scripts/scripts.ts b/apps/web/src/api/scripts/scripts.ts index 4ee28f146..d3862dcfc 100644 --- a/apps/web/src/api/scripts/scripts.ts +++ b/apps/web/src/api/scripts/scripts.ts @@ -1,37 +1,195 @@ /** - * 文案库 API - * 对接后端 /api/v1/scripts(CRUD + 列表解包) + * 文案库 API(#1811 v2) + * CRUD + 搜索/分类/分页;后端未就绪时使用 mock 数据(SCRIPTS_API_MOCK=true) */ import apiClient from "../client" import type { ScriptItem, + ScriptListParams, ScriptListResponse, - CreateScriptRequest, - UpdateScriptRequest, + ScriptUpsertRequest, + ScriptCategory, } from "./types" -/** 获取文案列表 — 必须解包 items(后端返回 {items,total})*/ -export const getScripts = async (): Promise => { - const response = await apiClient.get("/scripts") - const data = response.data as unknown - if (Array.isArray(data)) return data - const items = (data as { items?: ScriptItem[] })?.items - return Array.isArray(items) ? items : [] +/** 是否启用 mock(后端合入后改为 false) */ +export const SCRIPTS_API_MOCK = 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 const createScript = async (data: CreateScriptRequest): Promise => { - const response = await apiClient.post("/scripts", data) - return response.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 const updateScript = async (id: string, data: UpdateScriptRequest): Promise => { - const response = await apiClient.put(`/scripts/${id}`, data) - return response.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 const deleteScript = async (id: string): Promise => { +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 +}