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