| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146 |
- import { useEffect, useState, useCallback } from "react";
- import { useParams, useNavigate } from "react-router-dom";
- import { api } from "../api/client";
- import type { Creative, TemplateDetail } from "../types";
- import AssetUploader from "../components/AssetUploader";
- import ThemeEditor from "../components/ThemeEditor";
- import PlatformSelector from "../components/PlatformSelector";
- import BuildPanel from "../components/BuildPanel";
- import BuildHistory from "../components/BuildHistory";
- import PreviewPanel from "../components/PreviewPanel";
- import styles from "./CreativeDetail.module.css";
- export default function CreativeDetail() {
- const { id } = useParams<{ id: string }>();
- const navigate = useNavigate();
- const [creative, setCreative] = useState<Creative | null>(null);
- const [template, setTemplate] = useState<TemplateDetail | null>(null);
- const [loading, setLoading] = useState(true);
- const [error, setError] = useState("");
- const [selectedPlatforms, setSelectedPlatforms] = useState<string[]>([
- "google",
- "applovin",
- ]);
- const fetchCreative = useCallback(() => {
- if (!id) return;
- setError("");
- api
- .getCreative(id)
- .then(async (res) => {
- setCreative(res.data);
- const tplRes = await api.getTemplate(res.data.templateId);
- setTemplate(tplRes.data);
- // 初始化平台选择
- if (selectedPlatforms.length === 0 && tplRes.data.platforms?.available) {
- setSelectedPlatforms(
- tplRes.data.platforms?.defaults ?? tplRes.data.platforms.available.slice(0, 2)
- );
- }
- })
- .catch((e) => setError(e.message))
- .finally(() => setLoading(false));
- }, [id]); // eslint-disable-line react-hooks/exhaustive-deps
- useEffect(() => {
- fetchCreative();
- }, [fetchCreative]);
- async function handleDelete() {
- if (!id || !confirm("确认删除此创意?所有素材和构建产物将被永久删除。")) return;
- try {
- await api.deleteCreative(id);
- navigate("/");
- } catch (err: any) {
- setError(err.message);
- }
- }
- const statusLabel: Record<string, string> = {
- draft: "草稿",
- assets_ready: "素材已就绪",
- building: "构建中",
- built: "已构建",
- failed: "失败",
- };
- if (loading) return <div className={styles.state}>加载中…</div>;
- if (error) return <div className={styles.state}>加载失败:{error}</div>;
- if (!creative || !template) return <div className={styles.state}>创意不存在</div>;
- return (
- <div className={styles.container}>
- {/* 顶部栏 */}
- <div className={styles.topBar}>
- <button onClick={() => navigate(-1)} className={styles.back}>
- ← 返回
- </button>
- <div className={styles.topInfo}>
- <h1 className={styles.title}>{creative.name}</h1>
- <span className={`${styles.statusDot} ${styles[creative.status]}`}>
- ● {statusLabel[creative.status]}
- </span>
- </div>
- <button onClick={handleDelete} className={styles.deleteBtn}>
- 删除
- </button>
- </div>
- {/* 工作区域 */}
- <div className={styles.workspace}>
- {/* 左栏:素材上传 */}
- <section className={styles.section}>
- <h2 className={styles.sectionTitle}>素材</h2>
- <AssetUploader
- creativeId={creative.id}
- assets={creative.assets ?? []}
- assetDefs={template.assets}
- onUpdated={fetchCreative}
- />
- </section>
- {/* 右栏:主题配置 */}
- <section className={styles.section}>
- <h2 className={styles.sectionTitle}>主题配置</h2>
- <ThemeEditor
- creativeId={creative.id}
- theme={creative.theme ?? {}}
- themeProps={template.theme.properties}
- onUpdated={fetchCreative}
- />
- </section>
- </div>
- {/* 预览 */}
- <PreviewPanel
- creativeId={creative.id}
- creativeStatus={creative.status}
- theme={creative.theme ?? {}}
- />
- {/* 底部:构建 */}
- <section className={styles.buildSection}>
- <h2 className={styles.sectionTitle}>构建</h2>
- <PlatformSelector
- platforms={template.platforms?.available ?? []}
- selected={selectedPlatforms}
- onChange={setSelectedPlatforms}
- />
- <BuildPanel
- creativeId={creative.id}
- creativeStatus={creative.status}
- selectedPlatforms={selectedPlatforms}
- theme={creative.theme ?? {}}
- themeProps={template.theme.properties}
- onBuildComplete={fetchCreative}
- />
- <BuildHistory
- creativeId={creative.id}
- builds={creative.recentBuilds ?? []}
- onUpdated={fetchCreative}
- />
- </section>
- </div>
- );
- }
|