浏览代码

docs: record mobile-h5 Vant UI design, plan, and demo

Link the workspace UI demo and implementation plan from the
miniprogram UI spec for the unified mobile workbench.

Co-authored-by: Cursor <cursoragent@cursor.com>
zlwu 2 月之前
父节点
当前提交
031e456614

+ 11 - 1
docs/miniprogram-ui-spec-sanqiang.md

@@ -1,6 +1,6 @@
 # 小程序端 UI 规范(中科兴皖一体化门户)
 
-> **适用范围**:`miniprogram`(客户端 / 供应商端 / 配送员端)  
+> **适用范围**:`miniprogram`(客户端 / 供应商端 / 配送员端)与 **`mobile-h5`(统一工作台)**  
 > **设计基准文件(Demo)**:  
 > - `mobile-ui/sanqiang-client-ui-demo.html`
 > - `mobile-ui/sanqiang-client-cart-ui-demo.html`
@@ -8,6 +8,16 @@
 > - `mobile-ui/sanqiang-client-profile-ui-demo.html`
 > - `mobile-ui/sanqiang-supplier-ui-demo.html`
 > - `mobile-ui/sanqiang-deliverer-ui-demo.html`
+> - **工作台**:`mobile-ui/zkxw-mobile-vant-ui-demo.html`
+
+## 移动工作台(mobile-h5)
+
+- 工程:`mobile-h5`(HBuilderX uni-app Vue3 + `@vant/weapp`)
+- 视觉 Demo:`mobile-ui/zkxw-mobile-vant-ui-demo.html`
+- 设计说明:`docs/superpowers/specs/2026-08-10-mobile-h5-vant-ui-design.md`
+- 实现计划:`docs/superpowers/plans/2026-08-10-mobile-h5-vant-ui.md`
+- 信息架构:单一工作台 + 权限菜单卡片(上 Icon 下标题);样板页见 `mobile-h5/pages/demo/*`
+- Token:仍遵循本文 §2 三墙色板;**禁止**使用 web-admin `#10b981`
 
 ---
 

+ 807 - 0
docs/superpowers/plans/2026-08-10-mobile-h5-vant-ui.md

@@ -0,0 +1,807 @@
+# mobile-h5 Vant UI 骨架 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:** 在保留 HBuilderX uni-app 骨架的前提下,接入 `@vant/weapp`、落地三墙 Token 与全局样式层,并实现首页工作台 + 列表/详情/表单样板页,供后续业务页复制适配。
+
+**Architecture:** 视觉以 `mobile-ui/zkxw-mobile-vant-ui-demo.html` 为准;`styles/tokens.scss` 为唯一色板源,经 `vant-theme.scss` + `app.scss` + `styles/components/*` 注入 `App.vue`;交互控件用 `@vant/weapp`(easycom),业务壳(菜单宫格、列表卡、附件、流转)用自研 Vue 组件。本期菜单权限用 mock,不接真实 API。
+
+**Tech Stack:** uni-app Vue3(HBuilderX)、`@vant/weapp`、SCSS、微信小程序发行
+
+**Spec:** `docs/superpowers/specs/2026-08-10-mobile-h5-vant-ui-design.md`
+
+---
+
+## File map(将创建 / 修改)
+
+| 路径 | 职责 |
+|------|------|
+| `mobile-h5/package.json` | npm 依赖(`@vant/weapp`) |
+| `mobile-h5/pages.json` | easycom、页面路由、globalStyle |
+| `mobile-h5/uni.scss` | 覆盖 uni 默认主色为三墙绿;import tokens |
+| `mobile-h5/App.vue` | 全局样式入口 |
+| `mobile-h5/styles/tokens.scss` | SCSS + CSS 变量 Token |
+| `mobile-h5/styles/vant-theme.scss` | Vant CSS 变量覆盖 |
+| `mobile-h5/styles/mixins.scss` | Hero / 卡片 mixin |
+| `mobile-h5/styles/app.scss` | page 背景、工具类 |
+| `mobile-h5/styles/components/*.scss` | menu-grid / todo / list / detail / attach / flow / form-shell |
+| `mobile-h5/utils/menu-mock.js` | 移动端菜单 mock + 权限过滤 |
+| `mobile-h5/utils/biz-mock.js` | 列表/详情样板 mock 数据 |
+| `mobile-h5/components/MenuGrid.vue` | 上 Icon 下标题宫格 |
+| `mobile-h5/components/TodoPanel.vue` | 待办统计 + 条目 |
+| `mobile-h5/components/BizListCard.vue` | 列表业务卡 |
+| `mobile-h5/components/AttachList.vue` | 附件上传条 + 文件卡 |
+| `mobile-h5/components/FlowTimeline.vue` | 流转记录 |
+| `mobile-h5/pages/index/index.vue` | 首页工作台 |
+| `mobile-h5/pages/demo/list.vue` | 列表样板 |
+| `mobile-h5/pages/demo/detail.vue` | 详情样板 |
+| `mobile-h5/pages/demo/form.vue` | 表单样板 |
+| `mobile-h5/pages/demo/comps.vue` | 组件合集样板 |
+| `docs/miniprogram-ui-spec-sanqiang.md` | 增加「移动工作台」指向 |
+
+**不修改:** `main.js`、`manifest.json` 核心结构(仅后续填 appid 时改 `mp-weixin.appid`,本期可不改)、`index.html`。
+
+---
+
+### Task 1: 初始化 npm 并安装 `@vant/weapp`
+
+**Files:**
+- Create: `mobile-h5/package.json`
+- Create/Update: `mobile-h5/.gitignore`(确保含 `node_modules/`)
+
+- [ ] **Step 1: 写入 package.json**
+
+```json
+{
+  "name": "mobile-h5",
+  "version": "1.0.0",
+  "private": true,
+  "description": "zkxw mobile uni-app (HBuilderX) + Vant Weapp",
+  "dependencies": {
+    "@vant/weapp": "^1.11.7"
+  }
+}
+```
+
+- [ ] **Step 2: 安装依赖**
+
+Run(在 `mobile-h5` 目录):
+
+```bash
+cd /Users/wuzilong/work/00code/zkxw/mobile-h5 && npm install
+```
+
+Expected: 生成 `node_modules/@vant/weapp`,无报错。
+
+- [ ] **Step 3: 确认 easycom 实际组件路径**
+
+Run:
+
+```bash
+ls /Users/wuzilong/work/00code/zkxw/mobile-h5/node_modules/@vant/weapp/button
+```
+
+Expected: 存在 `index.js` / `index.wxml` 等。后续 `pages.json` 使用路径 `@vant/weapp/button/index`(无 `dist` 前缀)。若本机在 `dist/` 下,则改用 `@vant/weapp/dist/button/index`。
+
+- [ ] **Step 4: Commit**
+
+```bash
+cd /Users/wuzilong/work/00code/zkxw
+git add mobile-h5/package.json mobile-h5/package-lock.json mobile-h5/.gitignore
+git commit -m "$(cat <<'EOF'
+chore(mobile-h5): add package.json and @vant/weapp dependency
+
+EOF
+)"
+```
+
+---
+
+### Task 2: 配置 pages.json(easycom + 路由 + 全局导航色)
+
+**Files:**
+- Modify: `mobile-h5/pages.json`
+
+- [ ] **Step 1: 替换为以下完整内容**(路径以 Task 1 实测为准;默认无 dist)
+
+```json
+{
+  "easycom": {
+    "autoscan": true,
+    "custom": {
+      "^van-(.*)": "@vant/weapp/$1/index"
+    }
+  },
+  "pages": [
+    {
+      "path": "pages/index/index",
+      "style": {
+        "navigationStyle": "custom",
+        "navigationBarTitleText": "工作台"
+      }
+    },
+    {
+      "path": "pages/demo/list",
+      "style": {
+        "navigationStyle": "custom",
+        "navigationBarTitleText": "业务列表"
+      }
+    },
+    {
+      "path": "pages/demo/detail",
+      "style": {
+        "navigationStyle": "custom",
+        "navigationBarTitleText": "业务详情"
+      }
+    },
+    {
+      "path": "pages/demo/form",
+      "style": {
+        "navigationStyle": "custom",
+        "navigationBarTitleText": "编辑业务"
+      }
+    },
+    {
+      "path": "pages/demo/comps",
+      "style": {
+        "navigationStyle": "custom",
+        "navigationBarTitleText": "组件风格"
+      }
+    }
+  ],
+  "globalStyle": {
+    "navigationBarTextStyle": "white",
+    "navigationBarTitleText": "中科兴皖",
+    "navigationBarBackgroundColor": "#0a9f6d",
+    "backgroundColor": "#f5f8f7"
+  },
+  "uniIdRouter": {}
+}
+```
+
+- [ ] **Step 2: 校验 JSON 合法**
+
+Run:
+
+```bash
+node -e "JSON.parse(require('fs').readFileSync('mobile-h5/pages.json','utf8')); console.log('ok')"
+```
+
+Expected: `ok`
+
+- [ ] **Step 3: Commit**
+
+```bash
+git add mobile-h5/pages.json
+git commit -m "$(cat <<'EOF'
+chore(mobile-h5): configure Vant easycom and demo page routes
+
+EOF
+)"
+```
+
+---
+
+### Task 3: Token + uni.scss + 全局样式入口
+
+**Files:**
+- Create: `mobile-h5/styles/tokens.scss`
+- Create: `mobile-h5/styles/vant-theme.scss`
+- Create: `mobile-h5/styles/mixins.scss`
+- Create: `mobile-h5/styles/app.scss`
+- Modify: `mobile-h5/uni.scss`
+- Modify: `mobile-h5/App.vue`
+
+- [ ] **Step 1: 创建 `styles/tokens.scss`**
+
+```scss
+// 唯一 Token 源 — 对齐三墙 / Demo,禁止 #10b981
+$brand-green: #0a9f6d;
+$brand-green-dark: #087f57;
+$brand-green-soft: #e9f9f2;
+$barley-yellow: #d6a343;
+$barley-yellow-soft: #fff7e8;
+$bg-page: #f5f8f7;
+$surface: #ffffff;
+$text-primary: #1f2a26;
+$text-muted: #6b7b74;
+$border: #dcebe4;
+$danger: #d14343;
+$warning: #cf8a19;
+$success: #189b65;
+$radius-card: 28rpx;
+$radius-lg: 40rpx;
+
+@mixin token-css-vars {
+  --brand-green: #{$brand-green};
+  --brand-green-dark: #{$brand-green-dark};
+  --brand-green-soft: #{$brand-green-soft};
+  --barley-yellow: #{$barley-yellow};
+  --barley-yellow-soft: #{$barley-yellow-soft};
+  --bg-page: #{$bg-page};
+  --surface: #{$surface};
+  --text-primary: #{$text-primary};
+  --text-muted: #{$text-muted};
+  --border: #{$border};
+  --danger: #{$danger};
+  --warning: #{$warning};
+  --success: #{$success};
+  --radius-card: #{$radius-card};
+  --radius-lg: #{$radius-lg};
+  --shadow-soft: 0 8rpx 32rpx rgba(8, 127, 87, 0.08);
+}
+```
+
+- [ ] **Step 2: 创建 `styles/vant-theme.scss`**
+
+```scss
+page {
+  --button-primary-background-color: #0a9f6d;
+  --button-primary-border-color: #0a9f6d;
+  --button-primary-color: #ffffff;
+  --nav-bar-background-color: transparent;
+  --nav-bar-title-text-color: #ffffff;
+  --nav-bar-icon-color: #ffffff;
+  --tabs-bottom-bar-color: #0a9f6d;
+  --tag-primary-color: #0a9f6d;
+  --tag-success-color: #189b65;
+  --tag-danger-color: #d14343;
+  --tag-warning-color: #cf8a19;
+  --steps-active-color: #0a9f6d;
+  --notice-bar-background-color: #e9f9f2;
+  --notice-bar-text-color: #087f57;
+}
+```
+
+> `@vant/weapp` 部分主题通过组件 `color` / `custom-class` 传入;CSS 变量名以当前 weapp 版本文档为准,落地时对照 `node_modules/@vant/weapp` README 微调。
+
+- [ ] **Step 3: 创建 `styles/mixins.scss`**
+
+```scss
+@mixin hero-green {
+  background: linear-gradient(155deg, #066647 0%, #0a9f6d 48%, #12b37a 100%);
+  color: #ffffff;
+  border-radius: 0 0 56rpx 56rpx;
+  position: relative;
+  overflow: hidden;
+}
+
+@mixin surface-card {
+  background: var(--surface, #ffffff);
+  border: 1rpx solid var(--border, #dcebe4);
+  border-radius: var(--radius-card, 28rpx);
+  box-shadow: var(--shadow-soft);
+}
+```
+
+- [ ] **Step 4: 创建 `styles/app.scss`**
+
+```scss
+@import './tokens.scss';
+
+page {
+  @include token-css-vars;
+  background-color: var(--bg-page);
+  color: var(--text-primary);
+  font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue', sans-serif;
+  font-size: 28rpx;
+  line-height: 1.5;
+  box-sizing: border-box;
+}
+
+view, text {
+  box-sizing: border-box;
+}
+
+.page-shell {
+  min-height: 100vh;
+  background:
+    radial-gradient(840rpx 440rpx at 100% 0%, rgba(214, 163, 67, 0.14), transparent 55%),
+    radial-gradient(760rpx 400rpx at 0% 20%, rgba(10, 159, 109, 0.1), transparent 50%),
+    linear-gradient(180deg, #e8f5ef 0%, #f4f7f6 40%, #f7f9f8 100%);
+}
+
+.safe-bottom {
+  padding-bottom: calc(24rpx + env(safe-area-inset-bottom));
+}
+```
+
+- [ ] **Step 5: 修改 `uni.scss` 顶部主色**
+
+将文件中:
+
+```scss
+$uni-color-primary: #007aff;
+```
+
+改为:
+
+```scss
+@import './styles/tokens.scss';
+
+$uni-color-primary: $brand-green;
+$uni-color-success: $success;
+$uni-color-warning: $warning;
+$uni-color-error: $danger;
+```
+
+(保留其余 uni 默认变量,仅覆盖行为色。)
+
+- [ ] **Step 6: 修改 `App.vue`**
+
+```vue
+<script>
+  export default {
+    onLaunch() {
+      console.log('App Launch')
+    },
+    onShow() {
+      console.log('App Show')
+    },
+    onHide() {
+      console.log('App Hide')
+    }
+  }
+</script>
+
+<style lang="scss">
+  @import './styles/vant-theme.scss';
+  @import './styles/app.scss';
+  @import './styles/components/menu-grid.scss';
+  @import './styles/components/todo.scss';
+  @import './styles/components/list.scss';
+  @import './styles/components/detail.scss';
+  @import './styles/components/attach.scss';
+  @import './styles/components/flow.scss';
+  @import './styles/components/form-shell.scss';
+</style>
+```
+
+> Task 3 提交前,Task 4 需先创建空的 `styles/components/*.scss`(可先写文件头注释),避免 App 编译找不到 import。
+
+- [ ] **Step 7: 校验 Token 色值存在**
+
+Run:
+
+```bash
+grep -n '#0a9f6d' mobile-h5/styles/tokens.scss && ! grep -n '#10b981' mobile-h5/styles/tokens.scss
+```
+
+Expected: 命中 `#0a9f6d`;无 `#10b981`。
+
+- [ ] **Step 8: Commit**
+
+```bash
+git add mobile-h5/styles mobile-h5/uni.scss mobile-h5/App.vue
+git commit -m "$(cat <<'EOF'
+feat(mobile-h5): add design tokens and global style entry
+
+EOF
+)"
+```
+
+---
+
+### Task 4: 拆分组件级 SCSS(从 Demo 迁移关键类)
+
+**Files:**
+- Create: `mobile-h5/styles/components/menu-grid.scss`
+- Create: `mobile-h5/styles/components/todo.scss`
+- Create: `mobile-h5/styles/components/list.scss`
+- Create: `mobile-h5/styles/components/detail.scss`
+- Create: `mobile-h5/styles/components/attach.scss`
+- Create: `mobile-h5/styles/components/flow.scss`
+- Create: `mobile-h5/styles/components/form-shell.scss`
+
+**权威对照:** `mobile-ui/zkxw-mobile-vant-ui-demo.html` 中对应 CSS 块;实现时将 `px` 按 `×2 → rpx` 换算,类名保持与 Demo 一致(`.menu-grid`、`.biz-card`、`.detail-hero`、`.d-attach-*`、`.flow-*` 等)。
+
+- [ ] **Step 1: 创建七个 scss 文件**
+
+每个文件至少包含 `@import '../mixins.scss';`(若需要)及从 Demo 迁移的对应区块。最低要求类名:
+
+| 文件 | 必须包含的类 |
+|------|----------------|
+| `menu-grid.scss` | `.menu-grid` `.menu-card` `.menu-icon` `.menu-title` |
+| `todo.scss` | `.todo-panel` `.todo-stats` `.todo-stat` `.todo-list` `.todo-item` |
+| `list.scss` | `.list-hero` `.list-filter-dock` `.seg-tabs` `.biz-card` `.status-warning/primary/danger/success` |
+| `detail.scss` | `.detail-page` `.detail-hero` `.detail-metric-dock` `.detail-section` `.detail-footer` |
+| `attach.scss` | `.d-attach-list` `.d-attach-upload` `.d-attach-card` `.ft-pdf/.ft-img/.ft-xls/.ft-doc` |
+| `flow.scss` | `.flow-list` `.flow-item` `.flow-dot` `.flow-card` |
+| `form-shell.scss` | `.form-page` `.form-section-title` `.form-footer` |
+
+具体属性从 Demo 拷贝并做 rpx 换算;渐变与色值使用 `var(--brand-green)` 等 CSS 变量。
+
+- [ ] **Step 2: 静态检查类名是否齐全**
+
+Run:
+
+```bash
+for f in menu-grid todo list detail attach flow form-shell; do
+  test -f "mobile-h5/styles/components/$f.scss" || exit 1
+done
+grep -q 'menu-grid' mobile-h5/styles/components/menu-grid.scss
+grep -q 'biz-card' mobile-h5/styles/components/list.scss
+grep -q 'd-attach-upload' mobile-h5/styles/components/attach.scss
+grep -q 'flow-list' mobile-h5/styles/components/flow.scss
+echo 'scss ok'
+```
+
+Expected: `scss ok`
+
+- [ ] **Step 3: Commit**
+
+```bash
+git add mobile-h5/styles/components
+git commit -m "$(cat <<'EOF'
+feat(mobile-h5): add page-pattern SCSS modules from UI demo
+
+EOF
+)"
+```
+
+---
+
+### Task 5: Mock 数据工具
+
+**Files:**
+- Create: `mobile-h5/utils/menu-mock.js`
+- Create: `mobile-h5/utils/biz-mock.js`
+
+- [ ] **Step 1: 写入 `menu-mock.js`**
+
+```js
+/** 模拟「菜单管理 → 移动端菜单」授权结果 */
+export const ALL_MOBILE_MENUS = [
+  { id: 'm1', title: '待办审批', icon: 'todo-list-o', path: '/pages/demo/list' },
+  { id: 'm2', title: '采集协议', icon: 'orders-o', path: '/pages/demo/list' },
+  { id: 'm3', title: '缴费确认', icon: 'balance-list-o', path: '/pages/demo/list' },
+  { id: 'm4', title: '样本接收', icon: 'logistics', path: '/pages/demo/list' },
+  { id: 'm5', title: '质检工单', icon: 'certificate', path: '/pages/demo/list' },
+  { id: 'm6', title: '生产任务', icon: 'cluster-o', path: '/pages/demo/list' },
+  { id: 'm7', title: '库位出库', icon: 'send-gift-o', path: '/pages/demo/list' },
+  { id: 'm8', title: '组件示例', icon: 'apps-o', path: '/pages/demo/comps' }
+]
+
+/** @param {string[]} grantedIds 当前用户拥有的菜单 id */
+export function filterMenusByPermission(grantedIds) {
+  const set = new Set(grantedIds || [])
+  return ALL_MOBILE_MENUS.filter((m) => set.has(m.id))
+}
+
+export const DEFAULT_GRANTED_IDS = ['m1', 'm2', 'm3', 'm4', 'm5', 'm7', 'm8']
+```
+
+- [ ] **Step 2: 写入 `biz-mock.js`**
+
+从 Demo 的 `listData` 迁移 4 条样板(含 `attachments`、`logs`、`payments`、`steps`、`stepActive`、`tagType`、`amount`/`progress` 等字段),并导出:
+
+```js
+export function getList() { /* return listData */ }
+export function getById(id) { /* find or listData[0] */ }
+export function filterByTab(tabIndex, keyword) { /* 与 Demo filteredList 逻辑一致 */ }
+```
+
+- [ ] **Step 3: 快速自测过滤逻辑**
+
+Run:
+
+```bash
+cd mobile-h5 && node --input-type=module -e "
+import { filterMenusByPermission, DEFAULT_GRANTED_IDS, ALL_MOBILE_MENUS } from './utils/menu-mock.js'
+const v = filterMenusByPermission(DEFAULT_GRANTED_IDS)
+console.log(v.length, ALL_MOBILE_MENUS.length)
+if (v.length !== 7) process.exit(1)
+"
+```
+
+Expected: 打印 `7 8`,exit 0。
+
+> 若 HBuilderX/微信环境不支持 node ESM,可将文件改为无 `export` 的 CommonJS `module.exports`,或用 `uni` 内仅引用、本步改为人工阅读断言。优先保持 `export`,与 Vue3 script 一致。
+
+- [ ] **Step 4: Commit**
+
+```bash
+git add mobile-h5/utils
+git commit -m "$(cat <<'EOF'
+feat(mobile-h5): add menu and biz mock helpers
+
+EOF
+)"
+```
+
+---
+
+### Task 6: 公共组件 MenuGrid + TodoPanel
+
+**Files:**
+- Create: `mobile-h5/components/MenuGrid.vue`
+- Create: `mobile-h5/components/TodoPanel.vue`
+
+- [ ] **Step 1: 创建 `MenuGrid.vue`**
+
+Props: `menus: Array<{ id, title, icon, path }>`  
+Events: `select(menu)`
+
+模板结构:
+
+```vue
+<template>
+  <view class="menu-grid">
+    <view
+      v-for="m in menus"
+      :key="m.id"
+      class="menu-card"
+      @click="$emit('select', m)"
+    >
+      <view class="menu-icon">
+        <van-icon :name="m.icon" />
+      </view>
+      <text class="menu-title">{{ m.title }}</text>
+    </view>
+  </view>
+</template>
+
+<script>
+  export default {
+    name: 'MenuGrid',
+    props: {
+      menus: { type: Array, default: () => [] }
+    },
+    emits: ['select']
+  }
+</script>
+```
+
+- [ ] **Step 2: 创建 `TodoPanel.vue`**
+
+Props: `stats: { approve, pay, alert }`,`items: Array`  
+Events: `stat-click(key)`,`item-click(item)`,`more`
+
+结构对齐 Demo:三列 `.todo-stat` + `.todo-item` 列表;样式依赖全局 `todo.scss`。
+
+- [ ] **Step 3: Commit**
+
+```bash
+git add mobile-h5/components/MenuGrid.vue mobile-h5/components/TodoPanel.vue
+git commit -m "$(cat <<'EOF'
+feat(mobile-h5): add MenuGrid and TodoPanel components
+
+EOF
+)"
+```
+
+---
+
+### Task 7: 公共组件 BizListCard + AttachList + FlowTimeline
+
+**Files:**
+- Create: `mobile-h5/components/BizListCard.vue`
+- Create: `mobile-h5/components/AttachList.vue`
+- Create: `mobile-h5/components/FlowTimeline.vue`
+
+- [ ] **Step 1: `BizListCard.vue`**
+
+Props: `item`(含 `title, code, typeLabel, icon, status, tagType, desc, owner, time, amount, progress, hint`)  
+Events: `view`、`action`
+
+根节点:`<view class="biz-card" :class="'status-' + item.tagType">`,结构对齐 Demo 列表卡。
+
+- [ ] **Step 2: `AttachList.vue`**
+
+Props: `files: Array<{ name, size?, time? }>`  
+Events: `upload`、`preview`、`download`  
+内置 `fileExt` / `fileTypeClass` 方法(从 Demo 迁移)。下载按钮使用 Demo 定稿的 SVG(箭头+托盘)。
+
+- [ ] **Step 3: `FlowTimeline.vue`**
+
+Props: `logs: Array<{ title, desc, time, tone, actor?, icon?, badge? }>`  
+根节点 `.flow-list`,子项 `.flow-item`。
+
+- [ ] **Step 4: Commit**
+
+```bash
+git add mobile-h5/components/BizListCard.vue mobile-h5/components/AttachList.vue mobile-h5/components/FlowTimeline.vue
+git commit -m "$(cat <<'EOF'
+feat(mobile-h5): add BizListCard, AttachList, and FlowTimeline
+
+EOF
+)"
+```
+
+---
+
+### Task 8: 首页工作台 `pages/index/index.vue`
+
+**Files:**
+- Modify: `mobile-h5/pages/index/index.vue`
+
+- [ ] **Step 1: 实现首页**
+
+要点:
+
+1. 自定义绿渐变 Header(用户名 / 角色 / 部门 mock)。
+2. `MenuGrid`:`filterMenusByPermission(DEFAULT_GRANTED_IDS)`。
+3. `TodoPanel`:点统计或条目 → `uni.navigateTo({ url: '/pages/demo/list' })`。
+4. 菜单 `select`:有 `path` 则 `navigateTo`。
+5. 样式用已有全局类;页面根 `class="page-shell"` 可选(首页也可用纯白底 + header,对齐 Demo)。
+
+- [ ] **Step 2: HBuilderX 运行到微信开发者工具**
+
+手动验收:
+
+- 首页可见菜单宫格(默认 7 项,无「生产任务」)。
+- 点击「组件示例」进入 comps(若尚未实现 comps,可暂指向 list)。
+- 主色为绿色,无系统默认蓝。
+
+- [ ] **Step 3: Commit**
+
+```bash
+git add mobile-h5/pages/index/index.vue
+git commit -m "$(cat <<'EOF'
+feat(mobile-h5): implement permission-filtered home workspace
+
+EOF
+)"
+```
+
+---
+
+### Task 9: 列表样板页
+
+**Files:**
+- Create: `mobile-h5/pages/demo/list.vue`
+
+- [ ] **Step 1: 实现列表页**
+
+结构:`.list-hero`(返回 + 标题 + `van-search`)→ `.list-filter-dock` 四格 seg → `BizListCard` 列表。  
+数据:`biz-mock.filterByTab`。  
+「查看」→ `/pages/demo/detail?id=`;「处理」→ `/pages/demo/form?mode=edit&id=`;右上角加号 → form create。
+
+返回:`uni.navigateBack` 或 `reLaunch` 到首页。
+
+- [ ] **Step 2: 手动验收**
+
+- 分段切换条数变化。
+- 搜索过滤标题/单号。
+- 卡片状态色与 Demo 一致。
+
+- [ ] **Step 3: Commit**
+
+```bash
+git add mobile-h5/pages/demo/list.vue
+git commit -m "$(cat <<'EOF'
+feat(mobile-h5): add demo business list page
+
+EOF
+)"
+```
+
+---
+
+### Task 10: 详情 + 表单样板页
+
+**Files:**
+- Create: `mobile-h5/pages/demo/detail.vue`
+- Create: `mobile-h5/pages/demo/form.vue`
+
+- [ ] **Step 1: 详情页**
+
+`onLoad(query)` → `getById(query.id)`。  
+区块:状态 Hero、metric dock、基础信息、`van-steps`、缴费(有则显示)、`AttachList`、`FlowTimeline`、底栏返回/编辑。
+
+- [ ] **Step 2: 表单页**
+
+`van-cell-group` + `van-field`(名称、单号、类型、加急开关、负责人、日期、金额、备注);`.form-footer` 取消/保存;保存 `van-toast` 后 `navigateBack`。  
+注:`@vant/weapp` 表单为 `van-field` 等,无 Web 版 `van-form` 时用手动校验(名称非空)。
+
+- [ ] **Step 3: 手动联跑** list → detail → form → back
+
+- [ ] **Step 4: Commit**
+
+```bash
+git add mobile-h5/pages/demo/detail.vue mobile-h5/pages/demo/form.vue
+git commit -m "$(cat <<'EOF'
+feat(mobile-h5): add demo detail and form pages
+
+EOF
+)"
+```
+
+---
+
+### Task 11: 组件合集页 + 规范文档回写
+
+**Files:**
+- Create: `mobile-h5/pages/demo/comps.vue`
+- Modify: `docs/miniprogram-ui-spec-sanqiang.md`(文首或新章节)
+
+- [ ] **Step 1: 实现 `comps.vue`**
+
+对齐 Demo「组件风格」:按钮、Tag、Cell 卡、通知/空态/加载、Feedback、分段/步骤、`AttachList`。  
+使用 `.comps` 相关样式:可把 Demo 中 `.comps-page` / `.comp-block` 关键到 `styles/components/comps.scss` 并在 `App.vue` import(若未建 comps.scss,本步一并创建)。
+
+- [ ] **Step 2: 在 `miniprogram-ui-spec-sanqiang.md` 增加章节**
+
+在文档靠前位置增加:
+
+```markdown
+## 移动工作台(mobile-h5)
+
+- 工程:`mobile-h5`(HBuilderX uni-app Vue3 + `@vant/weapp`)
+- 视觉 Demo:`mobile-ui/zkxw-mobile-vant-ui-demo.html`
+- 设计说明:`docs/superpowers/specs/2026-08-10-mobile-h5-vant-ui-design.md`
+- 信息架构:单一工作台 + 权限菜单卡片;列表/详情/表单样板见 `pages/demo/*`
+- Token:仍遵循本文 §2 三墙色板;**禁止**使用 web-admin `#10b981`
+```
+
+- [ ] **Step 3: Commit**
+
+```bash
+git add mobile-h5/pages/demo/comps.vue mobile-h5/styles/components/comps.scss mobile-h5/App.vue docs/miniprogram-ui-spec-sanqiang.md
+git commit -m "$(cat <<'EOF'
+feat(mobile-h5): add component gallery and link UI spec
+
+EOF
+)"
+```
+
+---
+
+### Task 12: 总验收清单
+
+- [ ] **Step 1: 对照 Spec §8 打勾**
+
+| 项 | 验收方式 |
+|----|----------|
+| 五种页面模式可对应 | HBuilderX 打开 index/list/detail/form/comps |
+| Token 单源 | `tokens.scss` 含 `#0a9f6d`,全库 mobile-h5 无 `#10b981` 作主色 |
+| Vant easycom | 页面中 `van-button` / `van-search` 可渲染 |
+| HBuilderX 可运行到微信工具 | 人工确认 |
+| 新页可复用 shell 类 + 组件 | 阅读 `pages/demo/list.vue` 无大段内联色值 |
+
+Run:
+
+```bash
+! grep -R --include='*.vue' --include='*.scss' -n '#10b981' mobile-h5 || true
+grep -R --include='*.scss' -n '#0a9f6d' mobile-h5/styles/tokens.scss
+```
+
+- [ ] **Step 2: 若全部通过,打 tag 或最终说明 commit(可选)**
+
+```bash
+git status
+```
+
+Expected: 工作区干净或仅剩无关文件。
+
+---
+
+## Spec coverage(自检)
+
+| Spec 要求 | 对应 Task |
+|-----------|-----------|
+| 保留 HBuilderX 骨架 | Task 1–2(不改 main.js 结构) |
+| `@vant/weapp` + easycom | Task 1–2 |
+| tokens / vant-theme / app / components scss | Task 3–4 |
+| 菜单权限过滤(mock) | Task 5–6、8 |
+| 列表/详情/表单/组件样板 | Task 9–11 |
+| 回写 miniprogram UI 规范 | Task 11 |
+| 不接真实 API | 全计划无 api 联调 |
+| 验收标准 §8 | Task 12 |
+
+## Placeholder scan
+
+无 TBD /「类似 Task N」式省略;关键代码与命令已写出。SCSS 细属性要求从 Demo 迁移(Task 4),执行时打开 Demo 文件对照拷贝。
+
+---
+
+## Execution handoff
+
+Plan complete and saved to `docs/superpowers/plans/2026-08-10-mobile-h5-vant-ui.md`.
+
+**Two execution options:**
+
+1. **Subagent-Driven(推荐)** — 每个 Task 开一个新子代理,Task 间人工/父代理复审,迭代快  
+2. **Inline Execution** — 本会话按 executing-plans 连续执行,设检查点
+
+你更倾向哪一种?

+ 313 - 0
docs/superpowers/specs/2026-08-10-mobile-h5-vant-ui-design.md

@@ -0,0 +1,313 @@
+# 移动端(mobile-h5)UI 设计与 Vant 整合方案
+
+> 日期:2026-08-10  
+> 工程:`mobile-h5`(HBuilderX 创建的 uni-app Vue3,发布微信小程序)  
+> 视觉 Demo:`mobile-ui/zkxw-mobile-vant-ui-demo.html`  
+> 相关需求:`docs/中科兴皖需求V0.3.md`(小程序端 Vue3 + vant-ui)  
+> 品牌 Token:对齐 `docs/miniprogram-ui-spec-sanqiang.md` 三墙色板(**非** web-admin Fresh Mint)
+
+---
+
+## 1. 目标与约束
+
+### 1.1 目标
+
+1. 固化本轮 Demo 已确认的页面风格(首页 / 列表 / 详情 / 表单 / 组件合集)。
+2. 在**不改动 HBuilderX 工程骨架**(保留 `manifest.json` / `pages.json` / `main.js` / `App.vue` 等原生结构)前提下,接入 **Vant**,并建立可复用的**全局样式层**,方便后续业务页快速适配。
+3. 首页为**单一工作台**:功能菜单卡片受「菜单管理 → 移动端菜单」+ 角色/职位权限控制。
+
+### 1.2 硬约束
+
+| 项 | 约定 |
+|----|------|
+| 工程形态 | 继续使用 HBuilderX + uni-app,**不**迁移为独立 Vite CLI 工程 |
+| UI 库 | Vant 体系(微信端使用 `@vant/weapp`) |
+| Vue | Vue 3(`manifest.json` → `vueVersion: "3"`) |
+| 主色 | `#0a9f6d` / `#087f57` / `#e9f9f2`(禁止混用 web-admin `#10b981`) |
+| 发布 | HBuilderX 发行到微信小程序 |
+
+### 1.3 非目标(本期不做)
+
+- 不改造 `web-admin`。
+- 不实现完整业务 API 联调(本设计只定 UI / 样式 / 工程骨架)。
+- 不引入第二套 UI 框架(如 uView / wot 与 Vant 并存)。
+
+---
+
+## 2. 视觉权威与页面模式
+
+### 2.1 权威文件
+
+| 角色 | 路径 |
+|------|------|
+| 交互与视觉 Demo(本轮定稿) | `mobile-ui/zkxw-mobile-vant-ui-demo.html` |
+| 品牌 Token 基线 | `docs/miniprogram-ui-spec-sanqiang.md` §2 |
+| 本设计说明 | 本文档 |
+
+后续业务页 **SHALL** 以 Demo 中对应页面区块为准;Token **SHALL** 以三墙色板为准。
+
+### 2.2 已定稿页面
+
+| 模式 | Demo 入口 | 结构摘要 |
+|------|-----------|----------|
+| P-Home 首页工作台 | 「1. 首页」 | 绿渐变 Header → 权限菜单 4 列宫格(上 Icon 下标题)→ 待办统计卡 + 待办条目 |
+| P-List 列表 | 「2. 列表」 | Hero + 内嵌搜索 → 悬浮分段筛选 → 状态着色业务卡(图标/金额或进度/操作) |
+| P-Detail 详情 | 「3. 详情」 | 状态色 Hero → 指标 Dock → 分区卡片(基础信息 / Steps / 缴费 / 附件 / 流转)→ 底栏 |
+| P-Form 新增编辑 | 「4. 表单」 | 绿顶栏 → `van-form` 分组 Field → 底栏取消/保存 |
+| P-Comps 组件合集 | 「5. 组件」 | 按钮 / Tag / Cell / 通知空态加载 / Feedback / 分段步骤 / 附件 |
+
+### 2.3 首页菜单权限规则
+
+1. 菜单数据来自后台「菜单管理」中 **移动端菜单** 树(`parent_id` 指向移动端根)。
+2. 登录人仅展示其角色/职位授权内的菜单。
+3. 卡片样式:**上方 Icon,下方标题**;推荐 **4 列**宫格;触达区域 ≥ 44×44。
+
+---
+
+## 3. 设计 Token(全局变量)
+
+在 uni-app 中以 **SCSS 变量 + CSS 变量** 双轨落地,业务页优先用 CSS 变量,便于运行时/主题覆盖。
+
+```scss
+// styles/tokens.scss(设计源)
+$brand-green: #0a9f6d;
+$brand-green-dark: #087f57;
+$brand-green-soft: #e9f9f2;
+$barley-yellow: #d6a343;
+$barley-yellow-soft: #fff7e8;
+$bg-page: #f5f8f7;
+$surface: #ffffff;
+$text-primary: #1f2a26;
+$text-muted: #6b7b74;
+$border: #dcebe4;
+$danger: #d14343;
+$warning: #cf8a19;   // Demo 中部分强调用 #a67b20 / #d6a343 同族
+$success: #189b65;
+
+$radius-card: 14px;
+$radius-lg: 20px;    // Hero 下缘可用 28px
+$space: 8px;         // 节奏:8 / 10 / 12 / 14 / 16
+```
+
+映射到页面根(`App.vue` 或 `styles/app.scss`):
+
+```css
+page, :root {
+  --brand-green: #0a9f6d;
+  --brand-green-dark: #087f57;
+  --brand-green-soft: #e9f9f2;
+  --barley-yellow: #d6a343;
+  --barley-yellow-soft: #fff7e8;
+  --bg-page: #f5f8f7;
+  --surface: #ffffff;
+  --text-primary: #1f2a26;
+  --text-muted: #6b7b74;
+  --border: #dcebe4;
+  --danger: #d14343;
+  --warning: #cf8a19;
+  --success: #189b65;
+  --radius-card: 14rpx; /* 实现时按 rpx 换算,见 §5.3 */
+  --shadow-soft: 0 8rpx 32rpx rgba(8, 127, 87, 0.08);
+}
+```
+
+**语义状态色(列表卡 / Tag / 详情 Hero):**
+
+| 状态 | 主强调 | 用途 |
+|------|--------|------|
+| 待处理 | 琥珀 / 大麦黄 | warning 卡、Tag |
+| 进行中 | 品牌绿 | primary 卡、实心 Tag |
+| 异常 | `#d14343` | danger |
+| 已完成 | `#189b65` | success 浅绿底 |
+
+---
+
+## 4. Vant 整合方案(推荐)
+
+### 4.1 选型说明
+
+| 方案 | 说明 | 结论 |
+|------|------|------|
+| A. `@vant/weapp` | Vant 官方微信小程序版,HBuilderX 发微信主路径最稳 | **推荐(默认)** |
+| B. `vant`(Web Vue3) | 仅适合纯 H5;微信端组件不兼容 | 仅作 H5 调试补充,不作为主路径 |
+| C. 其它 uni UI | 与需求「vant-ui」不符 | 不采用 |
+
+> 需求原文要求 vant-ui;在 uni-app → 微信小程序场景下,**`@vant/weapp` 即 Vant 在微信端的官方落地形态**。
+
+### 4.2 接入步骤(不破坏 HBuilderX 骨架)
+
+1. 在 `mobile-h5` 目录用 HBuilderX 终端或本地 npm 安装:
+   ```bash
+   npm init -y   # 若尚无 package.json
+   npm i @vant/weapp -S --production
+   ```
+2. 在 `pages.json` 配置 easycom(按官方路径):
+   ```json
+   "easycom": {
+     "autoscan": true,
+     "custom": {
+       "^van-(.*)": "@vant/weapp/dist/$1/index"
+     }
+   }
+   ```
+   (若发行报路径错误,改为 `@vant/weapp/$1/index`,以本机 `node_modules/@vant/weapp` 实际目录为准。)
+3. 微信小程序需在 `manifest.json` → `mp-weixin` 填入 `appid`;开发阶段可继续 `urlCheck: false`。
+4. **主题色**:在全局样式覆盖 Vant CSS 变量 / 组件自定义色(见 §5.2),主色统一为 `#0a9f6d`。
+5. 页面中按需使用:`van-button`、`van-search`、`van-field`、`van-nav-bar`、`van-tag`、`van-empty`、`van-loading`、`van-notice-bar`、`van-steps`、`van-action-sheet`、`van-dialog`、`van-toast` 等。
+
+### 4.3 与自定义样式的边界
+
+| 用 Vant | 用项目全局样式 / 业务组件 |
+|---------|---------------------------|
+| 按钮、表单 Field、导航栏、Toast/Dialog/Sheet、Steps、Empty、Loading、NoticeBar、基础 Tag | 首页菜单宫格、待办统计、列表业务卡、详情 Hero/指标 Dock、附件卡、流转时间线、分段 Chip/Seg |
+
+原则:**交互控件跟 Vant;业务信息架构跟 Demo 自定义类名**,避免在每个页面复制大段 CSS。
+
+---
+
+## 5. 全局样式怎么写(方便后续适配)
+
+### 5.1 推荐目录(在 `mobile-h5` 内增量,不改框架入口文件职责)
+
+```
+mobile-h5/
+  App.vue                 # 引入全局样式;生命周期
+  main.js                 # 保持 HBuilderX 默认
+  pages.json
+  manifest.json
+  uni.scss                # 覆盖/扩展 uni 变量(可 @import tokens)
+  styles/
+    tokens.scss           # 色板、圆角、间距(唯一 Token 源)
+    vant-theme.scss       # Vant 主题覆盖
+    mixins.scss           # 渐变 Hero、卡片、安全区等 mixin
+    app.scss              # page 背景、通用工具类
+    components/
+      menu-grid.scss      # 首页菜单宫格
+      todo.scss           # 待办
+      list.scss           # 列表 Hero / Seg / biz-card
+      detail.scss         # 详情 Hero / Dock / section
+      attach.scss         # 附件上传与文件卡
+      flow.scss           # 流转记录
+      form-shell.scss     # 表单底栏等
+  components/             # 可复用 Vue 组件(后续落地)
+    AppHero.vue
+    MenuGrid.vue
+    BizListCard.vue
+    AttachList.vue
+    FlowTimeline.vue
+    ...
+  pages/
+    index/                # 工作台首页
+    ...
+  api/                    # 后续
+  store/                  # 后续
+  utils/
+```
+
+### 5.2 Vant 主题覆盖(示例)
+
+```scss
+/* styles/vant-theme.scss */
+page {
+  --van-primary-color: #0a9f6d;
+  --van-success-color: #189b65;
+  --van-warning-color: #cf8a19;
+  --van-danger-color: #d14343;
+  --van-border-color: #dcebe4;
+  --van-background: #f5f8f7;
+  --van-text-color: #1f2a26;
+  --van-text-color-2: #6b7b74;
+  --van-button-primary-background: #0a9f6d;
+  --van-button-primary-border-color: #0a9f6d;
+  --van-nav-bar-background: transparent;
+  --van-nav-bar-title-text-color: #ffffff;
+  --van-nav-bar-icon-color: #ffffff;
+}
+```
+
+`App.vue`:
+
+```vue
+<style lang="scss">
+  @import "@/styles/tokens.scss";
+  @import "@/styles/vant-theme.scss";
+  @import "@/styles/app.scss";
+  @import "@/styles/components/menu-grid.scss";
+  @import "@/styles/components/list.scss";
+  @import "@/styles/components/detail.scss";
+  @import "@/styles/components/attach.scss";
+  @import "@/styles/components/flow.scss";
+  /* 按需继续 import */
+</style>
+```
+
+> HBuilderX Vue3 若 `@` 别名未配置,可用相对路径 `./styles/...`,或在项目中按 uni-app 惯例配置。
+
+### 5.3 rpx 适配约定
+
+- Demo 以 **375 逻辑宽** 为基准(与小程序规范一致)。
+- 换算经验:`1px ≈ 2rpx`(375 设计稿)。
+- 全局样式落地时:**字号、间距、圆角、图标容器用 rpx**;`1px` 边框可用 `1rpx` 或 `0.5px`(按端表现微调)。
+- 触达高度:按钮 / 底栏主按钮 **≥ 88rpx(约 44px)**。
+
+### 5.4 可复用「壳」类名(与 Demo 对齐,落地时保持稳定)
+
+后续新页面优先组合下列类,而不是新起一套命名:
+
+| 类名 / 区块 | 用途 |
+|-------------|------|
+| `.page-shell` + 浅绿径向背景 | 列表/详情/组件页底 |
+| `.hero` / `.list-hero` / `.detail-hero` | 绿/状态色顶区 + 大圆角下缘 |
+| `.filter-dock` / `.seg-tabs` | 悬浮筛选、四格分段 |
+| `.biz-card.status-*` | 列表业务卡 |
+| `.detail-section` + `.sec-hd` | 详情分区 |
+| `.d-attach-*` / `.flow-*` | 附件、流转 |
+| `.menu-grid` / `.menu-card` | 首页菜单 |
+| `.form-footer` / `.detail-footer` | 底栏 |
+
+### 5.5 业务页适配清单(复制即用)
+
+新开一个业务列表页时:
+
+1. `pages.json` 注册路由;顶栏可用自定义 Hero,隐藏默认 navigationBar 或设透明。
+2. 结构:`hero(搜索)` → `seg-tabs` → `biz-card` 列表。
+3. 状态枚举映射到 `status-warning | status-primary | status-danger | status-success`。
+4. 主按钮仅一处实心 `type="primary"`(或项目封装的主按钮 class)。
+5. 附件、流转直接引用 `AttachList` / `FlowTimeline` 组件(落地后)。
+
+---
+
+## 6. 与现有文档的关系
+
+1. **`docs/miniprogram-ui-spec-sanqiang.md`**:继续作为三墙 Token / 媒体比例等基线;其中旧「客户端购物车三端」Demo 与本「统一工作台」产品形态不同,**工作台信息架构以 `zkxw-mobile-vant-ui-demo.html` 为准**。
+2. 建议在 `miniprogram-ui-spec-sanqiang.md` 增加一节「移动工作台(mobile-h5)」指向 Demo + 本文档(实现阶段文档任务,可与代码一并改)。
+3. **颜色隔离**:`web-admin` 仍用 Fresh Mint;`mobile-h5` 只用三墙绿。
+
+---
+
+## 7. 实施顺序建议(供下一阶段 plan)
+
+1. `mobile-h5` 增加 `package.json` + 安装 `@vant/weapp`,配置 `pages.json` easycom。  
+2. 落地 `styles/tokens.scss` + `vant-theme.scss` + `app.scss`,在 `App.vue` 引入。  
+3. 抽公共组件:`MenuGrid`、`BizListCard`、`AttachList`、`FlowTimeline`。  
+4. 实现首页(菜单权限 mock → 再接真实菜单 API)。  
+5. 实现列表 / 详情 / 表单样板页各一,作为后续业务复制模板。  
+6. 回写 `miniprogram-ui-spec-sanqiang.md` 工作台章节。
+
+---
+
+## 8. 验收标准
+
+- [ ] Demo 中五种页面模式在文档与样式类名上可一一对应。  
+- [ ] 全局 Token 仅一处定义(`tokens.scss`),Vant 主色与品牌绿一致。  
+- [ ] HBuilderX 仍可正常运行到微信开发者工具 / 发行。  
+- [ ] 新业务页可在不复制大段 CSS 的情况下,组合 shell + Vant 控件完成布局。  
+- [ ] 不出现 web-admin `#10b981` 主色。
+
+---
+
+## 9. 修订记录
+
+| 日期 | 说明 |
+|------|------|
+| 2026-08-10 | 初稿:定稿 Demo、Token、`@vant/weapp` 整合、全局样式目录与适配清单 |

+ 3676 - 0
mobile-ui/zkxw-mobile-vant-ui-demo.html

@@ -0,0 +1,3676 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+  <meta charset="UTF-8" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
+  <title>中科兴皖 · 移动端 UI Demo(Vant)</title>
+  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/vant@4.9.21/lib/index.css" />
+  <style>
+    :root {
+      --brand-green: #0a9f6d;
+      --brand-green-dark: #087f57;
+      --brand-green-soft: #e9f9f2;
+      --barley-yellow: #d6a343;
+      --barley-yellow-soft: #fff7e8;
+      --bg-page: #f5f8f7;
+      --surface: #ffffff;
+      --text-primary: #1f2a26;
+      --text-muted: #6b7b74;
+      --border: #dcebe4;
+      --danger: #d14343;
+      --warning: #cf8a19;
+      --success: #189b65;
+      --radius-card: 14px;
+      --radius-lg: 20px;
+      --shadow-soft: 0 4px 16px rgba(8, 127, 87, 0.08);
+
+      --van-primary-color: #0a9f6d;
+      --van-success-color: #189b65;
+      --van-warning-color: #cf8a19;
+      --van-danger-color: #d14343;
+      --van-border-color: #dcebe4;
+      --van-background: #f5f8f7;
+      --van-background-2: #ffffff;
+      --van-text-color: #1f2a26;
+      --van-text-color-2: #6b7b74;
+      --van-button-primary-background: #0a9f6d;
+      --van-button-primary-border-color: #0a9f6d;
+      --van-button-radius: 10px;
+      --van-cell-group-inset-padding: 12px;
+      --van-cell-group-inset-radius: 14px;
+      --van-dialog-confirm-button-text-color: #0a9f6d;
+      --van-nav-bar-icon-color: #ffffff;
+      --van-nav-bar-text-color: #ffffff;
+      --van-nav-bar-title-text-color: #ffffff;
+      --van-nav-bar-background: transparent;
+      --van-tabbar-item-active-color: #0a9f6d;
+      --van-tag-radius: 6px;
+    }
+
+    * { box-sizing: border-box; }
+    html, body {
+      margin: 0;
+      padding: 0;
+      min-height: 100%;
+      font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Helvetica Neue", sans-serif;
+      color: var(--text-primary);
+      background:
+        radial-gradient(900px 420px at 10% -10%, rgba(10, 159, 109, 0.14), transparent 55%),
+        radial-gradient(700px 380px at 90% 0%, rgba(214, 163, 67, 0.12), transparent 50%),
+        linear-gradient(180deg, #eef6f2 0%, #f7faf9 40%, #f3f6f5 100%);
+    }
+
+    body {
+      display: flex;
+      justify-content: center;
+      padding: 24px 16px 40px;
+    }
+
+    #app {
+      width: 100%;
+      max-width: 1120px;
+      display: grid;
+      grid-template-columns: 280px 1fr;
+      gap: 24px;
+      align-items: start;
+    }
+
+    .doc-panel {
+      position: sticky;
+      top: 24px;
+      background: rgba(255, 255, 255, 0.92);
+      border: 1px solid var(--border);
+      border-radius: 18px;
+      padding: 20px 18px;
+      box-shadow: var(--shadow-soft);
+      backdrop-filter: blur(8px);
+    }
+
+    .doc-panel h1 {
+      margin: 0 0 6px;
+      font-size: 20px;
+      line-height: 1.3;
+      color: var(--brand-green-dark);
+    }
+
+    .doc-panel .sub {
+      margin: 0 0 16px;
+      font-size: 12px;
+      color: var(--text-muted);
+      line-height: 1.5;
+    }
+
+    .doc-panel .section-title {
+      margin: 16px 0 8px;
+      font-size: 12px;
+      font-weight: 600;
+      color: var(--text-muted);
+      letter-spacing: 0.04em;
+      text-transform: uppercase;
+    }
+
+    .screen-nav {
+      display: flex;
+      flex-direction: column;
+      gap: 8px;
+    }
+
+    .screen-nav button {
+      appearance: none;
+      border: 1px solid var(--border);
+      background: #fff;
+      border-radius: 12px;
+      padding: 12px 14px;
+      text-align: left;
+      cursor: pointer;
+      font-size: 14px;
+      color: var(--text-primary);
+      transition: border-color .2s, background .2s, color .2s;
+    }
+
+    .screen-nav button:hover {
+      border-color: #a7e0c8;
+      background: var(--brand-green-soft);
+    }
+
+    .screen-nav button.active {
+      border-color: var(--brand-green);
+      background: var(--brand-green-soft);
+      color: var(--brand-green-dark);
+      font-weight: 600;
+    }
+
+    .token-row {
+      display: flex;
+      flex-wrap: wrap;
+      gap: 8px;
+    }
+
+    .token-chip {
+      display: inline-flex;
+      align-items: center;
+      gap: 6px;
+      font-size: 11px;
+      color: var(--text-muted);
+      background: #fff;
+      border: 1px solid var(--border);
+      border-radius: 999px;
+      padding: 4px 8px 4px 4px;
+    }
+
+    .token-chip i {
+      width: 14px;
+      height: 14px;
+      border-radius: 50%;
+      display: block;
+    }
+
+    .notes {
+      margin: 12px 0 0;
+      padding: 0;
+      list-style: none;
+      font-size: 12px;
+      color: var(--text-muted);
+      line-height: 1.55;
+    }
+
+    .notes li {
+      position: relative;
+      padding-left: 12px;
+      margin-bottom: 6px;
+    }
+
+    .notes li::before {
+      content: "";
+      position: absolute;
+      left: 0;
+      top: 7px;
+      width: 5px;
+      height: 5px;
+      border-radius: 50%;
+      background: var(--brand-green);
+    }
+
+    .phone-stage {
+      display: flex;
+      justify-content: center;
+    }
+
+    .phone {
+      width: 390px;
+      height: 844px;
+      background: #111;
+      border-radius: 42px;
+      padding: 12px;
+      box-shadow:
+        0 24px 60px rgba(15, 40, 30, 0.18),
+        inset 0 0 0 1px rgba(255, 255, 255, 0.08);
+      position: relative;
+    }
+
+    .phone::before {
+      content: "";
+      position: absolute;
+      top: 18px;
+      left: 50%;
+      transform: translateX(-50%);
+      width: 118px;
+      height: 28px;
+      background: #0a0a0a;
+      border-radius: 20px;
+      z-index: 5;
+    }
+
+    .phone-screen {
+      width: 100%;
+      height: 100%;
+      border-radius: 32px;
+      overflow: hidden;
+      background: var(--bg-page);
+      position: relative;
+      display: flex;
+      flex-direction: column;
+    }
+
+    .screen-scroll {
+      flex: 1;
+      overflow-y: auto;
+      -webkit-overflow-scrolling: touch;
+      padding-bottom: 16px;
+    }
+
+    /* ========== 首页 ========== */
+    .home-header {
+      background: linear-gradient(145deg, var(--brand-green-dark), var(--brand-green));
+      color: #fff;
+      padding: 54px 16px 28px;
+      border-radius: 0 0 var(--radius-lg) var(--radius-lg);
+      position: relative;
+      overflow: hidden;
+    }
+
+    .home-header::after {
+      content: "";
+      position: absolute;
+      right: -40px;
+      top: -30px;
+      width: 160px;
+      height: 160px;
+      border-radius: 50%;
+      background: rgba(255, 255, 255, 0.08);
+    }
+
+    .home-header .brand {
+      font-size: 12px;
+      opacity: 0.88;
+      letter-spacing: 0.04em;
+      margin-bottom: 8px;
+    }
+
+    .home-header h2 {
+      margin: 0;
+      font-size: 22px;
+      font-weight: 700;
+      letter-spacing: 0.02em;
+    }
+
+    .home-header .meta {
+      margin-top: 10px;
+      display: flex;
+      align-items: center;
+      gap: 8px;
+      font-size: 12px;
+      opacity: 0.92;
+    }
+
+    .home-header .meta .dot {
+      width: 4px;
+      height: 4px;
+      border-radius: 50%;
+      background: rgba(255,255,255,.7);
+    }
+
+    .home-body {
+      padding: 14px 12px 0;
+    }
+
+    .panel {
+      background: var(--surface);
+      border: 1px solid var(--border);
+      border-radius: var(--radius-card);
+      box-shadow: var(--shadow-soft);
+      padding: 14px 12px;
+      margin-bottom: 12px;
+    }
+
+    .panel-hd {
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      margin-bottom: 12px;
+      padding: 0 4px;
+    }
+
+    .panel-hd h3 {
+      margin: 0;
+      font-size: 15px;
+      font-weight: 650;
+    }
+
+    .panel-hd .hint {
+      font-size: 11px;
+      color: var(--text-muted);
+    }
+
+    .menu-grid {
+      display: grid;
+      grid-template-columns: repeat(4, 1fr);
+      gap: 10px 6px;
+    }
+
+    .menu-card {
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      justify-content: flex-start;
+      gap: 8px;
+      min-height: 84px;
+      padding: 10px 4px 6px;
+      border: none;
+      background: transparent;
+      cursor: pointer;
+      border-radius: 12px;
+      transition: background .18s, transform .18s;
+      -webkit-tap-highlight-color: transparent;
+    }
+
+    .menu-card:active {
+      background: var(--brand-green-soft);
+      transform: scale(0.97);
+    }
+
+    .menu-icon {
+      width: 48px;
+      height: 48px;
+      border-radius: 14px;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      background: var(--brand-green-soft);
+      color: var(--brand-green-dark);
+      font-size: 24px;
+      box-shadow: inset 0 0 0 1px rgba(10, 159, 109, 0.08);
+    }
+
+    .menu-icon.tone-yellow {
+      background: var(--barley-yellow-soft);
+      color: #a67b20;
+    }
+
+    .menu-icon.tone-slate {
+      background: #eef2f1;
+      color: #3d524a;
+    }
+
+    .menu-title {
+      font-size: 12px;
+      line-height: 1.25;
+      color: var(--text-primary);
+      text-align: center;
+      max-width: 72px;
+      word-break: break-all;
+    }
+
+    .perm-banner {
+      display: flex;
+      gap: 8px;
+      align-items: flex-start;
+      padding: 10px 12px;
+      border-radius: 12px;
+      background: var(--barley-yellow-soft);
+      border: 1px solid #f0e0b8;
+      font-size: 12px;
+      color: #8a6520;
+      margin-bottom: 12px;
+      line-height: 1.45;
+    }
+
+    /* ========== 待办摘要 ========== */
+    .todo-panel {
+      padding: 14px 12px 12px;
+      background:
+        linear-gradient(165deg, rgba(233, 249, 242, 0.95) 0%, #fff 42%),
+        #fff;
+    }
+
+    .todo-stats {
+      display: grid;
+      grid-template-columns: repeat(3, 1fr);
+      gap: 8px;
+      margin-bottom: 12px;
+    }
+
+    .todo-stat {
+      position: relative;
+      border: none;
+      border-radius: 12px;
+      padding: 12px 10px 10px;
+      text-align: left;
+      cursor: pointer;
+      background: #fff;
+      box-shadow: 0 2px 10px rgba(8, 127, 87, 0.06);
+      border: 1px solid rgba(220, 235, 228, 0.9);
+      transition: transform .18s, box-shadow .18s;
+      -webkit-tap-highlight-color: transparent;
+      overflow: hidden;
+    }
+
+    .todo-stat:active {
+      transform: scale(0.97);
+    }
+
+    .todo-stat::before {
+      content: "";
+      position: absolute;
+      left: 0;
+      top: 0;
+      bottom: 0;
+      width: 3px;
+      border-radius: 3px 0 0 3px;
+    }
+
+    .todo-stat.tone-warn::before { background: var(--warning); }
+    .todo-stat.tone-pay::before { background: var(--barley-yellow); }
+    .todo-stat.tone-danger::before { background: var(--danger); }
+
+    .todo-stat.tone-warn { background: linear-gradient(180deg, #fffaf0 0%, #fff 70%); }
+    .todo-stat.tone-pay { background: linear-gradient(180deg, #fff8ec 0%, #fff 70%); }
+    .todo-stat.tone-danger { background: linear-gradient(180deg, #fff5f5 0%, #fff 70%); }
+
+    .todo-stat .num {
+      display: block;
+      font-size: 22px;
+      font-weight: 700;
+      line-height: 1.1;
+      letter-spacing: -0.02em;
+      color: var(--text-primary);
+      font-variant-numeric: tabular-nums;
+    }
+
+    .todo-stat.tone-warn .num { color: #b77412; }
+    .todo-stat.tone-pay .num { color: #9a7424; }
+    .todo-stat.tone-danger .num { color: var(--danger); }
+
+    .todo-stat .label {
+      display: block;
+      margin-top: 4px;
+      font-size: 11px;
+      color: var(--text-muted);
+    }
+
+    .todo-stat .stat-icon {
+      position: absolute;
+      right: 8px;
+      top: 8px;
+      width: 26px;
+      height: 26px;
+      border-radius: 8px;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      font-size: 14px;
+      opacity: 0.9;
+    }
+
+    .todo-stat.tone-warn .stat-icon {
+      background: rgba(207, 138, 25, 0.12);
+      color: var(--warning);
+    }
+
+    .todo-stat.tone-pay .stat-icon {
+      background: rgba(214, 163, 67, 0.16);
+      color: #a67b20;
+    }
+
+    .todo-stat.tone-danger .stat-icon {
+      background: rgba(209, 67, 67, 0.12);
+      color: var(--danger);
+    }
+
+    .todo-list {
+      display: flex;
+      flex-direction: column;
+      gap: 8px;
+    }
+
+    .todo-item {
+      display: flex;
+      align-items: center;
+      gap: 10px;
+      width: 100%;
+      padding: 10px 10px;
+      border: 1px solid var(--border);
+      border-radius: 12px;
+      background: #fff;
+      cursor: pointer;
+      text-align: left;
+      transition: background .15s, border-color .15s;
+    }
+
+    .todo-item:active {
+      background: var(--brand-green-soft);
+      border-color: #b7e5d0;
+    }
+
+    .todo-item .ti-icon {
+      width: 36px;
+      height: 36px;
+      border-radius: 10px;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      flex-shrink: 0;
+      font-size: 18px;
+    }
+
+    .todo-item .ti-icon.i-approve {
+      background: #fff3e0;
+      color: var(--warning);
+    }
+
+    .todo-item .ti-icon.i-pay {
+      background: var(--barley-yellow-soft);
+      color: #a67b20;
+    }
+
+    .todo-item .ti-icon.i-alert {
+      background: #fdecec;
+      color: var(--danger);
+    }
+
+    .todo-item .ti-body {
+      flex: 1;
+      min-width: 0;
+    }
+
+    .todo-item .ti-title {
+      font-size: 13px;
+      font-weight: 600;
+      color: var(--text-primary);
+      line-height: 1.3;
+    }
+
+    .todo-item .ti-desc {
+      margin-top: 2px;
+      font-size: 11px;
+      color: var(--text-muted);
+      white-space: nowrap;
+      overflow: hidden;
+      text-overflow: ellipsis;
+    }
+
+    .todo-item .ti-right {
+      display: flex;
+      flex-direction: column;
+      align-items: flex-end;
+      gap: 4px;
+      flex-shrink: 0;
+    }
+
+    .todo-item .ti-time {
+      font-size: 10px;
+      color: #9aada4;
+    }
+
+    /* ========== 列表 ========== */
+    .page-nav {
+      background: linear-gradient(145deg, var(--brand-green-dark), var(--brand-green));
+      padding: 44px 8px 10px;
+    }
+
+    .page-nav .van-nav-bar {
+      background: transparent;
+    }
+
+    .page-nav .van-hairline--bottom::after {
+      display: none;
+    }
+
+    .list-page {
+      flex: 1;
+      display: flex;
+      flex-direction: column;
+      min-height: 0;
+      background:
+        radial-gradient(420px 220px at 100% 0%, rgba(214, 163, 67, 0.16), transparent 55%),
+        radial-gradient(380px 240px at 0% 30%, rgba(10, 159, 109, 0.10), transparent 50%),
+        linear-gradient(180deg, #e8f5ef 0%, #f3f7f5 38%, #f7f9f8 100%);
+    }
+
+    .list-page .screen-scroll {
+      background: transparent;
+    }
+
+    .list-hero {
+      background:
+        radial-gradient(220px 140px at 92% 18%, rgba(255, 255, 255, 0.18), transparent 60%),
+        linear-gradient(155deg, #066647 0%, #0a9f6d 48%, #12b37a 100%);
+      padding: 44px 14px 22px;
+      border-radius: 0 0 28px 28px;
+      position: relative;
+      overflow: hidden;
+    }
+
+    .list-hero::after {
+      content: "";
+      position: absolute;
+      width: 180px;
+      height: 180px;
+      right: -50px;
+      bottom: -70px;
+      border-radius: 50%;
+      background: rgba(255, 255, 255, 0.08);
+      pointer-events: none;
+    }
+
+    .list-hero .van-nav-bar {
+      background: transparent;
+      margin: 0 -6px;
+    }
+
+    .list-hero .van-hairline--bottom::after {
+      display: none;
+    }
+
+    .list-hero-copy {
+      position: relative;
+      z-index: 1;
+      margin: 4px 2px 14px;
+      color: #fff;
+    }
+
+    .list-hero-copy .eyebrow {
+      font-size: 11px;
+      letter-spacing: 0.08em;
+      opacity: 0.82;
+      margin-bottom: 4px;
+    }
+
+    .list-hero-copy h2 {
+      margin: 0;
+      font-size: 22px;
+      font-weight: 700;
+      letter-spacing: 0.01em;
+    }
+
+    .list-hero-copy .subline {
+      margin-top: 6px;
+      font-size: 12px;
+      opacity: 0.88;
+      display: flex;
+      align-items: center;
+      gap: 8px;
+    }
+
+    .list-hero-copy .subline em {
+      font-style: normal;
+      font-weight: 700;
+      background: rgba(255, 255, 255, 0.18);
+      border-radius: 999px;
+      padding: 1px 8px;
+    }
+
+    .list-search {
+      position: relative;
+      z-index: 1;
+    }
+
+    .list-search .van-search {
+      padding: 0;
+    }
+
+    .list-search .van-search__content {
+      background: rgba(255, 255, 255, 0.96);
+      border-radius: 14px;
+      box-shadow: 0 8px 24px rgba(4, 70, 48, 0.18);
+    }
+
+    .list-filter-dock {
+      margin: -14px 12px 0;
+      position: relative;
+      z-index: 3;
+      background: rgba(255, 255, 255, 0.92);
+      border: 1px solid rgba(220, 235, 228, 0.95);
+      border-radius: 18px;
+      padding: 10px;
+      box-shadow: 0 10px 28px rgba(15, 55, 40, 0.08);
+      backdrop-filter: blur(10px);
+    }
+
+    .seg-tabs {
+      display: grid;
+      grid-template-columns: repeat(4, 1fr);
+      gap: 4px;
+      padding: 4px;
+      border-radius: 14px;
+      background: #eef6f2;
+    }
+
+    .seg-tab {
+      border: none;
+      background: transparent;
+      border-radius: 11px;
+      padding: 8px 4px;
+      cursor: pointer;
+      color: #6b7b74;
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      gap: 2px;
+      transition: all .2s;
+      -webkit-tap-highlight-color: transparent;
+    }
+
+    .seg-tab .lab {
+      font-size: 12px;
+      line-height: 1.2;
+    }
+
+    .seg-tab .count {
+      font-size: 11px;
+      font-weight: 700;
+      font-variant-numeric: tabular-nums;
+      color: #9aada4;
+    }
+
+    .seg-tab.active {
+      background: #fff;
+      color: var(--brand-green-dark);
+      box-shadow: 0 4px 12px rgba(8, 127, 87, 0.14);
+      font-weight: 650;
+    }
+
+    .seg-tab.active .count {
+      color: var(--brand-green);
+    }
+
+    .list-wrap {
+      padding: 16px 12px 0;
+    }
+
+    .biz-card {
+      position: relative;
+      border: none;
+      border-radius: 20px;
+      padding: 0;
+      margin-bottom: 14px;
+      overflow: hidden;
+      box-shadow: 0 10px 28px rgba(20, 55, 40, 0.08);
+      transition: transform .16s, box-shadow .16s;
+      isolation: isolate;
+    }
+
+    .biz-card:active {
+      transform: translateY(1px) scale(0.995);
+      box-shadow: 0 6px 16px rgba(20, 55, 40, 0.07);
+    }
+
+    .biz-card::before {
+      content: "";
+      position: absolute;
+      inset: 0;
+      z-index: 0;
+      pointer-events: none;
+    }
+
+    .biz-card::after {
+      content: "";
+      position: absolute;
+      width: 140px;
+      height: 140px;
+      right: -36px;
+      top: -48px;
+      border-radius: 50%;
+      z-index: 0;
+      pointer-events: none;
+      opacity: 0.55;
+    }
+
+    .biz-card.status-warning {
+      background: linear-gradient(145deg, #fff9ef 0%, #ffffff 46%, #fffaf3 100%);
+    }
+    .biz-card.status-warning::before {
+      background: linear-gradient(90deg, rgba(214, 163, 67, 0.22), transparent 28%);
+    }
+    .biz-card.status-warning::after {
+      background: radial-gradient(circle, rgba(214, 163, 67, 0.28), transparent 68%);
+    }
+
+    .biz-card.status-primary {
+      background: linear-gradient(145deg, #eaf8f1 0%, #ffffff 48%, #f3fbf7 100%);
+    }
+    .biz-card.status-primary::before {
+      background: linear-gradient(90deg, rgba(10, 159, 109, 0.18), transparent 28%);
+    }
+    .biz-card.status-primary::after {
+      background: radial-gradient(circle, rgba(10, 159, 109, 0.22), transparent 68%);
+    }
+
+    .biz-card.status-danger {
+      background: linear-gradient(145deg, #fff1f1 0%, #ffffff 48%, #fff7f7 100%);
+    }
+    .biz-card.status-danger::before {
+      background: linear-gradient(90deg, rgba(209, 67, 67, 0.16), transparent 28%);
+    }
+    .biz-card.status-danger::after {
+      background: radial-gradient(circle, rgba(209, 67, 67, 0.18), transparent 68%);
+    }
+
+    .biz-card.status-success {
+      background: linear-gradient(145deg, #eef8f3 0%, #ffffff 50%, #f6fbf8 100%);
+    }
+    .biz-card.status-success::before {
+      background: linear-gradient(90deg, rgba(24, 155, 101, 0.16), transparent 28%);
+    }
+    .biz-card.status-success::after {
+      background: radial-gradient(circle, rgba(24, 155, 101, 0.18), transparent 68%);
+    }
+
+    .biz-card .card-inner {
+      position: relative;
+      z-index: 1;
+      padding: 14px 14px 12px;
+    }
+
+    .biz-card .row-top {
+      display: flex;
+      align-items: flex-start;
+      gap: 10px;
+      margin-bottom: 12px;
+    }
+
+    .biz-card .type-icon {
+      width: 44px;
+      height: 44px;
+      border-radius: 14px;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      flex-shrink: 0;
+      font-size: 22px;
+      box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.65);
+    }
+
+    .biz-card.status-warning .type-icon {
+      background: linear-gradient(145deg, #ffe8b8, #f6d089);
+      color: #7a5610;
+    }
+
+    .biz-card.status-primary .type-icon {
+      background: linear-gradient(145deg, #8fe0bf, #0a9f6d);
+      color: #fff;
+    }
+
+    .biz-card.status-danger .type-icon {
+      background: linear-gradient(145deg, #ffb4b4, #e85a5a);
+      color: #fff;
+    }
+
+    .biz-card.status-success .type-icon {
+      background: linear-gradient(145deg, #b8efd4, #189b65);
+      color: #fff;
+    }
+
+    .biz-card .title-wrap {
+      flex: 1;
+      min-width: 0;
+      padding-top: 1px;
+    }
+
+    .biz-card .type-chip {
+      display: inline-flex;
+      align-items: center;
+      gap: 4px;
+      font-size: 10px;
+      font-weight: 650;
+      letter-spacing: 0.04em;
+      padding: 2px 7px;
+      border-radius: 999px;
+      margin-bottom: 5px;
+    }
+
+    .biz-card.status-warning .type-chip {
+      background: rgba(214, 163, 67, 0.16);
+      color: #8f6718;
+    }
+
+    .biz-card.status-primary .type-chip {
+      background: rgba(10, 159, 109, 0.12);
+      color: #087f57;
+    }
+
+    .biz-card.status-danger .type-chip {
+      background: rgba(209, 67, 67, 0.12);
+      color: #b13535;
+    }
+
+    .biz-card.status-success .type-chip {
+      background: rgba(24, 155, 101, 0.12);
+      color: #147a50;
+    }
+
+    .biz-card .title {
+      font-size: 15px;
+      font-weight: 700;
+      line-height: 1.35;
+      color: #173128;
+      letter-spacing: -0.01em;
+    }
+
+    .biz-card .status-pill {
+      flex-shrink: 0;
+      margin-top: 2px;
+      font-size: 11px;
+      font-weight: 700;
+      padding: 4px 9px;
+      border-radius: 999px;
+      letter-spacing: 0.02em;
+    }
+
+    .biz-card.status-warning .status-pill {
+      background: #fff;
+      color: #a67b20;
+      box-shadow: 0 2px 8px rgba(214, 163, 67, 0.22);
+    }
+
+    .biz-card.status-primary .status-pill {
+      background: #0a9f6d;
+      color: #fff;
+      box-shadow: 0 4px 12px rgba(10, 159, 109, 0.28);
+    }
+
+    .biz-card.status-danger .status-pill {
+      background: #d14343;
+      color: #fff;
+      box-shadow: 0 4px 12px rgba(209, 67, 67, 0.25);
+    }
+
+    .biz-card.status-success .status-pill {
+      background: rgba(24, 155, 101, 0.12);
+      color: #147a50;
+    }
+
+    .biz-card .body-row {
+      display: flex;
+      gap: 12px;
+      align-items: stretch;
+    }
+
+    .biz-card .desc-box {
+      flex: 1;
+      margin: 0;
+      padding: 10px 11px;
+      border-radius: 12px;
+      background: rgba(255, 255, 255, 0.72);
+      border: 1px solid rgba(255, 255, 255, 0.9);
+      font-size: 12px;
+      color: #3f564c;
+      line-height: 1.5;
+      backdrop-filter: blur(2px);
+    }
+
+    .biz-card .side-metric {
+      width: 86px;
+      flex-shrink: 0;
+      border-radius: 12px;
+      padding: 8px 8px;
+      display: flex;
+      flex-direction: column;
+      justify-content: center;
+      align-items: flex-end;
+      text-align: right;
+      background: rgba(255, 255, 255, 0.65);
+      border: 1px solid rgba(255, 255, 255, 0.95);
+    }
+
+    .biz-card .side-metric .amt-label {
+      font-size: 10px;
+      color: #8aa198;
+    }
+
+    .biz-card .side-metric .amt {
+      margin-top: 2px;
+      font-size: 15px;
+      font-weight: 800;
+      color: #173128;
+      letter-spacing: -0.02em;
+      font-variant-numeric: tabular-nums;
+      line-height: 1.15;
+    }
+
+    .biz-card.status-warning .side-metric .amt { color: #9a7424; }
+    .biz-card.status-primary .side-metric .amt { color: #087f57; }
+    .biz-card.status-danger .side-metric .amt { color: #b13535; }
+
+    .biz-card .side-metric .time {
+      margin-top: 6px;
+      font-size: 10px;
+      color: #8aa198;
+    }
+
+    .biz-card .meta-row {
+      display: flex;
+      flex-wrap: wrap;
+      gap: 8px;
+      margin-top: 12px;
+    }
+
+    .biz-card .meta-row .mi {
+      display: inline-flex;
+      align-items: center;
+      gap: 4px;
+      font-size: 11px;
+      color: #5d746a;
+      background: rgba(255, 255, 255, 0.7);
+      border-radius: 999px;
+      padding: 3px 8px;
+    }
+
+    .biz-card .meta-row .van-icon {
+      font-size: 12px;
+      color: #7f968c;
+    }
+
+    .biz-card .code-pill {
+      font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
+      font-size: 10px;
+      letter-spacing: 0.02em;
+    }
+
+    .biz-card .footer {
+      margin-top: 12px;
+      padding-top: 10px;
+      border-top: 1px dashed rgba(140, 170, 155, 0.28);
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      gap: 8px;
+    }
+
+    .biz-card .footer .left-hint {
+      font-size: 11px;
+      color: #7f968c;
+      display: inline-flex;
+      align-items: center;
+      gap: 4px;
+    }
+
+    .biz-card .footer .left-hint i {
+      width: 6px;
+      height: 6px;
+      border-radius: 50%;
+      display: inline-block;
+      background: currentColor;
+      opacity: 0.75;
+    }
+
+    .biz-card.status-warning .footer .left-hint { color: #a67b20; }
+    .biz-card.status-primary .footer .left-hint { color: #0a9f6d; }
+    .biz-card.status-danger .footer .left-hint { color: #d14343; }
+    .biz-card.status-success .footer .left-hint { color: #189b65; }
+
+    .biz-card .footer .ops {
+      display: flex;
+      gap: 8px;
+    }
+
+    .biz-card .footer .van-button {
+      min-width: 64px;
+      border-radius: 999px;
+      height: 30px;
+    }
+
+    .biz-card .footer .van-button--primary {
+      background: linear-gradient(135deg, #12b37a, #087f57);
+      border: none;
+      box-shadow: 0 4px 12px rgba(8, 127, 87, 0.28);
+    }
+
+    .list-end {
+      padding: 8px 0 28px;
+      text-align: center;
+      font-size: 11px;
+      color: #8aa198;
+      letter-spacing: 0.08em;
+    }
+
+    .list-end span {
+      display: inline-flex;
+      align-items: center;
+      gap: 10px;
+    }
+
+    .list-end span::before,
+    .list-end span::after {
+      content: "";
+      width: 28px;
+      height: 1px;
+      background: linear-gradient(90deg, transparent, #c5d8ce);
+    }
+
+    .list-end span::after {
+      background: linear-gradient(90deg, #c5d8ce, transparent);
+    }
+
+    .list-empty-pad {
+      padding: 40px 0;
+    }
+
+    /* ========== 详情 ========== */
+    .detail-page {
+      flex: 1;
+      display: flex;
+      flex-direction: column;
+      min-height: 0;
+      background:
+        radial-gradient(380px 200px at 100% 8%, rgba(214, 163, 67, 0.14), transparent 55%),
+        linear-gradient(180deg, #e8f5ef 0%, #f4f7f6 42%, #f7f9f8 100%);
+    }
+
+    .detail-page .screen-scroll {
+      background: transparent;
+      padding-bottom: 88px;
+    }
+
+    .detail-hero {
+      position: relative;
+      overflow: hidden;
+      padding: 44px 14px 28px;
+      border-radius: 0 0 28px 28px;
+      color: #fff;
+    }
+
+    .detail-hero.status-warning {
+      background:
+        radial-gradient(220px 140px at 90% 10%, rgba(255, 255, 255, 0.2), transparent 60%),
+        linear-gradient(155deg, #a67b20 0%, #d6a343 45%, #e8c06a 100%);
+    }
+
+    .detail-hero.status-primary {
+      background:
+        radial-gradient(220px 140px at 90% 10%, rgba(255, 255, 255, 0.18), transparent 60%),
+        linear-gradient(155deg, #066647 0%, #0a9f6d 48%, #12b37a 100%);
+    }
+
+    .detail-hero.status-danger {
+      background:
+        radial-gradient(220px 140px at 90% 10%, rgba(255, 255, 255, 0.16), transparent 60%),
+        linear-gradient(155deg, #9c2f2f 0%, #d14343 50%, #e36a6a 100%);
+    }
+
+    .detail-hero.status-success {
+      background:
+        radial-gradient(220px 140px at 90% 10%, rgba(255, 255, 255, 0.16), transparent 60%),
+        linear-gradient(155deg, #0f6b48 0%, #189b65 52%, #3bb883 100%);
+    }
+
+    .detail-hero::after {
+      content: "";
+      position: absolute;
+      width: 180px;
+      height: 180px;
+      right: -48px;
+      bottom: -70px;
+      border-radius: 50%;
+      background: rgba(255, 255, 255, 0.1);
+      pointer-events: none;
+    }
+
+    .detail-hero .van-nav-bar {
+      background: transparent;
+      margin: 0 -6px;
+    }
+
+    .detail-hero .van-hairline--bottom::after {
+      display: none;
+    }
+
+    .detail-hero-body {
+      position: relative;
+      z-index: 1;
+      margin-top: 6px;
+    }
+
+    .detail-hero .type-line {
+      display: flex;
+      align-items: center;
+      gap: 8px;
+      margin-bottom: 10px;
+    }
+
+    .detail-hero .type-chip {
+      font-size: 11px;
+      font-weight: 650;
+      letter-spacing: 0.04em;
+      padding: 3px 9px;
+      border-radius: 999px;
+      background: rgba(255, 255, 255, 0.2);
+      color: #fff;
+    }
+
+    .detail-hero .status-chip {
+      font-size: 11px;
+      font-weight: 700;
+      padding: 3px 9px;
+      border-radius: 999px;
+      background: #fff;
+      color: #173128;
+    }
+
+    .detail-hero.status-warning .status-chip { color: #8f6718; }
+    .detail-hero.status-primary .status-chip { color: #087f57; }
+    .detail-hero.status-danger .status-chip { color: #b13535; }
+    .detail-hero.status-success .status-chip { color: #147a50; }
+
+    .detail-hero h2 {
+      margin: 0;
+      font-size: 20px;
+      font-weight: 750;
+      line-height: 1.35;
+      letter-spacing: -0.01em;
+    }
+
+    .detail-hero .code {
+      margin-top: 8px;
+      font-size: 12px;
+      opacity: 0.92;
+      font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
+      letter-spacing: 0.02em;
+    }
+
+    .detail-metric-dock {
+      margin: -18px 12px 0;
+      position: relative;
+      z-index: 3;
+      display: grid;
+      grid-template-columns: repeat(3, 1fr);
+      gap: 8px;
+      padding: 12px;
+      border-radius: 18px;
+      background: rgba(255, 255, 255, 0.94);
+      border: 1px solid rgba(220, 235, 228, 0.95);
+      box-shadow: 0 12px 30px rgba(15, 55, 40, 0.1);
+      backdrop-filter: blur(10px);
+    }
+
+    .detail-metric {
+      text-align: center;
+      padding: 4px 2px;
+    }
+
+    .detail-metric .lab {
+      display: block;
+      font-size: 10px;
+      color: #8aa198;
+      margin-bottom: 4px;
+    }
+
+    .detail-metric .val {
+      display: block;
+      font-size: 14px;
+      font-weight: 750;
+      color: #173128;
+      line-height: 1.25;
+      word-break: break-all;
+    }
+
+    .detail-metric .val.money {
+      color: #9a7424;
+      font-variant-numeric: tabular-nums;
+    }
+
+    .detail-body {
+      padding: 16px 12px 0;
+    }
+
+    .detail-section {
+      background: #fff;
+      border-radius: 18px;
+      border: 1px solid rgba(220, 235, 228, 0.9);
+      box-shadow: 0 8px 22px rgba(20, 55, 40, 0.05);
+      padding: 14px 14px 12px;
+      margin-bottom: 12px;
+      overflow: hidden;
+    }
+
+    .detail-section .sec-hd {
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      margin-bottom: 12px;
+    }
+
+    .detail-section .sec-hd h3 {
+      margin: 0;
+      font-size: 14px;
+      font-weight: 700;
+      color: #173128;
+      display: inline-flex;
+      align-items: center;
+      gap: 8px;
+    }
+
+    .detail-section .sec-hd h3::before {
+      content: "";
+      width: 4px;
+      height: 14px;
+      border-radius: 999px;
+      background: linear-gradient(180deg, #12b37a, #087f57);
+    }
+
+    .detail-section .sec-hd .more {
+      border: none;
+      background: transparent;
+      color: var(--brand-green);
+      font-size: 12px;
+      cursor: pointer;
+      padding: 0;
+    }
+
+    .kv-list {
+      display: flex;
+      flex-direction: column;
+      gap: 10px;
+    }
+
+    .kv-row {
+      display: flex;
+      align-items: flex-start;
+      gap: 12px;
+      font-size: 13px;
+      line-height: 1.45;
+    }
+
+    .kv-row .k {
+      width: 72px;
+      flex-shrink: 0;
+      color: #8aa198;
+    }
+
+    .kv-row .v {
+      flex: 1;
+      color: #173128;
+      font-weight: 550;
+      word-break: break-all;
+    }
+
+    .detail-note {
+      margin: 0;
+      padding: 10px 12px;
+      border-radius: 12px;
+      background: linear-gradient(135deg, #fff8ec, #fffdf8);
+      border: 1px solid #f0e0b8;
+      color: #8a6520;
+      font-size: 12px;
+      line-height: 1.5;
+    }
+
+    /* 详情 · 流转记录 */
+    .flow-list {
+      display: flex;
+      flex-direction: column;
+      gap: 0;
+      position: relative;
+    }
+
+    .flow-item {
+      position: relative;
+      display: flex;
+      gap: 12px;
+      padding-bottom: 14px;
+    }
+
+    .flow-item:last-child {
+      padding-bottom: 0;
+    }
+
+    .flow-item:not(:last-child) .flow-rail::after {
+      content: "";
+      position: absolute;
+      left: 50%;
+      top: 36px;
+      bottom: -14px;
+      width: 2px;
+      transform: translateX(-50%);
+      background: linear-gradient(180deg, rgba(10, 159, 109, 0.45), rgba(220, 235, 228, 0.9));
+      border-radius: 2px;
+    }
+
+    .flow-rail {
+      position: relative;
+      width: 36px;
+      flex-shrink: 0;
+      display: flex;
+      justify-content: center;
+    }
+
+    .flow-dot {
+      width: 36px;
+      height: 36px;
+      border-radius: 12px;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      font-size: 16px;
+      color: #fff;
+      box-shadow: 0 6px 14px rgba(8, 127, 87, 0.2);
+      position: relative;
+      z-index: 1;
+    }
+
+    .flow-item.done .flow-dot {
+      background: linear-gradient(145deg, #2ecf8f, #0a9f6d);
+    }
+
+    .flow-item.warn .flow-dot {
+      background: linear-gradient(145deg, #ecc06a, #d6a343);
+      box-shadow: 0 6px 14px rgba(214, 163, 67, 0.28);
+    }
+
+    .flow-item.danger .flow-dot {
+      background: linear-gradient(145deg, #ef7a7a, #d14343);
+      box-shadow: 0 6px 14px rgba(209, 67, 67, 0.25);
+    }
+
+    .flow-card {
+      flex: 1;
+      min-width: 0;
+      border-radius: 14px;
+      padding: 11px 12px;
+      background: linear-gradient(160deg, #f7fbf9 0%, #ffffff 55%);
+      border: 1px solid #e4eee9;
+      box-shadow: 0 4px 12px rgba(20, 55, 40, 0.04);
+    }
+
+    .flow-item.warn .flow-card {
+      background: linear-gradient(160deg, #fff9ef 0%, #ffffff 60%);
+      border-color: #f0e0b8;
+    }
+
+    .flow-item.danger .flow-card {
+      background: linear-gradient(160deg, #fff4f4 0%, #ffffff 60%);
+      border-color: #f3d0d0;
+    }
+
+    .flow-card .flow-top {
+      display: flex;
+      align-items: flex-start;
+      justify-content: space-between;
+      gap: 8px;
+      margin-bottom: 4px;
+    }
+
+    .flow-card .flow-title {
+      font-size: 13px;
+      font-weight: 700;
+      color: #173128;
+      line-height: 1.35;
+    }
+
+    .flow-card .flow-badge {
+      flex-shrink: 0;
+      font-size: 10px;
+      font-weight: 700;
+      padding: 2px 7px;
+      border-radius: 999px;
+      letter-spacing: 0.02em;
+    }
+
+    .flow-item.done .flow-badge {
+      background: rgba(10, 159, 109, 0.12);
+      color: #087f57;
+    }
+
+    .flow-item.warn .flow-badge {
+      background: rgba(214, 163, 67, 0.16);
+      color: #8f6718;
+    }
+
+    .flow-item.danger .flow-badge {
+      background: rgba(209, 67, 67, 0.12);
+      color: #b13535;
+    }
+
+    .flow-card .flow-desc {
+      font-size: 12px;
+      color: #5d746a;
+      line-height: 1.5;
+    }
+
+    .flow-card .flow-meta {
+      margin-top: 8px;
+      display: flex;
+      flex-wrap: wrap;
+      gap: 8px;
+      align-items: center;
+    }
+
+    .flow-card .flow-meta .chip {
+      display: inline-flex;
+      align-items: center;
+      gap: 4px;
+      font-size: 11px;
+      color: #6b7b74;
+      background: rgba(255, 255, 255, 0.9);
+      border: 1px solid #e8f0ec;
+      border-radius: 999px;
+      padding: 2px 8px;
+    }
+
+    .flow-card .flow-meta .van-icon {
+      font-size: 12px;
+      color: #8aa198;
+    }
+
+    .pay-row {
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      gap: 10px;
+      padding: 10px 12px;
+      border-radius: 12px;
+      background: #f7faf9;
+      margin-bottom: 8px;
+    }
+
+    .pay-row:last-child {
+      margin-bottom: 0;
+    }
+
+    .pay-row .left .name {
+      font-size: 13px;
+      font-weight: 650;
+      color: #173128;
+    }
+
+    .pay-row .left .sub {
+      margin-top: 2px;
+      font-size: 11px;
+      color: #8aa198;
+    }
+
+    .pay-row .right {
+      text-align: right;
+    }
+
+    .pay-row .right .amt {
+      font-size: 14px;
+      font-weight: 750;
+      color: #9a7424;
+      font-variant-numeric: tabular-nums;
+    }
+
+    /* 详情 · 附件 */
+    .d-attach-list {
+      display: flex;
+      flex-direction: column;
+      gap: 10px;
+    }
+
+    .d-attach-upload {
+      width: 100%;
+      border: 1.5px dashed #9ad9be;
+      border-radius: 14px;
+      background:
+        linear-gradient(135deg, rgba(233, 249, 242, 0.95), rgba(255, 255, 255, 0.95)),
+        #e9f9f2;
+      padding: 14px 12px;
+      display: flex;
+      align-items: center;
+      gap: 12px;
+      cursor: pointer;
+      text-align: left;
+      transition: transform .15s, border-color .15s;
+    }
+
+    .d-attach-upload:active {
+      transform: scale(0.99);
+      border-color: #0a9f6d;
+    }
+
+    .d-attach-upload .up-icon {
+      width: 44px;
+      height: 44px;
+      border-radius: 12px;
+      background: linear-gradient(145deg, #2ecf8f, #0a9f6d);
+      color: #fff;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      font-size: 22px;
+      box-shadow: 0 6px 14px rgba(10, 159, 109, 0.28);
+      flex-shrink: 0;
+    }
+
+    .d-attach-upload .up-text {
+      flex: 1;
+      min-width: 0;
+    }
+
+    .d-attach-upload .up-title {
+      font-size: 13px;
+      font-weight: 700;
+      color: #087f57;
+    }
+
+    .d-attach-upload .up-sub {
+      margin-top: 2px;
+      font-size: 11px;
+      color: #6b7b74;
+      line-height: 1.4;
+    }
+
+    .d-attach-card {
+      display: flex;
+      align-items: center;
+      gap: 12px;
+      padding: 12px;
+      border-radius: 14px;
+      background: #fff;
+      border: 1px solid #e4eee9;
+      box-shadow: 0 4px 14px rgba(20, 55, 40, 0.05);
+      position: relative;
+      overflow: hidden;
+    }
+
+    .d-attach-card::before {
+      content: "";
+      position: absolute;
+      left: 0;
+      top: 0;
+      bottom: 0;
+      width: 3px;
+      background: var(--file-accent, #0a9f6d);
+    }
+
+    .d-attach-card.ft-pdf { --file-accent: #d14343; }
+    .d-attach-card.ft-img { --file-accent: #0a9f6d; }
+    .d-attach-card.ft-xls { --file-accent: #189b65; }
+    .d-attach-card.ft-doc { --file-accent: #3b82f6; }
+    .d-attach-card.ft-other { --file-accent: #d6a343; }
+
+    .d-attach-card .file-icon {
+      width: 46px;
+      height: 46px;
+      border-radius: 13px;
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      justify-content: center;
+      gap: 1px;
+      flex-shrink: 0;
+      color: #fff;
+      font-weight: 800;
+      box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.2);
+    }
+
+    .d-attach-card.ft-pdf .file-icon {
+      background: linear-gradient(145deg, #f07171, #d14343);
+    }
+
+    .d-attach-card.ft-img .file-icon {
+      background: linear-gradient(145deg, #3dca96, #0a9f6d);
+    }
+
+    .d-attach-card.ft-xls .file-icon {
+      background: linear-gradient(145deg, #4fbf8a, #189b65);
+    }
+
+    .d-attach-card.ft-doc .file-icon {
+      background: linear-gradient(145deg, #6ea8ff, #3b82f6);
+    }
+
+    .d-attach-card.ft-other .file-icon {
+      background: linear-gradient(145deg, #e4bc6a, #d6a343);
+    }
+
+    .d-attach-card .file-icon .ext {
+      font-size: 10px;
+      letter-spacing: 0.04em;
+      line-height: 1;
+      text-transform: uppercase;
+    }
+
+    .d-attach-card .file-icon .van-icon {
+      font-size: 16px;
+      opacity: 0.95;
+    }
+
+    .d-attach-card .file-body {
+      flex: 1;
+      min-width: 0;
+    }
+
+    .d-attach-card .file-name {
+      font-size: 13px;
+      font-weight: 650;
+      color: #173128;
+      line-height: 1.35;
+      white-space: nowrap;
+      overflow: hidden;
+      text-overflow: ellipsis;
+    }
+
+    .d-attach-card .file-meta {
+      margin-top: 4px;
+      display: flex;
+      flex-wrap: wrap;
+      gap: 8px;
+      font-size: 11px;
+      color: #8aa198;
+    }
+
+    .d-attach-card .file-ops {
+      display: flex;
+      flex-direction: column;
+      gap: 6px;
+      flex-shrink: 0;
+    }
+
+    .d-attach-card .file-ops button {
+      border: none;
+      cursor: pointer;
+      width: 32px;
+      height: 32px;
+      border-radius: 10px;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      font-size: 16px;
+      transition: transform .12s;
+    }
+
+    .d-attach-card .file-ops button:active {
+      transform: scale(0.94);
+    }
+
+    .d-attach-card .file-ops .btn-preview {
+      background: var(--brand-green-soft);
+      color: var(--brand-green-dark);
+    }
+
+    .d-attach-card .file-ops .btn-download {
+      background: #f0f5f3;
+      color: #4d635a;
+    }
+
+    .d-attach-card .file-ops .icon-download {
+      width: 14px;
+      height: 14px;
+      display: block;
+    }
+
+    .detail-footer {
+      position: absolute;
+      left: 0;
+      right: 0;
+      bottom: 0;
+      display: flex;
+      gap: 10px;
+      padding: 12px 14px calc(12px + env(safe-area-inset-bottom));
+      background: rgba(255, 255, 255, 0.96);
+      border-top: 1px solid var(--border);
+      backdrop-filter: blur(10px);
+      z-index: 4;
+    }
+
+    .detail-footer .van-button {
+      flex: 1;
+      height: 44px;
+      border-radius: 999px;
+    }
+
+    .detail-footer .van-button--primary {
+      background: linear-gradient(135deg, #12b37a, #087f57);
+      border: none;
+      box-shadow: 0 6px 16px rgba(8, 127, 87, 0.28);
+    }
+
+    /* ========== 表单 ========== */
+    .form-page {
+      padding-bottom: 88px;
+    }
+
+    .form-section-title {
+      margin: 14px 16px 8px;
+      font-size: 13px;
+      font-weight: 600;
+      color: var(--text-muted);
+    }
+
+    .form-tip {
+      margin: 8px 16px 0;
+      font-size: 11px;
+      color: var(--text-muted);
+      line-height: 1.45;
+    }
+
+    .form-footer {
+      position: absolute;
+      left: 0;
+      right: 0;
+      bottom: 0;
+      display: flex;
+      gap: 10px;
+      padding: 12px 14px calc(12px + env(safe-area-inset-bottom));
+      background: rgba(255, 255, 255, 0.96);
+      border-top: 1px solid var(--border);
+      backdrop-filter: blur(8px);
+      z-index: 3;
+    }
+
+    .form-footer .van-button {
+      flex: 1;
+      height: 44px;
+    }
+
+    /* ========== 组件展示 ========== */
+    .comps-page {
+      flex: 1;
+      display: flex;
+      flex-direction: column;
+      min-height: 0;
+      background:
+        radial-gradient(400px 220px at 100% 0%, rgba(214, 163, 67, 0.14), transparent 55%),
+        radial-gradient(360px 200px at 0% 20%, rgba(10, 159, 109, 0.1), transparent 50%),
+        linear-gradient(180deg, #e8f5ef 0%, #f4f7f6 40%, #f7f9f8 100%);
+    }
+
+    .comps-page .screen-scroll {
+      background: transparent;
+    }
+
+    .comps-hero {
+      background:
+        radial-gradient(220px 140px at 90% 15%, rgba(255, 255, 255, 0.18), transparent 60%),
+        linear-gradient(155deg, #066647 0%, #0a9f6d 48%, #12b37a 100%);
+      padding: 44px 14px 22px;
+      border-radius: 0 0 28px 28px;
+      color: #fff;
+      position: relative;
+      overflow: hidden;
+    }
+
+    .comps-hero::after {
+      content: "";
+      position: absolute;
+      width: 160px;
+      height: 160px;
+      right: -40px;
+      bottom: -60px;
+      border-radius: 50%;
+      background: rgba(255, 255, 255, 0.08);
+      pointer-events: none;
+    }
+
+    .comps-hero .van-nav-bar {
+      background: transparent;
+      margin: 0 -6px;
+    }
+
+    .comps-hero .van-hairline--bottom::after {
+      display: none;
+    }
+
+    .comps-hero-copy {
+      position: relative;
+      z-index: 1;
+      margin: 4px 2px 0;
+    }
+
+    .comps-hero-copy .eyebrow {
+      font-size: 11px;
+      letter-spacing: 0.08em;
+      opacity: 0.82;
+      margin-bottom: 4px;
+    }
+
+    .comps-hero-copy h2 {
+      margin: 0;
+      font-size: 22px;
+      font-weight: 750;
+    }
+
+    .comps-hero-copy .subline {
+      margin-top: 6px;
+      font-size: 12px;
+      opacity: 0.9;
+      line-height: 1.45;
+    }
+
+    .comp-section {
+      padding: 14px 12px 28px;
+    }
+
+    .comp-block {
+      position: relative;
+      background: #fff;
+      border: 1px solid rgba(220, 235, 228, 0.95);
+      border-radius: 18px;
+      padding: 0;
+      margin-bottom: 12px;
+      box-shadow: 0 8px 22px rgba(20, 55, 40, 0.05);
+      overflow: hidden;
+    }
+
+    .comp-block .comp-hd {
+      display: flex;
+      align-items: center;
+      gap: 10px;
+      padding: 12px 14px;
+      background: linear-gradient(135deg, #f3faf6 0%, #ffffff 70%);
+      border-bottom: 1px solid #eef4f1;
+    }
+
+    .comp-block .comp-hd .hd-icon {
+      width: 32px;
+      height: 32px;
+      border-radius: 10px;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      font-size: 16px;
+      color: #fff;
+      background: linear-gradient(145deg, #2ecf8f, #0a9f6d);
+      box-shadow: 0 4px 10px rgba(10, 159, 109, 0.22);
+      flex-shrink: 0;
+    }
+
+    .comp-block .comp-hd .hd-icon.tone-amber {
+      background: linear-gradient(145deg, #ecc06a, #d6a343);
+      box-shadow: 0 4px 10px rgba(214, 163, 67, 0.25);
+    }
+
+    .comp-block .comp-hd .hd-icon.tone-rose {
+      background: linear-gradient(145deg, #f07171, #d14343);
+      box-shadow: 0 4px 10px rgba(209, 67, 67, 0.22);
+    }
+
+    .comp-block .comp-hd .hd-icon.tone-slate {
+      background: linear-gradient(145deg, #8aa198, #5d746a);
+      box-shadow: 0 4px 10px rgba(93, 116, 106, 0.2);
+    }
+
+    .comp-block .comp-hd h4 {
+      margin: 0;
+      font-size: 14px;
+      font-weight: 750;
+      color: #173128;
+      line-height: 1.2;
+    }
+
+    .comp-block .comp-hd .hd-sub {
+      margin-top: 2px;
+      font-size: 11px;
+      color: #8aa198;
+    }
+
+    .comp-block .comp-bd {
+      padding: 14px;
+    }
+
+    .comp-row {
+      display: flex;
+      flex-wrap: wrap;
+      gap: 8px;
+      align-items: center;
+      margin-bottom: 10px;
+    }
+
+    .comp-row:last-child { margin-bottom: 0; }
+
+    .label-muted {
+      font-size: 11px;
+      color: #8aa198;
+      width: 100%;
+      margin-bottom: 2px;
+    }
+
+    .comps-page .van-button--primary {
+      background: linear-gradient(135deg, #12b37a, #087f57);
+      border: none;
+      box-shadow: 0 4px 12px rgba(8, 127, 87, 0.22);
+    }
+
+    .comps-page .van-button--primary.van-button--plain {
+      background: #fff;
+      box-shadow: none;
+      color: #0a9f6d;
+      border-color: #0a9f6d;
+    }
+
+    .comps-page .van-button--round {
+      border-radius: 999px;
+    }
+
+    .tag-grid {
+      display: flex;
+      flex-wrap: wrap;
+      gap: 8px;
+    }
+
+    .demo-tag {
+      display: inline-flex;
+      align-items: center;
+      gap: 5px;
+      padding: 5px 10px;
+      border-radius: 999px;
+      font-size: 12px;
+      font-weight: 650;
+    }
+
+    .demo-tag i {
+      width: 6px;
+      height: 6px;
+      border-radius: 50%;
+      background: currentColor;
+    }
+
+    .demo-tag.t-warn {
+      background: #fff3e0;
+      color: #a67b20;
+    }
+
+    .demo-tag.t-primary {
+      background: #0a9f6d;
+      color: #fff;
+    }
+
+    .demo-tag.t-success {
+      background: rgba(24, 155, 101, 0.12);
+      color: #147a50;
+    }
+
+    .demo-tag.t-danger {
+      background: #fdecec;
+      color: #d14343;
+    }
+
+    .demo-tag.t-muted {
+      background: #eef2f1;
+      color: #6b7b74;
+    }
+
+    .comp-cell-list {
+      display: flex;
+      flex-direction: column;
+      gap: 8px;
+    }
+
+    .comp-cell-item {
+      display: flex;
+      align-items: center;
+      gap: 12px;
+      padding: 12px 12px;
+      border-radius: 14px;
+      background: linear-gradient(160deg, #f7fbf9 0%, #ffffff 55%);
+      border: 1px solid #e4eee9;
+      box-shadow: 0 4px 12px rgba(20, 55, 40, 0.04);
+      cursor: pointer;
+      text-align: left;
+      width: 100%;
+      transition: transform .15s, border-color .15s;
+    }
+
+    .comp-cell-item:active {
+      transform: scale(0.99);
+      border-color: #b7e5d0;
+    }
+
+    .comp-cell-item .ci-icon {
+      width: 40px;
+      height: 40px;
+      border-radius: 12px;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      font-size: 18px;
+      color: #fff;
+      flex-shrink: 0;
+      background: linear-gradient(145deg, #2ecf8f, #0a9f6d);
+      box-shadow: 0 4px 10px rgba(10, 159, 109, 0.2);
+    }
+
+    .comp-cell-item .ci-icon.tone-amber {
+      background: linear-gradient(145deg, #ecc06a, #d6a343);
+      box-shadow: 0 4px 10px rgba(214, 163, 67, 0.22);
+    }
+
+    .comp-cell-item .ci-icon.tone-slate {
+      background: linear-gradient(145deg, #8aa198, #5d746a);
+      box-shadow: 0 4px 10px rgba(93, 116, 106, 0.18);
+    }
+
+    .comp-cell-item .ci-body {
+      flex: 1;
+      min-width: 0;
+    }
+
+    .comp-cell-item .ci-title {
+      font-size: 14px;
+      font-weight: 700;
+      color: #173128;
+      line-height: 1.3;
+    }
+
+    .comp-cell-item .ci-label {
+      margin-top: 2px;
+      font-size: 11px;
+      color: #8aa198;
+      line-height: 1.4;
+    }
+
+    .comp-cell-item .ci-right {
+      display: flex;
+      align-items: center;
+      gap: 6px;
+      flex-shrink: 0;
+      color: #8aa198;
+      font-size: 13px;
+      font-weight: 550;
+    }
+
+    .comp-cell-item .ci-right .val {
+      color: #4d635a;
+    }
+
+    .comp-feedback-grid {
+      display: grid;
+      grid-template-columns: repeat(3, 1fr);
+      gap: 8px;
+    }
+
+    .comp-feedback-grid .fb-btn {
+      border: 1px solid #e4eee9;
+      background: linear-gradient(160deg, #f7fbf9, #fff);
+      border-radius: 14px;
+      padding: 14px 8px;
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      gap: 8px;
+      cursor: pointer;
+      color: #173128;
+      font-size: 12px;
+      font-weight: 650;
+      transition: transform .15s;
+    }
+
+    .comp-feedback-grid .fb-btn:active {
+      transform: scale(0.97);
+    }
+
+    .comp-feedback-grid .fb-btn .fb-icon {
+      width: 40px;
+      height: 40px;
+      border-radius: 12px;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      font-size: 20px;
+      color: #fff;
+    }
+
+    .comp-feedback-grid .fb-btn.fb-toast .fb-icon {
+      background: linear-gradient(145deg, #2ecf8f, #0a9f6d);
+    }
+
+    .comp-feedback-grid .fb-btn.fb-dialog .fb-icon {
+      background: linear-gradient(145deg, #ecc06a, #d6a343);
+    }
+
+    .comp-feedback-grid .fb-btn.fb-sheet .fb-icon {
+      background: linear-gradient(145deg, #8aa198, #5d746a);
+    }
+
+    .comp-steps-wrap {
+      padding: 8px 4px 0;
+      border-radius: 12px;
+      background: #f7faf9;
+    }
+
+    .comp-seg {
+      display: grid;
+      grid-template-columns: repeat(3, 1fr);
+      gap: 4px;
+      padding: 4px;
+      border-radius: 14px;
+      background: #eef6f2;
+      margin-bottom: 12px;
+    }
+
+    .comp-seg button {
+      border: none;
+      background: transparent;
+      border-radius: 11px;
+      padding: 8px 4px;
+      font-size: 12px;
+      color: #6b7b74;
+      cursor: pointer;
+    }
+
+    .comp-seg button.active {
+      background: #fff;
+      color: #087f57;
+      font-weight: 700;
+      box-shadow: 0 4px 12px rgba(8, 127, 87, 0.12);
+    }
+
+    .comp-notice-stack {
+      display: flex;
+      flex-direction: column;
+      gap: 12px;
+    }
+
+    .comp-notice-bar {
+      border-radius: 14px;
+      overflow: hidden;
+      border: 1px solid #c7e6d6;
+      box-shadow: 0 4px 12px rgba(10, 159, 109, 0.08);
+      min-height: 44px;
+    }
+
+    .comp-empty-box {
+      border-radius: 16px;
+      background: linear-gradient(165deg, #eef9f3 0%, #ffffff 55%);
+      border: 1.5px dashed #b7e5d0;
+      padding: 28px 16px 24px;
+      min-height: 220px;
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      justify-content: center;
+      box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.8);
+    }
+
+    .comp-empty-box .van-empty {
+      padding: 0;
+    }
+
+    .comp-empty-box .van-empty__description {
+      margin-top: 12px;
+      color: #5d746a;
+      font-size: 13px;
+    }
+
+    .comp-empty-box .empty-tip {
+      margin-top: 4px;
+      font-size: 11px;
+      color: #8aa198;
+      text-align: center;
+      line-height: 1.45;
+    }
+
+    .comp-loading-box {
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      justify-content: center;
+      gap: 12px;
+      padding: 28px 16px;
+      min-height: 120px;
+      border-radius: 16px;
+      background: linear-gradient(165deg, #f3faf6 0%, #ffffff 60%);
+      border: 1px solid #e4eee9;
+      color: #087f57;
+      font-size: 13px;
+      font-weight: 650;
+      box-shadow: 0 4px 12px rgba(20, 55, 40, 0.04);
+    }
+
+    .comp-loading-box .load-sub {
+      font-size: 11px;
+      font-weight: 500;
+      color: #8aa198;
+    }
+
+    .comps-page .comp-attach-list {
+      display: flex;
+      flex-direction: column;
+      gap: 10px;
+      width: 100%;
+    }
+
+    .comps-page .comp-upload-tile {
+      width: 100%;
+      border: 1.5px dashed #9ad9be;
+      border-radius: 16px;
+      background:
+        radial-gradient(120px 80px at 12% 30%, rgba(46, 207, 143, 0.16), transparent 70%),
+        linear-gradient(145deg, #e9f9f2 0%, #ffffff 55%);
+      padding: 16px 14px;
+      display: flex;
+      align-items: center;
+      gap: 12px;
+      cursor: pointer;
+      text-align: left;
+      transition: transform .15s, border-color .15s, box-shadow .15s;
+      box-shadow: 0 6px 16px rgba(10, 159, 109, 0.08);
+    }
+
+    .comps-page .comp-upload-tile:active {
+      transform: scale(0.99);
+      border-color: #0a9f6d;
+      box-shadow: 0 4px 12px rgba(10, 159, 109, 0.12);
+    }
+
+    .comps-page .comp-upload-tile .up-icon {
+      width: 48px;
+      height: 48px;
+      border-radius: 14px;
+      background: linear-gradient(145deg, #2ecf8f, #0a9f6d);
+      color: #fff;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      font-size: 24px;
+      box-shadow: 0 6px 14px rgba(10, 159, 109, 0.28);
+      flex-shrink: 0;
+    }
+
+    .comps-page .comp-upload-tile .up-text {
+      flex: 1;
+      min-width: 0;
+    }
+
+    .comps-page .comp-upload-tile .up-title {
+      display: block;
+      font-size: 14px;
+      font-weight: 750;
+      color: #087f57;
+    }
+
+    .comps-page .comp-upload-tile .up-sub {
+      display: block;
+      margin-top: 3px;
+      font-size: 11px;
+      color: #6b7b74;
+      line-height: 1.45;
+    }
+
+    .comps-page .comp-file-card {
+      display: flex;
+      align-items: center;
+      gap: 12px;
+      padding: 12px;
+      border-radius: 14px;
+      background: #fff;
+      border: 1px solid #e4eee9;
+      box-shadow: 0 4px 14px rgba(20, 55, 40, 0.05);
+      position: relative;
+      overflow: hidden;
+    }
+
+    .comps-page .comp-file-card::before {
+      content: "";
+      position: absolute;
+      left: 0;
+      top: 0;
+      bottom: 0;
+      width: 3px;
+      background: #3b82f6;
+    }
+
+    .comps-page .comp-file-card .file-icon {
+      width: 46px;
+      height: 46px;
+      border-radius: 13px;
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      justify-content: center;
+      gap: 1px;
+      flex-shrink: 0;
+      color: #fff;
+      font-weight: 800;
+      background: linear-gradient(145deg, #6ea8ff, #3b82f6);
+    }
+
+    .comps-page .comp-file-card .file-icon .ext {
+      font-size: 10px;
+      letter-spacing: 0.04em;
+      line-height: 1;
+      text-transform: uppercase;
+    }
+
+    .comps-page .comp-file-card .file-icon .van-icon {
+      font-size: 16px;
+    }
+
+    .comps-page .comp-file-card .file-body {
+      flex: 1;
+      min-width: 0;
+    }
+
+    .comps-page .comp-file-card .file-name {
+      font-size: 13px;
+      font-weight: 650;
+      color: #173128;
+      line-height: 1.35;
+      white-space: nowrap;
+      overflow: hidden;
+      text-overflow: ellipsis;
+    }
+
+    .comps-page .comp-file-card .file-meta {
+      margin-top: 4px;
+      display: flex;
+      flex-wrap: wrap;
+      gap: 8px;
+      font-size: 11px;
+      color: #8aa198;
+    }
+
+    .comps-page .comp-file-card .file-ops {
+      display: flex;
+      flex-direction: column;
+      gap: 6px;
+      flex-shrink: 0;
+    }
+
+    .comps-page .comp-file-card .file-ops button {
+      border: none;
+      cursor: pointer;
+      width: 32px;
+      height: 32px;
+      border-radius: 10px;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      font-size: 16px;
+    }
+
+    .comps-page .comp-file-card .file-ops .btn-preview {
+      background: var(--brand-green-soft);
+      color: var(--brand-green-dark);
+    }
+
+    .comps-page .comp-file-card .file-ops .btn-del {
+      background: #fdecec;
+      color: #d14343;
+    }
+
+    /* 表单页等仍可能用到的旧附件样式 */
+    .upload-card {
+      width: 88px;
+      height: 88px;
+      border: 1.5px dashed #9ad9be;
+      border-radius: 14px;
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      justify-content: center;
+      gap: 6px;
+      color: #087f57;
+      background: linear-gradient(145deg, #e9f9f2, #fff);
+      font-size: 11px;
+      font-weight: 650;
+    }
+
+    .attach-card {
+      width: 140px;
+      min-height: 88px;
+      border: 1px solid #e4eee9;
+      border-radius: 14px;
+      padding: 10px 12px;
+      background: #fff;
+      display: flex;
+      flex-direction: column;
+      justify-content: space-between;
+      font-size: 11px;
+    }
+
+    .attach-card .name {
+      color: #173128;
+      font-weight: 650;
+      word-break: break-all;
+      line-height: 1.35;
+    }
+
+    .attach-card .ops {
+      display: flex;
+      gap: 10px;
+      color: #0a9f6d;
+      font-weight: 650;
+    }
+
+    .demo-badge {
+      display: inline-flex;
+      align-items: center;
+      gap: 4px;
+      padding: 2px 8px;
+      border-radius: 999px;
+      font-size: 11px;
+      background: var(--brand-green-soft);
+      color: var(--brand-green-dark);
+    }
+
+    @media (max-width: 900px) {
+      #app {
+        grid-template-columns: 1fr;
+      }
+      .doc-panel {
+        position: static;
+      }
+      .phone {
+        width: min(390px, 100%);
+        height: min(844px, calc(100vh - 120px));
+        min-height: 640px;
+      }
+    }
+
+    @media (prefers-reduced-motion: reduce) {
+      * { transition: none !important; }
+    }
+  </style>
+</head>
+<body>
+  <div id="app">
+    <aside class="doc-panel">
+      <h1>移动端 UI Demo</h1>
+      <p class="sub">
+        Vant 4 + 三墙 Token(#0a9f6d)。首页为权限菜单卡片;列表 / 新增编辑 / 组件风格可在下方切换预览。
+      </p>
+
+      <div class="section-title">页面预览</div>
+      <div class="screen-nav">
+        <button
+          v-for="s in screens"
+          :key="s.key"
+          :class="{ active: screen === s.key }"
+          type="button"
+          @click="goScreen(s.key)"
+        >{{ s.label }}</button>
+      </div>
+
+      <div class="section-title">设计 Token</div>
+      <div class="token-row">
+        <span class="token-chip"><i style="background:#0a9f6d"></i>#0a9f6d</span>
+        <span class="token-chip"><i style="background:#087f57"></i>#087f57</span>
+        <span class="token-chip"><i style="background:#e9f9f2"></i>#e9f9f2</span>
+        <span class="token-chip"><i style="background:#d6a343"></i>#d6a343</span>
+        <span class="token-chip"><i style="background:#f5f8f7"></i>#f5f8f7</span>
+      </div>
+
+      <div class="section-title">权限说明</div>
+      <ul class="notes">
+        <li>菜单来自「菜单管理 → 移动端菜单」树</li>
+        <li>仅展示当前登录人角色/职位拥有的菜单权限</li>
+        <li>卡片:上 Icon、下标题;建议 4 列宫格</li>
+        <li>本 Demo 用右侧开关模拟「无某菜单权限」</li>
+      </ul>
+
+      <div class="section-title" v-if="screen === 'home'">权限模拟</div>
+      <div v-if="screen === 'home'" style="display:flex;flex-direction:column;gap:8px;">
+        <van-checkbox
+          v-for="m in allMenus"
+          :key="m.id"
+          v-model="grantedMap[m.id]"
+          shape="square"
+          icon-size="16px"
+        >{{ m.title }}</van-checkbox>
+      </div>
+    </aside>
+
+    <div class="phone-stage">
+      <div class="phone">
+        <div class="phone-screen">
+
+          <!-- ===== 首页 ===== -->
+          <template v-if="screen === 'home'">
+            <div class="screen-scroll">
+              <header class="home-header">
+                <div class="brand">中科兴皖 · 移动工作台</div>
+                <h2>你好,{{ userName }}</h2>
+                <div class="meta">
+                  <span>{{ roleName }}</span>
+                  <span class="dot"></span>
+                  <span>{{ deptName }}</span>
+                </div>
+              </header>
+
+              <div class="home-body">
+                <div class="perm-banner">
+                  菜单卡片按角色权限动态渲染;无权限的功能入口不会出现在宫格中。
+                </div>
+
+                <section class="panel">
+                  <div class="panel-hd">
+                    <h3>功能菜单</h3>
+                    <span class="hint">共 {{ visibleMenus.length }} 项</span>
+                  </div>
+                  <div v-if="visibleMenus.length" class="menu-grid">
+                    <button
+                      v-for="m in visibleMenus"
+                      :key="m.id"
+                      type="button"
+                      class="menu-card"
+                      @click="onMenuTap(m)"
+                    >
+                      <div class="menu-icon" :class="m.tone">
+                        <van-icon :name="m.icon" />
+                      </div>
+                      <span class="menu-title">{{ m.title }}</span>
+                    </button>
+                  </div>
+                  <div v-else class="list-empty-pad">
+                    <van-empty description="暂无可用菜单权限" image-size="80" />
+                  </div>
+                </section>
+
+                <section class="panel todo-panel">
+                  <div class="panel-hd">
+                    <h3>待办摘要</h3>
+                    <button type="button" class="demo-badge" style="border:none;cursor:pointer;" @click="screen = 'list'">
+                      查看全部
+                      <van-icon name="arrow" style="font-size:11px;" />
+                    </button>
+                  </div>
+
+                  <div class="todo-stats">
+                    <button type="button" class="todo-stat tone-warn" @click="goListTab(2)">
+                      <span class="stat-icon"><van-icon name="passed" /></span>
+                      <span class="num">{{ todoStats.approve }}</span>
+                      <span class="label">待审批</span>
+                    </button>
+                    <button type="button" class="todo-stat tone-pay" @click="goListTab(2)">
+                      <span class="stat-icon"><van-icon name="balance-pay" /></span>
+                      <span class="num">{{ todoStats.pay }}</span>
+                      <span class="label">待确认缴费</span>
+                    </button>
+                    <button type="button" class="todo-stat tone-danger" @click="goListTab(0)">
+                      <span class="stat-icon"><van-icon name="warning-o" /></span>
+                      <span class="num">{{ todoStats.alert }}</span>
+                      <span class="label">异常提醒</span>
+                    </button>
+                  </div>
+
+                  <div class="todo-list">
+                    <button
+                      v-for="t in todoItems"
+                      :key="t.id"
+                      type="button"
+                      class="todo-item"
+                      @click="screen = 'list'"
+                    >
+                      <div class="ti-icon" :class="t.iconClass">
+                        <van-icon :name="t.icon" />
+                      </div>
+                      <div class="ti-body">
+                        <div class="ti-title">{{ t.title }}</div>
+                        <div class="ti-desc">{{ t.desc }}</div>
+                      </div>
+                      <div class="ti-right">
+                        <van-tag :type="t.tagType" plain size="medium">{{ t.tag }}</van-tag>
+                        <span class="ti-time">{{ t.time }}</span>
+                      </div>
+                    </button>
+                  </div>
+                </section>
+              </div>
+            </div>
+          </template>
+
+          <!-- ===== 列表 ===== -->
+          <template v-else-if="screen === 'list'">
+            <div class="list-page">
+              <div class="screen-scroll">
+                <header class="list-hero">
+                  <van-nav-bar left-arrow @click-left="screen = 'home'">
+                    <template #title>
+                      <span style="font-weight:600;">业务列表</span>
+                    </template>
+                    <template #right>
+                      <van-icon name="plus" size="20" @click="openForm('create')" />
+                    </template>
+                  </van-nav-bar>
+                  <div class="list-hero-copy">
+                    <div class="eyebrow">WORKSPACE</div>
+                    <h2>今日待办一览</h2>
+                    <div class="subline">
+                      <span>当前展示</span>
+                      <em>{{ filteredList.length }} 条</em>
+                      <span v-if="keyword">· 已筛选</span>
+                    </div>
+                  </div>
+                  <div class="list-search">
+                    <van-search
+                      v-model="keyword"
+                      shape="round"
+                      background="transparent"
+                      placeholder="搜索单号 / 名称 / 负责人"
+                    />
+                  </div>
+                </header>
+
+                <div class="list-filter-dock">
+                  <div class="seg-tabs">
+                    <button
+                      v-for="(tab, idx) in listTabs"
+                      :key="tab.key"
+                      type="button"
+                      class="seg-tab"
+                      :class="{ active: listTab === idx }"
+                      @click="listTab = idx"
+                    >
+                      <span class="lab">{{ tab.label }}</span>
+                      <span class="count">{{ tabCount(idx) }}</span>
+                    </button>
+                  </div>
+                </div>
+
+                <div class="list-wrap">
+                  <article
+                    v-for="item in filteredList"
+                    :key="item.id"
+                    class="biz-card"
+                    :class="'status-' + item.tagType"
+                  >
+                    <div class="card-inner">
+                      <div class="row-top">
+                        <div class="type-icon">
+                          <van-icon :name="item.icon" />
+                        </div>
+                        <div class="title-wrap">
+                          <div class="type-chip">{{ item.typeLabel }}</div>
+                          <div class="title">{{ item.title }}</div>
+                        </div>
+                        <span class="status-pill">{{ item.status }}</span>
+                      </div>
+
+                      <div class="body-row">
+                        <p class="desc-box">{{ item.desc }}</p>
+                        <div class="side-metric">
+                          <template v-if="item.amount">
+                            <span class="amt-label">金额</span>
+                            <span class="amt">¥{{ item.amount }}</span>
+                          </template>
+                          <template v-else>
+                            <span class="amt-label">进度</span>
+                            <span class="amt" style="font-size:13px;">{{ item.progress || '—' }}</span>
+                          </template>
+                          <span class="time">{{ item.time }}</span>
+                        </div>
+                      </div>
+
+                      <div class="meta-row">
+                        <span class="mi code-pill">{{ item.code }}</span>
+                        <span class="mi"><van-icon name="manager-o" />{{ item.owner }}</span>
+                      </div>
+
+                      <div class="footer">
+                        <span class="left-hint"><i></i>{{ item.hint || '点击操作处理' }}</span>
+                        <div class="ops">
+                          <van-button size="small" plain hairline type="default" @click="openDetail(item)">查看</van-button>
+                          <van-button size="small" type="primary" @click="openForm('edit', item)">处理</van-button>
+                        </div>
+                      </div>
+                    </div>
+                  </article>
+
+                  <div v-if="!filteredList.length" class="list-empty-pad">
+                    <van-empty description="暂无数据" />
+                  </div>
+
+                  <div v-else class="list-end"><span>已经到底了</span></div>
+                </div>
+              </div>
+            </div>
+          </template>
+
+          <!-- ===== 详情 ===== -->
+          <template v-else-if="screen === 'detail'">
+            <div class="detail-page" v-if="detail">
+              <div class="screen-scroll">
+                <header class="detail-hero" :class="'status-' + detail.tagType">
+                  <van-nav-bar left-arrow @click-left="screen = 'list'">
+                    <template #title>
+                      <span style="font-weight:600;">业务详情</span>
+                    </template>
+                    <template #right>
+                      <van-icon name="ellipsis" size="20" @click="showAction = true" />
+                    </template>
+                  </van-nav-bar>
+                  <div class="detail-hero-body">
+                    <div class="type-line">
+                      <span class="type-chip">{{ detail.typeLabel }}</span>
+                      <span class="status-chip">{{ detail.status }}</span>
+                    </div>
+                    <h2>{{ detail.title }}</h2>
+                    <div class="code">{{ detail.code }}</div>
+                  </div>
+                </header>
+
+                <div class="detail-metric-dock">
+                  <div class="detail-metric">
+                    <span class="lab">{{ detail.amount ? '金额' : '进度' }}</span>
+                    <span class="val" :class="{ money: !!detail.amount }">
+                      {{ detail.amount ? ('¥' + detail.amount) : (detail.progress || '—') }}
+                    </span>
+                  </div>
+                  <div class="detail-metric">
+                    <span class="lab">负责人</span>
+                    <span class="val">{{ detail.owner }}</span>
+                  </div>
+                  <div class="detail-metric">
+                    <span class="lab">更新时间</span>
+                    <span class="val">{{ detail.time }}</span>
+                  </div>
+                </div>
+
+                <div class="detail-body">
+                  <section class="detail-section">
+                    <div class="sec-hd"><h3>基础信息</h3></div>
+                    <div class="kv-list">
+                      <div class="kv-row"><span class="k">业务类型</span><span class="v">{{ detail.typeLabel }}</span></div>
+                      <div class="kv-row"><span class="k">所属部门</span><span class="v">{{ detail.dept || deptName }}</span></div>
+                      <div class="kv-row"><span class="k">客户/对象</span><span class="v">{{ detail.customer || '—' }}</span></div>
+                      <div class="kv-row"><span class="k">创建时间</span><span class="v">{{ detail.createdAt || '—' }}</span></div>
+                    </div>
+                    <p class="detail-note" style="margin-top:12px;">{{ detail.desc }}</p>
+                  </section>
+
+                  <section class="detail-section">
+                    <div class="sec-hd"><h3>业务进度</h3></div>
+                    <van-steps :active="detail.stepActive" active-color="#0a9f6d" inactive-color="#dcebe4">
+                      <van-step v-for="s in detail.steps" :key="s">{{ s }}</van-step>
+                    </van-steps>
+                  </section>
+
+                  <section class="detail-section" v-if="detail.payments && detail.payments.length">
+                    <div class="sec-hd">
+                      <h3>缴费明细</h3>
+                      <button type="button" class="more" @click="toast('查看全部缴费')">全部</button>
+                    </div>
+                    <div
+                      v-for="p in detail.payments"
+                      :key="p.id"
+                      class="pay-row"
+                    >
+                      <div class="left">
+                        <div class="name">{{ p.name }}</div>
+                        <div class="sub">{{ p.sub }}</div>
+                      </div>
+                      <div class="right">
+                        <div class="amt">¥{{ p.amount }}</div>
+                        <van-tag :type="p.tagType" plain size="medium" style="margin-top:4px;">{{ p.status }}</van-tag>
+                      </div>
+                    </div>
+                  </section>
+
+                  <section class="detail-section">
+                    <div class="sec-hd">
+                      <h3>附件</h3>
+                      <span style="font-size:11px;color:#8aa198;">{{ detail.attachments.length }} 个文件</span>
+                    </div>
+                    <div class="d-attach-list">
+                      <button type="button" class="d-attach-upload" @click="toast('上传附件')">
+                        <span class="up-icon"><van-icon name="plus" /></span>
+                        <span class="up-text">
+                          <span class="up-title">添加附件</span>
+                          <span class="up-sub">支持 PDF / 图片 / Office,可多选上传</span>
+                        </span>
+                        <van-icon name="arrow" color="#0a9f6d" />
+                      </button>
+
+                      <div
+                        v-for="a in detail.attachments"
+                        :key="a.name"
+                        class="d-attach-card"
+                        :class="fileTypeClass(a.name)"
+                      >
+                        <div class="file-icon">
+                          <van-icon :name="fileTypeIcon(a.name)" />
+                          <span class="ext">{{ fileExt(a.name) }}</span>
+                        </div>
+                        <div class="file-body">
+                          <div class="file-name">{{ a.name }}</div>
+                          <div class="file-meta">
+                            <span>{{ a.size || '—' }}</span>
+                            <span>{{ a.time || '—' }}</span>
+                          </div>
+                        </div>
+                        <div class="file-ops">
+                          <button type="button" class="btn-preview" title="预览" @click="toast('预览 ' + a.name)">
+                            <van-icon name="eye-o" />
+                          </button>
+                          <button type="button" class="btn-download" title="下载" @click="toast('下载 ' + a.name)">
+                            <svg class="icon-download" viewBox="0 0 24 24" aria-hidden="true">
+                              <path fill="currentColor" d="M12 3a1 1 0 0 1 1 1v8.59l2.3-2.3a1 1 0 1 1 1.4 1.42l-4 4a1 1 0 0 1-1.4 0l-4-4a1 1 0 1 1 1.4-1.42L11 12.59V4a1 1 0 0 1 1-1Z"/>
+                              <path fill="currentColor" d="M5 15a1 1 0 0 1 1 1v2h12v-2a1 1 0 1 1 2 0v2a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2v-2a1 1 0 0 1 1-1Z"/>
+                            </svg>
+                          </button>
+                        </div>
+                      </div>
+                    </div>
+                  </section>
+
+                  <section class="detail-section">
+                    <div class="sec-hd">
+                      <h3>流转记录</h3>
+                      <span style="font-size:11px;color:#8aa198;">{{ detail.logs.length }} 步</span>
+                    </div>
+                    <div class="flow-list">
+                      <div
+                        v-for="(log, idx) in detail.logs"
+                        :key="idx"
+                        class="flow-item"
+                        :class="log.tone"
+                      >
+                        <div class="flow-rail">
+                          <div class="flow-dot">
+                            <van-icon :name="log.icon || 'checked'" />
+                          </div>
+                        </div>
+                        <div class="flow-card">
+                          <div class="flow-top">
+                            <div class="flow-title">{{ log.title }}</div>
+                            <span class="flow-badge">{{ log.badge || (log.tone === 'warn' ? '进行中' : '已完成') }}</span>
+                          </div>
+                          <div class="flow-desc">{{ log.desc }}</div>
+                          <div class="flow-meta">
+                            <span class="chip" v-if="log.actor">
+                              <van-icon name="manager-o" />{{ log.actor }}
+                            </span>
+                            <span class="chip">
+                              <van-icon name="clock-o" />{{ log.time }}
+                            </span>
+                          </div>
+                        </div>
+                      </div>
+                    </div>
+                  </section>
+                </div>
+              </div>
+
+              <div class="detail-footer">
+                <van-button plain hairline type="default" @click="screen = 'list'">返回</van-button>
+                <van-button type="primary" @click="openForm('edit', detail)">编辑处理</van-button>
+              </div>
+            </div>
+          </template>
+
+          <!-- ===== 新增 / 编辑 ===== -->
+          <template v-else-if="screen === 'form'">
+            <div class="page-nav">
+              <van-nav-bar
+                :title="formMode === 'create' ? '新增业务' : '编辑业务'"
+                left-arrow
+                @click-left="screen = 'list'"
+              />
+            </div>
+            <div class="screen-scroll form-page">
+              <div class="form-section-title">基础信息</div>
+              <van-form @submit="onSubmit">
+                <van-cell-group inset>
+                  <van-field
+                    v-model="form.name"
+                    name="name"
+                    label="名称"
+                    placeholder="请输入名称"
+                    :rules="[{ required: true, message: '请填写名称' }]"
+                  />
+                  <van-field
+                    v-model="form.code"
+                    name="code"
+                    label="业务单号"
+                    placeholder="系统自动生成或手工录入"
+                  />
+                  <van-field
+                    v-model="form.typeLabel"
+                    is-link
+                    readonly
+                    name="type"
+                    label="类型"
+                    placeholder="请选择类型"
+                    @click="showTypePicker = true"
+                  />
+                  <van-field name="urgent" label="加急">
+                    <template #input>
+                      <van-switch v-model="form.urgent" size="20px" active-color="#0a9f6d" />
+                    </template>
+                  </van-field>
+                </van-cell-group>
+
+                <div class="form-section-title">业务详情</div>
+                <van-cell-group inset>
+                  <van-field
+                    v-model="form.owner"
+                    name="owner"
+                    label="负责人"
+                    placeholder="请输入负责人"
+                  />
+                  <van-field
+                    v-model="form.date"
+                    is-link
+                    readonly
+                    name="date"
+                    label="业务日期"
+                    placeholder="选择日期"
+                    @click="showDate = true"
+                  />
+                  <van-field
+                    v-model="form.amount"
+                    type="number"
+                    name="amount"
+                    label="金额"
+                    placeholder="0.00"
+                  >
+                    <template #right-icon>元</template>
+                  </van-field>
+                  <van-field
+                    v-model="form.remark"
+                    rows="3"
+                    autosize
+                    type="textarea"
+                    maxlength="200"
+                    name="remark"
+                    label="备注"
+                    placeholder="选填,最多 200 字"
+                    show-word-limit
+                  />
+                </van-cell-group>
+
+                <div class="form-section-title">附件</div>
+                <van-cell-group inset>
+                  <van-cell title="协议附件" label="支持多附件,逗号分隔存储">
+                    <template #value>
+                      <div style="display:flex;gap:8px;justify-content:flex-end;">
+                        <div class="upload-card">
+                          <van-icon name="plus" size="20" />
+                          <span>上传</span>
+                        </div>
+                      </div>
+                    </template>
+                  </van-cell>
+                  <van-cell>
+                    <div style="display:flex;gap:8px;flex-wrap:wrap;">
+                      <div class="attach-card">
+                        <div class="name">协议扫描件.pdf</div>
+                        <div class="ops">
+                          <span>预览</span>
+                          <span>删除</span>
+                        </div>
+                      </div>
+                    </div>
+                  </van-cell>
+                </van-cell-group>
+
+                <p class="form-tip">表单:label 在左、控件在右(Vant Field 默认);必填项校验失败时 Toast / 字段红字提示。底栏固定「取消 / 保存」。</p>
+
+                <div class="form-footer">
+                  <van-button plain hairline type="default" @click="screen = 'list'">取消</van-button>
+                  <van-button round block type="primary" native-type="submit">保存</van-button>
+                </div>
+              </van-form>
+            </div>
+
+            <van-popup v-model:show="showTypePicker" position="bottom" round>
+              <van-picker
+                :columns="typeColumns"
+                @confirm="onTypeConfirm"
+                @cancel="showTypePicker = false"
+              />
+            </van-popup>
+
+            <van-calendar
+              v-model:show="showDate"
+              @confirm="onDateConfirm"
+              color="#0a9f6d"
+            />
+          </template>
+
+          <!-- ===== 组件风格 ===== -->
+          <template v-else>
+            <div class="comps-page">
+              <div class="screen-scroll">
+                <header class="comps-hero">
+                  <van-nav-bar left-arrow @click-left="screen = 'home'">
+                    <template #title>
+                      <span style="font-weight:600;">组件风格</span>
+                    </template>
+                  </van-nav-bar>
+                  <div class="comps-hero-copy">
+                    <div class="eyebrow">DESIGN SYSTEM</div>
+                    <h2>组件合集</h2>
+                    <div class="subline">按钮 · 状态 · 反馈 · 步骤 · 附件等基础样式规范</div>
+                  </div>
+                </header>
+
+                <div class="comp-section">
+                  <section class="comp-block">
+                    <div class="comp-hd">
+                      <div class="hd-icon"><van-icon name="font-o" /></div>
+                      <div>
+                        <h4>按钮 Button</h4>
+                        <div class="hd-sub">每组仅一个实心主色行动</div>
+                      </div>
+                    </div>
+                    <div class="comp-bd">
+                      <div class="comp-row">
+                        <span class="label-muted">常用尺寸</span>
+                        <van-button type="primary" size="small" round>主按钮</van-button>
+                        <van-button type="primary" size="small" plain hairline round>描边主色</van-button>
+                        <van-button type="default" size="small" plain hairline round>次要</van-button>
+                        <van-button type="danger" size="small" plain hairline round>危险</van-button>
+                      </div>
+                      <div class="comp-row">
+                        <van-button type="primary" block round style="height:44px;">通栏主按钮(44px)</van-button>
+                      </div>
+                    </div>
+                  </section>
+
+                  <section class="comp-block status-demo">
+                    <div class="comp-hd">
+                      <div class="hd-icon tone-amber"><van-icon name="label-o" /></div>
+                      <div>
+                        <h4>状态 Tag</h4>
+                        <div class="hd-sub">语义色:待处理 / 进行中 / 完成 / 异常</div>
+                      </div>
+                    </div>
+                    <div class="comp-bd">
+                      <div class="tag-grid">
+                        <span class="demo-tag t-warn"><i></i>待处理</span>
+                        <span class="demo-tag t-primary"><i></i>进行中</span>
+                        <span class="demo-tag t-success"><i></i>已完成</span>
+                        <span class="demo-tag t-danger"><i></i>异常</span>
+                        <span class="demo-tag t-muted"><i></i>已作废</span>
+                      </div>
+                      <div class="comp-row" style="margin-top:12px;">
+                        <span class="label-muted">Vant Tag 兼容</span>
+                        <van-tag type="warning" plain round>待处理</van-tag>
+                        <van-tag type="primary" round>进行中</van-tag>
+                        <van-tag type="success" plain round>已完成</van-tag>
+                        <van-tag type="danger" plain round>异常</van-tag>
+                      </div>
+                    </div>
+                  </section>
+
+                  <section class="comp-block">
+                    <div class="comp-hd">
+                      <div class="hd-icon tone-slate"><van-icon name="orders-o" /></div>
+                      <div>
+                        <h4>单元格 Cell</h4>
+                        <div class="hd-sub">分组列表与跳转行</div>
+                      </div>
+                    </div>
+                    <div class="comp-bd">
+                      <div class="comp-cell-list">
+                        <button type="button" class="comp-cell-item" @click="toast('查看内容')">
+                          <span class="ci-icon"><van-icon name="orders-o" /></span>
+                          <span class="ci-body">
+                            <span class="ci-title">标题</span>
+                            <span class="ci-label">基础信息展示</span>
+                          </span>
+                          <span class="ci-right"><span class="val">内容</span></span>
+                        </button>
+                        <button type="button" class="comp-cell-item" @click="toast('跳转详情')">
+                          <span class="ci-icon tone-amber"><van-icon name="guide-o" /></span>
+                          <span class="ci-body">
+                            <span class="ci-title">可跳转</span>
+                            <span class="ci-label">点击进入下一页</span>
+                          </span>
+                          <span class="ci-right">
+                            <span class="val">详情</span>
+                            <van-icon name="arrow" />
+                          </span>
+                        </button>
+                        <button type="button" class="comp-cell-item" @click="toast('状态:启用')">
+                          <span class="ci-icon tone-slate"><van-icon name="manager-o" /></span>
+                          <span class="ci-body">
+                            <span class="ci-title">带标签</span>
+                            <span class="ci-label">辅助说明文字</span>
+                          </span>
+                          <span class="ci-right">
+                            <span class="demo-tag t-success" style="padding:3px 8px;font-size:11px;"><i></i>启用</span>
+                          </span>
+                        </button>
+                      </div>
+                    </div>
+                  </section>
+
+                  <section class="comp-block">
+                    <div class="comp-hd">
+                      <div class="hd-icon"><van-icon name="volume-o" /></div>
+                      <div>
+                        <h4>通知 / 空态 / 加载</h4>
+                        <div class="hd-sub">提示条与占位反馈</div>
+                      </div>
+                    </div>
+                    <div class="comp-bd">
+                      <div class="comp-notice-stack">
+                        <div class="comp-notice-bar">
+                          <van-notice-bar
+                            left-icon="volume-o"
+                            text="移动端菜单在「菜单管理 → 移动端菜单」下配置,按角色授权后展示。"
+                            background="#e9f9f2"
+                            color="#087f57"
+                          />
+                        </div>
+                        <div class="comp-empty-box">
+                          <van-empty description="暂无相关数据" image-size="110" />
+                          <div class="empty-tip">可下拉刷新,或点击上方按钮新增一条记录</div>
+                        </div>
+                        <div class="comp-loading-box">
+                          <van-loading type="spinner" color="#0a9f6d" size="28px" />
+                          <div>内容加载中…</div>
+                          <div class="load-sub">正在拉取最新业务数据</div>
+                        </div>
+                      </div>
+                    </div>
+                  </section>
+
+                  <section class="comp-block">
+                    <div class="comp-hd">
+                      <div class="hd-icon tone-amber"><van-icon name="comment-o" /></div>
+                      <div>
+                        <h4>反馈 Feedback</h4>
+                        <div class="hd-sub">Toast / Dialog / ActionSheet</div>
+                      </div>
+                    </div>
+                    <div class="comp-bd">
+                      <div class="comp-feedback-grid">
+                        <button type="button" class="fb-btn fb-toast" @click="toast('操作成功')">
+                          <span class="fb-icon"><van-icon name="comment-o" /></span>
+                          Toast
+                        </button>
+                        <button type="button" class="fb-btn fb-dialog" @click="showDialog">
+                          <span class="fb-icon"><van-icon name="info-o" /></span>
+                          Dialog
+                        </button>
+                        <button type="button" class="fb-btn fb-sheet" @click="showAction = true">
+                          <span class="fb-icon"><van-icon name="wap-nav" /></span>
+                          Sheet
+                        </button>
+                      </div>
+                    </div>
+                  </section>
+
+                  <section class="comp-block">
+                    <div class="comp-hd">
+                      <div class="hd-icon"><van-icon name="cluster-o" /></div>
+                      <div>
+                        <h4>分段 / 步骤</h4>
+                        <div class="hd-sub">筛选分段与流程进度</div>
+                      </div>
+                    </div>
+                    <div class="comp-bd">
+                      <div class="comp-seg">
+                        <button type="button" :class="{ active: compTab === 0 }" @click="compTab = 0">资料</button>
+                        <button type="button" :class="{ active: compTab === 1 }" @click="compTab = 1">进度</button>
+                        <button type="button" :class="{ active: compTab === 2 }" @click="compTab = 2">附件</button>
+                      </div>
+                      <div class="comp-steps-wrap">
+                        <van-steps :active="compTab === 2 ? 2 : 1" active-color="#0a9f6d" inactive-color="#dcebe4">
+                          <van-step>提交</van-step>
+                          <van-step>审批中</van-step>
+                          <van-step>完成</van-step>
+                        </van-steps>
+                      </div>
+                    </div>
+                  </section>
+
+                  <section class="comp-block">
+                    <div class="comp-hd">
+                      <div class="hd-icon tone-rose"><van-icon name="description" /></div>
+                      <div>
+                        <h4>附件卡片</h4>
+                        <div class="hd-sub">上传入口与文件摘要卡</div>
+                      </div>
+                    </div>
+                    <div class="comp-bd">
+                      <div class="comp-attach-list">
+                        <button type="button" class="comp-upload-tile" @click="toast('上传附件')">
+                          <span class="up-icon"><van-icon name="plus" /></span>
+                          <span class="up-text">
+                            <span class="up-title">添加附件</span>
+                            <span class="up-sub">支持 PDF / 图片 / Office,点按即可上传</span>
+                          </span>
+                          <van-icon name="arrow" color="#0a9f6d" />
+                        </button>
+                        <div class="comp-file-card">
+                          <div class="file-icon">
+                            <van-icon name="records" />
+                            <span class="ext">DOCX</span>
+                          </div>
+                          <div class="file-body">
+                            <div class="file-name">质检报告.docx</div>
+                            <div class="file-meta">
+                              <span>1.2 MB</span>
+                              <span>08-10 10:20</span>
+                            </div>
+                          </div>
+                          <div class="file-ops">
+                            <button type="button" class="btn-preview" @click="toast('预览')">
+                              <van-icon name="eye-o" />
+                            </button>
+                            <button type="button" class="btn-del" @click="toast('删除')">
+                              <van-icon name="delete-o" />
+                            </button>
+                          </div>
+                        </div>
+                      </div>
+                    </div>
+                  </section>
+                </div>
+              </div>
+            </div>
+          </template>
+
+          <van-action-sheet
+            v-model:show="showAction"
+            :actions="actions"
+            cancel-text="取消"
+            close-on-click-action
+            @select="onActionSelect"
+          />
+
+        </div>
+      </div>
+    </div>
+  </div>
+
+  <script src="https://cdn.jsdelivr.net/npm/vue@3.5.13/dist/vue.global.prod.js"></script>
+  <script src="https://cdn.jsdelivr.net/npm/vant@4.9.21/lib/vant.min.js"></script>
+  <script>
+    const { createApp, computed, reactive, ref } = Vue
+
+    createApp({
+      setup() {
+        const screen = ref('home')
+        const screens = [
+          { key: 'home', label: '1. 首页 · 权限菜单卡片' },
+          { key: 'list', label: '2. 列表界面' },
+          { key: 'detail', label: '3. 详情界面' },
+          { key: 'form', label: '4. 新增 / 编辑表单' },
+          { key: 'comps', label: '5. 组件风格合集' }
+        ]
+
+        const userName = '张三'
+        const roleName = '业务专员'
+        const deptName = '样本运营部'
+
+        const allMenus = [
+          { id: 'm1', title: '待办审批', icon: 'todo-list-o', tone: '', path: 'list' },
+          { id: 'm2', title: '采集协议', icon: 'orders-o', tone: '', path: 'list' },
+          { id: 'm3', title: '缴费确认', icon: 'balance-pay', tone: 'tone-yellow', path: 'list' },
+          { id: 'm4', title: '样本接收', icon: 'logistics', tone: '', path: 'list' },
+          { id: 'm5', title: '质检工单', icon: 'certificate', tone: 'tone-slate', path: 'list' },
+          { id: 'm6', title: '生产任务', icon: 'cluster-o', tone: '', path: 'list' },
+          { id: 'm7', title: '库位出库', icon: 'send-gift-o', tone: 'tone-yellow', path: 'list' },
+          { id: 'm8', title: '我的', icon: 'user-o', tone: 'tone-slate', path: 'comps' }
+        ]
+
+        const grantedMap = reactive({
+          m1: true, m2: true, m3: true, m4: true,
+          m5: true, m6: false, m7: true, m8: true
+        })
+
+        const visibleMenus = computed(() =>
+          allMenus.filter((m) => grantedMap[m.id])
+        )
+
+        const todoStats = { approve: 3, pay: 1, alert: 1 }
+        const todoItems = [
+          {
+            id: 't1',
+            title: '采集协议审批',
+            desc: '王女士 · 分期首期待复核',
+            tag: '待审批',
+            tagType: 'warning',
+            time: '09:20',
+            icon: 'todo-list-o',
+            iconClass: 'i-approve'
+          },
+          {
+            id: 't2',
+            title: '缴费确认',
+            desc: 'XY20260810001 · ¥12,800',
+            tag: '待确认',
+            tagType: 'warning',
+            time: '昨天',
+            icon: 'balance-pay',
+            iconClass: 'i-pay'
+          },
+          {
+            id: 't3',
+            title: '质检异常',
+            desc: 'QC-8821 · 结论待录入',
+            tag: '异常',
+            tagType: 'danger',
+            time: '08-08',
+            icon: 'warning-o',
+            iconClass: 'i-alert'
+          }
+        ]
+
+        const keyword = ref('')
+        const listTab = ref(0)
+        const listTabs = [
+          { key: 'all', label: '全部' },
+          { key: 'doing', label: '进行中' },
+          { key: 'pending', label: '待处理' },
+          { key: 'done', label: '已完成' }
+        ]
+        const listData = [
+          {
+            id: 1, title: '脐带血采集协议-王女士', code: 'XY20260810001',
+            typeLabel: '采集协议', icon: 'orders-o',
+            owner: '李四', time: '08-10 09:20', amount: '12,800', progress: '',
+            desc: '分期缴费 · 待财务确认首期款项',
+            status: '待处理', tagType: 'warning', hint: '需尽快确认',
+            customer: '王女士', dept: '样本运营部', createdAt: '2026-08-08 14:20',
+            stepActive: 1,
+            steps: ['建档', '缴费确认', '样本采集', '入库'],
+            payments: [
+              { id: 'p1', name: '第 1 期(首期)', sub: '应缴 08-10 · 微信转账', amount: '6,400', status: '待确认', tagType: 'warning' },
+              { id: 'p2', name: '第 2 期', sub: '应缴 09-10 · 未到期', amount: '6,400', status: '待缴费', tagType: 'primary' }
+            ],
+            attachments: [
+              { name: '协议扫描件.pdf', size: '2.4 MB', time: '08-08 14:18' },
+              { name: '身份证明.jpg', size: '860 KB', time: '08-08 14:19' }
+            ],
+            logs: [
+              { title: '提交审批', desc: '提交采集协议,等待财务确认首期', time: '08-08 14:22', tone: 'done', actor: '李四', icon: 'edit', badge: '已完成' },
+              { title: '流转至财务', desc: '系统自动分配至财务确认岗', time: '08-08 14:23', tone: 'done', actor: '系统', icon: 'cluster-o', badge: '已完成' },
+              { title: '待确认缴费', desc: '首期 ¥6,400 等待财务核对附件', time: '08-10 09:20', tone: 'warn', actor: '财务岗', icon: 'balance-pay', badge: '进行中' }
+            ]
+          },
+          {
+            id: 2, title: '样本接收单-批次 A12', code: 'YS20260809018',
+            typeLabel: '样本接收', icon: 'logistics',
+            owner: '王五', time: '08-09 16:40', amount: '', progress: '在途',
+            desc: '冷链在途 · 预计今日 18:00 前到达库房',
+            status: '进行中', tagType: 'primary', hint: '跟踪物流',
+            customer: '批次 A12', dept: '仓储物流部', createdAt: '2026-08-09 09:10',
+            stepActive: 2,
+            steps: ['发运', '在途', '到库签收', '入库'],
+            payments: [],
+            attachments: [{ name: '冷链温度记录.pdf', size: '1.1 MB', time: '08-09 11:28' }],
+            logs: [
+              { title: '创建接收单', desc: '创建样本接收单并绑定批次 A12', time: '08-09 09:10', tone: 'done', actor: '王五', icon: 'orders-o', badge: '已完成' },
+              { title: '冷链发出', desc: '承运方已取件,全程温控开启', time: '08-09 11:30', tone: 'done', actor: '承运方', icon: 'logistics', badge: '已完成' },
+              { title: '运输中', desc: '当前位置:城东中转站', time: '08-09 16:40', tone: 'warn', actor: '物流跟踪', icon: 'location-o', badge: '进行中' }
+            ]
+          },
+          {
+            id: 3, title: '质检工单-QC-8821', code: 'ZJ20260808007',
+            typeLabel: '质检工单', icon: 'certificate',
+            owner: '赵六', time: '08-08 11:05', amount: '', progress: '阻塞',
+            desc: '检测结论待录入,阻塞下游入库流程',
+            status: '异常', tagType: 'danger', hint: '优先处理',
+            customer: '样本 S-8821', dept: '质检中心', createdAt: '2026-08-07 16:40',
+            stepActive: 1,
+            steps: ['取样', '检测', '结论录入', '关闭'],
+            payments: [],
+            attachments: [{ name: '初检原始数据.xlsx', size: '428 KB', time: '08-08 10:48' }],
+            logs: [
+              { title: '工单创建', desc: '系统根据接收单自动生成质检工单', time: '08-07 16:40', tone: 'done', actor: '系统', icon: 'add-o', badge: '已完成' },
+              { title: '检测完成', desc: '完成实验操作,待录入结论', time: '08-08 10:50', tone: 'done', actor: '赵六', icon: 'certificate', badge: '已完成' },
+              { title: '异常挂起', desc: '结论超时未录入,已阻塞入库', time: '08-08 11:05', tone: 'danger', actor: '质控规则', icon: 'warning-o', badge: '异常' }
+            ]
+          },
+          {
+            id: 4, title: '出库申请-库位 B-03', code: 'CK20260807002',
+            typeLabel: '库位出库', icon: 'send-gift-o',
+            owner: '张三', time: '08-07 14:22', amount: '', progress: '完结',
+            desc: '复核已完成,可归档关闭',
+            status: '已完成', tagType: 'success', hint: '已完结',
+            customer: '库位 B-03', dept: '仓储物流部', createdAt: '2026-08-06 10:00',
+            stepActive: 3,
+            steps: ['申请', '复核', '出库', '归档'],
+            payments: [],
+            attachments: [{ name: '出库单签收.pdf', size: '980 KB', time: '08-07 14:18' }],
+            logs: [
+              { title: '提交申请', desc: '发起出库申请,指定库位 B-03', time: '08-06 10:00', tone: 'done', actor: '张三', icon: 'edit', badge: '已完成' },
+              { title: '复核通过', desc: '库管复核通过,允许出库', time: '08-07 11:10', tone: 'done', actor: '库管', icon: 'passed', badge: '已完成' },
+              { title: '出库完成', desc: '实物已交接,单据归档', time: '08-07 14:22', tone: 'done', actor: '张三', icon: 'checked', badge: '已完成' }
+            ]
+          }
+        ]
+
+        const detail = ref(null)
+
+        function openDetail(item) {
+          detail.value = item || listData[0]
+          screen.value = 'detail'
+        }
+
+        function goScreen(key) {
+          if (key === 'detail' && !detail.value) detail.value = listData[0]
+          screen.value = key
+        }
+
+        function fileExt(name) {
+          const i = (name || '').lastIndexOf('.')
+          return i >= 0 ? name.slice(i + 1).slice(0, 4) : 'FILE'
+        }
+
+        function fileTypeClass(name) {
+          const ext = fileExt(name).toLowerCase()
+          if (ext === 'pdf') return 'ft-pdf'
+          if (['jpg', 'jpeg', 'png', 'gif', 'webp'].includes(ext)) return 'ft-img'
+          if (['xls', 'xlsx', 'csv'].includes(ext)) return 'ft-xls'
+          if (['doc', 'docx'].includes(ext)) return 'ft-doc'
+          return 'ft-other'
+        }
+
+        function fileTypeIcon(name) {
+          const cls = fileTypeClass(name)
+          if (cls === 'ft-pdf') return 'description'
+          if (cls === 'ft-img') return 'photo-o'
+          if (cls === 'ft-xls') return 'bar-chart-o'
+          if (cls === 'ft-doc') return 'records'
+          return 'bag-o'
+        }
+
+        function rowsByTab(idx) {
+          if (idx === 1) return listData.filter((r) => r.status === '进行中')
+          if (idx === 2) return listData.filter((r) => r.status === '待处理')
+          if (idx === 3) return listData.filter((r) => r.status === '已完成')
+          return listData
+        }
+
+        function tabCount(idx) {
+          return rowsByTab(idx).length
+        }
+
+        const filteredList = computed(() => {
+          const kw = keyword.value.trim()
+          let rows = rowsByTab(listTab.value)
+          if (!kw) return rows
+          return rows.filter((r) =>
+            r.title.includes(kw) || r.code.includes(kw) || r.owner.includes(kw)
+          )
+        })
+
+        function goListTab(idx) {
+          listTab.value = idx
+          screen.value = 'list'
+        }
+
+        const formMode = ref('create')
+        const form = reactive({
+          name: '',
+          code: '',
+          typeLabel: '',
+          urgent: false,
+          owner: '',
+          date: '',
+          amount: '',
+          remark: ''
+        })
+        const showTypePicker = ref(false)
+        const showDate = ref(false)
+        const typeColumns = [
+          { text: '采集协议', value: 'protocol' },
+          { text: '样本接收', value: 'sample' },
+          { text: '质检工单', value: 'qc' },
+          { text: '出库申请', value: 'outbound' }
+        ]
+
+        function resetForm() {
+          form.name = ''
+          form.code = ''
+          form.typeLabel = ''
+          form.urgent = false
+          form.owner = userName
+          form.date = ''
+          form.amount = ''
+          form.remark = ''
+        }
+
+        function openForm(mode, item) {
+          formMode.value = mode
+          if (mode === 'edit' && item) {
+            form.name = item.title
+            form.code = item.code
+            form.typeLabel = '采集协议'
+            form.urgent = item.tagType === 'danger'
+            form.owner = item.owner
+            form.date = '2026-08-10'
+            form.amount = '12800'
+            form.remark = item.desc
+          } else {
+            resetForm()
+          }
+          screen.value = 'form'
+        }
+
+        function onTypeConfirm({ selectedOptions }) {
+          form.typeLabel = selectedOptions[0]?.text || ''
+          showTypePicker.value = false
+        }
+
+        function onDateConfirm(val) {
+          const d = val
+          const y = d.getFullYear()
+          const m = String(d.getMonth() + 1).padStart(2, '0')
+          const day = String(d.getDate()).padStart(2, '0')
+          form.date = `${y}-${m}-${day}`
+          showDate.value = false
+        }
+
+        function onSubmit() {
+          vant.showToast({ message: formMode.value === 'create' ? '已保存(新增)' : '已保存(编辑)', type: 'success' })
+          screen.value = 'list'
+        }
+
+        function onMenuTap(m) {
+          if (m.path === 'list') screen.value = 'list'
+          else if (m.path === 'comps') screen.value = 'comps'
+          else vant.showToast(m.title)
+        }
+
+        function toast(msg) {
+          vant.showToast(msg)
+        }
+
+        function showDialog() {
+          vant.showConfirmDialog({
+            title: '确认提交',
+            message: '提交后将进入审批流程,是否继续?',
+            confirmButtonText: '确认',
+            confirmButtonColor: '#0a9f6d'
+          }).then(() => {
+            vant.showToast('已确认')
+          }).catch(() => {})
+        }
+
+        const showAction = ref(false)
+        const actions = [
+          { name: '查看', color: '#1f2a26' },
+          { name: '编辑', color: '#0a9f6d' },
+          { name: '作废', color: '#d14343' }
+        ]
+        const compTab = ref(0)
+
+        function onActionSelect(action) {
+          vant.showToast(action.name)
+        }
+
+        return {
+          screen, screens, goScreen, userName, roleName, deptName,
+          allMenus, grantedMap, visibleMenus,
+          todoStats, todoItems, goListTab,
+          keyword, listTab, listTabs, tabCount, filteredList,
+          detail, openDetail, fileExt, fileTypeClass, fileTypeIcon,
+          formMode, form, showTypePicker, showDate, typeColumns,
+          openForm, onTypeConfirm, onDateConfirm, onSubmit, onMenuTap,
+          toast, showDialog, showAction, actions, onActionSelect, compTab
+        }
+      }
+    }).use(vant).mount('#app')
+  </script>
+</body>
+</html>