fix: YYX测试清单16条收口——资产库弹窗层级/批次多选删/商品筛选/模特卡单卡化/垃圾桶清空等
- 资产库:批次弹窗降层(under-confirm 990<999)修单图删除确认弹窗被压;编辑态批次卡多选批删;facets 新增 products 关联商品筛选(前后端,+2 测试);视频成品 tab 补搜索/排序/分页 - 平台套图工作台:「更多」气泡与 icon 左对齐+鼠标移出自动收起;模特卡改单卡(空态「+」占位,点卡进模特库单选回填) - 工作台:删余额条,冻结/共计移至小标题行右侧(.dash-funds-mono) - 模特库:点模特卡弹详情(形象图/标签/三视图+灯箱放大) - 垃圾桶:新增「全部恢复」/「清空垃圾桶」(清空二次确认) - 视频项目页:字重/阴影等轻量对齐设计规范 - api.assetFacets 返回体逐键兜底:旧后端缺 products 键会致资产库整页 undefined.find 白屏(前后端部署时序防护,浏览器已复现验证) 验证:tsc --noEmit / vite build / apps.assets 测试全过;暂存树已独立编译验证,不依赖工作区其余在途改动 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import type { CSSProperties } from "react";
|
||||
import { api } from "../api";
|
||||
import { ConfirmModal } from "../components/overlays";
|
||||
import type { NavigateFn } from "./route-config";
|
||||
import type { Asset, Product } from "../types";
|
||||
import "../trash-page.css";
|
||||
@@ -32,6 +33,9 @@ export function TrashPage({ onRestore, onPurge }: {
|
||||
const [confirmId, setConfirmId] = useState<string | null>(null);
|
||||
const [busyId, setBusyId] = useState<string | null>(null);
|
||||
const [errText, setErrText] = useState("");
|
||||
const [bulkBusy, setBulkBusy] = useState<"restore" | "purge" | null>(null);
|
||||
const [purgeAllOpen, setPurgeAllOpen] = useState(false);
|
||||
const isEmpty = items.length === 0 && assets.length === 0;
|
||||
|
||||
useEffect(() => {
|
||||
let alive = true;
|
||||
@@ -70,6 +74,41 @@ export function TrashPage({ onRestore, onPurge }: {
|
||||
finally { setBusyId(null); }
|
||||
}
|
||||
|
||||
// 全部恢复:商品走 onRestore、资产走 api.restoreAsset,各自独立通道并发跑;失败的留在列表、错误如实亮出
|
||||
// 按 id 而非下标比对结果:防止执行期间单条操作改动了 items/assets 导致下标错位
|
||||
async function restoreAll() {
|
||||
setBulkBusy("restore");
|
||||
setErrText("");
|
||||
const itemIds = items.map((p) => p.id);
|
||||
const assetIds = assets.map((a) => a.id);
|
||||
const itemResults = await Promise.allSettled(itemIds.map((id) => Promise.resolve(onRestore(id))));
|
||||
const assetResults = await Promise.allSettled(assetIds.map((id) => api.restoreAsset(id)));
|
||||
const failedItemIds = new Set(itemIds.filter((_, i) => itemResults[i].status === "rejected"));
|
||||
const failedAssetIds = new Set(assetIds.filter((_, i) => assetResults[i].status === "rejected"));
|
||||
setItems((list) => list.filter((p) => failedItemIds.has(p.id)));
|
||||
setAssets((list) => list.filter((a) => failedAssetIds.has(a.id)));
|
||||
const firstFail = [...itemResults, ...assetResults].find((r): r is PromiseRejectedResult => r.status === "rejected");
|
||||
if (firstFail) setErrText(firstFail.reason instanceof Error ? firstFail.reason.message : "部分恢复失败");
|
||||
setBulkBusy(null);
|
||||
}
|
||||
// 清空垃圾桶:同上,走各自的彻底删除通道
|
||||
async function purgeAll() {
|
||||
setPurgeAllOpen(false);
|
||||
setBulkBusy("purge");
|
||||
setErrText("");
|
||||
const itemIds = items.map((p) => p.id);
|
||||
const assetIds = assets.map((a) => a.id);
|
||||
const itemResults = await Promise.allSettled(itemIds.map((id) => Promise.resolve(onPurge(id))));
|
||||
const assetResults = await Promise.allSettled(assetIds.map((id) => api.purgeAsset(id)));
|
||||
const failedItemIds = new Set(itemIds.filter((_, i) => itemResults[i].status === "rejected"));
|
||||
const failedAssetIds = new Set(assetIds.filter((_, i) => assetResults[i].status === "rejected"));
|
||||
setItems((list) => list.filter((p) => failedItemIds.has(p.id)));
|
||||
setAssets((list) => list.filter((a) => failedAssetIds.has(a.id)));
|
||||
const firstFail = [...itemResults, ...assetResults].find((r): r is PromiseRejectedResult => r.status === "rejected");
|
||||
if (firstFail) setErrText(firstFail.reason instanceof Error ? firstFail.reason.message : "部分删除失败");
|
||||
setBulkBusy(null);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="trash-page">
|
||||
<div className="page-head">
|
||||
@@ -81,6 +120,12 @@ export function TrashPage({ onRestore, onPurge }: {
|
||||
<span>可恢复,或彻底删除(不可恢复)</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="actions">
|
||||
<button className="btn" type="button" disabled={isEmpty || bulkBusy !== null} onClick={() => setPurgeAllOpen(true)}>清空垃圾桶</button>
|
||||
<button className="btn btn-primary" type="button" disabled={isEmpty || bulkBusy !== null} onClick={() => void restoreAll()}>
|
||||
{bulkBusy === "restore" ? "恢复中…" : "全部恢复"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{loading ? (
|
||||
@@ -165,6 +210,16 @@ export function TrashPage({ onRestore, onPurge }: {
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
<ConfirmModal
|
||||
open={purgeAllOpen}
|
||||
title="清空垃圾桶"
|
||||
subtitle="// PURGE ALL"
|
||||
detail="清空垃圾桶数据无法恢复,是否需要清空?"
|
||||
confirmText="清空"
|
||||
onCancel={() => setPurgeAllOpen(false)}
|
||||
onConfirm={purgeAll}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user