Commit 2bd2cb56a16da28d97054efad0abb3d58cd4ea76

Authored by YunaiV
2 parents 412c017f 3cee922e

Merge branch 'master' of https://gitee.com/zhijiantianya/ruoyi-vue-pro into feature/1.8.0-uniapp

 Conflicts:
	yudao-module-mall/yudao-module-trade-biz/pom.xml
Showing 180 changed files with 3840 additions and 1011 deletions
README.md
@@ -23,6 +23,8 @@ @@ -23,6 +23,8 @@
23 > 23 >
24 > 😜 给项目点点 Star 吧,这对我们真的很重要! 24 > 😜 给项目点点 Star 吧,这对我们真的很重要!
25 25
  26 +![架构图](https://static.iocoder.cn/ruoyi-vue-pro-architecture.png)
  27 +
26 * 管理后台的 Vue3 版本采用 [vue-element-plus-admin](https://gitee.com/kailong110120130/vue-element-plus-admin) ,Vue2 版本采用 [vue-element-admin](https://github.com/PanJiaChen/vue-element-admin) 28 * 管理后台的 Vue3 版本采用 [vue-element-plus-admin](https://gitee.com/kailong110120130/vue-element-plus-admin) ,Vue2 版本采用 [vue-element-admin](https://github.com/PanJiaChen/vue-element-admin)
27 * 管理后台的移动端采用 [uni-app](https://github.com/dcloudio/uni-app) 方案,一份代码多终端适配,同时支持 APP、小程序、H5! 29 * 管理后台的移动端采用 [uni-app](https://github.com/dcloudio/uni-app) 方案,一份代码多终端适配,同时支持 APP、小程序、H5!
28 * 后端采用 Spring Boot、MySQL + MyBatis Plus、Redis + Redisson 30 * 后端采用 Spring Boot、MySQL + MyBatis Plus、Redis + Redisson
@@ -42,16 +44,19 @@ @@ -42,16 +44,19 @@
42 | `yudao-cloud` | Spring Cloud 微服务 | **[Gitee](https://gitee.com/zhijiantianya/yudao-cloud)**     [Github](https://github.com/YunaiV/yudao-cloud) | 44 | `yudao-cloud` | Spring Cloud 微服务 | **[Gitee](https://gitee.com/zhijiantianya/yudao-cloud)**     [Github](https://github.com/YunaiV/yudao-cloud) |
43 | `Spring-Boot-Labs` | Spring Boot & Cloud 入门 | **[Gitee](https://gitee.com/zhijiantianya/SpringBoot-Labs)**     [Github](https://github.com/YunaiV/SpringBoot-Labs) | 45 | `Spring-Boot-Labs` | Spring Boot & Cloud 入门 | **[Gitee](https://gitee.com/zhijiantianya/SpringBoot-Labs)**     [Github](https://github.com/YunaiV/SpringBoot-Labs) |
44 46
45 -  
46 ## 🐼 内置功能 47 ## 🐼 内置功能
47 48
48 -分成多种内置功能: 49 +系统内置多种多种业务功能,可以用于快速你的业务系统:
  50 +
  51 +![功能分层](https://static.iocoder.cn/ruoyi-vue-pro-biz.png)
  52 +
49 * 系统功能 53 * 系统功能
50 * 基础设施 54 * 基础设施
51 * 工作流程 55 * 工作流程
52 * 支付系统 56 * 支付系统
53 -* 商城系统 57 +* 会员中心
54 * 数据报表 58 * 数据报表
  59 +* 商城系统
55 60
56 > 友情提示:本项目基于 RuoYi-Vue 修改,**重构优化**后端的代码,**美化**前端的界面。 61 > 友情提示:本项目基于 RuoYi-Vue 修改,**重构优化**后端的代码,**美化**前端的界面。
57 > 62 >
@@ -169,29 +174,29 @@ ps:核心功能已经实现,正在对接微信小程序中... @@ -169,29 +174,29 @@ ps:核心功能已经实现,正在对接微信小程序中...
169 174
170 ### 后端 175 ### 后端
171 176
172 -| 框架 | 说明 | 版本 | 学习指南 |  
173 -|---------------------------------------------------------------------------------------------|-----------------------|---------|----------------------------------------------------------------|  
174 -| [Spring Boot](https://spring.io/projects/spring-boot) | 应用开发框架 | 2.6.10 | [文档](https://github.com/YunaiV/SpringBoot-Labs) |  
175 -| [MySQL](https://www.mysql.com/cn/) | 数据库服务器 | 5.7 | |  
176 -| [Druid](https://github.com/alibaba/druid) | JDBC 连接池、监控组件 | 1.2.11 | [文档](http://www.iocoder.cn/Spring-Boot/datasource-pool/?yudao) |  
177 -| [MyBatis Plus](https://mp.baomidou.com/) | MyBatis 增强工具包 | 3.5.2 | [文档](http://www.iocoder.cn/Spring-Boot/MyBatis/?yudao) |  
178 -| [Dynamic Datasource](https://dynamic-datasource.com/) | 动态数据源 | 3.5.0 | [文档](http://www.iocoder.cn/Spring-Boot/datasource-pool/?yudao) |  
179 -| [Redis](https://redis.io/) | key-value 数据库 | 5.0 | |  
180 -| [Redisson](https://github.com/redisson/redisson) | Redis 客户端 | 3.17.4 | [文档](http://www.iocoder.cn/Spring-Boot/Redis/?yudao) |  
181 -| [Spring MVC](https://github.com/spring-projects/spring-framework/tree/master/spring-webmvc) | MVC 框架 | 5.3.20 | [文档](http://www.iocoder.cn/SpringMVC/MVC/?yudao) |  
182 -| [Spring Security](https://github.com/spring-projects/spring-security) | Spring 安全框架 | 5.6.5 | [文档](http://www.iocoder.cn/Spring-Boot/Spring-Security/?yudao) |  
183 -| [Hibernate Validator](https://github.com/hibernate/hibernate-validator) | 参数校验组件 | 6.2.3 | [文档](http://www.iocoder.cn/Spring-Boot/Validation/?yudao) |  
184 -| [Flowable](https://github.com/flowable/flowable-engine) | 工作流引擎 | 6.7.2 | [文档](https://doc.iocoder.cn/bpm/) |  
185 -| [Quartz](https://github.com/quartz-scheduler) | 任务调度组件 | 2.3.2 | [文档](http://www.iocoder.cn/Spring-Boot/Job/?yudao) |  
186 -| [Knife4j](https://gitee.com/xiaoym/knife4j) | Swagger 增强 UI 实现 | 3.0.3 | [文档](http://www.iocoder.cn/Spring-Boot/Swagger/?yudao) |  
187 -| [Resilience4j](https://github.com/resilience4j/resilience4j) | 服务保障组件 | 1.7.1 | [文档](http://www.iocoder.cn/Spring-Boot/Resilience4j/?yudao) |  
188 -| [SkyWalking](https://skywalking.apache.org/) | 分布式应用追踪系统 | 8.5.0 | [文档](http://www.iocoder.cn/Spring-Boot/SkyWalking/?yudao) |  
189 -| [Spring Boot Admin](https://github.com/codecentric/spring-boot-admin) | Spring Boot 监控平台 | 2.6.7 | [文档](http://www.iocoder.cn/Spring-Boot/Admin/?yudao) |  
190 -| [Jackson](https://github.com/FasterXML/jackson) | JSON 工具库 | 2.13.3 | |  
191 -| [MapStruct](https://mapstruct.org/) | Java Bean 转换 | 1.4.1 | [文档](http://www.iocoder.cn/Spring-Boot/MapStruct/?yudao) |  
192 -| [Lombok](https://projectlombok.org/) | 消除冗长的 Java 代码 | 1.16.14 | [文档](http://www.iocoder.cn/Spring-Boot/Lombok/?yudao) |  
193 -| [JUnit](https://junit.org/junit5/) | Java 单元测试框架 | 5.8.2 | - |  
194 -| [Mockito](https://github.com/mockito/mockito) | Java Mock 框架 | 4.0.0 | - | 177 +| 框架 | 说明 | 版本 | 学习指南 |
  178 +|---------------------------------------------------------------------------------------------|-----------------------|------------------|----------------------------------------------------------------|
  179 +| [Spring Boot](https://spring.io/projects/spring-boot) | 应用开发框架 | 2.6.12 | [文档](https://github.com/YunaiV/SpringBoot-Labs) |
  180 +| [MySQL](https://www.mysql.com/cn/) | 数据库服务器 | 5.7 / 8.0+ | |
  181 +| [Druid](https://github.com/alibaba/druid) | JDBC 连接池、监控组件 | 1.2.13-SNSAPSHOT | [文档](http://www.iocoder.cn/Spring-Boot/datasource-pool/?yudao) |
  182 +| [MyBatis Plus](https://mp.baomidou.com/) | MyBatis 增强工具包 | 3.5.2 | [文档](http://www.iocoder.cn/Spring-Boot/MyBatis/?yudao) |
  183 +| [Dynamic Datasource](https://dynamic-datasource.com/) | 动态数据源 | 3.5.2 | [文档](http://www.iocoder.cn/Spring-Boot/datasource-pool/?yudao) |
  184 +| [Redis](https://redis.io/) | key-value 数据库 | 5.0 | |
  185 +| [Redisson](https://github.com/redisson/redisson) | Redis 客户端 | 3.17.7 | [文档](http://www.iocoder.cn/Spring-Boot/Redis/?yudao) |
  186 +| [Spring MVC](https://github.com/spring-projects/spring-framework/tree/master/spring-webmvc) | MVC 框架 | 5.3.20 | [文档](http://www.iocoder.cn/SpringMVC/MVC/?yudao) |
  187 +| [Spring Security](https://github.com/spring-projects/spring-security) | Spring 安全框架 | 5.6.5 | [文档](http://www.iocoder.cn/Spring-Boot/Spring-Security/?yudao) |
  188 +| [Hibernate Validator](https://github.com/hibernate/hibernate-validator) | 参数校验组件 | 6.2.3 | [文档](http://www.iocoder.cn/Spring-Boot/Validation/?yudao) |
  189 +| [Flowable](https://github.com/flowable/flowable-engine) | 工作流引擎 | 6.7.2 | [文档](https://doc.iocoder.cn/bpm/) |
  190 +| [Quartz](https://github.com/quartz-scheduler) | 任务调度组件 | 2.3.2 | [文档](http://www.iocoder.cn/Spring-Boot/Job/?yudao) |
  191 +| [Knife4j](https://gitee.com/xiaoym/knife4j) | Swagger 增强 UI 实现 | 3.0.3 | [文档](http://www.iocoder.cn/Spring-Boot/Swagger/?yudao) |
  192 +| [Resilience4j](https://github.com/resilience4j/resilience4j) | 服务保障组件 | 1.7.1 | [文档](http://www.iocoder.cn/Spring-Boot/Resilience4j/?yudao) |
  193 +| [SkyWalking](https://skywalking.apache.org/) | 分布式应用追踪系统 | 8.5.0 | [文档](http://www.iocoder.cn/Spring-Boot/SkyWalking/?yudao) |
  194 +| [Spring Boot Admin](https://github.com/codecentric/spring-boot-admin) | Spring Boot 监控平台 | 2.6.9 | [文档](http://www.iocoder.cn/Spring-Boot/Admin/?yudao) |
  195 +| [Jackson](https://github.com/FasterXML/jackson) | JSON 工具库 | 2.13.3 | |
  196 +| [MapStruct](https://mapstruct.org/) | Java Bean 转换 | 1.5.2.Final | [文档](http://www.iocoder.cn/Spring-Boot/MapStruct/?yudao) |
  197 +| [Lombok](https://projectlombok.org/) | 消除冗长的 Java 代码 | 1.18.24 | [文档](http://www.iocoder.cn/Spring-Boot/Lombok/?yudao) |
  198 +| [JUnit](https://junit.org/junit5/) | Java 单元测试框架 | 5.8.2 | - |
  199 +| [Mockito](https://github.com/mockito/mockito) | Java Mock 框架 | 4.8.0 | - |
195 200
196 ### [管理后台 Vue2 前端](./yudao-ui-admin) 201 ### [管理后台 Vue2 前端](./yudao-ui-admin)
197 202
@@ -204,14 +209,14 @@ ps:核心功能已经实现,正在对接微信小程序中... @@ -204,14 +209,14 @@ ps:核心功能已经实现,正在对接微信小程序中...
204 209
205 | 框架 | 说明 | 版本 | 210 | 框架 | 说明 | 版本 |
206 |----------------------------------------------------------------------|-----------------|--------| 211 |----------------------------------------------------------------------|-----------------|--------|
207 -| [Vue](https://staging-cn.vuejs.org/) | Vue 框架 | 3.2.37 |  
208 -| [Vite](https://cn.vitejs.dev//) | 开发与构建工具 | 3.0.4 |  
209 -| [Element Plus](https://element-plus.org/zh-CN/) | Element Plus | 2.2.12 |  
210 -| [TypeScript](https://www.typescriptlang.org/docs/) | TypeScript | 4.7.4 |  
211 -| [pinia](https://pinia.vuejs.org/) | vuex5 | 2.0.17 | 212 +| [Vue](https://staging-cn.vuejs.org/) | Vue 框架 | 3.2.41 |
  213 +| [Vite](https://cn.vitejs.dev//) | 开发与构建工具 | 3.1.8 |
  214 +| [Element Plus](https://element-plus.org/zh-CN/) | Element Plus | 2.2.18 |
  215 +| [TypeScript](https://www.typescriptlang.org/docs/) | TypeScript | 4.8.4 |
  216 +| [pinia](https://pinia.vuejs.org/) | vuex5 | 2.0.23 |
212 | [vue-i18n](https://kazupon.github.io/vue-i18n/zh/introduction.html/) | 国际化 | 9.2.2 | 217 | [vue-i18n](https://kazupon.github.io/vue-i18n/zh/introduction.html/) | 国际化 | 9.2.2 |
213 | [windicss](https://cn.windicss.org/) | 下一代工具优先的 CSS 框架 | 3.5.6 | 218 | [windicss](https://cn.windicss.org/) | 下一代工具优先的 CSS 框架 | 3.5.6 |
214 -| [iconify](https://icon-sets.iconify.design/) | 在线图标库 | 2.2.1 | 219 +| [iconify](https://icon-sets.iconify.design/) | 在线图标库 | 3.0.0 |
215 220
216 ### [管理后台 uni-app 跨端](./yudao-ui-admin-uniapp) 221 ### [管理后台 uni-app 跨端](./yudao-ui-admin-uniapp)
217 222
@@ -12,7 +12,7 @@ @@ -12,7 +12,7 @@
12 <module>yudao-framework</module> 12 <module>yudao-framework</module>
13 <!-- Server 主项目 --> 13 <!-- Server 主项目 -->
14 <module>yudao-server</module> 14 <module>yudao-server</module>
15 - <!-- 各种 module 拓展 --> 15 + <!-- 各种 module 拓展 -->
16 <module>yudao-module-member</module> 16 <module>yudao-module-member</module>
17 <module>yudao-module-bpm</module> 17 <module>yudao-module-bpm</module>
18 <module>yudao-module-system</module> 18 <module>yudao-module-system</module>
@@ -20,6 +20,8 @@ @@ -20,6 +20,8 @@
20 <module>yudao-module-pay</module> 20 <module>yudao-module-pay</module>
21 <module>yudao-module-mall</module> 21 <module>yudao-module-mall</module>
22 <module>yudao-module-visualization</module> 22 <module>yudao-module-visualization</module>
  23 + <!-- 示例项目 -->
  24 + <module>yudao-example</module>
23 </modules> 25 </modules>
24 26
25 <name>${project.artifactId}</name> 27 <name>${project.artifactId}</name>
@@ -35,8 +37,8 @@ @@ -35,8 +37,8 @@
35 <maven-surefire-plugin.version>3.0.0-M5</maven-surefire-plugin.version> 37 <maven-surefire-plugin.version>3.0.0-M5</maven-surefire-plugin.version>
36 <maven-compiler-plugin.version>3.8.0</maven-compiler-plugin.version> 38 <maven-compiler-plugin.version>3.8.0</maven-compiler-plugin.version>
37 <!-- 看看咋放到 bom 里 --> 39 <!-- 看看咋放到 bom 里 -->
38 - <lombok.version>1.18.20</lombok.version>  
39 - <mapstruct.version>1.4.1.Final</mapstruct.version> 40 + <lombok.version>1.18.24</lombok.version>
  41 + <mapstruct.version>1.5.2.Final</mapstruct.version>
40 <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> 42 <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
41 </properties> 43 </properties>
42 44
yudao-dependencies/pom.xml
@@ -16,58 +16,58 @@ @@ -16,58 +16,58 @@
16 <properties> 16 <properties>
17 <revision>1.6.4-snapshot</revision> 17 <revision>1.6.4-snapshot</revision>
18 <!-- 统一依赖管理 --> 18 <!-- 统一依赖管理 -->
19 - <spring.boot.version>2.6.10</spring.boot.version> 19 + <spring.boot.version>2.6.12</spring.boot.version>
20 <!-- Web 相关 --> 20 <!-- Web 相关 -->
21 <knife4j.version>3.0.3</knife4j.version> 21 <knife4j.version>3.0.3</knife4j.version>
22 - <swagger-annotations.version>1.6.6</swagger-annotations.version> 22 + <swagger-annotations.version>1.6.7</swagger-annotations.version>
23 <servlet.versoin>2.5</servlet.versoin> 23 <servlet.versoin>2.5</servlet.versoin>
24 <!-- DB 相关 --> 24 <!-- DB 相关 -->
25 - <druid.version>1.2.11</druid.version> 25 + <druid.version>1.2.13-SNSAPSHOT</druid.version>
26 <mybatis-plus.version>3.5.2</mybatis-plus.version> 26 <mybatis-plus.version>3.5.2</mybatis-plus.version>
27 <mybatis-plus-generator.version>3.5.2</mybatis-plus-generator.version> 27 <mybatis-plus-generator.version>3.5.2</mybatis-plus-generator.version>
28 - <dynamic-datasource.version>3.5.0</dynamic-datasource.version>  
29 - <redisson.version>3.17.4</redisson.version> 28 + <dynamic-datasource.version>3.5.2</dynamic-datasource.version>
  29 + <redisson.version>3.17.7</redisson.version>
30 <!-- Config 配置中心相关 --> 30 <!-- Config 配置中心相关 -->
31 - <apollo.version>1.9.2</apollo.version> 31 + <apollo.version>2.0.1</apollo.version>
32 <!-- 服务保障相关 --> 32 <!-- 服务保障相关 -->
33 - <lock4j.version>2.2.0</lock4j.version> 33 + <lock4j.version>2.2.2</lock4j.version>
34 <resilience4j.version>1.7.1</resilience4j.version> 34 <resilience4j.version>1.7.1</resilience4j.version>
35 <!-- 监控相关 --> 35 <!-- 监控相关 -->
36 - <skywalking.version>8.7.0</skywalking.version>  
37 - <spring-boot-admin.version>2.6.7</spring-boot-admin.version>  
38 - <opentracing.version>0.31.0</opentracing.version> 36 + <skywalking.version>8.12.0</skywalking.version>
  37 + <spring-boot-admin.version>2.6.9</spring-boot-admin.version>
  38 + <opentracing.version>0.33.0</opentracing.version>
39 <!-- Test 测试相关 --> 39 <!-- Test 测试相关 -->
40 - <podam.version>7.2.6.RELEASE</podam.version>  
41 - <jedis-mock.version>0.1.16</jedis-mock.version>  
42 - <mockito-inline.version>4.0.0</mockito-inline.version> 40 + <podam.version>7.2.9.RELEASE</podam.version>
  41 + <jedis-mock.version>1.0.4</jedis-mock.version>
  42 + <mockito-inline.version>4.8.0</mockito-inline.version>
43 <!-- Bpm 工作流相关 --> 43 <!-- Bpm 工作流相关 -->
44 <flowable.version>6.7.2</flowable.version> 44 <flowable.version>6.7.2</flowable.version>
45 <!-- 工具类相关 --> 45 <!-- 工具类相关 -->
46 <jasypt-spring-boot-starter.version>3.0.4</jasypt-spring-boot-starter.version> 46 <jasypt-spring-boot-starter.version>3.0.4</jasypt-spring-boot-starter.version>
47 - <lombok.version>1.18.20</lombok.version>  
48 - <mapstruct.version>1.4.1.Final</mapstruct.version>  
49 - <hutool.version>5.8.5</hutool.version> 47 + <lombok.version>1.18.24</lombok.version>
  48 + <mapstruct.version>1.5.2.Final</mapstruct.version>
  49 + <hutool.version>5.8.8</hutool.version>
50 <easyexcel.verion>3.1.1</easyexcel.verion> 50 <easyexcel.verion>3.1.1</easyexcel.verion>
51 <velocity.version>2.3</velocity.version> 51 <velocity.version>2.3</velocity.version>
52 <screw.version>1.0.5</screw.version> 52 <screw.version>1.0.5</screw.version>
53 - <fastjson.version>1.2.83</fastjson.version>  
54 - <guava.version>30.1.1-jre</guava.version> 53 + <fastjson.version>1.2.83</fastjson.version>
  54 + <guava.version>31.1-jre</guava.version>
55 <guice.version>5.1.0</guice.version> 55 <guice.version>5.1.0</guice.version>
56 - <transmittable-thread-local.version>2.12.2</transmittable-thread-local.version> 56 + <transmittable-thread-local.version>2.14.0</transmittable-thread-local.version>
57 <commons-net.version>3.8.0</commons-net.version> 57 <commons-net.version>3.8.0</commons-net.version>
58 <jsch.version>0.1.55</jsch.version> 58 <jsch.version>0.1.55</jsch.version>
59 - <tika-core.version>2.4.1</tika-core.version> 59 + <tika-core.version>2.5.0</tika-core.version>
60 <aj-captcha.version>1.3.0</aj-captcha.version> 60 <aj-captcha.version>1.3.0</aj-captcha.version>
61 <netty-all.version>4.1.82.Final</netty-all.version> 61 <netty-all.version>4.1.82.Final</netty-all.version>
62 <!-- 三方云服务相关 --> 62 <!-- 三方云服务相关 -->
63 <minio.version>8.2.2</minio.version> 63 <minio.version>8.2.2</minio.version>
64 - <aliyun-java-sdk-core.version>4.6.0</aliyun-java-sdk-core.version> 64 + <aliyun-java-sdk-core.version>4.6.2</aliyun-java-sdk-core.version>
65 <aliyun-java-sdk-dysmsapi.version>2.2.1</aliyun-java-sdk-dysmsapi.version> 65 <aliyun-java-sdk-dysmsapi.version>2.2.1</aliyun-java-sdk-dysmsapi.version>
66 <tencentcloud-sdk-java.version>3.1.561</tencentcloud-sdk-java.version> 66 <tencentcloud-sdk-java.version>3.1.561</tencentcloud-sdk-java.version>
67 <yunpian-java-sdk.version>1.2.7</yunpian-java-sdk.version> 67 <yunpian-java-sdk.version>1.2.7</yunpian-java-sdk.version>
68 <justauth.version>1.4.0</justauth.version> 68 <justauth.version>1.4.0</justauth.version>
69 - <jimureport.version>1.5.2</jimureport.version>  
70 - <xercesImpl.version>2.12.0</xercesImpl.version> 69 + <jimureport.version>1.5.3</jimureport.version>
  70 + <xercesImpl.version>2.12.2</xercesImpl.version>
71 </properties> 71 </properties>
72 72
73 <dependencyManagement> 73 <dependencyManagement>
yudao-example/pom.xml 0 → 100644
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<project xmlns="http://maven.apache.org/POM/4.0.0"
  3 + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5 + <modelVersion>4.0.0</modelVersion>
  6 +
  7 + <!-- 由于方便大家拷贝,使用不使用 yudao 作为 Maven parent -->
  8 +
  9 + <groupId>cn.iocoder.boot</groupId>
  10 + <artifactId>yudao-example</artifactId>
  11 + <version>1.0.0-snapshot</version>
  12 + <packaging>pom</packaging>
  13 + <modules>
  14 + <module>yudao-sso-demo-by-code</module>
  15 + <module>yudao-sso-demo-by-password</module>
  16 + </modules>
  17 +
  18 + <name>${project.artifactId}</name>
  19 + <description>提供各种示例,例如说:SSO 单点登录</description>
  20 + <url>https://github.com/YunaiV/ruoyi-vue-pro</url>
  21 +
  22 +</project>
yudao-example/yudao-sso-demo-by-code/pom.xml 0 → 100644
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<project xmlns="http://maven.apache.org/POM/4.0.0"
  3 + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5 + <modelVersion>4.0.0</modelVersion>
  6 +
  7 + <!-- 由于方便大家拷贝,使用不使用 yudao 作为 Maven parent -->
  8 +
  9 + <groupId>cn.iocoder.boot</groupId>
  10 + <artifactId>yudao-sso-demo-by-code</artifactId>
  11 + <version>1.0.0-snapshot</version>
  12 + <packaging>jar</packaging>
  13 +
  14 + <name>${project.artifactId}</name>
  15 + <description>基于授权码模式,如何实现 SSO 单点登录?</description>
  16 + <url>https://github.com/YunaiV/ruoyi-vue-pro</url>
  17 +
  18 + <properties>
  19 + <!-- Maven 相关 -->
  20 + <maven.compiler.source>8</maven.compiler.source>
  21 + <maven.compiler.target>8</maven.compiler.target>
  22 + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  23 + <!-- 统一依赖管理 -->
  24 + <spring.boot.version>2.6.10</spring.boot.version>
  25 + </properties>
  26 +
  27 + <dependencyManagement>
  28 + <dependencies>
  29 + <!-- 统一依赖管理 -->
  30 + <dependency>
  31 + <groupId>org.springframework.boot</groupId>
  32 + <artifactId>spring-boot-dependencies</artifactId>
  33 + <version>${spring.boot.version}</version>
  34 + <type>pom</type>
  35 + <scope>import</scope>
  36 + </dependency>
  37 + </dependencies>
  38 + </dependencyManagement>
  39 +
  40 + <dependencies>
  41 + <!-- Web 相关 -->
  42 + <dependency>
  43 + <groupId>org.springframework.boot</groupId>
  44 + <artifactId>spring-boot-starter-web</artifactId>
  45 + </dependency>
  46 +
  47 + <dependency>
  48 + <groupId>org.springframework.boot</groupId>
  49 + <artifactId>spring-boot-starter-security</artifactId>
  50 + </dependency>
  51 +
  52 + <dependency>
  53 + <groupId>cn.hutool</groupId>
  54 + <artifactId>hutool-all</artifactId>
  55 + <version>5.8.8</version>
  56 + </dependency>
  57 +
  58 + <dependency>
  59 + <groupId>org.projectlombok</groupId>
  60 + <artifactId>lombok</artifactId>
  61 + <optional>true</optional>
  62 + </dependency>
  63 + </dependencies>
  64 +
  65 +</project>
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/SSODemoApplication.java 0 → 100644
  1 +package cn.iocoder.yudao.ssodemo;
  2 +
  3 +import org.springframework.boot.SpringApplication;
  4 +import org.springframework.boot.autoconfigure.SpringBootApplication;
  5 +
  6 +@SpringBootApplication
  7 +public class SSODemoApplication {
  8 +
  9 + public static void main(String[] args) {
  10 + SpringApplication.run(SSODemoApplication.class, args);
  11 + }
  12 +
  13 +}
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/client/OAuth2Client.java 0 → 100644
  1 +package cn.iocoder.yudao.ssodemo.client;
  2 +
  3 +import cn.iocoder.yudao.ssodemo.client.dto.CommonResult;
  4 +import cn.iocoder.yudao.ssodemo.client.dto.oauth2.OAuth2AccessTokenRespDTO;
  5 +import cn.iocoder.yudao.ssodemo.client.dto.oauth2.OAuth2CheckTokenRespDTO;
  6 +import org.springframework.core.ParameterizedTypeReference;
  7 +import org.springframework.http.*;
  8 +import org.springframework.stereotype.Component;
  9 +import org.springframework.util.Assert;
  10 +import org.springframework.util.Base64Utils;
  11 +import org.springframework.util.LinkedMultiValueMap;
  12 +import org.springframework.util.MultiValueMap;
  13 +import org.springframework.web.client.RestTemplate;
  14 +
  15 +import java.nio.charset.StandardCharsets;
  16 +
  17 +/**
  18 + * OAuth 2.0 客户端
  19 + *
  20 + * 对应调用 OAuth2OpenController 接口
  21 + */
  22 +@Component
  23 +public class OAuth2Client {
  24 +
  25 + private static final String BASE_URL = "http://127.0.0.1:48080/admin-api/system/oauth2";
  26 +
  27 + /**
  28 + * 租户编号
  29 + *
  30 + * 默认使用 1;如果使用别的租户,可以调整
  31 + */
  32 + public static final Long TENANT_ID = 1L;
  33 +
  34 + private static final String CLIENT_ID = "yudao-sso-demo-by-code";
  35 + private static final String CLIENT_SECRET = "test";
  36 +
  37 +
  38 +// @Resource // 可优化,注册一个 RestTemplate Bean,然后注入
  39 + private final RestTemplate restTemplate = new RestTemplate();
  40 +
  41 + /**
  42 + * 使用 code 授权码,获得访问令牌
  43 + *
  44 + * @param code 授权码
  45 + * @param redirectUri 重定向 URI
  46 + * @return 访问令牌
  47 + */
  48 + public CommonResult<OAuth2AccessTokenRespDTO> postAccessToken(String code, String redirectUri) {
  49 + // 1.1 构建请求头
  50 + HttpHeaders headers = new HttpHeaders();
  51 + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
  52 + headers.set("tenant-id", TENANT_ID.toString());
  53 + addClientHeader(headers);
  54 + // 1.2 构建请求参数
  55 + MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
  56 + body.add("grant_type", "authorization_code");
  57 + body.add("code", code);
  58 + body.add("redirect_uri", redirectUri);
  59 +// body.add("state", ""); // 选填;填了会校验
  60 +
  61 + // 2. 执行请求
  62 + ResponseEntity<CommonResult<OAuth2AccessTokenRespDTO>> exchange = restTemplate.exchange(
  63 + BASE_URL + "/token",
  64 + HttpMethod.POST,
  65 + new HttpEntity<>(body, headers),
  66 + new ParameterizedTypeReference<CommonResult<OAuth2AccessTokenRespDTO>>() {}); // 解决 CommonResult 的泛型丢失
  67 + Assert.isTrue(exchange.getStatusCode().is2xxSuccessful(), "响应必须是 200 成功");
  68 + return exchange.getBody();
  69 + }
  70 +
  71 + /**
  72 + * 校验访问令牌,并返回它的基本信息
  73 + *
  74 + * @param token 访问令牌
  75 + * @return 访问令牌的基本信息
  76 + */
  77 + public CommonResult<OAuth2CheckTokenRespDTO> checkToken(String token) {
  78 + // 1.1 构建请求头
  79 + HttpHeaders headers = new HttpHeaders();
  80 + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
  81 + headers.set("tenant-id", TENANT_ID.toString());
  82 + addClientHeader(headers);
  83 + // 1.2 构建请求参数
  84 + MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
  85 + body.add("token", token);
  86 +
  87 + // 2. 执行请求
  88 + ResponseEntity<CommonResult<OAuth2CheckTokenRespDTO>> exchange = restTemplate.exchange(
  89 + BASE_URL + "/check-token",
  90 + HttpMethod.POST,
  91 + new HttpEntity<>(body, headers),
  92 + new ParameterizedTypeReference<CommonResult<OAuth2CheckTokenRespDTO>>() {}); // 解决 CommonResult 的泛型丢失
  93 + Assert.isTrue(exchange.getStatusCode().is2xxSuccessful(), "响应必须是 200 成功");
  94 + return exchange.getBody();
  95 + }
  96 +
  97 + /**
  98 + * 使用刷新令牌,获得(刷新)访问令牌
  99 + *
  100 + * @param refreshToken 刷新令牌
  101 + * @return 访问令牌
  102 + */
  103 + public CommonResult<OAuth2AccessTokenRespDTO> refreshToken(String refreshToken) {
  104 + // 1.1 构建请求头
  105 + HttpHeaders headers = new HttpHeaders();
  106 + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
  107 + headers.set("tenant-id", TENANT_ID.toString());
  108 + addClientHeader(headers);
  109 + // 1.2 构建请求参数
  110 + MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
  111 + body.add("grant_type", "refresh_token");
  112 + body.add("refresh_token", refreshToken);
  113 +
  114 + // 2. 执行请求
  115 + ResponseEntity<CommonResult<OAuth2AccessTokenRespDTO>> exchange = restTemplate.exchange(
  116 + BASE_URL + "/token",
  117 + HttpMethod.POST,
  118 + new HttpEntity<>(body, headers),
  119 + new ParameterizedTypeReference<CommonResult<OAuth2AccessTokenRespDTO>>() {}); // 解决 CommonResult 的泛型丢失
  120 + Assert.isTrue(exchange.getStatusCode().is2xxSuccessful(), "响应必须是 200 成功");
  121 + return exchange.getBody();
  122 + }
  123 +
  124 + /**
  125 + * 删除访问令牌
  126 + *
  127 + * @param token 访问令牌
  128 + * @return 成功
  129 + */
  130 + public CommonResult<Boolean> revokeToken(String token) {
  131 + // 1.1 构建请求头
  132 + HttpHeaders headers = new HttpHeaders();
  133 + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
  134 + headers.set("tenant-id", TENANT_ID.toString());
  135 + addClientHeader(headers);
  136 + // 1.2 构建请求参数
  137 + MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
  138 + body.add("token", token);
  139 +
  140 + // 2. 执行请求
  141 + ResponseEntity<CommonResult<Boolean>> exchange = restTemplate.exchange(
  142 + BASE_URL + "/token",
  143 + HttpMethod.DELETE,
  144 + new HttpEntity<>(body, headers),
  145 + new ParameterizedTypeReference<CommonResult<Boolean>>() {}); // 解决 CommonResult 的泛型丢失
  146 + Assert.isTrue(exchange.getStatusCode().is2xxSuccessful(), "响应必须是 200 成功");
  147 + return exchange.getBody();
  148 + }
  149 +
  150 + private static void addClientHeader(HttpHeaders headers) {
  151 + // client 拼接,需要 BASE64 编码
  152 + String client = CLIENT_ID + ":" + CLIENT_SECRET;
  153 + client = Base64Utils.encodeToString(client.getBytes(StandardCharsets.UTF_8));
  154 + headers.add("Authorization", "Basic " + client);
  155 + }
  156 +
  157 +}
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/client/UserClient.java 0 → 100644
  1 +package cn.iocoder.yudao.ssodemo.client;
  2 +
  3 +import cn.iocoder.yudao.ssodemo.client.dto.CommonResult;
  4 +import cn.iocoder.yudao.ssodemo.client.dto.user.UserInfoRespDTO;
  5 +import cn.iocoder.yudao.ssodemo.client.dto.user.UserUpdateReqDTO;
  6 +import cn.iocoder.yudao.ssodemo.framework.core.LoginUser;
  7 +import cn.iocoder.yudao.ssodemo.framework.core.util.SecurityUtils;
  8 +import org.springframework.core.ParameterizedTypeReference;
  9 +import org.springframework.http.*;
  10 +import org.springframework.stereotype.Component;
  11 +import org.springframework.util.Assert;
  12 +import org.springframework.util.LinkedMultiValueMap;
  13 +import org.springframework.util.MultiValueMap;
  14 +import org.springframework.web.client.RestTemplate;
  15 +
  16 +/**
  17 + * 用户 User 信息的客户端
  18 + *
  19 + * 对应调用 OAuth2UserController 接口
  20 + */
  21 +@Component
  22 +public class UserClient {
  23 +
  24 + private static final String BASE_URL = "http://127.0.0.1:48080/admin-api//system/oauth2/user";
  25 +
  26 + // @Resource // 可优化,注册一个 RestTemplate Bean,然后注入
  27 + private final RestTemplate restTemplate = new RestTemplate();
  28 +
  29 + public CommonResult<UserInfoRespDTO> getUser() {
  30 + // 1.1 构建请求头
  31 + HttpHeaders headers = new HttpHeaders();
  32 + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
  33 + headers.set("tenant-id", OAuth2Client.TENANT_ID.toString());
  34 + addTokenHeader(headers);
  35 + // 1.2 构建请求参数
  36 + MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
  37 +
  38 + // 2. 执行请求
  39 + ResponseEntity<CommonResult<UserInfoRespDTO>> exchange = restTemplate.exchange(
  40 + BASE_URL + "/get",
  41 + HttpMethod.GET,
  42 + new HttpEntity<>(body, headers),
  43 + new ParameterizedTypeReference<CommonResult<UserInfoRespDTO>>() {}); // 解决 CommonResult 的泛型丢失
  44 + Assert.isTrue(exchange.getStatusCode().is2xxSuccessful(), "响应必须是 200 成功");
  45 + return exchange.getBody();
  46 + }
  47 +
  48 + public CommonResult<Boolean> updateUser(UserUpdateReqDTO updateReqDTO) {
  49 + // 1.1 构建请求头
  50 + HttpHeaders headers = new HttpHeaders();
  51 + headers.setContentType(MediaType.APPLICATION_JSON);
  52 + headers.set("tenant-id", OAuth2Client.TENANT_ID.toString());
  53 + addTokenHeader(headers);
  54 + // 1.2 构建请求参数
  55 + // 使用 updateReqDTO 即可
  56 +
  57 + // 2. 执行请求
  58 + ResponseEntity<CommonResult<Boolean>> exchange = restTemplate.exchange(
  59 + BASE_URL + "/update",
  60 + HttpMethod.PUT,
  61 + new HttpEntity<>(updateReqDTO, headers),
  62 + new ParameterizedTypeReference<CommonResult<Boolean>>() {}); // 解决 CommonResult 的泛型丢失
  63 + Assert.isTrue(exchange.getStatusCode().is2xxSuccessful(), "响应必须是 200 成功");
  64 + return exchange.getBody();
  65 + }
  66 +
  67 +
  68 + private static void addTokenHeader(HttpHeaders headers) {
  69 + LoginUser loginUser = SecurityUtils.getLoginUser();
  70 + Assert.notNull(loginUser, "登录用户不能为空");
  71 + headers.add("Authorization", "Bearer " + loginUser.getAccessToken());
  72 + }
  73 +}
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/client/dto/CommonResult.java 0 → 100644
  1 +package cn.iocoder.yudao.ssodemo.client.dto;
  2 +
  3 +import lombok.Data;
  4 +
  5 +import java.io.Serializable;
  6 +
  7 +/**
  8 + * 通用返回
  9 + *
  10 + * @param <T> 数据泛型
  11 + */
  12 +@Data
  13 +public class CommonResult<T> implements Serializable {
  14 +
  15 + /**
  16 + * 错误码
  17 + */
  18 + private Integer code;
  19 + /**
  20 + * 返回数据
  21 + */
  22 + private T data;
  23 + /**
  24 + * 错误提示,用户可阅读
  25 + */
  26 + private String msg;
  27 +
  28 +}
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/client/dto/oauth2/OAuth2AccessTokenRespDTO.java 0 → 100644
  1 +package cn.iocoder.yudao.ssodemo.client.dto.oauth2;
  2 +
  3 +import com.fasterxml.jackson.annotation.JsonProperty;
  4 +import lombok.AllArgsConstructor;
  5 +import lombok.Data;
  6 +import lombok.NoArgsConstructor;
  7 +
  8 +/**
  9 + * 访问令牌 Response DTO
  10 + */
  11 +@Data
  12 +@NoArgsConstructor
  13 +@AllArgsConstructor
  14 +public class OAuth2AccessTokenRespDTO {
  15 +
  16 + /**
  17 + * 访问令牌
  18 + */
  19 + @JsonProperty("access_token")
  20 + private String accessToken;
  21 +
  22 + /**
  23 + * 刷新令牌
  24 + */
  25 + @JsonProperty("refresh_token")
  26 + private String refreshToken;
  27 +
  28 + /**
  29 + * 令牌类型
  30 + */
  31 + @JsonProperty("token_type")
  32 + private String tokenType;
  33 +
  34 + /**
  35 + * 过期时间;单位:秒
  36 + */
  37 + @JsonProperty("expires_in")
  38 + private Long expiresIn;
  39 +
  40 + /**
  41 + * 授权范围;如果多个授权范围,使用空格分隔
  42 + */
  43 + private String scope;
  44 +
  45 +}
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/client/dto/oauth2/OAuth2CheckTokenRespDTO.java 0 → 100644
  1 +package cn.iocoder.yudao.ssodemo.client.dto.oauth2;
  2 +
  3 +import com.fasterxml.jackson.annotation.JsonProperty;
  4 +import lombok.AllArgsConstructor;
  5 +import lombok.Data;
  6 +import lombok.NoArgsConstructor;
  7 +
  8 +import java.util.List;
  9 +
  10 +/**
  11 + * 校验令牌 Response DTO
  12 + *
  13 + * @author 芋道源码
  14 + */
  15 +@Data
  16 +@NoArgsConstructor
  17 +@AllArgsConstructor
  18 +public class OAuth2CheckTokenRespDTO {
  19 +
  20 + /**
  21 + * 用户编号
  22 + */
  23 + @JsonProperty("user_id")
  24 + private Long userId;
  25 + /**
  26 + * 用户类型
  27 + */
  28 + @JsonProperty("user_type")
  29 + private Integer userType;
  30 + /**
  31 + * 租户编号
  32 + */
  33 + @JsonProperty("tenant_id")
  34 + private Long tenantId;
  35 +
  36 + /**
  37 + * 客户端编号
  38 + */
  39 + @JsonProperty("client_id")
  40 + private String clientId;
  41 + /**
  42 + * 授权范围
  43 + */
  44 + private List<String> scopes;
  45 +
  46 + /**
  47 + * 访问令牌
  48 + */
  49 + @JsonProperty("access_token")
  50 + private String accessToken;
  51 +
  52 + /**
  53 + * 过期时间
  54 + *
  55 + * 时间戳 / 1000,即单位:秒
  56 + */
  57 + private Long exp;
  58 +
  59 +}
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/client/dto/user/UserInfoRespDTO.java 0 → 100644
  1 +package cn.iocoder.yudao.ssodemo.client.dto.user;
  2 +
  3 +import lombok.AllArgsConstructor;
  4 +import lombok.Data;
  5 +import lombok.NoArgsConstructor;
  6 +
  7 +import java.util.List;
  8 +
  9 +/**
  10 + * 获得用户基本信息 Response dto
  11 + */
  12 +@Data
  13 +@NoArgsConstructor
  14 +@AllArgsConstructor
  15 +public class UserInfoRespDTO {
  16 +
  17 + /**
  18 + * 用户编号
  19 + */
  20 + private Long id;
  21 +
  22 + /**
  23 + * 用户账号
  24 + */
  25 + private String username;
  26 +
  27 + /**
  28 + * 用户昵称
  29 + */
  30 + private String nickname;
  31 +
  32 + /**
  33 + * 用户邮箱
  34 + */
  35 + private String email;
  36 + /**
  37 + * 手机号码
  38 + */
  39 + private String mobile;
  40 +
  41 + /**
  42 + * 用户性别
  43 + */
  44 + private Integer sex;
  45 +
  46 + /**
  47 + * 用户头像
  48 + */
  49 + private String avatar;
  50 +
  51 + /**
  52 + * 所在部门
  53 + */
  54 + private Dept dept;
  55 +
  56 + /**
  57 + * 所属岗位数组
  58 + */
  59 + private List<Post> posts;
  60 +
  61 + /**
  62 + * 部门
  63 + */
  64 + @Data
  65 + public static class Dept {
  66 +
  67 + /**
  68 + * 部门编号
  69 + */
  70 + private Long id;
  71 +
  72 + /**
  73 + * 部门名称
  74 + */
  75 + private String name;
  76 +
  77 + }
  78 +
  79 + /**
  80 + * 岗位
  81 + */
  82 + @Data
  83 + public static class Post {
  84 +
  85 + /**
  86 + * 岗位编号
  87 + */
  88 + private Long id;
  89 +
  90 + /**
  91 + * 岗位名称
  92 + */
  93 + private String name;
  94 +
  95 + }
  96 +
  97 +}
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/client/dto/user/UserUpdateReqDTO.java 0 → 100644
  1 +package cn.iocoder.yudao.ssodemo.client.dto.user;
  2 +
  3 +import lombok.AllArgsConstructor;
  4 +import lombok.Data;
  5 +import lombok.NoArgsConstructor;
  6 +
  7 +/**
  8 + * 更新用户基本信息 Request DTO
  9 + */
  10 +@Data
  11 +@NoArgsConstructor
  12 +@AllArgsConstructor
  13 +public class UserUpdateReqDTO {
  14 +
  15 + /**
  16 + * 用户昵称
  17 + */
  18 + private String nickname;
  19 +
  20 + /**
  21 + * 用户邮箱
  22 + */
  23 + private String email;
  24 +
  25 + /**
  26 + * 手机号码
  27 + */
  28 + private String mobile;
  29 +
  30 + /**
  31 + * 用户性别
  32 + */
  33 + private Integer sex;
  34 +
  35 +}
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/controller/AuthController.java 0 → 100644
  1 +package cn.iocoder.yudao.ssodemo.controller;
  2 +
  3 +import cn.hutool.core.util.StrUtil;
  4 +import cn.iocoder.yudao.ssodemo.client.OAuth2Client;
  5 +import cn.iocoder.yudao.ssodemo.client.dto.CommonResult;
  6 +import cn.iocoder.yudao.ssodemo.client.dto.oauth2.OAuth2AccessTokenRespDTO;
  7 +import cn.iocoder.yudao.ssodemo.framework.core.util.SecurityUtils;
  8 +import org.springframework.web.bind.annotation.PostMapping;
  9 +import org.springframework.web.bind.annotation.RequestMapping;
  10 +import org.springframework.web.bind.annotation.RequestParam;
  11 +import org.springframework.web.bind.annotation.RestController;
  12 +
  13 +import javax.annotation.Resource;
  14 +import javax.servlet.http.HttpServletRequest;
  15 +
  16 +@RestController
  17 +@RequestMapping("/auth")
  18 +public class AuthController {
  19 +
  20 + @Resource
  21 + private OAuth2Client oauth2Client;
  22 +
  23 + /**
  24 + * 使用 code 访问令牌,获得访问令牌
  25 + *
  26 + * @param code 授权码
  27 + * @param redirectUri 重定向 URI
  28 + * @return 访问令牌;注意,实际项目中,最好创建对应的 ResponseVO 类,只返回必要的字段
  29 + */
  30 + @PostMapping("/login-by-code")
  31 + public CommonResult<OAuth2AccessTokenRespDTO> loginByCode(@RequestParam("code") String code,
  32 + @RequestParam("redirectUri") String redirectUri) {
  33 + return oauth2Client.postAccessToken(code, redirectUri);
  34 + }
  35 +
  36 + /**
  37 + * 使用刷新令牌,获得(刷新)访问令牌
  38 + *
  39 + * @param refreshToken 刷新令牌
  40 + * @return 访问令牌;注意,实际项目中,最好创建对应的 ResponseVO 类,只返回必要的字段
  41 + */
  42 + @PostMapping("/refresh-token")
  43 + public CommonResult<OAuth2AccessTokenRespDTO> refreshToken(@RequestParam("refreshToken") String refreshToken) {
  44 + return oauth2Client.refreshToken(refreshToken);
  45 + }
  46 +
  47 + /**
  48 + * 退出登录
  49 + *
  50 + * @param request 请求
  51 + * @return 成功
  52 + */
  53 + @PostMapping("/logout")
  54 + public CommonResult<Boolean> logout(HttpServletRequest request) {
  55 + String token = SecurityUtils.obtainAuthorization(request, "Authorization");
  56 + if (StrUtil.isNotBlank(token)) {
  57 + return oauth2Client.revokeToken(token);
  58 + }
  59 + // 返回成功
  60 + return new CommonResult<>();
  61 + }
  62 +
  63 +}
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/controller/UserController.java 0 → 100644
  1 +package cn.iocoder.yudao.ssodemo.controller;
  2 +
  3 +import cn.iocoder.yudao.ssodemo.client.UserClient;
  4 +import cn.iocoder.yudao.ssodemo.client.dto.CommonResult;
  5 +import cn.iocoder.yudao.ssodemo.client.dto.user.UserInfoRespDTO;
  6 +import cn.iocoder.yudao.ssodemo.client.dto.user.UserUpdateReqDTO;
  7 +import org.springframework.web.bind.annotation.*;
  8 +
  9 +import javax.annotation.Resource;
  10 +
  11 +@RestController
  12 +@RequestMapping("/user")
  13 +public class UserController {
  14 +
  15 + @Resource
  16 + private UserClient userClient;
  17 +
  18 + /**
  19 + * 获得当前登录用户的基本信息
  20 + *
  21 + * @return 用户信息;注意,实际项目中,最好创建对应的 ResponseVO 类,只返回必要的字段
  22 + */
  23 + @GetMapping("/get")
  24 + public CommonResult<UserInfoRespDTO> getUser() {
  25 + return userClient.getUser();
  26 + }
  27 +
  28 + /**
  29 + * 更新当前登录用户的昵称
  30 + *
  31 + * @param nickname 昵称
  32 + * @return 成功
  33 + */
  34 + @PutMapping("/update")
  35 + public CommonResult<Boolean> updateUser(@RequestParam("nickname") String nickname) {
  36 + UserUpdateReqDTO updateReqDTO = new UserUpdateReqDTO(nickname, null, null, null);
  37 + return userClient.updateUser(updateReqDTO);
  38 + }
  39 +
  40 +}
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/framework/config/SecurityConfiguration.java 0 → 100644
  1 +package cn.iocoder.yudao.ssodemo.framework.config;
  2 +
  3 +import cn.iocoder.yudao.ssodemo.framework.core.filter.TokenAuthenticationFilter;
  4 +import cn.iocoder.yudao.ssodemo.framework.core.handler.AccessDeniedHandlerImpl;
  5 +import org.springframework.context.annotation.Configuration;
  6 +import org.springframework.http.HttpMethod;
  7 +import org.springframework.security.config.annotation.web.builders.HttpSecurity;
  8 +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
  9 +import org.springframework.security.web.AuthenticationEntryPoint;
  10 +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
  11 +
  12 +import javax.annotation.Resource;
  13 +
  14 +@Configuration
  15 +public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
  16 +
  17 + @Resource
  18 + private TokenAuthenticationFilter tokenAuthenticationFilter;
  19 +
  20 + @Resource
  21 + private AccessDeniedHandlerImpl accessDeniedHandler;
  22 + @Resource
  23 + private AuthenticationEntryPoint authenticationEntryPoint;
  24 +
  25 + @Override
  26 + protected void configure(HttpSecurity httpSecurity) throws Exception {
  27 + // 设置 URL 安全权限
  28 + httpSecurity.csrf().disable() // 禁用 CSRF 保护
  29 + .authorizeRequests()
  30 + // 1. 静态资源,可匿名访问
  31 + .antMatchers(HttpMethod.GET, "/*.html", "/**/*.html", "/**/*.css", "/**/*.js").permitAll()
  32 + // 2. 登录相关的接口,可匿名访问
  33 + .antMatchers("/auth/login-by-code").permitAll()
  34 + .antMatchers("/auth/refresh-token").permitAll()
  35 + .antMatchers("/auth/logout").permitAll()
  36 + // last. 兜底规则,必须认证
  37 + .and().authorizeRequests()
  38 + .anyRequest().authenticated();
  39 +
  40 + // 设置处理器
  41 + httpSecurity.exceptionHandling().accessDeniedHandler(accessDeniedHandler)
  42 + .authenticationEntryPoint(authenticationEntryPoint);
  43 +
  44 + // 添加 Token Filter
  45 + httpSecurity.addFilterBefore(tokenAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
  46 + }
  47 +
  48 +}
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/LoginUser.java 0 → 100644
  1 +package cn.iocoder.yudao.ssodemo.framework.core;
  2 +
  3 +import lombok.Data;
  4 +
  5 +import java.util.List;
  6 +
  7 +/**
  8 + * 登录用户信息
  9 + *
  10 + * @author 芋道源码
  11 + */
  12 +@Data
  13 +public class LoginUser {
  14 +
  15 + /**
  16 + * 用户编号
  17 + */
  18 + private Long id;
  19 + /**
  20 + * 用户类型
  21 + */
  22 + private Integer userType;
  23 + /**
  24 + * 租户编号
  25 + */
  26 + private Long tenantId;
  27 + /**
  28 + * 授权范围
  29 + */
  30 + private List<String> scopes;
  31 +
  32 + /**
  33 + * 访问令牌
  34 + */
  35 + private String accessToken;
  36 +
  37 +}
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/filter/TokenAuthenticationFilter.java 0 → 100644
  1 +package cn.iocoder.yudao.ssodemo.framework.core.filter;
  2 +
  3 +import cn.iocoder.yudao.ssodemo.client.OAuth2Client;
  4 +import cn.iocoder.yudao.ssodemo.client.dto.CommonResult;
  5 +import cn.iocoder.yudao.ssodemo.client.dto.oauth2.OAuth2CheckTokenRespDTO;
  6 +import cn.iocoder.yudao.ssodemo.framework.core.LoginUser;
  7 +import cn.iocoder.yudao.ssodemo.framework.core.util.SecurityUtils;
  8 +import org.springframework.stereotype.Component;
  9 +import org.springframework.util.StringUtils;
  10 +import org.springframework.web.filter.OncePerRequestFilter;
  11 +
  12 +import javax.annotation.Resource;
  13 +import javax.servlet.FilterChain;
  14 +import javax.servlet.ServletException;
  15 +import javax.servlet.http.HttpServletRequest;
  16 +import javax.servlet.http.HttpServletResponse;
  17 +import java.io.IOException;
  18 +
  19 +/**
  20 + * Token 过滤器,验证 token 的有效性
  21 + * 验证通过后,获得 {@link LoginUser} 信息,并加入到 Spring Security 上下文
  22 + *
  23 + * @author 芋道源码
  24 + */
  25 +@Component
  26 +public class TokenAuthenticationFilter extends OncePerRequestFilter {
  27 +
  28 + @Resource
  29 + private OAuth2Client oauth2Client;
  30 +
  31 + @Override
  32 + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
  33 + FilterChain filterChain) throws ServletException, IOException {
  34 + // 1. 获得访问令牌
  35 + String token = SecurityUtils.obtainAuthorization(request, "Authorization");
  36 + if (StringUtils.hasText(token)) {
  37 + // 2. 基于 token 构建登录用户
  38 + LoginUser loginUser = buildLoginUserByToken(token);
  39 + // 3. 设置当前用户
  40 + if (loginUser != null) {
  41 + SecurityUtils.setLoginUser(loginUser, request);
  42 + }
  43 + }
  44 +
  45 + // 继续过滤链
  46 + filterChain.doFilter(request, response);
  47 + }
  48 +
  49 + private LoginUser buildLoginUserByToken(String token) {
  50 + try {
  51 + CommonResult<OAuth2CheckTokenRespDTO> accessTokenResult = oauth2Client.checkToken(token);
  52 + OAuth2CheckTokenRespDTO accessToken = accessTokenResult.getData();
  53 + if (accessToken == null) {
  54 + return null;
  55 + }
  56 + // 构建登录用户
  57 + return new LoginUser().setId(accessToken.getUserId()).setUserType(accessToken.getUserType())
  58 + .setTenantId(accessToken.getTenantId()).setScopes(accessToken.getScopes())
  59 + .setAccessToken(accessToken.getAccessToken());
  60 + } catch (Exception exception) {
  61 + // 校验 Token 不通过时,考虑到一些接口是无需登录的,所以直接返回 null 即可
  62 + return null;
  63 + }
  64 + }
  65 +
  66 +}
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/handler/AccessDeniedHandlerImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.ssodemo.framework.core.handler;
  2 +
  3 +import cn.iocoder.yudao.ssodemo.client.dto.CommonResult;
  4 +import cn.iocoder.yudao.ssodemo.framework.core.util.SecurityUtils;
  5 +import cn.iocoder.yudao.ssodemo.framework.core.util.ServletUtils;
  6 +import lombok.extern.slf4j.Slf4j;
  7 +import org.springframework.http.HttpStatus;
  8 +import org.springframework.security.access.AccessDeniedException;
  9 +import org.springframework.security.web.access.AccessDeniedHandler;
  10 +import org.springframework.security.web.access.ExceptionTranslationFilter;
  11 +import org.springframework.stereotype.Component;
  12 +
  13 +import javax.servlet.FilterChain;
  14 +import javax.servlet.ServletException;
  15 +import javax.servlet.http.HttpServletRequest;
  16 +import javax.servlet.http.HttpServletResponse;
  17 +import java.io.IOException;
  18 +
  19 +/**
  20 + * 访问一个需要认证的 URL 资源,已经认证(登录)但是没有权限的情况下,返回 {@link GlobalErrorCodeConstants#FORBIDDEN} 错误码。
  21 + *
  22 + * 补充:Spring Security 通过 {@link ExceptionTranslationFilter#handleAccessDeniedException(HttpServletRequest, HttpServletResponse, FilterChain, AccessDeniedException)} 方法,调用当前类
  23 + *
  24 + * @author 芋道源码
  25 + */
  26 +@Component
  27 +@SuppressWarnings("JavadocReference")
  28 +@Slf4j
  29 +public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
  30 +
  31 + @Override
  32 + public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e)
  33 + throws IOException, ServletException {
  34 + // 打印 warn 的原因是,不定期合并 warn,看看有没恶意破坏
  35 + log.warn("[commence][访问 URL({}) 时,用户({}) 权限不够]", request.getRequestURI(),
  36 + SecurityUtils.getLoginUserId(), e);
  37 + // 返回 403
  38 + CommonResult<Object> result = new CommonResult<>();
  39 + result.setCode(HttpStatus.FORBIDDEN.value());
  40 + result.setMsg("没有该操作权限");
  41 + ServletUtils.writeJSON(response, result);
  42 + }
  43 +
  44 +}
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/handler/AuthenticationEntryPointImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.ssodemo.framework.core.handler;
  2 +
  3 +import cn.iocoder.yudao.ssodemo.client.dto.CommonResult;
  4 +import cn.iocoder.yudao.ssodemo.framework.core.util.ServletUtils;
  5 +import lombok.extern.slf4j.Slf4j;
  6 +import org.springframework.http.HttpStatus;
  7 +import org.springframework.security.core.AuthenticationException;
  8 +import org.springframework.security.web.AuthenticationEntryPoint;
  9 +import org.springframework.security.web.access.ExceptionTranslationFilter;
  10 +import org.springframework.stereotype.Component;
  11 +
  12 +import javax.servlet.FilterChain;
  13 +import javax.servlet.http.HttpServletRequest;
  14 +import javax.servlet.http.HttpServletResponse;
  15 +
  16 +/**
  17 + * 访问一个需要认证的 URL 资源,但是此时自己尚未认证(登录)的情况下,返回 {@link GlobalErrorCodeConstants#UNAUTHORIZED} 错误码,从而使前端重定向到登录页
  18 + *
  19 + * 补充:Spring Security 通过 {@link ExceptionTranslationFilter#sendStartAuthentication(HttpServletRequest, HttpServletResponse, FilterChain, AuthenticationException)} 方法,调用当前类
  20 + */
  21 +@Component
  22 +@Slf4j
  23 +@SuppressWarnings("JavadocReference") // 忽略文档引用报错
  24 +public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
  25 +
  26 + @Override
  27 + public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) {
  28 + log.debug("[commence][访问 URL({}) 时,没有登录]", request.getRequestURI(), e);
  29 + // 返回 401
  30 + CommonResult<Object> result = new CommonResult<>();
  31 + result.setCode(HttpStatus.UNAUTHORIZED.value());
  32 + result.setMsg("账号未登录");
  33 + ServletUtils.writeJSON(response, result);
  34 + }
  35 +
  36 +}
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/util/SecurityUtils.java 0 → 100644
  1 +package cn.iocoder.yudao.ssodemo.framework.core.util;
  2 +
  3 +import cn.iocoder.yudao.ssodemo.framework.core.LoginUser;
  4 +import org.springframework.lang.Nullable;
  5 +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
  6 +import org.springframework.security.core.Authentication;
  7 +import org.springframework.security.core.context.SecurityContext;
  8 +import org.springframework.security.core.context.SecurityContextHolder;
  9 +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
  10 +import org.springframework.util.StringUtils;
  11 +
  12 +import javax.servlet.http.HttpServletRequest;
  13 +import java.util.Collections;
  14 +
  15 +/**
  16 + * 安全服务工具类
  17 + *
  18 + * @author 芋道源码
  19 + */
  20 +public class SecurityUtils {
  21 +
  22 + public static final String AUTHORIZATION_BEARER = "Bearer";
  23 +
  24 + private SecurityUtils() {}
  25 +
  26 + /**
  27 + * 从请求中,获得认证 Token
  28 + *
  29 + * @param request 请求
  30 + * @param header 认证 Token 对应的 Header 名字
  31 + * @return 认证 Token
  32 + */
  33 + public static String obtainAuthorization(HttpServletRequest request, String header) {
  34 + String authorization = request.getHeader(header);
  35 + if (!StringUtils.hasText(authorization)) {
  36 + return null;
  37 + }
  38 + int index = authorization.indexOf(AUTHORIZATION_BEARER + " ");
  39 + if (index == -1) { // 未找到
  40 + return null;
  41 + }
  42 + return authorization.substring(index + 7).trim();
  43 + }
  44 +
  45 + /**
  46 + * 获得当前认证信息
  47 + *
  48 + * @return 认证信息
  49 + */
  50 + public static Authentication getAuthentication() {
  51 + SecurityContext context = SecurityContextHolder.getContext();
  52 + if (context == null) {
  53 + return null;
  54 + }
  55 + return context.getAuthentication();
  56 + }
  57 +
  58 + /**
  59 + * 获取当前用户
  60 + *
  61 + * @return 当前用户
  62 + */
  63 + @Nullable
  64 + public static LoginUser getLoginUser() {
  65 + Authentication authentication = getAuthentication();
  66 + if (authentication == null) {
  67 + return null;
  68 + }
  69 + return authentication.getPrincipal() instanceof LoginUser ? (LoginUser) authentication.getPrincipal() : null;
  70 + }
  71 +
  72 + /**
  73 + * 获得当前用户的编号,从上下文中
  74 + *
  75 + * @return 用户编号
  76 + */
  77 + @Nullable
  78 + public static Long getLoginUserId() {
  79 + LoginUser loginUser = getLoginUser();
  80 + return loginUser != null ? loginUser.getId() : null;
  81 + }
  82 +
  83 + /**
  84 + * 设置当前用户
  85 + *
  86 + * @param loginUser 登录用户
  87 + * @param request 请求
  88 + */
  89 + public static void setLoginUser(LoginUser loginUser, HttpServletRequest request) {
  90 + // 创建 Authentication,并设置到上下文
  91 + Authentication authentication = buildAuthentication(loginUser, request);
  92 + SecurityContextHolder.getContext().setAuthentication(authentication);
  93 + }
  94 +
  95 + private static Authentication buildAuthentication(LoginUser loginUser, HttpServletRequest request) {
  96 + // 创建 UsernamePasswordAuthenticationToken 对象
  97 + UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
  98 + loginUser, null, Collections.emptyList());
  99 + authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
  100 + return authenticationToken;
  101 + }
  102 +
  103 +}
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/util/ServletUtils.java 0 → 100644
  1 +package cn.iocoder.yudao.ssodemo.framework.core.util;
  2 +
  3 +import cn.hutool.extra.servlet.ServletUtil;
  4 +import cn.hutool.json.JSONUtil;
  5 +import org.springframework.http.MediaType;
  6 +
  7 +import javax.servlet.http.HttpServletResponse;
  8 +
  9 +/**
  10 + * 客户端工具类
  11 + *
  12 + * @author 芋道源码
  13 + */
  14 +public class ServletUtils {
  15 +
  16 + /**
  17 + * 返回 JSON 字符串
  18 + *
  19 + * @param response 响应
  20 + * @param object 对象,会序列化成 JSON 字符串
  21 + */
  22 + @SuppressWarnings("deprecation") // 必须使用 APPLICATION_JSON_UTF8_VALUE,否则会乱码
  23 + public static void writeJSON(HttpServletResponse response, Object object) {
  24 + String content = JSONUtil.toJsonStr(object);
  25 + ServletUtil.write(response, content, MediaType.APPLICATION_JSON_UTF8_VALUE);
  26 + }
  27 +
  28 +}
yudao-example/yudao-sso-demo-by-code/src/main/resources/application.yaml 0 → 100644
  1 +server:
  2 + port: 18080
yudao-example/yudao-sso-demo-by-code/src/main/resources/static/callback.html 0 → 100644
  1 +<!DOCTYPE html>
  2 +<html lang="en">
  3 +<head>
  4 + <meta charset="UTF-8">
  5 + <title>SSO 授权后的回调页</title>
  6 + <!-- jQuery:操作 dom、发起请求等 -->
  7 + <script src="https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/jquery/2.1.2/jquery.min.js" type="application/javascript"></script>
  8 + <!-- 工具类 -->
  9 + <script type="application/javascript">
  10 + (function ($) {
  11 + /**
  12 + * 获得 URL 的指定参数的值
  13 + *
  14 + * @param name 参数名
  15 + * @returns 参数值
  16 + */
  17 + $.getUrlParam = function (name) {
  18 + const reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)");
  19 + const r = window.location.search.substr(1).match(reg);
  20 + if (r != null) return unescape(r[2]); return null;
  21 + }
  22 + })(jQuery);
  23 + </script>
  24 +
  25 + <script type="application/javascript">
  26 + $(function () {
  27 + // 获得 code 授权码
  28 + const code = $.getUrlParam('code');
  29 + if (!code) {
  30 + alert('获取不到 code 参数,请排查!')
  31 + return;
  32 + }
  33 +
  34 + // 提交
  35 + const redirectUri = 'http://127.0.0.1:18080/callback.html'; // 需要修改成,你回调的地址,就是在 index.html 拼接的 redirectUri
  36 + $.ajax({
  37 + url: "http://127.0.0.1:18080/auth/login-by-code?code=" + code
  38 + + '&redirectUri=' + redirectUri,
  39 + method: 'POST',
  40 + success: function( result ) {
  41 + if (result.code !== 0) {
  42 + alert('获得访问令牌失败,原因:' + result.msg)
  43 + return;
  44 + }
  45 + alert('获得访问令牌成功!点击确认,跳转回首页')
  46 +
  47 + // 设置到 localStorage 中
  48 + localStorage.setItem('ACCESS-TOKEN', result.data.access_token);
  49 + localStorage.setItem('REFRESH-TOKEN', result.data.refresh_token);
  50 +
  51 + // 跳转回首页
  52 + window.location.href = '/index.html';
  53 + }
  54 + })
  55 + })
  56 + </script>
  57 +</head>
  58 +<body>
  59 +正在使用 code 授权码,进行 accessToken 访问令牌的获取
  60 +</body>
  61 +</html>
yudao-example/yudao-sso-demo-by-code/src/main/resources/static/index.html 0 → 100644
  1 +<!DOCTYPE html>
  2 +<html lang="en">
  3 +<head>
  4 + <meta charset="UTF-8">
  5 + <title>首页</title>
  6 + <!-- jQuery:操作 dom、发起请求等 -->
  7 + <script src="https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/jquery/2.1.2/jquery.min.js" type="application/javascript"></script>
  8 +
  9 + <script type="application/javascript">
  10 +
  11 + /**
  12 + * 跳转单点登录
  13 + */
  14 + function ssoLogin() {
  15 + const clientId = 'yudao-sso-demo-by-code'; // 可以改写成,你的 clientId
  16 + const redirectUri = encodeURIComponent('http://127.0.0.1:18080/callback.html'); // 注意,需要使用 encodeURIComponent 编码地址
  17 + const responseType = 'code'; // 1)授权码模式,对应 code;2)简化模式,对应 token
  18 + window.location.href = 'http://127.0.0.1:1024/sso?client_id=' + clientId
  19 + + '&redirect_uri=' + redirectUri
  20 + + '&response_type=' + responseType;
  21 + }
  22 +
  23 + /**
  24 + * 修改昵称
  25 + */
  26 + function updateNickname() {
  27 + const nickname = prompt("请输入新的昵称", "");
  28 + if (!nickname) {
  29 + return;
  30 + }
  31 + // 更新用户的昵称
  32 + const accessToken = localStorage.getItem('ACCESS-TOKEN');
  33 + $.ajax({
  34 + url: "http://127.0.0.1:18080/user/update?nickname=" + nickname,
  35 + method: 'PUT',
  36 + headers: {
  37 + 'Authorization': 'Bearer ' + accessToken
  38 + },
  39 + success: function (result) {
  40 + if (result.code !== 0) {
  41 + alert('更新昵称失败,原因:' + result.msg)
  42 + return;
  43 + }
  44 + alert('更新昵称成功!');
  45 + $('#nicknameSpan').html(nickname);
  46 + }
  47 + });
  48 + }
  49 +
  50 + /**
  51 + * 刷新令牌
  52 + */
  53 + function refreshToken() {
  54 + const refreshToken = localStorage.getItem('REFRESH-TOKEN');
  55 + if (!refreshToken) {
  56 + alert("获取不到刷新令牌");
  57 + return;
  58 + }
  59 + $.ajax({
  60 + url: "http://127.0.0.1:18080/auth/refresh-token?refreshToken=" + refreshToken,
  61 + method: 'POST',
  62 + success: function (result) {
  63 + if (result.code !== 0) {
  64 + alert('刷新访问令牌失败,原因:' + result.msg)
  65 + return;
  66 + }
  67 + alert('更新访问令牌成功!');
  68 + $('#accessTokenSpan').html(result.data.access_token);
  69 +
  70 + // 设置到 localStorage 中
  71 + localStorage.setItem('ACCESS-TOKEN', result.data.access_token);
  72 + localStorage.setItem('REFRESH-TOKEN', result.data.refresh_token);
  73 + }
  74 + });
  75 + }
  76 +
  77 + /**
  78 + * 登出,删除访问令牌
  79 + */
  80 + function logout() {
  81 + const accessToken = localStorage.getItem('ACCESS-TOKEN');
  82 + if (!accessToken) {
  83 + location.reload();
  84 + return;
  85 + }
  86 + $.ajax({
  87 + url: "http://127.0.0.1:18080/auth/logout",
  88 + method: 'POST',
  89 + headers: {
  90 + 'Authorization': 'Bearer ' + accessToken
  91 + },
  92 + success: function (result) {
  93 + if (result.code !== 0) {
  94 + alert('退出登录失败,原因:' + result.msg)
  95 + return;
  96 + }
  97 + alert('退出登录成功!');
  98 + // 删除 localStorage 中
  99 + localStorage.removeItem('ACCESS-TOKEN');
  100 + localStorage.removeItem('REFRESH-TOKEN');
  101 +
  102 + location.reload();
  103 + }
  104 + });
  105 + }
  106 +
  107 + $(function () {
  108 + const accessToken = localStorage.getItem('ACCESS-TOKEN');
  109 + // 情况一:未登录
  110 + if (!accessToken) {
  111 + $('#noLoginDiv').css("display", "block");
  112 + return;
  113 + }
  114 +
  115 + // 情况二:已登录
  116 + $('#yesLoginDiv').css("display", "block");
  117 + $('#accessTokenSpan').html(accessToken);
  118 + // 获得登录用户的信息
  119 + $.ajax({
  120 + url: "http://127.0.0.1:18080/user/get",
  121 + method: 'GET',
  122 + headers: {
  123 + 'Authorization': 'Bearer ' + accessToken
  124 + },
  125 + success: function (result) {
  126 + if (result.code !== 0) {
  127 + alert('获得个人信息失败,原因:' + result.msg)
  128 + return;
  129 + }
  130 + $('#nicknameSpan').html(result.data.nickname);
  131 + }
  132 + });
  133 + })
  134 + </script>
  135 +</head>
  136 +<body>
  137 +<!-- 情况一:未登录:1)跳转 ruoyi-vue-pro 的 SSO 登录页 -->
  138 +<div id="noLoginDiv" style="display: none">
  139 + 您未登录,点击 <a href="#" onclick="ssoLogin()">跳转 </a> SSO 单点登录
  140 +</div>
  141 +
  142 +<!-- 情况二:已登录:1)展示用户信息;2)刷新访问令牌;3)退出登录 -->
  143 +<div id="yesLoginDiv" style="display: none">
  144 + 您已登录!<button onclick="logout()">退出登录</button> <br />
  145 + 昵称:<span id="nicknameSpan"> 加载中... </span> <button onclick="updateNickname()">修改昵称</button> <br />
  146 + 访问令牌:<span id="accessTokenSpan"> 加载中... </span> <button onclick="refreshToken()">刷新令牌</button> <br />
  147 +</div>
  148 +</body>
  149 +<style>
  150 + body { /** 页面居中 */
  151 + border-radius: 20px;
  152 + height: 350px;
  153 + position: absolute;
  154 + left: 50%;
  155 + top: 50%;
  156 + transform: translate(-50%,-50%);
  157 + }
  158 +</style>
  159 +</html>
yudao-example/yudao-sso-demo-by-password/pom.xml 0 → 100644
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<project xmlns="http://maven.apache.org/POM/4.0.0"
  3 + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5 + <modelVersion>4.0.0</modelVersion>
  6 +
  7 + <!-- 由于方便大家拷贝,使用不使用 yudao 作为 Maven parent -->
  8 +
  9 + <groupId>cn.iocoder.boot</groupId>
  10 + <artifactId>yudao-sso-demo-by-password</artifactId>
  11 + <version>1.0.0-snapshot</version>
  12 + <packaging>jar</packaging>
  13 +
  14 + <name>${project.artifactId}</name>
  15 + <description>基于密码模式,如何实现 SSO 单点登录?</description>
  16 + <url>https://github.com/YunaiV/ruoyi-vue-pro</url>
  17 +
  18 + <properties>
  19 + <!-- Maven 相关 -->
  20 + <maven.compiler.source>8</maven.compiler.source>
  21 + <maven.compiler.target>8</maven.compiler.target>
  22 + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  23 + <!-- 统一依赖管理 -->
  24 + <spring.boot.version>2.6.10</spring.boot.version>
  25 + </properties>
  26 +
  27 + <dependencyManagement>
  28 + <dependencies>
  29 + <!-- 统一依赖管理 -->
  30 + <dependency>
  31 + <groupId>org.springframework.boot</groupId>
  32 + <artifactId>spring-boot-dependencies</artifactId>
  33 + <version>${spring.boot.version}</version>
  34 + <type>pom</type>
  35 + <scope>import</scope>
  36 + </dependency>
  37 + </dependencies>
  38 + </dependencyManagement>
  39 +
  40 + <dependencies>
  41 + <!-- Web 相关 -->
  42 + <dependency>
  43 + <groupId>org.springframework.boot</groupId>
  44 + <artifactId>spring-boot-starter-web</artifactId>
  45 + </dependency>
  46 +
  47 + <dependency>
  48 + <groupId>org.springframework.boot</groupId>
  49 + <artifactId>spring-boot-starter-security</artifactId>
  50 + </dependency>
  51 +
  52 + <dependency>
  53 + <groupId>cn.hutool</groupId>
  54 + <artifactId>hutool-all</artifactId>
  55 + <version>5.8.8</version>
  56 + </dependency>
  57 +
  58 + <dependency>
  59 + <groupId>org.projectlombok</groupId>
  60 + <artifactId>lombok</artifactId>
  61 + <optional>true</optional>
  62 + </dependency>
  63 + </dependencies>
  64 +
  65 +</project>
yudao-example/yudao-sso-demo-by-password/src/main/java/cn/iocoder/yudao/ssodemo/SSODemoApplication.java 0 → 100644
  1 +package cn.iocoder.yudao.ssodemo;
  2 +
  3 +import org.springframework.boot.SpringApplication;
  4 +import org.springframework.boot.autoconfigure.SpringBootApplication;
  5 +
  6 +@SpringBootApplication
  7 +public class SSODemoApplication {
  8 +
  9 + public static void main(String[] args) {
  10 + SpringApplication.run(SSODemoApplication.class, args);
  11 + }
  12 +
  13 +}
yudao-example/yudao-sso-demo-by-password/src/main/java/cn/iocoder/yudao/ssodemo/client/OAuth2Client.java 0 → 100644
  1 +package cn.iocoder.yudao.ssodemo.client;
  2 +
  3 +import cn.iocoder.yudao.ssodemo.client.dto.CommonResult;
  4 +import cn.iocoder.yudao.ssodemo.client.dto.oauth2.OAuth2AccessTokenRespDTO;
  5 +import cn.iocoder.yudao.ssodemo.client.dto.oauth2.OAuth2CheckTokenRespDTO;
  6 +import org.springframework.core.ParameterizedTypeReference;
  7 +import org.springframework.http.*;
  8 +import org.springframework.stereotype.Component;
  9 +import org.springframework.util.Assert;
  10 +import org.springframework.util.Base64Utils;
  11 +import org.springframework.util.LinkedMultiValueMap;
  12 +import org.springframework.util.MultiValueMap;
  13 +import org.springframework.web.client.RestTemplate;
  14 +
  15 +import java.nio.charset.StandardCharsets;
  16 +
  17 +/**
  18 + * OAuth 2.0 客户端
  19 + *
  20 + * 对应调用 OAuth2OpenController 接口
  21 + */
  22 +@Component
  23 +public class OAuth2Client {
  24 +
  25 + private static final String BASE_URL = "http://127.0.0.1:48080/admin-api/system/oauth2";
  26 +
  27 + /**
  28 + * 租户编号
  29 + *
  30 + * 默认使用 1;如果使用别的租户,可以调整
  31 + */
  32 + public static final Long TENANT_ID = 1L;
  33 +
  34 + private static final String CLIENT_ID = "yudao-sso-demo-by-password";
  35 + private static final String CLIENT_SECRET = "test";
  36 +
  37 +
  38 +// @Resource // 可优化,注册一个 RestTemplate Bean,然后注入
  39 + private final RestTemplate restTemplate = new RestTemplate();
  40 +
  41 + /**
  42 + * 校验访问令牌,并返回它的基本信息
  43 + *
  44 + * @param token 访问令牌
  45 + * @return 访问令牌的基本信息
  46 + */
  47 + public CommonResult<OAuth2CheckTokenRespDTO> checkToken(String token) {
  48 + // 1.1 构建请求头
  49 + HttpHeaders headers = new HttpHeaders();
  50 + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
  51 + headers.set("tenant-id", TENANT_ID.toString());
  52 + addClientHeader(headers);
  53 + // 1.2 构建请求参数
  54 + MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
  55 + body.add("token", token);
  56 +
  57 + // 2. 执行请求
  58 + ResponseEntity<CommonResult<OAuth2CheckTokenRespDTO>> exchange = restTemplate.exchange(
  59 + BASE_URL + "/check-token",
  60 + HttpMethod.POST,
  61 + new HttpEntity<>(body, headers),
  62 + new ParameterizedTypeReference<CommonResult<OAuth2CheckTokenRespDTO>>() {}); // 解决 CommonResult 的泛型丢失
  63 + Assert.isTrue(exchange.getStatusCode().is2xxSuccessful(), "响应必须是 200 成功");
  64 + return exchange.getBody();
  65 + }
  66 +
  67 + /**
  68 + * 使用刷新令牌,获得(刷新)访问令牌
  69 + *
  70 + * @param refreshToken 刷新令牌
  71 + * @return 访问令牌
  72 + */
  73 + public CommonResult<OAuth2AccessTokenRespDTO> refreshToken(String refreshToken) {
  74 + // 1.1 构建请求头
  75 + HttpHeaders headers = new HttpHeaders();
  76 + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
  77 + headers.set("tenant-id", TENANT_ID.toString());
  78 + addClientHeader(headers);
  79 + // 1.2 构建请求参数
  80 + MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
  81 + body.add("grant_type", "refresh_token");
  82 + body.add("refresh_token", refreshToken);
  83 +
  84 + // 2. 执行请求
  85 + ResponseEntity<CommonResult<OAuth2AccessTokenRespDTO>> exchange = restTemplate.exchange(
  86 + BASE_URL + "/token",
  87 + HttpMethod.POST,
  88 + new HttpEntity<>(body, headers),
  89 + new ParameterizedTypeReference<CommonResult<OAuth2AccessTokenRespDTO>>() {}); // 解决 CommonResult 的泛型丢失
  90 + Assert.isTrue(exchange.getStatusCode().is2xxSuccessful(), "响应必须是 200 成功");
  91 + return exchange.getBody();
  92 + }
  93 +
  94 + /**
  95 + * 删除访问令牌
  96 + *
  97 + * @param token 访问令牌
  98 + * @return 成功
  99 + */
  100 + public CommonResult<Boolean> revokeToken(String token) {
  101 + // 1.1 构建请求头
  102 + HttpHeaders headers = new HttpHeaders();
  103 + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
  104 + headers.set("tenant-id", TENANT_ID.toString());
  105 + addClientHeader(headers);
  106 + // 1.2 构建请求参数
  107 + MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
  108 + body.add("token", token);
  109 +
  110 + // 2. 执行请求
  111 + ResponseEntity<CommonResult<Boolean>> exchange = restTemplate.exchange(
  112 + BASE_URL + "/token",
  113 + HttpMethod.DELETE,
  114 + new HttpEntity<>(body, headers),
  115 + new ParameterizedTypeReference<CommonResult<Boolean>>() {}); // 解决 CommonResult 的泛型丢失
  116 + Assert.isTrue(exchange.getStatusCode().is2xxSuccessful(), "响应必须是 200 成功");
  117 + return exchange.getBody();
  118 + }
  119 +
  120 + private static void addClientHeader(HttpHeaders headers) {
  121 + // client 拼接,需要 BASE64 编码
  122 + String client = CLIENT_ID + ":" + CLIENT_SECRET;
  123 + client = Base64Utils.encodeToString(client.getBytes(StandardCharsets.UTF_8));
  124 + headers.add("Authorization", "Basic " + client);
  125 + }
  126 +
  127 +}
yudao-example/yudao-sso-demo-by-password/src/main/java/cn/iocoder/yudao/ssodemo/client/UserClient.java 0 → 100644
  1 +package cn.iocoder.yudao.ssodemo.client;
  2 +
  3 +import cn.iocoder.yudao.ssodemo.client.dto.CommonResult;
  4 +import cn.iocoder.yudao.ssodemo.client.dto.user.UserInfoRespDTO;
  5 +import cn.iocoder.yudao.ssodemo.client.dto.user.UserUpdateReqDTO;
  6 +import cn.iocoder.yudao.ssodemo.framework.core.LoginUser;
  7 +import cn.iocoder.yudao.ssodemo.framework.core.util.SecurityUtils;
  8 +import org.springframework.core.ParameterizedTypeReference;
  9 +import org.springframework.http.*;
  10 +import org.springframework.stereotype.Component;
  11 +import org.springframework.util.Assert;
  12 +import org.springframework.util.LinkedMultiValueMap;
  13 +import org.springframework.util.MultiValueMap;
  14 +import org.springframework.web.client.RestTemplate;
  15 +
  16 +/**
  17 + * 用户 User 信息的客户端
  18 + *
  19 + * 对应调用 OAuth2UserController 接口
  20 + */
  21 +@Component
  22 +public class UserClient {
  23 +
  24 + private static final String BASE_URL = "http://127.0.0.1:48080/admin-api//system/oauth2/user";
  25 +
  26 + // @Resource // 可优化,注册一个 RestTemplate Bean,然后注入
  27 + private final RestTemplate restTemplate = new RestTemplate();
  28 +
  29 + public CommonResult<UserInfoRespDTO> getUser() {
  30 + // 1.1 构建请求头
  31 + HttpHeaders headers = new HttpHeaders();
  32 + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
  33 + headers.set("tenant-id", OAuth2Client.TENANT_ID.toString());
  34 + addTokenHeader(headers);
  35 + // 1.2 构建请求参数
  36 + MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
  37 +
  38 + // 2. 执行请求
  39 + ResponseEntity<CommonResult<UserInfoRespDTO>> exchange = restTemplate.exchange(
  40 + BASE_URL + "/get",
  41 + HttpMethod.GET,
  42 + new HttpEntity<>(body, headers),
  43 + new ParameterizedTypeReference<CommonResult<UserInfoRespDTO>>() {}); // 解决 CommonResult 的泛型丢失
  44 + Assert.isTrue(exchange.getStatusCode().is2xxSuccessful(), "响应必须是 200 成功");
  45 + return exchange.getBody();
  46 + }
  47 +
  48 + public CommonResult<Boolean> updateUser(UserUpdateReqDTO updateReqDTO) {
  49 + // 1.1 构建请求头
  50 + HttpHeaders headers = new HttpHeaders();
  51 + headers.setContentType(MediaType.APPLICATION_JSON);
  52 + headers.set("tenant-id", OAuth2Client.TENANT_ID.toString());
  53 + addTokenHeader(headers);
  54 + // 1.2 构建请求参数
  55 + // 使用 updateReqDTO 即可
  56 +
  57 + // 2. 执行请求
  58 + ResponseEntity<CommonResult<Boolean>> exchange = restTemplate.exchange(
  59 + BASE_URL + "/update",
  60 + HttpMethod.PUT,
  61 + new HttpEntity<>(updateReqDTO, headers),
  62 + new ParameterizedTypeReference<CommonResult<Boolean>>() {}); // 解决 CommonResult 的泛型丢失
  63 + Assert.isTrue(exchange.getStatusCode().is2xxSuccessful(), "响应必须是 200 成功");
  64 + return exchange.getBody();
  65 + }
  66 +
  67 +
  68 + private static void addTokenHeader(HttpHeaders headers) {
  69 + LoginUser loginUser = SecurityUtils.getLoginUser();
  70 + Assert.notNull(loginUser, "登录用户不能为空");
  71 + headers.add("Authorization", "Bearer " + loginUser.getAccessToken());
  72 + }
  73 +}
yudao-example/yudao-sso-demo-by-password/src/main/java/cn/iocoder/yudao/ssodemo/client/dto/CommonResult.java 0 → 100644
  1 +package cn.iocoder.yudao.ssodemo.client.dto;
  2 +
  3 +import lombok.Data;
  4 +
  5 +import java.io.Serializable;
  6 +
  7 +/**
  8 + * 通用返回
  9 + *
  10 + * @param <T> 数据泛型
  11 + */
  12 +@Data
  13 +public class CommonResult<T> implements Serializable {
  14 +
  15 + /**
  16 + * 错误码
  17 + */
  18 + private Integer code;
  19 + /**
  20 + * 返回数据
  21 + */
  22 + private T data;
  23 + /**
  24 + * 错误提示,用户可阅读
  25 + */
  26 + private String msg;
  27 +
  28 +}
yudao-example/yudao-sso-demo-by-password/src/main/java/cn/iocoder/yudao/ssodemo/client/dto/oauth2/OAuth2AccessTokenRespDTO.java 0 → 100644
  1 +package cn.iocoder.yudao.ssodemo.client.dto.oauth2;
  2 +
  3 +import com.fasterxml.jackson.annotation.JsonProperty;
  4 +import lombok.AllArgsConstructor;
  5 +import lombok.Data;
  6 +import lombok.NoArgsConstructor;
  7 +
  8 +/**
  9 + * 访问令牌 Response DTO
  10 + */
  11 +@Data
  12 +@NoArgsConstructor
  13 +@AllArgsConstructor
  14 +public class OAuth2AccessTokenRespDTO {
  15 +
  16 + /**
  17 + * 访问令牌
  18 + */
  19 + @JsonProperty("access_token")
  20 + private String accessToken;
  21 +
  22 + /**
  23 + * 刷新令牌
  24 + */
  25 + @JsonProperty("refresh_token")
  26 + private String refreshToken;
  27 +
  28 + /**
  29 + * 令牌类型
  30 + */
  31 + @JsonProperty("token_type")
  32 + private String tokenType;
  33 +
  34 + /**
  35 + * 过期时间;单位:秒
  36 + */
  37 + @JsonProperty("expires_in")
  38 + private Long expiresIn;
  39 +
  40 + /**
  41 + * 授权范围;如果多个授权范围,使用空格分隔
  42 + */
  43 + private String scope;
  44 +
  45 +}
yudao-example/yudao-sso-demo-by-password/src/main/java/cn/iocoder/yudao/ssodemo/client/dto/oauth2/OAuth2CheckTokenRespDTO.java 0 → 100644
  1 +package cn.iocoder.yudao.ssodemo.client.dto.oauth2;
  2 +
  3 +import com.fasterxml.jackson.annotation.JsonProperty;
  4 +import lombok.AllArgsConstructor;
  5 +import lombok.Data;
  6 +import lombok.NoArgsConstructor;
  7 +
  8 +import java.util.List;
  9 +
  10 +/**
  11 + * 校验令牌 Response DTO
  12 + *
  13 + * @author 芋道源码
  14 + */
  15 +@Data
  16 +@NoArgsConstructor
  17 +@AllArgsConstructor
  18 +public class OAuth2CheckTokenRespDTO {
  19 +
  20 + /**
  21 + * 用户编号
  22 + */
  23 + @JsonProperty("user_id")
  24 + private Long userId;
  25 + /**
  26 + * 用户类型
  27 + */
  28 + @JsonProperty("user_type")
  29 + private Integer userType;
  30 + /**
  31 + * 租户编号
  32 + */
  33 + @JsonProperty("tenant_id")
  34 + private Long tenantId;
  35 +
  36 + /**
  37 + * 客户端编号
  38 + */
  39 + @JsonProperty("client_id")
  40 + private String clientId;
  41 + /**
  42 + * 授权范围
  43 + */
  44 + private List<String> scopes;
  45 +
  46 + /**
  47 + * 访问令牌
  48 + */
  49 + @JsonProperty("access_token")
  50 + private String accessToken;
  51 +
  52 + /**
  53 + * 过期时间
  54 + *
  55 + * 时间戳 / 1000,即单位:秒
  56 + */
  57 + private Long exp;
  58 +
  59 +}
yudao-example/yudao-sso-demo-by-password/src/main/java/cn/iocoder/yudao/ssodemo/client/dto/user/UserInfoRespDTO.java 0 → 100644
  1 +package cn.iocoder.yudao.ssodemo.client.dto.user;
  2 +
  3 +import lombok.AllArgsConstructor;
  4 +import lombok.Data;
  5 +import lombok.NoArgsConstructor;
  6 +
  7 +import java.util.List;
  8 +
  9 +/**
  10 + * 获得用户基本信息 Response dto
  11 + */
  12 +@Data
  13 +@NoArgsConstructor
  14 +@AllArgsConstructor
  15 +public class UserInfoRespDTO {
  16 +
  17 + /**
  18 + * 用户编号
  19 + */
  20 + private Long id;
  21 +
  22 + /**
  23 + * 用户账号
  24 + */
  25 + private String username;
  26 +
  27 + /**
  28 + * 用户昵称
  29 + */
  30 + private String nickname;
  31 +
  32 + /**
  33 + * 用户邮箱
  34 + */
  35 + private String email;
  36 + /**
  37 + * 手机号码
  38 + */
  39 + private String mobile;
  40 +
  41 + /**
  42 + * 用户性别
  43 + */
  44 + private Integer sex;
  45 +
  46 + /**
  47 + * 用户头像
  48 + */
  49 + private String avatar;
  50 +
  51 + /**
  52 + * 所在部门
  53 + */
  54 + private Dept dept;
  55 +
  56 + /**
  57 + * 所属岗位数组
  58 + */
  59 + private List<Post> posts;
  60 +
  61 + /**
  62 + * 部门
  63 + */
  64 + @Data
  65 + public static class Dept {
  66 +
  67 + /**
  68 + * 部门编号
  69 + */
  70 + private Long id;
  71 +
  72 + /**
  73 + * 部门名称
  74 + */
  75 + private String name;
  76 +
  77 + }
  78 +
  79 + /**
  80 + * 岗位
  81 + */
  82 + @Data
  83 + public static class Post {
  84 +
  85 + /**
  86 + * 岗位编号
  87 + */
  88 + private Long id;
  89 +
  90 + /**
  91 + * 岗位名称
  92 + */
  93 + private String name;
  94 +
  95 + }
  96 +
  97 +}
yudao-example/yudao-sso-demo-by-password/src/main/java/cn/iocoder/yudao/ssodemo/client/dto/user/UserUpdateReqDTO.java 0 → 100644
  1 +package cn.iocoder.yudao.ssodemo.client.dto.user;
  2 +
  3 +import lombok.AllArgsConstructor;
  4 +import lombok.Data;
  5 +import lombok.NoArgsConstructor;
  6 +
  7 +/**
  8 + * 更新用户基本信息 Request DTO
  9 + */
  10 +@Data
  11 +@NoArgsConstructor
  12 +@AllArgsConstructor
  13 +public class UserUpdateReqDTO {
  14 +
  15 + /**
  16 + * 用户昵称
  17 + */
  18 + private String nickname;
  19 +
  20 + /**
  21 + * 用户邮箱
  22 + */
  23 + private String email;
  24 +
  25 + /**
  26 + * 手机号码
  27 + */
  28 + private String mobile;
  29 +
  30 + /**
  31 + * 用户性别
  32 + */
  33 + private Integer sex;
  34 +
  35 +}
yudao-example/yudao-sso-demo-by-password/src/main/java/cn/iocoder/yudao/ssodemo/controller/AuthController.java 0 → 100644
  1 +package cn.iocoder.yudao.ssodemo.controller;
  2 +
  3 +import cn.hutool.core.util.StrUtil;
  4 +import cn.iocoder.yudao.ssodemo.client.OAuth2Client;
  5 +import cn.iocoder.yudao.ssodemo.client.dto.CommonResult;
  6 +import cn.iocoder.yudao.ssodemo.client.dto.oauth2.OAuth2AccessTokenRespDTO;
  7 +import cn.iocoder.yudao.ssodemo.framework.core.util.SecurityUtils;
  8 +import org.springframework.web.bind.annotation.PostMapping;
  9 +import org.springframework.web.bind.annotation.RequestMapping;
  10 +import org.springframework.web.bind.annotation.RequestParam;
  11 +import org.springframework.web.bind.annotation.RestController;
  12 +
  13 +import javax.annotation.Resource;
  14 +import javax.servlet.http.HttpServletRequest;
  15 +
  16 +@RestController
  17 +@RequestMapping("/auth")
  18 +public class AuthController {
  19 +
  20 + @Resource
  21 + private OAuth2Client oauth2Client;
  22 +
  23 + /**
  24 + * 使用刷新令牌,获得(刷新)访问令牌
  25 + *
  26 + * @param refreshToken 刷新令牌
  27 + * @return 访问令牌;注意,实际项目中,最好创建对应的 ResponseVO 类,只返回必要的字段
  28 + */
  29 + @PostMapping("/refresh-token")
  30 + public CommonResult<OAuth2AccessTokenRespDTO> refreshToken(@RequestParam("refreshToken") String refreshToken) {
  31 + return oauth2Client.refreshToken(refreshToken);
  32 + }
  33 +
  34 + /**
  35 + * 退出登录
  36 + *
  37 + * @param request 请求
  38 + * @return 成功
  39 + */
  40 + @PostMapping("/logout")
  41 + public CommonResult<Boolean> logout(HttpServletRequest request) {
  42 + String token = SecurityUtils.obtainAuthorization(request, "Authorization");
  43 + if (StrUtil.isNotBlank(token)) {
  44 + return oauth2Client.revokeToken(token);
  45 + }
  46 + // 返回成功
  47 + return new CommonResult<>();
  48 + }
  49 +
  50 +}
yudao-example/yudao-sso-demo-by-password/src/main/java/cn/iocoder/yudao/ssodemo/controller/UserController.java 0 → 100644
  1 +package cn.iocoder.yudao.ssodemo.controller;
  2 +
  3 +import cn.iocoder.yudao.ssodemo.client.UserClient;
  4 +import cn.iocoder.yudao.ssodemo.client.dto.CommonResult;
  5 +import cn.iocoder.yudao.ssodemo.client.dto.user.UserInfoRespDTO;
  6 +import cn.iocoder.yudao.ssodemo.client.dto.user.UserUpdateReqDTO;
  7 +import org.springframework.web.bind.annotation.*;
  8 +
  9 +import javax.annotation.Resource;
  10 +
  11 +@RestController
  12 +@RequestMapping("/user")
  13 +public class UserController {
  14 +
  15 + @Resource
  16 + private UserClient userClient;
  17 +
  18 + /**
  19 + * 获得当前登录用户的基本信息
  20 + *
  21 + * @return 用户信息;注意,实际项目中,最好创建对应的 ResponseVO 类,只返回必要的字段
  22 + */
  23 + @GetMapping("/get")
  24 + public CommonResult<UserInfoRespDTO> getUser() {
  25 + return userClient.getUser();
  26 + }
  27 +
  28 + /**
  29 + * 更新当前登录用户的昵称
  30 + *
  31 + * @param nickname 昵称
  32 + * @return 成功
  33 + */
  34 + @PutMapping("/update")
  35 + public CommonResult<Boolean> updateUser(@RequestParam("nickname") String nickname) {
  36 + UserUpdateReqDTO updateReqDTO = new UserUpdateReqDTO(nickname, null, null, null);
  37 + return userClient.updateUser(updateReqDTO);
  38 + }
  39 +
  40 +}
yudao-example/yudao-sso-demo-by-password/src/main/java/cn/iocoder/yudao/ssodemo/framework/config/SecurityConfiguration.java 0 → 100644
  1 +package cn.iocoder.yudao.ssodemo.framework.config;
  2 +
  3 +import cn.iocoder.yudao.ssodemo.framework.core.filter.TokenAuthenticationFilter;
  4 +import cn.iocoder.yudao.ssodemo.framework.core.handler.AccessDeniedHandlerImpl;
  5 +import org.springframework.context.annotation.Configuration;
  6 +import org.springframework.http.HttpMethod;
  7 +import org.springframework.security.config.annotation.web.builders.HttpSecurity;
  8 +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
  9 +import org.springframework.security.web.AuthenticationEntryPoint;
  10 +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
  11 +
  12 +import javax.annotation.Resource;
  13 +
  14 +@Configuration
  15 +public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
  16 +
  17 + @Resource
  18 + private TokenAuthenticationFilter tokenAuthenticationFilter;
  19 +
  20 + @Resource
  21 + private AccessDeniedHandlerImpl accessDeniedHandler;
  22 + @Resource
  23 + private AuthenticationEntryPoint authenticationEntryPoint;
  24 +
  25 + @Override
  26 + protected void configure(HttpSecurity httpSecurity) throws Exception {
  27 + // 设置 URL 安全权限
  28 + httpSecurity.csrf().disable() // 禁用 CSRF 保护
  29 + .authorizeRequests()
  30 + // 1. 静态资源,可匿名访问
  31 + .antMatchers(HttpMethod.GET, "/*.html", "/**/*.html", "/**/*.css", "/**/*.js").permitAll()
  32 + // 2. 登录相关的接口,可匿名访问
  33 + .antMatchers("/auth/login-by-code").permitAll()
  34 + .antMatchers("/auth/refresh-token").permitAll()
  35 + .antMatchers("/auth/logout").permitAll()
  36 + // last. 兜底规则,必须认证
  37 + .and().authorizeRequests()
  38 + .anyRequest().authenticated();
  39 +
  40 + // 设置处理器
  41 + httpSecurity.exceptionHandling().accessDeniedHandler(accessDeniedHandler)
  42 + .authenticationEntryPoint(authenticationEntryPoint);
  43 +
  44 + // 添加 Token Filter
  45 + httpSecurity.addFilterBefore(tokenAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
  46 + }
  47 +
  48 +}
yudao-example/yudao-sso-demo-by-password/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/LoginUser.java 0 → 100644
  1 +package cn.iocoder.yudao.ssodemo.framework.core;
  2 +
  3 +import lombok.Data;
  4 +
  5 +import java.util.List;
  6 +
  7 +/**
  8 + * 登录用户信息
  9 + *
  10 + * @author 芋道源码
  11 + */
  12 +@Data
  13 +public class LoginUser {
  14 +
  15 + /**
  16 + * 用户编号
  17 + */
  18 + private Long id;
  19 + /**
  20 + * 用户类型
  21 + */
  22 + private Integer userType;
  23 + /**
  24 + * 租户编号
  25 + */
  26 + private Long tenantId;
  27 + /**
  28 + * 授权范围
  29 + */
  30 + private List<String> scopes;
  31 +
  32 + /**
  33 + * 访问令牌
  34 + */
  35 + private String accessToken;
  36 +
  37 +}
yudao-example/yudao-sso-demo-by-password/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/filter/TokenAuthenticationFilter.java 0 → 100644
  1 +package cn.iocoder.yudao.ssodemo.framework.core.filter;
  2 +
  3 +import cn.iocoder.yudao.ssodemo.client.OAuth2Client;
  4 +import cn.iocoder.yudao.ssodemo.client.dto.CommonResult;
  5 +import cn.iocoder.yudao.ssodemo.client.dto.oauth2.OAuth2CheckTokenRespDTO;
  6 +import cn.iocoder.yudao.ssodemo.framework.core.LoginUser;
  7 +import cn.iocoder.yudao.ssodemo.framework.core.util.SecurityUtils;
  8 +import org.springframework.stereotype.Component;
  9 +import org.springframework.util.StringUtils;
  10 +import org.springframework.web.filter.OncePerRequestFilter;
  11 +
  12 +import javax.annotation.Resource;
  13 +import javax.servlet.FilterChain;
  14 +import javax.servlet.ServletException;
  15 +import javax.servlet.http.HttpServletRequest;
  16 +import javax.servlet.http.HttpServletResponse;
  17 +import java.io.IOException;
  18 +
  19 +/**
  20 + * Token 过滤器,验证 token 的有效性
  21 + * 验证通过后,获得 {@link LoginUser} 信息,并加入到 Spring Security 上下文
  22 + *
  23 + * @author 芋道源码
  24 + */
  25 +@Component
  26 +public class TokenAuthenticationFilter extends OncePerRequestFilter {
  27 +
  28 + @Resource
  29 + private OAuth2Client oauth2Client;
  30 +
  31 + @Override
  32 + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
  33 + FilterChain filterChain) throws ServletException, IOException {
  34 + // 1. 获得访问令牌
  35 + String token = SecurityUtils.obtainAuthorization(request, "Authorization");
  36 + if (StringUtils.hasText(token)) {
  37 + // 2. 基于 token 构建登录用户
  38 + LoginUser loginUser = buildLoginUserByToken(token);
  39 + // 3. 设置当前用户
  40 + if (loginUser != null) {
  41 + SecurityUtils.setLoginUser(loginUser, request);
  42 + }
  43 + }
  44 +
  45 + // 继续过滤链
  46 + filterChain.doFilter(request, response);
  47 + }
  48 +
  49 + private LoginUser buildLoginUserByToken(String token) {
  50 + try {
  51 + CommonResult<OAuth2CheckTokenRespDTO> accessTokenResult = oauth2Client.checkToken(token);
  52 + OAuth2CheckTokenRespDTO accessToken = accessTokenResult.getData();
  53 + if (accessToken == null) {
  54 + return null;
  55 + }
  56 + // 构建登录用户
  57 + return new LoginUser().setId(accessToken.getUserId()).setUserType(accessToken.getUserType())
  58 + .setTenantId(accessToken.getTenantId()).setScopes(accessToken.getScopes())
  59 + .setAccessToken(accessToken.getAccessToken());
  60 + } catch (Exception exception) {
  61 + // 校验 Token 不通过时,考虑到一些接口是无需登录的,所以直接返回 null 即可
  62 + return null;
  63 + }
  64 + }
  65 +
  66 +}
yudao-example/yudao-sso-demo-by-password/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/handler/AccessDeniedHandlerImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.ssodemo.framework.core.handler;
  2 +
  3 +import cn.iocoder.yudao.ssodemo.client.dto.CommonResult;
  4 +import cn.iocoder.yudao.ssodemo.framework.core.util.SecurityUtils;
  5 +import cn.iocoder.yudao.ssodemo.framework.core.util.ServletUtils;
  6 +import lombok.extern.slf4j.Slf4j;
  7 +import org.springframework.http.HttpStatus;
  8 +import org.springframework.security.access.AccessDeniedException;
  9 +import org.springframework.security.web.access.AccessDeniedHandler;
  10 +import org.springframework.security.web.access.ExceptionTranslationFilter;
  11 +import org.springframework.stereotype.Component;
  12 +
  13 +import javax.servlet.FilterChain;
  14 +import javax.servlet.ServletException;
  15 +import javax.servlet.http.HttpServletRequest;
  16 +import javax.servlet.http.HttpServletResponse;
  17 +import java.io.IOException;
  18 +
  19 +/**
  20 + * 访问一个需要认证的 URL 资源,已经认证(登录)但是没有权限的情况下,返回 {@link GlobalErrorCodeConstants#FORBIDDEN} 错误码。
  21 + *
  22 + * 补充:Spring Security 通过 {@link ExceptionTranslationFilter#handleAccessDeniedException(HttpServletRequest, HttpServletResponse, FilterChain, AccessDeniedException)} 方法,调用当前类
  23 + *
  24 + * @author 芋道源码
  25 + */
  26 +@Component
  27 +@SuppressWarnings("JavadocReference")
  28 +@Slf4j
  29 +public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
  30 +
  31 + @Override
  32 + public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e)
  33 + throws IOException, ServletException {
  34 + // 打印 warn 的原因是,不定期合并 warn,看看有没恶意破坏
  35 + log.warn("[commence][访问 URL({}) 时,用户({}) 权限不够]", request.getRequestURI(),
  36 + SecurityUtils.getLoginUserId(), e);
  37 + // 返回 403
  38 + CommonResult<Object> result = new CommonResult<>();
  39 + result.setCode(HttpStatus.FORBIDDEN.value());
  40 + result.setMsg("没有该操作权限");
  41 + ServletUtils.writeJSON(response, result);
  42 + }
  43 +
  44 +}
yudao-example/yudao-sso-demo-by-password/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/handler/AuthenticationEntryPointImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.ssodemo.framework.core.handler;
  2 +
  3 +import cn.iocoder.yudao.ssodemo.client.dto.CommonResult;
  4 +import cn.iocoder.yudao.ssodemo.framework.core.util.ServletUtils;
  5 +import lombok.extern.slf4j.Slf4j;
  6 +import org.springframework.http.HttpStatus;
  7 +import org.springframework.security.core.AuthenticationException;
  8 +import org.springframework.security.web.AuthenticationEntryPoint;
  9 +import org.springframework.security.web.access.ExceptionTranslationFilter;
  10 +import org.springframework.stereotype.Component;
  11 +
  12 +import javax.servlet.FilterChain;
  13 +import javax.servlet.http.HttpServletRequest;
  14 +import javax.servlet.http.HttpServletResponse;
  15 +
  16 +/**
  17 + * 访问一个需要认证的 URL 资源,但是此时自己尚未认证(登录)的情况下,返回 {@link GlobalErrorCodeConstants#UNAUTHORIZED} 错误码,从而使前端重定向到登录页
  18 + *
  19 + * 补充:Spring Security 通过 {@link ExceptionTranslationFilter#sendStartAuthentication(HttpServletRequest, HttpServletResponse, FilterChain, AuthenticationException)} 方法,调用当前类
  20 + */
  21 +@Component
  22 +@Slf4j
  23 +@SuppressWarnings("JavadocReference") // 忽略文档引用报错
  24 +public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
  25 +
  26 + @Override
  27 + public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) {
  28 + log.debug("[commence][访问 URL({}) 时,没有登录]", request.getRequestURI(), e);
  29 + // 返回 401
  30 + CommonResult<Object> result = new CommonResult<>();
  31 + result.setCode(HttpStatus.UNAUTHORIZED.value());
  32 + result.setMsg("账号未登录");
  33 + ServletUtils.writeJSON(response, result);
  34 + }
  35 +
  36 +}
yudao-example/yudao-sso-demo-by-password/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/util/SecurityUtils.java 0 → 100644
  1 +package cn.iocoder.yudao.ssodemo.framework.core.util;
  2 +
  3 +import cn.iocoder.yudao.ssodemo.framework.core.LoginUser;
  4 +import org.springframework.lang.Nullable;
  5 +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
  6 +import org.springframework.security.core.Authentication;
  7 +import org.springframework.security.core.context.SecurityContext;
  8 +import org.springframework.security.core.context.SecurityContextHolder;
  9 +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
  10 +import org.springframework.util.StringUtils;
  11 +
  12 +import javax.servlet.http.HttpServletRequest;
  13 +import java.util.Collections;
  14 +
  15 +/**
  16 + * 安全服务工具类
  17 + *
  18 + * @author 芋道源码
  19 + */
  20 +public class SecurityUtils {
  21 +
  22 + public static final String AUTHORIZATION_BEARER = "Bearer";
  23 +
  24 + private SecurityUtils() {}
  25 +
  26 + /**
  27 + * 从请求中,获得认证 Token
  28 + *
  29 + * @param request 请求
  30 + * @param header 认证 Token 对应的 Header 名字
  31 + * @return 认证 Token
  32 + */
  33 + public static String obtainAuthorization(HttpServletRequest request, String header) {
  34 + String authorization = request.getHeader(header);
  35 + if (!StringUtils.hasText(authorization)) {
  36 + return null;
  37 + }
  38 + int index = authorization.indexOf(AUTHORIZATION_BEARER + " ");
  39 + if (index == -1) { // 未找到
  40 + return null;
  41 + }
  42 + return authorization.substring(index + 7).trim();
  43 + }
  44 +
  45 + /**
  46 + * 获得当前认证信息
  47 + *
  48 + * @return 认证信息
  49 + */
  50 + public static Authentication getAuthentication() {
  51 + SecurityContext context = SecurityContextHolder.getContext();
  52 + if (context == null) {
  53 + return null;
  54 + }
  55 + return context.getAuthentication();
  56 + }
  57 +
  58 + /**
  59 + * 获取当前用户
  60 + *
  61 + * @return 当前用户
  62 + */
  63 + @Nullable
  64 + public static LoginUser getLoginUser() {
  65 + Authentication authentication = getAuthentication();
  66 + if (authentication == null) {
  67 + return null;
  68 + }
  69 + return authentication.getPrincipal() instanceof LoginUser ? (LoginUser) authentication.getPrincipal() : null;
  70 + }
  71 +
  72 + /**
  73 + * 获得当前用户的编号,从上下文中
  74 + *
  75 + * @return 用户编号
  76 + */
  77 + @Nullable
  78 + public static Long getLoginUserId() {
  79 + LoginUser loginUser = getLoginUser();
  80 + return loginUser != null ? loginUser.getId() : null;
  81 + }
  82 +
  83 + /**
  84 + * 设置当前用户
  85 + *
  86 + * @param loginUser 登录用户
  87 + * @param request 请求
  88 + */
  89 + public static void setLoginUser(LoginUser loginUser, HttpServletRequest request) {
  90 + // 创建 Authentication,并设置到上下文
  91 + Authentication authentication = buildAuthentication(loginUser, request);
  92 + SecurityContextHolder.getContext().setAuthentication(authentication);
  93 + }
  94 +
  95 + private static Authentication buildAuthentication(LoginUser loginUser, HttpServletRequest request) {
  96 + // 创建 UsernamePasswordAuthenticationToken 对象
  97 + UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
  98 + loginUser, null, Collections.emptyList());
  99 + authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
  100 + return authenticationToken;
  101 + }
  102 +
  103 +}
yudao-example/yudao-sso-demo-by-password/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/util/ServletUtils.java 0 → 100644
  1 +package cn.iocoder.yudao.ssodemo.framework.core.util;
  2 +
  3 +import cn.hutool.extra.servlet.ServletUtil;
  4 +import cn.hutool.json.JSONUtil;
  5 +import org.springframework.http.MediaType;
  6 +
  7 +import javax.servlet.http.HttpServletResponse;
  8 +
  9 +/**
  10 + * 客户端工具类
  11 + *
  12 + * @author 芋道源码
  13 + */
  14 +public class ServletUtils {
  15 +
  16 + /**
  17 + * 返回 JSON 字符串
  18 + *
  19 + * @param response 响应
  20 + * @param object 对象,会序列化成 JSON 字符串
  21 + */
  22 + @SuppressWarnings("deprecation") // 必须使用 APPLICATION_JSON_UTF8_VALUE,否则会乱码
  23 + public static void writeJSON(HttpServletResponse response, Object object) {
  24 + String content = JSONUtil.toJsonStr(object);
  25 + ServletUtil.write(response, content, MediaType.APPLICATION_JSON_UTF8_VALUE);
  26 + }
  27 +
  28 +}
yudao-example/yudao-sso-demo-by-password/src/main/resources/application.yaml 0 → 100644
  1 +server:
  2 + port: 18080
yudao-example/yudao-sso-demo-by-password/src/main/resources/static/index.html 0 → 100644
  1 +<!DOCTYPE html>
  2 +<html lang="en">
  3 +<head>
  4 + <meta charset="UTF-8">
  5 + <title>首页</title>
  6 + <!-- jQuery:操作 dom、发起请求等 -->
  7 + <script src="https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/jquery/2.1.2/jquery.min.js" type="application/javascript"></script>
  8 +
  9 + <script type="application/javascript">
  10 +
  11 + /**
  12 + * 跳转单点登录
  13 + */
  14 + function passwordLogin() {
  15 + window.location.href = '/login.html'
  16 + }
  17 +
  18 + /**
  19 + * 修改昵称
  20 + */
  21 + function updateNickname() {
  22 + const nickname = prompt("请输入新的昵称", "");
  23 + if (!nickname) {
  24 + return;
  25 + }
  26 + // 更新用户的昵称
  27 + const accessToken = localStorage.getItem('ACCESS-TOKEN');
  28 + $.ajax({
  29 + url: "http://127.0.0.1:18080/user/update?nickname=" + nickname,
  30 + method: 'PUT',
  31 + headers: {
  32 + 'Authorization': 'Bearer ' + accessToken
  33 + },
  34 + success: function (result) {
  35 + if (result.code !== 0) {
  36 + alert('更新昵称失败,原因:' + result.msg)
  37 + return;
  38 + }
  39 + alert('更新昵称成功!');
  40 + $('#nicknameSpan').html(nickname);
  41 + }
  42 + });
  43 + }
  44 +
  45 + /**
  46 + * 刷新令牌
  47 + */
  48 + function refreshToken() {
  49 + const refreshToken = localStorage.getItem('REFRESH-TOKEN');
  50 + if (!refreshToken) {
  51 + alert("获取不到刷新令牌");
  52 + return;
  53 + }
  54 + $.ajax({
  55 + url: "http://127.0.0.1:18080/auth/refresh-token?refreshToken=" + refreshToken,
  56 + method: 'POST',
  57 + success: function (result) {
  58 + if (result.code !== 0) {
  59 + alert('刷新访问令牌失败,原因:' + result.msg)
  60 + return;
  61 + }
  62 + alert('更新访问令牌成功!');
  63 + $('#accessTokenSpan').html(result.data.access_token);
  64 +
  65 + // 设置到 localStorage 中
  66 + localStorage.setItem('ACCESS-TOKEN', result.data.access_token);
  67 + localStorage.setItem('REFRESH-TOKEN', result.data.refresh_token);
  68 + }
  69 + });
  70 + }
  71 +
  72 + /**
  73 + * 登出,删除访问令牌
  74 + */
  75 + function logout() {
  76 + const accessToken = localStorage.getItem('ACCESS-TOKEN');
  77 + if (!accessToken) {
  78 + location.reload();
  79 + return;
  80 + }
  81 + $.ajax({
  82 + url: "http://127.0.0.1:18080/auth/logout",
  83 + method: 'POST',
  84 + headers: {
  85 + 'Authorization': 'Bearer ' + accessToken
  86 + },
  87 + success: function (result) {
  88 + if (result.code !== 0) {
  89 + alert('退出登录失败,原因:' + result.msg)
  90 + return;
  91 + }
  92 + alert('退出登录成功!');
  93 + // 删除 localStorage 中
  94 + localStorage.removeItem('ACCESS-TOKEN');
  95 + localStorage.removeItem('REFRESH-TOKEN');
  96 +
  97 + location.reload();
  98 + }
  99 + });
  100 + }
  101 +
  102 + $(function () {
  103 + const accessToken = localStorage.getItem('ACCESS-TOKEN');
  104 + // 情况一:未登录
  105 + if (!accessToken) {
  106 + $('#noLoginDiv').css("display", "block");
  107 + return;
  108 + }
  109 +
  110 + // 情况二:已登录
  111 + $('#yesLoginDiv').css("display", "block");
  112 + $('#accessTokenSpan').html(accessToken);
  113 + // 获得登录用户的信息
  114 + $.ajax({
  115 + url: "http://127.0.0.1:18080/user/get",
  116 + method: 'GET',
  117 + headers: {
  118 + 'Authorization': 'Bearer ' + accessToken
  119 + },
  120 + success: function (result) {
  121 + if (result.code !== 0) {
  122 + alert('获得个人信息失败,原因:' + result.msg)
  123 + return;
  124 + }
  125 + $('#nicknameSpan').html(result.data.nickname);
  126 + }
  127 + });
  128 + })
  129 + </script>
  130 +</head>
  131 +<body>
  132 +<!-- 情况一:未登录:1)跳转 ruoyi-vue-pro 的 SSO 登录页 -->
  133 +<div id="noLoginDiv" style="display: none">
  134 + 您未登录,点击 <a href="#" onclick="passwordLogin()">跳转 </a> 账号密码登录
  135 +</div>
  136 +
  137 +<!-- 情况二:已登录:1)展示用户信息;2)刷新访问令牌;3)退出登录 -->
  138 +<div id="yesLoginDiv" style="display: none">
  139 + 您已登录!<button onclick="logout()">退出登录</button> <br />
  140 + 昵称:<span id="nicknameSpan"> 加载中... </span> <button onclick="updateNickname()">修改昵称</button> <br />
  141 + 访问令牌:<span id="accessTokenSpan"> 加载中... </span> <button onclick="refreshToken()">刷新令牌</button> <br />
  142 +</div>
  143 +</body>
  144 +<style>
  145 + body { /** 页面居中 */
  146 + border-radius: 20px;
  147 + height: 350px;
  148 + position: absolute;
  149 + left: 50%;
  150 + top: 50%;
  151 + transform: translate(-50%,-50%);
  152 + }
  153 +</style>
  154 +</html>
yudao-example/yudao-sso-demo-by-password/src/main/resources/static/login.html 0 → 100644
  1 +<!DOCTYPE html>
  2 +<html lang="en">
  3 +<head>
  4 + <meta charset="UTF-8">
  5 + <title>登录</title>
  6 + <!-- jQuery:操作 dom、发起请求等 -->
  7 + <script src="https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/jquery/2.1.2/jquery.min.js" type="application/javascript"></script>
  8 +
  9 + <script type="application/javascript">
  10 +
  11 + /**
  12 + * 账号密码登录
  13 + */
  14 + function login() {
  15 + const clientId = 'yudao-sso-demo-by-password'; // 可以改写成,你的 clientId
  16 + const clientSecret = 'test'; // 可以改写成,你的 clientSecret
  17 + const grantType = 'password'; // 密码模式
  18 +
  19 + // 账号 + 密码
  20 + const username = $('#username').val();
  21 + const password = $('#password').val();
  22 + if (username.length === 0 || password.length === 0) {
  23 + alert('账号或密码未输入');
  24 + return;
  25 + }
  26 +
  27 + // 发起请求
  28 + $.ajax({
  29 + url: "http://127.0.0.1:48080/admin-api/system/oauth2/token?"
  30 + // 客户端
  31 + + "client_id=" + clientId
  32 + + "&client_secret=" + clientSecret
  33 + // 密码模式的参数
  34 + + "&grant_type=" + grantType
  35 + + "&username=" + username
  36 + + "&password=" + password
  37 + + '&scope=user.read user.write',
  38 + method: 'POST',
  39 + headers: {
  40 + 'tenant-id': '1', // 多租户编号,写死
  41 + },
  42 + success: function (result) {
  43 + if (result.code !== 0) {
  44 + alert('登录失败,原因:' + result.msg)
  45 + return;
  46 + }
  47 + // 设置到 localStorage 中
  48 + localStorage.setItem('ACCESS-TOKEN', result.data.access_token);
  49 + localStorage.setItem('REFRESH-TOKEN', result.data.refresh_token);
  50 +
  51 + // 提示登录成功
  52 + alert('登录成功!点击确认,跳转回首页');
  53 + window.location.href = '/index.html';
  54 + }
  55 + });
  56 + }
  57 + </script>
  58 +</head>
  59 +<body>
  60 +账号:<input id="username" value="admin" /> <br />
  61 +密码:<input id="password" value="admin123" > <br />
  62 +<button style="float: right; margin-top: 5px;" onclick="login()">登录</button>
  63 +</body>
  64 +<style>
  65 + body { /** 页面居中 */
  66 + border-radius: 20px;
  67 + height: 350px;
  68 + position: absolute;
  69 + left: 50%;
  70 + top: 50%;
  71 + transform: translate(-50%,-50%);
  72 + }
  73 +</style>
  74 +</html>
yudao-framework/yudao-spring-boot-starter-biz-pay/pom.xml
@@ -52,7 +52,7 @@ @@ -52,7 +52,7 @@
52 <dependency> 52 <dependency>
53 <groupId>com.alipay.sdk</groupId> 53 <groupId>com.alipay.sdk</groupId>
54 <artifactId>alipay-sdk-java</artifactId> 54 <artifactId>alipay-sdk-java</artifactId>
55 - <version>4.31.72.ALL</version> 55 + <version>4.33.44.ALL</version>
56 <exclusions> 56 <exclusions>
57 <exclusion> 57 <exclusion>
58 <groupId>org.bouncycastle</groupId> 58 <groupId>org.bouncycastle</groupId>
@@ -63,7 +63,7 @@ @@ -63,7 +63,7 @@
63 <dependency> 63 <dependency>
64 <groupId>com.github.binarywang</groupId> 64 <groupId>com.github.binarywang</groupId>
65 <artifactId>weixin-java-pay</artifactId> 65 <artifactId>weixin-java-pay</artifactId>
66 - <version>4.3.8.B</version> 66 + <version>4.4.0</version>
67 </dependency> 67 </dependency>
68 <!-- TODO 芋艿:清理 --> 68 <!-- TODO 芋艿:清理 -->
69 69
yudao-framework/yudao-spring-boot-starter-biz-weixin/pom.xml
@@ -35,12 +35,12 @@ @@ -35,12 +35,12 @@
35 <dependency> 35 <dependency>
36 <groupId>com.github.binarywang</groupId> 36 <groupId>com.github.binarywang</groupId>
37 <artifactId>wx-java-mp-spring-boot-starter</artifactId> 37 <artifactId>wx-java-mp-spring-boot-starter</artifactId>
38 - <version>4.3.8.B</version> 38 + <version>4.4.0</version>
39 </dependency> 39 </dependency>
40 <dependency> 40 <dependency>
41 <groupId>com.github.binarywang</groupId> 41 <groupId>com.github.binarywang</groupId>
42 <artifactId>wx-java-miniapp-spring-boot-starter</artifactId> 42 <artifactId>wx-java-miniapp-spring-boot-starter</artifactId>
43 - <version>4.3.8.B</version> 43 + <version>4.4.0</version>
44 </dependency> 44 </dependency>
45 <!-- TODO 芋艿:清理 --> 45 <!-- TODO 芋艿:清理 -->
46 </dependencies> 46 </dependencies>
yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/lock4j/core/DefaultLockFailureStrategy.java
@@ -5,6 +5,8 @@ import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstant @@ -5,6 +5,8 @@ import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstant
5 import com.baomidou.lock.LockFailureStrategy; 5 import com.baomidou.lock.LockFailureStrategy;
6 import lombok.extern.slf4j.Slf4j; 6 import lombok.extern.slf4j.Slf4j;
7 7
  8 +import java.lang.reflect.Method;
  9 +
8 /** 10 /**
9 * 自定义获取锁失败策略,抛出 {@link ServiceException} 异常 11 * 自定义获取锁失败策略,抛出 {@link ServiceException} 异常
10 */ 12 */
@@ -12,9 +14,8 @@ import lombok.extern.slf4j.Slf4j; @@ -12,9 +14,8 @@ import lombok.extern.slf4j.Slf4j;
12 public class DefaultLockFailureStrategy implements LockFailureStrategy { 14 public class DefaultLockFailureStrategy implements LockFailureStrategy {
13 15
14 @Override 16 @Override
15 - public void onLockFailure(String key, long acquireTimeout, int acquireCount) {  
16 - log.debug("[onLockFailure][线程:{} 获取锁失败,key:{} 获取超时时长:{} ms]", Thread.currentThread().getName(), key, acquireTimeout); 17 + public void onLockFailure(String key, Method method, Object[] arguments) {
  18 + log.debug("[onLockFailure][线程:{} 获取锁失败,key:{} 获取失败:{} ]", Thread.currentThread().getName(), key, arguments);
17 throw new ServiceException(GlobalErrorCodeConstants.LOCKED); 19 throw new ServiceException(GlobalErrorCodeConstants.LOCKED);
18 } 20 }
19 -  
20 } 21 }
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/message/BpmMessageConvert.java
@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.bpm.convert.message; @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.bpm.convert.message;
2 2
3 import cn.iocoder.yudao.module.system.api.sms.dto.send.SmsSendSingleToUserReqDTO; 3 import cn.iocoder.yudao.module.system.api.sms.dto.send.SmsSendSingleToUserReqDTO;
4 import org.mapstruct.Mapper; 4 import org.mapstruct.Mapper;
  5 +import org.mapstruct.Mapping;
5 import org.mapstruct.factory.Mappers; 6 import org.mapstruct.factory.Mappers;
6 7
7 import java.util.Map; 8 import java.util.Map;
@@ -11,6 +12,10 @@ public interface BpmMessageConvert { @@ -11,6 +12,10 @@ public interface BpmMessageConvert {
11 12
12 BpmMessageConvert INSTANCE = Mappers.getMapper(BpmMessageConvert.class); 13 BpmMessageConvert INSTANCE = Mappers.getMapper(BpmMessageConvert.class);
13 14
  15 + @Mapping(target = "mobile", ignore = true)
  16 + @Mapping(source = "userId", target = "userId")
  17 + @Mapping(source = "templateCode", target = "templateCode")
  18 + @Mapping(source = "templateParams", target = "templateParams")
14 SmsSendSingleToUserReqDTO convert(Long userId, String templateCode, Map<String, Object> templateParams); 19 SmsSendSingleToUserReqDTO convert(Long userId, String templateCode, Map<String, Object> templateParams);
15 20
16 } 21 }
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/views/index.vue.vm
@@ -50,23 +50,29 @@ @@ -50,23 +50,29 @@
50 50
51 // 提交按钮 51 // 提交按钮
52 const submitForm = async () => { 52 const submitForm = async () => {
53 - actionLoading.value = true  
54 - // 提交请求  
55 - try {  
56 - const data = unref(formRef)?.formModel as ${simpleClassName}VO  
57 - if (actionType.value === 'create') {  
58 - await ${simpleClassName}Api.create${simpleClassName}Api(data)  
59 - ElMessage.success(t('common.createSuccess'))  
60 - } else {  
61 - await ${simpleClassName}Api.update${simpleClassName}Api(data)  
62 - ElMessage.success(t('common.updateSuccess')) 53 + const elForm = unref(formRef)?.getElFormRef()
  54 + if (!elForm) return
  55 + elForm.validate(async (valid) => {
  56 + if (valid) {
  57 + actionLoading.value = true
  58 + // 提交请求
  59 + try {
  60 + const data = unref(formRef)?.formModel as ${simpleClassName}VO
  61 + if (actionType.value === 'create') {
  62 + await ${simpleClassName}Api.create${simpleClassName}Api(data)
  63 + ElMessage.success(t('common.createSuccess'))
  64 + } else {
  65 + await ${simpleClassName}Api.update${simpleClassName}Api(data)
  66 + ElMessage.success(t('common.updateSuccess'))
  67 + }
  68 + // 操作成功,重新加载列表
  69 + dialogVisible.value = false
  70 + await getList()
  71 + } finally {
  72 + actionLoading.value = false
  73 + }
63 } 74 }
64 - // 操作成功,重新加载列表  
65 - dialogVisible.value = false  
66 - await getList()  
67 - } finally {  
68 - actionLoading.value = false  
69 - } 75 + })
70 } 76 }
71 77
72 // ========== 详情相关 ========== 78 // ========== 详情相关 ==========
yudao-module-mall/yudao-module-coupon-biz/pom.xml
@@ -30,10 +30,6 @@ @@ -30,10 +30,6 @@
30 <groupId>cn.iocoder.boot</groupId> 30 <groupId>cn.iocoder.boot</groupId>
31 <artifactId>yudao-spring-boot-starter-biz-operatelog</artifactId> 31 <artifactId>yudao-spring-boot-starter-biz-operatelog</artifactId>
32 </dependency> 32 </dependency>
33 - <dependency>  
34 - <groupId>cn.iocoder.boot</groupId>  
35 - <artifactId>yudao-spring-boot-starter-biz-tenant</artifactId>  
36 - </dependency>  
37 33
38 <!-- Web 相关 --> 34 <!-- Web 相关 -->
39 <dependency> 35 <dependency>
@@ -42,7 +38,7 @@ @@ -42,7 +38,7 @@
42 </dependency> 38 </dependency>
43 <dependency> 39 <dependency>
44 <groupId>cn.iocoder.boot</groupId> 40 <groupId>cn.iocoder.boot</groupId>
45 - <artifactId>yudao-spring-boot-starter-excel</artifactId> 41 + <artifactId>yudao-spring-boot-starter-security</artifactId>
46 </dependency> 42 </dependency>
47 43
48 <!-- DB 相关 --> 44 <!-- DB 相关 -->
@@ -57,6 +53,12 @@ @@ -57,6 +53,12 @@
57 <artifactId>yudao-spring-boot-starter-test</artifactId> 53 <artifactId>yudao-spring-boot-starter-test</artifactId>
58 </dependency> 54 </dependency>
59 55
  56 + <!-- 工具类相关 -->
  57 + <dependency>
  58 + <groupId>cn.iocoder.boot</groupId>
  59 + <artifactId>yudao-spring-boot-starter-excel</artifactId>
  60 + </dependency>
  61 +
60 </dependencies> 62 </dependencies>
61 63
62 -</project>  
63 \ No newline at end of file 64 \ No newline at end of file
  65 +</project>
yudao-module-mall/yudao-module-coupon-biz/src/main/java/cn/iocoder/yudao/module/coupon/service/coupon/CouponServiceImpl.java
@@ -4,7 +4,6 @@ import cn.hutool.core.bean.BeanUtil; @@ -4,7 +4,6 @@ import cn.hutool.core.bean.BeanUtil;
4 import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; 4 import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
5 import cn.iocoder.yudao.module.coupon.dal.dataobject.CouponTemplete.CouponTempleteDO; 5 import cn.iocoder.yudao.module.coupon.dal.dataobject.CouponTemplete.CouponTempleteDO;
6 import cn.iocoder.yudao.module.coupon.dal.mysql.CouponTemplete.CouponTempleteMapper; 6 import cn.iocoder.yudao.module.coupon.dal.mysql.CouponTemplete.CouponTempleteMapper;
7 -import io.micrometer.core.instrument.Counter;  
8 import org.springframework.stereotype.Service; 7 import org.springframework.stereotype.Service;
9 import javax.annotation.Resource; 8 import javax.annotation.Resource;
10 import org.springframework.validation.annotation.Validated; 9 import org.springframework.validation.annotation.Validated;
yudao-module-mall/yudao-module-market-biz/pom.xml
@@ -39,16 +39,16 @@ @@ -39,16 +39,16 @@
39 <groupId>cn.iocoder.boot</groupId> 39 <groupId>cn.iocoder.boot</groupId>
40 <artifactId>yudao-spring-boot-starter-biz-weixin</artifactId> 40 <artifactId>yudao-spring-boot-starter-biz-weixin</artifactId>
41 </dependency> 41 </dependency>
42 - <dependency>  
43 - <groupId>cn.iocoder.boot</groupId>  
44 - <artifactId>yudao-spring-boot-starter-biz-tenant</artifactId>  
45 - </dependency>  
46 42
47 <!-- Web 相关 --> 43 <!-- Web 相关 -->
48 <dependency> 44 <dependency>
49 <groupId>cn.iocoder.boot</groupId> 45 <groupId>cn.iocoder.boot</groupId>
50 <artifactId>yudao-spring-boot-starter-web</artifactId> 46 <artifactId>yudao-spring-boot-starter-web</artifactId>
51 </dependency> 47 </dependency>
  48 + <dependency>
  49 + <groupId>cn.iocoder.boot</groupId>
  50 + <artifactId>yudao-spring-boot-starter-security</artifactId>
  51 + </dependency>
52 52
53 <!-- DB 相关 --> 53 <!-- DB 相关 -->
54 <dependency> 54 <dependency>
@@ -61,6 +61,12 @@ @@ -61,6 +61,12 @@
61 <groupId>cn.iocoder.boot</groupId> 61 <groupId>cn.iocoder.boot</groupId>
62 <artifactId>yudao-spring-boot-starter-test</artifactId> 62 <artifactId>yudao-spring-boot-starter-test</artifactId>
63 </dependency> 63 </dependency>
  64 +
  65 + <!-- 工具类相关 -->
  66 + <dependency>
  67 + <groupId>cn.iocoder.boot</groupId>
  68 + <artifactId>yudao-spring-boot-starter-excel</artifactId>
  69 + </dependency>
64 </dependencies> 70 </dependencies>
65 71
66 </project> 72 </project>
yudao-module-mall/yudao-module-market-biz/src/main/java/cn/iocoder/yudao/module/market/controller/app/banner/AppBannerController.java
@@ -7,13 +7,13 @@ import cn.iocoder.yudao.module.market.dal.dataobject.banner.BannerDO; @@ -7,13 +7,13 @@ import cn.iocoder.yudao.module.market.dal.dataobject.banner.BannerDO;
7 import cn.iocoder.yudao.module.market.service.banner.BannerService; 7 import cn.iocoder.yudao.module.market.service.banner.BannerService;
8 import io.swagger.annotations.Api; 8 import io.swagger.annotations.Api;
9 import io.swagger.annotations.ApiOperation; 9 import io.swagger.annotations.ApiOperation;
10 -import org.springframework.beans.factory.annotation.Autowired;  
11 import org.springframework.security.access.prepost.PreAuthorize; 10 import org.springframework.security.access.prepost.PreAuthorize;
12 import org.springframework.validation.annotation.Validated; 11 import org.springframework.validation.annotation.Validated;
13 import org.springframework.web.bind.annotation.GetMapping; 12 import org.springframework.web.bind.annotation.GetMapping;
14 import org.springframework.web.bind.annotation.RequestMapping; 13 import org.springframework.web.bind.annotation.RequestMapping;
15 import org.springframework.web.bind.annotation.RestController; 14 import org.springframework.web.bind.annotation.RestController;
16 15
  16 +import javax.annotation.Resource;
17 import java.util.List; 17 import java.util.List;
18 18
19 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; 19 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@@ -27,8 +27,7 @@ import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; @@ -27,8 +27,7 @@ import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
27 @Validated 27 @Validated
28 public class AppBannerController { 28 public class AppBannerController {
29 29
30 - // TODO @xia:使用 @Resource 哈  
31 - @Autowired 30 + @Resource
32 private BannerService bannerService; 31 private BannerService bannerService;
33 32
34 // TODO @xia:新建一个 AppBannerRespVO,只返回必要的字段。status 要过滤下。然后 sort 下结果 33 // TODO @xia:新建一个 AppBannerRespVO,只返回必要的字段。status 要过滤下。然后 sort 下结果
yudao-module-mall/yudao-module-product-biz/pom.xml
@@ -29,10 +29,6 @@ @@ -29,10 +29,6 @@
29 <groupId>cn.iocoder.boot</groupId> 29 <groupId>cn.iocoder.boot</groupId>
30 <artifactId>yudao-spring-boot-starter-biz-operatelog</artifactId> 30 <artifactId>yudao-spring-boot-starter-biz-operatelog</artifactId>
31 </dependency> 31 </dependency>
32 - <dependency>  
33 - <groupId>cn.iocoder.boot</groupId>  
34 - <artifactId>yudao-spring-boot-starter-biz-tenant</artifactId>  
35 - </dependency>  
36 32
37 <!-- Web 相关 --> 33 <!-- Web 相关 -->
38 <dependency> 34 <dependency>
@@ -41,7 +37,7 @@ @@ -41,7 +37,7 @@
41 </dependency> 37 </dependency>
42 <dependency> 38 <dependency>
43 <groupId>cn.iocoder.boot</groupId> 39 <groupId>cn.iocoder.boot</groupId>
44 - <artifactId>yudao-spring-boot-starter-excel</artifactId> 40 + <artifactId>yudao-spring-boot-starter-security</artifactId>
45 </dependency> 41 </dependency>
46 42
47 <!-- DB 相关 --> 43 <!-- DB 相关 -->
@@ -56,6 +52,11 @@ @@ -56,6 +52,11 @@
56 <artifactId>yudao-spring-boot-starter-test</artifactId> 52 <artifactId>yudao-spring-boot-starter-test</artifactId>
57 </dependency> 53 </dependency>
58 54
  55 + <!-- 工具类相关 -->
  56 + <dependency>
  57 + <groupId>cn.iocoder.boot</groupId>
  58 + <artifactId>yudao-spring-boot-starter-excel</artifactId>
  59 + </dependency>
59 </dependencies> 60 </dependencies>
60 61
61 </project> 62 </project>
yudao-module-mall/yudao-module-trade-biz/pom.xml
@@ -47,10 +47,6 @@ @@ -47,10 +47,6 @@
47 <groupId>cn.iocoder.boot</groupId> 47 <groupId>cn.iocoder.boot</groupId>
48 <artifactId>yudao-spring-boot-starter-biz-operatelog</artifactId> 48 <artifactId>yudao-spring-boot-starter-biz-operatelog</artifactId>
49 </dependency> 49 </dependency>
50 - <dependency>  
51 - <groupId>cn.iocoder.boot</groupId>  
52 - <artifactId>yudao-spring-boot-starter-biz-tenant</artifactId>  
53 - </dependency>  
54 50
55 <!-- Web 相关 --> 51 <!-- Web 相关 -->
56 <dependency> 52 <dependency>
@@ -60,13 +56,13 @@ @@ -60,13 +56,13 @@
60 56
61 <dependency> 57 <dependency>
62 <groupId>cn.iocoder.boot</groupId> 58 <groupId>cn.iocoder.boot</groupId>
63 - <artifactId>yudao-spring-boot-starter-biz-pay</artifactId> 59 + <artifactId>yudao-spring-boot-starter-security</artifactId>
64 </dependency> 60 </dependency>
65 61
66 - <!-- DB 相关 --> 62 + <!-- TODO 芋艿:引入依赖要优化下: -->
67 <dependency> 63 <dependency>
68 <groupId>cn.iocoder.boot</groupId> 64 <groupId>cn.iocoder.boot</groupId>
69 - <artifactId>yudao-spring-boot-starter-mybatis</artifactId> 65 + <artifactId>yudao-spring-boot-starter-biz-pay</artifactId>
70 </dependency> 66 </dependency>
71 67
72 <!-- DB 相关 --> 68 <!-- DB 相关 -->
@@ -80,6 +76,12 @@ @@ -80,6 +76,12 @@
80 <groupId>cn.iocoder.boot</groupId> 76 <groupId>cn.iocoder.boot</groupId>
81 <artifactId>yudao-spring-boot-starter-test</artifactId> 77 <artifactId>yudao-spring-boot-starter-test</artifactId>
82 </dependency> 78 </dependency>
  79 +
  80 + <!-- 工具类相关 -->
  81 + <dependency>
  82 + <groupId>cn.iocoder.boot</groupId>
  83 + <artifactId>yudao-spring-boot-starter-excel</artifactId>
  84 + </dependency>
83 </dependencies> 85 </dependencies>
84 86
85 </project> 87 </project>
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/sms/dto/send/SmsSendSingleToUserReqDTO.java
@@ -4,7 +4,6 @@ import cn.iocoder.yudao.framework.common.validation.Mobile; @@ -4,7 +4,6 @@ import cn.iocoder.yudao.framework.common.validation.Mobile;
4 import lombok.Data; 4 import lombok.Data;
5 5
6 import javax.validation.constraints.NotEmpty; 6 import javax.validation.constraints.NotEmpty;
7 -import javax.validation.constraints.NotNull;  
8 import java.util.Map; 7 import java.util.Map;
9 8
10 /** 9 /**
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/oauth2/OAuth2OpenController.java
@@ -106,7 +106,7 @@ public class OAuth2OpenController { @@ -106,7 +106,7 @@ public class OAuth2OpenController {
106 @RequestParam(value = "scope", required = false) String scope, // 密码模式 106 @RequestParam(value = "scope", required = false) String scope, // 密码模式
107 @RequestParam(value = "refresh_token", required = false) String refreshToken) { // 刷新模式 107 @RequestParam(value = "refresh_token", required = false) String refreshToken) { // 刷新模式
108 List<String> scopes = OAuth2Utils.buildScopes(scope); 108 List<String> scopes = OAuth2Utils.buildScopes(scope);
109 - // 授权类型 109 + // 1.1 校验授权类型
110 OAuth2GrantTypeEnum grantTypeEnum = OAuth2GrantTypeEnum.getByGranType(grantType); 110 OAuth2GrantTypeEnum grantTypeEnum = OAuth2GrantTypeEnum.getByGranType(grantType);
111 if (grantTypeEnum == null) { 111 if (grantTypeEnum == null) {
112 throw exception0(BAD_REQUEST.getCode(), StrUtil.format("未知授权类型({})", grantType)); 112 throw exception0(BAD_REQUEST.getCode(), StrUtil.format("未知授权类型({})", grantType));
@@ -115,12 +115,12 @@ public class OAuth2OpenController { @@ -115,12 +115,12 @@ public class OAuth2OpenController {
115 throw exception0(BAD_REQUEST.getCode(), "Token 接口不支持 implicit 授权模式"); 115 throw exception0(BAD_REQUEST.getCode(), "Token 接口不支持 implicit 授权模式");
116 } 116 }
117 117
118 - // 校验客户端 118 + // 1.2 校验客户端
119 String[] clientIdAndSecret = obtainBasicAuthorization(request); 119 String[] clientIdAndSecret = obtainBasicAuthorization(request);
120 OAuth2ClientDO client = oauth2ClientService.validOAuthClientFromCache(clientIdAndSecret[0], clientIdAndSecret[1], 120 OAuth2ClientDO client = oauth2ClientService.validOAuthClientFromCache(clientIdAndSecret[0], clientIdAndSecret[1],
121 grantType, scopes, redirectUri); 121 grantType, scopes, redirectUri);
122 122
123 - // 根据授权模式,获取访问令牌 123 + // 2. 根据授权模式,获取访问令牌
124 OAuth2AccessTokenDO accessTokenDO; 124 OAuth2AccessTokenDO accessTokenDO;
125 switch (grantTypeEnum) { 125 switch (grantTypeEnum) {
126 case AUTHORIZATION_CODE: 126 case AUTHORIZATION_CODE:
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/oauth2/vo/open/OAuth2OpenCheckTokenRespVO.java
@@ -26,6 +26,7 @@ public class OAuth2OpenCheckTokenRespVO { @@ -26,6 +26,7 @@ public class OAuth2OpenCheckTokenRespVO {
26 private Long tenantId; 26 private Long tenantId;
27 27
28 @ApiModelProperty(value = "客户端编号", required = true, example = "car") 28 @ApiModelProperty(value = "客户端编号", required = true, example = "car")
  29 + @JsonProperty("client_id")
29 private String clientId; 30 private String clientId;
30 @ApiModelProperty(value = "授权范围", required = true, example = "user_info") 31 @ApiModelProperty(value = "授权范围", required = true, example = "user_info")
31 private List<String> scopes; 32 private List<String> scopes;
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/oauth2/vo/user/OAuth2UserInfoRespVO.java
@@ -17,7 +17,7 @@ public class OAuth2UserInfoRespVO { @@ -17,7 +17,7 @@ public class OAuth2UserInfoRespVO {
17 @ApiModelProperty(value = "用户编号", required = true, example = "1") 17 @ApiModelProperty(value = "用户编号", required = true, example = "1")
18 private Long id; 18 private Long id;
19 19
20 - @ApiModelProperty(value = "用户昵称", required = true, example = "芋艿") 20 + @ApiModelProperty(value = "用户账号", required = true, example = "芋艿")
21 private String username; 21 private String username;
22 22
23 @ApiModelProperty(value = "用户昵称", required = true, example = "芋道") 23 @ApiModelProperty(value = "用户昵称", required = true, example = "芋道")
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/MenuController.java
@@ -6,7 +6,6 @@ import cn.iocoder.yudao.module.system.controller.admin.permission.vo.menu.*; @@ -6,7 +6,6 @@ import cn.iocoder.yudao.module.system.controller.admin.permission.vo.menu.*;
6 import cn.iocoder.yudao.module.system.convert.permission.MenuConvert; 6 import cn.iocoder.yudao.module.system.convert.permission.MenuConvert;
7 import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO; 7 import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO;
8 import cn.iocoder.yudao.module.system.service.permission.MenuService; 8 import cn.iocoder.yudao.module.system.service.permission.MenuService;
9 -import cn.iocoder.yudao.module.system.service.tenant.TenantService;  
10 import io.swagger.annotations.Api; 9 import io.swagger.annotations.Api;
11 import io.swagger.annotations.ApiImplicitParam; 10 import io.swagger.annotations.ApiImplicitParam;
12 import io.swagger.annotations.ApiOperation; 11 import io.swagger.annotations.ApiOperation;
@@ -29,8 +28,6 @@ public class MenuController { @@ -29,8 +28,6 @@ public class MenuController {
29 28
30 @Resource 29 @Resource
31 private MenuService menuService; 30 private MenuService menuService;
32 - @Resource  
33 - private TenantService tenantService;  
34 31
35 @PostMapping("/create") 32 @PostMapping("/create")
36 @ApiOperation("创建菜单") 33 @ApiOperation("创建菜单")
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/tenant/TenantPackageDO.java
@@ -34,7 +34,7 @@ public class TenantPackageDO extends BaseDO { @@ -34,7 +34,7 @@ public class TenantPackageDO extends BaseDO {
34 */ 34 */
35 private String name; 35 private String name;
36 /** 36 /**
37 - * 租户状态 37 + * 租户套餐状态
38 * 38 *
39 * 枚举 {@link CommonStatusEnum} 39 * 枚举 {@link CommonStatusEnum}
40 */ 40 */
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2GrantService.java
@@ -12,7 +12,7 @@ import java.util.List; @@ -12,7 +12,7 @@ import java.util.List;
12 * 将自身的 AdminUser 用户,授权给第三方应用,采用 OAuth2.0 的协议。 12 * 将自身的 AdminUser 用户,授权给第三方应用,采用 OAuth2.0 的协议。
13 * 13 *
14 * 问题:为什么自身也作为一个第三方应用,也走这套流程呢? 14 * 问题:为什么自身也作为一个第三方应用,也走这套流程呢?
15 - * 回复:当然可以这么做,采用 Implicit 模式。考虑到大多数开发者使用不到这个特性,OAuth2.0 毕竟有一定学习成本,所以暂时没有采取这种方式。 15 + * 回复:当然可以这么做,采用 password 模式。考虑到大多数开发者使用不到这个特性,OAuth2.0 毕竟有一定学习成本,所以暂时没有采取这种方式。
16 * 16 *
17 * @author 芋道源码 17 * @author 芋道源码
18 */ 18 */
yudao-server/src/main/resources/application-local.yaml
@@ -45,33 +45,33 @@ spring: @@ -45,33 +45,33 @@ spring:
45 datasource: 45 datasource:
46 master: 46 master:
47 name: ruoyi-vue-pro 47 name: ruoyi-vue-pro
48 - url: jdbc:mysql://139.9.196.247:3306/${spring.datasource.dynamic.datasource.master.name}?allowMultiQueries=true&useUnicode=true&useSSL=false&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&autoReconnect=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例 48 + url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.master.name}?allowMultiQueries=true&useUnicode=true&useSSL=false&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&autoReconnect=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例
49 # url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.master.name}?useSSL=false&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT # MySQL Connector/J 5.X 连接的示例 49 # url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.master.name}?useSSL=false&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT # MySQL Connector/J 5.X 连接的示例
50 # url: jdbc:postgresql://127.0.0.1:5432/${spring.datasource.dynamic.datasource.slave.name} # PostgreSQL 连接的示例 50 # url: jdbc:postgresql://127.0.0.1:5432/${spring.datasource.dynamic.datasource.slave.name} # PostgreSQL 连接的示例
51 # url: jdbc:oracle:thin:@127.0.0.1:1521:xe # Oracle 连接的示例 51 # url: jdbc:oracle:thin:@127.0.0.1:1521:xe # Oracle 连接的示例
52 # url: jdbc:sqlserver://127.0.0.1:1433;DatabaseName=${spring.datasource.dynamic.datasource.master.name} # SQLServer 连接的示例 52 # url: jdbc:sqlserver://127.0.0.1:1433;DatabaseName=${spring.datasource.dynamic.datasource.master.name} # SQLServer 连接的示例
53 username: root 53 username: root
54 - password: ${RUOYI_VUE_PRO} 54 + password: 123456
55 # username: sa 55 # username: sa
56 # password: JSm:g(*%lU4ZAkz06cd52KqT3)i1?H7W 56 # password: JSm:g(*%lU4ZAkz06cd52KqT3)i1?H7W
57 slave: # 模拟从库,可根据自己需要修改 57 slave: # 模拟从库,可根据自己需要修改
58 name: ruoyi-vue-pro 58 name: ruoyi-vue-pro
59 - url: jdbc:mysql://139.9.196.247:3306/${spring.datasource.dynamic.datasource.slave.name}?allowMultiQueries=true&useUnicode=true&useSSL=false&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&autoReconnect=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例 59 + url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.slave.name}?allowMultiQueries=true&useUnicode=true&useSSL=false&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&autoReconnect=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例
60 # url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.slave.name}?useSSL=false&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT # MySQL Connector/J 5.X 连接的示例 60 # url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.slave.name}?useSSL=false&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT # MySQL Connector/J 5.X 连接的示例
61 # url: jdbc:postgresql://127.0.0.1:5432/${spring.datasource.dynamic.datasource.slave.name} # PostgreSQL 连接的示例 61 # url: jdbc:postgresql://127.0.0.1:5432/${spring.datasource.dynamic.datasource.slave.name} # PostgreSQL 连接的示例
62 # url: jdbc:oracle:thin:@127.0.0.1:1521:xe # Oracle 连接的示例 62 # url: jdbc:oracle:thin:@127.0.0.1:1521:xe # Oracle 连接的示例
63 # url: jdbc:sqlserver://127.0.0.1:1433;DatabaseName=${spring.datasource.dynamic.datasource.slave.name} # SQLServer 连接的示例 63 # url: jdbc:sqlserver://127.0.0.1:1433;DatabaseName=${spring.datasource.dynamic.datasource.slave.name} # SQLServer 连接的示例
64 username: root 64 username: root
65 - password: ${RUOYI_VUE_PRO} 65 + password: 123456
66 # username: sa 66 # username: sa
67 # password: JSm:g(*%lU4ZAkz06cd52KqT3)i1?H7W 67 # password: JSm:g(*%lU4ZAkz06cd52KqT3)i1?H7W
68 68
69 # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优 69 # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优
70 redis: 70 redis:
71 - host: 139.9.196.247 # 地址 71 + host: 127.0.0.1 # 地址
72 port: 6379 # 端口 72 port: 6379 # 端口
73 database: 0 # 数据库索引 73 database: 0 # 数据库索引
74 - password: 123456 # 密码,建议生产环境开启 74 +# password: 123456 # 密码,建议生产环境开启
75 75
76 jasypt: 76 jasypt:
77 encryptor: 77 encryptor:
@@ -198,7 +198,7 @@ wx: @@ -198,7 +198,7 @@ wx:
198 # 芋道配置项,设置当前项目所有自定义的配置 198 # 芋道配置项,设置当前项目所有自定义的配置
199 yudao: 199 yudao:
200 captcha: 200 captcha:
201 - enable: false # 本地环境,暂时关闭图片验证码,方便登录等接口的测试 201 + enable: false # 本地环境,暂时关闭图片验证码,方便登录等接口的测试;
202 security: 202 security:
203 mock-enable: true 203 mock-enable: true
204 xss: 204 xss:
yudao-ui-admin-vue3/README.md
@@ -27,17 +27,17 @@ @@ -27,17 +27,17 @@
27 27
28 | 框架 | 说明 | 版本 | 28 | 框架 | 说明 | 版本 |
29 | --- | --- |--------| 29 | --- | --- |--------|
30 -| [Vue](https://staging-cn.vuejs.org/) | vue 框架 | 3.2.37 |  
31 -| [Vite](https://cn.vitejs.dev//) | 开发与构建工具 | 3.0.4 |  
32 -| [Element Plus](https://element-plus.org/zh-CN/) | Element Plus | 2.2.12 |  
33 -| [TypeScript](https://www.typescriptlang.org/docs/) | JavaScript 的超集 | 4.7.4 |  
34 -| [pinia](https://pinia.vuejs.org/) | Vue 存储库 替代 vuex5 | 2.0.17 |  
35 -| [vueuse](https://vueuse.org//) | 常用工具集 | 9.0.2 |  
36 -| [vue-i18n](https://kazupon.github.io/vue-i18n/zh/introduction.html/) | 国际化 | 9.2.0 |  
37 -| [vue-router](https://router.vuejs.org/) | vue 路由 | 4.1.3 | 30 +| [Vue](https://staging-cn.vuejs.org/) | vue 框架 | 3.2.41 |
  31 +| [Vite](https://cn.vitejs.dev//) | 开发与构建工具 | 3.1.8 |
  32 +| [Element Plus](https://element-plus.org/zh-CN/) | Element Plus | 2.2.18 |
  33 +| [TypeScript](https://www.typescriptlang.org/docs/) | JavaScript 的超集 | 4.8.4 |
  34 +| [pinia](https://pinia.vuejs.org/) | Vue 存储库 替代 vuex5 | 2.0.23 |
  35 +| [vueuse](https://vueuse.org//) | 常用工具集 | 9.3.1 |
  36 +| [vue-i18n](https://kazupon.github.io/vue-i18n/zh/introduction.html/) | 国际化 | 9.2.2 |
  37 +| [vue-router](https://router.vuejs.org/) | vue 路由 | 4.1.5 |
38 | [windicss](https://cn.windicss.org/) | 下一代工具优先的 CSS 框架 | 3.5.6 | 38 | [windicss](https://cn.windicss.org/) | 下一代工具优先的 CSS 框架 | 3.5.6 |
39 -| [iconify](https://icon-sets.iconify.design/) | 在线图标库 | 2.2.1 |  
40 -| [wangeditor](https://www.wangeditor.com/) | 富文本编辑器 | 5.1.14 | 39 +| [iconify](https://icon-sets.iconify.design/) | 在线图标库 | 3.0.0 |
  40 +| [wangeditor](https://www.wangeditor.com/) | 富文本编辑器 | 5.1.21 |
41 41
42 ### 推荐 VScode 开发,插件如下 42 ### 推荐 VScode 开发,插件如下
43 43
yudao-ui-admin-vue3/package.json
1 { 1 {
2 "name": "ruoyi-vue-pro-vue3", 2 "name": "ruoyi-vue-pro-vue3",
3 - "version": "1.6.3.1611", 3 + "version": "1.6.4.1811",
4 "description": "基于vue3、element-plus、typesScript、vite3", 4 "description": "基于vue3、element-plus、typesScript、vite3",
5 "author": "xingyu", 5 "author": "xingyu",
6 "private": false, 6 "private": false,
@@ -10,7 +10,7 @@ @@ -10,7 +10,7 @@
10 "ts:check": "vue-tsc --noEmit", 10 "ts:check": "vue-tsc --noEmit",
11 "build:pro": "vite build --mode pro", 11 "build:pro": "vite build --mode pro",
12 "build:dev": "vite build --mode dev", 12 "build:dev": "vite build --mode dev",
13 - "build:test": "pnpm run ts:check && vite build --mode test", 13 + "build:test": "npm run ts:check && vite build --mode test",
14 "serve:pro": "vite preview --mode pro", 14 "serve:pro": "vite preview --mode pro",
15 "serve:dev": "vite preview --mode dev", 15 "serve:dev": "vite preview --mode dev",
16 "serve:test": "vite preview --mode test", 16 "serve:test": "vite preview --mode test",
@@ -25,84 +25,84 @@ @@ -25,84 +25,84 @@
25 "p": "plop" 25 "p": "plop"
26 }, 26 },
27 "dependencies": { 27 "dependencies": {
28 - "@iconify/iconify": "^2.2.1",  
29 - "@vueuse/core": "^9.1.0",  
30 - "@wangeditor/editor": "^5.1.14", 28 + "@iconify/iconify": "^3.0.0",
  29 + "@vueuse/core": "^9.3.1",
  30 + "@wangeditor/editor": "^5.1.21",
31 "@wangeditor/editor-for-vue": "^5.1.10", 31 "@wangeditor/editor-for-vue": "^5.1.10",
32 - "@zxcvbn-ts/core": "^2.0.4", 32 + "@zxcvbn-ts/core": "^2.0.5",
33 "animate.css": "^4.1.1", 33 "animate.css": "^4.1.1",
34 "axios": "^0.27.2", 34 "axios": "^0.27.2",
35 "crypto-js": "^4.1.1", 35 "crypto-js": "^4.1.1",
36 - "dayjs": "^1.11.4",  
37 - "echarts": "^5.3.3", 36 + "dayjs": "^1.11.5",
  37 + "echarts": "^5.4.0",
38 "echarts-wordcloud": "^2.0.0", 38 "echarts-wordcloud": "^2.0.0",
39 - "element-plus": "2.2.12", 39 + "element-plus": "2.2.18",
40 "intro.js": "^6.0.0", 40 "intro.js": "^6.0.0",
41 "jsencrypt": "^3.2.1", 41 "jsencrypt": "^3.2.1",
42 "lodash-es": "^4.17.21", 42 "lodash-es": "^4.17.21",
43 "mitt": "^3.0.0", 43 "mitt": "^3.0.0",
44 "nprogress": "^0.2.0", 44 "nprogress": "^0.2.0",
45 - "pinia": "^2.0.17",  
46 - "pinia-plugin-persist": "^1.0.0", 45 + "pinia": "^2.0.23",
47 "qrcode": "^1.5.1", 46 "qrcode": "^1.5.1",
48 "qs": "^6.11.0", 47 "qs": "^6.11.0",
49 "url": "^0.11.0", 48 "url": "^0.11.0",
50 - "vue": "3.2.37", 49 + "vue": "3.2.41",
51 "vue-cropper": "^1.0.3", 50 "vue-cropper": "^1.0.3",
52 "vue-i18n": "9.2.2", 51 "vue-i18n": "9.2.2",
53 - "vue-router": "^4.1.3", 52 + "vue-router": "^4.1.5",
54 "vue-types": "^4.2.1", 53 "vue-types": "^4.2.1",
55 "web-storage-cache": "^1.1.1" 54 "web-storage-cache": "^1.1.1"
56 }, 55 },
57 "devDependencies": { 56 "devDependencies": {
58 - "@commitlint/cli": "^17.0.3",  
59 - "@commitlint/config-conventional": "^17.0.3",  
60 - "@iconify/json": "^2.1.89",  
61 - "@intlify/vite-plugin-vue-i18n": "^6.0.0", 57 + "@commitlint/cli": "^17.1.2",
  58 + "@commitlint/config-conventional": "^17.1.0",
  59 + "@iconify/json": "^2.1.122",
  60 + "@intlify/vite-plugin-vue-i18n": "^6.0.3",
62 "@purge-icons/generated": "^0.9.0", 61 "@purge-icons/generated": "^0.9.0",
63 "@types/intro.js": "^5.1.0", 62 "@types/intro.js": "^5.1.0",
64 "@types/lodash-es": "^4.17.6", 63 "@types/lodash-es": "^4.17.6",
65 - "@types/node": "^18.6.5", 64 + "@types/node": "^18.11.0",
66 "@types/nprogress": "^0.2.0", 65 "@types/nprogress": "^0.2.0",
67 - "@types/qrcode": "^1.4.2", 66 + "@types/qrcode": "^1.5.0",
68 "@types/qs": "^6.9.7", 67 "@types/qs": "^6.9.7",
69 - "@typescript-eslint/eslint-plugin": "^5.33.0",  
70 - "@typescript-eslint/parser": "^5.33.0",  
71 - "@vitejs/plugin-vue": "^3.0.1",  
72 - "@vitejs/plugin-vue-jsx": "^2.0.0",  
73 - "autoprefixer": "^10.4.8",  
74 - "eslint": "^8.21.0", 68 + "@typescript-eslint/eslint-plugin": "^5.40.0",
  69 + "@typescript-eslint/parser": "^5.40.0",
  70 + "@vitejs/plugin-vue": "^3.1.2",
  71 + "@vitejs/plugin-vue-jsx": "^2.0.1",
  72 + "autoprefixer": "^10.4.12",
  73 + "eslint": "^8.25.0",
75 "eslint-config-prettier": "^8.5.0", 74 "eslint-config-prettier": "^8.5.0",
76 - "eslint-define-config": "^1.6.0", 75 + "eslint-define-config": "^1.7.0",
77 "eslint-plugin-prettier": "^4.2.1", 76 "eslint-plugin-prettier": "^4.2.1",
78 - "eslint-plugin-vue": "^9.3.0", 77 + "eslint-plugin-vue": "^9.6.0",
79 "less": "^4.1.3", 78 "less": "^4.1.3",
80 "lint-staged": "^13.0.3", 79 "lint-staged": "^13.0.3",
81 "plop": "^3.1.1", 80 "plop": "^3.1.1",
82 - "postcss": "^8.4.16", 81 + "postcss": "^8.4.18",
83 "postcss-html": "^1.5.0", 82 "postcss-html": "^1.5.0",
84 "postcss-less": "^6.0.0", 83 "postcss-less": "^6.0.0",
85 "prettier": "^2.7.1", 84 "prettier": "^2.7.1",
86 "rimraf": "^3.0.2", 85 "rimraf": "^3.0.2",
87 - "rollup": "^2.77.2",  
88 - "stylelint": "^14.9.1", 86 + "rollup": "^2.79.1",
  87 + "stylelint": "^14.14.0",
89 "stylelint-config-html": "^1.1.0", 88 "stylelint-config-html": "^1.1.0",
90 "stylelint-config-prettier": "^9.0.3", 89 "stylelint-config-prettier": "^9.0.3",
91 - "stylelint-config-recommended": "^8.0.0",  
92 - "stylelint-config-standard": "^26.0.0", 90 + "stylelint-config-recommended": "^9.0.0",
  91 + "stylelint-config-standard": "^29.0.0",
93 "stylelint-order": "^5.0.0", 92 "stylelint-order": "^5.0.0",
94 - "typescript": "4.7.4",  
95 - "unplugin-vue-define-options": "^0.7.3",  
96 - "vite": "3.0.5", 93 + "typescript": "4.8.4",
  94 + "unplugin-vue-macros": "^0.13.2",
  95 + "vite": "3.1.8",
97 "vite-plugin-compression": "^0.5.1", 96 "vite-plugin-compression": "^0.5.1",
98 - "vite-plugin-eslint": "^1.7.0", 97 + "vite-plugin-eslint": "^1.8.1",
99 "vite-plugin-html": "^3.2.0", 98 "vite-plugin-html": "^3.2.0",
100 - "vite-plugin-purge-icons": "^0.9.0",  
101 - "vite-plugin-style-import": "^2.0.0", 99 + "vite-plugin-purge-icons": "^0.9.1",
  100 + "vite-plugin-style-import": "2.0.0",
102 "vite-plugin-svg-icons": "^2.0.1", 101 "vite-plugin-svg-icons": "^2.0.1",
103 - "vite-plugin-windicss": "^1.8.7",  
104 - "vue-tsc": "^0.39.5",  
105 - "windicss": "^3.5.6" 102 + "vite-plugin-windicss": "^1.8.8",
  103 + "vue-tsc": "^1.0.8",
  104 + "windicss": "^3.5.6",
  105 + "windicss-analysis": "^0.3.5"
106 }, 106 },
107 "engines": { 107 "engines": {
108 "node": ">= 14.18.0" 108 "node": ">= 14.18.0"
yudao-ui-admin-vue3/src/api/system/tenant/types.ts
@@ -4,6 +4,8 @@ export type TenantVO = { @@ -4,6 +4,8 @@ export type TenantVO = {
4 packageId: number 4 packageId: number
5 contactName: string 5 contactName: string
6 contactMobile: string 6 contactMobile: string
  7 + username: string
  8 + password: string
7 accountCount: number 9 accountCount: number
8 expireTime: string 10 expireTime: string
9 domain: string 11 domain: string
yudao-ui-admin-vue3/src/components/ConfigGlobal/src/ConfigGlobal.vue
@@ -7,13 +7,14 @@ import { useWindowSize } from &#39;@vueuse/core&#39; @@ -7,13 +7,14 @@ import { useWindowSize } from &#39;@vueuse/core&#39;
7 import { useAppStore } from '@/store/modules/app' 7 import { useAppStore } from '@/store/modules/app'
8 import { setCssVar } from '@/utils' 8 import { setCssVar } from '@/utils'
9 import { useDesign } from '@/hooks/web/useDesign' 9 import { useDesign } from '@/hooks/web/useDesign'
  10 +import { ElementPlusSize } from '@/types/elementPlus'
10 11
11 const { variables } = useDesign() 12 const { variables } = useDesign()
12 13
13 const appStore = useAppStore() 14 const appStore = useAppStore()
14 15
15 const props = defineProps({ 16 const props = defineProps({
16 - size: propTypes.oneOf<ElememtPlusSize[]>(['default', 'small', 'large']).def('default') 17 + size: propTypes.oneOf<ElementPlusSize[]>(['default', 'small', 'large']).def('default')
17 }) 18 })
18 19
19 provide('configGlobal', props) 20 provide('configGlobal', props)
yudao-ui-admin-vue3/src/components/ContextMenu/src/ContextMenu.vue
1 <script setup lang="ts"> 1 <script setup lang="ts">
2 import { ElDropdown, ElDropdownMenu, ElDropdownItem } from 'element-plus' 2 import { ElDropdown, ElDropdownMenu, ElDropdownItem } from 'element-plus'
3 -import { PropType, ref } from 'vue' 3 +import { PropType, ref, defineEmits } from 'vue'
4 import { useI18n } from '@/hooks/web/useI18n' 4 import { useI18n } from '@/hooks/web/useI18n'
5 import { useDesign } from '@/hooks/web/useDesign' 5 import { useDesign } from '@/hooks/web/useDesign'
6 import type { RouteLocationNormalizedLoaded } from 'vue-router' 6 import type { RouteLocationNormalizedLoaded } from 'vue-router'
  7 +import { contextMenuSchema } from '../../../types/contextMenu'
7 8
8 const { getPrefixCls } = useDesign() 9 const { getPrefixCls } = useDesign()
9 10
yudao-ui-admin-vue3/src/components/CountTo/src/CountTo.vue
1 <script setup lang="ts"> 1 <script setup lang="ts">
2 -import { reactive, computed, watch, onMounted, unref, toRef, PropType } from 'vue' 2 +import { reactive, computed, watch, onMounted, unref, toRef, PropType, defineEmits } from 'vue'
3 import { isNumber } from '@/utils/is' 3 import { isNumber } from '@/utils/is'
4 import { propTypes } from '@/utils/propTypes' 4 import { propTypes } from '@/utils/propTypes'
5 import { useDesign } from '@/hooks/web/useDesign' 5 import { useDesign } from '@/hooks/web/useDesign'
yudao-ui-admin-vue3/src/components/Crontab/src/Crontab.vue
@@ -24,7 +24,7 @@ import { @@ -24,7 +24,7 @@ import {
24 ElRadioButton, 24 ElRadioButton,
25 ElMessage 25 ElMessage
26 } from 'element-plus' 26 } from 'element-plus'
27 -import { ref, computed, onMounted, reactive, watch, PropType } from 'vue' 27 +import { ref, computed, onMounted, reactive, watch, PropType, defineEmits } from 'vue'
28 interface shortcutsType { 28 interface shortcutsType {
29 text: string 29 text: string
30 value: string 30 value: string
yudao-ui-admin-vue3/src/components/Descriptions/src/Descriptions.vue
@@ -4,6 +4,7 @@ import { useDesign } from &#39;@/hooks/web/useDesign&#39; @@ -4,6 +4,7 @@ import { useDesign } from &#39;@/hooks/web/useDesign&#39;
4 import { propTypes } from '@/utils/propTypes' 4 import { propTypes } from '@/utils/propTypes'
5 import { ref, unref, PropType, computed, useAttrs } from 'vue' 5 import { ref, unref, PropType, computed, useAttrs } from 'vue'
6 import { useAppStore } from '@/store/modules/app' 6 import { useAppStore } from '@/store/modules/app'
  7 +import { DescriptionsSchema } from '@/types/descriptions'
7 8
8 const appStore = useAppStore() 9 const appStore = useAppStore()
9 10
yudao-ui-admin-vue3/src/components/Dialog/src/Dialog.vue
@@ -74,7 +74,7 @@ const dialogStyle = computed(() =&gt; { @@ -74,7 +74,7 @@ const dialogStyle = computed(() =&gt; {
74 </slot> 74 </slot>
75 <Icon 75 <Icon
76 v-if="fullscreen" 76 v-if="fullscreen"
77 - class="mr-22px cursor-pointer is-hover mt-2px" 77 + class="mr-22px cursor-pointer is-hover mt-2px z-10"
78 :icon="isFullscreen ? 'zmdi:fullscreen-exit' : 'zmdi:fullscreen'" 78 :icon="isFullscreen ? 'zmdi:fullscreen-exit' : 'zmdi:fullscreen'"
79 color="var(--el-color-info)" 79 color="var(--el-color-info)"
80 @click="toggleFull" 80 @click="toggleFull"
yudao-ui-admin-vue3/src/components/Editor/src/Editor.vue
@@ -177,7 +177,7 @@ defineExpose({ @@ -177,7 +177,7 @@ defineExpose({
177 </script> 177 </script>
178 178
179 <template> 179 <template>
180 - <div class="border-1 border-solid border-[var(--tags-view-border-color)]"> 180 + <div class="border-1 border-solid border-[var(--tags-view-border-color)] z-3000">
181 <!-- 工具栏 --> 181 <!-- 工具栏 -->
182 <Toolbar 182 <Toolbar
183 :editor="editorRef" 183 :editor="editorRef"
yudao-ui-admin-vue3/src/components/Error/src/Error.vue
@@ -4,6 +4,7 @@ import networkError from &#39;@/assets/svgs/500.svg&#39; @@ -4,6 +4,7 @@ import networkError from &#39;@/assets/svgs/500.svg&#39;
4 import noPermission from '@/assets/svgs/403.svg' 4 import noPermission from '@/assets/svgs/403.svg'
5 import { propTypes } from '@/utils/propTypes' 5 import { propTypes } from '@/utils/propTypes'
6 import { useI18n } from '@/hooks/web/useI18n' 6 import { useI18n } from '@/hooks/web/useI18n'
  7 +import { defineEmits } from 'vue'
7 8
8 interface ErrorMap { 9 interface ErrorMap {
9 url: string 10 url: string
yudao-ui-admin-vue3/src/components/Form/index.ts
1 import Form from './src/Form.vue' 1 import Form from './src/Form.vue'
2 import { ElForm } from 'element-plus' 2 import { ElForm } from 'element-plus'
  3 +import { FormSchema, FormSetPropsType } from '@/types/form'
3 4
4 export interface FormExpose { 5 export interface FormExpose {
5 setValues: (data: Recordable) => void 6 setValues: (data: Recordable) => void
yudao-ui-admin-vue3/src/components/Form/src/Form.vue
@@ -20,6 +20,7 @@ import { findIndex } from &#39;@/utils&#39; @@ -20,6 +20,7 @@ import { findIndex } from &#39;@/utils&#39;
20 import { set } from 'lodash-es' 20 import { set } from 'lodash-es'
21 import { FormProps } from './types' 21 import { FormProps } from './types'
22 import { Icon } from '@/components/Icon' 22 import { Icon } from '@/components/Icon'
  23 +import { FormSchema, FormSetPropsType } from '@/types/form'
23 24
24 const { getPrefixCls } = useDesign() 25 const { getPrefixCls } = useDesign()
25 26
yudao-ui-admin-vue3/src/components/Form/src/componentMap.ts
@@ -20,6 +20,7 @@ import { @@ -20,6 +20,7 @@ import {
20 } from 'element-plus' 20 } from 'element-plus'
21 import { InputPassword } from '@/components/InputPassword' 21 import { InputPassword } from '@/components/InputPassword'
22 import { Editor } from '@/components/Editor' 22 import { Editor } from '@/components/Editor'
  23 +import { ComponentName } from '@/types/components'
23 24
24 const componentMap: Recordable<Component, ComponentName> = { 25 const componentMap: Recordable<Component, ComponentName> = {
25 Radio: ElRadioGroup, 26 Radio: ElRadioGroup,
yudao-ui-admin-vue3/src/components/Form/src/components/useRenderCheckbox.tsx
  1 +import { FormSchema } from '@/types/form'
1 import { ElCheckbox, ElCheckboxButton } from 'element-plus' 2 import { ElCheckbox, ElCheckboxButton } from 'element-plus'
2 import { defineComponent } from 'vue' 3 import { defineComponent } from 'vue'
3 4
yudao-ui-admin-vue3/src/components/Form/src/components/useRenderRadio.tsx
  1 +import { FormSchema } from '@/types/form'
1 import { ElRadio, ElRadioButton } from 'element-plus' 2 import { ElRadio, ElRadioButton } from 'element-plus'
2 import { defineComponent } from 'vue' 3 import { defineComponent } from 'vue'
3 4
yudao-ui-admin-vue3/src/components/Form/src/components/useRenderSelect.tsx
  1 +import { FormSchema } from '@/types/form'
  2 +import { ComponentOptions } from '@/types/components'
1 import { ElOption, ElOptionGroup } from 'element-plus' 3 import { ElOption, ElOptionGroup } from 'element-plus'
2 import { getSlot } from '@/utils/tsxHelper' 4 import { getSlot } from '@/utils/tsxHelper'
3 import { Slots } from 'vue' 5 import { Slots } from 'vue'
@@ -29,8 +31,15 @@ export const useRenderSelect = (slots: Slots) =&gt; { @@ -29,8 +31,15 @@ export const useRenderSelect = (slots: Slots) =&gt; {
29 // 如果有别名,就取别名 31 // 如果有别名,就取别名
30 const labelAlias = item?.componentProps?.optionsAlias?.labelField 32 const labelAlias = item?.componentProps?.optionsAlias?.labelField
31 const valueAlias = item?.componentProps?.optionsAlias?.valueField 33 const valueAlias = item?.componentProps?.optionsAlias?.valueField
  34 +
  35 + const { label, value, ...other } = option
  36 +
32 return ( 37 return (
33 - <ElOption label={option[labelAlias || 'label']} value={option[valueAlias || 'value']}> 38 + <ElOption
  39 + label={labelAlias ? option[labelAlias] : label}
  40 + value={valueAlias ? option[valueAlias] : value}
  41 + {...other}
  42 + >
34 {{ 43 {{
35 default: () => 44 default: () =>
36 // option 插槽名规则,{field}-option 45 // option 插槽名规则,{field}-option
yudao-ui-admin-vue3/src/components/Form/src/helper.ts
@@ -2,6 +2,8 @@ import { useI18n } from &#39;@/hooks/web/useI18n&#39; @@ -2,6 +2,8 @@ import { useI18n } from &#39;@/hooks/web/useI18n&#39;
2 import type { Slots } from 'vue' 2 import type { Slots } from 'vue'
3 import { getSlot } from '@/utils/tsxHelper' 3 import { getSlot } from '@/utils/tsxHelper'
4 import { PlaceholderMoel } from './types' 4 import { PlaceholderMoel } from './types'
  5 +import { FormSchema } from '@/types/form'
  6 +import { ColProps } from '@/types/components'
5 7
6 /** 8 /**
7 * 9 *
yudao-ui-admin-vue3/src/components/Form/src/types.ts
  1 +import { FormSchema } from '@/types/form'
  2 +
1 export interface PlaceholderMoel { 3 export interface PlaceholderMoel {
2 placeholder?: string 4 placeholder?: string
3 startPlaceholder?: string 5 startPlaceholder?: string
yudao-ui-admin-vue3/src/components/Icon/src/IconSelect.vue
1 <script setup lang="ts"> 1 <script setup lang="ts">
2 import { cloneDeep } from 'lodash-es' 2 import { cloneDeep } from 'lodash-es'
3 -import { ref, computed, CSSProperties, toRef, watch } from 'vue' 3 +import { ref, computed, CSSProperties, toRef, watch, defineEmits } from 'vue'
4 import { 4 import {
5 ElInput, 5 ElInput,
6 ElPopover, 6 ElPopover,
yudao-ui-admin-vue3/src/components/Infotip/src/Infotip.vue
1 <script setup lang="ts"> 1 <script setup lang="ts">
2 -import { PropType } from 'vue'  
3 -import { Highlight } from '@//components/Highlight' 2 +import { PropType, defineEmits } from 'vue'
  3 +import { Highlight } from '@/components/Highlight'
4 import { useDesign } from '@/hooks/web/useDesign' 4 import { useDesign } from '@/hooks/web/useDesign'
5 import { propTypes } from '@/utils/propTypes' 5 import { propTypes } from '@/utils/propTypes'
  6 +import { TipSchema } from '@/types/infoTip'
6 7
7 const { getPrefixCls } = useDesign() 8 const { getPrefixCls } = useDesign()
8 9
yudao-ui-admin-vue3/src/components/InputPassword/src/InputPassword.vue
1 <script setup lang="ts"> 1 <script setup lang="ts">
2 -import { ref, unref, computed, watch } from 'vue' 2 +import { ref, unref, computed, watch, defineEmits } from 'vue'
3 import { ElInput } from 'element-plus' 3 import { ElInput } from 'element-plus'
4 import { propTypes } from '@/utils/propTypes' 4 import { propTypes } from '@/utils/propTypes'
5 import { useConfigGlobal } from '@/hooks/web/useConfigGlobal' 5 import { useConfigGlobal } from '@/hooks/web/useConfigGlobal'
yudao-ui-admin-vue3/src/components/Menu/src/Menu.vue
@@ -3,11 +3,11 @@ import { computed, defineComponent, unref, PropType } from &#39;vue&#39; @@ -3,11 +3,11 @@ import { computed, defineComponent, unref, PropType } from &#39;vue&#39;
3 import { ElMenu, ElScrollbar } from 'element-plus' 3 import { ElMenu, ElScrollbar } from 'element-plus'
4 import { useAppStore } from '@/store/modules/app' 4 import { useAppStore } from '@/store/modules/app'
5 import { usePermissionStore } from '@/store/modules/permission' 5 import { usePermissionStore } from '@/store/modules/permission'
6 -import type { LayoutType } from '@/config/app'  
7 import { useRenderMenuItem } from './components/useRenderMenuItem' 6 import { useRenderMenuItem } from './components/useRenderMenuItem'
8 import { useRouter } from 'vue-router' 7 import { useRouter } from 'vue-router'
9 import { isUrl } from '@/utils/is' 8 import { isUrl } from '@/utils/is'
10 import { useDesign } from '@/hooks/web/useDesign' 9 import { useDesign } from '@/hooks/web/useDesign'
  10 +import { LayoutType } from '@/types/layout'
11 11
12 const { getPrefixCls } = useDesign() 12 const { getPrefixCls } = useDesign()
13 13
yudao-ui-admin-vue3/src/components/Qrcode/src/Qrcode.vue
1 <script setup lang="ts"> 1 <script setup lang="ts">
2 -import { PropType, nextTick, ref, watch, computed, unref } from 'vue' 2 +import { PropType, nextTick, ref, watch, computed, unref, defineEmits } from 'vue'
3 import QRCode from 'qrcode' 3 import QRCode from 'qrcode'
4 import { QRCodeRenderersOptions } from 'qrcode' 4 import { QRCodeRenderersOptions } from 'qrcode'
5 import { cloneDeep } from 'lodash-es' 5 import { cloneDeep } from 'lodash-es'
6 import { propTypes } from '@/utils/propTypes' 6 import { propTypes } from '@/utils/propTypes'
7 import { useDesign } from '@/hooks/web/useDesign' 7 import { useDesign } from '@/hooks/web/useDesign'
8 import { isString } from '@/utils/is' 8 import { isString } from '@/utils/is'
  9 +import { QrcodeLogo } from '@/types/qrcode'
9 10
10 const props = defineProps({ 11 const props = defineProps({
11 // img 或者 canvas,img不支持logo嵌套 12 // img 或者 canvas,img不支持logo嵌套
yudao-ui-admin-vue3/src/components/Search/src/Search.vue
1 <script setup lang="ts"> 1 <script setup lang="ts">
2 import { Form } from '@/components/Form' 2 import { Form } from '@/components/Form'
3 -import { PropType, computed, unref, ref } from 'vue' 3 +import { PropType, computed, unref, ref, defineEmits } from 'vue'
4 import { propTypes } from '@/utils/propTypes' 4 import { propTypes } from '@/utils/propTypes'
5 import { ElButton } from 'element-plus' 5 import { ElButton } from 'element-plus'
6 import { useI18n } from '@/hooks/web/useI18n' 6 import { useI18n } from '@/hooks/web/useI18n'
7 import { useForm } from '@/hooks/web/useForm' 7 import { useForm } from '@/hooks/web/useForm'
8 import { findIndex } from '@/utils' 8 import { findIndex } from '@/utils'
9 import { cloneDeep } from 'lodash-es' 9 import { cloneDeep } from 'lodash-es'
  10 +import { FormSchema } from '@/types/form'
10 11
11 const { t } = useI18n() 12 const { t } = useI18n()
12 13
yudao-ui-admin-vue3/src/components/Setting/src/Setting.vue
@@ -208,7 +208,7 @@ const clear = () =&gt; { @@ -208,7 +208,7 @@ const clear = () =&gt; {
208 <Icon icon="ep:setting" color="#fff" /> 208 <Icon icon="ep:setting" color="#fff" />
209 </div> 209 </div>
210 210
211 - <ElDrawer v-model="drawer" direction="rtl" size="350px"> 211 + <ElDrawer v-model="drawer" direction="rtl" size="350px" :z-index="4000">
212 <template #header> 212 <template #header>
213 <span class="text-16px font-700">{{ t('setting.projectSetting') }}</span> 213 <span class="text-16px font-700">{{ t('setting.projectSetting') }}</span>
214 </template> 214 </template>
yudao-ui-admin-vue3/src/components/Setting/src/components/ColorRadioPicker.vue
1 <script setup lang="ts"> 1 <script setup lang="ts">
2 -import { PropType, watch, unref, ref } from 'vue' 2 +import { PropType, watch, unref, ref, defineEmits } from 'vue'
3 import { propTypes } from '@/utils/propTypes' 3 import { propTypes } from '@/utils/propTypes'
4 import { useDesign } from '@/hooks/web/useDesign' 4 import { useDesign } from '@/hooks/web/useDesign'
5 5
yudao-ui-admin-vue3/src/components/Setting/src/components/InterfaceDisplay.vue
1 <script setup lang="ts"> 1 <script setup lang="ts">
2 -import { ElSwitch } from 'element-plus' 2 +import { ElSwitch, ElMessage } from 'element-plus'
3 import { useI18n } from '@/hooks/web/useI18n' 3 import { useI18n } from '@/hooks/web/useI18n'
4 import { useAppStore } from '@/store/modules/app' 4 import { useAppStore } from '@/store/modules/app'
5 import { computed, ref, watch } from 'vue' 5 import { computed, ref, watch } from 'vue'
@@ -107,6 +107,21 @@ const greyModeChange = (show: boolean) =&gt; { @@ -107,6 +107,21 @@ const greyModeChange = (show: boolean) =&gt; {
107 appStore.setGreyMode(show) 107 appStore.setGreyMode(show)
108 } 108 }
109 109
  110 +// 动态路由
  111 +const dynamicRouter = ref(appStore.getDynamicRouter)
  112 +
  113 +const dynamicRouterChange = (show: boolean) => {
  114 + ElMessage.info(t('setting.reExperienced'))
  115 + appStore.setDynamicRouter(show)
  116 +}
  117 +
  118 +// 固定菜单
  119 +const fixedMenu = ref(appStore.getFixedMenu)
  120 +
  121 +const fixedMenuChange = (show: boolean) => {
  122 + appStore.setFixedMenu(show)
  123 +}
  124 +
110 const layout = computed(() => appStore.getLayout) 125 const layout = computed(() => appStore.getLayout)
111 126
112 watch( 127 watch(
@@ -185,5 +200,15 @@ watch( @@ -185,5 +200,15 @@ watch(
185 <span class="text-14px">{{ t('setting.greyMode') }}</span> 200 <span class="text-14px">{{ t('setting.greyMode') }}</span>
186 <ElSwitch v-model="greyMode" @change="greyModeChange" /> 201 <ElSwitch v-model="greyMode" @change="greyModeChange" />
187 </div> 202 </div>
  203 +
  204 + <div class="flex justify-between items-center">
  205 + <span class="text-14px">{{ t('setting.dynamicRouter') }}</span>
  206 + <ElSwitch v-model="dynamicRouter" @change="dynamicRouterChange" />
  207 + </div>
  208 +
  209 + <div class="flex justify-between items-center">
  210 + <span class="text-14px">{{ t('setting.fixedMenu') }}</span>
  211 + <ElSwitch v-model="fixedMenu" @change="fixedMenuChange" />
  212 + </div>
188 </div> 213 </div>
189 </template> 214 </template>
yudao-ui-admin-vue3/src/components/SizeDropdown/src/SizeDropdown.vue
@@ -5,6 +5,7 @@ import { useAppStore } from &#39;@/store/modules/app&#39; @@ -5,6 +5,7 @@ import { useAppStore } from &#39;@/store/modules/app&#39;
5 import { useI18n } from '@/hooks/web/useI18n' 5 import { useI18n } from '@/hooks/web/useI18n'
6 import { propTypes } from '@/utils/propTypes' 6 import { propTypes } from '@/utils/propTypes'
7 import { useDesign } from '@/hooks/web/useDesign' 7 import { useDesign } from '@/hooks/web/useDesign'
  8 +import { ElementPlusSize } from '@/types/elementPlus'
8 9
9 const { getPrefixCls } = useDesign() 10 const { getPrefixCls } = useDesign()
10 11
@@ -20,7 +21,7 @@ const appStore = useAppStore() @@ -20,7 +21,7 @@ const appStore = useAppStore()
20 21
21 const sizeMap = computed(() => appStore.sizeMap) 22 const sizeMap = computed(() => appStore.sizeMap)
22 23
23 -const setCurrentSize = (size: ElememtPlusSize) => { 24 +const setCurrentSize = (size: ElementPlusSize) => {
24 appStore.setCurrentSize(size) 25 appStore.setCurrentSize(size)
25 } 26 }
26 </script> 27 </script>
yudao-ui-admin-vue3/src/components/TabMenu/src/TabMenu.vue
1 <script lang="tsx"> 1 <script lang="tsx">
2 import { usePermissionStore } from '@/store/modules/permission' 2 import { usePermissionStore } from '@/store/modules/permission'
3 import { useAppStore } from '@/store/modules/app' 3 import { useAppStore } from '@/store/modules/app'
4 -import { computed, unref, defineComponent, watch, ref } from 'vue' 4 +import { computed, unref, defineComponent, watch, ref, onMounted } from 'vue'
5 import { useI18n } from '@/hooks/web/useI18n' 5 import { useI18n } from '@/hooks/web/useI18n'
6 import { ElScrollbar } from 'element-plus' 6 import { ElScrollbar } from 'element-plus'
7 import { Icon } from '@/components/Icon' 7 import { Icon } from '@/components/Icon'
@@ -28,6 +28,8 @@ export default defineComponent({ @@ -28,6 +28,8 @@ export default defineComponent({
28 28
29 const collapse = computed(() => appStore.getCollapse) 29 const collapse = computed(() => appStore.getCollapse)
30 30
  31 + const fixedMenu = computed(() => appStore.getFixedMenu)
  32 +
31 const permissionStore = usePermissionStore() 33 const permissionStore = usePermissionStore()
32 34
33 const routers = computed(() => permissionStore.getRouters) 35 const routers = computed(() => permissionStore.getRouters)
@@ -38,6 +40,27 @@ export default defineComponent({ @@ -38,6 +40,27 @@ export default defineComponent({
38 appStore.setCollapse(!unref(collapse)) 40 appStore.setCollapse(!unref(collapse))
39 } 41 }
40 42
  43 + onMounted(() => {
  44 + if (unref(fixedMenu)) {
  45 + const path = `/${unref(currentRoute).path.split('/')[1]}`
  46 + const children = unref(tabRouters).find(
  47 + (v) =>
  48 + (v.meta?.alwaysShow || (v?.children?.length && v?.children?.length > 1)) &&
  49 + v.path === path
  50 + )?.children
  51 +
  52 + tabActive.value = path
  53 + if (children) {
  54 + permissionStore.setMenuTabRouters(
  55 + cloneDeep(children).map((v) => {
  56 + v.path = pathResolve(unref(tabActive), v.path)
  57 + return v
  58 + })
  59 + )
  60 + }
  61 + }
  62 + })
  63 +
41 watch( 64 watch(
42 () => routers.value, 65 () => routers.value,
43 (routers: AppRouteRecordRaw[]) => { 66 (routers: AppRouteRecordRaw[]) => {
@@ -66,7 +89,7 @@ export default defineComponent({ @@ -66,7 +89,7 @@ export default defineComponent({
66 ) 89 )
67 90
68 // 是否显示菜单 91 // 是否显示菜单
69 - const showMenu = ref(false) 92 + const showMenu = ref(unref(fixedMenu) ? true : false)
70 93
71 // tab高亮 94 // tab高亮
72 const tabActive = ref('') 95 const tabActive = ref('')
@@ -77,9 +100,13 @@ export default defineComponent({ @@ -77,9 +100,13 @@ export default defineComponent({
77 window.open(item.path) 100 window.open(item.path)
78 return 101 return
79 } 102 }
  103 + const newPath = item.children ? item.path : item.path.split('/')[0]
  104 + const oldPath = unref(tabActive)
80 tabActive.value = item.children ? item.path : item.path.split('/')[0] 105 tabActive.value = item.children ? item.path : item.path.split('/')[0]
81 if (item.children) { 106 if (item.children) {
82 - showMenu.value = !unref(showMenu) 107 + if (newPath === oldPath || !unref(showMenu)) {
  108 + showMenu.value = unref(fixedMenu) ? true : !unref(showMenu)
  109 + }
83 if (unref(showMenu)) { 110 if (unref(showMenu)) {
84 permissionStore.setMenuTabRouters( 111 permissionStore.setMenuTabRouters(
85 cloneDeep(item.children).map((v) => { 112 cloneDeep(item.children).map((v) => {
@@ -96,7 +123,7 @@ export default defineComponent({ @@ -96,7 +123,7 @@ export default defineComponent({
96 } 123 }
97 124
98 // 设置高亮 125 // 设置高亮
99 - const isActice = (currentPath: string) => { 126 + const isActive = (currentPath: string) => {
100 const { path } = unref(currentRoute) 127 const { path } = unref(currentRoute)
101 if (tabPathMap[currentPath].includes(path)) { 128 if (tabPathMap[currentPath].includes(path)) {
102 return true 129 return true
@@ -105,7 +132,7 @@ export default defineComponent({ @@ -105,7 +132,7 @@ export default defineComponent({
105 } 132 }
106 133
107 const mouseleave = () => { 134 const mouseleave = () => {
108 - if (!unref(showMenu)) return 135 + if (!unref(showMenu) || unref(fixedMenu)) return
109 showMenu.value = false 136 showMenu.value = false
110 } 137 }
111 138
@@ -114,7 +141,7 @@ export default defineComponent({ @@ -114,7 +141,7 @@ export default defineComponent({
114 id={`${variables.namespace}-menu`} 141 id={`${variables.namespace}-menu`}
115 class={[ 142 class={[
116 prefixCls, 143 prefixCls,
117 - 'relative bg-[var(--left-menu-bg-color)] top-1px z-999', 144 + 'relative bg-[var(--left-menu-bg-color)] top-1px z-3000',
118 { 145 {
119 'w-[var(--tab-menu-max-width)]': !unref(collapse), 146 'w-[var(--tab-menu-max-width)]': !unref(collapse),
120 'w-[var(--tab-menu-min-width)]': unref(collapse) 147 'w-[var(--tab-menu-min-width)]': unref(collapse)
@@ -140,7 +167,7 @@ export default defineComponent({ @@ -140,7 +167,7 @@ export default defineComponent({
140 `${prefixCls}__item`, 167 `${prefixCls}__item`,
141 'text-center text-12px relative py-12px cursor-pointer', 168 'text-center text-12px relative py-12px cursor-pointer',
142 { 169 {
143 - 'is-active': isActice(v.path) 170 + 'is-active': isActive(v.path)
144 } 171 }
145 ]} 172 ]}
146 onClick={() => { 173 onClick={() => {
@@ -174,8 +201,8 @@ export default defineComponent({ @@ -174,8 +201,8 @@ export default defineComponent({
174 { 201 {
175 '!left-[var(--tab-menu-min-width)]': unref(collapse), 202 '!left-[var(--tab-menu-min-width)]': unref(collapse),
176 '!left-[var(--tab-menu-max-width)]': !unref(collapse), 203 '!left-[var(--tab-menu-max-width)]': !unref(collapse),
177 - '!w-[calc(var(--left-menu-max-width)+1px)]': unref(showMenu),  
178 - '!w-0': !unref(showMenu) 204 + '!w-[calc(var(--left-menu-max-width)+1px)]': unref(showMenu) || unref(fixedMenu),
  205 + '!w-0': !unref(showMenu) && !unref(fixedMenu)
179 } 206 }
180 ]} 207 ]}
181 style="transition: width var(--transition-time-02), left var(--transition-time-02);" 208 style="transition: width var(--transition-time-02), left var(--transition-time-02);"
yudao-ui-admin-vue3/src/components/TabMenu/src/helper.ts
@@ -28,9 +28,9 @@ export const filterMenusPath = ( @@ -28,9 +28,9 @@ export const filterMenusPath = (
28 let data: Nullable<AppRouteRecordRaw> = null 28 let data: Nullable<AppRouteRecordRaw> = null
29 const meta = (v.meta ?? {}) as RouteMeta 29 const meta = (v.meta ?? {}) as RouteMeta
30 if (!meta.hidden || meta.canTo) { 30 if (!meta.hidden || meta.canTo) {
31 - const allParentPaht = getAllParentPath<AppRouteRecordRaw>(allRoutes, v.path) 31 + const allParentPath = getAllParentPath<AppRouteRecordRaw>(allRoutes, v.path)
32 32
33 - const fullPath = isUrl(v.path) ? v.path : allParentPaht.join('/') 33 + const fullPath = isUrl(v.path) ? v.path : allParentPath.join('/')
34 34
35 data = cloneDeep(v) 35 data = cloneDeep(v)
36 data.path = fullPath 36 data.path = fullPath
@@ -42,8 +42,8 @@ export const filterMenusPath = ( @@ -42,8 +42,8 @@ export const filterMenusPath = (
42 res.push(data) 42 res.push(data)
43 } 43 }
44 44
45 - if (allParentPaht.length && Reflect.has(tabPathMap, allParentPaht[0])) {  
46 - tabPathMap[allParentPaht[0]].push(fullPath) 45 + if (allParentPath.length && Reflect.has(tabPathMap, allParentPath[0])) {
  46 + tabPathMap[allParentPath[0]].push(fullPath)
47 } 47 }
48 } 48 }
49 } 49 }
yudao-ui-admin-vue3/src/components/Table/index.ts
1 import Table from './src/Table.vue' 1 import Table from './src/Table.vue'
  2 +import { ElTable } from 'element-plus'
  3 +import { TableSetPropsType } from '@/types/table'
2 4
3 export interface TableExpose { 5 export interface TableExpose {
4 setProps: (props: Recordable) => void 6 setProps: (props: Recordable) => void
5 setColumn: (columnProps: TableSetPropsType[]) => void 7 setColumn: (columnProps: TableSetPropsType[]) => void
6 selections: Recordable[] 8 selections: Recordable[]
  9 + elTableRef: ComponentRef<typeof ElTable>
7 } 10 }
8 11
9 export { Table } 12 export { Table }
yudao-ui-admin-vue3/src/components/Table/src/Table.vue
@@ -6,6 +6,7 @@ import { setIndex } from &#39;./helper&#39; @@ -6,6 +6,7 @@ import { setIndex } from &#39;./helper&#39;
6 import { getSlot } from '@/utils/tsxHelper' 6 import { getSlot } from '@/utils/tsxHelper'
7 import type { TableProps } from './types' 7 import type { TableProps } from './types'
8 import { set } from 'lodash-es' 8 import { set } from 'lodash-es'
  9 +import { Pagination, TableColumn, TableSetPropsType, TableSlotDefault } from '@/types/table'
9 10
10 export default defineComponent({ 11 export default defineComponent({
11 name: 'Table', 12 name: 'Table',
yudao-ui-admin-vue3/src/components/Table/src/types.ts
  1 +import { Pagination, TableColumn } from '@/types/table'
  2 +
1 export type TableProps = { 3 export type TableProps = {
2 pageSize?: number 4 pageSize?: number
3 currentPage?: number 5 currentPage?: number
yudao-ui-admin-vue3/src/components/UserInfo/src/UserInfo.vue
@@ -36,8 +36,8 @@ const loginOut = () =&gt; { @@ -36,8 +36,8 @@ const loginOut = () =&gt; {
36 }) 36 })
37 .then(async () => { 37 .then(async () => {
38 userStore.loginOut() 38 userStore.loginOut()
39 - tagsViewStore.delAllViews  
40 - replace('/login') 39 + tagsViewStore.delAllViews()
  40 + replace('/login?redirect=/index')
41 }) 41 })
42 .catch(() => {}) 42 .catch(() => {})
43 } 43 }
yudao-ui-admin-vue3/src/config/app.ts deleted
1 -import { useCache } from '@/hooks/web/useCache'  
2 -  
3 -const { wsCache } = useCache()  
4 -  
5 -export type LayoutType = 'classic' | 'topLeft' | 'top' | 'cutMenu'  
6 -  
7 -export type ThemeTypes = {  
8 - elColorPrimary?: string  
9 - leftMenuBorderColor?: string  
10 - leftMenuBgColor?: string  
11 - leftMenuBgLightColor?: string  
12 - leftMenuBgActiveColor?: string  
13 - leftMenuCollapseBgActiveColor?: string  
14 - leftMenuTextColor?: string  
15 - leftMenuTextActiveColor?: string  
16 - logoTitleTextColor?: string  
17 - logoBorderColor?: string  
18 - topHeaderBgColor?: string  
19 - topHeaderTextColor?: string  
20 - topHeaderHoverColor?: string  
21 - topToolBorderColor?: string  
22 -}  
23 -export interface AppState {  
24 - breadcrumb: boolean  
25 - breadcrumbIcon: boolean  
26 - collapse: boolean  
27 - uniqueOpened: boolean  
28 - hamburger: boolean  
29 - screenfull: boolean  
30 - size: boolean  
31 - locale: boolean  
32 - tagsView: boolean  
33 - tagsViewIcon: boolean  
34 - logo: boolean  
35 - fixedHeader: boolean  
36 - greyMode: boolean  
37 - pageLoading: boolean  
38 - layout: LayoutType  
39 - title: string  
40 - userInfo: string  
41 - isDark: boolean  
42 - currentSize: ElememtPlusSize  
43 - sizeMap: ElememtPlusSize[]  
44 - mobile: boolean  
45 - footer: boolean  
46 - theme: ThemeTypes  
47 -}  
48 -  
49 -export const appModules: AppState = {  
50 - userInfo: 'userInfo', // 登录信息存储字段-建议每个项目换一个字段,避免与其他项目冲突  
51 - sizeMap: ['default', 'large', 'small'],  
52 - mobile: false, // 是否是移动端  
53 - title: import.meta.env.VITE_APP_TITLE, // 标题  
54 - pageLoading: false, // 路由跳转loading  
55 -  
56 - breadcrumb: true, // 面包屑  
57 - breadcrumbIcon: false, // 面包屑图标  
58 - collapse: false, // 折叠菜单  
59 - uniqueOpened: true, // 是否只保持一个子菜单的展开  
60 - hamburger: true, // 折叠图标  
61 - screenfull: true, // 全屏图标  
62 - size: true, // 尺寸图标  
63 - locale: true, // 多语言图标  
64 - tagsView: true, // 标签页  
65 - tagsViewIcon: false, // 是否显示标签图标  
66 - logo: true, // logo  
67 - fixedHeader: true, // 固定toolheader  
68 - footer: true, // 显示页脚  
69 - greyMode: false, // 是否开始灰色模式,用于特殊悼念日  
70 -  
71 - layout: wsCache.get('layout') || 'topLeft', // layout布局  
72 - isDark: wsCache.get('isDark') || false, // 是否是暗黑模式  
73 - currentSize: wsCache.get('default') || 'default', // 组件尺寸  
74 - theme: wsCache.get('theme') || {  
75 - // 主题色  
76 - elColorPrimary: '#409eff',  
77 - // 左侧菜单边框颜色  
78 - leftMenuBorderColor: 'inherit',  
79 - // 左侧菜单背景颜色  
80 - leftMenuBgColor: '#001529',  
81 - // 左侧菜单浅色背景颜色  
82 - leftMenuBgLightColor: '#0f2438',  
83 - // 左侧菜单选中背景颜色  
84 - leftMenuBgActiveColor: 'var(--el-color-primary)',  
85 - // 左侧菜单收起选中背景颜色  
86 - leftMenuCollapseBgActiveColor: 'var(--el-color-primary)',  
87 - // 左侧菜单字体颜色  
88 - leftMenuTextColor: '#bfcbd9',  
89 - // 左侧菜单选中字体颜色  
90 - leftMenuTextActiveColor: '#fff',  
91 - // logo字体颜色  
92 - logoTitleTextColor: '#fff',  
93 - // logo边框颜色  
94 - logoBorderColor: 'inherit',  
95 - // 头部背景颜色  
96 - topHeaderBgColor: '#fff',  
97 - // 头部字体颜色  
98 - topHeaderTextColor: 'inherit',  
99 - // 头部悬停颜色  
100 - topHeaderHoverColor: '#f6f6f6',  
101 - // 头部边框颜色  
102 - topToolBorderColor: '#eee'  
103 - }  
104 -}  
yudao-ui-admin-vue3/src/config/axios/index.ts
1 -import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, AxiosError } from 'axios' 1 +import axios, {
  2 + AxiosInstance,
  3 + AxiosRequestConfig,
  4 + AxiosRequestHeaders,
  5 + AxiosResponse,
  6 + AxiosError
  7 +} from 'axios'
2 import { ElMessage, ElMessageBox, ElNotification } from 'element-plus' 8 import { ElMessage, ElMessageBox, ElNotification } from 'element-plus'
3 import qs from 'qs' 9 import qs from 'qs'
4 import { config } from '@/config/axios/config' 10 import { config } from '@/config/axios/config'
@@ -48,7 +54,8 @@ service.interceptors.request.use( @@ -48,7 +54,8 @@ service.interceptors.request.use(
48 const data = config.data || false 54 const data = config.data || false
49 if ( 55 if (
50 config.method?.toUpperCase() === 'POST' && 56 config.method?.toUpperCase() === 'POST' &&
51 - config!.headers!['Content-Type'] === 'application/x-www-form-urlencoded' 57 + (config.headers as AxiosRequestHeaders)['Content-Type'] ===
  58 + 'application/x-www-form-urlencoded'
52 ) { 59 ) {
53 config.data = qs.stringify(data) 60 config.data = qs.stringify(data)
54 } 61 }
@@ -121,12 +128,18 @@ service.interceptors.response.use( @@ -121,12 +128,18 @@ service.interceptors.response.use(
121 const refreshTokenRes = await refreshToken() 128 const refreshTokenRes = await refreshToken()
122 // 2.1 刷新成功,则回放队列的请求 + 当前请求 129 // 2.1 刷新成功,则回放队列的请求 + 当前请求
123 setToken(refreshTokenRes.data) 130 setToken(refreshTokenRes.data)
124 - requestList.forEach((cb: any) => cb()) 131 + ;(config as Recordable).headers.Authorization = 'Bearer ' + getAccessToken()
  132 + requestList.forEach((cb: any) => {
  133 + cb()
  134 + })
  135 + requestList = []
125 return service(response.config) 136 return service(response.config)
126 } catch (e) { 137 } catch (e) {
127 // 为什么需要 catch 异常呢?刷新失败时,请求因为 Promise.reject 触发异常。 138 // 为什么需要 catch 异常呢?刷新失败时,请求因为 Promise.reject 触发异常。
128 // 2.2 刷新失败,只回放队列的请求 139 // 2.2 刷新失败,只回放队列的请求
129 - requestList.forEach((cb: any) => cb()) 140 + requestList.forEach((cb: any) => {
  141 + cb()
  142 + })
130 // 提示是否要登出。即不回放当前请求!不然会形成递归 143 // 提示是否要登出。即不回放当前请求!不然会形成递归
131 return handleAuthorized() 144 return handleAuthorized()
132 } finally { 145 } finally {
@@ -187,10 +200,7 @@ service.interceptors.response.use( @@ -187,10 +200,7 @@ service.interceptors.response.use(
187 ) 200 )
188 201
189 const refreshToken = async () => { 202 const refreshToken = async () => {
190 - return await service({  
191 - url: '/system/auth/refresh-token?refreshToken=' + getRefreshToken(),  
192 - method: 'post'  
193 - }) 203 + return await axios.post(base_url + '/system/auth/refresh-token?refreshToken=' + getRefreshToken())
194 } 204 }
195 const handleAuthorized = () => { 205 const handleAuthorized = () => {
196 const { t } = useI18n() 206 const { t } = useI18n()
yudao-ui-admin-vue3/src/config/locale.ts deleted
1 -import { useCache } from '@/hooks/web/useCache'  
2 -import zhCn from 'element-plus/es/locale/lang/zh-cn'  
3 -import en from 'element-plus/es/locale/lang/en'  
4 -  
5 -const { wsCache } = useCache()  
6 -  
7 -export const elLocaleMap = {  
8 - 'zh-CN': zhCn,  
9 - en: en  
10 -}  
11 -export interface LocaleState {  
12 - currentLocale: LocaleDropdownType  
13 - localeMap: LocaleDropdownType[]  
14 -}  
15 -  
16 -export const localeModules: LocaleState = {  
17 - currentLocale: {  
18 - lang: wsCache.get('lang') || 'zh-CN',  
19 - elLocale: elLocaleMap[wsCache.get('lang') || 'zh-CN']  
20 - },  
21 - // 多语言  
22 - localeMap: [  
23 - {  
24 - lang: 'zh-CN',  
25 - name: '简体中文'  
26 - },  
27 - {  
28 - lang: 'en',  
29 - name: 'English'  
30 - }  
31 - ]  
32 -}  
yudao-ui-admin-vue3/src/hooks/web/useConfigGlobal.ts
  1 +import { ConfigGlobalTypes } from '@/types/configGlobal'
1 import { inject } from 'vue' 2 import { inject } from 'vue'
2 3
3 export const useConfigGlobal = () => { 4 export const useConfigGlobal = () => {
yudao-ui-admin-vue3/src/hooks/web/useCrudSchemas.ts
1 import { reactive } from 'vue' 1 import { reactive } from 'vue'
2 import { eachTree, treeMap, filter } from '@/utils/tree' 2 import { eachTree, treeMap, filter } from '@/utils/tree'
3 import { getIntDictOptions } from '@/utils/dict' 3 import { getIntDictOptions } from '@/utils/dict'
  4 +import { FormSchema } from '@/types/form'
  5 +import { TableColumn } from '@/types/table'
  6 +import { DescriptionsSchema } from '@/types/descriptions'
  7 +import { ComponentOptions } from '@/types/components'
4 8
5 export type CrudSchema = Omit<TableColumn, 'children'> & { 9 export type CrudSchema = Omit<TableColumn, 'children'> & {
6 search?: CrudSearchParams 10 search?: CrudSearchParams
yudao-ui-admin-vue3/src/hooks/web/useForm.ts
@@ -2,6 +2,7 @@ import type { Form, FormExpose } from &#39;@/components/Form&#39; @@ -2,6 +2,7 @@ import type { Form, FormExpose } from &#39;@/components/Form&#39;
2 import type { ElForm } from 'element-plus' 2 import type { ElForm } from 'element-plus'
3 import { ref, unref, nextTick } from 'vue' 3 import { ref, unref, nextTick } from 'vue'
4 import type { FormProps } from '@/components/Form/src/types' 4 import type { FormProps } from '@/components/Form/src/types'
  5 +import { FormSchema, FormSetPropsType } from '@/types/form'
5 6
6 export const useForm = (props?: FormProps) => { 7 export const useForm = (props?: FormProps) => {
7 // From实例 8 // From实例
yudao-ui-admin-vue3/src/hooks/web/useIcon.ts
1 import { h } from 'vue' 1 import { h } from 'vue'
2 import type { VNode } from 'vue' 2 import type { VNode } from 'vue'
3 import { Icon } from '@/components/Icon' 3 import { Icon } from '@/components/Icon'
  4 +import { IconTypes } from '@/types/icon'
4 5
5 export const useIcon = (props: IconTypes): VNode => { 6 export const useIcon = (props: IconTypes): VNode => {
6 return h(Icon, props) 7 return h(Icon, props)
yudao-ui-admin-vue3/src/hooks/web/useTable.ts
@@ -4,6 +4,7 @@ import { ElMessage, ElMessageBox, ElTable } from &#39;element-plus&#39; @@ -4,6 +4,7 @@ import { ElMessage, ElMessageBox, ElTable } from &#39;element-plus&#39;
4 import { computed, nextTick, reactive, ref, unref, watch } from 'vue' 4 import { computed, nextTick, reactive, ref, unref, watch } from 'vue'
5 import type { TableProps } from '@/components/Table/src/types' 5 import type { TableProps } from '@/components/Table/src/types'
6 import { useI18n } from '@/hooks/web/useI18n' 6 import { useI18n } from '@/hooks/web/useI18n'
  7 +import { TableSetPropsType } from '@/types/table'
7 8
8 const { t } = useI18n() 9 const { t } = useI18n()
9 interface ResponseType<T = any> { 10 interface ResponseType<T = any> {
@@ -150,13 +151,17 @@ export const useTable = &lt;T = any&gt;(config?: UseTableConfig&lt;T&gt;) =&gt; { @@ -150,13 +151,17 @@ export const useTable = &lt;T = any&gt;(config?: UseTableConfig&lt;T&gt;) =&gt; {
150 }, 151 },
151 // 与Search组件结合 152 // 与Search组件结合
152 setSearchParams: (data: Recordable) => { 153 setSearchParams: (data: Recordable) => {
153 - tableObject.currentPage = 1  
154 tableObject.params = Object.assign(tableObject.params, { 154 tableObject.params = Object.assign(tableObject.params, {
155 pageSize: tableObject.pageSize, 155 pageSize: tableObject.pageSize,
156 - pageNo: tableObject.currentPage, 156 + pageNo: 1,
157 ...data 157 ...data
158 }) 158 })
159 - methods.getList() 159 + // 页码不等于1时更新页码重新获取数据,页码等于1时重新获取数据
  160 + if (tableObject.currentPage !== 1) {
  161 + tableObject.currentPage = 1
  162 + } else {
  163 + methods.getList()
  164 + }
160 }, 165 },
161 // 删除数据 166 // 删除数据
162 delList: async ( 167 delList: async (
yudao-ui-admin-vue3/src/layout/components/useRenderLayout.tsx
@@ -32,6 +32,9 @@ const fixedHeader = computed(() =&gt; appStore.getFixedHeader) @@ -32,6 +32,9 @@ const fixedHeader = computed(() =&gt; appStore.getFixedHeader)
32 // 是否是移动端 32 // 是否是移动端
33 const mobile = computed(() => appStore.getMobile) 33 const mobile = computed(() => appStore.getMobile)
34 34
  35 +// 固定菜单
  36 +const fixedMenu = computed(() => appStore.getFixedMenu)
  37 +
35 export const useRenderLayout = () => { 38 export const useRenderLayout = () => {
36 const renderClassic = () => { 39 const renderClassic = () => {
37 return ( 40 return (
@@ -213,9 +216,13 @@ export const useRenderLayout = () =&gt; { @@ -213,9 +216,13 @@ export const useRenderLayout = () =&gt; {
213 'h-[100%]', 216 'h-[100%]',
214 { 217 {
215 'w-[calc(100%-var(--tab-menu-min-width))] left-[var(--tab-menu-min-width)]': 218 'w-[calc(100%-var(--tab-menu-min-width))] left-[var(--tab-menu-min-width)]':
216 - collapse.value, 219 + collapse.value && !fixedMenu.value,
217 'w-[calc(100%-var(--tab-menu-max-width))] left-[var(--tab-menu-max-width)]': 220 'w-[calc(100%-var(--tab-menu-max-width))] left-[var(--tab-menu-max-width)]':
218 - !collapse.value 221 + !collapse.value && !fixedMenu.value,
  222 + 'w-[calc(100%-var(--tab-menu-min-width)-var(--left-menu-max-width))] ml-[var(--left-menu-max-width)]':
  223 + collapse.value && fixedMenu.value,
  224 + 'w-[calc(100%-var(--tab-menu-max-width)-var(--left-menu-max-width))] ml-[var(--left-menu-max-width)]':
  225 + !collapse.value && fixedMenu.value
219 } 226 }
220 ]} 227 ]}
221 style="transition: all var(--transition-time-02);" 228 style="transition: all var(--transition-time-02);"
@@ -239,7 +246,13 @@ export const useRenderLayout = () =&gt; { @@ -239,7 +246,13 @@ export const useRenderLayout = () =&gt; {
239 'w-[calc(100%-var(--tab-menu-min-width))] left-[var(--tab-menu-min-width)] mt-[var(--logo-height)]': 246 'w-[calc(100%-var(--tab-menu-min-width))] left-[var(--tab-menu-min-width)] mt-[var(--logo-height)]':
240 collapse.value && fixedHeader.value, 247 collapse.value && fixedHeader.value,
241 'w-[calc(100%-var(--tab-menu-max-width))] left-[var(--tab-menu-max-width)] mt-[var(--logo-height)]': 248 'w-[calc(100%-var(--tab-menu-max-width))] left-[var(--tab-menu-max-width)] mt-[var(--logo-height)]':
242 - !collapse.value && fixedHeader.value 249 + !collapse.value && fixedHeader.value,
  250 + '!fixed top-0 left-[var(--tab-menu-min-width)+var(--left-menu-max-width)] z-10':
  251 + fixedHeader.value && fixedMenu.value,
  252 + 'w-[calc(100%-var(--tab-menu-min-width)-var(--left-menu-max-width))] left-[var(--tab-menu-min-width)+var(--left-menu-max-width)] mt-[var(--logo-height)]':
  253 + collapse.value && fixedHeader.value && fixedMenu.value,
  254 + 'w-[calc(100%-var(--tab-menu-max-width)-var(--left-menu-max-width))] left-[var(--tab-menu-max-width)+var(--left-menu-max-width)] mt-[var(--logo-height)]':
  255 + !collapse.value && fixedHeader.value && fixedMenu.value
243 } 256 }
244 ]} 257 ]}
245 style="transition: width var(--transition-time-02), left var(--transition-time-02);" 258 style="transition: width var(--transition-time-02), left var(--transition-time-02);"
yudao-ui-admin-vue3/src/locales/en.ts
@@ -91,7 +91,10 @@ export default { @@ -91,7 +91,10 @@ export default {
91 copyFailed: 'Copy failed', 91 copyFailed: 'Copy failed',
92 footer: 'Footer', 92 footer: 'Footer',
93 uniqueOpened: 'Unique opened', 93 uniqueOpened: 'Unique opened',
94 - tagsViewIcon: 'Tags view icon' 94 + tagsViewIcon: 'Tags view icon',
  95 + dynamicRouter: 'Dynamic router',
  96 + reExperienced: 'Please exit the login experience again',
  97 + fixedMenu: 'Fixed menu'
95 }, 98 },
96 size: { 99 size: {
97 default: 'Default', 100 default: 'Default',
yudao-ui-admin-vue3/src/locales/zh-CN.ts
@@ -91,7 +91,10 @@ export default { @@ -91,7 +91,10 @@ export default {
91 copyFailed: '拷贝失败', 91 copyFailed: '拷贝失败',
92 footer: '页脚', 92 footer: '页脚',
93 uniqueOpened: '菜单手风琴', 93 uniqueOpened: '菜单手风琴',
94 - tagsViewIcon: '标签页图标' 94 + tagsViewIcon: '标签页图标',
  95 + dynamicRouter: '动态路由',
  96 + reExperienced: '请重新退出登录体验',
  97 + fixedMenu: '固定菜单'
95 }, 98 },
96 size: { 99 size: {
97 default: '默认', 100 default: '默认',
yudao-ui-admin-vue3/src/store/index.ts
1 import type { App } from 'vue' 1 import type { App } from 'vue'
2 import { createPinia } from 'pinia' 2 import { createPinia } from 'pinia'
3 -import piniaPluginPersist from 'pinia-plugin-persist'  
4 3
5 const store = createPinia() 4 const store = createPinia()
6 5
7 -store.use(piniaPluginPersist)  
8 -  
9 export const setupStore = (app: App<Element>) => { 6 export const setupStore = (app: App<Element>) => {
10 app.use(store) 7 app.use(store)
11 } 8 }
yudao-ui-admin-vue3/src/store/modules/app.ts
1 import { defineStore } from 'pinia' 1 import { defineStore } from 'pinia'
2 import { store } from '../index' 2 import { store } from '../index'
3 -import { useCache } from '@/hooks/web/useCache'  
4 -import { appModules } from '@/config/app'  
5 -import type { AppState, LayoutType, ThemeTypes } from '@/config/app'  
6 import { setCssVar, humpToUnderline } from '@/utils' 3 import { setCssVar, humpToUnderline } from '@/utils'
7 import { ElMessage } from 'element-plus' 4 import { ElMessage } from 'element-plus'
  5 +import { useCache } from '@/hooks/web/useCache'
  6 +import { ElementPlusSize } from '@/types/elementPlus'
  7 +import { LayoutType } from '@/types/layout'
  8 +import { ThemeTypes } from '@/types/theme'
8 9
9 const { wsCache } = useCache() 10 const { wsCache } = useCache()
10 11
11 -export const useAppStore = defineStore({  
12 - id: 'app',  
13 - state: (): AppState => appModules,  
14 - persist: {  
15 - enabled: true 12 +interface AppState {
  13 + breadcrumb: boolean
  14 + breadcrumbIcon: boolean
  15 + collapse: boolean
  16 + uniqueOpened: boolean
  17 + hamburger: boolean
  18 + screenfull: boolean
  19 + size: boolean
  20 + locale: boolean
  21 + tagsView: boolean
  22 + tagsViewIcon: boolean
  23 + logo: boolean
  24 + fixedHeader: boolean
  25 + greyMode: boolean
  26 + pageLoading: boolean
  27 + layout: LayoutType
  28 + title: string
  29 + userInfo: string
  30 + isDark: boolean
  31 + currentSize: ElementPlusSize
  32 + sizeMap: ElementPlusSize[]
  33 + mobile: boolean
  34 + footer: boolean
  35 + theme: ThemeTypes
  36 + fixedMenu: boolean
  37 +}
  38 +
  39 +export const useAppStore = defineStore('app', {
  40 + state: (): AppState => {
  41 + return {
  42 + userInfo: 'userInfo', // 登录信息存储字段-建议每个项目换一个字段,避免与其他项目冲突
  43 + sizeMap: ['default', 'large', 'small'],
  44 + mobile: false, // 是否是移动端
  45 + title: import.meta.env.VITE_APP_TITLE, // 标题
  46 + pageLoading: false, // 路由跳转loading
  47 +
  48 + breadcrumb: true, // 面包屑
  49 + breadcrumbIcon: true, // 面包屑图标
  50 + collapse: false, // 折叠菜单
  51 + uniqueOpened: true, // 是否只保持一个子菜单的展开
  52 + hamburger: true, // 折叠图标
  53 + screenfull: true, // 全屏图标
  54 + size: true, // 尺寸图标
  55 + locale: true, // 多语言图标
  56 + tagsView: true, // 标签页
  57 + tagsViewIcon: true, // 是否显示标签图标
  58 + logo: true, // logo
  59 + fixedHeader: true, // 固定toolheader
  60 + footer: true, // 显示页脚
  61 + greyMode: false, // 是否开始灰色模式,用于特殊悼念日
  62 + fixedMenu: wsCache.get('fixedMenu') || false, // 是否固定菜单
  63 +
  64 + layout: wsCache.get('layout') || 'classic', // layout布局
  65 + isDark: wsCache.get('isDark') || false, // 是否是暗黑模式
  66 + currentSize: wsCache.get('default') || 'default', // 组件尺寸
  67 + theme: wsCache.get('theme') || {
  68 + // 主题色
  69 + elColorPrimary: '#409eff',
  70 + // 左侧菜单边框颜色
  71 + leftMenuBorderColor: 'inherit',
  72 + // 左侧菜单背景颜色
  73 + leftMenuBgColor: '#001529',
  74 + // 左侧菜单浅色背景颜色
  75 + leftMenuBgLightColor: '#0f2438',
  76 + // 左侧菜单选中背景颜色
  77 + leftMenuBgActiveColor: 'var(--el-color-primary)',
  78 + // 左侧菜单收起选中背景颜色
  79 + leftMenuCollapseBgActiveColor: 'var(--el-color-primary)',
  80 + // 左侧菜单字体颜色
  81 + leftMenuTextColor: '#bfcbd9',
  82 + // 左侧菜单选中字体颜色
  83 + leftMenuTextActiveColor: '#fff',
  84 + // logo字体颜色
  85 + logoTitleTextColor: '#fff',
  86 + // logo边框颜色
  87 + logoBorderColor: 'inherit',
  88 + // 头部背景颜色
  89 + topHeaderBgColor: '#fff',
  90 + // 头部字体颜色
  91 + topHeaderTextColor: 'inherit',
  92 + // 头部悬停颜色
  93 + topHeaderHoverColor: '#f6f6f6',
  94 + // 头部边框颜色
  95 + topToolBorderColor: '#eee'
  96 + }
  97 + }
16 }, 98 },
17 getters: { 99 getters: {
18 getBreadcrumb(): boolean { 100 getBreadcrumb(): boolean {
@@ -54,6 +136,9 @@ export const useAppStore = defineStore({ @@ -54,6 +136,9 @@ export const useAppStore = defineStore({
54 getGreyMode(): boolean { 136 getGreyMode(): boolean {
55 return this.greyMode 137 return this.greyMode
56 }, 138 },
  139 + getFixedMenu(): boolean {
  140 + return this.fixedMenu
  141 + },
57 getPageLoading(): boolean { 142 getPageLoading(): boolean {
58 return this.pageLoading 143 return this.pageLoading
59 }, 144 },
@@ -69,10 +154,10 @@ export const useAppStore = defineStore({ @@ -69,10 +154,10 @@ export const useAppStore = defineStore({
69 getIsDark(): boolean { 154 getIsDark(): boolean {
70 return this.isDark 155 return this.isDark
71 }, 156 },
72 - getCurrentSize(): ElememtPlusSize { 157 + getCurrentSize(): ElementPlusSize {
73 return this.currentSize 158 return this.currentSize
74 }, 159 },
75 - getSizeMap(): ElememtPlusSize[] { 160 + getSizeMap(): ElementPlusSize[] {
76 return this.sizeMap 161 return this.sizeMap
77 }, 162 },
78 getMobile(): boolean { 163 getMobile(): boolean {
@@ -125,6 +210,10 @@ export const useAppStore = defineStore({ @@ -125,6 +210,10 @@ export const useAppStore = defineStore({
125 setGreyMode(greyMode: boolean) { 210 setGreyMode(greyMode: boolean) {
126 this.greyMode = greyMode 211 this.greyMode = greyMode
127 }, 212 },
  213 + setFixedMenu(fixedMenu: boolean) {
  214 + wsCache.set('fixedMenu', fixedMenu)
  215 + this.fixedMenu = fixedMenu
  216 + },
128 setPageLoading(pageLoading: boolean) { 217 setPageLoading(pageLoading: boolean) {
129 this.pageLoading = pageLoading 218 this.pageLoading = pageLoading
130 }, 219 },
@@ -150,7 +239,7 @@ export const useAppStore = defineStore({ @@ -150,7 +239,7 @@ export const useAppStore = defineStore({
150 } 239 }
151 wsCache.set('isDark', this.isDark) 240 wsCache.set('isDark', this.isDark)
152 }, 241 },
153 - setCurrentSize(currentSize: ElememtPlusSize) { 242 + setCurrentSize(currentSize: ElementPlusSize) {
154 this.currentSize = currentSize 243 this.currentSize = currentSize
155 wsCache.set('currentSize', this.currentSize) 244 wsCache.set('currentSize', this.currentSize)
156 }, 245 },
yudao-ui-admin-vue3/src/store/modules/dict.ts
@@ -16,14 +16,10 @@ export interface DictState { @@ -16,14 +16,10 @@ export interface DictState {
16 dictMap: Recordable 16 dictMap: Recordable
17 } 17 }
18 18
19 -export const useDictStore = defineStore({  
20 - id: 'dict', 19 +export const useDictStore = defineStore('dict', {
21 state: (): DictState => ({ 20 state: (): DictState => ({
22 dictMap: {} 21 dictMap: {}
23 }), 22 }),
24 - persist: {  
25 - enabled: true  
26 - },  
27 getters: { 23 getters: {
28 getDictMap(): Recordable { 24 getDictMap(): Recordable {
29 return this.dictMap 25 return this.dictMap
yudao-ui-admin-vue3/src/store/modules/locale.ts
1 import { defineStore } from 'pinia' 1 import { defineStore } from 'pinia'
2 import { store } from '../index' 2 import { store } from '../index'
  3 +import zhCn from 'element-plus/es/locale/lang/zh-cn'
  4 +import en from 'element-plus/es/locale/lang/en'
3 import { useCache } from '@/hooks/web/useCache' 5 import { useCache } from '@/hooks/web/useCache'
4 -import { localeModules, elLocaleMap } from '@/config/locale'  
5 -import type { LocaleState } from '@/config/locale' 6 +import { LocaleDropdownType } from '@/types/localeDropdown'
6 7
7 const { wsCache } = useCache() 8 const { wsCache } = useCache()
8 9
9 -export const useLocaleStore = defineStore({  
10 - id: 'locales',  
11 - state: (): LocaleState => localeModules,  
12 - persist: {  
13 - enabled: true 10 +const elLocaleMap = {
  11 + 'zh-CN': zhCn,
  12 + en: en
  13 +}
  14 +interface LocaleState {
  15 + currentLocale: LocaleDropdownType
  16 + localeMap: LocaleDropdownType[]
  17 +}
  18 +
  19 +export const useLocaleStore = defineStore('locales', {
  20 + state: (): LocaleState => {
  21 + return {
  22 + currentLocale: {
  23 + lang: wsCache.get('lang') || 'zh-CN',
  24 + elLocale: elLocaleMap[wsCache.get('lang') || 'zh-CN']
  25 + },
  26 + // 多语言
  27 + localeMap: [
  28 + {
  29 + lang: 'zh-CN',
  30 + name: '简体中文'
  31 + },
  32 + {
  33 + lang: 'en',
  34 + name: 'English'
  35 + }
  36 + ]
  37 + }
14 }, 38 },
15 getters: { 39 getters: {
16 getCurrentLocale(): LocaleDropdownType { 40 getCurrentLocale(): LocaleDropdownType {
yudao-ui-admin-vue3/src/store/modules/permission.ts
@@ -14,16 +14,12 @@ export interface PermissionState { @@ -14,16 +14,12 @@ export interface PermissionState {
14 menuTabRouters: AppRouteRecordRaw[] 14 menuTabRouters: AppRouteRecordRaw[]
15 } 15 }
16 16
17 -export const usePermissionStore = defineStore({  
18 - id: 'permission', 17 +export const usePermissionStore = defineStore('permission', {
19 state: (): PermissionState => ({ 18 state: (): PermissionState => ({
20 routers: [], 19 routers: [],
21 addRouters: [], 20 addRouters: [],
22 menuTabRouters: [] 21 menuTabRouters: []
23 }), 22 }),
24 - persist: {  
25 - enabled: true  
26 - },  
27 getters: { 23 getters: {
28 getRouters(): AppRouteRecordRaw[] { 24 getRouters(): AppRouteRecordRaw[] {
29 return this.routers 25 return this.routers
yudao-ui-admin-vue3/src/store/modules/tagsView.ts
@@ -10,8 +10,7 @@ export interface TagsViewState { @@ -10,8 +10,7 @@ export interface TagsViewState {
10 cachedViews: Set<string> 10 cachedViews: Set<string>
11 } 11 }
12 12
13 -export const useTagsViewStore = defineStore({  
14 - id: 'tagsView', 13 +export const useTagsViewStore = defineStore('tagsView', {
15 state: (): TagsViewState => ({ 14 state: (): TagsViewState => ({
16 visitedViews: [], 15 visitedViews: [],
17 cachedViews: new Set() 16 cachedViews: new Set()
yudao-ui-admin-vue3/src/store/modules/user.ts
@@ -16,8 +16,7 @@ interface UserInfoVO { @@ -16,8 +16,7 @@ interface UserInfoVO {
16 user: UserVO 16 user: UserVO
17 } 17 }
18 18
19 -export const useUserStore = defineStore({  
20 - id: 'admin-user', 19 +export const useUserStore = defineStore('admin-user', {
21 state: (): UserInfoVO => ({ 20 state: (): UserInfoVO => ({
22 permissions: [], 21 permissions: [],
23 roles: [], 22 roles: [],
yudao-ui-admin-vue3/types/componentType/form.d.ts renamed to yudao-ui-admin-vue3/src/types/components.d.ts
1 -import type { CSSProperties } from 'vue'  
2 -  
3 -declare global {  
4 - declare type ComponentName =  
5 - | 'Radio'  
6 - | 'RadioButton'  
7 - | 'Checkbox'  
8 - | 'CheckboxButton'  
9 - | 'Input'  
10 - | 'Autocomplete'  
11 - | 'InputNumber'  
12 - | 'Select'  
13 - | 'Cascader'  
14 - | 'Switch'  
15 - | 'Slider'  
16 - | 'TimePicker'  
17 - | 'DatePicker'  
18 - | 'Rate'  
19 - | 'ColorPicker'  
20 - | 'Transfer'  
21 - | 'Divider'  
22 - | 'TimeSelect'  
23 - | 'SelectV2'  
24 - | 'InputPassword'  
25 - | 'Editor'  
26 -  
27 - declare type ColProps = {  
28 - span?: number  
29 - xs?: number  
30 - sm?: number  
31 - md?: number  
32 - lg?: number  
33 - xl?: number  
34 - tag?: string  
35 - }  
36 -  
37 - declare type FormValueType = string | number | string[] | number[] | boolean | undefined | null  
38 -  
39 - declare type FormItemProps = {  
40 - labelWidth?: string | number  
41 - required?: boolean  
42 - rules?: Recordable  
43 - error?: string  
44 - showMessage?: boolean  
45 - inlineMessage?: boolean  
46 - style?: CSSProperties  
47 - }  
48 -  
49 - declare type ComponentOptions = {  
50 - label?: string  
51 - value?: FormValueType  
52 - disabled?: boolean  
53 - key?: string | number  
54 - children?: ComponentOptions[]  
55 - options?: ComponentOptions[]  
56 - } & Recordable  
57 -  
58 - declare type ComponentOptionsAlias = {  
59 - labelField?: string  
60 - valueField?: string  
61 - }  
62 -  
63 - declare type ComponentProps = {  
64 - optionsAlias?: ComponentOptionsAlias  
65 - options?: ComponentOptions[]  
66 - optionsSlot?: boolean  
67 - } & Recordable  
68 -  
69 - declare type FormSchema = {  
70 - // 唯一值  
71 - field: string  
72 - // 标题  
73 - label?: string  
74 - // 提示  
75 - labelMessage?: string  
76 - // col组件属性  
77 - colProps?: ColProps  
78 - // 表单组件属性,slots对应的是表单组件的插槽,规则:${field}-xxx,具体可以查看element-plus文档  
79 - componentProps?: { slots?: Recordable } & ComponentProps  
80 - // formItem组件属性  
81 - formItemProps?: FormItemProps  
82 - // 渲染的组件  
83 - component?: ComponentName  
84 - // 初始值  
85 - value?: FormValueType  
86 - // 是否隐藏  
87 - hidden?: boolean  
88 - // 远程加载下拉项  
89 - api?: <T = any>() => Promise<T>  
90 - } 1 +export type ComponentName =
  2 + | 'Radio'
  3 + | 'RadioButton'
  4 + | 'Checkbox'
  5 + | 'CheckboxButton'
  6 + | 'Input'
  7 + | 'Autocomplete'
  8 + | 'InputNumber'
  9 + | 'Select'
  10 + | 'Cascader'
  11 + | 'Switch'
  12 + | 'Slider'
  13 + | 'TimePicker'
  14 + | 'DatePicker'
  15 + | 'Rate'
  16 + | 'ColorPicker'
  17 + | 'Transfer'
  18 + | 'Divider'
  19 + | 'TimeSelect'
  20 + | 'SelectV2'
  21 + | 'InputPassword'
  22 + | 'Editor'
  23 +
  24 +export type ColProps = {
  25 + span?: number
  26 + xs?: number
  27 + sm?: number
  28 + md?: number
  29 + lg?: number
  30 + xl?: number
  31 + tag?: string
  32 +}
91 33
92 - declare type FormSetPropsType = {  
93 - field: string  
94 - path: string  
95 - value: any  
96 - } 34 +export type ComponentOptions = {
  35 + label?: string
  36 + value?: FormValueType
  37 + disabled?: boolean
  38 + key?: string | number
  39 + children?: ComponentOptions[]
  40 + options?: ComponentOptions[]
  41 +} & Recordable
  42 +
  43 +export type ComponentOptionsAlias = {
  44 + labelField?: string
  45 + valueField?: string
97 } 46 }
  47 +
  48 +export type ComponentProps = {
  49 + optionsAlias?: ComponentOptionsAlias
  50 + options?: ComponentOptions[]
  51 + optionsSlot?: boolean
  52 +} & Recordable
yudao-ui-admin-vue3/src/types/configGlobal.d.ts 0 → 100644
  1 +import { ElementPlusSize } from './elementPlus'
  2 +export interface ConfigGlobalTypes {
  3 + size?: ElementPlusSize
  4 +}
yudao-ui-admin-vue3/types/componentType/contextMenu.d.ts renamed to yudao-ui-admin-vue3/src/types/contextMenu.d.ts
1 -declare type contextMenuSchema = { 1 +export type contextMenuSchema = {
2 disabled?: boolean 2 disabled?: boolean
3 divided?: boolean 3 divided?: boolean
4 icon?: string 4 icon?: string
5 label: string 5 label: string
6 - command?: (item: contextMenuSchema) => viod 6 + command?: (item: contextMenuSchema) => void
7 } 7 }
yudao-ui-admin-vue3/types/componentType/descriptions.d.ts renamed to yudao-ui-admin-vue3/src/types/descriptions.d.ts
1 -declare interface DescriptionsSchema { 1 +export interface DescriptionsSchema {
2 span?: number // 占多少分 2 span?: number // 占多少分
3 field: string // 字段名 3 field: string // 字段名
4 label?: string // label名 4 label?: string // label名
yudao-ui-admin-vue3/src/types/elementPlus.d.ts 0 → 100644
  1 +export type ElementPlusSize = 'default' | 'small' | 'large'
  2 +
  3 +export type ElementPlusInfoType = 'success' | 'info' | 'warning' | 'danger'
yudao-ui-admin-vue3/src/types/form.d.ts 0 → 100644
  1 +import type { CSSProperties } from 'vue'
  2 +import { ColProps, ComponentProps, ComponentName } from '@/types/components'
  3 +import { FormValueType, FormValueType } from '@/types/form'
  4 +import type { AxiosPromise } from 'axios'
  5 +
  6 +export type FormSetPropsType = {
  7 + field: string
  8 + path: string
  9 + value: any
  10 +}
  11 +
  12 +export type FormValueType = string | number | string[] | number[] | boolean | undefined | null
  13 +
  14 +export type FormItemProps = {
  15 + labelWidth?: string | number
  16 + required?: boolean
  17 + rules?: Recordable
  18 + error?: string
  19 + showMessage?: boolean
  20 + inlineMessage?: boolean
  21 + style?: CSSProperties
  22 +}
  23 +
  24 +export type FormSchema = {
  25 + // 唯一值
  26 + field: string
  27 + // 标题
  28 + label?: string
  29 + // 提示
  30 + labelMessage?: string
  31 + // col组件属性
  32 + colProps?: ColProps
  33 + // 表单组件属性,slots对应的是表单组件的插槽,规则:${field}-xxx,具体可以查看element-plus文档
  34 + componentProps?: { slots?: Recordable } & ComponentProps
  35 + // formItem组件属性
  36 + formItemProps?: FormItemProps
  37 + // 渲染的组件
  38 + component?: ComponentName
  39 + // 初始值
  40 + value?: FormValueType
  41 + // 是否隐藏
  42 + hidden?: boolean
  43 + // 远程加载下拉项
  44 + api?: <T = any>() => AxiosPromise<T>
  45 +}
yudao-ui-admin-vue3/types/componentType/icon.d.ts renamed to yudao-ui-admin-vue3/src/types/icon.d.ts
1 -declare interface IconTypes { 1 +export interface IconTypes {
2 size?: number 2 size?: number
3 color?: string 3 color?: string
4 icon: string 4 icon: string
yudao-ui-admin-vue3/types/componentType/infotip.d.ts renamed to yudao-ui-admin-vue3/src/types/infoTip.d.ts
1 -declare interface TipSchema { 1 +export interface TipSchema {
2 label: string 2 label: string
3 keys?: string[] 3 keys?: string[]
4 } 4 }
yudao-ui-admin-vue3/src/types/layout.d.ts 0 → 100644
  1 +export type LayoutType = 'classic' | 'topLeft' | 'top' | 'cutMenu'
yudao-ui-admin-vue3/types/componentType/localeDropdown.d.ts renamed to yudao-ui-admin-vue3/src/types/localeDropdown.d.ts
1 -declare interface Language { 1 +export interface Language {
2 el: Recordable 2 el: Recordable
3 name: string 3 name: string
4 } 4 }
5 5
6 -declare interface LocaleDropdownType { 6 +export interface LocaleDropdownType {
7 lang: LocaleType 7 lang: LocaleType
8 name?: string 8 name?: string
9 elLocale?: Language 9 elLocale?: Language
yudao-ui-admin-vue3/types/componentType/qrcode.d.ts renamed to yudao-ui-admin-vue3/src/types/qrcode.d.ts
1 -declare interface QrcodeLogo { 1 +export interface QrcodeLogo {
2 src?: string 2 src?: string
3 logoSize?: number 3 logoSize?: number
4 bgColor?: string 4 bgColor?: string
yudao-ui-admin-vue3/types/componentType/table.d.ts renamed to yudao-ui-admin-vue3/src/types/table.d.ts
1 -declare type TableColumn = { 1 +export type TableColumn = {
2 field: string 2 field: string
3 label?: string 3 label?: string
4 children?: TableColumn[] 4 children?: TableColumn[]
5 } & Recordable 5 } & Recordable
6 6
7 -declare type TableSlotDefault = { 7 +export type TableSlotDefault = {
8 row: Recordable 8 row: Recordable
9 column: TableColumn 9 column: TableColumn
10 $index: number 10 $index: number
11 } & Recordable 11 } & Recordable
12 12
13 -declare interface Pagination { 13 +export interface Pagination {
14 small?: boolean 14 small?: boolean
15 background?: boolean 15 background?: boolean
16 pageSize?: number 16 pageSize?: number
@@ -29,7 +29,7 @@ declare interface Pagination { @@ -29,7 +29,7 @@ declare interface Pagination {
29 hideOnSinglePage?: boolean 29 hideOnSinglePage?: boolean
30 } 30 }
31 31
32 -declare interface TableSetPropsType { 32 +export interface TableSetPropsType {
33 field: string 33 field: string
34 path: string 34 path: string
35 value: any 35 value: any
yudao-ui-admin-vue3/src/types/theme.d.ts 0 → 100644
  1 +export type ThemeTypes = {
  2 + elColorPrimary?: string
  3 + leftMenuBorderColor?: string
  4 + leftMenuBgColor?: string
  5 + leftMenuBgLightColor?: string
  6 + leftMenuBgActiveColor?: string
  7 + leftMenuCollapseBgActiveColor?: string
  8 + leftMenuTextColor?: string
  9 + leftMenuTextActiveColor?: string
  10 + logoTitleTextColor?: string
  11 + logoBorderColor?: string
  12 + topHeaderBgColor?: string
  13 + topHeaderTextColor?: string
  14 + topHeaderHoverColor?: string
  15 + topToolBorderColor?: string
  16 +}
yudao-ui-admin-vue3/src/utils/dict.ts
@@ -2,6 +2,7 @@ @@ -2,6 +2,7 @@
2 * 数据字典工具类 2 * 数据字典工具类
3 */ 3 */
4 import { useDictStoreWithOut } from '@/store/modules/dict' 4 import { useDictStoreWithOut } from '@/store/modules/dict'
  5 +import { ElementPlusInfoType } from '@/types/elementPlus'
5 6
6 const dictStore = useDictStoreWithOut() 7 const dictStore = useDictStoreWithOut()
7 8
yudao-ui-admin-vue3/src/views/Login/components/LoginForm.vue
@@ -103,20 +103,25 @@ const getCookie = () =&gt; { @@ -103,20 +103,25 @@ const getCookie = () =&gt; {
103 // 登录 103 // 登录
104 const handleLogin = async (params) => { 104 const handleLogin = async (params) => {
105 loginLoading.value = true 105 loginLoading.value = true
106 - await getTenantId()  
107 - const data = await validForm()  
108 - if (!data) { 106 + try {
  107 + await getTenantId()
  108 + const data = await validForm()
  109 + if (!data) {
  110 + return
  111 + }
  112 + loginData.loginForm.captchaVerification = params.captchaVerification
  113 + const res = await LoginApi.loginApi(loginData.loginForm)
  114 + if (!res) {
  115 + return
  116 + }
  117 + setToken(res)
  118 + if (!redirect.value) {
  119 + redirect.value = '/'
  120 + }
  121 + push({ path: redirect.value || permissionStore.addRouters[0].path })
  122 + } finally {
109 loginLoading.value = false 123 loginLoading.value = false
110 - return  
111 - }  
112 - loginData.loginForm.captchaVerification = params.captchaVerification  
113 - const res = await LoginApi.loginApi(loginData.loginForm)  
114 - setToken(res)  
115 - if (!redirect.value) {  
116 - redirect.value = '/'  
117 } 124 }
118 - push({ path: redirect.value || permissionStore.addRouters[0].path })  
119 - loginLoading.value = false  
120 } 125 }
121 126
122 // 社交登录 127 // 社交登录
yudao-ui-admin-vue3/src/views/Login/components/RegisterForm.vue
@@ -7,6 +7,7 @@ import { ElInput, FormRules } from &#39;element-plus&#39; @@ -7,6 +7,7 @@ import { ElInput, FormRules } from &#39;element-plus&#39;
7 import { useValidator } from '@/hooks/web/useValidator' 7 import { useValidator } from '@/hooks/web/useValidator'
8 import { useLoginState, LoginStateEnum } from './useLogin' 8 import { useLoginState, LoginStateEnum } from './useLogin'
9 import LoginFormTitle from './LoginFormTitle.vue' 9 import LoginFormTitle from './LoginFormTitle.vue'
  10 +import { FormSchema } from '@/types/form'
10 11
11 const { register, elFormRef } = useForm() 12 const { register, elFormRef } = useForm()
12 const { handleBackLogin, getLoginState } = useLoginState() 13 const { handleBackLogin, getLoginState } = useLoginState()
yudao-ui-admin-vue3/src/views/bpm/form/index.vue
@@ -49,23 +49,29 @@ const handleUpdate = async (row: FormVO) =&gt; { @@ -49,23 +49,29 @@ const handleUpdate = async (row: FormVO) =&gt; {
49 49
50 // 提交按钮 50 // 提交按钮
51 const submitForm = async () => { 51 const submitForm = async () => {
52 - actionLoading.value = true  
53 - // 提交请求  
54 - try {  
55 - const data = unref(formRef)?.formModel as FormVO  
56 - if (actionType.value === 'create') {  
57 - await FormApi.createFormApi(data)  
58 - ElMessage.success(t('common.createSuccess'))  
59 - } else {  
60 - await FormApi.updateFormApi(data)  
61 - ElMessage.success(t('common.updateSuccess')) 52 + const elForm = unref(formRef)?.getElFormRef()
  53 + if (!elForm) return
  54 + elForm.validate(async (valid) => {
  55 + if (valid) {
  56 + actionLoading.value = true
  57 + // 提交请求
  58 + try {
  59 + const data = unref(formRef)?.formModel as FormVO
  60 + if (actionType.value === 'create') {
  61 + await FormApi.createFormApi(data)
  62 + ElMessage.success(t('common.createSuccess'))
  63 + } else {
  64 + await FormApi.updateFormApi(data)
  65 + ElMessage.success(t('common.updateSuccess'))
  66 + }
  67 + // 操作成功,重新加载列表
  68 + dialogVisible.value = false
  69 + await getList()
  70 + } finally {
  71 + actionLoading.value = false
  72 + }
62 } 73 }
63 - // 操作成功,重新加载列表  
64 - dialogVisible.value = false  
65 - await getList()  
66 - } finally {  
67 - actionLoading.value = false  
68 - } 74 + })
69 } 75 }
70 76
71 // ========== 详情相关 ========== 77 // ========== 详情相关 ==========
yudao-ui-admin-vue3/src/views/bpm/group/index.vue
@@ -62,24 +62,30 @@ const handleUpdate = async (row: UserGroupVO) =&gt; { @@ -62,24 +62,30 @@ const handleUpdate = async (row: UserGroupVO) =&gt; {
62 62
63 // 提交按钮 63 // 提交按钮
64 const submitForm = async () => { 64 const submitForm = async () => {
65 - actionLoading.value = true  
66 - // 提交请求  
67 - try {  
68 - const data = unref(formRef)?.formModel as UserGroupVO  
69 - data.memberUserIds = userIds.value  
70 - if (actionType.value === 'create') {  
71 - await UserGroupApi.createUserGroupApi(data)  
72 - ElMessage.success(t('common.createSuccess'))  
73 - } else {  
74 - await UserGroupApi.updateUserGroupApi(data)  
75 - ElMessage.success(t('common.updateSuccess')) 65 + const elForm = unref(formRef)?.getElFormRef()
  66 + if (!elForm) return
  67 + elForm.validate(async (valid) => {
  68 + if (valid) {
  69 + actionLoading.value = true
  70 + // 提交请求
  71 + try {
  72 + const data = unref(formRef)?.formModel as UserGroupVO
  73 + data.memberUserIds = userIds.value
  74 + if (actionType.value === 'create') {
  75 + await UserGroupApi.createUserGroupApi(data)
  76 + ElMessage.success(t('common.createSuccess'))
  77 + } else {
  78 + await UserGroupApi.updateUserGroupApi(data)
  79 + ElMessage.success(t('common.updateSuccess'))
  80 + }
  81 + // 操作成功,重新加载列表
  82 + dialogVisible.value = false
  83 + await getList()
  84 + } finally {
  85 + actionLoading.value = false
  86 + }
76 } 87 }
77 - // 操作成功,重新加载列表  
78 - dialogVisible.value = false  
79 - await getList()  
80 - } finally {  
81 - actionLoading.value = false  
82 - } 88 + })
83 } 89 }
84 90
85 // 根据用户名获取用户真实名 91 // 根据用户名获取用户真实名
yudao-ui-admin-vue3/src/views/bpm/model/index.vue
@@ -51,23 +51,29 @@ const handleUpdate = async (row: ModelVO) =&gt; { @@ -51,23 +51,29 @@ const handleUpdate = async (row: ModelVO) =&gt; {
51 51
52 // 提交按钮 52 // 提交按钮
53 const submitForm = async () => { 53 const submitForm = async () => {
54 - actionLoading.value = true  
55 - // 提交请求  
56 - try {  
57 - const data = unref(formRef)?.formModel as ModelVO  
58 - if (actionType.value === 'create') {  
59 - await ModelApi.createModelApi(data)  
60 - message.success(t('common.createSuccess'))  
61 - } else {  
62 - await ModelApi.updateModelApi(data)  
63 - message.success(t('common.updateSuccess')) 54 + const elForm = unref(formRef)?.getElFormRef()
  55 + if (!elForm) return
  56 + elForm.validate(async (valid) => {
  57 + if (valid) {
  58 + actionLoading.value = true
  59 + // 提交请求
  60 + try {
  61 + const data = unref(formRef)?.formModel as ModelVO
  62 + if (actionType.value === 'create') {
  63 + await ModelApi.createModelApi(data)
  64 + message.success(t('common.createSuccess'))
  65 + } else {
  66 + await ModelApi.updateModelApi(data)
  67 + message.success(t('common.updateSuccess'))
  68 + }
  69 + // 操作成功,重新加载列表
  70 + dialogVisible.value = false
  71 + await getList()
  72 + } finally {
  73 + actionLoading.value = false
  74 + }
64 } 75 }
65 - // 操作成功,重新加载列表  
66 - dialogVisible.value = false  
67 - await getList()  
68 - } finally {  
69 - actionLoading.value = false  
70 - } 76 + })
71 } 77 }
72 78
73 /** 流程表单的详情按钮操作 */ 79 /** 流程表单的详情按钮操作 */
yudao-ui-admin-vue3/src/views/infra/codegen/EditTable.vue
@@ -26,6 +26,10 @@ const activeName = ref(&#39;cloum&#39;) @@ -26,6 +26,10 @@ const activeName = ref(&#39;cloum&#39;)
26 const basicInfoRef = ref<ComponentRef<typeof BasicInfoForm>>() 26 const basicInfoRef = ref<ComponentRef<typeof BasicInfoForm>>()
27 const genInfoRef = ref<ComponentRef<typeof GenInfoForm>>() 27 const genInfoRef = ref<ComponentRef<typeof GenInfoForm>>()
28 const cloumInfoRef = ref(null) 28 const cloumInfoRef = ref(null)
  29 +const parentMenuId = ref<number>()
  30 +const menu = (id: number) => {
  31 + parentMenuId.value = id
  32 +}
29 const submitForm = async () => { 33 const submitForm = async () => {
30 const basicInfo = unref(basicInfoRef) 34 const basicInfo = unref(basicInfoRef)
31 const genInfo = unref(genInfoRef) 35 const genInfo = unref(genInfoRef)
@@ -38,6 +42,11 @@ const submitForm = async () =&gt; { @@ -38,6 +42,11 @@ const submitForm = async () =&gt; {
38 table: Object.assign({}, basicInfoData, genInfoData), 42 table: Object.assign({}, basicInfoData, genInfoData),
39 columns: cloumCurrentRow.value 43 columns: cloumCurrentRow.value
40 } 44 }
  45 + if (parentMenuId.value) {
  46 + genInfoData.parentMenuId = parentMenuId.value
  47 + } else {
  48 + genInfoData.parentMenuId = 0
  49 + }
41 await updateCodegenTableApi(genTable) 50 await updateCodegenTableApi(genTable)
42 ElMessage.success(t('common.updateSuccess')) 51 ElMessage.success(t('common.updateSuccess'))
43 push('/infra/codegen') 52 push('/infra/codegen')
@@ -57,7 +66,7 @@ onMounted(() =&gt; { @@ -57,7 +66,7 @@ onMounted(() =&gt; {
57 <CloumInfoForm ref="cloumInfoRef" :info="cloumCurrentRow" /> 66 <CloumInfoForm ref="cloumInfoRef" :info="cloumCurrentRow" />
58 </el-tab-pane> 67 </el-tab-pane>
59 <el-tab-pane label="生成信息" name="genInfo"> 68 <el-tab-pane label="生成信息" name="genInfo">
60 - <GenInfoForm ref="genInfoRef" :genInfo="tableCurrentRow" /> 69 + <GenInfoForm ref="genInfoRef" :genInfo="tableCurrentRow" @menu="menu" />
61 </el-tab-pane> 70 </el-tab-pane>
62 </el-tabs> 71 </el-tabs>
63 <template #right> 72 <template #right>
yudao-ui-admin-vue3/src/views/infra/codegen/components/BasicInfoForm.vue
@@ -4,6 +4,8 @@ import { required } from &#39;@/utils/formRules&#39; @@ -4,6 +4,8 @@ import { required } from &#39;@/utils/formRules&#39;
4 import { CodegenTableVO } from '@/api/infra/codegen/types' 4 import { CodegenTableVO } from '@/api/infra/codegen/types'
5 import { Form } from '@/components/Form' 5 import { Form } from '@/components/Form'
6 import { useForm } from '@/hooks/web/useForm' 6 import { useForm } from '@/hooks/web/useForm'
  7 +import { FormSchema } from '@/types/form'
  8 +
7 const props = defineProps({ 9 const props = defineProps({
8 basicInfo: { 10 basicInfo: {
9 type: Object as PropType<Nullable<CodegenTableVO>>, 11 type: Object as PropType<Nullable<CodegenTableVO>>,
yudao-ui-admin-vue3/src/views/infra/codegen/components/GenInfoForm.vue
1 <script setup lang="ts"> 1 <script setup lang="ts">
2 -import { PropType, reactive, watch } from 'vue' 2 +import { onMounted, PropType, reactive, ref, watch } from 'vue'
3 import { required } from '@/utils/formRules' 3 import { required } from '@/utils/formRules'
4 -import { CodegenTableVO } from '@/api/infra/codegen/types'  
5 import { Form } from '@/components/Form' 4 import { Form } from '@/components/Form'
  5 +import { handleTree } from '@/utils/tree'
  6 +import { ElTreeSelect } from 'element-plus'
6 import { useForm } from '@/hooks/web/useForm' 7 import { useForm } from '@/hooks/web/useForm'
7 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' 8 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
  9 +import { listSimpleMenusApi } from '@/api/system/menu'
  10 +import { CodegenTableVO } from '@/api/infra/codegen/types'
  11 +import { FormSchema } from '@/types/form'
8 const props = defineProps({ 12 const props = defineProps({
9 genInfo: { 13 genInfo: {
10 type: Object as PropType<Nullable<CodegenTableVO>>, 14 type: Object as PropType<Nullable<CodegenTableVO>>,
11 default: () => null 15 default: () => null
12 } 16 }
13 }) 17 })
  18 +const menuProps = {
  19 + checkStrictly: true,
  20 + children: 'children',
  21 + label: 'name',
  22 + value: 'id'
  23 +}
14 const rules = reactive({ 24 const rules = reactive({
15 templateType: [required], 25 templateType: [required],
16 scene: [required], 26 scene: [required],
@@ -22,6 +32,12 @@ const rules = reactive({ @@ -22,6 +32,12 @@ const rules = reactive({
22 }) 32 })
23 const templateTypeOptions = getIntDictOptions(DICT_TYPE.INFRA_CODEGEN_TEMPLATE_TYPE) 33 const templateTypeOptions = getIntDictOptions(DICT_TYPE.INFRA_CODEGEN_TEMPLATE_TYPE)
24 const sceneOptions = getIntDictOptions(DICT_TYPE.INFRA_CODEGEN_SCENE) 34 const sceneOptions = getIntDictOptions(DICT_TYPE.INFRA_CODEGEN_SCENE)
  35 +const treeRef = ref<InstanceType<typeof ElTreeSelect>>()
  36 +const menuOptions = ref<any>([]) // 树形结构
  37 +const getTree = async () => {
  38 + const res = await listSimpleMenusApi()
  39 + menuOptions.value = handleTree(res)
  40 +}
25 const schema = reactive<FormSchema[]>([ 41 const schema = reactive<FormSchema[]>([
26 { 42 {
27 label: '生成模板', 43 label: '生成模板',
@@ -84,7 +100,9 @@ const schema = reactive&lt;FormSchema[]&gt;([ @@ -84,7 +100,9 @@ const schema = reactive&lt;FormSchema[]&gt;([
84 { 100 {
85 label: '上级菜单', 101 label: '上级菜单',
86 field: 'parentMenuId', 102 field: 'parentMenuId',
87 - component: 'Input', 103 + componentProps: {
  104 + optionsSlot: true
  105 + },
88 labelMessage: '分配到指定菜单下,例如 系统管理', 106 labelMessage: '分配到指定菜单下,例如 系统管理',
89 colProps: { 107 colProps: {
90 span: 12 108 span: 12
@@ -94,6 +112,17 @@ const schema = reactive&lt;FormSchema[]&gt;([ @@ -94,6 +112,17 @@ const schema = reactive&lt;FormSchema[]&gt;([
94 const { register, methods, elFormRef } = useForm({ 112 const { register, methods, elFormRef } = useForm({
95 schema 113 schema
96 }) 114 })
  115 +const parentMenuId = ref<number>()
  116 +//子组件像父组件传值
  117 +const emit = defineEmits(['menu'])
  118 +const handleNodeClick = () => {
  119 + emit('menu', parentMenuId.value)
  120 +}
  121 +
  122 +// ========== 初始化 ==========
  123 +onMounted(async () => {
  124 + await getTree()
  125 +})
97 watch( 126 watch(
98 () => props.genInfo, 127 () => props.genInfo,
99 (genInfo) => { 128 (genInfo) => {
@@ -106,12 +135,23 @@ watch( @@ -106,12 +135,23 @@ watch(
106 immediate: true 135 immediate: true
107 } 136 }
108 ) 137 )
109 -  
110 defineExpose({ 138 defineExpose({
111 elFormRef, 139 elFormRef,
112 getFormData: methods.getFormData 140 getFormData: methods.getFormData
113 }) 141 })
114 </script> 142 </script>
115 <template> 143 <template>
116 - <Form :rules="rules" @register="register" /> 144 + <Form :rules="rules" @register="register">
  145 + <template #parentMenuId>
  146 + <el-tree-select
  147 + v-model="parentMenuId"
  148 + ref="treeRef"
  149 + node-key="id"
  150 + :props="menuProps"
  151 + :data="menuOptions"
  152 + check-strictly
  153 + @node-click="handleNodeClick"
  154 + />
  155 + </template>
  156 + </Form>
117 </template> 157 </template>
yudao-ui-admin-vue3/src/views/infra/codegen/components/ImportTable.vue
1 <script setup lang="ts"> 1 <script setup lang="ts">
2 -import { reactive, ref } from 'vue' 2 +import { reactive, ref, defineEmits } from 'vue'
3 import { getSchemaTableListApi, createCodegenListApi } from '@/api/infra/codegen' 3 import { getSchemaTableListApi, createCodegenListApi } from '@/api/infra/codegen'
4 import { 4 import {
5 ElMessage, 5 ElMessage,
yudao-ui-admin-vue3/src/views/infra/config/index.vue
@@ -50,23 +50,29 @@ const handleUpdate = async (row: ConfigVO) =&gt; { @@ -50,23 +50,29 @@ const handleUpdate = async (row: ConfigVO) =&gt; {
50 50
51 // 提交按钮 51 // 提交按钮
52 const submitForm = async () => { 52 const submitForm = async () => {
53 - actionLoading.value = true  
54 - // 提交请求  
55 - try {  
56 - const data = unref(formRef)?.formModel as ConfigVO  
57 - if (actionType.value === 'create') {  
58 - await ConfigApi.createConfigApi(data)  
59 - ElMessage.success(t('common.createSuccess'))  
60 - } else {  
61 - await ConfigApi.updateConfigApi(data)  
62 - ElMessage.success(t('common.updateSuccess')) 53 + const elForm = unref(formRef)?.getElFormRef()
  54 + if (!elForm) return
  55 + elForm.validate(async (valid) => {
  56 + if (valid) {
  57 + actionLoading.value = true
  58 + // 提交请求
  59 + try {
  60 + const data = unref(formRef)?.formModel as ConfigVO
  61 + if (actionType.value === 'create') {
  62 + await ConfigApi.createConfigApi(data)
  63 + ElMessage.success(t('common.createSuccess'))
  64 + } else {
  65 + await ConfigApi.updateConfigApi(data)
  66 + ElMessage.success(t('common.updateSuccess'))
  67 + }
  68 + // 操作成功,重新加载列表
  69 + dialogVisible.value = false
  70 + await getList()
  71 + } finally {
  72 + actionLoading.value = false
  73 + }
63 } 74 }
64 - // 操作成功,重新加载列表  
65 - dialogVisible.value = false  
66 - await getList()  
67 - } finally {  
68 - actionLoading.value = false  
69 - } 75 + })
70 } 76 }
71 77
72 // ========== 详情相关 ========== 78 // ========== 详情相关 ==========
yudao-ui-admin-vue3/src/views/infra/dataSourceConfig/index.vue
@@ -44,23 +44,29 @@ const handleUpdate = async (row: DataSourceConfigVO) =&gt; { @@ -44,23 +44,29 @@ const handleUpdate = async (row: DataSourceConfigVO) =&gt; {
44 44
45 // 提交按钮 45 // 提交按钮
46 const submitForm = async () => { 46 const submitForm = async () => {
47 - loading.value = true  
48 - // 提交请求  
49 - try {  
50 - const data = unref(formRef)?.formModel as DataSourceConfigVO  
51 - if (actionType.value === 'create') {  
52 - await DataSourceConfiggApi.createDataSourceConfigApi(data)  
53 - ElMessage.success(t('common.createSuccess'))  
54 - } else {  
55 - await DataSourceConfiggApi.updateDataSourceConfigApi(data)  
56 - ElMessage.success(t('common.updateSuccess')) 47 + const elForm = unref(formRef)?.getElFormRef()
  48 + if (!elForm) return
  49 + elForm.validate(async (valid) => {
  50 + if (valid) {
  51 + loading.value = true
  52 + // 提交请求
  53 + try {
  54 + const data = unref(formRef)?.formModel as DataSourceConfigVO
  55 + if (actionType.value === 'create') {
  56 + await DataSourceConfiggApi.createDataSourceConfigApi(data)
  57 + ElMessage.success(t('common.createSuccess'))
  58 + } else {
  59 + await DataSourceConfiggApi.updateDataSourceConfigApi(data)
  60 + ElMessage.success(t('common.updateSuccess'))
  61 + }
  62 + // 操作成功,重新加载列表
  63 + dialogVisible.value = false
  64 + await getList()
  65 + } finally {
  66 + loading.value = false
  67 + }
57 } 68 }
58 - // 操作成功,重新加载列表  
59 - dialogVisible.value = false  
60 - await getList()  
61 - } finally {  
62 - loading.value = false  
63 - } 69 + })
64 } 70 }
65 71
66 // 删除操作 72 // 删除操作
yudao-ui-admin-vue3/src/views/infra/fileConfig/index.vue
@@ -60,23 +60,29 @@ const handleMaster = (row: FileConfigVO) =&gt; { @@ -60,23 +60,29 @@ const handleMaster = (row: FileConfigVO) =&gt; {
60 60
61 // 提交按钮 61 // 提交按钮
62 const submitForm = async () => { 62 const submitForm = async () => {
63 - actionLoading.value = true  
64 - // 提交请求  
65 - try {  
66 - const data = unref(formRef)?.formModel as FileConfigVO  
67 - if (actionType.value === 'create') {  
68 - await FileConfigApi.createFileConfigApi(data)  
69 - message.success(t('common.createSuccess'))  
70 - } else {  
71 - await FileConfigApi.updateFileConfigApi(data)  
72 - message.success(t('common.updateSuccess')) 63 + const elForm = unref(formRef)?.getElFormRef()
  64 + if (!elForm) return
  65 + elForm.validate(async (valid) => {
  66 + if (valid) {
  67 + actionLoading.value = true
  68 + // 提交请求
  69 + try {
  70 + const data = unref(formRef)?.formModel as FileConfigVO
  71 + if (actionType.value === 'create') {
  72 + await FileConfigApi.createFileConfigApi(data)
  73 + message.success(t('common.createSuccess'))
  74 + } else {
  75 + await FileConfigApi.updateFileConfigApi(data)
  76 + message.success(t('common.updateSuccess'))
  77 + }
  78 + // 操作成功,重新加载列表
  79 + dialogVisible.value = false
  80 + await getList()
  81 + } finally {
  82 + actionLoading.value = false
  83 + }
73 } 84 }
74 - // 操作成功,重新加载列表  
75 - dialogVisible.value = false  
76 - await getList()  
77 - } finally {  
78 - actionLoading.value = false  
79 - } 85 + })
80 } 86 }
81 87
82 // ========== 详情相关 ========== 88 // ========== 详情相关 ==========
yudao-ui-admin-vue3/src/views/infra/job/index.vue
@@ -98,24 +98,30 @@ const handleRun = (row: JobVO) =&gt; { @@ -98,24 +98,30 @@ const handleRun = (row: JobVO) =&gt; {
98 } 98 }
99 // 提交按钮 99 // 提交按钮
100 const submitForm = async () => { 100 const submitForm = async () => {
101 - actionLoading.value = true  
102 - // 提交请求  
103 - try {  
104 - const data = unref(formRef)?.formModel as JobVO  
105 - data.cronExpression = cronExpression.value  
106 - if (actionType.value === 'create') {  
107 - await JobApi.createJobApi(data)  
108 - message.success(t('common.createSuccess'))  
109 - } else {  
110 - await JobApi.updateJobApi(data)  
111 - message.success(t('common.updateSuccess')) 101 + const elForm = unref(formRef)?.getElFormRef()
  102 + if (!elForm) return
  103 + elForm.validate(async (valid) => {
  104 + if (valid) {
  105 + actionLoading.value = true
  106 + // 提交请求
  107 + try {
  108 + const data = unref(formRef)?.formModel as JobVO
  109 + data.cronExpression = cronExpression.value
  110 + if (actionType.value === 'create') {
  111 + await JobApi.createJobApi(data)
  112 + message.success(t('common.createSuccess'))
  113 + } else {
  114 + await JobApi.updateJobApi(data)
  115 + message.success(t('common.updateSuccess'))
  116 + }
  117 + // 操作成功,重新加载列表
  118 + dialogVisible.value = false
  119 + await getList()
  120 + } finally {
  121 + actionLoading.value = false
  122 + }
112 } 123 }
113 - // 操作成功,重新加载列表  
114 - dialogVisible.value = false  
115 - await getList()  
116 - } finally {  
117 - actionLoading.value = false  
118 - } 124 + })
119 } 125 }
120 126
121 // ========== 详情相关 ========== 127 // ========== 详情相关 ==========
yudao-ui-admin-vue3/src/views/pay/app/index.vue
@@ -50,23 +50,29 @@ const handleUpdate = async (row: AppVO) =&gt; { @@ -50,23 +50,29 @@ const handleUpdate = async (row: AppVO) =&gt; {
50 50
51 // 提交按钮 51 // 提交按钮
52 const submitForm = async () => { 52 const submitForm = async () => {
53 - actionLoading.value = true  
54 - // 提交请求  
55 - try {  
56 - const data = unref(formRef)?.formModel as AppVO  
57 - if (actionType.value === 'create') {  
58 - await AppApi.createAppApi(data)  
59 - ElMessage.success(t('common.createSuccess'))  
60 - } else {  
61 - await AppApi.updateAppApi(data)  
62 - ElMessage.success(t('common.updateSuccess')) 53 + const elForm = unref(formRef)?.getElFormRef()
  54 + if (!elForm) return
  55 + elForm.validate(async (valid) => {
  56 + if (valid) {
  57 + actionLoading.value = true
  58 + // 提交请求
  59 + try {
  60 + const data = unref(formRef)?.formModel as AppVO
  61 + if (actionType.value === 'create') {
  62 + await AppApi.createAppApi(data)
  63 + ElMessage.success(t('common.createSuccess'))
  64 + } else {
  65 + await AppApi.updateAppApi(data)
  66 + ElMessage.success(t('common.updateSuccess'))
  67 + }
  68 + // 操作成功,重新加载列表
  69 + dialogVisible.value = false
  70 + await getList()
  71 + } finally {
  72 + actionLoading.value = false
  73 + }
63 } 74 }
64 - // 操作成功,重新加载列表  
65 - dialogVisible.value = false  
66 - await getList()  
67 - } finally {  
68 - actionLoading.value = false  
69 - } 75 + })
70 } 76 }
71 77
72 // ========== 详情相关 ========== 78 // ========== 详情相关 ==========
yudao-ui-admin-vue3/src/views/pay/merchant/index.vue
@@ -50,23 +50,29 @@ const handleUpdate = async (row: MerchantVO) =&gt; { @@ -50,23 +50,29 @@ const handleUpdate = async (row: MerchantVO) =&gt; {
50 50
51 // 提交按钮 51 // 提交按钮
52 const submitForm = async () => { 52 const submitForm = async () => {
53 - actionLoading.value = true  
54 - // 提交请求  
55 - try {  
56 - const data = unref(formRef)?.formModel as MerchantVO  
57 - if (actionType.value === 'create') {  
58 - await MerchantApi.createMerchantApi(data)  
59 - ElMessage.success(t('common.createSuccess'))  
60 - } else {  
61 - await MerchantApi.updateMerchantApi(data)  
62 - ElMessage.success(t('common.updateSuccess')) 53 + const elForm = unref(formRef)?.getElFormRef()
  54 + if (!elForm) return
  55 + elForm.validate(async (valid) => {
  56 + if (valid) {
  57 + actionLoading.value = true
  58 + // 提交请求
  59 + try {
  60 + const data = unref(formRef)?.formModel as MerchantVO
  61 + if (actionType.value === 'create') {
  62 + await MerchantApi.createMerchantApi(data)
  63 + ElMessage.success(t('common.createSuccess'))
  64 + } else {
  65 + await MerchantApi.updateMerchantApi(data)
  66 + ElMessage.success(t('common.updateSuccess'))
  67 + }
  68 + // 操作成功,重新加载列表
  69 + dialogVisible.value = false
  70 + await getList()
  71 + } finally {
  72 + actionLoading.value = false
  73 + }
63 } 74 }
64 - // 操作成功,重新加载列表  
65 - dialogVisible.value = false  
66 - await getList()  
67 - } finally {  
68 - actionLoading.value = false  
69 - } 75 + })
70 } 76 }
71 77
72 // ========== 详情相关 ========== 78 // ========== 详情相关 ==========
yudao-ui-admin-vue3/src/views/pay/order/index.vue
@@ -48,23 +48,29 @@ const handleUpdate = async (row: OrderVO) =&gt; { @@ -48,23 +48,29 @@ const handleUpdate = async (row: OrderVO) =&gt; {
48 48
49 // 提交按钮 49 // 提交按钮
50 const submitForm = async () => { 50 const submitForm = async () => {
51 - actionLoading.value = true  
52 - // 提交请求  
53 - try {  
54 - const data = unref(formRef)?.formModel as OrderVO  
55 - if (actionType.value === 'create') {  
56 - await OrderApi.createOrderApi(data)  
57 - ElMessage.success(t('common.createSuccess'))  
58 - } else {  
59 - await OrderApi.updateOrderApi(data)  
60 - ElMessage.success(t('common.updateSuccess')) 51 + const elForm = unref(formRef)?.getElFormRef()
  52 + if (!elForm) return
  53 + elForm.validate(async (valid) => {
  54 + if (valid) {
  55 + actionLoading.value = true
  56 + // 提交请求
  57 + try {
  58 + const data = unref(formRef)?.formModel as OrderVO
  59 + if (actionType.value === 'create') {
  60 + await OrderApi.createOrderApi(data)
  61 + ElMessage.success(t('common.createSuccess'))
  62 + } else {
  63 + await OrderApi.updateOrderApi(data)
  64 + ElMessage.success(t('common.updateSuccess'))
  65 + }
  66 + // 操作成功,重新加载列表
  67 + dialogVisible.value = false
  68 + await getList()
  69 + } finally {
  70 + actionLoading.value = false
  71 + }
61 } 72 }
62 - // 操作成功,重新加载列表  
63 - dialogVisible.value = false  
64 - await getList()  
65 - } finally {  
66 - actionLoading.value = false  
67 - } 73 + })
68 } 74 }
69 75
70 // ========== 详情相关 ========== 76 // ========== 详情相关 ==========
yudao-ui-admin-vue3/src/views/system/dept/dept.data.ts
1 import { required } from '@/utils/formRules' 1 import { required } from '@/utils/formRules'
2 import { reactive } from 'vue' 2 import { reactive } from 'vue'
  3 +import { FormSchema } from '@/types/form'
  4 +
3 // 表单校验 5 // 表单校验
4 export const rules = reactive({ 6 export const rules = reactive({
5 name: [required], 7 name: [required],
yudao-ui-admin-vue3/src/views/system/dept/index.vue
@@ -82,22 +82,28 @@ const handleDelete = async (data: { id: number }) =&gt; { @@ -82,22 +82,28 @@ const handleDelete = async (data: { id: number }) =&gt; {
82 } 82 }
83 // 提交按钮 83 // 提交按钮
84 const submitForm = async () => { 84 const submitForm = async () => {
85 - loading.value = true  
86 - // 提交请求  
87 - try {  
88 - const data = unref(formRef)?.formModel as DeptVO  
89 - data.parentId = deptParentId.value  
90 - data.leaderUserId = leaderUserId.value  
91 - if (formTitle.value.startsWith('新增')) {  
92 - await DeptApi.createDeptApi(data)  
93 - } else if (formTitle.value.startsWith('修改')) {  
94 - await DeptApi.updateDeptApi(data) 85 + const elForm = unref(formRef)?.getElFormRef()
  86 + if (!elForm) return
  87 + elForm.validate(async (valid) => {
  88 + if (valid) {
  89 + loading.value = true
  90 + // 提交请求
  91 + try {
  92 + const data = unref(formRef)?.formModel as DeptVO
  93 + data.parentId = deptParentId.value
  94 + data.leaderUserId = leaderUserId.value
  95 + if (formTitle.value.startsWith('新增')) {
  96 + await DeptApi.createDeptApi(data)
  97 + } else if (formTitle.value.startsWith('修改')) {
  98 + await DeptApi.updateDeptApi(data)
  99 + }
  100 + // 操作成功,重新加载列表
  101 + dialogVisible.value = false
  102 + } finally {
  103 + loading.value = false
  104 + }
95 } 105 }
96 - // 操作成功,重新加载列表  
97 - dialogVisible.value = false  
98 - } finally {  
99 - loading.value = false  
100 - } 106 + })
101 } 107 }
102 onMounted(async () => { 108 onMounted(async () => {
103 await getTree() 109 await getTree()
yudao-ui-admin-vue3/src/views/system/dict/index.vue
@@ -92,43 +92,55 @@ const setDialogTile = (type: string) =&gt; { @@ -92,43 +92,55 @@ const setDialogTile = (type: string) =&gt; {
92 } 92 }
93 // 提交按钮 93 // 提交按钮
94 const submitTypeForm = async () => { 94 const submitTypeForm = async () => {
95 - actionLoading.value = true  
96 - // 提交请求  
97 - try {  
98 - const data = unref(typeFormRef)?.formModel as DictTypeVO  
99 - if (actionType.value === 'typeCreate') {  
100 - await DictTypeApi.createDictTypeApi(data)  
101 - ElMessage.success(t('common.createSuccess'))  
102 - } else if (actionType.value === 'typeUpdate') {  
103 - await DictTypeApi.updateDictTypeApi(data)  
104 - ElMessage.success(t('common.updateSuccess')) 95 + const elForm = unref(typeFormRef)?.getElFormRef()
  96 + if (!elForm) return
  97 + elForm.validate(async (valid) => {
  98 + if (valid) {
  99 + actionLoading.value = true
  100 + // 提交请求
  101 + try {
  102 + const data = unref(typeFormRef)?.formModel as DictTypeVO
  103 + if (actionType.value === 'typeCreate') {
  104 + await DictTypeApi.createDictTypeApi(data)
  105 + ElMessage.success(t('common.createSuccess'))
  106 + } else if (actionType.value === 'typeUpdate') {
  107 + await DictTypeApi.updateDictTypeApi(data)
  108 + ElMessage.success(t('common.updateSuccess'))
  109 + }
  110 + // 操作成功,重新加载列表
  111 + await getTypeList()
  112 + dialogVisible.value = false
  113 + } finally {
  114 + actionLoading.value = false
  115 + }
105 } 116 }
106 - // 操作成功,重新加载列表  
107 - await getTypeList()  
108 - dialogVisible.value = false  
109 - } finally {  
110 - actionLoading.value = false  
111 - } 117 + })
112 } 118 }
113 const submitDataForm = async () => { 119 const submitDataForm = async () => {
114 - actionLoading.value = true  
115 - // 提交请求  
116 - try {  
117 - const data = unref(dataFormRef)?.formModel as DictDataVO  
118 - if (actionType.value === 'dataCreate') {  
119 - data.dictType = parentType.value  
120 - await DictDataApi.createDictDataApi(data)  
121 - ElMessage.success(t('common.createSuccess'))  
122 - } else if (actionType.value === 'dataUpdate') {  
123 - await DictDataApi.updateDictDataApi(data)  
124 - ElMessage.success(t('common.updateSuccess')) 120 + const elForm = unref(dataFormRef)?.getElFormRef()
  121 + if (!elForm) return
  122 + elForm.validate(async (valid) => {
  123 + if (valid) {
  124 + actionLoading.value = true
  125 + // 提交请求
  126 + try {
  127 + const data = unref(dataFormRef)?.formModel as DictDataVO
  128 + if (actionType.value === 'dataCreate') {
  129 + data.dictType = parentType.value
  130 + await DictDataApi.createDictDataApi(data)
  131 + ElMessage.success(t('common.createSuccess'))
  132 + } else if (actionType.value === 'dataUpdate') {
  133 + await DictDataApi.updateDictDataApi(data)
  134 + ElMessage.success(t('common.updateSuccess'))
  135 + }
  136 + await getDataList()
  137 + // 操作成功,重新加载列表
  138 + dialogVisible.value = false
  139 + } finally {
  140 + actionLoading.value = false
  141 + }
125 } 142 }
126 - await getDataList()  
127 - // 操作成功,重新加载列表  
128 - dialogVisible.value = false  
129 - } finally {  
130 - actionLoading.value = false  
131 - } 143 + })
132 } 144 }
133 // 初始化查询 145 // 初始化查询
134 onMounted(async () => { 146 onMounted(async () => {
yudao-ui-admin-vue3/src/views/system/errorCode/index.vue
@@ -49,23 +49,29 @@ const handleUpdate = async (row: ErrorCodeVO) =&gt; { @@ -49,23 +49,29 @@ const handleUpdate = async (row: ErrorCodeVO) =&gt; {
49 49
50 // 提交按钮 50 // 提交按钮
51 const submitForm = async () => { 51 const submitForm = async () => {
52 - loading.value = true  
53 - // 提交请求  
54 - try {  
55 - const data = unref(formRef)?.formModel as ErrorCodeVO  
56 - if (actionType.value === 'create') {  
57 - await ErrorCodeApi.createErrorCodeApi(data)  
58 - ElMessage.success(t('common.createSuccess'))  
59 - } else {  
60 - await ErrorCodeApi.updateErrorCodeApi(data)  
61 - ElMessage.success(t('common.updateSuccess')) 52 + const elForm = unref(formRef)?.getElFormRef()
  53 + if (!elForm) return
  54 + elForm.validate(async (valid) => {
  55 + if (valid) {
  56 + loading.value = true
  57 + // 提交请求
  58 + try {
  59 + const data = unref(formRef)?.formModel as ErrorCodeVO
  60 + if (actionType.value === 'create') {
  61 + await ErrorCodeApi.createErrorCodeApi(data)
  62 + ElMessage.success(t('common.createSuccess'))
  63 + } else {
  64 + await ErrorCodeApi.updateErrorCodeApi(data)
  65 + ElMessage.success(t('common.updateSuccess'))
  66 + }
  67 + // 操作成功,重新加载列表
  68 + dialogVisible.value = false
  69 + await getList()
  70 + } finally {
  71 + loading.value = false
  72 + }
62 } 73 }
63 - // 操作成功,重新加载列表  
64 - dialogVisible.value = false  
65 - await getList()  
66 - } finally {  
67 - loading.value = false  
68 - } 74 + })
69 } 75 }
70 76
71 // ========== 详情相关 ========== 77 // ========== 详情相关 ==========
yudao-ui-admin-vue3/src/views/system/menu/index.vue
@@ -42,12 +42,18 @@ const menuProps = { @@ -42,12 +42,18 @@ const menuProps = {
42 label: 'name', 42 label: 'name',
43 value: 'id' 43 value: 'id'
44 } 44 }
45 -const menuOptions = ref() // 树形结构 45 +interface Tree {
  46 + id: number
  47 + name: string
  48 + children?: Tree[] | any[]
  49 +}
  50 +const menuOptions = ref<any[]>([]) // 树形结构
46 const getTree = async () => { 51 const getTree = async () => {
  52 + menuOptions.value = []
47 const res = await MenuApi.listSimpleMenusApi() 53 const res = await MenuApi.listSimpleMenusApi()
48 - const menu = { id: 0, name: '主类目', children: [] as any[] } 54 + let menu: Tree = { id: 0, name: '主类目', children: [] }
49 menu.children = handleTree(res) 55 menu.children = handleTree(res)
50 - menuOptions.value = menu 56 + menuOptions.value.push(menu)
51 } 57 }
52 // ========== 查询 ========== 58 // ========== 查询 ==========
53 const queryParams = reactive({ 59 const queryParams = reactive({
@@ -121,7 +127,7 @@ const handleDelete = async (row: MenuVO) =&gt; { @@ -121,7 +127,7 @@ const handleDelete = async (row: MenuVO) =&gt; {
121 await getList() 127 await getList()
122 } 128 }
123 // 保存操作 129 // 保存操作
124 -function isExternal(path: string) { 130 +const isExternal = (path: string) => {
125 return /^(https?:|mailto:|tel:)/.test(path) 131 return /^(https?:|mailto:|tel:)/.test(path)
126 } 132 }
127 const submitForm = async () => { 133 const submitForm = async () => {
@@ -268,7 +274,7 @@ onMounted(async () =&gt; { @@ -268,7 +274,7 @@ onMounted(async () =&gt; {
268 node-key="id" 274 node-key="id"
269 v-model="menuForm.parentId" 275 v-model="menuForm.parentId"
270 :props="menuProps" 276 :props="menuProps"
271 - :data="menuData" 277 + :data="menuOptions"
272 check-strictly 278 check-strictly
273 /> 279 />
274 </el-form-item> 280 </el-form-item>
yudao-ui-admin-vue3/src/views/system/notice/index.vue
@@ -48,23 +48,29 @@ const handleUpdate = async (row: NoticeVO) =&gt; { @@ -48,23 +48,29 @@ const handleUpdate = async (row: NoticeVO) =&gt; {
48 48
49 // 提交按钮 49 // 提交按钮
50 const submitForm = async () => { 50 const submitForm = async () => {
51 - actionLoading.value = true  
52 - // 提交请求  
53 - try {  
54 - const data = unref(formRef)?.formModel as NoticeVO  
55 - if (actionType.value === 'create') {  
56 - await NoticeApi.createNoticeApi(data)  
57 - ElMessage.success(t('common.createSuccess'))  
58 - } else {  
59 - await NoticeApi.updateNoticeApi(data)  
60 - ElMessage.success(t('common.updateSuccess')) 51 + const elForm = unref(formRef)?.getElFormRef()
  52 + if (!elForm) return
  53 + elForm.validate(async (valid) => {
  54 + if (valid) {
  55 + actionLoading.value = true
  56 + // 提交请求
  57 + try {
  58 + const data = unref(formRef)?.formModel as NoticeVO
  59 + if (actionType.value === 'create') {
  60 + await NoticeApi.createNoticeApi(data)
  61 + ElMessage.success(t('common.createSuccess'))
  62 + } else {
  63 + await NoticeApi.updateNoticeApi(data)
  64 + ElMessage.success(t('common.updateSuccess'))
  65 + }
  66 + // 操作成功,重新加载列表
  67 + dialogVisible.value = false
  68 + await getList()
  69 + } finally {
  70 + actionLoading.value = false
  71 + }
61 } 72 }
62 - // 操作成功,重新加载列表  
63 - dialogVisible.value = false  
64 - await getList()  
65 - } finally {  
66 - actionLoading.value = false  
67 - } 73 + })
68 } 74 }
69 75
70 // ========== 详情相关 ========== 76 // ========== 详情相关 ==========
yudao-ui-admin-vue3/src/views/system/oauth2/client/index.vue
@@ -49,23 +49,29 @@ const handleUpdate = async (row: OAuth2ClientVo) =&gt; { @@ -49,23 +49,29 @@ const handleUpdate = async (row: OAuth2ClientVo) =&gt; {
49 49
50 // 提交按钮 50 // 提交按钮
51 const submitForm = async () => { 51 const submitForm = async () => {
52 - actionLoading.value = true  
53 - // 提交请求  
54 - try {  
55 - const data = unref(formRef)?.formModel as OAuth2ClientVo  
56 - if (actionType.value === 'create') {  
57 - await ClientApi.createOAuth2ClientApi(data)  
58 - ElMessage.success(t('common.createSuccess'))  
59 - } else {  
60 - await ClientApi.updateOAuth2ClientApi(data)  
61 - ElMessage.success(t('common.updateSuccess')) 52 + const elForm = unref(formRef)?.getElFormRef()
  53 + if (!elForm) return
  54 + elForm.validate(async (valid) => {
  55 + if (valid) {
  56 + actionLoading.value = true
  57 + // 提交请求
  58 + try {
  59 + const data = unref(formRef)?.formModel as OAuth2ClientVo
  60 + if (actionType.value === 'create') {
  61 + await ClientApi.createOAuth2ClientApi(data)
  62 + ElMessage.success(t('common.createSuccess'))
  63 + } else {
  64 + await ClientApi.updateOAuth2ClientApi(data)
  65 + ElMessage.success(t('common.updateSuccess'))
  66 + }
  67 + // 操作成功,重新加载列表
  68 + dialogVisible.value = false
  69 + await getList()
  70 + } finally {
  71 + actionLoading.value = false
  72 + }
62 } 73 }
63 - // 操作成功,重新加载列表  
64 - dialogVisible.value = false  
65 - await getList()  
66 - } finally {  
67 - actionLoading.value = false  
68 - } 74 + })
69 } 75 }
70 76
71 // ========== 详情相关 ========== 77 // ========== 详情相关 ==========
yudao-ui-admin-vue3/src/views/system/post/index.vue
@@ -50,23 +50,29 @@ const handleUpdate = async (row: PostVO) =&gt; { @@ -50,23 +50,29 @@ const handleUpdate = async (row: PostVO) =&gt; {
50 50
51 // 提交按钮 51 // 提交按钮
52 const submitForm = async () => { 52 const submitForm = async () => {
53 - actionLoading.value = true  
54 - // 提交请求  
55 - try {  
56 - const data = unref(formRef)?.formModel as PostVO  
57 - if (actionType.value === 'create') {  
58 - await PostApi.createPostApi(data)  
59 - ElMessage.success(t('common.createSuccess'))  
60 - } else {  
61 - await PostApi.updatePostApi(data)  
62 - ElMessage.success(t('common.updateSuccess')) 53 + const elForm = unref(formRef)?.getElFormRef()
  54 + if (!elForm) return
  55 + elForm.validate(async (valid) => {
  56 + if (valid) {
  57 + actionLoading.value = true
  58 + // 提交请求
  59 + try {
  60 + const data = unref(formRef)?.formModel as PostVO
  61 + if (actionType.value === 'create') {
  62 + await PostApi.createPostApi(data)
  63 + ElMessage.success(t('common.createSuccess'))
  64 + } else {
  65 + await PostApi.updatePostApi(data)
  66 + ElMessage.success(t('common.updateSuccess'))
  67 + }
  68 + // 操作成功,重新加载列表
  69 + dialogVisible.value = false
  70 + await getList()
  71 + } finally {
  72 + actionLoading.value = false
  73 + }
63 } 74 }
64 - // 操作成功,重新加载列表  
65 - dialogVisible.value = false  
66 - await getList()  
67 - } finally {  
68 - actionLoading.value = false  
69 - } 75 + })
70 } 76 }
71 77
72 // ========== 详情相关 ========== 78 // ========== 详情相关 ==========
yudao-ui-admin-vue3/src/views/system/role/index.vue
@@ -68,23 +68,29 @@ const handleUpdate = async (row: RoleVO) =&gt; { @@ -68,23 +68,29 @@ const handleUpdate = async (row: RoleVO) =&gt; {
68 68
69 // 提交按钮 69 // 提交按钮
70 const submitForm = async () => { 70 const submitForm = async () => {
71 - loading.value = true  
72 - // 提交请求  
73 - try {  
74 - const data = unref(formRef)?.formModel as RoleVO  
75 - if (actionType.value === 'create') {  
76 - await RoleApi.createRoleApi(data)  
77 - ElMessage.success(t('common.createSuccess'))  
78 - } else {  
79 - await RoleApi.updateRoleApi(data)  
80 - ElMessage.success(t('common.updateSuccess')) 71 + const elForm = unref(formRef)?.getElFormRef()
  72 + if (!elForm) return
  73 + elForm.validate(async (valid) => {
  74 + if (valid) {
  75 + loading.value = true
  76 + // 提交请求
  77 + try {
  78 + const data = unref(formRef)?.formModel as RoleVO
  79 + if (actionType.value === 'create') {
  80 + await RoleApi.createRoleApi(data)
  81 + ElMessage.success(t('common.createSuccess'))
  82 + } else {
  83 + await RoleApi.updateRoleApi(data)
  84 + ElMessage.success(t('common.updateSuccess'))
  85 + }
  86 + // 操作成功,重新加载列表
  87 + dialogVisible.value = false
  88 + await getList()
  89 + } finally {
  90 + loading.value = false
  91 + }
81 } 92 }
82 - // 操作成功,重新加载列表  
83 - dialogVisible.value = false  
84 - await getList()  
85 - } finally {  
86 - loading.value = false  
87 - } 93 + })
88 } 94 }
89 95
90 // ========== 详情相关 ========== 96 // ========== 详情相关 ==========
yudao-ui-admin-vue3/src/views/system/sensitiveWord/index.vue
@@ -56,23 +56,29 @@ const handleUpdate = async (row: SensitiveWordVO) =&gt; { @@ -56,23 +56,29 @@ const handleUpdate = async (row: SensitiveWordVO) =&gt; {
56 56
57 // 提交按钮 57 // 提交按钮
58 const submitForm = async () => { 58 const submitForm = async () => {
59 - actionLoading.value = true  
60 - // 提交请求  
61 - try {  
62 - const data = unref(formRef)?.formModel as SensitiveWordVO  
63 - if (actionType.value === 'create') {  
64 - await SensitiveWordApi.createSensitiveWordApi(data)  
65 - ElMessage.success(t('common.createSuccess'))  
66 - } else {  
67 - await SensitiveWordApi.updateSensitiveWordApi(data)  
68 - ElMessage.success(t('common.updateSuccess')) 59 + const elForm = unref(formRef)?.getElFormRef()
  60 + if (!elForm) return
  61 + elForm.validate(async (valid) => {
  62 + if (valid) {
  63 + actionLoading.value = true
  64 + // 提交请求
  65 + try {
  66 + const data = unref(formRef)?.formModel as SensitiveWordVO
  67 + if (actionType.value === 'create') {
  68 + await SensitiveWordApi.createSensitiveWordApi(data)
  69 + ElMessage.success(t('common.createSuccess'))
  70 + } else {
  71 + await SensitiveWordApi.updateSensitiveWordApi(data)
  72 + ElMessage.success(t('common.updateSuccess'))
  73 + }
  74 + // 操作成功,重新加载列表
  75 + dialogVisible.value = false
  76 + await getList()
  77 + } finally {
  78 + actionLoading.value = false
  79 + }
69 } 80 }
70 - // 操作成功,重新加载列表  
71 - dialogVisible.value = false  
72 - await getList()  
73 - } finally {  
74 - actionLoading.value = false  
75 - } 81 + })
76 } 82 }
77 83
78 // ========== 详情相关 ========== 84 // ========== 详情相关 ==========
yudao-ui-admin-vue3/src/views/system/sms/smsChannel/index.vue
@@ -49,23 +49,29 @@ const handleUpdate = async (row: SmsChannelVO) =&gt; { @@ -49,23 +49,29 @@ const handleUpdate = async (row: SmsChannelVO) =&gt; {
49 49
50 // 提交按钮 50 // 提交按钮
51 const submitForm = async () => { 51 const submitForm = async () => {
52 - loading.value = true  
53 - // 提交请求  
54 - try {  
55 - const data = unref(formRef)?.formModel as SmsChannelVO  
56 - if (actionType.value === 'create') {  
57 - await SmsChannelApi.createSmsChannelApi(data)  
58 - ElMessage.success(t('common.createSuccess'))  
59 - } else {  
60 - await SmsChannelApi.updateSmsChannelApi(data)  
61 - ElMessage.success(t('common.updateSuccess')) 52 + const elForm = unref(formRef)?.getElFormRef()
  53 + if (!elForm) return
  54 + elForm.validate(async (valid) => {
  55 + if (valid) {
  56 + loading.value = true
  57 + // 提交请求
  58 + try {
  59 + const data = unref(formRef)?.formModel as SmsChannelVO
  60 + if (actionType.value === 'create') {
  61 + await SmsChannelApi.createSmsChannelApi(data)
  62 + ElMessage.success(t('common.createSuccess'))
  63 + } else {
  64 + await SmsChannelApi.updateSmsChannelApi(data)
  65 + ElMessage.success(t('common.updateSuccess'))
  66 + }
  67 + // 操作成功,重新加载列表
  68 + dialogVisible.value = false
  69 + await getList()
  70 + } finally {
  71 + loading.value = false
  72 + }
62 } 73 }
63 - // 操作成功,重新加载列表  
64 - dialogVisible.value = false  
65 - await getList()  
66 - } finally {  
67 - loading.value = false  
68 - } 74 + })
69 } 75 }
70 76
71 // ========== 详情相关 ========== 77 // ========== 详情相关 ==========
yudao-ui-admin-vue3/src/views/system/sms/smsTemplate/index.vue
@@ -49,23 +49,29 @@ const handleUpdate = async (row: SmsTemplateVO) =&gt; { @@ -49,23 +49,29 @@ const handleUpdate = async (row: SmsTemplateVO) =&gt; {
49 49
50 // 提交按钮 50 // 提交按钮
51 const submitForm = async () => { 51 const submitForm = async () => {
52 - loading.value = true  
53 - // 提交请求  
54 - try {  
55 - const data = unref(formRef)?.formModel as SmsTemplateVO  
56 - if (actionType.value === 'create') {  
57 - await SmsTemplateApi.createSmsTemplateApi(data)  
58 - ElMessage.success(t('common.createSuccess'))  
59 - } else {  
60 - await SmsTemplateApi.updateSmsTemplateApi(data)  
61 - ElMessage.success(t('common.updateSuccess')) 52 + const elForm = unref(formRef)?.getElFormRef()
  53 + if (!elForm) return
  54 + elForm.validate(async (valid) => {
  55 + if (valid) {
  56 + loading.value = true
  57 + // 提交请求
  58 + try {
  59 + const data = unref(formRef)?.formModel as SmsTemplateVO
  60 + if (actionType.value === 'create') {
  61 + await SmsTemplateApi.createSmsTemplateApi(data)
  62 + ElMessage.success(t('common.createSuccess'))
  63 + } else {
  64 + await SmsTemplateApi.updateSmsTemplateApi(data)
  65 + ElMessage.success(t('common.updateSuccess'))
  66 + }
  67 + // 操作成功,重新加载列表
  68 + dialogVisible.value = false
  69 + await getList()
  70 + } finally {
  71 + loading.value = false
  72 + }
62 } 73 }
63 - // 操作成功,重新加载列表  
64 - dialogVisible.value = false  
65 - await getList()  
66 - } finally {  
67 - loading.value = false  
68 - } 74 + })
69 } 75 }
70 76
71 // ========== 详情相关 ========== 77 // ========== 详情相关 ==========
yudao-ui-admin-vue3/src/views/system/tenant/index.vue
@@ -71,26 +71,32 @@ const handleUpdate = async (row: any) =&gt; { @@ -71,26 +71,32 @@ const handleUpdate = async (row: any) =&gt; {
71 71
72 // 提交按钮 72 // 提交按钮
73 const submitForm = async () => { 73 const submitForm = async () => {
74 - actionLoading.value = true  
75 - // 提交请求 unix()  
76 - try {  
77 - const data = unref(formRef)?.formModel as TenantVO  
78 - data.packageId = tenantPackageId.value  
79 - if (actionType.value === 'create') {  
80 - data.expireTime = dayjs(data.expireTime).valueOf().toString()  
81 - await TenantApi.createTenantApi(data)  
82 - ElMessage.success(t('common.createSuccess'))  
83 - } else {  
84 - data.expireTime = dayjs(data.expireTime).valueOf().toString()  
85 - await TenantApi.updateTenantApi(data)  
86 - ElMessage.success(t('common.updateSuccess')) 74 + const elForm = unref(formRef)?.getElFormRef()
  75 + if (!elForm) return
  76 + elForm.validate(async (valid) => {
  77 + if (valid) {
  78 + actionLoading.value = true
  79 + // 提交请求
  80 + try {
  81 + const data = unref(formRef)?.formModel as TenantVO
  82 + data.packageId = tenantPackageId.value
  83 + if (actionType.value === 'create') {
  84 + data.expireTime = dayjs(data.expireTime).valueOf().toString()
  85 + await TenantApi.createTenantApi(data)
  86 + ElMessage.success(t('common.createSuccess'))
  87 + } else {
  88 + data.expireTime = dayjs(data.expireTime).valueOf().toString()
  89 + await TenantApi.updateTenantApi(data)
  90 + ElMessage.success(t('common.updateSuccess'))
  91 + }
  92 + // 操作成功,重新加载列表
  93 + dialogVisible.value = false
  94 + await getList()
  95 + } finally {
  96 + actionLoading.value = false
  97 + }
87 } 98 }
88 - // 操作成功,重新加载列表  
89 - dialogVisible.value = false  
90 - await getList()  
91 - } finally {  
92 - actionLoading.value = false  
93 - } 99 + })
94 } 100 }
95 101
96 // ========== 详情相关 ========== 102 // ========== 详情相关 ==========
@@ -150,7 +156,6 @@ onMounted(async () =&gt; { @@ -150,7 +156,6 @@ onMounted(async () =&gt; {
150 <DictTag :type="DICT_TYPE.COMMON_STATUS" :value="row.status" /> 156 <DictTag :type="DICT_TYPE.COMMON_STATUS" :value="row.status" />
151 </template> 157 </template>
152 <template #packageId="{ row }"> 158 <template #packageId="{ row }">
153 - <!-- <DictTag :type="DICT_TYPE.SYSTEM_TENANT_PACKAGE_ID" :value="row.packageId" />-->  
154 <el-tag v-if="row.packageId === 0" type="danger">系统租户</el-tag> 159 <el-tag v-if="row.packageId === 0" type="danger">系统租户</el-tag>
155 <el-tag v-else type="success"> {{ getPackageName(row.packageId) }} </el-tag> 160 <el-tag v-else type="success"> {{ getPackageName(row.packageId) }} </el-tag>
156 </template> 161 </template>
@@ -161,7 +166,6 @@ onMounted(async () =&gt; { @@ -161,7 +166,6 @@ onMounted(async () =&gt; {
161 <span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span> 166 <span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
162 </template> 167 </template>
163 <template #action="{ row }"> 168 <template #action="{ row }">
164 - <!-- <el-button type="text" v-hasPermi="['system:tenant:update']" @click="handleUpdate(row)">-->  
165 <el-button 169 <el-button
166 link 170 link
167 type="primary" 171 type="primary"
@@ -199,7 +203,7 @@ onMounted(async () =&gt; { @@ -199,7 +203,7 @@ onMounted(async () =&gt; {
199 ref="formRef" 203 ref="formRef"
200 > 204 >
201 <template #packageId> 205 <template #packageId>
202 - <el-select v-model="tenantPackageId" placeholder="Select"> 206 + <el-select v-model="tenantPackageId">
203 <el-option 207 <el-option
204 v-for="item in tenantPackageOptions" 208 v-for="item in tenantPackageOptions"
205 :key="item.id" 209 :key="item.id"
yudao-ui-admin-vue3/src/views/system/tenant/tenant.data.ts
@@ -56,6 +56,29 @@ const crudSchemas = reactive&lt;CrudSchema[]&gt;([ @@ -56,6 +56,29 @@ const crudSchemas = reactive&lt;CrudSchema[]&gt;([
56 } 56 }
57 }, 57 },
58 { 58 {
  59 + label: '用户名称',
  60 + field: 'username',
  61 + table: {
  62 + show: false
  63 + },
  64 + detail: {
  65 + show: false
  66 + }
  67 + },
  68 + {
  69 + label: '用户密码',
  70 + field: 'password',
  71 + table: {
  72 + show: false
  73 + },
  74 + detail: {
  75 + show: false
  76 + },
  77 + form: {
  78 + component: 'InputPassword'
  79 + }
  80 + },
  81 + {
59 label: '账号额度', 82 label: '账号额度',
60 field: 'accountCount', 83 field: 'accountCount',
61 form: { 84 form: {
@@ -71,7 +94,7 @@ const crudSchemas = reactive&lt;CrudSchema[]&gt;([ @@ -71,7 +94,7 @@ const crudSchemas = reactive&lt;CrudSchema[]&gt;([
71 component: 'DatePicker', 94 component: 'DatePicker',
72 componentProps: { 95 componentProps: {
73 type: 'datetime', 96 type: 'datetime',
74 - valueFormat: 'YYYY-MM-DD HH:mm:ss' 97 + valueFormat: 'x'
75 } 98 }
76 } 99 }
77 }, 100 },
yudao-ui-admin-vue3/src/views/system/tenantPackage/index.vue
@@ -76,24 +76,30 @@ const handleUpdate = async (row: any) =&gt; { @@ -76,24 +76,30 @@ const handleUpdate = async (row: any) =&gt; {
76 } 76 }
77 // 提交按钮 77 // 提交按钮
78 const submitForm = async () => { 78 const submitForm = async () => {
79 - loading.value = true  
80 - // 提交请求  
81 - try {  
82 - const data = unref(formRef)?.formModel as TenantPackageVO  
83 - data.menuIds = treeRef.value!.getCheckedKeys(false) as string[]  
84 - if (actionType.value === 'create') {  
85 - await TenantPackageApi.createTenantPackageTypeApi(data)  
86 - ElMessage.success(t('common.createSuccess'))  
87 - } else {  
88 - await TenantPackageApi.updateTenantPackageTypeApi(data)  
89 - ElMessage.success(t('common.updateSuccess')) 79 + const elForm = unref(formRef)?.getElFormRef()
  80 + if (!elForm) return
  81 + elForm.validate(async (valid) => {
  82 + if (valid) {
  83 + loading.value = true
  84 + // 提交请求
  85 + try {
  86 + const data = unref(formRef)?.formModel as TenantPackageVO
  87 + data.menuIds = treeRef.value!.getCheckedKeys(false) as string[]
  88 + if (actionType.value === 'create') {
  89 + await TenantPackageApi.createTenantPackageTypeApi(data)
  90 + ElMessage.success(t('common.createSuccess'))
  91 + } else {
  92 + await TenantPackageApi.updateTenantPackageTypeApi(data)
  93 + ElMessage.success(t('common.updateSuccess'))
  94 + }
  95 + // 操作成功,重新加载列表
  96 + dialogVisible.value = false
  97 + await getList()
  98 + } finally {
  99 + loading.value = false
  100 + }
90 } 101 }
91 - // 操作成功,重新加载列表  
92 - dialogVisible.value = false  
93 - await getList()  
94 - } finally {  
95 - loading.value = false  
96 - } 102 + })
97 } 103 }
98 104
99 // ========== 初始化 ========== 105 // ========== 初始化 ==========
yudao-ui-admin-vue3/src/views/system/user/index.vue
@@ -112,9 +112,9 @@ const setDialogTile = async (type: string) =&gt; { @@ -112,9 +112,9 @@ const setDialogTile = async (type: string) =&gt; {
112 112
113 // 新增操作 113 // 新增操作
114 const handleAdd = () => { 114 const handleAdd = () => {
115 - setDialogTile('create')  
116 // 重置表单 115 // 重置表单
117 deptId.value = 0 116 deptId.value = 0
  117 + setDialogTile('create')
118 unref(formRef)?.getElFormRef()?.resetFields() 118 unref(formRef)?.getElFormRef()?.resetFields()
119 } 119 }
120 120
@@ -123,6 +123,7 @@ const handleUpdate = async (row: UserVO) =&gt; { @@ -123,6 +123,7 @@ const handleUpdate = async (row: UserVO) =&gt; {
123 await setDialogTile('update') 123 await setDialogTile('update')
124 // 设置数据 124 // 设置数据
125 const res = await UserApi.getUserApi(row.id) 125 const res = await UserApi.getUserApi(row.id)
  126 + console.info(res)
126 deptId.value = res.deptId 127 deptId.value = res.deptId
127 postIds.value = res.postIds 128 postIds.value = res.postIds
128 unref(formRef)?.setValues(res) 129 unref(formRef)?.setValues(res)
@@ -281,9 +282,9 @@ const excelUploadError = (): void =&gt; { @@ -281,9 +282,9 @@ const excelUploadError = (): void =&gt; {
281 } 282 }
282 // ========== 初始化 ========== 283 // ========== 初始化 ==========
283 onMounted(async () => { 284 onMounted(async () => {
284 - await getTree()  
285 await getPostOptions() 285 await getPostOptions()
286 await getList() 286 await getList()
  287 + await getTree()
287 }) 288 })
288 </script> 289 </script>
289 290
yudao-ui-admin-vue3/tsconfig.json
@@ -30,7 +30,7 @@ @@ -30,7 +30,7 @@
30 "@types/intro.js", 30 "@types/intro.js",
31 "@types/qrcode", 31 "@types/qrcode",
32 "vite-plugin-svg-icons/client", 32 "vite-plugin-svg-icons/client",
33 - "unplugin-vue-define-options" 33 + "unplugin-vue-macros/macros-global"
34 ], 34 ],
35 "typeRoots": ["./node_modules/@types/", "./types"] 35 "typeRoots": ["./node_modules/@types/", "./types"]
36 }, 36 },
yudao-ui-admin-vue3/types/componentType/configGlobal.d.ts deleted
1 -declare interface ConfigGlobalTypes {  
2 - size?: ElememtPlusSize  
3 -}  
yudao-ui-admin-vue3/types/global.d.ts
1 -declare interface Fn<T = any> {  
2 - (...arg: T[]): T  
3 -}  
4 -  
5 -declare type Nullable<T> = T | null 1 +import type { CSSProperties } from 'vue'
  2 +declare global {
  3 + declare interface Fn<T = any> {
  4 + (...arg: T[]): T
  5 + }
6 6
7 -declare type ElRef<T extends HTMLElement = HTMLDivElement> = Nullable<T> 7 + declare type Nullable<T> = T | null
8 8
9 -declare type ElememtPlusSize = 'default' | 'small' | 'large' 9 + declare type ElRef<T extends HTMLElement = HTMLDivElement> = Nullable<T>
10 10
11 -declare type ElementPlusInfoType = 'success' | 'info' | 'warning' | 'danger' 11 + declare type Recordable<T = any, K = string> = Record<K extends null | undefined ? string : K, T>
12 12
13 -declare type Recordable<T = any, K = string> = Record<K extends null | undefined ? string : K, T> 13 + declare type ComponentRef<T> = InstanceType<T>
14 14
15 -declare type ComponentRef<T> = InstanceType<T> 15 + declare type LocaleType = 'zh-CN' | 'en'
16 16
17 -declare type LocaleType = 'zh-CN' | 'en' 17 + declare type AxiosHeaders =
  18 + | 'application/json'
  19 + | 'application/x-www-form-urlencoded'
  20 + | 'multipart/form-data'
18 21
19 -declare type AxiosHeaders =  
20 - | 'application/json'  
21 - | 'application/x-www-form-urlencoded'  
22 - | 'multipart/form-data' 22 + declare type AxiosMethod = 'get' | 'post' | 'delete' | 'put' | 'GET' | 'POST' | 'DELETE' | 'PUT'
23 23
24 -declare type AxiosMethod = 'GET' | 'POST' | 'DELETE' | 'PUT' 24 + declare type AxiosResponseType = 'arraybuffer' | 'blob' | 'document' | 'json' | 'text' | 'stream'
25 25
26 -declare type AxiosResponseType = 'arraybuffer' | 'blob' | 'document' | 'json' | 'text' | 'stream'  
27 -  
28 -declare interface AxiosConfig {  
29 - params?: any  
30 - data?: any  
31 - url?: string  
32 - method?: AxiosMethod  
33 - headersType?: string  
34 - responseType?: AxiosResponseType  
35 -} 26 + declare interface AxiosConfig {
  27 + params?: any
  28 + data?: any
  29 + url?: string
  30 + method?: AxiosMethod
  31 + headersType?: string
  32 + responseType?: AxiosResponseType
  33 + }
36 34
37 -declare interface IResponse<T = any> {  
38 - code?: number  
39 - data: T extends any ? T : T & any 35 + declare interface IResponse<T = any> {
  36 + code: string
  37 + data: T extends any ? T : T & any
  38 + }
40 } 39 }
yudao-ui-admin-vue3/vite.config.ts
@@ -9,9 +9,9 @@ import VueI18n from &#39;@intlify/vite-plugin-vue-i18n&#39; @@ -9,9 +9,9 @@ import VueI18n from &#39;@intlify/vite-plugin-vue-i18n&#39;
9 import { createStyleImportPlugin, ElementPlusResolve } from 'vite-plugin-style-import' 9 import { createStyleImportPlugin, ElementPlusResolve } from 'vite-plugin-style-import'
10 import { createSvgIconsPlugin } from 'vite-plugin-svg-icons' 10 import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
11 import PurgeIcons from 'vite-plugin-purge-icons' 11 import PurgeIcons from 'vite-plugin-purge-icons'
12 -import DefineOptions from 'unplugin-vue-define-options/vite'  
13 import { createHtmlPlugin } from 'vite-plugin-html' 12 import { createHtmlPlugin } from 'vite-plugin-html'
14 import viteCompression from 'vite-plugin-compression' 13 import viteCompression from 'vite-plugin-compression'
  14 +import VueMarcos from 'unplugin-vue-macros/vite'
15 15
16 // 当前执行node命令时文件夹的地址(工作目录) 16 // 当前执行node命令时文件夹的地址(工作目录)
17 const root = process.cwd() 17 const root = process.cwd()
@@ -80,7 +80,7 @@ export default ({ command, mode }: ConfigEnv): UserConfig =&gt; { @@ -80,7 +80,7 @@ export default ({ command, mode }: ConfigEnv): UserConfig =&gt; {
80 svgoOptions: true 80 svgoOptions: true
81 }), 81 }),
82 PurgeIcons(), 82 PurgeIcons(),
83 - DefineOptions(), 83 + VueMarcos(),
84 viteCompression({ 84 viteCompression({
85 verbose: true, // 是否在控制台输出压缩结果 85 verbose: true, // 是否在控制台输出压缩结果
86 disable: true, // 是否禁用 86 disable: true, // 是否禁用
yudao-ui-admin/src/components/TopNav/index.vue
@@ -67,7 +67,7 @@ export default { @@ -67,7 +67,7 @@ export default {
67 }, 67 },
68 // 设置子路由 68 // 设置子路由
69 childrenMenus() { 69 childrenMenus() {
70 - var childrenMenus = []; 70 + const childrenMenus = [];
71 this.routers.map((router) => { 71 this.routers.map((router) => {
72 for (var item in router.children) { 72 for (var item in router.children) {
73 if (router.children[item].parentPath === undefined) { 73 if (router.children[item].parentPath === undefined) {
yudao-ui-admin/src/directive/index.js
@@ -5,6 +5,7 @@ import dialogDragWidth from &#39;./dialog/dragWidth&#39; @@ -5,6 +5,7 @@ import dialogDragWidth from &#39;./dialog/dragWidth&#39;
5 import dialogDragHeight from './dialog/dragHeight' 5 import dialogDragHeight from './dialog/dragHeight'
6 import clipboard from './module/clipboard' 6 import clipboard from './module/clipboard'
7 7
  8 +// Vue:自定义指令:https://v2.cn.vuejs.org/v2/guide/custom-directive.html
8 const install = function(Vue) { 9 const install = function(Vue) {
9 Vue.directive('hasRole', hasRole) 10 Vue.directive('hasRole', hasRole)
10 Vue.directive('hasPermi', hasPermi) 11 Vue.directive('hasPermi', hasPermi)
yudao-ui-admin/src/directive/permission/hasPermi.js
@@ -2,22 +2,21 @@ @@ -2,22 +2,21 @@
2 * 操作权限处理 2 * 操作权限处理
3 * Copyright (c) 2019 ruoyi 3 * Copyright (c) 2019 ruoyi
4 */ 4 */
5 -  
6 import store from '@/store' 5 import store from '@/store'
7 6
8 export default { 7 export default {
9 inserted(el, binding, vnode) { 8 inserted(el, binding, vnode) {
10 const { value } = binding 9 const { value } = binding
11 - const all_permission = "*:*:*";  
12 - const permissions = store.getters && store.getters.permissions 10 + const all_permission = "*:*:*"; // 全部权限
  11 + const permissions = store.getters && store.getters.permissions // 用户拥有的权限标识的数组
13 12
14 if (value && value instanceof Array && value.length > 0) { 13 if (value && value instanceof Array && value.length > 0) {
  14 + // 判断是否有权限
15 const permissionFlag = value 15 const permissionFlag = value
16 -  
17 const hasPermissions = permissions.some(permission => { 16 const hasPermissions = permissions.some(permission => {
18 return all_permission === permission || permissionFlag.includes(permission) 17 return all_permission === permission || permissionFlag.includes(permission)
19 }) 18 })
20 - 19 + // 如果没有权限,则移除元素
21 if (!hasPermissions) { 20 if (!hasPermissions) {
22 el.parentNode && el.parentNode.removeChild(el) 21 el.parentNode && el.parentNode.removeChild(el)
23 } 22 }
yudao-ui-admin/src/directive/permission/hasRole.js
@@ -2,22 +2,22 @@ @@ -2,22 +2,22 @@
2 * 角色权限处理 2 * 角色权限处理
3 * Copyright (c) 2019 ruoyi 3 * Copyright (c) 2019 ruoyi
4 */ 4 */
5 - 5 +
6 import store from '@/store' 6 import store from '@/store'
7 7
8 export default { 8 export default {
9 inserted(el, binding, vnode) { 9 inserted(el, binding, vnode) {
10 const { value } = binding 10 const { value } = binding
11 const super_admin = "admin"; 11 const super_admin = "admin";
12 - const roles = store.getters && store.getters.roles 12 + const roles = store.getters && store.getters.roles // 用户拥有的角色标识的数组
13 13
14 if (value && value instanceof Array && value.length > 0) { 14 if (value && value instanceof Array && value.length > 0) {
  15 + // 判断是否有角色
15 const roleFlag = value 16 const roleFlag = value
16 -  
17 const hasRole = roles.some(role => { 17 const hasRole = roles.some(role => {
18 return super_admin === role || roleFlag.includes(role) 18 return super_admin === role || roleFlag.includes(role)
19 }) 19 })
20 - 20 + // 如果没有角色,则移除元素
21 if (!hasRole) { 21 if (!hasRole) {
22 el.parentNode && el.parentNode.removeChild(el) 22 el.parentNode && el.parentNode.removeChild(el)
23 } 23 }
yudao-ui-admin/src/layout/components/Sidebar/index.vue
@@ -12,6 +12,7 @@ @@ -12,6 +12,7 @@
12 :collapse-transition="false" 12 :collapse-transition="false"
13 mode="vertical" 13 mode="vertical"
14 > 14 >
  15 + <!-- 根据 sidebarRouters 路由,生成菜单 -->
15 <sidebar-item 16 <sidebar-item
16 v-for="(route, index) in sidebarRouters" 17 v-for="(route, index) in sidebarRouters"
17 :key="route.path + index" 18 :key="route.path + index"
yudao-ui-admin/src/permission.js
@@ -24,11 +24,11 @@ router.beforeEach((to, from, next) =&gt; { @@ -24,11 +24,11 @@ router.beforeEach((to, from, next) =&gt; {
24 isRelogin.show = true 24 isRelogin.show = true
25 // 获取字典数据 add by 芋艿 25 // 获取字典数据 add by 芋艿
26 store.dispatch('dict/loadDictDatas') 26 store.dispatch('dict/loadDictDatas')
27 - // 判断当前用户是否已拉取完user_info信息 27 + // 判断当前用户是否已拉取完 user_info 信息
28 store.dispatch('GetInfo').then(() => { 28 store.dispatch('GetInfo').then(() => {
29 isRelogin.show = false 29 isRelogin.show = false
30 store.dispatch('GenerateRoutes').then(accessRoutes => { 30 store.dispatch('GenerateRoutes').then(accessRoutes => {
31 - // 根据roles权限生成可访问的路由表 31 + // 根据 roles 权限生成可访问的路由表
32 router.addRoutes(accessRoutes) // 动态添加可访问路由表 32 router.addRoutes(accessRoutes) // 动态添加可访问路由表
33 next({ ...to, replace: true }) // hack方法 确保addRoutes已完成 33 next({ ...to, replace: true }) // hack方法 确保addRoutes已完成
34 }) 34 })
@@ -48,7 +48,8 @@ router.beforeEach((to, from, next) =&gt; { @@ -48,7 +48,8 @@ router.beforeEach((to, from, next) =&gt; {
48 // 在免登录白名单,直接进入 48 // 在免登录白名单,直接进入
49 next() 49 next()
50 } else { 50 } else {
51 - next(`/login?redirect=${to.fullPath}`) // 否则全部重定向到登录页 51 + const redirect = encodeURIComponent(to.fullPath) // 编码 URI,保证参数跳转回去后,可以继续带上
  52 + next(`/login?redirect=${redirect}`) // 否则全部重定向到登录页
52 NProgress.done() 53 NProgress.done()
53 } 54 }
54 } 55 }
yudao-ui-admin/src/router/index.js
@@ -8,18 +8,20 @@ Vue.use(Router) @@ -8,18 +8,20 @@ Vue.use(Router)
8 /** 8 /**
9 * Note: 路由配置项 9 * Note: 路由配置项
10 * 10 *
11 - * hidden: true // 当设置 true 的时候该路由不会再侧边栏出现 如401,login等页面,或者如一些编辑页面/edit/1 11 + * hidden: true // 【重要】当设置 true 的时候该路由不会再侧边栏出现 如 401,login 等页面,或者如一些编辑页面 /edit/1
12 * alwaysShow: true // 当你一个路由下面的 children 声明的路由大于1个时,自动会变成嵌套的模式--如组件页面 12 * alwaysShow: true // 当你一个路由下面的 children 声明的路由大于1个时,自动会变成嵌套的模式--如组件页面
13 * // 只有一个时,会将那个子路由当做根路由显示在侧边栏--如引导页面 13 * // 只有一个时,会将那个子路由当做根路由显示在侧边栏--如引导页面
14 * // 若你想不管路由下面的 children 声明的个数都显示你的根路由 14 * // 若你想不管路由下面的 children 声明的个数都显示你的根路由
15 * // 你可以设置 alwaysShow: true,这样它就会忽略之前定义的规则,一直显示根路由 15 * // 你可以设置 alwaysShow: true,这样它就会忽略之前定义的规则,一直显示根路由
  16 + * path: '/login', // 【重要】访问的 URL 路径
  17 + * component: Layout, // 【重要】对应的组件;也可以是 (resolve) => require(['@/views/login'], resolve),
16 * redirect: noRedirect // 当设置 noRedirect 的时候该路由在面包屑导航中不可被点击 18 * redirect: noRedirect // 当设置 noRedirect 的时候该路由在面包屑导航中不可被点击
17 - * name:'router-name' // 设定路由的名字,一定要填写不然使用<keep-alive>时会出现各种问题 19 + * name:'router-name' // 【重要】设定路由的名字,一定要填写不然使用 <keep-alive> 时会出现各种问题
18 * meta : { 20 * meta : {
19 - noCache: true // 如果设置为true,则不会被 <keep-alive> 缓存(默认 false)  
20 - title: 'title' // 设置该路由在侧边栏和面包屑中展示的名字  
21 - icon: 'svg-name' // 设置该路由的图标,对应路径src/assets/icons/svg  
22 - breadcrumb: false // 如果设置为false,则不会在breadcrumb面包屑中显示 21 + noCache: true // 【重要】如果设置为 true,则不会被 <keep-alive> 缓存(默认 false)
  22 + title: 'title' // 【重要】设置该路由在侧边栏和面包屑中展示的名字
  23 + icon: 'svg-name' // 【重要】设置该路由的图标,对应路径 src/assets/icons/svg
  24 + breadcrumb: false // 如果设置为 false,则不会在 breadcrumb 面包屑中显示
23 activeMenu: '/system/user' // 当路由设置了该属性,则会高亮相对应的侧边栏。 25 activeMenu: '/system/user' // 当路由设置了该属性,则会高亮相对应的侧边栏。
24 } 26 }
25 */ 27 */
yudao-ui-admin/src/store/modules/permission.js
@@ -8,7 +8,8 @@ const permission = { @@ -8,7 +8,8 @@ const permission = {
8 state: { 8 state: {
9 routes: [], 9 routes: [],
10 addRoutes: [], 10 addRoutes: [],
11 - sidebarRouters: [] 11 + sidebarRouters: [], // 左侧边菜单的路由,被 Sidebar/index.vue 使用
  12 + topbarRouters: [], // 顶部菜单的路由,被 TopNav/index.vue 使用
12 }, 13 },
13 mutations: { 14 mutations: {
14 SET_ROUTES: (state, routes) => { 15 SET_ROUTES: (state, routes) => {
@@ -29,10 +30,10 @@ const permission = { @@ -29,10 +30,10 @@ const permission = {
29 // 生成路由 30 // 生成路由
30 GenerateRoutes({ commit }) { 31 GenerateRoutes({ commit }) {
31 return new Promise(resolve => { 32 return new Promise(resolve => {
32 - // 向后端请求路由数据 33 + // 向后端请求路由数据(菜单)
33 getRouters().then(res => { 34 getRouters().then(res => {
34 - const sdata = JSON.parse(JSON.stringify(res.data))  
35 - const rdata = JSON.parse(JSON.stringify(res.data)) 35 + const sdata = JSON.parse(JSON.stringify(res.data)) // 【重要】用于菜单中的数据
  36 + const rdata = JSON.parse(JSON.stringify(res.data)) // 用于最后添加到 Router 中的数据
36 const sidebarRoutes = filterAsyncRouter(sdata) 37 const sidebarRoutes = filterAsyncRouter(sdata)
37 const rewriteRoutes = filterAsyncRouter(rdata, false, true) 38 const rewriteRoutes = filterAsyncRouter(rdata, false, true)
38 rewriteRoutes.push({ path: '*', redirect: '/404', hidden: true }) 39 rewriteRoutes.push({ path: '*', redirect: '/404', hidden: true })
@@ -57,7 +58,7 @@ function filterAsyncRouter(asyncRouterMap, lastRouter = false, type = false) { @@ -57,7 +58,7 @@ function filterAsyncRouter(asyncRouterMap, lastRouter = false, type = false) {
57 icon: route.icon, 58 icon: route.icon,
58 noCache: !route.keepAlive, 59 noCache: !route.keepAlive,
59 } 60 }
60 - // 路由地址转首字母大写驼峰,作为路由名称,适配keepAlive 61 + // 路由地址转首字母大写驼峰,作为路由名称,适配 keepAlive
61 route.name = toCamelCase(route.path, true) 62 route.name = toCamelCase(route.path, true)
62 route.hidden = !route.visible 63 route.hidden = !route.visible
63 // 处理 component 属性 64 // 处理 component 属性
yudao-ui-admin/src/views/bpm/group/index.vue
@@ -127,7 +127,6 @@ export default { @@ -127,7 +127,6 @@ export default {
127 pageSize: 10, 127 pageSize: 10,
128 name: null, 128 name: null,
129 status: null, 129 status: null,
130 - tenantId: null,  
131 createTime: [] 130 createTime: []
132 }, 131 },
133 // 表单参数 132 // 表单参数
yudao-ui-admin/src/views/login.vue
@@ -190,7 +190,7 @@ export default { @@ -190,7 +190,7 @@ export default {
190 // 验证码开关 190 // 验证码开关
191 this.captchaEnable = getCaptchaEnable(); 191 this.captchaEnable = getCaptchaEnable();
192 // 重定向地址 192 // 重定向地址
193 - this.redirect = this.$route.query.redirect; 193 + this.redirect = this.$route.query.redirect ? decodeURIComponent(this.$route.query.redirect) : undefined;
194 this.getCookie(); 194 this.getCookie();
195 }, 195 },
196 methods: { 196 methods: {
yudao-ui-admin/src/views/socialLogin.vue
@@ -117,7 +117,7 @@ export default { @@ -117,7 +117,7 @@ export default {
117 // 验证码开关 117 // 验证码开关
118 this.captchaEnable = getCaptchaEnable(); 118 this.captchaEnable = getCaptchaEnable();
119 // 重定向地址 119 // 重定向地址
120 - this.redirect = this.$route.query.redirect; 120 + this.redirect = this.$route.query.redirect ? decodeURIComponent(this.$route.query.redirect) : undefined;
121 // 社交登录相关 121 // 社交登录相关
122 this.type = this.$route.query.type; 122 this.type = this.$route.query.type;
123 this.code = this.$route.query.code; 123 this.code = this.$route.query.code;
yudao-ui-admin/src/views/sso.vue
@@ -19,12 +19,7 @@ @@ -19,12 +19,7 @@
19 </el-tab-pane> 19 </el-tab-pane>
20 </el-tabs> 20 </el-tabs>
21 <div> 21 <div>
22 - <el-form ref="loginForm" :model="loginForm" :rules="LoginRules" class="login-form">  
23 - <el-form-item prop="tenantName" v-if="tenantEnable">  
24 - <el-input v-model="loginForm.tenantName" type="text" auto-complete="off" placeholder='租户'>  
25 - <svg-icon slot="prefix" icon-class="tree" class="el-input__icon input-icon"/>  
26 - </el-input>  
27 - </el-form-item> 22 + <el-form ref="loginForm" :model="loginForm" class="login-form">
28 <!-- 授权范围的选择 --> 23 <!-- 授权范围的选择 -->
29 此第三方应用请求获得以下权限: 24 此第三方应用请求获得以下权限:
30 <el-form-item prop="scopes"> 25 <el-form-item prop="scopes">
@@ -56,18 +51,13 @@ @@ -56,18 +51,13 @@
56 </template> 51 </template>
57 52
58 <script> 53 <script>
59 -import {getTenantIdByName} from "@/api/system/tenant";  
60 -import {getTenantEnable} from "@/utils/ruoyi";  
61 import {authorize, getAuthorize} from "@/api/login"; 54 import {authorize, getAuthorize} from "@/api/login";
62 -import {getTenantName, setTenantId} from "@/utils/auth";  
63 55
64 export default { 56 export default {
65 name: "Login", 57 name: "Login",
66 data() { 58 data() {
67 return { 59 return {
68 - tenantEnable: true,  
69 loginForm: { 60 loginForm: {
70 - tenantName: "芋道源码",  
71 scopes: [], // 已选中的 scope 数组 61 scopes: [], // 已选中的 scope 数组
72 }, 62 },
73 params: { // URL 上的 client_id、scope 等参数 63 params: { // URL 上的 client_id、scope 等参数
@@ -81,35 +71,10 @@ export default { @@ -81,35 +71,10 @@ export default {
81 name: '', 71 name: '',
82 logo: '', 72 logo: '',
83 }, 73 },
84 - LoginRules: {  
85 - tenantName: [  
86 - {required: true, trigger: "blur", message: "租户不能为空"},  
87 - {  
88 - validator: (rule, value, callback) => {  
89 - // debugger  
90 - getTenantIdByName(value).then(res => {  
91 - const tenantId = res.data;  
92 - if (tenantId && tenantId >= 0) {  
93 - // 设置租户  
94 - setTenantId(tenantId)  
95 - callback();  
96 - } else {  
97 - callback('租户不存在');  
98 - }  
99 - });  
100 - },  
101 - trigger: 'blur'  
102 - }  
103 - ]  
104 - },  
105 loading: false 74 loading: false
106 }; 75 };
107 }, 76 },
108 created() { 77 created() {
109 - // 租户开关  
110 - this.tenantEnable = getTenantEnable();  
111 - this.getCookie();  
112 -  
113 // 解析参数 78 // 解析参数
114 // 例如说【自动授权不通过】:client_id=default&redirect_uri=https%3A%2F%2Fwww.iocoder.cn&response_type=code&scope=user.read%20user.write 79 // 例如说【自动授权不通过】:client_id=default&redirect_uri=https%3A%2F%2Fwww.iocoder.cn&response_type=code&scope=user.read%20user.write
115 // 例如说【自动授权通过】:client_id=default&redirect_uri=https%3A%2F%2Fwww.iocoder.cn&response_type=code&scope=user.read 80 // 例如说【自动授权通过】:client_id=default&redirect_uri=https%3A%2F%2Fwww.iocoder.cn&response_type=code&scope=user.read
@@ -162,13 +127,6 @@ export default { @@ -162,13 +127,6 @@ export default {
162 }) 127 })
163 }, 128 },
164 methods: { 129 methods: {
165 - getCookie() {  
166 - const tenantName = getTenantName();  
167 - this.loginForm = {  
168 - ...this.loginForm,  
169 - tenantName: tenantName ? tenantName : this.loginForm.tenantName,  
170 - };  
171 - },  
172 handleAuthorize(approved) { 130 handleAuthorize(approved) {
173 this.$refs.loginForm.validate(valid => { 131 this.$refs.loginForm.validate(valid => {
174 if (!valid) { 132 if (!valid) {