feat: 任务中心批次弹窗加「加入资产库」状态+单图/一键切换 (row38)
任务中心点已完成批次的弹窗里,每张生成图现在: - 显示「已入库」绿标 + 入库态橙描边 - 右下角单图「加入资产库 / 取消」按钮(单图粒度,复用 Asset.in_library 真门控) - 弹窗头部「一键全部加入资产库 / 全部移出」批量切换 乐观更新 batchImgs+assets,失败回滚;生成时已扣费,此口只控是否进资产库列表、不二次扣费。 弹窗 createPortal 到 body 不在 .image-workbench 作用域,故 .modal 作用域补一份 gen 网格/徽标/按钮样式。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -214,6 +214,37 @@ export function AssetFactoryPage({ navigate }: { navigate: (page: Page) => void
|
||||
return () => document.removeEventListener("click", close);
|
||||
}, [openChip]);
|
||||
|
||||
// row38:任务中心批次弹窗 —— 每张生成图都可「加入资产库 / 取消加入」,真源是后端 Asset.in_library。
|
||||
// 乐观更新 batchImgs(弹窗)+ assets(列表 cover 用),失败回滚。资产生成时已落库扣费,此口只控是否进资产库列表、不二次扣费。
|
||||
const [libBusy, setLibBusy] = useState(false);
|
||||
function patchInLib(ids: Set<string>, next: boolean) {
|
||||
setBatchImgs((prev) => prev.map((a) => (ids.has(a.id) ? { ...a, in_library: next } : a)));
|
||||
setAssets((prev) => prev.map((a) => (ids.has(a.id) ? { ...a, in_library: next } : a)));
|
||||
}
|
||||
async function toggleBatchImgLibrary(asset: Asset) {
|
||||
if (!asset.id || libBusy) return;
|
||||
const next = !asset.in_library;
|
||||
const ids = new Set([asset.id]);
|
||||
patchInLib(ids, next);
|
||||
try { await api.setAssetLibrary(asset.id, next); }
|
||||
catch { patchInLib(ids, !next); }
|
||||
}
|
||||
// 一键:有未入库 → 全部加入;已全部入库 → 全部移出
|
||||
const batchWithId = batchImgs.filter((a) => a.id);
|
||||
const allInLib = batchWithId.length > 0 && batchWithId.every((a) => a.in_library);
|
||||
async function toggleAllBatchLibrary() {
|
||||
if (!batchWithId.length || libBusy) return;
|
||||
const next = !allInLib;
|
||||
const targets = batchWithId.filter((a) => a.in_library !== next).map((a) => a.id);
|
||||
if (!targets.length) return;
|
||||
const ids = new Set(targets);
|
||||
setLibBusy(true);
|
||||
patchInLib(ids, next);
|
||||
try { await Promise.all(targets.map((id) => api.setAssetLibrary(id, next))); }
|
||||
catch { patchInLib(ids, !next); }
|
||||
finally { setLibBusy(false); }
|
||||
}
|
||||
|
||||
const typeOptions = Array.from(new Set(taskBatches.map((b) => b.label).filter(Boolean)));
|
||||
const TIME_OPTS: Array<{ value: typeof timeFilter; label: string }> = [
|
||||
{ value: "all", label: "全部时间" }, { value: "1", label: "今天" }, { value: "7", label: "近 7 天" }, { value: "30", label: "近 30 天" }
|
||||
@@ -453,6 +484,13 @@ export function AssetFactoryPage({ navigate }: { navigate: (page: Page) => void
|
||||
<div className="ic-m"><LayoutGrid size={17} /></div>
|
||||
<div className="ti">{openBatch.label}<span>// {openBatch.count} 张 · {(openBatch.created_at || "").slice(0, 10)}</span></div>
|
||||
<span style={{ flex: 1 }} />
|
||||
{/* row38:一键全部加入资产库 / 全部移出(整批切换) */}
|
||||
{batchWithId.length > 0 && (
|
||||
<button className={`btn btn-sm${allInLib ? "" : " btn-primary"}`} type="button" disabled={libBusy} style={{ marginRight: 8 }} onClick={toggleAllBatchLibrary}>
|
||||
<Bookmark size={13} />
|
||||
{allInLib ? "全部移出资产库" : "一键全部加入资产库"}
|
||||
</button>
|
||||
)}
|
||||
<button className="x" type="button" aria-label="关闭" onClick={() => setOpenBatch(null)}><X size={16} /></button>
|
||||
</div>
|
||||
<div className="modal-b">
|
||||
@@ -464,13 +502,30 @@ export function AssetFactoryPage({ navigate }: { navigate: (page: Page) => void
|
||||
<div className="gen-images" style={{ "--cols": Math.min(4, batchImgs.length), "--ratio": "1 / 1" } as React.CSSProperties}>
|
||||
{batchImgs.map((a, i) => {
|
||||
const u = a.files?.find((f) => f.preview_url)?.preview_url || a.files?.[0]?.preview_url || "";
|
||||
const inLib = !!a.in_library;
|
||||
return (
|
||||
<div className="gen-image" key={a.id}>
|
||||
<div className={`gen-image${inLib && u ? " adopted" : ""}`} key={a.id}>
|
||||
{u ? (
|
||||
<img className="gen-image-img" src={u} alt={a.name} loading="lazy" style={{ cursor: "zoom-in" }} onClick={() => setPreview({ src: u, name: a.name })} />
|
||||
) : (
|
||||
<div className="placeholder"><span className="ph-frame">#{i + 1}</span></div>
|
||||
)}
|
||||
{/* row38:左上「已入库」绿标 + 右下角加入/取消按钮(单图粒度) */}
|
||||
{u && inLib && (
|
||||
<span className="gen-adopt-badge"><Check size={11} />已入库</span>
|
||||
)}
|
||||
{u && (
|
||||
<button
|
||||
type="button"
|
||||
className={`tc-lib-btn${inLib ? " on" : ""}`}
|
||||
title={inLib ? "取消加入资产库" : "加入资产库"}
|
||||
disabled={libBusy}
|
||||
onClick={() => toggleBatchImgLibrary(a)}
|
||||
>
|
||||
<Bookmark size={13} />
|
||||
{inLib ? "取消" : "加入资产库"}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
Reference in New Issue
Block a user