diff --git a/apps/web/src/api/scripts/index.ts b/apps/web/src/api/scripts/index.ts new file mode 100644 index 000000000..f9d5b5405 --- /dev/null +++ b/apps/web/src/api/scripts/index.ts @@ -0,0 +1,2 @@ +export * from "./scripts" +export * from "./types" diff --git a/apps/web/src/api/scripts/scripts.ts b/apps/web/src/api/scripts/scripts.ts new file mode 100644 index 000000000..4ee28f146 --- /dev/null +++ b/apps/web/src/api/scripts/scripts.ts @@ -0,0 +1,37 @@ +/** + * 文案库 API + * 对接后端 /api/v1/scripts(CRUD + 列表解包) + */ +import apiClient from "../client" +import type { + ScriptItem, + ScriptListResponse, + CreateScriptRequest, + UpdateScriptRequest, +} 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 : [] +} + +/** 新建文案 */ +export const createScript = async (data: CreateScriptRequest): Promise => { + const response = await apiClient.post("/scripts", data) + return response.data +} + +/** 更新文案 */ +export const updateScript = async (id: string, data: UpdateScriptRequest): Promise => { + const response = await apiClient.put(`/scripts/${id}`, data) + return response.data +} + +/** 删除文案 */ +export const deleteScript = async (id: string): Promise => { + await apiClient.delete(`/scripts/${id}`) +} diff --git a/apps/web/src/api/scripts/types.ts b/apps/web/src/api/scripts/types.ts new file mode 100644 index 000000000..8fa0f30e4 --- /dev/null +++ b/apps/web/src/api/scripts/types.ts @@ -0,0 +1,24 @@ +/** + * 文案库 API — 类型定义 + * 对接后端 /api/v1/scripts + */ +export interface ScriptItem { + id: string + title: string + content: string + char_count: number + created_at: string + updated_at?: string +} + +export interface ScriptListResponse { + items: ScriptItem[] + total: number +} + +export interface CreateScriptRequest { + title: string + content: string +} + +export type UpdateScriptRequest = Partial diff --git a/apps/web/src/config/navigation.ts b/apps/web/src/config/navigation.ts index 0105d5742..6279ca871 100644 --- a/apps/web/src/config/navigation.ts +++ b/apps/web/src/config/navigation.ts @@ -58,6 +58,12 @@ export const NAV_ITEMS: NavItem[] = [ path: "/app/titles", icon: React.createElement(FileTextOutlined), }, + { + key: "scripts", + label: "文案库", + path: "/app/scripts", + icon: React.createElement(EditOutlined), + }, { key: "voices", label: "配音库", @@ -173,6 +179,12 @@ export const NAV_GROUPS: NavGroup[] = [ path: "/app/titles", icon: React.createElement(FileTextOutlined), }, + { + key: "scripts", + label: "文案库", + path: "/app/scripts", + icon: React.createElement(EditOutlined), + }, { key: "products", label: "成品库", diff --git a/apps/web/src/pages/ai-avatar/components/PanelVoiceSelector.tsx b/apps/web/src/pages/ai-avatar/components/PanelVoiceSelector.tsx index 9ac80c71b..7b7c8fe74 100644 --- a/apps/web/src/pages/ai-avatar/components/PanelVoiceSelector.tsx +++ b/apps/web/src/pages/ai-avatar/components/PanelVoiceSelector.tsx @@ -3,6 +3,7 @@ * 音色来源切换(系统预设 / 我的音色)、音色选择与试听、情绪/语速/语言参数 */ import { useEffect, useRef, useState } from "react" +import { message } from "antd" import { fetchVoices } from "@/api/voices/voices" import type { UnifiedVoiceItem } from "@/api/voices/types" import { @@ -83,9 +84,14 @@ export function PanelVoiceSelector({ setPreviewingId(null) } + const NO_PREVIEW_TIP = "该音色暂无试听音频,请先用此音色生成一段配音后再试听" + const handlePreview = (voice: UnifiedVoiceItem) => { const url = voice.preview_url || voice.audio_url - if (!url) return + if (!url) { + message.warning(NO_PREVIEW_TIP) + return + } /* 再次点击当前试听音色 → 停止 */ if (previewingId === voice.id) { stopPreview() @@ -108,12 +114,12 @@ export function PanelVoiceSelector({ if (audioRef.current === audio) { audioRef.current = null setPreviewingId(null) - setError("试听音频加载失败") + message.error("试听音频加载失败") } } void audio.play().catch(() => { setPreviewingId(null) - setError("试听播放失败") + message.error("试听播放失败") }) } @@ -181,7 +187,9 @@ export function PanelVoiceSelector({ + + + + {/* 列表 / 空状态 */} + {loading ? ( +
加载中…
+ ) : filtered.length === 0 ? ( + + ) : ( +
+ {filtered.map((s) => ( +
+
+
{s.title}
+
+ + handleDelete(s.id)} + > + + +
+
+
{preview(s.content)}
+
+ {s.char_count ?? s.content.length} 字 + · + {formatTime(s.created_at)} +
+
+ ))} +
+ )} + + + {/* 新建弹窗 */} + +
+ setFormTitle(e.target.value)} + maxLength={200} + /> +