4 Commits

Author SHA1 Message Date
c1d5d91d5c fix auto run bug 2026-03-19 16:49:13 +08:00
86203f9b4f update 1.0.10 2026-03-16 15:04:44 +08:00
8950b56476 fix online client bug 2026-02-25 21:16:55 +08:00
7e1fc83891 Ignore the backend directory 2026-02-25 20:55:40 +08:00
8 changed files with 206 additions and 37 deletions

View File

@@ -17,4 +17,5 @@
/package-lock.json /package-lock.json
/package.json /package.json
/tsconfig.json /tsconfig.json
/backend/
debug.log debug.log

View File

@@ -1,3 +1,20 @@
# 1.0.11
1. 修复网页端无法自动拉起服务端的问题
2. 修复批量出库页面 server-host 默认值错误的问题
# 1.0.10
1. 查看库存界面新增右键菜单,支持快速跳转到立创商城、查看数据手册等功能
2. 在不支持放置器件的页面放置器件新增提示信息
3. 修改 README 文档
# 1.0.9
1. 修复全在线客户端无法获取器件数据的问题
2. 扩展包不再打包服务端源码
3. 更新 README 文档
# 1.0.8 # 1.0.8
1. 修复“允许拉起服务端”配置项无效的问题 1. 修复“允许拉起服务端”配置项无效的问题

View File

@@ -10,14 +10,16 @@ LEYE 电子元器件库存管理系统 EDA 联动扩展
本扩展允许用户通过立创商城 C 编号、立创商城订单导入元器件,支持从库存内查询、放置器件,支持通过 BOM 批量出库。 本扩展允许用户通过立创商城 C 编号、立创商城订单导入元器件,支持从库存内查询、放置器件,支持通过 BOM 批量出库。
使用本扩展需要在本地安装 [LEYE Service 服务端](https://lrurl.top/LeyeService)(目前仅提供 Windows 版),数据均储存在本地,不会上传。 使用本扩展需要在本地安装 [LEYE Service 服务端](https://lrurl.top/LeyeService)(目前仅提供 Windows 版[服务端开源](https://gitea.miri.site/Mr_Fang/eext-leye/src/branch/master/backend/server.js)),数据均储存在本地,不会上传。
## [介绍视频](https://www.bilibili.com/video/BV1nvcFzpEuP/) ## [介绍视频](https://www.bilibili.com/video/BV1nvcFzpEuP/)
https://www.bilibili.com/video/BV1nvcFzpEuP/ https://www.bilibili.com/video/BV1nvcFzpEuP/
## 如何使用 ## 如何使用
安装本扩展后**需要给予扩展外部交互**权限以与服务端交互: 安装本扩展后**需要给予扩展外部交互**权限以与服务端交互:
- V2.2:设置-扩展-扩展管理器-LEYE-允许外部交互; - V2.2:设置-扩展-扩展管理器-LEYE-允许外部交互;
- V3高级-扩展管理器-已安装-LEYE-配置-允许外部交互。 - V3高级-扩展管理器-已安装-LEYE-配置-允许外部交互。
@@ -41,12 +43,12 @@ V2.2 用户菜单栏将直接出现“LEYE”选项V3 用户若未开启“
![img03](/images/img_03.png) ![img03](/images/img_03.png)
> **Tip: 扫描立创商城二维码导入器件** *V1.0.5+* > **Tip: 扫描立创商城二维码导入器件** _V1.0.5+_
> >
> 在 V1.0.5 版本以上,可以通过扫描立创商城物料包装上的二维码导入器件,扫描后会自动识别 CID 并导入对应器件。 > 在 V1.0.5 版本以上,可以通过扫描立创商城物料包装上的二维码导入器件,扫描后会自动识别 CID 并导入对应器件。
> ![img07](/images/img_07.png) > ![img07](/images/img_07.png)
> **Tip: 使用手机扫码导入器件** *V1.0.6+* > **Tip: 使用手机扫码导入器件** _V1.0.6+_
> >
> 在 V1.0.6 版本以上可以在扫码界面选择“WebSocket 远程扫码”,点击“打开摄像头”按钮后用手机扫描二维码即可打开远程端。 > 在 V1.0.6 版本以上可以在扫码界面选择“WebSocket 远程扫码”,点击“打开摄像头”按钮后用手机扫描二维码即可打开远程端。
> 远程端同样支持扫描立创商城物料包装上的二维码导入器件,扫描后会自动识别 CID 并导入对应器件。 > 远程端同样支持扫描立创商城物料包装上的二维码导入器件,扫描后会自动识别 CID 并导入对应器件。
@@ -62,10 +64,13 @@ V2.2 用户菜单栏将直接出现“LEYE”选项V3 用户若未开启“
## 配置项说明 ## 配置项说明
| 配置项 | 说明 | 默认值 | | 配置项 | 说明 | 默认值 |
|:-------------|:-----------------------------------------------------------|:-------------------------| | :--------------- | :----------------------------------------------------------------------- | :----------------------- |
| 服务器地址 | LEYE Service 服务器地址 | `http://localhost:21816` | | 服务器地址 | LEYE Service 服务器地址 | `http://localhost:21816` |
| 允许拉起服务端 | 允许通过 [`leye://open`](leye://open) 拉起本地安装的 LEYE Service 服务端 | `true` | | 允许拉起服务端 | 允许通过 [`leye://open`](leye://open) 拉起本地安装的 LEYE Service 服务端 | `true` |
| 出库自动填充 BOM | 打开出库界面自动读取 BOM 并填充 | `false` |
| | | |
| 自动检查更新 | 允许在启动 EDA 时检查更新 | `true` |
## 已知问题 ## 已知问题
@@ -74,6 +79,7 @@ V2.2 用户菜单栏将直接出现“LEYE”选项V3 用户若未开启“
## 开源许可 ## 开源许可
本扩展使用以下开源软件: 本扩展使用以下开源软件:
- [SheetJS](https://www.npmjs.com/package/xlsx)Excel 解析库,使用 [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/) 许可协议授权
- [jsQR](https://www.npmjs.com/package/jsqr):二维码解析库,使用 [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/) 许可协议授权 - [node-qrcode](https://www.npmjs.com/package/qrcode):二维码生成库,使用 [MIT License](https://choosealicense.com/licenses/mit/) 许可协议授权

View File

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

View File

@@ -651,6 +651,10 @@ video {
z-index: 50; z-index: 50;
} }
.z-\[100\] {
z-index: 100;
}
.order-1 { .order-1 {
order: 1; order: 1;
} }
@@ -688,6 +692,11 @@ video {
margin-right: auto; margin-right: auto;
} }
.my-1 {
margin-top: 0.25rem;
margin-bottom: 0.25rem;
}
.mb-1 { .mb-1 {
margin-bottom: 0.25rem; margin-bottom: 0.25rem;
} }
@@ -983,6 +992,10 @@ video {
scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
} }
.cursor-not-allowed {
cursor: not-allowed;
}
.cursor-pointer { .cursor-pointer {
cursor: pointer; cursor: pointer;
} }
@@ -1585,6 +1598,11 @@ video {
color: rgb(30 64 175 / var(--tw-text-opacity, 1)); color: rgb(30 64 175 / var(--tw-text-opacity, 1));
} }
.text-gray-300 {
--tw-text-opacity: 1;
color: rgb(209 213 219 / var(--tw-text-opacity, 1));
}
.text-gray-400 { .text-gray-400 {
--tw-text-opacity: 1; --tw-text-opacity: 1;
color: rgb(156 163 175 / var(--tw-text-opacity, 1)); color: rgb(156 163 175 / var(--tw-text-opacity, 1));

View File

@@ -267,7 +267,8 @@
let detail = { name: '-', value: '-', footprint: '-', brand: '-', designatorStr: '手动添加', selected: false }; let detail = { name: '-', value: '-', footprint: '-', brand: '-', designatorStr: '手动添加', selected: false };
if (devs[0]) { if (devs[0]) {
const EDA_HOST = eda.sys_Environment.isClient() ? 'https://client' : 'https://pro.lceda.cn'; 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, { const infoRes = await eda.sys_ClientUrl.request(EDA_HOST + '/api/v2/devices/' + devs[0].uuid, 'GET', null, {
headers: { path: '0819f05c4eef4c71ace90d822a990e87' }, headers: { path: '0819f05c4eef4c71ace90d822a990e87' },
}); });
@@ -296,7 +297,7 @@
return eda.sys_Message.showToastMessage('请先勾选要出库的器件', ESYS_ToastMessageType.WARNING); return eda.sys_Message.showToastMessage('请先勾选要出库的器件', ESYS_ToastMessageType.WARNING);
} }
const SERVER = (await eda.sys_Storage.getExtensionUserConfig('server-host')) ?? 'http://localhost:21816'; const SERVER = (await eda.sys_Storage.getExtensionUserConfig('server-host')) ?? 'http://localhost:21816/api';
const AUTO_RUN = (await eda.sys_Storage.getExtensionUserConfig('server-auto-run')) ?? true; const AUTO_RUN = (await eda.sys_Storage.getExtensionUserConfig('server-auto-run')) ?? true;
let successCount = 0; let successCount = 0;
let failItems = []; let failItems = [];
@@ -305,16 +306,27 @@
for (const item of selectedItems) { for (const item of selectedItems) {
try { try {
let getRes = await eda.sys_ClientUrl.request(`${SERVER}/getLeyeList?lcscId=${item.lcscId}`, 'GET'); let getResult;
let getResult = await getRes.json(); try {
const getRes = await eda.sys_ClientUrl.request(`${SERVER}/getLeyeList?lcscId=${item.lcscId}`, 'GET');
getResult = await getRes.json();
} catch (e) {
getResult = { success: false };
}
if (AUTO_RUN && !getResult.success) { if (AUTO_RUN && !getResult.success) {
window.open('leye://open'); window.open('leye://open');
for (let i = 0; i < 3 && !getResult.success; i++) { for (let i = 0; i < 3 && !getResult.success; i++) {
eda.sys_Message.showToastMessage('等待拉起本地服务端...', ESYS_ToastMessageType.INFO); eda.sys_Message.showToastMessage('等待拉起本地服务端...', ESYS_ToastMessageType.INFO);
getRes = await eda.sys_ClientUrl.request(`${SERVER}/getLeyeList?lcscId=${item.lcscId}`, 'GET'); try {
getResult = await getRes.json(); const getRes = await eda.sys_ClientUrl.request(`${SERVER}/getLeyeList?lcscId=${item.lcscId}`, 'GET');
await new Promise((resolve) => setTimeout(resolve, 1500)); getResult = await getRes.json();
} catch (e) {
getResult = { success: false };
}
if (!getResult.success) {
await new Promise((resolve) => setTimeout(resolve, 1500));
}
} }
} }

View File

@@ -510,7 +510,8 @@
if (dev && dev.uuid) { if (dev && dev.uuid) {
try { try {
const EDA_HOST = eda.sys_Environment.isClient() ? 'https://client' : 'https://pro.lceda.cn'; 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, { const infoRes = await eda.sys_ClientUrl.request(EDA_HOST + '/api/v2/devices/' + dev.uuid, 'GET', null, {
headers: { path: '0819f05c4eef4c71ace90d822a990e87' }, headers: { path: '0819f05c4eef4c71ace90d822a990e87' },
}); });
@@ -571,21 +572,31 @@
quantity: item.quantity, quantity: item.quantity,
}); });
let res = await eda.sys_ClientUrl.request(SERVER + '/addLeyeList', 'POST', postData, { let result;
headers: { 'Content-Type': 'application/json' }, try {
}); const res = await eda.sys_ClientUrl.request(SERVER + '/addLeyeList', 'POST', postData, {
headers: { 'Content-Type': 'application/json' },
let result = await res.json(); });
result = await res.json();
} catch (e) {
result = { success: false };
}
if (AUTO_RUN && !result.success) { if (AUTO_RUN && !result.success) {
window.open('leye://open'); eda.sys_Window.open('leye://open');
for (let i = 0; i < 3 && !result.success; i++) { for (let i = 0; i < 3 && !result.success; i++) {
eda.sys_Message.showToastMessage('等待拉起本地服务端...', ESYS_ToastMessageType.INFO); eda.sys_Message.showToastMessage('等待拉起本地服务端...', ESYS_ToastMessageType.INFO);
res = await eda.sys_ClientUrl.request(SERVER + '/addLeyeList', 'POST', postData, { try {
headers: { 'Content-Type': 'application/json' }, const 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)); result = await res.json();
} catch (e) {
result = { success: false };
}
if (!result.success) {
await new Promise((resolve) => setTimeout(resolve, 1500));
}
} }
} }

View File

@@ -195,6 +195,29 @@
</div> </div>
</div> </div>
</div> </div>
<div
id="context-menu"
class="hidden fixed bg-white border border-gray-200 shadow-xl rounded-md py-1 z-[100] w-40 text-xs text-gray-700 shadow-2xl"
>
<div id="cm-view-info" class="px-3 py-2 text-gray-300 cursor-not-allowed flex items-center">
<span>查看详情</span>
</div>
<div id="cm-view-product" class="px-3 py-2 hover:bg-blue-50 hover:text-blue-600 cursor-pointer flex items-center">
<span>查看产品链接</span>
</div>
<div id="cm-view-datasheet" class="px-3 py-2 hover:bg-blue-50 hover:text-blue-600 cursor-pointer flex items-center">
<span>查看数据手册</span>
</div>
<div class="border-t border-gray-100 my-1"></div>
<div id="cm-add-lcsc-car" class="px-3 py-2 text-gray-300 cursor-not-allowed flex items-center">
<span>立创商城加购</span>
</div>
<div class="border-t border-gray-100 my-1"></div>
<div id="cm-edit" class="px-3 py-2 hover:bg-blue-50 hover:text-blue-600 cursor-pointer flex items-center">
<span>编辑</span>
</div>
</div>
</div> </div>
<script> <script>
@@ -220,6 +243,11 @@
const editNameInput = document.getElementById('edit-name-input'); const editNameInput = document.getElementById('edit-name-input');
const editLcscInput = document.getElementById('edit-lcsc-input'); const editLcscInput = document.getElementById('edit-lcsc-input');
const dialogIdDisplay = document.getElementById('dialog-id-display'); const dialogIdDisplay = document.getElementById('dialog-id-display');
const contextMenu = document.getElementById('context-menu');
const cmViewProduct = document.getElementById('cm-view-product');
const cmViewDatasheet = document.getElementById('cm-view-datasheet');
const cmAddLcscCarDatasheet = document.getElementById('cm-add-lcsc-car');
const cmEdit = document.getElementById('cm-edit');
const SERVER = eda.sys_Storage.getExtensionUserConfig('server-host') ?? 'http://localhost:21816/api'; const SERVER = eda.sys_Storage.getExtensionUserConfig('server-host') ?? 'http://localhost:21816/api';
const AUTO_RUN = eda.sys_Storage.getExtensionUserConfig('server-auto-run') ?? true; const AUTO_RUN = eda.sys_Storage.getExtensionUserConfig('server-auto-run') ?? true;
@@ -268,17 +296,32 @@
cachedDetails = []; cachedDetails = [];
} }
let listRes = await eda.sys_ClientUrl.request(SERVER + '/getLeyeList?pageSize=1000&current=1'); let listResult;
let listResult = await listRes.json(); try {
const listRes = await eda.sys_ClientUrl.request(SERVER + '/getLeyeList?pageSize=1000&current=1');
listResult = await listRes.json();
} catch (e) {
listResult = { success: false };
}
if (AUTO_RUN && !listResult.success) { if (AUTO_RUN && !listResult.success) {
window.open('leye://open'); eda.sys_Window.open('leye://open');
for (let i = 0; i < 3 && !listResult.success; i++) { for (let i = 0; i < 3 && !listResult.success; i++) {
eda.sys_Message.showToastMessage('等待拉起本地服务端...', ESYS_ToastMessageType.INFO); eda.sys_Message.showToastMessage('等待拉起本地服务端...', ESYS_ToastMessageType.INFO);
listRes = await eda.sys_ClientUrl.request(SERVER + '/getLeyeList?pageSize=1000&current=1');
listResult = await listRes.json(); try {
await new Promise((resolve) => setTimeout(resolve, 1500)); const listRes = await eda.sys_ClientUrl.request(SERVER + '/getLeyeList?pageSize=1000&current=1');
listResult = await listRes.json();
} catch (e) {
listResult = { success: false };
}
if (!listResult.success) {
await new Promise((resolve) => setTimeout(resolve, 1500));
}
} }
} }
if (!listResult.success || !listResult.data) { if (!listResult.success || !listResult.data) {
throw new Error('同步失败'); throw new Error('同步失败');
} }
@@ -298,7 +341,10 @@
const chunkData = await Promise.all( const chunkData = await Promise.all(
chunk.map(async (dev) => { chunk.map(async (dev) => {
try { try {
const EDA_HOST = eda.sys_Environment.isClient() ? 'https://client' : 'https://pro.lceda.cn'; 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, { const infoRes = await eda.sys_ClientUrl.request(EDA_HOST + '/api/v2/devices/' + dev.uuid, 'GET', null, {
headers: { path: '0819f05c4eef4c71ace90d822a990e87' }, headers: { path: '0819f05c4eef4c71ace90d822a990e87' },
}); });
@@ -334,7 +380,7 @@
} catch (error) { } catch (error) {
tableBody.innerHTML = `<tr><td colspan="8" class="text-center py-6 text-red-500">加载失败: ${error.message}</td></tr>`; tableBody.innerHTML = `<tr><td colspan="8" class="text-center py-6 text-red-500">加载失败: ${error.message}</td></tr>`;
console.log('加载失败: ', error); console.log('加载失败: ', error);
eda.sys_Log.add('加载失败: ', error.message); eda.sys_Log.add('加载失败: ' + error.message, ESYS_LogType.ERROR);
} }
} }
@@ -551,8 +597,34 @@
} }
}; };
tableBody.oncontextmenu = (e) => {
const row = e.target.closest('tr');
if (row && row.dataset.row) {
e.preventDefault();
selectRow(row);
contextMenu.classList.remove('hidden');
let x = e.clientX;
let y = e.clientY;
const menuWidth = 160;
const menuHeight = 110;
if (x + menuWidth > window.innerWidth) x -= menuWidth;
if (y + menuHeight > window.innerHeight) y -= menuHeight;
contextMenu.style.left = `${x}px`;
contextMenu.style.top = `${y}px`;
}
};
placeButton.onclick = async () => { placeButton.onclick = async () => {
if (!selectedRowData) return; if (!selectedRowData) return;
if ((await eda.dmt_SelectControl.getCurrentDocumentInfo()).documentType !== EDMT_EditorDocumentType.SCHEMATIC_PAGE) {
eda.sys_Message.showToastMessage('只有原理图页可放置器件', ESYS_ToastMessageType.WARNING);
return;
}
await eda.sys_IFrame.closeIFrame('leye-main'); await eda.sys_IFrame.closeIFrame('leye-main');
try { try {
await eda.sch_PrimitiveComponent.placeComponentWithMouse({ await eda.sch_PrimitiveComponent.placeComponentWithMouse({
@@ -671,8 +743,40 @@
} }
}; };
cmViewProduct.onclick = () => {
if (selectedRowData && selectedRowData.lcscId) {
const url = `https://so.szlcsc.com/global.html?c=&k=${selectedRowData.lcscId}`;
eda.sys_Window.open(url, ESYS_WindowOpenTarget.BLANK);
}
};
cmViewDatasheet.onclick = async () => {
if (selectedRowData && selectedRowData.uuid) {
const data = await eda.lib_Device.get(selectedRowData.uuid);
if (data && data.property && data.property.otherProperty && data.property.otherProperty['Datasheet']) {
const url = data.property.otherProperty['Datasheet'];
eda.sys_Window.open(String(url), ESYS_WindowOpenTarget.BLANK);
} else {
eda.sys_Message.showToastMessage('该器件无数据手册属性', ESYS_ToastMessageType.WARNING);
}
}
};
cmAddLcscCarDatasheet.onclick = () => {
// cmViewProduct.click();
eda.sys_Message.showToastMessage('暂不支持', ESYS_ToastMessageType.INFO);
};
cmEdit.onclick = () => {
editBtn.click();
};
document.getElementById('cancel-btn').onclick = () => eda.sys_IFrame.closeIFrame('leye-main'); document.getElementById('cancel-btn').onclick = () => eda.sys_IFrame.closeIFrame('leye-main');
document.addEventListener('click', () => {
contextMenu.classList.add('hidden');
});
initInventoryData(); initInventoryData();
}); });
</script> </script>