|
|
@@ -0,0 +1,2589 @@
|
|
|
+<!DOCTYPE html>
|
|
|
+<html lang="zh-CN">
|
|
|
+<head>
|
|
|
+ <meta charset="UTF-8" />
|
|
|
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
|
|
+ <title>山峡化工 · 运输计划移动端重设计 Demo</title>
|
|
|
+ <style>
|
|
|
+ :root {
|
|
|
+ --brand: #0a9f6d;
|
|
|
+ --brand-dark: #087f57;
|
|
|
+ --brand-soft: #e9f9f2;
|
|
|
+ --barley: #d6a343;
|
|
|
+ --barley-soft: #fff7e8;
|
|
|
+ --bg: #f3f6f5;
|
|
|
+ --surface: #fff;
|
|
|
+ --text: #1f2a26;
|
|
|
+ --muted: #6b7b74;
|
|
|
+ --border: #dcebe4;
|
|
|
+ --danger: #d14343;
|
|
|
+ --warning: #cf8a19;
|
|
|
+ --radius: 14px;
|
|
|
+ --shadow: 0 6px 16px rgba(10, 159, 109, 0.08);
|
|
|
+ --header: linear-gradient(145deg, #087f57 0%, #0a9f6d 72%, #d6a343 165%);
|
|
|
+ --safe-b: env(safe-area-inset-bottom, 0px);
|
|
|
+ }
|
|
|
+ * { box-sizing: border-box; margin: 0; padding: 0; }
|
|
|
+ button, input, select, textarea { font: inherit; }
|
|
|
+ body {
|
|
|
+ background: #cfd9d4;
|
|
|
+ color: var(--text);
|
|
|
+ font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue', sans-serif;
|
|
|
+ font-size: 14px;
|
|
|
+ line-height: 1.45;
|
|
|
+ min-height: 100vh;
|
|
|
+ }
|
|
|
+ .stage { max-width: 440px; margin: 0 auto; padding: 16px 12px 32px; }
|
|
|
+ .banner {
|
|
|
+ background: #1f2a26; color: #fff; border-radius: 12px; padding: 12px 14px;
|
|
|
+ margin-bottom: 12px; font-size: 12px; line-height: 1.55;
|
|
|
+ }
|
|
|
+ .banner strong { color: #9fe3c4; }
|
|
|
+ .banner code { background: rgba(255,255,255,.12); padding: 1px 5px; border-radius: 4px; }
|
|
|
+ .phone {
|
|
|
+ position: relative;
|
|
|
+ background: var(--bg);
|
|
|
+ border-radius: 28px;
|
|
|
+ overflow: hidden;
|
|
|
+ border: 8px solid #24312c;
|
|
|
+ height: 780px;
|
|
|
+ box-shadow: 0 18px 40px rgba(0,0,0,.16);
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ }
|
|
|
+ .status-bar {
|
|
|
+ flex: 0 0 auto;
|
|
|
+ height: 28px; background: var(--brand-dark); color: rgba(255,255,255,.85);
|
|
|
+ display: flex; align-items: center; justify-content: space-between;
|
|
|
+ padding: 0 16px; font-size: 11px;
|
|
|
+ }
|
|
|
+ .screen-host { flex: 1; position: relative; overflow: hidden; }
|
|
|
+ .screen {
|
|
|
+ position: absolute; inset: 0; display: none; flex-direction: column;
|
|
|
+ background: var(--bg); overflow: hidden;
|
|
|
+ }
|
|
|
+ .screen.active { display: flex; }
|
|
|
+ .header {
|
|
|
+ flex: 0 0 auto;
|
|
|
+ background: var(--header); color: #fff; padding: 8px 14px 14px;
|
|
|
+ }
|
|
|
+ .header-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
|
|
|
+ .back {
|
|
|
+ border: 0; background: transparent; color: #fff; opacity: .95;
|
|
|
+ font-size: 13px; cursor: pointer; padding: 4px 0;
|
|
|
+ }
|
|
|
+ .header h1 { font-size: 20px; font-weight: 700; margin-top: 6px; }
|
|
|
+ .header .sub { margin-top: 2px; font-size: 12px; opacity: .88; }
|
|
|
+ .search-wrap { margin-top: 10px; }
|
|
|
+ .search {
|
|
|
+ width: 100%; border: 0; border-radius: 999px; padding: 10px 14px;
|
|
|
+ background: rgba(255,255,255,.96); color: var(--text);
|
|
|
+ }
|
|
|
+ .search::placeholder { color: #9aa8a1; }
|
|
|
+ .scroll { flex: 1; min-height: 0; overflow-y: auto; -webkit-overflow-scrolling: touch; }
|
|
|
+ .scroll.pad-fab { padding-bottom: 12px; }
|
|
|
+ .scroll.pad-speed { padding-bottom: 100px; }
|
|
|
+ .chips, .tabs, .toolbar {
|
|
|
+ display: flex; gap: 8px; overflow-x: auto; padding: 10px 12px 0;
|
|
|
+ -webkit-overflow-scrolling: touch;
|
|
|
+ }
|
|
|
+ .tabs { padding-top: 8px; }
|
|
|
+ .toolbar { padding: 8px 12px 10px; }
|
|
|
+ .chip, .tab {
|
|
|
+ flex: 0 0 auto; border-radius: 999px; border: 1px solid var(--border);
|
|
|
+ background: #fff; color: var(--muted); font-size: 12px; padding: 6px 12px; cursor: pointer;
|
|
|
+ }
|
|
|
+ .chip.active, .tab.active {
|
|
|
+ background: var(--brand-soft); border-color: #b7ebce; color: var(--brand-dark); font-weight: 600;
|
|
|
+ }
|
|
|
+ .tab.active { background: var(--brand); border-color: var(--brand); color: #fff; }
|
|
|
+ /* JD 风格:检索下双下拉 */
|
|
|
+ .filter-bar {
|
|
|
+ flex: 0 0 auto;
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: 1fr 1fr;
|
|
|
+ background: #fff;
|
|
|
+ border-bottom: 1px solid var(--border);
|
|
|
+ position: relative;
|
|
|
+ z-index: 8;
|
|
|
+ }
|
|
|
+ .filter-trig {
|
|
|
+ display: flex; align-items: center; justify-content: center; gap: 4px;
|
|
|
+ height: 42px; border: 0; background: transparent; color: var(--text);
|
|
|
+ font-size: 13px; cursor: pointer; position: relative;
|
|
|
+ }
|
|
|
+ .filter-trig + .filter-trig::before {
|
|
|
+ content: ''; position: absolute; left: 0; top: 12px; bottom: 12px;
|
|
|
+ width: 1px; background: var(--border);
|
|
|
+ }
|
|
|
+ .filter-trig .arrow {
|
|
|
+ font-size: 10px; color: var(--muted); transition: transform .15s;
|
|
|
+ }
|
|
|
+ .filter-trig.open { color: var(--brand-dark); font-weight: 600; }
|
|
|
+ .filter-trig.open .arrow { color: var(--brand); transform: rotate(180deg); }
|
|
|
+ .filter-trig .val { max-width: 7em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
|
+ .filter-panel {
|
|
|
+ display: none; position: absolute; left: 0; right: 0; top: 100%;
|
|
|
+ background: #fff; border-bottom: 1px solid var(--border);
|
|
|
+ box-shadow: 0 10px 20px rgba(0,0,0,.08); max-height: 360px; overflow-y: auto; z-index: 9;
|
|
|
+ }
|
|
|
+ .filter-panel.open { display: block; }
|
|
|
+ .filter-opt {
|
|
|
+ display: flex; align-items: center; justify-content: space-between;
|
|
|
+ width: 100%; border: 0; background: #fff; text-align: left;
|
|
|
+ padding: 12px 16px; font-size: 14px; color: var(--text); cursor: pointer;
|
|
|
+ border-bottom: 1px solid #f0f4f2;
|
|
|
+ }
|
|
|
+ .filter-opt:active { background: var(--brand-soft); }
|
|
|
+ .filter-opt.active { color: var(--brand-dark); font-weight: 600; }
|
|
|
+ .filter-opt .check { color: var(--brand); font-size: 14px; visibility: hidden; }
|
|
|
+ .filter-opt.active .check { visibility: visible; }
|
|
|
+ .filter-mask {
|
|
|
+ display: none; position: absolute; left: 0; right: 0; top: 0; bottom: 0;
|
|
|
+ background: rgba(15, 30, 24, .35); z-index: 7;
|
|
|
+ }
|
|
|
+ .filter-mask.open { display: block; }
|
|
|
+ /* 右下角圆形悬浮 + 展开菜单 */
|
|
|
+ .speed-dial {
|
|
|
+ position: absolute; right: 16px; bottom: calc(16px + var(--safe-b));
|
|
|
+ z-index: 12; display: flex; flex-direction: column; align-items: flex-end; gap: 10px;
|
|
|
+ }
|
|
|
+ .speed-dial-menu {
|
|
|
+ display: none; flex-direction: column; align-items: flex-end; gap: 10px;
|
|
|
+ }
|
|
|
+ .speed-dial.open .speed-dial-menu { display: flex; }
|
|
|
+ .speed-item {
|
|
|
+ display: flex; align-items: center; gap: 8px; border: 0; background: transparent; cursor: pointer;
|
|
|
+ padding: 0;
|
|
|
+ }
|
|
|
+ .speed-item .label {
|
|
|
+ background: rgba(31,42,38,.88); color: #fff; font-size: 12px;
|
|
|
+ padding: 6px 10px; border-radius: 999px; white-space: nowrap;
|
|
|
+ box-shadow: 0 4px 12px rgba(0,0,0,.12);
|
|
|
+ }
|
|
|
+ .speed-item .mini {
|
|
|
+ width: 44px; height: 44px; border-radius: 50%; border: 0;
|
|
|
+ background: #fff; color: var(--brand-dark); font-size: 12px; font-weight: 700;
|
|
|
+ box-shadow: 0 6px 16px rgba(10,159,109,.2); display: flex; align-items: center; justify-content: center;
|
|
|
+ pointer-events: none;
|
|
|
+ }
|
|
|
+ .speed-item.primary .mini { background: var(--brand); color: #fff; }
|
|
|
+ .speed-main {
|
|
|
+ width: 56px; height: 56px; border-radius: 50%; border: 0;
|
|
|
+ background: var(--brand); color: #fff; font-size: 28px; line-height: 1;
|
|
|
+ font-weight: 400; cursor: pointer;
|
|
|
+ box-shadow: 0 8px 20px rgba(10,159,109,.35);
|
|
|
+ display: flex; align-items: center; justify-content: center;
|
|
|
+ transition: transform .18s ease, background .18s;
|
|
|
+ }
|
|
|
+ .speed-dial.open .speed-main {
|
|
|
+ background: var(--brand-dark); transform: rotate(45deg);
|
|
|
+ }
|
|
|
+ .speed-mask {
|
|
|
+ display: none; position: absolute; inset: 0; z-index: 11;
|
|
|
+ background: rgba(15, 30, 24, .28);
|
|
|
+ }
|
|
|
+ .speed-mask.open { display: block; }
|
|
|
+ .btn {
|
|
|
+ display: inline-flex; align-items: center; justify-content: center; gap: 4px;
|
|
|
+ height: 36px; padding: 0 12px; border-radius: 8px; border: 1px solid var(--border);
|
|
|
+ background: #fff; color: var(--text); font-size: 13px; cursor: pointer; white-space: nowrap;
|
|
|
+ }
|
|
|
+ .btn:active { transform: scale(.98); }
|
|
|
+ .btn-primary { background: var(--brand); border-color: var(--brand); color: #fff; font-weight: 600; }
|
|
|
+ .btn-ghost { background: var(--brand-soft); border-color: #b7ebce; color: var(--brand-dark); }
|
|
|
+ .btn-danger { color: var(--danger); border-color: #f0c2c2; background: #fff7f7; }
|
|
|
+ .btn-block { width: 100%; }
|
|
|
+ .btn-lg { height: 44px; font-size: 14px; }
|
|
|
+ .list { padding: 0 12px 12px; display: flex; flex-direction: column; gap: 10px; }
|
|
|
+ .card {
|
|
|
+ background: var(--surface); border-radius: var(--radius); box-shadow: var(--shadow);
|
|
|
+ border: 1px solid rgba(220,235,228,.85); padding: 12px; cursor: pointer;
|
|
|
+ }
|
|
|
+ .card-head { display: flex; justify-content: space-between; gap: 8px; align-items: flex-start; }
|
|
|
+ .plan-no { font-size: 15px; font-weight: 700; color: var(--brand-dark); }
|
|
|
+ .muted { color: var(--muted); font-size: 12px; }
|
|
|
+ .tag {
|
|
|
+ flex-shrink: 0; font-size: 11px; padding: 3px 8px; border-radius: 999px;
|
|
|
+ font-weight: 600; color: #fff; background: #64748b;
|
|
|
+ }
|
|
|
+ .tag-warn { background: #d97706; }
|
|
|
+ .tag-ok { background: #059669; }
|
|
|
+ .tag-wait { background: #cf8a19; }
|
|
|
+ .tag-mute { background: #64748b; }
|
|
|
+ .meta { margin-top: 8px; display: grid; gap: 3px; font-size: 12px; color: var(--muted); }
|
|
|
+ .meta b { color: var(--text); font-weight: 500; }
|
|
|
+ .products {
|
|
|
+ margin-top: 8px; background: var(--brand-soft); border-radius: 10px;
|
|
|
+ padding: 8px 10px; font-size: 12px; color: var(--brand-dark);
|
|
|
+ }
|
|
|
+ .card-actions { margin-top: 10px; display: flex; justify-content: flex-end; gap: 8px; flex-wrap: wrap; }
|
|
|
+ .empty { padding: 40px 16px; text-align: center; color: var(--muted); font-size: 13px; }
|
|
|
+ .fab-bar {
|
|
|
+ position: absolute; left: 0; right: 0; bottom: 0;
|
|
|
+ padding: 10px 12px calc(12px + var(--safe-b));
|
|
|
+ background: linear-gradient(180deg, transparent, var(--bg) 26%);
|
|
|
+ display: flex; gap: 8px; z-index: 5;
|
|
|
+ }
|
|
|
+ /* 详情/表单/打印/三方:底栏参与 flex,贴死屏幕底,无悬空空隙 */
|
|
|
+ #screen-paste .fab-bar,
|
|
|
+ #screen-detail .fab-bar,
|
|
|
+ #screen-form .fab-bar,
|
|
|
+ #screen-print .fab-bar {
|
|
|
+ position: relative;
|
|
|
+ left: auto;
|
|
|
+ right: auto;
|
|
|
+ bottom: auto;
|
|
|
+ flex: 0 0 auto;
|
|
|
+ width: 100%;
|
|
|
+ margin: 0;
|
|
|
+ padding: 10px 12px;
|
|
|
+ background: #fff;
|
|
|
+ border-top: 1px solid var(--border);
|
|
|
+ box-shadow: 0 -4px 16px rgba(0,0,0,.04);
|
|
|
+ z-index: 6;
|
|
|
+ }
|
|
|
+ .fab-bar .btn { flex: 1; height: 44px; }
|
|
|
+ .fab-bar .btn-primary { flex: 1.4; }
|
|
|
+ #form-fab {
|
|
|
+ flex-wrap: wrap;
|
|
|
+ }
|
|
|
+ #form-fab .btn {
|
|
|
+ flex: 1 1 calc(50% - 4px);
|
|
|
+ min-width: calc(50% - 4px);
|
|
|
+ height: 40px;
|
|
|
+ font-size: 13px;
|
|
|
+ }
|
|
|
+ #form-fab .btn-primary {
|
|
|
+ flex: 1 1 100%;
|
|
|
+ }
|
|
|
+ #detail-fab {
|
|
|
+ align-items: center;
|
|
|
+ }
|
|
|
+ #detail-fab .btn {
|
|
|
+ flex: 1;
|
|
|
+ height: 44px;
|
|
|
+ }
|
|
|
+ #detail-fab .btn-more {
|
|
|
+ flex: 0 0 48px;
|
|
|
+ width: 48px;
|
|
|
+ padding: 0;
|
|
|
+ font-size: 22px;
|
|
|
+ line-height: 1;
|
|
|
+ letter-spacing: 1px;
|
|
|
+ color: var(--text);
|
|
|
+ border-color: var(--border);
|
|
|
+ background: #fff;
|
|
|
+ }
|
|
|
+ #detail-fab .btn-primary {
|
|
|
+ flex: 1.2;
|
|
|
+ }
|
|
|
+ .more-action {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ width: 100%;
|
|
|
+ border: 0;
|
|
|
+ background: #fff;
|
|
|
+ text-align: left;
|
|
|
+ padding: 14px 12px;
|
|
|
+ font-size: 15px;
|
|
|
+ color: var(--text);
|
|
|
+ cursor: pointer;
|
|
|
+ border-bottom: 1px solid #eef4f1;
|
|
|
+ }
|
|
|
+ .more-action:active { background: var(--brand-soft); }
|
|
|
+ .more-action.danger { color: var(--danger); }
|
|
|
+ .link-row {
|
|
|
+ display: flex; gap: 8px; align-items: center;
|
|
|
+ }
|
|
|
+ .link-row .control { flex: 1; min-width: 0; font-size: 12px; }
|
|
|
+ .link-row .btn { flex: 0 0 auto; height: 40px; padding: 0 10px; }
|
|
|
+ .detail-remark {
|
|
|
+ font-size: 13px; color: var(--text); line-height: 1.55;
|
|
|
+ white-space: pre-wrap; word-break: break-word;
|
|
|
+ }
|
|
|
+ .list-more-wrap { padding: 4px 12px 20px; }
|
|
|
+ #custom-date-row.hidden { display: none !important; }
|
|
|
+ .prod-table {
|
|
|
+ margin-top: 8px; border: 1px solid var(--border); border-radius: 10px; overflow: hidden;
|
|
|
+ background: #fff;
|
|
|
+ }
|
|
|
+ .prod-row {
|
|
|
+ padding: 10px; border-bottom: 1px solid #eef4f1; font-size: 12px;
|
|
|
+ }
|
|
|
+ .prod-row:last-child { border-bottom: 0; }
|
|
|
+ .prod-row__name { font-size: 13px; font-weight: 700; color: var(--brand-dark); margin-bottom: 6px; }
|
|
|
+ .prod-row__grid {
|
|
|
+ display: grid; grid-template-columns: 1fr 1fr; gap: 4px 10px; color: var(--muted);
|
|
|
+ }
|
|
|
+ .prod-row__grid b { color: var(--text); font-weight: 500; }
|
|
|
+ .delivery-tabs {
|
|
|
+ display: flex; gap: 6px; overflow-x: auto; margin: 8px 0 4px;
|
|
|
+ -webkit-overflow-scrolling: touch;
|
|
|
+ }
|
|
|
+ .delivery-tab {
|
|
|
+ flex: 0 0 auto; border: 1px solid var(--border); background: #fff;
|
|
|
+ border-radius: 999px; padding: 6px 12px; font-size: 12px; color: var(--muted); cursor: pointer;
|
|
|
+ }
|
|
|
+ .delivery-tab.active {
|
|
|
+ background: var(--brand); border-color: var(--brand); color: #fff; font-weight: 600;
|
|
|
+ }
|
|
|
+ .feedback-box {
|
|
|
+ margin-top: 8px; padding: 10px; border-radius: 10px; background: #f7faf8;
|
|
|
+ border: 1px dashed var(--border); font-size: 12px;
|
|
|
+ }
|
|
|
+ .feedback-box h4 { font-size: 12px; color: var(--brand-dark); margin-bottom: 6px; }
|
|
|
+ .site-link-list { display: grid; gap: 8px; }
|
|
|
+ .vehicle-view-btn { margin-top: 8px; }
|
|
|
+ .cert-summary {
|
|
|
+ display: flex; align-items: center; justify-content: space-between; gap: 8px;
|
|
|
+ margin-bottom: 10px; font-size: 12px; color: var(--muted);
|
|
|
+ }
|
|
|
+ .cert-summary .tag { flex-shrink: 0; }
|
|
|
+ .cert-list { display: grid; gap: 10px; }
|
|
|
+ .cert-block {
|
|
|
+ border: 1px solid var(--border); border-radius: 10px; background: #fff; padding: 10px;
|
|
|
+ }
|
|
|
+ .cert-block__head {
|
|
|
+ display: flex; align-items: center; flex-wrap: wrap; gap: 6px; margin-bottom: 8px;
|
|
|
+ }
|
|
|
+ .cert-block__index { font-size: 12px; color: var(--muted); }
|
|
|
+ .cert-block__name { font-size: 14px; font-weight: 700; color: var(--brand-dark); }
|
|
|
+ .cert-mark {
|
|
|
+ display: inline-flex; align-items: center; justify-content: center;
|
|
|
+ min-width: 22px; height: 20px; padding: 0 6px; border-radius: 4px;
|
|
|
+ font-size: 11px; font-weight: 700; color: #fff;
|
|
|
+ }
|
|
|
+ .cert-mark.toxic { background: #d97706; }
|
|
|
+ .cert-mark.explosive { background: #dc2626; }
|
|
|
+ .cert-slots { display: grid; gap: 8px; }
|
|
|
+ .cert-slot {
|
|
|
+ display: flex; align-items: flex-start; justify-content: space-between; gap: 8px;
|
|
|
+ padding: 8px; border-radius: 8px; background: #f7faf8;
|
|
|
+ }
|
|
|
+ .cert-slot__label { font-size: 12px; color: var(--text); font-weight: 600; flex-shrink: 0; padding-top: 2px; }
|
|
|
+ .cert-slot__body { flex: 1; min-width: 0; text-align: right; }
|
|
|
+ .cert-file {
|
|
|
+ display: inline-flex; align-items: center; gap: 4px; max-width: 100%;
|
|
|
+ padding: 4px 8px; border-radius: 6px; border: 1px solid var(--border);
|
|
|
+ background: #fff; font-size: 12px; color: var(--brand-dark); cursor: pointer;
|
|
|
+ }
|
|
|
+ .cert-file span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
|
+ .cert-missing { font-size: 12px; color: #dc2626; font-weight: 600; }
|
|
|
+ .cert-hint { margin-top: 10px; font-size: 12px; color: var(--muted); line-height: 1.45; }
|
|
|
+ .section {
|
|
|
+ margin: 10px 12px; background: #fff; border-radius: var(--radius);
|
|
|
+ padding: 12px; box-shadow: var(--shadow);
|
|
|
+ }
|
|
|
+ .section h3 {
|
|
|
+ font-size: 13px; color: var(--brand-dark); margin-bottom: 8px;
|
|
|
+ display: flex; align-items: center; justify-content: space-between;
|
|
|
+ }
|
|
|
+ .kv {
|
|
|
+ display: flex; justify-content: space-between; gap: 12px;
|
|
|
+ padding: 7px 0; font-size: 13px; border-bottom: 1px dashed var(--border);
|
|
|
+ }
|
|
|
+ .kv:last-child { border-bottom: 0; }
|
|
|
+ .kv > span:first-child { color: var(--muted); flex-shrink: 0; }
|
|
|
+ .kv > span:last-child { text-align: right; }
|
|
|
+ .actions-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
|
|
|
+ .actions-grid .btn { height: 42px; }
|
|
|
+ .anchor-bar {
|
|
|
+ display: flex; gap: 6px; overflow-x: auto; padding: 8px 12px;
|
|
|
+ background: #fff; border-bottom: 1px solid var(--border); flex: 0 0 auto;
|
|
|
+ }
|
|
|
+ .anchor {
|
|
|
+ flex: 0 0 auto; border: 1px solid var(--border); background: #fff;
|
|
|
+ color: var(--muted); border-radius: 999px; padding: 5px 10px; font-size: 12px; cursor: pointer;
|
|
|
+ }
|
|
|
+ .anchor.active { background: var(--brand-soft); border-color: #b7ebce; color: var(--brand-dark); font-weight: 600; }
|
|
|
+ .field { margin-bottom: 12px; }
|
|
|
+ .field:last-child { margin-bottom: 0; }
|
|
|
+ .field label {
|
|
|
+ display: block; font-size: 12px; color: var(--muted); margin-bottom: 6px;
|
|
|
+ }
|
|
|
+ .field-row { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
|
|
|
+ .control, .picker {
|
|
|
+ width: 100%; border: 1px solid var(--border); border-radius: 8px;
|
|
|
+ background: #fff; padding: 10px 12px; color: var(--text); text-align: left;
|
|
|
+ }
|
|
|
+ .picker { cursor: pointer; display: flex; justify-content: space-between; align-items: center; gap: 8px; }
|
|
|
+ .picker .ph { color: #9aa8a1; }
|
|
|
+ .picker .chev { color: var(--muted); font-size: 12px; }
|
|
|
+ textarea.control { min-height: 72px; resize: vertical; }
|
|
|
+ .stop-card {
|
|
|
+ border: 1px solid var(--border); border-radius: 10px; padding: 10px; margin-top: 8px;
|
|
|
+ background: #fafcfb;
|
|
|
+ }
|
|
|
+ .stop-card + .stop-card { margin-top: 8px; }
|
|
|
+ .stop-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; }
|
|
|
+ .stop-head b { font-size: 13px; }
|
|
|
+ .link-btn {
|
|
|
+ border: 0; background: transparent; color: var(--brand-dark);
|
|
|
+ font-size: 12px; cursor: pointer; font-weight: 600;
|
|
|
+ }
|
|
|
+ .link-btn.danger { color: var(--danger); }
|
|
|
+ .hint-box {
|
|
|
+ margin: 8px 12px 0; padding: 8px 10px; border-radius: 8px;
|
|
|
+ background: var(--barley-soft); color: #7a5a18; font-size: 12px;
|
|
|
+ }
|
|
|
+ .print-preview {
|
|
|
+ margin: 12px; background: #fff; border-radius: 8px; border: 1px solid var(--border);
|
|
|
+ min-height: 360px; padding: 16px; font-size: 12px; color: #333;
|
|
|
+ }
|
|
|
+ .print-preview h2 { text-align: center; font-size: 16px; margin-bottom: 12px; }
|
|
|
+ .print-preview .line { display: flex; justify-content: space-between; padding: 4px 0; border-bottom: 1px dashed #eee; }
|
|
|
+ .sheet-mask {
|
|
|
+ position: absolute; inset: 0; background: rgba(15, 30, 24, .45);
|
|
|
+ z-index: 20; display: none;
|
|
|
+ }
|
|
|
+ .sheet-mask.open { display: block; }
|
|
|
+ .sheet {
|
|
|
+ position: absolute; left: 0; right: 0; bottom: 0; z-index: 21;
|
|
|
+ background: #fff; border-radius: 18px 18px 0 0;
|
|
|
+ max-height: 78%; transform: translateY(110%);
|
|
|
+ transition: transform .22s ease, visibility 0s linear .22s;
|
|
|
+ display: flex; flex-direction: column;
|
|
|
+ padding-bottom: var(--safe-b);
|
|
|
+ visibility: hidden;
|
|
|
+ pointer-events: none;
|
|
|
+ }
|
|
|
+ .sheet.open {
|
|
|
+ transform: translateY(0);
|
|
|
+ visibility: visible;
|
|
|
+ pointer-events: auto;
|
|
|
+ transition: transform .22s ease, visibility 0s;
|
|
|
+ }
|
|
|
+ .sheet.half { max-height: 42%; }
|
|
|
+ .sheet-handle {
|
|
|
+ width: 36px; height: 4px; border-radius: 999px; background: #d5e0db;
|
|
|
+ margin: 10px auto 6px;
|
|
|
+ }
|
|
|
+ .sheet-head {
|
|
|
+ display: flex; align-items: center; justify-content: space-between;
|
|
|
+ padding: 4px 16px 10px; border-bottom: 1px solid var(--border);
|
|
|
+ }
|
|
|
+ .sheet-head h3 { font-size: 15px; }
|
|
|
+ .sheet-body { overflow-y: auto; padding: 8px 12px 16px; flex: 1; }
|
|
|
+ .sheet-search {
|
|
|
+ padding: 0 12px 10px;
|
|
|
+ border-bottom: 1px solid var(--border);
|
|
|
+ flex: 0 0 auto;
|
|
|
+ }
|
|
|
+ .sheet-search input {
|
|
|
+ width: 100%;
|
|
|
+ border: 1px solid var(--border);
|
|
|
+ border-radius: 999px;
|
|
|
+ padding: 10px 14px;
|
|
|
+ background: #f5f8f7;
|
|
|
+ color: var(--text);
|
|
|
+ font-size: 14px;
|
|
|
+ }
|
|
|
+ .sheet-search input::placeholder { color: #9aa8a1; }
|
|
|
+ .sheet-empty {
|
|
|
+ padding: 28px 12px;
|
|
|
+ text-align: center;
|
|
|
+ color: var(--muted);
|
|
|
+ font-size: 13px;
|
|
|
+ }
|
|
|
+ .sheet-foot {
|
|
|
+ display: flex; gap: 8px; padding: 10px 12px; border-top: 1px solid var(--border);
|
|
|
+ }
|
|
|
+ .sheet-foot .btn { flex: 1; height: 44px; }
|
|
|
+ .option-row {
|
|
|
+ display: flex; align-items: center; justify-content: space-between;
|
|
|
+ gap: 10px; padding: 12px 10px; border-bottom: 1px solid #eef4f1; cursor: pointer;
|
|
|
+ }
|
|
|
+ .option-row:active { background: var(--brand-soft); }
|
|
|
+ .option-row .t { font-size: 14px; font-weight: 600; }
|
|
|
+ .option-row .d { font-size: 12px; color: var(--muted); margin-top: 2px; }
|
|
|
+ .toast {
|
|
|
+ position: absolute; left: 50%; bottom: 96px; transform: translateX(-50%);
|
|
|
+ background: rgba(31,42,38,.92); color: #fff; padding: 10px 16px;
|
|
|
+ border-radius: 999px; font-size: 13px; z-index: 40; display: none;
|
|
|
+ white-space: nowrap; max-width: 90%;
|
|
|
+ }
|
|
|
+ .toast.show { display: block; }
|
|
|
+ .map-nav {
|
|
|
+ margin-top: 10px; font-size: 11px; color: var(--muted); text-align: center;
|
|
|
+ }
|
|
|
+ .hidden { display: none !important; }
|
|
|
+ .paste-conflict-opt.active {
|
|
|
+ background: var(--brand-soft); border-color: #b7ebce; color: var(--brand-dark); font-weight: 600;
|
|
|
+ }
|
|
|
+ .btn[disabled] { opacity: .45; pointer-events: none; }
|
|
|
+ .check-row {
|
|
|
+ display: flex; align-items: center; gap: 8px;
|
|
|
+ font-size: 13px; color: var(--text); padding: 8px 0 0;
|
|
|
+ }
|
|
|
+ .product-card {
|
|
|
+ border: 1px solid var(--border); border-radius: 10px; padding: 10px;
|
|
|
+ margin-top: 8px; background: #fafcfb;
|
|
|
+ }
|
|
|
+ .product-card__title { font-size: 14px; font-weight: 700; margin-bottom: 8px; color: var(--brand-dark); }
|
|
|
+ .qty-balance {
|
|
|
+ margin-top: 12px; padding: 10px 12px; border-radius: 10px;
|
|
|
+ background: var(--brand-soft); color: var(--brand-dark); font-size: 12px;
|
|
|
+ display: grid; gap: 4px;
|
|
|
+ }
|
|
|
+ .qty-balance .qty-ok { font-weight: 700; margin-top: 2px; }
|
|
|
+ </style>
|
|
|
+</head>
|
|
|
+<body>
|
|
|
+ <div class="stage">
|
|
|
+ <div class="banner">
|
|
|
+ <strong>运输计划 · 移动端重设计 Demo(方案 1)</strong><br />
|
|
|
+ 长页分区 + 锚点 · 选择器/确认为 Bottom Sheet · 安排/编辑对齐 Web 同一表单。<br />
|
|
|
+ 文件:<code>bak/mobile-ui/shanxia-mp-pl-plan-mobile-redesign-demo.html</code>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="phone" id="phone">
|
|
|
+ <div class="status-bar"><span>21:29</span><span>WebView Demo</span></div>
|
|
|
+ <div class="screen-host">
|
|
|
+
|
|
|
+ <!-- LIST -->
|
|
|
+ <section class="screen active" id="screen-list" data-title="列表">
|
|
|
+ <header class="header">
|
|
|
+ <div class="header-row">
|
|
|
+ <button class="back" type="button" data-action="toast" data-msg="返回工作台(Demo)">← 工作台</button>
|
|
|
+ </div>
|
|
|
+ <h1>运输计划</h1>
|
|
|
+ <div class="sub">小程序 WebView · 三墙 Token</div>
|
|
|
+ <div class="search-wrap">
|
|
|
+ <input class="search" id="list-keyword" type="search" placeholder="计划编号 / 供方 / 需方 / 产品 / 车牌号" />
|
|
|
+ </div>
|
|
|
+ </header>
|
|
|
+ <div class="filter-bar" id="filter-bar">
|
|
|
+ <button class="filter-trig" type="button" id="trig-date" data-action="toggle-filter" data-filter="date">
|
|
|
+ <span class="val" id="label-date">今天</span>
|
|
|
+ <span class="arrow">▼</span>
|
|
|
+ </button>
|
|
|
+ <button class="filter-trig" type="button" id="trig-tab" data-action="toggle-filter" data-filter="tab">
|
|
|
+ <span class="val" id="label-tab">待安排</span>
|
|
|
+ <span class="arrow">▼</span>
|
|
|
+ </button>
|
|
|
+ <div class="filter-panel" id="panel-date">
|
|
|
+ <button class="filter-opt active" type="button" data-action="pick-date" data-date="today" data-label="今天">今天<span class="check">✓</span></button>
|
|
|
+ <button class="filter-opt" type="button" data-action="pick-date" data-date="tomorrow" data-label="明天">明天<span class="check">✓</span></button>
|
|
|
+ <button class="filter-opt" type="button" data-action="pick-date" data-date="week" data-label="本周">本周<span class="check">✓</span></button>
|
|
|
+ <button class="filter-opt" type="button" data-action="pick-date" data-date="month" data-label="本月">本月<span class="check">✓</span></button>
|
|
|
+ <button class="filter-opt" type="button" data-action="pick-date" data-date="custom" data-label="自定义">自定义<span class="check">✓</span></button>
|
|
|
+ <div id="custom-date-row" class="hidden" style="padding:8px 12px 12px">
|
|
|
+ <div class="field-row" style="margin-bottom:8px">
|
|
|
+ <input class="control" type="date" id="custom-date-from" value="2026-09-01" />
|
|
|
+ <input class="control" type="date" id="custom-date-to" value="2026-09-14" />
|
|
|
+ </div>
|
|
|
+ <button class="btn btn-primary btn-block" type="button" data-action="apply-custom-date">确定</button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="filter-panel" id="panel-tab">
|
|
|
+ <button class="filter-opt active" type="button" data-action="pick-tab" data-tab="pending_arrange" data-label="待安排">待安排<span class="check">✓</span></button>
|
|
|
+ <button class="filter-opt" type="button" data-action="pick-tab" data-tab="arranged" data-label="已安排">已安排<span class="check">✓</span></button>
|
|
|
+ <button class="filter-opt" type="button" data-action="pick-tab" data-tab="drafting" data-label="草稿中">草稿中<span class="check">✓</span></button>
|
|
|
+ <button class="filter-opt" type="button" data-action="pick-tab" data-tab="all" data-label="全部">全部<span class="check">✓</span></button>
|
|
|
+ <button class="filter-opt" type="button" data-action="pick-tab" data-tab="inbound" data-label="入库单">入库单<span class="check">✓</span></button>
|
|
|
+ <button class="filter-opt" type="button" data-action="pick-tab" data-tab="outbound" data-label="出库单">出库单<span class="check">✓</span></button>
|
|
|
+ <button class="filter-opt" type="button" data-action="pick-tab" data-tab="direct" data-label="直销单">直销单<span class="check">✓</span></button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="filter-mask" id="filter-mask" data-action="close-filter"></div>
|
|
|
+ <div class="scroll pad-speed">
|
|
|
+ <div class="list" id="plan-list"></div>
|
|
|
+ <div class="list-more-wrap">
|
|
|
+ <button class="btn btn-block" type="button" id="btn-load-more" data-action="load-more">加载更多</button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="speed-mask" id="speed-mask" data-action="close-speed"></div>
|
|
|
+ <div class="speed-dial" id="speed-dial">
|
|
|
+ <div class="speed-dial-menu">
|
|
|
+ <button class="speed-item" type="button" data-nav="paste" data-action="close-speed">
|
|
|
+ <span class="label">三方人车快速创建</span>
|
|
|
+ <span class="mini">人车</span>
|
|
|
+ </button>
|
|
|
+ <button class="speed-item primary" type="button" data-nav="form" data-mode="new" data-action="close-speed">
|
|
|
+ <span class="label">新增计划</span>
|
|
|
+ <span class="mini">新增</span>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ <button class="speed-main" type="button" data-action="toggle-speed" aria-label="快捷操作">+</button>
|
|
|
+ </div>
|
|
|
+ </section>
|
|
|
+
|
|
|
+ <!-- DETAIL -->
|
|
|
+ <section class="screen" id="screen-detail" data-title="详情">
|
|
|
+ <header class="header">
|
|
|
+ <div class="header-row">
|
|
|
+ <button class="back" type="button" data-nav="list">← 列表</button>
|
|
|
+ <span class="tag" id="detail-tag">待安排</span>
|
|
|
+ </div>
|
|
|
+ <h1 id="detail-no">JH20260914003</h1>
|
|
|
+ <div class="sub" id="detail-sub">出库单 · 计划日期 2026-09-14</div>
|
|
|
+ </header>
|
|
|
+ <div class="scroll pad-fab" id="detail-scroll">
|
|
|
+ <div class="section" id="d-sec-basic">
|
|
|
+ <h3>基础信息</h3>
|
|
|
+ <div class="kv"><span>计划编号</span><span id="d-plan-no">—</span></div>
|
|
|
+ <div class="kv"><span>计划日期</span><span id="d-plan-date">—</span></div>
|
|
|
+ <div class="kv"><span>归属公司</span><span id="d-company">安庆山峡</span></div>
|
|
|
+ <div class="kv"><span>单据类型</span><span id="d-type">—</span></div>
|
|
|
+ <div class="kv"><span>跟进人</span><span id="d-follow">王调度</span></div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="section" id="d-sec-transport">
|
|
|
+ <h3>运输信息</h3>
|
|
|
+ <div id="d-arrange-links" class="hidden">
|
|
|
+ <div class="field">
|
|
|
+ <label>司机链接</label>
|
|
|
+ <div class="link-row">
|
|
|
+ <input class="control" id="d-driver-url" readonly value="https://w.example.com/h5/driver?t=***" />
|
|
|
+ <button class="btn" type="button" data-action="toast" data-msg="已复制司机链接">复制</button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="field">
|
|
|
+ <label>现场链接</label>
|
|
|
+ <div class="site-link-list" id="d-site-links"></div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div id="d-vehicle-block">
|
|
|
+ <div class="products" style="margin:0">
|
|
|
+ <div><b id="d-v-plate">—</b></div>
|
|
|
+ <div class="muted" style="margin-top:4px" id="d-v-trailer">挂车 —</div>
|
|
|
+ <div class="muted" style="margin-top:2px" id="d-v-people">司机 — · 押运 —</div>
|
|
|
+ <div class="muted" style="margin-top:2px" id="d-v-carrier">承运商 —</div>
|
|
|
+ <button class="btn btn-block vehicle-view-btn" type="button" id="d-v-view" data-action="open-vehicle-view">查看车辆详情</button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <p id="d-vehicle-empty" class="muted hidden" style="padding:8px 0">暂无车辆信息</p>
|
|
|
+ <div id="d-feedback" class="hidden">
|
|
|
+ <div class="feedback-box">
|
|
|
+ <h4>司机回传摘要</h4>
|
|
|
+ <div id="d-fb-driver">装货已签到 · 预计 16:30 到场</div>
|
|
|
+ </div>
|
|
|
+ <div class="feedback-box">
|
|
|
+ <h4>现场 / 地磅回传</h4>
|
|
|
+ <div id="d-fb-site">现场已确认到场 · 地磅入库单已生成</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="section" id="d-sec-stops">
|
|
|
+ <h3>装送货信息</h3>
|
|
|
+ <div class="stop-card" style="margin-top:0">
|
|
|
+ <div class="stop-head"><b>装货信息</b></div>
|
|
|
+ <div class="kv"><span>装货客户</span><span id="d-load-customer">安庆山峡仓储</span></div>
|
|
|
+ <div class="kv"><span>装货时间</span><span id="d-load-time">2026-09-14</span></div>
|
|
|
+ <div class="kv"><span>装货地址</span><span id="d-load-addr">安庆市迎江区化工园 A3</span></div>
|
|
|
+ <div class="kv"><span>联系方式</span><span id="d-load-contact">0556-1234567</span></div>
|
|
|
+ <div class="kv"><span>注意事项</span><span id="d-load-notes">进门登记</span></div>
|
|
|
+ <div class="kv"><span>装货合同</span><span id="d-load-contract">内部客户(无合同)</span></div>
|
|
|
+ <div class="kv"><span>地磅单</span><span id="d-load-dibang">生成(入库)</span></div>
|
|
|
+ <div class="prod-table" id="d-load-products"></div>
|
|
|
+ </div>
|
|
|
+ <div class="stop-card">
|
|
|
+ <div class="stop-head"><b>送货信息</b></div>
|
|
|
+ <div class="delivery-tabs" id="d-delivery-tabs"></div>
|
|
|
+ <div id="d-delivery-body">
|
|
|
+ <div class="kv"><span>送货客户</span><span id="d-del-customer">—</span></div>
|
|
|
+ <div class="kv"><span>送货时间</span><span id="d-del-time">—</span></div>
|
|
|
+ <div class="kv"><span>送货地址</span><span id="d-del-addr">—</span></div>
|
|
|
+ <div class="kv"><span>联系方式</span><span id="d-del-contact">—</span></div>
|
|
|
+ <div class="kv"><span>注意事项</span><span id="d-del-notes">—</span></div>
|
|
|
+ <div class="kv"><span>送货合同</span><span id="d-del-contract">—</span></div>
|
|
|
+ <div class="kv"><span>地磅单</span><span id="d-del-dibang">—</span></div>
|
|
|
+ <div class="prod-table" id="d-del-products"></div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="qty-balance">
|
|
|
+ <div>计划总量 <b id="d-qty-plan">32</b> 吨</div>
|
|
|
+ <div>装货合计 <b id="d-qty-load">32</b> 吨</div>
|
|
|
+ <div>送货合计 <b id="d-qty-del">32</b> 吨</div>
|
|
|
+ <div class="qty-ok" id="d-qty-balance">✓ 数量平衡</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="section" id="d-sec-cert">
|
|
|
+ <h3>危化品证书信息</h3>
|
|
|
+ <div class="cert-summary" id="d-cert-summary">
|
|
|
+ <span id="d-cert-summary-text">对齐 Web:按产品展示所需证书槽位</span>
|
|
|
+ <span class="tag tag-mute" id="d-cert-ready-tag">—</span>
|
|
|
+ </div>
|
|
|
+ <div class="cert-list" id="d-cert-list"></div>
|
|
|
+ <p class="muted hidden" id="d-cert-empty" style="padding:4px 0">暂无易制毒/易制爆产品</p>
|
|
|
+ <p class="cert-hint" id="d-cert-hint">上传与齐备确认在「危化品证上传」页操作(本 Demo 仅展示)。</p>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="section" id="d-sec-freight">
|
|
|
+ <h3>运费信息</h3>
|
|
|
+ <div class="kv"><span>是否有运输费用</span><span id="d-freight-has">是</span></div>
|
|
|
+ <div class="kv"><span>运输公里数</span><span id="d-freight-km">186</span></div>
|
|
|
+ <div class="kv"><span>运费计算方式</span><span id="d-freight-type">按重量</span></div>
|
|
|
+ <div class="kv"><span>运输量(吨)</span><span id="d-freight-qty">32</span></div>
|
|
|
+ <div class="kv"><span>运输单价(元/吨)</span><span id="d-freight-price">120.00</span></div>
|
|
|
+ <div class="kv"><span>运费金额(元)</span><span id="d-freight-amount">3,840.00</span></div>
|
|
|
+ <div class="kv"><span>是否开票</span><span id="d-freight-invoice">是</span></div>
|
|
|
+ <div class="kv"><span>运费备注</span><span id="d-freight-remark">含过路费</span></div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="section" id="d-sec-remark">
|
|
|
+ <h3>备注</h3>
|
|
|
+ <p class="detail-remark" id="d-remark">注意厂区进门登记</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="fab-bar" id="detail-fab"></div>
|
|
|
+ </section>
|
|
|
+
|
|
|
+ <!-- FORM -->
|
|
|
+ <section class="screen" id="screen-form" data-title="表单">
|
|
|
+ <header class="header">
|
|
|
+ <div class="header-row">
|
|
|
+ <button class="back" type="button" data-action="back">← 返回</button>
|
|
|
+ </div>
|
|
|
+ <h1 id="form-title">新增运输计划</h1>
|
|
|
+ <div class="sub" id="form-sub">长页分区 · 锚点跳转 · 对齐 Web 同一表单</div>
|
|
|
+ </header>
|
|
|
+ <div class="anchor-bar" id="anchor-bar">
|
|
|
+ <button class="anchor active" type="button" data-anchor="sec-basic">基础</button>
|
|
|
+ <button class="anchor" type="button" data-anchor="sec-transport">运输</button>
|
|
|
+ <button class="anchor" type="button" data-anchor="sec-load">装货</button>
|
|
|
+ <button class="anchor" type="button" data-anchor="sec-delivery">送货</button>
|
|
|
+ <button class="anchor" type="button" data-anchor="sec-remark">备注</button>
|
|
|
+ </div>
|
|
|
+ <div class="scroll pad-fab" id="form-scroll">
|
|
|
+ <div class="hint-box" id="form-hint">对齐 Web 新增:取消 / 暂存 / 创建 / 创建并安排运输</div>
|
|
|
+
|
|
|
+ <div class="section" id="sec-basic">
|
|
|
+ <h3>基础信息</h3>
|
|
|
+ <div class="field-row">
|
|
|
+ <div class="field">
|
|
|
+ <label>创建日期</label>
|
|
|
+ <input class="control" type="date" value="2026-09-14" />
|
|
|
+ </div>
|
|
|
+ <div class="field">
|
|
|
+ <label>计划日期</label>
|
|
|
+ <input class="control" type="date" id="f-plan-date" value="2026-09-14" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="field-row">
|
|
|
+ <div class="field">
|
|
|
+ <label>归属公司</label>
|
|
|
+ <select class="control" id="f-company">
|
|
|
+ <option>安庆山峡</option>
|
|
|
+ <option>安徽云栋</option>
|
|
|
+ </select>
|
|
|
+ </div>
|
|
|
+ <div class="field">
|
|
|
+ <label>单据类型</label>
|
|
|
+ <select class="control" id="f-type">
|
|
|
+ <option>出库单</option>
|
|
|
+ <option>入库单</option>
|
|
|
+ <option>直销单</option>
|
|
|
+ </select>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="field">
|
|
|
+ <label>跟进人</label>
|
|
|
+ <input class="control" value="王调度" readonly />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="section" id="sec-transport">
|
|
|
+ <h3>运输信息</h3>
|
|
|
+ <div class="field">
|
|
|
+ <label>车牌号</label>
|
|
|
+ <button class="picker" type="button" data-sheet="vehicle">
|
|
|
+ <span id="f-plate"><span class="ph">请选择车牌号</span></span>
|
|
|
+ <span class="chev">选择</span>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ <div class="actions-grid" style="margin-bottom:12px">
|
|
|
+ <button class="btn" type="button" data-sheet="vehicle">从车辆库选择</button>
|
|
|
+ <button class="btn" type="button" data-nav="paste">三方人车快速创建</button>
|
|
|
+ </div>
|
|
|
+ <div class="field">
|
|
|
+ <label>选择司机(可选)</label>
|
|
|
+ <button class="picker" type="button" data-sheet="personnel" data-role="司机">
|
|
|
+ <span id="f-driver"><span class="ph">请选择司机</span></span>
|
|
|
+ <span class="chev">选择</span>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ <div class="field">
|
|
|
+ <label>选择押运员(可选)</label>
|
|
|
+ <button class="picker" type="button" data-sheet="personnel" data-role="押运">
|
|
|
+ <span id="f-escort"><span class="ph">请选择押运员</span></span>
|
|
|
+ <span class="chev">选择</span>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ <div class="field">
|
|
|
+ <label>运输公司</label>
|
|
|
+ <button class="picker" type="button" data-sheet="carrier" data-target="f-carrier">
|
|
|
+ <span id="f-carrier"><span class="ph">请选择运输公司</span></span>
|
|
|
+ <span class="chev">选择</span>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ <button class="btn btn-block" type="button" style="margin-top:8px" data-sheet="carrier" data-target="f-carrier">从承运商库选择</button>
|
|
|
+ <div class="products" id="f-vehicle-summary" style="margin-top:12px;display:none">
|
|
|
+ <div><b id="f-vs-plate">—</b></div>
|
|
|
+ <div class="muted" style="margin-top:4px" id="f-vs-people">司机 — · 押运 —</div>
|
|
|
+ <div class="muted" style="margin-top:2px" id="f-vs-carrier">承运商 —</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="section" id="sec-load">
|
|
|
+ <h3>装货信息</h3>
|
|
|
+ <div class="field">
|
|
|
+ <label>装货客户 <span style="color:var(--danger)">*</span></label>
|
|
|
+ <button class="picker" type="button" data-sheet="customer" data-target="load-c1">
|
|
|
+ <span id="load-c1">安庆山峡仓储</span>
|
|
|
+ <span class="chev">选择</span>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ <div class="field">
|
|
|
+ <label>装货合同</label>
|
|
|
+ <button class="picker" type="button" data-sheet="contract" data-target="load-ct1">
|
|
|
+ <span id="load-ct1"><span class="ph">请选择合同</span></span>
|
|
|
+ <span class="chev">选择</span>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ <div class="field-row">
|
|
|
+ <div class="field">
|
|
|
+ <label>装货时间 <span style="color:var(--danger)">*</span></label>
|
|
|
+ <input class="control" type="date" value="2026-09-14" />
|
|
|
+ </div>
|
|
|
+ <div class="field">
|
|
|
+ <label>联系方式</label>
|
|
|
+ <input class="control" value="0556-1234567" placeholder="联系方式" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="field">
|
|
|
+ <label>装货地址</label>
|
|
|
+ <div class="link-row">
|
|
|
+ <input class="control" id="load-addr-1" value="安庆市迎江区化工园 A3" placeholder="装货地址" />
|
|
|
+ <button class="btn" type="button" data-sheet="address" data-target="load-addr-1">地址库</button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="field">
|
|
|
+ <label>注意事项</label>
|
|
|
+ <input class="control" placeholder="装货注意事项" value="进门登记" />
|
|
|
+ </div>
|
|
|
+ <div class="field">
|
|
|
+ <label>地磅单</label>
|
|
|
+ <label class="check-row"><input type="checkbox" checked /> 生成地磅单(入库)</label>
|
|
|
+ <div class="muted" style="margin-top:4px">本段客户为内部客户时默认勾选</div>
|
|
|
+ </div>
|
|
|
+ <div class="stop-head" style="margin-top:12px">
|
|
|
+ <b>装货产品</b>
|
|
|
+ <button class="link-btn" type="button" data-action="add-product" data-side="load">+ 添加产品</button>
|
|
|
+ </div>
|
|
|
+ <div id="load-products">
|
|
|
+ <div class="product-card">
|
|
|
+ <div class="product-card__title">甲醇</div>
|
|
|
+ <div class="field-row">
|
|
|
+ <div class="field">
|
|
|
+ <label>型号规格</label>
|
|
|
+ <input class="control" value="优等品" placeholder="型号规格" />
|
|
|
+ </div>
|
|
|
+ <div class="field">
|
|
|
+ <label>品牌</label>
|
|
|
+ <input class="control" value="山峡" placeholder="品牌" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="field-row">
|
|
|
+ <div class="field">
|
|
|
+ <label>别名</label>
|
|
|
+ <input class="control" value="木精" placeholder="别名" />
|
|
|
+ </div>
|
|
|
+ <div class="field">
|
|
|
+ <label>可安排量</label>
|
|
|
+ <input class="control" value="32" readonly />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="field-row">
|
|
|
+ <div class="field">
|
|
|
+ <label>计划数量 <span style="color:var(--danger)">*</span></label>
|
|
|
+ <input class="control" type="number" value="32" />
|
|
|
+ </div>
|
|
|
+ <div class="field">
|
|
|
+ <label>单位 <span style="color:var(--danger)">*</span></label>
|
|
|
+ <select class="control"><option>吨</option><option>千克</option></select>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="field">
|
|
|
+ <label>包装规格 <span style="color:var(--danger)">*</span></label>
|
|
|
+ <select class="control"><option>吨袋</option><option>槽罐</option><option>桶装</option></select>
|
|
|
+ </div>
|
|
|
+ <button class="link-btn danger" type="button" data-action="toast" data-msg="至少保留一条产品">删除</button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="section" id="sec-delivery">
|
|
|
+ <h3>
|
|
|
+ 送货信息
|
|
|
+ <span>
|
|
|
+ <button class="link-btn" type="button" data-action="add-stop" data-side="delivery">+ 添加送货段</button>
|
|
|
+ </span>
|
|
|
+ </h3>
|
|
|
+ <div id="delivery-stops">
|
|
|
+ <div class="stop-card" data-side="delivery" data-stop="1">
|
|
|
+ <div class="stop-head">
|
|
|
+ <b>送货段 1</b>
|
|
|
+ <span>
|
|
|
+ <button class="link-btn" type="button" data-action="toast" data-msg="已上移(模拟)">上移</button>
|
|
|
+ <button class="link-btn" type="button" data-action="toast" data-msg="已下移(模拟)">下移</button>
|
|
|
+ <button class="link-btn danger" type="button" data-action="toast" data-msg="至少保留一段">删除</button>
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ <div class="field">
|
|
|
+ <label>送货客户 <span style="color:var(--danger)">*</span></label>
|
|
|
+ <button class="picker" type="button" data-sheet="customer" data-target="del-c1">
|
|
|
+ <span id="del-c1">合肥某化</span>
|
|
|
+ <span class="chev">选择</span>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ <div class="field">
|
|
|
+ <label>送货合同</label>
|
|
|
+ <button class="picker" type="button" data-sheet="contract" data-target="del-ct1">
|
|
|
+ <span id="del-ct1">HT202609-001</span>
|
|
|
+ <span class="chev">选择</span>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ <div class="field-row">
|
|
|
+ <div class="field">
|
|
|
+ <label>送货时间 <span style="color:var(--danger)">*</span></label>
|
|
|
+ <input class="control" type="date" value="2026-09-14" />
|
|
|
+ </div>
|
|
|
+ <div class="field">
|
|
|
+ <label>联系方式</label>
|
|
|
+ <input class="control" value="0551-88886666" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="field">
|
|
|
+ <label>送货地址</label>
|
|
|
+ <div class="link-row">
|
|
|
+ <input class="control" id="del-addr-1" value="合肥经开区某某路 88 号" />
|
|
|
+ <button class="btn" type="button" data-sheet="address" data-target="del-addr-1">地址库</button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="field">
|
|
|
+ <label>注意事项</label>
|
|
|
+ <input class="control" placeholder="送货注意事项" value="需预约卸货" />
|
|
|
+ </div>
|
|
|
+ <div class="field">
|
|
|
+ <label>地磅单</label>
|
|
|
+ <label class="check-row"><input type="checkbox" /> 生成地磅单(出库)</label>
|
|
|
+ <div class="muted" style="margin-top:4px">该车辆地磅单可选,请自行勾选</div>
|
|
|
+ </div>
|
|
|
+ <div class="stop-head" style="margin-top:8px">
|
|
|
+ <b>送货产品</b>
|
|
|
+ <span>
|
|
|
+ <button class="link-btn" type="button" data-action="toast" data-msg="已从装货产品同步">从装货同步</button>
|
|
|
+ <button class="link-btn" type="button" data-sheet="product" data-target="del-p1">+ 选择产品</button>
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ <div class="product-card">
|
|
|
+ <div class="product-card__title" id="del-p1">甲醇</div>
|
|
|
+ <div class="field-row">
|
|
|
+ <div class="field">
|
|
|
+ <label>型号规格</label>
|
|
|
+ <input class="control" value="优等品" />
|
|
|
+ </div>
|
|
|
+ <div class="field">
|
|
|
+ <label>品牌</label>
|
|
|
+ <input class="control" value="山峡" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="field-row">
|
|
|
+ <div class="field">
|
|
|
+ <label>计划数量 <span style="color:var(--danger)">*</span></label>
|
|
|
+ <input class="control" type="number" value="32" />
|
|
|
+ </div>
|
|
|
+ <div class="field">
|
|
|
+ <label>单位 <span style="color:var(--danger)">*</span></label>
|
|
|
+ <select class="control"><option>吨</option></select>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="field">
|
|
|
+ <label>包装规格 <span style="color:var(--danger)">*</span></label>
|
|
|
+ <select class="control"><option>吨袋</option><option>槽罐</option></select>
|
|
|
+ </div>
|
|
|
+ <div class="field">
|
|
|
+ <label>送货方式 <span style="color:var(--danger)">*</span></label>
|
|
|
+ <select class="control"><option>送货上门</option><option>客户自提</option></select>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="qty-balance">
|
|
|
+ <div>计划总量 <b>32</b> 吨</div>
|
|
|
+ <div>装货合计 <b>32</b> 吨</div>
|
|
|
+ <div>送货合计 <b>32</b> 吨</div>
|
|
|
+ <div class="qty-ok">✓ 数量平衡</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="section" id="sec-remark">
|
|
|
+ <h3>备注</h3>
|
|
|
+ <div class="field">
|
|
|
+ <label>备注</label>
|
|
|
+ <textarea class="control" id="f-remark" placeholder="请输入备注">注意厂区进门登记</textarea>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="map-nav">滚动或点上方锚点切换分区 · 字段对齐 Web 弹窗</div>
|
|
|
+ </div>
|
|
|
+ <div class="fab-bar" id="form-fab"></div>
|
|
|
+ </section>
|
|
|
+
|
|
|
+ <!-- PRINT -->
|
|
|
+ <section class="screen" id="screen-print" data-title="打印">
|
|
|
+ <header class="header">
|
|
|
+ <div class="header-row">
|
|
|
+ <button class="back" type="button" data-nav="detail">← 详情</button>
|
|
|
+ </div>
|
|
|
+ <h1>打印单据</h1>
|
|
|
+ <div class="sub">长按预览区可存图(Demo 示意)</div>
|
|
|
+ </header>
|
|
|
+ <div class="chips" id="print-kinds">
|
|
|
+ <button class="chip active" type="button" data-kind="inbound">入库单</button>
|
|
|
+ <button class="chip" type="button" data-kind="delivery">送(提)货单</button>
|
|
|
+ <button class="chip" type="button" data-kind="subpack">分装单</button>
|
|
|
+ <button class="chip" type="button" data-kind="blank-inbound">空白入库单</button>
|
|
|
+ <button class="chip" type="button" data-kind="blank-delivery">空白送货单</button>
|
|
|
+ <button class="chip" type="button" data-kind="blank-subpack">空白分装单</button>
|
|
|
+ </div>
|
|
|
+ <div class="scroll pad-fab">
|
|
|
+ <div class="hint-box">WebView 不调用系统打印机,引导长按存图。</div>
|
|
|
+ <div class="section" id="print-customer-block" style="margin:0 12px 12px">
|
|
|
+ <h3>送货客户</h3>
|
|
|
+ <div class="field" style="margin:0">
|
|
|
+ <label>打印送(提)货单时需选择送货段客户</label>
|
|
|
+ <button class="picker" type="button" data-action="pick-print-customer-open">
|
|
|
+ <span id="print-customer-label"><span class="ph">请选择送货客户</span></span>
|
|
|
+ <span class="chev">选择</span>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="print-preview" id="print-preview"></div>
|
|
|
+ </div>
|
|
|
+ <div class="fab-bar">
|
|
|
+ <button class="btn" type="button" data-nav="detail">返回详情</button>
|
|
|
+ <button class="btn btn-primary" type="button" data-action="toast" data-msg="已模拟保存预览图">保存预览</button>
|
|
|
+ </div>
|
|
|
+ </section>
|
|
|
+
|
|
|
+ <!-- PASTE:字段对齐 Web PlPasteRecognizeDialog · vehicle-bundle -->
|
|
|
+ <section class="screen" id="screen-paste" data-title="三方人车">
|
|
|
+ <header class="header">
|
|
|
+ <div class="header-row">
|
|
|
+ <button class="back" type="button" data-nav="list">← 列表</button>
|
|
|
+ </div>
|
|
|
+ <h1>三方人车快速创建</h1>
|
|
|
+ <div class="sub">对齐 Web 全量字段 · 识别后可编辑</div>
|
|
|
+ </header>
|
|
|
+ <div class="scroll pad-fab">
|
|
|
+ <div class="section">
|
|
|
+ <h3>客户原文</h3>
|
|
|
+ <div class="field">
|
|
|
+ <label>请按示例格式填写或粘贴后点击识别</label>
|
|
|
+ <textarea class="control" id="paste-text" style="min-height:150px" placeholder="请粘贴客户原文…">车牌:皖HD1234皖HG88挂
|
|
|
+核载:34 吨
|
|
|
+驾驶员:张伟
|
|
|
+身份证号:340102199001011234
|
|
|
+手机号:13800001111
|
|
|
+押运员:李强
|
|
|
+身份证号:340102199002021234
|
|
|
+电话:13900002222</textarea>
|
|
|
+ </div>
|
|
|
+ <button class="btn btn-primary btn-block btn-lg" type="button" data-action="paste-parse">识别</button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div id="paste-result-block" class="hidden">
|
|
|
+ <div class="hint-box" id="paste-warn" style="display:none"></div>
|
|
|
+
|
|
|
+ <div class="section">
|
|
|
+ <h3>车辆信息</h3>
|
|
|
+ <div class="field">
|
|
|
+ <label>承运商</label>
|
|
|
+ <button class="picker" type="button" data-sheet="carrier">
|
|
|
+ <span id="paste-carrier"><span class="ph">请选择承运商</span></span>
|
|
|
+ <span class="chev">选择</span>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ <div class="field">
|
|
|
+ <label>主车号</label>
|
|
|
+ <input class="control" id="paste-plate" maxlength="20" />
|
|
|
+ </div>
|
|
|
+ <div class="field">
|
|
|
+ <label>挂车号(必填;无挂车可填「货拉拉」)</label>
|
|
|
+ <input class="control" id="paste-trailer" maxlength="20" placeholder="必填" />
|
|
|
+ </div>
|
|
|
+ <div class="field">
|
|
|
+ <label>核载(吨)</label>
|
|
|
+ <input class="control" id="paste-load" type="number" min="0" step="0.01" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="section">
|
|
|
+ <h3>驾驶员</h3>
|
|
|
+ <div class="field">
|
|
|
+ <label>驾驶员</label>
|
|
|
+ <input class="control" id="paste-driver-name" maxlength="100" />
|
|
|
+ </div>
|
|
|
+ <div class="field">
|
|
|
+ <label>驾驶员手机</label>
|
|
|
+ <input class="control" id="paste-driver-phone" maxlength="32" />
|
|
|
+ </div>
|
|
|
+ <div class="field">
|
|
|
+ <label>驾驶员身份证</label>
|
|
|
+ <input class="control" id="paste-driver-id" maxlength="32" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="section">
|
|
|
+ <h3>押运员</h3>
|
|
|
+ <div class="field">
|
|
|
+ <label>押运员</label>
|
|
|
+ <input class="control" id="paste-escort-name" maxlength="100" />
|
|
|
+ </div>
|
|
|
+ <div class="field">
|
|
|
+ <label>押运员电话</label>
|
|
|
+ <input class="control" id="paste-escort-phone" maxlength="32" />
|
|
|
+ </div>
|
|
|
+ <div class="field">
|
|
|
+ <label>押运员身份证</label>
|
|
|
+ <input class="control" id="paste-escort-id" maxlength="32" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="section" id="paste-conflict-block">
|
|
|
+ <h3>冲突处理</h3>
|
|
|
+ <div class="stop-card">
|
|
|
+ <div class="muted" style="margin-bottom:8px">主车号已存在:皖H·D1234(演示)</div>
|
|
|
+ <div class="field">
|
|
|
+ <label>处理方式</label>
|
|
|
+ <div class="actions-grid">
|
|
|
+ <button class="btn filter-opt-like paste-conflict-opt active" type="button" data-action="pick-conflict" data-val="reuse">复用</button>
|
|
|
+ <button class="btn paste-conflict-opt" type="button" data-action="pick-conflict" data-val="overwrite">覆盖更新</button>
|
|
|
+ <button class="btn paste-conflict-opt" type="button" data-action="pick-conflict" data-val="cancel" style="grid-column:1/-1">取消创建本条</button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="fab-bar" id="paste-fab">
|
|
|
+ <button class="btn" type="button" data-nav="list">取消</button>
|
|
|
+ <button class="btn btn-primary" type="button" id="paste-submit-btn" data-action="paste-submit" disabled>确认生成</button>
|
|
|
+ </div>
|
|
|
+ </section>
|
|
|
+
|
|
|
+ <!-- sheets -->
|
|
|
+ <div class="sheet-mask" id="sheet-mask"></div>
|
|
|
+
|
|
|
+ <div class="sheet" id="sheet-vehicle">
|
|
|
+ <div class="sheet-handle"></div>
|
|
|
+ <div class="sheet-head"><h3>选择车辆</h3><button class="link-btn" type="button" data-action="close-sheet">关闭</button></div>
|
|
|
+ <div class="sheet-search">
|
|
|
+ <input class="sheet-search-input" type="search" placeholder="搜索车牌 / 挂车号" autocomplete="off" />
|
|
|
+ </div>
|
|
|
+ <div class="sheet-body" id="vehicle-options"></div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="sheet" id="sheet-personnel">
|
|
|
+ <div class="sheet-handle"></div>
|
|
|
+ <div class="sheet-head"><h3 id="personnel-title">选择人员</h3><button class="link-btn" type="button" data-action="close-sheet">关闭</button></div>
|
|
|
+ <div class="sheet-search">
|
|
|
+ <input class="sheet-search-input" type="search" placeholder="搜索姓名 / 手机号" autocomplete="off" />
|
|
|
+ </div>
|
|
|
+ <div class="sheet-body" id="personnel-options"></div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="sheet" id="sheet-customer">
|
|
|
+ <div class="sheet-handle"></div>
|
|
|
+ <div class="sheet-head"><h3>选择客户</h3><button class="link-btn" type="button" data-action="close-sheet">关闭</button></div>
|
|
|
+ <div class="sheet-search">
|
|
|
+ <input class="sheet-search-input" type="search" placeholder="搜索客户名称 / 地址" autocomplete="off" />
|
|
|
+ </div>
|
|
|
+ <div class="sheet-body" id="customer-options"></div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="sheet" id="sheet-product">
|
|
|
+ <div class="sheet-handle"></div>
|
|
|
+ <div class="sheet-head"><h3>选择产品</h3><button class="link-btn" type="button" data-action="close-sheet">关闭</button></div>
|
|
|
+ <div class="sheet-search">
|
|
|
+ <input class="sheet-search-input" type="search" placeholder="搜索产品名称 / 规格" autocomplete="off" />
|
|
|
+ </div>
|
|
|
+ <div class="sheet-body" id="product-options"></div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="sheet" id="sheet-carrier">
|
|
|
+ <div class="sheet-handle"></div>
|
|
|
+ <div class="sheet-head"><h3>选择承运商</h3><button class="link-btn" type="button" data-action="close-sheet">关闭</button></div>
|
|
|
+ <div class="sheet-search">
|
|
|
+ <input class="sheet-search-input" type="search" placeholder="搜索承运商名称" autocomplete="off" />
|
|
|
+ </div>
|
|
|
+ <div class="sheet-body" id="carrier-options"></div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="sheet" id="sheet-contract">
|
|
|
+ <div class="sheet-handle"></div>
|
|
|
+ <div class="sheet-head"><h3>选择合同</h3><button class="link-btn" type="button" data-action="close-sheet">关闭</button></div>
|
|
|
+ <div class="sheet-search">
|
|
|
+ <input class="sheet-search-input" type="search" placeholder="搜索合同编号 / 名称" autocomplete="off" />
|
|
|
+ </div>
|
|
|
+ <div class="sheet-body" id="contract-options"></div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="sheet" id="sheet-address">
|
|
|
+ <div class="sheet-handle"></div>
|
|
|
+ <div class="sheet-head"><h3>从地址库选择</h3><button class="link-btn" type="button" data-action="close-sheet">关闭</button></div>
|
|
|
+ <div class="sheet-search">
|
|
|
+ <input class="sheet-search-input" type="search" placeholder="搜索地址 / 联系人" autocomplete="off" />
|
|
|
+ </div>
|
|
|
+ <div class="sheet-body" id="address-options"></div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="sheet" id="sheet-vehicle-view">
|
|
|
+ <div class="sheet-handle"></div>
|
|
|
+ <div class="sheet-head"><h3>车辆详情</h3><button class="link-btn" type="button" data-action="close-sheet">关闭</button></div>
|
|
|
+ <div class="sheet-body" id="vehicle-view-body" style="padding:8px 4px 20px"></div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="sheet half" id="sheet-detail-more">
|
|
|
+ <div class="sheet-handle"></div>
|
|
|
+ <div class="sheet-head"><h3>更多操作</h3><button class="link-btn" type="button" data-action="close-sheet">关闭</button></div>
|
|
|
+ <div class="sheet-body" id="detail-more-options"></div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="sheet half" id="sheet-list-more">
|
|
|
+ <div class="sheet-handle"></div>
|
|
|
+ <div class="sheet-head"><h3>卡片操作</h3><button class="link-btn" type="button" data-action="close-sheet">关闭</button></div>
|
|
|
+ <div class="sheet-body" id="list-more-options"></div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="sheet half" id="sheet-confirm">
|
|
|
+ <div class="sheet-handle"></div>
|
|
|
+ <div class="sheet-head"><h3 id="confirm-title">确认</h3><button class="link-btn" type="button" data-action="close-sheet">关闭</button></div>
|
|
|
+ <div class="sheet-body"><p id="confirm-msg" style="padding:8px 4px;color:var(--muted);font-size:13px"></p></div>
|
|
|
+ <div class="sheet-foot">
|
|
|
+ <button class="btn" type="button" data-action="close-sheet">取消</button>
|
|
|
+ <button class="btn btn-primary" type="button" id="confirm-ok">确定</button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="toast" id="toast"></div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <script>
|
|
|
+ const methanolProd = { name: '甲醇', spec: '优等品', unit: '吨', pack: '吨袋', qtyPlan: 32, qtyActual: '—', method: '送货上门', alias: '木精', brand: '山峡', arrangeable: 32 }
|
|
|
+ const ethanolProd = { name: '乙醇', spec: '工业级', unit: '吨', pack: '槽罐', qtyPlan: 28, qtyActual: 27.8, method: '送货上门', alias: '酒精', brand: '某牌', arrangeable: 28 }
|
|
|
+ const aceticProd = { name: '冰醋酸', spec: '99.8%', unit: '吨', pack: '桶装', qtyPlan: 20, qtyActual: '—', method: '客户自提', alias: '', brand: '南通', arrangeable: 20 }
|
|
|
+
|
|
|
+ const plans = [
|
|
|
+ {
|
|
|
+ id: 'p1', no: 'JH20260914003', type: '出库单', status: '待安排', tag: 'tag-warn', date: '2026-09-14',
|
|
|
+ plate: '未派车', people: '—', product: '甲醇 32 吨 · 安庆山峡 → 合肥某化', time: '09-14 14:00',
|
|
|
+ supplier: '安庆山峡仓储', buyer: '合肥某化', follower: '王调度',
|
|
|
+ tabKeys: ['pending_arrange', 'outbound', 'all'], followStatus: 'pending_arrange', transportStatus: 'pending',
|
|
|
+ canArrange: true, canEdit: true, canWait: true, canCancelWait: false, canVoid: true, arrangeLabel: '安排运输',
|
|
|
+ loadCustomer: '安庆山峡仓储', loadTime: '2026-09-14', loadAddr: '安庆市迎江区化工园 A3',
|
|
|
+ loadContact: '0556-1234567', loadNotes: '进门登记', loadContract: '内部客户(无合同)', loadDibang: '生成(入库)',
|
|
|
+ loadProducts: [methanolProd],
|
|
|
+ deliveries: [
|
|
|
+ { customer: '合肥某化', time: '2026-09-14', addr: '合肥经开区某某路 88 号', contact: '0551-88886666', notes: '需预约卸货', contract: 'HT202609-001', dibang: '未生成', products: [{ ...methanolProd, qtyPlan: 24, arrangeable: 24 }] },
|
|
|
+ { customer: '南京某精细化工', time: '2026-09-15', addr: '南京江北新区', contact: '025-66668888', notes: '分卸 8 吨', contract: 'HT202607-056', dibang: '未生成', products: [{ ...methanolProd, qtyPlan: 8, arrangeable: 8 }] }
|
|
|
+ ],
|
|
|
+ siteLinks: [], vehicleDetail: null, feedback: null, qty: { plan: 32, load: 32, del: 32, balanced: true },
|
|
|
+ hazardCertReady: false,
|
|
|
+ hazardProducts: [
|
|
|
+ {
|
|
|
+ name: '甲醇', standardName: '甲醇', hazard: 'toxic',
|
|
|
+ purchaseCert: '甲醇购买证.pdf',
|
|
|
+ transportCert: '',
|
|
|
+ legalUseCert: ''
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 'p2', no: 'JH20260914001', type: '入库单', status: '已安排', tag: 'tag-ok', date: '2026-09-14',
|
|
|
+ plate: '皖H·D1234', people: '张伟 / 李强', product: '乙醇 28 吨 · 张家港 → 安庆仓', time: '09-14 10:30',
|
|
|
+ supplier: '张家港某仓', buyer: '安庆山峡仓储', follower: '李调度',
|
|
|
+ tabKeys: ['arranged', 'inbound', 'all'], followStatus: 'arranged', transportStatus: 'arranged',
|
|
|
+ canArrange: true, canEdit: true, canWait: false, canCancelWait: false, canVoid: true, arrangeLabel: '调整运输',
|
|
|
+ loadCustomer: '张家港某仓', loadTime: '2026-09-14', loadAddr: '张家港保税区',
|
|
|
+ loadContact: '0512-88880000', loadNotes: '危化通行证', loadContract: 'HT202608-118', loadDibang: '生成(出库)',
|
|
|
+ loadProducts: [ethanolProd],
|
|
|
+ deliveries: [
|
|
|
+ { customer: '安庆山峡仓储', time: '2026-09-14', addr: '安庆市迎江区化工园 A3', contact: '0556-1234567', notes: '入库过磅', contract: '内部客户(无合同)', dibang: '生成(入库)', products: [ethanolProd] }
|
|
|
+ ],
|
|
|
+ siteLinks: [
|
|
|
+ { label: '装货现场', url: 'https://w.example.com/h5/site?t=load***' },
|
|
|
+ { label: '送货现场', url: 'https://w.example.com/h5/site?t=del***' }
|
|
|
+ ],
|
|
|
+ vehicleDetail: { plate: '皖H·D1234', trailer: '皖H·G88', type: '危化品', loadTon: 34, carrier: '安庆某物流', driver: '张伟', driverPhone: '13800001111', escort: '李强', escortPhone: '13900002222' },
|
|
|
+ feedback: { driver: '装货已签到 · 预计 16:30 到场 · 实装 27.8 吨', site: '现场已确认到场 · 地磅入库单 DB20260914088 已生成' },
|
|
|
+ qty: { plan: 28, load: 28, del: 28, balanced: true },
|
|
|
+ hazardCertReady: true,
|
|
|
+ hazardProducts: [
|
|
|
+ {
|
|
|
+ name: '乙醇', standardName: '乙醇', hazard: 'toxic',
|
|
|
+ purchaseCert: '乙醇购买证.pdf',
|
|
|
+ transportCert: '乙醇运输证.pdf',
|
|
|
+ legalUseCert: ''
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 'p3', no: 'JH20260913018', type: '直销单', status: '等通知', tag: 'tag-wait', date: '2026-09-13',
|
|
|
+ plate: '苏E·5M892', people: '陈师傅', product: '冰醋酸 20 吨 · 南通 → 芜湖', time: '待通知',
|
|
|
+ supplier: '南通某厂', buyer: '芜湖港务', follower: '赵跟单',
|
|
|
+ tabKeys: ['pending_arrange', 'direct', 'all'], followStatus: 'pending_arrange', transportStatus: 'pending',
|
|
|
+ canArrange: true, canEdit: true, canWait: false, canCancelWait: true, canVoid: true, arrangeLabel: '安排运输',
|
|
|
+ loadCustomer: '南通某厂', loadTime: '待通知', loadAddr: '南通港区',
|
|
|
+ loadContact: '0513-70001111', loadNotes: '等客户通知后装车', loadContract: 'HT202607-056', loadDibang: '未生成',
|
|
|
+ loadProducts: [aceticProd],
|
|
|
+ deliveries: [
|
|
|
+ { customer: '芜湖港务', time: '待通知', addr: '芜湖鸠江区港口路', contact: '0553-22223333', notes: '港区预约', contract: 'HT202608-118', dibang: '未生成', products: [aceticProd] }
|
|
|
+ ],
|
|
|
+ siteLinks: [], vehicleDetail: { plate: '苏E·5M892', trailer: '无', type: '普货', loadTon: 20, carrier: '货拉拉(临调)', driver: '陈师傅', driverPhone: '13700003333', escort: '—', escortPhone: '—' },
|
|
|
+ feedback: null, qty: { plan: 20, load: 20, del: 20, balanced: true },
|
|
|
+ hazardCertReady: false,
|
|
|
+ hazardProducts: [
|
|
|
+ {
|
|
|
+ name: '冰醋酸', standardName: '冰醋酸', hazard: 'explosive',
|
|
|
+ purchaseCert: '',
|
|
|
+ transportCert: '',
|
|
|
+ legalUseCert: ''
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 'p4', no: 'JH20260912007', type: '出库单', status: '草稿中', tag: 'tag-mute', date: '2026-09-12',
|
|
|
+ plate: '—', people: '—', product: '待完善产品行', time: '—',
|
|
|
+ supplier: '—', buyer: '—', follower: '王调度',
|
|
|
+ tabKeys: ['drafting', 'outbound', 'all'], followStatus: 'drafting', transportStatus: 'pending',
|
|
|
+ canArrange: false, canEdit: true, canWait: false, canCancelWait: false, canVoid: true, arrangeLabel: '安排运输',
|
|
|
+ loadCustomer: '—', loadTime: '—', loadAddr: '—', loadContact: '—', loadNotes: '—', loadContract: '—', loadDibang: '—',
|
|
|
+ loadProducts: [], deliveries: [], siteLinks: [], vehicleDetail: null, feedback: null,
|
|
|
+ qty: { plan: 0, load: 0, del: 0, balanced: false },
|
|
|
+ hazardCertReady: true,
|
|
|
+ hazardProducts: []
|
|
|
+ }
|
|
|
+ ]
|
|
|
+
|
|
|
+ const vehicles = [
|
|
|
+ { id: 'v1', t: '皖H·D1234', d: '挂 皖H·G88 · 危化品' },
|
|
|
+ { id: 'v2', t: '苏E·5M892', d: '无挂车 · 普货' },
|
|
|
+ { id: 'v3', t: '皖A·88K21', d: '挂 皖A·G12 · 危化品' },
|
|
|
+ { id: 'v4', t: '皖H·F6688', d: '挂 皖H·G66 · 危化品' },
|
|
|
+ { id: 'v5', t: '浙B·9C321', d: '无挂车 · 普货' }
|
|
|
+ ]
|
|
|
+ const drivers = [
|
|
|
+ { id: 'd1', t: '张伟', d: '13800001111 · 司机' },
|
|
|
+ { id: 'd2', t: '陈师傅', d: '13700003333 · 司机' },
|
|
|
+ { id: 'd3', t: '刘洋', d: '13600005555 · 司机' },
|
|
|
+ { id: 'd4', t: '赵磊', d: '13500006666 · 司机' }
|
|
|
+ ]
|
|
|
+ const escorts = [
|
|
|
+ { id: 'e1', t: '李强', d: '13900002222 · 押运' },
|
|
|
+ { id: 'e2', t: '王磊', d: '13600004444 · 押运' },
|
|
|
+ { id: 'e3', t: '周敏', d: '13300007777 · 押运' }
|
|
|
+ ]
|
|
|
+ const customers = [
|
|
|
+ { id: 'c1', t: '安庆山峡仓储', d: '安庆迎江区化工园 A3' },
|
|
|
+ { id: 'c2', t: '合肥某化', d: '合肥经开区某某路 88 号' },
|
|
|
+ { id: 'c3', t: '张家港某仓', d: '张家港保税区' },
|
|
|
+ { id: 'c4', t: '芜湖港务', d: '芜湖鸠江区港口路' },
|
|
|
+ { id: 'c5', t: '南京某精细化工', d: '南京江北新区' }
|
|
|
+ ]
|
|
|
+ const products = [
|
|
|
+ { id: 'pr1', t: '甲醇 · 32 吨 · 吨袋', d: '危化品' },
|
|
|
+ { id: 'pr2', t: '乙醇 · 28 吨 · 槽罐', d: '危化品' },
|
|
|
+ { id: 'pr3', t: '冰醋酸 · 20 吨 · 桶装', d: '危化品' },
|
|
|
+ { id: 'pr4', t: '液碱 · 30 吨 · 槽罐', d: '危化品' },
|
|
|
+ { id: 'pr5', t: '盐酸 · 25 吨 · 槽罐', d: '危化品' }
|
|
|
+ ]
|
|
|
+ const carriers = [
|
|
|
+ { id: 'ca1', t: '安庆某物流', d: '三方承运商' },
|
|
|
+ { id: 'ca2', t: '合肥危运公司', d: '三方承运商' },
|
|
|
+ { id: 'ca3', t: '货拉拉(临调)', d: '临调' },
|
|
|
+ { id: 'ca4', t: '芜湖顺达运输', d: '三方承运商' },
|
|
|
+ { id: 'ca5', t: '南京危化专运', d: '三方承运商' },
|
|
|
+ { id: 'ca6', t: '安庆山峡自有车队', d: '自有' }
|
|
|
+ ]
|
|
|
+ const contracts = [
|
|
|
+ { id: 'ct1', t: 'HT202609-001', d: '合肥某化 · 甲醇销售' },
|
|
|
+ { id: 'ct2', t: 'HT202608-118', d: '芜湖港务 · 乙醇采购' },
|
|
|
+ { id: 'ct3', t: 'HT202607-056', d: '南京某精细化工 · 冰醋酸' }
|
|
|
+ ]
|
|
|
+ const addresses = [
|
|
|
+ { id: 'a1', t: '安庆市迎江区化工园 A3', d: '安庆山峡仓储 · 0556-1234567' },
|
|
|
+ { id: 'a2', t: '合肥经开区某某路 88 号', d: '合肥某化 · 0551-88886666' },
|
|
|
+ { id: 'a3', t: '张家港保税区', d: '张家港某仓 · 0512-88880000' },
|
|
|
+ { id: 'a4', t: '芜湖鸠江区港口路', d: '芜湖港务 · 0553-22223333' },
|
|
|
+ { id: 'a5', t: '南京江北新区', d: '南京某精细化工 · 025-66668888' }
|
|
|
+ ]
|
|
|
+
|
|
|
+ const state = {
|
|
|
+ screen: 'list',
|
|
|
+ history: [],
|
|
|
+ tab: 'pending_arrange',
|
|
|
+ date: 'today',
|
|
|
+ customDateLabel: '',
|
|
|
+ listPage: 1,
|
|
|
+ listPageSize: 2,
|
|
|
+ currentPlanId: 'p1',
|
|
|
+ deliveryTabIndex: 0,
|
|
|
+ formMode: 'new', // new | edit | arrange | copy
|
|
|
+ personnelRole: '司机',
|
|
|
+ pickerTarget: '',
|
|
|
+ confirmKind: '',
|
|
|
+ printKind: 'inbound',
|
|
|
+ printCustomer: '',
|
|
|
+ listMorePlanId: '',
|
|
|
+ stopCount: { load: 1, delivery: 1 },
|
|
|
+ pasteParsed: false,
|
|
|
+ pasteConflict: 'reuse',
|
|
|
+ picker: {
|
|
|
+ sheetId: '',
|
|
|
+ containerId: '',
|
|
|
+ pickAction: '',
|
|
|
+ items: [],
|
|
|
+ keyword: ''
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const $ = (sel, root = document) => root.querySelector(sel)
|
|
|
+ const $$ = (sel, root = document) => Array.from(root.querySelectorAll(sel))
|
|
|
+
|
|
|
+ function toast(msg) {
|
|
|
+ const el = $('#toast')
|
|
|
+ el.textContent = msg
|
|
|
+ el.classList.add('show')
|
|
|
+ clearTimeout(toast._t)
|
|
|
+ toast._t = setTimeout(() => el.classList.remove('show'), 1800)
|
|
|
+ }
|
|
|
+
|
|
|
+ function currentPlan() {
|
|
|
+ return plans.find((p) => p.id === state.currentPlanId) || plans[0]
|
|
|
+ }
|
|
|
+
|
|
|
+ function closeSheet() {
|
|
|
+ $('#sheet-mask').classList.remove('open')
|
|
|
+ $$('.sheet').forEach((s) => s.classList.remove('open'))
|
|
|
+ }
|
|
|
+
|
|
|
+ function openSheet(id) {
|
|
|
+ closeSheet()
|
|
|
+ $('#sheet-mask').classList.add('open')
|
|
|
+ const el = document.getElementById(id)
|
|
|
+ if (el) el.classList.add('open')
|
|
|
+ }
|
|
|
+
|
|
|
+ function closeFilter() {
|
|
|
+ $('#filter-mask')?.classList.remove('open')
|
|
|
+ $$('.filter-panel').forEach((p) => p.classList.remove('open'))
|
|
|
+ $$('.filter-trig').forEach((t) => t.classList.remove('open'))
|
|
|
+ }
|
|
|
+
|
|
|
+ function closeSpeed() {
|
|
|
+ $('#speed-dial')?.classList.remove('open')
|
|
|
+ $('#speed-mask')?.classList.remove('open')
|
|
|
+ }
|
|
|
+
|
|
|
+ function openFilter(kind) {
|
|
|
+ closeSpeed()
|
|
|
+ const isOpen = $('#panel-' + kind)?.classList.contains('open')
|
|
|
+ closeFilter()
|
|
|
+ if (isOpen) return
|
|
|
+ $('#panel-' + kind)?.classList.add('open')
|
|
|
+ $('#trig-' + kind)?.classList.add('open')
|
|
|
+ $('#filter-mask')?.classList.add('open')
|
|
|
+ }
|
|
|
+
|
|
|
+ function showScreen(name, { push = true } = {}) {
|
|
|
+ closeSheet()
|
|
|
+ closeFilter()
|
|
|
+ closeSpeed()
|
|
|
+ if (push && state.screen !== name) state.history.push(state.screen)
|
|
|
+ state.screen = name
|
|
|
+ $$('.screen').forEach((s) => s.classList.toggle('active', s.id === 'screen-' + name))
|
|
|
+ if (name === 'detail') renderDetail()
|
|
|
+ if (name === 'form') renderForm()
|
|
|
+ if (name === 'print') renderPrint()
|
|
|
+ if (name === 'list') renderList()
|
|
|
+ }
|
|
|
+
|
|
|
+ function goBack() {
|
|
|
+ const prev = state.history.pop()
|
|
|
+ if (prev) showScreen(prev, { push: false })
|
|
|
+ else showScreen('list', { push: false })
|
|
|
+ }
|
|
|
+
|
|
|
+ function getRequiredCertSlots(product) {
|
|
|
+ if (product.hazard === 'explosive') {
|
|
|
+ return [{ key: 'legalUseCert', label: '合法证明' }]
|
|
|
+ }
|
|
|
+ // 易制毒(与 Web getRequiredCertSlots 一致)
|
|
|
+ return [
|
|
|
+ { key: 'purchaseCert', label: '购买证' },
|
|
|
+ { key: 'transportCert', label: '运输证' }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+
|
|
|
+ function isHazardProductCertComplete(product) {
|
|
|
+ return getRequiredCertSlots(product).every((slot) => String(product[slot.key] || '').trim())
|
|
|
+ }
|
|
|
+
|
|
|
+ function renderHazardCert(p) {
|
|
|
+ const list = $('#d-cert-list')
|
|
|
+ const empty = $('#d-cert-empty')
|
|
|
+ const hint = $('#d-cert-hint')
|
|
|
+ const summaryText = $('#d-cert-summary-text')
|
|
|
+ const readyTag = $('#d-cert-ready-tag')
|
|
|
+ const products = p.hazardProducts || []
|
|
|
+
|
|
|
+ if (!products.length) {
|
|
|
+ list.innerHTML = ''
|
|
|
+ empty.classList.remove('hidden')
|
|
|
+ hint.classList.add('hidden')
|
|
|
+ summaryText.textContent = '本计划无易制毒/易制爆产品'
|
|
|
+ readyTag.className = 'tag tag-mute'
|
|
|
+ readyTag.textContent = '无需证书'
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ empty.classList.add('hidden')
|
|
|
+ hint.classList.remove('hidden')
|
|
|
+ const allOk = products.every(isHazardProductCertComplete)
|
|
|
+ const ready = !!p.hazardCertReady && allOk
|
|
|
+ readyTag.className = 'tag ' + (ready ? 'tag-ok' : 'tag-warn')
|
|
|
+ readyTag.textContent = ready ? '已齐备' : '待确认'
|
|
|
+ const missingCount = products.reduce((n, prod) => {
|
|
|
+ return n + getRequiredCertSlots(prod).filter((s) => !String(prod[s.key] || '').trim()).length
|
|
|
+ }, 0)
|
|
|
+ summaryText.textContent = missingCount
|
|
|
+ ? `${products.length} 个危化产品 · ${missingCount} 项证书未上传`
|
|
|
+ : `${products.length} 个危化产品 · 附件已齐,待业务确认`
|
|
|
+
|
|
|
+ list.innerHTML = products.map((prod, index) => {
|
|
|
+ const mark = prod.hazard === 'explosive'
|
|
|
+ ? '<span class="cert-mark explosive">爆</span>'
|
|
|
+ : '<span class="cert-mark toxic">毒</span>'
|
|
|
+ const slots = getRequiredCertSlots(prod).map((slot) => {
|
|
|
+ const file = String(prod[slot.key] || '').trim()
|
|
|
+ const body = file
|
|
|
+ ? `<button class="cert-file" type="button" data-action="toast" data-msg="预览附件:${file}(Demo)"><span>📎 ${file}</span></button>`
|
|
|
+ : '<span class="cert-missing">未上传</span>'
|
|
|
+ return `
|
|
|
+ <div class="cert-slot">
|
|
|
+ <span class="cert-slot__label">${slot.label}</span>
|
|
|
+ <div class="cert-slot__body">${body}</div>
|
|
|
+ </div>
|
|
|
+ `
|
|
|
+ }).join('')
|
|
|
+ return `
|
|
|
+ <div class="cert-block">
|
|
|
+ <div class="cert-block__head">
|
|
|
+ <span class="cert-block__index">${index + 1}.</span>
|
|
|
+ <span class="cert-block__name">${prod.standardName || prod.name}</span>
|
|
|
+ ${mark}
|
|
|
+ </div>
|
|
|
+ <div class="cert-slots">${slots}</div>
|
|
|
+ </div>
|
|
|
+ `
|
|
|
+ }).join('')
|
|
|
+ }
|
|
|
+
|
|
|
+ function renderProdTable(hostId, rows) {
|
|
|
+ const host = document.getElementById(hostId)
|
|
|
+ if (!host) return
|
|
|
+ if (!rows || !rows.length) {
|
|
|
+ host.innerHTML = '<div class="prod-row muted">暂无产品</div>'
|
|
|
+ return
|
|
|
+ }
|
|
|
+ host.innerHTML = rows.map((r) => `
|
|
|
+ <div class="prod-row">
|
|
|
+ <div class="prod-row__name">${r.name || '—'}${r.spec ? ' · ' + r.spec : ''}</div>
|
|
|
+ <div class="prod-row__grid">
|
|
|
+ <div>单位 <b>${r.unit || '—'}</b></div>
|
|
|
+ <div>包装 <b>${r.pack || '—'}</b></div>
|
|
|
+ <div>计划量 <b>${r.qtyPlan ?? '—'}</b></div>
|
|
|
+ <div>实际量 <b>${r.qtyActual ?? '—'}</b></div>
|
|
|
+ ${r.method ? `<div>送货方式 <b>${r.method}</b></div>` : ''}
|
|
|
+ ${r.brand ? `<div>品牌 <b>${r.brand}</b></div>` : ''}
|
|
|
+ ${r.alias ? `<div>别名 <b>${r.alias}</b></div>` : ''}
|
|
|
+ ${r.arrangeable != null ? `<div>可安排 <b>${r.arrangeable}</b></div>` : ''}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ `).join('')
|
|
|
+ }
|
|
|
+
|
|
|
+ function renderDeliveryTab(p, idx) {
|
|
|
+ const list = p.deliveries || []
|
|
|
+ const d = list[idx] || list[0]
|
|
|
+ const tabs = $('#d-delivery-tabs')
|
|
|
+ if (!tabs) return
|
|
|
+ if (!list.length) {
|
|
|
+ tabs.innerHTML = ''
|
|
|
+ $('#d-del-customer').textContent = '—'
|
|
|
+ $('#d-del-time').textContent = '—'
|
|
|
+ $('#d-del-addr').textContent = '—'
|
|
|
+ $('#d-del-contact').textContent = '—'
|
|
|
+ $('#d-del-notes').textContent = '—'
|
|
|
+ $('#d-del-contract').textContent = '—'
|
|
|
+ $('#d-del-dibang').textContent = '—'
|
|
|
+ renderProdTable('d-del-products', [])
|
|
|
+ return
|
|
|
+ }
|
|
|
+ tabs.innerHTML = list.map((item, i) =>
|
|
|
+ `<button class="delivery-tab ${i === idx ? 'active' : ''}" type="button" data-action="pick-delivery-tab" data-idx="${i}">送货段 ${i + 1}</button>`
|
|
|
+ ).join('')
|
|
|
+ $('#d-del-customer').textContent = d.customer || '—'
|
|
|
+ $('#d-del-time').textContent = d.time || '—'
|
|
|
+ $('#d-del-addr').textContent = d.addr || '—'
|
|
|
+ $('#d-del-contact').textContent = d.contact || '—'
|
|
|
+ $('#d-del-notes').textContent = d.notes || '—'
|
|
|
+ $('#d-del-contract').textContent = d.contract || '—'
|
|
|
+ $('#d-del-dibang').textContent = d.dibang || '—'
|
|
|
+ renderProdTable('d-del-products', d.products || [])
|
|
|
+ }
|
|
|
+
|
|
|
+ function renderList() {
|
|
|
+ const kw = ($('#list-keyword').value || '').trim()
|
|
|
+ const rows = plans.filter((p) => {
|
|
|
+ if (state.tab !== 'all' && !p.tabKeys.includes(state.tab)) return false
|
|
|
+ if (!kw) return true
|
|
|
+ const blob = [p.no, p.type, p.plate, p.people, p.product, p.supplier, p.buyer, p.follower].join(' ')
|
|
|
+ return blob.includes(kw)
|
|
|
+ })
|
|
|
+ const list = $('#plan-list')
|
|
|
+ const btnMore = $('#btn-load-more')
|
|
|
+ if (!rows.length) {
|
|
|
+ list.innerHTML = '<div class="empty">该筛选条件下暂无计划</div>'
|
|
|
+ if (btnMore) btnMore.style.display = 'none'
|
|
|
+ return
|
|
|
+ }
|
|
|
+ const visible = rows.slice(0, state.listPage * state.listPageSize)
|
|
|
+ list.innerHTML = visible.map((p) => `
|
|
|
+ <article class="card" data-action="open-detail" data-id="${p.id}">
|
|
|
+ <div class="card-head">
|
|
|
+ <div>
|
|
|
+ <div class="plan-no">${p.no}</div>
|
|
|
+ <div class="muted">${p.type} · ${p.date}</div>
|
|
|
+ </div>
|
|
|
+ <span class="tag ${p.tag}">${p.status}</span>
|
|
|
+ </div>
|
|
|
+ <div class="meta">
|
|
|
+ <div>供方 → 需方:<b>${p.supplier || '—'} → ${p.buyer || '—'}</b></div>
|
|
|
+ <div>跟进人:<b>${p.follower || '—'}</b></div>
|
|
|
+ <div>车牌:<b>${p.plate}</b></div>
|
|
|
+ <div>人员:<b>${p.people}</b></div>
|
|
|
+ </div>
|
|
|
+ <div class="products">${p.product}</div>
|
|
|
+ <div class="card-actions">
|
|
|
+ <button class="btn" type="button" data-action="open-list-more" data-id="${p.id}">更多</button>
|
|
|
+ <button class="btn" type="button" data-action="open-form" data-mode="copy" data-id="${p.id}">复制</button>
|
|
|
+ ${p.canArrange ? `<button class="btn btn-primary" type="button" data-action="open-form" data-mode="arrange" data-id="${p.id}">${p.arrangeLabel}</button>` : ''}
|
|
|
+ </div>
|
|
|
+ </article>
|
|
|
+ `).join('')
|
|
|
+ if (btnMore) {
|
|
|
+ const hasMore = visible.length < rows.length
|
|
|
+ btnMore.style.display = hasMore ? 'block' : 'none'
|
|
|
+ btnMore.textContent = hasMore ? `加载更多(${visible.length}/${rows.length})` : '没有更多了'
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ function renderDetail() {
|
|
|
+ const p = currentPlan()
|
|
|
+ $('#detail-no').textContent = p.no
|
|
|
+ $('#detail-sub').textContent = `${p.type} · 计划日期 ${p.date}`
|
|
|
+ const tag = $('#detail-tag')
|
|
|
+ tag.className = 'tag ' + p.tag
|
|
|
+ tag.textContent = p.status
|
|
|
+
|
|
|
+ $('#d-plan-no').textContent = p.no
|
|
|
+ $('#d-plan-date').textContent = p.date
|
|
|
+ $('#d-company').textContent = '安庆山峡'
|
|
|
+ $('#d-type').textContent = p.type
|
|
|
+ $('#d-follow').textContent = p.follower || '王调度'
|
|
|
+
|
|
|
+ const arranged = p.transportStatus === 'arranged'
|
|
|
+ const hasVehicle = p.plate && p.plate !== '未派车' && p.plate !== '—'
|
|
|
+ $('#d-arrange-links').classList.toggle('hidden', !arranged)
|
|
|
+ $('#d-vehicle-block').classList.toggle('hidden', !hasVehicle && !arranged)
|
|
|
+ $('#d-vehicle-empty').classList.toggle('hidden', !!(hasVehicle || arranged))
|
|
|
+ $('#d-feedback')?.classList.toggle('hidden', !(arranged && p.feedback))
|
|
|
+ if (arranged) {
|
|
|
+ const links = $('#d-site-links')
|
|
|
+ const siteLinks = p.siteLinks && p.siteLinks.length
|
|
|
+ ? p.siteLinks
|
|
|
+ : [{ label: '现场链接', url: 'https://w.example.com/h5/site?t=***' }]
|
|
|
+ links.innerHTML = siteLinks.map((s) => `
|
|
|
+ <div class="link-row">
|
|
|
+ <input class="control" readonly value="${s.url}" />
|
|
|
+ <button class="btn" type="button" data-action="toast" data-msg="已复制${s.label}">复制</button>
|
|
|
+ </div>
|
|
|
+ <div class="muted" style="margin:-4px 0 6px">${s.label}</div>
|
|
|
+ `).join('')
|
|
|
+ }
|
|
|
+ if (hasVehicle || arranged) {
|
|
|
+ const vd = p.vehicleDetail
|
|
|
+ $('#d-v-plate').textContent = vd?.plate || (hasVehicle ? p.plate : '皖H·D1234')
|
|
|
+ $('#d-v-trailer').textContent = vd ? `挂 ${vd.trailer}` : (hasVehicle ? '挂 皖H·G88' : '挂 —')
|
|
|
+ const people = p.people && p.people !== '—' ? p.people : '张伟 / 李强'
|
|
|
+ const parts = people.split(/[\/·]/).map((s) => s.trim()).filter(Boolean)
|
|
|
+ $('#d-v-people').textContent =
|
|
|
+ `司机 ${vd?.driver || parts[0] || '—'} · 押运 ${vd?.escort || parts[1] || '—'}`
|
|
|
+ $('#d-v-carrier').textContent = `承运商 ${vd?.carrier || (arranged ? '安庆某物流' : '—')}`
|
|
|
+ $('#d-v-view')?.classList.toggle('hidden', !vd && !hasVehicle)
|
|
|
+ }
|
|
|
+ if (p.feedback) {
|
|
|
+ $('#d-fb-driver').textContent = p.feedback.driver || '—'
|
|
|
+ $('#d-fb-site').textContent = p.feedback.site || '—'
|
|
|
+ }
|
|
|
+
|
|
|
+ $('#d-load-customer').textContent = p.loadCustomer || '—'
|
|
|
+ $('#d-load-time').textContent = p.loadTime || '—'
|
|
|
+ $('#d-load-addr').textContent = p.loadAddr || '—'
|
|
|
+ $('#d-load-contact').textContent = p.loadContact || '—'
|
|
|
+ $('#d-load-notes').textContent = p.loadNotes || '—'
|
|
|
+ $('#d-load-contract').textContent = p.loadContract || '—'
|
|
|
+ $('#d-load-dibang').textContent = p.loadDibang || '—'
|
|
|
+ renderProdTable('d-load-products', p.loadProducts || [])
|
|
|
+ if (state.deliveryTabIndex >= (p.deliveries || []).length) state.deliveryTabIndex = 0
|
|
|
+ renderDeliveryTab(p, state.deliveryTabIndex)
|
|
|
+
|
|
|
+ const q = p.qty || { plan: 0, load: 0, del: 0, balanced: true }
|
|
|
+ $('#d-qty-plan').textContent = String(q.plan)
|
|
|
+ $('#d-qty-load').textContent = String(q.load)
|
|
|
+ $('#d-qty-del').textContent = String(q.del)
|
|
|
+ if (q.balanced) {
|
|
|
+ $('#d-qty-balance').textContent = '✓ 数量平衡'
|
|
|
+ $('#d-qty-balance').className = 'qty-ok'
|
|
|
+ } else {
|
|
|
+ $('#d-qty-balance').textContent = p.id === 'p4' ? '数量不平衡(可暂存)' : '⚠ 装送数量不一致'
|
|
|
+ $('#d-qty-balance').className = ''
|
|
|
+ }
|
|
|
+
|
|
|
+ renderHazardCert(p)
|
|
|
+
|
|
|
+ if (p.id === 'p4') {
|
|
|
+ $('#d-freight-has').textContent = '否'
|
|
|
+ ;['d-freight-km','d-freight-type','d-freight-qty','d-freight-price','d-freight-amount','d-freight-invoice','d-freight-remark'].forEach((id) => {
|
|
|
+ const el = document.getElementById(id)
|
|
|
+ if (el) el.textContent = '—'
|
|
|
+ })
|
|
|
+ $('#d-remark').textContent = '—'
|
|
|
+ } else {
|
|
|
+ $('#d-freight-has').textContent = arranged ? '是' : '否'
|
|
|
+ if (arranged) {
|
|
|
+ $('#d-freight-km').textContent = '186'
|
|
|
+ $('#d-freight-type').textContent = '按重量'
|
|
|
+ $('#d-freight-qty').textContent = String(q.plan || 28)
|
|
|
+ $('#d-freight-price').textContent = '120.00'
|
|
|
+ $('#d-freight-amount').textContent = '3,840.00'
|
|
|
+ $('#d-freight-invoice').textContent = '是'
|
|
|
+ $('#d-freight-remark').textContent = '含过路费'
|
|
|
+ } else {
|
|
|
+ ;['d-freight-km','d-freight-type','d-freight-qty','d-freight-price','d-freight-amount','d-freight-invoice','d-freight-remark'].forEach((id) => {
|
|
|
+ const el = document.getElementById(id)
|
|
|
+ if (el) el.textContent = '—'
|
|
|
+ })
|
|
|
+ }
|
|
|
+ $('#d-remark').textContent = '注意厂区进门登记'
|
|
|
+ }
|
|
|
+
|
|
|
+ const primary = []
|
|
|
+ const more = []
|
|
|
+ if (p.canEdit) {
|
|
|
+ primary.push({
|
|
|
+ html: `<button class="btn btn-primary" type="button" data-action="open-form" data-mode="edit" data-id="${p.id}">编辑</button>`
|
|
|
+ })
|
|
|
+ }
|
|
|
+ if (p.canArrange) {
|
|
|
+ primary.push({
|
|
|
+ html: `<button class="btn ${p.canEdit ? '' : 'btn-primary'}" type="button" data-action="open-form" data-mode="arrange" data-id="${p.id}">${p.arrangeLabel}</button>`
|
|
|
+ })
|
|
|
+ }
|
|
|
+ more.push({
|
|
|
+ html: `<button class="more-action" type="button" data-action="open-form" data-mode="copy" data-id="${p.id}">复制</button>`
|
|
|
+ })
|
|
|
+ if (p.canWait) {
|
|
|
+ more.push({
|
|
|
+ html: `<button class="more-action" type="button" data-action="confirm" data-kind="wait">等客户通知</button>`
|
|
|
+ })
|
|
|
+ }
|
|
|
+ if (p.canCancelWait) {
|
|
|
+ more.push({
|
|
|
+ html: `<button class="more-action" type="button" data-action="confirm" data-kind="cancel-wait">取消等通知</button>`
|
|
|
+ })
|
|
|
+ }
|
|
|
+ more.push({
|
|
|
+ html: `<button class="more-action" type="button" data-nav="print">打印单据</button>`
|
|
|
+ })
|
|
|
+ if (p.canVoid) {
|
|
|
+ more.push({
|
|
|
+ html: `<button class="more-action danger" type="button" data-action="confirm" data-kind="void">作废</button>`
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ while (primary.length > 2) more.unshift(primary.pop())
|
|
|
+ while (primary.length < 2 && more.length) {
|
|
|
+ const item = more.shift()
|
|
|
+ const promoted = item.html
|
|
|
+ .replace('class="more-action danger"', 'class="btn btn-danger"')
|
|
|
+ .replace('class="more-action"', 'class="btn"')
|
|
|
+ primary.push({ html: promoted })
|
|
|
+ }
|
|
|
+
|
|
|
+ const fab = []
|
|
|
+ if (more.length) {
|
|
|
+ fab.push(`<button class="btn btn-more" type="button" data-action="open-detail-more" aria-label="更多">⋯</button>`)
|
|
|
+ }
|
|
|
+ primary.forEach((item) => fab.push(item.html))
|
|
|
+ $('#detail-fab').innerHTML = fab.join('')
|
|
|
+
|
|
|
+ const moreHost = $('#detail-more-options')
|
|
|
+ if (moreHost) {
|
|
|
+ moreHost.innerHTML = more.length
|
|
|
+ ? more.map((item) => item.html).join('')
|
|
|
+ : '<div class="sheet-empty">暂无更多操作</div>'
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ function renderForm() {
|
|
|
+ const titles = {
|
|
|
+ new: '新增运输计划',
|
|
|
+ edit: '编辑运输计划',
|
|
|
+ arrange: '安排运输',
|
|
|
+ copy: '新增运输计划'
|
|
|
+ }
|
|
|
+ const hints = {
|
|
|
+ new: '对齐 Web 新增:取消 / 暂存 / 创建 / 创建并安排运输',
|
|
|
+ edit: '对齐 Web 编辑:按跟进/运输状态显示暂存、保存、保存并安排',
|
|
|
+ arrange: '对齐 Web 安排/调整:同一表单,openForArrange 模式',
|
|
|
+ copy: '对齐 Web 复制:等同新增(预填来源计划)'
|
|
|
+ }
|
|
|
+ $('#form-title').textContent = titles[state.formMode] || titles.new
|
|
|
+ $('#form-hint').textContent = hints[state.formMode] || hints.new
|
|
|
+ const p = currentPlan()
|
|
|
+ if (state.formMode !== 'new' && state.formMode !== 'copy') {
|
|
|
+ $('#form-sub').textContent = `${p.no} · ${p.type} · ${p.status}`
|
|
|
+ } else {
|
|
|
+ $('#form-sub').textContent = '长页分区 · 锚点跳转 · 按钮对齐 Web 弹窗'
|
|
|
+ }
|
|
|
+
|
|
|
+ // 对齐 PlPlanFormDialog 底栏显隐规则
|
|
|
+ const isNewLike = state.formMode === 'new' || state.formMode === 'copy'
|
|
|
+ const isArrange = state.formMode === 'arrange'
|
|
|
+ const isRearrange = isArrange && p.transportStatus === 'arranged'
|
|
|
+ const showDraft =
|
|
|
+ isNewLike ||
|
|
|
+ (!isRearrange &&
|
|
|
+ (p.followStatus === 'drafting' || p.followStatus === 'pending_arrange'))
|
|
|
+ const showSave = isNewLike || (!isRearrange && p.transportStatus !== 'arranged')
|
|
|
+ const showSaveAndArrange =
|
|
|
+ isNewLike || (!isRearrange && (p.transportStatus === 'pending' || p.transportStatus === 'arranged'))
|
|
|
+ const showRearrange = isRearrange
|
|
|
+
|
|
|
+ const fab = []
|
|
|
+ fab.push(`<button class="btn" type="button" data-action="back">取消</button>`)
|
|
|
+ if (showDraft) {
|
|
|
+ fab.push(`<button class="btn" type="button" data-action="form-draft">暂存</button>`)
|
|
|
+ }
|
|
|
+ if (showSave) {
|
|
|
+ const saveLabel = isNewLike ? '创建' : '保存'
|
|
|
+ const saveCls = showSaveAndArrange || showRearrange ? 'btn' : 'btn btn-primary'
|
|
|
+ fab.push(
|
|
|
+ `<button class="${saveCls}" type="button" data-action="form-save">${saveLabel}</button>`
|
|
|
+ )
|
|
|
+ }
|
|
|
+ if (showSaveAndArrange) {
|
|
|
+ const arrangeLabel = isNewLike ? '创建并安排运输' : '保存并安排'
|
|
|
+ fab.push(
|
|
|
+ `<button class="btn btn-primary" type="button" data-action="form-save-arrange">${arrangeLabel}</button>`
|
|
|
+ )
|
|
|
+ }
|
|
|
+ if (showRearrange) {
|
|
|
+ fab.push(
|
|
|
+ `<button class="btn btn-primary" type="button" data-action="form-rearrange">调整运输</button>`
|
|
|
+ )
|
|
|
+ }
|
|
|
+ $('#form-fab').innerHTML = fab.join('')
|
|
|
+
|
|
|
+ if (isArrange) {
|
|
|
+ setTimeout(() => scrollToSection('sec-transport'), 60)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ function scrollToSection(id) {
|
|
|
+ const host = $('#form-scroll')
|
|
|
+ const el = document.getElementById(id)
|
|
|
+ if (!host || !el) return
|
|
|
+ host.scrollTo({ top: el.offsetTop - 8, behavior: 'smooth' })
|
|
|
+ $$('#anchor-bar .anchor').forEach((a) => a.classList.toggle('active', a.dataset.anchor === id))
|
|
|
+ }
|
|
|
+
|
|
|
+ function renderPrint() {
|
|
|
+ const p = currentPlan()
|
|
|
+ const kindLabel = {
|
|
|
+ inbound: '入库单',
|
|
|
+ delivery: '送(提)货单',
|
|
|
+ subpack: '分装单',
|
|
|
+ 'blank-inbound': '空白入库单',
|
|
|
+ 'blank-delivery': '空白送(提)货单',
|
|
|
+ 'blank-subpack': '空白分装单'
|
|
|
+ }[state.printKind] || '单据'
|
|
|
+ const needCustomer = state.printKind === 'delivery' || state.printKind === 'blank-delivery'
|
|
|
+ const block = $('#print-customer-block')
|
|
|
+ if (block) block.style.display = needCustomer ? 'block' : 'none'
|
|
|
+ if (!state.printCustomer && (p.deliveries || []).length) {
|
|
|
+ state.printCustomer = p.deliveries[0].customer
|
|
|
+ }
|
|
|
+ const custLabel = $('#print-customer-label')
|
|
|
+ if (custLabel) {
|
|
|
+ custLabel.textContent = state.printCustomer || '请选择送货客户'
|
|
|
+ if (!state.printCustomer) custLabel.innerHTML = '<span class="ph">请选择送货客户</span>'
|
|
|
+ }
|
|
|
+ const custLine = needCustomer
|
|
|
+ ? `<div class="line"><span>送货客户</span><span>${state.printCustomer || '未选择'}</span></div>`
|
|
|
+ : ''
|
|
|
+ $('#print-preview').innerHTML = `
|
|
|
+ <h2>${kindLabel}</h2>
|
|
|
+ <div class="line"><span>计划编号</span><span>${p.no}</span></div>
|
|
|
+ <div class="line"><span>单据类型</span><span>${p.type}</span></div>
|
|
|
+ <div class="line"><span>计划日期</span><span>${p.date}</span></div>
|
|
|
+ ${custLine}
|
|
|
+ <div class="line"><span>供方 → 需方</span><span>${p.supplier || '—'} → ${p.buyer || '—'}</span></div>
|
|
|
+ <div class="line"><span>车牌</span><span>${p.plate}</span></div>
|
|
|
+ <div class="line"><span>人员</span><span>${p.people}</span></div>
|
|
|
+ <div class="line"><span>产品摘要</span><span>${p.product}</span></div>
|
|
|
+ <p style="margin-top:16px;color:#888;text-align:center">—— 预览示意 ——</p>
|
|
|
+ `
|
|
|
+ }
|
|
|
+
|
|
|
+ function fillOptions(containerId, items, onPickAttr) {
|
|
|
+ const el = document.getElementById(containerId)
|
|
|
+ if (!el) return
|
|
|
+ if (!items.length) {
|
|
|
+ el.innerHTML = '<div class="sheet-empty">无匹配结果</div>'
|
|
|
+ return
|
|
|
+ }
|
|
|
+ el.innerHTML = items.map((it) => `
|
|
|
+ <div class="option-row" data-action="${onPickAttr}" data-label="${it.t}" data-desc="${it.d || ''}">
|
|
|
+ <div><div class="t">${it.t}</div><div class="d">${it.d || ''}</div></div>
|
|
|
+ <span class="chev">选用</span>
|
|
|
+ </div>
|
|
|
+ `).join('')
|
|
|
+ }
|
|
|
+
|
|
|
+ function fuzzyMatch(item, keyword) {
|
|
|
+ const kw = (keyword || '').trim().toLowerCase()
|
|
|
+ if (!kw) return true
|
|
|
+ const blob = `${item.t || ''} ${item.d || ''}`.toLowerCase()
|
|
|
+ return kw.split(/\s+/).every((part) => blob.includes(part))
|
|
|
+ }
|
|
|
+
|
|
|
+ function renderPickerOptions() {
|
|
|
+ const { containerId, pickAction, items, keyword } = state.picker
|
|
|
+ if (!containerId) return
|
|
|
+ const filtered = (items || []).filter((it) => fuzzyMatch(it, keyword))
|
|
|
+ fillOptions(containerId, filtered, pickAction)
|
|
|
+ }
|
|
|
+
|
|
|
+ function openPickerSheet(sheetId, containerId, items, pickAction) {
|
|
|
+ state.picker = {
|
|
|
+ sheetId,
|
|
|
+ containerId,
|
|
|
+ pickAction,
|
|
|
+ items: items || [],
|
|
|
+ keyword: ''
|
|
|
+ }
|
|
|
+ const input = $(`#${sheetId} .sheet-search-input`)
|
|
|
+ if (input) input.value = ''
|
|
|
+ renderPickerOptions()
|
|
|
+ openSheet(sheetId)
|
|
|
+ if (input) setTimeout(() => input.focus(), 220)
|
|
|
+ }
|
|
|
+
|
|
|
+ function openConfirm(kind) {
|
|
|
+ state.confirmKind = kind
|
|
|
+ if (kind === 'wait') {
|
|
|
+ $('#confirm-title').textContent = '等客户通知'
|
|
|
+ $('#confirm-msg').textContent = '确认将该计划标记为等客户通知?'
|
|
|
+ $('#confirm-ok').className = 'btn btn-primary'
|
|
|
+ $('#confirm-ok').textContent = '确认'
|
|
|
+ } else if (kind === 'cancel-wait') {
|
|
|
+ $('#confirm-title').textContent = '取消等通知'
|
|
|
+ $('#confirm-msg').textContent = '确认取消等通知,恢复为待安排?'
|
|
|
+ $('#confirm-ok').className = 'btn btn-primary'
|
|
|
+ $('#confirm-ok').textContent = '确认'
|
|
|
+ } else if (kind === 'void') {
|
|
|
+ $('#confirm-title').textContent = '作废运输计划'
|
|
|
+ $('#confirm-msg').textContent = '确认作废该运输计划吗?作废后将无法恢复。'
|
|
|
+ $('#confirm-ok').className = 'btn btn-danger'
|
|
|
+ $('#confirm-ok').textContent = '确认作废'
|
|
|
+ }
|
|
|
+ openSheet('sheet-confirm')
|
|
|
+ }
|
|
|
+
|
|
|
+ function addStop(side) {
|
|
|
+ if (side !== 'delivery') {
|
|
|
+ toast('装货信息与 Web 一致为单段,请编辑上方装货区')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ state.stopCount.delivery += 1
|
|
|
+ const n = state.stopCount.delivery
|
|
|
+ const host = $('#delivery-stops')
|
|
|
+ const idC = `del-c${n}`
|
|
|
+ const idCt = `del-ct${n}`
|
|
|
+ const idP = `del-p${n}`
|
|
|
+ const wrap = document.createElement('div')
|
|
|
+ wrap.className = 'stop-card'
|
|
|
+ wrap.dataset.side = 'delivery'
|
|
|
+ wrap.innerHTML = `
|
|
|
+ <div class="stop-head">
|
|
|
+ <b>送货段 ${n}</b>
|
|
|
+ <span>
|
|
|
+ <button class="link-btn" type="button" data-action="toast" data-msg="已上移(模拟)">上移</button>
|
|
|
+ <button class="link-btn" type="button" data-action="toast" data-msg="已下移(模拟)">下移</button>
|
|
|
+ <button class="link-btn danger" type="button" data-action="remove-stop">删除</button>
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ <div class="field">
|
|
|
+ <label>送货客户 <span style="color:var(--danger)">*</span></label>
|
|
|
+ <button class="picker" type="button" data-sheet="customer" data-target="${idC}">
|
|
|
+ <span id="${idC}"><span class="ph">请选择客户</span></span>
|
|
|
+ <span class="chev">选择</span>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ <div class="field">
|
|
|
+ <label>送货合同</label>
|
|
|
+ <button class="picker" type="button" data-sheet="contract" data-target="${idCt}">
|
|
|
+ <span id="${idCt}"><span class="ph">请选择合同</span></span>
|
|
|
+ <span class="chev">选择</span>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ <div class="field-row">
|
|
|
+ <div class="field">
|
|
|
+ <label>送货时间 <span style="color:var(--danger)">*</span></label>
|
|
|
+ <input class="control" type="date" value="2026-09-14" />
|
|
|
+ </div>
|
|
|
+ <div class="field">
|
|
|
+ <label>联系方式</label>
|
|
|
+ <input class="control" placeholder="联系方式" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="field">
|
|
|
+ <label>送货地址</label>
|
|
|
+ <div class="link-row">
|
|
|
+ <input class="control" id="del-addr-${n}" placeholder="送货地址" />
|
|
|
+ <button class="btn" type="button" data-sheet="address" data-target="del-addr-${n}">地址库</button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="field">
|
|
|
+ <label>注意事项</label>
|
|
|
+ <input class="control" placeholder="送货注意事项" />
|
|
|
+ </div>
|
|
|
+ <div class="field">
|
|
|
+ <label>地磅单</label>
|
|
|
+ <label class="check-row"><input type="checkbox" /> 生成地磅单(出库)</label>
|
|
|
+ </div>
|
|
|
+ <div class="stop-head" style="margin-top:8px">
|
|
|
+ <b>送货产品</b>
|
|
|
+ <span>
|
|
|
+ <button class="link-btn" type="button" data-action="toast" data-msg="已从装货产品同步">从装货同步</button>
|
|
|
+ <button class="link-btn" type="button" data-sheet="product" data-target="${idP}">+ 选择产品</button>
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ <div class="product-card">
|
|
|
+ <div class="product-card__title" id="${idP}"><span class="ph">请选择产品</span></div>
|
|
|
+ <div class="field-row">
|
|
|
+ <div class="field">
|
|
|
+ <label>计划数量</label>
|
|
|
+ <input class="control" type="number" value="" placeholder="0" />
|
|
|
+ </div>
|
|
|
+ <div class="field">
|
|
|
+ <label>单位</label>
|
|
|
+ <select class="control"><option>吨</option></select>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="field">
|
|
|
+ <label>包装规格</label>
|
|
|
+ <select class="control"><option>吨袋</option><option>槽罐</option></select>
|
|
|
+ </div>
|
|
|
+ <div class="field">
|
|
|
+ <label>送货方式</label>
|
|
|
+ <select class="control"><option>送货上门</option><option>客户自提</option></select>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ `
|
|
|
+ host.appendChild(wrap)
|
|
|
+ toast(`已添加送货段 ${n}`)
|
|
|
+ }
|
|
|
+
|
|
|
+ function refreshVehicleSummary() {
|
|
|
+ const plate = ($('#f-plate')?.textContent || '').trim()
|
|
|
+ const driver = ($('#f-driver')?.textContent || '').trim()
|
|
|
+ const escort = ($('#f-escort')?.textContent || '').trim()
|
|
|
+ const carrier = ($('#f-carrier')?.textContent || '').trim()
|
|
|
+ const box = $('#f-vehicle-summary')
|
|
|
+ if (!box) return
|
|
|
+ const has = plate && !plate.includes('请选择')
|
|
|
+ box.style.display = has ? 'block' : 'none'
|
|
|
+ if (!has) return
|
|
|
+ $('#f-vs-plate').textContent = plate
|
|
|
+ $('#f-vs-people').textContent =
|
|
|
+ `司机 ${driver.includes('请选择') ? '—' : driver} · 押运 ${escort.includes('请选择') ? '—' : escort}`
|
|
|
+ $('#f-vs-carrier').textContent =
|
|
|
+ `承运商 ${carrier.includes('请选择') ? '—' : carrier}`
|
|
|
+ }
|
|
|
+
|
|
|
+ function addLoadProduct(name) {
|
|
|
+ const host = $('#load-products')
|
|
|
+ if (!host) return
|
|
|
+ const card = document.createElement('div')
|
|
|
+ card.className = 'product-card'
|
|
|
+ card.innerHTML = `
|
|
|
+ <div class="product-card__title">${name || '新产品'}</div>
|
|
|
+ <div class="field-row">
|
|
|
+ <div class="field">
|
|
|
+ <label>型号规格</label>
|
|
|
+ <input class="control" placeholder="型号规格" />
|
|
|
+ </div>
|
|
|
+ <div class="field">
|
|
|
+ <label>品牌</label>
|
|
|
+ <input class="control" placeholder="品牌" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="field-row">
|
|
|
+ <div class="field">
|
|
|
+ <label>别名</label>
|
|
|
+ <input class="control" placeholder="别名" />
|
|
|
+ </div>
|
|
|
+ <div class="field">
|
|
|
+ <label>可安排量</label>
|
|
|
+ <input class="control" value="0" readonly />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="field-row">
|
|
|
+ <div class="field">
|
|
|
+ <label>计划数量 <span style="color:var(--danger)">*</span></label>
|
|
|
+ <input class="control" type="number" value="0" />
|
|
|
+ </div>
|
|
|
+ <div class="field">
|
|
|
+ <label>单位 <span style="color:var(--danger)">*</span></label>
|
|
|
+ <select class="control"><option>吨</option><option>千克</option></select>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="field">
|
|
|
+ <label>包装规格 <span style="color:var(--danger)">*</span></label>
|
|
|
+ <select class="control"><option>吨袋</option><option>槽罐</option><option>桶装</option></select>
|
|
|
+ </div>
|
|
|
+ <button class="link-btn danger" type="button" data-action="remove-product">删除</button>
|
|
|
+ `
|
|
|
+ host.appendChild(card)
|
|
|
+ }
|
|
|
+
|
|
|
+ function openVehicleView() {
|
|
|
+ const p = currentPlan()
|
|
|
+ const vd = p.vehicleDetail || {
|
|
|
+ plate: p.plate, trailer: '—', type: '—', loadTon: '—', carrier: '—',
|
|
|
+ driver: (p.people || '—').split('/')[0]?.trim() || '—',
|
|
|
+ driverPhone: '—', escort: (p.people || '—').split('/')[1]?.trim() || '—', escortPhone: '—'
|
|
|
+ }
|
|
|
+ $('#vehicle-view-body').innerHTML = `
|
|
|
+ <div class="kv"><span>主车号</span><span>${vd.plate || '—'}</span></div>
|
|
|
+ <div class="kv"><span>挂车号</span><span>${vd.trailer || '—'}</span></div>
|
|
|
+ <div class="kv"><span>车辆类型</span><span>${vd.type || '—'}</span></div>
|
|
|
+ <div class="kv"><span>核载(吨)</span><span>${vd.loadTon ?? '—'}</span></div>
|
|
|
+ <div class="kv"><span>承运商</span><span>${vd.carrier || '—'}</span></div>
|
|
|
+ <div class="kv"><span>司机</span><span>${vd.driver || '—'} · ${vd.driverPhone || '—'}</span></div>
|
|
|
+ <div class="kv"><span>押运</span><span>${vd.escort || '—'} · ${vd.escortPhone || '—'}</span></div>
|
|
|
+ `
|
|
|
+ openSheet('sheet-vehicle-view')
|
|
|
+ }
|
|
|
+
|
|
|
+ function openListMore(planId) {
|
|
|
+ state.listMorePlanId = planId
|
|
|
+ const p = plans.find((x) => x.id === planId) || currentPlan()
|
|
|
+ const host = $('#list-more-options')
|
|
|
+ const items = []
|
|
|
+ items.push(`<button class="more-action" type="button" data-action="open-detail" data-id="${p.id}">查看详情</button>`)
|
|
|
+ items.push(`<button class="more-action" type="button" data-action="open-form" data-mode="copy" data-id="${p.id}">复制</button>`)
|
|
|
+ if (p.canWait) items.push(`<button class="more-action" type="button" data-action="confirm" data-kind="wait">等客户通知</button>`)
|
|
|
+ if (p.canCancelWait) items.push(`<button class="more-action" type="button" data-action="confirm" data-kind="cancel-wait">取消等通知</button>`)
|
|
|
+ if (p.canVoid) items.push(`<button class="more-action danger" type="button" data-action="confirm" data-kind="void">作废</button>`)
|
|
|
+ host.innerHTML = items.join('')
|
|
|
+ openSheet('sheet-list-more')
|
|
|
+ }
|
|
|
+
|
|
|
+ // events
|
|
|
+ $('#phone').addEventListener('click', (e) => {
|
|
|
+ const t = e.target.closest('[data-nav],[data-action],[data-sheet],[data-kind],[data-anchor]')
|
|
|
+ if (!t) return
|
|
|
+
|
|
|
+ if (t.dataset.nav) {
|
|
|
+ e.preventDefault()
|
|
|
+ closeSpeed()
|
|
|
+ closeFilter()
|
|
|
+ if (t.dataset.nav === 'form') {
|
|
|
+ state.formMode = t.dataset.mode || 'new'
|
|
|
+ showScreen('form')
|
|
|
+ } else {
|
|
|
+ showScreen(t.dataset.nav)
|
|
|
+ }
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ if (t.dataset.kind && t.closest('#print-kinds')) {
|
|
|
+ $$('#print-kinds .chip').forEach((x) => x.classList.remove('active'))
|
|
|
+ t.classList.add('active')
|
|
|
+ state.printKind = t.dataset.kind
|
|
|
+ renderPrint()
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ if (t.dataset.anchor) {
|
|
|
+ scrollToSection(t.dataset.anchor)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ if (t.dataset.sheet) {
|
|
|
+ e.preventDefault()
|
|
|
+ state.pickerTarget = t.dataset.target || ''
|
|
|
+ if (t.dataset.sheet === 'vehicle') {
|
|
|
+ openPickerSheet('sheet-vehicle', 'vehicle-options', vehicles, 'pick-vehicle')
|
|
|
+ } else if (t.dataset.sheet === 'personnel') {
|
|
|
+ state.personnelRole = t.dataset.role || '司机'
|
|
|
+ $('#personnel-title').textContent = '选择' + state.personnelRole
|
|
|
+ openPickerSheet(
|
|
|
+ 'sheet-personnel',
|
|
|
+ 'personnel-options',
|
|
|
+ state.personnelRole === '押运' ? escorts : drivers,
|
|
|
+ 'pick-personnel'
|
|
|
+ )
|
|
|
+ } else if (t.dataset.sheet === 'customer') {
|
|
|
+ openPickerSheet('sheet-customer', 'customer-options', customers, 'pick-customer')
|
|
|
+ } else if (t.dataset.sheet === 'product') {
|
|
|
+ openPickerSheet('sheet-product', 'product-options', products, 'pick-product')
|
|
|
+ } else if (t.dataset.sheet === 'carrier') {
|
|
|
+ openPickerSheet('sheet-carrier', 'carrier-options', carriers, 'pick-carrier')
|
|
|
+ } else if (t.dataset.sheet === 'contract') {
|
|
|
+ openPickerSheet('sheet-contract', 'contract-options', contracts, 'pick-contract')
|
|
|
+ } else if (t.dataset.sheet === 'address') {
|
|
|
+ openPickerSheet('sheet-address', 'address-options', addresses, 'pick-address')
|
|
|
+ }
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ const action = t.dataset.action
|
|
|
+ if (!action) return
|
|
|
+ e.preventDefault()
|
|
|
+
|
|
|
+ if (action === 'toast') toast(t.dataset.msg || '已点击')
|
|
|
+ if (action === 'back') goBack()
|
|
|
+ if (action === 'close-sheet') closeSheet()
|
|
|
+ if (action === 'close-filter') closeFilter()
|
|
|
+ if (action === 'close-speed') closeSpeed()
|
|
|
+ if (action === 'open-detail-more') {
|
|
|
+ state.listMorePlanId = ''
|
|
|
+ openSheet('sheet-detail-more')
|
|
|
+ }
|
|
|
+ if (action === 'toggle-speed') {
|
|
|
+ const dial = $('#speed-dial')
|
|
|
+ const open = dial.classList.toggle('open')
|
|
|
+ $('#speed-mask').classList.toggle('open', open)
|
|
|
+ if (open) closeFilter()
|
|
|
+ }
|
|
|
+ if (action === 'toggle-filter') {
|
|
|
+ openFilter(t.dataset.filter)
|
|
|
+ }
|
|
|
+ if (action === 'pick-date') {
|
|
|
+ state.date = t.dataset.date
|
|
|
+ $$('#panel-date .filter-opt').forEach((o) => o.classList.toggle('active', o === t))
|
|
|
+ const customRow = $('#custom-date-row')
|
|
|
+ if (t.dataset.date === 'custom') {
|
|
|
+ customRow?.classList.remove('hidden')
|
|
|
+ $('#label-date').textContent = state.customDateLabel || '自定义'
|
|
|
+ return
|
|
|
+ }
|
|
|
+ customRow?.classList.add('hidden')
|
|
|
+ $('#label-date').textContent = t.dataset.label
|
|
|
+ closeFilter()
|
|
|
+ state.listPage = 1
|
|
|
+ renderList()
|
|
|
+ toast('已切换日期:' + t.dataset.label)
|
|
|
+ }
|
|
|
+ if (action === 'apply-custom-date') {
|
|
|
+ const from = $('#custom-date-from')?.value || ''
|
|
|
+ const to = $('#custom-date-to')?.value || ''
|
|
|
+ state.date = 'custom'
|
|
|
+ state.customDateLabel = `${from} ~ ${to}`
|
|
|
+ $('#label-date').textContent = state.customDateLabel
|
|
|
+ closeFilter()
|
|
|
+ state.listPage = 1
|
|
|
+ renderList()
|
|
|
+ toast('已应用自定义日期')
|
|
|
+ }
|
|
|
+ if (action === 'load-more') {
|
|
|
+ state.listPage += 1
|
|
|
+ renderList()
|
|
|
+ toast('已加载更多(模拟)')
|
|
|
+ }
|
|
|
+ if (action === 'pick-delivery-tab') {
|
|
|
+ state.deliveryTabIndex = Number(t.dataset.idx || 0)
|
|
|
+ renderDeliveryTab(currentPlan(), state.deliveryTabIndex)
|
|
|
+ }
|
|
|
+ if (action === 'open-vehicle-view') openVehicleView()
|
|
|
+ if (action === 'open-list-more') openListMore(t.dataset.id)
|
|
|
+ if (action === 'pick-print-customer-open') {
|
|
|
+ const p = currentPlan()
|
|
|
+ const items = (p.deliveries || []).map((d, i) => ({ id: 'pc' + i, t: d.customer, d: d.addr || '' }))
|
|
|
+ if (!items.length) {
|
|
|
+ toast('当前计划无送货客户')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ openPickerSheet('sheet-customer', 'customer-options', items, 'pick-print-customer')
|
|
|
+ }
|
|
|
+ if (action === 'pick-tab') {
|
|
|
+ state.tab = t.dataset.tab
|
|
|
+ $('#label-tab').textContent = t.dataset.label
|
|
|
+ $$('#panel-tab .filter-opt').forEach((o) => o.classList.toggle('active', o === t))
|
|
|
+ closeFilter()
|
|
|
+ state.listPage = 1
|
|
|
+ renderList()
|
|
|
+ }
|
|
|
+ if (action === 'open-detail') {
|
|
|
+ state.currentPlanId = t.dataset.id
|
|
|
+ state.deliveryTabIndex = 0
|
|
|
+ closeSheet()
|
|
|
+ showScreen('detail')
|
|
|
+ }
|
|
|
+ if (action === 'open-form') {
|
|
|
+ if (t.dataset.id) state.currentPlanId = t.dataset.id
|
|
|
+ state.formMode = t.dataset.mode || 'edit'
|
|
|
+ showScreen('form')
|
|
|
+ }
|
|
|
+ if (action === 'confirm') {
|
|
|
+ if (state.listMorePlanId) state.currentPlanId = state.listMorePlanId
|
|
|
+ openConfirm(t.dataset.kind)
|
|
|
+ }
|
|
|
+ if (action === 'add-stop') addStop(t.dataset.side)
|
|
|
+ if (action === 'add-product') {
|
|
|
+ openPickerSheet('sheet-product', 'product-options', products, 'pick-load-product')
|
|
|
+ }
|
|
|
+ if (action === 'remove-product') {
|
|
|
+ const card = t.closest('.product-card')
|
|
|
+ if (card) {
|
|
|
+ card.remove()
|
|
|
+ toast('已删除产品')
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (action === 'remove-stop') {
|
|
|
+ const card = t.closest('.stop-card')
|
|
|
+ if (card) {
|
|
|
+ card.remove()
|
|
|
+ toast('已删除该段')
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (action === 'form-draft') {
|
|
|
+ toast('已暂存(模拟)')
|
|
|
+ showScreen('list', { push: false })
|
|
|
+ state.history = []
|
|
|
+ }
|
|
|
+ if (action === 'form-save') {
|
|
|
+ const label = state.formMode === 'new' || state.formMode === 'copy' ? '已创建(模拟)' : '已保存(模拟)'
|
|
|
+ toast(label)
|
|
|
+ showScreen('detail', { push: false })
|
|
|
+ }
|
|
|
+ if (action === 'form-save-arrange') {
|
|
|
+ const label =
|
|
|
+ state.formMode === 'new' || state.formMode === 'copy'
|
|
|
+ ? '已创建并安排运输(模拟)'
|
|
|
+ : '已保存并安排(模拟)'
|
|
|
+ toast(label)
|
|
|
+ const p = currentPlan()
|
|
|
+ p.status = '已安排'
|
|
|
+ p.tag = 'tag-ok'
|
|
|
+ p.followStatus = 'arranged'
|
|
|
+ p.transportStatus = 'arranged'
|
|
|
+ p.canArrange = true
|
|
|
+ p.canWait = false
|
|
|
+ p.canCancelWait = false
|
|
|
+ p.arrangeLabel = '调整运输'
|
|
|
+ if ($('#f-plate').textContent.trim()) p.plate = $('#f-plate').textContent.trim()
|
|
|
+ showScreen('detail', { push: false })
|
|
|
+ }
|
|
|
+ if (action === 'form-rearrange') {
|
|
|
+ toast('运输调整成功(模拟)')
|
|
|
+ showScreen('detail', { push: false })
|
|
|
+ }
|
|
|
+ if (action === 'pick-vehicle') {
|
|
|
+ $('#f-plate').textContent = t.dataset.label
|
|
|
+ closeSheet()
|
|
|
+ refreshVehicleSummary()
|
|
|
+ toast('已选择车辆')
|
|
|
+ }
|
|
|
+ if (action === 'pick-personnel') {
|
|
|
+ const target = state.personnelRole === '押运' ? '#f-escort' : '#f-driver'
|
|
|
+ $(target).textContent = t.dataset.label
|
|
|
+ closeSheet()
|
|
|
+ refreshVehicleSummary()
|
|
|
+ toast('已选择' + state.personnelRole)
|
|
|
+ }
|
|
|
+ if (action === 'pick-customer') {
|
|
|
+ if (state.pickerTarget) {
|
|
|
+ const el = document.getElementById(state.pickerTarget)
|
|
|
+ if (el) el.textContent = t.dataset.label
|
|
|
+ }
|
|
|
+ closeSheet()
|
|
|
+ toast('已选择客户')
|
|
|
+ }
|
|
|
+ if (action === 'pick-product') {
|
|
|
+ if (state.pickerTarget) {
|
|
|
+ const el = document.getElementById(state.pickerTarget)
|
|
|
+ if (el) el.textContent = t.dataset.label
|
|
|
+ }
|
|
|
+ closeSheet()
|
|
|
+ toast('已选择产品')
|
|
|
+ }
|
|
|
+ if (action === 'pick-load-product') {
|
|
|
+ const name = (t.dataset.label || '').split('·')[0].trim() || t.dataset.label
|
|
|
+ addLoadProduct(name)
|
|
|
+ closeSheet()
|
|
|
+ toast('已添加装货产品')
|
|
|
+ }
|
|
|
+ if (action === 'pick-contract') {
|
|
|
+ if (state.pickerTarget) {
|
|
|
+ const el = document.getElementById(state.pickerTarget)
|
|
|
+ if (el) el.textContent = t.dataset.label
|
|
|
+ }
|
|
|
+ closeSheet()
|
|
|
+ toast('已选择合同')
|
|
|
+ }
|
|
|
+ if (action === 'pick-address') {
|
|
|
+ if (state.pickerTarget) {
|
|
|
+ const el = document.getElementById(state.pickerTarget)
|
|
|
+ if (el) {
|
|
|
+ if (el.tagName === 'INPUT') el.value = t.dataset.label
|
|
|
+ else el.textContent = t.dataset.label
|
|
|
+ }
|
|
|
+ }
|
|
|
+ closeSheet()
|
|
|
+ toast('已从地址库填入')
|
|
|
+ }
|
|
|
+ if (action === 'pick-print-customer') {
|
|
|
+ state.printCustomer = t.dataset.label
|
|
|
+ closeSheet()
|
|
|
+ renderPrint()
|
|
|
+ toast('已选择送货客户')
|
|
|
+ }
|
|
|
+ if (action === 'pick-carrier') {
|
|
|
+ const target = state.pickerTarget || 'paste-carrier'
|
|
|
+ const el = document.getElementById(target) || $('#paste-carrier')
|
|
|
+ if (el) el.textContent = t.dataset.label
|
|
|
+ closeSheet()
|
|
|
+ refreshVehicleSummary()
|
|
|
+ toast('已选择承运商')
|
|
|
+ }
|
|
|
+ if (action === 'paste-parse') {
|
|
|
+ $('#paste-plate').value = '皖HD1234'
|
|
|
+ $('#paste-trailer').value = '皖HG88挂'
|
|
|
+ $('#paste-load').value = '34'
|
|
|
+ $('#paste-driver-name').value = '张伟'
|
|
|
+ $('#paste-driver-phone').value = '13800001111'
|
|
|
+ $('#paste-driver-id').value = '340102199001011234'
|
|
|
+ $('#paste-escort-name').value = '李强'
|
|
|
+ $('#paste-escort-phone').value = '13900002222'
|
|
|
+ $('#paste-escort-id').value = '340102199002021234'
|
|
|
+ $('#paste-carrier').innerHTML = '<span class="ph">请选择承运商</span>'
|
|
|
+ $('#paste-result-block').classList.remove('hidden')
|
|
|
+ $('#paste-submit-btn').disabled = false
|
|
|
+ state.pasteParsed = true
|
|
|
+ const warn = $('#paste-warn')
|
|
|
+ warn.style.display = 'block'
|
|
|
+ warn.textContent = '演示:识别完成,请核对并补全承运商;若有冲突请选择处理方式。'
|
|
|
+ toast('识别完成')
|
|
|
+ setTimeout(() => {
|
|
|
+ $('#paste-result-block').scrollIntoView({ behavior: 'smooth', block: 'start' })
|
|
|
+ }, 50)
|
|
|
+ }
|
|
|
+ if (action === 'pick-conflict') {
|
|
|
+ state.pasteConflict = t.dataset.val
|
|
|
+ $$('.paste-conflict-opt').forEach((o) => o.classList.toggle('active', o === t))
|
|
|
+ toast('冲突处理:' + ({ reuse: '复用', overwrite: '覆盖更新', cancel: '取消' }[t.dataset.val] || t.dataset.val))
|
|
|
+ }
|
|
|
+ if (action === 'paste-submit') {
|
|
|
+ if (!state.pasteParsed) {
|
|
|
+ toast('请先识别')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if (!$('#paste-trailer').value.trim()) {
|
|
|
+ toast('挂车号必填')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if (state.pasteConflict === 'cancel') {
|
|
|
+ toast('已按冲突策略取消本条')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ toast('已生成三方人车(模拟)')
|
|
|
+ showScreen('list', { push: false })
|
|
|
+ state.history = []
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ $('#sheet-mask').addEventListener('click', closeSheet)
|
|
|
+ $('#confirm-ok').addEventListener('click', () => {
|
|
|
+ const p = currentPlan()
|
|
|
+ if (state.confirmKind === 'wait') {
|
|
|
+ p.status = '等通知'
|
|
|
+ p.tag = 'tag-wait'
|
|
|
+ p.canWait = false
|
|
|
+ p.canCancelWait = true
|
|
|
+ toast('已标记为等客户通知')
|
|
|
+ } else if (state.confirmKind === 'cancel-wait') {
|
|
|
+ p.status = '待安排'
|
|
|
+ p.tag = 'tag-warn'
|
|
|
+ p.canWait = true
|
|
|
+ p.canCancelWait = false
|
|
|
+ toast('已取消等通知,恢复为待安排')
|
|
|
+ } else if (state.confirmKind === 'void') {
|
|
|
+ p.status = '已作废'
|
|
|
+ p.tag = 'tag-mute'
|
|
|
+ p.canArrange = false
|
|
|
+ p.canEdit = false
|
|
|
+ p.canVoid = false
|
|
|
+ p.canWait = false
|
|
|
+ p.canCancelWait = false
|
|
|
+ toast('作废成功')
|
|
|
+ }
|
|
|
+ closeSheet()
|
|
|
+ state.listMorePlanId = ''
|
|
|
+ if (state.screen === 'detail') renderDetail()
|
|
|
+ else renderList()
|
|
|
+ })
|
|
|
+
|
|
|
+ $('#list-keyword').addEventListener('input', () => {
|
|
|
+ state.listPage = 1
|
|
|
+ renderList()
|
|
|
+ })
|
|
|
+
|
|
|
+ $$('.sheet-search-input').forEach((input) => {
|
|
|
+ input.addEventListener('input', () => {
|
|
|
+ const sheet = input.closest('.sheet')
|
|
|
+ if (!sheet || sheet.id !== state.picker.sheetId) return
|
|
|
+ state.picker.keyword = input.value || ''
|
|
|
+ renderPickerOptions()
|
|
|
+ })
|
|
|
+ input.addEventListener('click', (e) => e.stopPropagation())
|
|
|
+ })
|
|
|
+
|
|
|
+ renderList()
|
|
|
+ </script>
|
|
|
+</body>
|
|
|
+</html>
|