diff --git a/backend/app/api/mini_menu.py b/backend/app/api/mini_menu.py
index 67afe6fc..dffbf357 100644
--- a/backend/app/api/mini_menu.py
+++ b/backend/app/api/mini_menu.py
@@ -93,6 +93,16 @@ def _mobile_menu_icon(path: str) -> str:
p = (path or "").lower()
if "oa/start" in p:
return "approve"
+ if "oa/drafts" in p:
+ return "oa-drafts"
+ if "oa/pending" in p:
+ return "oa-pending"
+ if "oa/done" in p:
+ return "oa-done"
+ if "oa/mine" in p:
+ return "oa-mine"
+ if "oa/cc" in p:
+ return "oa-cc"
if "form-sink" in p:
return "orders-o"
return "apps-o"
@@ -101,8 +111,19 @@ def _mobile_menu_icon(path: str) -> str:
def _mobile_menu_icon_colors(path: str) -> dict:
"""宫格图标底色/图形色(可按入口自定义,不强制品牌绿)。"""
p = (path or "").lower()
- if "oa/start" in p:
- return {"iconBg": "#ffe4e6", "iconFg": "#e11d48"}
+ # OA 菜单 PNG 自带颜色,宫格不套色底
+ if any(
+ x in p
+ for x in (
+ "oa/start",
+ "oa/drafts",
+ "oa/pending",
+ "oa/done",
+ "oa/mine",
+ "oa/cc",
+ )
+ ):
+ return {"iconBg": "transparent", "iconFg": ""}
if "form-sink" in p:
return {"iconBg": "#eef2f1", "iconFg": "#3d524a"}
return {"iconBg": "#eef2f1", "iconFg": "#3d524a"}
diff --git a/backend/app/core/config.py b/backend/app/core/config.py
index a5aac3ea..c1d3c541 100644
--- a/backend/app/core/config.py
+++ b/backend/app/core/config.py
@@ -18,7 +18,7 @@ class Settings(BaseSettings):
# JWT配置
SECRET_KEY: str = "your-secret-key-change-in-production-please-use-random-string"
ALGORITHM: str = "HS256"
- ACCESS_TOKEN_EXPIRE_MINUTES: int = 30 * 24 # 30天
+ ACCESS_TOKEN_EXPIRE_MINUTES: int = 30 * 24 * 60 # 30天
# CORS配置
BACKEND_CORS_ORIGINS: list = [
diff --git a/backend/graphify-out/.graphify_extract.json b/backend/graphify-out/.graphify_extract.json
index d0311846..4e50031b 100644
--- a/backend/graphify-out/.graphify_extract.json
+++ b/backend/graphify-out/.graphify_extract.json
@@ -25646,7 +25646,7 @@
"label": "list_my_mobile_menus()",
"file_type": "code",
"source_file": "api/mini_menu.py",
- "source_location": "L112"
+ "source_location": "L113"
},
{
"id": "api_mini_menu_rationale_1",
@@ -103624,7 +103624,7 @@
"relation": "contains",
"confidence": "EXTRACTED",
"source_file": "api/mini_menu.py",
- "source_location": "L112",
+ "source_location": "L113",
"weight": 1.0
},
{
@@ -103633,7 +103633,7 @@
"relation": "calls",
"confidence": "EXTRACTED",
"source_file": "api/mini_menu.py",
- "source_location": "L114",
+ "source_location": "L115",
"weight": 1.0
},
{
@@ -103642,7 +103642,7 @@
"relation": "calls",
"confidence": "EXTRACTED",
"source_file": "api/mini_menu.py",
- "source_location": "L122",
+ "source_location": "L123",
"weight": 1.0
},
{
@@ -103651,7 +103651,7 @@
"relation": "calls",
"confidence": "EXTRACTED",
"source_file": "api/mini_menu.py",
- "source_location": "L126",
+ "source_location": "L127",
"weight": 1.0
},
{
@@ -103660,7 +103660,7 @@
"relation": "calls",
"confidence": "EXTRACTED",
"source_file": "api/mini_menu.py",
- "source_location": "L128",
+ "source_location": "L129",
"weight": 1.0
},
{
@@ -103669,7 +103669,7 @@
"relation": "calls",
"confidence": "EXTRACTED",
"source_file": "api/mini_menu.py",
- "source_location": "L139",
+ "source_location": "L140",
"weight": 1.0
},
{
@@ -103678,7 +103678,7 @@
"relation": "calls",
"confidence": "EXTRACTED",
"source_file": "api/mini_menu.py",
- "source_location": "L145",
+ "source_location": "L146",
"weight": 1.0
},
{
@@ -290562,7 +290562,7 @@
"confidence": "INFERRED",
"confidence_score": 0.8,
"source_file": "api/mini_menu.py",
- "source_location": "L116",
+ "source_location": "L117",
"weight": 1.0
},
{
@@ -290572,7 +290572,7 @@
"confidence": "INFERRED",
"confidence_score": 0.8,
"source_file": "api/mini_menu.py",
- "source_location": "L118",
+ "source_location": "L119",
"weight": 1.0
},
{
@@ -290582,7 +290582,7 @@
"confidence": "INFERRED",
"confidence_score": 0.8,
"source_file": "api/mini_menu.py",
- "source_location": "L118",
+ "source_location": "L119",
"weight": 1.0
},
{
@@ -290592,7 +290592,7 @@
"confidence": "INFERRED",
"confidence_score": 0.8,
"source_file": "api/mini_menu.py",
- "source_location": "L124",
+ "source_location": "L125",
"weight": 1.0
},
{
@@ -290602,7 +290602,7 @@
"confidence": "INFERRED",
"confidence_score": 0.8,
"source_file": "api/mini_menu.py",
- "source_location": "L127",
+ "source_location": "L128",
"weight": 1.0
},
{
diff --git a/backend/graphify-out/GRAPH_REPORT.md b/backend/graphify-out/GRAPH_REPORT.md
index be6fe2f6..125d884b 100644
--- a/backend/graphify-out/GRAPH_REPORT.md
+++ b/backend/graphify-out/GRAPH_REPORT.md
@@ -1,7 +1,7 @@
-# Graph Report - app (2026-08-11)
+# Graph Report - app (2026-08-12)
## Corpus Check
-- Large corpus: 250 files · ~303,240 words. Semantic extraction will be expensive (many Claude tokens). Consider running on a subfolder, or use --no-semantic to run AST-only.
+- Large corpus: 250 files · ~303,244 words. Semantic extraction will be expensive (many Claude tokens). Consider running on a subfolder, or use --no-semantic to run AST-only.
## Summary
- 5238 nodes · 28666 edges · 43 communities detected
diff --git a/backend/graphify-out/graph.json b/backend/graphify-out/graph.json
index 5909bdca..2e7e3ec1 100644
--- a/backend/graphify-out/graph.json
+++ b/backend/graphify-out/graph.json
@@ -32533,7 +32533,7 @@
"label": "list_my_mobile_menus()",
"file_type": "code",
"source_file": "api/mini_menu.py",
- "source_location": "L112",
+ "source_location": "L113",
"id": "api_mini_menu_list_my_mobile_menus",
"community": 10,
"norm_label": "list_my_mobile_menus()"
@@ -58348,7 +58348,7 @@
"confidence": "INFERRED",
"confidence_score": 0.8,
"source_file": "api/mini_menu.py",
- "source_location": "L124",
+ "source_location": "L125",
"weight": 1.0,
"_src": "api_mini_menu_list_my_mobile_menus",
"_tgt": "core_response_success_response",
@@ -67204,7 +67204,7 @@
"confidence": "INFERRED",
"confidence_score": 0.8,
"source_file": "api/mini_menu.py",
- "source_location": "L116",
+ "source_location": "L117",
"weight": 1.0,
"_src": "api_mini_menu_list_my_mobile_menus",
"_tgt": "core_response_error_response",
@@ -144364,7 +144364,7 @@
"confidence": "INFERRED",
"confidence_score": 0.8,
"source_file": "api/mini_menu.py",
- "source_location": "L118",
+ "source_location": "L119",
"weight": 1.0,
"_src": "api_mini_menu_list_my_mobile_menus",
"_tgt": "str",
@@ -340912,7 +340912,7 @@
"confidence": "INFERRED",
"confidence_score": 0.8,
"source_file": "api/mini_menu.py",
- "source_location": "L118",
+ "source_location": "L119",
"weight": 1.0,
"_src": "api_mini_menu_list_my_mobile_menus",
"_tgt": "crud_base_crudbase_get",
@@ -355611,7 +355611,7 @@
"relation": "contains",
"confidence": "EXTRACTED",
"source_file": "api/mini_menu.py",
- "source_location": "L112",
+ "source_location": "L113",
"weight": 1.0,
"_src": "api_mini_menu_py",
"_tgt": "api_mini_menu_list_my_mobile_menus",
@@ -355635,7 +355635,7 @@
"relation": "calls",
"confidence": "EXTRACTED",
"source_file": "api/mini_menu.py",
- "source_location": "L114",
+ "source_location": "L115",
"weight": 1.0,
"_src": "api_mini_menu_list_my_mobile_menus",
"_tgt": "api_mini_menu_current_user",
@@ -355647,7 +355647,7 @@
"relation": "calls",
"confidence": "EXTRACTED",
"source_file": "api/mini_menu.py",
- "source_location": "L122",
+ "source_location": "L123",
"weight": 1.0,
"_src": "api_mini_menu_list_my_mobile_menus",
"_tgt": "api_mini_menu_find_mobile_root",
@@ -355659,7 +355659,7 @@
"relation": "calls",
"confidence": "EXTRACTED",
"source_file": "api/mini_menu.py",
- "source_location": "L126",
+ "source_location": "L127",
"weight": 1.0,
"_src": "api_mini_menu_list_my_mobile_menus",
"_tgt": "api_mini_menu_collect_descendants",
@@ -355671,7 +355671,7 @@
"relation": "calls",
"confidence": "EXTRACTED",
"source_file": "api/mini_menu.py",
- "source_location": "L128",
+ "source_location": "L129",
"weight": 1.0,
"_src": "api_mini_menu_list_my_mobile_menus",
"_tgt": "api_mini_menu_user_menu_ids",
@@ -355695,7 +355695,7 @@
"relation": "calls",
"confidence": "EXTRACTED",
"source_file": "api/mini_menu.py",
- "source_location": "L145",
+ "source_location": "L146",
"weight": 1.0,
"_src": "api_mini_menu_list_my_mobile_menus",
"_tgt": "api_mini_menu_mobile_menu_icon",
@@ -355719,7 +355719,7 @@
"relation": "calls",
"confidence": "EXTRACTED",
"source_file": "api/mini_menu.py",
- "source_location": "L139",
+ "source_location": "L140",
"weight": 1.0,
"_src": "api_mini_menu_list_my_mobile_menus",
"_tgt": "api_mini_menu_mobile_menu_icon_colors",
@@ -355744,7 +355744,7 @@
"confidence": "INFERRED",
"confidence_score": 0.8,
"source_file": "api/mini_menu.py",
- "source_location": "L127",
+ "source_location": "L128",
"weight": 1.0,
"_src": "api_mini_menu_list_my_mobile_menus",
"_tgt": "services_web_auth_roles_resolve_web_role_for_user",
diff --git a/backend/graphify-out/manifest.json b/backend/graphify-out/manifest.json
index 629828cf..13ca7573 100644
--- a/backend/graphify-out/manifest.json
+++ b/backend/graphify-out/manifest.json
@@ -184,7 +184,7 @@
"/Users/wuzilong/work/00code/zkxw/backend/app/api/public.py": 1786374022.5691514,
"/Users/wuzilong/work/00code/zkxw/backend/app/api/material.py": 1782894693.6582327,
"/Users/wuzilong/work/00code/zkxw/backend/app/api/production_material.py": 1782404982.2474263,
- "/Users/wuzilong/work/00code/zkxw/backend/app/api/mini_menu.py": 1786438411.2164767,
+ "/Users/wuzilong/work/00code/zkxw/backend/app/api/mini_menu.py": 1786512705.799277,
"/Users/wuzilong/work/00code/zkxw/backend/app/api/qc.py": 1785207187.402777,
"/Users/wuzilong/work/00code/zkxw/backend/app/api/market.py": 1781522283.6823432,
"/Users/wuzilong/work/00code/zkxw/backend/app/api/sample_production_reception.py": 1782718998.90251,
diff --git a/docs/oa-start-footer-btn-style-demo.html b/docs/oa-start-footer-btn-style-demo.html
new file mode 100644
index 00000000..d99f7cac
--- /dev/null
+++ b/docs/oa-start-footer-btn-style-demo.html
@@ -0,0 +1,558 @@
+
+
+
+
+
+ 发起审批 · 底栏按钮重设计 Demo
+
+
+
+
+
底栏按钮 · 重新设计
+
+ 上一版问题:草稿与提交都抢绿色、圆角/描边千篇一律、缩略图看不出层级。
+ 这一版只保留 8 个差异化方向,右侧大屏实时预览;核心原则是
+ 草稿弱、提交强,避免双绿对撞。
+
+
+ 点左侧方案切换右侧手机。选好后回复编号,例如「用 S1」。
+ 取消是否保留也可一并说明。
+
+
+
+
+
+
+
+
diff --git a/docs/oa-start-picker-list-style-demo.html b/docs/oa-start-picker-list-style-demo.html
new file mode 100644
index 00000000..08dc9a2d
--- /dev/null
+++ b/docs/oa-start-picker-list-style-demo.html
@@ -0,0 +1,1051 @@
+
+
+
+
+
+ 发起审批 · 下拉选择弹窗列表样式 Demo
+
+
+
+
+
发起审批 · 下拉选择弹窗列表样式
+
+ 针对 OA 发起页中人员 / 部门 / 岗位 / 收款账户 / 关联审批等底部弹窗列表,提供多套视觉方案。
+ 左侧切换方案,右侧手机预览可点选体验;上方可切换「单选 / 多选」与「数据场景」。
+
+
+ Token 对齐 mobile-h5「薄荷微风」:主色 #22c55e、软底 #ecfdf5、正文 #0f291e。
+ 当前线上基线为方案 A(透明 cell + 间距,选中态弱)。
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ‹
+ 发起审批
+
+
+
+
+
+
+ 取消
+ 选择人员
+ 确定
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/superpowers/plans/2026-08-12-mobile-oa-task-centers.md b/docs/superpowers/plans/2026-08-12-mobile-oa-task-centers.md
new file mode 100644
index 00000000..4f68f044
--- /dev/null
+++ b/docs/superpowers/plans/2026-08-12-mobile-oa-task-centers.md
@@ -0,0 +1,788 @@
+# 移动端 OA 任务中心 Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** 在小程序落地草稿箱 / 待办 / 已办 / 我发起的 / 抄送我的五中心:原生列表 + H5 详情办理(对齐 Web 业务,UI 对齐既有 mobile-h5 / mobile-oa-h5)。
+
+**Architecture:** 列表在 `mobile-h5`;表单与办理在 `mobile-oa-h5` 新增 `/oa-detail`(`mode=view|approve`);草稿编辑继续 `/oa-start?draftId=`。API 复用 `/web/admin/oa-approval/*` + JWT。菜单种子挂「移动端菜单」根下。
+
+**Tech Stack:** uni-app 微信小程序、`web-view`、Vue3、Vant4、`@form-create/vant`、FastAPI(菜单 SQL)
+
+**Spec:** `docs/superpowers/specs/2026-08-12-mobile-oa-task-centers-design.md`
+
+**原则(强制):** 功能语义对齐 `web-admin` OA 对应页与 `instances/detail.vue`;UI 对齐 `mobile-h5`(如 `pages/demo/list`、`pages/oa/start`)与 `mobile-oa-h5`(`OaStart.vue`、footer、picker),禁止照搬 Element 桌面布局。
+
+**已完成(勿重复):** 五菜单 PNG → `mobile-h5/static/menu-icons/oa-*.png`;`van-icon` / `menu-icon-colors.js` / `backend/app/api/mini_menu.py` path→icon 映射。
+
+**Out of scope:** 时间范围/分组/流程状态等已剔除筛选;Web 设计器改造;小程序内直挂 form-create。
+
+---
+
+## File map
+
+| 路径 | 职责 |
+|------|------|
+| `docs/sql/mobile_menu_oa_task_centers.sql` | 五菜单种子(OA002–OA006) |
+| `mobile-h5/utils/oa-api.js` | drafts / tasks / mark-read / counts |
+| `mobile-h5/utils/oa-h5.js` | `buildOaDetailUrl`;扩展 start 预填若需要 |
+| `mobile-h5/utils/oa-badge.js` | 拉草稿/待办/抄送角标(可新建) |
+| `mobile-h5/pages.json` | 注册五列表 + 通用 webview(或扩 start/webview) |
+| `mobile-h5/pages/oa/drafts/index.vue` | 草稿箱 |
+| `mobile-h5/pages/oa/pending/index.vue` | 待办 |
+| `mobile-h5/pages/oa/done/index.vue` | 已办 |
+| `mobile-h5/pages/oa/mine/index.vue` | 我发起的 |
+| `mobile-h5/pages/oa/cc/index.vue` | 抄送我的 |
+| `mobile-h5/pages/oa/webview/index.vue` | 通用 web-view(start + detail)**或** 扩展 `start/webview.vue` |
+| `mobile-h5/components/OaTaskCard.vue` | 任务/草稿卡片(移动端样式,非 BizListCard 业务 mock) |
+| `mobile-oa-h5/src/api/oa.js` | instance detail + task actions + withdraw + cc read |
+| `mobile-oa-h5/src/views/OaDetail.vue` | 详情只读 / 办理 |
+| `mobile-oa-h5/src/oa/components/OaProgressTimeline.vue` | 审批进度(对齐 Web progress;可基于现有 Flow 组件) |
+| `mobile-oa-h5/src/oa/components/OaActionSheet*.vue` | 意见 / 加签 / 转交弹层(Vant Dialog/Popup) |
+| `mobile-oa-h5/src/router.js` | `/oa-detail` |
+| `mobile-oa-h5/src/oa/print/`(可选) | PDF 打印移植 |
+
+**Web 只读对照:**
+
+- `web-admin/src/views/oa-approval/{drafts,pending,done,mine,cc}/index.vue`
+- `web-admin/src/views/oa-approval/instances/detail.vue`
+- `web-admin/src/api/oaApproval.ts`
+- `web-admin/src/utils/oa-approval-print.ts`(打印)
+
+---
+
+## 目标用户路径
+
+```
+首页宫格(授权菜单)
+ → 五列表之一(原生)
+ → 草稿编辑 → webview → /oa-start?draftId=
+ → 待办办理 → webview → /oa-detail?mode=approve&instanceId=&taskId=
+ → 已办/抄送/我发起查看 → /oa-detail?mode=view&instanceId=
+ → postMessage → navigateBack → 列表 onShow 刷新 + 角标
+```
+
+---
+
+### Task 1: 菜单 SQL(S0)
+
+**Files:**
+- Create: `docs/sql/mobile_menu_oa_task_centers.sql`
+
+- [ ] **Step 1: 按 `mobile_menu_oa_start.sql` 写五条幂等 INSERT**
+
+完整内容:
+
+```sql
+-- 小程序 OA 五中心菜单种子(挂在「移动端菜单」根下)
+-- 前置:docs/sql/mobile_menu_oa_start.sql(发起审批 OA001)可已执行
+-- 角色授权请在 Web「角色管理」勾选本菜单。
+
+SET @mini_root = (
+ SELECT id FROM sys_menu
+ WHERE del_flag = 0 AND parent_id = '0' AND menu_name LIKE '%移动端%'
+ LIMIT 1
+);
+
+-- 草稿箱
+INSERT INTO sys_menu (
+ id, menu_name, parent_id, menu_type, path, component, perms,
+ is_frame, visible, status, tenant_no, order_num, remark,
+ create_by, create_dept_id, create_time, update_by, update_time, del_flag
+)
+SELECT
+ 'MINI000000000000000000000000OA002',
+ '草稿箱',
+ @mini_root,
+ 'C',
+ '/pages/oa/drafts/index',
+ NULL,
+ 'mini:oa:drafts',
+ 1,
+ '1',
+ '1',
+ 'zkxw',
+ 20,
+ '小程序草稿箱',
+ 'admin',
+ '',
+ NOW(),
+ NULL,
+ NOW(),
+ 0
+FROM DUAL
+WHERE @mini_root IS NOT NULL
+ AND NOT EXISTS (
+ SELECT 1 FROM sys_menu WHERE id = 'MINI000000000000000000000000OA002' AND del_flag = 0
+ );
+
+-- 待办中心
+INSERT INTO sys_menu (
+ id, menu_name, parent_id, menu_type, path, component, perms,
+ is_frame, visible, status, tenant_no, order_num, remark,
+ create_by, create_dept_id, create_time, update_by, update_time, del_flag
+)
+SELECT
+ 'MINI000000000000000000000000OA003',
+ '待办中心',
+ @mini_root,
+ 'C',
+ '/pages/oa/pending/index',
+ NULL,
+ 'mini:oa:pending',
+ 1,
+ '1',
+ '1',
+ 'zkxw',
+ 30,
+ '小程序待办中心',
+ 'admin',
+ '',
+ NOW(),
+ NULL,
+ NOW(),
+ 0
+FROM DUAL
+WHERE @mini_root IS NOT NULL
+ AND NOT EXISTS (
+ SELECT 1 FROM sys_menu WHERE id = 'MINI000000000000000000000000OA003' AND del_flag = 0
+ );
+
+-- 已办中心
+INSERT INTO sys_menu (
+ id, menu_name, parent_id, menu_type, path, component, perms,
+ is_frame, visible, status, tenant_no, order_num, remark,
+ create_by, create_dept_id, create_time, update_by, update_time, del_flag
+)
+SELECT
+ 'MINI000000000000000000000000OA004',
+ '已办中心',
+ @mini_root,
+ 'C',
+ '/pages/oa/done/index',
+ NULL,
+ 'mini:oa:done',
+ 1,
+ '1',
+ '1',
+ 'zkxw',
+ 40,
+ '小程序已办中心',
+ 'admin',
+ '',
+ NOW(),
+ NULL,
+ NOW(),
+ 0
+FROM DUAL
+WHERE @mini_root IS NOT NULL
+ AND NOT EXISTS (
+ SELECT 1 FROM sys_menu WHERE id = 'MINI000000000000000000000000OA004' AND del_flag = 0
+ );
+
+-- 我发起的
+INSERT INTO sys_menu (
+ id, menu_name, parent_id, menu_type, path, component, perms,
+ is_frame, visible, status, tenant_no, order_num, remark,
+ create_by, create_dept_id, create_time, update_by, update_time, del_flag
+)
+SELECT
+ 'MINI000000000000000000000000OA005',
+ '我发起的',
+ @mini_root,
+ 'C',
+ '/pages/oa/mine/index',
+ NULL,
+ 'mini:oa:mine',
+ 1,
+ '1',
+ '1',
+ 'zkxw',
+ 50,
+ '小程序我发起的',
+ 'admin',
+ '',
+ NOW(),
+ NULL,
+ NOW(),
+ 0
+FROM DUAL
+WHERE @mini_root IS NOT NULL
+ AND NOT EXISTS (
+ SELECT 1 FROM sys_menu WHERE id = 'MINI000000000000000000000000OA005' AND del_flag = 0
+ );
+
+-- 抄送我的
+INSERT INTO sys_menu (
+ id, menu_name, parent_id, menu_type, path, component, perms,
+ is_frame, visible, status, tenant_no, order_num, remark,
+ create_by, create_dept_id, create_time, update_by, update_time, del_flag
+)
+SELECT
+ 'MINI000000000000000000000000OA006',
+ '抄送我的',
+ @mini_root,
+ 'C',
+ '/pages/oa/cc/index',
+ NULL,
+ 'mini:oa:cc',
+ 1,
+ '1',
+ '1',
+ 'zkxw',
+ 60,
+ '小程序抄送我的',
+ 'admin',
+ '',
+ NOW(),
+ NULL,
+ NOW(),
+ 0
+FROM DUAL
+WHERE @mini_root IS NOT NULL
+ AND NOT EXISTS (
+ SELECT 1 FROM sys_menu WHERE id = 'MINI000000000000000000000000OA006' AND del_flag = 0
+ );
+```
+
+- [ ] **Step 2: 在测试库执行 SQL,Web 角色管理勾选后调 `GET /mini/menus` 应含 5 条 path**
+
+Expected: list 中出现 `/pages/oa/drafts|pending|done|mine|cc/index`,icon 分别为 `oa-drafts|oa-pending|oa-done|oa-mine|oa-cc`。
+
+- [ ] **Step 3: Commit**
+
+```bash
+git add docs/sql/mobile_menu_oa_task_centers.sql
+git commit -m "$(cat <<'EOF'
+feat(oa): add mobile menu seeds for five OA task centers
+
+EOF
+)"
+```
+
+---
+
+### Task 2: 扩展双端 OA API(S0/S1 前置)
+
+**Files:**
+- Modify: `mobile-h5/utils/oa-api.js`
+- Modify: `mobile-oa-h5/src/api/oa.js`
+
+- [ ] **Step 1: 在 `mobile-h5/utils/oa-api.js` 追加(保留现有 templates 方法;按项目 `request.js` 支持的 get/post/del 写法实现)**
+
+对齐 `web-admin/src/api/oaApproval.ts`:
+
+```js
+// drafts
+export function fetchOaInstanceDraftList(params) { /* GET /web/admin/oa-approval/drafts */ }
+export function fetchOaInstanceDraftCount() { /* GET .../drafts/count */ }
+export function fetchDeleteOaInstanceDraft(id) { /* DELETE .../drafts/{id} */ }
+export function fetchBatchDeleteOaInstanceDrafts(ids) { /* POST .../drafts/batch-delete { ids } */ }
+export function fetchSubmitOaInstanceDraft(id, data) { /* POST .../drafts/{id}/submit */ }
+
+// tasks
+export function fetchOaTasks(kind, params) {
+ // kind: pending|done|mine|cc
+ // GET .../tasks/{kind} query: page,pageSize,appName,initiatorName,readStatus(cc)
+}
+export function fetchOaPendingTaskCount() { /* GET .../tasks/pending/count */ }
+export function fetchOaCcUnreadCount() { /* GET .../tasks/cc/unread-count */ }
+export function fetchMarkOaCcTaskRead(taskId) { /* POST .../tasks/{id}/read */ }
+export function fetchBatchMarkOaCcTasksRead(ids) { /* POST .../tasks/cc/read-batch { ids } */ }
+```
+
+实现时复用文件内已有 `get` / `authHeader`;若 `request.js` 无 `del`/`post`,按同目录其它 utils 写法调用。每个函数必须带 `Authorization: Bearer`。
+
+- [ ] **Step 2: 在 `mobile-oa-h5/src/api/oa.js` 追加**
+
+```js
+export function fetchOaInstanceDetail(id) {
+ return get(`/web/admin/oa-approval/instances/${encodeURIComponent(id)}`)
+}
+
+export function fetchApproveOaTask(taskId, comment) {
+ return post(`/web/admin/oa-approval/tasks/${encodeURIComponent(taskId)}/approve`, { comment })
+}
+
+export function fetchRejectOaTask(taskId, comment) {
+ return post(`/web/admin/oa-approval/tasks/${encodeURIComponent(taskId)}/reject`, { comment })
+}
+
+export function fetchReturnOaTask(taskId, comment) {
+ return post(`/web/admin/oa-approval/tasks/${encodeURIComponent(taskId)}/return`, { comment })
+}
+
+export function fetchAddSignOaTask(taskId, data) {
+ // data: { userIds: string[], comment?: string }
+ return post(`/web/admin/oa-approval/tasks/${encodeURIComponent(taskId)}/add-sign`, data)
+}
+
+export function fetchTransferOaTask(taskId, data) {
+ // data: { targetUserId: string, comment?: string }
+ return post(`/web/admin/oa-approval/tasks/${encodeURIComponent(taskId)}/transfer`, data)
+}
+
+export function fetchWithdrawOaInstance(instanceId) {
+ return post(`/web/admin/oa-approval/instances/${encodeURIComponent(instanceId)}/withdraw`, {})
+}
+
+export function fetchMarkOaCcTaskRead(taskId) {
+ return post(`/web/admin/oa-approval/tasks/${encodeURIComponent(taskId)}/read`, {})
+}
+```
+
+- [ ] **Step 3: 本地用已登录 token 在浏览器/控制台或临时页面各打一条 drafts list / tasks pending 验证 200**
+
+- [ ] **Step 4: Commit**
+
+```bash
+git add mobile-h5/utils/oa-api.js mobile-oa-h5/src/api/oa.js
+git commit -m "$(cat <<'EOF'
+feat(oa): extend mobile OA APIs for drafts, tasks, and detail actions
+
+EOF
+)"
+```
+
+---
+
+### Task 3: 通用 web-view + `buildOaDetailUrl`(S0)
+
+**Files:**
+- Modify: `mobile-h5/utils/oa-h5.js`
+- Create or Modify: `mobile-h5/pages/oa/webview/index.vue`(推荐新建通用页)**或** 扩展 `pages/oa/start/webview.vue`
+- Modify: `mobile-h5/pages.json`
+
+- [ ] **Step 1: 在 `oa-h5.js` 增加**
+
+```js
+/** 审批详情 / 办理页 */
+export function buildOaDetailUrl({ instanceId, taskId, mode } = {}) {
+ const token = getToken()
+ const params = {}
+ if (instanceId) params.instanceId = instanceId
+ if (taskId) params.taskId = taskId
+ if (mode) params.mode = mode // view | approve
+ if (token) params.token = token
+ return withQuery('/oa-detail', params)
+}
+
+const DETAIL_TITLE_KEY = 'oa_detail_nav_title'
+
+export function stashOaDetailTitle(name) { /* 同 stashOaStartTitle 模式 */ }
+export function takeOaDetailTitle() { /* 同 takeOaStartTitle */ }
+```
+
+- [ ] **Step 2: 通用 webview 页逻辑**
+
+`onLoad(query)`:
+
+1. 无 token → `forceRelogin`
+2. 若 `query.draftId` 或(`query.appId` 且无 `instanceId`)→ `buildOaStartUrl`
+3. 若 `query.instanceId` → `buildOaDetailUrl({ instanceId, taskId, mode })`
+4. 标题:`takeOaDetailTitle()` / `takeOaStartTitle()`
+5. `CLOSE_ACTIONS` 扩展:`oa-detail-close`、`oa-detail-done`、`oa-detail-withdraw`、`oa-detail-restart` 等;H5 iframe `message` 与小程序 `@message` 行为对齐现有 start webview
+
+- [ ] **Step 3: `pages.json` 注册五列表空壳路径 + webview(标题用中文业务名)**
+
+示例条目:
+
+```json
+{
+ "path": "pages/oa/drafts/index",
+ "style": {
+ "navigationBarTitleText": "草稿箱",
+ "navigationBarBackgroundColor": "#ecfdf5",
+ "navigationBarTextStyle": "black"
+ }
+}
+```
+
+同样注册 `pending`/`done`/`mine`/`cc`;webview 标题默认「审批详情」。
+
+- [ ] **Step 4: 真机/开发者工具打开任意 webview URL 带 token,确认能加载 H5(detail 可先 404,本 Task 只要求 start 路径不回归)**
+
+- [ ] **Step 5: Commit**
+
+```bash
+git add mobile-h5/utils/oa-h5.js mobile-h5/pages.json mobile-h5/pages/oa/webview/
+git commit -m "$(cat <<'EOF'
+feat(oa): add shared OA webview and detail URL builder
+
+EOF
+)"
+```
+
+---
+
+### Task 4: 共用任务卡片 + 列表壳(UI)
+
+**Files:**
+- Create: `mobile-h5/components/OaTaskCard.vue`
+- Create: `mobile-h5/styles/components/oa-task-list.scss`(若项目按组件拆 scss)
+- Reference UI: `mobile-h5/pages/demo/list.vue`、`pages/oa/start/index.vue`、`styles/tokens.scss`
+
+- [ ] **Step 1: 实现 `OaTaskCard`**
+
+Props 建议:
+
+```js
+{
+ title: String, // 流程名
+ statusText: String,
+ statusTone: String, // warn|success|danger|info
+ metaLines: Array, // [{ label, value }]
+ unread: Boolean, // 抄送红点
+ actions: Array // [{ key, label, primary?, danger? }]
+}
+```
+
+Emits: `action` with `{ key, ... }`;整卡点击可 emit `open`。
+
+样式:三墙 Token;卡片圆角/间距对齐 start 列表与 demo list;**不要**复制 Web 表格列。
+
+- [ ] **Step 2: 抽可复用 list mixin 或 `utils/oa-list.js`(可选)**
+
+提供:`page`/`pageSize`/`loading`/`finished`、`loadMore`、`onPullDownRefresh` 重置。若不想抽 mixin,五页内复制最小分页逻辑亦可(YAGNI:先两页共用后再抽)。
+
+- [ ] **Step 3: Commit**
+
+```bash
+git add mobile-h5/components/OaTaskCard.vue mobile-h5/styles/components/oa-task-list.scss
+git commit -m "$(cat <<'EOF'
+feat(oa): add mobile OA task card for center lists
+
+EOF
+)"
+```
+
+---
+
+### Task 5: 草稿箱列表(S1)
+
+**Files:**
+- Create: `mobile-h5/pages/oa/drafts/index.vue`
+- Modify: `mobile-h5/pages.json`(若 Task3 已注册可跳过)
+
+**功能清单(规格 §2.1):** 列表、分页、流程名筛选、编辑、列表提交、单删、批量删、角标(角标可先调 count API,宫格角标 UI 放 Task 11)。
+
+- [ ] **Step 1: 实现页面**
+
+结构参考 `pages/demo/list.vue`:顶区搜索(仅关键词)+ 列表 + 多选模式批量删。
+
+关键方法:
+
+```js
+async loadList(reset) {
+ // fetchOaInstanceDraftList({ keyword, page, pageSize })
+}
+openEdit(row) {
+ stashOaStartTitle(row.appName || row.title)
+ uni.navigateTo({
+ url: `/pages/oa/webview/index?draftId=${row.id}&appId=${row.appId || ''}`
+ // 若仍用 start/webview:`/pages/oa/start/webview?draftId=...`
+ })
+}
+async submitRow(row) {
+ // uni.showModal 确认 → fetchSubmitOaInstanceDraft(row.id) → 刷新
+ // 失败:可改为 openEdit 让用户改表单(对齐 Web)
+}
+async deleteRow(row) { /* confirm → fetchDeleteOaInstanceDraft */ }
+async batchDelete() { /* selected ids → fetchBatchDeleteOaInstanceDrafts */ }
+```
+
+- [ ] **Step 2: onShow 刷新列表**
+
+- [ ] **Step 3: 手工验收**
+
+1. 发起页暂存 → 草稿箱可见
+2. 编辑打开 H5 回填 → 再暂存/提交
+3. 列表提交成功后草稿消失
+4. 单删、批量删
+
+- [ ] **Step 4: Commit**
+
+```bash
+git add mobile-h5/pages/oa/drafts/
+git commit -m "$(cat <<'EOF'
+feat(oa): add mobile draft box list with edit submit delete
+
+EOF
+)"
+```
+
+---
+
+### Task 6: H5 `/oa-detail` 只读(S2)
+
+**Files:**
+- Create: `mobile-oa-h5/src/views/OaDetail.vue`
+- Create: `mobile-oa-h5/src/oa/components/OaProgressTimeline.vue`(或复用/改造现有 flow 展示)
+- Modify: `mobile-oa-h5/src/router.js`
+- Reference: `OaStart.vue`(schema-prep、form-create disabled)、Web `detail.vue`(progress 字段)
+
+- [ ] **Step 1: 注册路由**
+
+```js
+import OaDetail from './views/OaDetail.vue'
+// ...
+{ path: '/oa-detail', component: OaDetail }
+```
+
+- [ ] **Step 2: `OaDetail.vue` 最小只读**
+
+1. `captureTokenFromUrl`(与 main/OaStart 一致)
+2. 读 `instanceId`、`mode`(默认 view)、`taskId`
+3. `fetchOaInstanceDetail` → `formSchemaJson` + `formData` → `schema-prep` → ``
+4. 渲染 `progress` 时间线
+5. 顶栏状态 Tag(文案映射对齐 Web `statusText`)
+6. 底栏:`mode=view` 且非发起人特殊操作时仅「关闭」→ `postMessage` / `wx.miniProgram.navigateBack`,action=`oa-detail-close`
+
+UI:padding、底栏按钮类名对齐 `OaStart` / `oa-form.css` / footer demo。
+
+- [ ] **Step 3: 浏览器直接打开**
+
+`http://127.0.0.1:5178/oa-h5/oa-detail?instanceId=真实ID&token=...&mode=view`
+Expected: 表单只读可见、进度可见、关闭可返回。
+
+- [ ] **Step 4: Commit**
+
+```bash
+git add mobile-oa-h5/src/views/OaDetail.vue mobile-oa-h5/src/oa/components/OaProgressTimeline.vue mobile-oa-h5/src/router.js
+git commit -m "$(cat <<'EOF'
+feat(oa): add mobile OA detail page in read-only mode
+
+EOF
+)"
+```
+
+---
+
+### Task 7: 已办 / 我发起 / 抄送列表进 view(S2)
+
+**Files:**
+- Create: `mobile-h5/pages/oa/done/index.vue`
+- Create: `mobile-h5/pages/oa/mine/index.vue`(本 Task 仅查看;打印/复制/撤销后续 Task)
+- Create: `mobile-h5/pages/oa/cc/index.vue`(本 Task:列表+打开已读+进详情;阅读筛选/批量可本 Task 一并做或见 Task 10)
+
+- [ ] **Step 1: 已办页**
+
+筛选:`appName`、`initiatorName`(搜索框 + 可选第二条件,移动端可用一行双字段或展开筛选,**不要** Web 日期/状态)。
+`fetchOaTasks('done', …)` → 卡片 → `stashOaDetailTitle` → navigate webview `instanceId&mode=view`。
+
+- [ ] **Step 2: 我发起的页(查看)**
+
+筛选仅 `appName`。`fetchOaTasks('mine', …)` → view。
+
+- [ ] **Step 3: 抄送页(查看 + 打开已读)**
+
+打开前或打开时:`fetchMarkOaCcTaskRead(taskId)`(task 行上的抄送任务 id,字段名对齐 Web `row.id`)。
+再进 `mode=view`。H5 详情可再兜底 mark-read。
+
+- [ ] **Step 4: 验收三页均可进只读详情并返回刷新**
+
+- [ ] **Step 5: Commit**
+
+```bash
+git add mobile-h5/pages/oa/done/ mobile-h5/pages/oa/mine/ mobile-h5/pages/oa/cc/
+git commit -m "$(cat <<'EOF'
+feat(oa): add done mine cc lists opening read-only OA detail
+
+EOF
+)"
+```
+
+---
+
+### Task 8: 待办列表 + 办理动作(S3)
+
+**Files:**
+- Create: `mobile-h5/pages/oa/pending/index.vue`
+- Modify: `mobile-oa-h5/src/views/OaDetail.vue`
+- Create: 意见弹层 / 加签选人 / 转交选人(复用 `OaPersonSelect` 或 Vant Popup + 已有选人)
+
+**对照 Web `detail.vue`:** `myPendingTask`、`canApprove`、`allowReturn`、`allowAddSign`、转交排除规则。
+
+- [ ] **Step 1: 待办列表**
+
+筛选:流程名、申请人。进入:`mode=approve&instanceId=&taskId=`(taskId 用行上待办任务 id)。
+
+- [ ] **Step 2: 详情底栏(`mode=approve` 且存在当前用户 pending task)**
+
+| 按钮 | API |
+|------|-----|
+| 同意 | `fetchApproveOaTask(taskId, comment?)` |
+| 拒绝 | `fetchRejectOaTask` |
+| 打回 | 意见必填 → `fetchReturnOaTask` |
+| 加签 | 选多人 → `fetchAddSignOaTask({ userIds, comment })` |
+| 转交 | 选一人(排除本人与本节点已有待办)→ `fetchTransferOaTask({ targetUserId, comment })` |
+
+成功后 toast + `postMessage({ source:'zkxw-oa-h5', action:'oa-detail-done' })` + navigateBack。
+
+意见输入用 Vant Dialog/Popup,样式对齐 mobile-oa-h5,不要 Element MessageBox。
+
+- [ ] **Step 3: 五条办理主路径各测至少一次(可用测试租户)**
+
+- [ ] **Step 4: Commit**
+
+```bash
+git add mobile-h5/pages/oa/pending/ mobile-oa-h5/src/views/OaDetail.vue mobile-oa-h5/src/oa/components/
+git commit -m "$(cat <<'EOF'
+feat(oa): add pending list and approve reject return add-sign transfer
+
+EOF
+)"
+```
+
+---
+
+### Task 9: 我发起的 — 撤销与复制重开(S4)
+
+**Files:**
+- Modify: `mobile-h5/pages/oa/mine/index.vue`
+- Modify: `mobile-oa-h5/src/views/OaDetail.vue`
+- Modify: `mobile-oa-h5/src/views/OaStart.vue`(接收预填)
+- Modify: `mobile-h5/utils/oa-h5.js`(可选 storage 传 fromInstance 预填)
+
+- [ ] **Step 1: 详情撤销**
+
+条件:当前用户是发起人且实例 `running`(与 Web `canWithdraw` 一致)。
+`fetchWithdrawOaInstance` → done 回列表。
+
+- [ ] **Step 2: 复制并重新发起**
+
+条件:发起人且 status ∈ `returned|cancelled|rejected`。
+实现择一(优先与 Web 一致):
+
+1. H5:`postMessage` action=`oa-detail-restart` + `appId` + 小程序再开 start webview,并用 `uni.setStorageSync('oa_restart_prefills', { formData, … })`
+2. 或 H5 内 `router.replace({ path:'/oa-start', query:{ appId, fromInstanceId } })`,`OaStart` 拉实例详情回填
+
+选定一种写清,并在 `OaStart` 实现回填;提交成功后勿留下旧 instanceId。
+
+- [ ] **Step 3: 列表行也提供「复制并重新发起」入口(终态时)**
+
+- [ ] **Step 4: 验收撤销与复制提交新单**
+
+- [ ] **Step 5: Commit**
+
+```bash
+git add mobile-h5/pages/oa/mine/ mobile-oa-h5/src/views/OaDetail.vue mobile-oa-h5/src/views/OaStart.vue mobile-h5/utils/oa-h5.js
+git commit -m "$(cat <<'EOF'
+feat(oa): support withdraw and copy-restart on mobile mine flows
+
+EOF
+)"
+```
+
+---
+
+### Task 10: 抄送增强(S5)
+
+**Files:**
+- Modify: `mobile-h5/pages/oa/cc/index.vue`
+
+- [ ] **Step 1: 阅读状态筛选** — `readStatus`: `unread`|`read`|`all`(默认 unread,对齐 Web)
+- [ ] **Step 2: 未读红点样式** — `OaTaskCard` `unread`
+- [ ] **Step 3: 单条已读** — 不打开详情也可 mark
+- [ ] **Step 4: 批量已读** — 多选 + `fetchBatchMarkOaCcTasksRead`
+- [ ] **Step 5: 验收后 Commit**
+
+```bash
+git add mobile-h5/pages/oa/cc/
+git commit -m "$(cat <<'EOF'
+feat(oa): add cc read filters batch mark and unread styles
+
+EOF
+)"
+```
+
+---
+
+### Task 11: 角标(S1/S3/S5/S7)
+
+**Files:**
+- Create: `mobile-h5/utils/oa-badge.js`
+- Modify: `mobile-h5/components/MenuGrid.vue` 和/或 `pages/index/index.vue`
+
+- [ ] **Step 1: `refreshOaMenuBadges`**
+
+并行请求:
+
+- `fetchOaInstanceDraftCount`
+- `fetchOaPendingTaskCount`
+- `fetchOaCcUnreadCount`
+
+返回 map:`path → count`(`/pages/oa/drafts/index` 等)。展示:`>99` 显示 `99+`。
+
+- [ ] **Step 2: 首页加载菜单后合并 badge;`onShow` 刷新**
+
+MenuGrid 在 title 旁或图标右上角显示数字(样式轻量,对齐三墙,勿过重)。
+
+- [ ] **Step 3: 从 webview 返回后各列表 `onShow` 也可触发首页角标(通过 storage 事件或全局简单 bus;最小做法:仅首页 onShow 刷新即可)
+
+- [ ] **Step 4: Commit**
+
+```bash
+git add mobile-h5/utils/oa-badge.js mobile-h5/components/MenuGrid.vue mobile-h5/pages/index/index.vue
+git commit -m "$(cat <<'EOF'
+feat(oa): show draft pending cc badges on mobile home menu
+
+EOF
+)"
+```
+
+---
+
+### Task 12: 打印(S6)
+
+**Files:**
+- 移植参考:`web-admin/src/utils/oa-approval-print.ts`
+- Create: `mobile-oa-h5/src/oa/print/exportOaApprovalPdf.js`(或等价)
+- Modify: `OaDetail.vue` + `mine`/`cc` 列表入口
+
+- [ ] **Step 1: 在 H5 内实现 PDF 生成(优先 jspdf/html2canvas 等与 Web 同类方案;以 Web 工具函数逻辑为准)**
+
+- [ ] **Step 2: 详情与列表「打印」按钮**:生成后尝试下载 / 新开预览;微信内若失败,`showToast` 明确文案(规格允许兜底,不阻塞其它功能)
+
+- [ ] **Step 3: 真机验收;记录限制到 `docs/superpowers/specs/` 旁注或本 plan 底部「打印验收笔记」
+
+- [ ] **Step 4: Commit**
+
+```bash
+git add mobile-oa-h5/src/oa/print/ mobile-oa-h5/src/views/OaDetail.vue mobile-h5/pages/oa/mine/ mobile-h5/pages/oa/cc/
+git commit -m "$(cat <<'EOF'
+feat(oa): add mobile approval PDF print for mine and cc
+
+EOF
+)"
+```
+
+---
+
+### Task 13: 回归与图谱(收尾)
+
+- [ ] **Step 1: 回归「发起审批」全链路未被破坏**(列表→填写→草稿→提交)
+- [ ] **Step 2: 按规格 §8 测试要点过一遍勾选
+- [ ] **Step 3: 若改了 backend `mini_menu.py`(本计划默认已改完),执行 `./scripts/update-graph.sh backend`
+- [ ] **Step 4: 最终 commit 若有文档修补**
+
+---
+
+## Spec coverage 自检
+
+| 规格项 | Task |
+|--------|------|
+| 菜单 SQL OA002–006 + 图标映射 | Task 1(图标已完成) |
+| 功能对齐 Web / UI 对齐移动端 | 全文原则 + Task 4–8 |
+| 草稿 1/2/3/6/7/8/9/10 | Task 5 + Task 11 |
+| 待办办理五动作 + 角标 | Task 8 + 11 |
+| 已办只读 | Task 6–7 |
+| 我发起撤销/复制/打印 | Task 9 + 12 |
+| 抄送阅读/批量/打印/角标 | Task 7/10/11/12 |
+| `/oa-detail` | Task 6 + 8 + 9 |
+| S0–S7 顺序 | Task 1→12 |
+
+---
+
+## 执行说明
+
+实现时每完成一个 Task 再开下一个;打印(Task 12)可与角标并行,但不得阻塞 Task 5–10 合并。
diff --git a/docs/superpowers/plans/2026-08-12-oa-person-picker-scheme-c.md b/docs/superpowers/plans/2026-08-12-oa-person-picker-scheme-c.md
new file mode 100644
index 00000000..e412723f
--- /dev/null
+++ b/docs/superpowers/plans/2026-08-12-oa-person-picker-scheme-c.md
@@ -0,0 +1,98 @@
+# OA 人员弹窗方案 C 对齐 Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** 让发起审批表单【人员】选择弹窗列表与「选择审批人/抄送人」一致,采用 Demo 方案 C(分割线行表 + 姓名与副信息同行)。
+
+**Architecture:** 只改 `OaPersonSelect.vue` 列表项模板与用户映射;在 `oa-form.css` 补充 `.oa-pick__name` / `.oa-pick__sub`。行级方案 C 样式已存在,无需重写。
+
+**Tech Stack:** Vue 3、Vant、`mobile-oa-h5`
+
+**Spec:** `docs/superpowers/specs/2026-08-12-oa-person-picker-scheme-c-design.md`
+
+---
+
+### Task 1: 列表项模板改为同行姓名 + 副信息
+
+**Files:**
+- Modify: `mobile-oa-h5/src/oa/components/OaPersonSelect.vue`
+
+- [x] **Step 1: 替换多选 / 单选 `van-cell` 为 `#title` 插槽结构**
+
+多选与单选均去掉 `:title` / `:label`,改为:
+
+```vue
+
+
+
+
+
+ {{ u.label }}
+ {{ u.sub }}
+
+
+```
+
+单选同理,`@click` 设 `pendingSingle`,icon 用 `van-radio`。
+
+- [x] **Step 2: 更新 `loadUsers` 副信息与搜索**
+
+```js
+const phone = String(u.phoneNo || '').trim()
+const userNo = String(u.userNo || '').trim()
+const nick = String(u.nickName || '').trim()
+const label = nick || userNo || id
+const sub = phone || (userNo && userNo !== label ? userNo : '')
+opts.push({ id, label, sub })
+```
+
+`filtered` 已用 `u.sub`,保持即可(手机或工号都会进 `sub`)。
+
+可选:搜索 placeholder 改为「搜索姓名、工号或手机号」。
+
+---
+
+### Task 2: 补充同行文字样式
+
+**Files:**
+- Modify: `mobile-oa-h5/src/styles/oa-form.css`
+
+- [x] **Step 1: 在 `.oa-pick__check` 规则前增加 name/sub 样式,并强化选中态**
+
+```css
+.oa-pick__name {
+ font-size: 14px;
+ color: var(--text-primary, #0f291e);
+}
+
+.oa-pick__sub {
+ margin-left: 8px;
+ font-size: 12px;
+ color: var(--text-muted, #5f7a6d);
+}
+
+.oa-pick__list .van-cell:has(.van-checkbox--checked) .oa-pick__name,
+.oa-pick__list .van-cell:has(.van-radio--checked) .oa-pick__name {
+ color: #15803d;
+}
+```
+
+(保留现有 `.van-cell__title` 选中规则作兜底。)
+
+---
+
+### Task 3: 验收与提交
+
+- [ ] **Step 1: 手动核对** — FormSink / 发起页打开【人员】弹窗:分割线行、选中浅绿、姓名副信息同行;与流程选人弹窗观感一致。
+
+- [ ] **Step 2: Commit**
+
+```bash
+git add mobile-oa-h5/src/oa/components/OaPersonSelect.vue mobile-oa-h5/src/styles/oa-form.css
+git commit -m "fix(mobile-oa-h5): 人员选择弹窗列表对齐方案 C"
+```
diff --git a/docs/superpowers/specs/2026-08-12-mobile-oa-task-centers-design.md b/docs/superpowers/specs/2026-08-12-mobile-oa-task-centers-design.md
new file mode 100644
index 00000000..950aa148
--- /dev/null
+++ b/docs/superpowers/specs/2026-08-12-mobile-oa-task-centers-design.md
@@ -0,0 +1,273 @@
+# 移动端 OA 任务中心(草稿 / 待办 / 已办 / 我发起 / 抄送)设计
+
+> 日期:2026-08-12
+> 工程:`mobile-h5`(微信小程序)+ `mobile-oa-h5`(OA 表单 H5)
+> 对齐参考:`web-admin` 下 OA审批 六菜单(发起已落地移动端;本设计覆盖其余五页)
+> 上游设计:`docs/superpowers/specs/2026-08-11-mobile-oa-start-approval-design.md`
+> 菜单种子参考:`docs/sql/mobile_menu_oa_start.sql`
+
+---
+
+## 1. 背景与目标
+
+### 1.1 现状
+
+- Web:`发起审批` / `草稿箱` / `待办中心` / `已办中心` / `我发起的` / `抄送我的` 已齐;详情办理在 `instances/detail.vue`(同意/拒绝/打回/加签/转交/撤销/复制重开/打印)。
+- 移动:`mobile-h5` 已实现「发起审批」列表;填写经 web-view 打开 `mobile-oa-h5` `/oa-start`(含 `draftId` 草稿再编辑)。
+- `mobile-oa-h5` 尚无实例详情 / 审批办理路由;无五中心列表。
+
+### 1.2 目标
+
+1. 在小程序中一次性补齐五中心功能(可分步开发),能力以本文「已确认范围」为准,办理动作对齐 Web 详情。
+2. 延续既有架构:**列表原生 `mobile-h5`;动态表单与办理在 `mobile-oa-h5`**。
+3. 新增移动端菜单种子 SQL,挂在「移动端菜单」根下,便于角色授权。
+
+### 1.3 非目标
+
+- 改造 Web 设计器或 Web 六页交互。
+- 在小程序原生内直挂 `@form-create/vant`。
+- 已在核对中明确剔除的筛选(见 §2)。
+- 审批流设计、应用管理、收款账户等非六中心能力。
+
+### 1.4 架构选型(已确认)
+
+**方案 A**:延续发起审批架构——五列表在 `mobile-h5`;详情/办理扩 `mobile-oa-h5` `/oa-detail`;草稿编辑继续走 `/oa-start?draftId=`。
+
+不采用:列表全进 H5;或在原生重做表单运行时。
+
+### 1.5 功能对齐 Web,UI 对齐移动端(已确认)
+
+| 维度 | 权威来源 | 说明 |
+|------|----------|------|
+| **功能 / 业务语义** | `web-admin` OA 六中心 + `instances/detail` | API 字段、操作条件、状态文案、办理规则与 Web 一致;本文 §2 已裁剪的能力除外 |
+| **UI / 交互样式** | `mobile-h5` + `mobile-oa-h5` 既有端到端模式 | **禁止**照搬 Web Element 桌面布局(多列表格筛选栏、桌面弹窗栅格等) |
+
+移动端实现时优先对照:
+
+- **列表 / 宫格 / 空态 / Token**:`mobile-h5`(如 `pages/oa/start`、`MenuGrid`、`styles/tokens.scss`、`BizListCard` 等已有列表卡片模式)
+- **表单 / 底栏 / 选人 / 流预览**:`mobile-oa-h5`(如 `OaStart.vue`、`oa-form.css`、`tokens.css`、既有 picker / footer 按钮样式)
+- **视觉规范**:`docs/miniprogram-ui-spec-sanqiang.md`(三墙色;勿混用 Web `#10b981`)
+
+原则:**Web 决定「做什么」;移动端决定「长什么样、怎么点」。**
+
+---
+
+## 2. 已确认功能范围(逐页核对)
+
+### 2.1 草稿箱
+
+| 要 | 不要 |
+|----|------|
+| 列表;分页/下拉刷新;流程名筛选;编辑;列表提交审批;单删;批量删;菜单角标 | 保存时间范围;模板分组筛选 |
+
+编辑 → `mobile-oa-h5` `/oa-start?draftId=`(已有能力)。
+
+### 2.2 待办中心
+
+| 要 | 不要 |
+|----|------|
+| 列表;分页;流程名+申请人筛选;进详情;角标 | 申请时间范围;流程状态筛选 |
+| 详情:表单只读+流进度;同意/拒绝/打回/加签/转交 | — |
+
+### 2.3 已办中心
+
+| 要 | 不要 |
+|----|------|
+| 列表;分页;流程名+申请人;只读详情(表单+流) | 时间/状态筛选;办理操作;角标 |
+
+### 2.4 我发起的
+
+| 要 | 不要 |
+|----|------|
+| 列表;分页;流程名筛选;查看;打印;复制并重新发起;详情撤销;详情内复制重开 | 申请时间;流程状态筛选 |
+
+### 2.5 抄送我的
+
+| 要 | 不要 |
+|----|------|
+| 列表;分页;流程名+申请人;阅读状态筛选;未读样式;查看(打开标已读);单条已读;批量已读;打印;未读角标;只读详情 | 申请时间;流程状态筛选;办理操作 |
+
+---
+
+## 3. 架构
+
+```
+┌─ mobile-h5 ─────────────────────────────────────────────┐
+│ 首页 MenuGrid ← fetchMyMobileMenus + 角色授权 │
+│ /pages/oa/drafts|pending|done|mine|cc/index (列表) │
+│ /pages/oa/webview(或复用 start/webview)整页 web-view │
+│ 角标:drafts/count、pending/count、cc/unread-count │
+└───────────────────────────┬─────────────────────────────┘
+ │ token + query
+ ▼
+┌─ mobile-oa-h5 ──────────────────────────────────────────┐
+│ /oa-start 发起 + 草稿编辑(已有) │
+│ /oa-detail 新增:mode=view|approve │
+│ GET instances/{id};表单 disabled;流进度 │
+│ approve:同意/拒绝/打回/加签/转交 │
+│ 发起人:撤销 / 复制并重新发起 │
+│ 打印:移植 Web PDF 能力 │
+│ 结束:postMessage → 小程序 navigateBack + 刷新 │
+└─────────────────────────────────────────────────────────┘
+```
+
+### 3.1 工程边界
+
+| `mobile-h5` | `mobile-oa-h5` |
+|-------------|----------------|
+| 五列表、筛选、批量删/已读、角标、菜单 path | `formSchemaJson` 渲染、流进度、办理弹层、打印 |
+| 草稿「编辑」→ 已有 start URL | `/oa-detail?instanceId=&taskId=&mode=&token=` |
+
+### 3.2 API
+
+继续复用 `/web/admin/oa-approval/*` + 小程序登录 JWT(与发起审批一致),不新开独立 mini OA 前缀(除非后续权限评审要求拆分)。
+
+主要端点:
+
+- 草稿:`/drafts*`、`/drafts/{id}/submit`、`/drafts/count`
+- 任务列表:`/tasks/{pending|done|mine|cc}`
+- 详情:`GET /instances/{id}`
+- 办理:`POST /tasks/{id}/approve|reject|return|add-sign|transfer`
+- 抄送已读:`POST /tasks/{id}/read`、`POST /tasks/cc/read-batch`
+- 撤销:`POST /instances/{id}/withdraw`
+- 角标:`/tasks/pending/count`、`/tasks/cc/unread-count`
+
+### 3.3 UI Token 与样式权威
+
+三墙品牌绿 `#0a9f6d` 等;**禁止**混用 Web Fresh Mint `#10b981`。
+
+列表 / 详情视觉对齐 **已有移动端界面**,而非 Web 列表页截图:
+
+- `mobile-h5`:发起审批列表、首页宫格、现有 list/card/empty 样式
+- `mobile-oa-h5`:`/oa-start` 填写页结构、底栏按钮、Vant 弹层与自研 picker 交互
+
+筛选在移动端宜用顶栏搜索 / 简易筛选条 / ActionSheet,避免 Web 多列 `filter-grid` 桌面形态。
+
+---
+
+## 4. 移动端菜单种子
+
+参考 `docs/sql/mobile_menu_oa_start.sql`:解析「移动端菜单」根(`parent_id='0'` 且名称含「移动端」),幂等 `INSERT`。
+
+新增脚本建议路径:`docs/sql/mobile_menu_oa_task_centers.sql`。
+
+| menu_name | id | path | perms | order_num |
+|-----------|-----|------|-------|-----------|
+| 草稿箱 | `MINI000000000000000000000000OA002` | `/pages/oa/drafts/index` | `mini:oa:drafts` | 20 |
+| 待办中心 | `MINI000000000000000000000000OA003` | `/pages/oa/pending/index` | `mini:oa:pending` | 30 |
+| 已办中心 | `MINI000000000000000000000000OA004` | `/pages/oa/done/index` | `mini:oa:done` | 40 |
+| 我发起的 | `MINI000000000000000000000000OA005` | `/pages/oa/mine/index` | `mini:oa:mine` | 50 |
+| 抄送我的 | `MINI000000000000000000000000OA006` | `/pages/oa/cc/index` | `mini:oa:cc` | 60 |
+
+约定:`menu_type='C'`、`is_frame=1`、`visible/status='1'`、`tenant_no='zkxw'`、`NOT EXISTS` 防重复;角色在 Web「角色管理」勾选后,首页 `MenuGrid` 经 `fetchMyMobileMenus()` 展示。
+
+发起审批既有:`MINI000000000000000000000000OA001` / `mini:oa:start` / order 10,本脚本不改动。
+
+### 4.1 宫格图标资源
+
+PNG 放在 `mobile-h5/static/menu-icons/`(颜色已烘焙,宫格 `iconBg: transparent`)。后端 `mini_menu._mobile_menu_icon` 按 path 返回 icon 名,与 `van-icon` 的 `PNG_ICONS` 对齐:
+
+| 菜单 | 文件 | icon 名 |
+|------|------|---------|
+| 草稿箱 | `oa-drafts.png` | `oa-drafts` |
+| 待办中心 | `oa-pending.png` | `oa-pending` |
+| 已办中心 | `oa-done.png` | `oa-done` |
+| 我发起的 | `oa-mine.png` | `oa-mine` |
+| 抄送我的 | `oa-cc.png` | `oa-cc` |
+
+---
+
+## 5. 小程序列表页
+
+### 5.1 路由与 web-view
+
+- 五页:`pages/oa/{drafts,pending,done,mine,cc}/index`
+- 通用 web-view:扩展现有 `pages/oa/start/webview` 或抽 `pages/oa/webview/index`,支持 start 与 detail 两类 URL。
+- `utils/oa-h5.js` 增补 `buildOaDetailUrl({ instanceId, taskId, mode })`;标题仍用 storage 暂存,避免中文进 URL。
+
+### 5.2 列表行为摘要
+
+| 页 | 筛选 | 行/工具栏 | 打开 H5 |
+|----|------|-----------|---------|
+| 草稿 | 流程名 | 编辑、提交、删;批量删 | `/oa-start?draftId=` |
+| 待办 | 流程名、申请人 | 进入办理 | `/oa-detail?mode=approve&instanceId=&taskId=` |
+| 已办 | 流程名、申请人 | 查看 | `mode=view` |
+| 我发起 | 流程名 | 查看、打印、复制重开(终态) | view;复制 → start(预填策略见 §6) |
+| 抄送 | 流程名、申请人、阅读状态 | 查看、已读、批量已读、打印 | view;打开时/前 mark-read |
+
+共用:卡片列表、下拉刷新、分页;扩展 `mobile-h5/utils/oa-api.js`。
+
+---
+
+## 6. H5 `/oa-detail`
+
+### 6.1 Query
+
+| 参数 | 说明 |
+|------|------|
+| `instanceId` | 必填;`GET /instances/{id}` |
+| `taskId` | 待办办理时定位任务 |
+| `mode` | `view` \| `approve` |
+| `token` | 与 `/oa-start` 相同,启动时捕获 |
+
+### 6.2 结构
+
+1. 顶:标题 + 状态 Tag
+2. 中:form-create **disabled** + 审批流进度(移动端已有/可对齐的 Flow 组件)
+3. 底:操作条(全屏 web-view,操作必须在 H5)
+
+### 6.3 操作可见性(对齐 Web `instances/detail.vue`)
+
+| 操作 | 条件 |
+|------|------|
+| 同意 / 拒绝 | `mode=approve` 且存在当前用户可办 task |
+| 打回 | 同上且 `allowReturn !== false` |
+| 加签 | 同上且 `allowAddSign !== false`;选人 |
+| 转交 | 同上;选人排除本人与本节点已有待办;可选说明 |
+| 撤销 | 当前用户为发起人且实例进行中 |
+| 复制并重新发起 | 发起人且终态(returned / cancelled / rejected);跳转发起填写并预填 |
+| 打印 | 我发起的、抄送场景需要;移植 Web `exportOaApprovalPdf` 思路 |
+
+成功后:`postMessage({ source: 'zkxw-oa-h5', action: 'done'|'back' })` → 小程序返回并刷新列表/角标。
+
+### 6.4 复制并重新发起
+
+优先与 Web 一致:携带原实例表单数据进入新发起(`/oa-start` + `appId` + 预填来源)。具体 query/storage 约定在实现计划中与现有 `OaStart.vue` 对齐,避免破坏已有草稿链路。
+
+### 6.5 打印风险
+
+小程序 web-view 内 PDF 能力受限。S6 单独验收;若真机受限,允许「H5 生成文件 + 明确引导」兜底,**不阻塞** S0–S5。
+
+---
+
+## 7. 分步交付(S0–S7)
+
+| 步骤 | 内容 | 验收 |
+|------|------|------|
+| S0 | 菜单 SQL;`pages.json`;通用 web-view / `buildOaDetailUrl` | 授权后菜单可见;未授权不可见 |
+| S1 | 草稿箱全量已确认能力 + 草稿角标 | 编辑/提交/删/批量删可用 |
+| S2 | `/oa-detail` 只读;已办 / 我发起 / 抄送进 view | 同实例只读正确;抄送打开已读 |
+| S3 | 待办列表 + approve 五动作 + 待办角标 | 办理后待办消失;进度正确 |
+| S4 | 我发起的撤销、复制重开(列表+详情) | 终态可复制;进行中可撤销 |
+| S5 | 抄送阅读筛选、未读样式、单条/批量已读、未读角标 | 与 Web 语义一致 |
+| S6 | 打印(我发起的 / 抄送) | 真机可打开或有明确失败提示 |
+| S7 | 角标汇总与返回刷新 | 办理/已读后角标更新 |
+
+---
+
+## 8. 测试要点(摘要)
+
+- 菜单:执行 SQL 后角色勾选/取消,首页宫格增减正确。
+- 草稿:暂存→草稿箱可见→编辑提交→出现在「我发起的」、草稿消失。
+- 待办:同意/拒绝/打回/加签/转交各至少一条主路径;转交人选人约束正确。
+- 已办/抄送:仅只读,无办理按钮。
+- 我发起:撤销后状态与 Web 一致;复制重开可提交新实例。
+- 抄送:未读角标、批量已读、打开详情自动已读。
+- 色值与 web-view 关闭桥接回归(发起审批不被破坏)。
+
+---
+
+## 9. 文档与实现衔接
+
+- 本文为需求/架构权威;实现计划:`docs/superpowers/plans/2026-08-12-mobile-oa-task-centers.md`(Task 1–13,对应 S0–S7)。
+- 变更若调整范围,先改本文再改计划与代码。
diff --git a/mobile-h5/components/AttachList.vue b/mobile-h5/components/AttachList.vue
index dead3d27..18a970d5 100644
--- a/mobile-h5/components/AttachList.vue
+++ b/mobile-h5/components/AttachList.vue
@@ -6,7 +6,7 @@
添加附件
支持 PDF / 图片 / Office,可多选上传
-
+
查看
-
+
处理
diff --git a/mobile-h5/components/MenuGrid.vue b/mobile-h5/components/MenuGrid.vue
index 75584818..c45d7f00 100644
--- a/mobile-h5/components/MenuGrid.vue
+++ b/mobile-h5/components/MenuGrid.vue
@@ -6,8 +6,12 @@
class="menu-card"
@click="$emit('select', m)"
>
-
@@ -32,10 +36,21 @@
iconFg(m) {
return this.colors(m).iconFg || ''
},
+ isPlainIcon(m) {
+ const bg = String(this.colors(m).iconBg || '').trim().toLowerCase()
+ return bg === 'transparent' || bg === 'none'
+ },
+ plainClass(m) {
+ return this.isPlainIcon(m) ? 'menu-icon--plain' : ''
+ },
+ iconSize(m) {
+ return this.isPlainIcon(m) ? '56rpx' : '52rpx'
+ },
iconBoxStyle(m) {
const { iconBg, iconFg } = this.colors(m)
const style = {}
if (iconBg) style.background = iconBg
+ if (this.isPlainIcon(m)) style.boxShadow = 'none'
if (iconFg) style.color = iconFg
return style
},
diff --git a/mobile-h5/components/OaAppIcon.vue b/mobile-h5/components/OaAppIcon.vue
index f579d000..4109cf89 100644
--- a/mobile-h5/components/OaAppIcon.vue
+++ b/mobile-h5/components/OaAppIcon.vue
@@ -22,7 +22,7 @@ export default {
name: 'OaAppIcon',
props: {
iconUrl: { type: String, default: '' },
- /** sm=32, card=36, md=56 */
+ /** xs=28, sm=32, card=36, md=40, lg=48 */
size: { type: String, default: 'sm' }
},
computed: {
@@ -51,35 +51,50 @@ export default {
align-items: center;
justify-content: center;
}
+.oa-app-icon--xs {
+ width: 28px;
+ height: 28px;
+ border-radius: 7px;
+}
.oa-app-icon--sm {
width: 32px;
height: 32px;
border-radius: 8px;
- background: #f1f5f9;
}
.oa-app-icon--card {
width: 36px;
height: 36px;
border-radius: 8px;
- background: #f1f5f9;
}
.oa-app-icon--md {
- width: 56px;
- height: 56px;
+ width: 40px;
+ height: 40px;
+ border-radius: 10px;
+}
+.oa-app-icon--lg {
+ width: 48px;
+ height: 48px;
border-radius: 12px;
- background: #f1f5f9;
}
.oa-app-icon__glyph {
width: 16px;
height: 16px;
}
+.oa-app-icon--xs .oa-app-icon__glyph {
+ width: 14px;
+ height: 14px;
+}
.oa-app-icon--card .oa-app-icon__glyph {
width: 18px;
height: 18px;
}
.oa-app-icon--md .oa-app-icon__glyph {
- width: 26px;
- height: 26px;
+ width: 20px;
+ height: 20px;
+}
+.oa-app-icon--lg .oa-app-icon__glyph {
+ width: 24px;
+ height: 24px;
}
.oa-app-icon__photo {
width: 100%;
diff --git a/mobile-h5/components/van-icon/van-icon.vue b/mobile-h5/components/van-icon/van-icon.vue
index 114ec3b1..a5d34d62 100644
--- a/mobile-h5/components/van-icon/van-icon.vue
+++ b/mobile-h5/components/van-icon/van-icon.vue
@@ -35,6 +35,8 @@
'eye-o': '◎',
down: '↓',
checked: '✓',
+ search: '⌕',
+ clear: '×',
'manager-o': '☺',
'clock-o': '◷',
description: '📄',
@@ -47,7 +49,12 @@
/** 本地位图(小程序 真机可靠);颜色已烘焙在 PNG 内 */
const PNG_ICONS = {
- approve: '/static/menu-icons/start-approval.png'
+ approve: '/static/menu-icons/start-approval.png',
+ 'oa-drafts': '/static/menu-icons/oa-drafts.png',
+ 'oa-pending': '/static/menu-icons/oa-pending.png',
+ 'oa-done': '/static/menu-icons/oa-done.png',
+ 'oa-mine': '/static/menu-icons/oa-mine.png',
+ 'oa-cc': '/static/menu-icons/oa-cc.png'
}
export default {
diff --git a/mobile-h5/components/van-tag/van-tag.vue b/mobile-h5/components/van-tag/van-tag.vue
index 656c67cc..cbc93023 100644
--- a/mobile-h5/components/van-tag/van-tag.vue
+++ b/mobile-h5/components/van-tag/van-tag.vue
@@ -57,42 +57,42 @@
}
.v-tag.is-primary {
- background: #f0fdf4;
- color: #15803d;
+ background: #ecfdf5;
+ color: #16a34a;
}
.v-tag.is-success {
- background: #f0fdf4;
- color: #189b65;
+ background: #ecfdf5;
+ color: #10b981;
}
.v-tag.is-warning {
- background: #fff7e8;
- color: #cf8a19;
+ background: #fffbeb;
+ color: #d97706;
}
.v-tag.is-danger {
- background: #fdecec;
- color: #d14343;
+ background: #fef2f2;
+ color: #ef4444;
}
.v-tag.is-plain.is-primary,
.v-tag.is-plain.is-success {
background: transparent;
- border-color: #16a34a;
- color: #16a34a;
+ border-color: #22c55e;
+ color: #22c55e;
}
.v-tag.is-plain.is-warning {
background: transparent;
- border-color: #cf8a19;
- color: #cf8a19;
+ border-color: #d97706;
+ color: #d97706;
}
.v-tag.is-plain.is-danger {
background: transparent;
- border-color: #d14343;
- color: #d14343;
+ border-color: #ef4444;
+ color: #ef4444;
}
.v-tag.is-plain.is-default {
diff --git a/mobile-h5/pages.json b/mobile-h5/pages.json
index af8015d6..f039838f 100644
--- a/mobile-h5/pages.json
+++ b/mobile-h5/pages.json
@@ -36,24 +36,24 @@
"path": "pages/oa/form-sink/index",
"style": {
"navigationBarTitleText": "表单水槽测试",
- "navigationBarBackgroundColor": "#16a34a",
- "navigationBarTextStyle": "white"
+ "navigationBarBackgroundColor": "#ecfdf5",
+ "navigationBarTextStyle": "black"
}
},
{
"path": "pages/oa/start/index",
"style": {
"navigationBarTitleText": "发起审批",
- "navigationBarBackgroundColor": "#16a34a",
- "navigationBarTextStyle": "white"
+ "navigationBarBackgroundColor": "#ecfdf5",
+ "navigationBarTextStyle": "black"
}
},
{
"path": "pages/oa/start/webview",
"style": {
"navigationBarTitleText": "填写审批",
- "navigationBarBackgroundColor": "#16a34a",
- "navigationBarTextStyle": "white"
+ "navigationBarBackgroundColor": "#ecfdf5",
+ "navigationBarTextStyle": "black"
}
},
{
@@ -86,10 +86,10 @@
}
],
"globalStyle": {
- "navigationBarTextStyle": "white",
+ "navigationBarTextStyle": "black",
"navigationBarTitleText": "中科兴皖",
- "navigationBarBackgroundColor": "#16a34a",
- "backgroundColor": "#f5f8f7"
+ "navigationBarBackgroundColor": "#ecfdf5",
+ "backgroundColor": "#ffffff"
},
"uniIdRouter": {}
}
diff --git a/mobile-h5/pages/demo/comps.vue b/mobile-h5/pages/demo/comps.vue
index 5abf2ab7..fbd283fb 100644
--- a/mobile-h5/pages/demo/comps.vue
+++ b/mobile-h5/pages/demo/comps.vue
@@ -13,14 +13,14 @@
按钮 Button
- 主按钮
- 描边
+ 主按钮
+ 描边
次要
危险
状态 Tag
待处理
- 进行中
+ 进行中
已完成
异常
@@ -45,7 +45,7 @@
left-icon="volume-o"
text="移动端菜单在「菜单管理 → 移动端菜单」下配置,按角色授权后展示。"
background="#f0fdf4"
- color="#15803d"
+ color="#16a34a"
/>
- 加载中
+ 加载中
反馈
-
+
Toast
-
+
Dialog
@@ -99,7 +99,7 @@
uni.showModal({
title: '确认提交',
content: '提交后将进入审批流程,是否继续?',
- confirmColor: '#16a34a'
+ confirmColor: '#22c55e'
})
}
}
diff --git a/mobile-h5/pages/demo/detail.vue b/mobile-h5/pages/demo/detail.vue
index 26321de7..8e8726bf 100644
--- a/mobile-h5/pages/demo/detail.vue
+++ b/mobile-h5/pages/demo/detail.vue
@@ -48,7 +48,7 @@
业务进度
-
+
{{ s }}
@@ -94,7 +94,7 @@
diff --git a/mobile-h5/pages/demo/form.vue b/mobile-h5/pages/demo/form.vue
index 5e335ff5..10ff4fac 100644
--- a/mobile-h5/pages/demo/form.vue
+++ b/mobile-h5/pages/demo/form.vue
@@ -32,7 +32,7 @@
/>
-
+
@@ -62,7 +62,7 @@
diff --git a/mobile-h5/pages/demo/list.vue b/mobile-h5/pages/demo/list.vue
index a9bca192..00d296b8 100644
--- a/mobile-h5/pages/demo/list.vue
+++ b/mobile-h5/pages/demo/list.vue
@@ -26,6 +26,7 @@
shape="round"
background="transparent"
placeholder="搜索单号 / 名称 / 负责人"
+ @update:model-value="onKeywordInput"
/>
@@ -79,6 +80,9 @@
},
methods: {
tabCount,
+ onKeywordInput(v) {
+ this.keyword = v == null ? '' : String(v)
+ },
goHome() {
uni.reLaunch({ url: '/pages/index/index' })
},
diff --git a/mobile-h5/pages/index/index.vue b/mobile-h5/pages/index/index.vue
index 6e37f016..bbf0cdb1 100644
--- a/mobile-h5/pages/index/index.vue
+++ b/mobile-h5/pages/index/index.vue
@@ -22,7 +22,7 @@
:circular="banners.length > 1"
:indicator-dots="banners.length > 1"
indicator-color="rgba(255,255,255,0.45)"
- indicator-active-color="#16a34a"
+ indicator-active-color="#22c55e"
:autoplay="false"
>
@@ -86,8 +86,7 @@
title: '发起审批',
icon: 'approve',
path: '/pages/oa/start/index',
- iconBg: '#ffe4e6',
- iconFg: '#e11d48'
+ iconBg: 'transparent'
},
{
id: 'oa-form-sink',
diff --git a/mobile-h5/pages/oa/start/index.vue b/mobile-h5/pages/oa/start/index.vue
index b7b036d9..a6e82e68 100644
--- a/mobile-h5/pages/oa/start/index.vue
+++ b/mobile-h5/pages/oa/start/index.vue
@@ -1,35 +1,49 @@
-
-
+
+
+
+
+
+ ×
+
+
+
+
+
+
+ 全部
+
+
+ {{ g.groupName }}
+
+
+
-
-
- 全部
-
-
- {{ g.groupName }}
-
-
-
- 选择一个审批单即可发起
-
@@ -45,7 +59,6 @@
{{ sec.name }}
-
{{ sec.apps.length }} 个表单
@@ -55,18 +68,8 @@
class="oa-app-card"
@click="onPick(row)"
>
-
-
-
- {{ row.appName }}
- {{
- row.description
- }}
-
-
-
- 发起
-
+
+ {{ row.appName }}
@@ -77,7 +80,7 @@
diff --git a/mobile-oa-h5/src/oa/components/OaDeptSelect.vue b/mobile-oa-h5/src/oa/components/OaDeptSelect.vue
index 69a17ce8..4e767a74 100644
--- a/mobile-oa-h5/src/oa/components/OaDeptSelect.vue
+++ b/mobile-oa-h5/src/oa/components/OaDeptSelect.vue
@@ -23,38 +23,70 @@
input-align="left"
placeholder="搜索部门名称"
/>
-
+
-
-
-
-
-
+
+
+
+
+
-
+
@@ -62,9 +94,10 @@
-
diff --git a/mobile-oa-h5/src/oa/components/OaFlowPreview.vue b/mobile-oa-h5/src/oa/components/OaFlowPreview.vue
index 9452c98f..4f0e918a 100644
--- a/mobile-oa-h5/src/oa/components/OaFlowPreview.vue
+++ b/mobile-oa-h5/src/oa/components/OaFlowPreview.vue
@@ -83,24 +83,33 @@
shape="round"
label-align="left"
input-align="left"
- placeholder="搜索姓名或手机号"
+ placeholder="搜索姓名、工号或手机号"
/>
-
-
-
-
-
- {{ u.nickName || u.id }}
- {{ u.phoneNo }}
-
-
+
+
+ {{ shortName(u.nickName || u.id) }}
+
+
+
{{ u.nickName || u.id }}
+
{{ pickerSub(u) }}
+
+
@@ -113,6 +122,8 @@
import { computed, ref } from 'vue'
import OaPulseLoading from './OaPulseLoading.vue'
+const AVATAR_COLORS = ['#3b82f6', '#22c55e', '#8b5cf6', '#f59e0b', '#06b6d4', '#ef4444']
+
const props = defineProps({
steps: { type: Array, default: () => [] },
resolved: { type: Boolean, default: true },
@@ -141,12 +152,37 @@ const filteredUsers = computed(() => {
String(u.phoneNo || '')
.toLowerCase()
.includes(q) ||
+ String(u.userNo || '')
+ .toLowerCase()
+ .includes(q) ||
String(u.id || '')
.toLowerCase()
.includes(q)
)
})
+function pickerSub(u) {
+ const phone = String(u?.phoneNo || '').trim()
+ if (phone) return phone
+ const userNo = String(u?.userNo || '').trim()
+ const name = String(u?.nickName || '').trim()
+ if (userNo && userNo !== name) return userNo
+ return ''
+}
+
+function shortName(name) {
+ const s = String(name || '').trim()
+ if (!s) return '?'
+ return s.length <= 2 ? s : s.slice(-2)
+}
+
+function avatarColor(name) {
+ const s = String(name || '')
+ let h = 0
+ for (let i = 0; i < s.length; i++) h = (h + s.charCodeAt(i) * 17) % AVATAR_COLORS.length
+ return AVATAR_COLORS[h]
+}
+
function isInitiatorSelect(s) {
const types =
Array.isArray(s.assigneeTypes) && s.assigneeTypes.length
@@ -226,19 +262,19 @@ function confirmPicker() {
padding: 14px 12px 8px;
background: #fff;
border-radius: 12px;
- border: 1px solid var(--border, #dcebe4);
+ border: 1px solid var(--border, #d8f3e5);
}
.oa-flow-preview__title {
margin-bottom: 12px;
font-size: 15px;
font-weight: 700;
- color: var(--text-primary, #1f2a26);
+ color: var(--text-primary, #0f291e);
}
.oa-flow-preview__loading,
.oa-flow-preview__pending,
.oa-flow-preview__empty {
padding: 12px 4px 16px;
- color: var(--text-muted, #6b7b74);
+ color: var(--text-muted, #5f7a6d);
font-size: 13px;
line-height: 1.5;
}
@@ -262,7 +298,7 @@ function confirmPicker() {
height: 10px;
margin-top: 6px;
border-radius: 50%;
- background: var(--brand-green, #16a34a);
+ background: var(--brand-green, #22c55e);
}
.oa-flow-dot.end {
background: #94a3b8;
@@ -280,44 +316,52 @@ function confirmPicker() {
}
.oa-flow-card__name-row {
display: flex;
- flex-wrap: wrap;
+ flex-wrap: nowrap;
gap: 6px;
align-items: center;
+ min-width: 0;
}
.oa-flow-card__name {
+ flex-shrink: 0;
font-size: 14px;
font-weight: 600;
- color: var(--text-primary, #1f2a26);
+ color: var(--text-primary, #0f291e);
}
.oa-flow-badge {
+ flex-shrink: 0;
padding: 1px 6px;
border-radius: 4px;
font-size: 11px;
}
.oa-flow-badge.and {
- color: #15803d;
- background: #f0fdf4;
+ color: #22c55e;
+ background: #ecfdf5;
}
.oa-flow-badge.or {
color: #b45309;
background: #fff7e8;
}
.oa-flow-card__meta {
- color: var(--text-muted, #6b7b74);
+ min-width: 0;
+ overflow: hidden;
+ color: var(--text-muted, #5f7a6d);
font-size: 12px;
+ text-overflow: ellipsis;
+ white-space: nowrap;
}
.oa-flow-card__sep {
+ flex-shrink: 0;
color: #cbd5e1;
font-size: 12px;
}
.oa-flow-card__placeholder {
- color: var(--text-muted, #6b7b74);
+ color: var(--text-muted, #5f7a6d);
font-size: 12px;
line-height: 1.2;
}
.oa-flow-card__err {
margin-top: 4px;
- color: #d14343;
+ color: #ef4444;
font-size: 12px;
}
.oa-flow-card__select {
@@ -347,9 +391,9 @@ function confirmPicker() {
align-items: center;
padding: 4px 8px;
border-radius: 999px;
- background: #f5f8f7;
+ background: #ffffff;
font-size: 12px;
- color: var(--text-primary, #1f2a26);
+ color: var(--text-primary, #0f291e);
}
.oa-person-chip__dot {
display: inline-flex;
@@ -358,7 +402,7 @@ function confirmPicker() {
width: 18px;
height: 18px;
border-radius: 50%;
- background: var(--brand-green, #16a34a);
+ background: var(--brand-green, #22c55e);
color: #fff;
font-size: 11px;
}
@@ -382,7 +426,7 @@ function confirmPicker() {
border: 1px solid #e2e8f0;
border-radius: 6px;
background: #fff;
- color: var(--text-primary, #1f2a26);
+ color: var(--text-primary, #0f291e);
font-size: 14px;
line-height: 1;
}
@@ -397,32 +441,16 @@ function confirmPicker() {
align-items: center;
justify-content: space-between;
padding: 14px 16px;
- border-bottom: 1px solid var(--border, #dcebe4);
+ border-bottom: 1px solid var(--border, #d8f3e5);
}
.oa-flow-picker__ok {
- color: var(--brand-green, #16a34a);
+ color: var(--brand-green, #22c55e);
font-weight: 600;
}
.oa-flow-picker__list {
flex: 1;
- padding: 12px;
+ padding: 0;
overflow: auto;
-}
-.oa-flow-picker__list .van-cell {
- margin-bottom: 10px;
- padding: 12px 14px;
- border-radius: 10px;
-}
-.oa-flow-picker__check {
- margin-right: 8px;
-}
-.oa-flow-picker__name {
- font-size: 14px;
- color: var(--text-primary, #1f2a26);
-}
-.oa-flow-picker__phone {
- margin-left: 8px;
- font-size: 13px;
- color: var(--text-muted, #6b7b74);
+ background: #fff;
}
diff --git a/mobile-oa-h5/src/oa/components/OaOwnerDeptSelect.vue b/mobile-oa-h5/src/oa/components/OaOwnerDeptSelect.vue
index a72447a1..10783cd6 100644
--- a/mobile-oa-h5/src/oa/components/OaOwnerDeptSelect.vue
+++ b/mobile-oa-h5/src/oa/components/OaOwnerDeptSelect.vue
@@ -1,12 +1,14 @@
-
+
+
+
diff --git a/mobile-oa-h5/src/oa/components/OaTripItinerary.vue b/mobile-oa-h5/src/oa/components/OaTripItinerary.vue
index e953f093..7a39c760 100644
--- a/mobile-oa-h5/src/oa/components/OaTripItinerary.vue
+++ b/mobile-oa-h5/src/oa/components/OaTripItinerary.vue
@@ -1,8 +1,9 @@
+
- 行程 {{ idx + 1 }}
+ 行程 {{ idx + 1 }}
+
patch(idx, { fromCity: v })"
+ placeholder="请输入出发城市"
+ @update:model-value="(v) => patchLeg(idx, { fromCity: String(v || '') })"
/>
patch(idx, { toCity: v })"
- />
-
-
-
-
- patch(idx, { duration: v === '' ? null : Number(v) })"
+ placeholder="请输入目的城市"
+ @update:model-value="(v) => patchLeg(idx, { toCity: String(v || '') })"
/>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ onDurationChange(idx, v)"
+ />
+
+
+
添加行程
@@ -128,7 +156,8 @@
diff --git a/mobile-oa-h5/src/oa/kit-runtime.js b/mobile-oa-h5/src/oa/kit-runtime.js
index edfc0589..5975fff7 100644
--- a/mobile-oa-h5/src/oa/kit-runtime.js
+++ b/mobile-oa-h5/src/oa/kit-runtime.js
@@ -25,6 +25,7 @@ export {
export {
applyConfirmationKitDateLinkage,
applyConfirmationKitDefaults,
+ clearConfirmationDateManualAndRelink,
ensureConfirmationKitVisibility,
listConfirmationKitBindings,
listManualConfirmationDateFields,
diff --git a/mobile-oa-h5/src/oa/kit/oa-leave-hours.js b/mobile-oa-h5/src/oa/kit/oa-leave-hours.js
index beeee4c1..8c0c0891 100644
--- a/mobile-oa-h5/src/oa/kit/oa-leave-hours.js
+++ b/mobile-oa-h5/src/oa/kit/oa-leave-hours.js
@@ -4,7 +4,7 @@ const DEFAULT_LEAVE_WORK_HOURS = {
afternoonStart: "13:00",
afternoonEnd: "17:30"
};
-const DT_RE = /^(\d{4})-(\d{2})-(\d{2})[ T](\d{2}):(\d{2})$/;
+const DT_RE = /^(\d{4})-(\d{2})-(\d{2})[ T](\d{2}):(\d{2})(?::\d{2})?$/;
function parseHm(hm) {
const m = String(hm || "").trim().match(/^(\d{1,2}):(\d{2})$/);
if (!m) return null;
diff --git a/mobile-oa-h5/src/oa/kit/oa-overtime-hours.js b/mobile-oa-h5/src/oa/kit/oa-overtime-hours.js
index 7395efa8..fb7189e9 100644
--- a/mobile-oa-h5/src/oa/kit/oa-overtime-hours.js
+++ b/mobile-oa-h5/src/oa/kit/oa-overtime-hours.js
@@ -4,7 +4,7 @@ const DEFAULT_OVERTIME_WINDOWS = {
restBreakStart: "11:30",
restBreakEnd: "13:30"
};
-const DT_RE = /^(\d{4})-(\d{2})-(\d{2})[ T](\d{2}):(\d{2})$/;
+const DT_RE = /^(\d{4})-(\d{2})-(\d{2})[ T](\d{2}):(\d{2})(?::\d{2})?$/;
function parseHm(hm) {
const m = String(hm || "").trim().match(/^(\d{1,2}):(\d{2})$/);
if (!m) return null;
diff --git a/mobile-oa-h5/src/oa/kitchen-sink-rule.js b/mobile-oa-h5/src/oa/kitchen-sink-rule.js
index 15da18ab..60e7b0ff 100644
--- a/mobile-oa-h5/src/oa/kitchen-sink-rule.js
+++ b/mobile-oa-h5/src/oa/kitchen-sink-rule.js
@@ -44,10 +44,32 @@ export const kitchenSinkRule = [
{
type: 'datePicker',
field: 'bizDate',
- title: '日期',
+ title: '日期(年月日)',
value: '',
props: { type: 'date' }
},
+ {
+ type: 'datePicker',
+ field: 'bizDateTime',
+ title: '日期时间(年月日时分秒)',
+ value: '',
+ props: {
+ type: 'datetime',
+ format: 'YYYY-MM-DD HH:mm:ss',
+ valueFormat: 'YYYY-MM-DD HH:mm:ss'
+ }
+ },
+ {
+ type: 'datePicker',
+ field: 'bizDateTimeMinute',
+ title: '日期时间(到分)',
+ value: '',
+ props: {
+ type: 'datetime',
+ format: 'YYYY-MM-DD HH:mm',
+ valueFormat: 'YYYY-MM-DD HH:mm'
+ }
+ },
{ type: 'upload', field: 'files', title: '附件 upload', value: [] },
{ type: 'money', field: 'amount', title: '金额 money', value: '' },
{
@@ -120,14 +142,14 @@ export const kitchenSinkRule = [
field: 'leaveStart',
title: '开始时间',
$required: true,
- props: { type: 'datetime' }
+ props: { type: 'datetime', format: 'YYYY-MM-DD HH:mm', valueFormat: 'YYYY-MM-DD HH:mm' }
},
{
type: 'datePicker',
field: 'leaveEnd',
title: '结束时间',
$required: true,
- props: { type: 'datetime' }
+ props: { type: 'datetime', format: 'YYYY-MM-DD HH:mm', valueFormat: 'YYYY-MM-DD HH:mm' }
},
{
type: 'inputNumber',
@@ -161,14 +183,14 @@ export const kitchenSinkRule = [
field: 'otStart',
title: '开始时间',
$required: true,
- props: { type: 'datetime' }
+ props: { type: 'datetime', format: 'YYYY-MM-DD HH:mm', valueFormat: 'YYYY-MM-DD HH:mm' }
},
{
type: 'datePicker',
field: 'otEnd',
title: '结束时间',
$required: true,
- props: { type: 'datetime' }
+ props: { type: 'datetime', format: 'YYYY-MM-DD HH:mm', valueFormat: 'YYYY-MM-DD HH:mm' }
},
{
type: 'inputNumber',
diff --git a/mobile-oa-h5/src/oa/register-components.js b/mobile-oa-h5/src/oa/register-components.js
index b962f4d2..b052c9d9 100644
--- a/mobile-oa-h5/src/oa/register-components.js
+++ b/mobile-oa-h5/src/oa/register-components.js
@@ -10,6 +10,7 @@ import OaOwnerPersonSelect from './components/OaOwnerPersonSelect.vue'
import OaOwnerDeptSelect from './components/OaOwnerDeptSelect.vue'
import OaKitShell from './components/OaKitShell.vue'
import OaTripItinerary from './components/OaTripItinerary.vue'
+import OaDatePicker from './components/OaDatePicker.vue'
/** 与 web-admin type 名对齐 */
const REAL_COMPONENTS = {
@@ -37,4 +38,7 @@ export function registerOaComponents(formCreate) {
formCreate.component(name, comp)
}
formCreate.component('fcUpload', OaUploadField)
+ // 覆盖 @form-create/vant 内置日期:按 props.type 区分 date / datetime
+ formCreate.component('datePicker', OaDatePicker)
+ formCreate.component('fcDatePicker', OaDatePicker)
}
diff --git a/mobile-oa-h5/src/styles/oa-form.css b/mobile-oa-h5/src/styles/oa-form.css
index cd850837..3fe95d31 100644
--- a/mobile-oa-h5/src/styles/oa-form.css
+++ b/mobile-oa-h5/src/styles/oa-form.css
@@ -1,24 +1,33 @@
/* form-create + Vant:上下布局与左对齐兜底 */
.form-create-m .van-field__label--top {
width: 100%;
- margin-bottom: 6px;
+ margin-bottom: 4px;
text-align: left;
- color: var(--text-primary, #1f2a26);
- font-size: 13px;
+ color: var(--text-primary, #0f291e);
+ font-size: 12px;
font-weight: 600;
}
.form-create-m .van-field__control {
text-align: left !important;
+ font-size: 13px;
+ min-height: 22px;
+ line-height: 22px;
}
.form-create-m .van-field__control::placeholder {
text-align: left;
- color: var(--text-muted, #6b7b74);
+ color: var(--text-muted, #5f7a6d);
}
.form-create-m .fc-form-item {
- margin-bottom: 4px;
+ margin-bottom: 2px;
+}
+
+/* 表单项整体更紧凑 */
+.form-create-m .fc-form-item.van-cell {
+ padding-top: 8px;
+ padding-bottom: 8px;
}
/* 自定义控件 / 内置选择触发条:占满整行,箭头贴右 */
@@ -28,6 +37,7 @@
.form-create-m ._fc-calendar,
.form-create-m ._fc-cascader,
.form-create-m .oa-pick,
+.form-create-m .oa-owner-field,
.form-create-m .oa-money,
.form-create-m .oa-number,
.form-create-m .oa-recv,
@@ -58,6 +68,40 @@
align-items: center;
width: 100%;
box-sizing: border-box;
+ min-height: 32px;
+ padding-top: 5px;
+ padding-bottom: 5px;
+ padding-left: 0;
+ padding-right: 0;
+}
+
+.form-create-m ._fc-select > .van-cell .van-field__control,
+.form-create-m ._fc-date-picker > .van-cell .van-field__control,
+.form-create-m ._fc-time-picker > .van-cell .van-field__control,
+.form-create-m ._fc-calendar > .van-cell .van-field__control,
+.form-create-m ._fc-cascader > .van-cell .van-field__control,
+.form-create-m .oa-pick > .van-cell .van-field__control,
+.form-create-m .oa-money > .van-cell .van-field__control,
+.form-create-m .oa-number > .van-cell .van-field__control,
+.form-create-m .oa-recv > .van-cell .van-field__control {
+ font-size: 13px;
+ min-height: 22px;
+ line-height: 22px;
+}
+
+/*
+ * 归属人 / 归属部门:不可改选时仍用 disabled 防点开,但展示色用正文色(勿灰字难读)。
+ * Vant disabled 会设 -webkit-text-fill-color,须一并覆盖。
+ */
+.form-create-m .oa-owner-field.is-locked .van-field__control,
+.form-create-m .oa-owner-field.is-locked .van-field__control:disabled {
+ color: var(--text-primary, #0f291e) !important;
+ -webkit-text-fill-color: var(--text-primary, #0f291e) !important;
+ opacity: 1;
+}
+
+.form-create-m .oa-owner-field.is-locked .van-cell__right-icon {
+ color: #cbd5e1;
}
.form-create-m ._fc-select > .van-cell > .van-cell__title,
@@ -94,8 +138,9 @@
.form-create-m .oa-pick > .van-cell > .van-cell__right-icon,
.form-create-m .oa-recv > .van-cell > .van-cell__right-icon {
flex: 0 0 auto;
- margin-left: 8px;
+ margin-left: 6px;
order: 3;
+ font-size: 14px;
}
/* 行程内带自身 label 的 Field:标题在上,箭头仍在内容行右侧 */
@@ -104,22 +149,77 @@
flex-wrap: wrap;
align-items: center;
width: 100%;
+ box-sizing: border-box;
+ background: transparent;
+ padding-top: 0;
+ padding-bottom: 0;
+ padding-left: 0;
+ padding-right: 0;
+}
+
+.form-create-m .oa-trip .van-field--label-top::after {
+ display: none;
}
.form-create-m .oa-trip .van-field--label-top > .van-cell__title {
flex: 0 0 100%;
width: 100%;
+ max-width: 100%;
+ margin-bottom: 4px;
}
+/*
+ * 内容行:value 吃剩余宽度,right-icon 固定同行右侧。
+ * 窄列(开始/结束时段)也勿让 value 占满导致箭头掉到下一行。
+ * value 与箭头强制同高并垂直居中,避免选中后文字相对箭头偏上/偏下。
+ */
.form-create-m .oa-trip .van-field--label-top > .van-cell__value {
- flex: 1 1 auto;
+ flex: 1 1 0% !important;
+ width: 0 !important;
+ max-width: none !important;
min-width: 0;
+ align-self: center;
+ display: flex;
+ align-items: center;
text-align: left;
}
+.form-create-m .oa-trip .van-field--label-top .van-field__body {
+ display: flex;
+ align-items: center;
+ width: 100%;
+ min-height: 22px;
+}
+
+.form-create-m .oa-trip .van-field--label-top .van-field__control {
+ height: 22px;
+ min-height: 22px;
+ line-height: 22px;
+ padding: 0;
+}
+
.form-create-m .oa-trip .van-field--label-top > .van-cell__right-icon {
flex: 0 0 auto;
- margin-left: 8px;
+ align-self: center;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ margin-left: 4px;
+ order: 3;
+ height: 22px;
+ font-size: 14px;
+ line-height: 22px;
+}
+
+/* 日期+时段同行:列间距,不再额外塞左右 padding(由卡片内边距统一) */
+.form-create-m .oa-trip .oa-trip__row .van-field {
+ padding-left: 0;
+ padding-right: 0;
+}
+
+.form-create-m .oa-trip .oa-trip__row .oa-trip__field--half.van-field {
+ padding-left: 0;
+ padding-right: 0;
}
/* 外层表单项:标题独占一行;内容区整行左对齐(箭头在内层控件右侧) */
@@ -130,7 +230,7 @@
.form-create-m .fc-form-item.van-field--label-top > .van-cell__title {
flex: 0 0 100%;
width: 100%;
- margin-bottom: 6px;
+ margin-bottom: 4px;
}
.form-create-m .fc-form-item.van-field--label-top > .van-cell__value {
@@ -156,7 +256,7 @@
align-items: center;
justify-content: space-between;
padding: 14px 16px;
- border-bottom: 1px solid var(--border, #dcebe4);
+ border-bottom: 1px solid var(--border, #d8f3e5);
}
.oa-pick__hd strong,
@@ -164,14 +264,14 @@
.oa-rel__hd strong {
font-size: 16px;
font-weight: 700;
- color: var(--text-primary, #1f2a26);
+ color: var(--text-primary, #0f291e);
}
.oa-pick__hd > span:first-child,
.oa-recv__hd > span:first-child,
.oa-rel__hd > span:first-child {
min-width: 48px;
- color: var(--text-muted, #6b7b74);
+ color: var(--text-muted, #5f7a6d);
font-size: 14px;
}
@@ -179,81 +279,152 @@
.oa-recv__ok,
.oa-rel__ok {
min-width: 48px;
- color: var(--brand-green, #16a34a);
+ color: var(--brand-green, #22c55e);
font-size: 14px;
font-weight: 600;
text-align: right;
}
+/* 方案 C · 分割线行表 */
.oa-pick__list,
.oa-recv__list,
.oa-rel__picker {
flex: 1;
- padding: 12px;
+ padding: 0;
overflow: auto;
- background: transparent;
+ background: #fff;
box-sizing: border-box;
}
-.oa-pick__list .van-cell,
-.oa-recv__list .van-cell,
-.oa-rel__picker .van-cell {
- align-items: center;
- margin-bottom: 10px;
- padding: 12px 14px;
- text-align: left;
- background: transparent;
- border-radius: 10px;
+/*
+ * 弹窗列表行(对齐关联审批单 oa-rel__pick-row)
+ * 人员 / 部门 / 收款账户 / 关联审批 共用,避免 van-cell 被表单样式污染
+ */
+.oa-sheet-row {
+ display: flex;
+ gap: 10px;
+ align-items: flex-start;
+ padding: 12px 16px;
+ background: #fff;
+ border-bottom: 1px solid #f1f5f9;
+ box-sizing: border-box;
}
-.oa-pick__list .van-cell:last-child,
-.oa-recv__list .van-cell:last-child,
-.oa-rel__picker .van-cell:last-child {
+.oa-sheet-row:last-child {
+ border-bottom: none;
+}
+
+.oa-sheet-row.is-selected {
+ background: var(--brand-green-soft, #ecfdf5);
+}
+
+.oa-sheet-row.is-selected .oa-sheet-row__title {
+ color: #15803d;
+}
+
+.oa-sheet-row--center {
+ align-items: center;
+}
+
+.oa-sheet-row__check {
+ flex-shrink: 0;
+ margin-top: 10px;
+ margin-right: 0;
+}
+
+.oa-sheet-row--center .oa-sheet-row__check {
+ margin-top: 0;
+}
+
+.oa-sheet-row__avatar {
+ display: flex;
+ flex-shrink: 0;
+ align-items: center;
+ justify-content: center;
+ width: 36px;
+ height: 36px;
+ margin-top: 2px;
+ border-radius: 8px;
+ color: #fff;
+ font-size: 12px;
+ font-weight: 600;
+ letter-spacing: 0.3px;
+}
+
+.oa-sheet-row__main {
+ flex: 1;
+ min-width: 0;
+}
+
+.oa-sheet-row__title {
+ margin: 0 0 4px;
+ color: var(--text-primary, #0f291e);
+ font-size: 14px;
+ font-weight: 600;
+ line-height: 1.4;
+ word-break: break-all;
+}
+
+.oa-sheet-row__title:last-child {
margin-bottom: 0;
}
-.oa-pick__list .van-cell::after,
-.oa-recv__list .van-cell::after,
-.oa-rel__picker .van-cell::after {
- display: none;
+.oa-sheet-row__meta {
+ overflow: hidden;
+ color: var(--text-muted, #5f7a6d);
+ font-size: 12px;
+ line-height: 1.45;
+ text-overflow: ellipsis;
+ white-space: nowrap;
}
-.oa-pick__list .van-cell__title,
-.oa-recv__list .van-cell__title,
-.oa-rel__picker .van-cell__title {
- text-align: left;
+.oa-sheet-row__exp {
+ display: inline-flex;
+ flex-shrink: 0;
+ align-items: center;
+ justify-content: center;
+ width: 22px;
+ height: 22px;
+ padding: 0;
+ border: 0;
+ background: transparent;
+ color: var(--text-muted, #5f7a6d);
+ font-size: 12px;
+ line-height: 1;
}
-.oa-pick__list .van-cell__label,
-.oa-recv__list .van-cell__label,
-.oa-rel__picker .van-cell__label {
- text-align: left;
- color: var(--text-muted, #6b7b74);
+.oa-sheet-row__exp--spacer {
+ visibility: hidden;
+ pointer-events: none;
}
.oa-pick__check,
.oa-recv__check,
.oa-rel__check {
- margin-right: 8px;
+ margin-right: 10px;
}
/* 弹窗检索:勿继承表单 labelAlign=top,保持单行垂直居中 */
.oa-pick__sheet .van-search,
.oa-recv__sheet .van-search,
-.oa-rel__sheet .van-search {
+.oa-rel__sheet .van-search,
+.oa-flow-picker .van-search {
padding: 10px 12px;
background: #fff;
}
.oa-pick__sheet .van-search .van-search__content,
.oa-recv__sheet .van-search .van-search__content,
-.oa-rel__sheet .van-search .van-search__content {
+.oa-rel__sheet .van-search .van-search__content,
+.oa-flow-picker .van-search .van-search__content {
align-items: center;
+ padding-right: 4px;
}
.oa-pick__sheet .van-search .van-cell,
.oa-recv__sheet .van-search .van-cell,
-.oa-rel__sheet .van-search .van-cell {
+.oa-rel__sheet .van-search .van-cell,
+.oa-flow-picker .van-search .van-cell {
align-items: center;
padding-top: 0;
padding-bottom: 0;
@@ -262,14 +433,28 @@
.oa-pick__sheet .van-search .van-field__body,
.oa-recv__sheet .van-search .van-field__body,
-.oa-rel__sheet .van-search .van-field__body {
+.oa-rel__sheet .van-search .van-field__body,
+.oa-flow-picker .van-search .van-field__body {
align-items: center;
}
.oa-pick__sheet .van-search .van-field__control,
.oa-recv__sheet .van-search .van-field__control,
-.oa-rel__sheet .van-search .van-field__control {
+.oa-rel__sheet .van-search .van-field__control,
+.oa-flow-picker .van-search .van-field__control {
height: 36px;
line-height: 36px;
text-align: left !important;
}
+
+/*
+ * Vant 默认 .van-field__clear { margin-right: calc(var(--van-padding-xs) * -1) }
+ * 会把清除图标贴到搜索框右缘;对齐发起审批页检索框内边距(约 12px)。
+ */
+.oa-pick__sheet .van-search .van-field__clear,
+.oa-recv__sheet .van-search .van-field__clear,
+.oa-rel__sheet .van-search .van-field__clear,
+.oa-flow-picker .van-search .van-field__clear {
+ margin-right: 4px;
+ padding-right: 8px;
+}
diff --git a/mobile-oa-h5/src/styles/tokens.css b/mobile-oa-h5/src/styles/tokens.css
index 05dc394c..2431ad3d 100644
--- a/mobile-oa-h5/src/styles/tokens.css
+++ b/mobile-oa-h5/src/styles/tokens.css
@@ -1,11 +1,20 @@
:root {
- --brand-green: #16a34a;
- --brand-green-dark: #15803d;
- --brand-green-soft: #f0fdf4;
- --bg-page: #f5f8f7;
- --text-primary: #1f2a26;
- --text-muted: #6b7b74;
- --border: #dcebe4;
+ /* 方案 A · 薄荷微风 — 与 mobile-h5 对齐 */
+ --brand-green: #22c55e;
+ --brand-green-dark: #16a34a;
+ --brand-green-soft: #ecfdf5;
+ --header-fg: #065f46;
+ --bg-page: #ffffff;
+ --surface: #ffffff;
+ --text-primary: #0f291e;
+ --text-muted: #5f7a6d;
+ --border: #d8f3e5;
+ --danger: #ef4444;
+ --warning: #d97706;
+ --success: #10b981;
+ --barley-yellow: #fbbf24;
+ --barley-yellow-soft: #fffbeb;
+ --shadow-soft: 0 4px 16px rgba(22, 163, 74, 0.07);
}
body {
margin: 0;
diff --git a/mobile-oa-h5/src/views/FormSink.vue b/mobile-oa-h5/src/views/FormSink.vue
index ae1912b5..f3f6b418 100644
--- a/mobile-oa-h5/src/views/FormSink.vue
+++ b/mobile-oa-h5/src/views/FormSink.vue
@@ -47,8 +47,8 @@ async function onValidate() {
.sink__hd {
padding: 14px 16px;
font-weight: 700;
- color: #fff;
- background: linear-gradient(135deg, var(--brand-green), var(--brand-green-dark));
+ color: var(--header-fg, #065f46);
+ background: linear-gradient(160deg, #f0fdf4 0%, #d1fae5 58%, #a7f3d0 100%);
}
.sink__body {
flex: 1;
diff --git a/mobile-oa-h5/src/views/OaStart.vue b/mobile-oa-h5/src/views/OaStart.vue
index cb4e4578..24adf5c5 100644
--- a/mobile-oa-h5/src/views/OaStart.vue
+++ b/mobile-oa-h5/src/views/OaStart.vue
@@ -10,6 +10,26 @@
:rule="formRule"
:option="formOption"
/>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
选择偏好方案(可点击下方卡片,也可在上方 Tab 预览):
+
+
+
+
+
+