diff --git a/.trellis/spec/hyb-oa/frontend-react-vite-design.md b/.trellis/spec/hyb-oa/frontend-react-vite-design.md index d3685254..09d32f2f 100644 --- a/.trellis/spec/hyb-oa/frontend-react-vite-design.md +++ b/.trellis/spec/hyb-oa/frontend-react-vite-design.md @@ -792,10 +792,18 @@ surfaces. They are not compressed PC management consoles. and bottom safe-area primary action. - Mobile home should show compact status and today's actionable items. Do not add a full module directory or dashboard. +- Mobile home finance summaries are conditional action cards. Render them only + when the current finance/cashier user has pending payment, payment exception + or risk-review counts to handle; do not show disabled finance placeholders or + unconditional mobile `我的` finance anchors. - Mobile lists use search/status filtering, cards and one primary action. Do not use wide tables, column configuration, complex multi-column filters, full-scope management, admin configuration, batch export, bulk approval or bulk payment. +- Mobile list search that filters the currently loaded list must expose the + visible label and aria label `当前列表搜索`; do not imply global search with + labels such as `搜索报销` or `搜索采购` unless the page actually queries a + global corpus. - Mobile details use a focus card, sectioned evidence and bottom actions. Do not copy PC two-column layouts. - Mobile forms keep necessary fields, material upload, field-local validation diff --git a/.trellis/tasks/05-30-mobile-confirmed-entry-copy-followups/check.jsonl b/.trellis/tasks/05-30-mobile-confirmed-entry-copy-followups/check.jsonl new file mode 100644 index 00000000..66cae78b --- /dev/null +++ b/.trellis/tasks/05-30-mobile-confirmed-entry-copy-followups/check.jsonl @@ -0,0 +1,2 @@ +{"file":".trellis/spec/hyb-oa/frontend-react-vite-design.md","reason":"检查移动页面布局、文案、动作位置和验证要求。"} +{"file":".trellis/spec/hyb-oa/wecom-boundary.md","reason":"检查移动请假不越过企业微信主办边界。"} diff --git a/.trellis/tasks/05-30-mobile-confirmed-entry-copy-followups/implement.jsonl b/.trellis/tasks/05-30-mobile-confirmed-entry-copy-followups/implement.jsonl new file mode 100644 index 00000000..4113b55c --- /dev/null +++ b/.trellis/tasks/05-30-mobile-confirmed-entry-copy-followups/implement.jsonl @@ -0,0 +1,2 @@ +{"file":".trellis/spec/hyb-oa/frontend-react-vite-design.md","reason":"移动端统一 shell、轻动作、待我处理、财务/付款和列表搜索规则。"} +{"file":".trellis/spec/hyb-oa/wecom-boundary.md","reason":"请假和考勤由企业微信主办,移动请假保持只读和 OA 应急边界。"} diff --git a/.trellis/tasks/05-30-mobile-confirmed-entry-copy-followups/implement.md b/.trellis/tasks/05-30-mobile-confirmed-entry-copy-followups/implement.md new file mode 100644 index 00000000..f9edc332 --- /dev/null +++ b/.trellis/tasks/05-30-mobile-confirmed-entry-copy-followups/implement.md @@ -0,0 +1,21 @@ +# Implementation Notes + +## Design Decision + +移动端继续走“轻动作、少空壳、状态先行”的方向: + +- 请假:企业微信主办,OA 只读展示记录和额度;OA 应急入口只给 `leave.manage`。 +- 财务:首页不常驻展示财务模块,只有待付款或异常/风险需要处理时才出现。 +- 搜索:报销和采购的移动搜索只过滤当前已加载列表,不暗示全局搜索。 +- 审批:统一叫 `待我处理`,避免 `待办` / `待我审批` / `审批` 多套叫法。 + +## Code Surface + +- `client/src/pages/MobileLeavePage.tsx` +- `client/src/pages/MobilePortalPage.tsx` +- `client/src/pages/MobileApprovalsPage.tsx` +- `client/src/pages/MobileReimbursementsPage.tsx` +- `client/src/pages/MobilePurchaseRequestsPage.tsx` +- `client/src/pages/MobileMePage.tsx` +- `docs/产品设计规则.md` +- 相关移动端 Playwright 测试 diff --git a/.trellis/tasks/05-30-mobile-confirmed-entry-copy-followups/prd.md b/.trellis/tasks/05-30-mobile-confirmed-entry-copy-followups/prd.md new file mode 100644 index 00000000..e4f0bc6d --- /dev/null +++ b/.trellis/tasks/05-30-mobile-confirmed-entry-copy-followups/prd.md @@ -0,0 +1,68 @@ +# 移动端已确认入口与文案收敛 + +## Goal + +把前一轮已经确认但未进入 PC 导航任务的移动端规则落到代码:移动请假强化只读额度,移动财务卡只在有待处理付款/异常时出现,移动报销和采购搜索明确为当前列表搜索,移动审批统一使用 `待我处理`。 + +## Background + +- 产品面向国内、小于 10 人的科技初创公司,移动端只承接轻动作:提交、补材料、查状态、处理待办、扫码和消息直达。 +- 用户已明确:请假和考勤由企业微信主办,OA 不重复处理日常请假,只展示镜像/记录/额度和应急入口。 +- 用户已确认: + - 移动请假展示只读假期余额。 + - 移动财务卡只在存在待付款、付款异常或风险复核时显示。 + - 移动报销和移动采购搜索标为 `当前列表搜索`。 + - 移动审批统一使用 `待我处理`,不再使用 `待我审批`。 + +## Scope + +- `MobileLeavePage`:普通员工记录视角也展示只读额度摘要和明细,不暴露 OA 日常请假提交动作。 +- `MobilePortalPage`:财务摘要只在有真实待付款或异常/风险时显示;无事项时不展示空壳财务卡。 +- `MobileReimbursementsPage` / `MobilePurchaseRequestsPage`:搜索控件中文标签统一为 `当前列表搜索`。 +- `MobileApprovalsPage` / `MobilePortalPage`:移动端审批入口、tab 和空态文案统一为 `待我处理`。 +- `MobileMePage`:不再提供无条件财务锚点入口,避免无待处理事项时跳到空财务区。 +- 产品事实源中明显过时的 `待我审批` 文案同步为 `待我处理`。 + +## Out Of Scope + +- 不新增企业微信写入、请假提交或考勤动作。 +- 不新增财务付款能力,不改变付款批次、AP、审批 API 或权限模型。 +- 不调整 PC 页面。 +- 不做生产部署或公网检查。 + +## Requirements + +1. 移动请假普通员工默认保持 `请假记录` + `企业微信主办` + `只读`。 +2. 移动请假在记录视角展示可用、冻结、已用额度汇总,并列出最多 3 个假种额度;加载、错误、无额度状态要自然中文表达。 +3. 移动财务卡只有在待付款、付款异常或风险复核数量大于 0 时展示;`paymentBatchEnabled` 只影响卡片语义,不制造空壳提示。 +4. 移动财务无待处理事项时,首页不显示财务空壳卡;`#finance` 深链也不应制造空白说明区。 +5. 移动报销和采购搜索控件具有明确 `当前列表搜索` 标签,不改变现有列表筛选语义。 +6. 移动审批相关用户文案统一使用 `待我处理`。 + +## Acceptance Criteria + +- [ ] 普通员工移动请假页能看到只读额度摘要和假种额度,不看到 OA 新建/编辑/提交日常请假。 +- [ ] 财务角色在无待付款、无异常、无风险时不看到 `mobile-finance-summary`。 +- [ ] 财务角色在有待付款或异常/风险时能看到财务摘要卡。 +- [ ] 移动报销和移动采购搜索区域显示 `当前列表搜索`。 +- [ ] 移动首页和移动审批页不再出现 `待我审批`。 +- [ ] 移动我的页不展示无条件财务锚点入口,审批入口使用 `待我处理`。 +- [ ] 前端 typecheck/build 和相关 Playwright 通过。 +- [ ] Trellis governance 和 `git diff --check` 通过。 + +## Test Plan + +- `cd client && npm run typecheck` +- `cd client && npm run build` +- 本地 Vite 启动后运行聚焦 Playwright: + - `cd client && npx playwright test tests/e2e/r124-mobile-entry-matrix.spec.ts tests/e2e/dg7-mobile-information-dedupe.spec.ts --workers=1 --reporter=list` +- `python3 ./.trellis/scripts/task.py validate 05-30-mobile-confirmed-entry-copy-followups` +- `node scripts/qa/check-trellis-governance.mjs` +- `git diff --check` + +## Implementation Plan + +1. 在移动请假记录视角复用现有余额查询,抽出只读额度渲染,避免把表单视角和普通员工路径混在一起。 +2. 在移动首页计算财务待办数量,只有待付款或异常大于 0 时渲染财务卡。 +3. 调整移动报销、采购、审批相关中文文案和测试断言。 +4. 跑前端构建和移动端聚焦 Playwright,必要时补充断言。 diff --git a/.trellis/tasks/05-30-mobile-confirmed-entry-copy-followups/task.json b/.trellis/tasks/05-30-mobile-confirmed-entry-copy-followups/task.json new file mode 100644 index 00000000..25a92ff9 --- /dev/null +++ b/.trellis/tasks/05-30-mobile-confirmed-entry-copy-followups/task.json @@ -0,0 +1,26 @@ +{ + "id": "mobile-confirmed-entry-copy-followups", + "name": "mobile-confirmed-entry-copy-followups", + "title": "移动端已确认入口与文案收敛", + "description": "收口已确认的移动端入口、财务卡显示条件、请假只读额度和待我处理文案。", + "status": "in_progress", + "dev_type": null, + "scope": "frontend-mobile", + "package": null, + "priority": "P2", + "creator": "shunli", + "assignee": "shunli", + "createdAt": "2026-05-30", + "completedAt": null, + "branch": null, + "base_branch": "main", + "worktree_path": null, + "commit": null, + "pr_url": null, + "subtasks": [], + "children": [], + "parent": null, + "relatedFiles": [], + "notes": "", + "meta": {} +} diff --git a/client/src/pages/MobileApprovalsPage.tsx b/client/src/pages/MobileApprovalsPage.tsx index 3b375028..4e83e9fc 100644 --- a/client/src/pages/MobileApprovalsPage.tsx +++ b/client/src/pages/MobileApprovalsPage.tsx @@ -1006,7 +1006,7 @@ export function MobileApprovalsPage() {
- 待我审批 + 待我处理 已处理 @@ -1062,7 +1062,7 @@ export function MobileApprovalsPage() { {taskTab === "completed" ? "最近 30 天暂无符合筛选条件的已处理任务。" : !canAccessRemoteApprovals && isOnline - ? "暂无待我审批;收到企业微信审批消息后可从这里查看。" + ? "暂无待我处理;收到企业微信审批消息后可从这里查看。" : canUseRemote ? "暂无符合筛选条件的审批任务。" : "暂无符合筛选条件的已缓存审批。"} diff --git a/client/src/pages/MobileLeavePage.tsx b/client/src/pages/MobileLeavePage.tsx index bab5a649..265c8a5e 100644 --- a/client/src/pages/MobileLeavePage.tsx +++ b/client/src/pages/MobileLeavePage.tsx @@ -13,6 +13,7 @@ import { type LeaveRequestItem, type LeaveRequestStatus, type LeaveSession, + type LeaveBalanceItem, } from "../api/leave"; import { useAuth } from "../auth/AuthContext"; import { hasPermission } from "../auth/permissions"; @@ -156,6 +157,78 @@ function LeaveRecordCard({ ); } +function MobileLeaveBalances({ + balances, + isLoading, + isError, + errorMessage, + summary, +}: { + balances: LeaveBalanceItem[]; + isLoading: boolean; + isError: boolean; + errorMessage: string; + summary: { available: number; reserved: number; used: number }; +}) { + return ( +
+
+
+

假期额度

+

企业微信主办,OA 只读展示同步额度

+
+ 只读 +
+ +
+
+
可用
+
{summary.available.toFixed(1)}
+
+
+
冻结
+
{summary.reserved.toFixed(1)}
+
+
+
已用
+
{summary.used.toFixed(1)}
+
+
+ + {isLoading ?
额度加载中...
: null} + {isError ?
{errorMessage}
: null} + {!isLoading && !isError && !balances.length ?
暂无可展示额度
: null} + + {balances.length ? ( +
+ {balances.slice(0, 3).map((item) => ( +
+
+
{item.leaveType.name}
+ 同步 +
+
+
+
可用
+
{item.available}
+
+
+
冻结
+
{item.reserved}
+
+
+
已用
+
{item.used}
+
+
+
+ ))} +
+ ) : null} +
+ ); +} + export function MobileLeavePage() { const queryClient = useQueryClient(); const location = useLocation(); @@ -412,25 +485,16 @@ export function MobileLeavePage() { + + {effectiveView === "form" ? ( <> -
-
-
-
可用
-
{balanceSummary.available.toFixed(1)}
-
-
-
冻结
-
{balanceSummary.reserved.toFixed(1)}
-
-
-
已用
-
{balanceSummary.used.toFixed(1)}
-
-
-
-

{editingItem ? "编辑 OA 应急请假" : "新建 OA 应急请假"}

@@ -533,35 +597,6 @@ export function MobileLeavePage() {
-
-

额度

-
- {balancesQuery.isLoading ?
加载中...
: null} - {!balancesQuery.isLoading && !balances.length ?
暂无额度
: null} - {balances.slice(0, 3).map((item) => ( -
-
-
{item.leaveType.name}
- 启用 -
-
-
-
可用
-
{item.available}
-
-
-
冻结
-
{item.reserved}
-
-
-
已用
-
{item.used}
-
-
-
- ))} -
-
) : null} diff --git a/client/src/pages/MobileMePage.tsx b/client/src/pages/MobileMePage.tsx index ddc8e8db..43d7e185 100644 --- a/client/src/pages/MobileMePage.tsx +++ b/client/src/pages/MobileMePage.tsx @@ -6,7 +6,7 @@ import { fetchEmployeeProfile } from "../api/employees"; import { mapApiErrorMessage } from "../api/errorMessages"; import { isApiError } from "../api/types"; import { useAuth } from "../auth/AuthContext"; -import { hasAnyPermission, hasBusinessApproverRole, hasFinanceRole } from "../auth/permissions"; +import { hasAnyPermission, hasBusinessApproverRole } from "../auth/permissions"; import { EmployeeAvatar } from "../components/common/EmployeeAvatar"; import { PageHeader } from "../components/common/PageHeader"; import { MobileIcon } from "../components/mobile/MobileIcon"; @@ -56,7 +56,6 @@ export function MobileMePage() { const canAccessAssets = hasAnyPermission(permissions, ["asset.view_own", "asset.view_all", "asset.manage", "asset.transfer", "asset.inventory"]); const canAccessPurchase = hasAnyPermission(permissions, [...MOBILE_PURCHASE_PERMISSIONS]); const canAccessApprovals = hasBusinessApproverRole(me?.roleCodes, permissions); - const canAccessFinance = hasFinanceRole(me?.roleCodes, permissions); const profileQuery = useQuery({ enabled: Boolean(me?.id), @@ -116,8 +115,7 @@ export function MobileMePage() { {canAccessPurchase ? : null} - - {canAccessFinance ? : null} + {canAccessAssets ? : null}
diff --git a/client/src/pages/MobilePortalPage.tsx b/client/src/pages/MobilePortalPage.tsx index 96ab2d50..c940aa66 100644 --- a/client/src/pages/MobilePortalPage.tsx +++ b/client/src/pages/MobilePortalPage.tsx @@ -182,6 +182,10 @@ export function MobilePortalPage() { .slice(0, 3); const financeSummary = portalHomeQuery.data?.financeSummary ?? null; const paymentBatchEnabled = Boolean(financeSummary?.paymentBatchEnabled); + const financePendingCount = financeSummary?.pendingBatchCount ?? 0; + const financeExceptionCount = (financeSummary?.failedItemCount ?? 0) + (financeSummary?.riskCount ?? 0); + const shouldShowFinanceCard = canUseFinance && !portalHomeQuery.isLoading && !portalHomeQuery.isError && (financePendingCount > 0 || financeExceptionCount > 0); + const financeCardBadge = paymentBatchEnabled ? "复核与付款" : financePendingCount > 0 ? "待付款" : "异常与复核"; async function openHomeNotification(item: NotificationItem) { const targetLink = resolveMobileNotificationTargetLink(item); @@ -261,7 +265,7 @@ export function MobilePortalPage() {
待处理
-
待我审批
+
待我处理
@@ -305,42 +309,26 @@ export function MobilePortalPage() { ) : null} - {canUseFinance ? ( + {shouldShowFinanceCard ? (
财务
- {paymentBatchEnabled ? "复核与付款" : "审批与台账"} + {financeCardBadge}
- {portalHomeQuery.isLoading ?

读取中...

: null} - {portalHomeQuery.isError ? ( -

- {resolveErrorMessage(portalHomeQuery.error)} -

- ) : null} - {!portalHomeQuery.isLoading && !portalHomeQuery.isError && paymentBatchEnabled ? ( -
-
-
待付款
-
{financeSummary?.pendingBatchCount ?? 0}
-
-
-
异常
-
- {(financeSummary?.failedItemCount ?? 0) + (financeSummary?.riskCount ?? 0)} -
-
-
-
电脑端
-
处理台账
-
+
+
+
待付款
+
{financePendingCount}
- ) : null} - {!portalHomeQuery.isLoading && !portalHomeQuery.isError && !paymentBatchEnabled ? ( -
-
付款批次未启用
-
可查看已流转到财务审批的报销单,待付款和异常批次暂不展示。
+
+
异常
+
{financeExceptionCount}
- ) : null} +
+
处理
+
电脑端
+
+
) : null} diff --git a/client/src/pages/MobilePurchaseRequestsPage.tsx b/client/src/pages/MobilePurchaseRequestsPage.tsx index 7c629383..de3deda4 100644 --- a/client/src/pages/MobilePurchaseRequestsPage.tsx +++ b/client/src/pages/MobilePurchaseRequestsPage.tsx @@ -193,8 +193,12 @@ export function MobilePurchaseRequestsPage() {
+ setSearchInput(event.target.value)} placeholder="搜索编号、标题、供应商" diff --git a/client/src/pages/MobileReimbursementsPage.tsx b/client/src/pages/MobileReimbursementsPage.tsx index 0c7d9ec3..4e1050aa 100644 --- a/client/src/pages/MobileReimbursementsPage.tsx +++ b/client/src/pages/MobileReimbursementsPage.tsx @@ -293,8 +293,12 @@ export function MobileReimbursementsPage() {
+ setSearchInput(event.target.value)} placeholder="搜索编号、说明、金额" diff --git a/client/tests/e2e/dg7-mobile-information-dedupe.spec.ts b/client/tests/e2e/dg7-mobile-information-dedupe.spec.ts index 4f01355c..22b5729d 100644 --- a/client/tests/e2e/dg7-mobile-information-dedupe.spec.ts +++ b/client/tests/e2e/dg7-mobile-information-dedupe.spec.ts @@ -918,10 +918,14 @@ test.describe("MIF7 移动信息架构整改", () => { await mobile.page.goto("/mobile/leave?view=records&status=approved"); await expect(mobile.page.getByTestId("mobile-leave-view-records")).toHaveAttribute("data-state", "active"); await expect(mobile.page.getByTestId("mobile-leave-record-card")).toBeVisible(); + await expect(mobile.page.getByTestId("mobile-leave-balances")).toBeVisible(); + await expect(mobile.page.getByTestId("mobile-leave-balances")).toContainText("企业微信主办"); + await expect(mobile.page.getByTestId("mobile-leave-balance-summary")).toContainText("4.0"); await expect(mobile.page.getByTestId("mobile-leave-form")).toHaveCount(0); await mobile.page.goto("/mobile/purchase-requests?status=approved"); await expect(mobile.page.getByTestId("mobile-purchase-requests-list")).toBeVisible(); + await expect(mobile.page.getByLabel("当前列表搜索")).toBeVisible(); await expect(mobile.page.getByTestId("mobile-purchase-request-card")).toBeVisible(); await expect(mobile.page.getByTestId("mobile-purchase-tab-approved")).toHaveAttribute("data-state", "active"); } finally { @@ -930,7 +934,7 @@ test.describe("MIF7 移动信息架构整改", () => { } }); - test("审批人首页只保留轻待办摘要,审批页按待我审批和已处理分组", async ({ browser }) => { + test("审批人首页只保留轻待办摘要,审批页按待我处理和已处理分组", async ({ browser }) => { const notifications = buildNotifications(); const mobile = await newMobilePage(browser, 390); try { @@ -986,7 +990,7 @@ test.describe("MIF7 移动信息架构整改", () => { await mobile.page.getByTestId("mobile-approvals-detail-header").getByRole("button", { name: "返回" }).click(); await expect(mobile.page).toHaveURL(/\/mobile$/); await mobile.page.goto("/mobile/approvals"); - await expect(mobile.page.getByRole("tab", { name: "待我审批" })).toBeVisible(); + await expect(mobile.page.getByRole("tab", { name: "待我处理" })).toBeVisible(); await expect(mobile.page.getByRole("tab", { name: "已处理" })).toBeVisible(); await expect(mobile.page.getByTestId("mobile-approvals-queue")).toHaveCount(0); await mobile.page.getByRole("tab", { name: "已处理" }).click(); @@ -1061,6 +1065,7 @@ test.describe("MIF7 移动信息架构整改", () => { await loginForMobile(mobile.page, "employee"); await mobile.page.goto("/mobile/reimbursements"); + await expect(mobile.page.getByLabel("当前列表搜索")).toBeVisible(); await mobile.page.getByTestId("mobile-reimbursements-tab-approved").click(); await expect(mobile.page).toHaveURL(/\/mobile\/reimbursements\?status=approved$/); await mobile.page.evaluate(() => { @@ -1116,16 +1121,18 @@ test.describe("MIF7 移动信息架构整改", () => { await loginForMobile(financeMobile.page, "finance"); await financeMobile.page.goto("/mobile"); await expect(financeMobile.page.getByTestId("mobile-finance-summary")).toBeVisible(); - await expect(financeMobile.page.getByTestId("mobile-finance-summary")).toContainText("付款批次未启用"); - await expect(financeMobile.page.getByTestId("mobile-finance-disabled-state")).toBeVisible(); + await expect(financeMobile.page.getByTestId("mobile-finance-summary")).toContainText("待付款"); + await expect(financeMobile.page.getByTestId("mobile-finance-summary")).toContainText("异常"); + await expect(financeMobile.page.getByTestId("mobile-finance-summary")).toContainText("4"); + await expect(financeMobile.page.getByTestId("mobile-finance-disabled-state")).toHaveCount(0); await expectNoMobileShellRegression(financeMobile.page); await financeMobile.page.screenshot({ path: screenshotPath("home-finance-summary"), fullPage: true }); await financeMobile.page.goto("/mobile/me"); - await expect(financeMobile.page.getByRole("link", { name: /财务/ })).toBeVisible(); - await financeMobile.page.getByRole("link", { name: /财务/ }).click(); + await expect(financeMobile.page.getByRole("link", { name: /财务/ })).toHaveCount(0); + await financeMobile.page.goto("/mobile#finance"); await expect(financeMobile.page).toHaveURL(/\/mobile#finance$/); await expect(financeMobile.page.getByTestId("mobile-finance-summary")).toBeInViewport(); - await expect(financeMobile.page.getByTestId("mobile-finance-disabled-state")).toBeInViewport(); + await expect(financeMobile.page.getByTestId("mobile-finance-disabled-state")).toHaveCount(0); await expectNoMobileShellRegression(financeMobile.page); } finally { await financeMobile.context.close(); diff --git a/client/tests/e2e/r124-mobile-entry-matrix.spec.ts b/client/tests/e2e/r124-mobile-entry-matrix.spec.ts index 798d413b..c98a4c0b 100644 --- a/client/tests/e2e/r124-mobile-entry-matrix.spec.ts +++ b/client/tests/e2e/r124-mobile-entry-matrix.spec.ts @@ -493,11 +493,13 @@ test.describe("R124 企业微信移动入口矩阵", () => { await mobile.page.goto("/mobile"); await expect(mobile.page.getByTestId("mobile-nav-home")).toBeVisible(); + await expect(mobile.page.getByTestId("mobile-finance-summary")).toHaveCount(0); await expectMobilePageContract(mobile.page); await mobile.page.getByTestId("mobile-nav-reimbursements").click(); await expect(mobile.page).toHaveURL(/\/mobile\/reimbursements$/); await expect(mobile.page.getByTestId("mobile-reimbursements-list")).toBeVisible(); + await expect(mobile.page.getByLabel("当前列表搜索")).toBeVisible(); await expectMobilePageContract(mobile.page); await mobile.page.goto("/mobile/capture"); @@ -524,7 +526,7 @@ test.describe("R124 企业微信移动入口矩阵", () => { await mobile.page.getByTestId("mobile-nav-approvals").click(); await expect(mobile.page).toHaveURL(/\/mobile\/approvals$/); - await expect(mobile.page.getByRole("heading", { name: "待办", exact: true })).toBeVisible(); + await expect(mobile.page.getByRole("heading", { level: 1, name: "待我处理", exact: true })).toBeVisible(); await expect(mobile.page.locator(".mobile-status-slot--approval").first()).toContainText("待处理"); await expectMobilePageContract(mobile.page); @@ -539,11 +541,9 @@ test.describe("R124 企业微信移动入口矩阵", () => { await mobile.page.screenshot({ path: screenshotPath("mobile-me") }); await expectMobilePageContract(mobile.page); - await expect(mobile.page.getByRole("link", { name: /财务/ })).toBeVisible(); - await mobile.page.getByRole("link", { name: /财务/ }).click(); - await expect(mobile.page).toHaveURL(/\/mobile#finance$/); - await expect(mobile.page.getByTestId("mobile-finance-summary")).toBeInViewport(); - await expect(mobile.page.getByTestId("mobile-finance-disabled-state")).toBeInViewport(); + await expect(mobile.page.getByRole("link", { name: /财务/ })).toHaveCount(0); + await mobile.page.goto("/mobile#finance"); + await expect(mobile.page.getByTestId("mobile-finance-summary")).toHaveCount(0); await expectMobilePageContract(mobile.page); await mobile.page.goto("/mobile/me"); @@ -572,12 +572,16 @@ test.describe("R124 企业微信移动入口矩阵", () => { await mobile.page.getByRole("link", { name: /请假/ }).click(); await expect(mobile.page).toHaveURL(/\/mobile\/leave$/); await expect(mobile.page.getByTestId("mobile-leave-records")).toBeVisible(); + await expect(mobile.page.getByTestId("mobile-leave-balances")).toBeVisible(); + await expect(mobile.page.getByTestId("mobile-leave-balances")).toContainText("企业微信主办"); + await expect(mobile.page.getByTestId("mobile-leave-balance-summary")).toContainText("4.0"); await expectMobilePageContract(mobile.page); await mobile.page.goto("/mobile/me"); await mobile.page.getByRole("link", { name: /报销/ }).click(); await expect(mobile.page).toHaveURL(/\/mobile\/reimbursements$/); await expect(mobile.page.getByTestId("mobile-reimbursements-list")).toBeVisible(); + await expect(mobile.page.getByLabel("当前列表搜索")).toBeVisible(); const draftCard = mobile.page.getByTestId("mobile-reimbursement-card").filter({ hasText: "R124 删除草稿" }).first(); await expect(draftCard).toBeVisible(); await draftCard.locator("summary").click(); diff --git a/docs/产品设计规则.md b/docs/产品设计规则.md index a194d649..caa365f6 100644 --- a/docs/产品设计规则.md +++ b/docs/产品设计规则.md @@ -119,7 +119,7 @@ A 类页面优先保证真实试运行路径顺畅;B 类页面优先保证台 - 首页不使用常驻欢迎大卡片、说明型折叠区或入口集合。首屏应使用紧凑状态摘要、可行动事项列表和条件出现的 `团队卡点` / `管理提醒` / `我的进度`,没有真实数据的区域不显示。 - `报销进度`、团队公告、试运行观测等不作为首页固定按钮或固定模块;只有存在可靠状态、真实阻塞或异常时才出现。 - 首页不承担常驻新手教学职责;首次欢迎提示只做一次性、可关闭的常用业务入口,最多 3 个,不放个人设置、通知中心或“了解流程”这类说明型入口。 -- 审批入口同时服务审批人和发起人:审批人处理待我审批,员工查看我发起的进度。 +- 审批入口同时服务审批人和发起人:审批人处理待我处理,员工查看我发起的进度。 - 普通员工不应看到不可点或无权限的审批入口;审批、财务、负责人和管理员只能看到服务端允许的动作。 - 个人设置默认只放个人资料、密码/安全、通知偏好和企业微信绑定;组织归属、直属汇报和角色摘要可折叠展示,不默认暴露权限 code、role code、原始账号状态或完整员工档案维护入口。 - 个人设置和管理配置彻底分离:组织、角色、审批规则、功能开关、主数据、审计日志、人员维护和系统配置统一进入 `管理`,不出现在普通员工路径或个人资料页里。