mirror of
https://gitee.com/lab1024/smart-admin.git
synced 2025-09-17 10:56:39 +08:00
v3.8.0【优化】简介明了的数据范围说明文档;【优化】Long序列化;【优化】标签页右键关闭;【优化】表格排序Demo
This commit is contained in:
parent
753283191a
commit
053d562157
@ -1,9 +1,11 @@
|
||||
package net.lab1024.sa.admin.module.business.goods.domain.form;
|
||||
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import net.lab1024.sa.admin.module.business.goods.constant.GoodsStatusEnum;
|
||||
import net.lab1024.sa.base.common.domain.PageParam;
|
||||
import net.lab1024.sa.base.common.json.deserializer.DictValueVoDeserializer;
|
||||
import net.lab1024.sa.base.common.swagger.SchemaEnum;
|
||||
import net.lab1024.sa.base.common.validator.enumeration.CheckEnum;
|
||||
import org.hibernate.validator.constraints.Length;
|
||||
@ -32,6 +34,7 @@ public class GoodsQueryForm extends PageParam {
|
||||
private Integer goodsStatus;
|
||||
|
||||
@Schema(description = "产地")
|
||||
@JsonDeserialize(using = DictValueVoDeserializer.class)
|
||||
private String place;
|
||||
|
||||
@Schema(description = "上架状态")
|
||||
|
@ -52,7 +52,6 @@ public class NoticeEmployeeService {
|
||||
public ResponseDTO<PageResult<NoticeEmployeeVO>> queryList(Long requestEmployeeId, NoticeEmployeeQueryForm noticeEmployeeQueryForm) {
|
||||
Page<?> page = SmartPageUtil.convert2PageQuery(noticeEmployeeQueryForm);
|
||||
|
||||
//获取请求人的 部门及其子部门
|
||||
List<Long> employeeDepartmentIdList = Lists.newArrayList();
|
||||
EmployeeEntity employeeEntity = employeeService.getById(requestEmployeeId);
|
||||
// 如果不是管理员 则获取请求人的 部门及其子部门
|
||||
|
@ -25,7 +25,7 @@ import java.util.List;
|
||||
* @Date 2021-12-09 22:57:49
|
||||
* @Wechat zhuoda1024
|
||||
* @Email lab1024@163.com
|
||||
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>
|
||||
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>
|
||||
*/
|
||||
@RestController
|
||||
@Tag(name = AdminSwaggerTagConst.System.SYSTEM_EMPLOYEE)
|
||||
@ -110,7 +110,7 @@ public class EmployeeController {
|
||||
@Operation(summary = "重置员工密码 @author 卓大")
|
||||
@GetMapping("/employee/update/password/reset/{employeeId}")
|
||||
@SaCheckPermission("system:employee:password:reset")
|
||||
public ResponseDTO<String> resetPassword(@PathVariable Integer employeeId) {
|
||||
public ResponseDTO<String> resetPassword(@PathVariable Long employeeId) {
|
||||
return employeeService.resetPassword(employeeId);
|
||||
}
|
||||
|
||||
|
@ -113,6 +113,6 @@ public interface EmployeeDao extends BaseMapper<EmployeeEntity> {
|
||||
/**
|
||||
* 员工重置密码
|
||||
*/
|
||||
Integer updatePassword(@Param("employeeId") Integer employeeId, @Param("password") String password);
|
||||
Integer updatePassword(@Param("employeeId") Long employeeId, @Param("password") String password);
|
||||
|
||||
}
|
@ -47,8 +47,6 @@ import java.util.stream.Collectors;
|
||||
@Service
|
||||
public class EmployeeService {
|
||||
|
||||
private static final String PASSWORD_SALT_FORMAT = "smart_%s_admin_$^&*";
|
||||
|
||||
@Resource
|
||||
private EmployeeDao employeeDao;
|
||||
|
||||
@ -364,7 +362,7 @@ public class EmployeeService {
|
||||
/**
|
||||
* 重置密码
|
||||
*/
|
||||
public ResponseDTO<String> resetPassword(Integer employeeId) {
|
||||
public ResponseDTO<String> resetPassword(Long employeeId) {
|
||||
String password = securityPasswordService.randomPassword();
|
||||
employeeDao.updatePassword(employeeId, SecurityPasswordService.getEncryptPwd(password));
|
||||
return ResponseDTO.ok(password);
|
||||
|
@ -16,7 +16,7 @@ import java.util.List;
|
||||
* @Date 2022-04-08 21:53:04
|
||||
* @Wechat zhuoda1024
|
||||
* @Email lab1024@163.com
|
||||
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>
|
||||
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>
|
||||
*/
|
||||
@Service
|
||||
public class RoleEmployeeManager extends ServiceImpl<RoleEmployeeDao, RoleEmployeeEntity> {
|
||||
|
@ -29,7 +29,7 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
* @Date 2023/10/17 19:07:27
|
||||
* @Wechat zhuoda1024
|
||||
* @Email lab1024@163.com
|
||||
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>,Since 2012
|
||||
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>,Since 2012
|
||||
*/
|
||||
|
||||
@RestController
|
||||
|
@ -95,25 +95,25 @@
|
||||
left join t_notice_type on t_notice.notice_type_id = t_notice_type.notice_type_id
|
||||
<where>
|
||||
<if test="!administratorFlag">
|
||||
(
|
||||
(
|
||||
t_notice.notice_id in
|
||||
(select t_notice_visible_range.notice_id
|
||||
from t_notice_visible_range
|
||||
where
|
||||
( t_notice_visible_range.data_type = #{departmentDataType}
|
||||
<if test="requestEmployeeDepartmentIdList != null and requestEmployeeDepartmentIdList.size > 0">
|
||||
and
|
||||
t_notice_visible_range.data_id
|
||||
in
|
||||
<foreach collection="requestEmployeeDepartmentIdList" open="(" close=")" separator="," item="item">
|
||||
#{item}
|
||||
</foreach>
|
||||
</if>
|
||||
)
|
||||
or ( t_notice_visible_range.data_type = #{employeeDataType} and t_notice_visible_range.data_id = #{requestEmployeeId} )
|
||||
<if test="requestEmployeeDepartmentIdList != null and requestEmployeeDepartmentIdList.size > 0">
|
||||
and
|
||||
t_notice_visible_range.data_id
|
||||
in
|
||||
<foreach collection="requestEmployeeDepartmentIdList" open="(" close=")" separator="," item="item">
|
||||
#{item}
|
||||
</foreach>
|
||||
</if>
|
||||
)
|
||||
or ( t_notice_visible_range.data_type = #{employeeDataType} and t_notice_visible_range.data_id = #{requestEmployeeId} )
|
||||
)
|
||||
or t_notice.all_visible_flag = true
|
||||
)
|
||||
)
|
||||
</if>
|
||||
|
||||
and t_notice.deleted_flag = #{deletedFlag}
|
||||
|
@ -8,7 +8,7 @@
|
||||
# 项目配置: 名称、日志目录
|
||||
project:
|
||||
name: sa-admin
|
||||
log-directory: /home/project/smartadmin/sit/log
|
||||
log-directory: /home/project/smartadmin/test/log
|
||||
|
||||
# 项目端口和url根路径
|
||||
server:
|
||||
|
@ -14,15 +14,24 @@ import java.io.IOException;
|
||||
* @Date 2020-06-02 22:55:07
|
||||
* @Wechat zhuoda1024
|
||||
* @Email lab1024@163.com
|
||||
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>
|
||||
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>
|
||||
*/
|
||||
public class LongJsonSerializer extends JsonSerializer<Long> {
|
||||
|
||||
public static final LongJsonSerializer INSTANCE = new LongJsonSerializer();
|
||||
|
||||
@Override
|
||||
public void serialize(Long value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
|
||||
String text = (value == null ? null : String.valueOf(value));
|
||||
if (text != null) {
|
||||
jsonGenerator.writeString(text);
|
||||
public void serialize(Long value, JsonGenerator gen, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
|
||||
if (null == value) {
|
||||
gen.writeNull();
|
||||
return;
|
||||
}
|
||||
// js中最大安全整数16位 Number.MAX_SAFE_INTEGER
|
||||
String longStr = String.valueOf(value);
|
||||
if (longStr.length() > 16) {
|
||||
gen.writeString(longStr);
|
||||
} else {
|
||||
gen.writeNumber(value);
|
||||
}
|
||||
}
|
||||
}
|
@ -6,6 +6,7 @@ import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
|
||||
import net.lab1024.sa.base.common.json.serializer.LongJsonSerializer;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
@ -17,16 +18,16 @@ import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeParseException;
|
||||
|
||||
/**
|
||||
* java8 localDate 时间类格式化配置
|
||||
* Json 序列化配置
|
||||
*
|
||||
* @Author 1024创新实验室-主任: 卓大
|
||||
* @Date 2017-11-28 15:21:10
|
||||
* @Wechat zhuoda1024
|
||||
* @Email lab1024@163.com
|
||||
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>
|
||||
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>
|
||||
*/
|
||||
@Configuration
|
||||
public class DateConfig {
|
||||
public class JsonConfig {
|
||||
|
||||
@Bean
|
||||
public Jackson2ObjectMapperBuilderCustomizer customizer() {
|
||||
@ -35,6 +36,7 @@ public class DateConfig {
|
||||
builder.deserializers(new LocalDateTimeDeserializer(DatePattern.NORM_DATETIME_FORMAT.getDateTimeFormatter()));
|
||||
builder.serializers(new LocalDateSerializer(DatePattern.NORM_DATE_FORMAT.getDateTimeFormatter()));
|
||||
builder.serializers(new LocalDateTimeSerializer(DatePattern.NORM_DATETIME_FORMAT.getDateTimeFormatter()));
|
||||
builder.serializerByType(Long.class, LongJsonSerializer.INSTANCE);
|
||||
};
|
||||
}
|
||||
|
@ -31,10 +31,9 @@ import java.util.Optional;
|
||||
/**
|
||||
* springdoc-openapi 配置
|
||||
* nginx配置前缀时如果需要访问【/swagger-ui/index.html】需添加额外nginx配置
|
||||
* location /v3/api-docs/ {
|
||||
* proxy_pass http://127.0.0.1:11024/v3/api-docs/;
|
||||
* location /v3/api-docs/ {
|
||||
* proxy_pass http://127.0.0.1:1024/v3/api-docs/;
|
||||
* }
|
||||
*
|
||||
* @Author 1024创新实验室-主任: 卓大
|
||||
* @Date 2020-03-25 22:54:46
|
||||
* @Wechat zhuoda1024
|
||||
@ -48,7 +47,7 @@ public class SwaggerConfig {
|
||||
/**
|
||||
* 用于解决/swagger-ui/index.html页面ServersUrl 测试环境部署错误问题
|
||||
*/
|
||||
@Value("${springdoc.swagger-ui.server-base-url:''}")
|
||||
@Value("${springdoc.swagger-ui.server-base-url}")
|
||||
private String serverBaseUrl;
|
||||
|
||||
public static final String[] SWAGGER_WHITELIST = {
|
||||
@ -59,20 +58,20 @@ public class SwaggerConfig {
|
||||
"/v3/api-docs",
|
||||
"/v3/api-docs/**",
|
||||
"/doc.html",
|
||||
};
|
||||
};
|
||||
|
||||
@Bean
|
||||
public OpenAPI api() {
|
||||
return new OpenAPI()
|
||||
.components(components())
|
||||
.info(new Info()
|
||||
.title("SmartAdmin 3.X 接口文档")
|
||||
.contact(new Contact().name("1024创新实验室").email("lab1024@163.com").url("https://1024lab.net"))
|
||||
.version("v3.X")
|
||||
.description("<font color=\"#DC143C\">**以「高质量代码」为核心,「简洁、高效、安全」**</font>基于 SpringBoot + Sa-Token + Mybatis-Plus 和 Vue3 + Vite5 + Ant Design (同时支持JavaScript和TypeScript双版本) 的快速开发平台。" +
|
||||
"<br/><font color=\"#DC143C\">**国内首个满足《网络安全》、《数据安全》、三级等保**</font>, 支持登录限制、支持国产接口加解密等安全、支持数据加解密等一系列安全体系的开源项目。" +
|
||||
"<br/><font color=\"#DC143C\">**我们开源一套漂亮的代码和一套整洁的代码规范**</font>,让大家在这浮躁的代码世界里感受到一股把代码写好的清流!同时又让开发者节省大量的时间,减少加班,快乐工作,保持谦逊,保持学习,热爱代码,更热爱生活!")
|
||||
)
|
||||
.title("SmartAdmin 3.X 接口文档")
|
||||
.contact(new Contact().name("1024创新实验室").email("lab1024@163.com").url("https://1024lab.net"))
|
||||
.version("v3.X")
|
||||
.description("<font color=\"#DC143C\">**以「高质量代码」为核心,「简洁、高效、安全」**</font>基于 SpringBoot + Sa-Token + Mybatis-Plus 和 Vue3 + Vite5 + Ant Design (同时支持JavaScript和TypeScript双版本) 的快速开发平台。" +
|
||||
"<br/><font color=\"#DC143C\">**国内首个满足《网络安全》、《数据安全》、三级等保**</font>, 支持登录限制、支持国产接口加解密等安全、支持数据加解密等一系列安全体系的开源项目。" +
|
||||
"<br/><font color=\"#DC143C\">**我们开源一套漂亮的代码和一套整洁的代码规范**</font>,让大家在这浮躁的代码世界里感受到一股把代码写好的清流!同时又让开发者节省大量的时间,减少加班,快乐工作,保持谦逊,保持学习,热爱代码,更热爱生活!")
|
||||
)
|
||||
.addSecurityItem(new SecurityRequirement().addList(RequestHeaderConst.TOKEN));
|
||||
}
|
||||
|
||||
@ -84,27 +83,26 @@ public class SwaggerConfig {
|
||||
@Bean
|
||||
public GroupedOpenApi businessApi() {
|
||||
return GroupedOpenApi.builder()
|
||||
.group("业务接口")
|
||||
.pathsToMatch("/**")
|
||||
.pathsToExclude(SwaggerTagConst.Support.URL_PREFIX + "/**")
|
||||
.addOperationCustomizer(new SmartOperationCustomizer())
|
||||
.build();
|
||||
.group("业务接口")
|
||||
.pathsToMatch("/**")
|
||||
.pathsToExclude(SwaggerTagConst.Support.URL_PREFIX + "/**")
|
||||
.addOperationCustomizer(new SmartOperationCustomizer())
|
||||
.build();
|
||||
|
||||
}
|
||||
|
||||
@Bean
|
||||
public GroupedOpenApi supportApi() {
|
||||
return GroupedOpenApi.builder()
|
||||
.group("支撑接口(Support)")
|
||||
.pathsToMatch(SwaggerTagConst.Support.URL_PREFIX + "/**")
|
||||
.addOperationCustomizer(new SmartOperationCustomizer())
|
||||
.build();
|
||||
.group("支撑接口(Support)")
|
||||
.pathsToMatch(SwaggerTagConst.Support.URL_PREFIX + "/**")
|
||||
.addOperationCustomizer(new SmartOperationCustomizer())
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 以下代码可以用于设置 /swagger-ui/index.html 的serverBaseUrl
|
||||
* 如果使用knife4j则不需要
|
||||
*
|
||||
* @param openAPI
|
||||
* @param securityParser
|
||||
* @param springDocConfigProperties
|
||||
@ -132,6 +130,6 @@ public class SwaggerConfig {
|
||||
}
|
||||
});
|
||||
return new OpenAPIService(openAPI, securityParser, springDocConfigProperties,
|
||||
propertyResolverUtils, openApiBuilderCustomizers, Optional.of(list), javadocProvider);
|
||||
propertyResolverUtils, openApiBuilderCustomizers, Optional.of(list), javadocProvider);
|
||||
}
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ import java.util.Set;
|
||||
* @Date 2022-05-30 21:22:12
|
||||
* @Wechat zhuoda1024
|
||||
* @Email lab1024@163.com
|
||||
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>
|
||||
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>
|
||||
*/
|
||||
@Configuration
|
||||
@Slf4j
|
||||
|
@ -67,14 +67,14 @@ public class WebServerListener implements ApplicationListener<WebServerInitializ
|
||||
String swaggerUrl = URLUtil.normalize(String.format("http://localhost:%d%s/swagger-ui/index.html", port, contextPath), false, true);
|
||||
String knife4jUrl = URLUtil.normalize(String.format("http://localhost:%d%s/doc.html", port, contextPath), false, true);
|
||||
log.warn("\n{}\n" +
|
||||
"\t当前启动环境:\t{} , {}" +
|
||||
"\n\t返回码初始化:\t完成{}个返回码初始化" +
|
||||
"\n\t服务本机地址:\t{}" +
|
||||
"\n\t服务外网地址:\t{}" +
|
||||
"\n\tSwagger地址:\t{}" +
|
||||
"\n\tknife4j地址:\t{}" +
|
||||
"\n-------------------------------------------------------------------------------------\n",
|
||||
title, profile, environmentEnum.getDesc(), codeCount, localhostUrl, externalUrl, swaggerUrl, knife4jUrl);
|
||||
"\t当前启动环境:\t{} , {}" +
|
||||
"\n\t返回码初始化:\t完成{}个返回码初始化" +
|
||||
"\n\t服务本机地址:\t{}" +
|
||||
"\n\t服务外网地址:\t{}" +
|
||||
"\n\tSwagger地址:\t{}" +
|
||||
"\n\tknife4j地址:\t{}" +
|
||||
"\n-------------------------------------------------------------------------------------\n",
|
||||
title, profile, environmentEnum.getDesc(), codeCount, localhostUrl, externalUrl, swaggerUrl, knife4jUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,6 +1,8 @@
|
||||
package net.lab1024.sa.base.module.support.apiencrypt.advice;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.lab1024.sa.base.common.domain.ResponseDTO;
|
||||
@ -33,6 +35,9 @@ public class EncryptResponseAdvice implements ResponseBodyAdvice<ResponseDTO> {
|
||||
@Resource
|
||||
private ApiEncryptService apiEncryptService;
|
||||
|
||||
@Resource
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
@Override
|
||||
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
|
||||
return returnType.hasMethodAnnotation(ApiEncrypt.class) || returnType.getContainingClass().isAnnotationPresent(ApiEncrypt.class);
|
||||
@ -44,7 +49,12 @@ public class EncryptResponseAdvice implements ResponseBodyAdvice<ResponseDTO> {
|
||||
return body;
|
||||
}
|
||||
|
||||
String encrypt = apiEncryptService.encrypt(JSON.toJSONString(body.getData()));
|
||||
String encrypt = null;
|
||||
try {
|
||||
encrypt = apiEncryptService.encrypt(objectMapper.writeValueAsString(body.getData()));
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
body.setData(encrypt);
|
||||
body.setDataType(DataTypeEnum.ENCRYPT.getValue());
|
||||
return body;
|
||||
|
@ -52,14 +52,6 @@ public class ApiEncryptServiceSmImpl implements ApiEncryptService {
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
String content = "zkm1024";
|
||||
String en = new ApiEncryptServiceSmImpl().encrypt(content);
|
||||
System.out.println(en);
|
||||
String ori = new ApiEncryptServiceSmImpl().decrypt(en);
|
||||
System.out.println(ori);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String decrypt(String data) {
|
||||
|
@ -45,7 +45,7 @@ import java.util.stream.Collectors;
|
||||
* @Date 2022-06-30 22:15:38
|
||||
* @Wechat zhuoda1024
|
||||
* @Email lab1024@163.com
|
||||
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>
|
||||
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>
|
||||
*/
|
||||
|
||||
@Service
|
||||
|
@ -18,7 +18,7 @@ import java.util.stream.Collectors;
|
||||
* @Date 2022/9/29 17:20:41
|
||||
* @Wechat zhuoda1024
|
||||
* @Email lab1024@163.com
|
||||
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>
|
||||
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>
|
||||
*/
|
||||
public abstract class CodeGenerateBaseVariableService {
|
||||
|
||||
|
@ -101,7 +101,6 @@ public class AddFormVariableService extends CodeGenerateBaseVariableService {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//字典
|
||||
if (SmartStringUtil.isNotEmpty(codeField.getDict())) {
|
||||
finalFieldMap.put("dict", "\n @JsonDeserialize(using = DictValueVoDeserializer.class)");
|
||||
|
@ -15,7 +15,7 @@ import java.util.*;
|
||||
* @Date 2022/9/29 17:20:41
|
||||
* @Wechat zhuoda1024
|
||||
* @Email lab1024@163.com
|
||||
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>
|
||||
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>
|
||||
*/
|
||||
|
||||
public class MapperVariableService extends CodeGenerateBaseVariableService {
|
||||
|
@ -18,7 +18,7 @@ import java.util.stream.Collectors;
|
||||
* @Date 2022/9/29 17:20:41
|
||||
* @Wechat zhuoda1024
|
||||
* @Email lab1024@163.com
|
||||
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>
|
||||
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>
|
||||
*/
|
||||
|
||||
public class QueryFormVariableService extends CodeGenerateBaseVariableService {
|
||||
|
@ -17,7 +17,7 @@ import java.util.stream.Collectors;
|
||||
* @Date 2022/9/29 17:20:41
|
||||
* @Wechat zhuoda1024
|
||||
* @Email lab1024@163.com
|
||||
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>
|
||||
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>
|
||||
*/
|
||||
|
||||
public class VOVariableService extends CodeGenerateBaseVariableService {
|
||||
|
@ -39,7 +39,7 @@ import java.util.stream.Collectors;
|
||||
* @Date 2019年10月11日 15:34:47
|
||||
* @Wechat zhuoda1024
|
||||
* @Email lab1024@163.com
|
||||
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>
|
||||
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>
|
||||
*/
|
||||
@Service
|
||||
public class FileService {
|
||||
|
@ -4,13 +4,13 @@ spring:
|
||||
url: jdbc:p6spy:mysql://127.0.0.1:3306/smart_admin_v3?autoReconnect=true&useServerPreparedStmts=false&rewriteBatchedStatements=true&characterEncoding=UTF-8&useSSL=false&allowMultiQueries=true&serverTimezone=Asia/Shanghai
|
||||
username: root
|
||||
password: SmartAdmin666
|
||||
driver-class-name: com.p6spy.engine.spy.P6SpyDriver
|
||||
initial-size: 2
|
||||
min-idle: 2
|
||||
max-active: 10
|
||||
max-wait: 60000
|
||||
time-between-eviction-runs-millis: 60000
|
||||
min-evictable-idle-time-millis: 300000
|
||||
driver-class-name: com.p6spy.engine.spy.P6SpyDriver
|
||||
filters: stat
|
||||
druid:
|
||||
username: druid
|
||||
@ -40,7 +40,7 @@ spring:
|
||||
host: smtp.163.com
|
||||
port: 465
|
||||
username: lab1024@163.com
|
||||
password: 1
|
||||
password: 1024lab
|
||||
properties:
|
||||
mail:
|
||||
smtp:
|
||||
|
@ -40,7 +40,7 @@ spring:
|
||||
host: smtp.163.com
|
||||
port: 465
|
||||
username: lab1024@163.com
|
||||
password: 1
|
||||
password: 1024lab
|
||||
properties:
|
||||
mail:
|
||||
smtp:
|
||||
|
@ -30,10 +30,11 @@ spring:
|
||||
timeout: 10000ms
|
||||
lettuce:
|
||||
pool:
|
||||
max-active: 100
|
||||
min-idle: 10
|
||||
max-idle: 50
|
||||
max-active: 5
|
||||
min-idle: 1
|
||||
max-idle: 3
|
||||
max-wait: 30000ms
|
||||
|
||||
# 邮件,置以SSL的方式发送, 这个需要使用这种方式并且端口是465
|
||||
mail:
|
||||
host: smtp.163.com
|
||||
|
@ -40,7 +40,7 @@ spring:
|
||||
host: smtp.163.com
|
||||
port: 465
|
||||
username: lab1024@163.com
|
||||
password: 1
|
||||
password: 1024lab
|
||||
properties:
|
||||
mail:
|
||||
smtp:
|
||||
|
@ -86,7 +86,7 @@ public class AdminInterceptor implements HandlerInterceptor {
|
||||
Method method = ((HandlerMethod) handler).getMethod();
|
||||
NoNeedLogin noNeedLogin = ((HandlerMethod) handler).getMethodAnnotation(NoNeedLogin.class);
|
||||
if (noNeedLogin != null) {
|
||||
checkActiveTimeout(requestEmployee,debugNumberTokenFlag);
|
||||
checkActiveTimeout(requestEmployee, debugNumberTokenFlag);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -96,7 +96,7 @@ public class AdminInterceptor implements HandlerInterceptor {
|
||||
}
|
||||
|
||||
// 检测token 活跃频率
|
||||
checkActiveTimeout(requestEmployee,debugNumberTokenFlag);
|
||||
checkActiveTimeout(requestEmployee, debugNumberTokenFlag);
|
||||
|
||||
|
||||
// --------------- 第三步: 校验 权限 ---------------
|
||||
@ -107,8 +107,8 @@ public class AdminInterceptor implements HandlerInterceptor {
|
||||
}
|
||||
|
||||
// 如果是超级管理员的话,不需要校验权限
|
||||
if(requestEmployee.getAdministratorFlag()){
|
||||
return true;
|
||||
if (requestEmployee.getAdministratorFlag()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
SaStrategy.instance.checkMethodAnnotation.accept(method);
|
||||
|
@ -110,7 +110,7 @@ public class EmployeeController {
|
||||
@Operation(summary = "重置员工密码 @author 卓大")
|
||||
@GetMapping("/employee/update/password/reset/{employeeId}")
|
||||
@SaCheckPermission("system:employee:password:reset")
|
||||
public ResponseDTO<String> resetPassword(@PathVariable Integer employeeId) {
|
||||
public ResponseDTO<String> resetPassword(@PathVariable Long employeeId) {
|
||||
return employeeService.resetPassword(employeeId);
|
||||
}
|
||||
|
||||
|
@ -113,6 +113,6 @@ public interface EmployeeDao extends BaseMapper<EmployeeEntity> {
|
||||
/**
|
||||
* 员工重置密码
|
||||
*/
|
||||
Integer updatePassword(@Param("employeeId") Integer employeeId, @Param("password") String password);
|
||||
Integer updatePassword(@Param("employeeId") Long employeeId, @Param("password") String password);
|
||||
|
||||
}
|
@ -362,7 +362,7 @@ public class EmployeeService {
|
||||
/**
|
||||
* 重置密码
|
||||
*/
|
||||
public ResponseDTO<String> resetPassword(Integer employeeId) {
|
||||
public ResponseDTO<String> resetPassword(Long employeeId) {
|
||||
String password = securityPasswordService.randomPassword();
|
||||
employeeDao.updatePassword(employeeId, SecurityPasswordService.getEncryptPwd(password));
|
||||
return ResponseDTO.ok(password);
|
||||
|
@ -14,15 +14,24 @@ import java.io.IOException;
|
||||
* @Date 2020-06-02 22:55:07
|
||||
* @Wechat zhuoda1024
|
||||
* @Email lab1024@163.com
|
||||
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>
|
||||
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>
|
||||
*/
|
||||
public class LongJsonSerializer extends JsonSerializer<Long> {
|
||||
|
||||
public static final LongJsonSerializer INSTANCE = new LongJsonSerializer();
|
||||
|
||||
@Override
|
||||
public void serialize(Long value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
|
||||
String text = (value == null ? null : String.valueOf(value));
|
||||
if (text != null) {
|
||||
jsonGenerator.writeString(text);
|
||||
public void serialize(Long value, JsonGenerator gen, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
|
||||
if (null == value) {
|
||||
gen.writeNull();
|
||||
return;
|
||||
}
|
||||
// js中最大安全整数16位 Number.MAX_SAFE_INTEGER
|
||||
String longStr = String.valueOf(value);
|
||||
if (longStr.length() > 16) {
|
||||
gen.writeString(longStr);
|
||||
} else {
|
||||
gen.writeNumber(value);
|
||||
}
|
||||
}
|
||||
}
|
@ -6,6 +6,7 @@ import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
|
||||
import net.lab1024.sa.base.common.json.serializer.LongJsonSerializer;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
@ -17,16 +18,16 @@ import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeParseException;
|
||||
|
||||
/**
|
||||
* java8 localDate 时间类格式化配置
|
||||
* json 序列化配置
|
||||
*
|
||||
* @Author 1024创新实验室-主任: 卓大
|
||||
* @Date 2017-11-28 15:21:10
|
||||
* @Wechat zhuoda1024
|
||||
* @Email lab1024@163.com
|
||||
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>
|
||||
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>
|
||||
*/
|
||||
@Configuration
|
||||
public class DateConfig {
|
||||
public class JsonConfig {
|
||||
|
||||
@Bean
|
||||
public Jackson2ObjectMapperBuilderCustomizer customizer() {
|
||||
@ -35,6 +36,7 @@ public class DateConfig {
|
||||
builder.deserializers(new LocalDateTimeDeserializer(DatePattern.NORM_DATETIME_FORMAT.getDateTimeFormatter()));
|
||||
builder.serializers(new LocalDateSerializer(DatePattern.NORM_DATE_FORMAT.getDateTimeFormatter()));
|
||||
builder.serializers(new LocalDateTimeSerializer(DatePattern.NORM_DATETIME_FORMAT.getDateTimeFormatter()));
|
||||
builder.serializerByType(Long.class, LongJsonSerializer.INSTANCE);
|
||||
};
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
package net.lab1024.sa.base.module.support.apiencrypt.advice;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.lab1024.sa.base.common.domain.ResponseDTO;
|
||||
import net.lab1024.sa.base.common.enumeration.DataTypeEnum;
|
||||
@ -34,6 +36,9 @@ public class EncryptResponseAdvice implements ResponseBodyAdvice<ResponseDTO> {
|
||||
@Resource
|
||||
private ApiEncryptService apiEncryptService;
|
||||
|
||||
@Resource
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
@Override
|
||||
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
|
||||
return returnType.hasMethodAnnotation(ApiEncrypt.class) || returnType.getContainingClass().isAnnotationPresent(ApiEncrypt.class);
|
||||
@ -45,7 +50,12 @@ public class EncryptResponseAdvice implements ResponseBodyAdvice<ResponseDTO> {
|
||||
return body;
|
||||
}
|
||||
|
||||
String encrypt = apiEncryptService.encrypt(JSON.toJSONString(body.getData()));
|
||||
String encrypt = null;
|
||||
try {
|
||||
encrypt = apiEncryptService.encrypt(objectMapper.writeValueAsString(body.getData()));
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
body.setData(encrypt);
|
||||
body.setDataType(DataTypeEnum.ENCRYPT.getValue());
|
||||
return body;
|
||||
|
@ -39,7 +39,7 @@ spring:
|
||||
host: smtp.163.com
|
||||
port: 465
|
||||
username: lab1024@163.com
|
||||
password: 1
|
||||
password: 1024lab
|
||||
properties:
|
||||
mail:
|
||||
smtp:
|
||||
|
@ -39,7 +39,7 @@ spring:
|
||||
host: smtp.163.com
|
||||
port: 465
|
||||
username: lab1024@163.com
|
||||
password: 1
|
||||
password: 1024lab
|
||||
properties:
|
||||
mail:
|
||||
smtp:
|
||||
|
@ -39,7 +39,7 @@ spring:
|
||||
host: smtp.163.com
|
||||
port: 465
|
||||
username: lab1024@163.com
|
||||
password: 1
|
||||
password: 1024lab
|
||||
properties:
|
||||
mail:
|
||||
smtp:
|
||||
|
@ -32,6 +32,7 @@
|
||||
},
|
||||
},
|
||||
}"
|
||||
:getPopupContainer="getPopupContainer"
|
||||
>
|
||||
<!---全局loading--->
|
||||
<a-spin :spinning="spinning" tip="稍等片刻,我在拼命加载中..." size="large">
|
||||
@ -49,6 +50,8 @@
|
||||
import { useSpinStore } from '/@/store/modules/system/spin';
|
||||
import { theme } from 'ant-design-vue';
|
||||
import { themeColors } from '/@/theme/color.js';
|
||||
import { SmartLoading } from '/@/components/framework/smart-loading/index.js';
|
||||
import { LAYOUT_ELEMENT_IDS } from '/@/layout/layout-const.js';
|
||||
|
||||
const antdLocale = computed(() => messages[useAppConfigStore().language].antdLocale);
|
||||
const dayjsLocale = computed(() => messages[useAppConfigStore().language].dayjsLocale);
|
||||
@ -67,4 +70,31 @@
|
||||
const borderRadius = computed(() => {
|
||||
return useAppConfigStore().borderRadius;
|
||||
});
|
||||
|
||||
function getPopupContainer(node, dialogContext) {
|
||||
let fullScreenFlag = useAppConfigStore().$state.fullScreenFlag;
|
||||
if(fullScreenFlag){
|
||||
return getFullScreenContainer(node, dialogContext);
|
||||
}else{
|
||||
return getNotFullScreenContainer(node, dialogContext);
|
||||
}
|
||||
}
|
||||
|
||||
function getFullScreenContainer(node, dialogContext) {
|
||||
if (node === document.body) {
|
||||
return document.getElementById(LAYOUT_ELEMENT_IDS.content);
|
||||
}else if (node) {
|
||||
return node.parentNode;
|
||||
} else {
|
||||
return document.getElementById(LAYOUT_ELEMENT_IDS.content);
|
||||
}
|
||||
}
|
||||
|
||||
function getNotFullScreenContainer(node, dialogContext) {
|
||||
if (node) {
|
||||
return node.parentNode;
|
||||
} else {
|
||||
return document.body;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -12,12 +12,12 @@
|
||||
<template>
|
||||
<span>
|
||||
<a-tooltip title="全屏" v-if="!fullScreenFlag">
|
||||
<a-button type="text" @click="fullScreen" size="small">
|
||||
<a-button type="text" @click="onFullScreen" size="small">
|
||||
<template #icon><fullscreen-outlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip title="取消全屏" v-if="fullScreenFlag">
|
||||
<a-button type="text" @click="fullScreen" size="small">
|
||||
<a-button type="text" @click="onFullScreen" size="small">
|
||||
<template #icon><fullscreen-exit-outlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
@ -43,11 +43,13 @@
|
||||
import { message } from 'ant-design-vue';
|
||||
import { mergeColumn } from './smart-table-column-merge';
|
||||
import { smartSentry } from '/@/lib/smart-sentry';
|
||||
import { LAYOUT_ELEMENT_IDS } from '/@/layout/layout-const.js';
|
||||
import { useAppConfigStore } from '/@/store/modules/system/app-config.js';
|
||||
const props = defineProps({
|
||||
// 表格列数组
|
||||
modelValue: {
|
||||
type: Array,
|
||||
default: new Array(),
|
||||
default: [],
|
||||
},
|
||||
// 刷新表格函数
|
||||
refresh: {
|
||||
@ -93,22 +95,39 @@
|
||||
|
||||
// ----------------- 全屏 -------------------
|
||||
const fullScreenFlag = ref(false);
|
||||
function fullScreen() {
|
||||
|
||||
function onFullScreen() {
|
||||
if (fullScreenFlag.value) {
|
||||
//取消全屏
|
||||
exitFullscreen(document.querySelector('#smartAdminLayoutContent'));
|
||||
fullScreenFlag.value = false;
|
||||
document.querySelector('#smartAdminPageTag').style.visibility = 'visible';
|
||||
// 退出全屏
|
||||
handleExitFullScreen();
|
||||
exitElementFullscreen(document.getElementById(LAYOUT_ELEMENT_IDS.content));
|
||||
} else {
|
||||
//全屏
|
||||
launchFullScreen(document.querySelector('#smartAdminLayoutContent'));
|
||||
message.config({
|
||||
getContainer: () => document.getElementById(LAYOUT_ELEMENT_IDS.content),
|
||||
});
|
||||
fullScreenFlag.value = true;
|
||||
document.querySelector('#smartAdminPageTag').style.visibility = 'hidden';
|
||||
useAppConfigStore().startFullScreen();
|
||||
launchElementFullScreen(document.getElementById(LAYOUT_ELEMENT_IDS.content));
|
||||
}
|
||||
}
|
||||
|
||||
// 处理退出全屏
|
||||
function handleExitFullScreen(){
|
||||
//取消全屏
|
||||
message.config({
|
||||
getContainer: () => document.body,
|
||||
});
|
||||
fullScreenFlag.value = false;
|
||||
useAppConfigStore().exitFullScreen();
|
||||
document.removeEventListener('fullscreenchange', handleFullscreenChange);
|
||||
document.removeEventListener('mozfullscreenchange', handleFullscreenChange); // Firefox
|
||||
document.removeEventListener('webkitfullscreenchange', handleFullscreenChange); // Chrome, Safari and Opera
|
||||
document.removeEventListener('MSFullscreenChange', handleFullscreenChange); // Internet Explorer and Edge
|
||||
}
|
||||
|
||||
//判断各种浏览器 -全屏
|
||||
function launchFullScreen(element) {
|
||||
function launchElementFullScreen(element) {
|
||||
if (element.requestFullscreen) {
|
||||
element.requestFullscreen();
|
||||
} else if (element.mozRequestFullScreen) {
|
||||
@ -120,9 +139,23 @@
|
||||
} else {
|
||||
message.error('当前浏览器不支持部分全屏!');
|
||||
}
|
||||
document.addEventListener('fullscreenchange', handleFullscreenChange);
|
||||
document.addEventListener('mozfullscreenchange', handleFullscreenChange); // Firefox
|
||||
document.addEventListener('webkitfullscreenchange', handleFullscreenChange); // Chrome, Safari and Opera
|
||||
document.addEventListener('MSFullscreenChange', handleFullscreenChange); // Internet Explorer and Edge
|
||||
}
|
||||
|
||||
function handleFullscreenChange() {
|
||||
if (document.fullscreenElement || document.mozFullScreenElement || document.webkitFullscreenElement || document.msFullscreenElement) {
|
||||
console.log('进入全屏模式');
|
||||
} else {
|
||||
console.log('退出全屏模式');
|
||||
handleExitFullScreen();
|
||||
}
|
||||
}
|
||||
|
||||
//判断各种浏览器 -退出全屏
|
||||
function exitFullscreen(element) {
|
||||
function exitElementFullscreen(element) {
|
||||
if (document.exitFullscreen) {
|
||||
document.exitFullscreen();
|
||||
} else if (document.mozCancelFullScreen) {
|
||||
|
@ -23,7 +23,7 @@
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
<template #rightExtra>
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item @click="closeByMenu(false)">关闭其他</a-menu-item>
|
||||
<a-menu-item @click="closeByMenu(true)">关闭所有</a-menu-item>
|
||||
|
@ -24,7 +24,7 @@
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
<template #rightExtra>
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item @click="closeByMenu(false)">关闭其他</a-menu-item>
|
||||
<a-menu-item @click="closeByMenu(true)">关闭所有</a-menu-item>
|
||||
|
@ -79,10 +79,10 @@
|
||||
const websiteName = computed(() => useAppConfigStore().websiteName);
|
||||
const windowHeight = window.innerHeight;
|
||||
onMounted(() => {
|
||||
watermark.set('smartAdminLayoutContent', useUserStore().actualName);
|
||||
watermark.set(LAYOUT_ELEMENT_IDS.content, useUserStore().actualName);
|
||||
});
|
||||
const backTopTarget = () => {
|
||||
return document.getElementById('smartAdminMain');
|
||||
return document.getElementById(LAYOUT_ELEMENT_IDS.main);
|
||||
};
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
|
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* layout 相关元素 id
|
||||
*/
|
||||
export const LAYOUT_ELEMENT_IDS = {
|
||||
menu: 'smartAdminMenu',
|
||||
main: 'smartAdminMain',
|
||||
header: 'smartAdminHeader',
|
||||
content: 'smartAdminLayoutContent',
|
||||
}
|
@ -10,15 +10,15 @@
|
||||
<template>
|
||||
<a-layout class="admin-layout" style="min-height: 100%">
|
||||
<!-- 侧边菜单 side-menu -->
|
||||
<a-layout-sider :theme="theme" class="side-menu" :collapsed="collapsed" :trigger="null">
|
||||
<a-layout-sider :id="LAYOUT_ELEMENT_IDS.menu" :theme="theme" class="side-menu" :collapsed="collapsed" :trigger="null">
|
||||
<!-- 左侧菜单 -->
|
||||
<SideExpandMenu :collapsed="collapsed" />
|
||||
</a-layout-sider>
|
||||
|
||||
<!--中间内容,一共三部分:1、顶部;2、中间内容区域;3、底部(一般是公司版权信息);-->
|
||||
<a-layout class="admin-layout-main" :style="`height: ${windowHeight}px`" id="smartAdminMain">
|
||||
<a-layout class="admin-layout-main" :style="`height: ${windowHeight}px`" :id="LAYOUT_ELEMENT_IDS.main">
|
||||
<!-- 顶部头部信息 -->
|
||||
<a-layout-header class="smart-layout-header">
|
||||
<a-layout-header class="smart-layout-header" :id="LAYOUT_ELEMENT_IDS.header">
|
||||
<a-row justify="space-between" class="smart-layout-header-user">
|
||||
<a-col class="smart-layout-header-left">
|
||||
<span class="collapsed-button">
|
||||
@ -44,19 +44,19 @@
|
||||
</a-layout-header>
|
||||
|
||||
<!--中间内容-->
|
||||
<a-layout-content class="admin-layout-content" id="smartAdminLayoutContent">
|
||||
<a-layout-content class="admin-layout-content" :id="LAYOUT_ELEMENT_IDS.content">
|
||||
<!--不keepAlive的iframe使用单个iframe组件-->
|
||||
<IframeIndex v-show="iframeNotKeepAlivePageFlag" :key="route.name" :name="route.name" :url="route.meta.frameUrl" />
|
||||
<!--keepAlive的iframe 每个页面一个iframe组件-->
|
||||
<IframeIndex
|
||||
v-for="item in keepAliveIframePages"
|
||||
v-show="route.name == item.name"
|
||||
v-show="route.name === item.name"
|
||||
:key="item.name"
|
||||
:name="item.name"
|
||||
:url="item.meta.frameUrl"
|
||||
/>
|
||||
<!--非iframe使用router-view-->
|
||||
<div v-show="!iframeNotKeepAlivePageFlag && keepAliveIframePages.every((e) => route.name != e.name)">
|
||||
<div v-show="!iframeNotKeepAlivePageFlag && keepAliveIframePages.every((e) => route.name !== e.name)">
|
||||
<router-view v-slot="{ Component }">
|
||||
<keep-alive :include="keepAliveIncludes">
|
||||
<component :is="Component" :key="route.name" />
|
||||
@ -98,6 +98,7 @@
|
||||
import SideHelpDoc from './components/side-help-doc/index.vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { HOME_PAGE_NAME } from '/@/constants/system/home-const';
|
||||
import { LAYOUT_ELEMENT_IDS } from '/@/layout/layout-const.js';
|
||||
|
||||
const windowHeight = ref(window.innerHeight);
|
||||
|
||||
@ -130,7 +131,7 @@
|
||||
//页面初始化的时候加载水印
|
||||
onMounted(() => {
|
||||
if (watermarkFlag.value) {
|
||||
watermark.set('smartAdminLayoutContent', useUserStore().actualName);
|
||||
watermark.set(LAYOUT_ELEMENT_IDS.content, useUserStore().actualName);
|
||||
} else {
|
||||
watermark.clear();
|
||||
}
|
||||
@ -140,7 +141,7 @@
|
||||
() => watermarkFlag.value,
|
||||
(newValue) => {
|
||||
if (newValue) {
|
||||
watermark.set('smartAdminLayoutContent', useUserStore().actualName);
|
||||
watermark.set(LAYOUT_ELEMENT_IDS.content, useUserStore().actualName);
|
||||
} else {
|
||||
watermark.clear();
|
||||
}
|
||||
@ -153,7 +154,7 @@
|
||||
|
||||
//回到顶部
|
||||
const backTopTarget = () => {
|
||||
return document.getElementById('smartAdminMain');
|
||||
return document.getElementById(LAYOUT_ELEMENT_IDS.main);
|
||||
};
|
||||
// ----------------------- keep-alive相关 -----------------------
|
||||
let { route, keepAliveIncludes, iframeNotKeepAlivePageFlag, keepAliveIframePages } = smartKeepAlive();
|
||||
|
@ -1,15 +1,15 @@
|
||||
<template>
|
||||
<a-layout class="admin-layout" style="min-height: 100%">
|
||||
<!-- 侧边菜单 side-menu -->
|
||||
<a-layout-sider class="side-menu" :width="sideMenuWidth" :collapsed="collapsed" :theme="theme">
|
||||
<a-layout-sider :id="LAYOUT_ELEMENT_IDS.menu" class="side-menu" :width="sideMenuWidth" :collapsed="collapsed" :theme="theme">
|
||||
<!-- 左侧菜单 -->
|
||||
<SideMenu :collapsed="collapsed" />
|
||||
</a-layout-sider>
|
||||
|
||||
<!--中间内容,一共三部分:1、顶部;2、中间内容区域;3、底部(一般是公司版权信息);-->
|
||||
<a-layout id="smartAdminMain" :style="`height: ${windowHeight}px`" class="admin-layout-main">
|
||||
<a-layout :id="LAYOUT_ELEMENT_IDS.main" :style="`height: ${windowHeight}px`" class="admin-layout-main">
|
||||
<!-- 顶部头部信息 -->
|
||||
<a-layout-header class="layout-header">
|
||||
<a-layout-header class="layout-header" :id="LAYOUT_ELEMENT_IDS.header">
|
||||
<a-row class="layout-header-user" justify="space-between">
|
||||
<a-col class="layout-header-left">
|
||||
<span class="collapsed-button">
|
||||
@ -35,13 +35,13 @@
|
||||
</a-layout-header>
|
||||
|
||||
<!--中间内容-->
|
||||
<a-layout-content id="smartAdminLayoutContent" class="admin-layout-content">
|
||||
<a-layout-content :id="LAYOUT_ELEMENT_IDS.content" class="admin-layout-content">
|
||||
<!--不keepAlive的iframe使用单个iframe组件-->
|
||||
<IframeIndex v-if="iframeNotKeepAlivePageFlag" :key="route.name" :name="route.name" :url="route.meta.frameUrl" />
|
||||
<!--keepAlive的iframe 每个页面一个iframe组件-->
|
||||
<IframeIndex
|
||||
v-for="item in keepAliveIframePages"
|
||||
v-show="route.name == item.name"
|
||||
v-show="route.name === item.name"
|
||||
:key="item.name"
|
||||
:name="item.name"
|
||||
:url="item.meta.frameUrl"
|
||||
@ -93,6 +93,7 @@
|
||||
import SideHelpDoc from './components/side-help-doc/index.vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { HOME_PAGE_NAME } from '/@/constants/system/home-const';
|
||||
import { LAYOUT_ELEMENT_IDS } from '/@/layout/layout-const.js';
|
||||
|
||||
const windowHeight = ref(window.innerHeight);
|
||||
//菜单宽度
|
||||
@ -126,7 +127,7 @@
|
||||
//页面初始化的时候加载水印
|
||||
onMounted(() => {
|
||||
if (watermarkFlag.value) {
|
||||
watermark.set('smartAdminLayoutContent', useUserStore().actualName);
|
||||
watermark.set(LAYOUT_ELEMENT_IDS.content, useUserStore().actualName);
|
||||
} else {
|
||||
watermark.clear();
|
||||
}
|
||||
@ -136,7 +137,7 @@
|
||||
() => watermarkFlag.value,
|
||||
(newValue) => {
|
||||
if (newValue) {
|
||||
watermark.set('smartAdminLayoutContent', useUserStore().actualName);
|
||||
watermark.set(LAYOUT_ELEMENT_IDS.content, useUserStore().actualName);
|
||||
} else {
|
||||
watermark.clear();
|
||||
}
|
||||
@ -145,7 +146,7 @@
|
||||
|
||||
//回到顶部
|
||||
const backTopTarget = () => {
|
||||
return document.getElementById('smartAdminMain');
|
||||
return document.getElementById(LAYOUT_ELEMENT_IDS.main);
|
||||
};
|
||||
|
||||
const router = useRouter();
|
||||
|
@ -1,14 +1,14 @@
|
||||
<template>
|
||||
<a-layout class="admin-layout">
|
||||
<!-- 顶部菜单 -->
|
||||
<a-layout-header class="top-menu" :theme="theme">
|
||||
<a-layout-header class="top-menu" :theme="theme" :id="LAYOUT_ELEMENT_IDS.menu">
|
||||
<TopMenu />
|
||||
</a-layout-header>
|
||||
|
||||
<!--中间内容-->
|
||||
<a-layout-content id="smartAdminLayoutContent" class="admin-layout-content">
|
||||
<a-layout-content :id="LAYOUT_ELEMENT_IDS.content" class="admin-layout-content">
|
||||
<!---标签页-->
|
||||
<div class="page-tag-div" v-show="pageTagFlag">
|
||||
<div class="page-tag-div" v-show="pageTagFlag" :id="LAYOUT_ELEMENT_IDS.header">
|
||||
<PageTag />
|
||||
</div>
|
||||
|
||||
@ -18,14 +18,14 @@
|
||||
<!--keepAlive的iframe 每个页面一个iframe组件-->
|
||||
<IframeIndex
|
||||
v-for="item in keepAliveIframePages"
|
||||
v-show="route.name == item.name"
|
||||
v-show="route.name === item.name"
|
||||
:key="item.name"
|
||||
:name="item.name"
|
||||
:url="item.meta.frameUrl"
|
||||
/>
|
||||
|
||||
<!--非iframe使用router-view-->
|
||||
<div v-show="!iframeNotKeepAlivePageFlag && keepAliveIframePages.every((e) => route.name != e.name)">
|
||||
<div v-show="!iframeNotKeepAlivePageFlag && keepAliveIframePages.every((e) => route.name !== e.name)">
|
||||
<router-view v-slot="{ Component }">
|
||||
<keep-alive :include="keepAliveIncludes">
|
||||
<component :is="Component" :key="route.name" />
|
||||
@ -55,6 +55,7 @@
|
||||
import { useUserStore } from '/@/store/modules/system/user';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { HOME_PAGE_NAME } from '/@/constants/system/home-const';
|
||||
import { LAYOUT_ELEMENT_IDS } from '/@/layout/layout-const.js';
|
||||
|
||||
const windowHeight = ref(window.innerHeight);
|
||||
//主题颜色
|
||||
@ -87,7 +88,7 @@
|
||||
//页面初始化的时候加载水印
|
||||
onMounted(() => {
|
||||
if (watermarkFlag.value) {
|
||||
watermark.set('smartAdminLayoutContent', useUserStore().actualName);
|
||||
watermark.set(LAYOUT_ELEMENT_IDS.content, useUserStore().actualName);
|
||||
} else {
|
||||
watermark.clear();
|
||||
}
|
||||
@ -97,7 +98,7 @@
|
||||
() => watermarkFlag.value,
|
||||
(newValue) => {
|
||||
if (newValue) {
|
||||
watermark.set('smartAdminLayoutContent', useUserStore().actualName);
|
||||
watermark.set(LAYOUT_ELEMENT_IDS.content, useUserStore().actualName);
|
||||
} else {
|
||||
watermark.clear();
|
||||
}
|
||||
@ -106,7 +107,7 @@
|
||||
|
||||
//回到顶部
|
||||
const backTopTarget = () => {
|
||||
return document.getElementById('smartAdminMain');
|
||||
return document.getElementById(LAYOUT_ELEMENT_IDS.main);
|
||||
};
|
||||
|
||||
const router = useRouter();
|
||||
|
@ -13,7 +13,9 @@ import localStorageKeyConst from '/@/constants/local-storage-key-const';
|
||||
import { smartSentry } from '/@/lib/smart-sentry';
|
||||
import { localRead } from '/@/utils/local-util';
|
||||
|
||||
let state = { ...appDefaultConfig };
|
||||
let state = {
|
||||
...appDefaultConfig
|
||||
};
|
||||
|
||||
let appConfigStr = localRead(localStorageKeyConst.APP_CONFIG);
|
||||
let language = appDefaultConfig.language;
|
||||
@ -38,6 +40,8 @@ export const useAppConfigStore = defineStore({
|
||||
state: () => ({
|
||||
// 读取config下的默认配置
|
||||
...state,
|
||||
// 全屏
|
||||
fullScreenFlag: false,
|
||||
}),
|
||||
actions: {
|
||||
reset() {
|
||||
@ -51,5 +55,11 @@ export const useAppConfigStore = defineStore({
|
||||
hideHelpDoc() {
|
||||
this.helpDocExpandFlag = false;
|
||||
},
|
||||
startFullScreen() {
|
||||
this.fullScreenFlag = true;
|
||||
},
|
||||
exitFullScreen() {
|
||||
this.fullScreenFlag = false;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -97,7 +97,6 @@
|
||||
</div>
|
||||
</a-row>
|
||||
<!---------- 表格操作行 end ----------->
|
||||
|
||||
<a-table
|
||||
size="small"
|
||||
:dataSource="tableData"
|
||||
@ -105,7 +104,9 @@
|
||||
rowKey="goodsId"
|
||||
bordered
|
||||
:pagination="false"
|
||||
:showSorterTooltip="false"
|
||||
:row-selection="{ selectedRowKeys: selectedRowKeyList, onChange: onSelectChange }"
|
||||
@change="onChange"
|
||||
>
|
||||
<template #bodyCell="{ text, record, column }">
|
||||
<template v-if="column.dataIndex === 'place'">
|
||||
@ -190,6 +191,7 @@
|
||||
import SmartEnumSelect from '/@/components/framework/smart-enum-select/index.vue';
|
||||
import { FILE_FOLDER_TYPE_ENUM } from '/@/constants/support/file-const.js';
|
||||
import FileUpload from '/@/components/support/file-upload/index.vue';
|
||||
import _ from 'lodash';
|
||||
|
||||
// ---------------------------- 表格列 ----------------------------
|
||||
|
||||
@ -205,6 +207,7 @@
|
||||
{
|
||||
title: '商品状态',
|
||||
dataIndex: 'goodsStatus',
|
||||
sorter: true
|
||||
},
|
||||
{
|
||||
title: '产地',
|
||||
@ -213,10 +216,12 @@
|
||||
{
|
||||
title: '商品价格',
|
||||
dataIndex: 'price',
|
||||
sorter: true
|
||||
},
|
||||
{
|
||||
title: '上架状态',
|
||||
dataIndex: 'shelvesFlag',
|
||||
sorter: true
|
||||
},
|
||||
{
|
||||
title: '备注',
|
||||
@ -246,9 +251,10 @@
|
||||
goodsType: undefined,
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
sortItemList: []
|
||||
};
|
||||
// 查询表单form
|
||||
const queryForm = reactive({ ...queryFormState });
|
||||
const queryForm = reactive(_.cloneDeep(queryFormState));
|
||||
// 表格加载loading
|
||||
const tableLoading = ref(false);
|
||||
// 表格数据
|
||||
@ -259,7 +265,7 @@
|
||||
// 重置查询条件
|
||||
function resetQuery() {
|
||||
let pageSize = queryForm.pageSize;
|
||||
Object.assign(queryForm, queryFormState);
|
||||
Object.assign(queryForm, _.cloneDeep(queryFormState));
|
||||
queryForm.pageSize = pageSize;
|
||||
queryData();
|
||||
}
|
||||
@ -414,4 +420,27 @@
|
||||
async function onExportGoods() {
|
||||
await goodsApi.exportGoods();
|
||||
}
|
||||
|
||||
function onChange(pagination, filters, sorter, { action }){
|
||||
if (action === 'sort') {
|
||||
const { order, field } = sorter;
|
||||
let column = camelToUnderscore(field);
|
||||
let findIndex = queryForm.sortItemList.findIndex(e => e.column === column);
|
||||
if (findIndex !== -1) {
|
||||
queryForm.sortItemList.splice(findIndex, 1);
|
||||
}
|
||||
if (order) {
|
||||
let isAsc = order !== 'ascend';
|
||||
queryForm.sortItemList.push({
|
||||
column,
|
||||
isAsc
|
||||
});
|
||||
}
|
||||
queryData();
|
||||
}
|
||||
}
|
||||
|
||||
function camelToUnderscore(str) {
|
||||
return str.replace(/([A-Z])/g, "_$1").toLowerCase();
|
||||
}
|
||||
</script>
|
||||
|
@ -1,5 +1,5 @@
|
||||
|
||||
VITE_APP_API_URL = 'http://smartadmin.dev.1024lab.net/api/'
|
||||
VITE_APP_API_URL = 'http://smartadmin.dev.1024lab.net/sa-api/'
|
||||
|
||||
VITE_APP_PROJECT_TITLE = 'SmartAdmin 开发环境(Dev)'
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
|
||||
VITE_APP_API_URL = 'http://smartadmin.dev.1024lab.net/api/'
|
||||
VITE_APP_API_URL = 'http://smartadmin.dev.1024lab.net/sa-api/'
|
||||
|
||||
VITE_APP_PROJECT_TITLE = 'SmartAdmin 测试环境(Test)'
|
||||
|
||||
|
@ -48,8 +48,10 @@
|
||||
"vue3-tabs-chrome": "^0.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"@types/lodash": "^4.14.186",
|
||||
"@types/nprogress": "^0.2.0",
|
||||
"@types/sm-crypto": "^0.3.4",
|
||||
"@typescript-eslint/eslint-plugin": "^5.40.1",
|
||||
"@typescript-eslint/parser": "^5.40.1",
|
||||
"@vitejs/plugin-legacy": "5.4.1",
|
||||
|
@ -0,0 +1,17 @@
|
||||
/**
|
||||
* 数据脱敏api
|
||||
*
|
||||
* @Author: 1024创新实验室-主任-卓大
|
||||
* @Date: 2024-07-31 21:02:37
|
||||
* @Copyright 1024创新实验室
|
||||
*/
|
||||
import { getRequest } from '/src/lib/axios';
|
||||
|
||||
export const dataMaskingApi = {
|
||||
/**
|
||||
* 查询脱敏数据
|
||||
*/
|
||||
query: () => {
|
||||
return getRequest('/support/dataMasking/demo/query');
|
||||
},
|
||||
};
|
@ -32,7 +32,7 @@ export const fileApi = {
|
||||
/**
|
||||
* 下载文件流(根据fileKey) @author 胡克
|
||||
*/
|
||||
downLoadFile: (fileKey) => {
|
||||
return getDownload('/support/file/downLoad', { fileKey });
|
||||
downLoadFile: (fileName, fileKey) => {
|
||||
return getDownload(fileName, '/support/file/downLoad', { fileKey });
|
||||
},
|
||||
};
|
||||
|
@ -13,5 +13,5 @@ export const heartBeatApi = {
|
||||
// 分页查询 @author 卓大
|
||||
queryList: (param) => {
|
||||
return postRequest('/support/heartBeat/query', param);
|
||||
},
|
||||
}
|
||||
};
|
||||
|
@ -0,0 +1,24 @@
|
||||
/**
|
||||
* 三级等保 api 封装
|
||||
*
|
||||
* @Author: 1024创新实验室-主任-卓大
|
||||
* @Date: 2024-07-31 21:02:37
|
||||
* @Copyright 1024创新实验室
|
||||
*/
|
||||
import { postRequest, getRequest } from '/src/lib/axios';
|
||||
|
||||
export const level3ProtectApi = {
|
||||
/**
|
||||
* 查询 三级等保配置 @author 1024创新实验室-主任-卓大
|
||||
*/
|
||||
getConfig: () => {
|
||||
return getRequest('/support/protect/level3protect/getConfig');
|
||||
},
|
||||
|
||||
/**
|
||||
* 更新三级等保配置 @author 1024创新实验室-主任-卓大
|
||||
*/
|
||||
updateConfig: (form) => {
|
||||
return postRequest('/support/protect/level3protect/updateConfig', form);
|
||||
},
|
||||
};
|
@ -24,7 +24,7 @@ export const departmentApi = {
|
||||
* @param {*}
|
||||
* @return {*}
|
||||
*/
|
||||
queryDepartmentTree: () => {
|
||||
queryDepartmentTree: () => {
|
||||
return getRequest('/department/treeList');
|
||||
},
|
||||
|
||||
@ -50,6 +50,6 @@ export const departmentApi = {
|
||||
* @return {*}
|
||||
*/
|
||||
deleteDepartment: (departmentId) => {
|
||||
return getRequest(`/department/delete/${departmentId}`);
|
||||
},
|
||||
return getRequest(`/department/delete/${ departmentId }`);
|
||||
}
|
||||
};
|
||||
|
@ -48,4 +48,18 @@ export const loginApi = {
|
||||
refresh: () => {
|
||||
return getRequest('/login/refresh');
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取邮箱登录验证码 @author 卓大
|
||||
*/
|
||||
sendLoginEmailCode: (loginName) => {
|
||||
return getRequest(`/login/sendEmailCode/${loginName}`);
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取双因子登录标识 @author 卓大
|
||||
*/
|
||||
getTwoFactorLoginFlag: () => {
|
||||
return getRequest('/login/getTwoFactorLoginFlag');
|
||||
},
|
||||
};
|
||||
|
@ -0,0 +1,55 @@
|
||||
/**
|
||||
* 职务表 api 封装
|
||||
*
|
||||
* @Author: kaiyun
|
||||
* @Date: 2024-06-23 23:31:38
|
||||
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>
|
||||
*/
|
||||
import { postRequest, getRequest } from '/@/lib/axios';
|
||||
|
||||
export const positionApi = {
|
||||
|
||||
/**
|
||||
* 分页查询 @author kaiyun
|
||||
*/
|
||||
queryPage : (param) => {
|
||||
return postRequest('/position/queryPage', param);
|
||||
},
|
||||
|
||||
/**
|
||||
* 增加 @author kaiyun
|
||||
*/
|
||||
add: (param) => {
|
||||
return postRequest('/position/add', param);
|
||||
},
|
||||
|
||||
/**
|
||||
* 修改 @author kaiyun
|
||||
*/
|
||||
update: (param) => {
|
||||
return postRequest('/position/update', param);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* 删除 @author kaiyun
|
||||
*/
|
||||
delete: (id) => {
|
||||
return getRequest(`/position/delete/${id}`);
|
||||
},
|
||||
|
||||
/**
|
||||
* 批量删除 @author kaiyun
|
||||
*/
|
||||
batchDelete: (idList) => {
|
||||
return postRequest('/position/batchDelete', idList);
|
||||
},
|
||||
|
||||
/**
|
||||
* 查询列表 @author kaiyun
|
||||
*/
|
||||
queryList: () => {
|
||||
return getRequest('/position/queryList');
|
||||
},
|
||||
|
||||
};
|
@ -23,7 +23,7 @@
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
<template #rightExtra>
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item @click="closeByMenu(false)">关闭其他</a-menu-item>
|
||||
<a-menu-item @click="closeByMenu(true)">关闭所有</a-menu-item>
|
||||
|
@ -23,7 +23,7 @@
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
<template #rightExtra>
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item @click="closeByMenu(false)">关闭其他</a-menu-item>
|
||||
<a-menu-item @click="closeByMenu(true)">关闭所有</a-menu-item>
|
||||
|
@ -1,32 +1,45 @@
|
||||
// @ts-ignore
|
||||
import CryptoJS from 'crypto-js';
|
||||
// @ts-ignore
|
||||
import CryptoSM from 'sm-crypto';
|
||||
|
||||
function object2string(data) {
|
||||
if (typeof data === 'Object') {
|
||||
function object2string(data: any) {
|
||||
if (typeof data === 'object') {
|
||||
return JSON.stringify(data);
|
||||
}
|
||||
|
||||
let str = JSON.stringify(data);
|
||||
if (str.startsWith("'") || str.startsWith('"')) {
|
||||
if (str.startsWith('\'') || str.startsWith('"')) {
|
||||
str = str.substring(1);
|
||||
}
|
||||
if (str.endsWith("'") || str.endsWith('"')) {
|
||||
if (str.endsWith('\'') || str.endsWith('"')) {
|
||||
str = str.substring(0, str.length - 1);
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
// ----------------------- AES 加密、解密 -----------------------
|
||||
const AES_KEY = '1024abcd1024abcd1024abcd1024abcd';
|
||||
/**
|
||||
* 字符串转为数字
|
||||
*/
|
||||
function stringToHex(str: string) {
|
||||
let hex = '';
|
||||
for(let i = 0; i < str.length; i++) {
|
||||
hex += str.charCodeAt(i).toString(16).padStart(2, '0');
|
||||
}
|
||||
return hex;
|
||||
}
|
||||
|
||||
const AES = {
|
||||
// ----------------------- AES 加密、解密 -----------------------
|
||||
const AES_KEY = '1024lab__1024lab';
|
||||
|
||||
export const AES = {
|
||||
encryptData: function (data: any) {
|
||||
// AES 加密 并转为 base64
|
||||
let utf8Data = CryptoJS.enc.Utf8.parse(object2string(data));
|
||||
const key = CryptoJS.enc.Utf8.parse(AES_KEY);
|
||||
const encrypted = CryptoJS.AES.encrypt(utf8Data, key, {
|
||||
mode: CryptoJS.mode.ECB,
|
||||
padding: CryptoJS.pad.Pkcs7,
|
||||
padding: CryptoJS.pad.Pkcs7
|
||||
});
|
||||
|
||||
return encrypted.toString();
|
||||
@ -38,20 +51,20 @@ const AES = {
|
||||
|
||||
// 第二步:AES 解密
|
||||
const key = CryptoJS.enc.Utf8.parse(AES_KEY);
|
||||
return CryptoJS.AES.decrypt({ ciphertext: words }, key, {
|
||||
return CryptoJS.AES.decrypt({ciphertext: words}, key, {
|
||||
mode: CryptoJS.mode.ECB,
|
||||
padding: CryptoJS.pad.Pkcs7,
|
||||
padding: CryptoJS.pad.Pkcs7
|
||||
}).toString(CryptoJS.enc.Utf8);
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
// ----------------------- 国密SM4算法 加密、解密 -----------------------
|
||||
const SM4_KEY = '1024abcd1024abcd1024abcd1024abcd';
|
||||
const SM4_KEY = '1024lab__1024lab';
|
||||
|
||||
const SM4 = {
|
||||
encryptData: function (data: any) {
|
||||
// 第一步:SM4 加密
|
||||
let encryptData = CryptoSM.sm4.encrypt(object2string(data), SM4_KEY);
|
||||
let encryptData = CryptoSM.sm4.encrypt(object2string(data), stringToHex(SM4_KEY));
|
||||
// 第二步: Base64 编码
|
||||
return CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse(encryptData));
|
||||
},
|
||||
@ -62,8 +75,8 @@ const SM4 = {
|
||||
let decode64Str = CryptoJS.enc.Utf8.stringify(words);
|
||||
|
||||
// 第二步:SM4 解密
|
||||
return CryptoSM.sm4.decrypt(decode64Str, SM4_KEY);
|
||||
},
|
||||
return CryptoSM.sm4.decrypt(decode64Str, stringToHex(SM4_KEY));
|
||||
}
|
||||
};
|
||||
|
||||
// ----------------------- 对外暴露: 加密、解密 -----------------------
|
||||
|
@ -10,21 +10,21 @@
|
||||
|
||||
import nProgress from 'nprogress';
|
||||
import 'nprogress/nprogress.css';
|
||||
import { nextTick } from 'vue';
|
||||
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router';
|
||||
import { routerArray } from './routers';
|
||||
import { PAGE_PATH_404, PAGE_PATH_LOGIN } from '/@/constants/common-const';
|
||||
import { HOME_PAGE_NAME } from '/@/constants/system/home-const';
|
||||
import {nextTick} from 'vue';
|
||||
import {createRouter, createWebHashHistory, RouteRecordRaw} from 'vue-router';
|
||||
import {routerArray} from './routers';
|
||||
import {PAGE_PATH_404, PAGE_PATH_LOGIN} from '/@/constants/common-const';
|
||||
import {HOME_PAGE_NAME} from '/@/constants/system/home-const';
|
||||
import SmartLayout from '/@/layout/smart-layout.vue';
|
||||
import { useUserStore } from '/@/store/modules/system/user';
|
||||
import { clearAllCoolies, getTokenFromCookie } from '/@/utils/cookie-util';
|
||||
import { localClear } from '/@/utils/local-util';
|
||||
import {useUserStore} from '/@/store/modules/system/user';
|
||||
import {clearAllCoolies, getTokenFromCookie} from '/@/utils/cookie-util';
|
||||
import {localClear} from '/@/utils/local-util';
|
||||
|
||||
export const router = createRouter({
|
||||
history: createWebHashHistory(),
|
||||
routes: routerArray,
|
||||
strict: true,
|
||||
scrollBehavior: () => ({ left: 0, top: 0 }),
|
||||
scrollBehavior: () => ({left: 0, top: 0})
|
||||
});
|
||||
|
||||
// ----------------------- 路由加载前 -----------------------
|
||||
@ -43,7 +43,7 @@ router.beforeEach(async (to, from, next) => {
|
||||
if (!token) {
|
||||
clearAllCoolies();
|
||||
localClear();
|
||||
next({ path: PAGE_PATH_LOGIN });
|
||||
next({path: PAGE_PATH_LOGIN});
|
||||
return;
|
||||
}
|
||||
|
||||
@ -79,6 +79,7 @@ router.afterEach(() => {
|
||||
|
||||
// ----------------------- 构建router对象 -----------------------
|
||||
const routerMap = new Map();
|
||||
|
||||
export function buildRoutes(menuRouterList) {
|
||||
let menuList = menuRouterList ? menuRouterList : useUserStore().getMenuRouterList || [];
|
||||
/**
|
||||
@ -119,8 +120,8 @@ export function buildRoutes(menuRouterList) {
|
||||
// 是否为外链
|
||||
frameFlag: e.frameFlag,
|
||||
// 外链地址
|
||||
frameUrl: e.frameUrl,
|
||||
},
|
||||
frameUrl: e.frameUrl
|
||||
}
|
||||
};
|
||||
|
||||
if (e.frameFlag) {
|
||||
@ -151,6 +152,6 @@ export function buildRoutes(menuRouterList) {
|
||||
path: '/',
|
||||
meta: {},
|
||||
component: SmartLayout,
|
||||
children: resList,
|
||||
children: resList
|
||||
});
|
||||
}
|
||||
|
@ -270,7 +270,7 @@ function view(file) {
|
||||
// 下载文件
|
||||
async function download(file) {
|
||||
try {
|
||||
await fileApi.downLoadFile(file.fileKey);
|
||||
await fileApi.downLoadFile(file.fileName, file.fileKey);
|
||||
} catch (e) {
|
||||
smartSentry.captureError(e);
|
||||
}
|
||||
|
@ -0,0 +1,137 @@
|
||||
<!--
|
||||
* 数据脱敏
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2024-08-02 20:23:08
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
-->
|
||||
<template>
|
||||
<a-card size="small" :bordered="false" :hoverable="true">
|
||||
<a-alert>
|
||||
<template v-slot:message>
|
||||
<h4>数据脱敏 Data Masking介绍:</h4>
|
||||
</template>
|
||||
<template v-slot:description>
|
||||
<pre>
|
||||
简介:《信息安全技术 网络安全等级保护基本要求》明确规定,二级以上保护则需要对敏感数据进行脱敏处理。
|
||||
原理:数据脱敏是指对某些敏感信息通过脱敏规则进行数据的变形,实现敏感隐私数据的可靠保护。
|
||||
举例:在不违反系统规则条件下,身份证号、手机号、卡号、客户号等个人信息都需要进行数据脱敏。
|
||||
|
||||
使用方式:
|
||||
1)脱敏注解 @DataMasking ,支持数据类型如:用户ID、手机号、密码、地址、银行卡、车牌号等;
|
||||
2)脱敏工具类: SmartDataMaskingUtil ;
|
||||
</pre
|
||||
>
|
||||
</template>
|
||||
</a-alert>
|
||||
|
||||
<a-form class="smart-query-form">
|
||||
<a-row class="smart-query-form-row">
|
||||
<a-form-item class="smart-query-form-item smart-margin-left10">
|
||||
<a-button-group>
|
||||
<a-button type="primary" @click="onSearch">
|
||||
<template #icon>
|
||||
<ReloadOutlined/>
|
||||
</template>
|
||||
查询
|
||||
</a-button>
|
||||
</a-button-group>
|
||||
</a-form-item>
|
||||
</a-row>
|
||||
</a-form>
|
||||
|
||||
<a-table
|
||||
size="small"
|
||||
bordered
|
||||
:scroll="{ x: 1100 }"
|
||||
:loading="tableLoading"
|
||||
class="smart-margin-top10"
|
||||
:dataSource="tableData"
|
||||
:columns="columns"
|
||||
:pagination="false"
|
||||
/>
|
||||
</a-card>
|
||||
</template>
|
||||
<script setup>
|
||||
import { onMounted, reactive, ref } from 'vue';
|
||||
import { heartBeatApi } from '/@/api/support/heart-beat/heart-beat-api';
|
||||
import { PAGE_SIZE_OPTIONS } from '/@/constants/common-const';
|
||||
import { defaultTimeRanges } from '/@/lib/default-time-ranges';
|
||||
import { smartSentry } from '/@/lib/smart-sentry';
|
||||
import TableOperator from '/@/components/support/table-operator/index.vue';
|
||||
import { TABLE_ID_CONST } from '/@/constants/support/table-id-const';
|
||||
import { dataMaskingApi } from '/@/api/support/data-masking/data-masking-api.js';
|
||||
|
||||
//------------------------ 表格渲染 ---------------------
|
||||
|
||||
const columns = ref([
|
||||
{
|
||||
title: '用户ID',
|
||||
dataIndex: 'userId',
|
||||
width: 70
|
||||
},
|
||||
{
|
||||
title: '默认',
|
||||
dataIndex: 'other',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '手机号',
|
||||
dataIndex: 'phone',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '身份证',
|
||||
dataIndex: 'idCard',
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: '密码',
|
||||
dataIndex: 'password',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '邮箱',
|
||||
dataIndex: 'email',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '车牌号',
|
||||
dataIndex: 'carLicense',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '银行卡',
|
||||
dataIndex: 'bankCard',
|
||||
width: 170
|
||||
},
|
||||
{
|
||||
title: '地址',
|
||||
dataIndex: 'address',
|
||||
width: 210
|
||||
}
|
||||
]);
|
||||
|
||||
const tableLoading = ref(false);
|
||||
const tableData = ref([]);
|
||||
|
||||
function onSearch() {
|
||||
ajaxQuery();
|
||||
}
|
||||
|
||||
async function ajaxQuery() {
|
||||
try {
|
||||
tableLoading.value = true;
|
||||
let responseModel = await dataMaskingApi.query();
|
||||
tableData.value = responseModel.data;
|
||||
} catch (e) {
|
||||
smartSentry.captureError(e);
|
||||
} finally {
|
||||
tableLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(ajaxQuery);
|
||||
</script>
|
@ -0,0 +1,254 @@
|
||||
<!--
|
||||
* 三级等保配置
|
||||
*
|
||||
* @Author: 1024创新实验室-主任-卓大
|
||||
* @Date: 2024-07-31 22:02:37
|
||||
* @Copyright 1024创新实验室
|
||||
-->
|
||||
<template>
|
||||
<a-alert closable>
|
||||
<template v-slot:message>
|
||||
<h4>三级等保:</h4>
|
||||
</template>
|
||||
<template v-slot:description>
|
||||
<pre>
|
||||
1.三级等保是中国国家等级保护认证中的最高级别认证,该认证包含了五个等级保护安全技术要求和五个安全管理要求,共涉及测评分类73类,要求非常严格。
|
||||
2.三级等保是地市级以上国家机关、重要企事业单位需要达成的认证,在金融行业中,可以看作是除了银行机构以外最高级别的信息安全等级保护。
|
||||
3.具体三级等保要求,请查看“1024创新实验室”写的相关文档 <a href="https://smartadmin.vip/views/level3protect/basic.html" target="_blank">三级等保文档</a></pre>
|
||||
</template>
|
||||
</a-alert>
|
||||
<br />
|
||||
<!---------- 三级等保配置表单 begin ----------->
|
||||
<a-card title="三级等保配置">
|
||||
<a-form
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
ref="formRef"
|
||||
style="width: 800px"
|
||||
:label-col="{ span: 6 }"
|
||||
:wrapper-col="{ span: 18 }"
|
||||
autocomplete="off"
|
||||
class="smart-query-form"
|
||||
>
|
||||
<a-form-item
|
||||
label="配置双因子登录模式"
|
||||
class="smart-query-form-item"
|
||||
extra="在用户登录时,需要同时提供用户名和密码以及其他形式的身份验证信息,例如短信验证码等"
|
||||
>
|
||||
<a-switch v-model:checked="form.twoFactorLoginEnabled" checked-children="开启 " un-checked-children="关闭 " />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="最大连续登录失败次数"
|
||||
class="smart-query-form-item"
|
||||
extra="连续登录失败超过一定次数,则需要锁定;默认5次;0则不锁定;"
|
||||
name="loginFailMaxTimes"
|
||||
>
|
||||
<a-input-number :min="0" :max="10" v-model:value="form.loginFailMaxTimes" placeholder="最大连续登录失败次数" addon-after="次" />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
name="loginFailLockMinutes"
|
||||
label="连续登录失败锁定分钟"
|
||||
class="smart-query-form-item"
|
||||
extra="连续登录失败锁定的时间;默认30分钟,0则不锁定"
|
||||
>
|
||||
<a-input-number :min="0" v-model:value="form.loginFailLockMinutes" placeholder="连续登录失败锁定时分钟" addon-after="分钟" />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
name="loginActiveTimeoutMinutes"
|
||||
label="登录后无操作自动退出的分钟"
|
||||
class="smart-query-form-item"
|
||||
extra="如:登录1小时没操作自动退出当前登录状态;默认30分钟"
|
||||
>
|
||||
<a-input-number :min="-1" v-model:value="form.loginActiveTimeoutMinutes" placeholder="登录后无操作自动退出的分钟" addon-after="分钟" />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="开启密码复杂度"
|
||||
class="smart-query-form-item"
|
||||
extra="密码长度为8-20位且必须包含字母、数字、特殊符号(如:@#$%^&*()_+-=)等三种字符"
|
||||
>
|
||||
<a-switch v-model:checked="form.passwordComplexityEnabled" checked-children="开启 " un-checked-children="关闭 " />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
name="regularChangePasswordMonths"
|
||||
label="定期修改密码时间间隔"
|
||||
class="smart-query-form-item"
|
||||
extra="定期修改密码时间间隔,默认3个月"
|
||||
>
|
||||
<a-input-number :min="-1" :max="6" v-model:value="form.regularChangePasswordMonths" placeholder="定期修改密码时间间隔" addon-after="月" />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
name="regularChangePasswordNotAllowRepeatTimes"
|
||||
label="定期修改密码不允许重复次数"
|
||||
class="smart-query-form-item"
|
||||
extra="定期修改密码不允许重复次数,默认:3次以内密码不能相同"
|
||||
>
|
||||
<a-input-number
|
||||
:min="-1"
|
||||
:max="6"
|
||||
v-model:value="form.regularChangePasswordNotAllowRepeatTimes"
|
||||
placeholder="相同密码不允许重复次数"
|
||||
addon-after="次"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="文件安全检测"
|
||||
class="smart-query-form-item"
|
||||
extra="对文件类型、恶意文件进行检测;(具体请看后端: SecurityFileService 类 checkFile 方法 )"
|
||||
>
|
||||
<a-switch v-model:checked="form.fileDetectFlag" checked-children="开启 " un-checked-children="关闭 " />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
name="maxUploadFileSizeMb"
|
||||
label="上传文件大小限制"
|
||||
class="smart-query-form-item"
|
||||
extra="上传文件大小限制,默认 50 mb ( 0 表示不限制)"
|
||||
>
|
||||
<a-input-number :min="0" v-model:value="form.maxUploadFileSizeMb" placeholder="上传文件大小限制" addon-after="mb(兆)" />
|
||||
</a-form-item>
|
||||
<br />
|
||||
<a-form-item :wrapper-col="{ span: 14, offset: 6 }">
|
||||
<a-button type="primary" style="margin-right: 20px" @click.prevent="onSubmit">保存配置</a-button>
|
||||
<a-button style="margin-right: 20px" @click="reset">恢复三级等保默认配置</a-button>
|
||||
<a-button danger @click="clear">清除所有配置</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-card>
|
||||
<!---------- 请求参数加密 end ----------->
|
||||
</template>
|
||||
<script setup>
|
||||
import { onMounted, reactive, ref } from 'vue';
|
||||
import { level3ProtectApi } from '/@/api/support/level3-protect/level3-protect-api.js';
|
||||
import { SmartLoading } from '/@/components/framework/smart-loading/index.ts';
|
||||
import { smartSentry } from '/@/lib/smart-sentry.ts';
|
||||
import { message, Modal } from 'ant-design-vue';
|
||||
|
||||
// 三级等保的默认值
|
||||
const protectDefaultValues = {
|
||||
// 连续登录失败次数则锁定
|
||||
loginFailMaxTimes: 5,
|
||||
// 连续登录失败锁定分钟
|
||||
loginFailLockMinutes: 30,
|
||||
// 最低活跃时间分钟
|
||||
loginActiveTimeoutMinutes: 30,
|
||||
// 密码复杂度
|
||||
passwordComplexityEnabled: true,
|
||||
// 定期修改密码时间间隔 月份
|
||||
regularChangePasswordMonths: 3,
|
||||
// 定期修改密码不允许重复次数,默认:3次以内密码不能相同
|
||||
regularChangePasswordNotAllowRepeatTimes: 3,
|
||||
// 开启双因子登录
|
||||
twoFactorLoginEnabled: true,
|
||||
// 文件检测,默认:不开启
|
||||
fileDetectFlag: true,
|
||||
// 文件大小限制,单位 mb ,(默认:50 mb)
|
||||
maxUploadFileSizeMb: 50,
|
||||
};
|
||||
|
||||
// 三级等保的不保护的默认值
|
||||
const noProtectDefaultValues = {
|
||||
// 连续登录失败次数则锁定
|
||||
loginFailMaxTimes: 0,
|
||||
// 连续登录失败锁定分钟
|
||||
loginFailLockMinutes: 0,
|
||||
// 最低活跃时间分钟
|
||||
loginActiveTimeoutMinutes: 0,
|
||||
// 密码复杂度
|
||||
passwordComplexityEnabled: false,
|
||||
// 定期修改密码时间间隔 月份
|
||||
regularChangePasswordMonths: 0,
|
||||
// 定期修改密码不允许重复次数,
|
||||
regularChangePasswordNotAllowRepeatTimes: 0,
|
||||
// 开启双因子登录
|
||||
twoFactorLoginEnabled: false,
|
||||
// 文件大小限制,单位 mb ,
|
||||
maxUploadFileSizeMb: 0,
|
||||
};
|
||||
|
||||
// 三级等保配置表单
|
||||
const form = reactive({
|
||||
...protectDefaultValues,
|
||||
});
|
||||
|
||||
const rules = {
|
||||
loginFailMaxTimes: [{ required: true, message: '请输入 最大连续登录失败次数' }],
|
||||
loginFailLockMinutes: [{ required: true, message: '请输入 连续登录失败锁定分钟' }],
|
||||
loginActiveTimeoutMinutes: [{ required: true, message: '请输入 最低活跃时间分钟' }],
|
||||
regularChangePasswordMonths: [{ required: true, message: '请输入 定期修改密码时间间隔' }],
|
||||
regularChangePasswordNotAllowRepeatTimes: [{ required: true, message: '请输入 定期修改密码时间间隔' }],
|
||||
maxUploadFileSizeMb: [{ required: true, message: '请输入 上传文件大小限制' }],
|
||||
};
|
||||
|
||||
//获取配置
|
||||
async function getConfig() {
|
||||
SmartLoading.show();
|
||||
try {
|
||||
let res = await level3ProtectApi.getConfig();
|
||||
if (!res.data) {
|
||||
message.warn('当前未配置三级等保');
|
||||
return;
|
||||
}
|
||||
let json = JSON.parse(res.data);
|
||||
form.loginFailMaxTimes = json.loginFailMaxTimes;
|
||||
form.loginFailLockMinutes = json.loginFailLockMinutes;
|
||||
form.loginActiveTimeoutMinutes = json.loginActiveTimeoutMinutes;
|
||||
form.passwordComplexityEnabled = json.passwordComplexityEnabled;
|
||||
form.regularChangePasswordMonths = json.regularChangePasswordMonths;
|
||||
form.regularChangePasswordNotAllowRepeatTimes = json.regularChangePasswordNotAllowRepeatTimes;
|
||||
form.twoFactorLoginEnabled = json.twoFactorLoginEnabled;
|
||||
form.maxUploadFileSizeMb = json.maxUploadFileSizeMb;
|
||||
form.fileDetectFlag = json.fileDetectFlag;
|
||||
} catch (e) {
|
||||
smartSentry.captureError(e);
|
||||
} finally {
|
||||
SmartLoading.hide();
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(getConfig);
|
||||
|
||||
const formRef = ref();
|
||||
// 提交修改
|
||||
function onSubmit() {
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(save)
|
||||
.catch((error) => {
|
||||
message.error('参数验证错误,请仔细填写表单数据!');
|
||||
});
|
||||
}
|
||||
|
||||
// 提交修改配置
|
||||
async function save() {
|
||||
SmartLoading.show();
|
||||
try {
|
||||
let res = await level3ProtectApi.updateConfig(form);
|
||||
message.success(res.msg);
|
||||
} catch (e) {
|
||||
smartSentry.captureError(e);
|
||||
} finally {
|
||||
SmartLoading.hide();
|
||||
}
|
||||
}
|
||||
|
||||
// 重置
|
||||
function reset() {
|
||||
Object.assign(form, protectDefaultValues);
|
||||
save();
|
||||
}
|
||||
|
||||
// 清除所有配置
|
||||
function clear() {
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: '确定要清除三级等保配置吗?这样系统不安全哦',
|
||||
okText: '清除三级等保配置',
|
||||
okType: 'danger',
|
||||
onOk() {
|
||||
Object.assign(form, noProtectDefaultValues);
|
||||
save();
|
||||
},
|
||||
cancelText: '取消',
|
||||
onCancel() {},
|
||||
});
|
||||
}
|
||||
</script>
|
@ -0,0 +1,138 @@
|
||||
<!--
|
||||
* 部门表单 弹窗
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-08-08 20:46:18
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
-->
|
||||
<template>
|
||||
<a-modal v-model:open="visible" :title="formState.departmentId ? '编辑部门' : '添加部门'" @ok="handleOk" destroyOnClose>
|
||||
<a-form ref="formRef" :model="formState" :rules="rules" layout="vertical">
|
||||
<a-form-item label="上级部门" name="parentId" v-if="formState.parentId != 0">
|
||||
<DepartmentTreeSelect ref="departmentTreeSelect" v-model:value="formState.parentId" :defaultValueFlag="false" width="100%" />
|
||||
</a-form-item>
|
||||
<a-form-item label="部门名称" name="name">
|
||||
<a-input v-model:value.trim="formState.name" placeholder="请输入部门名称" />
|
||||
</a-form-item>
|
||||
<a-form-item label="部门负责人" name="managerId">
|
||||
<EmployeeSelect ref="employeeSelect" placeholder="请选择部门负责人" width="100%" v-model:value="formState.managerId" :leaveFlag="false" />
|
||||
</a-form-item>
|
||||
<a-form-item label="部门排序 (值越大越靠前!)" name="sort">
|
||||
<a-input-number style="width: 100%" v-model:value="formState.sort" :min="0" placeholder="请输入部门名称" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import message from 'ant-design-vue/lib/message';
|
||||
import { reactive, ref } from 'vue';
|
||||
import { departmentApi } from '/@/api/system/department/department-api';
|
||||
import DepartmentTreeSelect from '/@/components/system/department-tree-select/index.vue';
|
||||
import EmployeeSelect from '/@/components/system/employee-select/index.vue';
|
||||
import { smartSentry } from '/@/lib/smart-sentry';
|
||||
import { SmartLoading } from '/@/components/framework/smart-loading';
|
||||
|
||||
// ----------------------- 对外暴漏 ---------------------
|
||||
|
||||
defineExpose({
|
||||
showModal,
|
||||
});
|
||||
|
||||
// ----------------------- modal 的显示与隐藏 ---------------------
|
||||
const emits = defineEmits(['refresh']);
|
||||
|
||||
const visible = ref(false);
|
||||
function showModal(data) {
|
||||
visible.value = true;
|
||||
updateFormData(data);
|
||||
}
|
||||
function closeModal() {
|
||||
visible.value = false;
|
||||
resetFormData();
|
||||
}
|
||||
|
||||
// ----------------------- form 表单操作 ---------------------
|
||||
const formRef = ref();
|
||||
const departmentTreeSelect = ref();
|
||||
const defaultDepartmentForm = {
|
||||
id: undefined,
|
||||
managerId: undefined, //部门负责人
|
||||
name: undefined,
|
||||
parentId: undefined,
|
||||
sort: 0,
|
||||
};
|
||||
const employeeSelect = ref();
|
||||
|
||||
let formState = reactive({
|
||||
...defaultDepartmentForm,
|
||||
});
|
||||
// 表单校验规则
|
||||
const rules = {
|
||||
parentId: [{ required: true, message: '上级部门不能为空' }],
|
||||
name: [
|
||||
{ required: true, message: '部门名称不能为空' },
|
||||
{ max: 50, message: '部门名称不能大于20个字符', trigger: 'blur' },
|
||||
],
|
||||
managerId: [{ required: true, message: '部门负责人不能为空' }],
|
||||
};
|
||||
// 更新表单数据
|
||||
function updateFormData(data) {
|
||||
Object.assign(formState, defaultDepartmentForm);
|
||||
if (data) {
|
||||
Object.assign(formState, data);
|
||||
}
|
||||
visible.value = true;
|
||||
}
|
||||
// 重置表单数据
|
||||
function resetFormData() {
|
||||
Object.assign(formState, defaultDepartmentForm);
|
||||
}
|
||||
|
||||
async function handleOk() {
|
||||
try {
|
||||
await formRef.value.validate();
|
||||
if (formState.departmentId) {
|
||||
updateDepartment();
|
||||
} else {
|
||||
addDepartment();
|
||||
}
|
||||
} catch (error) {
|
||||
message.error('参数验证错误,请仔细填写表单数据!');
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------- form 表单 ajax 操作 ---------------------
|
||||
//添加部门ajax请求
|
||||
async function addDepartment() {
|
||||
SmartLoading.show();
|
||||
try {
|
||||
await departmentApi.addDepartment(formState);
|
||||
emits('refresh');
|
||||
closeModal();
|
||||
} catch (error) {
|
||||
smartSentry.captureError(error);
|
||||
} finally {
|
||||
SmartLoading.hide();
|
||||
}
|
||||
}
|
||||
|
||||
//更新部门ajax请求
|
||||
async function updateDepartment() {
|
||||
SmartLoading.show();
|
||||
try {
|
||||
if (formState.parentId == formState.departmentId) {
|
||||
message.warning('上级菜单不能为自己');
|
||||
return;
|
||||
}
|
||||
await departmentApi.updateDepartment(formState);
|
||||
emits('refresh');
|
||||
closeModal();
|
||||
} catch (error) {
|
||||
smartSentry.captureError(error);
|
||||
} finally {
|
||||
SmartLoading.hide();
|
||||
}
|
||||
}
|
||||
</script>
|
@ -0,0 +1,258 @@
|
||||
<template>
|
||||
<a-form class="smart-query-form">
|
||||
<a-row class="smart-query-form-row">
|
||||
<a-form-item label="部门名称" class="smart-query-form-item">
|
||||
<a-input style="width: 300px" v-model:value="keywords" placeholder="请输入部门名称" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item class="smart-query-form-item smart-margin-left10">
|
||||
<a-button-group>
|
||||
<a-button v-privilege="'support:department:query'" type="primary" @click="onSearch">
|
||||
<template #icon>
|
||||
<ReloadOutlined />
|
||||
</template>
|
||||
查询
|
||||
</a-button>
|
||||
<a-button v-privilege="'support:department:query'" @click="resetQuery">
|
||||
<template #icon>
|
||||
<SearchOutlined />
|
||||
</template>
|
||||
重置
|
||||
</a-button>
|
||||
</a-button-group>
|
||||
<a-button v-privilege="'system:department:add'" type="primary" @click="addDepartment" class="smart-margin-left20">
|
||||
<template #icon>
|
||||
<PlusOutlined />
|
||||
</template>
|
||||
新建
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-row>
|
||||
</a-form>
|
||||
|
||||
<a-card size="small" :bordered="true">
|
||||
<a-table
|
||||
size="small"
|
||||
bordered
|
||||
:loading="tableLoading"
|
||||
rowKey="departmentId"
|
||||
:columns="columns"
|
||||
:data-source="departmentTreeData"
|
||||
:defaultExpandAllRows="false"
|
||||
:defaultExpandedRowKeys="defaultExpandedRowList"
|
||||
:pagination="false"
|
||||
>
|
||||
<template #bodyCell="{ record, column }">
|
||||
<template v-if="column.dataIndex === 'action'">
|
||||
<div class="smart-table-operate">
|
||||
<a-button @click="addDepartment(record)" v-privilege="'system:department:add'" type="link">添加下级</a-button>
|
||||
<a-button @click="updateDepartment(record)" v-privilege="'system:department:update'" type="link">编辑</a-button>
|
||||
<a-button
|
||||
danger
|
||||
v-if="record.departmentId !== topDepartmentId"
|
||||
v-privilege="'system:department:delete'"
|
||||
@click="deleteDepartment(record.departmentId)"
|
||||
type="link"
|
||||
>删除</a-button
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
<!-- 添加编辑部门弹窗 -->
|
||||
<DepartmentFormModal ref="departmentFormModal" @refresh="queryDepartmentTree" />
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, reactive, ref, watch, createVNode } from 'vue';
|
||||
import { departmentApi } from '/@/api/system/department/department-api';
|
||||
import { Modal } from 'ant-design-vue';
|
||||
import { ExclamationCircleOutlined } from '@ant-design/icons-vue';
|
||||
import _ from 'lodash';
|
||||
import { SmartLoading } from '/@/components/framework/smart-loading';
|
||||
import DepartmentFormModal from './components/department-form-modal.vue';
|
||||
import { smartSentry } from '/@/lib/smart-sentry';
|
||||
|
||||
const DEPARTMENT_PARENT_ID = 0;
|
||||
|
||||
// ----------------------- 筛选 ---------------------
|
||||
const keywords = ref('');
|
||||
|
||||
// ----------------------- 部门树的展示 ---------------------
|
||||
const tableLoading = ref(false);
|
||||
|
||||
const topDepartmentId = ref();
|
||||
// 所有部门列表
|
||||
const departmentList = ref([]);
|
||||
// 部门树形数据
|
||||
const departmentTreeData = ref([]);
|
||||
// 存放部门id和部门,用于查找
|
||||
const idInfoMap = ref(new Map());
|
||||
// 默认展开的行
|
||||
const defaultExpandedRowList = reactive([]);
|
||||
|
||||
const columns = ref([
|
||||
{
|
||||
title: '部门名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
},
|
||||
{
|
||||
title: '负责人',
|
||||
dataIndex: 'managerName',
|
||||
key: 'managerName',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '排序',
|
||||
dataIndex: 'sort',
|
||||
key: 'sort',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: '更新时间',
|
||||
dataIndex: 'updateTime',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'action',
|
||||
fixed: 'right',
|
||||
width: 200,
|
||||
},
|
||||
]);
|
||||
|
||||
onMounted(() => {
|
||||
queryDepartmentTree();
|
||||
});
|
||||
|
||||
// 查询部门列表并构建 部门树
|
||||
async function queryDepartmentTree() {
|
||||
try {
|
||||
tableLoading.value = true;
|
||||
let res = await departmentApi.queryAllDepartment();
|
||||
let data = res.data;
|
||||
|
||||
data.forEach((e) => {
|
||||
idInfoMap.value.set(e.departmentId, e);
|
||||
});
|
||||
|
||||
departmentList.value = data;
|
||||
departmentTreeData.value = buildDepartmentTree(data, DEPARTMENT_PARENT_ID);
|
||||
|
||||
// 默认显示 最顶级ID为列表中返回的第一条数据的ID
|
||||
if (!_.isEmpty(departmentTreeData.value) && departmentTreeData.value.length > 0) {
|
||||
topDepartmentId.value = departmentTreeData.value[0].departmentId;
|
||||
}
|
||||
|
||||
defaultExpandedRowList.value = [];
|
||||
defaultExpandedRowList.push(topDepartmentId.value);
|
||||
} catch (e) {
|
||||
smartSentry.captureError(e);
|
||||
} finally {
|
||||
tableLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 构建部门树
|
||||
function buildDepartmentTree(data, parentId) {
|
||||
let children = data.filter((e) => e.parentId === parentId) || [];
|
||||
if (!_.isEmpty(children)) {
|
||||
children.forEach((e) => {
|
||||
e.children = buildDepartmentTree(data, e.departmentId);
|
||||
});
|
||||
return children;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// 重置
|
||||
function resetQuery() {
|
||||
keywords.value = '';
|
||||
onSearch();
|
||||
}
|
||||
|
||||
// 搜索
|
||||
function onSearch() {
|
||||
if (!keywords.value) {
|
||||
departmentTreeData.value = buildDepartmentTree(departmentList.value, DEPARTMENT_PARENT_ID);
|
||||
return;
|
||||
}
|
||||
let originData = departmentList.value.concat();
|
||||
if (!originData) {
|
||||
return;
|
||||
}
|
||||
// 筛选出名称符合的部门
|
||||
let filterDepartment = originData.filter((e) => e.name.indexOf(keywords.value) > -1);
|
||||
let filterDepartmentList = [];
|
||||
// 循环筛选出的部门 构建部门树
|
||||
filterDepartment.forEach((e) => {
|
||||
recursionFilterDepartment(filterDepartmentList, e.departmentId, false);
|
||||
});
|
||||
departmentTreeData.value = buildDepartmentTree(filterDepartmentList, DEPARTMENT_PARENT_ID);
|
||||
}
|
||||
|
||||
// 根据ID递归筛选部门
|
||||
function recursionFilterDepartment(resList, id, unshift) {
|
||||
let info = idInfoMap.value.get(id);
|
||||
if (!info || resList.some((e) => e.departmentId === id)) {
|
||||
return;
|
||||
}
|
||||
if (unshift) {
|
||||
resList.unshift(info);
|
||||
} else {
|
||||
resList.push(info);
|
||||
}
|
||||
if (info.parentId && info.parentId !== 0) {
|
||||
recursionFilterDepartment(resList, info.parentId, unshift);
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------- 表单操作:添加部门/修改部门/删除部门/上下移动 ---------------------
|
||||
const departmentFormModal = ref();
|
||||
// 添加
|
||||
function addDepartment(e) {
|
||||
let data = {
|
||||
departmentId: 0,
|
||||
name: '',
|
||||
parentId: e.departmentId || null,
|
||||
};
|
||||
departmentFormModal.value.showModal(data);
|
||||
}
|
||||
// 编辑
|
||||
function updateDepartment(e) {
|
||||
departmentFormModal.value.showModal(e);
|
||||
}
|
||||
|
||||
// 删除
|
||||
function deleteDepartment(id) {
|
||||
Modal.confirm({
|
||||
title: '提醒',
|
||||
icon: createVNode(ExclamationCircleOutlined),
|
||||
content: '确定要删除该部门吗?',
|
||||
okText: '删除',
|
||||
okType: 'danger',
|
||||
async onOk() {
|
||||
SmartLoading.show();
|
||||
try {
|
||||
await departmentApi.deleteDepartment(id);
|
||||
await queryDepartmentTree();
|
||||
} catch (error) {
|
||||
smartSentry.captureError(error);
|
||||
} finally {
|
||||
SmartLoading.hide();
|
||||
}
|
||||
},
|
||||
cancelText: '取消',
|
||||
onCancel() {},
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less"></style>
|
@ -17,11 +17,20 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-item login">
|
||||
<img class="login-qr" :src="loginQR" />
|
||||
<img class="login-qr" :src="loginQR"/>
|
||||
<div class="login-title">账号登录</div>
|
||||
<a-form ref="formRef" class="login-form" :model="loginForm" :rules="rules">
|
||||
<a-form-item name="loginName">
|
||||
<a-input v-model:value.trim="loginForm.loginName" placeholder="请输入用户名" />
|
||||
<a-input v-model:value.trim="loginForm.loginName" placeholder="请输入用户名"/>
|
||||
</a-form-item>
|
||||
<a-form-item name="emailCode" v-if="emailCodeShowFlag">
|
||||
<a-input-group compact>
|
||||
<a-input style="width: calc(100% - 110px)" v-model:value="loginForm.emailCode" autocomplete="on"
|
||||
placeholder="请输入邮箱验证码"/>
|
||||
<a-button @click="sendSmsCode" class="code-btn" type="primary" :disabled="emailCodeButtonDisabled">
|
||||
{{ emailCodeTips }}
|
||||
</a-button>
|
||||
</a-input-group>
|
||||
</a-form-item>
|
||||
<a-form-item name="password">
|
||||
<a-input-password
|
||||
@ -32,8 +41,8 @@
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item name="captchaCode">
|
||||
<a-input class="captcha-input" v-model:value.trim="loginForm.captchaCode" placeholder="请输入验证码" />
|
||||
<img class="captcha-img" :src="captchaBase64Image" @click="getCaptcha" />
|
||||
<a-input class="captcha-input" v-model:value.trim="loginForm.captchaCode" placeholder="请输入验证码"/>
|
||||
<img class="captcha-img" :src="captchaBase64Image" @click="getCaptcha"/>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-checkbox v-model:checked="rememberPwd">记住密码</a-checkbox>
|
||||
@ -50,135 +59,188 @@
|
||||
<p class="line"></p>
|
||||
</div>
|
||||
<div class="login-type">
|
||||
<img :src="wechatIcon" />
|
||||
<img :src="aliIcon" />
|
||||
<img :src="douyinIcon" />
|
||||
<img :src="qqIcon" />
|
||||
<img :src="weiboIcon" />
|
||||
<img :src="feishuIcon" />
|
||||
<img :src="googleIcon" />
|
||||
<img :src="wechatIcon"/>
|
||||
<img :src="aliIcon"/>
|
||||
<img :src="douyinIcon"/>
|
||||
<img :src="qqIcon"/>
|
||||
<img :src="weiboIcon"/>
|
||||
<img :src="feishuIcon"/>
|
||||
<img :src="googleIcon"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { message } from 'ant-design-vue';
|
||||
import { onMounted, onUnmounted, reactive, ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { loginApi } from '/@/api/system/login/login-api';
|
||||
import { SmartLoading } from '/@/components/framework/smart-loading';
|
||||
import { LOGIN_DEVICE_ENUM } from '/@/constants/system/login-device-const';
|
||||
import { useUserStore } from '/@/store/modules/system/user';
|
||||
import loginQR from '/@/assets/images/login/login-qr.png';
|
||||
import wechatIcon from '/@/assets/images/login/wechat-icon.png';
|
||||
import aliIcon from '/@/assets/images/login/ali-icon.png';
|
||||
import douyinIcon from '/@/assets/images/login/douyin-icon.png';
|
||||
import qqIcon from '/@/assets/images/login/qq-icon.png';
|
||||
import weiboIcon from '/@/assets/images/login/weibo-icon.png';
|
||||
import feishuIcon from '/@/assets/images/login/feishu-icon.png';
|
||||
import googleIcon from '/@/assets/images/login/google-icon.png';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { onMounted, onUnmounted, reactive, ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { loginApi } from '/src/api/system/login/login-api';
|
||||
import { SmartLoading } from '/@/components/framework/smart-loading';
|
||||
import { LOGIN_DEVICE_ENUM } from '/@/constants/system/login-device-const';
|
||||
import { useUserStore } from '/@/store/modules/system/user';
|
||||
import loginQR from '/@/assets/images/login/login-qr.png';
|
||||
import wechatIcon from '/@/assets/images/login/wechat-icon.png';
|
||||
import aliIcon from '/@/assets/images/login/ali-icon.png';
|
||||
import douyinIcon from '/@/assets/images/login/douyin-icon.png';
|
||||
import qqIcon from '/@/assets/images/login/qq-icon.png';
|
||||
import weiboIcon from '/@/assets/images/login/weibo-icon.png';
|
||||
import feishuIcon from '/@/assets/images/login/feishu-icon.png';
|
||||
import googleIcon from '/@/assets/images/login/google-icon.png';
|
||||
|
||||
import { buildRoutes } from '/@/router/index';
|
||||
import { smartSentry } from '/@/lib/smart-sentry';
|
||||
import { encryptData } from '/@/lib/encrypt';
|
||||
import { localSave } from '/@/utils/local-util.ts';
|
||||
import LocalStorageKeyConst from '/@/constants/local-storage-key-const.ts';
|
||||
import { saveTokenToCookie } from '/@/utils/cookie-util';
|
||||
import { buildRoutes } from '/@/router/index';
|
||||
import { smartSentry } from '/@/lib/smart-sentry';
|
||||
import { encryptData } from '/@/lib/encrypt';
|
||||
import { saveTokenToCookie } from '/@/utils/cookie-util';
|
||||
|
||||
//--------------------- 登录表单 ---------------------------------
|
||||
//--------------------- 登录表单 ---------------------------------
|
||||
|
||||
const loginForm = reactive({
|
||||
loginName: 'admin',
|
||||
password: '',
|
||||
captchaCode: '',
|
||||
captchaUuid: '',
|
||||
loginDevice: LOGIN_DEVICE_ENUM.PC.value,
|
||||
});
|
||||
const rules = {
|
||||
loginName: [{ required: true, message: '用户名不能为空' }],
|
||||
password: [{ required: true, message: '密码不能为空' }],
|
||||
captchaCode: [{ required: true, message: '验证码不能为空' }],
|
||||
const loginForm = reactive({
|
||||
loginName: 'admin',
|
||||
password: '',
|
||||
emailCode: '', // 邮箱验证码
|
||||
captchaCode: '',
|
||||
captchaUuid: '',
|
||||
loginDevice: LOGIN_DEVICE_ENUM.PC.value
|
||||
});
|
||||
const rules = {
|
||||
loginName: [{required: true, message: '用户名不能为空'}],
|
||||
password: [{required: true, message: '密码不能为空'}],
|
||||
captchaCode: [{required: true, message: '验证码不能为空'}]
|
||||
};
|
||||
|
||||
const showPassword = ref(false);
|
||||
const router = useRouter();
|
||||
const formRef = ref();
|
||||
const rememberPwd = ref(false);
|
||||
|
||||
onMounted(() => {
|
||||
document.onkeyup = (e) => {
|
||||
if (e.keyCode == 13) {
|
||||
onLogin();
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
const showPassword = ref(false);
|
||||
const router = useRouter();
|
||||
const formRef = ref();
|
||||
const rememberPwd = ref(false);
|
||||
onUnmounted(() => {
|
||||
document.onkeyup = null;
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
document.onkeyup = (e) => {
|
||||
if (e.keyCode == 13) {
|
||||
onLogin();
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
document.onkeyup = null;
|
||||
});
|
||||
|
||||
//登录
|
||||
async function onLogin() {
|
||||
formRef.value.validate().then(async () => {
|
||||
try {
|
||||
SmartLoading.show();
|
||||
// 密码加密
|
||||
let encryptPasswordForm = Object.assign({}, loginForm, {
|
||||
password: encryptData(loginForm.password),
|
||||
});
|
||||
const res = await loginApi.login(encryptPasswordForm);
|
||||
stopRefrestCaptchaInterval();
|
||||
// localSave(LocalStorageKeyConst.USER_TOKEN, res.data.token ? res.data.token : '');
|
||||
saveTokenToCookie(res.data.token ? res.data.token : '');
|
||||
message.success('登录成功');
|
||||
//更新用户信息到pinia
|
||||
useUserStore().setUserLoginInfo(res.data);
|
||||
//构建系统的路由
|
||||
buildRoutes();
|
||||
router.push('/home');
|
||||
} catch (e) {
|
||||
if (e.data && e.data.code !== 0) {
|
||||
loginForm.captchaCode = '';
|
||||
getCaptcha();
|
||||
}
|
||||
smartSentry.captureError(e);
|
||||
} finally {
|
||||
SmartLoading.hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//--------------------- 验证码 ---------------------------------
|
||||
|
||||
const captchaBase64Image = ref('');
|
||||
async function getCaptcha() {
|
||||
//登录
|
||||
async function onLogin() {
|
||||
formRef.value.validate().then(async() => {
|
||||
try {
|
||||
let captchaResult = await loginApi.getCaptcha();
|
||||
captchaBase64Image.value = captchaResult.data.captchaBase64Image;
|
||||
loginForm.captchaUuid = captchaResult.data.captchaUuid;
|
||||
beginRefrestCaptchaInterval(captchaResult.data.expireSeconds);
|
||||
SmartLoading.show();
|
||||
// 密码加密
|
||||
let encryptPasswordForm = Object.assign({}, loginForm, {
|
||||
password: encryptData(loginForm.password)
|
||||
});
|
||||
const res = await loginApi.login(encryptPasswordForm);
|
||||
stopRefrestCaptchaInterval();
|
||||
// localSave(LocalStorageKeyConst.USER_TOKEN, res.data.token ? res.data.token : '');
|
||||
saveTokenToCookie(res.data.token ? res.data.token : '');
|
||||
message.success('登录成功');
|
||||
//更新用户信息到pinia
|
||||
useUserStore().setUserLoginInfo(res.data);
|
||||
//构建系统的路由
|
||||
buildRoutes();
|
||||
router.push('/home');
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
if (e.data && e.data.code !== 0) {
|
||||
loginForm.captchaCode = '';
|
||||
getCaptcha();
|
||||
}
|
||||
smartSentry.captureError(e);
|
||||
} finally {
|
||||
SmartLoading.hide();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let refrestCaptchaInterval = null;
|
||||
function beginRefrestCaptchaInterval(expireSeconds) {
|
||||
if (refrestCaptchaInterval === null) {
|
||||
refrestCaptchaInterval = setInterval(getCaptcha, (expireSeconds - 5) * 1000);
|
||||
//--------------------- 验证码 ---------------------------------
|
||||
|
||||
const captchaBase64Image = ref('');
|
||||
|
||||
async function getCaptcha() {
|
||||
try {
|
||||
let captchaResult = await loginApi.getCaptcha();
|
||||
captchaBase64Image.value = captchaResult.data.captchaBase64Image;
|
||||
loginForm.captchaUuid = captchaResult.data.captchaUuid;
|
||||
beginRefrestCaptchaInterval(captchaResult.data.expireSeconds);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
|
||||
let refrestCaptchaInterval = null;
|
||||
|
||||
function beginRefrestCaptchaInterval(expireSeconds) {
|
||||
if (refrestCaptchaInterval === null) {
|
||||
refrestCaptchaInterval = setInterval(getCaptcha, (expireSeconds - 5) * 1000);
|
||||
}
|
||||
}
|
||||
|
||||
function stopRefrestCaptchaInterval() {
|
||||
if (refrestCaptchaInterval != null) {
|
||||
clearInterval(refrestCaptchaInterval);
|
||||
refrestCaptchaInterval = null;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getCaptcha();
|
||||
getTwoFactorLoginFlag();
|
||||
});
|
||||
|
||||
//--------------------- 邮箱验证码 ---------------------------------
|
||||
|
||||
const emailCodeShowFlag = ref(false);
|
||||
let emailCodeTips = ref('获取邮箱验证码');
|
||||
let emailCodeButtonDisabled = ref(false);
|
||||
// 定时器
|
||||
let countDownTimer = null;
|
||||
|
||||
// 开始倒计时
|
||||
function runCountDown() {
|
||||
emailCodeButtonDisabled.value = true;
|
||||
let countDown = 60;
|
||||
emailCodeTips.value = `${ countDown }秒后重新获取`;
|
||||
countDownTimer = setInterval(() => {
|
||||
if (countDown > 1) {
|
||||
countDown--;
|
||||
emailCodeTips.value = `${ countDown }秒后重新获取`;
|
||||
} else {
|
||||
clearInterval(countDownTimer);
|
||||
emailCodeButtonDisabled.value = false;
|
||||
emailCodeTips.value = '获取验证码';
|
||||
}
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
function stopRefrestCaptchaInterval() {
|
||||
if (refrestCaptchaInterval != null) {
|
||||
clearInterval(refrestCaptchaInterval);
|
||||
refrestCaptchaInterval = null;
|
||||
}
|
||||
// 获取双因子登录标识
|
||||
async function getTwoFactorLoginFlag() {
|
||||
try {
|
||||
let result = await loginApi.getTwoFactorLoginFlag();
|
||||
emailCodeShowFlag.value = result.data;
|
||||
} catch (e) {
|
||||
smartSentry.captureError(e);
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(getCaptcha);
|
||||
// 发送邮箱验证码
|
||||
async function sendSmsCode() {
|
||||
try {
|
||||
SmartLoading.show();
|
||||
let result = await loginApi.sendLoginEmailCode(loginForm.loginName);
|
||||
message.success('验证码发送成功!请登录邮箱查看验证码~');
|
||||
runCountDown();
|
||||
} catch (e) {
|
||||
smartSentry.captureError(e);
|
||||
} finally {
|
||||
SmartLoading.hide();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
@import './login.less';
|
||||
@import "./login.less";
|
||||
</style>
|
||||
|
@ -0,0 +1,124 @@
|
||||
<!--
|
||||
* 职务表
|
||||
*
|
||||
* @Author: kaiyun
|
||||
* @Date: 2024-06-23 23:31:38
|
||||
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>
|
||||
-->
|
||||
<template>
|
||||
<a-modal
|
||||
:title="form.positionId ? '编辑' : '添加'"
|
||||
width="600px"
|
||||
:open="visibleFlag"
|
||||
@cancel="onClose"
|
||||
:maskClosable="false"
|
||||
:destroyOnClose="true"
|
||||
forceRender
|
||||
>
|
||||
<a-form ref="formRef" :model="form" :rules="rules" :label-col="{ span: 6 }">
|
||||
<a-form-item label="职务名称" name="positionName">
|
||||
<a-input style="width: 100%" v-model:value="form.positionName" placeholder="职务名称"/>
|
||||
</a-form-item>
|
||||
<a-form-item label="职级" name="level">
|
||||
<a-input style="width: 100%" v-model:value="form.level" placeholder="职级"/>
|
||||
</a-form-item>
|
||||
<a-form-item label="排序" name="sort">
|
||||
<a-input-number :min="0" :step="1" :precision="0" style="width: 100%" v-model:value="form.sort" placeholder="排序"/>
|
||||
</a-form-item>
|
||||
<a-form-item label="备注" name="remark">
|
||||
<a-input style="width: 100%" v-model:value="form.remark" placeholder="备注"/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
<template #footer>
|
||||
<a-space>
|
||||
<a-button @click="onClose">取消</a-button>
|
||||
<a-button type="primary" @click="onSubmit">保存</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</a-modal>
|
||||
</template>
|
||||
<script setup>
|
||||
import { reactive, ref, nextTick } from 'vue';
|
||||
import _ from 'lodash';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { SmartLoading } from '/@/components/framework/smart-loading';
|
||||
import { positionApi } from '/@/api/system/position/position-api';
|
||||
import { smartSentry } from '/@/lib/smart-sentry';
|
||||
|
||||
// ------------------------ 事件 ------------------------
|
||||
|
||||
const emits = defineEmits(['reloadList']);
|
||||
|
||||
// ------------------------ 显示与隐藏 ------------------------
|
||||
// 是否显示
|
||||
const visibleFlag = ref(false);
|
||||
|
||||
function show (rowData) {
|
||||
Object.assign(form, formDefault);
|
||||
if (rowData && !_.isEmpty(rowData)) {
|
||||
Object.assign(form, rowData);
|
||||
}
|
||||
visibleFlag.value = true;
|
||||
nextTick(() => {
|
||||
formRef.value.clearValidate();
|
||||
});
|
||||
}
|
||||
|
||||
function onClose () {
|
||||
Object.assign(form, formDefault);
|
||||
visibleFlag.value = false;
|
||||
}
|
||||
|
||||
// ------------------------ 表单 ------------------------
|
||||
|
||||
// 组件ref
|
||||
const formRef = ref();
|
||||
|
||||
const formDefault = {
|
||||
positionId: undefined,
|
||||
positionName: undefined, //职务名称
|
||||
level: undefined,//职纪
|
||||
sort: 0,
|
||||
remark: undefined, //备注
|
||||
};
|
||||
|
||||
let form = reactive({ ...formDefault });
|
||||
|
||||
const rules = {
|
||||
positionName: [{ required: true, message: '请输入职务名称' }],
|
||||
};
|
||||
|
||||
// 点击确定,验证表单
|
||||
async function onSubmit () {
|
||||
try {
|
||||
await formRef.value.validateFields();
|
||||
save();
|
||||
} catch (err) {
|
||||
message.error('参数验证错误,请仔细填写表单数据!');
|
||||
}
|
||||
}
|
||||
|
||||
// 新建、编辑API
|
||||
async function save () {
|
||||
SmartLoading.show();
|
||||
try {
|
||||
if (form.positionId) {
|
||||
await positionApi.update(form);
|
||||
} else {
|
||||
await positionApi.add(form);
|
||||
}
|
||||
message.success('操作成功');
|
||||
emits('reloadList');
|
||||
onClose();
|
||||
} catch (err) {
|
||||
smartSentry.captureError(err);
|
||||
} finally {
|
||||
SmartLoading.hide();
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
show,
|
||||
});
|
||||
</script>
|
@ -0,0 +1,262 @@
|
||||
<!--
|
||||
* 职务表
|
||||
*
|
||||
* @Author: kaiyun
|
||||
* @Date: 2024-06-23 23:31:38
|
||||
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>
|
||||
-->
|
||||
<template>
|
||||
<!---------- 查询表单form begin ----------->
|
||||
<a-form class="smart-query-form">
|
||||
<a-row class="smart-query-form-row">
|
||||
<a-form-item label="关键字查询" class="smart-query-form-item">
|
||||
<a-input style="width: 200px" v-model:value="queryForm.keywords" placeholder="关键字查询" />
|
||||
</a-form-item>
|
||||
<a-form-item class="smart-query-form-item">
|
||||
<a-button type="primary" @click="queryData">
|
||||
<template #icon>
|
||||
<SearchOutlined />
|
||||
</template>
|
||||
查询
|
||||
</a-button>
|
||||
<a-button @click="resetQuery" class="smart-margin-left10">
|
||||
<template #icon>
|
||||
<ReloadOutlined />
|
||||
</template>
|
||||
重置
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-row>
|
||||
</a-form>
|
||||
<!---------- 查询表单form end ----------->
|
||||
|
||||
<a-card size="small" :bordered="false" :hoverable="true">
|
||||
<!---------- 表格操作行 begin ----------->
|
||||
<a-row class="smart-table-btn-block">
|
||||
<div class="smart-table-operate-block">
|
||||
<a-button @click="showForm" type="primary">
|
||||
<template #icon>
|
||||
<PlusOutlined />
|
||||
</template>
|
||||
新建
|
||||
</a-button>
|
||||
<a-button @click="confirmBatchDelete" type="primary" danger :disabled="selectedRowKeyList.length === 0">
|
||||
<template #icon>
|
||||
<DeleteOutlined />
|
||||
</template>
|
||||
批量删除
|
||||
</a-button>
|
||||
</div>
|
||||
<div class="smart-table-setting-block">
|
||||
<TableOperator v-model="columns" :tableId="TABLE_ID_CONST.SYSTEM.EMPLOYEE" :refresh="queryData" />
|
||||
</div>
|
||||
</a-row>
|
||||
<!---------- 表格操作行 end ----------->
|
||||
|
||||
<!---------- 表格 begin ----------->
|
||||
<a-table
|
||||
size="small"
|
||||
:dataSource="tableData"
|
||||
:columns="columns"
|
||||
rowKey="positionId"
|
||||
bordered
|
||||
:loading="tableLoading"
|
||||
:pagination="false"
|
||||
:row-selection="{ selectedRowKeys: selectedRowKeyList, onChange: onSelectChange }"
|
||||
>
|
||||
<template #bodyCell="{ text, record, column }">
|
||||
<template v-if="column.dataIndex === 'action'">
|
||||
<div class="smart-table-operate">
|
||||
<a-button @click="showForm(record)" type="link">编辑</a-button>
|
||||
<a-button @click="onDelete(record)" danger type="link">删除</a-button>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
<!---------- 表格 end ----------->
|
||||
|
||||
<div class="smart-query-table-page">
|
||||
<a-pagination
|
||||
showSizeChanger
|
||||
showQuickJumper
|
||||
show-less-items
|
||||
:pageSizeOptions="PAGE_SIZE_OPTIONS"
|
||||
:defaultPageSize="queryForm.pageSize"
|
||||
v-model:current="queryForm.pageNum"
|
||||
v-model:pageSize="queryForm.pageSize"
|
||||
:total="total"
|
||||
@change="queryData"
|
||||
@showSizeChange="queryData"
|
||||
:show-total="(total) => `共${total}条`"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<PositionForm ref="formRef" @reloadList="queryData" />
|
||||
</a-card>
|
||||
</template>
|
||||
<script setup>
|
||||
import { reactive, ref, onMounted } from 'vue';
|
||||
import { message, Modal } from 'ant-design-vue';
|
||||
import { SmartLoading } from '/@/components/framework/smart-loading';
|
||||
import { positionApi } from '/@/api/system/position/position-api';
|
||||
import { PAGE_SIZE_OPTIONS } from '/@/constants/common-const';
|
||||
import { smartSentry } from '/@/lib/smart-sentry';
|
||||
import TableOperator from '/@/components/support/table-operator/index.vue';
|
||||
import PositionForm from './position-form.vue';
|
||||
import _ from 'lodash';
|
||||
import { TABLE_ID_CONST } from '/@/constants/support/table-id-const';
|
||||
// ---------------------------- 表格列 ----------------------------
|
||||
|
||||
const columns = ref([
|
||||
{
|
||||
title: '职务名称',
|
||||
dataIndex: 'positionName',
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '职级',
|
||||
dataIndex: 'level',
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '排序',
|
||||
dataIndex: 'sort',
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '备注',
|
||||
dataIndex: 'remark',
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime',
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'action',
|
||||
fixed: 'right',
|
||||
width: 90,
|
||||
},
|
||||
]);
|
||||
|
||||
// ---------------------------- 查询数据表单和方法 ----------------------------
|
||||
|
||||
const queryFormState = {
|
||||
keywords: undefined, //关键字查询
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
};
|
||||
// 查询表单form
|
||||
const queryForm = reactive({ ...queryFormState });
|
||||
// 表格加载loading
|
||||
const tableLoading = ref(false);
|
||||
// 表格数据
|
||||
const tableData = ref([]);
|
||||
// 总数
|
||||
const total = ref(0);
|
||||
|
||||
// 重置查询条件
|
||||
function resetQuery() {
|
||||
let pageSize = queryForm.pageSize;
|
||||
Object.assign(queryForm, queryFormState);
|
||||
queryForm.pageSize = pageSize;
|
||||
queryData();
|
||||
}
|
||||
|
||||
// 查询数据
|
||||
async function queryData() {
|
||||
tableLoading.value = true;
|
||||
try {
|
||||
let queryResult = await positionApi.queryPage(queryForm);
|
||||
tableData.value = queryResult.data.list;
|
||||
total.value = queryResult.data.total;
|
||||
} catch (e) {
|
||||
smartSentry.captureError(e);
|
||||
} finally {
|
||||
tableLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(queryData);
|
||||
|
||||
// ---------------------------- 添加/修改 ----------------------------
|
||||
const formRef = ref();
|
||||
|
||||
function showForm(data) {
|
||||
formRef.value.show(data);
|
||||
}
|
||||
|
||||
// ---------------------------- 单个删除 ----------------------------
|
||||
//确认删除
|
||||
function onDelete(data) {
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: '确定要删除选吗?',
|
||||
okText: '删除',
|
||||
okType: 'danger',
|
||||
onOk() {
|
||||
requestDelete(data);
|
||||
},
|
||||
cancelText: '取消',
|
||||
onCancel() {},
|
||||
});
|
||||
}
|
||||
|
||||
//请求删除
|
||||
async function requestDelete(data) {
|
||||
SmartLoading.show();
|
||||
try {
|
||||
await positionApi.delete(data.positionId);
|
||||
message.success('删除成功');
|
||||
queryData();
|
||||
} catch (e) {
|
||||
smartSentry.captureError(e);
|
||||
} finally {
|
||||
SmartLoading.hide();
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------- 批量删除 ----------------------------
|
||||
|
||||
// 选择表格行
|
||||
const selectedRowKeyList = ref([]);
|
||||
|
||||
function onSelectChange(selectedRowKeys) {
|
||||
selectedRowKeyList.value = selectedRowKeys;
|
||||
}
|
||||
|
||||
// 批量删除
|
||||
function confirmBatchDelete() {
|
||||
if (_.isEmpty(selectedRowKeyList.value)) {
|
||||
message.success('请选择要删除的数据');
|
||||
return;
|
||||
}
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: '确定要批量删除这些数据吗?',
|
||||
okText: '删除',
|
||||
okType: 'danger',
|
||||
onOk() {
|
||||
requestBatchDelete();
|
||||
},
|
||||
cancelText: '取消',
|
||||
onCancel() {},
|
||||
});
|
||||
}
|
||||
|
||||
//请求批量删除
|
||||
async function requestBatchDelete() {
|
||||
try {
|
||||
SmartLoading.show();
|
||||
await positionApi.batchDelete(selectedRowKeyList.value);
|
||||
message.success('删除成功');
|
||||
queryData();
|
||||
} catch (e) {
|
||||
smartSentry.captureError(e);
|
||||
} finally {
|
||||
SmartLoading.hide();
|
||||
}
|
||||
}
|
||||
</script>
|
Loading…
Reference in New Issue
Block a user