add registration UI redesign preview and state documentation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
901
docs/registration-ui-states.html
Normal file
901
docs/registration-ui-states.html
Normal file
@@ -0,0 +1,901 @@
|
||||
<!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">⇄</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">🔒</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">⚠</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>
|
||||
Reference in New Issue
Block a user