3beb65143b
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
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 / Check if frontend-only change (pull_request) Successful in 2s
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 2s
CI/CD Pipeline / Retag skipped Staging API 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 Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 1m26s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 1m36s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 1m40s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Successful in 1m42s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 2m4s
CI/CD Pipeline / Validate - Style (pull_request) Successful in 2m37s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 3m9s
CI/CD Pipeline / Validate - Security (pull_request) Successful in 3m41s
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Successful in 1s
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 2m52s
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 28s
AI Code Review / AI Code Review (pull_request) Successful in 6m45s
ACR Cleanup / ACR Image Cleanup (pull_request_target) Successful in 6m3s
根因(PR#2054字体重构引入): 1. Canvas fillStyle泄漏:bgEnabled预设画完背景矩形后未重置fillStyle为文字颜色, fillText用bgColor画字导致黑底/黄底/蓝底预设只显示色块。 2. 字体栈顺序:优设标题黑/抖音美好体把ZCOOL庆科黄油体/快乐体(Google Fonts仅400字重) 排在Noto Sans SC(已加载400/500/700/900)之前,Canvas请求bold时无法合成粗体导致 中文渲染异常(缺字/方框/乱码)。 修复: - TitleMiniPreview:画完bg矩形后显式 ctx.fillStyle = settings.color; 升级Web Font等待为async模式:await fonts.ready + fonts.load(),确保Canvas 绘制时字体已加载到位。 - constants.ts:调整优设标题黑/抖音美好体family顺序,将Noto Sans SC(bold可用) 前置到ZCOOL之前;ZCOOL保留作风格兜底。 验证:tsc/eslint/vitest 620 passed,Chrome headless截图10个预设全部正确渲染'标题'。
393 lines
9.3 KiB
TypeScript
393 lines
9.3 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: "优设标题黑",
|
||
// 原版"优设标题黑"为商用字体非开源;优先本地已安装字体,兜底用 Noto Sans SC(Google Fonts 已加载 wght@900,保证 bold 字重可用),再用 ZCOOL 庆科黄油体作风格兜底
|
||
family:
|
||
'"YouSheBiaoTiHei","YouShe Title Black","Noto Sans SC","ZCOOL QingKe HuangYou","PingFang SC","Microsoft YaHei",sans-serif',
|
||
tag: "hot",
|
||
},
|
||
{
|
||
value: "阿里普惠体Bold",
|
||
label: "阿里普惠体Bold",
|
||
// 阿里普惠体需从阿里官网下载;兜底用 Noto Sans SC 900(同等字重,已在 Google Fonts wght@400;500;700;900 加载)
|
||
family:
|
||
'"Alibaba PuHuiTi","Alibaba PuHuiTi Bold","Alibaba Sans","Noto Sans SC",system-ui,"PingFang SC","Microsoft YaHei",sans-serif',
|
||
tag: "hot",
|
||
},
|
||
{
|
||
value: "抖音美好体",
|
||
label: "抖音美好体",
|
||
// 抖音美好体为版权字体;兜底用 Noto Sans SC(确保 bold 字重可用),再用 ZCOOL KuaiLe(站酷快乐体,圆润卡通风格近似)
|
||
family:
|
||
'"Douyin Sans","DouyinSans","Noto Sans SC","ZCOOL KuaiLe","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)
|
||
}
|