新增:新增系统管理/部门管理/导出功能(引入 Easy Excel 依赖用于导出 Excel,详情可见 README 介绍。另请注意:测试导出功能时,前端需要关闭 mockjs,否则 responseType 会被 mockjs 设置为 '',导致导出的文件无法打开)

This commit is contained in:
Charles7c 2023-02-06 23:02:23 +08:00
parent 4bde837649
commit ceba8e9e53
30 changed files with 536 additions and 82 deletions

View File

@ -85,6 +85,7 @@ yarn dev
| [Liquibase](https://github.com/liquibase/liquibase) | 4.9.1 | 用于管理数据库版本,跟踪、管理和应用数据库变化。 |
| [Redis](https://redis.io/) | 6.2.7 | 高性能的 key-value 数据库。 |
| [Redisson](https://github.com/redisson/redisson/wiki/Redisson%E9%A1%B9%E7%9B%AE%E4%BB%8B%E7%BB%8D) | 3.19.0 | 不仅仅是一个 Redis Java 客户端,同其他 Redis Java 客户端有着很大的区别,相比之下其他客户端提供的功能还仅仅停留在作为数据库驱动层面上,比如仅针对 Redis 提供连接方式,发送命令和处理返回结果等。而 Redisson 充分的利用了 Redis 键值数据库提供的一系列优势,基于 Java 实用工具包中常用接口,为使用者提供了一系列具有分布式特性的常用工具类。使得原本作为协调单机多线程并发程序的工具包获得了协调分布式多机多线程并发系统的能力,大大降低了设计和研发大规模分布式系统的难度。同时结合各富特色的分布式服务,更进一步简化了分布式环境中程序相互之间的协作。 |
| [Easy Excel](https://easyexcel.opensource.alibaba.com/) | 3.2.0 | 一个基于 Java 的、快速、简洁、解决大文件内存溢出的 Excel 处理工具。 |
| Easy Captcha | 1.6.2 | Java 图形验证码,支持 gif、中文、算术等类型可用于 Java Web、JavaSE 等项目。 |
| [Knife4j](https://doc.xiaominfo.com/) | 4.0.0 | 前身是 swagger-bootstrap-ui集 Swagger2 和 OpenAPI3 为一体的增强解决方案。本项目使用的是 [knife4j-openapi3-spring-boot-starter](https://gitee.com/xiaoym/swagger-bootstrap-ui-demo/tree/master/knife4j-springdoc-openapi-demo) 基于 OpenAPI3 规范,在 Spring Boot < 3.0.0-M1 的单体架构下可以直接引用此 starter该模块包含了 UI 部分底层基于 springdoc-openapi 项目 |
| [Hutool](https://www.hutool.cn/) | 5.8.11 | 小而全的 Java 工具类库,通过静态方法封装,降低相关 API 的学习成本,提高工作效率,使 Java 拥有函数式语言般的优雅,让 Java 语言也可以“甜甜的”。 |
@ -214,6 +215,7 @@ continew-admin # 全局通用项目配置及依赖版本管理
│ ├─ annotation # 公共注解
│ ├─ base # 公共基类
│ ├─ config # 公共配置
│ │ ├─ easyexcel # Easy Excel 配置
│ │ ├─ jackson # Jackson 配置
│ │ ├─ mybatis # MyBatis Plus 配置
│ │ ├─ threadpool # 线程池配置

View File

@ -115,6 +115,12 @@ limitations under the License.
</dependency>
<!-- ################ 工具库相关 ################ -->
<!-- Easy Excel一个基于 Java 的、快速、简洁、解决大文件内存溢出的 Excel 处理工具) -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
</dependency>
<!-- 第三方封装 Ip2region离线 IP 数据管理框架和定位库支持亿级别的数据段10 微秒级别的查询性能,提供了许多主流编程语言的 xdb 数据管理引擎的实现) -->
<dependency>
<groupId>net.dreamlu</groupId>

View File

@ -22,6 +22,7 @@ import lombok.Data;
import io.swagger.v3.oas.annotations.media.Schema;
import com.alibaba.excel.annotation.ExcelProperty;
import com.fasterxml.jackson.annotation.JsonIgnore;
/**
@ -45,11 +46,13 @@ public class BaseDetailVO extends BaseVO {
* 修改人
*/
@Schema(description = "修改人")
@ExcelProperty(value = "修改人")
private String updateUserString;
/**
* 修改时间
*/
@Schema(description = "修改时间")
@ExcelProperty(value = "修改时间")
private LocalDateTime updateTime;
}

View File

@ -0,0 +1,39 @@
/*
* 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.base;
import java.io.Serializable;
import com.baomidou.mybatisplus.annotation.IEnum;
/**
* 枚举基类
*
* @param <V>
* value 类型
* @param <D>
* description 类型
* @author Charles7c
* @since 2023/2/5 20:44
*/
public interface BaseEnum<V extends Serializable, D extends Serializable> extends IEnum<V> {
/**
* 枚举描述
*/
D getDescription();
}

View File

@ -23,6 +23,7 @@ import lombok.Data;
import io.swagger.v3.oas.annotations.media.Schema;
import com.alibaba.excel.annotation.ExcelProperty;
import com.fasterxml.jackson.annotation.JsonIgnore;
/**
@ -46,11 +47,13 @@ public class BaseVO implements Serializable {
* 创建人
*/
@Schema(description = "创建人")
@ExcelProperty(value = "创建人")
private String createUserString;
/**
* 创建时间
*/
@Schema(description = "创建时间")
@ExcelProperty(value = "创建时间")
private LocalDateTime createTime;
}

View File

@ -86,6 +86,8 @@ public class WebMvcConfiguration implements WebMvcConfigurer {
corsProperties.getAllowedMethods().forEach(config::addAllowedMethod);
// 配置允许跨域的请求头
corsProperties.getAllowedHeaders().forEach(config::addAllowedHeader);
// 配置允许跨域的响应头
corsProperties.getExposedHeaders().forEach(config::addExposedHeader);
// 添加映射路径拦截一切请求
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();

View File

@ -0,0 +1,92 @@
/*
* 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.config.easyexcel;
import com.alibaba.excel.converters.Converter;
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.GlobalConfiguration;
import com.alibaba.excel.metadata.data.ReadCellData;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.metadata.property.ExcelContentProperty;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.ObjectUtil;
import top.charles7c.cnadmin.common.base.BaseEnum;
/**
* Easy Excel 枚举基类转换器
*
* @author Charles7c
* @since 2023/2/5 19:29
*/
public class ExcelBaseEnumConverter implements Converter<BaseEnum<Integer, String>> {
@Override
public Class<BaseEnum> supportJavaTypeKey() {
return BaseEnum.class;
}
@Override
public CellDataTypeEnum supportExcelTypeKey() {
return CellDataTypeEnum.STRING;
}
/**
* 转换为 Java 数据读取 Excel
*/
@Override
public BaseEnum convertToJavaData(ReadCellData<?> cellData, ExcelContentProperty contentProperty,
GlobalConfiguration globalConfiguration) {
return this.getEnum(BaseEnum.class, Convert.toStr(cellData.getData()));
}
/**
* 转换为 Excel 数据写入 Excel
*/
@Override
public WriteCellData<String> convertToExcelData(BaseEnum<Integer, String> value,
ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
if (ObjectUtil.isNull(value)) {
return new WriteCellData<>("");
}
return new WriteCellData<>(value.getDescription());
}
/**
* 通过 value 获取枚举对象获取不到时为 {@code null}
*
* @param enumType
* 枚举类型
* @param description
* 描述
* @return 对应枚举 获取不到时为 {@code null}
*/
private BaseEnum<Integer, String> getEnum(Class<?> enumType, String description) {
Object[] enumConstants = enumType.getEnumConstants();
for (Object enumConstant : enumConstants) {
if (ClassUtil.isAssignable(BaseEnum.class, enumType)) {
BaseEnum<Integer, String> baseEnum = (BaseEnum<Integer, String>)enumConstant;
if (baseEnum.getDescription().equals(description)) {
return baseEnum;
}
}
}
return null;
}
}

View File

@ -0,0 +1,79 @@
/*
* 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.config.easyexcel;
import java.math.BigDecimal;
import com.alibaba.excel.converters.Converter;
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.GlobalConfiguration;
import com.alibaba.excel.metadata.data.ReadCellData;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.metadata.property.ExcelContentProperty;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjectUtil;
/**
* Easy Excel 大数值转换器Excel 中对长度超过 15 位的数值输入是有限制的 16 位开始无论录入什么数字均会变为 0因此输入时只能以文本的形式进行录入
*
* @author Charles7c
* @since 2023/2/5 19:29
*/
public class ExcelBigNumberConverter implements Converter<Long> {
/**
* Excel 输入数值长度限制
*/
private static final int MAX_LENGTH = 15;
@Override
public Class<Long> supportJavaTypeKey() {
return Long.class;
}
@Override
public CellDataTypeEnum supportExcelTypeKey() {
return CellDataTypeEnum.STRING;
}
/**
* 转换为 Java 数据读取 Excel
*/
@Override
public Long convertToJavaData(ReadCellData<?> cellData, ExcelContentProperty contentProperty,
GlobalConfiguration globalConfiguration) {
return Convert.toLong(cellData.getData());
}
/**
* 转换为 Excel 数据写入 Excel
*/
@Override
public WriteCellData<Object> convertToExcelData(Long value, ExcelContentProperty contentProperty,
GlobalConfiguration globalConfiguration) {
if (ObjectUtil.isNotNull(value)) {
String str = Long.toString(value);
if (str.length() > MAX_LENGTH) {
return new WriteCellData<>(str);
}
}
WriteCellData<Object> writeCellData = new WriteCellData<>(new BigDecimal(value));
writeCellData.setType(CellDataTypeEnum.NUMBER);
return writeCellData;
}
}

View File

@ -19,7 +19,6 @@ package top.charles7c.cnadmin.common.config.jackson;
import java.io.IOException;
import java.lang.reflect.Field;
import com.baomidou.mybatisplus.annotation.IEnum;
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
@ -28,20 +27,23 @@ import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.ReflectUtil;
import top.charles7c.cnadmin.common.base.BaseEnum;
/**
* 通用枚举接口 IEnum 反序列化器
* 通用枚举基类 BaseEnum 反序列化器
*
* @author Charles7c
* @since 2023/1/8 13:56
*/
@JacksonStdImpl
public class IEnumDeserializer extends JsonDeserializer<IEnum> {
public class BaseEnumDeserializer extends JsonDeserializer<BaseEnum> {
/** 静态实例 */
public static final IEnumDeserializer SERIALIZER_INSTANCE = new IEnumDeserializer();
public static final BaseEnumDeserializer SERIALIZER_INSTANCE = new BaseEnumDeserializer();
@Override
public IEnum deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
public BaseEnum deserialize(JsonParser jsonParser, DeserializationContext deserializationContext)
throws IOException {
Class<?> targetClass = jsonParser.getCurrentValue().getClass();
String fieldName = jsonParser.getCurrentName();
String value = jsonParser.getText();
@ -49,7 +51,7 @@ public class IEnumDeserializer extends JsonDeserializer<IEnum> {
}
/**
* 通过某字段对应值获取枚举获取不到时为 {@code null}
* 通过某字段对应值获取枚举实例获取不到时为 {@code null}
*
* @param targetClass
* 目标类型
@ -57,17 +59,17 @@ public class IEnumDeserializer extends JsonDeserializer<IEnum> {
* 字段值
* @param fieldName
* 字段名
* @return 对应枚举 获取不到时为 {@code null}
* @return 对应枚举实例 获取不到时为 {@code null}
*/
public IEnum getEnum(Class<?> targetClass, String value, String fieldName) {
private BaseEnum getEnum(Class<?> targetClass, String value, String fieldName) {
Field field = ReflectUtil.getField(targetClass, fieldName);
Class<?> fieldTypeClass = field.getType();
Object[] enumConstants = fieldTypeClass.getEnumConstants();
for (Object enumConstant : enumConstants) {
if (ClassUtil.isAssignable(IEnum.class, fieldTypeClass)) {
IEnum iEnum = (IEnum)enumConstant;
if (iEnum.getValue().equals(Integer.valueOf(value))) {
return iEnum;
if (ClassUtil.isAssignable(BaseEnum.class, fieldTypeClass)) {
BaseEnum baseEnum = (BaseEnum)enumConstant;
if (baseEnum.getValue().equals(Integer.valueOf(value))) {
return baseEnum;
}
}
}

View File

@ -18,26 +18,27 @@ package top.charles7c.cnadmin.common.config.jackson;
import java.io.IOException;
import com.baomidou.mybatisplus.annotation.IEnum;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
import top.charles7c.cnadmin.common.base.BaseEnum;
/**
* 通用枚举接口 IEnum 序列化器
* 通用枚举接口 BaseEnum 序列化器
*
* @author Charles7c
* @since 2023/1/8 13:56
*/
@JacksonStdImpl
public class IEnumSerializer extends JsonSerializer<IEnum> {
public class BaseEnumSerializer extends JsonSerializer<BaseEnum> {
/** 静态实例 */
public static final IEnumSerializer SERIALIZER_INSTANCE = new IEnumSerializer();
public static final BaseEnumSerializer SERIALIZER_INSTANCE = new BaseEnumSerializer();
@Override
public void serialize(IEnum value, JsonGenerator generator, SerializerProvider serializers) throws IOException {
public void serialize(BaseEnum value, JsonGenerator generator, SerializerProvider serializers) throws IOException {
generator.writeObject(value.getValue());
}
}

View File

@ -31,7 +31,6 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import com.baomidou.mybatisplus.annotation.IEnum;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
@ -43,6 +42,8 @@ import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import top.charles7c.cnadmin.common.base.BaseEnum;
/**
* Jackson 配置
*
@ -89,15 +90,15 @@ public class JacksonConfiguration {
}
/**
* 针对通用枚举接口 IEnum 的序列化和反序列化
* 针对枚举基类 BaseEnum 的序列化和反序列化
*/
@Bean
public ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder) {
SimpleModule simpleModule = new SimpleModule();
simpleModule.addSerializer(IEnum.class, IEnumSerializer.SERIALIZER_INSTANCE);
simpleModule.addSerializer(BaseEnum.class, BaseEnumSerializer.SERIALIZER_INSTANCE);
SimpleDeserializersWrapper deserializers = new SimpleDeserializersWrapper();
deserializers.addDeserializer(IEnum.class, IEnumDeserializer.SERIALIZER_INSTANCE);
deserializers.addDeserializer(BaseEnum.class, BaseEnumDeserializer.SERIALIZER_INSTANCE);
simpleModule.setDeserializers(deserializers);
ObjectMapper objectMapper = builder.createXmlMapper(false).build();

View File

@ -38,7 +38,7 @@ import com.fasterxml.jackson.databind.type.ClassKey;
* 重写增强后<br>
* 1. 同默认 1<br>
* 2. 同默认 2<br>
* 3. 如果也找不到 Enum 类型所有枚举父类的反序列化器开始查找指定枚举类型的接口的反序列化器例如GenderEnum 枚举类型则是找它的接口 IEnum 的反序列化器<br>
* 3. 如果也找不到 Enum 类型所有枚举父类的反序列化器开始查找指定枚举类型的接口的反序列化器例如GenderEnum 枚举类型则是找它的接口 BaseEnum 的反序列化器<br>
* 4. 同默认 3
* </p>
*
@ -56,7 +56,7 @@ public class SimpleDeserializersWrapper extends SimpleDeserializers {
return deser;
}
// 重写增强开始查找指定枚举类型的接口的反序列化器例如GenderEnum 枚举类型则是找它的接口 IEnum 的反序列化器
// 重写增强开始查找指定枚举类型的接口的反序列化器例如GenderEnum 枚举类型则是找它的接口 BaseEnum 的反序列化器
for (Class<?> typeInterface : type.getInterfaces()) {
deser = this._classMappings.get(new ClassKey(typeInterface));
if (deser != null) {

View File

@ -49,4 +49,9 @@ public class CorsProperties {
* 允许跨域的请求头
*/
private List<String> allowedHeaders = new ArrayList<>();
/**
* 允许跨域的响应头
*/
private List<String> exposedHeaders = new ArrayList<>();
}

View File

@ -19,7 +19,7 @@ package top.charles7c.cnadmin.common.enums;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import com.baomidou.mybatisplus.annotation.IEnum;
import top.charles7c.cnadmin.common.base.BaseEnum;
/**
* 启用/禁用状态枚举
@ -29,7 +29,7 @@ import com.baomidou.mybatisplus.annotation.IEnum;
*/
@Getter
@RequiredArgsConstructor
public enum DisEnableStatusEnum implements IEnum<Integer> {
public enum DisEnableStatusEnum implements BaseEnum<Integer, String> {
/** 启用 */
ENABLE(1, "启用"),

View File

@ -19,7 +19,7 @@ package top.charles7c.cnadmin.common.enums;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import com.baomidou.mybatisplus.annotation.IEnum;
import top.charles7c.cnadmin.common.base.BaseEnum;
/**
* 性别枚举
@ -29,7 +29,7 @@ import com.baomidou.mybatisplus.annotation.IEnum;
*/
@Getter
@RequiredArgsConstructor
public enum GenderEnum implements IEnum<Integer> {
public enum GenderEnum implements BaseEnum<Integer, String> {
/** 未知 */
UNKNOWN(0, "未知"),

View File

@ -0,0 +1,95 @@
/*
* 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.util;
import java.util.Date;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import com.esotericsoftware.minlog.Log;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.URLUtil;
import top.charles7c.cnadmin.common.config.easyexcel.ExcelBigNumberConverter;
import top.charles7c.cnadmin.common.exception.ServiceException;
/**
* Excel 工具类
*
* @author Charles7c
* @since 2023/2/5 18:00
*/
@Slf4j
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class ExcelUtils {
/**
* 导出
*
* @param list
* 导出数据集合
* @param fileName
* 文件名
* @param clazz
* 导出数据类型
* @param response
* 响应对象
*/
public static <V> void export(List<V> list, String fileName, Class<V> clazz, HttpServletResponse response) {
export(list, fileName, "Sheet1", clazz, response);
}
/**
* 导出
*
* @param list
* 导出数据集合
* @param fileName
* 文件名
* @param sheetName
* 工作表名称
* @param clazz
* 导出数据类型
* @param response
* 响应对象
*/
public static <V> void export(List<V> list, String fileName, String sheetName, Class<V> clazz,
HttpServletResponse response) {
try {
fileName = String.format("%s_%s.xlsx", fileName, DateUtil.format(new Date(), "yyyyMMddHHmmss"));
fileName = URLUtil.encode(fileName);
response.setHeader("Content-disposition", "attachment;filename=" + fileName);
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8");
EasyExcel.write(response.getOutputStream(), clazz).autoCloseStream(false)
// 自动适配宽度
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
// 自动转换大数值
.registerConverter(new ExcelBigNumberConverter()).sheet(sheetName).doWrite(list);
} catch (Exception e) {
Log.error("Export excel occurred an error.", e);
throw new ServiceException("导出 Excel 出现错误");
}
}
}

View File

@ -91,6 +91,17 @@ public class ExceptionUtils {
return exToDefault(supplier, null, exConsumer);
}
/**
* 如果有异常返回空字符串
*
* @param exSupplier
* 可能会出现异常的方法执行
* @return /
*/
public static String exToBlank(ExSupplier<String> exSupplier) {
return exToDefault(exSupplier, "");
}
/**
* 如果有异常返回默认值
*

View File

@ -19,7 +19,7 @@ package top.charles7c.cnadmin.monitor.enums;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import com.baomidou.mybatisplus.annotation.IEnum;
import top.charles7c.cnadmin.common.base.BaseEnum;
/**
* 操作状态枚举
@ -29,7 +29,7 @@ import com.baomidou.mybatisplus.annotation.IEnum;
*/
@Getter
@RequiredArgsConstructor
public enum LogStatusEnum implements IEnum<Integer> {
public enum LogStatusEnum implements BaseEnum<Integer, String> {
/** 成功 */
SUCCESS(1, "成功"),

View File

@ -17,11 +17,14 @@
package top.charles7c.cnadmin.system.model.vo;
import lombok.Data;
import lombok.experimental.Accessors;
import io.swagger.v3.oas.annotations.media.Schema;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import top.charles7c.cnadmin.common.base.BaseDetailVO;
import top.charles7c.cnadmin.common.config.easyexcel.ExcelBaseEnumConverter;
import top.charles7c.cnadmin.common.enums.DisEnableStatusEnum;
/**
@ -31,7 +34,7 @@ import top.charles7c.cnadmin.common.enums.DisEnableStatusEnum;
* @since 2023/2/1 22:19
*/
@Data
@Accessors(chain = true)
@ExcelIgnoreUnannotated
@Schema(description = "部门详情信息")
public class DeptDetailVO extends BaseDetailVO {
@ -41,12 +44,14 @@ public class DeptDetailVO extends BaseDetailVO {
* 部门 ID
*/
@Schema(description = "部门 ID")
@ExcelProperty(value = "部门ID")
private Long deptId;
/**
* 部门名称
*/
@Schema(description = "部门名称")
@ExcelProperty(value = "部门名称")
private String deptName;
/**
@ -65,11 +70,13 @@ public class DeptDetailVO extends BaseDetailVO {
* 描述
*/
@Schema(description = "描述")
@ExcelProperty(value = "描述")
private String description;
/**
* 状态1启用 2禁用
*/
@Schema(description = "状态1启用 2禁用")
@ExcelProperty(value = "状态", converter = ExcelBaseEnumConverter.class)
private DisEnableStatusEnum status;
}

View File

@ -18,6 +18,8 @@ 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;
@ -64,4 +66,14 @@ public interface DeptService extends BaseService<DeptVO, DeptDetailVO, DeptQuery
* @return 是否存在
*/
boolean checkDeptNameExist(String deptName, Long parentId, Long deptId);
/**
* 导出
*
* @param query
* 查询条件
* @param response
* 响应对象
*/
void export(DeptQuery query, HttpServletResponse response);
}

View File

@ -17,10 +17,13 @@
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;
@ -37,6 +40,7 @@ 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.util.ExcelUtils;
import top.charles7c.cnadmin.common.util.ExceptionUtils;
import top.charles7c.cnadmin.common.util.TreeUtils;
import top.charles7c.cnadmin.common.util.helper.QueryHelper;
@ -65,13 +69,25 @@ public class DeptServiceImpl extends BaseServiceImpl<DeptMapper, DeptDO, DeptVO,
@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);
List<DeptVO> list = BeanUtil.copyToList(deptList, DeptVO.class);
list.forEach(this::fill);
return list;
return CollUtil.isNotEmpty(deptList) ? deptList : Collections.emptyList();
}
@Override
@ -170,6 +186,14 @@ public class DeptServiceImpl extends BaseServiceImpl<DeptMapper, DeptDO, DeptVO,
.eq(DeptDO::getParentId, parentId).ne(deptId != null, DeptDO::getDeptId, deptId));
}
@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);
}
/**
* 填充数据
*

View File

@ -1,9 +1,9 @@
import axios from 'axios';
import qs from 'query-string';
import { DeptParams } from '@/api/system/dept';
import { DeptParam } from '@/api/system/dept';
import { TreeNodeData } from '@arco-design/web-vue';
export default function listDeptTree(params: DeptParams) {
export default function listDeptTree(params: DeptParam) {
return axios.get<TreeNodeData[]>('/common/tree/dept', {
params,
paramsSerializer: (obj) => {

View File

@ -17,12 +17,12 @@ export interface DeptRecord {
children?: Array<DeptRecord>,
}
export interface DeptParams {
export interface DeptParam {
deptName?: string;
status?: number;
}
export function listDept(params: DeptParams) {
export function listDept(params: DeptParam) {
return axios.get<DeptRecord[]>(`${BASE_URL}/all`, {
params,
paramsSerializer: (obj) => {
@ -45,4 +45,14 @@ 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',
});
}

View File

@ -36,11 +36,17 @@ export interface HttpResponse<T = unknown> {
// response interceptors
axios.interceptors.response.use((response: AxiosResponse<HttpResponse>) => {
// 二进制数据则直接返回
if(response.request.responseType === 'blob' || response.request.responseType === 'arraybuffer'){
return response;
}
// 操作成功则直接返回
const res = response.data;
if (res.success) {
return res;
}
// 操作失败,弹出错误提示
Message.error({
content: res.msg,
duration: 3000

View File

@ -51,12 +51,12 @@
<a-button type="primary" status="danger" :disabled="multiple" :title="multiple ? '请选择要删除的数据' : ''" @click="handleBatchDelete">
<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" style="display: flex; align-items: center; justify-content: end">
<a-button type="primary" status="warning" disabled title="尚未开放">
<template #icon><icon-download /></template>导出
</a-button>
</a-col>
</a-row>
</div>
@ -239,12 +239,13 @@
import { SelectOptionData, TreeNodeData } from '@arco-design/web-vue';
import {
DeptRecord,
DeptParams,
DeptParam,
listDept,
getDept,
createDept,
updateDept,
deleteDept,
exportDept,
} from '@/api/system/dept';
import listDeptTree from '@/api/common';
@ -267,6 +268,7 @@
const multiple = 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[]>([
@ -296,7 +298,7 @@
*
* @param params 查询参数
*/
const getList = (params: DeptParams = { ...queryParams.value }) => {
const getList = (params: DeptParam = { ...queryParams.value }) => {
loading.value = true;
listDept(params).then((res) => {
deptList.value = res.data;
@ -309,29 +311,6 @@
};
getList();
/**
* 确定
*/
const handleOk = () => {
proxy.$refs.formRef.validate((valid: any) => {
if (!valid) {
if (form.value.deptId !== undefined) {
updateDept(form.value).then((res) => {
handleCancel();
getList();
proxy.$message.success(res.msg);
});
} else {
createDept(form.value).then((res) => {
handleCancel();
getList();
proxy.$message.success(res.msg);
});
}
}
});
};
/**
* 打开新增对话框
*/
@ -384,6 +363,29 @@
proxy.$refs.formRef.resetFields();
};
/**
* 确定
*/
const handleOk = () => {
proxy.$refs.formRef.validate((valid: any) => {
if (!valid) {
if (form.value.deptId !== undefined) {
updateDept(form.value).then((res) => {
handleCancel();
getList();
proxy.$message.success(res.msg);
});
} else {
createDept(form.value).then((res) => {
handleCancel();
getList();
proxy.$message.success(res.msg);
});
}
}
});
};
/**
* 查看详情
*
@ -407,20 +409,6 @@
detailVisible.value = false;
};
/**
* 修改状态
*
* @param record 记录信息
*/
const handleChangeStatus = (record: DeptRecord) => {
const tip = record.status === 1 ? '启用' : '禁用';
updateDept(record).then((res) => {
proxy.$message.success(`${tip}成功`);
}).catch(() => {
record.status = (record.status === 1) ? 2 : 1;
});
};
/**
* 批量删除
*/
@ -463,6 +451,52 @@
multiple.value = !rowKeys.length;
};
/**
* 导出
*/
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);
}).finally(() => {
exportLoading.value = false;
});
};
/**
* 修改状态
*
* @param record 记录信息
*/
const handleChangeStatus = (record: DeptRecord) => {
const tip = record.status === 1 ? '启用' : '禁用';
updateDept(record).then((res) => {
proxy.$message.success(`${tip}成功`);
}).catch(() => {
record.status = (record.status === 1) ? 2 : 1;
});
};
/**
* 过滤部门树
*

View File

@ -148,7 +148,7 @@
captchaLoading.value = false;
captchaDisable.value = true;
captchaBtnNameKey.value = `${t('userCenter.securitySettings.updateEmail.form.reSendCaptcha')}(${captchaTime.value -= 1}s)`;
captchaTimer.value = window.setInterval(function() {
captchaTimer.value = window.setInterval(() => {
captchaTime.value -= 1;
captchaBtnNameKey.value = `${t('userCenter.securitySettings.updateEmail.form.reSendCaptcha')}(${captchaTime.value}s)`;
if (captchaTime.value < 0) {

View File

@ -20,6 +20,8 @@ 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;
@ -52,4 +54,10 @@ public class DeptController extends BaseController<DeptService, DeptVO, DeptDeta
List<DeptVO> list = baseService.list(query);
return R.ok(baseService.buildListTree(list));
}
@Operation(summary = "导出部门数据")
@GetMapping("/export")
public void export(@Validated DeptQuery query, HttpServletResponse response) {
baseService.export(query, response);
}
}

View File

@ -187,3 +187,5 @@ cors:
allowedMethods: '*'
# 配置允许跨域的请求头
allowedHeaders: '*'
# 配置允许跨域的响应头
exposedHeaders: '*'

View File

@ -178,3 +178,5 @@ cors:
allowedMethods: '*'
# 配置允许跨域的请求头
allowedHeaders: '*'
# 配置允许跨域的响应头
exposedHeaders: '*'

View File

@ -51,6 +51,7 @@ limitations under the License.
<p6spy.version>3.9.1</p6spy.version>
<!-- ### 工具库相关 ### -->
<easyexcel.version>3.2.0</easyexcel.version>
<ip2region.version>2.7.6</ip2region.version>
<knife4j.version>4.0.0</knife4j.version>
<redisson.version>3.19.0</redisson.version>
@ -113,6 +114,13 @@ limitations under the License.
</dependency>
<!-- ################ 工具库相关 ################ -->
<!-- Easy Excel一个基于 Java 的、快速、简洁、解决大文件内存溢出的 Excel 处理工具) -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>${easyexcel.version}</version>
</dependency>
<!-- 第三方封装 Ip2region离线 IP 数据管理框架和定位库支持亿级别的数据段10 微秒级别的查询性能,提供了许多主流编程语言的 xdb 数据管理引擎的实现) -->
<dependency>
<groupId>net.dreamlu</groupId>