test(运单资料文件包下载): 需求文档与 QE Fleet 全流程测试设计产物入库

- 需求 v1.0 定稿(md+docx)与 UI 原型入库;确认单判定 docx 为同文载体、安徽运八并行无冲突
- 全流程产物:分析 + 59 测试点 + 85 用例 + 测试数据 145 组;评审 3 阻断与覆盖缺口 GAP-001 已修复,裁决 PASS_WITH_FIX
- 导出 Excel(云效模型)+ XMind + 版本快照 v1;含 docx 生成脚本与转换校验产物
- .gitignore 排除 node_modules 与 Excel 锁文件
This commit is contained in:
xst
2026-09-09 09:31:55 +08:00
parent c7cc4d5648
commit 901b6741ff
48 changed files with 8183 additions and 0 deletions
@@ -0,0 +1,391 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>安徽运八 · 运单文件包(原型)</title>
<style>
* { box-sizing:border-box; margin:0; padding:0; }
body { font-family:"Microsoft YaHei","PingFang SC",sans-serif; background:#f5f7fa; color:#333; display:flex; height:100vh; overflow:hidden; }
.sidebar { width:210px; background:#1a1a2e; color:#fff; display:flex; flex-direction:column; flex-shrink:0; }
.sidebar h2 { padding:20px 16px 12px; font-size:14px; color:#e0e0e0; border-bottom:1px solid #2a2a4e; }
.sidebar .version { padding:0 16px 8px; font-size:10px; color:#666; }
.sidebar nav { flex:1; overflow-y:auto; padding:4px 0; }
.sidebar nav .nav-group { padding:12px 16px 4px; font-size:10px; color:#555; text-transform:uppercase; letter-spacing:1px; }
.sidebar nav a { display:flex; align-items:center; gap:10px; padding:10px 16px 10px 20px; color:#aaa; text-decoration:none; font-size:13px; transition:.15s; border-left:3px solid transparent; cursor:pointer; }
.sidebar nav a:hover { background:#16213e; color:#e0e0e0; border-left-color:#1677ff; }
.sidebar nav a.active { background:#16213e; color:#fff; border-left-color:#1677ff; font-weight:600; }
.sidebar nav a .badge { margin-left:auto; background:#1677ff; color:#fff; font-size:10px; padding:1px 6px; border-radius:8px; }
.main { flex:1; display:flex; flex-direction:column; overflow:hidden; }
.topbar { height:52px; background:#fff; border-bottom:1px solid #e8e8e8; display:flex; align-items:center; padding:0 24px; flex-shrink:0; gap:12px; }
.topbar .breadcrumb { font-size:12px; color:#999; }
.topbar h1 { font-size:15px; font-weight:600; }
.content { flex:1; overflow-y:auto; padding:20px 24px; }
/* Stat cards */
.stat-row { display:flex; gap:16px; margin-bottom:20px; }
.stat-card { background:#fff; border-radius:10px; padding:16px 20px; flex:1; display:flex; align-items:center; gap:12px; box-shadow:0 1px 4px rgba(0,0,0,.06); border:1px solid #f0f0f0; }
.stat-card .sc-icon { width:40px; height:40px; border-radius:10px; display:flex; align-items:center; justify-content:center; font-size:18px; }
.sc-icon.blue { background:#e6f4ff; }
.sc-icon.green { background:#f6ffed; }
.sc-icon.red { background:#fff2f0; }
.sc-icon.orange { background:#fff7e6; }
.stat-num { font-size:22px; font-weight:700; line-height:1.2; }
.stat-label { font-size:12px; color:#999; }
/* Upload card */
.upload-card { background:#fff; border-radius:10px; padding:18px 20px; margin-bottom:14px; box-shadow:0 1px 4px rgba(0,0,0,.06); border:1px solid #f0f0f0; }
.uc-head { display:flex; align-items:center; gap:12px; }
.uc-head h3 { font-size:14px; font-weight:600; }
.uc-head .uc-sub { font-size:12px; color:#999; }
.uc-actions { margin-left:auto; display:flex; gap:10px; }
.hintbox { margin-top:12px; background:#fff7e6; border:1px solid #ffd591; border-radius:6px; padding:10px 14px; font-size:12px; color:#ad6800; line-height:1.8; }
.hintbox b { color:#ad6800; }
.steps { display:flex; align-items:center; gap:8px; flex-wrap:wrap; margin-top:12px; }
.step { background:#f0f7ff; border:1px solid #bfdcff; color:#1677ff; font-size:12px; padding:4px 12px; border-radius:14px; }
.step-arrow { color:#bbb; font-size:12px; }
/* Table */
.tw { background:#fff; border-radius:10px; overflow:hidden; box-shadow:0 1px 4px rgba(0,0,0,.06); border:1px solid #f0f0f0; }
.tw table { width:100%; border-collapse:collapse; font-size:13px; }
.tw th { background:#fafafa; padding:10px 12px; text-align:left; font-weight:600; color:#555; border-bottom:2px solid #e8e8e8; white-space:nowrap; position:sticky; top:0; z-index:1; }
.tw td { padding:10px 12px; border-bottom:1px solid #f5f5f5; }
.tw tbody tr:hover { background:#fafafa; }
/* Tags */
.tag { display:inline-block; padding:2px 8px; border-radius:4px; font-size:11px; font-weight:500; line-height:1.5; white-space:nowrap; }
.tag-blue { background:#e6f4ff; color:#1677ff; }
.tag-green { background:#f6ffed; color:#52c41a; }
.tag-red { background:#fff2f0; color:#f5222d; }
.tag-orange { background:#fff7e6; color:#fa8c16; }
.tag-gray { background:#f5f5f5; color:#999; }
/* Action buttons */
.acts { display:flex; gap:6px; flex-wrap:wrap; }
.bxs { height:28px; padding:0 10px; border:1px solid #d9d9d9; border-radius:6px; background:#fff; cursor:pointer; font-size:12px; transition:.15s; white-space:nowrap; }
.bxs:hover { border-color:#1677ff; color:#1677ff; }
.bxs.pri { background:#1677ff; color:#fff; border-color:#1677ff; }
.bxs.pri:hover { background:#0958d9; }
.bxs:disabled { color:#ccc; border-color:#f0f0f0; background:#fafafa; cursor:not-allowed; }
.qb { height:34px; padding:0 16px; border:1px solid #d9d9d9; border-radius:6px; background:#fff; cursor:pointer; font-size:13px; transition:.15s; }
.qb:hover { border-color:#1677ff; color:#1677ff; }
.qb.pri { background:#1677ff; color:#fff; border-color:#1677ff; }
.qb.pri:hover { background:#0958d9; }
/* Progress bar */
.progbar { width:110px; height:6px; background:#f0f0f0; border-radius:3px; overflow:hidden; display:inline-block; vertical-align:middle; margin-right:6px; }
.progbar i { display:block; height:100%; background:#1677ff; border-radius:3px; }
/* Pagination */
.pg { display:flex; justify-content:space-between; align-items:center; padding:14px 16px; font-size:12px; color:#888; }
.pg-info b { color:#333; }
.pgb { display:flex; gap:4px; }
.pgbtn { min-width:30px; height:30px; border:1px solid #d9d9d9; border-radius:6px; background:#fff; cursor:pointer; font-size:12px; transition:.15s; }
.pgbtn:hover { border-color:#1677ff; color:#1677ff; }
.pgbtn.ac { background:#1677ff; color:#fff; border-color:#1677ff; }
.pgbtn:disabled { color:#ccc; cursor:not-allowed; }
/* Modal */
.mm { display:none; position:fixed; inset:0; background:rgba(0,0,0,.45); z-index:1000; justify-content:center; align-items:flex-start; padding-top:50px; }
.mm.active { display:flex; }
.md { background:#fff; border-radius:12px; width:860px; max-height:85vh; overflow-y:auto; box-shadow:0 12px 48px rgba(0,0,0,.18); }
.mdh { padding:18px 24px; border-bottom:1px solid #e8e8e8; display:flex; align-items:center; justify-content:space-between; position:sticky; top:0; background:#fff; z-index:2; border-radius:12px 12px 0 0; }
.mdh h3 { font-size:15px; }
.mdb { padding:20px 24px; }
.mdf { padding:14px 24px; border-top:1px solid #e8e8e8; display:flex; justify-content:flex-end; gap:10px; position:sticky; bottom:0; background:#fff; border-radius:0 0 12px 12px; }
.cls { width:32px; height:32px; border:none; background:transparent; font-size:18px; cursor:pointer; border-radius:6px; }
.cls:hover { background:#f5f5f5; }
/* Modal sections & fields */
.so { margin-bottom:18px; border:1px solid #f0f0f0; border-radius:8px; overflow:hidden; }
.soh { font-size:13px; font-weight:600; padding:10px 14px; background:#fafafa; border-bottom:1px solid #f0f0f0; display:flex; align-items:center; gap:8px; }
.soc { padding:12px 14px; }
.fg { display:grid; grid-template-columns:repeat(2,1fr); gap:4px 20px; }
.fi { display:flex; gap:6px; font-size:12px; padding:3px 0; }
.fl { color:#999; min-width:100px; flex-shrink:0; }
.fv { color:#333; word-break:break-all; }
.af-note { background:#fff7e6; border:1px solid #ffd591; border-radius:6px; padding:10px 14px; font-size:12px; color:#ad6800; margin-bottom:16px; line-height:1.8; }
/* Drop zone */
.drop { border:2px dashed #d9d9d9; border-radius:10px; padding:34px; text-align:center; color:#999; font-size:13px; cursor:pointer; transition:.15s; }
.drop:hover { border-color:#1677ff; color:#1677ff; background:#f0f7ff; }
.drop.has { border-color:#52c41a; color:#333; background:#f6ffed; }
.drop.has .dname { color:#52c41a; font-weight:600; }
/* Package tree (dark code block) */
.tree { background:#0f172a; color:#cbd5e1; border-radius:8px; padding:14px 18px; font-family:"Consolas","Monaco",monospace; font-size:12px; line-height:1.9; overflow-x:auto; white-space:pre; }
.tree .hl { color:#7dd3fc; font-weight:600; }
.tree .cred { color:#86efac; }
.tree .dim { color:#64748b; }
/* Toast */
.toast { position:fixed; top:20px; left:50%; transform:translateX(-50%); background:#333; color:#fff; padding:10px 24px; border-radius:8px; font-size:13px; z-index:2000; opacity:0; transition:opacity .3s; pointer-events:none; }
.toast.show { opacity:1; }
.mono { font-family:"Consolas","Monaco",monospace; font-size:12px; }
.te { max-width:180px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; display:inline-block; }
</style>
</head>
<body>
<div class="toast" id="toast"></div>
<!-- Sidebar -->
<div class="sidebar">
<h2>安徽运八税务上报</h2>
<div class="version">原型 · 仅演示运单文件包</div>
<nav>
<div class="nav-group">资料管理</div>
<a class="active">📦 运单文件包<span class="badge">新</span></a>
</nav>
</div>
<!-- Main -->
<div class="main">
<div class="topbar">
<span class="breadcrumb">运单管理</span>
<span class="breadcrumb">/</span>
<h1>运单文件包</h1>
</div>
<div class="content">
<!-- 统计卡片 -->
<div class="stat-row">
<div class="stat-card"><div class="sc-icon blue">📦</div><div><div class="stat-num" style="color:#1677ff;" id="stTotal">26</div><div class="stat-label">全部任务</div></div></div>
<div class="stat-card"><div class="sc-icon orange">⏳</div><div><div class="stat-num" style="color:#fa8c16;">1</div><div class="stat-label">处理中</div></div></div>
<div class="stat-card"><div class="sc-icon green">✅</div><div><div class="stat-num" style="color:#52c41a;">20</div><div class="stat-label">已完成</div></div></div>
<div class="stat-card"><div class="sc-icon orange">🟡</div><div><div class="stat-num" style="color:#fa8c16;">4</div><div class="stat-label">已完成·部分失败</div></div></div>
<div class="stat-card"><div class="sc-icon red">⚠️</div><div><div class="stat-num" style="color:#f5222d;">1</div><div class="stat-label">失败</div></div></div>
</div>
<!-- 上传卡片 -->
<div class="upload-card">
<div class="uc-head">
<h3>上传运单清单</h3>
<span class="uc-sub">上传后自动创建打包任务,任务完成后可下载文件包</span>
<div class="uc-actions">
<button class="qb" onclick="toastMsg('下载模板:运单清单模板.xlsx(序号 / 货源单号 / 运单号)')">⬇️ 下载模板</button>
<button class="qb pri" onclick="openModal('mm-upload')">📤 上传清单</button>
</div>
</div>
<div class="hintbox">
<b>模板规则:</b>仅含三列 —— 序号(正整数、全文件唯一)、货源单号、运单号;单文件不超过 <b>2000 行</b>、大小不超过 <b>10MB</b>;仅支持 .xlsx。
</div>
<div class="steps">
<span class="step">① 上传清单</span><span class="step-arrow">→</span>
<span class="step">② 生成打包任务</span><span class="step-arrow">→</span>
<span class="step">③ 后台按批次打包</span><span class="step-arrow">→</span>
<span class="step">④ 任务完成后下载文件包</span>
</div>
</div>
<!-- 任务列表 -->
<div class="tw">
<table>
<thead><tr>
<th>任务编号</th><th>源文件名</th><th>上传人</th><th>上传时间</th>
<th>任务状态</th><th>总行数</th><th>成功</th><th>失败</th><th>完成时间</th><th>操作</th>
</tr></thead>
<tbody id="taskBody">
<tr>
<td class="mono">RW20260908001</td>
<td><span class="te" title="运单清单_合肥仓0908.xlsx">运单清单_合肥仓0908.xlsx</span></td>
<td>王运营</td><td class="mono">2026-09-08 10:32</td>
<td><span class="tag tag-green">已完成</span></td>
<td>3</td><td>3</td><td>0</td><td class="mono">2026-09-08 10:35</td>
<td><div class="acts">
<button class="bxs pri" onclick="toastMsg('开始下载:运单资料_RW20260908001.zip(含 2 个批次文件包)')">下载文件包</button>
<button class="bxs" onclick="openModal('mm-tree')">查看结构</button>
</div></td>
</tr>
<tr>
<td class="mono">RW20260907002</td>
<td><span class="te" title="运单清单_0907批.xlsx">运单清单_0907批.xlsx</span></td>
<td>李运营</td><td class="mono">2026-09-07 16:20</td>
<td><span class="tag tag-orange">已完成·部分失败</span></td>
<td>5</td><td>3</td><td>2</td><td class="mono">2026-09-07 16:26</td>
<td><div class="acts">
<button class="bxs pri" onclick="toastMsg('开始下载:运单资料_RW20260907002.zip(含 2 个批次文件包)')">下载文件包</button>
<button class="bxs warn" style="color:#fa8c16;border-color:#fa8c16;" onclick="openModal('mm-fail')">失败清单</button>
<button class="bxs" onclick="openModal('mm-tree')">查看结构</button>
</div></td>
</tr>
<tr>
<td class="mono">RW20260906003</td>
<td><span class="te" title="运单清单_0906批.xlsx">运单清单_0906批.xlsx</span></td>
<td>王运营</td><td class="mono">2026-09-06 09:10</td>
<td><span class="tag tag-blue">处理中</span></td>
<td>200</td><td colspan="2"><span class="progbar"><i style="width:93%;"></i></span><span class="mono">186/200</span></td>
<td>—</td>
<td><div class="acts"><button class="bxs" onclick="toastMsg('任务处理中,完成后操作列出现「下载文件包」按钮')">结构预览</button></div></td>
</tr>
<tr>
<td class="mono">RW20260905004</td>
<td><span class="te" title="运单清单_0905批.xlsx">运单清单_0905批.xlsx</span></td>
<td>张运营</td><td class="mono">2026-09-05 14:00</td>
<td><span class="tag tag-gray">待处理</span></td>
<td>120</td><td>—</td><td>—</td><td>—</td>
<td><div class="acts"><button class="bxs" disabled>等待处理</button></div></td>
</tr>
<tr>
<td class="mono">RW20260904005</td>
<td><span class="te" title="运单清单_0904批.xlsx">运单清单_0904批.xlsx</span></td>
<td>李运营</td><td class="mono">2026-09-04 11:26</td>
<td><span class="tag tag-red">失败</span></td>
<td>80</td><td>0</td><td>0</td><td>—</td>
<td><div class="acts"><button class="bxs" onclick="delTask(this)">删除</button></div></td>
</tr>
<tr>
<td class="mono">RW20260830006</td>
<td><span class="te" title="运单清单_0830批.xlsx">运单清单_0830批.xlsx</span></td>
<td>王运营</td><td class="mono">2026-08-30 15:41</td>
<td><span class="tag tag-gray">已过期</span></td>
<td>66</td><td>66</td><td>0</td><td class="mono">2026-08-30 15:58</td>
<td><div class="acts"><button class="bxs" onclick="toastMsg('文件包已超过 7 天保留期,请重新上传清单生成')">下载文件包</button></div></td>
</tr>
</tbody>
</table>
<div class="pg">
<span class="pg-info">共 <b>26</b> 条任务,当前显示 1-6</span>
<div class="pgb">
<button class="pgbtn" disabled>«</button>
<button class="pgbtn ac">1</button>
<button class="pgbtn">2</button>
<button class="pgbtn">3</button>
<button class="pgbtn">»</button>
</div>
</div>
</div>
</div>
</div>
<!-- 上传弹窗 -->
<div class="mm" id="mm-upload">
<div class="md" style="width:640px;">
<div class="mdh"><h3>上传运单清单</h3><button class="cls" onclick="closeModal('mm-upload')">✕</button></div>
<div class="mdb">
<div class="af-note">
<b>操作说明:</b>① 下载模板并按格式填写(序号、货源单号、运单号)→ ② 上传文件,系统校验通过后创建打包任务 → ③ 任务完成后在列表中下载文件包。<br>
文件级校验不通过(格式/列名/超限)将直接拒绝;数据级错误行剔除后继续处理,任务记为“已完成·部分失败”。
</div>
<div class="drop" id="drop" onclick="pickFile()">
<div id="dropText">点击选择或拖拽 .xlsx 文件到此处</div>
<div style="font-size:11px;color:#bbb;margin-top:6px;">仅支持 .xlsx · ≤2000 行 · ≤10MB</div>
</div>
</div>
<div class="mdf">
<button class="qb" onclick="closeModal('mm-upload')">取消</button>
<button class="qb pri" id="btnUpload" disabled onclick="doUpload()">开始上传并创建任务</button>
</div>
</div>
</div>
<!-- 文件包结构预览弹窗 -->
<div class="mm" id="mm-tree">
<div class="md">
<div class="mdh"><h3>任务详情 · RW20260908001(已完成)</h3><button class="cls" onclick="closeModal('mm-tree')">✕</button></div>
<div class="mdb">
<div class="so">
<div class="soh">📦 任务信息</div>
<div class="soc"><div class="fg">
<div class="fi"><span class="fl">源文件名</span><span class="fv">运单清单_合肥仓0908.xlsx</span></div>
<div class="fi"><span class="fl">上传人 / 时间</span><span class="fv">王运营 / 2026-09-08 10:32</span></div>
<div class="fi"><span class="fl">处理结果</span><span class="fv">总行数 3 · 成功 3 · 失败 0</span></div>
<div class="fi"><span class="fl">批次划分</span><span class="fv">2 个批次(司机+身份证+车辆 三要素归批)</span></div>
</div></div>
</div>
<div class="so">
<div class="soh">🗂️ 文件包结构预览(<span class="mono">运单资料_RW20260908001.zip</span>)</div>
<div class="soc">
<div class="tree"><span class="hl">运单资料_RW20260908001.zip</span> <span class="dim">← 任务下载总包</span>
├─ <span class="cred">张三-340102199001011234-皖A12345/</span> <span class="dim">← 批次文件包①</span>
│ ├─ <span class="hl">1-YD20260901001-HY202609001-含司机及车辆证件/</span> <span class="dim">← 最小序号运单包</span>
│ │ ├─ 合同/(该运单全部合同文件)
│ │ ├─ 电子回单/(该运单全部支付记录对应回单)
│ │ ├─ 司机证件/(身份证、驾驶证、从业资格证等照片)
│ │ └─ 车辆证件/(行驶证、道路运输证等照片)
│ └─ 3-YD20260901003-HY202609001/ <span class="dim">← 其余运单包:仅合同+回单</span>
│ ├─ 合同/
│ └─ 电子回单/
└─ <span class="cred">李四-340102199001015678-皖B67890/</span> <span class="dim">← 批次文件包②</span>
└─ <span class="hl">2-YD20260901002-HY202609002-含司机及车辆证件/</span> <span class="dim">← 单运单批次,仍含证件</span></div>
<div style="font-size:12px;color:#999;margin-top:10px;line-height:1.8;">
说明:同一批次内证件相同,仅随<span style="color:#1677ff;font-weight:600;">最小序号运单包</span>导出一份司机及车辆证件照片(包名含“-含司机及车辆证件”后缀);其余运单包仅含合同与电子回单。
</div>
</div>
</div>
</div>
<div class="mdf">
<button class="qb" onclick="closeModal('mm-tree')">关闭</button>
<button class="qb pri" onclick="toastMsg('开始下载:运单资料_RW20260908001.zip(含 2 个批次文件包)')">⬇️ 下载文件包</button>
</div>
</div>
</div>
<!-- 失败清单弹窗 -->
<div class="mm" id="mm-fail">
<div class="md" style="width:720px;">
<div class="mdh"><h3>失败清单 · RW20260907002(已完成·部分失败)</h3><button class="cls" onclick="closeModal('mm-fail')">✕</button></div>
<div class="mdb">
<div class="af-note">处理原则:失败行已剔除,其余 3 行已正常打包;修正数据后可重新上传。</div>
<div class="tw" style="box-shadow:none;">
<table>
<thead><tr><th>序号</th><th>货源单号</th><th>运单号</th><th>失败原因</th></tr></thead>
<tbody>
<tr><td>2</td><td class="mono">HY202609002</td><td class="mono">YD20260902011</td><td>运单号在系统中不存在</td></tr>
<tr><td>4</td><td class="mono">HY202609003</td><td class="mono">YD20260902013</td><td>文件中货源单号与该运单实际所属货源不一致</td></tr>
</tbody>
</table>
</div>
</div>
<div class="mdf">
<button class="qb" onclick="closeModal('mm-fail')">关闭</button>
<button class="qb pri" onclick="toastMsg('开始下载:失败清单_RW20260907002.xlsx')">⬇️ 下载失败清单</button>
</div>
</div>
</div>
<script>
function toastMsg(msg) {
const t = document.getElementById('toast');
t.textContent = msg;
t.classList.add('show');
clearTimeout(t._timer);
t._timer = setTimeout(() => t.classList.remove('show'), 2400);
}
function openModal(id) { document.getElementById(id).classList.add('active'); }
function closeModal(id) { document.getElementById(id).classList.remove('active'); }
document.querySelectorAll('.mm').forEach(m => m.addEventListener('click', e => { if (e.target === m) m.classList.remove('active'); }));
function pickFile() {
const drop = document.getElementById('drop');
drop.classList.add('has');
document.getElementById('dropText').innerHTML = '已选择:<span class="dname">运单清单_0908批.xlsx</span>(12 KB · 3 行)';
document.getElementById('btnUpload').disabled = false;
}
function doUpload() {
closeModal('mm-upload');
const drop = document.getElementById('drop');
drop.classList.remove('has');
document.getElementById('dropText').textContent = '点击选择或拖拽 .xlsx 文件到此处';
document.getElementById('btnUpload').disabled = true;
const tbody = document.getElementById('taskBody');
const tr = document.createElement('tr');
tr.innerHTML = '<td class="mono">RW20260908002</td>' +
'<td><span class="te" title="运单清单_0908批.xlsx">运单清单_0908批.xlsx</span></td>' +
'<td>当前用户</td><td class="mono">2026-09-08 15:35</td>' +
'<td><span class="tag tag-blue">处理中</span></td>' +
'<td>3</td><td colspan="2"><span class="progbar"><i style="width:8%;"></i></span><span class="mono">0/3</span></td><td>—</td>' +
'<td><div class="acts"><button class="bxs" disabled>等待处理</button></div></td>';
tbody.insertBefore(tr, tbody.firstChild);
const total = document.getElementById('stTotal');
total.textContent = parseInt(total.textContent, 10) + 1;
toastMsg('任务创建成功:RW20260908002,已开始后台处理');
}
function delTask(btn) {
btn.closest('tr').remove();
toastMsg('已删除失败任务 RW20260904005');
}
</script>
</body>
</html>