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:
@@ -1,9 +1,10 @@
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import type { FormEvent, ReactNode } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
import { Download, Image as ImageIcon, Images, Info, LayoutGrid, Music, Shield, Trash2, Upload, X } from "lucide-react";
|
||||
import { Download, Image as ImageIcon, Images, Info, LayoutGrid, Music, Trash2, Upload, X } from "lucide-react";
|
||||
import { api } from "../api";
|
||||
import { SkeletonGrid } from "../components/loading";
|
||||
import { ReviewBadge, type ReviewStatus } from "../components/review-badge";
|
||||
import type { Asset } from "../types";
|
||||
import { ConfirmModal, MediaLightbox, useBodyScrollLock, useOverlayTransition } from "../components/overlays";
|
||||
import { Pager } from "../components/pager";
|
||||
@@ -130,8 +131,22 @@ function AssetDetailModal({ asset, close, onZoom }: {
|
||||
{ k: "入库时间", v: createdAt }
|
||||
];
|
||||
|
||||
// 人物审核盾:仅真人有 review_status(processing/active 绿/failed 红)
|
||||
const review = asset.review_status;
|
||||
// 审核盾:仅送审类(角色/三视图/分镜)显示;灰盾可点提交。本地覆盖提交后即时反映,切资产清空
|
||||
const REVIEW_CATS = ["person", "tri_view", "storyboard"];
|
||||
const [reviewOverride, setReviewOverride] = useState<string | null>(null);
|
||||
const [submittingReview, setSubmittingReview] = useState(false);
|
||||
useEffect(() => { setReviewOverride(null); }, [asset?.id]);
|
||||
const review = (reviewOverride ?? asset.review_status ?? "") as ReviewStatus;
|
||||
async function submitReview() {
|
||||
if (!asset) return;
|
||||
setSubmittingReview(true);
|
||||
try {
|
||||
const r = await api.submitAssetReview(asset.id);
|
||||
setReviewOverride(r.review_status || "processing");
|
||||
} catch { /* 提交失败保持灰盾,用户可再点 */ } finally {
|
||||
setSubmittingReview(false);
|
||||
}
|
||||
}
|
||||
|
||||
function renderLead(): ReactNode {
|
||||
if (leadSrc && isVideo) {
|
||||
@@ -151,9 +166,9 @@ function AssetDetailModal({ asset, close, onZoom }: {
|
||||
<div className="adx-h">
|
||||
<h2>{asset.name || "资产详情"}</h2>
|
||||
<span className="adx-kind">/ {kindLabel}</span>
|
||||
{review === "active" && <span className="pill ok adx-shield"><Shield size={11} /> 已审核</span>}
|
||||
{review === "processing" && <span className="pill info adx-shield">审核中</span>}
|
||||
{review === "failed" && <span className="pill err adx-shield">审核未过</span>}
|
||||
{REVIEW_CATS.includes(asset.category) && (
|
||||
<ReviewBadge status={review} error={asset.review_error} busy={submittingReview} onSubmit={submitReview} />
|
||||
)}
|
||||
<button className="x adx-x" type="button" onClick={close} aria-label="关闭"><X size={14} /></button>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user