25 Commits

Author SHA1 Message Date
2b22c598eb release: v1.0.1 2023-08-17 22:37:40 +08:00
4a8af1f72d perf: 优化根据 ID 查询用户昵称方法 2023-07-19 23:11:00 +08:00
b0b1127b5b style: 优化 BaseController 中部分权限码的使用 2023-07-19 23:05:04 +08:00
bisheng
104f69e8a0 fix: 获取字典参数为空时的判断条件 2023-07-06 15:26:05 +08:00
76f04dd38f fix: 优化分页总记录数数据类型 2023-07-05 22:07:59 +08:00
b632c18399 fix: 完善使用通用查询注解时的参数验证提示 2023-07-01 17:03:26 +08:00
026247f677 fix: 完善查询用户数据权限
1、暂时对用户 Mapper 的 selectList 和 selectPage 进行了数据权限过滤;
2、由于对用户 Mapper 的 selectList 添加了数据权限过滤,请小心使用 selectOne 因为其底层使用的也是 selectList;
3、tips:如需在查询时进行数据权限过滤,尽量不要对 MyBatis Plus 原生查询进行过滤,而是单独声明一个查询方法来使用,以避免不需要过滤数据权限查询数据的场景受到较大影响。
2023-06-30 00:38:25 +08:00
8743ed14d9 style: 解决 IDE 报 Delete eslint(prettier/prettier) 警告的问题
endOfLine: 'auto',自动检测行尾符,根据当前操作系统自动选择行尾符。例如:Windows 上的行尾符是 CRLF(\r\n),而 Unix 和 Linux 上的行尾符是 LF(\n)。
2023-06-29 20:39:23 +08:00
39b0b9a48e style: 解决启动时部分行分隔符报 warning 的问题
来自 @woodlxl(小鹿)
2023-06-28 22:49:58 +08:00
9376d6fd5f style: 优化启动程序打印日志 2023-04-20 22:59:30 +08:00
efbcb9b39d style: 优化业务实现注释 2023-04-13 22:29:53 +08:00
165effedb9 chore: 优化生产环境接口文档配置 2023-04-13 21:03:17 +08:00
f3fabea7dd chore: 优化数据库表结构中部分类型长度 2023-04-13 20:51:49 +08:00
2d2a7e7c8e fix: 修复分页查询条件默认值未生效的问题
Spring MVC 对于对象型参数的属性赋值,如果属性值为 null 则不会调用其对应 set 方法,所以在 set
方法中添加默认处理逻辑无效
2023-04-09 00:40:28 +08:00
18c54a74fc fix: 完善事务注解 2023-04-03 21:31:29 +08:00
9b2a924184 docs: 更新 README 系统功能部分内容 2023-03-31 23:46:35 +08:00
e6f7429fa3 style: 使用常量优化部分魔法值 2023-03-31 23:31:30 +08:00
48de2e85e0 style: 优化部分 Properties 用法 2023-03-31 22:16:49 +08:00
5968f402ed fix: 修复邮箱健康检查报错问题并优化部分配置写法
关于邮箱健康检查的问题,作者一直忽略了这部分,倒不是诚心如此,而是作者没遇到过这个检查报错。原因是作者虽然没在本地单独调整配置文件,但作者在 IDEA 中给启动程序配置了 Program arguments:--spring.mail.username=xxx --spring.mail.password=xxx,所以实际配置也没问题,但各位小伙伴拉下代码后没注意到邮箱配置的话,那可就没那么友好了。所以作为一个非核心服务,干脆关闭邮箱的健康检查,如果哪个小伙伴真的有需要自行再打开就可以。
2023-03-31 21:50:41 +08:00
a623acd4a5 fix: 优化通用查询注解解析器 2023-03-31 21:31:07 +08:00
9f25925d46 fix: 调整 BaseController API 方法的访问权限修饰符 2023-03-31 21:17:55 +08:00
331491dd5d fix: 完善创建用户参数校验 2023-03-31 21:13:30 +08:00
fe9201427e 修复:修正版本号 2023-03-30 20:29:05 +08:00
8b955a0b1b 修复:补充校验 2023-03-29 23:47:48 +08:00
d4aedaabc8 优化:使用变量优化配置 2023-03-27 21:36:31 +08:00
84 changed files with 1170 additions and 675 deletions

View File

@@ -1,4 +1,26 @@
# v1.0.0 (2023-03-26)
## v1.0.1 (2023-08-17)
### 💎 功能优化
- 优化根据 ID 查询用户昵称方法 ([4a8af1f](https://github.com/Charles7c/continew-admin/commit/4a8af1f72d9249afa1c013e08674f492f453b020))
- 优化 BaseController 中部分权限码的使用 ([b0b1127](https://github.com/Charles7c/continew-admin/commit/b0b1127b5bd39e9bc431e9fa9c86201bbc18e891))
- 优化分页总记录数数据类型 ([76f04dd](https://github.com/Charles7c/continew-admin/commit/76f04dd38f90aad6abf82d2dccba031d4d9108cf))
- 优化通用查询注解解析器 ([a623acd](https://github.com/Charles7c/continew-admin/commit/a623acd4a5529ae42898ec359f595716acc5bab8)) ([b632c18](https://github.com/Charles7c/continew-admin/commit/b632c183994ac71382180a38bf7bdb7a6315c1e6))
- 优化数据库表结构中部分类型长度 ([f3fabea](https://github.com/Charles7c/continew-admin/commit/f3fabea7dd736d94badecbc08091eec6274f5fb7))
- 使用常量优化部分魔法值 ([e6f7429](https://github.com/Charles7c/continew-admin/commit/e6f7429fa30cbc87c03a073a53b6f7df24d33d8d))
- 优化部分 Properties 用法 ([48de2e8](https://github.com/Charles7c/continew-admin/commit/48de2e85e0fbf60f10769cd3529f79ac3c531e92))
### 🐛 问题修复
- 修复获取字典参数为空时的判断条件 ([#6](https://github.com/Charles7c/continew-admin/pull/6)) ([104f69e](https://github.com/Charles7c/continew-admin/commit/104f69e8a09ce36163f6f9680b2d8d61bb45f11a))
- 完善查询用户数据权限 ([026247f](https://github.com/Charles7c/continew-admin/commit/026247f677110ae199124a67c68503729cbaec92))
- 解决 IDE 报 Delete ␍ eslint(prettier/prettier) 警告的问题 ([8743ed1](https://github.com/Charles7c/continew-admin/commit/8743ed14d927ab52814ed5f5f166afaa7a6b78b2))
- 修复分页查询条件默认值未生效的问题 ([2d2a7e7](https://github.com/Charles7c/continew-admin/commit/2d2a7e7c8e31763ac3ea514d8a92c3938376dd3a))
- 完善各模块事务注解 ([18c54a7](https://github.com/Charles7c/continew-admin/commit/18c54a74fc6ff0650ff53eeadc094d7e1df0b0a5))
- 修复邮箱健康检查报错问题并优化部分配置写法 ([5968f40](https://github.com/Charles7c/continew-admin/commit/5968f402ed478244d36f5825373190ed00d8c1f1))
- 完善各模块参数校验 ([8b955a0](https://github.com/Charles7c/continew-admin/commit/8b955a0b1bde4e8959fc0dfbc11a326d9eec0b45))
## v1.0.0 (2023-03-26)
### ✨ 新特性
@@ -7,4 +29,4 @@
* 部门管理:可配置系统组织架构,树形表格展示
* 菜单管理:已实现菜单动态路由,后端可配置化,支持多级菜单
* 在线用户:管理当前登录用户,可一键踢下线
* 日志管理:提供在线用户监控、登录日志监控、操作日志监控和系统日志监控等监控功能
* 日志管理:提供在线用户监控、登录日志监控、操作日志监控和系统日志监控等监控功能

View File

@@ -1,13 +1,13 @@
# ContiNew Admin 中后台管理框架
[![License](https://img.shields.io/badge/License-Apache--2.0-blue.svg)](https://github.com/Charles7c/continew-admin/blob/dev/LICENSE)
![SNAPSHOT](https://img.shields.io/badge/Release-v1.0.0-%23ff3f59.svg)
![RELEASE](https://img.shields.io/badge/RELEASE-v1.0.1-%23ff3f59.svg)
[![GitHub Repo stars](https://img.shields.io/github/stars/Charles7c/continew-admin?style=social)](https://github.com/Charles7c/continew-admin)
[![GitHub forks](https://img.shields.io/github/forks/Charles7c/continew-admin?style=social)](https://github.com/Charles7c/continew-admin)
[![Gitee Repo stars](https://gitee.com/Charles7c/continew-admin/badge/star.svg?theme=white)](https://gitee.com/Charles7c/continew-admin)
[![Gitee forks](https://gitee.com/Charles7c/continew-admin/badge/fork.svg?theme=white)](https://gitee.com/Charles7c/continew-admin)
📚 [在线文档](https://doc.charles7c.top) | ✨ [提交需求](https://doc.charles7c.top/require) | 🚀 [演示地址](https://cnadmin.charles7c.top)(账号/密码admin/admin123
📚 [在线文档](https://doc.charles7c.top) | ✨ [提交需求](https://doc.charles7c.top/require.html) | 🚀 [演示地址](https://cnadmin.charles7c.top)(账号/密码admin/admin123
## 简介
@@ -36,39 +36,7 @@ ContiNew Admin 中后台管理框架/脚手架Continue New Admin持续以
## 系统功能
> v1.x 开发和 v2.x 开发同步进行中。小步快跑,快速迭代。
>
> 详细进度请查看 [GitHub Project](https://github.com/Charles7c/continew-admin/projects)
**v2.0.0** :fire: 升级并适配 Spring Boot 3.x。
- [ ] 依赖升级:升级并适配 Spring Boot 3.x
- [ ] 依赖升级:其他依赖升级
- [ ] 计划对接 [FlowLong](https://gitee.com/aizuda/flowlong) 纯国产工作流引擎
- [ ] 其他需求汇集中...
**v1.2.0** 第三方服务支持。
- [ ] 文件管理:提供 OSS 及本地文件管理
- [ ] 支持第三方登录
- [ ] 支持 SMS
- [x] 文档:整理使用文档,创建文档站点
- [ ] 依赖升级:升级至最新 Spring Boot 2.x 版本
- [ ] 依赖升级:其他依赖升级
- [ ] 其他需求汇集中...
**v1.1.0** 丰富中后台管理框架/脚手架的基础功能。
- [ ] 代码生成:高灵活度生成前后端代码,减少大量重复的工作任务
- [ ] 公告管理:提供系统公告的发布和维护功能
- [ ] 通知管理:提供系统通知管理,设为已读、未读
- [ ] 仪表盘优化:各区块数据动态渲染
- [ ] 网站配置:支持配置系统网站标题、网站 Logo、favicon、版权信息等
- [ ] 依赖升级:升级至最新 Spring Boot 2.x 版本
- [ ] 依赖升级:其他依赖升级
- [ ] 其他需求汇集中...
**v1.0.0** 初步完成中后台管理框架/脚手架的基础功能。
> 更多功能和优化正在赶来💦,最新项目计划和进展请关注 [GitHub Project](https://github.com/Charles7c/continew-admin/projects)
- 用户管理:提供用户的相关配置,新增用户后,默认密码为 123456
- 角色管理:对权限与菜单进行分配,可根据部门设置角色的数据权限
@@ -380,7 +348,7 @@ yarn dev
💬 非常欢迎各位小伙伴儿在 Issues、Discussions 中进行交流探讨~
💬 也欢迎各位小伙伴儿扫码加作者好友请备注cnadmin作者拉你进群现有作者(群主)及 7 位群友,随意聊聊技术、提提需求,吐吐槽~
💬 也欢迎各位小伙伴儿扫码加作者好友请备注cnadmin作者拉你进群随意聊聊技术、提提需求吐吐槽~
<div align="left">
<img src="https://s1.ax1x.com/2023/03/09/ppnhe0A.jpg" alt="二维码" width="200" />

View File

@@ -16,6 +16,8 @@
package top.charles7c.cnadmin.common.base;
import static top.charles7c.cnadmin.common.annotation.CrudRequestMapping.Api;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
@@ -75,8 +77,8 @@ public abstract class BaseController<S extends BaseService<V, D, Q, C>, V, D, Q,
@Operation(summary = "分页查询列表")
@ResponseBody
@GetMapping
protected R<PageDataVO<V>> page(@Validated Q query, @Validated PageQuery pageQuery) {
this.checkPermission("list");
public R<PageDataVO<V>> page(@Validated Q query, @Validated PageQuery pageQuery) {
this.checkPermission(Api.LIST);
PageDataVO<V> pageDataVO = baseService.page(query, pageQuery);
return R.ok(pageDataVO);
}
@@ -93,8 +95,8 @@ public abstract class BaseController<S extends BaseService<V, D, Q, C>, V, D, Q,
@Operation(summary = "查询树列表")
@ResponseBody
@GetMapping("/tree")
protected R<List<Tree<Long>>> tree(@Validated Q query, @Validated SortQuery sortQuery) {
this.checkPermission("list");
public R<List<Tree<Long>>> tree(@Validated Q query, @Validated SortQuery sortQuery) {
this.checkPermission(Api.LIST);
List<Tree<Long>> list = baseService.tree(query, sortQuery, false);
return R.ok(list);
}
@@ -111,8 +113,8 @@ public abstract class BaseController<S extends BaseService<V, D, Q, C>, V, D, Q,
@Operation(summary = "查询列表")
@ResponseBody
@GetMapping("/list")
protected R<List<V>> list(@Validated Q query, @Validated SortQuery sortQuery) {
this.checkPermission("list");
public R<List<V>> list(@Validated Q query, @Validated SortQuery sortQuery) {
this.checkPermission(Api.LIST);
List<V> list = baseService.list(query, sortQuery);
return R.ok(list);
}
@@ -128,8 +130,8 @@ public abstract class BaseController<S extends BaseService<V, D, Q, C>, V, D, Q,
@Parameter(name = "id", description = "ID", in = ParameterIn.PATH)
@ResponseBody
@GetMapping("/{id}")
protected R<D> get(@PathVariable Long id) {
this.checkPermission("list");
public R<D> get(@PathVariable Long id) {
this.checkPermission(Api.LIST);
D detail = baseService.get(id);
return R.ok(detail);
}
@@ -144,8 +146,8 @@ public abstract class BaseController<S extends BaseService<V, D, Q, C>, V, D, Q,
@Operation(summary = "新增数据")
@ResponseBody
@PostMapping
protected R<Long> add(@Validated(BaseRequest.Add.class) @RequestBody C request) {
this.checkPermission("add");
public R<Long> add(@Validated(BaseRequest.Add.class) @RequestBody C request) {
this.checkPermission(Api.ADD);
Long id = baseService.add(request);
return R.ok("新增成功", id);
}
@@ -162,8 +164,8 @@ public abstract class BaseController<S extends BaseService<V, D, Q, C>, V, D, Q,
@Operation(summary = "修改数据")
@ResponseBody
@PutMapping("/{id}")
protected R update(@Validated(BaseRequest.Update.class) @RequestBody C request, @PathVariable Long id) {
this.checkPermission("update");
public R update(@Validated(BaseRequest.Update.class) @RequestBody C request, @PathVariable Long id) {
this.checkPermission(Api.UPDATE);
baseService.update(request, id);
return R.ok("修改成功");
}
@@ -179,8 +181,8 @@ public abstract class BaseController<S extends BaseService<V, D, Q, C>, V, D, Q,
@Parameter(name = "ids", description = "ID 列表", in = ParameterIn.PATH)
@ResponseBody
@DeleteMapping("/{ids}")
protected R delete(@PathVariable List<Long> ids) {
this.checkPermission("delete");
public R delete(@PathVariable List<Long> ids) {
this.checkPermission(Api.DELETE);
baseService.delete(ids);
return R.ok("删除成功");
}
@@ -197,21 +199,21 @@ public abstract class BaseController<S extends BaseService<V, D, Q, C>, V, D, Q,
*/
@Operation(summary = "导出数据")
@GetMapping("/export")
protected void export(@Validated Q query, @Validated SortQuery sortQuery, HttpServletResponse response) {
this.checkPermission("export");
public void export(@Validated Q query, @Validated SortQuery sortQuery, HttpServletResponse response) {
this.checkPermission(Api.EXPORT);
baseService.export(query, sortQuery, response);
}
/**
* 权限
* 根据 API 类型进行权限
*
* @param subPermission
* 部分权限码
* @param api
* API 类型
*/
private void checkPermission(String subPermission) {
private void checkPermission(Api api) {
CrudRequestMapping crudRequestMapping = this.getClass().getDeclaredAnnotation(CrudRequestMapping.class);
String path = crudRequestMapping.value();
String permissionPrefix = String.join(StringConsts.COLON, StrUtil.splitTrim(path, StringConsts.SLASH));
StpUtil.checkPermission(String.format("%s:%s", permissionPrefix, subPermission));
StpUtil.checkPermission(String.format("%s:%s", permissionPrefix, api.name().toLowerCase()));
}
}

View File

@@ -32,7 +32,10 @@ import top.charles7c.cnadmin.common.model.dto.LoginUser;
import top.charles7c.cnadmin.common.model.dto.RoleDTO;
import top.charles7c.cnadmin.common.util.helper.LoginHelper;
import net.sf.jsqlparser.expression.*;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.Function;
import net.sf.jsqlparser.expression.LongValue;
import net.sf.jsqlparser.expression.Parenthesis;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
@@ -45,7 +48,7 @@ import net.sf.jsqlparser.statement.select.SelectExpressionItem;
import net.sf.jsqlparser.statement.select.SubSelect;
/**
* 数据权限处理器实现
* 数据权限处理器实现
* <p>
* 来源:<a href="https://gitee.com/baomidou/mybatis-plus/issues/I37I90">DataPermissionInterceptor 如何使用?</a>
* </p>

View File

@@ -16,9 +16,8 @@
package top.charles7c.cnadmin.common.config.properties;
import lombok.Data;
import org.springframework.stereotype.Component;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import cn.hutool.extra.spring.SpringUtil;
@@ -29,8 +28,7 @@ import cn.hutool.extra.spring.SpringUtil;
* @author Charles7c
* @since 2022/12/21 20:21
*/
@Data
@Component
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class RsaProperties {
/** 私钥 */

View File

@@ -20,11 +20,11 @@ import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadPoolExecutor;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
@@ -40,10 +40,9 @@ import top.charles7c.cnadmin.common.util.ExceptionUtils;
*/
@Slf4j
@Configuration
@RequiredArgsConstructor
@EnableConfigurationProperties(ThreadPoolProperties.class)
public class ThreadPoolConfiguration {
private final ThreadPoolProperties threadPoolProperties;
/** 核心(最小)线程数 = CPU 核心数 + 1 */
private final int corePoolSize = Runtime.getRuntime().availableProcessors() + 1;
@@ -52,7 +51,7 @@ public class ThreadPoolConfiguration {
*/
@Bean
@ConditionalOnProperty(prefix = "thread-pool", name = "enabled", havingValue = "true")
public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
public ThreadPoolTaskExecutor threadPoolTaskExecutor(ThreadPoolProperties threadPoolProperties) {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 核心(最小)线程数
executor.setCorePoolSize(corePoolSize);

View File

@@ -19,7 +19,6 @@ package top.charles7c.cnadmin.common.config.threadpool;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* 线程池配置属性
@@ -29,7 +28,6 @@ import org.springframework.stereotype.Component;
* @since 2022/12/23 23:06
*/
@Data
@Component
@ConfigurationProperties(prefix = "thread-pool")
public class ThreadPoolProperties {

View File

@@ -19,6 +19,8 @@ package top.charles7c.cnadmin.common.constant;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import cn.hutool.core.lang.RegexPool;
/**
* 正则相关常量
*
@@ -26,10 +28,25 @@ import lombok.NoArgsConstructor;
* @since 2023/1/10 20:06
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class RegExpConsts {
public class RegexConsts implements RegexPool {
/**
* 密码正则必须包含字母和数字的组合可以使用特殊字符长度在6-32之间
* 用户名正则长度为 4 16 可以包含字母数字下划线以字母开头
*/
public static final String USERNAME = "^[a-zA-Z][a-zA-Z0-9_]{3,15}$";
/**
* 密码正则长度为 6 32 可以包含字母数字下划线特殊字符同时包含字母和数字
*/
public static final String PASSWORD = "^(?=.*\\d)(?=.*[a-z]).{6,32}$";
/**
* 通用编码正则长度为 2 16 可以包含字母数字下划线以字母开头
*/
public static final String GENERAL_CODE = "^[a-zA-Z][a-zA-Z0-9_]{1,15}$";
/**
* 通用名称正则长度为 1 20 可以包含中文字母数字下划线短横线
*/
public static final String GENERAL_NAME = "^[\\u4e00-\\u9fa5a-zA-Z0-9_-]{1,20}$";
}

View File

@@ -45,6 +45,11 @@ public class StringConsts implements StrPool {
*/
public static final String ASTERISK = "*";
/**
* 问号
*/
public static final String QUESTION_MARK = "?";
/**
* 中文逗号
*/

View File

@@ -47,30 +47,24 @@ import cn.hutool.core.util.StrUtil;
public class PageQuery extends SortQuery {
private static final long serialVersionUID = 1L;
/** 默认页码1 */
private static final int DEFAULT_PAGE = 1;
/** 默认每页条数10 */
private static final int DEFAULT_SIZE = 10;
/**
* 页码
*/
@Schema(description = "页码")
@Min(value = 1, message = "页码最小值为 {value}")
private Integer page;
private Integer page = DEFAULT_PAGE;
/**
* 每页条数
*/
@Schema(description = "每页条数")
@Range(min = 1, max = 1000, message = "每页条数(取值范围 {min}-{max}")
private Integer size;
/** 默认页码1 */
private static final int DEFAULT_PAGE = 1;
/** 默认每页条数10 */
private static final int DEFAULT_SIZE = 10;
public PageQuery(Integer page, Integer size) {
this.setPage(page);
this.setSize(size);
}
private Integer size = DEFAULT_SIZE;
/**
* 基于分页查询条件转换为 MyBatis Plus 分页条件
@@ -92,12 +86,4 @@ public class PageQuery extends SortQuery {
}
return mybatisPage;
}
public void setPage(Integer page) {
this.page = page == null ? DEFAULT_PAGE : page;
}
public void setSize(Integer size) {
this.size = size == null ? DEFAULT_SIZE : size;
}
}

View File

@@ -55,7 +55,7 @@ public class PageDataVO<V> implements Serializable {
* 总记录数
*/
@Schema(description = "总记录数")
private Long total;
private int total;
/**
* 基于 MyBatis Plus 分页数据构建分页信息,并将源数据转换为指定类型数据
@@ -76,7 +76,7 @@ public class PageDataVO<V> implements Serializable {
}
PageDataVO<V> pageDataVO = new PageDataVO<>();
pageDataVO.setList(BeanUtil.copyToList(page.getRecords(), targetClass));
pageDataVO.setTotal(page.getTotal());
pageDataVO.setTotal((int) page.getTotal());
return pageDataVO;
}
@@ -95,7 +95,7 @@ public class PageDataVO<V> implements Serializable {
}
PageDataVO<V> pageDataVO = new PageDataVO<>();
pageDataVO.setList(page.getRecords());
pageDataVO.setTotal(page.getTotal());
pageDataVO.setTotal((int) page.getTotal());
return pageDataVO;
}
@@ -118,7 +118,7 @@ public class PageDataVO<V> implements Serializable {
return pageDataVO;
}
pageDataVO.setTotal((long)list.size());
pageDataVO.setTotal(list.size());
// 对列表数据进行分页
int fromIndex = (page - 1) * size;
int toIndex = page * size + size;

View File

@@ -25,6 +25,8 @@ import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import top.charles7c.cnadmin.common.constant.StringConsts;
/**
* 异常工具类
*
@@ -99,7 +101,7 @@ public class ExceptionUtils {
* @return /
*/
public static String exToBlank(ExSupplier<String> exSupplier) {
return exToDefault(exSupplier, "");
return exToDefault(exSupplier, StringConsts.EMPTY);
}
/**

View File

@@ -26,12 +26,13 @@ import lombok.extern.slf4j.Slf4j;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import top.charles7c.cnadmin.common.annotation.Query;
import top.charles7c.cnadmin.common.exception.BadRequestException;
import top.charles7c.cnadmin.common.util.ReflectUtils;
import top.charles7c.cnadmin.common.util.validate.ValidationUtils;
/**
* 查询助手
@@ -100,6 +101,10 @@ public class QueryHelper {
// 解析查询条件
parse(queryAnnotation, field.getName(), fieldValue, queryWrapper);
} catch (BadRequestException e) {
log.error("Build query occurred an validation error: {}. Query: {}, Field: {}.", e.getMessage(), query,
field, e);
throw e;
} catch (Exception e) {
log.error("Build query occurred an error: {}. Query: {}, Field: {}.", e.getMessage(), query, field, e);
} finally {
@@ -140,9 +145,9 @@ public class QueryHelper {
// 如果没有单独指定属性名,就和使用该注解的属性的名称一致
// 注意:数据库规范中列采用下划线连接法命名,程序规范中变量采用驼峰法命名
String property = queryAnnotation.property();
fieldName = StrUtil.blankToDefault(property, fieldName);
String columnName = StrUtil.toUnderlineCase(fieldName);
switch (queryAnnotation.type()) {
String columnName = StrUtil.toUnderlineCase(StrUtil.blankToDefault(property, fieldName));
Query.Type queryType = queryAnnotation.type();
switch (queryType) {
case EQUAL:
queryWrapper.eq(columnName, fieldValue);
break;
@@ -163,6 +168,7 @@ public class QueryHelper {
break;
case BETWEEN:
List<Object> between = new ArrayList<>((List<Object>)fieldValue);
ValidationUtils.throwIf(between.size() != 2, "[{}] 必须是一个范围", fieldName);
queryWrapper.between(columnName, between.get(0), between.get(1));
break;
case LEFT_LIKE:
@@ -175,14 +181,12 @@ public class QueryHelper {
queryWrapper.likeRight(columnName, fieldValue);
break;
case IN:
if (CollUtil.isNotEmpty((List<Object>)fieldValue)) {
queryWrapper.in(columnName, (List<Object>)fieldValue);
}
ValidationUtils.throwIfEmpty(fieldValue, "[{}] 不能为空", fieldName);
queryWrapper.in(columnName, (List<Object>)fieldValue);
break;
case NOT_IN:
if (CollUtil.isNotEmpty((List<Object>)fieldValue)) {
queryWrapper.notIn(columnName, (List<Object>)fieldValue);
}
ValidationUtils.throwIfEmpty(fieldValue, "[{}] 不能为空", fieldName);
queryWrapper.notIn(columnName, (List<Object>)fieldValue);
break;
case IS_NULL:
queryWrapper.isNull(columnName);
@@ -191,7 +195,7 @@ public class QueryHelper {
queryWrapper.isNotNull(columnName);
break;
default:
break;
throw new IllegalArgumentException(String.format("暂不支持 [%s] 查询类型", queryType));
}
}
}

View File

@@ -24,6 +24,7 @@ import lombok.extern.slf4j.Slf4j;
import cn.hutool.core.util.StrUtil;
import top.charles7c.cnadmin.common.constant.StringConsts;
import top.charles7c.cnadmin.common.exception.ServiceException;
/**
@@ -52,8 +53,8 @@ public class CheckUtils extends Validator {
* 字段值
*/
public static void throwIfNotExists(Object obj, String entityName, String fieldName, Object fieldValue) {
String message =
String.format("%s 为 [%s] 的 %s 记录已不存在", fieldName, fieldValue, StrUtil.replace(entityName, "DO", ""));
String message = String.format("%s 为 [%s] 的 %s 记录已不存在", fieldName, fieldValue,
StrUtil.replace(entityName, "DO", StringConsts.EMPTY));
throwIfNull(obj, message, EXCEPTION_TYPE);
}

View File

@@ -47,6 +47,7 @@ import cn.hutool.http.HttpStatus;
import cn.hutool.json.JSONUtil;
import top.charles7c.cnadmin.auth.model.request.LoginRequest;
import top.charles7c.cnadmin.common.constant.StringConsts;
import top.charles7c.cnadmin.common.constant.SysConsts;
import top.charles7c.cnadmin.common.model.dto.LogContext;
import top.charles7c.cnadmin.common.util.ExceptionUtils;
@@ -170,7 +171,8 @@ public class LogInterceptor implements HandlerInterceptor {
// (本框架代码规范)例如:@Tag(name = "部门管理 API") -> 部门管理
if (classTag != null) {
String name = classTag.name();
logDO.setModule(StrUtil.isNotBlank(name) ? name.replace("API", "").trim() : "请在该接口类上指定所属模块");
logDO
.setModule(StrUtil.isNotBlank(name) ? name.replace("API", StringConsts.EMPTY).trim() : "请在该接口类上指定所属模块");
}
// 例如:@Log(module = "部门管理") -> 部门管理
if (classLog != null && StrUtil.isNotBlank(classLog.module())) {
@@ -213,7 +215,7 @@ public class LogInterceptor implements HandlerInterceptor {
*/
private void logRequest(LogDO logDO, HttpServletRequest request) {
logDO.setRequestUrl(StrUtil.isBlank(request.getQueryString()) ? request.getRequestURL().toString()
: request.getRequestURL().append("?").append(request.getQueryString()).toString());
: request.getRequestURL().append(StringConsts.QUESTION_MARK).append(request.getQueryString()).toString());
logDO.setRequestMethod(request.getMethod());
logDO.setRequestHeaders(this.desensitize(ServletUtil.getHeaderMap(request)));
String requestBody = this.getRequestBody(request);

View File

@@ -28,6 +28,7 @@ import org.springdoc.api.annotations.ParameterObject;
import org.springframework.format.annotation.DateTimeFormat;
import top.charles7c.cnadmin.common.annotation.Query;
import top.charles7c.cnadmin.common.constant.StringConsts;
/**
* 登录日志查询条件
@@ -54,6 +55,6 @@ public class LoginLogQuery implements Serializable {
*/
@Schema(description = "登录时间")
@Query(type = Query.Type.BETWEEN)
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = StringConsts.NORM_DATE_TIME_PATTERN)
private List<Date> createTime;
}

View File

@@ -28,6 +28,7 @@ import org.springdoc.api.annotations.ParameterObject;
import org.springframework.format.annotation.DateTimeFormat;
import top.charles7c.cnadmin.common.annotation.Query;
import top.charles7c.cnadmin.common.constant.StringConsts;
/**
* 操作日志查询条件
@@ -61,7 +62,7 @@ public class OperationLogQuery implements Serializable {
*/
@Schema(description = "操作时间")
@Query(type = Query.Type.BETWEEN)
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = StringConsts.NORM_DATE_TIME_PATTERN)
private List<Date> createTime;
/**

View File

@@ -28,6 +28,7 @@ import org.springdoc.api.annotations.ParameterObject;
import org.springframework.format.annotation.DateTimeFormat;
import top.charles7c.cnadmin.common.annotation.Query;
import top.charles7c.cnadmin.common.constant.StringConsts;
/**
* 系统日志查询条件
@@ -47,6 +48,6 @@ public class SystemLogQuery implements Serializable {
*/
@Schema(description = "创建时间")
@Query(type = Query.Type.BETWEEN)
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = StringConsts.NORM_DATE_TIME_PATTERN)
private List<Date> createTime;
}

View File

@@ -49,7 +49,7 @@ import top.charles7c.cnadmin.monitor.model.vo.*;
import top.charles7c.cnadmin.monitor.service.LogService;
/**
* 系统日志业务实现
* 系统日志业务实现
*
* @author Charles7c
* @since 2022/12/23 20:12

View File

@@ -147,7 +147,7 @@ public class SaTokenRedisDaoImpl implements SaTokenDao {
@Override
public List<String> searchData(String prefix, String keyword, int start, int size, boolean sortType) {
Collection<String> keys = RedisUtils.keys(prefix + "*" + keyword + "*");
Collection<String> keys = RedisUtils.keys(String.format("%s*%s*", prefix, keyword));
List<String> list = new ArrayList<>(keys);
return SaFoxUtil.searchList(list, start, size, sortType);
}

View File

@@ -27,6 +27,8 @@ import io.swagger.v3.oas.annotations.media.Schema;
import org.springdoc.api.annotations.ParameterObject;
import org.springframework.format.annotation.DateTimeFormat;
import top.charles7c.cnadmin.common.constant.StringConsts;
/**
* 在线用户查询条件
*
@@ -50,6 +52,6 @@ public class OnlineUserQuery implements Serializable {
* 登录时间
*/
@Schema(description = "登录时间")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = StringConsts.NORM_DATE_TIME_PATTERN)
private List<Date> loginTime;
}

View File

@@ -53,7 +53,7 @@ import top.charles7c.cnadmin.system.service.RoleService;
import top.charles7c.cnadmin.system.service.UserService;
/**
* 登录业务实现
* 登录业务实现
*
* @author Charles7c
* @since 2022/12/21 21:49

View File

@@ -41,7 +41,7 @@ import top.charles7c.cnadmin.common.model.vo.PageDataVO;
import top.charles7c.cnadmin.common.util.helper.LoginHelper;
/**
* 在线用户业务实现
* 在线用户业务实现
*
* @author Charles7c
* @author Lion LiRuoYi-Vue-Plus

View File

@@ -30,7 +30,7 @@ import top.charles7c.cnadmin.system.service.MenuService;
import top.charles7c.cnadmin.system.service.RoleService;
/**
* 权限业务实现
* 权限业务实现
*
* @author Charles7c
* @since 2023/3/2 20:40

View File

@@ -16,6 +16,16 @@
package top.charles7c.cnadmin.system.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import top.charles7c.cnadmin.common.annotation.DataPermission;
import top.charles7c.cnadmin.common.base.BaseMapper;
import top.charles7c.cnadmin.system.model.entity.UserDO;
@@ -25,4 +35,33 @@ import top.charles7c.cnadmin.system.model.entity.UserDO;
* @author Charles7c
* @since 2022/12/22 21:47
*/
public interface UserMapper extends BaseMapper<UserDO> {}
public interface UserMapper extends BaseMapper<UserDO> {
@Override
@DataPermission
List<UserDO> selectList(@Param(Constants.WRAPPER) Wrapper<UserDO> queryWrapper);
@Override
@DataPermission
<P extends IPage<UserDO>> P selectPage(P page, @Param(Constants.WRAPPER) Wrapper<UserDO> queryWrapper);
/**
* 根据用户名查询
*
* @param username
* 用户名
* @return 用户信息
*/
@Select("SELECT * FROM `sys_user` WHERE `username` = #{username}")
UserDO selectByUsername(@Param("username") String username);
/**
* 根据 ID 查询昵称
*
* @param id
* ID
* @return 昵称
*/
@Select("SELECT `nickname` FROM `sys_user` WHERE `id` = #{id}")
String selectNicknameById(@Param("id") Long id);
}

View File

@@ -28,6 +28,7 @@ import org.springdoc.api.annotations.ParameterObject;
import org.springframework.format.annotation.DateTimeFormat;
import top.charles7c.cnadmin.common.annotation.Query;
import top.charles7c.cnadmin.common.constant.StringConsts;
/**
* 用户查询条件
@@ -61,7 +62,7 @@ public class UserQuery implements Serializable {
*/
@Schema(description = "创建时间")
@Query(type = Query.Type.BETWEEN)
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = StringConsts.NORM_DATE_TIME_PATTERN)
private List<Date> createTime;
/**

View File

@@ -18,6 +18,7 @@ package top.charles7c.cnadmin.system.model.request;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import lombok.Data;
@@ -26,6 +27,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
import org.hibernate.validator.constraints.Length;
import top.charles7c.cnadmin.common.base.BaseRequest;
import top.charles7c.cnadmin.common.constant.RegexConsts;
import top.charles7c.cnadmin.common.enums.DisEnableStatusEnum;
/**
@@ -52,6 +54,7 @@ public class DeptRequest extends BaseRequest {
*/
@Schema(description = "部门名称")
@NotBlank(message = "部门名称不能为空")
@Pattern(regexp = RegexConsts.GENERAL_NAME, message = "部门名称长度为 1 到 20 位,可以包含中文、字母、数字、下划线,短横线")
private String name;
/**

View File

@@ -18,12 +18,14 @@ package top.charles7c.cnadmin.system.model.request;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import lombok.Data;
import io.swagger.v3.oas.annotations.media.Schema;
import top.charles7c.cnadmin.common.base.BaseRequest;
import top.charles7c.cnadmin.common.constant.RegexConsts;
import top.charles7c.cnadmin.common.enums.DisEnableStatusEnum;
import top.charles7c.cnadmin.common.enums.MenuTypeEnum;
@@ -57,6 +59,7 @@ public class MenuRequest extends BaseRequest {
*/
@Schema(description = "菜单标题")
@NotBlank(message = "菜单标题不能为空")
@Pattern(regexp = RegexConsts.GENERAL_NAME, message = "菜单标题长度为 1 到 20 位,可以包含中文、字母、数字、下划线,短横线")
private String title;
/**

View File

@@ -21,6 +21,7 @@ import java.util.List;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import lombok.Data;
@@ -29,6 +30,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
import org.hibernate.validator.constraints.Length;
import top.charles7c.cnadmin.common.base.BaseRequest;
import top.charles7c.cnadmin.common.constant.RegexConsts;
import top.charles7c.cnadmin.common.enums.DataScopeEnum;
import top.charles7c.cnadmin.common.enums.DisEnableStatusEnum;
@@ -49,12 +51,15 @@ public class RoleRequest extends BaseRequest {
*/
@Schema(description = "角色名称")
@NotBlank(message = "角色名称不能为空")
@Pattern(regexp = RegexConsts.GENERAL_NAME, message = "角色名称长度为 1 到 20 位,可以包含中文、字母、数字、下划线,短横线")
private String name;
/**
* 角色编码
*/
@Schema(description = "角色编码")
@NotBlank(message = "角色编码不能为空")
@Pattern(regexp = RegexConsts.GENERAL_CODE, message = "角色编码长度为 2 到 16 位,可以包含字母、数字,下划线,以字母开头")
private String code;
/**

View File

@@ -20,13 +20,13 @@ import java.io.Serializable;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import lombok.Data;
import io.swagger.v3.oas.annotations.media.Schema;
import org.hibernate.validator.constraints.Length;
import top.charles7c.cnadmin.common.constant.RegexConsts;
import top.charles7c.cnadmin.common.enums.GenderEnum;
/**
@@ -46,7 +46,7 @@ public class UpdateBasicInfoRequest implements Serializable {
*/
@Schema(description = "昵称")
@NotBlank(message = "昵称不能为空")
@Length(max = 32, message = "昵称长度不能超过 {max} 个字符")
@Pattern(regexp = RegexConsts.GENERAL_NAME, message = "昵称长度为 1 到 20 位,可以包含中文、字母、数字、下划线,短横线")
private String nickname;
/**

View File

@@ -27,7 +27,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
import org.hibernate.validator.constraints.Length;
import cn.hutool.core.lang.RegexPool;
import top.charles7c.cnadmin.common.constant.RegexConsts;
/**
* 修改邮箱信息
@@ -46,7 +46,7 @@ public class UpdateEmailRequest implements Serializable {
*/
@Schema(description = "新邮箱")
@NotBlank(message = "新邮箱不能为空")
@Pattern(regexp = RegexPool.EMAIL, message = "邮箱格式错误")
@Pattern(regexp = RegexConsts.EMAIL, message = "邮箱格式错误")
private String newEmail;
/**

View File

@@ -18,7 +18,10 @@ package top.charles7c.cnadmin.system.model.request;
import java.util.List;
import javax.validation.constraints.*;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import lombok.Data;
@@ -26,9 +29,8 @@ import io.swagger.v3.oas.annotations.media.Schema;
import org.hibernate.validator.constraints.Length;
import cn.hutool.core.lang.RegexPool;
import top.charles7c.cnadmin.common.base.BaseRequest;
import top.charles7c.cnadmin.common.constant.RegexConsts;
import top.charles7c.cnadmin.common.enums.DisEnableStatusEnum;
import top.charles7c.cnadmin.common.enums.GenderEnum;
@@ -49,27 +51,29 @@ public class UserRequest extends BaseRequest {
*/
@Schema(description = "用户名")
@NotBlank(message = "用户名不能为空")
@Pattern(regexp = RegexConsts.USERNAME, message = "用户名长度为 4 到 16 位,可以包含字母、数字,下划线,以字母开头")
private String username;
/**
* 昵称
*/
@Schema(description = "昵称")
@Length(max = 32, message = "昵称长度不能超过 {max} 个字符")
@NotBlank(message = "昵称不能为空")
@Pattern(regexp = RegexConsts.GENERAL_NAME, message = "昵称长度为 1 到 20 位,可以包含中文、字母、数字、下划线,短横线")
private String nickname;
/**
* 邮箱
*/
@Schema(description = "邮箱")
@Pattern(regexp = RegexPool.EMAIL, message = "邮箱格式错误")
@Pattern(regexp = RegexConsts.EMAIL, message = "邮箱格式错误")
private String email;
/**
* 手机号码
*/
@Schema(description = "手机号码")
@Pattern(regexp = RegexPool.MOBILE, message = "手机号码格式错误")
@Pattern(regexp = RegexConsts.MOBILE, message = "手机号码格式错误")
private String phone;
/**
@@ -83,12 +87,14 @@ public class UserRequest extends BaseRequest {
* 所属部门
*/
@Schema(description = "所属部门")
@NotNull(message = "所属部门不能为空")
private Long deptId;
/**
* 所属角色
*/
@Schema(description = "所属角色")
@NotEmpty(message = "所属角色不能为空")
private List<Long> roleIds;
/**

View File

@@ -38,13 +38,12 @@ public interface RoleDeptService {
boolean save(List<Long> deptIds, Long roleId);
/**
* 根据角色 ID 查询
* 根据角色 ID 删除
*
* @param roleId
* 角色 ID
* @return 部门 ID 列表
* @param roleIds
* 角色 ID 列表
*/
List<Long> listDeptIdByRoleId(Long roleId);
void deleteByRoleIds(List<Long> roleIds);
/**
* 根据部门 ID 删除
@@ -55,10 +54,11 @@ public interface RoleDeptService {
void deleteByDeptIds(List<Long> deptIds);
/**
* 根据角色 ID 删除
* 根据角色 ID 查询
*
* @param roleIds
* 角色 ID 列表
* @param roleId
* 角色 ID
* @return 部门 ID 列表
*/
void deleteByRoleIds(List<Long> roleIds);
List<Long> listDeptIdByRoleId(Long roleId);
}

View File

@@ -37,6 +37,14 @@ public interface RoleMenuService {
*/
boolean save(List<Long> menuIds, Long roleId);
/**
* 根据角色 ID 删除
*
* @param roleIds
* 角色 ID 列表
*/
void deleteByRoleIds(List<Long> roleIds);
/**
* 根据角色 ID 查询
*
@@ -45,12 +53,4 @@ public interface RoleMenuService {
* @return 菜单 ID 列表
*/
List<Long> listMenuIdByRoleIds(List<Long> roleIds);
/**
* 根据角色 ID 删除
*
* @param roleIds
* 角色 ID 列表
*/
void deleteByRoleIds(List<Long> roleIds);
}

View File

@@ -38,13 +38,12 @@ public interface UserRoleService {
boolean save(List<Long> roleIds, Long userId);
/**
* 根据角色 ID 列表查询
* 根据用户 ID 删除
*
* @param roleIds
* 角色 ID 列表
* @return 总记录数
* @param userIds
* 用户 ID 列表
*/
Long countByRoleIds(List<Long> roleIds);
void deleteByUserIds(List<Long> userIds);
/**
* 根据用户 ID 查询
@@ -56,10 +55,11 @@ public interface UserRoleService {
List<Long> listRoleIdByUserId(Long userId);
/**
* 根据用户 ID 删除
* 根据角色 ID 列表查询
*
* @param userIds
* 用户 ID 列表
* @param roleIds
* 角色 ID 列表
* @return 总记录数
*/
void deleteByUserIds(List<Long> userIds);
Long countByRoleIds(List<Long> roleIds);
}

View File

@@ -23,6 +23,7 @@ import org.springframework.web.multipart.MultipartFile;
import top.charles7c.cnadmin.common.base.BaseService;
import top.charles7c.cnadmin.system.model.entity.UserDO;
import top.charles7c.cnadmin.system.model.query.UserQuery;
import top.charles7c.cnadmin.system.model.request.UpdateBasicInfoRequest;
import top.charles7c.cnadmin.system.model.request.UpdateUserRoleRequest;
import top.charles7c.cnadmin.system.model.request.UserRequest;
import top.charles7c.cnadmin.system.model.vo.UserDetailVO;
@@ -47,6 +48,16 @@ public interface UserService extends BaseService<UserVO, UserDetailVO, UserQuery
*/
String uploadAvatar(MultipartFile avatar, Long id);
/**
* 修改基础信息
*
* @param request
* 修改信息
* @param id
* ID
*/
void updateBasicInfo(UpdateBasicInfoRequest request, Long id);
/**
* 修改密码
*

View File

@@ -48,7 +48,7 @@ import top.charles7c.cnadmin.system.service.RoleDeptService;
import top.charles7c.cnadmin.system.service.UserService;
/**
* 部门业务实现
* 部门业务实现
*
* @author Charles7c
* @since 2023/1/22 17:55

View File

@@ -36,7 +36,7 @@ import top.charles7c.cnadmin.system.model.vo.MenuVO;
import top.charles7c.cnadmin.system.service.MenuService;
/**
* 菜单业务实现
* 菜单业务实现
*
* @author Charles7c
* @since 2023/2/15 20:30

View File

@@ -22,6 +22,7 @@ import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import cn.hutool.core.collection.CollUtil;
@@ -30,7 +31,7 @@ import top.charles7c.cnadmin.system.model.entity.RoleDeptDO;
import top.charles7c.cnadmin.system.service.RoleDeptService;
/**
* 角色和部门业务实现
* 角色和部门业务实现
*
* @author Charles7c
* @since 2023/2/19 10:47
@@ -42,6 +43,7 @@ public class RoleDeptServiceImpl implements RoleDeptService {
private final RoleDeptMapper roleDeptMapper;
@Override
@Transactional(rollbackFor = Exception.class)
public boolean save(List<Long> deptIds, Long roleId) {
// 检查是否有变更
List<Long> oldDeptIdList = roleDeptMapper.lambdaQuery().select(RoleDeptDO::getDeptId)
@@ -58,17 +60,19 @@ public class RoleDeptServiceImpl implements RoleDeptService {
}
@Override
public List<Long> listDeptIdByRoleId(Long roleId) {
return roleDeptMapper.selectDeptIdByRoleId(roleId);
@Transactional(rollbackFor = Exception.class)
public void deleteByRoleIds(List<Long> roleIds) {
roleDeptMapper.lambdaUpdate().in(RoleDeptDO::getRoleId, roleIds).remove();
}
@Override
@Transactional(rollbackFor = Exception.class)
public void deleteByDeptIds(List<Long> deptIds) {
roleDeptMapper.lambdaUpdate().in(RoleDeptDO::getDeptId, deptIds).remove();
}
@Override
public void deleteByRoleIds(List<Long> roleIds) {
roleDeptMapper.lambdaUpdate().in(RoleDeptDO::getRoleId, roleIds).remove();
public List<Long> listDeptIdByRoleId(Long roleId) {
return roleDeptMapper.selectDeptIdByRoleId(roleId);
}
}

View File

@@ -23,6 +23,7 @@ import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import cn.hutool.core.collection.CollUtil;
@@ -31,7 +32,7 @@ import top.charles7c.cnadmin.system.model.entity.RoleMenuDO;
import top.charles7c.cnadmin.system.service.RoleMenuService;
/**
* 角色和菜单业务实现
* 角色和菜单业务实现
*
* @author Charles7c
* @since 2023/2/19 10:43
@@ -43,6 +44,7 @@ public class RoleMenuServiceImpl implements RoleMenuService {
private final RoleMenuMapper roleMenuMapper;
@Override
@Transactional(rollbackFor = Exception.class)
public boolean save(List<Long> menuIds, Long roleId) {
// 检查是否有变更
List<Long> oldMenuIdList = roleMenuMapper.lambdaQuery().select(RoleMenuDO::getMenuId)
@@ -58,6 +60,12 @@ public class RoleMenuServiceImpl implements RoleMenuService {
return roleMenuMapper.insertBatch(roleMenuList);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void deleteByRoleIds(List<Long> roleIds) {
roleMenuMapper.lambdaUpdate().in(RoleMenuDO::getRoleId, roleIds).remove();
}
@Override
public List<Long> listMenuIdByRoleIds(List<Long> roleIds) {
if (CollUtil.isEmpty(roleIds)) {
@@ -65,9 +73,4 @@ public class RoleMenuServiceImpl implements RoleMenuService {
}
return roleMenuMapper.selectMenuIdByRoleIds(roleIds);
}
@Override
public void deleteByRoleIds(List<Long> roleIds) {
roleMenuMapper.lambdaUpdate().in(RoleMenuDO::getRoleId, roleIds).remove();
}
}

View File

@@ -47,7 +47,7 @@ import top.charles7c.cnadmin.system.model.vo.RoleVO;
import top.charles7c.cnadmin.system.service.*;
/**
* 角色业务实现
* 角色业务实现
*
* @author Charles7c
* @since 2023/2/8 23:17

View File

@@ -22,6 +22,7 @@ import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import cn.hutool.core.collection.CollUtil;
@@ -30,7 +31,7 @@ import top.charles7c.cnadmin.system.model.entity.UserRoleDO;
import top.charles7c.cnadmin.system.service.UserRoleService;
/**
* 用户和角色业务实现
* 用户和角色业务实现
*
* @author Charles7c
* @since 2023/2/20 21:30
@@ -42,6 +43,7 @@ public class UserRoleServiceImpl implements UserRoleService {
private final UserRoleMapper userRoleMapper;
@Override
@Transactional(rollbackFor = Exception.class)
public boolean save(List<Long> roleIds, Long userId) {
// 检查是否有变更
List<Long> oldRoleIdList = userRoleMapper.lambdaQuery().select(UserRoleDO::getRoleId)
@@ -58,8 +60,9 @@ public class UserRoleServiceImpl implements UserRoleService {
}
@Override
public Long countByRoleIds(List<Long> roleIds) {
return userRoleMapper.lambdaQuery().in(UserRoleDO::getRoleId, roleIds).count();
@Transactional(rollbackFor = Exception.class)
public void deleteByUserIds(List<Long> userIds) {
userRoleMapper.lambdaUpdate().in(UserRoleDO::getUserId, userIds).remove();
}
@Override
@@ -68,7 +71,7 @@ public class UserRoleServiceImpl implements UserRoleService {
}
@Override
public void deleteByUserIds(List<Long> userIds) {
userRoleMapper.lambdaUpdate().in(UserRoleDO::getUserId, userIds).remove();
public Long countByRoleIds(List<Long> roleIds) {
return userRoleMapper.lambdaQuery().in(UserRoleDO::getRoleId, roleIds).count();
}
}

View File

@@ -52,14 +52,18 @@ import top.charles7c.cnadmin.common.util.validate.CheckUtils;
import top.charles7c.cnadmin.system.mapper.UserMapper;
import top.charles7c.cnadmin.system.model.entity.UserDO;
import top.charles7c.cnadmin.system.model.query.UserQuery;
import top.charles7c.cnadmin.system.model.request.UpdateBasicInfoRequest;
import top.charles7c.cnadmin.system.model.request.UpdateUserRoleRequest;
import top.charles7c.cnadmin.system.model.request.UserRequest;
import top.charles7c.cnadmin.system.model.vo.UserDetailVO;
import top.charles7c.cnadmin.system.model.vo.UserVO;
import top.charles7c.cnadmin.system.service.*;
import top.charles7c.cnadmin.system.service.DeptService;
import top.charles7c.cnadmin.system.service.RoleService;
import top.charles7c.cnadmin.system.service.UserRoleService;
import top.charles7c.cnadmin.system.service.UserService;
/**
* 用户业务实现
* 用户业务实现
*
* @author Charles7c
* @since 2022/12/21 21:49
@@ -181,6 +185,14 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, UserDO, UserVO,
return newAvatar;
}
@Override
@Transactional(rollbackFor = Exception.class)
public void updateBasicInfo(UpdateBasicInfoRequest request, Long id) {
super.getById(id);
baseMapper.lambdaUpdate().set(UserDO::getNickname, request.getNickname())
.set(UserDO::getGender, request.getGender()).eq(UserDO::getId, id).update();
}
@Override
@Transactional(rollbackFor = Exception.class)
public void updatePassword(String oldPassword, String newPassword, Long id) {
@@ -208,6 +220,7 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, UserDO, UserVO,
}
@Override
@Transactional(rollbackFor = Exception.class)
public void resetPassword(Long id) {
UserDO user = super.getById(id);
user.setPassword(SecureUtils.md5Salt(SysConsts.DEFAULT_PASSWORD, id.toString()));
@@ -216,6 +229,7 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, UserDO, UserVO,
}
@Override
@Transactional(rollbackFor = Exception.class)
public void updateRole(UpdateUserRoleRequest request, Long id) {
super.getById(id);
// 保存用户和角色关联
@@ -224,7 +238,7 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, UserDO, UserVO,
@Override
public UserDO getByUsername(String username) {
return baseMapper.lambdaQuery().eq(UserDO::getUsername, username).one();
return baseMapper.selectByUsername(username);
}
@Override
@@ -234,7 +248,7 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, UserDO, UserVO,
@Override
public String getNicknameById(Long id) {
return super.getById(id).getNickname();
return baseMapper.selectNicknameById(id);
}
/**

View File

@@ -6,4 +6,5 @@ module.exports = {
quoteProps: 'consistent',
htmlWhitespaceSensitivity: 'strict',
vueIndentScriptAndStyle: true,
endOfLine: 'auto',
};

View File

@@ -1,84 +1,84 @@
// generated by unplugin-vue-components
// We suggest you to commit this file into source control
// Read more: https://github.com/vuejs/core/pull/3399
import '@vue/runtime-core'
import '@vue/runtime-core';
export {}
export {};
declare module '@vue/runtime-core' {
export interface GlobalComponents {
AAffix: typeof import('@arco-design/web-vue')['Affix']
AAlert: typeof import('@arco-design/web-vue')['Alert']
AAvatar: typeof import('@arco-design/web-vue')['Avatar']
ABadge: typeof import('@arco-design/web-vue')['Badge']
ABreadcrumb: typeof import('@arco-design/web-vue')['Breadcrumb']
ABreadcrumbItem: typeof import('@arco-design/web-vue')['BreadcrumbItem']
AButton: typeof import('@arco-design/web-vue')['Button']
AButtonGroup: typeof import('@arco-design/web-vue')['ButtonGroup']
ACard: typeof import('@arco-design/web-vue')['Card']
ACardMeta: typeof import('@arco-design/web-vue')['CardMeta']
ACarousel: typeof import('@arco-design/web-vue')['Carousel']
ACarouselItem: typeof import('@arco-design/web-vue')['CarouselItem']
ACheckbox: typeof import('@arco-design/web-vue')['Checkbox']
ACol: typeof import('@arco-design/web-vue')['Col']
AConfigProvider: typeof import('@arco-design/web-vue')['ConfigProvider']
ADescriptions: typeof import('@arco-design/web-vue')['Descriptions']
ADescriptionsItem: typeof import('@arco-design/web-vue')['DescriptionsItem']
ADivider: typeof import('@arco-design/web-vue')['Divider']
ADoption: typeof import('@arco-design/web-vue')['Doption']
ADrawer: typeof import('@arco-design/web-vue')['Drawer']
ADropdown: typeof import('@arco-design/web-vue')['Dropdown']
AForm: typeof import('@arco-design/web-vue')['Form']
AFormItem: typeof import('@arco-design/web-vue')['FormItem']
AGrid: typeof import('@arco-design/web-vue')['Grid']
AGridItem: typeof import('@arco-design/web-vue')['GridItem']
AInput: typeof import('@arco-design/web-vue')['Input']
AInputNumber: typeof import('@arco-design/web-vue')['InputNumber']
AInputPassword: typeof import('@arco-design/web-vue')['InputPassword']
AInputSearch: typeof import('@arco-design/web-vue')['InputSearch']
ALayout: typeof import('@arco-design/web-vue')['Layout']
ALayoutContent: typeof import('@arco-design/web-vue')['LayoutContent']
ALayoutFooter: typeof import('@arco-design/web-vue')['LayoutFooter']
ALayoutSider: typeof import('@arco-design/web-vue')['LayoutSider']
ALink: typeof import('@arco-design/web-vue')['Link']
AList: typeof import('@arco-design/web-vue')['List']
AListItem: typeof import('@arco-design/web-vue')['ListItem']
AListItemMeta: typeof import('@arco-design/web-vue')['ListItemMeta']
AMenu: typeof import('@arco-design/web-vue')['Menu']
AMenuItem: typeof import('@arco-design/web-vue')['MenuItem']
AModal: typeof import('@arco-design/web-vue')['Modal']
AOption: typeof import('@arco-design/web-vue')['Option']
APopconfirm: typeof import('@arco-design/web-vue')['Popconfirm']
APopover: typeof import('@arco-design/web-vue')['Popover']
ARadio: typeof import('@arco-design/web-vue')['Radio']
ARadioGroup: typeof import('@arco-design/web-vue')['RadioGroup']
ARangePicker: typeof import('@arco-design/web-vue')['RangePicker']
AResult: typeof import('@arco-design/web-vue')['Result']
ARow: typeof import('@arco-design/web-vue')['Row']
ASelect: typeof import('@arco-design/web-vue')['Select']
ASkeleton: typeof import('@arco-design/web-vue')['Skeleton']
ASkeletonLine: typeof import('@arco-design/web-vue')['SkeletonLine']
ASpace: typeof import('@arco-design/web-vue')['Space']
ASpin: typeof import('@arco-design/web-vue')['Spin']
AStatistic: typeof import('@arco-design/web-vue')['Statistic']
AStep: typeof import('@arco-design/web-vue')['Step']
ASteps: typeof import('@arco-design/web-vue')['Steps']
ASubMenu: typeof import('@arco-design/web-vue')['SubMenu']
ASwitch: typeof import('@arco-design/web-vue')['Switch']
ATable: typeof import('@arco-design/web-vue')['Table']
ATableColumn: typeof import('@arco-design/web-vue')['TableColumn']
ATabPane: typeof import('@arco-design/web-vue')['TabPane']
ATabs: typeof import('@arco-design/web-vue')['Tabs']
ATag: typeof import('@arco-design/web-vue')['Tag']
ATextarea: typeof import('@arco-design/web-vue')['Textarea']
ATooltip: typeof import('@arco-design/web-vue')['Tooltip']
ATree: typeof import('@arco-design/web-vue')['Tree']
ATreeSelect: typeof import('@arco-design/web-vue')['TreeSelect']
ATypographyParagraph: typeof import('@arco-design/web-vue')['TypographyParagraph']
ATypographyText: typeof import('@arco-design/web-vue')['TypographyText']
ATypographyTitle: typeof import('@arco-design/web-vue')['TypographyTitle']
AUpload: typeof import('@arco-design/web-vue')['Upload']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
AAffix: typeof import('@arco-design/web-vue')['Affix'];
AAlert: typeof import('@arco-design/web-vue')['Alert'];
AAvatar: typeof import('@arco-design/web-vue')['Avatar'];
ABadge: typeof import('@arco-design/web-vue')['Badge'];
ABreadcrumb: typeof import('@arco-design/web-vue')['Breadcrumb'];
ABreadcrumbItem: typeof import('@arco-design/web-vue')['BreadcrumbItem'];
AButton: typeof import('@arco-design/web-vue')['Button'];
AButtonGroup: typeof import('@arco-design/web-vue')['ButtonGroup'];
ACard: typeof import('@arco-design/web-vue')['Card'];
ACardMeta: typeof import('@arco-design/web-vue')['CardMeta'];
ACarousel: typeof import('@arco-design/web-vue')['Carousel'];
ACarouselItem: typeof import('@arco-design/web-vue')['CarouselItem'];
ACheckbox: typeof import('@arco-design/web-vue')['Checkbox'];
ACol: typeof import('@arco-design/web-vue')['Col'];
AConfigProvider: typeof import('@arco-design/web-vue')['ConfigProvider'];
ADescriptions: typeof import('@arco-design/web-vue')['Descriptions'];
ADescriptionsItem: typeof import('@arco-design/web-vue')['DescriptionsItem'];
ADivider: typeof import('@arco-design/web-vue')['Divider'];
ADoption: typeof import('@arco-design/web-vue')['Doption'];
ADrawer: typeof import('@arco-design/web-vue')['Drawer'];
ADropdown: typeof import('@arco-design/web-vue')['Dropdown'];
AForm: typeof import('@arco-design/web-vue')['Form'];
AFormItem: typeof import('@arco-design/web-vue')['FormItem'];
AGrid: typeof import('@arco-design/web-vue')['Grid'];
AGridItem: typeof import('@arco-design/web-vue')['GridItem'];
AInput: typeof import('@arco-design/web-vue')['Input'];
AInputNumber: typeof import('@arco-design/web-vue')['InputNumber'];
AInputPassword: typeof import('@arco-design/web-vue')['InputPassword'];
AInputSearch: typeof import('@arco-design/web-vue')['InputSearch'];
ALayout: typeof import('@arco-design/web-vue')['Layout'];
ALayoutContent: typeof import('@arco-design/web-vue')['LayoutContent'];
ALayoutFooter: typeof import('@arco-design/web-vue')['LayoutFooter'];
ALayoutSider: typeof import('@arco-design/web-vue')['LayoutSider'];
ALink: typeof import('@arco-design/web-vue')['Link'];
AList: typeof import('@arco-design/web-vue')['List'];
AListItem: typeof import('@arco-design/web-vue')['ListItem'];
AListItemMeta: typeof import('@arco-design/web-vue')['ListItemMeta'];
AMenu: typeof import('@arco-design/web-vue')['Menu'];
AMenuItem: typeof import('@arco-design/web-vue')['MenuItem'];
AModal: typeof import('@arco-design/web-vue')['Modal'];
AOption: typeof import('@arco-design/web-vue')['Option'];
APopconfirm: typeof import('@arco-design/web-vue')['Popconfirm'];
APopover: typeof import('@arco-design/web-vue')['Popover'];
ARadio: typeof import('@arco-design/web-vue')['Radio'];
ARadioGroup: typeof import('@arco-design/web-vue')['RadioGroup'];
ARangePicker: typeof import('@arco-design/web-vue')['RangePicker'];
AResult: typeof import('@arco-design/web-vue')['Result'];
ARow: typeof import('@arco-design/web-vue')['Row'];
ASelect: typeof import('@arco-design/web-vue')['Select'];
ASkeleton: typeof import('@arco-design/web-vue')['Skeleton'];
ASkeletonLine: typeof import('@arco-design/web-vue')['SkeletonLine'];
ASpace: typeof import('@arco-design/web-vue')['Space'];
ASpin: typeof import('@arco-design/web-vue')['Spin'];
AStatistic: typeof import('@arco-design/web-vue')['Statistic'];
AStep: typeof import('@arco-design/web-vue')['Step'];
ASteps: typeof import('@arco-design/web-vue')['Steps'];
ASubMenu: typeof import('@arco-design/web-vue')['SubMenu'];
ASwitch: typeof import('@arco-design/web-vue')['Switch'];
ATable: typeof import('@arco-design/web-vue')['Table'];
ATableColumn: typeof import('@arco-design/web-vue')['TableColumn'];
ATabPane: typeof import('@arco-design/web-vue')['TabPane'];
ATabs: typeof import('@arco-design/web-vue')['Tabs'];
ATag: typeof import('@arco-design/web-vue')['Tag'];
ATextarea: typeof import('@arco-design/web-vue')['Textarea'];
ATooltip: typeof import('@arco-design/web-vue')['Tooltip'];
ATree: typeof import('@arco-design/web-vue')['Tree'];
ATreeSelect: typeof import('@arco-design/web-vue')['TreeSelect'];
ATypographyParagraph: typeof import('@arco-design/web-vue')['TypographyParagraph'];
ATypographyText: typeof import('@arco-design/web-vue')['TypographyText'];
ATypographyTitle: typeof import('@arco-design/web-vue')['TypographyTitle'];
AUpload: typeof import('@arco-design/web-vue')['Upload'];
RouterLink: typeof import('vue-router')['RouterLink'];
RouterView: typeof import('vue-router')['RouterView'];
}
}

View File

@@ -1,7 +1,7 @@
{
"name": "continew-admin-ui",
"description": "ContiNew Admin 中后台管理框架Continue New Admin持续以最新流行技术栈构建拥抱变化迭代优化。",
"version": "1.0.0",
"version": "1.0.1",
"private": true,
"author": "Charles7c",
"license": "Apache-2.0",
@@ -11,6 +11,7 @@
"report": "cross-env REPORT=true npm run build",
"preview": "npm run build && vite preview --host",
"type:check": "vue-tsc --noEmit --skipLibCheck",
"lint": "eslint . --ext .vue,.js,.ts,.jsx,.tsx --fix",
"lint-staged": "npx lint-staged"
},
"lint-staged": {

View File

@@ -1,12 +1,26 @@
<template>
<a-layout-footer class="footer">
{{ `Copyright © 2022-${new Date().getFullYear()}` }}&nbsp;
<a href="https://blog.charles7c.top/about/me" target="_blank" rel="noopenner noreferrer">Charles7c</a>
<a
href="https://blog.charles7c.top/about/me"
target="_blank"
rel="noopenner noreferrer"
>Charles7c</a
>
<span>&nbsp;&nbsp;</span>
<a href="https://github.com/Charles7c/continew-admin" target="_blank" rel="noopenner noreferrer">{{ $t('title') }}</a>&nbsp;
v1.0.0
<a
href="https://github.com/Charles7c/continew-admin"
target="_blank"
rel="noopenner noreferrer"
>{{ $t('title') }}</a
>&nbsp; v1.0.0
<span>&nbsp;&nbsp;</span>
<a href="https://beian.miit.gov.cn" target="_blank" rel="noopenner noreferrer">津ICP备2022005864号-2</a>
<a
href="https://beian.miit.gov.cn"
target="_blank"
rel="noopenner noreferrer"
>津ICP备2022005864号-2</a
>
</a-layout-footer>
</template>

View File

@@ -160,7 +160,10 @@
:size="32"
:style="{ marginRight: '8px', cursor: 'pointer' }"
>
<img alt="avatar" :src="getAvatar(loginStore.avatar, loginStore.gender)" />
<img
alt="avatar"
:src="getAvatar(loginStore.avatar, loginStore.gender)"
/>
</a-avatar>
<template #content>
<a-doption>

View File

@@ -2,10 +2,14 @@
<div class="header-operation-right">
<a-button-group>
<a-tooltip :content="showQuery ? '隐藏搜索栏' : '显示搜索栏'">
<a-button @click="toggleSearch"><template #icon><icon-search /></template></a-button>
<a-button @click="toggleSearch"
><template #icon><icon-search /></template
></a-button>
</a-tooltip>
<a-tooltip content="刷新">
<a-button @click="handleRefresh"><template #icon><icon-refresh /></template></a-button>
<a-button @click="handleRefresh"
><template #icon><icon-refresh /></template
></a-button>
</a-tooltip>
</a-button-group>
</div>
@@ -42,4 +46,4 @@
};
</script>
<style scoped lang="less"></style>
<style scoped lang="less"></style>

View File

@@ -1,6 +1,6 @@
import axios, { Axios, AxiosResponse, AxiosRequestConfig } from 'axios';
declare module "axios" {
declare module 'axios' {
interface AxiosResponse<T = any> {
success: boolean; // 是否成功
code: number; // 状态码
@@ -9,4 +9,4 @@ declare module "axios" {
data: T; // 返回数据
}
export function create(config?: AxiosRequestConfig): AxiosInstance;
}
}

View File

@@ -15,7 +15,8 @@ const LIST: AppRouteRecordRaw = {
{
path: 'search-table', // The midline path complies with SEO specifications
name: 'SearchTable',
component: () => import('@/views/arco-design/list/search-table/index.vue'),
component: () =>
import('@/views/arco-design/list/search-table/index.vue'),
meta: {
locale: 'menu.list.searchTable',
requiresAuth: true,

View File

@@ -15,7 +15,8 @@ const VISUALIZATION: AppRouteRecordRaw = {
{
path: 'data-analysis',
name: 'DataAnalysis',
component: () => import('@/views/arco-design/visualization/data-analysis/index.vue'),
component: () =>
import('@/views/arco-design/visualization/data-analysis/index.vue'),
meta: {
locale: 'menu.visualization.dataAnalysis',
requiresAuth: true,
@@ -26,7 +27,9 @@ const VISUALIZATION: AppRouteRecordRaw = {
path: 'multi-dimension-data-analysis',
name: 'MultiDimensionDataAnalysis',
component: () =>
import('@/views/arco-design/visualization/multi-dimension-data-analysis/index.vue'),
import(
'@/views/arco-design/visualization/multi-dimension-data-analysis/index.vue'
),
meta: {
locale: 'menu.visualization.multiDimensionDataAnalysis',
requiresAuth: true,
@@ -36,7 +39,8 @@ const VISUALIZATION: AppRouteRecordRaw = {
{
path: 'monitor',
name: 'Monitor',
component: () => import('@/views/arco-design/visualization/monitor/index.vue'),
component: () =>
import('@/views/arco-design/visualization/monitor/index.vue'),
meta: {
locale: 'menu.dashboard.monitor',
requiresAuth: true,

View File

@@ -6,7 +6,7 @@ const useDictStore = defineStore('dict', {
actions: {
// 获取字典
getDict(_name: string) {
if (_name === null && _name === '') {
if (_name == null || _name === '') {
return null;
}
try {

View File

@@ -2,7 +2,10 @@ import Unknown from '../assets/images/avatar/unknown.png';
import Male from '../assets/images/avatar/male.png';
import Female from '../assets/images/avatar/female.png';
export default function getAvatar(avatar: string | undefined, gender: number | undefined) {
export default function getAvatar(
avatar: string | undefined,
gender: number | undefined
) {
if (avatar) {
const baseUrl = import.meta.env.VITE_API_BASE_URL;
return `${baseUrl}/avatar/${avatar}`;

View File

@@ -167,7 +167,9 @@
<div>
<a-checkbox
v-model="item.checked"
@change="handleChange($event, item as TableColumnData, index)"
@change="
handleChange($event, item as TableColumnData, index)
"
>
</a-checkbox>
</div>

View File

@@ -55,12 +55,7 @@
{{ $t('login.form.rememberMe') }}
</a-checkbox>
</div>
<a-button
:loading="loading"
type="primary"
long
html-type="submit"
>
<a-button :loading="loading" type="primary" long html-type="submit">
{{ $t('login.form.login') }}
</a-button>
</a-space>

View File

@@ -69,4 +69,4 @@
}
}
}
</style>
</style>

View File

@@ -1,6 +1,7 @@
export default {
'login.form.title': 'Login to ContiNew Admin',
'login.form.subTitle': 'Continue to build the latest popular technology stack in the background management framework',
'login.form.subTitle':
'Continue to build the latest popular technology stack in the background management framework',
'login.form.placeholder.username': 'Please enter username',
'login.form.placeholder.password': 'Please enter password',
@@ -27,5 +28,6 @@ export default {
'login.banner.subSlogan2':
'Rich basic functions, low threshold to use, enterprise rapid development scaffolding',
'login.banner.slogan3': 'The code is standard and open source and free',
'login.banner.subSlogan3': 'The backend code is fully compliant with the Alibaba coding specification, and the frontend code is strictly ESLint checked',
'login.banner.subSlogan3':
'The backend code is fully compliant with the Alibaba coding specification, and the frontend code is strictly ESLint checked',
};

View File

@@ -21,9 +21,11 @@ export default {
'login.form.logout.success': '退出成功',
'login.banner.slogan1': '中后台管理框架',
'login.banner.subSlogan1': 'Continue New Admin持续以最新流行技术栈构建拥抱变化迭代优化',
'login.banner.subSlogan1':
'Continue New Admin持续以最新流行技术栈构建拥抱变化迭代优化',
'login.banner.slogan2': '内置了常见问题的解决方案',
'login.banner.subSlogan2': '基础功能丰富,使用门槛低,企业级快速开发脚手架',
'login.banner.slogan3': '代码规范且开源免费',
'login.banner.subSlogan3': '后端代码完全遵循阿里巴巴编码规范,前端代码使用严格的 ESLint 检查',
'login.banner.subSlogan3':
'后端代码完全遵循阿里巴巴编码规范,前端代码使用严格的 ESLint 检查',
};

View File

@@ -61,7 +61,9 @@
<a-table-column title="登录行为" data-index="description" />
<a-table-column title="登录状态" align="center">
<template #cell="{ record }">
<a-tag v-if="record.status === 1" color="green"><span class="circle pass" />成功</a-tag>
<a-tag v-if="record.status === 1" color="green"
><span class="circle pass" />成功</a-tag
>
<a-tooltip v-else :content="record.errorMsg">
<a-tag color="red" style="cursor: pointer">
<span class="circle fail" />失败
@@ -88,7 +90,9 @@
} from '@/api/monitor/log';
const { proxy } = getCurrentInstance() as any;
const { SuccessFailureStatusEnum } = proxy.useDict('SuccessFailureStatusEnum');
const { SuccessFailureStatusEnum } = proxy.useDict(
'SuccessFailureStatusEnum'
);
const loginLogList = ref<LoginLogRecord[]>([]);
const total = ref(0);

View File

@@ -72,7 +72,9 @@
<a-table-column title="所属模块" data-index="module" />
<a-table-column title="操作状态" align="center">
<template #cell="{ record }">
<a-tag v-if="record.status === 1" color="green"><span class="circle pass" />成功</a-tag>
<a-tag v-if="record.status === 1" color="green"
><span class="circle pass" />成功</a-tag
>
<a-tooltip v-else :content="record.errorMsg">
<a-tag color="red" style="cursor: pointer">
<span class="circle fail" />失败
@@ -98,7 +100,9 @@
} from '@/api/monitor/log';
const { proxy } = getCurrentInstance() as any;
const { SuccessFailureStatusEnum } = proxy.useDict('SuccessFailureStatusEnum');
const { SuccessFailureStatusEnum } = proxy.useDict(
'SuccessFailureStatusEnum'
);
const operationLogList = ref<OperationLogRecord[]>([]);
const total = ref(0);

View File

@@ -50,15 +50,27 @@
</a-table-column>
<a-table-column title="状态码" align="center">
<template #cell="{ record }">
<a-tag v-if="record.statusCode >= 400" color="red">{{ record.statusCode }}</a-tag>
<a-tag v-else-if="record.statusCode === 200" color="green">{{ record.statusCode }}</a-tag>
<a-tag v-if="record.statusCode >= 400" color="red">{{
record.statusCode
}}</a-tag>
<a-tag v-else-if="record.statusCode === 200" color="green">{{
record.statusCode
}}</a-tag>
<a-tag v-else color="orange">{{ record.statusCode }}</a-tag>
</template>
</a-table-column>
<a-table-column title="请求方式" align="center" data-index="requestMethod" />
<a-table-column
title="请求方式"
align="center"
data-index="requestMethod"
/>
<a-table-column title="请求 URI">
<template #cell="{ record }">
<span :title="decodeURIComponent(record.requestUrl)">{{ record.requestUrl.match(/(\w+):\/\/([^/:]+)(:\d*)?([^#|\?|\n]*)(\?.*)?/)[4] }}</span>
<span :title="decodeURIComponent(record.requestUrl)">{{
record.requestUrl.match(
/(\w+):\/\/([^/:]+)(:\d*)?([^#|\?|\n]*)(\?.*)?/
)[4]
}}</span>
</template>
</a-table-column>
<a-table-column title="客户端 IP" data-index="clientIp" />
@@ -66,18 +78,33 @@
<a-table-column title="浏览器" data-index="browser" />
<a-table-column title="请求耗时">
<template #cell="{ record }">
<a-tag v-if="record.elapsedTime > 500" color="red">{{ record.elapsedTime }} ms</a-tag>
<a-tag v-else-if="record.elapsedTime > 200" color="orange">{{ record.elapsedTime }} ms</a-tag>
<a-tag v-if="record.elapsedTime > 500" color="red"
>{{ record.elapsedTime }} ms</a-tag
>
<a-tag v-else-if="record.elapsedTime > 200" color="orange"
>{{ record.elapsedTime }} ms</a-tag
>
<a-tag v-else color="green">{{ record.elapsedTime }} ms</a-tag>
</template>
</a-table-column>
<a-table-column title="创建时间" data-index="createTime" />
<a-table-column title="操作" align="center">
<template #cell="{ record }">
<a-button type="text" size="small" title="查看详情" @click="toDetail(record.id)">
<a-button
type="text"
size="small"
title="查看详情"
@click="toDetail(record.id)"
>
<template #icon><icon-eye /></template>详情
</a-button>
<a-button v-if="record.exceptionDetail" type="text" size="small" title="查看异常详情" @click="toExceptionDetail(record)">
<a-button
v-if="record.exceptionDetail"
type="text"
size="small"
title="查看异常详情"
@click="toExceptionDetail(record)"
>
<template #icon><icon-bug /></template>异常
</a-button>
</template>
@@ -126,7 +153,9 @@
<a-tag v-else-if="systemLog.elapsedTime > 200" color="orange">
{{ systemLog.elapsedTime }} ms
</a-tag>
<a-tag v-else color="green">{{ systemLog.elapsedTime }} ms</a-tag>
<a-tag v-else color="green"
>{{ systemLog.elapsedTime }} ms</a-tag
>
</span>
</a-descriptions-item>
<a-descriptions-item label="创建时间">
@@ -136,14 +165,23 @@
<span v-else>{{ systemLog.createTime }}</span>
</a-descriptions-item>
</a-descriptions>
<a-descriptions title="协议信息" :column="2" bordered style="margin-top: 25px">
<a-descriptions
title="协议信息"
:column="2"
bordered
style="margin-top: 25px"
>
<a-descriptions-item label="状态码">
<a-skeleton v-if="loading" :animation="true">
<a-skeleton-line :rows="1" />
</a-skeleton>
<span v-else>
<a-tag v-if="systemLog.statusCode >= 400" color="red">{{ systemLog.statusCode }}</a-tag>
<a-tag v-else-if="systemLog.statusCode === 200" color="green">{{ systemLog.statusCode }}</a-tag>
<a-tag v-if="systemLog.statusCode >= 400" color="red">{{
systemLog.statusCode
}}</a-tag>
<a-tag v-else-if="systemLog.statusCode === 200" color="green">{{
systemLog.statusCode
}}</a-tag>
<a-tag v-else color="orange">{{ systemLog.statusCode }}</a-tag>
</span>
</a-descriptions-item>
@@ -168,7 +206,8 @@
v-if="systemLog.responseBody"
:path="'res'"
:data="JSON.parse(systemLog.responseBody)"
:show-length="true" />
:show-length="true"
/>
<span v-else></span>
</a-space>
</a-descriptions-item>
@@ -181,7 +220,8 @@
v-if="systemLog.responseHeaders"
:path="'res'"
:data="JSON.parse(systemLog.responseHeaders)"
:show-length="true" />
:show-length="true"
/>
<span v-else></span>
</a-space>
</a-descriptions-item>
@@ -194,7 +234,8 @@
v-if="systemLog.requestBody"
:path="'res'"
:data="JSON.parse(systemLog.requestBody)"
:show-length="true" />
:show-length="true"
/>
<span v-else></span>
</a-space>
</a-descriptions-item>
@@ -206,7 +247,8 @@
<VueJsonPretty
v-if="systemLog.requestHeaders"
:data="JSON.parse(systemLog.requestHeaders)"
:show-length="true" />
:show-length="true"
/>
<span v-else></span>
</a-space>
</a-descriptions-item>

View File

@@ -12,7 +12,7 @@
v-model="queryParams.nickname"
placeholder="输入用户昵称搜索"
allow-clear
style="width: 150px;"
style="width: 150px"
@press-enter="handleQuery"
/>
</a-form-item>
@@ -59,22 +59,34 @@
</a-table-column>
<a-table-column title="用户昵称">
<template #cell="{ record }">
{{ record.nickname }}{{record.username}}
{{ record.nickname }}{{ record.username }}
</template>
</a-table-column>
<a-table-column title="登录 IP" data-index="clientIp" />
<a-table-column title="登录地点" data-index="location" />
<a-table-column title="浏览器" data-index="browser" />
<a-table-column title="登录时间" data-index="loginTime" />
<a-table-column v-if="checkPermission(['monitor:online:user:delete'])" title="操作" align="center">
<a-table-column
v-if="checkPermission(['monitor:online:user:delete'])"
title="操作"
align="center"
>
<template #cell="{ record }">
<a-popconfirm content="确定要强退该用户吗?" type="warning" @ok="handleKickout(record.token)">
<a-popconfirm
content="确定要强退该用户吗?"
type="warning"
@ok="handleKickout(record.token)"
>
<a-button
v-permission="['monitor:online:user:delete']"
type="text"
size="small"
:disabled="currentToken === record.token"
:title="currentToken === record.token ? '不能强退当前登录用户' : '强退'"
:title="
currentToken === record.token
? '不能强退当前登录用户'
: '强退'
"
>
<template #icon><icon-delete /></template>强退
</a-button>
@@ -124,12 +136,14 @@
*/
const getList = (params: OnlineUserParam = { ...queryParams.value }) => {
loading.value = true;
listOnlineUser(params).then((res) => {
onlineUserList.value = res.data.list;
total.value = res.data.total;
}).finally(() => {
loading.value = false;
});
listOnlineUser(params)
.then((res) => {
onlineUserList.value = res.data.list;
total.value = res.data.total;
})
.finally(() => {
loading.value = false;
});
};
getList();

View File

@@ -113,23 +113,19 @@
<template #columns>
<a-table-column title="部门名称">
<template #cell="{ record }">
<a-link @click="toDetail(record.id)">{{
record.name
}}</a-link>
<a-link @click="toDetail(record.id)">{{ record.name }}</a-link>
</template>
</a-table-column>
<a-table-column
title="部门排序"
align="center"
data-index="sort"
/>
<a-table-column title="部门排序" align="center" data-index="sort" />
<a-table-column title="状态" align="center" data-index="status">
<template #cell="{ record }">
<a-switch
v-model="record.status"
:checked-value="1"
:unchecked-value="2"
:disabled="record.disabled || !checkPermission(['system:dept:update'])"
:disabled="
record.disabled || !checkPermission(['system:dept:update'])
"
@change="handleChangeStatus(record)"
/>
</template>
@@ -188,7 +184,11 @@
@cancel="handleCancel"
>
<a-form ref="formRef" :model="form" :rules="rules" size="large">
<a-form-item label="上级部门" field="parentId" :disabled="form.disabled">
<a-form-item
label="上级部门"
field="parentId"
:disabled="form.disabled"
>
<a-tree-select
v-model="form.parentId"
:data="treeData"
@@ -527,7 +527,10 @@
}
} else if (record.children) {
record.children.forEach((r) => {
rowKeys.splice(rowKeys.findIndex((key: number | undefined) => key === r.id), 1);
rowKeys.splice(
rowKeys.findIndex((key: number | undefined) => key === r.id),
1
);
proxy.$refs.tableRef.select(r.id, false);
if (r.children) {
handleSelect(rowKeys, rowKey, r);

View File

@@ -307,21 +307,13 @@
<a-radio :value="false"></a-radio>
</a-radio-group>
</a-form-item>
<a-form-item
v-if="form.type === 2"
label="是否缓存"
field="isCache"
>
<a-form-item v-if="form.type === 2" label="是否缓存" field="isCache">
<a-radio-group v-model="form.isCache" type="button">
<a-radio :value="true"></a-radio>
<a-radio :value="false"></a-radio>
</a-radio-group>
</a-form-item>
<a-form-item
v-if="form.type !== 3"
label="是否隐藏"
field="isHidden"
>
<a-form-item v-if="form.type !== 3" label="是否隐藏" field="isHidden">
<a-radio-group v-model="form.isHidden" type="button">
<a-radio :value="true"></a-radio>
<a-radio :value="false"></a-radio>

View File

@@ -118,33 +118,31 @@
<a-table-column title="ID" data-index="id" />
<a-table-column title="角色名称" data-index="name" :width="130">
<template #cell="{ record }">
<a-link @click="toDetail(record.id)">{{
record.name
}}</a-link>
<a-link @click="toDetail(record.id)">{{ record.name }}</a-link>
</template>
</a-table-column>
<a-table-column title="角色编码" data-index="code" />
<a-table-column title="数据权限" :width="130">
<template #cell="{ record }">
<span v-if="record.dataScope === 1">全部数据权限</span>
<span v-else-if="record.dataScope === 2">本部门及以下数据权限</span>
<span v-else-if="record.dataScope === 2"
>本部门及以下数据权限</span
>
<span v-else-if="record.dataScope === 3">本部门数据权限</span>
<span v-else-if="record.dataScope === 4">仅本人数据权限</span>
<span v-else>自定义数据权限</span>
</template>
</a-table-column>
<a-table-column
title="角色排序"
align="center"
data-index="sort"
/>
<a-table-column title="角色排序" align="center" data-index="sort" />
<a-table-column title="状态" align="center" data-index="status">
<template #cell="{ record }">
<a-switch
v-model="record.status"
:checked-value="1"
:unchecked-value="2"
:disabled="record.disabled || !checkPermission(['system:role:update'])"
:disabled="
record.disabled || !checkPermission(['system:role:update'])
"
@change="handleChangeStatus(record)"
/>
</template>
@@ -204,7 +202,11 @@
@cancel="handleCancel"
>
<a-form ref="formRef" :model="form" :rules="rules" size="large">
<a-alert v-if="!form.disabled" type="warning" style="margin-bottom: 15px;">
<a-alert
v-if="!form.disabled"
type="warning"
style="margin-bottom: 15px"
>
变更角色编码功能权限或数据权限后关联在线用户会自动下线
</a-alert>
<fieldset>
@@ -212,7 +214,11 @@
<a-form-item label="角色名称" field="name">
<a-input v-model="form.name" placeholder="请输入角色名称" />
</a-form-item>
<a-form-item label="角色编码" field="code" :disabled="form.disabled">
<a-form-item
label="角色编码"
field="code"
:disabled="form.disabled"
>
<a-input v-model="form.code" placeholder="请输入角色编码" />
</a-form-item>
<a-form-item label="角色排序" field="sort">
@@ -239,8 +245,16 @@
<legend>功能权限</legend>
<a-form-item label="功能权限" :disabled="form.disabled">
<a-space style="margin-top: 2px">
<a-checkbox v-model="menuExpandAll" @change="handleExpandAll('menu')">展开/折叠</a-checkbox>
<a-checkbox v-model="menuCheckAll" @change="handleCheckAll('menu')">全选/全不选</a-checkbox>
<a-checkbox
v-model="menuExpandAll"
@change="handleExpandAll('menu')"
>展开/折叠</a-checkbox
>
<a-checkbox
v-model="menuCheckAll"
@change="handleCheckAll('menu')"
>全选/全不选</a-checkbox
>
<a-checkbox v-model="menuCheckStrictly">父子联动</a-checkbox>
</a-space>
<template #extra>
@@ -259,17 +273,33 @@
</fieldset>
<fieldset>
<legend>数据权限</legend>
<a-form-item label="数据权限" field="dataScope" :disabled="form.disabled">
<a-form-item
label="数据权限"
field="dataScope"
:disabled="form.disabled"
>
<a-select
v-model="form.dataScope"
:options="DataScopeEnum"
placeholder="请选择数据权限"
/>
</a-form-item>
<a-form-item v-if="form.dataScope === 5" label="权限范围" :disabled="form.disabled">
<a-form-item
v-if="form.dataScope === 5"
label="权限范围"
:disabled="form.disabled"
>
<a-space style="margin-top: 2px">
<a-checkbox v-model="deptExpandAll" @change="handleExpandAll('dept')">展开/折叠</a-checkbox>
<a-checkbox v-model="deptCheckAll" @change="handleCheckAll('dept')">全选/全不选</a-checkbox>
<a-checkbox
v-model="deptExpandAll"
@change="handleExpandAll('dept')"
>展开/折叠</a-checkbox
>
<a-checkbox
v-model="deptCheckAll"
@change="handleCheckAll('dept')"
>全选/全不选</a-checkbox
>
<a-checkbox v-model="deptCheckStrictly">父子联动</a-checkbox>
</a-space>
<template #extra>
@@ -328,7 +358,9 @@
</a-skeleton>
<span v-else>
<span v-if="role.dataScope === 1">全部数据权限</span>
<span v-else-if="role.dataScope === 2">本部门及以下数据权限</span>
<span v-else-if="role.dataScope === 2"
>本部门及以下数据权限</span
>
<span v-else-if="role.dataScope === 3">本部门数据权限</span>
<span v-else-if="role.dataScope === 4">仅本人数据权限</span>
<span v-else>自定义数据权限</span>
@@ -464,7 +496,21 @@
form: {} as RoleRecord,
// 表单验证规则
rules: {
name: [{ required: true, message: '请输入角色名称' }],
name: [
{ required: true, message: '请输入角色名称' },
{
match: /^[\u4e00-\u9fa5a-zA-Z0-9_-]{1,20}$/,
message:
'长度为 1 到 20 位,可以包含中文、字母、数字、下划线,短横线',
},
],
code: [
{ required: true, message: '请输入角色编码' },
{
match: /^[a-zA-Z][a-zA-Z0-9_]{1,15}$/,
message: '长度为 2 到 16 位,可以包含字母、数字,下划线,以字母开头',
},
],
dataScope: [{ required: true, message: '请选择数据权限' }],
sort: [{ required: true, message: '请输入角色排序' }],
},

View File

@@ -8,21 +8,30 @@
size="large"
class="form"
>
<a-form-item :label="$t('userCenter.basicInfo.form.label.username')" disabled>
<a-form-item
:label="$t('userCenter.basicInfo.form.label.username')"
disabled
>
<a-input
v-model="form.username"
:placeholder="$t('userCenter.basicInfo.form.placeholder.username')"
max-length="50"
max-length="16"
/>
</a-form-item>
<a-form-item :label="$t('userCenter.basicInfo.form.label.nickname')" field="nickname">
<a-form-item
:label="$t('userCenter.basicInfo.form.label.nickname')"
field="nickname"
>
<a-input
v-model="form.nickname"
:placeholder="$t('userCenter.basicInfo.form.placeholder.nickname')"
max-length="32"
max-length="20"
/>
</a-form-item>
<a-form-item :label="$t('userCenter.basicInfo.form.label.gender')" field="gender">
<a-form-item
:label="$t('userCenter.basicInfo.form.label.gender')"
field="gender"
>
<a-radio-group v-model="form.gender">
<a-radio :value="1"></a-radio>
<a-radio :value="2"></a-radio>
@@ -70,12 +79,20 @@
required: true,
message: t('userCenter.basicInfo.form.error.required.username'),
},
{
match: /^[a-zA-Z][a-zA-Z0-9_]{3,15}$/,
message: t('userCenter.basicInfo.form.error.match.username'),
},
],
nickname: [
{
required: true,
message: t('userCenter.basicInfo.form.error.required.nickname'),
},
{
match: /^[\u4e00-\u9fa5a-zA-Z0-9_-]{1,20}$/,
message: t('userCenter.basicInfo.form.error.match.nickname'),
},
],
};
}),

View File

@@ -29,7 +29,9 @@
<a-table-column title="所属模块" data-index="module" />
<a-table-column title="操作状态" align="center">
<template #cell="{ record }">
<a-tag v-if="record.status === 1" color="green"><span class="circle pass" />成功</a-tag>
<a-tag v-if="record.status === 1" color="green"
><span class="circle pass" />成功</a-tag
>
<a-tooltip v-else :content="record.errorMsg">
<a-tag color="red" style="cursor: pointer">
<span class="circle fail" />失败

View File

@@ -8,14 +8,25 @@
<template #description>
<div class="content">
<a-typography-paragraph v-if="loginStore.email">
{{ $t('userCenter.securitySettings.updateEmail.placeholder.success.email') }}{{ loginStore.email }}
{{
$t(
'userCenter.securitySettings.updateEmail.placeholder.success.email'
)
}}{{ loginStore.email }}
</a-typography-paragraph>
<a-typography-paragraph v-else class="tip">
{{ $t('userCenter.securitySettings.updateEmail.placeholder.error.email') }}
{{
$t(
'userCenter.securitySettings.updateEmail.placeholder.error.email'
)
}}
</a-typography-paragraph>
</div>
<div class="operation">
<a-link :title="$t('userCenter.securitySettings.button.update')" @click="toUpdate">
<a-link
:title="$t('userCenter.securitySettings.button.update')"
@click="toUpdate"
>
{{ $t('userCenter.securitySettings.button.update') }}
</a-link>
</div>
@@ -30,17 +41,35 @@
@cancel="handleCancel"
>
<a-form ref="formRef" :model="form" :rules="rules" size="large">
<a-form-item :label="$t('userCenter.securitySettings.updateEmail.form.label.newEmail')" field="newEmail">
<a-form-item
:label="
$t('userCenter.securitySettings.updateEmail.form.label.newEmail')
"
field="newEmail"
>
<a-input
v-model="form.newEmail"
:placeholder="$t('userCenter.securitySettings.updateEmail.form.placeholder.newEmail')"
:placeholder="
$t(
'userCenter.securitySettings.updateEmail.form.placeholder.newEmail'
)
"
allow-clear
/>
</a-form-item>
<a-form-item :label="$t('userCenter.securitySettings.updateEmail.form.label.captcha')" field="captcha">
<a-form-item
:label="
$t('userCenter.securitySettings.updateEmail.form.label.captcha')
"
field="captcha"
>
<a-input
v-model="form.captcha"
:placeholder="$t('userCenter.securitySettings.updateEmail.form.placeholder.captcha')"
:placeholder="
$t(
'userCenter.securitySettings.updateEmail.form.placeholder.captcha'
)
"
max-length="6"
allow-clear
style="width: 80%"
@@ -55,10 +84,21 @@
{{ captchaBtnName }}
</a-button>
</a-form-item>
<a-form-item :label="$t('userCenter.securitySettings.updateEmail.form.label.currentPassword')" field="currentPassword">
<a-form-item
:label="
$t(
'userCenter.securitySettings.updateEmail.form.label.currentPassword'
)
"
field="currentPassword"
>
<a-input-password
v-model="form.currentPassword"
:placeholder="$t('userCenter.securitySettings.updateEmail.form.placeholder.currentPassword')"
:placeholder="
$t(
'userCenter.securitySettings.updateEmail.form.placeholder.currentPassword'
)
"
max-length="32"
allow-clear
/>
@@ -85,7 +125,9 @@
const captchaLoading = ref(false);
const captchaDisable = ref(false);
const visible = ref(false);
const captchaBtnNameKey = ref('userCenter.securitySettings.updateEmail.form.sendCaptcha');
const captchaBtnNameKey = ref(
'userCenter.securitySettings.updateEmail.form.sendCaptcha'
);
const captchaBtnName = computed(() => t(captchaBtnNameKey.value));
// 表单数据
@@ -98,24 +140,48 @@
const rules = computed((): Record<string, FieldRule[]> => {
return {
newEmail: [
{ required: true, message: t('userCenter.securitySettings.updateEmail.form.error.required.newEmail') },
{ type: 'email', message: t('userCenter.securitySettings.updateEmail.form.error.match.newEmail') },
{
required: true,
message: t(
'userCenter.securitySettings.updateEmail.form.error.required.newEmail'
),
},
{
type: 'email',
message: t(
'userCenter.securitySettings.updateEmail.form.error.match.newEmail'
),
},
{
validator: (value, callback) => {
if (value === loginStore.email) {
callback(t('userCenter.securitySettings.updateEmail.form.error.validator.newEmail'))
callback(
t(
'userCenter.securitySettings.updateEmail.form.error.validator.newEmail'
)
);
} else {
callback()
callback();
}
}
}
},
},
],
captcha: [
{ required: true, message: t('userCenter.securitySettings.updateEmail.form.error.required.captcha') }
{
required: true,
message: t(
'userCenter.securitySettings.updateEmail.form.error.required.captcha'
),
},
],
currentPassword: [
{ required: true, message: t('userCenter.securitySettings.updateEmail.form.error.required.currentPassword') }
]
{
required: true,
message: t(
'userCenter.securitySettings.updateEmail.form.error.required.currentPassword'
),
},
],
};
});
@@ -125,9 +191,10 @@
const resetCaptcha = () => {
window.clearInterval(captchaTimer.value);
captchaTime.value = 60;
captchaBtnNameKey.value = 'userCenter.securitySettings.updateEmail.form.sendCaptcha';
captchaBtnNameKey.value =
'userCenter.securitySettings.updateEmail.form.sendCaptcha';
captchaDisable.value = false;
}
};
/**
* 发送验证码
@@ -137,28 +204,37 @@
proxy.$refs.formRef.validateField('newEmail', (valid: any) => {
if (!valid) {
captchaLoading.value = true;
captchaBtnNameKey.value = 'userCenter.securitySettings.updateEmail.form.loading.sendCaptcha';
captchaBtnNameKey.value =
'userCenter.securitySettings.updateEmail.form.loading.sendCaptcha';
getMailCaptcha({
email: form.newEmail
}).then((res) => {
captchaLoading.value = false;
captchaDisable.value = true;
captchaBtnNameKey.value = `${t('userCenter.securitySettings.updateEmail.form.reSendCaptcha')}(${captchaTime.value -= 1}s)`;
captchaTimer.value = window.setInterval(() => {
captchaTime.value -= 1;
captchaBtnNameKey.value = `${t('userCenter.securitySettings.updateEmail.form.reSendCaptcha')}(${captchaTime.value}s)`;
if (captchaTime.value < 0) {
window.clearInterval(captchaTimer.value);
captchaTime.value = 60;
captchaBtnNameKey.value = t('userCenter.securitySettings.updateEmail.form.reSendCaptcha');
captchaDisable.value = false;
}
}, 1000);
proxy.$message.success(res.msg);
}).catch(() => {
resetCaptcha();
captchaLoading.value = false;
});
email: form.newEmail,
})
.then((res) => {
captchaLoading.value = false;
captchaDisable.value = true;
captchaBtnNameKey.value = `${t(
'userCenter.securitySettings.updateEmail.form.reSendCaptcha'
)}(${(captchaTime.value -= 1)}s)`;
captchaTimer.value = window.setInterval(() => {
captchaTime.value -= 1;
captchaBtnNameKey.value = `${t(
'userCenter.securitySettings.updateEmail.form.reSendCaptcha'
)}(${captchaTime.value}s)`;
if (captchaTime.value < 0) {
window.clearInterval(captchaTimer.value);
captchaTime.value = 60;
captchaBtnNameKey.value = t(
'userCenter.securitySettings.updateEmail.form.reSendCaptcha'
);
captchaDisable.value = false;
}
}, 1000);
proxy.$message.success(res.msg);
})
.catch(() => {
resetCaptcha();
captchaLoading.value = false;
});
}
});
};

View File

@@ -8,10 +8,18 @@
<template #description>
<div class="content">
<a-typography-paragraph v-if="loginStore.phone">
{{ $t('userCenter.securitySettings.updatePhone.placeholder.success.phone') }}{{ loginStore.phone }}
{{
$t(
'userCenter.securitySettings.updatePhone.placeholder.success.phone'
)
}}{{ loginStore.phone }}
</a-typography-paragraph>
<a-typography-paragraph v-else class="tip">
{{ $t('userCenter.securitySettings.updatePhone.placeholder.error.phone') }}
{{
$t(
'userCenter.securitySettings.updatePhone.placeholder.error.phone'
)
}}
</a-typography-paragraph>
</div>
<div class="operation">

View File

@@ -8,14 +8,25 @@
<template #description>
<div class="content">
<a-typography-paragraph v-if="loginStore.pwdResetTime">
{{ $t('userCenter.securitySettings.updatePwd.placeholder.success.password') }}
{{
$t(
'userCenter.securitySettings.updatePwd.placeholder.success.password'
)
}}
</a-typography-paragraph>
<a-typography-paragraph v-else class="tip">
{{ $t('userCenter.securitySettings.updatePwd.placeholder.error.password') }}
{{
$t(
'userCenter.securitySettings.updatePwd.placeholder.error.password'
)
}}
</a-typography-paragraph>
</div>
<div class="operation">
<a-link :title="$t('userCenter.securitySettings.button.update')" @click="toUpdate">
<a-link
:title="$t('userCenter.securitySettings.button.update')"
@click="toUpdate"
>
{{ $t('userCenter.securitySettings.button.update') }}
</a-link>
</div>
@@ -30,26 +41,53 @@
@cancel="handleCancel"
>
<a-form ref="formRef" :model="form" :rules="rules" size="large">
<a-form-item :label="$t('userCenter.securitySettings.updatePwd.form.label.oldPassword')" field="oldPassword">
<a-form-item
:label="
$t('userCenter.securitySettings.updatePwd.form.label.oldPassword')
"
field="oldPassword"
>
<a-input-password
v-model="form.oldPassword"
:placeholder="$t('userCenter.securitySettings.updatePwd.form.placeholder.oldPassword')"
:placeholder="
$t(
'userCenter.securitySettings.updatePwd.form.placeholder.oldPassword'
)
"
max-length="32"
allow-clear
/>
</a-form-item>
<a-form-item :label="$t('userCenter.securitySettings.updatePwd.form.label.newPassword')" field="newPassword">
<a-form-item
:label="
$t('userCenter.securitySettings.updatePwd.form.label.newPassword')
"
field="newPassword"
>
<a-input-password
v-model="form.newPassword"
:placeholder="$t('userCenter.securitySettings.updatePwd.form.placeholder.newPassword')"
:placeholder="
$t(
'userCenter.securitySettings.updatePwd.form.placeholder.newPassword'
)
"
max-length="32"
allow-clear
/>
</a-form-item>
<a-form-item :label="$t('userCenter.securitySettings.updatePwd.form.label.rePassword')" field="rePassword">
<a-form-item
:label="
$t('userCenter.securitySettings.updatePwd.form.label.rePassword')
"
field="rePassword"
>
<a-input-password
v-model="form.rePassword"
:placeholder="$t('userCenter.securitySettings.updatePwd.form.placeholder.rePassword')"
:placeholder="
$t(
'userCenter.securitySettings.updatePwd.form.placeholder.rePassword'
)
"
max-length="32"
allow-clear
/>
@@ -81,31 +119,61 @@
// 表单验证规则
const rules = computed((): Record<string, FieldRule[]> => {
return {
oldPassword: [{ required: true, message: t('userCenter.securitySettings.updatePwd.form.error.required.oldPassword') }],
oldPassword: [
{
required: true,
message: t(
'userCenter.securitySettings.updatePwd.form.error.required.oldPassword'
),
},
],
newPassword: [
{ required: true, message: t('userCenter.securitySettings.updatePwd.form.error.required.newPassword') },
{ match: /^(?=.*\d)(?=.*[a-z]).{6,32}$/, message: t('userCenter.securitySettings.updatePwd.form.error.match.newPassword') },
{
required: true,
message: t(
'userCenter.securitySettings.updatePwd.form.error.required.newPassword'
),
},
{
match: /^(?=.*\d)(?=.*[a-z]).{6,32}$/,
message: t(
'userCenter.securitySettings.updatePwd.form.error.match.newPassword'
),
},
{
validator: (value, callback) => {
if (value === form.oldPassword) {
callback(t('userCenter.securitySettings.updatePwd.form.error.validator.newPassword'))
callback(
t(
'userCenter.securitySettings.updatePwd.form.error.validator.newPassword'
)
);
} else {
callback()
callback();
}
}
}
},
},
],
rePassword: [
{ required: true, message: t('userCenter.securitySettings.updatePwd.form.error.required.rePassword') },
{
required: true,
message: t(
'userCenter.securitySettings.updatePwd.form.error.required.rePassword'
),
},
{
validator: (value, callback) => {
if (value !== form.newPassword) {
callback(t('userCenter.securitySettings.updatePwd.form.error.validator.rePassword'))
callback(
t(
'userCenter.securitySettings.updatePwd.form.error.validator.rePassword'
)
);
} else {
callback()
callback();
}
}
}
},
},
],
};
});

View File

@@ -25,7 +25,11 @@ export default {
'userCenter.basicInfo.form.placeholder.nickname': 'Please enter nickname',
'userCenter.basicInfo.form.error.required.username': 'Please enter username',
'userCenter.basicInfo.form.error.match.username':
'Username are 4 to 16 characters long and can contain letters, numbers, underscores, and start with a letter',
'userCenter.basicInfo.form.error.required.nickname': 'Please enter nickname',
'userCenter.basicInfo.form.error.match.nickname':
'Nickname are 1 to 20 digits long and can contain Chinese, letters, numbers, underscores, dashes',
'userCenter.basicInfo.form.save': 'Save',
'userCenter.basicInfo.form.save.success': 'Save success',
@@ -34,55 +38,81 @@ export default {
// security-settings
// update-pwd
'userCenter.securitySettings.updatePwd.label.password': 'Login Password',
'userCenter.securitySettings.updatePwd.placeholder.success.password': 'Has been set',
'userCenter.securitySettings.updatePwd.placeholder.success.password':
'Has been set',
'userCenter.securitySettings.updatePwd.placeholder.error.password':
'You have not set a password yet. The password must contain at least six letters, digits, and special characters except Spaces.',
'userCenter.securitySettings.updatePwd.modal.title': 'Update login password',
'userCenter.securitySettings.updatePwd.form.label.oldPassword': 'Old password',
'userCenter.securitySettings.updatePwd.form.label.newPassword': 'New password',
'userCenter.securitySettings.updatePwd.form.label.rePassword': 'Confirm password',
'userCenter.securitySettings.updatePwd.form.label.oldPassword':
'Old password',
'userCenter.securitySettings.updatePwd.form.label.newPassword':
'New password',
'userCenter.securitySettings.updatePwd.form.label.rePassword':
'Confirm password',
'userCenter.securitySettings.updatePwd.form.placeholder.oldPassword': 'Please enter old password',
'userCenter.securitySettings.updatePwd.form.placeholder.newPassword': 'Password contains 6 to 32 digits and letters',
'userCenter.securitySettings.updatePwd.form.placeholder.rePassword': 'Please enter new password again',
'userCenter.securitySettings.updatePwd.form.placeholder.oldPassword':
'Please enter old password',
'userCenter.securitySettings.updatePwd.form.placeholder.newPassword':
'Password contains 6 to 32 digits and letters',
'userCenter.securitySettings.updatePwd.form.placeholder.rePassword':
'Please enter new password again',
'userCenter.securitySettings.updatePwd.form.error.required.oldPassword': 'Please enter old password',
'userCenter.securitySettings.updatePwd.form.error.required.newPassword': 'Please enter new password',
'userCenter.securitySettings.updatePwd.form.error.match.newPassword': 'Password contains 6 to 32 digits and letters',
'userCenter.securitySettings.updatePwd.form.error.validator.newPassword': 'New password cannot be the same as the old password',
'userCenter.securitySettings.updatePwd.form.error.required.rePassword': 'Please enter new password again',
'userCenter.securitySettings.updatePwd.form.error.validator.rePassword': 'Two passwords are different',
'userCenter.securitySettings.updatePwd.form.error.required.oldPassword':
'Please enter old password',
'userCenter.securitySettings.updatePwd.form.error.required.newPassword':
'Please enter new password',
'userCenter.securitySettings.updatePwd.form.error.match.newPassword':
'Password contains 6 to 32 digits and letters',
'userCenter.securitySettings.updatePwd.form.error.validator.newPassword':
'New password cannot be the same as the old password',
'userCenter.securitySettings.updatePwd.form.error.required.rePassword':
'Please enter new password again',
'userCenter.securitySettings.updatePwd.form.error.validator.rePassword':
'Two passwords are different',
// update-phone
'userCenter.securitySettings.updatePhone.label.phone': 'Phone',
'userCenter.securitySettings.updatePhone.placeholder.success.phone': 'Has been bound',
'userCenter.securitySettings.updatePhone.placeholder.success.phone':
'Has been bound',
'userCenter.securitySettings.updatePhone.placeholder.error.phone':
'You have not set a phone yet. The phone binding can be used to retrieve passwords and receive notifications and SMS login.',
// update-email
'userCenter.securitySettings.updateEmail.label.email': 'Email',
'userCenter.securitySettings.updateEmail.placeholder.success.email': 'Has been bound',
'userCenter.securitySettings.updateEmail.placeholder.success.email':
'Has been bound',
'userCenter.securitySettings.updateEmail.placeholder.error.email':
'You have not set a mailbox yet. The mailbox binding can be used to retrieve passwords and receive notifications.',
'userCenter.securitySettings.updateEmail.modal.title': 'Update email',
'userCenter.securitySettings.updateEmail.form.label.newEmail': 'New email',
'userCenter.securitySettings.updateEmail.form.label.captcha': 'Captcha',
'userCenter.securitySettings.updateEmail.form.label.currentPassword': 'Current password',
'userCenter.securitySettings.updateEmail.form.label.currentPassword':
'Current password',
'userCenter.securitySettings.updateEmail.form.sendCaptcha': 'Send captcha',
'userCenter.securitySettings.updateEmail.form.reSendCaptcha': 'Resend captcha',
'userCenter.securitySettings.updateEmail.form.loading.sendCaptcha': 'Sending...',
'userCenter.securitySettings.updateEmail.form.reSendCaptcha':
'Resend captcha',
'userCenter.securitySettings.updateEmail.form.loading.sendCaptcha':
'Sending...',
'userCenter.securitySettings.updateEmail.form.placeholder.newEmail': 'Please enter new email',
'userCenter.securitySettings.updateEmail.form.placeholder.captcha': 'Please enter email captcha',
'userCenter.securitySettings.updateEmail.form.placeholder.currentPassword': 'Please enter current password',
'userCenter.securitySettings.updateEmail.form.placeholder.newEmail':
'Please enter new email',
'userCenter.securitySettings.updateEmail.form.placeholder.captcha':
'Please enter email captcha',
'userCenter.securitySettings.updateEmail.form.placeholder.currentPassword':
'Please enter current password',
'userCenter.securitySettings.updateEmail.form.error.required.newEmail': 'Please enter new email',
'userCenter.securitySettings.updateEmail.form.error.match.newEmail': 'Please enter the correct email',
'userCenter.securitySettings.updateEmail.form.error.validator.newEmail': 'New email cannot be the same as the old email',
'userCenter.securitySettings.updateEmail.form.error.required.captcha': 'Please enter email captcha',
'userCenter.securitySettings.updateEmail.form.error.required.currentPassword': 'Please enter current password',
'userCenter.securitySettings.updateEmail.form.error.required.newEmail':
'Please enter new email',
'userCenter.securitySettings.updateEmail.form.error.match.newEmail':
'Please enter the correct email',
'userCenter.securitySettings.updateEmail.form.error.validator.newEmail':
'New email cannot be the same as the old email',
'userCenter.securitySettings.updateEmail.form.error.required.captcha':
'Please enter email captcha',
'userCenter.securitySettings.updateEmail.form.error.required.currentPassword':
'Please enter current password',
'userCenter.securitySettings.button.update': 'Update',
};

View File

@@ -25,7 +25,11 @@ export default {
'userCenter.basicInfo.form.placeholder.nickname': '请输入昵称',
'userCenter.basicInfo.form.error.required.username': '请输入用户名',
'userCenter.basicInfo.form.error.match.username':
'长度为 4 到 16 位,可以包含字母、数字,下划线,以字母开头',
'userCenter.basicInfo.form.error.required.nickname': '请输入昵称',
'userCenter.basicInfo.form.error.match.nickname':
'长度为 1 到 20 位,可以包含中文、字母、数字、下划线,短横线',
'userCenter.basicInfo.form.save': '保存',
'userCenter.basicInfo.form.save.success': '保存成功',
@@ -34,7 +38,8 @@ export default {
// security-settings
// update-pwd
'userCenter.securitySettings.updatePwd.label.password': '登录密码',
'userCenter.securitySettings.updatePwd.placeholder.success.password': '已设置',
'userCenter.securitySettings.updatePwd.placeholder.success.password':
'已设置',
'userCenter.securitySettings.updatePwd.placeholder.error.password':
'您暂未设置密码密码至少6位字符支持数字、字母和除空格外的特殊字符。',
@@ -43,16 +48,25 @@ export default {
'userCenter.securitySettings.updatePwd.form.label.newPassword': '新密码',
'userCenter.securitySettings.updatePwd.form.label.rePassword': '确认新密码',
'userCenter.securitySettings.updatePwd.form.placeholder.oldPassword': '请输入当前密码',
'userCenter.securitySettings.updatePwd.form.placeholder.newPassword': '密码长度 6 到 32 位,同时包含数字和字母',
'userCenter.securitySettings.updatePwd.form.placeholder.rePassword': '请再次输入新密码',
'userCenter.securitySettings.updatePwd.form.placeholder.oldPassword':
'请输入当前密码',
'userCenter.securitySettings.updatePwd.form.placeholder.newPassword':
'长度为 6 到 32 位,同时包含字母和数字',
'userCenter.securitySettings.updatePwd.form.placeholder.rePassword':
'请再次输入新密码',
'userCenter.securitySettings.updatePwd.form.error.required.oldPassword': '请输入当前密码',
'userCenter.securitySettings.updatePwd.form.error.required.newPassword': '请输入密码',
'userCenter.securitySettings.updatePwd.form.error.match.newPassword': '密码长度 6 到 32 位,同时包含数字和字母',
'userCenter.securitySettings.updatePwd.form.error.validator.newPassword': '新密码不能与当前密码相同',
'userCenter.securitySettings.updatePwd.form.error.required.rePassword': '请再次输入新密码',
'userCenter.securitySettings.updatePwd.form.error.validator.rePassword': '两次输入的密码不一致',
'userCenter.securitySettings.updatePwd.form.error.required.oldPassword':
'请输入当前密码',
'userCenter.securitySettings.updatePwd.form.error.required.newPassword':
'请输入新密码',
'userCenter.securitySettings.updatePwd.form.error.match.newPassword':
'长度为 6 到 32 位,同时包含字母和数字',
'userCenter.securitySettings.updatePwd.form.error.validator.newPassword':
'新密码不能与当前密码相同',
'userCenter.securitySettings.updatePwd.form.error.required.rePassword':
'请再次输入新密码',
'userCenter.securitySettings.updatePwd.form.error.validator.rePassword':
'两次输入的密码不一致',
// update-phone
'userCenter.securitySettings.updatePhone.label.phone': '安全手机',
@@ -69,20 +83,30 @@ export default {
'userCenter.securitySettings.updateEmail.modal.title': '修改邮箱',
'userCenter.securitySettings.updateEmail.form.label.newEmail': '新邮箱',
'userCenter.securitySettings.updateEmail.form.label.captcha': '验证码',
'userCenter.securitySettings.updateEmail.form.label.currentPassword': '当前密码',
'userCenter.securitySettings.updateEmail.form.label.currentPassword':
'当前密码',
'userCenter.securitySettings.updateEmail.form.sendCaptcha': '发送验证码',
'userCenter.securitySettings.updateEmail.form.reSendCaptcha': '重新发送',
'userCenter.securitySettings.updateEmail.form.loading.sendCaptcha': '发送中...',
'userCenter.securitySettings.updateEmail.form.loading.sendCaptcha':
'发送中...',
'userCenter.securitySettings.updateEmail.form.placeholder.newEmail': '请输入新邮箱',
'userCenter.securitySettings.updateEmail.form.placeholder.captcha': '请输入邮箱验证码',
'userCenter.securitySettings.updateEmail.form.placeholder.currentPassword': '请输入当前密码',
'userCenter.securitySettings.updateEmail.form.placeholder.newEmail':
'请输入邮箱',
'userCenter.securitySettings.updateEmail.form.placeholder.captcha':
'请输入邮箱验证码',
'userCenter.securitySettings.updateEmail.form.placeholder.currentPassword':
'请输入当前密码',
'userCenter.securitySettings.updateEmail.form.error.required.newEmail': '请输入新邮箱',
'userCenter.securitySettings.updateEmail.form.error.match.newEmail': '请输入正确的邮箱',
'userCenter.securitySettings.updateEmail.form.error.validator.newEmail': '新邮箱不能与当前邮箱相同',
'userCenter.securitySettings.updateEmail.form.error.required.captcha': '请输入邮箱验证码',
'userCenter.securitySettings.updateEmail.form.error.required.currentPassword': '请输入当前密码',
'userCenter.securitySettings.updateEmail.form.error.required.newEmail':
'请输入邮箱',
'userCenter.securitySettings.updateEmail.form.error.match.newEmail':
'请输入正确的邮箱',
'userCenter.securitySettings.updateEmail.form.error.validator.newEmail':
'新邮箱不能与当前邮箱相同',
'userCenter.securitySettings.updateEmail.form.error.required.captcha':
'请输入邮箱验证码',
'userCenter.securitySettings.updateEmail.form.error.required.currentPassword':
'请输入当前密码',
'userCenter.securitySettings.button.update': '修改',
};

View File

@@ -3,7 +3,14 @@
<Breadcrumb :items="['menu.system', 'menu.system.user.list']" />
<a-card class="general-card" :title="$t('menu.system.user.list')">
<a-row>
<a-col :xs="9" :sm="6" :md="5" :lg="4" :xl="4" style="margin-right: 10px">
<a-col
:xs="9"
:sm="6"
:md="5"
:lg="4"
:xl="4"
style="margin-right: 10px"
>
<a-input-search
v-model="deptName"
placeholder="输入部门名称搜索"
@@ -157,13 +164,16 @@
<a-table-column title="头像" align="center">
<template #cell="{ record }">
<a-avatar>
<img :src="getAvatar(record.avatar, record.gender)" alt="头像" />
<img
:src="getAvatar(record.avatar, record.gender)"
alt="头像"
/>
</a-avatar>
</template>
</a-table-column>
<a-table-column title="联系方式" :width="170">
<template #cell="{ record }">
{{ record.email }}<br v-if="record.email && record.phone">
{{ record.email }}<br v-if="record.email && record.phone" />
{{ record.phone }}
</template>
</a-table-column>
@@ -173,7 +183,10 @@
v-model="record.status"
:checked-value="1"
:unchecked-value="2"
:disabled="record.disabled || !checkPermission(['system:user:update'])"
:disabled="
record.disabled ||
!checkPermission(['system:user:update'])
"
@change="handleChangeStatus(record)"
/>
</template>
@@ -186,7 +199,7 @@
</a-table-column>
<a-table-column title="创建人/创建时间" :width="175">
<template #cell="{ record }">
{{ record.createUserString }}<br>
{{ record.createUserString }}<br />
{{ record.createTime }}
</template>
</a-table-column>
@@ -240,7 +253,9 @@
size="small"
title="重置密码"
>
<template #icon><svg-icon icon-class="privacy" /></template>
<template #icon
><svg-icon icon-class="privacy"
/></template>
</a-button>
</a-popconfirm>
<a-button
@@ -251,7 +266,9 @@
:disabled="record.disabled"
@click="toUpdateRole(record.id)"
>
<template #icon><svg-icon icon-class="reference" /></template>
<template #icon
><svg-icon icon-class="reference"
/></template>
</a-button>
</template>
</a-table-column>
@@ -325,7 +342,11 @@
style="width: 431px"
/>
</a-form-item>
<a-form-item label="所属角色" field="roleIds" :disabled="form.disabled">
<a-form-item
label="所属角色"
field="roleIds"
:disabled="form.disabled"
>
<a-select
v-model="form.roleIds"
:options="roleOptions"
@@ -554,8 +575,8 @@
rules: {
username: [{ required: true, message: '请输入用户名' }],
nickname: [{ required: true, message: '请输入昵称' }],
roleIds: [{ required: true, message: '请选择所属角色' }],
deptId: [{ required: true, message: '请选择所属部门' }],
roleIds: [{ required: true, message: '请选择所属角色' }],
},
});
const { queryParams, form, rules } = toRefs(data);

View File

@@ -34,6 +34,9 @@ import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import cn.dev33.satoken.annotation.SaIgnore;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.URLUtil;
import cn.hutool.extra.spring.SpringUtil;
import top.charles7c.cnadmin.common.config.properties.ContiNewAdminProperties;
@@ -73,10 +76,16 @@ public class ContiNewAdminApplication implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
String hostAddress = InetAddress.getLocalHost().getHostAddress();
Integer port = serverProperties.getPort();
String contextPath = serverProperties.getServlet().getContextPath();
String baseUrl = URLUtil.normalize(String.format("%s:%s%s", hostAddress, port, contextPath));
log.info("------------------------------------------------------");
log.info("{} backend service started successfully.", properties.getName());
log.info("后端 API 地址:http://{}:{}", hostAddress, serverProperties.getPort());
log.info("后端 API 文档http://{}:{}/doc.html", hostAddress, serverProperties.getPort());
log.info("后端 API 地址:{}", baseUrl);
Boolean docEnabled = Convert.toBool(SpringUtil.getProperty("springdoc.swagger-ui.enabled"));
if (Boolean.TRUE.equals(docEnabled)) {
log.info("后端 API 文档:{}/doc.html", baseUrl);
}
log.info("------------------------------------------------------");
}
}

View File

@@ -36,16 +36,18 @@ import com.wf.captcha.base.Captcha;
import cn.dev33.satoken.annotation.SaIgnore;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.lang.RegexPool;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.RandomUtil;
import top.charles7c.cnadmin.common.config.properties.CaptchaProperties;
import top.charles7c.cnadmin.common.config.properties.ContiNewAdminProperties;
import top.charles7c.cnadmin.common.constant.CacheConsts;
import top.charles7c.cnadmin.common.constant.RegexConsts;
import top.charles7c.cnadmin.common.model.vo.CaptchaVO;
import top.charles7c.cnadmin.common.model.vo.R;
import top.charles7c.cnadmin.common.util.*;
import top.charles7c.cnadmin.common.util.MailUtils;
import top.charles7c.cnadmin.common.util.RedisUtils;
import top.charles7c.cnadmin.common.util.TemplateUtils;
import top.charles7c.cnadmin.common.util.validate.CheckUtils;
/**
@@ -86,7 +88,7 @@ public class CaptchaController {
@Operation(summary = "获取邮箱验证码", description = "发送验证码到指定邮箱")
@GetMapping("/mail")
public R getMailCaptcha(
@NotBlank(message = "邮箱不能为空") @Pattern(regexp = RegexPool.EMAIL, message = "邮箱格式错误") String email)
@NotBlank(message = "邮箱不能为空") @Pattern(regexp = RegexConsts.EMAIL, message = "邮箱格式错误") String email)
throws MessagingException {
String limitKeyPrefix = CacheConsts.LIMIT_KEY_PREFIX;
String captchaKeyPrefix = CacheConsts.CAPTCHA_KEY_PREFIX;

View File

@@ -52,14 +52,14 @@ public class MenuController extends BaseController<MenuService, MenuVO, MenuVO,
@Override
@SaCheckPermission("system:menu:add")
protected R<Long> add(@Validated(BaseRequest.Add.class) @RequestBody MenuRequest request) {
public R<Long> add(@Validated(BaseRequest.Add.class) @RequestBody MenuRequest request) {
this.checkPath(request);
return super.add(request);
}
@Override
@SaCheckPermission("system:menu:update")
protected R update(@Validated(BaseRequest.Update.class) @RequestBody MenuRequest request, @PathVariable Long id) {
public R update(@Validated(BaseRequest.Update.class) @RequestBody MenuRequest request, @PathVariable Long id) {
this.checkPath(request);
return super.update(request, id);
}

View File

@@ -27,11 +27,10 @@ import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ReUtil;
import top.charles7c.cnadmin.common.constant.CacheConsts;
import top.charles7c.cnadmin.common.constant.RegExpConsts;
import top.charles7c.cnadmin.common.constant.RegexConsts;
import top.charles7c.cnadmin.common.model.vo.R;
import top.charles7c.cnadmin.common.util.ExceptionUtils;
import top.charles7c.cnadmin.common.util.RedisUtils;
@@ -41,7 +40,6 @@ import top.charles7c.cnadmin.common.util.validate.ValidationUtils;
import top.charles7c.cnadmin.system.model.request.UpdateBasicInfoRequest;
import top.charles7c.cnadmin.system.model.request.UpdateEmailRequest;
import top.charles7c.cnadmin.system.model.request.UpdatePasswordRequest;
import top.charles7c.cnadmin.system.model.request.UserRequest;
import top.charles7c.cnadmin.system.model.vo.AvatarVO;
import top.charles7c.cnadmin.system.service.UserService;
@@ -73,9 +71,7 @@ public class UserCenterController {
@Operation(summary = "修改基础信息", description = "修改用户基础信息")
@PatchMapping("/basic/info")
public R updateBasicInfo(@Validated @RequestBody UpdateBasicInfoRequest updateBasicInfoRequest) {
UserRequest userRequest = new UserRequest();
BeanUtil.copyProperties(updateBasicInfoRequest, userRequest);
userService.update(userRequest, LoginHelper.getUserId());
userService.updateBasicInfo(updateBasicInfoRequest, LoginHelper.getUserId());
return R.ok("修改成功");
}
@@ -88,7 +84,8 @@ public class UserCenterController {
String rawNewPassword =
ExceptionUtils.exToNull(() -> SecureUtils.decryptByRsaPrivateKey(updatePasswordRequest.getNewPassword()));
ValidationUtils.throwIfBlank(rawNewPassword, "新密码解密失败");
ValidationUtils.throwIf(!ReUtil.isMatch(RegExpConsts.PASSWORD, rawNewPassword), "密码长度 6 到 32 位,同时包含数字和字母");
ValidationUtils.throwIf(!ReUtil.isMatch(RegexConsts.PASSWORD, rawNewPassword),
"密码长度为 6 到 32 位,可以包含字母、数字、下划线,特殊字符,同时包含字母和数字");
// 修改密码
userService.updatePassword(rawOldPassword, rawNewPassword, LoginHelper.getUserId());

View File

@@ -53,7 +53,7 @@ public class UserController extends BaseController<UserService, UserVO, UserDeta
@Override
@SaCheckPermission("system:user:add")
protected R<Long> add(@Validated(BaseRequest.Add.class) @RequestBody UserRequest request) {
public R<Long> add(@Validated(BaseRequest.Add.class) @RequestBody UserRequest request) {
Long id = baseService.add(request);
return R.ok(String.format("新增成功,请牢记默认密码:%s", SysConsts.DEFAULT_PASSWORD), id);
}

View File

@@ -4,45 +4,44 @@ server:
port: 8000
--- ### 数据源配置
spring:
datasource:
type: com.zaxxer.hikari.HikariDataSource
## 动态数据源配置可配多主多从m1、s1...、纯粹多库mysql、oracle...、混合配置m1、s1、oracle...
dynamic:
# 是否启用 P6SpySQL 性能分析组件,默认 false该插件有性能损耗不建议生产环境使用
p6spy: true
# 设置默认的数据源或者数据源组(默认 master
primary: master
# 严格匹配数据源true 未匹配到指定数据源时抛异常false 使用默认数据源;默认 false
strict: false
datasource:
# 主库配置(可配多个,构成多主)
master:
url: jdbc:mysql://${DB_HOST:127.0.0.1}:${DB_PORT:3306}/${DB_NAME:continew_admin}?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&rewriteBatchedStatements=true&autoReconnect=true&maxReconnects=10&failOverReadOnly=false
username: ${DB_USER:root}
password: ${DB_PWD:123456}
driver-class-name: com.mysql.cj.jdbc.Driver
# 从库配置(可配多个,构成多从)
slave_1:
url: jdbc:mysql://${DB_HOST:127.0.0.1}:${DB_PORT:3306}/${DB_NAME:continew_admin}?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&rewriteBatchedStatements=true&autoReconnect=true&maxReconnects=10&failOverReadOnly=false
username:
password:
lazy: true
driver-class-name: com.mysql.cj.jdbc.Driver
type: ${spring.datasource.type}
# Hikari 连接池配置完整配置请参阅https://github.com/brettwooldridge/HikariCP
hikari:
# 最大连接数量(默认 10根据实际环境调整
# 注意:当连接达到上限,并且没有空闲连接可用时,获取连接将在超时前阻塞最多 connectionTimeout 毫秒
max-pool-size: 20
# 获取连接超时时间(默认 30000 毫秒30 秒)
connection-timeout: 30000
# 空闲连接最大存活时间(默认 600000 毫秒10 分钟)
idle-timeout: 600000
# 保持连接活动的频率,以防止它被数据库或网络基础设施超时。该值必须小于 maxLifetime默认 0禁用
keepaliveTime: 30000
# 连接最大生存时间(默认 1800000 毫秒30 分钟)
max-lifetime: 1800000
spring.datasource:
type: com.zaxxer.hikari.HikariDataSource
## 动态数据源配置可配多主多从m1、s1...、纯粹多库mysql、oracle...、混合配置m1、s1、oracle...
dynamic:
# 是否启用 P6SpySQL 性能分析组件,默认 false该插件有性能损耗不建议生产环境使用
p6spy: true
# 设置默认的数据源或者数据源组(默认 master
primary: master
# 严格匹配数据源true 未匹配到指定数据源时抛异常false 使用默认数据源;默认 false
strict: false
datasource:
# 主库配置(可配多个,构成多主)
master:
url: jdbc:mysql://${DB_HOST:127.0.0.1}:${DB_PORT:3306}/${DB_NAME:continew_admin}?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&rewriteBatchedStatements=true&autoReconnect=true&maxReconnects=10&failOverReadOnly=false
username: ${DB_USER:root}
password: ${DB_PWD:123456}
driver-class-name: com.mysql.cj.jdbc.Driver
# 从库配置(可配多个,构成多从)
slave_1:
url: jdbc:mysql://${DB_HOST:127.0.0.1}:${DB_PORT:3306}/${DB_NAME:continew_admin}?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&rewriteBatchedStatements=true&autoReconnect=true&maxReconnects=10&failOverReadOnly=false
username:
password:
lazy: true
driver-class-name: com.mysql.cj.jdbc.Driver
type: ${spring.datasource.type}
# Hikari 连接池配置完整配置请参阅https://github.com/brettwooldridge/HikariCP
hikari:
# 最大连接数量(默认 10根据实际环境调整
# 注意:当连接达到上限,并且没有空闲连接可用时,获取连接将在超时前阻塞最多 connectionTimeout 毫秒
max-pool-size: 20
# 获取连接超时时间(默认 30000 毫秒30 秒)
connection-timeout: 30000
# 空闲连接最大存活时间(默认 600000 毫秒10 分钟)
idle-timeout: 600000
# 保持连接活动的频率,以防止它被数据库或网络基础设施超时。该值必须小于 maxLifetime默认 0禁用
keepaliveTime: 30000
# 连接最大生存时间(默认 1800000 毫秒30 分钟)
max-lifetime: 1800000
--- ### Liquibase 配置
spring.liquibase:
@@ -68,21 +67,20 @@ spring:
ssl: false
--- ### 邮件配置
spring:
mail:
# 根据需要更换
host: smtp.126.com
port: 465
username: 你的邮箱
password: 你的邮箱授权码
default-encoding: utf-8
properties:
mail:
smtp:
auth: true
socketFactory:
class: javax.net.ssl.SSLSocketFactory
port: 465
spring.mail:
# 根据需要更换
host: smtp.126.com
port: 465
username: 你的邮箱
password: 你的邮箱授权码
default-encoding: utf-8
properties:
mail:
smtp:
auth: true
socketFactory:
class: javax.net.ssl.SSLSocketFactory
port: 465
--- ### 验证码配置
captcha:
@@ -109,26 +107,24 @@ captcha:
# 模板路径
templatePath: mail/captcha.ftl
--- ### 安全配置
security:
# 排除路径配置
excludes:
# 静态资源
- /*.html
- /**/*.html
- /**/*.css
- /**/*.js
- /webSocket/**
# 接口文档相关资源
- /favicon.ico
- /doc.html
- /webjars/**
- /swagger-ui/**
- /swagger-resources/**
- /*/api-docs/**
# 本地存储资源
- /avatar/**
- /file/**
--- ### 安全配置-排除路径配置
security.excludes:
# 静态资源
- /*.html
- /**/*.html
- /**/*.css
- /**/*.js
- /webSocket/**
# 接口文档相关资源
- /favicon.ico
- /doc.html
- /webjars/**
- /swagger-ui/**
- /swagger-resources/**
- /*/api-docs/**
# 本地存储资源
- /avatar/**
- /file/**
--- ### 非对称加密配置(例如:密码加密传输,前端公钥加密,后端私钥解密;在线生成 RSA 密钥对http://web.chacuo.net/netrsakeypair
rsa:
@@ -141,14 +137,13 @@ springdoc:
enabled: true
--- ### 文件上传配置
spring:
servlet:
multipart:
enabled: true
# 单文件上传大小限制
max-file-size: 10MB
# 单次总上传文件大小限制
max-request-size: 20MB
spring.servlet:
multipart:
enabled: true
# 单文件上传大小限制
max-file-size: 10MB
# 单次总上传文件大小限制
max-request-size: 20MB
--- ### 本地存储配置
local-storage:

View File

@@ -4,45 +4,44 @@ server:
port: 18000
--- ### 数据源配置
spring:
datasource:
type: com.zaxxer.hikari.HikariDataSource
## 动态数据源配置可配多主多从m1、s1...、纯粹多库mysql、oracle...、混合配置m1、s1、oracle...
dynamic:
# 是否启用 P6SpySQL 性能分析组件,默认 false该插件有性能损耗不建议生产环境使用
p6spy: false
# 设置默认的数据源或者数据源组(默认 master
primary: master
# 严格匹配数据源true 未匹配到指定数据源时抛异常false 使用默认数据源;默认 false
strict: false
datasource:
# 主库配置(可配多个,构成多主)
master:
url: jdbc:mysql://${DB_HOST:localhost}:${DB_PORT:3306}/${DB_NAME:continew_admin}?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&rewriteBatchedStatements=true&autoReconnect=true&maxReconnects=10&failOverReadOnly=false
username: ${DB_USER:root}
password: ${DB_PWD:123456}
driver-class-name: com.mysql.cj.jdbc.Driver
# 从库配置(可配多个,构成多从)
slave_1:
url: jdbc:mysql://${DB_HOST:localhost}:${DB_PORT:3306}/${DB_NAME:continew_admin}?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&rewriteBatchedStatements=true&autoReconnect=true&maxReconnects=10&failOverReadOnly=false
username:
password:
lazy: true
driver-class-name: com.mysql.cj.jdbc.Driver
type: ${spring.datasource.type}
# Hikari 连接池配置完整配置请参阅https://github.com/brettwooldridge/HikariCP
hikari:
# 最大连接数量(默认 10根据实际环境调整
# 注意:当连接达到上限,并且没有空闲连接可用时,获取连接将在超时前阻塞最多 connectionTimeout 毫秒
max-pool-size: 20
# 获取连接超时时间(默认 30000 毫秒30 秒)
connection-timeout: 30000
# 空闲连接最大存活时间(默认 600000 毫秒10 分钟)
idle-timeout: 600000
# 保持连接活动的频率,以防止它被数据库或网络基础设施超时。该值必须小于 maxLifetime默认 0禁用
keepaliveTime: 30000
# 连接最大生存时间(默认 1800000 毫秒30 分钟)
max-lifetime: 1800000
spring.datasource:
type: com.zaxxer.hikari.HikariDataSource
## 动态数据源配置可配多主多从m1、s1...、纯粹多库mysql、oracle...、混合配置m1、s1、oracle...
dynamic:
# 是否启用 P6SpySQL 性能分析组件,默认 false该插件有性能损耗不建议生产环境使用
p6spy: false
# 设置默认的数据源或者数据源组(默认 master
primary: master
# 严格匹配数据源true 未匹配到指定数据源时抛异常false 使用默认数据源;默认 false
strict: false
datasource:
# 主库配置(可配多个,构成多主)
master:
url: jdbc:mysql://${DB_HOST:localhost}:${DB_PORT:3306}/${DB_NAME:continew_admin}?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&rewriteBatchedStatements=true&autoReconnect=true&maxReconnects=10&failOverReadOnly=false
username: ${DB_USER:root}
password: ${DB_PWD:123456}
driver-class-name: com.mysql.cj.jdbc.Driver
# 从库配置(可配多个,构成多从)
slave_1:
url: jdbc:mysql://${DB_HOST:localhost}:${DB_PORT:3306}/${DB_NAME:continew_admin}?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&rewriteBatchedStatements=true&autoReconnect=true&maxReconnects=10&failOverReadOnly=false
username:
password:
lazy: true
driver-class-name: com.mysql.cj.jdbc.Driver
type: ${spring.datasource.type}
# Hikari 连接池配置完整配置请参阅https://github.com/brettwooldridge/HikariCP
hikari:
# 最大连接数量(默认 10根据实际环境调整
# 注意:当连接达到上限,并且没有空闲连接可用时,获取连接将在超时前阻塞最多 connectionTimeout 毫秒
max-pool-size: 20
# 获取连接超时时间(默认 30000 毫秒30 秒)
connection-timeout: 30000
# 空闲连接最大存活时间(默认 600000 毫秒10 分钟)
idle-timeout: 600000
# 保持连接活动的频率,以防止它被数据库或网络基础设施超时。该值必须小于 maxLifetime默认 0禁用
keepaliveTime: 30000
# 连接最大生存时间(默认 1800000 毫秒30 分钟)
max-lifetime: 1800000
--- ### Liquibase 配置
spring.liquibase:
@@ -68,21 +67,20 @@ spring:
ssl: false
--- ### 邮件配置
spring:
mail:
# 根据需要更换
host: smtp.126.com
port: 465
username: 你的邮箱
password: 你的邮箱授权码
default-encoding: utf-8
properties:
mail:
smtp:
auth: true
socketFactory:
class: javax.net.ssl.SSLSocketFactory
port: 465
spring.mail:
# 根据需要更换
host: smtp.126.com
port: 465
username: 你的邮箱
password: 你的邮箱授权码
default-encoding: utf-8
properties:
mail:
smtp:
auth: true
socketFactory:
class: javax.net.ssl.SSLSocketFactory
port: 465
--- ### 验证码配置
captcha:
@@ -109,19 +107,17 @@ captcha:
# 模板路径
templatePath: mail/captcha.ftl
--- ### 安全配置
security:
# 排除路径配置
excludes:
# 静态资源
- /*.html
- /**/*.html
- /**/*.css
- /**/*.js
- /webSocket/**
# 本地存储资源
- /avatar/**
- /file/**
--- ### 安全配置-排除路径配置
security.excludes:
# 静态资源
- /*.html
- /**/*.html
- /**/*.css
- /**/*.js
- /webSocket/**
# 本地存储资源
- /avatar/**
- /file/**
--- ### 非对称加密配置(例如:密码加密传输,前端公钥加密,后端私钥解密;在线生成 RSA 密钥对http://web.chacuo.net/netrsakeypair
rsa:
@@ -132,16 +128,19 @@ rsa:
springdoc:
swagger-ui:
enabled: false
## 接口文档增强配置
knife4j:
# 开启生产环境屏蔽
production: true
--- ### 文件上传配置
spring:
servlet:
multipart:
enabled: true
# 单文件上传大小限制
max-file-size: 10MB
# 单次总上传文件大小限制
max-request-size: 20MB
spring.servlet:
multipart:
enabled: true
# 单文件上传大小限制
max-file-size: 10MB
# 单次总上传文件大小限制
max-request-size: 20MB
--- ### 本地存储配置
local-storage:

View File

@@ -5,7 +5,7 @@ continew-admin:
# 应用名称
appName: continew-admin
# 版本
version: 1.0.0
version: 1.0.1
# 描述
description: ContiNew Admin 中后台管理框架/脚手架Continue New Admin持续以最新流行技术栈构建拥抱变化迭代优化。
# URL
@@ -69,7 +69,7 @@ knife4j:
# 是否自定义 footer默认 false 非自定义)
enable-footer-custom: true
# 自定义 footer 内容,支持 Markdown 语法
footer-custom-content: 'Copyright © 2022-present [Charles7c](${continew-admin.author.url})&nbsp;⋅&nbsp;[ContiNew Admin](https://github.com/Charles7c/continew-admin) v${continew-admin.version}'
footer-custom-content: 'Copyright © 2022-present [${continew-admin.author.name}](${continew-admin.author.url})&nbsp;⋅&nbsp;[${continew-admin.name}](${continew-admin.url}) v${continew-admin.version}'
--- ### Sa-Token 配置
sa-token:
@@ -187,6 +187,12 @@ spring:
# 允许反序列化不存在的属性
FAIL_ON_UNKNOWN_PROPERTIES: false
--- ### 健康检查配置
management.health:
mail:
# 关闭邮箱健康检查(邮箱配置错误或邮箱服务器不可用时,健康检查会报错)
enabled: false
--- ### 线程池配置
thread-pool:
# 是否启用线程池

View File

@@ -3,11 +3,11 @@
-- changeset Charles7c:1
CREATE TABLE IF NOT EXISTS `sys_menu` (
`id` bigint(20) UNSIGNED AUTO_INCREMENT COMMENT 'ID',
`title` varchar(255) NOT NULL COMMENT '菜单标题',
`title` varchar(50) NOT NULL COMMENT '菜单标题',
`parent_id` bigint(20) UNSIGNED DEFAULT 0 COMMENT '上级菜单ID',
`type` tinyint(1) UNSIGNED DEFAULT 1 COMMENT '菜单类型1目录2菜单3按钮',
`path` varchar(512) DEFAULT NULL COMMENT '路由地址',
`name` varchar(255) DEFAULT NULL COMMENT '组件名称',
`name` varchar(50) DEFAULT NULL COMMENT '组件名称',
`component` varchar(255) DEFAULT NULL COMMENT '组件路径',
`icon` varchar(255) DEFAULT NULL COMMENT '菜单图标',
`is_external` bit(1) DEFAULT b'0' COMMENT '是否外链',
@@ -28,7 +28,7 @@ CREATE TABLE IF NOT EXISTS `sys_menu` (
CREATE TABLE IF NOT EXISTS `sys_dept` (
`id` bigint(20) UNSIGNED AUTO_INCREMENT COMMENT 'ID',
`name` varchar(255) NOT NULL COMMENT '部门名称',
`name` varchar(50) NOT NULL COMMENT '部门名称',
`parent_id` bigint(20) UNSIGNED DEFAULT 0 COMMENT '上级部门ID',
`ancestors` varchar(512) DEFAULT '' COMMENT '祖级列表',
`description` varchar(512) DEFAULT NULL COMMENT '描述',
@@ -47,8 +47,8 @@ CREATE TABLE IF NOT EXISTS `sys_dept` (
CREATE TABLE IF NOT EXISTS `sys_role` (
`id` bigint(20) UNSIGNED AUTO_INCREMENT COMMENT 'ID',
`name` varchar(255) NOT NULL COMMENT '角色名称',
`code` varchar(255) NOT NULL COMMENT '角色编码',
`name` varchar(50) NOT NULL COMMENT '角色名称',
`code` varchar(50) NOT NULL COMMENT '角色编码',
`data_scope` tinyint(1) DEFAULT 4 COMMENT '数据权限1全部数据权限2本部门及以下数据权限3本部门数据权限4仅本人数据权限5自定义数据权限',
`description` varchar(512) DEFAULT NULL COMMENT '描述',
`sort` int(11) UNSIGNED DEFAULT 999 COMMENT '角色排序',
@@ -77,12 +77,12 @@ CREATE TABLE IF NOT EXISTS `sys_role_dept` (
CREATE TABLE IF NOT EXISTS `sys_user` (
`id` bigint(20) UNSIGNED AUTO_INCREMENT COMMENT 'ID',
`username` varchar(255) NOT NULL COMMENT '用户名',
`nickname` varchar(255) DEFAULT NULL COMMENT '昵称',
`username` varchar(50) NOT NULL COMMENT '用户名',
`nickname` varchar(50) DEFAULT NULL COMMENT '昵称',
`password` varchar(255) DEFAULT NULL COMMENT '密码',
`gender` tinyint(1) UNSIGNED DEFAULT 0 COMMENT '性别0未知12',
`email` varchar(255) DEFAULT NULL COMMENT '邮箱',
`phone` varchar(255) DEFAULT NULL COMMENT '手机号码',
`email` varchar(100) DEFAULT NULL COMMENT '邮箱',
`phone` varchar(50) DEFAULT NULL COMMENT '手机号码',
`avatar` varchar(255) DEFAULT NULL COMMENT '头像地址',
`description` varchar(512) DEFAULT NULL COMMENT '描述',
`status` tinyint(1) UNSIGNED DEFAULT 1 COMMENT '状态1启用2禁用',
@@ -110,7 +110,7 @@ CREATE TABLE IF NOT EXISTS `sys_user_role` (
CREATE TABLE IF NOT EXISTS `sys_log` (
`id` bigint(20) UNSIGNED AUTO_INCREMENT COMMENT 'ID',
`description` varchar(255) NOT NULL COMMENT '日志描述',
`module` varchar(255) NOT NULL COMMENT '所属模块',
`module` varchar(50) NOT NULL COMMENT '所属模块',
`request_url` varchar(512) NOT NULL COMMENT '请求URL',
`request_method` varchar(10) NOT NULL COMMENT '请求方式',
`request_headers` text DEFAULT NULL COMMENT '请求头',
@@ -120,9 +120,9 @@ CREATE TABLE IF NOT EXISTS `sys_log` (
`response_body` mediumtext DEFAULT NULL COMMENT '响应体',
`elapsed_time` bigint(20) UNSIGNED NOT NULL COMMENT '请求耗时ms',
`status` tinyint(1) UNSIGNED DEFAULT 1 COMMENT '操作状态1成功2失败',
`client_ip` varchar(255) DEFAULT NULL COMMENT '客户端IP',
`location` varchar(512) DEFAULT NULL COMMENT 'IP归属地',
`browser` varchar(255) DEFAULT NULL COMMENT '浏览器',
`client_ip` varchar(100) DEFAULT NULL COMMENT '客户端IP',
`location` varchar(255) DEFAULT NULL COMMENT 'IP归属地',
`browser` varchar(100) DEFAULT NULL COMMENT '浏览器',
`error_msg` text DEFAULT NULL COMMENT '错误信息',
`exception_detail` mediumtext DEFAULT NULL COMMENT '异常详情',
`create_user` bigint(20) UNSIGNED DEFAULT NULL COMMENT '创建人',

View File

@@ -59,7 +59,7 @@ limitations under the License.
<hutool.version>5.8.11</hutool.version>
<!-- ### 基础环境相关 ### -->
<revision>1.0.0</revision>
<revision>1.0.1</revision>
<java.version>1.8</java.version>
<spotless.version>2.28.0</spotless.version>
<maven.compiler.source>8</maven.compiler.source>