This commit is contained in:
Fang_Zhijian 2026-02-10 14:02:36 +08:00
parent 887aa55930
commit ebdc516a55
7 changed files with 133 additions and 16 deletions

View File

@ -1,3 +1,9 @@
# 1.0.6
1. 增加远程扫码入库功能
2. 修改 README 文档
3. 修复已知问题
# 1.0.5 # 1.0.5
1. 新增扫码入库功能 1. 新增扫码入库功能

View File

@ -68,8 +68,10 @@ V2.2 用户菜单栏将直接出现“LEYE”选项V3 用户若未开启“
## 开源许可 ## 开源许可
本扩展使用以下开源软件: 本扩展使用以下开源软件:
- [jsQR](https://www.jsdelivr.com/package/npm/jsqr):二维码解析库,使用 [Apache License 2.0](https://choosealicense.com/licenses/apache-2.0/) 许可协议授权 - [SheetJS](https://www.npmjs.com/package/xlsx)Excel 解析库,使用 [Apache License 2.0](https://choosealicense.com/licenses/apache-2.0/) 许可协议授权
- [xlsx](https://www.jsdelivr.com/package/npm/xlsx)Excel 解析库,使用 [Apache License 2.0](https://choosealicense.com/licenses/apache-2.0/) 许可协议授权 - [jsQR](https://www.npmjs.com/package/jsqr):二维码解析库,使用 [Apache License 2.0](https://choosealicense.com/licenses/apache-2.0/) 许可协议授权
- [Tailwind CSS](https://www.jsdelivr.com/package/npm/tailwindcss)CSS 框架,使用 [MIT License](https://choosealicense.com/licenses/mit/) 许可协议授权 - [node-qrcode](https://www.npmjs.com/package/qrcode):二维码生成库,使用 [MIT License](https://choosealicense.com/licenses/mit/) 许可协议授权
- [MQTT.js](https://www.npmjs.com/package/mqtt)MQTT 客户端库,使用 [MIT License](https://choosealicense.com/licenses/mit/) 许可协议授权
- [Tailwind CSS](https://www.npmjs.com/package/tailwindcss)CSS 框架,使用 [MIT License](https://choosealicense.com/licenses/mit/) 许可协议授权
本扩展使用 [Apache License 2.0](https://choosealicense.com/licenses/apache-2.0/) 开源许可协议,商业/教育用途请考虑联系开发者获取常规版。 本扩展使用 [Apache License 2.0](https://choosealicense.com/licenses/apache-2.0/) 开源许可协议,商业/教育用途请考虑联系开发者获取常规版。

View File

@ -3,7 +3,7 @@
"uuid": "944f7c94a8ca485e848f1118effcbb9a", "uuid": "944f7c94a8ca485e848f1118effcbb9a",
"displayName": "LEYE", "displayName": "LEYE",
"description": "LEYE 电子元器件库存管理系统 EDA 联动扩展", "description": "LEYE 电子元器件库存管理系统 EDA 联动扩展",
"version": "1.0.5", "version": "1.0.6",
"publisher": "Mr_Fang", "publisher": "Mr_Fang",
"engines": { "engines": {
"eda": "^3.2.80" "eda": "^3.2.80"

View File

@ -596,6 +596,10 @@ video {
visibility: collapse; visibility: collapse;
} }
.static {
position: static;
}
.fixed { .fixed {
position: fixed; position: fixed;
} }
@ -644,6 +648,10 @@ video {
margin: 0.75rem; margin: 0.75rem;
} }
.m-auto {
margin: auto;
}
.mx-auto { .mx-auto {
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
@ -1086,6 +1094,11 @@ video {
border-color: rgb(219 234 254 / var(--tw-border-opacity, 1)); border-color: rgb(219 234 254 / var(--tw-border-opacity, 1));
} }
.border-blue-300 {
--tw-border-opacity: 1;
border-color: rgb(147 197 253 / var(--tw-border-opacity, 1));
}
.border-blue-600 { .border-blue-600 {
--tw-border-opacity: 1; --tw-border-opacity: 1;
border-color: rgb(37 99 235 / var(--tw-border-opacity, 1)); border-color: rgb(37 99 235 / var(--tw-border-opacity, 1));

View File

@ -16,6 +16,8 @@
</style> </style>
<script src="/iframe/js/xlsx.full.min.js" language="JavaScript"></script> <script src="/iframe/js/xlsx.full.min.js" language="JavaScript"></script>
<script src="/iframe/js/jsQR.min.js" language="JavaScript"></script> <script src="/iframe/js/jsQR.min.js" language="JavaScript"></script>
<script src="/iframe/js/qrcode.min.js" language="JavaScript"></script>
<script src="/iframe/js/mqtt.min.js" language="JavaScript"></script>
</head> </head>
<body class="bg-gray-100 font-sans text-sm"> <body class="bg-gray-100 font-sans text-sm">
<div id="fixed-window" class="bg-gray-50 flex flex-col overflow-hidden mx-auto"> <div id="fixed-window" class="bg-gray-50 flex flex-col overflow-hidden mx-auto">
@ -124,6 +126,8 @@
let importList = []; let importList = [];
let videoStream = null; let videoStream = null;
let currentScanData = null; let currentScanData = null;
let mqttClient = null;
const myDeviceId = Math.random().toString(36).substring(2, 10);
function renderList() { function renderList() {
if (importList.length === 0) { if (importList.length === 0) {
@ -156,9 +160,12 @@
try { try {
const devices = await navigator.mediaDevices.enumerateDevices(); const devices = await navigator.mediaDevices.enumerateDevices();
const videoDevices = devices.filter(device => device.kind === 'videoinput'); const videoDevices = devices.filter(device => device.kind === 'videoinput');
cameraSelect.innerHTML = videoDevices.map(d => let options = videoDevices.map(d =>
`<option value="${d.deviceId}">${d.label || '摄像头 ' + d.deviceId.slice(0, 5)}</option>` `<option value="${d.deviceId}">${d.label || '摄像头 ' + d.deviceId.slice(0, 5)}</option>`
).join(''); ).join('');
options += `<option value="WEBSOCKET_MODE">WebSocket 远程扫码</option>`;
cameraSelect.innerHTML = options;
} catch (e) { } catch (e) {
eda.sys_Message.showToastMessage('无法获取摄像头列表: ' + e.message, ESYS_ToastMessageType.ERROR); eda.sys_Message.showToastMessage('无法获取摄像头列表: ' + e.message, ESYS_ToastMessageType.ERROR);
} }
@ -168,11 +175,12 @@
stopCamera(); stopCamera();
qrDialog.classList.add('hidden'); qrDialog.classList.add('hidden');
if (currentScanData) {
document.getElementById('res-cid').textContent = currentScanData.lcscId; document.getElementById('res-cid').textContent = currentScanData.lcscId;
document.getElementById('res-pm').textContent = currentScanData.name; document.getElementById('res-pm').textContent = currentScanData.name;
document.getElementById('res-qty').textContent = currentScanData.quantity; document.getElementById('res-qty').textContent = currentScanData.quantity;
currentScanData = null; currentScanData = null;
}
}; };
function stopCamera() { function stopCamera() {
@ -180,16 +188,68 @@
videoStream.getTracks().forEach(track => track.stop()); videoStream.getTracks().forEach(track => track.stop());
videoStream = null; videoStream = null;
} }
qrVideo.parentElement.classList.remove('scanning');
if (mqttClient) {
console.log("正在断开 MQTT 远程连接...");
mqttClient.end(true); // 强制关闭连接
mqttClient = null;
} }
document.getElementById('btn-start-camera').onclick = async () => { qrVideo.style.display = 'block';
stopCamera(); const oldQr = document.getElementById('remote-qr-canvas');
const deviceId = cameraSelect.value; if (oldQr) oldQr.remove();
if(qrVideo.parentElement) {
qrVideo.parentElement.classList.remove('scanning');
}
}
async function startRemoteMode() {
if (mqttClient) {
mqttClient.end(true);
}
const broker = 'wss://test.mosquitto.org:8081';
const topic = `leye/scan/${myDeviceId}`;
mqttClient = mqtt.connect(broker);
mqttClient.on('connect', () => {
mqttClient.subscribe(topic);
eda.sys_Message.showToastMessage('MQTT 远程模式启动', ESYS_ToastMessageType.SUCCESS);
const scanUrl = `https://leye.dragon.edu.kg/scan.html?id=${myDeviceId}`;
qrVideo.style.display = 'none';
let qrCanvas = document.getElementById('remote-qr-canvas') || createQrCanvas();
QRCode.toCanvas(qrCanvas, scanUrl, { width: 250 });
});
mqttClient.on('message', (t, message) => {
try {
const msg = JSON.parse(message.toString());
if (msg.type === 'SCAN_RESULT') {
parseQrData(msg.content);
eda.sys_Message.showToastMessage('远程扫码成功', ESYS_ToastMessageType.INFO);
}
} catch (e) {
console.error("MQTT数据解析失败", e);
}
});
}
function createQrCanvas() {
const canvas = document.createElement('canvas');
canvas.id = 'remote-qr-canvas';
canvas.className = "absolute inset-0 w-full h-full p-4 bg-white";
qrVideo.parentElement.appendChild(canvas);
return canvas;
}
async function startLocalCamera(deviceId) {
const constraints = { const constraints = {
video: deviceId ? { deviceId: { exact: deviceId } } : { facingMode: 'environment' } video: deviceId ? { deviceId: { exact: deviceId } } : { facingMode: 'environment' }
}; };
try { try {
videoStream = await navigator.mediaDevices.getUserMedia(constraints); videoStream = await navigator.mediaDevices.getUserMedia(constraints);
qrVideo.srcObject = videoStream; qrVideo.srcObject = videoStream;
@ -199,6 +259,17 @@
} catch (e) { } catch (e) {
eda.sys_Message.showToastMessage('启动摄像头失败', ESYS_ToastMessageType.ERROR); eda.sys_Message.showToastMessage('启动摄像头失败', ESYS_ToastMessageType.ERROR);
} }
}
document.getElementById('btn-start-camera').onclick = async () => {
stopCamera();
const mode = cameraSelect.value;
if (mode === "WEBSOCKET_MODE") {
startRemoteMode();
} else {
startLocalCamera(mode);
}
}; };
document.getElementById('btn-scan').onclick = () => { document.getElementById('btn-scan').onclick = () => {
@ -429,7 +500,6 @@
let successCount = 0; let successCount = 0;
let failCount = 0; let failCount = 0;
const SERVER = await eda.sys_Storage.getExtensionUserConfig('server-host') || 'http://localhost:21816';
for (const item of toSave) { for (const item of toSave) {
try { try {

19
iframe/js/mqtt.min.js vendored Normal file

File diff suppressed because one or more lines are too long

7
iframe/js/qrcode.min.js vendored Normal file

File diff suppressed because one or more lines are too long