|
|
@@ -0,0 +1,803 @@
|
|
|
+<!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>
|