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);
|
||||
|
||||
在新工单中引用
屏蔽一个用户