Files
shanxia2.0/docs/fresh-mint-pl-plan-contract-complete-demo.html
T
zlwuandCursor f9f32a8b68 docs: add transport plan contract completion design and UI demo
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>
2026-08-20 11:49:34 +08:00

804 lines
33 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.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>