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

483 lines
21 KiB
HTML
Raw 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: 280px; min-width: 280px; background: #fff; border-right: 1px solid #ddd;
overflow-y: auto; padding: 14px; display: flex; flex-direction: column; gap: 12px;
}
.main {
flex: 1; display: flex; align-items: center; justify-content: center; padding: 20px; gap: 40px;
}
/* ========== Control Panel ========== */
.group { border: 1px solid #e0e0e0; border-radius: 8px; padding: 10px; background: #fafbfc; }
.group-title { font-size: 12px; font-weight: 700; color: #333; margin-bottom: 6px; padding-bottom: 5px; border-bottom: 1px solid #eee; }
.btn-row { display: flex; flex-wrap: wrap; gap: 5px; margin-bottom: 5px; }
.btn-row:last-child { margin-bottom: 0; }
.btn { padding: 4px 9px; font-size: 11px; border: 1px solid #ccc; border-radius: 5px; 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; }
/* ========== Device Frame ========== */
.device {
width: 400px; height: 700px; border-radius: 16px; 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;
}
/* ========== AppBar ========== */
.appbar { height: 44px; min-height: 44px; background: #1565C0; color: #fff; display: flex; align-items: center; padding: 0 4px 0 12px; }
.appbar-title { font-size: 17px; font-weight: 600; white-space: nowrap; }
.appbar-gap { width: 8px; }
.mode-pill { flex: 1; height: 32px; border-radius: 8px; display: flex; align-items: center; justify-content: center; gap: 5px; font-size: 14px; font-weight: 800; color: #fff; border: none; transition: background .2s; }
.mode-pill.unlocked { background: #1976D2; }
.mode-pill.locked { background: #F57C00; }
.mode-pill .icon { font-size: 15px; }
.mode-pill .shortcut { font-size: 11px; font-weight: 700; opacity: .85; margin-left: 3px; }
/* ========== Body ========== */
.body { flex: 1; display: flex; flex-direction: column; overflow: hidden; }
/* ========== Paicha Header ========== */
.paicha-header { padding: 8px 12px 6px; background: #FAFAFA; border-bottom: 1px solid #E0E0E0; display: flex; align-items: center; gap: 12px; }
.paicha-header .left { flex-shrink: 0; }
.paicha-header .paicha-no { font-size: 24px; font-weight: 800; color: rgba(0,0,0,.87); }
.paicha-header .paicha-no.placeholder { color: #BDBDBD; font-weight: 700; }
.paicha-header .right { flex: 1; min-width: 0; display: flex; flex-direction: column; align-items: flex-end; gap: 3px; }
.loc-info { display: flex; align-items: center; gap: 5px; font-size: 12px; color: #757575; }
.loc-info .loc-code { font-weight: 600; color: #555; }
.loc-chip { padding: 1px 6px; border-radius: 4px; font-size: 10px; font-weight: 700; white-space: nowrap; }
.loc-chip.normal { background: rgba(33,150,243,.12); color: #1976D2; }
.loc-chip.transit { background: rgba(255,152,0,.12); color: #E65100; }
.stats { display: flex; align-items: center; gap: 5px; justify-content: flex-end; flex-wrap: wrap; }
.stat-tag { padding: 1px 7px; border-radius: 10px; font-size: 10px; font-weight: 700; white-space: nowrap; }
.stat-tag.total { background: #E3F2FD; color: #1565C0; }
.stat-tag.shelved { background: #E8F5E9; color: #2E7D32; }
.stat-tag.transferred { background: #FFF3E0; color: #E65100; }
.stat-tag.pending { background: #F5F5F5; color: #757575; }
.stat-tag.placeholder { background: #F5F5F5; color: #BDBDBD; }
/* ========== Table Head ========== */
.table-head { height: 28px; padding: 0 12px; background: #EEEEEE; border-bottom: 1px solid #BDBDBD; display: flex; align-items: center; }
.table-head span { font-size: 10px; font-weight: 700; color: #666; }
.table-head .th1 { flex: 22; }
.table-head .th2 { flex: 18; }
.table-head .th3 { flex: 12; text-align: center; }
.table-head .th4 { flex: 28; text-align: right; }
/* ========== List Body ========== */
.list-body { flex: 1; overflow-y: auto; }
/* Table rows */
.table-row { height: 36px; padding: 0 12px; display: flex; align-items: center; border-bottom: 1px solid #EEEEEE; position: relative; }
.table-row .color-bar { position: absolute; left: 0; top: 4px; bottom: 4px; width: 3px; border-radius: 2px; }
.table-row .cell { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.table-row .cell.c1 { flex: 22; font-size: 12px; font-weight: 600; color: rgba(0,0,0,.87); }
.table-row .cell.c2 { flex: 18; font-size: 12px; color: rgba(0,0,0,.6); }
.table-row .cell.c3 { flex: 12; font-size: 12px; color: rgba(0,0,0,.6); text-align: center; }
.table-row .cell.c4 { flex: 28; font-size: 12px; font-weight: 600; color: rgba(0,0,0,.87); text-align: right; }
/* Scanned row — always green indicator, regardless of underlying status */
.table-row.scanned { background: #F1F8E9; }
.table-row.scanned .color-bar { background: #43A047; }
.table-row.scanned .cell.c1 { font-weight: 700; }
/* Unscanned rows — status-based */
.table-row.on_shelf { background: #fff; }
.table-row.on_shelf .color-bar { background: #2196F3; }
.table-row.transferred { background: #FFF8E1; }
.table-row.transferred .color-bar { background: #FF9800; }
.table-row.not_shelved { background: #FAFAFA; }
.table-row.not_shelved .color-bar { background: #BDBDBD; }
/* Section divider */
.section-divider {
display: flex; align-items: center; gap: 8px;
padding: 0 12px; height: 24px;
border-bottom: 1px solid #EEEEEE;
}
.section-divider .line { flex: 1; height: 0; border-top: 1px dashed #CCC; }
.section-divider .label { font-size: 10px; font-weight: 600; color: #9E9E9E; white-space: nowrap; letter-spacing: .3px; }
.overview-msg { display: flex; align-items: center; justify-content: center; font-size: 13px; color: #9E9E9E; font-style: italic; height: 100%; }
/* ========== Bottom Bar ========== */
.bottom-bar { min-height: 50px; padding: 8px 12px; background: #fff; border-top: 1px solid #E0E0E0; display: flex; align-items: center; gap: 8px; }
.submit-btn { flex: 1; height: 34px; border-radius: 6px; border: none; font-size: 14px; font-weight: 700; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: background .15s; }
.submit-btn.disabled { background: #E0E0E0; color: #9E9E9E; cursor: default; }
.submit-btn.primary { background: #1976D2; color: #fff; }
.submit-btn.transit { background: #FF9800; color: #fff; }
.submit-btn .spinner { width: 18px; height: 18px; border: 2px solid rgba(255,255,255,.3); border-top-color: #fff; border-radius: 50%; animation: spin .8s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }
/* ========== Status Bar ========== */
.status-bar { height: 28px; min-height: 28px; padding: 0 12px; background: #F5F5F5; display: flex; align-items: center; gap: 7px; border-top: 1px solid #E0E0E0; }
.status-dot { width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0; }
.status-dot.blue { background: #2196F3; }
.status-dot.orange { background: #FF9800; }
.status-dot.green { background: #4CAF50; }
.status-dot.red { background: #F44336; }
.status-dot.yellow { background: #FBC02D; }
.status-text { font-size: 11px; color: #333; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; }
/* ========== Dialog ========== */
.dialog-overlay { position: absolute; inset: 0; background: rgba(0,0,0,.4); display: flex; align-items: center; justify-content: center; z-index: 100; }
.dialog-overlay.hidden { display: none; }
.dialog { background: #FFEBEE; border-radius: 12px; padding: 20px; width: 300px; box-shadow: 0 8px 30px rgba(0,0,0,.25); }
.dialog-title { display: flex; align-items: center; gap: 8px; margin-bottom: 12px; }
.dialog-title .icon { color: #F44336; font-size: 20px; }
.dialog-title .text { font-size: 17px; font-weight: 600; color: #F44336; }
.dialog-body { font-size: 13px; color: #333; line-height: 1.7; }
.dialog-body strong { font-weight: 700; }
.dialog-actions { margin-top: 14px; text-align: right; }
.dialog-close { padding: 5px 14px; font-size: 13px; border: none; border-radius: 5px; background: transparent; color: #1976D2; cursor: pointer; font-weight: 600; }
</style>
</head>
<body>
<!-- ==================== Sidebar ==================== -->
<div class="sidebar">
<div style="font-size:15px;font-weight:700;color:#1565C0;text-align:center;padding-bottom:8px;border-bottom:2px solid #1565C0;">
上架登记 - 重构预览控制
</div>
<div class="group">
<div class="group-title">1. 锁定模式</div>
<div class="btn-row">
<button class="btn active" onclick="setState('locked',false)">未锁定</button>
<button class="btn" onclick="setState('locked',true)">已锁定</button>
</div>
</div>
<div class="group">
<div class="group-title">2. 货位</div>
<div class="btn-row">
<button class="btn active" onclick="setState('location',null)"></button>
<button class="btn" onclick="setState('location','normal')">普通 A-01-03</button>
<button class="btn" onclick="setState('location','transit')">转运 TRANS-001</button>
</div>
</div>
<div class="group">
<div class="group-title">3. 总排号</div>
<div class="btn-row">
<button class="btn active" onclick="setState('zongpai',0)"></button>
<button class="btn" onclick="setState('zongpai',1)">1 条</button>
<button class="btn" onclick="setState('zongpai',3)">3 条</button>
</div>
</div>
<div class="group">
<div class="group-title">4. 提交中</div>
<div class="btn-row">
<button class="btn active" onclick="setState('submitting',false)">空闲</button>
<button class="btn" onclick="setState('submitting',true)">Loading</button>
</div>
</div>
<div class="group">
<div class="group-title">5. 总览</div>
<div class="btn-row">
<button class="btn active" onclick="setState('overview','empty')">未扫描</button>
<button class="btn" onclick="setState('overview','loaded')">正常</button>
</div>
</div>
<div class="group">
<div class="group-title">6. 状态栏</div>
<div class="btn-row">
<button class="btn active" onclick="setState('status','auto')">自动</button>
<button class="btn" onclick="setState('status','success')">上架成功</button>
<button class="btn" onclick="setState('status','offShelf')">下架成功</button>
<button class="btn" onclick="setState('status','batch')">批量成功(3条)</button>
</div>
<div class="btn-row">
<button class="btn" onclick="setState('status','invalid')">无效码</button>
<button class="btn" onclick="setState('status','network')">网络异常</button>
<button class="btn" onclick="setState('status','fail')">提交失败</button>
<button class="btn" onclick="setState('status','duplicate')">重复(弹窗)</button>
</div>
</div>
</div>
<!-- ==================== Device ==================== -->
<div class="main">
<div class="device" id="device">
<!-- AppBar -->
<div class="appbar">
<span class="appbar-title">上架登记</span>
<span class="appbar-gap"></span>
<button class="mode-pill unlocked" id="modePill">
<span class="icon">&#x21C4;</span>
<span id="modeLabel">未锁定</span>
<span class="shortcut">P2</span>
</button>
</div>
<!-- Body -->
<div class="body">
<!-- Paicha Header -->
<div class="paicha-header" id="paichaHeader">
<div class="left">
<span class="paicha-no placeholder" id="paichaNo">--</span>
</div>
<div class="right">
<span class="loc-info" id="locInfo" style="display:none;"></span>
<span class="stats" id="paichaStats"></span>
</div>
</div>
<!-- Table Head -->
<div class="table-head" id="tableHead" style="display:none;">
<span class="th1">总排号</span>
<span class="th2">工令号</span>
<span class="th3">数量</span>
<span class="th4">货位号</span>
</div>
<!-- List Body -->
<div class="list-body" id="listBody">
<div class="overview-msg">扫描总排号后自动显示排产号上架情况</div>
</div>
<!-- Bottom Bar -->
<div class="bottom-bar">
<button class="submit-btn disabled" id="submitBtn">
<span id="submitText">确 认 上 架</span>
</button>
</div>
<!-- Status Bar -->
<div class="status-bar">
<span class="status-dot blue" id="statusDot"></span>
<span class="status-text" id="statusTextEl">等待扫描总排号或货位号…</span>
</div>
</div>
<!-- Dialog -->
<div class="dialog-overlay hidden" id="dialogOverlay">
<div class="dialog">
<div class="dialog-title"><span class="icon">&#x26A0;</span><span class="text">重复上架</span></div>
<div class="dialog-body">
<div>总排号:<strong>26B35</strong></div>
<div style="margin-top:3px;">已登记货位:<strong>A-02-05</strong></div>
<div style="margin-top:3px;">登记时间:<strong>2026-05-19 14:30:22</strong></div>
<div style="margin-top:10px;font-weight:700;">请核查实物,确认是否操作错误。</div>
</div>
<div class="dialog-actions"><button class="dialog-close">关闭</button></div>
</div>
</div>
</div>
</div>
<script>
const S = { locked: false, location: null, zongpai: 0, submitting: false, overview: 'empty', status: 'auto' };
const DATA = {
paichaNo: 'R00015', totalCount: 5, shelvedCount: 2, transferredCount: 1, pendingCount: 2,
items: [
{ zp: '26B35', wo: '3-1', qty: 50, loc: 'A-01-03', status: 'on_shelf' },
{ zp: '26B36', wo: '3-2-1', qty: 100, loc: 'A-01-05', status: 'on_shelf' },
{ zp: '26B37', wo: '3-2-2', qty: 100, loc: null, status: 'not_shelved' },
{ zp: '26B38', wo: '3-2-3', qty: 50, loc: 'TRANS-001', status: 'transferred' },
{ zp: '26B39', wo: '3-3', qty: 45, loc: null, status: 'not_shelved' },
]
};
// scanned items are identified by index: when zongpai=1 → [0], zongpai=3 → [0,2,4]
const SCANNED_INDICES = { 1: [0], 3: [0, 2, 4] };
const STATUS_MAP = {
success: { dot: 'green', text: '上架成功' },
offShelf: { dot: 'green', text: '下架成功' },
batch: { dot: 'green', text: '批量上架成功3 条)' },
invalid: { dot: 'red', text: '无效码ABCXYZ123' },
network: { dot: 'yellow', text: '网络异常,请检查网络连接' },
fail: { dot: 'red', text: '请求参数错误' },
duplicate: { dot: 'red', text: '请确认信息并提交' },
};
function setState(key, val) {
S[key] = val;
document.querySelectorAll('.btn').forEach(btn => {
const m = btn.getAttribute('onclick')?.match(/setState\('(\w+)',\s*(.+)\)/);
if (!m) return;
btn.classList.toggle('active', String(val) === m[2].replace(/'/g, '') && m[1] === key);
});
render();
}
function render() {
renderAppBar();
renderPaichaHeader();
renderList();
renderBottomBar();
renderStatusBar();
renderDialog();
}
function renderAppBar() {
const pill = document.getElementById('modePill');
const label = document.getElementById('modeLabel');
pill.className = 'mode-pill ' + (S.locked ? 'locked' : 'unlocked');
label.textContent = S.locked ? '货位锁定' : '未锁定';
}
function renderPaichaHeader() {
const no = document.getElementById('paichaNo');
const locInfo = document.getElementById('locInfo');
const stats = document.getElementById('paichaStats');
const hasLoc = S.location !== null;
const locCode = S.location === 'normal' ? 'A-01-03' : S.location === 'transit' ? 'TRANS-001' : null;
// Paicha number (left)
if (S.overview === 'loaded') {
no.textContent = DATA.paichaNo; no.className = 'paicha-no';
} else {
no.textContent = '--'; no.className = 'paicha-no placeholder';
}
// Location info (right, top row)
if (hasLoc) {
locInfo.style.display = 'flex';
locInfo.innerHTML = `<span class="loc-code">${locCode}</span><span class="loc-chip ${S.location}">${S.location === 'transit' ? '转运区域' : '普通货架'}</span>`;
} else {
locInfo.style.display = 'none';
}
// Stats tags (right, bottom row)
if (S.overview === 'loaded') {
stats.innerHTML =
`<span class="stat-tag total">共 ${DATA.totalCount}</span>` +
`<span class="stat-tag shelved">上架 ${DATA.shelvedCount}</span>` +
`<span class="stat-tag transferred">转运 ${DATA.transferredCount}</span>` +
`<span class="stat-tag pending">待上架 ${DATA.pendingCount}</span>`;
} else {
stats.innerHTML =
`<span class="stat-tag placeholder">共 -</span>` +
`<span class="stat-tag placeholder">上架 -</span>` +
`<span class="stat-tag placeholder">转运 -</span>` +
`<span class="stat-tag placeholder">待上架 -</span>`;
}
}
function renderList() {
const tableHead = document.getElementById('tableHead');
const body = document.getElementById('listBody');
if (S.overview !== 'loaded') {
tableHead.style.display = 'none';
body.innerHTML = '<div class="overview-msg">扫描总排号后自动显示排产号上架情况</div>';
return;
}
tableHead.style.display = 'flex';
// Determine which items are scanned
const scannedSet = new Set(S.zongpai > 0 ? (SCANNED_INDICES[S.zongpai] || []) : []);
// Split into scanned and unscanned groups
const scanned = [], unscanned = [];
DATA.items.forEach((item, idx) => {
if (scannedSet.has(idx)) scanned.push({ ...item, origIdx: idx });
else unscanned.push({ ...item, origIdx: idx });
});
let html = '';
// Scanned items (always at top)
scanned.forEach(item => {
html += rowHtml(item, true);
});
// Divider between groups (only when both groups exist)
if (scanned.length > 0 && unscanned.length > 0) {
html += `<div class="section-divider">
<span class="line"></span>
<span class="label">以下 ${unscanned.length} 项未操作</span>
<span class="line"></span>
</div>`;
}
// Unscanned items
unscanned.forEach(item => {
html += rowHtml(item, false);
});
body.innerHTML = html;
}
function rowHtml(item, isScanned) {
const cls = isScanned ? 'scanned' : item.status;
return `<div class="table-row ${cls}">
<div class="color-bar"></div>
<span class="cell c1" style="padding-left:10px;">${item.zp}</span>
<span class="cell c2">${item.wo}</span>
<span class="cell c3">${item.qty}</span>
<span class="cell c4">${item.loc || '\u2014'}</span>
</div>`;
}
function renderBottomBar() {
const btn = document.getElementById('submitBtn');
const text = document.getElementById('submitText');
const hasLoc = S.location !== null, hasZp = S.zongpai > 0;
const isTransit = S.location === 'transit';
const canSubmit = hasLoc && hasZp && !S.submitting;
let label;
if (S.submitting) { label = null; }
else if (isTransit) { label = S.locked && S.zongpai > 1 ? `批量转运并凑箱(${S.zongpai} 条)` : '转运并装箱'; }
else { label = S.locked && S.zongpai > 1 ? `批量上架(${S.zongpai} 条)` : '确 认 上 架'; }
if (S.submitting) {
btn.className = 'submit-btn ' + (isTransit ? 'transit' : (canSubmit ? 'primary' : 'disabled'));
text.innerHTML = '<span class="spinner"></span>';
} else if (!canSubmit) {
btn.className = 'submit-btn disabled'; text.textContent = label;
} else {
btn.className = 'submit-btn ' + (isTransit ? 'transit' : 'primary'); text.textContent = label;
}
}
function renderStatusBar() {
const dot = document.getElementById('statusDot');
const text = document.getElementById('statusTextEl');
if (S.status !== 'auto' && STATUS_MAP[S.status]) {
const o = STATUS_MAP[S.status]; dot.className = 'status-dot ' + o.dot; text.textContent = o.text; return;
}
if (S.submitting) { dot.className = 'status-dot orange'; text.textContent = '正在提交…'; return; }
const hasLoc = S.location !== null, hasZp = S.zongpai > 0;
if (S.locked && hasLoc && !hasZp) { dot.className = 'status-dot blue'; text.textContent = '货位已锁定,请扫描下一张执行卡'; }
else if (hasLoc && hasZp) { dot.className = 'status-dot blue'; text.textContent = '请确认信息并提交'; }
else if (hasZp) { dot.className = 'status-dot blue'; text.textContent = '请扫描目标货位号'; }
else if (hasLoc) { dot.className = 'status-dot blue'; text.textContent = '请扫描执行卡'; }
else { dot.className = 'status-dot blue'; text.textContent = '等待扫描总排号或货位号…'; }
}
function renderDialog() {
document.getElementById('dialogOverlay').classList.toggle('hidden', S.status !== 'duplicate');
}
document.querySelector('.dialog-close').addEventListener('click', () => setState('status', 'auto'));
render();
</script>
</body>
</html>