commit c32381f8ed70e1d5d7f3bb189ee8a979a953190c
Author: ZiJIe <17738440858@163.com>
Date: Mon Sep 22 11:51:16 2025 +0800
Initial commit
diff --git a/.claude/1.md b/.claude/1.md
new file mode 100644
index 0000000..19111e9
--- /dev/null
+++ b/.claude/1.md
@@ -0,0 +1,48 @@
+---
+description:
+globs:
+alwaysApply: false
+---
+---
+description:
+globs:
+alwaysApply: false
+---
+
+# Your rule content
+#角色
+你是一名精通开发的高级工程师,拥有10年以上的应用开发经验,熟悉*等开发工具和技术栈。
+你的任务是帮助用户设计和开发易用且易于推护的 *** 应用。始终遵循最佳实践,并坚持干净代码和健壮架构的原则。
+
+#目标
+你的目标是以用户容易理解的方式帮助他们完成“应用的设计和开发工作,确保应用功能完善、性能优异、用户体验良好。
+
+#要求
+在理解用户需求、设计UI、编写代码、解决问题和项目选代优化时,你应该始终遵循以下原则:
+
+
+##需求理解
+-充分理解用户需求,站在用户角度思考,分析需求是否存在缺漏,并与用户讨论完善需求;
+-选择最简单的解决方案来满足用户需求,避免过度设计。
+##UI和样式设计
+-使用现代UI框架进行样式设计(例如***,这里可以根据不同开发项目仔纽展开,比如使用哪些视觉规范或者UI框架,没有的话也可以不用过多展开);
+-在不同平台上实现一致的设计和响应式模式
+##代码编写
+技术选型:根据项目需求选择合适的技术栈(例如***,这里需要仔细展开,比如介招某个技术栈用在什么地方,以及要遵循什么最佳实践)
+代码结构:强调代码的清晰性、模块化、可维护性,遵循最佳实践(如DRY原则、最小权限原则、响应式设计等)
+-代码安全性:在编写代码时,始终考虑安全性,避免引入漏洞,确保用户输入的安全处理
+-性能优化:优化代码的性能,减少资源占用,提升加载速度,确保项目的高效运行
+-测试与文档:编写单元测试,确保代码的健壮性,并提供清晰的中文注释和文档。方便后续阅读和维护
+##问题解决
+-全面阅读相关代码,理解***应用的工作原理
+-根据用户的反馈分析问题的原因,提出解决问题的思路
+-确保每次代码变更不会破坏现有功能,且尽可能保持最小的改动
+##迭代优化
+与用户保持密切沟通,根据反读调整功能和设计,确保应用符合用户需求
+在不确定需求时,主动询问用户以澄清需求或技术细节
+##方法论
+-系统2思维:以分析严谨的方式解决问题。将需求分解为更小、可管理的部分,并在实施前仔细考虑每一步
+思维树:评估多种可能的解决方案及其后果。使用结构化的方法探索不同的路径。并选择最优的解决方案
+-选代改进:在最终确定代码之前,考虑改进、边缘情况和优化。通过潜在增强的迭代,确保最终解决方案是健壮的
+
+
diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md
new file mode 100644
index 0000000..6e5be02
--- /dev/null
+++ b/.claude/CLAUDE.md
@@ -0,0 +1,235 @@
+
+---
+
+# CLAUDE.md
+
+This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
+
+## 项目概述
+
+这是一个综合性的 **RuoYi-Vue 企业管理系统**,并集成了跨境电商 **ERP 功能**。项目主要包含:
+
+* **RuoYi-Vue 核心**:基于 Spring Boot 后端和 Vue.js 前端的企业管理系统
+* **ERP 客户端 (erp\_client\_sb)**:跨境电商 ERP 系统,JavaFX 桌面应用
+* **自定义扩展**:客户端监控、数据报表、API 集成功能
+
+## 架构概览
+
+### 后端架构 (Spring Boot)
+
+后端采用模块化 Spring Boot 架构,职责清晰:
+
+```
+ruoyi-admin/ # 主应用入口和 Web 控制器
+ruoyi-framework/ # 核心框架配置 (安全、Redis、MyBatis)
+ruoyi-system/ # 系统领域实体、Mapper、业务逻辑
+ruoyi-common/ # 公共工具类、常量、通用功能
+ruoyi-quartz/ # 定时任务管理
+ruoyi-generator/ # 代码生成工具
+```
+
+**主要技术栈:**
+
+* Spring Boot 2.5.15 (Java 17)
+* Spring Security 5.7.12 + JWT 认证
+* MyBatis + PageHelper 分页插件
+* MySQL 数据库 + Redis 缓存与会话管理
+* Druid 1.2.23 连接池
+* Swagger 3.0.0 API 文档
+* Selenium 4.34.0 和 WebMagic 1.0.3 网页自动化
+
+### 前端架构 (Vue.js)
+
+前端是一个基于 Vue 2.6.12 的应用,UI 使用 Element UI:
+
+```
+ruoyi-ui/src/
+ api/ # API 服务模块
+ components/ # 可复用的 Vue 组件
+ layout/ # 应用布局组件
+ router/ # Vue Router 配置
+ store/ # Vuex 状态管理
+ utils/ # 工具函数
+ views/ # 页面组件,按功能组织
+```
+
+**主要技术栈:**
+
+* Vue 2.6.12 + Vue Router 3.4.9
+* Element UI 2.15.14
+* Vuex 3.6.0
+* Axios 网络请求
+
+### ERP 客户端架构 (JavaFX + Spring Boot)
+
+独立桌面应用,支持跨境电商业务:
+
+* Spring Boot 3.5.4 + JavaFX 17.0.1 UI
+* SQLite 3.42.0 数据库 + JPA/Hibernate
+* Playwright 1.54.0 和 WebMagic 1.0.3 数据采集
+* HutoolUtils 5.8.36 工具包
+* 七牛云存储集成
+* 多平台支持(亚马逊、日本乐天、Shopee 等)
+
+## 常用开发命令
+
+### 后端 (Maven)
+
+```bash
+# 构建打包
+mvn clean package
+
+# 启动 Spring Boot 应用
+mvn spring-boot:run
+
+# 从 ruoyi-admin 模块运行
+cd ruoyi-admin && mvn spring-boot:run
+
+# 跳过测试打包
+mvn clean package -DskipTests
+
+# 运行测试
+mvn test
+
+# Windows 运行脚本
+ry.bat
+```
+
+### 前端 (Vue.js)
+
+```bash
+cd ruoyi-ui
+
+# 安装依赖
+npm install
+# 国内镜像源安装
+npm install --registry=https://registry.npmmirror.com
+
+# 启动开发服务 (默认端口 80)
+npm run dev
+
+# 生产环境构建
+npm run build:prod
+
+# 测试环境构建
+npm run build:stage
+```
+
+### ERP 客户端 (JavaFX)
+
+```bash
+cd erp_client_sb
+
+# 启动应用
+mvn spring-boot:run
+
+# 打包可执行 JAR
+mvn clean package
+
+# 使用 JavaFX 插件运行
+mvn javafx:run
+
+# 运行测试
+mvn test
+```
+
+## 核心配置说明
+
+### 数据库
+
+* **主库**:MySQL (Druid 连接池)
+* **Redis**:缓存、会话、分布式锁
+* **MyBatis**:XML SQL 映射,内置分页
+* **ERP 客户端**:SQLite 本地存储
+
+### 安全与认证
+
+* **JWT**:无状态认证,可配置过期时间
+* **Spring Security**:基于角色的访问控制
+* **CORS**:跨域通信
+* **XSS 防护**:输入过滤
+
+### API 设计
+
+* **RESTful 风格**:标准 HTTP 动词和状态码
+* **客户端监控接口**:ERP 客户端状态上报
+* **匿名访问**:如 `/monitor/client/api/**`
+* **统一分页**:PageHelper 插件
+* **统一响应格式**:AjaxResult 封装
+* **跨域配置**:CORS 支持
+* **Swagger 接口文档**:`/swagger-ui/index.html`
+
+## 开发流程
+
+### 新功能开发
+
+1. 后端:在 `ruoyi-system/domain/` 新建实体
+2. 数据库:在 `ruoyi-system/mapper/` 增加 Mapper
+3. 服务层:实现业务逻辑
+4. 控制层:在 `ruoyi-admin/web/controller/` 增加接口
+5. 前端:开发 Vue 页面和 API 模块
+6. 集成:更新路由和菜单
+
+### 客户端集成功能
+
+* 实时状态跟踪
+* 错误上报与日志
+* 数据采集统计
+* API 请求监控
+* 版本分发追踪
+
+### 代码生成器
+
+* 访问 `/tool/gen` 使用
+* 自动生成:Java 实体、Mapper、Service、Controller
+* 自动生成前端 Vue 页面和 API
+* 自动生成数据库 SQL
+
+## 注意事项
+
+### 文件上传与静态资源
+
+* 上传路径在 `application.yml` 中配置 (`ruoyi.profile`)
+* 支持本地存储和七牛云存储
+* 静态资源路径 `/profile/**`
+* 前端开发服务器端口:80 (可在 `vue.config.js` 中修改)
+
+### 系统监控
+
+* 系统监控接口 `/monitor/server`
+* Redis 缓存监控
+* Druid 连接池监控
+* Quartz 定时任务
+* 操作日志审计
+
+### 多平台支持
+
+ERP 客户端支持:
+
+* 亚马逊 (Amazon)
+* 日本乐天 (Rakuten)
+* Shopee
+* 阿里巴巴 1688
+* 自定义采集与自动化工具
+
+### 重要配置文件
+
+* **后端主配置**:`ruoyi-admin/src/main/resources/application.yml`
+* **数据库配置**:`application-druid.yml`
+* **前端配置**:`ruoyi-ui/vue.config.js`
+* **Maven 配置**:根目录 `pom.xml`(父项目)和各模块 `pom.xml`
+* **ERP 客户端配置**:`erp_client_sb/src/main/resources/application.yml`
+
+### 常见问题排查
+
+* **端口冲突**:前端默认端口 80,可在 `vue.config.js` 修改
+* **跨域问题**:检查 `CorsConfig.java` 和 `vue.config.js` 代理配置
+* **数据库连接**:检查 `application-druid.yml` 中的数据库连接配置
+* **Maven 依赖**:使用阿里云镜像加速依赖下载
+---
+
+⚠️ **额外要求**:回答时必须使用中文。
+⚠️ **代码规范要求**:代码必须遵循CLAUDE.md中的规范,代码简洁度和性能优先。
+💡 **操作提示**:在每次修改代码前,先向我说明修改的思路和方案,我确认同意后再进行代码更改。
+
+---
diff --git a/.claude/settings.local.json b/.claude/settings.local.json
new file mode 100644
index 0000000..39d9804
--- /dev/null
+++ b/.claude/settings.local.json
@@ -0,0 +1,213 @@
+{
+ "permissions": {
+ "allow": [
+ "Bash(mvn clean:*)",
+ "Bash(where mysql)",
+ "Bash(dir:*)",
+ "Bash(mvn:*)",
+ "Bash(npm run dev:*)",
+ "Bash(xmllint:*)",
+ "Bash(sed:*)",
+ "Bash(rmdir:*)",
+ "Bash(rm:*)",
+ "mcp__talktofigma__get_selection",
+ "mcp__talktofigma__join_channel",
+ "mcp__talktofigma__get_document_info",
+ "mcp__talktofigma__read_my_design",
+ "mcp__talktofigma__get_node_info",
+ "mcp__talktofigma__scan_text_nodes",
+ "mcp__talktofigma__scan_nodes_by_types",
+ "mcp__talktofigma__get_nodes_info",
+ "Bash(mvn compile:*)",
+ "Bash(mkdir:*)",
+ "Bash(copy:*)",
+ "Bash(cp:*)",
+ "Bash(mvn:*)",
+ "Bash(del \"C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\erp_client_sb\\src\\main\\java\\module-info.java\")",
+ "Bash(jar tf:*)",
+ "Bash(./build-simple.bat)",
+ "Bash(java:*)",
+ "Bash(./build-custom-installer.bat)",
+ "Bash(echo $JAVA_HOME)",
+ "Bash(where java)",
+ "Bash(./build-installer-fixed.bat)",
+ "Bash(fix-npm-install.bat)",
+ "Bash(chmod:*)",
+ "Bash(where mvn)",
+ "Bash(./install-nsis.bat)",
+ "Bash(./test-final.bat)",
+ "Bash(./build-final-installer.bat)",
+ "Bash(npm install)",
+ "Bash(./快速启动-ERP客户端.bat)",
+ "Bash(npm install:*)",
+ "Bash(curl:*)",
+ "Bash(rg:*)",
+ "Bash(timeout:*)",
+ "Bash(del \"C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\erp_client_sb\\src\\main\\resources\\static\\js\\table-scroll-fix.js\")",
+ "mcp__context7-mcp__resolve-library-id",
+ "WebSearch",
+ "Bash(findstr:*)",
+ "Bash(move:*)",
+ "Bash(jar:*)",
+ "Bash(cat:*)",
+ "Bash(taskkill:*)",
+ "Bash(del \"C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\erp_client_sb\\src\\main\\java\\com\\tashow\\erp\\test\\SeleniumBrowserTest.java\")",
+ "Bash(del \"C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\erp_client_sb\\src\\main\\java\\com\\tashow\\erp\\service\\RakutenCacheService.java\")",
+ "Bash(del \"C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\erp_client_sb\\src\\main\\java\\com\\tashow\\erp\\service\\CacheService.java\")",
+ "Bash(git checkout:*)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\erp_client_sb\\src\\main\\resources\\static\\html/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\ruoyi-ui\\src\\views\\monitor\\key/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\ruoyi-admin\\src\\main\\java\\com\\ruoyi\\web\\controller\\monitor/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\ruoyi-system\\src\\main\\java\\com\\ruoyi\\system\\domain/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\ruoyi-system\\src\\main\\resources\\mapper\\system/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\erp_client_sb\\src\\main\\java\\com\\tashow\\erp\\controller/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\ruoyi-admin\\src\\main\\java\\com\\ruoyi\\web\\service/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\erp_client_sb/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\erp_client_sb\\src\\main\\java\\com\\tashow\\erp\\service/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\erp_client_sb/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\erp_client_sb\\src\\main\\java\\com\\tashow\\erp\\service\\impl/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\erp_client_sb/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\erp_client_sb\\src\\main\\resources\\static\\js/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\erp_client_sb\\src\\main\\resources\\static\\js/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\erp_client_sb\\src\\main\\resources\\static\\js/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\erp_client_sb\\src\\main\\resources\\static\\js/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\erp_client_sb\\src\\main\\resources\\static\\js/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\erp_client_sb\\src\\main\\resources\\static\\js/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\erp_client_sb\\src\\main\\resources\\static\\js/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\erp_client_sb\\src\\main\\resources\\static\\js/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\erp_client_sb\\src\\main\\resources\\static\\js/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\erp_client_sb\\src\\main\\resources\\static\\js/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\erp_client_sb\\src\\main\\resources\\static\\js/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\erp_client_sb\\src\\main\\resources\\static\\html/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\erp_client_sb\\src\\main\\resources\\static\\js/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\erp_client_sb\\src\\main\\resources/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\ruoyi-admin\\src\\main\\java\\com\\ruoyi\\web\\controller\\monitor/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\ruoyi-admin\\src\\main\\java\\com\\ruoyi\\web\\service\\impl/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\ruoyi-admin\\src\\main\\java\\com\\ruoyi\\web\\service\\impl/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\ruoyi-admin\\src\\main\\java\\com\\ruoyi\\web\\service\\impl/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\ruoyi-admin\\src\\main\\java\\com\\ruoyi\\web\\service\\impl/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\ruoyi-admin\\src\\main\\java\\com\\ruoyi\\web\\service\\impl/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\erp_client_sb\\src\\main\\resources\\static\\js/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\erp_client_sb\\src\\main\\resources\\static\\js/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\erp_client_sb\\src\\main\\resources\\static\\js/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\erp_client_sb\\src\\main\\resources\\static\\js/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\erp_client_sb\\src\\main\\resources\\static\\js/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\erp_client_sb\\src\\main\\resources\\static\\js/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\erp_client_sb\\src\\main\\resources\\static\\html/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\erp_client_sb\\src\\main\\resources\\static\\html/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\erp_client_sb\\src\\main\\resources\\static\\html/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\erp_client_sb\\src\\main\\resources\\static\\js/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\erp_client_sb\\src\\main\\resources\\static\\js/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\erp_client_sb\\src\\main\\resources\\static\\html/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\erp_client_sb\\src\\main\\resources\\static\\html/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\erp_client_sb\\src\\main\\resources\\static\\js/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\ruoyi-admin\\src\\main\\java\\com\\ruoyi\\web\\service/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\ruoyi-admin\\src\\main\\java\\com\\ruoyi\\web\\service/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\ruoyi-ui/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\erp_client_sb/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\ruoyi-admin\\src\\main\\java\\com\\ruoyi\\web\\service\\impl/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\ruoyi-admin\\src\\main\\java\\com\\ruoyi\\web\\service\\impl/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\erp_client_sb\\src\\main\\java\\com\\tashow\\erp\\service\\impl/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\ruoyi-admin/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\ruoyi-common\\src\\main\\java\\com\\ruoyi\\common\\core\\redis/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\ruoyi-admin/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\ruoyi-admin\\src\\main\\java\\com\\ruoyi\\web\\service\\impl/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\ruoyi-ui\\src\\views/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\erp_client_sb\\src\\main\\resources\\static\\js/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\erp_client_sb\\src\\main\\resources\\static\\html/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\erp_client_sb\\src\\main\\resources\\static\\html/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\erp_client_sb\\src\\main\\resources\\static\\html/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\ruoyi-admin\\src\\main\\java\\com\\ruoyi\\web\\controller\\monitor/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\ruoyi-admin\\src\\main\\java\\com\\ruoyi\\web\\service/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\ruoyi-admin\\src\\main\\java\\com\\ruoyi\\web\\service\\impl/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\ruoyi-ui\\src\\views/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\ruoyi-admin\\src\\main\\java\\com\\ruoyi\\web\\service\\impl/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\erp_client_sb\\src\\main\\java\\com\\tashow\\erp\\service\\impl/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\erp_client_sb\\src\\main\\resources\\static\\js/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\erp_client_sb/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\erp_client_sb/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\erp_client_sb\\src\\main\\resources\\static\\js/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\erp_client_sb\\src\\main\\resources\\static\\js/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\erp_client_sb\\src\\main\\resources\\static\\html/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\erp_client_sb\\src\\main\\resources\\static\\html/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\erp_client_sb\\src\\main\\resources\\static\\js/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\ruoyi-ui\\src\\views/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\ruoyi-admin\\src\\main\\java\\com\\ruoyi\\web\\controller\\monitor/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\ruoyi-system\\src\\main\\java\\com\\ruoyi\\system\\domain/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\ruoyi-admin\\src\\main\\java\\com\\ruoyi\\web\\service\\impl/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\erp_client_sb/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\erp_client_sb\\src\\main\\java\\com\\tashow\\erp\\controller/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\erp_client_sb/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\erp_client_sb\\src\\main\\java\\com\\tashow\\erp/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\ruoyi-system\\src\\main\\resources\\mapper\\system/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\erp_client_sb\\src\\main\\java\\com\\tashow\\erp\\util/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\ruoyi-admin\\src\\main\\java\\com\\ruoyi\\web\\controller\\monitor/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\erp_client_sb\\src\\main\\java\\com\\tashow\\erp/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\ruoyi-admin\\src\\main\\java\\com\\ruoyi\\web\\service\\impl/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\ruoyi-admin\\src\\main\\java\\com\\ruoyi\\web\\service\\impl/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\ruoyi-admin\\src\\main\\java\\com\\ruoyi\\web\\service\\impl/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\ruoyi-admin\\src\\main\\java\\com\\ruoyi\\web\\service\\impl/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\ruoyi-admin\\src\\main\\java\\com\\ruoyi\\web\\service\\impl/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\ruoyi-admin\\src\\main\\java\\com\\ruoyi\\web\\service\\impl/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\ruoyi-admin\\src\\main\\java\\com\\ruoyi\\web\\service\\impl/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\ruoyi-admin\\src\\main\\java\\com\\ruoyi\\web\\service\\impl/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\erp_client_sb/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\erp_client_sb/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\erp_client_sb\\src\\main\\java\\com\\tashow\\erp\\utils/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\erp_client_sb\\src\\main\\java\\com\\tashow\\erp\\service\\impl/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\ruoyi-ui\\src\\views/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\ruoyi-ui\\src\\views/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\erp_client_sb/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\erp_client_sb/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\erp_client_sb/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\erp_client_sb/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\erp_client_sb\\src\\main\\java\\com\\tashow\\erp\\utils/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\erp_client_sb\\src\\main\\java\\com\\tashow\\erp\\service\\impl/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\erp_client_sb\\src\\main\\java\\com\\tashow\\erp\\service\\impl/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\erp_client_sb\\src\\main\\resources/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\erp_client_sb/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\erp_client_sb\\src\\main\\resources\\static\\js/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\erp_client_sb/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\ruoyi-admin\\src\\main\\java\\com\\ruoyi\\web\\controller\\monitor/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\ruoyi-admin\\src\\main\\java\\com\\ruoyi\\web\\service\\impl/**)",
+ "Read(/C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\ruoyi-admin\\src\\main\\java\\com\\ruoyi\\web\\service\\impl/**)",
+ "Read(//c/Users/ZiJIe/Desktop/MongooCrawler-feature-monitor/**)",
+ "Bash(wmic process where:*)",
+ "Bash(del:*)",
+ "Bash(ren:*)",
+ "Bash(find:*)",
+ "Bash(netstat:*)",
+ "Bash(mysql:*)",
+ "Bash(rd:*)",
+ "Bash(git clean:*)",
+ "Bash(tasklist:*)",
+ "Bash(./fix-electron.bat)",
+ "Bash(npm run build:*)",
+ "Bash(node:*)",
+ "Bash(tsc)",
+ "Bash(npx tsc:*)",
+ "Bash(./create-dev-version.bat)",
+ "Bash(./prepare-backend.bat)",
+ "Bash(./start-erp.bat)",
+ "Bash(npm config set:*)",
+ "Bash(set ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/)",
+ "Bash(npm cache clean:*)",
+ "Bash(npx vite:*)",
+ "Bash(./dev-ruoyi-erp.bat)",
+ "Bash(./start-desktop-app.bat)",
+ "Bash(cnpm install:*)",
+ "Bash(cnpm uninstall:*)"
+ ],
+ "deny": [],
+ "ask": [],
+ "additionalDirectories": [
+ "C:\\c\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue",
+ "C:\\Users\\ZiJIe\\Desktop\\wox",
+ "C:\\c\\Users\\ZiJIe"
+ ]
+ },
+ "dangerouslySkipPermissions": true
+}
\ No newline at end of file
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
new file mode 100644
index 0000000..e6b8496
--- /dev/null
+++ b/.idea/compiler.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/encodings.xml b/.idea/encodings.xml
new file mode 100644
index 0000000..b950b6e
--- /dev/null
+++ b/.idea/encodings.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml
new file mode 100644
index 0000000..fc70dbe
--- /dev/null
+++ b/.idea/jarRepositories.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..e069031
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..8306744
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
new file mode 100644
index 0000000..854dc68
--- /dev/null
+++ b/.idea/workspace.xml
@@ -0,0 +1,111 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {
+ "customColor": "",
+ "associatedIndex": 0
+}
+
+
+
+
+
+ {
+ "keyToString": {
+ "ModuleVcsDetector.initialDetectionPerformed": "true",
+ "RunOnceActivity.ShowReadmeOnStart": "true",
+ "RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager": "true",
+ "RunOnceActivity.git.unshallow": "true",
+ "git-widget-placeholder": "master",
+ "node.js.detected.package.eslint": "true",
+ "node.js.detected.package.tslint": "true",
+ "node.js.selected.package.eslint": "(autodetect)",
+ "node.js.selected.package.tslint": "(autodetect)",
+ "nodejs_package_manager_path": "npm",
+ "vue.rearranger.settings.migration": "true"
+ }
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1758509443816
+
+
+ 1758509443816
+
+
+
+
+
+ 1758511833782
+
+
+
+ 1758511833782
+
+
+
+ 1758512348322
+
+
+
+ 1758512348322
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 0000000..74f7728
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1,211 @@
+
+---
+
+# CLAUDE.md
+
+This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
+
+## 项目概述
+
+这是一个基于 **RuoYi-Vue 3.9.0** 的企业管理系统,集成了跨境电商 ERP 功能。项目包含:
+
+* **RuoYi-Vue 核心**:基于 Spring Boot 2.5.15 后端和 Vue.js 2.6.12 前端的企业管理平台
+* **ERP 客户端 (erp_client_sb)**:独立的跨境电商 ERP 桌面应用 (JavaFX + Spring Boot 3.5.4)
+* **客户端监控扩展**:实时监控、数据报表、API 集成等自定义功能
+
+## 项目架构
+
+### 主项目模块结构 (Maven 多模块)
+
+```
+ruoyi-admin/ # 主应用入口,Web 控制器层
+ruoyi-framework/ # 核心框架配置 (Spring Security, Redis, MyBatis)
+ruoyi-system/ # 系统核心模块 (实体类、Mapper、Service)
+ruoyi-common/ # 公共工具类和常量定义
+ruoyi-quartz/ # 定时任务管理模块
+ruoyi-generator/ # 代码生成器模块
+ruoyi-ui/ # Vue.js 前端应用
+erp_client_sb/ # 独立的跨境电商 ERP 客户端
+```
+
+### 技术栈详情
+
+**后端 (Spring Boot 2.5.15)**
+- Java 17 + Maven 3.11.0
+- Spring Security 5.7.12 + JWT 0.9.1 认证
+- MyBatis + PageHelper 1.4.7 分页
+- MySQL + Redis 缓存
+- Druid 1.2.23 连接池
+- Swagger 3.0.0 API 文档
+- FastJSON 2.0.57 JSON 处理
+
+**前端 (Vue.js 2.6.12)**
+- Element UI 2.15.14 组件库
+- Vue Router 3.4.9 + Vuex 3.6.0
+- Axios 0.28.1 HTTP 客户端
+- ECharts 5.4.0 图表库
+- Webpack 构建 (vue-cli-service 4.4.6)
+
+**ERP 客户端 (JavaFX + Spring Boot 3.5.4)**
+- OpenJFX 17.0.1 桌面 UI
+- SQLite 3.42.0 本地数据库 + JPA/Hibernate
+- WebMagic 1.0.3 + Selenium 4.23.0 网页爬取
+- HutoolUtils 5.8.36 工具库
+- 七牛云 7.12.1 存储服务
+
+## 常用开发命令
+
+### 后端开发 (Maven)
+
+```bash
+# 启动主应用 (推荐)
+cd ruoyi-admin && mvn spring-boot:run
+
+# 从根目录启动
+mvn spring-boot:run -pl ruoyi-admin
+
+# 打包部署
+mvn clean package -DskipTests
+
+# 运行测试
+mvn test
+
+# 编译项目
+mvn clean compile
+```
+
+### 前端开发 (npm)
+
+```bash
+cd ruoyi-ui
+
+# 安装依赖 (建议使用国内镜像)
+npm install --registry=https://registry.npmmirror.com
+
+# 启动开发服务器 (端口 80)
+npm run dev
+
+# 生产环境构建
+npm run build:prod
+
+# 测试环境构建
+npm run build:stage
+```
+
+### ERP 客户端开发
+
+```bash
+cd erp_client_sb
+
+# 启动 JavaFX 应用
+mvn spring-boot:run
+# 或者
+mvn javafx:run
+
+# 打包可执行 JAR
+mvn clean package
+```
+
+## 核心配置与架构要点
+
+### Maven 依赖管理
+
+- 使用阿里云 Maven 镜像 (maven.aliyun.com)
+- 父 POM 统一管理版本号和依赖
+- 安全版本覆盖:Tomcat 9.0.106, Logback 1.2.13, Spring Framework 5.3.39
+
+### 前端开发配置
+
+- **开发服务器**:端口 80,支持热重载
+- **代理配置**:`vue.config.js` 中配置后端 API 代理 (`http://8.138.23.49:8080`)
+- **构建优化**:Gzip 压缩、代码分割、Element UI 单独打包
+
+### 数据库与缓存
+
+- **MySQL**:主数据库,通过 Druid 连接池管理
+- **Redis**:会话存储、缓存、分布式锁
+- **SQLite**:ERP 客户端本地数据存储
+
+### 安全与认证
+
+- **JWT 无状态认证**:支持多终端
+- **RBAC 权限模型**:角色-菜单-按钮权限
+- **跨域配置**:支持前后端分离部署
+- **XSS 防护**:输入过滤和输出编码
+
+## 开发工作流
+
+### 新功能开发标准流程
+
+1. **后端开发**:
+ - 在 `ruoyi-system/src/main/java/com/ruoyi/system/domain/` 创建实体类
+ - 在 `ruoyi-system/src/main/java/com/ruoyi/system/mapper/` 创建 Mapper 接口
+ - 在 `ruoyi-system/src/main/resources/mapper/system/` 创建 MyBatis XML
+ - 在 `ruoyi-system/src/main/java/com/ruoyi/system/service/` 实现业务逻辑
+ - 在 `ruoyi-admin/src/main/java/com/ruoyi/web/controller/` 创建 REST 控制器
+
+2. **前端开发**:
+ - 在 `ruoyi-ui/src/api/` 创建 API 服务模块
+ - 在 `ruoyi-ui/src/views/` 创建页面组件
+ - 更新路由配置和菜单权限
+
+3. **代码生成器**:
+ - 访问 `/tool/gen` 快速生成 CRUD 代码
+ - 支持 Java、Vue、SQL 代码自动生成
+
+### 客户端集成开发
+
+ERP 客户端提供的核心功能:
+- 多平台数据采集 (Amazon, Rakuten, Shopee, 1688)
+- 实时状态监控与错误上报
+- 与主系统的 API 集成 (`/monitor/client/api/**`)
+
+## 重要配置文件
+
+### 后端配置
+- **主配置**:`ruoyi-admin/src/main/resources/application.yml`
+- **数据源配置**:`ruoyi-admin/src/main/resources/application-druid.yml`
+- **Maven 配置**:根目录 `pom.xml` (父 POM)
+
+### 前端配置
+- **构建配置**:`ruoyi-ui/vue.config.js`
+- **包管理**:`ruoyi-ui/package.json`
+
+### ERP 客户端配置
+- **应用配置**:`erp_client_sb/src/main/resources/application.yml`
+- **Maven 配置**:`erp_client_sb/pom.xml`
+
+## 系统监控与工具
+
+### 内置监控功能
+- 系统性能监控:`/monitor/server`
+- Redis 缓存监控:缓存信息查询和命令统计
+- Druid 连接池监控:SQL 性能分析
+- 在线用户监控:当前活跃用户状态
+- 操作日志:系统操作记录和异常日志
+
+### API 文档
+- Swagger UI:`/swagger-ui/index.html`
+- 自动生成的接口文档
+
+## 部署和环境
+
+### 开发环境要求
+- **Java**:JDK 17
+- **Node.js**:>= 8.9
+- **npm**:>= 3.0.0
+- **MySQL**:5.7+ 或 8.0+
+- **Redis**:6.0+
+
+### 常见问题排查
+- **端口冲突**:前端默认 80 端口,可在 `vue.config.js` 修改
+- **跨域问题**:检查 `vue.config.js` 代理配置和后端 CORS 设置
+- **Maven 依赖**:已配置阿里云镜像加速
+- **JavaFX 运行**:确保 JDK 包含 JavaFX 模块或单独安装 OpenJFX
+---
+
+⚠️ **额外要求**:回答时必须使用中文。
+💡 **操作提示**:在每次修改代码前,我会先向您说明修改的思路和方案,请您确认同意后再进行代码更改。
+
+
+---
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..998475c
--- /dev/null
+++ b/README.md
@@ -0,0 +1,95 @@
+
+
+
+RuoYi v3.9.0
+基于SpringBoot+Vue前后端分离的Java快速开发框架
+
+
+
+
+
+
+## 平台简介
+
+若依是一套全部开源的快速开发平台,毫无保留给个人及企业免费使用。
+
+* 前端采用Vue、Element UI。
+* 后端采用Spring Boot、Spring Security、Redis & Jwt。
+* 权限认证使用Jwt,支持多终端认证系统。
+* 支持加载动态权限菜单,多方式轻松权限控制。
+* 高效率开发,使用代码生成器可以一键生成前后端代码。
+* 提供了技术栈([Vue3](https://v3.cn.vuejs.org) [Element Plus](https://element-plus.org/zh-CN) [Vite](https://cn.vitejs.dev))版本[RuoYi-Vue3](https://gitcode.com/yangzongzhuan/RuoYi-Vue3),保持同步更新。
+* 提供了单应用版本[RuoYi-Vue-fast](https://gitcode.com/yangzongzhuan/RuoYi-Vue-fast),Oracle版本[RuoYi-Vue-Oracle](https://gitcode.com/yangzongzhuan/RuoYi-Vue-Oracle),保持同步更新。
+* 不分离版本,请移步[RuoYi](https://gitee.com/y_project/RuoYi),微服务版本,请移步[RuoYi-Cloud](https://gitee.com/y_project/RuoYi-Cloud)
+* 阿里云折扣场:[点我进入](http://aly.ruoyi.vip),腾讯云秒杀场:[点我进入](http://txy.ruoyi.vip)
+
+## 内置功能
+
+1. 用户管理:用户是系统操作者,该功能主要完成系统用户配置。
+2. 部门管理:配置系统组织机构(公司、部门、小组),树结构展现支持数据权限。
+3. 岗位管理:配置系统用户所属担任职务。
+4. 菜单管理:配置系统菜单,操作权限,按钮权限标识等。
+5. 角色管理:角色菜单权限分配、设置角色按机构进行数据范围权限划分。
+6. 字典管理:对系统中经常使用的一些较为固定的数据进行维护。
+7. 参数管理:对系统动态配置常用参数。
+8. 通知公告:系统通知公告信息发布维护。
+9. 操作日志:系统正常操作日志记录和查询;系统异常信息日志记录和查询。
+10. 登录日志:系统登录日志记录查询包含登录异常。
+11. 在线用户:当前系统中活跃用户状态监控。
+12. 定时任务:在线(添加、修改、删除)任务调度包含执行结果日志。
+13. 代码生成:前后端代码的生成(java、html、xml、sql)支持CRUD下载 。
+14. 系统接口:根据业务代码自动生成相关的api接口文档。
+15. 服务监控:监视当前系统CPU、内存、磁盘、堆栈等相关信息。
+16. 缓存监控:对系统的缓存信息查询,命令统计等。
+17. 在线构建器:拖动表单元素生成相应的HTML代码。
+18. 连接池监视:监视当前系统数据库连接池状态,可进行分析SQL找出系统性能瓶颈。
+
+## 在线体验
+
+- admin/admin123
+- 陆陆续续收到一些打赏,为了更好的体验已用于演示服务器升级。谢谢各位小伙伴。
+
+演示地址:http://vue.ruoyi.vip
+文档地址:http://doc.ruoyi.vip
+
+## 演示图
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+## 若依前后端分离交流群
+
+QQ群: [](https://jq.qq.com/?_wv=1027&k=5bVB1og) [](https://jq.qq.com/?_wv=1027&k=5eiA4DH) [](https://jq.qq.com/?_wv=1027&k=5AxMKlC) [](https://jq.qq.com/?_wv=1027&k=51G72yr) [](https://jq.qq.com/?_wv=1027&k=VvjN2nvu) [](https://jq.qq.com/?_wv=1027&k=5vYAqA05) [](https://jq.qq.com/?_wv=1027&k=kOIINEb5) [](https://jq.qq.com/?_wv=1027&k=UKtX5jhs) [](https://jq.qq.com/?_wv=1027&k=EI9an8lJ) [](https://jq.qq.com/?_wv=1027&k=SWCtLnMz) [](https://jq.qq.com/?_wv=1027&k=96Dkdq0k) [](https://jq.qq.com/?_wv=1027&k=0fsNiYZt) [](https://jq.qq.com/?_wv=1027&k=7xw4xUG1) [](https://jq.qq.com/?_wv=1027&k=eCx8eyoJ) [](https://jq.qq.com/?_wv=1027&k=SpyH2875) [](https://jq.qq.com/?_wv=1027&k=tKEt51dz) [](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=0vBbSb0ztbBgVtn3kJS-Q4HUNYwip89G&authKey=8irq5PhutrZmWIvsUsklBxhj57l%2F1nOZqjzigkXZVoZE451GG4JHPOqW7AW6cf0T&noverify=0&group_code=143961921) [](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=ZFAPAbp09S2ltvwrJzp7wGlbopsc0rwi&authKey=HB2cxpxP2yspk%2Bo3WKTBfktRCccVkU26cgi5B16u0KcAYrVu7sBaE7XSEqmMdFQp&noverify=0&group_code=174951577) [](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=Fn2aF5IHpwsy8j6VlalNJK6qbwFLFHat&authKey=uyIT%2B97x2AXj3odyXpsSpVaPMC%2Bidw0LxG5MAtEqlrcBcWJUA%2FeS43rsF1Tg7IRJ&noverify=0&group_code=161281055) [](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=XIzkm_mV2xTsUtFxo63bmicYoDBA6Ifm&authKey=dDW%2F4qsmw3x9govoZY9w%2FoWAoC4wbHqGal%2BbqLzoS6VBarU8EBptIgPKN%2FviyC8j&noverify=0&group_code=138988063) [](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=DkugnCg68PevlycJSKSwjhFqfIgrWWwR&authKey=pR1Pa5lPIeGF%2FFtIk6d%2FGB5qFi0EdvyErtpQXULzo03zbhopBHLWcuqdpwY241R%2F&noverify=0&group_code=151450850) [](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=F58bgRa-Dp-rsQJThiJqIYv8t4-lWfXh&authKey=UmUs4CVG5OPA1whvsa4uSespOvyd8%2FAr9olEGaWAfdLmfKQk%2FVBp2YU3u2xXXt76&noverify=0&group_code=224622315) [](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=Nxb2EQ5qozWa218Wbs7zgBnjLSNk_tVT&authKey=obBKXj6SBKgrFTJZx0AqQnIYbNOvBB2kmgwWvGhzxR67RoRr84%2Bus5OadzMcdJl5&noverify=0&group_code=287842588) [](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=numtK1M_I4eVd2Gvg8qtbuL8JgX42qNh&authKey=giV9XWMaFZTY%2FqPlmWbkB9g3fi0Ev5CwEtT9Tgei0oUlFFCQLDp4ozWRiVIzubIm&noverify=0&group_code=187944233) [](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=G6r5KGCaa3pqdbUSXNIgYloyb8e0_L0D&authKey=4w8tF1eGW7%2FedWn%2FHAypQksdrML%2BDHolQSx7094Agm7Luakj9EbfPnSTxSi2T1LQ&noverify=0&group_code=228578329) [](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=GsOo-OLz53J8y_9TPoO6XXSGNRTgbFxA&authKey=R7Uy%2Feq%2BZsoKNqHvRKhiXpypW7DAogoWapOawUGHokJSBIBIre2%2FoiAZeZBSLuBc&noverify=0&group_code=191164766) 点击按钮入群。
\ No newline at end of file
diff --git a/data/device.id b/data/device.id
new file mode 100644
index 0000000..69e8f3d
--- /dev/null
+++ b/data/device.id
@@ -0,0 +1 @@
+DEVICE-C705AA3904F84D998D03B5CD83EEBBD7
\ No newline at end of file
diff --git a/data/erp-cache.db b/data/erp-cache.db
new file mode 100644
index 0000000..3cb31f8
Binary files /dev/null and b/data/erp-cache.db differ
diff --git a/data/erp-cache.db-shm b/data/erp-cache.db-shm
new file mode 100644
index 0000000..ba00241
Binary files /dev/null and b/data/erp-cache.db-shm differ
diff --git a/data/erp-cache.db-wal b/data/erp-cache.db-wal
new file mode 100644
index 0000000..75aaf18
Binary files /dev/null and b/data/erp-cache.db-wal differ
diff --git a/data/jwt_rsa_private.pem b/data/jwt_rsa_private.pem
new file mode 100644
index 0000000..c021c8a
--- /dev/null
+++ b/data/jwt_rsa_private.pem
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCMgaMZlZOiAK7e
+FWjhWuyIg+8XBIrF9/kZYGtX+3tn53T+sCPBjAIukouyP41H5g6IZDeTP1WXsNuQ
+kVIvbZwRJKTSO7EiqE1T3V9jPzTXOXvq0SPnjaMhmUKxkmgRSs9DBZUqrElZ5mzw
+1ua/9ISlwCLqFRrVL5Nzc9+TO8IIRjjPBPYssYCDpz4NpdQb4OQYhaTRrj1++wo4
+N+LuS+wKb0Ps8SH1+udYaUWR6NteR6hhj0+0AVK0cyqsbHYQE4z+gAJr+Aicd1ZX
+pON8jsFiSNdTgGIVKCb8haVkgkXwe1inBwhyVcWk8xaz2aKa+TsNI03WfBi8DY39
++WeisqnrAgMBAAECggEABJA3TF7rxxCvnT3jxKHv2bUzQDOhEDHwEK9tfROJXAQL
+7DOrTZ9u+LVAvT7MJ2Ak67AZj/o4HO+dCfJ2UV0FexcOFVfj9mSx8j3X2cDVRgIz
+cJpvSJd0i2RPYrYHFDyyQ5J8WED1NurBcgcAwo49+qYlXCXoU7EyYEcMpVsE/8C/
+xvW0GShTzXOpO5emQOkPixxNW8i6VHte23igUULn29eyePB+pvRdEuuKC+bEZ9cJ
+ZXJ0p/yS2sNUxvBAmdVZTTs88E3gu/ARRJgyHE//Ax+fHmlSpPGeJlEPivntu0So
+vrJan9uIA3mS7eE1i7M+JoZjIgwpHKfG07AipRmklQKBgQDEQAGgpMo3AtRMmYFi
+PP3Sb5p3cETekWXJBijlhE69XBZg/VBSpNrtmD4iXuYbBb3d140nIvESowpBN3gn
+uY6nNqJWSzhKzQhU8ienJS8PZPeeRE2mMI8sRxl+VnhMraa/E8k/hzqP0RCo+cfC
+uu1be7AJ0+W6WB0d9g6zSj99HQKBgQC3SOV/4i1KXybuaphJJrZF5cuMoD5cE5Ck
+vXTTc9MSkUtwLf5PiJGRzRWNd5amS0FMb3IuC8YfEqZ8k+vDDVbgigqDeIKEpJSQ
+3K8A/F/094cyMyqrQ2aiUPg6Da57QH+Q0Ab2S7n8RstZC6h28sYxgG1acuL2jqY7
+S7HlVAZ8pwKBgQC3UqMykT1kjfwLYgn+3sKsZRyCHhn3XxMZ6esiG6oCMZemGnuB
++AWalPDV4phI/eAS71woBvfzVOIrccmIMkoT4XFb8wAuv8DcuShZdt6zHrpA2cU/
+TXUxA2nJHrVZy41MSQthkM0fs0hA0LPOMBexsaUMSSj8HXt1lXi9+sm78QKBgAsE
+0udRTa++8LQ8rFMZhLPHEOmvaJBYjMWarj9YI0Rmf8aKvVNCvp2pWrZajjAJLi/O
+M2sZQhv0HxY2PmJHlwWAxwkIYbBfxJ7A5bSFd69egj4+XT5WmwD/JS04TVkTk5e9
+Ke38t323M9pynPoptkibk/dwGL0B7nR6JIPI/WrZAoGBALYitr4UK/H8I/XvYaUJ
+q0A93fIa9ACVHshZEAwY7o1PIZu9bLScGilkbkvdATwgkfrBZXFCDIAFu2Ta/yMF
+NEFXG1szFg0aDzEN0pm8o2dndQ9dKTPswytvR5DphgCmxhuJDm9Qwwjc1UQuWePp
+Q3mDFK0eVcWWdA03UbY2TEeK
+-----END PRIVATE KEY-----
diff --git a/data/jwt_rsa_public.pem b/data/jwt_rsa_public.pem
new file mode 100644
index 0000000..0523743
--- /dev/null
+++ b/data/jwt_rsa_public.pem
@@ -0,0 +1,9 @@
+-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjIGjGZWTogCu3hVo4Vrs
+iIPvFwSKxff5GWBrV/t7Z+d0/rAjwYwCLpKLsj+NR+YOiGQ3kz9Vl7DbkJFSL22c
+ESSk0juxIqhNU91fYz801zl76tEj542jIZlCsZJoEUrPQwWVKqxJWeZs8Nbmv/SE
+pcAi6hUa1S+Tc3PfkzvCCEY4zwT2LLGAg6c+DaXUG+DkGIWk0a49fvsKODfi7kvs
+Cm9D7PEh9frnWGlFkejbXkeoYY9PtAFStHMqrGx2EBOM/oACa/gInHdWV6TjfI7B
+YkjXU4BiFSgm/IWlZIJF8HtYpwcIclXFpPMWs9mimvk7DSNN1nwYvA2N/flnorKp
+6wIDAQAB
+-----END PUBLIC KEY-----
diff --git a/electron-vue-template/LICENSE b/electron-vue-template/LICENSE
new file mode 100644
index 0000000..d72f1d2
--- /dev/null
+++ b/electron-vue-template/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2021 Deluze
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/electron-vue-template/README.md b/electron-vue-template/README.md
new file mode 100644
index 0000000..a71b4cf
--- /dev/null
+++ b/electron-vue-template/README.md
@@ -0,0 +1,77 @@
+
+
+# Electron Vue Template
+
+
+
+A simple starter template for a **Vue3** + **Electron** TypeScript based application, including **ViteJS** and **Electron Builder**.
+
+
+## About
+
+This template utilizes [ViteJS](https://vitejs.dev) for building and serving your (Vue powered) front-end process, it provides Hot Reloads (HMR) to make development fast and easy ⚡
+
+Building the Electron (main) process is done with [Electron Builder](https://www.electron.build/), which makes your application easily distributable and supports cross-platform compilation 😎
+
+## Getting started
+
+Click the green **Use this template** button on top of the repository, and clone your own newly created repository.
+
+**Or..**
+
+Clone this repository: `git clone git@github.com:Deluze/electron-vue-template.git`
+
+
+### Install dependencies ⏬
+
+```bash
+npm install
+```
+
+### Start developing ⚒️
+
+```bash
+npm run dev
+```
+
+## Additional Commands
+
+```bash
+npm run dev # starts application with hot reload
+npm run build # builds application, distributable files can be found in "dist" folder
+
+# OR
+
+npm run build:win # uses windows as build target
+npm run build:mac # uses mac as build target
+npm run build:linux # uses linux as build target
+```
+
+Optional configuration options can be found in the [Electron Builder CLI docs](https://www.electron.build/cli.html).
+## Project Structure
+
+```bash
+- scripts/ # all the scripts used to build or serve your application, change as you like.
+- src/
+ - main/ # Main thread (Electron application source)
+ - renderer/ # Renderer thread (VueJS application source)
+```
+
+## Using static files
+
+If you have any files that you want to copy over to the app directory after installation, you will need to add those files in your `src/main/static` directory.
+
+Files in said directory are only accessible to the `main` process, similar to `src/renderer/assets` only being accessible to the `renderer` process. Besides that, the concept is the same as to what you're used to in your other front-end projects.
+
+#### Referencing static files from your main process
+
+```ts
+/* Assumes src/main/static/myFile.txt exists */
+
+import {app} from 'electron';
+import {join} from 'path';
+import {readFileSync} from 'fs';
+
+const path = join(app.getAppPath(), 'static', 'myFile.txt');
+const buffer = readFileSync(path);
+```
diff --git a/electron-vue-template/electron-builder.json b/electron-vue-template/electron-builder.json
new file mode 100644
index 0000000..5313c62
--- /dev/null
+++ b/electron-vue-template/electron-builder.json
@@ -0,0 +1,39 @@
+{
+ "appId": "com.electron.app",
+ "directories": {
+ "output": "dist"
+ },
+
+ "nsis": {
+ "oneClick": false,
+ "perMachine": false,
+ "allowToChangeInstallationDirectory": true,
+ "shortcutName": "Electron App"
+ },
+ "win": {
+ "target": "nsis"
+ },
+ "linux": {
+ "target": ["snap"]
+ },
+ "files": [
+ {
+ "from": "build/main",
+ "to": "main",
+ "filter": ["**/*"]
+ },
+ {
+ "from": "build/renderer",
+ "to": "renderer",
+ "filter": ["**/*"]
+ },
+ {
+ "from": "src/main/static",
+ "to": "static",
+ "filter": ["**/*"]
+ },
+ "!build",
+ "!dist",
+ "!scripts"
+ ]
+}
diff --git a/electron-vue-template/package-lock.json b/electron-vue-template/package-lock.json
new file mode 100644
index 0000000..959e329
--- /dev/null
+++ b/electron-vue-template/package-lock.json
@@ -0,0 +1,5847 @@
+{
+ "name": "electron-vue-template",
+ "version": "0.1.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "electron-vue-template",
+ "version": "0.1.0",
+ "dependencies": {
+ "vue": "^3.3.8"
+ },
+ "devDependencies": {
+ "@vitejs/plugin-vue": "^4.4.1",
+ "chalk": "^4.1.2",
+ "chokidar": "^3.5.3",
+ "electron": "^32.1.2",
+ "electron-builder": "^25.1.6",
+ "typescript": "^5.2.2",
+ "vite": "^4.5.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.23.3",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.3.tgz",
+ "integrity": "sha512-uVsWNvlVsIninV2prNz/3lHCb+5CJ+e+IUBfbjToAHODtfGYLfCFuY4AU7TskI+dAKk+njsPiBjq1gKTvZOBaw==",
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@develar/schema-utils": {
+ "version": "2.6.5",
+ "resolved": "https://registry.npmjs.org/@develar/schema-utils/-/schema-utils-2.6.5.tgz",
+ "integrity": "sha512-0cp4PsWQ/9avqTVMCtZ+GirikIA36ikvjtHweU4/j8yLtgObI0+JUPhYFScgwlteveGB1rt3Cm8UhN04XayDig==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ajv": "^6.12.0",
+ "ajv-keywords": "^3.4.1"
+ },
+ "engines": {
+ "node": ">= 8.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ }
+ },
+ "node_modules/@electron/asar": {
+ "version": "3.2.13",
+ "resolved": "https://registry.npmjs.org/@electron/asar/-/asar-3.2.13.tgz",
+ "integrity": "sha512-pY5z2qQSwbFzJsBdgfJIzXf5ElHTVMutC2dxh0FD60njknMu3n1NnTABOcQwbb5/v5soqE79m9UjaJryBf3epg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/glob": "^7.1.0",
+ "commander": "^5.0.0",
+ "glob": "^7.1.6",
+ "minimatch": "^3.0.4"
+ },
+ "bin": {
+ "asar": "bin/asar.js"
+ },
+ "engines": {
+ "node": ">=10.12.0"
+ }
+ },
+ "node_modules/@electron/asar/node_modules/brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/@electron/asar/node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/@electron/get": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/@electron/get/-/get-2.0.2.tgz",
+ "integrity": "sha512-eFZVFoRXb3GFGd7Ak7W4+6jBl9wBtiZ4AaYOse97ej6mKj5tkyO0dUnUChs1IhJZtx1BENo4/p4WUTXpi6vT+g==",
+ "dev": true,
+ "dependencies": {
+ "debug": "^4.1.1",
+ "env-paths": "^2.2.0",
+ "fs-extra": "^8.1.0",
+ "got": "^11.8.5",
+ "progress": "^2.0.3",
+ "semver": "^6.2.0",
+ "sumchecker": "^3.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "global-agent": "^3.0.0"
+ }
+ },
+ "node_modules/@electron/notarize": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/@electron/notarize/-/notarize-2.5.0.tgz",
+ "integrity": "sha512-jNT8nwH1f9X5GEITXaQ8IF/KdskvIkOFfB2CvwumsveVidzpSc+mvhhTMdAGSYF3O+Nq49lJ7y+ssODRXu06+A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.1.1",
+ "fs-extra": "^9.0.1",
+ "promise-retry": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/@electron/notarize/node_modules/fs-extra": {
+ "version": "9.1.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz",
+ "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "at-least-node": "^1.0.0",
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@electron/notarize/node_modules/jsonfile": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
+ "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "universalify": "^2.0.0"
+ },
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/@electron/notarize/node_modules/universalify": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
+ "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/@electron/osx-sign": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/@electron/osx-sign/-/osx-sign-1.3.1.tgz",
+ "integrity": "sha512-BAfviURMHpmb1Yb50YbCxnOY0wfwaLXH5KJ4+80zS0gUkzDX3ec23naTlEqKsN+PwYn+a1cCzM7BJ4Wcd3sGzw==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "compare-version": "^0.1.2",
+ "debug": "^4.3.4",
+ "fs-extra": "^10.0.0",
+ "isbinaryfile": "^4.0.8",
+ "minimist": "^1.2.6",
+ "plist": "^3.0.5"
+ },
+ "bin": {
+ "electron-osx-flat": "bin/electron-osx-flat.js",
+ "electron-osx-sign": "bin/electron-osx-sign.js"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
+ "node_modules/@electron/osx-sign/node_modules/fs-extra": {
+ "version": "10.1.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
+ "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@electron/osx-sign/node_modules/isbinaryfile": {
+ "version": "4.0.10",
+ "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz",
+ "integrity": "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/gjtorikian/"
+ }
+ },
+ "node_modules/@electron/osx-sign/node_modules/jsonfile": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
+ "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "universalify": "^2.0.0"
+ },
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/@electron/osx-sign/node_modules/universalify": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
+ "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/@electron/rebuild": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/@electron/rebuild/-/rebuild-3.6.0.tgz",
+ "integrity": "sha512-zF4x3QupRU3uNGaP5X1wjpmcjfw1H87kyqZ00Tc3HvriV+4gmOGuvQjGNkrJuXdsApssdNyVwLsy+TaeTGGcVw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@malept/cross-spawn-promise": "^2.0.0",
+ "chalk": "^4.0.0",
+ "debug": "^4.1.1",
+ "detect-libc": "^2.0.1",
+ "fs-extra": "^10.0.0",
+ "got": "^11.7.0",
+ "node-abi": "^3.45.0",
+ "node-api-version": "^0.2.0",
+ "node-gyp": "^9.0.0",
+ "ora": "^5.1.0",
+ "read-binary-file-arch": "^1.0.6",
+ "semver": "^7.3.5",
+ "tar": "^6.0.5",
+ "yargs": "^17.0.1"
+ },
+ "bin": {
+ "electron-rebuild": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=12.13.0"
+ }
+ },
+ "node_modules/@electron/rebuild/node_modules/fs-extra": {
+ "version": "10.1.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
+ "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@electron/rebuild/node_modules/jsonfile": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
+ "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "universalify": "^2.0.0"
+ },
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/@electron/rebuild/node_modules/semver": {
+ "version": "7.6.3",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
+ "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@electron/rebuild/node_modules/universalify": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
+ "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/@electron/universal": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/@electron/universal/-/universal-2.0.1.tgz",
+ "integrity": "sha512-fKpv9kg4SPmt+hY7SVBnIYULE9QJl8L3sCfcBsnqbJwwBwAeTLokJ9TRt9y7bK0JAzIW2y78TVVjvnQEms/yyA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@electron/asar": "^3.2.7",
+ "@malept/cross-spawn-promise": "^2.0.0",
+ "debug": "^4.3.1",
+ "dir-compare": "^4.2.0",
+ "fs-extra": "^11.1.1",
+ "minimatch": "^9.0.3",
+ "plist": "^3.1.0"
+ },
+ "engines": {
+ "node": ">=16.4"
+ }
+ },
+ "node_modules/@electron/universal/node_modules/fs-extra": {
+ "version": "11.2.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz",
+ "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=14.14"
+ }
+ },
+ "node_modules/@electron/universal/node_modules/jsonfile": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
+ "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "universalify": "^2.0.0"
+ },
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/@electron/universal/node_modules/minimatch": {
+ "version": "9.0.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
+ "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/@electron/universal/node_modules/universalify": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
+ "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz",
+ "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz",
+ "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz",
+ "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz",
+ "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz",
+ "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz",
+ "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz",
+ "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz",
+ "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz",
+ "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz",
+ "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz",
+ "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz",
+ "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz",
+ "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz",
+ "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz",
+ "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz",
+ "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz",
+ "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz",
+ "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz",
+ "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz",
+ "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz",
+ "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz",
+ "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@gar/promisify": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz",
+ "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@isaacs/cliui": {
+ "version": "8.0.2",
+ "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
+ "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "string-width": "^5.1.2",
+ "string-width-cjs": "npm:string-width@^4.2.0",
+ "strip-ansi": "^7.0.1",
+ "strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
+ "wrap-ansi": "^8.1.0",
+ "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@isaacs/cliui/node_modules/ansi-regex": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
+ "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
+ "node_modules/@isaacs/cliui/node_modules/ansi-styles": {
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
+ "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/@isaacs/cliui/node_modules/emoji-regex": {
+ "version": "9.2.2",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
+ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@isaacs/cliui/node_modules/string-width": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
+ "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "eastasianwidth": "^0.2.0",
+ "emoji-regex": "^9.2.2",
+ "strip-ansi": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@isaacs/cliui/node_modules/strip-ansi": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
+ "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
+ "node_modules/@isaacs/cliui/node_modules/wrap-ansi": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
+ "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^6.1.0",
+ "string-width": "^5.0.1",
+ "strip-ansi": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.4.15",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
+ "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg=="
+ },
+ "node_modules/@malept/cross-spawn-promise": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@malept/cross-spawn-promise/-/cross-spawn-promise-2.0.0.tgz",
+ "integrity": "sha512-1DpKU0Z5ThltBwjNySMC14g0CkbyhCaz9FkhxqNsZI6uAPJXFS8cMXlBKo26FJ8ZuW6S9GCMcR9IO5k2X5/9Fg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/malept"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/subscription/pkg/npm-.malept-cross-spawn-promise?utm_medium=referral&utm_source=npm_fund"
+ }
+ ],
+ "license": "Apache-2.0",
+ "dependencies": {
+ "cross-spawn": "^7.0.1"
+ },
+ "engines": {
+ "node": ">= 12.13.0"
+ }
+ },
+ "node_modules/@malept/flatpak-bundler": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/@malept/flatpak-bundler/-/flatpak-bundler-0.4.0.tgz",
+ "integrity": "sha512-9QOtNffcOF/c1seMCDnjckb3R9WHcG34tky+FHpNKKCW0wc/scYLwMtO+ptyGUfMW0/b/n4qRiALlaFHc9Oj7Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.1.1",
+ "fs-extra": "^9.0.0",
+ "lodash": "^4.17.15",
+ "tmp-promise": "^3.0.2"
+ },
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/@malept/flatpak-bundler/node_modules/fs-extra": {
+ "version": "9.1.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz",
+ "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "at-least-node": "^1.0.0",
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@malept/flatpak-bundler/node_modules/jsonfile": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
+ "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "universalify": "^2.0.0"
+ },
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/@malept/flatpak-bundler/node_modules/universalify": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
+ "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/@npmcli/fs": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-2.1.2.tgz",
+ "integrity": "sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "@gar/promisify": "^1.1.3",
+ "semver": "^7.3.5"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@npmcli/fs/node_modules/semver": {
+ "version": "7.6.3",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
+ "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@npmcli/move-file": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-2.0.1.tgz",
+ "integrity": "sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ==",
+ "deprecated": "This functionality has been moved to @npmcli/fs",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mkdirp": "^1.0.4",
+ "rimraf": "^3.0.2"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@pkgjs/parseargs": {
+ "version": "0.11.0",
+ "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
+ "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/@sindresorhus/is": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz",
+ "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/is?sponsor=1"
+ }
+ },
+ "node_modules/@szmarczak/http-timer": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz",
+ "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==",
+ "dev": true,
+ "dependencies": {
+ "defer-to-connect": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@tootallnate/once": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz",
+ "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@types/cacheable-request": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz",
+ "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==",
+ "dev": true,
+ "dependencies": {
+ "@types/http-cache-semantics": "*",
+ "@types/keyv": "^3.1.4",
+ "@types/node": "*",
+ "@types/responselike": "^1.0.0"
+ }
+ },
+ "node_modules/@types/debug": {
+ "version": "4.1.12",
+ "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz",
+ "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/ms": "*"
+ }
+ },
+ "node_modules/@types/fs-extra": {
+ "version": "9.0.13",
+ "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz",
+ "integrity": "sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/glob": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz",
+ "integrity": "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/minimatch": "*",
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/http-cache-semantics": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz",
+ "integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==",
+ "dev": true
+ },
+ "node_modules/@types/keyv": {
+ "version": "3.1.4",
+ "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz",
+ "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==",
+ "dev": true,
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/minimatch": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz",
+ "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/ms": {
+ "version": "0.7.34",
+ "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz",
+ "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/node": {
+ "version": "20.16.10",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.10.tgz",
+ "integrity": "sha512-vQUKgWTjEIRFCvK6CyriPH3MZYiYlNy0fKiEYHWbcoWLEgs4opurGGKlebrTLqdSMIbXImH6XExNiIyNUv3WpA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~6.19.2"
+ }
+ },
+ "node_modules/@types/plist": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/@types/plist/-/plist-3.0.5.tgz",
+ "integrity": "sha512-E6OCaRmAe4WDmWNsL/9RMqdkkzDCY1etutkflWk4c+AcjDU07Pcz1fQwTX0TQz+Pxqn9i4L1TU3UFpjnrcDgxA==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@types/node": "*",
+ "xmlbuilder": ">=11.0.1"
+ }
+ },
+ "node_modules/@types/responselike": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz",
+ "integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==",
+ "dev": true,
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/verror": {
+ "version": "1.10.10",
+ "resolved": "https://registry.npmjs.org/@types/verror/-/verror-1.10.10.tgz",
+ "integrity": "sha512-l4MM0Jppn18hb9xmM6wwD1uTdShpf9Pn80aXTStnK1C94gtPvJcV2FrDmbOQUAQfJ1cKZHktkQUDwEqaAKXMMg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/@types/yauzl": {
+ "version": "2.10.0",
+ "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.0.tgz",
+ "integrity": "sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==",
+ "dev": true,
+ "optional": true,
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@vitejs/plugin-vue": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-4.4.1.tgz",
+ "integrity": "sha512-HCQG8VDFDM7YDAdcj5QI5DvUi+r6xvo9LgvYdk7LSkUNwdpempdB5horkMSZsbdey9Ywsf5aaU8kEPw9M5kREA==",
+ "dev": true,
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "peerDependencies": {
+ "vite": "^4.0.0",
+ "vue": "^3.2.25"
+ }
+ },
+ "node_modules/@vue/compiler-core": {
+ "version": "3.3.8",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.3.8.tgz",
+ "integrity": "sha512-hN/NNBUECw8SusQvDSqqcVv6gWq8L6iAktUR0UF3vGu2OhzRqcOiAno0FmBJWwxhYEXRlQJT5XnoKsVq1WZx4g==",
+ "dependencies": {
+ "@babel/parser": "^7.23.0",
+ "@vue/shared": "3.3.8",
+ "estree-walker": "^2.0.2",
+ "source-map-js": "^1.0.2"
+ }
+ },
+ "node_modules/@vue/compiler-dom": {
+ "version": "3.3.8",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.3.8.tgz",
+ "integrity": "sha512-+PPtv+p/nWDd0AvJu3w8HS0RIm/C6VGBIRe24b9hSyNWOAPEUosFZ5diwawwP8ip5sJ8n0Pe87TNNNHnvjs0FQ==",
+ "dependencies": {
+ "@vue/compiler-core": "3.3.8",
+ "@vue/shared": "3.3.8"
+ }
+ },
+ "node_modules/@vue/compiler-sfc": {
+ "version": "3.3.8",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.3.8.tgz",
+ "integrity": "sha512-WMzbUrlTjfYF8joyT84HfwwXo+8WPALuPxhy+BZ6R4Aafls+jDBnSz8PDz60uFhuqFbl3HxRfxvDzrUf3THwpA==",
+ "dependencies": {
+ "@babel/parser": "^7.23.0",
+ "@vue/compiler-core": "3.3.8",
+ "@vue/compiler-dom": "3.3.8",
+ "@vue/compiler-ssr": "3.3.8",
+ "@vue/reactivity-transform": "3.3.8",
+ "@vue/shared": "3.3.8",
+ "estree-walker": "^2.0.2",
+ "magic-string": "^0.30.5",
+ "postcss": "^8.4.31",
+ "source-map-js": "^1.0.2"
+ }
+ },
+ "node_modules/@vue/compiler-ssr": {
+ "version": "3.3.8",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.3.8.tgz",
+ "integrity": "sha512-hXCqQL/15kMVDBuoBYpUnSYT8doDNwsjvm3jTefnXr+ytn294ySnT8NlsFHmTgKNjwpuFy7XVV8yTeLtNl/P6w==",
+ "dependencies": {
+ "@vue/compiler-dom": "3.3.8",
+ "@vue/shared": "3.3.8"
+ }
+ },
+ "node_modules/@vue/reactivity": {
+ "version": "3.3.8",
+ "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.3.8.tgz",
+ "integrity": "sha512-ctLWitmFBu6mtddPyOKpHg8+5ahouoTCRtmAHZAXmolDtuZXfjL2T3OJ6DL6ezBPQB1SmMnpzjiWjCiMYmpIuw==",
+ "dependencies": {
+ "@vue/shared": "3.3.8"
+ }
+ },
+ "node_modules/@vue/reactivity-transform": {
+ "version": "3.3.8",
+ "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.3.8.tgz",
+ "integrity": "sha512-49CvBzmZNtcHua0XJ7GdGifM8GOXoUMOX4dD40Y5DxI3R8OUhMlvf2nvgUAcPxaXiV5MQQ1Nwy09ADpnLQUqRw==",
+ "dependencies": {
+ "@babel/parser": "^7.23.0",
+ "@vue/compiler-core": "3.3.8",
+ "@vue/shared": "3.3.8",
+ "estree-walker": "^2.0.2",
+ "magic-string": "^0.30.5"
+ }
+ },
+ "node_modules/@vue/runtime-core": {
+ "version": "3.3.8",
+ "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.3.8.tgz",
+ "integrity": "sha512-qurzOlb6q26KWQ/8IShHkMDOuJkQnQcTIp1sdP4I9MbCf9FJeGVRXJFr2mF+6bXh/3Zjr9TDgURXrsCr9bfjUw==",
+ "dependencies": {
+ "@vue/reactivity": "3.3.8",
+ "@vue/shared": "3.3.8"
+ }
+ },
+ "node_modules/@vue/runtime-dom": {
+ "version": "3.3.8",
+ "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.3.8.tgz",
+ "integrity": "sha512-Noy5yM5UIf9UeFoowBVgghyGGPIDPy1Qlqt0yVsUdAVbqI8eeMSsTqBtauaEoT2UFXUk5S64aWVNJN4MJ2vRdA==",
+ "dependencies": {
+ "@vue/runtime-core": "3.3.8",
+ "@vue/shared": "3.3.8",
+ "csstype": "^3.1.2"
+ }
+ },
+ "node_modules/@vue/server-renderer": {
+ "version": "3.3.8",
+ "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.3.8.tgz",
+ "integrity": "sha512-zVCUw7RFskvPuNlPn/8xISbrf0zTWsTSdYTsUTN1ERGGZGVnRxM2QZ3x1OR32+vwkkCm0IW6HmJ49IsPm7ilLg==",
+ "dependencies": {
+ "@vue/compiler-ssr": "3.3.8",
+ "@vue/shared": "3.3.8"
+ },
+ "peerDependencies": {
+ "vue": "3.3.8"
+ }
+ },
+ "node_modules/@vue/shared": {
+ "version": "3.3.8",
+ "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.8.tgz",
+ "integrity": "sha512-8PGwybFwM4x8pcfgqEQFy70NaQxASvOC5DJwLQfpArw1UDfUXrJkdxD3BhVTMS+0Lef/TU7YO0Jvr0jJY8T+mw=="
+ },
+ "node_modules/@xmldom/xmldom": {
+ "version": "0.8.10",
+ "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz",
+ "integrity": "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/7zip-bin": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/7zip-bin/-/7zip-bin-5.2.0.tgz",
+ "integrity": "sha512-ukTPVhqG4jNzMro2qA9HSCSSVJN3aN7tlb+hfqYCt3ER0yWroeA2VR38MNrOHLQ/cVj+DaIMad0kFCtWWowh/A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/abbrev": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
+ "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/agent-base": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz",
+ "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/agentkeepalive": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz",
+ "integrity": "sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "humanize-ms": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 8.0.0"
+ }
+ },
+ "node_modules/aggregate-error": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz",
+ "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "clean-stack": "^2.0.0",
+ "indent-string": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ajv-keywords": {
+ "version": "3.5.2",
+ "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
+ "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "ajv": "^6.9.1"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/anymatch": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+ "dev": true,
+ "dependencies": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/app-builder-bin": {
+ "version": "5.0.0-alpha.10",
+ "resolved": "https://registry.npmjs.org/app-builder-bin/-/app-builder-bin-5.0.0-alpha.10.tgz",
+ "integrity": "sha512-Ev4jj3D7Bo+O0GPD2NMvJl+PGiBAfS7pUGawntBNpCbxtpncfUixqFj9z9Jme7V7s3LBGqsWZZP54fxBX3JKJw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/app-builder-lib": {
+ "version": "25.1.6",
+ "resolved": "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-25.1.6.tgz",
+ "integrity": "sha512-iui5q65skaawkTBcsaf2wCaegCWO+JoK5VaPnaNdIWXm2bq8a/g53W88FHjR535L9viLeGbuBp/iU1XZSe2DQQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@develar/schema-utils": "~2.6.5",
+ "@electron/notarize": "2.5.0",
+ "@electron/osx-sign": "1.3.1",
+ "@electron/rebuild": "3.6.0",
+ "@electron/universal": "2.0.1",
+ "@malept/flatpak-bundler": "^0.4.0",
+ "@types/fs-extra": "9.0.13",
+ "async-exit-hook": "^2.0.1",
+ "bluebird-lst": "^1.0.9",
+ "builder-util": "25.1.6",
+ "builder-util-runtime": "9.2.9",
+ "chromium-pickle-js": "^0.2.0",
+ "config-file-ts": "0.2.8-rc1",
+ "debug": "^4.3.4",
+ "dotenv": "^16.4.5",
+ "dotenv-expand": "^11.0.6",
+ "ejs": "^3.1.8",
+ "electron-publish": "25.1.6",
+ "form-data": "^4.0.0",
+ "fs-extra": "^10.1.0",
+ "hosted-git-info": "^4.1.0",
+ "is-ci": "^3.0.0",
+ "isbinaryfile": "^5.0.0",
+ "js-yaml": "^4.1.0",
+ "json5": "^2.2.3",
+ "lazy-val": "^1.0.5",
+ "minimatch": "^10.0.0",
+ "resedit": "^1.7.0",
+ "sanitize-filename": "^1.6.3",
+ "semver": "^7.3.8",
+ "tar": "^6.1.12",
+ "temp-file": "^3.4.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "dmg-builder": "25.1.6",
+ "electron-builder-squirrel-windows": "25.1.6"
+ }
+ },
+ "node_modules/app-builder-lib/node_modules/fs-extra": {
+ "version": "10.1.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
+ "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/app-builder-lib/node_modules/jsonfile": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
+ "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "universalify": "^2.0.0"
+ },
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/app-builder-lib/node_modules/semver": {
+ "version": "7.6.3",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
+ "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/app-builder-lib/node_modules/universalify": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
+ "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/aproba": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz",
+ "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/archiver": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.2.tgz",
+ "integrity": "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "archiver-utils": "^2.1.0",
+ "async": "^3.2.4",
+ "buffer-crc32": "^0.2.1",
+ "readable-stream": "^3.6.0",
+ "readdir-glob": "^1.1.2",
+ "tar-stream": "^2.2.0",
+ "zip-stream": "^4.1.0"
+ },
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/archiver-utils": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz",
+ "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "glob": "^7.1.4",
+ "graceful-fs": "^4.2.0",
+ "lazystream": "^1.0.0",
+ "lodash.defaults": "^4.2.0",
+ "lodash.difference": "^4.5.0",
+ "lodash.flatten": "^4.4.0",
+ "lodash.isplainobject": "^4.0.6",
+ "lodash.union": "^4.6.0",
+ "normalize-path": "^3.0.0",
+ "readable-stream": "^2.0.0"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/archiver-utils/node_modules/readable-stream": {
+ "version": "2.3.8",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
+ "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
+ }
+ },
+ "node_modules/archiver-utils/node_modules/safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/archiver-utils/node_modules/string_decoder": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "safe-buffer": "~5.1.0"
+ }
+ },
+ "node_modules/are-we-there-yet": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz",
+ "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==",
+ "deprecated": "This package is no longer supported.",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "delegates": "^1.0.0",
+ "readable-stream": "^3.6.0"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true,
+ "license": "Python-2.0"
+ },
+ "node_modules/assert-plus": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+ "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/astral-regex": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz",
+ "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/async": {
+ "version": "3.2.6",
+ "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
+ "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/async-exit-hook": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/async-exit-hook/-/async-exit-hook-2.0.1.tgz",
+ "integrity": "sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/at-least-node": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz",
+ "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">= 4.0.0"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/base64-js": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
+ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/binary-extensions": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
+ "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/bl": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
+ "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "buffer": "^5.5.0",
+ "inherits": "^2.0.4",
+ "readable-stream": "^3.4.0"
+ }
+ },
+ "node_modules/bluebird": {
+ "version": "3.7.2",
+ "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
+ "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/bluebird-lst": {
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/bluebird-lst/-/bluebird-lst-1.0.9.tgz",
+ "integrity": "sha512-7B1Rtx82hjnSD4PGLAjVWeYH3tHAcVUmChh85a3lltKQm6FresXh9ErQo6oAv6CqxttczC3/kEg8SY5NluPuUw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "bluebird": "^3.5.5"
+ }
+ },
+ "node_modules/boolean": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz",
+ "integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==",
+ "dev": true,
+ "optional": true
+ },
+ "node_modules/brace-expansion": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+ "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fill-range": "^7.1.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/buffer": {
+ "version": "5.7.1",
+ "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
+ "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "base64-js": "^1.3.1",
+ "ieee754": "^1.1.13"
+ }
+ },
+ "node_modules/buffer-crc32": {
+ "version": "0.2.13",
+ "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
+ "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==",
+ "dev": true,
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/buffer-from": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
+ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/builder-util": {
+ "version": "25.1.6",
+ "resolved": "https://registry.npmjs.org/builder-util/-/builder-util-25.1.6.tgz",
+ "integrity": "sha512-BOMVCO/CLYaIwK2uh7BKJUmRy9fYhn674c4Cauxc/lSZ7CyLLNkUMZJUFCPd3OqD1FIQO06MZ/u7akKtVyXlJw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/debug": "^4.1.6",
+ "7zip-bin": "~5.2.0",
+ "app-builder-bin": "5.0.0-alpha.10",
+ "bluebird-lst": "^1.0.9",
+ "builder-util-runtime": "9.2.9",
+ "chalk": "^4.1.2",
+ "cross-spawn": "^7.0.3",
+ "debug": "^4.3.4",
+ "fs-extra": "^10.1.0",
+ "http-proxy-agent": "^7.0.0",
+ "https-proxy-agent": "^7.0.0",
+ "is-ci": "^3.0.0",
+ "js-yaml": "^4.1.0",
+ "source-map-support": "^0.5.19",
+ "stat-mode": "^1.0.0",
+ "temp-file": "^3.4.0"
+ }
+ },
+ "node_modules/builder-util-runtime": {
+ "version": "9.2.9",
+ "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.2.9.tgz",
+ "integrity": "sha512-DWeHdrRFVvNnVyD4+vMztRpXegOGaQHodsAjyhstTbUNBIjebxM1ahxokQL+T1v8vpW8SY7aJ5is/zILH82lAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.3.4",
+ "sax": "^1.2.4"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
+ "node_modules/builder-util/node_modules/fs-extra": {
+ "version": "10.1.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
+ "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/builder-util/node_modules/jsonfile": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
+ "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "universalify": "^2.0.0"
+ },
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/builder-util/node_modules/universalify": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
+ "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/cacache": {
+ "version": "16.1.3",
+ "resolved": "https://registry.npmjs.org/cacache/-/cacache-16.1.3.tgz",
+ "integrity": "sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "@npmcli/fs": "^2.1.0",
+ "@npmcli/move-file": "^2.0.0",
+ "chownr": "^2.0.0",
+ "fs-minipass": "^2.1.0",
+ "glob": "^8.0.1",
+ "infer-owner": "^1.0.4",
+ "lru-cache": "^7.7.1",
+ "minipass": "^3.1.6",
+ "minipass-collect": "^1.0.2",
+ "minipass-flush": "^1.0.5",
+ "minipass-pipeline": "^1.2.4",
+ "mkdirp": "^1.0.4",
+ "p-map": "^4.0.0",
+ "promise-inflight": "^1.0.1",
+ "rimraf": "^3.0.2",
+ "ssri": "^9.0.0",
+ "tar": "^6.1.11",
+ "unique-filename": "^2.0.0"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/cacache/node_modules/glob": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz",
+ "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==",
+ "deprecated": "Glob versions prior to v9 are no longer supported",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^5.0.1",
+ "once": "^1.3.0"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/cacache/node_modules/lru-cache": {
+ "version": "7.18.3",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz",
+ "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/cacache/node_modules/minimatch": {
+ "version": "5.1.6",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
+ "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/cacheable-lookup": {
+ "version": "5.0.4",
+ "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz",
+ "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10.6.0"
+ }
+ },
+ "node_modules/cacheable-request": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.2.tgz",
+ "integrity": "sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew==",
+ "dev": true,
+ "dependencies": {
+ "clone-response": "^1.0.2",
+ "get-stream": "^5.1.0",
+ "http-cache-semantics": "^4.0.0",
+ "keyv": "^4.0.0",
+ "lowercase-keys": "^2.0.0",
+ "normalize-url": "^6.0.1",
+ "responselike": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/chokidar": {
+ "version": "3.5.3",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
+ "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://paulmillr.com/funding/"
+ }
+ ],
+ "dependencies": {
+ "anymatch": "~3.1.2",
+ "braces": "~3.0.2",
+ "glob-parent": "~5.1.2",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.6.0"
+ },
+ "engines": {
+ "node": ">= 8.10.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/chownr": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
+ "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/chromium-pickle-js": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/chromium-pickle-js/-/chromium-pickle-js-0.2.0.tgz",
+ "integrity": "sha512-1R5Fho+jBq0DDydt+/vHWj5KJNJCKdARKOCwZUen84I5BreWoLqRLANH1U87eJy1tiASPtMnGqJJq0ZsLoRPOw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/ci-info": {
+ "version": "3.9.0",
+ "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz",
+ "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/sibiraj-s"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/clean-stack": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz",
+ "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/cli-cursor": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz",
+ "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "restore-cursor": "^3.1.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cli-spinners": {
+ "version": "2.9.2",
+ "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz",
+ "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/cli-truncate": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz",
+ "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "slice-ansi": "^3.0.0",
+ "string-width": "^4.2.0"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/cliui": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
+ "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.1",
+ "wrap-ansi": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/clone": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz",
+ "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/clone-response": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz",
+ "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==",
+ "dev": true,
+ "dependencies": {
+ "mimic-response": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "node_modules/color-support": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz",
+ "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "color-support": "bin.js"
+ }
+ },
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/commander": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz",
+ "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/compare-version": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/compare-version/-/compare-version-0.1.2.tgz",
+ "integrity": "sha512-pJDh5/4wrEnXX/VWRZvruAGHkzKdr46z11OlTPN+VrATlWWhSKewNCJ1futCO5C7eJB3nPMFZA1LeYtcFboZ2A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/compress-commons": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.2.tgz",
+ "integrity": "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "buffer-crc32": "^0.2.13",
+ "crc32-stream": "^4.0.2",
+ "normalize-path": "^3.0.0",
+ "readable-stream": "^3.6.0"
+ },
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/config-file-ts": {
+ "version": "0.2.8-rc1",
+ "resolved": "https://registry.npmjs.org/config-file-ts/-/config-file-ts-0.2.8-rc1.tgz",
+ "integrity": "sha512-GtNECbVI82bT4RiDIzBSVuTKoSHufnU7Ce7/42bkWZJZFLjmDF2WBpVsvRkhKCfKBnTBb3qZrBwPpFBU/Myvhg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "glob": "^10.3.12",
+ "typescript": "^5.4.3"
+ }
+ },
+ "node_modules/config-file-ts/node_modules/glob": {
+ "version": "10.4.5",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
+ "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "foreground-child": "^3.1.0",
+ "jackspeak": "^3.1.2",
+ "minimatch": "^9.0.4",
+ "minipass": "^7.1.2",
+ "package-json-from-dist": "^1.0.0",
+ "path-scurry": "^1.11.1"
+ },
+ "bin": {
+ "glob": "dist/esm/bin.mjs"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/config-file-ts/node_modules/minimatch": {
+ "version": "9.0.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
+ "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/config-file-ts/node_modules/minipass": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
+ "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ }
+ },
+ "node_modules/console-control-strings": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
+ "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/core-util-is": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
+ "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/crc": {
+ "version": "3.8.0",
+ "resolved": "https://registry.npmjs.org/crc/-/crc-3.8.0.tgz",
+ "integrity": "sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "buffer": "^5.1.0"
+ }
+ },
+ "node_modules/crc-32": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz",
+ "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "peer": true,
+ "bin": {
+ "crc32": "bin/crc32.njs"
+ },
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/crc32-stream": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.3.tgz",
+ "integrity": "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "crc-32": "^1.2.0",
+ "readable-stream": "^3.4.0"
+ },
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
+ "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/csstype": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz",
+ "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ=="
+ },
+ "node_modules/debug": {
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+ "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+ "dev": true,
+ "dependencies": {
+ "ms": "2.1.2"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/decompress-response": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
+ "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
+ "dev": true,
+ "dependencies": {
+ "mimic-response": "^3.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/decompress-response/node_modules/mimic-response": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
+ "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/defaults": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz",
+ "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "clone": "^1.0.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/defer-to-connect": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz",
+ "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/define-properties": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz",
+ "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==",
+ "dev": true,
+ "optional": true,
+ "dependencies": {
+ "has-property-descriptors": "^1.0.0",
+ "object-keys": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/delegates": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
+ "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/detect-libc": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz",
+ "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/detect-node": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz",
+ "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==",
+ "dev": true,
+ "optional": true
+ },
+ "node_modules/dir-compare": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/dir-compare/-/dir-compare-4.2.0.tgz",
+ "integrity": "sha512-2xMCmOoMrdQIPHdsTawECdNPwlVFB9zGcz3kuhmBO6U3oU+UQjsue0i8ayLKpgBcm+hcXPMVSGUN9d+pvJ6+VQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "minimatch": "^3.0.5",
+ "p-limit": "^3.1.0 "
+ }
+ },
+ "node_modules/dir-compare/node_modules/brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/dir-compare/node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/dmg-builder": {
+ "version": "25.1.6",
+ "resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-25.1.6.tgz",
+ "integrity": "sha512-TeKjLNKBu6ODewl36Q1FH0+Bv/Rnb76x1vzPJ0Xw1T/YlAByYl1G+1PaKoEpVEDisrLRSrM9a0k3vDj53xQi9w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "app-builder-lib": "25.1.6",
+ "builder-util": "25.1.6",
+ "builder-util-runtime": "9.2.9",
+ "fs-extra": "^10.1.0",
+ "iconv-lite": "^0.6.2",
+ "js-yaml": "^4.1.0"
+ },
+ "optionalDependencies": {
+ "dmg-license": "^1.0.11"
+ }
+ },
+ "node_modules/dmg-builder/node_modules/fs-extra": {
+ "version": "10.1.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
+ "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/dmg-builder/node_modules/jsonfile": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
+ "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "universalify": "^2.0.0"
+ },
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/dmg-builder/node_modules/universalify": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
+ "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/dmg-license": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/dmg-license/-/dmg-license-1.0.11.tgz",
+ "integrity": "sha512-ZdzmqwKmECOWJpqefloC5OJy1+WZBBse5+MR88z9g9Zn4VY+WYUkAyojmhzJckH5YbbZGcYIuGAkY5/Ys5OM2Q==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "dependencies": {
+ "@types/plist": "^3.0.1",
+ "@types/verror": "^1.10.3",
+ "ajv": "^6.10.0",
+ "crc": "^3.8.0",
+ "iconv-corefoundation": "^1.1.7",
+ "plist": "^3.0.4",
+ "smart-buffer": "^4.0.2",
+ "verror": "^1.10.0"
+ },
+ "bin": {
+ "dmg-license": "bin/dmg-license.js"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/dotenv": {
+ "version": "16.4.5",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz",
+ "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://dotenvx.com"
+ }
+ },
+ "node_modules/dotenv-expand": {
+ "version": "11.0.6",
+ "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-11.0.6.tgz",
+ "integrity": "sha512-8NHi73otpWsZGBSZwwknTXS5pqMOrk9+Ssrna8xCaxkzEpU9OTf9R5ArQGVw03//Zmk9MOwLPng9WwndvpAJ5g==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "dotenv": "^16.4.4"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://dotenvx.com"
+ }
+ },
+ "node_modules/eastasianwidth": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
+ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/ejs": {
+ "version": "3.1.10",
+ "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz",
+ "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "jake": "^10.8.5"
+ },
+ "bin": {
+ "ejs": "bin/cli.js"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/electron": {
+ "version": "32.1.2",
+ "resolved": "https://registry.npmjs.org/electron/-/electron-32.1.2.tgz",
+ "integrity": "sha512-CXe6doFzhmh1U7daOvUzmF6Cj8hssdYWMeEPRnRO6rB9/bbwMlWctcQ7P8NJXhLQ88/vYUJQrJvlJPh8qM0BRQ==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "dependencies": {
+ "@electron/get": "^2.0.0",
+ "@types/node": "^20.9.0",
+ "extract-zip": "^2.0.1"
+ },
+ "bin": {
+ "electron": "cli.js"
+ },
+ "engines": {
+ "node": ">= 12.20.55"
+ }
+ },
+ "node_modules/electron-builder": {
+ "version": "25.1.6",
+ "resolved": "https://registry.npmjs.org/electron-builder/-/electron-builder-25.1.6.tgz",
+ "integrity": "sha512-a+w0leNGcr57u9fiQBsVvVuXtTsg/6VpMOps1Q1TPAceHtK4kWtX2wc7X2cnJQrIME8bWbdm/YR9Swr/xF1/ng==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "app-builder-lib": "25.1.6",
+ "builder-util": "25.1.6",
+ "builder-util-runtime": "9.2.9",
+ "chalk": "^4.1.2",
+ "dmg-builder": "25.1.6",
+ "fs-extra": "^10.1.0",
+ "is-ci": "^3.0.0",
+ "lazy-val": "^1.0.5",
+ "simple-update-notifier": "2.0.0",
+ "yargs": "^17.6.2"
+ },
+ "bin": {
+ "electron-builder": "cli.js",
+ "install-app-deps": "install-app-deps.js"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/electron-builder-squirrel-windows": {
+ "version": "25.1.6",
+ "resolved": "https://registry.npmjs.org/electron-builder-squirrel-windows/-/electron-builder-squirrel-windows-25.1.6.tgz",
+ "integrity": "sha512-g50ZPzcR8mw29O/FZoZIEsU3M+wHsxHISegxmdjftbUtLLT1UoHgPQkNIssqfpboy8ORL7PJopoumkZoMkXzFg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "app-builder-lib": "25.1.6",
+ "archiver": "^5.3.1",
+ "builder-util": "25.1.6",
+ "fs-extra": "^10.1.0"
+ }
+ },
+ "node_modules/electron-builder-squirrel-windows/node_modules/fs-extra": {
+ "version": "10.1.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
+ "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/electron-builder-squirrel-windows/node_modules/jsonfile": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
+ "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "universalify": "^2.0.0"
+ },
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/electron-builder-squirrel-windows/node_modules/universalify": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
+ "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/electron-builder/node_modules/fs-extra": {
+ "version": "10.1.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
+ "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
+ "dev": true,
+ "dependencies": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/electron-builder/node_modules/jsonfile": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
+ "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
+ "dev": true,
+ "dependencies": {
+ "universalify": "^2.0.0"
+ },
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/electron-builder/node_modules/universalify": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
+ "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==",
+ "dev": true,
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/electron-publish": {
+ "version": "25.1.6",
+ "resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-25.1.6.tgz",
+ "integrity": "sha512-Hiy/tVyu57cbkyxS0GrzEnxm7+B/9IeFcBTia2QQBCTg10zvHURdAj7Sk7XHKys8kKLr1tVNThuG165uTnNJdQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/fs-extra": "^9.0.11",
+ "builder-util": "25.1.6",
+ "builder-util-runtime": "9.2.9",
+ "chalk": "^4.1.2",
+ "fs-extra": "^10.1.0",
+ "lazy-val": "^1.0.5",
+ "mime": "^2.5.2"
+ }
+ },
+ "node_modules/electron-publish/node_modules/fs-extra": {
+ "version": "10.1.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
+ "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/electron-publish/node_modules/jsonfile": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
+ "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "universalify": "^2.0.0"
+ },
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/electron-publish/node_modules/universalify": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
+ "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/encoding": {
+ "version": "0.1.13",
+ "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
+ "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "iconv-lite": "^0.6.2"
+ }
+ },
+ "node_modules/end-of-stream": {
+ "version": "1.4.4",
+ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
+ "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
+ "dev": true,
+ "dependencies": {
+ "once": "^1.4.0"
+ }
+ },
+ "node_modules/env-paths": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz",
+ "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/err-code": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz",
+ "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/es6-error": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz",
+ "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==",
+ "dev": true,
+ "optional": true
+ },
+ "node_modules/esbuild": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz",
+ "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==",
+ "dev": true,
+ "hasInstallScript": true,
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/android-arm": "0.18.20",
+ "@esbuild/android-arm64": "0.18.20",
+ "@esbuild/android-x64": "0.18.20",
+ "@esbuild/darwin-arm64": "0.18.20",
+ "@esbuild/darwin-x64": "0.18.20",
+ "@esbuild/freebsd-arm64": "0.18.20",
+ "@esbuild/freebsd-x64": "0.18.20",
+ "@esbuild/linux-arm": "0.18.20",
+ "@esbuild/linux-arm64": "0.18.20",
+ "@esbuild/linux-ia32": "0.18.20",
+ "@esbuild/linux-loong64": "0.18.20",
+ "@esbuild/linux-mips64el": "0.18.20",
+ "@esbuild/linux-ppc64": "0.18.20",
+ "@esbuild/linux-riscv64": "0.18.20",
+ "@esbuild/linux-s390x": "0.18.20",
+ "@esbuild/linux-x64": "0.18.20",
+ "@esbuild/netbsd-x64": "0.18.20",
+ "@esbuild/openbsd-x64": "0.18.20",
+ "@esbuild/sunos-x64": "0.18.20",
+ "@esbuild/win32-arm64": "0.18.20",
+ "@esbuild/win32-ia32": "0.18.20",
+ "@esbuild/win32-x64": "0.18.20"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true,
+ "optional": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/estree-walker": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
+ "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
+ },
+ "node_modules/exponential-backoff": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.1.tgz",
+ "integrity": "sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/extract-zip": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz",
+ "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==",
+ "dev": true,
+ "dependencies": {
+ "debug": "^4.1.1",
+ "get-stream": "^5.1.0",
+ "yauzl": "^2.10.0"
+ },
+ "bin": {
+ "extract-zip": "cli.js"
+ },
+ "engines": {
+ "node": ">= 10.17.0"
+ },
+ "optionalDependencies": {
+ "@types/yauzl": "^2.9.1"
+ }
+ },
+ "node_modules/extsprintf": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.4.1.tgz",
+ "integrity": "sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA==",
+ "dev": true,
+ "engines": [
+ "node >=0.6.0"
+ ],
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fd-slicer": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
+ "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==",
+ "dev": true,
+ "dependencies": {
+ "pend": "~1.2.0"
+ }
+ },
+ "node_modules/filelist": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz",
+ "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "minimatch": "^5.0.1"
+ }
+ },
+ "node_modules/filelist/node_modules/minimatch": {
+ "version": "5.1.6",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
+ "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/fill-range": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/foreground-child": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz",
+ "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "cross-spawn": "^7.0.0",
+ "signal-exit": "^4.0.1"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/foreground-child/node_modules/signal-exit": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
+ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/form-data": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
+ "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/fs-constants": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
+ "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/fs-extra": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
+ "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
+ "dev": true,
+ "dependencies": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^4.0.0",
+ "universalify": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=6 <7 || >=8"
+ }
+ },
+ "node_modules/fs-minipass": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
+ "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "minipass": "^3.0.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
+ "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+ "dev": true,
+ "hasInstallScript": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
+ "dev": true,
+ "optional": true
+ },
+ "node_modules/gauge": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz",
+ "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==",
+ "deprecated": "This package is no longer supported.",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "aproba": "^1.0.3 || ^2.0.0",
+ "color-support": "^1.1.3",
+ "console-control-strings": "^1.1.0",
+ "has-unicode": "^2.0.1",
+ "signal-exit": "^3.0.7",
+ "string-width": "^4.2.3",
+ "strip-ansi": "^6.0.1",
+ "wide-align": "^1.1.5"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/get-caller-file": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": "6.* || 8.* || >= 10.*"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz",
+ "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==",
+ "dev": true,
+ "optional": true,
+ "dependencies": {
+ "function-bind": "^1.1.1",
+ "has": "^1.0.3",
+ "has-symbols": "^1.0.3"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-stream": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
+ "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==",
+ "dev": true,
+ "dependencies": {
+ "pump": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "deprecated": "Glob versions prior to v9 are no longer supported",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/glob/node_modules/brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/glob/node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/global-agent": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz",
+ "integrity": "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==",
+ "dev": true,
+ "optional": true,
+ "dependencies": {
+ "boolean": "^3.0.1",
+ "es6-error": "^4.1.1",
+ "matcher": "^3.0.0",
+ "roarr": "^2.15.3",
+ "semver": "^7.3.2",
+ "serialize-error": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=10.0"
+ }
+ },
+ "node_modules/global-agent/node_modules/semver": {
+ "version": "7.6.3",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
+ "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
+ "dev": true,
+ "license": "ISC",
+ "optional": true,
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/globalthis": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz",
+ "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==",
+ "dev": true,
+ "optional": true,
+ "dependencies": {
+ "define-properties": "^1.1.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/got": {
+ "version": "11.8.6",
+ "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz",
+ "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==",
+ "dev": true,
+ "dependencies": {
+ "@sindresorhus/is": "^4.0.0",
+ "@szmarczak/http-timer": "^4.0.5",
+ "@types/cacheable-request": "^6.0.1",
+ "@types/responselike": "^1.0.0",
+ "cacheable-lookup": "^5.0.3",
+ "cacheable-request": "^7.0.2",
+ "decompress-response": "^6.0.0",
+ "http2-wrapper": "^1.0.0-beta.5.2",
+ "lowercase-keys": "^2.0.0",
+ "p-cancelable": "^2.0.0",
+ "responselike": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10.19.0"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/got?sponsor=1"
+ }
+ },
+ "node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+ "dev": true
+ },
+ "node_modules/has": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+ "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+ "dev": true,
+ "optional": true,
+ "dependencies": {
+ "function-bind": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/has-property-descriptors": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz",
+ "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==",
+ "dev": true,
+ "optional": true,
+ "dependencies": {
+ "get-intrinsic": "^1.1.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
+ "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
+ "dev": true,
+ "optional": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-unicode": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
+ "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/hosted-git-info": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz",
+ "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "lru-cache": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/http-cache-semantics": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz",
+ "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==",
+ "dev": true
+ },
+ "node_modules/http-proxy-agent": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
+ "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "^7.1.0",
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/http2-wrapper": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz",
+ "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==",
+ "dev": true,
+ "dependencies": {
+ "quick-lru": "^5.1.1",
+ "resolve-alpn": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=10.19.0"
+ }
+ },
+ "node_modules/https-proxy-agent": {
+ "version": "7.0.5",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz",
+ "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "^7.0.2",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/humanize-ms": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz",
+ "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.0.0"
+ }
+ },
+ "node_modules/iconv-corefoundation": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/iconv-corefoundation/-/iconv-corefoundation-1.1.7.tgz",
+ "integrity": "sha512-T10qvkw0zz4wnm560lOEg0PovVqUXuOFhhHAkixw8/sycy7TJt7v/RrkEKEQnAw2viPSJu6iAkErxnzR0g8PpQ==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "dependencies": {
+ "cli-truncate": "^2.1.0",
+ "node-addon-api": "^1.6.3"
+ },
+ "engines": {
+ "node": "^8.11.2 || >=10"
+ }
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/ieee754": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
+ "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.19"
+ }
+ },
+ "node_modules/indent-string": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
+ "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/infer-owner": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz",
+ "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
+ "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/ip-address": {
+ "version": "9.0.5",
+ "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz",
+ "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "jsbn": "1.1.0",
+ "sprintf-js": "^1.1.3"
+ },
+ "engines": {
+ "node": ">= 12"
+ }
+ },
+ "node_modules/is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "dev": true,
+ "dependencies": {
+ "binary-extensions": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-ci": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz",
+ "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ci-info": "^3.2.0"
+ },
+ "bin": {
+ "is-ci": "bin.js"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-interactive": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz",
+ "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-lambda": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz",
+ "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/is-unicode-supported": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz",
+ "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/isarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+ "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/isbinaryfile": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-5.0.2.tgz",
+ "integrity": "sha512-GvcjojwonMjWbTkfMpnVHVqXW/wKMYDfEpY94/8zy8HFMOqb/VL6oeONq9v87q4ttVlaTLnGXnJD4B5B1OTGIg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 18.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/gjtorikian/"
+ }
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/jackspeak": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
+ "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "@isaacs/cliui": "^8.0.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ },
+ "optionalDependencies": {
+ "@pkgjs/parseargs": "^0.11.0"
+ }
+ },
+ "node_modules/jake": {
+ "version": "10.9.2",
+ "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz",
+ "integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "async": "^3.2.3",
+ "chalk": "^4.0.2",
+ "filelist": "^1.0.4",
+ "minimatch": "^3.1.2"
+ },
+ "bin": {
+ "jake": "bin/cli.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/jake/node_modules/brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/jake/node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/js-yaml": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+ "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/jsbn": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz",
+ "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-buffer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
+ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
+ "dev": true
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-stringify-safe": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
+ "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==",
+ "dev": true,
+ "optional": true
+ },
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/jsonfile": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
+ "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==",
+ "dev": true,
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/keyv": {
+ "version": "4.5.2",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.2.tgz",
+ "integrity": "sha512-5MHbFaKn8cNSmVW7BYnijeAVlE4cYA/SVkifVgrh7yotnfhKmjuXpDKjrABLnT0SfHWV21P8ow07OGfRrNDg8g==",
+ "dev": true,
+ "dependencies": {
+ "json-buffer": "3.0.1"
+ }
+ },
+ "node_modules/lazy-val": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.5.tgz",
+ "integrity": "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/lazystream": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz",
+ "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "readable-stream": "^2.0.5"
+ },
+ "engines": {
+ "node": ">= 0.6.3"
+ }
+ },
+ "node_modules/lazystream/node_modules/readable-stream": {
+ "version": "2.3.8",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
+ "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
+ }
+ },
+ "node_modules/lazystream/node_modules/safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/lazystream/node_modules/string_decoder": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "safe-buffer": "~5.1.0"
+ }
+ },
+ "node_modules/lodash": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/lodash.defaults": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
+ "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/lodash.difference": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz",
+ "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/lodash.flatten": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz",
+ "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/lodash.isplainobject": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
+ "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/lodash.union": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz",
+ "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/log-symbols": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
+ "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "chalk": "^4.1.0",
+ "is-unicode-supported": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/lowercase-keys": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz",
+ "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/lru-cache": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+ "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+ "dev": true,
+ "dependencies": {
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/magic-string": {
+ "version": "0.30.5",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz",
+ "integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.4.15"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/make-fetch-happen": {
+ "version": "10.2.1",
+ "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-10.2.1.tgz",
+ "integrity": "sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "agentkeepalive": "^4.2.1",
+ "cacache": "^16.1.0",
+ "http-cache-semantics": "^4.1.0",
+ "http-proxy-agent": "^5.0.0",
+ "https-proxy-agent": "^5.0.0",
+ "is-lambda": "^1.0.1",
+ "lru-cache": "^7.7.1",
+ "minipass": "^3.1.6",
+ "minipass-collect": "^1.0.2",
+ "minipass-fetch": "^2.0.3",
+ "minipass-flush": "^1.0.5",
+ "minipass-pipeline": "^1.2.4",
+ "negotiator": "^0.6.3",
+ "promise-retry": "^2.0.1",
+ "socks-proxy-agent": "^7.0.0",
+ "ssri": "^9.0.0"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/make-fetch-happen/node_modules/agent-base": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
+ "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6.0.0"
+ }
+ },
+ "node_modules/make-fetch-happen/node_modules/http-proxy-agent": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz",
+ "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@tootallnate/once": "2",
+ "agent-base": "6",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/make-fetch-happen/node_modules/https-proxy-agent": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
+ "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "6",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/make-fetch-happen/node_modules/lru-cache": {
+ "version": "7.18.3",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz",
+ "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/matcher": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz",
+ "integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==",
+ "dev": true,
+ "optional": true,
+ "dependencies": {
+ "escape-string-regexp": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/mime": {
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz",
+ "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "mime": "cli.js"
+ },
+ "engines": {
+ "node": ">=4.0.0"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mimic-fn": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
+ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/mimic-response": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz",
+ "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "10.0.1",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz",
+ "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": "20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/minimist": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
+ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/minipass": {
+ "version": "3.3.6",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
+ "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/minipass-collect": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz",
+ "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "minipass": "^3.0.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/minipass-fetch": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-2.1.2.tgz",
+ "integrity": "sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "minipass": "^3.1.6",
+ "minipass-sized": "^1.0.3",
+ "minizlib": "^2.1.2"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ },
+ "optionalDependencies": {
+ "encoding": "^0.1.13"
+ }
+ },
+ "node_modules/minipass-flush": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz",
+ "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "minipass": "^3.0.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/minipass-pipeline": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz",
+ "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "minipass": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/minipass-sized": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz",
+ "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "minipass": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/minizlib": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz",
+ "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "minipass": "^3.0.0",
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/mkdirp": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
+ "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "mkdirp": "bin/cmd.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.7",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
+ "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/negotiator": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
+ "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/node-abi": {
+ "version": "3.68.0",
+ "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.68.0.tgz",
+ "integrity": "sha512-7vbj10trelExNjFSBm5kTvZXXa7pZyKWx9RCKIyqe6I9Ev3IzGpQoqBP3a+cOdxY+pWj6VkP28n/2wWysBHD/A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "semver": "^7.3.5"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/node-abi/node_modules/semver": {
+ "version": "7.6.3",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
+ "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/node-addon-api": {
+ "version": "1.7.2",
+ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-1.7.2.tgz",
+ "integrity": "sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/node-api-version": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/node-api-version/-/node-api-version-0.2.0.tgz",
+ "integrity": "sha512-fthTTsi8CxaBXMaBAD7ST2uylwvsnYxh2PfaScwpMhos6KlSFajXQPcM4ogNE1q2s3Lbz9GCGqeIHC+C6OZnKg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "semver": "^7.3.5"
+ }
+ },
+ "node_modules/node-api-version/node_modules/semver": {
+ "version": "7.6.3",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
+ "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/node-gyp": {
+ "version": "9.4.1",
+ "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-9.4.1.tgz",
+ "integrity": "sha512-OQkWKbjQKbGkMf/xqI1jjy3oCTgMKJac58G2+bjZb3fza6gW2YrCSdMQYaoTb70crvE//Gngr4f0AgVHmqHvBQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "env-paths": "^2.2.0",
+ "exponential-backoff": "^3.1.1",
+ "glob": "^7.1.4",
+ "graceful-fs": "^4.2.6",
+ "make-fetch-happen": "^10.0.3",
+ "nopt": "^6.0.0",
+ "npmlog": "^6.0.0",
+ "rimraf": "^3.0.2",
+ "semver": "^7.3.5",
+ "tar": "^6.1.2",
+ "which": "^2.0.2"
+ },
+ "bin": {
+ "node-gyp": "bin/node-gyp.js"
+ },
+ "engines": {
+ "node": "^12.13 || ^14.13 || >=16"
+ }
+ },
+ "node_modules/node-gyp/node_modules/semver": {
+ "version": "7.6.3",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
+ "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/nopt": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/nopt/-/nopt-6.0.0.tgz",
+ "integrity": "sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "abbrev": "^1.0.0"
+ },
+ "bin": {
+ "nopt": "bin/nopt.js"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/normalize-url": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz",
+ "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/npmlog": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz",
+ "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==",
+ "deprecated": "This package is no longer supported.",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "are-we-there-yet": "^3.0.0",
+ "console-control-strings": "^1.1.0",
+ "gauge": "^4.0.3",
+ "set-blocking": "^2.0.0"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/object-keys": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
+ "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
+ "dev": true,
+ "optional": true,
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "dev": true,
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/onetime": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
+ "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mimic-fn": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/ora": {
+ "version": "5.4.1",
+ "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz",
+ "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "bl": "^4.1.0",
+ "chalk": "^4.1.0",
+ "cli-cursor": "^3.1.0",
+ "cli-spinners": "^2.5.0",
+ "is-interactive": "^1.0.0",
+ "is-unicode-supported": "^0.1.0",
+ "log-symbols": "^4.1.0",
+ "strip-ansi": "^6.0.0",
+ "wcwidth": "^1.0.1"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-cancelable": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz",
+ "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "yocto-queue": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-map": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz",
+ "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "aggregate-error": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/package-json-from-dist": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
+ "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
+ "dev": true,
+ "license": "BlueOak-1.0.0"
+ },
+ "node_modules/path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-scurry": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
+ "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "lru-cache": "^10.2.0",
+ "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/path-scurry/node_modules/lru-cache": {
+ "version": "10.4.3",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
+ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/path-scurry/node_modules/minipass": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
+ "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ }
+ },
+ "node_modules/pe-library": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/pe-library/-/pe-library-0.4.1.tgz",
+ "integrity": "sha512-eRWB5LBz7PpDu4PUlwT0PhnQfTQJlDDdPa35urV4Osrm0t0AqQFGn+UIkU3klZvwJ8KPO3VbBFsXquA6p6kqZw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12",
+ "npm": ">=6"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/jet2jet"
+ }
+ },
+ "node_modules/pend": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
+ "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==",
+ "dev": true
+ },
+ "node_modules/picocolors": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
+ "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true,
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/plist": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/plist/-/plist-3.1.0.tgz",
+ "integrity": "sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@xmldom/xmldom": "^0.8.8",
+ "base64-js": "^1.5.1",
+ "xmlbuilder": "^15.1.1"
+ },
+ "engines": {
+ "node": ">=10.4.0"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.4.31",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
+ "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "dependencies": {
+ "nanoid": "^3.3.6",
+ "picocolors": "^1.0.0",
+ "source-map-js": "^1.0.2"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/process-nextick-args": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
+ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/progress": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
+ "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/promise-inflight": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz",
+ "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/promise-retry": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz",
+ "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "err-code": "^2.0.2",
+ "retry": "^0.12.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/pump": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
+ "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
+ "dev": true,
+ "dependencies": {
+ "end-of-stream": "^1.1.0",
+ "once": "^1.3.1"
+ }
+ },
+ "node_modules/punycode": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/quick-lru": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz",
+ "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/read-binary-file-arch": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/read-binary-file-arch/-/read-binary-file-arch-1.0.6.tgz",
+ "integrity": "sha512-BNg9EN3DD3GsDXX7Aa8O4p92sryjkmzYYgmgTAc6CA4uGLEDzFfxOxugu21akOxpcXHiEgsYkC6nPsQvLLLmEg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.3.4"
+ },
+ "bin": {
+ "read-binary-file-arch": "cli.js"
+ }
+ },
+ "node_modules/readable-stream": {
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
+ "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "inherits": "^2.0.3",
+ "string_decoder": "^1.1.1",
+ "util-deprecate": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/readdir-glob": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz",
+ "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "peer": true,
+ "dependencies": {
+ "minimatch": "^5.1.0"
+ }
+ },
+ "node_modules/readdir-glob/node_modules/minimatch": {
+ "version": "5.1.6",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
+ "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
+ "dev": true,
+ "license": "ISC",
+ "peer": true,
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/readdirp": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "dev": true,
+ "dependencies": {
+ "picomatch": "^2.2.1"
+ },
+ "engines": {
+ "node": ">=8.10.0"
+ }
+ },
+ "node_modules/require-directory": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+ "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/resedit": {
+ "version": "1.7.1",
+ "resolved": "https://registry.npmjs.org/resedit/-/resedit-1.7.1.tgz",
+ "integrity": "sha512-/FJ6/gKAXbcHtivannhecWsa43kGVFK3aHHv9Jm3x0eFiM31MoGihkAOWbm3UsvjYLRVw0zTkfARy2dI96JL1Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "pe-library": "^0.4.1"
+ },
+ "engines": {
+ "node": ">=12",
+ "npm": ">=6"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/jet2jet"
+ }
+ },
+ "node_modules/resolve-alpn": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz",
+ "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==",
+ "dev": true
+ },
+ "node_modules/responselike": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz",
+ "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==",
+ "dev": true,
+ "dependencies": {
+ "lowercase-keys": "^2.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/restore-cursor": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz",
+ "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "onetime": "^5.1.0",
+ "signal-exit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/retry": {
+ "version": "0.12.0",
+ "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz",
+ "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/rimraf": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
+ "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
+ "deprecated": "Rimraf versions prior to v4 are no longer supported",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "glob": "^7.1.3"
+ },
+ "bin": {
+ "rimraf": "bin.js"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/roarr": {
+ "version": "2.15.4",
+ "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz",
+ "integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==",
+ "dev": true,
+ "optional": true,
+ "dependencies": {
+ "boolean": "^3.0.1",
+ "detect-node": "^2.0.4",
+ "globalthis": "^1.0.1",
+ "json-stringify-safe": "^5.0.1",
+ "semver-compare": "^1.0.0",
+ "sprintf-js": "^1.1.2"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "3.29.5",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.5.tgz",
+ "integrity": "sha512-GVsDdsbJzzy4S/v3dqWPJ7EfvZJfCHiDqe80IyrF59LYuP+e6U1LJoUqeuqRbwAWoMNoXivMNeNAOf5E22VA1w==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=14.18.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/sanitize-filename": {
+ "version": "1.6.3",
+ "resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.3.tgz",
+ "integrity": "sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg==",
+ "dev": true,
+ "license": "WTFPL OR ISC",
+ "dependencies": {
+ "truncate-utf8-bytes": "^1.0.0"
+ }
+ },
+ "node_modules/sax": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz",
+ "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/semver-compare": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz",
+ "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==",
+ "dev": true,
+ "optional": true
+ },
+ "node_modules/serialize-error": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz",
+ "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==",
+ "dev": true,
+ "optional": true,
+ "dependencies": {
+ "type-fest": "^0.13.1"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/set-blocking": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
+ "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/signal-exit": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
+ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/simple-update-notifier": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz",
+ "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "semver": "^7.5.3"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/simple-update-notifier/node_modules/semver": {
+ "version": "7.6.3",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
+ "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/slice-ansi": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz",
+ "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "astral-regex": "^2.0.0",
+ "is-fullwidth-code-point": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/smart-buffer": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
+ "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6.0.0",
+ "npm": ">= 3.0.0"
+ }
+ },
+ "node_modules/socks": {
+ "version": "2.8.3",
+ "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz",
+ "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ip-address": "^9.0.5",
+ "smart-buffer": "^4.2.0"
+ },
+ "engines": {
+ "node": ">= 10.0.0",
+ "npm": ">= 3.0.0"
+ }
+ },
+ "node_modules/socks-proxy-agent": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz",
+ "integrity": "sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "^6.0.2",
+ "debug": "^4.3.3",
+ "socks": "^2.6.2"
+ },
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/socks-proxy-agent/node_modules/agent-base": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
+ "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6.0.0"
+ }
+ },
+ "node_modules/source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
+ "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/source-map-support": {
+ "version": "0.5.21",
+ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
+ "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "buffer-from": "^1.0.0",
+ "source-map": "^0.6.0"
+ }
+ },
+ "node_modules/sprintf-js": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz",
+ "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==",
+ "dev": true,
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/ssri": {
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/ssri/-/ssri-9.0.1.tgz",
+ "integrity": "sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "minipass": "^3.1.1"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/stat-mode": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/stat-mode/-/stat-mode-1.0.0.tgz",
+ "integrity": "sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/string_decoder": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
+ "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "safe-buffer": "~5.2.0"
+ }
+ },
+ "node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/string-width-cjs": {
+ "name": "string-width",
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-ansi-cjs": {
+ "name": "strip-ansi",
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/sumchecker": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz",
+ "integrity": "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==",
+ "dev": true,
+ "dependencies": {
+ "debug": "^4.1.0"
+ },
+ "engines": {
+ "node": ">= 8.0"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/tar": {
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz",
+ "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "chownr": "^2.0.0",
+ "fs-minipass": "^2.0.0",
+ "minipass": "^5.0.0",
+ "minizlib": "^2.1.1",
+ "mkdirp": "^1.0.3",
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/tar-stream": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
+ "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "bl": "^4.0.3",
+ "end-of-stream": "^1.4.1",
+ "fs-constants": "^1.0.0",
+ "inherits": "^2.0.3",
+ "readable-stream": "^3.1.1"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/tar/node_modules/minipass": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz",
+ "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/temp-file": {
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/temp-file/-/temp-file-3.4.0.tgz",
+ "integrity": "sha512-C5tjlC/HCtVUOi3KWVokd4vHVViOmGjtLwIh4MuzPo/nMYTV/p1urt3RnMz2IWXDdKEGJH3k5+KPxtqRsUYGtg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "async-exit-hook": "^2.0.1",
+ "fs-extra": "^10.0.0"
+ }
+ },
+ "node_modules/temp-file/node_modules/fs-extra": {
+ "version": "10.1.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
+ "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/temp-file/node_modules/jsonfile": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
+ "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "universalify": "^2.0.0"
+ },
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/temp-file/node_modules/universalify": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
+ "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/tmp": {
+ "version": "0.2.3",
+ "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz",
+ "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.14"
+ }
+ },
+ "node_modules/tmp-promise": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/tmp-promise/-/tmp-promise-3.0.3.tgz",
+ "integrity": "sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tmp": "^0.2.0"
+ }
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/truncate-utf8-bytes": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz",
+ "integrity": "sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==",
+ "dev": true,
+ "license": "WTFPL",
+ "dependencies": {
+ "utf8-byte-length": "^1.0.1"
+ }
+ },
+ "node_modules/type-fest": {
+ "version": "0.13.1",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz",
+ "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==",
+ "dev": true,
+ "optional": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.6.2",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz",
+ "integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/undici-types": {
+ "version": "6.19.8",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
+ "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/unique-filename": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-2.0.1.tgz",
+ "integrity": "sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "unique-slug": "^3.0.0"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/unique-slug": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-3.0.0.tgz",
+ "integrity": "sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "imurmurhash": "^0.1.4"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/universalify": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
+ "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 4.0.0"
+ }
+ },
+ "node_modules/uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "node_modules/utf8-byte-length": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz",
+ "integrity": "sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA==",
+ "dev": true,
+ "license": "(WTFPL OR MIT)"
+ },
+ "node_modules/util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/verror": {
+ "version": "1.10.1",
+ "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.1.tgz",
+ "integrity": "sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "assert-plus": "^1.0.0",
+ "core-util-is": "1.0.2",
+ "extsprintf": "^1.2.0"
+ },
+ "engines": {
+ "node": ">=0.6.0"
+ }
+ },
+ "node_modules/vite": {
+ "version": "4.5.5",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.5.tgz",
+ "integrity": "sha512-ifW3Lb2sMdX+WU91s3R0FyQlAyLxOzCSCP37ujw0+r5POeHPwe6udWVIElKQq8gk3t7b8rkmvqC6IHBpCff4GQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.18.10",
+ "postcss": "^8.4.27",
+ "rollup": "^3.27.1"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ },
+ "peerDependencies": {
+ "@types/node": ">= 14",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vue": {
+ "version": "3.3.8",
+ "resolved": "https://registry.npmjs.org/vue/-/vue-3.3.8.tgz",
+ "integrity": "sha512-5VSX/3DabBikOXMsxzlW8JyfeLKlG9mzqnWgLQLty88vdZL7ZJgrdgBOmrArwxiLtmS+lNNpPcBYqrhE6TQW5w==",
+ "dependencies": {
+ "@vue/compiler-dom": "3.3.8",
+ "@vue/compiler-sfc": "3.3.8",
+ "@vue/runtime-dom": "3.3.8",
+ "@vue/server-renderer": "3.3.8",
+ "@vue/shared": "3.3.8"
+ },
+ "peerDependencies": {
+ "typescript": "*"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/wcwidth": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz",
+ "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "defaults": "^1.0.3"
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/wide-align": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz",
+ "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "string-width": "^1.0.2 || 2 || 3 || 4"
+ }
+ },
+ "node_modules/wrap-ansi": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi-cjs": {
+ "name": "wrap-ansi",
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+ "dev": true
+ },
+ "node_modules/xmlbuilder": {
+ "version": "15.1.1",
+ "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz",
+ "integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/y18n": {
+ "version": "5.0.8",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
+ "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/yallist": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+ "dev": true
+ },
+ "node_modules/yargs": {
+ "version": "17.7.2",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
+ "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cliui": "^8.0.1",
+ "escalade": "^3.1.1",
+ "get-caller-file": "^2.0.5",
+ "require-directory": "^2.1.1",
+ "string-width": "^4.2.3",
+ "y18n": "^5.0.5",
+ "yargs-parser": "^21.1.1"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/yargs-parser": {
+ "version": "21.1.1",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
+ "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/yauzl": {
+ "version": "2.10.0",
+ "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
+ "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==",
+ "dev": true,
+ "dependencies": {
+ "buffer-crc32": "~0.2.3",
+ "fd-slicer": "~1.1.0"
+ }
+ },
+ "node_modules/yocto-queue": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/zip-stream": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.1.tgz",
+ "integrity": "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "archiver-utils": "^3.0.4",
+ "compress-commons": "^4.1.2",
+ "readable-stream": "^3.6.0"
+ },
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/zip-stream/node_modules/archiver-utils": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-3.0.4.tgz",
+ "integrity": "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "glob": "^7.2.3",
+ "graceful-fs": "^4.2.0",
+ "lazystream": "^1.0.0",
+ "lodash.defaults": "^4.2.0",
+ "lodash.difference": "^4.5.0",
+ "lodash.flatten": "^4.4.0",
+ "lodash.isplainobject": "^4.0.6",
+ "lodash.union": "^4.6.0",
+ "normalize-path": "^3.0.0",
+ "readable-stream": "^3.6.0"
+ },
+ "engines": {
+ "node": ">= 10"
+ }
+ }
+ }
+}
diff --git a/electron-vue-template/package.json b/electron-vue-template/package.json
new file mode 100644
index 0000000..959161c
--- /dev/null
+++ b/electron-vue-template/package.json
@@ -0,0 +1,33 @@
+{
+ "name": "electron-vue-template",
+ "version": "0.1.0",
+ "description": "A minimal Electron + Vue application",
+ "main": "main/main.js",
+ "scripts": {
+ "dev": "node scripts/dev-server.js",
+ "build": "node scripts/build.js && electron-builder",
+ "build:win": "node scripts/build.js && electron-builder --win",
+ "build:mac": "node scripts/build.js && electron-builder --mac",
+ "build:linux": "node scripts/build.js && electron-builder --linux"
+ },
+ "repository": "https://github.com/deluze/electron-vue-template",
+ "author": {
+ "name": "Deluze",
+ "url": "https://github.com/Deluze"
+ },
+ "devDependencies": {
+ "@vitejs/plugin-vue": "^4.4.1",
+ "chalk": "^4.1.2",
+ "chokidar": "^3.5.3",
+ "electron": "^32.1.2",
+ "electron-builder": "^25.1.6",
+ "typescript": "^5.2.2",
+ "vite": "^4.5.0"
+ },
+ "dependencies": {
+ "@element-plus/icons-vue": "^2.3.2",
+ "element-plus": "^2.11.3",
+ "exceljs": "^4.4.0",
+ "vue": "^3.3.8"
+ }
+}
diff --git a/electron-vue-template/public/icon/icon.png b/electron-vue-template/public/icon/icon.png
new file mode 100644
index 0000000..d826be6
Binary files /dev/null and b/electron-vue-template/public/icon/icon.png differ
diff --git a/electron-vue-template/public/icon/img.png b/electron-vue-template/public/icon/img.png
new file mode 100644
index 0000000..54b070b
Binary files /dev/null and b/electron-vue-template/public/icon/img.png differ
diff --git a/electron-vue-template/public/icons/icon.ico b/electron-vue-template/public/icons/icon.ico
new file mode 100644
index 0000000..6fcfdc3
Binary files /dev/null and b/electron-vue-template/public/icons/icon.ico differ
diff --git a/electron-vue-template/public/icons/icon.png b/electron-vue-template/public/icons/icon.png
new file mode 100644
index 0000000..d826be6
Binary files /dev/null and b/electron-vue-template/public/icons/icon.png differ
diff --git a/electron-vue-template/public/image/111.png b/electron-vue-template/public/image/111.png
new file mode 100644
index 0000000..aabd8cd
Binary files /dev/null and b/electron-vue-template/public/image/111.png differ
diff --git a/electron-vue-template/public/image/splash_screen.png b/electron-vue-template/public/image/splash_screen.png
new file mode 100644
index 0000000..165407b
Binary files /dev/null and b/electron-vue-template/public/image/splash_screen.png differ
diff --git a/electron-vue-template/public/splash.html b/electron-vue-template/public/splash.html
new file mode 100644
index 0000000..c4abdbc
--- /dev/null
+++ b/electron-vue-template/public/splash.html
@@ -0,0 +1,33 @@
+
+
+
+
+
+ 正在启动...
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/electron-vue-template/scripts/build.js b/electron-vue-template/scripts/build.js
new file mode 100644
index 0000000..4abd4bc
--- /dev/null
+++ b/electron-vue-template/scripts/build.js
@@ -0,0 +1,32 @@
+const Path = require('path');
+const Chalk = require('chalk');
+const FileSystem = require('fs');
+const Vite = require('vite');
+const compileTs = require('./private/tsc');
+
+function buildRenderer() {
+ return Vite.build({
+ configFile: Path.join(__dirname, '..', 'vite.config.js'),
+ base: './',
+ mode: 'production'
+ });
+}
+
+function buildMain() {
+ const mainPath = Path.join(__dirname, '..', 'src', 'main');
+ return compileTs(mainPath);
+}
+
+FileSystem.rmSync(Path.join(__dirname, '..', 'build'), {
+ recursive: true,
+ force: true,
+})
+
+console.log(Chalk.blueBright('Transpiling renderer & main...'));
+
+Promise.allSettled([
+ buildRenderer(),
+ buildMain(),
+]).then(() => {
+ console.log(Chalk.greenBright('Renderer & main successfully transpiled! (ready to be built with electron-builder)'));
+});
diff --git a/electron-vue-template/scripts/dev-server.js b/electron-vue-template/scripts/dev-server.js
new file mode 100644
index 0000000..276b254
--- /dev/null
+++ b/electron-vue-template/scripts/dev-server.js
@@ -0,0 +1,121 @@
+process.env.NODE_ENV = 'development';
+
+const Vite = require('vite');
+const ChildProcess = require('child_process');
+const Path = require('path');
+const Chalk = require('chalk');
+const Chokidar = require('chokidar');
+const Electron = require('electron');
+const compileTs = require('./private/tsc');
+const FileSystem = require('fs');
+const { EOL } = require('os');
+
+let viteServer = null;
+let electronProcess = null;
+let electronProcessLocker = false;
+let rendererPort = 0;
+
+async function startRenderer() {
+ viteServer = await Vite.createServer({
+ configFile: Path.join(__dirname, '..', 'vite.config.js'),
+ mode: 'development',
+ });
+
+ return viteServer.listen();
+}
+
+async function startElectron() {
+ if (electronProcess) { // single instance lock
+ return;
+ }
+
+ try {
+ await compileTs(Path.join(__dirname, '..', 'src', 'main'));
+ } catch {
+ console.log(Chalk.redBright('Could not start Electron because of the above typescript error(s).'));
+ electronProcessLocker = false;
+ return;
+ }
+
+ const args = [
+ Path.join(__dirname, '..', 'build', 'main', 'main.js'),
+ rendererPort,
+ ];
+ electronProcess = ChildProcess.spawn(Electron, args);
+ electronProcessLocker = false;
+
+ electronProcess.stdout.on('data', data => {
+ if (data == EOL) {
+ return;
+ }
+
+ process.stdout.write(Chalk.blueBright(`[electron] `) + Chalk.white(data.toString()))
+ });
+
+ electronProcess.stderr.on('data', data =>
+ process.stderr.write(Chalk.blueBright(`[electron] `) + Chalk.white(data.toString()))
+ );
+
+ electronProcess.on('exit', () => stop());
+}
+
+function restartElectron() {
+ if (electronProcess) {
+ electronProcess.removeAllListeners('exit');
+ electronProcess.kill();
+ electronProcess = null;
+ }
+
+ if (!electronProcessLocker) {
+ electronProcessLocker = true;
+ startElectron();
+ }
+}
+
+function copyStaticFiles() {
+ copy('static');
+}
+
+/*
+The working dir of Electron is build/main instead of src/main because of TS.
+tsc does not copy static files, so copy them over manually for dev server.
+*/
+function copy(path) {
+ FileSystem.cpSync(
+ Path.join(__dirname, '..', 'src', 'main', path),
+ Path.join(__dirname, '..', 'build', 'main', path),
+ { recursive: true }
+ );
+}
+
+function stop() {
+ viteServer.close();
+ process.exit();
+}
+
+async function start() {
+ console.log(`${Chalk.greenBright('=======================================')}`);
+ console.log(`${Chalk.greenBright('Starting Electron + Vite Dev Server...')}`);
+ console.log(`${Chalk.greenBright('=======================================')}`);
+
+ const devServer = await startRenderer();
+ rendererPort = devServer.config.server.port;
+
+ copyStaticFiles();
+ startElectron();
+
+ const path = Path.join(__dirname, '..', 'src', 'main');
+ Chokidar.watch(path, {
+ cwd: path,
+ }).on('change', (path) => {
+ console.log(Chalk.blueBright(`[electron] `) + `Change in ${path}. reloading... 🚀`);
+
+ if (path.startsWith(Path.join('static', '/'))) {
+ copy(path);
+ }
+
+ restartElectron();
+ });
+}
+
+start();
diff --git a/electron-vue-template/scripts/private/tsc.js b/electron-vue-template/scripts/private/tsc.js
new file mode 100644
index 0000000..baf0aa1
--- /dev/null
+++ b/electron-vue-template/scripts/private/tsc.js
@@ -0,0 +1,24 @@
+const ChildProcess = require('child_process');
+const Chalk = require('chalk');
+
+function compile(directory) {
+ return new Promise((resolve, reject) => {
+ const tscProcess = ChildProcess.exec('tsc', {
+ cwd: directory,
+ });
+
+ tscProcess.stdout.on('data', data =>
+ process.stdout.write(Chalk.yellowBright(`[tsc] `) + Chalk.white(data.toString()))
+ );
+
+ tscProcess.on('exit', exitCode => {
+ if (exitCode > 0) {
+ reject(exitCode);
+ } else {
+ resolve();
+ }
+ });
+ });
+}
+
+module.exports = compile;
diff --git a/electron-vue-template/src/main/main.ts b/electron-vue-template/src/main/main.ts
new file mode 100644
index 0000000..7b75f33
--- /dev/null
+++ b/electron-vue-template/src/main/main.ts
@@ -0,0 +1,199 @@
+// 主进程:创建窗口、启动后端 JAR、隐藏菜单栏
+import {app, BrowserWindow, ipcMain, session, Menu, screen} from 'electron';
+import { Socket } from 'net';
+import { existsSync } from 'fs';
+import {join, dirname} from 'path';
+import {spawn, ChildProcessWithoutNullStreams} from 'child_process';
+
+// 保存后端进程与窗口引用,便于退出时清理
+let springProcess: ChildProcessWithoutNullStreams | null = null;
+let mainWindow: BrowserWindow | null = null;
+let splashWindow: BrowserWindow | null = null;
+let appOpened = false;
+
+function openAppIfNotOpened() {
+ if (appOpened) return;
+ appOpened = true;
+ if (mainWindow) {
+ mainWindow.show();
+ mainWindow.focus();
+ }
+ if (splashWindow) { splashWindow.close(); splashWindow = null; }
+}
+
+// 启动后端 Spring Boot(使用你提供的绝对路径)
+function startSpringBoot() {
+ const jarPath = 'C:/Users/ZiJIe/Desktop/wox/RuoYi-Vue/ruoyi-admin/target/ruoyi-admin.jar';
+
+ springProcess = spawn('java', ['-jar', jarPath], {
+ cwd: dirname(jarPath),
+ detached: false
+ });
+
+ // 打印后端日志,监听启动成功标志
+ springProcess.stdout.on('data', (data) => {
+ console.log(`SpringBoot: ${data}`);
+ // 检测到启动成功日志立即进入主界面
+ if (data.toString().includes('Started RuoYiApplication')) {
+ openAppIfNotOpened();
+ }
+ });
+
+ // 打印后端错误,检测启动失败
+ springProcess.stderr.on('data', (data) => {
+ console.error(`SpringBoot ERROR: ${data}`);
+ const errorStr = data.toString();
+ // 检测到关键错误信息,直接退出
+ if (errorStr.includes('APPLICATION FAILED TO START') ||
+ errorStr.includes('Port') && errorStr.includes('already in use') ||
+ errorStr.includes('Unable to start embedded Tomcat')) {
+ console.error('后端启动失败,程序即将退出');
+ app.quit();
+ }
+ });
+
+ // 后端退出时,前端同步退出
+ springProcess.on('close', (code) => {
+ console.log(`SpringBoot exited with code ${code}`);
+ if (mainWindow) {
+ mainWindow.close();
+ } else {
+ app.quit();
+ }
+ });
+}
+
+// 关闭后端进程(Windows 使用 taskkill 结束整个进程树)
+function stopSpringBoot() {
+ if (!springProcess) return;
+ try {
+ if (process.platform === 'win32') {
+ // Force kill the whole process tree on Windows
+ try {
+ const pid = springProcess.pid;
+ if (pid !== undefined && pid !== null) {
+ spawn('taskkill', ['/pid', String(pid), '/f', '/t']);
+ } else {
+ springProcess.kill();
+ }
+ } catch (e) {
+ // Fallback
+ springProcess.kill();
+ }
+ } else {
+ springProcess.kill('SIGTERM');
+ }
+ } catch (e) {
+ console.error('Failed to stop Spring Boot process:', e);
+ } finally {
+ springProcess = null;
+ }
+}
+
+// 创建主窗口(预创建但隐藏)
+function createWindow () {
+ mainWindow = new BrowserWindow({
+ width: 800,
+ height: 600,
+ show: false,
+ autoHideMenuBar: true,
+ webPreferences: {
+ preload: join(__dirname, 'preload.js'),
+ nodeIntegration: false,
+ contextIsolation: true,
+ }
+ });
+
+ // 彻底隐藏原生菜单栏
+ try {
+ Menu.setApplicationMenu(null);
+ mainWindow.setMenuBarVisibility(false);
+ if (typeof (mainWindow as any).setMenu === 'function') {
+ (mainWindow as any).setMenu(null);
+ }
+ } catch {}
+
+ const rendererPort = process.argv[2];
+ mainWindow.loadURL(`http://localhost:${rendererPort}`);
+
+ mainWindow.on('closed', () => {
+ mainWindow = null;
+ });
+}
+
+app.whenReady().then(() => {
+ // 预创建主窗口(隐藏)
+ createWindow();
+
+ // 显示启动页
+ const { width: sw, height: sh } = screen.getPrimaryDisplay().workAreaSize;
+ const splashW = Math.min(Math.floor(sw * 0.8), 1800);
+ const splashH = Math.min(Math.floor(sh * 0.8), 1200);
+ splashWindow = new BrowserWindow({
+ width: splashW,
+ height: splashH,
+ frame: false,
+ transparent: false,
+ resizable: false,
+ alwaysOnTop: true,
+ show: true,
+ center: true,
+ });
+
+ const candidateSplashPaths = [
+ join(__dirname, '../../public', 'splash.html'),
+ ];
+ const foundSplash = candidateSplashPaths.find(p => existsSync(p));
+ if (foundSplash) {
+ splashWindow.loadFile(foundSplash);
+ }
+
+ // 注释掉后端启动,便于快速调试前端
+ // startSpringBoot();
+
+ // 快速调试模式 - 直接打开主窗口
+ setTimeout(() => {
+ openAppIfNotOpened();
+ }, 1000);
+
+ // 注释掉超时机制
+ /*
+ setTimeout(() => {
+ if (!appOpened) {
+ console.error('后端启动超时,程序即将退出');
+ app.quit();
+ }
+ }, 30000);
+ */
+
+ // 保守 CSP(仅允许自身脚本),避免引入不必要的外部脚本
+ session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
+ callback({
+ responseHeaders: {
+ ...details.responseHeaders,
+ 'Content-Security-Policy': ['script-src \'self\'']
+ }
+ })
+ })
+
+ app.on('activate', function () {
+ // On macOS it's common to re-create a window in the app when the
+ // dock icon is clicked and there are no other windows open.
+ if (BrowserWindow.getAllWindows().length === 0) {
+ createWindow();
+ }
+ });
+});
+
+app.on('window-all-closed', function () {
+ stopSpringBoot();
+ if (process.platform !== 'darwin') app.quit()
+});
+
+app.on('before-quit', () => {
+ stopSpringBoot();
+});
+
+ipcMain.on('message', (event, message) => {
+ console.log(message);
+})
\ No newline at end of file
diff --git a/electron-vue-template/src/main/preload.ts b/electron-vue-template/src/main/preload.ts
new file mode 100644
index 0000000..128fc39
--- /dev/null
+++ b/electron-vue-template/src/main/preload.ts
@@ -0,0 +1,5 @@
+import {contextBridge, ipcRenderer} from 'electron';
+
+contextBridge.exposeInMainWorld('electronAPI', {
+ sendMessage: (message: string) => ipcRenderer.send('message', message)
+})
diff --git a/electron-vue-template/src/main/static/.gitkeep b/electron-vue-template/src/main/static/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/electron-vue-template/src/main/tsconfig.json b/electron-vue-template/src/main/tsconfig.json
new file mode 100644
index 0000000..3556a5a
--- /dev/null
+++ b/electron-vue-template/src/main/tsconfig.json
@@ -0,0 +1,14 @@
+{
+ "compilerOptions": {
+ "target": "ES2015",
+ "module": "commonjs",
+ "strict": true,
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "forceConsistentCasingInFileNames": true,
+ "outDir": "../../build/main",
+ "allowJs": true,
+ "noImplicitAny": false,
+ },
+ "exclude": ["static"]
+}
diff --git a/electron-vue-template/src/renderer/App.vue b/electron-vue-template/src/renderer/App.vue
new file mode 100644
index 0000000..73001b8
--- /dev/null
+++ b/electron-vue-template/src/renderer/App.vue
@@ -0,0 +1,535 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ activeMenu.toUpperCase() }} 面板
+
功能开发中...
+
+
+
+
+
+
+
+
+
+
+
+ 当前账号可以授权绑定 {{ deviceQuota.limit }} 台设备
+
+
+
+ {{ scope.row.name || scope.row.deviceId }}
+ 本机
+
+
+
+
+ {{ scope.row.status==='online' ? '已登录' : '已登出' }}
+
+
+
+
+ {{ scope.row.lastActiveAt || '-' }}
+
+
+
+
+ 移除设备
+
+
+
+
+ 关闭
+
+
+
+
+
+
+
+
+
diff --git a/electron-vue-template/src/renderer/api/amazon.ts b/electron-vue-template/src/renderer/api/amazon.ts
new file mode 100644
index 0000000..b4995b9
--- /dev/null
+++ b/electron-vue-template/src/renderer/api/amazon.ts
@@ -0,0 +1,38 @@
+import { http } from './http';
+
+export const amazonApi = {
+ // 上传Excel文件解析ASIN列表
+ importAsinFromExcel(file: File) {
+ const formData = new FormData();
+ formData.append('file', file);
+ return http.upload<{ code: number, data: { asinList: string[], total: number }, msg: string | null }>('/api/amazon/import/asin', formData);
+ },
+
+ getProductsBatch(asinList: string[], batchId: string) {
+ return http.post<{ code: number, data: { products: any[] }, msg: string | null }>('/api/amazon/products/batch', { asinList, batchId });
+ },
+ getLatestProducts() {
+ return http.get<{ code: number, data: { products: any[] }, msg: string | null }>('/api/amazon/products/latest');
+ },
+ getProductsByBatch(batchId: string) {
+ return http.get<{ products: any[] }>(`/api/amazon/products/batch/${batchId}`);
+ },
+ updateProduct(productData: unknown) {
+ return http.post('/api/amazon/products/update', productData);
+ },
+ deleteProduct(productId: string) {
+ return http.post('/api/amazon/products/delete', { id: productId });
+ },
+ exportToExcel(products: unknown[], options: Record = {}) {
+ return http.post('/api/amazon/export', { products, ...options });
+ },
+ getProductStats() {
+ return http.get('/api/amazon/stats');
+ },
+ searchProducts(searchParams: Record) {
+ return http.get('/api/amazon/products/search', searchParams);
+ },
+ openGenmaiSpirit() {
+ return http.post('/api/genmai/open');
+ },
+};
diff --git a/electron-vue-template/src/renderer/api/auth.ts b/electron-vue-template/src/renderer/api/auth.ts
new file mode 100644
index 0000000..807c9bc
--- /dev/null
+++ b/electron-vue-template/src/renderer/api/auth.ts
@@ -0,0 +1,90 @@
+import { http } from './http';
+
+// 统一响应处理函数 - 适配ERP客户端格式
+function unwrap(res: any): T {
+ if (res && typeof res.success === 'boolean') {
+ if (!res.success) {
+ const message: string = res.message || res.msg || '请求失败';
+ throw new Error(message);
+ }
+ return res as T;
+ }
+ // 兼容标准格式
+ if (res && typeof res.code === 'number') {
+ if (res.code !== 0) {
+ const message: string = res.msg || '请求失败';
+ throw new Error(message);
+ }
+ return (res.data as T) ?? ({} as T);
+ }
+ return res as T;
+}
+
+// 认证相关类型定义
+interface LoginRequest {
+ username: string;
+ password: string;
+}
+
+interface RegisterRequest {
+ username: string;
+ password: string;
+}
+
+interface LoginResponse {
+ success: boolean;
+ token: string;
+ permissions: string[];
+ username: string;
+ message?: string;
+}
+
+interface RegisterResponse {
+ success: boolean;
+ message?: string;
+}
+
+interface CheckUsernameResponse {
+ available: boolean;
+}
+
+export const authApi = {
+ // 用户登录
+ login(params: LoginRequest) {
+ return http
+ .post('/api/login', params)
+ .then(res => unwrap(res));
+ },
+
+ // 用户注册
+ register(params: RegisterRequest) {
+ return http
+ .post('/api/register', params)
+ .then(res => unwrap(res));
+ },
+
+ // 检查用户名可用性
+ checkUsername(username: string) {
+ return http
+ .get('/api/check-username', { username })
+ .then(res => {
+ // checkUsername 使用标准格式 {code: 200, data: boolean}
+ if (res && res.code === 200) {
+ return { available: res.data };
+ }
+ throw new Error(res?.msg || '检查用户名失败');
+ });
+ },
+
+ // 验证token有效性
+ verifyToken(token: string) {
+ return http
+ .post('/api/verify', { token })
+ .then(res => unwrap<{ success: boolean }>(res));
+ },
+
+ // 用户登出
+ logout(token: string) {
+ return http.postVoid('/api/logout', { token });
+ },
+};
\ No newline at end of file
diff --git a/electron-vue-template/src/renderer/api/device.ts b/electron-vue-template/src/renderer/api/device.ts
new file mode 100644
index 0000000..0619223
--- /dev/null
+++ b/electron-vue-template/src/renderer/api/device.ts
@@ -0,0 +1,48 @@
+import { http } from './http'
+
+// 与老版保持相同的接口路径与参数
+const base = '/api/device'
+
+export interface DeviceQuota {
+ limit: number
+ used: number
+}
+
+export interface DeviceItem {
+ deviceId: string
+ name?: string
+ status?: 'online' | 'offline'
+ lastActiveAt?: string
+}
+
+export const deviceApi = {
+ getQuota(username: string) {
+ return http.get(`${base}/quota`, { username }).then((res: any) => {
+ if (res && typeof res.limit !== 'undefined') return res as DeviceQuota
+ if (res && typeof res.code === 'number') return (res.data as DeviceQuota) || { limit: 0, used: 0 }
+ return (res?.data as DeviceQuota) || { limit: 0, used: 0 }
+ })
+ },
+
+ list(username: string) {
+ return http.get(`${base}/list`, { username }).then((res: any) => {
+ if (Array.isArray(res)) return res as DeviceItem[]
+ if (res && typeof res.code === 'number') return (res.data as DeviceItem[]) || []
+ return (res?.data as DeviceItem[]) || []
+ })
+ },
+
+ register(payload: { username: string }) {
+ return http.post(`${base}/register`, payload)
+ },
+
+ remove(payload: { deviceId: string }) {
+ return http.postVoid(`${base}/remove`, payload)
+ },
+
+ heartbeat(payload: { username: string; deviceId: string; version?: string }) {
+ return http.postVoid(`${base}/heartbeat`, payload)
+ },
+}
+
+
diff --git a/electron-vue-template/src/renderer/api/http.ts b/electron-vue-template/src/renderer/api/http.ts
new file mode 100644
index 0000000..fe6387f
--- /dev/null
+++ b/electron-vue-template/src/renderer/api/http.ts
@@ -0,0 +1,78 @@
+// 极简 HTTP 工具:仅封装 GET/POST,默认指向本地 8081
+export type HttpMethod = 'GET' | 'POST';
+
+const BASE_URL = 'http://localhost:8081';
+
+// 将对象转为查询字符串
+function buildQuery(params?: Record): string {
+ if (!params) return '';
+ const usp = new URLSearchParams();
+ Object.entries(params).forEach(([key, value]) => {
+ if (value === undefined || value === null) return;
+ usp.append(key, String(value));
+ });
+ const queryString = usp.toString();
+ return queryString ? `?${queryString}` : '';
+}
+
+// 统一请求入口:自动加上 BASE_URL、JSON 头与错误处理
+async function request(path: string, options: RequestInit): Promise {
+ const res = await fetch(`${BASE_URL}${path}`, {
+ credentials: 'omit',
+ cache: 'no-store',
+ ...options,
+ headers: {
+ 'Content-Type': 'application/json',
+ ...(options.headers || {}),
+ },
+ });
+ if (!res.ok) {
+ const text = await res.text().catch(() => '');
+ throw new Error(text || `HTTP ${res.status}`);
+ }
+ const contentType = res.headers.get('content-type') || '';
+ if (contentType.includes('application/json')) {
+ return (await res.json()) as T;
+ }
+ return (await res.text()) as unknown as T;
+}
+
+export const http = {
+ get(path: string, params?: Record) {
+ return request(`${path}${buildQuery(params)}`, { method: 'GET' });
+ },
+ post(path: string, body?: unknown) {
+ return request(path, { method: 'POST', body: body ? JSON.stringify(body) : undefined });
+ },
+ // 用于无需读取响应体的 POST(如删除/心跳等),从根源避免读取中断
+ postVoid(path: string, body?: unknown) {
+ return fetch(`${BASE_URL}${path}`, {
+ method: 'POST',
+ body: body ? JSON.stringify(body) : undefined,
+ credentials: 'omit',
+ cache: 'no-store',
+ headers: { 'Content-Type': 'application/json' },
+ }).then(res => {
+ if (!res.ok) return res.text().then(t => Promise.reject(new Error(t || `HTTP ${res.status}`)));
+ return undefined as unknown as void;
+ });
+ },
+ // 文件上传:透传 FormData,不设置 Content-Type 让浏览器自动处理
+ upload(path: string, form: FormData) {
+ const res = fetch(`${BASE_URL}${path}`, {
+ method: 'POST',
+ body: form,
+ credentials: 'omit',
+ cache: 'no-store',
+ });
+ return res.then(async response => {
+ if (!response.ok) {
+ const text = await response.text().catch(() => '');
+ throw new Error(text || `HTTP ${response.status}`);
+ }
+ return response.json() as Promise;
+ });
+ },
+};
+
+
diff --git a/electron-vue-template/src/renderer/api/rakuten.ts b/electron-vue-template/src/renderer/api/rakuten.ts
new file mode 100644
index 0000000..cfed3ba
--- /dev/null
+++ b/electron-vue-template/src/renderer/api/rakuten.ts
@@ -0,0 +1,38 @@
+import { http } from './http';
+
+function unwrap(res: any): T {
+ if (res && typeof res.code === 'number') {
+ if (res.code !== 0) {
+ const message: string = res.msg || '请求失败';
+ throw new Error(message);
+ }
+ return (res.data as T) ?? ({} as T);
+ }
+ return res as T;
+}
+
+export const rakutenApi = {
+ // 上传 Excel 或按店铺名查询
+ getProducts(params: { file?: File; shopName?: string; batchId?: string }) {
+ const formData = new FormData();
+ if (params.file) formData.append('file', params.file);
+ if (params.batchId) formData.append('batchId', params.batchId);
+ if (params.shopName) formData.append('shopName', params.shopName);
+ return http
+ .upload('/api/rakuten/products', formData)
+ .then(res => unwrap<{ products: any[]; total?: number; sessionId?: string }>(res));
+ },
+ search1688(imageUrl: string, sessionId?: string) {
+ const payload: Record = { imageUrl };
+ if (sessionId) payload.sessionId = sessionId;
+ return http.post('/api/rakuten/search1688', payload).then(res => unwrap(res));
+ },
+ getLatestProducts() {
+ return http.get('/api/rakuten/products/latest').then(res => unwrap<{ products: any[] }>(res));
+ },
+ exportAndSave(exportData: unknown) {
+ return http
+ .post('/api/rakuten/export-and-save', exportData)
+ .then(res => unwrap<{ filePath: string; fileName?: string; recordCount?: number; hasImages?: boolean }>(res));
+ },
+};
diff --git a/electron-vue-template/src/renderer/api/shopee.ts b/electron-vue-template/src/renderer/api/shopee.ts
new file mode 100644
index 0000000..aa17da9
--- /dev/null
+++ b/electron-vue-template/src/renderer/api/shopee.ts
@@ -0,0 +1,15 @@
+import { http } from './http';
+
+export const shopeeApi = {
+ getAdHosting(params: Record = {}) {
+ return http.get('/api/shopee/ad-hosting', params);
+ },
+ getReviews(params: Record = {}) {
+ return http.get('/api/shopee/reviews', params);
+ },
+ exportData(exportParams: Record = {}) {
+ return http.post('/api/shopee/export', exportParams);
+ },
+};
+
+
diff --git a/electron-vue-template/src/renderer/api/zebra.ts b/electron-vue-template/src/renderer/api/zebra.ts
new file mode 100644
index 0000000..bbec8cf
--- /dev/null
+++ b/electron-vue-template/src/renderer/api/zebra.ts
@@ -0,0 +1,56 @@
+// 斑马订单模型(根据页面所需字段精简定义)
+export interface ZebraOrder {
+ orderedAt?: string;
+ productImage?: string;
+ productTitle?: string;
+ shopOrderNumber?: string;
+ timeSinceOrder?: string;
+ priceJpy?: number;
+ productQuantity?: number;
+ shippingFeeJpy?: number;
+ serviceFee?: string;
+ productNumber?: string;
+ poNumber?: string;
+ shippingFeeCny?: number;
+ internationalShippingFee?: number;
+ poLogisticsCompany?: string;
+ poTrackingNumber?: string;
+ internationalTrackingNumber?: string;
+ trackInfo?: string;
+}
+
+export interface ZebraOrdersResp {
+ orders: ZebraOrder[];
+ total?: number;
+ totalPages?: number;
+}
+
+import { http } from './http';
+
+// 斑马 API:与原 zebra-api.js 对齐的接口封装
+export const zebraApi = {
+ getOrders(params: Record) {
+ return http.get('/api/banma/orders', params);
+ },
+ getOrdersByBatch(batchId: string) {
+ return http.get(`/api/banma/orders/batch/${batchId}`);
+ },
+ getLatestOrders() {
+ return http.get('/api/banma/orders/latest');
+ },
+ getShops() {
+ return http.get<{ data?: { list?: Array<{ id: string; shopName: string }> } }>('/api/banma/shops');
+ },
+ refreshToken() {
+ return http.post('/api/banma/refresh-token');
+ },
+ exportAndSaveOrders(exportData: unknown) {
+ return http.post<{ filePath: string }>('/api/banma/export-and-save', exportData);
+ },
+ getOrderStats() {
+ return http.get('/api/banma/orders/stats');
+ },
+ searchOrders(searchParams: Record) {
+ return http.get('/api/banma/orders/search', searchParams);
+ },
+};
diff --git a/electron-vue-template/src/renderer/assets/vite.svg b/electron-vue-template/src/renderer/assets/vite.svg
new file mode 100644
index 0000000..e7b8dfb
--- /dev/null
+++ b/electron-vue-template/src/renderer/assets/vite.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/electron-vue-template/src/renderer/assets/vue.svg b/electron-vue-template/src/renderer/assets/vue.svg
new file mode 100644
index 0000000..770e9d3
--- /dev/null
+++ b/electron-vue-template/src/renderer/assets/vue.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/electron-vue-template/src/renderer/components/HelloWorld.vue b/electron-vue-template/src/renderer/components/HelloWorld.vue
new file mode 100644
index 0000000..cc340bc
--- /dev/null
+++ b/electron-vue-template/src/renderer/components/HelloWorld.vue
@@ -0,0 +1 @@
+
diff --git a/electron-vue-template/src/renderer/components/amazon/AmazonDashboard.vue b/electron-vue-template/src/renderer/components/amazon/AmazonDashboard.vue
new file mode 100644
index 0000000..2b69c15
--- /dev/null
+++ b/electron-vue-template/src/renderer/components/amazon/AmazonDashboard.vue
@@ -0,0 +1,393 @@
+
+
+
+
+
+
+
+
+
+
+
+ 📂 {{ loading ? '处理中...' : '导入ASIN列表' }}
+
+
+
+
+
+
+ 查询
+
+
+
+
+ 停止获取
+ 导出Excel
+ {{ genmaiLoading ? '启动中...' : '跟卖精灵' }}
+
+
+
+
+
+
+
+
{{ progressPercentage }}%
+
+
{{ currentAsin }}
+
+
+
+
+
+
+
+
+
+
+
+
+ ASIN
+ 卖家/配送方
+ 当前售价
+
+
+
+
+ 暂无数据,请导入ASIN列表
+
+
+ {{ row.asin }}
+
+
+ {{ row.seller || '无货' }}
+ / {{ row.shipper }}
+
+
+
+ {{ row.price || '无货' }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/electron-vue-template/src/renderer/components/auth/LoginDialog.vue b/electron-vue-template/src/renderer/components/auth/LoginDialog.vue
new file mode 100644
index 0000000..d7804c7
--- /dev/null
+++ b/electron-vue-template/src/renderer/components/auth/LoginDialog.vue
@@ -0,0 +1,124 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 登录
+
+
+ 取消
+
+
+
+
+
+ 还没有账号?点击注册
+
+
+
+
+
\ No newline at end of file
diff --git a/electron-vue-template/src/renderer/components/auth/RegisterDialog.vue b/electron-vue-template/src/renderer/components/auth/RegisterDialog.vue
new file mode 100644
index 0000000..3706259
--- /dev/null
+++ b/electron-vue-template/src/renderer/components/auth/RegisterDialog.vue
@@ -0,0 +1,162 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ ✓ 用户名可用
+
+
+ ✗ 用户名已存在
+
+
+
+
+
+
+
+
+
+
+
+ 注册
+
+
+ 取消
+
+
+
+
+
+ 已有账号?返回登录
+
+
+
+
+
\ No newline at end of file
diff --git a/electron-vue-template/src/renderer/components/layout/NavigationBar.vue b/electron-vue-template/src/renderer/components/layout/NavigationBar.vue
new file mode 100644
index 0000000..2fa3ce1
--- /dev/null
+++ b/electron-vue-template/src/renderer/components/layout/NavigationBar.vue
@@ -0,0 +1,172 @@
+
+
+
+
+
+
+
+ 首页
+ >
+ {{ activeMenu }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/electron-vue-template/src/renderer/components/rakuten/RakutenDashboard.vue b/electron-vue-template/src/renderer/components/rakuten/RakutenDashboard.vue
new file mode 100644
index 0000000..5d118ec
--- /dev/null
+++ b/electron-vue-template/src/renderer/components/rakuten/RakutenDashboard.vue
@@ -0,0 +1,632 @@
+
+
+
+
+
+
+
+
+
+
+ 📂 {{ loading ? '处理中...' : '导入店铺名列表' }}
+
+
+
+
+
+
+ 查询
+
+
+
+
+ 停止获取
+ 导出Excel
+
+
+
+
+
+
+
+
+
+
{{ progressPercentage }}%
+
+
{{ statusMessage }}
+
+
+
+
+
+
+
+
+
+
+
+
+ 店铺名
+ 商品链接
+ 商品图片
+ 排名
+ 商品标题
+ 价格
+ 1688识图链接
+ 1688运费
+ 1688中位价
+ 1688最低价
+ 1688中间价
+ 1688最高价
+
+
+
+
+ 暂无数据,请导入店铺名列表
+
+
+ {{ row.originalShopName }}
+
+
+ --
+
+
+
+
+
+ 无图片
+
+
+ {{ row.ranking }}
+ --
+
+ {{ row.productTitle || '--' }}
+ {{ row.price ? row.price + '円' : '--' }}
+
+
+ 搜索中...
+ --
+
+ {{ row.freight ?? '--' }}
+ {{ row.median ?? '--' }}
+ {{ row.skuPrices?.[0] ?? '--' }}
+ {{ row.skuPrices?.[Math.floor(row.skuPrices.length / 2)] ?? '--' }}
+ {{ row.skuPrices?.[row.skuPrices.length - 1] ?? '--' }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/electron-vue-template/src/renderer/components/zebra/ZebraDashboard.vue b/electron-vue-template/src/renderer/components/zebra/ZebraDashboard.vue
new file mode 100644
index 0000000..6970023
--- /dev/null
+++ b/electron-vue-template/src/renderer/components/zebra/ZebraDashboard.vue
@@ -0,0 +1,322 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 📂 {{ loading ? '处理中...' : '获取订单数据' }}
+
+ 停止获取
+ 导出Excel
+
+
+
+
+
+
+
+
+
{{ progressPercentage }}%
+
+
+ {{ progressPercentage >= 100 ? '完成' : `获取中... (${allOrderData.length}/${fetchTotalItems})` }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 下单时间
+ 商品图片
+ 商品名称
+ 乐天订单号
+ 下单距今
+ 订单金额/日元
+ 数量
+ 税费/日元
+ 回款抽点rmb
+ 商品番号
+ 1688订单号
+ 采购金额/rmb
+ 国际运费/rmb
+ 国内物流
+ 国内单号
+ 日本单号
+ 地址状态
+
+
+
+
+ 暂无数据,请选择日期范围获取订单
+
+
+ {{ row.orderedAt || '-' }}
+
+
+
+
+ 无图片
+
+ {{ row.productTitle }}
+ {{ row.shopOrderNumber }}
+ {{ row.timeSinceOrder || '-' }}
+ {{ formatJpy(row.priceJpy) }}
+ {{ row.productQuantity || 0 }}
+ {{ formatJpy(row.shippingFeeJpy) }}
+ {{ row.serviceFee || '-' }}
+ {{ row.productNumber }}
+ {{ row.poNumber }}
+ {{ formatCny(row.shippingFeeCny) }}
+ {{ row.internationalShippingFee || '-' }}
+ {{ row.poLogisticsCompany || '-' }}
+ {{ row.poTrackingNumber }}
+ {{ row.internationalTrackingNumber }}
+
+ {{ row.trackInfo }}
+ 暂无
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/electron-vue-template/src/renderer/index.html b/electron-vue-template/src/renderer/index.html
new file mode 100644
index 0000000..e2214f1
--- /dev/null
+++ b/electron-vue-template/src/renderer/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+ Vite + Vue template
+
+
+
+
+
+
+
+
diff --git a/electron-vue-template/src/renderer/main.ts b/electron-vue-template/src/renderer/main.ts
new file mode 100644
index 0000000..f922389
--- /dev/null
+++ b/electron-vue-template/src/renderer/main.ts
@@ -0,0 +1,9 @@
+import { createApp } from 'vue'
+import './style.css';
+import 'element-plus/dist/index.css'
+import ElementPlus from 'element-plus'
+import App from './App.vue'
+
+const app = createApp(App)
+app.use(ElementPlus)
+app.mount('#app')
diff --git a/electron-vue-template/src/renderer/style.css b/electron-vue-template/src/renderer/style.css
new file mode 100644
index 0000000..84a0050
--- /dev/null
+++ b/electron-vue-template/src/renderer/style.css
@@ -0,0 +1,89 @@
+:root {
+ font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
+ line-height: 1.5;
+ font-weight: 400;
+
+ color-scheme: light dark;
+ color: rgba(255, 255, 255, 0.87);
+ background-color: #242424;
+
+ font-synthesis: none;
+ text-rendering: optimizeLegibility;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-text-size-adjust: 100%;
+}
+
+a {
+ font-weight: 500;
+ color: #646cff;
+ text-decoration: inherit;
+}
+a:hover {
+ color: #535bf2;
+}
+
+a {
+ font-weight: 500;
+ color: #646cff;
+ text-decoration: inherit;
+}
+a:hover {
+ color: #535bf2;
+}
+
+body {
+ margin: 0;
+ display: flex;
+ place-items: center;
+ min-width: 320px;
+ min-height: 100vh;
+}
+
+h1 {
+ font-size: 3.2em;
+ line-height: 1.1;
+}
+
+button {
+ border-radius: 8px;
+ border: 1px solid transparent;
+ padding: 0.6em 1.2em;
+ font-size: 1em;
+ font-weight: 500;
+ font-family: inherit;
+ background-color: #1a1a1a;
+ cursor: pointer;
+ transition: border-color 0.25s;
+}
+button:hover {
+ border-color: #646cff;
+}
+button:focus,
+button:focus-visible {
+ outline: 4px auto -webkit-focus-ring-color;
+}
+
+.card {
+ padding: 2em;
+}
+
+#app {
+ max-width: 1280px;
+ margin: 0 auto;
+ padding: 2rem;
+ text-align: center;
+}
+
+@media (prefers-color-scheme: light) {
+ :root {
+ color: #213547;
+ background-color: #ffffff;
+ }
+ a:hover {
+ color: #747bff;
+ }
+ button {
+ background-color: #f9f9f9;
+ }
+}
diff --git a/electron-vue-template/src/renderer/tsconfig.json b/electron-vue-template/src/renderer/tsconfig.json
new file mode 100644
index 0000000..041150b
--- /dev/null
+++ b/electron-vue-template/src/renderer/tsconfig.json
@@ -0,0 +1,19 @@
+{
+ "compilerOptions": {
+ "target": "esnext",
+ "useDefineForClassFields": true,
+ "module": "esnext",
+ "moduleResolution": "node",
+ "strict": true,
+ "jsx": "preserve",
+ "sourceMap": true,
+ "resolveJsonModule": true,
+ "esModuleInterop": true,
+ "allowSyntheticDefaultImports": true,
+ "skipLibCheck": true,
+ "lib": ["esnext", "dom"],
+ "types": ["vite/client"]
+ },
+ "include": ["./**/*.ts", "./**/*.d.ts", "./**/*.tsx", "./**/*.vue"],
+ }
+
diff --git a/electron-vue-template/src/renderer/typings/electron.d.ts b/electron-vue-template/src/renderer/typings/electron.d.ts
new file mode 100644
index 0000000..671acd1
--- /dev/null
+++ b/electron-vue-template/src/renderer/typings/electron.d.ts
@@ -0,0 +1,12 @@
+/**
+ * Should match main/preload.ts for typescript support in renderer
+ */
+export default interface ElectronApi {
+ sendMessage: (message: string) => void
+}
+
+declare global {
+ interface Window {
+ electronAPI: ElectronApi,
+ }
+}
diff --git a/electron-vue-template/src/renderer/typings/shims-vue.d.ts b/electron-vue-template/src/renderer/typings/shims-vue.d.ts
new file mode 100644
index 0000000..b07a059
--- /dev/null
+++ b/electron-vue-template/src/renderer/typings/shims-vue.d.ts
@@ -0,0 +1,6 @@
+declare module '*.vue' {
+ import type { DefineComponent } from 'vue'
+
+ const component: DefineComponent<{}, {}, any>
+ export default component
+}
diff --git a/electron-vue-template/vite.config.js b/electron-vue-template/vite.config.js
new file mode 100644
index 0000000..fe34814
--- /dev/null
+++ b/electron-vue-template/vite.config.js
@@ -0,0 +1,23 @@
+const Path = require('path');
+const vuePlugin = require('@vitejs/plugin-vue')
+
+const { defineConfig } = require('vite');
+
+/**
+ * https://vitejs.dev/config
+ */
+const config = defineConfig({
+ root: Path.join(__dirname, 'src', 'renderer'),
+ publicDir: Path.join(__dirname, 'public'),
+ server: {
+ port: 8083,
+ },
+ open: false,
+ build: {
+ outDir: Path.join(__dirname, 'build', 'renderer'),
+ emptyOutDir: true,
+ },
+ plugins: [vuePlugin()],
+});
+
+module.exports = config;
diff --git a/erp_client_sb/.claude/settings.local.json b/erp_client_sb/.claude/settings.local.json
new file mode 100644
index 0000000..01385df
--- /dev/null
+++ b/erp_client_sb/.claude/settings.local.json
@@ -0,0 +1,19 @@
+{
+ "permissions": {
+ "allow": [
+ "Bash(mvn:*)",
+ "Bash(del:*)",
+ "Bash(rm:*)",
+ "Bash(del \"C:\\Users\\ZiJIe\\Desktop\\wox\\erp_client_sb\\src\\main\\java\\com\\tashow\\erp\\controller\\AuthController.java\")",
+ "Bash(del \"C:\\Users\\ZiJIe\\Desktop\\wox\\erp_client_sb\\src\\main\\resources\\static\\html\\login.html\")",
+ "Bash(del \"C:\\Users\\ZiJIe\\Desktop\\wox\\erp_client_sb\\src\\main\\java\\com\\tashow\\erp\\controller\\AuthProxyController.java\")",
+ "Bash(java:*)",
+ "Bash(del \"C:\\Users\\ZiJIe\\Desktop\\wox\\erp_client_sb\\src\\main\\resources\\static\\html\\shopee-platform.html\")",
+ "Bash(mkdir:*)",
+ "Bash(\"taskkill\" \"/f\" \"/pid\" \"9804\")",
+ "Bash(dir:*)",
+ "Bash(find:*)"
+ ],
+ "deny": []
+ }
+}
\ No newline at end of file
diff --git a/erp_client_sb/data/device.id b/erp_client_sb/data/device.id
new file mode 100644
index 0000000..6a3963c
--- /dev/null
+++ b/erp_client_sb/data/device.id
@@ -0,0 +1 @@
+DEVICE-CD31378598644EAA8C0E8153A9D80959
\ No newline at end of file
diff --git a/erp_client_sb/data/erp-cache.db b/erp_client_sb/data/erp-cache.db
new file mode 100644
index 0000000..8a28c25
Binary files /dev/null and b/erp_client_sb/data/erp-cache.db differ
diff --git a/erp_client_sb/pom.xml b/erp_client_sb/pom.xml
new file mode 100644
index 0000000..bd56eee
--- /dev/null
+++ b/erp_client_sb/pom.xml
@@ -0,0 +1,185 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 3.5.4
+
+
+ com.tashow.erp
+ erp_client_sb
+ 2.4.7
+ erp_client_sb
+ erp客户端
+
+ UTF-8
+ ${java.version}
+ ${java.version}
+ ${java.version}
+ 17
+ 3.5.4
+ yyyy-MM-dd HH:mm:ss
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+ org.apache.poi
+ poi-ooxml
+ 4.1.2
+
+
+
+ com.qiniu
+ qiniu-java-sdk
+ 7.12.1
+
+
+
+ us.codecraft
+ webmagic-core
+ 1.0.3
+
+
+
+ us.codecraft
+ webmagic-extension
+ 1.0.3
+
+
+
+
+
+ org.projectlombok
+ lombok
+ 1.18.38
+ provided
+
+
+
+ cn.hutool
+ hutool-all
+ 5.8.36
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+ org.xerial
+ sqlite-jdbc
+ 3.42.0.0
+
+
+ org.hibernate.orm
+ hibernate-community-dialects
+ 6.2.7.Final
+
+
+ org.seleniumhq.selenium
+ selenium-java
+ 4.23.0
+
+
+ io.github.bonigarcia
+ webdrivermanager
+ 5.9.2
+
+
+
+ org.python
+ jython-standalone
+ 2.7.4
+
+
+
+
+ io.jsonwebtoken
+ jjwt-api
+ 0.11.5
+
+
+ io.jsonwebtoken
+ jjwt-impl
+ 0.11.5
+ runtime
+
+
+ io.jsonwebtoken
+ jjwt-jackson
+ 0.11.5
+ runtime
+
+
+
+
+
+
+ aliyun-repository
+ aliyun repository
+ http://maven.aliyun.com/nexus/content/groups/public/
+
+
+ jboss-repository
+ jboss repository
+ http://repository.jboss.org/nexus/content/groups/public-jboss/
+
+
+
+
+
+
+ src/main/resources
+ true
+
+ application*.yml
+ application*.properties
+
+
+
+ src/main/resources
+ false
+
+ application*.yml
+ application*.properties
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+ org.projectlombok
+ lombok
+
+
+ com.tashow.erp.ErpClientSbApplication
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ 17
+ 17
+
+
+
+
+
+
+
+
diff --git a/erp_client_sb/src/main/deploy/package/windows/javafx-boot-1.0.ico b/erp_client_sb/src/main/deploy/package/windows/javafx-boot-1.0.ico
new file mode 100644
index 0000000..6fcfdc3
Binary files /dev/null and b/erp_client_sb/src/main/deploy/package/windows/javafx-boot-1.0.ico differ
diff --git a/erp_client_sb/src/main/java/com/tashow/erp/ErpClientSbApplication.java b/erp_client_sb/src/main/java/com/tashow/erp/ErpClientSbApplication.java
new file mode 100644
index 0000000..9675cd2
--- /dev/null
+++ b/erp_client_sb/src/main/java/com/tashow/erp/ErpClientSbApplication.java
@@ -0,0 +1,37 @@
+package com.tashow.erp;
+
+import com.tashow.erp.utils.ErrorReporter;
+import com.tashow.erp.utils.ResourcePreloader;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.ConfigurableApplicationContext;
+
+@Slf4j
+@SpringBootApplication
+public class ErpClientSbApplication {
+
+ public static void main(String[] args) {
+ // 纯 Spring Boot 启动,不再依赖 JavaFX
+ ConfigurableApplicationContext applicationContext = SpringApplication.run(ErpClientSbApplication.class, args);
+ try {
+ ErrorReporter errorReporter = applicationContext.getBean(ErrorReporter.class);
+ Thread.setDefaultUncaughtExceptionHandler((thread, ex) -> {
+ log.error("捕获到未处理异常: " + ex.getMessage(), ex);
+ errorReporter.reportSystemError("未捕获异常: " + thread.getName(), (Exception) ex);
+ });
+ log.info("全局异常处理器已设置");
+ } catch (Exception e) {
+ log.warn("未设置 ErrorReporter,继续启动: {}", e.getMessage());
+ }
+
+ // 如需预加载资源,可按需保留
+ try {
+ ResourcePreloader.init();
+ ResourcePreloader.preloadErpDashboard();
+ ResourcePreloader.executePreloading();
+ } catch (Throwable t) {
+ log.warn("资源预加载失败: {}", t.getMessage());
+ }
+ }
+}
diff --git a/erp_client_sb/src/main/java/com/tashow/erp/common/CharsetKit.java b/erp_client_sb/src/main/java/com/tashow/erp/common/CharsetKit.java
new file mode 100644
index 0000000..27a3498
--- /dev/null
+++ b/erp_client_sb/src/main/java/com/tashow/erp/common/CharsetKit.java
@@ -0,0 +1,85 @@
+package com.tashow.erp.common;
+
+
+import com.tashow.erp.utils.StringUtils;
+
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * 字符集工具类
+ *
+ * @author ruoyi
+ */
+public class CharsetKit
+{
+ /** ISO-8859-1 */
+ public static final String ISO_8859_1 = "ISO-8859-1";
+ /** UTF-8 */
+ public static final String UTF_8 = "UTF-8";
+ /** GBK */
+ public static final String GBK = "GBK";
+ /** ISO-8859-1 */
+ public static final Charset CHARSET_ISO_8859_1 = Charset.forName(ISO_8859_1);
+ /** UTF-8 */
+ public static final Charset CHARSET_UTF_8 = Charset.forName(UTF_8);
+ /** GBK */
+ public static final Charset CHARSET_GBK = Charset.forName(GBK);
+ /**
+ * 转换为Charset对象
+ *
+ * @param charset 字符集,为空则返回默认字符集
+ * @return Charset
+ */
+ public static Charset charset(String charset)
+ {
+ return StringUtils.isEmpty(charset) ? Charset.defaultCharset() : Charset.forName(charset);
+ }
+
+ /**
+ * 转换字符串的字符集编码
+ *
+ * @param source 字符串
+ * @param srcCharset 源字符集,默认ISO-8859-1
+ * @param destCharset 目标字符集,默认UTF-8
+ * @return 转换后的字符集
+ */
+ public static String convert(String source, String srcCharset, String destCharset)
+ {
+ return convert(source, Charset.forName(srcCharset), Charset.forName(destCharset));
+ }
+
+ /**
+ * 转换字符串的字符集编码
+ *
+ * @param source 字符串
+ * @param srcCharset 源字符集,默认ISO-8859-1
+ * @param destCharset 目标字符集,默认UTF-8
+ * @return 转换后的字符集
+ */
+ public static String convert(String source, Charset srcCharset, Charset destCharset)
+ {
+ if (null == srcCharset)
+ {
+ srcCharset = StandardCharsets.ISO_8859_1;
+ }
+
+ if (null == destCharset)
+ {
+ destCharset = StandardCharsets.UTF_8;
+ }
+
+ if (StringUtils.isEmpty(source) || srcCharset.equals(destCharset))
+ {
+ return source;
+ }
+ return new String(source.getBytes(srcCharset), destCharset);
+ }
+ /**
+ * @return 系统字符集编码
+ */
+ public static String systemCharset()
+ {
+ return Charset.defaultCharset().name();
+ }
+}
diff --git a/erp_client_sb/src/main/java/com/tashow/erp/common/Constants.java b/erp_client_sb/src/main/java/com/tashow/erp/common/Constants.java
new file mode 100644
index 0000000..dd6f255
--- /dev/null
+++ b/erp_client_sb/src/main/java/com/tashow/erp/common/Constants.java
@@ -0,0 +1,169 @@
+package com.tashow.erp.common;
+
+
+import java.util.Locale;
+
+/**
+ * 通用常量信息
+ *
+ * @author ruoyi
+ */
+public class Constants
+{
+ /**
+ * UTF-8 字符集
+ */
+ public static final String UTF8 = "UTF-8";
+
+ /**
+ * GBK 字符集
+ */
+ public static final String GBK = "GBK";
+
+ /**
+ * 系统语言
+ */
+ public static final Locale DEFAULT_LOCALE = Locale.SIMPLIFIED_CHINESE;
+
+ /**
+ * www主域
+ */
+ public static final String WWW = "www.";
+
+ /**
+ * http请求
+ */
+ public static final String HTTP = "http://";
+
+ /**
+ * https请求
+ */
+ public static final String HTTPS = "https://";
+
+ /**
+ * 通用成功标识
+ */
+ public static final String SUCCESS = "0";
+
+ /**
+ * 通用失败标识
+ */
+ public static final String FAIL = "1";
+
+ /**
+ * 登录成功
+ */
+ public static final String LOGIN_SUCCESS = "Success";
+
+ /**
+ * 注销
+ */
+ public static final String LOGOUT = "Logout";
+
+ /**
+ * 注册
+ */
+ public static final String REGISTER = "Register";
+
+ /**
+ * 登录失败
+ */
+ public static final String LOGIN_FAIL = "Error";
+
+ /**
+ * 所有权限标识
+ */
+ public static final String ALL_PERMISSION = "*:*:*";
+
+ /**
+ * 管理员角色权限标识
+ */
+ public static final String SUPER_ADMIN = "admin";
+
+ /**
+ * 角色权限分隔符
+ */
+ public static final String ROLE_DELIMETER = ",";
+
+ /**
+ * 权限标识分隔符
+ */
+ public static final String PERMISSION_DELIMETER = ",";
+
+ /**
+ * 验证码有效期(分钟)
+ */
+ public static final Integer CAPTCHA_EXPIRATION = 2;
+
+ /**
+ * 令牌
+ */
+ public static final String TOKEN = "token";
+
+ /**
+ * 令牌前缀
+ */
+ public static final String TOKEN_PREFIX = "Bearer ";
+
+ /**
+ * 令牌前缀
+ */
+ public static final String LOGIN_USER_KEY = "login_user_key";
+
+ /**
+ * 用户ID
+ */
+ public static final String JWT_USERID = "userid";
+
+
+ /**
+ * 用户头像
+ */
+ public static final String JWT_AVATAR = "avatar";
+
+ /**
+ * 创建时间
+ */
+ public static final String JWT_CREATED = "created";
+
+ /**
+ * 用户权限
+ */
+ public static final String JWT_AUTHORITIES = "authorities";
+
+ /**
+ * 资源映射路径 前缀
+ */
+ public static final String RESOURCE_PREFIX = "/profile";
+
+ /**
+ * RMI 远程方法调用
+ */
+ public static final String LOOKUP_RMI = "rmi:";
+
+ /**
+ * LDAP 远程方法调用
+ */
+ public static final String LOOKUP_LDAP = "ldap:";
+
+ /**
+ * LDAPS 远程方法调用
+ */
+ public static final String LOOKUP_LDAPS = "ldaps:";
+
+ /**
+ * 自动识别json对象白名单配置(仅允许解析的包名,范围越小越安全)
+ */
+ public static final String[] JSON_WHITELIST_STR = { "org.springframework", "com.ruoyi" };
+
+ /**
+ * 定时任务白名单配置(仅允许访问的包名,如其他需要可以自行添加)
+ */
+ public static final String[] JOB_WHITELIST_STR = { "com.ruoyi.quartz.task" };
+
+ /**
+ * 定时任务违规的字符
+ */
+ public static final String[] JOB_ERROR_STR = { "java.net.URL", "javax.naming.InitialContext", "org.yaml.snakeyaml",
+ "org.springframework", "org.apache", "com.ruoyi.common.utils.file", "com.ruoyi.common.config", "com.ruoyi.generator" };
+}
diff --git a/erp_client_sb/src/main/java/com/tashow/erp/common/Convert.java b/erp_client_sb/src/main/java/com/tashow/erp/common/Convert.java
new file mode 100644
index 0000000..3f302bb
--- /dev/null
+++ b/erp_client_sb/src/main/java/com/tashow/erp/common/Convert.java
@@ -0,0 +1,1021 @@
+package com.tashow.erp.common;
+
+
+
+import com.tashow.erp.utils.StringUtils;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.math.RoundingMode;
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+import java.text.NumberFormat;
+import java.util.Set;
+
+/**
+ * 类型转换器
+ *
+ * @author ruoyi
+ */
+public class Convert
+{
+ /**
+ * 转换为字符串
+ * 如果给定的值为null,或者转换失败,返回默认值
+ * 转换失败不会报错
+ *
+ * @param value 被转换的值
+ * @param defaultValue 转换错误时的默认值
+ * @return 结果
+ */
+ public static String toStr(Object value, String defaultValue)
+ {
+ if (null == value)
+ {
+ return defaultValue;
+ }
+ if (value instanceof String)
+ {
+ return (String) value;
+ }
+ return value.toString();
+ }
+
+ /**
+ * 转换为字符串
+ * 如果给定的值为null,或者转换失败,返回默认值null
+ * 转换失败不会报错
+ *
+ * @param value 被转换的值
+ * @return 结果
+ */
+ public static String toStr(Object value)
+ {
+ return toStr(value, null);
+ }
+
+ /**
+ * 转换为字符
+ * 如果给定的值为null,或者转换失败,返回默认值
+ * 转换失败不会报错
+ *
+ * @param value 被转换的值
+ * @param defaultValue 转换错误时的默认值
+ * @return 结果
+ */
+ public static Character toChar(Object value, Character defaultValue)
+ {
+ if (null == value)
+ {
+ return defaultValue;
+ }
+ if (value instanceof Character)
+ {
+ return (Character) value;
+ }
+
+ final String valueStr = toStr(value, null);
+ return StringUtils.isEmpty(valueStr) ? defaultValue : valueStr.charAt(0);
+ }
+
+ /**
+ * 转换为字符
+ * 如果给定的值为null,或者转换失败,返回默认值null
+ * 转换失败不会报错
+ *
+ * @param value 被转换的值
+ * @return 结果
+ */
+ public static Character toChar(Object value)
+ {
+ return toChar(value, null);
+ }
+
+ /**
+ * 转换为byte
+ * 如果给定的值为null,或者转换失败,返回默认值
+ * 转换失败不会报错
+ *
+ * @param value 被转换的值
+ * @param defaultValue 转换错误时的默认值
+ * @return 结果
+ */
+ public static Byte toByte(Object value, Byte defaultValue)
+ {
+ if (value == null)
+ {
+ return defaultValue;
+ }
+ if (value instanceof Byte)
+ {
+ return (Byte) value;
+ }
+ if (value instanceof Number)
+ {
+ return ((Number) value).byteValue();
+ }
+ final String valueStr = toStr(value, null);
+ if (StringUtils.isEmpty(valueStr))
+ {
+ return defaultValue;
+ }
+ try
+ {
+ return Byte.parseByte(valueStr);
+ }
+ catch (Exception e)
+ {
+ return defaultValue;
+ }
+ }
+
+ /**
+ * 转换为byte
+ * 如果给定的值为null,或者转换失败,返回默认值null
+ * 转换失败不会报错
+ *
+ * @param value 被转换的值
+ * @return 结果
+ */
+ public static Byte toByte(Object value)
+ {
+ return toByte(value, null);
+ }
+
+ /**
+ * 转换为Short
+ * 如果给定的值为null,或者转换失败,返回默认值
+ * 转换失败不会报错
+ *
+ * @param value 被转换的值
+ * @param defaultValue 转换错误时的默认值
+ * @return 结果
+ */
+ public static Short toShort(Object value, Short defaultValue)
+ {
+ if (value == null)
+ {
+ return defaultValue;
+ }
+ if (value instanceof Short)
+ {
+ return (Short) value;
+ }
+ if (value instanceof Number)
+ {
+ return ((Number) value).shortValue();
+ }
+ final String valueStr = toStr(value, null);
+ if (StringUtils.isEmpty(valueStr))
+ {
+ return defaultValue;
+ }
+ try
+ {
+ return Short.parseShort(valueStr.trim());
+ }
+ catch (Exception e)
+ {
+ return defaultValue;
+ }
+ }
+
+ /**
+ * 转换为Short
+ * 如果给定的值为null,或者转换失败,返回默认值null
+ * 转换失败不会报错
+ *
+ * @param value 被转换的值
+ * @return 结果
+ */
+ public static Short toShort(Object value)
+ {
+ return toShort(value, null);
+ }
+
+ /**
+ * 转换为Number
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错
+ *
+ * @param value 被转换的值
+ * @param defaultValue 转换错误时的默认值
+ * @return 结果
+ */
+ public static Number toNumber(Object value, Number defaultValue)
+ {
+ if (value == null)
+ {
+ return defaultValue;
+ }
+ if (value instanceof Number)
+ {
+ return (Number) value;
+ }
+ final String valueStr = toStr(value, null);
+ if (StringUtils.isEmpty(valueStr))
+ {
+ return defaultValue;
+ }
+ try
+ {
+ return NumberFormat.getInstance().parse(valueStr);
+ }
+ catch (Exception e)
+ {
+ return defaultValue;
+ }
+ }
+
+ /**
+ * 转换为Number
+ * 如果给定的值为空,或者转换失败,返回默认值null
+ * 转换失败不会报错
+ *
+ * @param value 被转换的值
+ * @return 结果
+ */
+ public static Number toNumber(Object value)
+ {
+ return toNumber(value, null);
+ }
+
+ /**
+ * 转换为int
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错
+ *
+ * @param value 被转换的值
+ * @param defaultValue 转换错误时的默认值
+ * @return 结果
+ */
+ public static Integer toInt(Object value, Integer defaultValue)
+ {
+ if (value == null)
+ {
+ return defaultValue;
+ }
+ if (value instanceof Integer)
+ {
+ return (Integer) value;
+ }
+ if (value instanceof Number)
+ {
+ return ((Number) value).intValue();
+ }
+ final String valueStr = toStr(value, null);
+ if (StringUtils.isEmpty(valueStr))
+ {
+ return defaultValue;
+ }
+ try
+ {
+ return Integer.parseInt(valueStr.trim());
+ }
+ catch (Exception e)
+ {
+ return defaultValue;
+ }
+ }
+
+ /**
+ * 转换为int
+ * 如果给定的值为null,或者转换失败,返回默认值null
+ * 转换失败不会报错
+ *
+ * @param value 被转换的值
+ * @return 结果
+ */
+ public static Integer toInt(Object value)
+ {
+ return toInt(value, null);
+ }
+
+ /**
+ * 转换为Integer数组
+ *
+ * @param str 被转换的值
+ * @return 结果
+ */
+ public static Integer[] toIntArray(String str)
+ {
+ return toIntArray(",", str);
+ }
+
+ /**
+ * 转换为Long数组
+ *
+ * @param str 被转换的值
+ * @return 结果
+ */
+ public static Long[] toLongArray(String str)
+ {
+ return toLongArray(",", str);
+ }
+
+ /**
+ * 转换为Integer数组
+ *
+ * @param split 分隔符
+ * @param split 被转换的值
+ * @return 结果
+ */
+ public static Integer[] toIntArray(String split, String str)
+ {
+ if (StringUtils.isEmpty(str))
+ {
+ return new Integer[] {};
+ }
+ String[] arr = str.split(split);
+ final Integer[] ints = new Integer[arr.length];
+ for (int i = 0; i < arr.length; i++)
+ {
+ final Integer v = toInt(arr[i], 0);
+ ints[i] = v;
+ }
+ return ints;
+ }
+
+ /**
+ * 转换为Long数组
+ *
+ * @param split 分隔符
+ * @param str 被转换的值
+ * @return 结果
+ */
+ public static Long[] toLongArray(String split, String str)
+ {
+ if (StringUtils.isEmpty(str))
+ {
+ return new Long[] {};
+ }
+ String[] arr = str.split(split);
+ final Long[] longs = new Long[arr.length];
+ for (int i = 0; i < arr.length; i++)
+ {
+ final Long v = toLong(arr[i], null);
+ longs[i] = v;
+ }
+ return longs;
+ }
+
+ /**
+ * 转换为String数组
+ *
+ * @param str 被转换的值
+ * @return 结果
+ */
+ public static String[] toStrArray(String str)
+ {
+ if (StringUtils.isEmpty(str))
+ {
+ return new String[] {};
+ }
+ return toStrArray(",", str);
+ }
+
+ /**
+ * 转换为String数组
+ *
+ * @param split 分隔符
+ * @param split 被转换的值
+ * @return 结果
+ */
+ public static String[] toStrArray(String split, String str)
+ {
+ return str.split(split);
+ }
+
+ /**
+ * 转换为long
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错
+ *
+ * @param value 被转换的值
+ * @param defaultValue 转换错误时的默认值
+ * @return 结果
+ */
+ public static Long toLong(Object value, Long defaultValue)
+ {
+ if (value == null)
+ {
+ return defaultValue;
+ }
+ if (value instanceof Long)
+ {
+ return (Long) value;
+ }
+ if (value instanceof Number)
+ {
+ return ((Number) value).longValue();
+ }
+ final String valueStr = toStr(value, null);
+ if (StringUtils.isEmpty(valueStr))
+ {
+ return defaultValue;
+ }
+ try
+ {
+ // 支持科学计数法
+ return new BigDecimal(valueStr.trim()).longValue();
+ }
+ catch (Exception e)
+ {
+ return defaultValue;
+ }
+ }
+
+ /**
+ * 转换为long
+ * 如果给定的值为null,或者转换失败,返回默认值null
+ * 转换失败不会报错
+ *
+ * @param value 被转换的值
+ * @return 结果
+ */
+ public static Long toLong(Object value)
+ {
+ return toLong(value, null);
+ }
+
+ /**
+ * 转换为double
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错
+ *
+ * @param value 被转换的值
+ * @param defaultValue 转换错误时的默认值
+ * @return 结果
+ */
+ public static Double toDouble(Object value, Double defaultValue)
+ {
+ if (value == null)
+ {
+ return defaultValue;
+ }
+ if (value instanceof Double)
+ {
+ return (Double) value;
+ }
+ if (value instanceof Number)
+ {
+ return ((Number) value).doubleValue();
+ }
+ final String valueStr = toStr(value, null);
+ if (StringUtils.isEmpty(valueStr))
+ {
+ return defaultValue;
+ }
+ try
+ {
+ // 支持科学计数法
+ return new BigDecimal(valueStr.trim()).doubleValue();
+ }
+ catch (Exception e)
+ {
+ return defaultValue;
+ }
+ }
+
+ /**
+ * 转换为double
+ * 如果给定的值为空,或者转换失败,返回默认值null
+ * 转换失败不会报错
+ *
+ * @param value 被转换的值
+ * @return 结果
+ */
+ public static Double toDouble(Object value)
+ {
+ return toDouble(value, null);
+ }
+
+ /**
+ * 转换为Float
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错
+ *
+ * @param value 被转换的值
+ * @param defaultValue 转换错误时的默认值
+ * @return 结果
+ */
+ public static Float toFloat(Object value, Float defaultValue)
+ {
+ if (value == null)
+ {
+ return defaultValue;
+ }
+ if (value instanceof Float)
+ {
+ return (Float) value;
+ }
+ if (value instanceof Number)
+ {
+ return ((Number) value).floatValue();
+ }
+ final String valueStr = toStr(value, null);
+ if (StringUtils.isEmpty(valueStr))
+ {
+ return defaultValue;
+ }
+ try
+ {
+ return Float.parseFloat(valueStr.trim());
+ }
+ catch (Exception e)
+ {
+ return defaultValue;
+ }
+ }
+
+ /**
+ * 转换为Float
+ * 如果给定的值为空,或者转换失败,返回默认值null
+ * 转换失败不会报错
+ *
+ * @param value 被转换的值
+ * @return 结果
+ */
+ public static Float toFloat(Object value)
+ {
+ return toFloat(value, null);
+ }
+
+ /**
+ * 转换为boolean
+ * String支持的值为:true、false、yes、ok、no、1、0、是、否, 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错
+ *
+ * @param value 被转换的值
+ * @param defaultValue 转换错误时的默认值
+ * @return 结果
+ */
+ public static Boolean toBool(Object value, Boolean defaultValue)
+ {
+ if (value == null)
+ {
+ return defaultValue;
+ }
+ if (value instanceof Boolean)
+ {
+ return (Boolean) value;
+ }
+ String valueStr = toStr(value, null);
+ if (StringUtils.isEmpty(valueStr))
+ {
+ return defaultValue;
+ }
+ valueStr = valueStr.trim().toLowerCase();
+ switch (valueStr)
+ {
+ case "true":
+ case "yes":
+ case "ok":
+ case "1":
+ case "是":
+ return true;
+ case "false":
+ case "no":
+ case "0":
+ case "否":
+ return false;
+ default:
+ return defaultValue;
+ }
+ }
+
+ /**
+ * 转换为boolean
+ * 如果给定的值为空,或者转换失败,返回默认值null
+ * 转换失败不会报错
+ *
+ * @param value 被转换的值
+ * @return 结果
+ */
+ public static Boolean toBool(Object value)
+ {
+ return toBool(value, null);
+ }
+
+ /**
+ * 转换为Enum对象
+ * 如果给定的值为空,或者转换失败,返回默认值
+ *
+ * @param clazz Enum的Class
+ * @param value 值
+ * @param defaultValue 默认值
+ * @return Enum
+ */
+ public static > E toEnum(Class clazz, Object value, E defaultValue)
+ {
+ if (value == null)
+ {
+ return defaultValue;
+ }
+ if (clazz.isAssignableFrom(value.getClass()))
+ {
+ @SuppressWarnings("unchecked")
+ E myE = (E) value;
+ return myE;
+ }
+ final String valueStr = toStr(value, null);
+ if (StringUtils.isEmpty(valueStr))
+ {
+ return defaultValue;
+ }
+ try
+ {
+ return Enum.valueOf(clazz, valueStr);
+ }
+ catch (Exception e)
+ {
+ return defaultValue;
+ }
+ }
+
+ /**
+ * 转换为Enum对象
+ * 如果给定的值为空,或者转换失败,返回默认值null
+ *
+ * @param clazz Enum的Class
+ * @param value 值
+ * @return Enum
+ */
+ public static > E toEnum(Class clazz, Object value)
+ {
+ return toEnum(clazz, value, null);
+ }
+
+ /**
+ * 转换为BigInteger
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错
+ *
+ * @param value 被转换的值
+ * @param defaultValue 转换错误时的默认值
+ * @return 结果
+ */
+ public static BigInteger toBigInteger(Object value, BigInteger defaultValue)
+ {
+ if (value == null)
+ {
+ return defaultValue;
+ }
+ if (value instanceof BigInteger)
+ {
+ return (BigInteger) value;
+ }
+ if (value instanceof Long)
+ {
+ return BigInteger.valueOf((Long) value);
+ }
+ final String valueStr = toStr(value, null);
+ if (StringUtils.isEmpty(valueStr))
+ {
+ return defaultValue;
+ }
+ try
+ {
+ return new BigInteger(valueStr);
+ }
+ catch (Exception e)
+ {
+ return defaultValue;
+ }
+ }
+
+ /**
+ * 转换为BigInteger
+ * 如果给定的值为空,或者转换失败,返回默认值null
+ * 转换失败不会报错
+ *
+ * @param value 被转换的值
+ * @return 结果
+ */
+ public static BigInteger toBigInteger(Object value)
+ {
+ return toBigInteger(value, null);
+ }
+
+ /**
+ * 转换为BigDecimal
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错
+ *
+ * @param value 被转换的值
+ * @param defaultValue 转换错误时的默认值
+ * @return 结果
+ */
+ public static BigDecimal toBigDecimal(Object value, BigDecimal defaultValue)
+ {
+ if (value == null)
+ {
+ return defaultValue;
+ }
+ if (value instanceof BigDecimal)
+ {
+ return (BigDecimal) value;
+ }
+ if (value instanceof Long)
+ {
+ return new BigDecimal((Long) value);
+ }
+ if (value instanceof Double)
+ {
+ return BigDecimal.valueOf((Double) value);
+ }
+ if (value instanceof Integer)
+ {
+ return new BigDecimal((Integer) value);
+ }
+ final String valueStr = toStr(value, null);
+ if (StringUtils.isEmpty(valueStr))
+ {
+ return defaultValue;
+ }
+ try
+ {
+ return new BigDecimal(valueStr);
+ }
+ catch (Exception e)
+ {
+ return defaultValue;
+ }
+ }
+
+ /**
+ * 转换为BigDecimal
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错
+ *
+ * @param value 被转换的值
+ * @return 结果
+ */
+ public static BigDecimal toBigDecimal(Object value)
+ {
+ return toBigDecimal(value, null);
+ }
+
+ /**
+ * 将对象转为字符串
+ * 1、Byte数组和ByteBuffer会被转换为对应字符串的数组 2、对象数组会调用Arrays.toString方法
+ *
+ * @param obj 对象
+ * @return 字符串
+ */
+ public static String utf8Str(Object obj)
+ {
+ return str(obj, CharsetKit.CHARSET_UTF_8);
+ }
+
+ /**
+ * 将对象转为字符串
+ * 1、Byte数组和ByteBuffer会被转换为对应字符串的数组 2、对象数组会调用Arrays.toString方法
+ *
+ * @param obj 对象
+ * @param charsetName 字符集
+ * @return 字符串
+ */
+ public static String str(Object obj, String charsetName)
+ {
+ return str(obj, Charset.forName(charsetName));
+ }
+
+ /**
+ * 将对象转为字符串
+ * 1、Byte数组和ByteBuffer会被转换为对应字符串的数组 2、对象数组会调用Arrays.toString方法
+ *
+ * @param obj 对象
+ * @param charset 字符集
+ * @return 字符串
+ */
+ public static String str(Object obj, Charset charset)
+ {
+ if (null == obj)
+ {
+ return null;
+ }
+
+ if (obj instanceof String)
+ {
+ return (String) obj;
+ }
+ else if (obj instanceof byte[] || obj instanceof Byte[])
+ {
+ if (obj instanceof byte[])
+ {
+ return str((byte[]) obj, charset);
+ }
+ else
+ {
+ Byte[] bytes = (Byte[]) obj;
+ int length = bytes.length;
+ byte[] dest = new byte[length];
+ for (int i = 0; i < length; i++)
+ {
+ dest[i] = bytes[i];
+ }
+ return str(dest, charset);
+ }
+ }
+ else if (obj instanceof ByteBuffer)
+ {
+ return str((ByteBuffer) obj, charset);
+ }
+ return obj.toString();
+ }
+
+ /**
+ * 将byte数组转为字符串
+ *
+ * @param bytes byte数组
+ * @param charset 字符集
+ * @return 字符串
+ */
+ public static String str(byte[] bytes, String charset)
+ {
+ return str(bytes, StringUtils.isEmpty(charset) ? Charset.defaultCharset() : Charset.forName(charset));
+ }
+
+ /**
+ * 解码字节码
+ *
+ * @param data 字符串
+ * @param charset 字符集,如果此字段为空,则解码的结果取决于平台
+ * @return 解码后的字符串
+ */
+ public static String str(byte[] data, Charset charset)
+ {
+ if (data == null)
+ {
+ return null;
+ }
+
+ if (null == charset)
+ {
+ return new String(data);
+ }
+ return new String(data, charset);
+ }
+
+ /**
+ * 将编码的byteBuffer数据转换为字符串
+ *
+ * @param data 数据
+ * @param charset 字符集,如果为空使用当前系统字符集
+ * @return 字符串
+ */
+ public static String str(ByteBuffer data, String charset)
+ {
+ if (data == null)
+ {
+ return null;
+ }
+
+ return str(data, Charset.forName(charset));
+ }
+
+ /**
+ * 将编码的byteBuffer数据转换为字符串
+ *
+ * @param data 数据
+ * @param charset 字符集,如果为空使用当前系统字符集
+ * @return 字符串
+ */
+ public static String str(ByteBuffer data, Charset charset)
+ {
+ if (null == charset)
+ {
+ charset = Charset.defaultCharset();
+ }
+ return charset.decode(data).toString();
+ }
+
+ // ----------------------------------------------------------------------- 全角半角转换
+ /**
+ * 半角转全角
+ *
+ * @param input String.
+ * @return 全角字符串.
+ */
+ public static String toSBC(String input)
+ {
+ return toSBC(input, null);
+ }
+
+ /**
+ * 半角转全角
+ *
+ * @param input String
+ * @param notConvertSet 不替换的字符集合
+ * @return 全角字符串.
+ */
+ public static String toSBC(String input, Set notConvertSet)
+ {
+ char[] c = input.toCharArray();
+ for (int i = 0; i < c.length; i++)
+ {
+ if (null != notConvertSet && notConvertSet.contains(c[i]))
+ {
+ // 跳过不替换的字符
+ continue;
+ }
+
+ if (c[i] == ' ')
+ {
+ c[i] = '\u3000';
+ }
+ else if (c[i] < '\177')
+ {
+ c[i] = (char) (c[i] + 65248);
+
+ }
+ }
+ return new String(c);
+ }
+
+ /**
+ * 全角转半角
+ *
+ * @param input String.
+ * @return 半角字符串
+ */
+ public static String toDBC(String input)
+ {
+ return toDBC(input, null);
+ }
+
+ /**
+ * 替换全角为半角
+ *
+ * @param text 文本
+ * @param notConvertSet 不替换的字符集合
+ * @return 替换后的字符
+ */
+ public static String toDBC(String text, Set notConvertSet)
+ {
+ char[] c = text.toCharArray();
+ for (int i = 0; i < c.length; i++)
+ {
+ if (null != notConvertSet && notConvertSet.contains(c[i]))
+ {
+ // 跳过不替换的字符
+ continue;
+ }
+
+ if (c[i] == '\u3000')
+ {
+ c[i] = ' ';
+ }
+ else if (c[i] > '\uFF00' && c[i] < '\uFF5F')
+ {
+ c[i] = (char) (c[i] - 65248);
+ }
+ }
+ return new String(c);
+ }
+
+ /**
+ * 数字金额大写转换 先写个完整的然后将如零拾替换成零
+ *
+ * @param n 数字
+ * @return 中文大写数字
+ */
+ public static String digitUppercase(double n)
+ {
+ String[] fraction = { "角", "分" };
+ String[] digit = { "零", "壹", "贰", "叁", "肆", "伍", "陆", "柒", "捌", "玖" };
+ String[][] unit = { { "元", "万", "亿" }, { "", "拾", "佰", "仟" } };
+
+ String head = n < 0 ? "负" : "";
+ n = Math.abs(n);
+
+ String s = "";
+ for (int i = 0; i < fraction.length; i++)
+ {
+ // 优化double计算精度丢失问题
+ BigDecimal nNum = new BigDecimal(n);
+ BigDecimal decimal = new BigDecimal(10);
+ BigDecimal scale = nNum.multiply(decimal).setScale(2, RoundingMode.HALF_EVEN);
+ double d = scale.doubleValue();
+ s += (digit[(int) (Math.floor(d * Math.pow(10, i)) % 10)] + fraction[i]).replaceAll("(零.)+", "");
+ }
+ if (s.length() < 1)
+ {
+ s = "整";
+ }
+ int integerPart = (int) Math.floor(n);
+
+ for (int i = 0; i < unit[0].length && integerPart > 0; i++)
+ {
+ String p = "";
+ for (int j = 0; j < unit[1].length && n > 0; j++)
+ {
+ p = digit[integerPart % 10] + unit[1][j] + p;
+ integerPart = integerPart / 10;
+ }
+ s = p.replaceAll("(零.)*零$", "").replaceAll("^$", "零") + unit[0][i] + s;
+ }
+ return head + s.replaceAll("(零.)*零元", "元").replaceFirst("(零.)+", "").replaceAll("(零.)+", "零").replaceAll("^整$", "零元整");
+ }
+}
diff --git a/erp_client_sb/src/main/java/com/tashow/erp/config/DatabaseConfig.java b/erp_client_sb/src/main/java/com/tashow/erp/config/DatabaseConfig.java
new file mode 100644
index 0000000..1f37b0f
--- /dev/null
+++ b/erp_client_sb/src/main/java/com/tashow/erp/config/DatabaseConfig.java
@@ -0,0 +1,33 @@
+package com.tashow.erp.config;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.ApplicationArguments;
+import org.springframework.boot.ApplicationRunner;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Component;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+/**
+ * 数据库配置,确保数据目录存在
+ */
+@Slf4j
+@Component
+@Order(1) // 确保在其他组件之前运行
+public class DatabaseConfig implements ApplicationRunner {
+ @Override
+ public void run(ApplicationArguments args) throws Exception {
+ // 确保data目录存在
+ Path dataDir = Paths.get("data");
+ if (!Files.exists(dataDir)) {
+ try {
+ Files.createDirectories(dataDir);
+ log.info("创建数据目录: {}", dataDir.toAbsolutePath());
+ } catch (Exception e) {
+ log.error("创建数据目录失败: {}", e.getMessage());
+ throw e;
+ }
+ } else {
+ log.info("数据目录已存在: {}", dataDir.toAbsolutePath());
+ }
+ }
+}
\ No newline at end of file
diff --git a/erp_client_sb/src/main/java/com/tashow/erp/config/ErrorReportAspect.java b/erp_client_sb/src/main/java/com/tashow/erp/config/ErrorReportAspect.java
new file mode 100644
index 0000000..4b6e343
--- /dev/null
+++ b/erp_client_sb/src/main/java/com/tashow/erp/config/ErrorReportAspect.java
@@ -0,0 +1,50 @@
+package com.tashow.erp.config;
+
+import com.tashow.erp.utils.ErrorReporter;
+import com.tashow.erp.utils.JsonData;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+/**
+ * 错误上报切面 - 自动捕获所有Controller异常并上报,同时检测错误返回值
+ */
+@Aspect
+@Component
+public class ErrorReportAspect {
+
+ @Autowired
+ private ErrorReporter errorReporter;
+
+ /**
+ * 拦截所有Controller方法,自动上报异常和错误返回值
+ */
+ @Around("@within(org.springframework.web.bind.annotation.RestController) || @within(org.springframework.stereotype.Controller)")
+ public Object aroundController(ProceedingJoinPoint point) throws Throwable {
+ String methodName = point.getSignature().getDeclaringTypeName() + "." + point.getSignature().getName();
+
+ try {
+ Object result = point.proceed();
+
+ // 检查返回值是否表示错误
+ if (result instanceof JsonData) {
+ JsonData jsonData = (JsonData) result;
+ // code != 0 表示失败(根据JsonData注释:0表示成功,-1表示失败,1表示处理中)
+ if (jsonData.getCode() != null && jsonData.getCode() != 0) {
+ // 创建一个RuntimeException来包装错误信息
+ String errorMsg = jsonData.getMsg() != null ? jsonData.getMsg() : "未知错误";
+ Exception syntheticException = new RuntimeException("业务处理失败: " + errorMsg);
+ errorReporter.reportBusinessError(methodName + " 返回错误", syntheticException);
+ }
+ }
+
+ return result;
+ } catch (Exception e) {
+ // 自动上报未捕获的异常
+ errorReporter.reportBusinessError(methodName + " 抛出异常", e);
+ throw e;
+ }
+ }
+}
\ No newline at end of file
diff --git a/erp_client_sb/src/main/java/com/tashow/erp/config/FxWeaverConfig.java b/erp_client_sb/src/main/java/com/tashow/erp/config/FxWeaverConfig.java
new file mode 100644
index 0000000..f12c405
--- /dev/null
+++ b/erp_client_sb/src/main/java/com/tashow/erp/config/FxWeaverConfig.java
@@ -0,0 +1,18 @@
+// package com.tashow.erp.config;
+//
+// 已移除 FxWeaver 相关配置(项目改为纯 Spring Boot)。
+// 如需恢复 JavaFX 集成,请取消注释并恢复依赖。
+//
+// import net.rgielen.fxweaver.core.FxWeaver;
+// import net.rgielen.fxweaver.spring.SpringFxWeaver;
+// import org.springframework.context.ConfigurableApplicationContext;
+// import org.springframework.context.annotation.Bean;
+// import org.springframework.context.annotation.Configuration;
+//
+// @Configuration
+// public class FxWeaverConfig {
+// @Bean
+// public FxWeaver fxWeaver(ConfigurableApplicationContext applicationContext) {
+// return new SpringFxWeaver(applicationContext);
+// }
+// }
\ No newline at end of file
diff --git a/erp_client_sb/src/main/java/com/tashow/erp/config/JavaBridgeConfig.java b/erp_client_sb/src/main/java/com/tashow/erp/config/JavaBridgeConfig.java
new file mode 100644
index 0000000..5782d71
--- /dev/null
+++ b/erp_client_sb/src/main/java/com/tashow/erp/config/JavaBridgeConfig.java
@@ -0,0 +1,13 @@
+package com.tashow.erp.config;
+
+import com.tashow.erp.fx.controller.JavaBridge;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class JavaBridgeConfig {
+ @Bean
+ public JavaBridge javaBridge() {
+ return new JavaBridge();
+ }
+}
\ No newline at end of file
diff --git a/erp_client_sb/src/main/java/com/tashow/erp/config/MySplashScreen.java b/erp_client_sb/src/main/java/com/tashow/erp/config/MySplashScreen.java
new file mode 100644
index 0000000..4e44598
--- /dev/null
+++ b/erp_client_sb/src/main/java/com/tashow/erp/config/MySplashScreen.java
@@ -0,0 +1,63 @@
+// package com.tashow.erp.config;
+//
+// 已转为纯 Spring Boot,JavaFX 启动屏幕不再使用。如需恢复,可取消注释并补充 JavaFX 依赖。
+//
+// import javafx.application.Platform;
+// import javafx.scene.Scene;
+// import javafx.scene.control.ProgressBar;
+// import javafx.scene.image.Image;
+// import javafx.scene.image.ImageView;
+// import javafx.scene.layout.StackPane;
+// import javafx.scene.layout.VBox;
+// import javafx.stage.Stage;
+// import javafx.stage.StageStyle;
+// import lombok.extern.slf4j.Slf4j;
+// import org.springframework.stereotype.Component;
+//
+// /**
+// * 自定义启动屏幕
+// */
+// @Slf4j
+// @Component
+// public class MySplashScreen {
+// private static final String DEFAULT_IMAGE = "/static/image/splash_screen.png";
+// private Stage splashStage;
+// private ProgressBar progressBar;
+//
+// public void show() {
+// Platform.runLater(() -> {
+// try {
+// splashStage = new Stage();
+// splashStage.initStyle(StageStyle.UNDECORATED);
+// Image splashImage = new Image(getClass().getResourceAsStream(DEFAULT_IMAGE));
+// ImageView imageView = new ImageView(splashImage);
+// ProgressBar progressBar = new ProgressBar();
+// progressBar.setPrefWidth(splashImage.getWidth());
+// progressBar.setMaxWidth(Double.MAX_VALUE);
+// progressBar.setStyle("-fx-accent: #0078d4; -fx-background-color: transparent;");
+// StackPane root = new StackPane();
+// VBox progressContainer = new VBox();
+// progressContainer.setAlignment(javafx.geometry.Pos.BOTTOM_CENTER);
+// progressContainer.setSpacing(0);
+// progressContainer.getChildren().add(progressBar);
+// root.getChildren().addAll(imageView, progressContainer);
+// Scene scene = new Scene(root);
+// splashStage.setScene(scene);
+// splashStage.setResizable(false);
+// splashStage.centerOnScreen();
+// splashStage.show();
+// } catch (Exception e) {
+// log.error("显示启动屏幕失败", e);
+// }
+// });
+// }
+//
+// public void hide() {
+// Platform.runLater(() -> {
+// if (splashStage != null) {
+// splashStage.hide();
+// log.info("启动屏幕已隐藏");
+// }
+// });
+// }
+// }
\ No newline at end of file
diff --git a/erp_client_sb/src/main/java/com/tashow/erp/config/RestTemplateConfig.java b/erp_client_sb/src/main/java/com/tashow/erp/config/RestTemplateConfig.java
new file mode 100644
index 0000000..5d43dad
--- /dev/null
+++ b/erp_client_sb/src/main/java/com/tashow/erp/config/RestTemplateConfig.java
@@ -0,0 +1,19 @@
+package com.tashow.erp.config;
+
+import org.springframework.boot.web.client.RestTemplateBuilder;
+import org.springframework.context.annotation.Bean;
+import org.springframework.stereotype.Component;
+import org.springframework.web.client.RestTemplate;
+
+import java.time.Duration;
+
+@Component
+public class RestTemplateConfig {
+ @Bean
+ public RestTemplate restTemplate() {
+ RestTemplateBuilder builder = new RestTemplateBuilder();
+ builder.connectTimeout(Duration.ofSeconds(5));
+ builder.readTimeout(Duration.ofSeconds(10));
+ return builder.build();
+ }
+}
diff --git a/erp_client_sb/src/main/java/com/tashow/erp/config/WebConfig.java b/erp_client_sb/src/main/java/com/tashow/erp/config/WebConfig.java
new file mode 100644
index 0000000..7b24546
--- /dev/null
+++ b/erp_client_sb/src/main/java/com/tashow/erp/config/WebConfig.java
@@ -0,0 +1,23 @@
+package com.tashow.erp.config;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.CorsRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+/**
+ * Web配置类
+ * 配置CORS以支持JavaFX WebView的网络请求
+ */
+@Configuration
+public class WebConfig implements WebMvcConfigurer {
+
+ @Override
+ public void addCorsMappings(CorsRegistry registry) {
+ registry.addMapping("/**")
+ .allowedOrigins("http://localhost:8083", "http://127.0.0.1:8083", "file://")
+ .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
+ .allowedHeaders("*")
+ .allowCredentials(true)
+ .maxAge(3600);
+ }
+}
\ No newline at end of file
diff --git a/erp_client_sb/src/main/java/com/tashow/erp/config/WebMvcConfig.java b/erp_client_sb/src/main/java/com/tashow/erp/config/WebMvcConfig.java
new file mode 100644
index 0000000..1ee343f
--- /dev/null
+++ b/erp_client_sb/src/main/java/com/tashow/erp/config/WebMvcConfig.java
@@ -0,0 +1,27 @@
+package com.tashow.erp.config;
+
+import com.tashow.erp.security.LocalJwtAuthInterceptor;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+@Configuration
+public class WebMvcConfig implements WebMvcConfigurer {
+
+ @Autowired
+ private LocalJwtAuthInterceptor localJwtAuthInterceptor;
+
+ @Override
+ public void addInterceptors(InterceptorRegistry registry) {
+ registry.addInterceptor(localJwtAuthInterceptor)
+ .addPathPatterns("/api/**")
+ .excludePathPatterns(
+ "/api/login",
+ "/api/register",
+ "/api/verify",
+ "/api/check-username");
+ }
+}
+
+
diff --git a/erp_client_sb/src/main/java/com/tashow/erp/controller/AmazonController.java b/erp_client_sb/src/main/java/com/tashow/erp/controller/AmazonController.java
new file mode 100644
index 0000000..836ccce
--- /dev/null
+++ b/erp_client_sb/src/main/java/com/tashow/erp/controller/AmazonController.java
@@ -0,0 +1,89 @@
+package com.tashow.erp.controller;
+import com.tashow.erp.repository.AmazonProductRepository;
+import com.tashow.erp.service.IAmazonScrapingService;
+import com.tashow.erp.utils.ExcelParseUtil;
+import com.tashow.erp.utils.JsonData;
+import com.tashow.erp.utils.LoggerUtil;
+import org.slf4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+@RestController
+@RequestMapping("/api/amazon")
+public class AmazonController {
+ private static final Logger logger = LoggerUtil.getLogger(AmazonController.class);
+ @Autowired
+ private IAmazonScrapingService amazonScrapingService;
+ @Autowired
+ private AmazonProductRepository amazonProductRepository;
+ /**
+ * 批量获取亚马逊产品信息
+ */
+ @PostMapping("/products/batch")
+ public JsonData batchGetProducts(@RequestBody Object request) {
+ @SuppressWarnings("unchecked")
+ Map requestMap = (Map) request;
+ List asinList = (List) requestMap.get("asinList");
+ String batchId = (String) requestMap.get("batchId");
+ return JsonData.buildSuccess(amazonScrapingService.batchGetProductInfo(asinList, batchId));
+ }
+
+ /**
+ * 获取最新产品数据
+ */
+ @GetMapping("/products/latest")
+ public JsonData getLatestProducts() {
+ List> products = amazonProductRepository.findLatestProducts()
+ .parallelStream()
+ .map(entity -> {
+ Map map = new HashMap<>();
+ map.put("asin", entity.getAsin());
+ map.put("title", entity.getTitle());
+ map.put("price", entity.getPrice());
+ map.put("imageUrl", entity.getImageUrl());
+ map.put("productUrl", entity.getProductUrl());
+ map.put("brand", entity.getBrand());
+ map.put("category", entity.getCategory());
+ map.put("rating", entity.getRating());
+ map.put("reviewCount", entity.getReviewCount());
+ map.put("availability", entity.getAvailability());
+ map.put("seller", entity.getSeller());
+ map.put("shipper", entity.getSeller());
+ return map;
+ })
+ .collect(Collectors.toList());
+ Map result = new HashMap<>();
+ result.put("products", products);
+ result.put("total", products.size());
+ return JsonData.buildSuccess(result);
+ }
+
+ /**
+ * 解析Excel文件获取ASIN列表
+ */
+ @PostMapping("/import/asin")
+ public JsonData importAsinFromExcel(@RequestParam("file") MultipartFile file) {
+ if (file.isEmpty()) {
+ return JsonData.buildError("上传文件为空");
+ }
+ try {
+ List asinList = ExcelParseUtil.parseFirstColumn(file);
+ if (asinList.isEmpty()) {
+ return JsonData.buildError("未从文件中解析到ASIN数据");
+ }
+
+ Map result = new HashMap<>();
+ result.put("asinList", asinList);
+ result.put("total", asinList.size());
+ return JsonData.buildSuccess(result);
+
+ } catch (Exception e) {
+ logger.error("解析文件失败: {}", e.getMessage(), e);
+ return JsonData.buildError("解析失败: " + e.getMessage());
+ }
+ }
+}
\ No newline at end of file
diff --git a/erp_client_sb/src/main/java/com/tashow/erp/controller/AuthController.java b/erp_client_sb/src/main/java/com/tashow/erp/controller/AuthController.java
new file mode 100644
index 0000000..0cdba52
--- /dev/null
+++ b/erp_client_sb/src/main/java/com/tashow/erp/controller/AuthController.java
@@ -0,0 +1,193 @@
+package com.tashow.erp.controller;
+import com.tashow.erp.entity.AuthTokenEntity;
+import com.tashow.erp.entity.CacheDataEntity;
+import com.tashow.erp.repository.AuthTokenRepository;
+import com.tashow.erp.repository.CacheDataRepository;
+import com.tashow.erp.service.IAuthService;
+import com.tashow.erp.utils.JsonData;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+import java.util.Map;
+import java.util.Optional;
+
+@RestController
+@RequestMapping("/api")
+public class AuthController {
+ @Autowired
+ private IAuthService authService;
+ @Autowired
+ private AuthTokenRepository authTokenRepository;
+ @Autowired
+ private CacheDataRepository cacheDataRepository;
+ @PostMapping("/login")
+ public ResponseEntity> login(@RequestBody Map loginData) {
+ String username = (String) loginData.get("username");
+ String password = (String) loginData.get("password");
+ if (username == null || password == null) {
+ return ResponseEntity.ok(Map.of("code", 400, "message", "用户名和密码不能为空"));
+ }
+ Map result = authService.login(username, password);
+ Object success = result.get("success");
+ Object tokenObj = result.get("token");
+ if (Boolean.TRUE.equals(success) && tokenObj instanceof String token && token != null && !token.isEmpty()) {
+ HttpHeaders headers = new HttpHeaders();
+ headers.add(HttpHeaders.SET_COOKIE, buildHttpOnlyCookie("FX_TOKEN", token, 2 * 24 * 60 * 60));
+ return ResponseEntity.ok().headers(headers).body(result);
+ }
+ return ResponseEntity.ok(result);
+ }
+
+ @PostMapping("/verify")
+ public ResponseEntity> verifyToken(@RequestBody Map data) {
+ String token = (String) data.get("token");
+ if (token == null) {
+ return ResponseEntity.ok(Map.of("code", 400, "message", "token不能为空"));
+ }
+ Map result = authService.verifyToken(token);
+ return ResponseEntity.ok(result);
+ }
+
+ @PostMapping("/register")
+ public ResponseEntity> register(@RequestBody Map registerData) {
+ String username = (String) registerData.get("username");
+ String password = (String) registerData.get("password");
+ if (username == null || password == null) {
+ return ResponseEntity.ok(Map.of("code", 400, "message", "用户名和密码不能为空"));
+ }
+ Map result = authService.register(username, password);
+ Object success2 = result.get("success");
+ Object tokenObj2 = result.get("token");
+ if (Boolean.TRUE.equals(success2) && tokenObj2 instanceof String token && token != null && !token.isEmpty()) {
+ HttpHeaders headers = new HttpHeaders();
+ headers.add(HttpHeaders.SET_COOKIE, buildHttpOnlyCookie("FX_TOKEN", token, 2 * 24 * 60 * 60));
+ return ResponseEntity.ok().headers(headers).body(result);
+ }
+ return ResponseEntity.ok(result);
+ }
+
+ @GetMapping("/check-username")
+ public ResponseEntity> checkUsername(@RequestParam String username) {
+ if (username == null || username.trim().isEmpty()) {
+ return ResponseEntity.ok(Map.of("code", 400, "message", "用户名不能为空"));
+ }
+ boolean available = authService.checkUsername(username);
+ return ResponseEntity.ok(Map.of(
+ "code", 200,
+ "message", "检查成功",
+ "data", available
+ ));
+ }
+
+ /**
+ * 保存认证密钥
+ */
+ @PostMapping("/auth/save")
+ public JsonData saveAuth(@RequestBody Map data) {
+ String serviceName = (String) data.get("serviceName");
+ String authKey = (String) data.get("authKey");
+
+ if (serviceName == null || authKey == null) return JsonData.buildError("serviceName和authKey不能为空");
+
+ AuthTokenEntity entity = authTokenRepository.findByServiceName(serviceName).orElse(new AuthTokenEntity());
+ entity.setServiceName(serviceName);
+ entity.setToken(authKey);
+ authTokenRepository.save(entity);
+
+ return JsonData.buildSuccess("认证信息保存成功");
+ }
+
+
+ @GetMapping("/auth/get")
+ public JsonData getAuth(@RequestParam String serviceName) {
+ return JsonData.buildSuccess(authTokenRepository.findByServiceName(serviceName).map(AuthTokenEntity::getToken).orElse(null));
+ }
+
+ /**
+ * 删除认证密钥
+ */
+ @DeleteMapping("/auth/remove")
+ public JsonData removeAuth(@RequestParam String serviceName) {
+ authTokenRepository.findByServiceName(serviceName).ifPresent(authTokenRepository::delete);
+ return JsonData.buildSuccess("认证信息删除成功");
+ }
+
+ /**
+ * 保存缓存数据
+ */
+ @PostMapping("/cache/save")
+ public JsonData saveCache(@RequestBody Map data) {
+ String key = (String) data.get("key");
+ String value = (String) data.get("value");
+
+ if (key == null || value == null) return JsonData.buildError("key和value不能为空");
+
+ CacheDataEntity entity = cacheDataRepository.findByCacheKey(key).orElse(new CacheDataEntity());
+ entity.setCacheKey(key);
+ entity.setCacheValue(value);
+ cacheDataRepository.save(entity);
+
+ return JsonData.buildSuccess("缓存数据保存成功");
+ }
+
+ /**
+ * 获取缓存数据
+ */
+ @GetMapping("/cache/get")
+ public JsonData getCache(@RequestParam String key) {
+ return JsonData.buildSuccess(cacheDataRepository.findByCacheKey(key)
+ .map(CacheDataEntity::getCacheValue).orElse(null));
+ }
+
+ /**
+ * 删除缓存数据
+ */
+ @DeleteMapping("/cache/remove")
+ public JsonData removeCache(@RequestParam String key) {
+ cacheDataRepository.findByCacheKey(key).ifPresent(cacheDataRepository::delete);
+ return JsonData.buildSuccess("缓存数据删除成功");
+ }
+
+ /**
+ * 删除缓存数据 - POST方式
+ */
+ @PostMapping("/cache/delete")
+ public JsonData deleteCacheByPost(@RequestParam String key) {
+ if (key == null || key.trim().isEmpty()) {
+ return JsonData.buildError("key不能为空");
+ }
+ cacheDataRepository.deleteByCacheKey(key);
+ return JsonData.buildSuccess("缓存数据删除成功");
+ }
+
+ /**
+ * 会话引导:检查SQLite中是否存在token
+ */
+ @GetMapping("/session/bootstrap")
+ public ResponseEntity> sessionBootstrap() {
+ Optional tokenEntity = cacheDataRepository.findByCacheKey("token");
+ if (tokenEntity.isEmpty()) {
+ return ResponseEntity.status(401).body(Map.of("code", 401, "message", "无可用会话,请重新登录"));
+ }
+ String token = tokenEntity.get().getCacheValue();
+ if (token == null || token.isEmpty()) {
+ return ResponseEntity.status(401).body(Map.of("code", 401, "message", "无可用会话,请重新登录"));
+ }
+ HttpHeaders headers = new HttpHeaders();
+ headers.add(HttpHeaders.SET_COOKIE, buildHttpOnlyCookie("FX_TOKEN", token, 2 * 24 * 60 * 60));
+ return ResponseEntity.ok().headers(headers).body(Map.of("code", 200, "message", "会话已恢复"));
+ }
+
+ private String buildHttpOnlyCookie(String name, String value, int maxAgeSeconds) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(name).append("=").append(value).append(";");
+ sb.append(" Path=/;");
+ sb.append(" HttpOnly;");
+ sb.append(" SameSite=Strict;");
+ if (maxAgeSeconds > 0) {
+ sb.append(" Max-Age=").append(maxAgeSeconds).append(";");
+ }
+ return sb.toString();
+ }
+}
\ No newline at end of file
diff --git a/erp_client_sb/src/main/java/com/tashow/erp/controller/BanmaOrderController.java b/erp_client_sb/src/main/java/com/tashow/erp/controller/BanmaOrderController.java
new file mode 100644
index 0000000..d8cc465
--- /dev/null
+++ b/erp_client_sb/src/main/java/com/tashow/erp/controller/BanmaOrderController.java
@@ -0,0 +1,116 @@
+package com.tashow.erp.controller;
+import com.tashow.erp.fx.controller.JavaBridge;
+import com.tashow.erp.repository.BanmaOrderRepository;
+import com.tashow.erp.service.IBanmaOrderService;
+import com.tashow.erp.utils.ExcelExportUtil;
+import com.tashow.erp.utils.JsonData;
+import com.tashow.erp.utils.LoggerUtil;
+import org.slf4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.client.RestTemplate;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+@RestController
+@RequestMapping("/api/banma")
+public class BanmaOrderController {
+ private static final Logger logger = LoggerUtil.getLogger(BanmaOrderController.class);
+ @Autowired
+ IBanmaOrderService banmaOrderService;
+ @Autowired
+ BanmaOrderRepository banmaOrderRepository;
+ @Autowired
+ JavaBridge javaBridge;
+ @Autowired
+ RestTemplate restTemplate;
+ @GetMapping("/orders")
+ public ResponseEntity> getOrders(
+ @RequestParam(required = false, name = "startDate") String startDate,
+ @RequestParam(required = false, name = "endDate") String endDate,
+ @RequestParam(defaultValue = "1", name = "page") int page,
+ @RequestParam(defaultValue = "10", name = "pageSize") int pageSize,
+ @RequestParam(required = false, name = "batchId") String batchId,
+ @RequestParam(required = false, name = "shopIds") String shopIds) {
+ List shopIdList = shopIds != null ? java.util.Arrays.asList(shopIds.split(",")) : null;
+ Map result = banmaOrderService.getOrdersByPage(startDate, endDate, page, pageSize, batchId, shopIdList);
+ return ResponseEntity.ok(result);
+ }
+ /**
+ * 获取店铺列表
+ */
+ @GetMapping("/shops")
+ public JsonData getShops() {
+ try {
+ Map response = banmaOrderService.getShops();
+ return JsonData.buildSuccess(response);
+ } catch (Exception e) {
+ logger.error("获取店铺列表失败: {}", e.getMessage(), e);
+ return JsonData.buildError("获取店铺列表失败: " + e.getMessage());
+ }
+ }
+
+ /**
+ * 刷新斑马认证Token
+ */
+ @PostMapping("/refresh-token")
+ public JsonData refreshToken(){
+ try {
+ banmaOrderService.refreshToken();
+ return JsonData.buildSuccess("Token刷新成功");
+ } catch (Exception e) {
+ logger.error("刷新Token失败: {}", e.getMessage(), e);
+ return JsonData.buildError("Token刷新失败: " + e.getMessage());
+ }
+ }
+ /**
+ * 获取最新订单数据
+ */
+ @GetMapping("/orders/latest")
+ public JsonData getLatestOrders() {
+ com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();
+ List> orders = banmaOrderRepository.findLatestOrders()
+ .parallelStream()
+ .map(entity -> {
+ try {
+ @SuppressWarnings("unchecked")
+ Map data = mapper.readValue(entity.getOrderData(), Map.class);
+ return data;
+ } catch (Exception e) {
+ return new HashMap();
+ }
+ })
+ .filter(order -> !order.isEmpty())
+ .toList();
+
+ return JsonData.buildSuccess(Map.of("orders", orders, "total", orders.size()));
+ }
+
+ /**
+ * JavaFX专用:导出并保存Excel文件到桌面
+ */
+ @PostMapping("/export-and-save")
+ public JsonData exportAndSave(@RequestBody Map body) {
+ try {
+ @SuppressWarnings("unchecked")
+ List> orders = (List>) body.get("orders");
+ String[] headers = {"下单时间", "商品图片", "商品名称", "乐天订单号", "下单距今时间", "乐天订单金额/日元",
+ "购买数量", "税费/日元", "服务商回款抽点rmb", "商品番号", "1688采购订单号",
+ "采购金额/rmb", "国际运费/rmb", "国内物流公司", "国内物流单号", "日本物流单号", "地址状态"};
+
+ byte[] excelData = ExcelExportUtil.createExcelWithImages("斑马订单数据", headers, orders, 1, "productImage");
+ if (excelData.length == 0) return JsonData.buildError("生成Excel文件失败");
+
+ String fileName = String.format("斑马订单数据_%s.xlsx", java.time.LocalDate.now().toString());
+ String savedPath = javaBridge.saveExcelFileToDesktop(excelData, fileName);
+
+ return savedPath != null
+ ? JsonData.buildSuccess(Map.of("filePath", savedPath, "fileName", fileName))
+ : JsonData.buildError("保存文件失败,请检查权限");
+ } catch (Exception e) {
+ logger.error("导出并保存斑马订单Excel失败: {}", e.getMessage(), e);
+ return JsonData.buildError("导出并保存Excel失败: " + e.getMessage());
+ }
+ }
+}
\ No newline at end of file
diff --git a/erp_client_sb/src/main/java/com/tashow/erp/controller/ConfigController.java b/erp_client_sb/src/main/java/com/tashow/erp/controller/ConfigController.java
new file mode 100644
index 0000000..21ed894
--- /dev/null
+++ b/erp_client_sb/src/main/java/com/tashow/erp/controller/ConfigController.java
@@ -0,0 +1,30 @@
+package com.tashow.erp.controller;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.Map;
+
+/**
+ * 配置信息控制器
+ */
+@RestController
+@RequestMapping("/api/config")
+public class ConfigController {
+
+ @Value("${api.server.base-url}")
+ private String serverBaseUrl;
+
+ /**
+ * 获取服务器配置
+ */
+ @GetMapping("/server")
+ public Map getServerConfig() {
+ return Map.of(
+ "baseUrl", serverBaseUrl,
+ "sseUrl", serverBaseUrl + "/monitor/account/events"
+ );
+ }
+}
\ No newline at end of file
diff --git a/erp_client_sb/src/main/java/com/tashow/erp/controller/DeviceProxyController.java b/erp_client_sb/src/main/java/com/tashow/erp/controller/DeviceProxyController.java
new file mode 100644
index 0000000..1f1c49f
--- /dev/null
+++ b/erp_client_sb/src/main/java/com/tashow/erp/controller/DeviceProxyController.java
@@ -0,0 +1,66 @@
+package com.tashow.erp.controller;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.*;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.client.RestTemplate;
+import com.tashow.erp.utils.ApiForwarder;
+import com.tashow.erp.utils.DeviceUtils;
+import java.util.Map;
+import java.util.HashMap;
+
+/**
+ * 设备管理代理控制器
+ * 简化职责:透传请求到后端服务
+ */
+@RestController
+public class DeviceProxyController {
+
+ @Autowired
+ private RestTemplate restTemplate;
+
+ @Value("${api.server.base-url}")
+ private String serverBaseUrl;
+
+ /**
+ * 注册设备
+ */
+ @Autowired
+ private ApiForwarder apiForwarder;
+
+ @PostMapping("/api/device/register")
+ public ResponseEntity> deviceRegister(@RequestBody Map body, @RequestHeader(value = "Authorization", required = false) String auth) {
+ Map deviceData = new HashMap<>(body);
+ deviceData.put("deviceId", DeviceUtils.generateDeviceId());
+ return apiForwarder.post("/monitor/device/register", deviceData, auth);
+ }
+
+
+
+ @PostMapping("/api/device/remove")
+ public ResponseEntity> deviceRemove(@RequestBody Map body, @RequestHeader(value = "Authorization", required = false) String auth) {
+ return apiForwarder.post("/monitor/device/remove", body, auth);
+ }
+
+ /**
+ * 设备心跳
+ */
+ @PostMapping("/api/device/heartbeat")
+ public ResponseEntity> deviceHeartbeat(@RequestBody Map body, @RequestHeader(value = "Authorization", required = false) String auth) {
+ return apiForwarder.post("/monitor/device/heartbeat", body, auth);
+ }
+
+ @GetMapping("/api/device/quota")
+ public ResponseEntity> deviceQuota(@RequestParam("username") String username, @RequestHeader(value = "Authorization", required = false) String auth) {
+ return apiForwarder.get("/monitor/device/quota?username=" + username, auth);
+ }
+
+ @GetMapping("/api/device/list")
+ public ResponseEntity> deviceList(@RequestParam("username") String username, @RequestHeader(value = "Authorization", required = false) String auth) {
+ return apiForwarder.get("/monitor/device/list?username=" + username, auth);
+ }
+
+
+}
+
+
diff --git a/erp_client_sb/src/main/java/com/tashow/erp/controller/GenmaiController.java b/erp_client_sb/src/main/java/com/tashow/erp/controller/GenmaiController.java
new file mode 100644
index 0000000..5205ed4
--- /dev/null
+++ b/erp_client_sb/src/main/java/com/tashow/erp/controller/GenmaiController.java
@@ -0,0 +1,25 @@
+package com.tashow.erp.controller;
+
+import com.tashow.erp.service.IGenmaiService;
+import com.tashow.erp.utils.JsonData;
+import com.tashow.erp.utils.LoggerUtil;
+import org.slf4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+@RestController
+@RequestMapping("/api/genmai")
+public class GenmaiController {
+ private static final Logger logger = LoggerUtil.getLogger(GenmaiController.class);
+
+ @Autowired
+ private IGenmaiService genmaiService;
+
+ /**
+ * 打开跟卖精灵网页
+ */
+ @PostMapping("/open")
+ public void openGenmaiWebsite() {
+ genmaiService.openGenmaiWebsite();
+ }
+}
\ No newline at end of file
diff --git a/erp_client_sb/src/main/java/com/tashow/erp/controller/HomeController.java b/erp_client_sb/src/main/java/com/tashow/erp/controller/HomeController.java
new file mode 100644
index 0000000..ef7e85a
--- /dev/null
+++ b/erp_client_sb/src/main/java/com/tashow/erp/controller/HomeController.java
@@ -0,0 +1,18 @@
+package com.tashow.erp.controller;
+
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+
+@Controller
+public class HomeController {
+
+ @GetMapping("/")
+ public String home() {
+ return "redirect:/html/erp-dashboard.html";
+ }
+
+ @GetMapping("/erp")
+ public String erp() {
+ return "redirect:/html/erp-dashboard.html";
+ }
+}
\ No newline at end of file
diff --git a/erp_client_sb/src/main/java/com/tashow/erp/controller/ProxyController.java b/erp_client_sb/src/main/java/com/tashow/erp/controller/ProxyController.java
new file mode 100644
index 0000000..a6bdfa0
--- /dev/null
+++ b/erp_client_sb/src/main/java/com/tashow/erp/controller/ProxyController.java
@@ -0,0 +1,116 @@
+package com.tashow.erp.controller;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.*;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.client.RestTemplate;
+
+import java.util.Map;
+
+/**
+ * 代理控制器,用于解决CORS跨域问题
+ */
+@RestController
+@RequestMapping("/api/proxy")
+public class ProxyController {
+
+ @Autowired
+ private RestTemplate restTemplate;
+
+ /**
+ * 代理获取图片
+ * @param requestBody 包含图片URL的请求体
+ * @return 图片字节数组
+ */
+ @PostMapping("/image")
+ public ResponseEntity proxyImage(@RequestBody Map requestBody) {
+ String imageUrl = requestBody.get("imageUrl");
+ if (imageUrl == null || imageUrl.isEmpty()) {
+ return ResponseEntity.badRequest().build();
+ }
+
+ try {
+ // 设置请求头
+ HttpHeaders headers = new HttpHeaders();
+ headers.set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36");
+ headers.set("Accept", "image/jpeg, image/png, image/webp, image/*");
+
+ HttpEntity entity = new HttpEntity<>(headers);
+
+ // 发送请求获取图片
+ ResponseEntity response = restTemplate.exchange(
+ imageUrl,
+ HttpMethod.GET,
+ entity,
+ byte[].class
+ );
+
+ // 设置响应头
+ HttpHeaders responseHeaders = new HttpHeaders();
+ responseHeaders.setContentType(MediaType.IMAGE_JPEG);
+
+ return new ResponseEntity<>(response.getBody(), responseHeaders, HttpStatus.OK);
+ } catch (Exception e) {
+ e.printStackTrace();
+ return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
+ }
+ }
+
+ /**
+ * 通过URL参数代理获取图片(为JavaFX WebView优化)
+ * @param imageUrl 图片URL
+ * @return 图片字节数组
+ */
+ @GetMapping("/image-url")
+ public ResponseEntity proxyImageByUrl(@RequestParam("url") String imageUrl) {
+ if (imageUrl == null || imageUrl.isEmpty()) {
+ System.err.println("图片代理请求失败: 图片URL为空");
+ return ResponseEntity.badRequest().build();
+ }
+
+ System.out.println("代理图片请求: " + imageUrl);
+
+ try {
+ // 设置请求头
+ HttpHeaders headers = new HttpHeaders();
+ headers.set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36");
+ headers.set("Accept", "image/jpeg, image/png, image/webp, image/*");
+ headers.set("Referer", "https://item.rakuten.co.jp/");
+
+ HttpEntity entity = new HttpEntity<>(headers);
+
+ // 发送请求获取图片
+ ResponseEntity response = restTemplate.exchange(
+ imageUrl,
+ HttpMethod.GET,
+ entity,
+ byte[].class
+ );
+
+ System.out.println("图片代理成功,响应大小: " + (response.getBody() != null ? response.getBody().length : 0) + " bytes");
+
+ // 设置响应头,支持缓存以提升JavaFX WebView性能
+ HttpHeaders responseHeaders = new HttpHeaders();
+
+ // 尝试从原始响应中获取Content-Type
+ String contentType = response.getHeaders().getFirst("Content-Type");
+ if (contentType != null && contentType.startsWith("image/")) {
+ responseHeaders.setContentType(MediaType.parseMediaType(contentType));
+ } else {
+ responseHeaders.setContentType(MediaType.IMAGE_JPEG);
+ }
+
+ // 设置缓存头以提升性能
+ responseHeaders.setCacheControl("max-age=3600");
+ responseHeaders.set("Access-Control-Allow-Origin", "*");
+
+ return new ResponseEntity<>(response.getBody(), responseHeaders, HttpStatus.OK);
+ } catch (Exception e) {
+ System.err.println("图片代理失败: " + imageUrl + " - " + e.getMessage());
+ e.printStackTrace();
+ return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
+ }
+ }
+}
\ No newline at end of file
diff --git a/erp_client_sb/src/main/java/com/tashow/erp/controller/RakutenController.java b/erp_client_sb/src/main/java/com/tashow/erp/controller/RakutenController.java
new file mode 100644
index 0000000..e7a6f66
--- /dev/null
+++ b/erp_client_sb/src/main/java/com/tashow/erp/controller/RakutenController.java
@@ -0,0 +1,163 @@
+package com.tashow.erp.controller;
+
+import com.tashow.erp.model.RakutenProduct;
+import com.tashow.erp.model.SearchResult;
+import com.tashow.erp.repository.RakutenProductRepository;
+import com.tashow.erp.service.Alibaba1688Service;
+import com.tashow.erp.service.IRakutenCacheService;
+import com.tashow.erp.service.RakutenScrapingService;
+import com.tashow.erp.service.impl.Alibaba1688ServiceImpl;
+import com.tashow.erp.utils.DataReportUtil;
+import com.tashow.erp.utils.ExcelParseUtil;
+import com.tashow.erp.utils.JsonData;
+import com.tashow.erp.utils.QiniuUtil;
+import com.tashow.erp.fx.controller.JavaBridge;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.util.CollectionUtils;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+import org.springframework.web.client.RestTemplate;
+
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneOffset;
+import java.util.*;
+import java.util.stream.Collectors;
+import java.util.Base64;
+
+@RestController
+@RequestMapping("/api/rakuten")
+@Slf4j
+public class RakutenController {
+ @Autowired
+ private RakutenScrapingService rakutenScrapingService;
+ @Autowired
+ private Alibaba1688Service alibaba1688Service;
+ @Autowired
+ private IRakutenCacheService rakutenCacheService;
+ @Autowired
+ private JavaBridge javaBridge;
+ @Autowired
+ private DataReportUtil dataReportUtil;
+ /**
+ * 获取乐天商品数据(支持单个店铺名或 Excel 文件上传)
+ *
+ * @param file 可选,Excel 文件(首列为店铺名)
+ * @param shopName 可选,单个店铺名
+ * @param batchId 可选,批次号
+ * @return JsonData 响应
+ */
+ @PostMapping(value = "/products")
+ public JsonData getProducts(@RequestParam(value = "file", required = false) MultipartFile file, @RequestParam(value = "shopName", required = false) String shopName, @RequestParam(value = "batchId", required = false) String batchId) {
+ try {
+ // 1. 获取店铺名集合(优先 shopName,其次 Excel)
+ List shopNames = Optional.ofNullable(shopName).filter(s -> !s.trim().isEmpty()).map(s -> List.of(s.trim())).orElseGet(() -> file != null ? ExcelParseUtil.parseFirstColumn(file) : new ArrayList<>());
+
+ if (CollectionUtils.isEmpty(shopNames)) {
+ return JsonData.buildError("未从 Excel 中解析到店铺名,且 shopName 参数为空");
+ }
+
+ List allProducts = new ArrayList<>();
+ List skippedShops = new ArrayList<>();
+
+ // 2. 遍历店铺,优先缓存,缺失则爬取
+ for (String currentShopName : shopNames) {
+ if (rakutenCacheService.hasRecentData(currentShopName)) {
+ // 从缓存获取
+ List cached = rakutenCacheService.getProductsByShopName(currentShopName).stream().filter(p -> currentShopName.equals(p.getOriginalShopName())).toList();
+ rakutenCacheService.updateSpecificProductsSessionId(cached, batchId);
+ allProducts.addAll(cached);
+ skippedShops.add(currentShopName);
+ log.info("使用缓存数据,店铺: {},数量: {}", currentShopName, cached.size());
+ } else {
+ // 爬取新数据
+ log.info("采集新数据: {}", currentShopName);
+ List fresh = rakutenScrapingService.scrapeProductsWithSearch(currentShopName);
+ fresh.forEach(p -> p.setOriginalShopName(currentShopName));
+ allProducts.addAll(fresh);
+ log.info("采集完成: {}, 数量: {}", currentShopName, fresh.size());
+ }
+ }
+
+ // 3. 处理新采集的数据,存储并生成 sessionId
+ List newProducts = allProducts.stream().filter(p -> !skippedShops.contains(p.getOriginalShopName())).toList();
+
+ if (!newProducts.isEmpty()) {
+ // 使用已有 sessionId 保存
+ rakutenCacheService.saveProductsWithSessionId(newProducts, batchId);
+ }
+ // 4. 上报缓存数据使用情况
+ int cachedCount = allProducts.size() - newProducts.size();
+ if (cachedCount > 0) {
+ dataReportUtil.reportDataCollection("RAKUTEN_CACHE", cachedCount, "0");
+ }
+
+ // 5. 如果是单店铺查询,只返回该店铺的商品
+ List finalProducts = (shopName != null && !shopName.trim().isEmpty()) ? allProducts.stream().filter(p -> shopName.trim().equals(p.getOriginalShopName())).toList() : allProducts;
+
+ return JsonData.buildSuccess(Map.of("products", finalProducts, "total", finalProducts.size(), "sessionId", batchId, "skippedShops", skippedShops, "newProductsCount", newProducts.size()));
+ } catch (Exception e) {
+ log.error("获取乐天商品失败", e);
+ return JsonData.buildError("获取乐天商品失败: " + e.getMessage());
+ }
+ }
+
+ /**
+ * 1688识图搜索API - 自动保存1688搜索结果
+ */
+ @PostMapping("/search1688")
+ public JsonData search1688(@RequestBody Map params) {
+ String imageUrl = (String) params.get("imageUrl");
+ String sessionId = (String) params.get("sessionId");
+ try {
+ SearchResult result = alibaba1688Service.get1688Detail(imageUrl);
+ rakutenScrapingService.update1688DataByImageUrl(result, sessionId, imageUrl);
+ return JsonData.buildSuccess(result);
+ } catch (Exception e) {
+ log.error("1688识图搜索失败", e);
+ return JsonData.buildError("搜索失败: " + e.getMessage());
+ }
+ }
+
+
+ @GetMapping("/products/latest")
+ public JsonData getLatestProducts() {
+ try {
+ List> products = rakutenScrapingService.getLatestProductsForDisplay();
+ return JsonData.buildSuccess(Map.of("products", products, "total", products.size()));
+ } catch (Exception e) {
+
+ e.printStackTrace();
+ log.info("获取最新商品数据失败", e);
+ return JsonData.buildError("获取最新数据失败: " + e.getMessage());
+ }
+ }
+ @PostMapping("/export-and-save")
+ public JsonData exportAndSave(@RequestBody Map body) {
+ try {
+ @SuppressWarnings("unchecked") List> products = (List>) body.get("products");
+ if (CollectionUtils.isEmpty(products)) return JsonData.buildError("没有可导出的数据");
+
+ boolean skipImages = Optional.ofNullable((Boolean) body.get("skipImages")).orElse(false);
+ String fileName = Optional.ofNullable((String) body.get("fileName")).filter(name -> !name.trim().isEmpty()).orElse("乐天商品数据_" + java.time.LocalDate.now() + ".xlsx");
+
+ String[] headers = {"店铺名", "商品图片", "商品链接", "排名", "商品标题", "价格", "1688识图链接", "1688价格", "1688重量"};
+ byte[] excelData = com.tashow.erp.utils.ExcelExportUtil.createExcelWithImages("乐天商品数据", headers, products, skipImages ? -1 : 1, skipImages ? null : "imgUrl");
+
+ if (excelData == null || excelData.length == 0) return JsonData.buildError("生成Excel失败");
+
+ String savedPath = javaBridge.saveExcelFileToDesktop(excelData, fileName);
+ if (savedPath == null) return JsonData.buildError("保存文件失败");
+
+ log.info("导出Excel: {}, 记录数: {}", fileName, products.size());
+ return JsonData.buildSuccess(Map.of("filePath", savedPath, "fileName", fileName, "recordCount", products.size(), "hasImages", !skipImages));
+
+ } catch (Exception e) {
+ log.error("导出Excel失败", e);
+ return JsonData.buildError("导出Excel失败: " + e.getMessage());
+ }
+ }
+
+
+}
\ No newline at end of file
diff --git a/erp_client_sb/src/main/java/com/tashow/erp/controller/UpdateController.java b/erp_client_sb/src/main/java/com/tashow/erp/controller/UpdateController.java
new file mode 100644
index 0000000..4be22ff
--- /dev/null
+++ b/erp_client_sb/src/main/java/com/tashow/erp/controller/UpdateController.java
@@ -0,0 +1,603 @@
+package com.tashow.erp.controller;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.tashow.erp.entity.UpdateStatusEntity;
+import com.tashow.erp.repository.UpdateStatusRepository;
+import com.tashow.erp.service.IAuthService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.web.bind.annotation.*;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.net.URL;
+import java.net.URLConnection;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Web版本更新控制器
+ *
+ * @author Claude
+ */
+@RestController
+@RequestMapping("/api/update")
+public class UpdateController {
+ @Value("${project.version:2.3.6}")
+ private String currentVersion;
+ @Value("${project.build.time:}")
+ private String buildTime;
+ private static final ObjectMapper objectMapper = new ObjectMapper();
+ @Autowired
+ private IAuthService authService;
+
+ @Autowired
+ private UpdateStatusRepository updateStatusRepository;
+
+ // 下载进度跟踪
+ private volatile int downloadProgress = 0;
+ private volatile long downloadedBytes = 0;
+ private volatile long totalBytes = 0;
+ private volatile String downloadStatus = "ready"; // ready, downloading, completed, failed, cancelled
+ private volatile String downloadSpeed = "0 KB/s";
+ private volatile long downloadStartTime = 0;
+ private volatile boolean downloadCancelled = false;
+
+ /**
+ * 获取当前版本号
+ *
+ * @return 当前版本号
+ */
+ @GetMapping("/version")
+ public Map getVersion() {
+ Map result = new HashMap<>();
+ result.put("success", true);
+ result.put("currentVersion", currentVersion);
+ result.put("buildTime", buildTime);
+ return result;
+ }
+
+ /**
+ * 检查版本更新
+ *
+ * @return 版本更新信息
+ */
+ @GetMapping("/check")
+ public Map checkUpdate() {
+ Map result = new HashMap<>();
+ try {
+ String response = authService.checkVersion(currentVersion);
+ // 解析JSON响应
+ JsonNode responseNode = objectMapper.readTree(response);
+
+ result.put("success", true);
+ result.put("currentVersion", currentVersion);
+
+ // 检查响应格式并提取数据
+ JsonNode dataNode = null;
+ dataNode = responseNode.get("data");
+
+ // 直接使用服务器返回的needUpdate字段
+ boolean needUpdate = dataNode.has("needUpdate") &&
+ dataNode.get("needUpdate").asBoolean();
+ // 添加跳过版本信息,让前端自己判断
+ String skippedVersion = updateStatusRepository.findByKeyName("skippedUpdateVersion")
+ .map(entity -> entity.getValueData()).orElse(null);
+ result.put("skippedVersion", skippedVersion);
+
+ result.put("needUpdate", needUpdate);
+
+ // 获取最新版本号
+ if (dataNode.has("latestVersion")) {
+ result.put("latestVersion", dataNode.get("latestVersion").asText());
+ }
+
+ if (needUpdate) {
+ if (dataNode.has("downloadUrl")) {
+ String downloadUrl = dataNode.get("downloadUrl").asText();
+ result.put("downloadUrl", downloadUrl);
+ saveUpdateInfo("downloadUrl", downloadUrl);
+ }
+ if (dataNode.has("updateTime")) {
+ // 转换时间戳为可读格式
+ long updateTime = dataNode.get("updateTime").asLong();
+ java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+ String releaseDate = sdf.format(new java.util.Date(updateTime));
+ result.put("releaseDate", releaseDate);
+ saveUpdateInfo("releaseDate", releaseDate);
+ }
+ if (dataNode.has("updateNotes")) {
+ String updateNotes = dataNode.get("updateNotes").asText();
+ result.put("updateNotes", updateNotes);
+ saveUpdateInfo("updateNotes", updateNotes);
+ }
+ if (dataNode.has("fileSize")) {
+ String fileSize = dataNode.get("fileSize").asText();
+ result.put("fileSize", fileSize);
+ saveUpdateInfo("fileSize", fileSize);
+ }
+ if (dataNode.has("latestVersion")) {
+ String latestVersion = dataNode.get("latestVersion").asText();
+ saveUpdateInfo("latestVersion", latestVersion);
+ }
+ } else {
+ // 如果不需要更新,清理之前保存的更新信息
+ clearUpdateInfo();
+ }
+
+ // 从SQLite读取之前保存的更新信息
+ result.putAll(getStoredUpdateInfo());
+
+
+ } catch (Exception e) {
+ result.put("success", false);
+ result.put("message", "版本检查失败:" + e.getMessage());
+ authService.reportError("UPDATE_CHECK_ERROR", "Web版本检查失败", e);
+ }
+
+ return result;
+ }
+
+ /**
+ * 保存更新信息到SQLite
+ */
+ private void saveUpdateInfo(String key, String value) {
+ try {
+ UpdateStatusEntity entity = updateStatusRepository.findByKeyName(key)
+ .orElse(new UpdateStatusEntity());
+ entity.setKeyName(key);
+ entity.setValueData(value);
+ updateStatusRepository.save(entity);
+ } catch (Exception e) {
+ System.err.println("保存更新信息失败: " + key + " = " + value + ", 错误: " + e.getMessage());
+ }
+ }
+
+ /**
+ * 从SQLite获取存储的更新信息
+ */
+ private Map getStoredUpdateInfo() {
+ Map info = new HashMap<>();
+ try {
+ String[] keys = {"downloadUrl", "releaseDate", "updateNotes", "fileSize", "latestVersion"};
+ for (String key : keys) {
+ updateStatusRepository.findByKeyName(key).ifPresent(entity ->
+ info.put(key, entity.getValueData())
+ );
+ }
+ } catch (Exception e) {
+ System.err.println("读取更新信息失败: " + e.getMessage());
+ }
+ return info;
+ }
+
+ /**
+ * 清理更新信息
+ */
+ private void clearUpdateInfo() {
+ try {
+ String[] keys = {"downloadUrl", "releaseDate", "updateNotes", "fileSize", "latestVersion"};
+ for (String key : keys) {
+ updateStatusRepository.findByKeyName(key).ifPresent(entity ->
+ updateStatusRepository.delete(entity)
+ );
+ }
+ } catch (Exception e) {
+ System.err.println("清理更新信息失败: " + e.getMessage());
+ }
+ }
+
+
+ /**
+ * 获取下载进度
+ *
+ * @return 下载进度信息
+ */
+ @GetMapping("/progress")
+ public Map getDownloadProgress() {
+ Map result = new HashMap<>();
+ result.put("success", true);
+ result.put("progress", downloadProgress);
+ result.put("status", downloadStatus);
+ result.put("downloadedBytes", downloadedBytes);
+ result.put("totalBytes", totalBytes);
+ result.put("downloadSpeed", downloadSpeed);
+ result.put("downloadedMB", String.format("%.1f", downloadedBytes / 1024.0 / 1024.0));
+ result.put("totalMB", String.format("%.1f", totalBytes / 1024.0 / 1024.0));
+
+ return result;
+ }
+
+ /**
+ * 重置下载状态
+ *
+ * @return 重置结果
+ */
+ @PostMapping("/reset")
+ public Map resetDownloadStatus() {
+ Map result = new HashMap<>();
+ downloadProgress = 0;
+ downloadedBytes = 0;
+ totalBytes = 0;
+ downloadStatus = "ready";
+ downloadSpeed = "0 KB/s";
+ downloadStartTime = 0;
+ downloadCancelled = false;
+ tempUpdateFilePath = null;
+
+ result.put("success", true);
+ result.put("message", "下载状态已重置");
+
+ return result;
+ }
+
+ /**
+ * 取消下载
+ *
+ * @return 取消结果
+ */
+ @PostMapping("/cancel")
+ public Map cancelDownload() {
+ Map result = new HashMap<>();
+
+ if ("downloading".equals(downloadStatus)) {
+ downloadCancelled = true;
+ downloadStatus = "cancelled";
+ downloadSpeed = "已取消";
+ result.put("success", true);
+ result.put("message", "下载已取消");
+ } else if ("completed".equals(downloadStatus)) {
+ // 下载完成时点击取消,不删除文件,只是标记为稍后更新
+ result.put("success", true);
+ result.put("message", "已设置为稍后更新,文件保留");
+ System.out.println("用户选择稍后更新,文件路径: " + tempUpdateFilePath);
+ } else {
+ result.put("success", false);
+ result.put("message", "无效的操作状态");
+ }
+
+ return result;
+ }
+
+ /**
+ * 完全清除更新文件和状态
+ *
+ * @return 清除结果
+ */
+ @PostMapping("/clear")
+ public Map clearUpdateFiles() {
+ Map result = new HashMap<>();
+
+ if (tempUpdateFilePath != null) {
+ try {
+ java.io.File tempFile = new java.io.File(tempUpdateFilePath);
+ if (tempFile.exists()) {
+ tempFile.delete();
+ System.out.println("已删除更新文件: " + tempUpdateFilePath);
+ }
+ } catch (Exception e) {
+ System.err.println("删除临时文件失败: " + e.getMessage());
+ }
+ }
+
+ // 重置状态
+ downloadProgress = 0;
+ downloadedBytes = 0;
+ totalBytes = 0;
+ downloadStatus = "ready";
+ downloadSpeed = "0 KB/s";
+ downloadCancelled = false;
+ tempUpdateFilePath = null;
+ result.put("success", true);
+ result.put("message", "更新文件和状态已清除");
+
+ return result;
+ }
+ /**
+ * 验证更新文件是否存在
+ *
+ * @return 验证结果
+ */
+ @GetMapping("/verify-file")
+ public Map verifyUpdateFile() {
+ Map result = new HashMap<>();
+
+ try {
+ boolean fileExists = false;
+ String filePath = "";
+
+ if (tempUpdateFilePath != null) {
+ java.io.File updateFile = new java.io.File(tempUpdateFilePath);
+ fileExists = updateFile.exists();
+ filePath = tempUpdateFilePath;
+
+ if (fileExists) {
+ result.put("fileSize", updateFile.length());
+ result.put("lastModified", new java.util.Date(updateFile.lastModified()));
+ }
+ }
+
+ result.put("success", true);
+ result.put("fileExists", fileExists);
+ result.put("filePath", filePath);
+ result.put("downloadStatus", downloadStatus);
+
+ System.out.println("验证更新文件: " + filePath + ", 存在: " + fileExists);
+
+ } catch (Exception e) {
+ result.put("success", false);
+ result.put("message", "验证文件失败:" + e.getMessage());
+ result.put("fileExists", false);
+ }
+
+ return result;
+ }
+
+ /**
+ * 用户确认后执行安装
+ *
+ * @return 安装结果
+ */
+ @PostMapping("/install")
+ public Map installUpdate() {
+ Map result = new HashMap<>();
+
+ if (tempUpdateFilePath == null || !new java.io.File(tempUpdateFilePath).exists()) {
+ result.put("success", false);
+ result.put("message", "更新文件不存在,请重新下载");
+ return result;
+ }
+
+ try {
+ result.put("success", true);
+ result.put("message", "开始安装更新...");
+
+ // 异步执行安装,避免阻塞响应
+ new Thread(() -> {
+ String updateScript = createUpdateScript(null, tempUpdateFilePath);
+ if (updateScript != null) {
+ executeUpdateAndExit(updateScript);
+ }
+ }).start();
+
+ } catch (Exception e) {
+ result.put("success", false);
+ result.put("message", "启动安装失败:" + e.getMessage());
+ }
+
+ return result;
+ }
+
+ /**
+ * 自动更新:下载、替换、重启
+ *
+ * @return 更新结果
+ */
+ @PostMapping("/auto-update")
+ public Map autoUpdate(@RequestBody Map request) {
+ Map result = new HashMap<>();
+
+ try {
+ String downloadUrl = request.get("downloadUrl");
+ result.put("success", true);
+ result.put("message", "开始自动更新...");
+ result.put("downloadUrl", downloadUrl);
+ String finalDownloadUrl = downloadUrl;
+ new Thread(() -> {
+ performUpdate(finalDownloadUrl);
+ }).start();
+
+ } catch (Exception e) {
+ result.put("success", false);
+ result.put("message", "启动更新失败:" + e.getMessage());
+ }
+
+ return result;
+ }
+
+ /**
+ * 执行更新过程
+ */
+ private void performUpdate(String downloadUrl) {
+ String tempUpdateFile = downloadUpdate(downloadUrl);
+ if (tempUpdateFile == null) {
+ downloadStatus = "failed";
+ return;
+ }
+
+ // 下载完成后不自动重启,等待用户确认
+ downloadStatus = "completed";
+ System.out.println("下载完成,等待用户确认安装...");
+
+ // 将下载文件路径保存,供后续安装使用
+ this.tempUpdateFilePath = tempUpdateFile;
+ }
+
+ private String tempUpdateFilePath = null;
+
+
+ /**
+ * 下载更新文件
+ */
+ private String downloadUpdate(String downloadUrl) {
+ try {
+ downloadStatus = "downloading";
+ downloadProgress = 0;
+ downloadedBytes = 0;
+ downloadStartTime = System.currentTimeMillis();
+
+ URL url = new URL(downloadUrl);
+ URLConnection connection = url.openConnection();
+ connection.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36");
+ connection.setConnectTimeout(10000);
+ connection.setReadTimeout(30000);
+
+ // 获取文件大小
+ totalBytes = connection.getContentLength();
+ if (totalBytes <= 0) {
+ totalBytes = 70 * 1024 * 1024; // 默认70MB
+ }
+
+ String tempDir = System.getProperty("java.io.tmpdir");
+ String updateFileName = "erp_update_" + System.currentTimeMillis() + ".exe";
+ String tempUpdatePath = Paths.get(tempDir, updateFileName).toString();
+
+ System.out.println("开始下载更新文件,大小: " + (totalBytes / 1024 / 1024) + "MB");
+
+ try (InputStream inputStream = connection.getInputStream();
+ FileOutputStream outputStream = new FileOutputStream(tempUpdatePath)) {
+
+ byte[] buffer = new byte[8192];
+ int bytesRead;
+ long lastSpeedUpdate = System.currentTimeMillis();
+ long lastDownloadedBytes = 0;
+ while ((bytesRead = inputStream.read(buffer)) != -1) {
+ if (downloadCancelled) {
+ System.out.println("下载已取消,停止下载进程");
+ downloadStatus = "cancelled";
+ downloadSpeed = "已取消";
+ outputStream.close();
+ try {
+ java.io.File partialFile = new java.io.File(tempUpdatePath);
+ if (partialFile.exists()) {
+ partialFile.delete();
+ }
+ } catch (Exception e) {
+ System.err.println("删除部分下载文件失败: " + e.getMessage());
+ }
+ return null;
+ }
+ outputStream.write(buffer, 0, bytesRead);
+ downloadedBytes += bytesRead;
+
+ // 计算下载进度
+ downloadProgress = (int) ((downloadedBytes * 100) / totalBytes);
+ // 每秒计算一次下载速度
+ long currentTime = System.currentTimeMillis();
+ if (currentTime - lastSpeedUpdate >= 1000) {
+ long speedBytes = downloadedBytes - lastDownloadedBytes;
+ double speedKB = speedBytes / 1024.0;
+
+ if (speedKB >= 1024) {
+ downloadSpeed = String.format("%.1f MB/s", speedKB / 1024.0);
+ } else {
+ downloadSpeed = String.format("%.1f KB/s", speedKB);
+ }
+
+ lastSpeedUpdate = currentTime;
+ lastDownloadedBytes = downloadedBytes;
+
+ System.out.println(String.format("下载进度: %d%% (%d/%d MB) 速度: %s",
+ downloadProgress,
+ downloadedBytes / 1024 / 1024,
+ totalBytes / 1024 / 1024,
+ downloadSpeed));
+ }
+
+ // 防止阻塞UI线程
+ if (downloadedBytes % (1024 * 1024) == 0) { // 每MB休息一下
+ Thread.sleep(10);
+ }
+ }
+
+ downloadStatus = "completed";
+ downloadProgress = 100;
+ downloadSpeed = "完成";
+ System.out.println("下载完成: " + (downloadedBytes / 1024 / 1024) + "MB");
+ }
+
+ return tempUpdatePath;
+ } catch (Exception e) {
+ downloadStatus = "failed";
+ downloadSpeed = "失败";
+ System.err.println("下载失败: " + e.getMessage());
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ /**
+ * 创建更新脚本
+ */
+ private String createUpdateScript(String currentExePath, String tempUpdateFile) {
+ try {
+ String tempDir = System.getProperty("java.io.tmpdir");
+ String scriptPath = Paths.get(tempDir, "update_erp.bat").toString();
+
+ File currentDir = new File(System.getProperty("user.dir"));
+ File targetExe = new File(currentDir, "erpClient.exe");
+ String targetPath = targetExe.getAbsolutePath();
+
+ StringBuilder script = new StringBuilder();
+ script.append("@echo off\r\n");
+ script.append("chcp 65001 > nul\r\n");
+ script.append("echo 正在等待程序完全退出...\r\n");
+ script.append("timeout /t 1 /nobreak > nul\r\n");
+ script.append("echo 开始更新程序文件...\r\n");
+ script.append("if exist \"").append(targetPath).append("\" (");
+ script.append(" move \"").append(targetPath).append("\" \"").append(targetPath).append(".backup\"");
+ script.append(" )\r\n");
+ script.append("move \"").append(tempUpdateFile).append("\" \"").append(targetPath).append("\"\r\n");
+ script.append("if exist \"").append(targetPath).append("\" (\r\n");
+ script.append(" echo 更新成功!程序将在1秒后重新启动...\r\n");
+ script.append(" timeout /t 1 /nobreak > nul\r\n");
+ script.append(" start \"\" \"").append(targetPath).append("\"\r\n");
+ script.append(" if exist \"").append(targetPath).append(".backup\" del \"").append(targetPath).append(".backup\"\r\n");
+ script.append(") else (\r\n");
+ script.append(" echo 更新失败!正在恢复原版本...\r\n");
+ script.append(" if exist \"").append(targetPath).append(".backup\" (\r\n");
+ script.append(" move \"").append(targetPath).append(".backup\" \"").append(targetPath).append("\"\r\n");
+ script.append(" echo 已恢复原版本,程序将在1秒后重新启动...\r\n");
+ script.append(" timeout /t 1 /nobreak > nul\r\n");
+ script.append(" start \"\" \"").append(targetPath).append("\"\r\n");
+ script.append(" )\r\n");
+ script.append(")\r\n");
+ script.append("echo 更新操作完成!\r\n");
+ script.append("timeout /t 1 /nobreak > nul\r\n");
+ script.append("(goto) 2>nul & del \"%~f0\" & exit\r\n");
+
+ Files.write(Paths.get(scriptPath), script.toString().getBytes("GBK"));
+ return scriptPath;
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ /**
+ * 执行更新脚本并退出程序
+ */
+ private void executeUpdateAndExit(String scriptPath) {
+ try {
+ System.out.println("下载完成!正在准备更新...");
+ ProcessBuilder pb = new ProcessBuilder("cmd", "/c", scriptPath);
+ pb.redirectOutput(ProcessBuilder.Redirect.DISCARD);
+ pb.redirectError(ProcessBuilder.Redirect.DISCARD);
+ pb.start();
+ System.out.println("更新程序已启动,当前程序即将退出...");
+ Thread.sleep(1000);
+ Runtime.getRuntime().halt(0);
+ } catch (Exception e) {
+ Runtime.getRuntime().halt(1);
+ }
+ }
+
+ /**
+ * 保存跳过的版本
+ */
+ @PostMapping("/skip-version")
+ public Map saveSkippedVersion(@RequestBody Map request) {
+ Map result = new HashMap<>();
+ try {
+ saveUpdateInfo("skippedUpdateVersion", request.get("version"));
+ result.put("success", true);
+ } catch (Exception e) {
+ result.put("success", false);
+ }
+ return result;
+ }
+
+}
\ No newline at end of file
diff --git a/erp_client_sb/src/main/java/com/tashow/erp/entity/Alibaba1688ProductEntity.java b/erp_client_sb/src/main/java/com/tashow/erp/entity/Alibaba1688ProductEntity.java
new file mode 100644
index 0000000..2fda257
--- /dev/null
+++ b/erp_client_sb/src/main/java/com/tashow/erp/entity/Alibaba1688ProductEntity.java
@@ -0,0 +1,59 @@
+package com.tashow.erp.entity;
+
+import jakarta.persistence.*;
+import lombok.Data;
+import org.hibernate.annotations.CreationTimestamp;
+import org.hibernate.annotations.UpdateTimestamp;
+
+import java.time.LocalDateTime;
+
+/**
+ * 1688产品实体类
+ */
+@Entity
+@Table(name = "alibaba_1688_products")
+@Data
+public class Alibaba1688ProductEntity {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @Column(name = "product_id", unique = true, nullable = false)
+ private String productId;
+
+ @Column(name = "title", length = 1000)
+ private String title;
+
+ @Column(name = "price")
+ private String price;
+
+ @Column(name = "image_url", length = 1000)
+ private String imageUrl;
+
+ @Column(name = "product_url", length = 1000)
+ private String productUrl;
+
+ @Column(name = "supplier")
+ private String supplier;
+
+ @Column(name = "category")
+ private String category;
+
+ @Column(name = "min_order")
+ private String minOrder;
+
+ @Column(name = "trade_assurance")
+ private String tradeAssurance;
+
+ @Column(name = "session_id")
+ private String sessionId;
+
+ @CreationTimestamp
+ @Column(name = "created_at")
+ private LocalDateTime createdAt;
+
+ @UpdateTimestamp
+ @Column(name = "updated_at")
+ private LocalDateTime updatedAt;
+}
\ No newline at end of file
diff --git a/erp_client_sb/src/main/java/com/tashow/erp/entity/AmazonProductEntity.java b/erp_client_sb/src/main/java/com/tashow/erp/entity/AmazonProductEntity.java
new file mode 100644
index 0000000..272d18e
--- /dev/null
+++ b/erp_client_sb/src/main/java/com/tashow/erp/entity/AmazonProductEntity.java
@@ -0,0 +1,64 @@
+package com.tashow.erp.entity;
+
+import jakarta.persistence.*;
+import lombok.Data;
+import org.hibernate.annotations.CreationTimestamp;
+import org.hibernate.annotations.UpdateTimestamp;
+
+import java.time.LocalDateTime;
+
+/**
+ * Amazon产品缓存实体类
+ */
+@Entity
+@Table(name = "amazon_products")
+@Data
+public class AmazonProductEntity {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+ @Column(unique = true, nullable = false)
+ private String asin;
+
+ @Column(name = "title", length = 1000)
+ private String title;
+
+ @Column(name = "price")
+ private String price;
+
+ @Column(name = "image_url", length = 1000)
+ private String imageUrl;
+
+ @Column(name = "product_url", length = 1000)
+ private String productUrl;
+
+ @Column(name = "brand")
+ private String brand;
+
+ @Column(name = "category")
+ private String category;
+
+ @Column(name = "rating")
+ private String rating;
+
+ @Column(name = "review_count")
+ private String reviewCount;
+
+ @Column(name = "availability")
+ private String availability;
+
+ @Column(name = "seller")
+ private String seller;
+
+ @Column(name = "session_id")
+ private String sessionId;
+
+ @CreationTimestamp
+ @Column(name = "created_at")
+ private LocalDateTime createdAt;
+
+ @UpdateTimestamp
+ @Column(name = "updated_at")
+ private LocalDateTime updatedAt;
+}
\ No newline at end of file
diff --git a/erp_client_sb/src/main/java/com/tashow/erp/entity/AuthTokenEntity.java b/erp_client_sb/src/main/java/com/tashow/erp/entity/AuthTokenEntity.java
new file mode 100644
index 0000000..96512b2
--- /dev/null
+++ b/erp_client_sb/src/main/java/com/tashow/erp/entity/AuthTokenEntity.java
@@ -0,0 +1,38 @@
+package com.tashow.erp.entity;
+
+import jakarta.persistence.*;
+import lombok.Data;
+import org.hibernate.annotations.CreationTimestamp;
+import org.hibernate.annotations.UpdateTimestamp;
+
+import java.time.LocalDateTime;
+
+/**
+ * 认证令牌实体类
+ */
+@Entity
+@Table(name = "auth_tokens")
+@Data
+public class AuthTokenEntity {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @Column(name = "service_name", unique = true, nullable = false)
+ private String serviceName;
+
+ @Column(name = "token", nullable = false)
+ private String token;
+
+ @Column(name = "expire_time")
+ private LocalDateTime expireTime;
+
+ @CreationTimestamp
+ @Column(name = "created_at")
+ private LocalDateTime createdAt;
+
+ @UpdateTimestamp
+ @Column(name = "updated_at")
+ private LocalDateTime updatedAt;
+}
\ No newline at end of file
diff --git a/erp_client_sb/src/main/java/com/tashow/erp/entity/BanmaOrderEntity.java b/erp_client_sb/src/main/java/com/tashow/erp/entity/BanmaOrderEntity.java
new file mode 100644
index 0000000..307214b
--- /dev/null
+++ b/erp_client_sb/src/main/java/com/tashow/erp/entity/BanmaOrderEntity.java
@@ -0,0 +1,38 @@
+package com.tashow.erp.entity;
+
+import jakarta.persistence.*;
+import lombok.Data;
+import org.hibernate.annotations.CreationTimestamp;
+import org.hibernate.annotations.UpdateTimestamp;
+
+import java.time.LocalDateTime;
+
+/**
+ * 斑马订单实体类
+ */
+@Entity
+@Table(name = "banma_orders")
+@Data
+public class BanmaOrderEntity {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @Column(name = "tracking_number", unique = true, nullable = false)
+ private String trackingNumber;
+
+ @Column(name = "order_data", columnDefinition = "TEXT")
+ private String orderData;
+
+ @Column(name = "session_id")
+ private String sessionId;
+
+ @CreationTimestamp
+ @Column(name = "created_at")
+ private LocalDateTime createdAt;
+
+ @UpdateTimestamp
+ @Column(name = "updated_at")
+ private LocalDateTime updatedAt;
+}
\ No newline at end of file
diff --git a/erp_client_sb/src/main/java/com/tashow/erp/entity/CacheDataEntity.java b/erp_client_sb/src/main/java/com/tashow/erp/entity/CacheDataEntity.java
new file mode 100644
index 0000000..2865618
--- /dev/null
+++ b/erp_client_sb/src/main/java/com/tashow/erp/entity/CacheDataEntity.java
@@ -0,0 +1,38 @@
+package com.tashow.erp.entity;
+
+import jakarta.persistence.*;
+import lombok.Data;
+import org.hibernate.annotations.CreationTimestamp;
+import org.hibernate.annotations.UpdateTimestamp;
+
+import java.time.LocalDateTime;
+
+/**
+ * 缓存数据实体类
+ */
+@Entity
+@Table(name = "cache_data")
+@Data
+public class CacheDataEntity {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @Column(name = "cache_key", unique = true, nullable = false)
+ private String cacheKey;
+
+ @Column(name = "cache_value", columnDefinition = "TEXT")
+ private String cacheValue;
+
+ @Column(name = "expire_time")
+ private LocalDateTime expireTime;
+
+ @CreationTimestamp
+ @Column(name = "created_at")
+ private LocalDateTime createdAt;
+
+ @UpdateTimestamp
+ @Column(name = "updated_at")
+ private LocalDateTime updatedAt;
+}
\ No newline at end of file
diff --git a/erp_client_sb/src/main/java/com/tashow/erp/entity/RakutenProductEntity.java b/erp_client_sb/src/main/java/com/tashow/erp/entity/RakutenProductEntity.java
new file mode 100644
index 0000000..9aa3889
--- /dev/null
+++ b/erp_client_sb/src/main/java/com/tashow/erp/entity/RakutenProductEntity.java
@@ -0,0 +1,86 @@
+package com.tashow.erp.entity;
+
+import com.fasterxml.jackson.annotation.JsonRawValue;
+import jakarta.persistence.*;
+import lombok.AccessLevel;
+import lombok.Data;
+import lombok.Getter;
+import org.hibernate.annotations.CreationTimestamp;
+import org.hibernate.annotations.UpdateTimestamp;
+
+import java.time.LocalDateTime;
+
+/**
+ * 乐天产品缓存实体类
+ */
+@Entity
+@Table(name = "rakuten_products")
+@Data
+public class RakutenProductEntity {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @Column(name = "original_shop_name")
+ private String originalShopName;
+
+ @Column(name = "shop_name")
+ private String shopName;
+
+ @Column(name = "product_url", length = 1000)
+ private String productUrl;
+
+ @Column(name = "img_url", length = 1000)
+ private String imgUrl;
+
+ @Column(name = "product_title", length = 500)
+ private String productTitle;
+
+ @Column(name = "product_name", length = 500)
+ private String productName;
+
+ @Column(name = "price")
+ private String price;
+
+ @Column(name = "ranking")
+ private String ranking;
+
+ @Column(name = "price_1688")
+ private String price1688;
+
+
+ @Column(name = "detail_url_1688", length = 1000)
+ private String detailUrl1688;
+
+ @Column(name = "image_1688_url", length = 1000)
+ private String image1688Url;
+
+
+ @Column(name = "map_recognition_link", length = 1000)
+ private String mapRecognitionLink; // 1688识图链接
+
+ @Column(name = "freight")
+ private Double freight; // 运费
+
+ @Column(name = "median")
+ private Double median; // 中位价格
+
+ @Column(name = "weight")
+ private String weight; // 重量
+ @Column(name = "sku_price_json", columnDefinition = "JSON")
+ private String skuPriceJson; // SKU价格JSON字符串
+
+ @Column(name = "session_id")
+ private String sessionId; // 用于标识一次导入会话
+
+ @CreationTimestamp
+ @Column(name = "created_at")
+ private LocalDateTime createdAt;
+
+ @UpdateTimestamp
+ @Column(name = "updated_at")
+ private LocalDateTime updatedAt;
+
+
+}
\ No newline at end of file
diff --git a/erp_client_sb/src/main/java/com/tashow/erp/entity/UpdateStatusEntity.java b/erp_client_sb/src/main/java/com/tashow/erp/entity/UpdateStatusEntity.java
new file mode 100644
index 0000000..caac5d9
--- /dev/null
+++ b/erp_client_sb/src/main/java/com/tashow/erp/entity/UpdateStatusEntity.java
@@ -0,0 +1,81 @@
+package com.tashow.erp.entity;
+
+import jakarta.persistence.*;
+
+import java.time.LocalDateTime;
+
+/**
+ * 更新状态实体
+ */
+@Entity
+@Table(name = "update_status")
+public class UpdateStatusEntity {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @Column(name = "key_name", unique = true)
+ private String keyName;
+
+ @Column(name = "value_data", columnDefinition = "TEXT")
+ private String valueData;
+
+ @Column(name = "created_at")
+ private LocalDateTime createdAt;
+
+ @Column(name = "updated_at")
+ private LocalDateTime updatedAt;
+
+ @PrePersist
+ protected void onCreate() {
+ createdAt = LocalDateTime.now();
+ updatedAt = LocalDateTime.now();
+ }
+
+ @PreUpdate
+ protected void onUpdate() {
+ updatedAt = LocalDateTime.now();
+ }
+
+ // Getters and Setters
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public String getKeyName() {
+ return keyName;
+ }
+
+ public void setKeyName(String keyName) {
+ this.keyName = keyName;
+ }
+
+ public String getValueData() {
+ return valueData;
+ }
+
+ public void setValueData(String valueData) {
+ this.valueData = valueData;
+ }
+
+ public LocalDateTime getCreatedAt() {
+ return createdAt;
+ }
+
+ public void setCreatedAt(LocalDateTime createdAt) {
+ this.createdAt = createdAt;
+ }
+
+ public LocalDateTime getUpdatedAt() {
+ return updatedAt;
+ }
+
+ public void setUpdatedAt(LocalDateTime updatedAt) {
+ this.updatedAt = updatedAt;
+ }
+}
\ No newline at end of file
diff --git a/erp_client_sb/src/main/java/com/tashow/erp/entity/ZebraOrderEntity.java b/erp_client_sb/src/main/java/com/tashow/erp/entity/ZebraOrderEntity.java
new file mode 100644
index 0000000..eff0882
--- /dev/null
+++ b/erp_client_sb/src/main/java/com/tashow/erp/entity/ZebraOrderEntity.java
@@ -0,0 +1,65 @@
+package com.tashow.erp.entity;
+
+import jakarta.persistence.*;
+import lombok.Data;
+import org.hibernate.annotations.CreationTimestamp;
+import org.hibernate.annotations.UpdateTimestamp;
+
+import java.time.LocalDateTime;
+
+/**
+ * 斑马订单缓存实体类
+ */
+@Entity
+@Table(name = "zebra_orders")
+@Data
+public class ZebraOrderEntity {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @Column(name = "order_id")
+ private String orderId;
+
+ @Column(name = "product_name", length = 500)
+ private String productName;
+
+ @Column(name = "quantity")
+ private Integer quantity;
+
+ @Column(name = "price_jpy")
+ private String priceJpy;
+
+ @Column(name = "price_cny")
+ private String priceCny;
+
+ @Column(name = "image_url", length = 1000)
+ private String imageUrl;
+
+ @Column(name = "customer_name")
+ private String customerName;
+
+ @Column(name = "order_status")
+ private String orderStatus;
+
+ @Column(name = "order_date")
+ private String orderDate;
+
+ @Column(name = "shipping_address", length = 1000)
+ private String shippingAddress;
+
+ @Column(name = "tracking_number")
+ private String trackingNumber;
+
+ @Column(name = "session_id")
+ private String sessionId;
+
+ @CreationTimestamp
+ @Column(name = "created_at")
+ private LocalDateTime createdAt;
+
+ @UpdateTimestamp
+ @Column(name = "updated_at")
+ private LocalDateTime updatedAt;
+}
\ No newline at end of file
diff --git a/erp_client_sb/src/main/java/com/tashow/erp/fx/controller/JavaBridge.java b/erp_client_sb/src/main/java/com/tashow/erp/fx/controller/JavaBridge.java
new file mode 100644
index 0000000..1ff68b3
--- /dev/null
+++ b/erp_client_sb/src/main/java/com/tashow/erp/fx/controller/JavaBridge.java
@@ -0,0 +1,79 @@
+package com.tashow.erp.fx.controller;
+
+import lombok.extern.slf4j.Slf4j;
+
+import java.awt.*;
+import java.awt.datatransfer.Clipboard;
+import java.awt.datatransfer.StringSelection;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+@Slf4j
+public class JavaBridge {
+
+ /**
+ * 直接保存字节数组为Excel文件到桌面(纯 Spring Boot 环境,无文件对话框)
+ */
+ public String saveExcelFileToDesktop(byte[] data, String fileName) {
+ try {
+ if (data == null || data.length == 0) {
+ log.warn("文件数据为空,无法保存文件");
+ return null;
+ }
+
+ String userHome = System.getProperty("user.home");
+ File desktop = new File(userHome, "Desktop");
+ if (!desktop.exists()) {
+ // 回退到用户目录
+ desktop = new File(userHome);
+ }
+
+ File file = new File(desktop, fileName);
+ int counter = 1;
+ if (fileName != null && fileName.contains(".")) {
+ String baseName = fileName.substring(0, fileName.lastIndexOf('.'));
+ String extension = fileName.substring(fileName.lastIndexOf('.'));
+ while (file.exists()) {
+ file = new File(desktop, baseName + "_" + counter + extension);
+ counter++;
+ }
+ } else {
+ while (file.exists()) {
+ file = new File(desktop, fileName + "_" + counter);
+ counter++;
+ }
+ }
+
+ try (FileOutputStream fos = new FileOutputStream(file)) {
+ fos.write(data);
+ fos.flush();
+ }
+
+ String filePath = file.getAbsolutePath();
+ log.info("Excel文件已保存: {}", filePath);
+ return filePath;
+ } catch (IOException e) {
+ log.error("保存Excel文件失败: {}", e.getMessage(), e);
+ return null;
+ }
+ }
+
+ /**
+ * 复制文本到系统剪贴板
+ */
+ public boolean copyToClipboard(String text) {
+ try {
+ if (text == null || text.trim().isEmpty()) {
+ return false;
+ }
+ Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+ StringSelection selection = new StringSelection(text);
+ clipboard.setContents(selection, null);
+ return true;
+ } catch (Exception e) {
+ log.error("复制到剪贴板失败: {}", e.getMessage());
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/erp_client_sb/src/main/java/com/tashow/erp/fx/controller/MainCtrl.java b/erp_client_sb/src/main/java/com/tashow/erp/fx/controller/MainCtrl.java
new file mode 100644
index 0000000..376702c
--- /dev/null
+++ b/erp_client_sb/src/main/java/com/tashow/erp/fx/controller/MainCtrl.java
@@ -0,0 +1,43 @@
+// package com.tashow.erp.fx.controller;
+//
+// 已转为纯 Spring Boot,不再包含 JavaFX 控制器与视图逻辑。如需恢复 FX,请取消注释并恢复依赖。
+//
+// import javafx.application.Platform;
+// import javafx.fxml.FXML;
+// import javafx.fxml.Initializable;
+// import javafx.scene.layout.BorderPane;
+// import javafx.scene.web.WebEngine;
+// import javafx.scene.web.WebView;
+// import lombok.extern.slf4j.Slf4j;
+// import net.rgielen.fxweaver.core.FxmlView;
+// import org.springframework.stereotype.Component;
+// import netscape.javascript.JSObject;
+//
+// import java.net.URL;
+// import java.util.ResourceBundle;
+//
+// @Slf4j
+// @Component
+// @FxmlView("/static/fxml/Main.fxml")
+// public class MainCtrl implements Initializable {
+// @FXML
+// public BorderPane rootPane;
+// @FXML
+// public WebView webView;
+// private WebEngine webEngine;
+// @Override
+// public void initialize(URL location, ResourceBundle resources) {
+// if (Platform.isFxApplicationThread()) {
+// initWebView();
+// } else {
+// Platform.runLater(this::initWebView);
+// }
+// }
+//
+// private void initWebView() {
+// webEngine = webView.getEngine();
+// webEngine.setUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...");
+// webEngine.setJavaScriptEnabled(true);
+// webEngine.load("http://localhost:8081/html/erp-dashboard.html");
+// }
+// }
diff --git a/erp_client_sb/src/main/java/com/tashow/erp/fx/view/MainView.java b/erp_client_sb/src/main/java/com/tashow/erp/fx/view/MainView.java
new file mode 100644
index 0000000..eeacd8a
--- /dev/null
+++ b/erp_client_sb/src/main/java/com/tashow/erp/fx/view/MainView.java
@@ -0,0 +1,11 @@
+// package com.tashow.erp.fx.view;
+//
+// 已转为纯 Spring Boot,移除 FX 视图。
+// import javafx.scene.Parent;
+// import net.rgielen.fxweaver.core.FxmlView;
+// import org.springframework.stereotype.Component;
+//
+// @Component
+// @FxmlView("/static/fxml/Main.fxml")
+// public class MainView {
+// }
diff --git a/erp_client_sb/src/main/java/com/tashow/erp/model/ClientAlibaba1688Monitor.java b/erp_client_sb/src/main/java/com/tashow/erp/model/ClientAlibaba1688Monitor.java
new file mode 100644
index 0000000..ef9011b
--- /dev/null
+++ b/erp_client_sb/src/main/java/com/tashow/erp/model/ClientAlibaba1688Monitor.java
@@ -0,0 +1,25 @@
+package com.tashow.erp.model;
+import lombok.Data;
+import java.time.LocalDateTime;
+
+/**
+ * 1688爬取风控监控数据表实体类
+ */
+@Data
+public class ClientAlibaba1688Monitor {
+ private Long id;
+
+ private String clientId;
+
+ private String ipAddress;
+
+ private String eventType;
+
+ private Long eventTime;
+
+ private Long duration;
+
+ private LocalDateTime createTime;
+
+ private String remark;
+}
\ No newline at end of file
diff --git a/erp_client_sb/src/main/java/com/tashow/erp/model/MonitoringData.java b/erp_client_sb/src/main/java/com/tashow/erp/model/MonitoringData.java
new file mode 100644
index 0000000..ea69993
--- /dev/null
+++ b/erp_client_sb/src/main/java/com/tashow/erp/model/MonitoringData.java
@@ -0,0 +1,23 @@
+package com.tashow.erp.model;
+
+import lombok.Data;
+
+/**
+ * 监控数据类
+ */
+@Data
+public class MonitoringData {
+ private String ipAddress;
+ private MonitorEventType eventType;
+ private long eventTime;
+ private long duration;
+
+ /**
+ * 风控事件类型
+ */
+ public enum MonitorEventType {
+ MOBILE_FIRST_ACCESS, // 移动端首次访问
+ MOBILE_BLOCKED, // 移动端被风控
+ DESKTOP_BLOCKED // 电脑端被风控
+ }
+}
\ No newline at end of file
diff --git a/erp_client_sb/src/main/java/com/tashow/erp/model/ProductInfo.java b/erp_client_sb/src/main/java/com/tashow/erp/model/ProductInfo.java
new file mode 100644
index 0000000..0b498c8
--- /dev/null
+++ b/erp_client_sb/src/main/java/com/tashow/erp/model/ProductInfo.java
@@ -0,0 +1,16 @@
+package com.tashow.erp.model;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.AllArgsConstructor;
+
+/**
+ * 商品信息类
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class ProductInfo {
+ private String price;
+ private String weight;
+}
\ No newline at end of file
diff --git a/erp_client_sb/src/main/java/com/tashow/erp/model/RakutenProduct.java b/erp_client_sb/src/main/java/com/tashow/erp/model/RakutenProduct.java
new file mode 100644
index 0000000..feddb2c
--- /dev/null
+++ b/erp_client_sb/src/main/java/com/tashow/erp/model/RakutenProduct.java
@@ -0,0 +1,49 @@
+package com.tashow.erp.model;
+
+import lombok.Data;
+import java.util.List;
+
+/**
+ * 乐天商品模型类
+ */
+@Data
+public class RakutenProduct {
+
+ private String productUrl;
+ private String shopName;
+ private String imgUrl;
+ private String productTitle;
+ private String productName;
+ private String price;
+ private String ranking;
+
+ // 1688相关字段
+ private String price1688;
+ private String weight1688;
+ private String detailUrl1688;
+
+ // 新增:用于导入导出与展示
+ private String originalShopName;
+ private String image1688Url;
+ private String mapRecognitionLink;
+ private Double freight;
+ private Double median;
+ private String weight;
+
+ @Override
+ public String toString() {
+ return "RakutenProduct{" +
+ "productName='" + productName + '\'' +
+ ", productTitle='" + productTitle + '\'' +
+ ", price='" + price + '\'' +
+ ", imageUrl1='" + imgUrl+ '\'' +
+ ", productUrl='" + productUrl + '\'' +
+ ", shopName='" + shopName + '\'' +
+ ", price1688='" + price1688 + '\'' +
+ ", weight1688='" + weight1688 + '\'' +
+ ", detailUrl1688='" + detailUrl1688 + '\'' +
+ '}';
+ }
+
+
+}
\ No newline at end of file
diff --git a/erp_client_sb/src/main/java/com/tashow/erp/model/SearchResult.java b/erp_client_sb/src/main/java/com/tashow/erp/model/SearchResult.java
new file mode 100644
index 0000000..1b44e70
--- /dev/null
+++ b/erp_client_sb/src/main/java/com/tashow/erp/model/SearchResult.java
@@ -0,0 +1,29 @@
+package com.tashow.erp.model;
+
+import lombok.Data;
+
+import java.util.List;
+import java.util.Map;
+
+
+
+/**
+ * 1688识图搜索结果
+ */
+@Data
+public class SearchResult {
+
+
+ //运费
+ private Double freight;
+ //识图链接
+ private String mapRecognitionLink;
+ //sku价格
+ Map skuPrice;
+
+ //价格中间值
+ private Double median;
+ String weight;
+
+
+}
\ No newline at end of file
diff --git a/erp_client_sb/src/main/java/com/tashow/erp/repository/Alibaba1688ProductRepository.java b/erp_client_sb/src/main/java/com/tashow/erp/repository/Alibaba1688ProductRepository.java
new file mode 100644
index 0000000..f9c969e
--- /dev/null
+++ b/erp_client_sb/src/main/java/com/tashow/erp/repository/Alibaba1688ProductRepository.java
@@ -0,0 +1,58 @@
+package com.tashow.erp.repository;
+
+import com.tashow.erp.entity.Alibaba1688ProductEntity;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Modifying;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * 1688产品Repository
+ */
+@Repository
+public interface Alibaba1688ProductRepository extends JpaRepository {
+
+ /**
+ * 根据产品ID查找产品
+ */
+ Optional findByProductId(String productId);
+
+ /**
+ * 根据会话ID查找产品(分页)
+ */
+ Page findBySessionIdOrderByCreatedAtDesc(String sessionId, Pageable pageable);
+
+ /**
+ * 根据会话ID查找产品
+ */
+ List findBySessionIdOrderByCreatedAtDesc(String sessionId);
+
+ /**
+ * 根据会话ID删除产品
+ */
+ @Modifying
+ @Transactional
+ void deleteBySessionId(String sessionId);
+
+ /**
+ * 清理过期数据(7天前的数据)
+ */
+ @Modifying
+ @Transactional
+ @Query("DELETE FROM Alibaba1688ProductEntity a WHERE a.createdAt < :expireTime")
+ void deleteExpiredData(@Param("expireTime") LocalDateTime expireTime);
+
+ /**
+ * 获取最新的会话ID
+ */
+ @Query("SELECT DISTINCT a.sessionId FROM Alibaba1688ProductEntity a ORDER BY a.createdAt DESC")
+ List findRecentSessionIds(Pageable pageable);
+}
\ No newline at end of file
diff --git a/erp_client_sb/src/main/java/com/tashow/erp/repository/AmazonProductRepository.java b/erp_client_sb/src/main/java/com/tashow/erp/repository/AmazonProductRepository.java
new file mode 100644
index 0000000..94f8ac6
--- /dev/null
+++ b/erp_client_sb/src/main/java/com/tashow/erp/repository/AmazonProductRepository.java
@@ -0,0 +1,73 @@
+package com.tashow.erp.repository;
+
+import com.tashow.erp.entity.AmazonProductEntity;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Modifying;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * Amazon产品Repository
+ */
+@Repository
+public interface AmazonProductRepository extends JpaRepository {
+
+ /**
+ * 根据ASIN查找产品(取最新的一条)
+ */
+ @Query(value = "SELECT * FROM amazon_products WHERE asin = :asin ORDER BY created_at DESC LIMIT 1", nativeQuery = true)
+ Optional findByAsin(@Param("asin") String asin);
+
+ /**
+ * 根据会话ID查找产品(分页)
+ */
+ Page findBySessionIdOrderByCreatedAtDesc(String sessionId, Pageable pageable);
+
+ /**
+ * 根据会话ID查找产品
+ */
+ List findBySessionIdOrderByCreatedAtDesc(String sessionId);
+
+ /**
+ * 根据会话ID删除产品
+ */
+ @Modifying
+ @Transactional
+ void deleteBySessionId(String sessionId);
+
+ /**
+ * 清理过期数据(7天前的数据)
+ */
+ @Modifying
+ @Transactional
+ @Query("DELETE FROM AmazonProductEntity a WHERE a.createdAt < :expireTime")
+ void deleteExpiredData(@Param("expireTime") LocalDateTime expireTime);
+
+ /**
+ * 获取最新的会话ID
+ */
+ @Query("SELECT DISTINCT a.sessionId FROM AmazonProductEntity a ORDER BY a.createdAt DESC")
+ List findRecentSessionIds(Pageable pageable);
+
+ /**
+ * 获取最新会话的产品数据(只返回最后一次采集的结果)
+ */
+ @Query(value = "SELECT * FROM amazon_products WHERE session_id = (SELECT session_id FROM amazon_products ORDER BY created_at DESC LIMIT 1) ORDER BY created_at ASC, id ASC", nativeQuery = true)
+ List findLatestProducts();
+
+ /**
+ * 删除指定ASIN在指定时间后的数据(用于清理12小时内重复)
+ */
+ @Modifying
+ @Transactional
+ @Query("DELETE FROM AmazonProductEntity a WHERE a.asin = :asin AND a.createdAt >= :cutoffTime")
+ void deleteByAsinAndCreatedAtAfter(@Param("asin") String asin, @Param("cutoffTime") LocalDateTime cutoffTime);
+}
\ No newline at end of file
diff --git a/erp_client_sb/src/main/java/com/tashow/erp/repository/AuthTokenRepository.java b/erp_client_sb/src/main/java/com/tashow/erp/repository/AuthTokenRepository.java
new file mode 100644
index 0000000..407d5b9
--- /dev/null
+++ b/erp_client_sb/src/main/java/com/tashow/erp/repository/AuthTokenRepository.java
@@ -0,0 +1,44 @@
+package com.tashow.erp.repository;
+
+import com.tashow.erp.entity.AuthTokenEntity;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Modifying;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.time.LocalDateTime;
+import java.util.Optional;
+
+/**
+ * 认证令牌Repository
+ */
+@Repository
+public interface AuthTokenRepository extends JpaRepository {
+
+ /**
+ * 根据服务名查找有效的认证令牌
+ */
+ Optional findByServiceNameAndExpireTimeAfter(String serviceName, LocalDateTime currentTime);
+
+ /**
+ * 根据服务名查找认证令牌(不考虑过期时间)
+ */
+ Optional findByServiceName(String serviceName);
+
+ /**
+ * 清理过期令牌
+ */
+ @Modifying
+ @Transactional
+ @Query("DELETE FROM AuthTokenEntity a WHERE a.expireTime < :expireTime")
+ void deleteExpiredTokens(@Param("expireTime") LocalDateTime expireTime);
+
+ /**
+ * 根据服务名删除令牌
+ */
+ @Modifying
+ @Transactional
+ void deleteByServiceName(String serviceName);
+}
\ No newline at end of file
diff --git a/erp_client_sb/src/main/java/com/tashow/erp/repository/BanmaOrderRepository.java b/erp_client_sb/src/main/java/com/tashow/erp/repository/BanmaOrderRepository.java
new file mode 100644
index 0000000..783da28
--- /dev/null
+++ b/erp_client_sb/src/main/java/com/tashow/erp/repository/BanmaOrderRepository.java
@@ -0,0 +1,89 @@
+package com.tashow.erp.repository;
+
+import com.tashow.erp.entity.BanmaOrderEntity;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Modifying;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * 斑马订单Repository
+ */
+@Repository
+public interface BanmaOrderRepository extends JpaRepository {
+
+ /**
+ * 根据追踪号查找订单
+ */
+ Optional findByTrackingNumber(String trackingNumber);
+
+ /**
+ * 根据会话ID查找订单(分页)
+ */
+ Page findBySessionIdOrderByCreatedAtDesc(String sessionId, Pageable pageable);
+
+ /**
+ * 根据会话ID查找订单
+ */
+ List findBySessionIdOrderByCreatedAtDesc(String sessionId);
+
+ /**
+ * 根据会话ID删除订单
+ */
+ @Modifying
+ @Transactional
+ void deleteBySessionId(String sessionId);
+
+ /**
+ * 清理过期数据(7天前的数据)
+ */
+ @Modifying
+ @Transactional
+ @Query("DELETE FROM BanmaOrderEntity b WHERE b.createdAt < :expireTime")
+ void deleteExpiredData(@Param("expireTime") LocalDateTime expireTime);
+
+ /**
+ * 获取最新的会话ID
+ */
+ @Query("SELECT DISTINCT b.sessionId FROM BanmaOrderEntity b ORDER BY b.createdAt DESC")
+ List findRecentSessionIds(Pageable pageable);
+
+ /**
+ * 查找所有有效订单(按更新时间倒序)
+ */
+ List findAllByOrderByUpdatedAtDesc();
+
+ /**
+ * 获取最新会话的订单数据(只返回最后一次采集的结果)
+ */
+ @Query(value = "SELECT * FROM banma_orders WHERE session_id = (SELECT session_id FROM banma_orders ORDER BY created_at DESC LIMIT 1) ORDER BY updated_at ASC, id ASC", nativeQuery = true)
+ List findLatestOrders();
+
+ /**
+ * 删除指定追踪号在指定时间后的数据(用于清理12小时内重复)
+ */
+ @Modifying
+ @Transactional
+ @Query("DELETE FROM BanmaOrderEntity b WHERE b.trackingNumber = :trackingNumber AND b.createdAt >= :cutoffTime")
+ void deleteByTrackingNumberAndCreatedAtAfter(@Param("trackingNumber") String trackingNumber, @Param("cutoffTime") LocalDateTime cutoffTime);
+
+ /**
+ * 检查追踪号是否有12小时内的缓存数据
+ */
+ @Query("SELECT CASE WHEN COUNT(b) > 0 THEN true ELSE false END FROM BanmaOrderEntity b WHERE b.trackingNumber = :trackingNumber AND b.createdAt > :cutoffTime")
+ boolean existsByTrackingNumberAndCreatedAtAfter(@Param("trackingNumber") String trackingNumber, @Param("cutoffTime") LocalDateTime cutoffTime);
+
+ /**
+ * 根据追踪号获取最新的订单数据
+ */
+ @Query("SELECT b FROM BanmaOrderEntity b WHERE b.trackingNumber = :trackingNumber ORDER BY b.createdAt DESC")
+ Optional findLatestByTrackingNumber(@Param("trackingNumber") String trackingNumber);
+}
\ No newline at end of file
diff --git a/erp_client_sb/src/main/java/com/tashow/erp/repository/CacheDataRepository.java b/erp_client_sb/src/main/java/com/tashow/erp/repository/CacheDataRepository.java
new file mode 100644
index 0000000..9a20fa0
--- /dev/null
+++ b/erp_client_sb/src/main/java/com/tashow/erp/repository/CacheDataRepository.java
@@ -0,0 +1,33 @@
+package com.tashow.erp.repository;
+
+import com.tashow.erp.entity.CacheDataEntity;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Modifying;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.time.LocalDateTime;
+import java.util.Optional;
+
+/**
+ * 缓存数据Repository
+ */
+@Repository
+public interface CacheDataRepository extends JpaRepository {
+
+
+ /**
+ * 根据缓存键查找缓存数据(不考虑过期时间)
+ */
+ Optional findByCacheKey(String cacheKey);
+
+ /**
+ * 根据缓存键删除数据
+ */
+ @Modifying
+ @Transactional
+ void deleteByCacheKey(String cacheKey);
+
+}
\ No newline at end of file
diff --git a/erp_client_sb/src/main/java/com/tashow/erp/repository/RakutenProductRepository.java b/erp_client_sb/src/main/java/com/tashow/erp/repository/RakutenProductRepository.java
new file mode 100644
index 0000000..4a6fade
--- /dev/null
+++ b/erp_client_sb/src/main/java/com/tashow/erp/repository/RakutenProductRepository.java
@@ -0,0 +1,99 @@
+package com.tashow.erp.repository;
+
+import com.tashow.erp.entity.RakutenProductEntity;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Modifying;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Repository
+public interface RakutenProductRepository extends JpaRepository {
+
+ /**
+ * 根据会话ID查找产品(分页)
+ */
+ Page findBySessionIdOrderByCreatedAtAscIdAsc(String sessionId, Pageable pageable);
+
+ /**
+ * 根据会话ID查找产品
+ */
+ List findBySessionIdOrderByCreatedAtAscIdAsc(String sessionId);
+
+ /**
+ * 根据会话ID删除产品
+ */
+ @Modifying
+ @Transactional
+ void deleteBySessionId(String sessionId);
+
+ /**
+ * 清理过期数据(7天前的数据)
+ */
+ @Modifying
+ @Transactional
+ @Query("DELETE FROM RakutenProductEntity r WHERE r.createdAt < :expireTime")
+ void deleteExpiredData(@Param("expireTime") LocalDateTime expireTime);
+
+ /**
+ * 根据原始店铺名查找产品(按创建时间和ID升序排列,与最新数据查询一致)
+ */
+ List findByOriginalShopNameOrderByCreatedAtAscIdAsc(String originalShopName);
+
+ /**
+ * 检查指定店铺在指定时间后是否有数据
+ */
+ boolean existsByOriginalShopNameAndCreatedAtAfter(String originalShopName, LocalDateTime sinceTime);
+
+ /**
+ * 获取最新的会话ID
+ */
+ @Query("SELECT DISTINCT r.sessionId FROM RakutenProductEntity r ORDER BY r.createdAt DESC")
+ List findRecentSessionIds(Pageable pageable);
+
+ /**
+ * 获取最新会话的产品数据(只返回最后一次采集的结果)
+ */
+ @Query(value = "SELECT * FROM rakuten_products WHERE session_id = (SELECT session_id FROM rakuten_products ORDER BY created_at LIMIT 1) ORDER BY created_at ASC, id ASC", nativeQuery = true)
+ List findLatestProducts();
+
+ /**
+ * 删除指定商品URL在指定时间后的数据(用于清理12小时内重复)
+ */
+ @Modifying
+ @Transactional
+ @Query("DELETE FROM RakutenProductEntity r WHERE r.productUrl = :productUrl AND r.createdAt >= :cutoffTime")
+ void deleteByProductUrlAndCreatedAtAfter(@Param("productUrl") String productUrl, @Param("cutoffTime") LocalDateTime cutoffTime);
+
+ /**
+ * 删除指定店铺在指定时间后的数据(用于清理12小时内重复)
+ */
+ @Modifying
+ @Transactional
+ @Query("DELETE FROM RakutenProductEntity r WHERE r.originalShopName = :shopName AND r.createdAt >= :cutoffTime")
+ void deleteByOriginalShopNameAndCreatedAtAfter(@Param("shopName") String shopName, @Param("cutoffTime") LocalDateTime cutoffTime);
+
+ /**
+ * 删除指定店铺在指定时间前的数据(用于清理12小时之前的旧数据)
+ */
+ @Modifying
+ @Transactional
+ @Query("DELETE FROM RakutenProductEntity r WHERE r.originalShopName = :shopName AND r.createdAt < :cutoffTime")
+ void deleteByOriginalShopNameAndCreatedAtBefore(@Param("shopName") String shopName, @Param("cutoffTime") LocalDateTime cutoffTime);
+
+ /**
+ * 根据产品URL查找产品(用于1688数据更新)
+ */
+ List findByProductUrl(String productUrl);
+
+ /**
+ * 根据产品URL列表查找产品
+ */
+ List findByProductUrlIn(List productUrls);
+}
\ No newline at end of file
diff --git a/erp_client_sb/src/main/java/com/tashow/erp/repository/UpdateStatusRepository.java b/erp_client_sb/src/main/java/com/tashow/erp/repository/UpdateStatusRepository.java
new file mode 100644
index 0000000..0090a4f
--- /dev/null
+++ b/erp_client_sb/src/main/java/com/tashow/erp/repository/UpdateStatusRepository.java
@@ -0,0 +1,15 @@
+package com.tashow.erp.repository;
+
+import com.tashow.erp.entity.UpdateStatusEntity;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+import java.util.Optional;
+
+@Repository
+public interface UpdateStatusRepository extends JpaRepository {
+
+ Optional findByKeyName(String keyName);
+
+ void deleteByKeyName(String keyName);
+}
\ No newline at end of file
diff --git a/erp_client_sb/src/main/java/com/tashow/erp/repository/ZebraOrderRepository.java b/erp_client_sb/src/main/java/com/tashow/erp/repository/ZebraOrderRepository.java
new file mode 100644
index 0000000..fc9f907
--- /dev/null
+++ b/erp_client_sb/src/main/java/com/tashow/erp/repository/ZebraOrderRepository.java
@@ -0,0 +1,58 @@
+package com.tashow.erp.repository;
+
+import com.tashow.erp.entity.ZebraOrderEntity;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Modifying;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * 斑马订单Repository
+ */
+@Repository
+public interface ZebraOrderRepository extends JpaRepository {
+
+ /**
+ * 根据会话ID查找订单(分页)
+ */
+ Page findBySessionIdOrderByCreatedAtDesc(String sessionId, Pageable pageable);
+
+ /**
+ * 根据会话ID查找订单
+ */
+ List findBySessionIdOrderByCreatedAtDesc(String sessionId);
+
+ /**
+ * 根据会话ID删除订单
+ */
+ @Modifying
+ @Transactional
+ void deleteBySessionId(String sessionId);
+
+ /**
+ * 清理过期数据(7天前的数据)
+ */
+ @Modifying
+ @Transactional
+ @Query("DELETE FROM ZebraOrderEntity z WHERE z.createdAt < :expireTime")
+ void deleteExpiredData(@Param("expireTime") LocalDateTime expireTime);
+
+ /**
+ * 获取最新的会话ID
+ */
+ @Query("SELECT DISTINCT z.sessionId FROM ZebraOrderEntity z ORDER BY z.createdAt DESC")
+ List findRecentSessionIds(Pageable pageable);
+
+ /**
+ * 获取最新会话的所有订单
+ */
+ @Query("SELECT z FROM ZebraOrderEntity z WHERE z.sessionId = (SELECT z2.sessionId FROM ZebraOrderEntity z2 ORDER BY z2.createdAt DESC LIMIT 1) ORDER BY z.createdAt DESC")
+ List findLatestOrders();
+}
\ No newline at end of file
diff --git a/erp_client_sb/src/main/java/com/tashow/erp/security/JwksService.java b/erp_client_sb/src/main/java/com/tashow/erp/security/JwksService.java
new file mode 100644
index 0000000..75d259a
--- /dev/null
+++ b/erp_client_sb/src/main/java/com/tashow/erp/security/JwksService.java
@@ -0,0 +1,71 @@
+package com.tashow.erp.security;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Component;
+import org.springframework.web.client.RestTemplate;
+
+import java.math.BigInteger;
+import java.security.KeyFactory;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.RSAPublicKeySpec;
+import java.util.Base64;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+@Component
+public class JwksService {
+
+ @Value("${api.server.base-url}")
+ private String serverBaseUrl;
+
+ private final RestTemplate restTemplate;
+ private final ObjectMapper objectMapper = new ObjectMapper();
+ private final Map cache = new ConcurrentHashMap<>();
+
+ public JwksService(RestTemplate restTemplate) {
+ this.restTemplate = restTemplate;
+ }
+
+ public RSAPublicKey getKeyById(String kid) {
+ RSAPublicKey cached = cache.get(kid);
+ if (cached != null) return cached;
+ loadJwks();
+ return cache.get(kid);
+ }
+
+ public RSAPublicKey[] getAllPublicKeys() {
+ loadJwks();
+ return cache.values().toArray(new RSAPublicKey[0]);
+ }
+
+ private void loadJwks() {
+ try {
+ String url = serverBaseUrl + "/.well-known/jwks.json";
+ ResponseEntity resp = restTemplate.getForEntity(url, String.class);
+ JsonNode root = objectMapper.readTree(resp.getBody());
+ JsonNode keys = root.get("keys");
+ if (keys == null || !keys.isArray()) return;
+ for (JsonNode key : keys) {
+ String id = key.path("kid").asText(null);
+ String n = key.path("n").asText(null);
+ String e = key.path("e").asText(null);
+ if (id == null || n == null || e == null) continue;
+ if (!cache.containsKey(id)) {
+ cache.put(id, buildRsaPublicKey(n, e));
+ }
+ }
+ } catch (Exception ignored) {}
+ }
+
+ private RSAPublicKey buildRsaPublicKey(String nB64u, String eB64u) throws Exception {
+ BigInteger n = new BigInteger(1, Base64.getUrlDecoder().decode(nB64u));
+ BigInteger e = new BigInteger(1, Base64.getUrlDecoder().decode(eB64u));
+ RSAPublicKeySpec spec = new RSAPublicKeySpec(n, e);
+ return (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(spec);
+ }
+}
+
+
diff --git a/erp_client_sb/src/main/java/com/tashow/erp/security/LocalJwtAuthInterceptor.java b/erp_client_sb/src/main/java/com/tashow/erp/security/LocalJwtAuthInterceptor.java
new file mode 100644
index 0000000..2296d19
--- /dev/null
+++ b/erp_client_sb/src/main/java/com/tashow/erp/security/LocalJwtAuthInterceptor.java
@@ -0,0 +1,80 @@
+package com.tashow.erp.security;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.tashow.erp.service.IAuthService;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.stereotype.Component;
+import org.springframework.web.servlet.HandlerInterceptor;
+
+import java.io.IOException;
+import java.util.Base64;
+
+ /**
+ * 本地拦截器
+ */
+@Component
+public class LocalJwtAuthInterceptor implements HandlerInterceptor {
+
+ private final IAuthService authService;
+ private final ObjectMapper objectMapper = new ObjectMapper();
+
+ public LocalJwtAuthInterceptor(IAuthService authService) {
+ this.authService = authService;
+ }
+
+ @Override
+ public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
+ String uri = request.getRequestURI();
+ if (uri.startsWith("/libs/") || uri.startsWith("/static/") || uri.startsWith("/favicon")
+ || uri.startsWith("/api/cache") || uri.startsWith("/api/update")) {
+ return true;
+ }
+ String auth = request.getHeader("Authorization");
+ if ((auth == null || !auth.startsWith("Bearer ")) && request.getCookies() != null) {
+ for (jakarta.servlet.http.Cookie c : request.getCookies()) {
+ if ("FX_TOKEN".equals(c.getName()) && c.getValue() != null && !c.getValue().isEmpty()) {
+ auth = "Bearer " + c.getValue();
+ break;
+ }
+ }
+ }
+ // 移除本地token查找逻辑,改为仅依赖内存token或cookie
+ if (auth == null || !auth.startsWith("Bearer ")) {
+ return true; // 放行,无token场景由后端处理
+ }
+
+ String token = auth.substring(7).trim();
+ String[] parts = token.split("\\.");
+ if (parts.length != 3) {
+ return true; // 放行,由后端处理
+ }
+
+ try {
+ // 仅解析payload来确保基本格式,不进行设备强校验,避免前端大量401
+ String payloadJson = new String(Base64.getUrlDecoder().decode(parts[1]));
+ objectMapper.readTree(payloadJson);
+ } catch (Exception e) {
+ return true; // 放行,由后端处理
+ }
+ return true;
+ }
+
+ private void writeJson(HttpServletResponse response, int code, String msg) throws IOException {
+ response.setStatus(code == 401 ? 401 : 403);
+ response.setContentType("application/json;charset=UTF-8");
+ response.getWriter().write("{\"code\":" + code + ",\"msg\":\"" + msg + "\"}");
+ }
+
+ private String buildHttpOnlyCookie(String name, String value, int maxAgeSeconds) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(name).append("=").append(value).append(";");
+ sb.append(" Path=/;");
+ sb.append(" HttpOnly;");
+ sb.append(" SameSite=Strict;");
+ if (maxAgeSeconds > 0) {
+ sb.append(" Max-Age=").append(maxAgeSeconds).append(";");
+ }
+ return sb.toString();
+ }
+}
diff --git a/erp_client_sb/src/main/java/com/tashow/erp/service/Alibaba1688Service.java b/erp_client_sb/src/main/java/com/tashow/erp/service/Alibaba1688Service.java
new file mode 100644
index 0000000..aa3542c
--- /dev/null
+++ b/erp_client_sb/src/main/java/com/tashow/erp/service/Alibaba1688Service.java
@@ -0,0 +1,31 @@
+package com.tashow.erp.service;
+
+import com.tashow.erp.model.SearchResult;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 1688识图搜索服务接口
+ */
+public interface Alibaba1688Service {
+
+
+
+ /**
+ * 上传base64图片并获取imageId
+ * @param base64Image base64编码的图片数据
+ * @return 1688的imageId
+ */
+ String uploadImageBase64(String base64Image);
+
+
+ /**
+ * 获取图片的1688详情数据
+ * @param imageUrl
+ * @return
+ */
+ SearchResult get1688Detail(String imageUrl);
+
+
+}
\ No newline at end of file
diff --git a/erp_client_sb/src/main/java/com/tashow/erp/service/IAmazonScrapingService.java b/erp_client_sb/src/main/java/com/tashow/erp/service/IAmazonScrapingService.java
new file mode 100644
index 0000000..d21b0c8
--- /dev/null
+++ b/erp_client_sb/src/main/java/com/tashow/erp/service/IAmazonScrapingService.java
@@ -0,0 +1,27 @@
+package com.tashow.erp.service;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 亚马逊数据采集服务接口
+ *
+ * @author ruoyi
+ */
+public interface IAmazonScrapingService {
+
+ /**
+ * 批量获取亚马逊产品信息
+ *
+ * @param asinList ASIN列表
+ * @param batchId 批次ID
+ * @return 产品信息列表
+ */
+ Map batchGetProductInfo(List asinList, String batchId);
+
+
+
+
+
+
+}
\ No newline at end of file
diff --git a/erp_client_sb/src/main/java/com/tashow/erp/service/IAuthService.java b/erp_client_sb/src/main/java/com/tashow/erp/service/IAuthService.java
new file mode 100644
index 0000000..16ef0d2
--- /dev/null
+++ b/erp_client_sb/src/main/java/com/tashow/erp/service/IAuthService.java
@@ -0,0 +1,50 @@
+package com.tashow.erp.service;
+
+import java.util.Map;
+
+/**
+ * 认证服务接口
+ */
+public interface IAuthService {
+
+ /**
+ * 客户端登录认证
+ */
+ Map login(String username, String password);
+
+ /**
+ * 获取客户端信息
+ */
+ Map getClientInfo();
+
+ /**
+ * 上报错误
+ */
+ void reportError(String errorType, String errorMessage, Exception e);
+
+ /**
+ * 检查版本更新
+ */
+ String checkVersion(String currentVersion);
+
+ /**
+ * 获取客户端ID
+ */
+ String getClientId();
+
+ /**
+ * 验证token
+ */
+ Map verifyToken(String token);
+
+
+ /**
+ * 注册新账号
+ */
+ Map register(String username, String password);
+
+ /**
+ * 检查用户名是否可用
+ */
+ boolean checkUsername(String username);
+}
\ No newline at end of file
diff --git a/erp_client_sb/src/main/java/com/tashow/erp/service/IBanmaOrderService.java b/erp_client_sb/src/main/java/com/tashow/erp/service/IBanmaOrderService.java
new file mode 100644
index 0000000..5ae5e19
--- /dev/null
+++ b/erp_client_sb/src/main/java/com/tashow/erp/service/IBanmaOrderService.java
@@ -0,0 +1,35 @@
+package com.tashow.erp.service;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 斑马订单服务接口
+ *
+ * @author ruoyi
+ */
+public interface IBanmaOrderService {
+
+ /**
+ * 刷新认证Token
+ */
+ void refreshToken();
+
+ /**
+ * 获取店铺列表
+ * @return 店铺列表数据
+ */
+ Map getShops();
+
+ /**
+ * 分页获取订单数据(支持batchId)
+ * @param startDate 开始日期
+ * @param endDate 结束日期
+ * @param page 页码(从1开始)
+ * @param pageSize 每页大小
+ * @param batchId 批次ID
+ * @param shopIds 店铺ID列表
+ * @return 订单数据列表和总数
+ */
+ Map getOrdersByPage(String startDate, String endDate, int page, int pageSize, String batchId, List shopIds);
+}
diff --git a/erp_client_sb/src/main/java/com/tashow/erp/service/ICacheService.java b/erp_client_sb/src/main/java/com/tashow/erp/service/ICacheService.java
new file mode 100644
index 0000000..c25ac7a
--- /dev/null
+++ b/erp_client_sb/src/main/java/com/tashow/erp/service/ICacheService.java
@@ -0,0 +1,12 @@
+package com.tashow.erp.service;
+
+/**
+ * 缓存服务接口
+ */
+public interface ICacheService {
+
+ /**
+ * 保存认证令牌
+ */
+ void saveAuthToken(String service, String token, long expireTimeMillis);
+}
\ No newline at end of file
diff --git a/erp_client_sb/src/main/java/com/tashow/erp/service/IGenmaiService.java b/erp_client_sb/src/main/java/com/tashow/erp/service/IGenmaiService.java
new file mode 100644
index 0000000..eb2ef84
--- /dev/null
+++ b/erp_client_sb/src/main/java/com/tashow/erp/service/IGenmaiService.java
@@ -0,0 +1,18 @@
+package com.tashow.erp.service;
+
+/**
+ * 跟卖精灵服务接口
+ *
+ * @author ruoyi
+ */
+public interface IGenmaiService {
+
+ /**
+ * 使用Playwright打开跟卖精灵网页
+ *
+ * @return 是否成功打开
+ */
+ void openGenmaiWebsite();
+
+
+}
\ No newline at end of file
diff --git a/erp_client_sb/src/main/java/com/tashow/erp/service/IRakutenCacheService.java b/erp_client_sb/src/main/java/com/tashow/erp/service/IRakutenCacheService.java
new file mode 100644
index 0000000..c80acdb
--- /dev/null
+++ b/erp_client_sb/src/main/java/com/tashow/erp/service/IRakutenCacheService.java
@@ -0,0 +1,50 @@
+package com.tashow.erp.service;
+
+import com.tashow.erp.model.RakutenProduct;
+
+import java.util.List;
+
+/**
+ * 乐天产品缓存服务接口
+ */
+public interface IRakutenCacheService {
+
+ /**
+ * 保存产品数据
+ */
+ String saveProducts(List products);
+
+ /**
+ * 使用指定sessionId保存产品数据
+ */
+ void saveProductsWithSessionId(List products, String sessionId);
+
+
+
+ /**
+ * 检查店铺是否有最近的数据(12小时内)
+ */
+ boolean hasRecentData(String shopName);
+
+ /**
+ * 根据店铺名获取已有数据
+ */
+ List getProductsByShopName(String shopName);
+
+ /**
+ * 更新指定店铺的产品sessionId
+ */
+ void updateProductsSessionId(String shopName, String newSessionId);
+
+ /**
+ * 更新指定产品列表的sessionId
+ */
+ void updateSpecificProductsSessionId(List products, String newSessionId);
+
+ /**
+ * 清理指定店铺12小时内的重复数据,为新采集做准备
+ */
+ void cleanRecentDuplicateData(String shopName);
+
+
+}
\ No newline at end of file
diff --git a/erp_client_sb/src/main/java/com/tashow/erp/service/RakutenScrapingService.java b/erp_client_sb/src/main/java/com/tashow/erp/service/RakutenScrapingService.java
new file mode 100644
index 0000000..c70a349
--- /dev/null
+++ b/erp_client_sb/src/main/java/com/tashow/erp/service/RakutenScrapingService.java
@@ -0,0 +1,40 @@
+package com.tashow.erp.service;
+
+import com.tashow.erp.model.RakutenProduct;
+import com.tashow.erp.model.SearchResult;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 乐天商品爬取服务接口
+ */
+public interface RakutenScrapingService {
+
+
+
+
+
+
+
+ /**
+ * 商品爬取,包含1688识图搜索,支持实时回调
+ *
+ * @param shopName 店铺名
+ * @return 包含1688搜索结果的商品列表
+ */
+ List scrapeProductsWithSearch(String shopName);
+
+
+ /**
+ * 根据图片URL更新1688搜索结果的所有字段
+ */
+ void update1688DataByImageUrl(SearchResult searchResult, String sessionId, String imageUrl);
+
+ /**
+ * 获取最新产品数据并转换为前端格式
+ */
+ List> getLatestProductsForDisplay();
+
+
+}
\ No newline at end of file
diff --git a/erp_client_sb/src/main/java/com/tashow/erp/service/impl/Alibaba1688ServiceImpl.java b/erp_client_sb/src/main/java/com/tashow/erp/service/impl/Alibaba1688ServiceImpl.java
new file mode 100644
index 0000000..6033099
--- /dev/null
+++ b/erp_client_sb/src/main/java/com/tashow/erp/service/impl/Alibaba1688ServiceImpl.java
@@ -0,0 +1,307 @@
+package com.tashow.erp.service.impl;
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONObject;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.tashow.erp.model.SearchResult;
+import com.tashow.erp.service.Alibaba1688Service;
+import com.tashow.erp.utils.Alibaba1688CookieUtil;
+import com.tashow.erp.utils.ErrorReporter;
+import com.tashow.erp.utils.QiniuUtil;
+import com.tashow.erp.utils.PersistentDriverManager;
+import org.openqa.selenium.chrome.ChromeDriver;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.*;
+import org.springframework.http.client.SimpleClientHttpRequestFactory;
+import org.springframework.stereotype.Service;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.client.RestTemplate;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.security.cert.X509Certificate;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * 1688识图搜索服务
+ */
+@Service
+public class Alibaba1688ServiceImpl implements Alibaba1688Service {
+ private static final Logger logger = LoggerFactory.getLogger(Alibaba1688ServiceImpl.class);
+ private final Random random = new Random();
+ ObjectMapper objectMapper = new ObjectMapper();
+ private final RestTemplate restTemplate = new RestTemplate();
+ private final RestTemplate noSslRestTemplate = createNoSslRestTemplate();
+ @Autowired
+ private ErrorReporter errorReporter;
+ @Autowired
+ private PersistentDriverManager driverManager;
+ private RestTemplate createNoSslRestTemplate() {
+ try {
+ TrustManager[] trustManagers = new TrustManager[] {
+ new X509TrustManager() {
+ public X509Certificate[] getAcceptedIssuers() { return null; }
+ public void checkClientTrusted(X509Certificate[] certs, String authType) {}
+ public void checkServerTrusted(X509Certificate[] certs, String authType) {}
+ }
+ };
+ SSLContext sslContext = SSLContext.getInstance("SSL");
+ sslContext.init(null, trustManagers, new java.security.SecureRandom());
+ HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());
+ HttpsURLConnection.setDefaultHostnameVerifier((hostname, session) -> true);
+
+ SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory() {
+ @Override
+ protected void prepareConnection(HttpURLConnection connection, String httpMethod) throws IOException {
+ super.prepareConnection(connection, httpMethod);
+ if (connection instanceof HttpsURLConnection) {
+ ((HttpsURLConnection) connection).setSSLSocketFactory(sslContext.getSocketFactory());
+ ((HttpsURLConnection) connection).setHostnameVerifier((hostname, session) -> true);
+ }
+ }
+ };
+ return new RestTemplate(requestFactory);
+ } catch (Exception e) {
+ logger.warn("创建忽略SSL证书的RestTemplate失败,使用默认RestTemplate", e);
+ return new RestTemplate();
+ }
+ }
+
+
+ /**
+ * 通过1688 API获取商品详情链接,价格
+ * @return
+ */
+ @Override
+ public SearchResult get1688Detail(String uploadedUrl) {
+ String fileName = "temp_" + System.currentTimeMillis() + ".png";
+ List detailUrls = new ArrayList<>();
+ SearchResult result = new SearchResult();
+ List prices = new ArrayList<>();
+ try {
+ String imageUrl = QiniuUtil.uploadFromUrl(uploadedUrl, fileName);
+ String token = Alibaba1688CookieUtil.getToken();
+ long timestamp = System.currentTimeMillis();
+ // Step 1: 获取推荐的 offerId 列表
+ String jsonData = "{\"appId\":\"32517\",\"params\":\"{\\\"categoryId\\\":-1,\\\"imageAddress\\\":\\\"" + imageUrl + "\\\",\\\"interfaceName\\\":\\\"imageOfferSearchService\\\",\\\"needYolocrop\\\":false,\\\"pageIndex\\\":\\\"1\\\",\\\"pageSize\\\":\\\"40\\\",\\\"searchScene\\\":\\\"image\\\",\\\"snAppAb\\\":true,\\\"appName\\\":\\\"ios\\\",\\\"scene\\\":\\\"seoSearch\\\"}\"}";
+ String sign = Alibaba1688CookieUtil.generateSign(token, String.valueOf(timestamp), jsonData);
+ String url = "https://h5api.m.1688.com/h5/mtop.relationrecommend.wirelessrecommend.recommend/2.0/" + "?jsv=2.6.1&appKey=" + Alibaba1688CookieUtil.APP_KEY + "&t=" + timestamp + "&sign=" + sign + "&v=2.0&type=originaljson&isSec=0&timeout=10000" + "&api=mtop.relationrecommend.WirelessRecommend.recommend&ignoreLogin=true&prefix=h5api&dataType=json";
+ HttpHeaders headers = new HttpHeaders();
+ headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
+ headers.set("Cookie", Alibaba1688CookieUtil.getCookieString());
+ MultiValueMap formData = new LinkedMultiValueMap<>();
+ formData.add("data", jsonData);
+ HttpEntity> requestEntity = new HttpEntity<>(formData, headers);
+ ResponseEntity response = restTemplate.exchange(url, HttpMethod.POST, requestEntity, String.class);
+ JsonNode root = objectMapper.readTree(response.getBody());
+ Iterator offerIterator = root.path("data").path("offerList").path("offers").elements();
+ //运费
+ Set freight = new HashSet<>();
+ for (int i = 0; i < 10 && offerIterator.hasNext(); i++) {
+ JsonNode offer = offerIterator.next();
+ String offerId = offer.path("id").asText();
+ String freightProvFirstFee = offer.path("freightProvFirstFee").asText();
+ Optional.ofNullable(freightProvFirstFee)
+ .map(s -> s.split(";", 2)[0])
+ .map(s -> s.split(":", 2))
+ .filter(parts -> parts.length == 2 && !parts[1].isBlank())
+ .map(parts -> Double.parseDouble(parts[1]) / 100.0)
+ .ifPresent(freight::add);
+ prices.add(offer.path("normalPrice").asDouble());
+ detailUrls.add(offerId);
+ }
+ Collections.sort(prices);
+ List freightFee = new ArrayList<>(freight);
+ Collections.sort(freightFee);
+ //中间值
+ Double median=0.0;
+ if(!prices.isEmpty()){
+ median = prices.get(prices.size() / 2 - 1);
+ }
+ //sku价格
+ Map skuPrices = new TreeMap<>();
+ for (String offerId : detailUrls) {
+ skuPrices.clear();
+ skuPrices.putAll(getSkuPrice(offerId));
+ if (skuPrices.size() >= 3) {
+ break;
+ }
+ }
+
+ result.setSkuPrice(skuPrices);
+ result.setMedian( median);
+ result.setMapRecognitionLink( uploadImageBase64(imageUrl));
+ System.out.println("运费"+freightFee);
+ result.setFreight(freightFee.isEmpty() ? 0.0 :freightFee.get(freightFee.size()/2-1));
+ // String weight = getWeight(detailUrls);
+ // result.setWeight(weight);
+ return result;
+ } catch (Exception e) {
+ errorReporter.reportDataCollectError("Alibaba1688Service", e);
+ logger.error("通过1688 API获取商品详情链接失败: {}", e.getMessage(), e);
+ return result;
+ }
+ }
+
+
+ /**
+ * 获取sku价格
+ *
+ * @param offerId
+ * @return
+ */
+ private Map getSkuPrice(String offerId) {
+ TreeMap skuPrice = new TreeMap<>();
+ HttpHeaders headers = new HttpHeaders();
+ TreeMap resultMap = new TreeMap<>();
+ try {
+ headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
+ headers.set("Cookie", Alibaba1688CookieUtil.getCookieString());
+ String token = Alibaba1688CookieUtil.getToken();
+ long timestamp = System.currentTimeMillis();
+ Map payload = Map.of("offerId", offerId);
+ String requestJson = objectMapper.writeValueAsString(payload);
+ String requestSign = Alibaba1688CookieUtil.generateSign(token, String.valueOf(timestamp), requestJson);
+ String skuUrl = "https://h5api.m.1688.com/h5/mtop.1688.pc.plugin.od.sku.query/1.0/" + "?jsv=2.6.3&appKey=" + Alibaba1688CookieUtil.APP_KEY + "&t=" + timestamp + "&api=mtop.1688.pc.plugin.od.sku.query&sign=" + requestSign + "&v=1.0&type=originaljson&isSec=0&dataType=json";
+ MultiValueMap skuForm = new LinkedMultiValueMap<>();
+ skuForm.add("data", requestJson);
+ HttpEntity> skuRequestEntity = new HttpEntity<>(skuForm, headers);
+ ResponseEntity skuResponse = restTemplate.exchange(skuUrl, HttpMethod.POST, skuRequestEntity, String.class);
+ JsonNode skuRoot = objectMapper.readTree(skuResponse.getBody());
+ JsonNode skuMap = skuRoot.path("data");
+ if (skuMap.isEmpty()) {
+ skuPrice.put(-1.0, "无sku");
+ return skuPrice;
+ }
+ if (skuMap.isObject()) {
+ for (Iterator> it = skuMap.fields(); it.hasNext(); ) {
+ Map.Entry entry = it.next();
+ JsonNode skuInfo = entry.getValue();
+ String attributes = skuInfo.path("attributes").asText();
+ double price = extractPriceFromSku(skuInfo);
+ skuPrice.put(price, attributes);
+ }
+ }
+ List keys = new ArrayList<>(skuPrice.keySet());
+ Map.Entry midEntry = Map.entry(keys.get(keys.size() / 2), skuPrice.get(keys.get(keys.size() / 2)));
+ resultMap.put(skuPrice.firstKey(), skuPrice.firstEntry().getValue());
+ resultMap.put(midEntry.getKey(), midEntry.getValue());
+ resultMap.put(skuPrice.lastKey(), skuPrice.lastEntry().getValue());
+
+ } catch (JsonProcessingException e) {
+ logger.error("获取sku价格失败: {}", e.getMessage(), e);
+ return new HashMap<>();
+ }
+ return resultMap;
+ }
+
+ /**
+ * 从SKU信息中提取价格
+ * 处理1688 API中各种复杂的价格结构
+ *
+ * @param skuInfo SKU节点信息
+ * @return 提取到的价格,如果无法提取则返回0.0
+ */
+ private double extractPriceFromSku(JsonNode skuInfo) {
+ try {
+ JsonNode priceNode = skuInfo.path("price");
+ // 如果price是数值且大于0,直接返回
+ if (priceNode.isNumber() && priceNode.asDouble() > 0) {
+ return priceNode.asDouble();
+ }
+
+ // 如果price是数组且有元素,检查第一个元素
+ if (priceNode.isArray() && priceNode.size() > 0) {
+ JsonNode firstPrice = priceNode.get(0);
+ if (firstPrice.isNumber() && firstPrice.asDouble() > 0) {
+ return firstPrice.asDouble();
+ }
+ if (firstPrice.isObject() && firstPrice.has("price") && firstPrice.path("price").asDouble() > 0) {
+ return firstPrice.path("price").asDouble();
+ }
+ }
+ return 0.0;
+ } catch (Exception e) {
+ logger.error("提取SKU价格时发生异常: {}", e.getMessage(), e);
+ return 0.0;
+ }
+ }
+
+ /**
+ * 上传图片并获取图片ID
+ * @return
+ */
+ public String uploadImageBase64(String imageUrl) {
+ String token = Alibaba1688CookieUtil.getToken();
+ long timestamp = System.currentTimeMillis();
+ byte[] imageBytes = noSslRestTemplate.getForObject(imageUrl, byte[].class);
+ String base64Image = Base64.getEncoder().encodeToString(imageBytes);
+ String jsonData = "{\"appId\":32517,\"params\":\"{\\\"searchScene\\\":\\\"imageEx\\\",\\\"interfaceName\\\":\\\"imageBase64ToImageId\\\",\\\"serviceParam.extendParam[imageBase64]\\\":\\\"" + base64Image + "\\\",\\\"subChannel\\\":\\\"pc_image_search_image_id\\\"}\"}";
+ String sign = Alibaba1688CookieUtil.generateSign(token, String.valueOf(timestamp), jsonData);
+ String url = "https://h5api.m.1688.com/h5/mtop.relationrecommend.wirelessrecommend.recommend/2.0/?jsv=2.7.4&appKey=" + Alibaba1688CookieUtil.APP_KEY + "&t=" + timestamp + "&sign=" + sign + "&api=mtop.relationrecommend.wirelessrecommend.recommend&v=2.0&type=originaljson&timeout=20000&dataType=jsonp";
+ HttpHeaders headers = new HttpHeaders();
+ headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
+ headers.set("Cookie", Alibaba1688CookieUtil.getCookieString());
+ headers.set("authority", "h5api.m.1688.com");
+ headers.set("accept", "application/json");
+ headers.set("origin", "https://www.1688.com");
+ headers.set("referer", "https://www.1688.com/");
+ headers.set("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36");
+ MultiValueMap formData = new LinkedMultiValueMap<>();
+ formData.add("data", jsonData);
+ HttpEntity> requestEntity = new HttpEntity<>(formData, headers);
+ ResponseEntity response = restTemplate.exchange(url, HttpMethod.POST, requestEntity, String.class);
+ Alibaba1688CookieUtil.setCookie(response);
+ JSONObject json = JSON.parseObject(response.getBody());
+ return "https://s.1688.com/youyuan/index.htm?tab=imageSearch&imageId=" + json.getJSONObject("data").getString("imageId");
+ }
+
+
+ private String getWeight(List ids) {
+ Pattern WEIGHT_PATTERN = Pattern.compile("\"(?:weight|unitWeight)\":\\s*([1-9]\\d*(?:\\.\\d+)?)");
+ List weightList = new ArrayList<>();
+ ChromeDriver driver = driverManager.getCurrentDriver();
+ Set weightSet = new HashSet<>();
+ try {
+ for (int i = 0; weightSet.size() <= 2; i++) {
+ driver.get("https://detail.1688.com/offer/" + ids.get(i) + ".html");
+ if(i==0) driver.navigate().refresh();
+ if (Objects.equals(driver.getTitle(), "验证码拦截")) {
+ driver = driverManager.switchToHeadful();
+ while (Objects.equals(driver.getTitle(), "验证码拦截")) {
+ Thread.sleep(1000);
+ }
+ }else if(Objects.equals(driver.getTitle(), "淘宝网 - 淘!我喜欢")){
+
+ }
+ System.out.println("标题"+driver.getTitle());
+ String source = driver.getPageSource();
+ Matcher weightMatcher = WEIGHT_PATTERN.matcher(source);
+ if (weightMatcher.find()) {
+ String weightValue = weightMatcher.group(1);
+ String width = weightValue.contains(".") ? (int) (Float.parseFloat(weightValue) * 1000) + "g" : weightValue + "g";
+ weightSet.add(width);
+ System.out.println("重量"+width);
+ }
+ Thread.sleep(2000+random.nextInt(3000));
+ }
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ errorReporter.reportDataCollectError("获取重量出错",e);
+ }
+ weightList.addAll(weightSet);
+ weightList.sort((a, b) -> Integer.compare(Integer.parseInt(a.replace("g", "")), Integer.parseInt(b.replace("g", ""))));
+ System.out.println("weightList: " +ids+"::::::"+ weightList);
+ return weightList.get(1);
+ }
+
+}
\ No newline at end of file
diff --git a/erp_client_sb/src/main/java/com/tashow/erp/service/impl/AmazonScrapingServiceImpl.java b/erp_client_sb/src/main/java/com/tashow/erp/service/impl/AmazonScrapingServiceImpl.java
new file mode 100644
index 0000000..83f86c9
--- /dev/null
+++ b/erp_client_sb/src/main/java/com/tashow/erp/service/impl/AmazonScrapingServiceImpl.java
@@ -0,0 +1,163 @@
+package com.tashow.erp.service.impl;
+import com.tashow.erp.entity.AmazonProductEntity;
+import com.tashow.erp.repository.AmazonProductRepository;
+import com.tashow.erp.service.IAmazonScrapingService;
+import com.tashow.erp.utils.DataReportUtil;
+import com.tashow.erp.utils.RakutenProxyUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import us.codecraft.webmagic.Page;
+import us.codecraft.webmagic.Site;
+import us.codecraft.webmagic.Spider;
+import us.codecraft.webmagic.processor.PageProcessor;
+import us.codecraft.webmagic.selector.Html;
+import java.time.LocalDateTime;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * 亚马逊数据采集服务实现类
+ *
+ * @author ruoyi
+ */
+@Service
+public class AmazonScrapingServiceImpl implements IAmazonScrapingService, PageProcessor {
+ private static final Logger logger = LoggerFactory.getLogger(AmazonScrapingServiceImpl.class);
+ @Autowired
+ private AmazonProductRepository amazonProductRepository;
+ @Autowired
+ private DataReportUtil dataReportUtil;
+ private final Random random = new Random();
+ private static volatile Spider activeSpider = null;
+ private static final Object spiderLock = new Object();
+ private final Map> resultCache = new ConcurrentHashMap<>();
+ private final Site site = Site.me().setRetryTimes(3).setSleepTime(2000 + random.nextInt(2000))
+ .setTimeOut(15000).setUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) " + "AppleWebKit/537.36 (KHTML, like Gecko) " + "Chrome/128.0.0.0 Safari/537.36").addHeader("accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9").addHeader("accept-language", "ja,en;q=0.9,zh-CN;q=0.8,zh;q=0.7").addHeader("cache-control", "max-age=0").addHeader("upgrade-insecure-requests", "1").addHeader("sec-ch-ua", "\"Chromium\";v=\"128\", \"Not=A?Brand\";v=\"24\"").addHeader("sec-ch-ua-mobile", "?0").addHeader("sec-ch-ua-platform", "\"Windows\"").addHeader("sec-fetch-site", "none").addHeader("sec-fetch-mode", "navigate").addHeader("sec-fetch-user", "?1").addHeader("sec-fetch-dest", "document").addCookie("i18n-prefs", "JPY").addCookie("session-id", "358-1261309-0483141").addCookie("session-id-time", "2082787201l").addCookie("i18n-prefs", "JPY").addCookie("lc-acbjp", "zh_CN").addCookie("ubid-acbjp", "357-8224002-9668932");
+ /**
+ * 处理亚马逊页面数据提取
+ */
+ @Override
+ public void process(Page page) {
+ Html html = page.getHtml();
+ Map resultMap = new HashMap<>();
+ String priceSymbol = html.xpath("//span[@class='a-price-symbol']/text()").toString();
+ String priceWhole = html.xpath("//span[@class='a-price-whole']/text()").toString();
+ String price = priceSymbol + priceWhole;
+ if (price.isEmpty()) {
+ price = html.xpath("//span[@class='a-price-range']/text()").toString();
+ }
+
+ String seller = html.xpath("//a[@id='sellerProfileTriggerId']/text()").toString();
+ if (seller == null || seller.isEmpty()) {
+ seller = html.xpath("//span[@class='a-size-small offer-display-feature-text-message']/text()").toString();
+ }
+ resultMap.put("seller", seller);
+ if (price != null || seller != null) {
+ resultMap.put("price", price);
+ } else {
+ throw new RuntimeException("Retry this page");
+ }
+
+ String asin = html.xpath("//input[@id='ASIN']/@value").toString();
+ if (asin == null || asin.isEmpty()) {
+ String[] parts = page.getUrl().toString().split("/dp/");
+ if (parts.length > 1) asin = parts[1].split("/")[0].split("\\?")[0];
+ }
+ String title = html.xpath("//span[@id='productTitle']/text()").toString();
+ if (title == null || title.isEmpty())
+ title = html.xpath("//h1[@class='a-size-large a-spacing-none']/text()").toString();
+ resultMap.put("asin", asin != null ? asin : "");
+ resultMap.put("title", (title == null || title.isEmpty()) ? "未获取" : title.trim());
+
+ resultCache.put(asin, resultMap);
+ page.putField("resultMap", resultMap);
+ }
+
+ /**
+ * 获取WebMagic站点配置
+ */
+ @Override
+ public Site getSite() {
+ return site;
+ }
+
+ /**
+ * 批量获取产品信息
+ */
+ @Override
+ public Map batchGetProductInfo(List asinList, String batchId) {
+ String sessionId = (batchId != null) ? batchId : "SINGLE_" + UUID.randomUUID();
+ List> products = new ArrayList<>();
+
+ for (String asin : asinList) {
+ if (asin == null || asin.trim().isEmpty()) continue;
+ String cleanAsin = asin.replaceAll("[^a-zA-Z0-9]", "");
+ Map result = new HashMap<>();
+
+ amazonProductRepository.findByAsin(cleanAsin).ifPresentOrElse(entity -> {
+ if (entity.getCreatedAt().isAfter(LocalDateTime.now().minusHours(1))) {
+ result.put("asin", entity.getAsin());
+ result.put("title", entity.getTitle());
+ result.put("price", entity.getPrice());
+ result.put("seller", entity.getSeller());
+ result.put("imageUrl", entity.getImageUrl());
+ result.put("productUrl", entity.getProductUrl());
+ result.put("brand", entity.getBrand());
+ result.put("category", entity.getCategory());
+ result.put("rating", entity.getRating());
+ result.put("reviewCount", entity.getReviewCount());
+ result.put("availability", entity.getAvailability());
+ products.add(result);
+ }
+ }, () -> {
+ // 数据库没有或过期 -> 爬取
+ String url = "https://www.amazon.co.jp/dp/" + cleanAsin;
+ RakutenProxyUtil proxyUtil = new RakutenProxyUtil();
+ synchronized (spiderLock) {
+ activeSpider = Spider.create(this)
+ .addUrl(url)
+ .setDownloader(proxyUtil.createProxyDownloader(proxyUtil.detectSystemProxy(url)))
+ .thread(1);
+ activeSpider.run();
+ activeSpider = null;
+ }
+ result.putAll(resultCache.getOrDefault(cleanAsin, Map.of("asin", cleanAsin, "price", "", "seller", "", "title", "")));
+
+ // 存库
+ AmazonProductEntity entity = new AmazonProductEntity();
+ entity.setAsin(cleanAsin);
+ entity.setTitle((String) result.get("title"));
+ entity.setPrice((String) result.get("price"));
+ entity.setSeller((String) result.get("seller"));
+ entity.setImageUrl((String) result.get("imageUrl"));
+ entity.setProductUrl((String) result.get("productUrl"));
+ entity.setBrand((String) result.get("brand"));
+ entity.setCategory((String) result.get("category"));
+ entity.setRating((String) result.get("rating"));
+ entity.setReviewCount((String) result.get("reviewCount"));
+ entity.setAvailability((String) result.get("availability"));
+ entity.setSessionId(sessionId);
+ entity.setCreatedAt(LocalDateTime.now());
+ try {
+ amazonProductRepository.save(entity);
+ dataReportUtil.reportDataCollection("AMAZON", 1, "0");
+ } catch (Exception e) {
+ logger.warn("保存商品数据失败: {}", cleanAsin);
+ }
+ products.add(result);
+ });
+ }
+
+ long failedCount = products.stream().filter(p -> p.get("price").toString().isEmpty()).count();
+ return Map.of(
+ "products", products,
+ "total", products.size(),
+ "success", true,
+ "failedCount", failedCount
+ );
+ }
+
+
+}
\ No newline at end of file
diff --git a/erp_client_sb/src/main/java/com/tashow/erp/service/impl/AuthServiceImpl.java b/erp_client_sb/src/main/java/com/tashow/erp/service/impl/AuthServiceImpl.java
new file mode 100644
index 0000000..efe9f59
--- /dev/null
+++ b/erp_client_sb/src/main/java/com/tashow/erp/service/impl/AuthServiceImpl.java
@@ -0,0 +1,246 @@
+package com.tashow.erp.service.impl;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.tashow.erp.service.IAuthService;
+import com.tashow.erp.utils.ApiForwarder;
+import com.tashow.erp.utils.DeviceUtils;
+import lombok.Getter;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+import java.util.HashMap;
+import java.util.Map;
+import jakarta.annotation.PostConstruct;
+
+/**
+ * 认证服务实现类 - 简化版
+ * 提供基础的登录、注册、token验证功能
+ */
+@Service
+public class AuthServiceImpl implements IAuthService {
+
+ @Value("${api.server.base-url}")
+ private String serverApiUrl;
+
+ @Value("${project.version:2.1.0}")
+ private String appVersion;
+
+ private final ObjectMapper objectMapper = new ObjectMapper();
+ @org.springframework.beans.factory.annotation.Autowired
+ private ApiForwarder apiForwarder;
+
+ @org.springframework.beans.factory.annotation.Autowired
+ private com.tashow.erp.repository.CacheDataRepository cacheDataRepository;
+
+ @Getter
+ private String clientId = DeviceUtils.generateDeviceId();
+ private String accessToken;
+ private String refreshToken;
+
+ /**
+ * 应用启动时从SQLite恢复token
+ */
+ @PostConstruct
+ private void initTokenFromCache() {
+ try {
+ cacheDataRepository.findByCacheKey("token").ifPresent(entity ->
+ accessToken = entity.getCacheValue()
+ );
+ } catch (Exception ignored) {}
+ }
+
+ /**
+ * 保存token到SQLite
+ */
+ private void saveTokenToCache(String token) {
+ try {
+ com.tashow.erp.entity.CacheDataEntity entity = cacheDataRepository.findByCacheKey("token")
+ .orElse(new com.tashow.erp.entity.CacheDataEntity());
+ entity.setCacheKey("token");
+ entity.setCacheValue(token);
+ cacheDataRepository.save(entity);
+ } catch (Exception ignored) {}
+ }
+
+ /**
+ * 客户端登录认证
+ */
+ @Override
+ public Map login(String username, String password) {
+ Map result = new HashMap<>();
+ Map loginData = new HashMap<>();
+ loginData.put("username", username);
+ loginData.put("password", password);
+ loginData.putAll(getClientInfo());
+
+ JsonNode response = sendPostRequest("/monitor/account/login", loginData);
+ if (response.has("code") && response.get("code").asInt() == 200) {
+ JsonNode data = response.has("data") ? response.get("data") : response;
+ accessToken = data.has("accessToken") ? data.get("accessToken").asText() :
+ data.has("token") ? data.get("token").asText() : null;
+ refreshToken = data.has("refreshToken") ? data.get("refreshToken").asText() : null;
+
+ if (accessToken != null) {
+ saveTokenToCache(accessToken);
+ }
+
+ result.put("success", true);
+ result.put("message", "登录成功");
+ result.put("token", accessToken);
+ result.put("permissions", data.has("permissions") ? data.get("permissions").asText() : null);
+ } else {
+ result.put("success", false);
+ result.put("message", response.has("msg") ? response.get("msg").asText() : "登录失败");
+ }
+ return result;
+ }
+
+ /**
+ * 获取客户端基本信息
+ */
+ @Override
+ public Map getClientInfo() {
+ Map info = new HashMap<>();
+ info.put("clientId", clientId);
+ info.put("appVersion", appVersion);
+ info.put("osName", System.getProperty("os.name"));
+ return info;
+ }
+
+ /**
+ * 上报错误信息
+ */
+ @Override
+ public void reportError(String errorType, String errorMessage, Exception e) {
+ try {
+ Map errorData = new HashMap<>();
+ errorData.put("clientId", clientId);
+ errorData.put("errorType", errorType);
+ errorData.put("errorMessage", errorMessage);
+ sendPostRequest("/monitor/error", errorData);
+ } catch (Exception ignored) {}
+ }
+
+ /**
+ * 检查版本更新
+ */
+ @Override
+ public String checkVersion(String currentVersion) {
+ JsonNode response = sendGetRequest("/monitor/version?currentVersion=" + currentVersion);
+ return response.toString();
+ }
+
+
+ /**
+ * 发送POST请求
+ */
+ private JsonNode sendPostRequest(String path, Map data){
+ if (data != null) {
+ data.putIfAbsent("_clientUser", System.getProperty("user.name"));
+ data.putIfAbsent("_clientOs", System.getProperty("os.name"));
+ }
+ org.springframework.http.ResponseEntity> resp = apiForwarder.post(path, data, buildAuthHeader());
+ return objectMapper.valueToTree(resp.getBody());
+ }
+
+ /**
+ * 发送GET请求
+ */
+ private JsonNode sendGetRequest(String path){
+ org.springframework.http.ResponseEntity> resp = apiForwarder.get(path, buildAuthHeader());
+ return objectMapper.valueToTree(resp.getBody());
+ }
+
+ /**
+ * 构建认证头
+ */
+ private String buildAuthHeader(){
+ return (accessToken == null || accessToken.isEmpty()) ? null : ("Bearer " + accessToken.trim());
+ }
+
+ /**
+ * 验证token
+ */
+ @Override
+ public Map verifyToken(String token) {
+ Map result = new HashMap<>();
+ Map verifyData = new HashMap<>();
+ verifyData.put("token", token);
+ JsonNode response = sendPostRequest("/monitor/account/verify", verifyData);
+
+ if (response.has("code") && response.get("code").asInt() == 200) {
+ JsonNode dataNode = response.has("data") ? response.get("data") : response;
+ result.put("success", true);
+ result.put("username", dataNode.get("username").asText());
+ result.put("permissions", dataNode.get("permissions").asText());
+ } else {
+ result.put("success", false);
+ result.put("message", "token验证失败");
+ }
+ return result;
+ }
+
+ /**
+ * 注册新账号
+ */
+ @Override
+ public Map register(String username, String password) {
+ Map result = new HashMap<>();
+ try {
+ Map registerData = new HashMap<>();
+ registerData.put("accountName", username);
+ registerData.put("username", username);
+ registerData.put("password", password);
+ registerData.putAll(getClientInfo());
+ JsonNode response = sendPostRequest("/monitor/account/register", registerData);
+ if (response.has("code") && response.get("code").asInt() == 200) {
+ JsonNode data = response.has("data") ? response.get("data") : response;
+ String newAccessToken = data.has("accessToken") ? data.get("accessToken").asText() : null;
+ String newRefreshToken = data.has("refreshToken") ? data.get("refreshToken").asText() : null;
+
+ if (newAccessToken != null) {
+ accessToken = newAccessToken;
+ refreshToken = newRefreshToken;
+ saveTokenToCache(newAccessToken);
+ }
+
+ result.put("success", true);
+ result.put("message", "注册成功");
+ } else {
+ String errorMessage = response.has("msg") ? response.get("msg").asText() :
+ (response.has("message") ? response.get("message").asText() : "注册失败");
+ result.put("success", false);
+ result.put("message", errorMessage);
+ }
+ } catch (Exception e) {
+ result.put("success", false);
+ result.put("message", "注册失败:" + e.getMessage());
+ }
+ return result;
+ }
+
+ /**
+ * 检查用户名是否可用
+ */
+ @Override
+ public boolean checkUsername(String username) {
+ try {
+ JsonNode response = sendGetRequest("/monitor/account/check-username?username=" + username);
+ if (response.has("code") && response.get("code").asInt() == 200) {
+ JsonNode dataNode = response.has("data") ? response.get("data") : response;
+ return dataNode.asBoolean();
+ }
+ } catch (Exception ignored) {}
+ return false;
+ }
+
+ /**
+ * 退出登录,清除本地token
+ */
+ public void logout() {
+ try {
+ accessToken = null;
+ refreshToken = null;
+ cacheDataRepository.deleteByCacheKey("token");
+ } catch (Exception ignored) {}
+ }
+}
\ No newline at end of file
diff --git a/erp_client_sb/src/main/java/com/tashow/erp/service/impl/BanmaOrderServiceImpl.java b/erp_client_sb/src/main/java/com/tashow/erp/service/impl/BanmaOrderServiceImpl.java
new file mode 100644
index 0000000..48c6608
--- /dev/null
+++ b/erp_client_sb/src/main/java/com/tashow/erp/service/impl/BanmaOrderServiceImpl.java
@@ -0,0 +1,292 @@
+package com.tashow.erp.service.impl;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.tashow.erp.entity.BanmaOrderEntity;
+import com.tashow.erp.repository.BanmaOrderRepository;
+import com.tashow.erp.service.ICacheService;
+import com.tashow.erp.service.IBanmaOrderService;
+import com.tashow.erp.utils.DataReportUtil;
+import com.tashow.erp.utils.LoggerUtil;
+import com.tashow.erp.utils.SagawaExpressSdk;
+import com.tashow.erp.utils.StringUtils;
+import org.slf4j.Logger;
+import org.springframework.boot.web.client.RestTemplateBuilder;
+import org.springframework.http.*;
+import org.springframework.stereotype.Service;
+import org.springframework.web.client.RestTemplate;
+
+import java.time.Duration;
+import java.time.LocalDateTime;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
+/**
+ * 斑马订单服务实现类 - 极简版
+ * 所有功能统一到核心方法,彻底消除代码分散
+ *
+ * @author ruoyi
+ */
+@Service
+public class BanmaOrderServiceImpl implements IBanmaOrderService {
+ private static final Logger logger = LoggerUtil.getLogger(BanmaOrderServiceImpl.class);
+ private static final String SERVICE_NAME = "banma";
+ private static final String LOGIN_URL = "https://banma365.cn/api/login";
+ private static final String LOGIN_USERNAME = "大赢家网络科技(主账号)";
+ private static final String LOGIN_PASSWORD = "banma123456";
+ private static final String API_URL = "https://banma365.cn/api/order/list?%srecipientName=&page=%d&size=%d&markFlag=0&state=4&_t=%d";
+ private static final String API_URL_WITH_TIME = "https://banma365.cn/api/order/list?%srecipientName=&page=%d&size=%d&markFlag=0&state=4&orderedAtStart=%s&orderedAtEnd=%s&_t=%d";
+ private static final String TRACKING_URL = "https://banma365.cn/zebraExpressHub/web/tracking/getByExpressNumber/%s";
+ private static final long TOKEN_EXPIRE_TIME = 24 * 60 * 60 * 1000;
+ private RestTemplate restTemplate;
+ private final ObjectMapper objectMapper = new ObjectMapper();
+ private final ICacheService cacheService;
+ private final BanmaOrderRepository banmaOrderRepository;
+ private final DataReportUtil dataReportUtil;
+ private String currentAuthToken;
+ // 当前批量采集的sessionId
+ private String currentBatchSessionId = null;
+ // 物流信息缓存,避免重复查询
+ private final Map trackingInfoCache = new ConcurrentHashMap<>();
+ public BanmaOrderServiceImpl(BanmaOrderRepository banmaOrderRepository, ICacheService cacheService, DataReportUtil dataReportUtil) {
+ this.banmaOrderRepository = banmaOrderRepository;
+ this.cacheService = cacheService;
+ this.dataReportUtil = dataReportUtil;
+ RestTemplateBuilder builder = new RestTemplateBuilder();
+ builder.connectTimeout(Duration.ofSeconds(5));
+ builder.readTimeout(Duration.ofSeconds(10));
+ restTemplate = builder.build();
+ initializeAuthToken();
+ }
+ /**
+ * 初始化认证令牌
+ */
+ private void initializeAuthToken() {
+ refreshToken();
+ /* currentAuthToken = cacheService.getAuthToken(SERVICE_NAME);
+ if (currentAuthToken == null) {
+
+ } else {
+ logger.info("从缓存加载斑马认证令牌成功");
+ }*/
+ }
+
+ /**
+ * 刷新斑马认证令牌
+ */
+ @Override
+ public void refreshToken() {
+ Map params = new HashMap<>();
+ params.put("username", LOGIN_USERNAME);
+ params.put("password", LOGIN_PASSWORD);
+ HttpHeaders headers = new HttpHeaders();
+ headers.set("Content-Type", "application/json");
+
+ ResponseEntity response = restTemplate.postForEntity(LOGIN_URL, new HttpEntity<>(params, headers), Map.class);
+ Optional.ofNullable(response.getBody())
+ .filter(body -> Integer.valueOf(0).equals(body.get("code")))
+ .map(body -> (Map) body.get("data"))
+ .map(data -> (String) data.get("token"))
+ .filter(StringUtils::isNotEmpty)
+ .ifPresent(token -> {
+ currentAuthToken = "Bearer " + token;
+ cacheService.saveAuthToken(SERVICE_NAME, currentAuthToken, System.currentTimeMillis() + TOKEN_EXPIRE_TIME);
+ });
+ }
+
+ /**
+ * 获取店铺列表
+ */
+ @Override
+ public Map getShops() {
+ HttpHeaders headers = new HttpHeaders();
+ headers.set("Authorization", currentAuthToken);
+ HttpEntity httpEntity = new HttpEntity<>(headers);
+
+ String url = "https://banma365.cn/api/shop/list?_t=" + System.currentTimeMillis();
+ ResponseEntity response = restTemplate.exchange(url, HttpMethod.GET, httpEntity, Map.class);
+
+ return response.getBody() != null ? response.getBody() : new HashMap<>();
+ }
+
+ /**
+ * 分页获取订单数据
+ */
+ @Override
+ public Map getOrdersByPage(String startDate, String endDate, int page, int pageSize, String batchId, List shopIds) {
+ if (page == 1) {
+ currentBatchSessionId = batchId;
+ trackingInfoCache.clear();
+ }
+ HttpHeaders headers = new HttpHeaders();
+ headers.set("Authorization", currentAuthToken);
+ HttpEntity httpEntity = new HttpEntity<>(headers);
+
+ String shopIdsParam = "";
+ if (shopIds != null && !shopIds.isEmpty()) {
+ List validShopIds = shopIds.stream()
+ .filter(id -> id != null && !id.trim().isEmpty())
+ .collect(Collectors.toList());
+ if (!validShopIds.isEmpty()) {
+ shopIdsParam = "shopIds[]=" + String.join("&shopIds[]=", validShopIds) + "&";
+ }
+ }
+
+ String url = (StringUtils.isEmpty(startDate) || StringUtils.isEmpty(endDate))
+ ? String.format(API_URL, shopIdsParam, page, pageSize, System.currentTimeMillis())
+ : String.format(API_URL_WITH_TIME, shopIdsParam, page, pageSize, startDate, endDate, System.currentTimeMillis());
+ ResponseEntity response = restTemplate.exchange(url, HttpMethod.GET, httpEntity, Map.class);
+ if (response.getBody() == null || !Integer.valueOf(0).equals(response.getBody().get("code"))) {
+ Map errorResult = new HashMap<>();
+ errorResult.put("success", false);
+ errorResult.put("message", "获取订单数据失败,请点击'刷新认证'按钮重试");
+ return errorResult;
+ }
+
+ Map dataMap = (Map) response.getBody().get("data");
+ int total = ((Number) dataMap.getOrDefault("total", 0)).intValue();
+
+ List orders = Optional.ofNullable(dataMap.get("list"))
+ .map(list -> (List>) list)
+ .orElse(Collections.emptyList())
+ .stream()
+ .map(this::processOrderData)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toList());
+
+ if (!orders.isEmpty()) dataReportUtil.reportDataCollection("BANMA", orders.size(), "0");
+
+ Map result = new HashMap<>();
+ result.put("orders", orders);
+ result.put("total", total);
+ result.put("page", page);
+ result.put("pageSize", pageSize);
+ result.put("totalPages", (int) Math.ceil((double) total / pageSize));
+ result.put("success", true);
+ return result;
+ }
+
+
+ /**
+ * 处理订单数据
+ */
+ private Map processOrderData(Map order) {
+ String trackingNumber = (String) order.get("internationalTrackingNumber");
+ if (StringUtils.isNotEmpty(trackingNumber)) {
+ LocalDateTime cutoffTime = LocalDateTime.now().minusHours(1);
+ if (banmaOrderRepository.existsByTrackingNumberAndCreatedAtAfter(trackingNumber, cutoffTime)) {
+ return banmaOrderRepository.findLatestByTrackingNumber(trackingNumber)
+ .map(entity -> {
+ if (currentBatchSessionId != null && !currentBatchSessionId.equals(entity.getSessionId())) {
+ entity.setSessionId(currentBatchSessionId);
+ entity.setCreatedAt(LocalDateTime.now());
+ entity.setUpdatedAt(LocalDateTime.now());
+ banmaOrderRepository.save(entity);
+ }
+ try {
+ return objectMapper.readValue(entity.getOrderData(), Map.class);
+ } catch (Exception e) {
+ return new HashMap<>();
+ }
+ })
+ .orElse(null);
+ } else {
+ banmaOrderRepository.findByTrackingNumber(trackingNumber)
+ .ifPresent(banmaOrderRepository::delete);
+ }
+ }
+
+ // 构建新订单数据
+ Map result = new HashMap<>();
+ result.put("internationalTrackingNumber", trackingNumber);
+ result.put("internationalShippingFee", order.get("internationalShippingFee"));
+ result.put("trackInfo", trackingInfoCache.computeIfAbsent(trackingNumber, this::fetchTrackingInfo));
+
+ Optional.ofNullable(order.get("subOrders"))
+ .map(sub -> (List>) sub)
+ .filter(list -> !list.isEmpty())
+ .map(list -> list.get(0))
+ .ifPresent(subOrder -> extractSubOrderFields(result, subOrder));
+
+ BanmaOrderEntity entity = new BanmaOrderEntity();
+ String entityTrackingNumber = (String) result.get("internationalTrackingNumber");
+
+ if (StringUtils.isEmpty(entityTrackingNumber)) {
+ String shopOrderNumber = (String) result.get("shopOrderNumber");
+ String productTitle = (String) result.get("productTitle");
+ if (StringUtils.isNotEmpty(shopOrderNumber)) {
+ entityTrackingNumber = "ORDER_" + shopOrderNumber;
+ } else if (StringUtils.isNotEmpty(productTitle)) {
+ entityTrackingNumber = "PRODUCT_" + Math.abs(productTitle.hashCode());
+ } else {
+ entityTrackingNumber = "UNKNOWN_" + System.currentTimeMillis();
+ }
+ }
+
+ entity.setTrackingNumber(entityTrackingNumber);
+ try {
+ entity.setOrderData(objectMapper.writeValueAsString(result));
+ } catch (Exception e) {
+ entity.setOrderData("{}");
+ }
+
+ // 生成会话ID
+ String sessionId = currentBatchSessionId != null ? currentBatchSessionId :
+ Optional.ofNullable((String) result.get("orderedAt"))
+ .filter(orderedAt -> orderedAt.length() >= 10)
+ .map(orderedAt -> "SESSION_" + orderedAt.substring(0, 10))
+ .orElse("SESSION_" + java.time.LocalDate.now().toString());
+
+ entity.setSessionId(sessionId);
+ entity.setCreatedAt(LocalDateTime.now());
+ entity.setUpdatedAt(LocalDateTime.now());
+
+ try {
+ banmaOrderRepository.save(entity);
+ } catch (Exception e) {
+ logger.warn("保存订单数据失败,跳过: {}", entityTrackingNumber);
+ }
+
+ return result;
+ }
+
+ /**
+ * 提取子订单字段
+ */
+ private void extractSubOrderFields(Map simplifiedOrder, Map subOrder) {
+ String[] basicFields = {"orderedAt", "timeSinceOrder", "createdAt", "poTrackingNumber"};
+ String[] productFields = {"productTitle", "shopOrderNumber", "priceJpy", "productQuantity", "shippingFeeJpy", "productNumber", "serviceFee", "productImage"};
+ String[] purchaseFields = {"poNumber", "shippingFeeCny", "poLogisticsCompany"};
+ Arrays.stream(basicFields).forEach(field -> simplifiedOrder.put(field, subOrder.get(field)));
+ Arrays.stream(productFields).forEach(field -> simplifiedOrder.put(field, subOrder.get(field)));
+ Arrays.stream(purchaseFields).forEach(field -> simplifiedOrder.put(field, subOrder.get(field)));
+ }
+
+
+
+
+ /**
+ * 从API获取物流信息
+ */
+ private String fetchTrackingInfo(String trackingNumber) {
+ // 优先尝试佐川物流
+ Map trackInfoMap = (Map) new SagawaExpressSdk().getTrackingInfo(trackingNumber).get("trackInfo");
+ if (trackInfoMap != null) {
+ return trackInfoMap.get("dateTime") + " " + trackInfoMap.get("office") + " " + trackInfoMap.get("status");
+ }
+
+ // 斑马API
+ ResponseEntity response = restTemplate.getForEntity(String.format(TRACKING_URL, trackingNumber), Map.class);
+ return Optional.ofNullable(response.getBody())
+ .filter(body -> Integer.valueOf(0).equals(body.get("code")))
+ .map(body -> (List>) body.get("data"))
+ .filter(list -> !list.isEmpty())
+ .map(list -> list.get(0))
+ .map(track -> (String) track.get("track"))
+ .orElse("暂无物流信息");
+ }
+
+
+
+
+
+}
diff --git a/erp_client_sb/src/main/java/com/tashow/erp/service/impl/CacheServiceImpl.java b/erp_client_sb/src/main/java/com/tashow/erp/service/impl/CacheServiceImpl.java
new file mode 100644
index 0000000..5e176fe
--- /dev/null
+++ b/erp_client_sb/src/main/java/com/tashow/erp/service/impl/CacheServiceImpl.java
@@ -0,0 +1,34 @@
+package com.tashow.erp.service.impl;
+
+import com.tashow.erp.entity.AuthTokenEntity;
+import com.tashow.erp.repository.AuthTokenRepository;
+import com.tashow.erp.service.ICacheService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDateTime;
+import java.util.Optional;
+
+@Service
+public class CacheServiceImpl implements ICacheService {
+ private static final Logger logger = LoggerFactory.getLogger(CacheServiceImpl.class);
+
+ @Autowired
+ private AuthTokenRepository authTokenRepository;
+
+ @Override
+ public void saveAuthToken(String service, String token, long expireTimeMillis) {
+ try {
+ Optional existing = authTokenRepository.findByServiceName(service);
+ AuthTokenEntity entity = existing.orElse(new AuthTokenEntity());
+ entity.setServiceName(service);
+ entity.setToken(token);
+ entity.setExpireTime(LocalDateTime.now().plusSeconds(expireTimeMillis / 1000));
+ authTokenRepository.save(entity);
+ } catch (Exception e) {
+ logger.error("保存认证令牌失败: {}", service, e);
+ }
+ }
+}
\ No newline at end of file
diff --git a/erp_client_sb/src/main/java/com/tashow/erp/service/impl/GenmaiServiceImpl.java b/erp_client_sb/src/main/java/com/tashow/erp/service/impl/GenmaiServiceImpl.java
new file mode 100644
index 0000000..04389bd
--- /dev/null
+++ b/erp_client_sb/src/main/java/com/tashow/erp/service/impl/GenmaiServiceImpl.java
@@ -0,0 +1,95 @@
+package com.tashow.erp.service.impl;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.qiniu.util.UrlUtils;
+import io.github.bonigarcia.wdm.WebDriverManager;
+import lombok.SneakyThrows;
+import org.openqa.selenium.JavascriptExecutor;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.chrome.ChromeDriver;
+import org.openqa.selenium.chrome.ChromeOptions;
+import com.tashow.erp.service.IGenmaiService;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.*;
+import org.springframework.stereotype.Service;
+import org.springframework.web.client.RestTemplate;
+import java.util.HashMap;
+import java.util.Map;
+/**
+ * 跟卖精灵服务实现类
+ *
+ * @author ruoyi
+ */
+@Service
+public class GenmaiServiceImpl implements IGenmaiService {
+ @Value("${api.server.base-url}")
+ private String serverApiUrl;
+ @Value("${api.server.paths.getGenmaijlToken}")
+ private String genmaijlToken;
+ @Value("${api.server.paths.updateGenmaijlToken}")
+ private String updateGenmaijlToken;
+ private final RestTemplate restTemplate = new RestTemplate();
+ ObjectMapper objectMapper = new ObjectMapper();
+ @Override
+ @SneakyThrows
+ public void openGenmaiWebsite() {
+ // 先关闭所有Chrome进程
+ Runtime.getRuntime().exec("taskkill /f /im chrome.exe");
+ WebDriverManager.chromedriver().setup();
+ ChromeOptions options = new ChromeOptions();
+ String username = System.getProperty("user.name", "user");
+ String safeUsername;
+ char firstChar = username.charAt(0);
+ char flippedFirstChar = Character.isUpperCase(firstChar) ? Character.toLowerCase(firstChar) : Character.toUpperCase(firstChar);
+ safeUsername = flippedFirstChar + username.substring(1);
+ String chromeUserData = System.getProperty("user.home").replace(username, UrlUtils.urlEncode(safeUsername) )
+ + "\\AppData\\Local\\Google\\Chrome\\User Data";
+ options.addArguments("user-data-dir=" + chromeUserData.toLowerCase());
+ options.addArguments("profile-directory=Default");
+ WebDriver driver = new ChromeDriver(options);
+ driver.get("https://www.genmaijl.com/#/profile");
+ JavascriptExecutor js = (JavascriptExecutor) driver;
+ js.executeScript(String.format("localStorage.setItem('token','%s')", checkTokenExpired()));
+ driver.navigate().refresh();
+ }
+ /**
+ * 获取token验证token是否过期
+ */
+ public String checkTokenExpired() {
+ ResponseEntity exchange = restTemplate.exchange(serverApiUrl+genmaijlToken, HttpMethod.GET, null, String.class);
+ String token = exchange.getBody();
+ HttpHeaders headers = new HttpHeaders();
+ headers.set("Token", token);
+ HttpEntity entity = new HttpEntity<>(null, headers);
+ try {
+ ResponseEntity response = restTemplate.exchange("https://www.genmaijl.com/manage/user/balance", HttpMethod.GET, entity, String.class);
+ String body = response.getBody();
+ JsonNode root = objectMapper.readTree(body);
+ return root.get("code").asInt() == 0 ? token : login();
+ } catch (Exception e) {
+ return login();
+ }
+ }
+ /**
+ * 登录
+ */
+ @SneakyThrows
+ public String login() {
+ Map requestBody = new HashMap<>();
+ requestBody.put("nickname", "changzhu-4");
+ requestBody.put("password", "123456QWe@");
+ HttpEntity> entity = new HttpEntity<>(requestBody);
+ HttpHeaders headers = new HttpHeaders();
+ headers.setContentType(MediaType.TEXT_PLAIN);
+ ResponseEntity response = restTemplate.exchange("https://www.genmaijl.com/manage/user/subLogin", HttpMethod.POST, entity, String.class);
+ String body = response.getBody();
+ JsonNode root = objectMapper.readTree(body);
+ requestBody.clear();
+ String token= root.get("result").asText();
+ HttpEntity updateEntity = new HttpEntity<>(token,headers);
+ ResponseEntity exchange = restTemplate.exchange(serverApiUrl + updateGenmaijlToken, HttpMethod.POST, updateEntity, String.class);
+ System.out.println(exchange.getBody());
+ return token;
+ }
+
+}
\ No newline at end of file
diff --git a/erp_client_sb/src/main/java/com/tashow/erp/service/impl/RakutenCacheServiceImpl.java b/erp_client_sb/src/main/java/com/tashow/erp/service/impl/RakutenCacheServiceImpl.java
new file mode 100644
index 0000000..43c5960
--- /dev/null
+++ b/erp_client_sb/src/main/java/com/tashow/erp/service/impl/RakutenCacheServiceImpl.java
@@ -0,0 +1,153 @@
+package com.tashow.erp.service.impl;
+
+import com.alibaba.fastjson.JSON;
+import com.tashow.erp.entity.RakutenProductEntity;
+import com.tashow.erp.model.RakutenProduct;
+import com.tashow.erp.repository.RakutenProductRepository;
+import com.tashow.erp.service.IRakutenCacheService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.UUID;
+import java.util.stream.Collectors;
+
+/**
+ * 乐天产品缓存服务实现类
+ * 负责乐天商品数据的缓存存储、检索和会话管理
+ */
+@Slf4j
+@Service
+public class RakutenCacheServiceImpl implements IRakutenCacheService {
+
+ @Autowired
+ private RakutenProductRepository repository;
+
+ /**
+ * 保存产品列表并生成新的会话ID
+ */
+ @Override
+ @Transactional
+ public String saveProducts(List products) {
+ String sessionId = UUID.randomUUID().toString();
+ List entities = products.stream()
+ .map(product -> {
+ RakutenProductEntity entity = new RakutenProductEntity();
+ BeanUtils.copyProperties(product, entity);
+ entity.setSessionId(sessionId);
+ return entity;
+ })
+ .collect(Collectors.toList());
+ repository.saveAll(entities);
+ log.info("保存产品数据,sessionId: {},数量: {}", sessionId, products.size());
+ return sessionId;
+ }
+
+ /**
+ * 使用指定会话ID保存产品列表
+ */
+ @Override
+ @Transactional
+ public void saveProductsWithSessionId(List products, String sessionId) {
+ List entities = products.stream()
+ .map(product -> {
+ RakutenProductEntity entity = new RakutenProductEntity();
+ BeanUtils.copyProperties(product, entity);
+ entity.setSessionId(sessionId);
+ return entity;
+ })
+ .collect(Collectors.toList());
+
+ repository.saveAll(entities);
+ log.info("保存产品数据,sessionId: {},数量: {}", sessionId, products.size());
+ }
+
+ /**
+ * 检查店铺是否有1小时内的缓存数据
+ */
+ @Override
+ public boolean hasRecentData(String shopName) {
+ return repository.existsByOriginalShopNameAndCreatedAtAfter(shopName, LocalDateTime.now().minusHours(1));
+ }
+
+ /**
+ * 根据店铺名获取所有产品数据
+ */
+ @Override
+ public List getProductsByShopName(String shopName) {
+ return repository.findByOriginalShopNameOrderByCreatedAtAscIdAsc(shopName).stream()
+ .map(entity -> {
+ RakutenProduct product = new RakutenProduct();
+ BeanUtils.copyProperties(entity, product);
+ return product;
+ })
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * 更新指定店铺的所有产品的会话ID
+ */
+ @Override
+ @Transactional
+ public void updateProductsSessionId(String shopName, String newSessionId) {
+ List