mirror of
https://gitee.com/lab1024/smart-admin.git
synced 2025-09-18 03:16:40 +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;
|
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 io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import net.lab1024.sa.admin.module.business.goods.constant.GoodsStatusEnum;
|
import net.lab1024.sa.admin.module.business.goods.constant.GoodsStatusEnum;
|
||||||
import net.lab1024.sa.base.common.domain.PageParam;
|
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.swagger.SchemaEnum;
|
||||||
import net.lab1024.sa.base.common.validator.enumeration.CheckEnum;
|
import net.lab1024.sa.base.common.validator.enumeration.CheckEnum;
|
||||||
import org.hibernate.validator.constraints.Length;
|
import org.hibernate.validator.constraints.Length;
|
||||||
@ -32,6 +34,7 @@ public class GoodsQueryForm extends PageParam {
|
|||||||
private Integer goodsStatus;
|
private Integer goodsStatus;
|
||||||
|
|
||||||
@Schema(description = "产地")
|
@Schema(description = "产地")
|
||||||
|
@JsonDeserialize(using = DictValueVoDeserializer.class)
|
||||||
private String place;
|
private String place;
|
||||||
|
|
||||||
@Schema(description = "上架状态")
|
@Schema(description = "上架状态")
|
||||||
|
@ -52,7 +52,6 @@ public class NoticeEmployeeService {
|
|||||||
public ResponseDTO<PageResult<NoticeEmployeeVO>> queryList(Long requestEmployeeId, NoticeEmployeeQueryForm noticeEmployeeQueryForm) {
|
public ResponseDTO<PageResult<NoticeEmployeeVO>> queryList(Long requestEmployeeId, NoticeEmployeeQueryForm noticeEmployeeQueryForm) {
|
||||||
Page<?> page = SmartPageUtil.convert2PageQuery(noticeEmployeeQueryForm);
|
Page<?> page = SmartPageUtil.convert2PageQuery(noticeEmployeeQueryForm);
|
||||||
|
|
||||||
//获取请求人的 部门及其子部门
|
|
||||||
List<Long> employeeDepartmentIdList = Lists.newArrayList();
|
List<Long> employeeDepartmentIdList = Lists.newArrayList();
|
||||||
EmployeeEntity employeeEntity = employeeService.getById(requestEmployeeId);
|
EmployeeEntity employeeEntity = employeeService.getById(requestEmployeeId);
|
||||||
// 如果不是管理员 则获取请求人的 部门及其子部门
|
// 如果不是管理员 则获取请求人的 部门及其子部门
|
||||||
|
@ -110,7 +110,7 @@ public class EmployeeController {
|
|||||||
@Operation(summary = "重置员工密码 @author 卓大")
|
@Operation(summary = "重置员工密码 @author 卓大")
|
||||||
@GetMapping("/employee/update/password/reset/{employeeId}")
|
@GetMapping("/employee/update/password/reset/{employeeId}")
|
||||||
@SaCheckPermission("system:employee:password:reset")
|
@SaCheckPermission("system:employee:password:reset")
|
||||||
public ResponseDTO<String> resetPassword(@PathVariable Integer employeeId) {
|
public ResponseDTO<String> resetPassword(@PathVariable Long employeeId) {
|
||||||
return employeeService.resetPassword(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
|
@Service
|
||||||
public class EmployeeService {
|
public class EmployeeService {
|
||||||
|
|
||||||
private static final String PASSWORD_SALT_FORMAT = "smart_%s_admin_$^&*";
|
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private EmployeeDao employeeDao;
|
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();
|
String password = securityPasswordService.randomPassword();
|
||||||
employeeDao.updatePassword(employeeId, SecurityPasswordService.getEncryptPwd(password));
|
employeeDao.updatePassword(employeeId, SecurityPasswordService.getEncryptPwd(password));
|
||||||
return ResponseDTO.ok(password);
|
return ResponseDTO.ok(password);
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
# 项目配置: 名称、日志目录
|
# 项目配置: 名称、日志目录
|
||||||
project:
|
project:
|
||||||
name: sa-admin
|
name: sa-admin
|
||||||
log-directory: /home/project/smartadmin/sit/log
|
log-directory: /home/project/smartadmin/test/log
|
||||||
|
|
||||||
# 项目端口和url根路径
|
# 项目端口和url根路径
|
||||||
server:
|
server:
|
||||||
|
@ -18,11 +18,20 @@ import java.io.IOException;
|
|||||||
*/
|
*/
|
||||||
public class LongJsonSerializer extends JsonSerializer<Long> {
|
public class LongJsonSerializer extends JsonSerializer<Long> {
|
||||||
|
|
||||||
|
public static final LongJsonSerializer INSTANCE = new LongJsonSerializer();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void serialize(Long value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
|
public void serialize(Long value, JsonGenerator gen, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
|
||||||
String text = (value == null ? null : String.valueOf(value));
|
if (null == value) {
|
||||||
if (text != null) {
|
gen.writeNull();
|
||||||
jsonGenerator.writeString(text);
|
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.deser.LocalDateTimeDeserializer;
|
||||||
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
|
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
|
||||||
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
|
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.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
|
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
@ -17,7 +18,7 @@ import java.time.LocalDateTime;
|
|||||||
import java.time.format.DateTimeParseException;
|
import java.time.format.DateTimeParseException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* java8 localDate 时间类格式化配置
|
* Json 序列化配置
|
||||||
*
|
*
|
||||||
* @Author 1024创新实验室-主任: 卓大
|
* @Author 1024创新实验室-主任: 卓大
|
||||||
* @Date 2017-11-28 15:21:10
|
* @Date 2017-11-28 15:21:10
|
||||||
@ -26,7 +27,7 @@ import java.time.format.DateTimeParseException;
|
|||||||
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>
|
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>
|
||||||
*/
|
*/
|
||||||
@Configuration
|
@Configuration
|
||||||
public class DateConfig {
|
public class JsonConfig {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public Jackson2ObjectMapperBuilderCustomizer customizer() {
|
public Jackson2ObjectMapperBuilderCustomizer customizer() {
|
||||||
@ -35,6 +36,7 @@ public class DateConfig {
|
|||||||
builder.deserializers(new LocalDateTimeDeserializer(DatePattern.NORM_DATETIME_FORMAT.getDateTimeFormatter()));
|
builder.deserializers(new LocalDateTimeDeserializer(DatePattern.NORM_DATETIME_FORMAT.getDateTimeFormatter()));
|
||||||
builder.serializers(new LocalDateSerializer(DatePattern.NORM_DATE_FORMAT.getDateTimeFormatter()));
|
builder.serializers(new LocalDateSerializer(DatePattern.NORM_DATE_FORMAT.getDateTimeFormatter()));
|
||||||
builder.serializers(new LocalDateTimeSerializer(DatePattern.NORM_DATETIME_FORMAT.getDateTimeFormatter()));
|
builder.serializers(new LocalDateTimeSerializer(DatePattern.NORM_DATETIME_FORMAT.getDateTimeFormatter()));
|
||||||
|
builder.serializerByType(Long.class, LongJsonSerializer.INSTANCE);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -32,9 +32,8 @@ import java.util.Optional;
|
|||||||
* springdoc-openapi 配置
|
* springdoc-openapi 配置
|
||||||
* nginx配置前缀时如果需要访问【/swagger-ui/index.html】需添加额外nginx配置
|
* nginx配置前缀时如果需要访问【/swagger-ui/index.html】需添加额外nginx配置
|
||||||
* location /v3/api-docs/ {
|
* location /v3/api-docs/ {
|
||||||
* proxy_pass http://127.0.0.1:11024/v3/api-docs/;
|
* proxy_pass http://127.0.0.1:1024/v3/api-docs/;
|
||||||
* }
|
* }
|
||||||
*
|
|
||||||
* @Author 1024创新实验室-主任: 卓大
|
* @Author 1024创新实验室-主任: 卓大
|
||||||
* @Date 2020-03-25 22:54:46
|
* @Date 2020-03-25 22:54:46
|
||||||
* @Wechat zhuoda1024
|
* @Wechat zhuoda1024
|
||||||
@ -48,7 +47,7 @@ public class SwaggerConfig {
|
|||||||
/**
|
/**
|
||||||
* 用于解决/swagger-ui/index.html页面ServersUrl 测试环境部署错误问题
|
* 用于解决/swagger-ui/index.html页面ServersUrl 测试环境部署错误问题
|
||||||
*/
|
*/
|
||||||
@Value("${springdoc.swagger-ui.server-base-url:''}")
|
@Value("${springdoc.swagger-ui.server-base-url}")
|
||||||
private String serverBaseUrl;
|
private String serverBaseUrl;
|
||||||
|
|
||||||
public static final String[] SWAGGER_WHITELIST = {
|
public static final String[] SWAGGER_WHITELIST = {
|
||||||
@ -104,7 +103,6 @@ public class SwaggerConfig {
|
|||||||
/**
|
/**
|
||||||
* 以下代码可以用于设置 /swagger-ui/index.html 的serverBaseUrl
|
* 以下代码可以用于设置 /swagger-ui/index.html 的serverBaseUrl
|
||||||
* 如果使用knife4j则不需要
|
* 如果使用knife4j则不需要
|
||||||
*
|
|
||||||
* @param openAPI
|
* @param openAPI
|
||||||
* @param securityParser
|
* @param securityParser
|
||||||
* @param springDocConfigProperties
|
* @param springDocConfigProperties
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package net.lab1024.sa.base.module.support.apiencrypt.advice;
|
package net.lab1024.sa.base.module.support.apiencrypt.advice;
|
||||||
|
|
||||||
import com.alibaba.fastjson.JSON;
|
import com.alibaba.fastjson.JSON;
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import net.lab1024.sa.base.common.domain.ResponseDTO;
|
import net.lab1024.sa.base.common.domain.ResponseDTO;
|
||||||
@ -33,6 +35,9 @@ public class EncryptResponseAdvice implements ResponseBodyAdvice<ResponseDTO> {
|
|||||||
@Resource
|
@Resource
|
||||||
private ApiEncryptService apiEncryptService;
|
private ApiEncryptService apiEncryptService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ObjectMapper objectMapper;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
|
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
|
||||||
return returnType.hasMethodAnnotation(ApiEncrypt.class) || returnType.getContainingClass().isAnnotationPresent(ApiEncrypt.class);
|
return returnType.hasMethodAnnotation(ApiEncrypt.class) || returnType.getContainingClass().isAnnotationPresent(ApiEncrypt.class);
|
||||||
@ -44,7 +49,12 @@ public class EncryptResponseAdvice implements ResponseBodyAdvice<ResponseDTO> {
|
|||||||
return body;
|
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.setData(encrypt);
|
||||||
body.setDataType(DataTypeEnum.ENCRYPT.getValue());
|
body.setDataType(DataTypeEnum.ENCRYPT.getValue());
|
||||||
return body;
|
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
|
@Override
|
||||||
public String decrypt(String data) {
|
public String decrypt(String data) {
|
||||||
|
@ -101,7 +101,6 @@ public class AddFormVariableService extends CodeGenerateBaseVariableService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//字典
|
//字典
|
||||||
if (SmartStringUtil.isNotEmpty(codeField.getDict())) {
|
if (SmartStringUtil.isNotEmpty(codeField.getDict())) {
|
||||||
finalFieldMap.put("dict", "\n @JsonDeserialize(using = DictValueVoDeserializer.class)");
|
finalFieldMap.put("dict", "\n @JsonDeserialize(using = DictValueVoDeserializer.class)");
|
||||||
|
@ -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
|
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
|
username: root
|
||||||
password: SmartAdmin666
|
password: SmartAdmin666
|
||||||
driver-class-name: com.p6spy.engine.spy.P6SpyDriver
|
|
||||||
initial-size: 2
|
initial-size: 2
|
||||||
min-idle: 2
|
min-idle: 2
|
||||||
max-active: 10
|
max-active: 10
|
||||||
max-wait: 60000
|
max-wait: 60000
|
||||||
time-between-eviction-runs-millis: 60000
|
time-between-eviction-runs-millis: 60000
|
||||||
min-evictable-idle-time-millis: 300000
|
min-evictable-idle-time-millis: 300000
|
||||||
|
driver-class-name: com.p6spy.engine.spy.P6SpyDriver
|
||||||
filters: stat
|
filters: stat
|
||||||
druid:
|
druid:
|
||||||
username: druid
|
username: druid
|
||||||
@ -40,7 +40,7 @@ spring:
|
|||||||
host: smtp.163.com
|
host: smtp.163.com
|
||||||
port: 465
|
port: 465
|
||||||
username: lab1024@163.com
|
username: lab1024@163.com
|
||||||
password: 1
|
password: 1024lab
|
||||||
properties:
|
properties:
|
||||||
mail:
|
mail:
|
||||||
smtp:
|
smtp:
|
||||||
|
@ -40,7 +40,7 @@ spring:
|
|||||||
host: smtp.163.com
|
host: smtp.163.com
|
||||||
port: 465
|
port: 465
|
||||||
username: lab1024@163.com
|
username: lab1024@163.com
|
||||||
password: 1
|
password: 1024lab
|
||||||
properties:
|
properties:
|
||||||
mail:
|
mail:
|
||||||
smtp:
|
smtp:
|
||||||
|
@ -30,10 +30,11 @@ spring:
|
|||||||
timeout: 10000ms
|
timeout: 10000ms
|
||||||
lettuce:
|
lettuce:
|
||||||
pool:
|
pool:
|
||||||
max-active: 100
|
max-active: 5
|
||||||
min-idle: 10
|
min-idle: 1
|
||||||
max-idle: 50
|
max-idle: 3
|
||||||
max-wait: 30000ms
|
max-wait: 30000ms
|
||||||
|
|
||||||
# 邮件,置以SSL的方式发送, 这个需要使用这种方式并且端口是465
|
# 邮件,置以SSL的方式发送, 这个需要使用这种方式并且端口是465
|
||||||
mail:
|
mail:
|
||||||
host: smtp.163.com
|
host: smtp.163.com
|
||||||
|
@ -40,7 +40,7 @@ spring:
|
|||||||
host: smtp.163.com
|
host: smtp.163.com
|
||||||
port: 465
|
port: 465
|
||||||
username: lab1024@163.com
|
username: lab1024@163.com
|
||||||
password: 1
|
password: 1024lab
|
||||||
properties:
|
properties:
|
||||||
mail:
|
mail:
|
||||||
smtp:
|
smtp:
|
||||||
|
@ -86,7 +86,7 @@ public class AdminInterceptor implements HandlerInterceptor {
|
|||||||
Method method = ((HandlerMethod) handler).getMethod();
|
Method method = ((HandlerMethod) handler).getMethod();
|
||||||
NoNeedLogin noNeedLogin = ((HandlerMethod) handler).getMethodAnnotation(NoNeedLogin.class);
|
NoNeedLogin noNeedLogin = ((HandlerMethod) handler).getMethodAnnotation(NoNeedLogin.class);
|
||||||
if (noNeedLogin != null) {
|
if (noNeedLogin != null) {
|
||||||
checkActiveTimeout(requestEmployee,debugNumberTokenFlag);
|
checkActiveTimeout(requestEmployee, debugNumberTokenFlag);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,7 +96,7 @@ public class AdminInterceptor implements HandlerInterceptor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 检测token 活跃频率
|
// 检测token 活跃频率
|
||||||
checkActiveTimeout(requestEmployee,debugNumberTokenFlag);
|
checkActiveTimeout(requestEmployee, debugNumberTokenFlag);
|
||||||
|
|
||||||
|
|
||||||
// --------------- 第三步: 校验 权限 ---------------
|
// --------------- 第三步: 校验 权限 ---------------
|
||||||
@ -107,7 +107,7 @@ public class AdminInterceptor implements HandlerInterceptor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 如果是超级管理员的话,不需要校验权限
|
// 如果是超级管理员的话,不需要校验权限
|
||||||
if(requestEmployee.getAdministratorFlag()){
|
if (requestEmployee.getAdministratorFlag()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,7 +110,7 @@ public class EmployeeController {
|
|||||||
@Operation(summary = "重置员工密码 @author 卓大")
|
@Operation(summary = "重置员工密码 @author 卓大")
|
||||||
@GetMapping("/employee/update/password/reset/{employeeId}")
|
@GetMapping("/employee/update/password/reset/{employeeId}")
|
||||||
@SaCheckPermission("system:employee:password:reset")
|
@SaCheckPermission("system:employee:password:reset")
|
||||||
public ResponseDTO<String> resetPassword(@PathVariable Integer employeeId) {
|
public ResponseDTO<String> resetPassword(@PathVariable Long employeeId) {
|
||||||
return employeeService.resetPassword(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();
|
String password = securityPasswordService.randomPassword();
|
||||||
employeeDao.updatePassword(employeeId, SecurityPasswordService.getEncryptPwd(password));
|
employeeDao.updatePassword(employeeId, SecurityPasswordService.getEncryptPwd(password));
|
||||||
return ResponseDTO.ok(password);
|
return ResponseDTO.ok(password);
|
||||||
|
@ -18,11 +18,20 @@ import java.io.IOException;
|
|||||||
*/
|
*/
|
||||||
public class LongJsonSerializer extends JsonSerializer<Long> {
|
public class LongJsonSerializer extends JsonSerializer<Long> {
|
||||||
|
|
||||||
|
public static final LongJsonSerializer INSTANCE = new LongJsonSerializer();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void serialize(Long value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
|
public void serialize(Long value, JsonGenerator gen, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
|
||||||
String text = (value == null ? null : String.valueOf(value));
|
if (null == value) {
|
||||||
if (text != null) {
|
gen.writeNull();
|
||||||
jsonGenerator.writeString(text);
|
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.deser.LocalDateTimeDeserializer;
|
||||||
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
|
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
|
||||||
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
|
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.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
|
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
@ -17,7 +18,7 @@ import java.time.LocalDateTime;
|
|||||||
import java.time.format.DateTimeParseException;
|
import java.time.format.DateTimeParseException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* java8 localDate 时间类格式化配置
|
* json 序列化配置
|
||||||
*
|
*
|
||||||
* @Author 1024创新实验室-主任: 卓大
|
* @Author 1024创新实验室-主任: 卓大
|
||||||
* @Date 2017-11-28 15:21:10
|
* @Date 2017-11-28 15:21:10
|
||||||
@ -26,7 +27,7 @@ import java.time.format.DateTimeParseException;
|
|||||||
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>
|
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>
|
||||||
*/
|
*/
|
||||||
@Configuration
|
@Configuration
|
||||||
public class DateConfig {
|
public class JsonConfig {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public Jackson2ObjectMapperBuilderCustomizer customizer() {
|
public Jackson2ObjectMapperBuilderCustomizer customizer() {
|
||||||
@ -35,6 +36,7 @@ public class DateConfig {
|
|||||||
builder.deserializers(new LocalDateTimeDeserializer(DatePattern.NORM_DATETIME_FORMAT.getDateTimeFormatter()));
|
builder.deserializers(new LocalDateTimeDeserializer(DatePattern.NORM_DATETIME_FORMAT.getDateTimeFormatter()));
|
||||||
builder.serializers(new LocalDateSerializer(DatePattern.NORM_DATE_FORMAT.getDateTimeFormatter()));
|
builder.serializers(new LocalDateSerializer(DatePattern.NORM_DATE_FORMAT.getDateTimeFormatter()));
|
||||||
builder.serializers(new LocalDateTimeSerializer(DatePattern.NORM_DATETIME_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;
|
package net.lab1024.sa.base.module.support.apiencrypt.advice;
|
||||||
|
|
||||||
import com.alibaba.fastjson.JSON;
|
import com.alibaba.fastjson.JSON;
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import net.lab1024.sa.base.common.domain.ResponseDTO;
|
import net.lab1024.sa.base.common.domain.ResponseDTO;
|
||||||
import net.lab1024.sa.base.common.enumeration.DataTypeEnum;
|
import net.lab1024.sa.base.common.enumeration.DataTypeEnum;
|
||||||
@ -34,6 +36,9 @@ public class EncryptResponseAdvice implements ResponseBodyAdvice<ResponseDTO> {
|
|||||||
@Resource
|
@Resource
|
||||||
private ApiEncryptService apiEncryptService;
|
private ApiEncryptService apiEncryptService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ObjectMapper objectMapper;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
|
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
|
||||||
return returnType.hasMethodAnnotation(ApiEncrypt.class) || returnType.getContainingClass().isAnnotationPresent(ApiEncrypt.class);
|
return returnType.hasMethodAnnotation(ApiEncrypt.class) || returnType.getContainingClass().isAnnotationPresent(ApiEncrypt.class);
|
||||||
@ -45,7 +50,12 @@ public class EncryptResponseAdvice implements ResponseBodyAdvice<ResponseDTO> {
|
|||||||
return body;
|
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.setData(encrypt);
|
||||||
body.setDataType(DataTypeEnum.ENCRYPT.getValue());
|
body.setDataType(DataTypeEnum.ENCRYPT.getValue());
|
||||||
return body;
|
return body;
|
||||||
|
@ -39,7 +39,7 @@ spring:
|
|||||||
host: smtp.163.com
|
host: smtp.163.com
|
||||||
port: 465
|
port: 465
|
||||||
username: lab1024@163.com
|
username: lab1024@163.com
|
||||||
password: 1
|
password: 1024lab
|
||||||
properties:
|
properties:
|
||||||
mail:
|
mail:
|
||||||
smtp:
|
smtp:
|
||||||
|
@ -39,7 +39,7 @@ spring:
|
|||||||
host: smtp.163.com
|
host: smtp.163.com
|
||||||
port: 465
|
port: 465
|
||||||
username: lab1024@163.com
|
username: lab1024@163.com
|
||||||
password: 1
|
password: 1024lab
|
||||||
properties:
|
properties:
|
||||||
mail:
|
mail:
|
||||||
smtp:
|
smtp:
|
||||||
|
@ -39,7 +39,7 @@ spring:
|
|||||||
host: smtp.163.com
|
host: smtp.163.com
|
||||||
port: 465
|
port: 465
|
||||||
username: lab1024@163.com
|
username: lab1024@163.com
|
||||||
password: 1
|
password: 1024lab
|
||||||
properties:
|
properties:
|
||||||
mail:
|
mail:
|
||||||
smtp:
|
smtp:
|
||||||
|
@ -32,6 +32,7 @@
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}"
|
}"
|
||||||
|
:getPopupContainer="getPopupContainer"
|
||||||
>
|
>
|
||||||
<!---全局loading--->
|
<!---全局loading--->
|
||||||
<a-spin :spinning="spinning" tip="稍等片刻,我在拼命加载中..." size="large">
|
<a-spin :spinning="spinning" tip="稍等片刻,我在拼命加载中..." size="large">
|
||||||
@ -49,6 +50,8 @@
|
|||||||
import { useSpinStore } from '/@/store/modules/system/spin';
|
import { useSpinStore } from '/@/store/modules/system/spin';
|
||||||
import { theme } from 'ant-design-vue';
|
import { theme } from 'ant-design-vue';
|
||||||
import { themeColors } from '/@/theme/color.js';
|
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 antdLocale = computed(() => messages[useAppConfigStore().language].antdLocale);
|
||||||
const dayjsLocale = computed(() => messages[useAppConfigStore().language].dayjsLocale);
|
const dayjsLocale = computed(() => messages[useAppConfigStore().language].dayjsLocale);
|
||||||
@ -67,4 +70,31 @@
|
|||||||
const borderRadius = computed(() => {
|
const borderRadius = computed(() => {
|
||||||
return useAppConfigStore().borderRadius;
|
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>
|
</script>
|
||||||
|
@ -12,12 +12,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<span>
|
<span>
|
||||||
<a-tooltip title="全屏" v-if="!fullScreenFlag">
|
<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>
|
<template #icon><fullscreen-outlined /></template>
|
||||||
</a-button>
|
</a-button>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-tooltip title="取消全屏" v-if="fullScreenFlag">
|
<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>
|
<template #icon><fullscreen-exit-outlined /></template>
|
||||||
</a-button>
|
</a-button>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
@ -43,11 +43,13 @@
|
|||||||
import { message } from 'ant-design-vue';
|
import { message } from 'ant-design-vue';
|
||||||
import { mergeColumn } from './smart-table-column-merge';
|
import { mergeColumn } from './smart-table-column-merge';
|
||||||
import { smartSentry } from '/@/lib/smart-sentry';
|
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({
|
const props = defineProps({
|
||||||
// 表格列数组
|
// 表格列数组
|
||||||
modelValue: {
|
modelValue: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: new Array(),
|
default: [],
|
||||||
},
|
},
|
||||||
// 刷新表格函数
|
// 刷新表格函数
|
||||||
refresh: {
|
refresh: {
|
||||||
@ -93,22 +95,39 @@
|
|||||||
|
|
||||||
// ----------------- 全屏 -------------------
|
// ----------------- 全屏 -------------------
|
||||||
const fullScreenFlag = ref(false);
|
const fullScreenFlag = ref(false);
|
||||||
function fullScreen() {
|
|
||||||
|
function onFullScreen() {
|
||||||
if (fullScreenFlag.value) {
|
if (fullScreenFlag.value) {
|
||||||
//取消全屏
|
// 退出全屏
|
||||||
exitFullscreen(document.querySelector('#smartAdminLayoutContent'));
|
handleExitFullScreen();
|
||||||
fullScreenFlag.value = false;
|
exitElementFullscreen(document.getElementById(LAYOUT_ELEMENT_IDS.content));
|
||||||
document.querySelector('#smartAdminPageTag').style.visibility = 'visible';
|
|
||||||
} else {
|
} else {
|
||||||
//全屏
|
//全屏
|
||||||
launchFullScreen(document.querySelector('#smartAdminLayoutContent'));
|
message.config({
|
||||||
|
getContainer: () => document.getElementById(LAYOUT_ELEMENT_IDS.content),
|
||||||
|
});
|
||||||
fullScreenFlag.value = true;
|
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) {
|
if (element.requestFullscreen) {
|
||||||
element.requestFullscreen();
|
element.requestFullscreen();
|
||||||
} else if (element.mozRequestFullScreen) {
|
} else if (element.mozRequestFullScreen) {
|
||||||
@ -120,9 +139,23 @@
|
|||||||
} else {
|
} else {
|
||||||
message.error('当前浏览器不支持部分全屏!');
|
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) {
|
if (document.exitFullscreen) {
|
||||||
document.exitFullscreen();
|
document.exitFullscreen();
|
||||||
} else if (document.mozCancelFullScreen) {
|
} else if (document.mozCancelFullScreen) {
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
</a-tabs>
|
</a-tabs>
|
||||||
</div>
|
</div>
|
||||||
<template #rightExtra>
|
<template #overlay>
|
||||||
<a-menu>
|
<a-menu>
|
||||||
<a-menu-item @click="closeByMenu(false)">关闭其他</a-menu-item>
|
<a-menu-item @click="closeByMenu(false)">关闭其他</a-menu-item>
|
||||||
<a-menu-item @click="closeByMenu(true)">关闭所有</a-menu-item>
|
<a-menu-item @click="closeByMenu(true)">关闭所有</a-menu-item>
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
</a-tabs>
|
</a-tabs>
|
||||||
</div>
|
</div>
|
||||||
<template #rightExtra>
|
<template #overlay>
|
||||||
<a-menu>
|
<a-menu>
|
||||||
<a-menu-item @click="closeByMenu(false)">关闭其他</a-menu-item>
|
<a-menu-item @click="closeByMenu(false)">关闭其他</a-menu-item>
|
||||||
<a-menu-item @click="closeByMenu(true)">关闭所有</a-menu-item>
|
<a-menu-item @click="closeByMenu(true)">关闭所有</a-menu-item>
|
||||||
|
@ -79,10 +79,10 @@
|
|||||||
const websiteName = computed(() => useAppConfigStore().websiteName);
|
const websiteName = computed(() => useAppConfigStore().websiteName);
|
||||||
const windowHeight = window.innerHeight;
|
const windowHeight = window.innerHeight;
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
watermark.set('smartAdminLayoutContent', useUserStore().actualName);
|
watermark.set(LAYOUT_ELEMENT_IDS.content, useUserStore().actualName);
|
||||||
});
|
});
|
||||||
const backTopTarget = () => {
|
const backTopTarget = () => {
|
||||||
return document.getElementById('smartAdminMain');
|
return document.getElementById(LAYOUT_ELEMENT_IDS.main);
|
||||||
};
|
};
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const route = useRoute();
|
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>
|
<template>
|
||||||
<a-layout class="admin-layout" style="min-height: 100%">
|
<a-layout class="admin-layout" style="min-height: 100%">
|
||||||
<!-- 侧边菜单 side-menu -->
|
<!-- 侧边菜单 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" />
|
<SideExpandMenu :collapsed="collapsed" />
|
||||||
</a-layout-sider>
|
</a-layout-sider>
|
||||||
|
|
||||||
<!--中间内容,一共三部分:1、顶部;2、中间内容区域;3、底部(一般是公司版权信息);-->
|
<!--中间内容,一共三部分: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-row justify="space-between" class="smart-layout-header-user">
|
||||||
<a-col class="smart-layout-header-left">
|
<a-col class="smart-layout-header-left">
|
||||||
<span class="collapsed-button">
|
<span class="collapsed-button">
|
||||||
@ -44,19 +44,19 @@
|
|||||||
</a-layout-header>
|
</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组件-->
|
<!--不keepAlive的iframe使用单个iframe组件-->
|
||||||
<IframeIndex v-show="iframeNotKeepAlivePageFlag" :key="route.name" :name="route.name" :url="route.meta.frameUrl" />
|
<IframeIndex v-show="iframeNotKeepAlivePageFlag" :key="route.name" :name="route.name" :url="route.meta.frameUrl" />
|
||||||
<!--keepAlive的iframe 每个页面一个iframe组件-->
|
<!--keepAlive的iframe 每个页面一个iframe组件-->
|
||||||
<IframeIndex
|
<IframeIndex
|
||||||
v-for="item in keepAliveIframePages"
|
v-for="item in keepAliveIframePages"
|
||||||
v-show="route.name == item.name"
|
v-show="route.name === item.name"
|
||||||
:key="item.name"
|
:key="item.name"
|
||||||
:name="item.name"
|
:name="item.name"
|
||||||
:url="item.meta.frameUrl"
|
:url="item.meta.frameUrl"
|
||||||
/>
|
/>
|
||||||
<!--非iframe使用router-view-->
|
<!--非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 }">
|
<router-view v-slot="{ Component }">
|
||||||
<keep-alive :include="keepAliveIncludes">
|
<keep-alive :include="keepAliveIncludes">
|
||||||
<component :is="Component" :key="route.name" />
|
<component :is="Component" :key="route.name" />
|
||||||
@ -98,6 +98,7 @@
|
|||||||
import SideHelpDoc from './components/side-help-doc/index.vue';
|
import SideHelpDoc from './components/side-help-doc/index.vue';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import { HOME_PAGE_NAME } from '/@/constants/system/home-const';
|
import { HOME_PAGE_NAME } from '/@/constants/system/home-const';
|
||||||
|
import { LAYOUT_ELEMENT_IDS } from '/@/layout/layout-const.js';
|
||||||
|
|
||||||
const windowHeight = ref(window.innerHeight);
|
const windowHeight = ref(window.innerHeight);
|
||||||
|
|
||||||
@ -130,7 +131,7 @@
|
|||||||
//页面初始化的时候加载水印
|
//页面初始化的时候加载水印
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (watermarkFlag.value) {
|
if (watermarkFlag.value) {
|
||||||
watermark.set('smartAdminLayoutContent', useUserStore().actualName);
|
watermark.set(LAYOUT_ELEMENT_IDS.content, useUserStore().actualName);
|
||||||
} else {
|
} else {
|
||||||
watermark.clear();
|
watermark.clear();
|
||||||
}
|
}
|
||||||
@ -140,7 +141,7 @@
|
|||||||
() => watermarkFlag.value,
|
() => watermarkFlag.value,
|
||||||
(newValue) => {
|
(newValue) => {
|
||||||
if (newValue) {
|
if (newValue) {
|
||||||
watermark.set('smartAdminLayoutContent', useUserStore().actualName);
|
watermark.set(LAYOUT_ELEMENT_IDS.content, useUserStore().actualName);
|
||||||
} else {
|
} else {
|
||||||
watermark.clear();
|
watermark.clear();
|
||||||
}
|
}
|
||||||
@ -153,7 +154,7 @@
|
|||||||
|
|
||||||
//回到顶部
|
//回到顶部
|
||||||
const backTopTarget = () => {
|
const backTopTarget = () => {
|
||||||
return document.getElementById('smartAdminMain');
|
return document.getElementById(LAYOUT_ELEMENT_IDS.main);
|
||||||
};
|
};
|
||||||
// ----------------------- keep-alive相关 -----------------------
|
// ----------------------- keep-alive相关 -----------------------
|
||||||
let { route, keepAliveIncludes, iframeNotKeepAlivePageFlag, keepAliveIframePages } = smartKeepAlive();
|
let { route, keepAliveIncludes, iframeNotKeepAlivePageFlag, keepAliveIframePages } = smartKeepAlive();
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<a-layout class="admin-layout" style="min-height: 100%">
|
<a-layout class="admin-layout" style="min-height: 100%">
|
||||||
<!-- 侧边菜单 side-menu -->
|
<!-- 侧边菜单 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" />
|
<SideMenu :collapsed="collapsed" />
|
||||||
</a-layout-sider>
|
</a-layout-sider>
|
||||||
|
|
||||||
<!--中间内容,一共三部分:1、顶部;2、中间内容区域;3、底部(一般是公司版权信息);-->
|
<!--中间内容,一共三部分: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-row class="layout-header-user" justify="space-between">
|
||||||
<a-col class="layout-header-left">
|
<a-col class="layout-header-left">
|
||||||
<span class="collapsed-button">
|
<span class="collapsed-button">
|
||||||
@ -35,13 +35,13 @@
|
|||||||
</a-layout-header>
|
</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组件-->
|
<!--不keepAlive的iframe使用单个iframe组件-->
|
||||||
<IframeIndex v-if="iframeNotKeepAlivePageFlag" :key="route.name" :name="route.name" :url="route.meta.frameUrl" />
|
<IframeIndex v-if="iframeNotKeepAlivePageFlag" :key="route.name" :name="route.name" :url="route.meta.frameUrl" />
|
||||||
<!--keepAlive的iframe 每个页面一个iframe组件-->
|
<!--keepAlive的iframe 每个页面一个iframe组件-->
|
||||||
<IframeIndex
|
<IframeIndex
|
||||||
v-for="item in keepAliveIframePages"
|
v-for="item in keepAliveIframePages"
|
||||||
v-show="route.name == item.name"
|
v-show="route.name === item.name"
|
||||||
:key="item.name"
|
:key="item.name"
|
||||||
:name="item.name"
|
:name="item.name"
|
||||||
:url="item.meta.frameUrl"
|
:url="item.meta.frameUrl"
|
||||||
@ -93,6 +93,7 @@
|
|||||||
import SideHelpDoc from './components/side-help-doc/index.vue';
|
import SideHelpDoc from './components/side-help-doc/index.vue';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import { HOME_PAGE_NAME } from '/@/constants/system/home-const';
|
import { HOME_PAGE_NAME } from '/@/constants/system/home-const';
|
||||||
|
import { LAYOUT_ELEMENT_IDS } from '/@/layout/layout-const.js';
|
||||||
|
|
||||||
const windowHeight = ref(window.innerHeight);
|
const windowHeight = ref(window.innerHeight);
|
||||||
//菜单宽度
|
//菜单宽度
|
||||||
@ -126,7 +127,7 @@
|
|||||||
//页面初始化的时候加载水印
|
//页面初始化的时候加载水印
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (watermarkFlag.value) {
|
if (watermarkFlag.value) {
|
||||||
watermark.set('smartAdminLayoutContent', useUserStore().actualName);
|
watermark.set(LAYOUT_ELEMENT_IDS.content, useUserStore().actualName);
|
||||||
} else {
|
} else {
|
||||||
watermark.clear();
|
watermark.clear();
|
||||||
}
|
}
|
||||||
@ -136,7 +137,7 @@
|
|||||||
() => watermarkFlag.value,
|
() => watermarkFlag.value,
|
||||||
(newValue) => {
|
(newValue) => {
|
||||||
if (newValue) {
|
if (newValue) {
|
||||||
watermark.set('smartAdminLayoutContent', useUserStore().actualName);
|
watermark.set(LAYOUT_ELEMENT_IDS.content, useUserStore().actualName);
|
||||||
} else {
|
} else {
|
||||||
watermark.clear();
|
watermark.clear();
|
||||||
}
|
}
|
||||||
@ -145,7 +146,7 @@
|
|||||||
|
|
||||||
//回到顶部
|
//回到顶部
|
||||||
const backTopTarget = () => {
|
const backTopTarget = () => {
|
||||||
return document.getElementById('smartAdminMain');
|
return document.getElementById(LAYOUT_ELEMENT_IDS.main);
|
||||||
};
|
};
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
<template>
|
<template>
|
||||||
<a-layout class="admin-layout">
|
<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 />
|
<TopMenu />
|
||||||
</a-layout-header>
|
</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 />
|
<PageTag />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -18,14 +18,14 @@
|
|||||||
<!--keepAlive的iframe 每个页面一个iframe组件-->
|
<!--keepAlive的iframe 每个页面一个iframe组件-->
|
||||||
<IframeIndex
|
<IframeIndex
|
||||||
v-for="item in keepAliveIframePages"
|
v-for="item in keepAliveIframePages"
|
||||||
v-show="route.name == item.name"
|
v-show="route.name === item.name"
|
||||||
:key="item.name"
|
:key="item.name"
|
||||||
:name="item.name"
|
:name="item.name"
|
||||||
:url="item.meta.frameUrl"
|
:url="item.meta.frameUrl"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!--非iframe使用router-view-->
|
<!--非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 }">
|
<router-view v-slot="{ Component }">
|
||||||
<keep-alive :include="keepAliveIncludes">
|
<keep-alive :include="keepAliveIncludes">
|
||||||
<component :is="Component" :key="route.name" />
|
<component :is="Component" :key="route.name" />
|
||||||
@ -55,6 +55,7 @@
|
|||||||
import { useUserStore } from '/@/store/modules/system/user';
|
import { useUserStore } from '/@/store/modules/system/user';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import { HOME_PAGE_NAME } from '/@/constants/system/home-const';
|
import { HOME_PAGE_NAME } from '/@/constants/system/home-const';
|
||||||
|
import { LAYOUT_ELEMENT_IDS } from '/@/layout/layout-const.js';
|
||||||
|
|
||||||
const windowHeight = ref(window.innerHeight);
|
const windowHeight = ref(window.innerHeight);
|
||||||
//主题颜色
|
//主题颜色
|
||||||
@ -87,7 +88,7 @@
|
|||||||
//页面初始化的时候加载水印
|
//页面初始化的时候加载水印
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (watermarkFlag.value) {
|
if (watermarkFlag.value) {
|
||||||
watermark.set('smartAdminLayoutContent', useUserStore().actualName);
|
watermark.set(LAYOUT_ELEMENT_IDS.content, useUserStore().actualName);
|
||||||
} else {
|
} else {
|
||||||
watermark.clear();
|
watermark.clear();
|
||||||
}
|
}
|
||||||
@ -97,7 +98,7 @@
|
|||||||
() => watermarkFlag.value,
|
() => watermarkFlag.value,
|
||||||
(newValue) => {
|
(newValue) => {
|
||||||
if (newValue) {
|
if (newValue) {
|
||||||
watermark.set('smartAdminLayoutContent', useUserStore().actualName);
|
watermark.set(LAYOUT_ELEMENT_IDS.content, useUserStore().actualName);
|
||||||
} else {
|
} else {
|
||||||
watermark.clear();
|
watermark.clear();
|
||||||
}
|
}
|
||||||
@ -106,7 +107,7 @@
|
|||||||
|
|
||||||
//回到顶部
|
//回到顶部
|
||||||
const backTopTarget = () => {
|
const backTopTarget = () => {
|
||||||
return document.getElementById('smartAdminMain');
|
return document.getElementById(LAYOUT_ELEMENT_IDS.main);
|
||||||
};
|
};
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
@ -13,7 +13,9 @@ import localStorageKeyConst from '/@/constants/local-storage-key-const';
|
|||||||
import { smartSentry } from '/@/lib/smart-sentry';
|
import { smartSentry } from '/@/lib/smart-sentry';
|
||||||
import { localRead } from '/@/utils/local-util';
|
import { localRead } from '/@/utils/local-util';
|
||||||
|
|
||||||
let state = { ...appDefaultConfig };
|
let state = {
|
||||||
|
...appDefaultConfig
|
||||||
|
};
|
||||||
|
|
||||||
let appConfigStr = localRead(localStorageKeyConst.APP_CONFIG);
|
let appConfigStr = localRead(localStorageKeyConst.APP_CONFIG);
|
||||||
let language = appDefaultConfig.language;
|
let language = appDefaultConfig.language;
|
||||||
@ -38,6 +40,8 @@ export const useAppConfigStore = defineStore({
|
|||||||
state: () => ({
|
state: () => ({
|
||||||
// 读取config下的默认配置
|
// 读取config下的默认配置
|
||||||
...state,
|
...state,
|
||||||
|
// 全屏
|
||||||
|
fullScreenFlag: false,
|
||||||
}),
|
}),
|
||||||
actions: {
|
actions: {
|
||||||
reset() {
|
reset() {
|
||||||
@ -51,5 +55,11 @@ export const useAppConfigStore = defineStore({
|
|||||||
hideHelpDoc() {
|
hideHelpDoc() {
|
||||||
this.helpDocExpandFlag = false;
|
this.helpDocExpandFlag = false;
|
||||||
},
|
},
|
||||||
|
startFullScreen() {
|
||||||
|
this.fullScreenFlag = true;
|
||||||
|
},
|
||||||
|
exitFullScreen() {
|
||||||
|
this.fullScreenFlag = false;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -97,7 +97,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</a-row>
|
</a-row>
|
||||||
<!---------- 表格操作行 end ----------->
|
<!---------- 表格操作行 end ----------->
|
||||||
|
|
||||||
<a-table
|
<a-table
|
||||||
size="small"
|
size="small"
|
||||||
:dataSource="tableData"
|
:dataSource="tableData"
|
||||||
@ -105,7 +104,9 @@
|
|||||||
rowKey="goodsId"
|
rowKey="goodsId"
|
||||||
bordered
|
bordered
|
||||||
:pagination="false"
|
:pagination="false"
|
||||||
|
:showSorterTooltip="false"
|
||||||
:row-selection="{ selectedRowKeys: selectedRowKeyList, onChange: onSelectChange }"
|
:row-selection="{ selectedRowKeys: selectedRowKeyList, onChange: onSelectChange }"
|
||||||
|
@change="onChange"
|
||||||
>
|
>
|
||||||
<template #bodyCell="{ text, record, column }">
|
<template #bodyCell="{ text, record, column }">
|
||||||
<template v-if="column.dataIndex === 'place'">
|
<template v-if="column.dataIndex === 'place'">
|
||||||
@ -190,6 +191,7 @@
|
|||||||
import SmartEnumSelect from '/@/components/framework/smart-enum-select/index.vue';
|
import SmartEnumSelect from '/@/components/framework/smart-enum-select/index.vue';
|
||||||
import { FILE_FOLDER_TYPE_ENUM } from '/@/constants/support/file-const.js';
|
import { FILE_FOLDER_TYPE_ENUM } from '/@/constants/support/file-const.js';
|
||||||
import FileUpload from '/@/components/support/file-upload/index.vue';
|
import FileUpload from '/@/components/support/file-upload/index.vue';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
// ---------------------------- 表格列 ----------------------------
|
// ---------------------------- 表格列 ----------------------------
|
||||||
|
|
||||||
@ -205,6 +207,7 @@
|
|||||||
{
|
{
|
||||||
title: '商品状态',
|
title: '商品状态',
|
||||||
dataIndex: 'goodsStatus',
|
dataIndex: 'goodsStatus',
|
||||||
|
sorter: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '产地',
|
title: '产地',
|
||||||
@ -213,10 +216,12 @@
|
|||||||
{
|
{
|
||||||
title: '商品价格',
|
title: '商品价格',
|
||||||
dataIndex: 'price',
|
dataIndex: 'price',
|
||||||
|
sorter: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '上架状态',
|
title: '上架状态',
|
||||||
dataIndex: 'shelvesFlag',
|
dataIndex: 'shelvesFlag',
|
||||||
|
sorter: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '备注',
|
title: '备注',
|
||||||
@ -246,9 +251,10 @@
|
|||||||
goodsType: undefined,
|
goodsType: undefined,
|
||||||
pageNum: 1,
|
pageNum: 1,
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
|
sortItemList: []
|
||||||
};
|
};
|
||||||
// 查询表单form
|
// 查询表单form
|
||||||
const queryForm = reactive({ ...queryFormState });
|
const queryForm = reactive(_.cloneDeep(queryFormState));
|
||||||
// 表格加载loading
|
// 表格加载loading
|
||||||
const tableLoading = ref(false);
|
const tableLoading = ref(false);
|
||||||
// 表格数据
|
// 表格数据
|
||||||
@ -259,7 +265,7 @@
|
|||||||
// 重置查询条件
|
// 重置查询条件
|
||||||
function resetQuery() {
|
function resetQuery() {
|
||||||
let pageSize = queryForm.pageSize;
|
let pageSize = queryForm.pageSize;
|
||||||
Object.assign(queryForm, queryFormState);
|
Object.assign(queryForm, _.cloneDeep(queryFormState));
|
||||||
queryForm.pageSize = pageSize;
|
queryForm.pageSize = pageSize;
|
||||||
queryData();
|
queryData();
|
||||||
}
|
}
|
||||||
@ -414,4 +420,27 @@
|
|||||||
async function onExportGoods() {
|
async function onExportGoods() {
|
||||||
await goodsApi.exportGoods();
|
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>
|
</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)'
|
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)'
|
VITE_APP_PROJECT_TITLE = 'SmartAdmin 测试环境(Test)'
|
||||||
|
|
||||||
|
@ -48,8 +48,10 @@
|
|||||||
"vue3-tabs-chrome": "^0.3.3"
|
"vue3-tabs-chrome": "^0.3.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/crypto-js": "^4.2.2",
|
||||||
"@types/lodash": "^4.14.186",
|
"@types/lodash": "^4.14.186",
|
||||||
"@types/nprogress": "^0.2.0",
|
"@types/nprogress": "^0.2.0",
|
||||||
|
"@types/sm-crypto": "^0.3.4",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.40.1",
|
"@typescript-eslint/eslint-plugin": "^5.40.1",
|
||||||
"@typescript-eslint/parser": "^5.40.1",
|
"@typescript-eslint/parser": "^5.40.1",
|
||||||
"@vitejs/plugin-legacy": "5.4.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 胡克
|
* 下载文件流(根据fileKey) @author 胡克
|
||||||
*/
|
*/
|
||||||
downLoadFile: (fileKey) => {
|
downLoadFile: (fileName, fileKey) => {
|
||||||
return getDownload('/support/file/downLoad', { fileKey });
|
return getDownload(fileName, '/support/file/downLoad', { fileKey });
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -13,5 +13,5 @@ export const heartBeatApi = {
|
|||||||
// 分页查询 @author 卓大
|
// 分页查询 @author 卓大
|
||||||
queryList: (param) => {
|
queryList: (param) => {
|
||||||
return postRequest('/support/heartBeat/query', 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);
|
||||||
|
},
|
||||||
|
};
|
@ -50,6 +50,6 @@ export const departmentApi = {
|
|||||||
* @return {*}
|
* @return {*}
|
||||||
*/
|
*/
|
||||||
deleteDepartment: (departmentId) => {
|
deleteDepartment: (departmentId) => {
|
||||||
return getRequest(`/department/delete/${departmentId}`);
|
return getRequest(`/department/delete/${ departmentId }`);
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
|
@ -48,4 +48,18 @@ export const loginApi = {
|
|||||||
refresh: () => {
|
refresh: () => {
|
||||||
return getRequest('/login/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-tab-pane>
|
||||||
</a-tabs>
|
</a-tabs>
|
||||||
</div>
|
</div>
|
||||||
<template #rightExtra>
|
<template #overlay>
|
||||||
<a-menu>
|
<a-menu>
|
||||||
<a-menu-item @click="closeByMenu(false)">关闭其他</a-menu-item>
|
<a-menu-item @click="closeByMenu(false)">关闭其他</a-menu-item>
|
||||||
<a-menu-item @click="closeByMenu(true)">关闭所有</a-menu-item>
|
<a-menu-item @click="closeByMenu(true)">关闭所有</a-menu-item>
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
</a-tabs>
|
</a-tabs>
|
||||||
</div>
|
</div>
|
||||||
<template #rightExtra>
|
<template #overlay>
|
||||||
<a-menu>
|
<a-menu>
|
||||||
<a-menu-item @click="closeByMenu(false)">关闭其他</a-menu-item>
|
<a-menu-item @click="closeByMenu(false)">关闭其他</a-menu-item>
|
||||||
<a-menu-item @click="closeByMenu(true)">关闭所有</a-menu-item>
|
<a-menu-item @click="closeByMenu(true)">关闭所有</a-menu-item>
|
||||||
|
@ -1,32 +1,45 @@
|
|||||||
|
// @ts-ignore
|
||||||
import CryptoJS from 'crypto-js';
|
import CryptoJS from 'crypto-js';
|
||||||
|
// @ts-ignore
|
||||||
import CryptoSM from 'sm-crypto';
|
import CryptoSM from 'sm-crypto';
|
||||||
|
|
||||||
function object2string(data) {
|
function object2string(data: any) {
|
||||||
if (typeof data === 'Object') {
|
if (typeof data === 'object') {
|
||||||
return JSON.stringify(data);
|
return JSON.stringify(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
let str = JSON.stringify(data);
|
let str = JSON.stringify(data);
|
||||||
if (str.startsWith("'") || str.startsWith('"')) {
|
if (str.startsWith('\'') || str.startsWith('"')) {
|
||||||
str = str.substring(1);
|
str = str.substring(1);
|
||||||
}
|
}
|
||||||
if (str.endsWith("'") || str.endsWith('"')) {
|
if (str.endsWith('\'') || str.endsWith('"')) {
|
||||||
str = str.substring(0, str.length - 1);
|
str = str.substring(0, str.length - 1);
|
||||||
}
|
}
|
||||||
return str;
|
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) {
|
encryptData: function (data: any) {
|
||||||
// AES 加密 并转为 base64
|
// AES 加密 并转为 base64
|
||||||
let utf8Data = CryptoJS.enc.Utf8.parse(object2string(data));
|
let utf8Data = CryptoJS.enc.Utf8.parse(object2string(data));
|
||||||
const key = CryptoJS.enc.Utf8.parse(AES_KEY);
|
const key = CryptoJS.enc.Utf8.parse(AES_KEY);
|
||||||
const encrypted = CryptoJS.AES.encrypt(utf8Data, key, {
|
const encrypted = CryptoJS.AES.encrypt(utf8Data, key, {
|
||||||
mode: CryptoJS.mode.ECB,
|
mode: CryptoJS.mode.ECB,
|
||||||
padding: CryptoJS.pad.Pkcs7,
|
padding: CryptoJS.pad.Pkcs7
|
||||||
});
|
});
|
||||||
|
|
||||||
return encrypted.toString();
|
return encrypted.toString();
|
||||||
@ -38,20 +51,20 @@ const AES = {
|
|||||||
|
|
||||||
// 第二步:AES 解密
|
// 第二步:AES 解密
|
||||||
const key = CryptoJS.enc.Utf8.parse(AES_KEY);
|
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,
|
mode: CryptoJS.mode.ECB,
|
||||||
padding: CryptoJS.pad.Pkcs7,
|
padding: CryptoJS.pad.Pkcs7
|
||||||
}).toString(CryptoJS.enc.Utf8);
|
}).toString(CryptoJS.enc.Utf8);
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// ----------------------- 国密SM4算法 加密、解密 -----------------------
|
// ----------------------- 国密SM4算法 加密、解密 -----------------------
|
||||||
const SM4_KEY = '1024abcd1024abcd1024abcd1024abcd';
|
const SM4_KEY = '1024lab__1024lab';
|
||||||
|
|
||||||
const SM4 = {
|
const SM4 = {
|
||||||
encryptData: function (data: any) {
|
encryptData: function (data: any) {
|
||||||
// 第一步:SM4 加密
|
// 第一步:SM4 加密
|
||||||
let encryptData = CryptoSM.sm4.encrypt(object2string(data), SM4_KEY);
|
let encryptData = CryptoSM.sm4.encrypt(object2string(data), stringToHex(SM4_KEY));
|
||||||
// 第二步: Base64 编码
|
// 第二步: Base64 编码
|
||||||
return CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse(encryptData));
|
return CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse(encryptData));
|
||||||
},
|
},
|
||||||
@ -62,8 +75,8 @@ const SM4 = {
|
|||||||
let decode64Str = CryptoJS.enc.Utf8.stringify(words);
|
let decode64Str = CryptoJS.enc.Utf8.stringify(words);
|
||||||
|
|
||||||
// 第二步:SM4 解密
|
// 第二步: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 from 'nprogress';
|
||||||
import 'nprogress/nprogress.css';
|
import 'nprogress/nprogress.css';
|
||||||
import { nextTick } from 'vue';
|
import {nextTick} from 'vue';
|
||||||
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router';
|
import {createRouter, createWebHashHistory, RouteRecordRaw} from 'vue-router';
|
||||||
import { routerArray } from './routers';
|
import {routerArray} from './routers';
|
||||||
import { PAGE_PATH_404, PAGE_PATH_LOGIN } from '/@/constants/common-const';
|
import {PAGE_PATH_404, PAGE_PATH_LOGIN} from '/@/constants/common-const';
|
||||||
import { HOME_PAGE_NAME } from '/@/constants/system/home-const';
|
import {HOME_PAGE_NAME} from '/@/constants/system/home-const';
|
||||||
import SmartLayout from '/@/layout/smart-layout.vue';
|
import SmartLayout from '/@/layout/smart-layout.vue';
|
||||||
import { useUserStore } from '/@/store/modules/system/user';
|
import {useUserStore} from '/@/store/modules/system/user';
|
||||||
import { clearAllCoolies, getTokenFromCookie } from '/@/utils/cookie-util';
|
import {clearAllCoolies, getTokenFromCookie} from '/@/utils/cookie-util';
|
||||||
import { localClear } from '/@/utils/local-util';
|
import {localClear} from '/@/utils/local-util';
|
||||||
|
|
||||||
export const router = createRouter({
|
export const router = createRouter({
|
||||||
history: createWebHashHistory(),
|
history: createWebHashHistory(),
|
||||||
routes: routerArray,
|
routes: routerArray,
|
||||||
strict: true,
|
strict: true,
|
||||||
scrollBehavior: () => ({ left: 0, top: 0 }),
|
scrollBehavior: () => ({left: 0, top: 0})
|
||||||
});
|
});
|
||||||
|
|
||||||
// ----------------------- 路由加载前 -----------------------
|
// ----------------------- 路由加载前 -----------------------
|
||||||
@ -43,7 +43,7 @@ router.beforeEach(async (to, from, next) => {
|
|||||||
if (!token) {
|
if (!token) {
|
||||||
clearAllCoolies();
|
clearAllCoolies();
|
||||||
localClear();
|
localClear();
|
||||||
next({ path: PAGE_PATH_LOGIN });
|
next({path: PAGE_PATH_LOGIN});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,6 +79,7 @@ router.afterEach(() => {
|
|||||||
|
|
||||||
// ----------------------- 构建router对象 -----------------------
|
// ----------------------- 构建router对象 -----------------------
|
||||||
const routerMap = new Map();
|
const routerMap = new Map();
|
||||||
|
|
||||||
export function buildRoutes(menuRouterList) {
|
export function buildRoutes(menuRouterList) {
|
||||||
let menuList = menuRouterList ? menuRouterList : useUserStore().getMenuRouterList || [];
|
let menuList = menuRouterList ? menuRouterList : useUserStore().getMenuRouterList || [];
|
||||||
/**
|
/**
|
||||||
@ -119,8 +120,8 @@ export function buildRoutes(menuRouterList) {
|
|||||||
// 是否为外链
|
// 是否为外链
|
||||||
frameFlag: e.frameFlag,
|
frameFlag: e.frameFlag,
|
||||||
// 外链地址
|
// 外链地址
|
||||||
frameUrl: e.frameUrl,
|
frameUrl: e.frameUrl
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (e.frameFlag) {
|
if (e.frameFlag) {
|
||||||
@ -151,6 +152,6 @@ export function buildRoutes(menuRouterList) {
|
|||||||
path: '/',
|
path: '/',
|
||||||
meta: {},
|
meta: {},
|
||||||
component: SmartLayout,
|
component: SmartLayout,
|
||||||
children: resList,
|
children: resList
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -270,7 +270,7 @@ function view(file) {
|
|||||||
// 下载文件
|
// 下载文件
|
||||||
async function download(file) {
|
async function download(file) {
|
||||||
try {
|
try {
|
||||||
await fileApi.downLoadFile(file.fileKey);
|
await fileApi.downLoadFile(file.fileName, file.fileKey);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
smartSentry.captureError(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>
|
</div>
|
||||||
<div class="box-item login">
|
<div class="box-item login">
|
||||||
<img class="login-qr" :src="loginQR" />
|
<img class="login-qr" :src="loginQR"/>
|
||||||
<div class="login-title">账号登录</div>
|
<div class="login-title">账号登录</div>
|
||||||
<a-form ref="formRef" class="login-form" :model="loginForm" :rules="rules">
|
<a-form ref="formRef" class="login-form" :model="loginForm" :rules="rules">
|
||||||
<a-form-item name="loginName">
|
<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>
|
||||||
<a-form-item name="password">
|
<a-form-item name="password">
|
||||||
<a-input-password
|
<a-input-password
|
||||||
@ -32,8 +41,8 @@
|
|||||||
/>
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item name="captchaCode">
|
<a-form-item name="captchaCode">
|
||||||
<a-input class="captcha-input" v-model:value.trim="loginForm.captchaCode" placeholder="请输入验证码" />
|
<a-input class="captcha-input" v-model:value.trim="loginForm.captchaCode" placeholder="请输入验证码"/>
|
||||||
<img class="captcha-img" :src="captchaBase64Image" @click="getCaptcha" />
|
<img class="captcha-img" :src="captchaBase64Image" @click="getCaptcha"/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-checkbox v-model:checked="rememberPwd">记住密码</a-checkbox>
|
<a-checkbox v-model:checked="rememberPwd">记住密码</a-checkbox>
|
||||||
@ -50,82 +59,81 @@
|
|||||||
<p class="line"></p>
|
<p class="line"></p>
|
||||||
</div>
|
</div>
|
||||||
<div class="login-type">
|
<div class="login-type">
|
||||||
<img :src="wechatIcon" />
|
<img :src="wechatIcon"/>
|
||||||
<img :src="aliIcon" />
|
<img :src="aliIcon"/>
|
||||||
<img :src="douyinIcon" />
|
<img :src="douyinIcon"/>
|
||||||
<img :src="qqIcon" />
|
<img :src="qqIcon"/>
|
||||||
<img :src="weiboIcon" />
|
<img :src="weiboIcon"/>
|
||||||
<img :src="feishuIcon" />
|
<img :src="feishuIcon"/>
|
||||||
<img :src="googleIcon" />
|
<img :src="googleIcon"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import { message } from 'ant-design-vue';
|
import { message } from 'ant-design-vue';
|
||||||
import { onMounted, onUnmounted, reactive, ref } from 'vue';
|
import { onMounted, onUnmounted, reactive, ref } from 'vue';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import { loginApi } from '/@/api/system/login/login-api';
|
import { loginApi } from '/src/api/system/login/login-api';
|
||||||
import { SmartLoading } from '/@/components/framework/smart-loading';
|
import { SmartLoading } from '/@/components/framework/smart-loading';
|
||||||
import { LOGIN_DEVICE_ENUM } from '/@/constants/system/login-device-const';
|
import { LOGIN_DEVICE_ENUM } from '/@/constants/system/login-device-const';
|
||||||
import { useUserStore } from '/@/store/modules/system/user';
|
import { useUserStore } from '/@/store/modules/system/user';
|
||||||
import loginQR from '/@/assets/images/login/login-qr.png';
|
import loginQR from '/@/assets/images/login/login-qr.png';
|
||||||
import wechatIcon from '/@/assets/images/login/wechat-icon.png';
|
import wechatIcon from '/@/assets/images/login/wechat-icon.png';
|
||||||
import aliIcon from '/@/assets/images/login/ali-icon.png';
|
import aliIcon from '/@/assets/images/login/ali-icon.png';
|
||||||
import douyinIcon from '/@/assets/images/login/douyin-icon.png';
|
import douyinIcon from '/@/assets/images/login/douyin-icon.png';
|
||||||
import qqIcon from '/@/assets/images/login/qq-icon.png';
|
import qqIcon from '/@/assets/images/login/qq-icon.png';
|
||||||
import weiboIcon from '/@/assets/images/login/weibo-icon.png';
|
import weiboIcon from '/@/assets/images/login/weibo-icon.png';
|
||||||
import feishuIcon from '/@/assets/images/login/feishu-icon.png';
|
import feishuIcon from '/@/assets/images/login/feishu-icon.png';
|
||||||
import googleIcon from '/@/assets/images/login/google-icon.png';
|
import googleIcon from '/@/assets/images/login/google-icon.png';
|
||||||
|
|
||||||
import { buildRoutes } from '/@/router/index';
|
import { buildRoutes } from '/@/router/index';
|
||||||
import { smartSentry } from '/@/lib/smart-sentry';
|
import { smartSentry } from '/@/lib/smart-sentry';
|
||||||
import { encryptData } from '/@/lib/encrypt';
|
import { encryptData } from '/@/lib/encrypt';
|
||||||
import { localSave } from '/@/utils/local-util.ts';
|
import { saveTokenToCookie } from '/@/utils/cookie-util';
|
||||||
import LocalStorageKeyConst from '/@/constants/local-storage-key-const.ts';
|
|
||||||
import { saveTokenToCookie } from '/@/utils/cookie-util';
|
|
||||||
|
|
||||||
//--------------------- 登录表单 ---------------------------------
|
//--------------------- 登录表单 ---------------------------------
|
||||||
|
|
||||||
const loginForm = reactive({
|
const loginForm = reactive({
|
||||||
loginName: 'admin',
|
loginName: 'admin',
|
||||||
password: '',
|
password: '',
|
||||||
|
emailCode: '', // 邮箱验证码
|
||||||
captchaCode: '',
|
captchaCode: '',
|
||||||
captchaUuid: '',
|
captchaUuid: '',
|
||||||
loginDevice: LOGIN_DEVICE_ENUM.PC.value,
|
loginDevice: LOGIN_DEVICE_ENUM.PC.value
|
||||||
});
|
});
|
||||||
const rules = {
|
const rules = {
|
||||||
loginName: [{ required: true, message: '用户名不能为空' }],
|
loginName: [{required: true, message: '用户名不能为空'}],
|
||||||
password: [{ required: true, message: '密码不能为空' }],
|
password: [{required: true, message: '密码不能为空'}],
|
||||||
captchaCode: [{ required: true, message: '验证码不能为空' }],
|
captchaCode: [{required: true, message: '验证码不能为空'}]
|
||||||
};
|
};
|
||||||
|
|
||||||
const showPassword = ref(false);
|
const showPassword = ref(false);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const formRef = ref();
|
const formRef = ref();
|
||||||
const rememberPwd = ref(false);
|
const rememberPwd = ref(false);
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
document.onkeyup = (e) => {
|
document.onkeyup = (e) => {
|
||||||
if (e.keyCode == 13) {
|
if (e.keyCode == 13) {
|
||||||
onLogin();
|
onLogin();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
document.onkeyup = null;
|
document.onkeyup = null;
|
||||||
});
|
});
|
||||||
|
|
||||||
//登录
|
//登录
|
||||||
async function onLogin() {
|
async function onLogin() {
|
||||||
formRef.value.validate().then(async () => {
|
formRef.value.validate().then(async() => {
|
||||||
try {
|
try {
|
||||||
SmartLoading.show();
|
SmartLoading.show();
|
||||||
// 密码加密
|
// 密码加密
|
||||||
let encryptPasswordForm = Object.assign({}, loginForm, {
|
let encryptPasswordForm = Object.assign({}, loginForm, {
|
||||||
password: encryptData(loginForm.password),
|
password: encryptData(loginForm.password)
|
||||||
});
|
});
|
||||||
const res = await loginApi.login(encryptPasswordForm);
|
const res = await loginApi.login(encryptPasswordForm);
|
||||||
stopRefrestCaptchaInterval();
|
stopRefrestCaptchaInterval();
|
||||||
@ -147,12 +155,13 @@
|
|||||||
SmartLoading.hide();
|
SmartLoading.hide();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
//--------------------- 验证码 ---------------------------------
|
//--------------------- 验证码 ---------------------------------
|
||||||
|
|
||||||
const captchaBase64Image = ref('');
|
const captchaBase64Image = ref('');
|
||||||
async function getCaptcha() {
|
|
||||||
|
async function getCaptcha() {
|
||||||
try {
|
try {
|
||||||
let captchaResult = await loginApi.getCaptcha();
|
let captchaResult = await loginApi.getCaptcha();
|
||||||
captchaBase64Image.value = captchaResult.data.captchaBase64Image;
|
captchaBase64Image.value = captchaResult.data.captchaBase64Image;
|
||||||
@ -161,24 +170,77 @@
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e);
|
console.log(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let refrestCaptchaInterval = null;
|
let refrestCaptchaInterval = null;
|
||||||
function beginRefrestCaptchaInterval(expireSeconds) {
|
|
||||||
|
function beginRefrestCaptchaInterval(expireSeconds) {
|
||||||
if (refrestCaptchaInterval === null) {
|
if (refrestCaptchaInterval === null) {
|
||||||
refrestCaptchaInterval = setInterval(getCaptcha, (expireSeconds - 5) * 1000);
|
refrestCaptchaInterval = setInterval(getCaptcha, (expireSeconds - 5) * 1000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function stopRefrestCaptchaInterval() {
|
function stopRefrestCaptchaInterval() {
|
||||||
if (refrestCaptchaInterval != null) {
|
if (refrestCaptchaInterval != null) {
|
||||||
clearInterval(refrestCaptchaInterval);
|
clearInterval(refrestCaptchaInterval);
|
||||||
refrestCaptchaInterval = null;
|
refrestCaptchaInterval = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(getCaptcha);
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取双因子登录标识
|
||||||
|
async function getTwoFactorLoginFlag() {
|
||||||
|
try {
|
||||||
|
let result = await loginApi.getTwoFactorLoginFlag();
|
||||||
|
emailCodeShowFlag.value = result.data;
|
||||||
|
} catch (e) {
|
||||||
|
smartSentry.captureError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送邮箱验证码
|
||||||
|
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>
|
</script>
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
@import './login.less';
|
@import "./login.less";
|
||||||
</style>
|
</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