Break the approved WebView UI redesign into sequenced tasks for list, detail, form, print, and paste. Co-authored-by: Cursor <cursoragent@cursor.com>
25 KiB
小程序 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,勿改坏已有页):
/* === 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
<!-- web-admin/src/components/mp/MpBottomSheet.vue -->
<template>
<Teleport to="body">
<div v-show="modelValue" class="mp-sheet-mask" @click="close" />
<div
v-show="modelValue"
class="mp-sheet"
:class="{ 'mp-sheet--half': half }"
role="dialog"
aria-modal="true"
>
<div class="mp-sheet-handle" />
<div class="mp-sheet-head">
<h3>{{ title }}</h3>
<button class="mp-sheet-close" type="button" @click="close">关闭</button>
</div>
<div v-if="searchable" class="mp-sheet-search">
<input
v-model="keyword"
class="mp-sheet-search-input"
type="search"
:placeholder="searchPlaceholder"
autocomplete="off"
/>
</div>
<div class="mp-sheet-body">
<slot :keyword="keyword" />
</div>
<div v-if="$slots.footer" class="mp-sheet-foot">
<slot name="footer" />
</div>
</div>
</Teleport>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue'
const props = withDefaults(
defineProps<{
modelValue: boolean
title?: string
searchable?: boolean
searchPlaceholder?: string
half?: boolean
}>(),
{
title: '',
searchable: false,
searchPlaceholder: '搜索',
half: false
}
)
const emit = defineEmits<{ 'update:modelValue': [boolean] }>()
const keyword = ref('')
watch(
() => props.modelValue,
(open) => {
if (open) keyword.value = ''
}
)
function close() {
emit('update:modelValue', false)
}
</script>
<style scoped lang="scss">
.mp-sheet-mask {
position: fixed;
inset: 0;
z-index: 2000;
background: rgba(0, 0, 0, 0.4);
backdrop-filter: none;
}
.mp-sheet {
position: fixed;
left: 0;
right: 0;
bottom: 0;
z-index: 2001;
max-height: 78vh;
display: flex;
flex-direction: column;
background: #fff;
border-radius: 16px 16px 0 0;
padding-bottom: env(safe-area-inset-bottom, 0px);
}
.mp-sheet--half { max-height: 50vh; }
.mp-sheet-handle {
width: 36px;
height: 4px;
margin: 8px auto 0;
border-radius: 999px;
background: #dcebe4;
}
.mp-sheet-head {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 16px;
}
.mp-sheet-head h3 { margin: 0; font-size: 16px; }
.mp-sheet-close {
border: none;
background: none;
color: #0a9f6d;
font-size: 14px;
cursor: pointer;
}
.mp-sheet-search { padding: 0 16px 8px; }
.mp-sheet-search-input {
width: 100%;
height: 36px;
padding: 0 12px;
border: 1px solid #dcebe4;
border-radius: 8px;
font-size: 14px;
}
.mp-sheet-body {
flex: 1;
overflow: auto;
padding: 0 8px 12px;
-webkit-overflow-scrolling: touch;
}
.mp-sheet-foot {
display: flex;
gap: 8px;
padding: 10px 16px;
border-top: 1px solid #dcebe4;
}
</style>
- Step 3: 浏览器确认样式文件无 SCSS 语法错误
cd web-admin && pnpm exec vite build --mode development 2>&1 | tail -30
若全量 build 过慢,至少 pnpm dev 启动后打开任意 /#/m/pl/plan 看控制台无 SCSS 报错。
- Step 4: Commit
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返回字段
// 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: 写单测(字段存在)
// 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:
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: 跑测
cd web-admin && pnpm exec vitest run src/utils/mp-pl-plan-card.spec.ts
Expected: PASS(或按项目实际测试命令)。
- Step 4: Commit
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: 实现拆分逻辑
// 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: 单测
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
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 / 无权限空态):
- 去掉横向
mp-wv-chips+mp-wv-tabs+ 顶栏「新增」按钮 + 工具栏「三方人车」。 - Header 仅:← 工作台、标题、搜索。
mp-wv-filter-bar:日期触发器 + Tab 触发器;面板选项来自dateOptions与PL_PLAN_LIST_TABS。- 日期增加
custom:面板内两个input type="date"+ 确定,写入dateRange。 - 卡片展示
partyLine、follower;操作区:更多(MpBottomSheet:等通知/取消等通知/作废)、复制、安排(canArrangeTransport/arrangeTransportLabel)。 - Speed Dial:新增 →
/m/pl/plan/new;三方人车 →/m/pl/plan/paste。 - 保留
fetchPage/loadMore/planApi.list/ 403 处理。
卡片更多操作需:
import {
canArrangeTransport,
arrangeTransportLabel,
canWaitNotice,
canCancelWaitNotice,
canVoidPlan
} from '@/utils/pl-plan-action-guards'
作废/等通知调用与详情页相同的 planApi 方法(从现有 detail.vue 复制调用方式,勿发明新 API)。
- Step 2: 手动验收列表
cd web-admin && pnpm dev
浏览器开 /#/m/pl/plan(需 ticket 或本地已登录 mp 调试方式,与现网一致)。核对:
-
Tab 顺序含「全部」且在草稿后
-
自定义日期可筛
-
Speed Dial 两项可跳
-
卡片有供方→需方、跟进人
-
Step 3: Commit
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):
- 基础:计划编号、计划日期、归属公司、单据类型、跟进人
- 运输:已安排时司机链接复制、多现场链接复制、车辆摘要 +「查看」Sheet、回传摘要(有则显示)
- 装送货:装货 kv + 产品行;送货段 Tab 切换;数量平衡(用计划上已有汇总或本地加总 planQty)
- 危化:
<PlPlanHazardCertViewTable :products="hazardProducts" />+ 齐备 Tag(isHazardCertReady若 API 有字段) - 运费:只读 kv(是否有运费、公里、方式、量、单价、金额、开票、备注)— 无运费时其余为 —
- 备注
底栏:splitMpPlanDetailActions → ⋯ 打开 MpBottomSheet;主按钮绑定 edit/arrange/copy/wait/void/print 导航与现有 API。
导航:
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
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(如需:copyFromquery、标题) -
对照: Demo 表单锚点与底栏四按钮文案(已由 dialog 逻辑控制)
-
Step 1: page 变体增加锚点条
在 isPageVariant 时,于 .mp-plan-form-title 下增加:
<nav v-if="isPageVariant" class="mp-plan-anchor-bar">
<button
v-for="item in mpAnchors"
:key="item.id"
type="button"
class="mp-plan-anchor"
:class="{ active: mpActiveAnchor === item.id }"
@click="scrollMpAnchor(item.id)"
>
{{ item.label }}
</button>
</nav>
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' })
}
给现有各 <section class="pl-plan-form-section"> 加上对应 id(page 与 dialog 共用 id 无害)。
- Step 2: page footer 贴底样式
确保 .mp-plan-form-footer:
-
position: sticky; bottom: 0; -
padding-bottom: env(safe-area-inset-bottom) -
按钮可横向滚动或换行,文案已与 Web 一致(取消/暂存/创建/创建并安排运输等)— 核对
showDraftButton等计算,禁止再引入 Demo 曾误加的「校验」按钮 -
Step 3: 窄屏走通新增四按钮
/#/m/pl/plan/new:底栏 4 键;点锚点可跳;安排模式 /arrange 仅取消+调整运输。
- Step 4: Commit
git add web-admin/src/components/business/pl-plan-form-dialog/index.vue web-admin/src/views/m/pl/plan/form.vue
git commit -m "$(cat <<'EOF'
feat(web-admin): add mp plan form anchors and sticky footer.
Improve the page-variant plan form for WebView without changing submit rules.
EOF
)"
Task 7: 打印页与三方人车页打磨
Files:
-
Modify:
web-admin/src/views/m/pl/plan/print.vue -
Modify:
web-admin/src/views/m/pl/plan/paste.vue(仅当 page 变体 footer/安全区不够时) -
对照: Demo 打印 Chip + 送货客户;
PlPasteRecognizeDialog字段已全量则少改逻辑 -
Step 1: 打印 — 送货客户选择体验
现有 el-select 可保留;若窄屏难用,改为按钮打开 MpBottomSheet 列出 customers,选中后 reloadPreview。
规则保持:kind === 'delivery'(及 blank-delivery 若支持)未选客户时提示,不生成预览。
确认 Chip 含空白入库/送货/分装(与 MpPlanPrintKind / Demo 一致)。
- Step 2: 三方人车 — 验收 checklist
打开 /#/m/pl/plan/paste:识别 → 承运商/主挂/核载/驾押三要素/冲突 → 确认生成 → 回列表。若 footer 被遮,给 PlPasteRecognizeDialog page 变体补与表单相同的 sticky footer padding(最小改动)。
- Step 3: Commit
git add web-admin/src/views/m/pl/plan/print.vue web-admin/src/views/m/pl/plan/paste.vue
# 若改了 paste dialog page 样式也一并 add
git commit -m "$(cat <<'EOF'
feat(web-admin): polish mp plan print customer pick and paste layout.
Align WebView print/paste screens with the approved mobile demo interactions.
EOF
)"
Task 8: 端到端冒烟清单(无新代码则只记录)
Files: 无强制代码;若发现小缺口可热修后单独 commit。
- Step 1: 按清单在窄屏/真机 WebView 勾选
| # | 路径 | 期望 |
|---|---|---|
| 1 | 列表双下拉 + 自定义日期 | 筛选生效 |
| 2 | Speed Dial 新增 / 三方 | 跳转正确 |
| 3 | 卡片更多 等通知/作废 | 与桌面同账号行为一致 |
| 4 | 详情分区 + 危化槽位 | 毒/爆槽位正确;未上传可见 |
| 5 | 详情底栏 | ≤2 主按钮 + 更多 |
| 6 | 新增四按钮 | 暂存/创建/创建并安排 |
| 7 | 安排危化未齐 | 拦截提示 |
| 8 | 打印送货单 | 必选客户 |
| 9 | 三方人车 | 全字段可提交 |
| 10 | 现场原生「运输计划单」 | 不受影响 |
- Step 2: 若有热修,单独 commit;否则在 PR 描述粘贴冒烟结果
Spec 覆盖自检
| Spec 项 | Task |
|---|---|
| D7 双下拉 + 自定义日期 + PL_PLAN_LIST_TABS | Task 4 |
| D8 Speed Dial | Task 4 |
| D9 详情底栏 2 主 + 更多 | Task 3、5 |
| D10 危化证详情槽位 | Task 5 |
| D11 运费只读 | Task 5 |
| D12 打印选客户 | Task 7 |
| D3/D4/D5 表单锚点、同表单安排、Sheet | Task 1、6(选择器以 page 变体渐进;列表/详情更多已用 Sheet) |
| D6 不改壳/ticket | 全文文件结构 |
| 三方人车全字段 | Task 7 |
| 非目标:运费编辑/证上传页 | 无对应 Task |
说明: 表单内全部桌面 el-select 改为 Sheet 工作量大;本期以锚点+贴底+可用触控高度为必达,车辆库/承运商库等已有移动入口按钮的保持;若验收仍认为选择器过重,再开增量 Task 把高频选择器换成 MpBottomSheet,不阻塞本计划合入。
执行交接
Plan 完成后请选择执行方式(见下一条用户提示)。