Files
pad_scanner/docs/registration-ui-states.html
2026-05-19 15:58:46 +08:00

902 lines
32 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>上架登记模块 - UI 状态全览</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, 'Segoe UI', 'Noto Sans SC', 'Roboto', sans-serif;
background: #eef1f5; height: 100vh; display: flex; overflow: hidden;
}
/* ========== Layout ========== */
.sidebar {
width: 310px; min-width: 310px; background: #fff; border-right: 1px solid #ddd;
overflow-y: auto; padding: 16px; display: flex; flex-direction: column; gap: 14px;
}
.main {
flex: 1; display: flex; align-items: center; justify-content: center; padding: 20px;
}
/* ========== Control Panel ========== */
.group { border: 1px solid #e0e0e0; border-radius: 10px; padding: 12px; background: #fafbfc; }
.group-title {
font-size: 13px; font-weight: 700; color: #333; margin-bottom: 8px;
padding-bottom: 6px; border-bottom: 1px solid #eee;
}
.btn-row { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 6px; }
.btn-row:last-child { margin-bottom: 0; }
.btn {
padding: 5px 10px; font-size: 12px; border: 1px solid #ccc; border-radius: 6px;
background: #fff; cursor: pointer; transition: all .15s; white-space: nowrap;
color: #333;
}
.btn:hover { background: #e8f0fe; border-color: #90b4f0; }
.btn.active { background: #1a73e8; color: #fff; border-color: #1a73e8; }
.btn.active:hover { background: #1565c0; }
.label { font-size: 11px; color: #888; margin-bottom: 4px; margin-top: 4px; }
/* ========== Device Frame ========== */
.device {
width: 400px; height: 720px; border-radius: 18px; overflow: hidden;
box-shadow: 0 6px 30px rgba(0,0,0,.22); background: #fff;
display: flex; flex-direction: column; position: relative;
border: 2px solid #222;
}
/* ========== Flutter UI Simulation ========== */
/* AppBar */
.fl-appbar {
height: 56px; min-height: 56px; background: #1565C0; color: #fff;
display: flex; align-items: center; padding: 0 4px 0 12px; gap: 0;
}
.fl-appbar-title { font-size: 18px; font-weight: 600; white-space: nowrap; }
.fl-appbar-spacer { width: 12px; }
.fl-lock-btn {
flex: 1; height: 36px; border-radius: 8px; display: flex; align-items: center;
justify-content: center; gap: 6px; cursor: pointer; font-size: 15px;
font-weight: 800; color: #fff; border: none; transition: background .2s;
}
.fl-lock-btn.unlocked { background: #1976D2; }
.fl-lock-btn.locked { background: #F57C00; }
.fl-lock-btn .shortcut {
font-size: 12px; font-weight: 700; opacity: .88; margin-left: 4px;
}
.fl-lock-btn .icon { font-size: 16px; }
/* Body */
.fl-body { flex: 1; display: flex; flex-direction: column; overflow: hidden; }
/* Operation Area */
.fl-op-area { padding: 10px 12px 8px; }
.fl-section-title {
font-size: 14px; font-weight: 700; color: #333; margin-bottom: 6px;
}
/* Input Panel - Single */
.fl-input-single {
min-height: 58px; padding: 10px; border-radius: 8px;
display: flex; align-items: center; gap: 0;
transition: border .2s;
}
.fl-input-single.empty { border: 1px solid #BDBDBD; }
.fl-input-single.filled { border: 2px solid #4CAF50; }
.fl-input-single .loc-part {
flex: 5; display: flex; align-items: center; gap: 6px; min-width: 0;
}
.fl-input-single .loc-text {
font-size: 18px; font-weight: 700; color: rgba(0,0,0,.87);
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.fl-input-single .loc-text.placeholder { color: #9E9E9E; }
.fl-input-single .divider {
padding: 0 8px; font-size: 20px; color: #9E9E9E; flex-shrink: 0;
}
.fl-input-single .zp-part {
flex: 3; text-align: right; min-width: 0;
}
.fl-input-single .zp-text {
font-size: 18px; font-weight: 700; color: rgba(0,0,0,.87);
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.fl-input-single .zp-text.placeholder { color: #9E9E9E; }
/* Input Panel - Locked */
.fl-input-locked {
min-height: 82px; padding: 8px 10px; border-radius: 8px;
transition: border .2s;
}
.fl-input-locked.empty { border: 1px solid #BDBDBD; }
.fl-input-locked.filled { border: 2px solid #4CAF50; }
.fl-input-locked .loc-row {
display: flex; align-items: center; gap: 6px; margin-bottom: 8px;
}
.fl-input-locked .loc-text {
flex: 1; font-size: 18px; font-weight: 700; color: rgba(0,0,0,.87);
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.fl-lock-icon { color: #FF9800; font-size: 16px; flex-shrink: 0; }
.fl-chips-area { display: flex; flex-wrap: wrap; gap: 6px; min-height: 30px; }
.fl-chips-area.empty-space { height: 24px; }
/* InputChip */
.fl-chip {
display: inline-flex; align-items: center; gap: 4px;
padding: 2px 4px 2px 10px; background: #E8E8E8; border-radius: 16px;
font-size: 13px; font-weight: 500; color: #333;
}
.fl-chip-del {
width: 18px; height: 18px; border-radius: 50%; border: none;
background: rgba(0,0,0,.12); color: #555; font-size: 11px;
cursor: pointer; display: flex; align-items: center; justify-content: center;
line-height: 1;
}
/* Location Chip */
.fl-loc-chip {
padding: 2px 6px; border-radius: 4px; font-size: 11px; font-weight: 700;
white-space: nowrap; flex-shrink: 0;
}
.fl-loc-chip.normal { background: rgba(33,150,243,.15); color: #2196F3; }
.fl-loc-chip.transit { background: rgba(255,152,0,.15); color: #FF9800; }
/* Submit Button */
.fl-submit {
width: 100%; height: 46px; border-radius: 8px; border: none;
font-size: 17px; font-weight: 500; cursor: pointer;
display: flex; align-items: center; justify-content: center;
margin-top: 10px; transition: background .15s;
}
.fl-submit.disabled { background: #E0E0E0; color: #757575; cursor: default; }
.fl-submit.primary { background: #1976D2; color: #fff; }
.fl-submit.transit { background: #FF9800; color: #fff; }
.fl-submit.submitting { cursor: wait; }
/* Spinner */
.fl-spinner {
width: 22px; height: 22px; border: 2.5px solid rgba(255,255,255,.3);
border-top-color: #fff; border-radius: 50%;
animation: fl-spin .8s linear infinite;
}
.fl-spinner.small {
width: 16px; height: 16px; border-width: 2px;
border-color: rgba(0,0,0,.08); border-top-color: #666;
}
@keyframes fl-spin { to { transform: rotate(360deg); } }
/* Divider */
.fl-divider { height: 1px; background: #E0E0E0; }
/* Overview Section */
.fl-overview { flex: 1; padding: 8px 12px; display: flex; flex-direction: column; overflow: hidden; }
.fl-overview-header-row {
display: flex; align-items: center; gap: 8px; margin-bottom: 6px;
}
.fl-overview-title { font-size: 14px; font-weight: 700; color: #333; flex: 1; }
.fl-overview-body { flex: 1; overflow-y: auto; }
.fl-overview-msg {
display: flex; align-items: center; justify-content: center;
height: 100%; text-align: center; font-size: 13px; color: #757575;
}
.fl-overview-error {
display: flex; flex-direction: column; align-items: center; justify-content: center;
height: 100%; gap: 6px;
}
.fl-overview-error .msg { font-size: 13px; color: #F44336; text-align: center; }
.fl-retry-btn {
padding: 4px 12px; font-size: 13px; border: 1px solid #ccc; border-radius: 6px;
background: #fff; cursor: pointer; color: #1976D2;
}
/* Overview table */
.fl-ov-info { font-size: 13px; font-weight: 600; color: #333; margin-bottom: 6px; }
.fl-ov-error-bar {
padding-bottom: 6px; display: flex; align-items: center; gap: 8px;
}
.fl-ov-error-bar .msg { flex: 1; font-size: 12px; color: #F44336; }
.fl-table-head {
height: 32px; padding: 0 6px; background: #EEEEEE;
border: 1px solid #BDBDBD; display: flex; align-items: center;
}
.fl-table-head span { font-size: 12px; font-weight: 700; color: #333; }
.fl-table-head .c1 { flex: 20; }
.fl-table-head .c2 { flex: 20; }
.fl-table-head .c3 { flex: 14; text-align: center; }
.fl-table-head .c4 { flex: 26; text-align: right; }
.fl-table-row {
min-height: 34px; padding: 6px; display: flex; align-items: center;
border-left: 1px solid #E0E0E0; border-right: 1px solid #E0E0E0;
border-bottom: 1px solid #E0E0E0;
}
.fl-table-row.current {
border-left-color: #4CAF50; border-left-width: 2px;
border-right-color: #4CAF50; border-right-width: 2px;
border-bottom-color: #4CAF50; border-bottom-width: 2px;
}
.fl-table-row.bg-onshelf { background: #E3F2FD; }
.fl-table-row.bg-transferred { background: #FFF3E0; }
.fl-table-row.bg-default { background: #F5F5F5; }
.fl-table-row.bg-white { background: #fff; }
.fl-table-row span {
font-size: 12px; color: rgba(0,0,0,.87); overflow: hidden;
text-overflow: ellipsis; white-space: nowrap;
}
.fl-table-row span.bold { font-weight: 700; }
.fl-table-row .c1 { flex: 20; }
.fl-table-row .c2 { flex: 20; }
.fl-table-row .c3 { flex: 14; text-align: center; }
.fl-table-row .c4 { flex: 26; text-align: right; }
/* Status Bar */
.fl-statusbar {
height: 28px; min-height: 28px; padding: 0 12px;
background: #F5F5F5; display: flex; align-items: center; gap: 7px;
border-top: 1px solid #e0e0e0;
}
.fl-status-dot {
width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0;
}
.fl-status-dot.blue { background: #2196F3; }
.fl-status-dot.orange { background: #FF9800; }
.fl-status-dot.green { background: #4CAF50; }
.fl-status-dot.red { background: #F44336; }
.fl-status-dot.yellow { background: #FBC02D; }
.fl-status-dot.amber { background: #FFC107; }
.fl-status-text { font-size: 11px; color: #333; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; }
/* Dialog overlay */
.fl-dialog-overlay {
position: absolute; inset: 0; background: rgba(0,0,0,.4);
display: flex; align-items: center; justify-content: center; z-index: 100;
}
.fl-dialog-overlay.hidden { display: none; }
.fl-dialog {
background: #FFEBEE; border-radius: 12px; padding: 20px;
width: 320px; box-shadow: 0 8px 30px rgba(0,0,0,.25);
}
.fl-dialog-title {
display: flex; align-items: center; gap: 8px; margin-bottom: 14px;
}
.fl-dialog-title .icon { color: #F44336; font-size: 22px; }
.fl-dialog-title .text { font-size: 18px; font-weight: 600; color: #F44336; }
.fl-dialog-body { font-size: 14px; color: #333; line-height: 1.7; }
.fl-dialog-body strong { font-weight: 700; }
.fl-dialog-actions { margin-top: 16px; text-align: right; }
.fl-dialog-close {
padding: 6px 16px; font-size: 14px; border: none; border-radius: 6px;
background: transparent; color: #1976D2; cursor: pointer; font-weight: 600;
}
</style>
</head>
<body>
<!-- ==================== Sidebar Control Panel ==================== -->
<div class="sidebar">
<div style="font-size:16px;font-weight:700;color:#1565C0;text-align:center;padding-bottom:10px;border-bottom:2px solid #1565C0;">
上架登记 - UI 状态控制面板
</div>
<!-- Lock Mode -->
<div class="group">
<div class="group-title">1. 锁定模式</div>
<div class="btn-row">
<button class="btn active" onclick="setLock(false)">未锁定</button>
<button class="btn" onclick="setLock(true)">已锁定</button>
</div>
</div>
<!-- Location Type -->
<div class="group">
<div class="group-title">2. 货位类型</div>
<div class="btn-row">
<button class="btn active" onclick="setLocation(null)"></button>
<button class="btn" onclick="setLocation('normal')">普通货架 A-01-03</button>
<button class="btn" onclick="setLocation('transit')">转运区域 TRANS-001</button>
</div>
</div>
<!-- Zongpai Count -->
<div class="group">
<div class="group-title">3. 总排号数量</div>
<div class="btn-row">
<button class="btn active" onclick="setZongpai(0)"></button>
<button class="btn" onclick="setZongpai(1)">1 条</button>
<button class="btn" onclick="setZongpai(3)">3 条</button>
</div>
</div>
<!-- Submitting -->
<div class="group">
<div class="group-title">4. 提交中状态</div>
<div class="btn-row">
<button class="btn active" onclick="setSubmitting(false)">空闲</button>
<button class="btn" onclick="setSubmitting(true)">提交中 (Loading)</button>
</div>
</div>
<!-- Overview State -->
<div class="group">
<div class="group-title">5. 排产总览区域</div>
<div class="btn-row">
<button class="btn active" onclick="setOverview('empty')">未扫描</button>
<button class="btn" onclick="setOverview('loading')">加载中</button>
<button class="btn" onclick="setOverview('notFound')">暂无排产信息</button>
<button class="btn" onclick="setOverview('error')">加载失败</button>
</div>
<div class="btn-row">
<button class="btn" onclick="setOverview('loaded')">正常显示</button>
<button class="btn" onclick="setOverview('errorWithData')">失败(有缓存)</button>
</div>
</div>
<!-- Status Bar Override -->
<div class="group">
<div class="group-title">6. 状态栏覆盖状态</div>
<div class="label">点击后状态栏显示对应覆盖文本(持续显示至切换)</div>
<div class="btn-row">
<button class="btn active" onclick="setStatusBarOverride(null)">自动 (跟随基础状态)</button>
</div>
<div class="label">-- 扫描反馈 --</div>
<div class="btn-row">
<button class="btn" onclick="setStatusBarOverride('invalid')">无效码</button>
</div>
<div class="label">-- 配置/操作错误 --</div>
<div class="btn-row">
<button class="btn" onclick="setStatusBarOverride('noApi')">未配置API</button>
<button class="btn" onclick="setStatusBarOverride('noLocation')">请先扫描货位号</button>
<button class="btn" onclick="setStatusBarOverride('paichaMismatch')">排产号不一致</button>
</div>
<div class="label">-- 提交成功 --</div>
<div class="btn-row">
<button class="btn" onclick="setStatusBarOverride('success')">上架成功</button>
<button class="btn" onclick="setStatusBarOverride('successLocked')">锁定成功</button>
<button class="btn" onclick="setStatusBarOverride('offShelf')">下架成功</button>
<button class="btn" onclick="setStatusBarOverride('batchSuccess')">批量上架成功</button>
<button class="btn" onclick="setStatusBarOverride('batchOffShelf')">批量下架成功</button>
</div>
<div class="label">-- 提交失败 --</div>
<div class="btn-row">
<button class="btn" onclick="setStatusBarOverride('duplicate')">重复上架(弹窗)</button>
<button class="btn" onclick="setStatusBarOverride('alreadyOffShelf')">已下架冲突</button>
<button class="btn" onclick="setStatusBarOverride('networkError')">网络异常</button>
<button class="btn" onclick="setStatusBarOverride('submitFail')">提交失败</button>
<button class="btn" onclick="setStatusBarOverride('batchPartial')">批量部分失败</button>
</div>
</div>
<!-- Dialog -->
<div class="group">
<div class="group-title">7. 弹窗</div>
<div class="btn-row">
<button class="btn active" onclick="setDialog(false)">关闭</button>
<button class="btn" onclick="setDialog(true)">重复上架弹窗</button>
</div>
</div>
<!-- State Summary -->
<div class="group" style="background:#eef6ff;">
<div class="group-title" style="color:#1565C0;">当前状态摘要</div>
<div id="stateSummary" style="font-size:11px;color:#555;line-height:1.8;"></div>
</div>
</div>
<!-- ==================== Device Simulation ==================== -->
<div class="main">
<div class="device" id="device">
<!-- AppBar -->
<div class="fl-appbar">
<span class="fl-appbar-title">上架登记</span>
<span class="fl-appbar-spacer"></span>
<button class="fl-lock-btn unlocked" id="lockBtn">
<span class="icon">&#x21C4;</span>
<span id="lockLabel">未锁定</span>
<span class="shortcut">P2</span>
</button>
</div>
<!-- Body -->
<div class="fl-body">
<!-- Operation Area -->
<div class="fl-op-area">
<div class="fl-section-title">登记操作区</div>
<!-- Input Panel: Single Mode -->
<div class="fl-input-single empty" id="singlePanel">
<div class="loc-part">
<span class="loc-text placeholder" id="singleLoc"></span>
<span id="singleLocChip"></span>
</div>
<span class="divider">|</span>
<div class="zp-part">
<span class="zp-text placeholder" id="singleZp"></span>
</div>
</div>
<!-- Input Panel: Locked Mode -->
<div class="fl-input-locked empty" id="lockedPanel" style="display:none;">
<div class="loc-row">
<span class="loc-text" id="lockedLoc"></span>
<span class="fl-lock-icon">&#x1F512;</span>
<span id="lockedLocChip"></span>
</div>
<div class="fl-chips-area empty-space" id="chipArea"></div>
</div>
<!-- Submit Button -->
<button class="fl-submit disabled" id="submitBtn">
<span id="submitText">确 认 上 架</span>
</button>
</div>
<!-- Divider -->
<div class="fl-divider"></div>
<!-- Overview Section -->
<div class="fl-overview">
<div class="fl-overview-header-row">
<span class="fl-overview-title">排产号货架总览</span>
<span id="overviewSpinner" style="display:none;"><span class="fl-spinner small"></span></span>
</div>
<div class="fl-overview-body" id="overviewBody">
<div class="fl-overview-msg">扫描总排号后自动显示排产号上架情况</div>
</div>
</div>
<!-- Status Bar -->
<div class="fl-statusbar">
<span class="fl-status-dot blue" id="statusDot"></span>
<span class="fl-status-text" id="statusText">等待扫描总排号或货位号…</span>
</div>
</div>
<!-- Dialog Overlay -->
<div class="fl-dialog-overlay hidden" id="dialogOverlay">
<div class="fl-dialog">
<div class="fl-dialog-title">
<span class="icon">&#x26A0;</span>
<span class="text">重复上架</span>
</div>
<div class="fl-dialog-body">
<div>总排号:<strong>26B35</strong></div>
<div style="margin-top:4px;">已登记货位:<strong>A-02-05</strong></div>
<div style="margin-top:4px;">登记时间:<strong>2026-05-19 14:30:22</strong></div>
<div style="margin-top:12px;"><strong>请核查实物,确认是否操作错误。</strong></div>
</div>
<div class="fl-dialog-actions">
<button class="fl-dialog-close">关闭</button>
</div>
</div>
</div>
</div>
</div>
<script>
// ===================== State =====================
const state = {
locked: false,
locationType: null, // null | 'normal' | 'transit'
zongpaiCount: 0, // 0, 1, 3
submitting: false,
overviewState: 'empty',
statusOverride: null,
showDialog: false,
};
const sampleData = {
paichaNo: 'R00015',
totalCount: 5,
items: [
{ zongpaiNo: '26B35', workOrderNo: '3-1', quantity: 50, locationCode: 'A-01-03', status: 'on_shelf' },
{ zongpaiNo: '26B36', workOrderNo: '3-2-1', quantity: 100, locationCode: 'A-01-05', status: 'on_shelf' },
{ zongpaiNo: '26B37', workOrderNo: '3-2-2', quantity: 100, locationCode: null, status: 'not_shelved' },
{ zongpaiNo: '26B38', workOrderNo: '3-2-3', quantity: 50, locationCode: 'TRANS-001', status: 'transferred' },
{ zongpaiNo: '26B39', workOrderNo: '3-3', quantity: 45, locationCode: null, status: 'not_shelved' },
]
};
const zongpaiSamples = ['26B35', '26B37', '26B39'];
// ===================== State Setters =====================
function setLock(v) {
state.locked = v;
updateGroup('setLock', v);
updateUI();
}
function setLocation(v) {
state.locationType = v;
updateGroup('setLocation', v);
updateUI();
}
function setZongpai(v) {
state.zongpaiCount = v;
updateGroup('setZongpai', v);
updateUI();
}
function setSubmitting(v) {
state.submitting = v;
updateGroup('setSubmitting', v);
updateUI();
}
function setOverview(v) {
state.overviewState = v;
updateGroup('setOverview', v);
updateUI();
}
const statusOverrideMap = {
invalid: { text: '无效码ABCXYZ123', dot: 'red' },
noApi: { text: '未配置 API 地址,请前往设置', dot: 'red' },
noLocation: { text: '请先扫描货位号', dot: 'red' },
paichaMismatch:{ text: '批量凑箱仅支持同排产号产品,请移除不属于同一批次的总排号', dot: 'red' },
success: { text: '上架成功', dot: 'green' },
successLocked: { text: '货位已锁定,请扫描下一张执行卡', dot: 'green' },
offShelf: { text: '下架成功', dot: 'green' },
batchSuccess: { text: '批量上架成功3 条)', dot: 'green' },
batchOffShelf: { text: '批量下架成功3 条)', dot: 'green' },
duplicate: { text: '请确认信息并提交', dot: 'red', dialog: true },
alreadyOffShelf:{ text: '该总排号已下架至转运区域,不可重新上架', dot: 'red' },
networkError: { text: '网络异常,请检查网络连接', dot: 'yellow' },
submitFail: { text: '请求参数错误', dot: 'red' },
batchPartial: { text: '成功 2 条,失败 1 条', dot: 'red' },
};
function setStatusBarOverride(key) {
state.statusOverride = key;
updateGroup('setStatusBarOverride', key);
// auto show dialog for duplicate
if (key === 'duplicate') {
state.showDialog = true;
updateGroup('setDialog', true);
}
updateUI();
}
function setDialog(v) {
state.showDialog = v;
updateGroup('setDialog', v);
updateUI();
}
// ===================== Button Group Helper =====================
function updateGroup(fnName, activeValue) {
// Find all buttons in the same group by looking at the onclick pattern
const allBtns = document.querySelectorAll('.btn');
allBtns.forEach(btn => {
const onclick = btn.getAttribute('onclick') || '';
if (onclick.includes(fnName + '(')) {
// Extract the argument
const match = onclick.match(fnName + '\\(([^)]+)\\)');
if (match) {
let arg = match[1].trim().replace(/'/g, '').replace(/"/g, '');
let isActive = false;
if (arg === String(activeValue) ||
(activeValue === null && arg === 'null') ||
(activeValue === true && arg === 'true') ||
(activeValue === false && arg === 'false')) {
isActive = true;
}
if (isActive) {
btn.classList.add('active');
} else {
btn.classList.remove('active');
}
}
}
});
}
// ===================== UI Update =====================
function updateUI() {
updateAppBar();
updateInputPanel();
updateSubmitButton();
updateOverview();
updateStatusBar();
updateDialogVisibility();
updateSummary();
}
function updateAppBar() {
const btn = document.getElementById('lockBtn');
const label = document.getElementById('lockLabel');
if (state.locked) {
btn.className = 'fl-lock-btn locked';
label.textContent = '货位锁定';
} else {
btn.className = 'fl-lock-btn unlocked';
label.textContent = '未锁定';
}
}
function updateInputPanel() {
const singlePanel = document.getElementById('singlePanel');
const lockedPanel = document.getElementById('lockedPanel');
const hasLoc = state.locationType !== null;
const hasZp = state.zongpaiCount > 0;
const locCode = state.locationType === 'normal' ? 'A-01-03' : state.locationType === 'transit' ? 'TRANS-001' : null;
const locFilled = hasLoc || hasZp;
if (!state.locked) {
// Single mode
singlePanel.style.display = 'flex';
lockedPanel.style.display = 'none';
singlePanel.className = 'fl-input-single ' + (locFilled ? 'filled' : 'empty');
const singleLoc = document.getElementById('singleLoc');
const singleLocChip = document.getElementById('singleLocChip');
const singleZp = document.getElementById('singleZp');
if (hasLoc) {
singleLoc.textContent = locCode;
singleLoc.className = 'loc-text';
singleLocChip.innerHTML = buildLocChip(state.locationType);
} else {
singleLoc.textContent = '';
singleLoc.className = 'loc-text placeholder';
singleLocChip.innerHTML = '';
}
if (hasZp) {
singleZp.textContent = zongpaiSamples[0];
singleZp.className = 'zp-text';
} else {
singleZp.textContent = '';
singleZp.className = 'zp-text placeholder';
}
} else {
// Locked mode
singlePanel.style.display = 'none';
lockedPanel.style.display = 'block';
lockedPanel.className = 'fl-input-locked ' + (locFilled ? 'filled' : 'empty');
const lockedLoc = document.getElementById('lockedLoc');
const lockedLocChip = document.getElementById('lockedLocChip');
const chipArea = document.getElementById('chipArea');
if (hasLoc) {
lockedLoc.textContent = locCode;
lockedLocChip.innerHTML = buildLocChip(state.locationType);
} else {
lockedLoc.textContent = '';
lockedLocChip.innerHTML = '';
}
if (state.zongpaiCount === 0) {
chipArea.className = 'fl-chips-area empty-space';
chipArea.innerHTML = '';
} else {
chipArea.className = 'fl-chips-area';
let html = '';
for (let i = 0; i < state.zongpaiCount; i++) {
html += `<span class="fl-chip">${zongpaiSamples[i]}<span class="fl-chip-del">x</span></span>`;
}
chipArea.innerHTML = html;
}
}
}
function buildLocChip(type) {
if (!type) return '';
const cls = type === 'transit' ? 'transit' : 'normal';
const label = type === 'transit' ? '转运区域' : '普通货架';
return `<span class="fl-loc-chip ${cls}">${label}</span>`;
}
function updateSubmitButton() {
const btn = document.getElementById('submitBtn');
const textEl = document.getElementById('submitText');
const hasLoc = state.locationType !== null;
const hasZp = state.zongpaiCount > 0;
const isTransit = state.locationType === 'transit';
const canSubmit = hasLoc && hasZp && !state.submitting;
// Determine label
let label;
if (state.submitting) {
label = null; // show spinner
} else if (!canSubmit) {
if (isTransit) {
label = state.locked && state.zongpaiCount > 1
? `批量转运并凑箱(${state.zongpaiCount} 条)`
: '转运并装箱';
} else {
label = state.locked && state.zongpaiCount > 1
? `批量上架(${state.zongpaiCount} 条)`
: '确 认 上 架';
}
} else {
if (isTransit) {
label = state.locked && state.zongpaiCount > 1
? `批量转运并凑箱(${state.zongpaiCount} 条)`
: '转运并装箱';
} else {
label = state.locked && state.zongpaiCount > 1
? `批量上架(${state.zongpaiCount} 条)`
: '确 认 上 架';
}
}
// Determine style
let cls = 'fl-submit';
if (state.submitting) {
cls += isTransit ? ' transit submitting' : (canSubmit || (hasLoc && hasZp) ? ' primary submitting' : ' disabled submitting');
} else if (!canSubmit) {
cls += ' disabled';
} else if (isTransit) {
cls += ' transit';
} else {
cls += ' primary';
}
btn.className = cls;
if (state.submitting) {
textEl.innerHTML = '<span class="fl-spinner"></span>';
} else {
textEl.textContent = label;
}
}
function updateOverview() {
const body = document.getElementById('overviewBody');
const spinner = document.getElementById('overviewSpinner');
switch (state.overviewState) {
case 'empty':
spinner.style.display = 'none';
body.innerHTML = '<div class="fl-overview-msg">扫描总排号后自动显示排产号上架情况</div>';
break;
case 'loading':
spinner.style.display = 'inline-block';
body.innerHTML = '<div class="fl-overview-msg">正在加载排产号上架情况…</div>';
break;
case 'notFound':
spinner.style.display = 'none';
body.innerHTML = '<div class="fl-overview-msg">暂无排产信息</div>';
break;
case 'error':
spinner.style.display = 'none';
body.innerHTML = '<div class="fl-overview-error"><span class="msg">加载失败,点击重试</span><button class="fl-retry-btn">重试</button></div>';
break;
case 'loaded':
spinner.style.display = 'none';
renderOverviewTable(body, false);
break;
case 'errorWithData':
spinner.style.display = 'none';
renderOverviewTable(body, true);
break;
}
}
function renderOverviewTable(container, withError) {
let html = '';
if (withError) {
html += `<div class="fl-ov-error-bar">
<span class="msg">加载失败,点击重试</span>
<button class="fl-retry-btn">重试</button>
</div>`;
}
html += `<div class="fl-ov-info">${sampleData.paichaNo}${sampleData.totalCount} 个总排号</div>`;
html += `<div class="fl-table-head">
<span class="c1">总排号</span>
<span class="c2">工令号</span>
<span class="c3">数量</span>
<span class="c4">货位号</span>
</div>`;
sampleData.items.forEach((item, idx) => {
const isCurrent = idx === 0; // first item is current
let bgCls = 'bg-white';
if (item.status === 'on_shelf') bgCls = 'bg-onshelf';
else if (item.status === 'transferred') bgCls = 'bg-transferred';
else bgCls = 'bg-default';
const rowCls = `fl-table-row ${bgCls}` + (isCurrent ? ' current' : '');
const boldCls = isCurrent ? ' bold' : '';
html += `<div class="${rowCls}">
<span class="c1${boldCls}">${item.zongpaiNo}</span>
<span class="c2${boldCls}">${item.workOrderNo || '--'}</span>
<span class="c3${boldCls}">${item.quantity}</span>
<span class="c4${boldCls}">${item.locationCode || '\u2014'}</span>
</div>`;
});
container.innerHTML = html;
}
function updateStatusBar() {
const dot = document.getElementById('statusDot');
const text = document.getElementById('statusText');
// Check override first
if (state.statusOverride && statusOverrideMap[state.statusOverride]) {
const ov = statusOverrideMap[state.statusOverride];
dot.className = 'fl-status-dot ' + ov.dot;
text.textContent = ov.text;
return;
}
// Base status (mirrors _updateBaseStatus)
if (state.submitting) {
dot.className = 'fl-status-dot orange';
text.textContent = '正在提交…';
return;
}
if (state.locked && state.locationType !== null && state.zongpaiCount === 0) {
dot.className = 'fl-status-dot blue';
text.textContent = '货位已锁定,请扫描下一张执行卡';
return;
}
const hasZp = state.zongpaiCount > 0;
const hasLoc = state.locationType !== null;
if (hasZp && hasLoc) {
dot.className = 'fl-status-dot blue';
text.textContent = '请确认信息并提交';
} else if (hasZp) {
dot.className = 'fl-status-dot blue';
text.textContent = '请扫描目标货位号';
} else if (hasLoc) {
dot.className = 'fl-status-dot blue';
text.textContent = '请扫描执行卡';
} else {
dot.className = 'fl-status-dot blue';
text.textContent = '等待扫描总排号或货位号…';
}
}
function updateDialogVisibility() {
const overlay = document.getElementById('dialogOverlay');
if (state.showDialog) {
overlay.classList.remove('hidden');
} else {
overlay.classList.add('hidden');
}
}
function updateSummary() {
const el = document.getElementById('stateSummary');
const locLabel = state.locationType === 'normal' ? '普通货架' : state.locationType === 'transit' ? '转运区域' : '无';
const ovLabels = {
empty: '未扫描', loading: '加载中', notFound: '暂无信息',
error: '加载失败', loaded: '正常显示', errorWithData: '失败(有缓存)'
};
const hasLoc = state.locationType !== null;
const hasZp = state.zongpaiCount > 0;
const canSubmit = hasLoc && hasZp && !state.submitting;
el.innerHTML = `
<div><b>锁定:</b>${state.locked ? '已锁定' : '未锁定'}</div>
<div><b>货位:</b>${locLabel}</div>
<div><b>总排号:</b>${state.zongpaiCount === 0 ? '无' : state.zongpaiCount + ' 条'}</div>
<div><b>提交中:</b>${state.submitting ? '是' : '否'}</div>
<div><b>可提交:</b>${canSubmit ? '是' : '否'}</div>
<div><b>总览:</b>${ovLabels[state.overviewState]}</div>
<div><b>状态覆盖:</b>${state.statusOverride || '(自动)'}</div>
<div><b>弹窗:</b>${state.showDialog ? '显示中' : '无'}</div>
`;
}
// Close dialog via button
document.querySelector('.fl-dialog-close').addEventListener('click', () => {
setDialog(false);
});
// Initial render
updateUI();
</script>
</body>
</html>