From 28a3353f7c43f4eeef235f812fda19db55c7760f Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Fri, 25 Sep 2026 00:49:44 +0800 Subject: [PATCH] =?UTF-8?q?fix(cover):=20=E4=BF=AE=E5=A4=8D=E5=B0=81?= =?UTF-8?q?=E9=9D=A2=E7=BC=96=E8=BE=91=E5=99=A8=E5=A4=9A=E9=A1=B9=E9=97=AE?= =?UTF-8?q?=E9=A2=98=EF=BC=9B=E5=8A=A0=E9=95=BF=E8=8D=89=E7=A8=BF=E4=BF=9D?= =?UTF-8?q?=E5=AD=98/=E5=85=A8=E5=B1=80timeout=20(#2033)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 封面设置弹窗(CoverSettingsModal): - 补充底部『取消/确认应用』按钮,解决选模板后无确认入口问题 - 选中模板加紫色✓角标,空状态提示 - 删除无效的『选择素材文件/导出全部』占位按钮 2. 自定义封面编辑器(CoverEditorModal): - 主/副标题文字改为可输入的TextArea(此前是只读div,完全无法输入) - 预览canvas修复:默认背景改为深蓝渐变而非大黑框;蒙版默认关闭且opacity=40%; 人像默认关闭且占位用浅蓝;支持上传背景图/人像图 - 面板分组按『基础/背景/主标题/副标题/人像/蒙版』重组,默认展开基础+背景+主标题 - 新增backgroundColor/backgroundImage/portraitImage类型字段 - 文字按charsPerLine自动换行渲染到预览 - 打开弹窗时重置state,避免编辑旧模板污染新模板 3. API 超时调整: - 全局axios timeout 10s → 30s(解决智能选片/草稿保存偶发10s超时) - updateEditPlan(草稿保存) 单独60s - 智能选片from-assets已是60s、封面生成已是300s,保持不变 4. 类型/样式同步更新 --- apps/web/src/api/client.ts | 2 +- apps/web/src/api/template-editor/editPlans.ts | 9 +- .../cover-settings/CoverEditorModal.tsx | 837 +++++++++--------- .../cover-settings/CoverSettingsModal.tsx | 136 ++- apps/web/src/pages/generate/generate.css | 37 + apps/web/src/pages/generate/types/cover.ts | 15 +- 6 files changed, 557 insertions(+), 479 deletions(-) diff --git a/apps/web/src/api/client.ts b/apps/web/src/api/client.ts index 1d3ce3170..caeb38bba 100644 --- a/apps/web/src/api/client.ts +++ b/apps/web/src/api/client.ts @@ -11,7 +11,7 @@ import { cancelProactiveRefresh, executeTokenRefresh } from "./auth/tokenRefresh // 创建 Axios 实例 const apiClient = axios.create({ baseURL: "/api/v1", - timeout: 10000, + timeout: 30000, // 全局 30s;智能选片/封面生成/大文件上传接口单独覆盖更长超时 headers: { "Content-Type": "application/json", }, diff --git a/apps/web/src/api/template-editor/editPlans.ts b/apps/web/src/api/template-editor/editPlans.ts index c4d017c16..3533f9e4b 100644 --- a/apps/web/src/api/template-editor/editPlans.ts +++ b/apps/web/src/api/template-editor/editPlans.ts @@ -6,17 +6,20 @@ import type { EditPlan, UpdateEditPlanRequest, GeneratedVideo } from "./types" /** 获取单个模板草稿 */ export async function getEditPlan(templateId: string): Promise { - const response = await apiClient.get(`/templates/${templateId}/editor`) + const response = await apiClient.get(`/templates/${templateId}/editor`, { timeout: 30_000 }) return response.data } -/** 更新模板草稿(支持传入 AbortSignal 用于自动保存竞态取消) */ +/** 更新模板草稿(支持传入 AbortSignal 用于自动保存竞态取消;超时 60s 防止大 config 写入失败) */ export async function updateEditPlan( templateId: string, data: UpdateEditPlanRequest, signal?: AbortSignal, ): Promise { - const response = await apiClient.put(`/templates/${templateId}/editor`, data, { signal }) + const response = await apiClient.put(`/templates/${templateId}/editor`, data, { + signal, + timeout: 60_000, + }) return response.data } diff --git a/apps/web/src/pages/generate/components/cover-settings/CoverEditorModal.tsx b/apps/web/src/pages/generate/components/cover-settings/CoverEditorModal.tsx index ec2fc14b0..83d561bd8 100644 --- a/apps/web/src/pages/generate/components/cover-settings/CoverEditorModal.tsx +++ b/apps/web/src/pages/generate/components/cover-settings/CoverEditorModal.tsx @@ -1,13 +1,11 @@ -import React, { useState, useCallback, useRef } from "react" -import { Slider, Switch, Select, InputNumber } from "antd" +import React, { useState, useCallback, useRef, useEffect } from "react" +import { Slider, Switch, Select, InputNumber, Input } from "antd" import type { CoverTemplate, CoverEditorConfig, TextStyleConfig, - TextBackground, TextDirection, StrokeStyle, - TextBgShape, } from "../../types/cover" import { DEFAULT_EDITOR_CONFIG, PRESET_FONTS, SYSTEM_FONTS, ALL_FONTS } from "../../types/cover" import Modal from "@/components/ui/Modal" @@ -51,7 +49,7 @@ const PositionPair: React.FC<{ onChange({ x: v ?? 0, y })} @@ -60,7 +58,7 @@ const PositionPair: React.FC<{ onChange({ x, y: v ?? 0 })} @@ -105,15 +103,20 @@ const TextStylePanel: React.FC<{ }> = ({ config, onChange, placeholder }) => { const upd = (key: K, val: TextStyleConfig[K]) => onChange({ ...config, [key]: val }) - const updBg = (key: K, val: TextBackground[K]) => - upd("background", { ...config.background, [key]: val }) - return (
- {/* 文字内容 */} + {/* 文字内容 - 改为可输入 */}
-
{config.text || placeholder}
-
在基本信息中设置文案内容,此处显示拆分结果
+ + upd("text", e.target.value)} + placeholder={placeholder} + autoSize={{ minRows: 1, maxRows: 3 }} + size="small" + maxLength={50} + showCount + />
{/* 字体 */} @@ -125,8 +128,8 @@ const TextStylePanel: React.FC<{ options={fontOptions} style={{ width: "100%" }} popupClassName="xx-ce-font-select-dropdown" + size="small" /> -
绿色圆点=预置字体 | 蓝色圆点=系统字体(35种可用字体)
{/* 字号 */} @@ -150,7 +153,7 @@ const TextStylePanel: React.FC<{ step={100} value={config.fontWeight} onChange={(v) => upd("fontWeight", v)} - marks={{ 100: "100", 500: "500", 700: "700", 900: "900", 1000: "1000" }} + marks={{ 400: "常规", 700: "粗", 900: "黑" }} /> @@ -163,6 +166,7 @@ const TextStylePanel: React.FC<{ key={d} className={`xx-ce-radio-btn ${config.direction === d ? "active" : ""}`} onClick={() => upd("direction", d)} + type="button" > {d === "horizontal" ? "横排" : "竖排"} @@ -208,58 +212,43 @@ const TextStylePanel: React.FC<{ {/* 颜色 */}
- + upd("color", v)} />
- {/* 描边颜色 */} -
- - upd("strokeColor", v)} /> -
- - {/* 描边粗细 */} -
- - upd("strokeWidth", v)} - /> -
- - {/* 多层阴影 */} -
- -
- - -
-
- + {/* 描边 */} +
+
+ 文字描边 upd("traditionalShadow", v)} + checked={config.strokeWidth > 0} + onChange={(v) => upd("strokeWidth", v ? 3 : 0)} />
+ {config.strokeWidth > 0 && ( + <> +
+ + upd("strokeColor", v)} /> +
+
+ + upd("strokeWidth", v)} + /> +
+ + )}
{/* 位置 */}
- + upd("rotation", v)} />
- - {/* 文字背景 */} -
-
- - updBg("enabled", v)} - /> -
-
- - {config.background.enabled && ( -
- {/* 背景颜色 */} -
- - updBg("color", v)} /> -
- - {/* 透明度 */} -
- - updBg("opacity", v)} - /> -
- - {/* 形状 */} -
- - + + {bgImageUrl && ( + + )} + + handleFile(e.target.files?.[0], setBgImageUrl, "backgroundImage") + } + /> +
+
+
+ + upd("backgroundColor", v)} + /> +
+
+ + upd("backgroundSize", v)} + /> +
+
+ + upd("backgroundPosition", pos)} + /> +
+
+ )} +
+ + {/* 3. 主标题 */} +
+
toggle("title")}> + 🔠 主标题 + {sections.title ? "▾" : "▸"} +
+ {sections.title && ( +
+ +
+ )} +
+ + {/* 4. 副标题 */} +
+
toggle("subtitle")}> + 🔡 副标题 + {sections.subtitle ? "▾" : "▸"} +
+ {sections.subtitle && ( +
+ +
+ )} +
+ + {/* 5. 人像 */} +
+
toggle("portrait")}> + 👤 人像设置 +
e.stopPropagation()}> + upd("portraitEnabled", v)} + /> +
+
+ {sections.portrait && cfg.portraitEnabled && ( +
+
+ +
+ + + {portraitImageUrl && ( + + )} + + handleFile(e.target.files?.[0], setPortraitImageUrl, "portraitImage") + } + /> +
+
+
+ + upd("portraitSize", v)} + /> +
+
+ + upd("portraitPosition", pos)} + /> +
人物描边 @@ -540,13 +715,14 @@ const CoverEditorModal: React.FC = ({ open, onClose, temp <>
- {(["solid", "dashed"] as StrokeStyle[]).map((s) => ( + {(["solid", "dashed"] as StrokeStyle[]).map((st) => ( ))}
@@ -571,150 +747,6 @@ const CoverEditorModal: React.FC = ({ open, onClose, temp )}
- - {/* 文字自动拆分 */} -
-
- 文字自动拆分 - upd("autoSplitEnabled", v)} - /> -
- {cfg.autoSplitEnabled && ( - <> -
- - upd("titleMaxChars", v)} - /> -
-
- - upd("subtitleMaxChars", v)} - /> -
- - )} -
-
- )} -
- - {/* 2. 人像设置 */} -
-
toggle("portrait")}> - 人像设置 -
- 已开启 - upd("portraitEnabled", v)} - onClick={(_, e) => e.stopPropagation()} - /> -
-
- {sections.portrait && ( -
-
- - upd("portraitSize", v)} - /> -
-
- - upd("portraitPosition", pos)} - /> -
-
- )} -
- - {/* 3. 背景设置 */} -
-
toggle("background")}> - 背景设置 -
- 已开启 - upd("backgroundEnabled", v)} - onClick={(_, e) => e.stopPropagation()} - /> -
-
- {sections.background && ( -
-
- - upd("backgroundSize", v)} - /> -
-
- - upd("backgroundPosition", pos)} - /> -
-
- )} -
- - {/* 4. 主标题 */} -
-
toggle("title")}> - 主标题 - {sections.title ? "▾" : "▸"} -
- {sections.title && ( -
- -
- )} -
- - {/* 5. 副标题 */} -
-
toggle("subtitle")}> - 副标题 - {sections.subtitle ? "▾" : "▸"} -
- {sections.subtitle && ( -
-
)}
@@ -722,28 +754,30 @@ const CoverEditorModal: React.FC = ({ open, onClose, temp {/* 6. 蒙版 */}
toggle("mask")}> - 蒙版 -
- 已开启 + 🌓 蒙版 +
e.stopPropagation()}> upd("maskEnabled", v)} - onClick={(_, e) => e.stopPropagation()} />
- {sections.mask && ( + {sections.mask && cfg.maskEnabled && (
- +
- = ({ open, onClose, temp
- - upd("maskSize", v)} - /> -
-
- - upd("maskPosition", pos)} - /> -
-
- + upd("maskColor", v)} />
- + = ({ open, onClose, temp />
- -