feat(core): Wave 3 batch4 — 外壳账户菜单 + 设置dirty引擎 + 商品创建抽屉多图
全局外壳(sub-agent):侧栏 .user 点击弹账户菜单(头像+店名+用户名[非邮箱,认证定调]+
个人设置/消息/团队/消费/退出),复用现成 .shell-account-menu CSS,点外部/ESC 收起
设置页(sub-agent):dirty-state 引擎(baseline+draft diff 13字段,顶栏「保存所有变更」
无改动 disabled、改动激活+N项计数、统一保存、beforeunload、nav dirty-dot);修一处类型错(|| {} → 可选链)
商品创建抽屉(sub-agent):主图多图≤5+.pf-grid缩略网格+单张删、submit主图/卖点必填校验+收编bulletDraft、
上传区拖拽变橙、卖点字重对齐;内联 style 抽进 css
验证: tsc 0 · build 0 · 走查 账户菜单5项+ESC、设置保存按钮无改动disabled、抽屉multiple+pf-grid 全过 · 0 console error
⚠️ 外壳:顶栏头像菜单接线需改 App.tsx/pipeline.tsx(超出单文件范围),侧栏入口已全可用。移交监督。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
da8c6a70a7
commit
378b0a350c
@@ -0,0 +1,84 @@
|
||||
// Wave 3 batch4 walkthrough (scratch): 外壳账户菜单 + 设置dirty + 商品创建抽屉多图.
|
||||
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-batch4");
|
||||
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 = { shell: {}, settings: {}, drawer: {}, consoleErrors: [] };
|
||||
|
||||
// 外壳账户菜单(任意页)
|
||||
{
|
||||
const p = await ctx.newPage();
|
||||
p.on("console", (m) => { if (m.type() === "error") r.consoleErrors.push("shell:" + m.text()); });
|
||||
p.on("pageerror", (e) => r.consoleErrors.push("shell:PAGEERR:" + e.message));
|
||||
await p.goto(BASE + "/dashboard", { waitUntil: "load" });
|
||||
await p.waitForTimeout(1500);
|
||||
const user = p.locator(".user").first();
|
||||
if (await user.count()) {
|
||||
await user.click();
|
||||
await p.waitForTimeout(350);
|
||||
r.shell.menuShown = await p.locator(".shell-account-menu.show, .shell-account-menu").count();
|
||||
r.shell.menuItems = await p.locator(".shell-account-menu button").count();
|
||||
await p.screenshot({ path: path.join(OUT, "account-menu.png") });
|
||||
await p.keyboard.press("Escape");
|
||||
await p.waitForTimeout(300);
|
||||
r.shell.closedByEsc = (await p.locator(".shell-account-menu.show").count()) === 0;
|
||||
} else r.shell = "no .user";
|
||||
await p.close();
|
||||
}
|
||||
|
||||
// 设置 dirty 引擎
|
||||
{
|
||||
const p = await ctx.newPage();
|
||||
p.on("console", (m) => { if (m.type() === "error") r.consoleErrors.push("settings:" + m.text()); });
|
||||
p.on("pageerror", (e) => r.consoleErrors.push("settings:PAGEERR:" + e.message));
|
||||
await p.goto(BASE + "/settings", { waitUntil: "load" });
|
||||
await p.waitForTimeout(1500);
|
||||
const saveBtn = p.locator("button:has-text('保存所有变更'), button:has-text('保存')").first();
|
||||
r.settings.saveBtnDisabledInitially = await saveBtn.isDisabled().catch(() => "n/a");
|
||||
// 改一个开关(找第一个 switch/checkbox)触发 dirty
|
||||
const sw = p.locator(".switch input, input[type=checkbox]").first();
|
||||
if (await sw.count()) {
|
||||
await sw.click({ force: true });
|
||||
await p.waitForTimeout(400);
|
||||
r.settings.saveBtnEnabledAfterChange = !(await saveBtn.isDisabled().catch(() => true));
|
||||
r.settings.dirtyCount = await p.locator(".save-count").count();
|
||||
r.settings.navDirtyDot = await p.locator(".settings-nav a.has-changes, .nav-dot").count();
|
||||
}
|
||||
await p.screenshot({ path: path.join(OUT, "settings-dirty.png") });
|
||||
await p.close();
|
||||
}
|
||||
|
||||
// 商品创建抽屉多图
|
||||
{
|
||||
const p = await ctx.newPage();
|
||||
p.on("console", (m) => { if (m.type() === "error") r.consoleErrors.push("drawer:" + m.text()); });
|
||||
p.on("pageerror", (e) => r.consoleErrors.push("drawer:PAGEERR:" + e.message));
|
||||
await p.goto(BASE + "/products", { waitUntil: "load" });
|
||||
await p.waitForTimeout(1500);
|
||||
const newBtn = p.locator("button:has-text('新建商品'), button:has-text('新建')").first();
|
||||
if (await newBtn.count()) {
|
||||
await newBtn.click();
|
||||
await p.waitForTimeout(500);
|
||||
r.drawer.drawerShown = await p.locator(".drawer.show, .pc-drawer").count();
|
||||
r.drawer.multipleInput = await p.locator("input[type=file][multiple]").count();
|
||||
r.drawer.pfGrid = await p.locator(".pf-grid").count();
|
||||
await p.screenshot({ path: path.join(OUT, "create-drawer.png") });
|
||||
} else r.drawer = "no 新建商品 btn";
|
||||
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));
|
||||
Reference in New Issue
Block a user