diff --git a/docs/superpowers/plans/2026-09-15-mp-webview-pl-plan-mobile-redesign.md b/docs/superpowers/plans/2026-09-15-mp-webview-pl-plan-mobile-redesign.md
new file mode 100644
index 000000000..87e51cf1d
--- /dev/null
+++ b/docs/superpowers/plans/2026-09-15-mp-webview-pl-plan-mobile-redesign.md
@@ -0,0 +1,810 @@
+# 小程序 WebView 运输计划移动端重设计 Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** 按已验收 Demo 重写 `web-admin` `/#/m/pl/plan*` 的移动端 UI/交互,使列表→详情→表单(含安排)→打印→三方人车达到手机可用,且业务规则与桌面一致。
+
+**Architecture:** 不改小程序壳与 ticket。在现有 `views/m/pl/plan/*` 上按屏替换 UI;抽出可单测的卡片/底栏辅助函数;新增通用 Bottom Sheet;列表/详情原生重写;表单继续复用 `PlPlanFormDialog`(`variant=page`)并增强锚点与窄屏布局;打印/三方人车对齐 Demo 交互细节。权威对照:`bak/mobile-ui/shanxia-mp-pl-plan-mobile-redesign-demo.html`。
+
+**Tech Stack:** Vue 3 + TypeScript、Element Plus(仅必要时)、`mp-webview.scss` 三墙 Token、`planApi` / `pl-plan-action-guards` / `usePlToxicProductCertCheck` / `PlPlanFormDialog` / `PlPasteRecognizeDialog`。
+
+**Spec:** `docs/superpowers/specs/2026-09-15-mp-webview-pl-plan-mobile-redesign-design.md`
+
+---
+
+## 文件结构
+
+| 路径 | 职责 |
+|------|------|
+| `web-admin/src/styles/mp-webview.scss` | 扩展:双下拉筛选、Speed Dial、贴底 fab、Sheet、详情分区、产品行表 |
+| `web-admin/src/components/mp/MpBottomSheet.vue` | 通用底部抽屉(遮罩无 blur、可选搜索、关闭) |
+| `web-admin/src/utils/mp-pl-plan-card.ts` | 卡片字段:供方→需方、跟进人等 |
+| `web-admin/src/utils/mp-pl-plan-detail-actions.ts` | 详情底栏:主操作最多 2 个 + 更多列表 |
+| `web-admin/src/views/m/pl/plan/index.vue` | 列表重写 |
+| `web-admin/src/views/m/pl/plan/detail.vue` | 详情重写(分区 + 危化证 + 底栏) |
+| `web-admin/src/components/business/pl-plan-form-dialog/index.vue` | `variant=page`:锚点条、分区滚动、贴底 footer 样式 |
+| `web-admin/src/views/m/pl/plan/form.vue` | 路由壳保持;确认 arrange/copy query |
+| `web-admin/src/views/m/pl/plan/print.vue` | 送货客户用 Sheet/ picker 风格;空白单 Chip |
+| `web-admin/src/views/m/pl/plan/paste.vue` | 保持 page 变体;核对窄屏 footer |
+| `web-admin/src/utils/mp-pl-plan-card.spec.ts`(或 vitest 同名) | 卡片字段单测 |
+| `web-admin/src/utils/mp-pl-plan-detail-actions.spec.ts` | 底栏拆分单测 |
+
+**不改(除非发现缺口):** `MpWebviewShell.vue`、ticket API、小程序 `mp-webview`、后端计划业务规则。
+
+**对照顺序:** 每完成一屏,用浏览器窄屏(375)打开 `/#/m/pl/...`,并对照 Demo 同名屏。
+
+---
+
+### Task 1: 扩展 `mp-webview.scss` + `MpBottomSheet`
+
+**Files:**
+- Modify: `web-admin/src/styles/mp-webview.scss`
+- Create: `web-admin/src/components/mp/MpBottomSheet.vue`
+
+- [ ] **Step 1: 在 `mp-webview.scss` 末尾追加 Demo 对齐样式块**
+
+追加(保持现有 class,勿改坏已有页):
+
+```scss
+/* === redesign 2026-09-15 === */
+.mp-wv-filter-bar {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 0;
+ background: #fff;
+ border-bottom: 1px solid #dcebe4;
+ position: sticky;
+ top: 0;
+ z-index: 20;
+}
+.mp-wv-filter-trig {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 4px;
+ height: 40px;
+ font-size: 13px;
+ color: #1f2a26;
+ background: #fff;
+ border: none;
+ border-right: 1px solid #dcebe4;
+ cursor: pointer;
+}
+.mp-wv-filter-trig:last-child { border-right: none; }
+.mp-wv-filter-trig.open { color: #0a9f6d; font-weight: 600; }
+.mp-wv-filter-panel {
+ display: none;
+ position: absolute;
+ left: 0;
+ right: 0;
+ background: #fff;
+ border-bottom: 1px solid #dcebe4;
+ max-height: 280px;
+ overflow: auto;
+ z-index: 21;
+}
+.mp-wv-filter-panel.open { display: block; }
+.mp-wv-filter-opt {
+ display: flex;
+ justify-content: space-between;
+ width: 100%;
+ padding: 12px 16px;
+ font-size: 14px;
+ text-align: left;
+ background: #fff;
+ border: none;
+ border-bottom: 1px solid #eef4f1;
+ cursor: pointer;
+}
+.mp-wv-filter-opt.active { color: #0a9f6d; font-weight: 600; }
+.mp-wv-filter-mask {
+ display: none;
+ position: fixed;
+ inset: 0;
+ background: rgba(0, 0, 0, 0.35);
+ z-index: 19;
+}
+.mp-wv-filter-mask.open { display: block; }
+
+.mp-wv-speed-dial {
+ position: fixed;
+ right: 16px;
+ bottom: calc(16px + env(safe-area-inset-bottom, 0px));
+ z-index: 30;
+ display: flex;
+ flex-direction: column;
+ align-items: flex-end;
+ gap: 10px;
+}
+.mp-wv-speed-main {
+ width: 52px;
+ height: 52px;
+ border-radius: 50%;
+ border: none;
+ background: #0a9f6d;
+ color: #fff;
+ font-size: 28px;
+ line-height: 1;
+ box-shadow: 0 8px 20px rgba(10, 159, 109, 0.35);
+ cursor: pointer;
+}
+.mp-wv-speed-item {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 8px 12px;
+ border-radius: 999px;
+ border: 1px solid #dcebe4;
+ background: #fff;
+ font-size: 13px;
+ cursor: pointer;
+}
+.mp-wv-speed-item.primary {
+ background: #0a9f6d;
+ border-color: #0a9f6d;
+ color: #fff;
+}
+.mp-wv-speed-menu { display: none; flex-direction: column; gap: 8px; align-items: flex-end; }
+.mp-wv-speed-dial.open .mp-wv-speed-menu { display: flex; }
+.mp-wv-speed-mask {
+ display: none;
+ position: fixed;
+ inset: 0;
+ background: rgba(0, 0, 0, 0.25);
+ z-index: 29;
+}
+.mp-wv-speed-mask.open { display: block; }
+
+.mp-wv-fab-bar {
+ position: sticky;
+ bottom: 0;
+ display: flex;
+ gap: 8px;
+ padding: 10px 12px calc(10px + env(safe-area-inset-bottom, 0px));
+ background: #fff;
+ border-top: 1px solid #dcebe4;
+ z-index: 15;
+}
+.mp-wv-fab-bar .mp-wv-btn { flex: 1; }
+.mp-wv-fab-bar .mp-wv-btn-more { flex: 0 0 44px; }
+
+.mp-wv-section {
+ margin: 0 0 12px;
+ padding: 12px;
+ background: #fff;
+ border: 1px solid #dcebe4;
+ border-radius: 14px;
+}
+.mp-wv-section h3 {
+ margin: 0 0 10px;
+ font-size: 14px;
+ font-weight: 700;
+ color: #087f57;
+}
+.mp-wv-kv {
+ display: flex;
+ justify-content: space-between;
+ gap: 12px;
+ padding: 6px 0;
+ font-size: 13px;
+ border-bottom: 1px solid #eef4f1;
+}
+.mp-wv-kv span:first-child { color: #6b7b74; flex-shrink: 0; }
+.mp-wv-kv span:last-child { text-align: right; word-break: break-all; }
+
+.mp-wv-pad-speed { padding-bottom: 88px; }
+```
+
+- [ ] **Step 2: 创建 `MpBottomSheet.vue`**
+
+```vue
+
+
+
+
+
+
+
+
{{ title }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+- [ ] **Step 3: 浏览器确认样式文件无 SCSS 语法错误**
+
+```bash
+cd web-admin && pnpm exec vite build --mode development 2>&1 | tail -30
+```
+
+若全量 build 过慢,至少 `pnpm dev` 启动后打开任意 `/#/m/pl/plan` 看控制台无 SCSS 报错。
+
+- [ ] **Step 4: Commit**
+
+```bash
+git add web-admin/src/styles/mp-webview.scss web-admin/src/components/mp/MpBottomSheet.vue
+git commit -m "$(cat <<'EOF'
+feat(web-admin): add mp webview sheet and list chrome styles.
+
+Provide shared Bottom Sheet and filter/speed-dial/fab tokens for the transport plan mobile redesign.
+EOF
+)"
+```
+
+---
+
+### Task 2: 卡片字段辅助函数 + 单测
+
+**Files:**
+- Modify: `web-admin/src/utils/mp-pl-plan-card.ts`
+- Create: `web-admin/src/utils/mp-pl-plan-card.spec.ts`(若仓库无 vitest 前端单测惯例,则用 `node --experimental-vm-modules` 或跳过文件、在 Task 内用临时断言脚本;**优先**检查 `web-admin/package.json` 的 `test` 脚本,有则按项目方式跑)
+
+- [ ] **Step 1: 扩展 `buildPlanCard` 返回字段**
+
+```ts
+// web-admin/src/utils/mp-pl-plan-card.ts
+import type { Plan } from '@/api/pl'
+import {
+ formatPlVehiclePlateDisplay,
+ getPlPlanFollowStatusLabel,
+ getPlPlanFollowStatusTagType,
+ getPlPlanTypeLabel
+} from '@/utils/pl-display'
+
+function productSummary(plan: Plan): string {
+ const extra = (plan as Plan & { productSummary?: string }).productSummary
+ if (extra) return extra
+ const names = (plan.products || []).map((item) => item.productName).filter(Boolean)
+ if (names.length) return names.join(' · ')
+ const parties = [plan.supplierName, plan.customerName || plan.destinationName]
+ .filter(Boolean)
+ .join(' → ')
+ return parties || '—'
+}
+
+export function buildPlanCard(plan: Plan) {
+ const supplier = plan.supplierName || '—'
+ const buyer = plan.customerName || plan.destinationName || '—'
+ return {
+ id: plan.id,
+ planNo: plan.planNo || '—',
+ typeLabel: getPlPlanTypeLabel(plan.planType || ''),
+ planDate: plan.planDate || '—',
+ statusLabel: getPlPlanFollowStatusLabel(plan.followStatus),
+ statusTagType: getPlPlanFollowStatusTagType(plan.followStatus),
+ plate: formatPlVehiclePlateDisplay(plan.licensePlate, plan.trailerNo) || '—',
+ people: plan.personnelSummary || '—',
+ product: productSummary(plan),
+ supplier,
+ buyer,
+ partyLine: `${supplier} → ${buyer}`,
+ follower: plan.followUserName || '—'
+ }
+}
+```
+
+- [ ] **Step 2: 写单测(字段存在)**
+
+```ts
+// web-admin/src/utils/mp-pl-plan-card.spec.ts
+import { describe, expect, it } from 'vitest'
+import { buildPlanCard } from './mp-pl-plan-card'
+import type { Plan } from '@/api/pl'
+
+describe('buildPlanCard', () => {
+ it('exposes supplier buyer follower for list cards', () => {
+ const card = buildPlanCard({
+ id: '1',
+ supplierName: '安庆仓',
+ customerName: '合肥某化',
+ followUserName: '王调度',
+ planType: 'outbound',
+ followStatus: 'pending_arrange'
+ } as Plan)
+ expect(card.partyLine).toBe('安庆仓 → 合肥某化')
+ expect(card.follower).toBe('王调度')
+ })
+})
+```
+
+若项目无 vitest:改为在 `web-admin/scripts/check-mp-pl-plan-card.mjs` 用简单 assert,或本 Task 仅手工 `node` 导入检查;**不要空测**。先读 `package.json`:
+
+```bash
+cd web-admin && node -e "const p=require('./package.json'); console.log(p.scripts)"
+```
+
+有 `test`/`vitest` 则跑对应命令;没有则跳过 `.spec.ts`,在 Step 3 用临时脚本验证 `partyLine` 拼接逻辑(把纯函数抽到同文件 `export function formatPartyLine(s,b){...}` 再测)。
+
+- [ ] **Step 3: 跑测**
+
+```bash
+cd web-admin && pnpm exec vitest run src/utils/mp-pl-plan-card.spec.ts
+```
+
+Expected: PASS(或按项目实际测试命令)。
+
+- [ ] **Step 4: Commit**
+
+```bash
+git add web-admin/src/utils/mp-pl-plan-card.ts web-admin/src/utils/mp-pl-plan-card.spec.ts
+git commit -m "$(cat <<'EOF'
+feat(web-admin): enrich mp plan card with party and follower fields.
+
+List cards need supplier→buyer and follow user to match the approved mobile demo.
+EOF
+)"
+```
+
+---
+
+### Task 3: 详情底栏拆分辅助 + 单测
+
+**Files:**
+- Create: `web-admin/src/utils/mp-pl-plan-detail-actions.ts`
+- Create: `web-admin/src/utils/mp-pl-plan-detail-actions.spec.ts`
+
+- [ ] **Step 1: 实现拆分逻辑**
+
+```ts
+// web-admin/src/utils/mp-pl-plan-detail-actions.ts
+export type MpDetailActionKind =
+ | 'edit'
+ | 'arrange'
+ | 'copy'
+ | 'wait'
+ | 'cancel-wait'
+ | 'print'
+ | 'void'
+
+export type MpDetailAction = {
+ kind: MpDetailActionKind
+ label: string
+ danger?: boolean
+}
+
+/** 主栏最多 2 个:优先 edit、arrange;其余进 more */
+export function splitMpPlanDetailActions(input: {
+ canEdit: boolean
+ canArrange: boolean
+ arrangeLabel: string
+ canWait: boolean
+ canCancelWait: boolean
+ canVoid: boolean
+}): { primary: MpDetailAction[]; more: MpDetailAction[] } {
+ const primary: MpDetailAction[] = []
+ const more: MpDetailAction[] = []
+
+ if (input.canEdit) primary.push({ kind: 'edit', label: '编辑' })
+ if (input.canArrange) {
+ primary.push({ kind: 'arrange', label: input.arrangeLabel })
+ }
+
+ more.push({ kind: 'copy', label: '复制' })
+ if (input.canWait) more.push({ kind: 'wait', label: '等客户通知' })
+ if (input.canCancelWait) more.push({ kind: 'cancel-wait', label: '取消等通知' })
+ more.push({ kind: 'print', label: '打印单据' })
+ if (input.canVoid) more.push({ kind: 'void', label: '作废', danger: true })
+
+ while (primary.length > 2) more.unshift(primary.pop()!)
+ while (primary.length < 2 && more.length) {
+ primary.push(more.shift()!)
+ }
+ return { primary, more }
+}
+```
+
+- [ ] **Step 2: 单测**
+
+```ts
+import { describe, expect, it } from 'vitest'
+import { splitMpPlanDetailActions } from './mp-pl-plan-detail-actions'
+
+describe('splitMpPlanDetailActions', () => {
+ it('keeps edit and arrange as primary when both available', () => {
+ const { primary, more } = splitMpPlanDetailActions({
+ canEdit: true,
+ canArrange: true,
+ arrangeLabel: '安排运输',
+ canWait: true,
+ canCancelWait: false,
+ canVoid: true
+ })
+ expect(primary.map((a) => a.kind)).toEqual(['edit', 'arrange'])
+ expect(more.map((a) => a.kind)).toEqual(['copy', 'wait', 'print', 'void'])
+ })
+})
+```
+
+- [ ] **Step 3: 跑测并 Commit**
+
+```bash
+cd web-admin && pnpm exec vitest run src/utils/mp-pl-plan-detail-actions.spec.ts
+git add web-admin/src/utils/mp-pl-plan-detail-actions.ts web-admin/src/utils/mp-pl-plan-detail-actions.spec.ts
+git commit -m "$(cat <<'EOF'
+feat(web-admin): add mp plan detail action splitter.
+
+Cap the sticky footer at two primary actions and park the rest in a more sheet.
+EOF
+)"
+```
+
+---
+
+### Task 4: 重写列表页 `index.vue`
+
+**Files:**
+- Modify: `web-admin/src/views/m/pl/plan/index.vue`
+- 对照: Demo `#screen-list`;现有 list API 逻辑保留
+
+- [ ] **Step 1: 替换模板为双下拉 + 卡片字段 + Speed Dial**
+
+要点(完整替换 template,保留 `MpWebviewShell` / 无权限空态):
+
+1. 去掉横向 `mp-wv-chips` + `mp-wv-tabs` + 顶栏「新增」按钮 + 工具栏「三方人车」。
+2. Header 仅:← 工作台、标题、搜索。
+3. `mp-wv-filter-bar`:日期触发器 + Tab 触发器;面板选项来自 `dateOptions` 与 `PL_PLAN_LIST_TABS`。
+4. 日期增加 `custom`:面板内两个 `input type="date"` + 确定,写入 `dateRange`。
+5. 卡片展示 `partyLine`、`follower`;操作区:更多(`MpBottomSheet`:等通知/取消等通知/作废)、复制、安排(`canArrangeTransport` / `arrangeTransportLabel`)。
+6. Speed Dial:新增 → `/m/pl/plan/new`;三方人车 → `/m/pl/plan/paste`。
+7. 保留 `fetchPage` / `loadMore` / `planApi.list` / 403 处理。
+
+卡片更多操作需:
+
+```ts
+import {
+ canArrangeTransport,
+ arrangeTransportLabel,
+ canWaitNotice,
+ canCancelWaitNotice,
+ canVoidPlan
+} from '@/utils/pl-plan-action-guards'
+```
+
+作废/等通知调用与详情页相同的 `planApi` 方法(从现有 `detail.vue` 复制调用方式,勿发明新 API)。
+
+- [ ] **Step 2: 手动验收列表**
+
+```bash
+cd web-admin && pnpm dev
+```
+
+浏览器开 `/#/m/pl/plan`(需 ticket 或本地已登录 mp 调试方式,与现网一致)。核对:
+
+- Tab 顺序含「全部」且在草稿后
+- 自定义日期可筛
+- Speed Dial 两项可跳
+- 卡片有供方→需方、跟进人
+
+- [ ] **Step 3: Commit**
+
+```bash
+git add web-admin/src/views/m/pl/plan/index.vue
+git commit -m "$(cat <<'EOF'
+feat(web-admin): redesign mp transport plan list to match demo.
+
+Use dual dropdown filters, richer cards, and a speed dial for create actions.
+EOF
+)"
+```
+
+---
+
+### Task 5: 重写详情页 `detail.vue`
+
+**Files:**
+- Modify: `web-admin/src/views/m/pl/plan/detail.vue`
+- Reuse: `planApi.get`(或现有 detail 加载)、`PlPlanHazardCertViewTable`、`getRequiredCertSlots` / `isHazardProduct`、`splitMpPlanDetailActions`、`MpBottomSheet`
+- 对照: Demo 详情分区;Web `PlPlanViewTabs` 字段语义
+
+- [ ] **Step 1: 加载完整计划并分区渲染**
+
+数据:继续用现有 `planApi` 详情接口(查看 `detail.vue` 当前 `load`;若字段不足,对齐桌面查看所用的 get/detail 调用,**不要**新造后端)。
+
+分区(`mp-wv-section`):
+
+1. 基础:计划编号、计划日期、归属公司、单据类型、跟进人
+2. 运输:已安排时司机链接复制、多现场链接复制、车辆摘要 +「查看」Sheet、回传摘要(有则显示)
+3. 装送货:装货 kv + 产品行;送货段 Tab 切换;数量平衡(用计划上已有汇总或本地加总 planQty)
+4. 危化:`` + 齐备 Tag(`isHazardCertReady` 若 API 有字段)
+5. 运费:只读 kv(是否有运费、公里、方式、量、单价、金额、开票、备注)— 无运费时其余为 —
+6. 备注
+
+底栏:`splitMpPlanDetailActions` → ⋯ 打开 `MpBottomSheet`;主按钮绑定 edit/arrange/copy/wait/void/print 导航与现有 API。
+
+导航:
+
+```ts
+router.push(`/m/pl/plan/${id}/edit`)
+router.push(`/m/pl/plan/${id}/arrange`)
+router.push(`/m/pl/plan/new?copyFrom=${id}`)
+router.push(`/m/pl/plan/${id}/print`)
+```
+
+- [ ] **Step 2: 窄屏验收详情**
+
+打开已安排单与待安排单各一条:多送货段 Tab、危化槽位、底栏仅 2 主按钮 + 更多。
+
+- [ ] **Step 3: Commit**
+
+```bash
+git add web-admin/src/views/m/pl/plan/detail.vue
+git commit -m "$(cat <<'EOF'
+feat(web-admin): redesign mp transport plan detail sections.
+
+Align mobile detail with demo IA including hazard cert slots and capped footer actions.
+EOF
+)"
+```
+
+---
+
+### Task 6: 表单 page 变体对齐 Demo(锚点 + 贴底)
+
+**Files:**
+- Modify: `web-admin/src/components/business/pl-plan-form-dialog/index.vue`(仅增强 `isPageVariant` 分支的模板/样式,**不改**提交/校验核心)
+- Modify: `web-admin/src/views/m/pl/plan/form.vue`(如需:`copyFrom` query、标题)
+- 对照: Demo 表单锚点与底栏四按钮文案(已由 dialog 逻辑控制)
+
+- [ ] **Step 1: page 变体增加锚点条**
+
+在 `isPageVariant` 时,于 `.mp-plan-form-title` 下增加:
+
+```vue
+
+```
+
+```ts
+const mpAnchors = [
+ { id: 'sec-basic', label: '基础' },
+ { id: 'sec-transport', label: '运输' },
+ { id: 'sec-load', label: '装货' },
+ { id: 'sec-delivery', label: '送货' },
+ { id: 'sec-remark', label: '备注' }
+]
+const mpActiveAnchor = ref('sec-basic')
+
+function scrollMpAnchor(id: string) {
+ mpActiveAnchor.value = id
+ document.getElementById(id)?.scrollIntoView({ behavior: 'smooth', block: 'start' })
+}
+```
+
+给现有各 `