Files
Yb-QaAutomationHub/requirements/prototypes/物流公司管理原型.html
T
xst 8c61209dd4 test(物流公司管理需求): 需求文档与 QE Fleet 全流程设计产物入库
- 需求 v1.1 + 2026-09-29 需求方确认的 Q7~Q19 决议(含 BR-07/BR-09/6.7/6.9 矛盾表述修订)
- 测试点 159 个、测试用例 164 条(P0 55/P1 81/P2 23/P3 5),需求→测试点→用例三级追溯覆盖率 100%
- 质量裁决 PASS(首轮 PASS_WITH_FIX,13 项待确认确认后复裁决升级);评审原始 2 阻断项+9 建议项+3 优化项已全部修复
- 确认单:与 4 个历史需求均为并行关系、0 冲突、0 回写
- 附导出 v1/v2 版本快照、Playwright/Appium 脚本骨架、覆盖率审计脚本与指标、评审修复脚本
2026-09-29 10:03:35 +08:00

1333 lines
78 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>物流公司管理 - 交互原型</title>
<style>
* { margin:0; padding:0; box-sizing:border-box; }
body { font-family:"Helvetica Neue",Helvetica,"PingFang SC","Hiragino Sans GB","Microsoft YaHei",Arial,sans-serif; font-size:14px; color:#333; background:#f0f2f5; }
a { text-decoration:none; }
.app { display:flex; min-height:100vh; }
/* ===== 侧边栏 ===== */
.sidebar { width:200px; background:#263445; color:#bfcbd9; flex-shrink:0; min-height:100vh; overflow-y:auto; }
.logo { height:56px; display:flex; align-items:center; justify-content:center; color:#fff; font-size:16px; font-weight:bold; background:#1f2d3d; letter-spacing:1px; }
.menu { list-style:none; padding-bottom:40px; }
.menu > li { line-height:44px; }
.menu .mi { display:block; padding:0 20px; cursor:pointer; white-space:nowrap; }
.menu .mi:hover { background:#1f2d3d; }
.menu .mi .arrow { float:right; font-size:12px; color:#8a9bb0; }
.submenu { list-style:none; background:#1f2d3d; display:none; }
.submenu.open { display:block; }
.submenu li { padding-left:34px; line-height:40px; cursor:pointer; white-space:nowrap; }
.submenu li:hover { color:#fff; }
.submenu li.active { color:#409eff; }
.submenu .sub2 { list-style:none; background:#1a2433; display:none; }
.submenu .sub2.open { display:block; }
.submenu .sub2 li { padding-left:50px; line-height:36px; font-size:13px; }
/* ===== 主区域 ===== */
.main { flex:1; display:flex; flex-direction:column; min-width:0; }
.topbar { height:50px; background:#fff; display:flex; align-items:center; padding:0 16px; border-bottom:1px solid #e6e6e6; box-shadow:0 1px 4px rgba(0,21,41,.08); position:relative; z-index:5; }
.topbar .fold { font-size:18px; cursor:pointer; color:#5a5e66; margin-right:12px; }
.crumb { color:#97a8be; font-size:13px; }
.crumb b { color:#333; font-weight:normal; }
.crumb .sep { margin:0 6px; }
.topbar .right { margin-left:auto; display:flex; align-items:center; gap:16px; color:#5a5e66; }
.avatar { width:30px; height:30px; border-radius:50%; background:#409eff; color:#fff; display:inline-flex; align-items:center; justify-content:center; font-size:12px; }
.tabsbar { background:#fff; padding:6px 12px 0; border-bottom:1px solid #e6e6e6; display:flex; gap:6px; flex-wrap:wrap; }
.chip { background:#fff; border:1px solid #d8dce5; color:#495060; padding:4px 10px; font-size:12px; border-radius:2px; cursor:pointer; margin-bottom:6px; display:inline-flex; align-items:center; gap:6px; }
.chip .x { color:#9aa2b1; font-size:13px; }
.chip.active { background:#67c23a; border-color:#67c23a; color:#fff; }
.chip.active .x { color:#e6f7dd; }
.demo-strip { background:#fdf6ec; color:#b88230; font-size:12px; padding:5px 16px; border-bottom:1px solid #faecd8; }
.content { flex:1; padding:14px; }
/* ===== 卡片 / 页面 ===== */
.page { display:none; }
.page.active { display:block; }
.card { background:#fff; border-radius:4px; padding:18px; box-shadow:0 1px 2px rgba(0,0,0,.03); margin-bottom:14px; }
.page-title { font-size:15px; font-weight:bold; color:#303133; margin-bottom:14px; padding-left:8px; border-left:3px solid #409eff; }
.section-title { font-size:14px; color:#303133; font-weight:bold; margin:6px 0 12px; }
/* ===== 按钮 ===== */
.btn { display:inline-block; padding:8px 16px; font-size:13px; border-radius:3px; border:1px solid #dcdfe6; background:#fff; color:#606266; cursor:pointer; outline:none; }
.btn:hover { opacity:.9; }
.btn.primary { background:#409eff; border-color:#409eff; color:#fff; }
.btn.success { background:#67c23a; border-color:#67c23a; color:#fff; }
.btn.danger { background:#f56c6c; border-color:#f56c6c; color:#fff; }
.btn.mini { padding:4px 10px; font-size:12px; }
.btn:disabled { opacity:.5; cursor:not-allowed; }
.link { color:#409eff; cursor:pointer; margin-right:10px; font-size:13px; }
.link.danger { color:#f56c6c; }
.btn-row { margin-top:18px; padding-top:14px; border-top:1px solid #ebeef5; }
.btn-row .btn { margin-right:10px; }
/* ===== 表单 ===== */
.form-grid { display:grid; grid-template-columns:1fr 1fr; gap:2px 40px; max-width:1150px; }
.form-item { display:flex; align-items:flex-start; padding:10px 0; }
.form-item.full { grid-column:1 / -1; }
.form-item label { width:150px; text-align:right; padding-right:10px; line-height:32px; color:#606266; flex-shrink:0; font-size:13px; }
.req::before { content:"*"; color:#f56c6c; margin-right:3px; }
.form-item .ctrl { flex:1; min-width:0; }
.input, .select, .textarea { width:100%; height:32px; border:1px solid #dcdfe6; border-radius:3px; padding:0 10px; font-size:13px; color:#606266; outline:none; background:#fff; }
.input:focus, .select:focus, .textarea:focus { border-color:#409eff; }
.input.err, .select.err { border-color:#f56c6c; }
.textarea { height:64px; padding:6px 10px; resize:vertical; }
.tip { color:#909399; font-size:12px; line-height:32px; margin-left:8px; white-space:nowrap; }
.err-msg { color:#f56c6c; font-size:12px; line-height:1.4; margin-top:2px; display:none; }
.form-item.has-err .err-msg { display:block; }
/* ===== 上传证件框 ===== */
.upload-row { display:flex; gap:22px; flex-wrap:wrap; margin-bottom:8px; }
.upload-box { width:150px; cursor:pointer; }
.up-frame { width:150px; height:150px; border:1px dashed #c0c4cc; border-radius:4px; display:flex; align-items:center; justify-content:center; color:#c0c4cc; position:relative; background:#fafbfc; }
.up-frame .plus { font-size:44px; font-weight:200; }
.up-frame.done { border-style:solid; border-color:#67c23a; background:#f0f9eb; color:#67c23a; flex-direction:column; font-size:12px; gap:6px; }
.up-frame.done .plus { display:none; }
.up-label { text-align:center; margin-top:8px; color:#606266; font-size:13px; }
.up-label.req::before { content:"*"; color:#f56c6c; margin-right:3px; }
/* ===== 筛选区 / 表格 ===== */
.filter-bar { display:flex; flex-wrap:wrap; gap:10px 14px; align-items:center; margin-bottom:14px; }
.filter-bar .fi { display:flex; align-items:center; gap:6px; }
.filter-bar .input, .filter-bar .select { width:180px; }
.filter-bar .input.date { width:145px; }
.btn-bar { display:flex; gap:10px; margin-bottom:14px; }
.table-wrap { overflow-x:auto; }
table.tbl { width:100%; border-collapse:collapse; font-size:13px; }
.tbl th, .tbl td { border-bottom:1px solid #ebeef5; padding:10px 8px; text-align:left; color:#606266; white-space:nowrap; }
.tbl th { background:#f8f8f9; color:#515a6e; font-weight:bold; }
.tbl tbody tr:nth-child(even) { background:#fafbfc; }
.tbl tbody tr:hover { background:#f5f7fa; }
.tbl .name-link { color:#409eff; cursor:pointer; }
.tbl td .mini-input { width:110px; height:28px; border:1px solid #dcdfe6; border-radius:3px; padding:0 8px; font-size:12px; color:#909399; }
.tag { display:inline-block; padding:2px 8px; font-size:12px; border-radius:3px; line-height:18px; }
.tag.green { background:#f0f9eb; color:#67c23a; border:1px solid #e1f3d8; }
.tag.orange { background:#fdf6ec; color:#e6a23c; border:1px solid #faecd8; }
.tag.gray { background:#f4f4f5; color:#909399; border:1px solid #e9e9eb; }
.tag.blue { background:#ecf5ff; color:#409eff; border:1px solid #d9ecff; }
.tag.red { background:#fef0f0; color:#f56c6c; border:1px solid #fde2e2; }
.tbl tr.frow th { padding:6px 4px; }
.frow .input, .frow .select { height:28px; font-size:12px; color:#909399; }
.warn-txt { color:#e6a23c; font-size:12px; }
/* 开关 */
.switch { display:inline-block; width:36px; height:18px; border-radius:9px; background:#dcdfe6; position:relative; cursor:pointer; vertical-align:middle; transition:background .2s; }
.switch::after { content:""; position:absolute; top:2px; left:2px; width:14px; height:14px; border-radius:50%; background:#fff; transition:left .2s; }
.switch.on { background:#409eff; }
.switch.on::after { left:20px; }
/* 分页 */
.pagination { display:flex; align-items:center; justify-content:flex-end; gap:8px; margin-top:14px; font-size:13px; color:#606266; }
.pg-btn { min-width:28px; height:28px; border:1px solid #dcdfe6; border-radius:3px; background:#fff; color:#606266; cursor:pointer; font-size:12px; }
.pg-btn.cur { background:#409eff; border-color:#409eff; color:#fff; }
.pagination select { height:28px; border:1px solid #dcdfe6; border-radius:3px; }
/* Tab */
.tab-nav { display:flex; gap:2px; background:#fff; padding:0 18px; border-bottom:1px solid #e4e7ed; border-radius:4px 4px 0 0; }
.tab-item { padding:12px 20px; cursor:pointer; color:#303133; font-size:14px; border-bottom:2px solid transparent; }
.tab-item:hover { color:#409eff; }
.tab-item.active { color:#409eff; border-bottom-color:#409eff; font-weight:bold; }
.tab-panel { display:none; }
.tab-panel.active { display:block; }
/* 弹窗 */
.mask { display:none; position:fixed; inset:0; background:rgba(0,0,0,.5); z-index:100; align-items:flex-start; justify-content:center; }
.mask.open { display:flex; }
.dialog { background:#fff; border-radius:4px; min-width:560px; max-width:900px; margin-top:80px; box-shadow:0 4px 20px rgba(0,0,0,.2); }
.dialog .dlg-head { display:flex; align-items:center; padding:14px 18px; border-bottom:1px solid #ebeef5; font-weight:bold; color:#303133; }
.dialog .dlg-head .close { margin-left:auto; cursor:pointer; color:#909399; font-size:18px; line-height:1; }
.dialog .dlg-body { padding:22px 26px 6px; max-height:60vh; overflow-y:auto; }
.dialog .dlg-foot { padding:12px 18px 16px; text-align:right; }
.dialog .dlg-foot .btn { margin-left:10px; min-width:70px; }
.dlg-form .form-item label { width:110px; }
/* 已选公司 / 上传按钮 */
.pick-summary { display:flex; align-items:center; gap:10px; margin:12px 0 6px; }
.pick-summary .count { color:#409eff; font-weight:bold; }
.upload-btn { display:inline-block; padding:7px 14px; background:#409eff; color:#fff; border-radius:3px; font-size:13px; cursor:pointer; }
.file-name { margin-left:10px; color:#67c23a; font-size:13px; }
.hint { color:#909399; font-size:12px; margin-top:8px; }
/* Toast */
#toast-wrap { position:fixed; top:60px; left:50%; transform:translateX(-50%); z-index:200; display:flex; flex-direction:column; gap:10px; align-items:center; }
.toast { padding:9px 18px; border-radius:4px; color:#fff; font-size:13px; box-shadow:0 2px 12px rgba(0,0,0,.15); animation:fadein .2s; }
.toast.success { background:#67c23a; }
.toast.error { background:#f56c6c; }
.toast.warning { background:#e6a23c; }
@keyframes fadein { from { opacity:0; transform:translateY(-8px);} to { opacity:1; transform:none;} }
.empty { text-align:center; color:#909399; padding:30px 0; font-size:13px; }
.home-cards { display:flex; gap:14px; flex-wrap:wrap; }
.stat-card { background:#fff; border-radius:4px; padding:18px 22px; min-width:200px; box-shadow:0 1px 2px rgba(0,0,0,.03); }
.stat-card .num { font-size:24px; font-weight:bold; color:#303133; margin-top:6px; }
.stat-card .lbl { color:#909399; font-size:13px; }
.stat-card .num.blue { color:#409eff; }
.stat-card .num.green { color:#67c23a; }
</style>
</head>
<body>
<div class="app">
<!-- ===== 侧边栏 ===== -->
<aside class="sidebar">
<div class="logo">Ⓦ 运八网络货运平台系统</div>
<ul class="menu">
<li><span class="mi" onclick="go('home')">✈ 首页</span></li>
<li>
<span class="mi" onclick="toggleMenu('m-basic',this)">👤 基础信息 <span class="arrow">⌃</span></span>
<ul class="submenu open" id="m-basic">
<li onclick="toggleMenu('m-platform',this)">平台管理 ⌃
<ul class="sub2" id="m-platform"><li onclick="todo()">账号管理</li><li onclick="todo()">角色管理</li></ul>
</li>
<li onclick="toggleMenu('m-owner',this)">货主管理 ⌃
<ul class="sub2" id="m-owner">
<li onclick="todo()">个体货主</li><li onclick="todo()">货主信息</li><li onclick="todo()">企业货主</li><li onclick="todo()">企业信息</li><li onclick="todo()">分账记录</li>
</ul>
</li>
<li onclick="toggleMenu('m-carrier',this)">承运管理 ⌃
<ul class="sub2 open" id="m-carrier">
<li onclick="todo()">车队管理</li><li onclick="todo()">司机管理</li><li onclick="todo()">平台车辆</li>
<li class="active" onclick="go('list')">物流公司管理 <span style="color:#67c23a;font-size:11px;">NEW</span></li>
</ul>
</li>
<li onclick="toggleMenu('m-veh',this)">车辆服务 ⌃
<ul class="sub2" id="m-veh"><li onclick="todo()">车辆保险</li><li onclick="todo()">车辆维修</li></ul>
</li>
<li onclick="todo()">数字字典</li>
<li onclick="todo()">银行管理</li>
<li onclick="todo()">审核管理</li>
<li onclick="todo()">商户中心</li>
<li onclick="todo()">账户管理</li>
<li onclick="todo()">彩虹糖</li>
</ul>
</li>
<li>
<span class="mi" onclick="toggleMenu('m-tms',this)">🚚 整车运输 <span class="arrow">⌃</span></span>
<ul class="submenu" id="m-tms">
<li onclick="toggleMenu('m-goods',this)">货源管理 ⌃
<ul class="sub2" id="m-goods">
<li onclick="go('source')">发布货源</li>
<li onclick="go('goodslist')">货源管理 <span style="color:#67c23a;font-size:11px;">NEW</span></li>
<li onclick="todo()">货源大厅</li>
<li onclick="todo()">项目货源</li>
<li onclick="todo()">货源审核</li>
</ul>
</li>
<li onclick="toggleMenu('m-trans',this)">运输管理 ⌃
<ul class="sub2" id="m-trans">
<li onclick="todo()">运单跟踪</li>
<li onclick="go('waybill')">运单管理 <span style="color:#67c23a;font-size:11px;">NEW</span></li>
<li onclick="todo()">运单轨迹</li>
</ul>
</li>
<li onclick="toggleMenu('m-tms-settle',this)">承运结算 ⌃
<ul class="sub2" id="m-tms-settle">
<li onclick="go('settle')">运费核算 <span style="color:#67c23a;font-size:11px;">NEW</span></li>
<li onclick="gotoPay('owner')">货主打款</li>
<li onclick="gotoPay('finance')">财务打款</li>
<li onclick="todo()">打款记录</li>
</ul>
</li>
</ul>
</li>
</ul>
</aside>
<!-- ===== 主区域 ===== -->
<div class="main">
<header class="topbar">
<span class="fold">☰</span>
<span class="crumb" id="crumb"></span>
<div class="right">
<span title="搜索" style="cursor:pointer;">🔍</span>
<span title="全屏" style="cursor:pointer;">⛶</span>
<span class="avatar">管</span><span style="font-size:13px;">超级管理员</span>
</div>
</header>
<nav class="tabsbar" id="tabsbar">
<span class="chip" data-page="home" onclick="go('home')">首页 <span class="x">×</span></span>
<span class="chip" data-page="list" onclick="go('list')">物流公司列表 <span class="x">×</span></span>
<span class="chip" data-page="add" onclick="go('add')">新增物流公司 <span class="x">×</span></span>
<span class="chip" data-page="edit" onclick="go('edit')">编辑物流公司 <span class="x">×</span></span>
<span class="chip" data-page="source" onclick="go('source')">发布货源 <span class="x">×</span></span>
<span class="chip" data-page="settle" onclick="go('settle')">运费核算 <span class="x">×</span></span>
<span class="chip" data-page="pay" onclick="go('pay')">打款列表 <span class="x">×</span></span>
<span class="chip" data-page="goodslist" onclick="go('goodslist')">货源列表 <span class="x">×</span></span>
<span class="chip" data-page="waybill" onclick="go('waybill')">运单管理 <span class="x">×</span></span>
</nav>
<div class="demo-strip">交互原型:数据为演示数据、不落库;物流公司仅作运单记录、不影响任何流程;核算=把核算数据填充到运单表(无独立核算单);筛选样式参照运八真实页面(表头筛选行)。</div>
<main class="content">
<!-- ===== 首页 ===== -->
<section class="page" id="page-home">
<div class="home-cards">
<div class="stat-card"><div class="lbl">物流公司总数</div><div class="num" id="stat-total">0</div></div>
<div class="stat-card"><div class="lbl">启用中</div><div class="num green" id="stat-active">0</div></div>
<div class="stat-card"><div class="lbl">生效合同(份)</div><div class="num blue" id="stat-contracts">0</div></div>
</div>
<div class="card" style="margin-top:14px;">
<div class="page-title">快捷入口</div>
<div class="btn-bar" style="margin-bottom:0;">
<button class="btn primary" onclick="go('add')">新增物流公司</button>
<button class="btn" onclick="go('list')">物流公司列表</button>
<button class="btn success" onclick="go('settle')">运费核算(运单核算演示)</button>
</div>
</div>
</section>
<!-- ===== 物流公司列表 ===== -->
<section class="page" id="page-list">
<div class="card">
<div class="btn-bar">
<button class="btn primary" onclick="renderCompanyTable()">查询</button>
<button class="btn" onclick="resetQuery()">重置</button>
<button class="btn success" onclick="go('add')">新增</button>
<button class="btn" onclick="toast('演示:导出 Excel 成功(上限 20000 条)','success')">导出</button>
</div>
<div class="table-wrap">
<table class="tbl">
<thead><tr class="frow"><th></th><th><input class="input" id="q-name" placeholder="请输入公司名称"></th><th></th><th><input class="input" id="q-uscc" placeholder="请输入信用代码"></th><th></th><th></th><th></th><th></th><th><select class="select" id="q-status"><option value="">请选择状态</option><option value="1">启用</option><option value="0">停用</option></select></th><th></th><th><input type="date" class="input date" id="q-start" style="width:105px;"><input type="date" class="input date" id="q-end" style="width:105px;"></th><th></th></tr><tr>
<th style="width:34px;"><input type="checkbox" id="ck-all-company" onchange="toggleAllCompany(this)"></th>
<th>公司名称</th><th>简称码</th><th>统一社会信用代码</th><th>法人代表名称</th><th>联系人</th><th>联系方式</th>
<th>生效合同数</th><th>状态</th><th>创建人</th><th>创建时间</th><th style="width:110px;">操作</th>
</tr></thead>
<tbody id="company-tbody"></tbody>
</table>
</div>
<div class="pagination">
<span id="pg-info"></span>
<button class="pg-btn">«</button><button class="pg-btn">‹</button>
<button class="pg-btn cur">1</button><button class="pg-btn">›</button><button class="pg-btn">»</button>
<select><option>20条/页</option><option>50条/页</option></select>
<span>前往</span><input class="input" style="width:44px;height:28px;" value="1"><span>页</span>
</div>
</div>
</section>
<!-- ===== 新增物流公司 ===== -->
<section class="page" id="page-add">
<div class="card">
<div class="page-title">新增物流公司</div>
<div class="section-title">上传证件</div>
<div class="upload-row">
<div class="upload-box" data-required="1" onclick="toggleUpload(this)">
<div class="up-frame"><span class="plus">+</span></div>
<div class="up-label req">企业营业执照</div>
</div>
<div class="upload-box" data-required="1" onclick="toggleUpload(this)">
<div class="up-frame"><span class="plus">+</span></div>
<div class="up-label req">法人身份证人像面照片</div>
</div>
<div class="upload-box" data-required="1" onclick="toggleUpload(this)">
<div class="up-frame"><span class="plus">+</span></div>
<div class="up-label req">法人身份证国徽面照片</div>
</div>
<div class="upload-box" onclick="toggleUpload(this)">
<div class="up-frame"><span class="plus">+</span></div>
<div class="up-label">道路运输经营许可证</div>
</div>
</div>
<div class="hint" style="margin-bottom:10px;">支持 jpg/jpeg/png/pdf;点击证件框模拟上传 / 取消上传(附件暂不设大小上限)。</div>
<div class="form-grid" id="add-form"></div>
<div class="btn-row">
<button class="btn primary" onclick="saveCompany('add')">保 存</button>
<button class="btn" onclick="go('list')">取 消</button>
</div>
</div>
</section>
<!-- ===== 编辑物流公司 ===== -->
<section class="page" id="page-edit">
<div class="card" style="padding:0;">
<div class="tab-nav">
<div class="tab-item active" data-tab="profile" onclick="switchTab('profile')">企业资料</div>
<div class="tab-item" data-tab="contract" onclick="switchTab('contract')">合同管理</div>
</div>
<div style="padding:18px;">
<!-- 企业资料 -->
<div class="tab-panel active" id="tab-profile">
<div class="section-title">上传证件</div>
<div class="upload-row">
<div class="upload-box" data-required="1" onclick="toggleUpload(this)">
<div class="up-frame"><span class="plus">+</span></div>
<div class="up-label req">企业营业执照</div>
</div>
<div class="upload-box" onclick="toggleUpload(this)">
<div class="up-frame"><span class="plus">+</span></div>
<div class="up-label">法人身份证人像面照片</div>
</div>
<div class="upload-box" onclick="toggleUpload(this)">
<div class="up-frame"><span class="plus">+</span></div>
<div class="up-label">法人身份证国徽面照片</div>
</div>
<div class="upload-box" onclick="toggleUpload(this)">
<div class="up-frame"><span class="plus">+</span></div>
<div class="up-label">道路运输经营许可证</div>
</div>
</div>
<div class="hint" style="margin-bottom:10px;">点击证件框模拟上传 / 取消上传(附件暂不设大小上限)。</div>
<div class="form-grid" id="edit-form"></div>
<div class="btn-row">
<button class="btn primary" onclick="saveCompany('edit')">保 存</button>
<button class="btn" onclick="go('list')">返 回</button>
</div>
</div>
<!-- 合同管理 -->
<div class="tab-panel" id="tab-contract">
<div class="btn-bar">
<button class="btn primary" onclick="renderContractTable()">查询</button>
<button class="btn success" onclick="openContractDialog('add')">新增</button>
<button class="btn" onclick="toast('演示:合同列表导出成功','success')">导出</button>
</div>
<div class="filter-bar">
<input class="mini-input" id="cf-no" placeholder="请输入合同编号">
<select class="select" id="cf-tax" style="width:220px;"><option value="">请选择税源地</option></select>
<input class="mini-input" id="cf-creator" placeholder="请输入创建人" style="width:130px;">
<span>签订时间</span><input type="date" class="mini-input" id="cf-sign1" style="width:135px;"><span>-</span><input type="date" class="mini-input" id="cf-sign2" style="width:135px;">
<span>创建时间</span><input type="date" class="mini-input" id="cf-create1" style="width:135px;"><span>-</span><input type="date" class="mini-input" id="cf-create2" style="width:135px;">
</div>
<div class="table-wrap">
<table class="tbl">
<thead><tr>
<th style="width:34px;"><input type="checkbox" onchange="toggleAllContract(this)"></th>
<th>合同编号</th><th>税源地</th><th>合同状态</th><th>创建人</th><th>签订时间</th><th>创建时间</th><th>生效时间</th><th>过期时间</th><th style="width:130px;">操作</th>
</tr></thead>
<tbody id="contract-tbody"></tbody>
</table>
</div>
<div class="pagination">
<span id="cpg-info"></span>
<button class="pg-btn">«</button><button class="pg-btn">‹</button>
<button class="pg-btn cur">1</button><button class="pg-btn">›</button><button class="pg-btn">»</button>
<select><option>20条/页</option><option>50条/页</option></select>
</div>
</div>
</div>
</div>
</section>
<!-- ===== 运费核算(运单核算) ===== -->
<section class="page" id="page-settle">
<div class="card">
<div class="page-title">运费核算(整车运输 → 承运结算)</div>
<div class="hint" style="margin-bottom:10px;">核算即把核算数据填充到运单表,无独立核算单;点击行内"修改核算"填写本期新增字段。</div>
<div class="btn-bar">
<button class="btn primary" onclick="toast('演示:查询条件已应用','success')">查询</button>
<button class="btn" onclick="toast('演示:批量核算沿用现有逻辑(Q7)','warning')">批量核算</button>
<button class="btn" onclick="toast('演示:导出成功','success')">导出</button>
</div>
<div class="table-wrap">
<table class="tbl">
<thead><tr class="frow"><th><input class="input" id="af-no" placeholder="请输入运输单号"></th><th></th><th></th><th></th><th></th><th><select class="select" id="af-status" onchange="renderAccountList()"><option value="">请选择核算状态</option><option>未核算</option><option>已核算</option></select></th><th></th><th></th></tr><tr>
<th>运输单号</th><th>货源单号</th><th>货物名称</th><th>承运司机</th><th>物流公司</th><th>核算状态</th><th>客户收货地址</th><th style="width:100px;">操作</th>
</tr></thead>
<tbody id="acc-tbody"></tbody>
</table>
</div>
<div class="pagination"><span id="acc-pg"></span><button class="pg-btn">«</button><button class="pg-btn cur">1</button><button class="pg-btn">»</button><select><option>20条/页</option></select></div>
</div>
</section>
<!-- ===== 发布货源 ===== -->
<section class="page" id="page-source">
<div class="card">
<div class="page-title">发布货源(整车运输 → 货源管理)</div>
<div class="form-grid" style="max-width:900px;">
<div class="form-item"><label class="req">货源单编号</label><div class="ctrl"><input class="input" value="HY20260928-001" readonly style="background:#f5f7fa;"></div></div>
<div class="form-item"><label class="req">货物名称</label><div class="ctrl"><input class="input" id="src-goods" placeholder="请输入货物名称"></div></div>
<div class="form-item"><label class="req">起始地</label><div class="ctrl"><input class="input" id="src-from" placeholder="请输入起始地(原型模拟)"></div></div>
<div class="form-item"><label class="req">目的地</label><div class="ctrl"><input class="input" id="src-to" placeholder="请输入目的地(原型模拟)"></div></div>
<div class="form-item full">
<label>物流公司(选填)</label>
<div class="ctrl">
<button class="btn primary" onclick="openPickDialog('source')">选择物流公司</button>
<span class="tip">单选;仅作运单记录,不影响货源及运单流程</span>
<table class="tbl" style="max-width:760px;margin-top:10px;">
<thead><tr><th>公司名称</th><th>统一社会信用代码</th><th>联系人</th><th style="width:80px;">操作</th></tr></thead>
<tbody id="src-picked-tbody"><tr><td colspan="4" class="empty">未选择物流公司(选填)</td></tr></tbody>
</table>
</div>
</div>
<div class="form-item full"><label>备注</label><div class="ctrl"><textarea class="textarea" placeholder="选填"></textarea></div></div>
</div>
<div class="btn-row">
<button class="btn primary" onclick="saveSource()">保存货源</button>
<button class="btn" onclick="toast('演示:返回货源列表','success')">取 消</button>
</div>
</div>
</section>
<!-- ===== 打款列表(货主打款 / 财务打款) ===== -->
<section class="page" id="page-pay">
<div class="card">
<div class="page-title">打款列表(整车运输 → 承运结算)</div>
<div class="tab-nav" style="padding:0;margin-bottom:12px;">
<div class="tab-item active" data-paytab="owner" onclick="switchPayTab('owner')">货主打款</div>
<div class="tab-item" data-paytab="finance" onclick="switchPayTab('finance')">财务打款</div>
</div>
<div class="btn-bar">
<button class="btn primary" onclick="renderPaymentTable()">查询</button>
<button class="btn" onclick="resetPayQuery()">重置</button>
<button class="btn" onclick="toast('演示:导出成功','success')">导出</button>
</div>
<div class="table-wrap">
<table class="tbl">
<thead><tr class="frow">
<th><input class="input" id="pf-waybill" placeholder="请输入运输单号"></th>
<th></th>
<th><select class="select" id="pf-company" onchange="renderPaymentTable()"></select></th>
<th></th><th></th>
<th><select class="select" id="pf-status" onchange="renderPaymentTable()"><option value="">请选择状态</option><option>待打款</option><option>已打款</option><option>已驳回</option></select></th>
<th></th>
</tr><tr>
<th>运输单号</th><th>货源单号</th><th>物流公司</th><th>承运司机</th><th>打款金额(元)</th><th>打款状态</th><th>打款时间</th>
</tr></thead>
<tbody id="pay-tbody"></tbody>
</table>
</div>
<div class="pagination"><span id="pay-pg"></span><button class="pg-btn">«</button><button class="pg-btn cur">1</button><button class="pg-btn">»</button><select><option>20条/页</option></select></div>
<div class="hint">货主打款、财务打款两个页面均含"物流公司"筛选与列(FR-09);仅作查询,不影响打款流程。</div>
</div>
</section>
<!-- ===== 货源列表 ===== -->
<section class="page" id="page-goodslist">
<div class="card">
<div class="page-title">货源列表(整车运输 → 货源管理)</div>
<div class="btn-bar">
<button class="btn primary" onclick="renderGoodsTable()">查询</button>
<button class="btn" onclick="resetGoodsQuery()">重置</button>
</div>
<div class="table-wrap">
<table class="tbl">
<thead><tr class="frow"><th><input class="input" id="gf-no" placeholder="请输入货源单号"></th><th><input class="input" id="gf-goods" placeholder="请输入货物名称"></th><th colspan="6"></th></tr><tr>
<th>货源单号</th><th>货物名称</th><th>起始地</th><th>目的地</th><th>物流公司</th><th>发布人</th><th>创建时间</th><th style="width:80px;">操作</th>
</tr></thead>
<tbody id="goods-tbody"></tbody>
</table>
</div>
<div class="pagination"><span id="goods-pg"></span><button class="pg-btn">«</button><button class="pg-btn cur">1</button><button class="pg-btn">»</button><select><option>20条/页</option></select></div>
<div class="hint">"物流公司"列为货源上记录的快照信息(FR-10),只读展示、不影响任何流程。</div>
</div>
</section>
<!-- ===== 运单管理 ===== -->
<section class="page" id="page-waybill">
<div class="card">
<div class="page-title">运单管理(整车运输 → 运输管理)</div>
<div class="btn-bar">
<button class="btn primary" onclick="renderWaybillTable()">查询</button>
<button class="btn" onclick="resetWaybillQuery()">重置</button>
</div>
<div class="table-wrap">
<table class="tbl">
<thead><tr class="frow"><th><input class="input" id="wf-no" placeholder="请输入运单号"></th><th></th><th><input class="input" id="wf-goods" placeholder="请输入货物名称"></th><th colspan="4"></th></tr><tr>
<th>运单号</th><th>货源单号</th><th>货物名称</th><th>承运司机</th><th>物流公司</th><th>运单状态</th><th>运费金额(元)</th>
</tr></thead>
<tbody id="waybill-tbody"></tbody>
</table>
</div>
<div class="pagination"><span id="waybill-pg"></span><button class="pg-btn">«</button><button class="pg-btn cur">1</button><button class="pg-btn">»</button><select><option>20条/页</option></select></div>
<div class="hint">"物流公司"列随关联货源带入(FR-10),快照展示、只读,不影响任何流程。</div>
</div>
</section>
</main>
</div>
</div>
<!-- ===== 新增/编辑合同弹窗 ===== -->
<div class="mask" id="mask-contract">
<div class="dialog" style="width:600px;">
<div class="dlg-head"><span id="contract-dlg-title">新增</span><span class="close" onclick="closeDialog('mask-contract')">×</span></div>
<div class="dlg-body dlg-form">
<div class="form-item"><label class="req">合同编号</label><div class="ctrl"><input class="input" id="ct-no" placeholder="请输入合同编号"><div class="err-msg" id="err-ct-no"></div></div></div>
<div class="form-item"><label class="req">签订时间</label><div class="ctrl"><input type="date" class="input" id="ct-sign"></div></div>
<div class="form-item"><label class="req">生效时间</label><div class="ctrl"><input type="date" class="input" id="ct-eff"></div></div>
<div class="form-item"><label class="req">过期时间</label><div class="ctrl"><input type="date" class="input" id="ct-exp"><div class="err-msg" id="err-ct-date"></div></div></div>
<div class="form-item"><label class="req">所属税源地</label><div class="ctrl"><select class="select" id="ct-tax"><option value="">请选择</option></select></div></div>
<div class="form-item"><label class="req">附件</label><div class="ctrl">
<span class="upload-btn" onclick="mockUploadFile()">点击上传</span>
<span class="file-name" id="ct-file-name"></span>
<div class="hint">只能上传pdf/doc/docx/jpg/jpeg/bmp/png文件</div>
</div></div>
</div>
<div class="dlg-foot">
<button class="btn" onclick="closeDialog('mask-contract')">取 消</button>
<button class="btn primary" onclick="saveContract()">确 定</button>
</div>
</div>
</div>
<!-- ===== 查看合同弹窗 ===== -->
<div class="mask" id="mask-view">
<div class="dialog" style="width:520px;">
<div class="dlg-head">合同详情<span class="close" onclick="closeDialog('mask-view')">×</span></div>
<div class="dlg-body" id="view-body"></div>
<div class="dlg-foot"><button class="btn" onclick="closeDialog('mask-view')">关 闭</button></div>
</div>
</div>
<!-- ===== 修改核算弹窗(在运单上核算) ===== -->
<div class="mask" id="mask-account">
<div class="dialog" style="width:680px;">
<div class="dlg-head">修改核算<span class="close" onclick="closeDialog('mask-account')">×</span></div>
<div class="dlg-body">
<div class="section-title">运单信息(只读)</div>
<div class="form-grid" style="max-width:640px;">
<div class="form-item"><label>运单号</label><div class="ctrl"><input class="input" id="acc-no" readonly style="background:#f5f7fa;"></div></div>
<div class="form-item"><label>货物名称</label><div class="ctrl"><input class="input" id="acc-goods" readonly style="background:#f5f7fa;"></div></div>
<div class="form-item full"><label>承运司机</label><div class="ctrl"><input class="input" id="acc-driver" readonly style="background:#f5f7fa;"></div></div>
</div>
<div class="section-title" style="margin-top:10px;">核算字段(本期新增,填充到运单表)</div>
<div class="form-grid" style="max-width:640px;">
<div class="form-item full">
<label>物流公司(选填)</label>
<div class="ctrl">
<button class="btn primary" onclick="openPickDialog('account')">选择物流公司</button>
<span class="tip">单选;默认带入货源所选公司;无生效合同也可勾选</span>
<table class="tbl" style="max-width:520px;margin-top:10px;">
<thead><tr><th>公司名称</th><th>统一社会信用代码</th><th style="width:80px;">操作</th></tr></thead>
<tbody id="acc-picked-tbody"><tr><td colspan="3" class="empty">未选择物流公司(无外协公司)</td></tr></tbody>
</table>
</div>
</div>
<div class="form-item full"><label>客户收货地址</label><div class="ctrl"><input class="input" id="acc-addr" placeholder="选填,仅作记录,不影响流程"></div></div>
</div>
</div>
<div class="dlg-foot">
<button class="btn" onclick="closeDialog('mask-account')">取 消</button>
<button class="btn" onclick="saveAccount(false)">保 存</button>
<button class="btn primary" onclick="saveAccount(true)">核 算</button>
</div>
</div>
</div>
<!-- ===== 选择物流公司弹窗(核算勾选) ===== --><!-- ===== 选择物流公司弹窗(核算勾选) ===== -->
<div class="mask" id="mask-pick">
<div class="dialog" style="width:860px;">
<div class="dlg-head">选择物流公司(单选、选填)<span class="close" onclick="closeDialog('mask-pick')">×</span></div>
<div class="dlg-body">
<div class="filter-bar">
<input class="input" id="pick-kw" placeholder="公司名称 / 统一社会信用代码" style="width:260px;" oninput="renderPickTable()">
<span class="tag blue">仅显示启用状态公司</span>
</div>
<div class="table-wrap">
<table class="tbl">
<thead><tr>
<th style="width:40px;">选择</th>
<th>公司名称</th><th>统一社会信用代码</th><th>联系人</th><th>联系方式</th><th>生效合同</th>
</tr></thead>
<tbody id="pick-tbody"></tbody>
</table>
</div>
<div class="pagination"><span id="pick-pg"></span><button class="pg-btn">«</button><button class="pg-btn cur">1</button><button class="pg-btn">»</button><select><option>20条/页</option></select></div>
<div class="pick-summary" style="margin-bottom:0;">已选择 <span class="count" id="pick-dialog-count">0</span> 家物流公司(单选)</div>
</div>
<div class="dlg-foot">
<button class="btn" onclick="closeDialog('mask-pick')">取 消</button>
<button class="btn primary" onclick="confirmPick()">确 定</button>
</div>
</div>
</div>
<!-- ===== 通用确认弹窗 ===== -->
<div class="mask" id="mask-confirm" style="z-index:150;">
<div class="dialog" style="width:420px; margin-top:180px;">
<div class="dlg-head">提示<span class="close" onclick="closeDialog('mask-confirm')">×</span></div>
<div class="dlg-body" id="confirm-msg" style="padding:26px 26px 10px; color:#606266;"></div>
<div class="dlg-foot">
<button class="btn" onclick="closeDialog('mask-confirm')">取 消</button>
<button class="btn primary" id="confirm-ok">确 定</button>
</div>
</div>
</div>
<div id="toast-wrap"></div>
<script>
/* ================= 演示数据 ================= */
const TODAY = '2026-09-28';
const TAX_SOURCES = ['云南运八数据科技有限公司','富源运八数据科技有限公司','曲靖市马龙区呈钢加油站有限公司'];
let companySeq = 8;
let companies = [
{ id:1, name:'云南顺达物流有限公司', shortCode:'SDWL', uscc:'91530100MA6N5X2P7A', legal:'李建国', contact:'李建国', phone:'13888880001',
region:'云南省 昆明市 官渡区', address:'曙光东路67号', remark:'', status:1, usedInSettlement:true, creator:'super_admin', createdAt:'2026-07-02 10:32:40',
contracts:[
{ no:'HT2026-001', tax:0, creator:'super_admin', signDate:'2026-07-02', createDate:'2026-07-08 10:51:14', effDate:'2026-07-09', expDate:'2027-07-08', file:'顺达承运合同.pdf' },
{ no:'HT2026-002', tax:1, creator:'super_admin', signDate:'2026-05-11', createDate:'2026-05-12 09:20:03', effDate:'2026-05-12', expDate:'2026-08-31', file:'顺达补充协议.pdf' }
]},
{ id:2, name:'昆明恒通货运有限公司', shortCode:'HT', uscc:'91530100MA6K8T3Q5B', legal:'王志强', contact:'王志强', phone:'13888880002',
region:'云南省 昆明市 五华区', address:'学府路118号', remark:'', status:1, usedInSettlement:true, creator:'super_admin', createdAt:'2026-07-08 14:11:26',
contracts:[
{ no:'45345453453', tax:0, creator:'super_admin', signDate:'2026-07-15', createDate:'2026-07-08 17:38:59', effDate:'2026-07-14', expDate:'2027-07-14', file:'恒通框架合同.docx' }
]},
{ id:3, name:'曲靖晨光物流有限公司', shortCode:'CGL', uscc:'91530300MA6L1M9R3C', legal:'张艳', contact:'张艳', phone:'13888880003',
region:'云南省 曲靖市 麒麟区', address:'南宁东路22号', remark:'', status:0, usedInSettlement:false, creator:'super_admin', createdAt:'2026-07-11 09:34:07',
contracts:[
{ no:'ceshi', tax:2, creator:'super_admin', signDate:'2026-06-02', createDate:'2026-06-08 10:51:14', effDate:'2026-06-09', expDate:'2026-07-31', file:'晨光运输合同.pdf' }
]},
{ id:4, name:'富源永顺物流有限公司', shortCode:'YSWL', uscc:'91530325MA6M4B7K2D', legal:'刘伟', contact:'刘伟', phone:'13888880004',
region:'云南省 曲靖市 富源县', address:'胜境大道9号', remark:'', status:1, usedInSettlement:false, creator:'super_admin', createdAt:'2026-08-01 16:02:44',
contracts:[] },
{ id:5, name:'大理苍山运输有限公司', shortCode:'CSYS', uscc:'91532900MA6P9C4X6E', legal:'赵磊', contact:'赵磊', phone:'13888880005',
region:'云南省 大理州 大理市', address:'人民南路35号', remark:'', status:1, usedInSettlement:false, creator:'super_admin', createdAt:'2026-08-15 11:20:19',
contracts:[
{ no:'RH888899', tax:1, creator:'super_admin', signDate:'2026-09-01', createDate:'2026-09-02 10:34:07', effDate:'2026-10-15', expDate:'2027-10-14', file:'苍山运输合同2027.pdf' }
]},
{ id:6, name:'玉溪通达物流有限公司', shortCode:'TD', uscc:'91530400MA6Q2D8F1G', legal:'孙明', contact:'孙明', phone:'13888880006',
region:'云南省 玉溪市 红塔区', address:'凤凰路160号', remark:'', status:1, usedInSettlement:false, creator:'super_admin', createdAt:'2026-09-01 08:45:52',
contracts:[
{ no:'TDHT-2026-01', tax:0, creator:'super_admin', signDate:'2026-08-20', createDate:'2026-08-21 15:12:31', effDate:'2026-09-01', expDate:'2026-12-31', file:'通达承运合同.pdf' },
{ no:'TDHT-2026-02', tax:0, creator:'super_admin', signDate:'2026-09-10', createDate:'2026-09-11 09:05:00', effDate:'2026-09-12', expDate:'2027-09-11', file:'通达线路补充协议.pdf' }
]},
{ id:7, name:'保山联运物流有限公司', shortCode:'LYWL', uscc:'91530500MA6R7H9J3H', legal:'陈静', contact:'陈静', phone:'13888880007',
region:'云南省 保山市 隆阳区', address:'永昌大道88号', remark:'', status:1, usedInSettlement:false, creator:'super_admin', createdAt:'2026-09-12 17:30:08',
contracts:[
{ no:'1111', tax:0, creator:'super_admin', signDate:'2026-07-02', createDate:'2026-07-08 10:32:40', effDate:'2026-07-11', expDate:'2026-07-10', file:'联运合同.pdf' }
]}
];
let currentEditId = 1; // 当前编辑的公司
let contractEditIdx = null; // 正在编辑的合同下标,null=新增
let contractTmpFile = ''; // 合同弹窗内模拟附件
let pickedIds = new Set(); // 选择弹窗中已选公司(单选,最多1个)
let pickMode = 'account'; // 选择弹窗用途:account=运单核算 / source=货源
let accountIdx = null; // 当前修改核算的运单下标
let accountCompanyId = 0; // 修改核算弹窗中已选的物流公司
let sourceCompanyId = 0; // 货源上选择的物流公司
let confirmCb = null;
/* 打款记录演示数据(companyId=0 表示无外协公司;companyName 为运单记录的快照) */
let payments = [
{ no:'DK20260920-001', waybill:'YD2026091800126', companyId:1, companyName:'云南顺达物流有限公司', driver:'李国强', amount:12500.00, status:'已打款', time:'2026-09-20 10:12:36' },
{ no:'DK20260921-002', waybill:'YD2026091900303', companyId:6, companyName:'玉溪通达物流有限公司', driver:'王志明', amount:8600.50, status:'已打款', time:'2026-09-21 15:40:11' },
{ no:'DK20260922-003', waybill:'YD2026092000741', companyId:0, companyName:'', driver:'张永发', amount:5300.00, status:'待打款', time:'2026-09-22 09:05:00' },
{ no:'DK20260923-004', waybill:'YD2026092100215', companyId:2, companyName:'昆明恒通货运有限公司', driver:'刘小波', amount:15980.00, status:'待打款', time:'2026-09-23 14:22:45' },
{ no:'DK20260924-005', waybill:'YD2026092200966', companyId:1, companyName:'云南顺达物流有限公司', driver:'赵天宇', amount:7450.25, status:'已打款', time:'2026-09-24 11:30:09' },
{ no:'DK20260925-006', waybill:'YD2026092400482', companyId:0, companyName:'', driver:'孙建军', amount:9820.00, status:'已驳回', time:'2026-09-25 16:08:53' }
];
/* 货源 / 运单演示数据(companyName 为记录的快照;空表示无外协公司) */
let goodsSources = [
{ no:'HY20260918-001', goods:'钢材一批', from:'昆明市官渡区', to:'曲靖市麒麟区', companyId:1, companyName:'云南顺达物流有限公司', creator:'super_admin', createdAt:'2026-09-18 09:20:11' },
{ no:'HY20260919-002', goods:'瓷砖40吨', from:'昆明市五华区', to:'大理市', companyId:6, companyName:'玉溪通达物流有限公司', creator:'super_admin', createdAt:'2026-09-19 10:05:33' },
{ no:'HY20260920-003', goods:'化肥30吨', from:'曲靖市沾益区', to:'贵阳市修文县', companyId:0, companyName:'', creator:'super_admin', createdAt:'2026-09-20 14:41:02' },
{ no:'HY20260921-004', goods:'机械设备', from:'玉溪市红塔区', to:'昆明市官渡区', companyId:2, companyName:'昆明恒通货运有限公司', creator:'super_admin', createdAt:'2026-09-21 11:18:47' },
{ no:'HY20260922-005', goods:'日用百货', from:'昆明市官渡区', to:'保山市隆阳区', companyId:0, companyName:'', creator:'super_admin', createdAt:'2026-09-22 16:02:28' },
{ no:'HY20260923-006', goods:'铝锭35吨', from:'富源县', to:'昆明市西山区', companyId:1, companyName:'云南顺达物流有限公司', creator:'super_admin', createdAt:'2026-09-23 08:55:14' }
];
let waybills = [
{ no:'YD2026091800126', goodsNo:'HY20260918-001', goods:'钢材一批', driver:'李国强(云A12345)', companyId:1, companyName:'云南顺达物流有限公司', status:'运输中', amount:12500.00 },
{ no:'YD2026091900303', goodsNo:'HY20260919-002', goods:'瓷砖40吨', driver:'王志明(云A67890)', companyId:6, companyName:'玉溪通达物流有限公司', status:'已卸货', amount:8600.50 },
{ no:'YD2026092000741', goodsNo:'HY20260920-003', goods:'化肥30吨', driver:'张永发(云C23456)', companyId:0, companyName:'', status:'待运输', amount:5300.00 },
{ no:'YD2026092100215', goodsNo:'HY20260921-004', goods:'机械设备', driver:'刘小波(云D34567)', companyId:2, companyName:'昆明恒通货运有限公司', status:'已结算', amount:15980.00 },
{ no:'YD2026092200966', goodsNo:'HY20260922-005', goods:'日用百货', driver:'赵天宇(云F45678)', companyId:0, companyName:'', status:'运输中', amount:7450.25 },
{ no:'YD2026092400482', goodsNo:'HY20260923-006', goods:'铝锭35吨', driver:'孙建军(云G56789)', companyId:1, companyName:'云南顺达物流有限公司', status:'已卸货', amount:9820.00 }
];
/* ================= 工具 ================= */
function esc(s){ return String(s==null?'':s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;'); }
function todayStr(){ return TODAY; }
function contractStatus(c){
if (c.effDate > TODAY) return ['待生效','orange'];
if (c.expDate >= TODAY) return ['生效中','green'];
return ['已过期','gray'];
}
function activeContractCount(co){ return co.contracts.filter(c=>contractStatus(c)[0]==='生效中').length; }
function getCompany(id){ return companies.find(c=>c.id===id); }
function toast(msg, type){
const t = document.createElement('div');
t.className = 'toast ' + (type||'success');
t.textContent = msg;
document.getElementById('toast-wrap').appendChild(t);
setTimeout(()=>t.remove(), 2600);
}
function confirmDialog(msg, cb){
document.getElementById('confirm-msg').innerHTML = msg;
confirmCb = cb;
document.getElementById('mask-confirm').classList.add('open');
}
document.getElementById('confirm-ok').onclick = function(){
closeDialog('mask-confirm');
if (confirmCb) confirmCb();
};
function closeDialog(id){ document.getElementById(id).classList.remove('open'); }
function toggleMenu(id){
const el = document.getElementById(id);
el.classList.toggle('open');
}
function todo(){ toast('原型未实现该菜单,请通过左侧"物流公司管理"体验','warning'); }
function mockUploadFile(){
contractTmpFile = '合同扫描件_' + Math.floor(Math.random()*900+100) + '.pdf';
document.getElementById('ct-file-name').textContent = contractTmpFile;
}
function toggleUpload(box){
const frame = box.querySelector('.up-frame');
const done = frame.classList.toggle('done');
if (done){
frame.innerHTML = '<span class="plus">+</span><span>✓ 已上传</span><span style="color:#909399;">'+(box.dataset.name||'证照扫描件.jpg')+'</span>';
frame.querySelector('.plus').style.display='none';
} else {
frame.innerHTML = '<span class="plus">+</span>';
}
}
/* ================= 页面路由 ================= */
const CRUMBS = {
home: ['首页'],
list: ['首页','基础信息','承运管理','物流公司管理'],
add: ['首页','基础信息','承运管理','物流公司管理','新增物流公司'],
edit: ['首页','基础信息','承运管理','物流公司管理','编辑物流公司'],
source: ['首页','整车运输','货源管理','发布货源'],
settle: ['首页','整车运输','承运结算','运费核算'],
pay: ['首页','整车运输','承运结算','货主打款 / 财务打款'],
goodslist: ['首页','整车运输','货源管理','货源列表'],
waybill: ['首页','整车运输','运输管理','运单管理']
};
function go(page){
document.querySelectorAll('.page').forEach(p=>p.classList.remove('active'));
document.getElementById('page-'+page).classList.add('active');
document.querySelectorAll('.chip').forEach(c=>c.classList.toggle('active', c.dataset.page===page));
document.getElementById('crumb').innerHTML = CRUMBS[page].map((s,i)=>
i===CRUMBS[page].length-1 ? '<b>'+s+'</b>' : s).join('<span class="sep">/</span>');
if (page==='list') renderCompanyTable();
if (page==='add') resetAddForm();
if (page==='edit') loadEditCompany(currentEditId);
if (page==='settle') renderAccountList();
if (page==='source') renderSourcePicked();
if (page==='pay') renderPaymentTable();
if (page==='goodslist') renderGoodsTable();
if (page==='waybill') renderWaybillTable();
if (page==='home') renderStats();
window.scrollTo(0,0);
}
/* ================= 公司表单(新增/编辑共用渲染) ================= */
const FORM_FIELDS = [
{ key:'name', label:'公司名称', req:1, ph:'请输入公司名称' },
{ key:'uscc', label:'统一社会信用代码', req:1, ph:'请输入18位统一社会信用代码' },
{ key:'legal', label:'法人代表名称', req:1, ph:'请输入法人代表名称' },
{ key:'contact', label:'联系人', req:1, ph:'请输入联系人' },
{ key:'phone', label:'联系方式', req:1, ph:'请输入联系方式' },
{ key:'shortCode',label:'简称码', req:0, ph:'请输入2-4位纯字母的物流公司简称' },
{ key:'region', label:'企业通信地址', req:1, type:'region' },
{ key:'address', label:'详细地址', req:1, ph:'请输入详细地址' },
{ key:'remark', label:'备注', req:0, type:'textarea', full:1 }
];
const REGIONS = ['','云南省 昆明市 官渡区','云南省 昆明市 五华区','云南省 曲靖市 麒麟区','云南省 曲靖市 富源县','云南省 大理州 大理市','云南省 玉溪市 红塔区','云南省 保山市 隆阳区'];
function renderForm(containerId, data){
const box = document.getElementById(containerId);
box.innerHTML = FORM_FIELDS.map(f=>{
const val = data ? (data[f.key]||'') : '';
let ctrl;
if (f.type==='region'){
ctrl = '<select class="select" data-key="'+f.key+'">' + REGIONS.map(r=>
'<option value="'+esc(r)+'"'+(r===val?' selected':'')+'>'+(r||'请选择')+'</option>').join('') + '</select>';
} else if (f.type==='textarea'){
ctrl = '<textarea class="textarea" data-key="'+f.key+'" placeholder="'+f.ph+'">'+esc(val)+'</textarea>';
} else {
ctrl = '<input class="input" data-key="'+f.key+'" placeholder="'+f.ph+'" value="'+esc(val)+'">';
}
return '<div class="form-item'+(f.full?' full':'')+'"><label class="'+(f.req?'req':'')+'">'+f.label+'</label><div class="ctrl">'+ctrl+'</div></div>';
}).join('');
}
function readForm(containerId){
const out = {};
document.querySelectorAll('#'+containerId+' [data-key]').forEach(el=>{ out[el.dataset.key] = el.value.trim(); });
return out;
}
function resetAddForm(){ renderForm('add-form', null); }
function loadEditCompany(id){
const co = getCompany(id) || companies[0];
currentEditId = co.id;
renderForm('edit-form', co);
document.querySelectorAll('#page-edit .upload-box').forEach(b=>{ if(!b.querySelector('.up-frame.done')) toggleUpload(b); });
renderContractTable();
switchTab('profile');
}
/* ================= 保存公司(新增/编辑) ================= */
function saveCompany(mode){
const formId = mode==='add' ? 'add-form' : 'edit-form';
const data = readForm(formId);
const labelOf = k => FORM_FIELDS.find(f=>f.key===k).label;
// 必填校验
for (const f of FORM_FIELDS){
if (f.req && !data[f.key]){ toast('请填写「'+f.label+'」','error'); return; }
}
if (!/^[\dA-HJ-NPQRTUWXY]{18}$/.test(data.uscc)){ toast('统一社会信用代码须为18位(数字/字母,不含I、O、Z、S、V)','error'); return; }
if (!/^1\d{10}$/.test(data.phone)){ toast('联系方式须为11位手机号','error'); return; }
if (data.shortCode && !/^[A-Za-z]{2,4}$/.test(data.shortCode)){ toast('简称码须为2-4位纯字母','error'); return; }
// 证件:营业执照、法人身份证人像面/国徽面必传(道路运输经营许可证选填)
const page = document.getElementById(mode==='add'?'page-add':'page-edit');
for (const box of page.querySelectorAll('.upload-box[data-required="1"]')){
if (!box.querySelector('.up-frame.done')){
toast('请上传「'+box.querySelector('.up-label').textContent.trim()+'」','error');
return;
}
}
// 唯一性校验(排除自身)
const selfId = mode==='add' ? null : currentEditId;
if (companies.some(c=>c.name===data.name && c.id!==selfId)){ toast('公司名称已存在:'+data.name,'error'); return; }
if (companies.some(c=>c.uscc===data.uscc && c.id!==selfId)){ toast('统一社会信用代码已存在:'+data.uscc,'error'); return; }
if (data.shortCode && companies.some(c=>c.shortCode && c.shortCode.toUpperCase()===data.shortCode.toUpperCase() && c.id!==selfId)){
toast('简称码已存在:'+data.shortCode,'error'); return;
}
if (mode==='add'){
companies.unshift({
id: companySeq++, ...data, shortCode: data.shortCode.toUpperCase(),
status:1, usedInSettlement:false, creator:'super_admin',
createdAt:'2026-09-28 ' + new Date().toTimeString().slice(0,8), contracts:[]
});
toast('新增成功,状态默认为启用','success');
} else {
Object.assign(getCompany(currentEditId), data, { shortCode: data.shortCode.toUpperCase() });
toast('保存成功','success');
}
go('list');
}
/* ================= 公司列表 ================= */
function resetQuery(){
['q-name','q-uscc','q-start','q-end'].forEach(i=>document.getElementById(i).value='');
document.getElementById('q-status').value='';
renderCompanyTable();
}
function renderCompanyTable(){
const name = document.getElementById('q-name').value.trim();
const uscc = document.getElementById('q-uscc').value.trim();
const st = document.getElementById('q-status').value;
const s1 = document.getElementById('q-start').value, s2 = document.getElementById('q-end').value;
const rows = companies.filter(c=>{
if (name && !c.name.includes(name)) return false;
if (uscc && !c.uscc.includes(uscc)) return false;
if (st!=='' && String(c.status)!==st) return false;
if (s1 && c.createdAt.slice(0,10) < s1) return false;
if (s2 && c.createdAt.slice(0,10) > s2) return false;
return true;
});
const tbody = document.getElementById('company-tbody');
if (!rows.length){
tbody.innerHTML = '<tr><td colspan="12" class="empty">未找到符合条件的物流公司</td></tr>';
} else {
tbody.innerHTML = rows.map(c=>`<tr>
<td><input type="checkbox" class="ck-company"></td>
<td><span class="name-link" onclick="editCompany(${c.id})">${esc(c.name)}</span></td>
<td>${esc(c.shortCode||'-')}</td>
<td>${esc(c.uscc)}</td>
<td>${esc(c.legal)}</td>
<td>${esc(c.contact)}</td>
<td>${esc(c.phone)}</td>
<td>${activeContractCount(c)}</td>
<td><span class="switch ${c.status?'on':''}" onclick="toggleStatus(${c.id})" title="${c.status?'启用':'停用'}"></span>
<span class="tag ${c.status?'green':'gray'}" style="margin-left:6px;">${c.status?'启用':'停用'}</span></td>
<td>${esc(c.creator)}</td>
<td>${esc(c.createdAt)}</td>
<td><span class="link" onclick="editCompany(${c.id})">编辑</span><span class="link danger" onclick="delCompany(${c.id})">删除</span></td>
</tr>`).join('');
}
document.getElementById('pg-info').textContent = '共 '+rows.length+' 条记录';
document.getElementById('ck-all-company').checked = false;
renderStats();
}
function toggleAllCompany(box){ document.querySelectorAll('.ck-company').forEach(c=>c.checked=box.checked); }
function editCompany(id){ currentEditId = id; go('edit'); }
function toggleStatus(id){
const co = getCompany(id);
if (co.status){
confirmDialog('确定停用物流公司「'+esc(co.name)+'」吗?<br><span style="color:#909399;font-size:12px;">停用后将不再出现在运费核算勾选列表,历史核算单不受影响。</span>', ()=>{
co.status = 0; renderCompanyTable(); toast('已停用','success');
});
} else {
co.status = 1; renderCompanyTable(); toast('已启用','success');
}
}
function delCompany(id){
const co = getCompany(id);
const activeN = activeContractCount(co);
if (co.usedInSettlement || activeN>0){
const reasons = [];
if (activeN>0) reasons.push('存在 '+activeN+' 份生效中合同');
if (co.usedInSettlement) reasons.push('已被核算单引用');
toast('删除失败:该公司'+reasons.join(',')+',无法删除,可改为停用','error');
return;
}
confirmDialog('确定删除物流公司「'+esc(co.name)+'」吗?<br><span style="color:#909399;font-size:12px;">删除后不可恢复(逻辑删除)。</span>', ()=>{
companies = companies.filter(c=>c.id!==id);
pickedIds.delete(id);
renderCompanyTable(); toast('删除成功','success');
});
}
/* ================= 编辑页 Tab ================= */
function switchTab(name){
document.querySelectorAll('#page-edit .tab-item').forEach(t=>t.classList.toggle('active', t.dataset.tab===name));
document.querySelectorAll('#page-edit .tab-panel').forEach(p=>p.classList.remove('active'));
document.getElementById('tab-'+name).classList.add('active');
if (name==='contract') renderContractTable();
}
/* ================= 合同管理 ================= */
function initTaxSelects(){
document.getElementById('cf-tax').innerHTML = '<option value="">请选择税源地</option>' + TAX_SOURCES.map((t,i)=>'<option value="'+i+'">'+esc(t)+'</option>').join('');
document.getElementById('ct-tax').innerHTML = '<option value="">请选择</option>' + TAX_SOURCES.map((t,i)=>'<option value="'+i+'">'+esc(t)+'</option>').join('');
}
function renderContractTable(){
const co = getCompany(currentEditId);
const no = document.getElementById('cf-no').value.trim();
const tax = document.getElementById('cf-tax').value;
const creator = document.getElementById('cf-creator').value.trim();
const sg1 = document.getElementById('cf-sign1').value, sg2 = document.getElementById('cf-sign2').value;
const rows = co.contracts.map((c,idx)=>({c,idx})).filter(({c})=>{
if (no && !c.no.includes(no)) return false;
if (tax!=='' && String(c.tax)!==tax) return false;
if (creator && !c.creator.includes(creator)) return false;
if (sg1 && c.signDate < sg1) return false;
if (sg2 && c.signDate > sg2) return false;
return true;
});
const tbody = document.getElementById('contract-tbody');
if (!rows.length){
tbody.innerHTML = '<tr><td colspan="10" class="empty">暂无合同,点击"新增"录入</td></tr>';
} else {
tbody.innerHTML = rows.map(({c,idx})=>{
const [st, color] = contractStatus(c);
return `<tr>
<td><input type="checkbox" class="ck-contract"></td>
<td>${esc(c.no)}</td>
<td>${esc(TAX_SOURCES[c.tax])}</td>
<td><span class="tag ${color}">${st}</span></td>
<td>${esc(c.creator)}</td>
<td>${esc(c.signDate)} 00:00:00</td>
<td>${esc(c.createDate)}</td>
<td>${esc(c.effDate)}</td>
<td>${esc(c.expDate)}</td>
<td><span class="link" onclick="viewContract(${idx})">查看</span><span class="link" onclick="openContractDialog('edit',${idx})">编辑</span><span class="link danger" onclick="delContract(${idx})">删除</span></td>
</tr>`;
}).join('');
}
document.getElementById('cpg-info').textContent = '共 '+rows.length+' 条记录';
}
function toggleAllContract(box){ document.querySelectorAll('.ck-contract').forEach(c=>c.checked=box.checked); }
function openContractDialog(mode, idx){
contractEditIdx = mode==='edit' ? idx : null;
document.getElementById('contract-dlg-title').textContent = mode==='edit' ? '编辑合同' : '新增';
const co = getCompany(currentEditId);
const c = mode==='edit' ? co.contracts[idx] : null;
document.getElementById('ct-no').value = c ? c.no : '';
document.getElementById('ct-sign').value = c ? c.signDate : '';
document.getElementById('ct-eff').value = c ? c.effDate : '';
document.getElementById('ct-exp').value = c ? c.expDate : '';
document.getElementById('ct-tax').value = c ? String(c.tax) : '';
contractTmpFile = c ? c.file : '';
document.getElementById('ct-file-name').textContent = contractTmpFile;
document.getElementById('err-ct-no').style.display='none';
document.getElementById('err-ct-date').style.display='none';
document.getElementById('mask-contract').classList.add('open');
}
function saveContract(){
const co = getCompany(currentEditId);
const no = document.getElementById('ct-no').value.trim();
const sign = document.getElementById('ct-sign').value;
const eff = document.getElementById('ct-eff').value;
const exp = document.getElementById('ct-exp').value;
const tax = document.getElementById('ct-tax').value;
if (!no){ toast('请输入合同编号','error'); return; }
if (!sign){ toast('请选择签订时间','error'); return; }
if (!eff){ toast('请选择生效时间','error'); return; }
if (!exp){ toast('请选择过期时间','error'); return; }
if (exp <= eff){ toast('过期时间必须晚于生效时间','error'); return; }
if (tax===''){ toast('请选择所属税源地','error'); return; }
if (!contractTmpFile){ toast('请上传合同附件(pdf/doc/docx/jpg/jpeg/bmp/png)','error'); return; }
if (co.contracts.some((c,i)=>c.no===no && i!==contractEditIdx)){ toast('同一公司下合同编号已存在:'+no,'error'); return; }
if (contractEditIdx===null){
co.contracts.unshift({ no, tax:Number(tax), creator:'super_admin', signDate:sign,
createDate:'2026-09-28 ' + new Date().toTimeString().slice(0,8), effDate:eff, expDate:exp, file:contractTmpFile });
toast('合同新增成功','success');
} else {
Object.assign(co.contracts[contractEditIdx], { no, tax:Number(tax), signDate:sign, effDate:eff, expDate:exp, file:contractTmpFile });
toast('合同保存成功','success');
}
closeDialog('mask-contract');
renderContractTable();
}
function viewContract(idx){
const co = getCompany(currentEditId);
const c = co.contracts[idx];
const [st, color] = contractStatus(c);
document.getElementById('view-body').innerHTML = `
<div class="dlg-form">
<div class="form-item"><label>合同编号</label><div class="ctrl" style="line-height:32px;">${esc(c.no)}</div></div>
<div class="form-item"><label>合同状态</label><div class="ctrl"><span class="tag ${color}">${st}</span></div></div>
<div class="form-item"><label>所属税源地</label><div class="ctrl" style="line-height:32px;">${esc(TAX_SOURCES[c.tax])}</div></div>
<div class="form-item"><label>签订时间</label><div class="ctrl" style="line-height:32px;">${esc(c.signDate)}</div></div>
<div class="form-item"><label>生效时间</label><div class="ctrl" style="line-height:32px;">${esc(c.effDate)}</div></div>
<div class="form-item"><label>过期时间</label><div class="ctrl" style="line-height:32px;">${esc(c.expDate)}</div></div>
<div class="form-item"><label>附件</label><div class="ctrl"><span class="link">📎 ${esc(c.file)}</span><span class="tip">预览 / 下载</span></div></div>
</div>`;
document.getElementById('mask-view').classList.add('open');
}
function delContract(idx){
const co = getCompany(currentEditId);
const c = co.contracts[idx];
confirmDialog('确定删除合同「'+esc(c.no)+'」吗?', ()=>{
co.contracts.splice(idx,1);
renderContractTable(); toast('合同已删除','success');
});
}
/* ================= 选择物流公司弹窗(单选、选填:运单核算 / 货源) ================= */
function openPickDialog(mode){
pickMode = mode || 'account';
pickedIds.clear();
if (pickMode==='source' && sourceCompanyId) pickedIds.add(sourceCompanyId);
if (pickMode==='account' && accountCompanyId) pickedIds.add(accountCompanyId);
renderPickTable();
document.getElementById('mask-pick').classList.add('open');
}
function renderPickTable(){
const kw = document.getElementById('pick-kw').value.trim();
const rows = companies.filter(c=>c.status===1)
.filter(c=>!kw || c.name.includes(kw) || c.uscc.includes(kw));
const tbody = document.getElementById('pick-tbody');
if (!rows.length){
tbody.innerHTML = '<tr><td colspan="6" class="empty">未找到符合条件的物流公司</td></tr>';
} else {
tbody.innerHTML = rows.map(c=>{
const n = activeContractCount(c);
return `<tr>
<td><input type="radio" name="pick-radio" class="ck-pick" data-id="${c.id}" ${pickedIds.has(c.id)?'checked':''} onchange="pickChange(this)"></td>
<td>${esc(c.name)}</td>
<td>${esc(c.uscc)}</td>
<td>${esc(c.contact)}</td>
<td>${esc(c.phone)}</td>
<td>${n>0 ? n+' 份生效中' : '<span class="warn-txt">⚠ 无生效合同</span>'}</td>
</tr>`;
}).join('');
}
document.getElementById('pick-pg').textContent = '共 '+rows.length+' 条记录';
document.getElementById('pick-dialog-count').textContent = pickedIds.size;
}
function pickChange(box){
const id = Number(box.dataset.id);
if (box.checked){ pickedIds.clear(); pickedIds.add(id); } // 单选:替换上次选择
else pickedIds.delete(id);
document.getElementById('pick-dialog-count').textContent = pickedIds.size;
}
function confirmPick(){
closeDialog('mask-pick');
if (pickMode==='account'){
accountCompanyId = pickedIds.size ? [...pickedIds][0] : 0;
renderAccountPicked();
toast(accountCompanyId ? '已选择物流公司' : '已清除所选物流公司','success');
} else {
sourceCompanyId = pickedIds.size ? [...pickedIds][0] : 0;
renderSourcePicked();
toast(sourceCompanyId ? '已选择物流公司' : '已清除所选物流公司','success');
}
}
/* ================= 运费核算(在运单上核算,FR-07) ================= */
function renderAccountList(){
const noEl = document.getElementById('af-no');
const stEl = document.getElementById('af-status');
const kw = noEl ? noEl.value.trim() : '';
const st = stEl ? stEl.value : '';
const rows = waybills.map((w,i)=>({w,i})).filter(({w})=>(!kw || w.no.includes(kw)) && (!st || (st==='已核算') === !!w.accounted));
const tbody = document.getElementById('acc-tbody');
tbody.innerHTML = rows.map(({w,i})=>`<tr>
<td>${esc(w.no)}</td>
<td>${esc(w.goodsNo)}</td>
<td>${esc(w.goods)}</td>
<td>${esc(w.driver)}</td>
<td>${w.companyId ? esc(w.companyName) : '<span class="tip">无外协公司</span>'}</td>
<td>${w.accounted ? '<span class="tag green">已核算</span>' : '<span class="tag red">未核算</span>'}</td>
<td>${w.addr ? esc(w.addr) : '<span class="tip">-</span>'}</td>
<td><span class="link" onclick="openAccountDialog(${i})">修改核算</span></td>
</tr>`).join('');
document.getElementById('acc-pg').textContent = '共 '+rows.length+' 条记录';
}
function openAccountDialog(i){
accountIdx = i;
const w = waybills[i];
document.getElementById('acc-no').value = w.no;
document.getElementById('acc-goods').value = w.goods;
document.getElementById('acc-driver').value = w.driver;
accountCompanyId = w.companyId || 0;
renderAccountPicked();
document.getElementById('acc-addr').value = w.addr || '';
document.getElementById('mask-account').classList.add('open');
}
function renderAccountPicked(){
const tbody = document.getElementById('acc-picked-tbody');
const co = accountCompanyId ? getCompany(accountCompanyId) : null;
tbody.innerHTML = co
? `<tr><td>${esc(co.name)}</td><td>${esc(co.uscc)}</td><td><span class="link danger" onclick="clearAccountCompany()">移除</span></td></tr>`
: '<tr><td colspan="3" class="empty">未选择物流公司(无外协公司)</td></tr>';
}
function clearAccountCompany(){ accountCompanyId = 0; renderAccountPicked(); }
function saveAccount(account){
const w = waybills[accountIdx];
const addr = document.getElementById('acc-addr').value.trim();
if (addr.length > 200){ toast('客户收货地址不能超过200字符','error'); return; }
const co = accountCompanyId ? getCompany(accountCompanyId) : null;
if (co && co.status !== 1){ toast('保存失败:所选公司已被停用,请重新选择 —— '+co.name,'error'); return; }
w.companyId = accountCompanyId;
w.companyName = co ? co.name : '';
w.addr = addr;
if (account) w.accounted = true;
closeDialog('mask-account');
renderAccountList();
toast(account ? '核算完成:核算数据已写入运单' : '已保存:核算数据已写入运单','success');
}
/* ================= 货源选择物流公司(FR-08) ================= */
function renderSourcePicked(){
const tbody = document.getElementById('src-picked-tbody');
const co = sourceCompanyId ? getCompany(sourceCompanyId) : null;
if (!co){
tbody.innerHTML = '<tr><td colspan="4" class="empty">未选择物流公司(选填)</td></tr>';
return;
}
tbody.innerHTML = `<tr>
<td>${esc(co.name)}</td><td>${esc(co.uscc)}</td><td>${esc(co.contact)}</td>
<td><span class="link danger" onclick="removeSourcePicked()">移除</span></td>
</tr>`;
}
function removeSourcePicked(){ sourceCompanyId = 0; renderSourcePicked(); }
function saveSource(){
const goods = document.getElementById('src-goods').value.trim();
if (!goods){ toast('请填写「货物名称」','error'); return; }
const co = sourceCompanyId ? getCompany(sourceCompanyId) : null;
if (co && co.status!==1){ toast('保存失败:所选公司已被停用,请重新选择 —— '+co.name,'error'); return; }
toast('货源保存成功(演示):物流公司仅作运单记录'+(co?('('+co.name+')'):'(未选择)'),'success');
go('home');
}
let payTab = 'owner';
function gotoPay(t){ payTab = t; go('pay'); switchPayTab(t); }
function switchPayTab(t){ payTab = t; document.querySelectorAll('#page-pay .tab-item').forEach(x=>x.classList.toggle('active', x.dataset.paytab===t)); renderPaymentTable(); }
function goodsNoOf(p){ const wb = waybills.find(x=>x.no===p.waybill); return wb ? wb.goodsNo : '-'; }
/* ================= 打款列表(FR-09:物流公司筛选) ================= */
function initPayFilter(){
const sel = document.getElementById('pf-company');
if (sel.options.length) return;
sel.innerHTML = '<option value="">全部</option><option value="0">无外协公司</option>'
+ companies.map(c=>'<option value="'+c.id+'">'+esc(c.name)+(c.status?'':'(停用)')+'</option>').join('');
}
function renderPaymentTable(){
initPayFilter();
const cid = document.getElementById('pf-company').value;
const wb = document.getElementById('pf-waybill').value.trim();
const st = document.getElementById('pf-status').value;
const rows = payments.filter(p=>{
if (p.type !== payTab) return false;
if (cid !== ''){
const c = Number(cid);
if (c === 0){ if (p.companyId) return false; } // 无外协公司
else if (p.companyId !== c) return false;
}
if (wb && !p.waybill.includes(wb)) return false;
if (st && p.status !== st) return false;
return true;
});
const tbody = document.getElementById('pay-tbody');
if (!rows.length){
tbody.innerHTML = '<tr><td colspan="7" class="empty">未找到符合条件的打款记录</td></tr>';
} else {
const tagMap = {'已打款':'green','待打款':'orange','已驳回':'gray'};
tbody.innerHTML = rows.map(p=>`<tr>
<td>${esc(p.waybill)}</td>
<td>${esc(goodsNoOf(p))}</td>
<td>${esc(p.waybill)}</td>
<td>${p.companyId ? esc(p.companyName) : '<span class="tip">无外协公司</span>'}</td>
<td>${esc(p.driver)}</td>
<td>${p.amount.toFixed(2)}</td>
<td><span class="tag ${tagMap[p.status]||'gray'}">${esc(p.status)}</span></td>
<td>${esc(p.time)}</td>
</tr>`).join('');
}
document.getElementById('pay-pg').textContent = '共 '+rows.length+' 条记录';
}
function resetPayQuery(){
document.getElementById('pf-company').value='';
document.getElementById('pf-waybill').value='';
document.getElementById('pf-status').value='';
renderPaymentTable();
}
/* ================= 货源列表 / 运单管理(FR-10:显示物流公司列) ================= */
function renderGoodsTable(){
const kw = document.getElementById('gf-goods').value.trim();
const no = document.getElementById('gf-no').value.trim();
const rows = goodsSources.filter(g=>(!kw || g.goods.includes(kw)) && (!no || g.no.includes(no)));
const tbody = document.getElementById('goods-tbody');
if (!rows.length){
tbody.innerHTML = '<tr><td colspan="8" class="empty">未找到符合条件的货源</td></tr>';
} else {
tbody.innerHTML = rows.map(g=>`<tr>
<td>${esc(g.no)}</td>
<td>${esc(g.goods)}</td>
<td>${esc(g.from)}</td>
<td>${esc(g.to)}</td>
<td>${g.companyId ? esc(g.companyName) : '<span class="tip">无外协公司</span>'}</td>
<td>${esc(g.creator)}</td>
<td>${esc(g.createdAt)}</td>
<td><span class="link" onclick="todo()">编辑</span></td>
</tr>`).join('');
}
document.getElementById('goods-pg').textContent = '共 '+rows.length+' 条记录';
}
function resetGoodsQuery(){
document.getElementById('gf-goods').value='';
document.getElementById('gf-no').value='';
renderGoodsTable();
}
function renderWaybillTable(){
const no = document.getElementById('wf-no').value.trim();
const kw = document.getElementById('wf-goods').value.trim();
const rows = waybills.filter(w=>(!no || w.no.includes(no)) && (!kw || w.goods.includes(kw)));
const tbody = document.getElementById('waybill-tbody');
if (!rows.length){
tbody.innerHTML = '<tr><td colspan="7" class="empty">未找到符合条件的运单</td></tr>';
} else {
const tagMap = {'待运输':'gray','运输中':'blue','已卸货':'orange','已结算':'green'};
tbody.innerHTML = rows.map(w=>`<tr>
<td>${esc(w.no)}</td>
<td>${esc(w.goodsNo)}</td>
<td>${esc(w.goods)}</td>
<td>${esc(w.driver)}</td>
<td>${w.companyId ? esc(w.companyName) : '<span class="tip">无外协公司</span>'}</td>
<td><span class="tag ${tagMap[w.status]||'gray'}">${esc(w.status)}</span></td>
<td>${w.amount.toFixed(2)}</td>
</tr>`).join('');
}
document.getElementById('waybill-pg').textContent = '共 '+rows.length+' 条记录';
}
function resetWaybillQuery(){
document.getElementById('wf-no').value='';
document.getElementById('wf-goods').value='';
renderWaybillTable();
}
/* ================= 统计 ================= */
function renderStats(){
document.getElementById('stat-total').textContent = companies.length;
document.getElementById('stat-active').textContent = companies.filter(c=>c.status===1).length;
document.getElementById('stat-contracts').textContent = companies.reduce((s,c)=>s+activeContractCount(c),0);
}
/* ================= 初始化 ================= */
initTaxSelects();
waybills.forEach(w=>{ if (w.accounted === undefined){ w.accounted = false; w.addr = ''; } });
const PAY_TYPES = {'DK20260920-001':'owner','DK20260921-002':'finance','DK20260922-003':'owner',
'DK20260923-004':'finance','DK20260924-005':'owner','DK20260925-006':'finance'};
payments.forEach(p=>{ p.type = PAY_TYPES[p.no] || 'owner'; });
go('home');
</script>
</body>
</html>