fix(product-detail): 素材卡「归档」按钮 → 只读审核状态

- 状态不再是用户可点循环(通过/不通过/归档),改为只读展示团队审核结果
- 状态值 archive→pending:active(绿盾)=通过 / failed(红标)=不通过 / 其它=待审核
- 移除 statusOverrides 本地覆盖 + cycleAssetStatus + 采用三视图的状态联动
- 筛选项「归档」→「待审核」;CSS .pill.archive→.pill.pending 去 cursor:pointer

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
seaislee1209
2026-06-20 19:21:31 +08:00
co-authored by Claude Opus 4.8
parent 3469799da5
commit 998b0bfb93
2 changed files with 14 additions and 49 deletions
+2 -5
View File
@@ -502,24 +502,21 @@
.qa-item.primary .ic { background: var(--accent-white); color: var(--heat); border-color: var(--accent-white); }
.qa-item.primary:hover { color: var(--accent-white); box-shadow: var(--shadow-cta-hover); }
/* 状态 pill 三态(通过/不通过/归档) */
/* 审核状态 pill 三态(通过/不通过/待审核)· 只读,不再可点(去 cursor:pointer) */
.asset-card .meta .pill.pass {
background: var(--accent-emerald-bg, #e6f4ec);
color: var(--accent-emerald, #1f8a51);
border: 1px solid var(--accent-emerald-bd, #c4e3d1);
cursor: pointer;
}
.asset-card .meta .pill.fail {
background: var(--crimson-bg, #fdebea);
color: var(--accent-crimson, #c43d3d);
border: 1px solid var(--crimson-bd, #f5c2bf);
cursor: pointer;
}
.asset-card .meta .pill.archive {
.asset-card .meta .pill.pending {
background: var(--background-lighter);
color: var(--black-alpha-56);
border: 1px solid var(--border-faint);
cursor: pointer;
}
/* ─── Tabs ─── */
+12 -44
View File
@@ -521,22 +521,21 @@ export function ProductCreateUploadPage({ onCreate, onBack }: { onCreate: (paylo
// 商品详情页 · 从 public/exact/product-detail.html 忠实转写。
// 名称 / 品类 / 目标人群 / 卖点 来自 product;商品图网格 / AI 素材卡 / 视频项目卡
// 已接入真实数据(product.images + 团队 assets + 该商品 projects),保持设计稿像素布局。
type PdAssetStatus = "pass" | "fail" | "archive";
const PD_ASSET_STATUS_LABEL: Record<PdAssetStatus, string> = { pass: "通过", fail: "不通过", archive: "归档" };
// 点击循环顺序:通过 → 不通过 → 归档 → 通过(忠实移植 product-detail.html)
const PD_STATUS_CYCLE: PdAssetStatus[] = ["pass", "fail", "archive"];
// 素材审核状态(只读,反映团队审核结果,非用户可点切换)
type PdAssetStatus = "pass" | "fail" | "pending";
const PD_ASSET_STATUS_LABEL: Record<PdAssetStatus, string> = { pass: "通过", fail: "不通过", pending: "待审核" };
const PD_STATUS_FILTER_OPTS: Array<{ value: "" | PdAssetStatus; label: string }> = [
{ value: "", label: "全部状态" },
{ value: "pass", label: "通过" },
{ value: "fail", label: "不通过" },
{ value: "archive", label: "归档" }
{ value: "pending", label: "待审核" }
];
// 真实火山审核状态 → 三态基线:active(绿盾)→通过 · failed(红标)→不通过 · 其它("" / processing 等)→归档(中性,未定论)
// 真实审核状态 → 三态:active(绿盾)→通过 · failed(红标)→不通过 · 其它("" / processing 等)→待审核
function pdBaseStatus(asset: Asset): PdAssetStatus {
const r = asset.review_status || "";
if (r === "active") return "pass";
if (r === "failed") return "fail";
return "archive";
return "pending";
}
const PD_CAT_OPTIONS = ["美妆个护 / 精华液", "美妆个护", "服饰内衣", "食品饮料", "家居家电", "数码 3C", "个护清洁", "运动户外", "母婴亲子"];
@@ -596,13 +595,12 @@ export function ProductDetailPage({ product, projects, initialTab = "assets", na
// 素材 tab 筛选 / 排序 / 分页 / 视图
const [openFilter, setOpenFilter] = useState<"" | "type" | "status" | "sort">("");
const [typeFilter, setTypeFilter] = useState("");
const [statusFilter, setStatusFilter] = useState<"" | "pass" | "fail" | "archive">("");
const [statusFilter, setStatusFilter] = useState<"" | "pass" | "fail" | "pending">("");
const [assetView, setAssetView] = useState<"grid" | "list">("grid");
const [assetSortDesc, setAssetSortDesc] = useState(true);
const [assetLimit, setAssetLimit] = useState(12);
const [videoSortDesc, setVideoSortDesc] = useState(true);
// 素材审核状态本地覆盖(点击循环 / 采用联动写入);未覆盖则回退真实 review_status 映射
const [statusOverrides, setStatusOverrides] = useState<Record<string, "pass" | "fail" | "archive">>({});
// 素材审核状态为只读(直接反映 review_status),不再有用户点击覆盖
// 采用三视图后,本地 prepend 的新素材卡(供「采用版本联动 + prepend 新卡」即时呈现,不等服务端回灌)
const [adoptedAssets, setAdoptedAssets] = useState<Asset[]>([]);
// 图片预览灯箱
@@ -680,17 +678,7 @@ export function ProductDetailPage({ product, projects, initialTab = "assets", na
setTriAdoptedId(triViewing.id);
const adopted = triViewing.asset;
if (adopted) {
// 联动:被采用三视图 → 通过(绿);该商品其余三视图 → 不通过(红)。其他类型素材不受影响。
setStatusOverrides((prev) => {
const next = { ...prev };
const triIds = new Set<string>();
for (const a of allAssetsBase) {
if (a.category === "tri_view" || a.asset_type === "tri_view") triIds.add(a.id);
}
triIds.add(adopted.id);
triIds.forEach((id) => { next[id] = id === adopted.id ? "pass" : "fail"; });
return next;
});
// 审核状态已改为只读(反映 review_status),不再在采用三视图时本地覆盖状态
// prepend 新卡:若服务端素材列表里还没有这条采用资产,先本地置顶呈现(去重)
setAdoptedAssets((prev) => {
if (prev.some((a) => a.id === adopted.id) || allImageAssets.some((a) => a.id === adopted.id)) return prev;
@@ -720,7 +708,7 @@ export function ProductDetailPage({ product, projects, initialTab = "assets", na
// 联动采用状态前的「全部素材基线」(供 adoptTriView 找出该商品所有三视图)
const allAssetsBase = allImageAssets;
// 单个素材当前显示状态:本地覆盖(点击循环 / 采用联动)优先,否则回退真实 review_status 映射
const statusOf = (asset: Asset): PdAssetStatus => statusOverrides[asset.id] || pdBaseStatus(asset);
const statusOf = (asset: Asset): PdAssetStatus => pdBaseStatus(asset);
// 类型筛选选项(当前素材里真实存在的 category)
const typeOptions = Array.from(new Set(allImageAssets.map((a) => a.category).filter(Boolean)));
// 全集计数(给状态筛选下拉做计数前缀,不随当前筛选变化)
@@ -736,16 +724,6 @@ export function ProductDetailPage({ product, projects, initialTab = "assets", na
const imageAssets = filteredAssets.slice(0, assetLimit);
const hasActiveAssetFilter = Boolean(typeFilter || statusFilter);
const resetAssetFilters = () => { setTypeFilter(""); setStatusFilter(""); setAssetLimit(12); setOpenFilter(""); };
// 点击素材状态 pill 循环:通过 → 不通过 → 归档。三视图状态由「采用版本」决定,不在此循环(忠实移植 V1)。
const cycleAssetStatus = (asset: Asset) => {
if (asset.category === "tri_view" || asset.asset_type === "tri_view") return;
setStatusOverrides((prev) => {
const cur = prev[asset.id] || pdBaseStatus(asset);
const nextStatus = PD_STATUS_CYCLE[(PD_STATUS_CYCLE.indexOf(cur) + 1) % PD_STATUS_CYCLE.length];
return { ...prev, [asset.id]: nextStatus };
});
};
// 视频项目 · 用传入的该商品 projects 渲染真实项目名 / 状态 / 阶段(按更新时间排序)
const videoProjects = [...projects].sort((a, b) => {
const cmp = (b.updated_at || "").localeCompare(a.updated_at || "");
@@ -1100,7 +1078,6 @@ export function ProductDetailPage({ product, projects, initialTab = "assets", na
{imageAssets.map((asset) => {
const url = pdAssetPreview(asset);
const status = statusOf(asset);
const isTri = asset.category === "tri_view" || asset.asset_type === "tri_view";
return (
<div className="asset-card" key={asset.id}>
<div className="thumb placeholder" role={url ? "button" : undefined} tabIndex={url ? 0 : undefined} title={url ? "点击放大" : undefined} style={url ? { cursor: "zoom-in" } : undefined} onClick={url ? () => setPreview({ src: url, name: asset.name }) : undefined} onKeyDown={url ? (event) => { if (event.key === "Enter" || event.key === " ") { event.preventDefault(); setPreview({ src: url, name: asset.name }); } } : undefined}>
@@ -1109,17 +1086,8 @@ export function ProductDetailPage({ product, projects, initialTab = "assets", na
{url ? null : <span className="ph-frame">3:4</span>}
</div>
<div className="meta">
{/* 状态 pill 三态可点循环;三视图状态由「采用版本」决定,此处不可点 */}
<span
className={`pill ${status}`}
data-status={status}
role={isTri ? undefined : "button"}
tabIndex={isTri ? undefined : 0}
title={isTri ? "三视图状态由采用版本决定" : "点击切换状态"}
style={isTri ? { cursor: "default" } : undefined}
onClick={() => cycleAssetStatus(asset)}
onKeyDown={(event) => { if (!isTri && (event.key === "Enter" || event.key === " ")) { event.preventDefault(); cycleAssetStatus(asset); } }}
>{PD_ASSET_STATUS_LABEL[status]}</span>
{/* 审核状态(只读):团队审核给出 通过 / 不通过,未定论显示 待审核 —— 不再是可点切换的按钮 */}
<span className={`pill ${status}`} data-status={status} title="素材审核状态 · 由团队审核决定">{PD_ASSET_STATUS_LABEL[status]}</span>
<span className="date">{(asset.created_at || "").slice(0, 10)}</span>
</div>
</div>