|
|
<!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: 600px;
|
|
|
margin: 0 auto;
|
|
|
background: white;
|
|
|
padding: 30px;
|
|
|
border-radius: 12px;
|
|
|
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
|
|
}
|
|
|
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;
|
|
|
}
|
|
|
</style>
|
|
|
</head>
|
|
|
<body>
|
|
|
<body>
|
|
|
<div class="container">
|
|
|
<h1>盘点/随行工具</h1>
|
|
|
|
|
|
<div class="form-group">
|
|
|
<label for="countInput">个数</label>
|
|
|
<input type="number" id="countInput" value="21" min="1">
|
|
|
</div>
|
|
|
|
|
|
<div class="form-group">
|
|
|
<label for="specInput">品规</label>
|
|
|
<input type="text" id="specInput" value="0143">
|
|
|
</div>
|
|
|
|
|
|
<div class="switch-container">
|
|
|
<span class="switch-label">循环模式</span>
|
|
|
<label class="switch">
|
|
|
<input type="checkbox" id="loopToggle">
|
|
|
<span class="slider"></span>
|
|
|
</label>
|
|
|
</div>
|
|
|
|
|
|
<div class="button-group">
|
|
|
<button id="btnCheck" class="btn-check">盘点</button>
|
|
|
<button id="btnFollow" class="btn-follow">随行</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>
|
|
|
|
|
|
<script>
|
|
|
const btnCheck = document.getElementById('btnCheck');
|
|
|
const btnFollow = document.getElementById('btnFollow');
|
|
|
const loopToggle = document.getElementById('loopToggle');
|
|
|
const countInput = document.getElementById('countInput');
|
|
|
const specInput = document.getElementById('specInput');
|
|
|
const status = document.getElementById('status');
|
|
|
const progressBar = document.getElementById('progressBar');
|
|
|
const logContainer = document.getElementById('logContainer');
|
|
|
|
|
|
let isExecuting = false;
|
|
|
let shouldStop = false;
|
|
|
let currentTaskId = ''; // 当前任务ID,同一轮内保持一致
|
|
|
|
|
|
// 生成新的 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}`;
|
|
|
}
|
|
|
|
|
|
// 写死的接口列表
|
|
|
const checkList = [
|
|
|
{
|
|
|
name: '货物放回',
|
|
|
url: 'http://192.168.100.99:8083/extend',
|
|
|
method: 'GET',
|
|
|
timeout: 0
|
|
|
},
|
|
|
{
|
|
|
name: '品规盘点',
|
|
|
url: 'http://127.0.0.1:48080/admin-api/logistics/StockController/check',
|
|
|
method: 'POST',
|
|
|
params: {
|
|
|
srmNumber: '001',
|
|
|
cmdName: 'E1',
|
|
|
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: 'UR202606',
|
|
|
wmsTrayCode: 'UR202606',
|
|
|
wmsCategory: '',
|
|
|
wmsCount: '',
|
|
|
wmsItemCode: '{spec}',
|
|
|
wmsShelfCode: '',
|
|
|
wmsPltCode: '',
|
|
|
wmsCountNumber: '{count}',
|
|
|
batchNumber: '',
|
|
|
extendInfo: null
|
|
|
},
|
|
|
timeout: 0
|
|
|
},
|
|
|
{
|
|
|
name: '货物拉出',
|
|
|
url: 'http://192.168.100.99:8083/retract',
|
|
|
method: 'GET',
|
|
|
timeout: 0
|
|
|
},
|
|
|
{
|
|
|
name: '个数盘点',
|
|
|
url: 'http://127.0.0.1:48080/admin-api/logistics/StockController/check',
|
|
|
method: 'POST',
|
|
|
params: {
|
|
|
srmNumber: '001',
|
|
|
cmdName: 'E2',
|
|
|
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: '',
|
|
|
extendInfo: null
|
|
|
},
|
|
|
timeout: 0
|
|
|
},
|
|
|
{
|
|
|
name: '货物放回',
|
|
|
url: 'http://192.168.100.99:8083/retract',
|
|
|
method: 'GET',
|
|
|
timeout: 0
|
|
|
}
|
|
|
];
|
|
|
|
|
|
const followList = [
|
|
|
{
|
|
|
name: '货物放回',
|
|
|
url: 'http://192.168.100.99:8083/retract',
|
|
|
method: 'GET',
|
|
|
timeout: 0
|
|
|
},
|
|
|
{
|
|
|
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: '',
|
|
|
extendInfo: null
|
|
|
},
|
|
|
timeout: 0
|
|
|
},
|
|
|
{
|
|
|
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: '',
|
|
|
extendInfo: null
|
|
|
},
|
|
|
timeout: 0
|
|
|
},
|
|
|
{
|
|
|
name: '货物拉出',
|
|
|
url: 'http://192.168.100.99:8083/extend',
|
|
|
method: 'GET',
|
|
|
timeout: 0
|
|
|
},
|
|
|
{
|
|
|
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: '',
|
|
|
extendInfo: null
|
|
|
},
|
|
|
timeout: 0
|
|
|
},
|
|
|
{
|
|
|
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: '',
|
|
|
extendInfo: null
|
|
|
},
|
|
|
timeout: 0
|
|
|
},
|
|
|
{
|
|
|
name: '货物放回',
|
|
|
url: 'http://192.168.100.99:8083/retract',
|
|
|
method: 'GET',
|
|
|
timeout: 0
|
|
|
}
|
|
|
,
|
|
|
{
|
|
|
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: '',
|
|
|
extendInfo: null
|
|
|
},
|
|
|
timeout: 0
|
|
|
},
|
|
|
{
|
|
|
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: '',
|
|
|
extendInfo: null
|
|
|
},
|
|
|
timeout: 0
|
|
|
},
|
|
|
];
|
|
|
|
|
|
// 格式化时间
|
|
|
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 = '';
|
|
|
}
|
|
|
|
|
|
// 替换参数中的占位符
|
|
|
function replaceParams(params, count, spec, taskId) {
|
|
|
if (!params) return {};
|
|
|
const result = {};
|
|
|
for (const key in params) {
|
|
|
const value = params[key];
|
|
|
if (typeof value === 'string') {
|
|
|
result[key] = value
|
|
|
.replace('{count}', count)
|
|
|
.replace('{spec}', spec)
|
|
|
.replace('{taskId}', taskId);
|
|
|
} else {
|
|
|
result[key] = value;
|
|
|
}
|
|
|
}
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
// 执行单个请求
|
|
|
async function executeRequest(item, taskId) {
|
|
|
let url = item.url;
|
|
|
const name = item.name;
|
|
|
const count = parseInt(countInput.value) || 21;
|
|
|
const spec = specInput.value || '0143';
|
|
|
const method = item.method || 'POST';
|
|
|
const baseParams = item.params || {};
|
|
|
const timeout = item.timeout || 0; // 0 表示不超时
|
|
|
|
|
|
// 替换参数中的占位符(包括 taskId)
|
|
|
const params = replaceParams(baseParams, count, spec, taskId);
|
|
|
|
|
|
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) {
|
|
|
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);
|
|
|
|
|
|
// 每个请求完成后延时 0.5 秒
|
|
|
await new Promise(resolve => setTimeout(resolve, 500));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 主执行函数
|
|
|
async function execute(type) {
|
|
|
const list = type === 'check' ? checkList : followList;
|
|
|
const isLoop = loopToggle.checked;
|
|
|
|
|
|
clearLog();
|
|
|
isExecuting = true;
|
|
|
shouldStop = false;
|
|
|
btnCheck.disabled = true;
|
|
|
btnFollow.disabled = true;
|
|
|
const activeBtn = type === 'check' ? btnCheck : btnFollow;
|
|
|
activeBtn.textContent = '执行中...';
|
|
|
|
|
|
let loopCount = 0;
|
|
|
|
|
|
try {
|
|
|
do {
|
|
|
loopCount++;
|
|
|
// 每一轮生成新的 taskId
|
|
|
currentTaskId = generateTaskId();
|
|
|
addLog(`<span class="log-time">[${formatTime(new Date())}]</span> ========== 第 ${loopCount} ${type === 'check' ? '盘点' : '随行'} ========== (taskId: ${currentTaskId})`);
|
|
|
|
|
|
await executeList(list, type, currentTaskId);
|
|
|
|
|
|
if (shouldStop) {
|
|
|
addLog(`<span class="log-time">[${formatTime(new Date())}]</span> ========== 用户停止 ==========`);
|
|
|
break;
|
|
|
}
|
|
|
|
|
|
if (isLoop) {
|
|
|
// 循环模式下,等待一小段时间后继续
|
|
|
await new Promise(resolve => setTimeout(resolve, 500));
|
|
|
}
|
|
|
} while (isLoop);
|
|
|
|
|
|
const modeText = isLoop ? ` (循环 ${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;
|
|
|
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> ========== 循环已关闭,将在当前循环结束后停止 ==========`);
|
|
|
}
|
|
|
});
|
|
|
</script>
|
|
|
</body>
|
|
|
</html>
|