This commit is contained in:
2026-02-09 17:57:40 +08:00
parent 45fe4e70b6
commit 887aa55930
10 changed files with 334 additions and 27 deletions

View File

@@ -11,8 +11,11 @@
#fixed-window { width: 1280px; height: 680px; border: 1px solid #e5e7eb; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); }
.table-container { height: calc(680px - 160px - 64px); overflow-y: auto; }
.sticky-header { position: sticky; top: 0; z-index: 10; }
@keyframes scan { 0% { top: 0; } 100% { top: 100%; } }
.scanning #scan-line { display: block; animation: scan 2s linear infinite; }
</style>
<script src="/iframe/js/xlsx.full.min.js" language="JavaScript"></script>
<script src="/iframe/js/jsQR.min.js" language="JavaScript"></script>
</head>
<body class="bg-gray-100 font-sans text-sm">
<div id="fixed-window" class="bg-gray-50 flex flex-col overflow-hidden mx-auto">
@@ -22,6 +25,7 @@
<input id="order-uuid" type="text" placeholder="UUID 或者 订单链接" disabled class="flex-grow px-3 py-1.5 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 outline-none" />
<button id="import-order-btn" class="px-6 py-1.5 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition">添加订单</button>
<button id="import-order-file-btn" class="px-6 py-1.5 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition">添加订单详情 Excel 文档</button>
<button id="open-qr-btn" class="px-6 py-1.5 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition">扫描二维码</button>
</div>
<div class="flex items-center space-x-3">
<span class="font-medium text-gray-700 w-32">立创商城编号导入</span>
@@ -68,15 +72,58 @@
<button id="clear-list-btn" class="px-6 py-2 border border-red-300 text-red-600 rounded-md hover:bg-red-50">清空列表</button>
<button id="batch-save-btn" class="px-8 py-2 bg-blue-600 text-white font-bold rounded-md hover:bg-blue-700 shadow-md">批量入库</button>
</div>
<div id="qr-dialog" class="fixed inset-0 z-50 hidden flex items-center justify-center bg-black bg-opacity-50">
<div class="bg-white rounded-lg shadow-xl w-[450px] overflow-hidden">
<div class="px-4 py-3 border-b flex justify-between items-center bg-gray-50">
<h3 class="font-bold text-gray-700">扫码入库</h3>
<button onclick="closeQrDialog()" class="text-gray-400 hover:text-gray-600">&times;</button>
</div>
<div class="p-4 space-y-4">
<select id="camera-select" class="w-full px-3 py-2 border rounded-md text-sm outline-none focus:ring-2 focus:ring-blue-500">
<option value="">正在检测摄像头...</option>
</select>
<div class="relative w-[250px] h-[250px] mx-auto bg-black rounded-lg overflow-hidden border-2 border-gray-300">
<video id="qr-video" class="absolute inset-0 w-full h-full object-cover shadow-inner" playsinline></video>
<canvas id="qr-canvas" class="hidden"></canvas>
<div id="scan-line" class="absolute left-0 right-0 h-0.5 bg-blue-500 opacity-50 shadow-[0_0_8px_rgba(59,130,246,0.8)] hidden"></div>
</div>
<div id="scan-result" class="p-3 bg-blue-50 rounded-md border border-blue-100">
<div class="text-sm space-y-1">
<p>CID: <span id="res-cid"></span></p>
<p>型号: <span id="res-pm"></span></p>
<p>数量: <span id="res-qty"></span></p>
</div>
</div>
<div class="grid grid-cols-3 gap-2">
<button id="btn-start-camera" class="py-2 bg-gray-600 text-white rounded hover:bg-gray-700 text-sm">打开摄像头</button>
<button id="btn-scan" class="py-2 bg-blue-600 text-white rounded hover:bg-blue-700 text-sm disabled:opacity-50" disabled>扫描二维码</button>
<button id="btn-add-to-list" class="py-2 bg-green-600 text-white rounded hover:bg-green-700 text-sm disabled:opacity-50" disabled>加入列表</button>
</div>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function () {
const SERVER = eda.sys_Storage.getExtensionUserConfig('server-host') || 'http://localhost:21816/api';
const AUTO_RUN = eda.sys_Storage.getExtensionUserConfig('server-auto-run') || true;
const tableBody = document.getElementById('import-table-body');
const listCount = document.getElementById('list-count');
const qrDialog = document.getElementById('qr-dialog');
const qrVideo = document.getElementById('qr-video');
const qrCanvas = document.getElementById('qr-canvas');
const cameraSelect = document.getElementById('camera-select');
let importList = [];
let videoStream = null;
let currentScanData = null;
function renderList() {
if (importList.length === 0) {
@@ -105,6 +152,127 @@
listCount.textContent = importList.length;
}
async function updateCameraList() {
try {
const devices = await navigator.mediaDevices.enumerateDevices();
const videoDevices = devices.filter(device => device.kind === 'videoinput');
cameraSelect.innerHTML = videoDevices.map(d =>
`<option value="${d.deviceId}">${d.label || '摄像头 ' + d.deviceId.slice(0, 5)}</option>`
).join('');
} catch (e) {
eda.sys_Message.showToastMessage('无法获取摄像头列表: ' + e.message, ESYS_ToastMessageType.ERROR);
}
}
window.closeQrDialog = () => {
stopCamera();
qrDialog.classList.add('hidden');
document.getElementById('res-cid').textContent = currentScanData.lcscId;
document.getElementById('res-pm').textContent = currentScanData.name;
document.getElementById('res-qty').textContent = currentScanData.quantity;
currentScanData = null;
};
function stopCamera() {
if (videoStream) {
videoStream.getTracks().forEach(track => track.stop());
videoStream = null;
}
qrVideo.parentElement.classList.remove('scanning');
}
document.getElementById('btn-start-camera').onclick = async () => {
stopCamera();
const deviceId = cameraSelect.value;
const constraints = {
video: deviceId ? { deviceId: { exact: deviceId } } : { facingMode: 'environment' }
};
try {
videoStream = await navigator.mediaDevices.getUserMedia(constraints);
qrVideo.srcObject = videoStream;
qrVideo.play();
document.getElementById('btn-scan').disabled = false;
qrVideo.parentElement.classList.add('scanning');
} catch (e) {
eda.sys_Message.showToastMessage('启动摄像头失败', ESYS_ToastMessageType.ERROR);
}
};
document.getElementById('btn-scan').onclick = () => {
if (!videoStream) return;
const ctx = qrCanvas.getContext('2d', { willReadFrequently: true });
qrCanvas.width = qrVideo.videoWidth;
qrCanvas.height = qrVideo.videoHeight;
ctx.drawImage(qrVideo, 0, 0, qrCanvas.width, qrCanvas.height);
const imageData = ctx.getImageData(0, 0, qrCanvas.width, qrCanvas.height);
const code = jsQR(imageData.data, imageData.width, imageData.height);
if (code) {
eda.sys_Log.add(code.data);
parseQrData(code.data);
} else {
eda.sys_Message.showToastMessage('未扫描到有效二维码,请重试', ESYS_ToastMessageType.WARNING);
}
};
function parseQrData(data) {
try {
// 使用正则提取 pc, pm, qty
const pcMatch = data.match(/pc:([^,}]+)/);
const pmMatch = data.match(/pm:([^,}]+)/);
const qtyMatch = data.match(/qty:(\d+)/);
if (pcMatch) {
currentScanData = {
lcscId: pcMatch[1].trim().toUpperCase(),
name: pmMatch ? pmMatch[1].trim() : '未知型号',
quantity: qtyMatch ? parseInt(qtyMatch[1]) : 1,
selected: true
};
document.getElementById('res-cid').textContent = currentScanData.lcscId;
document.getElementById('res-pm').textContent = currentScanData.name;
document.getElementById('res-qty').textContent = currentScanData.quantity;
document.getElementById('btn-add-to-list').disabled = false;
} else {
throw new Error("无效的二维码格式");
}
} catch (e) {
eda.sys_Message.showToastMessage('解析失败: ' + e.message, ESYS_ToastMessageType.ERROR);
}
}
document.getElementById('btn-add-to-list').onclick = async () => {
if (!currentScanData) return;
const btn = document.getElementById('btn-add-to-list');
btn.disabled = true;
btn.textContent = '处理中...';
try {
const item = { ...currentScanData };
await enrichData([item]);
importList.push(item);
renderList();
eda.sys_Message.showToastMessage('已加入待入库列表', ESYS_ToastMessageType.SUCCESS);
document.getElementById('res-cid').textContent = '等待扫描...';
document.getElementById('res-pm').textContent = '等待扫描...';
document.getElementById('res-qty').textContent = '等待扫描...';
currentScanData = null;
} catch (e) {
eda.sys_Message.showToastMessage('获取详情失败', ESYS_ToastMessageType.ERROR);
} finally {
btn.disabled = false;
btn.textContent = '加入列表';
}
};
window.updateQty = (index, val) => { importList[index].quantity = parseInt(val) || 0; };
window.removeItem = (index) => { importList.splice(index, 1); renderList(); };
@@ -114,6 +282,11 @@
};
document.getElementById('open-qr-btn').onclick = async () => {
qrDialog.classList.remove('hidden');
await updateCameraList();
};
document.getElementById('import-order-file-btn').onclick = async () => {
try {
const file = await eda.sys_FileSystem.openReadFileDialog(['xls', ['xlsx']], false);