|
|
<!DOCTYPE html>
|
|
|
<html lang="zh-CN">
|
|
|
<head>
|
|
|
<meta charset="UTF-8">
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
<title>盘点/随行工具</title>
|
|
|
<style>
|
|
|
* {
|
|
|
margin: 0;
|
|
|
padding: 0;
|
|
|
box-sizing: border-box;
|
|
|
}
|
|
|
body {
|
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
|
padding: 40px;
|
|
|
background: #f5f5f5;
|
|
|
min-height: 100vh;
|
|
|
}
|
|
|
.container {
|
|
|
max-width: 900px;
|
|
|
margin: 0 auto;
|
|
|
background: white;
|
|
|
padding: 30px;
|
|
|
border-radius: 12px;
|
|
|
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
|
|
}
|
|
|
|
|
|
.form-row {
|
|
|
display: grid;
|
|
|
grid-template-columns: 1fr 1fr;
|
|
|
gap: 20px;
|
|
|
}
|
|
|
h1 {
|
|
|
text-align: center;
|
|
|
margin-bottom: 25px;
|
|
|
color: #333;
|
|
|
font-size: 24px;
|
|
|
}
|
|
|
.section {
|
|
|
margin-bottom: 25px;
|
|
|
padding: 20px;
|
|
|
background: #fafafa;
|
|
|
border-radius: 8px;
|
|
|
border: 1px solid #e8e8e8;
|
|
|
}
|
|
|
.section-title {
|
|
|
font-size: 16px;
|
|
|
font-weight: 600;
|
|
|
color: #333;
|
|
|
margin-bottom: 15px;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
}
|
|
|
.section-title .icon {
|
|
|
margin-right: 8px;
|
|
|
font-size: 18px;
|
|
|
}
|
|
|
.url-list {
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
gap: 12px;
|
|
|
}
|
|
|
.url-item {
|
|
|
display: grid;
|
|
|
grid-template-columns: 1fr 1.5fr;
|
|
|
gap: 10px;
|
|
|
}
|
|
|
.url-item input {
|
|
|
padding: 8px 12px;
|
|
|
border: 1px solid #ddd;
|
|
|
border-radius: 4px;
|
|
|
font-size: 14px;
|
|
|
outline: none;
|
|
|
}
|
|
|
.url-item input:focus {
|
|
|
border-color: #1890ff;
|
|
|
}
|
|
|
.form-group {
|
|
|
margin-bottom: 20px;
|
|
|
}
|
|
|
label {
|
|
|
display: block;
|
|
|
margin-bottom: 8px;
|
|
|
color: #555;
|
|
|
font-weight: 500;
|
|
|
}
|
|
|
input {
|
|
|
width: 100%;
|
|
|
padding: 12px 16px;
|
|
|
border: 1px solid #ddd;
|
|
|
border-radius: 6px;
|
|
|
font-size: 16px;
|
|
|
outline: none;
|
|
|
transition: border-color 0.2s;
|
|
|
}
|
|
|
input:focus {
|
|
|
border-color: #1890ff;
|
|
|
}
|
|
|
.switch-container {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: space-between;
|
|
|
margin-top: 20px;
|
|
|
padding: 15px;
|
|
|
background: #fff7e6;
|
|
|
border: 1px solid #ffd591;
|
|
|
border-radius: 6px;
|
|
|
}
|
|
|
.switch-label {
|
|
|
font-weight: 500;
|
|
|
color: #333;
|
|
|
}
|
|
|
.switch {
|
|
|
position: relative;
|
|
|
width: 50px;
|
|
|
height: 26px;
|
|
|
}
|
|
|
.switch input {
|
|
|
opacity: 0;
|
|
|
width: 0;
|
|
|
height: 0;
|
|
|
}
|
|
|
.slider {
|
|
|
position: absolute;
|
|
|
cursor: pointer;
|
|
|
top: 0;
|
|
|
left: 0;
|
|
|
right: 0;
|
|
|
bottom: 0;
|
|
|
background-color: #ccc;
|
|
|
transition: .3s;
|
|
|
border-radius: 26px;
|
|
|
}
|
|
|
.slider:before {
|
|
|
position: absolute;
|
|
|
content: "";
|
|
|
height: 20px;
|
|
|
width: 20px;
|
|
|
left: 3px;
|
|
|
bottom: 3px;
|
|
|
background-color: white;
|
|
|
transition: .3s;
|
|
|
border-radius: 50%;
|
|
|
}
|
|
|
input:checked + .slider {
|
|
|
background-color: #1890ff;
|
|
|
}
|
|
|
input:checked + .slider:before {
|
|
|
transform: translateX(24px);
|
|
|
}
|
|
|
.button-group {
|
|
|
display: flex;
|
|
|
gap: 12px;
|
|
|
margin-top: 25px;
|
|
|
}
|
|
|
button {
|
|
|
flex: 1;
|
|
|
padding: 12px 24px;
|
|
|
font-size: 16px;
|
|
|
border: none;
|
|
|
border-radius: 6px;
|
|
|
cursor: pointer;
|
|
|
transition: all 0.3s;
|
|
|
font-weight: 500;
|
|
|
}
|
|
|
button:disabled {
|
|
|
cursor: not-allowed;
|
|
|
opacity: 0.6;
|
|
|
}
|
|
|
.btn-check {
|
|
|
background: #1890ff;
|
|
|
color: white;
|
|
|
}
|
|
|
.btn-check:hover:not(:disabled) {
|
|
|
background: #40a9ff;
|
|
|
}
|
|
|
.btn-check:disabled {
|
|
|
background: #69c0ff;
|
|
|
}
|
|
|
.btn-follow {
|
|
|
background: #52c41a;
|
|
|
color: white;
|
|
|
}
|
|
|
.btn-follow:hover:not(:disabled) {
|
|
|
background: #73d13d;
|
|
|
}
|
|
|
.btn-follow:disabled {
|
|
|
background: #95de64;
|
|
|
}
|
|
|
.status {
|
|
|
margin-top: 20px;
|
|
|
padding: 12px 16px;
|
|
|
background: #f0f2f5;
|
|
|
border-radius: 6px;
|
|
|
color: #666;
|
|
|
font-size: 14px;
|
|
|
min-height: 40px;
|
|
|
}
|
|
|
.progress {
|
|
|
margin-top: 15px;
|
|
|
height: 6px;
|
|
|
background: #f0f0f0;
|
|
|
border-radius: 3px;
|
|
|
overflow: hidden;
|
|
|
}
|
|
|
.progress-bar {
|
|
|
height: 100%;
|
|
|
background: #1890ff;
|
|
|
width: 0%;
|
|
|
transition: width 0.3s;
|
|
|
}
|
|
|
.log-container {
|
|
|
margin-top: 20px;
|
|
|
max-height: 300px;
|
|
|
overflow-y: auto;
|
|
|
background: #1f1f1f;
|
|
|
border-radius: 6px;
|
|
|
padding: 15px;
|
|
|
font-family: 'Courier New', monospace;
|
|
|
font-size: 13px;
|
|
|
}
|
|
|
.log-item {
|
|
|
color: #d4d4d4;
|
|
|
margin-bottom: 4px;
|
|
|
line-height: 1.5;
|
|
|
}
|
|
|
.log-time {
|
|
|
color: #4ec9b0;
|
|
|
}
|
|
|
.log-process {
|
|
|
color: #9cdcfe;
|
|
|
}
|
|
|
.log-start {
|
|
|
color: #4ec9b0;
|
|
|
}
|
|
|
.log-end {
|
|
|
color: #ce9178;
|
|
|
}
|
|
|
|
|
|
.btn-pos {
|
|
|
background: #722ed1;
|
|
|
color: white;
|
|
|
}
|
|
|
.btn-pos:hover:not(:disabled) {
|
|
|
background: #9254de;
|
|
|
}
|
|
|
.btn-pos:disabled {
|
|
|
background: #b37feb;
|
|
|
}
|
|
|
.btn-update-batch {
|
|
|
background: #fa8c16;
|
|
|
color: white;
|
|
|
}
|
|
|
.btn-update-batch:hover:not(:disabled) {
|
|
|
background: #ffa940;
|
|
|
}
|
|
|
.btn-update-batch:disabled {
|
|
|
background: #ffd591;
|
|
|
}
|
|
|
.switch-right {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
gap: 16px;
|
|
|
}
|
|
|
.batch-number {
|
|
|
font-size: 14px;
|
|
|
color: #333;
|
|
|
}
|
|
|
.batch-number b {
|
|
|
color: #fa8c16;
|
|
|
font-family: 'Courier New', monospace;
|
|
|
}
|
|
|
|
|
|
/* 位置设置弹框 */
|
|
|
.pos-overlay {
|
|
|
position: fixed;
|
|
|
top: 0; left: 0; right: 0; bottom: 0;
|
|
|
background: rgba(0, 0, 0, 0.5);
|
|
|
display: flex;
|
|
|
justify-content: center;
|
|
|
align-items: center;
|
|
|
z-index: 1000;
|
|
|
}
|
|
|
.pos-modal {
|
|
|
background: #fff;
|
|
|
border-radius: 8px;
|
|
|
width: 90%;
|
|
|
max-width: 720px;
|
|
|
max-height: 90vh;
|
|
|
overflow: auto;
|
|
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
|
|
|
}
|
|
|
.pos-header {
|
|
|
padding: 15px 20px;
|
|
|
font-size: 18px;
|
|
|
font-weight: 600;
|
|
|
border-bottom: 1px solid #eee;
|
|
|
}
|
|
|
.pos-body {
|
|
|
padding: 20px;
|
|
|
}
|
|
|
.pos-field {
|
|
|
margin-bottom: 15px;
|
|
|
}
|
|
|
.pos-row {
|
|
|
display: flex;
|
|
|
gap: 12px;
|
|
|
}
|
|
|
.pos-row .pos-field {
|
|
|
flex: 1;
|
|
|
margin-bottom: 15px;
|
|
|
}
|
|
|
.pos-field label {
|
|
|
display: block;
|
|
|
margin-bottom: 6px;
|
|
|
color: #555;
|
|
|
}
|
|
|
.pos-field input, .pos-field select {
|
|
|
width: 100%;
|
|
|
padding: 8px 10px;
|
|
|
border: 1px solid #ddd;
|
|
|
border-radius: 4px;
|
|
|
box-sizing: border-box;
|
|
|
}
|
|
|
.pos-grid-tools {
|
|
|
margin: 10px 0;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
gap: 12px;
|
|
|
flex-wrap: wrap;
|
|
|
}
|
|
|
.pos-grid-tools button {
|
|
|
padding: 6px 14px;
|
|
|
border: none;
|
|
|
background: #1890ff;
|
|
|
color: #fff;
|
|
|
border-radius: 4px;
|
|
|
cursor: pointer;
|
|
|
}
|
|
|
.pos-grid-tools .btn-pos-clear {
|
|
|
background: #fa8c16;
|
|
|
}
|
|
|
.pos-grid-tools .btn-pos-clear:hover {
|
|
|
background: #ffa940;
|
|
|
}
|
|
|
.pos-hint {
|
|
|
color: #999;
|
|
|
font-size: 12px;
|
|
|
}
|
|
|
.grid-wrap {
|
|
|
overflow: auto;
|
|
|
max-height: 360px;
|
|
|
}
|
|
|
.grid-table {
|
|
|
border-collapse: collapse;
|
|
|
}
|
|
|
.grid-table th, .grid-table td {
|
|
|
border: 1px solid #ddd;
|
|
|
text-align: center;
|
|
|
padding: 0;
|
|
|
}
|
|
|
.grid-table .cell {
|
|
|
width: 30px;
|
|
|
height: 30px;
|
|
|
cursor: pointer;
|
|
|
background: #fafafa;
|
|
|
}
|
|
|
.grid-table .cell:hover {
|
|
|
background: #e6f7ff;
|
|
|
}
|
|
|
.grid-table .cell.selected {
|
|
|
background: #1890ff;
|
|
|
}
|
|
|
.grid-table .row-head, .grid-table .col-head {
|
|
|
background: #f0f0f0;
|
|
|
cursor: pointer;
|
|
|
padding: 4px 8px;
|
|
|
font-weight: 500;
|
|
|
user-select: none;
|
|
|
white-space: nowrap;
|
|
|
}
|
|
|
.grid-table .row-head:hover, .grid-table .col-head:hover {
|
|
|
background: #d9d9d9;
|
|
|
}
|
|
|
.grid-table .corner {
|
|
|
background: #f0f0f0;
|
|
|
}
|
|
|
.btn-pos-close {
|
|
|
padding: 8px 20px;
|
|
|
border: none;
|
|
|
background: #52c41a;
|
|
|
color: #fff;
|
|
|
border-radius: 4px;
|
|
|
cursor: pointer;
|
|
|
}
|
|
|
</style>
|
|
|
</head>
|
|
|
<body>
|
|
|
<body>
|
|
|
<div class="container">
|
|
|
<h1>盘点/随行工具</h1>
|
|
|
|
|
|
<div class="form-row">
|
|
|
<div class="form-group">
|
|
|
<label for="countInput">个数</label>
|
|
|
<input type="number" id="countInput" value="13" min="1">
|
|
|
</div>
|
|
|
|
|
|
<div class="form-group">
|
|
|
<label for="specInput">品规</label>
|
|
|
<input type="text" id="specInput" value="0143">
|
|
|
</div>
|
|
|
|
|
|
<div class="form-group">
|
|
|
<label for="trayCodeInput">托盘码</label>
|
|
|
<input type="text" id="trayCodeInput" value="E045EE00020051F2">
|
|
|
</div>
|
|
|
|
|
|
<div class="form-group">
|
|
|
<label for="scanCodeInput">相机扫码</label>
|
|
|
<input type="text" id="scanCodeInput" value="UR202606">
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<div class="switch-container">
|
|
|
<span class="switch-label">循环模式</span>
|
|
|
<div class="switch-right">
|
|
|
<label class="switch">
|
|
|
<input type="checkbox" id="loopToggle">
|
|
|
<span class="slider"></span>
|
|
|
</label>
|
|
|
<span class="batch-number">批次号:<b id="batchNumberDisplay">-</b></span>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<div class="button-group">
|
|
|
<button id="btnCheck" class="btn-check">盘点</button>
|
|
|
<button id="btnFollow" class="btn-follow">随行</button>
|
|
|
<button id="btnPos" class="btn-pos">位置设置</button>
|
|
|
<button id="btnUpdateBatch" class="btn-update-batch">更新批次号</button>
|
|
|
</div>
|
|
|
|
|
|
<div class="status" id="status">准备就绪</div>
|
|
|
<div class="progress">
|
|
|
<div class="progress-bar" id="progressBar"></div>
|
|
|
</div>
|
|
|
<div class="log-container" id="logContainer">
|
|
|
<div class="log-item">等待执行...</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- 位置设置弹框 -->
|
|
|
<div id="posDialog" class="pos-overlay" style="display: none;">
|
|
|
<div class="pos-modal">
|
|
|
<div class="pos-header">位置设置</div>
|
|
|
<div class="pos-body">
|
|
|
<div class="pos-row">
|
|
|
<div class="pos-field">
|
|
|
<label>巷道号 srmNumber</label>
|
|
|
<input type="text" id="posSrm" value="001">
|
|
|
</div>
|
|
|
<div class="pos-field">
|
|
|
<label>左右</label>
|
|
|
<select id="posSide">
|
|
|
<option value="1">1 - 左</option>
|
|
|
<option value="2">2 - 右</option>
|
|
|
</select>
|
|
|
</div>
|
|
|
<div class="pos-field">
|
|
|
<label>行(默认 7)</label>
|
|
|
<input type="number" id="posRows" value="7" min="1" max="50">
|
|
|
</div>
|
|
|
<div class="pos-field">
|
|
|
<label>列(默认 12)</label>
|
|
|
<input type="number" id="posCols" value="12" min="1" max="50">
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="pos-grid-tools">
|
|
|
<button type="button" id="posSelectAll">全选</button>
|
|
|
<button type="button" id="posClear" class="btn-pos-clear">清空</button>
|
|
|
<span class="pos-hint">点击单元格切换;点击行/列标题选择整行/整列</span>
|
|
|
</div>
|
|
|
<div id="gridTable" class="grid-wrap"></div>
|
|
|
</div>
|
|
|
<div class="pos-footer">
|
|
|
<button type="button" id="posClose" class="btn-pos-close">关闭</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<script>
|
|
|
const btnCheck = document.getElementById('btnCheck');
|
|
|
const btnFollow = document.getElementById('btnFollow');
|
|
|
const btnPos = document.getElementById('btnPos');
|
|
|
const btnUpdateBatch = document.getElementById('btnUpdateBatch');
|
|
|
const batchNumberDisplay = document.getElementById('batchNumberDisplay');
|
|
|
const loopToggle = document.getElementById('loopToggle');
|
|
|
const countInput = document.getElementById('countInput');
|
|
|
const specInput = document.getElementById('specInput');
|
|
|
const trayCodeInput = document.getElementById('trayCodeInput');
|
|
|
const scanCodeInput = document.getElementById('scanCodeInput');
|
|
|
const status = document.getElementById('status');
|
|
|
const progressBar = document.getElementById('progressBar');
|
|
|
const logContainer = document.getElementById('logContainer');
|
|
|
|
|
|
// ===== 位置设置:网格选择(数据持久,关闭弹框后仍保留) =====
|
|
|
let dialogSrm = '001';
|
|
|
let dialogSide = 1;
|
|
|
let gridRows = 7;
|
|
|
let gridCols = 12;
|
|
|
let selectedCells = []; // [{row, col}] 行/列均为 1-based
|
|
|
|
|
|
function cellSelected(r, c) {
|
|
|
return selectedCells.some(x => x.row === r && x.col === c);
|
|
|
}
|
|
|
function toggleCell(r, c) {
|
|
|
const i = selectedCells.findIndex(x => x.row === r && x.col === c);
|
|
|
if (i >= 0) selectedCells.splice(i, 1);
|
|
|
else selectedCells.push({ row: r, col: c });
|
|
|
}
|
|
|
function selectRow(r) {
|
|
|
for (let c = 1; c <= gridCols; c++) {
|
|
|
if (!cellSelected(r, c)) selectedCells.push({ row: r, col: c });
|
|
|
}
|
|
|
}
|
|
|
function selectCol(c) {
|
|
|
for (let r = 1; r <= gridRows; r++) {
|
|
|
if (!cellSelected(r, c)) selectedCells.push({ row: r, col: c });
|
|
|
}
|
|
|
}
|
|
|
function selectAllCells() {
|
|
|
selectedCells = [];
|
|
|
for (let r = 1; r <= gridRows; r++)
|
|
|
for (let c = 1; c <= gridCols; c++) selectedCells.push({ row: r, col: c });
|
|
|
}
|
|
|
function renderGrid() {
|
|
|
const el = document.getElementById('gridTable');
|
|
|
let html = '<table class="grid-table"><thead><tr><th class="corner"></th>';
|
|
|
for (let c = 1; c <= gridCols; c++) {
|
|
|
html += `<th class="col-head" data-col="${c}">${c}</th>`;
|
|
|
}
|
|
|
html += '</tr></thead><tbody>';
|
|
|
for (let r = 1; r <= gridRows; r++) {
|
|
|
html += `<tr><th class="row-head" data-row="${r}">${r}</th>`;
|
|
|
for (let c = 1; c <= gridCols; c++) {
|
|
|
html += `<td class="cell${cellSelected(r, c) ? ' selected' : ''}" data-row="${r}" data-col="${c}"></td>`;
|
|
|
}
|
|
|
html += '</tr>';
|
|
|
}
|
|
|
html += '</tbody></table>';
|
|
|
el.innerHTML = html;
|
|
|
}
|
|
|
function openPosDialog() {
|
|
|
document.getElementById('posDialog').style.display = 'flex';
|
|
|
renderGrid();
|
|
|
}
|
|
|
function closePosDialog() {
|
|
|
document.getElementById('posDialog').style.display = 'none';
|
|
|
}
|
|
|
|
|
|
document.getElementById('posSrm').addEventListener('input', e => { dialogSrm = e.target.value; });
|
|
|
document.getElementById('posSide').addEventListener('change', e => { dialogSide = parseInt(e.target.value) || 1; });
|
|
|
document.getElementById('posRows').addEventListener('input', e => {
|
|
|
gridRows = parseInt(e.target.value) || 7;
|
|
|
selectedCells = selectedCells.filter(x => x.row <= gridRows && x.col <= gridCols);
|
|
|
renderGrid();
|
|
|
});
|
|
|
document.getElementById('posCols').addEventListener('input', e => {
|
|
|
gridCols = parseInt(e.target.value) || 12;
|
|
|
selectedCells = selectedCells.filter(x => x.row <= gridRows && x.col <= gridCols);
|
|
|
renderGrid();
|
|
|
});
|
|
|
document.getElementById('gridTable').addEventListener('click', e => {
|
|
|
const t = e.target;
|
|
|
if (t.classList.contains('cell')) {
|
|
|
toggleCell(parseInt(t.dataset.row), parseInt(t.dataset.col));
|
|
|
renderGrid();
|
|
|
} else if (t.classList.contains('row-head')) {
|
|
|
selectRow(parseInt(t.dataset.row));
|
|
|
renderGrid();
|
|
|
} else if (t.classList.contains('col-head')) {
|
|
|
selectCol(parseInt(t.dataset.col));
|
|
|
renderGrid();
|
|
|
}
|
|
|
});
|
|
|
document.getElementById('posSelectAll').addEventListener('click', () => { selectAllCells(); renderGrid(); });
|
|
|
document.getElementById('posClear').addEventListener('click', () => { selectedCells = []; renderGrid(); });
|
|
|
document.getElementById('btnPos').addEventListener('click', openPosDialog);
|
|
|
document.getElementById('posClose').addEventListener('click', closePosDialog);
|
|
|
|
|
|
let isExecuting = false;
|
|
|
let shouldStop = false;
|
|
|
let currentTaskId = ''; // 当前任务ID,每个选中位置内保持一致
|
|
|
let currentBatchNumber = ''; // 当前批次号,一次执行内保持不变
|
|
|
|
|
|
// 生成新的 taskId(基于时间戳)
|
|
|
function generateTaskId() {
|
|
|
const now = new Date();
|
|
|
const year = now.getFullYear();
|
|
|
const month = String(now.getMonth() + 1).padStart(2, '0');
|
|
|
const day = String(now.getDate()).padStart(2, '0');
|
|
|
const hours = String(now.getHours()).padStart(2, '0');
|
|
|
const minutes = String(now.getMinutes()).padStart(2, '0');
|
|
|
const seconds = String(now.getSeconds()).padStart(2, '0');
|
|
|
const ms = String(now.getMilliseconds()).padStart(3, '0');
|
|
|
|
|
|
// 格式:20260401-143052-123
|
|
|
return `${year}${month}${day}-${hours}${minutes}${seconds}-${ms}`;
|
|
|
}
|
|
|
|
|
|
// 生成批次号(基于时间戳)
|
|
|
function generateBatchNumber() {
|
|
|
const now = new Date();
|
|
|
const year = now.getFullYear();
|
|
|
const month = String(now.getMonth() + 1).padStart(2, '0');
|
|
|
const day = String(now.getDate()).padStart(2, '0');
|
|
|
const hours = String(now.getHours()).padStart(2, '0');
|
|
|
const minutes = String(now.getMinutes()).padStart(2, '0');
|
|
|
const seconds = String(now.getSeconds()).padStart(2, '0');
|
|
|
const ms = String(now.getMilliseconds()).padStart(3, '0');
|
|
|
|
|
|
return `BN${year}${month}${day}${hours}${minutes}${seconds}${ms}`;
|
|
|
}
|
|
|
|
|
|
// 手动更新批次号并刷新显示
|
|
|
function refreshBatchNumber() {
|
|
|
currentBatchNumber = generateBatchNumber();
|
|
|
if (batchNumberDisplay) {
|
|
|
batchNumberDisplay.textContent = currentBatchNumber;
|
|
|
}
|
|
|
return currentBatchNumber;
|
|
|
}
|
|
|
|
|
|
// 写死的接口列表
|
|
|
const checkList = [
|
|
|
{
|
|
|
name: '货物放回',
|
|
|
url: 'http://192.168.1.99:10000/start',
|
|
|
method: 'GET',
|
|
|
timeout: 0,
|
|
|
delay: 10000
|
|
|
},
|
|
|
{
|
|
|
name: '品规盘点',
|
|
|
url: 'http://127.0.0.1:48080/admin-api/logistics/StockController/check',
|
|
|
method: 'POST',
|
|
|
params: {
|
|
|
cmdName: 'E1',
|
|
|
taskId: '{taskId}',
|
|
|
fromColumn: 3,
|
|
|
fromRow: 5,
|
|
|
fromDirection: 2,
|
|
|
fromSide: 2,
|
|
|
srmNumber: '001',
|
|
|
fromSeparation: 1,
|
|
|
lotnum: '{batchNumber}',
|
|
|
wmsExtendInfo: {
|
|
|
code: '{code}',
|
|
|
trayCode: '{trayCode}',
|
|
|
category: '{spec}',
|
|
|
count: '{count}'
|
|
|
}
|
|
|
},
|
|
|
timeout: 0,
|
|
|
delay: 500
|
|
|
},
|
|
|
{
|
|
|
name: '货物拉出',
|
|
|
url: 'http://192.168.1.99:10000/start',
|
|
|
method: 'GET',
|
|
|
timeout: 0,
|
|
|
delay: 10000
|
|
|
},
|
|
|
{
|
|
|
name: '个数盘点',
|
|
|
url: 'http://127.0.0.1:48080/admin-api/logistics/StockController/check',
|
|
|
method: 'POST',
|
|
|
params: {
|
|
|
cmdName: 'E2',
|
|
|
taskId: '{taskId}',
|
|
|
fromColumn: 3,
|
|
|
fromRow: 5,
|
|
|
fromDirection: 2,
|
|
|
fromSide: 2,
|
|
|
srmNumber: '001',
|
|
|
fromSeparation: 1,
|
|
|
lotnum: '{batchNumber}',
|
|
|
wmsExtendInfo: {
|
|
|
code: '{code}',
|
|
|
trayCode: '{trayCode}',
|
|
|
category: '{spec}',
|
|
|
count: '{count}'
|
|
|
}
|
|
|
},
|
|
|
timeout: 0,
|
|
|
delay: 500
|
|
|
},
|
|
|
// {
|
|
|
// name: '货物放回',
|
|
|
// url: 'http://192.168.100.99:8083/retract',
|
|
|
// method: 'GET',
|
|
|
// timeout: 0,
|
|
|
// delay: 500
|
|
|
// }
|
|
|
];
|
|
|
|
|
|
const followList = [
|
|
|
{
|
|
|
name: '货物归位',
|
|
|
url: 'http://192.168.100.99:8083/extend',
|
|
|
method: 'GET',
|
|
|
timeout: 0,
|
|
|
delay: 500
|
|
|
},
|
|
|
{
|
|
|
name: '初始化随行',
|
|
|
url: 'http://127.0.0.1:48080/admin-api/logistics/StockController/action',
|
|
|
method: 'POST',
|
|
|
params: {
|
|
|
srmNumber: '001',
|
|
|
cmdName: 'B1',
|
|
|
taskId: '{taskId}',
|
|
|
fromColumn: '1',
|
|
|
fromRow: '1',
|
|
|
fromDirection: '1',
|
|
|
fromSide: '1',
|
|
|
fromSeparation: 1,
|
|
|
toColumn: '2',
|
|
|
toRow: '2',
|
|
|
toDirection: '2',
|
|
|
toSide: '2',
|
|
|
toSeparation: 1,
|
|
|
warnCode: '',
|
|
|
ackStatus: '',
|
|
|
code: '',
|
|
|
trayCode: '',
|
|
|
category: '', // 品规
|
|
|
count: '',
|
|
|
itemCode: '',
|
|
|
shelfCode: '',
|
|
|
pltCode: '',
|
|
|
countNumber: '', // 个数
|
|
|
wmsCode: '',
|
|
|
wmsTrayCode: '',
|
|
|
wmsCategory: '',
|
|
|
wmsCount: '',
|
|
|
wmsItemCode: '{spec}',
|
|
|
wmsShelfCode: '',
|
|
|
wmsPltCode: '',
|
|
|
wmsCountNumber: '{count}',
|
|
|
batchNumber: '{batchNumber}',
|
|
|
extendInfo: null
|
|
|
},
|
|
|
timeout: 0,
|
|
|
delay: 500
|
|
|
},
|
|
|
{
|
|
|
name: '取货到位',
|
|
|
url: 'http://127.0.0.1:48080/admin-api/logistics/StockController/action',
|
|
|
method: 'POST',
|
|
|
params: {
|
|
|
srmNumber: '001',
|
|
|
cmdName: 'C1',
|
|
|
taskId: '{taskId}',
|
|
|
fromColumn: '1',
|
|
|
fromRow: '1',
|
|
|
fromDirection: '1',
|
|
|
fromSide: '1',
|
|
|
fromSeparation: 1,
|
|
|
toColumn: '2',
|
|
|
toRow: '2',
|
|
|
toDirection: '2',
|
|
|
toSide: '2',
|
|
|
toSeparation: 1,
|
|
|
warnCode: '',
|
|
|
ackStatus: '',
|
|
|
code: '',
|
|
|
trayCode: '',
|
|
|
category: '', // 品规
|
|
|
count: '',
|
|
|
itemCode: '',
|
|
|
shelfCode: '',
|
|
|
pltCode: '',
|
|
|
countNumber: '', // 个数
|
|
|
wmsCode: '',
|
|
|
wmsTrayCode: '',
|
|
|
wmsCategory: '',
|
|
|
wmsCount: '',
|
|
|
wmsItemCode: '{spec}',
|
|
|
wmsShelfCode: '',
|
|
|
wmsPltCode: '',
|
|
|
wmsCountNumber: '{count}',
|
|
|
batchNumber: '{batchNumber}',
|
|
|
extendInfo: null
|
|
|
},
|
|
|
timeout: 0,
|
|
|
delay: 500
|
|
|
},
|
|
|
{
|
|
|
name: '货物拉出',
|
|
|
url: 'http://192.168.100.99:8083/retract',
|
|
|
method: 'GET',
|
|
|
timeout: 0,
|
|
|
delay: 500
|
|
|
},
|
|
|
{
|
|
|
name: '取货完成',
|
|
|
url: 'http://127.0.0.1:48080/admin-api/logistics/StockController/action',
|
|
|
method: 'POST',
|
|
|
params: {
|
|
|
srmNumber: '001',
|
|
|
cmdName: 'C2',
|
|
|
taskId: '{taskId}',
|
|
|
fromColumn: '1',
|
|
|
fromRow: '1',
|
|
|
fromDirection: '1',
|
|
|
fromSide: '1',
|
|
|
fromSeparation: 1,
|
|
|
toColumn: '2',
|
|
|
toRow: '2',
|
|
|
toDirection: '2',
|
|
|
toSide: '2',
|
|
|
toSeparation: 1,
|
|
|
warnCode: '',
|
|
|
ackStatus: '',
|
|
|
code: '',
|
|
|
trayCode: '',
|
|
|
category: '', // 品规
|
|
|
count: '',
|
|
|
itemCode: '',
|
|
|
shelfCode: '',
|
|
|
pltCode: '',
|
|
|
countNumber: '', // 个数
|
|
|
wmsCode: '',
|
|
|
wmsTrayCode: '',
|
|
|
wmsCategory: '',
|
|
|
wmsCount: '',
|
|
|
wmsItemCode: '{spec}',
|
|
|
wmsShelfCode: '',
|
|
|
wmsPltCode: '',
|
|
|
wmsCountNumber: '{count}',
|
|
|
batchNumber: '{batchNumber}',
|
|
|
extendInfo: null
|
|
|
},
|
|
|
timeout: 0,
|
|
|
delay: 500
|
|
|
},
|
|
|
{
|
|
|
name: '送货到位',
|
|
|
url: 'http://127.0.0.1:48080/admin-api/logistics/StockController/action',
|
|
|
method: 'POST',
|
|
|
params: {
|
|
|
srmNumber: '001',
|
|
|
cmdName: 'C3',
|
|
|
taskId: '{taskId}',
|
|
|
fromColumn: '1',
|
|
|
fromRow: '1',
|
|
|
fromDirection: '1',
|
|
|
fromSide: '1',
|
|
|
fromSeparation: 1,
|
|
|
toColumn: '2',
|
|
|
toRow: '2',
|
|
|
toDirection: '2',
|
|
|
toSide: '2',
|
|
|
toSeparation: 1,
|
|
|
warnCode: '',
|
|
|
ackStatus: '',
|
|
|
code: '',
|
|
|
trayCode: '',
|
|
|
category: '', // 品规
|
|
|
count: '',
|
|
|
itemCode: '',
|
|
|
shelfCode: '',
|
|
|
pltCode: '',
|
|
|
countNumber: '', // 个数
|
|
|
wmsCode: '',
|
|
|
wmsTrayCode: '',
|
|
|
wmsCategory: '',
|
|
|
wmsCount: '',
|
|
|
wmsItemCode: '{spec}',
|
|
|
wmsShelfCode: '',
|
|
|
wmsPltCode: '',
|
|
|
wmsCountNumber: '{count}',
|
|
|
batchNumber: '{batchNumber}',
|
|
|
extendInfo: null
|
|
|
},
|
|
|
timeout: 0,
|
|
|
delay: 500
|
|
|
},
|
|
|
{
|
|
|
name: '货物放回',
|
|
|
url: 'http://192.168.100.99:8083/extend',
|
|
|
method: 'GET',
|
|
|
timeout: 0,
|
|
|
delay: 500
|
|
|
}
|
|
|
,
|
|
|
{
|
|
|
name: '送货完成',
|
|
|
url: 'http://127.0.0.1:48080/admin-api/logistics/StockController/action',
|
|
|
method: 'POST',
|
|
|
params: {
|
|
|
srmNumber: '001',
|
|
|
cmdName: 'C4',
|
|
|
taskId: '{taskId}',
|
|
|
fromColumn: '1',
|
|
|
fromRow: '1',
|
|
|
fromDirection: '1',
|
|
|
fromSide: '1',
|
|
|
fromSeparation: 1,
|
|
|
toColumn: '2',
|
|
|
toRow: '2',
|
|
|
toDirection: '2',
|
|
|
toSide: '2',
|
|
|
toSeparation: 1,
|
|
|
warnCode: '',
|
|
|
ackStatus: '',
|
|
|
code: '',
|
|
|
trayCode: '',
|
|
|
category: '', // 品规
|
|
|
count: '',
|
|
|
itemCode: '',
|
|
|
shelfCode: '',
|
|
|
pltCode: '',
|
|
|
countNumber: '', // 个数
|
|
|
wmsCode: '',
|
|
|
wmsTrayCode: '',
|
|
|
wmsCategory: '',
|
|
|
wmsCount: '',
|
|
|
wmsItemCode: '{spec}',
|
|
|
wmsShelfCode: '',
|
|
|
wmsPltCode: '',
|
|
|
wmsCountNumber: '{count}',
|
|
|
batchNumber: '{batchNumber}',
|
|
|
extendInfo: null
|
|
|
},
|
|
|
timeout: 0,
|
|
|
delay: 500
|
|
|
},
|
|
|
{
|
|
|
name: '随行结束',
|
|
|
url: 'http://127.0.0.1:48080/admin-api/logistics/StockController/action',
|
|
|
method: 'POST',
|
|
|
params: {
|
|
|
srmNumber: '001',
|
|
|
cmdName: 'B2',
|
|
|
taskId: '{taskId}',
|
|
|
fromColumn: '1',
|
|
|
fromRow: '1',
|
|
|
fromDirection: '1',
|
|
|
fromSide: '1',
|
|
|
fromSeparation: 1,
|
|
|
toColumn: '2',
|
|
|
toRow: '2',
|
|
|
toDirection: '2',
|
|
|
toSide: '2',
|
|
|
toSeparation: 1,
|
|
|
warnCode: '',
|
|
|
ackStatus: '',
|
|
|
code: '',
|
|
|
trayCode: '',
|
|
|
category: '', // 品规
|
|
|
count: '',
|
|
|
itemCode: '',
|
|
|
shelfCode: '',
|
|
|
pltCode: '',
|
|
|
countNumber: '', // 个数
|
|
|
wmsCode: '',
|
|
|
wmsTrayCode: '',
|
|
|
wmsCategory: '',
|
|
|
wmsCount: '',
|
|
|
wmsItemCode: '{spec}',
|
|
|
wmsShelfCode: '',
|
|
|
wmsPltCode: '',
|
|
|
wmsCountNumber: '{count}',
|
|
|
batchNumber: '{batchNumber}',
|
|
|
extendInfo: null
|
|
|
},
|
|
|
timeout: 0,
|
|
|
delay: 500
|
|
|
},
|
|
|
{
|
|
|
name: '货物放回',
|
|
|
url: 'http://192.168.100.99:8083/retract',
|
|
|
method: 'GET',
|
|
|
timeout: 0,
|
|
|
delay: 500
|
|
|
}
|
|
|
];
|
|
|
|
|
|
// 格式化时间
|
|
|
function formatTime(date) {
|
|
|
return date.toLocaleTimeString('zh-CN', { hour12: false });
|
|
|
}
|
|
|
|
|
|
// 添加日志
|
|
|
function addLog(message) {
|
|
|
const logItem = document.createElement('div');
|
|
|
logItem.className = 'log-item';
|
|
|
logItem.innerHTML = message;
|
|
|
logContainer.appendChild(logItem);
|
|
|
logContainer.scrollTop = logContainer.scrollHeight;
|
|
|
}
|
|
|
|
|
|
// 清空日志
|
|
|
function clearLog() {
|
|
|
logContainer.innerHTML = '';
|
|
|
}
|
|
|
|
|
|
// 替换占位符(支持嵌套对象,如 wmsExtendInfo)
|
|
|
function replaceValue(value, count, spec, code, trayCode, taskId, batchNumber) {
|
|
|
if (typeof value === 'string') {
|
|
|
return value
|
|
|
.replace('{count}', count)
|
|
|
.replace('{spec}', spec)
|
|
|
.replace('{code}', code)
|
|
|
.replace('{trayCode}', trayCode)
|
|
|
.replace('{taskId}', taskId)
|
|
|
.replace('{batchNumber}', batchNumber);
|
|
|
} else if (value && typeof value === 'object') {
|
|
|
const result = {};
|
|
|
for (const k in value) {
|
|
|
result[k] = replaceValue(value[k], count, spec, code, trayCode, taskId, batchNumber);
|
|
|
}
|
|
|
return result;
|
|
|
}
|
|
|
return value;
|
|
|
}
|
|
|
|
|
|
function replaceParams(params, count, spec, code, trayCode, taskId, batchNumber) {
|
|
|
if (!params) return {};
|
|
|
const result = {};
|
|
|
for (const key in params) {
|
|
|
result[key] = replaceValue(params[key], count, spec, code, trayCode, taskId, batchNumber);
|
|
|
}
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
// 执行单个请求
|
|
|
async function executeRequest(item, taskId, batchNumber, target) {
|
|
|
let url = item.url;
|
|
|
const name = item.name;
|
|
|
const count = parseInt(countInput.value) || 13;
|
|
|
const spec = specInput.value || '0143';
|
|
|
const code = scanCodeInput.value || 'UR202606';
|
|
|
const trayCode = trayCodeInput.value || 'E045EE00020051F2';
|
|
|
const method = item.method || 'POST';
|
|
|
const baseParams = item.params || {};
|
|
|
const timeout = item.timeout || 0; // 0 表示不超时
|
|
|
|
|
|
// 替换参数中的占位符(包括 taskId 和 batchNumber)
|
|
|
const params = replaceParams(baseParams, count, spec, code, trayCode, taskId, batchNumber);
|
|
|
|
|
|
// 按网格选择的位置覆盖 fromColumn/fromRow/fromDirection/fromSide/srmNumber
|
|
|
if (target) {
|
|
|
const mergeKeys = ['fromColumn', 'fromRow', 'fromDirection', 'fromSide', 'srmNumber'];
|
|
|
for (const k of mergeKeys) {
|
|
|
if (target[k] !== undefined && Object.prototype.hasOwnProperty.call(params, k)) {
|
|
|
params[k] = target[k];
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
const startTime = new Date();
|
|
|
addLog(`<span class="log-time">[${formatTime(startTime)}]</span> <span class="log-process">${name}</span> <span class="log-start">开始</span> [${method}]`);
|
|
|
|
|
|
try {
|
|
|
const fetchOptions = {
|
|
|
method: method,
|
|
|
headers: {
|
|
|
'Content-Type': 'application/json',
|
|
|
}
|
|
|
};
|
|
|
|
|
|
// GET 请求使用 URL 参数
|
|
|
if (method.toUpperCase() === 'GET') {
|
|
|
if (Object.keys(params).length > 0) {
|
|
|
const queryParams = new URLSearchParams(params).toString();
|
|
|
url = `${url}?${queryParams}`;
|
|
|
}
|
|
|
// GET 请求不需要 body,移除 Content-Type
|
|
|
delete fetchOptions.headers['Content-Type'];
|
|
|
} else if (method.toUpperCase() !== 'GET' && Object.keys(params).length > 0) {
|
|
|
// POST/PUT 等使用 body
|
|
|
fetchOptions.body = JSON.stringify({
|
|
|
...params,
|
|
|
timestamp: Date.now()
|
|
|
});
|
|
|
}
|
|
|
|
|
|
// 处理超时
|
|
|
let response;
|
|
|
if (timeout > 0) {
|
|
|
const controller = new AbortController();
|
|
|
fetchOptions.signal = controller.signal;
|
|
|
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
|
response = await fetch(url, fetchOptions);
|
|
|
clearTimeout(timeoutId);
|
|
|
} else {
|
|
|
response = await fetch(url, fetchOptions);
|
|
|
}
|
|
|
|
|
|
const endTime = new Date();
|
|
|
const duration = (endTime - startTime) / 1000;
|
|
|
|
|
|
if (!response.ok) {
|
|
|
throw new Error(`HTTP ${response.status}`);
|
|
|
}
|
|
|
|
|
|
// 获取返回内容
|
|
|
let responseText = '';
|
|
|
try {
|
|
|
responseText = await response.text();
|
|
|
} catch (e) {
|
|
|
responseText = '(无法读取返回内容)';
|
|
|
}
|
|
|
|
|
|
addLog(`<span class="log-time">[${formatTime(endTime)}]</span> <span class="log-process">${name}</span> <span class="log-end">结束</span> (耗时: ${duration.toFixed(2)}s) <span style="color: #dcdcaa;">返回: ${responseText}</span>`);
|
|
|
} catch (error) {
|
|
|
const endTime = new Date();
|
|
|
if (error.name === 'AbortError') {
|
|
|
addLog(`<span class="log-time">[${formatTime(endTime)}]</span> <span class="log-process">${name}</span> <span class="log-end">结束 (超时)</span>`);
|
|
|
} else {
|
|
|
addLog(`<span class="log-time">[${formatTime(endTime)}]</span> <span class="log-process">${name}</span> <span class="log-end">结束 (失败: ${error.message})</span>`);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 执行列表
|
|
|
async function executeList(list, type, taskId, batchNumber, target) {
|
|
|
const totalSteps = list.length;
|
|
|
|
|
|
for (let i = 0; i < totalSteps; i++) {
|
|
|
status.textContent = `${type === 'check' ? '盘点' : '随行'}中: ${i + 1}/${totalSteps} - ${list[i].name}`;
|
|
|
progressBar.style.width = `${((i + 1) / totalSteps) * 100}%`;
|
|
|
|
|
|
await executeRequest(list[i], taskId, batchNumber, target);
|
|
|
|
|
|
// 每个请求完成后延时(取接口自带 delay,未配置则默认 500ms)
|
|
|
const gap = (typeof list[i].delay === 'number') ? list[i].delay : 500;
|
|
|
if (gap > 0) {
|
|
|
addLog(`<span class="log-time">[${formatTime(new Date())}]</span> ⏱ 等待 ${gap}ms 后执行下一步`);
|
|
|
await new Promise(resolve => setTimeout(resolve, gap));
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 主执行函数
|
|
|
async function execute(type) {
|
|
|
const list = type === 'check' ? checkList : followList;
|
|
|
|
|
|
// 根据网格选择构建目标位置列表
|
|
|
let targets;
|
|
|
if (selectedCells.length > 0) {
|
|
|
targets = selectedCells.map(cell => ({
|
|
|
fromColumn: cell.col,
|
|
|
fromRow: cell.row,
|
|
|
fromDirection: dialogSide,
|
|
|
fromSide: dialogSide,
|
|
|
srmNumber: dialogSrm
|
|
|
}));
|
|
|
} else {
|
|
|
// 行/列均为空:单盘点默认参数,且循环模式不可开启
|
|
|
targets = [{
|
|
|
fromColumn: 3,
|
|
|
fromRow: 5,
|
|
|
fromDirection: dialogSide,
|
|
|
fromSide: dialogSide,
|
|
|
srmNumber: dialogSrm
|
|
|
}];
|
|
|
}
|
|
|
// 未选择任何位置时,强制关闭循环(循环模式无法开启)
|
|
|
const loopEnabled = loopToggle.checked && selectedCells.length > 0;
|
|
|
|
|
|
clearLog();
|
|
|
isExecuting = true;
|
|
|
shouldStop = false;
|
|
|
btnCheck.disabled = true;
|
|
|
btnFollow.disabled = true;
|
|
|
btnPos.disabled = true;
|
|
|
const activeBtn = type === 'check' ? btnCheck : btnFollow;
|
|
|
activeBtn.textContent = '执行中...';
|
|
|
|
|
|
let loopCount = 0;
|
|
|
// 批次号 batchNumber:由“更新批次号”按钮手动控制,此处不再自动生成
|
|
|
if (!currentBatchNumber) {
|
|
|
refreshBatchNumber();
|
|
|
}
|
|
|
|
|
|
try {
|
|
|
do {
|
|
|
loopCount++;
|
|
|
addLog(`<span class="log-time">[${formatTime(new Date())}]</span> ========== 第 ${loopCount} ${type === 'check' ? '盘点' : '随行'} ========== (位置数: ${targets.length}, batch: ${currentBatchNumber})`);
|
|
|
|
|
|
for (const target of targets) {
|
|
|
if (shouldStop) break;
|
|
|
// 任务号 taskId:每个选中位置重新生成(该位置的 checkList 内保持一致)
|
|
|
currentTaskId = generateTaskId();
|
|
|
addLog(`<span class="log-time">[${formatTime(new Date())}]</span> ---- 位置 行${target.fromRow}/列${target.fromColumn} (srm:${target.srmNumber}, 左右:${target.fromSide}, taskId: ${currentTaskId}) ----`);
|
|
|
await executeList(list, type, currentTaskId, currentBatchNumber, target);
|
|
|
}
|
|
|
|
|
|
if (shouldStop) {
|
|
|
addLog(`<span class="log-time">[${formatTime(new Date())}]</span> ========== 用户停止 ==========`);
|
|
|
break;
|
|
|
}
|
|
|
|
|
|
if (loopEnabled) {
|
|
|
// 循环模式下,等待一小段时间后继续
|
|
|
await new Promise(resolve => setTimeout(resolve, 500));
|
|
|
}
|
|
|
} while (loopEnabled);
|
|
|
|
|
|
const modeText = loopEnabled ? ` (循环 ${loopCount} 次)` : '';
|
|
|
status.textContent = `${type === 'check' ? '盘点' : '随行'}完成${modeText}!`;
|
|
|
addLog(`<span class="log-time">[${formatTime(new Date())}]</span> ========== 完成${modeText} ==========`);
|
|
|
} catch (error) {
|
|
|
status.textContent = `${type === 'check' ? '盘点' : '随行'}出错: ${error.message}`;
|
|
|
addLog(`<span class="log-time">[${formatTime(new Date())}]</span> ========== 出错: ${error.message} ==========`);
|
|
|
} finally {
|
|
|
isExecuting = false;
|
|
|
btnCheck.disabled = false;
|
|
|
btnFollow.disabled = false;
|
|
|
btnPos.disabled = false;
|
|
|
btnCheck.textContent = '盘点';
|
|
|
btnFollow.textContent = '随行';
|
|
|
|
|
|
// 3秒后重置进度条和状态
|
|
|
setTimeout(() => {
|
|
|
progressBar.style.width = '0%';
|
|
|
status.textContent = '准备就绪';
|
|
|
}, 3000);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 监听按钮点击
|
|
|
btnCheck.addEventListener('click', () => {
|
|
|
if (!isExecuting) {
|
|
|
execute('check');
|
|
|
} else if (isExecuting && !shouldStop) {
|
|
|
shouldStop = true;
|
|
|
addLog(`<span class="log-time">[${formatTime(new Date())}]</span> ========== 等待当前循环结束 ==========`);
|
|
|
}
|
|
|
});
|
|
|
|
|
|
btnFollow.addEventListener('click', () => {
|
|
|
if (!isExecuting) {
|
|
|
execute('follow');
|
|
|
} else if (isExecuting && !shouldStop) {
|
|
|
shouldStop = true;
|
|
|
addLog(`<span class="log-time">[${formatTime(new Date())}]</span> ========== 等待当前循环结束 ==========`);
|
|
|
}
|
|
|
});
|
|
|
|
|
|
// 监听循环开关
|
|
|
loopToggle.addEventListener('change', (e) => {
|
|
|
if (e.target.checked && isExecuting) {
|
|
|
addLog(`<span class="log-time">[${formatTime(new Date())}]</span> ========== 循环已开启,将在当前循环结束后继续 ==========`);
|
|
|
} else if (!e.target.checked && isExecuting) {
|
|
|
shouldStop = true;
|
|
|
addLog(`<span class="log-time">[${formatTime(new Date())}]</span> ========== 循环已关闭,将在当前循环结束后停止 ==========`);
|
|
|
}
|
|
|
});
|
|
|
|
|
|
// 更新批次号按钮:手动刷新批次号
|
|
|
btnUpdateBatch.addEventListener('click', () => {
|
|
|
refreshBatchNumber();
|
|
|
addLog(`<span class="log-time">[${formatTime(new Date())}]</span> ========== 批次号已更新: ${currentBatchNumber} ==========`);
|
|
|
});
|
|
|
|
|
|
// 初始化批次号显示
|
|
|
refreshBatchNumber();
|
|
|
</script>
|
|
|
</body>
|
|
|
</html>
|