mirror of
				https://github.com/continew-org/continew-admin.git
				synced 2025-10-31 22:57:17 +08:00 
			
		
		
		
	优化:🔥 深度优化后端 CRUD 公共组件,并抽取前端下载功能到 CRUD 公共组件
1. 后端抽取导出功能到 CRUD 公共组件 2. 查询列表及导出接口支持排序参数 3. 深度优化 BaseServiceImpl 中的 CRUD 公共实现 4. 前端抽取公共下载组件 5. 优化部分细节并修复部分错误
This commit is contained in:
		| @@ -72,7 +72,7 @@ yarn dev | ||||
| | :----------------------------------------------------------- | :----------- | :----------------------------------------------------------- | | ||||
| | [Vue](https://cn.vuejs.org/)                                 | 3.2.45       | 渐进式 JavaScript 框架,易学易用,性能出色,适用场景丰富的 Web 前端框架。 | | ||||
| | [TypeScript](https://www.typescriptlang.org/zh/)             | 4.9.4        | TypeScript 是微软开发的一个开源的编程语言,通过在 JavaScript 的基础上添加静态类型定义构建而成。 | | ||||
| | [Arco Design Pro Vue](http://pro.arco.design/)               | 2.6.0        | 基于 Arco Design Vue 组件库的开箱即用的中后台前端解决方案。  | | ||||
| | [Arco Design Pro Vue](http://pro.arco.design/)               | 2.6.1        | 基于 Arco Design Vue 组件库的开箱即用的中后台前端解决方案。  | | ||||
| | [Spring Boot](https://spring.io/projects/spring-boot)        | 2.7.8        | 简化新 Spring 应用的初始搭建以及开发过程。                   | | ||||
| | [Undertow](https://undertow.io/)                             | 2.2.22.Final | 采用 Java 开发的灵活的高性能 Web 服务器,提供包括阻塞和基于 NIO 的非堵塞机制。 | | ||||
| | [Sa-Token + JWT](https://sa-token.dev33.cn/)                 | 1.34.0       | 轻量级 Java 权限认证框架,让鉴权变得简单、优雅。             | | ||||
| @@ -229,6 +229,7 @@ continew-admin  # 全局通用项目配置及依赖版本管理 | ||||
|   │                │  ├─ query        # 公共查询条件 | ||||
|   │                │  ├─ request      # 公共请求对象 | ||||
|   │                │  └─ vo           # 公共 VO(View Object) | ||||
|   │                ├─ service      # 公共业务接口 | ||||
|   │                └─ util         # 公共工具类 | ||||
|   │                  ├─ helper        # 公共 Helper(助手) | ||||
|   │                  ├─ holder        # 公共 Holder(持有者) | ||||
|   | ||||
| @@ -37,7 +37,7 @@ public @interface CrudRequestMapping { | ||||
|     /** | ||||
|      * API 列表 | ||||
|      */ | ||||
|     Api[] api() default {Api.PAGE, Api.GET, Api.CREATE, Api.UPDATE, Api.DELETE}; | ||||
|     Api[] api() default {Api.PAGE, Api.GET, Api.CREATE, Api.UPDATE, Api.DELETE, Api.EXPORT}; | ||||
|  | ||||
|     /** | ||||
|      * API 枚举 | ||||
| @@ -70,6 +70,10 @@ public @interface CrudRequestMapping { | ||||
|         /** | ||||
|          * 删除 | ||||
|          */ | ||||
|         DELETE,; | ||||
|         DELETE, | ||||
|         /** | ||||
|          * 导出 | ||||
|          */ | ||||
|         EXPORT,; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -18,6 +18,8 @@ package top.charles7c.cnadmin.common.base; | ||||
|  | ||||
| import java.util.List; | ||||
|  | ||||
| import javax.servlet.http.HttpServletResponse; | ||||
|  | ||||
| import lombok.NoArgsConstructor; | ||||
|  | ||||
| import io.swagger.v3.oas.annotations.Operation; | ||||
| @@ -29,6 +31,7 @@ import org.springframework.validation.annotation.Validated; | ||||
| import org.springframework.web.bind.annotation.*; | ||||
|  | ||||
| import top.charles7c.cnadmin.common.model.query.PageQuery; | ||||
| import top.charles7c.cnadmin.common.model.query.SortQuery; | ||||
| import top.charles7c.cnadmin.common.model.vo.PageDataVO; | ||||
| import top.charles7c.cnadmin.common.model.vo.R; | ||||
|  | ||||
| @@ -76,13 +79,15 @@ public abstract class BaseController<S extends BaseService<V, D, Q, C>, V, D, Q, | ||||
|      * | ||||
|      * @param query | ||||
|      *            查询条件 | ||||
|      * @param sortQuery | ||||
|      *            排序查询条件 | ||||
|      * @return 列表信息 | ||||
|      */ | ||||
|     @Operation(summary = "查询列表") | ||||
|     @ResponseBody | ||||
|     @GetMapping("/all") | ||||
|     protected R<List<V>> list(@Validated Q query) { | ||||
|         List<V> list = baseService.list(query); | ||||
|     @GetMapping("/list") | ||||
|     protected R<List<V>> list(@Validated Q query, @Validated SortQuery sortQuery) { | ||||
|         List<V> list = baseService.list(query, sortQuery); | ||||
|         return R.ok(list); | ||||
|     } | ||||
|  | ||||
| @@ -147,4 +152,20 @@ public abstract class BaseController<S extends BaseService<V, D, Q, C>, V, D, Q, | ||||
|         baseService.delete(ids); | ||||
|         return R.ok("删除成功"); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 导出 | ||||
|      * | ||||
|      * @param query | ||||
|      *            查询条件 | ||||
|      * @param sortQuery | ||||
|      *            排序查询条件 | ||||
|      * @param response | ||||
|      *            响应对象 | ||||
|      */ | ||||
|     @Operation(summary = "导出数据") | ||||
|     @GetMapping("/export") | ||||
|     protected void export(@Validated Q query, @Validated SortQuery sortQuery, HttpServletResponse response) { | ||||
|         baseService.export(query, sortQuery, response); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -18,7 +18,10 @@ package top.charles7c.cnadmin.common.base; | ||||
|  | ||||
| import java.util.List; | ||||
|  | ||||
| import javax.servlet.http.HttpServletResponse; | ||||
|  | ||||
| import top.charles7c.cnadmin.common.model.query.PageQuery; | ||||
| import top.charles7c.cnadmin.common.model.query.SortQuery; | ||||
| import top.charles7c.cnadmin.common.model.vo.PageDataVO; | ||||
|  | ||||
| /** | ||||
| @@ -53,9 +56,11 @@ public interface BaseService<V, D, Q, C extends BaseRequest> { | ||||
|      * | ||||
|      * @param query | ||||
|      *            查询条件 | ||||
|      * @param sortQuery | ||||
|      *            排序查询条件 | ||||
|      * @return 列表信息 | ||||
|      */ | ||||
|     List<V> list(Q query); | ||||
|     List<V> list(Q query, SortQuery sortQuery); | ||||
|  | ||||
|     /** | ||||
|      * 查看详情 | ||||
| @@ -90,4 +95,16 @@ public interface BaseService<V, D, Q, C extends BaseRequest> { | ||||
|      *            ID 列表 | ||||
|      */ | ||||
|     void delete(List<Long> ids); | ||||
|  | ||||
|     /** | ||||
|      * 导出 | ||||
|      * | ||||
|      * @param query | ||||
|      *            查询条件 | ||||
|      * @param sortQuery | ||||
|      *            排序查询条件 | ||||
|      * @param response | ||||
|      *            响应对象 | ||||
|      */ | ||||
|     void export(Q query, SortQuery sortQuery, HttpServletResponse response); | ||||
| } | ||||
|   | ||||
| @@ -16,20 +16,35 @@ | ||||
|  | ||||
| package top.charles7c.cnadmin.common.base; | ||||
|  | ||||
| import java.util.Collections; | ||||
| import java.util.List; | ||||
|  | ||||
| import javax.servlet.http.HttpServletResponse; | ||||
|  | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.springframework.data.domain.Sort; | ||||
| import org.springframework.transaction.annotation.Transactional; | ||||
|  | ||||
| import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; | ||||
| import com.baomidou.mybatisplus.core.mapper.BaseMapper; | ||||
| import com.baomidou.mybatisplus.core.metadata.IPage; | ||||
| import com.baomidou.mybatisplus.core.metadata.TableInfo; | ||||
| import com.baomidou.mybatisplus.core.metadata.TableInfoHelper; | ||||
| import com.baomidou.mybatisplus.core.toolkit.Assert; | ||||
| import com.baomidou.mybatisplus.core.toolkit.ReflectionKit; | ||||
|  | ||||
| import cn.hutool.core.bean.BeanUtil; | ||||
| import cn.hutool.core.collection.CollUtil; | ||||
| import cn.hutool.core.convert.Convert; | ||||
| import cn.hutool.core.util.StrUtil; | ||||
| import cn.hutool.extra.spring.SpringUtil; | ||||
|  | ||||
| import top.charles7c.cnadmin.common.model.query.PageQuery; | ||||
| import top.charles7c.cnadmin.common.model.query.SortQuery; | ||||
| import top.charles7c.cnadmin.common.model.vo.PageDataVO; | ||||
| import top.charles7c.cnadmin.common.service.CommonUserService; | ||||
| import top.charles7c.cnadmin.common.util.ExcelUtils; | ||||
| import top.charles7c.cnadmin.common.util.ExceptionUtils; | ||||
| import top.charles7c.cnadmin.common.util.helper.QueryHelper; | ||||
| import top.charles7c.cnadmin.common.util.validate.CheckUtils; | ||||
|  | ||||
| @@ -65,32 +80,39 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T, V, D, Q, C ext | ||||
|     public PageDataVO<V> page(Q query, PageQuery pageQuery) { | ||||
|         QueryWrapper<T> queryWrapper = QueryHelper.build(query); | ||||
|         IPage<T> page = baseMapper.selectPage(pageQuery.toPage(), queryWrapper); | ||||
|         return PageDataVO.build(page, voClass); | ||||
|         PageDataVO<V> pageDataVO = PageDataVO.build(page, voClass); | ||||
|         pageDataVO.getList().forEach(this::fill); | ||||
|         return pageDataVO; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public List<V> list(Q query) { | ||||
|         QueryWrapper<T> queryWrapper = QueryHelper.build(query); | ||||
|         List<T> entityList = baseMapper.selectList(queryWrapper); | ||||
|         return BeanUtil.copyToList(entityList, voClass); | ||||
|     public List<V> list(Q query, SortQuery sortQuery) { | ||||
|         List<V> list = this.list(query, sortQuery, voClass); | ||||
|         list.forEach(this::fill); | ||||
|         return list; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public D get(Long id) { | ||||
|         T entity = this.getById(id); | ||||
|         return BeanUtil.copyProperties(entity, detailVoClass); | ||||
|         D detailVO = BeanUtil.copyProperties(entity, detailVoClass); | ||||
|         this.fillDetail(detailVO); | ||||
|         return detailVO; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 新增 | ||||
|      * | ||||
|      * @param request | ||||
|      *            创建信息 | ||||
|      * @return 自增 ID | ||||
|      */ | ||||
|     @Override | ||||
|     @Transactional(rollbackFor = Exception.class) | ||||
|     public abstract Long create(C request); | ||||
|     public Long create(C request) { | ||||
|         if (request == null) { | ||||
|             return 0L; | ||||
|         } | ||||
|         // 保存信息 | ||||
|         T entity = BeanUtil.copyProperties(request, entityClass); | ||||
|         baseMapper.insert(entity); | ||||
|         TableInfo tableInfo = TableInfoHelper.getTableInfo(entityClass); | ||||
|         Object idValue = tableInfo.getPropertyValue(entity, this.currentEntityIdName()); | ||||
|         return Convert.toLong(idValue); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     @Transactional(rollbackFor = Exception.class) | ||||
| @@ -105,6 +127,35 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T, V, D, Q, C ext | ||||
|         baseMapper.deleteBatchIds(ids); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void export(Q query, SortQuery sortQuery, HttpServletResponse response) { | ||||
|         List<D> list = this.list(query, sortQuery, detailVoClass); | ||||
|         list.forEach(this::fillDetail); | ||||
|         ExcelUtils.export(list, "导出数据", detailVoClass, response); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 查询列表 | ||||
|      * | ||||
|      * @param query | ||||
|      *            查询条件 | ||||
|      * @param sortQuery | ||||
|      *            排序查询条件 | ||||
|      * @param targetClass | ||||
|      *            指定类型 | ||||
|      * @return 列表信息 | ||||
|      */ | ||||
|     protected <E> List<E> list(Q query, SortQuery sortQuery, Class<E> targetClass) { | ||||
|         QueryWrapper<T> queryWrapper = QueryHelper.build(query); | ||||
|         // 设置排序 | ||||
|         Sort sort = sortQuery.getSort(); | ||||
|         for (Sort.Order order : sort) { | ||||
|             queryWrapper.orderBy(order != null, order.isAscending(), StrUtil.toUnderlineCase(order.getProperty())); | ||||
|         } | ||||
|         List<T> entityList = baseMapper.selectList(queryWrapper); | ||||
|         return CollUtil.isNotEmpty(entityList) ? BeanUtil.copyToList(entityList, targetClass) : Collections.emptyList(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 根据 ID 查询 | ||||
|      * | ||||
| @@ -118,6 +169,57 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T, V, D, Q, C ext | ||||
|         return entity; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 填充数据 | ||||
|      * | ||||
|      * @param baseObj | ||||
|      *            待填充列表信息 | ||||
|      */ | ||||
|     protected void fill(Object baseObj) { | ||||
|         if (baseObj instanceof BaseVO) { | ||||
|             BaseVO baseVO = (BaseVO)baseObj; | ||||
|             Long createUser = baseVO.getCreateUser(); | ||||
|             if (createUser == null) { | ||||
|                 return; | ||||
|             } | ||||
|             CommonUserService userService = SpringUtil.getBean(CommonUserService.class); | ||||
|             baseVO.setCreateUserString(ExceptionUtils.exToNull(() -> userService.getNicknameById(createUser))); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 填充详情数据 | ||||
|      * | ||||
|      * @param detailObj | ||||
|      *            待填充详情信息 | ||||
|      */ | ||||
|     protected void fillDetail(Object detailObj) { | ||||
|         if (detailObj instanceof BaseDetailVO) { | ||||
|             BaseDetailVO detailVO = (BaseDetailVO)detailObj; | ||||
|             this.fill(detailVO); | ||||
|  | ||||
|             Long updateUser = detailVO.getUpdateUser(); | ||||
|             if (updateUser == null) { | ||||
|                 return; | ||||
|             } | ||||
|             CommonUserService userService = SpringUtil.getBean(CommonUserService.class); | ||||
|             detailVO.setUpdateUserString(ExceptionUtils.exToNull(() -> userService.getNicknameById(updateUser))); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取实体类 ID 名称 | ||||
|      * | ||||
|      * @return 实体类 ID 名称 | ||||
|      */ | ||||
|     protected String currentEntityIdName() { | ||||
|         TableInfo tableInfo = TableInfoHelper.getTableInfo(entityClass); | ||||
|         Assert.notNull(tableInfo, "error: can not execute. because can not find cache of TableInfo for entity!"); | ||||
|         String keyProperty = tableInfo.getKeyProperty(); | ||||
|         Assert.notEmpty(keyProperty, "error: can not execute. because can not find column for id from entity!"); | ||||
|         return keyProperty; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取实体类 Class 对象 | ||||
|      * | ||||
|   | ||||
| @@ -16,14 +16,13 @@ | ||||
|  | ||||
| package top.charles7c.cnadmin.common.model.query; | ||||
|  | ||||
| import java.io.Serializable; | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
| import javax.validation.constraints.Min; | ||||
|  | ||||
| import lombok.Data; | ||||
|  | ||||
| import io.swagger.v3.oas.annotations.media.Schema; | ||||
|  | ||||
| import org.hibernate.validator.constraints.Range; | ||||
| import org.springdoc.api.annotations.ParameterObject; | ||||
| import org.springframework.data.domain.Sort; | ||||
|  | ||||
| @@ -32,7 +31,6 @@ import com.baomidou.mybatisplus.core.metadata.OrderItem; | ||||
| import com.baomidou.mybatisplus.extension.plugins.pagination.Page; | ||||
|  | ||||
| import cn.hutool.core.collection.CollUtil; | ||||
| import cn.hutool.core.util.ArrayUtil; | ||||
| import cn.hutool.core.util.StrUtil; | ||||
|  | ||||
| /** | ||||
| @@ -44,7 +42,7 @@ import cn.hutool.core.util.StrUtil; | ||||
| @Data | ||||
| @ParameterObject | ||||
| @Schema(description = "分页查询条件") | ||||
| public class PageQuery implements Serializable { | ||||
| public class PageQuery extends SortQuery { | ||||
|  | ||||
|     private static final long serialVersionUID = 1L; | ||||
|  | ||||
| @@ -52,60 +50,44 @@ public class PageQuery implements Serializable { | ||||
|      * 页码 | ||||
|      */ | ||||
|     @Schema(description = "页码") | ||||
|     private int page; | ||||
|     @Min(value = 1, message = "页码最小值为 1") | ||||
|     private Integer page; | ||||
|  | ||||
|     /** | ||||
|      * 每页记录数 | ||||
|      * 每页条数 | ||||
|      */ | ||||
|     @Schema(description = "每页记录数") | ||||
|     private int size; | ||||
|  | ||||
|     /** | ||||
|      * 排序条件 | ||||
|      */ | ||||
|     @Schema(description = "排序条件", example = "sort=published,desc&sort=title,asc") | ||||
|     private String[] sort; | ||||
|     @Schema(description = "每页条数") | ||||
|     @Range(min = 1, max = 1000, message = "每页条数(取值范围 1-1000)") | ||||
|     private Integer size; | ||||
|  | ||||
|     /** 默认页码:1 */ | ||||
|     private static final int DEFAULT_PAGE = 1; | ||||
|  | ||||
|     /** 默认每页记录数:10 */ | ||||
|     /** 默认每页条数:10 */ | ||||
|     private static final int DEFAULT_SIZE = 10; | ||||
|     private static final String DELIMITER = ","; | ||||
|     /** 默认每页最大条数:1000 */ | ||||
|     private static final int DEFAULT_MAX_SIZE = 1000; | ||||
|  | ||||
|     public PageQuery() { | ||||
|         this.page = DEFAULT_PAGE; | ||||
|         this(DEFAULT_PAGE, DEFAULT_SIZE); | ||||
|     } | ||||
|  | ||||
|     public PageQuery(Integer page, Integer size) { | ||||
|         this.setPage(page); | ||||
|         this.setSize(size); | ||||
|     } | ||||
|  | ||||
|     public void setPage(Integer page) { | ||||
|         this.page = page == null ? DEFAULT_PAGE : page; | ||||
|     } | ||||
|  | ||||
|     public void setSize(Integer size) { | ||||
|         if (size == null) { | ||||
|             this.size = DEFAULT_SIZE; | ||||
|     } | ||||
|  | ||||
|     public int getPage() { | ||||
|         return page < 0 ? DEFAULT_PAGE : page; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 解析排序条件为 Spring 分页排序实体 | ||||
|      * | ||||
|      * @return Spring 分页排序实体 | ||||
|      */ | ||||
|     public Sort getSort() { | ||||
|         if (ArrayUtil.isEmpty(sort)) { | ||||
|             return Sort.unsorted(); | ||||
|         } | ||||
|  | ||||
|         List<Sort.Order> orders = new ArrayList<>(sort.length); | ||||
|         if (sort[0].contains(DELIMITER)) { | ||||
|             // e.g "sort=published,desc&sort=title,asc" | ||||
|             for (String s : sort) { | ||||
|                 String[] sortArr = s.split(DELIMITER); | ||||
|                 Sort.Order order = new Sort.Order(Sort.Direction.valueOf(sortArr[1].toUpperCase()), sortArr[0]); | ||||
|                 orders.add(order); | ||||
|             } | ||||
|         } else if (size > DEFAULT_MAX_SIZE) { | ||||
|             this.size = DEFAULT_MAX_SIZE; | ||||
|         } else { | ||||
|             // e.g "sort=published,desc" | ||||
|             Sort.Order order = new Sort.Order(Sort.Direction.valueOf(sort[1].toUpperCase()), sort[0]); | ||||
|             orders.add(order); | ||||
|             this.size = size; | ||||
|         } | ||||
|         return Sort.by(orders); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|   | ||||
| @@ -0,0 +1,80 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
|  | ||||
| package top.charles7c.cnadmin.common.model.query; | ||||
|  | ||||
| import java.io.Serializable; | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
|  | ||||
| import lombok.Data; | ||||
|  | ||||
| import io.swagger.v3.oas.annotations.media.Schema; | ||||
|  | ||||
| import org.springdoc.api.annotations.ParameterObject; | ||||
| import org.springframework.data.domain.Sort; | ||||
|  | ||||
| import cn.hutool.core.util.ArrayUtil; | ||||
| import cn.hutool.core.util.StrUtil; | ||||
|  | ||||
| import top.charles7c.cnadmin.common.consts.CharConstants; | ||||
|  | ||||
| /** | ||||
|  * 排序查询条件 | ||||
|  * | ||||
|  * @author Charles7c | ||||
|  * @since 2023/2/12 21:30 | ||||
|  */ | ||||
| @Data | ||||
| @ParameterObject | ||||
| @Schema(description = "排序查询条件") | ||||
| public class SortQuery implements Serializable { | ||||
|  | ||||
|     private static final long serialVersionUID = 1L; | ||||
|  | ||||
|     /** | ||||
|      * 排序条件 | ||||
|      */ | ||||
|     @Schema(description = "排序条件", example = "sort=published,desc&sort=title,asc") | ||||
|     private String[] sort; | ||||
|  | ||||
|     /** | ||||
|      * 解析排序条件为 Spring 分页排序实体 | ||||
|      * | ||||
|      * @return Spring 分页排序实体 | ||||
|      */ | ||||
|     public Sort getSort() { | ||||
|         if (ArrayUtil.isEmpty(sort)) { | ||||
|             return Sort.unsorted(); | ||||
|         } | ||||
|  | ||||
|         List<Sort.Order> orders = new ArrayList<>(sort.length); | ||||
|         if (StrUtil.contains(sort[0], CharConstants.COMMA)) { | ||||
|             // e.g "sort=published,desc&sort=title,asc" | ||||
|             for (String s : sort) { | ||||
|                 List<String> sortList = StrUtil.split(s, CharConstants.COMMA); | ||||
|                 Sort.Order order = | ||||
|                     new Sort.Order(Sort.Direction.valueOf(sortList.get(1).toUpperCase()), sortList.get(0)); | ||||
|                 orders.add(order); | ||||
|             } | ||||
|         } else { | ||||
|             // e.g "sort=published,desc" | ||||
|             Sort.Order order = new Sort.Order(Sort.Direction.valueOf(sort[1].toUpperCase()), sort[0]); | ||||
|             orders.add(order); | ||||
|         } | ||||
|         return Sort.by(orders); | ||||
|     } | ||||
| } | ||||
| @@ -102,7 +102,7 @@ public class PageDataVO<V> { | ||||
|      * @param page | ||||
|      *            页码 | ||||
|      * @param size | ||||
|      *            每页记录数 | ||||
|      *            每页条数 | ||||
|      * @param list | ||||
|      *            列表数据 | ||||
|      * @param <V> | ||||
|   | ||||
| @@ -0,0 +1,35 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
|  | ||||
| package top.charles7c.cnadmin.common.service; | ||||
|  | ||||
| /** | ||||
|  * 公共用户业务接口 | ||||
|  * | ||||
|  * @author Charles7c | ||||
|  * @since 2023/2/13 20:37 | ||||
|  */ | ||||
| public interface CommonUserService { | ||||
|  | ||||
|     /** | ||||
|      * 根据 ID 查询昵称 | ||||
|      * | ||||
|      * @param userId | ||||
|      *            用户 ID | ||||
|      * @return 昵称 | ||||
|      */ | ||||
|     String getNicknameById(Long userId); | ||||
| } | ||||
| @@ -69,10 +69,10 @@ public class QueryHelper { | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取指定类的所有属性(包括私有的和父类的) | ||||
|      * 获取指定类型的所有属性(包括私有的和父类的) | ||||
|      * | ||||
|      * @param clazz | ||||
|      *            指定类 | ||||
|      *            指定类型 | ||||
|      * @param fieldList | ||||
|      *            属性列表 | ||||
|      * @param <Q> | ||||
|   | ||||
| @@ -18,8 +18,6 @@ package top.charles7c.cnadmin.system.service; | ||||
|  | ||||
| import java.util.List; | ||||
|  | ||||
| import javax.servlet.http.HttpServletResponse; | ||||
|  | ||||
| import cn.hutool.core.lang.tree.Tree; | ||||
|  | ||||
| import top.charles7c.cnadmin.common.base.BaseService; | ||||
| @@ -53,14 +51,4 @@ public interface DeptService extends BaseService<DeptVO, DeptDetailVO, DeptQuery | ||||
|      * @return 树列表 | ||||
|      */ | ||||
|     List<Tree<Long>> buildTree(List<DeptVO> list); | ||||
|  | ||||
|     /** | ||||
|      * 导出 | ||||
|      * | ||||
|      * @param query | ||||
|      *            查询条件 | ||||
|      * @param response | ||||
|      *            响应对象 | ||||
|      */ | ||||
|     void export(DeptQuery query, HttpServletResponse response); | ||||
| } | ||||
|   | ||||
| @@ -84,7 +84,7 @@ public interface UserService { | ||||
|      * 根据 ID 查询 | ||||
|      * | ||||
|      * @param userId | ||||
|      *            用户ID | ||||
|      *            用户 ID | ||||
|      * @return 用户信息 | ||||
|      */ | ||||
|     UserDO getById(Long userId); | ||||
|   | ||||
| @@ -17,32 +17,24 @@ | ||||
| package top.charles7c.cnadmin.system.service.impl; | ||||
|  | ||||
| import java.util.ArrayList; | ||||
| import java.util.Collections; | ||||
| import java.util.List; | ||||
| import java.util.Objects; | ||||
| import java.util.stream.Collectors; | ||||
|  | ||||
| import javax.servlet.http.HttpServletResponse; | ||||
|  | ||||
| import lombok.RequiredArgsConstructor; | ||||
|  | ||||
| import org.springframework.stereotype.Service; | ||||
| import org.springframework.transaction.annotation.Transactional; | ||||
|  | ||||
| import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; | ||||
| import com.baomidou.mybatisplus.core.toolkit.Wrappers; | ||||
|  | ||||
| import cn.hutool.core.bean.BeanUtil; | ||||
| import cn.hutool.core.collection.CollUtil; | ||||
| import cn.hutool.core.lang.tree.Tree; | ||||
|  | ||||
| import top.charles7c.cnadmin.common.base.BaseServiceImpl; | ||||
| import top.charles7c.cnadmin.common.base.BaseVO; | ||||
| import top.charles7c.cnadmin.common.enums.DisEnableStatusEnum; | ||||
| import top.charles7c.cnadmin.common.util.ExcelUtils; | ||||
| import top.charles7c.cnadmin.common.util.ExceptionUtils; | ||||
| import top.charles7c.cnadmin.common.util.TreeUtils; | ||||
| import top.charles7c.cnadmin.common.util.helper.QueryHelper; | ||||
| import top.charles7c.cnadmin.common.util.validate.CheckUtils; | ||||
| import top.charles7c.cnadmin.system.mapper.DeptMapper; | ||||
| import top.charles7c.cnadmin.system.model.entity.DeptDO; | ||||
| @@ -51,7 +43,6 @@ import top.charles7c.cnadmin.system.model.request.DeptRequest; | ||||
| import top.charles7c.cnadmin.system.model.vo.DeptDetailVO; | ||||
| import top.charles7c.cnadmin.system.model.vo.DeptVO; | ||||
| import top.charles7c.cnadmin.system.service.DeptService; | ||||
| import top.charles7c.cnadmin.system.service.UserService; | ||||
|  | ||||
| /** | ||||
|  * 部门业务实现类 | ||||
| @@ -64,38 +55,6 @@ import top.charles7c.cnadmin.system.service.UserService; | ||||
| public class DeptServiceImpl extends BaseServiceImpl<DeptMapper, DeptDO, DeptVO, DeptDetailVO, DeptQuery, DeptRequest> | ||||
|     implements DeptService { | ||||
|  | ||||
|     private final UserService userService; | ||||
|  | ||||
|     @Override | ||||
|     public List<DeptVO> list(DeptQuery query) { | ||||
|         List<DeptDO> deptList = this.listDept(query); | ||||
|         List<DeptVO> list = BeanUtil.copyToList(deptList, DeptVO.class); | ||||
|         list.forEach(this::fill); | ||||
|         return list; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 查询列表 | ||||
|      * | ||||
|      * @param query | ||||
|      *            查询条件 | ||||
|      * @return 列表信息 | ||||
|      */ | ||||
|     private List<DeptDO> listDept(DeptQuery query) { | ||||
|         QueryWrapper<DeptDO> queryWrapper = QueryHelper.build(query); | ||||
|         queryWrapper.lambda().orderByAsc(DeptDO::getParentId).orderByAsc(DeptDO::getDeptSort) | ||||
|             .orderByDesc(DeptDO::getCreateTime); | ||||
|         List<DeptDO> deptList = baseMapper.selectList(queryWrapper); | ||||
|         return CollUtil.isNotEmpty(deptList) ? deptList : Collections.emptyList(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public DeptDetailVO get(Long id) { | ||||
|         DeptDetailVO deptDetailVO = super.get(id); | ||||
|         this.fillDetail(deptDetailVO); | ||||
|         return deptDetailVO; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     @Transactional(rollbackFor = Exception.class) | ||||
|     public Long create(DeptRequest request) { | ||||
| @@ -103,11 +62,9 @@ public class DeptServiceImpl extends BaseServiceImpl<DeptMapper, DeptDO, DeptVO, | ||||
|         boolean isExist = this.checkNameExist(deptName, request.getParentId(), request.getDeptId()); | ||||
|         CheckUtils.throwIf(() -> isExist, String.format("新增失败,'%s'已存在", deptName)); | ||||
|  | ||||
|         // 保存部门信息 | ||||
|         DeptDO deptDO = BeanUtil.copyProperties(request, DeptDO.class); | ||||
|         deptDO.setStatus(DisEnableStatusEnum.ENABLE); | ||||
|         baseMapper.insert(deptDO); | ||||
|         return deptDO.getDeptId(); | ||||
|         // 保存信息 | ||||
|         request.setStatus(DisEnableStatusEnum.ENABLE); | ||||
|         return super.create(request); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
| @@ -189,14 +146,6 @@ public class DeptServiceImpl extends BaseServiceImpl<DeptMapper, DeptDO, DeptVO, | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void export(DeptQuery query, HttpServletResponse response) { | ||||
|         List<DeptDO> deptList = this.listDept(query); | ||||
|         List<DeptDetailVO> list = BeanUtil.copyToList(deptList, DeptDetailVO.class); | ||||
|         list.forEach(this::fillDetail); | ||||
|         ExcelUtils.export(list, "部门数据", DeptDetailVO.class, response); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 检查名称是否存在 | ||||
|      * | ||||
| @@ -213,34 +162,12 @@ public class DeptServiceImpl extends BaseServiceImpl<DeptMapper, DeptDO, DeptVO, | ||||
|             .eq(DeptDO::getParentId, parentId).ne(deptId != null, DeptDO::getDeptId, deptId)); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 填充数据 | ||||
|      * | ||||
|      * @param baseVO | ||||
|      *            待填充列表信息 | ||||
|      */ | ||||
|     private void fill(BaseVO baseVO) { | ||||
|         Long createUser = baseVO.getCreateUser(); | ||||
|         if (createUser == null) { | ||||
|             return; | ||||
|         } | ||||
|         baseVO.setCreateUserString(ExceptionUtils.exToNull(() -> userService.getById(createUser)).getNickname()); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 填充详情数据 | ||||
|      * | ||||
|      * @param detailVO | ||||
|      *            待填充详情信息 | ||||
|      */ | ||||
|     private void fillDetail(DeptDetailVO detailVO) { | ||||
|         this.fill(detailVO); | ||||
|  | ||||
|         Long updateUser = detailVO.getUpdateUser(); | ||||
|         if (updateUser == null) { | ||||
|             return; | ||||
|         } | ||||
|         detailVO.setUpdateUserString(ExceptionUtils.exToNull(() -> userService.getById(updateUser)).getNickname()); | ||||
|     @Override | ||||
|     public void fillDetail(Object detailObj) { | ||||
|         super.fillDetail(detailObj); | ||||
|         if (detailObj instanceof DeptDetailVO) { | ||||
|             DeptDetailVO detailVO = (DeptDetailVO)detailObj; | ||||
|             detailVO.setParentName(ExceptionUtils.exToNull(() -> this.get(detailVO.getParentId()).getDeptName())); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -16,8 +16,6 @@ | ||||
|  | ||||
| package top.charles7c.cnadmin.system.service.impl; | ||||
|  | ||||
| import java.util.List; | ||||
|  | ||||
| import lombok.RequiredArgsConstructor; | ||||
|  | ||||
| import org.springframework.stereotype.Service; | ||||
| @@ -25,15 +23,8 @@ import org.springframework.transaction.annotation.Transactional; | ||||
|  | ||||
| import com.baomidou.mybatisplus.core.toolkit.Wrappers; | ||||
|  | ||||
| import cn.hutool.core.bean.BeanUtil; | ||||
|  | ||||
| import top.charles7c.cnadmin.common.base.BaseDetailVO; | ||||
| import top.charles7c.cnadmin.common.base.BaseServiceImpl; | ||||
| import top.charles7c.cnadmin.common.base.BaseVO; | ||||
| import top.charles7c.cnadmin.common.enums.DisEnableStatusEnum; | ||||
| import top.charles7c.cnadmin.common.model.query.PageQuery; | ||||
| import top.charles7c.cnadmin.common.model.vo.PageDataVO; | ||||
| import top.charles7c.cnadmin.common.util.ExceptionUtils; | ||||
| import top.charles7c.cnadmin.common.util.validate.CheckUtils; | ||||
| import top.charles7c.cnadmin.system.mapper.RoleMapper; | ||||
| import top.charles7c.cnadmin.system.model.entity.RoleDO; | ||||
| @@ -42,7 +33,6 @@ import top.charles7c.cnadmin.system.model.request.RoleRequest; | ||||
| import top.charles7c.cnadmin.system.model.vo.RoleDetailVO; | ||||
| import top.charles7c.cnadmin.system.model.vo.RoleVO; | ||||
| import top.charles7c.cnadmin.system.service.RoleService; | ||||
| import top.charles7c.cnadmin.system.service.UserService; | ||||
|  | ||||
| /** | ||||
|  * 角色业务实现类 | ||||
| @@ -55,22 +45,6 @@ import top.charles7c.cnadmin.system.service.UserService; | ||||
| public class RoleServiceImpl extends BaseServiceImpl<RoleMapper, RoleDO, RoleVO, RoleDetailVO, RoleQuery, RoleRequest> | ||||
|     implements RoleService { | ||||
|  | ||||
|     private final UserService userService; | ||||
|  | ||||
|     @Override | ||||
|     public PageDataVO<RoleVO> page(RoleQuery query, PageQuery pageQuery) { | ||||
|         PageDataVO<RoleVO> pageDataVO = super.page(query, pageQuery); | ||||
|         pageDataVO.getList().forEach(this::fill); | ||||
|         return pageDataVO; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public RoleDetailVO get(Long id) { | ||||
|         RoleDetailVO roleDetailVO = super.get(id); | ||||
|         this.fillDetail(roleDetailVO); | ||||
|         return roleDetailVO; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     @Transactional(rollbackFor = Exception.class) | ||||
|     public Long create(RoleRequest request) { | ||||
| @@ -79,10 +53,8 @@ public class RoleServiceImpl extends BaseServiceImpl<RoleMapper, RoleDO, RoleVO, | ||||
|         CheckUtils.throwIf(() -> isExist, String.format("新增失败,'%s'已存在", roleName)); | ||||
|  | ||||
|         // 保存信息 | ||||
|         RoleDO roleDO = BeanUtil.copyProperties(request, RoleDO.class); | ||||
|         roleDO.setStatus(DisEnableStatusEnum.ENABLE); | ||||
|         baseMapper.insert(roleDO); | ||||
|         return roleDO.getRoleId(); | ||||
|         request.setStatus(DisEnableStatusEnum.ENABLE); | ||||
|         return super.create(request); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
| @@ -95,12 +67,6 @@ public class RoleServiceImpl extends BaseServiceImpl<RoleMapper, RoleDO, RoleVO, | ||||
|         super.update(request); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     @Transactional(rollbackFor = Exception.class) | ||||
|     public void delete(List<Long> ids) { | ||||
|         super.delete(ids); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 检查名称是否存在 | ||||
|      * | ||||
| @@ -114,34 +80,4 @@ public class RoleServiceImpl extends BaseServiceImpl<RoleMapper, RoleDO, RoleVO, | ||||
|         return baseMapper.exists(Wrappers.<RoleDO>lambdaQuery().eq(RoleDO::getRoleName, roleName).ne(roleId != null, | ||||
|             RoleDO::getRoleId, roleId)); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 填充数据 | ||||
|      * | ||||
|      * @param baseVO | ||||
|      *            待填充列表信息 | ||||
|      */ | ||||
|     private void fill(BaseVO baseVO) { | ||||
|         Long createUser = baseVO.getCreateUser(); | ||||
|         if (createUser == null) { | ||||
|             return; | ||||
|         } | ||||
|         baseVO.setCreateUserString(ExceptionUtils.exToNull(() -> userService.getById(createUser)).getNickname()); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 填充详情数据 | ||||
|      * | ||||
|      * @param detailVO | ||||
|      *            待填充详情信息 | ||||
|      */ | ||||
|     private void fillDetail(BaseDetailVO detailVO) { | ||||
|         this.fill(detailVO); | ||||
|  | ||||
|         Long updateUser = detailVO.getUpdateUser(); | ||||
|         if (updateUser == null) { | ||||
|             return; | ||||
|         } | ||||
|         detailVO.setUpdateUserString(ExceptionUtils.exToNull(() -> userService.getById(updateUser)).getNickname()); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -36,6 +36,7 @@ import cn.hutool.core.util.StrUtil; | ||||
| import top.charles7c.cnadmin.common.config.properties.LocalStorageProperties; | ||||
| import top.charles7c.cnadmin.common.consts.FileConstants; | ||||
| import top.charles7c.cnadmin.common.model.dto.LoginUser; | ||||
| import top.charles7c.cnadmin.common.service.CommonUserService; | ||||
| import top.charles7c.cnadmin.common.util.FileUtils; | ||||
| import top.charles7c.cnadmin.common.util.SecureUtils; | ||||
| import top.charles7c.cnadmin.common.util.helper.LoginHelper; | ||||
| @@ -52,7 +53,7 @@ import top.charles7c.cnadmin.system.service.UserService; | ||||
|  */ | ||||
| @Service | ||||
| @RequiredArgsConstructor | ||||
| public class UserServiceImpl implements UserService { | ||||
| public class UserServiceImpl implements UserService, CommonUserService { | ||||
|  | ||||
|     private final UserMapper userMapper; | ||||
|     private final LocalStorageProperties localStorageProperties; | ||||
| @@ -155,4 +156,9 @@ public class UserServiceImpl implements UserService { | ||||
|         CheckUtils.throwIfNull(userDO, String.format("ID为 [%s] 的用户已不存在", userId)); | ||||
|         return userDO; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public String getNicknameById(Long userId) { | ||||
|         return this.getById(userId).getNickname(); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -7,8 +7,8 @@ export interface DeptRecord { | ||||
|   deptId?: number; | ||||
|   deptName: string; | ||||
|   parentId?: number; | ||||
|   deptSort: number; | ||||
|   description?: string; | ||||
|   deptSort: number; | ||||
|   status?: number; | ||||
|   createUserString?: string; | ||||
|   createTime?: string; | ||||
| @@ -24,7 +24,7 @@ export interface DeptParam { | ||||
| } | ||||
|  | ||||
| export function listDept(params: DeptParam) { | ||||
|   return axios.get<DeptRecord[]>(`${BASE_URL}/all`, { | ||||
|   return axios.get<DeptRecord[]>(`${BASE_URL}/list`, { | ||||
|     params, | ||||
|     paramsSerializer: (obj) => { | ||||
|       return qs.stringify(obj); | ||||
| @@ -47,13 +47,3 @@ export function updateDept(req: DeptRecord) { | ||||
| export function deleteDept(ids: number | Array<number>) { | ||||
|   return axios.delete(`${BASE_URL}/${ids}`); | ||||
| } | ||||
|  | ||||
| export function exportDept(params: DeptParam) { | ||||
|   return axios.get(`${BASE_URL}/export`, { | ||||
|     params, | ||||
|     paramsSerializer: (obj) => { | ||||
|       return qs.stringify(obj); | ||||
|     }, | ||||
|     responseType: 'blob', | ||||
|   }); | ||||
| } | ||||
|   | ||||
							
								
								
									
										66
									
								
								continew-admin-ui/src/components/crud/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								continew-admin-ui/src/components/crud/index.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,66 @@ | ||||
| import axios from 'axios'; | ||||
| import qs from 'query-string'; | ||||
| import { Notification } from '@arco-design/web-vue'; | ||||
| import dayjs from 'dayjs'; | ||||
|  | ||||
| /** | ||||
|  * 下载 | ||||
|  * | ||||
|  * @param url URL | ||||
|  * @param params 查询条件 | ||||
|  * @param fileName 文件名 | ||||
|  */ | ||||
| export default function download( | ||||
|   url: string, | ||||
|   params: any, | ||||
|   fileName: string | undefined | ||||
| ) { | ||||
|   return axios | ||||
|     .get(url, { | ||||
|       params, | ||||
|       paramsSerializer: (obj) => { | ||||
|         return qs.stringify(obj); | ||||
|       }, | ||||
|       responseType: 'blob', | ||||
|     }) | ||||
|     .then(async (res) => { | ||||
|       // 获取文件名 | ||||
|       if (!fileName) { | ||||
|         const contentDisposition = res.headers['content-disposition']; | ||||
|         const pattern = new RegExp('filename=([^;]+\\.[^\\.;]+);*'); | ||||
|         const result = pattern.exec(contentDisposition) || ''; | ||||
|         // 对名字进行解码 | ||||
|         fileName = window.decodeURI(result[1]); | ||||
|       } else { | ||||
|         fileName = `${fileName}_${dayjs().format('YYYYMMDDHHmmss')}`; | ||||
|       } | ||||
|  | ||||
|       // 创建下载的链接 | ||||
|       const blob = new Blob([res.data], { | ||||
|         type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8', | ||||
|       }); | ||||
|       const downloadElement = document.createElement('a'); | ||||
|       const href = window.URL.createObjectURL(blob); | ||||
|       downloadElement.style.display = 'none'; | ||||
|       downloadElement.href = href; | ||||
|       // 下载后文件名 | ||||
|       downloadElement.download = fileName; | ||||
|       document.body.appendChild(downloadElement); | ||||
|       // 点击下载 | ||||
|       downloadElement.click(); | ||||
|       // 下载完成,移除元素 | ||||
|       document.body.removeChild(downloadElement); | ||||
|       // 释放掉 blob 对象 | ||||
|       window.URL.revokeObjectURL(href); | ||||
|     }) | ||||
|     .catch((error) => { | ||||
|       console.error(error); | ||||
|       Notification.warning({ | ||||
|         title: '警告', | ||||
|         content: | ||||
|           "如果您正在访问演示环境,点击导出会报错。这是由于演示环境开启了 Mock.js,而 Mock.js 会将 responseType 设置为 '',这不仅会导致关键判断出错,也会导致导出的文件无法打开。", | ||||
|         duration: 10000, | ||||
|         closable: true, | ||||
|       }); | ||||
|     }); | ||||
| } | ||||
| @@ -13,6 +13,7 @@ import Chart from './chart/index.vue'; | ||||
| import Breadcrumb from './breadcrumb/index.vue'; | ||||
| import DateRangePicker from './date-range-picker/index.vue'; | ||||
| import RightToolbar from './right-toolbar/index.vue'; | ||||
| import download from './crud'; | ||||
|  | ||||
| // Manually introduce ECharts modules to reduce packing size | ||||
|  | ||||
| @@ -31,6 +32,10 @@ use([ | ||||
|  | ||||
| export default { | ||||
|   install(Vue: App) { | ||||
|     // 全局方法挂载 | ||||
|     Vue.config.globalProperties.download = download; | ||||
|  | ||||
|     // 全局组件挂载 | ||||
|     Vue.component('Chart', Chart); | ||||
|     Vue.component('Breadcrumb', Breadcrumb); | ||||
|     Vue.component('DateRangePicker', DateRangePicker); | ||||
|   | ||||
| @@ -15,8 +15,9 @@ export function encryptByMd5(txt: string) { | ||||
|   return md5(txt).toString(); | ||||
| } | ||||
|  | ||||
| const publicKey = 'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAM51dgYtMyF+tTQt80sfFOpSV27a7t9u' + | ||||
|   'aUVeFrdGiVxscuizE7H8SMntYqfn9lp8a5GH5P1/GGehVjUD2gF/4kcCAwEAAQ==' | ||||
| const publicKey = | ||||
|   'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAM51dgYtMyF+tTQt80sfFOpSV27a7t9u' + | ||||
|   'aUVeFrdGiVxscuizE7H8SMntYqfn9lp8a5GH5P1/GGehVjUD2gF/4kcCAwEAAQ=='; | ||||
|  | ||||
| export function encryptByRsa(txt: string) { | ||||
|   const encryptor = new JSEncrypt(); | ||||
|   | ||||
| @@ -1,16 +1,17 @@ | ||||
| import axios from "axios"; | ||||
| import type { AxiosRequestConfig, AxiosResponse } from "axios"; | ||||
| import { Message } from "@arco-design/web-vue"; | ||||
| import { getToken } from "@/utils/auth"; | ||||
| import axios from 'axios'; | ||||
| import type { AxiosRequestConfig, AxiosResponse } from 'axios'; | ||||
| import { Message } from '@arco-design/web-vue'; | ||||
| import { getToken } from '@/utils/auth'; | ||||
|  | ||||
| // default config | ||||
| if (import.meta.env.VITE_API_BASE_URL) { | ||||
|   axios.defaults.baseURL = import.meta.env.VITE_API_BASE_URL; | ||||
|   axios.defaults.timeout = 60000 // 1 分钟 | ||||
|   axios.defaults.timeout = 60000; // 1 分钟 | ||||
| } | ||||
|  | ||||
| // request interceptors | ||||
| axios.interceptors.request.use((config: AxiosRequestConfig) => { | ||||
| axios.interceptors.request.use( | ||||
|   (config: AxiosRequestConfig) => { | ||||
|     const token = getToken(); | ||||
|     if (token) { | ||||
|       if (!config.headers) { | ||||
| @@ -35,9 +36,13 @@ export interface HttpResponse<T = unknown> { | ||||
| } | ||||
|  | ||||
| // response interceptors | ||||
| axios.interceptors.response.use((response: AxiosResponse<HttpResponse>) => { | ||||
| axios.interceptors.response.use( | ||||
|   (response: AxiosResponse<HttpResponse>) => { | ||||
|     // 二进制数据则直接返回 | ||||
|     if(response.request.responseType ===  'blob' || response.request.responseType ===  'arraybuffer'){ | ||||
|     if ( | ||||
|       response.request.responseType === 'blob' || | ||||
|       response.request.responseType === 'arraybuffer' | ||||
|     ) { | ||||
|       return response; | ||||
|     } | ||||
|  | ||||
| @@ -49,7 +54,7 @@ axios.interceptors.response.use((response: AxiosResponse<HttpResponse>) => { | ||||
|     // 操作失败,弹出错误提示 | ||||
|     Message.error({ | ||||
|       content: res.msg, | ||||
|       duration: 3000 | ||||
|       duration: 3000, | ||||
|     }); | ||||
|     // | ||||
|     // if (res.code === 401) { | ||||
| @@ -61,8 +66,8 @@ axios.interceptors.response.use((response: AxiosResponse<HttpResponse>) => { | ||||
|     console.error(`err: ${error}`); | ||||
|     const res = error.response.data; | ||||
|     Message.error({ | ||||
|       content: res.msg || "网络错误", | ||||
|       duration: 3000 | ||||
|       content: res.msg || '网络错误', | ||||
|       duration: 3000, | ||||
|     }); | ||||
|     return Promise.reject(error); | ||||
|   } | ||||
|   | ||||
| @@ -302,7 +302,6 @@ | ||||
|     createDept, | ||||
|     updateDept, | ||||
|     deleteDept, | ||||
|     exportDept, | ||||
|   } from '@/api/system/dept'; | ||||
|   import listDeptTree from '@/api/common'; | ||||
|  | ||||
| @@ -341,6 +340,7 @@ | ||||
|     queryParams: { | ||||
|       deptName: undefined, | ||||
|       status: undefined, | ||||
|       sort: ['parentId,asc', 'deptSort,asc', 'createTime,desc'], | ||||
|     }, | ||||
|     // 表单数据 | ||||
|     form: {} as DeptRecord, | ||||
| @@ -410,8 +410,9 @@ | ||||
|       deptId: undefined, | ||||
|       deptName: '', | ||||
|       parentId: undefined, | ||||
|       deptSort: 999, | ||||
|       description: '', | ||||
|       deptSort: 999, | ||||
|       status: 1, | ||||
|     }; | ||||
|     proxy.$refs.formRef?.resetFields(); | ||||
|   }; | ||||
| @@ -521,40 +522,8 @@ | ||||
|   const handleExport = () => { | ||||
|     if (exportLoading.value) return; | ||||
|     exportLoading.value = true; | ||||
|     exportDept({ ...queryParams.value }) | ||||
|       .then(async (res) => { | ||||
|         const blob = new Blob([res.data], { | ||||
|           type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8', | ||||
|         }); | ||||
|         const contentDisposition = res.headers['content-disposition']; | ||||
|         const pattern = new RegExp('filename=([^;]+\\.[^\\.;]+);*'); | ||||
|         const result = pattern.exec(contentDisposition) || ''; | ||||
|         // 对名字进行解码 | ||||
|         const fileName = window.decodeURI(result[1]); | ||||
|         // 创建下载的链接 | ||||
|         const downloadElement = document.createElement('a'); | ||||
|         const href = window.URL.createObjectURL(blob); | ||||
|         downloadElement.style.display = 'none'; | ||||
|         downloadElement.href = href; | ||||
|         // 下载后文件名 | ||||
|         downloadElement.download = fileName; | ||||
|         document.body.appendChild(downloadElement); | ||||
|         // 点击下载 | ||||
|         downloadElement.click(); | ||||
|         // 下载完成,移除元素 | ||||
|         document.body.removeChild(downloadElement); | ||||
|         // 释放掉 blob 对象 | ||||
|         window.URL.revokeObjectURL(href); | ||||
|       }) | ||||
|       .catch(() => { | ||||
|         proxy.$notification.warning({ | ||||
|           title: '警告', | ||||
|           content: | ||||
|             "如果您正在访问演示环境,点击导出会报错。这是由于演示环境开启了 Mock.js,而 Mock.js 会将 responseType 设置为 '',这不仅会导致关键判断出错,也会导致导出的文件无法打开。", | ||||
|           duration: 10000, | ||||
|           closable: true, | ||||
|         }); | ||||
|       }) | ||||
|     proxy | ||||
|       .download('/system/dept/export', { ...queryParams.value }, '部门数据') | ||||
|       .finally(() => { | ||||
|         exportLoading.value = false; | ||||
|       }); | ||||
|   | ||||
| @@ -63,6 +63,14 @@ | ||||
|                 > | ||||
|                   <template #icon><icon-delete /></template>删除 | ||||
|                 </a-button> | ||||
|                 <a-button | ||||
|                   :loading="exportLoading" | ||||
|                   type="primary" | ||||
|                   status="warning" | ||||
|                   @click="handleExport" | ||||
|                 > | ||||
|                   <template #icon><icon-download /></template>导出 | ||||
|                 </a-button> | ||||
|               </a-space> | ||||
|             </a-col> | ||||
|             <a-col :span="12"> | ||||
| @@ -383,6 +391,7 @@ | ||||
|   const showQuery = ref(true); | ||||
|   const loading = ref(false); | ||||
|   const detailLoading = ref(false); | ||||
|   const exportLoading = ref(false); | ||||
|   const visible = ref(false); | ||||
|   const detailVisible = ref(false); | ||||
|   const statusOptions = ref<SelectOptionData[]>([ | ||||
| @@ -478,6 +487,7 @@ | ||||
|       dataScopeDeptIds: undefined, | ||||
|       description: '', | ||||
|       roleSort: 999, | ||||
|       status: 1, | ||||
|     }; | ||||
|     proxy.$refs.formRef?.resetFields(); | ||||
|   }; | ||||
| @@ -580,6 +590,19 @@ | ||||
|     multiple.value = !rowKeys.length; | ||||
|   }; | ||||
|  | ||||
|   /** | ||||
|    * 导出 | ||||
|    */ | ||||
|   const handleExport = () => { | ||||
|     if (exportLoading.value) return; | ||||
|     exportLoading.value = true; | ||||
|     proxy | ||||
|       .download('/system/role/export', { ...queryParams.value }, '角色数据') | ||||
|       .finally(() => { | ||||
|         exportLoading.value = false; | ||||
|       }); | ||||
|   }; | ||||
|  | ||||
|   /** | ||||
|    * 修改状态 | ||||
|    * | ||||
|   | ||||
| @@ -30,6 +30,7 @@ import org.springframework.web.bind.annotation.RestController; | ||||
|  | ||||
| import cn.hutool.core.lang.tree.Tree; | ||||
|  | ||||
| import top.charles7c.cnadmin.common.model.query.SortQuery; | ||||
| import top.charles7c.cnadmin.common.model.vo.R; | ||||
| import top.charles7c.cnadmin.monitor.annotation.Log; | ||||
| import top.charles7c.cnadmin.system.model.query.DeptQuery; | ||||
| @@ -53,8 +54,8 @@ public class CommonController { | ||||
|     @Log(ignore = true) | ||||
|     @Operation(summary = "查询部门树", description = "查询树结构的部门列表") | ||||
|     @GetMapping("/tree/dept") | ||||
|     public R<List<Tree<Long>>> listDeptTree(@Validated DeptQuery query) { | ||||
|         List<DeptVO> list = deptService.list(query); | ||||
|     public R<List<Tree<Long>>> listDeptTree(@Validated DeptQuery query, @Validated SortQuery sortQuery) { | ||||
|         List<DeptVO> list = deptService.list(query, sortQuery); | ||||
|         List<Tree<Long>> deptTreeList = deptService.buildTree(list); | ||||
|         return R.ok(deptTreeList); | ||||
|     } | ||||
|   | ||||
| @@ -20,8 +20,6 @@ import static top.charles7c.cnadmin.common.annotation.CrudRequestMapping.Api; | ||||
|  | ||||
| import java.util.List; | ||||
|  | ||||
| import javax.servlet.http.HttpServletResponse; | ||||
|  | ||||
| import io.swagger.v3.oas.annotations.Operation; | ||||
| import io.swagger.v3.oas.annotations.tags.Tag; | ||||
|  | ||||
| @@ -30,6 +28,7 @@ import org.springframework.web.bind.annotation.*; | ||||
|  | ||||
| import top.charles7c.cnadmin.common.annotation.CrudRequestMapping; | ||||
| import top.charles7c.cnadmin.common.base.BaseController; | ||||
| import top.charles7c.cnadmin.common.model.query.SortQuery; | ||||
| import top.charles7c.cnadmin.common.model.vo.R; | ||||
| import top.charles7c.cnadmin.system.model.query.DeptQuery; | ||||
| import top.charles7c.cnadmin.system.model.request.DeptRequest; | ||||
| @@ -45,19 +44,13 @@ import top.charles7c.cnadmin.system.service.DeptService; | ||||
|  */ | ||||
| @Tag(name = "部门管理 API") | ||||
| @RestController | ||||
| @CrudRequestMapping(value = "/system/dept", api = {Api.LIST, Api.GET, Api.CREATE, Api.UPDATE, Api.DELETE}) | ||||
| @CrudRequestMapping(value = "/system/dept", api = {Api.LIST, Api.GET, Api.CREATE, Api.UPDATE, Api.DELETE, Api.EXPORT}) | ||||
| public class DeptController extends BaseController<DeptService, DeptVO, DeptDetailVO, DeptQuery, DeptRequest> { | ||||
|  | ||||
|     @Override | ||||
|     @Operation(summary = "查询列表树") | ||||
|     public R<List<DeptVO>> list(@Validated DeptQuery query) { | ||||
|         List<DeptVO> list = baseService.list(query); | ||||
|     public R<List<DeptVO>> list(@Validated DeptQuery query, @Validated SortQuery sortQuery) { | ||||
|         List<DeptVO> list = baseService.list(query, sortQuery); | ||||
|         return R.ok(baseService.buildListTree(list)); | ||||
|     } | ||||
|  | ||||
|     @Operation(summary = "导出部门数据") | ||||
|     @GetMapping("/export") | ||||
|     public void export(@Validated DeptQuery query, HttpServletResponse response) { | ||||
|         baseService.export(query, response); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -16,8 +16,6 @@ | ||||
|  | ||||
| package top.charles7c.cnadmin.webapi.controller.system; | ||||
|  | ||||
| import static top.charles7c.cnadmin.common.annotation.CrudRequestMapping.Api; | ||||
|  | ||||
| import io.swagger.v3.oas.annotations.tags.Tag; | ||||
|  | ||||
| import org.springframework.web.bind.annotation.RestController; | ||||
| @@ -38,5 +36,5 @@ import top.charles7c.cnadmin.system.service.RoleService; | ||||
|  */ | ||||
| @Tag(name = "角色管理 API") | ||||
| @RestController | ||||
| @CrudRequestMapping(value = "/system/role", api = {Api.PAGE, Api.GET, Api.CREATE, Api.UPDATE, Api.DELETE}) | ||||
| @CrudRequestMapping("/system/role") | ||||
| public class RoleController extends BaseController<RoleService, RoleVO, RoleDetailVO, RoleQuery, RoleRequest> {} | ||||
|   | ||||
		Reference in New Issue
	
	Block a user