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

|

|
||||||
|
|
||||||
> **Tip: 扫描立创商城二维码导入器件**
|
> **Tip: 扫描立创商城二维码导入器件** _V1.0.5+_
|
||||||
>
|
>
|
||||||
> 在 V1.0.5 版本以上,可以通过扫描立创商城物料包装上的二维码导入器件,扫描后会自动识别 CID 并导入对应器件。
|
> 在 V1.0.5 版本以上,可以通过扫描立创商城物料包装上的二维码导入器件,扫描后会自动识别 CID 并导入对应器件。
|
||||||
> 
|
> 
|
||||||
|
|
||||||
|
> **Tip: 使用手机扫码导入器件** _V1.0.6+_
|
||||||
|
>
|
||||||
|
> 在 V1.0.6 版本以上,可以在扫码界面选择“WebSocket 远程扫码”,点击“打开摄像头”按钮后用手机扫描二维码即可打开远程端。
|
||||||
|
> 远程端同样支持扫描立创商城物料包装上的二维码导入器件,扫描后会自动识别 CID 并导入对应器件。
|
||||||
|
> 
|
||||||
|
|
||||||
### 批量出库
|
### 批量出库
|
||||||
|
|
||||||
打开本页面后将自动整理 BOM 与 LEYE 库存相比较,非立创商城器件(无 CID)显示为红底,不在 LEYE 库存内的器件显示为黄底,在 LEYE 库存内的器件显示为绿底。
|
打开本页面后将自动整理 BOM 与 LEYE 库存相比较,非立创商城器件(无 CID)显示为红底,不在 LEYE 库存内的器件显示为黄底,在 LEYE 库存内的器件显示为绿底。
|
||||||
@ -57,7 +65,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` |
|
||||||
|
|
||||||
@ -68,8 +76,11 @@ V2.2 用户菜单栏将直接出现“LEYE”选项;V3 用户若未开启“
|
|||||||
## 开源许可
|
## 开源许可
|
||||||
|
|
||||||
本扩展使用以下开源软件:
|
本扩展使用以下开源软件:
|
||||||
- [jsQR](https://www.jsdelivr.com/package/npm/jsqr):二维码解析库,使用 [Apache License 2.0](https://choosealicense.com/licenses/apache-2.0/) 许可协议授权
|
|
||||||
- [xlsx](https://www.jsdelivr.com/package/npm/xlsx):Excel 解析库,使用 [Apache License 2.0](https://choosealicense.com/licenses/apache-2.0/) 许可协议授权
|
- [SheetJS](https://www.npmjs.com/package/xlsx):Excel 解析库,使用 [Apache License 2.0](https://choosealicense.com/licenses/apache-2.0/) 许可协议授权
|
||||||
- [Tailwind CSS](https://www.jsdelivr.com/package/npm/tailwindcss):CSS 框架,使用 [MIT License](https://choosealicense.com/licenses/mit/) 许可协议授权
|
- [jsQR](https://www.npmjs.com/package/jsqr):二维码解析库,使用 [Apache License 2.0](https://choosealicense.com/licenses/apache-2.0/) 许可协议授权
|
||||||
|
- [node-qrcode](https://www.npmjs.com/package/qrcode):二维码生成库,使用 [MIT License](https://choosealicense.com/licenses/mit/) 许可协议授权
|
||||||
|
- [MQTT.js](https://www.npmjs.com/package/mqtt):MQTT 客户端库,使用 [MIT License](https://choosealicense.com/licenses/mit/) 许可协议授权
|
||||||
|
- [Tailwind CSS](https://www.npmjs.com/package/tailwindcss):CSS 框架,使用 [MIT License](https://choosealicense.com/licenses/mit/) 许可协议授权
|
||||||
|
|
||||||
本扩展使用 [Apache License 2.0](https://choosealicense.com/licenses/apache-2.0/) 开源许可协议,商业/教育用途请考虑联系开发者获取常规版。
|
本扩展使用 [Apache License 2.0](https://choosealicense.com/licenses/apache-2.0/) 开源许可协议,商业/教育用途请考虑联系开发者获取常规版。
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
"uuid": "944f7c94a8ca485e848f1118effcbb9a",
|
"uuid": "944f7c94a8ca485e848f1118effcbb9a",
|
||||||
"displayName": "LEYE",
|
"displayName": "LEYE",
|
||||||
"description": "LEYE 电子元器件库存管理系统 EDA 联动扩展",
|
"description": "LEYE 电子元器件库存管理系统 EDA 联动扩展",
|
||||||
"version": "1.0.5",
|
"version": "1.0.9",
|
||||||
"publisher": "Mr_Fang",
|
"publisher": "Mr_Fang",
|
||||||
"engines": {
|
"engines": {
|
||||||
"eda": "^3.2.80"
|
"eda": "^3.2.80"
|
||||||
@ -14,9 +14,7 @@
|
|||||||
"url": "https://gitea.miri.site/Mr_Fang/eext-leye"
|
"url": "https://gitea.miri.site/Mr_Fang/eext-leye"
|
||||||
},
|
},
|
||||||
"categories": "Schematic",
|
"categories": "Schematic",
|
||||||
"keywords": [
|
"keywords": ["Tools", "库管", "库存管理"],
|
||||||
"Tools", "库管", "库存管理"
|
|
||||||
],
|
|
||||||
"images": {
|
"images": {
|
||||||
"logo": "./images/logo.png"
|
"logo": "./images/logo.png"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,4 +1,6 @@
|
|||||||
*, ::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;
|
||||||
@ -154,7 +156,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 */
|
||||||
@ -243,7 +245,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 */
|
||||||
@ -486,7 +488,8 @@ 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, textarea::-moz-placeholder {
|
input::-moz-placeholder,
|
||||||
|
textarea::-moz-placeholder {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
/* 1 */
|
/* 1 */
|
||||||
color: #9ca3af;
|
color: #9ca3af;
|
||||||
@ -506,7 +509,7 @@ Set the default cursor for buttons.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
button,
|
button,
|
||||||
[role="button"] {
|
[role='button'] {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -550,7 +553,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -596,6 +599,10 @@ video {
|
|||||||
visibility: collapse;
|
visibility: collapse;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.static {
|
||||||
|
position: static;
|
||||||
|
}
|
||||||
|
|
||||||
.fixed {
|
.fixed {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
}
|
}
|
||||||
@ -628,10 +635,34 @@ 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;
|
||||||
}
|
}
|
||||||
@ -640,10 +671,18 @@ video {
|
|||||||
margin: 0.25rem;
|
margin: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.m-2 {
|
||||||
|
margin: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
.m-3 {
|
.m-3 {
|
||||||
margin: 0.75rem;
|
margin: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.m-auto {
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.mx-auto {
|
.mx-auto {
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
@ -657,6 +696,10 @@ 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;
|
||||||
}
|
}
|
||||||
@ -770,6 +813,10 @@ video {
|
|||||||
height: 1rem;
|
height: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.h-5 {
|
||||||
|
height: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
.h-\[200px\] {
|
.h-\[200px\] {
|
||||||
height: 200px;
|
height: 200px;
|
||||||
}
|
}
|
||||||
@ -782,6 +829,18 @@ 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;
|
||||||
}
|
}
|
||||||
@ -822,6 +881,10 @@ video {
|
|||||||
width: 12rem;
|
width: 12rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.w-5 {
|
||||||
|
width: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
.w-56 {
|
.w-56 {
|
||||||
width: 14rem;
|
width: 14rem;
|
||||||
}
|
}
|
||||||
@ -862,10 +925,23 @@ 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%;
|
||||||
}
|
}
|
||||||
@ -874,10 +950,18 @@ 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;
|
||||||
}
|
}
|
||||||
@ -895,7 +979,8 @@ 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)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-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));
|
||||||
}
|
}
|
||||||
|
|
||||||
.cursor-pointer {
|
.cursor-pointer {
|
||||||
@ -912,6 +997,10 @@ 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));
|
||||||
}
|
}
|
||||||
@ -920,6 +1009,10 @@ 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;
|
||||||
}
|
}
|
||||||
@ -928,6 +1021,10 @@ video {
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.items-start {
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
.items-center {
|
.items-center {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
@ -944,6 +1041,10 @@ video {
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.gap-1 {
|
||||||
|
gap: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
.gap-2 {
|
.gap-2 {
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
}
|
}
|
||||||
@ -952,6 +1053,10 @@ 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));
|
||||||
@ -976,6 +1081,12 @@ 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)));
|
||||||
@ -1012,17 +1123,32 @@ 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;
|
||||||
}
|
}
|
||||||
@ -1041,6 +1167,10 @@ video {
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.whitespace-nowrap {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
.rounded {
|
.rounded {
|
||||||
border-radius: 0.25rem;
|
border-radius: 0.25rem;
|
||||||
}
|
}
|
||||||
@ -1053,6 +1183,10 @@ video {
|
|||||||
border-radius: 0.375rem;
|
border-radius: 0.375rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.rounded-xl {
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
.border {
|
.border {
|
||||||
border-width: 1px;
|
border-width: 1px;
|
||||||
}
|
}
|
||||||
@ -1069,10 +1203,18 @@ 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;
|
||||||
}
|
}
|
||||||
@ -1086,6 +1228,16 @@ video {
|
|||||||
border-color: rgb(219 234 254 / var(--tw-border-opacity, 1));
|
border-color: rgb(219 234 254 / var(--tw-border-opacity, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.border-blue-300 {
|
||||||
|
--tw-border-opacity: 1;
|
||||||
|
border-color: rgb(147 197 253 / var(--tw-border-opacity, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
.border-blue-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));
|
||||||
@ -1146,6 +1298,11 @@ 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));
|
||||||
@ -1171,6 +1328,11 @@ 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;
|
||||||
}
|
}
|
||||||
@ -1205,6 +1367,10 @@ video {
|
|||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.p-5 {
|
||||||
|
padding: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
.p-6 {
|
.p-6 {
|
||||||
padding: 1.5rem;
|
padding: 1.5rem;
|
||||||
}
|
}
|
||||||
@ -1229,6 +1395,11 @@ 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;
|
||||||
@ -1259,6 +1430,11 @@ 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;
|
||||||
@ -1274,15 +1450,29 @@ 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;
|
||||||
}
|
}
|
||||||
@ -1312,11 +1502,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 {
|
||||||
@ -1385,6 +1575,16 @@ 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));
|
||||||
@ -1464,8 +1664,14 @@ 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);
|
||||||
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);
|
||||||
}
|
}
|
||||||
@ -1513,24 +1719,89 @@ 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) var(--tw-sepia) var(--tw-drop-shadow);
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
.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) var(--tw-sepia) var(--tw-drop-shadow);
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
.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) var(--tw-sepia) var(--tw-drop-shadow);
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
.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: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter;
|
transition-property:
|
||||||
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter;
|
color,
|
||||||
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter;
|
background-color,
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,26 +1,59 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="zh">
|
<html lang="zh">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<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 { width: 1000px; height: 600px; border: 1px solid #e5e7eb; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); }
|
#fixed-window {
|
||||||
.table-container { height: calc(600px - 80px - 44px - 64px); overflow-y: auto; }
|
width: 1000px;
|
||||||
.bg-match { background-color: #f0fdf4; } /* 淡绿色:匹配成功 */
|
height: 600px;
|
||||||
.bg-no-match { background-color: #fefcf2; } /* 淡黄色:不匹配 */
|
border: 1px solid #e5e7eb;
|
||||||
.bg-no-cid { background-color: #fef2f2; } /* 淡红色:无 CID */
|
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
||||||
.sticky-header { position: sticky; top: 0; z-index: 10; }
|
}
|
||||||
.designator-cell { max-width: 120px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
.table-container {
|
||||||
|
height: calc(600px - 80px - 44px - 64px);
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
.bg-match {
|
||||||
|
background-color: #f0fdf4;
|
||||||
|
} /* 淡绿色:匹配成功 */
|
||||||
|
.bg-no-match {
|
||||||
|
background-color: #fefcf2;
|
||||||
|
} /* 淡黄色:不匹配 */
|
||||||
|
.bg-no-cid {
|
||||||
|
background-color: #fef2f2;
|
||||||
|
} /* 淡红色:无 CID */
|
||||||
|
.sticky-header {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
.designator-cell {
|
||||||
|
max-width: 120px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
</style>
|
</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 id="input-cid" type="text" placeholder="CID" class="w-48 px-3 py-1.5 border rounded-md outline-none focus:ring-2 focus:ring-blue-500" />
|
<input
|
||||||
<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" />
|
id="input-cid"
|
||||||
|
type="text"
|
||||||
|
placeholder="CID"
|
||||||
|
class="w-48 px-3 py-1.5 border rounded-md outline-none focus:ring-2 focus:ring-blue-500"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
id="input-qty"
|
||||||
|
type="number"
|
||||||
|
placeholder="数量"
|
||||||
|
class="w-24 px-3 py-1.5 border rounded-md outline-none focus:ring-2 focus:ring-blue-500"
|
||||||
|
/>
|
||||||
<button id="add-manual-btn" class="px-4 py-1.5 bg-blue-600 text-white rounded-md hover:bg-blue-700">添加</button>
|
<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>
|
||||||
|
|
||||||
@ -29,9 +62,7 @@
|
|||||||
<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">
|
<div class="text-gray-500 text-xs">待出库总数:<span id="list-count" class="font-bold text-blue-600">0</span></div>
|
||||||
待出库总数:<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">
|
||||||
@ -51,7 +82,9 @@
|
|||||||
</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><td colspan="9" class="text-center py-20 text-gray-400">获取 BOM...</td></tr>
|
<tr>
|
||||||
|
<td colspan="9" class="text-center py-20 text-gray-400">获取 BOM...</td>
|
||||||
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
@ -60,12 +93,13 @@
|
|||||||
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', async function () {
|
document.addEventListener('DOMContentLoaded', async function () {
|
||||||
const tableBody = document.getElementById('outbound-table-body');
|
const tableBody = document.getElementById('outbound-table-body');
|
||||||
const listCount = document.getElementById('list-count');
|
const listCount = document.getElementById('list-count');
|
||||||
@ -76,12 +110,14 @@
|
|||||||
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) { cachedCIDs = new Set(); }
|
} catch (e) {
|
||||||
|
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] || '-';
|
||||||
}
|
}
|
||||||
@ -96,35 +132,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();
|
||||||
@ -138,7 +174,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
listCount.textContent = outboundList.length;
|
listCount.textContent = outboundList.length;
|
||||||
tableBody.innerHTML = outboundList.map((item, index) => {
|
tableBody.innerHTML = outboundList
|
||||||
|
.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) {
|
||||||
@ -167,7 +204,8 @@
|
|||||||
<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) => {
|
||||||
@ -187,7 +225,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;
|
||||||
}
|
}
|
||||||
@ -196,7 +234,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;
|
||||||
}
|
}
|
||||||
@ -229,7 +267,11 @@
|
|||||||
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 infoRes = await eda.sys_ClientUrl.request('https://client/api/v2/devices/' + devs[0].uuid, 'GET', null, { headers: { path: '0819f05c4eef4c71ace90d822a990e87' } });
|
const EDA_HOST =
|
||||||
|
eda.sys_Environment.isClient() && !eda.sys_Environment.isOnlineMode() ? 'https://client' : 'https://pro.lceda.cn';
|
||||||
|
const infoRes = await eda.sys_ClientUrl.request(EDA_HOST + '/api/v2/devices/' + devs[0].uuid, 'GET', null, {
|
||||||
|
headers: { path: '0819f05c4eef4c71ace90d822a990e87' },
|
||||||
|
});
|
||||||
const info = (await infoRes.json()).result;
|
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'] || '-';
|
||||||
@ -249,14 +291,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 = [];
|
||||||
|
|
||||||
@ -264,22 +306,16 @@
|
|||||||
|
|
||||||
for (const item of selectedItems) {
|
for (const item of selectedItems) {
|
||||||
try {
|
try {
|
||||||
let getRes = await eda.sys_ClientUrl.request(
|
let getRes = await eda.sys_ClientUrl.request(`${SERVER}/getLeyeList?lcscId=${item.lcscId}`, 'GET');
|
||||||
`${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(
|
getRes = await eda.sys_ClientUrl.request(`${SERVER}/getLeyeList?lcscId=${item.lcscId}`, 'GET');
|
||||||
`${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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -300,9 +336,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();
|
||||||
@ -317,7 +353,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} (网络异常)`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -328,17 +364,18 @@
|
|||||||
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}`,
|
eda.sys_Message.showToastMessage(`出库完成:成功:${successCount},失败:${failItems.length}`, ESYS_ToastMessageType.WARNING);
|
||||||
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();
|
||||||
await scanSchematic();
|
if (eda.sys_Storage.getExtensionUserConfig('auto-fill-bom')) await scanSchematic();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@ -1,36 +1,83 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="zh">
|
<html lang="zh">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<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 { -ms-overflow-style: none; scrollbar-width: none; }
|
.scrollbar-hide {
|
||||||
.scrollbar-hide::-webkit-scrollbar { display: none; }
|
-ms-overflow-style: none;
|
||||||
#fixed-window { width: 1280px; height: 680px; border: 1px solid #e5e7eb; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); }
|
scrollbar-width: none;
|
||||||
.table-container { height: calc(680px - 160px - 64px); overflow-y: auto; }
|
}
|
||||||
.sticky-header { position: sticky; top: 0; z-index: 10; }
|
.scrollbar-hide::-webkit-scrollbar {
|
||||||
@keyframes scan { 0% { top: 0; } 100% { top: 100%; } }
|
display: none;
|
||||||
.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>
|
||||||
</head>
|
<script src="/iframe/js/qrcode.min.js" language="JavaScript"></script>
|
||||||
<body class="bg-gray-100 font-sans text-sm">
|
<script src="/iframe/js/mqtt.min.js" language="JavaScript"></script>
|
||||||
<div id="fixed-window" class="bg-gray-50 flex flex-col overflow-hidden mx-auto">
|
</head>
|
||||||
|
<body class="bg-gray-100 font-sans text-sm">
|
||||||
|
<div id="fixed-window" class="bg-gray-50 flex flex-col overflow-hidden mx-auto">
|
||||||
<header class="flex-shrink-0 bg-white border-b border-gray-200 shadow-sm p-4 space-y-3">
|
<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 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" />
|
<input
|
||||||
|
id="order-uuid"
|
||||||
|
type="text"
|
||||||
|
placeholder="UUID 或者 订单链接"
|
||||||
|
disabled
|
||||||
|
class="flex-grow px-3 py-1.5 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 outline-none"
|
||||||
|
/>
|
||||||
<button id="import-order-btn" class="px-6 py-1.5 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition">添加订单</button>
|
<button id="import-order-btn" class="px-6 py-1.5 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition">添加订单</button>
|
||||||
<button id="import-order-file-btn" class="px-6 py-1.5 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition">添加订单详情 Excel 文档</button>
|
<button id="import-order-file-btn" class="px-6 py-1.5 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition">
|
||||||
|
添加订单详情 Excel 文档
|
||||||
|
</button>
|
||||||
<button id="open-qr-btn" class="px-6 py-1.5 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition">扫描二维码</button>
|
<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 id="single-cid" type="text" placeholder="CID" class="w-64 px-3 py-1.5 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 outline-none" />
|
<input
|
||||||
<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" />
|
id="single-cid"
|
||||||
|
type="text"
|
||||||
|
placeholder="CID"
|
||||||
|
class="w-64 px-3 py-1.5 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 outline-none"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
id="single-qty"
|
||||||
|
type="number"
|
||||||
|
placeholder="数量"
|
||||||
|
class="w-32 px-3 py-1.5 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 outline-none"
|
||||||
|
/>
|
||||||
<button id="import-cid-btn" class="px-6 py-1.5 bg-green-600 text-white rounded-md hover:bg-green-700 transition">添加器件</button>
|
<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>
|
||||||
@ -40,9 +87,7 @@
|
|||||||
<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">
|
<div class="text-gray-500 text-xs">待入库项:<span id="list-count" class="font-bold text-blue-600">0</span></div>
|
||||||
待入库项:<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">
|
||||||
@ -62,7 +107,9 @@
|
|||||||
</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"><td colspan="9" class="text-center py-20 text-gray-400">列表为空,请从上方导入数据</td></tr>
|
<tr id="empty-row">
|
||||||
|
<td colspan="9" class="text-center py-20 text-gray-400">列表为空,请从上方导入数据</td>
|
||||||
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
@ -70,7 +117,9 @@
|
|||||||
|
|
||||||
<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>
|
<button id="batch-save-btn" class="px-8 py-2 bg-blue-600 text-white font-bold rounded-md hover:bg-blue-700 shadow-md">
|
||||||
|
批量入库
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<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">
|
||||||
@ -88,7 +137,10 @@
|
|||||||
<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 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
|
||||||
|
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">
|
||||||
@ -101,18 +153,26 @@
|
|||||||
|
|
||||||
<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>
|
<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-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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<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');
|
||||||
@ -124,6 +184,8 @@
|
|||||||
let importList = [];
|
let importList = [];
|
||||||
let videoStream = null;
|
let videoStream = null;
|
||||||
let currentScanData = null;
|
let currentScanData = null;
|
||||||
|
let mqttClient = null;
|
||||||
|
const myDeviceId = Math.random().toString(36).substring(2, 10);
|
||||||
|
|
||||||
function renderList() {
|
function renderList() {
|
||||||
if (importList.length === 0) {
|
if (importList.length === 0) {
|
||||||
@ -132,7 +194,9 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
tableBody.innerHTML = importList.map((item, index) => `
|
tableBody.innerHTML = importList
|
||||||
|
.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>
|
||||||
@ -148,17 +212,22 @@
|
|||||||
<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');
|
||||||
cameraSelect.innerHTML = videoDevices.map(d =>
|
let options = videoDevices
|
||||||
`<option value="${d.deviceId}">${d.label || '摄像头 ' + d.deviceId.slice(0, 5)}</option>`
|
.map((d) => `<option value="${d.deviceId}">${d.label || '摄像头 ' + d.deviceId.slice(0, 5)}</option>`)
|
||||||
).join('');
|
.join('');
|
||||||
|
|
||||||
|
options += `<option value="WEBSOCKET_MODE">WebSocket 远程扫码</option>`;
|
||||||
|
cameraSelect.innerHTML = options;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
eda.sys_Message.showToastMessage('无法获取摄像头列表: ' + e.message, ESYS_ToastMessageType.ERROR);
|
eda.sys_Message.showToastMessage('无法获取摄像头列表: ' + e.message, ESYS_ToastMessageType.ERROR);
|
||||||
}
|
}
|
||||||
@ -168,28 +237,81 @@
|
|||||||
stopCamera();
|
stopCamera();
|
||||||
qrDialog.classList.add('hidden');
|
qrDialog.classList.add('hidden');
|
||||||
|
|
||||||
|
if (currentScanData) {
|
||||||
document.getElementById('res-cid').textContent = currentScanData.lcscId;
|
document.getElementById('res-cid').textContent = currentScanData.lcscId;
|
||||||
document.getElementById('res-pm').textContent = currentScanData.name;
|
document.getElementById('res-pm').textContent = currentScanData.name;
|
||||||
document.getElementById('res-qty').textContent = currentScanData.quantity;
|
document.getElementById('res-qty').textContent = currentScanData.quantity;
|
||||||
|
|
||||||
currentScanData = null;
|
currentScanData = null;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
function stopCamera() {
|
function stopCamera() {
|
||||||
if (videoStream) {
|
if (videoStream) {
|
||||||
videoStream.getTracks().forEach(track => track.stop());
|
videoStream.getTracks().forEach((track) => track.stop());
|
||||||
videoStream = null;
|
videoStream = null;
|
||||||
}
|
}
|
||||||
qrVideo.parentElement.classList.remove('scanning');
|
|
||||||
|
if (mqttClient) {
|
||||||
|
console.log('正在断开 MQTT 远程连接...');
|
||||||
|
mqttClient.end(true); // 强制关闭连接
|
||||||
|
mqttClient = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById('btn-start-camera').onclick = async () => {
|
qrVideo.style.display = 'block';
|
||||||
stopCamera();
|
const oldQr = document.getElementById('remote-qr-canvas');
|
||||||
const deviceId = cameraSelect.value;
|
if (oldQr) oldQr.remove();
|
||||||
const constraints = {
|
|
||||||
video: deviceId ? { deviceId: { exact: deviceId } } : { facingMode: 'environment' }
|
|
||||||
};
|
|
||||||
|
|
||||||
|
if (qrVideo.parentElement) {
|
||||||
|
qrVideo.parentElement.classList.remove('scanning');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function startRemoteMode() {
|
||||||
|
if (mqttClient) {
|
||||||
|
mqttClient.end(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
const broker = 'wss://test.mosquitto.org:8081';
|
||||||
|
const topic = `leye/scan/${myDeviceId}`;
|
||||||
|
|
||||||
|
mqttClient = mqtt.connect(broker);
|
||||||
|
|
||||||
|
mqttClient.on('connect', () => {
|
||||||
|
mqttClient.subscribe(topic);
|
||||||
|
eda.sys_Message.showToastMessage('MQTT 远程模式启动', ESYS_ToastMessageType.SUCCESS);
|
||||||
|
|
||||||
|
const scanUrl = `https://leye.dragon.edu.kg/scan.html?id=${myDeviceId}`;
|
||||||
|
|
||||||
|
qrVideo.style.display = 'none';
|
||||||
|
let qrCanvas = document.getElementById('remote-qr-canvas') || createQrCanvas();
|
||||||
|
QRCode.toCanvas(qrCanvas, scanUrl, { width: 250 });
|
||||||
|
});
|
||||||
|
|
||||||
|
mqttClient.on('message', (t, message) => {
|
||||||
|
try {
|
||||||
|
const msg = JSON.parse(message.toString());
|
||||||
|
if (msg.type === 'SCAN_RESULT') {
|
||||||
|
parseQrData(msg.content);
|
||||||
|
eda.sys_Message.showToastMessage('远程扫码成功', ESYS_ToastMessageType.INFO);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('MQTT数据解析失败', e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function createQrCanvas() {
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
canvas.id = 'remote-qr-canvas';
|
||||||
|
canvas.className = 'absolute inset-0 w-full h-full p-4 bg-white';
|
||||||
|
qrVideo.parentElement.appendChild(canvas);
|
||||||
|
return canvas;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function startLocalCamera(deviceId) {
|
||||||
|
const constraints = {
|
||||||
|
video: deviceId ? { deviceId: { exact: deviceId } } : { facingMode: 'environment' },
|
||||||
|
};
|
||||||
try {
|
try {
|
||||||
videoStream = await navigator.mediaDevices.getUserMedia(constraints);
|
videoStream = await navigator.mediaDevices.getUserMedia(constraints);
|
||||||
qrVideo.srcObject = videoStream;
|
qrVideo.srcObject = videoStream;
|
||||||
@ -199,6 +321,17 @@
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
eda.sys_Message.showToastMessage('启动摄像头失败', ESYS_ToastMessageType.ERROR);
|
eda.sys_Message.showToastMessage('启动摄像头失败', ESYS_ToastMessageType.ERROR);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('btn-start-camera').onclick = async () => {
|
||||||
|
stopCamera();
|
||||||
|
const mode = cameraSelect.value;
|
||||||
|
|
||||||
|
if (mode === 'WEBSOCKET_MODE') {
|
||||||
|
startRemoteMode();
|
||||||
|
} else {
|
||||||
|
startLocalCamera(mode);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
document.getElementById('btn-scan').onclick = () => {
|
document.getElementById('btn-scan').onclick = () => {
|
||||||
@ -232,7 +365,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;
|
||||||
@ -240,7 +373,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);
|
||||||
@ -273,13 +406,16 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
window.updateQty = (index, val) => { importList[index].quantity = parseInt(val) || 0; };
|
window.updateQty = (index, val) => {
|
||||||
window.removeItem = (index) => { importList.splice(index, 1); renderList(); };
|
importList[index].quantity = parseInt(val) || 0;
|
||||||
|
};
|
||||||
|
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 () => {
|
||||||
@ -324,7 +460,7 @@
|
|||||||
quantity: quantity,
|
quantity: quantity,
|
||||||
value: '-',
|
value: '-',
|
||||||
childCat: '-',
|
childCat: '-',
|
||||||
selected: true
|
selected: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -340,7 +476,6 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
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);
|
||||||
@ -366,7 +501,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++) {
|
||||||
@ -375,12 +510,11 @@
|
|||||||
|
|
||||||
if (dev && dev.uuid) {
|
if (dev && dev.uuid) {
|
||||||
try {
|
try {
|
||||||
const infoRes = await eda.sys_ClientUrl.request(
|
const EDA_HOST =
|
||||||
'https://client/api/v2/devices/' + dev.uuid,
|
eda.sys_Environment.isClient() && !eda.sys_Environment.isOnlineMode() ? 'https://client' : 'https://pro.lceda.cn';
|
||||||
'GET',
|
const infoRes = await eda.sys_ClientUrl.request(EDA_HOST + '/api/v2/devices/' + dev.uuid, 'GET', null, {
|
||||||
null,
|
headers: { path: '0819f05c4eef4c71ace90d822a990e87' },
|
||||||
{ headers: { path: '0819f05c4eef4c71ace90d822a990e87' } }
|
});
|
||||||
);
|
|
||||||
const infoJson = await infoRes.json();
|
const infoJson = await infoRes.json();
|
||||||
const info = infoJson.result;
|
const info = infoJson.result;
|
||||||
|
|
||||||
@ -401,11 +535,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) => {
|
||||||
@ -421,7 +555,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);
|
||||||
@ -429,22 +563,18 @@
|
|||||||
|
|
||||||
let successCount = 0;
|
let successCount = 0;
|
||||||
let failCount = 0;
|
let failCount = 0;
|
||||||
const SERVER = await eda.sys_Storage.getExtensionUserConfig('server-host') || 'http://localhost:21816';
|
|
||||||
|
|
||||||
for (const item of toSave) {
|
for (const item of toSave) {
|
||||||
try {
|
try {
|
||||||
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(
|
let res = await eda.sys_ClientUrl.request(SERVER + '/addLeyeList', 'POST', postData, {
|
||||||
SERVER + '/addLeyeList',
|
headers: { 'Content-Type': 'application/json' },
|
||||||
'POST',
|
});
|
||||||
postData,
|
|
||||||
{ headers: { 'Content-Type': 'application/json' } }
|
|
||||||
);
|
|
||||||
|
|
||||||
let result = await res.json();
|
let result = await res.json();
|
||||||
|
|
||||||
@ -452,14 +582,11 @@
|
|||||||
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(
|
res = await eda.sys_ClientUrl.request(SERVER + '/addLeyeList', 'POST', postData, {
|
||||||
SERVER + '/addLeyeList',
|
headers: { 'Content-Type': 'application/json' },
|
||||||
'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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -482,11 +609,13 @@
|
|||||||
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(`入库完成,成功: ${successCount}, 失败: ${failCount}`,
|
eda.sys_Message.showToastMessage(
|
||||||
failCount > 0 ? ESYS_ToastMessageType.WARNING : ESYS_ToastMessageType.SUCCESS);
|
`入库完成,成功: ${successCount}, 失败: ${failCount}`,
|
||||||
|
failCount > 0 ? ESYS_ToastMessageType.WARNING : ESYS_ToastMessageType.SUCCESS,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
19
iframe/js/mqtt.min.js
vendored
Normal file
19
iframe/js/mqtt.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
7
iframe/js/qrcode.min.js
vendored
Normal file
7
iframe/js/qrcode.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -1,34 +1,78 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="zh">
|
<html lang="zh">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<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 { user-select: none; }
|
html {
|
||||||
.scrollbar-hide { -ms-overflow-style: none; scrollbar-width: none; }
|
user-select: none;
|
||||||
.scrollbar-hide::-webkit-scrollbar { display: none; }
|
}
|
||||||
#fixed-window { width: 1280px; height: 680px; border: 1px solid #e5e7eb; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); }
|
.scrollbar-hide {
|
||||||
.table-container { height: calc(680px - 48px - 52px - 64px); overflow-y: auto; }
|
-ms-overflow-style: none;
|
||||||
.sticky-header { position: sticky; top: 0; z-index: 10; }
|
scrollbar-width: none;
|
||||||
.sort-icon { display: inline-block; margin-left: 4px; font-size: 10px; color: #9ca3af; }
|
}
|
||||||
.sort-active { color: #2563eb !important; }
|
.scrollbar-hide::-webkit-scrollbar {
|
||||||
.hidden { display: none !important; }
|
display: none;
|
||||||
.cat-toggle { transition: transform 0.2s; cursor: pointer; padding: 4px; }
|
}
|
||||||
.cat-toggle.collapsed { transform: rotate(-90deg); }
|
#fixed-window {
|
||||||
.children-container { overflow: hidden; transition: max-height 0.3s ease-out; }
|
width: 1280px;
|
||||||
.children-container.hidden { display: none; }
|
height: 680px;
|
||||||
|
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">
|
||||||
<div id="fixed-window" class="bg-gray-50 flex flex-col overflow-hidden">
|
<div id="fixed-window" class="bg-gray-50 flex flex-col overflow-hidden">
|
||||||
<header class="flex-shrink-0 bg-white border-b border-gray-200 shadow-sm p-2 text-sm">
|
<header class="flex-shrink-0 bg-white border-b border-gray-200 shadow-sm p-2 text-sm">
|
||||||
<div class="max-w-full mx-auto flex items-center space-x-3">
|
<div class="max-w-full mx-auto flex items-center space-x-3">
|
||||||
<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 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" />
|
<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"
|
||||||
|
/>
|
||||||
<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>
|
||||||
@ -36,8 +80,7 @@
|
|||||||
<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 id="category-tree" class="flex-grow overflow-y-auto scrollbar-hide space-y-0.5 text-xs"></div>
|
||||||
</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">
|
||||||
@ -47,16 +90,24 @@
|
|||||||
<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">类型 <span class="sort-icon">⇅</span></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-value" class="w-20 px-3 py-2 text-left cursor-pointer hover:bg-gray-200">值 <span class="sort-icon">⇅</span></th>
|
类型 <span class="sort-icon">⇅</span>
|
||||||
|
</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">余量 <span class="sort-icon">⇅</span></th>
|
<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>
|
||||||
<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><td colspan="8" class="text-center py-6 text-gray-500">正在初始化器件数据...</td></tr>
|
<tr>
|
||||||
|
<td colspan="8" class="text-center py-6 text-gray-500">正在初始化器件数据...</td>
|
||||||
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
@ -65,9 +116,13 @@
|
|||||||
<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>
|
<button id="prev-page" class="px-2 py-0.5 border border-gray-300 rounded-md hover:bg-gray-200 disabled:opacity-30">
|
||||||
|
上页
|
||||||
|
</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>
|
<button id="next-page" class="px-2 py-0.5 border border-gray-300 rounded-md hover:bg-gray-200 disabled:opacity-30">
|
||||||
|
下页
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -90,39 +145,59 @@
|
|||||||
<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 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="必填" />
|
<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="必填"
|
||||||
|
/>
|
||||||
</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 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="必填" />
|
<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="必填"
|
||||||
|
/>
|
||||||
</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>
|
<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-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 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" />
|
<input
|
||||||
<div class="text-[10px] text-gray-400">
|
id="edit-qty-input"
|
||||||
<span id="current-qty-val">0</span> → <span id="target-qty-val">0</span>
|
type="number"
|
||||||
</div>
|
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"
|
||||||
|
/>
|
||||||
|
<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>
|
<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>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', function () {
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
const categoryTree = document.getElementById('category-tree');
|
const categoryTree = document.getElementById('category-tree');
|
||||||
const tableBody = document.getElementById('data-table-body');
|
const tableBody = document.getElementById('data-table-body');
|
||||||
@ -146,8 +221,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 = [];
|
||||||
@ -155,13 +230,20 @@
|
|||||||
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([['M', 1e6], ['k', 1e3], ['m', 1e-3], ['u', 1e-6], ['n', 1e-9], ['p', 1e-12]]);
|
const unitMap = new Map([
|
||||||
|
['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;
|
||||||
@ -180,7 +262,11 @@
|
|||||||
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 { cachedDetails = cachedRaw ? JSON.parse(cachedRaw) : []; } catch(e) { cachedDetails = []; }
|
try {
|
||||||
|
cachedDetails = cachedRaw ? JSON.parse(cachedRaw) : [];
|
||||||
|
} catch (e) {
|
||||||
|
cachedDetails = [];
|
||||||
|
}
|
||||||
|
|
||||||
let listRes = await eda.sys_ClientUrl.request(SERVER + '/getLeyeList?pageSize=1000¤t=1');
|
let listRes = await eda.sys_ClientUrl.request(SERVER + '/getLeyeList?pageSize=1000¤t=1');
|
||||||
let listResult = await listRes.json();
|
let listResult = await listRes.json();
|
||||||
@ -190,29 +276,35 @@
|
|||||||
eda.sys_Message.showToastMessage('等待拉起本地服务端...', ESYS_ToastMessageType.INFO);
|
eda.sys_Message.showToastMessage('等待拉起本地服务端...', ESYS_ToastMessageType.INFO);
|
||||||
listRes = await eda.sys_ClientUrl.request(SERVER + '/getLeyeList?pageSize=1000¤t=1');
|
listRes = await eda.sys_ClientUrl.request(SERVER + '/getLeyeList?pageSize=1000¤t=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);
|
||||||
const chunkSize = 20;
|
const chunkSize = 20;
|
||||||
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(chunk.map(async (dev) => {
|
const chunkData = await Promise.all(
|
||||||
|
chunk.map(async (dev) => {
|
||||||
try {
|
try {
|
||||||
const EDA_HOST = eda.sys_Environment.isClient() ? "https://client" : "https://pro.lceda.cn";
|
const EDA_HOST =
|
||||||
const infoRes = await eda.sys_ClientUrl.request(EDA_HOST + '/api/v2/devices/' + dev.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/' + 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 {
|
||||||
@ -225,15 +317,18 @@
|
|||||||
brand: info.attributes['Manufacturer'] || '-',
|
brand: info.attributes['Manufacturer'] || '-',
|
||||||
lcscId: String(info.attributes['Supplier Part']).toUpperCase(),
|
lcscId: String(info.attributes['Supplier Part']).toUpperCase(),
|
||||||
};
|
};
|
||||||
} catch (e) { return null; }
|
} catch (e) {
|
||||||
}));
|
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 };
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -248,7 +343,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);
|
||||||
});
|
});
|
||||||
@ -260,7 +355,9 @@
|
|||||||
<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).sort().forEach(parent => {
|
Object.keys(tree)
|
||||||
|
.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">
|
||||||
@ -271,7 +368,9 @@
|
|||||||
</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]).sort().forEach(child => {
|
Array.from(tree[parent])
|
||||||
|
.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}">
|
||||||
@ -284,7 +383,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;
|
||||||
@ -301,8 +400,9 @@
|
|||||||
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 = !keyword ||
|
const matchesSearch =
|
||||||
|
!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)) ||
|
||||||
@ -319,9 +419,16 @@
|
|||||||
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') { valA = a.childCat; valB = b.childCat; }
|
if (rule.key === 'type') {
|
||||||
else if (rule.key === 'value') { valA = parseValue(a.value); valB = parseValue(b.value); }
|
valA = a.childCat;
|
||||||
else if (rule.key === 'quantity') { valA = a.quantity; valB = b.quantity; }
|
valB = b.childCat;
|
||||||
|
} 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') {
|
||||||
@ -353,7 +460,9 @@
|
|||||||
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.map(item => `
|
tableBody.innerHTML = pageData
|
||||||
|
.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>
|
||||||
@ -364,21 +473,23 @@
|
|||||||
<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})` : '');
|
||||||
@ -394,8 +505,18 @@
|
|||||||
|
|
||||||
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 = () => { if (currentPage > 1) { currentPage--; applySortAndRender(); } };
|
document.getElementById('prev-page').onclick = () => {
|
||||||
document.getElementById('next-page').onclick = () => { if (currentPage * pageSize < filteredData.length) { currentPage++; applySortAndRender(); } };
|
if (currentPage > 1) {
|
||||||
|
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]');
|
||||||
@ -406,7 +527,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);
|
||||||
@ -441,7 +562,9 @@
|
|||||||
uuid: selectedRowData.uuid,
|
uuid: selectedRowData.uuid,
|
||||||
libraryUuid: '0819f05c4eef4c71ace90d822a990e87',
|
libraryUuid: '0819f05c4eef4c71ace90d822a990e87',
|
||||||
});
|
});
|
||||||
} catch (e) { console.error(e); }
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
editBtn.onclick = () => {
|
editBtn.onclick = () => {
|
||||||
@ -505,27 +628,30 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const newQuantity = currentOp === 'add' ?
|
const newQuantity = currentOp === 'add' ? selectedRowData.quantity + changeQty : selectedRowData.quantity - changeQty;
|
||||||
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(SERVER + '/editLeyeList', 'POST', JSON.stringify({
|
const res = await eda.sys_ClientUrl.request(
|
||||||
|
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;
|
||||||
@ -552,6 +678,6 @@
|
|||||||
|
|
||||||
initInventoryData();
|
initInventoryData();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@ -109,16 +109,27 @@
|
|||||||
</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>
|
||||||
@ -128,10 +139,14 @@
|
|||||||
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);
|
||||||
@ -139,16 +154,22 @@
|
|||||||
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) {
|
||||||
|
|||||||
BIN
images/img_08.png
Normal file
BIN
images/img_08.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 459 KiB |
@ -12,8 +12,7 @@
|
|||||||
"eslint:all": "eslint --ext .ts --fix .",
|
"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",
|
||||||
"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 compile && ts-node ./build/packaged.ts"
|
||||||
"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",
|
||||||
@ -36,8 +35,7 @@
|
|||||||
"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",
|
||||||
|
|||||||
38
src/index.ts
38
src/index.ts
@ -45,38 +45,56 @@ 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.registerShortcutKey(['Shift+L'], 'openLeyeIFrame', async () => {
|
eda.sys_ShortcutKey
|
||||||
|
.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.request('https://leye.dragon.edu.kg/release/notice.json').then(async (res: any) => {
|
eda.sys_ClientUrl
|
||||||
|
.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] 获取最新版本');
|
||||||
eda.sys_ClientUrl.request('https://leye.dragon.edu.kg/release/eext.ver.json').then(async (res: any) => {
|
if (eda.sys_Storage.getExtensionUserConfig('auto-check-update') ?? true) {
|
||||||
|
eda.sys_ClientUrl
|
||||||
|
.request('https://leye.dragon.edu.kg/release/eext.ver.json')
|
||||||
|
.then(async (res: any) => {
|
||||||
const data = await res.json();
|
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(data.versions[0].changelog, `LEYE 有新版本 ${data.versions[0].ver} 可用,是否前往下载?`, '前往下载', '算了', async (r) => {
|
eda.sys_Dialog.showConfirmationMessage(
|
||||||
|
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] 用户主动关闭自动检查更新');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user