feat(core): Wave 3 batch2 — 资产库详情/上传/批量 + AI工具模特库/gen-card/pl-modal

资产库(P0,sub-agent):多选+吸底 bulk-bar、资产详情弹窗(立绘+三视图+标签/属性+商品媒体画廊)、
  上传 Drawer→居中 Modal(类型分段+按类型字段+拖拽预览)、缺三视图徽标(复用现成类)
AI图片工具(P0,sub-agent):接入现成 ActorLibrary 模特库弹窗、gen-card 三件套(再生成/就地反馈/
  已采用绿角标,采用反馈去全局弹窗改就地)、平台套图恢复多选+分组、商品库 pl-modal 选择器(restraint重写)

验证: tsc 0 · build 0 · 走查 资产详情/上传弹窗开关ESC + 模特库/pl-modal 打开 全过 · 0 console error

⚠️ 真实数据缺(未造假):资产「移动到」改分类需 api.updateAsset 支持 category 字段;
  详情三视图版本历史 V1 是 mock 无真实源;平台无 platform 字段→前端组织分批。均移交监督。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
seaislee1209
2026-06-19 05:08:21 +08:00
co-authored by Claude Opus 4.8
parent 0083918207
commit 38f56cb037
5 changed files with 1395 additions and 164 deletions
+76
View File
@@ -0,0 +1,76 @@
// Wave 3 batch2 walkthrough (scratch): library 详情/上传/批量 + ai-tools 模特库/pl-modal.
import { chromium } from "playwright";
import fs from "node:fs";
import path from "node:path";
const BASE = process.env.BASE || "http://localhost:5173";
const TOKEN = process.env.TOKEN || "";
const OUT = path.resolve("../../../_qa_shots/wave3-batch2");
fs.mkdirSync(OUT, { recursive: true });
const browser = await chromium.launch({ headless: true });
const ctx = await browser.newContext({ viewport: { width: 1440, height: 900 }, colorScheme: "light" });
const seed = await ctx.newPage();
await seed.goto(BASE + "/", { waitUntil: "domcontentloaded" });
await seed.evaluate((t) => localStorage.setItem("airshelf_token", t), TOKEN);
await seed.close();
const r = { library: {}, aitools: {}, consoleErrors: [] };
// ---- LIBRARY ----
{
const p = await ctx.newPage();
p.on("console", (m) => { if (m.type() === "error") r.consoleErrors.push("lib:" + m.text()); });
p.on("pageerror", (e) => r.consoleErrors.push("lib:PAGEERR:" + e.message));
await p.goto(BASE + "/library", { waitUntil: "load" });
await p.waitForTimeout(1800);
r.library.assetCards = await p.locator(".asset-card").count();
await p.screenshot({ path: path.join(OUT, "library.png") });
// 详情弹窗:点首张卡
if (r.library.assetCards > 0) {
await p.locator(".asset-card").first().click();
await p.waitForTimeout(500);
r.library.detailModal = await p.locator(".asset-detail-modal, .modal-bg.show").count();
await p.screenshot({ path: path.join(OUT, "library-detail.png") });
await p.keyboard.press("Escape");
await p.waitForTimeout(400);
r.library.detailClosedByEsc = (await p.locator(".asset-detail-modal").count()) === 0;
}
// 上传弹窗:找含「上传」的按钮
const upBtn = p.locator("button:has-text('上传')").first();
if (await upBtn.count()) {
await upBtn.click();
await p.waitForTimeout(450);
r.library.uploadModal = await p.locator(".upload-modal, .modal-bg.show").count();
await p.screenshot({ path: path.join(OUT, "library-upload.png") });
await p.keyboard.press("Escape");
await p.waitForTimeout(300);
}
await p.close();
}
// ---- AI-TOOLS (model-photo) ----
{
const p = await ctx.newPage();
p.on("console", (m) => { if (m.type() === "error") r.consoleErrors.push("ai:" + m.text()); });
p.on("pageerror", (e) => r.consoleErrors.push("ai:PAGEERR:" + e.message));
await p.goto(BASE + "/model-photo", { waitUntil: "load" });
await p.waitForTimeout(1800);
r.aitools.actorLibBtn = await p.locator(".iw-actor-lib-btn").count();
r.aitools.plBtn = await p.locator(".iw-pl-btn").count();
await p.screenshot({ path: path.join(OUT, "model-photo.png") });
// 商品库 pl-modal
if (await p.locator(".iw-pl-btn").count()) {
await p.locator(".iw-pl-btn").first().click();
await p.waitForTimeout(500);
r.aitools.plModal = await p.locator(".pl-modal, .pl-modal-bg").count();
await p.screenshot({ path: path.join(OUT, "model-photo-plmodal.png") });
await p.keyboard.press("Escape");
await p.waitForTimeout(400);
}
await p.close();
}
await browser.close();
console.log(JSON.stringify(r, null, 2));
fs.writeFileSync(path.join(OUT, "summary.json"), JSON.stringify(r, null, 2));