| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117 |
- import { useState } from "react";
- import { api } from "../api/client";
- import type { ThemeProp } from "../types";
- import styles from "./BuildPanel.module.css";
- interface Props {
- creativeId: string;
- creativeStatus: string;
- selectedPlatforms: string[];
- theme: Record<string, string>;
- themeProps: ThemeProp[];
- onBuildComplete: () => void;
- }
- export default function BuildPanel({
- creativeId,
- creativeStatus,
- selectedPlatforms,
- theme,
- themeProps,
- onBuildComplete,
- }: Props) {
- const [building, setBuilding] = useState(false);
- const [buildId, setBuildId] = useState<string | null>(null);
- const [buildError, setBuildError] = useState("");
- const canBuild = creativeStatus === "assets_ready" || creativeStatus === "built";
- async function handleBuild() {
- if (selectedPlatforms.length === 0) {
- setBuildError("请至少选择一个目标平台");
- return;
- }
- setBuilding(true);
- setBuildError("");
- setBuildId(null);
- try {
- // 合并 theme:用 themeProps 的 default 补全缺失值
- const mergedTheme: Record<string, string> = {};
- for (const prop of themeProps) {
- mergedTheme[prop.key] = theme[prop.key] ?? prop.default;
- }
- const res = await api.triggerBuild(creativeId, {
- platforms: selectedPlatforms,
- theme: mergedTheme,
- });
- const bid = res.data.id;
- setBuildId(bid);
- // 轮询构建状态
- await pollBuildStatus(bid);
- onBuildComplete();
- } catch (err: any) {
- setBuildError(err.message);
- } finally {
- setBuilding(false);
- }
- }
- async function pollBuildStatus(bid: string): Promise<void> {
- return new Promise((resolve, reject) => {
- const interval = setInterval(async () => {
- try {
- const res = await api.getBuildStatus(bid);
- if (res.data.status === "completed") {
- clearInterval(interval);
- resolve();
- } else if (res.data.status === "failed") {
- clearInterval(interval);
- reject(new Error(res.data.errorLog || "构建失败"));
- }
- } catch (err: any) {
- clearInterval(interval);
- reject(err);
- }
- }, 2000);
- // 超时 120 秒
- setTimeout(() => {
- clearInterval(interval);
- reject(new Error("构建超时,请刷新查看状态"));
- }, 120_000);
- });
- }
- return (
- <div className={styles.wrapper}>
- <button
- onClick={handleBuild}
- disabled={!canBuild || building}
- className={styles.buildBtn}
- >
- {building ? (
- <>
- <span className={styles.spinner} /> 构建中…
- </>
- ) : (
- "🚀 开始构建"
- )}
- </button>
- {!canBuild && (
- <p className={styles.hint}>请先上传素材后再构建</p>
- )}
- {buildError && <p className={styles.error}>构建失败:{buildError}</p>}
- {buildId && !building && !buildError && (
- <p className={styles.success}>构建完成!请在下方下载产物。</p>
- )}
- </div>
- );
- }
|