Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8950b56476 | |||
| 7e1fc83891 | |||
| 01e79bcf93 | |||
| b0f13bc3f1 | |||
| 3118185b31 | |||
| 6a5f4aaed0 | |||
| ebdc516a55 | |||
| 887aa55930 |
@ -17,5 +17,5 @@
|
||||
/package-lock.json
|
||||
/package.json
|
||||
/tsconfig.json
|
||||
/backend/
|
||||
debug.log
|
||||
/iframe/js/s_*
|
||||
|
||||
30
CHANGELOG.md
30
CHANGELOG.md
@ -1,3 +1,33 @@
|
||||
# 1.0.9
|
||||
|
||||
1. 修复全在线客户端无法获取器件数据的问题
|
||||
2. 扩展包不再打包服务端源码
|
||||
3. 更新 README 文档
|
||||
|
||||
# 1.0.8
|
||||
|
||||
1. 修复“允许拉起服务端”配置项无效的问题
|
||||
2. 修复网页端无法获取器件数据的问题
|
||||
|
||||
# 1.0.7
|
||||
|
||||
1. 新增“出库自动填充 BOM”选项:开启后,打开出库界面将自动填充 BOM
|
||||
2. 新增“自动检查更新”选项:开启后,插件将在 EDA 启动时自动检查更新并提示用户
|
||||
3. 移除不必要的依赖包
|
||||
|
||||
# 1.0.6
|
||||
|
||||
1. 增加远程扫码入库功能
|
||||
2. 修改 README 文档
|
||||
3. 修复已知问题
|
||||
|
||||
# 1.0.5
|
||||
|
||||
1. 新增扫码入库功能
|
||||
2. 修改 README 文档
|
||||
3. 新增检查更新功能
|
||||
4. 修复已知问题
|
||||
|
||||
# 1.0.3
|
||||
|
||||
1. 由于可能存在的安全原因,移除了使用 Cookie 获取立创商城订单的功能,相关功能已被废弃
|
||||
|
||||
33
README.md
33
README.md
@ -10,14 +10,16 @@ LEYE 电子元器件库存管理系统 EDA 联动扩展
|
||||
|
||||
本扩展允许用户通过立创商城 C 编号、立创商城订单导入元器件,支持从库存内查询、放置器件,支持通过 BOM 批量出库。
|
||||
|
||||
使用本扩展需要在本地安装 [LEYE Service 服务端](https://lrurl.top/LeyeService)(目前仅提供 Windows 版),数据均储存在本地,不会上传。
|
||||
使用本扩展需要在本地安装 [LEYE Service 服务端](https://lrurl.top/LeyeService)(目前仅提供 Windows 版,[服务端开源](/backend/server.js)),数据均储存在本地,不会上传。
|
||||
|
||||
## [介绍视频](https://www.bilibili.com/video/BV1nvcFzpEuP/)
|
||||
|
||||
https://www.bilibili.com/video/BV1nvcFzpEuP/
|
||||
|
||||
## 如何使用
|
||||
|
||||
安装本扩展后**需要给予扩展外部交互**权限以与服务端交互:
|
||||
|
||||
- V2.2:设置-扩展-扩展管理器-LEYE-允许外部交互;
|
||||
- V3:高级-扩展管理器-已安装-LEYE-配置-允许外部交互。
|
||||
|
||||
@ -37,10 +39,21 @@ V2.2 用户菜单栏将直接出现“LEYE”选项;V3 用户若未开启“
|
||||
|
||||
## 器件入库
|
||||
|
||||
扩展支持从立创商城单 Excel 文档以及 CID 导入器件。
|
||||
扩展支持从立创商城单 Excel 文档、立创商城物料二维码以及 CID 导入器件。
|
||||
|
||||

|
||||
|
||||
> **Tip: 扫描立创商城二维码导入器件** _V1.0.5+_
|
||||
>
|
||||
> 在 V1.0.5 版本以上,可以通过扫描立创商城物料包装上的二维码导入器件,扫描后会自动识别 CID 并导入对应器件。
|
||||
> 
|
||||
|
||||
> **Tip: 使用手机扫码导入器件** _V1.0.6+_
|
||||
>
|
||||
> 在 V1.0.6 版本以上,可以在扫码界面选择“WebSocket 远程扫码”,点击“打开摄像头”按钮后用手机扫描二维码即可打开远程端。
|
||||
> 远程端同样支持扫描立创商城物料包装上的二维码导入器件,扫描后会自动识别 CID 并导入对应器件。
|
||||
> 
|
||||
|
||||
### 批量出库
|
||||
|
||||
打开本页面后将自动整理 BOM 与 LEYE 库存相比较,非立创商城器件(无 CID)显示为红底,不在 LEYE 库存内的器件显示为黄底,在 LEYE 库存内的器件显示为绿底。
|
||||
@ -51,10 +64,10 @@ V2.2 用户菜单栏将直接出现“LEYE”选项;V3 用户若未开启“
|
||||
|
||||
## 配置项说明
|
||||
|
||||
| 配置项 | 说明 | 默认值 |
|
||||
|:-------------|:-----------------------------------------------------------|:-------------------------|
|
||||
| 服务器地址 | LEYE Service 服务器地址 | `http://localhost:21816` |
|
||||
| 允许拉起服务端 | 允许通过 [`leye://open`](leye://open) 拉起本地安装的 LEYE Service 服务端 | `true` |
|
||||
| 配置项 | 说明 | 默认值 |
|
||||
| :------------- | :----------------------------------------------------------------------- | :----------------------- |
|
||||
| 服务器地址 | LEYE Service 服务器地址 | `http://localhost:21816` |
|
||||
| 允许拉起服务端 | 允许通过 [`leye://open`](leye://open) 拉起本地安装的 LEYE Service 服务端 | `true` |
|
||||
|
||||
## 已知问题
|
||||
|
||||
@ -62,4 +75,12 @@ V2.2 用户菜单栏将直接出现“LEYE”选项;V3 用户若未开启“
|
||||
|
||||
## 开源许可
|
||||
|
||||
本扩展使用以下开源软件:
|
||||
|
||||
- [SheetJS](https://www.npmjs.com/package/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/) 许可协议授权
|
||||
- [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/) 开源许可协议,商业/教育用途请考虑联系开发者获取常规版。
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
"uuid": "944f7c94a8ca485e848f1118effcbb9a",
|
||||
"displayName": "LEYE",
|
||||
"description": "LEYE 电子元器件库存管理系统 EDA 联动扩展",
|
||||
"version": "1.0.3",
|
||||
"version": "1.0.9",
|
||||
"publisher": "Mr_Fang",
|
||||
"engines": {
|
||||
"eda": "^3.2.80"
|
||||
@ -14,9 +14,7 @@
|
||||
"url": "https://gitea.miri.site/Mr_Fang/eext-leye"
|
||||
},
|
||||
"categories": "Schematic",
|
||||
"keywords": [
|
||||
"Tools", "库管", "库存管理"
|
||||
],
|
||||
"keywords": ["Tools", "库管", "库存管理"],
|
||||
"images": {
|
||||
"logo": "./images/logo.png"
|
||||
},
|
||||
|
||||
@ -221,18 +221,13 @@
|
||||
document.getElementById('check').addEventListener('click', function (event) {
|
||||
event.preventDefault();
|
||||
|
||||
eda.sys_Message.showToastMessage(
|
||||
'🥺 暂不支持',
|
||||
ESYS_ToastMessageType.ERROR
|
||||
);
|
||||
|
||||
/* eda.sys_ClientUrl
|
||||
.request('https://ext.lceda.cn/api/v1/extensions/his_version_list?bizKey=', 'GET')
|
||||
eda.sys_ClientUrl
|
||||
.request('https://leye.dragon.edu.kg/release/eext.ver.json', 'GET')
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
if (data && data.code === 0) {
|
||||
const his_list = data.result;
|
||||
const latestVersion = his_list[0].version;
|
||||
if (data) {
|
||||
const his_list = data.versions;
|
||||
const latestVersion = his_list[0].ver;
|
||||
const currentVersion = document.getElementById('version').textContent.replace('V', '');
|
||||
// 比较版本号,latestVersion 和 currentVersion
|
||||
const latestParts = latestVersion.split('.').map(Number);
|
||||
@ -251,7 +246,7 @@
|
||||
if (isNewVersionAvailable) {
|
||||
eda.sys_Message.showToastMessage(`😋 有新版本可用: V${latestVersion}`, ESYS_ToastMessageType.INFO);
|
||||
document.getElementById('tip').innerHTML =
|
||||
`<a href="https://ext.lceda.cn/item/fangs233/fangs-hyper-export" target="_blank" class="text-blue-600 hover:text-blue-800">前往更新新版本</a>`;
|
||||
`<a href="https://lrurl.top/LeyeEEXT" target="_blank" class="text-blue-600 hover:text-blue-800">前往更新新版本</a>`;
|
||||
} else {
|
||||
eda.sys_Message.showToastMessage('👍 当前已是最新版本', ESYS_ToastMessageType.SUCCESS);
|
||||
}
|
||||
@ -262,7 +257,7 @@
|
||||
undefined,
|
||||
undefined,
|
||||
'去瞅一眼',
|
||||
"eda.sys_Window.open('https://ext.lceda.cn/item/fangs233/fangs-hyper-export')",
|
||||
"eda.sys_Window.open('https://lrurl.top/LeyeEEXT')",
|
||||
);
|
||||
}
|
||||
})
|
||||
@ -274,9 +269,9 @@
|
||||
undefined,
|
||||
undefined,
|
||||
'去瞅一眼',
|
||||
"eda.sys_Window.open('https://ext.lceda.cn/item/fangs233/fangs-hyper-export')",
|
||||
"eda.sys_Window.open('https://lrurl.top/LeyeEEXT')",
|
||||
);
|
||||
}); */
|
||||
});
|
||||
});
|
||||
|
||||
document.getElementById('afdian').addEventListener('click', function (event) {
|
||||
|
||||
1543
iframe/css/index.css
1543
iframe/css/index.css
File diff suppressed because it is too large
Load Diff
@ -1,151 +1,188 @@
|
||||
<!doctype html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>批量出库 - LEYE</title>
|
||||
<link href="/iframe/css/index.css" rel="stylesheet" />
|
||||
<style>
|
||||
#fixed-window { width: 1000px; height: 600px; border: 1px solid #e5e7eb; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); }
|
||||
.table-container { height: calc(600px - 80px - 44px - 64px); overflow-y: auto; }
|
||||
.bg-match { background-color: #f0fdf4; } /* 淡绿色:匹配成功 */
|
||||
.bg-no-match { background-color: #fefcf2; } /* 淡黄色:不匹配 */
|
||||
.bg-no-cid { background-color: #fef2f2; } /* 淡红色:无 CID */
|
||||
.sticky-header { position: sticky; top: 0; z-index: 10; }
|
||||
.designator-cell { max-width: 120px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
</style>
|
||||
</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">
|
||||
<header class="flex-shrink-0 bg-white border-b p-4 flex items-center space-x-3">
|
||||
<span class="font-medium text-gray-700">立创商城编号导入</span>
|
||||
<input id="input-cid" type="text" placeholder="CID" class="w-48 px-3 py-1.5 border rounded-md outline-none focus:ring-2 focus:ring-blue-500" />
|
||||
<input id="input-qty" type="number" placeholder="数量" class="w-24 px-3 py-1.5 border rounded-md outline-none focus:ring-2 focus:ring-blue-500" />
|
||||
<button id="add-manual-btn" class="px-4 py-1.5 bg-blue-600 text-white rounded-md hover:bg-blue-700">添加</button>
|
||||
</header>
|
||||
|
||||
<div class="px-4 py-2 bg-gray-50 border-b flex justify-between items-center">
|
||||
<div class="space-x-2">
|
||||
<button id="select-all" class="px-3 py-1 bg-white border border-gray-300 rounded hover:bg-gray-100 text-xs">全选</button>
|
||||
<button id="select-reverse" class="px-3 py-1 bg-white border border-gray-300 rounded hover:bg-gray-100 text-xs">反选</button>
|
||||
</div>
|
||||
<div class="text-gray-500 text-xs">
|
||||
待出库总数:<span id="list-count" class="font-bold text-blue-600">0</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<main class="flex-grow bg-white m-3 border rounded-lg overflow-hidden shadow-inner">
|
||||
<div class="table-container overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200">
|
||||
<thead class="bg-gray-100 sticky-header text-xs text-gray-600">
|
||||
<tr>
|
||||
<th class="w-12 px-2 py-3 text-center">选择</th>
|
||||
<th class="px-3 py-3 text-left">型号</th>
|
||||
<th class="px-3 py-3 text-left">位号</th>
|
||||
<th class="px-3 py-3 text-left">值</th>
|
||||
<th class="px-3 py-3 text-left">封装</th>
|
||||
<th class="px-3 py-3 text-left">厂商</th>
|
||||
<th class="w-20 px-3 py-3 text-center">数量</th>
|
||||
<th class="w-32 px-3 py-3 text-left">CID</th>
|
||||
<th class="w-16 px-3 py-3 text-center">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="outbound-table-body" class="divide-y divide-gray-200 text-xs">
|
||||
<tr><td colspan="9" class="text-center py-20 text-gray-400">获取 BOM...</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="flex-shrink-0 p-4 bg-white border-t flex justify-end space-x-3">
|
||||
<p class="pr-24 text-red-500 py-2">建议“器件标准化”后使用此功能</p>
|
||||
<button id="cancel-btn" class="px-6 py-2 border border-gray-300 rounded-md hover:bg-gray-100">取消</button>
|
||||
<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="outbound-btn" class="px-6 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 font-bold">一键出库</button>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', async function () {
|
||||
const tableBody = document.getElementById('outbound-table-body');
|
||||
const listCount = document.getElementById('list-count');
|
||||
let outboundList = [];
|
||||
let cachedCIDs = new Set();
|
||||
|
||||
async function initCache() {
|
||||
const cachedRaw = await eda.sys_Storage.getExtensionUserConfig('cache-leye-device-details');
|
||||
try {
|
||||
const details = cachedRaw ? JSON.parse(cachedRaw) : [];
|
||||
cachedCIDs = new Set(details.map(d => String(d.lcscId).toUpperCase()));
|
||||
} catch(e) { cachedCIDs = new Set(); }
|
||||
}
|
||||
|
||||
function parseProperty(comp, key) {
|
||||
if (key === "Manufacturer Part") return comp.getState_ManufacturerId();
|
||||
const props = comp.getState_OtherProperty() || {};
|
||||
return props[key] || '-';
|
||||
}
|
||||
|
||||
function resolveModelName(comp) {
|
||||
let name = comp.getState_Name();
|
||||
if (name && name.startsWith('=')) {
|
||||
const key = name.replace(/[={}]/g, '');
|
||||
return parseProperty(comp, key);
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>批量出库 - LEYE</title>
|
||||
<link href="/iframe/css/index.css" rel="stylesheet" />
|
||||
<style>
|
||||
#fixed-window {
|
||||
width: 1000px;
|
||||
height: 600px;
|
||||
border: 1px solid #e5e7eb;
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
return name || '-';
|
||||
}
|
||||
|
||||
async function scanSchematic() {
|
||||
const all = await eda.sch_PrimitiveComponent.getAll("part", true);
|
||||
const groups = {};
|
||||
|
||||
all.forEach(comp => {
|
||||
const uuid = comp.getState_Component().uuid;
|
||||
if (!groups[uuid]) {
|
||||
groups[uuid] = {
|
||||
name: resolveModelName(comp),
|
||||
designators: [],
|
||||
value: parseProperty(comp, "Value"),
|
||||
footprint: parseProperty(comp, "Supplier Footprint"),
|
||||
brand: comp.getState_Manufacturer() || '-',
|
||||
lcscId: (comp.getState_SupplierId() || '').toUpperCase(),
|
||||
quantity: 0,
|
||||
selected: false
|
||||
};
|
||||
}
|
||||
groups[uuid].quantity++;
|
||||
groups[uuid].designators.push(comp.getState_Designator());
|
||||
});
|
||||
|
||||
outboundList = Object.keys(groups).map(uuid => {
|
||||
const item = groups[uuid];
|
||||
const match = item.lcscId && cachedCIDs.has(item.lcscId);
|
||||
return {
|
||||
...item,
|
||||
designatorStr: item.designators.sort().join(', '),
|
||||
uuid: uuid,
|
||||
selected: match
|
||||
};
|
||||
});
|
||||
renderTable();
|
||||
}
|
||||
|
||||
function renderTable() {
|
||||
if (outboundList.length === 0) {
|
||||
tableBody.innerHTML = `<tr><td colspan="9" class="text-center py-20 text-gray-400">BOM 为空</td></tr>`;
|
||||
listCount.textContent = '0';
|
||||
return;
|
||||
.table-container {
|
||||
height: calc(600px - 80px - 44px - 64px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
.bg-match {
|
||||
background-color: #f0fdf4;
|
||||
} /* 淡绿色:匹配成功 */
|
||||
.bg-no-match {
|
||||
background-color: #fefcf2;
|
||||
} /* 淡黄色:不匹配 */
|
||||
.bg-no-cid {
|
||||
background-color: #fef2f2;
|
||||
} /* 淡红色:无 CID */
|
||||
.sticky-header {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
}
|
||||
.designator-cell {
|
||||
max-width: 120px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
</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">
|
||||
<header class="flex-shrink-0 bg-white border-b p-4 flex items-center space-x-3">
|
||||
<span class="font-medium text-gray-700">立创商城编号导入</span>
|
||||
<input
|
||||
id="input-cid"
|
||||
type="text"
|
||||
placeholder="CID"
|
||||
class="w-48 px-3 py-1.5 border rounded-md outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
<input
|
||||
id="input-qty"
|
||||
type="number"
|
||||
placeholder="数量"
|
||||
class="w-24 px-3 py-1.5 border rounded-md outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
<button id="add-manual-btn" class="px-4 py-1.5 bg-blue-600 text-white rounded-md hover:bg-blue-700">添加</button>
|
||||
</header>
|
||||
|
||||
listCount.textContent = outboundList.length;
|
||||
tableBody.innerHTML = outboundList.map((item, index) => {
|
||||
const isMatch = item.lcscId && cachedCIDs.has(item.lcscId);
|
||||
let rowClass = 'bg-no-cid';
|
||||
if (item.lcscId) {
|
||||
rowClass = isMatch ? 'bg-match' : 'bg-no-match';
|
||||
<div class="px-4 py-2 bg-gray-50 border-b flex justify-between items-center">
|
||||
<div class="space-x-2">
|
||||
<button id="select-all" class="px-3 py-1 bg-white border border-gray-300 rounded hover:bg-gray-100 text-xs">全选</button>
|
||||
<button id="select-reverse" class="px-3 py-1 bg-white border border-gray-300 rounded hover:bg-gray-100 text-xs">反选</button>
|
||||
</div>
|
||||
<div class="text-gray-500 text-xs">待出库总数:<span id="list-count" class="font-bold text-blue-600">0</span></div>
|
||||
</div>
|
||||
|
||||
<main class="flex-grow bg-white m-3 border rounded-lg overflow-hidden shadow-inner">
|
||||
<div class="table-container overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200">
|
||||
<thead class="bg-gray-100 sticky-header text-xs text-gray-600">
|
||||
<tr>
|
||||
<th class="w-12 px-2 py-3 text-center">选择</th>
|
||||
<th class="px-3 py-3 text-left">型号</th>
|
||||
<th class="px-3 py-3 text-left">位号</th>
|
||||
<th class="px-3 py-3 text-left">值</th>
|
||||
<th class="px-3 py-3 text-left">封装</th>
|
||||
<th class="px-3 py-3 text-left">厂商</th>
|
||||
<th class="w-20 px-3 py-3 text-center">数量</th>
|
||||
<th class="w-32 px-3 py-3 text-left">CID</th>
|
||||
<th class="w-16 px-3 py-3 text-center">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="outbound-table-body" class="divide-y divide-gray-200 text-xs">
|
||||
<tr>
|
||||
<td colspan="9" class="text-center py-20 text-gray-400">获取 BOM...</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="flex-shrink-0 p-4 bg-white border-t flex justify-end space-x-3">
|
||||
<p class="pr-24 text-red-500 py-2">建议“器件标准化”后使用此功能</p>
|
||||
<button id="cancel-btn" class="px-6 py-2 border border-gray-300 rounded-md hover:bg-gray-100">取消</button>
|
||||
<button id="fill-bom-btn" class="px-6 py-2 border border-gray-300 rounded-md hover:bg-gray-100">应用 BOM</button>
|
||||
<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="outbound-btn" class="px-6 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 font-bold">一键出库</button>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', async function () {
|
||||
const tableBody = document.getElementById('outbound-table-body');
|
||||
const listCount = document.getElementById('list-count');
|
||||
let outboundList = [];
|
||||
let cachedCIDs = new Set();
|
||||
|
||||
async function initCache() {
|
||||
const cachedRaw = await eda.sys_Storage.getExtensionUserConfig('cache-leye-device-details');
|
||||
try {
|
||||
const details = cachedRaw ? JSON.parse(cachedRaw) : [];
|
||||
cachedCIDs = new Set(details.map((d) => String(d.lcscId).toUpperCase()));
|
||||
} catch (e) {
|
||||
cachedCIDs = new Set();
|
||||
}
|
||||
}
|
||||
|
||||
return `
|
||||
function parseProperty(comp, key) {
|
||||
if (key === 'Manufacturer Part') return comp.getState_ManufacturerId();
|
||||
const props = comp.getState_OtherProperty() || {};
|
||||
return props[key] || '-';
|
||||
}
|
||||
|
||||
function resolveModelName(comp) {
|
||||
let name = comp.getState_Name();
|
||||
if (name && name.startsWith('=')) {
|
||||
const key = name.replace(/[={}]/g, '');
|
||||
return parseProperty(comp, key);
|
||||
}
|
||||
return name || '-';
|
||||
}
|
||||
|
||||
async function scanSchematic() {
|
||||
const all = await eda.sch_PrimitiveComponent.getAll('part', true);
|
||||
const groups = {};
|
||||
|
||||
all.forEach((comp) => {
|
||||
const uuid = comp.getState_Component().uuid;
|
||||
if (!groups[uuid]) {
|
||||
groups[uuid] = {
|
||||
name: resolveModelName(comp),
|
||||
designators: [],
|
||||
value: parseProperty(comp, 'Value'),
|
||||
footprint: parseProperty(comp, 'Supplier Footprint'),
|
||||
brand: comp.getState_Manufacturer() || '-',
|
||||
lcscId: (comp.getState_SupplierId() || '').toUpperCase(),
|
||||
quantity: 0,
|
||||
selected: false,
|
||||
};
|
||||
}
|
||||
groups[uuid].quantity++;
|
||||
groups[uuid].designators.push(comp.getState_Designator());
|
||||
});
|
||||
|
||||
outboundList = Object.keys(groups).map((uuid) => {
|
||||
const item = groups[uuid];
|
||||
const match = item.lcscId && cachedCIDs.has(item.lcscId);
|
||||
return {
|
||||
...item,
|
||||
designatorStr: item.designators.sort().join(', '),
|
||||
uuid: uuid,
|
||||
selected: match,
|
||||
};
|
||||
});
|
||||
renderTable();
|
||||
}
|
||||
|
||||
function renderTable() {
|
||||
if (outboundList.length === 0) {
|
||||
tableBody.innerHTML = `<tr><td colspan="9" class="text-center py-20 text-gray-400">BOM 为空</td></tr>`;
|
||||
listCount.textContent = '0';
|
||||
return;
|
||||
}
|
||||
|
||||
listCount.textContent = outboundList.length;
|
||||
tableBody.innerHTML = outboundList
|
||||
.map((item, index) => {
|
||||
const isMatch = item.lcscId && cachedCIDs.has(item.lcscId);
|
||||
let rowClass = 'bg-no-cid';
|
||||
if (item.lcscId) {
|
||||
rowClass = isMatch ? 'bg-match' : 'bg-no-match';
|
||||
}
|
||||
|
||||
return `
|
||||
<tr class="${rowClass}">
|
||||
<td class="px-2 py-2 text-center">
|
||||
<input type="checkbox" class="row-checkbox" data-index="${index}" ${item.selected ? 'checked' : ''} ${!isMatch ? 'title="非库内器件无法选择"' : ''}>
|
||||
@ -167,178 +204,178 @@
|
||||
<button onclick="removeItem(${index})" class="text-red-500 hover:underline">删除</button>
|
||||
</td>
|
||||
</tr>`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
tableBody.addEventListener('change', (e) => {
|
||||
if (e.target.classList.contains('row-checkbox')) {
|
||||
const idx = e.target.dataset.index;
|
||||
const item = outboundList[idx];
|
||||
const isMatch = item.lcscId && cachedCIDs.has(item.lcscId);
|
||||
|
||||
if (e.target.checked && !isMatch) {
|
||||
e.target.checked = false;
|
||||
item.selected = false;
|
||||
eda.sys_Message.showToastMessage('只有已入库的器件才能被勾选', ESYS_ToastMessageType.WARNING);
|
||||
return;
|
||||
})
|
||||
.join('');
|
||||
}
|
||||
item.selected = e.target.checked;
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('select-all').onclick = () => {
|
||||
outboundList.forEach(item => {
|
||||
if (item.lcscId && cachedCIDs.has(item.lcscId)) {
|
||||
item.selected = true;
|
||||
}
|
||||
});
|
||||
renderTable();
|
||||
};
|
||||
tableBody.addEventListener('change', (e) => {
|
||||
if (e.target.classList.contains('row-checkbox')) {
|
||||
const idx = e.target.dataset.index;
|
||||
const item = outboundList[idx];
|
||||
const isMatch = item.lcscId && cachedCIDs.has(item.lcscId);
|
||||
|
||||
document.getElementById('select-reverse').onclick = () => {
|
||||
outboundList.forEach(item => {
|
||||
if (item.lcscId && cachedCIDs.has(item.lcscId)) {
|
||||
item.selected = !item.selected;
|
||||
}
|
||||
});
|
||||
renderTable();
|
||||
};
|
||||
|
||||
window.updateRow = (index, key, val) => {
|
||||
if (key === 'quantity') outboundList[index].quantity = parseInt(val) || 0;
|
||||
if (key === 'lcscId') {
|
||||
outboundList[index].lcscId = val.toUpperCase();
|
||||
if (!cachedCIDs.has(outboundList[index].lcscId)) {
|
||||
outboundList[index].selected = false;
|
||||
}
|
||||
renderTable();
|
||||
}
|
||||
};
|
||||
|
||||
window.removeItem = (index) => {
|
||||
outboundList.splice(index, 1);
|
||||
renderTable();
|
||||
};
|
||||
|
||||
document.getElementById('add-manual-btn').onclick = async () => {
|
||||
const cid = document.getElementById('input-cid').value.trim().toUpperCase();
|
||||
const qty = parseInt(document.getElementById('input-qty').value);
|
||||
if (!cid || !qty) return;
|
||||
|
||||
const devs = await eda.lib_Device.getByLcscIds([cid]);
|
||||
let detail = { name: '-', value: '-', footprint: '-', brand: '-', designatorStr: '手动添加', selected: false };
|
||||
|
||||
if (devs[0]) {
|
||||
const infoRes = await eda.sys_ClientUrl.request('https://client/api/v2/devices/' + devs[0].uuid, 'GET', null, { headers: { path: '0819f05c4eef4c71ace90d822a990e87' } });
|
||||
const info = (await infoRes.json()).result;
|
||||
detail.name = info.attributes['Manufacturer Part'] || '-';
|
||||
detail.value = info.attributes['Value'] || '-';
|
||||
detail.footprint = info.attributes['Supplier Footprint'] || '-';
|
||||
detail.brand = info.attributes['Manufacturer'] || '-';
|
||||
if (cachedCIDs.has(cid)) detail.selected = true;
|
||||
}
|
||||
|
||||
outboundList.push({ ...detail, lcscId: cid, quantity: qty });
|
||||
renderTable();
|
||||
document.getElementById('input-cid').value = '';
|
||||
};
|
||||
|
||||
document.getElementById('clear-list-btn').onclick = () => {
|
||||
outboundList = [];
|
||||
renderTable();
|
||||
};
|
||||
|
||||
document.getElementById('outbound-btn').onclick = async () => {
|
||||
const selectedItems = outboundList.filter(item => item.selected && item.lcscId);
|
||||
|
||||
if (selectedItems.length === 0) {
|
||||
return eda.sys_Message.showToastMessage('请先勾选要出库的器件', ESYS_ToastMessageType.WARNING);
|
||||
}
|
||||
|
||||
const SERVER = await eda.sys_Storage.getExtensionUserConfig('server-host') || 'http://localhost:21816';
|
||||
const AUTO_RUN = await eda.sys_Storage.getExtensionUserConfig('server-auto-run') || true;
|
||||
let successCount = 0;
|
||||
let failItems = [];
|
||||
|
||||
// eda.sys_Message.showToastMessage(`正在处理 ${selectedItems.length} 项出库...`, ESYS_ToastMessageType.INFO);
|
||||
|
||||
for (const item of selectedItems) {
|
||||
try {
|
||||
let getRes = await eda.sys_ClientUrl.request(
|
||||
`${SERVER}/getLeyeList?lcscId=${item.lcscId}`,
|
||||
'GET'
|
||||
);
|
||||
let getResult = await getRes.json();
|
||||
|
||||
if (AUTO_RUN && !getResult.success) {
|
||||
window.open('leye://open');
|
||||
for (let i = 0; i < 3 && !getResult.success; i++) {
|
||||
eda.sys_Message.showToastMessage('等待拉起本地服务端...', ESYS_ToastMessageType.INFO);
|
||||
getRes = await eda.sys_ClientUrl.request(
|
||||
`${SERVER}/getLeyeList?lcscId=${item.lcscId}`,
|
||||
'GET'
|
||||
);
|
||||
getResult = await getRes.json();
|
||||
await new Promise(resolve => setTimeout(resolve, 1500));
|
||||
}
|
||||
}
|
||||
|
||||
if (getResult.success && getResult.data && getResult.data.length > 0) {
|
||||
const remoteData = getResult.data[0];
|
||||
const remoteId = remoteData.id;
|
||||
const remoteQty = remoteData.quantity;
|
||||
|
||||
const newQuantity = remoteQty - item.quantity;
|
||||
|
||||
if (newQuantity < 0) {
|
||||
failItems.push(`${item.lcscId} (库存不足: 剩${remoteQty})`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const postRes = await eda.sys_ClientUrl.request(
|
||||
SERVER + '/editLeyeList',
|
||||
'POST',
|
||||
JSON.stringify({
|
||||
id: remoteId,
|
||||
quantity: newQuantity
|
||||
}),
|
||||
{ headers: { 'Content-Type': 'application/json' } }
|
||||
);
|
||||
|
||||
const postResult = await postRes.json();
|
||||
if (postResult.success) {
|
||||
successCount++;
|
||||
if (e.target.checked && !isMatch) {
|
||||
e.target.checked = false;
|
||||
item.selected = false;
|
||||
} else {
|
||||
failItems.push(`${item.lcscId} (更新失败)`);
|
||||
eda.sys_Message.showToastMessage('只有已入库的器件才能被勾选', ESYS_ToastMessageType.WARNING);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
failItems.push(`${item.lcscId} (库内未找到)`);
|
||||
item.selected = e.target.checked;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`出库请求异常: ${item.lcscId}`, e);
|
||||
eda.sys_Log.add(`出库请求异常: ${item.lcscId}: ${e.message}`)
|
||||
failItems.push(`${item.lcscId} (网络异常)`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
renderTable();
|
||||
document.getElementById('select-all').onclick = () => {
|
||||
outboundList.forEach((item) => {
|
||||
if (item.lcscId && cachedCIDs.has(item.lcscId)) {
|
||||
item.selected = true;
|
||||
}
|
||||
});
|
||||
renderTable();
|
||||
};
|
||||
|
||||
if (failItems.length === 0) {
|
||||
eda.sys_Message.showToastMessage(`成功出库 ${successCount} 项!`, ESYS_ToastMessageType.SUCCESS);
|
||||
} else {
|
||||
const errorMsg = failItems.join('; ');
|
||||
eda.sys_Message.showToastMessage(`出库完成:成功:${successCount},失败:${failItems.length}`,
|
||||
ESYS_ToastMessageType.WARNING);
|
||||
eda.sys_Log.add(errorMsg);
|
||||
}
|
||||
};
|
||||
document.getElementById('select-reverse').onclick = () => {
|
||||
outboundList.forEach((item) => {
|
||||
if (item.lcscId && cachedCIDs.has(item.lcscId)) {
|
||||
item.selected = !item.selected;
|
||||
}
|
||||
});
|
||||
renderTable();
|
||||
};
|
||||
|
||||
document.getElementById('cancel-btn').onclick = () => eda.sys_IFrame.closeIFrame();
|
||||
window.updateRow = (index, key, val) => {
|
||||
if (key === 'quantity') outboundList[index].quantity = parseInt(val) || 0;
|
||||
if (key === 'lcscId') {
|
||||
outboundList[index].lcscId = val.toUpperCase();
|
||||
if (!cachedCIDs.has(outboundList[index].lcscId)) {
|
||||
outboundList[index].selected = false;
|
||||
}
|
||||
renderTable();
|
||||
}
|
||||
};
|
||||
|
||||
await initCache();
|
||||
await scanSchematic();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
window.removeItem = (index) => {
|
||||
outboundList.splice(index, 1);
|
||||
renderTable();
|
||||
};
|
||||
|
||||
document.getElementById('add-manual-btn').onclick = async () => {
|
||||
const cid = document.getElementById('input-cid').value.trim().toUpperCase();
|
||||
const qty = parseInt(document.getElementById('input-qty').value);
|
||||
if (!cid || !qty) return;
|
||||
|
||||
const devs = await eda.lib_Device.getByLcscIds([cid]);
|
||||
let detail = { name: '-', value: '-', footprint: '-', brand: '-', designatorStr: '手动添加', selected: false };
|
||||
|
||||
if (devs[0]) {
|
||||
const EDA_HOST =
|
||||
eda.sys_Environment.isClient() && !eda.sys_Environment.isOnlineMode() ? 'https://client' : 'https://pro.lceda.cn';
|
||||
const infoRes = await eda.sys_ClientUrl.request(EDA_HOST + '/api/v2/devices/' + devs[0].uuid, 'GET', null, {
|
||||
headers: { path: '0819f05c4eef4c71ace90d822a990e87' },
|
||||
});
|
||||
const info = (await infoRes.json()).result;
|
||||
detail.name = info.attributes['Manufacturer Part'] || '-';
|
||||
detail.value = info.attributes['Value'] || '-';
|
||||
detail.footprint = info.attributes['Supplier Footprint'] || '-';
|
||||
detail.brand = info.attributes['Manufacturer'] || '-';
|
||||
if (cachedCIDs.has(cid)) detail.selected = true;
|
||||
}
|
||||
|
||||
outboundList.push({ ...detail, lcscId: cid, quantity: qty });
|
||||
renderTable();
|
||||
document.getElementById('input-cid').value = '';
|
||||
};
|
||||
|
||||
document.getElementById('clear-list-btn').onclick = () => {
|
||||
outboundList = [];
|
||||
renderTable();
|
||||
};
|
||||
|
||||
document.getElementById('outbound-btn').onclick = async () => {
|
||||
const selectedItems = outboundList.filter((item) => item.selected && item.lcscId);
|
||||
|
||||
if (selectedItems.length === 0) {
|
||||
return eda.sys_Message.showToastMessage('请先勾选要出库的器件', ESYS_ToastMessageType.WARNING);
|
||||
}
|
||||
|
||||
const SERVER = (await eda.sys_Storage.getExtensionUserConfig('server-host')) ?? 'http://localhost:21816';
|
||||
const AUTO_RUN = (await eda.sys_Storage.getExtensionUserConfig('server-auto-run')) ?? true;
|
||||
let successCount = 0;
|
||||
let failItems = [];
|
||||
|
||||
// eda.sys_Message.showToastMessage(`正在处理 ${selectedItems.length} 项出库...`, ESYS_ToastMessageType.INFO);
|
||||
|
||||
for (const item of selectedItems) {
|
||||
try {
|
||||
let getRes = await eda.sys_ClientUrl.request(`${SERVER}/getLeyeList?lcscId=${item.lcscId}`, 'GET');
|
||||
let getResult = await getRes.json();
|
||||
|
||||
if (AUTO_RUN && !getResult.success) {
|
||||
window.open('leye://open');
|
||||
for (let i = 0; i < 3 && !getResult.success; i++) {
|
||||
eda.sys_Message.showToastMessage('等待拉起本地服务端...', ESYS_ToastMessageType.INFO);
|
||||
getRes = await eda.sys_ClientUrl.request(`${SERVER}/getLeyeList?lcscId=${item.lcscId}`, 'GET');
|
||||
getResult = await getRes.json();
|
||||
await new Promise((resolve) => setTimeout(resolve, 1500));
|
||||
}
|
||||
}
|
||||
|
||||
if (getResult.success && getResult.data && getResult.data.length > 0) {
|
||||
const remoteData = getResult.data[0];
|
||||
const remoteId = remoteData.id;
|
||||
const remoteQty = remoteData.quantity;
|
||||
|
||||
const newQuantity = remoteQty - item.quantity;
|
||||
|
||||
if (newQuantity < 0) {
|
||||
failItems.push(`${item.lcscId} (库存不足: 剩${remoteQty})`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const postRes = await eda.sys_ClientUrl.request(
|
||||
SERVER + '/editLeyeList',
|
||||
'POST',
|
||||
JSON.stringify({
|
||||
id: remoteId,
|
||||
quantity: newQuantity,
|
||||
}),
|
||||
{ headers: { 'Content-Type': 'application/json' } },
|
||||
);
|
||||
|
||||
const postResult = await postRes.json();
|
||||
if (postResult.success) {
|
||||
successCount++;
|
||||
item.selected = false;
|
||||
} else {
|
||||
failItems.push(`${item.lcscId} (更新失败)`);
|
||||
}
|
||||
} else {
|
||||
failItems.push(`${item.lcscId} (库内未找到)`);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`出库请求异常: ${item.lcscId}`, e);
|
||||
eda.sys_Log.add(`出库请求异常: ${item.lcscId}: ${e.message}`);
|
||||
failItems.push(`${item.lcscId} (网络异常)`);
|
||||
}
|
||||
}
|
||||
|
||||
renderTable();
|
||||
|
||||
if (failItems.length === 0) {
|
||||
eda.sys_Message.showToastMessage(`成功出库 ${successCount} 项!`, ESYS_ToastMessageType.SUCCESS);
|
||||
} else {
|
||||
const errorMsg = failItems.join('; ');
|
||||
eda.sys_Message.showToastMessage(`出库完成:成功:${successCount},失败:${failItems.length}`, ESYS_ToastMessageType.WARNING);
|
||||
eda.sys_Log.add(errorMsg);
|
||||
}
|
||||
};
|
||||
|
||||
document.getElementById('fill-bom-btn').onclick = () => scanSchematic();
|
||||
|
||||
document.getElementById('cancel-btn').onclick = () => eda.sys_IFrame.closeIFrame();
|
||||
|
||||
await initCache();
|
||||
if (eda.sys_Storage.getExtensionUserConfig('auto-fill-bom')) await scanSchematic();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -1,91 +1,202 @@
|
||||
<!doctype html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>器件入库 - LEYE</title>
|
||||
<link href="/iframe/css/index.css" rel="stylesheet" />
|
||||
<style>
|
||||
.scrollbar-hide { -ms-overflow-style: none; scrollbar-width: none; }
|
||||
.scrollbar-hide::-webkit-scrollbar { display: none; }
|
||||
#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; }
|
||||
</style>
|
||||
<script src="/iframe/js/xlsx.full.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">
|
||||
<header class="flex-shrink-0 bg-white border-b border-gray-200 shadow-sm p-4 space-y-3">
|
||||
<div class="flex items-center space-x-3">
|
||||
<span class="font-medium text-gray-700 w-32">立创商城订单导入</span>
|
||||
<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>
|
||||
</div>
|
||||
<div class="flex items-center space-x-3">
|
||||
<span class="font-medium text-gray-700 w-32">立创商城编号导入</span>
|
||||
<input id="single-cid" type="text" placeholder="CID" class="w-64 px-3 py-1.5 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 outline-none" />
|
||||
<input id="single-qty" type="number" placeholder="数量" class="w-32 px-3 py-1.5 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 outline-none" />
|
||||
<button id="import-cid-btn" class="px-6 py-1.5 bg-green-600 text-white rounded-md hover:bg-green-700 transition">添加器件</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="px-4 py-2 bg-gray-50 border-b flex justify-between items-center">
|
||||
<div class="space-x-2">
|
||||
<button id="select-all" class="px-3 py-1 bg-white border border-gray-300 rounded hover:bg-gray-100 text-xs">全选</button>
|
||||
<button id="select-reverse" class="px-3 py-1 bg-white border border-gray-300 rounded hover:bg-gray-100 text-xs">反选</button>
|
||||
</div>
|
||||
<div class="text-gray-500 text-xs">
|
||||
待入库项:<span id="list-count" class="font-bold text-blue-600">0</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<main class="flex-grow bg-white overflow-hidden m-3 border rounded-lg shadow-inner">
|
||||
<div class="table-container overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200">
|
||||
<thead class="bg-gray-100 sticky-header text-xs uppercase text-gray-600">
|
||||
<tr>
|
||||
<th class="w-12 px-4 py-3 text-center">选择</th>
|
||||
<th class="px-3 py-3 text-left">型号</th>
|
||||
<th class="px-3 py-3 text-left">类型</th>
|
||||
<th class="px-3 py-3 text-left">值</th>
|
||||
<th class="px-3 py-3 text-left">封装</th>
|
||||
<th class="px-3 py-3 text-left">品牌</th>
|
||||
<th class="w-24 px-3 py-3 text-center">入库数量</th>
|
||||
<th class="px-3 py-3 text-left">CID</th>
|
||||
<th class="w-16 px-3 py-3 text-center">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="import-table-body" class="bg-white divide-y divide-gray-200 text-xs">
|
||||
<tr id="empty-row"><td colspan="9" class="text-center py-20 text-gray-400">列表为空,请从上方导入数据</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<div class="flex-shrink-0 p-4 bg-white border-t border-gray-200 flex justify-end space-x-3 shadow-lg">
|
||||
<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>
|
||||
|
||||
<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');
|
||||
let importList = [];
|
||||
|
||||
function renderList() {
|
||||
if (importList.length === 0) {
|
||||
tableBody.innerHTML = `<tr id="empty-row"><td colspan="9" class="text-center py-20 text-gray-400">列表为空,请从上方导入数据</td></tr>`;
|
||||
listCount.textContent = 0;
|
||||
return;
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>器件入库 - LEYE</title>
|
||||
<link href="/iframe/css/index.css" rel="stylesheet" />
|
||||
<style>
|
||||
.scrollbar-hide {
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
.scrollbar-hide::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
#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>
|
||||
<script src="/iframe/js/qrcode.min.js" language="JavaScript"></script>
|
||||
<script src="/iframe/js/mqtt.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">
|
||||
<header class="flex-shrink-0 bg-white border-b border-gray-200 shadow-sm p-4 space-y-3">
|
||||
<div class="flex items-center space-x-3">
|
||||
<span class="font-medium text-gray-700 w-32">立创商城订单导入</span>
|
||||
<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>
|
||||
<input
|
||||
id="single-cid"
|
||||
type="text"
|
||||
placeholder="CID"
|
||||
class="w-64 px-3 py-1.5 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 outline-none"
|
||||
/>
|
||||
<input
|
||||
id="single-qty"
|
||||
type="number"
|
||||
placeholder="数量"
|
||||
class="w-32 px-3 py-1.5 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 outline-none"
|
||||
/>
|
||||
<button id="import-cid-btn" class="px-6 py-1.5 bg-green-600 text-white rounded-md hover:bg-green-700 transition">添加器件</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
tableBody.innerHTML = importList.map((item, index) => `
|
||||
<div class="px-4 py-2 bg-gray-50 border-b flex justify-between items-center">
|
||||
<div class="space-x-2">
|
||||
<button id="select-all" class="px-3 py-1 bg-white border border-gray-300 rounded hover:bg-gray-100 text-xs">全选</button>
|
||||
<button id="select-reverse" class="px-3 py-1 bg-white border border-gray-300 rounded hover:bg-gray-100 text-xs">反选</button>
|
||||
</div>
|
||||
<div class="text-gray-500 text-xs">待入库项:<span id="list-count" class="font-bold text-blue-600">0</span></div>
|
||||
</div>
|
||||
|
||||
<main class="flex-grow bg-white overflow-hidden m-3 border rounded-lg shadow-inner">
|
||||
<div class="table-container overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200">
|
||||
<thead class="bg-gray-100 sticky-header text-xs uppercase text-gray-600">
|
||||
<tr>
|
||||
<th class="w-12 px-4 py-3 text-center">选择</th>
|
||||
<th class="px-3 py-3 text-left">型号</th>
|
||||
<th class="px-3 py-3 text-left">类型</th>
|
||||
<th class="px-3 py-3 text-left">值</th>
|
||||
<th class="px-3 py-3 text-left">封装</th>
|
||||
<th class="px-3 py-3 text-left">品牌</th>
|
||||
<th class="w-24 px-3 py-3 text-center">入库数量</th>
|
||||
<th class="px-3 py-3 text-left">CID</th>
|
||||
<th class="w-16 px-3 py-3 text-center">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="import-table-body" class="bg-white divide-y divide-gray-200 text-xs">
|
||||
<tr id="empty-row">
|
||||
<td colspan="9" class="text-center py-20 text-gray-400">列表为空,请从上方导入数据</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<div class="flex-shrink-0 p-4 bg-white border-t border-gray-200 flex justify-end space-x-3 shadow-lg">
|
||||
<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">×</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;
|
||||
let mqttClient = null;
|
||||
const myDeviceId = Math.random().toString(36).substring(2, 10);
|
||||
|
||||
function renderList() {
|
||||
if (importList.length === 0) {
|
||||
tableBody.innerHTML = `<tr id="empty-row"><td colspan="9" class="text-center py-20 text-gray-400">列表为空,请从上方导入数据</td></tr>`;
|
||||
listCount.textContent = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
tableBody.innerHTML = importList
|
||||
.map(
|
||||
(item, index) => `
|
||||
<tr class="hover:bg-gray-50">
|
||||
<td class="px-4 py-2 text-center"><input type="checkbox" class="row-checkbox" data-index="${index}" ${item.selected ? 'checked' : ''}></td>
|
||||
<td class="px-3 py-2 font-medium">${item.name || '-'}</td>
|
||||
@ -101,219 +212,410 @@
|
||||
<button onclick="removeItem(${index})" class="text-red-500 hover:text-red-700">删除</button>
|
||||
</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
listCount.textContent = importList.length;
|
||||
}
|
||||
`,
|
||||
)
|
||||
.join('');
|
||||
listCount.textContent = importList.length;
|
||||
}
|
||||
|
||||
window.updateQty = (index, val) => { importList[index].quantity = parseInt(val) || 0; };
|
||||
window.removeItem = (index) => { importList.splice(index, 1); renderList(); };
|
||||
async function updateCameraList() {
|
||||
try {
|
||||
const devices = await navigator.mediaDevices.enumerateDevices();
|
||||
const videoDevices = devices.filter((device) => device.kind === 'videoinput');
|
||||
let options = videoDevices
|
||||
.map((d) => `<option value="${d.deviceId}">${d.label || '摄像头 ' + d.deviceId.slice(0, 5)}</option>`)
|
||||
.join('');
|
||||
|
||||
document.getElementById('import-order-btn').onclick = async () => {
|
||||
|
||||
eda.sys_Dialog.showInformationMessage('此功能因可能存在的安全问题已被弃用', '提示', '知道了');
|
||||
|
||||
};
|
||||
|
||||
document.getElementById('import-order-file-btn').onclick = async () => {
|
||||
try {
|
||||
const file = await eda.sys_FileSystem.openReadFileDialog(['xls', ['xlsx']], false);
|
||||
|
||||
if (!file) return;
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = async (e) => {
|
||||
const data = new Uint8Array(e.target.result);
|
||||
const workbook = XLSX.read(data, { type: 'array' });
|
||||
|
||||
const firstSheetName = workbook.SheetNames[0];
|
||||
const worksheet = workbook.Sheets[firstSheetName];
|
||||
|
||||
const jsonData = XLSX.utils.sheet_to_json(worksheet, { header: 1 });
|
||||
|
||||
const newItems = [];
|
||||
for (let i = 18; i < jsonData.length; i++) {
|
||||
const row = jsonData[i];
|
||||
if (!row || row.length < 2) continue; // 跳过空行
|
||||
|
||||
// 第2列(1): lcscId, 第3列(2): 品牌, 第4列(3): 型号, 第5列(4): 封装, 第7列(6): 数量
|
||||
const lcscId = (row[1] || '').toString().trim().toUpperCase();
|
||||
if (!lcscId) continue;
|
||||
|
||||
const rawQty = (row[6] || '0').toString();
|
||||
const qtyMatch = rawQty.match(/\d+/);
|
||||
const quantity = qtyMatch ? parseInt(qtyMatch[0]) : 0;
|
||||
|
||||
newItems.push({
|
||||
lcscId: lcscId,
|
||||
brand: row[2] || '-',
|
||||
name: row[3] || '-',
|
||||
footprint: row[4] || '-',
|
||||
quantity: quantity,
|
||||
value: '-',
|
||||
childCat: '-',
|
||||
selected: true
|
||||
});
|
||||
options += `<option value="WEBSOCKET_MODE">WebSocket 远程扫码</option>`;
|
||||
cameraSelect.innerHTML = options;
|
||||
} catch (e) {
|
||||
eda.sys_Message.showToastMessage('无法获取摄像头列表: ' + e.message, ESYS_ToastMessageType.ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
if (newItems.length > 0) {
|
||||
// await enrichData(newItems);
|
||||
window.closeQrDialog = () => {
|
||||
stopCamera();
|
||||
qrDialog.classList.add('hidden');
|
||||
|
||||
importList.push(...newItems);
|
||||
renderList();
|
||||
eda.sys_Message.showToastMessage(`从文件成功导入 ${newItems.length} 个器件`, ESYS_ToastMessageType.SUCCESS);
|
||||
} else {
|
||||
eda.sys_Message.showToastMessage('未在找到有效数据', ESYS_ToastMessageType.WARNING);
|
||||
if (currentScanData) {
|
||||
document.getElementById('res-cid').textContent = currentScanData.lcscId;
|
||||
document.getElementById('res-pm').textContent = currentScanData.name;
|
||||
document.getElementById('res-qty').textContent = currentScanData.quantity;
|
||||
currentScanData = null;
|
||||
}
|
||||
};
|
||||
|
||||
reader.readAsArrayBuffer(file);
|
||||
function stopCamera() {
|
||||
if (videoStream) {
|
||||
videoStream.getTracks().forEach((track) => track.stop());
|
||||
videoStream = null;
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
eda.sys_Message.showToastMessage('读取文件失败: ' + err.message, ESYS_ToastMessageType.ERROR);
|
||||
eda.sys_Log.add('Excel 导入错误: ' + err.stack);
|
||||
}
|
||||
};
|
||||
if (mqttClient) {
|
||||
console.log('正在断开 MQTT 远程连接...');
|
||||
mqttClient.end(true); // 强制关闭连接
|
||||
mqttClient = null;
|
||||
}
|
||||
|
||||
document.getElementById('import-cid-btn').onclick = async () => {
|
||||
const cid = document.getElementById('single-cid').value.trim().toUpperCase();
|
||||
const qty = parseInt(document.getElementById('single-qty').value);
|
||||
if (!cid || !qty) return eda.sys_Message.showToastMessage('请完整填写编号和数量', ESYS_ToastMessageType.ERROR);
|
||||
qrVideo.style.display = 'block';
|
||||
const oldQr = document.getElementById('remote-qr-canvas');
|
||||
if (oldQr) oldQr.remove();
|
||||
|
||||
try {
|
||||
const items = [{ lcscId: cid, quantity: qty, selected: true }];
|
||||
await enrichData(items);
|
||||
importList.push(...items);
|
||||
renderList();
|
||||
document.getElementById('single-cid').value = '';
|
||||
document.getElementById('single-qty').value = '';
|
||||
} catch (e) {
|
||||
eda.sys_Message.showToastMessage('查询失败', ESYS_ToastMessageType.ERROR);
|
||||
eda.sys_Log.add(e.message);
|
||||
}
|
||||
};
|
||||
|
||||
async function enrichData(items) {
|
||||
const lcscIds = items.map(i => i.lcscId);
|
||||
const devs = await eda.lib_Device.getByLcscIds(lcscIds);
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const dev = devs[i];
|
||||
const item = items[i];
|
||||
|
||||
if (dev && dev.uuid) {
|
||||
try {
|
||||
const infoRes = await eda.sys_ClientUrl.request(
|
||||
'https://client/api/v2/devices/' + dev.uuid,
|
||||
'GET',
|
||||
null,
|
||||
{ headers: { path: '0819f05c4eef4c71ace90d822a990e87' } }
|
||||
);
|
||||
const infoJson = await infoRes.json();
|
||||
const info = infoJson.result;
|
||||
|
||||
if (info && info.attributes) {
|
||||
item.name = info.attributes['Manufacturer Part'] || item.name;
|
||||
item.childCat = info.tags?.child_tag?.name_cn || item.childCat;
|
||||
item.value = info.attributes['Value'] || '-';
|
||||
item.footprint = info.attributes['Supplier Footprint'] || item.footprint;
|
||||
item.brand = info.attributes['Manufacturer'] || item.brand;
|
||||
item.uuid = dev.uuid;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`获取器件 ${item.lcscId} 详情失败:`, e);
|
||||
eda.sys_Log.add(`获取器件 ${item.lcscId} 详情失败: ${e.message}`);
|
||||
if (qrVideo.parentElement) {
|
||||
qrVideo.parentElement.classList.remove('scanning');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('select-all').onclick = () => {
|
||||
importList.forEach(i => i.selected = true);
|
||||
renderList();
|
||||
};
|
||||
document.getElementById('select-reverse').onclick = () => {
|
||||
importList.forEach(i => i.selected = !i.selected);
|
||||
renderList();
|
||||
};
|
||||
tableBody.addEventListener('change', (e) => {
|
||||
if (e.target.classList.contains('row-checkbox')) {
|
||||
const idx = e.target.dataset.index;
|
||||
importList[idx].selected = e.target.checked;
|
||||
}
|
||||
});
|
||||
async function startRemoteMode() {
|
||||
if (mqttClient) {
|
||||
mqttClient.end(true);
|
||||
}
|
||||
|
||||
document.getElementById('clear-list-btn').onclick = () => {
|
||||
importList = [];
|
||||
renderList();
|
||||
};
|
||||
const broker = 'wss://test.mosquitto.org:8081';
|
||||
const topic = `leye/scan/${myDeviceId}`;
|
||||
|
||||
document.getElementById('batch-save-btn').onclick = async () => {
|
||||
const toSave = importList.filter(i => i.selected && i.lcscId);
|
||||
mqttClient = mqtt.connect(broker);
|
||||
|
||||
if (toSave.length === 0) {
|
||||
return eda.sys_Message.showToastMessage('请先选择要入库的项', ESYS_ToastMessageType.WARNING);
|
||||
}
|
||||
mqttClient.on('connect', () => {
|
||||
mqttClient.subscribe(topic);
|
||||
eda.sys_Message.showToastMessage('MQTT 远程模式启动', ESYS_ToastMessageType.SUCCESS);
|
||||
|
||||
let successCount = 0;
|
||||
let failCount = 0;
|
||||
const SERVER = await eda.sys_Storage.getExtensionUserConfig('server-host') || 'http://localhost:21816';
|
||||
const scanUrl = `https://leye.dragon.edu.kg/scan.html?id=${myDeviceId}`;
|
||||
|
||||
for (const item of toSave) {
|
||||
try {
|
||||
const postData = JSON.stringify({
|
||||
name: item.name || '-',
|
||||
lcscId: item.lcscId,
|
||||
quantity: item.quantity
|
||||
qrVideo.style.display = 'none';
|
||||
let qrCanvas = document.getElementById('remote-qr-canvas') || createQrCanvas();
|
||||
QRCode.toCanvas(qrCanvas, scanUrl, { width: 250 });
|
||||
});
|
||||
|
||||
let res = await eda.sys_ClientUrl.request(
|
||||
SERVER + '/addLeyeList',
|
||||
'POST',
|
||||
postData,
|
||||
{ headers: { 'Content-Type': 'application/json' } }
|
||||
);
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let result = await res.json();
|
||||
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;
|
||||
}
|
||||
|
||||
if (AUTO_RUN && !result.success) {
|
||||
window.open('leye://open');
|
||||
for (let i = 0; i < 3 && !result.success; i++) {
|
||||
eda.sys_Message.showToastMessage('等待拉起本地服务端...', ESYS_ToastMessageType.INFO);
|
||||
res = await eda.sys_ClientUrl.request(
|
||||
SERVER + '/addLeyeList',
|
||||
'POST',
|
||||
postData,
|
||||
{ headers: { 'Content-Type': 'application/json' } }
|
||||
);
|
||||
result = await res.json();
|
||||
await new Promise(resolve => setTimeout(resolve, 1500));
|
||||
async function startLocalCamera(deviceId) {
|
||||
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-start-camera').onclick = async () => {
|
||||
stopCamera();
|
||||
const mode = cameraSelect.value;
|
||||
|
||||
if (mode === 'WEBSOCKET_MODE') {
|
||||
startRemoteMode();
|
||||
} else {
|
||||
startLocalCamera(mode);
|
||||
}
|
||||
};
|
||||
|
||||
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();
|
||||
};
|
||||
|
||||
document.getElementById('import-order-btn').onclick = async () => {
|
||||
eda.sys_Dialog.showInformationMessage('此功能因可能存在的安全问题已被弃用', '提示', '知道了');
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
if (!file) return;
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = async (e) => {
|
||||
const data = new Uint8Array(e.target.result);
|
||||
const workbook = XLSX.read(data, { type: 'array' });
|
||||
|
||||
const firstSheetName = workbook.SheetNames[0];
|
||||
const worksheet = workbook.Sheets[firstSheetName];
|
||||
|
||||
const jsonData = XLSX.utils.sheet_to_json(worksheet, { header: 1 });
|
||||
|
||||
const newItems = [];
|
||||
for (let i = 18; i < jsonData.length; i++) {
|
||||
const row = jsonData[i];
|
||||
if (!row || row.length < 2) continue; // 跳过空行
|
||||
|
||||
// 第2列(1): lcscId, 第3列(2): 品牌, 第4列(3): 型号, 第5列(4): 封装, 第7列(6): 数量
|
||||
const lcscId = (row[1] || '').toString().trim().toUpperCase();
|
||||
if (!lcscId) continue;
|
||||
|
||||
const rawQty = (row[6] || '0').toString();
|
||||
const qtyMatch = rawQty.match(/\d+/);
|
||||
const quantity = qtyMatch ? parseInt(qtyMatch[0]) : 0;
|
||||
|
||||
newItems.push({
|
||||
lcscId: lcscId,
|
||||
brand: row[2] || '-',
|
||||
name: row[3] || '-',
|
||||
footprint: row[4] || '-',
|
||||
quantity: quantity,
|
||||
value: '-',
|
||||
childCat: '-',
|
||||
selected: true,
|
||||
});
|
||||
}
|
||||
|
||||
if (newItems.length > 0) {
|
||||
// await enrichData(newItems);
|
||||
|
||||
importList.push(...newItems);
|
||||
renderList();
|
||||
eda.sys_Message.showToastMessage(`从文件成功导入 ${newItems.length} 个器件`, ESYS_ToastMessageType.SUCCESS);
|
||||
} else {
|
||||
eda.sys_Message.showToastMessage('未在找到有效数据', ESYS_ToastMessageType.WARNING);
|
||||
}
|
||||
};
|
||||
|
||||
reader.readAsArrayBuffer(file);
|
||||
} catch (err) {
|
||||
eda.sys_Message.showToastMessage('读取文件失败: ' + err.message, ESYS_ToastMessageType.ERROR);
|
||||
eda.sys_Log.add('Excel 导入错误: ' + err.stack);
|
||||
}
|
||||
};
|
||||
|
||||
document.getElementById('import-cid-btn').onclick = async () => {
|
||||
const cid = document.getElementById('single-cid').value.trim().toUpperCase();
|
||||
const qty = parseInt(document.getElementById('single-qty').value);
|
||||
if (!cid || !qty) return eda.sys_Message.showToastMessage('请完整填写编号和数量', ESYS_ToastMessageType.ERROR);
|
||||
|
||||
try {
|
||||
const items = [{ lcscId: cid, quantity: qty, selected: true }];
|
||||
await enrichData(items);
|
||||
importList.push(...items);
|
||||
renderList();
|
||||
document.getElementById('single-cid').value = '';
|
||||
document.getElementById('single-qty').value = '';
|
||||
} catch (e) {
|
||||
eda.sys_Message.showToastMessage('查询失败', ESYS_ToastMessageType.ERROR);
|
||||
eda.sys_Log.add(e.message);
|
||||
}
|
||||
};
|
||||
|
||||
async function enrichData(items) {
|
||||
const lcscIds = items.map((i) => i.lcscId);
|
||||
const devs = await eda.lib_Device.getByLcscIds(lcscIds);
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const dev = devs[i];
|
||||
const item = items[i];
|
||||
|
||||
if (dev && dev.uuid) {
|
||||
try {
|
||||
const EDA_HOST =
|
||||
eda.sys_Environment.isClient() && !eda.sys_Environment.isOnlineMode() ? 'https://client' : 'https://pro.lceda.cn';
|
||||
const infoRes = await eda.sys_ClientUrl.request(EDA_HOST + '/api/v2/devices/' + dev.uuid, 'GET', null, {
|
||||
headers: { path: '0819f05c4eef4c71ace90d822a990e87' },
|
||||
});
|
||||
const infoJson = await infoRes.json();
|
||||
const info = infoJson.result;
|
||||
|
||||
if (info && info.attributes) {
|
||||
item.name = info.attributes['Manufacturer Part'] || item.name;
|
||||
item.childCat = info.tags?.child_tag?.name_cn || item.childCat;
|
||||
item.value = info.attributes['Value'] || '-';
|
||||
item.footprint = info.attributes['Supplier Footprint'] || item.footprint;
|
||||
item.brand = info.attributes['Manufacturer'] || item.brand;
|
||||
item.uuid = dev.uuid;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`获取器件 ${item.lcscId} 详情失败:`, e);
|
||||
eda.sys_Log.add(`获取器件 ${item.lcscId} 详情失败: ${e.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('select-all').onclick = () => {
|
||||
importList.forEach((i) => (i.selected = true));
|
||||
renderList();
|
||||
};
|
||||
document.getElementById('select-reverse').onclick = () => {
|
||||
importList.forEach((i) => (i.selected = !i.selected));
|
||||
renderList();
|
||||
};
|
||||
tableBody.addEventListener('change', (e) => {
|
||||
if (e.target.classList.contains('row-checkbox')) {
|
||||
const idx = e.target.dataset.index;
|
||||
importList[idx].selected = e.target.checked;
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('clear-list-btn').onclick = () => {
|
||||
importList = [];
|
||||
renderList();
|
||||
};
|
||||
|
||||
document.getElementById('batch-save-btn').onclick = async () => {
|
||||
const toSave = importList.filter((i) => i.selected && i.lcscId);
|
||||
|
||||
if (toSave.length === 0) {
|
||||
return eda.sys_Message.showToastMessage('请先选择要入库的项', ESYS_ToastMessageType.WARNING);
|
||||
}
|
||||
|
||||
let successCount = 0;
|
||||
let failCount = 0;
|
||||
|
||||
for (const item of toSave) {
|
||||
try {
|
||||
const postData = JSON.stringify({
|
||||
name: item.name || '-',
|
||||
lcscId: item.lcscId,
|
||||
quantity: item.quantity,
|
||||
});
|
||||
|
||||
let res = await eda.sys_ClientUrl.request(SERVER + '/addLeyeList', 'POST', postData, {
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
|
||||
let result = await res.json();
|
||||
|
||||
if (AUTO_RUN && !result.success) {
|
||||
window.open('leye://open');
|
||||
for (let i = 0; i < 3 && !result.success; i++) {
|
||||
eda.sys_Message.showToastMessage('等待拉起本地服务端...', ESYS_ToastMessageType.INFO);
|
||||
res = await eda.sys_ClientUrl.request(SERVER + '/addLeyeList', 'POST', postData, {
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
result = await res.json();
|
||||
await new Promise((resolve) => setTimeout(resolve, 1500));
|
||||
}
|
||||
}
|
||||
|
||||
if (result.success) {
|
||||
successCount++;
|
||||
const idx = importList.indexOf(item);
|
||||
if (idx > -1) importList.splice(idx, 1);
|
||||
} else {
|
||||
failCount++;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`入库失败: ${item.lcscId}`, e);
|
||||
eda.sys_Log.add(`入库失败: ${item.lcscId}: ${e.message}`);
|
||||
failCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (result.success) {
|
||||
successCount++;
|
||||
const idx = importList.indexOf(item);
|
||||
if (idx > -1) importList.splice(idx, 1);
|
||||
renderList();
|
||||
|
||||
if (failCount === 0) {
|
||||
eda.sys_Message.showToastMessage(`全部入库成功,共 ${successCount} 项`, ESYS_ToastMessageType.SUCCESS);
|
||||
} else {
|
||||
failCount++;
|
||||
eda.sys_Message.showToastMessage(
|
||||
`入库完成,成功: ${successCount}, 失败: ${failCount}`,
|
||||
failCount > 0 ? ESYS_ToastMessageType.WARNING : ESYS_ToastMessageType.SUCCESS,
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`入库失败: ${item.lcscId}`, e);
|
||||
eda.sys_Log.add(`入库失败: ${item.lcscId}: ${e.message}`);
|
||||
failCount++;
|
||||
}
|
||||
}
|
||||
|
||||
renderList();
|
||||
|
||||
if (failCount === 0) {
|
||||
eda.sys_Message.showToastMessage(`全部入库成功,共 ${successCount} 项`, ESYS_ToastMessageType.SUCCESS);
|
||||
} else {
|
||||
eda.sys_Message.showToastMessage(`入库完成,成功: ${successCount}, 失败: ${failCount}`,
|
||||
failCount > 0 ? ESYS_ToastMessageType.WARNING : ESYS_ToastMessageType.SUCCESS);
|
||||
}
|
||||
};
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
};
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
8
iframe/js/jsQR.min.js
vendored
Normal file
8
iframe/js/jsQR.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
19
iframe/js/mqtt.min.js
vendored
Normal file
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
7
iframe/js/qrcode.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1088
iframe/newLeye.html
1088
iframe/newLeye.html
File diff suppressed because it is too large
Load Diff
@ -109,16 +109,27 @@
|
||||
</label>
|
||||
<input type="checkbox" id="settings-server-auto-run" />
|
||||
</div>
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<label for="settings-auto-fill-bom" class="text-gray-700">出库自动填充 BOM</label>
|
||||
<input type="checkbox" id="settings-auto-fill-bom" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-white p-4 rounded-lg w-[400px]">
|
||||
<h2 class="text-lg font-semibold text-gray-800 mb-4">
|
||||
高级设置
|
||||
<span class="text-[12px] text-gray-500 font-light ml-2"> 实验或调试选项 </span>
|
||||
</h2>
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<label for="settings-auto-check-update" class="text-gray-700">
|
||||
自动检查更新
|
||||
<span class="tooltip text-sm text-gray-500 font-light" data-tooltip="允许在启动 EDA 时检查更新">?</span>
|
||||
</label>
|
||||
<input type="checkbox" id="settings-auto-check-update" />
|
||||
</div>
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<label for="settings-clean-cache" class="text-gray-700">
|
||||
清除缓存
|
||||
<span class="tooltip text-sm text-gray-500 font-light" data-tooltip="用于移除损坏的缓存数据">?</span>
|
||||
<span class="tooltip text-sm text-gray-500 font-light" data-tooltip="库存数据异常可尝试使用此功能修复">?</span>
|
||||
</label>
|
||||
<button id="settings-clean-cache" class="bg-red-600 text-white px-4 py-1 rounded hover:bg-red-700">清除</button>
|
||||
</div>
|
||||
@ -128,10 +139,14 @@
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
const serverHostInput = document.getElementById('settings-server-host');
|
||||
const serverAutoRun = document.getElementById('settings-server-auto-run');
|
||||
const autoFillBomInput = document.getElementById('settings-auto-fill-bom');
|
||||
const autoCheckUpdateInput = document.getElementById('settings-auto-check-update');
|
||||
const cleanCacheBtn = document.getElementById('settings-clean-cache');
|
||||
|
||||
serverHostInput.value = (await eda.sys_Storage.getExtensionUserConfig('server-host')) ?? 'http://localhost:21816/api';
|
||||
serverAutoRun.checked = (await eda.sys_Storage.getExtensionUserConfig('server-auto-run')) ?? true;
|
||||
autoFillBomInput.checked = (await eda.sys_Storage.getExtensionUserConfig('auto-fill-bom')) ?? false;
|
||||
autoCheckUpdateInput.checked = (await eda.sys_Storage.getExtensionUserConfig('auto-check-update')) ?? true;
|
||||
|
||||
serverHostInput.addEventListener('change', async () => {
|
||||
saveConfig('server-host', serverHostInput.value);
|
||||
@ -139,16 +154,22 @@
|
||||
serverAutoRun.addEventListener('change', async () => {
|
||||
saveConfig('server-auto-run', serverAutoRun.checked);
|
||||
});
|
||||
autoFillBomInput.addEventListener('change', async () => {
|
||||
saveConfig('auto-fill-bom', autoFillBomInput.checked);
|
||||
});
|
||||
autoCheckUpdateInput.addEventListener('change', async () => {
|
||||
saveConfig('auto-check-update', autoCheckUpdateInput.checked);
|
||||
});
|
||||
|
||||
cleanCacheBtn.addEventListener('click', async () => {
|
||||
eda.sys_Storage.deleteExtensionUserConfig('cache-leye-device-details').then(s => {
|
||||
eda.sys_Storage.deleteExtensionUserConfig('cache-leye-device-details').then((s) => {
|
||||
if (s) {
|
||||
eda.sys_Message.showToastMessage('缓存清除成功!', ESYS_ToastMessageType.SUCCESS);
|
||||
} else {
|
||||
eda.sys_Message.showToastMessage('缓存清除失败...', ESYS_ToastMessageType.ERROR);
|
||||
}
|
||||
})
|
||||
})
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function saveConfig(key, value) {
|
||||
|
||||
BIN
images/img_07.png
Normal file
BIN
images/img_07.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 301 KiB |
BIN
images/img_08.png
Normal file
BIN
images/img_08.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 459 KiB |
@ -12,8 +12,7 @@
|
||||
"eslint:all": "eslint --ext .ts --fix .",
|
||||
"fix": "npm run prettier:all && npm run eslint:all",
|
||||
"tailwind": "tailwindcss -i ./iframe/css/input.css -o ./iframe/css/index.css",
|
||||
"obf": "node -e \"const g=require('glob');const {execSync}=require('child_process');const p=require('path');g.sync('iframe/js/s_*.js').forEach(f=>{const d=p.dirname(f),b=p.basename(f,'.js').slice(2)+'.js';execSync(`javascript-obfuscator \\\"${f}\\\" --string-array-encoding rc4 --output \\\"${p.join(d,b)}\\\"`)})\"",
|
||||
"build": "npm run tailwind && npm run obf && npm run compile && ts-node ./build/packaged.ts"
|
||||
"build": "npm run tailwind && npm run compile && ts-node ./build/packaged.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@jlceda/pro-api-types": "^0.1.175",
|
||||
@ -36,8 +35,7 @@
|
||||
"rimraf": "^6.0.1",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.7.3",
|
||||
"javascript-obfuscator": "^4.1.1"
|
||||
"typescript": "^5.7.3"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.ts": "eslint --cache --fix",
|
||||
|
||||
7
src/global.d.ts
vendored
7
src/global.d.ts
vendored
@ -1,7 +0,0 @@
|
||||
export {};
|
||||
|
||||
declare global {
|
||||
interface GlobalThis {
|
||||
__LEYE_INIT_FLAG__?: boolean;
|
||||
}
|
||||
}
|
||||
71
src/index.ts
71
src/index.ts
@ -10,12 +10,13 @@
|
||||
* 如需了解更多开发细节,请阅读:
|
||||
* https://prodocs.lceda.cn/cn/api/guide/
|
||||
*/
|
||||
import * as extensionConfig from '../extension.json';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export function activate(status?: 'onStartupFinished', arg?: string): void {}
|
||||
|
||||
export function about(): void {
|
||||
eda.sys_IFrame.openIFrame('/iframe/about.html', 400, 200);
|
||||
export async function about(): Promise<void> {
|
||||
await eda.sys_IFrame.openIFrame('/iframe/about.html', 400, 200);
|
||||
}
|
||||
|
||||
export async function openLeyeIFrame(): Promise<void> {
|
||||
@ -34,8 +35,8 @@ export async function openExportIFrame(): Promise<void> {
|
||||
await eda.sys_IFrame.openIFrame('/iframe/export.html', 1000, 600, 'leye-export', { minimizeButton: true, grayscaleMask: true });
|
||||
}
|
||||
|
||||
export function openSettings(): void {
|
||||
eda.sys_IFrame.openIFrame('/iframe/settings.html', 400, 600);
|
||||
export async function openSettings(): Promise<void> {
|
||||
await eda.sys_IFrame.openIFrame('/iframe/settings.html', 400, 600);
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
@ -44,22 +45,56 @@ if (!globalThis['__LEYE_INIT_FLAG__']) {
|
||||
// @ts-ignore
|
||||
globalThis['__LEYE_INIT_FLAG__'] = true;
|
||||
// @ts-ignore
|
||||
eda.sys_ShortcutKey.unregisterShortcutKey(['Shift+L']).then(r => console.log('[LEYE] 注销快捷键: ', r));
|
||||
eda.sys_ShortcutKey.unregisterShortcutKey(['Shift+L']).then((r) => console.log('[LEYE] 注销快捷键: ', r));
|
||||
// @ts-ignore
|
||||
eda.sys_ShortcutKey.registerShortcutKey(['Shift+L'], 'openLeyeIFrame', async () => {
|
||||
await openLeyeIFrameNew();
|
||||
}).then(r => console.log('[LEYE] 注册快捷键: ', r));
|
||||
eda.sys_ShortcutKey
|
||||
.registerShortcutKey(['Shift+L'], 'openLeyeIFrame', async () => {
|
||||
await openLeyeIFrameNew();
|
||||
})
|
||||
.then((r) => console.log('[LEYE] 注册快捷键: ', r));
|
||||
|
||||
// 获取公告
|
||||
console.log('[LEYE] 获取公告和更新');
|
||||
eda.sys_ClientUrl.request('https://leye.dragon.edu.kg/release/notice.json').then(async (res: any) => {
|
||||
const data = await res.json();
|
||||
console.log('[LEYE] 获取公告: ', data);
|
||||
if (eda.sys_Storage.getExtensionUserConfig('cache-notice-id') !== data.notices[0].id) {
|
||||
await eda.sys_Storage.setExtensionUserConfig('cache-notice-id', data.notices[0].id);
|
||||
eda.sys_Dialog.showInformationMessage(data.notices[0].content, data.notices[0].title, '知道了');
|
||||
}
|
||||
}).catch((err: any) => {
|
||||
console.error('[LEYE] 获取公告和更新失败: ', err);
|
||||
});
|
||||
eda.sys_ClientUrl
|
||||
.request('https://leye.dragon.edu.kg/release/notice.json')
|
||||
.then(async (res: any) => {
|
||||
const data = await res.json();
|
||||
console.log('[LEYE] 获取公告: ', data);
|
||||
if (eda.sys_Storage.getExtensionUserConfig('cache-notice-id') !== data.notices[0].id) {
|
||||
await eda.sys_Storage.setExtensionUserConfig('cache-notice-id', data.notices[0].id);
|
||||
eda.sys_Dialog.showInformationMessage(data.notices[0].content, data.notices[0].title, '知道了');
|
||||
}
|
||||
})
|
||||
.catch((err: any) => {
|
||||
console.error('[LEYE] 获取公告和更新失败: ', err);
|
||||
});
|
||||
|
||||
// 获取最新版本
|
||||
console.log('[LEYE] 获取最新版本');
|
||||
if (eda.sys_Storage.getExtensionUserConfig('auto-check-update') ?? true) {
|
||||
eda.sys_ClientUrl
|
||||
.request('https://leye.dragon.edu.kg/release/eext.ver.json')
|
||||
.then(async (res: any) => {
|
||||
const data = await res.json();
|
||||
console.log('[LEYE] 获取最新版本: ', data);
|
||||
if (extensionConfig.version !== data.versions[0].ver) {
|
||||
eda.sys_Dialog.showConfirmationMessage(
|
||||
data.versions[0].changelog,
|
||||
`LEYE 有新版本 ${data.versions[0].ver} 可用,是否前往下载?`,
|
||||
'前往下载',
|
||||
'算了',
|
||||
async (r) => {
|
||||
if (r) {
|
||||
eda.sys_Window.open('https://lrurl.top/LeyeEEXT', ESYS_WindowOpenTarget.BLANK);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
})
|
||||
.catch((err: any) => {
|
||||
console.error('[LEYE] 获取最新版本失败: ', err);
|
||||
});
|
||||
} else {
|
||||
console.log('[LEYE] 用户主动关闭自动检查更新');
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user