Merge box number and paichan info into a single header bar, compress list rows to 40px with left color bars, fix bottom operation area (scan/submit/finish) above status bar, and shrink notification bars and status bar for more list space. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1190 lines
34 KiB
HTML
1190 lines
34 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>装箱编号 - 多码凑箱模式 UI 展示</title>
|
||
<style>
|
||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||
|
||
body {
|
||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans SC', sans-serif;
|
||
background: #f0f2f5;
|
||
color: #333;
|
||
padding: 20px;
|
||
}
|
||
|
||
h1 {
|
||
text-align: center;
|
||
font-size: 22px;
|
||
margin-bottom: 6px;
|
||
color: #1a1a1a;
|
||
}
|
||
|
||
.subtitle {
|
||
text-align: center;
|
||
font-size: 14px;
|
||
color: #888;
|
||
margin-bottom: 30px;
|
||
}
|
||
|
||
/* === 场景选择 Tab === */
|
||
.scenario-tabs {
|
||
display: flex;
|
||
gap: 8px;
|
||
margin-bottom: 24px;
|
||
flex-wrap: wrap;
|
||
justify-content: center;
|
||
}
|
||
|
||
.scenario-tab {
|
||
padding: 8px 18px;
|
||
border: 2px solid #ddd;
|
||
border-radius: 20px;
|
||
background: #fff;
|
||
cursor: pointer;
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
transition: all 0.2s;
|
||
color: #555;
|
||
}
|
||
|
||
.scenario-tab:hover { border-color: #1976d2; color: #1976d2; }
|
||
.scenario-tab.active { background: #1976d2; color: #fff; border-color: #1976d2; }
|
||
|
||
/* === 手机框架 === */
|
||
.phone-container {
|
||
display: flex;
|
||
justify-content: center;
|
||
gap: 32px;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.phone-wrapper {
|
||
text-align: center;
|
||
}
|
||
|
||
.phone-label {
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
color: #555;
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.phone-frame {
|
||
width: 380px;
|
||
background: #fff;
|
||
border-radius: 24px;
|
||
box-shadow: 0 4px 24px rgba(0,0,0,0.12), 0 0 0 1px rgba(0,0,0,0.05);
|
||
overflow: hidden;
|
||
}
|
||
|
||
/* === AppBar === */
|
||
.app-bar {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 14px 12px;
|
||
background: #f5f5f5;
|
||
border-bottom: 1px solid #e0e0e0;
|
||
}
|
||
|
||
.app-bar-title {
|
||
font-size: 18px;
|
||
font-weight: 700;
|
||
color: #1a1a1a;
|
||
}
|
||
|
||
.mode-badge {
|
||
margin-left: 12px;
|
||
padding: 6px 10px;
|
||
border-radius: 8px;
|
||
background: #e65100;
|
||
color: #fff;
|
||
font-size: 13px;
|
||
font-weight: 800;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
cursor: pointer;
|
||
flex: 1;
|
||
justify-content: center;
|
||
}
|
||
|
||
.mode-badge .icon { font-size: 14px; }
|
||
.mode-badge .shortcut {
|
||
font-size: 11px;
|
||
font-weight: 700;
|
||
opacity: 0.88;
|
||
margin-left: 4px;
|
||
}
|
||
|
||
/* === Alert Bars === */
|
||
.alert-bar {
|
||
width: 100%;
|
||
padding: 8px 16px;
|
||
font-size: 13px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.alert-sync {
|
||
background: #fff3e0;
|
||
color: #e65100;
|
||
}
|
||
|
||
.alert-sync .spinner {
|
||
width: 16px; height: 16px;
|
||
border: 2px solid #fb8c00;
|
||
border-top-color: transparent;
|
||
border-radius: 50%;
|
||
animation: spin 0.8s linear infinite;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
@keyframes spin { to { transform: rotate(360deg); } }
|
||
|
||
.alert-warning {
|
||
background: #fff8e1;
|
||
color: #ff8f00;
|
||
}
|
||
|
||
.alert-blue {
|
||
background: #e3f2fd;
|
||
color: #0d47a1;
|
||
}
|
||
|
||
.alert-amber {
|
||
background: #fff8e1;
|
||
color: #ff8f00;
|
||
}
|
||
|
||
/* === Body Content === */
|
||
.body-content {
|
||
padding: 12px;
|
||
}
|
||
|
||
/* --- Box No Panel --- */
|
||
.box-no-panel {
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.box-no-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.box-no-label {
|
||
font-size: 22px;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.box-no-input {
|
||
width: 96px;
|
||
height: 44px;
|
||
text-align: center;
|
||
font-size: 24px;
|
||
font-weight: 700;
|
||
border: 2px solid #9e9e9e;
|
||
border-radius: 6px;
|
||
outline: none;
|
||
color: #333;
|
||
}
|
||
|
||
.box-no-input:focus { border-color: #1976d2; }
|
||
|
||
.box-no-divider {
|
||
height: 1px;
|
||
background: #e0e0e0;
|
||
}
|
||
|
||
/* --- Info Row --- */
|
||
.info-row {
|
||
display: flex;
|
||
align-items: center;
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.info-row-text {
|
||
flex: 1;
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
color: #333;
|
||
}
|
||
|
||
.info-row-btn {
|
||
padding: 4px 8px;
|
||
font-size: 13px;
|
||
color: #1976d2;
|
||
background: none;
|
||
border: none;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.info-row-btn.disabled {
|
||
color: #bdbdbd;
|
||
cursor: default;
|
||
}
|
||
|
||
/* --- Packed List --- */
|
||
.packed-header {
|
||
font-size: 13px;
|
||
font-weight: 700;
|
||
margin-bottom: 6px;
|
||
}
|
||
|
||
.packed-empty {
|
||
font-size: 13px;
|
||
color: #9e9e9e;
|
||
}
|
||
|
||
.packed-item {
|
||
margin-bottom: 6px;
|
||
padding: 6px 8px;
|
||
border: 1px solid #e0e0e0;
|
||
border-radius: 6px;
|
||
background: #fafafa;
|
||
}
|
||
|
||
.packed-item.editing {
|
||
background: #fff3e0;
|
||
border-color: #fb8c00;
|
||
}
|
||
|
||
.packed-item-row {
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
.packed-item-info {
|
||
flex: 1;
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.packed-item-qty {
|
||
font-size: 13px;
|
||
color: #555;
|
||
margin-right: 4px;
|
||
}
|
||
|
||
.packed-item-edit-input {
|
||
width: 56px;
|
||
height: 32px;
|
||
text-align: center;
|
||
border: 1px solid #bbb;
|
||
border-radius: 4px;
|
||
font-size: 13px;
|
||
outline: none;
|
||
margin-right: 4px;
|
||
}
|
||
|
||
.icon-btn {
|
||
width: 32px;
|
||
height: 32px;
|
||
border: none;
|
||
background: none;
|
||
cursor: pointer;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
border-radius: 50%;
|
||
font-size: 16px;
|
||
color: #666;
|
||
}
|
||
|
||
.icon-btn:hover { background: #e0e0e0; }
|
||
.icon-btn.check { color: #4caf50; }
|
||
.icon-btn.close { color: #f44336; }
|
||
.icon-btn.edit { color: #ff9800; }
|
||
.icon-btn.delete { color: #e53935; }
|
||
|
||
.delete-confirm {
|
||
display: flex;
|
||
align-items: center;
|
||
padding-top: 4px;
|
||
font-size: 13px;
|
||
}
|
||
|
||
.delete-confirm span { flex: 1; }
|
||
|
||
.delete-confirm-btn {
|
||
border: none;
|
||
background: none;
|
||
font-size: 14px;
|
||
cursor: pointer;
|
||
padding: 4px 8px;
|
||
}
|
||
|
||
.delete-confirm-btn.yes { color: #e53935; }
|
||
.delete-confirm-btn.no { color: #666; }
|
||
|
||
/* --- Divider --- */
|
||
.divider {
|
||
height: 1px;
|
||
background: #e0e0e0;
|
||
margin: 12px 0;
|
||
}
|
||
|
||
/* --- Scan Area --- */
|
||
.scan-area {
|
||
width: 100%;
|
||
padding: 14px;
|
||
border-radius: 8px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.scan-area.waiting {
|
||
background: #f5f5f5;
|
||
color: #9e9e9e;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.scan-area.scanned {
|
||
background: #fff;
|
||
border: 2px solid #4caf50;
|
||
justify-content: flex-start;
|
||
padding: 10px;
|
||
}
|
||
|
||
.scan-area .check-icon {
|
||
color: #4caf50;
|
||
font-size: 18px;
|
||
}
|
||
|
||
.scan-area .scanned-text {
|
||
font-size: 14px;
|
||
font-weight: 700;
|
||
flex: 1;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.scan-area .scanner-icon {
|
||
font-size: 20px;
|
||
color: #9e9e9e;
|
||
}
|
||
|
||
/* --- Submit Area --- */
|
||
.submit-area {
|
||
display: flex;
|
||
align-items: flex-end;
|
||
gap: 8px;
|
||
}
|
||
|
||
.submit-qty-col {
|
||
flex: 1;
|
||
}
|
||
|
||
.submit-label {
|
||
font-size: 13px;
|
||
color: #999;
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.submit-label.active { color: #666; }
|
||
|
||
.submit-qty-input {
|
||
width: 100%;
|
||
height: 40px;
|
||
border: 1px solid #e0e0e0;
|
||
border-radius: 4px;
|
||
padding: 0 10px;
|
||
font-size: 16px;
|
||
outline: none;
|
||
}
|
||
|
||
.submit-qty-input:focus { border-color: #1976d2; }
|
||
|
||
.submit-qty-input.error { border-color: #f44336; }
|
||
.submit-qty-input:disabled { background: #f5f5f5; color: #bbb; }
|
||
|
||
.submit-btn {
|
||
height: 40px;
|
||
padding: 0 16px;
|
||
border: none;
|
||
border-radius: 6px;
|
||
font-size: 15px;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
transition: background 0.2s;
|
||
}
|
||
|
||
.submit-btn.active {
|
||
background: #1976d2;
|
||
color: #fff;
|
||
}
|
||
|
||
.submit-btn.disabled {
|
||
background: #e0e0e0;
|
||
color: #9e9e9e;
|
||
cursor: default;
|
||
}
|
||
|
||
/* --- Finish Button --- */
|
||
.finish-btn-area {
|
||
padding: 4px 12px;
|
||
}
|
||
|
||
.finish-btn {
|
||
width: 100%;
|
||
height: 40px;
|
||
border: 1px solid #ccc;
|
||
border-radius: 6px;
|
||
background: #fff;
|
||
font-size: 15px;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
color: #333;
|
||
transition: all 0.2s;
|
||
}
|
||
|
||
.finish-btn:hover { background: #f5f5f5; border-color: #999; }
|
||
|
||
.finish-btn.disabled {
|
||
color: #bbb;
|
||
border-color: #e0e0e0;
|
||
cursor: default;
|
||
}
|
||
|
||
/* --- Status Bar --- */
|
||
.status-bar {
|
||
width: 100%;
|
||
padding: 12px 16px;
|
||
background: #f0f0f0;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.status-dot {
|
||
width: 8px;
|
||
height: 8px;
|
||
border-radius: 50%;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.status-dot.blue { background: #1976d2; }
|
||
.status-dot.orange { background: #ff9800; }
|
||
.status-dot.green { background: #4caf50; }
|
||
.status-dot.red { background: #f44336; }
|
||
.status-dot.yellow { background: #fbc02d; }
|
||
.status-dot.amber { background: #ff8f00; }
|
||
|
||
.status-text {
|
||
font-size: 14px;
|
||
color: #333;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
/* === 说明卡片 === */
|
||
.notes-section {
|
||
max-width: 820px;
|
||
margin: 40px auto 0;
|
||
padding: 0 20px;
|
||
}
|
||
|
||
.notes-card {
|
||
background: #fff;
|
||
border-radius: 12px;
|
||
padding: 24px;
|
||
box-shadow: 0 2px 12px rgba(0,0,0,0.06);
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.notes-card h3 {
|
||
font-size: 16px;
|
||
margin-bottom: 12px;
|
||
color: #1a1a1a;
|
||
border-bottom: 2px solid #1976d2;
|
||
padding-bottom: 6px;
|
||
display: inline-block;
|
||
}
|
||
|
||
.notes-card p, .notes-card li {
|
||
font-size: 14px;
|
||
line-height: 1.8;
|
||
color: #555;
|
||
}
|
||
|
||
.notes-card ul {
|
||
padding-left: 20px;
|
||
}
|
||
|
||
.notes-card code {
|
||
background: #f5f5f5;
|
||
padding: 2px 6px;
|
||
border-radius: 4px;
|
||
font-size: 13px;
|
||
color: #d32f2f;
|
||
}
|
||
|
||
.flow-diagram {
|
||
background: #fafafa;
|
||
border: 1px solid #e0e0e0;
|
||
border-radius: 8px;
|
||
padding: 16px;
|
||
font-family: 'Courier New', monospace;
|
||
font-size: 13px;
|
||
line-height: 1.6;
|
||
white-space: pre;
|
||
overflow-x: auto;
|
||
color: #333;
|
||
}
|
||
|
||
.color-legend {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 16px;
|
||
margin-top: 8px;
|
||
}
|
||
|
||
.color-legend-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
font-size: 13px;
|
||
}
|
||
|
||
.color-legend-dot {
|
||
width: 10px;
|
||
height: 10px;
|
||
border-radius: 50%;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<h1>装箱编号 — 多码凑箱模式 UI 展示</h1>
|
||
<p class="subtitle">基于 boxing_page.dart 实际代码还原 | 源文件:lib/pages/boxing_page.dart (2312行)</p>
|
||
|
||
<!-- 场景选择 -->
|
||
<div class="scenario-tabs">
|
||
<div class="scenario-tab active" onclick="showScenario('s1', this)">初始状态</div>
|
||
<div class="scenario-tab" onclick="showScenario('s2', this)">已扫码识别</div>
|
||
<div class="scenario-tab" onclick="showScenario('s3', this)">已装入多项</div>
|
||
<div class="scenario-tab" onclick="showScenario('s4', this)">编辑/删除已装入项</div>
|
||
<div class="scenario-tab" onclick="showScenario('s5', this)">排产号切换</div>
|
||
<div class="scenario-tab" onclick="showScenario('s6', this)">自动同步转运</div>
|
||
</div>
|
||
|
||
<!-- 场景容器 -->
|
||
<div class="phone-container">
|
||
|
||
<!-- ========== 场景1:初始状态 ========== -->
|
||
<div class="phone-wrapper" id="s1">
|
||
<div class="phone-label">场景1:初始等待扫码</div>
|
||
<div class="phone-frame">
|
||
<!-- AppBar -->
|
||
<div class="app-bar">
|
||
<span class="app-bar-title">装箱编号</span>
|
||
<div class="mode-badge">
|
||
<span class="icon">⇄</span>
|
||
多码凑箱
|
||
<span class="shortcut">P2</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Body -->
|
||
<div class="body-content">
|
||
<!-- Box No Panel -->
|
||
<div class="box-no-panel">
|
||
<div class="box-no-row">
|
||
<span class="box-no-label">当前箱号</span>
|
||
<input class="box-no-input" value="1" />
|
||
</div>
|
||
<div class="box-no-divider"></div>
|
||
</div>
|
||
|
||
<!-- Packed List -->
|
||
<div class="packed-header">已装入本箱(0项)</div>
|
||
<div class="packed-empty">(空)</div>
|
||
|
||
<div class="divider"></div>
|
||
|
||
<!-- Scan Area (waiting) -->
|
||
<div class="scan-area waiting">
|
||
<span class="scanner-icon">📷</span>
|
||
请扫描执行卡二维码
|
||
</div>
|
||
|
||
<div style="height: 10px"></div>
|
||
|
||
<!-- Submit Area -->
|
||
<div class="submit-area">
|
||
<div class="submit-qty-col">
|
||
<div class="submit-label">数量</div>
|
||
<input class="submit-qty-input" disabled placeholder="" />
|
||
</div>
|
||
<button class="submit-btn disabled">确认</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Finish Button -->
|
||
<div class="finish-btn-area">
|
||
<button class="finish-btn disabled">完成本箱</button>
|
||
</div>
|
||
|
||
<!-- Status Bar -->
|
||
<div class="status-bar">
|
||
<div class="status-dot blue"></div>
|
||
<span class="status-text">等待扫码</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ========== 场景2:已扫码识别 ========== -->
|
||
<div class="phone-wrapper" id="s2" style="display:none;">
|
||
<div class="phone-label">场景2:已扫码,显示信息待确认</div>
|
||
<div class="phone-frame">
|
||
<div class="app-bar">
|
||
<span class="app-bar-title">装箱编号</span>
|
||
<div class="mode-badge">
|
||
<span class="icon">⇄</span>
|
||
多码凑箱
|
||
<span class="shortcut">P2</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="body-content">
|
||
<!-- Box No Panel -->
|
||
<div class="box-no-panel">
|
||
<div class="box-no-row">
|
||
<span class="box-no-label">当前箱号</span>
|
||
<input class="box-no-input" value="1" />
|
||
</div>
|
||
<div class="box-no-divider"></div>
|
||
</div>
|
||
|
||
<!-- Info Row -->
|
||
<div class="info-row">
|
||
<span class="info-row-text">W00009 已有0箱 最大箱号0</span>
|
||
<button class="info-row-btn disabled">详情 →</button>
|
||
</div>
|
||
|
||
<!-- Packed List -->
|
||
<div class="packed-header">已装入本箱(0项)</div>
|
||
<div class="packed-empty">(空)</div>
|
||
|
||
<div class="divider"></div>
|
||
|
||
<!-- Scan Area (scanned) -->
|
||
<div class="scan-area scanned">
|
||
<span class="check-icon">✓</span>
|
||
<span class="scanned-text">26BW0011 WO-2026-001 80件</span>
|
||
</div>
|
||
|
||
<div style="height: 10px"></div>
|
||
|
||
<!-- Submit Area -->
|
||
<div class="submit-area">
|
||
<div class="submit-qty-col">
|
||
<div class="submit-label active">数量</div>
|
||
<input class="submit-qty-input" value="80" />
|
||
</div>
|
||
<button class="submit-btn active">确认</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="finish-btn-area">
|
||
<button class="finish-btn disabled">完成本箱</button>
|
||
</div>
|
||
|
||
<div class="status-bar">
|
||
<div class="status-dot blue"></div>
|
||
<span class="status-text">数量已填入,请确认或修改</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ========== 场景3:已装入多项 ========== -->
|
||
<div class="phone-wrapper" id="s3" style="display:none;">
|
||
<div class="phone-label">场景3:已装入3项,等待继续扫码</div>
|
||
<div class="phone-frame">
|
||
<div class="app-bar">
|
||
<span class="app-bar-title">装箱编号</span>
|
||
<div class="mode-badge">
|
||
<span class="icon">⇄</span>
|
||
多码凑箱
|
||
<span class="shortcut">P2</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="body-content">
|
||
<!-- Box No Panel -->
|
||
<div class="box-no-panel">
|
||
<div class="box-no-row">
|
||
<span class="box-no-label">当前箱号</span>
|
||
<input class="box-no-input" value="1" />
|
||
</div>
|
||
<div class="box-no-divider"></div>
|
||
</div>
|
||
|
||
<!-- Info Row -->
|
||
<div class="info-row">
|
||
<span class="info-row-text">W00009 已有1箱 最大箱号1</span>
|
||
<button class="info-row-btn">详情 →</button>
|
||
</div>
|
||
|
||
<!-- Packed List (3 items) -->
|
||
<div class="packed-header">已装入本箱(3项)</div>
|
||
|
||
<div class="packed-item">
|
||
<div class="packed-item-row">
|
||
<span class="packed-item-info">26BW0011 WO-2026-001</span>
|
||
<span class="packed-item-qty">80/80</span>
|
||
<button class="icon-btn edit" title="修改">✏️</button>
|
||
<button class="icon-btn delete" title="删除">🗑</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="packed-item">
|
||
<div class="packed-item-row">
|
||
<span class="packed-item-info">26BW0012 WO-2026-002</span>
|
||
<span class="packed-item-qty">60/60</span>
|
||
<button class="icon-btn edit" title="修改">✏️</button>
|
||
<button class="icon-btn delete" title="删除">🗑</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="packed-item">
|
||
<div class="packed-item-row">
|
||
<span class="packed-item-info">26BW0013 WO-2026-003</span>
|
||
<span class="packed-item-qty">40/40</span>
|
||
<button class="icon-btn edit" title="修改">✏️</button>
|
||
<button class="icon-btn delete" title="删除">🗑</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="divider"></div>
|
||
|
||
<!-- Scan Area (waiting for next) -->
|
||
<div class="scan-area waiting">
|
||
<span class="scanner-icon">📷</span>
|
||
请扫描执行卡二维码
|
||
</div>
|
||
|
||
<div style="height: 10px"></div>
|
||
|
||
<!-- Submit Area (disabled, waiting) -->
|
||
<div class="submit-area">
|
||
<div class="submit-qty-col">
|
||
<div class="submit-label">数量</div>
|
||
<input class="submit-qty-input" disabled />
|
||
</div>
|
||
<button class="submit-btn disabled">确认</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="finish-btn-area">
|
||
<button class="finish-btn">完成本箱</button>
|
||
</div>
|
||
|
||
<div class="status-bar">
|
||
<div class="status-dot green"></div>
|
||
<span class="status-text">装箱成功,请继续扫码</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ========== 场景4:编辑/删除 ========== -->
|
||
<div class="phone-wrapper" id="s4" style="display:none;">
|
||
<div class="phone-label">场景4:编辑数量 & 删除确认</div>
|
||
<div class="phone-frame">
|
||
<div class="app-bar">
|
||
<span class="app-bar-title">装箱编号</span>
|
||
<div class="mode-badge">
|
||
<span class="icon">⇄</span>
|
||
多码凑箱
|
||
<span class="shortcut">P2</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="body-content">
|
||
<!-- Box No Panel -->
|
||
<div class="box-no-panel">
|
||
<div class="box-no-row">
|
||
<span class="box-no-label">当前箱号</span>
|
||
<input class="box-no-input" value="1" />
|
||
</div>
|
||
<div class="box-no-divider"></div>
|
||
</div>
|
||
|
||
<!-- Info Row -->
|
||
<div class="info-row">
|
||
<span class="info-row-text">W00009 已有1箱 最大箱号1</span>
|
||
<button class="info-row-btn">详情 →</button>
|
||
</div>
|
||
|
||
<!-- Packed List -->
|
||
<div class="packed-header">已装入本箱(3项)</div>
|
||
|
||
<!-- Item being edited -->
|
||
<div class="packed-item editing">
|
||
<div class="packed-item-row">
|
||
<span class="packed-item-info">26BW0011 WO-2026-001</span>
|
||
<input class="packed-item-edit-input" value="75" />
|
||
<button class="icon-btn check" title="保存">✓</button>
|
||
<button class="icon-btn close" title="取消">✕</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Normal item -->
|
||
<div class="packed-item">
|
||
<div class="packed-item-row">
|
||
<span class="packed-item-info">26BW0012 WO-2026-002</span>
|
||
<span class="packed-item-qty">60/60</span>
|
||
<button class="icon-btn edit" title="修改">✏️</button>
|
||
<button class="icon-btn delete" title="删除">🗑</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Item with delete confirmation -->
|
||
<div class="packed-item">
|
||
<div class="packed-item-row">
|
||
<span class="packed-item-info">26BW0013 WO-2026-003</span>
|
||
<span class="packed-item-qty">40/40</span>
|
||
<button class="icon-btn edit" title="修改">✏️</button>
|
||
<button class="icon-btn delete" title="删除">🗑</button>
|
||
</div>
|
||
<div class="delete-confirm">
|
||
<span>确认删除此条记录?</span>
|
||
<button class="delete-confirm-btn yes">是</button>
|
||
<button class="delete-confirm-btn no">否</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="divider"></div>
|
||
|
||
<!-- Scan Area (waiting) -->
|
||
<div class="scan-area waiting">
|
||
<span class="scanner-icon">📷</span>
|
||
请扫描执行卡二维码
|
||
</div>
|
||
|
||
<div style="height: 10px"></div>
|
||
|
||
<div class="submit-area">
|
||
<div class="submit-qty-col">
|
||
<div class="submit-label">数量</div>
|
||
<input class="submit-qty-input" disabled />
|
||
</div>
|
||
<button class="submit-btn disabled">确认</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="finish-btn-area">
|
||
<button class="finish-btn">完成本箱</button>
|
||
</div>
|
||
|
||
<div class="status-bar">
|
||
<div class="status-dot blue"></div>
|
||
<span class="status-text">请继续扫码或完成本箱</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ========== 场景5:排产号切换 ========== -->
|
||
<div class="phone-wrapper" id="s5" style="display:none;">
|
||
<div class="phone-label">场景5:排产号切换时(蓝色提示条)</div>
|
||
<div class="phone-frame">
|
||
<div class="app-bar">
|
||
<span class="app-bar-title">装箱编号</span>
|
||
<div class="mode-badge">
|
||
<span class="icon">⇄</span>
|
||
多码凑箱
|
||
<span class="shortcut">P2</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Blue notice bar -->
|
||
<div class="alert-bar alert-blue">
|
||
排产号已切换:W00010
|
||
</div>
|
||
|
||
<div class="body-content">
|
||
<!-- Box No Panel - box no auto reset to max+1 -->
|
||
<div class="box-no-panel">
|
||
<div class="box-no-row">
|
||
<span class="box-no-label">当前箱号</span>
|
||
<input class="box-no-input" value="2" />
|
||
</div>
|
||
<div class="box-no-divider"></div>
|
||
</div>
|
||
|
||
<!-- Info Row -->
|
||
<div class="info-row">
|
||
<span class="info-row-text">W00010 已有0箱 最大箱号1</span>
|
||
<button class="info-row-btn">详情 →</button>
|
||
</div>
|
||
|
||
<!-- Packed List (cleared) -->
|
||
<div class="packed-header">已装入本箱(0项)</div>
|
||
<div class="packed-empty">(空)</div>
|
||
|
||
<div class="divider"></div>
|
||
|
||
<!-- Scan Area (new scanned code) -->
|
||
<div class="scan-area scanned">
|
||
<span class="check-icon">✓</span>
|
||
<span class="scanned-text">26BW0020 WO-2026-010 100件</span>
|
||
</div>
|
||
|
||
<div style="height: 10px"></div>
|
||
|
||
<div class="submit-area">
|
||
<div class="submit-qty-col">
|
||
<div class="submit-label active">数量</div>
|
||
<input class="submit-qty-input" value="100" />
|
||
</div>
|
||
<button class="submit-btn active">确认</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="finish-btn-area">
|
||
<button class="finish-btn disabled">完成本箱</button>
|
||
</div>
|
||
|
||
<div class="status-bar">
|
||
<div class="status-dot blue"></div>
|
||
<span class="status-text">排产号已切换,箱号已重置</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ========== 场景6:自动同步转运 ========== -->
|
||
<div class="phone-wrapper" id="s6" style="display:none;">
|
||
<div class="phone-label">场景6:从转运同步自动装箱(处理中)</div>
|
||
<div class="phone-frame">
|
||
<div class="app-bar">
|
||
<span class="app-bar-title">装箱编号</span>
|
||
<div class="mode-badge">
|
||
<span class="icon">⇄</span>
|
||
多码凑箱
|
||
<span class="shortcut">P2</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Sync in progress bar -->
|
||
<div class="alert-bar alert-sync">
|
||
<div class="spinner"></div>
|
||
正在同步转运数据...
|
||
</div>
|
||
|
||
<!-- Warning bar -->
|
||
<div class="alert-bar alert-warning">
|
||
总排号 26BW0099 未找到排产号信息,已忽略
|
||
</div>
|
||
|
||
<div class="body-content">
|
||
<!-- Box No Panel -->
|
||
<div class="box-no-panel">
|
||
<div class="box-no-row">
|
||
<span class="box-no-label">当前箱号</span>
|
||
<input class="box-no-input" value="3" />
|
||
</div>
|
||
<div class="box-no-divider"></div>
|
||
</div>
|
||
|
||
<!-- Info Row -->
|
||
<div class="info-row">
|
||
<span class="info-row-text">W00009 已有2箱 最大箱号2</span>
|
||
<button class="info-row-btn">详情 →</button>
|
||
</div>
|
||
|
||
<!-- Packed List (2 items already packed during sync) -->
|
||
<div class="packed-header">已装入本箱(2项)</div>
|
||
|
||
<div class="packed-item">
|
||
<div class="packed-item-row">
|
||
<span class="packed-item-info">26BW0015 WO-2026-005</span>
|
||
<span class="packed-item-qty">30/30</span>
|
||
<button class="icon-btn edit" title="修改">✏️</button>
|
||
<button class="icon-btn delete" title="删除">🗑</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="packed-item">
|
||
<div class="packed-item-row">
|
||
<span class="packed-item-info">26BW0016 WO-2026-006</span>
|
||
<span class="packed-item-qty">50/50</span>
|
||
<button class="icon-btn edit" title="修改">✏️</button>
|
||
<button class="icon-btn delete" title="删除">🗑</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="divider"></div>
|
||
|
||
<!-- Scan Area (waiting) -->
|
||
<div class="scan-area waiting">
|
||
<span class="scanner-icon">📷</span>
|
||
请扫描执行卡二维码
|
||
</div>
|
||
|
||
<div style="height: 10px"></div>
|
||
|
||
<div class="submit-area">
|
||
<div class="submit-qty-col">
|
||
<div class="submit-label">数量</div>
|
||
<input class="submit-qty-input" disabled />
|
||
</div>
|
||
<button class="submit-btn disabled">确认</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="finish-btn-area">
|
||
<button class="finish-btn">完成本箱</button>
|
||
</div>
|
||
|
||
<div class="status-bar">
|
||
<div class="status-dot orange"></div>
|
||
<span class="status-text">正在同步转运数据...</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<!-- 说明文档 -->
|
||
<div class="notes-section">
|
||
|
||
<div class="notes-card">
|
||
<h3>状态栏颜色含义</h3>
|
||
<div class="color-legend">
|
||
<div class="color-legend-item"><div class="color-legend-dot" style="background:#1976d2"></div>蓝色 — 空闲/等待扫码</div>
|
||
<div class="color-legend-item"><div class="color-legend-dot" style="background:#ff9800"></div>橙色 — 提交中/同步中</div>
|
||
<div class="color-legend-item"><div class="color-legend-dot" style="background:#4caf50"></div>绿色 — 操作成功</div>
|
||
<div class="color-legend-item"><div class="color-legend-dot" style="background:#f44336"></div>红色 — 错误(无效码/提交失败)</div>
|
||
<div class="color-legend-item"><div class="color-legend-dot" style="background:#fbc02d"></div>黄色 — 网络异常</div>
|
||
<div class="color-legend-item"><div class="color-legend-dot" style="background:#ff8f00"></div>琥珀 — 警告(重复箱号)</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="notes-card">
|
||
<h3>多码凑箱模式 UI 结构</h3>
|
||
<div class="flow-diagram">┌──────────────────────────────────────────┐
|
||
│ AppBar: "装箱编号" + [多码凑箱] 模式按钮 (P2) │
|
||
├──────────────────────────────────────────┤
|
||
│ [提示条区域] │
|
||
│ - 橙色同步条:正在同步转运数据... │
|
||
│ - 黄色警告条:自动装箱失败的警告信息 │
|
||
│ - 蓝色通知条:排产号已切换 │
|
||
│ - 琥珀警告条:箱号已存在 │
|
||
├──────────────────────────────────────────┤
|
||
│ 当前箱号: [ N ] ← 顶部大号输入框 │
|
||
│ ─────────────────────────────────────── │
|
||
│ W00009 已有3箱 最大箱号3 [详情 →] │
|
||
│ │
|
||
│ 已装入本箱(N项) │
|
||
│ ┌──────────────────────────────────────┐ │
|
||
│ │ 26BW0011 WO-001 80/80 ✏️ 🗑 │ │
|
||
│ │ 26BW0012 WO-002 60/60 ✏️ 🗑 │ │
|
||
│ │ 26BW0013 WO-003 40/40 ✏️ 🗑 │ │
|
||
│ └──────────────────────────────────────┘ │
|
||
│ ─────────────────────────────────────── │
|
||
│ [✓ 26BW0014 WO-004 50件] ← 扫码显示区 │
|
||
│ │
|
||
│ 数量 │
|
||
│ [ 50 ] [确认] │
|
||
├──────────────────────────────────────────┤
|
||
│ [ 完成本箱 ] ← 全宽按钮 │
|
||
├──────────────────────────────────────────┤
|
||
│ ● 等待扫码 / 请继续扫码或完成本箱 │
|
||
└──────────────────────────────────────────┘</div>
|
||
</div>
|
||
|
||
<div class="notes-card">
|
||
<h3>核心业务流程</h3>
|
||
<div class="flow-diagram">多码凑箱流程(_buildManyToOneBody):
|
||
|
||
1. 扫码 → 解析总排号 → 调用 fetchBoxInfo API
|
||
│
|
||
▼
|
||
2. 判断排产号上下文(decideMultiCodePaichanContext)
|
||
├─ firstScan → 自动填入箱号 (maxBoxNo+1)
|
||
├─ samePaichan → 保持当前箱号
|
||
└─ switchedPaichan → 清空已装入列表,重置箱号 (maxBoxNo+1)
|
||
│
|
||
▼
|
||
3. 自动填充剩余数量 → 等待用户确认
|
||
├─ 修改箱号 → 清除编辑/删除状态
|
||
└─ 修改数量 → 实时校验是否超额
|
||
│
|
||
▼
|
||
4. 点击 [确认] → saveBoxRecord API
|
||
├─ 成功 → 添加到 _manyToOnePackedItems
|
||
│ phase 回到 waiting
|
||
│ 状态: "装箱成功,请继续扫码"
|
||
└─ 失败 → 显示错误状态
|
||
│
|
||
▼
|
||
5. 继续扫码 / 完成本箱
|
||
├─ 继续扫码 → 回到步骤1
|
||
└─ 完成本箱 → 清空已装入列表
|
||
箱号 +1,回到初始状态</div>
|
||
</div>
|
||
|
||
<div class="notes-card">
|
||
<h3>与单码装箱模式的差异</h3>
|
||
<ul>
|
||
<li><strong>UI 布局不同:</strong>多码凑箱使用独立的 <code>_buildManyToOneBody</code>,将箱号提升到顶部大号输入;单码装箱使用 <code>_buildScanArea</code> + <code>_buildInfoArea</code> + <code>_buildInputArea</code> 传统布局。</li>
|
||
<li><strong>箱号管理:</strong>多码模式下不检测重复箱号(<code>_boxNoIsDuplicate</code> 对 multiCode 直接返回 false),允许多个总排号装入同一箱。</li>
|
||
<li><strong>底部按钮:</strong>多码模式显示"完成本箱"按钮(OutlinedButton),无已分配列表编辑功能;单码模式显示"返回"按钮和已分配箱的编辑/删除。</li>
|
||
<li><strong>提交成功后行为:</strong>多码模式将结果存入 <code>_manyToOnePackedItems</code> 列表,phase 回到 waiting,继续扫码;单码模式保持在 scanned 阶段,箱号 +1。</li>
|
||
<li><strong>排产号切换感知:</strong>多码模式通过 <code>decideMultiCodePaichanContext</code> 检测排产号变化,切换时清空已装入列表并弹出蓝色通知。</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<div class="notes-card">
|
||
<h3>关键代码引用</h3>
|
||
<ul>
|
||
<li><strong>模式枚举:</strong><code>BoxingMode.multiCode</code> — boxing_page.dart:18</li>
|
||
<li><strong>多码体布局:</strong><code>_buildManyToOneBody()</code> — boxing_page.dart:1656</li>
|
||
<li><strong>箱号面板:</strong><code>_buildManyToOneBoxNoPanel()</code> — boxing_page.dart:1679</li>
|
||
<li><strong>已装入列表:</strong><code>_buildManyToOnePackedList()</code> — boxing_page.dart:1756</li>
|
||
<li><strong>扫码区:</strong><code>_buildManyToOneScanArea()</code> — boxing_page.dart:1898</li>
|
||
<li><strong>提交区:</strong><code>_buildManyToOneSubmitArea()</code> — boxing_page.dart:1944</li>
|
||
<li><strong>排产号上下文:</strong><code>decideMultiCodePaichanContext()</code> — boxing_context.dart:12</li>
|
||
<li><strong>自动填充逻辑:</strong><code>_applyAutoFill()</code> — boxing_page.dart:400</li>
|
||
<li><strong>提交成功回调:</strong><code>_onSubmitSuccess()</code> — boxing_page.dart:637</li>
|
||
<li><strong>完成本箱:</strong><code>_finishManyToOneBox()</code> — boxing_page.dart:821</li>
|
||
<li><strong>状态栏组件:</strong><code>StatusBar</code> — widgets/status_bar.dart:14</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<div class="notes-card">
|
||
<h3>提示条(Alert Bars)触发条件</h3>
|
||
<ul>
|
||
<li><strong style="color:#e65100">橙色同步条</strong>:从转运页自动跳转时,<code>_isAutoProcessing = true</code> 时显示</li>
|
||
<li><strong style="color:#ff8f00">黄色警告条</strong>:自动处理中部分总排号失败时,累积在 <code>_autoWarnings</code> 中显示</li>
|
||
<li><strong style="color:#0d47a1">蓝色通知条</strong>:排产号切换时,<code>_paichanSwitchNotice</code> 非空时显示</li>
|
||
<li><strong style="color:#ff8f00">琥珀重复条</strong>:箱号与已有记录冲突时显示(多码模式下不会触发)</li>
|
||
</ul>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<script>
|
||
function showScenario(id, tab) {
|
||
document.querySelectorAll('.phone-wrapper').forEach(el => el.style.display = 'none');
|
||
document.querySelectorAll('.scenario-tab').forEach(el => el.classList.remove('active'));
|
||
|
||
document.getElementById(id).style.display = '';
|
||
tab.classList.add('active');
|
||
}
|
||
</script>
|
||
|
||
</body>
|
||
</html>
|