From 695f5c2c7554757dd1f4171763860fb10de8027e Mon Sep 17 00:00:00 2001 From: shunli Date: Sat, 30 May 2026 13:52:20 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E6=9E=84=E7=A7=BB=E5=8A=A8=E7=AB=AF?= =?UTF-8?q?=E8=BD=BB=E5=8A=A8=E4=BD=9C=E9=A1=B5=E9=9D=A2=EF=BC=8805-30-mob?= =?UTF-8?q?ile-light-action-surfaces=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../05-30-mobile-light-action-surfaces/prd.md | 10 +- .../task.json | 2 +- client/src/pages/MobileApprovalsPage.tsx | 270 +----------------- client/src/pages/MobileMePage.tsx | 2 - client/src/pages/MobilePortalPage.tsx | 34 +-- .../src/pages/MobilePurchaseRequestsPage.tsx | 53 +++- client/src/pages/MobileReimbursementsPage.tsx | 66 +++-- .../r6-mobile-unified-design-rollout.spec.ts | 9 +- 8 files changed, 138 insertions(+), 308 deletions(-) diff --git a/.trellis/tasks/05-30-mobile-light-action-surfaces/prd.md b/.trellis/tasks/05-30-mobile-light-action-surfaces/prd.md index f89d816c..6a07a296 100644 --- a/.trellis/tasks/05-30-mobile-light-action-surfaces/prd.md +++ b/.trellis/tasks/05-30-mobile-light-action-surfaces/prd.md @@ -39,10 +39,10 @@ ## Acceptance Criteria -- [ ] 移动端所有核心页面都符合焦点卡/卡片列表/底部动作的统一结构。 -- [ ] 小屏按钮文字完整可读,底部动作避开安全区和键盘遮挡。 -- [ ] 移动通知不复制待办处理列表。 -- [ ] 移动 HR/考勤不误导为 OA 日常提交入口。 +- [x] 移动端所有核心页面都符合焦点卡/卡片列表/底部动作的统一结构。 +- [x] 小屏按钮文字完整可读,底部动作避开安全区和键盘遮挡。 +- [x] 移动通知不复制待办处理列表。 +- [x] 移动 HR/考勤不误导为 OA 日常提交入口。 ## Test Plan @@ -56,3 +56,5 @@ - 这是跨多个移动页面的统一任务,实施时应分页面小步提交或至少分阶段验证截图。 - 若发现 PC 与移动共享组件冲突,优先保留移动轻动作语义,不把 PC 管理壳搬到移动端。 +- 2026-05-30 实施:移动首页请假入口改为请假记录/企微主办;移动审批去掉批量处理,只保留列表筛选和详情底部处理;移动报销/采购列表补搜索;移动我的去掉没有移动页承接的项目动态入口;移动视觉测试改为验证请假只读记录页。 +- 验证:`cd client && npm run typecheck`、`cd client && npm run build`、`cd client && npx playwright test tests/e2e/r6-mobile-unified-design-rollout.spec.ts --workers=1 --reporter=list`、`python3 ./.trellis/scripts/task.py validate 05-30-mobile-light-action-surfaces`、`node scripts/qa/check-trellis-governance.mjs`、`git diff --check`。 diff --git a/.trellis/tasks/05-30-mobile-light-action-surfaces/task.json b/.trellis/tasks/05-30-mobile-light-action-surfaces/task.json index d2975428..965438b5 100644 --- a/.trellis/tasks/05-30-mobile-light-action-surfaces/task.json +++ b/.trellis/tasks/05-30-mobile-light-action-surfaces/task.json @@ -3,7 +3,7 @@ "name": "mobile-light-action-surfaces", "title": "移动端轻动作页面统一重构", "description": "统一移动首页、移动审批、移动报销、移动采购、移动通知、移动我的的焦点卡、卡片列表和底部动作规则。", - "status": "planning", + "status": "in_progress", "dev_type": null, "scope": "frontend-page-redesign", "package": null, diff --git a/client/src/pages/MobileApprovalsPage.tsx b/client/src/pages/MobileApprovalsPage.tsx index ba219209..3b375028 100644 --- a/client/src/pages/MobileApprovalsPage.tsx +++ b/client/src/pages/MobileApprovalsPage.tsx @@ -388,9 +388,6 @@ export function MobileApprovalsPage() { const [entityFilter, setEntityFilter] = useState("all"); const [searchInput, setSearchInput] = useState(""); const deferredSearch = useDeferredValue(searchInput); - const [batchMode, setBatchMode] = useState(false); - const [selectedBatchTaskIds, setSelectedBatchTaskIds] = useState([]); - const [batchRejectComment, setBatchRejectComment] = useState(""); const [error, setError] = useState(""); const [message, setMessage] = useState(""); const [comment, setComment] = useState(""); @@ -441,14 +438,6 @@ export function MobileApprovalsPage() { setTaskTab(resolveTaskTab(searchParams.get("taskTab"))); }, [searchParams]); - useEffect(() => { - if (taskTab === "completed") { - setBatchMode(false); - setSelectedBatchTaskIds([]); - setBatchRejectComment(""); - } - }, [taskTab]); - function handleTaskSelection(taskId: string) { navigate(`/mobile/approvals/${encodeURIComponent(taskId)}`, { state: { from: currentMobileSource } }); } @@ -569,11 +558,6 @@ export function MobileApprovalsPage() { [baseTaskCards, deferredSearch, entityFilter], ); - const pendingTaskMap = useMemo( - () => new Map(filteredTaskCards.map((task) => [task.id, task])), - [filteredTaskCards], - ); - const selectedCachedTaskDetail = useMemo( () => filteredTaskCards.find((task) => task.id === selectedTaskId)?.detail ?? null, [filteredTaskCards, selectedTaskId], @@ -627,11 +611,6 @@ export function MobileApprovalsPage() { const employeeProgressError = reimbursementProgressQuery.error ?? leaveProgressQuery.error ?? purchaseProgressQuery.error ?? null; - useEffect(() => { - const visibleIds = new Set(filteredTaskCards.map((task) => task.id)); - setSelectedBatchTaskIds((current) => current.filter((taskId) => visibleIds.has(taskId))); - }, [filteredTaskCards]); - const canTransfer = Boolean( canUseRemote && selectedTaskAllowsActions && @@ -785,100 +764,6 @@ export function MobileApprovalsPage() { }, }); - const batchActionMutation = useMutation({ - mutationFn: async (input: { action: WorkflowDraftAction; taskIds: string[]; comment?: string }) => { - if (!input.taskIds.length) { - throw new Error("请先选择至少一条待办"); - } - if (input.action === "reject" && !input.comment?.trim()) { - throw new Error("批量驳回必须填写共享意见"); - } - - let succeeded = 0; - let failed = 0; - let skipped = 0; - const failures: string[] = []; - - for (const taskId of input.taskIds) { - const taskCard = pendingTaskMap.get(taskId); - if (!taskCard) { - skipped += 1; - failures.push(`${taskId}: 当前筛选结果中找不到该待办`); - continue; - } - - if (!taskCard.allowedActions.includes(input.action)) { - skipped += 1; - failures.push(`${taskId}: 当前待办不允许${input.action === "approve" ? "通过" : "驳回"}`); - continue; - } - - try { - if (canUseRemote) { - if (input.action === "approve") { - await approveWorkflowTask(taskId); - } else { - await rejectWorkflowTask(taskId, input.comment!.trim()); - } - } else { - if (!taskCard.detail) { - throw new Error("当前待办尚未缓存,请联网后重试"); - } - await enqueueWorkflowActionDraft({ - taskId, - action: input.action, - comment: input.action === "reject" ? input.comment?.trim() ?? null : null, - ownerEmployeeId, - detail: taskCard.detail, - }); - } - succeeded += 1; - } catch (batchError) { - failed += 1; - failures.push(`${taskId}: ${resolveErrorMessage(batchError)}`); - } - } - - return { ...input, succeeded, failed, skipped, failures }; - }, - onSuccess: async (result) => { - if (canUseRemote) { - await invalidateWorkflowQueries(selectedTaskId); - } else { - await refreshDraftQueueCache(); - await queryClient.invalidateQueries({ queryKey: ["mobile", "workflow", "task-cache", ownerEmployeeId] }); - } - - const actionLabel = result.action === "approve" ? "通过" : "驳回"; - const summary = canUseRemote - ? `批量${actionLabel}完成:成功 ${result.succeeded},失败 ${result.failed},跳过 ${result.skipped}` - : `离线批量${actionLabel}已入队:成功 ${result.succeeded},失败 ${result.failed},跳过 ${result.skipped}`; - - setBatchMode(false); - setSelectedBatchTaskIds([]); - setBatchRejectComment(""); - setComment(""); - setError(result.failures.length ? result.failures.slice(0, 3).join(";") : ""); - setMessage(summary); - if (canUseRemote && result.taskIds.includes(selectedTaskId)) { - navigate("/mobile/approvals", { replace: true }); - } - - if (result.failed > 0) { - toast.error(summary); - return; - } - - toast.success(summary); - }, - onError: (batchError) => { - const nextError = resolveErrorMessage(batchError); - setError(nextError); - setMessage(""); - toast.error(nextError); - }, - }); - const isActionSubmitting = actionMutation.isPending || offlineDraftSubmitting; const syncMutation = useMutation({ @@ -967,33 +852,7 @@ export function MobileApprovalsPage() { } }, [canUseRemote, retryableQueueItems, syncMutation]); - function toggleBatchTask(taskId: string) { - setSelectedBatchTaskIds((current) => - current.includes(taskId) ? current.filter((id) => id !== taskId) : [...current, taskId], - ); - } - - function toggleSelectAllVisibleTasks() { - const visibleIds = filteredTaskCards.map((task) => task.id); - const allSelected = visibleIds.length > 0 && visibleIds.every((taskId) => selectedBatchTaskIds.includes(taskId)); - if (allSelected) { - setSelectedBatchTaskIds([]); - return; - } - setSelectedBatchTaskIds(visibleIds); - } - - function handleBatchAction(action: WorkflowDraftAction) { - batchActionMutation.mutate({ - action, - taskIds: selectedBatchTaskIds, - comment: action === "reject" ? batchRejectComment : undefined, - }); - } - - const listTitle = taskTab === "pending" ? "待我审批" : "已处理"; - const allVisibleSelected = - filteredTaskCards.length > 0 && filteredTaskCards.every((task) => selectedBatchTaskIds.includes(task.id)); + const listTitle = taskTab === "pending" ? "待我处理" : "已处理"; const shouldShowDraftQueue = !isOnline || currentQueueItems.length > 0; const showMobileApprovalActions = Boolean(isPushDetail && selectedTask && selectedTaskAllowsActions); const approvalFocusText = taskTab === "pending" @@ -1004,7 +863,7 @@ export function MobileApprovalsPage() { const selectedActionText = selectedTask ? selectedTaskAllowsActions ? canUseRemote - ? "核对摘要和历史后,可在底部直接通过、驳回或退回。" + ? "核对摘要和历史后,可在底部通过、驳回或退回。" : "当前离线,仅支持保存通过或驳回草稿,联网后再同步。" : "当前任务已处理,本页只展示业务摘要和处理历史。" : ""; @@ -1033,7 +892,7 @@ export function MobileApprovalsPage() { /> ) : ( )} @@ -1127,8 +986,8 @@ export function MobileApprovalsPage() {
{currentQueueItems.length}
-
批量模式
-
{batchMode ? `已选 ${selectedBatchTaskIds.length}` : "未开启"}
+
处理方式
+
逐条核对
@@ -1161,7 +1020,7 @@ export function MobileApprovalsPage() {
- 筛选与批量 + 筛选
setEntityFilter(value as EntityFilter)}> @@ -1174,82 +1033,13 @@ export function MobileApprovalsPage() { - {taskTab === "pending" ? ( -
- - {batchMode ? ( - - ) : null} - - 当前 {filteredTaskCards.length} 条 - {batchMode ? `,已选 ${selectedBatchTaskIds.length}` : ""} - -
- ) : ( -

仅显示最近 30 天。

- )} +

+ {taskTab === "pending" ? `当前 ${filteredTaskCards.length} 条` : "仅显示最近 30 天。"} +

- {batchMode ? ( -
-
-
已选 {selectedBatchTaskIds.length} 条
-
- - -
-
-