transport-code-audit.md 26 KB

运输移动端只读代码审计

日期:2026-10-21。审计版本:97cbba3b0 工作区。范围:/m/pl/plan、/m/pl/plan-cert 及其直接表单、选择器、附件和共享业务逻辑。本报告是八页测试任务的子报告,不替代主报告的真实 UI 证据。

结论:存在已由纯逻辑复现的搜索竞态与失败分页漏页;运输表单移动化和详情字段对齐仍有缺口。危化证核心查询、状态门禁、上传载荷及后端状态保护的 22 项既有逻辑/入口检查通过,但不能据此判定业务验收通过。 本子任务没有浏览器操作、接口登录、业务 API 请求、数据库连接/写入或对象存储上传,没有创建业务数据。

1. 环境与审计依据

1.1 环境隔离结论

对象 只读核查结论 配置来源
当前本机后端 8000 端口存在 Python 监听进程,工作目录为本仓库 backend,命令指向 app.main:app;进程环境未观察到 ENV_FILE、DATABASE_URL、MINIO_ENDPOINT、MINIO_BUCKET 显式覆盖 lsof + 对该进程 ps eww 的内存内白名单提取,未输出完整命令或环境
默认业务数据库 非回环公网端点,属于远程资源,不是本地隔离数据库;与生产文件的数据库配置不同 backend/.env:19;backend/app/core/config.py:204–206 默认读取此文件
默认 MinIO 非回环公网端点,不是本地隔离对象存储;端点和 bucket 与生产文件不同 backend/.env:39;同一配置加载机制
生产文件 数据库、MinIO 同样为远程端点;不能因默认文件不同就推断默认环境是本次专用、无人共享的测试环境 backend/.env.production:19、:39;生产启动脚本 backend/start-prod.sh:19

没有输出密码、token、完整连接串或存储密钥。上述证据支持“当前默认配置指向远程资源、未证明专用隔离”,不证明该远程资源只有本测试账号使用。没有发起数据库探测查询或 MinIO 请求。真实新增、安排、齐备、上传等写闭环应继续标为待验证。

1.2 规范优先级与图谱

  • 已阅读根 AGENTS.md;没有发现目标目录下覆盖它的 AGENTS 文件。
  • 已阅读根、前端、后端 graphify-out/GRAPH_REPORT.md 的社区/核心节点;三个 wiki/index.md 路径均不存在。前端图谱核心节点包括 @/api/pl、pl-plan-form-dialog;根图谱包括 get_tenant_no、DbUserContext。图谱构建提交早于本次工作区,最终定位按当前源码核实。
  • 移动视觉以 docs/miniprogram-ui-spec-sanqiang.md 第 12 节、尤其 12.7/12.8 及对应 OpenSpec 补遗为准;/m/** 使用三墙色板,不能按桌面 Fresh Mint 颜色规则误报。
  • 运输范围:docs/superpowers/specs/2026-09-14-mp-webview-transport-plan-design.md 的 D2/D9,及 2026-09-15-mp-webview-pl-plan-mobile-redesign-design.md 的 D3–D13。
  • 危化证范围:docs/superpowers/specs/2026-10-17-mp-webview-pl-plan-cert-design.md;其旧 expense-card 视觉被最新扁平卡规范取代,业务范围仍有效。
  • 历史记录 2026-10-17-mp-pl-plan-cert-delivery.md 只作为线索;本次没有把其历史模拟浏览器通过结果计入当前通过数。

2. Web / 移动能力矩阵

“代码具备”表示有实现,不等于真实服务、微信或权限验收通过。

能力 Web 基准 / 移动应保留 当前移动代码 判定与证据
运输列表搜索、状态、日期 保留 PL_PLAN_LIST_TABS、日期区间、关键词 有,300ms 输入查询、滚动分页 代码具备;竞态已逻辑复现,见 T01
运输列表复杂列筛选/导出/列设置 09-15 D13 明确不含列设置、导出、强制改状态 未做桌面全部列筛选 不把明确删减报成缺陷;现有更多筛选仍应真实生效
新增、草稿、编辑、安排/调整 同一业务表单和状态规则 form.vue:5 复用 PlPlanFormDialog variant=page;同一 handleSubmit 核心入口具备;移动交互与加载失败状态见 T03/T07
创建成功但安排失败 保留已创建 ID,不能重复创建 pl-plan-form-dialog/index.vue:2732–2744 发送 {planId, arranged:false} 并退出创建态 静态保护存在;真实部分成功闭环待测
复制、等通知、取消等通知、作废 保留;归档/作废等状态约束同 Web 使用 pl-plan-action-guards.ts 和 mp-pl-plan-detail-actions.ts 代码具备;未实测多角色及真实状态变化
三方人车快速创建 明确保留 plan/paste.vue 全屏包装;表单另有快捷入口 入口具备;嵌套快捷入口仍是桌面组件,见 T03
打印预览、存图 09-14 D2/D9、09-15 D12 明确保留;不要求手机系统打印机 当前无 print.vue、无 print 路由、操作类型中无 print 静态范围缺口 T04;本次未找到后续明确取消打印的依据
运输详情基础、运输、多送货段 应与 Web 查看字段一致 有独立详情和多段切换 仅部分对齐,见 T05
运费 编辑明确不在本期;只读字段仍应一致 详情只显示有无运费、金额、备注 运费编辑不报缺陷;只读字段缺漏报 T05
详情锚点 长页分区定位;当前测试计划还要求滚动高亮同步 运输详情有 scroll 监听;危化证只有点击定位 危化证滚动高亮静态缺口 T06;运输表单没有锚点导航,见 T03
危化证五 Tab、默认未上传 必须含危化产品;未上传需附件缺口且未齐备;默认排除已取消 model.buildQuery、默认状态及 Tab 冲突优先级 8 项 model 测试中的相关断言通过
危化证完整筛选、动态候选、排序 复用 Web 列筛定义;仅去掉导出 CertFilters.vue、Web columnFilterDefs.ts,服务端候选接口 代码具备;实际结果集合、候选覆盖与 UI 待测
危化证批量齐备/取消 重取最新状态,跳过不适用项,报告成功/失败/跳过 index.vue:395–443 分别计数;逐个 GET 后筛选再提交 静态具备;不同角色和混合状态的真实批量闭环待测
上传/齐备前端权限 pl:plan-cert:upload / pl:plan-cert:ready,权限失败关闭入口 index.vue:446–460 默认 false,失败保留 false;详情同样检查 静态具备;服务端既有权限缺口单列,不称已修复
已取消/已归档不可修改证书 Web/服务端统一限制 model.canEdit;后端 update_plan_product_certs 已拒绝 4 项后端状态/保存替身测试通过
危化证保存 按计划产品 ID 提交三个 URL;允许先齐备后补附件 CertUpload.vue:99–140 重取详情、上传中保护、保存失败保留内容;model.certPayload 白名单 逻辑/静态具备;空槽位不阻止保存是明确规则,不是遗漏必填
分段与证书关联 证书挂计划产品,段产品通过 planProductId 引用;不同合同同产品要区分 CertDetail.vue:445–465 共享段显示映射;CertStop.vue:110–130 元数据/反馈按 ID;上传按 p.id 有正确 ID 关联路径;多段同产品不同证书的真实保存回显待测,未证实串证
附件预览/下载/移除 图片、PDF 等既有 WebView 支持格式;72×72 宫格 CertAttachments 复用 AttachmentUploadField,后者按 /m 路由切宫格 静态具备;图片旋转/缩放工具触达见 T08;微信 PDF/下载待真机
危化证导出 唯一明确删除的业务能力 无导出 明确删减,不报缺陷

3. 选择器与两列布局清单

选择器/布局 数据与过滤方式 查全/交互结论
装/送客户下拉 pl-plan-load-section/index.vue:331–359,初始 limit:20,关键词服务端 limit:50;送货侧同类实现 不是只过滤首批内存;超过 50 条同关键词结果无下拉分页。可进一步缩小关键词,或进入完整客户选择器,但后者当前是桌面弹窗
完整客户选择 CustomerSelector.vue:2 桌面 el-dialog width=1500px + 表格;:277–302 服务端分页20/关键词 有全量分页能力,移动主交互形态不符合规范;父表单没有移动分支
司机/押运员 usePlRemoteOptions.ts:155–185 服务端姓名/手机号 q,第一页最多100;父表单按启用状态及人员类型过滤,车辆未选时禁用 不是首批本地过滤;无下一页 UI。应使用不在初始100条内的唯一姓名/手机验证检索;普通/跨归属账号待测
车辆/承运商库 专用 Sheet,服务端 q、每页20;车辆分三方/山峡 查询覆盖服务端;存在竞态、分页漏页 T01/T02;仍用自制 MpBottomSheet,未统一 Vant
装/卸地址库 MpTransportAddressSelectSheet.vue:91 服务端 q、status:'1'、每页20 过滤名称/地址/联系人有查询入口;失败漏页与竞态已复现
合同 MpContractSelectSheet.vue:147 客户限定、排除 invalid、每页20;默认 approved/executing 可选 有服务端搜索与分页;竞态/漏页已复现;客户切换后候选归属待 UI 验证
内部客户产品 MpProductSelectSheet.vue:229 完整目录服务端检索,matchModel:true、matchProductNo:false、status:'on'、每页20 可查名称/拼音/型号(接口实现仍需真实结果验证);编号明确不参与该查询,placeholder 也没承诺编号;竞态/漏页已复现
非内部客户合同产品行 pl-plan-form-dialog/index.vue:910–937 汇总全部合同明细,打开桌面 el-dialog/el-table 不是普通产品 Sheet;没有关键词控件,长合同明细定位困难,是当前可达交互缺口
产品 Sheet 的受限模式 MpProductSelectSheet.vue:198–225 全部允许 ID 逐条读取,再本地匹配名称/型号 “拼音首字母”placeholder 与该函数不一致,纯逻辑已复现;但当前运输表单有合同走专用行选择器、无合同时允许 ID 为空,未证明非空受限模式在本路径可达,作为共享组件测试债务,不计新增可达业务缺陷
运输表单短字段两列 pl-plan-form-dialog/index.vue:2774–2810 创建/计划日期、公司/类型、时间/联系方式;:2851 司机/押运员两列 已实现,不能笼统报“所有字段单列”。375/414 下长手机号、司机名、日期是否截断尚未测
两类详情的短字段 运输详情逐行 KV;危化证基础逐个 adv-field,段产品逐行 cert-kv 可将单位/包装、计划量/实绩量、状态/类型等成对布局;地址、备注、合同长标识保留通栏。这是 P3 密度建议,尚无实测滚动距离

4. 已复现逻辑缺陷与静态缺口

T01 — P2:旧请求覆盖最新搜索/筛选结果

  • 证据等级:实际源码函数的纯逻辑复现;未做浏览器复现。
  • 运输列表 web-admin/src/views/m/pl/plan/index.vue:527–550 没有请求序号/取消/结果条件比对;旧请求后返回会覆盖 plans 和 total。
  • 同类函数:MpVehicleLibrarySelectSheet.vue:127、MpCarrierLibrarySelectSheet.vue:93、MpTransportAddressSelectSheet.vue:91、MpContractSelectSheet.vue:147、MpProductSelectSheet.vue:229(完整路径均位于 web-admin/src/components/mp/)。
  • 受控步骤:依次发 old、new 搜索;先返回 new,再返回 old;最终关键词是 new,列表却为 old。6个目标均复现。车辆切换 Tab 也依赖同一无保护函数。
  • UI 复现:在隔离网络替身/可控弱网下输入 A,待请求发出后改 B,让 A 晚返回;核对标题输入与候选集合。仅快速输入而未控制返回顺序不能保证触发。
  • 对照:危化证列表 index.vue:261–287 已用 requestVersion,此问题不能套报到危化证主列表。

T02 — P2:选择器加载更多失败后跳过失败页

  • 证据等级:实际源码函数的纯逻辑复现。
  • 五个 Sheet 的 loadMore 在请求前递增页码,失败不回退;位置依次为车辆:163、承运商:121、地址:120、合同:178、产品:266。
  • 受控步骤:保留第1页;加载第2页返回错误;再次加载更多;实际请求第3页,漏第2页。5个目标均复现。
  • 运输主列表 plan/index.vue:439–445 同样提前加页,:527–550 无回退,列为同机制静态候选,未在本次单独模拟滚动观察器。
  • UI 复现:候选大于40条,失败仅注入第2页,再点加载更多/继续滚动;对照第21–40条是否遗漏,确认错误之后是否有可见重试入口。
  • 对照:危化证主列表先计算 nextPage,成功后才赋值 page(:264、:283),有错误/重试状态。

T03 — P2:运输表单仍混用桌面弹窗和过小控件,缺少分区锚点

  • 证据等级:静态确定实现差异;溢出、触达和键盘影响待 UI 测量。
  • plan/form.vue:5–16 使用 page 包装,但共享表单 pl-plan-form-dialog/index.vue:24–174 仍为 Element Plus 表单、日期和司机/押运下拉;:363 客户选择器无移动分支;:411 合同产品行仍为宽桌面表格弹窗;:405 三方快速建档未传 page variant。
  • 客户选择器自身 CustomerSelector.vue:2–5 指定1500px宽;不能仅凭此断言最终 DOM 宽度,需 CUA 确认全局样式是否收敛。
  • 表单只有 sec-basic/sec-transport/sec-load/sec-remark section ID,没有分区导航/scrollIntoView;不满足09-15确认的“长页+分区锚点”。
  • pl-plan-form-dialog/index.vue:2930–2947 把输入最小高设34px、按钮28px、小按钮26px;与44px触达目标冲突,需检查最终命中区而非只看可见字高。
  • UI 复现:375/414打开新增/编辑→客户放大选择→合同产品行→司机→三方快速建档;记录是否可单手检索、是否横滚、底栏/关闭被遮挡、软键盘后能否确认;不要保存共享业务记录。

T04 — P2:确认范围内的打印预览入口缺失

  • 证据等级:静态范围差异。
  • 当前 web-admin/src/router/routes/staticRoutes.ts:145–179 只有列表、新增、paste、编辑、安排、详情;views/m/pl/plan/ 无 print 文件。
  • web-admin/src/utils/mp-pl-plan-detail-actions.ts:1–10 无 print 类型;列表更多 plan/index.vue:464–477 和详情均无打印入口。
  • Web 保留 web-admin/src/views/pl/plan/index.vue:939 打印菜单和:988–990 三类单据组件。
  • 09-14/09-15范围明确保留预览与存图;未找到后续明确取消依据。若存在未入库的后续产品决策,应把此条改为范围变更,不能仅按当前实现视作已删减。
  • UI 复现:同一可打印计划,在 Web 查看打印选项;移动列表更多/详情更多均查找。无需实际打印或写数据。

T05 — P2:运输详情缺少应保留的实际数量、运费和回传信息

  • 证据等级:静态字段差异;真实记录对照待测。
  • plan/detail.vue:133–194 装/送产品只显示单位、包装、计划量及送货方式,没有入/出库实际量;Web pl-plan-view-tabs/index.vue:117–124 使用 show-stop-actual-qty。
  • 移动 plan/detail.vue:206–212 只有3个运费字段,Web pl-plan-view-tabs/index.vue:1544–1585 还含公里数、计算方式、运输量、单价、是否开票。
  • 移动 plan/detail.vue:471–482 反馈只收集现场备注;Web pl-plan-view-tabs/index.vue:132–141 按段展示司机/现场/地磅反馈,能触达对应附件。移动当前无对应完整反馈/附件面板。
  • 移动合同只是拼接文本(:426–434),Web 查看面板有合同组件;是否所有关联查看都应保留按最新全量对齐范围复核。
  • UI 复现:只读选择已安排且含实绩、计重运费、司机/现场附件的计划;同账号 Web/移动对照,不用“暂无数据”的样本判断字段完整性。

T06 — P3:危化证详情手动滚动后 Tab 高亮不随当前分区变化

  • 证据等级:静态逻辑缺口,未称浏览器已复现。
  • plan-cert/CertDetail.vue:471–489 只有点击 scrollIntoView、activeTab=base、resize监听;没有 scroll/IntersectionObserver 更新活动分区。
  • 对照 plan/detail.vue:659–696 已实现滚动高亮及点击滚动锁。
  • UI 复现:打开危化证详情,先不点Tab,手动滚至证书与附件区;检查是否仍高亮基础信息。再点击Tab检查固定头偏移、末节可达性和返回位置。

T07 — P1候选:编辑详情读取失败后仍展示空表单和提交入口

  • 证据等级:解锁行为已纯逻辑复现;未做任何保存,数据影响尚未证实。
  • pl-plan-form-dialog/index.vue:2225–2231 先 resetForm 再 GET 现有计划;GET失败会抛出。:2196–2209 无错误态,finally 将 detailLoading 设false。
  • 模板:16、:317 仅按 detailLoading 显示表单/底栏;showSaveButton 在:770–773 主要看运输状态,未检查详情是否加载成功。
  • 受控调用真实 handleOpened,令读取失败,确认最终 detailLoading=false。是否因其他校验挡住保存、哪些字段可被覆盖,仍需隔离测试,不能报告已发生数据丢失。
  • UI 复现:用网络替身使“编辑现有计划”的GET失败;检查是否展示错误态与重试,或变为空表单并保留保存/安排按钮。仅在完全隔离替身中点击提交检查调用,禁止向远程共享记录保存。

T08 — P2:附件图片预览工具的移动命中区小于规范

  • 证据等级:静态尺寸证据;实际触控区域待 UI 测量。
  • components/business/attachment-upload-field/index.vue:1072–1079 的 .preview-toolbar--mp .el-button 显式 height/min-height:28px !important,适用于 /m/** 预览旋转、缩放等按钮。
  • 上传宫格自身有移动分支与下载/删除入口,不能把整个附件组件误报成140×96桌面卡。缺口集中在图片预览工具条。
  • UI 复现:危化证详情打开现有图片,375/414下测旋转/放大/缩小/重置命中区及是否与关闭按钮重叠;PDF需另测加载失败、下载与微信内核支持。

5. 已有明确延期事项与未证实问题

  • docs/superpowers/specs/2026-10-17-mp-pl-plan-cert-existing-issues.md 第3项记录:证书上传/齐备后端未接对应按钮权限函数,并注明当时用户要求忽略。本次复核 backend/app/api/pl/plan.py:2934、:2966 仍主要检查登录、租户和数据范围;pl_plan_cert_perm.py:18/22 函数没有路由调用。不重定义为此次移动迁移新回归,不实施越权请求,也不声称真实权限已通过。
  • 已取消计划禁止证书写入的历史问题已修复,当前后端替身测试通过;不重复报旧缺陷。
  • 证书按计划产品归属,不能机械要求每个送货段各存一份证书。本次未发现确定的串证代码证据;必须用“同一 CRM 产品、不同计划产品ID/包装/合同、两段不同附件”样本核对选择、保存载荷、重进以及 Web 回显。
  • stopProductMeta 优先精确 planProductId,无此ID时才按 productId 兜底;后者存在同产品多行时的歧义,需要先证明数据允许丢失 planProductId 才能定缺陷。
  • 暂存校验比正式创建宽松、人工齐备允许后补附件、无手机系统打印机、无运费编辑、无导出,均有设计依据,不作为缺陷。
  • 产品 Sheet 受限模式拼音不匹配是共享组件逻辑问题,但本审计未确认非空受限模式在运输表单的实际可达路径;不与当前合同产品行无搜索混为一项。

6. 本次执行结果

检查 方法及安全边界 结果
危化证查询/状态/载荷/日期/元数据/合同补全 pnpm exec tsx --test src/views/m/pl/plan-cert/model.test.ts,纯函数和传入假lookup 8/8通过
小程序菜单、H5独立路由、菜单SQL约束 node --test scripts/mp-pl-plan-cert-entry.test.mjs,只读文件/AST,不执行SQL 3/3通过
安排运输产品必填 pnpm exec tsx --test src/utils/pl-plan-stop-product-arrange-required.test.ts,纯函数 4/4通过
后端证书状态、筛选SQL构造、菜单徽标 下方网络禁用命令;MagicMock DB、SQL编译,不连数据库 7/7通过;9条已有Pydantic/SQLAlchemy弃用warning
五个 Sheet 的搜索竞态、失败页重试 从当前SFC AST提取真实fetchList/loadMore,注入受控Promise;无Vue挂载、无浏览器、无API模块导入 10/10探针复现错误行为(不是业务通过)
运输主列表旧结果覆盖 提取真实fetchPage,倒序完成Promise 1/1探针复现
编辑加载失败解锁 提取真实handleOpened,读取函数抛错 1/1探针复现;提交副作用未测
产品受限模式拼音 提取真实matchesQuickKeyword 1/1探针确认名称/型号匹配但拼音不匹配;可达性限制见上

既有检查共 22项通过,额外 13个探针记录缺陷/候选的实际函数行为;两者分别统计。没有执行任何Playwright浏览器脚本;test-mp-pl-plan-cert.mjs 及历史镜像函数脚本未作为本次真实UI或实际函数覆盖使用。

后端命令(从 backend/ 执行),显式阻断任何遗漏的真实网络及Engine连接,关闭字节码和pytest缓存写入:

PYTHONDONTWRITEBYTECODE=1 venv/bin/python -B - <<'PY'
import socket
def blocked(*args, **kwargs):
    raise RuntimeError('Audit guard: real network disabled')
socket.create_connection = blocked
socket.socket.connect = blocked
socket.socket.connect_ex = blocked
from sqlalchemy.engine import Engine
Engine.connect = blocked
import pytest
raise SystemExit(pytest.main([
    'tests/test_pl_plan_product_certs.py',
    'tests/test_pl_plan_cert_list_filters.py',
    'tests/test_pl_menu_badge_plan_cert.py', '-q', '-p', 'no:cacheprovider'
]))
PY

6.1 五个选择器的可重复源码探针

从 web-admin/ 执行。仅读取SFC、使用本地TypeScript转译及Node VM,不加载整个应用或网络API;断言目标是“复现当前缺陷”,后续修复后这些旧行为断言应失败,不能作为产品正确性的验收测试。

const fs = require('node:fs'), vm = require('node:vm')
const ts = require('typescript'), assert = require('node:assert/strict')
function extract(file, names) {
  const source = fs.readFileSync(file, 'utf8').match(/<script setup[^>]*>([\s\S]*?)<\/script>/)[1]
  const ast = ts.createSourceFile(file, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS)
  return names.map(name => {
    const fn = ast.statements.find(n => ts.isFunctionDeclaration(n) && n.name.text === name)
    assert(fn)
    return ts.transpile(fn.getText(ast), { target: ts.ScriptTarget.ES2022 })
  }).join('\n')
}
const targets = [
  ['MpVehicleLibrarySelectSheet', 'vehicleApi', 'page'],
  ['MpCarrierLibrarySelectSheet', 'carrierApi', 'page'],
  ['MpTransportAddressSelectSheet', 'transportAddressApi', 'page'],
  ['MpContractSelectSheet', 'listContract', 'pageNum'],
  ['MpProductSelectSheet', 'fetchProductList', 'pageNum']
]
;(async () => {
  for (const [name, api, pageKey] of targets) {
    let calls = []
    const ctx = {
      rows: { value: [] }, loading: { value: false }, total: { value: 0 },
      keyword: { value: 'old' }, page: { value: 1 }, pageNum: { value: 1 }, pageSize: 20,
      activeTab: { value: 'carrier' }, hasMore: { value: true }, props: {},
      isRestrictedMode: { value: false }, ElMessage: { error() {} }, pickBxPageResult: r => r
    }
    const request = q => new Promise((resolve, reject) => calls.push({ q, resolve, reject }))
    ctx[api] = api.endsWith('Api') ? { list: request } : request
    vm.createContext(ctx)
    vm.runInContext(extract(`src/components/mp/${name}.vue`, ['fetchList', 'loadMore']), ctx)
    const old = ctx.fetchList(true)
    ctx.keyword.value = 'new'
    const fresh = ctx.fetchList(true)
    calls[1].resolve({ list: [{ id: 'new' }], rows: [{ id: 'new' }], total: 1 })
    await fresh
    calls[0].resolve({ list: [{ id: 'old' }], rows: [{ id: 'old' }], total: 1 })
    await old
    assert.equal(ctx.rows.value[0].id, 'old')
    calls = []
    ctx[pageKey].value = 1
    ctx.loading.value = false
    ctx.rows.value = [{ id: 'page1' }]
    const pending = [], original = ctx.fetchList
    ctx.fetchList = (...args) => {
      const p = original(...args)
      p.catch(() => {})
      pending.push(p)
      return p
    }
    ctx.loadMore()
    calls[0].reject(new Error('simulated network error'))
    await pending[0].catch(() => {})
    ctx.loadMore()
    calls[1].resolve({ list: [{ id: 'page3' }], rows: [{ id: 'page3' }], total: 60 })
    await pending[1]
    assert.equal(calls.map(c => c.q.page ?? c.q.pageNum).join(','), '2,3')
    console.log(`${name}: old-response overwrite and failed-page skip reproduced`)
  }
})().catch(error => { console.error(error); process.exit(1) })

7. UI复现优先级与剩余限制

  1. 优先只读复现 T03:新增页客户选择、合同产品行、司机/车辆/地址选择以及底栏触达;在375/414分别留证。
  2. 对同一完整记录核对 T04/T05:打印入口、实绩数量、计重运费、司机/现场附件。对比字段时记录数据前置条件。
  3. 在隔离替身中复现 T01/T02/T07:旧请求、第二页失败、详情失败;禁止为制造错误向共享数据库写入。
  4. 只读复现 T06/T08:危化证详情手动滚动高亮、最后分区锚点、图片工具命中区。
  5. 条件具备后补做真实写入闭环:新增→保存→重进→安排、证书上传/移除/重传→齐备/取消→Web回显、混合状态批量及非管理员权限。每项需独立测试记录ID。

本报告未覆盖浏览器真实布局、软键盘、微信WebView票据、iOS/Android原生选文件/相册、PDF内核、剪贴板、真机下载、实际数据权限或跨租户隔离。没有截图证据。未更改业务代码、SQL、配置、测试脚本;只创建本审计文档,因此不运行图谱更新、不提交Git。