8c715474f4
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 2s
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 1s
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 1s
CI/CD Pipeline / Check push changed paths (push) Successful in 4s
CI/CD Pipeline / Validate - Style (pull_request) Has been skipped
CI/CD Pipeline / Validate - Security (pull_request) Has been skipped
CI/CD Pipeline / Validate - Python (mypy + alembic) (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 / Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 16s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 19s
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 19s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 2m49s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 3m53s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Build Staging API Image (push) Successful in 33s
AI Code Review / AI Code Review (pull_request) Successful in 6m43s
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 / 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 2s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (push) Successful in 41s
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 / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 36s
CI/CD Pipeline / Retag skipped Staging API Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 3m55s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 8m8s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m38s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m48s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 4m45s
CI/CD Pipeline / Validate - Style (push) Successful in 27m21s
CI/CD Pipeline / Integration Tests (push) Successful in 26m59s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 31m35s
CI/CD Pipeline / Unit Tests (push) Failing after 35m54s
CI/CD Pipeline / Validate - Security (push) Has been cancelled
CI/CD Pipeline / Build Production API Image (push) Has been cancelled
CI/CD Pipeline / Build Production Web Image (push) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Production (push) Has been cancelled
CI/CD Pipeline / Production Browser E2E (push) Has been cancelled
CI/CD Pipeline / Canary Release to Production (push) Has been cancelled
CI/CD Pipeline / CI Gate (push) Has been cancelled
240 lines
10 KiB
TypeScript
Executable File
240 lines
10 KiB
TypeScript
Executable File
import { useEffect } from "react"
|
|
import type { CoverConfig } from "../../types/cover"
|
|
import type { TitleSettings } from "../../types"
|
|
import type { TitleLineOverride } from "@/components/title/types"
|
|
import type { TitleConfig } from "@/api/template-editor"
|
|
import { getEditPlan } from "@/api/template-editor"
|
|
|
|
interface UsePlanConfigLoaderOptions {
|
|
editPlanId: string | null
|
|
planConfigStr: string | null
|
|
setTitleSettings: (settings: TitleSettings | ((prev: TitleSettings) => TitleSettings)) => void
|
|
setCoverSettings: (settings: CoverConfig | ((prev: CoverConfig) => CoverConfig)) => void
|
|
setSelectedMaterials: (ids: string[]) => void
|
|
}
|
|
|
|
/** #2001:统一归一化 title_config snake_case -> camelCase TitleSettings */
|
|
function mapTitleCfgToSettings(
|
|
prev: TitleSettings,
|
|
tc: TitleConfig & Record<string, unknown>,
|
|
): TitleSettings {
|
|
const stroke = tc.stroke as
|
|
boolean | { enabled?: boolean; width?: number; color?: string } | undefined
|
|
const strokeEnabled: boolean | undefined =
|
|
typeof stroke === "object" && stroke ? stroke.enabled !== false : !!stroke || undefined
|
|
const strokeW: number | undefined =
|
|
typeof stroke === "object" && stroke
|
|
? (stroke.width ?? (tc.stroke_width as number | undefined))
|
|
: (tc.stroke_width as number | undefined)
|
|
const strokeC: string | undefined =
|
|
typeof stroke === "object" && stroke
|
|
? (stroke.color ?? (tc.stroke_color as string | undefined))
|
|
: (tc.stroke_color as string | undefined)
|
|
|
|
const shadow = tc.shadow as
|
|
| boolean
|
|
| { enabled?: boolean; offset_x?: number; offset_y?: number; blur?: number; color?: string }
|
|
| undefined
|
|
const shadowEnabled: boolean | undefined =
|
|
typeof shadow === "object" && shadow ? shadow.enabled !== false : !!shadow || undefined
|
|
const shOffX: number | undefined =
|
|
typeof shadow === "object" && shadow
|
|
? (shadow.offset_x ?? (tc.shadow_offset_x as number | undefined))
|
|
: (tc.shadow_offset_x as number | undefined)
|
|
const shOffY: number | undefined =
|
|
typeof shadow === "object" && shadow
|
|
? (shadow.offset_y ?? (tc.shadow_offset_y as number | undefined))
|
|
: (tc.shadow_offset_y as number | undefined)
|
|
const shBlur: number | undefined =
|
|
typeof shadow === "object" && shadow
|
|
? (shadow.blur ?? (tc.shadow_blur as number | undefined))
|
|
: (tc.shadow_blur as number | undefined)
|
|
const shColor: string | undefined =
|
|
typeof shadow === "object" && shadow
|
|
? (shadow.color ?? (tc.shadow_color as string | undefined))
|
|
: (tc.shadow_color as string | undefined)
|
|
|
|
const bg = tc.background as
|
|
{ enabled?: boolean; color?: string; padding?: number; radius?: number } | undefined
|
|
const bgEnabled: boolean | undefined =
|
|
(bg && typeof bg === "object" ? bg.enabled : undefined) ??
|
|
(tc.bg_enabled as boolean | undefined)
|
|
const bgColor: string | undefined =
|
|
(bg && typeof bg === "object" ? bg.color : undefined) ?? (tc.bg_color as string | undefined)
|
|
const bgPadding: number | undefined =
|
|
(bg && typeof bg === "object" ? bg.padding : undefined) ?? (tc.bg_padding as number | undefined)
|
|
const bgRadius: number | undefined =
|
|
(bg && typeof bg === "object" ? bg.radius : undefined) ?? (tc.bg_radius as number | undefined)
|
|
|
|
const ct = (tc.cover_title_config ?? null) as null | Record<string, unknown>
|
|
let coverTitle: TitleSettings["coverTitle"] = prev.coverTitle
|
|
if (ct) {
|
|
const ctStroke = ct.stroke as
|
|
boolean | { enabled?: boolean; width?: number; color?: string } | undefined
|
|
const ctShadow = ct.shadow as
|
|
| boolean
|
|
| { enabled?: boolean; offset_x?: number; offset_y?: number; blur?: number; color?: string }
|
|
| undefined
|
|
const ctBg = ct.background as
|
|
{ enabled?: boolean; color?: string; padding?: number; radius?: number } | undefined
|
|
coverTitle = {
|
|
title: (ct.title as string | undefined) ?? prev.coverTitle?.title ?? "",
|
|
font: (ct.font as string | undefined) ?? prev.coverTitle?.font,
|
|
size:
|
|
(ct.font_size as number | undefined) ??
|
|
(ct.size as number | undefined) ??
|
|
prev.coverTitle?.size,
|
|
color:
|
|
(ct.font_color as string | undefined) ??
|
|
(ct.color as string | undefined) ??
|
|
prev.coverTitle?.color,
|
|
bold: (ct.bold as boolean | undefined) ?? prev.coverTitle?.bold,
|
|
italic: (ct.italic as boolean | undefined) ?? prev.coverTitle?.italic,
|
|
position: (ct.position as string | undefined) ?? prev.coverTitle?.position,
|
|
stroke:
|
|
typeof ctStroke === "object" && ctStroke
|
|
? ctStroke.enabled !== false
|
|
: ((ctStroke as boolean | undefined) ?? prev.coverTitle?.stroke),
|
|
strokeWidth:
|
|
(typeof ctStroke === "object" && ctStroke ? ctStroke.width : undefined) ??
|
|
(ct.stroke_width as number | undefined) ??
|
|
prev.coverTitle?.strokeWidth,
|
|
strokeColor:
|
|
(typeof ctStroke === "object" && ctStroke ? ctStroke.color : undefined) ??
|
|
(ct.stroke_color as string | undefined) ??
|
|
prev.coverTitle?.strokeColor,
|
|
shadow:
|
|
typeof ctShadow === "object" && ctShadow
|
|
? ctShadow.enabled !== false
|
|
: ((ctShadow as boolean | undefined) ?? prev.coverTitle?.shadow),
|
|
shadowOffsetX:
|
|
(typeof ctShadow === "object" && ctShadow ? ctShadow.offset_x : undefined) ??
|
|
(ct.shadow_offset_x as number | undefined) ??
|
|
prev.coverTitle?.shadowOffsetX,
|
|
shadowOffsetY:
|
|
(typeof ctShadow === "object" && ctShadow ? ctShadow.offset_y : undefined) ??
|
|
(ct.shadow_offset_y as number | undefined) ??
|
|
prev.coverTitle?.shadowOffsetY,
|
|
shadowBlur:
|
|
(typeof ctShadow === "object" && ctShadow ? ctShadow.blur : undefined) ??
|
|
(ct.shadow_blur as number | undefined) ??
|
|
prev.coverTitle?.shadowBlur,
|
|
shadowColor:
|
|
(typeof ctShadow === "object" && ctShadow ? ctShadow.color : undefined) ??
|
|
(ct.shadow_color as string | undefined) ??
|
|
prev.coverTitle?.shadowColor,
|
|
bgEnabled:
|
|
ctBg?.enabled ?? (ct.bg_enabled as boolean | undefined) ?? prev.coverTitle?.bgEnabled,
|
|
bgColor: ctBg?.color ?? (ct.bg_color as string | undefined) ?? prev.coverTitle?.bgColor,
|
|
bgPadding:
|
|
ctBg?.padding ?? (ct.bg_padding as number | undefined) ?? prev.coverTitle?.bgPadding,
|
|
bgRadius: ctBg?.radius ?? (ct.bg_radius as number | undefined) ?? prev.coverTitle?.bgRadius,
|
|
}
|
|
}
|
|
|
|
const result: TitleSettings = {
|
|
...prev,
|
|
title: (tc.content as string | undefined) || prev.title,
|
|
aiAutoSelect: (tc.ai_auto_select as boolean | undefined) || false,
|
|
position: prev.position,
|
|
font: (tc.font_preset as string | undefined) || prev.font,
|
|
size: (tc.font_size as number | undefined) || prev.size,
|
|
color: (tc.font_color as string | undefined) || prev.color,
|
|
bold: (tc.bold as boolean | undefined) ?? prev.bold,
|
|
italic: (tc.italic as boolean | undefined) ?? prev.italic,
|
|
stroke: strokeEnabled ?? prev.stroke,
|
|
strokeWidth: strokeW ?? prev.strokeWidth,
|
|
strokeColor: strokeC ?? prev.strokeColor,
|
|
shadow: shadowEnabled ?? prev.shadow,
|
|
shadowOffsetX: shOffX ?? prev.shadowOffsetX,
|
|
shadowOffsetY: shOffY ?? prev.shadowOffsetY,
|
|
shadowBlur: shBlur ?? prev.shadowBlur,
|
|
shadowColor: shColor ?? prev.shadowColor,
|
|
lineHeight: (tc.line_height as number | undefined) ?? prev.lineHeight,
|
|
marginTop: (tc.margin_top as number | undefined) ?? prev.marginTop,
|
|
maxCharsPerLine: (tc.max_chars_per_line as number | undefined) ?? prev.maxCharsPerLine,
|
|
bgEnabled: bgEnabled ?? prev.bgEnabled,
|
|
bgColor: bgColor ?? prev.bgColor,
|
|
bgPadding: bgPadding ?? prev.bgPadding,
|
|
bgRadius: bgRadius ?? prev.bgRadius,
|
|
lineOverrides: ((tc.line_overrides as unknown[] | undefined) ?? []) as TitleLineOverride[],
|
|
coverTitle,
|
|
}
|
|
return result
|
|
}
|
|
|
|
/**
|
|
* 从 URL 参数或编辑计划 ID 加载表单配置
|
|
*/
|
|
export function usePlanConfigLoader({
|
|
editPlanId,
|
|
planConfigStr,
|
|
setTitleSettings,
|
|
setCoverSettings,
|
|
setSelectedMaterials,
|
|
}: UsePlanConfigLoaderOptions) {
|
|
/** 解析 plan_config 并自动填充表单 */
|
|
useEffect(() => {
|
|
if (!planConfigStr) return
|
|
try {
|
|
const config = JSON.parse(planConfigStr) as {
|
|
title_config?: Record<string, unknown>
|
|
subtitle_config?: { enabled?: boolean }
|
|
bgm_config?: { enabled?: boolean; music_id?: string }
|
|
mode?: string
|
|
total_duration?: number
|
|
segments?: Array<{ media_asset_id?: string; material_type?: string }>
|
|
}
|
|
|
|
if (config.title_config) {
|
|
const tc = config.title_config as TitleConfig & Record<string, unknown>
|
|
setTitleSettings((prev: TitleSettings) => mapTitleCfgToSettings(prev, tc))
|
|
}
|
|
if (config.segments && config.segments.length > 0) {
|
|
const assetIds = config.segments
|
|
.map((s) => s.media_asset_id)
|
|
.filter((id): id is string => !!id)
|
|
if (assetIds.length > 0) {
|
|
setSelectedMaterials(assetIds)
|
|
}
|
|
}
|
|
} catch (err) {
|
|
console.warn("解析 plan_config 失败:", err)
|
|
}
|
|
}, [planConfigStr, setTitleSettings, setSelectedMaterials])
|
|
|
|
/** 如果没有 plan_config,尝试通过 edit_plan_id 从后端拉取配置 */
|
|
useEffect(() => {
|
|
if (!editPlanId || planConfigStr) return
|
|
const loadPlanConfig = async () => {
|
|
try {
|
|
const plan = await getEditPlan(editPlanId)
|
|
if (plan.name) setTitleSettings((prev: TitleSettings) => ({ ...prev, title: plan.name }))
|
|
const cfg = plan.config
|
|
if (cfg?.title_config) {
|
|
const tc2 = cfg.title_config as unknown as TitleConfig & Record<string, unknown>
|
|
setTitleSettings((prev: TitleSettings) => mapTitleCfgToSettings(prev, tc2))
|
|
}
|
|
if (cfg?.cover_config) {
|
|
const cc = cfg.cover_config as CoverConfig
|
|
setCoverSettings((prev: CoverConfig) => ({
|
|
...prev,
|
|
enabled: cc.enabled ?? prev.enabled,
|
|
mode: cc.mode || prev.mode,
|
|
frame_time: cc.frame_time ?? prev.frame_time,
|
|
upload_url: cc.upload_url || prev.upload_url,
|
|
ai_suggested_time: cc.ai_suggested_time ?? prev.ai_suggested_time,
|
|
thumbnail_url: cc.thumbnail_url || prev.thumbnail_url,
|
|
}))
|
|
}
|
|
if (cfg?.asset_ids) {
|
|
setSelectedMaterials(cfg.asset_ids.filter((v): v is string => typeof v === "string"))
|
|
}
|
|
} catch (err) {
|
|
console.warn("加载模板草稿配置失败:", err)
|
|
}
|
|
}
|
|
loadPlanConfig()
|
|
}, [editPlanId, planConfigStr, setTitleSettings, setCoverSettings, setSelectedMaterials])
|
|
}
|