From 43bdb7dd0234aa5841c46fe45594cb2e229947fd Mon Sep 17 00:00:00 2001 From: saas-backend-agent Date: Wed, 23 Sep 2026 13:06:23 +0800 Subject: [PATCH] =?UTF-8?q?feat(title):=20=E6=A0=87=E9=A2=98=E6=A8=A1?= =?UTF-8?q?=E6=9D=BF=20v3=20=E9=87=8D=E6=9E=84=20-=20=E5=9B=BE=E7=89=87?= =?UTF-8?q?=E8=83=8C=E6=99=AF=E5=A4=A7=E5=8D=A1=E7=89=87+=E7=8B=AC?= =?UTF-8?q?=E7=AB=8B=E7=BC=96=E8=BE=91=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 按 sketch v3 重写标题模板交互: - 主面板(enableTemplates=true)只展示大卡片网格(3:4竖版,minmax(180px,1fr)自适应) - 卡片:图片背景(3张轮换) + 暗色渐变遮罩 + 透明Canvas真实字体渲染 - 左上角系统/我的彩色标签,右上角紫色勾选圆圈 - 底部始终可见 编辑/复制/导出/删除 4 按钮(编辑紫色高亮,系统模板编辑/删除置灰) - 选中卡片:紫色边框(#6c5ce7)+紫色阴影;hover 轻微上浮+阴影 - 编辑器弹窗(860px):左300px(9:16预览+名称输入) / 右参数Tab(基础/描边/阴影/背景/排版) - 新建模板以当前选中模板样式为起点 - TitleMiniPreview 新增 transparent/portrait props 支持透明背景+竖屏比例 - TitleStylePanel 新增 paramsOnly 模式(仅渲染参数Tab,供编辑器右侧复用) - 删除废弃 TitleTemplateSelector.tsx(PR#2014弹窗选择器) - 公共组件两入口(智能剪辑Step4 + AI数字人PanelTitleConfig)无需额外改动 --- apps/web/public/title-templates/portrait1.jpg | Bin 0 -> 21947 bytes apps/web/public/title-templates/portrait2.jpg | Bin 0 -> 28041 bytes apps/web/public/title-templates/scene1.jpg | Bin 0 -> 33626 bytes .../src/components/title/TitleTemplate.css | 678 +++++------ .../components/title/TitleTemplateEditor.tsx | 147 ++- .../title/TitleTemplateSelector.tsx | 237 ---- .../components/title/TitleMiniPreview.tsx | 25 +- .../components/title/TitleStylePanel.tsx | 1068 +++++++++-------- 8 files changed, 1023 insertions(+), 1132 deletions(-) create mode 100644 apps/web/public/title-templates/portrait1.jpg create mode 100644 apps/web/public/title-templates/portrait2.jpg create mode 100644 apps/web/public/title-templates/scene1.jpg delete mode 100644 apps/web/src/components/title/TitleTemplateSelector.tsx diff --git a/apps/web/public/title-templates/portrait1.jpg b/apps/web/public/title-templates/portrait1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..10ff584b544dd0c2bd531e82bbb11fa83c712179 GIT binary patch literal 21947 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 literal 0 HcmV?d00001 diff --git a/apps/web/public/title-templates/scene1.jpg b/apps/web/public/title-templates/scene1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..cba869f7b9391d7956ab8a62f9a312710e70fc38 GIT binary patch literal 33626 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 z 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 Y)%?18YP*G}E?*w|gy)BQ(tkGp9~-6H^8f$< literal 0 HcmV?d00001 diff --git a/apps/web/src/components/title/TitleTemplate.css b/apps/web/src/components/title/TitleTemplate.css index 4d1522a4d..6cf273837 100644 --- a/apps/web/src/components/title/TitleTemplate.css +++ b/apps/web/src/components/title/TitleTemplate.css @@ -1,383 +1,391 @@ /* ============================================================ - 标题模板系统(#2003):选择器 + 编辑器 + 保存弹窗 + 标题模板系统 v3(按 sketch 重构) + - 大卡片网格(图片背景 + 透明 Canvas 叠字 + 始终可见操作按钮) + - 编辑器弹窗(左竖屏预览 + 右参数 Tab) ============================================================ */ -/* ── Modal 头部 ── */ -.tt-modal-title { - display: flex; - align-items: center; - justify-content: space-between; - padding-right: 32px; -} -.tt-modal .ant-modal-body { - padding: 16px 20px; - max-height: 70vh; - overflow-y: auto; -} - -/* ── 分组 ── */ -.tt-section { - margin-bottom: 20px; -} -.tt-section:last-child { - margin-bottom: 0; -} -.tt-section-title { - font-size: 13px; - font-weight: 600; - color: var(--text-secondary, #6b7280); - margin: 0 0 10px; -} - -/* ── 空状态 ── */ -.tt-empty { - padding: 32px 16px; - text-align: center; - color: var(--text-secondary, #9ca3af); - background: var(--bg-secondary, #f9fafb); - border: 1px dashed var(--border-color, #e5e7eb); - border-radius: 8px; - font-size: 13px; -} -.tt-empty-emoji { - font-size: 32px; - margin-bottom: 8px; -} - -/* ── 卡片网格(4列) ── */ -.tt-grid { - display: grid; - grid-template-columns: repeat(4, 1fr); - gap: 12px; -} -@media (max-width: 768px) { - .tt-grid { - grid-template-columns: repeat(2, 1fr); - } -} - -.tt-card { - border: 2px solid var(--border-color, #e5e7eb); - border-radius: 10px; - background: #fff; - cursor: pointer; - transition: all 0.18s ease; - overflow: hidden; - position: relative; -} -.tt-card:hover { - border-color: var(--primary-color, #7c3aed); - transform: translateY(-2px); - box-shadow: 0 6px 16px rgba(124, 58, 237, 0.12); -} -.tt-card.active { - border-color: var(--primary-color, #7c3aed); - background: #faf5ff; - box-shadow: 0 0 0 1px var(--primary-color, #7c3aed); -} - -/* ── 预览区 ── */ -.tt-card-preview { - position: relative; - height: 80px; - background: #0f172a; - display: flex; - align-items: center; - justify-content: center; - overflow: hidden; -} -.tt-card-preview canvas { - max-width: 100%; - max-height: 100%; -} - -/* ── 标签 ── */ -.tt-tag { - position: absolute; - top: 6px; - left: 6px; - padding: 1px 6px; - border-radius: 4px; - font-size: 10px; - font-weight: 600; - line-height: 16px; -} -.tt-tag.sys { - background: rgba(124, 58, 237, 0.85); - color: #fff; -} -.tt-tag.mine { - background: rgba(16, 185, 129, 0.9); - color: #fff; -} -.tt-check { - position: absolute; - top: 6px; - right: 6px; - width: 20px; - height: 20px; - border-radius: 50%; - background: var(--primary-color, #7c3aed); - color: #fff; - font-size: 11px; - display: flex; - align-items: center; - justify-content: center; -} - -/* ── 卡片操作按钮(hover 显示) ── */ -.tt-card-actions { - position: absolute; - top: 6px; - right: 6px; - display: none; - gap: 4px; - align-items: center; -} -.tt-card.active .tt-check + .tt-card-actions { - top: 30px; -} -.tt-card:hover .tt-card-actions { - display: flex; -} -.tt-ico-btn { - width: 24px; - height: 24px; - border-radius: 4px; - border: none; - background: rgba(0, 0, 0, 0.55); - color: #fff; - cursor: pointer; - display: inline-flex; - align-items: center; - justify-content: center; - font-size: 12px; - padding: 0; - transition: 0.15s; -} -.tt-ico-btn:hover { - background: var(--primary-color, #7c3aed); -} -.tt-ico-btn.danger:hover { - background: #ef4444; -} - -/* ── 卡片底部 meta ── */ -.tt-card-meta { - display: flex; - align-items: center; - gap: 4px; - padding: 6px 8px; - font-size: 12px; - color: var(--text-primary, #1f2937); -} -.tt-card-emoji { - font-size: 13px; - flex-shrink: 0; -} -.tt-card-name { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - flex: 1; -} - -/* ── 编辑器(左右布局) ── */ -.tt-editor { - display: grid; - grid-template-columns: 360px 1fr; - gap: 16px; - min-height: 480px; -} -.tt-editor-preview { - background: #0f172a; - border-radius: 10px; - padding: 16px; - display: flex; - align-items: center; - justify-content: center; - min-height: 400px; -} -.tt-editor-panel { - overflow-y: auto; - max-height: 65vh; - padding-right: 4px; -} - -@media (max-width: 768px) { - .tt-editor { - grid-template-columns: 1fr; - } -} - -/* ============================================================ - #2003 内联网格(直接嵌在 TitleStylePanel 预览下方) - ============================================================ */ - -.tt-inline-wrap { +/* ── 面板容器(模板模式) ── */ +.ttv3-panel { display: flex; flex-direction: column; gap: 14px; } - -/* section header (label + 新建按钮) */ -.tt-section-header { +.ttv3-header { display: flex; - align-items: center; justify-content: space-between; + align-items: center; +} +.ttv3-title { + font-size: 15px; + font-weight: 600; + color: var(--text-primary, #1f2937); +} +.ttv3-new-btn.ant-btn { + background: linear-gradient(135deg, #6c5ce7, #a29bfe); + border: none; + border-radius: 20px; + font-size: 12px; + height: 28px; + padding: 0 14px; + font-weight: 500; + box-shadow: 0 2px 8px rgba(108, 92, 231, 0.25); +} +.ttv3-new-btn.ant-btn:hover { + background: linear-gradient(135deg, #5b4cdb, #8c83f5) !important; + color: #fff !important; +} +.ttv3-section-label { + font-size: 12px; + color: #888; + font-weight: 500; margin-bottom: 8px; } -.tt-section-header .tt-section-label, -.tt-section-header .tt-section-title { - font-size: 12px; - font-weight: 600; - color: var(--text-secondary, #6b7280); - margin: 0; -} -.tt-new-btn.ant-btn { - border-radius: 6px; - font-size: 12px; - height: 26px; - padding: 0 10px; +.ttv3-section { + margin-bottom: 6px; } -/* 内联网格:自适应列数(窄面板 2 列、宽面板 3-4 列) */ -.tt-grid--inline { - grid-template-columns: repeat(2, minmax(0, 1fr)); - gap: 8px; +/* ── 空状态 ── */ +.ttv3-empty { + background: #f8f8fc; + border-radius: 12px; + padding: 28px 16px; + text-align: center; + color: #aaa; } -@media (min-width: 1200px) { - .tt-grid--inline { - grid-template-columns: repeat(3, minmax(0, 1fr)); - } +.ttv3-empty-icon { + font-size: 28px; + margin-bottom: 6px; } -@media (min-width: 1600px) { - .tt-grid--inline { - grid-template-columns: repeat(4, minmax(0, 1fr)); - } +.ttv3-empty-text { + font-size: 12px; } -/* 内联卡片 */ -.tt-card--inline { - display: flex; - flex-direction: column; - border: 2px solid var(--border-color, #e5e7eb); - border-radius: 8px; +/* ── 卡片网格:minmax(180px,1fr) 自适应 ── */ +.ttv3-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); + gap: 12px; +} + +/* ── 卡片:3:4 竖版,圆角 14px ── */ +.ttv3-card { background: #fff; + border-radius: 14px; + border: 3px solid #e8e8ed; cursor: pointer; - transition: all 0.18s ease; + transition: all 0.15s ease; overflow: hidden; padding: 0; - font: inherit; + font-family: inherit; text-align: left; - position: relative; } -.tt-card--inline:hover { - border-color: var(--primary-color, #7c3aed); - transform: translateY(-1px); - box-shadow: 0 4px 12px rgba(124, 58, 237, 0.12); +.ttv3-card:hover { + border-color: #c5c0f0; + transform: translateY(-2px); + box-shadow: 0 6px 20px rgba(0, 0, 0, 0.1); } -.tt-card--inline.active { - border-color: var(--primary-color, #7c3aed); - background: #faf5ff; - box-shadow: 0 0 0 1px var(--primary-color, #7c3aed); +.ttv3-card.selected { + border-color: #6c5ce7; + box-shadow: 0 4px 16px rgba(108, 92, 231, 0.25); } -/* 大预览区(上半):深色背景+canvas */ -.tt-card-preview--lg { +/* ── 卡片预览区(3:4) ── */ +.ttv3-preview { + width: 100%; + aspect-ratio: 3/4; position: relative; - height: 90px; - background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%); + overflow: hidden; + border-radius: 11px 11px 0 0; +} +.ttv3-bg { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + object-fit: cover; + display: block; +} +/* 暗色渐变遮罩:顶部15%半透明黑 + 中部透明 + 底部45%黑 */ +.ttv3-vignette { + position: absolute; + inset: 0; + background: linear-gradient( + 180deg, + rgba(0, 0, 0, 0.45) 0%, + rgba(0, 0, 0, 0.15) 15%, + transparent 30%, + transparent 55%, + rgba(0, 0, 0, 0.6) 100% + ); + z-index: 1; + pointer-events: none; +} +/* 透明 Canvas 标题填充整个预览区 */ +.ttv3-preview .tt-fill-canvas-wrap { + position: absolute; + inset: 0; + z-index: 2; +} +.ttv3-preview .tt-fill-canvas-wrap canvas { + width: 100% !important; + height: 100% !important; + border-radius: 0; + display: block; +} + +/* ── 左上角角标(系统/我的) ── */ +.ttv3-badge { + position: absolute; + top: 8px; + left: 8px; + font-size: 10px; + padding: 2px 8px; + border-radius: 8px; + font-weight: 500; + color: #fff; + z-index: 3; + backdrop-filter: blur(4px); +} +.ttv3-badge--sys { + background: rgba(108, 92, 231, 0.88); +} +.ttv3-badge--mine { + background: rgba(0, 184, 148, 0.88); +} + +/* ── 右上角勾选圆圈 ── */ +.ttv3-check { + position: absolute; + top: 8px; + right: 8px; + width: 24px; + height: 24px; + border-radius: 50%; + background: rgba(0, 0, 0, 0.3); + border: 2px solid rgba(255, 255, 255, 0.7); + z-index: 3; display: flex; align-items: center; justify-content: center; - overflow: hidden; + font-size: 12px; + color: transparent; + transition: all 0.15s; } -.tt-card-preview--lg canvas { - max-width: 100%; - max-height: 100%; +.ttv3-check.on { + background: #6c5ce7; + border-color: #fff; + color: #fff; } -/* 选中圆点(单选 radio 样式) */ -.tt-radio-dot { - position: absolute; - top: 8px; - right: 8px; - width: 16px; - height: 16px; - border-radius: 50%; - border: 2px solid var(--primary-color, #7c3aed); - background: #fff; +/* ── 卡片底栏(名称 + 操作按钮) ── */ +.ttv3-footer { + padding: 10px 10px 12px; } -.tt-radio-dot::after { - content: ""; - position: absolute; - inset: 2px; - border-radius: 50%; - background: var(--primary-color, #7c3aed); -} - -/* 标签位置微调(内联卡片) */ -.tt-card--inline .tt-tag { - top: 8px; - left: 8px; -} - -/* hover/选中时操作按钮位置(避开 radio-dot) */ -.tt-card--inline .tt-card-actions { - top: 8px; - right: 8px; - display: none; - gap: 4px; -} -.tt-card--inline:hover .tt-card-actions { - display: flex; -} -/* 选中态 hover 时 actions 下移以避开 radio-dot */ -.tt-card--inline.active:hover .tt-card-actions { - top: 30px; -} -.tt-card--inline.active .tt-radio-dot { - display: block; -} -.tt-card--inline.active:hover .tt-radio-dot { - display: none; -} - -/* 底部 meta(下半):emoji + name */ -.tt-card-meta--lg { +.ttv3-name-row { display: flex; align-items: center; gap: 6px; - padding: 6px 8px; - font-size: 12px; - color: var(--text-primary, #1f2937); - background: #fff; - border-top: 1px solid var(--border-light, #f3f4f6); -} -.tt-card--inline.active .tt-card-meta--lg { - background: #faf5ff; -} -.tt-card-meta--lg .tt-card-emoji { + margin-bottom: 8px; font-size: 13px; + font-weight: 500; + white-space: nowrap; + overflow: hidden; + color: #1f2937; +} +.ttv3-emoji { + font-size: 14px; flex-shrink: 0; } -.tt-card-meta--lg .tt-card-name { - flex: 1; +.ttv3-name { overflow: hidden; text-overflow: ellipsis; - white-space: nowrap; + flex: 1; + min-width: 0; +} +.ttv3-tag { + font-size: 10px; + padding: 1px 6px; + border-radius: 6px; font-weight: 500; + flex-shrink: 0; +} +.ttv3-tag--sys { + background: #f0ecff; + color: #6c5ce7; +} +.ttv3-tag--mine { + background: #e6f9f4; + color: #00b894; } -/* 空状态(紧凑型) */ -.tt-empty--sm { - padding: 16px 12px; +/* ── 操作按钮:始终可见,等宽排列 ── */ +.ttv3-actions { + display: flex; + gap: 5px; +} +.ttv3-act { + flex: 1; + padding: 5px 0; + border: 1px solid #e8e8ed; + background: #fff; + border-radius: 7px; + font-size: 11px; + cursor: pointer; + color: #666; + font-weight: 500; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 3px; + transition: all 0.15s; + font-family: inherit; +} +.ttv3-act:hover:not(:disabled) { + background: #f5f5fa; + border-color: #d5d3e8; +} +.ttv3-act--primary { + background: #6c5ce7; + color: #fff; + border-color: #6c5ce7; +} +.ttv3-act--primary:hover:not(:disabled) { + background: #5b4cdb; + border-color: #5b4cdb; +} +.ttv3-act--danger { + color: #e74c3c; +} +.ttv3-act--danger:hover:not(:disabled) { + background: #fef2f2; +} +.ttv3-act:disabled { + opacity: 0.35; + cursor: not-allowed; +} + +/* ── FillPreview 公共容器 ── */ +.tt-fill-canvas-wrap { + position: relative; +} + +/* ── params-only(编辑器右侧)去掉多余 margin ── */ +.ttv3-params-only { + padding: 0; +} +.ttv3-params-only .ant-tabs { + margin: 0; +} + +/* ============================================================ + 编辑器 Modal(v3) + ============================================================ */ +.ttv3-modal .ant-modal-content { + border-radius: 16px; + overflow: hidden; + padding: 0; +} +.ttv3-modal .ant-modal-header { + padding: 16px 20px; + margin: 0; + border-bottom: 1px solid #f0f0f0; +} +.ttv3-modal .ant-modal-body { + padding: 0; + max-height: 75vh; +} +.ttv3-modal .ant-modal-footer { + padding: 14px 20px; + margin: 0; + border-top: 1px solid #f0f0f0; +} + +/* 编辑器两栏布局 */ +.ttv3-editor { + display: flex; + min-height: 500px; +} +.ttv3-editor-left { + width: 300px; + padding: 20px; + background: #f8f8fc; + flex-shrink: 0; + display: flex; + flex-direction: column; + align-items: center; + gap: 12px; + border-right: 1px solid #f0f0f0; +} +.ttv3-editor-canvas { + width: 200px; + aspect-ratio: 9/16; + border-radius: 12px; + overflow: hidden; + position: relative; + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12); + flex-shrink: 0; +} +.ttv3-editor-bg { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + object-fit: cover; +} +.ttv3-editor-canvas-inner { + position: absolute; + inset: 0; + z-index: 2; +} +.ttv3-editor-canvas-inner canvas { + width: 100% !important; + height: 100% !important; + border-radius: 0; + display: block; +} +.ttv3-editor-form { + width: 100%; + display: flex; + flex-direction: column; + gap: 10px; +} +.ttv3-form-row { + display: flex; + align-items: center; + gap: 10px; +} +.ttv3-form-row label { font-size: 12px; - gap: 6px; - border-radius: 6px; + font-weight: 600; + color: #333; + white-space: nowrap; + min-width: 44px; } -.tt-empty--sm .tt-empty-emoji { - font-size: 22px; - margin-bottom: 0; +.ttv3-form-row--grow { + flex: 1; +} +.ttv3-form-row--grow .ant-input { + flex: 1; +} + +.ttv3-editor-right { + flex: 1; + padding: 16px 20px; + overflow-y: auto; + min-width: 0; +} + +/* 侧栏较窄时(380px 侧栏):强制 2 列,卡片稍微紧凑 */ +@media (max-width: 540px) { + .ttv3-grid { + grid-template-columns: repeat(2, 1fr); + gap: 10px; + } + .ttv3-act { + font-size: 10px; + padding: 4px 0; + } + .ttv3-act .anticon { + display: none; + } } diff --git a/apps/web/src/components/title/TitleTemplateEditor.tsx b/apps/web/src/components/title/TitleTemplateEditor.tsx index 0e8e258c8..6b3b95fbe 100644 --- a/apps/web/src/components/title/TitleTemplateEditor.tsx +++ b/apps/web/src/components/title/TitleTemplateEditor.tsx @@ -1,15 +1,18 @@ /** - * 标题模板编辑器(#2003) + * 标题模板编辑器(v3 重构) * - * 左侧大预览(400×225,16:9),右侧复用 TitleStylePanel 进行参数调整。 - * 编辑完成后点"保存"弹出 SaveTemplateModal(名称必填),保存后回调 onSaved。 + * - Modal 弹窗 860px 宽 + * - 左侧:300px 竖屏预览区(图片背景+暗色渐变遮罩+透明 Canvas 叠字)+ 模板名称输入框 + * - 右侧:参数 Tab 面板(基础/描边/阴影/背景/排版),复用 TitleStylePanel 的 paramsOnly 模式 + * - 底部:取消 / 保存模板 按钮 + * - 内置模板编辑时保存会创建副本(带"副本"逻辑由 handleSave 处理) */ -import React, { useState } from "react" +import React, { useEffect, useMemo, useState } from "react" import { Modal, Button, Input, message } from "antd" import TitleStylePanel from "../../pages/generate/components/title/TitleStylePanel" import TitleMiniPreview from "../../pages/generate/components/title/TitleMiniPreview" import { POSITION_OPTIONS } from "../../pages/generate/constants" -import { FONT_OPTIONS, TITLE_PRESETS as SYSTEM_TITLE_PRESETS } from "./constants" +import { FONT_OPTIONS } from "./constants" import type { TitleSettings } from "../../pages/generate/types" import { DEFAULT_TITLE_SETTINGS_FULL } from "../../pages/generate/types" import { titleStyleConfigToCamel, camelToTitleStyleConfig } from "./utils" @@ -23,34 +26,32 @@ interface Props { onSaved: (template: TitleTemplate) => void } +/** 编辑器预览用的背景图(复用卡片池第一张) */ +const EDITOR_BG = "/title-templates/portrait1.jpg" + const TitleTemplateEditor: React.FC = ({ open, template, onClose, onSaved }) => { const { updateTemplate, createTemplate } = useTitleTemplates() - // 编辑态:完整 TitleSettings(camelCase) const [settings, setSettings] = useState(() => ({ ...DEFAULT_TITLE_SETTINGS_FULL, ...titleStyleConfigToCamel(template.style || {}), - title: "标题预览", + title: "预览标题文字", })) const [formName, setFormName] = useState(template.name || "") - const [formDesc, setFormDesc] = useState(template.description || "") const [formEmoji, setFormEmoji] = useState(template.emoji || "✨") const [saving, setSaving] = useState(false) - // 每次 open 重置 - React.useEffect(() => { + useEffect(() => { if (open) { setSettings({ ...DEFAULT_TITLE_SETTINGS_FULL, ...titleStyleConfigToCamel(template.style || {}), - title: "标题预览", + title: "预览标题文字", }) setFormName(template.name || "") - setFormDesc(template.description || "") setFormEmoji(template.emoji || "✨") } }, [open, template]) - /** 把单个 updater 包装成 setSettings patch */ const upd = (patch: Partial) => setSettings((s) => ({ ...s, ...patch })) const handleSave = () => { @@ -63,15 +64,13 @@ const TitleTemplateEditor: React.FC = ({ open, template, onClose, onSaved try { const snake = camelToTitleStyleConfig(settings) if (template.isBuiltin) { - // 内置模板保存时创建一个副本 - const t = createTemplate({ name, description: formDesc, emoji: formEmoji, style: snake }) + const t = createTemplate({ name, emoji: formEmoji, style: snake }) onSaved(t) } else { - updateTemplate(template.id, { name, description: formDesc, emoji: formEmoji, style: snake }) + updateTemplate(template.id, { name, emoji: formEmoji, style: snake }) onSaved({ ...template, name, - description: formDesc, emoji: formEmoji, style: snake, updatedAt: new Date().toISOString(), @@ -82,14 +81,20 @@ const TitleTemplateEditor: React.FC = ({ open, template, onClose, onSaved } } + // 编辑器内的预览用 settings:字号适配竖屏 + const previewSettings = useMemo(() => { + // 竖屏宽度 200px,按比例缩放字号,让预览看起来协调 + return { ...settings, size: Math.round(settings.size * 0.55) } + }, [settings]) + return ( +
} destroyOnClose - className="tt-modal tt-editor-modal" + className="ttv3-modal" + styles={{ body: { padding: 0 } }} > - {/* 模板名称/描述表单 */} -
-
- - setFormEmoji(e.target.value)} - /> +
+ {/* 左侧:竖屏预览 + 名称 */} +
+
+ +
+
+ +
+
+
+
+ + setFormEmoji(e.target.value)} + /> +
+
+ + setFormName(e.target.value)} + /> +
+
-
- - setFormName(e.target.value)} - /> -
-
- - setFormDesc(e.target.value)} - /> -
-
- -
- {/* 左侧实时预览 */} -
- -
- {/* 右侧编辑器 — 复用 TitleStylePanel 的细粒度能力 */} -
+ {/* 右侧:参数 Tab */} +
upd({ position: p, posX: null, posY: null })} @@ -160,23 +161,15 @@ const TitleTemplateEditor: React.FC = ({ open, template, onClose, onSaved }) } onToggleShadow={() => upd({ shadow: !settings.shadow })} - onApplyPreset={(key) => { - // 在编辑器中点击系统预设:把 preset 作为编辑起点 - const pp = SYSTEM_TITLE_PRESETS.find((x) => x.key === key) - if (pp) { - setSettings((cs) => ({ - ...cs, - ...titleStyleConfigToCamel(pp.style), - lineOverrides: [], - title: "标题预览", - })) - } + onApplyPreset={() => { + /* 编辑器内不使用系统预设快捷键 */ }} onUpdateStyle={(patch) => upd(patch)} activePreset={null} titlePresets={[]} POSITION_OPTIONS={POSITION_OPTIONS} FONT_OPTIONS={FONT_OPTIONS} + paramsOnly />
diff --git a/apps/web/src/components/title/TitleTemplateSelector.tsx b/apps/web/src/components/title/TitleTemplateSelector.tsx deleted file mode 100644 index 64107a298..000000000 --- a/apps/web/src/components/title/TitleTemplateSelector.tsx +++ /dev/null @@ -1,237 +0,0 @@ -/** - * 标题模板选择器(#2003) - * - * 参考「IP智能体设置 → 字幕设置 → 字幕模板」交互: - * - Modal 打开后展示模板卡片网格(4 列),每张卡片含 Canvas 预览 + 名称 + 标签 - * - 系统模板(sys:):只能「复制为我的」「应用」 - * - 自定义模板(usr:):支持编辑/复制/导出/删除 - * - 右上角「+ 新建模板」按钮进入编辑器 - * - * 受控使用:visible/onCancel/onSelect - */ -import React, { useMemo, useState } from "react" -import { Modal, Button, message, Popconfirm, Tooltip } from "antd" -import { - PlusOutlined, - EditOutlined, - CopyOutlined, - DeleteOutlined, - ExportOutlined, - CheckOutlined, -} from "@ant-design/icons" -import TitleMiniPreview from "../../pages/generate/components/title/TitleMiniPreview" -import type { TitleSettings } from "../../pages/generate/types" -import { DEFAULT_TITLE_SETTINGS_FULL } from "../../pages/generate/types" -import { titleStyleConfigToCamel } from "./utils" -import { useTitleTemplates } from "./useTitleTemplates" -import type { TitleTemplate } from "./template-types" -import TitleTemplateEditor from "./TitleTemplateEditor" -import "./TitleTemplate.css" - -interface Props { - open: boolean - /** 当前选中模板 id(可选) */ - selectedTemplateId?: string | null - onClose: () => void - /** 选择/应用模板:返回 camelCase TitleSettings 给调用方 */ - onSelect: (settings: TitleSettings, template: TitleTemplate) => void -} - -/** 把模板 style 渲染为完整 TitleSettings(带默认值),用于预览 */ -function templateToSettings(t: TitleTemplate): TitleSettings { - return { ...DEFAULT_TITLE_SETTINGS_FULL, ...titleStyleConfigToCamel(t.style) } -} - -const TitleTemplateSelector: React.FC = ({ - open, - selectedTemplateId, - onClose, - onSelect, -}) => { - const { templates, duplicateTemplate, deleteTemplate, exportTemplate, createTemplate } = - useTitleTemplates() - const [editingTemplate, setEditingTemplate] = useState(null) - const [editorOpen, setEditorOpen] = useState(false) - - const grouped = useMemo(() => { - return { - builtin: templates.filter((t) => t.isBuiltin), - custom: templates.filter((t) => !t.isBuiltin), - } - }, [templates]) - - const handleCreate = () => { - // 基于当前默认样式创建空白模板进入编辑 - const t = createTemplate({ - name: "我的标题模板", - style: { - font: DEFAULT_TITLE_SETTINGS_FULL.font, - size: 56, - color: "#ffffff", - bold: true, - italic: false, - stroke: true, - stroke_width: 4, - stroke_color: "#000000", - shadow: false, - bg_enabled: false, - line_height: 1.2, - max_chars_per_line: 10, - position: "bottom", - margin_top: 32, - line_overrides: [], - cover_title_config: null, - }, - }) - setEditingTemplate(t) - setEditorOpen(true) - } - - const handleEdit = (t: TitleTemplate) => { - setEditingTemplate(t) - setEditorOpen(true) - } - - const handleDuplicate = (t: TitleTemplate) => { - const dup = duplicateTemplate(t.id) - if (dup) message.success(`已复制:${dup.name}`) - } - - const handleDelete = (t: TitleTemplate) => { - deleteTemplate(t.id) - message.success("已删除模板") - } - - const handleExport = (t: TitleTemplate) => { - const json = exportTemplate(t.id) - if (!json) return - const blob = new Blob([json], { type: "application/json" }) - const url = URL.createObjectURL(blob) - const a = document.createElement("a") - a.href = url - a.download = `${t.name}.title-template.json` - a.click() - URL.revokeObjectURL(url) - } - - const handleApply = (t: TitleTemplate) => { - const settings = templateToSettings(t) - onSelect(settings, t) - } - - const renderCard = (t: TitleTemplate) => { - const isSelected = selectedTemplateId === t.id - const settings = templateToSettings(t) - return ( -
handleApply(t)} - > -
- - - {t.isBuiltin ? "系统" : "我的"} - - {isSelected && ( - - - - )} -
e.stopPropagation()}> - {!t.isBuiltin && ( - - - - )} - - - - - - - {!t.isBuiltin && ( - handleDelete(t)}> - - - - - )} -
-
-
- {t.emoji || "✨"} - - {t.name} - -
-
- ) - } - - return ( - <> - - 🎨 选择标题模板 - -
- } - open={open && !editorOpen} - onCancel={onClose} - footer={null} - width={880} - className="tt-modal" - destroyOnClose - > -
-
我的模板
- {grouped.custom.length === 0 ? ( -
-
✨
-
还没有自定义模板,点击右上角「新建模板」创建第一个吧
-
- ) : ( -
{grouped.custom.map(renderCard)}
- )} -
- -
-
系统模板
-
{grouped.builtin.map(renderCard)}
-
- - - {editorOpen && editingTemplate && ( - { - setEditorOpen(false) - setEditingTemplate(null) - }} - onSaved={(t) => { - setEditorOpen(false) - setEditingTemplate(null) - message.success(`已保存:${t.name}`) - // 保存后自动应用 - handleApply(t) - }} - /> - )} - - ) -} - -export default TitleTemplateSelector diff --git a/apps/web/src/pages/generate/components/title/TitleMiniPreview.tsx b/apps/web/src/pages/generate/components/title/TitleMiniPreview.tsx index 9611c0743..f2e189f91 100644 --- a/apps/web/src/pages/generate/components/title/TitleMiniPreview.tsx +++ b/apps/web/src/pages/generate/components/title/TitleMiniPreview.tsx @@ -18,10 +18,14 @@ interface Props { settings: TitleSettings width?: number sampleText?: string - /** 背景(预览用,默认深色渐变模拟视频底) */ + /** 背景(预览用,默认深色渐变模拟视频底),transparent=true 时忽略 */ background?: string - /** 高度(可选,默认 width/2) */ + /** 高度(可选,默认按 portrait 选比例) */ height?: number + /** 透明背景(卡片/编辑器预览叠加在图片上时使用) */ + transparent?: boolean + /** 纵向竖屏预览(9:16),true 时 aspect=16/9 适配手机视频比例 */ + portrait?: boolean } /** 按 maxCharsPerLine 自动换行 */ @@ -56,9 +60,11 @@ const TitleMiniPreview: React.FC = ({ sampleText, background = "linear-gradient(135deg,#1f2937,#111827)", height, + transparent = false, + portrait = false, }) => { const canvasRef = useRef(null) - const h = height ?? Math.round(width / 1.8) + const h = height ?? Math.round(width * (portrait ? 16 / 9 : 1 / 1.8)) const text = (sampleText || settings.title || "预览标题").trim() || "预览标题" useEffect(() => { @@ -74,9 +80,11 @@ const TitleMiniPreview: React.FC = ({ ctx.scale(dpr, dpr) ctx.clearRect(0, 0, width, h) - // 背景 - ctx.fillStyle = "#111827" - ctx.fillRect(0, 0, width, h) + // 背景(transparent 时跳过,用于叠加在图片上) + if (!transparent) { + ctx.fillStyle = "#111827" + ctx.fillRect(0, 0, width, h) + } // 分辨率缩放:以 360 宽为基准(对应 720p 的一半) const scale = width / 360 @@ -121,7 +129,8 @@ const TitleMiniPreview: React.FC = ({ startY = h / 2 - totalH / 2 + size / 2 } else { // bottom - startY = h - totalH - r(16) + size / 2 + const botMargin = portrait ? r(24) : r(16) + startY = h - totalH - botMargin + size / 2 } let centerX = width / 2 if (settings.position === "custom" && settings.posX != null) { @@ -190,7 +199,7 @@ const TitleMiniPreview: React.FC = ({ borderRadius: 6, display: "block", maxWidth: "100%", - background, + background: transparent ? "transparent" : background, }} /> ) diff --git a/apps/web/src/pages/generate/components/title/TitleStylePanel.tsx b/apps/web/src/pages/generate/components/title/TitleStylePanel.tsx index 0542d4c56..43d7b4de0 100644 --- a/apps/web/src/pages/generate/components/title/TitleStylePanel.tsx +++ b/apps/web/src/pages/generate/components/title/TitleStylePanel.tsx @@ -1,20 +1,25 @@ /** - * 标题样式设置面板(#2001 升级 / #2003 模板内联改造) + * 标题样式设置面板(v3 重构) * - * - #2001:描边/阴影/背景/排版/封面 6个Tab + Canvas 迷你预览 + 4款爆款字体 - * - #2003:标题模板直接内嵌在预览下方(卡片网格始终可见),不再弹窗选择 - * 支持新建/编辑/复制/导出/删除,编辑仍用 Modal 编辑器(左预览+右参数) + * 三种模式(mode): + * - 「默认」(不传):顶部实时预览 + 爆款预设网格 + 参数 Tab(基础/描边/阴影/背景/排版/封面) + * - 「模板网格」(enableTemplates=true, paramsOnly=false): + * 仅显示顶部标题栏「标题模板」+ 我的模板/系统模板两个分组的大卡片网格, + * 卡片用图片背景 + 真实标题渲染(Canvas 叠加透明渲染),底部始终可见 + * 编辑/复制/导出/删除 4 个按钮,选中卡片紫边紫影。 + * - 「仅参数」(paramsOnly=true):仅渲染参数 Tab(给编辑器右侧用,不显示预览/预设/模板网格) * - * 向后兼容:旧 props 全部保留;enableTemplates=true 时渲染内联网格,false 保留旧爆款预设。 + * 历史:#2001 6 Tab 参数面板;#2003 初版内联模板网格;v3 按 sketch 重写大卡片 + 图片背景 */ -import React, { useMemo, useState } from "react" -import { Tabs, Button, message, Popconfirm, Tooltip } from "antd" +import React, { useCallback, useEffect, useMemo, useRef, useState } from "react" +import { Tabs, Button, message, Popconfirm } from "antd" import { PlusOutlined, EditOutlined, CopyOutlined, DeleteOutlined, ExportOutlined, + CheckOutlined, } from "@ant-design/icons" import type { TitleSettings } from "../../types" import { @@ -65,12 +70,16 @@ interface TitleStylePanelProps { onUpdateStyle?: (patch: Partial) => void showCoverToggle?: boolean previewWidth?: number - /** 是否启用「标题模板」内联网格(替代旧爆款预设) */ + /** 启用「标题模板」模式:显示大卡片网格,隐藏参数 Tab 和旧预览 */ enableTemplates?: boolean /** 当前选中模板 id */ selectedTemplateId?: string | null /** 应用模板回调(父组件负责合并到 state) */ onApplyTemplate?: (settings: TitleSettings, template: TitleTemplate) => void + /** 仅渲染参数 Tab(编辑器右侧用,不显示预览/预设/模板网格) */ + paramsOnly?: boolean + /** 编辑器模式下,模板编辑器组件不在这里渲染(由父组件承载) */ + hideEditor?: boolean } /* ── Slider 行 ── */ @@ -154,6 +163,45 @@ const ColorPicker: React.FC<{ ) } +/* ── 卡片预览:用 ref 测量容器宽度后再渲染透明 Canvas,保证文字清晰 ── */ +const FillPreview: React.FC<{ + settings: TitleSettings + sampleText: string + portrait?: boolean +}> = ({ settings, sampleText, portrait }) => { + const wrapRef = useRef(null) + const [w, setW] = useState(0) + useEffect(() => { + const el = wrapRef.current + if (!el) return + const update = () => setW(Math.floor(el.clientWidth)) + update() + const ro = new ResizeObserver(update) + ro.observe(el) + return () => ro.disconnect() + }, []) + return ( +
+ {w > 0 && ( + + )} +
+ ) +} + +/* ── 模板卡片预览背景图池:按 index 轮换 ── */ +const PREVIEW_BG_IMAGES = [ + "/title-templates/portrait1.jpg", + "/title-templates/portrait2.jpg", + "/title-templates/scene1.jpg", +] + const TitleStylePanel: React.FC = ({ settings, onUpdatePosition, @@ -173,6 +221,8 @@ const TitleStylePanel: React.FC = ({ selectedTemplateId, onApplyTemplate, onUpdateStyle, + paramsOnly = false, + hideEditor = false, }) => { const upd = (patch: Partial) => { onUpdateStyle?.(patch) @@ -194,142 +244,186 @@ const TitleStylePanel: React.FC = ({ [templates], ) - const handleRequestCreate = () => { + const handleRequestCreate = useCallback(() => { + // 新建模板:若已有选中模板,以其样式为起点;否则以当前 settings 为起点 + let base: TitleSettings = settings + if (selectedTemplateId) { + const sel = templates.find((t) => t.id === selectedTemplateId) + if (sel) { + base = { + ...DEFAULT_TITLE_SETTINGS_FULL, + ...titleStyleConfigToCamel(sel.style), + title: settings.title, + aiAutoSelect: settings.aiAutoSelect, + coverTitle: settings.coverTitle, + } + } + } const t = createTemplate({ name: "我的标题模板", style: { - font: settings.font, - size: settings.size, - color: settings.color, - bold: settings.bold, - italic: settings.italic, - stroke: settings.stroke, - stroke_width: settings.strokeWidth ?? 4, - stroke_color: settings.strokeColor ?? "#000000", - shadow: settings.shadow, - shadow_offset_x: settings.shadowOffsetX ?? 2, - shadow_offset_y: settings.shadowOffsetY ?? 2, - shadow_blur: settings.shadowBlur ?? 4, - shadow_color: settings.shadowColor ?? "rgba(0,0,0,0.8)", - bg_enabled: settings.bgEnabled, - bg_color: settings.bgColor, - bg_padding: settings.bgPadding, - bg_radius: settings.bgRadius, - line_height: settings.lineHeight, - margin_top: settings.marginTop, - max_chars_per_line: settings.maxCharsPerLine, - position: (settings.position === "custom" ? "bottom" : settings.position) as + font: base.font, + size: base.size, + color: base.color, + bold: base.bold, + italic: base.italic, + stroke: base.stroke, + stroke_width: base.strokeWidth ?? 4, + stroke_color: base.strokeColor ?? "#000000", + shadow: base.shadow, + shadow_offset_x: base.shadowOffsetX ?? 2, + shadow_offset_y: base.shadowOffsetY ?? 2, + shadow_blur: base.shadowBlur ?? 4, + shadow_color: base.shadowColor ?? "rgba(0,0,0,0.8)", + bg_enabled: base.bgEnabled, + bg_color: base.bgColor, + bg_padding: base.bgPadding, + bg_radius: base.bgRadius, + line_height: base.lineHeight, + margin_top: base.marginTop, + max_chars_per_line: base.maxCharsPerLine, + position: (base.position === "custom" ? "bottom" : base.position) as "top" | "center" | "bottom" | "custom", - pos_x: settings.posX ?? undefined, - pos_y: settings.posY ?? undefined, + pos_x: base.posX ?? undefined, + pos_y: base.posY ?? undefined, line_overrides: [], cover_title_config: null, }, }) setEditingTemplate(t) setEditorOpen(true) - } + }, [createTemplate, settings, selectedTemplateId, templates]) - const handleRequestEdit = (tpl: TitleTemplate) => { + const handleRequestEdit = useCallback((tpl: TitleTemplate) => { setEditingTemplate(tpl) setEditorOpen(true) - } + }, []) - const handleDuplicate = (t: TitleTemplate) => { - const dup = duplicateTemplate(t.id) - if (dup) message.success(`已复制:${dup.name}`) - } - const handleDelete = (t: TitleTemplate) => { - deleteTemplate(t.id) - message.success("已删除模板") - } - const handleExport = (t: TitleTemplate) => { - const json = exportTemplate(t.id) - if (!json) return - const blob = new Blob([json], { type: "application/json" }) - const url = URL.createObjectURL(blob) - const a = document.createElement("a") - a.href = url - a.download = `${t.name}.title-template.json` - a.click() - URL.revokeObjectURL(url) - } + const handleDuplicate = useCallback( + (t: TitleTemplate) => { + const dup = duplicateTemplate(t.id) + if (dup) message.success(`已复制:${dup.name}`) + }, + [duplicateTemplate], + ) + const handleDelete = useCallback( + (t: TitleTemplate) => { + deleteTemplate(t.id) + message.success("已删除模板") + }, + [deleteTemplate], + ) + const handleExport = useCallback( + (t: TitleTemplate) => { + const json = exportTemplate(t.id) + if (!json) return + const blob = new Blob([json], { type: "application/json" }) + const url = URL.createObjectURL(blob) + const a = document.createElement("a") + a.href = url + a.download = `${t.name}.title-template.json` + a.click() + URL.revokeObjectURL(url) + }, + [exportTemplate], + ) - const handleSelectTemplate = (_preview: TitleSettings, tpl: TitleTemplate) => { - // _preview 是归一化小预览,应用时用完整 DEFAULT + 原始 style 保证真实尺寸 - const full: TitleSettings = { - ...DEFAULT_TITLE_SETTINGS_FULL, - ...titleStyleConfigToCamel(tpl.style), - title: settings.title, - aiAutoSelect: settings.aiAutoSelect, - coverTitle: settings.coverTitle, - } - onApplyTemplate?.(full, tpl) - } + const handleSelectTemplate = useCallback( + (tpl: TitleTemplate) => { + const full: TitleSettings = { + ...DEFAULT_TITLE_SETTINGS_FULL, + ...titleStyleConfigToCamel(tpl.style), + title: settings.title, + aiAutoSelect: settings.aiAutoSelect, + coverTitle: settings.coverTitle, + } + onApplyTemplate?.(full, tpl) + }, + [onApplyTemplate, settings], + ) - const handleEditorSaved = (tpl: TitleTemplate) => { - setEditorOpen(false) - setEditingTemplate(null) - message.success(`已保存:${tpl.name}`) - handleSelectTemplate(DEFAULT_TITLE_SETTINGS_FULL, tpl) - } + const handleEditorSaved = useCallback( + (tpl: TitleTemplate) => { + setEditorOpen(false) + setEditingTemplate(null) + message.success(`已保存:${tpl.name}`) + handleSelectTemplate(tpl) + }, + [handleSelectTemplate], + ) - const renderTemplateCard = (t: TitleTemplate) => { + /* ── 渲染模板大卡片(v3:图片背景 + 透明 Canvas 叠字 + 底栏始终可见操作按钮) ── */ + const renderTemplateCard = (t: TitleTemplate, idx: number, section: "mine" | "sys") => { const isSelected = selectedTemplateId === t.id - const preview = templateToPreviewSettings(t, 48) + const bgIdx = idx % PREVIEW_BG_IMAGES.length + const bgImg = PREVIEW_BG_IMAGES[bgIdx] + // 卡片用的预览 settings:字号按 3:4 竖版比例缩放 + const preview = templateToPreviewSettings(t, 42) return ( - - - )} - -
+
+
+ {t.emoji || "✨"} + + {t.name} + + + {section === "sys" ? "系统" : "我的"} + +
+
e.stopPropagation()}> + + + + handleDelete(t)}> + - - - - - {!t.isBuiltin && ( - handleDelete(t)}> - - - - - )} +
-
- {t.emoji || "✨"} - - {t.name} - -
- +
) } - /* ── 旧爆款预设网格(enableTemplates=false 时显示,作为编辑器内快速起点) ── */ + /* ── 旧爆款预设网格(paramsOnly 或 enableTemplates 时不显示) ── */ const renderPresetGrid = () => (
{TITLE_PRESETS.map((p) => { @@ -356,6 +450,380 @@ const TitleStylePanel: React.FC = ({
) + /* ── 参数 Tab 面板(编辑器右侧复用) ── */ + const renderParamsTabs = () => ( + +
+
+ + +
+
+ + +
+
+ + + +
+ +
+ + + + +
+
+ + upd({ color: c })} + /> + + ), + }, + { + key: "stroke", + label: "描边", + children: ( + <> +
+ +
+ {settings.stroke && ( + <> + upd({ strokeWidth: v })} + /> + upd({ strokeColor: c })} + /> + + )} + + ), + }, + { + key: "shadow", + label: "阴影", + children: ( + <> +
+ +
+ {settings.shadow && ( + <> + upd({ shadowOffsetX: v })} + /> + upd({ shadowOffsetY: v })} + /> + upd({ shadowBlur: v })} + /> +
+ + upd({ shadowColor: e.target.value })} + placeholder="rgba(0,0,0,0.8)" + /> +
+ + )} + + ), + }, + { + key: "bg", + label: "背景", + children: ( + <> +
+ +
+ {settings.bgEnabled && ( + <> + upd({ bgColor: c })} + /> + upd({ bgPadding: v })} + /> + upd({ bgRadius: v })} + /> + + )} + + ), + }, + { + key: "layout", + label: "排版", + children: ( + <> + upd({ maxCharsPerLine: v })} + /> +
+ 0 = 不自动换行(按 / 手动分行) +
+ upd({ lineHeight: Number(v.toFixed(2)) })} + /> + upd({ marginTop: v })} + /> + + ), + }, + ...(showCoverToggle + ? [ + { + key: "cover", + label: "封面", + children: ( + <> +
+ +
+ {coverOpen && settings.coverTitle && ( +
+ 封面样式已开启。可在「封面设置」面板单独调整封面标题的字体/字号/颜色。 +
+ )} + + ), + }, + ] + : []), + ]} + /> + ) + + /* ── 模板模式:只显示网格 ── */ + if (enableTemplates && !paramsOnly) { + return ( +
+
+ 标题模板 + +
+ +
+
我的模板
+ {grouped.custom.length === 0 ? ( +
+
✨
+
还没有自定义模板,点右上角「新建模板」创建
+
+ ) : ( +
+ {grouped.custom.map((t, i) => renderTemplateCard(t, i, "mine"))} +
+ )} +
+ +
+
系统模板
+
+ {grouped.builtin.map((t, i) => renderTemplateCard(t, i, "sys"))} +
+
+ + {!hideEditor && editorOpen && editingTemplate && ( + { + setEditorOpen(false) + setEditingTemplate(null) + }} + onSaved={handleEditorSaved} + /> + )} +
+ ) + } + + /* ── paramsOnly:仅参数 Tab ── */ + if (paramsOnly) { + return
{renderParamsTabs()}
+ } + + /* ── 默认模式:顶部预览 + 预设 + 参数 Tab ── */ return (
{/* 实时迷你预览 */} @@ -367,368 +835,18 @@ const TitleStylePanel: React.FC = ({ />
- {/* 标题模板(内联)/ 爆款预设(旧) */} - {enableTemplates ? ( -
-
-
-
- 我的模板 - -
- {grouped.custom.length === 0 ? ( -
- ✨ - 还没有自定义模板,点击右上角「新建模板」 -
- ) : ( -
- {grouped.custom.map(renderTemplateCard)} -
- )} -
- -
-
- 系统模板 -
-
- {grouped.builtin.map(renderTemplateCard)} -
-
-
+ {/* 爆款预设 */} +
+
+ + {activePreset && 已选}
- ) : ( -
-
- - {activePreset && 已选} -
- {renderPresetGrid()} -
- )} + {renderPresetGrid()} +
- -
-
- - -
-
- - -
-
+ {renderParamsTabs()} - - -
- -
- - - - -
-
- - upd({ color: c })} - /> - - ), - }, - { - key: "stroke", - label: "描边", - children: ( - <> -
- -
- {settings.stroke && ( - <> - upd({ strokeWidth: v })} - /> - upd({ strokeColor: c })} - /> - - )} - - ), - }, - { - key: "shadow", - label: "阴影", - children: ( - <> -
- -
- {settings.shadow && ( - <> - upd({ shadowOffsetX: v })} - /> - upd({ shadowOffsetY: v })} - /> - upd({ shadowBlur: v })} - /> -
- - upd({ shadowColor: e.target.value })} - placeholder="rgba(0,0,0,0.8)" - /> -
- - )} - - ), - }, - { - key: "bg", - label: "背景", - children: ( - <> -
- -
- {settings.bgEnabled && ( - <> - upd({ bgColor: c })} - /> - upd({ bgPadding: v })} - /> - upd({ bgRadius: v })} - /> - - )} - - ), - }, - { - key: "layout", - label: "排版", - children: ( - <> - upd({ maxCharsPerLine: v })} - /> -
- 0 = 不自动换行(按 / 手动分行) -
- upd({ lineHeight: Number(v.toFixed(2)) })} - /> - upd({ marginTop: v })} - /> - - ), - }, - ...(showCoverToggle - ? [ - { - key: "cover", - label: "封面", - children: ( - <> -
- -
- {coverOpen && settings.coverTitle && ( -
- 封面样式已开启。可在「封面设置」面板单独调整封面标题的字体/字号/颜色。 -
- )} - - ), - }, - ] - : []), - ]} - /> - - {editorOpen && editingTemplate && ( + {!hideEditor && editorOpen && editingTemplate && (