2654c2afcc
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 1s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 1s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Successful in 1m37s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 1m57s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 1m59s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 2m4s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 2m15s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 2m28s
CI/CD Pipeline / Validate - Style (pull_request) Successful in 2m53s
AI Code Review / AI Code Review (pull_request) Successful in 6m33s
CI/CD Pipeline / Validate - Security (pull_request) Successful in 6m40s
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Successful in 1s
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 5m21s
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 28s
ACR Cleanup / ACR Image Cleanup (pull_request_target) Successful in 31s
根因分析:
1. AI数字人TitleStylePanel 传 enableTemplates=true 导致面板直接进入
「标题模板网格」分支,完全不渲染字体/字号/位置等参数Tab,用户看不到任何字体选择UI
2. 智能剪辑 GeneratePage 直接写了裸 prop `enableTemplates` 但没定义该变量
(TS不报错是因为之前 props 解构顺序导致引用了 formState 之外的全局?
实际效果是传了 undefined,面板落回默认模式但易出错),显式声明为 false
3. 项目零 @font-face/零字体文件,优设标题黑/阿里普惠体/抖音美好体等自定义
字体从未加载,浏览器直接 fallback 到 sans-serif,用户感知「切换字体没反应」
4. CoverEditorModal 的 PRESET_FONTS family 用的是不通用的英文引号/错误名字
('DouYin MeiHaoTi'/'DouYu ZhuangGuangTi' 实际不存在),且和标题面板字体列表
不同步,两边视觉不一致
5. CSS .xx-ce-canvas>div { pointer-events:none } 把可拖拽的标题文字层也禁了,
封面预览文字无法响应mousedown拖拽(这是「文字拖拽不动」的另一个原因)
6. TitleMiniPreview 用Canvas绘制,Web Font异步加载,首次draw时字体还没就绪
先用sans-serif画完不重绘,视觉上「选了字体没反应」
修复:
- index.html: 引入Google Fonts CDN(Noto Sans SC / Noto Serif SC / ZCOOL
三款 / Ma Shan Zheng / Long Cang / Liu Jian Mao Cao / Zhi Mang Xing)
- components/title/constants.ts: FONT_OPTIONS family栈补全兜底字体链;
新增「站酷小薇体/马善政毛笔/龙藏体/流江毛笔草/志莽行书」5款可加载字体
- pages/generate/types/cover.ts: 重写PRESET_FONTS/SYSTEM_FONTS,复用统一family栈,
新增tag字段区分preset/hand/serif/sans/mono;删去不可用的「斗鱼追光体2.0」
- CoverEditorModal.tsx: 字体下拉合并ALL_FONTS、圆点颜色按tag区分;
文字层显式 pointer-events:auto 保证可拖拽;清理未使用import
- generate.css: 精准选择装饰层禁用pointer-events(不再一刀切xx-ce-canvas>div);
新增hand/serif/mono三色字体圆点
- GeneratePage.tsx: 显式 const enableTemplates=false,JSX传参改为
enableTemplates={enableTemplates}
- PanelTitleConfig.tsx(AI数字人): enableTemplates 改 false,让AI数字人
标题面板正确渲染「预设+参数Tab」,字体/描边/阴影/排版参数全部可见
- TitleMiniPreview.tsx: useEffect里监听document.fonts.ready,字体加载完成后
重绘一次Canvas,避免首帧fallback sans-serif
393 lines
9.2 KiB
TypeScript
393 lines
9.2 KiB
TypeScript
/**
|
||
* 标题样式相关常量(#2001)
|
||
* - 字体列表(新增4款爆款字体)
|
||
* - 色板(常用标题字色/描边色/背景色)
|
||
* - 预设样式方案(10 个,含抖音爆款黄)
|
||
*/
|
||
import type { TitleStyleConfig } from "./types"
|
||
|
||
/* ── 字体选项(#2001:新增优设标题黑/阿里普惠体Bold/抖音美好体/思源黑体Heavy) ── */
|
||
export interface FontOption {
|
||
value: string
|
||
label: string
|
||
/** CSS font-family 栈 */
|
||
family: string
|
||
/** 爆款/常用标签 */
|
||
tag?: "hot" | "new"
|
||
}
|
||
|
||
export const FONT_OPTIONS: FontOption[] = [
|
||
{
|
||
value: "优设标题黑",
|
||
label: "优设标题黑",
|
||
// 原版"优设标题黑"为商用字体,非开源;这里优先使用本地已安装字体,兜底用 ZCOOL QingKe HuangYou(站酷庆科黄油体,同为厚重黑体/海报风格,可从 Google Fonts 加载)
|
||
family:
|
||
'"YouSheBiaoTiHei","YouShe Title Black","ZCOOL QingKe HuangYou","Noto Sans SC","PingFang SC","Microsoft YaHei",sans-serif',
|
||
tag: "hot",
|
||
},
|
||
{
|
||
value: "阿里普惠体Bold",
|
||
label: "阿里普惠体Bold",
|
||
// 阿里普惠体需要从阿里官网下载;兜底用 Noto Sans SC 900(同等字重)
|
||
family:
|
||
'"Alibaba PuHuiTi","Alibaba PuHuiTi Bold","Alibaba Sans","Noto Sans SC",system-ui,"PingFang SC","Microsoft YaHei",sans-serif',
|
||
tag: "hot",
|
||
},
|
||
{
|
||
value: "抖音美好体",
|
||
label: "抖音美好体",
|
||
// 抖音美好体版权字体兜底 ZCOOL KuaiLe(站酷快乐体,圆润卡通风格近似)
|
||
family:
|
||
'"Douyin Sans","DouyinSans","ZCOOL KuaiLe","Noto Sans SC","PingFang SC","Microsoft YaHei",sans-serif',
|
||
tag: "hot",
|
||
},
|
||
{
|
||
value: "思源黑体Heavy",
|
||
label: "思源黑体Heavy",
|
||
family:
|
||
'"Noto Sans SC","Source Han Sans SC","Source Han Sans CN Heavy","PingFang SC","Microsoft YaHei",sans-serif',
|
||
tag: "new",
|
||
},
|
||
{
|
||
value: "思源黑体",
|
||
label: "思源黑体",
|
||
family: '"Noto Sans SC","Source Han Sans SC","PingFang SC","Microsoft YaHei",sans-serif',
|
||
},
|
||
{
|
||
value: "思源宋体",
|
||
label: "思源宋体",
|
||
family: '"Noto Serif SC","Source Han Serif SC","Songti SC","SimSun",serif',
|
||
},
|
||
{
|
||
value: "苹方",
|
||
label: "苹方",
|
||
family:
|
||
'"PingFang SC",-apple-system,blinkmacsystemfont,"Helvetica Neue","Noto Sans SC",sans-serif',
|
||
},
|
||
{
|
||
value: "微软雅黑",
|
||
label: "微软雅黑",
|
||
family: '"Microsoft YaHei","PingFang SC","Noto Sans SC",sans-serif',
|
||
},
|
||
{
|
||
value: "楷体",
|
||
label: "楷体",
|
||
family: '"KaiTi","STKaiti","DFKai-SB","Kaiti SC",serif',
|
||
},
|
||
{
|
||
value: "站酷小薇体",
|
||
label: "站酷小薇体",
|
||
family: '"ZCOOL XiaoWei","Noto Serif SC",serif',
|
||
},
|
||
{
|
||
value: "马善政毛笔",
|
||
label: "马善政毛笔",
|
||
family: '"Ma Shan Zheng","STXingkai","KaiTi",cursive',
|
||
},
|
||
{
|
||
value: "龙藏体",
|
||
label: "龙藏体",
|
||
family: '"Long Cang","STXingkai","KaiTi",cursive',
|
||
},
|
||
{
|
||
value: "流江毛笔草",
|
||
label: "流江毛笔草",
|
||
family: '"Liu Jian Mao Cao","STXingkai",cursive',
|
||
},
|
||
{
|
||
value: "志莽行书",
|
||
label: "志莽行书",
|
||
family: '"Zhi Mang Xing","STXingkai",cursive',
|
||
},
|
||
]
|
||
|
||
/** 根据中文名取 font-family 栈(找不到回退思源黑体) */
|
||
export function getFontFamily(font: string): string {
|
||
const f = FONT_OPTIONS.find((x) => x.value === font)
|
||
if (f) return f.family
|
||
return FONT_OPTIONS[4].family // 思源黑体
|
||
}
|
||
|
||
/* ── 色板 ── */
|
||
/** 标题字色(常用爆款色) */
|
||
export const TITLE_COLOR_PALETTE: string[] = [
|
||
"#ffffff",
|
||
"#000000",
|
||
"#ffd700", // 抖音黄
|
||
"#ff2d55", // 抖音红
|
||
"#ff4081",
|
||
"#00e5ff",
|
||
"#d4a843",
|
||
"#ffa500",
|
||
"#52c41a",
|
||
"#1890ff",
|
||
"#7c3aed",
|
||
"#ff6b35",
|
||
]
|
||
|
||
/** 描边色(黑/白/灰为主) */
|
||
export const STROKE_COLOR_PALETTE: string[] = [
|
||
"#000000",
|
||
"#ffffff",
|
||
"#333333",
|
||
"#555555",
|
||
"#8b0000",
|
||
"#001f3f",
|
||
]
|
||
|
||
/** 背景色(带透明度) */
|
||
export const BG_COLOR_PALETTE: string[] = [
|
||
"rgba(0,0,0,0.5)",
|
||
"rgba(0,0,0,0.7)",
|
||
"rgba(0,0,0,0.3)",
|
||
"rgba(255,215,0,0.9)",
|
||
"rgba(255,45,85,0.85)",
|
||
"rgba(124,58,237,0.85)",
|
||
"rgba(24,144,255,0.85)",
|
||
"rgba(82,196,26,0.85)",
|
||
]
|
||
|
||
/* ── 预设样式方案(10 个,含抖音爆款黄) ── */
|
||
export interface TitlePreset {
|
||
key: string
|
||
label: string
|
||
emoji: string
|
||
/** 应用时覆盖到 TitleStyleConfig 的字段(其他字段保持当前值) */
|
||
style: Partial<TitleStyleConfig>
|
||
}
|
||
|
||
const BASE: Partial<TitleStyleConfig> = {
|
||
position: "bottom",
|
||
line_overrides: [],
|
||
cover_title_config: null,
|
||
}
|
||
|
||
export const TITLE_PRESETS: TitlePreset[] = [
|
||
{
|
||
key: "douyin_hot",
|
||
label: "抖音爆款黄",
|
||
emoji: "🔥",
|
||
style: {
|
||
...BASE,
|
||
font: "优设标题黑",
|
||
size: 80,
|
||
color: "#ffd700",
|
||
bold: true,
|
||
italic: false,
|
||
stroke: true,
|
||
stroke_width: 8,
|
||
stroke_color: "#000000",
|
||
shadow: true,
|
||
shadow_offset_x: 3,
|
||
shadow_offset_y: 3,
|
||
shadow_blur: 6,
|
||
shadow_color: "rgba(0,0,0,0.6)",
|
||
bg_enabled: false,
|
||
line_height: 1.25,
|
||
max_chars_per_line: 8,
|
||
},
|
||
},
|
||
{
|
||
key: "classic_white",
|
||
label: "经典白字黑描边",
|
||
emoji: "⚪",
|
||
style: {
|
||
...BASE,
|
||
font: "思源黑体Heavy",
|
||
size: 56,
|
||
color: "#ffffff",
|
||
bold: true,
|
||
italic: false,
|
||
stroke: true,
|
||
stroke_width: 5,
|
||
stroke_color: "#000000",
|
||
shadow: false,
|
||
bg_enabled: false,
|
||
line_height: 1.2,
|
||
max_chars_per_line: 10,
|
||
},
|
||
},
|
||
{
|
||
key: "red_bold",
|
||
label: "醒目红字",
|
||
emoji: "🔴",
|
||
style: {
|
||
...BASE,
|
||
font: "优设标题黑",
|
||
size: 72,
|
||
color: "#ff2d55",
|
||
bold: true,
|
||
italic: false,
|
||
stroke: true,
|
||
stroke_width: 6,
|
||
stroke_color: "#ffffff",
|
||
shadow: true,
|
||
shadow_offset_x: 2,
|
||
shadow_offset_y: 2,
|
||
shadow_blur: 5,
|
||
shadow_color: "rgba(0,0,0,0.5)",
|
||
bg_enabled: false,
|
||
line_height: 1.2,
|
||
max_chars_per_line: 9,
|
||
},
|
||
},
|
||
{
|
||
key: "black_gold",
|
||
label: "黑金质感",
|
||
emoji: "🟡",
|
||
style: {
|
||
...BASE,
|
||
font: "思源宋体",
|
||
size: 52,
|
||
color: "#d4a843",
|
||
bold: true,
|
||
italic: false,
|
||
stroke: false,
|
||
shadow: true,
|
||
shadow_offset_x: 2,
|
||
shadow_offset_y: 2,
|
||
shadow_blur: 8,
|
||
shadow_color: "rgba(0,0,0,0.8)",
|
||
bg_enabled: false,
|
||
line_height: 1.25,
|
||
max_chars_per_line: 10,
|
||
},
|
||
},
|
||
{
|
||
key: "neon_blue",
|
||
label: "霓虹发光",
|
||
emoji: "💙",
|
||
style: {
|
||
...BASE,
|
||
font: "阿里普惠体Bold",
|
||
size: 60,
|
||
color: "#00e5ff",
|
||
bold: true,
|
||
italic: false,
|
||
stroke: false,
|
||
shadow: true,
|
||
shadow_offset_x: 0,
|
||
shadow_offset_y: 0,
|
||
shadow_blur: 16,
|
||
shadow_color: "#00e5ff",
|
||
bg_enabled: false,
|
||
line_height: 1.2,
|
||
max_chars_per_line: 10,
|
||
},
|
||
},
|
||
{
|
||
key: "bg_black",
|
||
label: "黑底白字",
|
||
emoji: "⬛",
|
||
style: {
|
||
...BASE,
|
||
font: "思源黑体Heavy",
|
||
size: 52,
|
||
color: "#ffffff",
|
||
bold: true,
|
||
italic: false,
|
||
stroke: false,
|
||
shadow: false,
|
||
bg_enabled: true,
|
||
bg_color: "rgba(0,0,0,0.7)",
|
||
bg_padding: 16,
|
||
bg_radius: 8,
|
||
line_height: 1.3,
|
||
max_chars_per_line: 10,
|
||
},
|
||
},
|
||
{
|
||
key: "bg_yellow",
|
||
label: "黄底黑字",
|
||
emoji: "🟨",
|
||
style: {
|
||
...BASE,
|
||
font: "抖音美好体",
|
||
size: 56,
|
||
color: "#000000",
|
||
bold: true,
|
||
italic: false,
|
||
stroke: false,
|
||
shadow: false,
|
||
bg_enabled: true,
|
||
bg_color: "rgba(255,215,0,0.95)",
|
||
bg_padding: 14,
|
||
bg_radius: 6,
|
||
line_height: 1.2,
|
||
max_chars_per_line: 9,
|
||
},
|
||
},
|
||
{
|
||
key: "sweet_pink",
|
||
label: "温柔甜美粉",
|
||
emoji: "🌸",
|
||
style: {
|
||
...BASE,
|
||
font: "阿里普惠体Bold",
|
||
size: 50,
|
||
color: "#ff4081",
|
||
bold: false,
|
||
italic: false,
|
||
stroke: true,
|
||
stroke_width: 4,
|
||
stroke_color: "#ffffff",
|
||
shadow: true,
|
||
shadow_offset_x: 2,
|
||
shadow_offset_y: 2,
|
||
shadow_blur: 4,
|
||
shadow_color: "rgba(255,64,129,0.4)",
|
||
bg_enabled: false,
|
||
line_height: 1.3,
|
||
max_chars_per_line: 11,
|
||
},
|
||
},
|
||
{
|
||
key: "business_dark",
|
||
label: "商务深色",
|
||
emoji: "💼",
|
||
style: {
|
||
...BASE,
|
||
font: "思源黑体",
|
||
size: 44,
|
||
color: "#ffffff",
|
||
bold: false,
|
||
italic: false,
|
||
stroke: false,
|
||
shadow: true,
|
||
shadow_offset_x: 1,
|
||
shadow_offset_y: 1,
|
||
shadow_blur: 3,
|
||
shadow_color: "rgba(0,0,0,0.8)",
|
||
bg_enabled: true,
|
||
bg_color: "rgba(24,144,255,0.85)",
|
||
bg_padding: 12,
|
||
bg_radius: 4,
|
||
line_height: 1.3,
|
||
max_chars_per_line: 12,
|
||
},
|
||
},
|
||
{
|
||
key: "minimal_clean",
|
||
label: "极简无描边",
|
||
emoji: "✨",
|
||
style: {
|
||
...BASE,
|
||
font: "苹方",
|
||
size: 48,
|
||
color: "#ffffff",
|
||
bold: true,
|
||
italic: false,
|
||
stroke: false,
|
||
shadow: false,
|
||
bg_enabled: false,
|
||
line_height: 1.3,
|
||
max_chars_per_line: 10,
|
||
},
|
||
},
|
||
]
|
||
|
||
/** 根据 key 获取预设 */
|
||
export function getTitlePreset(key: string): TitlePreset | undefined {
|
||
return TITLE_PRESETS.find((p) => p.key === key)
|
||
}
|