feat(review): 全站统一「审核盾」徽章 + 灰盾点击手动提交兜底
火山人像审核状态此前三处文案各异(已审核/通过/审核✓…)。统一成一个共享 ReviewBadge: 全色填充盾(无对勾叉)+ 文案 + hover 气泡说明,4 态: 🟢已过审(active) / 🔴未过审(failed) / 🟠审核中(processing,橙盾光扫动画) / ⚪待审核(空) 灰盾(未送审)可点 → 提交审核(兜底,不加固定按钮):后端新增 POST /api/assets/{id}/submit-review/ (团队级,仅送审三类),前端点击乐观置 processing。气泡组件顺带成为全站首个统一 tooltip。 - 后端 submit-review 端点 + 单测(送审类 200 / 非送审类 400) - 共享 components/review-badge.{tsx,css}(盾用 SVG 蒙版填色 + 橙态 rv-sweep 光扫) - 资产库详情弹窗 / 商品详情素材组弹窗 / 视频流程基础资产 三处全换成它(各自接 onSubmit) 验收:assets 16 测绿;tsc+build 绿;无头核对四态(灰可点+气泡/绿已过审/橙审核中不可点)、0 console error Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -8,6 +8,7 @@ import { money, stageOrder, statusPill } from "./stage-config";
|
||||
import { CornerMarks, Decorations, Sidebar, ToastLike } from "../components/app-shell";
|
||||
import { MediaLightbox, useBodyScrollLock } from "../components/overlays";
|
||||
import { ActorLibrary } from "../components/actor-library";
|
||||
import { ReviewBadge, type ReviewStatus } from "../components/review-badge";
|
||||
import { IconKitSvg } from "../components/IconKitSvg";
|
||||
|
||||
// 真实资产缩略图注入:与全站一致用 --mock-media-url(.placeholder.has-mock-media 负责 cover 裁切 + 8px 圆角)
|
||||
@@ -859,6 +860,17 @@ export function PipelinePage(props: {
|
||||
// 火山人像审核状态(真人资产绿/红盾):本地覆盖 map(轮询刷新),回退 asset.review_status
|
||||
const [reviews, setReviews] = useState<Record<string, string>>({});
|
||||
const assetReview = (id?: string | null) => (id ? (reviews[id] || byId.get(id)?.review_status || "") : "");
|
||||
// 手动兜底:灰盾点击 → 提交审核,乐观置 processing(下次轮询回真态)
|
||||
const [reviewBusyId, setReviewBusyId] = useState<string | null>(null);
|
||||
async function submitAssetReview(id: string) {
|
||||
setReviewBusyId(id);
|
||||
try {
|
||||
const r = await api.submitAssetReview(id);
|
||||
setReviews((m) => ({ ...m, [id]: r.review_status || "processing" }));
|
||||
} catch { /* 失败保持灰盾可再点 */ } finally {
|
||||
setReviewBusyId(null);
|
||||
}
|
||||
}
|
||||
// 在基础资产趴(stage 2)定时轮询审核状态,刷新徽章(processing → active绿 / failed红)
|
||||
useEffect(() => {
|
||||
if (viewStage !== 2) return;
|
||||
@@ -2805,9 +2817,14 @@ export function PipelinePage(props: {
|
||||
<div className="hstack">
|
||||
<strong style={{ fontSize: "13.5px", cursor: "pointer" }} onClick={() => openAssetDetail(kind, entity)}>{entity.name}</strong>
|
||||
<span className="spacer"></span>
|
||||
{rs === "active" && <span className="pill ok" title="火山真人审核已通过"><span className="dot"></span>审核✓</span>}
|
||||
{rs === "failed" && <span className="pill err" title={`审核未过:${grp.adopted_asset_review_error || byId.get(grp.adopted_asset!)?.review_error || "建议改提示词重新生成"}`}><span className="dot"></span>审核✗</span>}
|
||||
{rs === "processing" && <span className="pill neutral"><span className="dot"></span>审核中</span>}
|
||||
{kind === "person" && grp.adopted_asset && (
|
||||
<ReviewBadge
|
||||
status={rs as ReviewStatus}
|
||||
error={grp.adopted_asset_review_error || byId.get(grp.adopted_asset)?.review_error}
|
||||
busy={reviewBusyId === grp.adopted_asset}
|
||||
onSubmit={() => submitAssetReview(grp.adopted_asset!)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<PromptBox key={entity.key} value={promptValue} onChange={(v) => setAssetPromptDraft((m) => ({ ...m, [entity.key]: v }))} />
|
||||
<div className="hstack" style={{ marginTop: 10 }}>
|
||||
|
||||
Reference in New Issue
Block a user