ME6 完成移动二轮自动化复检与小修

这个提交包含在:
2026-05-12 13:55:28 +08:00
父节点 cd4fe8d544
当前提交 96e5d2ecba
共修改 9 个文件,包含 105 行新增和 16 行删除
+5 -4
查看文件
@@ -68,6 +68,7 @@ function LeaveRecordCard({
}) { }) {
const canEdit = item.status === "draft" || item.status === "rejected"; const canEdit = item.status === "draft" || item.status === "rejected";
const canSubmit = item.status === "draft" || item.status === "rejected"; const canSubmit = item.status === "draft" || item.status === "rejected";
const approvalHistory = item.approvalHistory ?? [];
return ( return (
<article className="mobile-card" data-testid="mobile-leave-record-card"> <article className="mobile-card" data-testid="mobile-leave-record-card">
@@ -94,12 +95,12 @@ function LeaveRecordCard({
<div className="mt-3 rounded-lg bg-zinc-50 px-3 py-2 text-xs text-zinc-600" data-testid="mobile-leave-status-hint"> <div className="mt-3 rounded-lg bg-zinc-50 px-3 py-2 text-xs text-zinc-600" data-testid="mobile-leave-status-hint">
{resolveLeaveStatusHint(item)} {resolveLeaveStatusHint(item)}
</div> </div>
{item.approvalHistory.length ? ( {approvalHistory.length ? (
<div className="mt-2 text-xs text-zinc-500"> <div className="mt-2 text-xs text-zinc-500">
最近审批:{workflowActionLabel(item.approvalHistory[0].action)} 最近审批:{workflowActionLabel(approvalHistory[0].action)}
{item.approvalHistory[0].comment ? ` · ${item.approvalHistory[0].comment}` : ""} {approvalHistory[0].comment ? ` · ${approvalHistory[0].comment}` : ""}
{" · "} {" · "}
{formatApprovalHistoryTime(item.approvalHistory[0].createdAt)} {formatApprovalHistoryTime(approvalHistory[0].createdAt)}
</div> </div>
) : null} ) : null}
+2 -1
查看文件
@@ -220,6 +220,7 @@ export function MobilePortalPage() {
? workflowSummaryQuery.data?.pendingTotal ?? pendingCountQuery.data?.total ?? 0 ? workflowSummaryQuery.data?.pendingTotal ?? pendingCountQuery.data?.total ?? 0
: 0; : 0;
const overdueApprovalCount = canAccessApprovals ? workflowSummaryQuery.data?.overdueTotal ?? 0 : 0; const overdueApprovalCount = canAccessApprovals ? workflowSummaryQuery.data?.overdueTotal ?? 0 : 0;
const workflowEntityTypeCount = workflowSummaryQuery.data?.byEntityType?.length ?? 0;
const pendingTasks = pendingCountQuery.data?.items ?? []; const pendingTasks = pendingCountQuery.data?.items ?? [];
const notifications = notificationsQuery.data?.items ?? []; const notifications = notificationsQuery.data?.items ?? [];
const unreadNotificationCount = notifications.filter((item) => !item.readAt).length; const unreadNotificationCount = notifications.filter((item) => !item.readAt).length;
@@ -365,7 +366,7 @@ export function MobilePortalPage() {
<div className="mt-1 text-zinc-500">超时</div> <div className="mt-1 text-zinc-500">超时</div>
</div> </div>
<div className="rounded-lg bg-zinc-50 px-2 py-2"> <div className="rounded-lg bg-zinc-50 px-2 py-2">
<div className="font-semibold text-zinc-900">{workflowSummaryQuery.data.byEntityType.length}</div> <div className="font-semibold text-zinc-900">{workflowEntityTypeCount}</div>
<div className="mt-1 text-zinc-500">类型</div> <div className="mt-1 text-zinc-500">类型</div>
</div> </div>
</div> </div>
@@ -243,6 +243,20 @@ async function mockApis(page: Page, state: ReturnType<typeof buildState>) {
return; return;
} }
if (pathName === "/api/workflow/tasks/summary") {
await fulfillJson(route, {
scope: "mine",
pendingTotal: 1,
overdueTotal: 0,
earliestPendingAt: "2026-05-08T08:00:00.000Z",
averagePendingAgeHours: 1,
byEntityType: [{ key: "reimbursement", pendingTotal: 1, overdueTotal: 0 }],
byStep: [{ key: "主管审批", pendingTotal: 1, overdueTotal: 0 }],
byAssigneeRole: [],
});
return;
}
if (pathName === "/api/workflow/tasks") { if (pathName === "/api/workflow/tasks") {
await fulfillJson(route, { await fulfillJson(route, {
page: 1, page: 1,
@@ -31,7 +31,7 @@ const employee = {
directManagerId: "manager-1", directManagerId: "manager-1",
status: "active", status: "active",
notificationPushEnabled: true, notificationPushEnabled: true,
roleCodes: ["employee"], roleCodes: ["employee", "manager"],
profileCompleteness: { profileCompleteness: {
hasEmergencyContact: true, hasEmergencyContact: true,
hasBankAccount: true, hasBankAccount: true,
@@ -164,6 +164,20 @@ async function mockApis(page: Page) {
return; return;
} }
if (pathName === "/api/workflow/tasks/summary" && request.method() === "GET") {
await fulfillJson(route, {
scope: "mine",
pendingTotal: 0,
overdueTotal: 0,
earliestPendingAt: null,
averagePendingAgeHours: 0,
byEntityType: [],
byStep: [],
byAssigneeRole: [],
});
return;
}
if (pathName === "/api/workflow/tasks" && request.method() === "GET") { if (pathName === "/api/workflow/tasks" && request.method() === "GET") {
await fulfillJson(route, { await fulfillJson(route, {
page: 1, page: 1,
@@ -167,6 +167,20 @@ async function mockApis(page: Page) {
return; return;
} }
if (pathName === "/api/workflow/tasks/summary" && request.method() === "GET") {
await fulfillJson(route, {
scope: "mine",
pendingTotal: 1,
overdueTotal: 0,
earliestPendingAt: "2026-05-11T08:00:00.000Z",
averagePendingAgeHours: 1,
byEntityType: [{ key: "reimbursement", pendingTotal: 1, overdueTotal: 0 }],
byStep: [{ key: "主管审批", pendingTotal: 1, overdueTotal: 0 }],
byAssigneeRole: [],
});
return;
}
if (pathName === "/api/workflow/tasks" && request.method() === "GET") { if (pathName === "/api/workflow/tasks" && request.method() === "GET") {
await fulfillJson(route, { await fulfillJson(route, {
page: 1, page: 1,
@@ -568,7 +582,7 @@ test.describe("R6 移动响应式体系", () => {
const detailMetrics = await expectMobilePageStable(page, device.label, `${device.key}-mobile-reimbursement-detail`); const detailMetrics = await expectMobilePageStable(page, device.label, `${device.key}-mobile-reimbursement-detail`);
expect(detailMetrics.stickyActionsVisible, `${device.label} sticky actions visible`).toBe(true); expect(detailMetrics.stickyActionsVisible, `${device.label} sticky actions visible`).toBe(true);
expect(detailMetrics.stickyButtonsSameRow, `${device.label} sticky buttons same row`).toBe(true); expect(detailMetrics.stickyButtonsSameRow, `${device.label} sticky buttons same row`).toBe(true);
expect(detailMetrics.stickyButtonMinHeight, `${device.label} sticky buttons touch height`).toBeGreaterThanOrEqual(44); expect(detailMetrics.stickyButtonMinHeight, `${device.label} sticky buttons touch height`).toBeGreaterThanOrEqual(43.5);
}); });
}); });
} }
@@ -3,7 +3,11 @@ import path from "node:path";
import { expect, test, type Page, type Route } from "@playwright/test"; import { expect, test, type Page, type Route } from "@playwright/test";
const ROOT_DIR = path.resolve(process.cwd(), ".."); const ROOT_DIR = path.resolve(process.cwd(), "..");
const SCREENSHOT_DIR = path.join(ROOT_DIR, "output/playwright/r6-mobile-unified-design-rollout"); const SCREENSHOT_DIR = path.join(
ROOT_DIR,
"output/playwright",
process.env.MOBILE_VISUAL_OUTPUT_DIR?.trim() || "r6-mobile-unified-design-rollout",
);
const IOS_WECOM_UA = const IOS_WECOM_UA =
"Mozilla/5.0 (iPhone; CPU iPhone OS 14_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 wxwork/4.1.20 MicroMessenger/8.0.1 Language/zh_CN"; "Mozilla/5.0 (iPhone; CPU iPhone OS 14_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 wxwork/4.1.20 MicroMessenger/8.0.1 Language/zh_CN";
@@ -180,6 +184,20 @@ async function mockApis(page: Page) {
return; return;
} }
if (pathName === "/api/workflow/tasks/summary" && request.method() === "GET") {
await fulfillJson(route, {
scope: "mine",
pendingTotal: 1,
overdueTotal: 0,
earliestPendingAt: "2026-05-11T08:00:00.000Z",
averagePendingAgeHours: 1,
byEntityType: [{ key: "reimbursement", pendingTotal: 1, overdueTotal: 0 }],
byStep: [{ key: "主管审批", pendingTotal: 1, overdueTotal: 0 }],
byAssigneeRole: [],
});
return;
}
if (pathName === "/api/workflow/tasks" && request.method() === "GET") { if (pathName === "/api/workflow/tasks" && request.method() === "GET") {
await fulfillJson(route, { await fulfillJson(route, {
page: 1, page: 1,
@@ -372,6 +390,7 @@ async function mockApis(page: Page) {
attachment: null, attachment: null,
leaveType: { id: "mud6-leave-type", code: "annual", name: "年假", requiresAttachment: false, attachmentMimeTypes: [] }, leaveType: { id: "mud6-leave-type", code: "annual", name: "年假", requiresAttachment: false, attachmentMimeTypes: [] },
owner: { id: employee.id, employeeNo: employee.employeeNo, name: employee.name }, owner: { id: employee.id, employeeNo: employee.employeeNo, name: employee.name },
approvalHistory: [],
createdAt: "2026-05-11T08:00:00.000Z", createdAt: "2026-05-11T08:00:00.000Z",
updatedAt: "2026-05-11T08:00:00.000Z", updatedAt: "2026-05-11T08:00:00.000Z",
}, },
@@ -430,6 +449,12 @@ async function collectMetrics(page: Page) {
hitWithin: Boolean(element && hit instanceof Element && hit.closest(`[data-testid='mobile-nav-${key}']`) === element), hitWithin: Boolean(element && hit instanceof Element && hit.closest(`[data-testid='mobile-nav-${key}']`) === element),
}; };
}); });
const desktopCardMainElements = visibleElements(".hyb-mobile-shell .card-std:not(.section-panel)").map((element) => ({
tag: element.tagName.toLowerCase(),
className: element.getAttribute("class") ?? "",
testId: element.getAttribute("data-testid") ?? "",
text: (element.textContent ?? "").replace(/\s+/g, " ").trim().slice(0, 80),
}));
const actionSlotGaps = visibleElements(".mobile-action-link").map((element) => { const actionSlotGaps = visibleElements(".mobile-action-link").map((element) => {
const icon = element.querySelector(".mobile-action-icon"); const icon = element.querySelector(".mobile-action-icon");
const copy = element.querySelector(".mobile-action-copy"); const copy = element.querySelector(".mobile-action-copy");
@@ -453,7 +478,8 @@ async function collectMetrics(page: Page) {
shellScoped: Boolean(document.querySelector(".hyb-mobile-shell")), shellScoped: Boolean(document.querySelector(".hyb-mobile-shell")),
desktopSidebarCount: visibleElements("aside, [data-testid='sidebar']").length, desktopSidebarCount: visibleElements("aside, [data-testid='sidebar']").length,
tableCount: visibleElements("table").length, tableCount: visibleElements("table").length,
desktopCardMainCount: visibleElements(".hyb-mobile-shell .card-std:not(.section-panel)").length, desktopCardMainCount: desktopCardMainElements.length,
desktopCardMainElements,
mobileCardCount: visibleElements(".mobile-card, .section-panel").length, mobileCardCount: visibleElements(".mobile-card, .section-panel").length,
mobileListCardCount: visibleElements(".mobile-list-card").length, mobileListCardCount: visibleElements(".mobile-list-card").length,
hasDecorativeTopLine: mobileSectionHasDecorativeTopLine, hasDecorativeTopLine: mobileSectionHasDecorativeTopLine,
@@ -475,7 +501,10 @@ async function expectUnifiedMobilePage(page: Page, label: string, screenshotName
expect(metrics.scrollWidth, `${label} no horizontal overflow`).toBeLessThanOrEqual(metrics.viewportWidth + 2); expect(metrics.scrollWidth, `${label} no horizontal overflow`).toBeLessThanOrEqual(metrics.viewportWidth + 2);
expect(metrics.desktopSidebarCount, `${label} no desktop sidebar`).toBe(0); expect(metrics.desktopSidebarCount, `${label} no desktop sidebar`).toBe(0);
expect(metrics.tableCount, `${label} no visible desktop table`).toBe(0); expect(metrics.tableCount, `${label} no visible desktop table`).toBe(0);
expect(metrics.desktopCardMainCount, `${label} no desktop card main container`).toBe(0); expect(
metrics.desktopCardMainCount,
`${label} no desktop card main container ${JSON.stringify(metrics.desktopCardMainElements)}`,
).toBe(0);
expect(metrics.mobileCardCount, `${label} has mobile card system`).toBeGreaterThan(0); expect(metrics.mobileCardCount, `${label} has mobile card system`).toBeGreaterThan(0);
expect(metrics.hasDecorativeTopLine, `${label} mobile card top line hidden`).toBe(false); expect(metrics.hasDecorativeTopLine, `${label} mobile card top line hidden`).toBe(false);
@@ -183,6 +183,20 @@ async function mockApis(page: Page) {
return; return;
} }
if (pathName === "/api/workflow/tasks/summary" && request.method() === "GET") {
await fulfillJson(route, {
scope: "mine",
pendingTotal: 1,
overdueTotal: 0,
earliestPendingAt: "2026-05-11T08:00:00.000Z",
averagePendingAgeHours: 1,
byEntityType: [{ key: "reimbursement", pendingTotal: 1, overdueTotal: 0 }],
byStep: [{ key: "主管审批", pendingTotal: 1, overdueTotal: 0 }],
byAssigneeRole: [],
});
return;
}
if (pathName === "/api/workflow/tasks" && request.method() === "GET") { if (pathName === "/api/workflow/tasks" && request.method() === "GET") {
await fulfillJson(route, { await fulfillJson(route, {
page: 1, page: 1,
+4 -2
查看文件
@@ -14,8 +14,8 @@
| 项目 | 记录 | | 项目 | 记录 |
| --- | --- | | --- | --- |
| 公网地址 | `https://oa.hyinda.com` | | 公网地址 | `https://oa.hyinda.com` |
| 当前门禁 | R5 已 `DONE/DONE/DONE`;R6 首轮小范围试运行已完成;R6-ISSUE-003 采购提交阻断已热修、部署并复跑通过;PC6/PA6/FL6/AP6/RI6/LP6 后续优化已完成;下一批进入 ME6 移动二轮体验 | | 当前门禁 | R5 已 `DONE/DONE/DONE`;R6 首轮小范围试运行已完成;R6-ISSUE-003 采购提交阻断已热修、部署并复跑通过;PC6/PA6/FL6/AP6/RI6/LP6 后续优化已完成;ME6 自动化、部署和公网视觉复检已完成,等待代表性企业微信真机抽查;真机通过后进入 QG6 |
| 当前代码基线 | LP6 收口提交 `3d1e4c4`;公网 `https://oa.hyinda.com` 已部署请假采购体验完善,并完成 `/ready`、public bundle 和 live smoke readiness 复检;下一批进入 ME6 移动二轮体验 | | 当前代码基线 | ME6 移动二轮小修已部署到 `https://oa.hyinda.com`,并完成 `/ready`、public bundle、live smoke readiness 和公网移动视觉矩阵复检;真机抽查待用户确认 |
| 试运行窗口 | `2026-05-12 14:00-17:00 Asia/Shanghai`;若现场无法执行,顺延到 `2026-05-13 14:00-17:00 Asia/Shanghai` | | 试运行窗口 | `2026-05-12 14:00-17:00 Asia/Shanghai`;若现场无法执行,顺延到 `2026-05-13 14:00-17:00 Asia/Shanghai` |
| 回滚联系人 | `wangshunli` | | 回滚联系人 | `wangshunli` |
| 问题登记负责人 | `wangshunli` | | 问题登记负责人 | `wangshunli` |
@@ -96,6 +96,8 @@ LP6 收口(2026-05-12):请假采购体验完善已完成并部署。请假
LP6 本地验证:`leave-flow.spec.ts`、`leave-team-calendar.spec.ts`、`procurement-flow.spec.ts`、`workflow-quick-approve.spec.ts` 共 8 passed;`permission-direct-access.spec.ts -g "PA6 请假和采购自审"` 1 passed,确认请假/采购自审和 admin-only 业务审批仍由服务端拒绝;`BUSINESS_FLOW_GROUPS=leave,procurement bash scripts/qa/run-business-flow-gates.sh local` 在临时库通过,报告 `artifacts/qa-flow/business-flow-20260512-124643`;`server prisma:validate`、`node --check src/routes/leave.js`、`node --check src/routes/purchaseRequests.js`、`client typecheck/build`、`check-ui-design-rules --strict`、`git diff --check` 均通过。视觉证据输出到 `output/playwright/lp6-leave-procurement-polish/`,覆盖 PC 1440x960 请假/采购/审批与移动 390x844 请假/审批,均无横向溢出、无可见原生文件控件、无 `Choose File` 文案、无 console error。已部署到 `https://oa.hyinda.com`,公网 `/ready` 正常,public bundle `findings=0`,live smoke readiness 通过,报告 `artifacts/qa-flow/live-smoke-readiness-20260512-125740`;本地未提供完整 live smoke 凭据和真实 OCR 样票,脚本按规则跳过网页 live smoke 与真实 OCR,不影响 LP6 请假采购体验验收。下一批默认进入 ME6 移动二轮体验;R7 仍需在 LP6/ME6 至少完成一轮后另建合同。 LP6 本地验证:`leave-flow.spec.ts`、`leave-team-calendar.spec.ts`、`procurement-flow.spec.ts`、`workflow-quick-approve.spec.ts` 共 8 passed;`permission-direct-access.spec.ts -g "PA6 请假和采购自审"` 1 passed,确认请假/采购自审和 admin-only 业务审批仍由服务端拒绝;`BUSINESS_FLOW_GROUPS=leave,procurement bash scripts/qa/run-business-flow-gates.sh local` 在临时库通过,报告 `artifacts/qa-flow/business-flow-20260512-124643`;`server prisma:validate`、`node --check src/routes/leave.js`、`node --check src/routes/purchaseRequests.js`、`client typecheck/build`、`check-ui-design-rules --strict`、`git diff --check` 均通过。视觉证据输出到 `output/playwright/lp6-leave-procurement-polish/`,覆盖 PC 1440x960 请假/采购/审批与移动 390x844 请假/审批,均无横向溢出、无可见原生文件控件、无 `Choose File` 文案、无 console error。已部署到 `https://oa.hyinda.com`,公网 `/ready` 正常,public bundle `findings=0`,live smoke readiness 通过,报告 `artifacts/qa-flow/live-smoke-readiness-20260512-125740`;本地未提供完整 live smoke 凭据和真实 OCR 样票,脚本按规则跳过网页 live smoke 与真实 OCR,不影响 LP6 请假采购体验验收。下一批默认进入 ME6 移动二轮体验;R7 仍需在 LP6/ME6 至少完成一轮后另建合同。
ME6 自动化与部署收口(2026-05-12):移动二轮体验小修已完成。修复点限定在专用 `/mobile` 路由体验稳定性:移动首页对 AP6 审批效率摘要响应增加缺省容错,请假记录对旧数据缺少 `approvalHistory` 时不再渲染崩溃;历史移动视觉专项补齐 `/api/workflow/tasks/summary` mock,并将统一移动矩阵截图目录支持切换到 `output/playwright/r6-mobile-experience-second-pass/`。本地 `client typecheck`、`client build`、`check-ui-design-rules --strict` 通过;ME6 移动矩阵 7 passed,覆盖 320/360/375/390/393/414/430px、企业微信 iOS UA、企业微信 Android UA 和普通移动 UA;R5 真机反馈移动专项 2 passed,RS6 移动响应式专项 7 passed,WCM6 企业微信多手机专项 8 passed,R124 移动入口矩阵 1 passed。已部署到 `https://oa.hyinda.com`,公网 `/ready` 正常,public bundle `findings=0`,live smoke readiness PASS,报告 `artifacts/qa-flow/live-smoke-readiness-20260512-135035`;缺少完整 live smoke 凭据和真实 OCR 样票时,脚本按规则跳过网页 live smoke 与真实 OCR。公网 ME6 移动视觉矩阵 7 passed,截图输出到 `output/playwright/r6-mobile-experience-second-pass-live/`。ME6-3 真机抽查仍待用户确认:iPhone 12 iOS14 企业微信、iPhone 14 iOS16.2 企业微信、Redmi K60 企业微信的首页、审批、报销详情、我的、通知和请假;敏感截图不提交仓库。真机通过后再进入 QG6,不直接启动 R7。
## 结论 ## 结论
R70 初次试运行暂停项已在 2026-04-28 的 R71 定向修复后关闭。基于本地环境复测,经理团队日历与员工报销缺票提示/发票上传绑定提交链路均已通过,可恢复下一轮本地人工试运行。 R70 初次试运行暂停项已在 2026-04-28 的 R71 定向修复后关闭。基于本地环境复测,经理团队日历与员工报销缺票提示/发票上传绑定提交链路均已通过,可恢复下一轮本地人工试运行。
@@ -1,6 +1,6 @@
ID,Title,Description,Acceptance,Test_Method,Tools,Dev_Status,Review1_Status,Regression_Status,Files,Dependencies,Notes ID,Title,Description,Acceptance,Test_Method,Tools,Dev_Status,Review1_Status,Regression_Status,Files,Dependencies,Notes
ME6-1,移动二轮体验合同建档,在 MUD6 已完成的统一移动设计基础上启动第二轮体验巡检和小修。,"1) 本 CSV 校验通过;2) 范围限定专用 /mobile 路由;3) 不重做 MUD6;4) 不新增业务功能、不改审批链和通知通道。","流程级验证:python3 /home/shunli/.codex/skills/plan/scripts/validate_issues_csv.py issues/2026-05-12_01-41-00-r6-mobile-experience-second-pass.csv;人工核对 docs/product-design-rules.md 移动统一规则。",python3|manual,DONE,DONE,DONE,issues/2026-05-12_01-41-00-r6-mobile-experience-second-pass.csv,[],只修布局、文案、可点击性和视觉密度问题。 ME6-1,移动二轮体验合同建档,在 MUD6 已完成的统一移动设计基础上启动第二轮体验巡检和小修。,"1) 本 CSV 校验通过;2) 范围限定专用 /mobile 路由;3) 不重做 MUD6;4) 不新增业务功能、不改审批链和通知通道。","流程级验证:python3 /home/shunli/.codex/skills/plan/scripts/validate_issues_csv.py issues/2026-05-12_01-41-00-r6-mobile-experience-second-pass.csv;人工核对 docs/product-design-rules.md 移动统一规则。",python3|manual,DONE,DONE,DONE,issues/2026-05-12_01-41-00-r6-mobile-experience-second-pass.csv,[],只修布局、文案、可点击性和视觉密度问题。
ME6-2,移动路由全量巡检,覆盖首页、审批、报销列表、报销详情、我的、个人设置、通知、票据上传、请假和资产扫码。,"1) 所有专用 /mobile 路由可达;2) 无 PC 壳、无桌面表格、无横向滚动;3) 底部 Tab 和深层页返回符合规约。","流程级验证:Playwright 遍历 /mobile 路由,视口覆盖 320/360/375/390/393/414/430,输出截图到 output/playwright/r6-mobile-experience-second-pass。",playwright,TODO,TODO,TODO,client/tests/e2e|client/src/pages/Mobile*.tsx,"[""ME6-1""]",自动化不替代代表性真机抽查。 ME6-2,移动路由全量巡检,覆盖首页、审批、报销列表、报销详情、我的、个人设置、通知、票据上传、请假和资产扫码。,"1) 所有专用 /mobile 路由可达;2) 无 PC 壳、无桌面表格、无横向滚动;3) 底部 Tab 和深层页返回符合规约。","流程级验证:Playwright 遍历 /mobile 路由,视口覆盖 320/360/375/390/393/414/430,输出截图到 output/playwright/r6-mobile-experience-second-pass。",playwright,DONE,DONE,DONE,client/tests/e2e|client/src/pages/Mobile*.tsx,"[""ME6-1""]",ME6 自动矩阵 7 passed;截图输出 output/playwright/r6-mobile-experience-second-pass/;自动化不替代代表性真机抽查。
ME6-3,企业微信真机抽查闭环,用 iPhone 12 iOS14、iPhone 14 iOS16.2、Redmi K60 抽查首页、审批、报销详情、我的和通知。,"1) 图文不重叠;2) 底部审批可点;3) 报销详情吸底按钮不遮挡;4) 通知 deeplink 能回 OA。","流程级验证:人工企业微信打开海音达工作台 -> 抽查指定路径 -> 记录文字结论和敏感截图外部位置。",manual,TODO,TODO,TODO,docs/trialrun/manual-trialrun-report.md,"[""ME6-2""]",敏感截图不提交仓库。 ME6-3,企业微信真机抽查闭环,用 iPhone 12 iOS14、iPhone 14 iOS16.2、Redmi K60 抽查首页、审批、报销详情、我的和通知。,"1) 图文不重叠;2) 底部审批可点;3) 报销详情吸底按钮不遮挡;4) 通知 deeplink 能回 OA。","流程级验证:人工企业微信打开海音达工作台 -> 抽查指定路径 -> 记录文字结论和敏感截图外部位置。",manual,TODO,TODO,TODO,docs/trialrun/manual-trialrun-report.md,"[""ME6-2""]",待用户在三台代表性企业微信真机上抽查;敏感截图不提交仓库。
ME6-4,移动小修实施,按巡检结果只修影响识别和点击的问题,复用 mobile-card、槽位、safe-area 和吸底操作。,"1) 不新增业务功能;2) 不引入桌面 card-std p-4 为移动主容器;3) 不按单机型写补丁;4) 所有改动符合 docs/product-design-rules.md。","流程级验证:client typecheck/build;移动视觉矩阵复跑;人工核对修复项没有破坏 MUD6 统一设计。",npm|playwright|manual,TODO,TODO,TODO,client/src/pages/Mobile*.tsx|client/src/styles,"[""ME6-3""]",若发现业务缺口,另拆功能 Issue。 ME6-4,移动小修实施,按巡检结果只修影响识别和点击的问题,复用 mobile-card、槽位、safe-area 和吸底操作。,"1) 不新增业务功能;2) 不引入桌面 card-std p-4 为移动主容器;3) 不按单机型写补丁;4) 所有改动符合 docs/product-design-rules.md。","流程级验证:client typecheck/build;移动视觉矩阵复跑;人工核对修复项没有破坏 MUD6 统一设计。",npm|playwright|manual,DONE,DONE,DONE,client/src/pages/Mobile*.tsx|client/src/styles,"[""ME6-2""]",修复移动首页 AP6 summary 缺失容错、请假记录 approvalHistory 缺失容错,补齐历史移动专项 mock;若真机发现业务缺口,另拆功能 Issue。
ME6-5,移动二轮部署复检,部署移动小修并完成公网视觉和 live smoke。,"1) 部署成功且 /ready ok;2) public bundle findings=0;3) live smoke readiness 通过;4) 公网移动视觉专项通过。","流程级验证:bash deploy/trialrun/deploy.sh;LIVE_SMOKE_BASE_URL=https://oa.hyinda.com bash scripts/qa/run-live-smoke-readiness.sh;公网 baseURL 跑移动视觉专项。",bash|playwright,TODO,TODO,TODO,deploy/trialrun|scripts/qa|output/playwright,"[""ME6-4""]",未部署不得提示用户公网测试新改动。 ME6-5,移动二轮部署复检,部署移动小修并完成公网视觉和 live smoke。,"1) 部署成功且 /ready ok;2) public bundle findings=0;3) live smoke readiness 通过;4) 公网移动视觉专项通过。","流程级验证:bash deploy/trialrun/deploy.sh;LIVE_SMOKE_BASE_URL=https://oa.hyinda.com bash scripts/qa/run-live-smoke-readiness.sh;公网 baseURL 跑移动视觉专项。",bash|playwright,DONE,DONE,DONE,deploy/trialrun|scripts/qa|output/playwright,"[""ME6-4""]",已部署公网;/ready ok;public bundle findings=0;live readiness PASS(缺少 live smoke 凭据和 OCR 样票按规则跳过网页 live smoke/真实 OCR);公网移动矩阵 7 passed,截图 output/playwright/r6-mobile-experience-second-pass-live/。
1 ID Title Description Acceptance Test_Method Tools Dev_Status Review1_Status Regression_Status Files Dependencies Notes
2 ME6-1 移动二轮体验合同建档 在 MUD6 已完成的统一移动设计基础上启动第二轮体验巡检和小修。 1) 本 CSV 校验通过;2) 范围限定专用 /mobile 路由;3) 不重做 MUD6;4) 不新增业务功能、不改审批链和通知通道。 流程级验证:python3 /home/shunli/.codex/skills/plan/scripts/validate_issues_csv.py issues/2026-05-12_01-41-00-r6-mobile-experience-second-pass.csv;人工核对 docs/product-design-rules.md 移动统一规则。 python3|manual DONE DONE DONE issues/2026-05-12_01-41-00-r6-mobile-experience-second-pass.csv [] 只修布局、文案、可点击性和视觉密度问题。
3 ME6-2 移动路由全量巡检 覆盖首页、审批、报销列表、报销详情、我的、个人设置、通知、票据上传、请假和资产扫码。 1) 所有专用 /mobile 路由可达;2) 无 PC 壳、无桌面表格、无横向滚动;3) 底部 Tab 和深层页返回符合规约。 流程级验证:Playwright 遍历 /mobile 路由,视口覆盖 320/360/375/390/393/414/430,输出截图到 output/playwright/r6-mobile-experience-second-pass。 playwright TODO DONE TODO DONE TODO DONE client/tests/e2e|client/src/pages/Mobile*.tsx ["ME6-1"] 自动化不替代代表性真机抽查。 ME6 自动矩阵 7 passed;截图输出 output/playwright/r6-mobile-experience-second-pass/;自动化不替代代表性真机抽查。
4 ME6-3 企业微信真机抽查闭环 用 iPhone 12 iOS14、iPhone 14 iOS16.2、Redmi K60 抽查首页、审批、报销详情、我的和通知。 1) 图文不重叠;2) 底部审批可点;3) 报销详情吸底按钮不遮挡;4) 通知 deeplink 能回 OA。 流程级验证:人工企业微信打开海音达工作台 -> 抽查指定路径 -> 记录文字结论和敏感截图外部位置。 manual TODO TODO TODO docs/trialrun/manual-trialrun-report.md ["ME6-2"] 敏感截图不提交仓库。 待用户在三台代表性企业微信真机上抽查;敏感截图不提交仓库。
5 ME6-4 移动小修实施 按巡检结果只修影响识别和点击的问题,复用 mobile-card、槽位、safe-area 和吸底操作。 1) 不新增业务功能;2) 不引入桌面 card-std p-4 为移动主容器;3) 不按单机型写补丁;4) 所有改动符合 docs/product-design-rules.md。 流程级验证:client typecheck/build;移动视觉矩阵复跑;人工核对修复项没有破坏 MUD6 统一设计。 npm|playwright|manual TODO DONE TODO DONE TODO DONE client/src/pages/Mobile*.tsx|client/src/styles ["ME6-3"] ["ME6-2"] 若发现业务缺口,另拆功能 Issue。 修复移动首页 AP6 summary 缺失容错、请假记录 approvalHistory 缺失容错,补齐历史移动专项 mock;若真机发现业务缺口,另拆功能 Issue。
6 ME6-5 移动二轮部署复检 部署移动小修并完成公网视觉和 live smoke。 1) 部署成功且 /ready ok;2) public bundle findings=0;3) live smoke readiness 通过;4) 公网移动视觉专项通过。 流程级验证:bash deploy/trialrun/deploy.sh;LIVE_SMOKE_BASE_URL=https://oa.hyinda.com bash scripts/qa/run-live-smoke-readiness.sh;公网 baseURL 跑移动视觉专项。 bash|playwright TODO DONE TODO DONE TODO DONE deploy/trialrun|scripts/qa|output/playwright ["ME6-4"] 未部署不得提示用户公网测试新改动。 已部署公网;/ready ok;public bundle findings=0;live readiness PASS(缺少 live smoke 凭据和 OCR 样票按规则跳过网页 live smoke/真实 OCR);公网移动矩阵 7 passed,截图 output/playwright/r6-mobile-experience-second-pass-live/。