Compare commits

..

No commits in common. "master" and "v1.0.6" have entirely different histories.

11 changed files with 1991 additions and 2526 deletions

View File

@ -17,5 +17,5 @@
/package-lock.json /package-lock.json
/package.json /package.json
/tsconfig.json /tsconfig.json
/backend/
debug.log debug.log
/iframe/js/s_*

View File

@ -1,20 +1,3 @@
# 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.0.6
1. 增加远程扫码入库功能 1. 增加远程扫码入库功能

View File

@ -10,16 +10,14 @@ LEYE 电子元器件库存管理系统 EDA 联动扩展
本扩展允许用户通过立创商城 C 编号、立创商城订单导入元器件,支持从库存内查询、放置器件,支持通过 BOM 批量出库。 本扩展允许用户通过立创商城 C 编号、立创商城订单导入元器件,支持从库存内查询、放置器件,支持通过 BOM 批量出库。
使用本扩展需要在本地安装 [LEYE Service 服务端](https://lrurl.top/LeyeService)(目前仅提供 Windows 版[服务端开源](/backend/server.js)),数据均储存在本地,不会上传。 使用本扩展需要在本地安装 [LEYE Service 服务端](https://lrurl.top/LeyeService)(目前仅提供 Windows 版),数据均储存在本地,不会上传。
## [介绍视频](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-配置-允许外部交互。
@ -43,12 +41,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 并导入对应器件。
@ -65,7 +63,7 @@ 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` |
@ -76,7 +74,6 @@ 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.9", "version": "1.0.6",
"publisher": "Mr_Fang", "publisher": "Mr_Fang",
"engines": { "engines": {
"eda": "^3.2.80" "eda": "^3.2.80"
@ -14,7 +14,9 @@
"url": "https://gitea.miri.site/Mr_Fang/eext-leye" "url": "https://gitea.miri.site/Mr_Fang/eext-leye"
}, },
"categories": "Schematic", "categories": "Schematic",
"keywords": ["Tools", "库管", "库存管理"], "keywords": [
"Tools", "库管", "库存管理"
],
"images": { "images": {
"logo": "./images/logo.png" "logo": "./images/logo.png"
}, },

View File

@ -1,6 +1,4 @@
*, *, ::before, ::after {
::before,
::after {
--tw-border-spacing-x: 0; --tw-border-spacing-x: 0;
--tw-border-spacing-y: 0; --tw-border-spacing-y: 0;
--tw-translate-x: 0; --tw-translate-x: 0;
@ -156,7 +154,7 @@ html,
-o-tab-size: 4; -o-tab-size: 4;
tab-size: 4; tab-size: 4;
/* 3 */ /* 3 */
font-family: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
/* 4 */ /* 4 */
font-feature-settings: normal; font-feature-settings: normal;
/* 5 */ /* 5 */
@ -245,7 +243,7 @@ code,
kbd, kbd,
samp, samp,
pre { pre {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
/* 1 */ /* 1 */
font-feature-settings: normal; font-feature-settings: normal;
/* 2 */ /* 2 */
@ -488,8 +486,7 @@ textarea {
2. Set the default placeholder color to the user's configured gray 400 color. 2. Set the default placeholder color to the user's configured gray 400 color.
*/ */
input::-moz-placeholder, input::-moz-placeholder, textarea::-moz-placeholder {
textarea::-moz-placeholder {
opacity: 1; opacity: 1;
/* 1 */ /* 1 */
color: #9ca3af; color: #9ca3af;
@ -509,7 +506,7 @@ Set the default cursor for buttons.
*/ */
button, button,
[role='button'] { [role="button"] {
cursor: pointer; cursor: pointer;
} }
@ -553,7 +550,7 @@ video {
/* Make elements with the HTML hidden attribute stay hidden by default */ /* Make elements with the HTML hidden attribute stay hidden by default */
[hidden]:where(:not([hidden='until-found'])) { [hidden]:where(:not([hidden="until-found"])) {
display: none; display: none;
} }
@ -635,34 +632,10 @@ video {
right: 0px; right: 0px;
} }
.top-0 {
top: 0px;
}
.z-10 {
z-index: 10;
}
.z-40 {
z-index: 40;
}
.z-50 { .z-50 {
z-index: 50; z-index: 50;
} }
.order-1 {
order: 1;
}
.order-2 {
order: 2;
}
.order-3 {
order: 3;
}
.col-span-2 { .col-span-2 {
grid-column: span 2 / span 2; grid-column: span 2 / span 2;
} }
@ -671,10 +644,6 @@ video {
margin: 0.25rem; margin: 0.25rem;
} }
.m-2 {
margin: 0.5rem;
}
.m-3 { .m-3 {
margin: 0.75rem; margin: 0.75rem;
} }
@ -696,10 +665,6 @@ video {
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
} }
.mb-3 {
margin-bottom: 0.75rem;
}
.mb-4 { .mb-4 {
margin-bottom: 1rem; margin-bottom: 1rem;
} }
@ -813,10 +778,6 @@ video {
height: 1rem; height: 1rem;
} }
.h-5 {
height: 1.25rem;
}
.h-\[200px\] { .h-\[200px\] {
height: 200px; height: 200px;
} }
@ -829,18 +790,6 @@ video {
height: 100%; height: 100%;
} }
.h-screen {
height: 100vh;
}
.max-h-full {
max-height: 100%;
}
.min-h-0 {
min-height: 0px;
}
.min-h-screen { .min-h-screen {
min-height: 100vh; min-height: 100vh;
} }
@ -881,10 +830,6 @@ video {
width: 12rem; width: 12rem;
} }
.w-5 {
width: 1.25rem;
}
.w-56 { .w-56 {
width: 14rem; width: 14rem;
} }
@ -925,23 +870,10 @@ video {
width: 92%; width: 92%;
} }
.w-auto {
width: auto;
}
.w-fit {
width: -moz-fit-content;
width: fit-content;
}
.w-full { .w-full {
width: 100%; width: 100%;
} }
.min-w-0 {
min-width: 0px;
}
.min-w-full { .min-w-full {
min-width: 100%; min-width: 100%;
} }
@ -950,18 +882,10 @@ video {
max-width: 100%; max-width: 100%;
} }
.max-w-md {
max-width: 28rem;
}
.flex-1 { .flex-1 {
flex: 1 1 0%; flex: 1 1 0%;
} }
.flex-none {
flex: none;
}
.flex-shrink { .flex-shrink {
flex-shrink: 1; flex-shrink: 1;
} }
@ -979,8 +903,7 @@ video {
} }
.transform { .transform {
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
} }
.cursor-pointer { .cursor-pointer {
@ -997,10 +920,6 @@ video {
resize: both; resize: both;
} }
.grid-cols-1 {
grid-template-columns: repeat(1, minmax(0, 1fr));
}
.grid-cols-2 { .grid-cols-2 {
grid-template-columns: repeat(2, minmax(0, 1fr)); grid-template-columns: repeat(2, minmax(0, 1fr));
} }
@ -1009,10 +928,6 @@ video {
grid-template-columns: repeat(3, minmax(0, 1fr)); grid-template-columns: repeat(3, minmax(0, 1fr));
} }
.flex-row {
flex-direction: row;
}
.flex-col { .flex-col {
flex-direction: column; flex-direction: column;
} }
@ -1021,10 +936,6 @@ video {
flex-wrap: wrap; flex-wrap: wrap;
} }
.items-start {
align-items: flex-start;
}
.items-center { .items-center {
align-items: center; align-items: center;
} }
@ -1041,10 +952,6 @@ video {
justify-content: space-between; justify-content: space-between;
} }
.gap-1 {
gap: 0.25rem;
}
.gap-2 { .gap-2 {
gap: 0.5rem; gap: 0.5rem;
} }
@ -1053,10 +960,6 @@ video {
gap: 0.75rem; gap: 0.75rem;
} }
.gap-4 {
gap: 1rem;
}
.space-x-1 > :not([hidden]) ~ :not([hidden]) { .space-x-1 > :not([hidden]) ~ :not([hidden]) {
--tw-space-x-reverse: 0; --tw-space-x-reverse: 0;
margin-right: calc(0.25rem * var(--tw-space-x-reverse)); margin-right: calc(0.25rem * var(--tw-space-x-reverse));
@ -1081,12 +984,6 @@ video {
margin-left: calc(1rem * calc(1 - var(--tw-space-x-reverse))); margin-left: calc(1rem * calc(1 - var(--tw-space-x-reverse)));
} }
.space-x-6 > :not([hidden]) ~ :not([hidden]) {
--tw-space-x-reverse: 0;
margin-right: calc(1.5rem * var(--tw-space-x-reverse));
margin-left: calc(1.5rem * calc(1 - var(--tw-space-x-reverse)));
}
.space-y-0 > :not([hidden]) ~ :not([hidden]) { .space-y-0 > :not([hidden]) ~ :not([hidden]) {
--tw-space-y-reverse: 0; --tw-space-y-reverse: 0;
margin-top: calc(0px * calc(1 - var(--tw-space-y-reverse))); margin-top: calc(0px * calc(1 - var(--tw-space-y-reverse)));
@ -1123,32 +1020,17 @@ video {
margin-bottom: calc(1rem * var(--tw-space-y-reverse)); margin-bottom: calc(1rem * var(--tw-space-y-reverse));
} }
.space-y-5 > :not([hidden]) ~ :not([hidden]) {
--tw-space-y-reverse: 0;
margin-top: calc(1.25rem * calc(1 - var(--tw-space-y-reverse)));
margin-bottom: calc(1.25rem * var(--tw-space-y-reverse));
}
.divide-y > :not([hidden]) ~ :not([hidden]) { .divide-y > :not([hidden]) ~ :not([hidden]) {
--tw-divide-y-reverse: 0; --tw-divide-y-reverse: 0;
border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse))); border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse)));
border-bottom-width: calc(1px * var(--tw-divide-y-reverse)); border-bottom-width: calc(1px * var(--tw-divide-y-reverse));
} }
.divide-gray-100 > :not([hidden]) ~ :not([hidden]) {
--tw-divide-opacity: 1;
border-color: rgb(243 244 246 / var(--tw-divide-opacity, 1));
}
.divide-gray-200 > :not([hidden]) ~ :not([hidden]) { .divide-gray-200 > :not([hidden]) ~ :not([hidden]) {
--tw-divide-opacity: 1; --tw-divide-opacity: 1;
border-color: rgb(229 231 235 / var(--tw-divide-opacity, 1)); border-color: rgb(229 231 235 / var(--tw-divide-opacity, 1));
} }
.overflow-auto {
overflow: auto;
}
.overflow-hidden { .overflow-hidden {
overflow: hidden; overflow: hidden;
} }
@ -1167,10 +1049,6 @@ video {
white-space: nowrap; white-space: nowrap;
} }
.whitespace-nowrap {
white-space: nowrap;
}
.rounded { .rounded {
border-radius: 0.25rem; border-radius: 0.25rem;
} }
@ -1183,10 +1061,6 @@ video {
border-radius: 0.375rem; border-radius: 0.375rem;
} }
.rounded-xl {
border-radius: 0.75rem;
}
.border { .border {
border-width: 1px; border-width: 1px;
} }
@ -1203,18 +1077,10 @@ video {
border-bottom-width: 1px; border-bottom-width: 1px;
} }
.border-b-0 {
border-bottom-width: 0px;
}
.border-l { .border-l {
border-left-width: 1px; border-left-width: 1px;
} }
.border-r {
border-right-width: 1px;
}
.border-t { .border-t {
border-top-width: 1px; border-top-width: 1px;
} }
@ -1233,11 +1099,6 @@ video {
border-color: rgb(147 197 253 / var(--tw-border-opacity, 1)); border-color: rgb(147 197 253 / var(--tw-border-opacity, 1));
} }
.border-blue-500 {
--tw-border-opacity: 1;
border-color: rgb(59 130 246 / var(--tw-border-opacity, 1));
}
.border-blue-600 { .border-blue-600 {
--tw-border-opacity: 1; --tw-border-opacity: 1;
border-color: rgb(37 99 235 / var(--tw-border-opacity, 1)); border-color: rgb(37 99 235 / var(--tw-border-opacity, 1));
@ -1298,11 +1159,6 @@ video {
background-color: rgb(243 244 246 / var(--tw-bg-opacity, 1)); background-color: rgb(243 244 246 / var(--tw-bg-opacity, 1));
} }
.bg-gray-200 {
--tw-bg-opacity: 1;
background-color: rgb(229 231 235 / var(--tw-bg-opacity, 1));
}
.bg-gray-50 { .bg-gray-50 {
--tw-bg-opacity: 1; --tw-bg-opacity: 1;
background-color: rgb(249 250 251 / var(--tw-bg-opacity, 1)); background-color: rgb(249 250 251 / var(--tw-bg-opacity, 1));
@ -1328,11 +1184,6 @@ video {
background-color: rgb(220 38 38 / var(--tw-bg-opacity, 1)); background-color: rgb(220 38 38 / var(--tw-bg-opacity, 1));
} }
.bg-slate-900 {
--tw-bg-opacity: 1;
background-color: rgb(15 23 42 / var(--tw-bg-opacity, 1));
}
.bg-transparent { .bg-transparent {
background-color: transparent; background-color: transparent;
} }
@ -1367,10 +1218,6 @@ video {
padding: 1rem; padding: 1rem;
} }
.p-5 {
padding: 1.25rem;
}
.p-6 { .p-6 {
padding: 1.5rem; padding: 1.5rem;
} }
@ -1395,11 +1242,6 @@ video {
padding-right: 1rem; padding-right: 1rem;
} }
.px-5 {
padding-left: 1.25rem;
padding-right: 1.25rem;
}
.px-6 { .px-6 {
padding-left: 1.5rem; padding-left: 1.5rem;
padding-right: 1.5rem; padding-right: 1.5rem;
@ -1430,11 +1272,6 @@ video {
padding-bottom: 0.375rem; padding-bottom: 0.375rem;
} }
.py-10 {
padding-top: 2.5rem;
padding-bottom: 2.5rem;
}
.py-2 { .py-2 {
padding-top: 0.5rem; padding-top: 0.5rem;
padding-bottom: 0.5rem; padding-bottom: 0.5rem;
@ -1450,29 +1287,15 @@ video {
padding-bottom: 0.75rem; padding-bottom: 0.75rem;
} }
.py-4 {
padding-top: 1rem;
padding-bottom: 1rem;
}
.py-6 { .py-6 {
padding-top: 1.5rem; padding-top: 1.5rem;
padding-bottom: 1.5rem; padding-bottom: 1.5rem;
} }
.py-8 {
padding-top: 2rem;
padding-bottom: 2rem;
}
.pb-1 { .pb-1 {
padding-bottom: 0.25rem; padding-bottom: 0.25rem;
} }
.pb-2 {
padding-bottom: 0.5rem;
}
.pb-20 { .pb-20 {
padding-bottom: 5rem; padding-bottom: 5rem;
} }
@ -1502,11 +1325,11 @@ video {
} }
.font-mono { .font-mono {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
} }
.font-sans { .font-sans {
font-family: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
} }
.text-2xl { .text-2xl {
@ -1575,16 +1398,6 @@ video {
color: rgb(37 99 235 / var(--tw-text-opacity, 1)); color: rgb(37 99 235 / var(--tw-text-opacity, 1));
} }
.text-blue-700 {
--tw-text-opacity: 1;
color: rgb(29 78 216 / var(--tw-text-opacity, 1));
}
.text-blue-800 {
--tw-text-opacity: 1;
color: rgb(30 64 175 / 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));
@ -1664,12 +1477,6 @@ video {
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
} }
.shadow-2xl {
--tw-shadow: 0 25px 50px -12px rgb(0 0 0 / 0.25);
--tw-shadow-colored: 0 25px 50px -12px var(--tw-shadow-color);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
}
.shadow-\[0_0_8px_rgba\(59\2c 130\2c 246\2c 0\.8\)\] { .shadow-\[0_0_8px_rgba\(59\2c 130\2c 246\2c 0\.8\)\] {
--tw-shadow: 0 0 8px rgba(59,130,246,0.8); --tw-shadow: 0 0 8px rgba(59,130,246,0.8);
--tw-shadow-colored: 0 0 8px var(--tw-shadow-color); --tw-shadow-colored: 0 0 8px var(--tw-shadow-color);
@ -1719,89 +1526,24 @@ video {
outline-style: solid; outline-style: solid;
} }
.ring-blue-500 {
--tw-ring-opacity: 1;
--tw-ring-color: rgb(59 130 246 / var(--tw-ring-opacity, 1));
}
.blur { .blur {
--tw-blur: blur(8px); --tw-blur: blur(8px);
filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
var(--tw-sepia) var(--tw-drop-shadow);
} }
.invert { .invert {
--tw-invert: invert(100%); --tw-invert: invert(100%);
filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
var(--tw-sepia) var(--tw-drop-shadow);
} }
.filter { .filter {
filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
var(--tw-sepia) var(--tw-drop-shadow);
}
.backdrop-blur-sm {
--tw-backdrop-blur: blur(4px);
-webkit-backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale)
var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale)
var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
}
.backdrop-filter {
-webkit-backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale)
var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale)
var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
} }
.transition { .transition {
transition-property: transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter;
color, transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter;
background-color, transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter;
border-color,
text-decoration-color,
fill,
stroke,
opacity,
box-shadow,
transform,
filter,
-webkit-backdrop-filter;
transition-property:
color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter;
transition-property:
color,
background-color,
border-color,
text-decoration-color,
fill,
stroke,
opacity,
box-shadow,
transform,
filter,
backdrop-filter,
-webkit-backdrop-filter;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
}
.transition-all {
transition-property: all;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
}
.transition-colors {
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
}
.transition-transform {
transition-property: transform;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms; transition-duration: 150ms;
} }

View File

@ -6,54 +6,21 @@
<title>批量出库 - LEYE</title> <title>批量出库 - LEYE</title>
<link href="/iframe/css/index.css" rel="stylesheet" /> <link href="/iframe/css/index.css" rel="stylesheet" />
<style> <style>
#fixed-window { #fixed-window { width: 1000px; height: 600px; border: 1px solid #e5e7eb; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); }
width: 1000px; .table-container { height: calc(600px - 80px - 44px - 64px); overflow-y: auto; }
height: 600px; .bg-match { background-color: #f0fdf4; } /* 淡绿色:匹配成功 */
border: 1px solid #e5e7eb; .bg-no-match { background-color: #fefcf2; } /* 淡黄色:不匹配 */
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); .bg-no-cid { background-color: #fef2f2; } /* 淡红色:无 CID */
} .sticky-header { position: sticky; top: 0; z-index: 10; }
.table-container { .designator-cell { max-width: 120px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
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> </style>
</head> </head>
<body class="bg-gray-100 font-sans text-sm"> <body class="bg-gray-100 font-sans text-sm">
<div id="fixed-window" class="bg-gray-50 flex flex-col overflow-hidden mx-auto"> <div id="fixed-window" class="bg-gray-50 flex flex-col overflow-hidden mx-auto">
<header class="flex-shrink-0 bg-white border-b p-4 flex items-center space-x-3"> <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> <span class="font-medium text-gray-700">立创商城编号导入</span>
<input <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" />
id="input-cid" <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" />
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> <button id="add-manual-btn" class="px-4 py-1.5 bg-blue-600 text-white rounded-md hover:bg-blue-700">添加</button>
</header> </header>
@ -62,7 +29,9 @@
<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-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> <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>
<div class="text-gray-500 text-xs">待出库总数:<span id="list-count" class="font-bold text-blue-600">0</span></div> <div class="text-gray-500 text-xs">
待出库总数:<span id="list-count" class="font-bold text-blue-600">0</span>
</div>
</div> </div>
<main class="flex-grow bg-white m-3 border rounded-lg overflow-hidden shadow-inner"> <main class="flex-grow bg-white m-3 border rounded-lg overflow-hidden shadow-inner">
@ -82,9 +51,7 @@
</tr> </tr>
</thead> </thead>
<tbody id="outbound-table-body" class="divide-y divide-gray-200 text-xs"> <tbody id="outbound-table-body" class="divide-y divide-gray-200 text-xs">
<tr> <tr><td colspan="9" class="text-center py-20 text-gray-400">获取 BOM...</td></tr>
<td colspan="9" class="text-center py-20 text-gray-400">获取 BOM...</td>
</tr>
</tbody> </tbody>
</table> </table>
</div> </div>
@ -93,7 +60,6 @@
<footer class="flex-shrink-0 p-4 bg-white border-t flex justify-end space-x-3"> <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> <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="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="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> <button id="outbound-btn" class="px-6 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 font-bold">一键出库</button>
</footer> </footer>
@ -110,14 +76,12 @@
const cachedRaw = await eda.sys_Storage.getExtensionUserConfig('cache-leye-device-details'); const cachedRaw = await eda.sys_Storage.getExtensionUserConfig('cache-leye-device-details');
try { try {
const details = cachedRaw ? JSON.parse(cachedRaw) : []; const details = cachedRaw ? JSON.parse(cachedRaw) : [];
cachedCIDs = new Set(details.map((d) => String(d.lcscId).toUpperCase())); cachedCIDs = new Set(details.map(d => String(d.lcscId).toUpperCase()));
} catch (e) { } catch(e) { cachedCIDs = new Set(); }
cachedCIDs = new Set();
}
} }
function parseProperty(comp, key) { function parseProperty(comp, key) {
if (key === 'Manufacturer Part') return comp.getState_ManufacturerId(); if (key === "Manufacturer Part") return comp.getState_ManufacturerId();
const props = comp.getState_OtherProperty() || {}; const props = comp.getState_OtherProperty() || {};
return props[key] || '-'; return props[key] || '-';
} }
@ -132,35 +96,35 @@
} }
async function scanSchematic() { async function scanSchematic() {
const all = await eda.sch_PrimitiveComponent.getAll('part', true); const all = await eda.sch_PrimitiveComponent.getAll("part", true);
const groups = {}; const groups = {};
all.forEach((comp) => { all.forEach(comp => {
const uuid = comp.getState_Component().uuid; const uuid = comp.getState_Component().uuid;
if (!groups[uuid]) { if (!groups[uuid]) {
groups[uuid] = { groups[uuid] = {
name: resolveModelName(comp), name: resolveModelName(comp),
designators: [], designators: [],
value: parseProperty(comp, 'Value'), value: parseProperty(comp, "Value"),
footprint: parseProperty(comp, 'Supplier Footprint'), footprint: parseProperty(comp, "Supplier Footprint"),
brand: comp.getState_Manufacturer() || '-', brand: comp.getState_Manufacturer() || '-',
lcscId: (comp.getState_SupplierId() || '').toUpperCase(), lcscId: (comp.getState_SupplierId() || '').toUpperCase(),
quantity: 0, quantity: 0,
selected: false, selected: false
}; };
} }
groups[uuid].quantity++; groups[uuid].quantity++;
groups[uuid].designators.push(comp.getState_Designator()); groups[uuid].designators.push(comp.getState_Designator());
}); });
outboundList = Object.keys(groups).map((uuid) => { outboundList = Object.keys(groups).map(uuid => {
const item = groups[uuid]; const item = groups[uuid];
const match = item.lcscId && cachedCIDs.has(item.lcscId); const match = item.lcscId && cachedCIDs.has(item.lcscId);
return { return {
...item, ...item,
designatorStr: item.designators.sort().join(', '), designatorStr: item.designators.sort().join(', '),
uuid: uuid, uuid: uuid,
selected: match, selected: match
}; };
}); });
renderTable(); renderTable();
@ -174,8 +138,7 @@
} }
listCount.textContent = outboundList.length; listCount.textContent = outboundList.length;
tableBody.innerHTML = outboundList tableBody.innerHTML = outboundList.map((item, index) => {
.map((item, index) => {
const isMatch = item.lcscId && cachedCIDs.has(item.lcscId); const isMatch = item.lcscId && cachedCIDs.has(item.lcscId);
let rowClass = 'bg-no-cid'; let rowClass = 'bg-no-cid';
if (item.lcscId) { if (item.lcscId) {
@ -204,8 +167,7 @@
<button onclick="removeItem(${index})" class="text-red-500 hover:underline">删除</button> <button onclick="removeItem(${index})" class="text-red-500 hover:underline">删除</button>
</td> </td>
</tr>`; </tr>`;
}) }).join('');
.join('');
} }
tableBody.addEventListener('change', (e) => { tableBody.addEventListener('change', (e) => {
@ -225,7 +187,7 @@
}); });
document.getElementById('select-all').onclick = () => { document.getElementById('select-all').onclick = () => {
outboundList.forEach((item) => { outboundList.forEach(item => {
if (item.lcscId && cachedCIDs.has(item.lcscId)) { if (item.lcscId && cachedCIDs.has(item.lcscId)) {
item.selected = true; item.selected = true;
} }
@ -234,7 +196,7 @@
}; };
document.getElementById('select-reverse').onclick = () => { document.getElementById('select-reverse').onclick = () => {
outboundList.forEach((item) => { outboundList.forEach(item => {
if (item.lcscId && cachedCIDs.has(item.lcscId)) { if (item.lcscId && cachedCIDs.has(item.lcscId)) {
item.selected = !item.selected; item.selected = !item.selected;
} }
@ -267,11 +229,7 @@
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 = const infoRes = await eda.sys_ClientUrl.request('https://client/api/v2/devices/' + devs[0].uuid, 'GET', null, { headers: { path: '0819f05c4eef4c71ace90d822a990e87' } });
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; const info = (await infoRes.json()).result;
detail.name = info.attributes['Manufacturer Part'] || '-'; detail.name = info.attributes['Manufacturer Part'] || '-';
detail.value = info.attributes['Value'] || '-'; detail.value = info.attributes['Value'] || '-';
@ -291,14 +249,14 @@
}; };
document.getElementById('outbound-btn').onclick = async () => { document.getElementById('outbound-btn').onclick = async () => {
const selectedItems = outboundList.filter((item) => item.selected && item.lcscId); const selectedItems = outboundList.filter(item => item.selected && item.lcscId);
if (selectedItems.length === 0) { if (selectedItems.length === 0) {
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';
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 = [];
@ -306,16 +264,22 @@
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 getRes = await eda.sys_ClientUrl.request(
`${SERVER}/getLeyeList?lcscId=${item.lcscId}`,
'GET'
);
let getResult = await getRes.json(); let getResult = await getRes.json();
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'); getRes = await eda.sys_ClientUrl.request(
`${SERVER}/getLeyeList?lcscId=${item.lcscId}`,
'GET'
);
getResult = await getRes.json(); getResult = await getRes.json();
await new Promise((resolve) => setTimeout(resolve, 1500)); await new Promise(resolve => setTimeout(resolve, 1500));
} }
} }
@ -336,9 +300,9 @@
'POST', 'POST',
JSON.stringify({ JSON.stringify({
id: remoteId, id: remoteId,
quantity: newQuantity, quantity: newQuantity
}), }),
{ headers: { 'Content-Type': 'application/json' } }, { headers: { 'Content-Type': 'application/json' } }
); );
const postResult = await postRes.json(); const postResult = await postRes.json();
@ -353,7 +317,7 @@
} }
} catch (e) { } catch (e) {
console.error(`出库请求异常: ${item.lcscId}`, e); console.error(`出库请求异常: ${item.lcscId}`, e);
eda.sys_Log.add(`出库请求异常: ${item.lcscId}: ${e.message}`); eda.sys_Log.add(`出库请求异常: ${item.lcscId}: ${e.message}`)
failItems.push(`${item.lcscId} (网络异常)`); failItems.push(`${item.lcscId} (网络异常)`);
} }
} }
@ -364,17 +328,16 @@
eda.sys_Message.showToastMessage(`成功出库 ${successCount} 项!`, ESYS_ToastMessageType.SUCCESS); eda.sys_Message.showToastMessage(`成功出库 ${successCount} 项!`, ESYS_ToastMessageType.SUCCESS);
} else { } else {
const errorMsg = failItems.join('; '); const errorMsg = failItems.join('; ');
eda.sys_Message.showToastMessage(`出库完成:成功:${successCount},失败:${failItems.length}`, ESYS_ToastMessageType.WARNING); eda.sys_Message.showToastMessage(`出库完成:成功:${successCount},失败:${failItems.length}`,
ESYS_ToastMessageType.WARNING);
eda.sys_Log.add(errorMsg); eda.sys_Log.add(errorMsg);
} }
}; };
document.getElementById('fill-bom-btn').onclick = () => scanSchematic();
document.getElementById('cancel-btn').onclick = () => eda.sys_IFrame.closeIFrame(); document.getElementById('cancel-btn').onclick = () => eda.sys_IFrame.closeIFrame();
await initCache(); await initCache();
if (eda.sys_Storage.getExtensionUserConfig('auto-fill-bom')) await scanSchematic(); await scanSchematic();
}); });
</script> </script>
</body> </body>

View File

@ -6,40 +6,13 @@
<title>器件入库 - LEYE</title> <title>器件入库 - LEYE</title>
<link href="/iframe/css/index.css" rel="stylesheet" /> <link href="/iframe/css/index.css" rel="stylesheet" />
<style> <style>
.scrollbar-hide { .scrollbar-hide { -ms-overflow-style: none; scrollbar-width: none; }
-ms-overflow-style: none; .scrollbar-hide::-webkit-scrollbar { display: none; }
scrollbar-width: 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; }
.scrollbar-hide::-webkit-scrollbar { .sticky-header { position: sticky; top: 0; z-index: 10; }
display: none; @keyframes scan { 0% { top: 0; } 100% { top: 100%; } }
} .scanning #scan-line { display: block; animation: scan 2s linear infinite; }
#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> </style>
<script src="/iframe/js/xlsx.full.min.js" language="JavaScript"></script> <script src="/iframe/js/xlsx.full.min.js" language="JavaScript"></script>
<script src="/iframe/js/jsQR.min.js" language="JavaScript"></script> <script src="/iframe/js/jsQR.min.js" language="JavaScript"></script>
@ -51,33 +24,15 @@
<header class="flex-shrink-0 bg-white border-b border-gray-200 shadow-sm p-4 space-y-3"> <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"> <div class="flex items-center space-x-3">
<span class="font-medium text-gray-700 w-32">立创商城订单导入</span> <span class="font-medium text-gray-700 w-32">立创商城订单导入</span>
<input <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" />
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-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"> <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>
添加订单详情 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> <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>
<div class="flex items-center space-x-3"> <div class="flex items-center space-x-3">
<span class="font-medium text-gray-700 w-32">立创商城编号导入</span> <span class="font-medium text-gray-700 w-32">立创商城编号导入</span>
<input <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" />
id="single-cid" <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" />
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> <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> </div>
</header> </header>
@ -87,7 +42,9 @@
<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-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> <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>
<div class="text-gray-500 text-xs">待入库项:<span id="list-count" class="font-bold text-blue-600">0</span></div> <div class="text-gray-500 text-xs">
待入库项:<span id="list-count" class="font-bold text-blue-600">0</span>
</div>
</div> </div>
<main class="flex-grow bg-white overflow-hidden m-3 border rounded-lg shadow-inner"> <main class="flex-grow bg-white overflow-hidden m-3 border rounded-lg shadow-inner">
@ -107,9 +64,7 @@
</tr> </tr>
</thead> </thead>
<tbody id="import-table-body" class="bg-white divide-y divide-gray-200 text-xs"> <tbody id="import-table-body" class="bg-white divide-y divide-gray-200 text-xs">
<tr id="empty-row"> <tr id="empty-row"><td colspan="9" class="text-center py-20 text-gray-400">列表为空,请从上方导入数据</td></tr>
<td colspan="9" class="text-center py-20 text-gray-400">列表为空,请从上方导入数据</td>
</tr>
</tbody> </tbody>
</table> </table>
</div> </div>
@ -117,9 +72,7 @@
<div class="flex-shrink-0 p-4 bg-white border-t border-gray-200 flex justify-end space-x-3 shadow-lg"> <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="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 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>
批量入库
</button>
</div> </div>
<div id="qr-dialog" class="fixed inset-0 z-50 hidden flex items-center justify-center bg-black bg-opacity-50"> <div id="qr-dialog" class="fixed inset-0 z-50 hidden flex items-center justify-center bg-black bg-opacity-50">
@ -137,10 +90,7 @@
<div class="relative w-[250px] h-[250px] mx-auto bg-black rounded-lg overflow-hidden border-2 border-gray-300"> <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> <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> <canvas id="qr-canvas" class="hidden"></canvas>
<div <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>
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>
<div id="scan-result" class="p-3 bg-blue-50 rounded-md border border-blue-100"> <div id="scan-result" class="p-3 bg-blue-50 rounded-md border border-blue-100">
@ -153,16 +103,8 @@
<div class="grid grid-cols-3 gap-2"> <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-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 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>
</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> </div>
@ -171,8 +113,8 @@
<script> <script>
document.addEventListener('DOMContentLoaded', function () { document.addEventListener('DOMContentLoaded', function () {
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;
const tableBody = document.getElementById('import-table-body'); const tableBody = document.getElementById('import-table-body');
const listCount = document.getElementById('list-count'); const listCount = document.getElementById('list-count');
@ -194,9 +136,7 @@
return; return;
} }
tableBody.innerHTML = importList tableBody.innerHTML = importList.map((item, index) => `
.map(
(item, index) => `
<tr class="hover:bg-gray-50"> <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-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> <td class="px-3 py-2 font-medium">${item.name || '-'}</td>
@ -212,19 +152,17 @@
<button onclick="removeItem(${index})" class="text-red-500 hover:text-red-700">删除</button> <button onclick="removeItem(${index})" class="text-red-500 hover:text-red-700">删除</button>
</td> </td>
</tr> </tr>
`, `).join('');
)
.join('');
listCount.textContent = importList.length; listCount.textContent = importList.length;
} }
async function updateCameraList() { async function updateCameraList() {
try { try {
const devices = await navigator.mediaDevices.enumerateDevices(); const devices = await navigator.mediaDevices.enumerateDevices();
const videoDevices = devices.filter((device) => device.kind === 'videoinput'); const videoDevices = devices.filter(device => device.kind === 'videoinput');
let options = videoDevices let options = videoDevices.map(d =>
.map((d) => `<option value="${d.deviceId}">${d.label || '摄像头 ' + d.deviceId.slice(0, 5)}</option>`) `<option value="${d.deviceId}">${d.label || '摄像头 ' + d.deviceId.slice(0, 5)}</option>`
.join(''); ).join('');
options += `<option value="WEBSOCKET_MODE">WebSocket 远程扫码</option>`; options += `<option value="WEBSOCKET_MODE">WebSocket 远程扫码</option>`;
cameraSelect.innerHTML = options; cameraSelect.innerHTML = options;
@ -247,12 +185,12 @@
function stopCamera() { function stopCamera() {
if (videoStream) { if (videoStream) {
videoStream.getTracks().forEach((track) => track.stop()); videoStream.getTracks().forEach(track => track.stop());
videoStream = null; videoStream = null;
} }
if (mqttClient) { if (mqttClient) {
console.log('正在断开 MQTT 远程连接...'); console.log("正在断开 MQTT 远程连接...");
mqttClient.end(true); // 强制关闭连接 mqttClient.end(true); // 强制关闭连接
mqttClient = null; mqttClient = null;
} }
@ -295,7 +233,7 @@
eda.sys_Message.showToastMessage('远程扫码成功', ESYS_ToastMessageType.INFO); eda.sys_Message.showToastMessage('远程扫码成功', ESYS_ToastMessageType.INFO);
} }
} catch (e) { } catch (e) {
console.error('MQTT数据解析失败', e); console.error("MQTT数据解析失败", e);
} }
}); });
} }
@ -303,14 +241,14 @@
function createQrCanvas() { function createQrCanvas() {
const canvas = document.createElement('canvas'); const canvas = document.createElement('canvas');
canvas.id = 'remote-qr-canvas'; canvas.id = 'remote-qr-canvas';
canvas.className = 'absolute inset-0 w-full h-full p-4 bg-white'; canvas.className = "absolute inset-0 w-full h-full p-4 bg-white";
qrVideo.parentElement.appendChild(canvas); qrVideo.parentElement.appendChild(canvas);
return canvas; return canvas;
} }
async function startLocalCamera(deviceId) { async function startLocalCamera(deviceId) {
const constraints = { const constraints = {
video: deviceId ? { deviceId: { exact: deviceId } } : { facingMode: 'environment' }, video: deviceId ? { deviceId: { exact: deviceId } } : { facingMode: 'environment' }
}; };
try { try {
videoStream = await navigator.mediaDevices.getUserMedia(constraints); videoStream = await navigator.mediaDevices.getUserMedia(constraints);
@ -327,7 +265,7 @@
stopCamera(); stopCamera();
const mode = cameraSelect.value; const mode = cameraSelect.value;
if (mode === 'WEBSOCKET_MODE') { if (mode === "WEBSOCKET_MODE") {
startRemoteMode(); startRemoteMode();
} else { } else {
startLocalCamera(mode); startLocalCamera(mode);
@ -365,7 +303,7 @@
lcscId: pcMatch[1].trim().toUpperCase(), lcscId: pcMatch[1].trim().toUpperCase(),
name: pmMatch ? pmMatch[1].trim() : '未知型号', name: pmMatch ? pmMatch[1].trim() : '未知型号',
quantity: qtyMatch ? parseInt(qtyMatch[1]) : 1, quantity: qtyMatch ? parseInt(qtyMatch[1]) : 1,
selected: true, selected: true
}; };
document.getElementById('res-cid').textContent = currentScanData.lcscId; document.getElementById('res-cid').textContent = currentScanData.lcscId;
@ -373,7 +311,7 @@
document.getElementById('res-qty').textContent = currentScanData.quantity; document.getElementById('res-qty').textContent = currentScanData.quantity;
document.getElementById('btn-add-to-list').disabled = false; document.getElementById('btn-add-to-list').disabled = false;
} else { } else {
throw new Error('无效的二维码格式'); throw new Error("无效的二维码格式");
} }
} catch (e) { } catch (e) {
eda.sys_Message.showToastMessage('解析失败: ' + e.message, ESYS_ToastMessageType.ERROR); eda.sys_Message.showToastMessage('解析失败: ' + e.message, ESYS_ToastMessageType.ERROR);
@ -406,16 +344,13 @@
} }
}; };
window.updateQty = (index, val) => { window.updateQty = (index, val) => { importList[index].quantity = parseInt(val) || 0; };
importList[index].quantity = parseInt(val) || 0; window.removeItem = (index) => { importList.splice(index, 1); renderList(); };
};
window.removeItem = (index) => {
importList.splice(index, 1);
renderList();
};
document.getElementById('import-order-btn').onclick = async () => { document.getElementById('import-order-btn').onclick = async () => {
eda.sys_Dialog.showInformationMessage('此功能因可能存在的安全问题已被弃用', '提示', '知道了'); eda.sys_Dialog.showInformationMessage('此功能因可能存在的安全问题已被弃用', '提示', '知道了');
}; };
document.getElementById('open-qr-btn').onclick = async () => { document.getElementById('open-qr-btn').onclick = async () => {
@ -460,7 +395,7 @@
quantity: quantity, quantity: quantity,
value: '-', value: '-',
childCat: '-', childCat: '-',
selected: true, selected: true
}); });
} }
@ -476,6 +411,7 @@
}; };
reader.readAsArrayBuffer(file); reader.readAsArrayBuffer(file);
} catch (err) { } catch (err) {
eda.sys_Message.showToastMessage('读取文件失败: ' + err.message, ESYS_ToastMessageType.ERROR); eda.sys_Message.showToastMessage('读取文件失败: ' + err.message, ESYS_ToastMessageType.ERROR);
eda.sys_Log.add('Excel 导入错误: ' + err.stack); eda.sys_Log.add('Excel 导入错误: ' + err.stack);
@ -501,7 +437,7 @@
}; };
async function enrichData(items) { async function enrichData(items) {
const lcscIds = items.map((i) => i.lcscId); const lcscIds = items.map(i => i.lcscId);
const devs = await eda.lib_Device.getByLcscIds(lcscIds); const devs = await eda.lib_Device.getByLcscIds(lcscIds);
for (let i = 0; i < items.length; i++) { for (let i = 0; i < items.length; i++) {
@ -510,11 +446,12 @@
if (dev && dev.uuid) { if (dev && dev.uuid) {
try { try {
const EDA_HOST = const infoRes = await eda.sys_ClientUrl.request(
eda.sys_Environment.isClient() && !eda.sys_Environment.isOnlineMode() ? 'https://client' : 'https://pro.lceda.cn'; 'https://client/api/v2/devices/' + dev.uuid,
const infoRes = await eda.sys_ClientUrl.request(EDA_HOST + '/api/v2/devices/' + dev.uuid, 'GET', null, { 'GET',
headers: { path: '0819f05c4eef4c71ace90d822a990e87' }, null,
}); { headers: { path: '0819f05c4eef4c71ace90d822a990e87' } }
);
const infoJson = await infoRes.json(); const infoJson = await infoRes.json();
const info = infoJson.result; const info = infoJson.result;
@ -535,11 +472,11 @@
} }
document.getElementById('select-all').onclick = () => { document.getElementById('select-all').onclick = () => {
importList.forEach((i) => (i.selected = true)); importList.forEach(i => i.selected = true);
renderList(); renderList();
}; };
document.getElementById('select-reverse').onclick = () => { document.getElementById('select-reverse').onclick = () => {
importList.forEach((i) => (i.selected = !i.selected)); importList.forEach(i => i.selected = !i.selected);
renderList(); renderList();
}; };
tableBody.addEventListener('change', (e) => { tableBody.addEventListener('change', (e) => {
@ -555,7 +492,7 @@
}; };
document.getElementById('batch-save-btn').onclick = async () => { document.getElementById('batch-save-btn').onclick = async () => {
const toSave = importList.filter((i) => i.selected && i.lcscId); const toSave = importList.filter(i => i.selected && i.lcscId);
if (toSave.length === 0) { if (toSave.length === 0) {
return eda.sys_Message.showToastMessage('请先选择要入库的项', ESYS_ToastMessageType.WARNING); return eda.sys_Message.showToastMessage('请先选择要入库的项', ESYS_ToastMessageType.WARNING);
@ -569,12 +506,15 @@
const postData = JSON.stringify({ const postData = JSON.stringify({
name: item.name || '-', name: item.name || '-',
lcscId: item.lcscId, lcscId: item.lcscId,
quantity: item.quantity, quantity: item.quantity
}); });
let res = await eda.sys_ClientUrl.request(SERVER + '/addLeyeList', 'POST', postData, { let res = await eda.sys_ClientUrl.request(
headers: { 'Content-Type': 'application/json' }, SERVER + '/addLeyeList',
}); 'POST',
postData,
{ headers: { 'Content-Type': 'application/json' } }
);
let result = await res.json(); let result = await res.json();
@ -582,11 +522,14 @@
window.open('leye://open'); 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, { res = await eda.sys_ClientUrl.request(
headers: { 'Content-Type': 'application/json' }, SERVER + '/addLeyeList',
}); 'POST',
postData,
{ headers: { 'Content-Type': 'application/json' } }
);
result = await res.json(); result = await res.json();
await new Promise((resolve) => setTimeout(resolve, 1500)); await new Promise(resolve => setTimeout(resolve, 1500));
} }
} }
@ -609,10 +552,8 @@
if (failCount === 0) { if (failCount === 0) {
eda.sys_Message.showToastMessage(`全部入库成功,共 ${successCount} 项`, ESYS_ToastMessageType.SUCCESS); eda.sys_Message.showToastMessage(`全部入库成功,共 ${successCount} 项`, ESYS_ToastMessageType.SUCCESS);
} else { } else {
eda.sys_Message.showToastMessage( eda.sys_Message.showToastMessage(`入库完成,成功: ${successCount}, 失败: ${failCount}`,
`入库完成,成功: ${successCount}, 失败: ${failCount}`, failCount > 0 ? ESYS_ToastMessageType.WARNING : ESYS_ToastMessageType.SUCCESS);
failCount > 0 ? ESYS_ToastMessageType.WARNING : ESYS_ToastMessageType.SUCCESS,
);
} }
}; };
}); });

View File

@ -6,58 +6,19 @@
<title>查看库存 - LEYE</title> <title>查看库存 - LEYE</title>
<link href="/iframe/css/index.css" rel="stylesheet" /> <link href="/iframe/css/index.css" rel="stylesheet" />
<style> <style>
html { html { user-select: none; }
user-select: none; .scrollbar-hide { -ms-overflow-style: none; scrollbar-width: none; }
} .scrollbar-hide::-webkit-scrollbar { display: none; }
.scrollbar-hide { #fixed-window { width: 1280px; height: 680px; border: 1px solid #e5e7eb; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); }
-ms-overflow-style: none; .table-container { height: calc(680px - 48px - 52px - 64px); overflow-y: auto; }
scrollbar-width: none; .sticky-header { position: sticky; top: 0; z-index: 10; }
} .sort-icon { display: inline-block; margin-left: 4px; font-size: 10px; color: #9ca3af; }
.scrollbar-hide::-webkit-scrollbar { .sort-active { color: #2563eb !important; }
display: none; .hidden { display: none !important; }
} .cat-toggle { transition: transform 0.2s; cursor: pointer; padding: 4px; }
#fixed-window { .cat-toggle.collapsed { transform: rotate(-90deg); }
width: 1280px; .children-container { overflow: hidden; transition: max-height 0.3s ease-out; }
height: 680px; .children-container.hidden { display: none; }
border: 1px solid #e5e7eb;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
}
.table-container {
height: calc(680px - 48px - 52px - 64px);
overflow-y: auto;
}
.sticky-header {
position: sticky;
top: 0;
z-index: 10;
}
.sort-icon {
display: inline-block;
margin-left: 4px;
font-size: 10px;
color: #9ca3af;
}
.sort-active {
color: #2563eb !important;
}
.hidden {
display: none !important;
}
.cat-toggle {
transition: transform 0.2s;
cursor: pointer;
padding: 4px;
}
.cat-toggle.collapsed {
transform: rotate(-90deg);
}
.children-container {
overflow: hidden;
transition: max-height 0.3s ease-out;
}
.children-container.hidden {
display: none;
}
</style> </style>
</head> </head>
<body class="bg-gray-100 font-sans text-sm"> <body class="bg-gray-100 font-sans text-sm">
@ -67,12 +28,7 @@
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" /> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg> </svg>
<input <input id="global-search-input" type="text" placeholder="搜索型号、CID、品牌、值..." class="w-[92%] px-2 py-1 text-sm border-0 focus:ring-0 focus:outline-none placeholder-gray-400" />
id="global-search-input"
type="text"
placeholder="搜索型号、CID、品牌、值..."
class="w-[92%] px-2 py-1 text-sm border-0 focus:ring-0 focus:outline-none placeholder-gray-400"
/>
<button id="search-btn" class="w-[5%] px-3 py-1 bg-blue-600 text-white rounded-md hover:bg-blue-700 text-xs">搜索</button> <button id="search-btn" class="w-[5%] px-3 py-1 bg-blue-600 text-white rounded-md hover:bg-blue-700 text-xs">搜索</button>
</div> </div>
</header> </header>
@ -80,7 +36,8 @@
<div id="main-content" class="flex-grow flex p-3 space-x-3 overflow-hidden"> <div id="main-content" class="flex-grow flex p-3 space-x-3 overflow-hidden">
<aside class="w-60 flex-shrink-0 bg-white border border-gray-200 rounded-lg shadow-md p-3 flex flex-col overflow-hidden"> <aside class="w-60 flex-shrink-0 bg-white border border-gray-200 rounded-lg shadow-md p-3 flex flex-col overflow-hidden">
<h3 class="text-base font-semibold text-gray-800 border-b pb-1 mb-2">筛选类别</h3> <h3 class="text-base font-semibold text-gray-800 border-b pb-1 mb-2">筛选类别</h3>
<div id="category-tree" class="flex-grow overflow-y-auto scrollbar-hide space-y-0.5 text-xs"></div> <div id="category-tree" class="flex-grow overflow-y-auto scrollbar-hide space-y-0.5 text-xs">
</div>
</aside> </aside>
<main class="flex-grow flex flex-col bg-white border border-gray-200 rounded-lg shadow-md overflow-hidden"> <main class="flex-grow flex flex-col bg-white border border-gray-200 rounded-lg shadow-md overflow-hidden">
@ -90,24 +47,16 @@
<tr> <tr>
<th scope="col" class="w-12 px-2 py-2 text-center">ID</th> <th scope="col" class="w-12 px-2 py-2 text-center">ID</th>
<th scope="col" class="w-48 px-3 py-2 text-left">型号</th> <th scope="col" class="w-48 px-3 py-2 text-left">型号</th>
<th scope="col" id="sort-type" class="w-24 px-3 py-2 text-left cursor-pointer hover:bg-gray-200"> <th scope="col" id="sort-type" class="w-24 px-3 py-2 text-left cursor-pointer hover:bg-gray-200">类型 <span class="sort-icon"></span></th>
类型 <span class="sort-icon"></span> <th scope="col" id="sort-value" class="w-20 px-3 py-2 text-left cursor-pointer hover:bg-gray-200"><span class="sort-icon"></span></th>
</th>
<th scope="col" id="sort-value" class="w-20 px-3 py-2 text-left cursor-pointer hover:bg-gray-200">
<span class="sort-icon"></span>
</th>
<th scope="col" class="w-24 px-3 py-2 text-left">封装</th> <th scope="col" class="w-24 px-3 py-2 text-left">封装</th>
<th scope="col" class="w-32 px-3 py-2 text-left">品牌</th> <th scope="col" class="w-32 px-3 py-2 text-left">品牌</th>
<th scope="col" id="sort-quantity" class="w-24 px-3 py-2 text-right cursor-pointer hover:bg-gray-200"> <th scope="col" id="sort-quantity" class="w-24 px-3 py-2 text-right cursor-pointer hover:bg-gray-200">余量 <span class="sort-icon"></span></th>
余量 <span class="sort-icon"></span>
</th>
<th scope="col" class="w-32 px-3 py-2 text-left">CID</th> <th scope="col" class="w-32 px-3 py-2 text-left">CID</th>
</tr> </tr>
</thead> </thead>
<tbody id="data-table-body" class="bg-white divide-y divide-gray-200 text-xs"> <tbody id="data-table-body" class="bg-white divide-y divide-gray-200 text-xs">
<tr> <tr><td colspan="8" class="text-center py-6 text-gray-500">正在初始化器件数据...</td></tr>
<td colspan="8" class="text-center py-6 text-gray-500">正在初始化器件数据...</td>
</tr>
</tbody> </tbody>
</table> </table>
</div> </div>
@ -116,13 +65,9 @@
<div class="flex items-center space-x-4"> <div class="flex items-center space-x-4">
<div class="text-gray-600">展示 <span id="display-range">0-0</span> / 共 <span id="total-items">0</span></div> <div class="text-gray-600">展示 <span id="display-range">0-0</span> / 共 <span id="total-items">0</span></div>
<div class="flex items-center space-x-1"> <div class="flex items-center space-x-1">
<button id="prev-page" class="px-2 py-0.5 border border-gray-300 rounded-md hover:bg-gray-200 disabled:opacity-30"> <button id="prev-page" class="px-2 py-0.5 border border-gray-300 rounded-md hover:bg-gray-200 disabled:opacity-30">上页</button>
上页
</button>
<span class="px-2 font-medium" id="page-num">1</span> <span class="px-2 font-medium" id="page-num">1</span>
<button id="next-page" class="px-2 py-0.5 border border-gray-300 rounded-md hover:bg-gray-200 disabled:opacity-30"> <button id="next-page" class="px-2 py-0.5 border border-gray-300 rounded-md hover:bg-gray-200 disabled:opacity-30">下页</button>
下页
</button>
</div> </div>
</div> </div>
</div> </div>
@ -145,53 +90,33 @@
<div class="grid grid-cols-2 gap-3"> <div class="grid grid-cols-2 gap-3">
<div class="col-span-2"> <div class="col-span-2">
<label class="block text-xs text-gray-500 mb-1">型号 (Manufacturer Part) <span class="text-red-500">*</span></label> <label class="block text-xs text-gray-500 mb-1">型号 (Manufacturer Part) <span class="text-red-500">*</span></label>
<input <input id="edit-name-input" type="text" class="w-full px-2 py-1.5 border border-gray-300 rounded focus:ring-1 focus:ring-blue-500 outline-none text-sm" placeholder="必填" />
id="edit-name-input"
type="text"
class="w-full px-2 py-1.5 border border-gray-300 rounded focus:ring-1 focus:ring-blue-500 outline-none text-sm"
placeholder="必填"
/>
</div> </div>
<div class="col-span-2"> <div class="col-span-2">
<label class="block text-xs text-gray-500 mb-1">LCSC ID (Supplier Part) <span class="text-red-500">*</span></label> <label class="block text-xs text-gray-500 mb-1">LCSC ID (Supplier Part) <span class="text-red-500">*</span></label>
<input <input id="edit-lcsc-input" type="text" class="w-full px-2 py-1.5 border border-gray-300 rounded focus:ring-1 focus:ring-blue-500 outline-none text-sm" placeholder="必填" />
id="edit-lcsc-input"
type="text"
class="w-full px-2 py-1.5 border border-gray-300 rounded focus:ring-1 focus:ring-blue-500 outline-none text-sm"
placeholder="必填"
/>
</div> </div>
</div> </div>
<hr class="border-gray-100" /> <hr class="border-gray-100">
<div> <div>
<label class="block text-xs text-gray-500 mb-1">变更库存</label> <label class="block text-xs text-gray-500 mb-1">变更库存</label>
<div class="flex space-x-2 mb-2"> <div class="flex space-x-2 mb-2">
<button id="op-add" class="flex-1 py-1.5 border border-blue-600 bg-blue-50 text-blue-600 rounded text-xs font-medium"> <button id="op-add" class="flex-1 py-1.5 border border-blue-600 bg-blue-50 text-blue-600 rounded text-xs font-medium">入库 (+)</button>
入库 (+) <button id="op-sub" class="flex-1 py-1.5 border border-gray-300 text-gray-600 rounded text-xs font-medium">出库 (-)</button>
</button>
<button id="op-sub" class="flex-1 py-1.5 border border-gray-300 text-gray-600 rounded text-xs font-medium">
出库 (-)
</button>
</div> </div>
<div class="flex items-center space-x-3"> <div class="flex items-center space-x-3">
<input <input id="edit-qty-input" type="number" min="0" value="0" class="w-24 px-2 py-1.5 border border-gray-300 rounded focus:ring-1 focus:ring-blue-500 outline-none text-sm" />
id="edit-qty-input" <div class="text-[10px] text-gray-400">
type="number" <span id="current-qty-val">0</span><span id="target-qty-val">0</span>
min="0" </div>
value="0"
class="w-24 px-2 py-1.5 border border-gray-300 rounded focus:ring-1 focus:ring-blue-500 outline-none text-sm"
/>
<div class="text-[10px] text-gray-400"><span id="current-qty-val">0</span><span id="target-qty-val">0</span></div>
</div> </div>
</div> </div>
</div> </div>
<div class="flex border-t border-gray-100"> <div class="flex border-t border-gray-100">
<button id="dialog-cancel" class="flex-1 px-4 py-3 text-gray-500 hover:bg-gray-50 text-xs">取消</button> <button id="dialog-cancel" class="flex-1 px-4 py-3 text-gray-500 hover:bg-gray-50 text-xs">取消</button>
<button id="dialog-confirm" class="flex-1 px-4 py-3 bg-blue-600 text-white hover:bg-blue-700 text-xs font-bold"> <button id="dialog-confirm" class="flex-1 px-4 py-3 bg-blue-600 text-white hover:bg-blue-700 text-xs font-bold">提交更改</button>
提交更改
</button>
</div> </div>
</div> </div>
</div> </div>
@ -221,8 +146,8 @@
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 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;
const CACHE_KEY = 'cache-leye-device-details'; const CACHE_KEY = 'cache-leye-device-details';
let allDevicesData = []; let allDevicesData = [];
@ -230,20 +155,13 @@
let selectedRowData = null; let selectedRowData = null;
let sortRules = [ let sortRules = [
{ key: 'type', order: 'desc' }, { key: 'type', order: 'desc' },
{ key: 'value', order: 'desc' }, { key: 'value', order: 'desc' }
]; ];
let currentOp = 'add'; let currentOp = 'add';
let currentPage = 1; let currentPage = 1;
const pageSize = 20; const pageSize = 20;
const unitMap = new Map([ const unitMap = new Map([['M', 1e6], ['k', 1e3], ['m', 1e-3], ['u', 1e-6], ['n', 1e-9], ['p', 1e-12]]);
['M', 1e6],
['k', 1e3],
['m', 1e-3],
['u', 1e-6],
['n', 1e-9],
['p', 1e-12],
]);
function parseValue(val) { function parseValue(val) {
if (!val || val === '-') return -Infinity; if (!val || val === '-') return -Infinity;
@ -262,11 +180,7 @@
try { try {
const cachedRaw = await eda.sys_Storage.getExtensionUserConfig(CACHE_KEY); const cachedRaw = await eda.sys_Storage.getExtensionUserConfig(CACHE_KEY);
let cachedDetails = []; let cachedDetails = [];
try { try { cachedDetails = cachedRaw ? JSON.parse(cachedRaw) : []; } catch(e) { cachedDetails = []; }
cachedDetails = cachedRaw ? JSON.parse(cachedRaw) : [];
} catch (e) {
cachedDetails = [];
}
let listRes = await eda.sys_ClientUrl.request(SERVER + '/getLeyeList?pageSize=1000&current=1'); let listRes = await eda.sys_ClientUrl.request(SERVER + '/getLeyeList?pageSize=1000&current=1');
let listResult = await listRes.json(); let listResult = await listRes.json();
@ -276,18 +190,18 @@
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'); listRes = await eda.sys_ClientUrl.request(SERVER + '/getLeyeList?pageSize=1000&current=1');
listResult = await listRes.json(); listResult = await listRes.json();
await new Promise((resolve) => setTimeout(resolve, 1500)); await new Promise(resolve => setTimeout(resolve, 1500));
} }
} }
if (!listResult.success || !listResult.data) { if (!listResult.success || !listResult.data) {
throw new Error('同步失败'); throw new Error('同步失败');
} }
const validItems = listResult.data.filter((item) => item.lcscId); const validItems = listResult.data.filter(item => item.lcscId);
const currentLcscIds = validItems.map((item) => String(item.lcscId).toUpperCase()); const currentLcscIds = validItems.map(item => String(item.lcscId).toUpperCase());
const cachedLcscIds = cachedDetails.map((d) => String(d.lcscId).toUpperCase()); const cachedLcscIds = cachedDetails.map(d => String(d.lcscId).toUpperCase());
const newLcscIds = currentLcscIds.filter((id) => !cachedLcscIds.includes(id)); const newLcscIds = currentLcscIds.filter(id => !cachedLcscIds.includes(id));
let updatedDetails = cachedDetails.filter((d) => currentLcscIds.includes(String(d.lcscId).toUpperCase())); let updatedDetails = cachedDetails.filter(d => currentLcscIds.includes(String(d.lcscId).toUpperCase()));
if (newLcscIds.length > 0) { if (newLcscIds.length > 0) {
const devices = await eda.lib_Device.getByLcscIds(newLcscIds); const devices = await eda.lib_Device.getByLcscIds(newLcscIds);
@ -295,16 +209,10 @@
for (let i = 0; i < devices.length; i += chunkSize) { for (let i = 0; i < devices.length; i += chunkSize) {
const chunk = devices.slice(i, i + chunkSize); const chunk = devices.slice(i, i + chunkSize);
tableBody.innerHTML = `<tr><td colspan="8" class="text-center py-6 text-gray-500">发现 ${newLcscIds.length} 个新器件,正在更新 (${Math.min(i+chunkSize, devices.length)}/${devices.length})...</td></tr>`; tableBody.innerHTML = `<tr><td colspan="8" class="text-center py-6 text-gray-500">发现 ${newLcscIds.length} 个新器件,正在更新 (${Math.min(i+chunkSize, devices.length)}/${devices.length})...</td></tr>`;
const chunkData = await Promise.all( const chunkData = await Promise.all(chunk.map(async (dev) => {
chunk.map(async (dev) => {
try { try {
const EDA_HOST = const EDA_HOST = eda.sys_Environment.isClient() ? "https://client" : "https://pro.lceda.cn";
eda.sys_Environment.isClient() && !eda.sys_Environment.isOnlineMode() const infoRes = await eda.sys_ClientUrl.request(EDA_HOST + '/api/v2/devices/' + dev.uuid, 'GET', null, { headers: { path: '0819f05c4eef4c71ace90d822a990e87' } });
? '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 infoJson = await infoRes.json();
const info = infoJson.result; const info = infoJson.result;
return { return {
@ -317,18 +225,15 @@
brand: info.attributes['Manufacturer'] || '-', brand: info.attributes['Manufacturer'] || '-',
lcscId: String(info.attributes['Supplier Part']).toUpperCase(), lcscId: String(info.attributes['Supplier Part']).toUpperCase(),
}; };
} catch (e) { } catch (e) { return null; }
return null; }));
} updatedDetails.push(...chunkData.filter(d => d !== null));
}),
);
updatedDetails.push(...chunkData.filter((d) => d !== null));
} }
await eda.sys_Storage.setExtensionUserConfig(CACHE_KEY, JSON.stringify(updatedDetails)); await eda.sys_Storage.setExtensionUserConfig(CACHE_KEY, JSON.stringify(updatedDetails));
} }
allDevicesData = updatedDetails.map((detail) => { allDevicesData = updatedDetails.map(detail => {
const invItem = validItems.find((vi) => String(vi.lcscId).toUpperCase() === detail.lcscId); const invItem = validItems.find(vi => String(vi.lcscId).toUpperCase() === detail.lcscId);
return { ...detail, id: invItem ? invItem.id : '?', quantity: invItem ? invItem.quantity : 0 }; return { ...detail, id: invItem ? invItem.id : '?', quantity: invItem ? invItem.quantity : 0 };
}); });
@ -343,7 +248,7 @@
function generateCategoryTree() { function generateCategoryTree() {
const tree = {}; const tree = {};
allDevicesData.forEach((d) => { allDevicesData.forEach(d => {
if (!tree[d.parentCat]) tree[d.parentCat] = new Set(); if (!tree[d.parentCat]) tree[d.parentCat] = new Set();
tree[d.parentCat].add(d.childCat); tree[d.parentCat].add(d.childCat);
}); });
@ -355,9 +260,7 @@
<label for="cat-all" class="font-bold text-gray-900 cursor-pointer">全部</label> <label for="cat-all" class="font-bold text-gray-900 cursor-pointer">全部</label>
</div>`; </div>`;
Object.keys(tree) Object.keys(tree).sort().forEach(parent => {
.sort()
.forEach((parent) => {
const parentId = btoa(encodeURIComponent(parent)).replace(/=/g, ''); const parentId = btoa(encodeURIComponent(parent)).replace(/=/g, '');
html += ` html += `
<div class="parent-group"> <div class="parent-group">
@ -368,9 +271,7 @@
</div> </div>
<div id="children-${parentId}" class="children-container ml-4 border-l border-gray-100 pl-2 hidden">`; <div id="children-${parentId}" class="children-container ml-4 border-l border-gray-100 pl-2 hidden">`;
Array.from(tree[parent]) Array.from(tree[parent]).sort().forEach(child => {
.sort()
.forEach((child) => {
const childId = btoa(encodeURIComponent(child)).replace(/=/g, ''); const childId = btoa(encodeURIComponent(child)).replace(/=/g, '');
html += ` html += `
<div class="cursor-pointer hover:bg-blue-50 rounded p-1 flex items-center" data-cat="${child}"> <div class="cursor-pointer hover:bg-blue-50 rounded p-1 flex items-center" data-cat="${child}">
@ -383,7 +284,7 @@
}); });
categoryTree.innerHTML = html; categoryTree.innerHTML = html;
categoryTree.querySelectorAll('.cat-toggle').forEach((btn) => { categoryTree.querySelectorAll('.cat-toggle').forEach(btn => {
btn.onclick = (e) => { btn.onclick = (e) => {
e.stopPropagation(); e.stopPropagation();
const targetId = btn.dataset.toggle; const targetId = btn.dataset.toggle;
@ -400,9 +301,8 @@
const selectedRadio = document.querySelector('input[name="category"]:checked'); const selectedRadio = document.querySelector('input[name="category"]:checked');
const catValue = selectedRadio ? selectedRadio.value : 'all'; const catValue = selectedRadio ? selectedRadio.value : 'all';
filteredData = allDevicesData.filter((d) => { filteredData = allDevicesData.filter(d => {
const matchesSearch = const matchesSearch = !keyword ||
!keyword ||
(d.name && d.name.toLowerCase().includes(keyword)) || (d.name && d.name.toLowerCase().includes(keyword)) ||
(d.lcscId && d.lcscId.toLowerCase().includes(keyword)) || (d.lcscId && d.lcscId.toLowerCase().includes(keyword)) ||
(d.brand && d.brand.toLowerCase().includes(keyword)) || (d.brand && d.brand.toLowerCase().includes(keyword)) ||
@ -419,16 +319,9 @@
data.sort((a, b) => { data.sort((a, b) => {
for (const rule of sortRules) { for (const rule of sortRules) {
let valA, valB; let valA, valB;
if (rule.key === 'type') { if (rule.key === 'type') { valA = a.childCat; valB = b.childCat; }
valA = a.childCat; else if (rule.key === 'value') { valA = parseValue(a.value); valB = parseValue(b.value); }
valB = b.childCat; else if (rule.key === 'quantity') { valA = a.quantity; valB = b.quantity; }
} else if (rule.key === 'value') {
valA = parseValue(a.value);
valB = parseValue(b.value);
} else if (rule.key === 'quantity') {
valA = a.quantity;
valB = b.quantity;
}
if (valA === valB) continue; if (valA === valB) continue;
if (typeof valA === 'number' && typeof valB === 'number') { if (typeof valA === 'number' && typeof valB === 'number') {
@ -460,9 +353,7 @@
document.getElementById('prev-page').disabled = currentPage === 1; document.getElementById('prev-page').disabled = currentPage === 1;
document.getElementById('next-page').disabled = end >= total; document.getElementById('next-page').disabled = end >= total;
tableBody.innerHTML = pageData tableBody.innerHTML = pageData.map(item => `
.map(
(item) => `
<tr class="hover:bg-blue-50 cursor-pointer ${selectedRowData && selectedRowData.lcscId === item.lcscId ? 'bg-blue-100' : ''}" data-row='${JSON.stringify(item)}'> <tr class="hover:bg-blue-50 cursor-pointer ${selectedRowData && selectedRowData.lcscId === item.lcscId ? 'bg-blue-100' : ''}" data-row='${JSON.stringify(item)}'>
<td class="px-2 py-1.5 text-center text-gray-500">${item.id}</td> <td class="px-2 py-1.5 text-center text-gray-500">${item.id}</td>
<td class="px-3 py-1.5 font-medium text-blue-600">${item.name}</td> <td class="px-3 py-1.5 font-medium text-blue-600">${item.name}</td>
@ -473,23 +364,21 @@
<td class="px-3 py-1.5 text-right ${item.quantity > 0 ? 'text-green-600' : 'text-red-500'}">${item.quantity}</td> <td class="px-3 py-1.5 text-right ${item.quantity > 0 ? 'text-green-600' : 'text-red-500'}">${item.quantity}</td>
<td class="px-3 py-1.5 text-left text-gray-500">${item.lcscId}</td> <td class="px-3 py-1.5 text-left text-gray-500">${item.lcscId}</td>
</tr> </tr>
`, `).join('');
)
.join('');
} }
function selectRow(row) { function selectRow(row) {
tableBody.querySelectorAll('tr').forEach((r) => r.classList.remove('bg-blue-100')); tableBody.querySelectorAll('tr').forEach(r => r.classList.remove('bg-blue-100'));
row.classList.add('bg-blue-100'); row.classList.add('bg-blue-100');
selectedRowData = JSON.parse(row.dataset.row); selectedRowData = JSON.parse(row.dataset.row);
selectedRowsInfo.textContent = '已选择 1 行'; selectedRowsInfo.textContent = '已选择 1 行';
} }
function updateSortIcons() { function updateSortIcons() {
['type', 'value', 'quantity'].forEach((key) => { ['type', 'value', 'quantity'].forEach(key => {
const th = document.getElementById(`sort-${key}`); const th = document.getElementById(`sort-${key}`);
const icon = th.querySelector('.sort-icon'); const icon = th.querySelector('.sort-icon');
const ruleIndex = sortRules.findIndex((r) => r.key === key); const ruleIndex = sortRules.findIndex(r => r.key === key);
const rule = sortRules[ruleIndex]; const rule = sortRules[ruleIndex];
if (rule) { if (rule) {
icon.textContent = (rule.order === 'asc' ? '↑' : '↓') + (sortRules.length > 1 ? `(${ruleIndex + 1})` : ''); icon.textContent = (rule.order === 'asc' ? '↑' : '↓') + (sortRules.length > 1 ? `(${ruleIndex + 1})` : '');
@ -505,18 +394,8 @@
searchButton.onclick = handleLocalSearch; searchButton.onclick = handleLocalSearch;
searchInput.onkeyup = (e) => e.key === 'Enter' && handleLocalSearch(); searchInput.onkeyup = (e) => e.key === 'Enter' && handleLocalSearch();
document.getElementById('prev-page').onclick = () => { document.getElementById('prev-page').onclick = () => { if (currentPage > 1) { currentPage--; applySortAndRender(); } };
if (currentPage > 1) { document.getElementById('next-page').onclick = () => { if (currentPage * pageSize < filteredData.length) { currentPage++; applySortAndRender(); } };
currentPage--;
applySortAndRender();
}
};
document.getElementById('next-page').onclick = () => {
if (currentPage * pageSize < filteredData.length) {
currentPage++;
applySortAndRender();
}
};
categoryTree.onclick = (e) => { categoryTree.onclick = (e) => {
const div = e.target.closest('div[data-cat]'); const div = e.target.closest('div[data-cat]');
@ -527,7 +406,7 @@
}; };
const onSortClick = (key) => { const onSortClick = (key) => {
const idx = sortRules.findIndex((r) => r.key === key); const idx = sortRules.findIndex(r => r.key === key);
if (idx > -1) { if (idx > -1) {
if (sortRules[idx].order === 'asc') sortRules[idx].order = 'desc'; if (sortRules[idx].order === 'asc') sortRules[idx].order = 'desc';
else sortRules.splice(idx, 1); else sortRules.splice(idx, 1);
@ -562,9 +441,7 @@
uuid: selectedRowData.uuid, uuid: selectedRowData.uuid,
libraryUuid: '0819f05c4eef4c71ace90d822a990e87', libraryUuid: '0819f05c4eef4c71ace90d822a990e87',
}); });
} catch (e) { } catch (e) { console.error(e); }
console.error(e);
}
}; };
editBtn.onclick = () => { editBtn.onclick = () => {
@ -628,30 +505,27 @@
return; return;
} }
const newQuantity = currentOp === 'add' ? selectedRowData.quantity + changeQty : selectedRowData.quantity - changeQty; const newQuantity = currentOp === 'add' ?
selectedRowData.quantity + changeQty :
selectedRowData.quantity - changeQty;
dialogConfirm.disabled = true; dialogConfirm.disabled = true;
dialogConfirm.textContent = '正在保存...'; dialogConfirm.textContent = '正在保存...';
try { try {
const res = await eda.sys_ClientUrl.request( const res = await eda.sys_ClientUrl.request(SERVER + '/editLeyeList', 'POST', JSON.stringify({
SERVER + '/editLeyeList',
'POST',
JSON.stringify({
id: selectedRowData.id, id: selectedRowData.id,
name: newName, name: newName,
lcscId: newLcscId, lcscId: newLcscId,
quantity: newQuantity, quantity: newQuantity
}), }), { headers: { 'Content-Type': 'application/json' } });
{ headers: { 'Content-Type': 'application/json' } },
);
const result = await res.json(); const result = await res.json();
if (result.success) { if (result.success) {
eda.sys_Message.showToastMessage('更新成功', ESYS_ToastMessageType.SUCCESS); eda.sys_Message.showToastMessage('更新成功', ESYS_ToastMessageType.SUCCESS);
const deviceIndex = allDevicesData.findIndex((d) => d.id === selectedRowData.id); const deviceIndex = allDevicesData.findIndex(d => d.id === selectedRowData.id);
if (deviceIndex !== -1) { if (deviceIndex !== -1) {
allDevicesData[deviceIndex].name = newName; allDevicesData[deviceIndex].name = newName;
allDevicesData[deviceIndex].lcscId = newLcscId; allDevicesData[deviceIndex].lcscId = newLcscId;

View File

@ -109,27 +109,16 @@
</label> </label>
<input type="checkbox" id="settings-server-auto-run" /> <input type="checkbox" id="settings-server-auto-run" />
</div> </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>
<div class="bg-white p-4 rounded-lg w-[400px]"> <div class="bg-white p-4 rounded-lg w-[400px]">
<h2 class="text-lg font-semibold text-gray-800 mb-4"> <h2 class="text-lg font-semibold text-gray-800 mb-4">
高级设置 高级设置
<span class="text-[12px] text-gray-500 font-light ml-2"> 实验或调试选项 </span> <span class="text-[12px] text-gray-500 font-light ml-2"> 实验或调试选项 </span>
</h2> </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"> <div class="flex items-center justify-between mb-4">
<label for="settings-clean-cache" class="text-gray-700"> <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> </label>
<button id="settings-clean-cache" class="bg-red-600 text-white px-4 py-1 rounded hover:bg-red-700">清除</button> <button id="settings-clean-cache" class="bg-red-600 text-white px-4 py-1 rounded hover:bg-red-700">清除</button>
</div> </div>
@ -139,14 +128,10 @@
document.addEventListener('DOMContentLoaded', async () => { document.addEventListener('DOMContentLoaded', async () => {
const serverHostInput = document.getElementById('settings-server-host'); const serverHostInput = document.getElementById('settings-server-host');
const serverAutoRun = document.getElementById('settings-server-auto-run'); 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'); const cleanCacheBtn = document.getElementById('settings-clean-cache');
serverHostInput.value = (await eda.sys_Storage.getExtensionUserConfig('server-host')) ?? 'http://localhost:21816/api'; serverHostInput.value = (await eda.sys_Storage.getExtensionUserConfig('server-host')) ?? 'http://localhost:21816/api';
serverAutoRun.checked = (await eda.sys_Storage.getExtensionUserConfig('server-auto-run')) ?? true; 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 () => { serverHostInput.addEventListener('change', async () => {
saveConfig('server-host', serverHostInput.value); saveConfig('server-host', serverHostInput.value);
@ -154,22 +139,16 @@
serverAutoRun.addEventListener('change', async () => { serverAutoRun.addEventListener('change', async () => {
saveConfig('server-auto-run', serverAutoRun.checked); 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 () => { 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) { if (s) {
eda.sys_Message.showToastMessage('缓存清除成功!', ESYS_ToastMessageType.SUCCESS); eda.sys_Message.showToastMessage('缓存清除成功!', ESYS_ToastMessageType.SUCCESS);
} else { } else {
eda.sys_Message.showToastMessage('缓存清除失败...', ESYS_ToastMessageType.ERROR); eda.sys_Message.showToastMessage('缓存清除失败...', ESYS_ToastMessageType.ERROR);
} }
}); })
}); })
}); });
function saveConfig(key, value) { function saveConfig(key, value) {

View File

@ -12,7 +12,8 @@
"eslint:all": "eslint --ext .ts --fix .", "eslint:all": "eslint --ext .ts --fix .",
"fix": "npm run prettier:all && npm run eslint:all", "fix": "npm run prettier:all && npm run eslint:all",
"tailwind": "tailwindcss -i ./iframe/css/input.css -o ./iframe/css/index.css", "tailwind": "tailwindcss -i ./iframe/css/input.css -o ./iframe/css/index.css",
"build": "npm run tailwind && npm run compile && ts-node ./build/packaged.ts" "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"
}, },
"devDependencies": { "devDependencies": {
"@jlceda/pro-api-types": "^0.1.175", "@jlceda/pro-api-types": "^0.1.175",
@ -35,7 +36,8 @@
"rimraf": "^6.0.1", "rimraf": "^6.0.1",
"tailwindcss": "^3.4.17", "tailwindcss": "^3.4.17",
"ts-node": "^10.9.2", "ts-node": "^10.9.2",
"typescript": "^5.7.3" "typescript": "^5.7.3",
"javascript-obfuscator": "^4.1.1"
}, },
"lint-staged": { "lint-staged": {
"*.ts": "eslint --cache --fix", "*.ts": "eslint --cache --fix",

View File

@ -45,56 +45,38 @@ if (!globalThis['__LEYE_INIT_FLAG__']) {
// @ts-ignore // @ts-ignore
globalThis['__LEYE_INIT_FLAG__'] = true; globalThis['__LEYE_INIT_FLAG__'] = true;
// @ts-ignore // @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 // @ts-ignore
eda.sys_ShortcutKey eda.sys_ShortcutKey.registerShortcutKey(['Shift+L'], 'openLeyeIFrame', async () => {
.registerShortcutKey(['Shift+L'], 'openLeyeIFrame', async () => {
await openLeyeIFrameNew(); await openLeyeIFrameNew();
}) }).then(r => console.log('[LEYE] 注册快捷键: ', r));
.then((r) => console.log('[LEYE] 注册快捷键: ', r));
// 获取公告 // 获取公告
console.log('[LEYE] 获取公告和更新'); console.log('[LEYE] 获取公告和更新');
eda.sys_ClientUrl eda.sys_ClientUrl.request('https://leye.dragon.edu.kg/release/notice.json').then(async (res: any) => {
.request('https://leye.dragon.edu.kg/release/notice.json')
.then(async (res: any) => {
const data = await res.json(); const data = await res.json();
console.log('[LEYE] 获取公告: ', data); console.log('[LEYE] 获取公告: ', data);
if (eda.sys_Storage.getExtensionUserConfig('cache-notice-id') !== data.notices[0].id) { if (eda.sys_Storage.getExtensionUserConfig('cache-notice-id') !== data.notices[0].id) {
await eda.sys_Storage.setExtensionUserConfig('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, '知道了'); eda.sys_Dialog.showInformationMessage(data.notices[0].content, data.notices[0].title, '知道了');
} }
}) }).catch((err: any) => {
.catch((err: any) => {
console.error('[LEYE] 获取公告和更新失败: ', err); console.error('[LEYE] 获取公告和更新失败: ', err);
}); });
// 获取最新版本 // 获取最新版本
console.log('[LEYE] 获取最新版本'); 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) => {
eda.sys_ClientUrl
.request('https://leye.dragon.edu.kg/release/eext.ver.json')
.then(async (res: any) => {
const data = await res.json(); const data = await res.json();
console.log('[LEYE] 获取最新版本: ', data); console.log('[LEYE] 获取最新版本: ', data);
if (extensionConfig.version !== data.versions[0].ver) { if (extensionConfig.version !== data.versions[0].ver) {
eda.sys_Dialog.showConfirmationMessage( eda.sys_Dialog.showConfirmationMessage(data.versions[0].changelog, `LEYE 有新版本 ${data.versions[0].ver} 可用,是否前往下载?`, '前往下载', '算了', async (r) => {
data.versions[0].changelog,
`LEYE 有新版本 ${data.versions[0].ver} 可用,是否前往下载?`,
'前往下载',
'算了',
async (r) => {
if (r) { if (r) {
eda.sys_Window.open('https://lrurl.top/LeyeEEXT', ESYS_WindowOpenTarget.BLANK); eda.sys_Window.open('https://lrurl.top/LeyeEEXT', ESYS_WindowOpenTarget.BLANK);
} }
},
);
}
}) })
.catch((err: any) => { }
console.error('[LEYE] 获取最新版本失败: ', err); }).catch((err: any) => {
console.error('[LEYE] 获取最新版本: ', err);
}); });
} else {
console.log('[LEYE] 用户主动关闭自动检查更新');
}
} }