Capture the agreed independent page (inline expand, per-side save, menu badge) for completing missing supplier/demander contracts after they became optional on create/edit. Co-authored-by: Cursor <cursoragent@cursor.com>
804 lines
33 KiB
HTML
804 lines
33 KiB
HTML
<!doctype html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||
<title>Fresh Mint · 运输计划合同完善 Demo</title>
|
||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
|
||
<style>
|
||
:root {
|
||
--primary: #10b981;
|
||
--primary-dark: #059669;
|
||
--primary-bg: #ecfdf5;
|
||
--text: #1e293b;
|
||
--text-muted: #64748b;
|
||
--border: #d9e8e2;
|
||
--surface: #ffffff;
|
||
--table-head: #f8fafc;
|
||
--bg-page: #f8fafc;
|
||
--row-alt: #fafafa;
|
||
--row-hover: #f1f5f9;
|
||
--shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
|
||
--shadow-lg: 0 12px 40px rgba(15, 23, 42, 0.12);
|
||
--radius: 10px;
|
||
--warn: #d97706;
|
||
--warn-bg: #fffbeb;
|
||
--danger: #dc2626;
|
||
--danger-bg: #fef2f2;
|
||
}
|
||
|
||
* { box-sizing: border-box; }
|
||
body {
|
||
margin: 0;
|
||
min-height: 100vh;
|
||
font-family: Inter, -apple-system, BlinkMacSystemFont, sans-serif;
|
||
font-size: 14px;
|
||
line-height: 1.5;
|
||
color: var(--text);
|
||
background: var(--bg-page);
|
||
}
|
||
|
||
.demo-page { max-width: 1280px; margin: 0 auto; padding: 28px 24px 100px; }
|
||
.demo-head { margin-bottom: 24px; }
|
||
.demo-head h1 { margin: 0 0 8px; font-size: 24px; font-weight: 700; letter-spacing: -0.02em; }
|
||
.demo-head h1 span { color: var(--primary); }
|
||
.demo-head p { margin: 0 0 8px; color: var(--text-muted); max-width: 880px; }
|
||
.meta { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }
|
||
.pill {
|
||
display: inline-flex; align-items: center; gap: 6px;
|
||
padding: 4px 10px; border-radius: 999px; font-size: 12px; font-weight: 500;
|
||
background: var(--primary-bg); color: var(--primary-dark); border: 1px solid #a7f3d0;
|
||
}
|
||
.pill.muted { background: #f1f5f9; color: var(--text-muted); border-color: #e2e8f0; }
|
||
.pill.warn { background: var(--warn-bg); color: var(--warn); border-color: rgba(217,119,6,.25); }
|
||
|
||
.brief {
|
||
display: grid; grid-template-columns: 1fr 1fr; gap: 14px; margin-bottom: 22px;
|
||
}
|
||
@media (max-width: 900px) { .brief { grid-template-columns: 1fr; } }
|
||
.card {
|
||
background: var(--surface); border: 1px solid var(--border);
|
||
border-radius: var(--radius); padding: 16px 18px; box-shadow: var(--shadow);
|
||
}
|
||
.card h2 {
|
||
margin: 0 0 10px; font-size: 15px; font-weight: 600;
|
||
padding-left: 10px; border-left: 3px solid var(--primary);
|
||
color: var(--primary-dark); line-height: 1.2;
|
||
}
|
||
.card ul { margin: 0; padding-left: 18px; color: var(--text-muted); }
|
||
.card li { margin: 5px 0; }
|
||
.card li strong { color: var(--text); }
|
||
|
||
.scheme-tabs {
|
||
display: flex; gap: 8px; margin-bottom: 14px; flex-wrap: wrap;
|
||
}
|
||
.scheme-tab {
|
||
appearance: none; border: 1px solid var(--border); background: #fff;
|
||
padding: 8px 14px; border-radius: 8px; font-size: 13px; font-weight: 500;
|
||
color: var(--text-muted); cursor: pointer;
|
||
}
|
||
.scheme-tab.active {
|
||
color: var(--primary-dark); border-color: #6ee7b7; background: var(--primary-bg);
|
||
}
|
||
.scheme-pane { display: none; }
|
||
.scheme-pane.active { display: block; }
|
||
|
||
.approach-note {
|
||
margin: 0 0 14px; padding: 10px 14px; border-radius: 8px;
|
||
background: #f8fafc; border: 1px dashed #cbd5e1; color: var(--text-muted); font-size: 13px;
|
||
}
|
||
.approach-note strong { color: var(--text); }
|
||
.approach-note.rec {
|
||
background: var(--primary-bg); border-color: #6ee7b7; border-style: solid;
|
||
color: var(--primary-dark);
|
||
}
|
||
|
||
/* ── 列表壳(对齐 web-admin panel) ── */
|
||
.panel {
|
||
background: var(--surface); border: 1px solid var(--border);
|
||
border-radius: var(--radius); box-shadow: var(--shadow); overflow: hidden;
|
||
}
|
||
.panel-top {
|
||
display: flex; align-items: center; justify-content: space-between;
|
||
gap: 12px; padding: 16px 20px 12px; border-bottom: 1px solid #eef2f6;
|
||
}
|
||
.panel-top h2 {
|
||
margin: 0; font-size: 16px; font-weight: 600;
|
||
display: flex; align-items: center; gap: 0;
|
||
}
|
||
.panel-top h2::before {
|
||
content: ""; display: inline-block; width: 3px; height: 16px;
|
||
background: var(--primary); border-radius: 2px; margin-right: 10px;
|
||
}
|
||
.stat-chip {
|
||
display: inline-flex; align-items: center; gap: 6px;
|
||
padding: 4px 10px; border-radius: 8px; font-size: 12px; font-weight: 600;
|
||
background: var(--warn-bg); color: var(--warn); border: 1px solid rgba(217,119,6,.2);
|
||
}
|
||
|
||
.filter-block { padding: 14px 20px 6px; }
|
||
.filter-grid {
|
||
display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px 16px;
|
||
}
|
||
@media (max-width: 1100px) { .filter-grid { grid-template-columns: repeat(2, 1fr); } }
|
||
.field label {
|
||
display: block; font-size: 11px; color: var(--text-muted); margin-bottom: 4px;
|
||
}
|
||
.field input, .field select {
|
||
width: 100%; height: 36px; border: 1px solid var(--border); border-radius: 8px;
|
||
padding: 0 10px; font-size: 14px; color: var(--text); background: #fff;
|
||
}
|
||
.filter-actions {
|
||
display: flex; justify-content: flex-end; gap: 8px; padding: 10px 0 4px;
|
||
}
|
||
|
||
.list-toolbar {
|
||
display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
|
||
padding: 10px 20px; border-top: 1px solid #eef2f6;
|
||
}
|
||
.list-toolbar .hint { color: var(--text-muted); font-size: 13px; margin-right: auto; }
|
||
|
||
.btn {
|
||
appearance: none; display: inline-flex; align-items: center; gap: 6px;
|
||
height: 36px; padding: 0 14px; border-radius: 8px; font-size: 13px; font-weight: 500;
|
||
cursor: pointer; border: 1px solid var(--border); background: #fff; color: var(--text);
|
||
}
|
||
.btn:hover { background: #f8fafc; }
|
||
.btn-main {
|
||
background: var(--primary); border-color: var(--primary); color: #fff;
|
||
}
|
||
.btn-main:hover { background: var(--primary-dark); }
|
||
.btn-ghost { background: #fff; }
|
||
.btn-link {
|
||
height: auto; padding: 0; border: none; background: transparent;
|
||
color: var(--primary-dark); font-weight: 500;
|
||
}
|
||
.btn-link:hover { text-decoration: underline; background: transparent; }
|
||
.btn-sm { height: 30px; padding: 0 10px; font-size: 12px; }
|
||
.btn.warn-outline {
|
||
border-color: rgba(217,119,6,.35); color: var(--warn); background: var(--warn-bg);
|
||
}
|
||
|
||
.list-body { border-top: 1px solid #eef2f6; }
|
||
table.list-table {
|
||
width: 100%; border-collapse: collapse; table-layout: fixed;
|
||
}
|
||
.list-table th {
|
||
background: var(--table-head); text-align: left; font-weight: 600; font-size: 12px;
|
||
color: var(--text-muted); padding: 10px 12px; border-bottom: 1px solid #eef2f6;
|
||
white-space: nowrap;
|
||
}
|
||
.list-table td {
|
||
padding: 12px; border-bottom: 1px solid #f1f5f9; vertical-align: top;
|
||
font-size: 13px;
|
||
}
|
||
.list-table tbody tr:nth-child(even) { background: var(--row-alt); }
|
||
.list-table tbody tr:hover { background: var(--row-hover); }
|
||
.list-table .mono { font-variant-numeric: tabular-nums; font-weight: 600; color: var(--primary-dark); }
|
||
.list-table .muted { color: var(--text-muted); }
|
||
.list-table .party { display: flex; flex-direction: column; gap: 2px; }
|
||
.list-table .party .name { font-weight: 500; }
|
||
.list-table .party .sub { font-size: 12px; color: var(--text-muted); }
|
||
|
||
.tag {
|
||
display: inline-flex; align-items: center; padding: 2px 8px;
|
||
border-radius: 6px; font-size: 12px; font-weight: 500; white-space: nowrap;
|
||
}
|
||
.tag.success { background: #059669; color: #fff; }
|
||
.tag.warn { background: #d97706; color: #fff; }
|
||
.tag.danger { background: #dc2626; color: #fff; }
|
||
.tag.info { background: transparent; color: #1e293b; border: 1px solid #e2e8f0; }
|
||
.tag.muted { background: #f1f5f9; color: var(--text-muted); }
|
||
|
||
.gap-tags { display: flex; flex-wrap: wrap; gap: 6px; }
|
||
.ops { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
|
||
|
||
.pager {
|
||
display: flex; align-items: center; justify-content: flex-end; gap: 12px;
|
||
padding: 12px 20px; border-top: 1px solid #eef2f6; color: var(--text-muted); font-size: 13px;
|
||
}
|
||
|
||
/* 方案 B:行内展开补合同 */
|
||
.expand-row td { background: #f8fffb !important; padding: 0 !important; }
|
||
.expand-panel { padding: 14px 16px 16px; border-top: 1px dashed #a7f3d0; }
|
||
.expand-grid {
|
||
display: grid; grid-template-columns: 1fr 1fr; gap: 14px;
|
||
}
|
||
@media (max-width: 900px) { .expand-grid { grid-template-columns: 1fr; } }
|
||
.side-box {
|
||
background: #fff; border: 1px solid var(--border); border-radius: 8px; padding: 12px;
|
||
}
|
||
.side-box.need { border-color: rgba(217,119,6,.35); background: #fffdf8; }
|
||
.side-box.ok { border-color: #a7f3d0; background: #f6fffb; }
|
||
.side-box.exempt { border-color: #e2e8f0; background: #f8fafc; }
|
||
.side-box h4 {
|
||
margin: 0 0 8px; font-size: 13px; display: flex; align-items: center;
|
||
justify-content: space-between; gap: 8px;
|
||
}
|
||
.side-box .customer { font-size: 13px; margin-bottom: 10px; }
|
||
.contract-chip {
|
||
display: flex; align-items: center; justify-content: space-between; gap: 8px;
|
||
padding: 8px 10px; border: 1px solid #e2e8f0; border-radius: 8px;
|
||
background: #fff; margin-bottom: 8px; font-size: 12px;
|
||
}
|
||
.contract-chip .no { font-weight: 600; color: var(--primary-dark); }
|
||
.empty-slot {
|
||
padding: 14px; border: 1px dashed #cbd5e1; border-radius: 8px;
|
||
text-align: center; color: var(--text-muted); font-size: 12px; margin-bottom: 8px;
|
||
}
|
||
.side-actions { display: flex; gap: 8px; flex-wrap: wrap; }
|
||
|
||
/* 弹窗 */
|
||
.modal-mask {
|
||
display: none; position: fixed; inset: 0; background: rgba(15, 23, 42, 0.45);
|
||
z-index: 1000; align-items: center; justify-content: center; padding: 24px;
|
||
backdrop-filter: none;
|
||
}
|
||
.modal-mask.open { display: flex; }
|
||
.dialog {
|
||
width: min(920px, 100%); max-height: calc(100vh - 100px);
|
||
background: #fff; border-radius: 12px; box-shadow: var(--shadow-lg);
|
||
display: flex; flex-direction: column; overflow: hidden;
|
||
}
|
||
.dialog-head {
|
||
display: flex; align-items: center; justify-content: space-between;
|
||
padding: 14px 20px; border-bottom: 1px solid #eef2f6; flex-shrink: 0;
|
||
}
|
||
.dialog-head h3 { margin: 0; font-size: 16px; font-weight: 600; }
|
||
.dialog-body { padding: 18px 20px; overflow: auto; flex: 1; }
|
||
.dialog-foot {
|
||
display: flex; justify-content: flex-end; gap: 8px;
|
||
padding: 12px 20px; border-top: 1px solid #eef2f6; flex-shrink: 0;
|
||
}
|
||
|
||
.plan-summary {
|
||
display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px;
|
||
margin-bottom: 16px; padding: 12px; background: #f8fafc; border-radius: 8px;
|
||
border: 1px solid #eef2f6;
|
||
}
|
||
@media (max-width: 800px) { .plan-summary { grid-template-columns: 1fr 1fr; } }
|
||
.kv .k { font-size: 11px; color: var(--text-muted); }
|
||
.kv .v { font-size: 13px; font-weight: 500; margin-top: 2px; }
|
||
|
||
.party-sections { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
|
||
@media (max-width: 800px) { .party-sections { grid-template-columns: 1fr; } }
|
||
|
||
.selector-list { max-height: 220px; overflow: auto; border: 1px solid var(--border); border-radius: 8px; }
|
||
.selector-item {
|
||
display: flex; align-items: flex-start; gap: 10px;
|
||
padding: 10px 12px; border-bottom: 1px solid #f1f5f9; cursor: pointer;
|
||
}
|
||
.selector-item:last-child { border-bottom: none; }
|
||
.selector-item:hover { background: var(--primary-bg); }
|
||
.selector-item.selected { background: var(--primary-bg); }
|
||
.selector-item .meta-line { font-size: 12px; color: var(--text-muted); margin-top: 2px; }
|
||
.selector-item input { margin-top: 3px; }
|
||
|
||
.legend {
|
||
margin-top: 22px; display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px;
|
||
}
|
||
@media (max-width: 900px) { .legend { grid-template-columns: 1fr; } }
|
||
.legend .card h3 { margin: 0 0 8px; font-size: 14px; }
|
||
.legend code {
|
||
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
||
font-size: 12px; background: #f1f5f9; padding: 1px 6px; border-radius: 4px;
|
||
}
|
||
|
||
.toast {
|
||
position: fixed; top: 20px; left: 50%; transform: translateX(-50%) translateY(-120%);
|
||
background: #059669; color: #fff; padding: 10px 18px; border-radius: 8px;
|
||
font-size: 13px; font-weight: 500; box-shadow: var(--shadow-lg);
|
||
transition: transform .25s ease; z-index: 2000;
|
||
}
|
||
.toast.show { transform: translateX(-50%) translateY(0); }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="demo-page">
|
||
<header class="demo-head">
|
||
<h1>运输计划 · <span>合同完善</span> 界面 Demo</h1>
|
||
<p>
|
||
仅展示「缺合同」的运输计划。判定规则:供需双方若非内置公司
|
||
(安庆市山峡化工有限公司 / 安徽云栋化工有限公司),该侧至少需关联 1 份合同;
|
||
内置公司该侧合同可免。本页只做<strong>关联已有合同</strong>,不支持新增/编辑合同。
|
||
</p>
|
||
<div class="meta">
|
||
<span class="pill">Fresh Mint · web-admin</span>
|
||
<span class="pill muted">参考模式:P02 复杂列表 + 关联弹窗</span>
|
||
<span class="pill warn">可点击交互 Demo</span>
|
||
<span class="pill muted">菜单建议:运输计划模块下</span>
|
||
</div>
|
||
</header>
|
||
|
||
<section class="brief">
|
||
<div class="card">
|
||
<h2>业务目标</h2>
|
||
<ul>
|
||
<li>创建计划时合同改为非必填,事后用本页<strong>快速补齐</strong></li>
|
||
<li>列表默认只显示缺口:缺供方 / 缺需方 / 两侧都缺</li>
|
||
<li>行内操作「补充合同」→ 打开选择器关联 CRM 已有合同</li>
|
||
<li>补齐后自动从本列表消失(或刷新后消失)</li>
|
||
</ul>
|
||
</div>
|
||
<div class="card">
|
||
<h2>本 Demo 两种交互</h2>
|
||
<ul>
|
||
<li><strong>方案 A(推荐)</strong>:列表 +「补充合同」弹窗,两侧分区关联</li>
|
||
<li><strong>方案 B</strong>:列表行展开,直接在行内补两侧合同</li>
|
||
<li>下方可切换方案,并点击按钮体验弹窗</li>
|
||
</ul>
|
||
</div>
|
||
</section>
|
||
|
||
<div class="scheme-tabs" role="tablist">
|
||
<button type="button" class="scheme-tab active" data-scheme="a">方案 A · 列表 + 弹窗(推荐)</button>
|
||
<button type="button" class="scheme-tab" data-scheme="b">方案 B · 行内展开</button>
|
||
</div>
|
||
|
||
<!-- ═══ 方案 A ═══ -->
|
||
<div class="scheme-pane active" id="scheme-a">
|
||
<p class="approach-note rec">
|
||
<strong>推荐:</strong>与现有「危化品证上传」「运输计划」列表一致;弹窗内可专注选合同,
|
||
不打断表格滚动;便于后续加菜单角标(待完善数量)。
|
||
</p>
|
||
|
||
<section class="panel">
|
||
<div class="panel-top">
|
||
<h2>运输计划合同完善</h2>
|
||
<span class="stat-chip">待完善 3 条</span>
|
||
</div>
|
||
|
||
<div class="filter-block">
|
||
<div class="filter-grid">
|
||
<div class="field">
|
||
<label>计划编号</label>
|
||
<input placeholder="搜索计划编号" />
|
||
</div>
|
||
<div class="field">
|
||
<label>缺口类型</label>
|
||
<select>
|
||
<option>全部缺口</option>
|
||
<option>仅缺供方合同</option>
|
||
<option>仅缺需方合同</option>
|
||
<option>两侧均缺</option>
|
||
</select>
|
||
</div>
|
||
<div class="field">
|
||
<label>供方 / 需方</label>
|
||
<input placeholder="客户名称" />
|
||
</div>
|
||
<div class="field">
|
||
<label>计划日期</label>
|
||
<input type="text" placeholder="开始日期 - 结束日期" />
|
||
</div>
|
||
</div>
|
||
<div class="filter-actions">
|
||
<button type="button" class="btn btn-ghost">重置</button>
|
||
<button type="button" class="btn btn-main">搜索</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="list-toolbar">
|
||
<span class="hint">仅展示仍缺合同的运输计划 · 内置公司侧已自动豁免</span>
|
||
<button type="button" class="btn btn-ghost">刷新</button>
|
||
</div>
|
||
|
||
<div class="list-body">
|
||
<table class="list-table">
|
||
<colgroup>
|
||
<col style="width: 12%" />
|
||
<col style="width: 9%" />
|
||
<col style="width: 18%" />
|
||
<col style="width: 18%" />
|
||
<col style="width: 14%" />
|
||
<col style="width: 12%" />
|
||
<col style="width: 17%" />
|
||
</colgroup>
|
||
<thead>
|
||
<tr>
|
||
<th>计划编号</th>
|
||
<th>计划日期</th>
|
||
<th>供方</th>
|
||
<th>需方</th>
|
||
<th>合同缺口</th>
|
||
<th>跟进状态</th>
|
||
<th>操作</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td><a class="mono btn-link" href="javascript:void(0)">YP20260820001</a></td>
|
||
<td>2026-08-20</td>
|
||
<td>
|
||
<div class="party">
|
||
<span class="name">合肥某某商贸有限公司</span>
|
||
<span class="sub">供方合同:未关联</span>
|
||
</div>
|
||
</td>
|
||
<td>
|
||
<div class="party">
|
||
<span class="name">安庆市山峡化工有限公司</span>
|
||
<span class="sub">内置 · 合同可免</span>
|
||
</div>
|
||
</td>
|
||
<td>
|
||
<div class="gap-tags">
|
||
<span class="tag warn">缺供方合同</span>
|
||
</div>
|
||
</td>
|
||
<td><span class="tag success">已安排</span></td>
|
||
<td>
|
||
<div class="ops">
|
||
<button type="button" class="btn btn-sm warn-outline" data-open-dialog="row1">补充合同</button>
|
||
<button type="button" class="btn-link btn-sm">查看计划</button>
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<td><a class="mono btn-link" href="javascript:void(0)">YP20260819088</a></td>
|
||
<td>2026-08-19</td>
|
||
<td>
|
||
<div class="party">
|
||
<span class="name">安庆市山峡化工有限公司</span>
|
||
<span class="sub">内置 · 合同可免</span>
|
||
</div>
|
||
</td>
|
||
<td>
|
||
<div class="party">
|
||
<span class="name">芜湖某某材料有限公司</span>
|
||
<span class="sub">需方合同:未关联</span>
|
||
</div>
|
||
</td>
|
||
<td>
|
||
<div class="gap-tags">
|
||
<span class="tag warn">缺需方合同</span>
|
||
</div>
|
||
</td>
|
||
<td><span class="tag info">待安排</span></td>
|
||
<td>
|
||
<div class="ops">
|
||
<button type="button" class="btn btn-sm warn-outline" data-open-dialog="row2">补充合同</button>
|
||
<button type="button" class="btn-link btn-sm">查看计划</button>
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<td><a class="mono btn-link" href="javascript:void(0)">YP20260818042</a></td>
|
||
<td>2026-08-18</td>
|
||
<td>
|
||
<div class="party">
|
||
<span class="name">马鞍山某某化工有限公司</span>
|
||
<span class="sub">供方合同:未关联</span>
|
||
</div>
|
||
</td>
|
||
<td>
|
||
<div class="party">
|
||
<span class="name">铜陵某某实业有限公司</span>
|
||
<span class="sub">需方合同:未关联</span>
|
||
</div>
|
||
</td>
|
||
<td>
|
||
<div class="gap-tags">
|
||
<span class="tag danger">两侧均缺</span>
|
||
</div>
|
||
</td>
|
||
<td><span class="tag success">运输中</span></td>
|
||
<td>
|
||
<div class="ops">
|
||
<button type="button" class="btn btn-sm warn-outline" data-open-dialog="row3">补充合同</button>
|
||
<button type="button" class="btn-link btn-sm">查看计划</button>
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
<div class="pager">共 3 条 · 每页 20 条 · 1 / 1</div>
|
||
</section>
|
||
</div>
|
||
|
||
<!-- ═══ 方案 B ═══ -->
|
||
<div class="scheme-pane" id="scheme-b">
|
||
<p class="approach-note">
|
||
<strong>备选:</strong>适合一次补几条、不想频繁开弹窗的场景;表格变高,窄屏体验略差。
|
||
</p>
|
||
|
||
<section class="panel">
|
||
<div class="panel-top">
|
||
<h2>运输计划合同完善</h2>
|
||
<span class="stat-chip">待完善 2 条(示例)</span>
|
||
</div>
|
||
<div class="list-toolbar">
|
||
<span class="hint">点击「展开补充」在行内关联合同</span>
|
||
</div>
|
||
<div class="list-body">
|
||
<table class="list-table">
|
||
<thead>
|
||
<tr>
|
||
<th style="width:12%">计划编号</th>
|
||
<th style="width:10%">计划日期</th>
|
||
<th style="width:22%">供方</th>
|
||
<th style="width:22%">需方</th>
|
||
<th style="width:14%">合同缺口</th>
|
||
<th style="width:20%">操作</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td class="mono">YP20260820001</td>
|
||
<td>2026-08-20</td>
|
||
<td>合肥某某商贸有限公司</td>
|
||
<td>安庆市山峡化工有限公司</td>
|
||
<td><span class="tag warn">缺供方合同</span></td>
|
||
<td><button type="button" class="btn btn-sm warn-outline" id="toggle-expand">收起补充</button></td>
|
||
</tr>
|
||
<tr class="expand-row" id="expand-demo">
|
||
<td colspan="6">
|
||
<div class="expand-panel">
|
||
<div class="expand-grid">
|
||
<div class="side-box need">
|
||
<h4>
|
||
供方合同
|
||
<span class="tag warn">待补充</span>
|
||
</h4>
|
||
<div class="customer">合肥某某商贸有限公司</div>
|
||
<div class="empty-slot">暂未关联合同</div>
|
||
<div class="side-actions">
|
||
<button type="button" class="btn btn-sm btn-main" data-open-dialog="row1">选择合同</button>
|
||
</div>
|
||
</div>
|
||
<div class="side-box exempt">
|
||
<h4>
|
||
需方合同
|
||
<span class="tag muted">可免</span>
|
||
</h4>
|
||
<div class="customer">安庆市山峡化工有限公司(内置)</div>
|
||
<div class="empty-slot" style="border-style:solid;background:#f8fafc;">
|
||
内置公司,无需关联合同
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<td class="mono">YP20260818042</td>
|
||
<td>2026-08-18</td>
|
||
<td>马鞍山某某化工有限公司</td>
|
||
<td>铜陵某某实业有限公司</td>
|
||
<td><span class="tag danger">两侧均缺</span></td>
|
||
<td><button type="button" class="btn btn-sm warn-outline" data-open-dialog="row3">展开补充</button></td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</section>
|
||
</div>
|
||
|
||
<section class="legend">
|
||
<div class="card">
|
||
<h3>缺口判定(与内置名单一致)</h3>
|
||
<ul>
|
||
<li>名称 ∈ {山峡, 云栋} → 该侧<strong>不要求</strong>合同</li>
|
||
<li>否则 → 该侧 <code>contracts.length ≥ 1</code></li>
|
||
<li>任一侧不满足 → 进入本列表</li>
|
||
</ul>
|
||
</div>
|
||
<div class="card">
|
||
<h3>本页能力边界</h3>
|
||
<ul>
|
||
<li>✅ 筛选、列表、关联已有合同、保存</li>
|
||
<li>✅ 跳转查看运输计划(只读)</li>
|
||
<li>❌ 不在此页新增 / 编辑 / 审批合同</li>
|
||
</ul>
|
||
</div>
|
||
<div class="card">
|
||
<h3>待你确认(下一步提问)</h3>
|
||
<ul>
|
||
<li>是否含已作废计划?归档计划?</li>
|
||
<li>菜单角标是否显示待完善数量?</li>
|
||
<li>关联时是否允许多份合同?</li>
|
||
</ul>
|
||
</div>
|
||
</section>
|
||
</div>
|
||
|
||
<!-- 补充合同弹窗 -->
|
||
<div class="modal-mask" id="dialog-mask" role="dialog" aria-modal="true">
|
||
<div class="dialog">
|
||
<div class="dialog-head">
|
||
<h3>补充合同 · <span id="dlg-plan-no">YP20260820001</span></h3>
|
||
<button type="button" class="btn-link" id="dlg-close" aria-label="关闭">✕</button>
|
||
</div>
|
||
<div class="dialog-body">
|
||
<div class="plan-summary">
|
||
<div class="kv"><div class="k">计划日期</div><div class="v" id="dlg-date">2026-08-20</div></div>
|
||
<div class="kv"><div class="k">跟进状态</div><div class="v"><span class="tag success">已安排</span></div></div>
|
||
<div class="kv"><div class="k">供方</div><div class="v" id="dlg-supplier">合肥某某商贸有限公司</div></div>
|
||
<div class="kv"><div class="k">需方</div><div class="v" id="dlg-demander">安庆市山峡化工有限公司</div></div>
|
||
</div>
|
||
|
||
<div class="party-sections">
|
||
<div class="side-box need" id="dlg-supplier-box">
|
||
<h4>供方合同 <span class="tag warn" id="dlg-supplier-tag">待补充</span></h4>
|
||
<div class="customer" id="dlg-supplier-name">合肥某某商贸有限公司</div>
|
||
<div id="dlg-supplier-selected" class="empty-slot">暂未选择 · 下方勾选后确认</div>
|
||
<div class="selector-list" id="dlg-supplier-list">
|
||
<label class="selector-item">
|
||
<input type="checkbox" name="sc" value="c1" />
|
||
<div>
|
||
<div class="no" style="font-weight:600;color:var(--primary-dark)">HT-2026-0312</div>
|
||
<div class="meta-line">销售合同 · 执行中 · 液碱 32%</div>
|
||
</div>
|
||
</label>
|
||
<label class="selector-item">
|
||
<input type="checkbox" name="sc" value="c2" />
|
||
<div>
|
||
<div class="no" style="font-weight:600;color:var(--primary-dark)">HT-2026-0288</div>
|
||
<div class="meta-line">框架合同 · 已审批 · 盐酸</div>
|
||
</div>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="side-box exempt" id="dlg-demander-box">
|
||
<h4>需方合同 <span class="tag muted" id="dlg-demander-tag">可免</span></h4>
|
||
<div class="customer" id="dlg-demander-name">安庆市山峡化工有限公司(内置)</div>
|
||
<div class="empty-slot" style="border-style:solid;">内置公司,无需选择合同</div>
|
||
<div class="selector-list" id="dlg-demander-list" style="display:none;"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="dialog-foot">
|
||
<button type="button" class="btn btn-ghost" id="dlg-cancel">取消</button>
|
||
<button type="button" class="btn btn-main" id="dlg-save">保存关联</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="toast" id="toast">已保存合同关联(Demo)</div>
|
||
|
||
<script>
|
||
const scenarios = {
|
||
row1: {
|
||
planNo: 'YP20260820001',
|
||
date: '2026-08-20',
|
||
supplier: '合肥某某商贸有限公司',
|
||
demander: '安庆市山峡化工有限公司',
|
||
supplierNeed: true,
|
||
demanderNeed: false,
|
||
supplierExempt: false,
|
||
demanderExempt: true
|
||
},
|
||
row2: {
|
||
planNo: 'YP20260819088',
|
||
date: '2026-08-19',
|
||
supplier: '安庆市山峡化工有限公司',
|
||
demander: '芜湖某某材料有限公司',
|
||
supplierNeed: false,
|
||
demanderNeed: true,
|
||
supplierExempt: true,
|
||
demanderExempt: false
|
||
},
|
||
row3: {
|
||
planNo: 'YP20260818042',
|
||
date: '2026-08-18',
|
||
supplier: '马鞍山某某化工有限公司',
|
||
demander: '铜陵某某实业有限公司',
|
||
supplierNeed: true,
|
||
demanderNeed: true,
|
||
supplierExempt: false,
|
||
demanderExempt: false
|
||
}
|
||
}
|
||
|
||
const mask = document.getElementById('dialog-mask')
|
||
|
||
function paintSide(prefix, need, exempt, name) {
|
||
const box = document.getElementById('dlg-' + prefix + '-box')
|
||
const tag = document.getElementById('dlg-' + prefix + '-tag')
|
||
const list = document.getElementById('dlg-' + prefix + '-list')
|
||
const selected = document.getElementById('dlg-' + prefix + '-selected')
|
||
document.getElementById('dlg-' + prefix + '-name').textContent =
|
||
exempt ? name + '(内置)' : name
|
||
|
||
box.classList.remove('need', 'ok', 'exempt')
|
||
if (exempt) {
|
||
box.classList.add('exempt')
|
||
tag.className = 'tag muted'
|
||
tag.textContent = '可免'
|
||
if (selected) {
|
||
selected.className = 'empty-slot'
|
||
selected.style.borderStyle = 'solid'
|
||
selected.textContent = '内置公司,无需选择合同'
|
||
}
|
||
if (list) list.style.display = 'none'
|
||
} else if (need) {
|
||
box.classList.add('need')
|
||
tag.className = 'tag warn'
|
||
tag.textContent = '待补充'
|
||
if (selected) {
|
||
selected.className = 'empty-slot'
|
||
selected.style.borderStyle = ''
|
||
selected.textContent = '暂未选择 · 下方勾选后确认'
|
||
}
|
||
if (list) {
|
||
list.style.display = ''
|
||
list.innerHTML = `
|
||
<label class="selector-item">
|
||
<input type="checkbox" name="${prefix}" value="a" />
|
||
<div>
|
||
<div style="font-weight:600;color:var(--primary-dark)">HT-2026-0312</div>
|
||
<div class="meta-line">销售合同 · 执行中 · 示例产品 A</div>
|
||
</div>
|
||
</label>
|
||
<label class="selector-item">
|
||
<input type="checkbox" name="${prefix}" value="b" />
|
||
<div>
|
||
<div style="font-weight:600;color:var(--primary-dark)">HT-2026-0288</div>
|
||
<div class="meta-line">框架合同 · 已审批 · 示例产品 B</div>
|
||
</div>
|
||
</label>`
|
||
}
|
||
} else {
|
||
box.classList.add('ok')
|
||
tag.className = 'tag success'
|
||
tag.textContent = '已齐'
|
||
if (list) list.style.display = 'none'
|
||
}
|
||
}
|
||
|
||
function openDialog(key) {
|
||
const s = scenarios[key] || scenarios.row1
|
||
document.getElementById('dlg-plan-no').textContent = s.planNo
|
||
document.getElementById('dlg-date').textContent = s.date
|
||
document.getElementById('dlg-supplier').textContent = s.supplier
|
||
document.getElementById('dlg-demander').textContent = s.demander
|
||
paintSide('supplier', s.supplierNeed, s.supplierExempt, s.supplier)
|
||
paintSide('demander', s.demanderNeed, s.demanderExempt, s.demander)
|
||
mask.classList.add('open')
|
||
}
|
||
|
||
function closeDialog() {
|
||
mask.classList.remove('open')
|
||
}
|
||
|
||
document.querySelectorAll('.scheme-tab').forEach(tab => {
|
||
tab.addEventListener('click', () => {
|
||
document.querySelectorAll('.scheme-tab').forEach(t => t.classList.remove('active'))
|
||
document.querySelectorAll('.scheme-pane').forEach(p => p.classList.remove('active'))
|
||
tab.classList.add('active')
|
||
document.getElementById('scheme-' + tab.dataset.scheme).classList.add('active')
|
||
})
|
||
})
|
||
|
||
document.querySelectorAll('[data-open-dialog]').forEach(btn => {
|
||
btn.addEventListener('click', () => openDialog(btn.dataset.openDialog))
|
||
})
|
||
|
||
document.getElementById('dlg-close').addEventListener('click', closeDialog)
|
||
document.getElementById('dlg-cancel').addEventListener('click', closeDialog)
|
||
mask.addEventListener('click', e => { if (e.target === mask) closeDialog() })
|
||
|
||
document.getElementById('dlg-save').addEventListener('click', () => {
|
||
closeDialog()
|
||
const toast = document.getElementById('toast')
|
||
toast.classList.add('show')
|
||
setTimeout(() => toast.classList.remove('show'), 1800)
|
||
})
|
||
|
||
const expandBtn = document.getElementById('toggle-expand')
|
||
const expandRow = document.getElementById('expand-demo')
|
||
if (expandBtn && expandRow) {
|
||
expandBtn.addEventListener('click', () => {
|
||
const hidden = expandRow.style.display === 'none'
|
||
expandRow.style.display = hidden ? '' : 'none'
|
||
expandBtn.textContent = hidden ? '收起补充' : '展开补充'
|
||
})
|
||
}
|
||
</script>
|
||
</body>
|
||
</html>
|