This commit is contained in:
zlwu committed 2026-08-03 21:20:00 +08:00
1 parent e233269abf
commit 732c1b1dc9
17 files changed
+9607 -88132

No files matched your search

+1 -1
View File
@@ -1 +1 @@
19114
26701
@@ -0,0 +1,602 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Fresh Mint · 回传卡片 KV 重设计 Demo</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
<style>
:root {
--primary: #10b981;
--primary-dark: #059669;
--primary-bg: #ecfdf5;
--text: #1e293b;
--text-muted: #64748b;
--border: #d9e8e2;
--border-soft: #e2e8f0;
--surface: #fff;
--table-head: #f8fafc;
--danger: #dc2626;
--warn: #d97706;
--radius: 10px;
}
* { box-sizing: border-box; }
body {
margin: 0;
font-family: Inter, -apple-system, sans-serif;
font-size: 14px;
line-height: 1.5;
color: var(--text);
background: #eef2f6;
}
.page { max-width: 1200px; margin: 0 auto; padding: 32px 20px 72px; }
.head { margin-bottom: 28px; }
.head h1 { margin: 0 0 8px; font-size: 24px; font-weight: 700; }
.head h1 span { color: var(--primary-dark); }
.head p { margin: 0; color: var(--text-muted); max-width: 760px; }
.block { margin-bottom: 36px; }
.block > h2 {
margin: 0 0 6px;
font-size: 16px;
font-weight: 700;
}
.block > .desc {
margin: 0 0 14px;
font-size: 13px;
color: var(--text-muted);
}
.pick {
display: inline-block;
margin-left: 8px;
padding: 1px 8px;
border-radius: 999px;
font-size: 11px;
font-weight: 600;
vertical-align: middle;
}
.pick-1 { background: #ecfdf5; color: var(--primary-dark); }
.pick-2 { background: #eff6ff; color: #1d4ed8; }
.pick-3 { background: #fff7ed; color: #c2410c; }
.row3 {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 14px;
}
@media (max-width: 980px) {
.row3 { grid-template-columns: 1fr; }
}
.panel {
background: var(--surface);
border: 1px solid var(--border-soft);
border-radius: var(--radius);
padding: 14px;
min-height: 300px;
display: flex;
flex-direction: column;
}
.panel-hd {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
margin-bottom: 12px;
}
.panel-hd h3 {
margin: 0;
padding-left: 10px;
border-left: 3px solid var(--primary);
color: var(--primary-dark);
font-size: 14px;
font-weight: 600;
line-height: 1.2;
}
.ops { display: flex; gap: 6px; }
.btn {
height: 28px;
padding: 0 10px;
border-radius: 6px;
border: 1px solid var(--border-soft);
background: #fff;
font-size: 12px;
font-weight: 500;
color: var(--text);
}
.btn-p { background: var(--primary); border-color: var(--primary); color: #fff; }
/* ========== 方案 1:单据表(系统原生 ViewKv 表格式) ========== */
.kv-table {
width: 100%;
border-collapse: collapse;
table-layout: fixed;
border: 1px solid var(--border);
border-radius: 8px;
overflow: hidden;
background: #fff;
}
.kv-table th,
.kv-table td {
padding: 9px 12px;
border-bottom: 1px solid #eef2f6;
border-right: 1px solid #eef2f6;
vertical-align: top;
text-align: left;
line-height: 1.4;
}
.kv-table tr:last-child th,
.kv-table tr:last-child td { border-bottom: none; }
.kv-table th {
width: 78px;
background: var(--table-head);
color: var(--text-muted);
font-size: 12px;
font-weight: 500;
white-space: nowrap;
}
.kv-table td {
font-size: 13px;
font-weight: 600;
color: var(--text);
word-break: break-word;
}
.kv-table td.red { color: var(--danger); font-size: 15px; }
.kv-table .tag {
display: inline-block;
padding: 1px 8px;
border-radius: 4px;
background: #059669;
color: #fff;
font-size: 12px;
font-weight: 600;
}
.kv-table .thumbs {
display: flex;
gap: 6px;
flex-wrap: wrap;
}
.kv-table .thumb {
width: 40px;
height: 40px;
border-radius: 6px;
background: #ecfdf5;
border: 1px solid #a7f3d0;
display: grid;
place-items: center;
font-size: 11px;
color: var(--primary-dark);
font-weight: 600;
}
/* ========== 方案 2:一句摘要 + 次要元信息 ========== */
.summary-card { display: flex; flex-direction: column; gap: 12px; }
.summary-hero {
padding: 14px 14px 12px;
border-radius: 8px;
background: #0f172a;
color: #f8fafc;
}
.summary-hero .kicker {
font-size: 11px;
font-weight: 500;
color: #94a3b8;
margin-bottom: 6px;
}
.summary-hero .main {
font-size: 20px;
font-weight: 700;
letter-spacing: -0.02em;
line-height: 1.25;
}
.summary-hero .main strong { color: #fca5a5; font-weight: 700; }
.summary-hero .sub {
margin-top: 8px;
font-size: 12px;
color: #cbd5e1;
line-height: 1.45;
}
.chip-row { display: flex; flex-wrap: wrap; gap: 8px; }
.chip {
display: inline-flex;
align-items: center;
gap: 6px;
max-width: 100%;
padding: 6px 10px;
border-radius: 999px;
background: #f8fafc;
border: 1px solid var(--border-soft);
font-size: 12px;
color: var(--text);
}
.chip b { color: var(--text-muted); font-weight: 500; }
.chip .t { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 180px; }
.media-row {
display: flex;
align-items: center;
gap: 8px;
padding-top: 2px;
}
.media-row .lab { font-size: 12px; color: var(--text-muted); white-space: nowrap; }
.media-row .thumb {
width: 44px;
height: 44px;
border-radius: 8px;
object-fit: cover;
background: linear-gradient(135deg, #d1fae5, #a7f3d0);
border: 1px solid #a7f3d0;
}
/* ========== 方案 3:回执 / 小票式 ========== */
.ticket {
border: 1px solid var(--border-soft);
border-radius: 8px;
overflow: hidden;
background: #fff;
}
.ticket-band {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
padding: 10px 12px;
background: linear-gradient(90deg, #ecfdf5 0%, #f0fdf4 60%, #fff 100%);
border-bottom: 1px solid #d1fae5;
}
.ticket-band .who {
font-size: 12px;
font-weight: 600;
color: var(--primary-dark);
}
.ticket-band .when {
font-size: 12px;
color: var(--text-muted);
font-variant-numeric: tabular-nums;
white-space: nowrap;
}
.ticket-qty {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 0;
border-bottom: 1px dashed #e2e8f0;
}
.ticket-qty > div {
padding: 12px;
text-align: center;
}
.ticket-qty > div + div { border-left: 1px dashed #e2e8f0; }
.ticket-qty .lab {
font-size: 11px;
color: var(--text-muted);
margin-bottom: 4px;
}
.ticket-qty .val {
font-size: 22px;
font-weight: 700;
color: var(--danger);
font-variant-numeric: tabular-nums;
letter-spacing: -0.02em;
line-height: 1.1;
}
.ticket-qty .val.sm { font-size: 14px; line-height: 1.35; }
.ticket-lines { padding: 4px 0; }
.ticket-line {
display: grid;
grid-template-columns: 72px 1fr;
gap: 8px;
padding: 7px 12px;
font-size: 13px;
}
.ticket-line .lab { color: var(--text-muted); }
.ticket-line .val { font-weight: 600; color: var(--text); word-break: break-word; }
.ticket-foot {
padding: 10px 12px;
border-top: 1px dashed #e2e8f0;
background: #f8fafc;
display: flex;
align-items: center;
gap: 8px;
}
.ticket-foot .lab { font-size: 12px; color: var(--text-muted); }
.ticket-foot .thumb {
width: 36px;
height: 36px;
border-radius: 6px;
background: #ecfdf5;
border: 1px solid #a7f3d0;
}
.footer-note {
margin-top: 8px;
padding: 14px 16px;
background: #fff;
border: 1px solid var(--border-soft);
border-radius: 8px;
font-size: 13px;
color: var(--text-muted);
}
.footer-note strong { color: var(--text); }
</style>
</head>
<body>
<div class="page">
<header class="head">
<h1>回传卡片 · <span>重新设计</span></h1>
<p>
上一版三种「上下 KV / 指标卡 / 左右行」都不合适。本页换成三种完全不同的语言:
<b>单据表</b>、<b>摘要优先</b>、<b>回执小票</b>。请直接点名要哪一种(或混搭)。
</p>
</header>
<!-- 1 -->
<section class="block">
<h2>方案 1 · 单据表 <span class="pick pick-1">贴近现有 ViewKvTable</span></h2>
<p class="desc">放弃「漂浮的上下摆」,改成管理端熟悉的「左标签右值」表格。数量单元格加大、红色。信息密度高、和系统其它查看弹窗一致。</p>
<div class="row3">
<article class="panel">
<div class="panel-hd">
<h3>司机回传信息</h3>
<div class="ops"><button class="btn btn-p" type="button">编辑</button></div>
</div>
<table class="kv-table">
<tr>
<th>实际数量</th>
<td class="red">28.5 吨</td>
<th>品牌数量</th>
<td class="red">云化 18.5 / 华谊 10</td>
</tr>
<tr>
<th>回传时间</th>
<td>2026-07-31 14:22</td>
<th>回传备注</th>
<td>货已装完,现场确认无误</td>
</tr>
<tr>
<th>回传附件</th>
<td colspan="3">
<div class="thumbs"><span class="thumb">图1</span><span class="thumb">图2</span></div>
</td>
</tr>
</table>
</article>
<article class="panel">
<div class="panel-hd">
<h3>出库单回传</h3>
<div class="ops"><button class="btn btn-p" type="button">编辑</button></div>
</div>
<table class="kv-table">
<tr>
<th>单号</th>
<td>CK20260731001</td>
<th>状态</th>
<td><span class="tag">已完成</span></td>
</tr>
<tr>
<th>计划号</th>
<td>PL-202607-088</td>
<th>车牌</th>
<td>皖H·D1234</td>
</tr>
<tr>
<th>往来单位</th>
<td colspan="3">安庆某某化工有限公司</td>
</tr>
<tr>
<th>实际数量</th>
<td class="red">28.5 吨</td>
<th>品牌数量</th>
<td class="red">云化 18.5 / 华谊 10</td>
</tr>
<tr>
<th>现场备注</th>
<td>过磅完成,单据已签</td>
<th>现场附件</th>
<td><div class="thumbs"><span class="thumb">图1</span></div></td>
</tr>
</table>
</article>
<article class="panel">
<div class="panel-hd"><h3>地磅单信息(示意)</h3></div>
<table class="kv-table">
<tr>
<th>出入库单号</th>
<td>RK20260731008</td>
<th>业务类型</th>
<td><span class="tag">出库</span></td>
</tr>
<tr>
<th>毛重</th>
<td>42580 kg</td>
<th>皮重</th>
<td>14200 kg</td>
</tr>
<tr>
<th>净重</th>
<td class="red">28380 kg</td>
<th>车牌</th>
<td>皖H·D1234</td>
</tr>
</table>
</article>
</div>
</section>
<!-- 2 -->
<section class="block">
<h2>方案 2 · 摘要优先 <span class="pick pick-2">先看结论</span></h2>
<p class="desc">卡片顶部用一句「结论句」把数量讲清楚,其余降级成胶囊标签。适合一眼扫量,弱化字段罗列感。</p>
<div class="row3">
<article class="panel">
<div class="panel-hd">
<h3>司机回传信息</h3>
<div class="ops"><button class="btn btn-p" type="button">编辑</button></div>
</div>
<div class="summary-card">
<div class="summary-hero">
<div class="kicker">司机已回传</div>
<div class="main">实际 <strong>28.5</strong> 吨</div>
<div class="sub">品牌构成:云化 18.5 · 华谊 10</div>
</div>
<div class="chip-row">
<span class="chip"><b>时间</b><span class="t">2026-07-31 14:22</span></span>
<span class="chip"><b>备注</b><span class="t">货已装完,现场确认无误</span></span>
</div>
<div class="media-row">
<span class="lab">附件</span>
<span class="thumb"></span>
<span class="thumb"></span>
</div>
</div>
</article>
<article class="panel">
<div class="panel-hd">
<h3>出库单回传</h3>
<div class="ops"><button class="btn btn-p" type="button">编辑</button></div>
</div>
<div class="summary-card">
<div class="summary-hero">
<div class="kicker">出库单 · 已完成 · CK20260731001</div>
<div class="main">实际 <strong>28.5</strong> 吨</div>
<div class="sub">云化 18.5 · 华谊 10 | 皖H·D1234</div>
</div>
<div class="chip-row">
<span class="chip"><b>计划</b><span class="t">PL-202607-088</span></span>
<span class="chip"><b>往来</b><span class="t">安庆某某化工有限公司</span></span>
<span class="chip"><b>备注</b><span class="t">过磅完成,单据已签</span></span>
</div>
<div class="media-row">
<span class="lab">附件</span>
<span class="thumb"></span>
</div>
</div>
</article>
<article class="panel">
<div class="panel-hd"><h3>地磅单信息(示意)</h3></div>
<div class="summary-card">
<div class="summary-hero">
<div class="kicker">地磅 · 出库 · RK20260731008</div>
<div class="main">净重 <strong>28.38</strong> 吨</div>
<div class="sub">毛重 42.58 · 皮重 14.20 | 皖H·D1234</div>
</div>
<div class="chip-row">
<span class="chip"><b>往来</b><span class="t">安庆某某化工</span></span>
<span class="chip"><b>状态</b><span class="t">已过磅</span></span>
</div>
</div>
</article>
</div>
</section>
<!-- 3 -->
<section class="block">
<h2>方案 3 · 回执小票 <span class="pick pick-3">像一张回执</span></h2>
<p class="desc">顶部条带写时间/身份,中间居中放大数量,下方清单式字段。结构像「装车回执」,不是普通表单字段堆叠。</p>
<div class="row3">
<article class="panel">
<div class="panel-hd">
<h3>司机回传信息</h3>
<div class="ops"><button class="btn btn-p" type="button">编辑</button></div>
</div>
<div class="ticket">
<div class="ticket-band">
<span class="who">司机回传回执</span>
<span class="when">2026-07-31 14:22</span>
</div>
<div class="ticket-qty">
<div>
<div class="lab">实际数量(吨)</div>
<div class="val">28.5</div>
</div>
<div>
<div class="lab">品牌数量</div>
<div class="val sm">云化 18.5<br />华谊 10</div>
</div>
</div>
<div class="ticket-lines">
<div class="ticket-line"><span class="lab">回传备注</span><span class="val">货已装完,现场确认无误</span></div>
</div>
<div class="ticket-foot">
<span class="lab">附件</span>
<span class="thumb"></span>
<span class="thumb"></span>
</div>
</div>
</article>
<article class="panel">
<div class="panel-hd">
<h3>出库单回传</h3>
<div class="ops"><button class="btn btn-p" type="button">编辑</button></div>
</div>
<div class="ticket">
<div class="ticket-band">
<span class="who">出库单 CK20260731001 · 已完成</span>
<span class="when">PL-202607-088</span>
</div>
<div class="ticket-qty">
<div>
<div class="lab">实际数量(吨)</div>
<div class="val">28.5</div>
</div>
<div>
<div class="lab">品牌数量</div>
<div class="val sm">云化 18.5 / 华谊 10</div>
</div>
</div>
<div class="ticket-lines">
<div class="ticket-line"><span class="lab">车牌</span><span class="val">皖H·D1234</span></div>
<div class="ticket-line"><span class="lab">往来单位</span><span class="val">安庆某某化工有限公司</span></div>
<div class="ticket-line"><span class="lab">现场备注</span><span class="val">过磅完成,单据已签</span></div>
</div>
<div class="ticket-foot">
<span class="lab">附件</span>
<span class="thumb"></span>
</div>
</div>
</article>
<article class="panel">
<div class="panel-hd"><h3>地磅单信息(示意)</h3></div>
<div class="ticket">
<div class="ticket-band">
<span class="who">地磅回执 · 出库</span>
<span class="when">RK20260731008</span>
</div>
<div class="ticket-qty">
<div>
<div class="lab">净重(吨)</div>
<div class="val">28.38</div>
</div>
<div>
<div class="lab">毛重 / 皮重</div>
<div class="val sm">42.58 / 14.20</div>
</div>
</div>
<div class="ticket-lines">
<div class="ticket-line"><span class="lab">车牌</span><span class="val">皖H·D1234</span></div>
<div class="ticket-line"><span class="lab">往来单位</span><span class="val">安庆某某化工</span></div>
</div>
</div>
</article>
</div>
</section>
<div class="footer-note">
<strong>怎么选:</strong>
要和系统其它查看页统一 → 选 <strong>方案 1</strong>;
归档时最关心「装了多少」→ 选 <strong>方案 2</strong>;
想强调「这是一份回传凭证」→ 选 <strong>方案 3</strong>。
也可以说「1 的表格 + 2 的顶部结论」这类混搭。文件:
<code>docs/fresh-mint-pl-feedback-kv-polish-demo.html</code>
</div>
</div>
</body>
</html>
+1059 -1059
View File
File diff suppressed because it is too large. Load diff
+29 -37
View File
@@ -16,7 +16,7 @@
"14": "Community 14",
"15": "Community 15",
"16": "Community 16",
"17": "Community 17",
"17": "Ha",
"18": "Community 18",
"19": "Community 19",
"20": "Community 20",
@@ -25,22 +25,32 @@
"23": "Community 23",
"24": "Community 24",
"25": "Community 25",
"26": "Community 26",
"26": "theme.min.js",
"27": "Community 27",
"28": "Community 28",
"28": "useCommon",
"29": "Community 29",
"30": "loader.ts",
"31": "RenewDialog.vue",
"32": "index.vue",
"33": "Community 33",
"34": "main.ts",
"35": "Community 35",
"36": "VoidDialog.vue",
"37": "pl-batch-print.ts",
"38": "view-fields.ts",
"39": "Community 39",
"40": "Community 40",
"41": "colors.ts",
"42": "Community 42",
"43": "Community 43",
"44": "Community 44",
"45": "Community 45",
"46": "Community 46",
"47": "fetchCustomerDetail",
"48": "Community 48",
"49": "Community 49",
"50": "Community 50",
"51": "price-letter-template.ts",
"52": "Community 52",
"53": "Community 53",
"54": "Community 54",
@@ -48,8 +58,13 @@
"56": "Community 56",
"57": "Community 57",
"58": "Community 58",
"59": "fetchData",
"60": "index.vue",
"61": "Community 61",
"62": "highlight.ts",
"63": "price-letter.ts",
"64": "Community 64",
"65": "detail.vue",
"66": "Community 66",
"67": "index.vue",
"68": "Community 68",
@@ -59,17 +74,27 @@
"72": "Community 72",
"73": "Community 73",
"74": "Community 74",
"75": "openEdit",
"76": "handleSearch",
"77": "loadUserOptions",
"78": "customerPool.ts",
"79": "Community 79",
"80": "product.ts",
"81": "Community 81",
"82": "Community 82",
"83": "StorageKeyManager",
"84": "Community 84",
"85": "handleEdit",
"86": "Community 86",
"87": "canDeleteByCreator",
"88": "index.vue",
"90": "Community 90",
"91": "Community 91",
"92": "Community 92",
"93": "loadUserOptions",
"94": "loadUserOptions",
"95": "Community 95",
"96": "Community 96",
"104": "Community 104",
"106": "Community 106",
"107": "Community 107",
"108": "Community 108",
@@ -83,7 +108,6 @@
"120": "Community 120",
"125": "Community 125",
"126": "Community 126",
"127": "Community 127",
"128": "Community 128",
"129": "Community 129",
"132": "Community 132",
@@ -117,13 +141,11 @@
"188": "Community 188",
"189": "Community 189",
"190": "Community 190",
"191": "Community 191",
"194": "Community 194",
"195": "Community 195",
"196": "Community 196",
"197": "Community 197",
"202": "Community 202",
"203": "Community 203",
"207": "Community 207",
"208": "Community 208",
"209": "Community 209",
@@ -135,23 +157,19 @@
"222": "types.ts",
"223": "Community 223",
"224": "Community 224",
"225": "ContactFormDialog.vue",
"227": "Community 227",
"228": "Community 228",
"233": "Community 233",
"234": "Community 234",
"235": "index.vue",
"236": "Community 236",
"237": "index.vue",
"239": "Community 239",
"241": "SettingActions.vue",
"242": "Community 242",
"243": "Community 243",
"244": "index.vue",
"246": "price-letter-template.ts",
"247": "Community 247",
"248": "Community 248",
"249": "main.ts",
"251": "handleDisburse",
"253": "Community 253",
"254": "Community 254",
@@ -159,14 +177,10 @@
"259": "Community 259",
"260": "Community 260",
"261": "Community 261",
"263": "Community 263",
"264": "Community 264",
"265": "Community 265",
"269": "Community 269",
"270": "Community 270",
"274": "Community 274",
"276": "Community 276",
"278": "Ha",
"280": "Community 280",
"283": "Community 283",
"284": "Community 284",
@@ -186,7 +200,6 @@
"308": "Community 308",
"309": "Community 309",
"310": "Community 310",
"314": "detail.vue",
"315": "Community 315",
"316": "Community 316",
"317": "Community 317",
@@ -216,27 +229,17 @@
"341": "Community 341",
"342": "error-handle.ts",
"344": "ArtException.vue",
"345": "Community 345",
"346": "Community 346",
"347": "Community 347",
"348": "canPreview",
"349": "Community 349",
"350": "Community 350",
"353": "index.vue",
"354": "Community 354",
"355": "Community 355",
"356": "handleDisburse",
"362": "Community 362",
"364": "Community 364",
"365": "Community 365",
"366": "highlight.ts",
"367": "Community 367",
"370": "loader.ts",
"371": "fetchData",
"372": "detail.vue",
"376": "index.vue",
"377": "Community 377",
"378": "index.vue",
"379": "Community 379",
"380": "Community 380",
"381": "Community 381",
@@ -246,7 +249,6 @@
"392": "Community 392",
"393": "Community 393",
"394": "Community 394",
"401": "fetchPriceLetterDetail",
"405": "zh_CN.js",
"406": "Community 406",
"407": "index.vue",
@@ -256,7 +258,6 @@
"412": "Community 412",
"416": "Community 416",
"417": "Community 417",
"418": "SettledPanel.vue",
"422": "Community 422",
"423": "Community 423",
"424": "Community 424",
@@ -269,20 +270,15 @@
"443": "index.vue",
"444": "index.vue",
"445": "Community 445",
"446": "Community 446",
"447": "crm-product-display.ts",
"448": "theme.min.js",
"453": "ViewKvTableCell.vue",
"454": "zh_CN.js",
"455": "zh_CN.js",
"457": "pl-batch-print.ts",
"458": "Community 458",
"459": "Community 459",
"460": "Community 460",
"461": "loading.ts",
"462": "Community 462",
"463": "index.vue",
"464": "zh_CN.js",
"465": "index.vue",
"470": "Ta",
"471": "Community 471",
@@ -294,11 +290,8 @@
"483": "crm-product-display.ts",
"487": "Community 487",
"488": "Community 488",
"493": "SettingActions.vue",
"495": "colors.ts",
"496": "Community 496",
"499": "Community 499",
"506": "loading.ts",
"521": "Community 521",
"522": "Community 522",
"528": "Community 528",
@@ -373,7 +366,6 @@
"653": "Community 653",
"654": "Community 654",
"655": "Community 655",
"656": "Community 656",
"657": "Community 657",
"658": "Community 658",
"659": "Community 659",
+1 -1
View File
@@ -1 +1 @@
web-admin
/Users/wuzilong/work/00code/shanxia2.0/web-admin
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -14,115 +14,121 @@
@closed="handleClosed"
@opened="handleOpened"
>
<div class="pl-arrange-layout">
<section class="pl-arrange-form-panel">
<div v-loading="loading" class="pl-arrange-form-scroll">
<div ref="feedbackMirrorEl" class="pl-archive-feedback-mirror" />
<div v-loading="loading" class="pl-archive-layout">
<el-tabs v-model="activeTab" class="pl-archive-tabs">
<el-tab-pane label="归档填写" name="archive" />
<el-tab-pane label="计划概况" name="overview" />
<el-tab-pane label="运输信息" name="transport" />
<el-tab-pane label="合同信息" name="contract" />
</el-tabs>
<section class="pl-plan-form-section">
<h4 class="pl-plan-subtitle">产品实际数量</h4>
<PlPlanFormProductTable
:products="productRows"
show-actual-qty
editable-actual-qty-only
<div v-show="activeTab === 'archive'" class="pl-archive-tab-panel pl-arrange-form-scroll">
<div ref="feedbackMirrorEl" class="pl-archive-feedback-mirror" />
<section class="pl-plan-form-section">
<h3 class="pl-plan-form-section-title">产品实际数量</h3>
<PlPlanFormProductTable
:products="productRows"
show-actual-qty
editable-actual-qty-only
/>
<div
v-for="row in brandedProductRows"
:key="row.id || row.productId"
class="pl-archive-brand-qty-block"
>
<div class="pl-archive-brand-qty-head">
<span class="pl-archive-brand-qty-title">{{ row.productName }} · 按品牌填量</span>
<el-tag v-if="row.brandMode === 'required'" size="small" type="warning"
>指定品牌</el-tag
>
<el-tag v-else size="small" type="info">不限品牌</el-tag>
</div>
<BrandQtyChipsField
v-model="row.brandQtyRows"
:brand-mode="archiveBrandMode(row)"
:required-brand-id="row.brandId"
:brand-options="row.brandOptions || []"
:unit="resolveProductUnitLabel(row.unit)"
variant="admin"
:actual-qty="Number(row.actualQty ?? 0)"
@update:actual-qty="(val) => onArchiveBrandActualQty(row, val)"
/>
<div
v-for="row in brandedProductRows"
:key="row.id || row.productId"
class="pl-archive-brand-qty-block"
>
<div class="pl-archive-brand-qty-head">
<span class="pl-archive-brand-qty-title">{{ row.productName }} · 按品牌填量</span>
<el-tag v-if="row.brandMode === 'required'" size="small" type="warning"
>指定品牌</el-tag
>
<el-tag v-else size="small" type="info">不限品牌</el-tag>
</div>
<BrandQtyChipsField
v-model="row.brandQtyRows"
:brand-mode="archiveBrandMode(row)"
:required-brand-id="row.brandId"
:brand-options="row.brandOptions || []"
:unit="resolveProductUnitLabel(row.unit)"
variant="admin"
:actual-qty="Number(row.actualQty ?? 0)"
@update:actual-qty="(val) => onArchiveBrandActualQty(row, val)"
/>
</div>
</section>
</div>
</section>
<section class="pl-plan-form-section pl-plan-freight-section">
<div class="pl-plan-freight-section-head">
<h4 class="pl-plan-subtitle">运费信息</h4>
<label class="pl-plan-freight-switch">
<span class="pl-plan-freight-switch-label">是否有运输费用</span>
<el-switch v-model="formData.hasFreight" :active-value="1" :inactive-value="0" />
</label>
<section class="pl-plan-form-section pl-plan-freight-section">
<div class="pl-plan-freight-section-head">
<h3 class="pl-plan-form-section-title">运费信息</h3>
<label class="pl-plan-freight-switch">
<span class="pl-plan-freight-switch-label">是否有运输费用</span>
<el-switch v-model="formData.hasFreight" :active-value="1" :inactive-value="0" />
</label>
</div>
<el-form
v-if="formData.hasFreight === 1"
:model="formData"
label-position="top"
class="pl-plan-freight-form"
>
<div class="form-grid-5 pl-plan-archive-freight-grid">
<el-form-item label="运费计算方式">
<el-select v-model="formData.freightType">
<el-option label="按重量" value="by_weight" />
<el-option label="按趟" value="by_trip" />
</el-select>
</el-form-item>
<el-form-item label="运输公里数">
<el-input-number v-model="formData.distanceKm" :min="0" :precision="2" />
</el-form-item>
<el-form-item
v-if="formData.freightType === 'by_weight'"
:label="`运输量(${freightUnitLabel})`"
>
<el-input-number
v-model="freightCalc.transportQty.value"
:min="0"
:precision="3"
@change="freightCalc.markTransportQtyTouched()"
/>
</el-form-item>
<el-form-item
v-if="formData.freightType === 'by_weight'"
:label="`运输单价(元/${freightUnitLabel})`"
>
<el-input-number
v-model="freightCalc.freightPrice.value"
:min="0"
:precision="2"
/>
</el-form-item>
<el-form-item label="运费金额(元)">
<el-input-number
v-model="freightCalc.freightAmount.value"
:min="0"
:precision="2"
@change="freightCalc.markFreightAmountTouched()"
/>
</el-form-item>
</div>
<el-form
v-if="formData.hasFreight === 1"
:model="formData"
label-position="top"
class="pl-plan-freight-form"
>
<div class="form-grid-5 pl-plan-archive-freight-grid">
<el-form-item label="运费计算方式">
<el-select v-model="formData.freightType">
<el-option label="按重量" value="by_weight" />
<el-option label="按趟" value="by_trip" />
</el-select>
</el-form-item>
<el-form-item label="运输公里数">
<el-input-number v-model="formData.distanceKm" :min="0" :precision="2" />
</el-form-item>
<el-form-item
v-if="formData.freightType === 'by_weight'"
:label="`运输量(${freightUnitLabel})`"
>
<el-input-number
v-model="freightCalc.transportQty.value"
:min="0"
:precision="3"
@change="freightCalc.markTransportQtyTouched()"
/>
</el-form-item>
<el-form-item
v-if="formData.freightType === 'by_weight'"
:label="`运输单价(元/${freightUnitLabel})`"
>
<el-input-number
v-model="freightCalc.freightPrice.value"
:min="0"
:precision="2"
/>
</el-form-item>
<el-form-item label="运费金额(元)">
<el-input-number
v-model="freightCalc.freightAmount.value"
:min="0"
:precision="2"
@change="freightCalc.markFreightAmountTouched()"
/>
</el-form-item>
</div>
</el-form>
</section>
</div>
</section>
</el-form>
</section>
</div>
<section class="pl-arrange-view-panel">
<h3 class="pl-arrange-view-title">计划单查看</h3>
<div v-show="activeTab !== 'archive'" class="pl-archive-tab-panel pl-arrange-view-panel">
<PlPlanViewTabs
v-if="planId && feedbackMirrorTarget"
ref="tabsRef"
:plan-id="planId"
embedded
hide-nav
:tab="viewTab"
feedback-editable
:arrange-driver-url="driverUrl"
:arrange-site-records="siteRecords"
:feedback-mirror-to="feedbackMirrorTarget"
:feedback-mirror-to="archiveFeedbackMirrorTo"
/>
</section>
</div>
</div>
<template #footer>
@@ -183,11 +189,19 @@
const loading = ref(false)
const submitLoading = ref(false)
const activeTab = ref<'archive' | 'overview' | 'transport' | 'contract'>('archive')
const tabsRef = ref<InstanceType<typeof PlPlanViewTabs> | null>(null)
const plan = ref<Partial<Plan>>({})
const feedbackMirrorEl = ref<HTMLElement | null>(null)
/** 仅在弹窗 opened 且镜像节点已挂载后传入,避免 Teleport 目标为空导致 Vue patch 崩溃 */
const feedbackMirrorTarget = ref<HTMLElement | null>(null)
/** 归档填写 Tab 时把回传区镜像过去;其他 Tab 时留在运输信息区内 */
const archiveFeedbackMirrorTo = computed(() =>
activeTab.value === 'archive' ? feedbackMirrorTarget.value : null
)
const viewTab = computed(() =>
activeTab.value === 'archive' ? 'overview' : activeTab.value
)
const { driverUrl, siteRecords, resetArrangeActions, loadArrangeActions } =
usePlPlanArrangeActions()
@@ -378,6 +392,7 @@
}
function resetState() {
activeTab.value = 'archive'
plan.value = {}
productRows.value = []
Object.assign(formData, {
@@ -578,16 +593,23 @@
</script>
<style scoped lang="scss">
.pl-arrange-layout {
display: flex;
flex-direction: column;
gap: 16px;
.pl-archive-layout {
min-height: 480px;
}
.pl-arrange-form-panel {
flex: 0 0 auto;
overflow: visible;
.pl-archive-tabs {
:deep(> .el-tabs__header) {
margin-bottom: 16px;
}
/* 仅用顶部导航,内容区由下方面板接管 */
:deep(> .el-tabs__content) {
display: none;
}
}
.pl-archive-tab-panel {
min-height: 360px;
}
.pl-arrange-form-scroll {
@@ -608,30 +630,28 @@
}
.pl-arrange-view-panel {
flex: 0 0 auto;
min-height: 280px;
display: flex;
flex-direction: column;
overflow: visible;
}
.pl-arrange-view-title {
margin: 0 0 12px;
font-size: 14px;
font-weight: 600;
color: var(--section-bar-title-color, var(--primary-dark, #059669));
}
.pl-plan-form-section {
flex: 0 0 auto;
min-width: 0;
}
.pl-plan-subtitle {
margin: 0 0 10px;
font-size: 13px;
.pl-plan-form-section-title {
margin: 0 0 12px;
padding-left: 10px;
font-size: 14px;
font-weight: 600;
color: #475569;
color: var(--section-bar-title-color, var(--primary-dark, #059669));
border-left: 3px solid var(--section-bar-border-color, var(--primary, #10b981));
line-height: 1;
}
.pl-plan-freight-section-head .pl-plan-form-section-title {
margin-bottom: 0;
}
.pl-archive-brand-qty-block {
@@ -657,11 +677,6 @@
}
.pl-plan-freight-section {
.pl-plan-subtitle {
margin-top: 4px;
margin-bottom: 0;
}
:deep(.el-form-item__label) {
width: 140px !important;
max-width: 140px;
@@ -31,21 +31,28 @@
<el-tag v-if="item.isExplosive" type="danger" size="small">易制爆</el-tag>
<el-tag v-if="item.isToxic" type="warning" size="small">易制毒</el-tag>
</div>
<div class="form-grid-2 product-cert-grid">
<el-form-item
v-for="slot in getRequiredCertSlots(item)"
:key="slot.key"
:label="slot.label"
:class="{ 'product-cert-grid__span-all': getRequiredCertSlots(item).length === 1 }"
>
<AttachmentUploadField
v-model="item[slot.key]"
folder="pl/plan-cert"
:limit="1"
:hint="`支持图片、PDF 等,上传${slot.label}`"
/>
</el-form-item>
</div>
<el-form
label-position="left"
label-width="72px"
class="product-cert-form"
@submit.prevent
>
<div class="form-grid-2 product-cert-grid">
<el-form-item
v-for="slot in getRequiredCertSlots(item)"
:key="slot.key"
:label="slot.label"
:class="{ 'product-cert-grid__span-all': getRequiredCertSlots(item).length === 1 }"
>
<AttachmentUploadField
v-model="item[slot.key]"
folder="pl/plan-cert"
:limit="1"
:hint="`支持图片、PDF 等,上传${slot.label}`"
/>
</el-form-item>
</div>
</el-form>
</div>
</div>
@@ -200,10 +207,27 @@ function handleClosed() {
color: var(--text, #1e293b);
}
.product-cert-grid {
.product-cert-form {
:deep(.el-form-item) {
align-items: flex-start;
margin-bottom: 0;
}
/* 购买证 / 运输证 / 合法证明 统一标题宽度,四字不换行,附件上传区左对齐 */
:deep(.el-form-item__label) {
flex-shrink: 0;
width: 72px !important;
justify-content: flex-start;
text-align: left;
white-space: nowrap;
line-height: 1.4 !important;
height: auto !important;
padding-top: 8px;
}
:deep(.el-form-item__content) {
min-width: 0;
}
}
.product-cert-grid__span-all {
File diff suppressed because it is too large. Load diff
@@ -1,6 +1,10 @@
<!-- eslint-disable vue/no-v-model-argument -->
<template>
<div v-loading="loading" class="pl-plan-view-tabs" :class="{ 'is-embedded': embedded }">
<div
v-loading="loading"
class="pl-plan-view-tabs"
:class="{ 'is-embedded': embedded, 'is-hide-nav': hideNav }"
>
<el-tabs v-model="activeTab" class="pl-plan-view-tabs__nav">
<el-tab-pane label="计划概况" name="overview">
<section class="pl-plan-form-section pl-plan-form-section--plain">
@@ -80,8 +84,8 @@
<section class="pl-plan-form-section pl-plan-form-section--plain">
<h3 class="pl-plan-form-section-title">物流节点</h3>
<div class="parties-row">
<div class="party-panel party-panel--plain">
<ViewKvTable :fields="pickupViewFields" mode="quad" />
<div class="party-panel party-panel--plain pl-plan-logistics-panel">
<ViewKvTable :fields="pickupViewFields" mode="dual" />
</div>
<div class="party-flow-bridge">
@@ -105,8 +109,8 @@
</span>
</div>
<div class="party-panel party-panel--plain">
<ViewKvTable :fields="deliveryViewFields" mode="quad" />
<div class="party-panel party-panel--plain pl-plan-logistics-panel">
<ViewKvTable :fields="deliveryViewFields" mode="dual" />
</div>
</div>
</section>
@@ -347,7 +351,6 @@
import PlVehicleViewDialog from '@/components/business/pl-vehicle-view-dialog/index.vue'
import ViewKvTable from '@/components/business/system-view-dialog/ViewKvTable.vue'
import type { ViewField } from '@/components/business/system-view-dialog/types'
import { businessTypeTagType } from '@/components/business/db/db-related-view'
import type { ContractVO } from '@/api/crm/contract/types'
import { inventoryApi } from '@/api/db/inventory'
import {
@@ -362,7 +365,7 @@
mapContractProducts,
type PlanFormProductRow
} from '@/composables/usePlContractToPlan'
import { mapPlanProductToFormRow, usePlPlanViewDetail, type PlPlanTransportDibangRow, type PlPlanTransportSiteRow } from '@/composables/usePlPlanViewDetail'
import { mapPlanProductToFormRow, usePlPlanViewDetail, type PlPlanTransportSiteRow } from '@/composables/usePlPlanViewDetail'
import { resolveFreightUnitLabel } from '@/composables/usePlFreightCalc'
import { isHazardProduct } from '@/composables/usePlToxicProductCertCheck'
import { useDictStore } from '@/store/modules/dict'
@@ -400,6 +403,10 @@
feedbackEditable?: boolean
/** 将司机/现场/地磅回传区块镜像渲染到归档左侧等目标节点 */
feedbackMirrorTo?: string | HTMLElement | null
/** 隐藏内部 Tab 导航(由外层统一控制 Tab) */
hideNav?: boolean
/** 受控当前 Tab:overview / transport / contract */
tab?: string
}>(),
{
embedded: false,
@@ -407,11 +414,33 @@
arrangeDriverUrl: '',
arrangeSiteRecords: () => [],
feedbackEditable: false,
feedbackMirrorTo: null
feedbackMirrorTo: null,
hideNav: false,
tab: undefined
}
)
const activeTab = ref('overview')
const emit = defineEmits<{
'update:tab': [value: string]
}>()
const innerActiveTab = ref(props.tab || 'overview')
const activeTab = computed({
get: () => props.tab || innerActiveTab.value,
set: (value: string) => {
innerActiveTab.value = value
emit('update:tab', value)
}
})
watch(
() => props.tab,
(value) => {
if (value && value !== innerActiveTab.value) {
innerActiveTab.value = value
}
}
)
const contractDetailDialogActive = ref(false)
const contractDetailRef = ref<InstanceType<typeof ContractDetailDialog>>()
const vehicleViewVisible = ref(false)
@@ -899,28 +928,6 @@
}))
)
function dibangBasicViewFields(row: PlPlanTransportDibangRow): ViewField[] {
const businessType = row.inventory?.businessType
// long: false — 强制六字段同一行展示,避免往来单位等长文案自动折行
return [
{ label: '出入库单号', value: row.inventory?.batchNo || row.link.batchNo, long: false },
{
label: '业务类型',
value: businessType,
tagType: businessType ? businessTypeTagType(businessType) : undefined,
long: false
},
{ label: '往来单位', value: row.inventory?.partnerCompany, long: false },
{
label: '车牌号',
value: row.inventory?.vehicleNo || plan.value?.licensePlate || transport.value?.licensePlate,
long: false
},
{ label: '产品', value: row.inventory?.product || row.link.productName, long: false },
{ label: '数量(吨)', value: row.inventory?.quantity, long: false }
]
}
const dibangPlanRows = computed(() =>
dibangRows.value.filter(row => Boolean(row.link.dbPurchasePlanId?.trim()))
)
@@ -1048,8 +1055,7 @@
ioOrderViewFields: ioOrderViewFields.value,
ioOrderImageCsv: ioOrderImageCsv.value,
siteFeedbackTableRows: siteFeedbackTableRows.value,
dibangPlanRows: dibangPlanRows.value,
dibangBasicViewFields
dibangPlanRows: dibangPlanRows.value
}))
const feedbackBlocksOn = {
@@ -1284,6 +1290,16 @@
margin-top: 0;
}
}
&.is-hide-nav {
.pl-plan-view-tabs__nav :deep(> .el-tabs__header) {
display: none;
}
.pl-plan-view-tabs__nav :deep(> .el-tabs__content) {
padding-top: 0;
}
}
}
.pl-plan-view-tabs__nav :deep(.el-tabs__content) {
@@ -1449,6 +1465,23 @@
}
}
.pl-plan-logistics-panel {
:deep(.view-kv-table--dual th) {
width: 88px;
white-space: nowrap;
}
:deep(.view-kv-table--dual td) {
width: auto;
min-width: 7.5em;
}
/* 取货/送达时间所在值列禁止换行,避免 2026-07-31 被拆开 */
:deep(.view-kv-table--dual tr:not(.row-long) td:first-of-type) {
white-space: nowrap;
}
}
.party-panel__title {
margin-bottom: 12px;
padding-left: 10px;
@@ -15,9 +15,13 @@
<li v-for="(item, idx) in field.items" :key="idx">{{ item }}</li>
</ul>
<template v-else>
<span :class="{ 'money-display': field.money }">{{
formatViewValue(field.value)
}}</span>
<span
:class="{
'money-display': field.money,
'danger-display': field.danger
}"
>{{ formatViewValue(field.value) }}</span
>
</template>
</dd>
</div>
@@ -60,6 +64,22 @@
&--cols-5 {
grid-template-columns: repeat(5, minmax(0, 1fr));
}
&--cols-6 {
grid-template-columns: repeat(6, minmax(0, 1fr));
}
&--cols-7 {
grid-template-columns: repeat(7, minmax(0, 1fr));
}
&--cols-8 {
grid-template-columns: repeat(8, minmax(0, 1fr));
}
&--cols-9 {
grid-template-columns: repeat(9, minmax(0, 1fr));
}
}
.view-kv {
@@ -85,6 +105,11 @@
word-break: break-word;
line-height: 1.5;
}
.danger-display {
color: #dc2626;
font-weight: 600;
}
}
.view-kv-span-2 {
@@ -105,9 +130,20 @@
}
}
@media (max-width: 1400px) {
.view-kv-grid--cols-8,
.view-kv-grid--cols-9 {
grid-template-columns: repeat(5, minmax(0, 1fr));
}
}
@media (max-width: 1100px) {
.view-kv-grid--cols-4,
.view-kv-grid--cols-5 {
.view-kv-grid--cols-5,
.view-kv-grid--cols-6,
.view-kv-grid--cols-7,
.view-kv-grid--cols-8,
.view-kv-grid--cols-9 {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
}
@@ -115,7 +151,11 @@
@media (max-width: 900px) {
.view-kv-grid--cols-3,
.view-kv-grid--cols-4,
.view-kv-grid--cols-5 {
.view-kv-grid--cols-5,
.view-kv-grid--cols-6,
.view-kv-grid--cols-7,
.view-kv-grid--cols-8,
.view-kv-grid--cols-9 {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@@ -124,7 +164,11 @@
.view-kv-grid,
.view-kv-grid--cols-3,
.view-kv-grid--cols-4,
.view-kv-grid--cols-5 {
.view-kv-grid--cols-5,
.view-kv-grid--cols-6,
.view-kv-grid--cols-7,
.view-kv-grid--cols-8,
.view-kv-grid--cols-9 {
grid-template-columns: 1fr;
}
+5 -1
View File
@@ -351,7 +351,11 @@
/>
</el-form-item>
<el-form-item label="往来单位" prop="customerName">
<DbCrmCustomerSelect v-model="formData.customerName" placeholder="请输入往来单位搜索" />
<DbCrmCustomerSelect
v-model="formData.customerName"
:allow-create="false"
placeholder="请从客户管理中搜索选择"
/>
</el-form-item>
<el-form-item label="产品" prop="productId">
<DbCrmProductSelect
+12 -4
View File
@@ -371,7 +371,11 @@
<el-input :value="currentQuantityInTons" placeholder="本次量(吨)" readonly />
</el-form-item>
<el-form-item label="供货单位" prop="supplier">
<DbCrmCustomerSelect v-model="formData.supplier" placeholder="请输入供货单位搜索" />
<DbCrmCustomerSelect
v-model="formData.supplier"
:allow-create="false"
placeholder="请从客户管理中搜索选择"
/>
</el-form-item>
<el-form-item label="扣杂(kg)" prop="deduction">
<el-input-number
@@ -385,7 +389,11 @@
/>
</el-form-item>
<el-form-item label="收货单位" prop="consignee">
<DbCrmCustomerSelect v-model="formData.consignee" placeholder="请输入收货单位搜索" />
<DbCrmCustomerSelect
v-model="formData.consignee"
:allow-create="false"
placeholder="请从客户管理中搜索选择"
/>
</el-form-item>
<el-form-item label="毛重(kg)" prop="grossWeight">
<el-input-number
@@ -631,8 +639,8 @@
const formRules = {
vehicleNo: [{ required: true, message: '请选择车牌号', trigger: 'change' }],
productId: [{ required: true, message: '请选择 CRM 产品', trigger: 'change' }],
supplier: [{ required: true, message: '请输入供货单位', trigger: 'blur' }],
consignee: [{ required: true, message: '请输入收货单位', trigger: 'blur' }]
supplier: [{ required: true, message: '请选择供货单位', trigger: 'change' }],
consignee: [{ required: true, message: '请选择收货单位', trigger: 'change' }]
}
const netWeight = computed(() => {