MIF7F-1/MIF7F-2/MIF7F-3 修复移动返回标签并勘误
这个提交包含在:
@@ -100,6 +100,10 @@ function resolveProgressTab(value: string | null | undefined): EmployeeProgressT
|
||||
return "reviewing";
|
||||
}
|
||||
|
||||
function resolveTaskTab(value: string | null | undefined): MobileTaskTab {
|
||||
return value === "completed" ? "completed" : "pending";
|
||||
}
|
||||
|
||||
function progressStatusQuery(tab: EmployeeProgressTab): EmployeeProgressQueryStatus {
|
||||
return tab === "returned" ? "rejected" : tab;
|
||||
}
|
||||
@@ -349,7 +353,7 @@ export function MobileApprovalsPage() {
|
||||
const isPushDetail = Boolean(deepLinkTaskId);
|
||||
|
||||
const [selectedTaskId, setSelectedTaskId] = useState(deepLinkTaskId);
|
||||
const [taskTab, setTaskTab] = useState<MobileTaskTab>("pending");
|
||||
const [taskTab, setTaskTab] = useState<MobileTaskTab>(() => resolveTaskTab(searchParams.get("taskTab")));
|
||||
const [progressTab, setProgressTab] = useState<EmployeeProgressTab>(() =>
|
||||
resolveProgressTab(searchParams.get("progress")),
|
||||
);
|
||||
@@ -401,6 +405,10 @@ export function MobileApprovalsPage() {
|
||||
setProgressTab(resolveProgressTab(searchParams.get("progress")));
|
||||
}, [searchParams]);
|
||||
|
||||
useEffect(() => {
|
||||
setTaskTab(resolveTaskTab(searchParams.get("taskTab")));
|
||||
}, [searchParams]);
|
||||
|
||||
useEffect(() => {
|
||||
if (taskTab === "completed") {
|
||||
setBatchMode(false);
|
||||
@@ -421,6 +429,14 @@ export function MobileApprovalsPage() {
|
||||
setSearchParams(nextParams, { replace: true });
|
||||
}
|
||||
|
||||
function handleTaskTabChange(value: string) {
|
||||
const nextTab = resolveTaskTab(value);
|
||||
setTaskTab(nextTab);
|
||||
const nextParams = new URLSearchParams(searchParams);
|
||||
nextParams.set("taskTab", nextTab);
|
||||
setSearchParams(nextParams, { replace: true });
|
||||
}
|
||||
|
||||
async function invalidateWorkflowQueries(taskId: string | null = selectedTaskId) {
|
||||
await Promise.all([
|
||||
queryClient.invalidateQueries({ queryKey: ["mobile", "workflow", "tasks"] }),
|
||||
@@ -936,7 +952,7 @@ export function MobileApprovalsPage() {
|
||||
<StatusBadge status={selectedTask.task.status} label={workflowStatusLabel(selectedTask.task.status)} />
|
||||
) : null
|
||||
}
|
||||
fallbackTo="/mobile/approvals"
|
||||
fallbackTo={`/mobile/approvals?taskTab=${taskTab}`}
|
||||
testId="mobile-approvals-detail-header"
|
||||
/>
|
||||
) : (
|
||||
@@ -1022,7 +1038,7 @@ export function MobileApprovalsPage() {
|
||||
<span className={`text-sm ${isOnline ? "text-emerald-600" : "text-orange-600"}`}>{isOnline ? "在线" : "离线"}</span>
|
||||
</div>
|
||||
|
||||
<Tabs value={taskTab} onValueChange={(value) => setTaskTab(value as MobileTaskTab)}>
|
||||
<Tabs value={taskTab} onValueChange={handleTaskTabChange}>
|
||||
<TabsList className="flex w-full justify-start gap-1">
|
||||
<TabsTrigger value="pending">待我审批</TabsTrigger>
|
||||
<TabsTrigger value="completed" disabled={!canUseRemote}>已处理</TabsTrigger>
|
||||
|
||||
@@ -130,7 +130,13 @@ export function MobileReimbursementsPage() {
|
||||
const createIntentHandledRef = useRef(false);
|
||||
const canCreateExpense = hasPermission(permissions, "expense.create");
|
||||
const canDeleteExpense = hasPermission(permissions, "expense.delete_own");
|
||||
const currentMobileSource = `${location.pathname}${location.search}`;
|
||||
const currentMobileSource = useMemo(() => {
|
||||
const params = new URLSearchParams(location.search);
|
||||
params.delete("intent");
|
||||
params.set("status", status);
|
||||
const query = params.toString();
|
||||
return `${location.pathname}${query ? `?${query}` : ""}`;
|
||||
}, [location.pathname, location.search, status]);
|
||||
|
||||
const listQuery = useQuery({
|
||||
queryKey: ["mobile", "reimbursements", status],
|
||||
@@ -156,6 +162,17 @@ export function MobileReimbursementsPage() {
|
||||
}
|
||||
}, [searchParams]);
|
||||
|
||||
function handleStatusChange(value: string) {
|
||||
if (!isReimbursementStatus(value)) {
|
||||
return;
|
||||
}
|
||||
setStatus(value);
|
||||
const nextParams = new URLSearchParams(searchParams);
|
||||
nextParams.delete("intent");
|
||||
nextParams.set("status", value);
|
||||
setSearchParams(nextParams, { replace: true });
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (searchParams.get("intent") !== "create" || !canCreateExpense || createIntentHandledRef.current) {
|
||||
return;
|
||||
@@ -222,7 +239,7 @@ export function MobileReimbursementsPage() {
|
||||
</section>
|
||||
|
||||
<section className="mobile-card" data-testid="mobile-reimbursements-list">
|
||||
<Tabs value={status} onValueChange={(value) => setStatus(value as ReimbursementStatus)}>
|
||||
<Tabs value={status} onValueChange={handleStatusChange}>
|
||||
<TabsList className="grid h-auto w-full grid-cols-4">
|
||||
{STATUS_TABS.map((item) => (
|
||||
<TabsTrigger key={item.value} value={item.value} data-testid={`mobile-reimbursements-tab-${item.value}`}>
|
||||
|
||||
@@ -240,22 +240,23 @@ function buildNotifications() {
|
||||
] satisfies MockNotification[];
|
||||
}
|
||||
|
||||
function workflowTaskSummary() {
|
||||
function workflowTaskSummary(status: "pending" | "completed" = "pending") {
|
||||
const isCompleted = status === "completed";
|
||||
return {
|
||||
id: "task-dg7",
|
||||
id: isCompleted ? "task-dg7-completed" : "task-dg7",
|
||||
instanceId: "instance-dg7",
|
||||
stepId: "step-dg7",
|
||||
stepName: "主管审批",
|
||||
assigneeId: "employee-dg7-manager",
|
||||
assigneeRole: null,
|
||||
status: "pending",
|
||||
outcome: null,
|
||||
status,
|
||||
outcome: isCompleted ? "approved" : null,
|
||||
createdAt: "2026-05-12T08:00:00.000Z",
|
||||
completedAt: null,
|
||||
completedAt: isCompleted ? "2026-05-12T09:00:00.000Z" : null,
|
||||
workflowInstance: {
|
||||
entityType: "reimbursement",
|
||||
entityId: "reimbursement-dg7",
|
||||
currentStatus: "reviewing",
|
||||
currentStatus: isCompleted ? "approved" : "reviewing",
|
||||
},
|
||||
businessSummary: {
|
||||
reference: "BX20260512001",
|
||||
@@ -263,40 +264,41 @@ function workflowTaskSummary() {
|
||||
applicantName: "测试员工",
|
||||
amount: "168.00",
|
||||
duration: null,
|
||||
businessStatus: "reviewing",
|
||||
businessStatus: isCompleted ? "approved" : "reviewing",
|
||||
},
|
||||
allowedActions: ["approve", "reject", "return", "transfer"],
|
||||
allowedActions: isCompleted ? [] : ["approve", "reject", "return", "transfer"],
|
||||
};
|
||||
}
|
||||
|
||||
function workflowTaskDetail() {
|
||||
function workflowTaskDetail(status: "pending" | "completed" = "pending") {
|
||||
const isCompleted = status === "completed";
|
||||
return {
|
||||
task: {
|
||||
id: "task-dg7",
|
||||
id: isCompleted ? "task-dg7-completed" : "task-dg7",
|
||||
instanceId: "instance-dg7",
|
||||
stepId: "step-dg7",
|
||||
stepName: "主管审批",
|
||||
assigneeId: "employee-dg7-manager",
|
||||
assigneeRole: null,
|
||||
status: "pending",
|
||||
outcome: null,
|
||||
status,
|
||||
outcome: isCompleted ? "approved" : null,
|
||||
comment: null,
|
||||
createdAt: "2026-05-12T08:00:00.000Z",
|
||||
completedAt: null,
|
||||
completedAt: isCompleted ? "2026-05-12T09:00:00.000Z" : null,
|
||||
},
|
||||
workflowInstance: {
|
||||
id: "instance-dg7",
|
||||
entityType: "reimbursement",
|
||||
currentStatus: "reviewing",
|
||||
currentStepId: "step-dg7",
|
||||
currentStatus: isCompleted ? "approved" : "reviewing",
|
||||
currentStepId: isCompleted ? null : "step-dg7",
|
||||
contextData: { standardViolations: [] },
|
||||
startedAt: "2026-05-12T08:00:00.000Z",
|
||||
completedAt: null,
|
||||
completedAt: isCompleted ? "2026-05-12T09:00:00.000Z" : null,
|
||||
},
|
||||
reimbursement: {
|
||||
id: "reimbursement-dg7",
|
||||
formNumber: "BX20260512001",
|
||||
status: "reviewing",
|
||||
status: isCompleted ? "approved" : "reviewing",
|
||||
reimbursementDate: "2026-05-12",
|
||||
totalAmount: "168.00",
|
||||
description: "差旅报销",
|
||||
@@ -306,7 +308,7 @@ function workflowTaskDetail() {
|
||||
leaveRequest: null,
|
||||
purchaseRequest: null,
|
||||
travelRequest: null,
|
||||
allowedActions: ["approve", "reject", "return", "transfer"],
|
||||
allowedActions: isCompleted ? [] : ["approve", "reject", "return", "transfer"],
|
||||
history: [],
|
||||
};
|
||||
}
|
||||
@@ -543,11 +545,12 @@ async function mockApis(page: Page, role: RolePreset, notifications: MockNotific
|
||||
}
|
||||
|
||||
if (pathName === "/api/workflow/tasks") {
|
||||
const workflowStatus = url.searchParams.get("status") === "completed" ? "completed" : "pending";
|
||||
await fulfillJson(route, {
|
||||
page: 1,
|
||||
pageSize: Number(url.searchParams.get("pageSize") ?? 100),
|
||||
total: role === "manager" ? 1 : 0,
|
||||
items: role === "manager" ? [workflowTaskSummary()] : [],
|
||||
items: role === "manager" ? [workflowTaskSummary(workflowStatus)] : [],
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -557,6 +560,11 @@ async function mockApis(page: Page, role: RolePreset, notifications: MockNotific
|
||||
return;
|
||||
}
|
||||
|
||||
if (pathName === "/api/workflow/tasks/task-dg7-completed") {
|
||||
await fulfillJson(route, workflowTaskDetail("completed"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (pathName === "/api/workflow/approvers") {
|
||||
await fulfillJson(route, { page: 1, pageSize: 100, total: 0, items: [] });
|
||||
return;
|
||||
@@ -861,6 +869,16 @@ test.describe("MIF7 移动信息架构整改", () => {
|
||||
await mobile.page.goto("/mobile/approvals");
|
||||
await expect(mobile.page.getByRole("tab", { name: "待我审批" })).toBeVisible();
|
||||
await expect(mobile.page.getByRole("tab", { name: "已处理" })).toBeVisible();
|
||||
await mobile.page.getByRole("tab", { name: "已处理" }).click();
|
||||
await expect(mobile.page).toHaveURL(/\/mobile\/approvals\?taskTab=completed$/);
|
||||
await expect(mobile.page.getByTestId("mobile-approvals-task-task-dg7-completed")).toBeVisible();
|
||||
await mobile.page.getByTestId("mobile-approvals-task-task-dg7-completed").click();
|
||||
await expect(mobile.page).toHaveURL(/\/mobile\/approvals\/task-dg7-completed$/);
|
||||
await expect(mobile.page.getByTestId("mobile-approvals-detail-header")).toBeVisible();
|
||||
await expect(mobile.page.getByTestId("mobile-bottom-nav")).toHaveCount(0);
|
||||
await mobile.page.getByTestId("mobile-approvals-detail-header").getByRole("button", { name: "返回" }).click();
|
||||
await expect(mobile.page).toHaveURL(/\/mobile\/approvals\?taskTab=completed$/);
|
||||
await expect(mobile.page.getByRole("tab", { name: "已处理" })).toHaveAttribute("data-state", "active");
|
||||
await mobile.page.screenshot({ path: screenshotPath("approver-task-tabs"), fullPage: true });
|
||||
} finally {
|
||||
await mobile.context.close();
|
||||
@@ -922,6 +940,17 @@ test.describe("MIF7 移动信息架构整改", () => {
|
||||
await mockApis(mobile.page, "employee", notifications);
|
||||
await loginForMobile(mobile.page, "employee");
|
||||
|
||||
await mobile.page.goto("/mobile/reimbursements");
|
||||
await mobile.page.getByTestId("mobile-reimbursements-tab-approved").click();
|
||||
await expect(mobile.page).toHaveURL(/\/mobile\/reimbursements\?status=approved$/);
|
||||
await mobile.page.getByTestId("mobile-reimbursement-card").first().getByRole("link", { name: "查看详情" }).click();
|
||||
await expect(mobile.page).toHaveURL(/\/mobile\/reimbursements\/reimbursement-dg7$/);
|
||||
await expect(mobile.page.getByTestId("mobile-reimbursement-detail-header")).toBeVisible();
|
||||
await expect(mobile.page.getByTestId("mobile-bottom-nav")).toHaveCount(0);
|
||||
await mobile.page.getByTestId("mobile-reimbursement-detail-header").getByRole("button", { name: "返回" }).click();
|
||||
await expect(mobile.page).toHaveURL(/\/mobile\/reimbursements\?status=approved$/);
|
||||
await expect(mobile.page.getByTestId("mobile-reimbursements-tab-approved")).toHaveAttribute("data-state", "active");
|
||||
|
||||
await mobile.page.goto("/mobile/reimbursements/reimbursement-dg7");
|
||||
await expect(mobile.page.getByTestId("mobile-reimbursement-detail-header")).toBeVisible();
|
||||
await expect(mobile.page.getByTestId("mobile-bottom-nav")).toHaveCount(0);
|
||||
|
||||
+2
-2
@@ -54,7 +54,7 @@
|
||||
| `docs/trialrun/试运行覆盖矩阵.csv` | 历史证据 | 轻量覆盖摘要 |
|
||||
| `docs/trialrun/项目级流程覆盖矩阵.csv` | QA 参考 | `check-flow-coverage` 使用 |
|
||||
| `docs/trialrun/权限直连审计记录.md` | 当前证据 | 权限直连审计 |
|
||||
| `docs/trialrun/移动信息架构视觉流程审查.md` | 当前证据 | MIA7 审查结论;MIF7 仍为 TODO |
|
||||
| `docs/trialrun/移动信息架构视觉流程审查.md` | 当前证据 | MIA7 审查结论;MIF7-1..6 本地整改已完成,MIF7-7 部署和真机复测待收口 |
|
||||
|
||||
## 技术参考
|
||||
|
||||
@@ -88,5 +88,5 @@
|
||||
## 当前门禁
|
||||
|
||||
- `ME6-3` 代表性企业微信真机门禁仍未关闭。
|
||||
- MIF7 是下一批移动信息架构整改合同,当前仍为 TODO。
|
||||
- MIF7-1..6 本地整改已完成;MIF7-7 部署、公网复检和代表性真机复测仍为 TODO。
|
||||
- R7 真实扩大试运行尚未启动。
|
||||
|
||||
+2
-2
@@ -13,12 +13,12 @@
|
||||
|
||||
## 最近事实源链
|
||||
|
||||
R6 -> PC6 -> PA6 -> FL6 -> AP6 -> RI6 -> LP6 -> ME6/QG6 -> DG7/MRF7 -> MIA7 -> MIF7(TODO)。
|
||||
R6 -> PC6 -> PA6 -> FL6 -> AP6 -> RI6 -> LP6 -> ME6/QG6 -> DG7/MRF7 -> MIA7 -> MIF7(本地整改完成,部署/真机待收口)。
|
||||
|
||||
- R6、PC6、PA6、FL6、AP6、RI6、LP6 已完成并部署。
|
||||
- ME6 自动化和公网视觉矩阵已完成;`ME6-3` 代表性企业微信真机门禁仍待确认。
|
||||
- QG6 已完成 QA 治理升级。
|
||||
- MIA7 已完成移动信息架构、视觉和流程审查;MIF7 是下一批移动整改合同,尚未实施。
|
||||
- MIA7 已完成移动信息架构、视觉和流程审查;MIF7-1..6 本地整改已完成,MIF7-7 部署、公网复检和代表性真机复测仍待收口。
|
||||
- R7/OPS7/DOC7 是准备材料,不代表已上线或已启动真实 R7。
|
||||
|
||||
## 当前事实源
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
|
||||
**文档状态**:当前操作手册,用于本地、公网、企业微信和真实 OCR 试运行验证。
|
||||
|
||||
**当前状态(2026-05-12 / DOCG7)**:R6 已通过;R7 未真实启动,R7/OPS7/DOC7 只是准备材料,不代表已启动或已上线;`ME6-3` 代表性企业微信真机门禁仍待确认;MIF7 是下一批移动信息架构整改合同,尚未完成。自动化、QG6 和公网视觉矩阵不能替代代表性真机验收。
|
||||
**当前状态(2026-05-13 / MIF7F)**:R6 已通过;R7 未真实启动,R7/OPS7/DOC7 只是准备材料,不代表已启动或已上线;`ME6-3` 代表性企业微信真机门禁仍待确认;MIF7-1..6 本地移动信息架构整改已完成,MIF7-7 部署、公网复检和代表性真机复测仍待收口。自动化、QG6 和公网视觉矩阵不能替代代表性真机验收。
|
||||
|
||||
本文用于 hyb-oa 本地、企业微信和公网试运行。默认策略是:能自动化的先自动化,自动化无法覆盖的才请求人工配合。
|
||||
|
||||
|
||||
@@ -5,4 +5,4 @@ MIF7-3,通知中心事件化,"把移动通知中心从业务卡片列表调整
|
||||
MIF7-4,统一移动详情壳和返回语义,"抽出或复用统一移动详情壳,统一报销、请假、采购、审批详情的返回位置、标题/状态、内容顺序和底部 Tab 隐藏规则。","1) 所有移动深层详情左侧返回、右侧状态且隐藏底部 Tab;2) 内容顺序统一为摘要、关键字段、材料/附件/发票、审批流、下一步动作;3) 返回优先回来源;无来源时回业务列表或进度标签;4) 请假详情不回新建表单顶部,采购详情不无条件回审批首页。","流程级验证:Playwright 从首页摘要、审批/进度、通知中心分别进入报销/请假/采购/审批详情;断言返回落点、底部 Tab 隐藏、统一返回控件和关键区块顺序。",playwright|npm,DONE,DONE,DONE,client/src/components/mobile|client/src/pages/MobileReimbursementDetailPage.tsx|client/src/pages/MobileLeavePage.tsx|client/src/pages/MobilePurchaseRequestDetailPage.tsx|client/src/pages/MobileApprovalsPage.tsx|client/src/lib/runtimeContext.ts,["MIF7-3"],若实现需要新增后端接口或改状态机,停止并拆新 Issue。
|
||||
MIF7-5,财务移动首页角色化摘要,"优化财务账号移动首页,突出财务待复核、待付款、台账/导出和风险异常入口,避免与经理首页同构。","1) 财务账号首页出现财务摘要或财务快捷入口;2) 普通员工不显示财务入口;3) 管理员单角色不默认获得财务业务能力;4) 财务首页仍使用同一移动框架。","流程级验证:Playwright 使用员工、审批人、财务、管理员账号访问 /mobile;验证财务摘要裁剪和入口权限;如直连 API 涉及权限补跑 governance 相关门禁。",playwright|npm|bash,DONE,DONE,DONE,client/src/pages/MobilePortalPage.tsx|client/src/api|client/src/auth/permissions.ts,["MIF7-4"],优先复用现有 FL6 财务接口;如必须新增后端接口则拆新 Issue。
|
||||
MIF7-6,移动 IA 自动化断言和视觉矩阵,"补充入口重复、摘要数量重复、进度标签、通知直达、详情壳统一、返回目标和财务首页差异的自动化规则与截图矩阵。","1) 新增或更新 Playwright 用例覆盖 320/360/375/390/393/414/430;2) check-ui-design-rules 保持 strict 通过;3) 输出截图到 output/playwright/mobile-ia-flow-remediation/;4) 真机门禁 ME6-3 仍不被自动化替代。","流程级验证:cd client && npm run typecheck && npm run build;node scripts/qa/check-ui-design-rules.mjs --strict;移动专项 Playwright;git diff --check。",npm|node|playwright|git,DONE,DONE,DONE,client/tests/e2e|scripts/qa/check-ui-design-rules.mjs|output/playwright,["MIF7-5"],截图产物按项目规则处理;敏感截图不提交。
|
||||
MIF7-7,本地回归部署和真机复测清单,"完成 MIF7 本地回归、受控部署、公网复检,并给出 iPhone12/iPhone14/Redmi K60 企业微信复测清单。","1) 本地 typecheck/build、设计规则和移动专项通过;2) 部署公网后 /ready、public bundle、live smoke readiness 通过;3) docs/trialrun/manual-trialrun-report.md 记录 MIF7 收口;4) ME6-3 仅在代表性真机复测通过或用户明确接受风险后关闭。","流程级验证:bash deploy/trialrun/deploy.sh;curl -fsS https://oa.hyinda.com/ready;PUBLIC_BASE_URL=https://oa.hyinda.com node scripts/qa/check-public-bundle-copy.mjs;LIVE_SMOKE_BASE_URL=https://oa.hyinda.com bash scripts/qa/run-live-smoke-readiness.sh;人工真机复测首页、进度、通知、详情返回和财务首页。",bash|curl|node|manual,TODO,TODO,TODO,deploy/trialrun|docs/trialrun/manual-trialrun-report.md,["MIF7-6"],面向用户可测改动必须部署后再提示公网/企业微信验证。
|
||||
MIF7-7,本地回归部署和真机复测清单,"完成 MIF7 本地回归、受控部署、公网复检,并给出 iPhone12/iPhone14/Redmi K60 企业微信复测清单。","1) 本地 typecheck/build、设计规则和移动专项通过;2) 部署公网后 /ready、public bundle、live smoke readiness 通过;3) docs/trialrun/人工试运行报告.md 记录 MIF7 收口;4) ME6-3 仅在代表性真机复测通过或用户明确接受风险后关闭。","流程级验证:bash deploy/trialrun/deploy.sh;curl -fsS https://oa.hyinda.com/ready;PUBLIC_BASE_URL=https://oa.hyinda.com node scripts/qa/check-public-bundle-copy.mjs;LIVE_SMOKE_BASE_URL=https://oa.hyinda.com bash scripts/qa/run-live-smoke-readiness.sh;人工真机复测首页、进度、通知、详情返回和财务首页。",bash|curl|node|manual,TODO,TODO,TODO,deploy/trialrun|docs/trialrun/人工试运行报告.md,["MIF7-6"],面向用户可测改动必须部署后再提示公网/企业微信验证。
|
||||
|
||||
|
无法渲染此文件,因为它包含了意外字符,其位于第 3 行和第 719 列。
|
@@ -0,0 +1,4 @@
|
||||
ID,Title,Description,Acceptance,Test_Method,Tools,Dev_Status,Review1_Status,Regression_Status,Files,Dependencies,Notes
|
||||
MIF7F-1,移动详情返回标签语义修复,"修复移动报销列表和审批人列表进入深层详情后的来源标签语义:报销列表 status、审批人任务 taskTab 必须进入 URL,并作为详情返回来源。","1) 报销列表切换草稿、审批中、已通过、已支付后 URL 同步 status;2) 从任一报销标签打开详情后返回仍落在同一 status 标签;3) 审批人待我审批和已处理切换后 URL 同步 taskTab;4) 从已处理审批任务打开详情后返回仍落在已处理标签。","流程级验证:Playwright 使用员工账号访问 /mobile/reimbursements,切换已通过标签并进入报销详情,点击返回后断言 URL 含 status=approved 且标签仍选中;使用审批人账号访问 /mobile/approvals,切换已处理并进入任务详情,点击返回后断言 URL 含 taskTab=completed 且已处理标签仍选中。",playwright|npm,DONE,DONE,DONE,client/src/pages/MobileReimbursementsPage.tsx|client/src/pages/MobileApprovalsPage.tsx,[],只改前端路由查询语义;不改后端 API、权限模型、workflow 状态机或 Prisma schema。
|
||||
MIF7F-2,移动 IA 自动化补断言,"扩展 MIF7 移动专项 Playwright,补齐返回标签、深层详情隐藏底部 Tab、通知直达和既有跨视口截图矩阵断言。","1) 移动专项继续覆盖 320/360/375/390/393/414/430;2) 新增报销 status 返回和审批 taskTab 返回断言;3) 首页、进度/审批、通知、报销详情、请假详情、采购详情、财务首页截图仍输出到 output/playwright/mobile-ia-flow-remediation/;4) 自动化不关闭 ME6-3 真机门禁。","流程级验证:cd client && npx playwright test tests/e2e/dg7-mobile-information-dedupe.spec.ts --workers=1 --reporter=list;同时执行 cd client && npm run typecheck;cd client && npm run build;node scripts/qa/check-ui-design-rules.mjs --strict;git diff --check。",playwright|npm|node|git,DONE,DONE,DONE,client/tests/e2e/dg7-mobile-information-dedupe.spec.ts|output/playwright/mobile-ia-flow-remediation,["MIF7F-1"],截图产物按项目规则处理,不提交敏感或临时截图。
|
||||
MIF7F-3,MIF7-7 合同勘误和部署前门禁,"修正 MIF7-7 与 DCL7 后中文文档事实源不一致的问题,并把当前状态表述为 MIF7-1..6 本地整改完成、MIF7-7 部署和真机复测待收口。","1) MIF7-7 报告路径统一为 docs/trialrun/人工试运行报告.md;2) docs/文档索引.md、docs/文档事实源状态矩阵.md 和试运行手册不再写 MIF7 整体尚未实施;3) 部署前仍不宣称公网或企业微信可测;4) ME6-3 仍保持未关闭。","流程级验证:python3 /home/shunli/.codex/skills/plan/scripts/validate_issues_csv.py issues/2026-05-12_21-48-40-mobile-ia-flow-remediation.csv;python3 /home/shunli/.codex/skills/plan/scripts/validate_issues_csv.py issues/2026-05-13_00-38-05-mif7-follow-up-return-and-release.csv;rg 'manual-trialrun-report|MIF7\\(TODO\\)|尚未实施|当前仍为 TODO|尚未完成' docs/文档索引.md docs/文档事实源状态矩阵.md docs/试运行与自动化验证手册.md issues/2026-05-12_21-48-40-mobile-ia-flow-remediation.csv 返回 1。",npm|node|git|rg,DONE,DONE,DONE,issues/2026-05-12_21-48-40-mobile-ia-flow-remediation.csv|docs/文档索引.md|docs/文档事实源状态矩阵.md|docs/试运行与自动化验证手册.md,["MIF7F-2"],受控部署和公网 live smoke 仍由 MIF7-7 执行;deploy/trialrun/deploy.sh 会改远端运行状态,执行前需确认。
|
||||
|
无法渲染此文件,因为它包含了意外字符,其位于第 3 行和第 928 列。
|
在新工单中引用
屏蔽一个用户