日期: 2026-09-14
状态: 已实现
方案: 小程序通用web-view壳 +web-admin移动端路由/#/m/*;首个业务为管理端「运输计划」全量能力
Demo:bak/mobile-ui/shanxia-mp-pl-plan-webview-demo.html(卡片流列表,已选方案 A)
在微信小程序中提供与 Web 管理端 运输管理 → 运输计划(/#/pl/plan-module/plan)同等业务能力,并以 WebView 加载 H5 落地,使业务改动只发门户静态资源,不必每次提审小程序。
本次同时沉淀 可复用模板:后续其它管理端页面按同一骨架接入,不再复制鉴权、壳页与布局。
| # | 项 | 决策 |
|---|---|---|
| D1 | 列表形态 | 卡片流(Demo A),不做桌面表格横滑 |
| D2 | 功能范围 | 全量对齐管理端:新增/编辑、安排运输、复制、等通知、打印、作废、三方人车快速创建 |
| D3 | H5 落点 | 在 web-admin 新增 /#/m/... 移动端路由,复用现有 API 与业务逻辑;不为 WebView 新建独立工程 |
| D4 | 登录态 | 必须使用小程序已登录用户的 JWT;H5 禁止再走 /auth/login |
| D5 | Token 传递 | 一次性短时 ticket 换票;URL 不得长期携带 accessToken |
| D6 | 模板化 | 小程序只维护一个通用 WebView 壳;后续模块只加 H5 路由 + 工作台入口 |
| D7 | 与现网小程序页 | 并存:现有 pages/pl/transport-plan/*(现场记录/现场回传)不替换、不改名混用 |
| D8 | 视觉 | WebView 内 H5 使用小程序三墙 Token(#0a9f6d 等),不套管理端 Fresh Mint 侧栏/WorkTab;这是「嵌在小程序里的 H5」,不是管理端皮肤 |
| D9 | 打印 | WebView 内先做单据 预览 / 存图;不承诺调用手机系统打印机或 Lodop |
el-table / el-dialog 原样塞进 WebView小程序工作台
→ pages/common/mp-webview?path=/m/pl/plan
1) POST /api/web/auth/mp-webview-ticket (小程序 Bearer)
2) web-view src = {WEB_ORIGIN}/#/m/pl/plan?mpTicket={ticket}
(Hash 路由下 query 在 # 之后,不会进 Nginx 的 path/query 日志;见 §5)
3) H5 启动:POST /api/web/auth/mp-webview-exchange
→ accessToken + refreshToken 写入 web-admin userStore
4) 之后与管理端相同:Authorization: Bearer + 现有 /api/transport/plan/...
后续模块
→ 同一壳页 ?path=/m/<module>
→ 只需新增 web-admin 页面,不必再提审小程序
┌─────────────┐ ticket ┌──────────────────┐ JWT ┌────────────┐
│ miniprogram│ ──────────────► │ web-admin /#/m/* │ ───────────► │ FastAPI │
│ web-view壳 │ ◄────────────── │ MpWebviewShell │ ◄─────────── │ 既有计划API │
└─────────────┘ 401→reLaunch └──────────────────┘ 租户/权限 └────────────┘
边界
| 单元 | 职责 | 依赖 |
|---|---|---|
pages/common/mp-webview |
取票、拼 URL、承载 <web-view>、接收 H5 回登录指令 |
小程序 token、WEB_ORIGIN、业务域名白名单 |
MpWebviewShell |
无侧栏布局、安全区、401 桥接、禁止桌面壳 | 换票结果、wx.miniProgram |
/#/m/pl/plan* |
运输计划移动端 UI | 现有 planApi / composable / 校验 |
| ticket API | 签发与一次性兑换 | Redis + 当前用户 session |
微信后台 业务域名 配置一次:w.shanxiachem.com(测试环境另配对应 IP/域名)。开发者工具本地联调使用 WEB_ORIGIN(如 127.0.0.1:3006),需在开发者工具中关闭/放宽域名校验。
web-view 是独立浏览上下文:读不到 uni.setStorageSync,H5 的 localStorage 初始为空。两端虽都走 /web/auth/login 发的同一套 JWT,但 不会自动共享。
禁止:H5 弹出管理端登录页;禁止 URL 明文长期挂 accessToken。
签发 POST /api/web/auth/mp-webview-ticket
Authorization: Bearer{ ticket, expiresIn: 60 }mp_wv_ticket:{ticket} → { userId, sessionId, refreshJti? },TTL 60 秒,读后删除(一次性)兑换 POST /api/web/auth/mp-webview-exchange body { ticket }
token、refreshToken、expiresIn、userInforeLaunch 登录页加密排除:mp-webview-exchange 须加入 API_ENCRYPT_EXCLUDE_PATHS(与 login 同类,此时 H5 尚未持有会话密钥)。签发接口走已登录加密通道。
URL 形态(约定)
Hash 路由下 ticket 放在 hash 内,避免出现在 Nginx access log 的 path/query:
https://w.shanxiachem.com/#/m/pl/plan?mpTicket=<ticket>
实现时 H5 启动立刻 replace 掉 mpTicket,只留干净路由。若必须进一步降低泄露,可将 ticket 仅通过 wx.miniProgram.postMessage 不可行(加载前 H5 收不到);因此短 TTL + 一次性是主要防护。
/#/m/* 不能走现有「未登录 → /auth/login」逻辑,也 不能在换票后走桌面动态菜单注册(会挂侧栏、WorkTab)。
约定 meta.isMpWebview: true:
userStore.isLogin 进入页面setLoginInfo(或等价写入 token)handleDynamicRoutes 的管理端菜单注入wx.miniProgram.reLaunch({ url: '/pages/login/login' }),永不 next({ name: 'Login' })driver_access_token / site_access_token 是单据级公开链接,禁止用于本模板。本模板始终是员工登录态。
现网「运输计划单」perms = mini:pl:transportPlan:list,对应 现场回传,保持不变。
新增菜单(名称与管理端对齐,避免再叫「运输计划单」):
| 字段 | 值 |
|---|---|
| menu_name | 运输计划 |
| path | mp-webview 或工作台 code pl-plan-webview |
| perms | mini:pl:plan:manage |
| 父级 | 运输管理(MINI_PL_ROOT) |
| order_num | 排在现有「运输计划单」之后,避免打乱现场人员入口顺序 |
工作台配置:enabled + code → 打开通用壳 PAGE_PATHS.mpWebview + '?path=' + encodeURIComponent('/m/pl/plan')。
运输计划接口继续校验 Web 菜单码 pl:plan:*(及现有按钮权限),不另写一套后端。
因此:能在小程序里办计划的角色,必须同时拥有:
mini:pl:plan:managepl:plan:list 等(否则 H5 调接口 403)无 Web 计划权限时:H5 展示「无权限」空态,不跳桌面 403 页。
视觉与结构对齐 Demo:bak/mobile-ui/shanxia-mp-pl-plan-webview-demo.html。规范:docs/miniprogram-ui-spec-sanqiang.md。
| 能力 | 移动端形态 | 逻辑来源 |
|---|---|---|
| 列表 | 日期快捷 + 状态/单据 Tab + 检索 + 卡片 | planApi 列表筛选(PL_PLAN_LIST_TABS) |
| 查看 | 全屏详情 | 现有查看字段/状态 Tag |
| 新增/编辑 | 全屏分区表单(非桌面弹窗) | PlPlanFormDialog 校验与提交,拆成移动分区 |
| 安排/调整运输 | 全屏安排页 | usePlPlanArrangeActions 等 |
| 复制 | 详情/列表操作 + 确认 | 现有复制 API |
| 等客户通知 | 同上 | 现有 wait-notice |
| 作废 | 危险操作确认 | 现有 void |
| 打印单据 | 预览页(入库单/送提货单/分装单/空白单) | 现有 print API;无系统打印则引导长按存图 |
| 三方人车快速创建 | 独立全屏页 | 现有 paste-create 逻辑 |
列表卡片主信息:计划编号、单据类型、计划日期、跟进状态 Tag、车牌、人员摘要、产品/供需摘要。点卡片进详情。
代码复用原则:抽 composable / 纯函数,避免复制 index.vue 整页。桌面弹窗组件可按「移动全屏包装」复用表单子块,不在 WebView 里渲染 el-dialog 作为主路径。
建议路由(均在 /#/m 下):
/m/pl/plan 列表
/m/pl/plan/new 新增
/m/pl/plan/:id 详情
/m/pl/plan/:id/edit 编辑
/m/pl/plan/:id/arrange 安排运输
/m/pl/plan/paste 三方人车
/m/pl/plan/:id/print 打印预览
下一个模块只需:
web-admin:views/m/<module>/... + 路由 meta.isMpWebviewMpWebviewShellmp-webview 壳 + path=/m/...禁止:每个模块再做一个 web-view 页面、再做一套登录注入。
| 情况 | 行为 |
|---|---|
| 未登录打开壳 | 小程序拦截,先去登录页 |
| ticket 过期/已用 | H5 提示后 reLaunch 登录或重新进入工作台再开页 |
| API 401 | 清 H5 token,通知小程序登录 |
| API 403 | 页内无权限 |
| 网络失败 | Toast,可重试 |
非小程序打开 /#/m/* |
无 ticket 则提示「请从山峡化工小程序打开」,不提供 Web 登录入口(防把移动页当第二套后台) |
/#/auth/loginweb-admin 静态资源、不发包:卡片文案或样式变更在小程序内可见| 层 | 路径(预期) |
|---|---|
| 后端 | backend/app/api/public.py 增加 ticket/exchange;Redis TTL |
| H5 壳 | web-admin/src/layouts/MpWebviewShell.vue、src/views/m/**、守卫分支 |
| 小程序壳 | miniprogram/pages/common/mp-webview.vue、pages.json、config/api.js、workbench-menus.js |
| 菜单 SQL | docs/sql/migrations/2026-09-14-mini-pl-plan-webview-menus.sql |
| Demo | 已有 bak/mobile-ui/shanxia-mp-pl-plan-webview-demo.html |
web-view 同一页面只能有一个组件、分享/原生导航受限;打印与文件下载体验弱于桌面。WEB_ORIGIN 不在业务域名内,真机必须指向已配置的测试/生产门户。