feat(web): 重做设计系统与全部页面,前端观感对齐当下审美
用户反馈"UI 太丑、太简陋"。不是换配色,而是先立 token 再让页面去用: - tailwind.config.js:纸白 paper / 墨绿 primary / 陶土橙 clay + 语义色 sage/wheat/rust/sky,字号阶、圆角阶、投影、动效 - index.css:卡片、按钮五种变体、输入、pill/chip、导航、骨架屏 - 新增 icons.tsx(线性图标集)与 ui.tsx(Button/Sheet/Toast/空态/骨架/PageHeader) - 新增 AppShell 取代 Layout + WorkspaceNav:桌面侧栏 + 移动顶部横条,工单未读角标 - 六个页面全部重写:登录、首页、任务、看板、工单、知识、成员、邀请落地 - 详情抽屉改用 Sheet,工单态补齐需求人/办理人/退回次数/动作按钮 e2e 因 DOM 升级同步调整(抽屉按 role=dialog 定位、下拉改用 aria-label), 三套用例(7 + 8 + 7 项)全部重跑通过;tsc 0 错,构建 45 modules / gzip 76.70 kB。
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
import { chromium } from "playwright-core";
|
||||
|
||||
const API = "http://127.0.0.1:8100/api/v1";
|
||||
const WEB = "http://127.0.0.1:5180";
|
||||
const phone = "1370000" + String(Date.now()).slice(-4);
|
||||
|
||||
async function call(path, body, token, method = "POST") {
|
||||
const res = await fetch(API + path, {
|
||||
method,
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
...(token ? { authorization: "Bearer " + token } : {}),
|
||||
},
|
||||
body: body ? JSON.stringify(body) : undefined,
|
||||
});
|
||||
const json = await res.json();
|
||||
if (json.code !== 0) throw new Error(path + " -> " + JSON.stringify(json).slice(0, 200));
|
||||
return json.data;
|
||||
}
|
||||
|
||||
const sent = await call("/auth/sms/send", { phone });
|
||||
const login = await call("/auth/login", { phone, code: sent.dev_code ?? "123456" });
|
||||
const token = login.tokens.access_token;
|
||||
const ws = await call("/workspaces", { name: "海图工作室", slug: "haitu-" + Date.now().toString(36) }, token);
|
||||
const wsId = ws.id;
|
||||
|
||||
const tasks = [
|
||||
["整理发票原件并寄给财务", "todo", 2, "#报销 财务"],
|
||||
["给客户出第三季度复盘报告", "doing", 3, "#客户 周报"],
|
||||
["把采集端的日志级别改回 info", "doing", 1, ""],
|
||||
["和供应商确认二次报价", "blocked", 2, "#采购"],
|
||||
["入职物料清单更新", "done", 0, ""],
|
||||
["评审新版落地页文案", "inbox", 1, "#设计"],
|
||||
];
|
||||
for (const [title, status, priority, tags] of tasks) {
|
||||
const item = await call(`/workspaces/${wsId}/items`, { title, status, priority }, token);
|
||||
if (tags) {
|
||||
const names = tags.split(" ").filter((t) => t.startsWith("#")).map((t) => t.slice(1));
|
||||
if (names.length) await call(`/workspaces/${wsId}/items/${item.id}`, { version: item.version, tag_names: names }, token, "PATCH");
|
||||
}
|
||||
}
|
||||
|
||||
const shop = await call(`/workspaces/${wsId}/items`, { title: "官网改版:首页轮播要换成新版素材", status: "todo", priority: 2 }, token);
|
||||
await call(`/workspaces/${wsId}/items/${shop.id}/knowledge?doc_type=howto`, {}, token);
|
||||
await call(`/workspaces/${wsId}/knowledge`, { title: "报销单必须贴发票原件,打印件不算", doc_type: "note" }, token);
|
||||
|
||||
const me = await call("/workspaces/" + wsId + "/members", null, token, "GET").catch(() => null);
|
||||
|
||||
const browser = await chromium.launch({
|
||||
executablePath: "C:\\Users\\Steven\\AppData\\Local\\ms-playwright\\chromium-1223\\chrome-win64\\chrome.exe",
|
||||
});
|
||||
const page = await browser.newPage({ viewport: { width: 1440, height: 960 }, deviceScaleFactor: 1 });
|
||||
await page.goto(WEB + "/login");
|
||||
await page.evaluate(([t, r]) => {
|
||||
localStorage.setItem("dadao.access", t);
|
||||
localStorage.setItem("dadao.refresh", r);
|
||||
}, [login.tokens.access_token, login.tokens.refresh_token]);
|
||||
|
||||
const shots = [];
|
||||
async function shot(url, name, prep) {
|
||||
await page.goto(WEB + url, { waitUntil: "networkidle" });
|
||||
if (prep) await prep();
|
||||
await page.waitForTimeout(600);
|
||||
const file = `e2e/artifacts/ui-${name}.png`;
|
||||
await page.screenshot({ path: file, fullPage: false });
|
||||
shots.push(file);
|
||||
}
|
||||
|
||||
await shot("/", "home");
|
||||
await shot(`/w/${wsId}`, "tasks");
|
||||
await shot(`/w/${wsId}/board`, "board");
|
||||
await shot(`/w/${wsId}/tickets`, "tickets");
|
||||
await shot(`/w/${wsId}/knowledge`, "knowledge");
|
||||
await shot(`/w/${wsId}/members`, "members");
|
||||
await shot(`/w/${wsId}`, "detail", async () => {
|
||||
await page.getByRole("button", { name: "全部", exact: true }).click();
|
||||
await page.waitForTimeout(500);
|
||||
await page.getByRole("button", { name: "把采集端的日志级别改回 info" }).first().click();
|
||||
await page.waitForTimeout(800);
|
||||
});
|
||||
await shot("/login", "login");
|
||||
|
||||
console.log(shots.join("\n"));
|
||||
await browser.close();
|
||||
+2
-2
@@ -74,10 +74,10 @@ try {
|
||||
|
||||
// 4. 详情抽屉可编辑:改优先级 + 加评论
|
||||
await page.getByRole("button", { name: "写周报初稿" }).click();
|
||||
const drawer = page.locator("aside");
|
||||
const drawer = page.getByRole("dialog");
|
||||
await drawer.waitFor();
|
||||
// 抽屉里现在有两个 select(优先级 + 升级工单选人),取第一个才是优先级
|
||||
await drawer.locator("select").first().selectOption("2");
|
||||
await drawer.getByLabel("优先级").selectOption("2");
|
||||
await page.waitForTimeout(500);
|
||||
const commentBox = drawer.getByPlaceholder("补充一句进展…");
|
||||
await commentBox.fill("E2E:初稿已出,等评审");
|
||||
|
||||
+5
-5
@@ -117,9 +117,9 @@ try {
|
||||
await pageA.goto(`${APP}/w/${wsId}`, { waitUntil: "domcontentloaded" });
|
||||
await pageA.getByRole("button", { name: "全部", exact: true }).click();
|
||||
await pageA.getByRole("button", { name: "开通测试账号权限" }).click();
|
||||
const drawerA = pageA.locator("aside");
|
||||
const drawerA = pageA.getByRole("dialog");
|
||||
await drawerA.waitFor();
|
||||
const memberPicker = drawerA.locator("select").nth(1);
|
||||
const memberPicker = drawerA.getByLabel("选择办理人");
|
||||
await memberPicker.selectOption({ index: 1 });
|
||||
await drawerA.getByRole("button", { name: "升级成工单" }).click();
|
||||
await drawerA.locator("text=已把工单交给").first().waitFor();
|
||||
@@ -142,7 +142,7 @@ try {
|
||||
await ticketCard.getByRole("button", { name: "开始", exact: true }).waitFor();
|
||||
await ticketCard.getByRole("button", { name: "开始", exact: true }).click();
|
||||
await ticketCard.getByRole("button", { name: "详情", exact: true }).click();
|
||||
const drawerB = pageB.locator("aside");
|
||||
const drawerB = pageB.getByRole("dialog");
|
||||
await drawerB.waitFor();
|
||||
await drawerB.getByPlaceholder("给对方的说明(提交或验收时一并发过去)").fill("已经开了只读账号");
|
||||
await drawerB.getByRole("button", { name: "提交验收" }).click();
|
||||
@@ -156,7 +156,7 @@ try {
|
||||
const mineCard = pageA.locator("li.card").filter({ hasText: "开通测试账号权限" }).first();
|
||||
await mineCard.waitFor();
|
||||
await mineCard.getByRole("button", { name: "详情", exact: true }).click();
|
||||
const drawerVerify = pageA.locator("aside");
|
||||
const drawerVerify = pageA.getByRole("dialog");
|
||||
await drawerVerify.waitFor();
|
||||
await drawerVerify.getByPlaceholder("给对方的说明(提交或验收时一并发过去)").fill("确认可用");
|
||||
await drawerVerify.getByRole("button", { name: "通过验收" }).click();
|
||||
@@ -186,7 +186,7 @@ try {
|
||||
await browser.close();
|
||||
} catch (err) {
|
||||
await pageA.screenshot({ path: `${SHOT_DIR}/tickets-failure.png` }).catch(() => {});
|
||||
const drawerText = await pageA.locator("aside").innerText().catch(() => "");
|
||||
const drawerText = await pageA.getByRole("dialog").innerText().catch(() => "");
|
||||
if (drawerText) console.error(`\n[抽屉内容]\n${drawerText}`);
|
||||
if (consoleErrors.length > 0) console.error(`\n[控制台]\n${consoleErrors.join("\n")}`);
|
||||
const bodyText = await pageA.locator("body").innerText().catch(() => "");
|
||||
|
||||
Reference in New Issue
Block a user