diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 index 1739d49e..18ccf36e --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -/target/ +target/ !.mvn/wrapper/maven-wrapper.jar ### STS ### @@ -8,20 +8,18 @@ .project .settings .springBeans -.sts4-cache ### IntelliJ IDEA ### .idea *.iws *.iml *.ipr -*.png ### NetBeans ### -/nbproject/private/ -/nbbuild/ -/dist/ -/nbdist/ -/.nb-gradle/ -/build/ - +nbproject/private/ +build/ +nbbuild/ +dist/ +nbdist/ +.nb-gradle/ +/local-config/ diff --git a/Dockerfile b/Dockerfile old mode 100644 new mode 100755 index a3339e2b..15a4455b --- a/Dockerfile +++ b/Dockerfile @@ -4,10 +4,8 @@ VOLUME /sop # 将所有应用放到一个镜像当中 ADD sop-gateway/target/*.jar sop/sop-gateway/sop-gateway.jar -ADD sop-admin/sop-admin-server/target/*.jar sop/sop-admin/sop-admin.jar -ADD sop-website/sop-website-server/target/*.jar sop/sop-website/sop-website.jar -ADD sop-auth/target/*.jar sop/sop-auth/sop-auth.jar -ADD sop-example/sop-story/target/*.jar sop/sop-story/sop-story.jar +ADD sop-admin/sop-admin-backend/backend-boot/target/*.jar sop/sop-admin/sop-admin.jar +ADD sop-example/example-story/target/*.jar sop/sop-story/sop-story.jar # 拷贝启动脚本 COPY docker-entrypoint.sh /usr/local/bin/ diff --git a/LICENSE b/LICENSE old mode 100644 new mode 100755 diff --git a/README.md b/README.md old mode 100644 new mode 100755 index d1234866..b8dd7526 --- a/README.md +++ b/README.md @@ -1,144 +1,137 @@ # SOP(Simple Open Platform) - -一个开放平台解决方案项目,基于Spring Cloud实现,目标让用户快速搭建自己的开放平台。 +一个开放平台解决方案项目,基于dubbo实现,目标让用户快速搭建自己的开放平台。 通过简单的配置后,你的项目就具备了和支付宝开放平台的一样的接口提供能力。 SOP封装了开放平台大部分功能包括:签名验证、统一异常处理、统一返回内容 、业务参数验证(JSR-303)、秘钥管理等,未来还会实现更多功能。 -## 项目特点 -- 接入方式简单,与老项目不冲突,老项目注册到注册中心,然后在方法上加上注解即可。 -- 架构松耦合,业务代码实现在各自微服务上,SOP不参与业务实现,这也是Spring Cloud微服务体系带来的好处。 -- 扩展简单,开放平台对应的功能各自独立,可以自定义实现自己的需求,如:更改参数,更改签名规则等。 +## 项目特点 ++ 接入方式简单,与老项目不冲突,老项目注册到注册中心,然后在方法上加上注解即可。 ++ 架构松耦合,业务代码实现在各自微服务上,SOP不参与业务实现,这也是dubbo微服务体系带来的好处。 ++ 扩展简单,开放平台对应的功能各自独立,可以自定义实现自己的需求,如:更改参数,更改签名规则等。 ## 谁可以使用这个项目 - -- 有现成的项目,想改造成开放平台供他人调用 -- 有现成的项目,想暴露其中几个接口并通过开放平台供他人调用 -- 想搭一个开放平台新项目,并结合微服务的方式去维护 -- 对开放平台感兴趣的朋友 ++ 有现成的项目,想改造成开放平台供他人调用 ++ 有现成的项目,想暴露其中几个接口并通过开放平台供他人调用 ++ 想搭一个开放平台新项目,并结合微服务的方式去维护 ++ 对开放平台感兴趣的朋友 以上情况都可以考虑使用SOP ## 例子 +开放接口定义 ```java -// 加一个注解即可 -@Open("story.get") -@RequestMapping("/get") -public StoryResult get() { - StoryResult result = new StoryResult(); - result.setId(1L); - result.setName("海底小纵队"); - return result; +/** + * 支付接口 + * + * @author 六如 + */ +@Api("支付接口") +public interface OpenPayment { + + @ApiOperation( + value = "手机网站支付接口", + notes = "该接口是页面跳转接口,用于生成用户访问跳转链接。" + + "请在服务端执行SDK中pageExecute方法,读取响应中的body()结果。" + + "该结果用于跳转到页面,返回到用户浏览器渲染或重定向跳转到页面。" + + "具体使用方法请参考 接入指南" + ) + @Open("pay.trade.wap.pay") + PayTradeWapPayResponse tradeWapPay(PayTradeWapPayRequest request); + } ``` + + +接口实现 + +```java +/** + * 开放接口实现 + * + * @author 六如 + */ +@DubboService(validation = "true") +public class OpenPaymentImpl implements OpenPayment { + + @Override + public PayTradeWapPayResponse tradeWapPay(PayTradeWapPayRequest request) { + PayTradeWapPayResponse payTradeWapPayResponse = new PayTradeWapPayResponse(); + payTradeWapPayResponse.setPageRedirectionData(UUID.randomUUID().toString()); + return payTradeWapPayResponse; + } +} + +``` + 调用: ```java -// 公共请求参数 -Map params = new HashMap(); -params.put("app_id", appId); -params.put("method", "story.get"); -params.put("format", "json"); -params.put("charset", "utf-8"); -params.put("sign_type", "RSA2"); -params.put("timestamp", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())); -params.put("version", "1.0"); - -// 业务参数 -Map bizContent = new HashMap<>(); -bizContent.put("id", "1"); -bizContent.put("name", "葫芦娃"); - -params.put("biz_content", JSON.toJSONString(bizContent)); - -System.out.println("----------- 请求信息 -----------"); -System.out.println("请求参数:" + buildParamQuery(params)); -System.out.println("商户秘钥:" + privateKey); -String content = AlipaySignature.getSignContent(params); -System.out.println("待签名内容:" + content); -String sign = AlipaySignature.rsa256Sign(content, privateKey, "utf-8"); -System.out.println("签名(sign):" + sign); - -params.put("sign", sign); -System.out.println("URL参数:" + buildUrlQuery(params)); - -System.out.println("----------- 返回结果 -----------"); -String responseData = get(url, params);// 发送请求 -System.out.println(responseData); +@Test +public void testGet() throws Exception { + // 公共请求参数 + Map params = new HashMap(); + params.put("app_id", appId); + params.put("method", "pay.trade.wap.pay"); + params.put("format", "json"); + params.put("charset", "utf-8"); + params.put("sign_type", "RSA2"); + params.put("timestamp", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())); + params.put("version", "1.0"); + + // 业务参数 + Map bizContent = new HashMap<>(); + bizContent.put("outTradeNo", "70501111111S001111119"); + bizContent.put("totalAmount", "9.00"); + bizContent.put("subject", "衣服"); + bizContent.put("productCode", "QUICK_WAP_WAY"); + + params.put("biz_content", JSON.toJSONString(bizContent)); + String content = AlipaySignature.getSignContent(params); + String sign = AlipaySignature.rsa256Sign(content, privateKey, "utf-8"); + params.put("sign", sign); + + System.out.println("----------- 请求信息 -----------"); + System.out.println("请求参数:" + buildParamQuery(params)); + System.out.println("商户秘钥:" + privateKey); + System.out.println("待签名内容:" + content); + System.out.println("签名(sign):" + sign); + System.out.println("URL参数:" + buildUrlQuery(params)); + + System.out.println("----------- 返回结果 -----------"); + String responseData = postJson(url, params);// 发送请求 + System.out.println(responseData); +} ``` -## 架构图 +SDK调用 -![架构图](https://images.gitee.com/uploads/images/2019/1227/145216_c9b45109_332975.png "sop3.png") +```java +@Test +public void test() { + PayTradeWapPayRequest request = new PayTradeWapPayRequest(); + + PayTradeWapPayModel model = new PayTradeWapPayModel(); + model.setOutTradeNo("70501111111S001111119"); + model.setTotalAmount(new BigDecimal("1000")); + model.setSubject("衣服"); + model.setProductCode("QUICK_WAP_WAY"); + + request.setBizModel(model); -> 如上图所示,整个系统运行后,开发者只需关注微服务中的业务代码,接口变更后重新部署微服务应用即可 + Result result = client.execute(request); + if (result.isSuccess()) { + PayTradeWapPayResponse response = result.getData(); + System.out.println(response); + } else { + System.out.println(result); + } +} +``` +## 整体架构 +![整体架构](./asset/arc.jpg) -## 已完成列表 - -- 签名验证 -- 统一异常处理 -- 统一返回内容 -- session管理 -- 秘钥管理 -- 微服务端自动验证(JSR-303) -- Admin管理平台,统一管理微服务配置,管理路由管理,微服务上下线 -- 门户网站,提供用户注册账号 -- 接入方管理+秘钥管理 -- 接口权限分配 -- 文件上传/下载 -- 提供基础SDK(含:Java,C++,C#,Python,Go,Rust,Nodejs) -- 接口限流 -- 文档整合 -- 应用授权 -- 监控日志 -- 注册中心支持nacos/eureka -- 网关动态修改参数 -- 预发布/灰度环境切换 - -## 界面预览 - -![服务列表](https://images.gitee.com/uploads/images/2020/1016/134354_c1915902_332975.png "service.png") - -![路由管理](https://images.gitee.com/uploads/images/2020/1016/134039_bed1608d_332975.png "route.png") - -![限流管理](https://images.gitee.com/uploads/images/2020/1016/134102_f2dcfb25_332975.png "limit.png") - -![秘钥信息](https://images.gitee.com/uploads/images/2019/0711/174921_bd817533_332975.png "秘钥信息") - -- 门户网站 - -![首页2](https://images.gitee.com/uploads/images/2021/0318/195935_1d610da8_332975.png "portal-vue.png") - -![文档页](https://images.gitee.com/uploads/images/2020/1107/104342_d44849a9_332975.png "portal1.png") - -## 工程说明 - -> 运行环境:JDK8,Maven3,[Nacos](https://nacos.io/zh-cn/docs/what-is-nacos.html),Mysql - -- doc:开发文档 -- sop-common:公共模块,封装常用功能,包含签名校验、错误处理、限流等功能 -- sop-gateway:网关,统一访问入口,含`Spring Cloud Zuul`和`Spring Cloud Gateway`实现 -- sop-example:微服务示例,含springboot,springmvc示例 -- sop-website:开放平台对应网站,提供文档API、沙箱测试等内容 -- sop-auth:应用授权服务示例 -- sop-admin:后台管理 -- sop-sdk:基础sdk,含Java、C#版本 -- sop-test:接口调用测试用例 - -## 分支说明 - -- master:发版分支 -- develop:日常开发分支 -- eureka:使用eureka注册中心 -- pr:接受PR的分支,提交PR请提交到此分支 - -[更新说明](./changelog.md) - -## 相关文档 - -[开发文档](http://durcframework.gitee.io/sop) diff --git a/asset/arc.jpg b/asset/arc.jpg new file mode 100755 index 00000000..e27db1b0 Binary files /dev/null and b/asset/arc.jpg differ diff --git a/build-admin.sh b/build-admin.sh new file mode 100755 index 00000000..a11191ec --- /dev/null +++ b/build-admin.sh @@ -0,0 +1,49 @@ +# 构建admin, 结果输出在dist/sop-admin目录 + +# 获取当前路径并赋值给变量 current_path +current_path=$(pwd) + +# 打印变量的值,以验证赋值是否成功 +echo "当前路径是: $current_path" + +# 构建目录 +dist_dir="dist" +# 服务端文件夹名称 +# 执行文件名称 +app_name="sop-admin" +version="5.0" +build_folder="${app_name}-${version}" +# 输出目录 +target_dir="$dist_dir/${build_folder}" + +server_source=sop-admin/sop-admin-backend/admin-boot +# admin前端路径 +front_source=sop-admin/sop-admin-frontend + +# ------ 构建前端 ------ +echo "开始构建sop-admin前端..." +cd $front_source +sh build.sh +cd $current_path + + +# ------ 构建后端 ------ +echo "开始构建sop-admin服务端..." + +mvn clean package -pl $server_source -am -DskipTests + +# ------ 复制文件 ------ +if [ ! -d "$target_dir" ]; then + mkdir -p $target_dir +fi + +rm -rf ${target_dir}/* + +# 复制前端资源 +echo "复制前端文件到$target_dir" +cp -r ${front_source}/dist ./$target_dir + +# 复制服务端资源 +cp -r ${server_source}/target/*.jar $target_dir + +echo "服务端构建完毕,构建结果在${target_dir}文件夹下" diff --git a/build-gateway.sh b/build-gateway.sh new file mode 100644 index 00000000..0aed5357 --- /dev/null +++ b/build-gateway.sh @@ -0,0 +1,37 @@ +# 构建网关 + + +# 获取当前路径并赋值给变量 current_path +current_path=$(pwd) + +# 打印变量的值,以验证赋值是否成功 +echo "当前路径是: $current_path" + +# 构建目录 +dist_dir="dist" +# 服务端文件夹名称 +# 执行文件名称 +app_name="sop-gateway" +version="5.0" +build_folder="${app_name}-${version}" +# 输出目录 +target_dir="$dist_dir/${build_folder}" + +server_source=sop-gateway + +# ------ 构建后端 ------ +echo "开始构建sop-gateway..." + +mvn clean package -pl $server_source -am -DskipTests + +# ------ 复制文件 ------ +if [ ! -d "$target_dir" ]; then + mkdir -p $target_dir +fi + +rm -rf ${target_dir}/* + +# 复制服务端资源 +cp -r ${server_source}/target/*.jar $target_dir + +echo "服务端构建完毕,构建结果在${target_dir}文件夹下" diff --git a/build-website.sh b/build-website.sh new file mode 100755 index 00000000..bd69b2c5 --- /dev/null +++ b/build-website.sh @@ -0,0 +1,49 @@ +# 构建admin, 结果输出在dist/sop-admin目录 + +# 获取当前路径并赋值给变量 current_path +current_path=$(pwd) + +# 打印变量的值,以验证赋值是否成功 +echo "当前路径是: $current_path" + +# 构建目录 +dist_dir="dist" +# 服务端文件夹名称 +# 执行文件名称 +app_name="sop-website" +version="5.0" +build_folder="${app_name}-${version}" +# 输出目录 +target_dir="$dist_dir/${build_folder}" + +server_source=sop-website/sop-website-backend/website-boot +# admin前端路径 +front_source=sop-website/sop-website-frontend + +# ------ 构建前端 ------ +echo "开始构建sop-website前端..." +cd $front_source +sh build.sh +cd $current_path + + +# ------ 构建后端 ------ +echo "开始构建sop-website服务端..." + +mvn clean package -pl $server_source -am -DskipTests + +# ------ 复制文件 ------ +if [ ! -d "$target_dir" ]; then + mkdir -p $target_dir +fi + +rm -rf ${target_dir}/* + +# 复制前端资源 +echo "复制前端文件到$target_dir" +cp -r ${front_source}/dist ./$target_dir + +# 复制服务端资源 +cp -r ${server_source}/target/*.jar $target_dir + +echo "服务端构建完毕,构建结果在${target_dir}文件夹下" diff --git a/changelog.md b/changelog.md old mode 100644 new mode 100755 index 13b0c39d..b60e47d4 --- a/changelog.md +++ b/changelog.md @@ -1,419 +1,5 @@ # changelog -## 4.4.2 +## 5.0 -- 优化参数绑定 - -## 4.4.1 - -- 修复单值参数绑定问题 - -## 4.4.0 - -**【重要】:升级前请阅读 [升级到4.4.0注意事项](./升级到4.4.0注意事项.md)** - -- 优化异常处理 -- 优化网关多实例数据库重复保存问题 -- 修复`IP`,`IP+路由ID`,`IP+APP_ID`限流不生效问题 - -## 4.3.4 - -- 修复Request参数在第一位导致绑定失败问题 - -## 4.3.3 - -- 修复获取eureka地址问题 - -## 4.3.2 - -- 修复微服务方法获取不到OpenContext问题 - -## 4.3.1 - -- 修复serviceId有大小写出现404问题 -- 修复路有监控错误面板分页问题 - -## 4.3.0 - -- 升级`spring-boot/spring-cloud/spring-cloud-alibaba`版本 -- 修复`DataBufferLimitException: Exceeded limit on max bytes to buffer : 262144`问题 -- 修复压测出现`ClosedChannelException`,`Connection has been closed BEFORE response`问题 - -## 4.2.7 - -- 修复两个微服务相同path问题 -- 修复Python-SDK下参数传递问题 - -## 4.2.6 - -- 优化网关超时处理 - -## 4.2.5 - -- 修复restful负载均衡问题 - -## 4.2.4 - -- 修复sop-auth模块启动失败问题 - -## 4.2.3 - -- 修复nacos的group不生效问题 - -## 4.2.2 - -- 优化版本号 - -## 4.2.1 - -- 可指定nacos的group,(`spring.cloud.nacos.discovery.group=xx`) -- 修复spring循环依赖问题 - -## 4.2.0 - -需要执行`sop-upgrade-4.2.0.sql` - -- 新增ISV用户平台 -- 新增门户网站(portal) -- 新增`C++`,`Rust`语言SDK - -## 4.1.0 - -需要执行`sop-upgrade-4.1.0.sql` - -- 重构路由监控功能 -- 升级SpringBoot,SpringCloud,SpringCloudAlibaba版本 - -## 4.0.3 - -- 可定义业务错误码(见`@Open`注解中的`bizCode`属性) -- 文档参数可指定最大长度(使用`@Length(max = xx)`) -- 修复返回大文本导致的错误(Exceeded limit on max bytes to buffer : 262144) -- 增强参数绑定 - -## 4.0.2 - -- 支持swagger排序(position属性) - -## 4.0.1 - -- 修复超大文本无法请求BUG(设置`spring.codec.max-in-memory-size`无效) - -## 4.0.0(不兼容3.x) - -- 新增@Open注解,代替ApiMapping和ApiAbility -- service接入减少代码入侵 -- 修改admin密码存储规则 -- 完善example - -### 不兼容部分 - -- 移除ApiMapping和ApiAbility,改为Open注解 -- 移除OpenContext -- 移除zuul -- 移除对easyopen支持 - -## 3.2.1 - -- 强化RouteInterceptorContext,可获取微服务信息 - -## 3.2.0 - -- 使用alibaba cloud -- 新增Python,Go版本SDK -- 返回结果新增全局request_id -- 沙箱环境可填写token - -Hoxton.SR3(Spring Cloud Version), 2.2.1.RELEASE(Spring Cloud Alibaba Version), 2.2.5.RELEASE(Spring Boot Version) - -- 优化pom文件 -- 优化灰度发布 - -## 3.1.5 - -修复文件上传大小不一致问题 - -## 3.1.4 - -- 优化跨域 - -## 3.1.3 - -- 修复监控日志乱码问题 - -## 3.1.2 - -- 修复图片上传保存后图片破损问题 - -## 3.1.1 - -- 修复继承WebMvcConfigurationSupport导致的jackson序列化时间问题 -- 修复微服务接口返回void网关不会返回code和msg问题 - -## 3.1.0 - -- 新增路由监控功能 -- 新增路由拦截器 -- 优化负载均衡策略 - -## 3.0.1 - -- 增强国际化消息(现SpringCouldGateway支持英文国际化) -- 优化限流配置页 - -## 3.0.0 - -- 重构spring cloud gateway网关 -- 重构`预发布/灰度发布环境选择` -- zuul和gateway网关二合一,可随意切换 -- 精简配置文件 -- 优化文档中心页面 -- 优化接口限流 - -## 2.5.10 - -- 优化签名认证,优化校验日志打印 - -## 2.5.9 - -- 优化获取context-path -- 修复结果返回产生的NPE问题 - -## 2.5.8 - -- 优化参数绑定 - -## 2.5.7 - -- 优化restful接口调用 - -## 2.5.6 - -- 优化文档显示 -- 修复路由拉取接口重复BUG - -## 2.5.5 - -- 优化文档显示 - -## 2.5.4(不建议使用) - -- 可排除其它服务(`sop.service.exclude=your-serviceId1,your-serviceId2`) - -## 2.5.3(不建议使用) - -- 修复JSR-303校验问题 - -## 2.5.2(不建议使用) - -- 修复JSR-303校验枚举对象问题 - -## 2.5.1 - -- JSR-303支持嵌套校验 - -## 2.5.0 - -**(需要执行`sop-2.5.0.sql`升级文件)** - -- 网关可校验token [doc](https://durcframework.gitee.io/sop/#/files/10089_%E8%87%AA%E5%AE%9A%E4%B9%89%E6%A0%A1%E9%AA%8Ctoken?t=1572076365259) - -## 2.4.1 - -- 优化restful接口调用(如果正在使用此功能,必看 [doc](https://durcframework.gitee.io/sop/#/files/10100_%E6%8F%90%E4%BE%9Brestful%E6%8E%A5%E5%8F%A3?t=1571107529449)) - -## 2.4.0 - -- 支持自定义限流持续时间(每n秒允许m个请求,需要执行`sop-2.4.0.sql`升级脚本) - -## 2.3.2 - -- 支持spring cloud gateway下restful接口调用 - -## 2.3.1 - -- 修复restful接口调用通配符问题 - -## 2.3.0 - -- 支持请求restful接口(设置`sop.restful.enable=true`) - -## 2.2.0(需要执行`sop-2.2.0.sql`升级文件) - -- 支持eureka注册中心,见`eureka`分支 -- 签名内容支持urlencode(设置`sign.urlencode=true`) -- 可扩展其它注册中心 - -## 2.1.3 - -- 优化文件上传校验 - -## 2.1.2 - -- 优化获取路由配置 - -## 2.1.1 - -- 修复springmvc获取路由问题 - -## 2.1.0 - -- 支持分布式限流(redis实现) -- 可调整JSR-303校验顺序 -- 修复springmvc工程注册到nacos无法读取路由配置问题 - -## 2.0.0 - -- 全面使用nacos,舍弃zookeeper(1.x版本见1.x分支) -- 可自定义文档模块显示顺序 - -## 1.15.2 - -- 优化SpringCloudGateway上传文件功能 -- 优化SpringCloudGateway动态修改参数功能 - -## 1.15.1 - -- 修复未配置正确MessageConverter导致的异常 - -## 1.15.0 - -- 优化预发布、灰度 -- 网关动态修改请求参数 -- 支持swagger-bootstrap插件 -- 优化admin服务列表显示 -- 优化文档刷新逻辑 -- 新增测试all in one -- 修复中文乱码问题 - -## 1.14.0 - -- 支持预发布、灰度发布环境 - -## 1.13.7 - -- 修复修复context-path识别问题 - -## 1.13.6 - -- 修复@RequestBody不能绑定问题 - -## 1.13.5 - -- 修复postJson下version获取不到问题 - -## 1.13.4 - -- 修复admin服务列表最后更新时间不显示问题 -- 优化上传路由配置逻辑 -- 微服务可获得access_token, notify_url参数 - -## 1.13.3 - -- 优化参数绑定 - -## 1.13.2 - -- 修复json方式请求获取不到参数问题 -- 微服务端新增获取开放平台请求参数 - -## 1.13.1 - -- 支持json方式请求(application/json) -- 支持传统web服务开发(见文档`传统web开发`) - -## 1.13.0 - -- 新增IP黑名单 - -## 1.12.4 - -- 优化属性文件配置 -- 新增sleuth接入文档 -- admin的isv列表新增备注字段 - -## 1.12.3 - -- 修复删除zk节点导致的BUG - -## 1.12.2 - -- 沙盒支持文件上传 - -## 1.12.1 - -- 修复重启网关路由状态重置BUG -- 优化SpringCloudGateway - -## 1.12.0 - -- admin后台新增角色管理 -- 支持nacos作为注册中心 - -## 1.11.0 - -- 秘钥管理改造 -- 服务端返回sign -- 新增SDK返回sign处理 -- 新增沙箱环境 - -## 1.10.0 - -- 新增监控日志 - -## 1.9.0 - -- 改造限流 -- 增强参数绑定 - -## 1.8.0 - -- 支持文件上传 - -## 1.7.2 - -- 修复微服务参数绑定BUG -- Admin新增vue界面 - -## 1.7.1 - -- 支持接口名版本号放在url后面 - -## 1.7.0 - -- 可自定义数据节点名称 - -## 1.6.0 - -- 新增应用授权 - -## 1.5.0 - -- admin新增signType字段 -- 修复easyopen接入无法访问BUG - -## 1.4.0 - -- 新增文档分组显示 -- 支持easyopen文档注解 -- BUG修复 - -## 1.3.0 - -- 新增接口限流功能 [doc](http://durcframework.gitee.io/sop/#/files/10092_%E6%8E%A5%E5%8F%A3%E9%99%90%E6%B5%81?t=1555378655699) -- 新增文档整合功能 [doc](http://durcframework.gitee.io/sop/#/files/10041_%E7%BC%96%E5%86%99%E6%96%87%E6%A1%A3?t=1555378655698) -- 新增springmvc项目接入demo - -## 1.2.0 - -- SOP Admin新增用户登录 -- 新增基础SDK(Java,C#) [doc](http://durcframework.gitee.io/sop/#/files/10095_SDK%E5%BC%80%E5%8F%91?t=1554693919597) - -## 1.1.0 - -- 新增ISV管理 [doc](http://durcframework.gitee.io/sop/#/files/10085_ISV%E7%AE%A1%E7%90%86?t=1554123435621) -- 新增接口授权 [doc](http://durcframework.gitee.io/sop/#/files/10090_%E8%B7%AF%E7%94%B1%E6%8E%88%E6%9D%83?t=1554123435621) - -## 1.0.0 - -- 第一次发布 +全面重构,欢迎体验:[文档](https://www.yuque.com/u1604442/sop) diff --git a/checkstyle.xml b/checkstyle.xml new file mode 100755 index 00000000..3ddecb5e --- /dev/null +++ b/checkstyle.xml @@ -0,0 +1,176 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/.gitignore b/doc/.gitignore deleted file mode 100644 index 1739d49e..00000000 --- a/doc/.gitignore +++ /dev/null @@ -1,27 +0,0 @@ -/target/ -!.mvn/wrapper/maven-wrapper.jar - -### STS ### -.apt_generated -.classpath -.factorypath -.project -.settings -.springBeans -.sts4-cache - -### IntelliJ IDEA ### -.idea -*.iws -*.iml -*.ipr -*.png - -### NetBeans ### -/nbproject/private/ -/nbbuild/ -/dist/ -/nbdist/ -/.nb-gradle/ -/build/ - diff --git a/doc/README.md b/doc/README.md deleted file mode 100644 index e6955bf6..00000000 --- a/doc/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# 开发文档 - -文档放在docs/files下,写完文档记得执行下`SidebarTest.main()`方法 - -配合gitee pages服务使用,gitee pages服务指定docs目录。 - -## 本地查看开发文档 - -- 前提:先安装好npm,[npm安装教程](https://blog.csdn.net/zhangwenwu2/article/details/52778521)。建议使用淘宝镜像。 -- 安装docsify,执行npm命令`npm i docsify-cli -g --registry=https://registry.npm.taobao.org` -- cd到当前目录,运行命令`docsify serve docs`,然后访问:`http://localhost:3000`即可查看。 diff --git a/doc/docs/README.md b/doc/docs/README.md deleted file mode 100644 index 386a1291..00000000 --- a/doc/docs/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# SOP开发文档 - -Git地址:[SOP](https://gitee.com/durcframework/SOP) diff --git a/doc/docs/_config.yml b/doc/docs/_config.yml deleted file mode 100644 index aa7fd0af..00000000 --- a/doc/docs/_config.yml +++ /dev/null @@ -1 +0,0 @@ -include: [_navbar,_sidebar] \ No newline at end of file diff --git a/doc/docs/_coverpage.md b/doc/docs/_coverpage.md deleted file mode 100644 index 55969331..00000000 --- a/doc/docs/_coverpage.md +++ /dev/null @@ -1,12 +0,0 @@ -![logo](_media/icon.svg) - -# docsify 4.6.10 - -> A magical documentation site generator. - -* Simple and lightweight (~19kB gzipped) -* No statically built html files -* Multiple themes - -[GitHub](https://github.com/QingWei-Li/docsify/) -[Get Started](#docsify) diff --git a/doc/docs/_navbar.md b/doc/docs/_navbar.md deleted file mode 100644 index deff3845..00000000 --- a/doc/docs/_navbar.md +++ /dev/null @@ -1,3 +0,0 @@ -- 关于 - - [帮助](/zh-cn/) - - [API](/) diff --git a/doc/docs/_sidebar.md b/doc/docs/_sidebar.md deleted file mode 100644 index 4ab6e4e6..00000000 --- a/doc/docs/_sidebar.md +++ /dev/null @@ -1,36 +0,0 @@ -* [首页](/?t=1616211903021) -* 开发文档 - * [快速体验](files/10010_快速体验.md?t=1616211903027) - * [项目接入到SOP](files/10011_项目接入到SOP.md?t=1616211903048) - * [新增接口](files/10020_新增接口.md?t=1616211903048) - * [开发流程](files/10021_开发流程.md?t=1616211903048) - * [用户注册](files/10022_用户注册.md?t=1616211903049) - * [业务参数校验](files/10030_业务参数校验.md?t=1616211903049) - * [错误处理](files/10040_错误处理.md?t=1616211903049) - * [编写文档](files/10041_编写文档.md?t=1616211903049) - * [接口交互详解](files/10050_接口交互详解.md?t=1616211903049) - * [使用签名校验工具](files/10080_使用签名校验工具.md?t=1616211903049) - * [ISV管理](files/10085_ISV管理.md?t=1616211903050) - * [自定义返回结果](files/10087_自定义返回结果.md?t=1616211903050) - * [自定义过滤器](files/10088_自定义过滤器.md?t=1616211903050) - * [自定义校验token](files/10089_自定义校验token.md?t=1616211903050) - * [网关拦截器](files/10090_网关拦截器.md?t=1616211903050) - * [路由授权](files/10091_路由授权.md?t=1616211903050) - * [接口限流](files/10092_接口限流.md?t=1616211903051) - * [路由监控](files/10093_路由监控.md?t=1616211903051) - * [SDK开发](files/10095_SDK开发.md?t=1616211903051) - * [应用授权](files/10097_应用授权.md?t=1616211903051) - * [提供restful接口](files/10100_提供restful接口.md?t=1616211903051) - * [文件上传](files/10104_文件上传.md?t=1616211903051) - * [配置Sleuth链路追踪](files/10109_配置Sleuth链路追踪.md?t=1616211903052) - * [预发布灰度发布](files/10110_预发布灰度发布.md?t=1616211903052) - * [动态修改请求参数](files/10111_动态修改请求参数.md?t=1616211903052) - * [使用eureka](files/10112_使用eureka.md?t=1616211903052) - * [超时设置](files/10113_超时设置.md?t=1616211903052) -* 原理分析 - * [网关性能测试](files/90001_网关性能测试.md?t=1616211903052) - * [原理分析之如何存储路由](files/90011_原理分析之如何存储路由.md?t=1616211903052) - * [原理分析之如何路由](files/90012_原理分析之如何路由.md?t=1616211903052) - * [原理分析之文档归纳](files/90013_原理分析之文档归纳.md?t=1616211903052) - * [原理分析之预发布灰度发布](files/90014_原理分析之预发布灰度发布.md?t=1616211903052) - * [常见问题](files/90100_常见问题.md?t=1616211903053) diff --git a/doc/docs/files/10010_快速体验.md b/doc/docs/files/10010_快速体验.md deleted file mode 100644 index edcce36c..00000000 --- a/doc/docs/files/10010_快速体验.md +++ /dev/null @@ -1,52 +0,0 @@ -# 快速体验 - -## 方式1 - -> 运行环境:JDK8,Maven3,[Nacos](https://nacos.io/zh-cn/docs/what-is-nacos.html),Mysql - -- 安装并启动Nacos,[安装教程](https://nacos.io/zh-cn/docs/quick-start.html) -- 执行Mysql脚本`sop.sql`(Mysql版本5.6+),5.6以下运行`sop-mysql5.6以下版本.sql` -- IDE安装lombok插件,然后打开项目(IDEA下可以打开根pom.xml,然后open as project) -- 启动网关:打开sop-gateway下的`application-dev.properties` - 1. 修改数据库`username/password` - 2. 指定nacos地址,如果nacos安装在本机则不用改 - 3. 运行`SopGatewayApplication.java` -- 启动微服务:打开`sop-example/sop-story`下的`application-dev.properties`文件 - 1. 指定nacos地址,如果nacos安装在本机则不用改 - 2. 运行`SopStoryApplication.java` -- 找到sop-test,运行`com.gitee.sop.test.AlipayClientPostTest.testGet`进行接口调用测试 - -## 方式2(docker) - -> 前提:安装好docker - -- 安装并启动Nacos,[安装教程](https://nacos.io/zh-cn/docs/quick-start.html) -- 执行Mysql脚本`sop.sql`(Mysql版本5.6+),5.6以下运行`sop-mysql5.6以下版本.sql` -- 打开`docker-entrypoint.sh`,修改mysql,nacos配置 -- 执行`docker-build.sh` -- 找到sop-test,运行`com.gitee.sop.test.AlipayClientPostTest.testGet`进行接口调用测试 - - -> - admin地址:http://ip:8082 登录账号:admin/123456 -> - 文档地址:http://ip:8083/ - -## 使用admin - -- 找到`sop-admin/sop-admin-server`工程,打开sop-admin-server下的`application-dev.properties`,修改相关配置 -- 运行`SopAdminServerApplication.java` -- 访问:`http://localhost:8082` - -登录账号:admin/123456 - -## 启动门户网站 - -见:`sop-website/sop-portal/README.md` - -## 启动文档中心/ISV后台 - -文档中心代码在sop-website工程中 - -- 确保注册中心、网关、微服务正常启动 -- 修改sop-website-server下的application-dev.properties相关配置 -- 运行WebsiteServerApplication.java -- 访问http://localhost:8083 diff --git a/doc/docs/files/10011_项目接入到SOP.md b/doc/docs/files/10011_项目接入到SOP.md deleted file mode 100644 index cc8826e0..00000000 --- a/doc/docs/files/10011_项目接入到SOP.md +++ /dev/null @@ -1,217 +0,0 @@ -# 项目接入到SOP - -以springboot项目为例,完整项目可参考sop-example下的sop-story - -- pom.xml添加版本配置 - -```xml - -2.6.15 - -2021.0.5 - - -2021.0.5.0 -``` - -- pom.xml添加``控制版本 - -```xml - - - - org.springframework.boot - spring-boot-dependencies - ${spring-boot.version} - pom - import - - - org.springframework.cloud - spring-cloud-dependencies - ${spring-cloud.version} - pom - import - - - com.alibaba.cloud - spring-cloud-alibaba-dependencies - ${spring-cloud-alibaba.version} - pom - import - - - -``` - -- pom.xml依赖sop-service-common和nacos服务发现 - -```xml - - - com.gitee.sop - sop-service-common - 最新版本 - - - - com.alibaba.cloud - spring-cloud-starter-alibaba-nacos-discovery - -``` - -- application.properties配置文件添加 - -```properties -server.port=2222 -spring.application.name=story-service -# nacos注册中心 -spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848 -``` - -- 在springboot启动类上添加`@EnableDiscoveryClient` -- 新增一个配置类,继承`AlipayServiceConfiguration.java`,内容为空 - -```java -@Configuration -public class OpenServiceConfig extends AlipayServiceConfiguration { - -} -``` - -- 全局异常处理 - -在微服务项目的全局异常处理中添加一句:`ExceptionHolder.hold(request, response, exception);` - -```java - @ExceptionHandler(Exception.class) - @ResponseBody - public Object exceptionHandler(HttpServletRequest request, HttpServletResponse response, Exception exception) { - ... - // 在返回前加这一句 - ExceptionHolder.hold(request, response, exception); - ... - return ..; - } -``` - -如果没有配置全局异常,可参考下面配置 - -```java -@ControllerAdvice -@Slf4j -public class StoryGlobalExceptionHandler { - - - /** - * 捕获手动抛出的异常 - * - * @param request request - * @param response response - * @param exception 异常信息 - * @return 返回提示信息 - */ - @ExceptionHandler(Exception.class) - @ResponseBody - public Object exceptionHandler(HttpServletRequest request, HttpServletResponse response, Exception exception) { - // 在返回前加这一句 - ExceptionHolder.hold(request, response, exception); - // 下面可以实现自己的全局异常处理 - return new ErrorResult(500, exception.getMessage()); - } - - @Data - @AllArgsConstructor - public static class ErrorResult { - private int code; - private String msg; - } -} -``` - -到此准备工作就完成了,接下来可前往`新增接口`查看如何新增接口。 - -## 非Java项目接入 - -如果是非Java项目接入,如php,需要做到以下几点: - -> 1. 提供路由配置接口 -> 2. 服务注册到nacos,并在nacos的metadata中指定接口路径,metadata的key为:`sop.routes.path` - - -- 提供路由配置接口 - -php应用提供的接口需要返回如下json内容: - -假设请求的接口为:`http://open.xxx.com/get_routes` - -```json -{ - "serviceId": "goods-service", - "createTime": 1568603471646, - "updateTime": 1568603471646, - "description": null, - "routeDefinitionList": [ - { - "id": "goods.list1.0", - "name": "goods.list", - "version": "1.0", - "uri": "lb://goods-service", - "path": "/goods/list_goods", - "order": 0, - "ignoreValidate": 0, - "status": 1, - "mergeResult": 1, - "permission": 0 - }, - ... - ] -} -``` - -json参数说明 - -|参数名|是否必填|说明| -|:----|:----|:----| -|serviceId |是|serviceId,服务id | -|createTime |是|创建时间,Unix timestamp,毫秒 | -|updateTime |是|修改时间,Unix timestamp,毫秒 | -|description|否|描述| -|routeDefinitionList元素参数说明|是|路由配置,见:routeDefinitionList元素参数说明| - -routeDefinitionList元素参数说明 - -|参数名|是否必填|说明| -|:----|:----|:----| -|id |是|路由id,全局唯一,格式:接口名+版本号 | -|name|是|接口名称| -|version|是|版本号| -|uri|是|格式:lb:// + serviceId,如:lb://goods-service| -|path|是|接口path,填端口号后面的path,如你的接口为`http://open.domain.com:8080/goods/list_goods`,填:`/goods/list_goods`| -|order|是|固定填:0| -|ignoreValidate|是|忽略签名验证,1:是,0:否| -|status|是|启用状态,1:启用,2:禁用| -|mergeResult|是|是否统一返回结果,1:是,0:否| -|permission|是|是否需要权限访问,1:是,0:否| - -- 服务注册到nacos - -可前往nacos官网,参考[open-api](https://nacos.io/zh-cn/docs/open-api.html),使用nacos提供的接口完成服务注册 - - -- 在nacos的metadata中指定接口路径 - -伪代码如下: - -```java -Instance instance = new Instance(); -instance.setServiceName("goods-service"); -instance.setIp("192.168.0.11"); -instance.setPort(8080); -// 在nacos的metadata中指定接口路径 -instance.getMetadata().put("sop.routes.path", "http://open.xxx.com/get_routes"); - -namingService.registerInstance(serviceId, instance); -``` - -完成以上步骤后,php服务注册到nacos,网关会触发监听事件,获取新注册的服务,然后会向你的服务拉取路由配置。 \ No newline at end of file diff --git a/doc/docs/files/10020_新增接口.md b/doc/docs/files/10020_新增接口.md deleted file mode 100644 index bd31cc62..00000000 --- a/doc/docs/files/10020_新增接口.md +++ /dev/null @@ -1,147 +0,0 @@ -# 新增接口 - -假设要对下面这个接口提供开放能力。 - -```java -@RestController -public class StoryDemoController { - - @RequestMapping("/story/get") - public StoryResult getStory() { - StoryResult result = new StoryResult(); - result.setId(1L); - result.setName("海底小纵队"); - return result; - } -} -``` - -只需要在方法上新增一个`@Open`注解,指定接口名即可 - -```java -// 添加一个@Open注解 -@Open("story.demo.get") -@RequestMapping("/story/get") -public StoryResult getStory() { - StoryResult result = new StoryResult(); - result.setId(1L); - result.setName("海底小纵队"); - return result; -} -``` - -如果要加上版本号,指定`version`参数:`@Open(value = "story.demo.get", version = "2.0")` - -- 重启服务,这样接口就可以使用了。 - -## 绑定业务参数 - -网关校验通过后,请求参数会传递到微服务上来,完整的参数如下所示: - -``` -请求参数:charset=utf-8&biz_content={"goods_remark":"iphone6"}&method=goods.add&format=json&app_id=2019032617262200001&sign_type=RSA2&version=1.0×tamp=2019-04-29 19:18:38 -``` - -其中biz_content部分是我们想要的,在方法上申明一个对象,对应biz_content中的内容即可完成参数绑定,并且对参数进行JSR-303校验。 - -**注意:接口方法必须有且只有一个对象参数,如果申明多个会出现参数绑定失败** - -```java -@Open("goods.add") -@RequestMapping("/goods/add") -public Object addGoods(GoodsParam param) { - return param; -} - -@Data -public class GoodsParam { - @NotEmpty(message = "不能为空") // 支持JSR-303校验 - private String goods_remark; -} -``` - -一般情况下,只需要获取业务参数即可,如果想要获取更多的参数,可在后面跟一个`HttpServletRequest`对象。 - -```java -@Open("goods.add") -@RequestMapping("/goods/add") -public Object addGoods(GoodsParam param, HttpServletRequest request) { - System.out.println(request.getParameter("method")); - return param; -} -``` - - -## 接口命名 - -接口命名没有做强制要求,但我们还是推荐按照下面的方式进行命名: - -接口名的命名规则为:`服务模块.业务模块.功能模块.行为`,如: - -- mini.user.userinfo.get 小程序服务.用户模块.用户信息.获取 -- member.register.total.get 会员服务.注册模块.注册总数.获取 - -如果觉得命名规则有点长可以精简为:`服务模块.功能模块.行为`,如`member.usercount.get`,前提是确保前缀要有所区分,不和其它服务冲突。 - -## 测试接口 - -- 在sop-test工程下新建一个测试用例,`StoryDemoTest`,继承TestBase - -```java -public class StoryDemoTest extends TestBase { - - String url = "http://localhost:8081"; - String appId = "2019032617262200001"; - // 私钥 - String privateKey = "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCXJv1pQFqWNA/++OYEV7WYXwexZK/J8LY1OWlP9X0T6wHFOvxNKRvMkJ5544SbgsJpVcvRDPrcxmhPbi/sAhdO4x2PiPKIz9Yni2OtYCCeaiE056B+e1O2jXoLeXbfi9fPivJZkxH/tb4xfLkH3bA8ZAQnQsoXA0SguykMRZntF0TndUfvDrLqwhlR8r5iRdZLB6F8o8qXH6UPDfNEnf/K8wX5T4EB1b8x8QJ7Ua4GcIUqeUxGHdQpzNbJdaQvoi06lgccmL+PHzminkFYON7alj1CjDN833j7QMHdPtS9l7B67fOU/p2LAAkPMtoVBfxQt9aFj7B8rEhGCz02iJIBAgMBAAECggEARqOuIpY0v6WtJBfmR3lGIOOokLrhfJrGTLF8CiZMQha+SRJ7/wOLPlsH9SbjPlopyViTXCuYwbzn2tdABigkBHYXxpDV6CJZjzmRZ+FY3S/0POlTFElGojYUJ3CooWiVfyUMhdg5vSuOq0oCny53woFrf32zPHYGiKdvU5Djku1onbDU0Lw8w+5tguuEZ76kZ/lUcccGy5978FFmYpzY/65RHCpvLiLqYyWTtaNT1aQ/9pw4jX9HO9NfdJ9gYFK8r/2f36ZE4hxluAfeOXQfRC/WhPmiw/ReUhxPznG/WgKaa/OaRtAx3inbQ+JuCND7uuKeRe4osP2jLPHPP6AUwQKBgQDUNu3BkLoKaimjGOjCTAwtp71g1oo+k5/uEInAo7lyEwpV0EuUMwLA/HCqUgR4K9pyYV+Oyb8d6f0+Hz0BMD92I2pqlXrD7xV2WzDvyXM3s63NvorRooKcyfd9i6ccMjAyTR2qfLkxv0hlbBbsPHz4BbU63xhTJp3Ghi0/ey/1HQKBgQC2VsgqC6ykfSidZUNLmQZe3J0p/Qf9VLkfrQ+xaHapOs6AzDU2H2osuysqXTLJHsGfrwVaTs00ER2z8ljTJPBUtNtOLrwNRlvgdnzyVAKHfOgDBGwJgiwpeE9voB1oAV/mXqSaUWNnuwlOIhvQEBwekqNyWvhLqC7nCAIhj3yvNQKBgQCqYbeec56LAhWP903Zwcj9VvG7sESqXUhIkUqoOkuIBTWFFIm54QLTA1tJxDQGb98heoCIWf5x/A3xNI98RsqNBX5JON6qNWjb7/dobitti3t99v/ptDp9u8JTMC7penoryLKK0Ty3bkan95Kn9SC42YxaSghzqkt+uvfVQgiNGQKBgGxU6P2aDAt6VNwWosHSe+d2WWXt8IZBhO9d6dn0f7ORvcjmCqNKTNGgrkewMZEuVcliueJquR47IROdY8qmwqcBAN7Vg2K7r7CPlTKAWTRYMJxCT1Hi5gwJb+CZF3+IeYqsJk2NF2s0w5WJTE70k1BSvQsfIzAIDz2yE1oPHvwVAoGAA6e+xQkVH4fMEph55RJIZ5goI4Y76BSvt2N5OKZKd4HtaV+eIhM3SDsVYRLIm9ZquJHMiZQGyUGnsvrKL6AAVNK7eQZCRDk9KQz+0GKOGqku0nOZjUbAu6A2/vtXAaAuFSFx1rUQVVjFulLexkXR3KcztL1Qu2k5pB6Si0K/uwQ="; - - - @Test - public void testDemo() throws Exception { - // 公共请求参数 - Map params = new HashMap(); - params.put("app_id", appId); - // 这里对应@Open.value属性 - params.put("method", "story.demo.get"); - params.put("format", "json"); - params.put("charset", "utf-8"); - params.put("sign_type", "RSA2"); - params.put("timestamp", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())); - // 这里对应@Open.version属性 - params.put("version", "1.0"); - - // 业务参数 - Map bizContent = new HashMap<>(); - - params.put("biz_content", JSON.toJSONString(bizContent)); - - System.out.println("----------- 请求信息 -----------"); - System.out.println("请求参数:" + buildParamQuery(params)); - System.out.println("商户秘钥:" + privateKey); - String content = AlipaySignature.getSignContent(params); - System.out.println("待签名内容:" + content); - String sign = AlipaySignature.rsa256Sign(content, privateKey, "utf-8"); - System.out.println("签名(sign):" + sign); - - params.put("sign", sign); - - System.out.println("----------- 返回结果 -----------"); - String responseData = post(url, params);// 发送请求 - System.out.println(responseData); - } - -} -``` - -- 请求成功后,控制台会打印: - -``` ------------ 请求信息 ----------- -请求参数:charset=utf-8&biz_content={}&method=story.demo.get&format=json&app_id=alipay_test&sign_type=RSA2&version=1.0×tamp=2019-03-23 15:41:22 -商户秘钥:MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCXJv1pQFqWNA/++OYEV7WYXwexZK/J8LY1OWlP9X0T6wHFOvxNKRvMkJ5544SbgsJpVcvRDPrcxmhPbi/sAhdO4x2PiPKIz9Yni2OtYCCeaiE056B+e1O2jXoLeXbfi9fPivJZkxH/tb4xfLkH3bA8ZAQnQsoXA0SguykMRZntF0TndUfvDrLqwhlR8r5iRdZLB6F8o8qXH6UPDfNEnf/K8wX5T4EB1b8x8QJ7Ua4GcIUqeUxGHdQpzNbJdaQvoi06lgccmL+PHzminkFYON7alj1CjDN833j7QMHdPtS9l7B67fOU/p2LAAkPMtoVBfxQt9aFj7B8rEhGCz02iJIBAgMBAAECggEARqOuIpY0v6WtJBfmR3lGIOOokLrhfJrGTLF8CiZMQha+SRJ7/wOLPlsH9SbjPlopyViTXCuYwbzn2tdABigkBHYXxpDV6CJZjzmRZ+FY3S/0POlTFElGojYUJ3CooWiVfyUMhdg5vSuOq0oCny53woFrf32zPHYGiKdvU5Djku1onbDU0Lw8w+5tguuEZ76kZ/lUcccGy5978FFmYpzY/65RHCpvLiLqYyWTtaNT1aQ/9pw4jX9HO9NfdJ9gYFK8r/2f36ZE4hxluAfeOXQfRC/WhPmiw/ReUhxPznG/WgKaa/OaRtAx3inbQ+JuCND7uuKeRe4osP2jLPHPP6AUwQKBgQDUNu3BkLoKaimjGOjCTAwtp71g1oo+k5/uEInAo7lyEwpV0EuUMwLA/HCqUgR4K9pyYV+Oyb8d6f0+Hz0BMD92I2pqlXrD7xV2WzDvyXM3s63NvorRooKcyfd9i6ccMjAyTR2qfLkxv0hlbBbsPHz4BbU63xhTJp3Ghi0/ey/1HQKBgQC2VsgqC6ykfSidZUNLmQZe3J0p/Qf9VLkfrQ+xaHapOs6AzDU2H2osuysqXTLJHsGfrwVaTs00ER2z8ljTJPBUtNtOLrwNRlvgdnzyVAKHfOgDBGwJgiwpeE9voB1oAV/mXqSaUWNnuwlOIhvQEBwekqNyWvhLqC7nCAIhj3yvNQKBgQCqYbeec56LAhWP903Zwcj9VvG7sESqXUhIkUqoOkuIBTWFFIm54QLTA1tJxDQGb98heoCIWf5x/A3xNI98RsqNBX5JON6qNWjb7/dobitti3t99v/ptDp9u8JTMC7penoryLKK0Ty3bkan95Kn9SC42YxaSghzqkt+uvfVQgiNGQKBgGxU6P2aDAt6VNwWosHSe+d2WWXt8IZBhO9d6dn0f7ORvcjmCqNKTNGgrkewMZEuVcliueJquR47IROdY8qmwqcBAN7Vg2K7r7CPlTKAWTRYMJxCT1Hi5gwJb+CZF3+IeYqsJk2NF2s0w5WJTE70k1BSvQsfIzAIDz2yE1oPHvwVAoGAA6e+xQkVH4fMEph55RJIZ5goI4Y76BSvt2N5OKZKd4HtaV+eIhM3SDsVYRLIm9ZquJHMiZQGyUGnsvrKL6AAVNK7eQZCRDk9KQz+0GKOGqku0nOZjUbAu6A2/vtXAaAuFSFx1rUQVVjFulLexkXR3KcztL1Qu2k5pB6Si0K/uwQ= -待签名内容:app_id=alipay_test&biz_content={}&charset=utf-8&format=json&method=story.demo.get&sign_type=RSA2×tamp=2019-03-23 15:41:22&version=1.0 -签名(sign):YMbxTPdovi6htcn1K3USTS6/Tbg6MOAMigG6x/kG0kQFCYH8ljvxXzcY86UT056nUG3OXxnj0xkw07eV6E03HMlu7bn3/jrT3PCcV3YguhA92aWz720x2xJWdfXY13OUPS9VOCC9zIVxu6EBD+PoZ7ojYChYvOfCR5I8bR/oOc0ZLjK63PWTBdf0eFS4sybXzRf81uNLMROsMhmBDDy0Fhml3ml77qzWBIpsmq5ECZ+89rMPbkNhAUcnFAe7ik7xZIL6WcUhAOhKVa8ZQK1GMjoGnAbGRed1FbuOHZGubgffg4/vMqrY10Bcy6h9jt/zK5w9L3HVgK3aPgQlfP16Gg== ------------ 返回结果 ----------- -{"story_demo_get_response":{"msg":"Success","code":"10000","name":"白雪公主","id":1},"sign":"YMbxTPdovi6htcn1K3USTS6/Tbg6MOAMigG6x/kG0kQFCYH8ljvxXzcY86UT056nUG3OXxnj0xkw07eV6E03HMlu7bn3/jrT3PCcV3YguhA92aWz720x2xJWdfXY13OUPS9VOCC9zIVxu6EBD+PoZ7ojYChYvOfCR5I8bR/oOc0ZLjK63PWTBdf0eFS4sybXzRf81uNLMROsMhmBDDy0Fhml3ml77qzWBIpsmq5ECZ+89rMPbkNhAUcnFAe7ik7xZIL6WcUhAOhKVa8ZQK1GMjoGnAbGRed1FbuOHZGubgffg4/vMqrY10Bcy6h9jt/zK5w9L3HVgK3aPgQlfP16Gg=="} -``` - diff --git a/doc/docs/files/10021_开发流程.md b/doc/docs/files/10021_开发流程.md deleted file mode 100644 index 439f8e34..00000000 --- a/doc/docs/files/10021_开发流程.md +++ /dev/null @@ -1,20 +0,0 @@ -# 开发流程 - -如果您打算使用SOP做开放平台开发,流程大致如下: - -- cd到/SOP/sop-common目录,执行命令`mvn clean deploy`,把jar上传到maven私服,如果没有maven私服,可以打包到本地`mvn clean install` -- 打包`sop-gateway`(网关)、`sop-admin`(后台管理)、`sop-website`(文档),部署到服务器上 - -以上服务是固定的,启动一次即可,后续不用做改动。 - -- 你的项目接入到SOP,参考[项目接入到SOP](10011_项目接入到SOP.md),在微服务端开发接口,编写swagger注解文档 -- 接口开发完成,启动微服务,注册到注册中心。 -- 【可选】编写sdk,在`SDK管理`页发布SDK,提供下载地址 - -## ISV对接流程 - -假设接口开发完毕,开始对接ISV,大致步骤如下: - -1. ISV访问门户网站,注册账号并登陆 -2. 上传应用公钥 -3. 如果有SDK,下载SDK,进行接口调用 \ No newline at end of file diff --git a/doc/docs/files/10022_用户注册.md b/doc/docs/files/10022_用户注册.md deleted file mode 100644 index c94f2fc2..00000000 --- a/doc/docs/files/10022_用户注册.md +++ /dev/null @@ -1,11 +0,0 @@ -# 用户(ISV)注册 - -新增ISV有两种方式 - -- 方式1 - -启动sop-admin,在admin后台`ISV管理添加` - -- 方式2 - -启动`sop-website-server`,用户访问自主注册 diff --git a/doc/docs/files/10030_业务参数校验.md b/doc/docs/files/10030_业务参数校验.md deleted file mode 100644 index 267f70fc..00000000 --- a/doc/docs/files/10030_业务参数校验.md +++ /dev/null @@ -1,88 +0,0 @@ -# 业务参数校验 - -业务参数校验采用JSR-303方式,关于JSR-303介绍可以参考这篇博文:[JSR 303 - Bean Validation 介绍及最佳实践](https://www.ibm.com/developerworks/cn/java/j-lo-jsr303/) - -在参数中使用注解即可,框架会自动进行验证。如下面一个添加商品接口,它的参数是GoodsParam - -```java -@Open("goods.add") -@RequestMapping("/goods/add") -public void addGoods(GoodsParam param) { - ... -} -``` -在GoodsParam中添加JSR-303注解: - -```java -@Data -public class GoodsParam { - @NotEmpty(message = "商品名称不能为空") - private String goods_name; -} -``` - -如果不传商品名称则返回 - -``` -{"goods_add_response":{"msg":"Success","code":"10000","sub_msg":"商品名称不能为空","sub_code":"isv.invalid-parameter"},"sign":"Eh3Z5CxDCHsb4MyYFVxsPSmBpwVi1LISJdOkrzglxXoqG7RVyEOt4ef1kNpznUvMI3FDQU1suR7Rsmx6NjGdEVS6NSH2Kt0d8TFBRpLhWz8hApnxOtgzqMqbYeMuJie7X5gF6m8hTnvuuxF21IrkixMe+lyBcXw7dk0C3w1SwdEZkHQ+xC+M4bLqAZt5/3kl79/FWSMFJWHiZmg5YeEi8e8XhYCNcz+xlJRJL0x2Y87fFxqSY0UYWNxbQHgdVI8xRfn1n31nzkcLxiAtTh4LPtNRrG7w7absK/C1Oi/vczuBlFeq2EWUsYVWOVpKiJifUwvYVUUsztSLElzplzOjbg=="} - -``` - -- 校验顺序 - -如果存在多个注解,可以指定groups来控制校验顺序,如下代码所示: - -```java -@NotBlank(message = "NotBlank", groups = Group1.class) -// 优先校验Group2 -// 可交换下面Group2,Group3,看下校验顺序 -@Length(min = 2, max = 20, message = "length must 10~20", groups = Group2.class) -@Pattern(regexp = "[a-zA-Z]*", message = "name must letters", groups = Group3.class) -private String name; -``` - -优先校验`@Length`,通过后再校验`@Pattern` - -## 参数校验国际化 - -国际化的配置方式如下: - -```java -@NotEmpty(message = "{goods.remark.notNull}") -private String goods_remark; -``` - -国际化资源文件`bizerror_en.properties`中添加: -``` -goods.remark.notNull=The goods_remark can not be null -``` - -bizerror_zh_CN.properties中添加: - -``` -# 商品备注不能为空 -goods.remark.notNull=\u5546\u54c1\u5907\u6ce8\u4e0d\u80fd\u4e3a\u7a7a -``` - -## 参数校验国际化传参 - -下面校验商品评论的长度,要求大于等于3且小于等于20。数字3和20要填充到国际化资源中去。 - -``` -// 传参的格式:{xxx}=value1,value2... -@Length(min = 3, max = 20, message = "{goods.comment.length}=3,20") -private String goods_comment; -``` - -bizerror_en.properties: -``` -goods.comment.length=The goods_comment length must >= {0} and <= {1} -``` - -bizerror_zh_CN.properties中添加: - -``` -# 商品评论长度必须在{0}和{1}之间 -goods.comment.length=\u5546\u54c1\u8bc4\u8bba\u957f\u5ea6\u5fc5\u987b\u5728{0}\u548c{1}\u4e4b\u95f4 -``` -这样value1,value2会分别填充到{0},{1}中 diff --git a/doc/docs/files/10040_错误处理.md b/doc/docs/files/10040_错误处理.md deleted file mode 100644 index e2c3df63..00000000 --- a/doc/docs/files/10040_错误处理.md +++ /dev/null @@ -1,91 +0,0 @@ -# 错误处理 - -SOP对错误处理已经封装好了,简单做法是`throw ServiceException`,在最顶层的Controller会做统一处理。例如: - -```java -if(StringUtils.isEmpty(param.getGoods_name())) { - throw new ServiceException("goods_name不能为null"); -} -``` - -为了保证编码风格的一致性,推荐统一使用ServiceException - -## i18n国际化 - -SOP支持国际化消息。通过Request对象中的getLocale()来决定具体返回那种语言,客户端通过设置Accept-Language头部来决定返回哪种语言,中文是zh,英文是en。 - -SOP通过模块化来管理国际化消息,这样做的好处结构清晰,维护方便。下面就来讲解如何设置国际化消息。 - -以story服务为例,假设我们要对商品模块进行设置,步骤如下: - -- 在`resource/i18n/isp`目录下新建goods_error_zh_CN.properties属性文件 - -属性文件的文件名有规律, **i18n/isp/goods_error** 表示模块路径, **_zh_CN.properties** 表示中文错误消息。如果要使用英文错误,则新建一个`goods_error_en.properties`即可。 - -- 在goods_error_zh_CN.properties中配置错误信息 - -``` -# 商品名字不能为空 -isp.goods_error_100=\u5546\u54C1\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A -``` - -isp.goods_error_为固定前缀,100为错误码,这两个值后续会用到。 - -接下来是把属性文件加载到国际化容器当中。 - -- 添加国际化配置,在OpenServiceConfig中的static块中添加,代码如下: - -```java -@Configuration -public class OpenServiceConfig extends AlipayServiceConfiguration { - - static { - ServiceConfig.getInstance().getI18nModules().add("i18n/isp/goods_error"); - } -} -``` - -- 新建一个枚举用来定义错误 - -```java -// 按模块来定义异常消息,团队开发可以分开进行 -public enum GoodsErrorEnum { - /** 参数错误 */ - NO_GOODS_NAME("100"), - ; - private ServiceErrorMeta errorMeta; - - StoryErrorEnum(String subCode) { - this.errorMeta = new ServiceErrorMeta("isp.goods_error_", subCode); - } - - public ServiceErrorMeta getErrorMeta() { - return errorMeta; - } -} - -``` - -接下来就可以使用了 - -```java -if (StringUtils.isEmpty(param.getGoods_name())) { - throw GoodsErrorEnum.NO_GOODS_NAME.getErrorMeta().getException(); -} -``` - -### 国际化消息传参 - -即代码中变量传入到properties文件中去,做法是采用{0},{1}占位符。0代表第一个参数,1表示第二个参数。 - -``` -# 商品名称太短,不能小于{0}个字 -isp.goods_error_101=\u5546\u54C1\u540D\u79F0\u592A\u77ED\uFF0C\u4E0D\u80FD\u5C0F\u4E8E{0}\u4E2A\u5B57 -``` - -```java -if (param.getGoods_name().length() <= 3) { - throw GoodsErrorEnum.LESS_GOODS_NAME_LEN.getErrorMeta().getException(3); -} -``` -直接放进getException(Object... params)方法参数中,因为是可变参数,可随意放。 \ No newline at end of file diff --git a/doc/docs/files/10041_编写文档.md b/doc/docs/files/10041_编写文档.md deleted file mode 100644 index cc389727..00000000 --- a/doc/docs/files/10041_编写文档.md +++ /dev/null @@ -1,126 +0,0 @@ -# 编写文档 - -作为开放平台,必须要提供API文档。 - -SOP采用微服务架构实现,因此文档应该由各个微服务各自实现。难点就是如何统一归纳各个微服务端提供的文档信息,并且统一展示。 - -写完接口后使用swagger注解来定义自己的文档信息。步骤如下: - -- maven添加swagger - -```xml - - - io.springfox - springfox-swagger2 - 2.9.2 - - - com.github.xiaoymin - swagger-bootstrap-ui - 1.9.5 - - -``` - -- 在config中添加swagger配置 - -```java -@Configuration -public class OpenServiceConfig extends AlipayServiceConfiguration { - /** - * 开启文档,本地微服务文档地址:http://localhost:2222/doc.html - * http://ip:port/v2/api-docs - */ - @Configuration - @EnableSwagger2 - public static class Swagger2 extends SwaggerSupport { - @Override - protected String getDocTitle() { - return "故事API"; - } - } -} -``` - -其中`getDocTitle()`返回文档模块名,不能和其它微服务重复。比如订单服务返回:`订单API`;库存服务返回:`库存API` - -- 编写swagger注解 - -分别在请求参数和返回结果类中编写`@ApiModelProperty` - -```java -// 请求参数 -@Data -public class StoryParam { - @ApiModelProperty(value = "故事ID", example = "111") - private int id; - - @ApiModelProperty(value = "故事名称", required = true, example = "白雪公主") - private String name; -} - -// 返回结果 -@Data -public class StoryResult { - @ApiModelProperty(value = "故事ID", example = "1") - private Long id; - - @ApiModelProperty(value = "故事名称", example = "海底小纵队") - private String name; - - @ApiModelProperty(value = "创建时间", example = "2019-04-14 19:02:12") - private Date gmt_create; -} -``` - -- 在接口方法上编写`@ApiOperation`注解 - -```java -/** - * 参数绑定 - * - * @param story 对应biz_content中的内容,并自动JSR-303校验 - * @return - */ -@ApiOperation(value = "获取故事信息", notes = "说明接口的详细信息,介绍,用途,注意事项等。") -@Open(value = "alipay.story.find", bizCode = { - // 定义业务错误码,用于文档显示 - @BizCode(code = "100001", msg = "姓名错误", solution = "填写正确的姓名"), - @BizCode(code = "100002", msg = "备注错误", solution = "填写正确备注"), - }) -public StoryResult getStory2(StoryParam story) { - log.info("获取故事信息参数, story: {}", story); - // 获取其它参数 - OpenContext openContext = ServiceContext.getCurrentContext().getOpenContext(); - String app_id = openContext.getAppId(); - StoryResult result = new StoryResult(); - result.setName("白雪公主, app_id:" + app_id); - result.setGmt_create(new Date()); - return result; -} -``` - -其中`value`属性填接口名称,简明扼要。`notes`填写接口的详细信息,介绍,用途,注意事项等。 - -## 查看文档 - -- 确保注册中心、网关、微服务正常启动 -- 运行WebsiteServerApplication.java -- 访问http://localhost:8083 - -效果图如下 - -![预览](images/10041_1.png "10041_1.png") - -## 注解对应关系 - -swagger注解和文档界面显示关系如下图所示: - -![预览](images/10041_2.png "10041_2.png") - - -![预览](images/10041_3.png "10041_3.png") - - -![预览](images/10041_4.png "10041_4.png") \ No newline at end of file diff --git a/doc/docs/files/10050_接口交互详解.md b/doc/docs/files/10050_接口交互详解.md deleted file mode 100644 index c83a04f5..00000000 --- a/doc/docs/files/10050_接口交互详解.md +++ /dev/null @@ -1,61 +0,0 @@ -# 接口交互详解 - -开放平台所提供的接口有几十个到几百个不等,同样支持的服务也是多个的。就拿[支付宝开放平台](https://docs.open.alipay.com/api)来说 -它所提供的服务有,支付服务、会员服务、店铺服务、芝麻信用服务等。相信这些服务接口肯定不是写在同一个项目中,但是它的接口地址只有一个:https://openapi.alipay.com/gateway.do -从地址信息中可以看到,这是一个网关服务。也就是说,网关是所有请求的入口,然后通过请求分发的方式,把请求路由到具体某个服务中去。 -虽然支付宝开放平台的实现方式我们不得而知,但是这种思路是可行的。 - -SOP也是采用这种方式实现,大致步骤如下: - -- 每个服务注册到nacos -- 网关启动时同样注册到nacos,然后从各服务中拉取路由信息 -- 网关收到客户端请求后,先进行签名校验,通过之后根据接口信息找到对应的服务,然后进行路由 -- 网关对返回结果进行处理(或不处理),返回给客户端。 - -如何通过接口参数找到对应的服务呢? - -在网关定义一个`Map routeMap = ...`,key为接口名+版本号。 - -网关启动时,从各微服务中获取路由信息,并保存到routeMap中 - -```java -routeMap = requestFormServices(); -``` - -接口请求进来后,根据`方法名+版本号`获取路由信息,然后进行路由转发。 - -```java -String method = request.getParameter("method"); -String version = request.getParameter("version"); - -RouteInfo routeInfo = routeMap.get(method + version); - -doRoute(routeInfo); -``` - -因为nacos需要拉取各个微服务的路由信息,接口名有可能会冲突,因此需要确保接口名唯一,即`method`全局唯一。 - -我们推荐接口名的命名规则应该是:`服务模块.业务模块.功能模块.行为`,如: - -mini.user.userinfo.get 小程序服务.用户模块.用户信息.获取 - -member.register.total.get 会员服务.注册模块.注册总数.获取 - -如果觉得命名规则有点长可以精简为:`服务模块.功能模块.行为`,如`member.usercount.get`,前提是确保前缀要有所区分,不和其它服务冲突。 - -得益于Spring Cloud的注册中心和和网关功能,我们能很方便的进行接口路由,并且还能实现LoadBalance,不需要自己再去实现。 - -整个SOP的架构如下图所示: - -![架构图](https://images.gitee.com/uploads/images/2019/1227/145216_c9b45109_332975.png "sop2.png") - -- 完整请求路线 - -``` -客户端生成签名串 → 客户端发送请求 →【网关签名校验 → 权限校验 → 限流处理 → 路由转发】→ {微服务端业务参数校验 → 处理业务逻辑 → 微服务端返回结果} - ↓ -客户端业务处理 ← 客户端验证服务端签名 ← 客户端收到结果 ← -------------【网关返回最终结果 ← 生成服务端签名 ← 网关处理结果】← 结果返回到网关 - -【】:表示网关处理 -{}:表示微服务端处理 -``` \ No newline at end of file diff --git a/doc/docs/files/10080_使用签名校验工具.md b/doc/docs/files/10080_使用签名校验工具.md deleted file mode 100644 index 26c13dea..00000000 --- a/doc/docs/files/10080_使用签名校验工具.md +++ /dev/null @@ -1,41 +0,0 @@ -# 使用签名校验工具 - -## 生成公私钥 - -SOP默认签名算法仿照的是支付宝开放平台,因此我们可以使用支付宝开放平台提供的密钥生成工具,[下载地址](https://docs.open.alipay.com/291/105971/) - -工具下载完后,运行工具 - -- 秘钥格式选择:PKCS8(JAVA适用) -- 秘钥长度:2048 - -然后点击`生成秘钥`,下面文本框会生成,公私钥,如下图所示: - -![示例图](https://gw.alipayobjects.com/zos/skylark/6dbc42cc-6b9b-4691-83f1-e7b875e1a602/2018/png/e6b725d0-8257-4a71-b7a0-f5479c9d43d0.png) - -sop-admin创建一个新ISV,将公私钥放入对应文本框中,保存。 - -接着私钥放入客户端进行调用。参见AlipayClientPostTest类 - -## 签名校验 - -验证工具切换到`签名`tab页 - -例如执行com.gitee.sop.AlipayClientPostTest.testPost()方法,控制台会打印如下信息: - -``` ------------ 请求信息 ----------- -请求参数:charset=utf-8&biz_content={"name":"葫芦娃","id":"1"}&method=alipay.story.get&format=json&app_id=2019032617262200001&sign_type=RSA2&version=1.0×tamp=2019-03-26 17:37:41 -商户秘钥:MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCXJv1pQFqWNA/++OYEV7WYXwexZK/J8LY1OWlP9X0T6wHFOvxNKRvMkJ5544SbgsJpVcvRDPrcxmhPbi/sAhdO4x2PiPKIz9Yni2OtYCCeaiE056B+e1O2jXoLeXbfi9fPivJZkxH/tb4xfLkH3bA8ZAQnQsoXA0SguykMRZntF0TndUfvDrLqwhlR8r5iRdZLB6F8o8qXH6UPDfNEnf/K8wX5T4EB1b8x8QJ7Ua4GcIUqeUxGHdQpzNbJdaQvoi06lgccmL+PHzminkFYON7alj1CjDN833j7QMHdPtS9l7B67fOU/p2LAAkPMtoVBfxQt9aFj7B8rEhGCz02iJIBAgMBAAECggEARqOuIpY0v6WtJBfmR3lGIOOokLrhfJrGTLF8CiZMQha+SRJ7/wOLPlsH9SbjPlopyViTXCuYwbzn2tdABigkBHYXxpDV6CJZjzmRZ+FY3S/0POlTFElGojYUJ3CooWiVfyUMhdg5vSuOq0oCny53woFrf32zPHYGiKdvU5Djku1onbDU0Lw8w+5tguuEZ76kZ/lUcccGy5978FFmYpzY/65RHCpvLiLqYyWTtaNT1aQ/9pw4jX9HO9NfdJ9gYFK8r/2f36ZE4hxluAfeOXQfRC/WhPmiw/ReUhxPznG/WgKaa/OaRtAx3inbQ+JuCND7uuKeRe4osP2jLPHPP6AUwQKBgQDUNu3BkLoKaimjGOjCTAwtp71g1oo+k5/uEInAo7lyEwpV0EuUMwLA/HCqUgR4K9pyYV+Oyb8d6f0+Hz0BMD92I2pqlXrD7xV2WzDvyXM3s63NvorRooKcyfd9i6ccMjAyTR2qfLkxv0hlbBbsPHz4BbU63xhTJp3Ghi0/ey/1HQKBgQC2VsgqC6ykfSidZUNLmQZe3J0p/Qf9VLkfrQ+xaHapOs6AzDU2H2osuysqXTLJHsGfrwVaTs00ER2z8ljTJPBUtNtOLrwNRlvgdnzyVAKHfOgDBGwJgiwpeE9voB1oAV/mXqSaUWNnuwlOIhvQEBwekqNyWvhLqC7nCAIhj3yvNQKBgQCqYbeec56LAhWP903Zwcj9VvG7sESqXUhIkUqoOkuIBTWFFIm54QLTA1tJxDQGb98heoCIWf5x/A3xNI98RsqNBX5JON6qNWjb7/dobitti3t99v/ptDp9u8JTMC7penoryLKK0Ty3bkan95Kn9SC42YxaSghzqkt+uvfVQgiNGQKBgGxU6P2aDAt6VNwWosHSe+d2WWXt8IZBhO9d6dn0f7ORvcjmCqNKTNGgrkewMZEuVcliueJquR47IROdY8qmwqcBAN7Vg2K7r7CPlTKAWTRYMJxCT1Hi5gwJb+CZF3+IeYqsJk2NF2s0w5WJTE70k1BSvQsfIzAIDz2yE1oPHvwVAoGAA6e+xQkVH4fMEph55RJIZ5goI4Y76BSvt2N5OKZKd4HtaV+eIhM3SDsVYRLIm9ZquJHMiZQGyUGnsvrKL6AAVNK7eQZCRDk9KQz+0GKOGqku0nOZjUbAu6A2/vtXAaAuFSFx1rUQVVjFulLexkXR3KcztL1Qu2k5pB6Si0K/uwQ= -待签名内容:app_id=2019032617262200001&biz_content={"name":"葫芦娃","id":"1"}&charset=utf-8&format=json&method=alipay.story.get&sign_type=RSA2×tamp=2019-03-26 17:37:41&version=1.0 -签名(sign):JCZMSFkXSjw/4TokyM9/9shyrMl7KxQGIZDHIm7+Bvl49Z816/iF/xXLYjUiPXWAXYfp+HlEs3VVQp1Kjh4tIKuKX/i1+exNVs+ICcqVGBewPSZwiWHGpZTfEUiYOoPyUL/eoRIj7Mvlaow0sI9uP7NXNo0kxEFjUOMCzZA7eKm/pu2FHRXt4OhgXq2Go30K5a9oCbbMc/2xcQCc2+zwvOgV3o0A6eMyeAXDJW+eQ2KLhtlqPQvbRV+xyfSut7TkwYSEuNXVVQAfN2lwAS3ru9CQIs8Uz7lK1ITkLu80yLapZVL7tS1PdxK0e3QYToCWD43Wtuoow4ZdDwwzir90HQ== ------------ 返回结果 ----------- -{"alipay_story_get_response":{"msg":"Success","code":"10000","name":"海底小纵队(alipay.story.get)","id":1},"sign":"JCZMSFkXSjw/4TokyM9/9shyrMl7KxQGIZDHIm7+Bvl49Z816/iF/xXLYjUiPXWAXYfp+HlEs3VVQp1Kjh4tIKuKX/i1+exNVs+ICcqVGBewPSZwiWHGpZTfEUiYOoPyUL/eoRIj7Mvlaow0sI9uP7NXNo0kxEFjUOMCzZA7eKm/pu2FHRXt4OhgXq2Go30K5a9oCbbMc/2xcQCc2+zwvOgV3o0A6eMyeAXDJW+eQ2KLhtlqPQvbRV+xyfSut7TkwYSEuNXVVQAfN2lwAS3ru9CQIs8Uz7lK1ITkLu80yLapZVL7tS1PdxK0e3QYToCWD43Wtuoow4ZdDwwzir90HQ=="} -``` - -字符集选UTF-8,签名方式RSA2 - -把控制台中的`请求参数`和`商户秘钥`填入文本框中,然后点击`开始签名`,下方会出现待签名内容和sign。 - -通过比对判断签名过程是否正确。 - diff --git a/doc/docs/files/10085_ISV管理.md b/doc/docs/files/10085_ISV管理.md deleted file mode 100644 index e6334502..00000000 --- a/doc/docs/files/10085_ISV管理.md +++ /dev/null @@ -1,24 +0,0 @@ -# ISV管理 - -ISV:独立软体开发商(independent software vendor),即接入方或者说接口调用者,在SOP中称为ISV。 - ---- - -在1.1.0版本中新增了ISV管理功能,在sop-admin中ISV管理模块下。功能如下: - -- 基本信息的增查改 -- 设置对应角色 - -界面如下图所示: - -![admin预览](images/10085_1.png "10085_1.png") - -## 秘钥管理 - -点击操作列的`秘钥管理`,可对ISV的秘钥进行设置。 - -- 如果采用淘宝开放平台签名方式,签名方式选择`MD5`,如果采用支付宝开放平台签名方式,选择`RSA` -- 如果对接的开发者使用非Java语言,秘钥格式选择`PKCS1` -- 带 ★ 的分配给开发者 - -![admin预览](images/10085_2.png "10085_2.png") diff --git a/doc/docs/files/10087_自定义返回结果.md b/doc/docs/files/10087_自定义返回结果.md deleted file mode 100644 index e5609bea..00000000 --- a/doc/docs/files/10087_自定义返回结果.md +++ /dev/null @@ -1,110 +0,0 @@ -# 自定义返回结果 - -网关默认对业务结果进行合并,然后返回统一的格式。 - -针对`alipay.story.find`接口,微服务端返回结果如下: - -```json -{ - "name": "白雪公主", - "id": 1, - "gmtCreate": 1554193987378 -} -``` - -网关合并后,最终结果如下 - -```json -{ - "alipay_story_find_response": { - "msg": "Success", - "code": "10000", - "name": "白雪公主", - "id": 1, - "gmtCreate": 1554193987378 - }, - "sign": "xxxxx" -} -``` - -其中`alipay_story_find_response`是它的数据节点。规则是: - -> 将接口名中的点`.`转换成下划线`_`,后面加上`_response` - -代码实现如下: - -```java -String method = "alipay.story.find"; -return method.replace('.', '_') + "_response"; -``` - -详见`DefaultDataNameBuilder.java` - -如果要更改数据节点,比如`result`,可使用`CustomDataNameBuilder.java`。 - -```java -@Configuration -public class MyConfig { - - static { - ... - ApiConfig.getInstance().setDataNameBuilder(new CustomDataNameBuilder()); - ... - } - -} -``` - -设置后,网关统一的返回结果如下: - -```json -{ - "result": { - ... - }, - "sign": "xxxxx" -} -``` - -此外,构造方法可指定自定义字段名称:`new CustomDataNameBuilder("data");`。 -设置后,数据节点将变成`data` - -```json -{ - "data": { - ... - }, - "sign": "xxxxx" -} -``` - -**注**:网关设置了CustomDataNameBuilder后,SDK也要做相应的更改:`OpenConfig.dataNameBuilder = new CustomDataNameBuilder();` - -## 自定义结果处理 - -如果想要对微服务结果做更深一步处理,步骤如下: - -1. 新增一个类,继承`GatewayResultExecutor.java`,并重写`String mergeResult(T request, String serviceResult)`方法 - -2. 配置自定义类 - -```java -@Configuration -public class MyConfig { - - static { - ... - ApiConfig.getInstance().setGatewayResultExecutor(new MyGatewayResultExecutor()); - ... - } - -} -``` - - -## 不合并结果 - -如果不希望对结果进行合并,可在application.properties中设置`sop.api-config.merge-result=false` - - -这样,网关最终返回结果即为微服务端的返回结果。 \ No newline at end of file diff --git a/doc/docs/files/10088_自定义过滤器.md b/doc/docs/files/10088_自定义过滤器.md deleted file mode 100644 index 853dc57b..00000000 --- a/doc/docs/files/10088_自定义过滤器.md +++ /dev/null @@ -1,40 +0,0 @@ -# 自定义过滤器 - -演示在网关追加一个header - -```java -public class CustomFilter implements GlobalFilter, Ordered { - - @Override - public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - // 演示在网关追加header - ApiParam apiParam = ServerWebExchangeUtil.getApiParam(exchange); - String token = apiParam.fetchAccessToken(); - ServerWebExchange serverWebExchange = ServerWebExchangeUtil.addHeaders(exchange, httpHeaders -> { - httpHeaders.add("token", token); - }); - return chain.filter(serverWebExchange); - } - - @Override - public int getOrder() { - // 自定义过滤器可以从0开始 - return 0; - } -} -``` - -使用过滤器,在sop-gateway中找到MyConfig,添加: - -```java -@Configuration -public class MyConfig { - - ... - @Bean - CustomFilter customFilter() { - return new CustomFilter(); - } - ... -} -``` \ No newline at end of file diff --git a/doc/docs/files/10089_自定义校验token.md b/doc/docs/files/10089_自定义校验token.md deleted file mode 100644 index 6716ec55..00000000 --- a/doc/docs/files/10089_自定义校验token.md +++ /dev/null @@ -1,52 +0,0 @@ -# 自定义校验token - -在`@Open`注解中有一个属性`needToken`,用来告诉网关是否校验token - -```java -/** -* 是否需要appAuthToken,设置为true,网关端会校验token是否存在 -*/ -boolean needToken() default false; -``` - -使用方式: - -```java -@ApiOperation(value="传递token", notes = "传递token") -@Open(value = "story.get.token", needToken = true/* 设置true,网关会校验token是否存在 */) -@RequestMapping("token") -public StoryResult token(StoryParam story, HttpServletRequest request) { - OpenContext openContext = ServiceContext.getCurrentContext().getOpenContext(); - StoryResult result = new StoryResult(); - result.setName("appAuthToken:" + openContext.getAppAuthToken()); - return result; -} -``` - -指定了needToken=true后,网关会判断客户端是否传了`app_auth_token`参数,没有传则返回错误信息。 - -网关默认简单校验参数值是否存在,如果要校验有效性,需要自己实现。 - -自己实现步骤: - -- 设置`ApiConfig中的tokenValidator属性` - -`TokenValidator`是一个函数式接口,可以直接使用Lambda表达式,示例代码如下: - -```java -@Component -public class MyConfig { - - @PostConstruct - public void after() { - ApiConfig.getInstance().setTokenValidator(apiParam -> { - // 获取客户端传递过来的token - String token = apiParam.fetchAccessToken(); - return !StringUtils.isBlank(token); - // TODO: 校验token有效性,可以从redis中读取 - - // 返回true表示这个token真实、有效 - }); - } -} -``` diff --git a/doc/docs/files/10090_网关拦截器.md b/doc/docs/files/10090_网关拦截器.md deleted file mode 100644 index ee086054..00000000 --- a/doc/docs/files/10090_网关拦截器.md +++ /dev/null @@ -1,57 +0,0 @@ -# 网关拦截器 - -从3.1.0开始新增了网关拦截器,使用该拦截器可做一些数据统计,日志记录等工作。 - -使用方法如下: - -- 在sop-gateway工程下新增一个类,实现`RouteInterceptor`接口,实现接口中的方法。别忘了加`@Component` - -```java -@Component -public class MyRouteInterceptor implements RouteInterceptor { - @Override - public void preRoute(RouteInterceptorContext context) { - ApiParam apiParam = context.getApiParam(); - System.out.println("请求接口:" + apiParam.fetchNameVersion()); - } - - @Override - public void afterRoute(RouteInterceptorContext context) { - System.out.println("请求成功,微服务返回结果:" + context.getServiceResult()); - } - - @Override - public int getOrder() { - return 0; - } -} -``` - -RouteInterceptor接口方法说明: - -- `public void preRoute(RouteInterceptorContext context)` - -路由转发前执行,在签名验证通过之后会立即执行这个方法。 - -- `public void afterRoute(RouteInterceptorContext context)` - -路由转发完成后,即拿到微服务返回结果后执行这个方法 - -- `public int getOrder()` - -指定拦截执行顺序,数字小的优先执行,建议从0开始。 - -- `default boolean match(RouteInterceptorContext context)` - -是否匹配,返回true执行拦截器,默认true - -RouteInterceptorContext参数存放了各类参数信息。 - -参考类: - -- `com.gitee.sop.gatewaycommon.interceptor.RouteInterceptor` 拦截器接口 -- `com.gitee.sop.gatewaycommon.interceptor.RouteInterceptorContext` 拦截器上下文 -- `com.gitee.sop.gatewaycommon.interceptor.MonitorRouteInterceptor` 默认实现的拦截器,用于收集监控数据 - - - diff --git a/doc/docs/files/10091_路由授权.md b/doc/docs/files/10091_路由授权.md deleted file mode 100644 index 9823f89c..00000000 --- a/doc/docs/files/10091_路由授权.md +++ /dev/null @@ -1,25 +0,0 @@ -# 路由授权 - -1.1.0版本新增了路由授权功能,采用RBAC权限管理方式实现。 - -- 每个ISV(appKey)对应一个或多个角色 -- 每个角色分配多个路由权限 - -接口跟角色相关联,ISV拥有哪些角色,就具有角色对应的接口访问权限。 - -假设把路由a,b,c分配给了`VIP角色`,那么具有VIP角色的ISV可以访问a,b,c三个路由。 - -默认情况下,接口访问时公开的,ISV都能访问。如果要设置某个接口访问权限,在`@Open`注解中指定permission=true。 -如:`@Open(value = "permission.story.get", permission = true)`。这样该接口是需要经过授权给ISV才能访问的。 - -重启服务后,登录admin,服务管理-路由列表界面中,`访问权限`列会出现一个点击授权,点击出现授权窗口,勾选对应的角色即可完成授权。 - -- `点击授权`,进行角色授权 - -![admin预览](images/10090_1.png "10090_1.png") - -- 勾选对应角色,点击保存 - -![admin预览](images/10090_2.png "10090_2.png") - -这里演示的是:具有普通权限的ISV能够访问`permission.story.get`接口,运行`PermissionDemoPostTest`测试用例进行验证 diff --git a/doc/docs/files/10092_接口限流.md b/doc/docs/files/10092_接口限流.md deleted file mode 100644 index 4a6cb5f2..00000000 --- a/doc/docs/files/10092_接口限流.md +++ /dev/null @@ -1,80 +0,0 @@ -# 接口限流 - -SOP提供了简单的接口限流策略: - -- 窗口策略:每秒处理固定数量的请求,超出请求返回错误信息。 -- 令牌桶策略:每秒放置固定数量的令牌数,每个请求进来后先去拿令牌,拿到了令牌才能继续,拿不到则等候令牌重新生成了再拿。 - -如果一个接口设置了窗口策略,假设接口每秒可处理5个请求,一秒内同时有6个请求进来,前5个接口是能够访问的,第六个请求将返回错误信息。 - -如果设置了令牌桶策略,桶的容量是5,那么每秒中生成5个令牌,同一时间有6个请求进来,那么前5个能成功拿到令牌继续,第六个则等待,令牌重新生成了再拿。 - -默认情况下接口的限流功能是关闭的,可在sop admin中配置并开启。功能在`路由管理-->限流管理`下。 - -## 多维度限流 - -- 可针对接口进行限流,所有访问该接口的请求都被限流 -- 可针对appKey进行限流,某个appKey请求过来后,对他限流 -- 可针对IP进行限流,某个IP请求过来后,对他限流 - -此外还可以进行组合 - -- 可针对接口+appKey进行限流,这个appKey调用某个接口比较频繁,可以将它限制住 -- 可针对接口+IP进行限流,某个ip在频繁调用接口,可以将它限制住 - -由于存在组合情况,一个接口可能会配置多个限流规则。在这种情况下会优先取排序值小的那一条,如果排序值一样,则默认取第一条。 - -假设有下面几个限流规则: - -- 接口:`goods.get`, 排序值:1, 每秒可处理请求数:10 -- 接口:`goods.get`, appKey:xxxx, 排序值:0, 每秒可处理请求数:5 -- 接口:`goods.get`, ip:172.1.2.2, 排序值:2, 每秒可处理请求数:6 - -客户端调用接口:`http://open.domain.com?method=goods.get&app_id=xxxx`,客户端IP为`172.1.2.2` - -这种情况下上面三条限流规则都命中了,由于排序值小优先执行,因此第二条规则命中. - - -具体设置方式可在sop admin中配置,功能在`服务管理-->限流管理`下。执行`com.gitee.sop.test.LimitTest`测试用例验证限流情况 - -![限流配置](images/10092_1.png "10092_1.png") - -![限流配置](images/10092_2.png "10092_2.png") - -## 分布式限流 - -默认的限流方式是单机的,如果要部署多台网关实例,需要使用分布式限流 - -SOP使用redis进行分布式限流(只支持窗口策略),操作步骤如下: - -- sop-gateway/pom.xml添加redis依赖 - -```xml - - org.springframework.boot - spring-boot-starter-data-redis - -``` - -- sop-gateway下的application-dev.properties文件添加redis配置 - -```properties -# redis -spring.redis.database=0 -spring.redis.host=127.0.0.1 -spring.redis.port=6379 -``` - -- 在`com.gitee.sop.gateway.config.MyConfig`中添加如下代码: - -```java -@Autowired -private RedisTemplate redisTemplate; - -@PostConstruct -public void after() { - ApiConfig.getInstance().setLimitManager(new RedisLimitManager(redisTemplate)); - ... -} -``` - diff --git a/doc/docs/files/10093_路由监控.md b/doc/docs/files/10093_路由监控.md deleted file mode 100644 index b0e56a87..00000000 --- a/doc/docs/files/10093_路由监控.md +++ /dev/null @@ -1,27 +0,0 @@ -# 路由监控 - -路由监控功能可以查看各个接口的调用情况,监控信息收集采用拦截器实现,前往【服务管理】-【路由监控】查看 - -- 后台预览 - -![监控日志](images/10093_1.png "10093_1.png") - -![监控日志](images/10093_2.png "10093_2.png") - - -- 注意事项 - -处理完错误后,请及时`标记解决`,一个接口默认保存50条错误信息,采用LRU机制,淘汰老的。标记解决后则会空出一个位置存放新的错误信息。 - -重复的错误只会存放一条记录,然后累加错误次数,重复错误定义如下: - -`instanceId + routeId + errorMsg`,即一个实例 + 路由id + 错误信息确定一个错误 - -可在网关设置`sop.monitor.error-count-capacity=50`参数调整错误容量 - -考虑到数据库压力,网关收到错误信息后并不会立即保存到数据库,而是先保存到内容中,然后定时保存到时间,默认时间隔为30秒 -可通过`sop.monitor.flush-period-seconds=30`调整间隔时间。 - -相关类: - -- com.gitee.sop.gateway.interceptor.MonitorRouteInterceptor diff --git a/doc/docs/files/10095_SDK开发.md b/doc/docs/files/10095_SDK开发.md deleted file mode 100644 index 0b970c3c..00000000 --- a/doc/docs/files/10095_SDK开发.md +++ /dev/null @@ -1,343 +0,0 @@ -# 开发SDK - -开放平台把接口开发完毕后,一般需要开发对应的SDK,提供给ISV。SOP提供了一个基础的SDK开发包 - -开发者可以在此基础上做开发,就拿sdk-java来说,具体步骤如下: - -## sdk-java - -SDK依赖了三个jar包 - -- okhttp.jar 用于网络请求 -- fastjson.jar 用于json处理 -- commons-logging.jar 日志处理 - -### 接口封装步骤 - -比如获取故事信息接口 - -- 接口名:alipay.story.find -- 版本号:1.0 -- 参数:name 故事名称 -- 返回信息 - -```json -{ - "alipay_story_find_response": { - "msg": "Success", - "code": "10000", - "name": "白雪公主", - "id": 1, - "gmtCreate": 1554193987378 - }, - "sign": "xxxxx" -} -``` - -针对这个接口,封装步骤如下: - -1.在`model`包下新建一个类,定义业务参数 - - -```java -@Data -public class GetStoryModel { - - @JSONField(name = "name") - private String name; -} -``` - -2.在`response`包下新建一个返回类GetStoryResponse,继承`BaseResponse` - -里面填写返回的字段 - -``` -@Data -public class GetStoryResponse extends BaseResponse { - private Long id; - private String name; - private Date gmtCreate; -} -``` - -3.在`request`包下新建一个请求类,继承`BaseRequest` - -BaseRequest中有个泛型参数,填`GetStoryResponse`类,表示这个请求对应的返回类。 -重写`method()`方法,填接口名。 - -如果要指定版本号,可重写`version()`方法,或者后续使用`request.setVersion(version)`进行设置 - -```java -public class GetStoryRequest extends BaseRequest { - @Override - protected String method() { - return "alipay.story.find"; - } -} -``` - -可重写`getRequestMethod()`方法指定HTTP请求method,默认是POST。 - -```java -@Override -protected RequestMethod getRequestMethod() { - return RequestMethod.GET; -} -``` - -**建议读请求用GET,写请求用POST**, - -### 使用方式 - -```java -String url = "http://localhost:8081"; -String appId = "2019032617262200001"; -String privateKey = "你的私钥"; - -// 声明一个就行 -OpenClient client = new OpenClient(url, appId, privateKey); - -// 标准用法 -@Test -public void testGet() { - // 创建请求对象 - GetStoryRequest request = new GetStoryRequest(); - // 请求参数 - GetStoryModel model = new GetStoryModel(); - model.setName("白雪公主"); - - request.setBizModel(model); - - // 发送请求 - GetStoryResponse response = client.execute(request); - - if (response.isSuccess()) { - // 返回结果 - System.out.println(String.format("成功!response:%s\n响应原始内容:%s", - JSON.toJSONString(response), response.getBody())); - } else { - System.out.println("错误,subCode:" + response.getSubCode() + ", subMsg:" + response.getSubMsg()); - } -} -``` - -### 使用方式2(懒人版) - -如果不想添加Request,Response,Model。可以用这种方式,返回body部分是字符串,后续自己处理 - -body对应的是alipay_story_find_response部分 - -```java -@Test -public void testLazy() { - // 创建请求对象 - CommonRequest request = new CommonRequest("alipay.story.find"); - // 请求参数 - Map bizModel = new HashMap<>(); - bizModel.put("name", "白雪公主"); - request.setBizModel(bizModel); - - // 发送请求 - CommonResponse response = client.execute(request); - - if (response.isSuccess()) { - // 返回结果,body对应的是alipay_story_find_response部分 - String body = response.getBody(); - JSONObject jsonObject = JSON.parseObject(body); - System.out.println(jsonObject); - } else { - System.out.println(response); - } -} -``` - -## sdk-.net - - -### 接口封装步骤 - -比如获取故事信息接口 - -- 接口名:alipay.story.find -- 版本号:1.0 -- 参数:name 故事名称 -- 返回信息 - -``` -{ - "alipay_story_find_response": { - "msg": "Success", - "code": "10000", - "name": "白雪公主", - "id": 1, - "gmtCreate": 1554193987378 - }, - "sign": "xxxxx" -} -``` - -针对这个接口,封装步骤如下: - -1.在`Model`包下新建一个类,定义业务参数 - -``` -namespace SDKCSharp.Model -{ - public class GetStoryModel - { - /// - /// 故事名称 - /// - /// The name. - [JsonProperty("name")] - public string Name { get; set; } - } -} -``` - -`[JsonProperty("name")]`是Newtonsoft.Json组件中的类,用于Json序列化,括号中的是参数名称。 -类似于Java中的注解,`@JSONField(name = "xx")` - -2.在`Response`包下新建一个返回类GetStoryResponse,继承`BaseResponse` - -里面填写返回的字段 - -``` -namespace SDKCSharp.Response -{ - public class GetStoryResponse: BaseResponse - { - [JsonProperty("id")] - public int Id { get; set; } - - [JsonProperty("name")] - public string Name { get; set; } - - [JsonProperty("gmt_create")] - public string GmtCreate { get; set; } - - } -} - -``` - -3.在`Request`文件夹下新建一个请求类,继承`BaseRequest` - -BaseRequest中有个泛型参数,填`GetStoryResponse`类,表示这个请求对应的返回类。 -重写`GetMethod()`方法,填接口名。 - -如果要指定版本号,可重写`GetVersion()`方法,或者后续使用`request.Version = version`进行设置 - -``` -namespace SDKCSharp.Request -{ - public class GetStoryRequest : BaseRequest - { - public override string GetMethod() - { - return "alipay.story.find"; - } - } - -} -``` - - - -### 使用方式 - -``` -class MainClass -{ - static string url = "http://localhost:8081"; - static string appId = "2019032617262200001"; - // 平台提供的私钥 - static string privateKey = "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCXJv1pQFqWNA/++OYEV7WYXwexZK/J8LY1OWlP9X0T6wHFOvxNKRvMkJ5544SbgsJpVcvRDPrcxmhPbi/sAhdO4x2PiPKIz9Yni2OtYCCeaiE056B+e1O2jXoLeXbfi9fPivJZkxH/tb4xfLkH3bA8ZAQnQsoXA0SguykMRZntF0TndUfvDrLqwhlR8r5iRdZLB6F8o8qXH6UPDfNEnf/K8wX5T4EB1b8x8QJ7Ua4GcIUqeUxGHdQpzNbJdaQvoi06lgccmL+PHzminkFYON7alj1CjDN833j7QMHdPtS9l7B67fOU/p2LAAkPMtoVBfxQt9aFj7B8rEhGCz02iJIBAgMBAAECggEARqOuIpY0v6WtJBfmR3lGIOOokLrhfJrGTLF8CiZMQha+SRJ7/wOLPlsH9SbjPlopyViTXCuYwbzn2tdABigkBHYXxpDV6CJZjzmRZ+FY3S/0POlTFElGojYUJ3CooWiVfyUMhdg5vSuOq0oCny53woFrf32zPHYGiKdvU5Djku1onbDU0Lw8w+5tguuEZ76kZ/lUcccGy5978FFmYpzY/65RHCpvLiLqYyWTtaNT1aQ/9pw4jX9HO9NfdJ9gYFK8r/2f36ZE4hxluAfeOXQfRC/WhPmiw/ReUhxPznG/WgKaa/OaRtAx3inbQ+JuCND7uuKeRe4osP2jLPHPP6AUwQKBgQDUNu3BkLoKaimjGOjCTAwtp71g1oo+k5/uEInAo7lyEwpV0EuUMwLA/HCqUgR4K9pyYV+Oyb8d6f0+Hz0BMD92I2pqlXrD7xV2WzDvyXM3s63NvorRooKcyfd9i6ccMjAyTR2qfLkxv0hlbBbsPHz4BbU63xhTJp3Ghi0/ey/1HQKBgQC2VsgqC6ykfSidZUNLmQZe3J0p/Qf9VLkfrQ+xaHapOs6AzDU2H2osuysqXTLJHsGfrwVaTs00ER2z8ljTJPBUtNtOLrwNRlvgdnzyVAKHfOgDBGwJgiwpeE9voB1oAV/mXqSaUWNnuwlOIhvQEBwekqNyWvhLqC7nCAIhj3yvNQKBgQCqYbeec56LAhWP903Zwcj9VvG7sESqXUhIkUqoOkuIBTWFFIm54QLTA1tJxDQGb98heoCIWf5x/A3xNI98RsqNBX5JON6qNWjb7/dobitti3t99v/ptDp9u8JTMC7penoryLKK0Ty3bkan95Kn9SC42YxaSghzqkt+uvfVQgiNGQKBgGxU6P2aDAt6VNwWosHSe+d2WWXt8IZBhO9d6dn0f7ORvcjmCqNKTNGgrkewMZEuVcliueJquR47IROdY8qmwqcBAN7Vg2K7r7CPlTKAWTRYMJxCT1Hi5gwJb+CZF3+IeYqsJk2NF2s0w5WJTE70k1BSvQsfIzAIDz2yE1oPHvwVAoGAA6e+xQkVH4fMEph55RJIZ5goI4Y76BSvt2N5OKZKd4HtaV+eIhM3SDsVYRLIm9ZquJHMiZQGyUGnsvrKL6AAVNK7eQZCRDk9KQz+0GKOGqku0nOZjUbAu6A2/vtXAaAuFSFx1rUQVVjFulLexkXR3KcztL1Qu2k5pB6Si0K/uwQ="; - - - // 声明一个就行 - static OpenClient client = new OpenClient(url, appId, privateKey); - - public static void Main(string[] args) - { - TestGet(); - } - - // 标准用法 - private static void TestGet() - { - // 创建请求对象 - GetStoryRequest request = new GetStoryRequest(); - // 请求参数 - GetStoryModel model = new GetStoryModel(); - model.Name = "白雪公主"; - request.BizModel = model; - - // 发送请求 - GetStoryResponse response = client.Execute(request); - - if (response.IsSuccess()) - { - // 返回结果 - Console.WriteLine("故事名称:{0}", response.Name); - } - else - { - Console.WriteLine("错误, code:{0}, msg:{1}, subCode:{2}, subMsg:{3}", - response.Code, response.Msg, response.SubCode, response.SubMsg); - } - } - - -} -``` - -### 使用方式2(懒人版) - -如果不想添加Request,Response,Model。可以用这种方式,返回data部分是Dictionary,后续自己处理 - -``` -// 懒人版,如果不想添加Request,Response,Model。可以用这种方式,返回Dictionary,后续自己处理 -private static void TestCommon() -{ - // 创建请求对象 - CommonRequest request = new CommonRequest("alipay.story.find"); - // 请求参数 - Dictionary bizModel = new Dictionary - { - ["name"] = "白雪公主" - }; - - request.BizModel = bizModel; - - // 发送请求 - CommonResponse response = client.Execute(request); - - if (response.IsSuccess()) - { - // 返回结果 - string body = response.Body; - Dictionary dict = JsonUtil.ParseToDictionary(body); - - Console.WriteLine("Dictionary内容:"); - foreach (var item in dict) - { - Console.WriteLine("{0}:{1}", item.Key, item.Value); - } - } - else - { - Console.WriteLine("错误, code:{0}, msg:{1}, subCode:{2}, subMsg:{3}", - response.Code, response.Msg, response.SubCode, response.SubMsg); - } -} -``` - -## 发布SDK - -将编写好的SDK打包后上传到云服务器,如百度网盘 - -前往sop-admin,点击`SDK管理`菜单,点击`发布SDK`,填写SDK语言、版本、下载地址(网盘地址)、调用示例 - -保存后,ISV端会看到发布的SDK \ No newline at end of file diff --git a/doc/docs/files/10097_应用授权.md b/doc/docs/files/10097_应用授权.md deleted file mode 100644 index b6e447a9..00000000 --- a/doc/docs/files/10097_应用授权.md +++ /dev/null @@ -1,117 +0,0 @@ -# 应用授权 - -## 概述 - -- 1、用户对开发者进行应用授权后,开发者可以帮助用户完成相应的业务逻辑。 -- 2、授权采用标准的OAuth 2.0流程。 - -## 授权流程 - -![授权流程](images/10097_1.png "10097_1.png") - -## 快速接入 - -- 第一步:应用授权URL拼装 - -拼接规则: - -http://openauth.yourdomain.com/oauth2/appToAppAuth?app_id=2019032617262200001&redirect_uri=http%3a%2f%2flocalhost%3a8087%2foauth2callback - -参数说明: - -| 参数 | 参数名称 | 类型 | 必填 | 描述 | 范例 | -|--------------|-------------|--------|----|---------------|--------------------------| -| app_id | 开发者应用的AppId | String | 是 | 开发者应用的AppId | 2015101400446982 | -| redirect_uri | 回调页面 | String | 是 | 参数需要UrlEncode | http%3A%2F%2Fexample.com | - - -- 第二步:获取code - -授权成功后,会跳转至开发者定义的回调页面(即redirect_uri参数对应的url),在回调页面请求中会带上当次授权的授权码code和开发者的app_id,示例如下: - -http://www.xxx.com/oauth2callback?app_id=2015101400446982&code=ca34ea491e7146cc87d25fca24c4cD11 - - -- 第三步:使用code换取app_auth_token - -接口名称:open.auth.token.app - -开发者通过code可以换取app_auth_token、授权用户的userId。 - -**注意**:应用授权的code唯一,code使用一次后失效,有效期24小时; app_auth_token永久有效。 - -**请求参数说明** - -| 参数 | 参数名称 | 类型 | 必填 | 描述 | 范例 | -|---------------|------|--------|----|---------------------------------------------------------------------------------|------------------------------------------| -| grant_type | 授权类型 | String | 是 | 如果使用code换取token,则为authorization_code,如果使用refresh_token换取新的token,则为refresh_token | authorization_code | -| code | 授权码 | String | 否 | 与refresh_token二选一,用户对应用授权后得到,即第一步中开发者获取到的code值 | bf67d8d5ed754af297f72cc482287X62 | -| refresh_token | 刷新令牌 | String | 否 | 与code二选一,可为空,刷新令牌时使用 | 201510BB0c409dd5758b4d939d4008a525463X62 | - -**接口请求SDK示例** - -```java -@GetMapping("oauth2callback") -@ResponseBody -public String callback(HttpServletRequest servletRequest, HttpServletResponse servletResponse) { - String app_id = servletRequest.getParameter("app_id"); - String code = servletRequest.getParameter("code"); - OpenAuthTokenAppRequest request = new OpenAuthTokenAppRequest(); - OpenAuthTokenAppModel model = new OpenAuthTokenAppModel(); - model.setCode(code); - model.setGrant_type("authorization_code"); - request.setBizModel(model); - // 根据code获取token - OpenAuthTokenAppResponse response = openClient.execute(request); - if (response.isSuccess()) { - // 成功拿到token,开发者在这里保存token信息 - // 后续使用token进行接口访问 - log.info("授权成功,body:{}", response.getBody()); - } - return response.getBody(); -} -``` - -**同步响应参数说明** - -| 参数 | 参数名称 | 类型 | 必填 | 描述 | 范例 | -|-------------------|---------|--------|----|----------------------------------------------------------|----------------------------------| -| app_auth_token | 用户授权令牌 | String | 是 | 通过该令牌来帮助用户发起请求,完成业务 | 856faf8d77d3b985c1073557ce6ea724 | -| user_id | 授权用户的ID | String | 是 | 授权者id | 1 | -| expires_in | 令牌有效期 | Long | 是 | 负值表示永久有效 | -1 | -| re_expires_in | 刷新令牌有效期 | Long | 是 | 负值表示永久有效 | -3 | -| app_refresh_token | 刷新令牌时使用 | String | 是 | 刷新令牌后,我们会保证老的app_auth_token从刷新开始10分钟内可继续使用,请及时替换为最新token | 88e68196d2359667f0dc8136e6c86803 | - - -**同步响应结果示例** - -```json -{ - "open_auth_token_app_response": { - "code": "10000", - "msg": "Success", - "app_auth_token": "88e68196d2359667f0dc8136e6c86803", - "app_refresh_token": "856faf8d77d3b985c1073557ce6ea724", - "expires_in": -1, - "re_expires_in": -3, - "user_id": "1" - }, - "sign": "xxx" -} -``` - -**刷新token** - -```java -OpenAuthTokenAppRequest request = new OpenAuthTokenAppRequest(); -OpenAuthTokenAppModel model = new OpenAuthTokenAppModel(); -model.setGrant_type("refresh_token"); -model.setRefresh_token("856faf8d77d3b985c1073557ce6ea724"); -request.setBizModel(model); -OpenAuthTokenAppResponse response = openClient.execute(request); -if (response.isSuccess()) { - // 成功拿到token,开发者在这里保存token信息 - // 后续使用token进行接口访问 - log.info("换取token成功,body:{}", response.getBody()); -} -``` \ No newline at end of file diff --git a/doc/docs/files/10100_提供restful接口.md b/doc/docs/files/10100_提供restful接口.md deleted file mode 100644 index 13ee9393..00000000 --- a/doc/docs/files/10100_提供restful接口.md +++ /dev/null @@ -1,147 +0,0 @@ -# 提供restful接口 - -有些接口没有被开放,但是也想要通过网关来访问,SOP提供一个固定的请求格式来访问。 - -请求格式: - -`http://ip:port/rest/服务id/your_path`,其中`http://ip:port/rest/`为固定部分,后面跟微服务请求路径。 - -下面是一个微服务的接口例子 - -```java -@RestController -@RequestMapping("food") -public class TraditionalWebappController { - @RequestMapping(value = "getFoodById", method = RequestMethod.GET) - public Food getFoodById(Integer id) { - Food food = new Food(); - food.setId(id); - food.setName("香蕉"); - food.setPrice(new BigDecimal(20.00)); - return food; - } -} -``` - -这是一个`食品服务`例子,serviceId为`food-service`,假设网关ip为10.0.1.11,端口8081;食品服务ip为10.0.1.22,端口2222 - -1. 网关访问:`http://10.0.1.11:8081/rest/food-service/food/getFoodById?id=2` - -2. 本地访问:`http://10.0.1.22:2222/food/getFoodById/?id=2` - - -由此可见,对于前端调用者来说,它把网关看做一个大服务,只访问网关提供的请求,不需要关心网关后面的路由转发。网关后面各个微服务独自管理, -微服务之间的调用可以使用dubbo或feign,有了版本号的管理,可以做到服务的平滑升级,对用户来说都是无感知的。结合SOP-Admin提供的上下线功能, -可实现预发布环境功能。 - -默认情况下,`http://ip:port/rest/`为固定部分,如果想要更改`rest`,可在网关配置文件指定:`sop.restful.path=/aaa` - -请求前缀将变成:`http://ip:port/aaa/` - -- 关闭restful功能 - -如果不想用该功能,在配置文件指定: - -```properties -# 开启restful请求,默认开启 -sop.restful.enable=false -``` - -- 封装请求工具【可选】 - -封装请求,方便调用,针对vue的封装如下: - -```js -import axios from 'axios' - -// 创建axios实例 -const client = axios.create({ - baseURL: process.env.BASE_API, // api 的 base_url - timeout: 5000, // 请求超时时间 - headers: { 'Content-Type': 'application/json' } -}) - -const RequestUtil = { - /** - * 请求接口 - * @param url 请求路径,如http://localhost:8081/rest/food-service/food/getFoodById - * @param data 请求数据,json格式 - * @param callback 成功回调 - * @param errorCallback 失败回调 - */ - post: function(url, data, callback, errorCallback) { - client.post(url, data) - .then(function(response) { - const resp = response.result - const code = resp.code - // 成功,网关正常且业务正常 - if (code === '10000' && !resp.sub_code) { - callback(resp) - } else { - // 报错 - Message({ - message: resp.msg, - type: 'error', - duration: 5 * 1000 - }) - } - }) - .catch(function(error) { - console.log('err' + error) // for debug - errorCallback && errorCallback(error) - }) - } -} - -export default RequestUtil -``` - -jQuery版本如下: - -```js -var RequestUtil = { - /** - * 请求接口 - * @param url 请求路径,如http://localhost:8081/rest/food-service/food/getFoodById - * @param data 请求数据,json格式 - * @param callback 成功回调 - * @param errorCallback 失败回调 - */ - post: function(url, data, callback, errorCallback) { - $.ajax({ - url: 'http://localhost:8081' // 网关url - , type: 'post' - , headers: { 'Content-Type': 'application/json' } - , data: data - ,success:function(response) { - var resp = response.result - var code = resp.code - // 成功,网关正常且业务正常 - if (code === '10000' && !resp.sub_code) { - callback(resp) - } else { - // 报错 - alert(resp.msg); - } - } - , error: function(error) { - errorCallback && errorCallback(error) - } - }); - } -} -``` - -jQuery调用示例: - -```js -$(function () { - var data = { - id: 1 - ,name: '葫芦娃' - } - RequestUtil.post('http://localhost:8081/rest/food-service/food/getFoodById', data, function (result) { - console.log(result) - }); -}) -``` diff --git a/doc/docs/files/10104_文件上传.md b/doc/docs/files/10104_文件上传.md deleted file mode 100644 index ac60ab36..00000000 --- a/doc/docs/files/10104_文件上传.md +++ /dev/null @@ -1,111 +0,0 @@ -# 文件上传 - -请求接口时带上文件 - -## 客户端调用 - -```java -DemoFileUploadRequest request = new DemoFileUploadRequest(); - -DemoFileUploadModel model = new DemoFileUploadModel(); -model.setRemark("上传文件参数"); -request.setBizModel(model); - -List files = new ArrayList<>(); -String root = System.getProperty("user.dir"); -System.out.println(root); -// 这里演示将resources下的两个文件上传到服务器 -files.add(new UploadFile("file1", new File(root + "/src/main/resources/file1.txt"))); -files.add(new UploadFile("file2", new File(root + "/src/main/resources/file2.txt"))); -request.setFiles(files); - -DemoFileUploadResponse response = client.execute(request); - -System.out.println("--------------------"); -if (response.isSuccess()) { - List responseFiles = response.getFiles(); - System.out.println("您上传的文件信息:"); - responseFiles.stream().forEach(file->{ - System.out.println(file); - }); -} else { - System.out.println("errorCode:" + response.getCode() + ",errorMsg:" + response.getMsg()); -} -System.out.println("--------------------"); -``` - -客户端使用`UploadFile`添加上传文件 - -```java -/** - * @param name 表单名称,不能重复 - * @param file 文件 - * @throws IOException - */ -public UploadFile(String name, File file) throws IOException { - this(name, file.getName(), FileUtil.toBytes(file)); -} -``` - -其中`name`表示字段名称,相当于``中的name - -源码详见sop-sdk - -## 服务端接收文件 - -- 方式1 - -```java -/** - * 方式1:将文件写在参数中,可直接获取。好处是可以校验是否上传 - * @param param - * @return - */ -@Open("file.upload") -@RequestMapping("file1") -public FileUploadVO file1(FileUploadParam param) { - System.out.println(param.getRemark()); - // 获取上传的文件 - MultipartFile file1 = param.getFile1(); - MultipartFile file2 = param.getFile2(); - ... - return vo; -} - -@Data -public class FileUploadParam { - private String remark; - - // 上传文件,字段名称对应表单中的name属性值 - @NotNull(message = "文件1不能为空") - private MultipartFile file1; - - @NotNull(message = "文件2不能为空") - private MultipartFile file2; -} -``` - -方式1的好处是可以使用`JSR-303`校验文件是否上传 - -- 方式2 - -```java -/** - * 方式2:从request中获取上传文件 - * - * @param param - * @return - */ -@Open("file.upload2") -@RequestMapping("file2") -public FileUploadVO file2(FileUploadParam2 param, HttpServletRequest request) { - System.out.println(param.getRemark()); - FileUploadVO vo = new FileUploadVO(); - // 获取上传的文件 - Collection uploadFiles = UploadUtil.getUploadFiles(request); - ... - return vo; -} -``` - -微服务端源码参考`FileUploadDemoController.java` \ No newline at end of file diff --git a/doc/docs/files/10109_配置Sleuth链路追踪.md b/doc/docs/files/10109_配置Sleuth链路追踪.md deleted file mode 100644 index 794bba25..00000000 --- a/doc/docs/files/10109_配置Sleuth链路追踪.md +++ /dev/null @@ -1,104 +0,0 @@ -# 配置Sleuth链路追踪 - -配置了Sleuth可以很方便查看微服务的调用路线图,可快速定位问题。 - -SOP基于SpringCloud,因此只要整合[Spring Cloud Sleuth](https://spring.io/projects/spring-cloud-sleuth)即可。 -除此之外,还需要支持dubbo的链路的跟踪,Sleuth在2.0已经对dubbo做了支持,详见:[brave-instrumentation-dubbo-rpc](https://github.com/openzipkin/brave/tree/master/instrumentation/dubbo-rpc) - -接入Spring Cloud Sleuth步骤如下: - -- 下载zipkin服务器 - -以mac环境为例,执行下面命令,下载jar并启动zipkin服务 - -``` -curl -sSL https://zipkin.io/quickstart.sh | bash -s -java -jar zipkin.jar -``` - -默认端口是9411,更多安装方式详见:[quickstart](https://zipkin.io/pages/quickstart.html) - -- sop-gateway/pom.xml添加依赖 - -```xml - - - org.springframework.cloud - spring-cloud-starter-zipkin - -``` - -配置文件新增 - -```properties -# zipkin服务跟踪 -spring.zipkin.base-url=http://127.0.0.1:9411/ -# 设置sleuth收集信息的比率,默认0.1,最大是1,数字越大越耗性能 -spring.sleuth.sampler.probability=1 -``` -重启sop-gateway - -- 打开sop-story-web/pom.xml - -添加依赖: - -```xml - - - org.springframework.cloud - spring-cloud-starter-zipkin - - - - io.zipkin.brave - brave-instrumentation-dubbo-rpc - 5.6.6 - -``` - -配置文件新增: - -```properties -# zipkin服务跟踪 -spring.zipkin.base-url=http://127.0.0.1:9411/ -# 设置sleuth收集信息的比率,默认0.1,最大是1,数字越大越耗性能 -spring.sleuth.sampler.probability=1 -# dubbo使用zipkin过滤器 -dubbo.provider.filter=tracing -dubbo.consumer.filter=tracing -``` - -重启服务 - -- 打开sop-book/sop-book-web/pom.xml - -步骤同上 - -- 运行DubboDemoTest.java单元测试 - -运行完毕看控制台,找到日志信息 - -```text -2019-07-18 16:22:04.438 INFO [story-service,59dae98250b276bd,60828035658f175f,true] 90553 --- [:12345-thread-2] c.g.s.s.service.DefaultDemoService : dubbo provider, param: DemoParam(id=222) -``` - -日志内容多了`[story-service,59dae98250b276bd,60828035658f175f,true]`部分,这些是zipkin加进去的,说明如下: - -```text -story-service:服务名称 -59dae98250b276bd:traceId -60828035658f175f:spanId -true:是否上传到zipkin服务器 -``` - -查看各个服务的控制台,可以发现traceId是一致的。 - -- 浏览器打开:http://127.0.0.1:9411/ - -将traceId复制黏贴到右上角文本框进行查询,可看到服务调用链。 - - -![预览](images/10109_1.png "10109_1.png") - - - diff --git a/doc/docs/files/10110_预发布灰度发布.md b/doc/docs/files/10110_预发布灰度发布.md deleted file mode 100644 index 4d5a0cb3..00000000 --- a/doc/docs/files/10110_预发布灰度发布.md +++ /dev/null @@ -1,71 +0,0 @@ -# 预发布灰度发布 - -从1.14.0开始支持预发布、灰度发布,可登陆`SOP-Admin`,然后选择`服务列表`进行操作。 - -## 使用预发布 - -SOP中预发布的思路如下: - -假设网关工程sop-gateway在阿里云负载均衡有两台服务器,域名分别为: - -|域名|说明| -|:---- |:---- | -|open1.domain.com |网关服务器1 | -|openpre.domain.com | 网关服务器2,作为预发布请求入口| - -SLB对外域名为:`open.domain.com`,即开放平台入口为:`http://open.domain.com` - -访问`open.domain.com`会负载均衡到`open1.domain.com`和`openpre.domain.com`这两台实例 - -如果单独从`openpre.domain.com`访问,则会访问到预发布微服务。 - -SOP开启预发布步骤如下: - -修改网关工程配置文件,指定预发布域名 - -```properties -# 预发布网关域名,多个用英文逗号(,)隔开 -pre.domain=openpre.domain.com -``` - -重启网关 - - -微服务启动参数添加:`--spring.cloud.nacos.discovery.metadata.env=pre`(eureka下是:`--eureka.instance.metadata-map.env=pre`)。 -建议线上配两套启动脚本,其中预发布启动脚本添加启动参数`--spring.cloud.nacos.discovery.metadata.env=pre` - -登录SOP-Admin,在服务列表中点击预发布。 - -从`openpre.domain.com`请求进来的用户都会进预发布服务器,从SLB域名进来请求路由到非预发服务器 - -## 使用灰度发布 - -- 灰度接口定义 - -接口名相同,版本号不同。比如接口`user.get`,version:1.0 - -新建一个接口`user.get`,version:2.0 - -那么这个2.0接口就是灰度接口 - -灰度发布可允许指定的用户访问灰度服务器,其它用户还是走正常流程。 - -登录SOP-Admin,前往`服务列表`。 - -- 先设置灰度参数,指定灰度appId和灰度接口 -- 服务器实例开启灰度 - - -参考类: - -- LoadBalanceServerChooser.java 预发布/灰度发布服务实例选择 - -### 自定义判断灰度用户 - -默认根据`appId`和`IP`来判断灰度用户,如果要通过其它维度来判断是否是灰度用户,可实现GrayUserBuilder接口, -然后在springboot main方法中调用如下方法 - -```java -ApiConfig.getInstance().addGrayUserBuilder(new XXGrayUserBuilder()); -``` -参考:com.gitee.sop.gatewaycommon.loadbalancer.builder.AppIdGrayUserBuilder.java \ No newline at end of file diff --git a/doc/docs/files/10111_动态修改请求参数.md b/doc/docs/files/10111_动态修改请求参数.md deleted file mode 100644 index 64f000ab..00000000 --- a/doc/docs/files/10111_动态修改请求参数.md +++ /dev/null @@ -1,33 +0,0 @@ -# 动态修改请求参数 - -自1.14.0开始,zuul网关支持动态修改请求参数。即在网关修改客户端传递过来的参数,然后发送到微服务端。 - -``` -客户端参数{"name": "jim"} --> zuul中修改为{"name": "Lucy"} --> 微服务端将收到{"name": "Lucy"} -``` - -使用场景:客户端请求参数经过加密,在网关解密后,再次发送明文参数给微服务端 - -- 如何使用 - -在网关springboot启动函数中添加如下代码 - -```java -public static void main(String[] args) { - ApiConfig.getInstance().setParameterFormatter(requestParams -> { - // 获取biz_content - JSONObject jsonObject = requestParams.getJSONObject(ParamNames.BIZ_CONTENT_NAME); - // 修改biz_content中的值 - jsonObject.put("name", "name修改了111"); - jsonObject.put("remark", "remark修改了222"); - // 重新设置biz_content - requestParams.put(ParamNames.BIZ_CONTENT_NAME, jsonObject); - }); - SpringApplication.run(SopGatewayApplication.class, args); -} - -``` - -其中requestParams是客户端传递过来的参数,可在此基础上添加修改参数。 - -更多参考:com.gitee.sop.gatewaycommon.zuul.filter.PreParameterFormatterFilter.java diff --git a/doc/docs/files/10112_使用eureka.md b/doc/docs/files/10112_使用eureka.md deleted file mode 100644 index 64bc2fe9..00000000 --- a/doc/docs/files/10112_使用eureka.md +++ /dev/null @@ -1,3 +0,0 @@ -# 使用eureka - -切换到`eureka`分支 diff --git a/doc/docs/files/10113_超时设置.md b/doc/docs/files/10113_超时设置.md deleted file mode 100644 index 89a1081e..00000000 --- a/doc/docs/files/10113_超时设置.md +++ /dev/null @@ -1,14 +0,0 @@ -# 超时设置 - -当微服务处理业务逻辑时间过长,网关会报超时错误,默认等待时间是5秒。 - -可在网关指定`spring.cloud.gateway.httpclient.response-timeout`参数设置超时时间,单位毫秒 - -```properties -# 设置响应超时10秒 -spring.cloud.gateway.httpclient.response-timeout=10000 -``` - -更多配置参见:`org.springframework.cloud.gateway.config.HttpClientProperties` - -测试用例参见:`com.gitee.sop.test.TimeoutTest` diff --git a/doc/docs/files/90001_网关性能测试.md b/doc/docs/files/90001_网关性能测试.md deleted file mode 100644 index 334299ec..00000000 --- a/doc/docs/files/90001_网关性能测试.md +++ /dev/null @@ -1,149 +0,0 @@ -# 网关性能测试 - -> 注意:记得关闭限流功能 - -**测试环境** - -- 测试工具:[wrk](https://github.com/wg/wrk),[安装教程](https://www.cnblogs.com/quanxiaoha/p/10661650.html) -- 服务器:CentOS7(虚拟机,宿主机:macbookpro),内存:2G,CPU:1,核数:2核 -- 运行环境:Java8、Mysql-5.7、Nacos-1.1.3 -- 网关启动参数: - -``` --verbose:gc -XX:+PrintGCDetails -XX:+PrintHeapAtGC -Xloggc:gc-zuul.log \ - -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=128m -Xms1024m -Xmx1024m -Xmn512m -Xss256k -XX:SurvivorRatio=8 \ - -XX:+UseConcMarkSweepGC -``` - -- zuul配置(仅针对zuul,Spring Cloud Gateway没有做优化配置): - -```properties -# 不校验时间,这样一个链接可以一直进行测试 -sop.api-config.timeout-seconds=0 -sop.restful.enable=true - -logging.level.com.gitee=info - -# zuul调优 -zuul.host.max-per-route-connections=5000 -zuul.host.max-total-connections=5000 -zuul.semaphore.max-semaphores=5000 - -ribbon.ReadTimeout=5000 -hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=13000 - -logging.file=sop-gateway.log -``` - -CentOS允许最大连接数 -``` -$ ulimit -n -65535 -``` - -## 调用开放接口 - -- wrk命令: - -``` -wrk -t8 -c200 -d30s "http://10.1.31.227:8081/?charset=utf-8&biz_content=%7B%22name%22%3A%22%E8%91%AB%E8%8A%A6%E5%A8%83%22%2C%22id%22%3A%221%22%7D&method=alipay.story.get&format=json&sign=RjK%2FThnzAJQOw%2BfoVLS18PgWZAR%2B25SI2XdONFhS%2BmS8vsv2jNT3rygFoh%2ByX1AJbMgIEfcBzkQyqrs29jjd5dcwHVkcf7vxXshyfcEgl0fbMF6Ihycnz7rqSqkW3lzAWx4NuWUfkPnTX8Ffuf%2BhYRaI0NCpNv%2FV300HvsvmUjS6ZzS4YHaT1peSq0agfUhwRPd97aYMnUwRZDzxNfc5wuXA7OQ1o%2FPYIuIb%2FajVfwNP5ysitc%2FKtYEqt9rNAuzkcFmsw71d2RRnrPLsDN%2BuBXnIEh482f%2FbMj2Rj4%2FMq%2B0PEtlTRbg3rYnxyfowymfX%2BNmI4gNRUt70D4a%2FL3Qiug%3D%3D&app_id=2019032617262200001&sign_type=RSA2&version=1.0×tamp=2020-01-19+13%3A34%3A12" -``` - -- Spring Cloud Gateway测试结果 - -``` - 8 threads and 200 connections - Thread Stats Avg Stdev Max +/- Stdev - Latency 139.74ms 69.39ms 617.14ms 69.82% - Req/Sec 182.12 55.74 343.00 66.24% - 43391 requests in 30.09s, 11.96MB read -Requests/sec: 1441.96 -Transfer/sec: 406.96KB -``` - -结果说明,下同: - -``` - 8 threads and 200 connections (共8个测试线程,200个连接) - (平均值) (标准差) (最大值)(正负一个标准差所占比例) - Thread Stats Avg Stdev Max +/- Stdev - (延迟) - Latency 139.74ms 69.39ms 617.14ms 69.82% - (每秒请求数) - Req/Sec 182.12 55.74 343.00 66.24% - 43391 requests in 30.09s, 11.96MB read (30.09秒内处理了43391个请求,耗费流量11.96MB) -Requests/sec: 1441.96 (QPS 1441.96,即平均每秒处理请求数为1441.96) -Transfer/sec: 406.96KB (平均每秒流量406.96KB) -``` - -- Spring Cloud Zuul测试结果 - -``` - 8 threads and 200 connections - Thread Stats Avg Stdev Max +/- Stdev - Latency 230.14ms 331.27ms 2.00s 86.98% - Req/Sec 141.69 51.04 323.00 66.99% - 33945 requests in 30.09s, 9.88MB read - Socket errors: connect 0, read 0, write 0, timeout 385 -Requests/sec: 1128.05 -Transfer/sec: 336.15KB -``` - -## 调用restful请求 - -- wrk命令: - -``` -wrk -t8 -c200 -d30s "http://10.1.31.227:8081/rest/story-service/food/getFoodById?id=2" -``` - -线程数为 8,模拟 200 个并发请求,持续 30 秒 - -- Spring Cloud Gateway测试结果 - -``` - 8 threads and 200 connections - Thread Stats Avg Stdev Max +/- Stdev - Latency 120.14ms 58.30ms 513.85ms 67.47% - Req/Sec 210.47 54.26 770.00 69.37% - 50301 requests in 30.10s, 7.53MB read -Requests/sec: 1670.97 -Transfer/sec: 256.21KB -``` - - -- Spring Cloud Zuul测试结果 - -``` - 8 threads and 200 connections - Thread Stats Avg Stdev Max +/- Stdev - Latency 185.86ms 285.65ms 1.99s 88.55% - Req/Sec 167.75 55.60 460.00 68.05% - 40070 requests in 30.09s, 6.65MB read - Socket errors: connect 0, read 0, write 0, timeout 466 -Requests/sec: 1331.81 -Transfer/sec: 226.50KB -``` - - -综上所述,Spring Cloud Gateway在没有优化的情况下,压测表现比zuul好,但zuul的数据表现也不差,但是出现超时现象,总的来说还是Spring Cloud Gateway具有优势。 - -附启动脚本: - -`restart.sh` - -```bash -echo "Stopping sop-gateway-4.2.4-SNAPSHOT.jar" -pid=`ps -ef | grep sop-gateway-4.2.4-SNAPSHOT.jar | grep -v grep | awk '{print $2}'` -if [ -n "$pid" ] -then - echo "kill -9 的id:" $pid - kill -9 $pid -fi -nohup java -jar -verbose:gc -XX:+PrintGCDetails -XX:+PrintHeapAtGC -Xloggc:gc.log \ - -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=128m -Xms1024m -Xmx1024m -Xmn256m -Xss256k -XX:SurvivorRatio=8\ - -XX:+UseConcMarkSweepGC sop-gateway-4.2.4-SNAPSHOT.jar\ - --spring.profiles.active=dev --server.port=8081 & - -tail -f nohup.out -``` \ No newline at end of file diff --git a/doc/docs/files/90011_原理分析之如何存储路由.md b/doc/docs/files/90011_原理分析之如何存储路由.md deleted file mode 100644 index 575e5f82..00000000 --- a/doc/docs/files/90011_原理分析之如何存储路由.md +++ /dev/null @@ -1,46 +0,0 @@ -# 原理分析之如何存储路由 - -SOP基于spring cloud,因此会涉及到网关路由。但是开发者不用去配置文件定义路由的隐射关系,SOP帮你解决了这个问题。 - -## 获取路由信息 - -网关启动成后会触发一个事件,代码见:`com.gitee.sop.gatewaycommon.config.AbstractConfiguration.listenEvent` - -这个事件会取拉取微服务中提供的路由信息 - -下面以nacos为例,介绍拉取路由过程 - -1.从nacos中获取微服务实例 - -入口代码:`com.gitee.sop.bridge.route.NacosRegistryListener.onEvent` - -2.拿到微服务信息,调用微服务提供的接口拉取路由数据 - -入口代码:`com.gitee.sop.gatewaycommon.route.BaseRegistryListener.pullRoutes` - -最终落实到:`com.gitee.sop.gatewaycommon.route.ServiceRouteListener.onAddInstance` - -微服务提供一个url:`http://ip:port/sop/routes`,对应Controller在:`com.gitee.sop.servercommon.route.ServiceRouteController.listRoutes` - -微服务找到被`@Open`注解的方法,然后封装成一个路由对象,放到List中,最后返回给网关。 - -3.网关拿到路由信息,经过处理,转化成网关路由配置 - -关联方法: - -`com.gitee.sop.gatewaycommon.gateway.route.GatewayRouteCache.add` - -`com.gitee.sop.gatewaycommon.gateway.route.GatewayRouteCache.refresh` - - -路由的存储方式是一个Map,key为路由id,即接口名+版本号。 - -```java -/** - * key:nameVersion - */ -private static final Map routes = synchronizedMap(new LinkedHashMap<>()); -``` - -因为客户端调用接口都会传递一个接口名和版本号,因此通过这两个字段能够很快查询出路由信息,然后进行路由转发操作。 - diff --git a/doc/docs/files/90012_原理分析之如何路由.md b/doc/docs/files/90012_原理分析之如何路由.md deleted file mode 100644 index da7d95be..00000000 --- a/doc/docs/files/90012_原理分析之如何路由.md +++ /dev/null @@ -1,33 +0,0 @@ -# 原理分析之如何路由 - -Spring Cloud Gateway通过一系列的Filter来进行数据的传输,如下图所示: - -![流程图](images/90012_1.png) - -SOP网关在此基础上新增了几个Filter用来处理自己的逻辑,如:前置校验、结果返回。 - -| 过滤器 | 类型 | Order | 功能 | -| ----- | ---- | ----------------------- | ---------------------------- | -|IndexFilter| `自定义` | -2147483648 | 入口过滤器,获取参数、签名校验 | -|ParameterFormatterFilter | `自定义` | -2147482647 | 格式化参数 | -|LimitFilter|`自定义`|-2147482447|限流| -|ForwardPathFilter|系统自带|0 |设置转发的path| -|RouteToRequestUrlFilter|系统自带|10000|设置转发host| -|SopLoadBalancerClientFilter|`自定义`|10100|LoadBalance获取转发实例| -|NettyRoutingFilter|系统自带|2147483647|获取httpclient发送请求| -|ForwardRoutingFilter|系统自带|2147483647|请求分发| -|GatewayModifyResponseGatewayFilter|`自定义`|-2|处理响应结果| - -一个完整的请求会自上而下经过这些Filter,下面讲解如何动态设置路由。 - -## 动态设置路由 - -网关启动后会从注册中心拉取微服务实例,然后请求微服务提供的一个接口(`/sop/routes`),获取开放接口信息(被`@Open`注解的接口)。 - -监听处理类在:`com.gitee.sop.bridge.route.NacosRegistryListener` - -获取到路由信息后,将路由信息缓存到本地,并保存到数据库,代码在:`com.gitee.sop.gatewaycommon.gateway.route.GatewayRouteCache.load` - -然后动态设置Gateway路由,代码在:`com.gitee.sop.gatewaycommon.gateway.route.GatewayRouteRepository.refresh` - -当有微服务重新启动时,网关会监听到微服务实例有变更,会重复上述步骤,确保网关存有最新的路由。 diff --git a/doc/docs/files/90013_原理分析之文档归纳.md b/doc/docs/files/90013_原理分析之文档归纳.md deleted file mode 100644 index b782dd8e..00000000 --- a/doc/docs/files/90013_原理分析之文档归纳.md +++ /dev/null @@ -1,26 +0,0 @@ -# 原理分析之文档归纳 - -作为开放平台,必须要提供API文档。 - -SOP采用微服务架构实现,因此文档应该由各个微服务各自实现。难点是如何归纳各个微服务端提供的文档信息,并统一展示。 - -SOP的解决思路如下: - -- 各微服务使用swagger定义自己的接口信息 -- sop-website项目在启动时向注册中心获取所有服务实例,分别调用各个服务提供的swagger文档信息,保存到本地 -- sop-website前端页面负责展示swagger提供的文档信息 - -由于注册中心的存在,可以很方便的获取每个微服务提供的接口,因此可以获取到swagger提供的文档信息。 - -如此一来的好处是,各微服务不用关心文档该怎么展示,只需要写好swagger注解即可;文档信息展示统一交给另外一个工程来维护,各司其职。 - -SOP设计初衷亦是如此,微服务只管写业务代码,其它的都交给SOP来处理。 - -文档归纳原理图: - -![文档归纳原理图](images/90013_1.png "10090_1.png") - -- sop-website服务启动时向各微服务获取接口信息,保存到本地 -- 用户访问website页面,website提供对应的接口文档并展示 - - diff --git a/doc/docs/files/90014_原理分析之预发布灰度发布.md b/doc/docs/files/90014_原理分析之预发布灰度发布.md deleted file mode 100644 index 04bc5350..00000000 --- a/doc/docs/files/90014_原理分析之预发布灰度发布.md +++ /dev/null @@ -1,92 +0,0 @@ -# 原理分析之预发布灰度发布 - -SOP网关采用`自定义负载均衡策略`来实现对预发布/灰度发布服务器实例的选择。 - -spring cloud gateway默认的负载均衡实现类在:`org.springframework.cloud.gateway.filter.LoadBalancerClientFilter.java`中 - -这个类主要做了几件事情: - -1. 解析出请求路径中的scheme -2. 如果scheme不是以`lb`协议开头直接跳过 -3. 如果scheme以`lb`协议开头,则说明需要进行负载均衡,选出一台微服务实例 -4. 将`lb`协议解析成`http://ip:port`,继续向下请求 - -其中第4步是由`org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient`来完成的, -我们只要分别继承`LoadBalancerClientFilter`和`RibbonLoadBalancerClient`,然后重写其中的方法就能完成自定义负载均衡。 - -SOP中的重写类是:`SopLoadBalancerClientFilter`和`SopLoadBalancerClient`,核心代码委托给了`LoadBalanceServerChooser`处理,核心代码如下: - -```java - /** - * 选择服务器 - * - * @param serviceId serviceId,仅gateway网关有作用 - * @param exchange 请求上下文 - * @param loadBalancer loadBalancer - * @param superChooser 父类默认的选择 - * @param serverChooserFunction 执行选择操作 - * @return 返回服务器实例,没有选到则返回null - */ - public R choose( - String serviceId - , T exchange - , ILoadBalancer loadBalancer - , Supplier superChooser - , Function, R> serverChooserFunction) { - // 获取所有服务实例 - List servers = loadBalancer.getReachableServers(); - - // 存放预发服务器 - List preServers = new ArrayList<>(4); - // 存放灰度发布服务器 - List grayServers = new ArrayList<>(4); - // 存放非预发服务器 - List notPreServers = new ArrayList<>(4); - - for (Server server : servers) { - // 获取实例metadata - Map metadata = getMetadata(serviceId, server); - // 是否开启了预发模式 - if (this.isPreServer(metadata)) { - preServers.add(server); - } else if (this.isGrayServer(metadata)) { - grayServers.add(server); - } else { - notPreServers.add(server); - } - } - notPreServers.addAll(grayServers); - // 如果没有开启预发布服务和灰度发布,直接用默认的方式 - if (preServers.isEmpty() && grayServers.isEmpty()) { - return superChooser.get(); - } - // 如果是从预发布域名访问过来,则认为是预发布请求,选出预发服务器 - if (this.isRequestFromPreDomain(exchange)) { - return serverChooserFunction.apply(preServers); - } - // 如果是灰度请求,则认为是灰度用户,选出灰度服务器 - if (this.isRequestGrayServer(exchange)) { - return serverChooserFunction.apply(grayServers); - } - - // 到这里说明不能访问预发/灰度服务器,则需要路由到非预发服务器 - // 注意:这里允许走灰度服务器,如果不允许走,注释notPreServers.addAll(grayServers);这行 - return serverChooserFunction.apply(notPreServers); - } -``` - -其业务逻辑如下: - -1. 选出`serviceId`对应的所有服务器实例 -2. 将服务器实例进行分类,分别放进`预发布List`,`灰度List`,`非预发布List`中 -3. 如果`预发布List`,`灰度List`都为空,表示没有开启任何预发/灰度服务,直接使用父类的负载均衡策略 -4. 如果是从预发布域名访问过来,则认为是预发布请求,选出预发服务器 -5. 如果是灰度请求,则认为是灰度用户,选出灰度服务器 -6. 最后剩下的是正常用户,正常用户不能走预发环境 - -## 参考类 - -- com.gitee.sop.gatewaycommon.gateway.filter.SopLoadBalancerClientFilter -- com.gitee.sop.gatewaycommon.gateway.loadbalancer.SopLoadBalancerClient -- com.gitee.sop.gatewaycommon.gateway.loadbalancer.GatewayLoadBalanceServerChooser - diff --git a/doc/docs/files/90100_常见问题.md b/doc/docs/files/90100_常见问题.md deleted file mode 100644 index 917a2974..00000000 --- a/doc/docs/files/90100_常见问题.md +++ /dev/null @@ -1,125 +0,0 @@ -# 常见问题 - -## Connection has been closed BEFORE send operation - -压测时出现`Connection has been closed BEFORE send operation`,`java.nio.channels.ClosedChannelException`之类的错误 - -- 解决办法 - -尝试调大内存分配1G或2G,`-Xms1g -Xmx1g` - -## spring cloud gateway [DataBufferLimitException: Exceeded limit on max bytes to buffer : 262144] - -如果POST请求body内容太大可能会报这个错误 - -- 解决版办法 - -网关指定配置`spring.codec.max-in-memory-size=xx`,xx默认是262144,即256K(262144=256*1024) - -## primordials is not defined - -node版本太高导致与gulp版本不兼容,解决方法:node退回11版本。参考https://blog.csdn.net/zxxzxx23/article/details/103000393 - -## Nacos指定group - -可在配置文件中添加:`spring.cloud.nacos.discovery.group=xxx`指定group,不加默认是:DEFAULT_GROUP - -## 在SpringCloudGateway中获取请求参数 - -```java -ApiParam apiParam = ServerWebExchangeUtil.getApiParam(exchange); -``` - -## 微服务端如何获取appId等参数 - -```java -OpenContext openContext = ServiceContext.getCurrentContext().getOpenContext(); -System.out.println("app_id:" + openContext.getAppId()); -System.out.println("token:" + openContext.getAppAuthToken()); -``` - - -## 如何关闭签名验证 - -- 针对某一个接口关闭签名验证 -`@Open(value = "alipay.story.get", ignoreValidate = true)` - - -## 注册到eureka显示hostname,非ip - -```properties -eureka.instance.prefer-ip-address=true -eureka.instance.instance-id=${spring.cloud.client.ip-address}:${server.port} -``` - -```xml - - org.springframework.cloud - spring-cloud-commons - -``` - -参考:https://www.jianshu.com/p/5ad8317961b7 - -## 直接访问服务的swagger-ui.html ,提示access forbidden - -找到微服务的`OpenServiceConfig.java`,重写内部类Swagger2中的swaggerAccessProtected()方法,返回false。线上请设置成true - -```java -// 开启文档 -@Configuration -@EnableSwagger2 -public static class Swagger2 extends SwaggerSupport { - @Override - protected String getDocTitle() { - return "故事API"; - } - - @Override - protected boolean swaggerAccessProtected() { - return false; - } -} -``` - -## 调试网关出现服务不可用 - -打断点调试,网关出现Read Timeout - -参考:https://blog.csdn.net/qq_36872046/article/details/81058045 - -yml添加: - -```properties -# https://blog.csdn.net/qq_36872046/article/details/81058045 -# 路由转发超时时间,毫秒,默认值1000,详见:RibbonClientConfiguration.DEFAULT_READ_TIMEOUT。 -# 如果微服务端 处理时间过长,会导致ribbon read超时,解决办法将这个值调大一点 -ribbon.ReadTimeout= 60000 -``` - - -## 其它微服务没有开放接口,需要排除 - -在sop-gateway项目中配置 - -```properties -# 排除服务,多个用,隔开 -sop.service.exclude=your-serviceId1,your-serviceId2 -``` - -或者使用正则: - -```properties -# 排除以"test-"开头的 -# 多个正则用英文分号(;)隔开 -sop.service.exclude-regex=test\\-.* -``` - -## ISV公私钥 & 平台公私钥 - -```java -ISV私钥(必须):ISV保存,用来生成签名 --> ISV公钥(必须):平台保存,用来校验签名是否正确 -平台私钥(非必须):平台保存,对返回结果生成签名 --> 平台公钥(非必须):ISV保存,用来校验签名是否正确 -``` - -总结:私钥负责加密生成签名,公钥负责校验签名是否正确 diff --git a/doc/docs/files/images/10041_1.png b/doc/docs/files/images/10041_1.png deleted file mode 100644 index 406a760e..00000000 Binary files a/doc/docs/files/images/10041_1.png and /dev/null differ diff --git a/doc/docs/files/images/10041_2.png b/doc/docs/files/images/10041_2.png deleted file mode 100644 index 49885e16..00000000 Binary files a/doc/docs/files/images/10041_2.png and /dev/null differ diff --git a/doc/docs/files/images/10041_3.png b/doc/docs/files/images/10041_3.png deleted file mode 100644 index 7dffd9b7..00000000 Binary files a/doc/docs/files/images/10041_3.png and /dev/null differ diff --git a/doc/docs/files/images/10041_4.png b/doc/docs/files/images/10041_4.png deleted file mode 100644 index 89f55a40..00000000 Binary files a/doc/docs/files/images/10041_4.png and /dev/null differ diff --git a/doc/docs/files/images/10085_1.png b/doc/docs/files/images/10085_1.png deleted file mode 100644 index 83ebecb7..00000000 Binary files a/doc/docs/files/images/10085_1.png and /dev/null differ diff --git a/doc/docs/files/images/10085_2.png b/doc/docs/files/images/10085_2.png deleted file mode 100644 index ab7fd094..00000000 Binary files a/doc/docs/files/images/10085_2.png and /dev/null differ diff --git a/doc/docs/files/images/10086_1.png b/doc/docs/files/images/10086_1.png deleted file mode 100644 index d5da23b6..00000000 Binary files a/doc/docs/files/images/10086_1.png and /dev/null differ diff --git a/doc/docs/files/images/10086_2.png b/doc/docs/files/images/10086_2.png deleted file mode 100644 index de61367b..00000000 Binary files a/doc/docs/files/images/10086_2.png and /dev/null differ diff --git a/doc/docs/files/images/10090_1.png b/doc/docs/files/images/10090_1.png deleted file mode 100644 index db577156..00000000 Binary files a/doc/docs/files/images/10090_1.png and /dev/null differ diff --git a/doc/docs/files/images/10090_2.png b/doc/docs/files/images/10090_2.png deleted file mode 100644 index 3ec5b9b8..00000000 Binary files a/doc/docs/files/images/10090_2.png and /dev/null differ diff --git a/doc/docs/files/images/10092_1.png b/doc/docs/files/images/10092_1.png deleted file mode 100644 index f8dc032f..00000000 Binary files a/doc/docs/files/images/10092_1.png and /dev/null differ diff --git a/doc/docs/files/images/10092_2.png b/doc/docs/files/images/10092_2.png deleted file mode 100644 index 7c00d359..00000000 Binary files a/doc/docs/files/images/10092_2.png and /dev/null differ diff --git a/doc/docs/files/images/10093_1.png b/doc/docs/files/images/10093_1.png deleted file mode 100644 index e9537dd0..00000000 Binary files a/doc/docs/files/images/10093_1.png and /dev/null differ diff --git a/doc/docs/files/images/10093_2.png b/doc/docs/files/images/10093_2.png deleted file mode 100644 index 5f52c578..00000000 Binary files a/doc/docs/files/images/10093_2.png and /dev/null differ diff --git a/doc/docs/files/images/10097_1.png b/doc/docs/files/images/10097_1.png deleted file mode 100644 index 6965e7e9..00000000 Binary files a/doc/docs/files/images/10097_1.png and /dev/null differ diff --git a/doc/docs/files/images/10109_1.png b/doc/docs/files/images/10109_1.png deleted file mode 100644 index 6dc46da9..00000000 Binary files a/doc/docs/files/images/10109_1.png and /dev/null differ diff --git a/doc/docs/files/images/90012_1.png b/doc/docs/files/images/90012_1.png deleted file mode 100644 index 1f94dca9..00000000 Binary files a/doc/docs/files/images/90012_1.png and /dev/null differ diff --git a/doc/docs/files/images/90013_1.png b/doc/docs/files/images/90013_1.png deleted file mode 100644 index aac955d9..00000000 Binary files a/doc/docs/files/images/90013_1.png and /dev/null differ diff --git a/doc/docs/index.html b/doc/docs/index.html deleted file mode 100644 index 951b1933..00000000 --- a/doc/docs/index.html +++ /dev/null @@ -1,68 +0,0 @@ - - - - - - SOP开发文档 - - - - - - - - - - - -
加载中 ...
- - - - - - - - - - - \ No newline at end of file diff --git a/doc/docs/请不要删除其它文件.txt b/doc/docs/请不要删除其它文件.txt deleted file mode 100644 index e69de29b..00000000 diff --git a/doc/pom.xml b/doc/pom.xml deleted file mode 100644 index bae1abe3..00000000 --- a/doc/pom.xml +++ /dev/null @@ -1,49 +0,0 @@ - - 4.0.0 - com.gitee.sop - doc - 4.4.2-SNAPSHOT - - - - 1.8 - UTF-8 - UTF-8 - - - - - junit - junit - 4.8 - test - - - - - - - - - org.apache.maven.plugins - maven-surefire-plugin - 2.18.1 - - true - - - - org.apache.maven.plugins - maven-compiler-plugin - 2.3.2 - - ${java.version} - ${java.version} - ${project.build.sourceEncoding} - - - - - - diff --git a/doc/run.sh b/doc/run.sh deleted file mode 100644 index 8354ccf3..00000000 --- a/doc/run.sh +++ /dev/null @@ -1 +0,0 @@ -docsify serve docs diff --git a/doc/server.js b/doc/server.js deleted file mode 100644 index 785e4575..00000000 --- a/doc/server.js +++ /dev/null @@ -1,55 +0,0 @@ -const liveServer = require('live-server') -const isSSR = !!process.env.SSR -const middleware = [] - -if (isSSR) { - const Renderer = require('./packages/docsify-server-renderer/build.js') - const renderer = new Renderer({ - template: ` - - - - - docsify - - - - - - - - - `, - config: { - name: 'docsify', - repo: 'docsifyjs/docsify', - basePath: 'https://docsify.js.org/', - loadNavbar: true, - loadSidebar: true, - subMaxLevel: 3, - auto2top: true, - alias: { - '/de-de/changelog': '/changelog', - '/zh-cn/changelog': '/changelog', - '/changelog': - 'https://raw.githubusercontent.com/docsifyjs/docsify/master/CHANGELOG' - } - }, - path: './' - }) - - middleware.push(function(req, res, next) { - if (/\.(css|js)$/.test(req.url)) { - return next() - } - renderer.renderToString(req.url).then(html => res.end(html)) - }) -} - -const params = { - port: 3000, - watch: ['lib', 'docs', 'themes'], - middleware -} - -liveServer.start(params) diff --git a/doc/src/main/java/com/gitee/sop/doc/SidebarTest.java b/doc/src/main/java/com/gitee/sop/doc/SidebarTest.java deleted file mode 100644 index 4554af94..00000000 --- a/doc/src/main/java/com/gitee/sop/doc/SidebarTest.java +++ /dev/null @@ -1,99 +0,0 @@ -package com.gitee.sop.doc; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.util.Comparator; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -/** - * 生成_sidebar.md文件,直接运行即可 - * - * @author tanghc - */ -public class SidebarTest { - - static String format = " * [%s](files/%s?t=%s)\r\n"; - static Map levelMap = new HashMap<>(8); - - static { - int i = 0; - levelMap.put("1", new Menu("* 开发文档\n", i++)); - levelMap.put("9", new Menu("* 原理分析\n", i++)); - } - - public static void main(String[] args) throws Exception { - String path = SidebarTest.class.getClassLoader().getResource("").getPath(); - String root = path.substring(0, path.indexOf("doc")) + "doc"; - String fileDir = root + "/docs/files"; - File dir = new File(fileDir); - File[] files = dir.listFiles(); - Stream filesStream = Stream.of(files); - Map> menuMap = filesStream - .sorted(Comparator.comparing(File::getName)) - .map(file -> { - if (file.isDirectory()) { - return null; - } - FileExt fileExt = new FileExt(); - fileExt.menu = file.getName().substring(0, 1); - fileExt.file = file; - return fileExt; - }) - .filter(Objects::nonNull) - .collect(Collectors.groupingBy(FileExt::getMenu)); - - StringBuilder output = new StringBuilder(); - output.append("* [首页](/?t=" + System.currentTimeMillis() + ")\n"); - for (Map.Entry> entry : menuMap.entrySet()) { - Menu menu = levelMap.get(entry.getKey()); - if (menu == null) { - continue; - } - output.append(menu.parentName); - for (FileExt fileExt : entry.getValue()) { - String filename = fileExt.file.getName(); - String title = filename.substring(filename.indexOf("_") + 1, filename.length() - 3); - String line = String.format(format, title, filename, System.currentTimeMillis()); - output.append(line); - } - } - - System.out.println(output); - - String sidebarFilepath = root + "/docs/_sidebar.md"; - - FileOutputStream out = new FileOutputStream(new File(sidebarFilepath)); - out.write(output.toString().getBytes()); - out.close(); - } - - static class Menu { - String parentName; - int order; - - public Menu(String parentName, int order) { - this.parentName = parentName; - this.order = order; - } - - } - - static class FileExt { - File file; - String menu; - - public File getFile() { - return file; - } - - public String getMenu() { - return menu; - } - } -} diff --git a/docker-build.sh b/docker-build.sh old mode 100644 new mode 100755 diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh old mode 100644 new mode 100755 index e0b60768..6ae17cb7 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -3,11 +3,9 @@ JAVA_OPTS="-Xms128m -Xmx128m" # mysql, nacos配置 -args="--mysql.host=10.1.30.110:3306 --mysql.username=root --mysql.password=root --register.url=10.1.30.110:8848" +args="--mysql.host=10.1.30.110:3306 --mysql.username=root --mysql.password=root --dubbo.registry.address=10.1.30.110:8848" java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /sop/sop-gateway/sop-gateway.jar $args --logging.file.path=/sop/sop-gateway/log & java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /sop/sop-admin/sop-admin.jar $args --logging.file.path=/sop/sop-admin/log & -java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /sop/sop-website/sop-website.jar $args --logging.file.path=/sop/sop-website/log & -java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /sop/sop-auth/sop-auth.jar $args --logging.file.path=/sop/sop-auth/log & # 最后一条没有& -java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /sop/sop-story/sop-story.jar $args --logging.file.path=/sop/sop-story/log \ No newline at end of file +java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /sop/sop-story/sop-story.jar $args --logging.file.path=/sop/sop-story/log diff --git a/pom.xml b/pom.xml old mode 100644 new mode 100755 index 1f43e5a0..66517aab --- a/pom.xml +++ b/pom.xml @@ -11,28 +11,25 @@ 4.0.0 com.gitee.sop sop-parent - 4.4.2-SNAPSHOT + 5.0.0-SNAPSHOT pom - 一个开放平台解决方案项目,基于Spring Cloud实现,目标是能够让用户快速得搭建起自己的开放平台 + 一个开放平台解决方案项目,基于Dubbo实现,目标是能够让用户快速得搭建起自己的开放平台 - doc - sop-common - sop-auth sop-example sop-admin - sop-gateway + sop-website sop-test sop-sdk - sop-website + sop-gateway + sop-registry + sop-support - 1.8 - + 8 + 8 UTF-8 - 1.8 - 1.8 2.6.15 @@ -41,15 +38,10 @@ 2021.0.5.0 + + 3.2.10 - - - 1.2.3 - - 4.11 - - 1.2.73 2.5 1.3.3 3.2.2 @@ -58,15 +50,7 @@ 1.2 2.0.1.Final 6.0.13.Final - 2.4.8 - 3.5.3.1 - 29.0-jre - 3.0.2 - 1.5.21 - 3.0.0 - 1.16.9 - 6.2 - 5.2.0 + 3.0.12 @@ -93,60 +77,30 @@ pom import - - com.google.guava - guava - ${guava.version} + org.apache.dubbo + dubbo-bom + ${dubbo.version} + pom + import - com.alibaba - fastjson - ${fastjson.version} - - - - net.oschina.durcframework + io.gitee.durcframework fastmybatis-spring-boot-starter ${fastmybatis.version} - - com.baomidou - mybatis-plus-boot-starter - ${mybatis-plus.version} - - - io.springfox - springfox-swagger2 - ${springfox.version} - - - io.springfox - springfox-spring-web - ${springfox.version} - - - io.springfox - springfox-swagger-ui - ${springfox.version} - - - io.springfox - springfox-boot-starter - ${springfox.version} - - - - com.github.xiaoymin - knife4j-spring-boot-starter - ${knife4j.version} - net.oschina.durcframework - easyopen - ${easyopen.version} + http-helper + 1.0.0 + + + + com.alibaba.fastjson2 + fastjson2 + 2.0.52 @@ -155,12 +109,6 @@ 3.14.7 - - net.oschina.durcframework - easyopen-spring-boot-starter - ${easyopen.version} - - javax.validation validation-api @@ -171,11 +119,20 @@ hibernate-validator ${hibernate-validator.version} - - ch.qos.logback - logback-classic - ${logback.version} + com.auth0 + java-jwt + 3.2.0 + + + com.alibaba + transmittable-thread-local + 2.14.5 + + + com.google.guava + guava + 32.1.3-jre @@ -210,22 +167,10 @@ ${commons-logging.version} - - org.ow2.asm - asm - ${asm.version} - - - - com.github.pagehelper - pagehelper - ${pagehelper.version} - - org.projectlombok lombok - 1.18.4 + 1.18.34 javax.servlet @@ -264,6 +209,26 @@ UTF-8 + + org.apache.maven.plugins + maven-checkstyle-plugin + 3.3.1 + + checkstyle.xml + true + true + false + + + + validate + validate + + check + + + + diff --git a/sop-admin/pom.xml b/sop-admin/pom.xml old mode 100644 new mode 100755 index 79208df3..b98d40ee --- a/sop-admin/pom.xml +++ b/sop-admin/pom.xml @@ -3,18 +3,16 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + 4.0.0 com.gitee.sop sop-parent - 4.4.2-SNAPSHOT - ../pom.xml + 5.0.0-SNAPSHOT - - 4.0.0 sop-admin pom - sop-admin-server + sop-admin-backend - \ No newline at end of file + diff --git a/sop-admin/readme.md b/sop-admin/readme.md old mode 100644 new mode 100755 index ed194d02..6410dedb --- a/sop-admin/readme.md +++ b/sop-admin/readme.md @@ -1,5 +1,5 @@ # 后台admin -- sop-admin-server: admin服务端,使用方式见readme.md -- sop-admin-vue: admin前端vue实现 +- sop-admin-backend: admin服务端 +- sop-admin-frontend: admin前端实现 diff --git a/sop-admin/sop-admin-backend/admin-boot/pom.xml b/sop-admin/sop-admin-backend/admin-boot/pom.xml new file mode 100755 index 00000000..74c09825 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-boot/pom.xml @@ -0,0 +1,141 @@ + + + 4.0.0 + + com.gitee.sop + sop-admin-backend + 5.0.0-SNAPSHOT + + + admin-boot + + + 8 + 8 + UTF-8 + + + + + com.gitee.sop + admin-web + 5.0.0-SNAPSHOT + + + org.springframework.boot + spring-boot-starter + + + + org.apache.dubbo + dubbo-spring-boot-starter + + + + + org.apache.dubbo + dubbo-nacos-spring-boot-starter + + + + org.springframework.boot + spring-boot-starter-data-redis + + + + org.springframework.boot + spring-boot-starter-jdbc + + + + mysql + mysql-connector-java + + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.apache.dubbo + dubbo-dependencies-zookeeper-curator5 + ${dubbo.version} + pom + provided + + + + org.projectlombok + lombok + true + + + + + + + + aliyun + aliyun + https://maven.aliyun.com/repository/public + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.12.4 + + true + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + ${java.version} + ${java.version} + UTF-8 + + + + org.apache.maven.plugins + maven-checkstyle-plugin + 3.3.1 + + checkstyle.xml + true + true + false + + + + validate + validate + + check + + + + + + + + + diff --git a/sop-admin/sop-admin-backend/admin-boot/src/main/java/com/gitee/sop/admin/SopAdminApplication.java b/sop-admin/sop-admin-backend/admin-boot/src/main/java/com/gitee/sop/admin/SopAdminApplication.java new file mode 100755 index 00000000..616417d3 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-boot/src/main/java/com/gitee/sop/admin/SopAdminApplication.java @@ -0,0 +1,15 @@ +package com.gitee.sop.admin; + +import org.apache.dubbo.config.spring.context.annotation.EnableDubbo; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +@EnableDubbo +public class SopAdminApplication { + + public static void main(String[] args) { + SpringApplication.run(SopAdminApplication.class, args); + } + +} diff --git a/sop-admin/sop-admin-backend/admin-boot/src/main/java/com/gitee/sop/admin/config/AdminConfig.java b/sop-admin/sop-admin-backend/admin-boot/src/main/java/com/gitee/sop/admin/config/AdminConfig.java new file mode 100755 index 00000000..552bca33 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-boot/src/main/java/com/gitee/sop/admin/config/AdminConfig.java @@ -0,0 +1,17 @@ +package com.gitee.sop.admin.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +/** + * @author 六如 + */ +@Configuration +@ConfigurationProperties(prefix = "admin") +@Data +public class AdminConfig { + + private int jwtTimeoutDays; + +} diff --git a/sop-admin/sop-admin-backend/admin-boot/src/main/java/com/gitee/sop/admin/config/SopAdminConfiguration.java b/sop-admin/sop-admin-backend/admin-boot/src/main/java/com/gitee/sop/admin/config/SopAdminConfiguration.java new file mode 100755 index 00000000..c0a85998 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-boot/src/main/java/com/gitee/sop/admin/config/SopAdminConfiguration.java @@ -0,0 +1,106 @@ +package com.gitee.sop.admin.config; + +import com.gitee.sop.admin.common.context.SpringContext; +import com.gitee.sop.admin.common.util.SystemUtil; +import com.gitee.sop.admin.interceptor.LoginInterceptor; +import com.gitee.sop.admin.service.sys.UserCacheService; +import com.gitee.sop.admin.service.sys.impl.LocalUserCacheService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.util.StringUtils; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import org.springframework.web.filter.CorsFilter; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +/** + * @author 六如 + */ +@Configuration +@Slf4j +public class SopAdminConfiguration implements ApplicationContextAware, WebMvcConfigurer { + + @Value("${front-location:}") + private String frontLocation; + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + SpringContext.setApplicationContext(applicationContext); + } + + @Bean + @ConditionalOnProperty(value = "user.cache.type", havingValue = "local", matchIfMissing = true) + public UserCacheService userCacheService() { + return new LocalUserCacheService(); + } + + /** + * 配置拦截器 + * + * @param registry + */ + @Override + public void addInterceptors(InterceptorRegistry registry) { + String[] excludes = { + "/", "/error", + // 排除前端资源 + "/*.html", "/*.ico", "/*.png", "/*.json", "/static/**", "/assets/**" + }; + registry.addInterceptor(new LoginInterceptor()) + .excludePathPatterns(excludes); + } + + /** + * 跨域设置 + */ + @Bean + public CorsFilter corsFilter( + @Value("${torna.cors.allowed-origin-pattern:*}") String allowedOriginPattern, + @Value("${torna.cors.allowed-header:*}") String allowedHeader + ) { + CorsConfiguration corsConfiguration = new CorsConfiguration(); + // SpringBoot升级2.4.0之后,跨域配置中的.allowedOrigins不再可用,改成addAllowedOriginPattern + corsConfiguration.addAllowedOriginPattern(allowedOriginPattern); + corsConfiguration.addAllowedHeader(allowedHeader); + corsConfiguration.addAllowedMethod(CorsConfiguration.ALL); + corsConfiguration.addExposedHeader("Content-Disposition"); + corsConfiguration.setAllowCredentials(true); + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", corsConfiguration); + return new CorsFilter(source); + } + + + /** + * 配置静态资源 + * + * @param registry + */ + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) { + String homeDir = SystemUtil.getBinPath(); + String frontRoot; + if (StringUtils.hasText(frontLocation)) { + frontRoot = StringUtils.trimTrailingCharacter(frontLocation, '/'); + } else { + frontRoot = homeDir + "/dist"; + } + log.info("前端资源目录:{}", frontRoot); + String location = "file:" + frontRoot; + registry.addResourceHandler("/index.html").addResourceLocations(location + "/index.html"); + registry.addResourceHandler("/favicon.ico").addResourceLocations(location + "/favicon.ico"); + registry.addResourceHandler("/logo.png").addResourceLocations(location + "/logo.png"); + registry.addResourceHandler("/platform-config.json").addResourceLocations(location + "/platform-config.json"); + registry.addResourceHandler("/static/**").addResourceLocations(location + "/static/"); + registry.addResourceHandler("/assets/**").addResourceLocations(location + "/assets/"); + } + +} diff --git a/sop-admin/sop-admin-backend/admin-boot/src/main/java/com/gitee/sop/admin/interceptor/LoginInterceptor.java b/sop-admin/sop-admin-backend/admin-boot/src/main/java/com/gitee/sop/admin/interceptor/LoginInterceptor.java new file mode 100755 index 00000000..4c5beb9a --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-boot/src/main/java/com/gitee/sop/admin/interceptor/LoginInterceptor.java @@ -0,0 +1,45 @@ +package com.gitee.sop.admin.interceptor; + +import com.gitee.sop.admin.common.annotation.NoToken; +import com.gitee.sop.admin.common.enums.StatusEnum; +import com.gitee.sop.admin.common.user.User; +import com.gitee.sop.admin.common.context.UserContext; +import com.gitee.sop.admin.common.exception.LoginFailureException; +import com.gitee.sop.admin.common.util.RequestUtil; +import lombok.extern.slf4j.Slf4j; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.HandlerInterceptor; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * @author tanghc + */ +@Slf4j +public class LoginInterceptor implements HandlerInterceptor { + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + if (!(handler instanceof HandlerMethod)) { + return true; + } + HandlerMethod handlerMethod = (HandlerMethod) handler; + NoToken noLogin = handlerMethod.getMethodAnnotation(NoToken.class); + if (noLogin != null) { + return true; + } + noLogin = AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), NoToken.class); + if (noLogin != null) { + return true; + } + User user = UserContext.getUser(request); + if (user == null || StatusEnum.of(user.getStatus()) == StatusEnum.DISABLED) { + log.error("登录失败, 客户端ip:{}, uri:{}", RequestUtil.getIP(request), request.getRequestURI()); + throw new LoginFailureException("登录失败"); + } + return true; + } + +} diff --git a/sop-admin/sop-admin-backend/admin-boot/src/main/resources/application-dev.properties b/sop-admin/sop-admin-backend/admin-boot/src/main/resources/application-dev.properties new file mode 100755 index 00000000..0b2b9000 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-boot/src/main/resources/application-dev.properties @@ -0,0 +1,8 @@ +dubbo.registry.address=zookeeper://localhost:2181 + +mybatis.print-sql=true + +# mysql config +mysql.host=127.0.0.1:3306 +mysql.username=root +mysql.password=root diff --git a/sop-admin/sop-admin-backend/admin-boot/src/main/resources/application-test.properties b/sop-admin/sop-admin-backend/admin-boot/src/main/resources/application-test.properties new file mode 100755 index 00000000..bd1c17c0 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-boot/src/main/resources/application-test.properties @@ -0,0 +1,8 @@ +dubbo.registry.address=nacos://localhost:8848 + +mybatis.print-sql=true + +# mysql config +mysql.host=127.0.0.1:3306 +mysql.username=root +mysql.password=root diff --git a/sop-admin/sop-admin-backend/admin-boot/src/main/resources/application.properties b/sop-admin/sop-admin-backend/admin-boot/src/main/resources/application.properties new file mode 100755 index 00000000..dfa98f82 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-boot/src/main/resources/application.properties @@ -0,0 +1,48 @@ +server.port=8082 +spring.profiles.active=dev + +spring.application.name=sop-admin + +index.path=/ + +####### admin config ####### +# user cache +user.cache.type=local + +dubbo.protocol.name=dubbo +dubbo.protocol.port=-1 +dubbo.application.qos-enable=false +dubbo.consumer.check=false +# ### register config see:https://cn.dubbo.apache.org/zh-cn/overview/mannual/java-sdk/reference-manual/registry/overview/ +# ------ +# nacos://localhost:8848 Cluster config:nacos://localhost:8848?backup=localshot:8846,localshot:8847 +# zookeeper://localhost:2181 Cluster config:zookeeper://10.20.153.10:2181?backup=10.20.153.11:2181,10.20.153.12:2181 +# redis://localhost:6379 Cluster config:redis://10.20.153.10:6379?backup=10.20.153.11:6379,10.20.153.12:6379 +# ------ +dubbo.registry.address=zookeeper://localhost:2181 + +####### mysql config ####### +mysql.host=127.0.0.1:3306 +mysql.username= +mysql.password= +mysql.db=sop + +spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver +spring.datasource.url=jdbc:mysql://${mysql.host}/${mysql.db}?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai +spring.datasource.username=${mysql.username} +spring.datasource.password=${mysql.password} + +####### mybatis config ####### +mybatis.fill.com.gitee.fastmybatis.core.support.LocalDateTimeFillInsert=add_time +mybatis.fill.com.gitee.fastmybatis.core.support.LocalDateTimeFillUpdate=update_time +mybatis.fill.com.gitee.sop.admin.common.fill.AddByFill= +mybatis.fill.com.gitee.sop.admin.common.fill.UpdateByFill= +# mybatis config file +mybatis.config-location=classpath:mybatis/mybatisConfig.xml +mybatis.mapper-locations=classpath:mybatis/mapper/*.xml + + +# print SQL +logging.level.com.gitee.sop.admin.dao=error +logging.level.com.gitee.fastmybatis=info +mybatis.print-sql=false diff --git a/sop-admin/sop-admin-backend/admin-boot/src/test/java/com/gitee/sop/admin/BaseTest.java b/sop-admin/sop-admin-backend/admin-boot/src/test/java/com/gitee/sop/admin/BaseTest.java new file mode 100755 index 00000000..d5994028 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-boot/src/test/java/com/gitee/sop/admin/BaseTest.java @@ -0,0 +1,13 @@ +package com.gitee.sop.admin; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +public class BaseTest { + + @Test + void contextLoads() { + } + +} diff --git a/sop-admin/sop-admin-backend/admin-boot/src/test/java/com/gitee/sop/admin/service/LoginServiceTest.java b/sop-admin/sop-admin-backend/admin-boot/src/test/java/com/gitee/sop/admin/service/LoginServiceTest.java new file mode 100755 index 00000000..d03e397f --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-boot/src/test/java/com/gitee/sop/admin/service/LoginServiceTest.java @@ -0,0 +1,44 @@ +package com.gitee.sop.admin.service; + +import com.alibaba.fastjson2.JSON; +import com.gitee.sop.admin.BaseTest; +import com.gitee.sop.admin.service.sys.login.LoginService; +import com.gitee.sop.admin.service.sys.login.dto.LoginDTO; +import com.gitee.sop.admin.service.sys.login.dto.LoginUser; +import com.gitee.sop.admin.service.sys.login.enums.RegTypeEnum; +import org.springframework.beans.factory.annotation.Autowired; + +import org.apache.commons.codec.digest.DigestUtils; +import org.junit.jupiter.api.Test; +import org.springframework.security.crypto.bcrypt.BCrypt; +import org.springframework.util.Assert; + + +/** + * @author 六如 + */ +public class LoginServiceTest extends BaseTest { + + @Autowired + LoginService loginService; + + @Test + public void login() { + LoginDTO loginDTO = new LoginDTO(); + loginDTO.setUsername("admin"); + loginDTO.setPassword("123456"); + loginDTO.setRegType(RegTypeEnum.BACKEND); + LoginUser loginUser = loginService.login(loginDTO); + Assert.notNull(loginUser, "not null"); + System.out.println(JSON.toJSONString(loginUser)); + } + + @Test + public void resetAdminPwd() { + // 初始密码 + String defPassword = "123456"; + defPassword = DigestUtils.sha256Hex(defPassword); + String encodedPassword = BCrypt.hashpw(defPassword, BCrypt.gensalt()); + } + +} diff --git a/sop-admin/sop-admin-backend/admin-boot/src/test/java/com/gitee/sop/admin/service/PasswordTest.java b/sop-admin/sop-admin-backend/admin-boot/src/test/java/com/gitee/sop/admin/service/PasswordTest.java new file mode 100755 index 00000000..65d26cf4 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-boot/src/test/java/com/gitee/sop/admin/service/PasswordTest.java @@ -0,0 +1,36 @@ +package com.gitee.sop.admin.service; + +import com.gitee.sop.admin.BaseTest; +import com.gitee.sop.admin.dao.entity.SysUser; +import com.gitee.sop.admin.service.sys.SysUserService; +import org.apache.commons.codec.digest.DigestUtils; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.crypto.bcrypt.BCrypt; + + +/** + * @author 六如 + */ +public class PasswordTest extends BaseTest { + + @Autowired + SysUserService sysAdminUserService; + + /** + * 重置admin密码 + */ + @Test + public void resetAdminPwd() { + String username = "admin"; + String defPassword = "123456"; + defPassword = DigestUtils.sha256Hex(defPassword); + String encodedPassword = BCrypt.hashpw(defPassword, BCrypt.gensalt()); + System.out.println("数据库保存:" + encodedPassword); + sysAdminUserService.query() + .eq(SysUser::getUsername, username) + .set(SysUser::getPassword, encodedPassword) + .update(); + } + +} diff --git a/sop-admin/sop-admin-backend/admin-common/pom.xml b/sop-admin/sop-admin-backend/admin-common/pom.xml new file mode 100755 index 00000000..5baa8277 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-common/pom.xml @@ -0,0 +1,97 @@ + + + 4.0.0 + + com.gitee.sop + sop-admin-backend + 5.0.0-SNAPSHOT + + + admin-common + + + 8 + 8 + UTF-8 + + + + + com.gitee.sop + sop-service-support + 5.0.0-SNAPSHOT + + + + io.gitee.durcframework + fastmybatis-spring-boot-starter + + + + com.google.guava + guava + + + + + com.alibaba.fastjson2 + fastjson2 + + + + commons-codec + commons-codec + + + org.apache.commons + commons-lang3 + + + commons-fileupload + commons-fileupload + + + + org.hibernate + hibernate-validator + + + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + + + com.auth0 + java-jwt + + + + org.springframework.security + spring-security-crypto + + + + net.oschina.durcframework + http-helper + + + + org.springframework.boot + spring-boot-starter-web + + + + org.projectlombok + lombok + true + + + junit + junit + test + + + + diff --git a/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/annotation/NoToken.java b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/annotation/NoToken.java new file mode 100755 index 00000000..12f46067 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/annotation/NoToken.java @@ -0,0 +1,17 @@ +package com.gitee.sop.admin.common.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 不需要登录验证 + * @author tanghc + */ +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface NoToken { +} diff --git a/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/config/Configs.java b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/config/Configs.java new file mode 100755 index 00000000..e361911e --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/config/Configs.java @@ -0,0 +1,45 @@ +package com.gitee.sop.admin.common.config; + +import com.gitee.sop.admin.common.context.SpringContext; +import com.gitee.sop.admin.common.enums.ConfigKeyEnum; + +import java.util.function.Supplier; + +/** + * @author 六如 + */ +public class Configs { + + /** + * 获取配置参数 + * + * @param keyGetter 配置key + * @return 返回配参数,没有则返回null + */ + public static String getValue(ConfigKeyEnum keyGetter) { + return getValue(keyGetter, keyGetter.getDefaultValue()); + } + + /** + * 获取配置参数 + * + * @param keyGetter 配置key + * @param defaultValue 默认值 + * @return 返回配参数,没有则返回默认值 + */ + public static String getValue(ConfigKeyEnum keyGetter, String defaultValue) { + return SpringContext.getBean(IConfig.class).getConfig(keyGetter.getKey(), defaultValue); + } + + /** + * 获取配置参数 + * + * @param keyGetter 配置key + * @param defaultValue 默认值 + * @return 返回配参数,没有则返回默认值 + */ + public static String getValue(ConfigKeyEnum keyGetter, Supplier defaultValue) { + return getValue(keyGetter, defaultValue.get()); + } + +} diff --git a/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/config/IConfig.java b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/config/IConfig.java new file mode 100755 index 00000000..82322890 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/config/IConfig.java @@ -0,0 +1,9 @@ +package com.gitee.sop.admin.common.config; + +public interface IConfig { + + String getConfig(String key); + + String getConfig(String key, String defaultValue); + +} diff --git a/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/constants/YesOrNo.java b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/constants/YesOrNo.java new file mode 100755 index 00000000..058c0a5c --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/constants/YesOrNo.java @@ -0,0 +1,20 @@ +package com.gitee.sop.admin.common.constants; + +import java.util.Objects; + +/** + * @author 六如 + */ +public class YesOrNo { + public static final int YES = 1; + public static final int NO = 0; + + public static boolean yes(Number value) { + return value != null && value.intValue() == YES; + } + + public static int of(Boolean b) { + return Objects.equals(b, true) ? YES : NO; + } + +} diff --git a/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/context/SpringContext.java b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/context/SpringContext.java new file mode 100755 index 00000000..d59c896d --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/context/SpringContext.java @@ -0,0 +1,31 @@ +package com.gitee.sop.admin.common.context; + +import org.springframework.context.ApplicationContext; + +/** + * @author 六如 + */ +public class SpringContext { + + private static ApplicationContext ctx; + + public static T getBean(Class clazz) { + return ctx.getBean(clazz); + } + + public static Object getBean(String beanName) { + return ctx.getBean(beanName); + } + + public static void setApplicationContext(ApplicationContext ctx) { + SpringContext.ctx = ctx; + } + + public static ApplicationContext getApplicationContext() { + return ctx; + } + + public static void publishEvent(Object event) { + ctx.publishEvent(event); + } +} diff --git a/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/context/UserContext.java b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/context/UserContext.java new file mode 100755 index 00000000..aa67e2f1 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/context/UserContext.java @@ -0,0 +1,126 @@ +package com.gitee.sop.admin.common.context; + +import com.auth0.jwt.interfaces.Claim; +import com.gitee.sop.admin.common.manager.UserCacheManager; +import com.gitee.sop.admin.common.user.User; +import com.gitee.sop.admin.common.config.Configs; +import com.gitee.sop.admin.common.enums.ConfigKeyEnum; +import com.gitee.sop.admin.common.exception.ErrorTokenException; +import com.gitee.sop.admin.common.exception.JwtErrorException; +import com.gitee.sop.admin.common.exception.JwtExpiredException; +import com.gitee.sop.admin.common.exception.LoginFailureException; +import com.gitee.sop.admin.common.util.JwtUtil; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.math.NumberUtils; +import org.springframework.util.StringUtils; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import javax.servlet.http.HttpServletRequest; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; +import java.util.function.Supplier; + +/** + * @author tanghc + */ +@Slf4j +public class UserContext { + + public static final String HEADER_AUTHORIZATION = "Authorization"; + public static final String JWT_PREFIX = "Bearer "; + + + private static Supplier tokenGetter = () -> { + HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest(); + return getToken(request); + }; + + public static void setTokenGetter(Supplier tokenGetter) { + UserContext.tokenGetter = tokenGetter; + } + + /** + * 获取当前登录用户id + * @return 返回id,没有返回null + */ + public static Long getUserId() { + return Optional.ofNullable(getUser()).map(User::getUserId).orElse(null); + } + + /** + * 获取当前登录用户 + * + * @return 返回当前登录用户,没有返回null + */ + public static User getUser() { + String token = tokenGetter.get(); + try { + return getUser(token); + } catch (ErrorTokenException e) { + throw new LoginFailureException(); + } + } + + /** + * 获取当前登录用户 + * + * @return 返回当前登录用户,没有返回null + */ + public static User getUser(HttpServletRequest request) { + String token = getToken(request); + try { + return getUser(token); + } catch (ErrorTokenException e) { + throw new LoginFailureException(); + } + } + + public static String getToken(HttpServletRequest request) { + String token = request.getHeader(HEADER_AUTHORIZATION); + if (StringUtils.hasText(token) && token.startsWith(JWT_PREFIX)) { + return token.substring(JWT_PREFIX.length()); + } + return token; + } + + + /** + * 获取登录用户 + * + * @param token 格式:: + * @return 返回token对应的用户,没有返回null + */ + private static User getUser(String token) throws ErrorTokenException { + if (StringUtils.isEmpty(token)) { + return null; + } + String secret = Configs.getValue(ConfigKeyEnum.JWT_SECRET); + Map data; + // verify jwt + try { + data = JwtUtil.verifyJwt(token, secret); + } catch (JwtExpiredException | JwtErrorException e) { + log.error("jwt verify failed, token:{}, message:{}", token, e.getMessage(), e); + throw new ErrorTokenException(); + } + Claim id = data.get("id"); + long userId = NumberUtils.toLong(id.asString(), 0); + if (userId == 0) { + return null; + } + return SpringContext.getBean(UserCacheManager.class).getUser(userId); + } + + + @Deprecated + public static Locale getLocale() { + try { + HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest(); + return request.getLocale(); + } catch (Exception e) { + return Locale.SIMPLIFIED_CHINESE; + } + } +} diff --git a/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/dto/IdDTO.java b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/dto/IdDTO.java new file mode 100755 index 00000000..944562ec --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/dto/IdDTO.java @@ -0,0 +1,14 @@ +package com.gitee.sop.admin.common.dto; + +import lombok.Data; + +import javax.validation.constraints.NotNull; + +/** + * @author 六如 + */ +@Data +public class IdDTO { + @NotNull(message = "id不能为空") + private Long id; +} diff --git a/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/dto/IdsDTO.java b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/dto/IdsDTO.java new file mode 100755 index 00000000..f3e2816f --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/dto/IdsDTO.java @@ -0,0 +1,13 @@ +package com.gitee.sop.admin.common.dto; + +import lombok.Data; + +import java.util.List; + +/** + * @author 六如 + */ +@Data +public class IdsDTO { + private List ids; +} diff --git a/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/dto/StatusUpdateBatchDTO.java b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/dto/StatusUpdateBatchDTO.java new file mode 100755 index 00000000..d12eecc1 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/dto/StatusUpdateBatchDTO.java @@ -0,0 +1,16 @@ +package com.gitee.sop.admin.common.dto; + +import lombok.Data; + +import javax.validation.constraints.NotNull; + +/** + * @author 六如 + */ +@Data +public class StatusUpdateBatchDTO extends IdsDTO { + + @NotNull(message = "状态不能为空") + private Integer status; + +} diff --git a/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/dto/StatusUpdateDTO.java b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/dto/StatusUpdateDTO.java new file mode 100755 index 00000000..d71e9a0a --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/dto/StatusUpdateDTO.java @@ -0,0 +1,16 @@ +package com.gitee.sop.admin.common.dto; + +import lombok.Data; + +import javax.validation.constraints.NotNull; + +/** + * @author 六如 + */ +@Data +public class StatusUpdateDTO extends IdDTO { + + @NotNull(message = "状态不能为空") + private Integer status; + +} diff --git a/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/dto/UserDTO.java b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/dto/UserDTO.java new file mode 100755 index 00000000..b27578a3 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/dto/UserDTO.java @@ -0,0 +1,31 @@ +package com.gitee.sop.admin.common.dto; + +import lombok.Data; + +/** + * @author 六如 + */ +@Data +public class UserDTO { + + /** + * 创建人id + */ + private Long addBy; + + /** + * 添加人姓名 + */ + private String addByName; + + /** + * 修改人id + */ + private Long updateBy; + + /** + * 修改人姓名 + */ + private String updateByName; + +} diff --git a/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/enums/ConfigKeyEnum.java b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/enums/ConfigKeyEnum.java new file mode 100755 index 00000000..541e3866 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/enums/ConfigKeyEnum.java @@ -0,0 +1,32 @@ +package com.gitee.sop.admin.common.enums; + +import com.gitee.sop.admin.common.config.Configs; +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * @author 六如 + */ +@AllArgsConstructor +@Getter +public enum ConfigKeyEnum { + PASSWORD_SALT("admin.password-salt", ""), + JWT_TIMEOUT_DAYS("admin.jwt-timeout-days", "365"), + JWT_SECRET("admin.jwt.secret", ""), + TORNA_SERVER_ADDR("admin.torna-server-addr", ""), + OPEN_PROD_URL("admin.open-prod-url", ""), + OPEN_SANDBOX_URL("admin.open-sandbox-url", ""); + + private final String key; + + private final String defaultValue; + + public String getValue() { + return getValue(this.defaultValue); + } + + public String getValue(String defaultValue) { + return Configs.getValue(this, defaultValue); + } + +} diff --git a/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/enums/DocSourceTypeEnum.java b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/enums/DocSourceTypeEnum.java new file mode 100755 index 00000000..87df0b6f --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/enums/DocSourceTypeEnum.java @@ -0,0 +1,19 @@ +package com.gitee.sop.admin.common.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 来源类型,1-torna,2-自建 + * + * @author 六如 + */ +@Getter +@AllArgsConstructor +public enum DocSourceTypeEnum implements IntEnum { + TORNA(1, "Torna"), + CUSTOM(2, "自建"); + private final Integer value; + private final String description; + +} diff --git a/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/enums/DocTypeEnum.java b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/enums/DocTypeEnum.java new file mode 100755 index 00000000..06b63d65 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/enums/DocTypeEnum.java @@ -0,0 +1,21 @@ +package com.gitee.sop.admin.common.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 文档类型,0:http,1:dubbo,2:富文本,3:Markdown + * + * @author 六如 + */ +@Getter +@AllArgsConstructor +public enum DocTypeEnum implements IntEnum { + HTTP(0, "HTTP"), + DUBBO(1, "dubbo"), + RICH_TEXT(2, "富文本"), + MARKDOWN(3, "Markdown"); + private final Integer value; + private final String description; + +} diff --git a/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/enums/IEnum.java b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/enums/IEnum.java new file mode 100755 index 00000000..cadc9410 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/enums/IEnum.java @@ -0,0 +1,25 @@ +package com.gitee.sop.admin.common.enums; + +import java.io.Serializable; + +/** + * @author 六如 + * @param 参数类型 + */ +public interface IEnum { + + /** + * 获取值 + * + * @return 返回枚举值 + */ + T getValue(); + + /** + * 获取描述 + * + * @return 返回枚举描述 + */ + String getDescription(); + +} diff --git a/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/enums/IntEnum.java b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/enums/IntEnum.java new file mode 100755 index 00000000..668259bd --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/enums/IntEnum.java @@ -0,0 +1,24 @@ +package com.gitee.sop.admin.common.enums; + +import java.util.Objects; +import java.util.Optional; + +/** + * @author 六如 + */ +public interface IntEnum extends IEnum { + + static Optional of(T[] values, Integer value) { + for (IntEnum intEnum : values) { + if (Objects.equals(intEnum.getValue(), value)) { + return Optional.of((T) intEnum); + } + } + return Optional.empty(); + } + + static Optional ofDescription(T[] values, Integer value) { + return of(values, value).map(IntEnum::getDescription); + } + +} diff --git a/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/enums/MenuTypeEnum.java b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/enums/MenuTypeEnum.java new file mode 100755 index 00000000..5e160839 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/enums/MenuTypeEnum.java @@ -0,0 +1,21 @@ +package com.gitee.sop.admin.common.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 菜单类型 + * @author 六如 + */ +@Getter +@AllArgsConstructor +public enum MenuTypeEnum implements IntEnum { + MENU(0, "菜单"), + IFRAME(1, "iframe"), + LINK(2, "外链"), + BUTTON(3, "按钮"); + + private final Integer value; + + private final String description; +} diff --git a/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/enums/StatusEnum.java b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/enums/StatusEnum.java new file mode 100755 index 00000000..2b4f0527 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/enums/StatusEnum.java @@ -0,0 +1,31 @@ +package com.gitee.sop.admin.common.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Objects; + +/** + * @author 六如 + */ +@Getter +@AllArgsConstructor +public enum StatusEnum implements IntEnum { + DISABLED(2, "禁用"), + ENABLE(1, "启用"), + SET_PWD(3, "重置密码"); + + private final Integer value; + private final String description; + + public static StatusEnum of(Integer value) { + for (StatusEnum statusEnum : StatusEnum.values()) { + if (Objects.equals(statusEnum.value, value)) { + return statusEnum; + } + } + return DISABLED; + } + + +} diff --git a/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/enums/StringEnum.java b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/enums/StringEnum.java new file mode 100755 index 00000000..73952750 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/enums/StringEnum.java @@ -0,0 +1,24 @@ +package com.gitee.sop.admin.common.enums; + +import java.util.Objects; +import java.util.Optional; + +/** + * @author 六如 + */ +public interface StringEnum extends IEnum { + + static Optional of(T[] values, String value) { + for (StringEnum intEnum : values) { + if (Objects.equals(intEnum.getValue(), value)) { + return Optional.of((T) intEnum); + } + } + return Optional.empty(); + } + + static Optional ofDescription(T[] values, String value) { + return of(values, value).map(StringEnum::getDescription); + } + +} diff --git a/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/exception/BizException.java b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/exception/BizException.java new file mode 100755 index 00000000..33242a91 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/exception/BizException.java @@ -0,0 +1,10 @@ +package com.gitee.sop.admin.common.exception; + +/** + * @author tanghc + */ +public class BizException extends RuntimeException { + public BizException(String message) { + super(message); + } +} diff --git a/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/exception/ErrorCode.java b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/exception/ErrorCode.java new file mode 100755 index 00000000..2e862850 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/exception/ErrorCode.java @@ -0,0 +1,29 @@ +package com.gitee.sop.admin.common.exception; + +/** + * @author tanghc + */ +public enum ErrorCode { + // 1000: 登录失败 + LOGIN_FAIL("1000", "login error"), + JWT_CREATE("1000", "create token error"), + JWT_ERROR("1000", "invalid token"), + JWT_EXPIRED("1000", "token expired"), + SET_PASSWORD("2000", "set password"); + + ErrorCode(String code, String msg) { + this.code = code; + this.msg = msg; + } + + private final String code; + private final String msg; + + public String getCode() { + return code; + } + + public String getMsg() { + return msg; + } +} diff --git a/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/exception/ErrorTokenException.java b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/exception/ErrorTokenException.java new file mode 100755 index 00000000..88b629b8 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/exception/ErrorTokenException.java @@ -0,0 +1,7 @@ +package com.gitee.sop.admin.common.exception; + +/** + * @author tanghc + */ +public class ErrorTokenException extends Exception { +} diff --git a/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/exception/ExceptionCode.java b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/exception/ExceptionCode.java new file mode 100755 index 00000000..a14e64f3 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/exception/ExceptionCode.java @@ -0,0 +1,8 @@ +package com.gitee.sop.admin.common.exception; + +/** + * @author tanghc + */ +public interface ExceptionCode { + ErrorCode getCode(); +} diff --git a/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/exception/JwtCreateException.java b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/exception/JwtCreateException.java new file mode 100755 index 00000000..f067027c --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/exception/JwtCreateException.java @@ -0,0 +1,11 @@ +package com.gitee.sop.admin.common.exception; + +/** + * @author tanghc + */ +public class JwtCreateException extends RuntimeException implements ExceptionCode { + @Override + public ErrorCode getCode() { + return ErrorCode.JWT_CREATE; + } +} diff --git a/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/exception/JwtErrorException.java b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/exception/JwtErrorException.java new file mode 100755 index 00000000..232d4a82 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/exception/JwtErrorException.java @@ -0,0 +1,12 @@ +package com.gitee.sop.admin.common.exception; + +/** + * @author tanghc + */ +public class JwtErrorException extends Exception { + + @Override + public String getMessage() { + return "jwt verify error"; + } +} diff --git a/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/exception/JwtExpiredException.java b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/exception/JwtExpiredException.java new file mode 100755 index 00000000..2d1d5c84 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/exception/JwtExpiredException.java @@ -0,0 +1,11 @@ +package com.gitee.sop.admin.common.exception; + +/** + * @author tanghc + */ +public class JwtExpiredException extends Exception { + @Override + public String getMessage() { + return "jwt expired"; + } +} diff --git a/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/exception/LoginFailureException.java b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/exception/LoginFailureException.java new file mode 100755 index 00000000..724e02bf --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/exception/LoginFailureException.java @@ -0,0 +1,18 @@ +package com.gitee.sop.admin.common.exception; + +/** + * @author tanghc + */ +public class LoginFailureException extends RuntimeException implements ExceptionCode { + @Override + public ErrorCode getCode() { + return ErrorCode.LOGIN_FAIL; + } + + public LoginFailureException(String message) { + super(message); + } + + public LoginFailureException() { + } +} diff --git a/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/exception/SetPasswordException.java b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/exception/SetPasswordException.java new file mode 100755 index 00000000..956e1cb7 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/exception/SetPasswordException.java @@ -0,0 +1,11 @@ +package com.gitee.sop.admin.common.exception; + +/** + * @author tanghc + */ +public class SetPasswordException extends RuntimeException implements ExceptionCode { + @Override + public ErrorCode getCode() { + return ErrorCode.SET_PASSWORD; + } +} diff --git a/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/fill/AddByFill.java b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/fill/AddByFill.java new file mode 100755 index 00000000..5250dae1 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/fill/AddByFill.java @@ -0,0 +1,50 @@ +package com.gitee.sop.admin.common.fill; + +import com.gitee.fastmybatis.core.handler.BaseFill; +import com.gitee.fastmybatis.core.handler.FillType; +import com.gitee.sop.admin.common.context.UserContext; +import com.gitee.sop.admin.common.user.User; + +import java.lang.reflect.Field; +import java.util.Objects; + +/** + * 保存到数据库自动设置添加人 + * + * @author 六如 + */ +public class AddByFill extends BaseFill { + + private static final String ADD_BY = "addBy"; + + @Override + public FillType getFillType() { + return FillType.INSERT; + } + + @Override + protected Object getFillValue(Long defaultValue) { + User user = UserContext.getUser(); + return user != null ? user.getUserId() : defaultValue; + } + + @Override + protected Long convertValue(Object columnValue) { + if (columnValue == null) { + return null; + } + if (columnValue instanceof Long) { + return (Long) columnValue; + } + return Long.parseLong(String.valueOf(columnValue)); + } + + protected String getTargetFieldName() { + return ADD_BY; + } + + @Override + public boolean match(Class entityClass, Field field, String columnName) { + return Objects.equals(getTargetFieldName(), field.getName()); + } +} diff --git a/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/fill/UpdateByFill.java b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/fill/UpdateByFill.java new file mode 100755 index 00000000..014caf32 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/fill/UpdateByFill.java @@ -0,0 +1,22 @@ +package com.gitee.sop.admin.common.fill; + +import com.gitee.fastmybatis.core.handler.FillType; + +/** + * 保存到数据库自动设置修改人 + * @author 六如 + */ +public class UpdateByFill extends AddByFill { + + private static final String UPDATE_BY = "updateBy"; + + @Override + public FillType getFillType() { + return FillType.UPDATE; + } + + @Override + protected String getTargetFieldName() { + return UPDATE_BY; + } +} diff --git a/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/jackson/DateConverterProperties.java b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/jackson/DateConverterProperties.java new file mode 100755 index 00000000..f15f604c --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/jackson/DateConverterProperties.java @@ -0,0 +1,41 @@ +package com.gitee.sop.admin.common.jackson; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + +import java.util.Locale; +import java.util.TimeZone; + +/** + * 日期序列化配置类 + */ +@Data +@ConfigurationProperties(prefix = "jackson") +public class DateConverterProperties { + + /** + * Date/LocalDateTime 格式化 + */ + private String dateTimeFormat = "yyyy-MM-dd HH:mm:ss"; + + /** + * localDate 格式化 + */ + private String localDateFormat = "yyyy-MM-dd"; + + /** + * localTime 格式化 + */ + private String localTimeFormat = "HH:mm:ss"; + + /** + * 时区, 默认 GMT+8 + */ + private TimeZone timeZone = TimeZone.getTimeZone("GMT+8"); + + /** + * 地区, 默认 zh_CN + */ + private Locale locale = Locale.CHINA; + +} diff --git a/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/jackson/JacksonDateConverterConfig.java b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/jackson/JacksonDateConverterConfig.java new file mode 100755 index 00000000..ed965cc1 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/jackson/JacksonDateConverterConfig.java @@ -0,0 +1,147 @@ +package com.gitee.sop.admin.common.jackson; + +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.deser.std.DateDeserializers; +import com.fasterxml.jackson.databind.ser.std.DateSerializer; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.convert.converter.Converter; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import java.util.Date; + +/** + * 基于Jackson的日期格式配置 + */ +@Slf4j +@Configuration +@EnableConfigurationProperties(DateConverterProperties.class) +public class JacksonDateConverterConfig { + + @Autowired + private DateConverterProperties props; + + @Bean + public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() { + log.info("@================= 初始化jackson日期序列化 ==================@"); + log.info("Locale : 【{}】", props.getLocale()); + log.info("TimeZone : 【{}】", props.getTimeZone().getDisplayName()); + log.info("Date : 【{}】", props.getDateTimeFormat()); + log.info("LocalDateTime : 【{}】", props.getDateTimeFormat()); + log.info("LocalDate : 【{}】", props.getLocalDateFormat()); + log.info("LocalTime : 【{}】", props.getLocalTimeFormat()); + + final DateTimeFormatter dateTimeFormatter = this.ofPattern(props.getDateTimeFormat()); + final DateTimeFormatter localDateFormatter = this.ofPattern(props.getLocalDateFormat()); + final DateTimeFormatter localTimeFormatter = this.ofPattern(props.getLocalTimeFormat()); + final SimpleDateFormat simpleDateFormat = new SimpleDateFormat(props.getDateTimeFormat()); + + return builder -> { + builder.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + // 地区 + .locale(props.getLocale()) + // 时区 + .timeZone(props.getTimeZone()); + // 序列化 + builder.serializerByType(LocalDateTime.class, new LocalDateTimeSerializer(dateTimeFormatter)) + .serializerByType(LocalDate.class, new LocalDateSerializer(localDateFormatter)) + .serializerByType(LocalTime.class, new LocalTimeSerializer(localTimeFormatter)) + .serializerByType(Date.class, new DateSerializer(false, simpleDateFormat)); + // 反序列化 + builder.deserializerByType(LocalDateTime.class, new LocalDateTimeDeserializer(dateTimeFormatter)) + .deserializerByType(LocalDate.class, new LocalDateDeserializer(localDateFormatter)) + .deserializerByType(LocalTime.class, new LocalTimeDeserializer(localTimeFormatter)) + .deserializerByType(Date.class, + new DateDeserializers.DateDeserializer(DateDeserializers.DateDeserializer.instance, + simpleDateFormat, props.getDateTimeFormat())); + }; + } + + + /** + * 根据字符串 获取 DateTimeFormatter + * + * @param pattern 日期格式字符串 + * @return DateTimeFormatter + */ + private DateTimeFormatter ofPattern(String pattern) { + return DateTimeFormatter.ofPattern(pattern); + } + + + /** + * LocalDate转换器,用于转换RequestParam和PathVariable参数 + */ + @Bean + public Converter localDateConverter() { + return new Converter() { + @Override + public LocalDate convert(String source) { + return LocalDate.parse(source, DateTimeFormatter.ofPattern(props.getLocalDateFormat())); + } + }; + } + + /** + * LocalDateTime转换器,用于转换RequestParam和PathVariable参数 + */ + @Bean + public Converter localDateTimeConverter() { + return new Converter() { + @Override + public LocalDateTime convert(String source) { + return LocalDateTime.parse(source, DateTimeFormatter.ofPattern(props.getDateTimeFormat())); + } + }; + } + + /** + * LocalTime转换器,用于转换RequestParam和PathVariable参数 + */ + @Bean + public Converter localTimeConverter() { + return new Converter() { + @Override + public LocalTime convert(String source) { + return LocalTime.parse(source, DateTimeFormatter.ofPattern(props.getLocalTimeFormat())); + } + }; + } + + /** + * Date转换器,用于转换RequestParam和PathVariable参数 + */ + @Bean + public Converter dateConverter() { + return new Converter() { + @Override + public Date convert(String source) { + final SimpleDateFormat simpleDateFormat = new SimpleDateFormat(props.getDateTimeFormat()); + + try { + return simpleDateFormat.parse(source); + } catch (ParseException e) { + log.error(">>>> init Converter String to Date error!", e); + throw new RuntimeException(e); + } + } + }; + } + +} + diff --git a/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/jackson/convert/annotation/Bool.java b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/jackson/convert/annotation/Bool.java new file mode 100755 index 00000000..1b771bc5 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/jackson/convert/annotation/Bool.java @@ -0,0 +1,25 @@ +package com.gitee.sop.admin.common.jackson.convert.annotation; + + +import com.fasterxml.jackson.annotation.JacksonAnnotationsInside; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.gitee.sop.admin.common.jackson.convert.serde.BoolDeserializer; +import com.gitee.sop.admin.common.jackson.convert.serde.BoolSerializer; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 序列化自动转换成boolean值,反序列化转换成数字 + * @author 六如 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +@JacksonAnnotationsInside +@JsonSerialize(using = BoolSerializer.class) +@JsonDeserialize(using = BoolDeserializer.class) +public @interface Bool { +} diff --git a/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/jackson/convert/serde/BoolDeserializer.java b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/jackson/convert/serde/BoolDeserializer.java new file mode 100755 index 00000000..fa796e78 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/jackson/convert/serde/BoolDeserializer.java @@ -0,0 +1,44 @@ +package com.gitee.sop.admin.common.jackson.convert.serde; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import lombok.extern.slf4j.Slf4j; + +import java.io.IOException; +import java.util.Objects; + +/** + *
+ * {@literal
+ * 将布尔值类型转换成数字
+ *
+ * @IntBool
+ * private Integer isLoading;
+ * }
+ * 
+ * + * @author 六如 + */ +@Slf4j +public class BoolDeserializer extends StdDeserializer { + private static final long serialVersionUID = -4993230470571124275L; + + private static final String TRUE = "true"; + + + public BoolDeserializer() { + this(null); + } + + protected BoolDeserializer(final Class vc) { + super(vc); + } + + + @Override + public Object deserialize(final JsonParser p, final DeserializationContext ctxt) throws IOException { + String text = p.getText(); + return Objects.equals(TRUE, text) ? 1 : 0; + } +} diff --git a/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/jackson/convert/serde/BoolSerializer.java b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/jackson/convert/serde/BoolSerializer.java new file mode 100755 index 00000000..dcfa5d91 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/jackson/convert/serde/BoolSerializer.java @@ -0,0 +1,38 @@ +package com.gitee.sop.admin.common.jackson.convert.serde; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import lombok.extern.slf4j.Slf4j; + +import java.io.IOException; +import java.util.Objects; + +/** + *
+ * {@literal
+ * 将数字类型转换成布尔
+ *
+ * @Bool
+ * private Integer isLoading; // 返回给前端: "isLoading": true/false
+ *
+ * }
+ * 
+ * + * @author 六如 + */ +@Slf4j +public class BoolSerializer extends JsonSerializer { + + private static final String TRUE_VALUE = "1"; + + @Override + public void serialize(Object value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { + if (value == null) { + jsonGenerator.writeNull(); + return; + } + jsonGenerator.writeBoolean(Objects.equals(TRUE_VALUE, String.valueOf(value))); + } + +} diff --git a/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/manager/UserCacheManager.java b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/manager/UserCacheManager.java new file mode 100755 index 00000000..e0d980ff --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/manager/UserCacheManager.java @@ -0,0 +1,21 @@ +package com.gitee.sop.admin.common.manager; + +import com.gitee.sop.admin.common.user.User; + +/** + * @author 六如 + */ +public interface UserCacheManager { + /** + * 返回用户信息 + * @param userId 用户id + * @return 查不到返回null + */ + User getUser(long userId); + + /** + * 保存用户 + * @param user 用户 + */ + void saveUser(User user); +} diff --git a/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/req/IdParam.java b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/req/IdParam.java new file mode 100755 index 00000000..8f4c38f2 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/req/IdParam.java @@ -0,0 +1,14 @@ +package com.gitee.sop.admin.common.req; + +import lombok.Data; + +import javax.validation.constraints.NotNull; + +/** + * @author 六如 + */ +@Data +public class IdParam { + @NotNull(message = "id不能为空") + private Long id; +} diff --git a/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/req/IdsParam.java b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/req/IdsParam.java new file mode 100755 index 00000000..68c8465e --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/req/IdsParam.java @@ -0,0 +1,15 @@ +package com.gitee.sop.admin.common.req; + +import lombok.Data; + +import javax.validation.constraints.NotEmpty; +import java.util.List; + +/** + * @author 六如 + */ +@Data +public class IdsParam { + @NotEmpty(message = "id不能为空") + private List ids; +} diff --git a/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/req/StatusUpdateBatchParam.java b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/req/StatusUpdateBatchParam.java new file mode 100755 index 00000000..5003daa7 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/req/StatusUpdateBatchParam.java @@ -0,0 +1,18 @@ +package com.gitee.sop.admin.common.req; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.NotNull; + +/** + * @author 六如 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class StatusUpdateBatchParam extends IdsParam { + + @NotNull(message = "状态不能为空") + private Integer status; + +} diff --git a/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/req/StatusUpdateParam.java b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/req/StatusUpdateParam.java new file mode 100755 index 00000000..5ea9fb42 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/req/StatusUpdateParam.java @@ -0,0 +1,18 @@ +package com.gitee.sop.admin.common.req; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.NotNull; + +/** + * @author 六如 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class StatusUpdateParam extends IdParam { + + @NotNull(message = "状态不能为空") + private Integer status; + +} diff --git a/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/resp/Result.java b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/resp/Result.java new file mode 100755 index 00000000..5a16d167 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/resp/Result.java @@ -0,0 +1,47 @@ +package com.gitee.sop.admin.common.resp; + +import lombok.Data; + +import java.util.Objects; + +/** + * @param 数据 + * @author thc + */ +@Data +public class Result { + + private static final Result RESULT = new Result<>(); + + private String code = "0"; + private T data; + private String msg = "success"; + + public static Result ok() { + return RESULT; + } + + public static Result ok(E obj) { + Result result = new Result<>(); + result.setData(obj); + return result; + } + + public static Result err(String msg) { + Result result = new Result<>(); + result.setCode("1"); + result.setMsg(msg); + return result; + } + + public static Result err(String code, String msg) { + Result result = new Result<>(); + result.setCode(code); + result.setMsg(msg); + return result; + } + + public boolean getSuccess() { + return Objects.equals("0", code); + } +} diff --git a/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/resp/UserVO.java b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/resp/UserVO.java new file mode 100755 index 00000000..a779ca81 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/resp/UserVO.java @@ -0,0 +1,19 @@ +package com.gitee.sop.admin.common.resp; + +import lombok.Data; + +/** + * @author 六如 + */ +@Data +public class UserVO { + + private Long id; + + private String username; + + private String nickname; + + private String showName; + +} diff --git a/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/support/ServiceSupport.java b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/support/ServiceSupport.java new file mode 100755 index 00000000..2b563caa --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/support/ServiceSupport.java @@ -0,0 +1,54 @@ +package com.gitee.sop.admin.common.support; + +import com.gitee.fastmybatis.core.mapper.BaseMapper; +import com.gitee.fastmybatis.core.query.LambdaQuery; +import com.gitee.fastmybatis.core.support.LambdaService; +import com.gitee.sop.admin.common.context.UserContext; +import com.gitee.sop.admin.common.util.DateUtil; + +import java.time.LocalDateTime; + +/** + * @param 实体类 + * @param Mapper + * @author 六如 + */ +public interface ServiceSupport> extends LambdaService { + + @Override + default LambdaQuery query() { + return new MyLambdaQuery<>(this.getEntityClass()); + } + + class MyLambdaQuery extends LambdaQuery { + private static final long serialVersionUID = 5232128649485429495L; + + private static final String UPDATE_BY_FORMAT = "update_by = %s"; + private static final String UPDATE_TIME_FORMAT = "update_time = '%s'"; + + public MyLambdaQuery(Class entityClass) { + super(entityClass); + } + + @Override + public int update() { + this.appendUpdate(); + return super.update(); + } + + @Override + public int delete() { + this.appendUpdate(); + return super.delete(); + } + + private void appendUpdate() { + Long userId = UserContext.getUserId(); + if (userId != null) { + this.setExpression(String.format(UPDATE_BY_FORMAT, userId)); + } + this.setExpression(String.format(UPDATE_TIME_FORMAT, DateUtil.formatYmdhms(LocalDateTime.now()))); + } + + } +} diff --git a/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/user/User.java b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/user/User.java new file mode 100755 index 00000000..732f00f0 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/user/User.java @@ -0,0 +1,35 @@ +package com.gitee.sop.admin.common.user; + +/** + * 登录用户信息 + * @author 六如 + */ +public interface User { + + /** + * 用户id + * @return + */ + Long getUserId(); + + /** + * 返回username + * @return + */ + String getUsername(); + + /** + * 昵称 + * @return + */ + String getNickname(); + + Integer getStatus(); + + String getToken(); + + default String getShowName() { + String nickname = getNickname(); + return nickname != null && !nickname.isEmpty() ? nickname : getUsername(); + } +} diff --git a/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/util/CopyUtil.java b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/util/CopyUtil.java new file mode 100755 index 00000000..c6bc688f --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/util/CopyUtil.java @@ -0,0 +1,360 @@ +package com.gitee.sop.admin.common.util; + +import com.alibaba.fastjson2.JSON; +import com.gitee.fastmybatis.core.PageInfo; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.BeansException; +import org.springframework.beans.FatalBeanException; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; + +import java.beans.PropertyDescriptor; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +/** + * 属性拷贝工具类 + * + * @author 六如 + */ +public class CopyUtil extends BeanUtils { + + /** + * 属性拷贝,第一个参数中的属性值拷贝到第二个参数中
+ * 注意:当第一个参数中的属性有null值时,不会拷贝进去 + * + * @param from 源对象 + * @param to 目标对象 + * @param ignoreProperties 忽略的字段 + * @throws BeansException + */ + public static void copyPropertiesIgnoreNull(Object from, Object to, String... ignoreProperties) + throws BeansException { + Assert.notNull(from, "Source must not be null"); + Assert.notNull(to, "Target must not be null"); + + Class actualEditable = to.getClass(); + PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable); + List ignoreList = (ignoreProperties != null ? Arrays.asList(ignoreProperties) : Collections.emptyList()); + + for (PropertyDescriptor targetPd : targetPds) { + if (ignoreList.contains(targetPd.getName())) { + continue; + } + Method writeMethod = targetPd.getWriteMethod(); + if (writeMethod != null) { + PropertyDescriptor sourcePd = getPropertyDescriptor(from.getClass(), targetPd.getName()); + if (sourcePd != null) { + Method readMethod = sourcePd.getReadMethod(); + if (readMethod != null && + ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) { + try { + if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) { + readMethod.setAccessible(true); + } + Object value = readMethod.invoke(from); + if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) { + writeMethod.setAccessible(true); + } + // 这里判断value是否为空 当然这里也能进行一些特殊要求的处理 + // 例如绑定时格式转换等等 + if (value != null) { + writeMethod.invoke(to, value); + } + } catch (Throwable ex) { + throw new FatalBeanException( + "Could not copy property '" + targetPd.getName() + "' from source to target", ex); + } + } + } + } + } + } + + /** + * 拷贝指定的字段 + * + * @param from 源对象 + * @param to 目标对象 + * @param includeFields 指定字段 + */ + public static void copyPropertiesInclude(Object from, Object to, Set includeFields) { + Objects.requireNonNull(includeFields, "includeFields can not null"); + Assert.notNull(from, "Source must not be null"); + Assert.notNull(to, "Target must not be null"); + if (includeFields.isEmpty()) { + return; + } + Class actualEditable = to.getClass(); + PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable); + + for (PropertyDescriptor targetPd : targetPds) { + if (!includeFields.contains(targetPd.getName())) { + continue; + } + Method writeMethod = targetPd.getWriteMethod(); + if (writeMethod != null) { + PropertyDescriptor sourcePd = getPropertyDescriptor(from.getClass(), targetPd.getName()); + if (sourcePd != null) { + Method readMethod = sourcePd.getReadMethod(); + if (readMethod != null && + ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) { + try { + if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) { + readMethod.setAccessible(true); + } + Object value = readMethod.invoke(from); + if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) { + writeMethod.setAccessible(true); + } + writeMethod.invoke(to, value); + } catch (Throwable ex) { + throw new FatalBeanException( + "Could not copy property '" + targetPd.getName() + "' from source to target", ex); + } + } + } + } + } + } + + /** + * 拷贝属性 + * + * @param from 被拷贝类 + * @param to 目标类 + */ + public static void copyProperties(Object from, Object to) { + BeanUtils.copyProperties(from, to); + } + + /** + * 拷贝bean成为一个新类 + * + * @param from 被拷贝类 + * @param supplier 新的类获取回调 + * @param 新的类 + * @return 返回新的类实例,from为null时,返回null + */ + public static T copyBean(Object from, Supplier supplier) { + if (from == null) { + return null; + } + T to = supplier.get(); + BeanUtils.copyProperties(from, to); + return to; + } + + /** + * 拷贝实例 + * + * @param from 被拷贝类 + * @param supplier 新的类获取回调 + * @param after 对新的类最后续处理回调 + * @param 新的类 + * @return 返回新的类 + */ + public static T copyBean(Object from, Supplier supplier, Consumer after) { + if (from == null) { + return null; + } + T to = supplier.get(); + BeanUtils.copyProperties(from, to); + after.accept(to); + return to; + } + + /** + * 拷贝List,将list中的类转换成新的对象 + * + * @param collection 被拷贝的集合 + * @param toElement List新元素 + * @param 新元素类型 + * @return 返回新的List + */ + public static List copyList(Collection collection, Supplier toElement) { + if (collection == null || collection.isEmpty()) { + return new ArrayList<>(); + } + return collection.stream() + .map(source -> { + T target = toElement.get(); + BeanUtils.copyProperties(source, target); + return target; + }) + .collect(Collectors.toList()); + } + + public static List copyList(Collection fromList, Function function) { + if (fromList == null) { + return new ArrayList<>(); + } + return fromList.stream() + .map(source -> { + R target = function.apply(source); + BeanUtils.copyProperties(source, target); + return target; + }) + .collect(Collectors.toList()); + } + + /** + * 拷贝List,并做后续处理 + * + * @param fromList 被拷贝的list + * @param toElement 新元素 + * @param after 对新元素做后续处理 + * @param 新类型 + * @return 返回新的List + */ + public static List copyList(Collection fromList, Supplier toElement, Consumer after) { + if (fromList == null) { + return new ArrayList<>(); + } + return fromList.stream() + .map(source -> { + T target = toElement.get(); + BeanUtils.copyProperties(source, target); + after.accept(target); + return target; + }) + .collect(Collectors.toList()); + } + + /** + * 拷贝List,并做后续处理 + * + * @param fromList 被拷贝的list + * @param toElement 新元素 + * @param after 对新元素做后续处理 + * @param 新类型 + * @return 返回新的List + */ + public static List copyList(Collection fromList, Supplier toElement, CopyConsumer after) { + if (fromList == null) { + return new ArrayList<>(); + } + return fromList.stream() + .map(source -> { + T target = toElement.get(); + BeanUtils.copyProperties(source, target); + after.apply(source, target); + return target; + }) + .collect(Collectors.toList()); + } + + /** + * 深层次拷贝,通过json转换的方式实现 + * + * @param from 待转换的类 + * @param toClass 目标类class + * @param 目标类 + * @return 返回目标类 + */ + public static T deepCopy(Object from, Class toClass) { + String json = JSON.toJSONString(from); + return JSON.parseObject(json, toClass); + } + + /** + * 深层次拷贝,通过json转换的方式实现 + * + * @param from 待转换的类 + * @param toClass 目标类class + * @param 目标类 + * @return 返回目标类 + */ + public static List deepCopyList(Object from, Class toClass) { + String json = JSON.toJSONString(from); + return JSON.parseArray(json, toClass); + } + + + /** + * 拷贝map + * + * @param srcMap 原map + * @param valueGetter 值转换 + * @param Key类型 + * @param Value类型 + * @return 返回新map + */ + public static Map copyMap(Map srcMap, Supplier valueGetter) { + Map ret = new LinkedHashMap<>(srcMap.size() * 2); + for (Map.Entry entry : srcMap.entrySet()) { + V value = copyBean(entry.getValue(), valueGetter); + ret.put(entry.getKey(), value); + } + return ret; + } + + /** + * 拷贝map + * + * @param srcMap 原map + * @param function 值转换 + * @param Key类型 + * @param Value类型 + * @return 返回新map + */ + public static Map copyMap(Map srcMap, Function function) { + Map ret = new LinkedHashMap<>(srcMap.size() * 2); + for (Map.Entry entry : srcMap.entrySet()) { + V value = function.apply(entry.getValue()); + ret.put(entry.getKey(), value); + } + return ret; + } + + /** + * 拷贝map,value是list + * + * @param srcMap 原map + * @param valueGetter 值转换 + * @param Key类型 + * @param Value类型 + * @return 返回新map + */ + public static Map> copyMapList(Map> srcMap, Function, List> valueGetter) { + Map> ret = new LinkedHashMap<>(srcMap.size() * 2); + for (Map.Entry> entry : srcMap.entrySet()) { + List value = valueGetter.apply(entry.getValue()); + ret.put(entry.getKey(), value); + } + return ret; + } + + public static PageInfo copyPageInfo(PageInfo srcPageInfo, Function, List> function) { + PageInfo pageInfo = new PageInfo<>(); + List list = srcPageInfo.getList(); + if (list != null && !list.isEmpty()) { + pageInfo.setList(function.apply(list)); + } else { + pageInfo.setList(new ArrayList<>(0)); + } + pageInfo.setPageCount(srcPageInfo.getPageCount()); + pageInfo.setPageIndex(srcPageInfo.getPageIndex()); + pageInfo.setPageSize(srcPageInfo.getPageSize()); + pageInfo.setTotal(srcPageInfo.getTotal()); + return pageInfo; + } + + public interface CopyConsumer { + void apply(F from, T to); + } + +} diff --git a/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/util/DateUtil.java b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/util/DateUtil.java new file mode 100755 index 00000000..76a10ea1 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/util/DateUtil.java @@ -0,0 +1,22 @@ +package com.gitee.sop.admin.common.util; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +/** + * @author 六如 + */ +public class DateUtil { + + static final DateTimeFormatter FORMATTER_FRONT = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss"); + static final DateTimeFormatter FORMATTER_YMDHMS = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + + public static String formatFrontDate(LocalDateTime localDateTime) { + return FORMATTER_FRONT.format(localDateTime); + } + + public static String formatYmdhms(LocalDateTime localDateTime) { + return FORMATTER_YMDHMS.format(localDateTime); + } + +} diff --git a/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/util/GenerateUtil.java b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/util/GenerateUtil.java new file mode 100755 index 00000000..e9a0fd5e --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/util/GenerateUtil.java @@ -0,0 +1,22 @@ +package com.gitee.sop.admin.common.util; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.util.DigestUtils; + +import java.nio.charset.StandardCharsets; + +/** + * @author 六如 + */ +@Slf4j +public class GenerateUtil { + + public static String getUserPassword(String username, String password, String salt) { + return DigestUtils.md5DigestAsHex((username + password + salt).getBytes(StandardCharsets.UTF_8)); + } + + public static String getUUID() { + return IdGen.uuid(); + } + +} diff --git a/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/util/IdGen.java b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/util/IdGen.java new file mode 100755 index 00000000..3c594e8f --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/util/IdGen.java @@ -0,0 +1,193 @@ +package com.gitee.sop.admin.common.util; + +import java.util.UUID; + +public class IdGen { + private static long workId = 0; + private static final SnowflakeIdWorker SNOWFLAKE_ID_WORKER = new SnowflakeIdWorker(workId++, 0); + + public static long genId() { + return SNOWFLAKE_ID_WORKER.nextId(); + } + + /** + * 生成唯一id + * + * @return + */ + public static String nextId() { + return String.valueOf(genId()); + } + + public static String uuid() { + return UUID.randomUUID().toString().replace("-", ""); + } + + /** + * Twitter_Snowflake
+ * SnowFlake的结构如下(每部分用-分开):
+ * 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000
+ * 1位标识,由于long基本类型在Java中是带符号的,最高位是符号位,正数是0,负数是1,所以id一般是正数,最高位是0
+ * 41位时间截(毫秒级),注意,41位时间截不是存储当前时间的时间截,而是存储时间截的差值(当前时间截 - 开始时间截) + * 得到的值),这里的的开始时间截,一般是我们的id生成器开始使用的时间,由我们程序来指定的(如下下面程序IdWorker类的startTime属性)。41位的时间截,可以使用69年,年T = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69
+ * 10位的数据机器位,可以部署在1024个节点,包括5位datacenterId和5位workerId
+ * 12位序列,毫秒内的计数,12位的计数顺序号支持每个节点每毫秒(同一机器,同一时间截)产生4096个ID序号
+ * 加起来刚好64位,为一个Long型。
+ * SnowFlake的优点是,整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由数据中心ID和机器ID作区分),并且效率较高,经测试,SnowFlake每秒能够产生26万ID左右。 + */ + public static class SnowflakeIdWorker { + + // ==============================Fields=========================================== + /** + * 开始时间截 (2015-01-01) + */ + private final long twepoch = 1420041100000L; + + /** + * 机器id所占的位数 + */ + private final long workerIdBits = 5L; + + /** + * 数据标识id所占的位数 + */ + private final long datacenterIdBits = 5L; + + /** + * 支持的最大机器id,结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数) + */ + private final long maxWorkerId = -1L ^ (-1L << workerIdBits); + + /** + * 支持的最大数据标识id,结果是31 + */ + private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits); + + /** + * 序列在id中占的位数 + */ + private final long sequenceBits = 12L; + + /** + * 机器ID向左移12位 + */ + private final long workerIdShift = sequenceBits; + + /** + * 数据标识id向左移17位(12+5) + */ + private final long datacenterIdShift = sequenceBits + workerIdBits; + + /** + * 时间截向左移22位(5+5+12) + */ + private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits; + + /** + * 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095) + */ + private final long sequenceMask = -1L ^ (-1L << sequenceBits); + + /** + * 工作机器ID(0~31) + */ + private long workerId; + + /** + * 数据中心ID(0~31) + */ + private long datacenterId; + + /** + * 毫秒内序列(0~4095) + */ + private long sequence = 0L; + + /** + * 上次生成ID的时间截 + */ + private long lastTimestamp = -1L; + + //==============================Constructors===================================== + + /** + * 构造函数 + * + * @param workerId 工作ID (0~31) + * @param datacenterId 数据中心ID (0~31) + */ + public SnowflakeIdWorker(long workerId, long datacenterId) { + if (workerId > maxWorkerId || workerId < 0) { + throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId)); + } + if (datacenterId > maxDatacenterId || datacenterId < 0) { + throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId)); + } + this.workerId = workerId; + this.datacenterId = datacenterId; + } + + // ==============================Methods========================================== + + /** + * 获得下一个ID (该方法是线程安全的) + * + * @return SnowflakeId + */ + public synchronized long nextId() { + long timestamp = timeGen(); + + //如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常 + if (timestamp < lastTimestamp) { + throw new RuntimeException( + String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp)); + } + + //如果是同一时间生成的,则进行毫秒内序列 + if (lastTimestamp == timestamp) { + sequence = (sequence + 1) & sequenceMask; + //毫秒内序列溢出 + if (sequence == 0) { + //阻塞到下一个毫秒,获得新的时间戳 + timestamp = tilNextMillis(lastTimestamp); + } + } else { + //时间戳改变,毫秒内序列重置 + sequence = 0L; + } + + //上次生成ID的时间截 + lastTimestamp = timestamp; + + //移位并通过或运算拼到一起组成64位的ID + return ((timestamp - twepoch) << timestampLeftShift) + | (datacenterId << datacenterIdShift) + | (workerId << workerIdShift) + | sequence; + } + + /** + * 阻塞到下一个毫秒,直到获得新的时间戳 + * + * @param lastTimestamp 上次生成ID的时间截 + * @return 当前时间戳 + */ + protected long tilNextMillis(long lastTimestamp) { + long timestamp = timeGen(); + while (timestamp <= lastTimestamp) { + timestamp = timeGen(); + } + return timestamp; + } + + /** + * 返回以毫秒为单位的当前时间 + * + * @return 当前时间(毫秒) + */ + protected long timeGen() { + return System.currentTimeMillis(); + } + + } +} diff --git a/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/util/JwtUtil.java b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/util/JwtUtil.java new file mode 100755 index 00000000..c0346fe7 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/util/JwtUtil.java @@ -0,0 +1,97 @@ +package com.gitee.sop.admin.common.util; + +import com.auth0.jwt.JWT; +import com.auth0.jwt.JWTCreator; +import com.auth0.jwt.JWTVerifier; +import com.auth0.jwt.algorithms.Algorithm; +import com.auth0.jwt.exceptions.TokenExpiredException; +import com.auth0.jwt.interfaces.Claim; +import com.auth0.jwt.interfaces.DecodedJWT; +import com.gitee.sop.admin.common.exception.JwtCreateException; +import com.gitee.sop.admin.common.exception.JwtErrorException; +import com.gitee.sop.admin.common.exception.JwtExpiredException; +import lombok.extern.slf4j.Slf4j; + +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.Arrays; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * @author tanghc + */ +@Slf4j +public class JwtUtil { + + private static final Map HEADER_CLAIMS = new HashMap<>(); + + /** + * 登录成功后跳转页面 + */ + private static final String SUCCESS_HTML = String.join("\n", Arrays.asList( + "" + )); + + static { + HEADER_CLAIMS.put("typ", "JWT"); + HEADER_CLAIMS.put("alg", "HS256"); + } + + public static String createJwt(Map data, int timeoutDays, String secret) { + JWTCreator.Builder builder = JWT.create().withHeader(HEADER_CLAIMS); + Set> entrySet = data.entrySet(); + for (Map.Entry entry : entrySet) { + builder.withClaim(entry.getKey(), entry.getValue()); + } + LocalDateTime expiredDay = LocalDateTime.now().plusDays(timeoutDays); + Date expiredDate = Date.from(expiredDay.atZone(ZoneId.systemDefault()).toInstant()); + try { + return builder + // 过期时间 + .withExpiresAt(expiredDate) + // 创建时间 + .withIssuedAt(new Date()) + // 签名 + .sign(Algorithm.HMAC256(secret)); + } catch (Exception e) { + log.error(e.getMessage(), e); + throw new JwtCreateException(); + } + } + + public static Map verifyJwt(String token, String secret) throws JwtExpiredException, JwtErrorException { + JWTVerifier verifier = null; + try { + verifier = JWT.require(Algorithm.HMAC256(secret)).build(); + } catch (Exception e) { + log.error("验证jwt失败", e); + throw new JwtErrorException(); + } + + DecodedJWT jwt; + try { + jwt = verifier.verify(token); + } catch (TokenExpiredException e) { + throw new JwtExpiredException(); + } catch (Exception e) { + log.error("验证jwt失败", e); + throw new JwtErrorException(); + } + + return jwt.getClaims(); + } + + public static String getJumpPageHtml(String token) { + return getJumpPageHtml(token, "/"); + } + + public static String getJumpPageHtml(String token, String redirectUrl) { + return String.format(SUCCESS_HTML, token, redirectUrl); + } +} diff --git a/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/util/PasswordUtil.java b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/util/PasswordUtil.java new file mode 100755 index 00000000..27fca864 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/util/PasswordUtil.java @@ -0,0 +1,61 @@ +package com.gitee.sop.admin.common.util; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Random; + +public class PasswordUtil { + + private static final List CHARS; + private static final List SIMPLE_CHARS; + + static { + CHARS = getCharList("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890~!@#$%^&*-=,.<>"); + SIMPLE_CHARS = getCharList("abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789"); + } + + private static List getCharList(String str) { + char[] chars = str.toCharArray(); + List list = new ArrayList<>(chars.length); + for (char c : chars) { + list.add(c); + } + Collections.shuffle(list); + return list; + } + + /** + * 随机密码生成,仅字母数字 + * + * @param len 密码长度,必须大于等于4 + */ + public static String getRandomSimplePassword(int len) { + if (len < 4) { + throw new IllegalArgumentException("'len' must >= 4"); + } + StringBuilder sb = new StringBuilder(); + Random r = new Random(); + for (int x = 0; x < len; ++x) { + sb.append(SIMPLE_CHARS.get(r.nextInt(SIMPLE_CHARS.size()))); + } + return sb.toString(); + } + + /** + * 随机密码生成 + * + * @param len 密码长度,必须大于等于6 + */ + public static String getRandomPassword(int len) { + if (len < 6) { + throw new IllegalArgumentException("'len' must >= 6"); + } + StringBuilder sb = new StringBuilder(); + Random r = new Random(); + for (int x = 0; x < len; ++x) { + sb.append(CHARS.get(r.nextInt(CHARS.size()))); + } + return sb.toString(); + } +} diff --git a/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/util/RSATool.java b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/util/RSATool.java new file mode 100755 index 00000000..7a79ec30 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/util/RSATool.java @@ -0,0 +1,391 @@ +package com.gitee.sop.admin.common.util; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.apache.commons.codec.binary.Base64; +import org.bouncycastle.asn1.ASN1Encodable; +import org.bouncycastle.asn1.ASN1Primitive; +import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; + +import javax.crypto.Cipher; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.Objects; + +/** + * RSA加解密工具
+ * + * @author 六如 + */ +public class RSATool { + private static final String RSA_ALGORITHM = "RSA"; + + private final KeyFormat keyFormat; + private final KeyLength keyLength; + + public RSATool(KeyFormat keyFormat, KeyLength keyLength) { + this.keyFormat = keyFormat; + this.keyLength = keyLength; + } + + /** + * 创建公钥私钥 + * + * @return 返回公私钥对 + * @throws Exception + */ + public KeyStore createKeys() throws Exception { + KeyPairGenerator keyPairGeno = KeyPairGenerator.getInstance(RSA_ALGORITHM); + keyPairGeno.initialize(keyLength.getLength()); + KeyPair keyPair = keyPairGeno.generateKeyPair(); + + RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); + RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate(); + + KeyStore keyStore = new KeyStore(); + if (this.keyFormat == KeyFormat.PKCS1) { + keyStore.setPublicKey(Base64.encodeBase64String(publicKey.getEncoded())); + keyStore.setPrivateKey(convertPkcs8ToPkcs1(privateKey.getEncoded())); + } else { + keyStore.setPublicKey(Base64.encodeBase64String(publicKey.getEncoded())); + keyStore.setPrivateKey(Base64.encodeBase64String(privateKey.getEncoded())); + } + return keyStore; + } + + /** + * 获取公钥对象 + * + * @param pubKeyData 公钥 + * @return 返回公钥对象 + * @throws Exception + */ + public RSAPublicKey getPublicKey(byte[] pubKeyData) throws Exception { + X509EncodedKeySpec keySpec = new X509EncodedKeySpec(pubKeyData); + KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM); + return (RSAPublicKey) keyFactory.generatePublic(keySpec); + } + + /** + * 获取公钥对象 + * + * @param pubKey 公钥 + * @return 返回私钥对象 + * @throws Exception + */ + public RSAPublicKey getPublicKey(String pubKey) throws Exception { + return getPublicKey(Base64.decodeBase64(pubKey)); + + } + + /** + * 获取私钥对象 + * + * @param priKey 私钥 + * @return 私钥对象 + * @throws Exception + */ + public RSAPrivateKey getPrivateKey(String priKey) throws Exception { + return getPrivateKey(Base64.decodeBase64(priKey)); + } + + /** + * 通过私钥byte[]将公钥还原,适用于RSA算法 + * + * @param keyBytes + * @return 返回私钥 + * @throws Exception + */ + public RSAPrivateKey getPrivateKey(byte[] keyBytes) throws Exception { + PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes); + KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM); + return (RSAPrivateKey) keyFactory.generatePrivate(keySpec); + + } + + /** + * 公钥加密 + * + * @param data 待加密内容 + * @param publicKey 公钥 + * @return 返回密文 + * @throws Exception + */ + public String encryptByPublicKey(String data, RSAPublicKey publicKey) throws Exception { + Cipher cipher = Cipher.getInstance(keyFormat.getCipherAlgorithm()); + cipher.init(Cipher.ENCRYPT_MODE, publicKey); + // 模长 + int keyLen = publicKey.getModulus().bitLength() / 8; + // 加密数据长度 <= 模长-11 + String[] datas = splitString(data, keyLen - 11); + String mi = ""; + // 如果明文长度大于模长-11则要分组加密 + for (String s : datas) { + mi += bcd2Str(cipher.doFinal(s.getBytes())); + } + return mi; + } + + public String encryptByPrivateKey(String data, String privateKey) throws Exception { + return encryptByPrivateKey(data, getPrivateKey(privateKey)); + } + + /** + * 私钥加密 + * + * @param data 待加密数据 + * @param privateKey 私钥 + * @return 返回密文 + * @throws Exception + */ + public String encryptByPrivateKey(String data, RSAPrivateKey privateKey) throws Exception { + Cipher cipher = Cipher.getInstance(keyFormat.getCipherAlgorithm()); + cipher.init(Cipher.ENCRYPT_MODE, privateKey); + // 模长 + int keyLen = privateKey.getModulus().bitLength() / 8; + // 加密数据长度 <= 模长-11 + String[] datas = splitString(data, keyLen - 11); + String mi = ""; + // 如果明文长度大于模长-11则要分组加密 + for (String s : datas) { + mi += bcd2Str(cipher.doFinal(s.getBytes())); + } + return mi; + } + + public String decryptByPrivateKey(String data, String privateKey) throws Exception { + return decryptByPrivateKey(data, getPrivateKey(privateKey)); + } + + /** + * 私钥解密 + * + * @param data 待解密内容 + * @param privateKey 私钥 + * @return 返回明文 + * @throws Exception + */ + public String decryptByPrivateKey(String data, RSAPrivateKey privateKey) throws Exception { + Cipher cipher = Cipher.getInstance(keyFormat.getCipherAlgorithm()); + cipher.init(Cipher.DECRYPT_MODE, privateKey); + // 模长 + int keyLen = privateKey.getModulus().bitLength() / 8; + byte[] bytes = data.getBytes(); + byte[] bcd = asciiToBcd(bytes, bytes.length); + // 如果密文长度大于模长则要分组解密 + String ming = ""; + byte[][] arrays = splitArray(bcd, keyLen); + for (byte[] arr : arrays) { + ming += new String(cipher.doFinal(arr)); + } + return ming; + } + + /** + * 公钥解密 + * + * @param data 待解密内容 + * @param rsaPublicKey 公钥 + * @return 返回明文 + * @throws Exception + */ + public String decryptByPublicKey(String data, RSAPublicKey rsaPublicKey) throws Exception { + Cipher cipher = Cipher.getInstance(keyFormat.getCipherAlgorithm()); + cipher.init(Cipher.DECRYPT_MODE, rsaPublicKey); + // 模长 + int keyLen = rsaPublicKey.getModulus().bitLength() / 8; + byte[] bytes = data.getBytes(); + byte[] bcd = asciiToBcd(bytes, bytes.length); + // 如果密文长度大于模长则要分组解密 + String ming = ""; + byte[][] arrays = splitArray(bcd, keyLen); + for (byte[] arr : arrays) { + ming += new String(cipher.doFinal(arr)); + } + return ming; + } + + public static String convertPkcs8ToPkcs1(byte[] privateKeyData) throws Exception { + PrivateKeyInfo pkInfo = PrivateKeyInfo.getInstance(privateKeyData); + ASN1Encodable encodable = pkInfo.parsePrivateKey(); + ASN1Primitive primitive = encodable.toASN1Primitive(); + byte[] privateKeyPKCS1 = primitive.getEncoded(); + return Base64.encodeBase64String(privateKeyPKCS1); + } + + + /** + * ASCII码转BCD码 + */ + public static byte[] asciiToBcd(byte[] ascii, int asc_len) { + byte[] bcd = new byte[asc_len / 2]; + int j = 0; + for (int i = 0; i < (asc_len + 1) / 2; i++) { + bcd[i] = ascToBcd(ascii[j++]); + bcd[i] = (byte) (((j >= asc_len) ? 0x00 : ascToBcd(ascii[j++]) & 0xff) + (bcd[i] << 4)); + } + return bcd; + } + + public static byte ascToBcd(byte asc) { + byte bcd; + + if ((asc >= '0') && (asc <= '9')) { + bcd = (byte) (asc - '0'); + } else if ((asc >= 'A') && (asc <= 'F')) { + bcd = (byte) (asc - 'A' + 10); + } else if ((asc >= 'a') && (asc <= 'f')) { + bcd = (byte) (asc - 'a' + 10); + } else { + bcd = (byte) (asc - 48); + } + return bcd; + } + + /** + * BCD转字符串 + */ + public String bcd2Str(byte[] bytes) { + char[] temp = new char[bytes.length * 2]; + char val; + + for (int i = 0; i < bytes.length; i++) { + val = (char) (((bytes[i] & 0xf0) >> 4) & 0x0f); + temp[i * 2] = (char) (val > 9 ? val + 'A' - 10 : val + '0'); + + val = (char) (bytes[i] & 0x0f); + temp[i * 2 + 1] = (char) (val > 9 ? val + 'A' - 10 : val + '0'); + } + return new String(temp); + } + + /** + * 拆分字符串 + */ + public String[] splitString(String string, int len) { + int x = string.length() / len; + int y = string.length() % len; + int z = 0; + if (y != 0) { + z = 1; + } + String[] strings = new String[x + z]; + String str = ""; + for (int i = 0; i < x + z; i++) { + if (i == x + z - 1 && y != 0) { + str = string.substring(i * len, i * len + y); + } else { + str = string.substring(i * len, i * len + len); + } + strings[i] = str; + } + return strings; + } + + /** + * 拆分数组 + */ + public byte[][] splitArray(byte[] data, int len) { + int x = data.length / len; + int y = data.length % len; + int z = 0; + if (y != 0) { + z = 1; + } + byte[][] arrays = new byte[x + z][]; + byte[] arr; + for (int i = 0; i < x + z; i++) { + arr = new byte[len]; + if (i == x + z - 1 && y != 0) { + System.arraycopy(data, i * len, arr, 0, y); + } else { + System.arraycopy(data, i * len, arr, 0, len); + } + arrays[i] = arr; + } + return arrays; + } + + public enum KeyLength { + /** + * 秘钥长度:1024 + */ + LENGTH_1024(1024), + /** + * 秘钥长度:2048 + */ + LENGTH_2048(2048); + private int length; + + KeyLength(int length) { + this.length = length; + } + + public int getLength() { + return length; + } + } + + public static class KeyStore { + private String publicKey; + private String privateKey; + + public String getPublicKey() { + return publicKey; + } + + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; + } + + public String getPrivateKey() { + return privateKey; + } + + public void setPrivateKey(String privateKey) { + this.privateKey = privateKey; + } + + @Override + public String toString() { + return "KeyStore{" + + "publicKey='" + publicKey + '\'' + + ", privateKey='" + privateKey + '\'' + + '}'; + } + } + + @AllArgsConstructor + @Getter + public enum KeyFormat { + PKCS8(1, "RSA"), + PKCS1(2, "RSA/ECB/PKCS1Padding"); + + private Integer value; + private String cipherAlgorithm; + + public static KeyFormat of(Integer value) { + for (KeyFormat keyFormat : KeyFormat.values()) { + if (Objects.equals(value, keyFormat.value)) { + return keyFormat; + } + } + return PKCS8; + } + } + + public KeyFormat getKeyFormat() { + return keyFormat; + } + + public KeyLength getKeyLength() { + return keyLength; + } + + +} diff --git a/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/util/RequestUtil.java b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/util/RequestUtil.java new file mode 100755 index 00000000..c5fed8aa --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/util/RequestUtil.java @@ -0,0 +1,118 @@ +package com.gitee.sop.admin.common.util; + +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.io.IOUtils; +import org.springframework.util.StringUtils; +import org.springframework.web.util.UriUtils; + +import javax.servlet.ServletInputStream; +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * @author tanghc + */ +@Slf4j +public class RequestUtil { + + private static final String IP_UNKNOWN = "unknown"; + private static final String IP_LOCAL = "127.0.0.1"; + private static final int IP_LEN = 15; + + /** + * 获取表单中的字段,请求类型是application/x-www-form-urlencoded + * + * @param request request + * @return 返回字段内容 + */ + public static Map getFormFields(HttpServletRequest request) { + String query; + try { + ServletInputStream inputStream = request.getInputStream(); + query = IOUtils.toString(inputStream, request.getCharacterEncoding()); + } catch (IOException e) { + log.error("获取form参数失败", e); + throw new RuntimeException("请求失败"); + } + return parseQueryString(query); + } + + public static Map parseQueryString(String query) { + if (StringUtils.isEmpty(query)) { + return Collections.emptyMap(); + } + query = StringUtils.trimLeadingCharacter(query, '?'); + String[] pairs = query.split("&"); + Map form = new HashMap<>(pairs.length * 2); + for (String pair : pairs) { + String[] param = pair.split("="); + String key = param[0]; + String value = UriUtils.decode(param[1], StandardCharsets.UTF_8); + form.put(key, value); + } + return form; + } + + /** + * 获取上传文件表单中的字段,排除query参数 + * + * @param request request + * @return 返回字段内容 + */ + public static Map getMultipartFields(HttpServletRequest request) { + Map queryParams = parseQueryString(request.getQueryString()); + Map uploadParams = new HashMap<>(16); + Set queryKeys = queryParams.keySet(); + request.getParameterMap().forEach((key, value) -> { + // 排除query param + if (!queryKeys.contains(key)) { + uploadParams.put(key, value[0]); + } + }); + return uploadParams; + } + + /** + * 获取客户端IP + * + * @param request request + * @return 返回ip + */ + public static String getIP(HttpServletRequest request) { + String ipAddress = request.getHeader("x-forwarded-for"); + if (ipAddress == null || ipAddress.length() == 0 || IP_UNKNOWN.equalsIgnoreCase(ipAddress)) { + ipAddress = request.getHeader("Proxy-Client-IP"); + } + if (ipAddress == null || ipAddress.length() == 0 || IP_UNKNOWN.equalsIgnoreCase(ipAddress)) { + ipAddress = request.getHeader("WL-Proxy-Client-IP"); + } + if (ipAddress == null || ipAddress.length() == 0 || IP_UNKNOWN.equalsIgnoreCase(ipAddress)) { + ipAddress = request.getRemoteAddr(); + if (IP_LOCAL.equals(ipAddress)) { + // 根据网卡取本机配置的IP + try { + InetAddress inet = InetAddress.getLocalHost(); + ipAddress = inet.getHostAddress(); + } catch (UnknownHostException e) { + // ignore + } + } + + } + + // 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割 + if (ipAddress != null && ipAddress.length() > IP_LEN) { + if (ipAddress.indexOf(",") > 0) { + ipAddress = ipAddress.substring(0, ipAddress.indexOf(",")); + } + } + return ipAddress; + } +} diff --git a/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/util/SystemUtil.java b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/util/SystemUtil.java new file mode 100755 index 00000000..74751de3 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/util/SystemUtil.java @@ -0,0 +1,32 @@ +package com.gitee.sop.admin.common.util; + +import org.springframework.boot.system.ApplicationHome; + +import java.io.File; + +/** + * @author tanghc + */ +public class SystemUtil { + + /** + * 获取程序执行目录,即jar包所在的目录。此方法只在部署后有用,开发模式下,这里返回target路径 + * @return 返回路径 + */ + public static String getBinPath() { + ApplicationHome applicationHome = new ApplicationHome(SystemUtil.class); + File file = applicationHome.getSource(); + if (file == null) { + return getUserDir(); + } + return file.getParentFile().toString(); + } + + public static String getUserDir() { + return System.getProperty("user.dir"); + } + + public static String getUserHome() { + return System.getProperty("user.home"); + } +} diff --git a/sop-admin/sop-admin-backend/admin-common/src/test/java/com/gitee/sop/admin/common/RSAToolTest.java b/sop-admin/sop-admin-backend/admin-common/src/test/java/com/gitee/sop/admin/common/RSAToolTest.java new file mode 100755 index 00000000..603159b9 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-common/src/test/java/com/gitee/sop/admin/common/RSAToolTest.java @@ -0,0 +1,19 @@ +package com.gitee.sop.admin.common; + +import com.gitee.sop.admin.common.util.RSATool; +import org.junit.Test; + +/** + * @author 六如 + */ +public class RSAToolTest { + + @Test + public void create() throws Exception { + RSATool.KeyStore keys = new RSATool(RSATool.KeyFormat.PKCS1, RSATool.KeyLength.LENGTH_2048).createKeys(); + System.out.println(keys.getPrivateKey()); + System.out.println(keys.getPublicKey()); + } + + +} diff --git a/sop-admin/sop-admin-backend/admin-dao/pom.xml b/sop-admin/sop-admin-backend/admin-dao/pom.xml new file mode 100755 index 00000000..c1ae93d8 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-dao/pom.xml @@ -0,0 +1,33 @@ + + + 4.0.0 + + com.gitee.sop + sop-admin-backend + 5.0.0-SNAPSHOT + + + admin-dao + + + 8 + 8 + UTF-8 + + + + + io.gitee.durcframework + fastmybatis-spring-boot-starter + + + + org.projectlombok + lombok + true + + + + diff --git a/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/entity/ApiInfo.java b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/entity/ApiInfo.java new file mode 100755 index 00000000..309e21f4 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/entity/ApiInfo.java @@ -0,0 +1,111 @@ +package com.gitee.sop.admin.dao.entity; + +import com.gitee.fastmybatis.annotation.Pk; +import com.gitee.fastmybatis.annotation.PkStrategy; +import com.gitee.fastmybatis.annotation.Table; +import lombok.Data; + +import java.time.LocalDateTime; + + +/** + * 表名:api_info + * 备注:接口信息表 + * + * @author 六如 + */ +@Table(name = "api_info", pk = @Pk(name = "id", strategy = PkStrategy.INCREMENT)) +@Data +public class ApiInfo { + + /** + * id + */ + private Long id; + + /** + * 所属应用 + */ + private String application; + + /** + * 接口名称 + */ + private String apiName; + + /** + * 版本号 + */ + private String apiVersion; + + /** + * 接口描述 + */ + private String description; + + /** + * 备注 + */ + private String remark; + + /** + * 接口class + */ + private String interfaceClassName; + + /** + * 方法名称 + */ + private String methodName; + + /** + * 参数信息 + */ + private String paramInfo; + + /** + * 接口是否需要授权访问 + */ + private Integer isPermission; + + /** + * 是否需要appAuthToken + */ + private Integer isNeedToken; + + /** + * 是否有公共响应参数 + */ + private Integer hasCommonResponse; + + /** + * 注册来源,1-系统注册,2-手动注册 + */ + private Integer regSource; + + /** + * 1启用,0禁用 + */ + private Integer status; + + /** + * 添加时间 + */ + private LocalDateTime addTime; + + /** + * 修改时间 + */ + private LocalDateTime updateTime; + + /** + * 创建人id + */ + private Long addBy; + + /** + * 最后更新人id + */ + private Long updateBy; + +} diff --git a/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/entity/DocApp.java b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/entity/DocApp.java new file mode 100755 index 00000000..3f7fffda --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/entity/DocApp.java @@ -0,0 +1,63 @@ +package com.gitee.sop.admin.dao.entity; + +import java.time.LocalDateTime; + +import com.gitee.fastmybatis.annotation.Pk; +import com.gitee.fastmybatis.annotation.PkStrategy; +import com.gitee.fastmybatis.annotation.Table; + +import lombok.Data; + + +/** + * 表名:doc_app + * 备注:文档应用 + * + * @author 六如 + */ +@Table(name = "doc_app", pk = @Pk(name = "id", strategy = PkStrategy.INCREMENT)) +@Data +public class DocApp { + + /** + * id + */ + private Long id; + + /** + * 应用名称 + */ + private String appName; + + /** + * Torna应用token + */ + private String token; + + /** + * 状态, 0-未发布,1-已发布 + */ + private Integer isPublish; + + /** + * 添加时间 + */ + private LocalDateTime addTime; + + /** + * 修改时间 + */ + private LocalDateTime updateTime; + + /** + * 创建人id + */ + private Long addBy; + + /** + * 修改人id + */ + private Long updateBy; + + +} diff --git a/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/entity/DocContent.java b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/entity/DocContent.java new file mode 100755 index 00000000..f8d3c905 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/entity/DocContent.java @@ -0,0 +1,58 @@ +package com.gitee.sop.admin.dao.entity; + +import java.time.LocalDateTime; + +import com.gitee.fastmybatis.annotation.Pk; +import com.gitee.fastmybatis.annotation.PkStrategy; +import com.gitee.fastmybatis.annotation.Table; + +import lombok.Data; + + +/** + * 表名:doc_content + * 备注:文档内容 + * + * @author 六如 + */ +@Table(name = "doc_content", pk = @Pk(name = "id", strategy = PkStrategy.INCREMENT)) +@Data +public class DocContent { + + /** + * id + */ + private Long id; + + /** + * doc_info.id + */ + private Long docInfoId; + + /** + * 文档内容 + */ + private String content; + + /** + * 添加时间 + */ + private LocalDateTime addTime; + + /** + * 修改时间 + */ + private LocalDateTime updateTime; + + /** + * 创建人id + */ + private Long addBy; + + /** + * 修改人id + */ + private Long updateBy; + + +} diff --git a/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/entity/DocInfo.java b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/entity/DocInfo.java new file mode 100755 index 00000000..59b92d63 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/entity/DocInfo.java @@ -0,0 +1,108 @@ +package com.gitee.sop.admin.dao.entity; + +import java.time.LocalDateTime; + +import com.gitee.fastmybatis.annotation.Pk; +import com.gitee.fastmybatis.annotation.PkStrategy; +import com.gitee.fastmybatis.annotation.Table; + +import lombok.Data; + + +/** + * 表名:doc_info + * 备注:文档信息 + * + * @author 六如 + */ +@Table(name = "doc_info", pk = @Pk(name = "id", strategy = PkStrategy.INCREMENT)) +@Data +public class DocInfo { + + /** + * id + */ + private Long id; + + /** + * doc_app.id + */ + private Long docAppId; + + /** + * 远程文档id + */ + private Long docId; + + /** + * 文档标题 + */ + private String docTitle; + + /** + * 文档code + */ + private String docCode; + + /** + * 文档类型,1-dubbo,2-富文本,3-Markdown + */ + private Integer docType; + + /** + * 来源类型,1-torna,2-自建 + */ + private Integer sourceType; + + /** + * 文档名称 + */ + private String docName; + + /** + * 版本号 + */ + private String docVersion; + + /** + * 描述 + */ + private String description; + + /** + * 是否分类 + */ + private Integer isFolder; + + /** + * 状态, 0-未发布,1-已发布 + */ + private Integer isPublish; + + /** + * 父节点id, 对应docId + */ + private Long parentId; + + /** + * 添加时间 + */ + private LocalDateTime addTime; + + /** + * 修改时间 + */ + private LocalDateTime updateTime; + + /** + * 创建人id + */ + private Long addBy; + + /** + * 修改人id + */ + private Long updateBy; + + +} diff --git a/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/entity/IsvInfo.java b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/entity/IsvInfo.java new file mode 100755 index 00000000..e76d2aa5 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/entity/IsvInfo.java @@ -0,0 +1,60 @@ +package com.gitee.sop.admin.dao.entity; + +import com.gitee.fastmybatis.annotation.Pk; +import com.gitee.fastmybatis.annotation.PkStrategy; +import com.gitee.fastmybatis.annotation.Table; +import lombok.Data; + +import java.time.LocalDateTime; + + +/** + * 表名:isv_info + * 备注:isv信息表 + * + * @author 六如 + */ +@Table(name = "isv_info", pk = @Pk(name = "id", strategy = PkStrategy.INCREMENT)) +@Data +public class IsvInfo { + + /** + * id + */ + private Long id; + + /** + * appKey + */ + private String appId; + + /** + * 1启用,2禁用 + */ + private Integer status; + + /** + * 备注 + */ + private String remark; + + /** + * 添加时间 + */ + private LocalDateTime addTime; + + /** + * 修改时间 + */ + private LocalDateTime updateTime; + + /** + * 创建人id + */ + private Long addBy; + + /** + * 最后更新人id + */ + private Long updateBy; +} diff --git a/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/entity/IsvKeys.java b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/entity/IsvKeys.java new file mode 100755 index 00000000..e9bd02d6 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/entity/IsvKeys.java @@ -0,0 +1,75 @@ +package com.gitee.sop.admin.dao.entity; + +import com.gitee.fastmybatis.annotation.Pk; +import com.gitee.fastmybatis.annotation.PkStrategy; +import com.gitee.fastmybatis.annotation.Table; +import lombok.Data; + +import java.time.LocalDateTime; + + +/** + * 表名:isv_keys + * 备注:ISV秘钥管理 + * + * @author 六如 + */ +@Table(name = "isv_keys", pk = @Pk(name = "id", strategy = PkStrategy.INCREMENT)) +@Data +public class IsvKeys { + + /** + * id + */ + private Long id; + + /** + * isv_info.id + */ + private Long isvId; + + /** + * 秘钥格式,1:PKCS8(JAVA适用),2:PKCS1(非JAVA适用) + */ + private Integer keyFormat; + + /** + * 开发者生成的公钥 + */ + private String publicKeyIsv; + + /** + * 开发者生成的私钥(交给开发者) + */ + private String privateKeyIsv; + + /** + * 平台生成的公钥(交给开发者) + */ + private String publicKeyPlatform; + + /** + * 平台生成的私钥 + */ + private String privateKeyPlatform; + + /** + * 添加时间 + */ + private LocalDateTime addTime; + + /** + * 修改时间 + */ + private LocalDateTime updateTime; + + /** + * 创建人id + */ + private Long addBy; + + /** + * 最后更新人id + */ + private Long updateBy; +} diff --git a/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/entity/PermGroup.java b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/entity/PermGroup.java new file mode 100755 index 00000000..bf03026a --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/entity/PermGroup.java @@ -0,0 +1,56 @@ +package com.gitee.sop.admin.dao.entity; + +import com.gitee.fastmybatis.annotation.Pk; +import com.gitee.fastmybatis.annotation.PkStrategy; +import com.gitee.fastmybatis.annotation.Table; +import lombok.Data; + +import java.time.LocalDateTime; + + +/** + * 表名:perm_group + * 备注:分组表 + * + * @author 六如 + */ +@Table(name = "perm_group", pk = @Pk(name = "id", strategy = PkStrategy.INCREMENT)) +@Data +public class PermGroup { + + /** + * id + */ + private Long id; + + /** + * 分组名称 + */ + private String groupName; + + /** + * 是否删除 + */ + @com.gitee.fastmybatis.annotation.Column(logicDelete = true) + private Integer isDeleted; + + /** + * 添加时间 + */ + private LocalDateTime addTime; + + /** + * 修改时间 + */ + private LocalDateTime updateTime; + + /** + * 创建人id + */ + private Long addBy; + + /** + * 最后更新人id + */ + private Long updateBy; +} diff --git a/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/entity/PermGroupPermission.java b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/entity/PermGroupPermission.java new file mode 100755 index 00000000..6bc21b56 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/entity/PermGroupPermission.java @@ -0,0 +1,55 @@ +package com.gitee.sop.admin.dao.entity; + +import com.gitee.fastmybatis.annotation.Pk; +import com.gitee.fastmybatis.annotation.PkStrategy; +import com.gitee.fastmybatis.annotation.Table; +import lombok.Data; + +import java.time.LocalDateTime; + + +/** + * 表名:perm_group_permission + * 备注:组权限表 + * + * @author 六如 + */ +@Table(name = "perm_group_permission", pk = @Pk(name = "id", strategy = PkStrategy.INCREMENT)) +@Data +public class PermGroupPermission { + + /** + * id + */ + private Long id; + + /** + * 组id + */ + private Long groupId; + + /** + * api_info.id + */ + private Long apiId; + + /** + * 添加时间 + */ + private LocalDateTime addTime; + + /** + * 修改时间 + */ + private LocalDateTime updateTime; + + /** + * 创建人id + */ + private Long addBy; + + /** + * 最后更新人id + */ + private Long updateBy; +} diff --git a/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/entity/PermIsvGroup.java b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/entity/PermIsvGroup.java new file mode 100755 index 00000000..0d922675 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/entity/PermIsvGroup.java @@ -0,0 +1,46 @@ +package com.gitee.sop.admin.dao.entity; + +import com.gitee.fastmybatis.annotation.Pk; +import com.gitee.fastmybatis.annotation.PkStrategy; +import com.gitee.fastmybatis.annotation.Table; +import lombok.Data; + +import java.time.LocalDateTime; + + +/** + * 表名:perm_isv_group + * 备注:isv分组 + * + * @author 六如 + */ +@Table(name = "perm_isv_group", pk = @Pk(name = "id", strategy = PkStrategy.INCREMENT)) +@Data +public class PermIsvGroup { + + private Long id; + + /** + * isv_info表id + */ + private Long isvId; + + /** + * 组id + */ + private Long groupId; + + private LocalDateTime addTime; + + private LocalDateTime updateTime; + + /** + * 创建人id + */ + private Long addBy; + + /** + * 最后更新人id + */ + private Long updateBy; +} diff --git a/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/entity/SysAdminUser.java b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/entity/SysAdminUser.java new file mode 100755 index 00000000..75a91145 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/entity/SysAdminUser.java @@ -0,0 +1,80 @@ +package com.gitee.sop.admin.dao.entity; + +import com.gitee.fastmybatis.annotation.Pk; +import com.gitee.fastmybatis.annotation.PkStrategy; +import com.gitee.fastmybatis.annotation.Table; +import lombok.Data; + +import java.time.LocalDateTime; + + +/** + * 表名:sys_admin_user + * 备注:系统用户表 + * + * @author 六如 + */ +@Table(name = "sys_admin_user", pk = @Pk(name = "id", strategy = PkStrategy.INCREMENT)) +@Data +public class SysAdminUser { + + /** + * id + */ + private Long id; + + /** + * 用户名 + */ + private String username; + + /** + * 密码 + */ + private String password; + + /** + * 用户名 + */ + private String nickname; + + /** + * 邮箱 + */ + private String email; + + /** + * 头像 + */ + private String avatar; + + /** + * 状态,1:启用,2:禁用 + */ + private Integer status; + + /** + * 注册类型,1-系统,2-手动 + */ + private String regType; + + /** + * 添加时间 + */ + private LocalDateTime addTime; + + /** + * 修改时间 + */ + private LocalDateTime updateTime; + + /** + * 创建人id + */ + private Long addBy; + + /** + * 最后更新人id + */ + private Long updateBy; +} diff --git a/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/entity/SysConfig.java b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/entity/SysConfig.java new file mode 100755 index 00000000..c1dcc237 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/entity/SysConfig.java @@ -0,0 +1,45 @@ +package com.gitee.sop.admin.dao.entity; + +import com.gitee.fastmybatis.annotation.Pk; +import com.gitee.fastmybatis.annotation.PkStrategy; +import com.gitee.fastmybatis.annotation.Table; +import lombok.Data; + +import java.time.LocalDateTime; + + +/** + * 表名:sys_config + * 备注:系统配置表 + * + * @author 六如 + */ +@Table(name = "sys_config", pk = @Pk(name = "id", strategy = PkStrategy.INCREMENT)) +@Data +public class SysConfig { + + private Long id; + + private String configKey; + + private String configValue; + + private String remark; + + @com.gitee.fastmybatis.annotation.Column(logicDelete = true) + private Integer isDeleted; + + private LocalDateTime addTime; + + private LocalDateTime updateTime; + + /** + * 创建人id + */ + private Long addBy; + + /** + * 最后更新人id + */ + private Long updateBy; +} diff --git a/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/entity/SysDept.java b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/entity/SysDept.java new file mode 100755 index 00000000..6cb0d592 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/entity/SysDept.java @@ -0,0 +1,73 @@ +package com.gitee.sop.admin.dao.entity; + +import java.time.LocalDateTime; + +import com.gitee.fastmybatis.annotation.Pk; +import com.gitee.fastmybatis.annotation.PkStrategy; +import com.gitee.fastmybatis.annotation.Table; + +import lombok.Data; + + +/** + * 表名:sys_dept + * 备注:部门表 + * + * @author 六如 + */ +@Table(name = "sys_dept", pk = @Pk(name = "id", strategy = PkStrategy.INCREMENT)) +@Data +public class SysDept { + + /** + * id + */ + private Long id; + + /** + * 部门名称 + */ + private String name; + + /** + * 排序 + */ + private Integer sort; + + /** + * 状态,1:启用,2:禁用 + */ + private Integer status; + + /** + * 备注 + */ + private String remark; + + /** + * 父级id + */ + private Long parentId; + + /** + * 添加时间 + */ + private LocalDateTime addTime; + + /** + * 修改时间 + */ + private LocalDateTime updateTime; + + /** + * 创建人id + */ + private Long addBy; + + /** + * 修改人id + */ + private Long updateBy; + + +} diff --git a/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/entity/SysResource.java b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/entity/SysResource.java new file mode 100755 index 00000000..3708972e --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/entity/SysResource.java @@ -0,0 +1,159 @@ +package com.gitee.sop.admin.dao.entity; + +import java.time.LocalDateTime; + +import com.gitee.fastmybatis.annotation.Pk; +import com.gitee.fastmybatis.annotation.PkStrategy; +import com.gitee.fastmybatis.annotation.Table; + +import lombok.Data; + + +/** + * 表名:sys_resource + * 备注:菜单资源表 + * + * @author 六如 + */ +@Table(name = "sys_resource", pk = @Pk(name = "id", strategy = PkStrategy.INCREMENT)) +@Data +public class SysResource { + + /** + * id + */ + private Long id; + + /** + * 菜单类型(0代表菜单、1代表iframe、2代表外链、3代表按钮) + */ + private Integer menuType; + + /** + * 菜单名称 + */ + private String title; + + /** + * 路由名称 + */ + private String name; + + /** + * 路由路径 + */ + private String path; + + /** + * 路由路径 + */ + private String component; + + /** + * 排序 + */ + private Integer rank; + + /** + * 路由重定向 + */ + private String redirect; + + /** + * 菜单图标 + */ + private String icon; + + /** + * 右侧图标 + */ + private String extraIcon; + + /** + * 进场动画(页面加载动画) + */ + private String enterTransition; + + /** + * 离场动画(页面加载动画) + */ + private String leaveTransition; + + /** + * 菜单激活 + */ + private String activePath; + + /** + * 权限标识 + */ + private String auths; + + /** + * 链接地址(需要内嵌的`iframe`链接地址) + */ + private String frameSrc; + + /** + * 加载动画(内嵌的`iframe`页面是否开启首次加载动画) + */ + private Integer frameLoading; + + /** + * 缓存页面 + */ + private Integer keepAlive; + + /** + * 标签页(当前菜单名称或自定义信息禁止添加到标签页) + */ + private Integer hiddenTag; + + /** + * 固定标签页(当前菜单名称是否固定显示在标签页且不可关闭) + */ + private Integer fixedTag; + + /** + * 菜单(是否显示该菜单) + */ + private Integer showLink; + + /** + * 父级菜单(是否显示父级菜单 + */ + private Integer showParent; + + /** + * 父级id + */ + private Long parentId; + + /** + * 是否删除 + */ + @com.gitee.fastmybatis.annotation.Column(logicDelete = true) + private Integer isDeleted; + + /** + * 添加时间 + */ + private LocalDateTime addTime; + + /** + * 修改时间 + */ + private LocalDateTime updateTime; + + /** + * 创建人id + */ + private Long addBy; + + /** + * 修改人id + */ + private Long updateBy; + + +} diff --git a/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/entity/SysRole.java b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/entity/SysRole.java new file mode 100755 index 00000000..40fea60b --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/entity/SysRole.java @@ -0,0 +1,71 @@ +package com.gitee.sop.admin.dao.entity; + +import java.time.LocalDateTime; + +import com.gitee.fastmybatis.annotation.Pk; +import com.gitee.fastmybatis.annotation.PkStrategy; +import com.gitee.fastmybatis.annotation.Table; + +import lombok.Data; + + +/** + * 表名:sys_role + * 备注:角色表 + * + * @author 六如 + */ +@Table(name = "sys_role", pk = @Pk(name = "id", strategy = PkStrategy.INCREMENT)) +@Data +public class SysRole { + + /** + * id + */ + private Long id; + + /** + * 角色名称 + */ + private String name; + + /** + * 角色code + */ + private String code; + + /** + * 备注 + */ + private String remark; + + /** + * 状态,1:启用,2:禁用 + */ + private Integer status; + + @com.gitee.fastmybatis.annotation.Column(logicDelete = true) + private Integer isDeleted; + + /** + * 添加时间 + */ + private LocalDateTime addTime; + + /** + * 修改时间 + */ + private LocalDateTime updateTime; + + /** + * 创建人id + */ + private Long addBy; + + /** + * 修改人id + */ + private Long updateBy; + + +} diff --git a/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/entity/SysRoleResource.java b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/entity/SysRoleResource.java new file mode 100755 index 00000000..1bd8eb55 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/entity/SysRoleResource.java @@ -0,0 +1,58 @@ +package com.gitee.sop.admin.dao.entity; + +import java.time.LocalDateTime; + +import com.gitee.fastmybatis.annotation.Pk; +import com.gitee.fastmybatis.annotation.PkStrategy; +import com.gitee.fastmybatis.annotation.Table; + +import lombok.Data; + + +/** + * 表名:sys_role_resource + * 备注:角色资源关联表 + * + * @author 六如 + */ +@Table(name = "sys_role_resource", pk = @Pk(name = "id", strategy = PkStrategy.INCREMENT)) +@Data +public class SysRoleResource { + + /** + * id + */ + private Long id; + + /** + * sys_role.id + */ + private Long roleId; + + /** + * sys_resource.id + */ + private Long resourceId; + + /** + * 添加时间 + */ + private LocalDateTime addTime; + + /** + * 修改时间 + */ + private LocalDateTime updateTime; + + /** + * 创建人id + */ + private Long addBy; + + /** + * 修改人id + */ + private Long updateBy; + + +} diff --git a/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/entity/SysUser.java b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/entity/SysUser.java new file mode 100755 index 00000000..26526eac --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/entity/SysUser.java @@ -0,0 +1,98 @@ +package com.gitee.sop.admin.dao.entity; + +import java.time.LocalDateTime; + +import com.gitee.fastmybatis.annotation.Pk; +import com.gitee.fastmybatis.annotation.PkStrategy; +import com.gitee.fastmybatis.annotation.Table; + +import lombok.Data; + + +/** + * 表名:sys_user + * 备注:系统用户表 + * + * @author 六如 + */ +@Table(name = "sys_user", pk = @Pk(name = "id", strategy = PkStrategy.INCREMENT)) +@Data +public class SysUser { + + /** + * id + */ + private Long id; + + /** + * 用户名 + */ + private String username; + + /** + * 密码 + */ + private String password; + + /** + * 昵称 + */ + private String nickname; + + /** + * 邮箱 + */ + private String phone; + + /** + * 邮箱 + */ + private String email; + + /** + * 头像 + */ + private String avatar; + + /** + * 性别,0-未知,1-男,2-女 + */ + private Integer gender; + + /** + * 状态,1:启用,2:禁用 + */ + private Integer status; + + /** + * 注册类型,1-系统,2-手动 + */ + private String regType; + + /** + * 备注 + */ + private String remark; + + /** + * 添加时间 + */ + private LocalDateTime addTime; + + /** + * 修改时间 + */ + private LocalDateTime updateTime; + + /** + * 创建人id + */ + private Long addBy; + + /** + * 修改人id + */ + private Long updateBy; + + +} diff --git a/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/entity/SysUserDept.java b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/entity/SysUserDept.java new file mode 100755 index 00000000..9e3ca4dc --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/entity/SysUserDept.java @@ -0,0 +1,58 @@ +package com.gitee.sop.admin.dao.entity; + +import java.time.LocalDateTime; + +import com.gitee.fastmybatis.annotation.Pk; +import com.gitee.fastmybatis.annotation.PkStrategy; +import com.gitee.fastmybatis.annotation.Table; + +import lombok.Data; + + +/** + * 表名:sys_user_dept + * 备注:用户部门关联表 + * + * @author 六如 + */ +@Table(name = "sys_user_dept", pk = @Pk(name = "id", strategy = PkStrategy.INCREMENT)) +@Data +public class SysUserDept { + + /** + * id + */ + private Long id; + + /** + * sys_user.id + */ + private Long userId; + + /** + * sys_dept.id + */ + private Long deptId; + + /** + * 添加时间 + */ + private LocalDateTime addTime; + + /** + * 修改时间 + */ + private LocalDateTime updateTime; + + /** + * 创建人id + */ + private Long addBy; + + /** + * 修改人id + */ + private Long updateBy; + + +} diff --git a/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/entity/SysUserRole.java b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/entity/SysUserRole.java new file mode 100755 index 00000000..7a4ff0ae --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/entity/SysUserRole.java @@ -0,0 +1,58 @@ +package com.gitee.sop.admin.dao.entity; + +import java.time.LocalDateTime; + +import com.gitee.fastmybatis.annotation.Pk; +import com.gitee.fastmybatis.annotation.PkStrategy; +import com.gitee.fastmybatis.annotation.Table; + +import lombok.Data; + + +/** + * 表名:sys_user_role + * 备注:用户角色关联表 + * + * @author 六如 + */ +@Table(name = "sys_user_role", pk = @Pk(name = "id", strategy = PkStrategy.INCREMENT)) +@Data +public class SysUserRole { + + /** + * id + */ + private Long id; + + /** + * sys_role.id + */ + private Long roleId; + + /** + * sys_user.id + */ + private Long userId; + + /** + * 添加时间 + */ + private LocalDateTime addTime; + + /** + * 修改时间 + */ + private LocalDateTime updateTime; + + /** + * 创建人id + */ + private Long addBy; + + /** + * 修改人id + */ + private Long updateBy; + + +} diff --git a/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/mapper/ApiInfoMapper.java b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/mapper/ApiInfoMapper.java new file mode 100755 index 00000000..de1d257a --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/mapper/ApiInfoMapper.java @@ -0,0 +1,21 @@ +package com.gitee.sop.admin.dao.mapper; + + +import com.gitee.fastmybatis.core.mapper.BaseMapper; +import com.gitee.sop.admin.dao.entity.ApiInfo; +import org.apache.ibatis.annotations.Mapper; + +/** + * @author 六如 + */ +@Mapper +public interface ApiInfoMapper extends BaseMapper { + + default ApiInfo getByNameVersion(String apiName, String apiVersion) { + return this.query() + .eq(ApiInfo::getApiName, apiName) + .eq(ApiInfo::getApiVersion, apiVersion) + .get(); + } + +} diff --git a/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/mapper/DocAppMapper.java b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/mapper/DocAppMapper.java new file mode 100755 index 00000000..e1b00de2 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/mapper/DocAppMapper.java @@ -0,0 +1,17 @@ +package com.gitee.sop.admin.dao.mapper; + +import com.gitee.fastmybatis.core.mapper.BaseMapper; +import com.gitee.sop.admin.dao.entity.DocApp; +import org.apache.ibatis.annotations.Mapper; + +/** + * @author 六如 + */ +@Mapper +public interface DocAppMapper extends BaseMapper { + default String getToken(Long id) { + return this.query() + .eq(DocApp::getId, id) + .getValue(DocApp::getToken); + } +} diff --git a/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/mapper/DocContentMapper.java b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/mapper/DocContentMapper.java new file mode 100755 index 00000000..84f64979 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/mapper/DocContentMapper.java @@ -0,0 +1,13 @@ +package com.gitee.sop.admin.dao.mapper; + +import com.gitee.fastmybatis.core.mapper.BaseMapper; +import com.gitee.sop.admin.dao.entity.DocContent; +import org.apache.ibatis.annotations.Mapper; + +/** + * @author 六如 + */ +@Mapper +public interface DocContentMapper extends BaseMapper { + +} diff --git a/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/mapper/DocInfoMapper.java b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/mapper/DocInfoMapper.java new file mode 100755 index 00000000..72491a4d --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/mapper/DocInfoMapper.java @@ -0,0 +1,13 @@ +package com.gitee.sop.admin.dao.mapper; + +import com.gitee.fastmybatis.core.mapper.BaseMapper; +import com.gitee.sop.admin.dao.entity.DocInfo; +import org.apache.ibatis.annotations.Mapper; + +/** + * @author 六如 + */ +@Mapper +public interface DocInfoMapper extends BaseMapper { + +} diff --git a/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/mapper/IsvInfoMapper.java b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/mapper/IsvInfoMapper.java new file mode 100755 index 00000000..a95f17ba --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/mapper/IsvInfoMapper.java @@ -0,0 +1,17 @@ +package com.gitee.sop.admin.dao.mapper; + +import com.gitee.fastmybatis.core.mapper.BaseMapper; +import com.gitee.sop.admin.dao.entity.IsvInfo; +import org.apache.ibatis.annotations.Mapper; + +/** + * @author 六如 + */ +@Mapper +public interface IsvInfoMapper extends BaseMapper { + + default IsvInfo getByAppId(String appId) { + return this.get(IsvInfo::getAppId, appId); + } + +} diff --git a/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/mapper/IsvKeysMapper.java b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/mapper/IsvKeysMapper.java new file mode 100755 index 00000000..532a64c8 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/mapper/IsvKeysMapper.java @@ -0,0 +1,15 @@ +package com.gitee.sop.admin.dao.mapper; + +import com.gitee.fastmybatis.core.mapper.BaseMapper; +import com.gitee.sop.admin.dao.entity.IsvKeys; + +/** + * @author 六如 + */ +public interface IsvKeysMapper extends BaseMapper { + + default IsvKeys getByIsvInfoId(Long isvId) { + return this.get(IsvKeys::getIsvId, isvId); + } + +} diff --git a/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/mapper/PermGroupMapper.java b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/mapper/PermGroupMapper.java new file mode 100755 index 00000000..ef0a959e --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/mapper/PermGroupMapper.java @@ -0,0 +1,11 @@ +package com.gitee.sop.admin.dao.mapper; + +import com.gitee.fastmybatis.core.mapper.BaseMapper; +import com.gitee.sop.admin.dao.entity.PermGroup; + +/** + * @author 六如 + */ +public interface PermGroupMapper extends BaseMapper { + +} diff --git a/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/mapper/PermGroupPermissionMapper.java b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/mapper/PermGroupPermissionMapper.java new file mode 100755 index 00000000..ffb5c6dc --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/mapper/PermGroupPermissionMapper.java @@ -0,0 +1,11 @@ +package com.gitee.sop.admin.dao.mapper; + +import com.gitee.fastmybatis.core.mapper.BaseMapper; +import com.gitee.sop.admin.dao.entity.PermGroupPermission; + +/** + * @author 六如 + */ +public interface PermGroupPermissionMapper extends BaseMapper { + +} diff --git a/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/mapper/PermIsvGroupMapper.java b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/mapper/PermIsvGroupMapper.java new file mode 100755 index 00000000..871f940a --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/mapper/PermIsvGroupMapper.java @@ -0,0 +1,13 @@ +package com.gitee.sop.admin.dao.mapper; + +import com.gitee.fastmybatis.core.mapper.BaseMapper; +import com.gitee.sop.admin.dao.entity.PermIsvGroup; +import org.apache.ibatis.annotations.Mapper; + +/** + * @author 六如 + */ +@Mapper +public interface PermIsvGroupMapper extends BaseMapper { + +} diff --git a/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/mapper/SysAdminUserMapper.java b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/mapper/SysAdminUserMapper.java new file mode 100755 index 00000000..c024294a --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/mapper/SysAdminUserMapper.java @@ -0,0 +1,11 @@ +package com.gitee.sop.admin.dao.mapper; + +import com.gitee.fastmybatis.core.mapper.BaseMapper; +import com.gitee.sop.admin.dao.entity.SysAdminUser; + +/** + * @author 六如 + */ +public interface SysAdminUserMapper extends BaseMapper { + +} diff --git a/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/mapper/SysConfigMapper.java b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/mapper/SysConfigMapper.java new file mode 100755 index 00000000..48aea28e --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/mapper/SysConfigMapper.java @@ -0,0 +1,11 @@ +package com.gitee.sop.admin.dao.mapper; + +import com.gitee.fastmybatis.core.mapper.BaseMapper; +import com.gitee.sop.admin.dao.entity.SysConfig; + +/** + * @author 六如 + */ +public interface SysConfigMapper extends BaseMapper { + +} diff --git a/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/mapper/SysDeptMapper.java b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/mapper/SysDeptMapper.java new file mode 100755 index 00000000..10cf6c3e --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/mapper/SysDeptMapper.java @@ -0,0 +1,13 @@ +package com.gitee.sop.admin.dao.mapper; + +import com.gitee.fastmybatis.core.mapper.BaseMapper; +import com.gitee.sop.admin.dao.entity.SysDept; +import org.apache.ibatis.annotations.Mapper; + +/** + * @author 六如 + */ +@Mapper +public interface SysDeptMapper extends BaseMapper { + +} diff --git a/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/mapper/SysResourceMapper.java b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/mapper/SysResourceMapper.java new file mode 100755 index 00000000..d2f03deb --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/mapper/SysResourceMapper.java @@ -0,0 +1,13 @@ +package com.gitee.sop.admin.dao.mapper; + +import com.gitee.fastmybatis.core.mapper.BaseMapper; +import com.gitee.sop.admin.dao.entity.SysResource; +import org.apache.ibatis.annotations.Mapper; + +/** + * @author 六如 + */ +@Mapper +public interface SysResourceMapper extends BaseMapper { + +} diff --git a/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/mapper/SysRoleMapper.java b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/mapper/SysRoleMapper.java new file mode 100755 index 00000000..f3d21d45 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/mapper/SysRoleMapper.java @@ -0,0 +1,13 @@ +package com.gitee.sop.admin.dao.mapper; + +import com.gitee.fastmybatis.core.mapper.BaseMapper; +import com.gitee.sop.admin.dao.entity.SysRole; +import org.apache.ibatis.annotations.Mapper; + +/** + * @author 六如 + */ +@Mapper +public interface SysRoleMapper extends BaseMapper { + +} diff --git a/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/mapper/SysRoleResourceMapper.java b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/mapper/SysRoleResourceMapper.java new file mode 100755 index 00000000..7969c25a --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/mapper/SysRoleResourceMapper.java @@ -0,0 +1,13 @@ +package com.gitee.sop.admin.dao.mapper; + +import com.gitee.fastmybatis.core.mapper.BaseMapper; +import com.gitee.sop.admin.dao.entity.SysRoleResource; +import org.apache.ibatis.annotations.Mapper; + +/** + * @author 六如 + */ +@Mapper +public interface SysRoleResourceMapper extends BaseMapper { + +} diff --git a/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/mapper/SysUserDeptMapper.java b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/mapper/SysUserDeptMapper.java new file mode 100755 index 00000000..d2a2301e --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/mapper/SysUserDeptMapper.java @@ -0,0 +1,13 @@ +package com.gitee.sop.admin.dao.mapper; + +import com.gitee.fastmybatis.core.mapper.BaseMapper; +import com.gitee.sop.admin.dao.entity.SysUserDept; +import org.apache.ibatis.annotations.Mapper; + +/** + * @author 六如 + */ +@Mapper +public interface SysUserDeptMapper extends BaseMapper { + +} diff --git a/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/mapper/SysUserMapper.java b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/mapper/SysUserMapper.java new file mode 100755 index 00000000..a3e69b69 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/mapper/SysUserMapper.java @@ -0,0 +1,13 @@ +package com.gitee.sop.admin.dao.mapper; + +import com.gitee.fastmybatis.core.mapper.BaseMapper; +import com.gitee.sop.admin.dao.entity.SysUser; +import org.apache.ibatis.annotations.Mapper; + +/** + * @author 六如 + */ +@Mapper +public interface SysUserMapper extends BaseMapper { + +} diff --git a/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/mapper/SysUserRoleMapper.java b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/mapper/SysUserRoleMapper.java new file mode 100755 index 00000000..47fe3ab2 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/mapper/SysUserRoleMapper.java @@ -0,0 +1,13 @@ +package com.gitee.sop.admin.dao.mapper; + +import com.gitee.fastmybatis.core.mapper.BaseMapper; +import com.gitee.sop.admin.dao.entity.SysUserRole; +import org.apache.ibatis.annotations.Mapper; + +/** + * @author 六如 + */ +@Mapper +public interface SysUserRoleMapper extends BaseMapper { + +} diff --git a/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/mapper/UpgradeMapper.java b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/mapper/UpgradeMapper.java new file mode 100755 index 00000000..5309b9ac --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/mapper/UpgradeMapper.java @@ -0,0 +1,27 @@ +package com.gitee.sop.admin.dao.mapper; + +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; +import java.util.Map; + +/** + * @author tanghc + */ +@Mapper +public interface UpgradeMapper { + + void runSql(@Param("sql") String sql); + + /** + * 查看MYSQL表字段信息 + * @param tableName 表名 + * @return 返回字段信息 + */ + List> listColumnInfo(@Param("tableName") String tableName); + + List listTableName(); + + List> listTableIndex(@Param("tableName") String tableName); +} diff --git a/sop-admin/sop-admin-backend/admin-dao/src/main/resources/mybatis/mapper/UpgradeMapper.xml b/sop-admin/sop-admin-backend/admin-dao/src/main/resources/mybatis/mapper/UpgradeMapper.xml new file mode 100755 index 00000000..9c0f5700 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-dao/src/main/resources/mybatis/mapper/UpgradeMapper.xml @@ -0,0 +1,22 @@ + + + + + + ${sql} + + + + + + + + + + diff --git a/sop-admin/sop-admin-backend/admin-dao/src/main/resources/mybatis/mybatisConfig.xml b/sop-admin/sop-admin-backend/admin-dao/src/main/resources/mybatis/mybatisConfig.xml new file mode 100755 index 00000000..3c4b234f --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-dao/src/main/resources/mybatis/mybatisConfig.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/sop-admin/sop-admin-backend/admin-service/pom.xml b/sop-admin/sop-admin-backend/admin-service/pom.xml new file mode 100755 index 00000000..4489abd1 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-service/pom.xml @@ -0,0 +1,44 @@ + + + 4.0.0 + + com.gitee.sop + sop-admin-backend + 5.0.0-SNAPSHOT + + + admin-service + + + 8 + 8 + UTF-8 + + + + + com.gitee.sop + admin-common + 5.0.0-SNAPSHOT + + + com.gitee.sop + admin-dao + 5.0.0-SNAPSHOT + + + org.apache.dubbo + dubbo + + + + org.projectlombok + lombok + true + + + + + diff --git a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/doc/DocAppService.java b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/doc/DocAppService.java new file mode 100755 index 00000000..b7c3a142 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/doc/DocAppService.java @@ -0,0 +1,52 @@ +package com.gitee.sop.admin.service.doc; + +import com.gitee.sop.admin.common.support.ServiceSupport; +import com.gitee.sop.admin.common.user.User; +import com.gitee.sop.admin.common.util.CopyUtil; +import com.gitee.sop.admin.dao.entity.DocApp; +import com.gitee.sop.admin.dao.mapper.DocAppMapper; +import com.gitee.sop.admin.service.doc.dto.DocAppDTO; +import com.gitee.sop.admin.service.doc.dto.torna.TornaModuleDTO; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; + + +/** + * @author 六如 + */ +@Service +public class DocAppService implements ServiceSupport { + + @Autowired + private TornaClient tornaClient; + + @Autowired + private DocInfoService docInfoService; + @Autowired + private DocInfoSyncService docInfoSyncService; + + public Long addDocApp(String token, User user) { + TornaModuleDTO tornaModuleDTO = tornaClient.execute("module.get", null, token, TornaModuleDTO.class); + DocApp docApp = this.get(DocApp::getToken, token); + if (docApp == null) { + docApp = new DocApp(); + docApp.setAppName(tornaModuleDTO.getName()); + docApp.setToken(token); + this.save(docApp); + } else { + docApp.setAppName(tornaModuleDTO.getName()); + this.update(docApp); + } + // 同步文档 + docInfoSyncService.syncDocInfo(docApp, null, user); + return docApp.getId(); + } + + public List listDocApp() { + List docApps = this.listAll(); + return CopyUtil.copyList(docApps, DocAppDTO::new); + } + +} diff --git a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/doc/DocContentService.java b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/doc/DocContentService.java new file mode 100755 index 00000000..883d8930 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/doc/DocContentService.java @@ -0,0 +1,39 @@ +package com.gitee.sop.admin.service.doc; + +import com.gitee.sop.admin.common.support.ServiceSupport; +import com.gitee.sop.admin.dao.entity.DocContent; +import com.gitee.sop.admin.dao.mapper.DocContentMapper; +import org.springframework.stereotype.Service; + + +/** + * @author 六如 + */ +@Service +public class DocContentService implements ServiceSupport { + + public void saveContent(Long docInfoId, String content) { + DocContent docContent = this.get(DocContent::getDocInfoId, docInfoId); + boolean save = false; + if (docContent == null) { + save = true; + docContent = new DocContent(); + } + docContent.setDocInfoId(docInfoId); + docContent.setContent(content); + if (save) { + this.save(docContent); + } else { + this.update(docContent); + } + } + + public String getContent(Long docInfoId) { + return this.query() + .eq(DocContent::getDocInfoId, docInfoId) + .getValueOptional(DocContent::getContent) + .orElse(""); + } + + +} diff --git a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/doc/DocInfoService.java b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/doc/DocInfoService.java new file mode 100755 index 00000000..45fdc88b --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/doc/DocInfoService.java @@ -0,0 +1,115 @@ +package com.gitee.sop.admin.service.doc; + +import com.alibaba.fastjson2.JSON; +import com.gitee.fastmybatis.core.util.TreeUtil; +import com.gitee.sop.admin.common.constants.YesOrNo; +import com.gitee.sop.admin.common.exception.BizException; +import com.gitee.sop.admin.common.support.ServiceSupport; +import com.gitee.sop.admin.common.util.CopyUtil; +import com.gitee.sop.admin.dao.entity.DocApp; +import com.gitee.sop.admin.dao.entity.DocInfo; +import com.gitee.sop.admin.dao.mapper.DocAppMapper; +import com.gitee.sop.admin.dao.mapper.DocInfoMapper; +import com.gitee.sop.admin.service.doc.dto.DocInfoPublishUpdateDTO; +import com.gitee.sop.admin.service.doc.dto.DocInfoTreeDTO; +import com.gitee.sop.admin.service.doc.dto.torna.TornaDocInfoViewDTO; +import com.gitee.sop.admin.service.sys.SysUserService; +import com.google.common.collect.Lists; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + + +/** + * @author 六如 + */ +@Service +public class DocInfoService implements ServiceSupport { + + @Autowired + private DocAppMapper docAppMapper; + @Autowired + private DocContentService docContentService; + @Autowired + private SysUserService sysUserService; + + public List listChildDoc(Long parentId) { + return this.list(DocInfo::getParentId, parentId); + } + + public List listDocTree(Long docAppId) { + List list = this.list(DocInfo::getDocAppId, docAppId); + if (CollectionUtils.isEmpty(list)) { + return new ArrayList<>(0); + } + Set userIds = list.stream().flatMap(docInfo -> Lists.newArrayList(docInfo.getAddBy(), docInfo.getUpdateBy()).stream()) + .filter(val -> val != null && val > 0) + .collect(Collectors.toSet()); + List docInfoTreeDTOS = CopyUtil.copyList(list, DocInfoTreeDTO::new); + sysUserService.fillShowName(docInfoTreeDTOS); + return TreeUtil.convertTree(docInfoTreeDTOS, 0L); + } + + public int publish(DocInfoPublishUpdateDTO docInfoUpdateDTO) { + DocInfo docInfo = this.getById(docInfoUpdateDTO.getId()); + Integer isPublish = docInfoUpdateDTO.getIsPublish(); + + // 如果是文件夹,发布下面所有的文档 + int i; + if (YesOrNo.yes(docInfo.getIsFolder())) { + List children = this.listChildDoc(docInfo.getDocId()); + Set ids = children.stream().map(DocInfo::getId).collect(Collectors.toSet()); + i = this.query() + .in(DocInfo::getId, ids) + .set(DocInfo::getIsPublish, isPublish) + .update(); + } else { + // 发布单个文档 + i = this.query() + .eq(DocInfo::getId, docInfoUpdateDTO.getId()) + .set(DocInfo::getIsPublish, isPublish) + .update(); + } + + // 发布一个接口自动发布所属应用 + Long docAppId = docInfo.getDocAppId(); + if (YesOrNo.yes(isPublish)) { + docAppMapper.query() + .eq(DocApp::getId, docAppId) + .set(DocApp::getIsPublish, isPublish) + .update(); + } else { + // 如果应用下的接口都未发布,应用也改成未发布 + long count = this.query() + .eq(DocInfo::getDocAppId, docAppId) + .eq(DocInfo::getIsFolder, YesOrNo.NO) + .eq(DocInfo::getIsPublish, YesOrNo.YES) + .getCount(); + if (count == 0) { + docAppMapper.query() + .eq(DocApp::getId, docAppId) + .set(DocApp::getIsPublish, YesOrNo.NO) + .update(); + } + } + + return i; + } + + + public TornaDocInfoViewDTO getDocDetail(Long id) { + DocInfo docInfo = this.getById(id); + if (docInfo == null || !YesOrNo.yes(docInfo.getIsPublish())) { + throw new BizException("文档不存在"); + } + String content = docContentService.getContent(docInfo.getId()); + return JSON.parseObject(content, TornaDocInfoViewDTO.class); + } + + +} diff --git a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/doc/DocInfoSyncService.java b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/doc/DocInfoSyncService.java new file mode 100755 index 00000000..42872dc9 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/doc/DocInfoSyncService.java @@ -0,0 +1,222 @@ +package com.gitee.sop.admin.service.doc; + +import com.alibaba.fastjson2.JSON; +import com.gitee.fastmybatis.core.util.TreeUtil; +import com.gitee.sop.admin.common.constants.YesOrNo; +import com.gitee.sop.admin.common.enums.DocSourceTypeEnum; +import com.gitee.sop.admin.common.user.User; +import com.gitee.sop.admin.dao.entity.DocApp; +import com.gitee.sop.admin.dao.entity.DocInfo; +import com.gitee.sop.admin.dao.mapper.DocAppMapper; +import com.gitee.sop.admin.service.doc.dto.torna.DocIdParam; +import com.gitee.sop.admin.service.doc.dto.torna.DocIdsParam; +import com.gitee.sop.admin.service.doc.dto.torna.TornaDocDTO; +import com.gitee.sop.admin.service.doc.dto.torna.TornaDocInfoDTO; +import com.gitee.sop.admin.service.doc.dto.torna.TornaDocInfoViewDTO; +import com.gitee.sop.admin.service.doc.dto.torna.TornaDocParamDTO; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * 接口信息同步 + * + * @author 六如 + */ +@Service +public class DocInfoSyncService { + + @Autowired + private TornaClient tornaClient; + @Autowired + private DocAppMapper docAppMapper; + @Autowired + private DocInfoService docInfoService; + @Autowired + private DocContentService docContentService; + + public void syncAppDoc(Long docAppId, User user) { + DocApp docApp = docAppMapper.getById(docAppId); + this.syncDocInfo(docApp, null, user); + } + + public void syncDoc(Long docInfoId, User user) { + DocInfo docInfo = docInfoService.getById(docInfoId); + DocApp docApp = docAppMapper.getById(docInfo.getDocAppId()); + this.syncDocInfo(docApp, docInfoId, user); + } + + public void syncDocInfo(DocApp docApp, Long docInfoId, User user) { + Long docAppId = docApp.getId(); + Map nameVersionMap = docInfoService.list(DocInfo::getDocAppId, docAppId) + .stream() + .collect(Collectors.toMap(docInfo -> docInfo.getDocName() + ":" + docInfo.getDocVersion(), Function.identity(), (v1, v2) -> v2)); + + String token = docApp.getToken(); + // add doc + DocIdsParam docIdsParam = buildSearchParam(docInfoId); + TornaDocDTO tornaDocDTO = tornaClient.execute("doc.list", docIdsParam, token, TornaDocDTO.class); + List docList = tornaDocDTO.getDocList(); + if (CollectionUtils.isEmpty(docList)) { + return; + } + + List updateList = new ArrayList<>(); + for (TornaDocInfoDTO tornaDocInfoDTO : docList) { + String key = buildKey(tornaDocInfoDTO); + DocInfo docInfo = nameVersionMap.get(key); + // 需要修改的文档 + if (docInfo != null) { + docInfo.setDocId(tornaDocInfoDTO.getId()); + docInfo.setDocTitle(tornaDocInfoDTO.getName()); + docInfo.setDocCode(""); + if (YesOrNo.yes(tornaDocInfoDTO.getIsFolder())) { + docInfo.setIsPublish(YesOrNo.YES); + docInfo.setDocName(tornaDocInfoDTO.getName()); + } + docInfo.setDocId(tornaDocInfoDTO.getId()); + docInfo.setDocType(tornaDocInfoDTO.getType().intValue()); + docInfo.setDescription(tornaDocInfoDTO.getDescription()); + docInfo.setIsFolder(tornaDocInfoDTO.getIsFolder().intValue()); + docInfo.setParentId(tornaDocInfoDTO.getParentId()); + docInfo.setUpdateBy(user.getUserId()); + updateList.add(docInfo); + } + } + for (DocInfo docInfo : updateList) { + docInfoService.update(docInfo); + } + + // 新增的文档 + List saveList = docList.stream() + .filter(tornaDocInfoDTO -> { + String key = buildKey(tornaDocInfoDTO); + return !nameVersionMap.containsKey(key); + }) + .map(tornaDocInfoDTO -> { + DocInfo docInfo = new DocInfo(); + docInfo.setDocAppId(docAppId); + docInfo.setDocId(tornaDocInfoDTO.getId()); + docInfo.setDocTitle(tornaDocInfoDTO.getName()); + docInfo.setDocCode(""); + docInfo.setDocType(tornaDocInfoDTO.getType().intValue()); + docInfo.setSourceType(DocSourceTypeEnum.TORNA.getValue()); + if (YesOrNo.yes(tornaDocInfoDTO.getIsFolder())) { + docInfo.setIsPublish(YesOrNo.YES); + docInfo.setDocName(tornaDocInfoDTO.getName()); + } else { + docInfo.setIsPublish(YesOrNo.NO); + docInfo.setDocName(tornaDocInfoDTO.getUrl()); + } + docInfo.setDocVersion(tornaDocInfoDTO.getVersion()); + docInfo.setDescription(tornaDocInfoDTO.getDescription()); + docInfo.setIsFolder(tornaDocInfoDTO.getIsFolder().intValue()); + docInfo.setParentId(tornaDocInfoDTO.getParentId()); + docInfo.setAddBy(user.getUserId()); + return docInfo; + }) + .collect(Collectors.toList()); + docInfoService.saveBatch(saveList); + + Set docIds = docList.stream().map(TornaDocInfoDTO::getId).collect(Collectors.toSet()); + this.syncContent(docApp, docIds); + } + + private void syncContent(DocApp docApp, Set docIds) { + List list = docInfoService.query() + .eq(DocInfo::getDocAppId, docApp.getId()) + .in(DocInfo::getDocId, docIds) + .list(); + + Map docIdMap = this.getContentMap(docApp.getToken(), docIds); + for (DocInfo docInfo : list) { + String content = docIdMap.getOrDefault(docInfo.getDocId(), ""); + docContentService.saveContent( + docInfo.getId(), + content + ); + } + } + + /** + * 批量获取Torna文档内容 + * + * @param token token + * @param docIds Torna文档id + * @return key:文档id, value:文档内容 + */ + private Map getContentMap(String token, Collection docIds) { + // 获取torna文档信息 + List tornaDocInfoViewList = tornaClient.executeList( + "doc.details", + new DocIdsParam(docIds), + token, + TornaDocInfoViewDTO.class + ); + for (TornaDocInfoViewDTO docInfoViewDTO : tornaDocInfoViewList) { + convertTree(docInfoViewDTO); + } + return tornaDocInfoViewList.stream() + .collect(Collectors.toMap(TornaDocInfoViewDTO::getId, JSON::toJSONString, (v1, v2) -> v1)); + } + + private String getContent(String token, Long docId) { + // 获取torna文档信息 + TornaDocInfoViewDTO tornaDocInfoViewDTO = tornaClient.execute( + "doc.detail", + new DocIdParam(docId), + token, + TornaDocInfoViewDTO.class + ); + convertTree(tornaDocInfoViewDTO); + return JSON.toJSONString(tornaDocInfoViewDTO); + } + + private void convertTree(TornaDocInfoViewDTO tornaDocInfoViewDTO) { + List requestParams = tornaDocInfoViewDTO.getRequestParams(); + List responseParams = tornaDocInfoViewDTO.getResponseParams(); + List requestTree = TreeUtil.convertTree(requestParams, 0L); + List responseTree = TreeUtil.convertTree(responseParams, 0L); + + tornaDocInfoViewDTO.setRequestParams(requestTree); + tornaDocInfoViewDTO.setResponseParams(responseTree); + } + + private DocIdsParam buildSearchParam(Long docInfoId) { + if (docInfoId == null) { + return null; + } + DocIdsParam docIdsParam = new DocIdsParam(); + DocInfo docInfo = docInfoService.getById(docInfoId); + List docIdList = new ArrayList<>(); + docIdList.add(docInfo.getDocId()); + // 如果是文件夹,找下面的子文档 + if (YesOrNo.yes(docInfo.getIsFolder())) { + List docIds = this.listChildrenDocId(docInfo.getDocId()); + docIdList.addAll(docIds); + } + docIdsParam.setDocIds(docIdList); + return docIdsParam; + } + + private List listChildrenDocId(Long parentId) { + return docInfoService.query() + .eq(DocInfo::getParentId, parentId) + .listValue(DocInfo::getDocId); + } + + private String buildKey(TornaDocInfoDTO tornaDocInfoDTO) { + return YesOrNo.yes(tornaDocInfoDTO.getIsFolder()) ? + tornaDocInfoDTO.getName() + ":" + tornaDocInfoDTO.getVersion() + : tornaDocInfoDTO.getUrl() + ":" + tornaDocInfoDTO.getVersion(); + } + +} diff --git a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/doc/DocSettingService.java b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/doc/DocSettingService.java new file mode 100755 index 00000000..65ed3984 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/doc/DocSettingService.java @@ -0,0 +1,40 @@ +package com.gitee.sop.admin.service.doc; + +import com.gitee.sop.admin.common.enums.ConfigKeyEnum; +import com.gitee.sop.admin.service.doc.dto.DocSettingDTO; +import com.gitee.sop.admin.service.sys.SysConfigService; +import com.gitee.sop.admin.service.sys.dto.SystemConfigDTO; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.Collection; + + +/** + * @author 六如 + */ +@Service +public class DocSettingService { + + @Autowired + private SysConfigService sysConfigService; + + public DocSettingDTO getDocSetting() { + DocSettingDTO docSettingDTO = new DocSettingDTO(); + docSettingDTO.setTornaServerAddr(ConfigKeyEnum.TORNA_SERVER_ADDR.getValue()); + docSettingDTO.setOpenProdUrl(ConfigKeyEnum.OPEN_PROD_URL.getValue()); + docSettingDTO.setOpenSandboxUrl(ConfigKeyEnum.OPEN_SANDBOX_URL.getValue()); + return docSettingDTO; + } + + public void save(DocSettingDTO docSettingDTO) { + Collection systemConfigDTOS = new ArrayList<>(); + systemConfigDTOS.add(new SystemConfigDTO(ConfigKeyEnum.TORNA_SERVER_ADDR.getKey(), docSettingDTO.getTornaServerAddr(), "Torna服务器地址")); + systemConfigDTOS.add(new SystemConfigDTO(ConfigKeyEnum.OPEN_PROD_URL.getKey(), docSettingDTO.getOpenProdUrl(), "开放平台线上地址")); + systemConfigDTOS.add(new SystemConfigDTO(ConfigKeyEnum.OPEN_SANDBOX_URL.getKey(), docSettingDTO.getOpenSandboxUrl(), "开放平台沙箱地址")); + sysConfigService.save(systemConfigDTOS); + } + + +} diff --git a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/doc/TornaClient.java b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/doc/TornaClient.java new file mode 100755 index 00000000..3ac63446 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/doc/TornaClient.java @@ -0,0 +1,67 @@ +package com.gitee.sop.admin.service.doc; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import com.gitee.httphelper.HttpHelper; +import com.gitee.sop.admin.common.enums.ConfigKeyEnum; +import com.gitee.sop.admin.common.exception.BizException; +import org.springframework.stereotype.Component; +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; +import org.springframework.web.util.UriUtils; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +/** + * @author 六如 + */ +@Component +public class TornaClient { + + public T execute(String name, Object param, String token, Class respClass) { + JSONObject data = request(name, param, token).getJSONObject("data"); + return data.toJavaObject(respClass); + } + + public List executeList(String name, Object param, String token, Class respClass) { + JSONArray data = request(name, param, token).getJSONArray("data"); + return data.toList(respClass); + } + + private JSONObject request(String name, Object param, String token) { + Map params = new HashMap<>(); + params.put("name", name); + params.put("access_token", token); + if (param != null) { + String json = JSON.toJSONString(param); + params.put("data", UriUtils.encode(json, StandardCharsets.UTF_8)); + } + try { + String body = HttpHelper.postJson(getTornaApiUrl(), JSON.toJSONString(params)) + .execute() + .asString(); + JSONObject jsonObject = JSON.parseObject(body); + if (!Objects.equals("0", jsonObject.getString("code"))) { + throw new BizException(jsonObject.getString("msg")); + } + return jsonObject; + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public String getTornaApiUrl() { + String value = ConfigKeyEnum.TORNA_SERVER_ADDR.getValue(); + if (ObjectUtils.isEmpty(value)) { + throw new BizException("Torna服务器地址未配置"); + } + return StringUtils.trimTrailingCharacter(value, '/') + "/api"; + } + +} diff --git a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/doc/dto/DocAppDTO.java b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/doc/dto/DocAppDTO.java new file mode 100755 index 00000000..2757c180 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/doc/dto/DocAppDTO.java @@ -0,0 +1,52 @@ +package com.gitee.sop.admin.service.doc.dto; + +import lombok.Data; + +import java.time.LocalDateTime; + + +/** + * 备注:文档应用 + * + * @author 六如 + */ +@Data +public class DocAppDTO { + + /** + * id + */ + private Long id; + + /** + * 应用名称 + */ + private String appName; + + /** + * Torna应用token + */ + private String token; + + /** + * 添加时间 + */ + private LocalDateTime addTime; + + /** + * 修改时间 + */ + private LocalDateTime updateTime; + + /** + * 创建人id + */ + private Long addBy; + + /** + * 修改人id + */ + private Long updateBy; + + +} diff --git a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/doc/dto/DocInfoConfigDTO.java b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/doc/dto/DocInfoConfigDTO.java new file mode 100755 index 00000000..dbd0b792 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/doc/dto/DocInfoConfigDTO.java @@ -0,0 +1,14 @@ +package com.gitee.sop.admin.service.doc.dto; + +import lombok.Data; + +/** + * @author 六如 + */ +@Data +public class DocInfoConfigDTO { + + private String openProdUrl; + private String openSandboxUrl; + +} diff --git a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/doc/dto/DocInfoPublishUpdateDTO.java b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/doc/dto/DocInfoPublishUpdateDTO.java new file mode 100755 index 00000000..59e958b1 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/doc/dto/DocInfoPublishUpdateDTO.java @@ -0,0 +1,21 @@ +package com.gitee.sop.admin.service.doc.dto; + +import lombok.Data; + +import javax.validation.constraints.NotNull; + +/** + * @author 六如 + */ +@Data +public class DocInfoPublishUpdateDTO { + + @NotNull + private Long id; + + /** + * 1-发布,0-下线 + */ + private Integer isPublish; + +} diff --git a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/doc/dto/DocInfoTreeDTO.java b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/doc/dto/DocInfoTreeDTO.java new file mode 100755 index 00000000..9ec3845d --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/doc/dto/DocInfoTreeDTO.java @@ -0,0 +1,107 @@ +package com.gitee.sop.admin.service.doc.dto; + +import com.gitee.fastmybatis.core.support.TreeNode; +import com.gitee.sop.admin.common.dto.UserDTO; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; + + +/** + * 备注:文档信息 + * + * @author 六如 + */ +@Data +public class DocInfoTreeDTO extends UserDTO implements TreeNode { + + /** + * id + */ + private Long id; + + /** + * doc_app.id + */ + private Long docAppId; + + /** + * 文档id + */ + private Long docId; + + /** + * 文档标题 + */ + private String docTitle; + + /** + * 文档code + */ + private String docCode; + + /** + * 文档类型,1-dubbo,2-富文本,3-Markdown + */ + private Integer docType; + + /** + * 来源类型,1-torna,2-自建 + */ + private Integer sourceType; + + /** + * 文档名称 + */ + private String docName; + + /** + * 版本号 + */ + private String docVersion; + + /** + * 描述 + */ + private String description; + + /** + * 是否分类 + */ + private Integer isFolder; + + /** + * 状态, 0-未发布,1-已发布 + */ + private Integer isPublish; + + /** + * 父节点id + */ + private Long parentId; + + /** + * 添加时间 + */ + private LocalDateTime addTime; + + /** + * 修改时间 + */ + private LocalDateTime updateTime; + + private List children; + + + @Override + public Long takeId() { + return docId; + } + + @Override + public Long takeParentId() { + return parentId; + } + +} diff --git a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/doc/dto/DocInfoViewDTO.java b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/doc/dto/DocInfoViewDTO.java new file mode 100755 index 00000000..a9b8413f --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/doc/dto/DocInfoViewDTO.java @@ -0,0 +1,16 @@ +package com.gitee.sop.admin.service.doc.dto; + +import com.gitee.sop.admin.service.doc.dto.torna.TornaDocInfoViewDTO; +import lombok.Data; + +/** + * @author 六如 + */ +@Data +public class DocInfoViewDTO { + + private TornaDocInfoViewDTO docInfoView; + + private DocInfoConfigDTO docInfoConfig; + +} diff --git a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/doc/dto/DocSettingDTO.java b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/doc/dto/DocSettingDTO.java new file mode 100755 index 00000000..ef55fbfe --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/doc/dto/DocSettingDTO.java @@ -0,0 +1,20 @@ +package com.gitee.sop.admin.service.doc.dto; + +import lombok.Data; +import org.hibernate.validator.constraints.Length; + +/** + * @author 六如 + */ +@Data +public class DocSettingDTO { + + @Length(max = 256) + private String tornaServerAddr; + @Length(max = 256) + private String openProdUrl; + @Length(max = 256) + private String openSandboxUrl; + + +} diff --git a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/doc/dto/torna/DocIdParam.java b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/doc/dto/torna/DocIdParam.java new file mode 100755 index 00000000..c2129417 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/doc/dto/torna/DocIdParam.java @@ -0,0 +1,13 @@ +package com.gitee.sop.admin.service.doc.dto.torna; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class DocIdParam { + + private Long docId; +} diff --git a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/doc/dto/torna/DocIdsParam.java b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/doc/dto/torna/DocIdsParam.java new file mode 100755 index 00000000..1a91ecba --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/doc/dto/torna/DocIdsParam.java @@ -0,0 +1,22 @@ +package com.gitee.sop.admin.service.doc.dto.torna; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.validation.constraints.NotNull; +import java.util.Collection; + +/** + * @author 六如 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class DocIdsParam { + + @NotNull + private Collection docIds; + + +} diff --git a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/doc/dto/torna/TornaDocDTO.java b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/doc/dto/torna/TornaDocDTO.java new file mode 100755 index 00000000..c17cf171 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/doc/dto/torna/TornaDocDTO.java @@ -0,0 +1,15 @@ +package com.gitee.sop.admin.service.doc.dto.torna; + +import lombok.Data; + +import java.util.List; + +/** + * @author 六如 + */ +@Data +public class TornaDocDTO { + + private List docList; + +} diff --git a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/doc/dto/torna/TornaDocInfoDTO.java b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/doc/dto/torna/TornaDocInfoDTO.java new file mode 100755 index 00000000..8b48aacf --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/doc/dto/torna/TornaDocInfoDTO.java @@ -0,0 +1,62 @@ +package com.gitee.sop.admin.service.doc.dto.torna; + +import lombok.Data; + +/** + * @author tanghc + */ +@Data +public class TornaDocInfoDTO { + private Long id; + + /** + * 文档名称 + */ + private String name; + + /** + * 文档概述 + */ + private String description; + + /** + * 访问URL + */ + private String url; + + /** + * 版本号 + */ + private String version; + + /** + * http方法 + */ + private String httpMethod; + + /** + * contentType + */ + private String contentType; + + /** + * 文档类型,0:http,1:dubbo,2:富文本,3:Markdown + */ + private Byte type; + + /** + * 是否是分类,0:不是,1:是 + */ + private Byte isFolder; + + /** + * 父节点 + */ + private Long parentId; + + /** + * 是否显示 + */ + private Byte isShow; + +} diff --git a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/doc/dto/torna/TornaDocInfoViewDTO.java b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/doc/dto/torna/TornaDocInfoViewDTO.java new file mode 100755 index 00000000..66794e92 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/doc/dto/torna/TornaDocInfoViewDTO.java @@ -0,0 +1,178 @@ +package com.gitee.sop.admin.service.doc.dto.torna; + +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.Collections; +import java.util.List; + +/** + * @author tanghc + */ +@Data +public class TornaDocInfoViewDTO { + private Long id; + + /** + * 文档名称 + */ + private String name; + + /** + * 文档概述 + */ + + private String description; + + /** + * 0:http,1:dubbo,2:富文本,3:Markdown + */ + private Byte type; + + /** + * 访问URL + */ + + private String url; + + /** + * 版本号 + */ + private String version = ""; + + private String docKey; + + /** + * http方法 + */ + + private String httpMethod; + + /** + * contentType + */ + + private String contentType; + + + /** + * 是否是分类,0:不是,1:是 + */ + private Byte isFolder; + + /** + * 父节点 + */ + + private Long parentId; + + /** + * 模块id,module.id + */ + + private Long moduleId; + + /** + * 项目id + */ + + private Long projectId; + + /** + * 是否使用全局请求参数 + */ + + private Byte isUseGlobalHeaders; + + /** + * 是否使用全局请求参数 + */ + + private Byte isUseGlobalParams; + + /** + * 是否使用全局返回参数 + */ + + private Byte isUseGlobalReturns; + + /** + * 是否请求数组 + */ + + private Byte isRequestArray; + + /** + * 是否返回数组 + */ + + private Byte isResponseArray; + + /** + * 请求数组时元素类型 + */ + + private String requestArrayType; + + /** + * 返回数组时元素类型 + */ + + private String responseArrayType; + + /** + * 文档状态 + */ + + private Byte status; + + private String remark; + + private Integer orderIndex; + + /** + * 数据库字段:gmt_create + */ + private LocalDateTime gmtCreate; + + /** + * 数据库字段:gmt_modified + */ + private LocalDateTime gmtModified; + + + private List pathParams = Collections.emptyList(); + + + private List headerParams = Collections.emptyList(); + + private List headerParamsRaw = Collections.emptyList(); + + + private List queryParams = Collections.emptyList(); + + + private List requestParams = Collections.emptyList(); + + + private List responseParams = Collections.emptyList(); + + private List errorCodeParams = Collections.emptyList(); + + private List globalHeaders = Collections.emptyList(); + private List globalParams = Collections.emptyList(); + private List globalReturns = Collections.emptyList(); + + private String errorCodeInfo; + + private List children = Collections.emptyList(); + + public String getDocName() { + return name; + } + + public String getDocTitle() { + return name; + } + +} diff --git a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/doc/dto/torna/TornaDocParamDTO.java b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/doc/dto/torna/TornaDocParamDTO.java new file mode 100755 index 00000000..0526be35 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/doc/dto/torna/TornaDocParamDTO.java @@ -0,0 +1,128 @@ +package com.gitee.sop.admin.service.doc.dto.torna; + +import com.gitee.fastmybatis.core.support.TreeNode; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Objects; + +/** + * @author tanghc + */ +@Data +public class TornaDocParamDTO implements TreeNode { + + private Long id; + + /** + * 字段名称 + */ + + private String name; + + /** + * 字段类型 + */ + + private String type; + + /** + * 是否必须,1:是,0:否 + */ + + private Byte required; + + /** + * 最大长度 + */ + + private String maxLength; + + /** + * 示例值 + */ + private String example; + + /** + * 描述 + */ + + private String description; + + + private Long enumId; + + /** + * doc_info.id + */ + + private Long docId; + + /** + * 父节点 + */ + + private Long parentId; + + /** + * 0:header, 1:请求参数,2:返回参数,3:错误码 + */ + private Byte style; + + /** + * 新增操作方式,0:人工操作,1:开放平台推送 + */ + private Byte createMode; + + /** + * 修改操作方式,0:人工操作,1:开放平台推送 + */ + private Byte modifyMode; + + /** + * 创建人 + */ + private String creatorName; + + /** + * 修改人 + */ + private String modifierName; + + /** + * 排序 + */ + private Integer orderIndex; + + private Byte isDeleted; + + /** + * 数据库字段:gmt_create + */ + private LocalDateTime gmtCreate; + + /** + * 数据库字段:gmt_modified + */ + private LocalDateTime gmtModified; + + private boolean global; + + private List children; + + + public boolean getRequire() { + return Objects.equals(this.required, 1); + } + + @Override + public Long takeId() { + return id; + } + + @Override + public Long takeParentId() { + return parentId; + } +} diff --git a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/doc/dto/torna/TornaModuleDTO.java b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/doc/dto/torna/TornaModuleDTO.java new file mode 100755 index 00000000..418139d7 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/doc/dto/torna/TornaModuleDTO.java @@ -0,0 +1,15 @@ +package com.gitee.sop.admin.service.doc.dto.torna; + +import lombok.Data; + +/** + * @author 六如 + */ +@Data +public class TornaModuleDTO { + + private Long id; + + private String name; + +} diff --git a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/isv/IsvInfoService.java b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/isv/IsvInfoService.java new file mode 100755 index 00000000..271af57e --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/isv/IsvInfoService.java @@ -0,0 +1,166 @@ +package com.gitee.sop.admin.service.isv; + +import com.gitee.fastmybatis.core.PageInfo; +import com.gitee.fastmybatis.core.query.LambdaQuery; +import com.gitee.sop.admin.common.constants.YesOrNo; +import com.gitee.sop.admin.common.context.SpringContext; +import com.gitee.sop.admin.common.dto.StatusUpdateDTO; +import com.gitee.sop.admin.common.enums.StatusEnum; +import com.gitee.sop.admin.common.support.ServiceSupport; +import com.gitee.sop.admin.common.util.CopyUtil; +import com.gitee.sop.admin.common.util.IdGen; +import com.gitee.sop.admin.common.util.RSATool; +import com.gitee.sop.admin.dao.entity.IsvInfo; +import com.gitee.sop.admin.dao.entity.IsvKeys; +import com.gitee.sop.admin.dao.mapper.IsvInfoMapper; +import com.gitee.sop.admin.service.isv.dto.IsvInfoAddDTO; +import com.gitee.sop.admin.service.isv.dto.IsvInfoDTO; +import com.gitee.sop.admin.service.isv.dto.IsvInfoUpdateDTO; +import com.gitee.sop.admin.service.isv.dto.IsvInfoUpdateKeysDTO; +import com.gitee.sop.admin.service.isv.dto.IsvKeysDTO; +import com.gitee.sop.admin.service.isv.event.ChangeIsvInfoEvent; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; + +import java.text.SimpleDateFormat; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Collectors; + + +/** + * @author 六如 + */ +@Service +public class IsvInfoService implements ServiceSupport { + + @Autowired + private IsvKeysService isvKeysService; + @Autowired + private PermIsvGroupService permIsvGroupService; + + public PageInfo doPage(LambdaQuery query) { + query.orderByDesc(IsvInfo::getId); + PageInfo page = this.page(query); + List list = page.getList(); + if (CollectionUtils.isEmpty(list)) { + return page.convert(isvInfo -> new IsvInfoDTO()); + } + List idList = list.stream() + .map(IsvInfo::getId).collect(Collectors.toList()); + + Map isvIdMap = isvKeysService.query() + .in(IsvKeys::getIsvId, idList) + .map(IsvKeys::getIsvId, Function.identity()); + + Map> isvGroupMap = permIsvGroupService.getIsvGroupNameMap(idList); + + // 格式转换 + return page.convert(isvInfo -> { + IsvInfoDTO isvInfoDTO = CopyUtil.copyBean(isvInfo, IsvInfoDTO::new); + boolean hasKey = false; + Optional isvKeysOpt = Optional.ofNullable(isvIdMap.get(isvInfo.getId())); + if (isvKeysOpt.isPresent()) { + IsvKeys isvKeys = isvKeysOpt.get(); + hasKey = !StringUtils.isAllBlank( + isvKeys.getPrivateKeyIsv(), + isvKeys.getPrivateKeyPlatform(), + isvKeys.getPublicKeyIsv(), + isvKeys.getPublicKeyPlatform() + ); + } + isvInfoDTO.setHasKeys(YesOrNo.of(hasKey)); + List groupNames = isvGroupMap.getOrDefault(isvInfo.getId(), Collections.emptyList()); + isvInfoDTO.setGroupNames(String.join("/", groupNames)); + return isvInfoDTO; + }); + } + + public RSATool.KeyStore createKeys(RSATool.KeyFormat keyFormat) throws Exception { + if (keyFormat == null) { + keyFormat = RSATool.KeyFormat.PKCS8; + } + RSATool rsaTool = new RSATool(keyFormat, RSATool.KeyLength.LENGTH_2048); + return rsaTool.createKeys(); + } + + /** + * 添加ISV + * + * @param isvInfoAddDTO + * @return 返回id + */ + public long add(IsvInfoAddDTO isvInfoAddDTO) { + IsvInfo rec = CopyUtil.copyBean(isvInfoAddDTO, IsvInfo::new); + String appKey = new SimpleDateFormat("yyyyMMdd").format(new Date()) + IdGen.nextId(); + rec.setAppId(appKey); + rec.setStatus(StatusEnum.ENABLE.getValue()); + this.save(rec); + this.sendChangeEvent(rec.getId()); + return rec.getId(); + } + + public int update(IsvInfoUpdateDTO isvInfoUpdateDTO) { + IsvInfo isvInfo = new IsvInfo(); + isvInfo.setId(isvInfoUpdateDTO.getId()); + isvInfo.setStatus(isvInfoUpdateDTO.getStatus()); + isvInfo.setRemark(isvInfoUpdateDTO.getRemark()); + int cnt = this.update(isvInfo); + sendChangeEvent(isvInfoUpdateDTO.getId()); + return cnt; + } + + public int updateKeys(IsvInfoUpdateKeysDTO isvInfoUpdateKeysDTO) { + return isvKeysService.saveKeys(isvInfoUpdateKeysDTO); + } + + public IsvKeysDTO getKeys(Long isvId) { + IsvKeys isvKeys = isvKeysService.get(IsvKeys::getIsvId, isvId); + IsvKeysDTO isvKeysDTO; + if (isvKeys == null) { + isvKeysDTO = new IsvKeysDTO(); + isvKeysDTO.setIsvId(isvId); + isvKeysDTO.setKeyFormat(RSATool.KeyFormat.PKCS8.getValue()); + isvKeysDTO.setPublicKeyIsv(""); + isvKeysDTO.setPrivateKeyIsv(""); + isvKeysDTO.setPublicKeyPlatform(""); + isvKeysDTO.setPrivateKeyPlatform(""); + } else { + isvKeysDTO = CopyUtil.copyBean(isvKeys, IsvKeysDTO::new); + } + + IsvInfo isvInfo = this.getById(isvId); + isvKeysDTO.setAppId(isvInfo.getAppId()); + + return isvKeysDTO; + } + + /** + * 修改状态 + * + * @param statusUpdateDTO 修改值 + * @return 返回影响行数 + */ + public int updateStatus(StatusUpdateDTO statusUpdateDTO) { + Long isvId = statusUpdateDTO.getId(); + int cnt = this.query() + .eq(IsvInfo::getId, isvId) + .set(IsvInfo::getStatus, statusUpdateDTO.getStatus()) + .update(); + // 同步isv信息 + this.sendChangeEvent(isvId); + return cnt; + } + + private void sendChangeEvent(Long isvId) { + SpringContext.publishEvent(new ChangeIsvInfoEvent(Collections.singletonList(isvId))); + } + + +} diff --git a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/isv/IsvKeysService.java b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/isv/IsvKeysService.java new file mode 100755 index 00000000..539b62f6 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/isv/IsvKeysService.java @@ -0,0 +1,33 @@ +package com.gitee.sop.admin.service.isv; + +import com.gitee.sop.admin.common.context.SpringContext; +import com.gitee.sop.admin.common.support.ServiceSupport; +import com.gitee.sop.admin.common.util.CopyUtil; +import com.gitee.sop.admin.dao.entity.IsvKeys; +import com.gitee.sop.admin.dao.mapper.IsvKeysMapper; +import com.gitee.sop.admin.service.isv.dto.IsvInfoUpdateKeysDTO; +import com.gitee.sop.admin.service.isv.event.ChangeIsvKeyEvent; +import org.springframework.stereotype.Service; + +import java.util.Collections; + + +/** + * @author 六如 + */ +@Service +public class IsvKeysService implements ServiceSupport { + + public int saveKeys(IsvInfoUpdateKeysDTO isvInfoUpdateKeysDTO) { + IsvKeys isvKeys = this.get(IsvKeys::getIsvId, isvInfoUpdateKeysDTO.getIsvId()); + if (isvKeys == null) { + isvKeys = new IsvKeys(); + } + CopyUtil.copyPropertiesIgnoreNull(isvInfoUpdateKeysDTO, isvKeys); + int cnt = this.saveOrUpdate(isvKeys); + // 发送变更事件 + SpringContext.publishEvent(new ChangeIsvKeyEvent(Collections.singletonList(isvInfoUpdateKeysDTO.getIsvId()))); + return cnt; + } + +} diff --git a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/isv/PermGroupPermissionService.java b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/isv/PermGroupPermissionService.java new file mode 100755 index 00000000..603b1d9e --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/isv/PermGroupPermissionService.java @@ -0,0 +1,114 @@ +package com.gitee.sop.admin.service.isv; + +import com.gitee.fastmybatis.core.PageInfo; +import com.gitee.fastmybatis.core.query.LambdaQuery; +import com.gitee.sop.admin.common.context.SpringContext; +import com.gitee.sop.admin.common.support.ServiceSupport; +import com.gitee.sop.admin.dao.entity.ApiInfo; +import com.gitee.sop.admin.dao.entity.PermGroupPermission; +import com.gitee.sop.admin.dao.entity.PermIsvGroup; +import com.gitee.sop.admin.dao.mapper.ApiInfoMapper; +import com.gitee.sop.admin.dao.mapper.PermGroupPermissionMapper; +import com.gitee.sop.admin.dao.mapper.PermIsvGroupMapper; +import com.gitee.sop.admin.service.isv.dto.PermGroupPermissionDTO; +import com.gitee.sop.admin.service.isv.event.ChangeIsvPermEvent; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; + +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + + +/** + * @author 六如 + */ +@Service +public class PermGroupPermissionService implements ServiceSupport { + + @Autowired + private ApiInfoMapper apiInfoMapper; + @Autowired + private PermIsvGroupMapper permIsvGroupMapper; + + public PageInfo doPage(LambdaQuery query) { + query.orderByDesc(PermGroupPermission::getId); + PageInfo page = this.page(query); + + // 格式转换 + return page.convert(isvInfo -> { + + return isvInfo; + }); + } + + public PageInfo pageGroupApiId(Long groupId, LambdaQuery query) { + List apiIds = this.query() + .eq(PermGroupPermission::getGroupId, groupId) + .listUniqueValue(PermGroupPermission::getApiId); + + if (apiIds.isEmpty()) { + return new PageInfo<>(); + } + + query.in(ApiInfo::getId, apiIds); + + return apiInfoMapper.page(query); + } + + /** + * 设置组权限 + * + * @param param + * @return + */ + public int setting(PermGroupPermissionDTO param) { + Long groupId = param.getGroupId(); + List apiIdList = param.getApiIdList(); + if (CollectionUtils.isEmpty(apiIdList)) { + return 0; + } + + List existApiIdList = this.query() + .eq(PermGroupPermission::getGroupId, groupId) + .listUniqueValue(PermGroupPermission::getApiId); + + List saveList = apiIdList.stream() + // 已存在的不添加 + .filter(apiId -> !existApiIdList.contains(apiId)) + .map(apiId -> { + PermGroupPermission permGroupPermission = new PermGroupPermission(); + permGroupPermission.setGroupId(groupId); + permGroupPermission.setApiId(apiId); + return permGroupPermission; + }) + .collect(Collectors.toList()); + int cnt = this.saveBatch(saveList); + + // 刷新isv权限 + this.refreshIsvPerm(groupId); + + return cnt; + } + + private void refreshIsvPerm(Long groupId) { + List isvIds = permIsvGroupMapper.query() + .eq(PermIsvGroup::getGroupId, groupId) + .listUniqueValue(PermIsvGroup::getIsvId); + + SpringContext.publishEvent(new ChangeIsvPermEvent(isvIds)); + } + + + public int delete(Long groupId, Collection apiIds) { + int cnt = this.query() + .eq(PermGroupPermission::getGroupId, groupId) + .in(PermGroupPermission::getApiId, apiIds) + .delete(); + + this.refreshIsvPerm(groupId); + + return cnt; + } +} diff --git a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/isv/PermGroupService.java b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/isv/PermGroupService.java new file mode 100755 index 00000000..102e3cab --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/isv/PermGroupService.java @@ -0,0 +1,55 @@ +package com.gitee.sop.admin.service.isv; + +import com.gitee.fastmybatis.core.PageInfo; +import com.gitee.fastmybatis.core.query.LambdaQuery; +import com.gitee.sop.admin.common.exception.BizException; +import com.gitee.sop.admin.common.support.ServiceSupport; +import com.gitee.sop.admin.dao.entity.PermGroup; +import com.gitee.sop.admin.dao.entity.PermIsvGroup; +import com.gitee.sop.admin.dao.mapper.PermGroupMapper; +import com.gitee.sop.admin.dao.mapper.PermIsvGroupMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.Collection; +import java.util.Map; + + +/** + * @author 六如 + */ +@Service +public class PermGroupService implements ServiceSupport { + + @Autowired + PermIsvGroupMapper permIsvGroupMapper; + + public PageInfo doPage(LambdaQuery query) { + query.orderByDesc(PermGroup::getId); + PageInfo page = this.page(query); + + // 格式转换 + return page.convert(isvInfo -> { + + return isvInfo; + }); + } + + public Map getCodeNameMap(Collection groupIdList) { + return this.query() + .in(PermGroup::getId, groupIdList) + .map(PermGroup::getId, PermGroup::getGroupName); + } + + public int delete(Long groupId) { + boolean used = permIsvGroupMapper.query() + .eq(PermIsvGroup::getGroupId, groupId) + .get() != null; + if (used) { + throw new BizException("无法删除:分组已被使用"); + } + return this.deleteById(groupId); + } + + +} diff --git a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/isv/PermIsvGroupService.java b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/isv/PermIsvGroupService.java new file mode 100755 index 00000000..eb987911 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/isv/PermIsvGroupService.java @@ -0,0 +1,90 @@ +package com.gitee.sop.admin.service.isv; + +import com.gitee.fastmybatis.core.PageInfo; +import com.gitee.fastmybatis.core.query.LambdaQuery; +import com.gitee.sop.admin.common.support.ServiceSupport; +import com.gitee.sop.admin.dao.entity.PermIsvGroup; +import com.gitee.sop.admin.dao.mapper.PermIsvGroupMapper; +import com.gitee.sop.admin.service.isv.dto.IsvGroupSettingDTO; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.CollectionUtils; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + + +/** + * @author 六如 + */ +@Service +public class PermIsvGroupService implements ServiceSupport { + + @Autowired + private PermGroupService permGroupService; + + public PageInfo doPage(LambdaQuery query) { + query.orderByDesc(PermIsvGroup::getId); + PageInfo page = this.page(query); + + // 格式转换 + return page.convert(isvInfo -> { + + return isvInfo; + }); + } + + public Map> getIsvGroupNameMap(Collection isvIds) { + List list = this.list(PermIsvGroup::getIsvId, isvIds); + if (list.isEmpty()) { + return new HashMap<>(); + } + Set groupIds = list.stream().map(PermIsvGroup::getGroupId).collect(Collectors.toSet()); + Map groupCodeMap = permGroupService.getCodeNameMap(groupIds); + return this.query() + .in(PermIsvGroup::getIsvId, isvIds) + .group(PermIsvGroup::getIsvId, + permIsvGroup -> groupCodeMap.getOrDefault(permIsvGroup.getGroupId(), "")); + } + + @Transactional(rollbackFor = Exception.class) + public int updateIsvGroup(IsvGroupSettingDTO isvGroupSettingDTO) { + // 先删除所有 + int i = this.deleteByColumn(PermIsvGroup::getIsvId, isvGroupSettingDTO.getIsvId()); + List groupIds = isvGroupSettingDTO.getGroupIds(); + if (CollectionUtils.isEmpty(groupIds)) { + return i; + } + List saveList = groupIds + .stream() + .map(groupId -> { + PermIsvGroup permIsvGroup = new PermIsvGroup(); + permIsvGroup.setIsvId(isvGroupSettingDTO.getIsvId()); + permIsvGroup.setGroupId(groupId); + return permIsvGroup; + }).collect(Collectors.toList()); + + return this.saveBatch(saveList); + } + + public List listIsvGroupId(Long isvId) { + List list = this.list(PermIsvGroup::getIsvId, isvId); + if (list.isEmpty()) { + return new ArrayList<>(0); + } + return list.stream().map(PermIsvGroup::getGroupId).distinct().collect(Collectors.toList()); + } + + public boolean isUsed(Long groupId) { + return this.query() + .eq(PermIsvGroup::getGroupId, groupId) + .getCount() > 0; + } + +} diff --git a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/isv/dto/IsvGroupSettingDTO.java b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/isv/dto/IsvGroupSettingDTO.java new file mode 100755 index 00000000..bb496996 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/isv/dto/IsvGroupSettingDTO.java @@ -0,0 +1,19 @@ +package com.gitee.sop.admin.service.isv.dto; + +import lombok.Data; + +import javax.validation.constraints.NotNull; +import java.util.List; + +/** + * @author 六如 + */ +@Data +public class IsvGroupSettingDTO { + + @NotNull + private Long isvId; + + private List groupIds; + +} diff --git a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/isv/dto/IsvInfoAddDTO.java b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/isv/dto/IsvInfoAddDTO.java new file mode 100755 index 00000000..b7935343 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/isv/dto/IsvInfoAddDTO.java @@ -0,0 +1,27 @@ +package com.gitee.sop.admin.service.isv.dto; + +import lombok.Data; +import org.hibernate.validator.constraints.Length; + +import javax.validation.constraints.NotNull; + + +/** + * @author 六如 + */ +@Data +public class IsvInfoAddDTO { + + /** + * 1启用,2禁用 + */ + @NotNull + private Integer status; + + /** + * 备注 + */ + @Length(max = 500) + private String remark; + +} diff --git a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/isv/dto/IsvInfoDTO.java b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/isv/dto/IsvInfoDTO.java new file mode 100755 index 00000000..46a81e96 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/isv/dto/IsvInfoDTO.java @@ -0,0 +1,56 @@ +package com.gitee.sop.admin.service.isv.dto; + +import com.gitee.sop.admin.service.jackson.convert.annotation.UserFormat; +import lombok.Data; + +import java.time.LocalDateTime; + + +/** + * + * @author 六如 + */ +@Data +public class IsvInfoDTO { + + private Long id; + + /** + * appKey + */ + private String appId; + + /** + * 1启用,2禁用 + */ + private Integer status; + + /** + * 是否有秘钥 + */ + private Integer hasKeys; + + /** + * 备注 + */ + private String remark; + + private String groupNames; + + private LocalDateTime addTime; + + private LocalDateTime updateTime; + + /** + * 创建人id + */ + @UserFormat + private Long addBy; + + /** + * 修改人id + */ + @UserFormat + private Long updateBy; + +} diff --git a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/isv/dto/IsvInfoUpdateDTO.java b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/isv/dto/IsvInfoUpdateDTO.java new file mode 100755 index 00000000..6c65d94d --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/isv/dto/IsvInfoUpdateDTO.java @@ -0,0 +1,14 @@ +package com.gitee.sop.admin.service.isv.dto; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * @author 六如 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class IsvInfoUpdateDTO extends IsvInfoAddDTO { + private Long id; + +} diff --git a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/isv/dto/IsvInfoUpdateKeysDTO.java b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/isv/dto/IsvInfoUpdateKeysDTO.java new file mode 100755 index 00000000..1c1e2891 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/isv/dto/IsvInfoUpdateKeysDTO.java @@ -0,0 +1,38 @@ +package com.gitee.sop.admin.service.isv.dto; + +import lombok.Data; + + +/** + * @author 六如 + */ +@Data +public class IsvInfoUpdateKeysDTO { + + private Long isvId; + + /** + * 秘钥格式,1:PKCS8(JAVA适用),2:PKCS1(非JAVA适用) + */ + private Integer keyFormat; + + /** + * 开发者生成的公钥, 数据库字段:public_key_isv + */ + private String publicKeyIsv; + + /** + * 开发者生成的私钥(交给开发者), 数据库字段:private_key_isv + */ + private String privateKeyIsv; + + /** + * 平台生成的公钥(交给开发者), 数据库字段:public_key_platform + */ + private String publicKeyPlatform; + + /** + * 平台生成的私钥, 数据库字段:private_key_platform + */ + private String privateKeyPlatform; +} diff --git a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/isv/dto/IsvKeysDTO.java b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/isv/dto/IsvKeysDTO.java new file mode 100755 index 00000000..e143709f --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/isv/dto/IsvKeysDTO.java @@ -0,0 +1,40 @@ +package com.gitee.sop.admin.service.isv.dto; + +import lombok.Data; + +/** + * @author 六如 + */ +@Data +public class IsvKeysDTO { + + private String appId; + + private Long isvId; + + /** + * 秘钥格式,1:PKCS8(JAVA适用),2:PKCS1(非JAVA适用) + */ + private Integer keyFormat; + + /** + * 开发者生成的公钥, 数据库字段:public_key_isv + */ + private String publicKeyIsv; + + /** + * 开发者生成的私钥(交给开发者), 数据库字段:private_key_isv + */ + private String privateKeyIsv; + + /** + * 平台生成的公钥(交给开发者), 数据库字段:public_key_platform + */ + private String publicKeyPlatform; + + /** + * 平台生成的私钥, 数据库字段:private_key_platform + */ + private String privateKeyPlatform; + +} diff --git a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/isv/dto/IsvKeysGenDTO.java b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/isv/dto/IsvKeysGenDTO.java new file mode 100755 index 00000000..392a8035 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/isv/dto/IsvKeysGenDTO.java @@ -0,0 +1,31 @@ +package com.gitee.sop.admin.service.isv.dto; + +import lombok.Data; + +/** + * @author 六如 + */ +@Data +public class IsvKeysGenDTO { + + /** + * 开发者生成的公钥, 数据库字段:public_key_isv + */ + private String publicKeyIsv; + + /** + * 开发者生成的私钥(交给开发者), 数据库字段:private_key_isv + */ + private String privateKeyIsv; + + /** + * 平台生成的公钥(交给开发者), 数据库字段:public_key_platform + */ + private String publicKeyPlatform; + + /** + * 平台生成的私钥, 数据库字段:private_key_platform + */ + private String privateKeyPlatform; + +} diff --git a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/isv/dto/PermGroupPermissionDTO.java b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/isv/dto/PermGroupPermissionDTO.java new file mode 100755 index 00000000..b5e45438 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/isv/dto/PermGroupPermissionDTO.java @@ -0,0 +1,19 @@ +package com.gitee.sop.admin.service.isv.dto; + +import lombok.Data; + +import javax.validation.constraints.NotNull; +import java.util.List; + +/** + * @author 六如 + */ +@Data +public class PermGroupPermissionDTO { + + @NotNull + private Long groupId; + + private List apiIdList; + +} diff --git a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/isv/event/ChangeApiInfoEvent.java b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/isv/event/ChangeApiInfoEvent.java new file mode 100755 index 00000000..d26f99bc --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/isv/event/ChangeApiInfoEvent.java @@ -0,0 +1,17 @@ +package com.gitee.sop.admin.service.isv.event; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Collection; + +/** + * @author 六如 + */ +@Getter +@AllArgsConstructor +public class ChangeApiInfoEvent { + + private final Collection apiIds; + +} diff --git a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/isv/event/ChangeIsvInfoEvent.java b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/isv/event/ChangeIsvInfoEvent.java new file mode 100755 index 00000000..8c48f422 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/isv/event/ChangeIsvInfoEvent.java @@ -0,0 +1,17 @@ +package com.gitee.sop.admin.service.isv.event; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Collection; + +/** + * @author 六如 + */ +@Getter +@AllArgsConstructor +public class ChangeIsvInfoEvent { + + private final Collection isvIds; + +} diff --git a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/isv/event/ChangeIsvKeyEvent.java b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/isv/event/ChangeIsvKeyEvent.java new file mode 100755 index 00000000..f7f2d2a3 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/isv/event/ChangeIsvKeyEvent.java @@ -0,0 +1,17 @@ +package com.gitee.sop.admin.service.isv.event; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.List; + +/** + * @author 六如 + */ +@Getter +@AllArgsConstructor +public class ChangeIsvKeyEvent { + + private final List isvIds; + +} diff --git a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/isv/event/ChangeIsvPermEvent.java b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/isv/event/ChangeIsvPermEvent.java new file mode 100755 index 00000000..00ed9f4a --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/isv/event/ChangeIsvPermEvent.java @@ -0,0 +1,17 @@ +package com.gitee.sop.admin.service.isv.event; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.List; + +/** + * @author 六如 + */ +@Getter +@AllArgsConstructor +public class ChangeIsvPermEvent { + + private final List isvIds; + +} diff --git a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/isv/listener/ChangeListener.java b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/isv/listener/ChangeListener.java new file mode 100755 index 00000000..67de45d9 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/isv/listener/ChangeListener.java @@ -0,0 +1,79 @@ +package com.gitee.sop.admin.service.isv.listener; + +import com.gitee.sop.admin.dao.entity.IsvInfo; +import com.gitee.sop.admin.dao.mapper.IsvInfoMapper; +import com.gitee.sop.admin.service.isv.event.ChangeApiInfoEvent; +import com.gitee.sop.admin.service.isv.event.ChangeIsvInfoEvent; +import com.gitee.sop.admin.service.isv.event.ChangeIsvKeyEvent; +import com.gitee.sop.admin.service.isv.event.ChangeIsvPermEvent; +import com.gitee.sop.support.service.RefreshService; +import org.apache.dubbo.config.annotation.DubboReference; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Component; + +import java.util.Collection; +import java.util.List; + +/** + * 变更监听 + * + * @author 六如 + */ +@Component +public class ChangeListener { + + @DubboReference + private RefreshService refreshService; + + @Autowired + private IsvInfoMapper isvInfoMapper; + + /** + * 监听isv信息变更 + * + * @param event + */ + @EventListener + public void onChangeApiInfoEvent(ChangeApiInfoEvent event) { + Collection apiIds = event.getApiIds(); + refreshService.refreshApi(apiIds); + } + + /** + * 监听isv信息变更 + * + * @param event + */ + @EventListener + public void onChangeIsvInfoEvent(ChangeIsvInfoEvent event) { + Collection isvIds = event.getIsvIds(); + Collection appIds = isvInfoMapper.query() + .in(IsvInfo::getId, isvIds) + .listUniqueValue(IsvInfo::getAppId); + refreshService.refreshIsv(appIds); + } + + /** + * 监听isv秘钥变更 + * + * @param event + */ + @EventListener + public void onChangeIsvKeyEvent(ChangeIsvKeyEvent event) { + List isvIds = event.getIsvIds(); + refreshService.refreshSecret(isvIds); + } + + /** + * 监听isv分组变更 + * + * @param event + */ + @EventListener + public void onChangeIsvGroupEvent(ChangeIsvPermEvent event) { + List isvIds = event.getIsvIds(); + refreshService.refreshIsvPerm(isvIds); + } + +} diff --git a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/jackson/convert/annotation/UserFormat.java b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/jackson/convert/annotation/UserFormat.java new file mode 100755 index 00000000..ea1ef521 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/jackson/convert/annotation/UserFormat.java @@ -0,0 +1,31 @@ +package com.gitee.sop.admin.service.jackson.convert.annotation; + + +import com.fasterxml.jackson.annotation.JacksonAnnotationsInside; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.gitee.sop.admin.service.jackson.convert.serde.UserFormatSerializer; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 序列化自动转换成用户名称 + * + * @author 六如 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +@JacksonAnnotationsInside +@JsonSerialize(using = UserFormatSerializer.class) +public @interface UserFormat { + + /** + * 显示用户名称 + * @return + */ + boolean showName() default true; + + +} diff --git a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/jackson/convert/serde/UserFormatSerializer.java b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/jackson/convert/serde/UserFormatSerializer.java new file mode 100755 index 00000000..129156c5 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/jackson/convert/serde/UserFormatSerializer.java @@ -0,0 +1,43 @@ +package com.gitee.sop.admin.service.jackson.convert.serde; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.gitee.sop.admin.common.context.SpringContext; +import com.gitee.sop.admin.common.resp.UserVO; +import com.gitee.sop.admin.common.user.User; +import com.gitee.sop.admin.common.util.CopyUtil; +import com.gitee.sop.admin.service.sys.UserCacheService; +import lombok.extern.slf4j.Slf4j; + +import java.io.IOException; +import java.util.Objects; +import java.util.Optional; + +/** + * @author 六如 + */ +@Slf4j +public class UserFormatSerializer extends JsonSerializer { + + @Override + public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException { + if (value == null) { + gen.writeNull(); + return; + } + Long userId = (Long) value; + if (Objects.equals(userId, 0L)) { + gen.writeNumber(userId); + return; + } + Optional userOpt = SpringContext.getBean(UserCacheService.class).getUser(userId); + if (userOpt.isPresent()) { + User user = userOpt.get(); + UserVO userVO = CopyUtil.copyBean(user, UserVO::new); + gen.writeObject(userVO); + } else { + gen.writeObject(value); + } + } +} diff --git a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/jackson/package-info.java b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/jackson/package-info.java new file mode 100755 index 00000000..ec956c92 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/jackson/package-info.java @@ -0,0 +1 @@ +package com.gitee.sop.admin.service.jackson; diff --git a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/serve/ApiInfoService.java b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/serve/ApiInfoService.java new file mode 100755 index 00000000..352ffb34 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/serve/ApiInfoService.java @@ -0,0 +1,47 @@ +package com.gitee.sop.admin.service.serve; + +import com.gitee.sop.admin.common.context.SpringContext; +import com.gitee.sop.admin.common.dto.StatusUpdateDTO; +import com.gitee.sop.admin.common.support.ServiceSupport; +import com.gitee.sop.admin.dao.entity.ApiInfo; +import com.gitee.sop.admin.dao.mapper.ApiInfoMapper; +import com.gitee.sop.admin.service.isv.event.ChangeApiInfoEvent; +import org.springframework.stereotype.Service; + +import java.util.Collections; + + +/** + * @author 六如 + */ +@Service +public class ApiInfoService implements ServiceSupport { + + /** + * 修改状态 + * + * @param statusUpdateDTO 修改值 + * @return 返回影响行数 + */ + public int updateStatus(StatusUpdateDTO statusUpdateDTO) { + int cnt = this.query() + .eq(ApiInfo::getId, statusUpdateDTO.getId()) + .set(ApiInfo::getStatus, statusUpdateDTO.getStatus()) + .update(); + this.sendChangeEvent(statusUpdateDTO.getId()); + return cnt; + } + + @Override + public int update(ApiInfo entity) { + int cnt = ServiceSupport.super.update(entity); + sendChangeEvent(entity.getId()); + return cnt; + } + + + + private void sendChangeEvent(Long id) { + SpringContext.publishEvent(new ChangeApiInfoEvent(Collections.singletonList(id))); + } +} diff --git a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/SysConfigService.java b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/SysConfigService.java new file mode 100755 index 00000000..ceda3f9b --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/SysConfigService.java @@ -0,0 +1,107 @@ +package com.gitee.sop.admin.service.sys; + +import com.gitee.fastmybatis.core.support.BaseLambdaService; +import com.gitee.sop.admin.common.config.IConfig; +import com.gitee.sop.admin.common.util.CopyUtil; +import com.gitee.sop.admin.dao.entity.SysConfig; +import com.gitee.sop.admin.dao.mapper.SysConfigMapper; +import com.gitee.sop.admin.service.sys.dto.SystemConfigDTO; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.env.Environment; +import org.springframework.stereotype.Service; + +import java.util.Collection; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.TimeUnit; + +/** + * @author 六如 + */ +@Service +public class SysConfigService extends BaseLambdaService implements IConfig { + + @Autowired + private Environment environment; + + // key: configKey, value: configValue + private final LoadingCache> configCache = CacheBuilder.newBuilder() + .expireAfterAccess(15, TimeUnit.MINUTES) + .build(new CacheLoader>() { + @Override + public Optional load(String key) throws Exception { + return Optional.ofNullable(getConfigValue(key, null)); + } + }); + + public void save(Collection configs) { + configs.forEach(this::setConfig); + } + + public String getRawValue(String key) { + return this.query() + .eq(SysConfig::getConfigKey, key) + .getValue(SysConfig::getConfigValue); + } + + public void setConfig(String key, String value) { + setConfig(key, value, ""); + } + + public void setConfig(String key, String value, String remark) { + SystemConfigDTO systemConfigDTO = new SystemConfigDTO(); + systemConfigDTO.setConfigKey(key); + systemConfigDTO.setConfigValue(value); + systemConfigDTO.setRemark(remark); + setConfig(systemConfigDTO); + } + + public void setConfig(SystemConfigDTO systemConfigDTO) { + Objects.requireNonNull(systemConfigDTO.getConfigKey(), "need key"); + Objects.requireNonNull(systemConfigDTO.getConfigValue(), "need value"); + SysConfig systemConfig = get(SysConfig::getConfigKey, systemConfigDTO.getConfigKey()); + if (systemConfig == null) { + systemConfig = CopyUtil.copyBean(systemConfigDTO, SysConfig::new); + this.save(systemConfig); + } else { + CopyUtil.copyPropertiesIgnoreNull(systemConfigDTO, systemConfig); + this.update(systemConfig); + } + configCache.invalidate(systemConfigDTO.getConfigKey()); + } + + /** + * 获取配置信息 + *
+     *  优先级:
+     *  数据库
+     *  Environment
+     *  默认配置
+     * 
+ * + * @param key 配置key + * @param defaultValue 没有获取到返回的默认值 + * @return 返回配置信息,如果没有获取到值,则返回默认值 + */ + public String getConfigValue(String key, String defaultValue) { + Objects.requireNonNull(key, "need key"); + SysConfig systemConfig = get(SysConfig::getConfigKey, key); + return Optional.ofNullable(systemConfig) + .map(SysConfig::getConfigValue) + .orElseGet(() -> environment.getProperty(key, defaultValue)); + } + + @Override + public String getConfig(String key) { + return configCache.getUnchecked(key).orElse(null); + } + + @Override + public String getConfig(String key, String defaultValue) { + return configCache.getUnchecked(key).orElse(defaultValue); + } + +} diff --git a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/SysDeptService.java b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/SysDeptService.java new file mode 100755 index 00000000..0089d1dd --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/SysDeptService.java @@ -0,0 +1,54 @@ +package com.gitee.sop.admin.service.sys; + +import com.gitee.fastmybatis.core.query.LambdaQuery; +import com.gitee.fastmybatis.core.util.TreeUtil; +import com.gitee.sop.admin.common.dto.StatusUpdateDTO; +import com.gitee.sop.admin.common.exception.BizException; +import com.gitee.sop.admin.common.support.ServiceSupport; +import com.gitee.sop.admin.common.util.CopyUtil; +import com.gitee.sop.admin.dao.entity.SysDept; +import com.gitee.sop.admin.dao.mapper.SysDeptMapper; +import com.gitee.sop.admin.service.sys.dto.SysDeptDTO; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Objects; + + +/** + * @author 六如 + */ +@Service +public class SysDeptService implements ServiceSupport { + + public Long addDept(SysDept sysDept) { + long count = this.query() + .eq(SysDept::getParentId, 0) + .getCount(); + if (count > 0 && Objects.equals(sysDept.getParentId(), 0L)) { + throw new BizException("只能有一个根节点"); + } + this.save(sysDept); + return sysDept.getId(); + } + + public List listTree(LambdaQuery query) { + List list = this.list(query); + List sysDeptList = CopyUtil.copyList(list, SysDeptDTO::new); + return TreeUtil.convertTree(sysDeptList, 0L); + } + + + /** + * 修改状态 + * + * @param statusUpdateDTO 修改值 + * @return 返回影响行数 + */ + public int updateStatus(StatusUpdateDTO statusUpdateDTO) { + return this.query() + .eq(SysDept::getId, statusUpdateDTO.getId()) + .set(SysDept::getStatus, statusUpdateDTO.getStatus()) + .update(); + } +} diff --git a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/SysResourceService.java b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/SysResourceService.java new file mode 100755 index 00000000..9f17dc32 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/SysResourceService.java @@ -0,0 +1,15 @@ +package com.gitee.sop.admin.service.sys; + +import com.gitee.sop.admin.common.support.ServiceSupport; +import com.gitee.sop.admin.dao.entity.SysResource; +import com.gitee.sop.admin.dao.mapper.SysResourceMapper; +import org.springframework.stereotype.Service; + + +/** + * @author 六如 + */ +@Service +public class SysResourceService implements ServiceSupport { + +} diff --git a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/SysRoleResourceService.java b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/SysRoleResourceService.java new file mode 100755 index 00000000..b1f567c5 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/SysRoleResourceService.java @@ -0,0 +1,60 @@ +package com.gitee.sop.admin.service.sys; + +import com.gitee.sop.admin.common.support.ServiceSupport; +import com.gitee.sop.admin.dao.entity.SysRoleResource; +import com.gitee.sop.admin.dao.mapper.SysRoleResourceMapper; +import com.gitee.sop.admin.service.sys.dto.SysRoleResourceDTO; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + + +/** + * @author 六如 + */ +@Service +public class SysRoleResourceService implements ServiceSupport { + + + /** + * 保存角色菜单配置 + * + * @param sysRoleResourceDTO 入参 + * @return 返回影响行数 + */ + @Transactional(rollbackFor = Exception.class) + public int saveRoleResource(SysRoleResourceDTO sysRoleResourceDTO) { + Long roleId = sysRoleResourceDTO.getRoleId(); + // 删除之前的 + this.deleteByColumn(SysRoleResource::getRoleId, roleId); + + List saveList = sysRoleResourceDTO.getResourceIds().stream() + .map(resourceId -> { + SysRoleResource sysRoleResource = new SysRoleResource(); + sysRoleResource.setRoleId(roleId); + sysRoleResource.setResourceId(resourceId); + sysRoleResource.setAddBy(sysRoleResourceDTO.getAddBy()); + sysRoleResource.setUpdateBy(sysRoleResourceDTO.getAddBy()); + return sysRoleResource; + }) + .collect(Collectors.toList()); + + return this.saveBatch(saveList); + } + + public List listRoleResource(Long roleId) { + return this.query() + .eq(SysRoleResource::getRoleId, roleId) + .listUniqueValue(SysRoleResource::getResourceId); + } + + public List listRoleResource(Collection roleIds) { + return this.query() + .in(SysRoleResource::getRoleId, roleIds) + .listUniqueValue(SysRoleResource::getResourceId); + } + +} diff --git a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/SysRoleService.java b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/SysRoleService.java new file mode 100755 index 00000000..aacba537 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/SysRoleService.java @@ -0,0 +1,32 @@ +package com.gitee.sop.admin.service.sys; + +import com.gitee.sop.admin.common.dto.StatusUpdateDTO; +import com.gitee.sop.admin.common.support.ServiceSupport; +import com.gitee.sop.admin.dao.entity.SysRole; +import com.gitee.sop.admin.dao.mapper.SysRoleMapper; +import org.springframework.stereotype.Service; + + +/** + * @author 六如 + */ +@Service +public class SysRoleService implements ServiceSupport { + + + /** + * 修改状态 + * + * @param statusUpdateDTO 修改值 + * @return 返回影响行数 + */ + public int updateStatus(StatusUpdateDTO statusUpdateDTO) { + return this.query() + .eq(SysRole::getId, statusUpdateDTO.getId()) + .set(SysRole::getStatus, statusUpdateDTO.getStatus()) + .update(); + } + + + +} diff --git a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/SysUserDeptService.java b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/SysUserDeptService.java new file mode 100755 index 00000000..2ccc1a5a --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/SysUserDeptService.java @@ -0,0 +1,116 @@ +package com.gitee.sop.admin.service.sys; + +import com.gitee.sop.admin.common.support.ServiceSupport; +import com.gitee.sop.admin.common.util.CopyUtil; +import com.gitee.sop.admin.dao.entity.SysDept; +import com.gitee.sop.admin.dao.entity.SysUserDept; +import com.gitee.sop.admin.dao.mapper.SysDeptMapper; +import com.gitee.sop.admin.dao.mapper.SysUserDeptMapper; +import com.gitee.sop.admin.service.sys.dto.DeptUserResultDTO; +import com.gitee.sop.admin.service.sys.dto.SysDeptDTO; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.Function; +import java.util.stream.Collectors; + + +/** + * @author 六如 + */ +@Service +public class SysUserDeptService implements ServiceSupport { + + @Autowired + SysDeptMapper sysDeptMapper; + + public Map getUserDeptId(Collection userIds) { + Map deptIdMap = sysDeptMapper.query() + .map(SysDept::getId, Function.identity()); + return this.query() + .in(SysUserDept::getUserId, userIds) + .map(SysUserDept::getUserId, sysUserDept -> { + SysDept sysDept = deptIdMap.get(sysUserDept.getDeptId()); + return CopyUtil.copyBean(sysDept, SysDeptDTO::new); + }, (v1, v2) -> v2); + } + + /** + * 查找部门及子部门下所有用户 + * + * @param deptId 部门 + * @return 返回空表示选中了根节点或没有选 + */ + public DeptUserResultDTO listDeptUserIdsDeep(Long deptId) { + if (deptId == null) { + return new DeptUserResultDTO(false, null); + } + SysDept sysDept = sysDeptMapper.getById(deptId); + // 选中了根节点或没有选 + if (sysDept == null || Objects.equals(sysDept.getParentId(), 0L)) { + return new DeptUserResultDTO(false, null); + } + List userIdsAll = new ArrayList<>(); + List userIds = this.query() + .eq(SysUserDept::getDeptId, deptId) + .listUniqueValue(SysUserDept::getUserId); + + appendChildren(userIdsAll, deptId); + + userIdsAll.addAll(userIds); + return new DeptUserResultDTO(true, userIdsAll); + } + + private void appendChildren(List userIdsAll, Long deptId) { + List deptIds = sysDeptMapper.query() + .eq(SysDept::getParentId, deptId) + .listUniqueValue(SysDept::getId); + if (deptIds.isEmpty()) { + return; + } + List userIds = this.query() + .in(SysUserDept::getDeptId, deptIds) + .listUniqueValue(SysUserDept::getUserId); + userIdsAll.addAll(userIds); + + for (Long childDeptId : deptIds) { + appendChildren(userIdsAll, childDeptId); + } + } + + /** + * 保存用户部门 + * + * @param userId 用户id + * @param deptIds 部门id + * @return 返回影响行数 + */ + public int saveUserDept(Long userId, List deptIds) { + this.query() + .eq(SysUserDept::getUserId, userId) + .delete(); + + if (CollectionUtils.isEmpty(deptIds)) { + return 0; + } + + List saveList = deptIds.stream() + .map(deptId -> { + SysUserDept sysUserDept = new SysUserDept(); + sysUserDept.setUserId(userId); + sysUserDept.setDeptId(deptId); + return sysUserDept; + }) + .collect(Collectors.toList()); + + return this.saveBatch(saveList); + } + + +} diff --git a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/SysUserRoleService.java b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/SysUserRoleService.java new file mode 100755 index 00000000..df31378a --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/SysUserRoleService.java @@ -0,0 +1,74 @@ +package com.gitee.sop.admin.service.sys; + +import com.gitee.sop.admin.common.support.ServiceSupport; +import com.gitee.sop.admin.common.user.User; +import com.gitee.sop.admin.common.util.CopyUtil; +import com.gitee.sop.admin.dao.entity.SysRole; +import com.gitee.sop.admin.dao.entity.SysUserRole; +import com.gitee.sop.admin.dao.mapper.SysRoleMapper; +import com.gitee.sop.admin.dao.mapper.SysUserRoleMapper; +import com.gitee.sop.admin.service.sys.dto.SysRoleDTO; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; + +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.stream.Collectors; + + +/** + * @author 六如 + */ +@Service +public class SysUserRoleService implements ServiceSupport { + + @Autowired + private SysRoleMapper sysRoleMapper; + + /** + * 设置用户角色 + * + * @param userId 用户id + * @param roleIds 角色id + * @param operator 操作人 + */ + public void setUserRole(Long userId, List roleIds, User operator) { + this.query() + .eq(SysUserRole::getUserId, userId) + .delete(); + + if (CollectionUtils.isEmpty(roleIds)) { + return; + } + List saveList = new LinkedHashSet<>(roleIds).stream() + .map(roleId -> { + SysUserRole sysUserRole = new SysUserRole(); + sysUserRole.setRoleId(roleId); + sysUserRole.setUserId(userId); + sysUserRole.setAddBy(operator.getUserId()); + return sysUserRole; + }) + .collect(Collectors.toList()); + + this.saveBatch(saveList); + } + + public List listUserRoleIds(Long userId) { + return this.query() + .eq(SysUserRole::getUserId, userId) + .listUniqueValue(SysUserRole::getRoleId); + } + + public List listUserRoles(Long userId) { + List roleIds = this.listUserRoleIds(userId); + if (roleIds.isEmpty()) { + return new ArrayList<>(0); + } + return sysRoleMapper.query() + .in(SysRole::getId, roleIds) + .list(sysRole -> CopyUtil.copyBean(sysRole, SysRoleDTO::new)); + } + +} diff --git a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/SysUserService.java b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/SysUserService.java new file mode 100755 index 00000000..02a1707c --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/SysUserService.java @@ -0,0 +1,180 @@ +package com.gitee.sop.admin.service.sys; + +import com.gitee.fastmybatis.core.PageInfo; +import com.gitee.fastmybatis.core.query.LambdaQuery; +import com.gitee.sop.admin.common.config.Configs; +import com.gitee.sop.admin.common.dto.StatusUpdateBatchDTO; +import com.gitee.sop.admin.common.dto.StatusUpdateDTO; +import com.gitee.sop.admin.common.dto.UserDTO; +import com.gitee.sop.admin.common.enums.ConfigKeyEnum; +import com.gitee.sop.admin.common.support.ServiceSupport; +import com.gitee.sop.admin.common.util.CopyUtil; +import com.gitee.sop.admin.common.util.GenerateUtil; +import com.gitee.sop.admin.dao.entity.SysUser; +import com.gitee.sop.admin.dao.mapper.SysUserMapper; +import com.gitee.sop.admin.service.sys.dto.DeptUserResultDTO; +import com.gitee.sop.admin.service.sys.dto.SysDeptDTO; +import com.gitee.sop.admin.service.sys.dto.SysUserAddDTO; +import com.gitee.sop.admin.service.sys.dto.SysUserDTO; +import com.gitee.sop.admin.service.sys.dto.SysUserSearchDTO; +import com.gitee.sop.admin.service.sys.dto.SysUserUpdateDTO; +import com.gitee.sop.admin.service.sys.login.enums.RegTypeEnum; +import com.google.common.collect.Lists; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.crypto.bcrypt.BCrypt; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + + +/** + * @author 六如 + */ +@Service +public class SysUserService implements ServiceSupport { + + @Autowired + SysUserDeptService sysUserDeptService; + + public void fillShowName(Collection userDTOS) { + if (CollectionUtils.isEmpty(userDTOS)) { + return; + } + Set userIds = userDTOS.stream().flatMap(docInfo -> Lists.newArrayList(docInfo.getAddBy(), docInfo.getUpdateBy()).stream()) + .filter(val -> val != null && val > 0) + .collect(Collectors.toSet()); + Map userIdMap = this.getUserInfo(userIds); + for (T userDTO : userDTOS) { + SysUserDTO addUser = userIdMap.get(userDTO.getAddBy()); + if (addUser != null) { + userDTO.setAddByName(addUser.getShowName()); + } + SysUserDTO updateUser = userIdMap.get(userDTO.getUpdateBy()); + if (updateUser != null) { + userDTO.setUpdateByName(updateUser.getShowName()); + } + } + } + + public Map getUserInfo(Collection userIds) { + if (CollectionUtils.isEmpty(userIds)) { + return Collections.emptyMap(); + } + return this.query() + .in(SysUser::getId, userIds) + .map(SysUser::getId, sysUser -> CopyUtil.copyBean(sysUser, SysUserDTO::new)); + } + + public PageInfo doPage(SysUserSearchDTO sysUserSearchDTO) { + LambdaQuery query = sysUserSearchDTO.toLambdaQuery(SysUser.class); + Long deptId = sysUserSearchDTO.getDeptId(); + DeptUserResultDTO deptUserResultDTO = sysUserDeptService.listDeptUserIdsDeep(deptId); + if (deptUserResultDTO.getSelect()) { + List userIds = deptUserResultDTO.getUserIds(); + // 查不到返回空 + if (CollectionUtils.isEmpty(userIds)) { + return new PageInfo<>(); + } + query.in(SysUser::getId, userIds); + } + + query.orderByDesc(SysUser::getId); + + // 格式转换 + return this.pageAndConvert(query, records -> { + if (records.isEmpty()) { + return Collections.emptyList(); + } + Set userIds = records.stream().map(SysUser::getId).collect(Collectors.toSet()); + Map userIdDeptMap = sysUserDeptService.getUserDeptId(userIds); + + return CopyUtil.copyList(records, SysUserDTO::new, after -> { + SysDeptDTO sysDeptDTO = userIdDeptMap.get(after.getId()); + after.setDept(sysDeptDTO); + }); + }); + } + + public Long addUser(SysUserAddDTO sysUserAddDTO) { + SysUser sysUser = CopyUtil.copyBean(sysUserAddDTO, SysUser::new); + String encodedPassword = BCrypt.hashpw(sysUserAddDTO.getPassword(), BCrypt.gensalt()); + sysUser.setPassword(encodedPassword); + sysUser.setRegType(RegTypeEnum.BACKEND.getValue()); + this.save(sysUser); + + // 添加部门 + Long deptId = sysUserAddDTO.getParentId(); + if (deptId != null && deptId > 0) { + sysUserDeptService.saveUserDept(sysUser.getId(), Lists.newArrayList(deptId)); + } + + return sysUser.getId(); + } + + public int updateUser(SysUserUpdateDTO sysUserUpdateDTO) { + SysUser sysUser = CopyUtil.copyBean(sysUserUpdateDTO, SysUser::new); + int cnt = this.update(sysUser); + + // 添加部门 + Long deptId = sysUserUpdateDTO.getParentId(); + if (deptId != null && deptId > 0) { + sysUserDeptService.saveUserDept(sysUser.getId(), Lists.newArrayList(deptId)); + } + return cnt; + } + + /** + * 修改状态 + * + * @param statusUpdateDTO 修改值 + * @return 返回影响行数 + */ + public int updateStatus(StatusUpdateDTO statusUpdateDTO) { + return this.query() + .eq(SysUser::getId, statusUpdateDTO.getId()) + .set(SysUser::getStatus, statusUpdateDTO.getStatus()) + .update(); + } + + public int updateStatus(StatusUpdateBatchDTO statusUpdateDTO) { + return this.query() + .in(SysUser::getId, statusUpdateDTO.getIds()) + .set(SysUser::getStatus, statusUpdateDTO.getStatus()) + .update(); + } + + public void resetUserPassword(Long userId, String passwordHash) { + String encodedPassword = BCrypt.hashpw(passwordHash, BCrypt.gensalt()); + this.query() + .eq(SysUser::getId, userId) + .set(SysUser::getPassword, encodedPassword) + .update(); + } + + public SysUser getByUsername(String username) { + return this.get(SysUser::getUsername, username); + } + + public String getDbPassword(String username, String password) { + return getDbPassword(username, password, getPasswordSalt()); + } + + public String getDbPassword(String username) { + return this.query().eq(SysUser::getUsername, username).getValue(SysUser::getPassword); + } + + public String getDbPassword(String username, String password, String salt) { + return GenerateUtil.getUserPassword(username, password, salt); + } + + private String getPasswordSalt() { + return Configs.getValue(ConfigKeyEnum.PASSWORD_SALT); + } + +} diff --git a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/UpgradeService.java b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/UpgradeService.java new file mode 100755 index 00000000..5b86c59c --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/UpgradeService.java @@ -0,0 +1,67 @@ +package com.gitee.sop.admin.service.sys; + +import com.gitee.sop.admin.common.enums.ConfigKeyEnum; +import com.gitee.sop.admin.common.util.PasswordUtil; +import com.gitee.sop.admin.dao.entity.SysUser; +import com.gitee.sop.admin.dao.mapper.UpgradeMapper; +import com.gitee.sop.admin.service.sys.login.enums.RegTypeEnum; +import org.apache.commons.codec.digest.DigestUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.crypto.bcrypt.BCrypt; +import org.springframework.stereotype.Service; + +import javax.annotation.PostConstruct; + +/** + * @author 六如 + */ +@Service +public class UpgradeService { + + @Autowired + private SysConfigService sysConfigService; + + @Autowired + private SysUserService sysUserService; + + @Autowired + private UpgradeMapper upgradeMapper; + + @PostConstruct + public void init() { + this.initJwtSecret(); + this.insertAdmin(); + } + + private void initJwtSecret() { + String configKey = ConfigKeyEnum.JWT_SECRET.getKey(); + String value = sysConfigService.getRawValue(configKey); + if (StringUtils.isBlank(value)) { + value = PasswordUtil.getRandomSimplePassword(30); + sysConfigService.setConfig(configKey, value); + } + } + + + public void insertAdmin() { + SysUser userInfo = sysUserService.getByUsername("admin"); + if (userInfo != null) { + return; + } + String username = "admin"; + String tpl = "INSERT INTO `sys_user` ( `username`, `password`, `nickname`, `reg_type`) VALUES \n" + + "\t('%s','%s','%s','%s');"; + // 初始密码 + String defPassword = "123456"; + defPassword = DigestUtils.sha256Hex(defPassword); + String encodedPassword = BCrypt.hashpw(defPassword, BCrypt.gensalt()); + String sql = String.format(tpl, username, encodedPassword, username, RegTypeEnum.BACKEND.getValue()); + runSql(sql); + } + + protected void runSql(String sql) { + upgradeMapper.runSql(sql); + } + +} diff --git a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/UserCacheService.java b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/UserCacheService.java new file mode 100755 index 00000000..e7dc1521 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/UserCacheService.java @@ -0,0 +1,14 @@ +package com.gitee.sop.admin.service.sys; + +import com.gitee.sop.admin.common.user.User; + +import java.util.Optional; + +/** + * @author 六如 + */ +public interface UserCacheService { + + Optional getUser(Long userId); + +} diff --git a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/UserPermissionService.java b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/UserPermissionService.java new file mode 100755 index 00000000..8bcf1e25 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/UserPermissionService.java @@ -0,0 +1,99 @@ +package com.gitee.sop.admin.service.sys; + +import com.gitee.fastmybatis.core.util.TreeUtil; +import com.gitee.sop.admin.common.enums.MenuTypeEnum; +import com.gitee.sop.admin.common.util.CopyUtil; +import com.gitee.sop.admin.dao.entity.SysResource; +import com.gitee.sop.admin.dao.entity.SysRoleResource; +import com.gitee.sop.admin.service.sys.dto.MenuTreeDTO; +import com.gitee.sop.admin.service.sys.dto.MenuTreeDTO.Meta; +import com.gitee.sop.admin.service.sys.dto.SysRoleDTO; +import com.gitee.sop.admin.service.sys.dto.UserPermissionDTO; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + + +/** + * @author 六如 + */ +@Service +public class UserPermissionService { + + @Autowired + private SysResourceService sysResourceService; + @Autowired + private SysRoleResourceService sysRoleResourceService; + @Autowired + private SysUserRoleService sysUserRoleService; + + public UserPermissionDTO getUserPermission(Long userId) { + List sysRoleDTOS = sysUserRoleService.listUserRoles(userId); + if (sysRoleDTOS.isEmpty()) { + return UserPermissionDTO.empty(); + } + + Set roleIds = sysRoleDTOS.stream().map(SysRoleDTO::getId).collect(Collectors.toSet()); + List roleCodes = sysRoleDTOS.stream().map(SysRoleDTO::getCode).distinct().collect(Collectors.toList()); + + // 角色对应的资源 + List resourceIds = sysRoleResourceService.query() + .in(SysRoleResource::getRoleId, roleIds) + .listUniqueValue(SysRoleResource::getResourceId); + + // 获取按钮权限 + List permissions = sysResourceService.query() + .eq(SysResource::getMenuType, MenuTypeEnum.BUTTON.getValue()) + .in(SysResource::getId, resourceIds) + .listUniqueValue(SysResource::getAuths); + + UserPermissionDTO userPermissionDTO = new UserPermissionDTO(); + userPermissionDTO.setRoles(roleCodes); + userPermissionDTO.setPermissions(permissions); + return userPermissionDTO; + } + + + /** + * 获取用户菜单 + * + * @param userId 用户id + * @return 返回菜单树 + */ + public List listUserMenuTree(Long userId) { + List roleIds = sysUserRoleService.listUserRoleIds(userId); + if (roleIds.isEmpty()) { + return Collections.emptyList(); + } + List resourceIds = sysRoleResourceService.listRoleResource(roleIds); + if (resourceIds.isEmpty()) { + return Collections.emptyList(); + } + List list = sysResourceService.query() + .in(SysResource::getId, resourceIds) + .eq(SysResource::getMenuType, MenuTypeEnum.MENU) + .list(); + + return convert(list); + + + } + + private List convert(List list) { + List menuTreeDTOS = list.stream() + .map(sysResource -> { + MenuTreeDTO menuTreeDTO = CopyUtil.copyBean(sysResource, MenuTreeDTO::new); + Meta meta = CopyUtil.copyBean(sysResource, Meta::new); + menuTreeDTO.setMeta(meta); + return menuTreeDTO; + }) + .collect(Collectors.toList()); + + return TreeUtil.convertTree(menuTreeDTOS, 0L); + } + +} diff --git a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/dto/DeptUserResultDTO.java b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/dto/DeptUserResultDTO.java new file mode 100755 index 00000000..ef3fc6b2 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/dto/DeptUserResultDTO.java @@ -0,0 +1,19 @@ +package com.gitee.sop.admin.service.sys.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; + +import java.util.List; + +/** + * @author 六如 + */ +@Data +@AllArgsConstructor +public class DeptUserResultDTO { + + private Boolean select = true; + + private List userIds; + +} diff --git a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/dto/MenuTreeDTO.java b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/dto/MenuTreeDTO.java new file mode 100755 index 00000000..d1725bb9 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/dto/MenuTreeDTO.java @@ -0,0 +1,130 @@ +package com.gitee.sop.admin.service.sys.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.gitee.fastmybatis.core.support.TreeNode; +import com.gitee.sop.admin.common.jackson.convert.annotation.Bool; +import lombok.Data; + +import java.util.List; + + +/** + * 菜单资源 + * + * @author 六如 + * @see ... + */ +@Data +@JsonInclude(JsonInclude.Include.NON_EMPTY) +public class MenuTreeDTO implements TreeNode { + + /** + * id + */ + private Long id; + + /** + * 路由路径 + */ + private String path; + + /** + * 路由名称(必须保持唯一) + */ + private String name; + + /** + * 路由重定向(默认跳转地址) + */ + private String redirect; + + /** + * 父级id + */ + private Long parentId; + + /** + * 路由元信息 + */ + private Meta meta; + + private List children; + + @Override + public Long takeId() { + return id; + } + + @Override + public Long takeParentId() { + return parentId; + } + + /** + * 路由元信息,通俗来说就是路由上的额外信息 https://router.vuejs.org/zh/guide/advanced/meta.html#%E8%B7%AF%E7%94%B1%E5%85%83%E4%BF%A1%E6%81%AF + */ + @Data + @JsonInclude(JsonInclude.Include.NON_EMPTY) + public static class Meta { + /** + * 菜单名称(兼容国际化、非国际化,如果用国际化的写法就必须在根目录的locales文件夹下对应添加) + */ + private String title; + + /** + * 菜单图标 + */ + private String icon; + + /** + * 右侧图标 + */ + private String extraIcon; + + /** + * 菜单(是否显示该菜单) + */ + @Bool + private Integer showLink; + + /** + * 父级菜单(是否显示父级菜单 + */ + @Bool + private Integer showParent; + + /** + * 链接地址(需要内嵌的`iframe`链接地址) + */ + private String frameSrc; + + /** + * 加载动画(内嵌的`iframe`页面是否开启首次加载动画) + */ + @Bool + private Integer frameLoading; + + /** + * 缓存页面 + */ + @Bool + private Integer keepAlive; + + /** + * 标签页(当前菜单名称或自定义信息禁止添加到标签页) + */ + @Bool + private Integer hiddenTag; + + /** + * 菜单激活 + */ + private String activePath; + + /** + * 排序 + */ + private Integer rank; + + } +} diff --git a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/dto/SysDeptDTO.java b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/dto/SysDeptDTO.java new file mode 100755 index 00000000..40d8f4ea --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/dto/SysDeptDTO.java @@ -0,0 +1,81 @@ +package com.gitee.sop.admin.service.sys.dto; + +import com.gitee.fastmybatis.core.support.TreeNode; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; + + +/** + * 备注:部门表 + * + * @author 六如 + */ +@Data +public class SysDeptDTO implements TreeNode { + + /** + * id + */ + private Long id; + + /** + * 部门名称 + */ + private String name; + + /** + * 排序 + */ + private Integer sort; + + /** + * 状态,1:启用,2:禁用 + */ + private Integer status; + + /** + * 备注 + */ + private String remark; + + /** + * 父级id + */ + private Long parentId; + + /** + * 添加时间 + */ + private LocalDateTime addTime; + + /** + * 修改时间 + */ + private LocalDateTime updateTime; + + /** + * 创建人id + */ + private Long addBy; + + /** + * 修改人id + */ + private Long updateBy; + + private List children; + + + @Override + public Long takeId() { + return id; + } + + @Override + public Long takeParentId() { + return parentId; + } + +} diff --git a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/dto/SysRoleDTO.java b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/dto/SysRoleDTO.java new file mode 100755 index 00000000..53d2e4ea --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/dto/SysRoleDTO.java @@ -0,0 +1,29 @@ +package com.gitee.sop.admin.service.sys.dto; + +import lombok.Data; + +/** + * @author 六如 + */ +@Data +public class SysRoleDTO { + /** + * id + */ + private Long id; + + /** + * 角色名称 + */ + private String name; + + /** + * 角色code + */ + private String code; + + /** + * 备注 + */ + private String remark; +} diff --git a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/dto/SysRoleResourceDTO.java b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/dto/SysRoleResourceDTO.java new file mode 100755 index 00000000..87aa25b2 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/dto/SysRoleResourceDTO.java @@ -0,0 +1,28 @@ +package com.gitee.sop.admin.service.sys.dto; + +import lombok.Data; + +import java.util.List; + +/** + * @author 六如 + */ +@Data +public class SysRoleResourceDTO { + + /** + * 角色id + */ + private Long roleId; + + /** + * 资源id + */ + private List resourceIds; + + /** + * 添加人 + */ + private Long addBy; + +} diff --git a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/dto/SysUserAddDTO.java b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/dto/SysUserAddDTO.java new file mode 100755 index 00000000..9150bc55 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/dto/SysUserAddDTO.java @@ -0,0 +1,88 @@ +package com.gitee.sop.admin.service.sys.dto; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * @author 六如 + */ +@Data +public class SysUserAddDTO { + + /** + * 用户名 + */ + private String username; + + /** + * 密码 + */ + private String password; + + /** + * 昵称 + */ + private String nickname; + + /** + * 邮箱 + */ + private String phone; + + /** + * 邮箱 + */ + private String email; + + /** + * 头像 + */ + private String avatar; + + /** + * 性别,0-未知,1-男,2-女 + */ + private Integer gender; + + /** + * 状态,1:启用,2:禁用 + */ + private Integer status; + + /** + * 注册类型,1-系统,2-手动 + */ + private String regType; + + /** + * 备注 + */ + private String remark; + + /** + * 添加时间 + */ + private LocalDateTime addTime; + + /** + * 修改时间 + */ + private LocalDateTime updateTime; + + /** + * 创建人id + */ + private Long addBy; + + /** + * 修改人id + */ + private Long updateBy; + + /** + * 部门id + */ + private Long parentId; + +} diff --git a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/dto/SysUserDTO.java b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/dto/SysUserDTO.java new file mode 100755 index 00000000..653ac3ae --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/dto/SysUserDTO.java @@ -0,0 +1,109 @@ +package com.gitee.sop.admin.service.sys.dto; + +import com.gitee.sop.admin.common.user.User; +import lombok.Data; + +import java.time.LocalDateTime; + + +/** + * + * @author 六如 + */ +@Data +public class SysUserDTO implements User { + + /** + * id + */ + private Long id; + + /** + * 用户名 + */ + private String username; + + /** + * 密码 + */ + private String password; + + /** + * 昵称 + */ + private String nickname; + + /** + * 邮箱 + */ + private String phone; + + /** + * 邮箱 + */ + private String email; + + /** + * 头像 + */ + private String avatar; + + /** + * 性别,0-未知,1-男,2-女 + */ + private Integer gender; + + /** + * 状态,1:启用,2:禁用 + */ + private Integer status; + + /** + * 注册类型,1-系统,2-手动 + */ + private String regType; + + /** + * 备注 + */ + private String remark; + + /** + * 添加时间 + */ + private LocalDateTime addTime; + + /** + * 修改时间 + */ + private LocalDateTime updateTime; + + /** + * 创建人id + */ + private Long addBy; + + /** + * 修改人id + */ + private Long updateBy; + + /** + * 部门 + */ + private SysDeptDTO dept; + + @Override + public Long getUserId() { + return id; + } + + @Override + public String getToken() { + return ""; + } + + public String getShowName() { + return nickname != null && !nickname.isEmpty() ? nickname : username; + } +} diff --git a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/dto/SysUserSearchDTO.java b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/dto/SysUserSearchDTO.java new file mode 100755 index 00000000..895de6ef --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/dto/SysUserSearchDTO.java @@ -0,0 +1,42 @@ +package com.gitee.sop.admin.service.sys.dto; + +import com.gitee.fastmybatis.core.query.Operator; +import com.gitee.fastmybatis.core.query.annotation.Condition; +import com.gitee.fastmybatis.core.query.param.PageParam; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 备注:角色表 + * + * @author 六如 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class SysUserSearchDTO extends PageParam { + private static final long serialVersionUID = 7794265174728302379L; + + /** + * 用户名 + */ + @Condition(operator = Operator.like) + private String username; + + /** + * 邮箱 + */ + @Condition(operator = Operator.like) + private String phone; + + + /** + * 状态,1:启用,2:禁用 + */ + @Condition + private Integer status; + + @Condition(ignore = true) + private Long deptId; + + +} diff --git a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/dto/SysUserUpdateDTO.java b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/dto/SysUserUpdateDTO.java new file mode 100755 index 00000000..bfef2883 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/dto/SysUserUpdateDTO.java @@ -0,0 +1,75 @@ +package com.gitee.sop.admin.service.sys.dto; + +import lombok.Data; + +import javax.validation.constraints.NotNull; + + +/** + * @author 六如 + */ +@Data +public class SysUserUpdateDTO { + + /** + * id + */ + @NotNull(message = "id必填") + private Long id; + + /** + * 用户名 + */ + private String username; + + /** + * 昵称 + */ + private String nickname; + + /** + * 邮箱 + */ + private String phone; + + /** + * 邮箱 + */ + private String email; + + /** + * 头像 + */ + private String avatar; + + /** + * 性别,0-未知,1-男,2-女 + */ + private Integer gender; + + /** + * 状态,1:启用,2:禁用 + */ + private Integer status; + + /** + * 注册类型,1-系统,2-手动 + */ + private String regType; + + /** + * 备注 + */ + private String remark; + + /** + * 修改人id + */ + private Long updateBy; + + /** + * 部门id + */ + private Long parentId; + +} diff --git a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/dto/SystemConfigDTO.java b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/dto/SystemConfigDTO.java new file mode 100755 index 00000000..cbcf3cfd --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/dto/SystemConfigDTO.java @@ -0,0 +1,19 @@ +package com.gitee.sop.admin.service.sys.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author tanghc + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class SystemConfigDTO { + private String configKey; + + private String configValue; + + private String remark; +} diff --git a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/dto/UserPermissionDTO.java b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/dto/UserPermissionDTO.java new file mode 100755 index 00000000..a3a36c95 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/dto/UserPermissionDTO.java @@ -0,0 +1,32 @@ +package com.gitee.sop.admin.service.sys.dto; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + + +/** + * @author 六如 + */ +@Data +public class UserPermissionDTO { + + /** + * 用户角色code + */ + private List roles; + + /** + * 用户按钮级别code + */ + private List permissions; + + public static UserPermissionDTO empty() { + UserPermissionDTO userPermissionDTO = new UserPermissionDTO(); + userPermissionDTO.setRoles(new ArrayList<>()); + userPermissionDTO.setPermissions(new ArrayList<>()); + return userPermissionDTO; + } + +} diff --git a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/dto/UserRoleInfoDTO.java b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/dto/UserRoleInfoDTO.java new file mode 100755 index 00000000..6dd9e565 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/dto/UserRoleInfoDTO.java @@ -0,0 +1,20 @@ +package com.gitee.sop.admin.service.sys.dto; + +import lombok.Data; + +import java.util.List; + +/** + * 用户角色信息 + * @author 六如 + */ +@Data +public class UserRoleInfoDTO { + + private Long userId; + + private List roleIds; + + private List roleCodes; + +} diff --git a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/impl/LocalUserCacheService.java b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/impl/LocalUserCacheService.java new file mode 100755 index 00000000..88f19dca --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/impl/LocalUserCacheService.java @@ -0,0 +1,56 @@ +package com.gitee.sop.admin.service.sys.impl; + +import com.gitee.sop.admin.common.user.User; +import com.gitee.sop.admin.common.util.CopyUtil; +import com.gitee.sop.admin.dao.entity.SysUser; +import com.gitee.sop.admin.service.sys.SysUserService; +import com.gitee.sop.admin.service.sys.UserCacheService; +import com.gitee.sop.admin.service.sys.dto.SysUserDTO; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import org.springframework.beans.factory.annotation.Autowired; + +import javax.annotation.PostConstruct; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.TimeUnit; + +/** + * @author 六如 + */ +public class LocalUserCacheService implements UserCacheService { + + @Autowired + private SysUserService sysUserService; + + // key: configKey, value: configValue + private final LoadingCache> configCache = CacheBuilder.newBuilder() + .expireAfterAccess(30, TimeUnit.MINUTES) + .build(new CacheLoader>() { + @Override + public Optional load(Long key) throws Exception { + return Optional.ofNullable(loadFromDb(key)); + } + }); + + @Override + public Optional getUser(Long userId) { + return configCache.getUnchecked(userId); + } + + private User loadFromDb(Long userId) { + SysUser sysUser = sysUserService.getById(userId); + return CopyUtil.copyBean(sysUser, SysUserDTO::new); + } + + @PostConstruct + public void init() { + List sysUsers = sysUserService.listAll(); + List sysUserDTOS = CopyUtil.copyList(sysUsers, SysUserDTO::new); + for (SysUserDTO sysUserDTO : sysUserDTOS) { + configCache.put(sysUserDTO.getUserId(), Optional.of(sysUserDTO)); + } + } + +} diff --git a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/login/LoginService.java b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/login/LoginService.java new file mode 100755 index 00000000..8617b58b --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/login/LoginService.java @@ -0,0 +1,148 @@ +package com.gitee.sop.admin.service.sys.login; + +import com.gitee.sop.admin.common.config.Configs; +import com.gitee.sop.admin.common.enums.ConfigKeyEnum; +import com.gitee.sop.admin.common.enums.StatusEnum; +import com.gitee.sop.admin.common.exception.BizException; +import com.gitee.sop.admin.common.manager.UserCacheManager; +import com.gitee.sop.admin.common.util.CopyUtil; +import com.gitee.sop.admin.common.util.DateUtil; +import com.gitee.sop.admin.common.util.GenerateUtil; +import com.gitee.sop.admin.common.util.JwtUtil; +import com.gitee.sop.admin.dao.entity.SysUser; +import com.gitee.sop.admin.service.sys.SysUserService; +import com.gitee.sop.admin.service.sys.UserPermissionService; +import com.gitee.sop.admin.service.sys.dto.UserPermissionDTO; +import com.gitee.sop.admin.service.sys.login.dto.LoginDTO; +import com.gitee.sop.admin.service.sys.login.dto.LoginForm; +import com.gitee.sop.admin.service.sys.login.dto.LoginResult; +import com.gitee.sop.admin.service.sys.login.dto.LoginUser; +import com.gitee.sop.admin.service.sys.login.enums.RegTypeEnum; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.math.NumberUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.crypto.bcrypt.BCrypt; +import org.springframework.stereotype.Service; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +/** + * @author 六如 + */ +@Service +@Slf4j +public class LoginService { + + @Autowired + private SysUserService sysUserService; + @Autowired + private UserCacheManager userCacheManager; + @Autowired + private UserPermissionService userPermissionService; + + + public LoginUser login(LoginDTO loginDTO) { + String username = loginDTO.getUsername(); + String password = loginDTO.getPassword(); + RegTypeEnum regType = loginDTO.getRegType(); + SysUser userInfo; + switch (regType) { + case FORM: + throw new UnsupportedOperationException("第三方登录暂未支持"); + case LDAP: + // LDAP登录 + throw new UnsupportedOperationException("LDAP登录登录暂未支持"); + default: + // 默认注册账号登录 + userInfo = this.doDatabaseLogin(username, password); + } + LoginUser loginUser = buildLoginUser(userInfo); + // 保存到缓存 + userCacheManager.saveUser(loginUser); + return loginUser; + } + + private LoginUser buildLoginUser(SysUser sysUser) { + if (StatusEnum.of(sysUser.getStatus()) == StatusEnum.DISABLED) { + throw new BizException("账号已禁用,请联系管理员"); + } + // 登录成功 + LoginUser loginUser = CopyUtil.copyBean(sysUser, LoginUser::new); + // 创建token + String token = this.createToken(sysUser.getId()); + loginUser.setAccessToken(token); + UserPermissionDTO userPermission = userPermissionService.getUserPermission(loginUser.getUserId()); + // 角色权限 + loginUser.setRoles(userPermission.getRoles()); + loginUser.setPermissions(userPermission.getPermissions()); + // 设置token过期时间 + String value = Configs.getValue(ConfigKeyEnum.JWT_TIMEOUT_DAYS); + LocalDateTime expireDate = LocalDateTime.now().plusDays(NumberUtils.toInt(value)); + loginUser.setExpires(DateUtil.formatFrontDate(expireDate)); + return loginUser; + } + + private String createToken(long userId) { + Map data = new HashMap<>(4); + data.put("id", String.valueOf(userId)); + String value = Configs.getValue(ConfigKeyEnum.JWT_TIMEOUT_DAYS); + return JwtUtil.createJwt(data, NumberUtils.toInt(value), getJwtSecret()); + } + + public static String getJwtSecret() { + return Configs.getValue(ConfigKeyEnum.JWT_SECRET); + } + + private SysUser doThirdPartyLogin(ThirdPartyLoginManager thirdPartyLoginManager, String username, String password) { + LoginForm loginForm = new LoginForm(); + loginForm.setUsername(username); + loginForm.setPassword(password); + LoginResult loginResult; + try { + loginResult = thirdPartyLoginManager.login(loginForm); + } catch (Exception e) { + log.error("第三方登录失败", e); + throw new BizException(e.getMessage()); + } + + SysUser userInfo = sysUserService.getByUsername(username); + + // 用户第一次登录则插入到user_info表 + if (userInfo == null) { + userInfo = new SysUser(); + userInfo.setUsername(username); + userInfo.setPassword(GenerateUtil.getUUID()); + userInfo.setNickname(loginResult.getNickname()); + userInfo.setAvatar(""); + userInfo.setStatus(StatusEnum.ENABLE.getValue()); + userInfo.setRegType(loginResult.getRegTypeEnum().getValue()); + userInfo.setEmail(loginResult.getEmail()); + sysUserService.save(userInfo); + } else { + String email = loginResult.getEmail(); + // 如果更改了邮箱 + if (StringUtils.hasText(email) && !Objects.equals(email, userInfo.getEmail())) { + userInfo.setEmail(email); + sysUserService.update(userInfo); + } + } + return userInfo; + } + + private SysUser doDatabaseLogin(String username, String password) { + SysUser sysUser = sysUserService.getByUsername(username); + Assert.notNull(sysUser, () -> "用户名密码不正确"); + String encodedPasswordDb = sysUser.getPassword(); + // 校验 + boolean flag = BCrypt.checkpw(password, encodedPasswordDb); + Assert.isTrue(flag, () -> "用户名密码不正确"); + Assert.isTrue(Objects.equals(sysUser.getStatus(), StatusEnum.ENABLE.getValue()), () -> "用户已禁用"); + return sysUser; + } + +} diff --git a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/login/NotNullStringBuilder.java b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/login/NotNullStringBuilder.java new file mode 100755 index 00000000..6a116353 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/login/NotNullStringBuilder.java @@ -0,0 +1,40 @@ +package com.gitee.sop.admin.service.sys.login; + +/** + * @author thc + */ +public class NotNullStringBuilder { + + private final StringBuilder stringBuilder = new StringBuilder(); + + public NotNullStringBuilder append(Number o) { + stringBuilder.append(formatValue(o)); + return this; + } + + public NotNullStringBuilder append(String o) { + stringBuilder.append(formatValue(o)); + return this; + } + + public NotNullStringBuilder append(String o, String defaultValue) { + if (o == null || o.length() == 0) { + o = defaultValue; + } + stringBuilder.append(formatValue(o)); + return this; + } + + private static String formatValue(Number o) { + return o == null ? "0" : String.valueOf(o); + } + + private static String formatValue(String o) { + return o == null ? "" : o; + } + + @Override + public String toString() { + return stringBuilder.toString(); + } +} diff --git a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/login/ThirdPartyLoginManager.java b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/login/ThirdPartyLoginManager.java new file mode 100755 index 00000000..4f47942a --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/login/ThirdPartyLoginManager.java @@ -0,0 +1,17 @@ +package com.gitee.sop.admin.service.sys.login; + +import com.gitee.sop.admin.service.sys.login.dto.LoginForm; +import com.gitee.sop.admin.service.sys.login.dto.LoginResult; + +/** + * @author 六如 + */ +public interface ThirdPartyLoginManager { + + /** + * 第三方登录 + * @param loginForm 登录表单 + * @return 返回登录结果 + */ + LoginResult login(LoginForm loginForm) throws Exception; +} diff --git a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/login/dto/LoginDTO.java b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/login/dto/LoginDTO.java new file mode 100755 index 00000000..6c1b7a0d --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/login/dto/LoginDTO.java @@ -0,0 +1,11 @@ +package com.gitee.sop.admin.service.sys.login.dto; + +import com.gitee.sop.admin.service.sys.login.enums.RegTypeEnum; +import lombok.Data; + +@Data +public class LoginDTO { + private String username; + private String password; + private RegTypeEnum regType = RegTypeEnum.BACKEND; +} diff --git a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/login/dto/LoginForm.java b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/login/dto/LoginForm.java new file mode 100755 index 00000000..225f6136 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/login/dto/LoginForm.java @@ -0,0 +1,12 @@ +package com.gitee.sop.admin.service.sys.login.dto; + +import lombok.Data; + +/** + * @author 六如 + */ +@Data +public class LoginForm { + private String username; + private String password; +} diff --git a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/login/dto/LoginResult.java b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/login/dto/LoginResult.java new file mode 100755 index 00000000..debe6dac --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/login/dto/LoginResult.java @@ -0,0 +1,20 @@ +package com.gitee.sop.admin.service.sys.login.dto; + +import com.gitee.sop.admin.service.sys.login.enums.RegTypeEnum; +import lombok.Data; + +/** + * @author 六如 + */ +@Data +public class LoginResult { + + private String username; + + private String nickname; + + private String email; + + private RegTypeEnum regTypeEnum = RegTypeEnum.FORM; + +} diff --git a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/login/dto/LoginUser.java b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/login/dto/LoginUser.java new file mode 100755 index 00000000..de4dbe7c --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/login/dto/LoginUser.java @@ -0,0 +1,83 @@ +package com.gitee.sop.admin.service.sys.login.dto; + +import com.gitee.sop.admin.common.user.User; +import lombok.Data; + +import java.util.List; + +/** + * @author 六如 + */ +@Data +public class LoginUser implements User { + + /* + avatar: "https://avatars.githubusercontent.com/u/44761321", + username: "admin", + nickname: "小铭", + // 一个用户可能有多个角色 + roles: ["admin"], + // 按钮级别权限 + permissions: ["*:*:*"], + accessToken: "eyJhbGciOiJIUzUxMiJ9.admin", + refreshToken: "eyJhbGciOiJIUzUxMiJ9.adminRefresh", + expires: "2030/10/30 00:00:00" + */ + + private Long id; + + /** + * 用户名 + */ + private String username; + + /** + * 用户名 + */ + private String nickname; + + /** + * 密码 + */ + private String password; + + /** + * 头像 + */ + private String avatar; + + /** + * 注册类型 + */ + private Integer regType; + + /** + * 状态,1:启用,0:禁用 + */ + private Integer status; + + private List roles; + + private List permissions; + + private String accessToken; + + private String refreshToken; + + private String expires; + + @Override + public Long getUserId() { + return id; + } + + @Override + public Integer getStatus() { + return status; + } + + @Override + public String getToken() { + return accessToken; + } +} diff --git a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/login/enums/RegTypeEnum.java b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/login/enums/RegTypeEnum.java new file mode 100755 index 00000000..15f9477a --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/login/enums/RegTypeEnum.java @@ -0,0 +1,29 @@ +package com.gitee.sop.admin.service.sys.login.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Objects; + +/** + * @author 六如 + */ +@AllArgsConstructor +@Getter +public enum RegTypeEnum { + BACKEND("backend"), + FORM("form"), + OAUTH("oauth"), + LDAP("ldap"); + + public static RegTypeEnum of(String source) { + for (RegTypeEnum value : RegTypeEnum.values()) { + if (Objects.equals(source, value.value)) { + return value; + } + } + return RegTypeEnum.BACKEND; + } + + private final String value; +} diff --git a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/login/impl/DefaultUserCacheManager.java b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/login/impl/DefaultUserCacheManager.java new file mode 100755 index 00000000..5bddf3e2 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/sys/login/impl/DefaultUserCacheManager.java @@ -0,0 +1,90 @@ +package com.gitee.sop.admin.service.sys.login.impl; + +import com.gitee.sop.admin.common.enums.StatusEnum; +import com.gitee.sop.admin.common.manager.UserCacheManager; +import com.gitee.sop.admin.common.user.User; +import com.gitee.sop.admin.common.util.CopyUtil; +import com.gitee.sop.admin.dao.entity.SysUser; +import com.gitee.sop.admin.service.sys.SysUserService; +import com.gitee.sop.admin.service.sys.login.dto.LoginUser; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import java.util.Optional; +import java.util.concurrent.TimeUnit; + +/** + * @author 六如 + */ +@Service +@Slf4j +public class DefaultUserCacheManager implements UserCacheManager, InitializingBean { + + @Autowired + private SysUserService sysUserService; + + @Value("${admin.user-cache-timeout-minutes:15}") + private int timeoutMinutes; + + // key: userId + private LoadingCache> userCache; + + @Override + public User getUser(long userId) { + return userCache.getUnchecked(userId).orElse(null); + } + + @Override + public void saveUser(User user) { + if (user == null) { + return; + } + userCache.put(user.getUserId(), Optional.of(user)); + } + + private LoadingCache> buildCache(int timeout) { + if (timeout <= 0) { + throw new IllegalArgumentException("timeout must be gt 0"); + } + CacheBuilder cacheBuilder = CacheBuilder.newBuilder(); + cacheBuilder.expireAfterAccess(timeout, TimeUnit.MINUTES); + return cacheBuilder + .build(new CacheLoader>() { + @Override + public Optional load(Long id) throws Exception { + User user = getLoginUser(id); + return Optional.ofNullable(user); + } + }); + } + + /** + * 获取登陆用户 + * @param id + * @return + */ + private User getLoginUser(long id) { + SysUser userInfo = sysUserService.getById(id); + if (userInfo == null) { + log.warn("登录用户不存在,userId:{}", id); + return null; + } + if (userInfo.getStatus() == StatusEnum.DISABLED.getValue()) { + log.warn("用户被禁用, userId:{}, username:{}, nickname:{}", userInfo.getId(), userInfo.getUsername(), userInfo.getNickname()); + return null; + } + return CopyUtil.copyBean(userInfo, LoginUser::new); + } + + @Override + public void afterPropertiesSet() throws Exception { + userCache = buildCache(timeoutMinutes); + } + +} diff --git a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/website/WebsiteService.java b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/website/WebsiteService.java new file mode 100755 index 00000000..80a41bfe --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/website/WebsiteService.java @@ -0,0 +1,64 @@ +package com.gitee.sop.admin.service.website; + +import com.gitee.fastmybatis.core.util.TreeUtil; +import com.gitee.sop.admin.common.constants.YesOrNo; +import com.gitee.sop.admin.common.util.CopyUtil; +import com.gitee.sop.admin.dao.entity.DocApp; +import com.gitee.sop.admin.dao.entity.DocInfo; +import com.gitee.sop.admin.service.doc.DocAppService; +import com.gitee.sop.admin.service.doc.DocInfoService; +import com.gitee.sop.admin.service.doc.DocSettingService; +import com.gitee.sop.admin.service.doc.dto.DocAppDTO; +import com.gitee.sop.admin.service.doc.dto.DocInfoConfigDTO; +import com.gitee.sop.admin.service.doc.dto.DocInfoTreeDTO; +import com.gitee.sop.admin.service.doc.dto.DocInfoViewDTO; +import com.gitee.sop.admin.service.doc.dto.DocSettingDTO; +import com.gitee.sop.admin.service.doc.dto.torna.TornaDocInfoViewDTO; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * @author 六如 + */ +@Service +public class WebsiteService { + + @Autowired + private DocAppService docAppService; + @Autowired + private DocInfoService docInfoService; + @Autowired + private DocSettingService docSettingService; + + public List listDocApp() { + List docApps = docAppService.list(DocApp::getIsPublish, YesOrNo.YES); + return CopyUtil.copyList(docApps, DocAppDTO::new); + } + + public List listDocMenuTree(Long docAppId) { + List list = docInfoService.query() + .eq(DocInfo::getDocAppId, docAppId) + .eq(DocInfo::getIsPublish, YesOrNo.YES) + .list(); + List treeList = CopyUtil.copyList(list, DocInfoTreeDTO::new); + return TreeUtil.convertTree(treeList, 0L); + } + + public DocInfoViewDTO getDocDetail(Long id) { + TornaDocInfoViewDTO tornaDocInfoViewDTO = docInfoService.getDocDetail(id); + DocInfoConfigDTO docInfoConfigDTO = buildDocInfoConfig(); + + DocInfoViewDTO docInfoViewDTO = new DocInfoViewDTO(); + docInfoViewDTO.setDocInfoView(tornaDocInfoViewDTO); + docInfoViewDTO.setDocInfoConfig(docInfoConfigDTO); + return docInfoViewDTO; + } + + + private DocInfoConfigDTO buildDocInfoConfig() { + DocSettingDTO docSetting = docSettingService.getDocSetting(); + return CopyUtil.copyBean(docSetting, DocInfoConfigDTO::new); + } +} diff --git a/sop-admin/sop-admin-backend/admin-web/pom.xml b/sop-admin/sop-admin-backend/admin-web/pom.xml new file mode 100755 index 00000000..8036c63d --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-web/pom.xml @@ -0,0 +1,34 @@ + + + 4.0.0 + + com.gitee.sop + sop-admin-backend + 5.0.0-SNAPSHOT + + + admin-web + + + 8 + 8 + UTF-8 + + + + + com.gitee.sop + admin-service + 5.0.0-SNAPSHOT + + + + org.projectlombok + lombok + true + + + + diff --git a/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/AdminHomeController.java b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/AdminHomeController.java new file mode 100755 index 00000000..56312745 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/AdminHomeController.java @@ -0,0 +1,19 @@ +package com.gitee.sop.admin.controller; + +import com.gitee.sop.admin.common.annotation.NoToken; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; + +@Controller +@NoToken +public class AdminHomeController { + + private static final String REDIRECT_INDEX = "forward:index.html"; + + // 后台admin入口地址 + @GetMapping("${index.path}") + public String index() { + return REDIRECT_INDEX; + } + +} diff --git a/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/ExceptionHandlerController.java b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/ExceptionHandlerController.java new file mode 100755 index 00000000..ccce9aff --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/ExceptionHandlerController.java @@ -0,0 +1,51 @@ +package com.gitee.sop.admin.controller; + +import com.gitee.sop.admin.common.exception.BizException; +import com.gitee.sop.admin.common.exception.ErrorCode; +import com.gitee.sop.admin.common.exception.ExceptionCode; +import com.gitee.sop.admin.common.resp.Result; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.support.DefaultMessageSourceResolvable; +import org.springframework.validation.ObjectError; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +import javax.servlet.http.HttpServletRequest; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 全局错误处理 + * + * @author tanghc + */ +@RestControllerAdvice +@Slf4j +public class ExceptionHandlerController { + + @ExceptionHandler(Exception.class) + public Object exceptionHandler(HttpServletRequest request, Exception e) { + if (e instanceof ExceptionCode) { + ExceptionCode exceptionCode = (ExceptionCode) e; + ErrorCode errorCode = exceptionCode.getCode(); + log.error("报错,code:{}, msg:{}", errorCode.getCode(), errorCode.getMsg(), e); + return Result.err(errorCode.getCode(), errorCode.getMsg()); + } + if (e instanceof BizException || e instanceof IllegalArgumentException) { + return Result.err(e.getMessage()); + } + // 处理JSR-303错误 + if (e instanceof MethodArgumentNotValidException) { + MethodArgumentNotValidException exception = (MethodArgumentNotValidException) e; + List allErrors = exception.getBindingResult().getAllErrors(); + String msg = allErrors.stream() + .map(DefaultMessageSourceResolvable::getDefaultMessage) + .collect(Collectors.joining(", ")); + return Result.err(msg); + } + log.error("未知错误,URI:{},HttpMethod:{}", request.getRequestURI(), request.getMethod(), e); + return Result.err("系统错误,请查看日志"); + } + +} diff --git a/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/doc/DocController.java b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/doc/DocController.java new file mode 100755 index 00000000..ed780b14 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/doc/DocController.java @@ -0,0 +1,80 @@ +package com.gitee.sop.admin.controller.doc; + +import com.gitee.sop.admin.common.context.UserContext; +import com.gitee.sop.admin.common.req.IdParam; +import com.gitee.sop.admin.common.resp.Result; +import com.gitee.sop.admin.common.util.CopyUtil; +import com.gitee.sop.admin.controller.doc.param.DocAppAddParam; +import com.gitee.sop.admin.controller.doc.param.DocInfoUpdateParam; +import com.gitee.sop.admin.controller.doc.vo.DocAppVO; +import com.gitee.sop.admin.controller.doc.vo.DocInfoTreeVO; +import com.gitee.sop.admin.service.doc.DocAppService; +import com.gitee.sop.admin.service.doc.DocInfoService; +import com.gitee.sop.admin.service.doc.DocInfoSyncService; +import com.gitee.sop.admin.service.doc.dto.DocAppDTO; +import com.gitee.sop.admin.service.doc.dto.DocInfoPublishUpdateDTO; +import com.gitee.sop.admin.service.doc.dto.DocInfoTreeDTO; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +/** + * @author 六如 + */ +@RestController +@RequestMapping("doc") +public class DocController { + + @Autowired + private DocInfoService docInfoService; + @Autowired + private DocAppService docAppService; + @Autowired + private DocInfoSyncService docInfoSyncService; + + @GetMapping("app/list") + public Result> listApp() { + List docAppDTOS = docAppService.listDocApp(); + List docAppVOS = CopyUtil.copyList(docAppDTOS, DocAppVO::new); + return Result.ok(docAppVOS); + } + + @PostMapping("app/add") + public Result addApp(@Validated @RequestBody DocAppAddParam param) { + Long docAppId = docAppService.addDocApp(param.getTornaToken(), UserContext.getUser()); + return Result.ok(docAppId); + } + + @PostMapping("app/syncAppDoc") + public Result syncAppDoc(@Validated @RequestBody IdParam param) { + docInfoSyncService.syncAppDoc(param.getId(), UserContext.getUser()); + return Result.ok(1); + } + + @PostMapping("app/syncDoc") + public Result syncDoc(@Validated @RequestBody IdParam param) { + docInfoSyncService.syncDoc(param.getId(), UserContext.getUser()); + return Result.ok(1); + } + + @GetMapping("info/tree") + public Result> docTree(@RequestParam Long docAppId) { + List docInfoTreeDTOS = docInfoService.listDocTree(docAppId); + List docInfoTreeVOS = CopyUtil.deepCopyList(docInfoTreeDTOS, DocInfoTreeVO.class); + return Result.ok(docInfoTreeVOS); + } + + @PostMapping("info/publish") + public Result publish(@Validated @RequestBody DocInfoUpdateParam param) { + int cnt = docInfoService.publish(CopyUtil.copyBean(param, DocInfoPublishUpdateDTO::new)); + return Result.ok(cnt); + } + +} diff --git a/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/doc/DocSettingController.java b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/doc/DocSettingController.java new file mode 100755 index 00000000..e94f68f6 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/doc/DocSettingController.java @@ -0,0 +1,41 @@ +package com.gitee.sop.admin.controller.doc; + +import com.gitee.sop.admin.common.resp.Result; +import com.gitee.sop.admin.common.util.CopyUtil; +import com.gitee.sop.admin.controller.doc.vo.DocSettingVO; +import com.gitee.sop.admin.service.doc.DocSettingService; +import com.gitee.sop.admin.service.doc.dto.DocSettingDTO; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * 文档设置 + * + * @author 六如 + */ +@RestController +@RequestMapping("/doc/setting") +public class DocSettingController { + + @Autowired + private DocSettingService docSettingService; + + @GetMapping("get") + public Result get() { + DocSettingDTO docSetting = docSettingService.getDocSetting(); + DocSettingVO docSettingVO = CopyUtil.copyBean(docSetting, DocSettingVO::new); + return Result.ok(docSettingVO); + } + + @PostMapping("save") + public Result save(@Validated @RequestBody DocSettingDTO docSettingVO) { + docSettingService.save(docSettingVO); + return Result.ok(1); + } + +} diff --git a/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/doc/param/DocAppAddParam.java b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/doc/param/DocAppAddParam.java new file mode 100755 index 00000000..b70faeaf --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/doc/param/DocAppAddParam.java @@ -0,0 +1,18 @@ +package com.gitee.sop.admin.controller.doc.param; + +import lombok.Data; +import org.hibernate.validator.constraints.Length; + +import javax.validation.constraints.NotBlank; + +/** + * @author 六如 + */ +@Data +public class DocAppAddParam { + + @NotBlank + @Length(max = 128) + private String tornaToken; + +} diff --git a/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/doc/param/DocInfoUpdateParam.java b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/doc/param/DocInfoUpdateParam.java new file mode 100755 index 00000000..0685c465 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/doc/param/DocInfoUpdateParam.java @@ -0,0 +1,18 @@ +package com.gitee.sop.admin.controller.doc.param; + +import lombok.Data; + +import javax.validation.constraints.NotNull; + +/** + * @author 六如 + */ +@Data +public class DocInfoUpdateParam { + + @NotNull + private Long id; + + private Integer isPublish; + +} diff --git a/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/doc/vo/DocAppVO.java b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/doc/vo/DocAppVO.java new file mode 100755 index 00000000..eddcca44 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/doc/vo/DocAppVO.java @@ -0,0 +1,24 @@ +package com.gitee.sop.admin.controller.doc.vo; + +import lombok.Data; + + +/** + * 备注:文档应用 + * + * @author 六如 + */ +@Data +public class DocAppVO { + + /** + * id + */ + private Long id; + + /** + * 应用名称 + */ + private String appName; + +} diff --git a/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/doc/vo/DocInfoTreeVO.java b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/doc/vo/DocInfoTreeVO.java new file mode 100755 index 00000000..6ae7ca31 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/doc/vo/DocInfoTreeVO.java @@ -0,0 +1,129 @@ +package com.gitee.sop.admin.controller.doc.vo; + +import com.gitee.fastmybatis.core.support.TreeNode; +import com.gitee.sop.admin.common.constants.YesOrNo; +import com.gitee.sop.admin.service.jackson.convert.annotation.UserFormat; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Objects; + + +/** + * 备注:文档信息 + * + * @author 六如 + */ +@Data +public class DocInfoTreeVO implements TreeNode { + + /** + * id + */ + private Long id; + + /** + * doc_app.id + */ + private Long docAppId; + + /** + * 远程文档id + */ + private Long docId; + + /** + * 文档标题 + */ + private String docTitle; + + /** + * 文档code + */ + private String docCode; + + /** + * 文档类型,1-dubbo,2-富文本,3-Markdown + */ + private Integer docType; + + /** + * 来源类型,1-torna,2-自建 + */ + private Integer sourceType; + + /** + * 文档名称 + */ + private String docName; + + /** + * 版本号 + */ + private String docVersion; + + /** + * 描述 + */ + private String description; + + /** + * 是否分类 + */ + private Integer isFolder; + + /** + * 状态, 0-未发布,1-已发布 + */ + private Integer isPublish; + + /** + * 父节点id, 对应docId + */ + private Long parentId; + + /** + * 添加时间 + */ + private LocalDateTime addTime; + + /** + * 修改时间 + */ + private LocalDateTime updateTime; + + /** + * 创建人id + */ + @UserFormat + private Long addBy; + + /** + * 修改人id + */ + @UserFormat + private Long updateBy; + + + private List children; + + + @Override + public Long takeId() { + return docId; + } + + @Override + public Long takeParentId() { + return parentId; + } + + + public String getDocName() { + if (Objects.equals(isFolder, YesOrNo.YES)) { + return ""; + } + return docName; + } +} diff --git a/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/doc/vo/DocSettingVO.java b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/doc/vo/DocSettingVO.java new file mode 100755 index 00000000..fd96adcc --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/doc/vo/DocSettingVO.java @@ -0,0 +1,19 @@ +package com.gitee.sop.admin.controller.doc.vo; + +import lombok.Data; + +/** + * @author 六如 + */ +@Data +public class DocSettingVO { + + private String tornaServerAddr; + + private String openProdUrl; + + private String openSandboxUrl; + + + +} diff --git a/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/isv/IsvInfoController.java b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/isv/IsvInfoController.java new file mode 100755 index 00000000..eeb261b8 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/isv/IsvInfoController.java @@ -0,0 +1,137 @@ +package com.gitee.sop.admin.controller.isv; + +import com.gitee.fastmybatis.core.PageInfo; +import com.gitee.fastmybatis.core.query.LambdaQuery; +import com.gitee.fastmybatis.core.query.param.PageParam; +import com.gitee.sop.admin.common.dto.StatusUpdateDTO; +import com.gitee.sop.admin.common.req.StatusUpdateParam; +import com.gitee.sop.admin.common.resp.Result; +import com.gitee.sop.admin.common.util.CopyUtil; +import com.gitee.sop.admin.common.util.RSATool; +import com.gitee.sop.admin.controller.isv.param.IsvInfoAddParam; +import com.gitee.sop.admin.controller.isv.param.IsvInfoUpdateKeysParam; +import com.gitee.sop.admin.controller.isv.param.IsvInfoUpdateParam; +import com.gitee.sop.admin.controller.isv.param.IsvKeysGenParam; +import com.gitee.sop.admin.controller.isv.vo.IsvInfoVO; +import com.gitee.sop.admin.dao.entity.IsvInfo; +import com.gitee.sop.admin.service.isv.IsvInfoService; +import com.gitee.sop.admin.service.isv.PermIsvGroupService; +import com.gitee.sop.admin.service.isv.dto.IsvInfoAddDTO; +import com.gitee.sop.admin.service.isv.dto.IsvInfoDTO; +import com.gitee.sop.admin.service.isv.dto.IsvInfoUpdateDTO; +import com.gitee.sop.admin.service.isv.dto.IsvInfoUpdateKeysDTO; +import com.gitee.sop.admin.service.isv.dto.IsvKeysDTO; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + + +/** + * @author 六如 + */ +@RestController +@RequestMapping("isv") +public class IsvInfoController { + + @Autowired + private IsvInfoService isvInfoService; + @Autowired + private PermIsvGroupService permIsvGroupService; + + + /** + * 分页查询 + * + * @param param + * @return + */ + @GetMapping("/page") + public Result> page(PageParam param) { + LambdaQuery query = param.toLambdaQuery(IsvInfo.class); + PageInfo isvInfoDTOPageInfo = isvInfoService.doPage(query); + PageInfo retPage = isvInfoDTOPageInfo.convert(isvInfoDTO -> CopyUtil.copyBean(isvInfoDTO, IsvInfoVO::new)); + return Result.ok(retPage); + } + + /** + * 生成秘钥 + * + * @param param + * @return + * @throws Exception + */ + @PostMapping("createKeys") + public Result createKeys(@Validated @RequestBody IsvKeysGenParam param) throws Exception { + RSATool.KeyFormat format = RSATool.KeyFormat.of(param.getKeyFormat()); + RSATool.KeyStore keyStore = isvInfoService.createKeys(format); + return Result.ok(keyStore); + } + + /** + * 获取秘钥信息 + * + * @param isvId + * @return + */ + @GetMapping("/getKeys") + public Result getKeys(Long isvId) { + IsvKeysDTO isvKeysDTO = isvInfoService.getKeys(isvId); + return Result.ok(isvKeysDTO); + } + + /** + * 新增记录 + * + * @param param + * @return 返回添加后的主键值 + */ + @PostMapping("/add") + public Result add(@Validated @RequestBody IsvInfoAddParam param) throws Exception { + IsvInfoAddDTO isvInfoAddDTO = CopyUtil.copyBean(param, IsvInfoAddDTO::new); + long id = isvInfoService.add(isvInfoAddDTO); + // 返回添加后的主键值 + return Result.ok(id); + } + + /** + * 修改记录 + * + * @param param 表单数据 + * @return 返回影响行数 + */ + @PostMapping("/update") + public Result update(@Validated @RequestBody IsvInfoUpdateParam param) { + IsvInfoUpdateDTO isvInfoUpdateDTO = CopyUtil.copyBean(param, IsvInfoUpdateDTO::new); + return Result.ok(isvInfoService.update(isvInfoUpdateDTO)); + } + + /** + * 修改秘钥 + * + * @param param 表单数据 + * @return 返回影响行数 + */ + @PostMapping("/updateKeys") + public Result updateKeys(@Validated @RequestBody IsvInfoUpdateKeysParam param) { + IsvInfoUpdateKeysDTO isvInfoUpdateKeysDTO = CopyUtil.copyBean(param, IsvInfoUpdateKeysDTO::new); + return Result.ok(isvInfoService.updateKeys(isvInfoUpdateKeysDTO)); + } + + /** + * 修改状态 + * + * @param param 表单数据 + * @return 返回影响行数 + */ + @PostMapping("/updateStatus") + public Result updateStatus(@Validated @RequestBody StatusUpdateParam param) { + StatusUpdateDTO statusUpdateDTO = CopyUtil.copyBean(param, StatusUpdateDTO::new); + return Result.ok(isvInfoService.updateStatus(statusUpdateDTO)); + } + + +} diff --git a/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/isv/PermGroupController.java b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/isv/PermGroupController.java new file mode 100755 index 00000000..e160c12f --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/isv/PermGroupController.java @@ -0,0 +1,93 @@ +package com.gitee.sop.admin.controller.isv; + +import com.gitee.fastmybatis.core.PageInfo; +import com.gitee.fastmybatis.core.query.LambdaQuery; +import com.gitee.sop.admin.common.req.IdParam; +import com.gitee.sop.admin.common.resp.Result; +import com.gitee.sop.admin.controller.isv.param.PermGroupPageParam; +import com.gitee.sop.admin.controller.isv.param.PermGroupParam; +import com.gitee.sop.admin.dao.entity.PermGroup; +import com.gitee.sop.admin.service.isv.PermGroupService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +/** + * 权限分组 + * + * @author 六如 + */ +@RestController +@RequestMapping("perm/group") +public class PermGroupController { + + @Autowired + private PermGroupService permGroupService; + + /** + * 查询全部 + * + * @return 返回分页结果 + */ + @GetMapping("/listAll") + public Result> listAll(PermGroupParam param) { + List list = permGroupService.list(param.toQuery()); + return Result.ok(list); + } + + /** + * 分页查询 + * + * @param param 查询参数 + * @return 返回分页结果 + */ + @GetMapping("/page") + public Result> page(PermGroupPageParam param) { + LambdaQuery query = param.toLambdaQuery(PermGroup.class); + PageInfo pageInfo = permGroupService.doPage(query); + return Result.ok(pageInfo); + } + + /** + * 新增记录 + * + * @param permGroup 表单参数 + * @return 返回添加后的主键值 + */ + @PostMapping("/add") + public Result add(@Validated @RequestBody PermGroup permGroup) { + permGroupService.save(permGroup); + // 返回添加后的主键值 + return Result.ok(permGroup.getId()); + } + + /** + * 修改记录 + * + * @param permGroup 表单数据 + * @return 返回影响行数 + */ + @PostMapping("/update") + public Result update(@Validated @RequestBody PermGroup permGroup) { + return Result.ok(permGroupService.update(permGroup)); + } + + /** + * 删除记录 + * + * @param param 参数 + * @return 返回影响行数 + */ + @PostMapping("/delete") + public Result delete(@Validated @RequestBody IdParam param) { + return Result.ok(permGroupService.delete(param.getId())); + } + + +} diff --git a/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/isv/PermGroupPermissionController.java b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/isv/PermGroupPermissionController.java new file mode 100755 index 00000000..5935caa9 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/isv/PermGroupPermissionController.java @@ -0,0 +1,57 @@ +package com.gitee.sop.admin.controller.isv; + +import com.gitee.fastmybatis.core.PageInfo; +import com.gitee.fastmybatis.core.query.LambdaQuery; +import com.gitee.sop.admin.common.resp.Result; +import com.gitee.sop.admin.common.util.CopyUtil; +import com.gitee.sop.admin.controller.isv.param.PermGroupApiInfoParam; +import com.gitee.sop.admin.controller.isv.param.PermGroupPermissionParam; +import com.gitee.sop.admin.dao.entity.ApiInfo; +import com.gitee.sop.admin.service.isv.PermGroupPermissionService; +import com.gitee.sop.admin.service.isv.dto.PermGroupPermissionDTO; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author 六如 + */ +@RestController +@RequestMapping("perm/group/permission") +public class PermGroupPermissionController { + + @Autowired + private PermGroupPermissionService permGroupPermissionService; + + /** + * 查询分组接口 + * + * @param param param + * @return 返回分页结果 + */ + @GetMapping("/page") + public Result> page(Long groupId, @Validated PermGroupApiInfoParam param) { + LambdaQuery query = param.toLambdaQuery(ApiInfo.class); + PageInfo apiInfoList = permGroupPermissionService.pageGroupApiId(groupId, query); + return Result.ok(apiInfoList); + } + + @PostMapping("setting") + public Result setting(@RequestBody @Validated PermGroupPermissionParam param) { + PermGroupPermissionDTO permGroupPermissionDTO = CopyUtil.copyBean(param, PermGroupPermissionDTO::new); + int cnt = permGroupPermissionService.setting(permGroupPermissionDTO); + return Result.ok(cnt); + } + + @PostMapping("delete") + public Result delete(@RequestBody @Validated PermGroupPermissionParam param) { + int cnt = permGroupPermissionService.delete(param.getGroupId(), param.getApiIdList()); + return Result.ok(cnt); + } + + +} diff --git a/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/isv/PermIsvGroupController.java b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/isv/PermIsvGroupController.java new file mode 100755 index 00000000..1b9a8974 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/isv/PermIsvGroupController.java @@ -0,0 +1,61 @@ +package com.gitee.sop.admin.controller.isv; + +import com.gitee.sop.admin.common.context.SpringContext; +import com.gitee.sop.admin.common.resp.Result; +import com.gitee.sop.admin.common.util.CopyUtil; +import com.gitee.sop.admin.controller.isv.param.IsvGroupSettingParam; +import com.gitee.sop.admin.service.isv.PermIsvGroupService; +import com.gitee.sop.admin.service.isv.dto.IsvGroupSettingDTO; +import com.gitee.sop.admin.service.isv.event.ChangeIsvPermEvent; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Collections; +import java.util.List; + +/** + * @author 六如 + */ +@RestController +@RequestMapping("perm/isv/group") +public class PermIsvGroupController { + + @Autowired + private PermIsvGroupService permIsvGroupService; + + /** + * 查询isv分组 + * + * @param isvId isvId + * @return 返回影响行数 + */ + @GetMapping("listIsvGroupId") + public Result> listIsvGroupId(Long isvId) { + List permGroups = permIsvGroupService.listIsvGroupId(isvId); + return Result.ok(permGroups); + } + + /** + * 设置分组 + * + * @param param 表单数据 + * @return 返回影响行数 + */ + @PostMapping("setting") + public Result updateIsvGroup(@Validated @RequestBody IsvGroupSettingParam param) { + IsvGroupSettingDTO isvGroupSettingDTO = CopyUtil.copyBean(param, IsvGroupSettingDTO::new); + int i = permIsvGroupService.updateIsvGroup(isvGroupSettingDTO); + if (i > 0) { + // 刷新isv权限 + SpringContext.publishEvent(new ChangeIsvPermEvent(Collections.singletonList(isvGroupSettingDTO.getIsvId()))); + } + return Result.ok(i); + } + + +} diff --git a/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/isv/param/IsvGroupSettingParam.java b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/isv/param/IsvGroupSettingParam.java new file mode 100755 index 00000000..c0ee8407 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/isv/param/IsvGroupSettingParam.java @@ -0,0 +1,19 @@ +package com.gitee.sop.admin.controller.isv.param; + +import lombok.Data; + +import javax.validation.constraints.NotNull; +import java.util.List; + +/** + * @author 六如 + */ +@Data +public class IsvGroupSettingParam { + + @NotNull + private Long isvId; + + private List groupIds; + +} diff --git a/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/isv/param/IsvInfoAddParam.java b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/isv/param/IsvInfoAddParam.java new file mode 100755 index 00000000..306a97ea --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/isv/param/IsvInfoAddParam.java @@ -0,0 +1,27 @@ +package com.gitee.sop.admin.controller.isv.param; + +import lombok.Data; +import org.hibernate.validator.constraints.Length; + +import javax.validation.constraints.NotNull; + + +/** + * @author 六如 + */ +@Data +public class IsvInfoAddParam { + + /** + * 1启用,2禁用 + */ + @NotNull + private Integer status; + + /** + * 备注 + */ + @Length(max = 500) + private String remark; + +} diff --git a/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/isv/param/IsvInfoUpdateKeysParam.java b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/isv/param/IsvInfoUpdateKeysParam.java new file mode 100755 index 00000000..7ff2d57a --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/isv/param/IsvInfoUpdateKeysParam.java @@ -0,0 +1,47 @@ +package com.gitee.sop.admin.controller.isv.param; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + + +/** + * @author 六如 + */ +@Data +public class IsvInfoUpdateKeysParam { + + @NotNull + private Long isvId; + + /** + * 秘钥格式,1:PKCS8(JAVA适用),2:PKCS1(非JAVA适用) + */ + @NotNull + private Integer keyFormat; + + /** + * 开发者生成的公钥, 数据库字段:public_key_isv + */ + @NotBlank + private String publicKeyIsv; + + /** + * 开发者生成的私钥(交给开发者), 数据库字段:private_key_isv + */ + @NotBlank + private String privateKeyIsv; + + /** + * 平台生成的公钥(交给开发者), 数据库字段:public_key_platform + */ + @NotBlank + private String publicKeyPlatform; + + /** + * 平台生成的私钥, 数据库字段:private_key_platform + */ + @NotBlank + private String privateKeyPlatform; +} diff --git a/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/isv/param/IsvInfoUpdateParam.java b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/isv/param/IsvInfoUpdateParam.java new file mode 100755 index 00000000..1f381e07 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/isv/param/IsvInfoUpdateParam.java @@ -0,0 +1,19 @@ +package com.gitee.sop.admin.controller.isv.param; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.NotNull; + + +/** + * @author 六如 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class IsvInfoUpdateParam extends IsvInfoAddParam { + + @NotNull + private Long id; + +} diff --git a/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/isv/param/IsvKeysGenParam.java b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/isv/param/IsvKeysGenParam.java new file mode 100755 index 00000000..70c72ab9 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/isv/param/IsvKeysGenParam.java @@ -0,0 +1,19 @@ +package com.gitee.sop.admin.controller.isv.param; + +import lombok.Data; + +import javax.validation.constraints.Max; +import javax.validation.constraints.Min; + +/** + * @author 六如 + */ +@Data +public class IsvKeysGenParam { + /** + * 秘钥格式,1:PKCS8(JAVA适用),2:PKCS1(非JAVA适用) + */ + @Min(value = 1, message = "秘钥格式错误") + @Max(value = 2, message = "秘钥格式错误") + private Integer keyFormat; +} diff --git a/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/isv/param/PermGroupApiInfoParam.java b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/isv/param/PermGroupApiInfoParam.java new file mode 100755 index 00000000..6a0d6efc --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/isv/param/PermGroupApiInfoParam.java @@ -0,0 +1,22 @@ +package com.gitee.sop.admin.controller.isv.param; + +import com.gitee.fastmybatis.core.query.Operator; +import com.gitee.fastmybatis.core.query.annotation.Condition; +import com.gitee.fastmybatis.core.query.param.PageParam; +import lombok.Data; + +/** + * @author 六如 + */ +@Data +public class PermGroupApiInfoParam extends PageParam { + + @Condition(operator = Operator.like) + private String apiName; + + @Condition + private Integer status; + + @Condition + private Integer isPermission; +} diff --git a/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/isv/param/PermGroupPageParam.java b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/isv/param/PermGroupPageParam.java new file mode 100755 index 00000000..066fc34b --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/isv/param/PermGroupPageParam.java @@ -0,0 +1,24 @@ +package com.gitee.sop.admin.controller.isv.param; + +import com.gitee.fastmybatis.core.query.Operator; +import com.gitee.fastmybatis.core.query.annotation.Condition; +import com.gitee.fastmybatis.core.query.param.PageParam; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 备注:分组表 + * + * @author 六如 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class PermGroupPageParam extends PageParam { + + /** + * 分组描述 + */ + @Condition(operator = Operator.like) + private String groupName; + +} diff --git a/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/isv/param/PermGroupParam.java b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/isv/param/PermGroupParam.java new file mode 100755 index 00000000..5b3be85e --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/isv/param/PermGroupParam.java @@ -0,0 +1,20 @@ +package com.gitee.sop.admin.controller.isv.param; + +import com.gitee.fastmybatis.core.query.Operator; +import com.gitee.fastmybatis.core.query.annotation.Condition; +import com.gitee.fastmybatis.core.query.param.IParam; +import lombok.Data; + +/** + * @author 六如 + */ +@Data +public class PermGroupParam implements IParam { + + /** + * 分组描述 + */ + @Condition(operator = Operator.like) + private String groupName; + +} diff --git a/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/isv/param/PermGroupPermissionParam.java b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/isv/param/PermGroupPermissionParam.java new file mode 100755 index 00000000..81ca1d98 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/isv/param/PermGroupPermissionParam.java @@ -0,0 +1,19 @@ +package com.gitee.sop.admin.controller.isv.param; + +import lombok.Data; + +import javax.validation.constraints.NotNull; +import java.util.List; + +/** + * @author 六如 + */ +@Data +public class PermGroupPermissionParam { + + @NotNull + private Long groupId; + + private List apiIdList; + +} diff --git a/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/isv/vo/IsvInfoVO.java b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/isv/vo/IsvInfoVO.java new file mode 100755 index 00000000..039d3ea1 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/isv/vo/IsvInfoVO.java @@ -0,0 +1,56 @@ +package com.gitee.sop.admin.controller.isv.vo; + +import com.gitee.sop.admin.service.jackson.convert.annotation.UserFormat; +import lombok.Data; + +import java.time.LocalDateTime; + + +/** + * + * @author 六如 + */ +@Data +public class IsvInfoVO { + + private Long id; + + /** + * appKey + */ + private String appId; + + /** + * 1启用,2禁用 + */ + private Integer status; + + /** + * 是否有秘钥 + */ + private Integer hasKeys; + + /** + * 备注 + */ + private String remark; + + private String groupNames; + + private LocalDateTime addTime; + + private LocalDateTime updateTime; + + /** + * 创建人id + */ + @UserFormat + private Long addBy; + + /** + * 修改人id + */ + @UserFormat + private Long updateBy; + +} diff --git a/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/serve/ApiInfoController.java b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/serve/ApiInfoController.java new file mode 100755 index 00000000..c256286c --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/serve/ApiInfoController.java @@ -0,0 +1,101 @@ +package com.gitee.sop.admin.controller.serve; + +import com.gitee.fastmybatis.core.PageInfo; +import com.gitee.fastmybatis.core.query.Query; +import com.gitee.sop.admin.common.dto.StatusUpdateDTO; +import com.gitee.sop.admin.common.req.StatusUpdateParam; +import com.gitee.sop.admin.common.resp.Result; +import com.gitee.sop.admin.common.util.CopyUtil; +import com.gitee.sop.admin.controller.serve.param.ApiInfoPageParam; +import com.gitee.sop.admin.controller.serve.vo.ApiInfoVO; +import com.gitee.sop.admin.dao.entity.ApiInfo; +import com.gitee.sop.admin.service.serve.ApiInfoService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + + +/** + * 接口管理 + * + * @author 六如 + */ +@RestController +@RequestMapping("serve/api") +public class ApiInfoController { + + @Autowired + private ApiInfoService apiInfoService; + + /** + * 分页查询 + * + * @param param + * @return + */ + @GetMapping("/page") + public Result> page(ApiInfoPageParam param) { + Query query = param.toQuery(); + query.orderByDesc("id"); + PageInfo pageInfo = apiInfoService.page(query); + pageInfo.convert(apiInfo -> CopyUtil.copyBean(apiInfo, ApiInfoVO::new)); + return Result.ok(pageInfo); + } + + /** + * 查询全部 + * + * @param param + * @return + */ + @GetMapping("/listAll") + public Result> listAll(ApiInfoPageParam param) { + Query query = param.toQuery(); + query.orderByDesc("id"); + List list = apiInfoService.list(query); + return Result.ok(CopyUtil.copyList(list, ApiInfoVO::new)); + } + + /** + * 新增记录 + * + * @param user + * @return 返回添加后的主键值 + */ + @PostMapping("/add") + public Result add(@Validated @RequestBody ApiInfo user) { + apiInfoService.save(user); + // 返回添加后的主键值 + return Result.ok(user.getId()); + } + + /** + * 修改记录 + * + * @param user 表单数据 + * @return 返回影响行数 + */ + @PostMapping("/update") + public Result update(@Validated @RequestBody ApiInfo user) { + return Result.ok(apiInfoService.update(user)); + } + + /** + * 修改状态 + * + * @param param 表单数据 + * @return 返回影响行数 + */ + @PostMapping("/updateStatus") + public Result updateStatus(@Validated @RequestBody StatusUpdateParam param) { + StatusUpdateDTO statusUpdateDTO = CopyUtil.copyBean(param, StatusUpdateDTO::new); + return Result.ok(apiInfoService.updateStatus(statusUpdateDTO)); + } + +} diff --git a/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/serve/param/ApiInfoPageParam.java b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/serve/param/ApiInfoPageParam.java new file mode 100755 index 00000000..793fdd03 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/serve/param/ApiInfoPageParam.java @@ -0,0 +1,25 @@ +package com.gitee.sop.admin.controller.serve.param; + +import com.gitee.fastmybatis.core.query.Operator; +import com.gitee.fastmybatis.core.query.annotation.Condition; +import com.gitee.fastmybatis.core.query.param.PageParam; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * @author 六如 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class ApiInfoPageParam extends PageParam { + + @Condition(operator = Operator.like) + private String apiName; + + @Condition + private Integer status; + + @Condition + private Integer isPermission; + +} diff --git a/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/serve/param/ApiInfoParam.java b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/serve/param/ApiInfoParam.java new file mode 100755 index 00000000..14e98ed0 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/serve/param/ApiInfoParam.java @@ -0,0 +1,19 @@ +package com.gitee.sop.admin.controller.serve.param; + +import com.gitee.fastmybatis.core.query.Operator; +import com.gitee.fastmybatis.core.query.annotation.Condition; +import lombok.Data; + +/** + * @author 六如 + */ +@Data +public class ApiInfoParam { + + @Condition(operator = Operator.like) + private String apiName; + + @Condition + private Integer status; + +} diff --git a/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/serve/vo/ApiInfoVO.java b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/serve/vo/ApiInfoVO.java new file mode 100755 index 00000000..2a7ce9c9 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/serve/vo/ApiInfoVO.java @@ -0,0 +1,107 @@ +package com.gitee.sop.admin.controller.serve.vo; + +import com.gitee.sop.admin.service.jackson.convert.annotation.UserFormat; +import lombok.Data; + +import java.time.LocalDateTime; + + +/** + * @author 六如 + */ +@Data +public class ApiInfoVO { + + /** + * id + */ + private Long id; + + /** + * 所属应用 + */ + private String application; + + /** + * 接口名称 + */ + private String apiName; + + /** + * 版本号 + */ + private String apiVersion; + + /** + * 接口描述 + */ + private String description; + + /** + * 备注 + */ + private String remark; + + /** + * 接口class + */ + private String interfaceClassName; + + /** + * 方法名称 + */ + private String methodName; + + /** + * 参数信息 + */ + private String paramInfo; + + /** + * 接口是否需要授权访问 + */ + private Integer isPermission; + + /** + * 是否需要appAuthToken + */ + private Integer isNeedToken; + + /** + * 是否有公共响应参数 + */ + private Integer hasCommonResponse; + + /** + * 注册来源,1-系统注册,2-手动注册 + */ + private Integer regSource; + + /** + * 1启用,0禁用 + */ + private Integer status; + + /** + * 添加时间 + */ + private LocalDateTime addTime; + + /** + * 修改时间 + */ + private LocalDateTime updateTime; + + /** + * 创建人id + */ + @UserFormat + private Long addBy; + + /** + * 最后更新人id + */ + @UserFormat + private Long updateBy; + +} diff --git a/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/sys/LoginController.java b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/sys/LoginController.java new file mode 100755 index 00000000..5ad91b5f --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/sys/LoginController.java @@ -0,0 +1,48 @@ +package com.gitee.sop.admin.controller.sys; + +import com.gitee.sop.admin.common.annotation.NoToken; +import com.gitee.sop.admin.common.resp.Result; +import com.gitee.sop.admin.common.util.CopyUtil; +import com.gitee.sop.admin.controller.sys.param.LoginParam; +import com.gitee.sop.admin.controller.sys.vo.LoginResultVO; +import com.gitee.sop.admin.service.sys.login.LoginService; +import com.gitee.sop.admin.service.sys.login.dto.LoginDTO; +import com.gitee.sop.admin.service.sys.login.dto.LoginUser; +import com.gitee.sop.admin.service.sys.login.enums.RegTypeEnum; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * 登录相关接口 + * + * @author 六如 + */ +@RestController +@RequestMapping("sys") +public class LoginController { + + @Autowired + private LoginService loginService; + + /** + * 用户登录 + * + * @param param + * @return + */ + @PostMapping("login") + @NoToken + public Result login(@Validated @RequestBody LoginParam param) { + LoginDTO loginDTO = new LoginDTO(); + loginDTO.setUsername(param.getUsername()); + loginDTO.setPassword(param.getPassword()); + loginDTO.setRegType(RegTypeEnum.BACKEND); + LoginUser loginUser = loginService.login(loginDTO); + LoginResultVO loginResult = CopyUtil.copyBean(loginUser, LoginResultVO::new); + return Result.ok(loginResult); + } +} diff --git a/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/sys/SysConfigController.java b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/sys/SysConfigController.java new file mode 100755 index 00000000..772d07e1 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/sys/SysConfigController.java @@ -0,0 +1,35 @@ +package com.gitee.sop.admin.controller.sys; + +import com.gitee.sop.admin.common.resp.Result; +import com.gitee.sop.admin.controller.sys.param.ConfigSettingParam; +import com.gitee.sop.admin.service.sys.SysConfigService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author 六如 + */ +@RestController +@RequestMapping("sys/config") +public class SysConfigController { + + @Autowired + private SysConfigService sysConfigService; + + /** + * 保存配置 + * + * @param param param + */ + @PostMapping("/save") + public Result add(@Validated @RequestBody ConfigSettingParam param) { + sysConfigService.save(param.getItems()); + return Result.ok(1); + } + + +} diff --git a/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/sys/SysDeptController.java b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/sys/SysDeptController.java new file mode 100755 index 00000000..a5ba960f --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/sys/SysDeptController.java @@ -0,0 +1,82 @@ +package com.gitee.sop.admin.controller.sys; + +import com.gitee.sop.admin.common.dto.StatusUpdateDTO; +import com.gitee.sop.admin.common.exception.BizException; +import com.gitee.sop.admin.common.req.StatusUpdateParam; +import com.gitee.sop.admin.common.resp.Result; +import com.gitee.sop.admin.common.util.CopyUtil; +import com.gitee.sop.admin.dao.entity.SysDept; +import com.gitee.sop.admin.service.sys.SysDeptService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; +import java.util.Objects; + +/** + * @author 六如 + */ +@RestController +@RequestMapping("sys/dept") +public class SysDeptController { + + @Autowired + private SysDeptService sysDeptService; + + /** + * listAll + * + * @return 返回分页结果 + */ + @GetMapping("/listAll") + public Result> listAll() { + List sysDepts = sysDeptService.listAll(); + return Result.ok(sysDepts); + } + + /** + * 新增记录 + * + * @param sysDept 表单参数 + * @return 返回添加后的主键值 + */ + @PostMapping("/add") + public Result add(@Validated @RequestBody SysDept sysDept) { + Long id = sysDeptService.addDept(sysDept); + // 返回添加后的主键值 + return Result.ok(id); + } + + /** + * 修改记录 + * + * @param sysDept 表单数据 + * @return 返回影响行数 + */ + @PostMapping("/update") + public Result update(@Validated @RequestBody SysDept sysDept) { + if (Objects.equals(sysDept.getParentId(), sysDept.getId())) { + throw new BizException("父级不能选自己"); + } + return Result.ok(sysDeptService.update(sysDept)); + } + + + /** + * 修改状态 + * + * @param param 表单数据 + * @return 返回影响行数 + */ + @PostMapping("/updateStatus") + public Result updateStatus(@Validated @RequestBody StatusUpdateParam param) { + StatusUpdateDTO statusUpdateDTO = CopyUtil.copyBean(param, StatusUpdateDTO::new); + return Result.ok(sysDeptService.updateStatus(statusUpdateDTO)); + } + +} diff --git a/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/sys/SysResourceController.java b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/sys/SysResourceController.java new file mode 100755 index 00000000..1988d04c --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/sys/SysResourceController.java @@ -0,0 +1,128 @@ +package com.gitee.sop.admin.controller.sys; + +import com.gitee.sop.admin.common.context.UserContext; +import com.gitee.sop.admin.common.req.IdParam; +import com.gitee.sop.admin.common.resp.Result; +import com.gitee.sop.admin.common.util.CopyUtil; +import com.gitee.sop.admin.controller.sys.param.SysResourceAddParam; +import com.gitee.sop.admin.controller.sys.param.SysResourceUpdateParam; +import com.gitee.sop.admin.controller.sys.param.SysRoleResourceSaveParam; +import com.gitee.sop.admin.controller.sys.vo.SysResourceVO; +import com.gitee.sop.admin.dao.entity.SysResource; +import com.gitee.sop.admin.service.sys.SysResourceService; +import com.gitee.sop.admin.service.sys.SysRoleResourceService; +import com.gitee.sop.admin.service.sys.dto.SysRoleResourceDTO; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +/** + * 资源菜单 + * + * @author 六如 + */ +@RestController +@RequestMapping("sys/resource") +public class SysResourceController { + + @Autowired + private SysResourceService sysResourceService; + @Autowired + private SysRoleResourceService sysRoleResourceService; + + /** + * 获取菜单树 + * + * @return 返回分页结果 + */ + @GetMapping("/tree") + public Result> tree() { + List list = sysResourceService.listAll(); + List retList = CopyUtil.copyList(list, SysResourceVO::new); + return Result.ok(retList); + } + + /** + * 查询全部资源 + * + * @return 返回分页结果 + */ + @GetMapping("/listAll") + public Result> listAll() { + List list = sysResourceService.listAll(); + List retList = CopyUtil.copyList(list, SysResourceVO::new); + return Result.ok(retList); + } + + + /** + * 新增记录 + * + * @param param 表单参数 + * @return 返回添加后的主键值 + */ + @PostMapping("/add") + public Result add(@Validated @RequestBody SysResourceAddParam param) { + SysResource sysResource = CopyUtil.copyBean(param, SysResource::new); + sysResourceService.save(sysResource); + // 返回添加后的主键值 + return Result.ok(sysResource.getId()); + } + + /** + * 修改记录 + * + * @param param 表单数据 + * @return 返回影响行数 + */ + @PostMapping("/update") + public Result update(@Validated @RequestBody SysResourceUpdateParam param) { + SysResource sysResource = CopyUtil.copyBean(param, SysResource::new); + return Result.ok(sysResourceService.update(sysResource)); + } + + /** + * 删除记录 + * + * @param param 参数 + * @return 返回影响行数 + */ + @PostMapping("/delete") + public Result delete(@Validated @RequestBody IdParam param) { + return Result.ok(sysResourceService.deleteById(param.getId())); + } + + /** + * 保存角色资源 + * + * @param param 参数 + * @return 返回影响行数 + */ + @PostMapping("/saveRoleResource") + public Result saveRoleResource(@Validated @RequestBody SysRoleResourceSaveParam param) { + SysRoleResourceDTO sysRoleResourceDTO = CopyUtil.copyBean(param, SysRoleResourceDTO::new); + sysRoleResourceDTO.setAddBy(UserContext.getUser().getUserId()); + int cnt = sysRoleResourceService.saveRoleResource(sysRoleResourceDTO); + return Result.ok(cnt); + } + + /** + * 获取角色资源 + * + * @param param 参数 + * @return 返回影响行数 + */ + @GetMapping("/listRoleResource") + public Result> listRoleResource(@Validated IdParam param) { + List resourceIds = sysRoleResourceService.listRoleResource(param.getId()); + return Result.ok(resourceIds); + } + + +} diff --git a/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/sys/SysRoleController.java b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/sys/SysRoleController.java new file mode 100755 index 00000000..52744359 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/sys/SysRoleController.java @@ -0,0 +1,106 @@ +package com.gitee.sop.admin.controller.sys; + +import com.gitee.fastmybatis.core.PageInfo; +import com.gitee.fastmybatis.core.query.LambdaQuery; +import com.gitee.sop.admin.common.dto.StatusUpdateDTO; +import com.gitee.sop.admin.common.req.IdParam; +import com.gitee.sop.admin.common.req.StatusUpdateParam; +import com.gitee.sop.admin.common.resp.Result; +import com.gitee.sop.admin.common.util.CopyUtil; +import com.gitee.sop.admin.controller.sys.param.SysRoleParam; +import com.gitee.sop.admin.dao.entity.SysRole; +import com.gitee.sop.admin.service.sys.SysRoleService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +/** + * @author 六如 + */ +@RestController +@RequestMapping("sys/role") +public class SysRoleController { + + @Autowired + private SysRoleService sysRoleService; + + /** + * 分页查询 + * + * @param param 查询参数 + * @return 返回分页结果 + */ + @GetMapping("/page") + public Result> page(SysRoleParam param) { + LambdaQuery query = param.toLambdaQuery(SysRole.class); + PageInfo pageInfo = sysRoleService.page(query); + return Result.ok(pageInfo); + } + + /** + * 所有角色 + * + * @return 返回分页结果 + */ + @GetMapping("/all") + public Result> all() { + List list = sysRoleService.listAll(); + return Result.ok(list); + } + + /** + * 新增记录 + * + * @param sysRole 表单参数 + * @return 返回添加后的主键值 + */ + @PostMapping("/add") + public Result add(@Validated @RequestBody SysRole sysRole) { + sysRoleService.save(sysRole); + // 返回添加后的主键值 + return Result.ok(sysRole.getId()); + } + + /** + * 修改记录 + * + * @param sysRole 表单数据 + * @return 返回影响行数 + */ + @PostMapping("/update") + public Result update(@Validated @RequestBody SysRole sysRole) { + return Result.ok(sysRoleService.update(sysRole)); + } + + /** + * 删除记录 + * + * @param param 参数 + * @return 返回影响行数 + */ + @PostMapping("/delete") + public Result delete(@Validated @RequestBody IdParam param) { + return Result.ok(sysRoleService.deleteById(param.getId())); + } + + + /** + * 修改状态 + * + * @param param 表单数据 + * @return 返回影响行数 + */ + @PostMapping("/updateStatus") + public Result updateStatus(@Validated @RequestBody StatusUpdateParam param) { + StatusUpdateDTO statusUpdateDTO = CopyUtil.copyBean(param, StatusUpdateDTO::new); + return Result.ok(sysRoleService.updateStatus(statusUpdateDTO)); + } + + +} diff --git a/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/sys/SysUserController.java b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/sys/SysUserController.java new file mode 100755 index 00000000..ad50f41b --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/sys/SysUserController.java @@ -0,0 +1,120 @@ +package com.gitee.sop.admin.controller.sys; + +import com.gitee.fastmybatis.core.PageInfo; +import com.gitee.sop.admin.common.context.UserContext; +import com.gitee.sop.admin.common.dto.StatusUpdateBatchDTO; +import com.gitee.sop.admin.common.dto.StatusUpdateDTO; +import com.gitee.sop.admin.common.req.StatusUpdateBatchParam; +import com.gitee.sop.admin.common.req.StatusUpdateParam; +import com.gitee.sop.admin.common.resp.Result; +import com.gitee.sop.admin.common.util.CopyUtil; +import com.gitee.sop.admin.controller.sys.param.RestPasswordParam; +import com.gitee.sop.admin.controller.sys.param.SysUserAddParam; +import com.gitee.sop.admin.controller.sys.param.SysUserParam; +import com.gitee.sop.admin.controller.sys.param.SysUserUpdateParam; +import com.gitee.sop.admin.controller.sys.vo.SysUserVO; +import com.gitee.sop.admin.service.sys.SysUserService; +import com.gitee.sop.admin.service.sys.dto.SysUserAddDTO; +import com.gitee.sop.admin.service.sys.dto.SysUserDTO; +import com.gitee.sop.admin.service.sys.dto.SysUserSearchDTO; +import com.gitee.sop.admin.service.sys.dto.SysUserUpdateDTO; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + + +/** + * @author 六如 + */ +@RestController +@RequestMapping("sys/user") +public class SysUserController { + + @Autowired + private SysUserService sysUserService; + + /** + * 分页查询 + * + * @param param 查询参数 + * @return 返回分页结果 + */ + @GetMapping("/page") + public Result> page(SysUserParam param) { + SysUserSearchDTO sysUserSearchDTO = CopyUtil.copyBean(param, SysUserSearchDTO::new); + PageInfo pageInfo = sysUserService.doPage(sysUserSearchDTO); + PageInfo ret = CopyUtil.copyPageInfo(pageInfo, records -> CopyUtil.deepCopyList(records, SysUserVO.class)); + return Result.ok(ret); + } + + /** + * 新增记录 + * + * @param param 表单参数 + * @return 返回添加后的主键值 + */ + @PostMapping("/add") + public Result add(@Validated @RequestBody SysUserAddParam param) { + SysUserAddDTO sysUser = CopyUtil.copyBean(param, SysUserAddDTO::new); + sysUser.setUpdateBy(UserContext.getUser().getUserId()); + Long id = sysUserService.addUser(sysUser); + // 返回添加后的主键值 + return Result.ok(id); + } + + /** + * 修改记录 + * + * @param param 表单数据 + * @return 返回影响行数 + */ + @PostMapping("/update") + public Result update(@Validated @RequestBody SysUserUpdateParam param) { + SysUserUpdateDTO sysUser = CopyUtil.copyBean(param, SysUserUpdateDTO::new); + sysUser.setUpdateBy(UserContext.getUser().getUserId()); + int cnt = sysUserService.updateUser(sysUser); + return Result.ok(cnt); + } + + /** + * 修改状态 + * + * @param param 表单数据 + * @return 返回影响行数 + */ + @PostMapping("/updateStatus") + public Result updateStatus(@Validated @RequestBody StatusUpdateParam param) { + StatusUpdateDTO statusUpdateDTO = CopyUtil.copyBean(param, StatusUpdateDTO::new); + return Result.ok(sysUserService.updateStatus(statusUpdateDTO)); + } + + /** + * 修改状态批量 + * + * @param param 表单数据 + * @return 返回影响行数 + */ + @PostMapping("/updateStatusBatch") + public Result updateStatus(@Validated @RequestBody StatusUpdateBatchParam param) { + StatusUpdateBatchDTO statusUpdateBatchDTO = CopyUtil.copyBean(param, StatusUpdateBatchDTO::new); + return Result.ok(sysUserService.updateStatus(statusUpdateBatchDTO)); + } + + /** + * 重置用户密码 + * + * @param param 表单数据 + * @return 返回影响行数 + */ + @PostMapping("/resetPassword") + public Result resetPassword(@Validated @RequestBody RestPasswordParam param) { + sysUserService.resetUserPassword(param.getUserId(), param.getPasswordHash()); + return Result.ok(1); + } + + +} diff --git a/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/sys/SysUserRoleController.java b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/sys/SysUserRoleController.java new file mode 100755 index 00000000..b7216c4b --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/sys/SysUserRoleController.java @@ -0,0 +1,51 @@ +package com.gitee.sop.admin.controller.sys; + +import com.gitee.sop.admin.common.context.UserContext; +import com.gitee.sop.admin.common.resp.Result; +import com.gitee.sop.admin.controller.sys.param.SysUserRoleSettingParam; +import com.gitee.sop.admin.service.sys.SysUserRoleService; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; +import java.util.List; + +/** + * @author 六如 + */ +@RestController +@RequestMapping("sys/userrole") +public class SysUserRoleController { + + @Resource + private SysUserRoleService sysUserRoleService; + + /** + * 获取用户角色id + * + * @return 返回角色id + */ + @GetMapping("/getUserRoleIds") + public Result> getUserRoleIds(Long userId) { + List roleIds = sysUserRoleService.listUserRoleIds(userId); + return Result.ok(roleIds); + } + + /** + * 设置用户角色 + * + * @param param 表单数据 + * @return 返回影响行数 + */ + @PostMapping("/setUserRole") + public Result setUserRole(@Validated @RequestBody SysUserRoleSettingParam param) { + sysUserRoleService.setUserRole(param.getUserId(), param.getRoleIds(), UserContext.getUser()); + return Result.ok(1); + } + + +} diff --git a/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/sys/UserPermissionController.java b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/sys/UserPermissionController.java new file mode 100755 index 00000000..48879153 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/sys/UserPermissionController.java @@ -0,0 +1,35 @@ +package com.gitee.sop.admin.controller.sys; + +import com.gitee.sop.admin.common.context.UserContext; +import com.gitee.sop.admin.common.resp.Result; +import com.gitee.sop.admin.service.sys.UserPermissionService; +import com.gitee.sop.admin.service.sys.dto.MenuTreeDTO; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +/** + * @author 六如 + */ +@RestController +@RequestMapping("sys/userperm") +public class UserPermissionController { + + @Autowired + private UserPermissionService userPermissionService; + + /** + * 获取当前登录用户菜单 + * + * @return 返回菜单树 + */ + @GetMapping("listCurrentUserMenu") + public Result> listCurrentUserMenu() { + List menuTreeDTOS = userPermissionService.listUserMenuTree(UserContext.getUserId()); + return Result.ok(menuTreeDTOS); + } + +} diff --git a/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/sys/param/ConfigSettingParam.java b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/sys/param/ConfigSettingParam.java new file mode 100755 index 00000000..9cc0ab7d --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/sys/param/ConfigSettingParam.java @@ -0,0 +1,19 @@ +package com.gitee.sop.admin.controller.sys.param; + +import com.gitee.sop.admin.service.sys.dto.SystemConfigDTO; +import lombok.Data; + +import javax.validation.constraints.NotEmpty; +import java.util.List; + +/** + * @author 六如 + */ +@Data +public class ConfigSettingParam { + + @NotEmpty + private List items; + + +} diff --git a/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/sys/param/LoginParam.java b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/sys/param/LoginParam.java new file mode 100755 index 00000000..63a7743e --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/sys/param/LoginParam.java @@ -0,0 +1,14 @@ +package com.gitee.sop.admin.controller.sys.param; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; + +@Data +public class LoginParam { + @NotBlank(message = "用户名不能为空") + private String username; + + @NotBlank(message = "密码不能为空") + private String password; +} diff --git a/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/sys/param/RestPasswordParam.java b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/sys/param/RestPasswordParam.java new file mode 100755 index 00000000..a525295d --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/sys/param/RestPasswordParam.java @@ -0,0 +1,19 @@ +package com.gitee.sop.admin.controller.sys.param; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +/** + * @author 六如 + */ +@Data +public class RestPasswordParam { + + @NotNull + private Long userId; + + @NotBlank + private String passwordHash; +} diff --git a/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/sys/param/SysDeptParam.java b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/sys/param/SysDeptParam.java new file mode 100755 index 00000000..08094174 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/sys/param/SysDeptParam.java @@ -0,0 +1,31 @@ +package com.gitee.sop.admin.controller.sys.param; + +import com.gitee.fastmybatis.core.query.Operator; +import com.gitee.fastmybatis.core.query.annotation.Condition; +import com.gitee.fastmybatis.core.query.param.PageParam; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 备注:部门表 + * + * @author 六如 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class SysDeptParam extends PageParam { + private static final long serialVersionUID = 7104037961585738100L; + + /** + * 部门名称 + */ + @Condition(operator = Operator.like) + private String name; + + /** + * 状态,1:启用,2:禁用 + */ + @Condition + private Integer status; + +} diff --git a/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/sys/param/SysResourceAddParam.java b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/sys/param/SysResourceAddParam.java new file mode 100755 index 00000000..c4d79f0d --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/sys/param/SysResourceAddParam.java @@ -0,0 +1,155 @@ +package com.gitee.sop.admin.controller.sys.param; + +import com.gitee.sop.admin.common.jackson.convert.annotation.Bool; +import lombok.Data; + +import javax.validation.constraints.NotNull; +import java.time.LocalDateTime; + + +/** + * + * @author 六如 + */ +@Data +public class SysResourceAddParam { + + /** + * 菜单类型(0代表菜单、1代表iframe、2代表外链、3代表按钮) + */ + @NotNull(message = "菜单类型必填") + private Integer menuType; + + /** + * 菜单名称, menuType=0时必填 + */ + private String title; + + /** + * 路由名称, menuType=0时必填 + */ + private String name; + + /** + * 路由路径, menuType=0时必填 + */ + private String path; + + /** + * 组件路径 + */ + private String component; + + /** + * 排序 + */ + private Integer rank; + + /** + * 路由重定向 + */ + private String redirect; + + /** + * 路由重定向 + */ + private String icon; + + /** + * 右侧图标 + */ + private String extraIcon; + + /** + * 进场动画(页面加载动画) + */ + private String enterTransition; + + /** + * 离场动画(页面加载动画) + */ + private String leaveTransition; + + /** + * 菜单激活 + */ + private String activePath; + + /** + * 权限标识 + */ + private String auths; + + /** + * 链接地址(需要内嵌的`iframe`链接地址) + */ + private String frameSrc; + + /** + * 加载动画(内嵌的`iframe`页面是否开启首次加载动画) + */ + @Bool + private Integer frameLoading; + + /** + * 缓存页面 + */ + @Bool + private Integer keepAlive; + + /** + * 标签页(当前菜单名称或自定义信息禁止添加到标签页) + */ + @Bool + private Integer hiddenTag; + + /** + * 固定标签页(当前菜单名称是否固定显示在标签页且不可关闭) + */ + @Bool + private Integer fixedTag; + + /** + * 菜单(是否显示该菜单) + */ + @Bool + private Integer showLink; + + /** + * 父级菜单(是否显示父级菜单 + */ + @Bool + private Integer showParent; + + /** + * 父级id + */ + private Long parentId; + + /** + * 是否删除 + */ + private Integer isDeleted; + + /** + * 添加时间 + */ + private LocalDateTime addTime; + + /** + * 修改时间 + */ + private LocalDateTime updateTime; + + /** + * 创建人id + */ + private Long addBy; + + /** + * 修改人id + */ + private Long updateBy; + + +} diff --git a/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/sys/param/SysResourceUpdateParam.java b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/sys/param/SysResourceUpdateParam.java new file mode 100755 index 00000000..7e1f864a --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/sys/param/SysResourceUpdateParam.java @@ -0,0 +1,19 @@ +package com.gitee.sop.admin.controller.sys.param; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.NotNull; + +/** + * @author 六如 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class SysResourceUpdateParam extends SysResourceAddParam { + /** + * id + */ + @NotNull(message = "id必填") + private Long id; +} diff --git a/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/sys/param/SysRoleParam.java b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/sys/param/SysRoleParam.java new file mode 100755 index 00000000..d5b72929 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/sys/param/SysRoleParam.java @@ -0,0 +1,38 @@ +package com.gitee.sop.admin.controller.sys.param; + +import com.gitee.fastmybatis.core.query.Operator; +import com.gitee.fastmybatis.core.query.annotation.Condition; +import com.gitee.fastmybatis.core.query.param.PageParam; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 备注:角色表 + * + * @author 六如 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class SysRoleParam extends PageParam { + private static final long serialVersionUID = 7794265174728302379L; + + /** + * 角色名称 + */ + @Condition(operator = Operator.like) + private String name; + + /** + * 角色code + */ + @Condition(operator = Operator.like) + private String code; + + /** + * 状态,1:启用,2:禁用 + */ + @Condition + private Integer status; + + +} diff --git a/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/sys/param/SysRoleResourceSaveParam.java b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/sys/param/SysRoleResourceSaveParam.java new file mode 100755 index 00000000..65991a6c --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/sys/param/SysRoleResourceSaveParam.java @@ -0,0 +1,27 @@ +package com.gitee.sop.admin.controller.sys.param; + +import lombok.Data; + +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; +import java.util.List; + +/** + * @author 六如 + */ +@Data +public class SysRoleResourceSaveParam { + + /** + * 角色id + */ + @NotNull + private Long roleId; + + /** + * 资源id + */ + @NotEmpty + private List resourceIds; + +} diff --git a/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/sys/param/SysUserAddParam.java b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/sys/param/SysUserAddParam.java new file mode 100755 index 00000000..ba18c6e2 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/sys/param/SysUserAddParam.java @@ -0,0 +1,95 @@ +package com.gitee.sop.admin.controller.sys.param; + +import lombok.Data; + +import javax.validation.constraints.Min; +import javax.validation.constraints.NotNull; +import java.time.LocalDateTime; + + +/** + * + * @author 六如 + */ +@Data +public class SysUserAddParam { + + /** + * 用户名 + */ + private String username; + + /** + * 密码 + */ + private String password; + + /** + * 昵称 + */ + private String nickname; + + /** + * 邮箱 + */ + private String phone; + + /** + * 邮箱 + */ + private String email; + + /** + * 头像 + */ + private String avatar; + + /** + * 性别,0-未知,1-男,2-女 + */ + private Integer gender; + + /** + * 状态,1:启用,2:禁用 + */ + private Integer status; + + /** + * 注册类型,1-系统,2-手动 + */ + private String regType; + + /** + * 备注 + */ + private String remark; + + /** + * 添加时间 + */ + private LocalDateTime addTime; + + /** + * 修改时间 + */ + private LocalDateTime updateTime; + + /** + * 创建人id + */ + private Long addBy; + + /** + * 修改人id + */ + private Long updateBy; + + /** + * 部门id + */ + @NotNull(message = "请选择部门") + @Min(value = 1, message = "请选择部门") + private Long parentId; + + +} diff --git a/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/sys/param/SysUserParam.java b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/sys/param/SysUserParam.java new file mode 100755 index 00000000..98f2b78e --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/sys/param/SysUserParam.java @@ -0,0 +1,41 @@ +package com.gitee.sop.admin.controller.sys.param; + +import com.gitee.fastmybatis.core.query.Operator; +import com.gitee.fastmybatis.core.query.annotation.Condition; +import com.gitee.fastmybatis.core.query.param.PageParam; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 备注:系统用户表 + * + * @author 六如 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class SysUserParam extends PageParam { + private static final long serialVersionUID = 1L; + + /** + * 用户名 + */ + @Condition(operator = Operator.like) + private String username; + + /** + * 邮箱 + */ + @Condition(operator = Operator.like) + private String phone; + + + /** + * 状态,1:启用,2:禁用 + */ + @Condition + private Integer status; + + @Condition(ignore = true) + private Long deptId; + +} diff --git a/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/sys/param/SysUserRoleSettingParam.java b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/sys/param/SysUserRoleSettingParam.java new file mode 100755 index 00000000..d90e4d19 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/sys/param/SysUserRoleSettingParam.java @@ -0,0 +1,28 @@ +package com.gitee.sop.admin.controller.sys.param; + +import lombok.Data; + +import javax.validation.constraints.NotNull; +import java.util.List; + + +/** + * + * @author 六如 + */ +@Data +public class SysUserRoleSettingParam { + + /** + * sys_role.id + */ + private List roleIds; + + /** + * sys_user.id + */ + @NotNull + private Long userId; + + +} diff --git a/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/sys/param/SysUserUpdateParam.java b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/sys/param/SysUserUpdateParam.java new file mode 100755 index 00000000..a370a715 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/sys/param/SysUserUpdateParam.java @@ -0,0 +1,23 @@ +package com.gitee.sop.admin.controller.sys.param; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.NotNull; + + +/** + * + * @author 六如 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class SysUserUpdateParam extends SysUserAddParam { + + /** + * id + */ + @NotNull(message = "id必填") + private Long id; + +} diff --git a/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/sys/vo/LoginResultVO.java b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/sys/vo/LoginResultVO.java new file mode 100755 index 00000000..2e07c88e --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/sys/vo/LoginResultVO.java @@ -0,0 +1,53 @@ +package com.gitee.sop.admin.controller.sys.vo; + +import lombok.Data; + +import java.util.List; + +/** + * @author 六如 + */ +@Data +public class LoginResultVO { + private Long id; + + /** + * 用户名 + */ + private String username; + + /** + * 用户名 + */ + private String nickname; + + /** + * 密码 + */ + private String password; + + /** + * 头像 + */ + private String avatar; + + /** + * 注册类型 + */ + private Integer regType; + + /** + * 状态,1:启用,0:禁用 + */ + private Integer status; + + private List roles; + + private List permissions; + + private String accessToken; + + private String refreshToken; + + private String expires; +} diff --git a/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/sys/vo/SysDeptVO.java b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/sys/vo/SysDeptVO.java new file mode 100755 index 00000000..4e16320f --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/sys/vo/SysDeptVO.java @@ -0,0 +1,71 @@ +package com.gitee.sop.admin.controller.sys.vo; + +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; + + +/** + * 表名:sys_dept + * 备注:部门表 + * + * @author 六如 + */ +@Data +public class SysDeptVO { + + /** + * id + */ + private Long id; + + /** + * 部门名称 + */ + private String name; + + /** + * 排序 + */ + private Integer sort; + + /** + * 状态,1:启用,2:禁用 + */ + private Integer status; + + /** + * 备注 + */ + private String remark; + + /** + * 父级id + */ + private Long parentId; + + /** + * 添加时间 + */ + private LocalDateTime addTime; + + /** + * 修改时间 + */ + private LocalDateTime updateTime; + + /** + * 创建人id + */ + private Long addBy; + + /** + * 修改人id + */ + private Long updateBy; + + private List children; + + +} diff --git a/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/sys/vo/SysResourceVO.java b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/sys/vo/SysResourceVO.java new file mode 100755 index 00000000..c360b241 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/sys/vo/SysResourceVO.java @@ -0,0 +1,163 @@ +package com.gitee.sop.admin.controller.sys.vo; + +import com.gitee.sop.admin.common.jackson.convert.annotation.Bool; +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import java.time.LocalDateTime; + + +/** + * @author 六如 + */ +@Data +public class SysResourceVO { + + /** + * id + */ + private Long id; + + /** + * 菜单类型(0代表菜单、1代表iframe、2代表外链、3代表按钮) + */ + @NotNull + private Integer menuType; + + /** + * 菜单名称 + */ + @NotBlank + private String title; + + /** + * 路由名称 + */ + @NotBlank + private String name; + + /** + * 路由路径 + */ + @NotBlank + private String path; + + /** + * 路由路径 + */ + private String component; + + /** + * 排序 + */ + private Integer rank; + + /** + * 路由重定向 + */ + private String redirect; + + /** + * 路由重定向 + */ + private String icon; + + /** + * 右侧图标 + */ + private String extraIcon; + + /** + * 进场动画(页面加载动画) + */ + private String enterTransition; + + /** + * 离场动画(页面加载动画) + */ + private String leaveTransition; + + /** + * 菜单激活 + */ + private String activePath; + + /** + * 权限标识 + */ + private String auths; + + /** + * 链接地址(需要内嵌的`iframe`链接地址) + */ + private String frameSrc; + + /** + * 加载动画(内嵌的`iframe`页面是否开启首次加载动画) + */ + @Bool + private Integer frameLoading; + + /** + * 缓存页面 + */ + @Bool + private Integer keepAlive; + + /** + * 标签页(当前菜单名称或自定义信息禁止添加到标签页) + */ + @Bool + private Integer hiddenTag; + + /** + * 固定标签页(当前菜单名称是否固定显示在标签页且不可关闭) + */ + @Bool + private Integer fixedTag; + + /** + * 菜单(是否显示该菜单) + */ + @Bool + private Integer showLink; + + /** + * 父级菜单(是否显示父级菜单 + */ + @Bool + private Integer showParent; + + /** + * 父级id + */ + private Long parentId; + + /** + * 是否删除 + */ + private Integer isDeleted; + + /** + * 添加时间 + */ + private LocalDateTime addTime; + + /** + * 修改时间 + */ + private LocalDateTime updateTime; + + /** + * 创建人id + */ + private Long addBy; + + /** + * 修改人id + */ + private Long updateBy; + + +} diff --git a/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/sys/vo/SysUserVO.java b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/sys/vo/SysUserVO.java new file mode 100755 index 00000000..92f9081f --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/sys/vo/SysUserVO.java @@ -0,0 +1,91 @@ +package com.gitee.sop.admin.controller.sys.vo; + +import lombok.Data; + +import java.time.LocalDateTime; + + +/** + * + * @author 六如 + */ +@Data +public class SysUserVO { + + /** + * id + */ + private Long id; + + /** + * 用户名 + */ + private String username; + + /** + * 昵称 + */ + private String nickname; + + /** + * 邮箱 + */ + private String phone; + + /** + * 邮箱 + */ + private String email; + + /** + * 头像 + */ + private String avatar; + + /** + * 性别,0-未知,1-男,2-女 + */ + private Integer gender; + + /** + * 状态,1:启用,2:禁用 + */ + private Integer status; + + /** + * 注册类型,1-系统,2-手动 + */ + private String regType; + + /** + * 备注 + */ + private String remark; + + /** + * 添加时间 + */ + private LocalDateTime addTime; + + /** + * 修改时间 + */ + private LocalDateTime updateTime; + + /** + * 创建人id + */ + private Long addBy; + + /** + * 修改人id + */ + private Long updateBy; + + /** + * 部门 + */ + private SysDeptVO dept; + + +} diff --git a/sop-admin/sop-admin-backend/pom.xml b/sop-admin/sop-admin-backend/pom.xml new file mode 100755 index 00000000..dc671027 --- /dev/null +++ b/sop-admin/sop-admin-backend/pom.xml @@ -0,0 +1,30 @@ + + + 4.0.0 + + com.gitee.sop + sop-admin + 5.0.0-SNAPSHOT + + + sop-admin-backend + pom + + + 8 + 8 + UTF-8 + + + + + admin-common + admin-service + admin-dao + admin-boot + admin-web + + + diff --git a/sop-admin/sop-admin-backend/website-web/pom.xml b/sop-admin/sop-admin-backend/website-web/pom.xml new file mode 100755 index 00000000..3bcccf8f --- /dev/null +++ b/sop-admin/sop-admin-backend/website-web/pom.xml @@ -0,0 +1,34 @@ + + + 4.0.0 + + com.gitee.sop + sop-admin-backend + 5.0.0-SNAPSHOT + + + website-web + + + 8 + 8 + UTF-8 + + + + + com.gitee.sop + backend-service + 5.0.0-SNAPSHOT + + + + org.projectlombok + lombok + true + + + + diff --git a/sop-admin/sop-admin-backend/website-web/src/main/java/com/gitee/sop/adminbackend/controller/website/WebsiteController.java b/sop-admin/sop-admin-backend/website-web/src/main/java/com/gitee/sop/adminbackend/controller/website/WebsiteController.java new file mode 100755 index 00000000..5cc98562 --- /dev/null +++ b/sop-admin/sop-admin-backend/website-web/src/main/java/com/gitee/sop/adminbackend/controller/website/WebsiteController.java @@ -0,0 +1,67 @@ +package com.gitee.sop.admin.controller.website; + +import com.gitee.sop.admin.common.annotation.NoToken; +import com.gitee.sop.admin.common.resp.Result; +import com.gitee.sop.admin.common.util.CopyUtil; +import com.gitee.sop.admin.controller.website.vo.DocAppVO; +import com.gitee.sop.admin.controller.website.vo.DocInfoViewVO; +import com.gitee.sop.admin.controller.website.vo.DocInfoTreeVO; +import com.gitee.sop.admin.service.doc.dto.DocAppDTO; +import com.gitee.sop.admin.service.doc.dto.DocInfoTreeDTO; +import com.gitee.sop.admin.service.doc.dto.DocInfoViewDTO; +import com.gitee.sop.admin.service.website.WebsiteService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +/** + * 提供给网站的接口,不需要校验token + * + * @author 六如 + */ +@RestController +@RequestMapping("website") +@NoToken +public class WebsiteController { + + @Autowired + private WebsiteService websiteService; + + /** + * 获取文档应用列表 + */ + @GetMapping("docapp/list") + public Result> listDocApp() { + List docAppDTOS = websiteService.listDocApp(); + List docAppVOS = CopyUtil.deepCopyList(docAppDTOS, DocAppVO.class); + return Result.ok(docAppVOS); + } + + /** + * 获取文档菜单树 + * + * @param docAppId 应用id + */ + @GetMapping("docinfo/tree") + public Result> listDocMenuTree(Long docAppId) { + List docInfoTreeDTOS = websiteService.listDocMenuTree(docAppId); + List docAppVOS = CopyUtil.deepCopyList(docInfoTreeDTOS, DocInfoTreeVO.class); + return Result.ok(docAppVOS); + } + + /** + * 获取文档详情 + * + * @param id id + */ + @GetMapping("docinfo/detail") + public Result getDocDetail(Long id) { + DocInfoViewDTO docInfoViewDTO = websiteService.getDocDetail(id); + DocInfoViewVO docInfoViewVO = CopyUtil.deepCopy(docInfoViewDTO, DocInfoViewVO.class); + return Result.ok(docInfoViewVO); + } + +} diff --git a/sop-admin/sop-admin-backend/website-web/src/main/java/com/gitee/sop/adminbackend/controller/website/vo/DocAppVO.java b/sop-admin/sop-admin-backend/website-web/src/main/java/com/gitee/sop/adminbackend/controller/website/vo/DocAppVO.java new file mode 100755 index 00000000..1917877f --- /dev/null +++ b/sop-admin/sop-admin-backend/website-web/src/main/java/com/gitee/sop/adminbackend/controller/website/vo/DocAppVO.java @@ -0,0 +1,24 @@ +package com.gitee.sop.admin.controller.website.vo; + +import lombok.Data; + + +/** + * 备注:文档应用 + * + * @author 六如 + */ +@Data +public class DocAppVO { + + /** + * id + */ + private Long id; + + /** + * 应用名称 + */ + private String appName; + +} diff --git a/sop-admin/sop-admin-backend/website-web/src/main/java/com/gitee/sop/adminbackend/controller/website/vo/DocInfoConfigVO.java b/sop-admin/sop-admin-backend/website-web/src/main/java/com/gitee/sop/adminbackend/controller/website/vo/DocInfoConfigVO.java new file mode 100755 index 00000000..00a263b1 --- /dev/null +++ b/sop-admin/sop-admin-backend/website-web/src/main/java/com/gitee/sop/adminbackend/controller/website/vo/DocInfoConfigVO.java @@ -0,0 +1,14 @@ +package com.gitee.sop.admin.controller.website.vo; + +import lombok.Data; + +/** + * @author 六如 + */ +@Data +public class DocInfoConfigVO { + + private String openProdUrl; + private String openSandboxUrl; + +} diff --git a/sop-admin/sop-admin-backend/website-web/src/main/java/com/gitee/sop/adminbackend/controller/website/vo/DocInfoTreeVO.java b/sop-admin/sop-admin-backend/website-web/src/main/java/com/gitee/sop/adminbackend/controller/website/vo/DocInfoTreeVO.java new file mode 100755 index 00000000..c72bc487 --- /dev/null +++ b/sop-admin/sop-admin-backend/website-web/src/main/java/com/gitee/sop/adminbackend/controller/website/vo/DocInfoTreeVO.java @@ -0,0 +1,100 @@ +package com.gitee.sop.admin.controller.website.vo; + +import com.gitee.fastmybatis.core.support.TreeNode; +import com.gitee.sop.admin.common.constants.YesOrNo; +import lombok.Data; + +import java.util.List; +import java.util.Objects; + + +/** + * 备注:文档信息 + * + * @author 六如 + */ +@Data +public class DocInfoTreeVO implements TreeNode { + + /** + * id + */ + private Long id; + + /** + * doc_app.id + */ + private Long docAppId; + + /** + * 文档id + */ + private Long docId; + + /** + * 文档标题 + */ + private String docTitle; + + /** + * 文档code + */ + private String docCode; + + /** + * 文档类型,1-dubbo,2-富文本,3-Markdown + */ + private Integer docType; + + /** + * 来源类型,1-torna,2-自建 + */ + private Integer sourceType; + + /** + * 文档名称 + */ + private String docName; + + /** + * 版本号 + */ + private String docVersion; + + /** + * 描述 + */ + private String description; + + /** + * 是否分类 + */ + private Integer isFolder; + + /** + * 父节点id + */ + private Long parentId; + + + private List children; + + + @Override + public Long takeId() { + return docId; + } + + @Override + public Long takeParentId() { + return parentId; + } + + + public String getDocName() { + if (Objects.equals(isFolder, YesOrNo.YES)) { + return ""; + } + return docName; + } +} diff --git a/sop-admin/sop-admin-backend/website-web/src/main/java/com/gitee/sop/adminbackend/controller/website/vo/DocInfoViewVO.java b/sop-admin/sop-admin-backend/website-web/src/main/java/com/gitee/sop/adminbackend/controller/website/vo/DocInfoViewVO.java new file mode 100755 index 00000000..b2a68e54 --- /dev/null +++ b/sop-admin/sop-admin-backend/website-web/src/main/java/com/gitee/sop/adminbackend/controller/website/vo/DocInfoViewVO.java @@ -0,0 +1,15 @@ +package com.gitee.sop.admin.controller.website.vo; + +import lombok.Data; + +/** + * @author 六如 + */ +@Data +public class DocInfoViewVO { + + private TornaDocInfoViewVO docInfoView; + + private DocInfoConfigVO docInfoConfig; + +} diff --git a/sop-admin/sop-admin-backend/website-web/src/main/java/com/gitee/sop/adminbackend/controller/website/vo/TornaDocInfoViewVO.java b/sop-admin/sop-admin-backend/website-web/src/main/java/com/gitee/sop/adminbackend/controller/website/vo/TornaDocInfoViewVO.java new file mode 100755 index 00000000..cf1a5778 --- /dev/null +++ b/sop-admin/sop-admin-backend/website-web/src/main/java/com/gitee/sop/adminbackend/controller/website/vo/TornaDocInfoViewVO.java @@ -0,0 +1,179 @@ +package com.gitee.sop.admin.controller.website.vo; + +import com.gitee.sop.admin.service.doc.dto.torna.TornaDocParamDTO; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.Collections; +import java.util.List; + +/** + * @author tanghc + */ +@Data +public class TornaDocInfoViewVO { + private Long id; + + /** + * 文档名称 + */ + private String name; + + /** + * 文档概述 + */ + + private String description; + + /** + * 0:http,1:dubbo + */ + private Byte type; + + /** + * 访问URL + */ + + private String url; + + /** + * 版本号 + */ + private String version = ""; + + private String docKey; + + /** + * http方法 + */ + + private String httpMethod; + + /** + * contentType + */ + + private String contentType; + + + /** + * 是否是分类,0:不是,1:是 + */ + private Byte isFolder; + + /** + * 父节点 + */ + + private Long parentId; + + /** + * 模块id,module.id + */ + + private Long moduleId; + + /** + * 项目id + */ + + private Long projectId; + + /** + * 是否使用全局请求参数 + */ + + private Byte isUseGlobalHeaders; + + /** + * 是否使用全局请求参数 + */ + + private Byte isUseGlobalParams; + + /** + * 是否使用全局返回参数 + */ + + private Byte isUseGlobalReturns; + + /** + * 是否请求数组 + */ + + private Byte isRequestArray; + + /** + * 是否返回数组 + */ + + private Byte isResponseArray; + + /** + * 请求数组时元素类型 + */ + + private String requestArrayType; + + /** + * 返回数组时元素类型 + */ + + private String responseArrayType; + + /** + * 文档状态 + */ + + private Byte status; + + private String remark; + + private Integer orderIndex; + + /** + * 数据库字段:gmt_create + */ + private LocalDateTime gmtCreate; + + /** + * 数据库字段:gmt_modified + */ + private LocalDateTime gmtModified; + + + private List pathParams = Collections.emptyList(); + + + private List headerParams = Collections.emptyList(); + + private List headerParamsRaw = Collections.emptyList(); + + + private List queryParams = Collections.emptyList(); + + + private List requestParams = Collections.emptyList(); + + + private List responseParams = Collections.emptyList(); + + private List errorCodeParams = Collections.emptyList(); + + private List globalHeaders = Collections.emptyList(); + private List globalParams = Collections.emptyList(); + private List globalReturns = Collections.emptyList(); + + private String errorCodeInfo; + + private List children = Collections.emptyList(); + + public String getDocName() { + return url; + } + + public String getDocTitle() { + return name; + } + +} diff --git a/sop-admin/sop-admin-frontend/.browserslistrc b/sop-admin/sop-admin-frontend/.browserslistrc new file mode 100755 index 00000000..40bd99ce --- /dev/null +++ b/sop-admin/sop-admin-frontend/.browserslistrc @@ -0,0 +1,4 @@ +> 1% +last 2 versions +not dead +not ie 11 \ No newline at end of file diff --git a/sop-admin/sop-admin-frontend/.dockerignore b/sop-admin/sop-admin-frontend/.dockerignore new file mode 100755 index 00000000..0376edde --- /dev/null +++ b/sop-admin/sop-admin-frontend/.dockerignore @@ -0,0 +1,21 @@ +node_modules +.DS_Store +dist +dist-ssr +*.local +.eslintcache +report.html + +yarn.lock +npm-debug.log* +.pnpm-error.log* +.pnpm-debug.log +tests/**/coverage/ + +# Editor directories and files +.idea +*.suo +*.ntvs* +*.njsproj +*.sln +tsconfig.tsbuildinfo diff --git a/sop-admin/sop-admin-vue/.editorconfig b/sop-admin/sop-admin-frontend/.editorconfig old mode 100644 new mode 100755 similarity index 100% rename from sop-admin/sop-admin-vue/.editorconfig rename to sop-admin/sop-admin-frontend/.editorconfig diff --git a/sop-admin/sop-admin-frontend/.env b/sop-admin/sop-admin-frontend/.env new file mode 100755 index 00000000..d34647a5 --- /dev/null +++ b/sop-admin/sop-admin-frontend/.env @@ -0,0 +1,5 @@ +# 平台本地运行端口号 +VITE_PORT = 9123 + +# 是否隐藏首页 隐藏 true 不隐藏 false (勿删除,VITE_HIDE_HOME只需在.env文件配置) +VITE_HIDE_HOME = false diff --git a/sop-admin/sop-admin-frontend/.env.development b/sop-admin/sop-admin-frontend/.env.development new file mode 100755 index 00000000..1f52c45c --- /dev/null +++ b/sop-admin/sop-admin-frontend/.env.development @@ -0,0 +1,8 @@ +# 平台本地运行端口号 +VITE_PORT = 9123 + +# 开发环境读取配置文件路径 +VITE_PUBLIC_PATH = / + +# 开发环境路由历史模式(Hash模式传"hash"、HTML5模式传"h5"、Hash模式带base参数传"hash,base参数"、HTML5模式带base参数传"h5,base参数") +VITE_ROUTER_HISTORY = "hash" diff --git a/sop-admin/sop-admin-frontend/.env.production b/sop-admin/sop-admin-frontend/.env.production new file mode 100755 index 00000000..84e60861 --- /dev/null +++ b/sop-admin/sop-admin-frontend/.env.production @@ -0,0 +1,13 @@ +# 线上环境平台打包路径 +VITE_PUBLIC_PATH = / + +# 线上环境路由历史模式(Hash模式传"hash"、HTML5模式传"h5"、Hash模式带base参数传"hash,base参数"、HTML5模式带base参数传"h5,base参数") +VITE_ROUTER_HISTORY = "hash" + +# 是否在打包时使用cdn替换本地库 替换 true 不替换 false +VITE_CDN = false + +# 是否启用gzip压缩或brotli压缩(分两种情况,删除原始文件和不删除原始文件) +# 压缩时不删除原始文件的配置:gzip、brotli、both(同时开启 gzip 与 brotli 压缩)、none(不开启压缩,默认) +# 压缩时删除原始文件的配置:gzip-clear、brotli-clear、both-clear(同时开启 gzip 与 brotli 压缩)、none(不开启压缩,默认) +VITE_COMPRESSION = "none" \ No newline at end of file diff --git a/sop-admin/sop-admin-frontend/.env.staging b/sop-admin/sop-admin-frontend/.env.staging new file mode 100755 index 00000000..65b57e3c --- /dev/null +++ b/sop-admin/sop-admin-frontend/.env.staging @@ -0,0 +1,16 @@ +# 预发布也需要生产环境的行为 +# https://cn.vitejs.dev/guide/env-and-mode.html#modes +# NODE_ENV = development + +VITE_PUBLIC_PATH = / + +# 预发布环境路由历史模式(Hash模式传"hash"、HTML5模式传"h5"、Hash模式带base参数传"hash,base参数"、HTML5模式带base参数传"h5,base参数") +VITE_ROUTER_HISTORY = "hash" + +# 是否在打包时使用cdn替换本地库 替换 true 不替换 false +VITE_CDN = true + +# 是否启用gzip压缩或brotli压缩(分两种情况,删除原始文件和不删除原始文件) +# 压缩时不删除原始文件的配置:gzip、brotli、both(同时开启 gzip 与 brotli 压缩)、none(不开启压缩,默认) +# 压缩时删除原始文件的配置:gzip-clear、brotli-clear、both-clear(同时开启 gzip 与 brotli 压缩)、none(不开启压缩,默认) +VITE_COMPRESSION = "none" diff --git a/sop-admin/sop-admin-frontend/.gitignore b/sop-admin/sop-admin-frontend/.gitignore new file mode 100755 index 00000000..423ed2b8 --- /dev/null +++ b/sop-admin/sop-admin-frontend/.gitignore @@ -0,0 +1,22 @@ +node_modules +.DS_Store +dist +dist-ssr +*.local +.eslintcache +report.html +vite.config.*.timestamp* + +yarn.lock +npm-debug.log* +.pnpm-error.log* +.pnpm-debug.log +tests/**/coverage/ + +# Editor directories and files +.idea +*.suo +*.ntvs* +*.njsproj +*.sln +tsconfig.tsbuildinfo \ No newline at end of file diff --git a/sop-admin/sop-admin-frontend/.husky/commit-msg b/sop-admin/sop-admin-frontend/.husky/commit-msg new file mode 100755 index 00000000..5ee2d163 --- /dev/null +++ b/sop-admin/sop-admin-frontend/.husky/commit-msg @@ -0,0 +1,8 @@ +#!/bin/sh + +# shellcheck source=./_/husky.sh +. "$(dirname "$0")/_/husky.sh" + +PATH="/usr/local/bin:$PATH" + +npx --no-install commitlint --edit "$1" \ No newline at end of file diff --git a/sop-admin/sop-admin-frontend/.husky/common.sh b/sop-admin/sop-admin-frontend/.husky/common.sh new file mode 100755 index 00000000..5f0540b7 --- /dev/null +++ b/sop-admin/sop-admin-frontend/.husky/common.sh @@ -0,0 +1,9 @@ +#!/bin/sh +command_exists () { + command -v "$1" >/dev/null 2>&1 +} + +# Workaround for Windows 10, Git Bash and Pnpm +if command_exists winpty && test -t 1; then + exec < /dev/tty +fi diff --git a/sop-admin/sop-admin-frontend/.husky/pre-commit b/sop-admin/sop-admin-frontend/.husky/pre-commit new file mode 100755 index 00000000..6e229ea3 --- /dev/null +++ b/sop-admin/sop-admin-frontend/.husky/pre-commit @@ -0,0 +1,10 @@ +#!/bin/sh +. "$(dirname "$0")/_/husky.sh" +. "$(dirname "$0")/common.sh" + +[ -n "$CI" ] && exit 0 + +PATH="/usr/local/bin:$PATH" + +# Perform lint check on files in the staging area through .lintstagedrc configuration +pnpm exec lint-staged \ No newline at end of file diff --git a/sop-admin/sop-admin-frontend/.lintstagedrc b/sop-admin/sop-admin-frontend/.lintstagedrc new file mode 100755 index 00000000..ebf359aa --- /dev/null +++ b/sop-admin/sop-admin-frontend/.lintstagedrc @@ -0,0 +1,20 @@ +{ + "*.{js,jsx,ts,tsx}": [ + "prettier --cache --ignore-unknown --write", + "eslint --cache --fix" + ], + "{!(package)*.json,*.code-snippets,.!({browserslist,npm,nvm})*rc}": [ + "prettier --cache --write--parser json" + ], + "package.json": ["prettier --cache --write"], + "*.vue": [ + "prettier --write", + "eslint --cache --fix", + "stylelint --fix --allow-empty-input" + ], + "*.{css,scss,html}": [ + "prettier --cache --ignore-unknown --write", + "stylelint --fix --allow-empty-input" + ], + "*.md": ["prettier --cache --ignore-unknown --write"] +} diff --git a/sop-admin/sop-admin-frontend/.markdownlint.json b/sop-admin/sop-admin-frontend/.markdownlint.json new file mode 100755 index 00000000..d628d441 --- /dev/null +++ b/sop-admin/sop-admin-frontend/.markdownlint.json @@ -0,0 +1,11 @@ +{ + "default": true, + "MD003": false, + "MD033": false, + "MD013": false, + "MD001": false, + "MD025": false, + "MD024": false, + "MD007": { "indent": 4 }, + "no-hard-tabs": false +} diff --git a/sop-admin/sop-admin-frontend/.npmrc b/sop-admin/sop-admin-frontend/.npmrc new file mode 100755 index 00000000..dddf8bc0 --- /dev/null +++ b/sop-admin/sop-admin-frontend/.npmrc @@ -0,0 +1,4 @@ +shell-emulator=true +shamefully-hoist=true +enable-pre-post-scripts=false +strict-peer-dependencies=false \ No newline at end of file diff --git a/sop-admin/sop-admin-frontend/.nvmrc b/sop-admin/sop-admin-frontend/.nvmrc new file mode 100755 index 00000000..47465842 --- /dev/null +++ b/sop-admin/sop-admin-frontend/.nvmrc @@ -0,0 +1 @@ +v20.15.0 \ No newline at end of file diff --git a/sop-admin/sop-admin-frontend/.prettierrc.js b/sop-admin/sop-admin-frontend/.prettierrc.js new file mode 100755 index 00000000..775d970a --- /dev/null +++ b/sop-admin/sop-admin-frontend/.prettierrc.js @@ -0,0 +1,9 @@ +// @ts-check + +/** @type {import("prettier").Config} */ +export default { + bracketSpacing: true, + singleQuote: false, + arrowParens: "avoid", + trailingComma: "none" +}; diff --git a/sop-admin/sop-admin-frontend/.stylelintignore b/sop-admin/sop-admin-frontend/.stylelintignore new file mode 100755 index 00000000..0c34e619 --- /dev/null +++ b/sop-admin/sop-admin-frontend/.stylelintignore @@ -0,0 +1,4 @@ +/dist/* +/public/* +public/* +src/style/reset.scss \ No newline at end of file diff --git a/sop-admin/sop-admin-frontend/.vscode/extensions.json b/sop-admin/sop-admin-frontend/.vscode/extensions.json new file mode 100755 index 00000000..b36b3f82 --- /dev/null +++ b/sop-admin/sop-admin-frontend/.vscode/extensions.json @@ -0,0 +1,19 @@ +{ + "recommendations": [ + "christian-kohler.path-intellisense", + "warmthsea.vscode-custom-code-color", + "vscode-icons-team.vscode-icons", + "davidanson.vscode-markdownlint", + "ms-azuretools.vscode-docker", + "stylelint.vscode-stylelint", + "bradlc.vscode-tailwindcss", + "dbaeumer.vscode-eslint", + "esbenp.prettier-vscode", + "redhat.vscode-yaml", + "csstools.postcss", + "mikestead.dotenv", + "eamodio.gitlens", + "antfu.iconify", + "Vue.volar" + ] +} \ No newline at end of file diff --git a/sop-admin/sop-admin-frontend/.vscode/settings.json b/sop-admin/sop-admin-frontend/.vscode/settings.json new file mode 100755 index 00000000..388b96f4 --- /dev/null +++ b/sop-admin/sop-admin-frontend/.vscode/settings.json @@ -0,0 +1,43 @@ +{ + "editor.formatOnType": true, + "editor.formatOnSave": true, + "[vue]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "editor.tabSize": 2, + "editor.formatOnPaste": true, + "editor.guides.bracketPairs": "active", + "files.autoSave": "afterDelay", + "git.confirmSync": false, + "workbench.startupEditor": "newUntitledFile", + "editor.suggestSelection": "first", + "editor.acceptSuggestionOnCommitCharacter": false, + "css.lint.propertyIgnoredDueToDisplay": "ignore", + "editor.quickSuggestions": { + "other": true, + "comments": true, + "strings": true + }, + "files.associations": { + "editor.snippetSuggestions": "top" + }, + "[css]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "editor.codeActionsOnSave": { + "source.fixAll.eslint": "explicit" + }, + "iconify.excludes": [ + "el" + ], + "vscodeCustomCodeColor.highlightValue": [ + "v-loading", + "v-auth", + "v-copy", + "v-longpress", + "v-optimize", + "v-perms", + "v-ripple" + ], + "vscodeCustomCodeColor.highlightValueColor": "#b392f0", +} \ No newline at end of file diff --git a/sop-admin/sop-admin-frontend/.vscode/vue3.0.code-snippets b/sop-admin/sop-admin-frontend/.vscode/vue3.0.code-snippets new file mode 100755 index 00000000..bb43589a --- /dev/null +++ b/sop-admin/sop-admin-frontend/.vscode/vue3.0.code-snippets @@ -0,0 +1,22 @@ +{ + "Vue3.0快速生成模板": { + "scope": "vue", + "prefix": "Vue3.0", + "body": [ + "\n", + "\n", + "", + "$2" + ], + "description": "Vue3.0" + } +} diff --git a/sop-admin/sop-admin-frontend/.vscode/vue3.2.code-snippets b/sop-admin/sop-admin-frontend/.vscode/vue3.2.code-snippets new file mode 100755 index 00000000..2cebb463 --- /dev/null +++ b/sop-admin/sop-admin-frontend/.vscode/vue3.2.code-snippets @@ -0,0 +1,17 @@ +{ + "Vue3.2+快速生成模板": { + "scope": "vue", + "prefix": "Vue3.2+", + "body": [ + "\n", + "\n", + "", + "$2" + ], + "description": "Vue3.2+" + } +} diff --git a/sop-admin/sop-admin-frontend/.vscode/vue3.3.code-snippets b/sop-admin/sop-admin-frontend/.vscode/vue3.3.code-snippets new file mode 100755 index 00000000..dc7a1062 --- /dev/null +++ b/sop-admin/sop-admin-frontend/.vscode/vue3.3.code-snippets @@ -0,0 +1,20 @@ +{ + "Vue3.3+defineOptions快速生成模板": { + "scope": "vue", + "prefix": "Vue3.3+", + "body": [ + "\n", + "\n", + "", + "$2" + ], + "description": "Vue3.3+defineOptions快速生成模板" + } +} diff --git a/sop-admin/sop-admin-frontend/Dockerfile b/sop-admin/sop-admin-frontend/Dockerfile new file mode 100755 index 00000000..cd6d51a9 --- /dev/null +++ b/sop-admin/sop-admin-frontend/Dockerfile @@ -0,0 +1,20 @@ +FROM node:20-alpine as build-stage + +WORKDIR /app +RUN corepack enable +RUN corepack prepare pnpm@latest --activate + +RUN npm config set registry https://registry.npmmirror.com + +COPY .npmrc package.json pnpm-lock.yaml ./ +RUN pnpm install --frozen-lockfile + +COPY . . +RUN pnpm build + +FROM nginx:stable-alpine as production-stage + +COPY --from=build-stage /app/dist /usr/share/nginx/html +EXPOSE 80 + +CMD ["nginx", "-g", "daemon off;"] \ No newline at end of file diff --git a/sop-admin/sop-admin-frontend/LICENSE b/sop-admin/sop-admin-frontend/LICENSE new file mode 100755 index 00000000..6d4889d0 --- /dev/null +++ b/sop-admin/sop-admin-frontend/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020-present, pure-admin + +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/sop-admin/sop-admin-frontend/README.md b/sop-admin/sop-admin-frontend/README.md new file mode 100755 index 00000000..2096471a --- /dev/null +++ b/sop-admin/sop-admin-frontend/README.md @@ -0,0 +1,39 @@ +# SOP Admin 前端实现 + +> 采用 ECMAScript 模块(ESM)规范来编写和组织代码,使用了最新的 Vue3、Vite、Element-Plus、TypeScript、Pinia、Tailwindcss 等主流技术 + +## 参考文档 + +- 模板:https://pure-admin.github.io/pure-admin-doc/ +- 表单表格组件:https://plus-pro-components.com/ + +## 开发部署 + +安装nodejs + +> node版本 >= 18.18.0 + +- macOS用户在命令前面添加`sudo` + +```shell +# 安装pnpm +npm install -g pnpm + +# 安装依赖 +pnpm install + +# 启动 +pnpm dev +``` + +打包: + +`pnpm build` + +安装一个包: + +`pnpm add 包名` + +卸载一个包: + +`pnpm remove 包名` \ No newline at end of file diff --git a/sop-admin/sop-admin-frontend/build.sh b/sop-admin/sop-admin-frontend/build.sh new file mode 100755 index 00000000..2633fe08 --- /dev/null +++ b/sop-admin/sop-admin-frontend/build.sh @@ -0,0 +1,2 @@ +# 构建 +sudo pnpm build diff --git a/sop-admin/sop-admin-frontend/build/cdn.ts b/sop-admin/sop-admin-frontend/build/cdn.ts new file mode 100755 index 00000000..9e4bfe04 --- /dev/null +++ b/sop-admin/sop-admin-frontend/build/cdn.ts @@ -0,0 +1,60 @@ +import { Plugin as importToCDN } from "vite-plugin-cdn-import"; + +/** + * @description 打包时采用`cdn`模式,仅限外网使用(默认不采用,如果需要采用cdn模式,请在 .env.production 文件,将 VITE_CDN 设置成true) + * 平台采用国内cdn:https://www.bootcdn.cn,当然你也可以选择 https://unpkg.com 或者 https://www.jsdelivr.com + * 注意:上面提到的仅限外网使用也不是完全肯定的,如果你们公司内网部署的有相关js、css文件,也可以将下面配置对应改一下,整一套内网版cdn + */ +export const cdn = importToCDN({ + //(prodUrl解释: name: 对应下面modules的name,version: 自动读取本地package.json中dependencies依赖中对应包的版本号,path: 对应下面modules的path,当然也可写完整路径,会替换prodUrl) + prodUrl: "https://cdn.bootcdn.net/ajax/libs/{name}/{version}/{path}", + modules: [ + { + name: "vue", + var: "Vue", + path: "vue.global.prod.min.js" + }, + { + name: "vue-router", + var: "VueRouter", + path: "vue-router.global.min.js" + }, + { + name: "vue-i18n", + var: "VueI18n", + path: "vue-i18n.runtime.global.prod.min.js" + }, + // 项目中没有直接安装vue-demi,但是pinia用到了,所以需要在引入pinia前引入vue-demi(https://github.com/vuejs/pinia/blob/v2/packages/pinia/package.json#L77) + { + name: "vue-demi", + var: "VueDemi", + path: "index.iife.min.js" + }, + { + name: "pinia", + var: "Pinia", + path: "pinia.iife.min.js" + }, + { + name: "element-plus", + var: "ElementPlus", + path: "index.full.min.js", + css: "index.min.css" + }, + { + name: "axios", + var: "axios", + path: "axios.min.js" + }, + { + name: "dayjs", + var: "dayjs", + path: "dayjs.min.js" + }, + { + name: "echarts", + var: "echarts", + path: "echarts.min.js" + } + ] +}); diff --git a/sop-admin/sop-admin-frontend/build/compress.ts b/sop-admin/sop-admin-frontend/build/compress.ts new file mode 100755 index 00000000..6178986b --- /dev/null +++ b/sop-admin/sop-admin-frontend/build/compress.ts @@ -0,0 +1,63 @@ +import type { Plugin } from "vite"; +import { isArray } from "@pureadmin/utils"; +import compressPlugin from "vite-plugin-compression"; + +export const configCompressPlugin = ( + compress: ViteCompression +): Plugin | Plugin[] => { + if (compress === "none") return null; + + const gz = { + // 生成的压缩包后缀 + ext: ".gz", + // 体积大于threshold才会被压缩 + threshold: 0, + // 默认压缩.js|mjs|json|css|html后缀文件,设置成true,压缩全部文件 + filter: () => true, + // 压缩后是否删除原始文件 + deleteOriginFile: false + }; + const br = { + ext: ".br", + algorithm: "brotliCompress", + threshold: 0, + filter: () => true, + deleteOriginFile: false + }; + + const codeList = [ + { k: "gzip", v: gz }, + { k: "brotli", v: br }, + { k: "both", v: [gz, br] } + ]; + + const plugins: Plugin[] = []; + + codeList.forEach(item => { + if (compress.includes(item.k)) { + if (compress.includes("clear")) { + if (isArray(item.v)) { + item.v.forEach(vItem => { + plugins.push( + compressPlugin(Object.assign(vItem, { deleteOriginFile: true })) + ); + }); + } else { + plugins.push( + compressPlugin(Object.assign(item.v, { deleteOriginFile: true })) + ); + } + } else { + if (isArray(item.v)) { + item.v.forEach(vItem => { + plugins.push(compressPlugin(vItem)); + }); + } else { + plugins.push(compressPlugin(item.v)); + } + } + } + }); + + return plugins; +}; diff --git a/sop-admin/sop-admin-frontend/build/info.ts b/sop-admin/sop-admin-frontend/build/info.ts new file mode 100755 index 00000000..f1c0086e --- /dev/null +++ b/sop-admin/sop-admin-frontend/build/info.ts @@ -0,0 +1,55 @@ +import type { Plugin } from "vite"; +import { getPackageSize } from "./utils"; +import dayjs, { type Dayjs } from "dayjs"; +import duration from "dayjs/plugin/duration"; +import gradientString from "gradient-string"; +import boxen, { type Options as BoxenOptions } from "boxen"; +dayjs.extend(duration); + +const welcomeMessage = gradientString("cyan", "magenta").multiline(`SOP Admin`); + +const boxenOptions: BoxenOptions = { + padding: 0.5, + borderColor: "cyan", + borderStyle: "round" +}; + +export function viteBuildInfo(): Plugin { + let config: { command: string }; + let startTime: Dayjs; + let endTime: Dayjs; + let outDir: string; + return { + name: "vite:buildInfo", + configResolved(resolvedConfig) { + config = resolvedConfig; + outDir = resolvedConfig.build?.outDir ?? "dist"; + }, + buildStart() { + console.log(boxen(welcomeMessage, boxenOptions)); + if (config.command === "build") { + startTime = dayjs(new Date()); + } + }, + closeBundle() { + if (config.command === "build") { + endTime = dayjs(new Date()); + getPackageSize({ + folder: outDir, + callback: (size: string) => { + console.log( + boxen( + gradientString("cyan", "magenta").multiline( + `🎉 恭喜打包完成(总用时${dayjs + .duration(endTime.diff(startTime)) + .format("mm分ss秒")},打包后的大小为${size})` + ), + boxenOptions + ) + ); + } + }); + } + } + }; +} diff --git a/sop-admin/sop-admin-frontend/build/optimize.ts b/sop-admin/sop-admin-frontend/build/optimize.ts new file mode 100755 index 00000000..5f594911 --- /dev/null +++ b/sop-admin/sop-admin-frontend/build/optimize.ts @@ -0,0 +1,34 @@ +/** + * 此文件作用于 `vite.config.ts` 的 `optimizeDeps.include` 依赖预构建配置项 + * 依赖预构建,`vite` 启动时会将下面 include 里的模块,编译成 esm 格式并缓存到 node_modules/.vite 文件夹,页面加载到对应模块时如果浏览器有缓存就读取浏览器缓存,如果没有会读取本地缓存并按需加载 + * 尤其当您禁用浏览器缓存时(这种情况只应该发生在调试阶段)必须将对应模块加入到 include里,否则会遇到开发环境切换页面卡顿的问题(vite 会认为它是一个新的依赖包会重新加载并强制刷新页面),因为它既无法使用浏览器缓存,又没有在本地 node_modules/.vite 里缓存 + * 温馨提示:如果您使用的第三方库是全局引入,也就是引入到 src/main.ts 文件里,就不需要再添加到 include 里了,因为 vite 会自动将它们缓存到 node_modules/.vite + */ +const include = [ + "qs", + "mitt", + "dayjs", + "axios", + "pinia", + "vue-i18n", + "vue-types", + "js-cookie", + "vue-tippy", + "pinyin-pro", + "sortablejs", + "@vueuse/core", + "@pureadmin/utils", + "responsive-storage" +]; + +/** + * 在预构建中强制排除的依赖项 + * 温馨提示:所有以 `@iconify-icons/` 开头引入的的本地图标模块,都应该加入到下面的 `exclude` 里,因为平台推荐的使用方式是哪里需要哪里引入而且都是单个的引入,不需要预构建,直接让浏览器加载就好 + */ +const exclude = [ + "@iconify-icons/ep", + "@iconify-icons/ri", + "@pureadmin/theme/dist/browser-utils" +]; + +export { include, exclude }; diff --git a/sop-admin/sop-admin-frontend/build/plugins.ts b/sop-admin/sop-admin-frontend/build/plugins.ts new file mode 100755 index 00000000..37c8278a --- /dev/null +++ b/sop-admin/sop-admin-frontend/build/plugins.ts @@ -0,0 +1,76 @@ +import { cdn } from "./cdn"; +import vue from "@vitejs/plugin-vue"; +import { pathResolve } from "./utils"; +import { viteBuildInfo } from "./info"; +import svgLoader from "vite-svg-loader"; +import type { PluginOption } from "vite"; +import checker from "vite-plugin-checker"; +import vueJsx from "@vitejs/plugin-vue-jsx"; +import Inspector from "vite-plugin-vue-inspector"; +import { configCompressPlugin } from "./compress"; +import removeNoMatch from "vite-plugin-router-warn"; +import { visualizer } from "rollup-plugin-visualizer"; +import removeConsole from "vite-plugin-remove-console"; +import { themePreprocessorPlugin } from "@pureadmin/theme"; +import VueI18nPlugin from "@intlify/unplugin-vue-i18n/vite"; +import { genScssMultipleScopeVars } from "../src/layout/theme"; +import { vitePluginFakeServer } from "vite-plugin-fake-server"; + +export function getPluginsList( + VITE_CDN: boolean, + VITE_COMPRESSION: ViteCompression +): PluginOption[] { + const lifecycle = process.env.npm_lifecycle_event; + return [ + vue(), + // jsx、tsx语法支持 + vueJsx(), + VueI18nPlugin({ + jitCompilation: false, + include: [pathResolve("../locales/**")] + }), + checker({ + typescript: true, + vueTsc: true, + eslint: { + lintCommand: `eslint ${pathResolve("../{src,mock,build}/**/*.{vue,js,ts,tsx}")}`, + useFlatConfig: true + }, + terminal: false, + enableBuild: false + }), + // 按下Command(⌘)+Shift(⇧),然后点击页面元素会自动打开本地IDE并跳转到对应的代码位置 + Inspector(), + viteBuildInfo(), + /** + * 开发环境下移除非必要的vue-router动态路由警告No match found for location with path + * 非必要具体看 https://github.com/vuejs/router/issues/521 和 https://github.com/vuejs/router/issues/359 + * vite-plugin-router-warn只在开发环境下启用,只处理vue-router文件并且只在服务启动或重启时运行一次,性能消耗可忽略不计 + */ + removeNoMatch(), + // mock支持 + vitePluginFakeServer({ + logger: false, + include: "mock", + infixName: false, + enableProd: true + }), + // 自定义主题 + themePreprocessorPlugin({ + scss: { + multipleScopeVars: genScssMultipleScopeVars(), + extract: true + } + }), + // svg组件化支持 + svgLoader(), + VITE_CDN ? cdn : null, + configCompressPlugin(VITE_COMPRESSION), + // 线上环境删除console + removeConsole({ external: ["src/assets/iconfont/iconfont.js"] }), + // 打包分析 + lifecycle === "report" + ? visualizer({ open: true, brotliSize: true, filename: "report.html" }) + : (null as any) + ]; +} diff --git a/sop-admin/sop-admin-frontend/build/utils.ts b/sop-admin/sop-admin-frontend/build/utils.ts new file mode 100755 index 00000000..3d778fe6 --- /dev/null +++ b/sop-admin/sop-admin-frontend/build/utils.ts @@ -0,0 +1,110 @@ +import dayjs from "dayjs"; +import { readdir, stat } from "node:fs"; +import { fileURLToPath } from "node:url"; +import { dirname, resolve } from "node:path"; +import { sum, formatBytes } from "@pureadmin/utils"; +import { + name, + version, + engines, + dependencies, + devDependencies +} from "../package.json"; + +/** 启动`node`进程时所在工作目录的绝对路径 */ +const root: string = process.cwd(); + +/** + * @description 根据可选的路径片段生成一个新的绝对路径 + * @param dir 路径片段,默认`build` + * @param metaUrl 模块的完整`url`,如果在`build`目录外调用必传`import.meta.url` + */ +const pathResolve = (dir = ".", metaUrl = import.meta.url) => { + // 当前文件目录的绝对路径 + const currentFileDir = dirname(fileURLToPath(metaUrl)); + // build 目录的绝对路径 + const buildDir = resolve(currentFileDir, "build"); + // 解析的绝对路径 + const resolvedPath = resolve(currentFileDir, dir); + // 检查解析的绝对路径是否在 build 目录内 + if (resolvedPath.startsWith(buildDir)) { + // 在 build 目录内,返回当前文件路径 + return fileURLToPath(metaUrl); + } + // 不在 build 目录内,返回解析后的绝对路径 + return resolvedPath; +}; + +/** 设置别名 */ +const alias: Record = { + "@": pathResolve("../src"), + "@build": pathResolve() +}; + +/** 平台的名称、版本、运行所需的`node`和`pnpm`版本、依赖、最后构建时间的类型提示 */ +const __APP_INFO__ = { + pkg: { name, version, engines, dependencies, devDependencies }, + lastBuildTime: dayjs(new Date()).format("YYYY-MM-DD HH:mm:ss") +}; + +/** 处理环境变量 */ +const wrapperEnv = (envConf: Recordable): ViteEnv => { + // 默认值 + const ret: ViteEnv = { + VITE_PORT: 8848, + VITE_PUBLIC_PATH: "", + VITE_ROUTER_HISTORY: "", + VITE_CDN: false, + VITE_HIDE_HOME: "false", + VITE_COMPRESSION: "none" + }; + + for (const envName of Object.keys(envConf)) { + let realName = envConf[envName].replace(/\\n/g, "\n"); + realName = + realName === "true" ? true : realName === "false" ? false : realName; + + if (envName === "VITE_PORT") { + realName = Number(realName); + } + ret[envName] = realName; + if (typeof realName === "string") { + process.env[envName] = realName; + } else if (typeof realName === "object") { + process.env[envName] = JSON.stringify(realName); + } + } + return ret; +}; + +const fileListTotal: number[] = []; + +/** 获取指定文件夹中所有文件的总大小 */ +const getPackageSize = options => { + const { folder = "dist", callback, format = true } = options; + readdir(folder, (err, files: string[]) => { + if (err) throw err; + let count = 0; + const checkEnd = () => { + ++count == files.length && + callback(format ? formatBytes(sum(fileListTotal)) : sum(fileListTotal)); + }; + files.forEach((item: string) => { + stat(`${folder}/${item}`, async (err, stats) => { + if (err) throw err; + if (stats.isFile()) { + fileListTotal.push(stats.size); + checkEnd(); + } else if (stats.isDirectory()) { + getPackageSize({ + folder: `${folder}/${item}/`, + callback: checkEnd + }); + } + }); + }); + files.length === 0 && callback(0); + }); +}; + +export { root, pathResolve, alias, __APP_INFO__, wrapperEnv, getPackageSize }; diff --git a/sop-admin/sop-admin-frontend/commitlint.config.js b/sop-admin/sop-admin-frontend/commitlint.config.js new file mode 100755 index 00000000..eea755d0 --- /dev/null +++ b/sop-admin/sop-admin-frontend/commitlint.config.js @@ -0,0 +1,35 @@ +// @ts-check + +/** @type {import("@commitlint/types").UserConfig} */ +export default { + ignores: [commit => commit.includes("init")], + extends: ["@commitlint/config-conventional"], + rules: { + "body-leading-blank": [2, "always"], + "footer-leading-blank": [1, "always"], + "header-max-length": [2, "always", 108], + "subject-empty": [2, "never"], + "type-empty": [2, "never"], + "type-enum": [ + 2, + "always", + [ + "feat", + "fix", + "perf", + "style", + "docs", + "test", + "refactor", + "build", + "ci", + "chore", + "revert", + "wip", + "workflow", + "types", + "release" + ] + ] + } +}; diff --git a/sop-admin/sop-admin-frontend/eslint.config.js b/sop-admin/sop-admin-frontend/eslint.config.js new file mode 100755 index 00000000..6884f077 --- /dev/null +++ b/sop-admin/sop-admin-frontend/eslint.config.js @@ -0,0 +1,181 @@ +import js from "@eslint/js"; +import pluginVue from "eslint-plugin-vue"; +import * as parserVue from "vue-eslint-parser"; +import configPrettier from "eslint-config-prettier"; +import pluginPrettier from "eslint-plugin-prettier"; +import { defineFlatConfig } from "eslint-define-config"; +import * as parserTypeScript from "@typescript-eslint/parser"; +import pluginTypeScript from "@typescript-eslint/eslint-plugin"; + +export default defineFlatConfig([ + { + ...js.configs.recommended, + ignores: [ + "**/.*", + "dist/*", + "*.d.ts", + "public/*", + "src/assets/**", + "src/**/iconfont/**" + ], + languageOptions: { + globals: { + // index.d.ts + RefType: "readonly", + EmitType: "readonly", + TargetContext: "readonly", + ComponentRef: "readonly", + ElRef: "readonly", + ForDataType: "readonly", + AnyFunction: "readonly", + PropType: "readonly", + Writable: "readonly", + Nullable: "readonly", + NonNullable: "readonly", + Recordable: "readonly", + ReadonlyRecordable: "readonly", + Indexable: "readonly", + DeepPartial: "readonly", + Without: "readonly", + Exclusive: "readonly", + TimeoutHandle: "readonly", + IntervalHandle: "readonly", + Effect: "readonly", + ChangeEvent: "readonly", + WheelEvent: "readonly", + ImportMetaEnv: "readonly", + Fn: "readonly", + PromiseFn: "readonly", + ComponentElRef: "readonly", + parseInt: "readonly", + parseFloat: "readonly" + } + }, + plugins: { + prettier: pluginPrettier + }, + rules: { + ...configPrettier.rules, + ...pluginPrettier.configs.recommended.rules, + "no-debugger": "off", + "no-unused-vars": [ + "error", + { + argsIgnorePattern: "^_", + varsIgnorePattern: "^_" + } + ], + "prettier/prettier": [ + "error", + { + endOfLine: "auto" + } + ] + } + }, + { + files: ["**/*.?([cm])ts", "**/*.?([cm])tsx"], + languageOptions: { + parser: parserTypeScript, + parserOptions: { + sourceType: "module" + } + }, + plugins: { + "@typescript-eslint": pluginTypeScript + }, + rules: { + ...pluginTypeScript.configs.strict.rules, + "@typescript-eslint/ban-types": "off", + "@typescript-eslint/no-redeclare": "error", + "@typescript-eslint/ban-ts-comment": "off", + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/prefer-as-const": "warn", + "@typescript-eslint/no-empty-function": "off", + "@typescript-eslint/no-non-null-assertion": "off", + "@typescript-eslint/no-import-type-side-effects": "off", + "@typescript-eslint/explicit-module-boundary-types": "off", + "@typescript-eslint/consistent-type-imports": [ + "error", + { disallowTypeAnnotations: false, fixStyle: "inline-type-imports" } + ], + "@typescript-eslint/prefer-literal-enum-member": [ + "error", + { allowBitwiseExpressions: true } + ], + "@typescript-eslint/no-unused-vars": [ + "error", + { + argsIgnorePattern: "^_", + varsIgnorePattern: "^_" + } + ] + } + }, + { + files: ["**/*.d.ts"], + rules: { + "eslint-comments/no-unlimited-disable": "off", + "import/no-duplicates": "off", + "unused-imports/no-unused-vars": "off" + } + }, + { + files: ["**/*.?([cm])js"], + rules: { + "@typescript-eslint/no-require-imports": "off", + "@typescript-eslint/no-var-requires": "off" + } + }, + { + files: ["**/*.vue"], + languageOptions: { + globals: { + $: "readonly", + $$: "readonly", + $computed: "readonly", + $customRef: "readonly", + $ref: "readonly", + $shallowRef: "readonly", + $toRef: "readonly" + }, + parser: parserVue, + parserOptions: { + ecmaFeatures: { + jsx: true + }, + extraFileExtensions: [".vue"], + parser: "@typescript-eslint/parser", + sourceType: "module" + } + }, + plugins: { + vue: pluginVue + }, + processor: pluginVue.processors[".vue"], + rules: { + ...pluginVue.configs.base.rules, + ...pluginVue.configs["vue3-essential"].rules, + ...pluginVue.configs["vue3-recommended"].rules, + "no-undef": "off", + "no-unused-vars": "off", + "vue/no-v-html": "off", + "vue/require-default-prop": "off", + "vue/require-explicit-emits": "off", + "vue/multi-word-component-names": "off", + "vue/no-setup-props-reactivity-loss": "off", + "vue/html-self-closing": [ + "error", + { + html: { + void: "always", + normal: "always", + component: "always" + }, + svg: "always", + math: "always" + } + ] + } + } +]); diff --git a/sop-admin/sop-admin-frontend/index.html b/sop-admin/sop-admin-frontend/index.html new file mode 100755 index 00000000..4cd6bc4b --- /dev/null +++ b/sop-admin/sop-admin-frontend/index.html @@ -0,0 +1,87 @@ + + + + + + + + SOP Admin + + + + + +
+ +
+
+ + + diff --git a/sop-admin/sop-admin-frontend/locales/en.yaml b/sop-admin/sop-admin-frontend/locales/en.yaml new file mode 100755 index 00000000..ed471887 --- /dev/null +++ b/sop-admin/sop-admin-frontend/locales/en.yaml @@ -0,0 +1,96 @@ +buttons: + pureLoginOut: LoginOut + pureLogin: Login + pureOpenSystemSet: Open System Configs + pureReload: Reload + pureCloseCurrentTab: Close CurrentTab + pureCloseLeftTabs: Close LeftTabs + pureCloseRightTabs: Close RightTabs + pureCloseOtherTabs: Close OtherTabs + pureCloseAllTabs: Close AllTabs + pureContentFullScreen: Content FullScreen + pureContentExitFullScreen: Content ExitFullScreen + pureClickCollapse: Collapse + pureClickExpand: Expand + pureConfirm: Confirm + pureSwitch: Switch + pureClose: Close + pureBackTop: BackTop + pureOpenText: Open + pureCloseText: Close +search: + pureTotal: Total + pureHistory: History + pureCollect: Collect + pureDragSort: (Drag Sort) + pureEmpty: Empty + purePlaceholder: Search Menu +panel: + pureSystemSet: System Configs + pureCloseSystemSet: Close System Configs + pureClearCacheAndToLogin: Clear cache and return to login page + pureClearCache: Clear Cache + pureOverallStyle: Overall Style + pureOverallStyleLight: Light + pureOverallStyleLightTip: Set sail freshly and light up the comfortable work interface + pureOverallStyleDark: Dark + pureOverallStyleDarkTip: Moonlight Overture, indulge in the tranquility and elegance of the night + pureOverallStyleSystem: Auto + pureOverallStyleSystemTip: Synchronize time, the interface naturally responds to morning and dusk + pureThemeColor: Theme Color + pureLayoutModel: Layout Model + pureVerticalTip: The menu on the left is familiar and friendly + pureHorizontalTip: Top menu, concise overview + pureMixTip: Mixed menu, flexible + pureStretch: Stretch Page + pureStretchFixed: Fixed + pureStretchFixedTip: Compact pages make it easy to find the information you need + pureStretchCustom: Custom + pureStretchCustomTip: Minimum 1280, maximum 1600 + pureTagsStyle: Tags Style + pureTagsStyleSmart: Smart + pureTagsStyleSmartTip: Smart tags add fun and brilliance + pureTagsStyleCard: Card + pureTagsStyleCardTip: Card tags for efficient browsing + pureTagsStyleChrome: Chrome + pureTagsStyleChromeTip: Chrome style is classic and elegant + pureInterfaceDisplay: Interface Display + pureGreyModel: Grey Model + pureWeakModel: Weak Model + pureHiddenTags: Hidden Tags + pureHiddenFooter: Hidden Footer + pureMultiTagsCache: MultiTags Cache +menus: + pureHome: Home + pureLogin: Login + pureAbnormal: Abnormal Page + pureFourZeroFour: "404" + pureFourZeroOne: "403" + pureFive: "500" + purePermission: Permission Manage + purePermissionPage: Page Permission + purePermissionButton: Button Permission + purePermissionButtonRouter: Route return button permission + purePermissionButtonLogin: Login interface return button permission + pureSysManagement: System Manage + pureUser: User Manage + pureRole: Role Manage + pureSystemMenu: Menu Manage + pureDept: Dept Manage +status: + pureLoad: Loading... + pureMessage: Message + pureNotify: Notify + pureTodo: Todo + pureNoMessage: No Message + pureNoNotify: No Notify + pureNoTodo: No Todo +login: + pureUsername: Username + purePassword: Password + pureLogin: Login + pureLoginSuccess: Login Success + pureLoginFail: Login Fail + pureUsernameReg: Please enter username + purePassWordReg: Please enter password + purePassWordRuleReg: The password format should be any combination of 8-18 digits diff --git a/sop-admin/sop-admin-frontend/locales/zh-CN.yaml b/sop-admin/sop-admin-frontend/locales/zh-CN.yaml new file mode 100755 index 00000000..8f3f31be --- /dev/null +++ b/sop-admin/sop-admin-frontend/locales/zh-CN.yaml @@ -0,0 +1,96 @@ +buttons: + pureLoginOut: 退出系统 + pureLogin: 登录 + pureOpenSystemSet: 打开系统配置 + pureReload: 重新加载 + pureCloseCurrentTab: 关闭当前标签页 + pureCloseLeftTabs: 关闭左侧标签页 + pureCloseRightTabs: 关闭右侧标签页 + pureCloseOtherTabs: 关闭其他标签页 + pureCloseAllTabs: 关闭全部标签页 + pureContentFullScreen: 内容区全屏 + pureContentExitFullScreen: 内容区退出全屏 + pureClickCollapse: 点击折叠 + pureClickExpand: 点击展开 + pureConfirm: 确认 + pureSwitch: 切换 + pureClose: 关闭 + pureBackTop: 回到顶部 + pureOpenText: 开 + pureCloseText: 关 +search: + pureTotal: 共 + pureHistory: 搜索历史 + pureCollect: 收藏 + pureDragSort: (可拖拽排序) + pureEmpty: 暂无搜索结果 + purePlaceholder: 搜索菜单(支持拼音搜索) +panel: + pureSystemSet: 系统配置 + pureCloseSystemSet: 关闭配置 + pureClearCacheAndToLogin: 清空缓存并返回登录页 + pureClearCache: 清空缓存 + pureOverallStyle: 整体风格 + pureOverallStyleLight: 浅色 + pureOverallStyleLightTip: 清新启航,点亮舒适的工作界面 + pureOverallStyleDark: 深色 + pureOverallStyleDarkTip: 月光序曲,沉醉于夜的静谧雅致 + pureOverallStyleSystem: 自动 + pureOverallStyleSystemTip: 同步时光,界面随晨昏自然呼应 + pureThemeColor: 主题色 + pureLayoutModel: 导航模式 + pureVerticalTip: 左侧菜单,亲切熟悉 + pureHorizontalTip: 顶部菜单,简洁概览 + pureMixTip: 混合菜单,灵活多变 + pureStretch: 页宽 + pureStretchFixed: 固定 + pureStretchFixedTip: 紧凑页面,轻松找到所需信息 + pureStretchCustom: 自定义 + pureStretchCustomTip: 最小1280、最大1600 + pureTagsStyle: 页签风格 + pureTagsStyleSmart: 灵动 + pureTagsStyleSmartTip: 灵动标签,添趣生辉 + pureTagsStyleCard: 卡片 + pureTagsStyleCardTip: 卡片标签,高效浏览 + pureTagsStyleChrome: 谷歌 + pureTagsStyleChromeTip: 谷歌风格,经典美观 + pureInterfaceDisplay: 界面显示 + pureGreyModel: 灰色模式 + pureWeakModel: 色弱模式 + pureHiddenTags: 隐藏标签页 + pureHiddenFooter: 隐藏页脚 + pureMultiTagsCache: 页签持久化 +menus: + pureHome: 首页 + pureLogin: 登录 + pureAbnormal: 异常页面 + pureFourZeroFour: "404" + pureFourZeroOne: "403" + pureFive: "500" + purePermission: 权限管理 + purePermissionPage: 页面权限 + purePermissionButton: 按钮权限 + purePermissionButtonRouter: 路由返回按钮权限 + purePermissionButtonLogin: 登录接口返回按钮权限 + pureSysManagement: 系统管理 + pureUser: 用户管理 + pureRole: 角色管理 + pureSystemMenu: 菜单管理 + pureDept: 部门管理 +status: + pureLoad: 加载中... + pureMessage: 消息 + pureNotify: 通知 + pureTodo: 待办 + pureNoMessage: 暂无消息 + pureNoNotify: 暂无通知 + pureNoTodo: 暂无待办 +login: + pureUsername: 账号 + purePassword: 密码 + pureLogin: 登录 + pureLoginSuccess: 登录成功 + pureLoginFail: 登录失败 + pureUsernameReg: 请输入账号 + purePassWordReg: 请输入密码 + purePassWordRuleReg: 密码格式应为8-18位数字、字母、符号的任意两种组合 diff --git a/sop-admin/sop-admin-frontend/mock/asyncRoutes.ts b/sop-admin/sop-admin-frontend/mock/asyncRoutes.ts new file mode 100755 index 00000000..fad699dd --- /dev/null +++ b/sop-admin/sop-admin-frontend/mock/asyncRoutes.ts @@ -0,0 +1,12 @@ +// 模拟后端动态生成路由 +import { defineFakeRoute } from "vite-plugin-fake-server/client"; + +export default defineFakeRoute([ + { + url: "/get-async-routes", + method: "get", + response: () => { + return []; + } + } +]); diff --git a/sop-admin/sop-admin-frontend/mock/login.ts b/sop-admin/sop-admin-frontend/mock/login.ts new file mode 100755 index 00000000..55897d8f --- /dev/null +++ b/sop-admin/sop-admin-frontend/mock/login.ts @@ -0,0 +1,42 @@ +// 根据角色动态生成路由 +import { defineFakeRoute } from "vite-plugin-fake-server/client"; + +export default defineFakeRoute([ + { + url: "/login", + method: "post", + response: ({ body }) => { + if (body.username === "admin") { + return { + success: true, + data: { + avatar: "https://avatars.githubusercontent.com/u/44761321", + username: "admin", + nickname: "小铭", + // 一个用户可能有多个角色 + roles: ["admin"], + // 按钮级别权限 + permissions: ["*:*:*"], + accessToken: "eyJhbGciOiJIUzUxMiJ9.admin", + refreshToken: "eyJhbGciOiJIUzUxMiJ9.adminRefresh", + expires: "2030/10/30 00:00:00" + } + }; + } else { + return { + success: true, + data: { + avatar: "https://avatars.githubusercontent.com/u/52823142", + username: "common", + nickname: "小林", + roles: ["common"], + permissions: ["permission:btn:add", "permission:btn:edit"], + accessToken: "eyJhbGciOiJIUzUxMiJ9.common", + refreshToken: "eyJhbGciOiJIUzUxMiJ9.commonRefresh", + expires: "2030/10/30 00:00:00" + } + }; + } + } + } +]); diff --git a/sop-admin/sop-admin-frontend/mock/refreshToken.ts b/sop-admin/sop-admin-frontend/mock/refreshToken.ts new file mode 100755 index 00000000..34d0e876 --- /dev/null +++ b/sop-admin/sop-admin-frontend/mock/refreshToken.ts @@ -0,0 +1,27 @@ +import { defineFakeRoute } from "vite-plugin-fake-server/client"; + +// 模拟刷新token接口 +export default defineFakeRoute([ + { + url: "/refresh-token", + method: "post", + response: ({ body }) => { + if (body.refreshToken) { + return { + success: true, + data: { + accessToken: "eyJhbGciOiJIUzUxMiJ9.newAdmin", + refreshToken: "eyJhbGciOiJIUzUxMiJ9.newAdminRefresh", + // `expires`选择这种日期格式是为了方便调试,后端直接设置时间戳或许更方便(每次都应该递增)。如果后端返回的是时间戳格式,前端开发请来到这个目录`src/utils/auth.ts`,把第`38`行的代码换成expires = data.expires即可。 + expires: "2030/10/30 23:59:59" + } + }; + } else { + return { + success: false, + data: {} + }; + } + } + } +]); diff --git a/sop-admin/sop-admin-frontend/package.json b/sop-admin/sop-admin-frontend/package.json new file mode 100755 index 00000000..e844482a --- /dev/null +++ b/sop-admin/sop-admin-frontend/package.json @@ -0,0 +1,166 @@ +{ + "name": "SOP Admin", + "version": "5.8.0", + "private": true, + "type": "module", + "scripts": { + "dev": "NODE_OPTIONS=--max-old-space-size=4096 vite", + "serve": "pnpm dev", + "build": "rimraf dist && NODE_OPTIONS=--max-old-space-size=8192 vite build", + "build:staging": "rimraf dist && vite build --mode staging", + "report": "rimraf dist && vite build", + "preview": "vite preview", + "preview:build": "pnpm build && vite preview", + "typecheck": "tsc --noEmit && vue-tsc --noEmit --skipLibCheck", + "svgo": "svgo -f . -r", + "clean:cache": "rimraf .eslintcache && rimraf pnpm-lock.yaml && rimraf node_modules && pnpm store prune && pnpm install", + "lint:eslint": "eslint --cache --max-warnings 0 \"{src,mock,build}/**/*.{vue,js,ts,tsx}\" --fix", + "lint:prettier": "prettier --write \"src/**/*.{js,ts,json,tsx,css,scss,vue,html,md}\"", + "lint:stylelint": "stylelint --cache --fix \"**/*.{html,vue,css,scss}\" --cache-location node_modules/.cache/stylelint/", + "lint": "pnpm lint:eslint && pnpm lint:prettier && pnpm lint:stylelint", + "prepare": "husky", + "preinstall": "npx only-allow pnpm" + }, + "keywords": [ + "pure-admin-thin", + "vue-pure-admin", + "element-plus", + "tailwindcss", + "pure-admin", + "typescript", + "pinia", + "vue3", + "vite", + "esm" + ], + "homepage": "https://github.com/pure-admin/pure-admin-thin/tree/i18n", + "repository": { + "type": "git", + "url": "git+https://github.com/pure-admin/pure-admin-thin.git" + }, + "bugs": { + "url": "https://github.com/pure-admin/vue-pure-admin/issues" + }, + "license": "MIT", + "author": { + "name": "xiaoxian521", + "email": "pureadmin@163.com", + "url": "https://github.com/xiaoxian521" + }, + "dependencies": { + "@element-plus/icons-vue": "^2.3.1", + "@pureadmin/descriptions": "1.2.1", + "@pureadmin/table": "3.2.0", + "@pureadmin/utils": "2.4.8", + "@vueuse/core": "10.11.1", + "@vueuse/motion": "2.2.3", + "animate.css": "4.1.1", + "axios": "1.7.4", + "crypto-js": "4.2.0", + "dayjs": "1.11.12", + "echarts": "5.5.1", + "element-plus": "2.8.0", + "js-cookie": "3.0.5", + "localforage": "1.10.0", + "mitt": "3.0.1", + "nprogress": "0.2.0", + "path": "0.12.7", + "pinia": "2.2.2", + "pinyin-pro": "3.24.2", + "plus-pro-components": "0.1.17", + "qs": "6.13.0", + "responsive-storage": "2.2.0", + "sortablejs": "1.15.2", + "vue": "3.4.38", + "vue-i18n": "9.14.0", + "vue-router": "4.4.3", + "vue-tippy": "6.4.4", + "vue-types": "5.1.3", + "@zxcvbn-ts/core": "3.0.4", + "cropperjs": "1.6.2" + }, + "devDependencies": { + "@commitlint/cli": "19.4.0", + "@commitlint/config-conventional": "19.2.2", + "@commitlint/types": "19.0.3", + "@eslint/js": "9.9.0", + "@faker-js/faker": "8.4.1", + "@iconify-icons/ep": "1.2.12", + "@iconify-icons/ri": "1.2.10", + "@iconify/vue": "4.1.2", + "@intlify/unplugin-vue-i18n": "4.0.0", + "@pureadmin/theme": "3.2.0", + "@types/gradient-string": "1.1.6", + "@types/js-cookie": "3.0.6", + "@types/node": "20.16.1", + "@types/nprogress": "0.2.3", + "@types/qs": "6.9.15", + "@types/sortablejs": "1.15.8", + "@typescript-eslint/eslint-plugin": "7.18.0", + "@typescript-eslint/parser": "7.18.0", + "@vitejs/plugin-vue": "5.1.2", + "@vitejs/plugin-vue-jsx": "4.0.1", + "autoprefixer": "10.4.20", + "boxen": "7.1.1", + "cssnano": "7.0.5", + "eslint": "9.9.0", + "eslint-config-prettier": "9.1.0", + "eslint-define-config": "2.1.0", + "eslint-plugin-prettier": "5.2.1", + "eslint-plugin-vue": "9.27.0", + "gradient-string": "2.0.2", + "husky": "9.1.4", + "lint-staged": "15.2.9", + "postcss": "8.4.41", + "postcss-html": "1.7.0", + "postcss-import": "16.1.0", + "postcss-scss": "4.0.9", + "prettier": "3.3.3", + "rimraf": "5.0.10", + "rollup-plugin-visualizer": "5.12.0", + "sass": "1.77.8", + "stylelint": "16.8.2", + "stylelint-config-recess-order": "5.0.1", + "stylelint-config-recommended-vue": "1.5.0", + "stylelint-config-standard-scss": "13.1.0", + "stylelint-prettier": "5.0.2", + "svgo": "3.3.2", + "tailwindcss": "3.4.10", + "typescript": "5.5.4", + "vite": "5.4.1", + "vite-plugin-cdn-import": "1.0.1", + "vite-plugin-checker": "0.7.2", + "vite-plugin-compression": "0.5.1", + "vite-plugin-fake-server": "2.1.1", + "vite-plugin-remove-console": "2.2.0", + "vite-plugin-router-warn": "1.0.0", + "vite-plugin-vue-inspector": "5.1.3", + "vite-svg-loader": "5.1.0", + "vue-eslint-parser": "9.4.3", + "vue-tsc": "2.0.29" + }, + "engines": { + "node": "18.18.0 || ^20.9.0 || >=21.1.0", + "pnpm": ">=9" + }, + "pnpm": { + "allowedDeprecatedVersions": { + "are-we-there-yet": "*", + "sourcemap-codec": "*", + "domexception": "*", + "w3c-hr-time": "*", + "inflight": "*", + "npmlog": "*", + "rimraf": "*", + "stable": "*", + "gauge": "*", + "abab": "*", + "glob": "*" + }, + "peerDependencyRules": { + "allowedVersions": { + "eslint": "9" + } + } + } +} diff --git a/sop-admin/sop-admin-frontend/pnpm-lock.yaml b/sop-admin/sop-admin-frontend/pnpm-lock.yaml new file mode 100755 index 00000000..e37da681 --- /dev/null +++ b/sop-admin/sop-admin-frontend/pnpm-lock.yaml @@ -0,0 +1,7876 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@element-plus/icons-vue': + specifier: ^2.3.1 + version: 2.3.1(vue@3.4.38(typescript@5.5.4)) + '@pureadmin/descriptions': + specifier: 1.2.1 + version: 1.2.1(echarts@5.5.1)(element-plus@2.8.0(vue@3.4.38(typescript@5.5.4)))(typescript@5.5.4) + '@pureadmin/table': + specifier: 3.2.0 + version: 3.2.0(element-plus@2.8.0(vue@3.4.38(typescript@5.5.4)))(typescript@5.5.4) + '@pureadmin/utils': + specifier: 2.4.8 + version: 2.4.8(echarts@5.5.1)(vue@3.4.38(typescript@5.5.4)) + '@vueuse/core': + specifier: 10.11.1 + version: 10.11.1(vue@3.4.38(typescript@5.5.4)) + '@vueuse/motion': + specifier: 2.2.3 + version: 2.2.3(rollup@4.21.0)(vue@3.4.38(typescript@5.5.4)) + '@zxcvbn-ts/core': + specifier: 3.0.4 + version: 3.0.4 + animate.css: + specifier: 4.1.1 + version: 4.1.1 + axios: + specifier: 1.7.4 + version: 1.7.4 + cropperjs: + specifier: 1.6.2 + version: 1.6.2 + crypto-js: + specifier: 4.2.0 + version: 4.2.0 + dayjs: + specifier: 1.11.12 + version: 1.11.12 + echarts: + specifier: 5.5.1 + version: 5.5.1 + element-plus: + specifier: 2.8.0 + version: 2.8.0(vue@3.4.38(typescript@5.5.4)) + js-cookie: + specifier: 3.0.5 + version: 3.0.5 + localforage: + specifier: 1.10.0 + version: 1.10.0 + mitt: + specifier: 3.0.1 + version: 3.0.1 + nprogress: + specifier: 0.2.0 + version: 0.2.0 + path: + specifier: 0.12.7 + version: 0.12.7 + pinia: + specifier: 2.2.2 + version: 2.2.2(typescript@5.5.4)(vue@3.4.38(typescript@5.5.4)) + pinyin-pro: + specifier: 3.24.2 + version: 3.24.2 + plus-pro-components: + specifier: 0.1.17 + version: 0.1.17(element-plus@2.8.0(vue@3.4.38(typescript@5.5.4)))(vue@3.4.38(typescript@5.5.4)) + qs: + specifier: 6.13.0 + version: 6.13.0 + responsive-storage: + specifier: 2.2.0 + version: 2.2.0 + sortablejs: + specifier: 1.15.2 + version: 1.15.2 + vue: + specifier: 3.4.38 + version: 3.4.38(typescript@5.5.4) + vue-i18n: + specifier: 9.14.0 + version: 9.14.0(vue@3.4.38(typescript@5.5.4)) + vue-router: + specifier: 4.4.3 + version: 4.4.3(vue@3.4.38(typescript@5.5.4)) + vue-tippy: + specifier: 6.4.4 + version: 6.4.4(vue@3.4.38(typescript@5.5.4)) + vue-types: + specifier: 5.1.3 + version: 5.1.3(vue@3.4.38(typescript@5.5.4)) + devDependencies: + '@commitlint/cli': + specifier: 19.4.0 + version: 19.4.0(@types/node@20.16.1)(typescript@5.5.4) + '@commitlint/config-conventional': + specifier: 19.2.2 + version: 19.2.2 + '@commitlint/types': + specifier: 19.0.3 + version: 19.0.3 + '@eslint/js': + specifier: 9.9.0 + version: 9.9.0 + '@faker-js/faker': + specifier: 8.4.1 + version: 8.4.1 + '@iconify-icons/ep': + specifier: 1.2.12 + version: 1.2.12 + '@iconify-icons/ri': + specifier: 1.2.10 + version: 1.2.10 + '@iconify/vue': + specifier: 4.1.2 + version: 4.1.2(vue@3.4.38(typescript@5.5.4)) + '@intlify/unplugin-vue-i18n': + specifier: 4.0.0 + version: 4.0.0(rollup@4.21.0)(vue-i18n@9.14.0(vue@3.4.38(typescript@5.5.4))) + '@pureadmin/theme': + specifier: 3.2.0 + version: 3.2.0 + '@types/gradient-string': + specifier: 1.1.6 + version: 1.1.6 + '@types/js-cookie': + specifier: 3.0.6 + version: 3.0.6 + '@types/node': + specifier: 20.16.1 + version: 20.16.1 + '@types/nprogress': + specifier: 0.2.3 + version: 0.2.3 + '@types/qs': + specifier: 6.9.15 + version: 6.9.15 + '@types/sortablejs': + specifier: 1.15.8 + version: 1.15.8 + '@typescript-eslint/eslint-plugin': + specifier: 7.18.0 + version: 7.18.0(@typescript-eslint/parser@7.18.0(eslint@9.9.0(jiti@1.21.6))(typescript@5.5.4))(eslint@9.9.0(jiti@1.21.6))(typescript@5.5.4) + '@typescript-eslint/parser': + specifier: 7.18.0 + version: 7.18.0(eslint@9.9.0(jiti@1.21.6))(typescript@5.5.4) + '@vitejs/plugin-vue': + specifier: 5.1.2 + version: 5.1.2(vite@5.4.1(@types/node@20.16.1)(sass@1.77.8))(vue@3.4.38(typescript@5.5.4)) + '@vitejs/plugin-vue-jsx': + specifier: 4.0.1 + version: 4.0.1(vite@5.4.1(@types/node@20.16.1)(sass@1.77.8))(vue@3.4.38(typescript@5.5.4)) + autoprefixer: + specifier: 10.4.20 + version: 10.4.20(postcss@8.4.41) + boxen: + specifier: 7.1.1 + version: 7.1.1 + cssnano: + specifier: 7.0.5 + version: 7.0.5(postcss@8.4.41) + eslint: + specifier: 9.9.0 + version: 9.9.0(jiti@1.21.6) + eslint-config-prettier: + specifier: 9.1.0 + version: 9.1.0(eslint@9.9.0(jiti@1.21.6)) + eslint-define-config: + specifier: 2.1.0 + version: 2.1.0 + eslint-plugin-prettier: + specifier: 5.2.1 + version: 5.2.1(eslint-config-prettier@9.1.0(eslint@9.9.0(jiti@1.21.6)))(eslint@9.9.0(jiti@1.21.6))(prettier@3.3.3) + eslint-plugin-vue: + specifier: 9.27.0 + version: 9.27.0(eslint@9.9.0(jiti@1.21.6)) + gradient-string: + specifier: 2.0.2 + version: 2.0.2 + husky: + specifier: 9.1.4 + version: 9.1.4 + lint-staged: + specifier: 15.2.9 + version: 15.2.9 + postcss: + specifier: 8.4.41 + version: 8.4.41 + postcss-html: + specifier: 1.7.0 + version: 1.7.0 + postcss-import: + specifier: 16.1.0 + version: 16.1.0(postcss@8.4.41) + postcss-scss: + specifier: 4.0.9 + version: 4.0.9(postcss@8.4.41) + prettier: + specifier: 3.3.3 + version: 3.3.3 + rimraf: + specifier: 5.0.10 + version: 5.0.10 + rollup-plugin-visualizer: + specifier: 5.12.0 + version: 5.12.0(rollup@4.21.0) + sass: + specifier: 1.77.8 + version: 1.77.8 + stylelint: + specifier: 16.8.2 + version: 16.8.2(typescript@5.5.4) + stylelint-config-recess-order: + specifier: 5.0.1 + version: 5.0.1(stylelint@16.8.2(typescript@5.5.4)) + stylelint-config-recommended-vue: + specifier: 1.5.0 + version: 1.5.0(postcss-html@1.7.0)(stylelint@16.8.2(typescript@5.5.4)) + stylelint-config-standard-scss: + specifier: 13.1.0 + version: 13.1.0(postcss@8.4.41)(stylelint@16.8.2(typescript@5.5.4)) + stylelint-prettier: + specifier: 5.0.2 + version: 5.0.2(prettier@3.3.3)(stylelint@16.8.2(typescript@5.5.4)) + svgo: + specifier: 3.3.2 + version: 3.3.2 + tailwindcss: + specifier: 3.4.10 + version: 3.4.10 + typescript: + specifier: 5.5.4 + version: 5.5.4 + vite: + specifier: 5.4.1 + version: 5.4.1(@types/node@20.16.1)(sass@1.77.8) + vite-plugin-cdn-import: + specifier: 1.0.1 + version: 1.0.1(rollup@4.21.0)(vite@5.4.1(@types/node@20.16.1)(sass@1.77.8)) + vite-plugin-checker: + specifier: 0.7.2 + version: 0.7.2(eslint@9.9.0(jiti@1.21.6))(optionator@0.9.4)(stylelint@16.8.2(typescript@5.5.4))(typescript@5.5.4)(vite@5.4.1(@types/node@20.16.1)(sass@1.77.8))(vue-tsc@2.0.29(typescript@5.5.4)) + vite-plugin-compression: + specifier: 0.5.1 + version: 0.5.1(vite@5.4.1(@types/node@20.16.1)(sass@1.77.8)) + vite-plugin-fake-server: + specifier: 2.1.1 + version: 2.1.1 + vite-plugin-remove-console: + specifier: 2.2.0 + version: 2.2.0 + vite-plugin-router-warn: + specifier: 1.0.0 + version: 1.0.0 + vite-plugin-vue-inspector: + specifier: 5.1.3 + version: 5.1.3(vite@5.4.1(@types/node@20.16.1)(sass@1.77.8)) + vite-svg-loader: + specifier: 5.1.0 + version: 5.1.0(vue@3.4.38(typescript@5.5.4)) + vue-eslint-parser: + specifier: 9.4.3 + version: 9.4.3(eslint@9.9.0(jiti@1.21.6)) + vue-tsc: + specifier: 2.0.29 + version: 2.0.29(typescript@5.5.4) + +packages: + + '@alloc/quick-lru@5.2.0': + resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} + engines: {node: '>=10'} + + '@ampproject/remapping@2.3.0': + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + engines: {node: '>=6.0.0'} + + '@babel/code-frame@7.24.7': + resolution: {integrity: sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.25.2': + resolution: {integrity: sha512-bYcppcpKBvX4znYaPEeFau03bp89ShqNMLs+rmdptMw+heSZh9+z84d2YG+K7cYLbWwzdjtDoW/uqZmPjulClQ==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.25.2': + resolution: {integrity: sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.25.0': + resolution: {integrity: sha512-3LEEcj3PVW8pW2R1SR1M89g/qrYk/m/mB/tLqn7dn4sbBUQyTqnlod+II2U4dqiGtUmkcnAmkMDralTFZttRiw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-annotate-as-pure@7.24.7': + resolution: {integrity: sha512-BaDeOonYvhdKw+JoMVkAixAAJzG2jVPIwWoKBPdYuY9b452e2rPuI9QPYh3KpofZ3pW2akOmwZLOiOsHMiqRAg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.25.2': + resolution: {integrity: sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-create-class-features-plugin@7.25.0': + resolution: {integrity: sha512-GYM6BxeQsETc9mnct+nIIpf63SAyzvyYN7UB/IlTyd+MBg06afFGp0mIeUqGyWgS2mxad6vqbMrHVlaL3m70sQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-member-expression-to-functions@7.24.8': + resolution: {integrity: sha512-LABppdt+Lp/RlBxqrh4qgf1oEH/WxdzQNDJIu5gC/W1GyvPVrOBiItmmM8wan2fm4oYqFuFfkXmlGpLQhPY8CA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.22.15': + resolution: {integrity: sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.24.7': + resolution: {integrity: sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.25.2': + resolution: {integrity: sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-optimise-call-expression@7.24.7': + resolution: {integrity: sha512-jKiTsW2xmWwxT1ixIdfXUZp+P5yURx2suzLZr5Hi64rURpDYdMW0pv+Uf17EYk2Rd428Lx4tLsnjGJzYKDM/6A==} + engines: {node: '>=6.9.0'} + + '@babel/helper-plugin-utils@7.24.8': + resolution: {integrity: sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-replace-supers@7.25.0': + resolution: {integrity: sha512-q688zIvQVYtZu+i2PsdIu/uWGRpfxzr5WESsfpShfZECkO+d2o+WROWezCi/Q6kJ0tfPa5+pUGUlfx2HhrA3Bg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-simple-access@7.24.7': + resolution: {integrity: sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-skip-transparent-expression-wrappers@7.24.7': + resolution: {integrity: sha512-IO+DLT3LQUElMbpzlatRASEyQtfhSE0+m465v++3jyyXeBTBUjtVZg28/gHeV5mrTJqvEKhKroBGAvhW+qPHiQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.24.8': + resolution: {integrity: sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.24.7': + resolution: {integrity: sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.24.8': + resolution: {integrity: sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.25.0': + resolution: {integrity: sha512-MjgLZ42aCm0oGjJj8CtSM3DB8NOOf8h2l7DCTePJs29u+v7yO/RBX9nShlKMgFnRks/Q4tBAe7Hxnov9VkGwLw==} + engines: {node: '>=6.9.0'} + + '@babel/highlight@7.24.7': + resolution: {integrity: sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.25.3': + resolution: {integrity: sha512-iLTJKDbJ4hMvFPgQwwsVoxtHyWpKKPBrxkANrSYewDPaPpT5py5yeVkgPIJ7XYXhndxJpaA3PyALSXQ7u8e/Dw==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-proposal-decorators@7.24.7': + resolution: {integrity: sha512-RL9GR0pUG5Kc8BUWLNDm2T5OpYwSX15r98I0IkgmRQTXuELq/OynH8xtMTMvTJFjXbMWFVTKtYkTaYQsuAwQlQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-decorators@7.24.7': + resolution: {integrity: sha512-Ui4uLJJrRV1lb38zg1yYTmRKmiZLiftDEvZN2iq3kd9kUFU+PttmzTbAFC2ucRk/XJmtek6G23gPsuZbhrT8fQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-attributes@7.24.7': + resolution: {integrity: sha512-hbX+lKKeUMGihnK8nvKqmXBInriT3GVjzXKFriV3YC6APGxMbP8RZNFwy91+hocLXq90Mta+HshoB31802bb8A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-meta@7.10.4': + resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-jsx@7.24.7': + resolution: {integrity: sha512-6ddciUPe/mpMnOKv/U+RSd2vvVy+Yw/JfBB0ZHYjEZt9NLHmCUylNYlsbqCCS1Bffjlb0fCwC9Vqz+sBz6PsiQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-typescript@7.24.7': + resolution: {integrity: sha512-c/+fVeJBB0FeKsFvwytYiUD+LBvhHjGSI0g446PRGdSVGZLRNArBUno2PETbAly3tpiNAQR5XaZ+JslxkotsbA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-typescript@7.25.2': + resolution: {integrity: sha512-lBwRvjSmqiMYe/pS0+1gggjJleUJi7NzjvQ1Fkqtt69hBa/0t1YuW/MLQMAPixfwaQOHUXsd6jeU3Z+vdGv3+A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/standalone@7.25.3': + resolution: {integrity: sha512-uR+EoBqIIIvKGCG7fOj7HKupu3zVObiMfdEwoPZfVCPpcWJaZ1PkshaP5/6cl6BKAm1Zcv25O1rf+uoQ7V8nqA==} + engines: {node: '>=6.9.0'} + + '@babel/template@7.25.0': + resolution: {integrity: sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.25.3': + resolution: {integrity: sha512-HefgyP1x754oGCsKmV5reSmtV7IXj/kpaE1XYY+D9G5PvKKoFfSbiS4M77MdjuwlZKDIKFCffq9rPU+H/s3ZdQ==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.25.2': + resolution: {integrity: sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q==} + engines: {node: '>=6.9.0'} + + '@commitlint/cli@19.4.0': + resolution: {integrity: sha512-sJX4J9UioVwZHq7JWM9tjT5bgWYaIN3rC4FP7YwfEwBYiIO+wMyRttRvQLNkow0vCdM0D67r9NEWU0Ui03I4Eg==} + engines: {node: '>=v18'} + hasBin: true + + '@commitlint/config-conventional@19.2.2': + resolution: {integrity: sha512-mLXjsxUVLYEGgzbxbxicGPggDuyWNkf25Ht23owXIH+zV2pv1eJuzLK3t1gDY5Gp6pxdE60jZnWUY5cvgL3ufw==} + engines: {node: '>=v18'} + + '@commitlint/config-validator@19.0.3': + resolution: {integrity: sha512-2D3r4PKjoo59zBc2auodrSCaUnCSALCx54yveOFwwP/i2kfEAQrygwOleFWswLqK0UL/F9r07MFi5ev2ohyM4Q==} + engines: {node: '>=v18'} + + '@commitlint/ensure@19.0.3': + resolution: {integrity: sha512-SZEpa/VvBLoT+EFZVb91YWbmaZ/9rPH3ESrINOl0HD2kMYsjvl0tF7nMHh0EpTcv4+gTtZBAe1y/SS6/OhfZzQ==} + engines: {node: '>=v18'} + + '@commitlint/execute-rule@19.0.0': + resolution: {integrity: sha512-mtsdpY1qyWgAO/iOK0L6gSGeR7GFcdW7tIjcNFxcWkfLDF5qVbPHKuGATFqRMsxcO8OUKNj0+3WOHB7EHm4Jdw==} + engines: {node: '>=v18'} + + '@commitlint/format@19.3.0': + resolution: {integrity: sha512-luguk5/aF68HiF4H23ACAfk8qS8AHxl4LLN5oxPc24H+2+JRPsNr1OS3Gaea0CrH7PKhArBMKBz5RX9sA5NtTg==} + engines: {node: '>=v18'} + + '@commitlint/is-ignored@19.2.2': + resolution: {integrity: sha512-eNX54oXMVxncORywF4ZPFtJoBm3Tvp111tg1xf4zWXGfhBPKpfKG6R+G3G4v5CPlRROXpAOpQ3HMhA9n1Tck1g==} + engines: {node: '>=v18'} + + '@commitlint/lint@19.2.2': + resolution: {integrity: sha512-xrzMmz4JqwGyKQKTpFzlN0dx0TAiT7Ran1fqEBgEmEj+PU98crOFtysJgY+QdeSagx6EDRigQIXJVnfrI0ratA==} + engines: {node: '>=v18'} + + '@commitlint/load@19.4.0': + resolution: {integrity: sha512-I4lCWaEZYQJ1y+Y+gdvbGAx9pYPavqZAZ3/7/8BpWh+QjscAn8AjsUpLV2PycBsEx7gupq5gM4BViV9xwTIJuw==} + engines: {node: '>=v18'} + + '@commitlint/message@19.0.0': + resolution: {integrity: sha512-c9czf6lU+9oF9gVVa2lmKaOARJvt4soRsVmbR7Njwp9FpbBgste5i7l/2l5o8MmbwGh4yE1snfnsy2qyA2r/Fw==} + engines: {node: '>=v18'} + + '@commitlint/parse@19.0.3': + resolution: {integrity: sha512-Il+tNyOb8VDxN3P6XoBBwWJtKKGzHlitEuXA5BP6ir/3loWlsSqDr5aecl6hZcC/spjq4pHqNh0qPlfeWu38QA==} + engines: {node: '>=v18'} + + '@commitlint/read@19.4.0': + resolution: {integrity: sha512-r95jLOEZzKDakXtnQub+zR3xjdnrl2XzerPwm7ch1/cc5JGq04tyaNpa6ty0CRCWdVrk4CZHhqHozb8yZwy2+g==} + engines: {node: '>=v18'} + + '@commitlint/resolve-extends@19.1.0': + resolution: {integrity: sha512-z2riI+8G3CET5CPgXJPlzftH+RiWYLMYv4C9tSLdLXdr6pBNimSKukYP9MS27ejmscqCTVA4almdLh0ODD2KYg==} + engines: {node: '>=v18'} + + '@commitlint/rules@19.0.3': + resolution: {integrity: sha512-TspKb9VB6svklxNCKKwxhELn7qhtY1rFF8ls58DcFd0F97XoG07xugPjjbVnLqmMkRjZDbDIwBKt9bddOfLaPw==} + engines: {node: '>=v18'} + + '@commitlint/to-lines@19.0.0': + resolution: {integrity: sha512-vkxWo+VQU5wFhiP9Ub9Sre0FYe019JxFikrALVoD5UGa8/t3yOJEpEhxC5xKiENKKhUkTpEItMTRAjHw2SCpZw==} + engines: {node: '>=v18'} + + '@commitlint/top-level@19.0.0': + resolution: {integrity: sha512-KKjShd6u1aMGNkCkaX4aG1jOGdn7f8ZI8TR1VEuNqUOjWTOdcDSsmglinglJ18JTjuBX5I1PtjrhQCRcixRVFQ==} + engines: {node: '>=v18'} + + '@commitlint/types@19.0.3': + resolution: {integrity: sha512-tpyc+7i6bPG9mvaBbtKUeghfyZSDgWquIDfMgqYtTbmZ9Y9VzEm2je9EYcQ0aoz5o7NvGS+rcDec93yO08MHYA==} + engines: {node: '>=v18'} + + '@csstools/css-parser-algorithms@3.0.1': + resolution: {integrity: sha512-lSquqZCHxDfuTg/Sk2hiS0mcSFCEBuj49JfzPHJogDBT0mGCyY5A1AQzBWngitrp7i1/HAZpIgzF/VjhOEIJIg==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-tokenizer': ^3.0.1 + + '@csstools/css-tokenizer@3.0.1': + resolution: {integrity: sha512-UBqaiu7kU0lfvaP982/o3khfXccVlHPWp0/vwwiIgDF0GmqqqxoiXC/6FCjlS9u92f7CoEz6nXKQnrn1kIAkOw==} + engines: {node: '>=18'} + + '@csstools/media-query-list-parser@3.0.1': + resolution: {integrity: sha512-HNo8gGD02kHmcbX6PvCoUuOQvn4szyB9ca63vZHKX5A81QytgDG4oxG4IaEfHTlEZSZ6MjPEMWIVU+zF2PZcgw==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-parser-algorithms': ^3.0.1 + '@csstools/css-tokenizer': ^3.0.1 + + '@csstools/selector-specificity@4.0.0': + resolution: {integrity: sha512-189nelqtPd8++phaHNwYovKZI0FOzH1vQEE3QhHHkNIGrg5fSs9CbYP3RvfEH5geztnIA9Jwq91wyOIwAW5JIQ==} + engines: {node: '>=18'} + peerDependencies: + postcss-selector-parser: ^6.1.0 + + '@ctrl/tinycolor@3.6.1': + resolution: {integrity: sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==} + engines: {node: '>=10'} + + '@dual-bundle/import-meta-resolve@4.1.0': + resolution: {integrity: sha512-+nxncfwHM5SgAtrVzgpzJOI1ol0PkumhVo469KCf9lUi21IGcY90G98VuHm9VRrUypmAzawAHO9bs6hqeADaVg==} + + '@element-plus/icons-vue@2.3.1': + resolution: {integrity: sha512-XxVUZv48RZAd87ucGS48jPf6pKu0yV5UCg9f4FFwtrYxXOwWuVJo6wOvSLKEoMQKjv8GsX/mhP6UsC1lRwbUWg==} + peerDependencies: + vue: ^3.2.0 + + '@esbuild/aix-ppc64@0.19.12': + resolution: {integrity: sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + + '@esbuild/aix-ppc64@0.21.5': + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.19.12': + resolution: {integrity: sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm64@0.21.5': + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.19.12': + resolution: {integrity: sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + + '@esbuild/android-arm@0.21.5': + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.19.12': + resolution: {integrity: sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + + '@esbuild/android-x64@0.21.5': + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.19.12': + resolution: {integrity: sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-arm64@0.21.5': + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.19.12': + resolution: {integrity: sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + + '@esbuild/darwin-x64@0.21.5': + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.19.12': + resolution: {integrity: sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-arm64@0.21.5': + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.19.12': + resolution: {integrity: sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.21.5': + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.19.12': + resolution: {integrity: sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm64@0.21.5': + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.19.12': + resolution: {integrity: sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-arm@0.21.5': + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.19.12': + resolution: {integrity: sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-ia32@0.21.5': + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.19.12': + resolution: {integrity: sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-loong64@0.21.5': + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.19.12': + resolution: {integrity: sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-mips64el@0.21.5': + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.19.12': + resolution: {integrity: sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-ppc64@0.21.5': + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.19.12': + resolution: {integrity: sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-riscv64@0.21.5': + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.19.12': + resolution: {integrity: sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-s390x@0.21.5': + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.19.12': + resolution: {integrity: sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + + '@esbuild/linux-x64@0.21.5': + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-x64@0.19.12': + resolution: {integrity: sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.21.5': + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-x64@0.19.12': + resolution: {integrity: sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.21.5': + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.19.12': + resolution: {integrity: sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + + '@esbuild/sunos-x64@0.21.5': + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.19.12': + resolution: {integrity: sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-arm64@0.21.5': + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.19.12': + resolution: {integrity: sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-ia32@0.21.5': + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.19.12': + resolution: {integrity: sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + + '@esbuild/win32-x64@0.21.5': + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + + '@eslint-community/eslint-utils@4.4.0': + resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.11.0': + resolution: {integrity: sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.17.1': + resolution: {integrity: sha512-BlYOpej8AQ8Ev9xVqroV7a02JK3SkBAaN9GfMMH9W6Ch8FlQlkjGw4Ir7+FgYwfirivAf4t+GtzuAxqfukmISA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/eslintrc@3.1.0': + resolution: {integrity: sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.9.0': + resolution: {integrity: sha512-hhetes6ZHP3BlXLxmd8K2SNgkhNSi+UcecbnwWKwpP7kyi/uC75DJ1lOOBO3xrC4jyojtGE3YxKZPHfk4yrgug==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.4': + resolution: {integrity: sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@faker-js/faker@8.4.1': + resolution: {integrity: sha512-XQ3cU+Q8Uqmrbf2e0cIC/QN43sTBSC8KF12u29Mb47tWrt2hAgBXSgpZMj4Ao8Uk0iJcU99QsOCaIL8934obCg==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0, npm: '>=6.14.13'} + + '@floating-ui/core@1.6.7': + resolution: {integrity: sha512-yDzVT/Lm101nQ5TCVeK65LtdN7Tj4Qpr9RTXJ2vPFLqtLxwOrpoxAHAJI8J3yYWUc40J0BDBheaitK5SJmno2g==} + + '@floating-ui/dom@1.6.10': + resolution: {integrity: sha512-fskgCFv8J8OamCmyun8MfjB1Olfn+uZKjOKZ0vhYF3gRmEUXcGOjxWL8bBr7i4kIuPZ2KD2S3EUIOxnjC8kl2A==} + + '@floating-ui/utils@0.2.7': + resolution: {integrity: sha512-X8R8Oj771YRl/w+c1HqAC1szL8zWQRwFvgDwT129k9ACdBoud/+/rX9V0qiMl6LWUdP9voC2nDVZYPMQQsb6eA==} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.3.0': + resolution: {integrity: sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==} + engines: {node: '>=18.18'} + + '@iconify-icons/ep@1.2.12': + resolution: {integrity: sha512-8EJULn048sQq3fvytpQ5j40omnVOdBKpo+sXdYM35NRrqCe1BihxBesMcCOLWaocqkWia6uTQ3cnRHff4ZA11w==} + + '@iconify-icons/ri@1.2.10': + resolution: {integrity: sha512-wNaXsQYK55WDUWCbcjvnwnODV4Jtsp+VC0duPanibEVu876TUYf6kdgTGtH7/GErBCNdJuJJbncG7vbOaeQi7w==} + + '@iconify/types@2.0.0': + resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} + + '@iconify/vue@4.1.2': + resolution: {integrity: sha512-CQnYqLiQD5LOAaXhBrmj1mdL2/NCJvwcC4jtW2Z8ukhThiFkLDkutarTOV2trfc9EXqUqRs0KqXOL9pZ/IyysA==} + peerDependencies: + vue: '>=3' + + '@intlify/bundle-utils@8.0.0': + resolution: {integrity: sha512-1B++zykRnMwQ+20SpsZI1JCnV/YJt9Oq7AGlEurzkWJOFtFAVqaGc/oV36PBRYeiKnTbY9VYfjBimr2Vt42wLQ==} + engines: {node: '>= 14.16'} + peerDependencies: + petite-vue-i18n: '*' + vue-i18n: '*' + peerDependenciesMeta: + petite-vue-i18n: + optional: true + vue-i18n: + optional: true + + '@intlify/core-base@9.14.0': + resolution: {integrity: sha512-zJn0imh9HIsZZUtt9v8T16PeVstPv6bP2YzlrYJwoF8F30gs4brZBwW2KK6EI5WYKFi3NeqX6+UU4gniz5TkGg==} + engines: {node: '>= 16'} + + '@intlify/message-compiler@9.14.0': + resolution: {integrity: sha512-sXNsoMI0YsipSXW8SR75drmVK56tnJHoYbPXUv2Cf9lz6FzvwsosFm6JtC1oQZI/kU+n7qx0qRrEWkeYFTgETA==} + engines: {node: '>= 16'} + + '@intlify/shared@9.14.0': + resolution: {integrity: sha512-r+N8KRQL7LgN1TMTs1A2svfuAU0J94Wu9wWdJVJqYsoMMLIeJxrPjazihfHpmJqfgZq0ah3Y9Q4pgWV2O90Fyg==} + engines: {node: '>= 16'} + + '@intlify/unplugin-vue-i18n@4.0.0': + resolution: {integrity: sha512-q2Mhqa/mLi0tulfLFO4fMXXvEbkSZpI5yGhNNsLTNJJ41icEGUuyDe+j5zRZIKSkOJRgX6YbCyibTDJdRsukmw==} + engines: {node: '>= 14.16'} + peerDependencies: + petite-vue-i18n: '*' + vue-i18n: '*' + vue-i18n-bridge: '*' + peerDependenciesMeta: + petite-vue-i18n: + optional: true + vue-i18n: + optional: true + vue-i18n-bridge: + optional: true + + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + + '@jridgewell/gen-mapping@0.3.5': + resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} + engines: {node: '>=6.0.0'} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/set-array@1.2.1': + resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.0': + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + + '@jridgewell/trace-mapping@0.3.25': + resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@nuxt/kit@3.12.4': + resolution: {integrity: sha512-aNRD1ylzijY0oYolldNcZJXVyxdGzNTl+Xd0UYyFQCu9f4wqUZqQ9l+b7arCEzchr96pMK0xdpvLcS3xo1wDcw==} + engines: {node: ^14.18.0 || >=16.10.0} + + '@nuxt/schema@3.12.4': + resolution: {integrity: sha512-H7FwBV4ChssMaeiLyPdVLOLUa0326ebp3pNbJfGgFt7rSoKh1MmgjorecA8JMxOQZziy3w6EELf4+5cgLh/F1w==} + engines: {node: ^14.18.0 || >=16.10.0} + + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + + '@pkgr/core@0.1.1': + resolution: {integrity: sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + + '@popperjs/core@2.11.8': + resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} + + '@pureadmin/descriptions@1.2.1': + resolution: {integrity: sha512-7jDJuqz8xnhcmwXdWQnBzOYeX2WK27TRFaVgs9AdiRr+DnKb9W+krHByJwQtxo5lg4qyRh4/IWQGEMfhC2ljeQ==} + peerDependencies: + element-plus: ^2.0.0 + + '@pureadmin/table@3.2.0': + resolution: {integrity: sha512-5H09KrQDeGpxVxOKCISi65WcT7mSuyhGTHiDcHUcaZFXMkB874ZikDBuNFMUp4ccqyztoD6AAo9Tk/cqCMhbuQ==} + peerDependencies: + element-plus: ^2.0.0 + + '@pureadmin/theme@3.2.0': + resolution: {integrity: sha512-SBlTvEl0rmfqTW/mOJUPftvZe4yF+38CJdlBOvVITpehzCytqlG5i8XKpcs8aAR9SVfhcrLVS5Q6xh7xDVQcJQ==} + + '@pureadmin/utils@2.4.8': + resolution: {integrity: sha512-7baOiunmno3mlvk7oKIrEvIEVAbJgIrvvMw9EJMOvTmebfn31F5ps9JyIeDzsnVRABZLSEg0iIVVfYe+DGAeqg==} + peerDependencies: + echarts: '*' + vue: '*' + peerDependenciesMeta: + echarts: + optional: true + vue: + optional: true + + '@rollup/pluginutils@5.1.0': + resolution: {integrity: sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/rollup-android-arm-eabi@4.21.0': + resolution: {integrity: sha512-WTWD8PfoSAJ+qL87lE7votj3syLavxunWhzCnx3XFxFiI/BA/r3X7MUM8dVrH8rb2r4AiO8jJsr3ZjdaftmnfA==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.21.0': + resolution: {integrity: sha512-a1sR2zSK1B4eYkiZu17ZUZhmUQcKjk2/j9Me2IDjk1GHW7LB5Z35LEzj9iJch6gtUfsnvZs1ZNyDW2oZSThrkA==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.21.0': + resolution: {integrity: sha512-zOnKWLgDld/svhKO5PD9ozmL6roy5OQ5T4ThvdYZLpiOhEGY+dp2NwUmxK0Ld91LrbjrvtNAE0ERBwjqhZTRAA==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.21.0': + resolution: {integrity: sha512-7doS8br0xAkg48SKE2QNtMSFPFUlRdw9+votl27MvT46vo44ATBmdZdGysOevNELmZlfd+NEa0UYOA8f01WSrg==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-linux-arm-gnueabihf@4.21.0': + resolution: {integrity: sha512-pWJsfQjNWNGsoCq53KjMtwdJDmh/6NubwQcz52aEwLEuvx08bzcy6tOUuawAOncPnxz/3siRtd8hiQ32G1y8VA==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm-musleabihf@4.21.0': + resolution: {integrity: sha512-efRIANsz3UHZrnZXuEvxS9LoCOWMGD1rweciD6uJQIx2myN3a8Im1FafZBzh7zk1RJ6oKcR16dU3UPldaKd83w==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-arm64-gnu@4.21.0': + resolution: {integrity: sha512-ZrPhydkTVhyeGTW94WJ8pnl1uroqVHM3j3hjdquwAcWnmivjAwOYjTEAuEDeJvGX7xv3Z9GAvrBkEzCgHq9U1w==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm64-musl@4.21.0': + resolution: {integrity: sha512-cfaupqd+UEFeURmqNP2eEvXqgbSox/LHOyN9/d2pSdV8xTrjdg3NgOFJCtc1vQ/jEke1qD0IejbBfxleBPHnPw==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-powerpc64le-gnu@4.21.0': + resolution: {integrity: sha512-ZKPan1/RvAhrUylwBXC9t7B2hXdpb/ufeu22pG2psV7RN8roOfGurEghw1ySmX/CmDDHNTDDjY3lo9hRlgtaHg==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-gnu@4.21.0': + resolution: {integrity: sha512-H1eRaCwd5E8eS8leiS+o/NqMdljkcb1d6r2h4fKSsCXQilLKArq6WS7XBLDu80Yz+nMqHVFDquwcVrQmGr28rg==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-s390x-gnu@4.21.0': + resolution: {integrity: sha512-zJ4hA+3b5tu8u7L58CCSI0A9N1vkfwPhWd/puGXwtZlsB5bTkwDNW/+JCU84+3QYmKpLi+XvHdmrlwUwDA6kqw==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-gnu@4.21.0': + resolution: {integrity: sha512-e2hrvElFIh6kW/UNBQK/kzqMNY5mO+67YtEh9OA65RM5IJXYTWiXjX6fjIiPaqOkBthYF1EqgiZ6OXKcQsM0hg==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-musl@4.21.0': + resolution: {integrity: sha512-1vvmgDdUSebVGXWX2lIcgRebqfQSff0hMEkLJyakQ9JQUbLDkEaMsPTLOmyccyC6IJ/l3FZuJbmrBw/u0A0uCQ==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rollup/rollup-win32-arm64-msvc@4.21.0': + resolution: {integrity: sha512-s5oFkZ/hFcrlAyBTONFY1TWndfyre1wOMwU+6KCpm/iatybvrRgmZVM+vCFwxmC5ZhdlgfE0N4XorsDpi7/4XQ==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.21.0': + resolution: {integrity: sha512-G9+TEqRnAA6nbpqyUqgTiopmnfgnMkR3kMukFBDsiyy23LZvUCpiUwjTRx6ezYCjJODXrh52rBR9oXvm+Fp5wg==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.21.0': + resolution: {integrity: sha512-2jsCDZwtQvRhejHLfZ1JY6w6kEuEtfF9nzYsZxzSlNVKDX+DpsDJ+Rbjkm74nvg2rdx0gwBS+IMdvwJuq3S9pQ==} + cpu: [x64] + os: [win32] + + '@sindresorhus/merge-streams@2.3.0': + resolution: {integrity: sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==} + engines: {node: '>=18'} + + '@sxzz/popperjs-es@2.11.7': + resolution: {integrity: sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ==} + + '@trysound/sax@0.2.0': + resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==} + engines: {node: '>=10.13.0'} + + '@types/conventional-commits-parser@5.0.0': + resolution: {integrity: sha512-loB369iXNmAZglwWATL+WRe+CRMmmBPtpolYzIebFaX4YA3x+BEfLqhUAV9WanycKI3TG1IMr5bMJDajDKLlUQ==} + + '@types/estree@1.0.5': + resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} + + '@types/gradient-string@1.1.6': + resolution: {integrity: sha512-LkaYxluY4G5wR1M4AKQUal2q61Di1yVVCw42ImFTuaIoQVgmV0WP1xUaLB8zwb47mp82vWTpePI9JmrjEnJ7nQ==} + + '@types/js-cookie@3.0.6': + resolution: {integrity: sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==} + + '@types/lodash-es@4.17.12': + resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==} + + '@types/lodash@4.17.7': + resolution: {integrity: sha512-8wTvZawATi/lsmNu10/j2hk1KEP0IvjubqPE3cu1Xz7xfXXt5oCq3SNUz4fMIP4XGF9Ky+Ue2tBA3hcS7LSBlA==} + + '@types/node@20.16.1': + resolution: {integrity: sha512-zJDo7wEadFtSyNz5QITDfRcrhqDvQI1xQNQ0VoizPjM/dVAODqqIUWbJPkvsxmTI0MYRGRikcdjMPhOssnPejQ==} + + '@types/nprogress@0.2.3': + resolution: {integrity: sha512-k7kRA033QNtC+gLc4VPlfnue58CM1iQLgn1IMAU8VPHGOj7oIHPp9UlhedEnD/Gl8evoCjwkZjlBORtZ3JByUA==} + + '@types/qs@6.9.15': + resolution: {integrity: sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg==} + + '@types/sortablejs@1.15.8': + resolution: {integrity: sha512-b79830lW+RZfwaztgs1aVPgbasJ8e7AXtZYHTELNXZPsERt4ymJdjV4OccDbHQAvHrCcFpbF78jkm0R6h/pZVg==} + + '@types/tinycolor2@1.4.6': + resolution: {integrity: sha512-iEN8J0BoMnsWBqjVbWH/c0G0Hh7O21lpR2/+PrvAVgWdzL7eexIFm4JN/Wn10PTcmNdtS6U67r499mlWMXOxNw==} + + '@types/web-bluetooth@0.0.16': + resolution: {integrity: sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==} + + '@types/web-bluetooth@0.0.20': + resolution: {integrity: sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==} + + '@typescript-eslint/eslint-plugin@7.18.0': + resolution: {integrity: sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + '@typescript-eslint/parser': ^7.0.0 + eslint: ^8.56.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/parser@7.18.0': + resolution: {integrity: sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + eslint: ^8.56.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/scope-manager@7.18.0': + resolution: {integrity: sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==} + engines: {node: ^18.18.0 || >=20.0.0} + + '@typescript-eslint/type-utils@7.18.0': + resolution: {integrity: sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + eslint: ^8.56.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/types@7.18.0': + resolution: {integrity: sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==} + engines: {node: ^18.18.0 || >=20.0.0} + + '@typescript-eslint/typescript-estree@7.18.0': + resolution: {integrity: sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/utils@7.18.0': + resolution: {integrity: sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + eslint: ^8.56.0 + + '@typescript-eslint/visitor-keys@7.18.0': + resolution: {integrity: sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==} + engines: {node: ^18.18.0 || >=20.0.0} + + '@vitejs/plugin-vue-jsx@4.0.1': + resolution: {integrity: sha512-7mg9HFGnFHMEwCdB6AY83cVK4A6sCqnrjFYF4WIlebYAQVVJ/sC/CiTruVdrRlhrFoeZ8rlMxY9wYpPTIRhhAg==} + engines: {node: ^18.0.0 || >=20.0.0} + peerDependencies: + vite: ^5.0.0 + vue: ^3.0.0 + + '@vitejs/plugin-vue@5.1.2': + resolution: {integrity: sha512-nY9IwH12qeiJqumTCLJLE7IiNx7HZ39cbHaysEUd+Myvbz9KAqd2yq+U01Kab1R/H1BmiyM2ShTYlNH32Fzo3A==} + engines: {node: ^18.0.0 || >=20.0.0} + peerDependencies: + vite: ^5.0.0 + vue: ^3.2.25 + + '@volar/language-core@2.4.0': + resolution: {integrity: sha512-FTla+khE+sYK0qJP+6hwPAAUwiNHVMph4RUXpxf/FIPKUP61NFrVZorml4mjFShnueR2y9/j8/vnh09YwVdH7A==} + + '@volar/source-map@2.4.0': + resolution: {integrity: sha512-2ceY8/NEZvN6F44TXw2qRP6AQsvCYhV2bxaBPWxV9HqIfkbRydSksTFObCF1DBDNBfKiZTS8G/4vqV6cvjdOIQ==} + + '@volar/typescript@2.4.0': + resolution: {integrity: sha512-9zx3lQWgHmVd+JRRAHUSRiEhe4TlzL7U7e6ulWXOxHH/WNYxzKwCvZD7WYWEZFdw4dHfTD9vUR0yPQO6GilCaQ==} + + '@vue/babel-helper-vue-transform-on@1.2.2': + resolution: {integrity: sha512-nOttamHUR3YzdEqdM/XXDyCSdxMA9VizUKoroLX6yTyRtggzQMHXcmwh8a7ZErcJttIBIc9s68a1B8GZ+Dmvsw==} + + '@vue/babel-plugin-jsx@1.2.2': + resolution: {integrity: sha512-nYTkZUVTu4nhP199UoORePsql0l+wj7v/oyQjtThUVhJl1U+6qHuoVhIvR3bf7eVKjbCK+Cs2AWd7mi9Mpz9rA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + peerDependenciesMeta: + '@babel/core': + optional: true + + '@vue/babel-plugin-resolve-type@1.2.2': + resolution: {integrity: sha512-EntyroPwNg5IPVdUJupqs0CFzuf6lUrVvCspmv2J1FITLeGnUCuoGNNk78dgCusxEiYj6RMkTJflGSxk5aIC4A==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@vue/compiler-core@3.4.38': + resolution: {integrity: sha512-8IQOTCWnLFqfHzOGm9+P8OPSEDukgg3Huc92qSG49if/xI2SAwLHQO2qaPQbjCWPBcQoO1WYfXfTACUrWV3c5A==} + + '@vue/compiler-dom@3.4.38': + resolution: {integrity: sha512-Osc/c7ABsHXTsETLgykcOwIxFktHfGSUDkb05V61rocEfsFDcjDLH/IHJSNJP+/Sv9KeN2Lx1V6McZzlSb9EhQ==} + + '@vue/compiler-sfc@3.4.38': + resolution: {integrity: sha512-s5QfZ+9PzPh3T5H4hsQDJtI8x7zdJaew/dCGgqZ2630XdzaZ3AD8xGZfBqpT8oaD/p2eedd+pL8tD5vvt5ZYJQ==} + + '@vue/compiler-ssr@3.4.38': + resolution: {integrity: sha512-YXznKFQ8dxYpAz9zLuVvfcXhc31FSPFDcqr0kyujbOwNhlmaNvL2QfIy+RZeJgSn5Fk54CWoEUeW+NVBAogGaw==} + + '@vue/compiler-vue2@2.7.16': + resolution: {integrity: sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==} + + '@vue/devtools-api@6.6.3': + resolution: {integrity: sha512-0MiMsFma/HqA6g3KLKn+AGpL1kgKhFWszC9U29NfpWK5LE7bjeXxySWJrOJ77hBz+TBrBQ7o4QJqbPbqbs8rJw==} + + '@vue/language-core@2.0.29': + resolution: {integrity: sha512-o2qz9JPjhdoVj8D2+9bDXbaI4q2uZTHQA/dbyZT4Bj1FR9viZxDJnLcKVHfxdn6wsOzRgpqIzJEEmSSvgMvDTQ==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@vue/reactivity@3.4.38': + resolution: {integrity: sha512-4vl4wMMVniLsSYYeldAKzbk72+D3hUnkw9z8lDeJacTxAkXeDAP1uE9xr2+aKIN0ipOL8EG2GPouVTH6yF7Gnw==} + + '@vue/runtime-core@3.4.38': + resolution: {integrity: sha512-21z3wA99EABtuf+O3IhdxP0iHgkBs1vuoCAsCKLVJPEjpVqvblwBnTj42vzHRlWDCyxu9ptDm7sI2ZMcWrQqlA==} + + '@vue/runtime-dom@3.4.38': + resolution: {integrity: sha512-afZzmUreU7vKwKsV17H1NDThEEmdYI+GCAK/KY1U957Ig2NATPVjCROv61R19fjZNzMmiU03n79OMnXyJVN0UA==} + + '@vue/server-renderer@3.4.38': + resolution: {integrity: sha512-NggOTr82FbPEkkUvBm4fTGcwUY8UuTsnWC/L2YZBmvaQ4C4Jl/Ao4HHTB+l7WnFCt5M/dN3l0XLuyjzswGYVCA==} + peerDependencies: + vue: 3.4.38 + + '@vue/shared@3.4.38': + resolution: {integrity: sha512-q0xCiLkuWWQLzVrecPb0RMsNWyxICOjPrcrwxTUEHb1fsnvni4dcuyG7RT/Ie7VPTvnjzIaWzRMUBsrqNj/hhw==} + + '@vueuse/core@10.11.1': + resolution: {integrity: sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww==} + + '@vueuse/core@9.13.0': + resolution: {integrity: sha512-pujnclbeHWxxPRqXWmdkKV5OX4Wk4YeK7wusHqRwU0Q7EFusHoqNA/aPhB6KCh9hEqJkLAJo7bb0Lh9b+OIVzw==} + + '@vueuse/metadata@10.11.1': + resolution: {integrity: sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw==} + + '@vueuse/metadata@9.13.0': + resolution: {integrity: sha512-gdU7TKNAUVlXXLbaF+ZCfte8BjRJQWPCa2J55+7/h+yDtzw3vOoGQDRXzI6pyKyo6bXFT5/QoPE4hAknExjRLQ==} + + '@vueuse/motion@2.2.3': + resolution: {integrity: sha512-QsS9P7MDKFF2j4eDPuo6lezUDPE7MQp4+unBeR2Ym8RkhtMEw/oVbnHPHL986rsmnnlUZkHVSv5kKoHJzjbtmQ==} + peerDependencies: + vue: '>=3.0.0' + + '@vueuse/shared@10.11.1': + resolution: {integrity: sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA==} + + '@vueuse/shared@9.13.0': + resolution: {integrity: sha512-UrnhU+Cnufu4S6JLCPZnkWh0WwZGUp72ktOF2DFptMlOs3TOdVv8xJN53zhHGARmVOsz5KqOls09+J1NR6sBKw==} + + '@zougt/some-loader-utils@1.4.3': + resolution: {integrity: sha512-0FsoqSTQ+qOyp6x5Q6LZQ7xVwquEgLYiIStG3L8p0Q2GsGGYKDkOZ0mIpMt67aNdr8XLsbxXjzTl/iHtTz5zcA==} + engines: {node: '>= 10.13.0'} + hasBin: true + + '@zxcvbn-ts/core@3.0.4': + resolution: {integrity: sha512-aQeiT0F09FuJaAqNrxynlAwZ2mW/1MdXakKWNmGM1Qp/VaY6CnB/GfnMS2T8gB2231Esp1/maCWd8vTG4OuShw==} + + JSONStream@1.3.5: + resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==} + hasBin: true + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.12.1: + resolution: {integrity: sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + + ajv@8.17.1: + resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} + + animate.css@4.1.1: + resolution: {integrity: sha512-+mRmCTv6SbCmtYJCN4faJMNFVNN5EuCTTprDTAo7YzIGji2KADmakjVA3+8mVDkZ2Bf09vayB35lSQIex2+QaQ==} + + ansi-align@3.0.1: + resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==} + + ansi-escapes@4.3.2: + resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} + engines: {node: '>=8'} + + ansi-escapes@7.0.0: + resolution: {integrity: sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==} + engines: {node: '>=18'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.0.1: + resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} + engines: {node: '>=12'} + + ansi-styles@3.2.1: + resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} + engines: {node: '>=4'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@6.2.1: + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + engines: {node: '>=12'} + + any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + arg@5.0.2: + resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + array-ify@1.0.0: + resolution: {integrity: sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==} + + array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + + astral-regex@2.0.0: + resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} + engines: {node: '>=8'} + + async-validator@4.2.5: + resolution: {integrity: sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + autoprefixer@10.4.20: + resolution: {integrity: sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==} + engines: {node: ^10 || ^12 || >=14} + hasBin: true + peerDependencies: + postcss: ^8.1.0 + + axios@1.7.4: + resolution: {integrity: sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + balanced-match@2.0.0: + resolution: {integrity: sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==} + + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + + boolbase@1.0.0: + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + + boxen@7.1.1: + resolution: {integrity: sha512-2hCgjEmP8YLWQ130n2FerGv7rYpfBmnmp9Uy2Le1vge6X3gZIfSmEzP5QTDElFxcvVcXlEn8Aq6MU/PZygIOog==} + engines: {node: '>=14.16'} + + brace-expansion@1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + + brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browserslist@4.23.3: + resolution: {integrity: sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + bundle-import@0.0.1: + resolution: {integrity: sha512-L0z0iPX7t7ff5eZsK7oMEH+Ly2lzJczFKPHwrta6X8SF64a20R3wOrAOYK1MzHZVaWWugg9qlSTVfVwqvQJ2dw==} + + c12@1.11.1: + resolution: {integrity: sha512-KDU0TvSvVdaYcQKQ6iPHATGz/7p/KiVjPg4vQrB6Jg/wX9R0yl5RZxWm9IoZqaIHD2+6PZd81+KMGwRr/lRIUg==} + peerDependencies: + magicast: ^0.3.4 + peerDependenciesMeta: + magicast: + optional: true + + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + + call-bind@1.0.7: + resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==} + engines: {node: '>= 0.4'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + camelcase-css@2.0.1: + resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} + engines: {node: '>= 6'} + + camelcase@6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} + + camelcase@7.0.1: + resolution: {integrity: sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==} + engines: {node: '>=14.16'} + + caniuse-api@3.0.0: + resolution: {integrity: sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==} + + caniuse-lite@1.0.30001651: + resolution: {integrity: sha512-9Cf+Xv1jJNe1xPZLGuUXLNkE1BoDkqRqYyFJ9TDYSqhduqA4hu4oR9HluGoWYQC/aj8WHjsGVV+bwkh0+tegRg==} + + chalk@2.4.2: + resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} + engines: {node: '>=4'} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + chalk@5.3.0: + resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + + chownr@2.0.0: + resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} + engines: {node: '>=10'} + + citty@0.1.6: + resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==} + + cli-boxes@3.0.0: + resolution: {integrity: sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==} + engines: {node: '>=10'} + + cli-cursor@5.0.0: + resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} + engines: {node: '>=18'} + + cli-truncate@4.0.0: + resolution: {integrity: sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==} + engines: {node: '>=18'} + + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + color-convert@1.9.3: + resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.3: + resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + color-string@1.9.1: + resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} + + color@4.2.3: + resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} + engines: {node: '>=12.5.0'} + + colord@2.9.3: + resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==} + + colorette@2.0.20: + resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + commander@12.1.0: + resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} + engines: {node: '>=18'} + + commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + + commander@7.2.0: + resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} + engines: {node: '>= 10'} + + commander@8.3.0: + resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} + engines: {node: '>= 12'} + + compare-func@2.0.0: + resolution: {integrity: sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==} + + compatx@0.1.8: + resolution: {integrity: sha512-jcbsEAR81Bt5s1qOFymBufmCbXCXbk0Ql+K5ouj6gCyx2yHlu6AgmGIi9HxfKixpUDO5bCFJUHQ5uM6ecbTebw==} + + computeds@0.0.1: + resolution: {integrity: sha512-7CEBgcMjVmitjYo5q8JTJVra6X5mQ20uTThdK+0kR7UEaDrAWEQcRiBtWJzga4eRpP6afNwwLsX2SET2JhVB1Q==} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + confbox@0.1.7: + resolution: {integrity: sha512-uJcB/FKZtBMCJpK8MQji6bJHgu1tixKPxRLeGkNzBoOZzpnZUJm0jm2/sBDWcuBx1dYgxV4JU+g5hmNxCyAmdA==} + + consola@3.2.3: + resolution: {integrity: sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ==} + engines: {node: ^14.18.0 || >=16.10.0} + + conventional-changelog-angular@7.0.0: + resolution: {integrity: sha512-ROjNchA9LgfNMTTFSIWPzebCwOGFdgkEq45EnvvrmSLvCtAw0HSmrCs7/ty+wAeYUZyNay0YMUNYFTRL72PkBQ==} + engines: {node: '>=16'} + + conventional-changelog-conventionalcommits@7.0.2: + resolution: {integrity: sha512-NKXYmMR/Hr1DevQegFB4MwfM5Vv0m4UIxKZTTYuD98lpTknaZlSRrDOG4X7wIXpGkfsYxZTghUN+Qq+T0YQI7w==} + engines: {node: '>=16'} + + conventional-commits-parser@5.0.0: + resolution: {integrity: sha512-ZPMl0ZJbw74iS9LuX9YIAiW8pfM5p3yh2o/NbXHbkFuZzY5jvdi5jFycEOkmBW5H5I7nA+D6f3UcsCLP2vvSEA==} + engines: {node: '>=16'} + hasBin: true + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cosmiconfig-typescript-loader@5.0.0: + resolution: {integrity: sha512-+8cK7jRAReYkMwMiG+bxhcNKiHJDM6bR9FD/nGBXOWdMLuYawjF5cGrtLilJ+LGd3ZjCXnJjR5DkfWPoIVlqJA==} + engines: {node: '>=v16'} + peerDependencies: + '@types/node': '*' + cosmiconfig: '>=8.2' + typescript: '>=4' + + cosmiconfig@9.0.0: + resolution: {integrity: sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==} + engines: {node: '>=14'} + peerDependencies: + typescript: '>=4.9.5' + peerDependenciesMeta: + typescript: + optional: true + + cropperjs@1.6.2: + resolution: {integrity: sha512-nhymn9GdnV3CqiEHJVai54TULFAE3VshJTXSqSJKa8yXAKyBKDWdhHarnlIPrshJ0WMFTGuFvG02YjLXfPiuOA==} + + cross-spawn@7.0.3: + resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} + engines: {node: '>= 8'} + + crypto-js@4.2.0: + resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==} + + css-declaration-sorter@6.4.1: + resolution: {integrity: sha512-rtdthzxKuyq6IzqX6jEcIzQF/YqccluefyCYheovBOLhFT/drQA9zj/UbRAa9J7C0o6EG6u3E6g+vKkay7/k3g==} + engines: {node: ^10 || ^12 || >=14} + peerDependencies: + postcss: ^8.0.9 + + css-declaration-sorter@7.2.0: + resolution: {integrity: sha512-h70rUM+3PNFuaBDTLe8wF/cdWu+dOZmb7pJt8Z2sedYbAcQVQV/tEchueg3GWxwqS0cxtbxmaHEdkNACqcvsow==} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.0.9 + + css-functions-list@3.2.2: + resolution: {integrity: sha512-c+N0v6wbKVxTu5gOBBFkr9BEdBWaqqjQeiJ8QvSRIJOf+UxlJh930m8e6/WNeODIK0mYLFkoONrnj16i2EcvfQ==} + engines: {node: '>=12 || >=16'} + + css-select@4.3.0: + resolution: {integrity: sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==} + + css-select@5.1.0: + resolution: {integrity: sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==} + + css-tree@1.1.3: + resolution: {integrity: sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==} + engines: {node: '>=8.0.0'} + + css-tree@2.2.1: + resolution: {integrity: sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} + + css-tree@2.3.1: + resolution: {integrity: sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + + css-what@6.1.0: + resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==} + engines: {node: '>= 6'} + + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + + cssnano-preset-default@5.2.14: + resolution: {integrity: sha512-t0SFesj/ZV2OTylqQVOrFgEh5uanxbO6ZAdeCrNsUQ6fVuXwYTxJPNAGvGTxHbD68ldIJNec7PyYZDBrfDQ+6A==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + cssnano-preset-default@7.0.5: + resolution: {integrity: sha512-Jbzja0xaKwc5JzxPQoc+fotKpYtWEu4wQLMQe29CM0FjjdRjA4omvbGHl2DTGgARKxSTpPssBsok+ixv8uTBqw==} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.31 + + cssnano-preset-lite@2.1.3: + resolution: {integrity: sha512-samvnCll/DUVZu0Qc+JH36nt7dlaOT7WjOgg8SbLJ78sp51JZ12s2hyerxrarjPBG4O53rErUtOY2IYLYgBGEQ==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + cssnano-utils@3.1.0: + resolution: {integrity: sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + cssnano-utils@5.0.0: + resolution: {integrity: sha512-Uij0Xdxc24L6SirFr25MlwC2rCFX6scyUmuKpzI+JQ7cyqDEwD42fJ0xfB3yLfOnRDU5LKGgjQ9FA6LYh76GWQ==} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.31 + + cssnano@5.1.15: + resolution: {integrity: sha512-j+BKgDcLDQA+eDifLx0EO4XSA56b7uut3BQFH+wbSaSTuGLuiyTa/wbRYthUXX8LC9mLg+WWKe8h+qJuwTAbHw==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + cssnano@7.0.5: + resolution: {integrity: sha512-Aq0vqBLtpTT5Yxj+hLlLfNPFuRQCDIjx5JQAhhaedQKLNDvDGeVziF24PS+S1f0Z5KCxWvw0QVI3VNHNBITxVQ==} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.31 + + csso@4.2.0: + resolution: {integrity: sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==} + engines: {node: '>=8.0.0'} + + csso@5.0.5: + resolution: {integrity: sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} + + csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + + dargs@8.1.0: + resolution: {integrity: sha512-wAV9QHOsNbwnWdNW2FYvE1P56wtgSbM+3SZcdGiWQILwVjACCXDCI3Ai8QlCjMDB8YK5zySiXZYBiwGmNY3lnw==} + engines: {node: '>=12'} + + dayjs@1.11.12: + resolution: {integrity: sha512-Rt2g+nTbLlDWZTwwrIXjy9MeiZmSDI375FvZs72ngxx8PDC6YXOeR3q5LAuPzjZQxhiWdRKac7RKV+YyQYfYIg==} + + de-indent@1.0.2: + resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==} + + debug@4.3.6: + resolution: {integrity: sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + + define-lazy-prop@2.0.0: + resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==} + engines: {node: '>=8'} + + defu@6.1.4: + resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + destr@2.0.3: + resolution: {integrity: sha512-2N3BOUU4gYMpTP24s5rF5iP7BDr7uNTCs4ozw3kf/eKfvWSIu93GEBi5m427YoyJoeOzQ5smuu4nNAPGb8idSQ==} + + didyoumean@1.2.2: + resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} + + dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + + dlv@1.1.3: + resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} + + dom-serializer@1.4.1: + resolution: {integrity: sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==} + + dom-serializer@2.0.0: + resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} + + domelementtype@2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + + domhandler@4.3.1: + resolution: {integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==} + engines: {node: '>= 4'} + + domhandler@5.0.3: + resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} + engines: {node: '>= 4'} + + domutils@2.8.0: + resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==} + + domutils@3.1.0: + resolution: {integrity: sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==} + + dot-prop@5.3.0: + resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==} + engines: {node: '>=8'} + + dotenv@16.4.5: + resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==} + engines: {node: '>=12'} + + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + echarts@5.5.1: + resolution: {integrity: sha512-Fce8upazaAXUVUVsjgV6mBnGuqgO+JNDlcgF79Dksy4+wgGpQB2lmYoO4TSweFg/mZITdpGHomw/cNBJZj1icA==} + + electron-to-chromium@1.5.11: + resolution: {integrity: sha512-R1CccCDYqndR25CaXFd6hp/u9RaaMcftMkphmvuepXr5b1vfLkRml6aWVeBhXJ7rbevHkKEMJtz8XqPf7ffmew==} + + element-plus@2.8.0: + resolution: {integrity: sha512-7ngapVlVlQAjocVqD4MUKvKXlBneT9DSDk2mmBOSLRFWNm/HLDT15ozmsvUBfy18sajnyUeSIHTtINE8gfrGMg==} + peerDependencies: + vue: ^3.2.0 + + emoji-regex@10.3.0: + resolution: {integrity: sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + entities@2.2.0: + resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==} + + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + + env-paths@2.2.1: + resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} + engines: {node: '>=6'} + + environment@1.1.0: + resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} + engines: {node: '>=18'} + + error-ex@1.3.2: + resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} + + es-define-property@1.0.0: + resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-module-lexer@0.4.1: + resolution: {integrity: sha512-ooYciCUtfw6/d2w56UVeqHPcoCFAiJdz5XOkYpv/Txl1HMUozpXjz/2RIQgqwKdXNDPSF1W7mJCFse3G+HDyAA==} + + esbuild@0.19.12: + resolution: {integrity: sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==} + engines: {node: '>=12'} + hasBin: true + + esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} + engines: {node: '>=12'} + hasBin: true + + escalade@3.1.2: + resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==} + engines: {node: '>=6'} + + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + + escape-string-regexp@1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + escape-string-regexp@5.0.0: + resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} + engines: {node: '>=12'} + + escodegen@2.1.0: + resolution: {integrity: sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==} + engines: {node: '>=6.0'} + hasBin: true + + eslint-config-prettier@9.1.0: + resolution: {integrity: sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==} + hasBin: true + peerDependencies: + eslint: '>=7.0.0' + + eslint-define-config@2.1.0: + resolution: {integrity: sha512-QUp6pM9pjKEVannNAbSJNeRuYwW3LshejfyBBpjeMGaJjaDUpVps4C6KVR8R7dWZnD3i0synmrE36znjTkJvdQ==} + engines: {node: '>=18.0.0', npm: '>=9.0.0', pnpm: '>=8.6.0'} + + eslint-plugin-prettier@5.2.1: + resolution: {integrity: sha512-gH3iR3g4JfF+yYPaJYkN7jEl9QbweL/YfkoRlNnuIEHEz1vHVlCmWOS+eGGiRuzHQXdJFCOTxRgvju9b8VUmrw==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + '@types/eslint': '>=8.0.0' + eslint: '>=8.0.0' + eslint-config-prettier: '*' + prettier: '>=3.0.0' + peerDependenciesMeta: + '@types/eslint': + optional: true + eslint-config-prettier: + optional: true + + eslint-plugin-vue@9.27.0: + resolution: {integrity: sha512-5Dw3yxEyuBSXTzT5/Ge1X5kIkRTQ3nvBn/VwPwInNiZBSJOO/timWMUaflONnFBzU6NhB68lxnCda7ULV5N7LA==} + engines: {node: ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.2.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 + + eslint-scope@7.2.2: + resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-scope@8.0.2: + resolution: {integrity: sha512-6E4xmrTw5wtxnLA5wYL3WDfhZ/1bUBGOXV0zQvVRDOtrR8D0p6W7fs3JweNYhwRYeGvd/1CKX2se0/2s7Q/nJA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@4.0.0: + resolution: {integrity: sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint@9.9.0: + resolution: {integrity: sha512-JfiKJrbx0506OEerjK2Y1QlldtBxkAlLxT5OEcRF8uaQ86noDe2k31Vw9rnSWv+MXZHj7OOUV/dA0AhdLFcyvA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@10.1.0: + resolution: {integrity: sha512-M1M6CpiE6ffoigIOWYO9UDP8TMUw9kqb21tf+08IgDYjCsOvCuDt4jQcZmoYxx+w7zlKw9/N0KXfto+I8/FrXA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + espree@9.6.1: + resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + + esquery@1.6.0: + resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + eventemitter3@5.0.1: + resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} + + execa@8.0.1: + resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} + engines: {node: '>=16.17'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-diff@1.3.0: + resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} + + fast-glob@3.3.2: + resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} + engines: {node: '>=8.6.0'} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fast-uri@3.0.1: + resolution: {integrity: sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw==} + + fastest-levenshtein@1.0.16: + resolution: {integrity: sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==} + engines: {node: '>= 4.9.1'} + + fastq@1.17.1: + resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} + + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + + file-entry-cache@9.0.0: + resolution: {integrity: sha512-6MgEugi8p2tiUhqO7GnPsmbCCzj0YRCwwaTbpGRyKZesjRSzkqkAE9fPp7V2yMs5hwfgbQLgdvSSkGNg1s5Uvw==} + engines: {node: '>=18'} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + find-up@7.0.0: + resolution: {integrity: sha512-YyZM99iHrqLKjmt4LJDj58KI+fYyufRLBSYcqycxf//KpBk9FoewoGX0450m9nB44qrZnovzC2oeP5hUibxc/g==} + engines: {node: '>=18'} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flat-cache@5.0.0: + resolution: {integrity: sha512-JrqFmyUl2PnPi1OvLyTVHnQvwQ0S+e6lGSwu8OkAZlSaNIZciTY2H/cOOROxsBA1m/LZNHDsqAgDZt6akWcjsQ==} + engines: {node: '>=18'} + + flatted@3.3.1: + resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} + + follow-redirects@1.15.6: + resolution: {integrity: sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + foreground-child@3.3.0: + resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} + engines: {node: '>=14'} + + form-data@4.0.0: + resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} + engines: {node: '>= 6'} + + fraction.js@4.3.7: + resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} + + framesync@6.1.2: + resolution: {integrity: sha512-jBTqhX6KaQVDyus8muwZbBeGGP0XgujBRbQ7gM7BRdS3CadCZIHiawyzYLnafYcvZIh5j8WE7cxZKFn7dXhu9g==} + + fs-extra@10.1.0: + resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} + engines: {node: '>=12'} + + fs-extra@11.2.0: + resolution: {integrity: sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==} + engines: {node: '>=14.14'} + + fs-minipass@2.1.0: + resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} + engines: {node: '>= 8'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-east-asian-width@1.2.0: + resolution: {integrity: sha512-2nk+7SIVb14QrgXFHcm84tD4bKQz0RxPuMT8Ag5KPOq7J5fEmAg0UbXdTOSHqNuHSU28k55qnceesxXRZGzKWA==} + engines: {node: '>=18'} + + get-intrinsic@1.2.4: + resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} + engines: {node: '>= 0.4'} + + get-stream@8.0.1: + resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} + engines: {node: '>=16'} + + get-tsconfig@4.7.6: + resolution: {integrity: sha512-ZAqrLlu18NbDdRaHq+AKXzAmqIUPswPWKUchfytdAjiRFnCe5ojG2bstg6mRiZabkKfCoL/e98pbBELIV/YCeA==} + + giget@1.2.3: + resolution: {integrity: sha512-8EHPljDvs7qKykr6uw8b+lqLiUc/vUg+KVTI0uND4s63TdsZM2Xus3mflvF0DDG9SiM4RlCkFGL+7aAjRmV7KA==} + hasBin: true + + git-raw-commits@4.0.0: + resolution: {integrity: sha512-ICsMM1Wk8xSGMowkOmPrzo2Fgmfo4bMHLNX6ytHjajRJUqvHOw/TFapQ+QG75c3X/tTDDhOSRPGC52dDbNM8FQ==} + engines: {node: '>=16'} + hasBin: true + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + glob@10.4.5: + resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + hasBin: true + + global-directory@4.0.1: + resolution: {integrity: sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==} + engines: {node: '>=18'} + + global-modules@2.0.0: + resolution: {integrity: sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==} + engines: {node: '>=6'} + + global-prefix@3.0.0: + resolution: {integrity: sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==} + engines: {node: '>=6'} + + globals@11.12.0: + resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} + engines: {node: '>=4'} + + globals@13.24.0: + resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} + engines: {node: '>=8'} + + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + + globby@11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} + + globby@14.0.2: + resolution: {integrity: sha512-s3Fq41ZVh7vbbe2PN3nrW7yC7U7MFVc5c98/iTl9c2GawNMKx/J648KQRW6WKkuU8GIbbh2IXfIRQjOZnXcTnw==} + engines: {node: '>=18'} + + globjoin@0.1.4: + resolution: {integrity: sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==} + + gopd@1.0.1: + resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + gradient-string@2.0.2: + resolution: {integrity: sha512-rEDCuqUQ4tbD78TpzsMtt5OIf0cBCSDWSJtUDaF6JsAh+k0v9r++NzxNEG87oDZx9ZwGhD8DaezR2L/yrw0Jdw==} + engines: {node: '>=10'} + + graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + + has-flag@3.0.0: + resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} + engines: {node: '>=4'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + + has-proto@1.0.3: + resolution: {integrity: sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==} + engines: {node: '>= 0.4'} + + has-symbols@1.0.3: + resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} + engines: {node: '>= 0.4'} + + hash-sum@2.0.0: + resolution: {integrity: sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg==} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + he@1.2.0: + resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} + hasBin: true + + hey-listen@1.0.8: + resolution: {integrity: sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==} + + hookable@5.5.3: + resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} + + html-tags@3.3.1: + resolution: {integrity: sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==} + engines: {node: '>=8'} + + htmlparser2@8.0.2: + resolution: {integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==} + + human-signals@5.0.0: + resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} + engines: {node: '>=16.17.0'} + + husky@9.1.4: + resolution: {integrity: sha512-bho94YyReb4JV7LYWRWxZ/xr6TtOTt8cMfmQ39MQYJ7f/YE268s3GdghGwi+y4zAeqewE5zYLvuhV0M0ijsDEA==} + engines: {node: '>=18'} + hasBin: true + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + immediate@3.0.6: + resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==} + + immutable@4.3.7: + resolution: {integrity: sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==} + + import-fresh@3.3.0: + resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} + engines: {node: '>=6'} + + import-from-string@0.0.4: + resolution: {integrity: sha512-ZmtWHOGv55OEFb3HxfQH4L4vAR7g3HUm82N5LmvXugiXlaZ1j/epItoUDjQ+gJ+MjNl+apczmCnqGFq8q2CM6w==} + + import-meta-resolve@3.1.1: + resolution: {integrity: sha512-qeywsE/KC3w9Fd2ORrRDUw6nS/nLwZpXgfrOc2IILvZYnCaEMd+D56Vfg9k4G29gIeVi3XKql1RQatME8iYsiw==} + + import-meta-resolve@4.1.0: + resolution: {integrity: sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + inherits@2.0.3: + resolution: {integrity: sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==} + + ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + + ini@4.1.1: + resolution: {integrity: sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + + is-arrayish@0.3.2: + resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} + + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + + is-core-module@2.15.0: + resolution: {integrity: sha512-Dd+Lb2/zvk9SKy1TGCt1wFJFo/MWBPMX5x7KcvLajWTGuomczdQX61PvY5yK6SVACwpoexWo81IfFyoKY2QnTA==} + engines: {node: '>= 0.4'} + + is-docker@2.2.1: + resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} + engines: {node: '>=8'} + hasBin: true + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-fullwidth-code-point@4.0.0: + resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==} + engines: {node: '>=12'} + + is-fullwidth-code-point@5.0.0: + resolution: {integrity: sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==} + engines: {node: '>=18'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-obj@2.0.0: + resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==} + engines: {node: '>=8'} + + is-path-inside@3.0.3: + resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} + engines: {node: '>=8'} + + is-plain-object@5.0.0: + resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==} + engines: {node: '>=0.10.0'} + + is-reference@3.0.2: + resolution: {integrity: sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==} + + is-stream@3.0.0: + resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + is-text-path@2.0.0: + resolution: {integrity: sha512-+oDTluR6WEjdXEJMnC2z6A4FRwFoYuvShVVEGsS7ewc0UTi2QtAKMDJuL4BDEVt+5T7MjFo12RP8ghOM75oKJw==} + engines: {node: '>=8'} + + is-wsl@2.2.0: + resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} + engines: {node: '>=8'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + + jiti@1.21.6: + resolution: {integrity: sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==} + hasBin: true + + js-cookie@3.0.5: + resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==} + engines: {node: '>=14'} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-tokens@9.0.0: + resolution: {integrity: sha512-WriZw1luRMlmV3LGJaR6QOJjWwgLUTf89OwT2lUOyjX2dJGBwgmIkbcz+7WFZjrZM635JOIR517++e/67CP9dQ==} + + js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + + jsesc@2.5.2: + resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} + engines: {node: '>=4'} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + jsonc-eslint-parser@2.4.0: + resolution: {integrity: sha512-WYDyuc/uFcGp6YtM2H0uKmUwieOuzeE/5YocFJLnLfclZ4inf3mRn8ZVy1s7Hxji7Jxm6Ss8gqpexD/GlKoGgg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + jsonfile@6.1.0: + resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} + + jsonparse@1.3.1: + resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==} + engines: {'0': node >= 0.2.0} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + kind-of@6.0.3: + resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} + engines: {node: '>=0.10.0'} + + klona@2.0.6: + resolution: {integrity: sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==} + engines: {node: '>= 8'} + + knitwork@1.1.0: + resolution: {integrity: sha512-oHnmiBUVHz1V+URE77PNot2lv3QiYU2zQf1JjOVkMt3YDKGbu8NAFr+c4mcNOhdsGrB/VpVbRwPwhiXrPhxQbw==} + + known-css-properties@0.34.0: + resolution: {integrity: sha512-tBECoUqNFbyAY4RrbqsBQqDFpGXAEbdD5QKr8kACx3+rnArmuuR22nKQWKazvp07N9yjTyDZaw/20UIH8tL9DQ==} + + kolorist@1.8.0: + resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lie@3.1.1: + resolution: {integrity: sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==} + + lilconfig@2.1.0: + resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} + engines: {node: '>=10'} + + lilconfig@3.1.2: + resolution: {integrity: sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==} + engines: {node: '>=14'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + lint-staged@15.2.9: + resolution: {integrity: sha512-BZAt8Lk3sEnxw7tfxM7jeZlPRuT4M68O0/CwZhhaw6eeWu0Lz5eERE3m386InivXB64fp/mDID452h48tvKlRQ==} + engines: {node: '>=18.12.0'} + hasBin: true + + listr2@8.2.4: + resolution: {integrity: sha512-opevsywziHd3zHCVQGAj8zu+Z3yHNkkoYhWIGnq54RrCVwLz0MozotJEDnKsIBLvkfLGN6BLOyAeRrYI0pKA4g==} + engines: {node: '>=18.0.0'} + + local-pkg@0.5.0: + resolution: {integrity: sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==} + engines: {node: '>=14'} + + localforage@1.10.0: + resolution: {integrity: sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + locate-path@7.2.0: + resolution: {integrity: sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + lodash-es@4.17.21: + resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} + + lodash-unified@1.0.3: + resolution: {integrity: sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==} + peerDependencies: + '@types/lodash-es': '*' + lodash: '*' + lodash-es: '*' + + lodash.camelcase@4.3.0: + resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} + + lodash.isplainobject@4.0.6: + resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + + lodash.kebabcase@4.1.1: + resolution: {integrity: sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==} + + lodash.memoize@4.1.2: + resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + lodash.mergewith@4.6.2: + resolution: {integrity: sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==} + + lodash.snakecase@4.1.1: + resolution: {integrity: sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==} + + lodash.startcase@4.4.0: + resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==} + + lodash.truncate@4.4.2: + resolution: {integrity: sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==} + + lodash.uniq@4.5.0: + resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==} + + lodash.upperfirst@4.3.1: + resolution: {integrity: sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg==} + + lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + + log-update@6.1.0: + resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==} + engines: {node: '>=18'} + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + magic-string@0.25.9: + resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==} + + magic-string@0.30.11: + resolution: {integrity: sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==} + + mathml-tag-names@2.1.3: + resolution: {integrity: sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==} + + mdn-data@2.0.14: + resolution: {integrity: sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==} + + mdn-data@2.0.28: + resolution: {integrity: sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==} + + mdn-data@2.0.30: + resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==} + + memoize-one@6.0.0: + resolution: {integrity: sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==} + + meow@12.1.1: + resolution: {integrity: sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==} + engines: {node: '>=16.10'} + + meow@13.2.0: + resolution: {integrity: sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==} + engines: {node: '>=18'} + + merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromatch@4.0.7: + resolution: {integrity: sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==} + engines: {node: '>=8.6'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + mimic-fn@4.0.0: + resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} + engines: {node: '>=12'} + + mimic-function@5.0.1: + resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} + engines: {node: '>=18'} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + minipass@3.3.6: + resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} + engines: {node: '>=8'} + + minipass@5.0.0: + resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} + engines: {node: '>=8'} + + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + + minizlib@2.1.2: + resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} + engines: {node: '>= 8'} + + mitt@3.0.1: + resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} + + mkdirp@1.0.4: + resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} + engines: {node: '>=10'} + hasBin: true + + mlly@1.7.1: + resolution: {integrity: sha512-rrVRZRELyQzrIUAVMHxP97kv+G786pHmOKzuFII8zDYahFBS7qnHh2AlYSl1GAHhaMPCz6/oHjVMcfFYgFYHgA==} + + mri@1.2.0: + resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} + engines: {node: '>=4'} + + ms@2.1.2: + resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + + muggle-string@0.4.1: + resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} + + mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + + nanoid@3.3.7: + resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + node-fetch-native@1.6.4: + resolution: {integrity: sha512-IhOigYzAKHd244OC0JIMIUrjzctirCmPkaIfhDeGcEETWof5zKYUW7e7MYvChGWh/4CJeXEgsRyGzuF334rOOQ==} + + node-releases@2.0.18: + resolution: {integrity: sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + normalize-range@0.1.2: + resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} + engines: {node: '>=0.10.0'} + + normalize-url@6.1.0: + resolution: {integrity: sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==} + engines: {node: '>=10'} + + normalize-wheel-es@1.2.0: + resolution: {integrity: sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==} + + npm-run-path@4.0.1: + resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} + engines: {node: '>=8'} + + npm-run-path@5.3.0: + resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + nprogress@0.2.0: + resolution: {integrity: sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==} + + nth-check@2.1.1: + resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + + nypm@0.3.9: + resolution: {integrity: sha512-BI2SdqqTHg2d4wJh8P9A1W+bslg33vOE9IZDY6eR2QC+Pu1iNBVZUqczrd43rJb+fMzHU7ltAYKsEFY/kHMFcw==} + engines: {node: ^14.16.0 || >=16.10.0} + hasBin: true + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-hash@3.0.0: + resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} + engines: {node: '>= 6'} + + object-inspect@1.13.2: + resolution: {integrity: sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==} + engines: {node: '>= 0.4'} + + ohash@1.1.3: + resolution: {integrity: sha512-zuHHiGTYTA1sYJ/wZN+t5HKZaH23i4yI1HMwbuXm24Nid7Dv0KcuRlKoNKS9UNfAVSBlnGLcuQrnOKWOZoEGaw==} + + onetime@6.0.0: + resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} + engines: {node: '>=12'} + + onetime@7.0.0: + resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} + engines: {node: '>=18'} + + open@8.4.2: + resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==} + engines: {node: '>=12'} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-limit@4.0.0: + resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + p-locate@6.0.0: + resolution: {integrity: sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + package-json-from-dist@1.0.0: + resolution: {integrity: sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + + path-browserify@1.0.1: + resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-exists@5.0.0: + resolution: {integrity: sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-key@4.0.0: + resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} + engines: {node: '>=12'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + + path-to-regexp@6.2.2: + resolution: {integrity: sha512-GQX3SSMokngb36+whdpRXE+3f9V8UzyAorlYvOGx87ufGHehNTn5lCxrKtLyZ4Yl/wEKnNnr98ZzOwwDZV5ogw==} + + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + + path-type@5.0.0: + resolution: {integrity: sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==} + engines: {node: '>=12'} + + path@0.12.7: + resolution: {integrity: sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q==} + + pathe@1.1.2: + resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + + perfect-debounce@1.0.0: + resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} + + picocolors@1.0.1: + resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + pidtree@0.6.0: + resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==} + engines: {node: '>=0.10'} + hasBin: true + + pify@2.3.0: + resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} + engines: {node: '>=0.10.0'} + + pinia@2.2.2: + resolution: {integrity: sha512-ja2XqFWZC36mupU4z1ZzxeTApV7DOw44cV4dhQ9sGwun+N89v/XP7+j7q6TanS1u1tdbK4r+1BUx7heMaIdagA==} + peerDependencies: + '@vue/composition-api': ^1.4.0 + typescript: '>=4.4.4' + vue: ^2.6.14 || ^3.3.0 + peerDependenciesMeta: + '@vue/composition-api': + optional: true + typescript: + optional: true + + pinyin-pro@3.24.2: + resolution: {integrity: sha512-5tPyLhxT4CZ9dWqQRqm3X5ADdS18Sb2w0ranNBgr6jCrqO4O8gtfuyqG7Y6+1Mre+0n2VlhKDz+3P5oqSLrkOw==} + + pirates@4.0.6: + resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} + engines: {node: '>= 6'} + + pkg-types@1.1.3: + resolution: {integrity: sha512-+JrgthZG6m3ckicaOB74TwQ+tBWsFl3qVQg7mN8ulwSOElJ7gBhKzj2VkCPnZ4NlF6kEquYU+RIYNVAvzd54UA==} + + plus-pro-components@0.1.17: + resolution: {integrity: sha512-a4UaLJHAf+e1aph3iAjNceVFoyaPxmrX6gS7mWmTg8rfou3sPX7qL0IARKuj8P8oQih65AJRunIfaT+s9IZLvw==} + peerDependencies: + element-plus: ^2.3.4 + vue: ^3.2.0 + + popmotion@11.0.5: + resolution: {integrity: sha512-la8gPM1WYeFznb/JqF4GiTkRRPZsfaj2+kCxqQgr2MJylMmIKUwBfWW8Wa5fml/8gmtlD5yI01MP1QCZPWmppA==} + + postcss-calc@10.0.2: + resolution: {integrity: sha512-DT/Wwm6fCKgpYVI7ZEWuPJ4az8hiEHtCUeYjZXqU7Ou4QqYh1Df2yCQ7Ca6N7xqKPFkxN3fhf+u9KSoOCJNAjg==} + engines: {node: ^18.12 || ^20.9 || >=22.0} + peerDependencies: + postcss: ^8.4.38 + + postcss-calc@8.2.4: + resolution: {integrity: sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q==} + peerDependencies: + postcss: ^8.2.2 + + postcss-colormin@5.3.1: + resolution: {integrity: sha512-UsWQG0AqTFQmpBegeLLc1+c3jIqBNB0zlDGRWR+dQ3pRKJL1oeMzyqmH3o2PIfn9MBdNrVPWhDbT769LxCTLJQ==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + postcss-colormin@7.0.2: + resolution: {integrity: sha512-YntRXNngcvEvDbEjTdRWGU606eZvB5prmHG4BF0yLmVpamXbpsRJzevyy6MZVyuecgzI2AWAlvFi8DAeCqwpvA==} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-convert-values@5.1.3: + resolution: {integrity: sha512-82pC1xkJZtcJEfiLw6UXnXVXScgtBrjlO5CBmuDQc+dlb88ZYheFsjTn40+zBVi3DkfF7iezO0nJUPLcJK3pvA==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + postcss-convert-values@7.0.3: + resolution: {integrity: sha512-yJhocjCs2SQer0uZ9lXTMOwDowbxvhwFVrZeS6NPEij/XXthl73ggUmfwVvJM+Vaj5gtCKJV1jiUu4IhAUkX/Q==} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-discard-comments@5.1.2: + resolution: {integrity: sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + postcss-discard-comments@7.0.2: + resolution: {integrity: sha512-/Hje9Ls1IYcB9duELO/AyDUJI6aQVY3h5Rj1ziXgaLYCTi1iVBLnjg/TS0D6NszR/kDG6I86OwLmAYe+bvJjiQ==} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-discard-duplicates@5.1.0: + resolution: {integrity: sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + postcss-discard-duplicates@7.0.1: + resolution: {integrity: sha512-oZA+v8Jkpu1ct/xbbrntHRsfLGuzoP+cpt0nJe5ED2FQF8n8bJtn7Bo28jSmBYwqgqnqkuSXJfSUEE7if4nClQ==} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-discard-empty@5.1.1: + resolution: {integrity: sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + postcss-discard-empty@7.0.0: + resolution: {integrity: sha512-e+QzoReTZ8IAwhnSdp/++7gBZ/F+nBq9y6PomfwORfP7q9nBpK5AMP64kOt0bA+lShBFbBDcgpJ3X4etHg4lzA==} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-discard-overridden@5.1.0: + resolution: {integrity: sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + postcss-discard-overridden@7.0.0: + resolution: {integrity: sha512-GmNAzx88u3k2+sBTZrJSDauR0ccpE24omTQCVmaTTZFz1du6AasspjaUPMJ2ud4RslZpoFKyf+6MSPETLojc6w==} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-html@1.7.0: + resolution: {integrity: sha512-MfcMpSUIaR/nNgeVS8AyvyDugXlADjN9AcV7e5rDfrF1wduIAGSkL4q2+wgrZgA3sHVAHLDO9FuauHhZYW2nBw==} + engines: {node: ^12 || >=14} + + postcss-import@15.1.0: + resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} + engines: {node: '>=14.0.0'} + peerDependencies: + postcss: ^8.0.0 + + postcss-import@16.1.0: + resolution: {integrity: sha512-7hsAZ4xGXl4MW+OKEWCnF6T5jqBw80/EE9aXg1r2yyn1RsVEU8EtKXbijEODa+rg7iih4bKf7vlvTGYR4CnPNg==} + engines: {node: '>=18.0.0'} + peerDependencies: + postcss: ^8.0.0 + + postcss-js@4.0.1: + resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==} + engines: {node: ^12 || ^14 || >= 16} + peerDependencies: + postcss: ^8.4.21 + + postcss-load-config@4.0.2: + resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==} + engines: {node: '>= 14'} + peerDependencies: + postcss: '>=8.0.9' + ts-node: '>=9.0.0' + peerDependenciesMeta: + postcss: + optional: true + ts-node: + optional: true + + postcss-media-query-parser@0.2.3: + resolution: {integrity: sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==} + + postcss-merge-longhand@5.1.7: + resolution: {integrity: sha512-YCI9gZB+PLNskrK0BB3/2OzPnGhPkBEwmwhfYk1ilBHYVAZB7/tkTHFBAnCrvBBOmeYyMYw3DMjT55SyxMBzjQ==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + postcss-merge-longhand@7.0.3: + resolution: {integrity: sha512-8waYomFxshdv6M9Em3QRM9MettRLDRcH2JQi2l0Z1KlYD/vhal3gbkeSES0NuACXOlZBB0V/B0AseHZaklzWOA==} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-merge-rules@5.1.4: + resolution: {integrity: sha512-0R2IuYpgU93y9lhVbO/OylTtKMVcHb67zjWIfCiKR9rWL3GUk1677LAqD/BcHizukdZEjT8Ru3oHRoAYoJy44g==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + postcss-merge-rules@7.0.3: + resolution: {integrity: sha512-2eSas2p3voPxNfdI5sQrvIkMaeUHpVc3EezgVs18hz/wRTQAC9U99tp9j3W5Jx9/L3qHkEDvizEx/LdnmumIvQ==} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-minify-font-values@5.1.0: + resolution: {integrity: sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + postcss-minify-font-values@7.0.0: + resolution: {integrity: sha512-2ckkZtgT0zG8SMc5aoNwtm5234eUx1GGFJKf2b1bSp8UflqaeFzR50lid4PfqVI9NtGqJ2J4Y7fwvnP/u1cQog==} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-minify-gradients@5.1.1: + resolution: {integrity: sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + postcss-minify-gradients@7.0.0: + resolution: {integrity: sha512-pdUIIdj/C93ryCHew0UgBnL2DtUS3hfFa5XtERrs4x+hmpMYGhbzo6l/Ir5de41O0GaKVpK1ZbDNXSY6GkXvtg==} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-minify-params@5.1.4: + resolution: {integrity: sha512-+mePA3MgdmVmv6g+30rn57USjOGSAyuxUmkfiWpzalZ8aiBkdPYjXWtHuwJGm1v5Ojy0Z0LaSYhHaLJQB0P8Jw==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + postcss-minify-params@7.0.2: + resolution: {integrity: sha512-nyqVLu4MFl9df32zTsdcLqCFfE/z2+f8GE1KHPxWOAmegSo6lpV2GNy5XQvrzwbLmiU7d+fYay4cwto1oNdAaQ==} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-minify-selectors@5.2.1: + resolution: {integrity: sha512-nPJu7OjZJTsVUmPdm2TcaiohIwxP+v8ha9NehQ2ye9szv4orirRU3SDdtUmKH+10nzn0bAyOXZ0UEr7OpvLehg==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + postcss-minify-selectors@7.0.3: + resolution: {integrity: sha512-SxTgUQSgBk6wEqzQZKEv1xQYIp9UBju6no9q+npohzSdhuSICQdkqmD1UMKkZWItS3olJSJMDDEY9WOJ5oGJew==} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-nested@6.2.0: + resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.2.14 + + postcss-normalize-charset@5.1.0: + resolution: {integrity: sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + postcss-normalize-charset@7.0.0: + resolution: {integrity: sha512-ABisNUXMeZeDNzCQxPxBCkXexvBrUHV+p7/BXOY+ulxkcjUZO0cp8ekGBwvIh2LbCwnWbyMPNJVtBSdyhM2zYQ==} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-normalize-display-values@5.1.0: + resolution: {integrity: sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + postcss-normalize-display-values@7.0.0: + resolution: {integrity: sha512-lnFZzNPeDf5uGMPYgGOw7v0BfB45+irSRz9gHQStdkkhiM0gTfvWkWB5BMxpn0OqgOQuZG/mRlZyJxp0EImr2Q==} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-normalize-positions@5.1.1: + resolution: {integrity: sha512-6UpCb0G4eofTCQLFVuI3EVNZzBNPiIKcA1AKVka+31fTVySphr3VUgAIULBhxZkKgwLImhzMR2Bw1ORK+37INg==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + postcss-normalize-positions@7.0.0: + resolution: {integrity: sha512-I0yt8wX529UKIGs2y/9Ybs2CelSvItfmvg/DBIjTnoUSrPxSV7Z0yZ8ShSVtKNaV/wAY+m7bgtyVQLhB00A1NQ==} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-normalize-repeat-style@5.1.1: + resolution: {integrity: sha512-mFpLspGWkQtBcWIRFLmewo8aC3ImN2i/J3v8YCFUwDnPu3Xz4rLohDO26lGjwNsQxB3YF0KKRwspGzE2JEuS0g==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + postcss-normalize-repeat-style@7.0.0: + resolution: {integrity: sha512-o3uSGYH+2q30ieM3ppu9GTjSXIzOrRdCUn8UOMGNw7Af61bmurHTWI87hRybrP6xDHvOe5WlAj3XzN6vEO8jLw==} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-normalize-string@5.1.0: + resolution: {integrity: sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + postcss-normalize-string@7.0.0: + resolution: {integrity: sha512-w/qzL212DFVOpMy3UGyxrND+Kb0fvCiBBujiaONIihq7VvtC7bswjWgKQU/w4VcRyDD8gpfqUiBQ4DUOwEJ6Qg==} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-normalize-timing-functions@5.1.0: + resolution: {integrity: sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + postcss-normalize-timing-functions@7.0.0: + resolution: {integrity: sha512-tNgw3YV0LYoRwg43N3lTe3AEWZ66W7Dh7lVEpJbHoKOuHc1sLrzMLMFjP8SNULHaykzsonUEDbKedv8C+7ej6g==} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-normalize-unicode@5.1.1: + resolution: {integrity: sha512-qnCL5jzkNUmKVhZoENp1mJiGNPcsJCs1aaRmURmeJGES23Z/ajaln+EPTD+rBeNkSryI+2WTdW+lwcVdOikrpA==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + postcss-normalize-unicode@7.0.2: + resolution: {integrity: sha512-ztisabK5C/+ZWBdYC+Y9JCkp3M9qBv/XFvDtSw0d/XwfT3UaKeW/YTm/MD/QrPNxuecia46vkfEhewjwcYFjkg==} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-normalize-url@5.1.0: + resolution: {integrity: sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + postcss-normalize-url@7.0.0: + resolution: {integrity: sha512-+d7+PpE+jyPX1hDQZYG+NaFD+Nd2ris6r8fPTBAjE8z/U41n/bib3vze8x7rKs5H1uEw5ppe9IojewouHk0klQ==} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-normalize-whitespace@5.1.1: + resolution: {integrity: sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + postcss-normalize-whitespace@7.0.0: + resolution: {integrity: sha512-37/toN4wwZErqohedXYqWgvcHUGlT8O/m2jVkAfAe9Bd4MzRqlBmXrJRePH0e9Wgnz2X7KymTgTOaaFizQe3AQ==} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-ordered-values@5.1.3: + resolution: {integrity: sha512-9UO79VUhPwEkzbb3RNpqqghc6lcYej1aveQteWY+4POIwlqkYE21HKWaLDF6lWNuqCobEAyTovVhtI32Rbv2RQ==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + postcss-ordered-values@7.0.1: + resolution: {integrity: sha512-irWScWRL6nRzYmBOXReIKch75RRhNS86UPUAxXdmW/l0FcAsg0lvAXQCby/1lymxn/o0gVa6Rv/0f03eJOwHxw==} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-reduce-initial@5.1.2: + resolution: {integrity: sha512-dE/y2XRaqAi6OvjzD22pjTUQ8eOfc6m/natGHgKFBK9DxFmIm69YmaRVQrGgFlEfc1HePIurY0TmDeROK05rIg==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + postcss-reduce-initial@7.0.2: + resolution: {integrity: sha512-pOnu9zqQww7dEKf62Nuju6JgsW2V0KRNBHxeKohU+JkHd/GAH5uvoObqFLqkeB2n20mr6yrlWDvo5UBU5GnkfA==} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-reduce-transforms@5.1.0: + resolution: {integrity: sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + postcss-reduce-transforms@7.0.0: + resolution: {integrity: sha512-pnt1HKKZ07/idH8cpATX/ujMbtOGhUfE+m8gbqwJE05aTaNw8gbo34a2e3if0xc0dlu75sUOiqvwCGY3fzOHew==} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-resolve-nested-selector@0.1.6: + resolution: {integrity: sha512-0sglIs9Wmkzbr8lQwEyIzlDOOC9bGmfVKcJTaxv3vMmd3uo4o4DerC3En0bnmgceeql9BfC8hRkp7cg0fjdVqw==} + + postcss-safe-parser@6.0.0: + resolution: {integrity: sha512-FARHN8pwH+WiS2OPCxJI8FuRJpTVnn6ZNFiqAM2aeW2LwTHWWmWgIyKC6cUo0L8aeKiF/14MNvnpls6R2PBeMQ==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.3.3 + + postcss-safe-parser@7.0.0: + resolution: {integrity: sha512-ovehqRNVCpuFzbXoTb4qLtyzK3xn3t/CUBxOs8LsnQjQrShaB4lKiHoVqY8ANaC0hBMHq5QVWk77rwGklFUDrg==} + engines: {node: '>=18.0'} + peerDependencies: + postcss: ^8.4.31 + + postcss-scss@4.0.9: + resolution: {integrity: sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.4.29 + + postcss-selector-parser@6.1.2: + resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} + engines: {node: '>=4'} + + postcss-sorting@8.0.2: + resolution: {integrity: sha512-M9dkSrmU00t/jK7rF6BZSZauA5MAaBW4i5EnJXspMwt4iqTh/L9j6fgMnbElEOfyRyfLfVbIHj/R52zHzAPe1Q==} + peerDependencies: + postcss: ^8.4.20 + + postcss-svgo@5.1.0: + resolution: {integrity: sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + postcss-svgo@7.0.1: + resolution: {integrity: sha512-0WBUlSL4lhD9rA5k1e5D8EN5wCEyZD6HJk0jIvRxl+FDVOMlJ7DePHYWGGVc5QRqrJ3/06FTXM0bxjmJpmTPSA==} + engines: {node: ^18.12.0 || ^20.9.0 || >= 18} + peerDependencies: + postcss: ^8.4.31 + + postcss-unique-selectors@5.1.1: + resolution: {integrity: sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + postcss-unique-selectors@7.0.2: + resolution: {integrity: sha512-CjSam+7Vf8cflJQsHrMS0P2hmy9u0+n/P001kb5eAszLmhjMqrt/i5AqQuNFihhViwDvEAezqTmXqaYXL2ugMw==} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-value-parser@4.2.0: + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + + postcss@8.4.41: + resolution: {integrity: sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==} + engines: {node: ^10 || ^12 || >=14} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prettier-linter-helpers@1.0.0: + resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==} + engines: {node: '>=6.0.0'} + + prettier@2.8.8: + resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} + engines: {node: '>=10.13.0'} + hasBin: true + + prettier@3.3.3: + resolution: {integrity: sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==} + engines: {node: '>=14'} + hasBin: true + + process@0.11.10: + resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} + engines: {node: '>= 0.6.0'} + + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + qs@6.13.0: + resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==} + engines: {node: '>=0.6'} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + rc9@2.1.2: + resolution: {integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==} + + read-cache@1.0.0: + resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} + + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + + resolve@1.22.8: + resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} + hasBin: true + + responsive-storage@2.2.0: + resolution: {integrity: sha512-94W5Chr2F5kDBT6J+OCOeJguEkSTDc3jPOUQXYmzNG64DCNl5p7hoBDF7bx7u6EXAEcpUKF64OZR4b7Nn8h/Gg==} + + restore-cursor@5.1.0: + resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} + engines: {node: '>=18'} + + reusify@1.0.4: + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rfdc@1.4.1: + resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + + rimraf@5.0.10: + resolution: {integrity: sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==} + hasBin: true + + rollup-plugin-external-globals@0.10.0: + resolution: {integrity: sha512-RXlupZrmn97AaaS5dWnktkjM+Iy+od0E+8L0mUkMIs3iuoUXNJebueQocQKV7Ircd54fSGGmkBaXwNzY05J1yQ==} + peerDependencies: + rollup: ^2.25.0 || ^3.3.0 || ^4.1.4 + + rollup-plugin-visualizer@5.12.0: + resolution: {integrity: sha512-8/NU9jXcHRs7Nnj07PF2o4gjxmm9lXIrZ8r175bT9dK8qoLlvKTwRMArRCMgpMGlq8CTLugRvEmyMeMXIU2pNQ==} + engines: {node: '>=14'} + hasBin: true + peerDependencies: + rollup: 2.x || 3.x || 4.x + peerDependenciesMeta: + rollup: + optional: true + + rollup@4.21.0: + resolution: {integrity: sha512-vo+S/lfA2lMS7rZ2Qoubi6I5hwZwzXeUIctILZLbHI+laNtvhhOIon2S1JksA5UEDQ7l3vberd0fxK44lTYjbQ==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + sass@1.77.8: + resolution: {integrity: sha512-4UHg6prsrycW20fqLGPShtEvo/WyHRVRHwOP4DzkUrObWoWI05QBSfzU71TVB7PFaL104TwNaHpjlWXAZbQiNQ==} + engines: {node: '>=14.0.0'} + hasBin: true + + scule@1.3.0: + resolution: {integrity: sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.6.3: + resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} + engines: {node: '>=10'} + hasBin: true + + set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + side-channel@1.0.6: + resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==} + engines: {node: '>= 0.4'} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + simple-swizzle@0.2.2: + resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} + + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + + slash@5.1.0: + resolution: {integrity: sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==} + engines: {node: '>=14.16'} + + slice-ansi@4.0.0: + resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==} + engines: {node: '>=10'} + + slice-ansi@5.0.0: + resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==} + engines: {node: '>=12'} + + slice-ansi@7.1.0: + resolution: {integrity: sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==} + engines: {node: '>=18'} + + sortablejs@1.15.2: + resolution: {integrity: sha512-FJF5jgdfvoKn1MAKSdGs33bIqLi3LmsgVTliuX6iITj834F+JRQZN90Z93yql8h0K2t0RwDPBmxwlbZfDcxNZA==} + + source-map-js@1.2.0: + resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} + engines: {node: '>=0.10.0'} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + source-map@0.7.4: + resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==} + engines: {node: '>= 8'} + + sourcemap-codec@1.4.8: + resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==} + deprecated: Please use @jridgewell/sourcemap-codec instead + + split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} + engines: {node: '>= 10.x'} + + stable@0.1.8: + resolution: {integrity: sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==} + deprecated: 'Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility' + + std-env@3.7.0: + resolution: {integrity: sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==} + + string-argv@0.3.2: + resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} + engines: {node: '>=0.6.19'} + + string-hash@1.1.3: + resolution: {integrity: sha512-kJUvRUFK49aub+a7T1nNE66EJbZBMnBgoC1UbCZ5n6bsZKBRga4KgBRTMn/pFkeCZSYtNeSyMxPDM0AXWELk2A==} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + + string-width@7.2.0: + resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} + engines: {node: '>=18'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.1.0: + resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} + engines: {node: '>=12'} + + strip-final-newline@3.0.0: + resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} + engines: {node: '>=12'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + strip-literal@2.1.0: + resolution: {integrity: sha512-Op+UycaUt/8FbN/Z2TWPBLge3jWrP3xj10f3fnYxf052bKuS3EKs1ZQcVGjnEMdsNVAM+plXRdmjrZ/KgG3Skw==} + + style-value-types@5.1.2: + resolution: {integrity: sha512-Vs9fNreYF9j6W2VvuDTP7kepALi7sk0xtk2Tu8Yxi9UoajJdEVpNpCov0HsLTqXvNGKX+Uv09pkozVITi1jf3Q==} + + stylehacks@5.1.1: + resolution: {integrity: sha512-sBpcd5Hx7G6seo7b1LkpttvTz7ikD0LlH5RmdcBNb6fFR0Fl7LQwHDFr300q4cwUqi+IYrFGmsIHieMBfnN/Bw==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + stylehacks@7.0.3: + resolution: {integrity: sha512-4DqtecvI/Nd+2BCvW9YEF6lhBN5UM50IJ1R3rnEAhBwbCKf4VehRf+uqvnVArnBayjYD/WtT3g0G/HSRxWfTRg==} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.31 + + stylelint-config-html@1.1.0: + resolution: {integrity: sha512-IZv4IVESjKLumUGi+HWeb7skgO6/g4VMuAYrJdlqQFndgbj6WJAXPhaysvBiXefX79upBdQVumgYcdd17gCpjQ==} + engines: {node: ^12 || >=14} + peerDependencies: + postcss-html: ^1.0.0 + stylelint: '>=14.0.0' + + stylelint-config-recess-order@5.0.1: + resolution: {integrity: sha512-rKbGkoa3h0rINrGln9TFVowvSCLgPJC5O0EuPiqlqWcJMb1lImEtXktcjFCVz+hwtSUiHD3ijJc3vP9muFOgJg==} + peerDependencies: + stylelint: '>=16' + + stylelint-config-recommended-scss@14.1.0: + resolution: {integrity: sha512-bhaMhh1u5dQqSsf6ri2GVWWQW5iUjBYgcHkh7SgDDn92ijoItC/cfO/W+fpXshgTQWhwFkP1rVcewcv4jaftRg==} + engines: {node: '>=18.12.0'} + peerDependencies: + postcss: ^8.3.3 + stylelint: ^16.6.1 + peerDependenciesMeta: + postcss: + optional: true + + stylelint-config-recommended-vue@1.5.0: + resolution: {integrity: sha512-65TAK/clUqkNtkZLcuytoxU0URQYlml+30Nhop7sRkCZ/mtWdXt7T+spPSB3KMKlb+82aEVJ4OrcstyDBdbosg==} + engines: {node: ^12 || >=14} + peerDependencies: + postcss-html: ^1.0.0 + stylelint: '>=14.0.0' + + stylelint-config-recommended@14.0.1: + resolution: {integrity: sha512-bLvc1WOz/14aPImu/cufKAZYfXs/A/owZfSMZ4N+16WGXLoX5lOir53M6odBxvhgmgdxCVnNySJmZKx73T93cg==} + engines: {node: '>=18.12.0'} + peerDependencies: + stylelint: ^16.1.0 + + stylelint-config-standard-scss@13.1.0: + resolution: {integrity: sha512-Eo5w7/XvwGHWkeGLtdm2FZLOMYoZl1omP2/jgFCXyl2x5yNz7/8vv4Tj6slHvMSSUNTaGoam/GAZ0ZhukvalfA==} + engines: {node: '>=18.12.0'} + peerDependencies: + postcss: ^8.3.3 + stylelint: ^16.3.1 + peerDependenciesMeta: + postcss: + optional: true + + stylelint-config-standard@36.0.1: + resolution: {integrity: sha512-8aX8mTzJ6cuO8mmD5yon61CWuIM4UD8Q5aBcWKGSf6kg+EC3uhB+iOywpTK4ca6ZL7B49en8yanOFtUW0qNzyw==} + engines: {node: '>=18.12.0'} + peerDependencies: + stylelint: ^16.1.0 + + stylelint-order@6.0.4: + resolution: {integrity: sha512-0UuKo4+s1hgQ/uAxlYU4h0o0HS4NiQDud0NAUNI0aa8FJdmYHA5ZZTFHiV5FpmE3071e9pZx5j0QpVJW5zOCUA==} + peerDependencies: + stylelint: ^14.0.0 || ^15.0.0 || ^16.0.1 + + stylelint-prettier@5.0.2: + resolution: {integrity: sha512-qJ+BN+1T2ZcKz9WIrv0x+eFGHzSUnXfXd5gL///T6XoJvr3D8/ztzz2fhtmXef7Vb8P33zBXmLTTveByr0nwBw==} + engines: {node: '>=18.12.0'} + peerDependencies: + prettier: '>=3.0.0' + stylelint: '>=16.0.0' + + stylelint-scss@6.5.0: + resolution: {integrity: sha512-yOnYlr71wrTPT3rYyUurgTj6Rw7JUtzsZQsiPEjvs+k/yqoYHdweqpw6XN/ARpxjAuvJpddoMUvV8aAIpvUwTg==} + engines: {node: '>=18.12.0'} + peerDependencies: + stylelint: ^16.0.2 + + stylelint@16.8.2: + resolution: {integrity: sha512-fInKATippQhcSm7AB+T32GpI+626yohrg33GkFT/5jzliUw5qhlwZq2UQQwgl3HsHrf09oeARi0ZwgY/UWEv9A==} + engines: {node: '>=18.12.0'} + hasBin: true + + sucrase@3.35.0: + resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + + supports-color@5.5.0: + resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} + engines: {node: '>=4'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-hyperlinks@3.0.0: + resolution: {integrity: sha512-QBDPHyPQDRTy9ku4URNGY5Lah8PAaXs6tAAwp55sL5WCsSW7GIfdf6W5ixfziW+t7wh3GVvHyHHyQ1ESsoRvaA==} + engines: {node: '>=14.18'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + svg-tags@1.0.0: + resolution: {integrity: sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==} + + svgo@2.8.0: + resolution: {integrity: sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==} + engines: {node: '>=10.13.0'} + hasBin: true + + svgo@3.3.2: + resolution: {integrity: sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==} + engines: {node: '>=14.0.0'} + hasBin: true + + synckit@0.9.1: + resolution: {integrity: sha512-7gr8p9TQP6RAHusBOSLs46F4564ZrjV8xFmw5zCmgmhGUcw2hxsShhJ6CEiHQMgPDwAQ1fWHPM0ypc4RMAig4A==} + engines: {node: ^14.18.0 || >=16.0.0} + + table@6.8.2: + resolution: {integrity: sha512-w2sfv80nrAh2VCbqR5AK27wswXhqcck2AhfnNW76beQXskGZ1V12GwS//yYVa3d3fcvAip2OUnbDAjW2k3v9fA==} + engines: {node: '>=10.0.0'} + + tailwindcss@3.4.10: + resolution: {integrity: sha512-KWZkVPm7yJRhdu4SRSl9d4AK2wM3a50UsvgHZO7xY77NQr2V+fIrEuoDGQcbvswWvFGbS2f6e+jC/6WJm1Dl0w==} + engines: {node: '>=14.0.0'} + hasBin: true + + tar@6.2.1: + resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} + engines: {node: '>=10'} + + text-extensions@2.4.0: + resolution: {integrity: sha512-te/NtwBwfiNRLf9Ijqx3T0nlqZiQ2XrrtBvu+cLL8ZRrGkO0NHTug8MYFKyoSrv/sHTaSKfilUkizV6XhxMJ3g==} + engines: {node: '>=8'} + + text-table@0.2.0: + resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} + + thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + + thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + + through@2.3.8: + resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} + + tiny-invariant@1.3.3: + resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} + + tinycolor2@1.6.0: + resolution: {integrity: sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==} + + tinygradient@1.1.5: + resolution: {integrity: sha512-8nIfc2vgQ4TeLnk2lFj4tRLvvJwEfQuabdsmvDdQPT0xlk9TaNtpGd6nNRxXoK6vQhN6RSzj+Cnp5tTQmpxmbw==} + + tippy.js@6.3.7: + resolution: {integrity: sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==} + + to-fast-properties@2.0.0: + resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} + engines: {node: '>=4'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + ts-api-utils@1.3.0: + resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==} + engines: {node: '>=16'} + peerDependencies: + typescript: '>=4.2.0' + + ts-interface-checker@0.1.13: + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + + tslib@2.3.0: + resolution: {integrity: sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==} + + tslib@2.4.0: + resolution: {integrity: sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==} + + tslib@2.6.3: + resolution: {integrity: sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==} + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + type-fest@0.20.2: + resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} + engines: {node: '>=10'} + + type-fest@0.21.3: + resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} + engines: {node: '>=10'} + + type-fest@2.19.0: + resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} + engines: {node: '>=12.20'} + + typescript@5.5.4: + resolution: {integrity: sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==} + engines: {node: '>=14.17'} + hasBin: true + + ufo@1.5.4: + resolution: {integrity: sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==} + + uncrypto@0.1.3: + resolution: {integrity: sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==} + + unctx@2.3.1: + resolution: {integrity: sha512-PhKke8ZYauiqh3FEMVNm7ljvzQiph0Mt3GBRve03IJm7ukfaON2OBK795tLwhbyfzknuRRkW0+Ze+CQUmzOZ+A==} + + undici-types@6.19.8: + resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} + + unicorn-magic@0.1.0: + resolution: {integrity: sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==} + engines: {node: '>=18'} + + unimport@3.10.0: + resolution: {integrity: sha512-/UvKRfWx3mNDWwWQhR62HsoM3wxHwYdTq8ellZzMOHnnw4Dp8tovgthyW7DjTrbjDL+i4idOp06voz2VKlvrLw==} + + universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} + + unplugin@1.12.2: + resolution: {integrity: sha512-bEqQxeC7rxtxPZ3M5V4Djcc4lQqKPgGe3mAWZvxcSmX5jhGxll19NliaRzQSQPrk4xJZSGniK3puLWpRuZN7VQ==} + engines: {node: '>=14.0.0'} + + untyped@1.4.2: + resolution: {integrity: sha512-nC5q0DnPEPVURPhfPQLahhSTnemVtPzdx7ofiRxXpOB2SYnb3MfdU3DVGyJdS8Lx+tBWeAePO8BfU/3EgksM7Q==} + hasBin: true + + update-browserslist-db@1.1.0: + resolution: {integrity: sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + util@0.10.4: + resolution: {integrity: sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==} + + uuid@8.3.2: + resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + hasBin: true + + vite-plugin-cdn-import@1.0.1: + resolution: {integrity: sha512-lgjLxgwFSKvJLbqjVBirUZ0rQo00GpUGJzRpgQu8RyBw9LA7jaqG6fUMQzBC9qWmTGabPC3iOzwCcoi7PseRAQ==} + + vite-plugin-checker@0.7.2: + resolution: {integrity: sha512-xeYeJbG0gaCaT0QcUC4B2Zo4y5NR8ZhYenc5gPbttrZvraRFwkEADCYwq+BfEHl9zYz7yf85TxsiGoYwyyIjhw==} + engines: {node: '>=14.16'} + peerDependencies: + '@biomejs/biome': '>=1.7' + eslint: '>=7' + meow: ^9.0.0 + optionator: ^0.9.1 + stylelint: '>=13' + typescript: '*' + vite: '>=2.0.0' + vls: '*' + vti: '*' + vue-tsc: '>=2.0.0' + peerDependenciesMeta: + '@biomejs/biome': + optional: true + eslint: + optional: true + meow: + optional: true + optionator: + optional: true + stylelint: + optional: true + typescript: + optional: true + vls: + optional: true + vti: + optional: true + vue-tsc: + optional: true + + vite-plugin-compression@0.5.1: + resolution: {integrity: sha512-5QJKBDc+gNYVqL/skgFAP81Yuzo9R+EAf19d+EtsMF/i8kFUpNi3J/H01QD3Oo8zBQn+NzoCIFkpPLynoOzaJg==} + peerDependencies: + vite: '>=2.0.0' + + vite-plugin-externals@0.6.2: + resolution: {integrity: sha512-R5oVY8xDJjLXLTs2XDYzvYbc/RTZuIwOx2xcFbYf+/VXB6eJuatDgt8jzQ7kZ+IrgwQhe6tU8U2fTyy72C25CQ==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + vite: '>=2.0.0' + + vite-plugin-fake-server@2.1.1: + resolution: {integrity: sha512-QUgssvE7jI9XU1WuDZ3gkzzi9GzVeapELIlFNMvmE2swDKL7O2y2nV0kRZ9VYOsD+hV312uSJyzHBJvcmBw7UQ==} + + vite-plugin-remove-console@2.2.0: + resolution: {integrity: sha512-qgjh5pz75MdE9Kzs8J0kBwaCfifHV0ezRbB9rpGsIOxam+ilcGV7WOk91vFJXquzRmiKrFh3Hxlh0JJWAmXTbQ==} + + vite-plugin-router-warn@1.0.0: + resolution: {integrity: sha512-jnr7faHJPkKxukBXVpg7Ui1UDqhmxD7xU6JGidq8ivSHTsNAPqzSpPpwW8O1PBP/0+Owq4bLfNNk11drOkz4xA==} + + vite-plugin-vue-inspector@5.1.3: + resolution: {integrity: sha512-pMrseXIDP1Gb38mOevY+BvtNGNqiqmqa2pKB99lnLsADQww9w9xMbAfT4GB6RUoaOkSPrtlXqpq2Fq+Dj2AgFg==} + peerDependencies: + vite: ^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 + + vite-svg-loader@5.1.0: + resolution: {integrity: sha512-M/wqwtOEjgb956/+m5ZrYT/Iq6Hax0OakWbokj8+9PXOnB7b/4AxESHieEtnNEy7ZpjsjYW1/5nK8fATQMmRxw==} + peerDependencies: + vue: '>=3.2.13' + + vite@5.4.1: + resolution: {integrity: sha512-1oE6yuNXssjrZdblI9AfBbHCC41nnyoVoEZxQnID6yvQZAFBzxxkqoFLtHUMkYunL8hwOLEjgTuxpkRxvba3kA==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + + vscode-jsonrpc@6.0.0: + resolution: {integrity: sha512-wnJA4BnEjOSyFMvjZdpiOwhSq9uDoK8e/kpRJDTaMYzwlkrhG1fwDIZI94CLsLzlCK5cIbMMtFlJlfR57Lavmg==} + engines: {node: '>=8.0.0 || >=10.0.0'} + + vscode-languageclient@7.0.0: + resolution: {integrity: sha512-P9AXdAPlsCgslpP9pRxYPqkNYV7Xq8300/aZDpO35j1fJm/ncize8iGswzYlcvFw5DQUx4eVk+KvfXdL0rehNg==} + engines: {vscode: ^1.52.0} + + vscode-languageserver-protocol@3.16.0: + resolution: {integrity: sha512-sdeUoAawceQdgIfTI+sdcwkiK2KU+2cbEYA0agzM2uqaUy2UpnnGHtWTHVEtS0ES4zHU0eMFRGN+oQgDxlD66A==} + + vscode-languageserver-textdocument@1.0.12: + resolution: {integrity: sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==} + + vscode-languageserver-types@3.16.0: + resolution: {integrity: sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA==} + + vscode-languageserver@7.0.0: + resolution: {integrity: sha512-60HTx5ID+fLRcgdHfmz0LDZAXYEV68fzwG0JWwEPBode9NuMYTIxuYXPg4ngO8i8+Ou0lM7y6GzaYWbiDL0drw==} + hasBin: true + + vscode-uri@3.0.8: + resolution: {integrity: sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==} + + vue-demi@0.14.10: + resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==} + engines: {node: '>=12'} + hasBin: true + peerDependencies: + '@vue/composition-api': ^1.0.0-rc.1 + vue: ^3.0.0-0 || ^2.6.0 + peerDependenciesMeta: + '@vue/composition-api': + optional: true + + vue-eslint-parser@9.4.3: + resolution: {integrity: sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg==} + engines: {node: ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: '>=6.0.0' + + vue-i18n@9.14.0: + resolution: {integrity: sha512-LxmpRuCt2rI8gqU+kxeflRZMQn4D5+4M3oP3PWZdowW/ePJraHqhF7p4CuaME52mUxdw3Mmy2yAUKgfZYgCRjA==} + engines: {node: '>= 16'} + peerDependencies: + vue: ^3.0.0 + + vue-router@4.4.3: + resolution: {integrity: sha512-sv6wmNKx2j3aqJQDMxLFzs/u/mjA9Z5LCgy6BE0f7yFWMjrPLnS/sPNn8ARY/FXw6byV18EFutn5lTO6+UsV5A==} + peerDependencies: + vue: ^3.2.0 + + vue-tippy@6.4.4: + resolution: {integrity: sha512-0C5TSU482FvjhEeKrPkz08tzyC/KJC0CiEbm3yW9oS+n3fa03ajEzU2QcxI9oR6Hwlg8NOP0U6T4EsGuccq6YQ==} + peerDependencies: + vue: ^3.2.0 + + vue-tsc@2.0.29: + resolution: {integrity: sha512-MHhsfyxO3mYShZCGYNziSbc63x7cQ5g9kvijV7dRe1TTXBRLxXyL0FnXWpUF1xII2mJ86mwYpYsUmMwkmerq7Q==} + hasBin: true + peerDependencies: + typescript: '>=5.0.0' + + vue-types@5.1.3: + resolution: {integrity: sha512-3Wy6QcZl0VusCCHX3vYrWSILFlrOB2EQDoySnuYmASM5cUp1FivJGfkS5lp1CutDgyRb41g32r/1QCmiBj5i1Q==} + engines: {node: '>=14.0.0'} + peerDependencies: + vue: ^2.0.0 || ^3.0.0 + peerDependenciesMeta: + vue: + optional: true + + vue@3.4.38: + resolution: {integrity: sha512-f0ZgN+mZ5KFgVv9wz0f4OgVKukoXtS3nwET4c2vLBGQR50aI8G0cqbFtLlX9Yiyg3LFGBitruPHt2PxwTduJEw==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + webpack-sources@3.2.3: + resolution: {integrity: sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==} + engines: {node: '>=10.13.0'} + + webpack-virtual-modules@0.6.2: + resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} + + which@1.3.1: + resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} + hasBin: true + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + widest-line@4.0.1: + resolution: {integrity: sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==} + engines: {node: '>=12'} + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + wrap-ansi@9.0.0: + resolution: {integrity: sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==} + engines: {node: '>=18'} + + write-file-atomic@5.0.1: + resolution: {integrity: sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + xml-name-validator@4.0.0: + resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==} + engines: {node: '>=12'} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + + yaml-eslint-parser@1.2.3: + resolution: {integrity: sha512-4wZWvE398hCP7O8n3nXKu/vdq1HcH01ixYlCREaJL5NUMwQ0g3MaGFUBNSlmBtKmhbtVG/Cm6lyYmSVTEVil8A==} + engines: {node: ^14.17.0 || >=16.0.0} + + yaml@1.10.2: + resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} + engines: {node: '>= 6'} + + yaml@2.5.0: + resolution: {integrity: sha512-2wWLbGbYDiSqqIKoPjar3MPgB94ErzCtrNE1FdqGuaO0pi2JGjmE8aW8TDZwzU7vuxcGRdL/4gPQwQ7hD5AMSw==} + engines: {node: '>= 14'} + hasBin: true + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + yocto-queue@1.1.1: + resolution: {integrity: sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==} + engines: {node: '>=12.20'} + + zrender@5.6.0: + resolution: {integrity: sha512-uzgraf4njmmHAbEUxMJ8Oxg+P3fT04O+9p7gY+wJRVxo8Ge+KmYv0WJev945EH4wFuc4OY2NLXz46FZrWS9xJg==} + +snapshots: + + '@alloc/quick-lru@5.2.0': {} + + '@ampproject/remapping@2.3.0': + dependencies: + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 + + '@babel/code-frame@7.24.7': + dependencies: + '@babel/highlight': 7.24.7 + picocolors: 1.0.1 + + '@babel/compat-data@7.25.2': {} + + '@babel/core@7.25.2': + dependencies: + '@ampproject/remapping': 2.3.0 + '@babel/code-frame': 7.24.7 + '@babel/generator': 7.25.0 + '@babel/helper-compilation-targets': 7.25.2 + '@babel/helper-module-transforms': 7.25.2(@babel/core@7.25.2) + '@babel/helpers': 7.25.0 + '@babel/parser': 7.25.3 + '@babel/template': 7.25.0 + '@babel/traverse': 7.25.3 + '@babel/types': 7.25.2 + convert-source-map: 2.0.0 + debug: 4.3.6 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.25.0': + dependencies: + '@babel/types': 7.25.2 + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 + jsesc: 2.5.2 + + '@babel/helper-annotate-as-pure@7.24.7': + dependencies: + '@babel/types': 7.25.2 + + '@babel/helper-compilation-targets@7.25.2': + dependencies: + '@babel/compat-data': 7.25.2 + '@babel/helper-validator-option': 7.24.8 + browserslist: 4.23.3 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-create-class-features-plugin@7.25.0(@babel/core@7.25.2)': + dependencies: + '@babel/core': 7.25.2 + '@babel/helper-annotate-as-pure': 7.24.7 + '@babel/helper-member-expression-to-functions': 7.24.8 + '@babel/helper-optimise-call-expression': 7.24.7 + '@babel/helper-replace-supers': 7.25.0(@babel/core@7.25.2) + '@babel/helper-skip-transparent-expression-wrappers': 7.24.7 + '@babel/traverse': 7.25.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/helper-member-expression-to-functions@7.24.8': + dependencies: + '@babel/traverse': 7.25.3 + '@babel/types': 7.25.2 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-imports@7.22.15': + dependencies: + '@babel/types': 7.25.2 + + '@babel/helper-module-imports@7.24.7': + dependencies: + '@babel/traverse': 7.25.3 + '@babel/types': 7.25.2 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.25.2(@babel/core@7.25.2)': + dependencies: + '@babel/core': 7.25.2 + '@babel/helper-module-imports': 7.24.7 + '@babel/helper-simple-access': 7.24.7 + '@babel/helper-validator-identifier': 7.24.7 + '@babel/traverse': 7.25.3 + transitivePeerDependencies: + - supports-color + + '@babel/helper-optimise-call-expression@7.24.7': + dependencies: + '@babel/types': 7.25.2 + + '@babel/helper-plugin-utils@7.24.8': {} + + '@babel/helper-replace-supers@7.25.0(@babel/core@7.25.2)': + dependencies: + '@babel/core': 7.25.2 + '@babel/helper-member-expression-to-functions': 7.24.8 + '@babel/helper-optimise-call-expression': 7.24.7 + '@babel/traverse': 7.25.3 + transitivePeerDependencies: + - supports-color + + '@babel/helper-simple-access@7.24.7': + dependencies: + '@babel/traverse': 7.25.3 + '@babel/types': 7.25.2 + transitivePeerDependencies: + - supports-color + + '@babel/helper-skip-transparent-expression-wrappers@7.24.7': + dependencies: + '@babel/traverse': 7.25.3 + '@babel/types': 7.25.2 + transitivePeerDependencies: + - supports-color + + '@babel/helper-string-parser@7.24.8': {} + + '@babel/helper-validator-identifier@7.24.7': {} + + '@babel/helper-validator-option@7.24.8': {} + + '@babel/helpers@7.25.0': + dependencies: + '@babel/template': 7.25.0 + '@babel/types': 7.25.2 + + '@babel/highlight@7.24.7': + dependencies: + '@babel/helper-validator-identifier': 7.24.7 + chalk: 2.4.2 + js-tokens: 4.0.0 + picocolors: 1.0.1 + + '@babel/parser@7.25.3': + dependencies: + '@babel/types': 7.25.2 + + '@babel/plugin-proposal-decorators@7.24.7(@babel/core@7.25.2)': + dependencies: + '@babel/core': 7.25.2 + '@babel/helper-create-class-features-plugin': 7.25.0(@babel/core@7.25.2) + '@babel/helper-plugin-utils': 7.24.8 + '@babel/plugin-syntax-decorators': 7.24.7(@babel/core@7.25.2) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-syntax-decorators@7.24.7(@babel/core@7.25.2)': + dependencies: + '@babel/core': 7.25.2 + '@babel/helper-plugin-utils': 7.24.8 + + '@babel/plugin-syntax-import-attributes@7.24.7(@babel/core@7.25.2)': + dependencies: + '@babel/core': 7.25.2 + '@babel/helper-plugin-utils': 7.24.8 + + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.25.2)': + dependencies: + '@babel/core': 7.25.2 + '@babel/helper-plugin-utils': 7.24.8 + + '@babel/plugin-syntax-jsx@7.24.7(@babel/core@7.25.2)': + dependencies: + '@babel/core': 7.25.2 + '@babel/helper-plugin-utils': 7.24.8 + + '@babel/plugin-syntax-typescript@7.24.7(@babel/core@7.25.2)': + dependencies: + '@babel/core': 7.25.2 + '@babel/helper-plugin-utils': 7.24.8 + + '@babel/plugin-transform-typescript@7.25.2(@babel/core@7.25.2)': + dependencies: + '@babel/core': 7.25.2 + '@babel/helper-annotate-as-pure': 7.24.7 + '@babel/helper-create-class-features-plugin': 7.25.0(@babel/core@7.25.2) + '@babel/helper-plugin-utils': 7.24.8 + '@babel/helper-skip-transparent-expression-wrappers': 7.24.7 + '@babel/plugin-syntax-typescript': 7.24.7(@babel/core@7.25.2) + transitivePeerDependencies: + - supports-color + + '@babel/standalone@7.25.3': + optional: true + + '@babel/template@7.25.0': + dependencies: + '@babel/code-frame': 7.24.7 + '@babel/parser': 7.25.3 + '@babel/types': 7.25.2 + + '@babel/traverse@7.25.3': + dependencies: + '@babel/code-frame': 7.24.7 + '@babel/generator': 7.25.0 + '@babel/parser': 7.25.3 + '@babel/template': 7.25.0 + '@babel/types': 7.25.2 + debug: 4.3.6 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.25.2': + dependencies: + '@babel/helper-string-parser': 7.24.8 + '@babel/helper-validator-identifier': 7.24.7 + to-fast-properties: 2.0.0 + + '@commitlint/cli@19.4.0(@types/node@20.16.1)(typescript@5.5.4)': + dependencies: + '@commitlint/format': 19.3.0 + '@commitlint/lint': 19.2.2 + '@commitlint/load': 19.4.0(@types/node@20.16.1)(typescript@5.5.4) + '@commitlint/read': 19.4.0 + '@commitlint/types': 19.0.3 + execa: 8.0.1 + yargs: 17.7.2 + transitivePeerDependencies: + - '@types/node' + - typescript + + '@commitlint/config-conventional@19.2.2': + dependencies: + '@commitlint/types': 19.0.3 + conventional-changelog-conventionalcommits: 7.0.2 + + '@commitlint/config-validator@19.0.3': + dependencies: + '@commitlint/types': 19.0.3 + ajv: 8.17.1 + + '@commitlint/ensure@19.0.3': + dependencies: + '@commitlint/types': 19.0.3 + lodash.camelcase: 4.3.0 + lodash.kebabcase: 4.1.1 + lodash.snakecase: 4.1.1 + lodash.startcase: 4.4.0 + lodash.upperfirst: 4.3.1 + + '@commitlint/execute-rule@19.0.0': {} + + '@commitlint/format@19.3.0': + dependencies: + '@commitlint/types': 19.0.3 + chalk: 5.3.0 + + '@commitlint/is-ignored@19.2.2': + dependencies: + '@commitlint/types': 19.0.3 + semver: 7.6.3 + + '@commitlint/lint@19.2.2': + dependencies: + '@commitlint/is-ignored': 19.2.2 + '@commitlint/parse': 19.0.3 + '@commitlint/rules': 19.0.3 + '@commitlint/types': 19.0.3 + + '@commitlint/load@19.4.0(@types/node@20.16.1)(typescript@5.5.4)': + dependencies: + '@commitlint/config-validator': 19.0.3 + '@commitlint/execute-rule': 19.0.0 + '@commitlint/resolve-extends': 19.1.0 + '@commitlint/types': 19.0.3 + chalk: 5.3.0 + cosmiconfig: 9.0.0(typescript@5.5.4) + cosmiconfig-typescript-loader: 5.0.0(@types/node@20.16.1)(cosmiconfig@9.0.0(typescript@5.5.4))(typescript@5.5.4) + lodash.isplainobject: 4.0.6 + lodash.merge: 4.6.2 + lodash.uniq: 4.5.0 + transitivePeerDependencies: + - '@types/node' + - typescript + + '@commitlint/message@19.0.0': {} + + '@commitlint/parse@19.0.3': + dependencies: + '@commitlint/types': 19.0.3 + conventional-changelog-angular: 7.0.0 + conventional-commits-parser: 5.0.0 + + '@commitlint/read@19.4.0': + dependencies: + '@commitlint/top-level': 19.0.0 + '@commitlint/types': 19.0.3 + execa: 8.0.1 + git-raw-commits: 4.0.0 + minimist: 1.2.8 + + '@commitlint/resolve-extends@19.1.0': + dependencies: + '@commitlint/config-validator': 19.0.3 + '@commitlint/types': 19.0.3 + global-directory: 4.0.1 + import-meta-resolve: 4.1.0 + lodash.mergewith: 4.6.2 + resolve-from: 5.0.0 + + '@commitlint/rules@19.0.3': + dependencies: + '@commitlint/ensure': 19.0.3 + '@commitlint/message': 19.0.0 + '@commitlint/to-lines': 19.0.0 + '@commitlint/types': 19.0.3 + execa: 8.0.1 + + '@commitlint/to-lines@19.0.0': {} + + '@commitlint/top-level@19.0.0': + dependencies: + find-up: 7.0.0 + + '@commitlint/types@19.0.3': + dependencies: + '@types/conventional-commits-parser': 5.0.0 + chalk: 5.3.0 + + '@csstools/css-parser-algorithms@3.0.1(@csstools/css-tokenizer@3.0.1)': + dependencies: + '@csstools/css-tokenizer': 3.0.1 + + '@csstools/css-tokenizer@3.0.1': {} + + '@csstools/media-query-list-parser@3.0.1(@csstools/css-parser-algorithms@3.0.1(@csstools/css-tokenizer@3.0.1))(@csstools/css-tokenizer@3.0.1)': + dependencies: + '@csstools/css-parser-algorithms': 3.0.1(@csstools/css-tokenizer@3.0.1) + '@csstools/css-tokenizer': 3.0.1 + + '@csstools/selector-specificity@4.0.0(postcss-selector-parser@6.1.2)': + dependencies: + postcss-selector-parser: 6.1.2 + + '@ctrl/tinycolor@3.6.1': {} + + '@dual-bundle/import-meta-resolve@4.1.0': {} + + '@element-plus/icons-vue@2.3.1(vue@3.4.38(typescript@5.5.4))': + dependencies: + vue: 3.4.38(typescript@5.5.4) + + '@esbuild/aix-ppc64@0.19.12': + optional: true + + '@esbuild/aix-ppc64@0.21.5': + optional: true + + '@esbuild/android-arm64@0.19.12': + optional: true + + '@esbuild/android-arm64@0.21.5': + optional: true + + '@esbuild/android-arm@0.19.12': + optional: true + + '@esbuild/android-arm@0.21.5': + optional: true + + '@esbuild/android-x64@0.19.12': + optional: true + + '@esbuild/android-x64@0.21.5': + optional: true + + '@esbuild/darwin-arm64@0.19.12': + optional: true + + '@esbuild/darwin-arm64@0.21.5': + optional: true + + '@esbuild/darwin-x64@0.19.12': + optional: true + + '@esbuild/darwin-x64@0.21.5': + optional: true + + '@esbuild/freebsd-arm64@0.19.12': + optional: true + + '@esbuild/freebsd-arm64@0.21.5': + optional: true + + '@esbuild/freebsd-x64@0.19.12': + optional: true + + '@esbuild/freebsd-x64@0.21.5': + optional: true + + '@esbuild/linux-arm64@0.19.12': + optional: true + + '@esbuild/linux-arm64@0.21.5': + optional: true + + '@esbuild/linux-arm@0.19.12': + optional: true + + '@esbuild/linux-arm@0.21.5': + optional: true + + '@esbuild/linux-ia32@0.19.12': + optional: true + + '@esbuild/linux-ia32@0.21.5': + optional: true + + '@esbuild/linux-loong64@0.19.12': + optional: true + + '@esbuild/linux-loong64@0.21.5': + optional: true + + '@esbuild/linux-mips64el@0.19.12': + optional: true + + '@esbuild/linux-mips64el@0.21.5': + optional: true + + '@esbuild/linux-ppc64@0.19.12': + optional: true + + '@esbuild/linux-ppc64@0.21.5': + optional: true + + '@esbuild/linux-riscv64@0.19.12': + optional: true + + '@esbuild/linux-riscv64@0.21.5': + optional: true + + '@esbuild/linux-s390x@0.19.12': + optional: true + + '@esbuild/linux-s390x@0.21.5': + optional: true + + '@esbuild/linux-x64@0.19.12': + optional: true + + '@esbuild/linux-x64@0.21.5': + optional: true + + '@esbuild/netbsd-x64@0.19.12': + optional: true + + '@esbuild/netbsd-x64@0.21.5': + optional: true + + '@esbuild/openbsd-x64@0.19.12': + optional: true + + '@esbuild/openbsd-x64@0.21.5': + optional: true + + '@esbuild/sunos-x64@0.19.12': + optional: true + + '@esbuild/sunos-x64@0.21.5': + optional: true + + '@esbuild/win32-arm64@0.19.12': + optional: true + + '@esbuild/win32-arm64@0.21.5': + optional: true + + '@esbuild/win32-ia32@0.19.12': + optional: true + + '@esbuild/win32-ia32@0.21.5': + optional: true + + '@esbuild/win32-x64@0.19.12': + optional: true + + '@esbuild/win32-x64@0.21.5': + optional: true + + '@eslint-community/eslint-utils@4.4.0(eslint@9.9.0(jiti@1.21.6))': + dependencies: + eslint: 9.9.0(jiti@1.21.6) + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.11.0': {} + + '@eslint/config-array@0.17.1': + dependencies: + '@eslint/object-schema': 2.1.4 + debug: 4.3.6 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@eslint/eslintrc@3.1.0': + dependencies: + ajv: 6.12.6 + debug: 4.3.6 + espree: 10.1.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.0 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@9.9.0': {} + + '@eslint/object-schema@2.1.4': {} + + '@faker-js/faker@8.4.1': {} + + '@floating-ui/core@1.6.7': + dependencies: + '@floating-ui/utils': 0.2.7 + + '@floating-ui/dom@1.6.10': + dependencies: + '@floating-ui/core': 1.6.7 + '@floating-ui/utils': 0.2.7 + + '@floating-ui/utils@0.2.7': {} + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.3.0': {} + + '@iconify-icons/ep@1.2.12': + dependencies: + '@iconify/types': 2.0.0 + + '@iconify-icons/ri@1.2.10': + dependencies: + '@iconify/types': 2.0.0 + + '@iconify/types@2.0.0': {} + + '@iconify/vue@4.1.2(vue@3.4.38(typescript@5.5.4))': + dependencies: + '@iconify/types': 2.0.0 + vue: 3.4.38(typescript@5.5.4) + + '@intlify/bundle-utils@8.0.0(vue-i18n@9.14.0(vue@3.4.38(typescript@5.5.4)))': + dependencies: + '@intlify/message-compiler': 9.14.0 + '@intlify/shared': 9.14.0 + acorn: 8.12.1 + escodegen: 2.1.0 + estree-walker: 2.0.2 + jsonc-eslint-parser: 2.4.0 + mlly: 1.7.1 + source-map-js: 1.2.0 + yaml-eslint-parser: 1.2.3 + optionalDependencies: + vue-i18n: 9.14.0(vue@3.4.38(typescript@5.5.4)) + + '@intlify/core-base@9.14.0': + dependencies: + '@intlify/message-compiler': 9.14.0 + '@intlify/shared': 9.14.0 + + '@intlify/message-compiler@9.14.0': + dependencies: + '@intlify/shared': 9.14.0 + source-map-js: 1.2.0 + + '@intlify/shared@9.14.0': {} + + '@intlify/unplugin-vue-i18n@4.0.0(rollup@4.21.0)(vue-i18n@9.14.0(vue@3.4.38(typescript@5.5.4)))': + dependencies: + '@intlify/bundle-utils': 8.0.0(vue-i18n@9.14.0(vue@3.4.38(typescript@5.5.4))) + '@intlify/shared': 9.14.0 + '@rollup/pluginutils': 5.1.0(rollup@4.21.0) + '@vue/compiler-sfc': 3.4.38 + debug: 4.3.6 + fast-glob: 3.3.2 + js-yaml: 4.1.0 + json5: 2.2.3 + pathe: 1.1.2 + picocolors: 1.0.1 + source-map-js: 1.2.0 + unplugin: 1.12.2 + optionalDependencies: + vue-i18n: 9.14.0(vue@3.4.38(typescript@5.5.4)) + transitivePeerDependencies: + - rollup + - supports-color + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@jridgewell/gen-mapping@0.3.5': + dependencies: + '@jridgewell/set-array': 1.2.1 + '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/trace-mapping': 0.3.25 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/set-array@1.2.1': {} + + '@jridgewell/sourcemap-codec@1.5.0': {} + + '@jridgewell/trace-mapping@0.3.25': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.0 + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.17.1 + + '@nuxt/kit@3.12.4(rollup@4.21.0)': + dependencies: + '@nuxt/schema': 3.12.4(rollup@4.21.0) + c12: 1.11.1 + consola: 3.2.3 + defu: 6.1.4 + destr: 2.0.3 + globby: 14.0.2 + hash-sum: 2.0.0 + ignore: 5.3.2 + jiti: 1.21.6 + klona: 2.0.6 + knitwork: 1.1.0 + mlly: 1.7.1 + pathe: 1.1.2 + pkg-types: 1.1.3 + scule: 1.3.0 + semver: 7.6.3 + ufo: 1.5.4 + unctx: 2.3.1 + unimport: 3.10.0(rollup@4.21.0) + untyped: 1.4.2 + transitivePeerDependencies: + - magicast + - rollup + - supports-color + optional: true + + '@nuxt/schema@3.12.4(rollup@4.21.0)': + dependencies: + compatx: 0.1.8 + consola: 3.2.3 + defu: 6.1.4 + hookable: 5.5.3 + pathe: 1.1.2 + pkg-types: 1.1.3 + scule: 1.3.0 + std-env: 3.7.0 + ufo: 1.5.4 + uncrypto: 0.1.3 + unimport: 3.10.0(rollup@4.21.0) + untyped: 1.4.2 + transitivePeerDependencies: + - rollup + - supports-color + optional: true + + '@pkgjs/parseargs@0.11.0': + optional: true + + '@pkgr/core@0.1.1': {} + + '@popperjs/core@2.11.8': {} + + '@pureadmin/descriptions@1.2.1(echarts@5.5.1)(element-plus@2.8.0(vue@3.4.38(typescript@5.5.4)))(typescript@5.5.4)': + dependencies: + '@element-plus/icons-vue': 2.3.1(vue@3.4.38(typescript@5.5.4)) + '@pureadmin/utils': 2.4.8(echarts@5.5.1)(vue@3.4.38(typescript@5.5.4)) + element-plus: 2.8.0(vue@3.4.38(typescript@5.5.4)) + vue: 3.4.38(typescript@5.5.4) + transitivePeerDependencies: + - echarts + - typescript + + '@pureadmin/table@3.2.0(element-plus@2.8.0(vue@3.4.38(typescript@5.5.4)))(typescript@5.5.4)': + dependencies: + element-plus: 2.8.0(vue@3.4.38(typescript@5.5.4)) + vue: 3.4.38(typescript@5.5.4) + transitivePeerDependencies: + - typescript + + '@pureadmin/theme@3.2.0': + dependencies: + '@zougt/some-loader-utils': 1.4.3 + fs-extra: 11.2.0 + string-hash: 1.1.3 + + '@pureadmin/utils@2.4.8(echarts@5.5.1)(vue@3.4.38(typescript@5.5.4))': + optionalDependencies: + echarts: 5.5.1 + vue: 3.4.38(typescript@5.5.4) + + '@rollup/pluginutils@5.1.0(rollup@4.21.0)': + dependencies: + '@types/estree': 1.0.5 + estree-walker: 2.0.2 + picomatch: 2.3.1 + optionalDependencies: + rollup: 4.21.0 + + '@rollup/rollup-android-arm-eabi@4.21.0': + optional: true + + '@rollup/rollup-android-arm64@4.21.0': + optional: true + + '@rollup/rollup-darwin-arm64@4.21.0': + optional: true + + '@rollup/rollup-darwin-x64@4.21.0': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.21.0': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.21.0': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.21.0': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.21.0': + optional: true + + '@rollup/rollup-linux-powerpc64le-gnu@4.21.0': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.21.0': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.21.0': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.21.0': + optional: true + + '@rollup/rollup-linux-x64-musl@4.21.0': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.21.0': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.21.0': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.21.0': + optional: true + + '@sindresorhus/merge-streams@2.3.0': + optional: true + + '@sxzz/popperjs-es@2.11.7': {} + + '@trysound/sax@0.2.0': {} + + '@types/conventional-commits-parser@5.0.0': + dependencies: + '@types/node': 20.16.1 + + '@types/estree@1.0.5': {} + + '@types/gradient-string@1.1.6': + dependencies: + '@types/tinycolor2': 1.4.6 + + '@types/js-cookie@3.0.6': {} + + '@types/lodash-es@4.17.12': + dependencies: + '@types/lodash': 4.17.7 + + '@types/lodash@4.17.7': {} + + '@types/node@20.16.1': + dependencies: + undici-types: 6.19.8 + + '@types/nprogress@0.2.3': {} + + '@types/qs@6.9.15': {} + + '@types/sortablejs@1.15.8': {} + + '@types/tinycolor2@1.4.6': {} + + '@types/web-bluetooth@0.0.16': {} + + '@types/web-bluetooth@0.0.20': {} + + '@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@9.9.0(jiti@1.21.6))(typescript@5.5.4))(eslint@9.9.0(jiti@1.21.6))(typescript@5.5.4)': + dependencies: + '@eslint-community/regexpp': 4.11.0 + '@typescript-eslint/parser': 7.18.0(eslint@9.9.0(jiti@1.21.6))(typescript@5.5.4) + '@typescript-eslint/scope-manager': 7.18.0 + '@typescript-eslint/type-utils': 7.18.0(eslint@9.9.0(jiti@1.21.6))(typescript@5.5.4) + '@typescript-eslint/utils': 7.18.0(eslint@9.9.0(jiti@1.21.6))(typescript@5.5.4) + '@typescript-eslint/visitor-keys': 7.18.0 + eslint: 9.9.0(jiti@1.21.6) + graphemer: 1.4.0 + ignore: 5.3.2 + natural-compare: 1.4.0 + ts-api-utils: 1.3.0(typescript@5.5.4) + optionalDependencies: + typescript: 5.5.4 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@7.18.0(eslint@9.9.0(jiti@1.21.6))(typescript@5.5.4)': + dependencies: + '@typescript-eslint/scope-manager': 7.18.0 + '@typescript-eslint/types': 7.18.0 + '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.5.4) + '@typescript-eslint/visitor-keys': 7.18.0 + debug: 4.3.6 + eslint: 9.9.0(jiti@1.21.6) + optionalDependencies: + typescript: 5.5.4 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@7.18.0': + dependencies: + '@typescript-eslint/types': 7.18.0 + '@typescript-eslint/visitor-keys': 7.18.0 + + '@typescript-eslint/type-utils@7.18.0(eslint@9.9.0(jiti@1.21.6))(typescript@5.5.4)': + dependencies: + '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.5.4) + '@typescript-eslint/utils': 7.18.0(eslint@9.9.0(jiti@1.21.6))(typescript@5.5.4) + debug: 4.3.6 + eslint: 9.9.0(jiti@1.21.6) + ts-api-utils: 1.3.0(typescript@5.5.4) + optionalDependencies: + typescript: 5.5.4 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@7.18.0': {} + + '@typescript-eslint/typescript-estree@7.18.0(typescript@5.5.4)': + dependencies: + '@typescript-eslint/types': 7.18.0 + '@typescript-eslint/visitor-keys': 7.18.0 + debug: 4.3.6 + globby: 11.1.0 + is-glob: 4.0.3 + minimatch: 9.0.5 + semver: 7.6.3 + ts-api-utils: 1.3.0(typescript@5.5.4) + optionalDependencies: + typescript: 5.5.4 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@7.18.0(eslint@9.9.0(jiti@1.21.6))(typescript@5.5.4)': + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@9.9.0(jiti@1.21.6)) + '@typescript-eslint/scope-manager': 7.18.0 + '@typescript-eslint/types': 7.18.0 + '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.5.4) + eslint: 9.9.0(jiti@1.21.6) + transitivePeerDependencies: + - supports-color + - typescript + + '@typescript-eslint/visitor-keys@7.18.0': + dependencies: + '@typescript-eslint/types': 7.18.0 + eslint-visitor-keys: 3.4.3 + + '@vitejs/plugin-vue-jsx@4.0.1(vite@5.4.1(@types/node@20.16.1)(sass@1.77.8))(vue@3.4.38(typescript@5.5.4))': + dependencies: + '@babel/core': 7.25.2 + '@babel/plugin-transform-typescript': 7.25.2(@babel/core@7.25.2) + '@vue/babel-plugin-jsx': 1.2.2(@babel/core@7.25.2) + vite: 5.4.1(@types/node@20.16.1)(sass@1.77.8) + vue: 3.4.38(typescript@5.5.4) + transitivePeerDependencies: + - supports-color + + '@vitejs/plugin-vue@5.1.2(vite@5.4.1(@types/node@20.16.1)(sass@1.77.8))(vue@3.4.38(typescript@5.5.4))': + dependencies: + vite: 5.4.1(@types/node@20.16.1)(sass@1.77.8) + vue: 3.4.38(typescript@5.5.4) + + '@volar/language-core@2.4.0': + dependencies: + '@volar/source-map': 2.4.0 + + '@volar/source-map@2.4.0': {} + + '@volar/typescript@2.4.0': + dependencies: + '@volar/language-core': 2.4.0 + path-browserify: 1.0.1 + vscode-uri: 3.0.8 + + '@vue/babel-helper-vue-transform-on@1.2.2': {} + + '@vue/babel-plugin-jsx@1.2.2(@babel/core@7.25.2)': + dependencies: + '@babel/helper-module-imports': 7.22.15 + '@babel/helper-plugin-utils': 7.24.8 + '@babel/plugin-syntax-jsx': 7.24.7(@babel/core@7.25.2) + '@babel/template': 7.25.0 + '@babel/traverse': 7.25.3 + '@babel/types': 7.25.2 + '@vue/babel-helper-vue-transform-on': 1.2.2 + '@vue/babel-plugin-resolve-type': 1.2.2(@babel/core@7.25.2) + camelcase: 6.3.0 + html-tags: 3.3.1 + svg-tags: 1.0.0 + optionalDependencies: + '@babel/core': 7.25.2 + transitivePeerDependencies: + - supports-color + + '@vue/babel-plugin-resolve-type@1.2.2(@babel/core@7.25.2)': + dependencies: + '@babel/code-frame': 7.24.7 + '@babel/core': 7.25.2 + '@babel/helper-module-imports': 7.22.15 + '@babel/helper-plugin-utils': 7.24.8 + '@babel/parser': 7.25.3 + '@vue/compiler-sfc': 3.4.38 + + '@vue/compiler-core@3.4.38': + dependencies: + '@babel/parser': 7.25.3 + '@vue/shared': 3.4.38 + entities: 4.5.0 + estree-walker: 2.0.2 + source-map-js: 1.2.0 + + '@vue/compiler-dom@3.4.38': + dependencies: + '@vue/compiler-core': 3.4.38 + '@vue/shared': 3.4.38 + + '@vue/compiler-sfc@3.4.38': + dependencies: + '@babel/parser': 7.25.3 + '@vue/compiler-core': 3.4.38 + '@vue/compiler-dom': 3.4.38 + '@vue/compiler-ssr': 3.4.38 + '@vue/shared': 3.4.38 + estree-walker: 2.0.2 + magic-string: 0.30.11 + postcss: 8.4.41 + source-map-js: 1.2.0 + + '@vue/compiler-ssr@3.4.38': + dependencies: + '@vue/compiler-dom': 3.4.38 + '@vue/shared': 3.4.38 + + '@vue/compiler-vue2@2.7.16': + dependencies: + de-indent: 1.0.2 + he: 1.2.0 + + '@vue/devtools-api@6.6.3': {} + + '@vue/language-core@2.0.29(typescript@5.5.4)': + dependencies: + '@volar/language-core': 2.4.0 + '@vue/compiler-dom': 3.4.38 + '@vue/compiler-vue2': 2.7.16 + '@vue/shared': 3.4.38 + computeds: 0.0.1 + minimatch: 9.0.5 + muggle-string: 0.4.1 + path-browserify: 1.0.1 + optionalDependencies: + typescript: 5.5.4 + + '@vue/reactivity@3.4.38': + dependencies: + '@vue/shared': 3.4.38 + + '@vue/runtime-core@3.4.38': + dependencies: + '@vue/reactivity': 3.4.38 + '@vue/shared': 3.4.38 + + '@vue/runtime-dom@3.4.38': + dependencies: + '@vue/reactivity': 3.4.38 + '@vue/runtime-core': 3.4.38 + '@vue/shared': 3.4.38 + csstype: 3.1.3 + + '@vue/server-renderer@3.4.38(vue@3.4.38(typescript@5.5.4))': + dependencies: + '@vue/compiler-ssr': 3.4.38 + '@vue/shared': 3.4.38 + vue: 3.4.38(typescript@5.5.4) + + '@vue/shared@3.4.38': {} + + '@vueuse/core@10.11.1(vue@3.4.38(typescript@5.5.4))': + dependencies: + '@types/web-bluetooth': 0.0.20 + '@vueuse/metadata': 10.11.1 + '@vueuse/shared': 10.11.1(vue@3.4.38(typescript@5.5.4)) + vue-demi: 0.14.10(vue@3.4.38(typescript@5.5.4)) + transitivePeerDependencies: + - '@vue/composition-api' + - vue + + '@vueuse/core@9.13.0(vue@3.4.38(typescript@5.5.4))': + dependencies: + '@types/web-bluetooth': 0.0.16 + '@vueuse/metadata': 9.13.0 + '@vueuse/shared': 9.13.0(vue@3.4.38(typescript@5.5.4)) + vue-demi: 0.14.10(vue@3.4.38(typescript@5.5.4)) + transitivePeerDependencies: + - '@vue/composition-api' + - vue + + '@vueuse/metadata@10.11.1': {} + + '@vueuse/metadata@9.13.0': {} + + '@vueuse/motion@2.2.3(rollup@4.21.0)(vue@3.4.38(typescript@5.5.4))': + dependencies: + '@vueuse/core': 10.11.1(vue@3.4.38(typescript@5.5.4)) + '@vueuse/shared': 10.11.1(vue@3.4.38(typescript@5.5.4)) + csstype: 3.1.3 + framesync: 6.1.2 + popmotion: 11.0.5 + style-value-types: 5.1.2 + vue: 3.4.38(typescript@5.5.4) + optionalDependencies: + '@nuxt/kit': 3.12.4(rollup@4.21.0) + transitivePeerDependencies: + - '@vue/composition-api' + - magicast + - rollup + - supports-color + + '@vueuse/shared@10.11.1(vue@3.4.38(typescript@5.5.4))': + dependencies: + vue-demi: 0.14.10(vue@3.4.38(typescript@5.5.4)) + transitivePeerDependencies: + - '@vue/composition-api' + - vue + + '@vueuse/shared@9.13.0(vue@3.4.38(typescript@5.5.4))': + dependencies: + vue-demi: 0.14.10(vue@3.4.38(typescript@5.5.4)) + transitivePeerDependencies: + - '@vue/composition-api' + - vue + + '@zougt/some-loader-utils@1.4.3': + dependencies: + cac: 6.7.14 + color: 4.2.3 + cssnano: 5.1.15(postcss@8.4.41) + cssnano-preset-lite: 2.1.3(postcss@8.4.41) + fs-extra: 10.1.0 + postcss: 8.4.41 + prettier: 2.8.8 + uuid: 8.3.2 + + '@zxcvbn-ts/core@3.0.4': + dependencies: + fastest-levenshtein: 1.0.16 + + JSONStream@1.3.5: + dependencies: + jsonparse: 1.3.1 + through: 2.3.8 + + acorn-jsx@5.3.2(acorn@8.12.1): + dependencies: + acorn: 8.12.1 + + acorn@8.12.1: {} + + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ajv@8.17.1: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.0.1 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + animate.css@4.1.1: {} + + ansi-align@3.0.1: + dependencies: + string-width: 4.2.3 + + ansi-escapes@4.3.2: + dependencies: + type-fest: 0.21.3 + + ansi-escapes@7.0.0: + dependencies: + environment: 1.1.0 + + ansi-regex@5.0.1: {} + + ansi-regex@6.0.1: {} + + ansi-styles@3.2.1: + dependencies: + color-convert: 1.9.3 + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@6.2.1: {} + + any-promise@1.3.0: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + arg@5.0.2: {} + + argparse@2.0.1: {} + + array-ify@1.0.0: {} + + array-union@2.1.0: {} + + astral-regex@2.0.0: {} + + async-validator@4.2.5: {} + + asynckit@0.4.0: {} + + autoprefixer@10.4.20(postcss@8.4.41): + dependencies: + browserslist: 4.23.3 + caniuse-lite: 1.0.30001651 + fraction.js: 4.3.7 + normalize-range: 0.1.2 + picocolors: 1.0.1 + postcss: 8.4.41 + postcss-value-parser: 4.2.0 + + axios@1.7.4: + dependencies: + follow-redirects: 1.15.6 + form-data: 4.0.0 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + + balanced-match@1.0.2: {} + + balanced-match@2.0.0: {} + + binary-extensions@2.3.0: {} + + boolbase@1.0.0: {} + + boxen@7.1.1: + dependencies: + ansi-align: 3.0.1 + camelcase: 7.0.1 + chalk: 5.3.0 + cli-boxes: 3.0.0 + string-width: 5.1.2 + type-fest: 2.19.0 + widest-line: 4.0.1 + wrap-ansi: 8.1.0 + + brace-expansion@1.1.11: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.1: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.23.3: + dependencies: + caniuse-lite: 1.0.30001651 + electron-to-chromium: 1.5.11 + node-releases: 2.0.18 + update-browserslist-db: 1.1.0(browserslist@4.23.3) + + bundle-import@0.0.1: + dependencies: + get-tsconfig: 4.7.6 + import-from-string: 0.0.4 + + c12@1.11.1: + dependencies: + chokidar: 3.6.0 + confbox: 0.1.7 + defu: 6.1.4 + dotenv: 16.4.5 + giget: 1.2.3 + jiti: 1.21.6 + mlly: 1.7.1 + ohash: 1.1.3 + pathe: 1.1.2 + perfect-debounce: 1.0.0 + pkg-types: 1.1.3 + rc9: 2.1.2 + optional: true + + cac@6.7.14: {} + + call-bind@1.0.7: + dependencies: + es-define-property: 1.0.0 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.2.4 + set-function-length: 1.2.2 + + callsites@3.1.0: {} + + camelcase-css@2.0.1: {} + + camelcase@6.3.0: {} + + camelcase@7.0.1: {} + + caniuse-api@3.0.0: + dependencies: + browserslist: 4.23.3 + caniuse-lite: 1.0.30001651 + lodash.memoize: 4.1.2 + lodash.uniq: 4.5.0 + + caniuse-lite@1.0.30001651: {} + + chalk@2.4.2: + dependencies: + ansi-styles: 3.2.1 + escape-string-regexp: 1.0.5 + supports-color: 5.5.0 + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + chalk@5.3.0: {} + + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + + chownr@2.0.0: + optional: true + + citty@0.1.6: + dependencies: + consola: 3.2.3 + optional: true + + cli-boxes@3.0.0: {} + + cli-cursor@5.0.0: + dependencies: + restore-cursor: 5.1.0 + + cli-truncate@4.0.0: + dependencies: + slice-ansi: 5.0.0 + string-width: 7.2.0 + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + color-convert@1.9.3: + dependencies: + color-name: 1.1.3 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.3: {} + + color-name@1.1.4: {} + + color-string@1.9.1: + dependencies: + color-name: 1.1.4 + simple-swizzle: 0.2.2 + + color@4.2.3: + dependencies: + color-convert: 2.0.1 + color-string: 1.9.1 + + colord@2.9.3: {} + + colorette@2.0.20: {} + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + commander@12.1.0: {} + + commander@4.1.1: {} + + commander@7.2.0: {} + + commander@8.3.0: {} + + compare-func@2.0.0: + dependencies: + array-ify: 1.0.0 + dot-prop: 5.3.0 + + compatx@0.1.8: + optional: true + + computeds@0.0.1: {} + + concat-map@0.0.1: {} + + confbox@0.1.7: {} + + consola@3.2.3: + optional: true + + conventional-changelog-angular@7.0.0: + dependencies: + compare-func: 2.0.0 + + conventional-changelog-conventionalcommits@7.0.2: + dependencies: + compare-func: 2.0.0 + + conventional-commits-parser@5.0.0: + dependencies: + JSONStream: 1.3.5 + is-text-path: 2.0.0 + meow: 12.1.1 + split2: 4.2.0 + + convert-source-map@2.0.0: {} + + cosmiconfig-typescript-loader@5.0.0(@types/node@20.16.1)(cosmiconfig@9.0.0(typescript@5.5.4))(typescript@5.5.4): + dependencies: + '@types/node': 20.16.1 + cosmiconfig: 9.0.0(typescript@5.5.4) + jiti: 1.21.6 + typescript: 5.5.4 + + cosmiconfig@9.0.0(typescript@5.5.4): + dependencies: + env-paths: 2.2.1 + import-fresh: 3.3.0 + js-yaml: 4.1.0 + parse-json: 5.2.0 + optionalDependencies: + typescript: 5.5.4 + + cropperjs@1.6.2: {} + + cross-spawn@7.0.3: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + crypto-js@4.2.0: {} + + css-declaration-sorter@6.4.1(postcss@8.4.41): + dependencies: + postcss: 8.4.41 + + css-declaration-sorter@7.2.0(postcss@8.4.41): + dependencies: + postcss: 8.4.41 + + css-functions-list@3.2.2: {} + + css-select@4.3.0: + dependencies: + boolbase: 1.0.0 + css-what: 6.1.0 + domhandler: 4.3.1 + domutils: 2.8.0 + nth-check: 2.1.1 + + css-select@5.1.0: + dependencies: + boolbase: 1.0.0 + css-what: 6.1.0 + domhandler: 5.0.3 + domutils: 3.1.0 + nth-check: 2.1.1 + + css-tree@1.1.3: + dependencies: + mdn-data: 2.0.14 + source-map: 0.6.1 + + css-tree@2.2.1: + dependencies: + mdn-data: 2.0.28 + source-map-js: 1.2.0 + + css-tree@2.3.1: + dependencies: + mdn-data: 2.0.30 + source-map-js: 1.2.0 + + css-what@6.1.0: {} + + cssesc@3.0.0: {} + + cssnano-preset-default@5.2.14(postcss@8.4.41): + dependencies: + css-declaration-sorter: 6.4.1(postcss@8.4.41) + cssnano-utils: 3.1.0(postcss@8.4.41) + postcss: 8.4.41 + postcss-calc: 8.2.4(postcss@8.4.41) + postcss-colormin: 5.3.1(postcss@8.4.41) + postcss-convert-values: 5.1.3(postcss@8.4.41) + postcss-discard-comments: 5.1.2(postcss@8.4.41) + postcss-discard-duplicates: 5.1.0(postcss@8.4.41) + postcss-discard-empty: 5.1.1(postcss@8.4.41) + postcss-discard-overridden: 5.1.0(postcss@8.4.41) + postcss-merge-longhand: 5.1.7(postcss@8.4.41) + postcss-merge-rules: 5.1.4(postcss@8.4.41) + postcss-minify-font-values: 5.1.0(postcss@8.4.41) + postcss-minify-gradients: 5.1.1(postcss@8.4.41) + postcss-minify-params: 5.1.4(postcss@8.4.41) + postcss-minify-selectors: 5.2.1(postcss@8.4.41) + postcss-normalize-charset: 5.1.0(postcss@8.4.41) + postcss-normalize-display-values: 5.1.0(postcss@8.4.41) + postcss-normalize-positions: 5.1.1(postcss@8.4.41) + postcss-normalize-repeat-style: 5.1.1(postcss@8.4.41) + postcss-normalize-string: 5.1.0(postcss@8.4.41) + postcss-normalize-timing-functions: 5.1.0(postcss@8.4.41) + postcss-normalize-unicode: 5.1.1(postcss@8.4.41) + postcss-normalize-url: 5.1.0(postcss@8.4.41) + postcss-normalize-whitespace: 5.1.1(postcss@8.4.41) + postcss-ordered-values: 5.1.3(postcss@8.4.41) + postcss-reduce-initial: 5.1.2(postcss@8.4.41) + postcss-reduce-transforms: 5.1.0(postcss@8.4.41) + postcss-svgo: 5.1.0(postcss@8.4.41) + postcss-unique-selectors: 5.1.1(postcss@8.4.41) + + cssnano-preset-default@7.0.5(postcss@8.4.41): + dependencies: + browserslist: 4.23.3 + css-declaration-sorter: 7.2.0(postcss@8.4.41) + cssnano-utils: 5.0.0(postcss@8.4.41) + postcss: 8.4.41 + postcss-calc: 10.0.2(postcss@8.4.41) + postcss-colormin: 7.0.2(postcss@8.4.41) + postcss-convert-values: 7.0.3(postcss@8.4.41) + postcss-discard-comments: 7.0.2(postcss@8.4.41) + postcss-discard-duplicates: 7.0.1(postcss@8.4.41) + postcss-discard-empty: 7.0.0(postcss@8.4.41) + postcss-discard-overridden: 7.0.0(postcss@8.4.41) + postcss-merge-longhand: 7.0.3(postcss@8.4.41) + postcss-merge-rules: 7.0.3(postcss@8.4.41) + postcss-minify-font-values: 7.0.0(postcss@8.4.41) + postcss-minify-gradients: 7.0.0(postcss@8.4.41) + postcss-minify-params: 7.0.2(postcss@8.4.41) + postcss-minify-selectors: 7.0.3(postcss@8.4.41) + postcss-normalize-charset: 7.0.0(postcss@8.4.41) + postcss-normalize-display-values: 7.0.0(postcss@8.4.41) + postcss-normalize-positions: 7.0.0(postcss@8.4.41) + postcss-normalize-repeat-style: 7.0.0(postcss@8.4.41) + postcss-normalize-string: 7.0.0(postcss@8.4.41) + postcss-normalize-timing-functions: 7.0.0(postcss@8.4.41) + postcss-normalize-unicode: 7.0.2(postcss@8.4.41) + postcss-normalize-url: 7.0.0(postcss@8.4.41) + postcss-normalize-whitespace: 7.0.0(postcss@8.4.41) + postcss-ordered-values: 7.0.1(postcss@8.4.41) + postcss-reduce-initial: 7.0.2(postcss@8.4.41) + postcss-reduce-transforms: 7.0.0(postcss@8.4.41) + postcss-svgo: 7.0.1(postcss@8.4.41) + postcss-unique-selectors: 7.0.2(postcss@8.4.41) + + cssnano-preset-lite@2.1.3(postcss@8.4.41): + dependencies: + cssnano-utils: 3.1.0(postcss@8.4.41) + postcss: 8.4.41 + postcss-discard-comments: 5.1.2(postcss@8.4.41) + postcss-discard-empty: 5.1.1(postcss@8.4.41) + postcss-normalize-whitespace: 5.1.1(postcss@8.4.41) + + cssnano-utils@3.1.0(postcss@8.4.41): + dependencies: + postcss: 8.4.41 + + cssnano-utils@5.0.0(postcss@8.4.41): + dependencies: + postcss: 8.4.41 + + cssnano@5.1.15(postcss@8.4.41): + dependencies: + cssnano-preset-default: 5.2.14(postcss@8.4.41) + lilconfig: 2.1.0 + postcss: 8.4.41 + yaml: 1.10.2 + + cssnano@7.0.5(postcss@8.4.41): + dependencies: + cssnano-preset-default: 7.0.5(postcss@8.4.41) + lilconfig: 3.1.2 + postcss: 8.4.41 + + csso@4.2.0: + dependencies: + css-tree: 1.1.3 + + csso@5.0.5: + dependencies: + css-tree: 2.2.1 + + csstype@3.1.3: {} + + dargs@8.1.0: {} + + dayjs@1.11.12: {} + + de-indent@1.0.2: {} + + debug@4.3.6: + dependencies: + ms: 2.1.2 + + deep-is@0.1.4: {} + + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.0 + es-errors: 1.3.0 + gopd: 1.0.1 + + define-lazy-prop@2.0.0: {} + + defu@6.1.4: + optional: true + + delayed-stream@1.0.0: {} + + destr@2.0.3: + optional: true + + didyoumean@1.2.2: {} + + dir-glob@3.0.1: + dependencies: + path-type: 4.0.0 + + dlv@1.1.3: {} + + dom-serializer@1.4.1: + dependencies: + domelementtype: 2.3.0 + domhandler: 4.3.1 + entities: 2.2.0 + + dom-serializer@2.0.0: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + entities: 4.5.0 + + domelementtype@2.3.0: {} + + domhandler@4.3.1: + dependencies: + domelementtype: 2.3.0 + + domhandler@5.0.3: + dependencies: + domelementtype: 2.3.0 + + domutils@2.8.0: + dependencies: + dom-serializer: 1.4.1 + domelementtype: 2.3.0 + domhandler: 4.3.1 + + domutils@3.1.0: + dependencies: + dom-serializer: 2.0.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 + + dot-prop@5.3.0: + dependencies: + is-obj: 2.0.0 + + dotenv@16.4.5: + optional: true + + eastasianwidth@0.2.0: {} + + echarts@5.5.1: + dependencies: + tslib: 2.3.0 + zrender: 5.6.0 + + electron-to-chromium@1.5.11: {} + + element-plus@2.8.0(vue@3.4.38(typescript@5.5.4)): + dependencies: + '@ctrl/tinycolor': 3.6.1 + '@element-plus/icons-vue': 2.3.1(vue@3.4.38(typescript@5.5.4)) + '@floating-ui/dom': 1.6.10 + '@popperjs/core': '@sxzz/popperjs-es@2.11.7' + '@types/lodash': 4.17.7 + '@types/lodash-es': 4.17.12 + '@vueuse/core': 9.13.0(vue@3.4.38(typescript@5.5.4)) + async-validator: 4.2.5 + dayjs: 1.11.12 + escape-html: 1.0.3 + lodash: 4.17.21 + lodash-es: 4.17.21 + lodash-unified: 1.0.3(@types/lodash-es@4.17.12)(lodash-es@4.17.21)(lodash@4.17.21) + memoize-one: 6.0.0 + normalize-wheel-es: 1.2.0 + vue: 3.4.38(typescript@5.5.4) + transitivePeerDependencies: + - '@vue/composition-api' + + emoji-regex@10.3.0: {} + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + entities@2.2.0: {} + + entities@4.5.0: {} + + env-paths@2.2.1: {} + + environment@1.1.0: {} + + error-ex@1.3.2: + dependencies: + is-arrayish: 0.2.1 + + es-define-property@1.0.0: + dependencies: + get-intrinsic: 1.2.4 + + es-errors@1.3.0: {} + + es-module-lexer@0.4.1: {} + + esbuild@0.19.12: + optionalDependencies: + '@esbuild/aix-ppc64': 0.19.12 + '@esbuild/android-arm': 0.19.12 + '@esbuild/android-arm64': 0.19.12 + '@esbuild/android-x64': 0.19.12 + '@esbuild/darwin-arm64': 0.19.12 + '@esbuild/darwin-x64': 0.19.12 + '@esbuild/freebsd-arm64': 0.19.12 + '@esbuild/freebsd-x64': 0.19.12 + '@esbuild/linux-arm': 0.19.12 + '@esbuild/linux-arm64': 0.19.12 + '@esbuild/linux-ia32': 0.19.12 + '@esbuild/linux-loong64': 0.19.12 + '@esbuild/linux-mips64el': 0.19.12 + '@esbuild/linux-ppc64': 0.19.12 + '@esbuild/linux-riscv64': 0.19.12 + '@esbuild/linux-s390x': 0.19.12 + '@esbuild/linux-x64': 0.19.12 + '@esbuild/netbsd-x64': 0.19.12 + '@esbuild/openbsd-x64': 0.19.12 + '@esbuild/sunos-x64': 0.19.12 + '@esbuild/win32-arm64': 0.19.12 + '@esbuild/win32-ia32': 0.19.12 + '@esbuild/win32-x64': 0.19.12 + + esbuild@0.21.5: + optionalDependencies: + '@esbuild/aix-ppc64': 0.21.5 + '@esbuild/android-arm': 0.21.5 + '@esbuild/android-arm64': 0.21.5 + '@esbuild/android-x64': 0.21.5 + '@esbuild/darwin-arm64': 0.21.5 + '@esbuild/darwin-x64': 0.21.5 + '@esbuild/freebsd-arm64': 0.21.5 + '@esbuild/freebsd-x64': 0.21.5 + '@esbuild/linux-arm': 0.21.5 + '@esbuild/linux-arm64': 0.21.5 + '@esbuild/linux-ia32': 0.21.5 + '@esbuild/linux-loong64': 0.21.5 + '@esbuild/linux-mips64el': 0.21.5 + '@esbuild/linux-ppc64': 0.21.5 + '@esbuild/linux-riscv64': 0.21.5 + '@esbuild/linux-s390x': 0.21.5 + '@esbuild/linux-x64': 0.21.5 + '@esbuild/netbsd-x64': 0.21.5 + '@esbuild/openbsd-x64': 0.21.5 + '@esbuild/sunos-x64': 0.21.5 + '@esbuild/win32-arm64': 0.21.5 + '@esbuild/win32-ia32': 0.21.5 + '@esbuild/win32-x64': 0.21.5 + + escalade@3.1.2: {} + + escape-html@1.0.3: {} + + escape-string-regexp@1.0.5: {} + + escape-string-regexp@4.0.0: {} + + escape-string-regexp@5.0.0: + optional: true + + escodegen@2.1.0: + dependencies: + esprima: 4.0.1 + estraverse: 5.3.0 + esutils: 2.0.3 + optionalDependencies: + source-map: 0.6.1 + + eslint-config-prettier@9.1.0(eslint@9.9.0(jiti@1.21.6)): + dependencies: + eslint: 9.9.0(jiti@1.21.6) + + eslint-define-config@2.1.0: {} + + eslint-plugin-prettier@5.2.1(eslint-config-prettier@9.1.0(eslint@9.9.0(jiti@1.21.6)))(eslint@9.9.0(jiti@1.21.6))(prettier@3.3.3): + dependencies: + eslint: 9.9.0(jiti@1.21.6) + prettier: 3.3.3 + prettier-linter-helpers: 1.0.0 + synckit: 0.9.1 + optionalDependencies: + eslint-config-prettier: 9.1.0(eslint@9.9.0(jiti@1.21.6)) + + eslint-plugin-vue@9.27.0(eslint@9.9.0(jiti@1.21.6)): + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@9.9.0(jiti@1.21.6)) + eslint: 9.9.0(jiti@1.21.6) + globals: 13.24.0 + natural-compare: 1.4.0 + nth-check: 2.1.1 + postcss-selector-parser: 6.1.2 + semver: 7.6.3 + vue-eslint-parser: 9.4.3(eslint@9.9.0(jiti@1.21.6)) + xml-name-validator: 4.0.0 + transitivePeerDependencies: + - supports-color + + eslint-scope@7.2.2: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-scope@8.0.2: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.0.0: {} + + eslint@9.9.0(jiti@1.21.6): + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@9.9.0(jiti@1.21.6)) + '@eslint-community/regexpp': 4.11.0 + '@eslint/config-array': 0.17.1 + '@eslint/eslintrc': 3.1.0 + '@eslint/js': 9.9.0 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.3.0 + '@nodelib/fs.walk': 1.2.8 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.3 + debug: 4.3.6 + escape-string-regexp: 4.0.0 + eslint-scope: 8.0.2 + eslint-visitor-keys: 4.0.0 + espree: 10.1.0 + esquery: 1.6.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + is-path-inside: 3.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + levn: 0.4.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + strip-ansi: 6.0.1 + text-table: 0.2.0 + optionalDependencies: + jiti: 1.21.6 + transitivePeerDependencies: + - supports-color + + espree@10.1.0: + dependencies: + acorn: 8.12.1 + acorn-jsx: 5.3.2(acorn@8.12.1) + eslint-visitor-keys: 4.0.0 + + espree@9.6.1: + dependencies: + acorn: 8.12.1 + acorn-jsx: 5.3.2(acorn@8.12.1) + eslint-visitor-keys: 3.4.3 + + esprima@4.0.1: {} + + esquery@1.6.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + estree-walker@2.0.2: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.5 + + esutils@2.0.3: {} + + eventemitter3@5.0.1: {} + + execa@8.0.1: + dependencies: + cross-spawn: 7.0.3 + get-stream: 8.0.1 + human-signals: 5.0.0 + is-stream: 3.0.0 + merge-stream: 2.0.0 + npm-run-path: 5.3.0 + onetime: 6.0.0 + signal-exit: 4.1.0 + strip-final-newline: 3.0.0 + + fast-deep-equal@3.1.3: {} + + fast-diff@1.3.0: {} + + fast-glob@3.3.2: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.7 + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fast-uri@3.0.1: {} + + fastest-levenshtein@1.0.16: {} + + fastq@1.17.1: + dependencies: + reusify: 1.0.4 + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + file-entry-cache@9.0.0: + dependencies: + flat-cache: 5.0.0 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + find-up@7.0.0: + dependencies: + locate-path: 7.2.0 + path-exists: 5.0.0 + unicorn-magic: 0.1.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.3.1 + keyv: 4.5.4 + + flat-cache@5.0.0: + dependencies: + flatted: 3.3.1 + keyv: 4.5.4 + + flatted@3.3.1: {} + + follow-redirects@1.15.6: {} + + foreground-child@3.3.0: + dependencies: + cross-spawn: 7.0.3 + signal-exit: 4.1.0 + + form-data@4.0.0: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + + fraction.js@4.3.7: {} + + framesync@6.1.2: + dependencies: + tslib: 2.4.0 + + fs-extra@10.1.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.1.0 + universalify: 2.0.1 + + fs-extra@11.2.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.1.0 + universalify: 2.0.1 + + fs-minipass@2.1.0: + dependencies: + minipass: 3.3.6 + optional: true + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + gensync@1.0.0-beta.2: {} + + get-caller-file@2.0.5: {} + + get-east-asian-width@1.2.0: {} + + get-intrinsic@1.2.4: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + has-proto: 1.0.3 + has-symbols: 1.0.3 + hasown: 2.0.2 + + get-stream@8.0.1: {} + + get-tsconfig@4.7.6: + dependencies: + resolve-pkg-maps: 1.0.0 + + giget@1.2.3: + dependencies: + citty: 0.1.6 + consola: 3.2.3 + defu: 6.1.4 + node-fetch-native: 1.6.4 + nypm: 0.3.9 + ohash: 1.1.3 + pathe: 1.1.2 + tar: 6.2.1 + optional: true + + git-raw-commits@4.0.0: + dependencies: + dargs: 8.1.0 + meow: 12.1.1 + split2: 4.2.0 + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + glob@10.4.5: + dependencies: + foreground-child: 3.3.0 + jackspeak: 3.4.3 + minimatch: 9.0.5 + minipass: 7.1.2 + package-json-from-dist: 1.0.0 + path-scurry: 1.11.1 + + global-directory@4.0.1: + dependencies: + ini: 4.1.1 + + global-modules@2.0.0: + dependencies: + global-prefix: 3.0.0 + + global-prefix@3.0.0: + dependencies: + ini: 1.3.8 + kind-of: 6.0.3 + which: 1.3.1 + + globals@11.12.0: {} + + globals@13.24.0: + dependencies: + type-fest: 0.20.2 + + globals@14.0.0: {} + + globby@11.1.0: + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.2 + ignore: 5.3.2 + merge2: 1.4.1 + slash: 3.0.0 + + globby@14.0.2: + dependencies: + '@sindresorhus/merge-streams': 2.3.0 + fast-glob: 3.3.2 + ignore: 5.3.2 + path-type: 5.0.0 + slash: 5.1.0 + unicorn-magic: 0.1.0 + optional: true + + globjoin@0.1.4: {} + + gopd@1.0.1: + dependencies: + get-intrinsic: 1.2.4 + + graceful-fs@4.2.11: {} + + gradient-string@2.0.2: + dependencies: + chalk: 4.1.2 + tinygradient: 1.1.5 + + graphemer@1.4.0: {} + + has-flag@3.0.0: {} + + has-flag@4.0.0: {} + + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.0 + + has-proto@1.0.3: {} + + has-symbols@1.0.3: {} + + hash-sum@2.0.0: + optional: true + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + he@1.2.0: {} + + hey-listen@1.0.8: {} + + hookable@5.5.3: + optional: true + + html-tags@3.3.1: {} + + htmlparser2@8.0.2: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + domutils: 3.1.0 + entities: 4.5.0 + + human-signals@5.0.0: {} + + husky@9.1.4: {} + + ignore@5.3.2: {} + + immediate@3.0.6: {} + + immutable@4.3.7: {} + + import-fresh@3.3.0: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + import-from-string@0.0.4: + dependencies: + esbuild: 0.19.12 + import-meta-resolve: 3.1.1 + + import-meta-resolve@3.1.1: {} + + import-meta-resolve@4.1.0: {} + + imurmurhash@0.1.4: {} + + inherits@2.0.3: {} + + ini@1.3.8: {} + + ini@4.1.1: {} + + is-arrayish@0.2.1: {} + + is-arrayish@0.3.2: {} + + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + + is-core-module@2.15.0: + dependencies: + hasown: 2.0.2 + + is-docker@2.2.1: {} + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-fullwidth-code-point@4.0.0: {} + + is-fullwidth-code-point@5.0.0: + dependencies: + get-east-asian-width: 1.2.0 + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-number@7.0.0: {} + + is-obj@2.0.0: {} + + is-path-inside@3.0.3: {} + + is-plain-object@5.0.0: {} + + is-reference@3.0.2: + dependencies: + '@types/estree': 1.0.5 + + is-stream@3.0.0: {} + + is-text-path@2.0.0: + dependencies: + text-extensions: 2.4.0 + + is-wsl@2.2.0: + dependencies: + is-docker: 2.2.1 + + isexe@2.0.0: {} + + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + + jiti@1.21.6: {} + + js-cookie@3.0.5: {} + + js-tokens@4.0.0: {} + + js-tokens@9.0.0: {} + + js-yaml@4.1.0: + dependencies: + argparse: 2.0.1 + + jsesc@2.5.2: {} + + json-buffer@3.0.1: {} + + json-parse-even-better-errors@2.3.1: {} + + json-schema-traverse@0.4.1: {} + + json-schema-traverse@1.0.0: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + json5@2.2.3: {} + + jsonc-eslint-parser@2.4.0: + dependencies: + acorn: 8.12.1 + eslint-visitor-keys: 3.4.3 + espree: 9.6.1 + semver: 7.6.3 + + jsonfile@6.1.0: + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + + jsonparse@1.3.1: {} + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + kind-of@6.0.3: {} + + klona@2.0.6: + optional: true + + knitwork@1.1.0: + optional: true + + known-css-properties@0.34.0: {} + + kolorist@1.8.0: {} + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lie@3.1.1: + dependencies: + immediate: 3.0.6 + + lilconfig@2.1.0: {} + + lilconfig@3.1.2: {} + + lines-and-columns@1.2.4: {} + + lint-staged@15.2.9: + dependencies: + chalk: 5.3.0 + commander: 12.1.0 + debug: 4.3.6 + execa: 8.0.1 + lilconfig: 3.1.2 + listr2: 8.2.4 + micromatch: 4.0.7 + pidtree: 0.6.0 + string-argv: 0.3.2 + yaml: 2.5.0 + transitivePeerDependencies: + - supports-color + + listr2@8.2.4: + dependencies: + cli-truncate: 4.0.0 + colorette: 2.0.20 + eventemitter3: 5.0.1 + log-update: 6.1.0 + rfdc: 1.4.1 + wrap-ansi: 9.0.0 + + local-pkg@0.5.0: + dependencies: + mlly: 1.7.1 + pkg-types: 1.1.3 + optional: true + + localforage@1.10.0: + dependencies: + lie: 3.1.1 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + locate-path@7.2.0: + dependencies: + p-locate: 6.0.0 + + lodash-es@4.17.21: {} + + lodash-unified@1.0.3(@types/lodash-es@4.17.12)(lodash-es@4.17.21)(lodash@4.17.21): + dependencies: + '@types/lodash-es': 4.17.12 + lodash: 4.17.21 + lodash-es: 4.17.21 + + lodash.camelcase@4.3.0: {} + + lodash.isplainobject@4.0.6: {} + + lodash.kebabcase@4.1.1: {} + + lodash.memoize@4.1.2: {} + + lodash.merge@4.6.2: {} + + lodash.mergewith@4.6.2: {} + + lodash.snakecase@4.1.1: {} + + lodash.startcase@4.4.0: {} + + lodash.truncate@4.4.2: {} + + lodash.uniq@4.5.0: {} + + lodash.upperfirst@4.3.1: {} + + lodash@4.17.21: {} + + log-update@6.1.0: + dependencies: + ansi-escapes: 7.0.0 + cli-cursor: 5.0.0 + slice-ansi: 7.1.0 + strip-ansi: 7.1.0 + wrap-ansi: 9.0.0 + + lru-cache@10.4.3: {} + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + magic-string@0.25.9: + dependencies: + sourcemap-codec: 1.4.8 + + magic-string@0.30.11: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 + + mathml-tag-names@2.1.3: {} + + mdn-data@2.0.14: {} + + mdn-data@2.0.28: {} + + mdn-data@2.0.30: {} + + memoize-one@6.0.0: {} + + meow@12.1.1: {} + + meow@13.2.0: {} + + merge-stream@2.0.0: {} + + merge2@1.4.1: {} + + micromatch@4.0.7: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mimic-fn@4.0.0: {} + + mimic-function@5.0.1: {} + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.11 + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.1 + + minimist@1.2.8: {} + + minipass@3.3.6: + dependencies: + yallist: 4.0.0 + optional: true + + minipass@5.0.0: + optional: true + + minipass@7.1.2: {} + + minizlib@2.1.2: + dependencies: + minipass: 3.3.6 + yallist: 4.0.0 + optional: true + + mitt@3.0.1: {} + + mkdirp@1.0.4: + optional: true + + mlly@1.7.1: + dependencies: + acorn: 8.12.1 + pathe: 1.1.2 + pkg-types: 1.1.3 + ufo: 1.5.4 + + mri@1.2.0: + optional: true + + ms@2.1.2: {} + + muggle-string@0.4.1: {} + + mz@2.7.0: + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + + nanoid@3.3.7: {} + + natural-compare@1.4.0: {} + + node-fetch-native@1.6.4: + optional: true + + node-releases@2.0.18: {} + + normalize-path@3.0.0: {} + + normalize-range@0.1.2: {} + + normalize-url@6.1.0: {} + + normalize-wheel-es@1.2.0: {} + + npm-run-path@4.0.1: + dependencies: + path-key: 3.1.1 + + npm-run-path@5.3.0: + dependencies: + path-key: 4.0.0 + + nprogress@0.2.0: {} + + nth-check@2.1.1: + dependencies: + boolbase: 1.0.0 + + nypm@0.3.9: + dependencies: + citty: 0.1.6 + consola: 3.2.3 + execa: 8.0.1 + pathe: 1.1.2 + pkg-types: 1.1.3 + ufo: 1.5.4 + optional: true + + object-assign@4.1.1: {} + + object-hash@3.0.0: {} + + object-inspect@1.13.2: {} + + ohash@1.1.3: + optional: true + + onetime@6.0.0: + dependencies: + mimic-fn: 4.0.0 + + onetime@7.0.0: + dependencies: + mimic-function: 5.0.1 + + open@8.4.2: + dependencies: + define-lazy-prop: 2.0.0 + is-docker: 2.2.1 + is-wsl: 2.2.0 + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-limit@4.0.0: + dependencies: + yocto-queue: 1.1.1 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + p-locate@6.0.0: + dependencies: + p-limit: 4.0.0 + + package-json-from-dist@1.0.0: {} + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parse-json@5.2.0: + dependencies: + '@babel/code-frame': 7.24.7 + error-ex: 1.3.2 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + + path-browserify@1.0.1: {} + + path-exists@4.0.0: {} + + path-exists@5.0.0: {} + + path-key@3.1.1: {} + + path-key@4.0.0: {} + + path-parse@1.0.7: {} + + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.2 + + path-to-regexp@6.2.2: {} + + path-type@4.0.0: {} + + path-type@5.0.0: + optional: true + + path@0.12.7: + dependencies: + process: 0.11.10 + util: 0.10.4 + + pathe@1.1.2: {} + + perfect-debounce@1.0.0: + optional: true + + picocolors@1.0.1: {} + + picomatch@2.3.1: {} + + pidtree@0.6.0: {} + + pify@2.3.0: {} + + pinia@2.2.2(typescript@5.5.4)(vue@3.4.38(typescript@5.5.4)): + dependencies: + '@vue/devtools-api': 6.6.3 + vue: 3.4.38(typescript@5.5.4) + vue-demi: 0.14.10(vue@3.4.38(typescript@5.5.4)) + optionalDependencies: + typescript: 5.5.4 + + pinyin-pro@3.24.2: {} + + pirates@4.0.6: {} + + pkg-types@1.1.3: + dependencies: + confbox: 0.1.7 + mlly: 1.7.1 + pathe: 1.1.2 + + plus-pro-components@0.1.17(element-plus@2.8.0(vue@3.4.38(typescript@5.5.4)))(vue@3.4.38(typescript@5.5.4)): + dependencies: + '@element-plus/icons-vue': 2.3.1(vue@3.4.38(typescript@5.5.4)) + element-plus: 2.8.0(vue@3.4.38(typescript@5.5.4)) + lodash-es: 4.17.21 + sortablejs: 1.15.2 + vue: 3.4.38(typescript@5.5.4) + + popmotion@11.0.5: + dependencies: + framesync: 6.1.2 + hey-listen: 1.0.8 + style-value-types: 5.1.2 + tslib: 2.4.0 + + postcss-calc@10.0.2(postcss@8.4.41): + dependencies: + postcss: 8.4.41 + postcss-selector-parser: 6.1.2 + postcss-value-parser: 4.2.0 + + postcss-calc@8.2.4(postcss@8.4.41): + dependencies: + postcss: 8.4.41 + postcss-selector-parser: 6.1.2 + postcss-value-parser: 4.2.0 + + postcss-colormin@5.3.1(postcss@8.4.41): + dependencies: + browserslist: 4.23.3 + caniuse-api: 3.0.0 + colord: 2.9.3 + postcss: 8.4.41 + postcss-value-parser: 4.2.0 + + postcss-colormin@7.0.2(postcss@8.4.41): + dependencies: + browserslist: 4.23.3 + caniuse-api: 3.0.0 + colord: 2.9.3 + postcss: 8.4.41 + postcss-value-parser: 4.2.0 + + postcss-convert-values@5.1.3(postcss@8.4.41): + dependencies: + browserslist: 4.23.3 + postcss: 8.4.41 + postcss-value-parser: 4.2.0 + + postcss-convert-values@7.0.3(postcss@8.4.41): + dependencies: + browserslist: 4.23.3 + postcss: 8.4.41 + postcss-value-parser: 4.2.0 + + postcss-discard-comments@5.1.2(postcss@8.4.41): + dependencies: + postcss: 8.4.41 + + postcss-discard-comments@7.0.2(postcss@8.4.41): + dependencies: + postcss: 8.4.41 + postcss-selector-parser: 6.1.2 + + postcss-discard-duplicates@5.1.0(postcss@8.4.41): + dependencies: + postcss: 8.4.41 + + postcss-discard-duplicates@7.0.1(postcss@8.4.41): + dependencies: + postcss: 8.4.41 + + postcss-discard-empty@5.1.1(postcss@8.4.41): + dependencies: + postcss: 8.4.41 + + postcss-discard-empty@7.0.0(postcss@8.4.41): + dependencies: + postcss: 8.4.41 + + postcss-discard-overridden@5.1.0(postcss@8.4.41): + dependencies: + postcss: 8.4.41 + + postcss-discard-overridden@7.0.0(postcss@8.4.41): + dependencies: + postcss: 8.4.41 + + postcss-html@1.7.0: + dependencies: + htmlparser2: 8.0.2 + js-tokens: 9.0.0 + postcss: 8.4.41 + postcss-safe-parser: 6.0.0(postcss@8.4.41) + + postcss-import@15.1.0(postcss@8.4.41): + dependencies: + postcss: 8.4.41 + postcss-value-parser: 4.2.0 + read-cache: 1.0.0 + resolve: 1.22.8 + + postcss-import@16.1.0(postcss@8.4.41): + dependencies: + postcss: 8.4.41 + postcss-value-parser: 4.2.0 + read-cache: 1.0.0 + resolve: 1.22.8 + + postcss-js@4.0.1(postcss@8.4.41): + dependencies: + camelcase-css: 2.0.1 + postcss: 8.4.41 + + postcss-load-config@4.0.2(postcss@8.4.41): + dependencies: + lilconfig: 3.1.2 + yaml: 2.5.0 + optionalDependencies: + postcss: 8.4.41 + + postcss-media-query-parser@0.2.3: {} + + postcss-merge-longhand@5.1.7(postcss@8.4.41): + dependencies: + postcss: 8.4.41 + postcss-value-parser: 4.2.0 + stylehacks: 5.1.1(postcss@8.4.41) + + postcss-merge-longhand@7.0.3(postcss@8.4.41): + dependencies: + postcss: 8.4.41 + postcss-value-parser: 4.2.0 + stylehacks: 7.0.3(postcss@8.4.41) + + postcss-merge-rules@5.1.4(postcss@8.4.41): + dependencies: + browserslist: 4.23.3 + caniuse-api: 3.0.0 + cssnano-utils: 3.1.0(postcss@8.4.41) + postcss: 8.4.41 + postcss-selector-parser: 6.1.2 + + postcss-merge-rules@7.0.3(postcss@8.4.41): + dependencies: + browserslist: 4.23.3 + caniuse-api: 3.0.0 + cssnano-utils: 5.0.0(postcss@8.4.41) + postcss: 8.4.41 + postcss-selector-parser: 6.1.2 + + postcss-minify-font-values@5.1.0(postcss@8.4.41): + dependencies: + postcss: 8.4.41 + postcss-value-parser: 4.2.0 + + postcss-minify-font-values@7.0.0(postcss@8.4.41): + dependencies: + postcss: 8.4.41 + postcss-value-parser: 4.2.0 + + postcss-minify-gradients@5.1.1(postcss@8.4.41): + dependencies: + colord: 2.9.3 + cssnano-utils: 3.1.0(postcss@8.4.41) + postcss: 8.4.41 + postcss-value-parser: 4.2.0 + + postcss-minify-gradients@7.0.0(postcss@8.4.41): + dependencies: + colord: 2.9.3 + cssnano-utils: 5.0.0(postcss@8.4.41) + postcss: 8.4.41 + postcss-value-parser: 4.2.0 + + postcss-minify-params@5.1.4(postcss@8.4.41): + dependencies: + browserslist: 4.23.3 + cssnano-utils: 3.1.0(postcss@8.4.41) + postcss: 8.4.41 + postcss-value-parser: 4.2.0 + + postcss-minify-params@7.0.2(postcss@8.4.41): + dependencies: + browserslist: 4.23.3 + cssnano-utils: 5.0.0(postcss@8.4.41) + postcss: 8.4.41 + postcss-value-parser: 4.2.0 + + postcss-minify-selectors@5.2.1(postcss@8.4.41): + dependencies: + postcss: 8.4.41 + postcss-selector-parser: 6.1.2 + + postcss-minify-selectors@7.0.3(postcss@8.4.41): + dependencies: + cssesc: 3.0.0 + postcss: 8.4.41 + postcss-selector-parser: 6.1.2 + + postcss-nested@6.2.0(postcss@8.4.41): + dependencies: + postcss: 8.4.41 + postcss-selector-parser: 6.1.2 + + postcss-normalize-charset@5.1.0(postcss@8.4.41): + dependencies: + postcss: 8.4.41 + + postcss-normalize-charset@7.0.0(postcss@8.4.41): + dependencies: + postcss: 8.4.41 + + postcss-normalize-display-values@5.1.0(postcss@8.4.41): + dependencies: + postcss: 8.4.41 + postcss-value-parser: 4.2.0 + + postcss-normalize-display-values@7.0.0(postcss@8.4.41): + dependencies: + postcss: 8.4.41 + postcss-value-parser: 4.2.0 + + postcss-normalize-positions@5.1.1(postcss@8.4.41): + dependencies: + postcss: 8.4.41 + postcss-value-parser: 4.2.0 + + postcss-normalize-positions@7.0.0(postcss@8.4.41): + dependencies: + postcss: 8.4.41 + postcss-value-parser: 4.2.0 + + postcss-normalize-repeat-style@5.1.1(postcss@8.4.41): + dependencies: + postcss: 8.4.41 + postcss-value-parser: 4.2.0 + + postcss-normalize-repeat-style@7.0.0(postcss@8.4.41): + dependencies: + postcss: 8.4.41 + postcss-value-parser: 4.2.0 + + postcss-normalize-string@5.1.0(postcss@8.4.41): + dependencies: + postcss: 8.4.41 + postcss-value-parser: 4.2.0 + + postcss-normalize-string@7.0.0(postcss@8.4.41): + dependencies: + postcss: 8.4.41 + postcss-value-parser: 4.2.0 + + postcss-normalize-timing-functions@5.1.0(postcss@8.4.41): + dependencies: + postcss: 8.4.41 + postcss-value-parser: 4.2.0 + + postcss-normalize-timing-functions@7.0.0(postcss@8.4.41): + dependencies: + postcss: 8.4.41 + postcss-value-parser: 4.2.0 + + postcss-normalize-unicode@5.1.1(postcss@8.4.41): + dependencies: + browserslist: 4.23.3 + postcss: 8.4.41 + postcss-value-parser: 4.2.0 + + postcss-normalize-unicode@7.0.2(postcss@8.4.41): + dependencies: + browserslist: 4.23.3 + postcss: 8.4.41 + postcss-value-parser: 4.2.0 + + postcss-normalize-url@5.1.0(postcss@8.4.41): + dependencies: + normalize-url: 6.1.0 + postcss: 8.4.41 + postcss-value-parser: 4.2.0 + + postcss-normalize-url@7.0.0(postcss@8.4.41): + dependencies: + postcss: 8.4.41 + postcss-value-parser: 4.2.0 + + postcss-normalize-whitespace@5.1.1(postcss@8.4.41): + dependencies: + postcss: 8.4.41 + postcss-value-parser: 4.2.0 + + postcss-normalize-whitespace@7.0.0(postcss@8.4.41): + dependencies: + postcss: 8.4.41 + postcss-value-parser: 4.2.0 + + postcss-ordered-values@5.1.3(postcss@8.4.41): + dependencies: + cssnano-utils: 3.1.0(postcss@8.4.41) + postcss: 8.4.41 + postcss-value-parser: 4.2.0 + + postcss-ordered-values@7.0.1(postcss@8.4.41): + dependencies: + cssnano-utils: 5.0.0(postcss@8.4.41) + postcss: 8.4.41 + postcss-value-parser: 4.2.0 + + postcss-reduce-initial@5.1.2(postcss@8.4.41): + dependencies: + browserslist: 4.23.3 + caniuse-api: 3.0.0 + postcss: 8.4.41 + + postcss-reduce-initial@7.0.2(postcss@8.4.41): + dependencies: + browserslist: 4.23.3 + caniuse-api: 3.0.0 + postcss: 8.4.41 + + postcss-reduce-transforms@5.1.0(postcss@8.4.41): + dependencies: + postcss: 8.4.41 + postcss-value-parser: 4.2.0 + + postcss-reduce-transforms@7.0.0(postcss@8.4.41): + dependencies: + postcss: 8.4.41 + postcss-value-parser: 4.2.0 + + postcss-resolve-nested-selector@0.1.6: {} + + postcss-safe-parser@6.0.0(postcss@8.4.41): + dependencies: + postcss: 8.4.41 + + postcss-safe-parser@7.0.0(postcss@8.4.41): + dependencies: + postcss: 8.4.41 + + postcss-scss@4.0.9(postcss@8.4.41): + dependencies: + postcss: 8.4.41 + + postcss-selector-parser@6.1.2: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss-sorting@8.0.2(postcss@8.4.41): + dependencies: + postcss: 8.4.41 + + postcss-svgo@5.1.0(postcss@8.4.41): + dependencies: + postcss: 8.4.41 + postcss-value-parser: 4.2.0 + svgo: 2.8.0 + + postcss-svgo@7.0.1(postcss@8.4.41): + dependencies: + postcss: 8.4.41 + postcss-value-parser: 4.2.0 + svgo: 3.3.2 + + postcss-unique-selectors@5.1.1(postcss@8.4.41): + dependencies: + postcss: 8.4.41 + postcss-selector-parser: 6.1.2 + + postcss-unique-selectors@7.0.2(postcss@8.4.41): + dependencies: + postcss: 8.4.41 + postcss-selector-parser: 6.1.2 + + postcss-value-parser@4.2.0: {} + + postcss@8.4.41: + dependencies: + nanoid: 3.3.7 + picocolors: 1.0.1 + source-map-js: 1.2.0 + + prelude-ls@1.2.1: {} + + prettier-linter-helpers@1.0.0: + dependencies: + fast-diff: 1.3.0 + + prettier@2.8.8: {} + + prettier@3.3.3: {} + + process@0.11.10: {} + + proxy-from-env@1.1.0: {} + + punycode@2.3.1: {} + + qs@6.13.0: + dependencies: + side-channel: 1.0.6 + + queue-microtask@1.2.3: {} + + rc9@2.1.2: + dependencies: + defu: 6.1.4 + destr: 2.0.3 + optional: true + + read-cache@1.0.0: + dependencies: + pify: 2.3.0 + + readdirp@3.6.0: + dependencies: + picomatch: 2.3.1 + + require-directory@2.1.1: {} + + require-from-string@2.0.2: {} + + resolve-from@4.0.0: {} + + resolve-from@5.0.0: {} + + resolve-pkg-maps@1.0.0: {} + + resolve@1.22.8: + dependencies: + is-core-module: 2.15.0 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + responsive-storage@2.2.0: {} + + restore-cursor@5.1.0: + dependencies: + onetime: 7.0.0 + signal-exit: 4.1.0 + + reusify@1.0.4: {} + + rfdc@1.4.1: {} + + rimraf@5.0.10: + dependencies: + glob: 10.4.5 + + rollup-plugin-external-globals@0.10.0(rollup@4.21.0): + dependencies: + '@rollup/pluginutils': 5.1.0(rollup@4.21.0) + estree-walker: 3.0.3 + is-reference: 3.0.2 + magic-string: 0.30.11 + rollup: 4.21.0 + + rollup-plugin-visualizer@5.12.0(rollup@4.21.0): + dependencies: + open: 8.4.2 + picomatch: 2.3.1 + source-map: 0.7.4 + yargs: 17.7.2 + optionalDependencies: + rollup: 4.21.0 + + rollup@4.21.0: + dependencies: + '@types/estree': 1.0.5 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.21.0 + '@rollup/rollup-android-arm64': 4.21.0 + '@rollup/rollup-darwin-arm64': 4.21.0 + '@rollup/rollup-darwin-x64': 4.21.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.21.0 + '@rollup/rollup-linux-arm-musleabihf': 4.21.0 + '@rollup/rollup-linux-arm64-gnu': 4.21.0 + '@rollup/rollup-linux-arm64-musl': 4.21.0 + '@rollup/rollup-linux-powerpc64le-gnu': 4.21.0 + '@rollup/rollup-linux-riscv64-gnu': 4.21.0 + '@rollup/rollup-linux-s390x-gnu': 4.21.0 + '@rollup/rollup-linux-x64-gnu': 4.21.0 + '@rollup/rollup-linux-x64-musl': 4.21.0 + '@rollup/rollup-win32-arm64-msvc': 4.21.0 + '@rollup/rollup-win32-ia32-msvc': 4.21.0 + '@rollup/rollup-win32-x64-msvc': 4.21.0 + fsevents: 2.3.3 + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + sass@1.77.8: + dependencies: + chokidar: 3.6.0 + immutable: 4.3.7 + source-map-js: 1.2.0 + + scule@1.3.0: + optional: true + + semver@6.3.1: {} + + semver@7.6.3: {} + + set-function-length@1.2.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.2.4 + gopd: 1.0.1 + has-property-descriptors: 1.0.2 + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + side-channel@1.0.6: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + get-intrinsic: 1.2.4 + object-inspect: 1.13.2 + + signal-exit@4.1.0: {} + + simple-swizzle@0.2.2: + dependencies: + is-arrayish: 0.3.2 + + slash@3.0.0: {} + + slash@5.1.0: + optional: true + + slice-ansi@4.0.0: + dependencies: + ansi-styles: 4.3.0 + astral-regex: 2.0.0 + is-fullwidth-code-point: 3.0.0 + + slice-ansi@5.0.0: + dependencies: + ansi-styles: 6.2.1 + is-fullwidth-code-point: 4.0.0 + + slice-ansi@7.1.0: + dependencies: + ansi-styles: 6.2.1 + is-fullwidth-code-point: 5.0.0 + + sortablejs@1.15.2: {} + + source-map-js@1.2.0: {} + + source-map@0.6.1: {} + + source-map@0.7.4: {} + + sourcemap-codec@1.4.8: {} + + split2@4.2.0: {} + + stable@0.1.8: {} + + std-env@3.7.0: + optional: true + + string-argv@0.3.2: {} + + string-hash@1.1.3: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.0 + + string-width@7.2.0: + dependencies: + emoji-regex: 10.3.0 + get-east-asian-width: 1.2.0 + strip-ansi: 7.1.0 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.1.0: + dependencies: + ansi-regex: 6.0.1 + + strip-final-newline@3.0.0: {} + + strip-json-comments@3.1.1: {} + + strip-literal@2.1.0: + dependencies: + js-tokens: 9.0.0 + optional: true + + style-value-types@5.1.2: + dependencies: + hey-listen: 1.0.8 + tslib: 2.4.0 + + stylehacks@5.1.1(postcss@8.4.41): + dependencies: + browserslist: 4.23.3 + postcss: 8.4.41 + postcss-selector-parser: 6.1.2 + + stylehacks@7.0.3(postcss@8.4.41): + dependencies: + browserslist: 4.23.3 + postcss: 8.4.41 + postcss-selector-parser: 6.1.2 + + stylelint-config-html@1.1.0(postcss-html@1.7.0)(stylelint@16.8.2(typescript@5.5.4)): + dependencies: + postcss-html: 1.7.0 + stylelint: 16.8.2(typescript@5.5.4) + + stylelint-config-recess-order@5.0.1(stylelint@16.8.2(typescript@5.5.4)): + dependencies: + stylelint: 16.8.2(typescript@5.5.4) + stylelint-order: 6.0.4(stylelint@16.8.2(typescript@5.5.4)) + + stylelint-config-recommended-scss@14.1.0(postcss@8.4.41)(stylelint@16.8.2(typescript@5.5.4)): + dependencies: + postcss-scss: 4.0.9(postcss@8.4.41) + stylelint: 16.8.2(typescript@5.5.4) + stylelint-config-recommended: 14.0.1(stylelint@16.8.2(typescript@5.5.4)) + stylelint-scss: 6.5.0(stylelint@16.8.2(typescript@5.5.4)) + optionalDependencies: + postcss: 8.4.41 + + stylelint-config-recommended-vue@1.5.0(postcss-html@1.7.0)(stylelint@16.8.2(typescript@5.5.4)): + dependencies: + postcss-html: 1.7.0 + semver: 7.6.3 + stylelint: 16.8.2(typescript@5.5.4) + stylelint-config-html: 1.1.0(postcss-html@1.7.0)(stylelint@16.8.2(typescript@5.5.4)) + stylelint-config-recommended: 14.0.1(stylelint@16.8.2(typescript@5.5.4)) + + stylelint-config-recommended@14.0.1(stylelint@16.8.2(typescript@5.5.4)): + dependencies: + stylelint: 16.8.2(typescript@5.5.4) + + stylelint-config-standard-scss@13.1.0(postcss@8.4.41)(stylelint@16.8.2(typescript@5.5.4)): + dependencies: + stylelint: 16.8.2(typescript@5.5.4) + stylelint-config-recommended-scss: 14.1.0(postcss@8.4.41)(stylelint@16.8.2(typescript@5.5.4)) + stylelint-config-standard: 36.0.1(stylelint@16.8.2(typescript@5.5.4)) + optionalDependencies: + postcss: 8.4.41 + + stylelint-config-standard@36.0.1(stylelint@16.8.2(typescript@5.5.4)): + dependencies: + stylelint: 16.8.2(typescript@5.5.4) + stylelint-config-recommended: 14.0.1(stylelint@16.8.2(typescript@5.5.4)) + + stylelint-order@6.0.4(stylelint@16.8.2(typescript@5.5.4)): + dependencies: + postcss: 8.4.41 + postcss-sorting: 8.0.2(postcss@8.4.41) + stylelint: 16.8.2(typescript@5.5.4) + + stylelint-prettier@5.0.2(prettier@3.3.3)(stylelint@16.8.2(typescript@5.5.4)): + dependencies: + prettier: 3.3.3 + prettier-linter-helpers: 1.0.0 + stylelint: 16.8.2(typescript@5.5.4) + + stylelint-scss@6.5.0(stylelint@16.8.2(typescript@5.5.4)): + dependencies: + css-tree: 2.3.1 + is-plain-object: 5.0.0 + known-css-properties: 0.34.0 + postcss-media-query-parser: 0.2.3 + postcss-resolve-nested-selector: 0.1.6 + postcss-selector-parser: 6.1.2 + postcss-value-parser: 4.2.0 + stylelint: 16.8.2(typescript@5.5.4) + + stylelint@16.8.2(typescript@5.5.4): + dependencies: + '@csstools/css-parser-algorithms': 3.0.1(@csstools/css-tokenizer@3.0.1) + '@csstools/css-tokenizer': 3.0.1 + '@csstools/media-query-list-parser': 3.0.1(@csstools/css-parser-algorithms@3.0.1(@csstools/css-tokenizer@3.0.1))(@csstools/css-tokenizer@3.0.1) + '@csstools/selector-specificity': 4.0.0(postcss-selector-parser@6.1.2) + '@dual-bundle/import-meta-resolve': 4.1.0 + balanced-match: 2.0.0 + colord: 2.9.3 + cosmiconfig: 9.0.0(typescript@5.5.4) + css-functions-list: 3.2.2 + css-tree: 2.3.1 + debug: 4.3.6 + fast-glob: 3.3.2 + fastest-levenshtein: 1.0.16 + file-entry-cache: 9.0.0 + global-modules: 2.0.0 + globby: 11.1.0 + globjoin: 0.1.4 + html-tags: 3.3.1 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-plain-object: 5.0.0 + known-css-properties: 0.34.0 + mathml-tag-names: 2.1.3 + meow: 13.2.0 + micromatch: 4.0.7 + normalize-path: 3.0.0 + picocolors: 1.0.1 + postcss: 8.4.41 + postcss-resolve-nested-selector: 0.1.6 + postcss-safe-parser: 7.0.0(postcss@8.4.41) + postcss-selector-parser: 6.1.2 + postcss-value-parser: 4.2.0 + resolve-from: 5.0.0 + string-width: 4.2.3 + strip-ansi: 7.1.0 + supports-hyperlinks: 3.0.0 + svg-tags: 1.0.0 + table: 6.8.2 + write-file-atomic: 5.0.1 + transitivePeerDependencies: + - supports-color + - typescript + + sucrase@3.35.0: + dependencies: + '@jridgewell/gen-mapping': 0.3.5 + commander: 4.1.1 + glob: 10.4.5 + lines-and-columns: 1.2.4 + mz: 2.7.0 + pirates: 4.0.6 + ts-interface-checker: 0.1.13 + + supports-color@5.5.0: + dependencies: + has-flag: 3.0.0 + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-hyperlinks@3.0.0: + dependencies: + has-flag: 4.0.0 + supports-color: 7.2.0 + + supports-preserve-symlinks-flag@1.0.0: {} + + svg-tags@1.0.0: {} + + svgo@2.8.0: + dependencies: + '@trysound/sax': 0.2.0 + commander: 7.2.0 + css-select: 4.3.0 + css-tree: 1.1.3 + csso: 4.2.0 + picocolors: 1.0.1 + stable: 0.1.8 + + svgo@3.3.2: + dependencies: + '@trysound/sax': 0.2.0 + commander: 7.2.0 + css-select: 5.1.0 + css-tree: 2.3.1 + css-what: 6.1.0 + csso: 5.0.5 + picocolors: 1.0.1 + + synckit@0.9.1: + dependencies: + '@pkgr/core': 0.1.1 + tslib: 2.6.3 + + table@6.8.2: + dependencies: + ajv: 8.17.1 + lodash.truncate: 4.4.2 + slice-ansi: 4.0.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + tailwindcss@3.4.10: + dependencies: + '@alloc/quick-lru': 5.2.0 + arg: 5.0.2 + chokidar: 3.6.0 + didyoumean: 1.2.2 + dlv: 1.1.3 + fast-glob: 3.3.2 + glob-parent: 6.0.2 + is-glob: 4.0.3 + jiti: 1.21.6 + lilconfig: 2.1.0 + micromatch: 4.0.7 + normalize-path: 3.0.0 + object-hash: 3.0.0 + picocolors: 1.0.1 + postcss: 8.4.41 + postcss-import: 15.1.0(postcss@8.4.41) + postcss-js: 4.0.1(postcss@8.4.41) + postcss-load-config: 4.0.2(postcss@8.4.41) + postcss-nested: 6.2.0(postcss@8.4.41) + postcss-selector-parser: 6.1.2 + resolve: 1.22.8 + sucrase: 3.35.0 + transitivePeerDependencies: + - ts-node + + tar@6.2.1: + dependencies: + chownr: 2.0.0 + fs-minipass: 2.1.0 + minipass: 5.0.0 + minizlib: 2.1.2 + mkdirp: 1.0.4 + yallist: 4.0.0 + optional: true + + text-extensions@2.4.0: {} + + text-table@0.2.0: {} + + thenify-all@1.6.0: + dependencies: + thenify: 3.3.1 + + thenify@3.3.1: + dependencies: + any-promise: 1.3.0 + + through@2.3.8: {} + + tiny-invariant@1.3.3: {} + + tinycolor2@1.6.0: {} + + tinygradient@1.1.5: + dependencies: + '@types/tinycolor2': 1.4.6 + tinycolor2: 1.6.0 + + tippy.js@6.3.7: + dependencies: + '@popperjs/core': 2.11.8 + + to-fast-properties@2.0.0: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + ts-api-utils@1.3.0(typescript@5.5.4): + dependencies: + typescript: 5.5.4 + + ts-interface-checker@0.1.13: {} + + tslib@2.3.0: {} + + tslib@2.4.0: {} + + tslib@2.6.3: {} + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + type-fest@0.20.2: {} + + type-fest@0.21.3: {} + + type-fest@2.19.0: {} + + typescript@5.5.4: {} + + ufo@1.5.4: {} + + uncrypto@0.1.3: + optional: true + + unctx@2.3.1: + dependencies: + acorn: 8.12.1 + estree-walker: 3.0.3 + magic-string: 0.30.11 + unplugin: 1.12.2 + optional: true + + undici-types@6.19.8: {} + + unicorn-magic@0.1.0: {} + + unimport@3.10.0(rollup@4.21.0): + dependencies: + '@rollup/pluginutils': 5.1.0(rollup@4.21.0) + acorn: 8.12.1 + escape-string-regexp: 5.0.0 + estree-walker: 3.0.3 + fast-glob: 3.3.2 + local-pkg: 0.5.0 + magic-string: 0.30.11 + mlly: 1.7.1 + pathe: 1.1.2 + pkg-types: 1.1.3 + scule: 1.3.0 + strip-literal: 2.1.0 + unplugin: 1.12.2 + transitivePeerDependencies: + - rollup + optional: true + + universalify@2.0.1: {} + + unplugin@1.12.2: + dependencies: + acorn: 8.12.1 + chokidar: 3.6.0 + webpack-sources: 3.2.3 + webpack-virtual-modules: 0.6.2 + + untyped@1.4.2: + dependencies: + '@babel/core': 7.25.2 + '@babel/standalone': 7.25.3 + '@babel/types': 7.25.2 + defu: 6.1.4 + jiti: 1.21.6 + mri: 1.2.0 + scule: 1.3.0 + transitivePeerDependencies: + - supports-color + optional: true + + update-browserslist-db@1.1.0(browserslist@4.23.3): + dependencies: + browserslist: 4.23.3 + escalade: 3.1.2 + picocolors: 1.0.1 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + util-deprecate@1.0.2: {} + + util@0.10.4: + dependencies: + inherits: 2.0.3 + + uuid@8.3.2: {} + + vite-plugin-cdn-import@1.0.1(rollup@4.21.0)(vite@5.4.1(@types/node@20.16.1)(sass@1.77.8)): + dependencies: + rollup-plugin-external-globals: 0.10.0(rollup@4.21.0) + vite-plugin-externals: 0.6.2(vite@5.4.1(@types/node@20.16.1)(sass@1.77.8)) + transitivePeerDependencies: + - rollup + - vite + + vite-plugin-checker@0.7.2(eslint@9.9.0(jiti@1.21.6))(optionator@0.9.4)(stylelint@16.8.2(typescript@5.5.4))(typescript@5.5.4)(vite@5.4.1(@types/node@20.16.1)(sass@1.77.8))(vue-tsc@2.0.29(typescript@5.5.4)): + dependencies: + '@babel/code-frame': 7.24.7 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + chokidar: 3.6.0 + commander: 8.3.0 + fast-glob: 3.3.2 + fs-extra: 11.2.0 + npm-run-path: 4.0.1 + strip-ansi: 6.0.1 + tiny-invariant: 1.3.3 + vite: 5.4.1(@types/node@20.16.1)(sass@1.77.8) + vscode-languageclient: 7.0.0 + vscode-languageserver: 7.0.0 + vscode-languageserver-textdocument: 1.0.12 + vscode-uri: 3.0.8 + optionalDependencies: + eslint: 9.9.0(jiti@1.21.6) + optionator: 0.9.4 + stylelint: 16.8.2(typescript@5.5.4) + typescript: 5.5.4 + vue-tsc: 2.0.29(typescript@5.5.4) + + vite-plugin-compression@0.5.1(vite@5.4.1(@types/node@20.16.1)(sass@1.77.8)): + dependencies: + chalk: 4.1.2 + debug: 4.3.6 + fs-extra: 10.1.0 + vite: 5.4.1(@types/node@20.16.1)(sass@1.77.8) + transitivePeerDependencies: + - supports-color + + vite-plugin-externals@0.6.2(vite@5.4.1(@types/node@20.16.1)(sass@1.77.8)): + dependencies: + acorn: 8.12.1 + es-module-lexer: 0.4.1 + fs-extra: 10.1.0 + magic-string: 0.25.9 + vite: 5.4.1(@types/node@20.16.1)(sass@1.77.8) + + vite-plugin-fake-server@2.1.1: + dependencies: + bundle-import: 0.0.1 + chokidar: 3.6.0 + fast-glob: 3.3.2 + path-to-regexp: 6.2.2 + picocolors: 1.0.1 + + vite-plugin-remove-console@2.2.0: {} + + vite-plugin-router-warn@1.0.0: {} + + vite-plugin-vue-inspector@5.1.3(vite@5.4.1(@types/node@20.16.1)(sass@1.77.8)): + dependencies: + '@babel/core': 7.25.2 + '@babel/plugin-proposal-decorators': 7.24.7(@babel/core@7.25.2) + '@babel/plugin-syntax-import-attributes': 7.24.7(@babel/core@7.25.2) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.25.2) + '@babel/plugin-transform-typescript': 7.25.2(@babel/core@7.25.2) + '@vue/babel-plugin-jsx': 1.2.2(@babel/core@7.25.2) + '@vue/compiler-dom': 3.4.38 + kolorist: 1.8.0 + magic-string: 0.30.11 + vite: 5.4.1(@types/node@20.16.1)(sass@1.77.8) + transitivePeerDependencies: + - supports-color + + vite-svg-loader@5.1.0(vue@3.4.38(typescript@5.5.4)): + dependencies: + svgo: 3.3.2 + vue: 3.4.38(typescript@5.5.4) + + vite@5.4.1(@types/node@20.16.1)(sass@1.77.8): + dependencies: + esbuild: 0.21.5 + postcss: 8.4.41 + rollup: 4.21.0 + optionalDependencies: + '@types/node': 20.16.1 + fsevents: 2.3.3 + sass: 1.77.8 + + vscode-jsonrpc@6.0.0: {} + + vscode-languageclient@7.0.0: + dependencies: + minimatch: 3.1.2 + semver: 7.6.3 + vscode-languageserver-protocol: 3.16.0 + + vscode-languageserver-protocol@3.16.0: + dependencies: + vscode-jsonrpc: 6.0.0 + vscode-languageserver-types: 3.16.0 + + vscode-languageserver-textdocument@1.0.12: {} + + vscode-languageserver-types@3.16.0: {} + + vscode-languageserver@7.0.0: + dependencies: + vscode-languageserver-protocol: 3.16.0 + + vscode-uri@3.0.8: {} + + vue-demi@0.14.10(vue@3.4.38(typescript@5.5.4)): + dependencies: + vue: 3.4.38(typescript@5.5.4) + + vue-eslint-parser@9.4.3(eslint@9.9.0(jiti@1.21.6)): + dependencies: + debug: 4.3.6 + eslint: 9.9.0(jiti@1.21.6) + eslint-scope: 7.2.2 + eslint-visitor-keys: 3.4.3 + espree: 9.6.1 + esquery: 1.6.0 + lodash: 4.17.21 + semver: 7.6.3 + transitivePeerDependencies: + - supports-color + + vue-i18n@9.14.0(vue@3.4.38(typescript@5.5.4)): + dependencies: + '@intlify/core-base': 9.14.0 + '@intlify/shared': 9.14.0 + '@vue/devtools-api': 6.6.3 + vue: 3.4.38(typescript@5.5.4) + + vue-router@4.4.3(vue@3.4.38(typescript@5.5.4)): + dependencies: + '@vue/devtools-api': 6.6.3 + vue: 3.4.38(typescript@5.5.4) + + vue-tippy@6.4.4(vue@3.4.38(typescript@5.5.4)): + dependencies: + tippy.js: 6.3.7 + vue: 3.4.38(typescript@5.5.4) + + vue-tsc@2.0.29(typescript@5.5.4): + dependencies: + '@volar/typescript': 2.4.0 + '@vue/language-core': 2.0.29(typescript@5.5.4) + semver: 7.6.3 + typescript: 5.5.4 + + vue-types@5.1.3(vue@3.4.38(typescript@5.5.4)): + dependencies: + is-plain-object: 5.0.0 + optionalDependencies: + vue: 3.4.38(typescript@5.5.4) + + vue@3.4.38(typescript@5.5.4): + dependencies: + '@vue/compiler-dom': 3.4.38 + '@vue/compiler-sfc': 3.4.38 + '@vue/runtime-dom': 3.4.38 + '@vue/server-renderer': 3.4.38(vue@3.4.38(typescript@5.5.4)) + '@vue/shared': 3.4.38 + optionalDependencies: + typescript: 5.5.4 + + webpack-sources@3.2.3: {} + + webpack-virtual-modules@0.6.2: {} + + which@1.3.1: + dependencies: + isexe: 2.0.0 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + widest-line@4.0.1: + dependencies: + string-width: 5.1.2 + + word-wrap@1.2.5: {} + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.1 + string-width: 5.1.2 + strip-ansi: 7.1.0 + + wrap-ansi@9.0.0: + dependencies: + ansi-styles: 6.2.1 + string-width: 7.2.0 + strip-ansi: 7.1.0 + + write-file-atomic@5.0.1: + dependencies: + imurmurhash: 0.1.4 + signal-exit: 4.1.0 + + xml-name-validator@4.0.0: {} + + y18n@5.0.8: {} + + yallist@3.1.1: {} + + yallist@4.0.0: + optional: true + + yaml-eslint-parser@1.2.3: + dependencies: + eslint-visitor-keys: 3.4.3 + lodash: 4.17.21 + yaml: 2.5.0 + + yaml@1.10.2: {} + + yaml@2.5.0: {} + + yargs-parser@21.1.1: {} + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.1.2 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + yocto-queue@0.1.0: {} + + yocto-queue@1.1.1: {} + + zrender@5.6.0: + dependencies: + tslib: 2.3.0 diff --git a/sop-admin/sop-admin-frontend/postcss.config.js b/sop-admin/sop-admin-frontend/postcss.config.js new file mode 100755 index 00000000..86239486 --- /dev/null +++ b/sop-admin/sop-admin-frontend/postcss.config.js @@ -0,0 +1,12 @@ +// @ts-check + +/** @type {import('postcss-load-config').Config} */ +export default { + plugins: { + "postcss-import": {}, + "tailwindcss/nesting": {}, + tailwindcss: {}, + autoprefixer: {}, + ...(process.env.NODE_ENV === "production" ? { cssnano: {} } : {}) + } +}; diff --git a/sop-admin/sop-admin-frontend/public/favicon.ico b/sop-admin/sop-admin-frontend/public/favicon.ico new file mode 100755 index 00000000..d6795cce Binary files /dev/null and b/sop-admin/sop-admin-frontend/public/favicon.ico differ diff --git a/sop-admin/sop-admin-frontend/public/logo.png b/sop-admin/sop-admin-frontend/public/logo.png new file mode 100755 index 00000000..148e694a Binary files /dev/null and b/sop-admin/sop-admin-frontend/public/logo.png differ diff --git a/sop-admin/sop-admin-frontend/public/platform-config.json b/sop-admin/sop-admin-frontend/public/platform-config.json new file mode 100755 index 00000000..49e8fccc --- /dev/null +++ b/sop-admin/sop-admin-frontend/public/platform-config.json @@ -0,0 +1,27 @@ +{ + "Version": "5.8.0", + "Title": "SOP Admin", + "FixedHeader": true, + "HiddenSideBar": false, + "MultiTagsCache": false, + "KeepAlive": false, + "Locale": "zh", + "Layout": "vertical", + "Theme": "light", + "DarkMode": false, + "OverallStyle": "light", + "Grey": false, + "Weak": false, + "HideTabs": true, + "HideFooter": true, + "Stretch": false, + "SidebarStatus": true, + "EpThemeColor": "#409EFF", + "ShowLogo": false, + "ShowModel": "smart", + "MenuArrowIconNoTransition": false, + "CachingAsyncRoutes": false, + "TooltipEffect": "light", + "ResponsiveStorageNameSpace": "sopadmin-responsive-", + "MenuSearchHistory": 6 +} diff --git a/sop-admin/sop-admin-frontend/src/App.vue b/sop-admin/sop-admin-frontend/src/App.vue new file mode 100755 index 00000000..533be72e --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/App.vue @@ -0,0 +1,38 @@ + + + diff --git a/sop-admin/sop-admin-frontend/src/api/doc.ts b/sop-admin/sop-admin-frontend/src/api/doc.ts new file mode 100755 index 00000000..7ca90ba1 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/api/doc.ts @@ -0,0 +1,66 @@ +import { createUrl, http } from "@/utils/http"; +import type { Result } from "@/model"; + +// 后端请求接口 +const apiUrl: any = createUrl({ + addApp: "/doc/app/add", + syncAppDoc: "/doc/app/syncAppDoc", + syncDoc: "/doc/app/syncDoc", + listApp: "/doc/app/list", + listDocTree: "/doc/info/tree", + publish: "/doc/info/publish" +}); + +interface DocApp { + id: number; + appName: string; +} + +/** + * 接口管理 + */ +export const api: any = { + /** + * 分页查询 + */ + listApp(): Promise>> { + return http.get>, any>(apiUrl.listApp, {}); + }, + /** + * 新增 + * @param data 表单内容 + */ + addApp(data: object) { + return http.post, any>(apiUrl.addApp, { data }); + }, + /** + * 同步文档 + * @param data 表单内容 + */ + syncAppDoc(docAppId) { + const data = { + id: docAppId + }; + return http.post, any>(apiUrl.syncAppDoc, { data }); + }, + syncDoc(docInfoId) { + const data = { + id: docInfoId + }; + return http.post, any>(apiUrl.syncDoc, { data }); + }, + /** + * 发布 + * @param data 表单内容 + */ + publish(data: object) { + return http.post, any>(apiUrl.publish, { data }); + }, + /** + * 查询文档树 + * @param data + */ + listDocTree(params: object) { + return http.get>, any>(apiUrl.listDocTree, { params }); + } +}; diff --git a/sop-admin/sop-admin-frontend/src/api/docSetting.ts b/sop-admin/sop-admin-frontend/src/api/docSetting.ts new file mode 100755 index 00000000..1da41de2 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/api/docSetting.ts @@ -0,0 +1,27 @@ +import { createUrl, http } from "@/utils/http"; +import type { PageResult, Result } from "@/model"; + +// 后端请求接口 +const apiUrl: any = createUrl({ + get: "/doc/setting/get", + save: "/doc/setting/save" +}); + +/** + * 接口管理 + */ +export const api: any = { + /** + * 分页查询 + */ + get(): Promise { + return http.get(apiUrl.get, {}); + }, + /** + * 新增 + * @param data 表单内容 + */ + save(data: object) { + return http.post, any>(apiUrl.save, { data }); + } +}; diff --git a/sop-admin/sop-admin-frontend/src/api/isvList.ts b/sop-admin/sop-admin-frontend/src/api/isvList.ts new file mode 100755 index 00000000..dd2c9ae5 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/api/isvList.ts @@ -0,0 +1,115 @@ +import { createUrl, http } from "@/utils/http"; +import type { PageResult, Result } from "@/model"; + +// 后端请求接口 +const apiUrl: any = createUrl({ + page: "/isv/page", + add: "/isv/add", + update: "/isv/update", + del: "/isv/delete", + getKeys: "/isv/getKeys", + updateStatus: "/isv/updateStatus", + createKeys: "/isv/createKeys", + updateKeys: "/isv/updateKeys", + listGroup: "perm/isv/group/listIsvGroupId", + updateGroup: "perm/isv/group/setting" +}); + +/* +private String publicKey; +private String privateKey; + */ +export interface KeyStore { + publicKey: string; + privateKey: string; +} + +/** + * 接口管理 + */ +export const api: any = { + /** + * 分页查询 + * @param params 查询参数 + */ + page(params: object): Promise { + return http.get(apiUrl.page, { params }); + }, + /** + * 新增 + * @param data 表单内容 + */ + add(data: object) { + return http.post, any>(apiUrl.add, { data }); + }, + /** + * 修改 + * @param data 表单内容 + */ + update(data: object) { + return http.post, any>(apiUrl.update, { data }); + }, + /** + * 删除 + * @param data 表单内容 + */ + del(data: object) { + return http.post, any>(apiUrl.del, { data }); + }, + /** + * 查看秘钥 + * @param params + */ + viewKeys(params: object) { + return http.get, any>(apiUrl.getKeys, { params }); + }, + /** + * 修改状态 + * @param data 表单内容 + */ + updateStatus(data: object) { + return http.post, any>(apiUrl.updateStatus, { data }); + }, + /** + * 创建秘钥 + * @param keyFormat 秘钥格式,1:PKCS8(JAVA适用),2:PKCS1(非JAVA适用) + */ + createKeys(keyFormat): Promise> { + const data = { + keyFormat: keyFormat + }; + return http.post, any>(apiUrl.createKeys, { data }); + }, + /** + * 修改秘钥 + * @param data 表单内容 + */ + updateKeys(data: object): Promise> { + return http.post, any>(apiUrl.updateKeys, { data }); + }, + /** + * 修改秘钥 + * @param data 表单内容 + */ + listGroup(isvId: Number): Promise> { + const data = { + isvId: isvId + }; + return http.get, any>(apiUrl.listGroup, { params: data }); + }, + /** + * 设置分组 + * @param isvId isvId + * @param groupCodes groupCodes + */ + updateGroup( + isvId: Number, + groupIds: Array + ): Promise> { + const data = { + isvId: isvId, + groupIds: groupIds + }; + return http.post, any>(apiUrl.updateGroup, { data }); + } +}; diff --git a/sop-admin/sop-admin-frontend/src/api/permGroup.ts b/sop-admin/sop-admin-frontend/src/api/permGroup.ts new file mode 100755 index 00000000..a4775b74 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/api/permGroup.ts @@ -0,0 +1,51 @@ +import { createUrl, http } from "@/utils/http"; +import type { PageResult, Result } from "@/model"; + +// 后端请求接口 +const apiUrl: any = createUrl({ + page: "/perm/group/page", + add: "/perm/group/add", + update: "/perm/group/update", + del: "/perm/group/delete", + listAll: "/perm/group/listAll" +}); + +/** + * 接口管理 + */ +export const api: any = { + /** + * 分页查询 + * @param params 查询参数 + */ + page(params: object): Promise { + return http.get(apiUrl.page, { params }); + }, + /** + * 查询全部 + */ + listAll(): Promise> { + return http.get, any>(apiUrl.listAll, {}); + }, + /** + * 新增 + * @param data 表单内容 + */ + add(data: object) { + return http.post, any>(apiUrl.add, { data }); + }, + /** + * 修改 + * @param data 表单内容 + */ + update(data: object) { + return http.post, any>(apiUrl.update, { data }); + }, + /** + * 删除 + * @param data 表单内容 + */ + del(data: object) { + return http.post, any>(apiUrl.del, { data }); + } +}; diff --git a/sop-admin/sop-admin-frontend/src/api/permGroupPermission.ts b/sop-admin/sop-admin-frontend/src/api/permGroupPermission.ts new file mode 100755 index 00000000..b85a729e --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/api/permGroupPermission.ts @@ -0,0 +1,43 @@ +import { createUrl, http } from "@/utils/http"; +import type { PageResult, Result } from "@/model"; + +// 后端请求接口 +const apiUrl: any = createUrl({ + page: "perm/group/permission/page", + setting: "perm/group/permission/setting", + del: "perm/group/permission/delete" +}); + +/** + * 接口管理 + */ +export const api: any = { + /** + * 查询分组权限 + * + * @param params params + */ + page(params): Promise { + return http.get(apiUrl.page, { params }); + }, + /** + * 设置接口权限 + * + * @param groupId groupId + * @param apiIdList apiIdList + */ + setting(groupId, apiIdList) { + const data = { + groupId: groupId, + apiIdList: apiIdList + }; + return http.post, any>(apiUrl.setting, { data }); + }, + /** + * 删除 + * @param data 表单内容 + */ + del(data: object) { + return http.post, any>(apiUrl.del, { data }); + } +}; diff --git a/sop-admin/sop-admin-frontend/src/api/routes.ts b/sop-admin/sop-admin-frontend/src/api/routes.ts new file mode 100755 index 00000000..7ac46745 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/api/routes.ts @@ -0,0 +1,16 @@ +import { createUrl, http } from "@/utils/http"; + +// 后端请求接口 +const apiUrl: any = createUrl({ + listMenu: "sys/userperm/listCurrentUserMenu" +}); + +type Result = { + success: boolean; + data: Array; +}; + +export const getAsyncRoutes = () => { + return http.request("get", apiUrl.listMenu); + // return http.request("get", "/get-async-routes"); +}; diff --git a/sop-admin/sop-admin-frontend/src/api/serveApi.ts b/sop-admin/sop-admin-frontend/src/api/serveApi.ts new file mode 100755 index 00000000..35901eb0 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/api/serveApi.ts @@ -0,0 +1,60 @@ +import { createUrl, http } from "@/utils/http"; +import type { PageResult, Result } from "@/model"; + +// 后端请求接口 +const apiUrl: any = createUrl({ + page: "/serve/api/page", + listAll: "/serve/api/listAll", + add: "/serve/api/add", + update: "/serve/api/update", + del: "/serve/api/delete", + updateStatus: "/serve/api/updateStatus" +}); + +/** + * 接口管理 + */ +export const api: any = { + /** + * 分页查询 + * @param params 查询参数 + */ + page(params: object): Promise { + return http.get(apiUrl.page, { params }); + }, + /** + * 查询全部 + * @param params + */ + listAll(params: object): Promise { + return http.get, any>(apiUrl.listAll, { params }); + }, + /** + * 新增 + * @param data 表单内容 + */ + add(data: object) { + return http.post, any>(apiUrl.add, { data }); + }, + /** + * 修改 + * @param data 表单内容 + */ + update(data: object) { + return http.post, any>(apiUrl.update, { data }); + }, + /** + * 删除 + * @param data 表单内容 + */ + del(data: object) { + return http.post, any>(apiUrl.del, { data }); + }, + /** + * 修改状态 + * @param data 表单内容 + */ + updateStatus(data: object) { + return http.post, any>(apiUrl.updateStatus, { data }); + } +}; diff --git a/sop-admin/sop-admin-frontend/src/api/sysAdminUser.ts b/sop-admin/sop-admin-frontend/src/api/sysAdminUser.ts new file mode 100755 index 00000000..533d9b0e --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/api/sysAdminUser.ts @@ -0,0 +1,44 @@ +import { createUrl, http } from "@/utils/http"; +import type { PageResult, Result } from "@/model"; + +// 后端请求接口 +const apiUrl: any = createUrl({ + page: "/sys/user/page", + add: "/sys/user/add", + update: "/sys/user/update", + del: "/sys/user/delete" +}); + +/** + * 接口管理 + */ +export const api: any = { + /** + * 分页查询 + * @param params 查询参数 + */ + page(params: object): Promise { + return http.get(apiUrl.page, { params }); + }, + /** + * 新增 + * @param data 表单内容 + */ + add(data: object) { + return http.post, any>(apiUrl.add, { data }); + }, + /** + * 修改 + * @param data 表单内容 + */ + update(data: object) { + return http.post, any>(apiUrl.update, { data }); + }, + /** + * 删除 + * @param data 表单内容 + */ + del(data: object) { + return http.post, any>(apiUrl.del, { data }); + } +}; diff --git a/sop-admin/sop-admin-frontend/src/api/system.ts b/sop-admin/sop-admin-frontend/src/api/system.ts new file mode 100755 index 00000000..acb119eb --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/api/system.ts @@ -0,0 +1,197 @@ +import { baseUrl, http } from "@/utils/http"; + +type Result = { + success: boolean; + data?: Array; +}; + +type ResultTable = { + success: boolean; + data?: { + /** 列表数据 */ + list: Array; + /** 总条目数 */ + total?: number; + /** 每页显示条目个数 */ + pageSize?: number; + /** 当前页数 */ + pageIndex?: number; + }; +}; + +/** 获取系统管理-用户管理列表 */ +export const getUserList = (params?: object) => { + return http.request("get", baseUrl("sys/user/page"), { params }); +}; + +/** 获取系统管理-用户管理-添加 */ +export const addUser = (data?: object) => { + return http.request("post", baseUrl("sys/user/add"), { + data + }); +}; + +/** 获取系统管理-用户管理-修改 */ +export const updateUser = (data?: object) => { + return http.request("post", baseUrl("sys/user/update"), { + data + }); +}; + +/** 获取系统管理-用户管理-修改状态 */ +export const updateUserStatus = (data?: object) => { + return http.request("post", baseUrl("sys/user/updateStatus"), { + data + }); +}; + +/** 获取系统管理-用户管理-重置密码 */ +export const resetPassword = (data?: object) => { + return http.request("post", baseUrl("sys/user/resetPassword"), { + data + }); +}; + +/** 获取系统管理-用户管理-批量修改状态 */ +export const updateStatusBatch = (data?: object) => { + return http.request( + "post", + baseUrl("sys/user/updateStatusBatch"), + { data } + ); +}; + +/** 系统管理-用户管理-根据userId,获取对应角色id列表(userId:用户id) */ +export const getRoleIds = (params?: object) => { + return http.request("get", baseUrl("sys/userrole/getUserRoleIds"), { + params + }); +}; + +/** 获取系统管理-用户管理-设置用户角色 */ +export const setUserRole = (data?: object) => { + return http.request( + "post", + baseUrl("sys/userrole/setUserRole"), + { data } + ); +}; + +/** 系统管理-用户管理-获取所有角色列表 */ +export const getAllRoleList = () => { + return http.request("get", baseUrl("sys/role/all")); +}; + +/** 获取系统管理-角色管理列表 */ +export const getRoleList = (params?: object) => { + return http.request("get", baseUrl("sys/role/page"), { params }); +}; + +/** 添加角色 */ +export const addRole = (data?: object) => { + return http.request("post", baseUrl("sys/role/add"), { data }); +}; + +/** 删除角色状态 */ +export const updateRoleStatus = (data?: object) => { + return http.request("post", baseUrl("sys/role/updateStatus"), { + data + }); +}; + +/** 修改角色 */ +export const updateRole = (data?: object) => { + return http.request("post", baseUrl("sys/role/update"), { + data + }); +}; + +/** 删除角色 */ +export const delRole = (data?: object) => { + return http.request("post", baseUrl("sys/role/delete"), { + data + }); +}; + +/** 获取系统管理-菜单管理列表 */ +export const getMenuList = (data?: object) => { + return http.request("get", baseUrl("sys/resource/listAll"), { + data + }); +}; + +/** 获取系统管理-菜单管理-添加 */ +export const addMenu = (data?: object) => { + return http.request("post", baseUrl("sys/resource/add"), { data }); +}; + +/** 获取系统管理-菜单管理-修改 */ +export const updateMenu = (data?: object) => { + return http.request("post", baseUrl("sys/resource/update"), { data }); +}; + +/** 获取系统管理-菜单管理-删除 */ +export const deleteMenu = (data?: object) => { + return http.request("post", baseUrl("sys/resource/delete"), { data }); +}; + +/** 系统管理-角色管理-保存角色菜单 */ +export const saveRoleMenu = (data?: object) => { + return http.request( + "post", + baseUrl("sys/resource/saveRoleResource"), + { data } + ); +}; + +/** 获取系统管理-部门管理列表 */ +export const getDeptList = (params?: object) => { + return http.request("get", baseUrl("sys/dept/listAll"), { params }); +}; + +/** 获取系统管理-添加部门 */ +export const addDept = (data?: object) => { + return http.request("post", baseUrl("sys/dept/add"), { data }); +}; + +/** 获取系统管理-修改部门 */ +export const updateDept = (data?: object) => { + return http.request("post", baseUrl("sys/dept/update"), { data }); +}; + +/** 获取系统监控-在线用户列表 */ +export const getOnlineLogsList = (data?: object) => { + return http.request("post", "/online-logs", { data }); +}; + +/** 获取系统监控-登录日志列表 */ +export const getLoginLogsList = (data?: object) => { + return http.request("post", "/login-logs", { data }); +}; + +/** 获取系统监控-操作日志列表 */ +export const getOperationLogsList = (data?: object) => { + return http.request("post", "/operation-logs", { data }); +}; + +/** 获取系统监控-系统日志列表 */ +export const getSystemLogsList = (data?: object) => { + return http.request("post", "/system-logs", { data }); +}; + +/** 获取系统监控-系统日志-根据 id 查日志详情 */ +export const getSystemLogsDetail = (data?: object) => { + return http.request("post", "/system-logs-detail", { data }); +}; + +/** 获取角色管理-权限-菜单权限 */ +export const getRoleMenu = () => { + return http.request("get", baseUrl("sys/resource/listAll"), {}); +}; + +/** 获取角色管理-权限-菜单权限-根据角色 id 查对应菜单 */ +export const getRoleMenuIds = (params?: object) => { + return http.request("get", baseUrl("sys/resource/listRoleResource"), { + params + }); +}; diff --git a/sop-admin/sop-admin-frontend/src/api/user.ts b/sop-admin/sop-admin-frontend/src/api/user.ts new file mode 100755 index 00000000..5479e14c --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/api/user.ts @@ -0,0 +1,52 @@ +import { baseUrl, http } from "@/utils/http"; + +export type UserResult = { + success: boolean; + data: { + /** 头像 */ + avatar: string; + /** 用户名 */ + username: string; + /** 昵称 */ + nickname: string; + /** 当前登录用户的角色 */ + roles: Array; + /** 按钮级别权限 */ + permissions: Array; + /** `token` */ + accessToken: string; + /** 用于调用刷新`accessToken`的接口时所需的`token` */ + refreshToken: string; + /** `accessToken`的过期时间(格式'xxxx/xx/xx xx:xx:xx') */ + expires: Date; + }; +}; + +export type RefreshTokenResult = { + success: boolean; + data: { + /** `token` */ + accessToken: string; + /** 用于调用刷新`accessToken`的接口时所需的`token` */ + refreshToken: string; + /** `accessToken`的过期时间(格式'xxxx/xx/xx xx:xx:xx') */ + expires: Date; + }; +}; + +const apiUrl = { + login: baseUrl("/sys/login"), + refreshToken: baseUrl("/sys/refresh-token") +}; + +/** 登录 */ +export const getLogin = (data?: object) => { + return http.request("post", apiUrl.login, { data }); +}; + +/** 刷新`token` */ +export const refreshTokenApi = (data?: object) => { + return http.request("post", apiUrl.refreshToken, { + data + }); +}; diff --git a/sop-admin/sop-admin-frontend/src/assets/iconfont/iconfont.css b/sop-admin/sop-admin-frontend/src/assets/iconfont/iconfont.css new file mode 100755 index 00000000..9a153df3 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/assets/iconfont/iconfont.css @@ -0,0 +1,27 @@ +@font-face { + font-family: "iconfont"; /* Project id 2208059 */ + src: + url("iconfont.woff2?t=1671895108120") format("woff2"), + url("iconfont.woff?t=1671895108120") format("woff"), + url("iconfont.ttf?t=1671895108120") format("truetype"); +} + +.iconfont { + font-family: "iconfont" !important; + font-size: 16px; + font-style: normal; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.pure-iconfont-tabs:before { + content: "\e63e"; +} + +.pure-iconfont-logo:before { + content: "\e620"; +} + +.pure-iconfont-new:before { + content: "\e615"; +} diff --git a/sop-admin/sop-admin-frontend/src/assets/iconfont/iconfont.js b/sop-admin/sop-admin-frontend/src/assets/iconfont/iconfont.js new file mode 100755 index 00000000..64d8bd84 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/assets/iconfont/iconfont.js @@ -0,0 +1,69 @@ +(window._iconfont_svg_string_2208059 = + ''), + (function (e) { + var t = (t = document.getElementsByTagName("script"))[t.length - 1], + c = t.getAttribute("data-injectcss"), + t = t.getAttribute("data-disable-injectsvg"); + if (!t) { + var n, + l, + i, + o, + a, + h = function (t, c) { + c.parentNode.insertBefore(t, c); + }; + if (c && !e.__iconfont__svg__cssinject__) { + e.__iconfont__svg__cssinject__ = !0; + try { + document.write( + "" + ); + } catch (t) { + console && console.log(t); + } + } + (n = function () { + var t, + c = document.createElement("div"); + (c.innerHTML = e._iconfont_svg_string_2208059), + (c = c.getElementsByTagName("svg")[0]) && + (c.setAttribute("aria-hidden", "true"), + (c.style.position = "absolute"), + (c.style.width = 0), + (c.style.height = 0), + (c.style.overflow = "hidden"), + (c = c), + (t = document.body).firstChild + ? h(c, t.firstChild) + : t.appendChild(c)); + }), + document.addEventListener + ? ~["complete", "loaded", "interactive"].indexOf(document.readyState) + ? setTimeout(n, 0) + : ((l = function () { + document.removeEventListener("DOMContentLoaded", l, !1), n(); + }), + document.addEventListener("DOMContentLoaded", l, !1)) + : document.attachEvent && + ((i = n), + (o = e.document), + (a = !1), + v(), + (o.onreadystatechange = function () { + "complete" == o.readyState && + ((o.onreadystatechange = null), d()); + })); + } + function d() { + a || ((a = !0), i()); + } + function v() { + try { + o.documentElement.doScroll("left"); + } catch (t) { + return void setTimeout(v, 50); + } + d(); + } + })(window); diff --git a/sop-admin/sop-admin-frontend/src/assets/iconfont/iconfont.json b/sop-admin/sop-admin-frontend/src/assets/iconfont/iconfont.json new file mode 100755 index 00000000..cec48060 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/assets/iconfont/iconfont.json @@ -0,0 +1,30 @@ +{ + "id": "2208059", + "name": "pure-admin", + "font_family": "iconfont", + "css_prefix_text": "pure-iconfont-", + "description": "pure-admin-iconfont", + "glyphs": [ + { + "icon_id": "20594647", + "name": "Tabs", + "font_class": "tabs", + "unicode": "e63e", + "unicode_decimal": 58942 + }, + { + "icon_id": "22129506", + "name": "PureLogo", + "font_class": "logo", + "unicode": "e620", + "unicode_decimal": 58912 + }, + { + "icon_id": "7795615", + "name": "New", + "font_class": "new", + "unicode": "e615", + "unicode_decimal": 58901 + } + ] +} diff --git a/sop-admin/sop-admin-frontend/src/assets/iconfont/iconfont.ttf b/sop-admin/sop-admin-frontend/src/assets/iconfont/iconfont.ttf new file mode 100755 index 00000000..82efcf8f Binary files /dev/null and b/sop-admin/sop-admin-frontend/src/assets/iconfont/iconfont.ttf differ diff --git a/sop-admin/sop-admin-frontend/src/assets/iconfont/iconfont.woff b/sop-admin/sop-admin-frontend/src/assets/iconfont/iconfont.woff new file mode 100755 index 00000000..0fdaa0a4 Binary files /dev/null and b/sop-admin/sop-admin-frontend/src/assets/iconfont/iconfont.woff differ diff --git a/sop-admin/sop-admin-frontend/src/assets/iconfont/iconfont.woff2 b/sop-admin/sop-admin-frontend/src/assets/iconfont/iconfont.woff2 new file mode 100755 index 00000000..e957d747 Binary files /dev/null and b/sop-admin/sop-admin-frontend/src/assets/iconfont/iconfont.woff2 differ diff --git a/sop-admin/sop-admin-frontend/src/assets/login/avatar.svg b/sop-admin/sop-admin-frontend/src/assets/login/avatar.svg new file mode 100755 index 00000000..a63d2b1a --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/assets/login/avatar.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/sop-admin/sop-admin-frontend/src/assets/login/bg.png b/sop-admin/sop-admin-frontend/src/assets/login/bg.png new file mode 100755 index 00000000..8cdd3001 Binary files /dev/null and b/sop-admin/sop-admin-frontend/src/assets/login/bg.png differ diff --git a/sop-admin/sop-admin-frontend/src/assets/login/illustration.svg b/sop-admin/sop-admin-frontend/src/assets/login/illustration.svg new file mode 100755 index 00000000..b58ffd08 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/assets/login/illustration.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/sop-admin/sop-admin-frontend/src/assets/status/403.svg b/sop-admin/sop-admin-frontend/src/assets/status/403.svg new file mode 100755 index 00000000..ba3ce293 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/assets/status/403.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/sop-admin/sop-admin-frontend/src/assets/status/404.svg b/sop-admin/sop-admin-frontend/src/assets/status/404.svg new file mode 100755 index 00000000..aacb7402 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/assets/status/404.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/sop-admin/sop-admin-frontend/src/assets/status/500.svg b/sop-admin/sop-admin-frontend/src/assets/status/500.svg new file mode 100755 index 00000000..ea23a378 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/assets/status/500.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/sop-admin/sop-admin-frontend/src/assets/svg/back_top.svg b/sop-admin/sop-admin-frontend/src/assets/svg/back_top.svg new file mode 100755 index 00000000..f8e6aa02 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/assets/svg/back_top.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/sop-admin/sop-admin-frontend/src/assets/svg/dark.svg b/sop-admin/sop-admin-frontend/src/assets/svg/dark.svg new file mode 100755 index 00000000..b5c4d2d5 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/assets/svg/dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/sop-admin/sop-admin-frontend/src/assets/svg/day.svg b/sop-admin/sop-admin-frontend/src/assets/svg/day.svg new file mode 100755 index 00000000..b7600345 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/assets/svg/day.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/sop-admin/sop-admin-frontend/src/assets/svg/enter_outlined.svg b/sop-admin/sop-admin-frontend/src/assets/svg/enter_outlined.svg new file mode 100755 index 00000000..45e0bafe --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/assets/svg/enter_outlined.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/sop-admin/sop-admin-frontend/src/assets/svg/exit_screen.svg b/sop-admin/sop-admin-frontend/src/assets/svg/exit_screen.svg new file mode 100755 index 00000000..007c0b63 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/assets/svg/exit_screen.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/sop-admin/sop-admin-frontend/src/assets/svg/full_screen.svg b/sop-admin/sop-admin-frontend/src/assets/svg/full_screen.svg new file mode 100755 index 00000000..fff93a5d --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/assets/svg/full_screen.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/sop-admin/sop-admin-frontend/src/assets/svg/globalization.svg b/sop-admin/sop-admin-frontend/src/assets/svg/globalization.svg new file mode 100755 index 00000000..5f6bce6b --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/assets/svg/globalization.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/sop-admin/sop-admin-frontend/src/assets/svg/keyboard_esc.svg b/sop-admin/sop-admin-frontend/src/assets/svg/keyboard_esc.svg new file mode 100755 index 00000000..bd671654 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/assets/svg/keyboard_esc.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/sop-admin/sop-admin-frontend/src/assets/svg/system.svg b/sop-admin/sop-admin-frontend/src/assets/svg/system.svg new file mode 100755 index 00000000..9ad39a56 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/assets/svg/system.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/sop-admin/sop-admin-frontend/src/assets/table-bar/collapse.svg b/sop-admin/sop-admin-frontend/src/assets/table-bar/collapse.svg new file mode 100755 index 00000000..0823ae63 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/assets/table-bar/collapse.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/sop-admin/sop-admin-frontend/src/assets/table-bar/drag.svg b/sop-admin/sop-admin-frontend/src/assets/table-bar/drag.svg new file mode 100755 index 00000000..8ac32a7b --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/assets/table-bar/drag.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/sop-admin/sop-admin-frontend/src/assets/table-bar/expand.svg b/sop-admin/sop-admin-frontend/src/assets/table-bar/expand.svg new file mode 100755 index 00000000..bb41c350 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/assets/table-bar/expand.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/sop-admin/sop-admin-frontend/src/assets/table-bar/refresh.svg b/sop-admin/sop-admin-frontend/src/assets/table-bar/refresh.svg new file mode 100755 index 00000000..140288cd --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/assets/table-bar/refresh.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/sop-admin/sop-admin-frontend/src/assets/table-bar/settings.svg b/sop-admin/sop-admin-frontend/src/assets/table-bar/settings.svg new file mode 100755 index 00000000..4ecd0779 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/assets/table-bar/settings.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/sop-admin/sop-admin-frontend/src/assets/user.jpg b/sop-admin/sop-admin-frontend/src/assets/user.jpg new file mode 100755 index 00000000..a2973ace Binary files /dev/null and b/sop-admin/sop-admin-frontend/src/assets/user.jpg differ diff --git a/sop-admin/sop-admin-frontend/src/components/ApiSelect/index.ts b/sop-admin/sop-admin-frontend/src/components/ApiSelect/index.ts new file mode 100755 index 00000000..4c30085b --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/components/ApiSelect/index.ts @@ -0,0 +1,177 @@ +import { ref } from "vue"; +import { type PageInfo, type PlusColumn, useTable } from "plus-pro-components"; +import { withInstall } from "@pureadmin/utils"; +import apiSelect from "./index.vue"; +import { StatusEnum } from "@/model/enums"; +import { api as serveApi } from "@/api/serveApi"; + +export const dlgApiSelectShow = ref(false); +export const dlgWidth = ref(1100); +export const handleSaveApi = ref((_: Array) => {}); + +// ========= search form ========= + +// 查询表单对象 +export const searchFormData = ref({ + apiName: "", + status: "", + isPermission: null, + pageIndex: 1, + pageSize: 10 +}); + +// 查询表单字段定义 +export const searchFormColumns: PlusColumn[] = [ + { + label: "接口名称", + prop: "apiName" + }, + { + label: "状态", + prop: "status", + width: 80, + valueType: "select", + options: [ + { + label: "启用", + value: StatusEnum.ENABLE, + color: "green" + }, + { + label: "禁用", + value: StatusEnum.DISABLE, + color: "red" + } + ] + } +]; + +// ========= table ========= + +// 表格对象 +export const { tableData, total, pageInfo } = useTable(); +// 默认每页条数,默认10 +pageInfo.value.pageSize = 10; + +const multipleSelection = ref([]); +export const selectable = (row: any) => row.status === StatusEnum.ENABLE; +// 表格字段定义 +export const tableColumns: PlusColumn[] = [ + { + label: "所属应用", + prop: "application", + tableColumnProps: { + showOverflowTooltip: true + } + }, + { + label: "接口名称", + prop: "apiName", + tableColumnProps: { + showOverflowTooltip: true + } + }, + { + label: "版本号", + prop: "apiVersion", + width: 80 + }, + { + label: "接口描述", + prop: "description", + tableColumnProps: { + showOverflowTooltip: true + } + }, + { + label: "注册来源", + prop: "regSource", + width: 100, + valueType: "select", + options: [ + { + label: "系统", + value: 1, + color: "blue" + }, + { + label: "手动", + value: 2, + color: "green" + } + ] + }, + { + label: "状态", + prop: "status", + width: 80, + valueType: "select", + options: [ + { + label: "启用", + value: StatusEnum.ENABLE, + color: "green" + }, + { + label: "禁用", + value: StatusEnum.DISABLE, + color: "red" + } + ] + } +]; + +export const openDlg = () => { + dlgApiSelectShow.value = true; + search(); +}; + +export const handleConfirm = () => { + const idList = multipleSelection.value.map(row => row.id); + handleSaveApi.value(idList); +}; + +export const handleClose = () => { + dlgApiSelectShow.value = false; +}; + +export const handleSelectionChange = (rows: any) => { + multipleSelection.value = rows; +}; + +// 点击查询按钮 +export const handleSearch = () => { + search(); +}; + +// 分页事件 +export const handlePaginationChange = (_pageInfo: PageInfo): void => { + pageInfo.value = _pageInfo; + search(); +}; + +// 查询 +export const search = async () => { + try { + const { data } = await doSearch(); + tableData.value = data.list; + total.value = data.total; + } catch (error) {} +}; +// 请求接口 +const doSearch = async () => { + // 查询参数 + const data = searchFormData.value; + // 添加分页参数 + data.pageIndex = pageInfo.value.page; + data.pageSize = pageInfo.value.pageSize; + + return serveApi.page(data); +}; + +// 页面加载 +search(); + +const ApiSelect = withInstall(apiSelect); + +export { ApiSelect }; diff --git a/sop-admin/sop-admin-frontend/src/components/ApiSelect/index.vue b/sop-admin/sop-admin-frontend/src/components/ApiSelect/index.vue new file mode 100755 index 00000000..c1cfc524 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/components/ApiSelect/index.vue @@ -0,0 +1,79 @@ + + + diff --git a/sop-admin/sop-admin-frontend/src/components/ReAnimateSelector/index.ts b/sop-admin/sop-admin-frontend/src/components/ReAnimateSelector/index.ts new file mode 100755 index 00000000..87c9008b --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/components/ReAnimateSelector/index.ts @@ -0,0 +1,7 @@ +import { withInstall } from "@pureadmin/utils"; +import reAnimateSelector from "./src/index.vue"; + +/** [animate.css](https://animate.style/) 选择器组件 */ +export const ReAnimateSelector = withInstall(reAnimateSelector); + +export default ReAnimateSelector; diff --git a/sop-admin/sop-admin-frontend/src/components/ReAnimateSelector/src/animate.ts b/sop-admin/sop-admin-frontend/src/components/ReAnimateSelector/src/animate.ts new file mode 100755 index 00000000..2b0593c7 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/components/ReAnimateSelector/src/animate.ts @@ -0,0 +1,114 @@ +export const animates = [ + /* Attention seekers */ + "bounce", + "flash", + "pulse", + "rubberBand", + "shakeX", + "headShake", + "swing", + "tada", + "wobble", + "jello", + "heartBeat", + /* Back entrances */ + "backInDown", + "backInLeft", + "backInRight", + "backInUp", + /* Back exits */ + "backOutDown", + "backOutLeft", + "backOutRight", + "backOutUp", + /* Bouncing entrances */ + "bounceIn", + "bounceInDown", + "bounceInLeft", + "bounceInRight", + "bounceInUp", + /* Bouncing exits */ + "bounceOut", + "bounceOutDown", + "bounceOutLeft", + "bounceOutRight", + "bounceOutUp", + /* Fading entrances */ + "fadeIn", + "fadeInDown", + "fadeInDownBig", + "fadeInLeft", + "fadeInLeftBig", + "fadeInRight", + "fadeInRightBig", + "fadeInUp", + "fadeInUpBig", + "fadeInTopLeft", + "fadeInTopRight", + "fadeInBottomLeft", + "fadeInBottomRight", + /* Fading exits */ + "fadeOut", + "fadeOutDown", + "fadeOutDownBig", + "fadeOutLeft", + "fadeOutLeftBig", + "fadeOutRight", + "fadeOutRightBig", + "fadeOutUp", + "fadeOutUpBig", + "fadeOutTopLeft", + "fadeOutTopRight", + "fadeOutBottomRight", + "fadeOutBottomLeft", + /* Flippers */ + "flip", + "flipInX", + "flipInY", + "flipOutX", + "flipOutY", + /* Lightspeed */ + "lightSpeedInRight", + "lightSpeedInLeft", + "lightSpeedOutRight", + "lightSpeedOutLeft", + /* Rotating entrances */ + "rotateIn", + "rotateInDownLeft", + "rotateInDownRight", + "rotateInUpLeft", + "rotateInUpRight", + /* Rotating exits */ + "rotateOut", + "rotateOutDownLeft", + "rotateOutDownRight", + "rotateOutUpLeft", + "rotateOutUpRight", + /* Specials */ + "hinge", + "jackInTheBox", + "rollIn", + "rollOut", + /* Zooming entrances */ + "zoomIn", + "zoomInDown", + "zoomInLeft", + "zoomInRight", + "zoomInUp", + /* Zooming exits */ + "zoomOut", + "zoomOutDown", + "zoomOutLeft", + "zoomOutRight", + "zoomOutUp", + /* Sliding entrances */ + "slideInDown", + "slideInLeft", + "slideInRight", + "slideInUp", + /* Sliding exits */ + "slideOutDown", + "slideOutLeft", + "slideOutRight", + "slideOutUp" +]; diff --git a/sop-admin/sop-admin-frontend/src/components/ReAnimateSelector/src/index.vue b/sop-admin/sop-admin-frontend/src/components/ReAnimateSelector/src/index.vue new file mode 100755 index 00000000..e10056b8 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/components/ReAnimateSelector/src/index.vue @@ -0,0 +1,136 @@ + + + + + diff --git a/sop-admin/sop-admin-frontend/src/components/ReAuth/index.ts b/sop-admin/sop-admin-frontend/src/components/ReAuth/index.ts new file mode 100755 index 00000000..975ed2ca --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/components/ReAuth/index.ts @@ -0,0 +1,5 @@ +import auth from "./src/auth"; + +const Auth = auth; + +export { Auth }; diff --git a/sop-admin/sop-admin-frontend/src/components/ReAuth/src/auth.tsx b/sop-admin/sop-admin-frontend/src/components/ReAuth/src/auth.tsx new file mode 100755 index 00000000..d2cf9b35 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/components/ReAuth/src/auth.tsx @@ -0,0 +1,20 @@ +import { defineComponent, Fragment } from "vue"; +import { hasAuth } from "@/router/utils"; + +export default defineComponent({ + name: "Auth", + props: { + value: { + type: undefined, + default: [] + } + }, + setup(props, { slots }) { + return () => { + if (!slots) return null; + return hasAuth(props.value) ? ( + {slots.default?.()} + ) : null; + }; + } +}); diff --git a/sop-admin/sop-admin-frontend/src/components/ReCol/index.ts b/sop-admin/sop-admin-frontend/src/components/ReCol/index.ts new file mode 100755 index 00000000..7a6c9374 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/components/ReCol/index.ts @@ -0,0 +1,29 @@ +import { ElCol } from "element-plus"; +import { h, defineComponent } from "vue"; + +// 封装element-plus的el-col组件 +export default defineComponent({ + name: "ReCol", + props: { + value: { + type: Number, + default: 24 + } + }, + render() { + const attrs = this.$attrs; + const val = this.value; + return h( + ElCol, + { + xs: val, + sm: val, + md: val, + lg: val, + xl: val, + ...attrs + }, + { default: () => this.$slots.default() } + ); + } +}); diff --git a/sop-admin/sop-admin-frontend/src/components/ReCropper/index.ts b/sop-admin/sop-admin-frontend/src/components/ReCropper/index.ts new file mode 100755 index 00000000..62e25903 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/components/ReCropper/index.ts @@ -0,0 +1,7 @@ +import reCropper from "./src"; +import { withInstall } from "@pureadmin/utils"; + +/** 图片裁剪组件 */ +export const ReCropper = withInstall(reCropper); + +export default ReCropper; diff --git a/sop-admin/sop-admin-frontend/src/components/ReCropper/src/circled.css b/sop-admin/sop-admin-frontend/src/components/ReCropper/src/circled.css new file mode 100755 index 00000000..54c77d23 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/components/ReCropper/src/circled.css @@ -0,0 +1,8 @@ +@import "cropperjs/dist/cropper.css"; + +.re-circled { + .cropper-view-box, + .cropper-face { + border-radius: 50%; + } +} diff --git a/sop-admin/sop-admin-frontend/src/components/ReCropper/src/index.tsx b/sop-admin/sop-admin-frontend/src/components/ReCropper/src/index.tsx new file mode 100755 index 00000000..826ffd0a --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/components/ReCropper/src/index.tsx @@ -0,0 +1,457 @@ +import "./circled.css"; +import Cropper from "cropperjs"; +import { ElUpload } from "element-plus"; +import type { CSSProperties } from "vue"; +import { useEventListener } from "@vueuse/core"; +import { longpress } from "@/directives/longpress"; +import { useTippy, directive as tippy } from "vue-tippy"; +import { + type PropType, + ref, + unref, + computed, + onMounted, + onUnmounted, + defineComponent +} from "vue"; +import { + delay, + debounce, + isArray, + downloadByBase64, + useResizeObserver +} from "@pureadmin/utils"; +import { + Reload, + Upload, + ArrowH, + ArrowV, + ArrowUp, + ArrowDown, + ArrowLeft, + ChangeIcon, + ArrowRight, + RotateLeft, + SearchPlus, + RotateRight, + SearchMinus, + DownloadIcon +} from "./svg"; + +type Options = Cropper.Options; + +const defaultOptions: Options = { + aspectRatio: 1, + zoomable: true, + zoomOnTouch: true, + zoomOnWheel: true, + cropBoxMovable: true, + cropBoxResizable: true, + toggleDragModeOnDblclick: true, + autoCrop: true, + background: true, + highlight: true, + center: true, + responsive: true, + restore: true, + checkCrossOrigin: true, + checkOrientation: true, + scalable: true, + modal: true, + guides: true, + movable: true, + rotatable: true +}; + +const props = { + src: { type: String, required: true }, + alt: { type: String }, + circled: { type: Boolean, default: false }, + /** 是否可以通过点击裁剪区域关闭右键弹出的功能菜单,默认 `true` */ + isClose: { type: Boolean, default: true }, + realTimePreview: { type: Boolean, default: true }, + height: { type: [String, Number], default: "360px" }, + crossorigin: { + type: String as PropType<"" | "anonymous" | "use-credentials" | undefined>, + default: undefined + }, + imageStyle: { type: Object as PropType, default: () => ({}) }, + options: { type: Object as PropType, default: () => ({}) } +}; + +export default defineComponent({ + name: "ReCropper", + props, + setup(props, { attrs, emit }) { + const tippyElRef = ref>(); + const imgElRef = ref>(); + const cropper = ref>(); + const inCircled = ref(props.circled); + const isInClose = ref(props.isClose); + const inSrc = ref(props.src); + const isReady = ref(false); + const imgBase64 = ref(); + + let scaleX = 1; + let scaleY = 1; + + const debounceRealTimeCroppered = debounce(realTimeCroppered, 80); + + const getImageStyle = computed((): CSSProperties => { + return { + height: props.height, + maxWidth: "100%", + ...props.imageStyle + }; + }); + + const getClass = computed(() => { + return [ + attrs.class, + { + ["re-circled"]: inCircled.value + } + ]; + }); + + const iconClass = computed(() => { + return [ + "p-[6px]", + "h-[30px]", + "w-[30px]", + "outline-none", + "rounded-[4px]", + "cursor-pointer", + "hover:bg-[rgba(0,0,0,0.06)]" + ]; + }); + + const getWrapperStyle = computed((): CSSProperties => { + return { height: `${props.height}`.replace(/px/, "") + "px" }; + }); + + onMounted(init); + + onUnmounted(() => { + cropper.value?.destroy(); + isReady.value = false; + cropper.value = null; + imgBase64.value = ""; + scaleX = 1; + scaleY = 1; + }); + + useResizeObserver(tippyElRef, () => handCropper("reset")); + + async function init() { + const imgEl = unref(imgElRef); + if (!imgEl) return; + cropper.value = new Cropper(imgEl, { + ...defaultOptions, + ready: () => { + isReady.value = true; + realTimeCroppered(); + delay(400).then(() => emit("readied", cropper.value)); + }, + crop() { + debounceRealTimeCroppered(); + }, + zoom() { + debounceRealTimeCroppered(); + }, + cropmove() { + debounceRealTimeCroppered(); + }, + ...props.options + }); + } + + function realTimeCroppered() { + props.realTimePreview && croppered(); + } + + function croppered() { + if (!cropper.value) return; + const canvas = inCircled.value + ? getRoundedCanvas() + : cropper.value.getCroppedCanvas(); + // https://developer.mozilla.org/zh-CN/docs/Web/API/HTMLCanvasElement/toBlob + canvas.toBlob(blob => { + if (!blob) return; + const fileReader: FileReader = new FileReader(); + fileReader.readAsDataURL(blob); + fileReader.onloadend = e => { + if (!e.target?.result || !blob) return; + imgBase64.value = e.target.result; + emit("cropper", { + base64: e.target.result, + blob, + info: { size: blob.size, ...cropper.value.getData() } + }); + }; + fileReader.onerror = () => { + emit("error"); + }; + }); + } + + function getRoundedCanvas() { + const sourceCanvas = cropper.value!.getCroppedCanvas(); + const canvas = document.createElement("canvas"); + const context = canvas.getContext("2d")!; + const width = sourceCanvas.width; + const height = sourceCanvas.height; + canvas.width = width; + canvas.height = height; + context.imageSmoothingEnabled = true; + context.drawImage(sourceCanvas, 0, 0, width, height); + context.globalCompositeOperation = "destination-in"; + context.beginPath(); + context.arc( + width / 2, + height / 2, + Math.min(width, height) / 2, + 0, + 2 * Math.PI, + true + ); + context.fill(); + return canvas; + } + + function handCropper(event: string, arg?: number | Array) { + if (event === "scaleX") { + scaleX = arg = scaleX === -1 ? 1 : -1; + } + + if (event === "scaleY") { + scaleY = arg = scaleY === -1 ? 1 : -1; + } + arg && isArray(arg) + ? cropper.value?.[event]?.(...arg) + : cropper.value?.[event]?.(arg); + } + + function beforeUpload(file) { + const reader = new FileReader(); + reader.readAsDataURL(file); + inSrc.value = ""; + reader.onload = e => { + inSrc.value = e.target?.result as string; + }; + reader.onloadend = () => { + init(); + }; + return false; + } + + const menuContent = defineComponent({ + directives: { + tippy, + longpress + }, + setup() { + return () => ( +
+ + + + downloadByBase64(imgBase64.value, "cropping.png")} + /> + { + inCircled.value = !inCircled.value; + realTimeCroppered(); + }} + /> + handCropper("reset")} + /> + handCropper("move", [0, -10]), "0:100"]} + /> + handCropper("move", [0, 10]), "0:100"]} + /> + handCropper("move", [-10, 0]), "0:100"]} + /> + handCropper("move", [10, 0]), "0:100"]} + /> + handCropper("scaleX", -1)} + /> + handCropper("scaleY", -1)} + /> + handCropper("rotate", -45)} + /> + handCropper("rotate", 45)} + /> + handCropper("zoom", 0.1), "0:100"]} + /> + handCropper("zoom", -0.1), "0:100"]} + /> +
+ ); + } + }); + + function onContextmenu(event) { + event.preventDefault(); + + const { show, setProps, destroy, state } = useTippy(tippyElRef, { + content: menuContent, + arrow: false, + theme: "light", + trigger: "manual", + interactive: true, + appendTo: "parent", + // hideOnClick: false, + placement: "bottom-end" + }); + + setProps({ + getReferenceClientRect: () => ({ + width: 0, + height: 0, + top: event.clientY, + bottom: event.clientY, + left: event.clientX, + right: event.clientX + }) + }); + + show(); + + if (isInClose.value) { + if (!state.value.isShown && !state.value.isVisible) return; + useEventListener(tippyElRef, "click", destroy); + } + } + + return { + inSrc, + props, + imgElRef, + tippyElRef, + getClass, + getWrapperStyle, + getImageStyle, + isReady, + croppered, + onContextmenu + }; + }, + + render() { + const { + inSrc, + isReady, + getClass, + getImageStyle, + onContextmenu, + getWrapperStyle + } = this; + const { alt, crossorigin } = this.props; + + return inSrc ? ( +
onContextmenu(event)} + > + {alt} +
+ ) : null; + } +}); diff --git a/sop-admin/sop-admin-frontend/src/components/ReCropper/src/svg/arrow-down.svg b/sop-admin/sop-admin-frontend/src/components/ReCropper/src/svg/arrow-down.svg new file mode 100755 index 00000000..36558e89 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/components/ReCropper/src/svg/arrow-down.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/sop-admin/sop-admin-frontend/src/components/ReCropper/src/svg/arrow-h.svg b/sop-admin/sop-admin-frontend/src/components/ReCropper/src/svg/arrow-h.svg new file mode 100755 index 00000000..f955c41e --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/components/ReCropper/src/svg/arrow-h.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/sop-admin/sop-admin-frontend/src/components/ReCropper/src/svg/arrow-left.svg b/sop-admin/sop-admin-frontend/src/components/ReCropper/src/svg/arrow-left.svg new file mode 100755 index 00000000..5f1c01e0 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/components/ReCropper/src/svg/arrow-left.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/sop-admin/sop-admin-frontend/src/components/ReCropper/src/svg/arrow-right.svg b/sop-admin/sop-admin-frontend/src/components/ReCropper/src/svg/arrow-right.svg new file mode 100755 index 00000000..1a0fe00e --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/components/ReCropper/src/svg/arrow-right.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/sop-admin/sop-admin-frontend/src/components/ReCropper/src/svg/arrow-up.svg b/sop-admin/sop-admin-frontend/src/components/ReCropper/src/svg/arrow-up.svg new file mode 100755 index 00000000..942f9261 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/components/ReCropper/src/svg/arrow-up.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/sop-admin/sop-admin-frontend/src/components/ReCropper/src/svg/arrow-v.svg b/sop-admin/sop-admin-frontend/src/components/ReCropper/src/svg/arrow-v.svg new file mode 100755 index 00000000..bbd0476f --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/components/ReCropper/src/svg/arrow-v.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/sop-admin/sop-admin-frontend/src/components/ReCropper/src/svg/change.svg b/sop-admin/sop-admin-frontend/src/components/ReCropper/src/svg/change.svg new file mode 100755 index 00000000..ec3f02b3 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/components/ReCropper/src/svg/change.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/sop-admin/sop-admin-frontend/src/components/ReCropper/src/svg/download.svg b/sop-admin/sop-admin-frontend/src/components/ReCropper/src/svg/download.svg new file mode 100755 index 00000000..854b2c99 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/components/ReCropper/src/svg/download.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/sop-admin/sop-admin-frontend/src/components/ReCropper/src/svg/index.ts b/sop-admin/sop-admin-frontend/src/components/ReCropper/src/svg/index.ts new file mode 100755 index 00000000..1306ba77 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/components/ReCropper/src/svg/index.ts @@ -0,0 +1,31 @@ +import Reload from "./reload.svg?component"; +import Upload from "./upload.svg?component"; +import ArrowH from "./arrow-h.svg?component"; +import ArrowV from "./arrow-v.svg?component"; +import ArrowUp from "./arrow-up.svg?component"; +import ChangeIcon from "./change.svg?component"; +import ArrowDown from "./arrow-down.svg?component"; +import ArrowLeft from "./arrow-left.svg?component"; +import DownloadIcon from "./download.svg?component"; +import ArrowRight from "./arrow-right.svg?component"; +import RotateLeft from "./rotate-left.svg?component"; +import SearchPlus from "./search-plus.svg?component"; +import RotateRight from "./rotate-right.svg?component"; +import SearchMinus from "./search-minus.svg?component"; + +export { + Reload, + Upload, + ArrowH, + ArrowV, + ArrowUp, + ArrowDown, + ArrowLeft, + ChangeIcon, + ArrowRight, + RotateLeft, + SearchPlus, + RotateRight, + SearchMinus, + DownloadIcon +}; diff --git a/sop-admin/sop-admin-frontend/src/components/ReCropper/src/svg/reload.svg b/sop-admin/sop-admin-frontend/src/components/ReCropper/src/svg/reload.svg new file mode 100755 index 00000000..9f9615a7 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/components/ReCropper/src/svg/reload.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/sop-admin/sop-admin-frontend/src/components/ReCropper/src/svg/rotate-left.svg b/sop-admin/sop-admin-frontend/src/components/ReCropper/src/svg/rotate-left.svg new file mode 100755 index 00000000..bea3fc01 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/components/ReCropper/src/svg/rotate-left.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/sop-admin/sop-admin-frontend/src/components/ReCropper/src/svg/rotate-right.svg b/sop-admin/sop-admin-frontend/src/components/ReCropper/src/svg/rotate-right.svg new file mode 100755 index 00000000..67ecdc60 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/components/ReCropper/src/svg/rotate-right.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/sop-admin/sop-admin-frontend/src/components/ReCropper/src/svg/search-minus.svg b/sop-admin/sop-admin-frontend/src/components/ReCropper/src/svg/search-minus.svg new file mode 100755 index 00000000..73727063 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/components/ReCropper/src/svg/search-minus.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/sop-admin/sop-admin-frontend/src/components/ReCropper/src/svg/search-plus.svg b/sop-admin/sop-admin-frontend/src/components/ReCropper/src/svg/search-plus.svg new file mode 100755 index 00000000..5fa8ae9e --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/components/ReCropper/src/svg/search-plus.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/sop-admin/sop-admin-frontend/src/components/ReCropper/src/svg/upload.svg b/sop-admin/sop-admin-frontend/src/components/ReCropper/src/svg/upload.svg new file mode 100755 index 00000000..a0080199 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/components/ReCropper/src/svg/upload.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/sop-admin/sop-admin-frontend/src/components/ReCropperPreview/index.ts b/sop-admin/sop-admin-frontend/src/components/ReCropperPreview/index.ts new file mode 100755 index 00000000..e7949fee --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/components/ReCropperPreview/index.ts @@ -0,0 +1,7 @@ +import reCropperPreview from "./src/index.vue"; +import { withInstall } from "@pureadmin/utils"; + +/** 图片裁剪预览组件 */ +export const ReCropperPreview = withInstall(reCropperPreview); + +export default ReCropperPreview; diff --git a/sop-admin/sop-admin-frontend/src/components/ReCropperPreview/src/index.vue b/sop-admin/sop-admin-frontend/src/components/ReCropperPreview/src/index.vue new file mode 100755 index 00000000..c34cc94e --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/components/ReCropperPreview/src/index.vue @@ -0,0 +1,76 @@ + + + diff --git a/sop-admin/sop-admin-frontend/src/components/ReDialog/index.ts b/sop-admin/sop-admin-frontend/src/components/ReDialog/index.ts new file mode 100755 index 00000000..b471764b --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/components/ReDialog/index.ts @@ -0,0 +1,69 @@ +import { ref } from "vue"; +import reDialog from "./index.vue"; +import { useTimeoutFn } from "@vueuse/core"; +import { withInstall } from "@pureadmin/utils"; +import type { + EventType, + ArgsType, + DialogProps, + ButtonProps, + DialogOptions +} from "./type"; + +const dialogStore = ref>([]); + +/** 打开弹框 */ +const addDialog = (options: DialogOptions) => { + const open = () => + dialogStore.value.push(Object.assign(options, { visible: true })); + if (options?.openDelay) { + useTimeoutFn(() => { + open(); + }, options.openDelay); + } else { + open(); + } +}; + +/** 关闭弹框 */ +const closeDialog = (options: DialogOptions, index: number, args?: any) => { + dialogStore.value[index].visible = false; + options.closeCallBack && options.closeCallBack({ options, index, args }); + + const closeDelay = options?.closeDelay ?? 200; + useTimeoutFn(() => { + dialogStore.value.splice(index, 1); + }, closeDelay); +}; + +/** + * @description 更改弹框自身属性值 + * @param value 属性值 + * @param key 属性,默认`title` + * @param index 弹框索引(默认`0`,代表只有一个弹框,对于嵌套弹框要改哪个弹框的属性值就把该弹框索引赋给`index`) + */ +const updateDialog = (value: any, key = "title", index = 0) => { + dialogStore.value[index][key] = value; +}; + +/** 关闭所有弹框 */ +const closeAllDialog = () => { + dialogStore.value = []; +}; + +/** 千万别忘了在下面这三处引入并注册下,放心注册,不使用`addDialog`调用就不会被挂载 + * https://github.com/pure-admin/vue-pure-admin/blob/main/src/App.vue#L4 + * https://github.com/pure-admin/vue-pure-admin/blob/main/src/App.vue#L12 + * https://github.com/pure-admin/vue-pure-admin/blob/main/src/App.vue#L22 + */ +const ReDialog = withInstall(reDialog); + +export type { EventType, ArgsType, DialogProps, ButtonProps, DialogOptions }; +export { + ReDialog, + dialogStore, + addDialog, + closeDialog, + updateDialog, + closeAllDialog +}; diff --git a/sop-admin/sop-admin-frontend/src/components/ReDialog/index.vue b/sop-admin/sop-admin-frontend/src/components/ReDialog/index.vue new file mode 100755 index 00000000..23a0106e --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/components/ReDialog/index.vue @@ -0,0 +1,206 @@ + + + diff --git a/sop-admin/sop-admin-frontend/src/components/ReDialog/type.ts b/sop-admin/sop-admin-frontend/src/components/ReDialog/type.ts new file mode 100755 index 00000000..7efbe201 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/components/ReDialog/type.ts @@ -0,0 +1,275 @@ +import type { CSSProperties, VNode, Component } from "vue"; + +type DoneFn = (cancel?: boolean) => void; +type EventType = + | "open" + | "close" + | "openAutoFocus" + | "closeAutoFocus" + | "fullscreenCallBack"; +type ArgsType = { + /** `cancel` 点击取消按钮、`sure` 点击确定按钮、`close` 点击右上角关闭按钮或空白页或按下了esc键 */ + command: "cancel" | "sure" | "close"; +}; +type ButtonType = + | "primary" + | "success" + | "warning" + | "danger" + | "info" + | "text"; + +/** https://element-plus.org/zh-CN/component/dialog.html#attributes */ +type DialogProps = { + /** `Dialog` 的显示与隐藏 */ + visible?: boolean; + /** `Dialog` 的标题 */ + title?: string; + /** `Dialog` 的宽度,默认 `50%` */ + width?: string | number; + /** 是否为全屏 `Dialog`(会一直处于全屏状态,除非弹框关闭),默认 `false`,`fullscreen` 和 `fullscreenIcon` 都传时只有 `fullscreen` 会生效 */ + fullscreen?: boolean; + /** 是否显示全屏操作图标,默认 `false`,`fullscreen` 和 `fullscreenIcon` 都传时只有 `fullscreen` 会生效 */ + fullscreenIcon?: boolean; + /** `Dialog CSS` 中的 `margin-top` 值,默认 `15vh` */ + top?: string; + /** 是否需要遮罩层,默认 `true` */ + modal?: boolean; + /** `Dialog` 自身是否插入至 `body` 元素上。嵌套的 `Dialog` 必须指定该属性并赋值为 `true`,默认 `false` */ + appendToBody?: boolean; + /** 是否在 `Dialog` 出现时将 `body` 滚动锁定,默认 `true` */ + lockScroll?: boolean; + /** `Dialog` 的自定义类名 */ + class?: string; + /** `Dialog` 的自定义样式 */ + style?: CSSProperties; + /** `Dialog` 打开的延时时间,单位毫秒,默认 `0` */ + openDelay?: number; + /** `Dialog` 关闭的延时时间,单位毫秒,默认 `0` */ + closeDelay?: number; + /** 是否可以通过点击 `modal` 关闭 `Dialog`,默认 `true` */ + closeOnClickModal?: boolean; + /** 是否可以通过按下 `ESC` 关闭 `Dialog`,默认 `true` */ + closeOnPressEscape?: boolean; + /** 是否显示关闭按钮,默认 `true` */ + showClose?: boolean; + /** 关闭前的回调,会暂停 `Dialog` 的关闭. 回调函数内执行 `done` 参数方法的时候才是真正关闭对话框的时候 */ + beforeClose?: (done: DoneFn) => void; + /** 为 `Dialog` 启用可拖拽功能,默认 `false` */ + draggable?: boolean; + /** 是否让 `Dialog` 的 `header` 和 `footer` 部分居中排列,默认 `false` */ + center?: boolean; + /** 是否水平垂直对齐对话框,默认 `false` */ + alignCenter?: boolean; + /** 当关闭 `Dialog` 时,销毁其中的元素,默认 `false` */ + destroyOnClose?: boolean; +}; + +//element-plus.org/zh-CN/component/popconfirm.html#attributes +type Popconfirm = { + /** 标题 */ + title?: string; + /** 确定按钮文字 */ + confirmButtonText?: string; + /** 取消按钮文字 */ + cancelButtonText?: string; + /** 确定按钮类型,默认 `primary` */ + confirmButtonType?: ButtonType; + /** 取消按钮类型,默认 `text` */ + cancelButtonType?: ButtonType; + /** 自定义图标,默认 `QuestionFilled` */ + icon?: string | Component; + /** `Icon` 颜色,默认 `#f90` */ + iconColor?: string; + /** 是否隐藏 `Icon`,默认 `false` */ + hideIcon?: boolean; + /** 关闭时的延迟,默认 `200` */ + hideAfter?: number; + /** 是否将 `popover` 的下拉列表插入至 `body` 元素,默认 `true` */ + teleported?: boolean; + /** 当 `popover` 组件长时间不触发且 `persistent` 属性设置为 `false` 时, `popover` 将会被删除,默认 `false` */ + persistent?: boolean; + /** 弹层宽度,最小宽度 `150px`,默认 `150` */ + width?: string | number; +}; + +type BtnClickDialog = { + options?: DialogOptions; + index?: number; +}; +type BtnClickButton = { + btn?: ButtonProps; + index?: number; +}; +/** https://element-plus.org/zh-CN/component/button.html#button-attributes */ +type ButtonProps = { + /** 按钮文字 */ + label: string; + /** 按钮尺寸 */ + size?: "large" | "default" | "small"; + /** 按钮类型 */ + type?: "primary" | "success" | "warning" | "danger" | "info"; + /** 是否为朴素按钮,默认 `false` */ + plain?: boolean; + /** 是否为文字按钮,默认 `false` */ + text?: boolean; + /** 是否显示文字按钮背景颜色,默认 `false` */ + bg?: boolean; + /** 是否为链接按钮,默认 `false` */ + link?: boolean; + /** 是否为圆角按钮,默认 `false` */ + round?: boolean; + /** 是否为圆形按钮,默认 `false` */ + circle?: boolean; + /** 确定按钮的 `Popconfirm` 气泡确认框相关配置 */ + popconfirm?: Popconfirm; + /** 是否为加载中状态,默认 `false` */ + loading?: boolean; + /** 自定义加载中状态图标组件 */ + loadingIcon?: string | Component; + /** 按钮是否为禁用状态,默认 `false` */ + disabled?: boolean; + /** 图标组件 */ + icon?: string | Component; + /** 是否开启原生 `autofocus` 属性,默认 `false` */ + autofocus?: boolean; + /** 原生 `type` 属性,默认 `button` */ + nativeType?: "button" | "submit" | "reset"; + /** 自动在两个中文字符之间插入空格 */ + autoInsertSpace?: boolean; + /** 自定义按钮颜色, 并自动计算 `hover` 和 `active` 触发后的颜色 */ + color?: string; + /** `dark` 模式, 意味着自动设置 `color` 为 `dark` 模式的颜色,默认 `false` */ + dark?: boolean; + /** 自定义元素标签 */ + tag?: string | Component; + /** 点击按钮后触发的回调 */ + btnClick?: ({ + dialog, + button + }: { + /** 当前 `Dialog` 信息 */ + dialog: BtnClickDialog; + /** 当前 `button` 信息 */ + button: BtnClickButton; + }) => void; +}; + +interface DialogOptions extends DialogProps { + /** 内容区组件的 `props`,可通过 `defineProps` 接收 */ + props?: any; + /** 是否隐藏 `Dialog` 按钮操作区的内容 */ + hideFooter?: boolean; + /** 确定按钮的 `Popconfirm` 气泡确认框相关配置 */ + popconfirm?: Popconfirm; + /** 点击确定按钮后是否开启 `loading` 加载动画 */ + sureBtnLoading?: boolean; + /** + * @description 自定义对话框标题的内容渲染器 + * @see {@link https://element-plus.org/zh-CN/component/dialog.html#%E8%87%AA%E5%AE%9A%E4%B9%89%E5%A4%B4%E9%83%A8} + */ + headerRenderer?: ({ + close, + titleId, + titleClass + }: { + close: Function; + titleId: string; + titleClass: string; + }) => VNode | Component; + /** 自定义内容渲染器 */ + contentRenderer?: ({ + options, + index + }: { + options: DialogOptions; + index: number; + }) => VNode | Component; + /** 自定义按钮操作区的内容渲染器,会覆盖`footerButtons`以及默认的 `取消` 和 `确定` 按钮 */ + footerRenderer?: ({ + options, + index + }: { + options: DialogOptions; + index: number; + }) => VNode | Component; + /** 自定义底部按钮操作 */ + footerButtons?: Array; + /** `Dialog` 打开后的回调 */ + open?: ({ + options, + index + }: { + options: DialogOptions; + index: number; + }) => void; + /** `Dialog` 关闭后的回调(只有点击右上角关闭按钮或空白页或按下了esc键关闭页面时才会触发) */ + close?: ({ + options, + index + }: { + options: DialogOptions; + index: number; + }) => void; + /** `Dialog` 关闭后的回调。 `args` 返回的 `command` 值解析:`cancel` 点击取消按钮、`sure` 点击确定按钮、`close` 点击右上角关闭按钮或空白页或按下了esc键 */ + closeCallBack?: ({ + options, + index, + args + }: { + options: DialogOptions; + index: number; + args: any; + }) => void; + /** 点击全屏按钮时的回调 */ + fullscreenCallBack?: ({ + options, + index + }: { + options: DialogOptions; + index: number; + }) => void; + /** 输入焦点聚焦在 `Dialog` 内容时的回调 */ + openAutoFocus?: ({ + options, + index + }: { + options: DialogOptions; + index: number; + }) => void; + /** 输入焦点从 `Dialog` 内容失焦时的回调 */ + closeAutoFocus?: ({ + options, + index + }: { + options: DialogOptions; + index: number; + }) => void; + /** 点击底部取消按钮的回调,会暂停 `Dialog` 的关闭. 回调函数内执行 `done` 参数方法的时候才是真正关闭对话框的时候 */ + beforeCancel?: ( + done: Function, + { + options, + index + }: { + options: DialogOptions; + index: number; + } + ) => void; + /** 点击底部确定按钮的回调,会暂停 `Dialog` 的关闭. 回调函数内执行 `done` 参数方法的时候才是真正关闭对话框的时候 */ + beforeSure?: ( + done: Function, + { + options, + index, + closeLoading + }: { + options: DialogOptions; + index: number; + /** 关闭确定按钮的 `loading` 加载动画 */ + closeLoading: Function; + } + ) => void; +} + +export type { EventType, ArgsType, DialogProps, ButtonProps, DialogOptions }; diff --git a/sop-admin/sop-admin-frontend/src/components/ReIcon/data.ts b/sop-admin/sop-admin-frontend/src/components/ReIcon/data.ts new file mode 100755 index 00000000..b5769e8c --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/components/ReIcon/data.ts @@ -0,0 +1,3869 @@ +/** + * 想要哪个图标集 自行添加即可 请注意此处添加的图标集均为在线图标(https://iconify.design/docs/api/#public-api) + * 如果项目在内网环境下 参考 https://www.bilibili.com/video/BV17S4y1J79d?p=4&vd_source=5a992808de6229d78e7810536c5f9ab3 教程自行离线部署图标 + * https://icones.js.org/collections/图标集前缀名-meta.json(如:https://icones.js.org/collections/ri-meta.json 取icons字段,可获得当前图标集的所有图标) + */ +export const IconJson = { + // https://icones.js.org/collections/ep-meta.json + "ep:": [ + "add-location", + "aim", + "alarm-clock", + "apple", + "arrow-down", + "arrow-down-bold", + "arrow-left", + "arrow-left-bold", + "arrow-right", + "arrow-right-bold", + "arrow-up", + "arrow-up-bold", + "avatar", + "back", + "baseball", + "basketball", + "bell", + "bell-filled", + "bicycle", + "bottom", + "bottom-left", + "bottom-right", + "bowl", + "box", + "briefcase", + "brush", + "brush-filled", + "burger", + "calendar", + "camera", + "camera-filled", + "caret-bottom", + "caret-left", + "caret-right", + "caret-top", + "cellphone", + "chat-dot-round", + "chat-dot-square", + "chat-line-round", + "chat-line-square", + "chat-round", + "chat-square", + "check", + "checked", + "cherry", + "chicken", + "chrome-filled", + "circle-check", + "circle-check-filled", + "circle-close", + "circle-close-filled", + "circle-plus", + "circle-plus-filled", + "clock", + "close", + "close-bold", + "cloudy", + "coffee", + "coffee-cup", + "coin", + "cold-drink", + "collection", + "collection-tag", + "comment", + "compass", + "connection", + "coordinate", + "copy-document", + "cpu", + "credit-card", + "crop", + "d-arrow-left", + "d-arrow-right", + "d-caret", + "data-analysis", + "data-board", + "data-line", + "delete", + "delete-filled", + "delete-location", + "dessert", + "discount", + "dish", + "dish-dot", + "document", + "document-add", + "document-checked", + "document-copy", + "document-delete", + "document-remove", + "download", + "drizzling", + "edit", + "edit-pen", + "eleme", + "eleme-filled", + "element-plus", + "expand", + "failed", + "female", + "files", + "film", + "filter", + "finished", + "first-aid-kit", + "flag", + "fold", + "folder", + "folder-add", + "folder-checked", + "folder-delete", + "folder-opened", + "folder-remove", + "food", + "football", + "fork-spoon", + "fries", + "full-screen", + "goblet", + "goblet-full", + "goblet-square", + "goblet-square-full", + "gold-medal", + "goods", + "goods-filled", + "grape", + "grid", + "guide", + "handbag", + "headset", + "help", + "help-filled", + "hide", + "histogram", + "home-filled", + "hot-water", + "house", + "ice-cream", + "ice-cream-round", + "ice-cream-square", + "ice-drink", + "ice-tea", + "info-filled", + "iphone", + "key", + "knife-fork", + "lightning", + "link", + "list", + "loading", + "location", + "location-filled", + "location-information", + "lock", + "lollipop", + "magic-stick", + "magnet", + "male", + "management", + "map-location", + "medal", + "memo", + "menu", + "message", + "message-box", + "mic", + "microphone", + "milk-tea", + "minus", + "money", + "monitor", + "moon", + "moon-night", + "more", + "more-filled", + "mostly-cloudy", + "mouse", + "mug", + "mute", + "mute-notification", + "no-smoking", + "notebook", + "notification", + "odometer", + "office-building", + "open", + "operation", + "opportunity", + "orange", + "paperclip", + "partly-cloudy", + "pear", + "phone", + "phone-filled", + "picture", + "picture-filled", + "picture-rounded", + "pie-chart", + "place", + "platform", + "plus", + "pointer", + "position", + "postcard", + "pouring", + "present", + "price-tag", + "printer", + "promotion", + "quartz-watch", + "question-filled", + "rank", + "reading", + "reading-lamp", + "refresh", + "refresh-left", + "refresh-right", + "refrigerator", + "remove", + "remove-filled", + "right", + "scale-to-original", + "school", + "scissor", + "search", + "select", + "sell", + "semi-select", + "service", + "set-up", + "setting", + "share", + "ship", + "shop", + "shopping-bag", + "shopping-cart", + "shopping-cart-full", + "shopping-trolley", + "smoking", + "soccer", + "sold-out", + "sort", + "sort-down", + "sort-up", + "stamp", + "star", + "star-filled", + "stopwatch", + "success-filled", + "sugar", + "suitcase", + "suitcase-line", + "sunny", + "sunrise", + "sunset", + "switch", + "switch-button", + "switch-filled", + "takeaway-box", + "ticket", + "tickets", + "timer", + "toilet-paper", + "tools", + "top", + "top-left", + "top-right", + "trend-charts", + "trophy", + "trophy-base", + "turn-off", + "umbrella", + "unlock", + "upload", + "upload-filled", + "user", + "user-filled", + "van", + "video-camera", + "video-camera-filled", + "video-pause", + "video-play", + "view", + "wallet", + "wallet-filled", + "warn-triangle-filled", + "warning", + "warning-filled", + "watch", + "watermelon", + "wind-power", + "zoom-in", + "zoom-out" + ], + // https://icones.js.org/collections/ri-meta.json + "ri:": [ + "24-hours-fill", + "24-hours-line", + "4k-fill", + "4k-line", + "a-b", + "account-box-fill", + "account-box-line", + "account-circle-fill", + "account-circle-line", + "account-pin-box-fill", + "account-pin-box-line", + "account-pin-circle-fill", + "account-pin-circle-line", + "add-box-fill", + "add-box-line", + "add-circle-fill", + "add-circle-line", + "add-fill", + "add-line", + "admin-fill", + "admin-line", + "advertisement-fill", + "advertisement-line", + "ai-generate", + "airplay-fill", + "airplay-line", + "alarm-fill", + "alarm-line", + "alarm-warning-fill", + "alarm-warning-line", + "album-fill", + "album-line", + "alert-fill", + "alert-line", + "aliens-fill", + "aliens-line", + "align-bottom", + "align-center", + "align-justify", + "align-left", + "align-right", + "align-top", + "align-vertically", + "alipay-fill", + "alipay-line", + "amazon-fill", + "amazon-line", + "anchor-fill", + "anchor-line", + "ancient-gate-fill", + "ancient-gate-line", + "ancient-pavilion-fill", + "ancient-pavilion-line", + "android-fill", + "android-line", + "angularjs-fill", + "angularjs-line", + "anticlockwise-2-fill", + "anticlockwise-2-line", + "anticlockwise-fill", + "anticlockwise-line", + "app-store-fill", + "app-store-line", + "apple-fill", + "apple-line", + "apps-2-fill", + "apps-2-line", + "apps-fill", + "apps-line", + "archive-2-fill", + "archive-2-line", + "archive-drawer-fill", + "archive-drawer-line", + "archive-fill", + "archive-line", + "arrow-down-circle-fill", + "arrow-down-circle-line", + "arrow-down-double-fill", + "arrow-down-double-line", + "arrow-down-fill", + "arrow-down-line", + "arrow-down-s-fill", + "arrow-down-s-line", + "arrow-drop-down-fill", + "arrow-drop-down-line", + "arrow-drop-left-fill", + "arrow-drop-left-line", + "arrow-drop-right-fill", + "arrow-drop-right-line", + "arrow-drop-up-fill", + "arrow-drop-up-line", + "arrow-go-back-fill", + "arrow-go-back-line", + "arrow-go-forward-fill", + "arrow-go-forward-line", + "arrow-left-circle-fill", + "arrow-left-circle-line", + "arrow-left-double-fill", + "arrow-left-double-line", + "arrow-left-down-fill", + "arrow-left-down-line", + "arrow-left-fill", + "arrow-left-line", + "arrow-left-right-fill", + "arrow-left-right-line", + "arrow-left-s-fill", + "arrow-left-s-line", + "arrow-left-up-fill", + "arrow-left-up-line", + "arrow-right-circle-fill", + "arrow-right-circle-line", + "arrow-right-double-fill", + "arrow-right-double-line", + "arrow-right-down-fill", + "arrow-right-down-line", + "arrow-right-fill", + "arrow-right-line", + "arrow-right-s-fill", + "arrow-right-s-line", + "arrow-right-up-fill", + "arrow-right-up-line", + "arrow-turn-back-fill", + "arrow-turn-back-line", + "arrow-turn-forward-fill", + "arrow-turn-forward-line", + "arrow-up-circle-fill", + "arrow-up-circle-line", + "arrow-up-double-fill", + "arrow-up-double-line", + "arrow-up-down-fill", + "arrow-up-down-line", + "arrow-up-fill", + "arrow-up-line", + "arrow-up-s-fill", + "arrow-up-s-line", + "artboard-2-fill", + "artboard-2-line", + "artboard-fill", + "artboard-line", + "article-fill", + "article-line", + "aspect-ratio-fill", + "aspect-ratio-line", + "asterisk", + "at-fill", + "at-line", + "attachment-2", + "attachment-fill", + "attachment-line", + "auction-fill", + "auction-line", + "award-fill", + "award-line", + "baidu-fill", + "baidu-line", + "ball-pen-fill", + "ball-pen-line", + "bank-card-2-fill", + "bank-card-2-line", + "bank-card-fill", + "bank-card-line", + "bank-fill", + "bank-line", + "bar-chart-2-fill", + "bar-chart-2-line", + "bar-chart-box-fill", + "bar-chart-box-line", + "bar-chart-fill", + "bar-chart-grouped-fill", + "bar-chart-grouped-line", + "bar-chart-horizontal-fill", + "bar-chart-horizontal-line", + "bar-chart-line", + "barcode-box-fill", + "barcode-box-line", + "barcode-fill", + "barcode-line", + "bard-fill", + "bard-line", + "barricade-fill", + "barricade-line", + "base-station-fill", + "base-station-line", + "basketball-fill", + "basketball-line", + "battery-2-charge-fill", + "battery-2-charge-line", + "battery-2-fill", + "battery-2-line", + "battery-charge-fill", + "battery-charge-line", + "battery-fill", + "battery-line", + "battery-low-fill", + "battery-low-line", + "battery-saver-fill", + "battery-saver-line", + "battery-share-fill", + "battery-share-line", + "bear-smile-fill", + "bear-smile-line", + "beer-fill", + "beer-line", + "behance-fill", + "behance-line", + "bell-fill", + "bell-line", + "bike-fill", + "bike-line", + "bilibili-fill", + "bilibili-line", + "bill-fill", + "bill-line", + "billiards-fill", + "billiards-line", + "bit-coin-fill", + "bit-coin-line", + "blaze-fill", + "blaze-line", + "blender-fill", + "blender-line", + "bluetooth-connect-fill", + "bluetooth-connect-line", + "bluetooth-fill", + "bluetooth-line", + "blur-off-fill", + "blur-off-line", + "body-scan-fill", + "body-scan-line", + "bold", + "book-2-fill", + "book-2-line", + "book-3-fill", + "book-3-line", + "book-fill", + "book-line", + "book-mark-fill", + "book-mark-line", + "book-open-fill", + "book-open-line", + "book-read-fill", + "book-read-line", + "booklet-fill", + "booklet-line", + "bookmark-2-fill", + "bookmark-2-line", + "bookmark-3-fill", + "bookmark-3-line", + "bookmark-fill", + "bookmark-line", + "bootstrap-fill", + "bootstrap-line", + "box-1-fill", + "box-1-line", + "box-2-fill", + "box-2-line", + "box-3-fill", + "box-3-line", + "boxing-fill", + "boxing-line", + "braces-fill", + "braces-line", + "brackets-fill", + "brackets-line", + "brain-fill", + "brain-line", + "briefcase-2-fill", + "briefcase-2-line", + "briefcase-3-fill", + "briefcase-3-line", + "briefcase-4-fill", + "briefcase-4-line", + "briefcase-5-fill", + "briefcase-5-line", + "briefcase-fill", + "briefcase-line", + "bring-forward", + "bring-to-front", + "broadcast-fill", + "broadcast-line", + "brush-2-fill", + "brush-2-line", + "brush-3-fill", + "brush-3-line", + "brush-4-fill", + "brush-4-line", + "brush-fill", + "brush-line", + "bubble-chart-fill", + "bubble-chart-line", + "bug-2-fill", + "bug-2-line", + "bug-fill", + "bug-line", + "building-2-fill", + "building-2-line", + "building-3-fill", + "building-3-line", + "building-4-fill", + "building-4-line", + "building-fill", + "building-line", + "bus-2-fill", + "bus-2-line", + "bus-fill", + "bus-line", + "bus-wifi-fill", + "bus-wifi-line", + "cactus-fill", + "cactus-line", + "cake-2-fill", + "cake-2-line", + "cake-3-fill", + "cake-3-line", + "cake-fill", + "cake-line", + "calculator-fill", + "calculator-line", + "calendar-2-fill", + "calendar-2-line", + "calendar-check-fill", + "calendar-check-line", + "calendar-close-fill", + "calendar-close-line", + "calendar-event-fill", + "calendar-event-line", + "calendar-fill", + "calendar-line", + "calendar-todo-fill", + "calendar-todo-line", + "camera-2-fill", + "camera-2-line", + "camera-3-fill", + "camera-3-line", + "camera-fill", + "camera-lens-fill", + "camera-lens-line", + "camera-line", + "camera-off-fill", + "camera-off-line", + "camera-switch-fill", + "camera-switch-line", + "candle-fill", + "candle-line", + "capsule-fill", + "capsule-line", + "car-fill", + "car-line", + "car-washing-fill", + "car-washing-line", + "caravan-fill", + "caravan-line", + "cash-fill", + "cash-line", + "cast-fill", + "cast-line", + "cellphone-fill", + "cellphone-line", + "celsius-fill", + "celsius-line", + "centos-fill", + "centos-line", + "character-recognition-fill", + "character-recognition-line", + "charging-pile-2-fill", + "charging-pile-2-line", + "charging-pile-fill", + "charging-pile-line", + "chat-1-fill", + "chat-1-line", + "chat-2-fill", + "chat-2-line", + "chat-3-fill", + "chat-3-line", + "chat-4-fill", + "chat-4-line", + "chat-check-fill", + "chat-check-line", + "chat-delete-fill", + "chat-delete-line", + "chat-download-fill", + "chat-download-line", + "chat-follow-up-fill", + "chat-follow-up-line", + "chat-forward-fill", + "chat-forward-line", + "chat-heart-fill", + "chat-heart-line", + "chat-history-fill", + "chat-history-line", + "chat-new-fill", + "chat-new-line", + "chat-off-fill", + "chat-off-line", + "chat-poll-fill", + "chat-poll-line", + "chat-private-fill", + "chat-private-line", + "chat-quote-fill", + "chat-quote-line", + "chat-settings-fill", + "chat-settings-line", + "chat-smile-2-fill", + "chat-smile-2-line", + "chat-smile-3-fill", + "chat-smile-3-line", + "chat-smile-fill", + "chat-smile-line", + "chat-upload-fill", + "chat-upload-line", + "chat-voice-fill", + "chat-voice-line", + "check-double-fill", + "check-double-line", + "check-fill", + "check-line", + "checkbox-blank-circle-fill", + "checkbox-blank-circle-line", + "checkbox-blank-fill", + "checkbox-blank-line", + "checkbox-circle-fill", + "checkbox-circle-line", + "checkbox-fill", + "checkbox-indeterminate-fill", + "checkbox-indeterminate-line", + "checkbox-line", + "checkbox-multiple-blank-fill", + "checkbox-multiple-blank-line", + "checkbox-multiple-fill", + "checkbox-multiple-line", + "china-railway-fill", + "china-railway-line", + "chrome-fill", + "chrome-line", + "circle-fill", + "circle-line", + "clapperboard-fill", + "clapperboard-line", + "clipboard-fill", + "clipboard-line", + "clockwise-2-fill", + "clockwise-2-line", + "clockwise-fill", + "clockwise-line", + "close-circle-fill", + "close-circle-line", + "close-fill", + "close-line", + "closed-captioning-fill", + "closed-captioning-line", + "cloud-fill", + "cloud-line", + "cloud-off-fill", + "cloud-off-line", + "cloud-windy-fill", + "cloud-windy-line", + "cloudy-2-fill", + "cloudy-2-line", + "cloudy-fill", + "cloudy-line", + "code-box-fill", + "code-box-line", + "code-fill", + "code-line", + "code-s-fill", + "code-s-line", + "code-s-slash-fill", + "code-s-slash-line", + "code-view", + "codepen-fill", + "codepen-line", + "coin-fill", + "coin-line", + "coins-fill", + "coins-line", + "collage-fill", + "collage-line", + "command-fill", + "command-line", + "community-fill", + "community-line", + "compass-2-fill", + "compass-2-line", + "compass-3-fill", + "compass-3-line", + "compass-4-fill", + "compass-4-line", + "compass-discover-fill", + "compass-discover-line", + "compass-fill", + "compass-line", + "compasses-2-fill", + "compasses-2-line", + "compasses-fill", + "compasses-line", + "computer-fill", + "computer-line", + "contacts-book-2-fill", + "contacts-book-2-line", + "contacts-book-fill", + "contacts-book-line", + "contacts-book-upload-fill", + "contacts-book-upload-line", + "contacts-fill", + "contacts-line", + "contract-left-fill", + "contract-left-line", + "contract-left-right-fill", + "contract-left-right-line", + "contract-right-fill", + "contract-right-line", + "contract-up-down-fill", + "contract-up-down-line", + "contrast-2-fill", + "contrast-2-line", + "contrast-drop-2-fill", + "contrast-drop-2-line", + "contrast-drop-fill", + "contrast-drop-line", + "contrast-fill", + "contrast-line", + "copilot-fill", + "copilot-line", + "copper-coin-fill", + "copper-coin-line", + "copper-diamond-fill", + "copper-diamond-line", + "copyleft-fill", + "copyleft-line", + "copyright-fill", + "copyright-line", + "coreos-fill", + "coreos-line", + "corner-down-left-fill", + "corner-down-left-line", + "corner-down-right-fill", + "corner-down-right-line", + "corner-left-down-fill", + "corner-left-down-line", + "corner-left-up-fill", + "corner-left-up-line", + "corner-right-down-fill", + "corner-right-down-line", + "corner-right-up-fill", + "corner-right-up-line", + "corner-up-left-double-fill", + "corner-up-left-double-line", + "corner-up-left-fill", + "corner-up-left-line", + "corner-up-right-double-fill", + "corner-up-right-double-line", + "corner-up-right-fill", + "corner-up-right-line", + "coupon-2-fill", + "coupon-2-line", + "coupon-3-fill", + "coupon-3-line", + "coupon-4-fill", + "coupon-4-line", + "coupon-5-fill", + "coupon-5-line", + "coupon-fill", + "coupon-line", + "cpu-fill", + "cpu-line", + "creative-commons-by-fill", + "creative-commons-by-line", + "creative-commons-fill", + "creative-commons-line", + "creative-commons-nc-fill", + "creative-commons-nc-line", + "creative-commons-nd-fill", + "creative-commons-nd-line", + "creative-commons-sa-fill", + "creative-commons-sa-line", + "creative-commons-zero-fill", + "creative-commons-zero-line", + "criminal-fill", + "criminal-line", + "crop-2-fill", + "crop-2-line", + "crop-fill", + "crop-line", + "cross-fill", + "cross-line", + "crosshair-2-fill", + "crosshair-2-line", + "crosshair-fill", + "crosshair-line", + "css3-fill", + "css3-line", + "cup-fill", + "cup-line", + "currency-fill", + "currency-line", + "cursor-fill", + "cursor-line", + "customer-service-2-fill", + "customer-service-2-line", + "customer-service-fill", + "customer-service-line", + "dashboard-2-fill", + "dashboard-2-line", + "dashboard-3-fill", + "dashboard-3-line", + "dashboard-fill", + "dashboard-line", + "database-2-fill", + "database-2-line", + "database-fill", + "database-line", + "delete-back-2-fill", + "delete-back-2-line", + "delete-back-fill", + "delete-back-line", + "delete-bin-2-fill", + "delete-bin-2-line", + "delete-bin-3-fill", + "delete-bin-3-line", + "delete-bin-4-fill", + "delete-bin-4-line", + "delete-bin-5-fill", + "delete-bin-5-line", + "delete-bin-6-fill", + "delete-bin-6-line", + "delete-bin-7-fill", + "delete-bin-7-line", + "delete-bin-fill", + "delete-bin-line", + "delete-column", + "delete-row", + "device-fill", + "device-line", + "device-recover-fill", + "device-recover-line", + "dingding-fill", + "dingding-line", + "direction-fill", + "direction-line", + "disc-fill", + "disc-line", + "discord-fill", + "discord-line", + "discuss-fill", + "discuss-line", + "dislike-fill", + "dislike-line", + "disqus-fill", + "disqus-line", + "divide-fill", + "divide-line", + "donut-chart-fill", + "donut-chart-line", + "door-closed-fill", + "door-closed-line", + "door-fill", + "door-line", + "door-lock-box-fill", + "door-lock-box-line", + "door-lock-fill", + "door-lock-line", + "door-open-fill", + "door-open-line", + "dossier-fill", + "dossier-line", + "douban-fill", + "douban-line", + "double-quotes-l", + "double-quotes-r", + "download-2-fill", + "download-2-line", + "download-cloud-2-fill", + "download-cloud-2-line", + "download-cloud-fill", + "download-cloud-line", + "download-fill", + "download-line", + "draft-fill", + "draft-line", + "drag-drop-fill", + "drag-drop-line", + "drag-move-2-fill", + "drag-move-2-line", + "drag-move-fill", + "drag-move-line", + "draggable", + "dribbble-fill", + "dribbble-line", + "drive-fill", + "drive-line", + "drizzle-fill", + "drizzle-line", + "drop-fill", + "drop-line", + "dropbox-fill", + "dropbox-line", + "dropdown-list", + "dual-sim-1-fill", + "dual-sim-1-line", + "dual-sim-2-fill", + "dual-sim-2-line", + "dv-fill", + "dv-line", + "dvd-fill", + "dvd-line", + "e-bike-2-fill", + "e-bike-2-line", + "e-bike-fill", + "e-bike-line", + "earth-fill", + "earth-line", + "earthquake-fill", + "earthquake-line", + "edge-fill", + "edge-line", + "edge-new-fill", + "edge-new-line", + "edit-2-fill", + "edit-2-line", + "edit-box-fill", + "edit-box-line", + "edit-circle-fill", + "edit-circle-line", + "edit-fill", + "edit-line", + "eject-fill", + "eject-line", + "emoji-sticker-fill", + "emoji-sticker-line", + "emotion-2-fill", + "emotion-2-line", + "emotion-fill", + "emotion-happy-fill", + "emotion-happy-line", + "emotion-laugh-fill", + "emotion-laugh-line", + "emotion-line", + "emotion-normal-fill", + "emotion-normal-line", + "emotion-sad-fill", + "emotion-sad-line", + "emotion-unhappy-fill", + "emotion-unhappy-line", + "empathize-fill", + "empathize-line", + "emphasis", + "emphasis-cn", + "english-input", + "equal-fill", + "equal-line", + "equalizer-fill", + "equalizer-line", + "eraser-fill", + "eraser-line", + "error-warning-fill", + "error-warning-line", + "evernote-fill", + "evernote-line", + "exchange-box-fill", + "exchange-box-line", + "exchange-cny-fill", + "exchange-cny-line", + "exchange-dollar-fill", + "exchange-dollar-line", + "exchange-fill", + "exchange-funds-fill", + "exchange-funds-line", + "exchange-line", + "expand-left-fill", + "expand-left-line", + "expand-left-right-fill", + "expand-left-right-line", + "expand-right-fill", + "expand-right-line", + "expand-up-down-fill", + "expand-up-down-line", + "external-link-fill", + "external-link-line", + "eye-2-fill", + "eye-2-line", + "eye-close-fill", + "eye-close-line", + "eye-fill", + "eye-line", + "eye-off-fill", + "eye-off-line", + "facebook-box-fill", + "facebook-box-line", + "facebook-circle-fill", + "facebook-circle-line", + "facebook-fill", + "facebook-line", + "fahrenheit-fill", + "fahrenheit-line", + "feedback-fill", + "feedback-line", + "file-2-fill", + "file-2-line", + "file-3-fill", + "file-3-line", + "file-4-fill", + "file-4-line", + "file-add-fill", + "file-add-line", + "file-chart-2-fill", + "file-chart-2-line", + "file-chart-fill", + "file-chart-line", + "file-close-fill", + "file-close-line", + "file-cloud-fill", + "file-cloud-line", + "file-code-fill", + "file-code-line", + "file-copy-2-fill", + "file-copy-2-line", + "file-copy-fill", + "file-copy-line", + "file-damage-fill", + "file-damage-line", + "file-download-fill", + "file-download-line", + "file-edit-fill", + "file-edit-line", + "file-excel-2-fill", + "file-excel-2-line", + "file-excel-fill", + "file-excel-line", + "file-fill", + "file-forbid-fill", + "file-forbid-line", + "file-gif-fill", + "file-gif-line", + "file-history-fill", + "file-history-line", + "file-hwp-fill", + "file-hwp-line", + "file-image-fill", + "file-image-line", + "file-info-fill", + "file-info-line", + "file-line", + "file-list-2-fill", + "file-list-2-line", + "file-list-3-fill", + "file-list-3-line", + "file-list-fill", + "file-list-line", + "file-lock-fill", + "file-lock-line", + "file-mark-fill", + "file-mark-line", + "file-music-fill", + "file-music-line", + "file-paper-2-fill", + "file-paper-2-line", + "file-paper-fill", + "file-paper-line", + "file-pdf-2-fill", + "file-pdf-2-line", + "file-pdf-fill", + "file-pdf-line", + "file-ppt-2-fill", + "file-ppt-2-line", + "file-ppt-fill", + "file-ppt-line", + "file-reduce-fill", + "file-reduce-line", + "file-search-fill", + "file-search-line", + "file-settings-fill", + "file-settings-line", + "file-shield-2-fill", + "file-shield-2-line", + "file-shield-fill", + "file-shield-line", + "file-shred-fill", + "file-shred-line", + "file-text-fill", + "file-text-line", + "file-transfer-fill", + "file-transfer-line", + "file-unknow-fill", + "file-unknow-line", + "file-upload-fill", + "file-upload-line", + "file-user-fill", + "file-user-line", + "file-video-fill", + "file-video-line", + "file-warning-fill", + "file-warning-line", + "file-word-2-fill", + "file-word-2-line", + "file-word-fill", + "file-word-line", + "file-zip-fill", + "file-zip-line", + "film-fill", + "film-line", + "filter-2-fill", + "filter-2-line", + "filter-3-fill", + "filter-3-line", + "filter-fill", + "filter-line", + "filter-off-fill", + "filter-off-line", + "find-replace-fill", + "find-replace-line", + "finder-fill", + "finder-line", + "fingerprint-2-fill", + "fingerprint-2-line", + "fingerprint-fill", + "fingerprint-line", + "fire-fill", + "fire-line", + "firefox-fill", + "firefox-line", + "first-aid-kit-fill", + "first-aid-kit-line", + "flag-2-fill", + "flag-2-line", + "flag-fill", + "flag-line", + "flashlight-fill", + "flashlight-line", + "flask-fill", + "flask-line", + "flickr-fill", + "flickr-line", + "flight-land-fill", + "flight-land-line", + "flight-takeoff-fill", + "flight-takeoff-line", + "flood-fill", + "flood-line", + "flow-chart", + "flutter-fill", + "flutter-line", + "focus-2-fill", + "focus-2-line", + "focus-3-fill", + "focus-3-line", + "focus-fill", + "focus-line", + "foggy-fill", + "foggy-line", + "folder-2-fill", + "folder-2-line", + "folder-3-fill", + "folder-3-line", + "folder-4-fill", + "folder-4-line", + "folder-5-fill", + "folder-5-line", + "folder-add-fill", + "folder-add-line", + "folder-chart-2-fill", + "folder-chart-2-line", + "folder-chart-fill", + "folder-chart-line", + "folder-download-fill", + "folder-download-line", + "folder-fill", + "folder-forbid-fill", + "folder-forbid-line", + "folder-history-fill", + "folder-history-line", + "folder-image-fill", + "folder-image-line", + "folder-info-fill", + "folder-info-line", + "folder-keyhole-fill", + "folder-keyhole-line", + "folder-line", + "folder-lock-fill", + "folder-lock-line", + "folder-music-fill", + "folder-music-line", + "folder-open-fill", + "folder-open-line", + "folder-received-fill", + "folder-received-line", + "folder-reduce-fill", + "folder-reduce-line", + "folder-settings-fill", + "folder-settings-line", + "folder-shared-fill", + "folder-shared-line", + "folder-shield-2-fill", + "folder-shield-2-line", + "folder-shield-fill", + "folder-shield-line", + "folder-transfer-fill", + "folder-transfer-line", + "folder-unknow-fill", + "folder-unknow-line", + "folder-upload-fill", + "folder-upload-line", + "folder-user-fill", + "folder-user-line", + "folder-video-fill", + "folder-video-line", + "folder-warning-fill", + "folder-warning-line", + "folder-zip-fill", + "folder-zip-line", + "folders-fill", + "folders-line", + "font-color", + "font-family", + "font-mono", + "font-sans", + "font-sans-serif", + "font-size", + "font-size-2", + "football-fill", + "football-line", + "footprint-fill", + "footprint-line", + "forbid-2-fill", + "forbid-2-line", + "forbid-fill", + "forbid-line", + "format-clear", + "forward-10-fill", + "forward-10-line", + "forward-15-fill", + "forward-15-line", + "forward-30-fill", + "forward-30-line", + "forward-5-fill", + "forward-5-line", + "fridge-fill", + "fridge-line", + "fullscreen-exit-fill", + "fullscreen-exit-line", + "fullscreen-fill", + "fullscreen-line", + "function-fill", + "function-line", + "functions", + "funds-box-fill", + "funds-box-line", + "funds-fill", + "funds-line", + "gallery-fill", + "gallery-line", + "gallery-upload-fill", + "gallery-upload-line", + "game-fill", + "game-line", + "gamepad-fill", + "gamepad-line", + "gas-station-fill", + "gas-station-line", + "gatsby-fill", + "gatsby-line", + "genderless-fill", + "genderless-line", + "ghost-2-fill", + "ghost-2-line", + "ghost-fill", + "ghost-line", + "ghost-smile-fill", + "ghost-smile-line", + "gift-2-fill", + "gift-2-line", + "gift-fill", + "gift-line", + "git-branch-fill", + "git-branch-line", + "git-close-pull-request-fill", + "git-close-pull-request-line", + "git-commit-fill", + "git-commit-line", + "git-merge-fill", + "git-merge-line", + "git-pull-request-fill", + "git-pull-request-line", + "git-repository-commits-fill", + "git-repository-commits-line", + "git-repository-fill", + "git-repository-line", + "git-repository-private-fill", + "git-repository-private-line", + "github-fill", + "github-line", + "gitlab-fill", + "gitlab-line", + "global-fill", + "global-line", + "globe-fill", + "globe-line", + "goblet-fill", + "goblet-line", + "google-fill", + "google-line", + "google-play-fill", + "google-play-line", + "government-fill", + "government-line", + "gps-fill", + "gps-line", + "gradienter-fill", + "gradienter-line", + "graduation-cap-fill", + "graduation-cap-line", + "grid-fill", + "grid-line", + "group-2-fill", + "group-2-line", + "group-fill", + "group-line", + "guide-fill", + "guide-line", + "h-1", + "h-2", + "h-3", + "h-4", + "h-5", + "h-6", + "hail-fill", + "hail-line", + "hammer-fill", + "hammer-line", + "hand-coin-fill", + "hand-coin-line", + "hand-heart-fill", + "hand-heart-line", + "hand-sanitizer-fill", + "hand-sanitizer-line", + "handbag-fill", + "handbag-line", + "hard-drive-2-fill", + "hard-drive-2-line", + "hard-drive-3-fill", + "hard-drive-3-line", + "hard-drive-fill", + "hard-drive-line", + "hashtag", + "haze-2-fill", + "haze-2-line", + "haze-fill", + "haze-line", + "hd-fill", + "hd-line", + "heading", + "headphone-fill", + "headphone-line", + "health-book-fill", + "health-book-line", + "heart-2-fill", + "heart-2-line", + "heart-3-fill", + "heart-3-line", + "heart-add-fill", + "heart-add-line", + "heart-fill", + "heart-line", + "heart-pulse-fill", + "heart-pulse-line", + "hearts-fill", + "hearts-line", + "heavy-showers-fill", + "heavy-showers-line", + "hexagon-fill", + "hexagon-line", + "history-fill", + "history-line", + "home-2-fill", + "home-2-line", + "home-3-fill", + "home-3-line", + "home-4-fill", + "home-4-line", + "home-5-fill", + "home-5-line", + "home-6-fill", + "home-6-line", + "home-7-fill", + "home-7-line", + "home-8-fill", + "home-8-line", + "home-fill", + "home-gear-fill", + "home-gear-line", + "home-heart-fill", + "home-heart-line", + "home-line", + "home-office-fill", + "home-office-line", + "home-smile-2-fill", + "home-smile-2-line", + "home-smile-fill", + "home-smile-line", + "home-wifi-fill", + "home-wifi-line", + "honor-of-kings-fill", + "honor-of-kings-line", + "honour-fill", + "honour-line", + "hospital-fill", + "hospital-line", + "hotel-bed-fill", + "hotel-bed-line", + "hotel-fill", + "hotel-line", + "hotspot-fill", + "hotspot-line", + "hourglass-2-fill", + "hourglass-2-line", + "hourglass-fill", + "hourglass-line", + "hq-fill", + "hq-line", + "html5-fill", + "html5-line", + "ie-fill", + "ie-line", + "image-2-fill", + "image-2-line", + "image-add-fill", + "image-add-line", + "image-edit-fill", + "image-edit-line", + "image-fill", + "image-line", + "inbox-2-fill", + "inbox-2-line", + "inbox-archive-fill", + "inbox-archive-line", + "inbox-fill", + "inbox-line", + "inbox-unarchive-fill", + "inbox-unarchive-line", + "increase-decrease-fill", + "increase-decrease-line", + "indent-decrease", + "indent-increase", + "indeterminate-circle-fill", + "indeterminate-circle-line", + "infinity-fill", + "infinity-line", + "information-fill", + "information-line", + "infrared-thermometer-fill", + "infrared-thermometer-line", + "ink-bottle-fill", + "ink-bottle-line", + "input-cursor-move", + "input-method-fill", + "input-method-line", + "insert-column-left", + "insert-column-right", + "insert-row-bottom", + "insert-row-top", + "instagram-fill", + "instagram-line", + "install-fill", + "install-line", + "instance-fill", + "instance-line", + "invision-fill", + "invision-line", + "italic", + "javascript-fill", + "javascript-line", + "kakao-talk-fill", + "kakao-talk-line", + "key-2-fill", + "key-2-line", + "key-fill", + "key-line", + "keyboard-box-fill", + "keyboard-box-line", + "keyboard-fill", + "keyboard-line", + "keynote-fill", + "keynote-line", + "kick-fill", + "kick-line", + "knife-blood-fill", + "knife-blood-line", + "knife-fill", + "knife-line", + "landscape-fill", + "landscape-line", + "layout-2-fill", + "layout-2-line", + "layout-3-fill", + "layout-3-line", + "layout-4-fill", + "layout-4-line", + "layout-5-fill", + "layout-5-line", + "layout-6-fill", + "layout-6-line", + "layout-bottom-2-fill", + "layout-bottom-2-line", + "layout-bottom-fill", + "layout-bottom-line", + "layout-column-fill", + "layout-column-line", + "layout-fill", + "layout-grid-fill", + "layout-grid-line", + "layout-left-2-fill", + "layout-left-2-line", + "layout-left-fill", + "layout-left-line", + "layout-line", + "layout-masonry-fill", + "layout-masonry-line", + "layout-right-2-fill", + "layout-right-2-line", + "layout-right-fill", + "layout-right-line", + "layout-row-fill", + "layout-row-line", + "layout-top-2-fill", + "layout-top-2-line", + "layout-top-fill", + "layout-top-line", + "leaf-fill", + "leaf-line", + "lifebuoy-fill", + "lifebuoy-line", + "lightbulb-fill", + "lightbulb-flash-fill", + "lightbulb-flash-line", + "lightbulb-line", + "line-chart-fill", + "line-chart-line", + "line-fill", + "line-height", + "line-line", + "link", + "link-m", + "link-unlink", + "link-unlink-m", + "linkedin-box-fill", + "linkedin-box-line", + "linkedin-fill", + "linkedin-line", + "links-fill", + "links-line", + "list-check", + "list-check-2", + "list-check-3", + "list-indefinite", + "list-ordered", + "list-ordered-2", + "list-radio", + "list-settings-fill", + "list-settings-line", + "list-unordered", + "live-fill", + "live-line", + "loader-2-fill", + "loader-2-line", + "loader-3-fill", + "loader-3-line", + "loader-4-fill", + "loader-4-line", + "loader-5-fill", + "loader-5-line", + "loader-fill", + "loader-line", + "lock-2-fill", + "lock-2-line", + "lock-fill", + "lock-line", + "lock-password-fill", + "lock-password-line", + "lock-unlock-fill", + "lock-unlock-line", + "login-box-fill", + "login-box-line", + "login-circle-fill", + "login-circle-line", + "logout-box-fill", + "logout-box-line", + "logout-box-r-fill", + "logout-box-r-line", + "logout-circle-fill", + "logout-circle-line", + "logout-circle-r-fill", + "logout-circle-r-line", + "loop-left-fill", + "loop-left-line", + "loop-right-fill", + "loop-right-line", + "luggage-cart-fill", + "luggage-cart-line", + "luggage-deposit-fill", + "luggage-deposit-line", + "lungs-fill", + "lungs-line", + "mac-fill", + "mac-line", + "macbook-fill", + "macbook-line", + "magic-fill", + "magic-line", + "mail-add-fill", + "mail-add-line", + "mail-check-fill", + "mail-check-line", + "mail-close-fill", + "mail-close-line", + "mail-download-fill", + "mail-download-line", + "mail-fill", + "mail-forbid-fill", + "mail-forbid-line", + "mail-line", + "mail-lock-fill", + "mail-lock-line", + "mail-open-fill", + "mail-open-line", + "mail-send-fill", + "mail-send-line", + "mail-settings-fill", + "mail-settings-line", + "mail-star-fill", + "mail-star-line", + "mail-unread-fill", + "mail-unread-line", + "mail-volume-fill", + "mail-volume-line", + "map-2-fill", + "map-2-line", + "map-fill", + "map-line", + "map-pin-2-fill", + "map-pin-2-line", + "map-pin-3-fill", + "map-pin-3-line", + "map-pin-4-fill", + "map-pin-4-line", + "map-pin-5-fill", + "map-pin-5-line", + "map-pin-add-fill", + "map-pin-add-line", + "map-pin-fill", + "map-pin-line", + "map-pin-range-fill", + "map-pin-range-line", + "map-pin-time-fill", + "map-pin-time-line", + "map-pin-user-fill", + "map-pin-user-line", + "mark-pen-fill", + "mark-pen-line", + "markdown-fill", + "markdown-line", + "markup-fill", + "markup-line", + "mastercard-fill", + "mastercard-line", + "mastodon-fill", + "mastodon-line", + "medal-2-fill", + "medal-2-line", + "medal-fill", + "medal-line", + "medicine-bottle-fill", + "medicine-bottle-line", + "medium-fill", + "medium-line", + "megaphone-fill", + "megaphone-line", + "memories-fill", + "memories-line", + "men-fill", + "men-line", + "mental-health-fill", + "mental-health-line", + "menu-2-fill", + "menu-2-line", + "menu-3-fill", + "menu-3-line", + "menu-4-fill", + "menu-4-line", + "menu-5-fill", + "menu-5-line", + "menu-add-fill", + "menu-add-line", + "menu-fill", + "menu-fold-fill", + "menu-fold-line", + "menu-line", + "menu-search-fill", + "menu-search-line", + "menu-unfold-fill", + "menu-unfold-line", + "merge-cells-horizontal", + "merge-cells-vertical", + "message-2-fill", + "message-2-line", + "message-3-fill", + "message-3-line", + "message-fill", + "message-line", + "messenger-fill", + "messenger-line", + "meta-fill", + "meta-line", + "meteor-fill", + "meteor-line", + "mic-2-fill", + "mic-2-line", + "mic-fill", + "mic-line", + "mic-off-fill", + "mic-off-line", + "mickey-fill", + "mickey-line", + "microscope-fill", + "microscope-line", + "microsoft-fill", + "microsoft-line", + "microsoft-loop-fill", + "microsoft-loop-line", + "mind-map", + "mini-program-fill", + "mini-program-line", + "mist-fill", + "mist-line", + "money-cny-box-fill", + "money-cny-box-line", + "money-cny-circle-fill", + "money-cny-circle-line", + "money-dollar-box-fill", + "money-dollar-box-line", + "money-dollar-circle-fill", + "money-dollar-circle-line", + "money-euro-box-fill", + "money-euro-box-line", + "money-euro-circle-fill", + "money-euro-circle-line", + "money-pound-box-fill", + "money-pound-box-line", + "money-pound-circle-fill", + "money-pound-circle-line", + "moon-clear-fill", + "moon-clear-line", + "moon-cloudy-fill", + "moon-cloudy-line", + "moon-fill", + "moon-foggy-fill", + "moon-foggy-line", + "moon-line", + "more-2-fill", + "more-2-line", + "more-fill", + "more-line", + "motorbike-fill", + "motorbike-line", + "mouse-fill", + "mouse-line", + "movie-2-fill", + "movie-2-line", + "movie-fill", + "movie-line", + "music-2-fill", + "music-2-line", + "music-fill", + "music-line", + "mv-fill", + "mv-line", + "navigation-fill", + "navigation-line", + "netease-cloud-music-fill", + "netease-cloud-music-line", + "netflix-fill", + "netflix-line", + "newspaper-fill", + "newspaper-line", + "nft-fill", + "nft-line", + "node-tree", + "notification-2-fill", + "notification-2-line", + "notification-3-fill", + "notification-3-line", + "notification-4-fill", + "notification-4-line", + "notification-badge-fill", + "notification-badge-line", + "notification-fill", + "notification-line", + "notification-off-fill", + "notification-off-line", + "notion-fill", + "notion-line", + "npmjs-fill", + "npmjs-line", + "number-0", + "number-1", + "number-2", + "number-3", + "number-4", + "number-5", + "number-6", + "number-7", + "number-8", + "number-9", + "numbers-fill", + "numbers-line", + "nurse-fill", + "nurse-line", + "octagon-fill", + "octagon-line", + "oil-fill", + "oil-line", + "omega", + "open-arm-fill", + "open-arm-line", + "open-source-fill", + "open-source-line", + "openai-fill", + "openai-line", + "openbase-fill", + "openbase-line", + "opera-fill", + "opera-line", + "order-play-fill", + "order-play-line", + "organization-chart", + "outlet-2-fill", + "outlet-2-line", + "outlet-fill", + "outlet-line", + "overline", + "p2p-fill", + "p2p-line", + "page-separator", + "pages-fill", + "pages-line", + "paint-brush-fill", + "paint-brush-line", + "paint-fill", + "paint-line", + "palette-fill", + "palette-line", + "pantone-fill", + "pantone-line", + "paragraph", + "parent-fill", + "parent-line", + "parentheses-fill", + "parentheses-line", + "parking-box-fill", + "parking-box-line", + "parking-fill", + "parking-line", + "pass-expired-fill", + "pass-expired-line", + "pass-pending-fill", + "pass-pending-line", + "pass-valid-fill", + "pass-valid-line", + "passport-fill", + "passport-line", + "patreon-fill", + "patreon-line", + "pause-circle-fill", + "pause-circle-line", + "pause-fill", + "pause-line", + "pause-mini-fill", + "pause-mini-line", + "paypal-fill", + "paypal-line", + "pen-nib-fill", + "pen-nib-line", + "pencil-fill", + "pencil-line", + "pencil-ruler-2-fill", + "pencil-ruler-2-line", + "pencil-ruler-fill", + "pencil-ruler-line", + "pentagon-fill", + "pentagon-line", + "percent-fill", + "percent-line", + "phone-camera-fill", + "phone-camera-line", + "phone-fill", + "phone-find-fill", + "phone-find-line", + "phone-line", + "phone-lock-fill", + "phone-lock-line", + "picture-in-picture-2-fill", + "picture-in-picture-2-line", + "picture-in-picture-exit-fill", + "picture-in-picture-exit-line", + "picture-in-picture-fill", + "picture-in-picture-line", + "pie-chart-2-fill", + "pie-chart-2-line", + "pie-chart-box-fill", + "pie-chart-box-line", + "pie-chart-fill", + "pie-chart-line", + "pin-distance-fill", + "pin-distance-line", + "ping-pong-fill", + "ping-pong-line", + "pinterest-fill", + "pinterest-line", + "pinyin-input", + "pixelfed-fill", + "pixelfed-line", + "plane-fill", + "plane-line", + "planet-fill", + "planet-line", + "plant-fill", + "plant-line", + "play-circle-fill", + "play-circle-line", + "play-fill", + "play-line", + "play-list-2-fill", + "play-list-2-line", + "play-list-add-fill", + "play-list-add-line", + "play-list-fill", + "play-list-line", + "play-mini-fill", + "play-mini-line", + "playstation-fill", + "playstation-line", + "plug-2-fill", + "plug-2-line", + "plug-fill", + "plug-line", + "polaroid-2-fill", + "polaroid-2-line", + "polaroid-fill", + "polaroid-line", + "police-car-fill", + "police-car-line", + "presentation-fill", + "presentation-line", + "price-tag-2-fill", + "price-tag-2-line", + "price-tag-3-fill", + "price-tag-3-line", + "price-tag-fill", + "price-tag-line", + "printer-cloud-fill", + "printer-cloud-line", + "printer-fill", + "printer-line", + "product-hunt-fill", + "product-hunt-line", + "profile-fill", + "profile-line", + "prohibited-fill", + "prohibited-line", + "projector-2-fill", + "projector-2-line", + "projector-fill", + "projector-line", + "psychotherapy-fill", + "psychotherapy-line", + "pulse-fill", + "pulse-line", + "pushpin-2-fill", + "pushpin-2-line", + "pushpin-fill", + "pushpin-line", + "qq-fill", + "qq-line", + "qr-code-fill", + "qr-code-line", + "qr-scan-2-fill", + "qr-scan-2-line", + "qr-scan-fill", + "qr-scan-line", + "question-answer-fill", + "question-answer-line", + "question-fill", + "question-line", + "question-mark", + "questionnaire-fill", + "questionnaire-line", + "quill-pen-fill", + "quill-pen-line", + "quote-text", + "radar-fill", + "radar-line", + "radio-2-fill", + "radio-2-line", + "radio-button-fill", + "radio-button-line", + "radio-fill", + "radio-line", + "rainbow-fill", + "rainbow-line", + "rainy-fill", + "rainy-line", + "reactjs-fill", + "reactjs-line", + "record-circle-fill", + "record-circle-line", + "record-mail-fill", + "record-mail-line", + "rectangle-fill", + "rectangle-line", + "recycle-fill", + "recycle-line", + "red-packet-fill", + "red-packet-line", + "reddit-fill", + "reddit-line", + "refresh-fill", + "refresh-line", + "refund-2-fill", + "refund-2-line", + "refund-fill", + "refund-line", + "registered-fill", + "registered-line", + "remixicon-fill", + "remixicon-line", + "remote-control-2-fill", + "remote-control-2-line", + "remote-control-fill", + "remote-control-line", + "repeat-2-fill", + "repeat-2-line", + "repeat-fill", + "repeat-line", + "repeat-one-fill", + "repeat-one-line", + "replay-10-fill", + "replay-10-line", + "replay-15-fill", + "replay-15-line", + "replay-30-fill", + "replay-30-line", + "replay-5-fill", + "replay-5-line", + "reply-all-fill", + "reply-all-line", + "reply-fill", + "reply-line", + "reserved-fill", + "reserved-line", + "rest-time-fill", + "rest-time-line", + "restart-fill", + "restart-line", + "restaurant-2-fill", + "restaurant-2-line", + "restaurant-fill", + "restaurant-line", + "rewind-fill", + "rewind-line", + "rewind-mini-fill", + "rewind-mini-line", + "rfid-fill", + "rfid-line", + "rhythm-fill", + "rhythm-line", + "riding-fill", + "riding-line", + "road-map-fill", + "road-map-line", + "roadster-fill", + "roadster-line", + "robot-2-fill", + "robot-2-line", + "robot-fill", + "robot-line", + "rocket-2-fill", + "rocket-2-line", + "rocket-fill", + "rocket-line", + "rotate-lock-fill", + "rotate-lock-line", + "rounded-corner", + "route-fill", + "route-line", + "router-fill", + "router-line", + "rss-fill", + "rss-line", + "ruler-2-fill", + "ruler-2-line", + "ruler-fill", + "ruler-line", + "run-fill", + "run-line", + "safari-fill", + "safari-line", + "safe-2-fill", + "safe-2-line", + "safe-fill", + "safe-line", + "sailboat-fill", + "sailboat-line", + "save-2-fill", + "save-2-line", + "save-3-fill", + "save-3-line", + "save-fill", + "save-line", + "scales-2-fill", + "scales-2-line", + "scales-3-fill", + "scales-3-line", + "scales-fill", + "scales-line", + "scan-2-fill", + "scan-2-line", + "scan-fill", + "scan-line", + "school-fill", + "school-line", + "scissors-2-fill", + "scissors-2-line", + "scissors-cut-fill", + "scissors-cut-line", + "scissors-fill", + "scissors-line", + "screenshot-2-fill", + "screenshot-2-line", + "screenshot-fill", + "screenshot-line", + "sd-card-fill", + "sd-card-line", + "sd-card-mini-fill", + "sd-card-mini-line", + "search-2-fill", + "search-2-line", + "search-eye-fill", + "search-eye-line", + "search-fill", + "search-line", + "secure-payment-fill", + "secure-payment-line", + "seedling-fill", + "seedling-line", + "send-backward", + "send-plane-2-fill", + "send-plane-2-line", + "send-plane-fill", + "send-plane-line", + "send-to-back", + "sensor-fill", + "sensor-line", + "seo-fill", + "seo-line", + "separator", + "server-fill", + "server-line", + "service-fill", + "service-line", + "settings-2-fill", + "settings-2-line", + "settings-3-fill", + "settings-3-line", + "settings-4-fill", + "settings-4-line", + "settings-5-fill", + "settings-5-line", + "settings-6-fill", + "settings-6-line", + "settings-fill", + "settings-line", + "shake-hands-fill", + "shake-hands-line", + "shape-2-fill", + "shape-2-line", + "shape-fill", + "shape-line", + "shapes-fill", + "shapes-line", + "share-box-fill", + "share-box-line", + "share-circle-fill", + "share-circle-line", + "share-fill", + "share-forward-2-fill", + "share-forward-2-line", + "share-forward-box-fill", + "share-forward-box-line", + "share-forward-fill", + "share-forward-line", + "share-line", + "shield-check-fill", + "shield-check-line", + "shield-cross-fill", + "shield-cross-line", + "shield-fill", + "shield-flash-fill", + "shield-flash-line", + "shield-keyhole-fill", + "shield-keyhole-line", + "shield-line", + "shield-star-fill", + "shield-star-line", + "shield-user-fill", + "shield-user-line", + "shining-2-fill", + "shining-2-line", + "shining-fill", + "shining-line", + "ship-2-fill", + "ship-2-line", + "ship-fill", + "ship-line", + "shirt-fill", + "shirt-line", + "shopping-bag-2-fill", + "shopping-bag-2-line", + "shopping-bag-3-fill", + "shopping-bag-3-line", + "shopping-bag-fill", + "shopping-bag-line", + "shopping-basket-2-fill", + "shopping-basket-2-line", + "shopping-basket-fill", + "shopping-basket-line", + "shopping-cart-2-fill", + "shopping-cart-2-line", + "shopping-cart-fill", + "shopping-cart-line", + "showers-fill", + "showers-line", + "shuffle-fill", + "shuffle-line", + "shut-down-fill", + "shut-down-line", + "side-bar-fill", + "side-bar-line", + "signal-tower-fill", + "signal-tower-line", + "signal-wifi-1-fill", + "signal-wifi-1-line", + "signal-wifi-2-fill", + "signal-wifi-2-line", + "signal-wifi-3-fill", + "signal-wifi-3-line", + "signal-wifi-error-fill", + "signal-wifi-error-line", + "signal-wifi-fill", + "signal-wifi-line", + "signal-wifi-off-fill", + "signal-wifi-off-line", + "sim-card-2-fill", + "sim-card-2-line", + "sim-card-fill", + "sim-card-line", + "single-quotes-l", + "single-quotes-r", + "sip-fill", + "sip-line", + "sketching", + "skip-back-fill", + "skip-back-line", + "skip-back-mini-fill", + "skip-back-mini-line", + "skip-down-fill", + "skip-down-line", + "skip-forward-fill", + "skip-forward-line", + "skip-forward-mini-fill", + "skip-forward-mini-line", + "skip-left-fill", + "skip-left-line", + "skip-right-fill", + "skip-right-line", + "skip-up-fill", + "skip-up-line", + "skull-2-fill", + "skull-2-line", + "skull-fill", + "skull-line", + "skype-fill", + "skype-line", + "slack-fill", + "slack-line", + "slash-commands", + "slash-commands-2", + "slice-fill", + "slice-line", + "slideshow-2-fill", + "slideshow-2-line", + "slideshow-3-fill", + "slideshow-3-line", + "slideshow-4-fill", + "slideshow-4-line", + "slideshow-fill", + "slideshow-line", + "slow-down-fill", + "slow-down-line", + "smartphone-fill", + "smartphone-line", + "snapchat-fill", + "snapchat-line", + "snowy-fill", + "snowy-line", + "sort-asc", + "sort-desc", + "sound-module-fill", + "sound-module-line", + "soundcloud-fill", + "soundcloud-line", + "space", + "space-ship-fill", + "space-ship-line", + "spam-2-fill", + "spam-2-line", + "spam-3-fill", + "spam-3-line", + "spam-fill", + "spam-line", + "sparkling-2-fill", + "sparkling-2-line", + "sparkling-fill", + "sparkling-line", + "speak-fill", + "speak-line", + "speaker-2-fill", + "speaker-2-line", + "speaker-3-fill", + "speaker-3-line", + "speaker-fill", + "speaker-line", + "spectrum-fill", + "spectrum-line", + "speed-fill", + "speed-line", + "speed-mini-fill", + "speed-mini-line", + "speed-up-fill", + "speed-up-line", + "split-cells-horizontal", + "split-cells-vertical", + "spotify-fill", + "spotify-line", + "spy-fill", + "spy-line", + "square-fill", + "square-line", + "stack-fill", + "stack-line", + "stack-overflow-fill", + "stack-overflow-line", + "stackshare-fill", + "stackshare-line", + "star-fill", + "star-half-fill", + "star-half-line", + "star-half-s-fill", + "star-half-s-line", + "star-line", + "star-s-fill", + "star-s-line", + "star-smile-fill", + "star-smile-line", + "steam-fill", + "steam-line", + "steering-2-fill", + "steering-2-line", + "steering-fill", + "steering-line", + "stethoscope-fill", + "stethoscope-line", + "sticky-note-2-fill", + "sticky-note-2-line", + "sticky-note-fill", + "sticky-note-line", + "stock-fill", + "stock-line", + "stop-circle-fill", + "stop-circle-line", + "stop-fill", + "stop-line", + "stop-mini-fill", + "stop-mini-line", + "store-2-fill", + "store-2-line", + "store-3-fill", + "store-3-line", + "store-fill", + "store-line", + "strikethrough", + "strikethrough-2", + "subscript", + "subscript-2", + "subtract-fill", + "subtract-line", + "subway-fill", + "subway-line", + "subway-wifi-fill", + "subway-wifi-line", + "suitcase-2-fill", + "suitcase-2-line", + "suitcase-3-fill", + "suitcase-3-line", + "suitcase-fill", + "suitcase-line", + "sun-cloudy-fill", + "sun-cloudy-line", + "sun-fill", + "sun-foggy-fill", + "sun-foggy-line", + "sun-line", + "supabase-fill", + "supabase-line", + "superscript", + "superscript-2", + "surgical-mask-fill", + "surgical-mask-line", + "surround-sound-fill", + "surround-sound-line", + "survey-fill", + "survey-line", + "swap-box-fill", + "swap-box-line", + "swap-fill", + "swap-line", + "switch-fill", + "switch-line", + "sword-fill", + "sword-line", + "syringe-fill", + "syringe-line", + "t-box-fill", + "t-box-line", + "t-shirt-2-fill", + "t-shirt-2-line", + "t-shirt-air-fill", + "t-shirt-air-line", + "t-shirt-fill", + "t-shirt-line", + "table-2", + "table-alt-fill", + "table-alt-line", + "table-fill", + "table-line", + "tablet-fill", + "tablet-line", + "takeaway-fill", + "takeaway-line", + "taobao-fill", + "taobao-line", + "tape-fill", + "tape-line", + "task-fill", + "task-line", + "taxi-fill", + "taxi-line", + "taxi-wifi-fill", + "taxi-wifi-line", + "team-fill", + "team-line", + "telegram-fill", + "telegram-line", + "temp-cold-fill", + "temp-cold-line", + "temp-hot-fill", + "temp-hot-line", + "tent-fill", + "tent-line", + "terminal-box-fill", + "terminal-box-line", + "terminal-fill", + "terminal-line", + "terminal-window-fill", + "terminal-window-line", + "test-tube-fill", + "test-tube-line", + "text", + "text-direction-l", + "text-direction-r", + "text-spacing", + "text-wrap", + "thermometer-fill", + "thermometer-line", + "threads-fill", + "threads-line", + "thumb-down-fill", + "thumb-down-line", + "thumb-up-fill", + "thumb-up-line", + "thunderstorms-fill", + "thunderstorms-line", + "ticket-2-fill", + "ticket-2-line", + "ticket-fill", + "ticket-line", + "tiktok-fill", + "tiktok-line", + "time-fill", + "time-line", + "timer-2-fill", + "timer-2-line", + "timer-fill", + "timer-flash-fill", + "timer-flash-line", + "timer-line", + "todo-fill", + "todo-line", + "toggle-fill", + "toggle-line", + "token-swap-fill", + "token-swap-line", + "tools-fill", + "tools-line", + "tornado-fill", + "tornado-line", + "trademark-fill", + "trademark-line", + "traffic-light-fill", + "traffic-light-line", + "train-fill", + "train-line", + "train-wifi-fill", + "train-wifi-line", + "translate", + "translate-2", + "travesti-fill", + "travesti-line", + "treasure-map-fill", + "treasure-map-line", + "tree-fill", + "tree-line", + "trello-fill", + "trello-line", + "triangle-fill", + "triangle-line", + "trophy-fill", + "trophy-line", + "truck-fill", + "truck-line", + "tumblr-fill", + "tumblr-line", + "tv-2-fill", + "tv-2-line", + "tv-fill", + "tv-line", + "twitch-fill", + "twitch-line", + "twitter-fill", + "twitter-line", + "twitter-x-fill", + "twitter-x-line", + "typhoon-fill", + "typhoon-line", + "u-disk-fill", + "u-disk-line", + "ubuntu-fill", + "ubuntu-line", + "umbrella-fill", + "umbrella-line", + "underline", + "uninstall-fill", + "uninstall-line", + "unpin-fill", + "unpin-line", + "unsplash-fill", + "unsplash-line", + "upload-2-fill", + "upload-2-line", + "upload-cloud-2-fill", + "upload-cloud-2-line", + "upload-cloud-fill", + "upload-cloud-line", + "upload-fill", + "upload-line", + "usb-fill", + "usb-line", + "user-2-fill", + "user-2-line", + "user-3-fill", + "user-3-line", + "user-4-fill", + "user-4-line", + "user-5-fill", + "user-5-line", + "user-6-fill", + "user-6-line", + "user-add-fill", + "user-add-line", + "user-fill", + "user-follow-fill", + "user-follow-line", + "user-forbid-fill", + "user-forbid-line", + "user-heart-fill", + "user-heart-line", + "user-line", + "user-location-fill", + "user-location-line", + "user-received-2-fill", + "user-received-2-line", + "user-received-fill", + "user-received-line", + "user-search-fill", + "user-search-line", + "user-settings-fill", + "user-settings-line", + "user-shared-2-fill", + "user-shared-2-line", + "user-shared-fill", + "user-shared-line", + "user-smile-fill", + "user-smile-line", + "user-star-fill", + "user-star-line", + "user-unfollow-fill", + "user-unfollow-line", + "user-voice-fill", + "user-voice-line", + "verified-badge-fill", + "verified-badge-line", + "video-add-fill", + "video-add-line", + "video-chat-fill", + "video-chat-line", + "video-download-fill", + "video-download-line", + "video-fill", + "video-line", + "video-upload-fill", + "video-upload-line", + "vidicon-2-fill", + "vidicon-2-line", + "vidicon-fill", + "vidicon-line", + "vimeo-fill", + "vimeo-line", + "vip-crown-2-fill", + "vip-crown-2-line", + "vip-crown-fill", + "vip-crown-line", + "vip-diamond-fill", + "vip-diamond-line", + "vip-fill", + "vip-line", + "virus-fill", + "virus-line", + "visa-fill", + "visa-line", + "voice-recognition-fill", + "voice-recognition-line", + "voiceprint-fill", + "voiceprint-line", + "volume-down-fill", + "volume-down-line", + "volume-mute-fill", + "volume-mute-line", + "volume-off-vibrate-fill", + "volume-off-vibrate-line", + "volume-up-fill", + "volume-up-line", + "volume-vibrate-fill", + "volume-vibrate-line", + "vuejs-fill", + "vuejs-line", + "walk-fill", + "walk-line", + "wallet-2-fill", + "wallet-2-line", + "wallet-3-fill", + "wallet-3-line", + "wallet-fill", + "wallet-line", + "water-flash-fill", + "water-flash-line", + "water-percent-fill", + "water-percent-line", + "webcam-fill", + "webcam-line", + "wechat-2-fill", + "wechat-2-line", + "wechat-channels-fill", + "wechat-channels-line", + "wechat-fill", + "wechat-line", + "wechat-pay-fill", + "wechat-pay-line", + "weibo-fill", + "weibo-line", + "whatsapp-fill", + "whatsapp-line", + "wheelchair-fill", + "wheelchair-line", + "wifi-fill", + "wifi-line", + "wifi-off-fill", + "wifi-off-line", + "window-2-fill", + "window-2-line", + "window-fill", + "window-line", + "windows-fill", + "windows-line", + "windy-fill", + "windy-line", + "wireless-charging-fill", + "wireless-charging-line", + "women-fill", + "women-line", + "wordpress-fill", + "wordpress-line", + "wubi-input", + "xbox-fill", + "xbox-line", + "xing-fill", + "xing-line", + "youtube-fill", + "youtube-line", + "yuque-fill", + "yuque-line", + "zcool-fill", + "zcool-line", + "zhihu-fill", + "zhihu-line", + "zoom-in-fill", + "zoom-in-line", + "zoom-out-fill", + "zoom-out-line", + "zzz-fill", + "zzz-line" + ], + // https://icones.js.org/collections/fa-solid-meta.json + "fa-solid:": [ + "abacus", + "ad", + "address-book", + "address-card", + "adjust", + "air-freshener", + "align-center", + "align-justify", + "align-left", + "align-right", + "allergies", + "ambulance", + "american-sign-language-interpreting", + "anchor", + "angle-double-down", + "angle-double-left", + "angle-double-right", + "angle-double-up", + "angle-down", + "angle-left", + "angle-right", + "angle-up", + "angry", + "ankh", + "apple-alt", + "archive", + "archway", + "arrow-alt-circle-down", + "arrow-alt-circle-left", + "arrow-alt-circle-right", + "arrow-alt-circle-up", + "arrow-circle-down", + "arrow-circle-left", + "arrow-circle-right", + "arrow-circle-up", + "arrow-down", + "arrow-left", + "arrow-right", + "arrow-up", + "arrows-alt", + "arrows-alt-h", + "arrows-alt-v", + "assistive-listening-systems", + "asterisk", + "at", + "atlas", + "atom", + "audio-description", + "award", + "baby", + "baby-carriage", + "backspace", + "backward", + "bacon", + "bacteria", + "bacterium", + "bahai", + "balance-scale", + "balance-scale-left", + "balance-scale-right", + "ban", + "band-aid", + "barcode", + "bars", + "baseball-ball", + "basketball-ball", + "bath", + "battery-empty", + "battery-full", + "battery-half", + "battery-quarter", + "battery-three-quarters", + "bed", + "beer", + "bell", + "bell-slash", + "bezier-curve", + "bible", + "bicycle", + "biking", + "binoculars", + "biohazard", + "birthday-cake", + "blender", + "blender-phone", + "blind", + "blog", + "bold", + "bolt", + "bomb", + "bone", + "bong", + "book", + "book-dead", + "book-medical", + "book-open", + "book-reader", + "bookmark", + "border-all", + "border-none", + "border-style", + "bowling-ball", + "box", + "box-open", + "box-tissue", + "boxes", + "braille", + "brain", + "bread-slice", + "briefcase", + "briefcase-medical", + "broadcast-tower", + "broom", + "brush", + "bug", + "building", + "bullhorn", + "bullseye", + "burn", + "bus", + "bus-alt", + "business-time", + "calculator", + "calculator-alt", + "calendar", + "calendar-alt", + "calendar-check", + "calendar-day", + "calendar-minus", + "calendar-plus", + "calendar-times", + "calendar-week", + "camera", + "camera-retro", + "campground", + "candy-cane", + "cannabis", + "capsules", + "car", + "car-alt", + "car-battery", + "car-crash", + "car-side", + "caravan", + "caret-down", + "caret-left", + "caret-right", + "caret-square-down", + "caret-square-left", + "caret-square-right", + "caret-square-up", + "caret-up", + "carrot", + "cart-arrow-down", + "cart-plus", + "cash-register", + "cat", + "certificate", + "chair", + "chalkboard", + "chalkboard-teacher", + "charging-station", + "chart-area", + "chart-bar", + "chart-line", + "chart-pie", + "check", + "check-circle", + "check-double", + "check-square", + "cheese", + "chess", + "chess-bishop", + "chess-board", + "chess-king", + "chess-knight", + "chess-pawn", + "chess-queen", + "chess-rook", + "chevron-circle-down", + "chevron-circle-left", + "chevron-circle-right", + "chevron-circle-up", + "chevron-down", + "chevron-left", + "chevron-right", + "chevron-up", + "child", + "church", + "circle", + "circle-notch", + "city", + "clinic-medical", + "clipboard", + "clipboard-check", + "clipboard-list", + "clock", + "clone", + "closed-captioning", + "cloud", + "cloud-download-alt", + "cloud-meatball", + "cloud-moon", + "cloud-moon-rain", + "cloud-rain", + "cloud-showers-heavy", + "cloud-sun", + "cloud-sun-rain", + "cloud-upload-alt", + "cocktail", + "code", + "code-branch", + "coffee", + "cog", + "cogs", + "coins", + "columns", + "comment", + "comment-alt", + "comment-dollar", + "comment-dots", + "comment-medical", + "comment-slash", + "comments", + "comments-dollar", + "compact-disc", + "compass", + "compress", + "compress-alt", + "compress-arrows-alt", + "concierge-bell", + "cookie", + "cookie-bite", + "copy", + "copyright", + "couch", + "credit-card", + "crop", + "crop-alt", + "cross", + "crosshairs", + "crow", + "crown", + "crutch", + "cube", + "cubes", + "cut", + "database", + "deaf", + "democrat", + "desktop", + "dharmachakra", + "diagnoses", + "dice", + "dice-d20", + "dice-d6", + "dice-five", + "dice-four", + "dice-one", + "dice-six", + "dice-three", + "dice-two", + "digital-tachograph", + "directions", + "disease", + "divide", + "dizzy", + "dna", + "dog", + "dollar-sign", + "dolly", + "dolly-flatbed", + "donate", + "door-closed", + "door-open", + "dot-circle", + "dove", + "download", + "drafting-compass", + "dragon", + "draw-polygon", + "drum", + "drum-steelpan", + "drumstick-bite", + "dumbbell", + "dumpster", + "dumpster-fire", + "dungeon", + "edit", + "egg", + "eject", + "ellipsis-h", + "ellipsis-v", + "empty-set", + "envelope", + "envelope-open", + "envelope-open-text", + "envelope-square", + "equals", + "eraser", + "ethernet", + "euro-sign", + "exchange-alt", + "exclamation", + "exclamation-circle", + "exclamation-triangle", + "expand", + "expand-alt", + "expand-arrows-alt", + "external-link-alt", + "external-link-square-alt", + "eye", + "eye-dropper", + "eye-slash", + "fan", + "fast-backward", + "fast-forward", + "faucet", + "fax", + "feather", + "feather-alt", + "female", + "fighter-jet", + "file", + "file-alt", + "file-archive", + "file-audio", + "file-code", + "file-contract", + "file-csv", + "file-download", + "file-excel", + "file-export", + "file-image", + "file-import", + "file-invoice", + "file-invoice-dollar", + "file-medical", + "file-medical-alt", + "file-pdf", + "file-powerpoint", + "file-prescription", + "file-signature", + "file-upload", + "file-video", + "file-word", + "fill", + "fill-drip", + "film", + "filter", + "fingerprint", + "fire", + "fire-alt", + "fire-extinguisher", + "first-aid", + "fish", + "fist-raised", + "flag", + "flag-checkered", + "flag-usa", + "flask", + "flushed", + "folder", + "folder-minus", + "folder-open", + "folder-plus", + "font", + "football-ball", + "forward", + "frog", + "frown", + "frown-open", + "function", + "funnel-dollar", + "futbol", + "gamepad", + "gas-pump", + "gavel", + "gem", + "genderless", + "ghost", + "gift", + "gifts", + "glass-cheers", + "glass-martini", + "glass-martini-alt", + "glass-whiskey", + "glasses", + "globe", + "globe-africa", + "globe-americas", + "globe-asia", + "globe-europe", + "golf-ball", + "gopuram", + "graduation-cap", + "greater-than", + "greater-than-equal", + "grimace", + "grin", + "grin-alt", + "grin-beam", + "grin-beam-sweat", + "grin-hearts", + "grin-squint", + "grin-squint-tears", + "grin-stars", + "grin-tears", + "grin-tongue", + "grin-tongue-squint", + "grin-tongue-wink", + "grin-wink", + "grip-horizontal", + "grip-lines", + "grip-lines-vertical", + "grip-vertical", + "guitar", + "h-square", + "hamburger", + "hammer", + "hamsa", + "hand-holding", + "hand-holding-heart", + "hand-holding-medical", + "hand-holding-usd", + "hand-holding-water", + "hand-lizard", + "hand-middle-finger", + "hand-paper", + "hand-peace", + "hand-point-down", + "hand-point-left", + "hand-point-right", + "hand-point-up", + "hand-pointer", + "hand-rock", + "hand-scissors", + "hand-sparkles", + "hand-spock", + "hands", + "hands-helping", + "hands-wash", + "handshake", + "handshake-alt-slash", + "handshake-slash", + "hanukiah", + "hard-hat", + "hashtag", + "hat-cowboy", + "hat-cowboy-side", + "hat-wizard", + "hdd", + "head-side-cough", + "head-side-cough-slash", + "head-side-mask", + "head-side-virus", + "heading", + "headphones", + "headphones-alt", + "headset", + "heart", + "heart-broken", + "heartbeat", + "helicopter", + "highlighter", + "hiking", + "hippo", + "history", + "hockey-puck", + "holly-berry", + "home", + "horse", + "horse-head", + "hospital", + "hospital-alt", + "hospital-symbol", + "hospital-user", + "hot-tub", + "hotdog", + "hotel", + "hourglass", + "hourglass-end", + "hourglass-half", + "hourglass-start", + "house-damage", + "house-user", + "hryvnia", + "i-cursor", + "ice-cream", + "icicles", + "icons", + "id-badge", + "id-card", + "id-card-alt", + "igloo", + "image", + "images", + "inbox", + "indent", + "industry", + "infinity", + "info", + "info-circle", + "integral", + "intersection", + "italic", + "jedi", + "joint", + "journal-whills", + "kaaba", + "key", + "keyboard", + "khanda", + "kiss", + "kiss-beam", + "kiss-wink-heart", + "kiwi-bird", + "lambda", + "landmark", + "language", + "laptop", + "laptop-code", + "laptop-house", + "laptop-medical", + "laugh", + "laugh-beam", + "laugh-squint", + "laugh-wink", + "layer-group", + "leaf", + "lemon", + "less-than", + "less-than-equal", + "level-down-alt", + "level-up-alt", + "life-ring", + "lightbulb", + "link", + "lira-sign", + "list", + "list-alt", + "list-ol", + "list-ul", + "location-arrow", + "lock", + "lock-open", + "long-arrow-alt-down", + "long-arrow-alt-left", + "long-arrow-alt-right", + "long-arrow-alt-up", + "low-vision", + "luggage-cart", + "lungs", + "lungs-virus", + "magic", + "magnet", + "mail-bulk", + "male", + "map", + "map-marked", + "map-marked-alt", + "map-marker", + "map-marker-alt", + "map-pin", + "map-signs", + "marker", + "mars", + "mars-double", + "mars-stroke", + "mars-stroke-h", + "mars-stroke-v", + "mask", + "medal", + "medkit", + "meh", + "meh-blank", + "meh-rolling-eyes", + "memory", + "menorah", + "mercury", + "meteor", + "microchip", + "microphone", + "microphone-alt", + "microphone-alt-slash", + "microphone-slash", + "microscope", + "minus", + "minus-circle", + "minus-square", + "mitten", + "mobile", + "mobile-alt", + "money-bill", + "money-bill-alt", + "money-bill-wave", + "money-bill-wave-alt", + "money-check", + "money-check-alt", + "monument", + "moon", + "mortar-pestle", + "mosque", + "motorcycle", + "mountain", + "mouse", + "mouse-pointer", + "mug-hot", + "music", + "network-wired", + "neuter", + "newspaper", + "not-equal", + "notes-medical", + "object-group", + "object-ungroup", + "oil-can", + "om", + "omega", + "otter", + "outdent", + "pager", + "paint-brush", + "paint-roller", + "palette", + "pallet", + "paper-plane", + "paperclip", + "parachute-box", + "paragraph", + "parking", + "passport", + "pastafarianism", + "paste", + "pause", + "pause-circle", + "paw", + "peace", + "pen", + "pen-alt", + "pen-fancy", + "pen-nib", + "pen-square", + "pencil-alt", + "pencil-ruler", + "people-arrows", + "people-carry", + "pepper-hot", + "percent", + "percentage", + "person-booth", + "phone", + "phone-alt", + "phone-slash", + "phone-square", + "phone-square-alt", + "phone-volume", + "photo-video", + "pi", + "piggy-bank", + "pills", + "pizza-slice", + "place-of-worship", + "plane", + "plane-arrival", + "plane-departure", + "plane-slash", + "play", + "play-circle", + "plug", + "plus", + "plus-circle", + "plus-square", + "podcast", + "poll", + "poll-h", + "poo", + "poo-storm", + "poop", + "portrait", + "pound-sign", + "power-off", + "pray", + "praying-hands", + "prescription", + "prescription-bottle", + "prescription-bottle-alt", + "print", + "procedures", + "project-diagram", + "pump-medical", + "pump-soap", + "puzzle-piece", + "qrcode", + "question", + "question-circle", + "quidditch", + "quote-left", + "quote-right", + "quran", + "radiation", + "radiation-alt", + "rainbow", + "random", + "receipt", + "record-vinyl", + "recycle", + "redo", + "redo-alt", + "registered", + "remove-format", + "reply", + "reply-all", + "republican", + "restroom", + "retweet", + "ribbon", + "ring", + "road", + "robot", + "rocket", + "route", + "rss", + "rss-square", + "ruble-sign", + "ruler", + "ruler-combined", + "ruler-horizontal", + "ruler-vertical", + "running", + "rupee-sign", + "sad-cry", + "sad-tear", + "satellite", + "satellite-dish", + "save", + "school", + "screwdriver", + "scroll", + "sd-card", + "search", + "search-dollar", + "search-location", + "search-minus", + "search-plus", + "seedling", + "server", + "shapes", + "share", + "share-alt", + "share-alt-square", + "share-square", + "shekel-sign", + "shield-alt", + "shield-virus", + "ship", + "shipping-fast", + "shoe-prints", + "shopping-bag", + "shopping-basket", + "shopping-cart", + "shower", + "shuttle-van", + "sigma", + "sign", + "sign-in-alt", + "sign-language", + "sign-out-alt", + "signal", + "signal-alt", + "signal-alt-slash", + "signal-slash", + "signature", + "sim-card", + "sink", + "sitemap", + "skating", + "skiing", + "skiing-nordic", + "skull", + "skull-crossbones", + "slash", + "sleigh", + "sliders-h", + "smile", + "smile-beam", + "smile-wink", + "smog", + "smoking", + "smoking-ban", + "sms", + "snowboarding", + "snowflake", + "snowman", + "snowplow", + "soap", + "socks", + "solar-panel", + "sort", + "sort-alpha-down", + "sort-alpha-down-alt", + "sort-alpha-up", + "sort-alpha-up-alt", + "sort-amount-down", + "sort-amount-down-alt", + "sort-amount-up", + "sort-amount-up-alt", + "sort-down", + "sort-numeric-down", + "sort-numeric-down-alt", + "sort-numeric-up", + "sort-numeric-up-alt", + "sort-up", + "spa", + "space-shuttle", + "spell-check", + "spider", + "spinner", + "splotch", + "spray-can", + "square", + "square-full", + "square-root", + "square-root-alt", + "stamp", + "star", + "star-and-crescent", + "star-half", + "star-half-alt", + "star-of-david", + "star-of-life", + "step-backward", + "step-forward", + "stethoscope", + "sticky-note", + "stop", + "stop-circle", + "stopwatch", + "stopwatch-20", + "store", + "store-alt", + "store-alt-slash", + "store-slash", + "stream", + "street-view", + "strikethrough", + "stroopwafel", + "subscript", + "subway", + "suitcase", + "suitcase-rolling", + "sun", + "superscript", + "surprise", + "swatchbook", + "swimmer", + "swimming-pool", + "synagogue", + "sync", + "sync-alt", + "syringe", + "table", + "table-tennis", + "tablet", + "tablet-alt", + "tablets", + "tachometer-alt", + "tag", + "tags", + "tally", + "tape", + "tasks", + "taxi", + "teeth", + "teeth-open", + "temperature-high", + "temperature-low", + "tenge", + "terminal", + "text-height", + "text-width", + "th", + "th-large", + "th-list", + "theater-masks", + "thermometer", + "thermometer-empty", + "thermometer-full", + "thermometer-half", + "thermometer-quarter", + "thermometer-three-quarters", + "theta", + "thumbs-down", + "thumbs-up", + "thumbtack", + "ticket-alt", + "tilde", + "times", + "times-circle", + "tint", + "tint-slash", + "tired", + "toggle-off", + "toggle-on", + "toilet", + "toilet-paper", + "toilet-paper-slash", + "toolbox", + "tools", + "tooth", + "torah", + "torii-gate", + "tractor", + "trademark", + "traffic-light", + "trailer", + "train", + "tram", + "transgender", + "transgender-alt", + "trash", + "trash-alt", + "trash-restore", + "trash-restore-alt", + "tree", + "trophy", + "truck", + "truck-loading", + "truck-monster", + "truck-moving", + "truck-pickup", + "tshirt", + "tty", + "tv", + "umbrella", + "umbrella-beach", + "underline", + "undo", + "undo-alt", + "union", + "universal-access", + "university", + "unlink", + "unlock", + "unlock-alt", + "upload", + "user", + "user-alt", + "user-alt-slash", + "user-astronaut", + "user-check", + "user-circle", + "user-clock", + "user-cog", + "user-edit", + "user-friends", + "user-graduate", + "user-injured", + "user-lock", + "user-md", + "user-minus", + "user-ninja", + "user-nurse", + "user-plus", + "user-secret", + "user-shield", + "user-slash", + "user-tag", + "user-tie", + "user-times", + "users", + "users-cog", + "users-slash", + "utensil-spoon", + "utensils", + "value-absolute", + "vector-square", + "venus", + "venus-double", + "venus-mars", + "vest", + "vest-patches", + "vial", + "vials", + "video", + "video-slash", + "vihara", + "virus", + "virus-slash", + "viruses", + "voicemail", + "volleyball-ball", + "volume", + "volume-down", + "volume-mute", + "volume-off", + "volume-slash", + "volume-up", + "vote-yea", + "vr-cardboard", + "walking", + "wallet", + "warehouse", + "water", + "wave-square", + "weight", + "weight-hanging", + "wheelchair", + "wifi", + "wifi-slash", + "wind", + "window-close", + "window-maximize", + "window-minimize", + "window-restore", + "wine-bottle", + "wine-glass", + "wine-glass-alt", + "won-sign", + "wrench", + "x-ray", + "yen-sign", + "yin-yang" + ] +}; diff --git a/sop-admin/sop-admin-frontend/src/components/ReIcon/index.ts b/sop-admin/sop-admin-frontend/src/components/ReIcon/index.ts new file mode 100755 index 00000000..9f77a1eb --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/components/ReIcon/index.ts @@ -0,0 +1,15 @@ +import iconifyIconOffline from "./src/iconifyIconOffline"; +import iconifyIconOnline from "./src/iconifyIconOnline"; +import iconSelect from "./src/Select.vue"; +import fontIcon from "./src/iconfont"; + +/** 本地图标组件 */ +const IconifyIconOffline = iconifyIconOffline; +/** 在线图标组件 */ +const IconifyIconOnline = iconifyIconOnline; +/** `IconSelect`图标选择器组件 */ +const IconSelect = iconSelect; +/** `iconfont`组件 */ +const FontIcon = fontIcon; + +export { IconifyIconOffline, IconifyIconOnline, IconSelect, FontIcon }; diff --git a/sop-admin/sop-admin-frontend/src/components/ReIcon/src/Select.vue b/sop-admin/sop-admin-frontend/src/components/ReIcon/src/Select.vue new file mode 100755 index 00000000..aad1042d --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/components/ReIcon/src/Select.vue @@ -0,0 +1,268 @@ + + + + + diff --git a/sop-admin/sop-admin-frontend/src/components/ReIcon/src/hooks.ts b/sop-admin/sop-admin-frontend/src/components/ReIcon/src/hooks.ts new file mode 100755 index 00000000..5a377dac --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/components/ReIcon/src/hooks.ts @@ -0,0 +1,61 @@ +import type { iconType } from "./types"; +import { h, defineComponent, type Component } from "vue"; +import { IconifyIconOnline, IconifyIconOffline, FontIcon } from "../index"; + +/** + * 支持 `iconfont`、自定义 `svg` 以及 `iconify` 中所有的图标 + * @see 点击查看文档图标篇 {@link https://pure-admin.github.io/pure-admin-doc/pages/icon/} + * @param icon 必传 图标 + * @param attrs 可选 iconType 属性 + * @returns Component + */ +export function useRenderIcon(icon: any, attrs?: iconType): Component { + // iconfont + const ifReg = /^IF-/; + // typeof icon === "function" 属于SVG + if (ifReg.test(icon)) { + // iconfont + const name = icon.split(ifReg)[1]; + const iconName = name.slice( + 0, + name.indexOf(" ") == -1 ? name.length : name.indexOf(" ") + ); + const iconType = name.slice(name.indexOf(" ") + 1, name.length); + return defineComponent({ + name: "FontIcon", + render() { + return h(FontIcon, { + icon: iconName, + iconType, + ...attrs + }); + } + }); + } else if (typeof icon === "function" || typeof icon?.render === "function") { + // svg + return attrs ? h(icon, { ...attrs }) : icon; + } else if (typeof icon === "object") { + return defineComponent({ + name: "OfflineIcon", + render() { + return h(IconifyIconOffline, { + icon: icon, + ...attrs + }); + } + }); + } else { + // 通过是否存在 : 符号来判断是在线还是本地图标,存在即是在线图标,反之 + return defineComponent({ + name: "Icon", + render() { + const IconifyIcon = + icon && icon.includes(":") ? IconifyIconOnline : IconifyIconOffline; + return h(IconifyIcon, { + icon: icon, + ...attrs + }); + } + }); + } +} diff --git a/sop-admin/sop-admin-frontend/src/components/ReIcon/src/iconfont.ts b/sop-admin/sop-admin-frontend/src/components/ReIcon/src/iconfont.ts new file mode 100755 index 00000000..c1104519 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/components/ReIcon/src/iconfont.ts @@ -0,0 +1,48 @@ +import { h, defineComponent } from "vue"; + +// 封装iconfont组件,默认`font-class`引用模式,支持`unicode`引用、`font-class`引用、`symbol`引用 (https://www.iconfont.cn/help/detail?spm=a313x.7781069.1998910419.20&helptype=code) +export default defineComponent({ + name: "FontIcon", + props: { + icon: { + type: String, + default: "" + } + }, + render() { + const attrs = this.$attrs; + if (Object.keys(attrs).includes("uni") || attrs?.iconType === "uni") { + return h( + "i", + { + class: "iconfont", + ...attrs + }, + this.icon + ); + } else if ( + Object.keys(attrs).includes("svg") || + attrs?.iconType === "svg" + ) { + return h( + "svg", + { + class: "icon-svg", + "aria-hidden": true + }, + { + default: () => [ + h("use", { + "xlink:href": `#${this.icon}` + }) + ] + } + ); + } else { + return h("i", { + class: `iconfont ${this.icon}`, + ...attrs + }); + } + } +}); diff --git a/sop-admin/sop-admin-frontend/src/components/ReIcon/src/iconifyIconOffline.ts b/sop-admin/sop-admin-frontend/src/components/ReIcon/src/iconifyIconOffline.ts new file mode 100755 index 00000000..b47aa99a --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/components/ReIcon/src/iconifyIconOffline.ts @@ -0,0 +1,30 @@ +import { h, defineComponent } from "vue"; +import { Icon as IconifyIcon, addIcon } from "@iconify/vue/dist/offline"; + +// Iconify Icon在Vue里本地使用(用于内网环境) +export default defineComponent({ + name: "IconifyIconOffline", + components: { IconifyIcon }, + props: { + icon: { + default: null + } + }, + render() { + if (typeof this.icon === "object") addIcon(this.icon, this.icon); + const attrs = this.$attrs; + return h( + IconifyIcon, + { + icon: this.icon, + style: attrs?.style + ? Object.assign(attrs.style, { outline: "none" }) + : { outline: "none" }, + ...attrs + }, + { + default: () => [] + } + ); + } +}); diff --git a/sop-admin/sop-admin-frontend/src/components/ReIcon/src/iconifyIconOnline.ts b/sop-admin/sop-admin-frontend/src/components/ReIcon/src/iconifyIconOnline.ts new file mode 100755 index 00000000..a5f5822d --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/components/ReIcon/src/iconifyIconOnline.ts @@ -0,0 +1,30 @@ +import { h, defineComponent } from "vue"; +import { Icon as IconifyIcon } from "@iconify/vue"; + +// Iconify Icon在Vue里在线使用(用于外网环境) +export default defineComponent({ + name: "IconifyIconOnline", + components: { IconifyIcon }, + props: { + icon: { + type: String, + default: "" + } + }, + render() { + const attrs = this.$attrs; + return h( + IconifyIcon, + { + icon: `${this.icon}`, + style: attrs?.style + ? Object.assign(attrs.style, { outline: "none" }) + : { outline: "none" }, + ...attrs + }, + { + default: () => [] + } + ); + } +}); diff --git a/sop-admin/sop-admin-frontend/src/components/ReIcon/src/offlineIcon.ts b/sop-admin/sop-admin-frontend/src/components/ReIcon/src/offlineIcon.ts new file mode 100755 index 00000000..2283a55d --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/components/ReIcon/src/offlineIcon.ts @@ -0,0 +1,70 @@ +// 这里存放本地图标,在 src/layout/index.vue 文件中加载,避免在首启动加载 +import { addIcon } from "@iconify/vue/dist/offline"; + +// 本地菜单图标,后端在路由的 icon 中返回对应的图标字符串并且前端在此处使用 addIcon 添加即可渲染菜单图标 +// @iconify-icons/ep +import Menu from "@iconify-icons/ep/menu"; +import Edit from "@iconify-icons/ep/edit"; +import SetUp from "@iconify-icons/ep/set-up"; +import Guide from "@iconify-icons/ep/guide"; +import Monitor from "@iconify-icons/ep/monitor"; +import Lollipop from "@iconify-icons/ep/lollipop"; +import Histogram from "@iconify-icons/ep/histogram"; +import HomeFilled from "@iconify-icons/ep/home-filled"; +addIcon("ep:menu", Menu); +addIcon("ep:edit", Edit); +addIcon("ep:set-up", SetUp); +addIcon("ep:guide", Guide); +addIcon("ep:monitor", Monitor); +addIcon("ep:lollipop", Lollipop); +addIcon("ep:histogram", Histogram); +addIcon("ep:home-filled", HomeFilled); +// @iconify-icons/ri +import Tag from "@iconify-icons/ri/bookmark-2-line"; +import Ppt from "@iconify-icons/ri/file-ppt-2-line"; +import Card from "@iconify-icons/ri/bank-card-line"; +import Role from "@iconify-icons/ri/admin-fill"; +import Info from "@iconify-icons/ri/file-info-line"; +import Dept from "@iconify-icons/ri/git-branch-line"; +import Table from "@iconify-icons/ri/table-line"; +import Links from "@iconify-icons/ri/links-fill"; +import Search from "@iconify-icons/ri/search-line"; +import FlUser from "@iconify-icons/ri/admin-line"; +import Setting from "@iconify-icons/ri/settings-3-line"; +import MindMap from "@iconify-icons/ri/mind-map"; +import BarChart from "@iconify-icons/ri/bar-chart-horizontal-line"; +import LoginLog from "@iconify-icons/ri/window-line"; +import Artboard from "@iconify-icons/ri/artboard-line"; +import SystemLog from "@iconify-icons/ri/file-search-line"; +import ListCheck from "@iconify-icons/ri/list-check"; +import UbuntuFill from "@iconify-icons/ri/ubuntu-fill"; +import OnlineUser from "@iconify-icons/ri/user-voice-line"; +import EditBoxLine from "@iconify-icons/ri/edit-box-line"; +import OperationLog from "@iconify-icons/ri/history-fill"; +import InformationLine from "@iconify-icons/ri/information-line"; +import TerminalWindowLine from "@iconify-icons/ri/terminal-window-line"; +import CheckboxCircleLine from "@iconify-icons/ri/checkbox-circle-line"; +addIcon("ri:bookmark-2-line", Tag); +addIcon("ri:file-ppt-2-line", Ppt); +addIcon("ri:bank-card-line", Card); +addIcon("ri:admin-fill", Role); +addIcon("ri:file-info-line", Info); +addIcon("ri:git-branch-line", Dept); +addIcon("ri:links-fill", Links); +addIcon("ri:table-line", Table); +addIcon("ri:search-line", Search); +addIcon("ri:admin-line", FlUser); +addIcon("ri:settings-3-line", Setting); +addIcon("ri:mind-map", MindMap); +addIcon("ri:bar-chart-horizontal-line", BarChart); +addIcon("ri:window-line", LoginLog); +addIcon("ri:file-search-line", SystemLog); +addIcon("ri:artboard-line", Artboard); +addIcon("ri:list-check", ListCheck); +addIcon("ri:ubuntu-fill", UbuntuFill); +addIcon("ri:user-voice-line", OnlineUser); +addIcon("ri:edit-box-line", EditBoxLine); +addIcon("ri:history-fill", OperationLog); +addIcon("ri:information-line", InformationLine); +addIcon("ri:terminal-window-line", TerminalWindowLine); +addIcon("ri:checkbox-circle-line", CheckboxCircleLine); diff --git a/sop-admin/sop-admin-frontend/src/components/ReIcon/src/types.ts b/sop-admin/sop-admin-frontend/src/components/ReIcon/src/types.ts new file mode 100755 index 00000000..000bdc59 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/components/ReIcon/src/types.ts @@ -0,0 +1,20 @@ +export interface iconType { + // iconify (https://docs.iconify.design/icon-components/vue/#properties) + inline?: boolean; + width?: string | number; + height?: string | number; + horizontalFlip?: boolean; + verticalFlip?: boolean; + flip?: string; + rotate?: number | string; + color?: string; + horizontalAlign?: boolean; + verticalAlign?: boolean; + align?: string; + onLoad?: Function; + includes?: Function; + // svg 需要什么SVG属性自行添加 + fill?: string; + // all icon + style?: object; +} diff --git a/sop-admin/sop-admin-frontend/src/components/RePerms/index.ts b/sop-admin/sop-admin-frontend/src/components/RePerms/index.ts new file mode 100755 index 00000000..3701c3c1 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/components/RePerms/index.ts @@ -0,0 +1,5 @@ +import perms from "./src/perms"; + +const Perms = perms; + +export { Perms }; diff --git a/sop-admin/sop-admin-frontend/src/components/RePerms/src/perms.tsx b/sop-admin/sop-admin-frontend/src/components/RePerms/src/perms.tsx new file mode 100755 index 00000000..da01bc16 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/components/RePerms/src/perms.tsx @@ -0,0 +1,20 @@ +import { defineComponent, Fragment } from "vue"; +import { hasPerms } from "@/utils/auth"; + +export default defineComponent({ + name: "Perms", + props: { + value: { + type: undefined, + default: [] + } + }, + setup(props, { slots }) { + return () => { + if (!slots) return null; + return hasPerms(props.value) ? ( + {slots.default?.()} + ) : null; + }; + } +}); diff --git a/sop-admin/sop-admin-frontend/src/components/RePureTableBar/index.ts b/sop-admin/sop-admin-frontend/src/components/RePureTableBar/index.ts new file mode 100755 index 00000000..31b8a16e --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/components/RePureTableBar/index.ts @@ -0,0 +1,5 @@ +import pureTableBar from "./src/bar"; +import { withInstall } from "@pureadmin/utils"; + +/** 配合 `@pureadmin/table` 实现快速便捷的表格操作 https://github.com/pure-admin/pure-admin-table */ +export const PureTableBar = withInstall(pureTableBar); diff --git a/sop-admin/sop-admin-frontend/src/components/RePureTableBar/src/bar.tsx b/sop-admin/sop-admin-frontend/src/components/RePureTableBar/src/bar.tsx new file mode 100755 index 00000000..5367c6b3 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/components/RePureTableBar/src/bar.tsx @@ -0,0 +1,393 @@ +import Sortable from "sortablejs"; +import { transformI18n } from "@/plugins/i18n"; +import { useEpThemeStoreHook } from "@/store/modules/epTheme"; +import { + type PropType, + ref, + unref, + computed, + nextTick, + defineComponent, + getCurrentInstance +} from "vue"; +import { + delay, + cloneDeep, + isBoolean, + isFunction, + getKeyList +} from "@pureadmin/utils"; + +import Fullscreen from "@iconify-icons/ri/fullscreen-fill"; +import ExitFullscreen from "@iconify-icons/ri/fullscreen-exit-fill"; +import DragIcon from "@/assets/table-bar/drag.svg?component"; +import ExpandIcon from "@/assets/table-bar/expand.svg?component"; +import RefreshIcon from "@/assets/table-bar/refresh.svg?component"; +import SettingIcon from "@/assets/table-bar/settings.svg?component"; +import CollapseIcon from "@/assets/table-bar/collapse.svg?component"; + +const props = { + /** 头部最左边的标题 */ + title: { + type: String, + default: "列表" + }, + /** 对于树形表格,如果想启用展开和折叠功能,传入当前表格的ref即可 */ + tableRef: { + type: Object as PropType + }, + /** 需要展示的列 */ + columns: { + type: Array as PropType, + default: () => [] + }, + isExpandAll: { + type: Boolean, + default: true + }, + tableKey: { + type: [String, Number] as PropType, + default: "0" + } +}; + +export default defineComponent({ + name: "PureTableBar", + props, + emits: ["refresh"], + setup(props, { emit, slots, attrs }) { + const size = ref("default"); + const loading = ref(false); + const checkAll = ref(true); + const isFullscreen = ref(false); + const isIndeterminate = ref(false); + const instance = getCurrentInstance()!; + const isExpandAll = ref(props.isExpandAll); + const filterColumns = cloneDeep(props?.columns).filter(column => + isBoolean(column?.hide) + ? !column.hide + : !(isFunction(column?.hide) && column?.hide()) + ); + let checkColumnList = getKeyList(cloneDeep(props?.columns), "label"); + const checkedColumns = ref(getKeyList(cloneDeep(filterColumns), "label")); + const dynamicColumns = ref(cloneDeep(props?.columns)); + + const getDropdownItemStyle = computed(() => { + return s => { + return { + background: + s === size.value ? useEpThemeStoreHook().epThemeColor : "", + color: s === size.value ? "#fff" : "var(--el-text-color-primary)" + }; + }; + }); + + const iconClass = computed(() => { + return [ + "text-black", + "dark:text-white", + "duration-100", + "hover:!text-primary", + "cursor-pointer", + "outline-none" + ]; + }); + + const topClass = computed(() => { + return [ + "flex", + "justify-between", + "pt-[3px]", + "px-[11px]", + "border-b-[1px]", + "border-solid", + "border-[#dcdfe6]", + "dark:border-[#303030]" + ]; + }); + + function onReFresh() { + loading.value = true; + emit("refresh"); + delay(500).then(() => (loading.value = false)); + } + + function onExpand() { + isExpandAll.value = !isExpandAll.value; + toggleRowExpansionAll(props.tableRef.data, isExpandAll.value); + } + + function toggleRowExpansionAll(data, isExpansion) { + data.forEach(item => { + props.tableRef.toggleRowExpansion(item, isExpansion); + if (item.children !== undefined && item.children !== null) { + toggleRowExpansionAll(item.children, isExpansion); + } + }); + } + + function handleCheckAllChange(val: boolean) { + checkedColumns.value = val ? checkColumnList : []; + isIndeterminate.value = false; + dynamicColumns.value.map(column => + val ? (column.hide = false) : (column.hide = true) + ); + } + + function handleCheckedColumnsChange(value: string[]) { + checkedColumns.value = value; + const checkedCount = value.length; + checkAll.value = checkedCount === checkColumnList.length; + isIndeterminate.value = + checkedCount > 0 && checkedCount < checkColumnList.length; + } + + function handleCheckColumnListChange(val: boolean, label: string) { + dynamicColumns.value.filter( + item => transformI18n(item.label) === transformI18n(label) + )[0].hide = !val; + } + + async function onReset() { + checkAll.value = true; + isIndeterminate.value = false; + dynamicColumns.value = cloneDeep(props?.columns); + checkColumnList = []; + checkColumnList = await getKeyList(cloneDeep(props?.columns), "label"); + checkedColumns.value = getKeyList(cloneDeep(filterColumns), "label"); + } + + const dropdown = { + dropdown: () => ( + + (size.value = "large")} + > + 宽松 + + (size.value = "default")} + > + 默认 + + (size.value = "small")} + > + 紧凑 + + + ) + }; + + /** 列展示拖拽排序 */ + const rowDrop = (event: { preventDefault: () => void }) => { + event.preventDefault(); + nextTick(() => { + const wrapper: HTMLElement = ( + instance?.proxy?.$refs[`GroupRef${unref(props.tableKey)}`] as any + ).$el.firstElementChild; + Sortable.create(wrapper, { + animation: 300, + handle: ".drag-btn", + onEnd: ({ newIndex, oldIndex, item }) => { + const targetThElem = item; + const wrapperElem = targetThElem.parentNode as HTMLElement; + const oldColumn = dynamicColumns.value[oldIndex]; + const newColumn = dynamicColumns.value[newIndex]; + if (oldColumn?.fixed || newColumn?.fixed) { + // 当前列存在fixed属性 则不可拖拽 + const oldThElem = wrapperElem.children[oldIndex] as HTMLElement; + if (newIndex > oldIndex) { + wrapperElem.insertBefore(targetThElem, oldThElem); + } else { + wrapperElem.insertBefore( + targetThElem, + oldThElem ? oldThElem.nextElementSibling : oldThElem + ); + } + return; + } + const currentRow = dynamicColumns.value.splice(oldIndex, 1)[0]; + dynamicColumns.value.splice(newIndex, 0, currentRow); + } + }); + }); + }; + + const isFixedColumn = (label: string) => { + return dynamicColumns.value.filter( + item => transformI18n(item.label) === transformI18n(label) + )[0].fixed + ? true + : false; + }; + + const rendTippyProps = (content: string) => { + // https://vue-tippy.netlify.app/props + return { + content, + offset: [0, 18], + duration: [300, 0], + followCursor: true, + hideOnClick: "toggle" + }; + }; + + const reference = { + reference: () => ( + + ) + }; + + return () => ( + <> +
+
+ {slots?.title ? ( + slots.title() + ) : ( +

{props.title}

+ )} +
+ {slots?.buttons ? ( +
{slots.buttons()}
+ ) : null} + {props.tableRef?.size ? ( + <> + onExpand()} + /> + + + ) : null} + onReFresh()} + /> + + + + + + + +
+ handleCheckAllChange(value)} + /> + onReset()}> + 重置 + +
+ +
+ + handleCheckedColumnsChange(value)} + > + + {checkColumnList.map((item, index) => { + return ( +
+ void; + }) => rowDrop(event)} + /> + + handleCheckColumnListChange(value, item) + } + > + + {transformI18n(item)} + + +
+ ); + })} +
+
+
+
+
+ + + (isFullscreen.value = !isFullscreen.value)} + /> +
+
+ {slots.default({ + size: size.value, + dynamicColumns: dynamicColumns.value + })} +
+ + ); + } +}); diff --git a/sop-admin/sop-admin-frontend/src/components/ReSegmented/index.ts b/sop-admin/sop-admin-frontend/src/components/ReSegmented/index.ts new file mode 100755 index 00000000..de4253c4 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/components/ReSegmented/index.ts @@ -0,0 +1,8 @@ +import reSegmented from "./src/index"; +import { withInstall } from "@pureadmin/utils"; + +/** 分段控制器组件 */ +export const ReSegmented = withInstall(reSegmented); + +export default ReSegmented; +export type { OptionsType } from "./src/type"; diff --git a/sop-admin/sop-admin-frontend/src/components/ReSegmented/src/index.css b/sop-admin/sop-admin-frontend/src/components/ReSegmented/src/index.css new file mode 100755 index 00000000..503bbe43 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/components/ReSegmented/src/index.css @@ -0,0 +1,157 @@ +.pure-segmented { + --pure-control-padding-horizontal: 12px; + --pure-control-padding-horizontal-sm: 8px; + --pure-segmented-track-padding: 2px; + --pure-segmented-line-width: 1px; + + --pure-segmented-border-radius-small: 4px; + --pure-segmented-border-radius-base: 6px; + --pure-segmented-border-radius-large: 8px; + + box-sizing: border-box; + display: inline-block; + padding: var(--pure-segmented-track-padding); + font-size: var(--el-font-size-base); + color: rgba(0, 0, 0, 0.65); + background-color: rgb(0 0 0 / 4%); + border-radius: var(--pure-segmented-border-radius-base); +} + +.pure-segmented-block { + display: flex; +} + +.pure-segmented-block .pure-segmented-item { + flex: 1; + min-width: 0; +} + +.pure-segmented-block .pure-segmented-item > .pure-segmented-item-label > span { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} + +/* small */ +.pure-segmented.pure-segmented--small { + border-radius: var(--pure-segmented-border-radius-small); +} +.pure-segmented.pure-segmented--small .pure-segmented-item { + border-radius: var(--el-border-radius-small); +} +.pure-segmented.pure-segmented--small .pure-segmented-item > div { + min-height: calc( + var(--el-component-size-small) - var(--pure-segmented-track-padding) * 2 + ); + line-height: calc( + var(--el-component-size-small) - var(--pure-segmented-track-padding) * 2 + ); + padding: 0 + calc( + var(--pure-control-padding-horizontal-sm) - + var(--pure-segmented-line-width) + ); +} + +/* large */ +.pure-segmented.pure-segmented--large { + border-radius: var(--pure-segmented-border-radius-large); +} +.pure-segmented.pure-segmented--large .pure-segmented-item { + border-radius: calc( + var(--el-border-radius-base) + var(--el-border-radius-small) + ); +} +.pure-segmented.pure-segmented--large .pure-segmented-item > div { + min-height: calc( + var(--el-component-size-large) - var(--pure-segmented-track-padding) * 2 + ); + line-height: calc( + var(--el-component-size-large) - var(--pure-segmented-track-padding) * 2 + ); + padding: 0 + calc( + var(--pure-control-padding-horizontal) - var(--pure-segmented-line-width) + ); + font-size: var(--el-font-size-medium); +} + +/* default */ +.pure-segmented-item { + position: relative; + text-align: center; + cursor: pointer; + border-radius: var(--el-border-radius-base); + transition: all 0.1s cubic-bezier(0.645, 0.045, 0.355, 1); +} +.pure-segmented .pure-segmented-item > div { + min-height: calc( + var(--el-component-size) - var(--pure-segmented-track-padding) * 2 + ); + line-height: calc( + var(--el-component-size) - var(--pure-segmented-track-padding) * 2 + ); + padding: 0 + calc( + var(--pure-control-padding-horizontal) - var(--pure-segmented-line-width) + ); + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + transition: 0.1s; +} + +.pure-segmented-group { + position: relative; + display: flex; + align-items: stretch; + justify-items: flex-start; + width: 100%; +} + +.pure-segmented-item-selected { + position: absolute; + top: 0; + left: 0; + box-sizing: border-box; + display: none; + width: 0; + height: 100%; + padding: 4px 0; + background-color: #fff; + border-radius: 4px; + box-shadow: + 0 2px 8px -2px rgb(0 0 0 / 5%), + 0 1px 4px -1px rgb(0 0 0 / 7%), + 0 0 1px rgb(0 0 0 / 7%); + transition: + transform 0.5s cubic-bezier(0.645, 0.045, 0.355, 1), + width 0.5s cubic-bezier(0.645, 0.045, 0.355, 1); + will-change: transform, width; +} + +.pure-segmented-item > input { + position: absolute; + inset-block-start: 0; + inset-inline-start: 0; + width: 0; + height: 0; + opacity: 0; + pointer-events: none; +} + +.pure-segmented-item-label { + display: flex; + align-items: center; + justify-content: center; +} + +.pure-segmented-item-icon svg { + width: 16px; + height: 16px; +} + +.pure-segmented-item-disabled { + color: rgba(0, 0, 0, 0.25); + cursor: not-allowed; +} diff --git a/sop-admin/sop-admin-frontend/src/components/ReSegmented/src/index.tsx b/sop-admin/sop-admin-frontend/src/components/ReSegmented/src/index.tsx new file mode 100755 index 00000000..39580ed8 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/components/ReSegmented/src/index.tsx @@ -0,0 +1,216 @@ +import "./index.css"; +import type { OptionsType } from "./type"; +import { useRenderIcon } from "@/components/ReIcon/src/hooks"; +import { + useDark, + isNumber, + isFunction, + useResizeObserver +} from "@pureadmin/utils"; +import { + type PropType, + h, + ref, + toRef, + watch, + nextTick, + defineComponent, + getCurrentInstance +} from "vue"; + +const props = { + options: { + type: Array, + default: () => [] + }, + /** 默认选中,按照第一个索引为 `0` 的模式,可选(`modelValue`只有传`number`类型时才为响应式) */ + modelValue: { + type: undefined, + require: false, + default: "0" + }, + /** 将宽度调整为父元素宽度 */ + block: { + type: Boolean, + default: false + }, + /** 控件尺寸 */ + size: { + type: String as PropType<"small" | "default" | "large"> + }, + /** 是否全局禁用,默认 `false` */ + disabled: { + type: Boolean, + default: false + }, + /** 当内容发生变化时,设置 `resize` 可使其自适应容器位置 */ + resize: { + type: Boolean, + default: false + } +}; + +export default defineComponent({ + name: "ReSegmented", + props, + emits: ["change", "update:modelValue"], + setup(props, { emit }) { + const width = ref(0); + const translateX = ref(0); + const { isDark } = useDark(); + const initStatus = ref(false); + const curMouseActive = ref(-1); + const segmentedItembg = ref(""); + const instance = getCurrentInstance()!; + const curIndex = isNumber(props.modelValue) + ? toRef(props, "modelValue") + : ref(0); + + function handleChange({ option, index }, event: Event) { + if (props.disabled || option.disabled) return; + event.preventDefault(); + isNumber(props.modelValue) + ? emit("update:modelValue", index) + : (curIndex.value = index); + segmentedItembg.value = ""; + emit("change", { index, option }); + } + + function handleMouseenter({ option, index }, event: Event) { + if (props.disabled) return; + event.preventDefault(); + curMouseActive.value = index; + if (option.disabled || curIndex.value === index) { + segmentedItembg.value = ""; + } else { + segmentedItembg.value = isDark.value + ? "#1f1f1f" + : "rgba(0, 0, 0, 0.06)"; + } + } + + function handleMouseleave(_, event: Event) { + if (props.disabled) return; + event.preventDefault(); + curMouseActive.value = -1; + } + + function handleInit(index = curIndex.value) { + nextTick(() => { + const curLabelRef = instance?.proxy?.$refs[`labelRef${index}`] as ElRef; + if (!curLabelRef) return; + width.value = curLabelRef.clientWidth; + translateX.value = curLabelRef.offsetLeft; + initStatus.value = true; + }); + } + + function handleResizeInit() { + useResizeObserver(".pure-segmented", () => { + nextTick(() => { + handleInit(curIndex.value); + }); + }); + } + + (props.block || props.resize) && handleResizeInit(); + + watch( + () => curIndex.value, + index => { + nextTick(() => { + handleInit(index); + }); + }, + { + immediate: true + } + ); + + watch(() => props.size, handleResizeInit, { + immediate: true + }); + + const rendLabel = () => { + return props.options.map((option, index) => { + return ( + + ); + }); + }; + + return () => ( +
+
+
+ {rendLabel()} +
+
+ ); + } +}); diff --git a/sop-admin/sop-admin-frontend/src/components/ReSegmented/src/type.ts b/sop-admin/sop-admin-frontend/src/components/ReSegmented/src/type.ts new file mode 100755 index 00000000..205e34dc --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/components/ReSegmented/src/type.ts @@ -0,0 +1,20 @@ +import type { VNode, Component } from "vue"; +import type { iconType } from "@/components/ReIcon/src/types.ts"; + +export interface OptionsType { + /** 文字 */ + label?: string | (() => VNode | Component); + /** + * @description 图标,采用平台内置的 `useRenderIcon` 函数渲染 + * @see {@link 用法参考 https://pure-admin.github.io/pure-admin-doc/pages/icon/#%E9%80%9A%E7%94%A8%E5%9B%BE%E6%A0%87-userendericon-hooks } + */ + icon?: string | Component; + /** 图标属性、样式配置 */ + iconAttrs?: iconType; + /** 值 */ + value?: any; + /** 是否禁用 */ + disabled?: boolean; + /** `tooltip` 提示 */ + tip?: string; +} diff --git a/sop-admin/sop-admin-frontend/src/components/ReText/index.ts b/sop-admin/sop-admin-frontend/src/components/ReText/index.ts new file mode 100755 index 00000000..62135660 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/components/ReText/index.ts @@ -0,0 +1,7 @@ +import reText from "./src/index.vue"; +import { withInstall } from "@pureadmin/utils"; + +/** 支持`Tooltip`提示的文本省略组件 */ +export const ReText = withInstall(reText); + +export default ReText; diff --git a/sop-admin/sop-admin-frontend/src/components/ReText/src/index.vue b/sop-admin/sop-admin-frontend/src/components/ReText/src/index.vue new file mode 100755 index 00000000..ecaebdbb --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/components/ReText/src/index.vue @@ -0,0 +1,66 @@ + + + diff --git a/sop-admin/sop-admin-frontend/src/config/index.ts b/sop-admin/sop-admin-frontend/src/config/index.ts new file mode 100755 index 00000000..c81d1c4d --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/config/index.ts @@ -0,0 +1,55 @@ +import axios from "axios"; +import type { App } from "vue"; + +let config: object = {}; +const { VITE_PUBLIC_PATH } = import.meta.env; + +const setConfig = (cfg?: unknown) => { + config = Object.assign(config, cfg); +}; + +const getConfig = (key?: string): PlatformConfigs => { + if (typeof key === "string") { + const arr = key.split("."); + if (arr && arr.length) { + let data = config; + arr.forEach(v => { + if (data && typeof data[v] !== "undefined") { + data = data[v]; + } else { + data = null; + } + }); + return data; + } + } + return config; +}; + +/** 获取项目动态全局配置 */ +export const getPlatformConfig = async (app: App): Promise => { + app.config.globalProperties.$config = getConfig(); + return axios({ + method: "get", + url: `${VITE_PUBLIC_PATH}platform-config.json` + }) + .then(({ data: config }) => { + let $config = app.config.globalProperties.$config; + // 自动注入系统配置 + if (app && $config && typeof config === "object") { + $config = Object.assign($config, config); + app.config.globalProperties.$config = $config; + // 设置全局配置 + setConfig($config); + } + return $config; + }) + .catch(() => { + throw "请在public文件夹下添加platform-config.json配置文件"; + }); +}; + +/** 本地响应式存储的命名空间 */ +const responsiveStorageNameSpace = () => getConfig().ResponsiveStorageNameSpace; + +export { getConfig, setConfig, responsiveStorageNameSpace }; diff --git a/sop-admin/sop-admin-frontend/src/directives/auth/index.ts b/sop-admin/sop-admin-frontend/src/directives/auth/index.ts new file mode 100755 index 00000000..2fc64904 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/directives/auth/index.ts @@ -0,0 +1,15 @@ +import { hasAuth } from "@/router/utils"; +import type { Directive, DirectiveBinding } from "vue"; + +export const auth: Directive = { + mounted(el: HTMLElement, binding: DirectiveBinding>) { + const { value } = binding; + if (value) { + !hasAuth(value) && el.parentNode?.removeChild(el); + } else { + throw new Error( + "[Directive: auth]: need auths! Like v-auth=\"['btn.add','btn.edit']\"" + ); + } + } +}; diff --git a/sop-admin/sop-admin-frontend/src/directives/copy/index.ts b/sop-admin/sop-admin-frontend/src/directives/copy/index.ts new file mode 100755 index 00000000..b71fa190 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/directives/copy/index.ts @@ -0,0 +1,33 @@ +import { message } from "@/utils/message"; +import { useEventListener } from "@vueuse/core"; +import { copyTextToClipboard } from "@pureadmin/utils"; +import type { Directive, DirectiveBinding } from "vue"; + +export interface CopyEl extends HTMLElement { + copyValue: string; +} + +/** 文本复制指令(默认双击复制) */ +export const copy: Directive = { + mounted(el: CopyEl, binding: DirectiveBinding) { + const { value } = binding; + if (value) { + el.copyValue = value; + const arg = binding.arg ?? "dblclick"; + // Register using addEventListener on mounted, and removeEventListener automatically on unmounted + useEventListener(el, arg, () => { + const success = copyTextToClipboard(el.copyValue); + success + ? message("复制成功", { type: "success" }) + : message("复制失败", { type: "error" }); + }); + } else { + throw new Error( + '[Directive: copy]: need value! Like v-copy="modelValue"' + ); + } + }, + updated(el: CopyEl, binding: DirectiveBinding) { + el.copyValue = binding.value; + } +}; diff --git a/sop-admin/sop-admin-frontend/src/directives/index.ts b/sop-admin/sop-admin-frontend/src/directives/index.ts new file mode 100755 index 00000000..d01fe714 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/directives/index.ts @@ -0,0 +1,6 @@ +export * from "./auth"; +export * from "./copy"; +export * from "./longpress"; +export * from "./optimize"; +export * from "./perms"; +export * from "./ripple"; diff --git a/sop-admin/sop-admin-frontend/src/directives/longpress/index.ts b/sop-admin/sop-admin-frontend/src/directives/longpress/index.ts new file mode 100755 index 00000000..4eec6a22 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/directives/longpress/index.ts @@ -0,0 +1,63 @@ +import { useEventListener } from "@vueuse/core"; +import type { Directive, DirectiveBinding } from "vue"; +import { subBefore, subAfter, isFunction } from "@pureadmin/utils"; + +export const longpress: Directive = { + mounted(el: HTMLElement, binding: DirectiveBinding) { + const cb = binding.value; + if (cb && isFunction(cb)) { + let timer = null; + let interTimer = null; + let num = 500; + let interNum = null; + const isInter = binding?.arg?.includes(":") ?? false; + + if (isInter) { + num = Number(subBefore(binding.arg, ":")); + interNum = Number(subAfter(binding.arg, ":")); + } else if (binding.arg) { + num = Number(binding.arg); + } + + const clear = () => { + if (timer) { + clearTimeout(timer); + timer = null; + } + if (interTimer) { + clearInterval(interTimer); + interTimer = null; + } + }; + + const onDownInter = (ev: PointerEvent) => { + ev.preventDefault(); + if (interTimer === null) { + interTimer = setInterval(() => cb(), interNum); + } + }; + + const onDown = (ev: PointerEvent) => { + clear(); + ev.preventDefault(); + if (timer === null) { + timer = isInter + ? setTimeout(() => { + cb(); + onDownInter(ev); + }, num) + : setTimeout(() => cb(), num); + } + }; + + // Register using addEventListener on mounted, and removeEventListener automatically on unmounted + useEventListener(el, "pointerdown", onDown); + useEventListener(el, "pointerup", clear); + useEventListener(el, "pointerleave", clear); + } else { + throw new Error( + '[Directive: longpress]: need callback and callback must be a function! Like v-longpress="callback"' + ); + } + } +}; diff --git a/sop-admin/sop-admin-frontend/src/directives/optimize/index.ts b/sop-admin/sop-admin-frontend/src/directives/optimize/index.ts new file mode 100755 index 00000000..7b92538d --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/directives/optimize/index.ts @@ -0,0 +1,68 @@ +import { + isArray, + throttle, + debounce, + isObject, + isFunction +} from "@pureadmin/utils"; +import { useEventListener } from "@vueuse/core"; +import type { Directive, DirectiveBinding } from "vue"; + +export interface OptimizeOptions { + /** 事件名 */ + event: string; + /** 事件触发的方法 */ + fn: (...params: any) => any; + /** 是否立即执行 */ + immediate?: boolean; + /** 防抖或节流的延迟时间(防抖默认:`200`毫秒、节流默认:`1000`毫秒) */ + timeout?: number; + /** 传递的参数 */ + params?: any; +} + +/** 防抖(v-optimize或v-optimize:debounce)、节流(v-optimize:throttle)指令 */ +export const optimize: Directive = { + mounted(el: HTMLElement, binding: DirectiveBinding) { + const { value } = binding; + const optimizeType = binding.arg ?? "debounce"; + const type = ["debounce", "throttle"].find(t => t === optimizeType); + if (type) { + if (value && value.event && isFunction(value.fn)) { + let params = value?.params; + if (params) { + if (isArray(params) || isObject(params)) { + params = isObject(params) ? Array.of(params) : params; + } else { + throw new Error( + "[Directive: optimize]: `params` must be an array or object" + ); + } + } + // Register using addEventListener on mounted, and removeEventListener automatically on unmounted + useEventListener( + el, + value.event, + type === "debounce" + ? debounce( + params ? () => value.fn(...params) : value.fn, + value?.timeout ?? 200, + value?.immediate ?? false + ) + : throttle( + params ? () => value.fn(...params) : value.fn, + value?.timeout ?? 1000 + ) + ); + } else { + throw new Error( + "[Directive: optimize]: `event` and `fn` are required, and `fn` must be a function" + ); + } + } else { + throw new Error( + "[Directive: optimize]: only `debounce` and `throttle` are supported" + ); + } + } +}; diff --git a/sop-admin/sop-admin-frontend/src/directives/perms/index.ts b/sop-admin/sop-admin-frontend/src/directives/perms/index.ts new file mode 100755 index 00000000..073c918b --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/directives/perms/index.ts @@ -0,0 +1,15 @@ +import { hasPerms } from "@/utils/auth"; +import type { Directive, DirectiveBinding } from "vue"; + +export const perms: Directive = { + mounted(el: HTMLElement, binding: DirectiveBinding>) { + const { value } = binding; + if (value) { + !hasPerms(value) && el.parentNode?.removeChild(el); + } else { + throw new Error( + "[Directive: perms]: need perms! Like v-perms=\"['btn.add','btn.edit']\"" + ); + } + } +}; diff --git a/sop-admin/sop-admin-frontend/src/directives/ripple/index.scss b/sop-admin/sop-admin-frontend/src/directives/ripple/index.scss new file mode 100755 index 00000000..061c82c9 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/directives/ripple/index.scss @@ -0,0 +1,48 @@ +/* stylelint-disable-next-line scss/dollar-variable-colon-space-after */ +$ripple-animation-transition-in: + transform 0.4s cubic-bezier(0, 0, 0.2, 1), + opacity 0.2s cubic-bezier(0, 0, 0.2, 1) !default; +$ripple-animation-transition-out: opacity 0.5s cubic-bezier(0, 0, 0.2, 1) !default; +$ripple-animation-visible-opacity: 0.25 !default; + +.v-ripple { + &__container { + position: absolute; + top: 0; + left: 0; + z-index: 0; + width: 100%; + height: 100%; + overflow: hidden; + pointer-events: none; + border-radius: inherit; + contain: strict; + } + + &__animation { + position: absolute; + top: 0; + left: 0; + overflow: hidden; + pointer-events: none; + background: currentcolor; + border-radius: 50%; + opacity: 0; + will-change: transform, opacity; + + &--enter { + opacity: 0; + transition: none; + } + + &--in { + opacity: $ripple-animation-visible-opacity; + transition: $ripple-animation-transition-in; + } + + &--out { + opacity: 0; + transition: $ripple-animation-transition-out; + } + } +} diff --git a/sop-admin/sop-admin-frontend/src/directives/ripple/index.ts b/sop-admin/sop-admin-frontend/src/directives/ripple/index.ts new file mode 100755 index 00000000..3fd94d9c --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/directives/ripple/index.ts @@ -0,0 +1,229 @@ +import "./index.scss"; +import { isObject } from "@pureadmin/utils"; +import type { Directive, DirectiveBinding } from "vue"; + +export interface RippleOptions { + /** 自定义`ripple`颜色,支持`tailwindcss` */ + class?: string; + /** 是否从中心扩散 */ + center?: boolean; + circle?: boolean; +} + +export interface RippleDirectiveBinding + extends Omit { + value?: boolean | { class: string }; + modifiers: { + center?: boolean; + circle?: boolean; + }; +} + +function transform(el: HTMLElement, value: string) { + el.style.transform = value; + el.style.webkitTransform = value; +} + +const calculate = ( + e: PointerEvent, + el: HTMLElement, + value: RippleOptions = {} +) => { + const offset = el.getBoundingClientRect(); + + // 获取点击位置距离 el 的垂直和水平距离 + let localX = e.clientX - offset.left; + let localY = e.clientY - offset.top; + + let radius = 0; + let scale = 0.3; + // 计算点击位置到 el 顶点最远距离,即为圆的最大半径(勾股定理) + if (el._ripple?.circle) { + scale = 0.15; + radius = el.clientWidth / 2; + radius = value.center + ? radius + : radius + Math.sqrt((localX - radius) ** 2 + (localY - radius) ** 2) / 4; + } else { + radius = Math.sqrt(el.clientWidth ** 2 + el.clientHeight ** 2) / 2; + } + + // 中心点坐标 + const centerX = `${(el.clientWidth - radius * 2) / 2}px`; + const centerY = `${(el.clientHeight - radius * 2) / 2}px`; + + // 点击位置坐标 + const x = value.center ? centerX : `${localX - radius}px`; + const y = value.center ? centerY : `${localY - radius}px`; + + return { radius, scale, x, y, centerX, centerY }; +}; + +const ripples = { + show(e: PointerEvent, el: HTMLElement, value: RippleOptions = {}) { + if (!el?._ripple?.enabled) { + return; + } + + // 创建 ripple 元素和 ripple 父元素 + const container = document.createElement("span"); + const animation = document.createElement("span"); + + container.appendChild(animation); + container.className = "v-ripple__container"; + + if (value.class) { + container.className += ` ${value.class}`; + } + + const { radius, scale, x, y, centerX, centerY } = calculate(e, el, value); + + // ripple 圆大小 + const size = `${radius * 2}px`; + + animation.className = "v-ripple__animation"; + animation.style.width = size; + animation.style.height = size; + + el.appendChild(container); + + // 获取目标元素样式表 + const computed = window.getComputedStyle(el); + // 防止 position 被覆盖导致 ripple 位置有问题 + if (computed && computed.position === "static") { + el.style.position = "relative"; + el.dataset.previousPosition = "static"; + } + + animation.classList.add("v-ripple__animation--enter"); + animation.classList.add("v-ripple__animation--visible"); + transform( + animation, + `translate(${x}, ${y}) scale3d(${scale},${scale},${scale})` + ); + animation.dataset.activated = String(performance.now()); + + setTimeout(() => { + animation.classList.remove("v-ripple__animation--enter"); + animation.classList.add("v-ripple__animation--in"); + transform(animation, `translate(${centerX}, ${centerY}) scale3d(1,1,1)`); + }, 0); + }, + + hide(el: HTMLElement | null) { + if (!el?._ripple?.enabled) return; + + const ripples = el.getElementsByClassName("v-ripple__animation"); + + if (ripples.length === 0) return; + const animation = ripples[ripples.length - 1] as HTMLElement; + + if (animation.dataset.isHiding) return; + else animation.dataset.isHiding = "true"; + + const diff = performance.now() - Number(animation.dataset.activated); + const delay = Math.max(250 - diff, 0); + + setTimeout(() => { + animation.classList.remove("v-ripple__animation--in"); + animation.classList.add("v-ripple__animation--out"); + + setTimeout(() => { + const ripples = el.getElementsByClassName("v-ripple__animation"); + if (ripples.length === 1 && el.dataset.previousPosition) { + el.style.position = el.dataset.previousPosition; + delete el.dataset.previousPosition; + } + + if (animation.parentNode?.parentNode === el) + el.removeChild(animation.parentNode); + }, 300); + }, delay); + } +}; + +function isRippleEnabled(value: any): value is true { + return typeof value === "undefined" || !!value; +} + +function rippleShow(e: PointerEvent) { + const value: RippleOptions = {}; + const element = e.currentTarget as HTMLElement | undefined; + + if (!element?._ripple || element._ripple.touched) return; + + value.center = element._ripple.centered; + if (element._ripple.class) { + value.class = element._ripple.class; + } + + ripples.show(e, element, value); +} + +function rippleHide(e: Event) { + const element = e.currentTarget as HTMLElement | null; + if (!element?._ripple) return; + + window.setTimeout(() => { + if (element._ripple) { + element._ripple.touched = false; + } + }); + ripples.hide(element); +} + +function updateRipple( + el: HTMLElement, + binding: RippleDirectiveBinding, + wasEnabled: boolean +) { + const { value, modifiers } = binding; + const enabled = isRippleEnabled(value); + if (!enabled) { + ripples.hide(el); + } + + el._ripple = el._ripple ?? {}; + el._ripple.enabled = enabled; + el._ripple.centered = modifiers.center; + el._ripple.circle = modifiers.circle; + if (isObject(value) && value.class) { + el._ripple.class = value.class; + } + + if (enabled && !wasEnabled) { + el.addEventListener("pointerdown", rippleShow); + el.addEventListener("pointerup", rippleHide); + } else if (!enabled && wasEnabled) { + removeListeners(el); + } +} + +function removeListeners(el: HTMLElement) { + el.removeEventListener("pointerdown", rippleShow); + el.removeEventListener("pointerup", rippleHide); +} + +function mounted(el: HTMLElement, binding: RippleDirectiveBinding) { + updateRipple(el, binding, false); +} + +function unmounted(el: HTMLElement) { + delete el._ripple; + removeListeners(el); +} + +function updated(el: HTMLElement, binding: RippleDirectiveBinding) { + if (binding.value === binding.oldValue) { + return; + } + + const wasEnabled = isRippleEnabled(binding.oldValue); + updateRipple(el, binding, wasEnabled); +} + +export const Ripple: Directive = { + mounted, + unmounted, + updated +}; diff --git a/sop-admin/sop-admin-frontend/src/layout/components/lay-content/index.vue b/sop-admin/sop-admin-frontend/src/layout/components/lay-content/index.vue new file mode 100755 index 00000000..5810d665 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/layout/components/lay-content/index.vue @@ -0,0 +1,215 @@ + + + + + diff --git a/sop-admin/sop-admin-frontend/src/layout/components/lay-footer/index.vue b/sop-admin/sop-admin-frontend/src/layout/components/lay-footer/index.vue new file mode 100755 index 00000000..77631343 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/layout/components/lay-footer/index.vue @@ -0,0 +1,31 @@ + + + + + diff --git a/sop-admin/sop-admin-frontend/src/layout/components/lay-frame/index.vue b/sop-admin/sop-admin-frontend/src/layout/components/lay-frame/index.vue new file mode 100755 index 00000000..b2bb9d51 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/layout/components/lay-frame/index.vue @@ -0,0 +1,79 @@ + + diff --git a/sop-admin/sop-admin-frontend/src/layout/components/lay-navbar/index.vue b/sop-admin/sop-admin-frontend/src/layout/components/lay-navbar/index.vue new file mode 100755 index 00000000..b8d7f428 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/layout/components/lay-navbar/index.vue @@ -0,0 +1,199 @@ + + + + + diff --git a/sop-admin/sop-admin-frontend/src/layout/components/lay-notice/components/NoticeItem.vue b/sop-admin/sop-admin-frontend/src/layout/components/lay-notice/components/NoticeItem.vue new file mode 100755 index 00000000..823d9cd8 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/layout/components/lay-notice/components/NoticeItem.vue @@ -0,0 +1,177 @@ + + + + + + diff --git a/sop-admin/sop-admin-frontend/src/layout/components/lay-notice/components/NoticeList.vue b/sop-admin/sop-admin-frontend/src/layout/components/lay-notice/components/NoticeList.vue new file mode 100755 index 00000000..7bc9922c --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/layout/components/lay-notice/components/NoticeList.vue @@ -0,0 +1,24 @@ + + + diff --git a/sop-admin/sop-admin-frontend/src/layout/components/lay-notice/data.ts b/sop-admin/sop-admin-frontend/src/layout/components/lay-notice/data.ts new file mode 100755 index 00000000..bd49f5e5 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/layout/components/lay-notice/data.ts @@ -0,0 +1,99 @@ +import { $t } from "@/plugins/i18n"; + +export interface ListItem { + avatar: string; + title: string; + datetime: string; + type: string; + description: string; + status?: "primary" | "success" | "warning" | "info" | "danger"; + extra?: string; +} + +export interface TabItem { + key: string; + name: string; + list: ListItem[]; + emptyText: string; +} + +export const noticesData: TabItem[] = [ + { + key: "1", + name: $t("status.pureNotify"), + list: [], + emptyText: $t("status.pureNoNotify") + }, + { + key: "2", + name: $t("status.pureMessage"), + list: [ + { + avatar: "https://xiaoxian521.github.io/hyperlink/svg/smile1.svg", + title: "小铭 评论了你", + description: "诚在于心,信在于行,诚信在于心行合一。", + datetime: "今天", + type: "2" + }, + { + avatar: "https://xiaoxian521.github.io/hyperlink/svg/smile2.svg", + title: "李白 回复了你", + description: "长风破浪会有时,直挂云帆济沧海。", + datetime: "昨天", + type: "2" + }, + { + avatar: "https://xiaoxian521.github.io/hyperlink/svg/smile5.svg", + title: "标题", + description: + "请将鼠标移动到此处,以便测试超长的消息在此处将如何处理。本例中设置的描述最大行数为2,超过2行的描述内容将被省略并且可以通过tooltip查看完整内容", + datetime: "时间", + type: "2" + } + ], + emptyText: $t("status.pureNoMessage") + }, + { + key: "3", + name: $t("status.pureTodo"), + list: [ + { + avatar: "", + title: "第三方紧急代码变更", + description: + "小林提交于 2024-05-10,需在 2024-05-11 前完成代码变更任务", + datetime: "", + extra: "马上到期", + status: "danger", + type: "3" + }, + { + avatar: "", + title: "版本发布", + description: "指派小铭于 2024-06-18 前完成更新并发布", + datetime: "", + extra: "已耗时 8 天", + status: "warning", + type: "3" + }, + { + avatar: "", + title: "新功能开发", + description: "开发多租户管理", + datetime: "", + extra: "进行中", + type: "3" + }, + { + avatar: "", + title: "任务名称", + description: "任务需要在 2030-10-30 10:00 前启动", + datetime: "", + extra: "未开始", + status: "info", + type: "3" + } + ], + emptyText: $t("status.pureNoTodo") + } +]; diff --git a/sop-admin/sop-admin-frontend/src/layout/components/lay-notice/index.vue b/sop-admin/sop-admin-frontend/src/layout/components/lay-notice/index.vue new file mode 100755 index 00000000..d85cf0f7 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/layout/components/lay-notice/index.vue @@ -0,0 +1,98 @@ + + + + + diff --git a/sop-admin/sop-admin-frontend/src/layout/components/lay-panel/index.vue b/sop-admin/sop-admin-frontend/src/layout/components/lay-panel/index.vue new file mode 100755 index 00000000..fb4fb20a --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/layout/components/lay-panel/index.vue @@ -0,0 +1,149 @@ + + + + + diff --git a/sop-admin/sop-admin-frontend/src/layout/components/lay-search/components/SearchFooter.vue b/sop-admin/sop-admin-frontend/src/layout/components/lay-search/components/SearchFooter.vue new file mode 100755 index 00000000..d8350d0f --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/layout/components/lay-search/components/SearchFooter.vue @@ -0,0 +1,63 @@ + + + + + diff --git a/sop-admin/sop-admin-frontend/src/layout/components/lay-search/components/SearchHistory.vue b/sop-admin/sop-admin-frontend/src/layout/components/lay-search/components/SearchHistory.vue new file mode 100755 index 00000000..87d5488c --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/layout/components/lay-search/components/SearchHistory.vue @@ -0,0 +1,204 @@ + + + + + diff --git a/sop-admin/sop-admin-frontend/src/layout/components/lay-search/components/SearchHistoryItem.vue b/sop-admin/sop-admin-frontend/src/layout/components/lay-search/components/SearchHistoryItem.vue new file mode 100755 index 00000000..0ee9f194 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/layout/components/lay-search/components/SearchHistoryItem.vue @@ -0,0 +1,53 @@ + + + + + diff --git a/sop-admin/sop-admin-frontend/src/layout/components/lay-search/components/SearchModal.vue b/sop-admin/sop-admin-frontend/src/layout/components/lay-search/components/SearchModal.vue new file mode 100755 index 00000000..48fb9cf7 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/layout/components/lay-search/components/SearchModal.vue @@ -0,0 +1,338 @@ + + + + + diff --git a/sop-admin/sop-admin-frontend/src/layout/components/lay-search/components/SearchResult.vue b/sop-admin/sop-admin-frontend/src/layout/components/lay-search/components/SearchResult.vue new file mode 100755 index 00000000..12507b15 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/layout/components/lay-search/components/SearchResult.vue @@ -0,0 +1,114 @@ + + + + + diff --git a/sop-admin/sop-admin-frontend/src/layout/components/lay-search/index.vue b/sop-admin/sop-admin-frontend/src/layout/components/lay-search/index.vue new file mode 100755 index 00000000..123d6a66 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/layout/components/lay-search/index.vue @@ -0,0 +1,21 @@ + + + diff --git a/sop-admin/sop-admin-frontend/src/layout/components/lay-search/types.ts b/sop-admin/sop-admin-frontend/src/layout/components/lay-search/types.ts new file mode 100755 index 00000000..a39adbd4 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/layout/components/lay-search/types.ts @@ -0,0 +1,20 @@ +interface optionsItem { + path: string; + type: "history" | "collect"; + meta: { + icon?: string; + title?: string; + }; +} + +interface dragItem { + oldIndex: number; + newIndex: number; +} + +interface Props { + value: string; + options: Array; +} + +export type { optionsItem, dragItem, Props }; diff --git a/sop-admin/sop-admin-frontend/src/layout/components/lay-setting/index.vue b/sop-admin/sop-admin-frontend/src/layout/components/lay-setting/index.vue new file mode 100755 index 00000000..18aacf25 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/layout/components/lay-setting/index.vue @@ -0,0 +1,642 @@ + + + + + diff --git a/sop-admin/sop-admin-frontend/src/layout/components/lay-sidebar/NavHorizontal.vue b/sop-admin/sop-admin-frontend/src/layout/components/lay-sidebar/NavHorizontal.vue new file mode 100755 index 00000000..e2e81d06 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/layout/components/lay-sidebar/NavHorizontal.vue @@ -0,0 +1,176 @@ + + + + + diff --git a/sop-admin/sop-admin-frontend/src/layout/components/lay-sidebar/NavMix.vue b/sop-admin/sop-admin-frontend/src/layout/components/lay-sidebar/NavMix.vue new file mode 100755 index 00000000..fc50ede5 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/layout/components/lay-sidebar/NavMix.vue @@ -0,0 +1,197 @@ + + + + + diff --git a/sop-admin/sop-admin-frontend/src/layout/components/lay-sidebar/NavVertical.vue b/sop-admin/sop-admin-frontend/src/layout/components/lay-sidebar/NavVertical.vue new file mode 100755 index 00000000..0e9fa129 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/layout/components/lay-sidebar/NavVertical.vue @@ -0,0 +1,137 @@ + + + + + diff --git a/sop-admin/sop-admin-frontend/src/layout/components/lay-sidebar/components/SidebarBreadCrumb.vue b/sop-admin/sop-admin-frontend/src/layout/components/lay-sidebar/components/SidebarBreadCrumb.vue new file mode 100755 index 00000000..417b8394 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/layout/components/lay-sidebar/components/SidebarBreadCrumb.vue @@ -0,0 +1,121 @@ + + + diff --git a/sop-admin/sop-admin-frontend/src/layout/components/lay-sidebar/components/SidebarCenterCollapse.vue b/sop-admin/sop-admin-frontend/src/layout/components/lay-sidebar/components/SidebarCenterCollapse.vue new file mode 100755 index 00000000..447fa500 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/layout/components/lay-sidebar/components/SidebarCenterCollapse.vue @@ -0,0 +1,75 @@ + + + + + diff --git a/sop-admin/sop-admin-frontend/src/layout/components/lay-sidebar/components/SidebarExtraIcon.vue b/sop-admin/sop-admin-frontend/src/layout/components/lay-sidebar/components/SidebarExtraIcon.vue new file mode 100755 index 00000000..7cad16e6 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/layout/components/lay-sidebar/components/SidebarExtraIcon.vue @@ -0,0 +1,20 @@ + + + diff --git a/sop-admin/sop-admin-frontend/src/layout/components/lay-sidebar/components/SidebarFullScreen.vue b/sop-admin/sop-admin-frontend/src/layout/components/lay-sidebar/components/SidebarFullScreen.vue new file mode 100755 index 00000000..4d38bd0c --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/layout/components/lay-sidebar/components/SidebarFullScreen.vue @@ -0,0 +1,30 @@ + + + diff --git a/sop-admin/sop-admin-frontend/src/layout/components/lay-sidebar/components/SidebarItem.vue b/sop-admin/sop-admin-frontend/src/layout/components/lay-sidebar/components/SidebarItem.vue new file mode 100755 index 00000000..cffa6771 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/layout/components/lay-sidebar/components/SidebarItem.vue @@ -0,0 +1,223 @@ + + + diff --git a/sop-admin/sop-admin-frontend/src/layout/components/lay-sidebar/components/SidebarLeftCollapse.vue b/sop-admin/sop-admin-frontend/src/layout/components/lay-sidebar/components/SidebarLeftCollapse.vue new file mode 100755 index 00000000..c007d3b8 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/layout/components/lay-sidebar/components/SidebarLeftCollapse.vue @@ -0,0 +1,73 @@ + + + + + diff --git a/sop-admin/sop-admin-frontend/src/layout/components/lay-sidebar/components/SidebarLinkItem.vue b/sop-admin/sop-admin-frontend/src/layout/components/lay-sidebar/components/SidebarLinkItem.vue new file mode 100755 index 00000000..8911c122 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/layout/components/lay-sidebar/components/SidebarLinkItem.vue @@ -0,0 +1,32 @@ + + + diff --git a/sop-admin/sop-admin-frontend/src/layout/components/lay-sidebar/components/SidebarLogo.vue b/sop-admin/sop-admin-frontend/src/layout/components/lay-sidebar/components/SidebarLogo.vue new file mode 100755 index 00000000..0441f52f --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/layout/components/lay-sidebar/components/SidebarLogo.vue @@ -0,0 +1,72 @@ + + + + + diff --git a/sop-admin/sop-admin-frontend/src/layout/components/lay-sidebar/components/SidebarTopCollapse.vue b/sop-admin/sop-admin-frontend/src/layout/components/lay-sidebar/components/SidebarTopCollapse.vue new file mode 100755 index 00000000..c2f1b5ad --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/layout/components/lay-sidebar/components/SidebarTopCollapse.vue @@ -0,0 +1,38 @@ + + + diff --git a/sop-admin/sop-admin-frontend/src/layout/components/lay-tag/components/TagChrome.vue b/sop-admin/sop-admin-frontend/src/layout/components/lay-tag/components/TagChrome.vue new file mode 100755 index 00000000..137365b4 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/layout/components/lay-tag/components/TagChrome.vue @@ -0,0 +1,33 @@ + diff --git a/sop-admin/sop-admin-frontend/src/layout/components/lay-tag/index.scss b/sop-admin/sop-admin-frontend/src/layout/components/lay-tag/index.scss new file mode 100755 index 00000000..b8812169 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/layout/components/lay-tag/index.scss @@ -0,0 +1,371 @@ +@keyframes schedule-in-width { + from { + width: 0; + } + + to { + width: 100%; + } +} + +@keyframes schedule-out-width { + from { + width: 100%; + } + + to { + width: 0; + } +} + +.tags-view { + position: relative; + display: flex; + align-items: center; + width: 100%; + font-size: 14px; + color: var(--el-text-color-primary); + background: #fff; + box-shadow: 0 0 1px #888; + + .scroll-item { + position: relative; + display: inline-block; + height: 34px; + padding-left: 6px; + line-height: 34px; + cursor: pointer; + transition: all 0.4s; + + &:not(:first-child) { + padding-right: 24px; + } + + &.chrome-item { + padding-right: 0; + padding-left: 0; + margin-right: -18px; + box-shadow: none; + } + + .el-icon-close { + position: absolute; + top: 50%; + display: inline-flex; + align-items: center; + justify-content: center; + width: 18px; + height: 18px; + color: var(--el-color-primary); + cursor: pointer; + border-radius: 4px; + transition: + background-color 0.12s, + color 0.12s; + transform: translate(0, -50%); + + &:hover { + color: rgb(0 0 0 / 88%) !important; + background-color: rgb(0 0 0 / 6%); + } + } + } + + .tag-title { + padding: 0 4px; + color: var(--el-text-color-primary); + text-decoration: none; + } + + .scroll-container { + position: relative; + flex: 1; + overflow: hidden; + white-space: nowrap; + + &.chrome-scroll-container { + padding-top: 4px; + + .fixed-tag { + padding: 0 !important; + } + } + + .tab { + position: relative; + float: left; + overflow: visible; + white-space: nowrap; + list-style: none; + + .scroll-item { + transition: all 0.2s cubic-bezier(0.645, 0.045, 0.355, 1); + + &:nth-child(1) { + padding: 0 12px; + } + + &.chrome-item { + &:nth-child(1) { + padding: 0; + } + } + } + + .fixed-tag { + padding: 0 12px; + } + } + } + + /* 右键菜单 */ + .contextmenu { + position: absolute; + padding: 5px 0; + margin: 0; + font-size: 13px; + font-weight: normal; + color: var(--el-text-color-primary); + white-space: nowrap; + list-style-type: none; + background: #fff; + border-radius: 4px; + outline: 0; + box-shadow: 0 2px 8px rgb(0 0 0 / 15%); + + li { + display: flex; + align-items: center; + width: 100%; + padding: 7px 12px; + margin: 0; + cursor: pointer; + + &:hover { + color: var(--el-color-primary); + } + + svg { + display: block; + margin-right: 0.5em; + } + } + } +} + +.el-dropdown-menu { + li { + display: flex; + align-items: center; + width: 100%; + margin: 0; + cursor: pointer; + + svg { + display: block; + margin-right: 0.5em; + } + } +} + +.el-dropdown-menu__item:not(.is-disabled):hover { + color: #606266; + background: #f0f0f0; +} + +:deep(.el-dropdown-menu__item) i { + margin-right: 10px; +} + +:deep(.el-dropdown-menu__item--divided) { + margin: 1px 0; +} + +.el-dropdown-menu__item--divided::before { + margin: 0; +} + +.el-dropdown-menu__item.is-disabled { + cursor: not-allowed; +} + +.scroll-item.is-active { + position: relative; + color: #fff; + box-shadow: 0 0 0.7px #888; + + .chrome-tab { + z-index: 10; + } + + .chrome-tab__bg { + color: var(--el-color-primary-light-9) !important; + } + + .tag-title { + color: var(--el-color-primary) !important; + } + + .chrome-close-btn { + color: var(--el-color-primary); + + &:hover { + background-color: var(--el-color-primary); + } + } + + .chrome-tab-divider { + opacity: 0; + } +} + +.arrow-left, +.arrow-right, +.arrow-down { + position: relative; + display: flex; + align-items: center; + justify-content: center; + width: 40px; + height: 34px; + color: var(--el-text-color-primary); + + svg { + width: 20px; + height: 20px; + } +} + +.arrow-left { + box-shadow: 5px 0 5px -6px #ccc; + + &:hover { + cursor: w-resize; + } +} + +.arrow-right { + border-right: 0.5px solid #ccc; + box-shadow: -5px 0 5px -6px #ccc; + + &:hover { + cursor: e-resize; + } +} + +/* 卡片模式下鼠标移入显示蓝色边框 */ +.card-in { + color: var(--el-color-primary); + + .tag-title { + color: var(--el-color-primary); + } +} + +/* 卡片模式下鼠标移出隐藏蓝色边框 */ +.card-out { + color: #666; + border: none; + + .tag-title { + color: #666; + } +} + +/* 灵动模式 */ +.schedule-active { + position: absolute; + bottom: 0; + left: 0; + width: 100%; + height: 2px; + background: var(--el-color-primary); +} + +/* 灵动模式下鼠标移入显示蓝色进度条 */ +.schedule-in { + position: absolute; + bottom: 0; + left: 0; + width: 100%; + height: 2px; + background: var(--el-color-primary); + animation: schedule-in-width 200ms ease-in; +} + +/* 灵动模式下鼠标移出隐藏蓝色进度条 */ +.schedule-out { + position: absolute; + bottom: 0; + left: 0; + width: 0; + height: 2px; + background: var(--el-color-primary); + animation: schedule-out-width 200ms ease-in; +} + +/* 谷歌风格的页签 */ +.chrome-tab { + position: relative; + display: inline-flex; + gap: 16px; + align-items: center; + justify-content: center; + padding: 0 24px; + white-space: nowrap; + cursor: pointer; + + .tag-title { + padding: 0; + } + + .chrome-tab-divider { + position: absolute; + right: 7px; + width: 1px; + height: 14px; + background-color: #2b2d2f; + } + + &:hover { + z-index: 10; + + .chrome-tab__bg { + color: #dee1e6; + } + + .tag-title { + color: #1f1f1f; + } + + .chrome-tab-divider { + opacity: 0; + } + } + + .chrome-tab__bg { + position: absolute; + top: 0; + left: 0; + z-index: -10; + width: 100%; + height: 100%; + color: transparent; + pointer-events: none; + } + + .chrome-close-btn { + display: inline-flex; + align-items: center; + justify-content: center; + width: 16px; + height: 16px; + color: #666; + border-radius: 50%; + + &:hover { + color: white; + background-color: #b1b3b8; + } + } +} diff --git a/sop-admin/sop-admin-frontend/src/layout/components/lay-tag/index.vue b/sop-admin/sop-admin-frontend/src/layout/components/lay-tag/index.vue new file mode 100755 index 00000000..87f234fd --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/layout/components/lay-tag/index.vue @@ -0,0 +1,685 @@ + + + + + diff --git a/sop-admin/sop-admin-frontend/src/layout/frame.vue b/sop-admin/sop-admin-frontend/src/layout/frame.vue new file mode 100755 index 00000000..4243b57d --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/layout/frame.vue @@ -0,0 +1,97 @@ + + +