mirror of
https://gitee.com/lab1024/smart-admin.git
synced 2026-03-03 05:54:25 +08:00
Compare commits
7 Commits
efff2dd6d2
...
v3.30.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6867198bf6 | ||
|
|
2c661120ca | ||
|
|
a5adcf1cef | ||
|
|
fcbdb9afe0 | ||
|
|
885ad21184 | ||
|
|
36de38fda3 | ||
|
|
374cc92a79 |
@@ -4,7 +4,7 @@
|
||||
|
||||
**<font color="#DC143C">国内首个满足《网络安全-三级等保》、《数据安全》</font>** 功能要求,支持登录限制、接口国产加解密、数据脱敏等一系列安全要求。
|
||||
|
||||
**<font color="#DC143C">支持国产数据库:【达梦、金仓、南大通用、海量数据、神州通用、OceanBase、GaussDB 高斯、阿里PolarDB、GoldenDB】等,主流数据库:【Mysql、PostgreSQL、SqlServer】等</font>**
|
||||
**<font color="#DC143C">支持国产数据库:【达梦、金仓、南大通用、海量数据、神州通用、OceanBase、GaussDB 高斯、阿里PolarDB、GoldenDB】等,主流数据库:【Mysql、PostgreSQL、SqlServer、Oracle】等</font>**
|
||||
|
||||
**<font color="#DC143C">前端提供JavaScript和TypeScript双版本,后端提供Java8+SpringBoot2.X和Java17+SpringBoot3.X 双版本</font>**。
|
||||
|
||||
|
||||
@@ -36,7 +36,6 @@ public class DepartmentEntity {
|
||||
/**
|
||||
* 负责人员工 id
|
||||
*/
|
||||
@TableField(updateStrategy = FieldStrategy.NEVER)
|
||||
private Long managerId;
|
||||
|
||||
/**
|
||||
|
||||
@@ -274,12 +274,12 @@ public class EmployeeService {
|
||||
if (null == employeeEntity) {
|
||||
return ResponseDTO.error(UserErrorCode.DATA_NOT_EXIST);
|
||||
}
|
||||
|
||||
// 更新禁用状态
|
||||
employeeDao.updateDisableFlag(employeeId, !employeeEntity.getDisabledFlag());
|
||||
|
||||
if (employeeEntity.getDisabledFlag()) {
|
||||
// 强制退出登录
|
||||
StpUtil.logout(UserTypeEnum.ADMIN_EMPLOYEE.getValue() + StringConst.COLON + employeeId);
|
||||
}
|
||||
// 强制退出登录
|
||||
StpUtil.logout(UserTypeEnum.ADMIN_EMPLOYEE.getValue() + StringConst.COLON + employeeId);
|
||||
|
||||
return ResponseDTO.ok();
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
SELECT * FROM t_goods
|
||||
<where>
|
||||
<if test="query.searchWord != null and query.searchWord !=''">
|
||||
INSTR(goods_name,#{query.searchWord})
|
||||
AND INSTR(goods_name,#{query.searchWord})
|
||||
</if>
|
||||
<if test="query.place != null">
|
||||
AND INSTR(place,#{query.place})
|
||||
|
||||
@@ -23,8 +23,11 @@ import org.springframework.data.redis.serializer.RedisSerializationContext;
|
||||
public class CacheConfig {
|
||||
|
||||
private static final String REDIS_CACHE = "redis";
|
||||
|
||||
private static final String CAFFEINE_CACHE = "caffeine";
|
||||
|
||||
public static final String REDIS_CACHE_PREFIX = "cache";
|
||||
|
||||
|
||||
@Resource
|
||||
private RedisConnectionFactory redisConnectionFactory;
|
||||
@@ -45,7 +48,7 @@ public class CacheConfig {
|
||||
RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig()
|
||||
// 禁止缓存 null 值,避免缓存穿透
|
||||
.disableCachingNullValues()
|
||||
.computePrefixWith(name -> "cache:" + name + ":")
|
||||
.computePrefixWith(name -> REDIS_CACHE_PREFIX + name + ":")
|
||||
// 使用 FastJSON 序列化缓存值,支持复杂对象
|
||||
.serializeValuesWith(RedisSerializationContext.SerializationPair
|
||||
.fromSerializer(new GenericFastJsonRedisSerializer()));
|
||||
|
||||
@@ -15,6 +15,7 @@ import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
|
||||
import software.amazon.awssdk.regions.Region;
|
||||
import software.amazon.awssdk.services.s3.S3Client;
|
||||
import software.amazon.awssdk.services.s3.S3Configuration;
|
||||
import software.amazon.awssdk.services.s3.presigner.S3Presigner;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
@@ -31,57 +32,72 @@ import java.net.URI;
|
||||
@Configuration
|
||||
public class FileConfig implements WebMvcConfigurer {
|
||||
|
||||
private static final String HTTPS = "https://";
|
||||
|
||||
private static final String HTTP = "http://";
|
||||
|
||||
private static final String MODE_CLOUD = "cloud";
|
||||
|
||||
private static final String MODE_LOCAL = "local";
|
||||
|
||||
@Value("${file.storage.cloud.region}")
|
||||
private String region;
|
||||
|
||||
@Value("${file.storage.cloud.endpoint}")
|
||||
private String endpoint;
|
||||
|
||||
@Value("${file.storage.cloud.bucket-name}")
|
||||
private String bucketName;
|
||||
|
||||
@Value("${file.storage.cloud.access-key}")
|
||||
private String accessKey;
|
||||
|
||||
@Value("${file.storage.cloud.secret-key}")
|
||||
private String secretKey;
|
||||
|
||||
@Value("${file.storage.cloud.private-url-expire-seconds}")
|
||||
private Long privateUrlExpireSeconds;
|
||||
|
||||
@Value("${file.storage.cloud.url-prefix}")
|
||||
private String urlPrefix;
|
||||
|
||||
@Value("${file.storage.local.upload-path}")
|
||||
private String uploadPath;
|
||||
|
||||
@Value("${file.storage.mode}")
|
||||
private String mode;
|
||||
|
||||
@Value("${file.storage.cloud.region}")
|
||||
private String cloudRegion;
|
||||
|
||||
@Value("${file.storage.cloud.endpoint}")
|
||||
private String cloudEndpoint;
|
||||
|
||||
@Value("${file.storage.cloud.bucket-name}")
|
||||
private String cloudBucketName;
|
||||
|
||||
@Value("${file.storage.cloud.access-key}")
|
||||
private String cloudAccessKey;
|
||||
|
||||
@Value("${file.storage.cloud.secret-key}")
|
||||
private String cloudSecretKey;
|
||||
|
||||
@Value("${file.storage.cloud.private-url-expire-seconds}")
|
||||
private Long cloudPrivateUrlExpireSeconds;
|
||||
|
||||
@Value("${file.storage.cloud.public-url-prefix}")
|
||||
private String cloudPublicUrlPrefix;
|
||||
|
||||
@Value("${file.storage.local.upload-path}")
|
||||
private String localUploadPath;
|
||||
|
||||
|
||||
/**
|
||||
* 初始化 云oss client 配置
|
||||
*
|
||||
* @return
|
||||
* 初始化 s3 client 配置
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnProperty(prefix = "file.storage", name = {"mode"}, havingValue = MODE_CLOUD)
|
||||
public S3Client initAmazonS3() {
|
||||
public S3Client initS3Client() {
|
||||
return S3Client.builder()
|
||||
.region(Region.AWS_GLOBAL)
|
||||
.endpointOverride(URI.create((urlPrefix.startsWith(HTTPS) ? HTTPS : HTTP) + endpoint))
|
||||
.region(Region.of(cloudRegion))
|
||||
.endpointOverride(URI.create(cloudEndpoint))
|
||||
.credentialsProvider(
|
||||
StaticCredentialsProvider.create(
|
||||
AwsBasicCredentials.create(accessKey, secretKey)))
|
||||
AwsBasicCredentials.create(cloudAccessKey, cloudSecretKey)))
|
||||
.serviceConfiguration(S3Configuration.builder()
|
||||
.pathStyleAccessEnabled(true)
|
||||
.pathStyleAccessEnabled(false)
|
||||
.chunkedEncodingEnabled(false)
|
||||
.build())
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化 s3 预签名
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnProperty(prefix = "file.storage", name = {"mode"}, havingValue = MODE_CLOUD)
|
||||
public S3Presigner initS3Presigner() {
|
||||
return S3Presigner
|
||||
.builder()
|
||||
.region(Region.of(cloudRegion))
|
||||
.endpointOverride(URI.create(cloudEndpoint))
|
||||
.credentialsProvider(
|
||||
StaticCredentialsProvider.create(
|
||||
AwsBasicCredentials.create(cloudAccessKey, cloudSecretKey)))
|
||||
.serviceConfiguration(S3Configuration.builder()
|
||||
.pathStyleAccessEnabled(false)
|
||||
.chunkedEncodingEnabled(false)
|
||||
.build())
|
||||
.build();
|
||||
@@ -102,7 +118,7 @@ public class FileConfig implements WebMvcConfigurer {
|
||||
@Override
|
||||
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
||||
if (MODE_LOCAL.equals(mode)) {
|
||||
String path = uploadPath.endsWith("/") ? uploadPath : uploadPath + "/";
|
||||
String path = localUploadPath.endsWith("/") ? localUploadPath : localUploadPath + "/";
|
||||
registry.addResourceHandler(FileStorageLocalServiceImpl.UPLOAD_MAPPING + "/**").addResourceLocations("file:" + path);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package net.lab1024.sa.base.module.support.cache;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.google.common.collect.Lists;
|
||||
import jakarta.annotation.Resource;
|
||||
import net.lab1024.sa.base.config.CacheConfig;
|
||||
import net.lab1024.sa.base.constant.ReloadConst;
|
||||
import net.lab1024.sa.base.module.support.reload.core.annoation.SmartReload;
|
||||
import org.springframework.data.redis.cache.RedisCache;
|
||||
@@ -49,7 +50,7 @@ public class RedisCacheServiceImpl implements CacheService {
|
||||
// 获取 Redis 连接
|
||||
RedisConnection connection = redisConnectionFactory.getConnection();
|
||||
// 根据指定的 key 模式获取所有匹配的键
|
||||
Set<byte[]> keys = connection.keyCommands().keys((cacheName + ":*").getBytes());
|
||||
Set<byte[]> keys = connection.keyCommands().keys((CacheConfig.REDIS_CACHE_PREFIX + ":" + cacheName + "*").getBytes());
|
||||
|
||||
if (keys != null) {
|
||||
return keys.stream().map(key -> {
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package net.lab1024.sa.base.module.support.dict.constant;
|
||||
|
||||
import lombok.Getter;
|
||||
import net.lab1024.sa.base.common.enumeration.BaseEnum;
|
||||
|
||||
/**
|
||||
* 字典回显样式 枚举
|
||||
*/
|
||||
@Getter
|
||||
public enum DictDataStyleEnum implements BaseEnum {
|
||||
|
||||
DEFAULT("默认", "default"),
|
||||
PRIMARY("主要", "primary"),
|
||||
SUCCESS("成功", "success"),
|
||||
INFO("信息", "info"),
|
||||
WARNING("警告", "warning"),
|
||||
DANGER("危险", "danger");
|
||||
|
||||
private final String value;
|
||||
|
||||
private final String desc;
|
||||
|
||||
DictDataStyleEnum(String desc, String value) {
|
||||
this.desc = desc;
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
@@ -40,6 +40,11 @@ public class DictDataEntity {
|
||||
*/
|
||||
private String dataLabel;
|
||||
|
||||
/**
|
||||
* 字典项样式
|
||||
*/
|
||||
private String dataStyle;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
|
||||
@@ -4,6 +4,10 @@ import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
import net.lab1024.sa.base.common.swagger.SchemaEnum;
|
||||
import net.lab1024.sa.base.common.validator.enumeration.CheckEnum;
|
||||
import net.lab1024.sa.base.module.support.dict.constant.DictDataStyleEnum;
|
||||
|
||||
|
||||
/**
|
||||
* 字典数据表 新建表单
|
||||
@@ -28,6 +32,10 @@ public class DictDataAddForm {
|
||||
@NotBlank(message = "字典项显示名称 不能为空")
|
||||
private String dataLabel;
|
||||
|
||||
@SchemaEnum(value = DictDataStyleEnum.class, desc = "数据样式")
|
||||
@CheckEnum(message = "样式参数错误", value = DictDataStyleEnum.class)
|
||||
private String dataStyle;
|
||||
|
||||
@Schema(description = "备注")
|
||||
private String remark;
|
||||
|
||||
|
||||
@@ -38,6 +38,9 @@ public class DictDataVO implements Serializable {
|
||||
@Schema(description = "字典项显示名称")
|
||||
private String dataLabel;
|
||||
|
||||
@Schema(description = "字典项回显")
|
||||
private String dataStyle;
|
||||
|
||||
@Schema(description = "备注")
|
||||
private String remark;
|
||||
|
||||
|
||||
@@ -20,22 +20,22 @@ public enum FileFolderTypeEnum implements BaseEnum {
|
||||
/**
|
||||
* 通用
|
||||
*/
|
||||
COMMON(1, FileFolderTypeEnum.FOLDER_PUBLIC + "/common/", "通用"),
|
||||
COMMON(1, FileFolderTypeEnum.FOLDER_PRIVATE + "/common/", "通用"),
|
||||
|
||||
/**
|
||||
* 公告
|
||||
*/
|
||||
NOTICE(2, FileFolderTypeEnum.FOLDER_PUBLIC + "/notice/", "公告"),
|
||||
NOTICE(2, FileFolderTypeEnum.FOLDER_PRIVATE + "/notice/", "公告"),
|
||||
|
||||
/**
|
||||
* 帮助中心
|
||||
*/
|
||||
HELP_DOC(3, FileFolderTypeEnum.FOLDER_PUBLIC + "/help-doc/", "帮助中心"),
|
||||
HELP_DOC(3, FileFolderTypeEnum.FOLDER_PRIVATE + "/help-doc/", "帮助中心"),
|
||||
|
||||
/**
|
||||
* 意见反馈
|
||||
*/
|
||||
FEEDBACK(4, FileFolderTypeEnum.FOLDER_PUBLIC + "/feedback/", "意见反馈"),
|
||||
FEEDBACK(4, FileFolderTypeEnum.FOLDER_PRIVATE + "/feedback/", "意见反馈"),
|
||||
|
||||
;
|
||||
|
||||
|
||||
@@ -71,6 +71,9 @@ public class FileStorageCloudServiceImpl implements IFileStorageService {
|
||||
@Resource
|
||||
private S3Client s3Client;
|
||||
|
||||
@Resource
|
||||
private S3Presigner s3Presigner;
|
||||
|
||||
@Resource
|
||||
private FileConfig cloudConfig;
|
||||
|
||||
@@ -104,7 +107,7 @@ public class FileStorageCloudServiceImpl implements IFileStorageService {
|
||||
// 根据文件路径获取并设置访问权限
|
||||
ObjectCannedACL acl = this.getACL(path);
|
||||
PutObjectRequest putObjectRequest = PutObjectRequest.builder()
|
||||
.bucket(cloudConfig.getBucketName())
|
||||
.bucket(cloudConfig.getCloudBucketName())
|
||||
.key(fileKey)
|
||||
.metadata(userMetadata)
|
||||
.contentLength(file.getSize())
|
||||
@@ -128,7 +131,7 @@ public class FileStorageCloudServiceImpl implements IFileStorageService {
|
||||
uploadVO.setFileName(originalFileName);
|
||||
uploadVO.setFileType(fileType);
|
||||
// 根据 访问权限 返回不同的 URL
|
||||
String url = cloudConfig.getUrlPrefix() + fileKey;
|
||||
String url = cloudConfig.getCloudPublicUrlPrefix() + fileKey;
|
||||
if (ObjectCannedACL.PRIVATE.equals(acl)) {
|
||||
// 获取临时访问的URL
|
||||
url = this.getFileUrl(fileKey).getData();
|
||||
@@ -153,11 +156,10 @@ public class FileStorageCloudServiceImpl implements IFileStorageService {
|
||||
|
||||
if (!fileKey.startsWith(FileFolderTypeEnum.FOLDER_PRIVATE)) {
|
||||
// 不是私有的 都公共读
|
||||
return ResponseDTO.ok(cloudConfig.getUrlPrefix() + fileKey);
|
||||
return ResponseDTO.ok(cloudConfig.getCloudPublicUrlPrefix() + fileKey);
|
||||
}
|
||||
|
||||
// 如果是私有的,则规定时间内可以访问,超过规定时间,则连接失效
|
||||
|
||||
String fileRedisKey = RedisKeyConst.Support.FILE_PRIVATE_VO + fileKey;
|
||||
FileVO fileVO = redisService.getObject(fileRedisKey, FileVO.class);
|
||||
if (fileVO == null) {
|
||||
@@ -165,15 +167,22 @@ public class FileStorageCloudServiceImpl implements IFileStorageService {
|
||||
if (fileVO == null) {
|
||||
return ResponseDTO.userErrorParam("文件不存在");
|
||||
}
|
||||
GetObjectRequest getUrlRequest = GetObjectRequest.builder().bucket(cloudConfig.getBucketName()).key(fileKey).build();
|
||||
GetObjectPresignRequest getObjectPresignRequest = GetObjectPresignRequest.builder().signatureDuration(Duration.ofSeconds(cloudConfig.getPrivateUrlExpireSeconds())).getObjectRequest(getUrlRequest).build();
|
||||
GetObjectRequest getUrlRequest = GetObjectRequest
|
||||
.builder()
|
||||
.bucket(cloudConfig.getCloudBucketName())
|
||||
.key(fileKey)
|
||||
.build();
|
||||
|
||||
S3Presigner presigner = S3Presigner.builder().region(Region.of(cloudConfig.getRegion())).build();
|
||||
GetObjectPresignRequest getObjectPresignRequest = GetObjectPresignRequest
|
||||
.builder()
|
||||
.signatureDuration(Duration.ofSeconds(cloudConfig.getCloudPrivateUrlExpireSeconds()))
|
||||
.getObjectRequest(getUrlRequest)
|
||||
.build();
|
||||
|
||||
PresignedGetObjectRequest presignedGetObjectRequest = presigner.presignGetObject(getObjectPresignRequest);
|
||||
PresignedGetObjectRequest presignedGetObjectRequest = s3Presigner.presignGetObject(getObjectPresignRequest);
|
||||
String url = presignedGetObjectRequest.url().toString();
|
||||
fileVO.setFileUrl(url);
|
||||
redisService.set(fileRedisKey, fileVO, cloudConfig.getPrivateUrlExpireSeconds() - 5);
|
||||
redisService.set(fileRedisKey, fileVO, cloudConfig.getCloudPrivateUrlExpireSeconds() - 5);
|
||||
}
|
||||
|
||||
return ResponseDTO.ok(fileVO.getFileUrl());
|
||||
@@ -187,7 +196,7 @@ public class FileStorageCloudServiceImpl implements IFileStorageService {
|
||||
public ResponseDTO<FileDownloadVO> download(String key) {
|
||||
|
||||
// 获取文件 meta
|
||||
HeadObjectRequest objectRequest = HeadObjectRequest.builder().bucket(this.cloudConfig.getBucketName()).key(key).build();
|
||||
HeadObjectRequest objectRequest = HeadObjectRequest.builder().bucket(this.cloudConfig.getCloudBucketName()).key(key).build();
|
||||
HeadObjectResponse headObjectResponse = s3Client.headObject(objectRequest);
|
||||
Map<String, String> userMetadata = headObjectResponse.metadata();
|
||||
FileMetadataVO metadataDTO = null;
|
||||
@@ -201,7 +210,7 @@ public class FileStorageCloudServiceImpl implements IFileStorageService {
|
||||
}
|
||||
|
||||
//获取oss对象
|
||||
GetObjectRequest getObjectRequest = GetObjectRequest.builder().bucket(cloudConfig.getBucketName()).key(key).build();
|
||||
GetObjectRequest getObjectRequest = GetObjectRequest.builder().bucket(cloudConfig.getCloudBucketName()).key(key).build();
|
||||
ResponseBytes<GetObjectResponse> s3ClientObject = s3Client.getObject(getObjectRequest, ResponseTransformer.toBytes());
|
||||
|
||||
// 输入流转换为字节流
|
||||
@@ -236,7 +245,7 @@ public class FileStorageCloudServiceImpl implements IFileStorageService {
|
||||
*/
|
||||
@Override
|
||||
public ResponseDTO<String> delete(String fileKey) {
|
||||
DeleteObjectRequest deleteObjectRequest = DeleteObjectRequest.builder().bucket(cloudConfig.getBucketName()).key(fileKey).build();
|
||||
DeleteObjectRequest deleteObjectRequest = DeleteObjectRequest.builder().bucket(cloudConfig.getCloudBucketName()).key(fileKey).build();
|
||||
s3Client.deleteObject(deleteObjectRequest);
|
||||
return ResponseDTO.ok();
|
||||
}
|
||||
|
||||
@@ -78,8 +78,8 @@ public class FileStorageLocalServiceImpl implements IFileStorageService {
|
||||
// 目录不存在,新建
|
||||
directory.mkdirs();
|
||||
}
|
||||
if (!path.endsWith("/")) {
|
||||
path = path + "/";
|
||||
if (!path.endsWith(File.separator)) {
|
||||
path = path + File.separator;
|
||||
}
|
||||
FileUploadVO fileUploadVO = new FileUploadVO();
|
||||
//原文件名
|
||||
|
||||
@@ -3,8 +3,10 @@ package net.lab1024.sa.base.module.support.loginlog.domain;
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@@ -20,6 +22,8 @@ import java.time.LocalDateTime;
|
||||
@TableName("t_login_log")
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class LoginLogEntity {
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
|
||||
@@ -3,8 +3,10 @@ package net.lab1024.sa.base.module.support.securityprotect.domain;
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@@ -21,6 +23,8 @@ import java.time.LocalDateTime;
|
||||
@Data
|
||||
@Builder
|
||||
@TableName("t_login_fail")
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class LoginFailEntity {
|
||||
|
||||
|
||||
|
||||
@@ -103,12 +103,13 @@ file:
|
||||
url-prefix:
|
||||
cloud:
|
||||
region: oss-cn-hangzhou
|
||||
endpoint: oss-cn-hangzhou.aliyuncs.com
|
||||
endpoint: https://oss-cn-hangzhou.aliyuncs.com
|
||||
bucket-name: 1024lab-smart-admin
|
||||
access-key:
|
||||
secret-key:
|
||||
url-prefix: https://${file.storage.cloud.bucket-name}.${file.storage.cloud.endpoint}/
|
||||
private-url-expire-seconds: 3600
|
||||
# 云计算厂商支持公开的文件访问模式;minio默认是不支持的,对于minio用户可以配置为空
|
||||
public-url-prefix: https://1024lab-smart-admin.oss-cn-hangzhou.aliyuncs.com/
|
||||
|
||||
# open api配置
|
||||
springdoc:
|
||||
|
||||
@@ -103,12 +103,13 @@ file:
|
||||
url-prefix:
|
||||
cloud:
|
||||
region: oss-cn-hangzhou
|
||||
endpoint: oss-cn-hangzhou.aliyuncs.com
|
||||
endpoint: https://oss-cn-hangzhou.aliyuncs.com
|
||||
bucket-name: 1024lab-smart-admin
|
||||
access-key:
|
||||
secret-key:
|
||||
url-prefix: https://${file.storage.cloud.bucket-name}.${file.storage.cloud.endpoint}/
|
||||
private-url-expire-seconds: 3600
|
||||
# 云计算厂商支持公开的文件访问模式;minio默认是不支持的,对于minio用户可以配置为空
|
||||
public-url-prefix: https://1024lab-smart-admin.oss-cn-hangzhou.aliyuncs.com/
|
||||
|
||||
# open api配置
|
||||
springdoc:
|
||||
|
||||
@@ -103,12 +103,13 @@ file:
|
||||
url-prefix:
|
||||
cloud:
|
||||
region: oss-cn-hangzhou
|
||||
endpoint: oss-cn-hangzhou.aliyuncs.com
|
||||
endpoint: https://oss-cn-hangzhou.aliyuncs.com
|
||||
bucket-name: 1024lab-smart-admin
|
||||
access-key:
|
||||
secret-key:
|
||||
url-prefix: https://${file.storage.cloud.bucket-name}.${file.storage.cloud.endpoint}/
|
||||
private-url-expire-seconds: 3600
|
||||
# 云计算厂商支持公开的文件访问模式;minio默认是不支持的,对于minio用户可以配置为空
|
||||
public-url-prefix: https://1024lab-smart-admin.oss-cn-hangzhou.aliyuncs.com/
|
||||
|
||||
# open api配置
|
||||
springdoc:
|
||||
|
||||
@@ -103,12 +103,13 @@ file:
|
||||
url-prefix:
|
||||
cloud:
|
||||
region: oss-cn-hangzhou
|
||||
endpoint: oss-cn-hangzhou.aliyuncs.com
|
||||
endpoint: https://oss-cn-hangzhou.aliyuncs.com
|
||||
bucket-name: 1024lab-smart-admin
|
||||
access-key:
|
||||
secret-key:
|
||||
url-prefix: https://${file.storage.cloud.bucket-name}.${file.storage.cloud.endpoint}/
|
||||
private-url-expire-seconds: 3600
|
||||
# 云计算厂商支持公开的文件访问模式;minio默认是不支持的,对于minio用户可以配置为空
|
||||
public-url-prefix: https://1024lab-smart-admin.oss-cn-hangzhou.aliyuncs.com/
|
||||
|
||||
# open api配置
|
||||
springdoc:
|
||||
|
||||
@@ -36,7 +36,6 @@ public class DepartmentEntity {
|
||||
/**
|
||||
* 负责人员工 id
|
||||
*/
|
||||
@TableField(updateStrategy = FieldStrategy.NEVER)
|
||||
private Long managerId;
|
||||
|
||||
/**
|
||||
|
||||
@@ -275,12 +275,12 @@ public class EmployeeService {
|
||||
if (null == employeeEntity) {
|
||||
return ResponseDTO.error(UserErrorCode.DATA_NOT_EXIST);
|
||||
}
|
||||
|
||||
// 更新禁用状态
|
||||
employeeDao.updateDisableFlag(employeeId, !employeeEntity.getDisabledFlag());
|
||||
|
||||
if (employeeEntity.getDisabledFlag()) {
|
||||
// 强制退出登录
|
||||
StpUtil.logout(UserTypeEnum.ADMIN_EMPLOYEE.getValue() + StringConst.COLON + employeeId);
|
||||
}
|
||||
// 强制退出登录
|
||||
StpUtil.logout(UserTypeEnum.ADMIN_EMPLOYEE.getValue() + StringConst.COLON + employeeId);
|
||||
|
||||
return ResponseDTO.ok();
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
SELECT * FROM t_goods
|
||||
<where>
|
||||
<if test="query.searchWord != null and query.searchWord !=''">
|
||||
INSTR(goods_name,#{query.searchWord})
|
||||
AND INSTR(goods_name,#{query.searchWord})
|
||||
</if>
|
||||
<if test="query.place != null">
|
||||
AND INSTR(place,#{query.place})
|
||||
|
||||
@@ -24,8 +24,11 @@ import javax.annotation.Resource;
|
||||
public class CacheConfig {
|
||||
|
||||
private static final String REDIS_CACHE = "redis";
|
||||
|
||||
private static final String CAFFEINE_CACHE = "caffeine";
|
||||
|
||||
public static final String REDIS_CACHE_PREFIX = "cache";
|
||||
|
||||
|
||||
@Resource
|
||||
private RedisConnectionFactory redisConnectionFactory;
|
||||
@@ -46,7 +49,7 @@ public class CacheConfig {
|
||||
RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig()
|
||||
// 禁止缓存 null 值,避免缓存穿透
|
||||
.disableCachingNullValues()
|
||||
.computePrefixWith(name -> "cache:" + name + ":")
|
||||
.computePrefixWith(name -> REDIS_CACHE_PREFIX + name + ":")
|
||||
// 使用 FastJSON 序列化缓存值,支持复杂对象
|
||||
.serializeValuesWith(RedisSerializationContext.SerializationPair
|
||||
.fromSerializer(new GenericFastJsonRedisSerializer()));
|
||||
@@ -56,16 +59,16 @@ public class CacheConfig {
|
||||
}
|
||||
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(prefix = "spring.cache", name = {"type"}, havingValue = REDIS_CACHE)
|
||||
public CacheService redisCacheService() {
|
||||
return new RedisCacheServiceImpl();
|
||||
}
|
||||
@Bean
|
||||
@ConditionalOnProperty(prefix = "spring.cache", name = {"type"}, havingValue = REDIS_CACHE)
|
||||
public CacheService redisCacheService() {
|
||||
return new RedisCacheServiceImpl();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(prefix = "spring.cache", name = {"type"}, havingValue = CAFFEINE_CACHE)
|
||||
public CacheService caffeineCacheService() {
|
||||
return new CaffeineCacheServiceImpl();
|
||||
}
|
||||
@Bean
|
||||
@ConditionalOnProperty(prefix = "spring.cache", name = {"type"}, havingValue = CAFFEINE_CACHE)
|
||||
public CacheService caffeineCacheService() {
|
||||
return new CaffeineCacheServiceImpl();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
|
||||
import software.amazon.awssdk.regions.Region;
|
||||
import software.amazon.awssdk.services.s3.S3Client;
|
||||
import software.amazon.awssdk.services.s3.S3Configuration;
|
||||
import software.amazon.awssdk.services.s3.presigner.S3Presigner;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
@@ -31,57 +32,72 @@ import java.net.URI;
|
||||
@Configuration
|
||||
public class FileConfig implements WebMvcConfigurer {
|
||||
|
||||
private static final String HTTPS = "https://";
|
||||
|
||||
private static final String HTTP = "http://";
|
||||
|
||||
private static final String MODE_CLOUD = "cloud";
|
||||
|
||||
private static final String MODE_LOCAL = "local";
|
||||
|
||||
@Value("${file.storage.cloud.region}")
|
||||
private String region;
|
||||
|
||||
@Value("${file.storage.cloud.endpoint}")
|
||||
private String endpoint;
|
||||
|
||||
@Value("${file.storage.cloud.bucket-name}")
|
||||
private String bucketName;
|
||||
|
||||
@Value("${file.storage.cloud.access-key}")
|
||||
private String accessKey;
|
||||
|
||||
@Value("${file.storage.cloud.secret-key}")
|
||||
private String secretKey;
|
||||
|
||||
@Value("${file.storage.cloud.private-url-expire-seconds}")
|
||||
private Long privateUrlExpireSeconds;
|
||||
|
||||
@Value("${file.storage.cloud.url-prefix}")
|
||||
private String urlPrefix;
|
||||
|
||||
@Value("${file.storage.local.upload-path}")
|
||||
private String uploadPath;
|
||||
|
||||
@Value("${file.storage.mode}")
|
||||
private String mode;
|
||||
|
||||
@Value("${file.storage.cloud.region}")
|
||||
private String cloudRegion;
|
||||
|
||||
@Value("${file.storage.cloud.endpoint}")
|
||||
private String cloudEndpoint;
|
||||
|
||||
@Value("${file.storage.cloud.bucket-name}")
|
||||
private String cloudBucketName;
|
||||
|
||||
@Value("${file.storage.cloud.access-key}")
|
||||
private String cloudAccessKey;
|
||||
|
||||
@Value("${file.storage.cloud.secret-key}")
|
||||
private String cloudSecretKey;
|
||||
|
||||
@Value("${file.storage.cloud.private-url-expire-seconds}")
|
||||
private Long cloudPrivateUrlExpireSeconds;
|
||||
|
||||
@Value("${file.storage.cloud.public-url-prefix}")
|
||||
private String cloudPublicUrlPrefix;
|
||||
|
||||
@Value("${file.storage.local.upload-path}")
|
||||
private String localUploadPath;
|
||||
|
||||
|
||||
/**
|
||||
* 初始化 云oss client 配置
|
||||
*
|
||||
* @return
|
||||
* 初始化 s3 client 配置
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnProperty(prefix = "file.storage", name = {"mode"}, havingValue = MODE_CLOUD)
|
||||
public S3Client initAmazonS3() {
|
||||
public S3Client initS3Client() {
|
||||
return S3Client.builder()
|
||||
.region(Region.AWS_GLOBAL)
|
||||
.endpointOverride(URI.create((urlPrefix.startsWith(HTTPS) ? HTTPS : HTTP) + endpoint))
|
||||
.region(Region.of(cloudRegion))
|
||||
.endpointOverride(URI.create(cloudEndpoint))
|
||||
.credentialsProvider(
|
||||
StaticCredentialsProvider.create(
|
||||
AwsBasicCredentials.create(accessKey, secretKey)))
|
||||
AwsBasicCredentials.create(cloudAccessKey, cloudSecretKey)))
|
||||
.serviceConfiguration(S3Configuration.builder()
|
||||
.pathStyleAccessEnabled(true)
|
||||
.pathStyleAccessEnabled(false)
|
||||
.chunkedEncodingEnabled(false)
|
||||
.build())
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化 s3 预签名
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnProperty(prefix = "file.storage", name = {"mode"}, havingValue = MODE_CLOUD)
|
||||
public S3Presigner initS3Presigner() {
|
||||
return S3Presigner
|
||||
.builder()
|
||||
.region(Region.of(cloudRegion))
|
||||
.endpointOverride(URI.create(cloudEndpoint))
|
||||
.credentialsProvider(
|
||||
StaticCredentialsProvider.create(
|
||||
AwsBasicCredentials.create(cloudAccessKey, cloudSecretKey)))
|
||||
.serviceConfiguration(S3Configuration.builder()
|
||||
.pathStyleAccessEnabled(false)
|
||||
.chunkedEncodingEnabled(false)
|
||||
.build())
|
||||
.build();
|
||||
@@ -102,7 +118,7 @@ public class FileConfig implements WebMvcConfigurer {
|
||||
@Override
|
||||
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
||||
if (MODE_LOCAL.equals(mode)) {
|
||||
String path = uploadPath.endsWith("/") ? uploadPath : uploadPath + "/";
|
||||
String path = localUploadPath.endsWith("/") ? localUploadPath : localUploadPath + "/";
|
||||
registry.addResourceHandler(FileStorageLocalServiceImpl.UPLOAD_MAPPING + "/**").addResourceLocations("file:" + path);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package net.lab1024.sa.base.module.support.cache;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.google.common.collect.Lists;
|
||||
import net.lab1024.sa.base.config.CacheConfig;
|
||||
import net.lab1024.sa.base.constant.ReloadConst;
|
||||
import net.lab1024.sa.base.module.support.reload.core.annoation.SmartReload;
|
||||
import org.springframework.data.redis.cache.RedisCache;
|
||||
@@ -49,7 +50,7 @@ public class RedisCacheServiceImpl implements CacheService {
|
||||
// 获取 Redis 连接
|
||||
RedisConnection connection = redisConnectionFactory.getConnection();
|
||||
// 根据指定的 key 模式获取所有匹配的键
|
||||
Set<byte[]> keys = connection.keyCommands().keys((cacheName + ":*").getBytes());
|
||||
Set<byte[]> keys = connection.keyCommands().keys((CacheConfig.REDIS_CACHE_PREFIX + ":" + cacheName + "*").getBytes());
|
||||
|
||||
if (keys != null) {
|
||||
return keys.stream().map(key -> {
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package net.lab1024.sa.base.module.support.dict.constant;
|
||||
|
||||
import lombok.Getter;
|
||||
import net.lab1024.sa.base.common.enumeration.BaseEnum;
|
||||
|
||||
/**
|
||||
* 字典回显样式 枚举
|
||||
*/
|
||||
@Getter
|
||||
public enum DictDataStyleEnum implements BaseEnum {
|
||||
|
||||
DEFAULT("默认", "default"),
|
||||
PRIMARY("主要", "primary"),
|
||||
SUCCESS("成功", "success"),
|
||||
INFO("信息", "info"),
|
||||
WARNING("警告", "warning"),
|
||||
DANGER("危险", "danger");
|
||||
|
||||
private final String value;
|
||||
|
||||
private final String desc;
|
||||
|
||||
DictDataStyleEnum(String desc, String value) {
|
||||
this.desc = desc;
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
@@ -40,6 +40,11 @@ public class DictDataEntity {
|
||||
*/
|
||||
private String dataLabel;
|
||||
|
||||
/**
|
||||
* 字典项样式
|
||||
*/
|
||||
private String dataStyle;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
|
||||
@@ -2,6 +2,9 @@ package net.lab1024.sa.base.module.support.dict.domain.form;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import net.lab1024.sa.base.common.swagger.SchemaEnum;
|
||||
import net.lab1024.sa.base.common.validator.enumeration.CheckEnum;
|
||||
import net.lab1024.sa.base.module.support.dict.constant.DictDataStyleEnum;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
@@ -29,6 +32,10 @@ public class DictDataAddForm {
|
||||
@NotBlank(message = "字典项显示名称 不能为空")
|
||||
private String dataLabel;
|
||||
|
||||
@SchemaEnum(value = DictDataStyleEnum.class, desc = "数据样式")
|
||||
@CheckEnum(message = "样式参数错误", value = DictDataStyleEnum.class)
|
||||
private String dataStyle;
|
||||
|
||||
@Schema(description = "备注")
|
||||
private String remark;
|
||||
|
||||
|
||||
@@ -38,6 +38,9 @@ public class DictDataVO implements Serializable {
|
||||
@Schema(description = "字典项显示名称")
|
||||
private String dataLabel;
|
||||
|
||||
@Schema(description = "字典项回显")
|
||||
private String dataStyle;
|
||||
|
||||
@Schema(description = "备注")
|
||||
private String remark;
|
||||
|
||||
|
||||
@@ -20,22 +20,22 @@ public enum FileFolderTypeEnum implements BaseEnum {
|
||||
/**
|
||||
* 通用
|
||||
*/
|
||||
COMMON(1, FileFolderTypeEnum.FOLDER_PUBLIC + "/common/", "通用"),
|
||||
COMMON(1, FileFolderTypeEnum.FOLDER_PRIVATE + "/common/", "通用"),
|
||||
|
||||
/**
|
||||
* 公告
|
||||
*/
|
||||
NOTICE(2, FileFolderTypeEnum.FOLDER_PUBLIC + "/notice/", "公告"),
|
||||
NOTICE(2, FileFolderTypeEnum.FOLDER_PRIVATE + "/notice/", "公告"),
|
||||
|
||||
/**
|
||||
* 帮助中心
|
||||
*/
|
||||
HELP_DOC(3, FileFolderTypeEnum.FOLDER_PUBLIC + "/help-doc/", "帮助中心"),
|
||||
HELP_DOC(3, FileFolderTypeEnum.FOLDER_PRIVATE + "/help-doc/", "帮助中心"),
|
||||
|
||||
/**
|
||||
* 意见反馈
|
||||
*/
|
||||
FEEDBACK(4, FileFolderTypeEnum.FOLDER_PUBLIC + "/feedback/", "意见反馈"),
|
||||
FEEDBACK(4, FileFolderTypeEnum.FOLDER_PRIVATE + "/feedback/", "意见反馈"),
|
||||
|
||||
;
|
||||
|
||||
|
||||
@@ -24,7 +24,6 @@ import org.springframework.web.multipart.MultipartFile;
|
||||
import software.amazon.awssdk.core.ResponseBytes;
|
||||
import software.amazon.awssdk.core.sync.RequestBody;
|
||||
import software.amazon.awssdk.core.sync.ResponseTransformer;
|
||||
import software.amazon.awssdk.regions.Region;
|
||||
import software.amazon.awssdk.services.s3.S3Client;
|
||||
import software.amazon.awssdk.services.s3.model.*;
|
||||
import software.amazon.awssdk.services.s3.presigner.S3Presigner;
|
||||
@@ -72,6 +71,9 @@ public class FileStorageCloudServiceImpl implements IFileStorageService {
|
||||
@Resource
|
||||
private S3Client s3Client;
|
||||
|
||||
@Resource
|
||||
private S3Presigner s3Presigner;
|
||||
|
||||
@Resource
|
||||
private FileConfig cloudConfig;
|
||||
|
||||
@@ -109,7 +111,7 @@ public class FileStorageCloudServiceImpl implements IFileStorageService {
|
||||
// 根据文件路径获取并设置访问权限
|
||||
ObjectCannedACL acl = this.getACL(path);
|
||||
PutObjectRequest putObjectRequest = PutObjectRequest.builder()
|
||||
.bucket(cloudConfig.getBucketName())
|
||||
.bucket(cloudConfig.getCloudBucketName())
|
||||
.key(fileKey)
|
||||
.metadata(userMetadata)
|
||||
.contentLength(file.getSize())
|
||||
@@ -133,7 +135,7 @@ public class FileStorageCloudServiceImpl implements IFileStorageService {
|
||||
uploadVO.setFileName(originalFileName);
|
||||
uploadVO.setFileType(fileType);
|
||||
// 根据 访问权限 返回不同的 URL
|
||||
String url = cloudConfig.getUrlPrefix() + fileKey;
|
||||
String url = cloudConfig.getCloudPublicUrlPrefix() + fileKey;
|
||||
if (ObjectCannedACL.PRIVATE.equals(acl)) {
|
||||
// 获取临时访问的URL
|
||||
url = this.getFileUrl(fileKey).getData();
|
||||
@@ -158,11 +160,10 @@ public class FileStorageCloudServiceImpl implements IFileStorageService {
|
||||
|
||||
if (!fileKey.startsWith(FileFolderTypeEnum.FOLDER_PRIVATE)) {
|
||||
// 不是私有的 都公共读
|
||||
return ResponseDTO.ok(cloudConfig.getUrlPrefix() + fileKey);
|
||||
return ResponseDTO.ok(cloudConfig.getCloudPublicUrlPrefix() + fileKey);
|
||||
}
|
||||
|
||||
// 如果是私有的,则规定时间内可以访问,超过规定时间,则连接失效
|
||||
|
||||
String fileRedisKey = RedisKeyConst.Support.FILE_PRIVATE_VO + fileKey;
|
||||
FileVO fileVO = redisService.getObject(fileRedisKey, FileVO.class);
|
||||
if (fileVO == null) {
|
||||
@@ -170,15 +171,22 @@ public class FileStorageCloudServiceImpl implements IFileStorageService {
|
||||
if (fileVO == null) {
|
||||
return ResponseDTO.userErrorParam("文件不存在");
|
||||
}
|
||||
GetObjectRequest getUrlRequest = GetObjectRequest.builder().bucket(cloudConfig.getBucketName()).key(fileKey).build();
|
||||
GetObjectPresignRequest getObjectPresignRequest = GetObjectPresignRequest.builder().signatureDuration(Duration.ofSeconds(cloudConfig.getPrivateUrlExpireSeconds())).getObjectRequest(getUrlRequest).build();
|
||||
GetObjectRequest getUrlRequest = GetObjectRequest
|
||||
.builder()
|
||||
.bucket(cloudConfig.getCloudBucketName())
|
||||
.key(fileKey)
|
||||
.build();
|
||||
|
||||
S3Presigner presigner = S3Presigner.builder().region(Region.of(cloudConfig.getRegion())).build();
|
||||
GetObjectPresignRequest getObjectPresignRequest = GetObjectPresignRequest
|
||||
.builder()
|
||||
.signatureDuration(Duration.ofSeconds(cloudConfig.getCloudPrivateUrlExpireSeconds()))
|
||||
.getObjectRequest(getUrlRequest)
|
||||
.build();
|
||||
|
||||
PresignedGetObjectRequest presignedGetObjectRequest = presigner.presignGetObject(getObjectPresignRequest);
|
||||
PresignedGetObjectRequest presignedGetObjectRequest = s3Presigner.presignGetObject(getObjectPresignRequest);
|
||||
String url = presignedGetObjectRequest.url().toString();
|
||||
fileVO.setFileUrl(url);
|
||||
redisService.set(fileRedisKey, fileVO, cloudConfig.getPrivateUrlExpireSeconds() - 5);
|
||||
redisService.set(fileRedisKey, fileVO, cloudConfig.getCloudPrivateUrlExpireSeconds() - 5);
|
||||
}
|
||||
|
||||
return ResponseDTO.ok(fileVO.getFileUrl());
|
||||
@@ -192,7 +200,7 @@ public class FileStorageCloudServiceImpl implements IFileStorageService {
|
||||
public ResponseDTO<FileDownloadVO> download(String key) {
|
||||
|
||||
// 获取文件 meta
|
||||
HeadObjectRequest objectRequest = HeadObjectRequest.builder().bucket(this.cloudConfig.getBucketName()).key(key).build();
|
||||
HeadObjectRequest objectRequest = HeadObjectRequest.builder().bucket(this.cloudConfig.getCloudBucketName()).key(key).build();
|
||||
HeadObjectResponse headObjectResponse = s3Client.headObject(objectRequest);
|
||||
Map<String, String> userMetadata = headObjectResponse.metadata();
|
||||
FileMetadataVO metadataDTO = null;
|
||||
@@ -206,7 +214,7 @@ public class FileStorageCloudServiceImpl implements IFileStorageService {
|
||||
}
|
||||
|
||||
//获取oss对象
|
||||
GetObjectRequest getObjectRequest = GetObjectRequest.builder().bucket(cloudConfig.getBucketName()).key(key).build();
|
||||
GetObjectRequest getObjectRequest = GetObjectRequest.builder().bucket(cloudConfig.getCloudBucketName()).key(key).build();
|
||||
ResponseBytes<GetObjectResponse> s3ClientObject = s3Client.getObject(getObjectRequest, ResponseTransformer.toBytes());
|
||||
|
||||
// 输入流转换为字节流
|
||||
@@ -241,7 +249,7 @@ public class FileStorageCloudServiceImpl implements IFileStorageService {
|
||||
*/
|
||||
@Override
|
||||
public ResponseDTO<String> delete(String fileKey) {
|
||||
DeleteObjectRequest deleteObjectRequest = DeleteObjectRequest.builder().bucket(cloudConfig.getBucketName()).key(fileKey).build();
|
||||
DeleteObjectRequest deleteObjectRequest = DeleteObjectRequest.builder().bucket(cloudConfig.getCloudBucketName()).key(fileKey).build();
|
||||
s3Client.deleteObject(deleteObjectRequest);
|
||||
return ResponseDTO.ok();
|
||||
}
|
||||
|
||||
@@ -78,8 +78,8 @@ public class FileStorageLocalServiceImpl implements IFileStorageService {
|
||||
// 目录不存在,新建
|
||||
directory.mkdirs();
|
||||
}
|
||||
if (!path.endsWith("/")) {
|
||||
path = path + "/";
|
||||
if (!path.endsWith(File.separator)) {
|
||||
path = path + File.separator;
|
||||
}
|
||||
FileUploadVO fileUploadVO = new FileUploadVO();
|
||||
//原文件名
|
||||
|
||||
@@ -3,8 +3,10 @@ package net.lab1024.sa.base.module.support.loginlog.domain;
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@@ -20,6 +22,8 @@ import java.time.LocalDateTime;
|
||||
@TableName("t_login_log")
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class LoginLogEntity {
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
|
||||
@@ -3,8 +3,10 @@ package net.lab1024.sa.base.module.support.securityprotect.domain;
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@@ -21,6 +23,8 @@ import java.time.LocalDateTime;
|
||||
@Data
|
||||
@Builder
|
||||
@TableName("t_login_fail")
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class LoginFailEntity {
|
||||
|
||||
|
||||
|
||||
@@ -102,12 +102,13 @@ file:
|
||||
url-prefix:
|
||||
cloud:
|
||||
region: oss-cn-hangzhou
|
||||
endpoint: oss-cn-hangzhou.aliyuncs.com
|
||||
endpoint: https://oss-cn-hangzhou.aliyuncs.com
|
||||
bucket-name: 1024lab-smart-admin
|
||||
access-key:
|
||||
secret-key:
|
||||
url-prefix: https://${file.storage.cloud.bucket-name}.${file.storage.cloud.endpoint}/
|
||||
private-url-expire-seconds: 3600
|
||||
# 云计算厂商支持公开的文件访问模式;minio默认是不支持的,对于minio用户可以配置为空
|
||||
public-url-prefix: https://1024lab-smart-admin.oss-cn-hangzhou.aliyuncs.com/
|
||||
|
||||
# open api配置
|
||||
springdoc:
|
||||
|
||||
@@ -102,12 +102,13 @@ file:
|
||||
url-prefix:
|
||||
cloud:
|
||||
region: oss-cn-hangzhou
|
||||
endpoint: oss-cn-hangzhou.aliyuncs.com
|
||||
endpoint: https://oss-cn-hangzhou.aliyuncs.com
|
||||
bucket-name: 1024lab-smart-admin
|
||||
access-key:
|
||||
secret-key:
|
||||
url-prefix: https://${file.storage.cloud.bucket-name}.${file.storage.cloud.endpoint}/
|
||||
private-url-expire-seconds: 3600
|
||||
# 云计算厂商支持公开的文件访问模式;minio默认是不支持的,对于minio用户可以配置为空
|
||||
public-url-prefix: https://1024lab-smart-admin.oss-cn-hangzhou.aliyuncs.com/
|
||||
|
||||
# open api配置
|
||||
springdoc:
|
||||
|
||||
@@ -102,12 +102,13 @@ file:
|
||||
url-prefix:
|
||||
cloud:
|
||||
region: oss-cn-hangzhou
|
||||
endpoint: oss-cn-hangzhou.aliyuncs.com
|
||||
endpoint: https://oss-cn-hangzhou.aliyuncs.com
|
||||
bucket-name: 1024lab-smart-admin
|
||||
access-key:
|
||||
secret-key:
|
||||
url-prefix: https://${file.storage.cloud.bucket-name}.${file.storage.cloud.endpoint}/
|
||||
private-url-expire-seconds: 3600
|
||||
# 云计算厂商支持公开的文件访问模式;minio默认是不支持的,对于minio用户可以配置为空
|
||||
public-url-prefix: https://1024lab-smart-admin.oss-cn-hangzhou.aliyuncs.com/
|
||||
|
||||
# open api配置
|
||||
springdoc:
|
||||
|
||||
@@ -102,12 +102,13 @@ file:
|
||||
url-prefix:
|
||||
cloud:
|
||||
region: oss-cn-hangzhou
|
||||
endpoint: oss-cn-hangzhou.aliyuncs.com
|
||||
endpoint: https://oss-cn-hangzhou.aliyuncs.com
|
||||
bucket-name: 1024lab-smart-admin
|
||||
access-key:
|
||||
secret-key:
|
||||
url-prefix: https://${file.storage.cloud.bucket-name}.${file.storage.cloud.endpoint}/
|
||||
private-url-expire-seconds: 3600
|
||||
# 云计算厂商支持公开的文件访问模式;minio默认是不支持的,对于minio用户可以配置为空
|
||||
public-url-prefix: https://1024lab-smart-admin.oss-cn-hangzhou.aliyuncs.com/
|
||||
|
||||
# open api配置
|
||||
springdoc:
|
||||
|
||||
@@ -17,14 +17,14 @@ module.exports = {
|
||||
ecmaVersion: 12, // 默认情况下,ESLint使用的是ECMAScript5语法,此处我们设置的选项是 es12
|
||||
sourceType: 'module', // 指定js导入的方式
|
||||
},
|
||||
extends: ['plugin:vue/vue3-essential', 'eslint:recommended', 'plugin:vue/base'],
|
||||
extends: ['plugin:vue/vue3-essential', 'eslint:recommended', 'plugin:vue/base', 'prettier'],
|
||||
globals: {
|
||||
defineProps: 'readonly',
|
||||
defineEmits: 'readonly',
|
||||
defineExpose: 'readonly',
|
||||
withDefaults: 'readonly',
|
||||
},
|
||||
plugins: ['vue'],
|
||||
plugins: ['vue', 'prettier'],
|
||||
rules: {
|
||||
'no-unused-vars': [
|
||||
'error',
|
||||
@@ -62,5 +62,6 @@ module.exports = {
|
||||
],
|
||||
// Enable vue/script-setup-uses-vars rule
|
||||
'vue/script-setup-uses-vars': 'error',
|
||||
'prettier/prettier': 'error',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
import { useAppConfigStore } from '/@/store/modules/system/app-config';
|
||||
import { useSpinStore } from '/@/store/modules/system/spin';
|
||||
import { Popover, theme } from 'ant-design-vue';
|
||||
import { themeColors } from '/@/theme/color.js';
|
||||
import { themeColors } from '/@/theme/color';
|
||||
import SmartCopyIcon from '/@/components/framework/smart-copy-icon/index.vue';
|
||||
|
||||
const antdLocale = computed(() => messages[useAppConfigStore().language].antdLocale);
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
|
||||
<script setup>
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { useDictStore } from '/@/store/modules/system/dict.js';
|
||||
import { useDictStore } from '/@/store/modules/system/dict';
|
||||
|
||||
const props = defineProps({
|
||||
value: [Array, String, Number],
|
||||
|
||||
@@ -1,16 +1,46 @@
|
||||
<template>
|
||||
<span>{{ dataLabels }}</span>
|
||||
<template v-if="props.showStyle">
|
||||
<template v-for="(item, index) in dataList" :key="item.dictDataId">
|
||||
<span v-if="item.color" :style="{ color: token[item.color] }"> {{ item.dataLabel }} </span>
|
||||
<span v-else>{{ item.dataLabel }}</span>
|
||||
{{ index < dataList.length - 1 ? ',' : '' }}
|
||||
</template>
|
||||
</template>
|
||||
<!--不显示回显的颜色演示-->
|
||||
<template v-else>
|
||||
<span>{{ dataList }}</span>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { useDictStore } from '/@/store/modules/system/dict.js';
|
||||
import { useDictStore } from '/@/store/modules/system/dict';
|
||||
import { theme } from 'ant-design-vue';
|
||||
import { DICT_DATA_STYLE_ENUM } from '/@/constants/support/dict-const';
|
||||
|
||||
const props = defineProps({
|
||||
dictCode: String,
|
||||
dataValue: [String, Number],
|
||||
showStyle: { type: Boolean, default: false },
|
||||
});
|
||||
const dataLabels = computed(() => {
|
||||
return useDictStore().getDataLabels(props.dictCode, props.dataValue);
|
||||
|
||||
const dataList = computed(() => {
|
||||
if (!props.showStyle) {
|
||||
return useDictStore().getDataLabels(props.dictCode, props.dataValue);
|
||||
}
|
||||
|
||||
let dictDataList = useDictStore().getDataList(props.dictCode, props.dataValue);
|
||||
let dataList = [];
|
||||
for (const item of dictDataList) {
|
||||
if (item.dataStyle) {
|
||||
dataList.push(Object.assign({}, item, { color: DICT_DATA_STYLE_ENUM[item.dataStyle.toUpperCase()].color }));
|
||||
} else {
|
||||
dataList.push(Object.assign({}, item));
|
||||
}
|
||||
}
|
||||
return dataList;
|
||||
});
|
||||
|
||||
const { useToken } = theme;
|
||||
const { token } = useToken();
|
||||
</script>
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
|
||||
<script setup>
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { useDictStore } from '/@/store/modules/system/dict.js';
|
||||
import { useDictStore } from '/@/store/modules/system/dict';
|
||||
|
||||
const props = defineProps({
|
||||
dictCode: String,
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
import { message } from 'ant-design-vue';
|
||||
import { mergeColumn } from './smart-table-column-merge';
|
||||
import { smartSentry } from '/@/lib/smart-sentry';
|
||||
import { useAppConfigStore } from '/@/store/modules/system/app-config.js';
|
||||
import { useAppConfigStore } from '/@/store/modules/system/app-config';
|
||||
|
||||
const props = defineProps({
|
||||
// 表格列数组
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
<script setup>
|
||||
import { onMounted, ref, watch } from 'vue';
|
||||
import { smartSentry } from '/@/lib/smart-sentry';
|
||||
import { positionApi } from '/@/api/system/position-api.js';
|
||||
import { positionApi } from '/@/api/system/position-api';
|
||||
|
||||
// =========== 属性定义 和 事件方法暴露 =============
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import message from './business/message/message-const';
|
||||
import codeGeneratorConst from './support/code-generator-const';
|
||||
import changeLogConst from './support/change-log-const';
|
||||
import jobConst from './support/job-const';
|
||||
import dictConst from './support/dict-const';
|
||||
|
||||
export default {
|
||||
FLAG_NUMBER_ENUM,
|
||||
@@ -39,4 +40,5 @@ export default {
|
||||
...codeGeneratorConst,
|
||||
...changeLogConst,
|
||||
...jobConst,
|
||||
...dictConst,
|
||||
};
|
||||
|
||||
@@ -16,6 +16,40 @@ export const DICT_CODE_ENUM = {
|
||||
GOODS_PLACE: 'GOODS_PLACE',
|
||||
};
|
||||
|
||||
export const DICT_DATA_STYLE_ENUM = {
|
||||
DEFAULT: {
|
||||
value: 'default',
|
||||
desc: '默认',
|
||||
color: 'colorText',
|
||||
},
|
||||
PRIMARY: {
|
||||
value: 'primary',
|
||||
desc: '主要',
|
||||
color: 'colorPrimary',
|
||||
},
|
||||
SUCCESS: {
|
||||
value: 'success',
|
||||
desc: '成功',
|
||||
color: 'colorSuccess',
|
||||
},
|
||||
INFO: {
|
||||
value: 'info',
|
||||
desc: '信息',
|
||||
color: 'colorInfo',
|
||||
},
|
||||
WARN: {
|
||||
value: 'warn',
|
||||
desc: '警告',
|
||||
color: 'colorWarning',
|
||||
},
|
||||
DANGER: {
|
||||
value: 'danger',
|
||||
desc: '危险',
|
||||
color: 'colorError',
|
||||
},
|
||||
};
|
||||
|
||||
export default {
|
||||
DICT_CODE_ENUM,
|
||||
DICT_DATA_STYLE_ENUM,
|
||||
};
|
||||
|
||||
@@ -16,10 +16,10 @@
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import Password from '/@/views/system/account/components/password/index.vue';
|
||||
import { useUserStore } from '/@/store/modules/system/user.js';
|
||||
import { loginApi } from '/@/api/system/login-api.js';
|
||||
import { smartSentry } from '/@/lib/smart-sentry.js';
|
||||
import { SmartLoading } from '/@/components/framework/smart-loading/index.js';
|
||||
import { useUserStore } from '/@/store/modules/system/user';
|
||||
import { loginApi } from '/@/api/system/login-api';
|
||||
import { smartSentry } from '/@/lib/smart-sentry';
|
||||
import { SmartLoading } from '/@/components/framework/smart-loading/index';
|
||||
|
||||
/**
|
||||
* 修改密码弹窗
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
import { smartSentry } from '/@/lib/smart-sentry';
|
||||
import HeaderResetPassword from './header-reset-password-modal/index.vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { ACCOUNT_MENU } from '/@/views/system/account/account-menu.js';
|
||||
import { ACCOUNT_MENU } from '/@/views/system/account/account-menu';
|
||||
|
||||
// 头像背景颜色
|
||||
const AVATAR_BACKGROUND_COLOR_ARRAY = ['#87d068', '#00B853', '#f56a00', '#1890ff'];
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
</template>
|
||||
<script setup>
|
||||
import { reactive, ref } from 'vue';
|
||||
import { messageApi } from '/@/api/support/message-api.js';
|
||||
import { messageApi } from '/@/api/support/message-api';
|
||||
|
||||
const emit = defineEmits(['refresh']);
|
||||
|
||||
|
||||
@@ -74,9 +74,9 @@
|
||||
<script setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import { BellOutlined } from '@ant-design/icons-vue';
|
||||
import { useUserStore } from '/@/store/modules/system/user.js';
|
||||
import { smartSentry } from '/@/lib/smart-sentry.js';
|
||||
import { messageApi } from '/@/api/support/message-api.js';
|
||||
import { useUserStore } from '/@/store/modules/system/user';
|
||||
import { smartSentry } from '/@/lib/smart-sentry';
|
||||
import { messageApi } from '/@/api/support/message-api';
|
||||
import dayjs from 'dayjs';
|
||||
import { theme } from 'ant-design-vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
@@ -137,7 +137,7 @@
|
||||
import { useAppConfigStore } from '/@/store/modules/system/app-config';
|
||||
import { Modal } from 'ant-design-vue';
|
||||
import { appDefaultConfig } from '/@/config/app-config';
|
||||
import { themeColors } from '/@/theme/color.js';
|
||||
import { themeColors } from '/@/theme/color';
|
||||
|
||||
// ----------------- modal 显示与隐藏 -----------------
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
import DefaultTab from './components/default-tab.vue';
|
||||
import AntdTab from './components/antd-tab.vue';
|
||||
import ChromeTab from './components/chrome-tab.vue';
|
||||
import { PAGE_TAG_ENUM } from '/@/constants/layout-const.js';
|
||||
import { PAGE_TAG_ENUM } from '/@/constants/layout-const';
|
||||
import { theme } from 'ant-design-vue';
|
||||
|
||||
const pageTagStyle = computed(() => useAppConfigStore().$state.pageTagStyle);
|
||||
|
||||
@@ -104,7 +104,7 @@
|
||||
import SideHelpDoc from './components/side-help-doc/index.vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { HOME_PAGE_NAME } from '/@/constants/system/home-const';
|
||||
import { LAYOUT_ELEMENT_IDS } from '/@/layout/layout-const.js';
|
||||
import { LAYOUT_ELEMENT_IDS } from '/@/layout/layout-const';
|
||||
import { theme as antDesignTheme } from 'ant-design-vue';
|
||||
const appConfigStore = useAppConfigStore();
|
||||
|
||||
|
||||
@@ -112,7 +112,7 @@
|
||||
import SideHelpDoc from './components/side-help-doc/index.vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { HOME_PAGE_NAME } from '/@/constants/system/home-const';
|
||||
import { LAYOUT_ELEMENT_IDS } from '/@/layout/layout-const.js';
|
||||
import { LAYOUT_ELEMENT_IDS } from '/@/layout/layout-const';
|
||||
import { theme as antDesignTheme } from 'ant-design-vue';
|
||||
|
||||
const appConfigStore = useAppConfigStore();
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
import { useUserStore } from '/@/store/modules/system/user';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { HOME_PAGE_NAME } from '/@/constants/system/home-const';
|
||||
import { LAYOUT_ELEMENT_IDS } from '/@/layout/layout-const.js';
|
||||
import { LAYOUT_ELEMENT_IDS } from '/@/layout/layout-const';
|
||||
import MenuLocationBreadcrumb from './components/menu-location-breadcrumb/index.vue';
|
||||
import { theme as antDesignTheme } from 'ant-design-vue';
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import { useUserStore } from '/@/store/modules/system/user';
|
||||
import { decryptData, encryptData } from './encrypt';
|
||||
import { DATA_TYPE_ENUM } from '../constants/common-const';
|
||||
import _ from 'lodash';
|
||||
import LocalStorageKeyConst from '/@/constants/local-storage-key-const.js';
|
||||
import LocalStorageKeyConst from '/@/constants/local-storage-key-const';
|
||||
|
||||
// token的消息头
|
||||
const TOKEN_HEADER = 'Authorization';
|
||||
|
||||
@@ -28,11 +28,11 @@ import { store } from '/@/store';
|
||||
import { useUserStore } from '/@/store/modules/system/user';
|
||||
import 'ant-design-vue/dist/reset.css';
|
||||
import '/@/theme/index.less';
|
||||
import { localRead } from '/@/utils/local-util.js';
|
||||
import LocalStorageKeyConst from '/@/constants/local-storage-key-const.js';
|
||||
import { localRead } from '/@/utils/local-util';
|
||||
import LocalStorageKeyConst from '/@/constants/local-storage-key-const';
|
||||
import '/@/utils/ployfill';
|
||||
import { useDictStore } from '/@/store/modules/system/dict.js';
|
||||
import { dictApi } from '/@/api/support/dict-api.js';
|
||||
import { useDictStore } from '/@/store/modules/system/dict';
|
||||
import { dictApi } from '/@/api/support/dict-api';
|
||||
|
||||
/*
|
||||
* -------------------- ※ 着重 解释说明下main.js的初始化逻辑 begin ※ --------------------
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
*/
|
||||
import { useDictStore } from '/@/store/modules/system/dict.js';
|
||||
import { useDictStore } from '/@/store/modules/system/dict';
|
||||
|
||||
export default {
|
||||
install: (app) => {
|
||||
|
||||
@@ -18,7 +18,7 @@ import SmartLayout from '../layout/index.vue';
|
||||
import { useUserStore } from '/@/store/modules/system/user';
|
||||
import { localClear, localRead } from '/@/utils/local-util';
|
||||
import _ from 'lodash';
|
||||
import LocalStorageKeyConst from '/@/constants/local-storage-key-const.js';
|
||||
import LocalStorageKeyConst from '/@/constants/local-storage-key-const';
|
||||
|
||||
export const router = createRouter({
|
||||
history: createWebHashHistory(),
|
||||
|
||||
@@ -12,7 +12,7 @@ export const loginRouters = [
|
||||
{
|
||||
path: '/login',
|
||||
name: 'Login',
|
||||
component: () => import('/@/views/system/login3/login.vue'),
|
||||
component: () => import('/@/views/system/login/login.vue'),
|
||||
meta: {
|
||||
title: '登录',
|
||||
hideInMenu: true,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { DICT_SPLIT } from '/@/constants/support/dict-const.js';
|
||||
import { DICT_SPLIT } from '/@/constants/support/dict-const';
|
||||
import _ from 'lodash';
|
||||
import { dictApi } from '/@/api/support/dict-api.js';
|
||||
import { smartSentry } from '/@/lib/smart-sentry.js';
|
||||
import { dictApi } from '/@/api/support/dict-api';
|
||||
import { smartSentry } from '/@/lib/smart-sentry';
|
||||
|
||||
export const useDictStore = defineStore({
|
||||
id: 'dict',
|
||||
@@ -26,6 +26,34 @@ export const useDictStore = defineStore({
|
||||
return dictDataList ? dictDataList : [];
|
||||
},
|
||||
|
||||
// 获取字典的值名称
|
||||
getDataList(dictCode, dataValue) {
|
||||
if (_.isNil(dataValue) || _.isNaN(dataValue)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
let dict = this.getDictData(dictCode);
|
||||
if (dict.length === 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// 是数字的话,需要特殊处理
|
||||
if (_.isNumber(dataValue)) {
|
||||
let target = _.find(dict, { dataValue: String(dataValue) });
|
||||
return target ? target.dataLabel : '';
|
||||
}
|
||||
|
||||
let valueArray = dataValue.split(DICT_SPLIT);
|
||||
let result = [];
|
||||
for (let item of valueArray) {
|
||||
let target = _.find(dict, { dataValue: item });
|
||||
if (target) {
|
||||
result.push(target);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
},
|
||||
|
||||
// 获取字典的值名称
|
||||
getDataLabels(dictCode, dataValue) {
|
||||
if (_.isNil(dataValue) || _.isNaN(dataValue)) {
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
*/
|
||||
import { defineStore } from 'pinia';
|
||||
import { smartSentry } from '/@/lib/smart-sentry.js';
|
||||
import { smartSentry } from '/@/lib/smart-sentry';
|
||||
|
||||
export const useSpinStore = defineStore({
|
||||
id: 'spin',
|
||||
|
||||
@@ -12,8 +12,8 @@ import { defineStore } from 'pinia';
|
||||
import localKey from '/@/constants/local-storage-key-const';
|
||||
import { HOME_PAGE_NAME } from '/@/constants/system/home-const';
|
||||
import { MENU_TYPE_ENUM } from '/@/constants/system/menu-const';
|
||||
import { messageApi } from '/@/api/support/message-api.js';
|
||||
import { smartSentry } from '/@/lib/smart-sentry.js';
|
||||
import { messageApi } from '/@/api/support/message-api';
|
||||
import { smartSentry } from '/@/lib/smart-sentry';
|
||||
import { localRead, localSave, localRemove } from '/@/utils/local-util';
|
||||
|
||||
|
||||
|
||||
@@ -118,7 +118,7 @@
|
||||
{{ text }}
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'place'">
|
||||
<DictLabel :dict-code="DICT_CODE_ENUM.GOODS_PLACE" :data-value="text" />
|
||||
<DictLabel :show-style="true" :dict-code="DICT_CODE_ENUM.GOODS_PLACE" :data-value="text" />
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'remark'">
|
||||
<span>{{ text ? text : '' }}</span>
|
||||
@@ -200,7 +200,7 @@
|
||||
import SmartEnumSelect from '/@/components/framework/smart-enum-select/index.vue';
|
||||
import _ from 'lodash';
|
||||
import SmartHeaderCell from '/@/components/support/table-header-cell/index.vue';
|
||||
import { DICT_CODE_ENUM } from '/@/constants/support/dict-const.js';
|
||||
import { DICT_CODE_ENUM } from '/@/constants/support/dict-const';
|
||||
import DictLabel from '/@/components/support/dict-label/index.vue';
|
||||
|
||||
// ---------------------------- 表格列 ----------------------------
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
<a-form-item label="创建时间" class="smart-query-form-item">
|
||||
<a-space direction="vertical" :size="12">
|
||||
<a-range-picker v-model:value="searchDate" @change="dateChange" />
|
||||
<a-range-picker v-model:value="searchDate" :presets="defaultTimeRanges" @change="dateChange" />
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
|
||||
@@ -93,6 +93,7 @@
|
||||
import { SmartLoading } from '/@/components/framework/smart-loading';
|
||||
import { message, Modal } from 'ant-design-vue';
|
||||
import { smartSentry } from '/@/lib/smart-sentry';
|
||||
import { defaultTimeRanges } from '/@/lib/default-time-ranges';
|
||||
import TableOperator from '/@/components/support/table-operator/index.vue';
|
||||
import { TABLE_ID_CONST } from '/@/constants/support/table-id-const';
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
<a-form-item label="创建时间" class="smart-query-form-item">
|
||||
<a-space direction="vertical" :size="12">
|
||||
<a-range-picker v-model:value="searchDate" @change="dateChange" />
|
||||
<a-range-picker v-model:value="searchDate" :presets="defaultTimeRanges" @change="dateChange" />
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
|
||||
@@ -92,6 +92,7 @@
|
||||
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 { defaultTimeRanges } from '/@/lib/default-time-ranges';
|
||||
|
||||
const props = defineProps({
|
||||
enterpriseId: {
|
||||
|
||||
@@ -13,16 +13,20 @@ JavaTypeMap.set('integer', 'Integer');
|
||||
JavaTypeMap.set('year', 'Integer');
|
||||
JavaTypeMap.set('bigint', 'Long');
|
||||
JavaTypeMap.set('int8', 'Long');
|
||||
JavaTypeMap.set('number', 'Long');
|
||||
JavaTypeMap.set('float', 'BigDecimal');
|
||||
JavaTypeMap.set('double', 'BigDecimal');
|
||||
JavaTypeMap.set('decimal', 'BigDecimal');
|
||||
JavaTypeMap.set('char', 'String');
|
||||
JavaTypeMap.set('varchar', 'String');
|
||||
JavaTypeMap.set('varchar2', 'String');
|
||||
JavaTypeMap.set('nvarchar', 'String');
|
||||
JavaTypeMap.set('nvarchar2', 'String');
|
||||
JavaTypeMap.set('tinytext', 'String');
|
||||
JavaTypeMap.set('text', 'String');
|
||||
JavaTypeMap.set('longtext', 'String');
|
||||
JavaTypeMap.set('blob', 'String');
|
||||
JavaTypeMap.set('clob', 'String');
|
||||
JavaTypeMap.set('date', 'LocalDate');
|
||||
JavaTypeMap.set('datetime', 'LocalDateTime');
|
||||
JavaTypeMap.set('datetime2', 'LocalDateTime');
|
||||
@@ -52,6 +56,7 @@ JsTypeMap.set('int', 'Number');
|
||||
JsTypeMap.set('int2', 'Number');
|
||||
JsTypeMap.set('int4', 'Number');
|
||||
JsTypeMap.set('int8', 'Number');
|
||||
JsTypeMap.set('number', 'Number');
|
||||
JsTypeMap.set('tinyint', 'Number');
|
||||
JsTypeMap.set('smallint', 'Number');
|
||||
JsTypeMap.set('integer', 'Number');
|
||||
@@ -62,12 +67,15 @@ JsTypeMap.set('double', 'Number');
|
||||
JsTypeMap.set('decimal', 'Number');
|
||||
JsTypeMap.set('char', 'String');
|
||||
JsTypeMap.set('varchar', 'String');
|
||||
JsTypeMap.set('varchar2', 'String');
|
||||
JsTypeMap.set('nvarchar', 'String');
|
||||
JsTypeMap.set('nvarchar2', 'String');
|
||||
JsTypeMap.set('character', 'String');
|
||||
JsTypeMap.set('tinytext', 'String');
|
||||
JsTypeMap.set('text', 'String');
|
||||
JsTypeMap.set('longtext', 'String');
|
||||
JsTypeMap.set('blob', 'String');
|
||||
JsTypeMap.set('clob', 'String');
|
||||
JsTypeMap.set('date', 'Date');
|
||||
JsTypeMap.set('datetime', 'Date');
|
||||
JsTypeMap.set('datetime2', 'Date');
|
||||
@@ -94,6 +102,7 @@ FrontComponentMap.set('int', 'InputNumber');
|
||||
FrontComponentMap.set('int2', 'InputNumber');
|
||||
FrontComponentMap.set('int4', 'InputNumber');
|
||||
FrontComponentMap.set('int8', 'InputNumber');
|
||||
FrontComponentMap.set('number', 'InputNumber');
|
||||
FrontComponentMap.set('tinyint', 'InputNumber');
|
||||
FrontComponentMap.set('smallint', 'InputNumber');
|
||||
FrontComponentMap.set('integer', 'InputNumber');
|
||||
@@ -105,12 +114,15 @@ FrontComponentMap.set('double', 'InputNumber');
|
||||
FrontComponentMap.set('decimal', 'InputNumber');
|
||||
FrontComponentMap.set('char', 'Input');
|
||||
FrontComponentMap.set('varchar', 'Input');
|
||||
FrontComponentMap.set('varchar2', 'Input');
|
||||
FrontComponentMap.set('nvarchar', 'Input');
|
||||
FrontComponentMap.set('nvarchar2', 'Input');
|
||||
FrontComponentMap.set('character', 'Input');
|
||||
FrontComponentMap.set('tinytext', 'Input');
|
||||
FrontComponentMap.set('text', 'Textarea');
|
||||
FrontComponentMap.set('longtext', 'Textarea');
|
||||
FrontComponentMap.set('blob', 'FileUpload');
|
||||
FrontComponentMap.set('clob', 'FileUpload');
|
||||
FrontComponentMap.set('date', 'Date');
|
||||
FrontComponentMap.set('datetime', 'DateTime');
|
||||
FrontComponentMap.set('datetime2', 'DateTime');
|
||||
|
||||
@@ -183,6 +183,7 @@
|
||||
let removePrefixTableName = tableInfo.tableName;
|
||||
if (_.startsWith(tableInfo.tableName, tablePrefix.value)) {
|
||||
removePrefixTableName = _.trimStart(removePrefixTableName, tablePrefix.value);
|
||||
removePrefixTableName = removePrefixTableName.slice(tablePrefix.value.length);
|
||||
}
|
||||
formData.moduleName = basic && basic.moduleName ? basic.moduleName : removePrefixTableName;
|
||||
formData.moduleName = convertUpperCamel(formData.moduleName);
|
||||
@@ -205,7 +206,7 @@
|
||||
function onChangeTablePrefix(e) {
|
||||
let removePrefixTableName = tableInfo.tableName;
|
||||
if (_.startsWith(tableInfo.tableName, tablePrefix.value)) {
|
||||
removePrefixTableName = _.trim(removePrefixTableName, tablePrefix.value);
|
||||
removePrefixTableName = tableInfo.tableName.slice(tablePrefix.value.length);
|
||||
}
|
||||
formData.moduleName = convertUpperCamel(removePrefixTableName);
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@
|
||||
import DictCodeSelect from '/@/components/support/dict-code-select/index.vue';
|
||||
import { convertUpperCamel, convertLowerCamel } from '/@/utils/str-util';
|
||||
import _ from 'lodash';
|
||||
import { useDictStore } from '/@/store/modules/system/dict.js';
|
||||
import { useDictStore } from '/@/store/modules/system/dict';
|
||||
|
||||
const dictRef = ref();
|
||||
function refreshDict() {
|
||||
|
||||
@@ -102,6 +102,7 @@
|
||||
const visibleFlag = ref(false);
|
||||
function showModal(table) {
|
||||
Object.assign(tableInfo, table);
|
||||
tableInfo.tableName = tableInfo.tableName.toLowerCase();
|
||||
tableInfo.createTime = table.createTime ? table.createTime : new Date();
|
||||
activeKey.value = '1';
|
||||
visibleFlag.value = true;
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
import { JAVA_FILE_LIST, LANGUAGE_LIST, JS_FILE_LIST, TS_FILE_LIST } from '../../code-generator-util';
|
||||
import { smartSentry } from '/@/lib/smart-sentry';
|
||||
import { lineNumbersBlock } from '/@/lib/highlight-line-number';
|
||||
import hljs from 'highlight.js';
|
||||
import hljs from 'highlight';
|
||||
import 'highlight.js/styles/github-dark.css';
|
||||
import javascript from 'highlight.js/lib/languages/javascript';
|
||||
import typescript from 'highlight.js/lib/languages/typescript';
|
||||
|
||||
@@ -17,6 +17,15 @@
|
||||
<a-form-item label="字典项值" name="dataValue">
|
||||
<a-input v-model:value="form.dataValue" placeholder="请输入 字典项值" />
|
||||
</a-form-item>
|
||||
<a-form-item label="显示样式">
|
||||
<a-select ref="dataStyleSelect" v-model:value="form.dataStyle" :allowClear="true">
|
||||
<template v-for="item in DICT_DATA_STYLE_ENUM" :key="item.value">
|
||||
<a-select-option :value="item.value">
|
||||
<div :style="{ color: token[item.color] }">{{ item.desc }}({{ item.value }})</div>
|
||||
</a-select-option>
|
||||
</template>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="排序" name="sort" help="值越大越靠前">
|
||||
<a-input-number style="width: 100%" v-model:value="form.sortOrder" :min="0" :max="1000" />
|
||||
</a-form-item>
|
||||
@@ -28,10 +37,14 @@
|
||||
</template>
|
||||
<script setup>
|
||||
import { ref, reactive } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { message, theme } from 'ant-design-vue';
|
||||
import { SmartLoading } from '/@/components/framework/smart-loading';
|
||||
import { dictApi } from '/@/api/support/dict-api';
|
||||
import { smartSentry } from '/@/lib/smart-sentry';
|
||||
import { DICT_DATA_STYLE_ENUM } from '/@/constants/support/dict-const';
|
||||
|
||||
const { useToken } = theme;
|
||||
const { token } = useToken();
|
||||
|
||||
// emit
|
||||
const emit = defineEmits(['reloadList']);
|
||||
@@ -46,6 +59,7 @@
|
||||
sortOrder: 0,
|
||||
dataValue: '',
|
||||
dataLabel: '',
|
||||
dataStyle: '',
|
||||
remark: '',
|
||||
};
|
||||
let form = reactive({ ...formDefault });
|
||||
|
||||
@@ -168,9 +168,9 @@
|
||||
let keywordsFilterFlag = true;
|
||||
if (keywords.value) {
|
||||
keywordsFilterFlag =
|
||||
_.includes(item.dataValue.toLowerCase(), keywords.value.toLowerCase()) ||
|
||||
_.includes(item.dataLabel.toLowerCase(), keywords.value.toLowerCase()) ||
|
||||
_.includes(item.remark.toLowerCase(), keywords.value.toLowerCase());
|
||||
(item.dataValue &&_.includes(item.dataValue.toLowerCase(), keywords.value.toLowerCase())) ||
|
||||
(item.dataLabel && _.includes(item.dataLabel.toLowerCase(), keywords.value.toLowerCase())) ||
|
||||
(item.remark && _.includes(item.remark.toLowerCase(), keywords.value.toLowerCase()));
|
||||
}
|
||||
let disabledFilterFlag = _.isNull(disabledFlag.value) ? true : item.disabledFlag === disabledFlag.value;
|
||||
return disabledFilterFlag && keywordsFilterFlag;
|
||||
|
||||
@@ -145,11 +145,12 @@
|
||||
title: '文件夹',
|
||||
dataIndex: 'folderType',
|
||||
ellipsis: true,
|
||||
width: 100,
|
||||
width: 70,
|
||||
},
|
||||
{
|
||||
title: '文件名称',
|
||||
dataIndex: 'fileName',
|
||||
ellipsis: true,
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
@@ -164,24 +165,18 @@
|
||||
ellipsis: true,
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: '上传人',
|
||||
dataIndex: 'creatorName',
|
||||
ellipsis: true,
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '人类型',
|
||||
dataIndex: 'creatorUserType',
|
||||
ellipsis: true,
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '上传时间',
|
||||
dataIndex: 'createTime',
|
||||
ellipsis: true,
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: '文件Key',
|
||||
dataIndex: 'fileKey',
|
||||
ellipsis: true,
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'action',
|
||||
|
||||
@@ -130,7 +130,7 @@
|
||||
import { jobApi } from '/@/api/support/job-api';
|
||||
import { PAGE_SIZE_OPTIONS } from '/@/constants/common-const';
|
||||
import { smartSentry } from '/@/lib/smart-sentry';
|
||||
import { TRIGGER_TYPE_ENUM } from '/@/constants/support/job-const.js';
|
||||
import { TRIGGER_TYPE_ENUM } from '/@/constants/support/job-const';
|
||||
import JobLogListModal from './job-log-list-modal.vue';
|
||||
import {TABLE_ID_CONST} from "/@/constants/support/table-id-const.js";
|
||||
import TableOperator from "/@/components/support/table-operator/index.vue";
|
||||
|
||||
@@ -120,8 +120,8 @@ import { message } from 'ant-design-vue';
|
||||
import { reactive, ref } from 'vue';
|
||||
import { jobApi } from '/@/api/support/job-api';
|
||||
import { smartSentry } from '/@/lib/smart-sentry';
|
||||
import { SmartLoading } from '/@/components/framework/smart-loading/index.js';
|
||||
import { TRIGGER_TYPE_ENUM } from '/@/constants/support/job-const.js';
|
||||
import { SmartLoading } from '/@/components/framework/smart-loading/index';
|
||||
import { TRIGGER_TYPE_ENUM } from '/@/constants/support/job-const';
|
||||
|
||||
// emit
|
||||
const emit = defineEmits(['reloadList']);
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* @Date: 2024/06/25
|
||||
-->
|
||||
<template>
|
||||
<a-drawer v-model:open="showFlag" :width="1000" :title="title" placement="right" :destroyOnClose="true">
|
||||
<a-drawer v-model:open="showFlag" :width="1100" :title="title" placement="right" :destroyOnClose="true">
|
||||
<a-form class="smart-query-form">
|
||||
<a-row class="smart-query-form-row">
|
||||
<a-form-item label="关键字" class="smart-query-form-item">
|
||||
@@ -12,13 +12,13 @@
|
||||
</a-form-item>
|
||||
<a-form-item label="执行结果" class="smart-query-form-item">
|
||||
<a-select style="width: 100px" v-model:value="queryForm.successFlag" placeholder="请选择" allowClear>
|
||||
<a-select-option :key="1"> 成功 </a-select-option>
|
||||
<a-select-option :key="0"> 失败 </a-select-option>
|
||||
<a-select-option :key="1"> 成功</a-select-option>
|
||||
<a-select-option :key="0"> 失败</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="执行时间" class="smart-query-form-item">
|
||||
<a-space direction="vertical" :size="12">
|
||||
<a-range-picker v-model:value="searchDate" style="width: 220px" @change="dateChange" />
|
||||
<a-range-picker v-model:value="searchDate" style="width: 220px" :presets="defaultTimeRanges" @change="dateChange" />
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
|
||||
@@ -41,7 +41,6 @@
|
||||
</a-row>
|
||||
</a-form>
|
||||
|
||||
<a-card size="small" :bordered="false" :hoverable="true">
|
||||
<a-row justify="end">
|
||||
<TableOperator class="smart-margin-bottom5" v-model="columns" :tableId="TABLE_ID_CONST.SUPPORT.JOB_LOG" :refresh="queryLogList" />
|
||||
</a-row>
|
||||
@@ -49,13 +48,25 @@
|
||||
<a-table size="small" :loading="tableLoading" bordered :dataSource="tableData" :columns="columns" rowKey="jobLogId" :pagination="false">
|
||||
<template #bodyCell="{ record, column }">
|
||||
<template v-if="column.dataIndex === 'executeStartTime'">
|
||||
<div><a-tag color="green">始</a-tag>{{ record.executeStartTime }}</div>
|
||||
<div style="margin-top: 5px"><a-tag color="blue">终</a-tag>{{ record.executeEndTime }}</div>
|
||||
<div>
|
||||
<a-tag color="green">始</a-tag>
|
||||
{{ record.executeStartTime }}
|
||||
</div>
|
||||
<div style="margin-top: 5px">
|
||||
<a-tag color="blue">终</a-tag>
|
||||
{{ record.executeEndTime }}
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'executeTimeMillis'"> {{ record.executeTimeMillis }} ms </template>
|
||||
<template v-if="column.dataIndex === 'executeTimeMillis'"> {{ record.executeTimeMillis }} ms</template>
|
||||
<template v-if="column.dataIndex === 'successFlag'">
|
||||
<div v-if="record.successFlag" style="color: #39c710"><CheckOutlined />成功</div>
|
||||
<div v-else style="color: #f50"><WarningOutlined />失败</div>
|
||||
<div v-if="record.successFlag" style="color: #39c710">
|
||||
<CheckOutlined />
|
||||
成功
|
||||
</div>
|
||||
<div v-else style="color: #f50">
|
||||
<WarningOutlined />
|
||||
失败
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
@@ -74,7 +85,6 @@
|
||||
:show-total="(total) => `共${total}条`"
|
||||
/>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-drawer>
|
||||
</template>
|
||||
<script setup>
|
||||
@@ -84,6 +94,7 @@
|
||||
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 { defaultTimeRanges } from '/@/lib/default-time-ranges';
|
||||
|
||||
const showFlag = ref(false);
|
||||
const title = ref('');
|
||||
|
||||
@@ -160,10 +160,10 @@
|
||||
import TableOperator from '/@/components/support/table-operator/index.vue';
|
||||
import { TABLE_ID_CONST } from '/@/constants/support/table-id-const';
|
||||
import DeletedJobList from './components/deleted-job-list.vue';
|
||||
import { TRIGGER_TYPE_ENUM } from '/@/constants/support/job-const.js';
|
||||
import { TRIGGER_TYPE_ENUM } from '/@/constants/support/job-const';
|
||||
import JobFormModal from './components/job-form-modal.vue';
|
||||
import JobLogListModal from './components/job-log-list-modal.vue';
|
||||
import { SmartLoading } from '/@/components/framework/smart-loading/index.js';
|
||||
import { SmartLoading } from '/@/components/framework/smart-loading/index';
|
||||
const activeKey = ref('1');
|
||||
const columns = ref([
|
||||
{
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
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-api.js';
|
||||
import { dataMaskingApi } from '/@/api/support/data-masking-api';
|
||||
|
||||
//------------------------ 表格渲染 ---------------------
|
||||
|
||||
|
||||
@@ -117,9 +117,9 @@
|
||||
</template>
|
||||
<script setup>
|
||||
import { onMounted, reactive, ref } from 'vue';
|
||||
import { level3ProtectApi } from '/@/api/support/level3-protect-api.js';
|
||||
import { SmartLoading } from '/@/components/framework/smart-loading/index.js';
|
||||
import { smartSentry } from '/@/lib/smart-sentry.js';
|
||||
import { level3ProtectApi } from '/@/api/support/level3-protect-api';
|
||||
import { SmartLoading } from '/@/components/framework/smart-loading/index';
|
||||
import { smartSentry } from '/@/lib/smart-sentry';
|
||||
import { message, Modal } from 'ant-design-vue';
|
||||
|
||||
// 三级等保的默认值
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<SmartEnumSelect width="120px" enum-name="FLAG_NUMBER_ENUM" v-model:value="queryForm.readFlag" />
|
||||
</a-form-item>
|
||||
<a-form-item label="创建时间" class="smart-query-form-item">
|
||||
<a-range-picker v-model:value="queryForm.createTime" style="width: 200px" @change="onChangeCreateTime" />
|
||||
<a-range-picker v-model:value="queryForm.createTime" :presets="defaultTimeRanges" style="width: 200px" @change="onChangeCreateTime" />
|
||||
</a-form-item>
|
||||
<a-form-item class="smart-query-form-item">
|
||||
<a-button type="primary" @click="searchQuery">
|
||||
@@ -96,6 +96,7 @@
|
||||
import TableOperator from '/@/components/support/table-operator/index.vue';
|
||||
import MessageSendForm from './components/message-send-form.vue';
|
||||
import { TABLE_ID_CONST } from '/@/constants/support/table-id-const';
|
||||
import { defaultTimeRanges } from '/@/lib/default-time-ranges';
|
||||
// ---------------------------- 表格列 ----------------------------
|
||||
|
||||
const columns = ref([
|
||||
|
||||
@@ -76,18 +76,18 @@
|
||||
</template>
|
||||
<script setup>
|
||||
import { onMounted, reactive, ref } from 'vue';
|
||||
import { regular } from '/@/constants/regular-const.js';
|
||||
import { regular } from '/@/constants/regular-const';
|
||||
import DepartmentTreeSelect from '/@/components/system/department-tree-select/index.vue';
|
||||
import PositionSelect from '/@/components/system/position-select/index.vue';
|
||||
import SmartEnumSelect from '/@/components/framework/smart-enum-select/index.vue';
|
||||
import { loginApi } from '/@/api/system/login-api.js';
|
||||
import { useUserStore } from '/@/store/modules/system/user.js';
|
||||
import { loginApi } from '/@/api/system/login-api';
|
||||
import { useUserStore } from '/@/store/modules/system/user';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { smartSentry } from '/@/lib/smart-sentry.js';
|
||||
import { smartSentry } from '/@/lib/smart-sentry';
|
||||
import { employeeApi } from '/@/api/system/employee-api';
|
||||
import { SmartLoading } from '/@/components/framework/smart-loading/index.js';
|
||||
import { fileApi } from '/@/api/support/file-api.js';
|
||||
import { FILE_FOLDER_TYPE_ENUM } from '/@/constants/support/file-const.js';
|
||||
import { SmartLoading } from '/@/components/framework/smart-loading/index';
|
||||
import { fileApi } from '/@/api/support/file-api';
|
||||
import { FILE_FOLDER_TYPE_ENUM } from '/@/constants/support/file-const';
|
||||
|
||||
// 组件ref
|
||||
const formRef = ref();
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
</template>
|
||||
<script setup>
|
||||
import { reactive, ref } from 'vue';
|
||||
import { messageApi } from '/@/api/support/message-api.js';
|
||||
import { useUserStore } from '/@/store/modules/system/user.js';
|
||||
import { messageApi } from '/@/api/support/message-api';
|
||||
import { useUserStore } from '/@/store/modules/system/user';
|
||||
|
||||
const emit = defineEmits(['refresh']);
|
||||
|
||||
|
||||
@@ -86,7 +86,7 @@
|
||||
import { messageApi } from '/@/api/support/message-api';
|
||||
import { PAGE_SIZE, PAGE_SIZE_OPTIONS } from '/@/constants/common-const';
|
||||
import SmartEnumSelect from '/@/components/framework/smart-enum-select//index.vue';
|
||||
import { smartSentry } from '/@/lib/smart-sentry.js';
|
||||
import { smartSentry } from '/@/lib/smart-sentry';
|
||||
import MessageDetail from './components/message-detail.vue';
|
||||
|
||||
const columns = reactive([
|
||||
|
||||
@@ -20,9 +20,9 @@
|
||||
<script setup>
|
||||
import { computed, onMounted, reactive, ref } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { SmartLoading } from '/@/components/framework/smart-loading/index.js';
|
||||
import { employeeApi } from '/@/api/system/employee-api.js';
|
||||
import { smartSentry } from '/@/lib/smart-sentry.js';
|
||||
import { SmartLoading } from '/@/components/framework/smart-loading/index';
|
||||
import { employeeApi } from '/@/api/system/employee-api';
|
||||
import { smartSentry } from '/@/lib/smart-sentry';
|
||||
|
||||
const emits = defineEmits(['onSuccess']);
|
||||
|
||||
|
||||
@@ -21,9 +21,9 @@
|
||||
<script setup>
|
||||
import { computed, onMounted, ref, watch } from 'vue';
|
||||
import _ from 'lodash';
|
||||
import { ACCOUNT_MENU } from '/@/views/system/account/account-menu.js';
|
||||
import { ACCOUNT_MENU } from '/@/views/system/account/account-menu';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useUserStore } from '/@/store/modules/system/user.js';
|
||||
import { useUserStore } from '/@/store/modules/system/user';
|
||||
import { theme as antDesignTheme } from 'ant-design-vue';
|
||||
|
||||
// 菜单展示
|
||||
|
||||
@@ -8,9 +8,10 @@
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
-->
|
||||
<template>
|
||||
<a-modal :open="visible" width="600px" :bodyStyle="{height:'360px'}" title="" :closable="false" :maskClosable="true">
|
||||
<a-modal :open="visible" width="600px" :bodyStyle="{height:'400px'}" title="" :closable="false" :maskClosable="true">
|
||||
<a-row><div style="font-weight:bolder;margin: 0 auto;font-size: 16px;color: red;">重磅更新:国产数据库支持🎉🎉</div> </a-row>
|
||||
<a-row><div style="font-weight:bolder;margin: 10px auto;font-size: 16px;color: red;">支持:达梦、人大金仓、华为高斯GaussDB 等🎉🎉</div> </a-row>
|
||||
<a-row><div style="font-weight:bolder;margin: 10px auto;font-size: 16px;color: red;">国产数据库:达梦、人大金仓、华为高斯GaussDB 等🎉🎉</div> </a-row>
|
||||
<a-row><div style="font-weight:bolder;margin: 0 auto;font-size: 16px;color: red;">主流数据库:Mysql、PostgreSQL 等🎉🎉</div> </a-row>
|
||||
<br />
|
||||
<div class="app-qr-box">
|
||||
<div class="app-qr">
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
import ToBeDoneModal from './to-be-done-modal.vue';
|
||||
import localKey from '/@/constants/local-storage-key-const';
|
||||
import { localRead, localSave } from '/@/utils/local-util';
|
||||
import { useUserStore } from '/@/store/modules/system/user.js';
|
||||
import { useUserStore } from '/@/store/modules/system/user';
|
||||
import { computed, ref, onMounted } from 'vue';
|
||||
import { Modal } from 'ant-design-vue';
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
.box-item {
|
||||
width: 444px;
|
||||
width: 464px;
|
||||
height: 600px;
|
||||
&.desc {
|
||||
background: #003b94;
|
||||
@@ -108,7 +108,7 @@
|
||||
transform: translateX(0);
|
||||
}
|
||||
100% {
|
||||
transform: translateX(-30%);
|
||||
transform: translateX(-5%);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,7 +119,7 @@
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
animation: marquee 5s linear infinite;
|
||||
animation: marquee 3s linear infinite;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -151,7 +151,7 @@
|
||||
.code-btn{
|
||||
height: 44px;
|
||||
padding: 4px 5px;
|
||||
width: 108px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.eye-box {
|
||||
@@ -165,7 +165,7 @@
|
||||
}
|
||||
}
|
||||
.btn {
|
||||
width: 350px;
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
background: #1748FD;
|
||||
border-radius: 4px;
|
||||
|
||||
@@ -46,7 +46,8 @@
|
||||
<img :src="gzh" />
|
||||
<div class="qr-desc-marquee">
|
||||
<div class="marquee">
|
||||
<span>关注:六边形工程师,分享:赚钱、代码、生活</span>
|
||||
<span>关注:六边形工程师</span>
|
||||
<span>分享:AI、赚钱、代码、健康</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -68,24 +69,33 @@
|
||||
</a-input-group>
|
||||
</a-form-item>
|
||||
<a-form-item name="password">
|
||||
<a-input-password
|
||||
v-model:value="loginForm.password"
|
||||
autocomplete="on"
|
||||
:type="showPassword ? 'text' : 'password'"
|
||||
placeholder="请输入密码"
|
||||
/>
|
||||
<a-popover placement="top">
|
||||
<template #content>
|
||||
<a-flex :vertical="true" justify="center" align="center">
|
||||
<img :src="gzh" />
|
||||
<a-typography-text type="danger">扫码关注:【六边形工程师】</a-typography-text>
|
||||
<a-typography-text type="danger">完成问卷调查,获取登录密码</a-typography-text>
|
||||
</a-flex>
|
||||
</template>
|
||||
<a-input-password
|
||||
v-model:value="loginForm.password"
|
||||
autocomplete="on"
|
||||
:type="showPassword ? 'text' : 'password'"
|
||||
placeholder="请输入密码"
|
||||
/>
|
||||
</a-popover>
|
||||
</a-form-item>
|
||||
<a-form-item name="captchaCode">
|
||||
<a-input class="captcha-input" v-model:value.trim="loginForm.captchaCode" placeholder="请输入验证码" />
|
||||
<img class="captcha-img" :src="captchaBase64Image" @click="getCaptcha" />
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-checkbox v-model:checked="rememberPwd">记住密码</a-checkbox>
|
||||
<span> ( 账号:admin, 密码:123456)</span>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
<div class="btn" @click="onLogin">登录</div>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<span> 账号:admin, 关注【六边形工程师】,参与问卷,获取密码</span>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<div class="more">
|
||||
<div class="title-box">
|
||||
@@ -129,10 +139,10 @@
|
||||
import { smartSentry } from '/@/lib/smart-sentry';
|
||||
import { encryptData } from '/@/lib/encrypt';
|
||||
import { h } from 'vue';
|
||||
import { localSave } from '/@/utils/local-util.js';
|
||||
import LocalStorageKeyConst from '/@/constants/local-storage-key-const.js';
|
||||
import { useDictStore } from '/@/store/modules/system/dict.js';
|
||||
import {dictApi} from "/@/api/support/dict-api.js";
|
||||
import { localSave } from '/@/utils/local-util';
|
||||
import LocalStorageKeyConst from '/@/constants/local-storage-key-const';
|
||||
import { useDictStore } from '/@/store/modules/system/dict';
|
||||
import { dictApi } from '/@/api/support/dict-api';
|
||||
|
||||
//--------------------- 登录表单 ---------------------------------
|
||||
|
||||
@@ -221,6 +231,7 @@
|
||||
//--------------------- 验证码 ---------------------------------
|
||||
|
||||
const captchaBase64Image = ref('');
|
||||
|
||||
async function getCaptcha() {
|
||||
try {
|
||||
let captchaResult = await loginApi.getCaptcha();
|
||||
@@ -233,6 +244,7 @@
|
||||
}
|
||||
|
||||
let refreshCaptchaInterval = null;
|
||||
|
||||
function beginRefreshCaptchaInterval(expireSeconds) {
|
||||
if (refreshCaptchaInterval === null) {
|
||||
refreshCaptchaInterval = setInterval(getCaptcha, (expireSeconds - 5) * 1000);
|
||||
@@ -258,6 +270,7 @@
|
||||
let emailCodeButtonDisabled = ref(false);
|
||||
// 定时器
|
||||
let countDownTimer = null;
|
||||
|
||||
// 开始倒计时
|
||||
function runCountDown() {
|
||||
emailCodeButtonDisabled.value = true;
|
||||
|
||||
@@ -90,10 +90,10 @@
|
||||
import { buildRoutes } from '/@/router/index';
|
||||
import { smartSentry } from '/@/lib/smart-sentry';
|
||||
import { encryptData } from '/@/lib/encrypt';
|
||||
import { localSave } from '/@/utils/local-util.js';
|
||||
import LocalStorageKeyConst from '/@/constants/local-storage-key-const.js';
|
||||
import { useDictStore } from '/@/store/modules/system/dict.js';
|
||||
import { dictApi } from '/@/api/support/dict-api.js';
|
||||
import { localSave } from '/@/utils/local-util';
|
||||
import LocalStorageKeyConst from '/@/constants/local-storage-key-const';
|
||||
import { useDictStore } from '/@/store/modules/system/dict';
|
||||
import { dictApi } from '/@/api/support/dict-api';
|
||||
|
||||
//--------------------- 登录表单 ---------------------------------
|
||||
|
||||
|
||||
@@ -92,10 +92,10 @@
|
||||
import { buildRoutes } from '/@/router/index';
|
||||
import { smartSentry } from '/@/lib/smart-sentry';
|
||||
import { encryptData } from '/@/lib/encrypt';
|
||||
import { localSave } from '/@/utils/local-util.js';
|
||||
import LocalStorageKeyConst from '/@/constants/local-storage-key-const.js';
|
||||
import { useDictStore } from '/@/store/modules/system/dict.js';
|
||||
import { dictApi } from '/@/api/support/dict-api.js';
|
||||
import { localSave } from '/@/utils/local-util';
|
||||
import LocalStorageKeyConst from '/@/constants/local-storage-key-const';
|
||||
import { useDictStore } from '/@/store/modules/system/dict';
|
||||
import { dictApi } from '/@/api/support/dict-api';
|
||||
|
||||
//--------------------- 登录表单 ---------------------------------
|
||||
|
||||
|
||||
@@ -9,91 +9,96 @@
|
||||
*/
|
||||
import { resolve } from 'path';
|
||||
import vue from '@vitejs/plugin-vue';
|
||||
import customVariables from '/@/theme/custom-variables.js';
|
||||
import { loadEnv } from 'vite';
|
||||
import customVariables from '/@/theme/custom-variables';
|
||||
|
||||
const pathResolve = (dir) => {
|
||||
return resolve(__dirname, '.', dir);
|
||||
};
|
||||
export default {
|
||||
base: process.env.NODE_ENV === 'production' ? '/' : '/',
|
||||
root: process.cwd(),
|
||||
resolve: {
|
||||
alias: [
|
||||
// 国际化替换
|
||||
{
|
||||
find: 'vue-i18n',
|
||||
replacement: 'vue-i18n/dist/vue-i18n.cjs.js',
|
||||
},
|
||||
// 绝对路径重命名:/@/xxxx => src/xxxx
|
||||
{
|
||||
find: /\/@\//,
|
||||
replacement: pathResolve('src') + '/',
|
||||
},
|
||||
{
|
||||
find: /^~/,
|
||||
replacement: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
server: {
|
||||
host: '0.0.0.0',
|
||||
port: 8081,
|
||||
|
||||
export default ({ mode }) => {
|
||||
const env = loadEnv(mode, process.cwd());
|
||||
return {
|
||||
base: process.env.NODE_ENV === 'production' ? '/' : '/',
|
||||
root: process.cwd(),
|
||||
resolve: {
|
||||
alias: [
|
||||
// 国际化替换
|
||||
{
|
||||
find: 'vue-i18n',
|
||||
replacement: 'vue-i18n/dist/vue-i18n.cjs.js',
|
||||
},
|
||||
// 绝对路径重命名:/@/xxxx => src/xxxx
|
||||
{
|
||||
find: /\/@\//,
|
||||
replacement: pathResolve('src') + '/',
|
||||
},
|
||||
{
|
||||
find: /^~/,
|
||||
replacement: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
server: {
|
||||
proxy: {
|
||||
// 代理路径
|
||||
'/': {
|
||||
target: 'http://127.0.0.1:1024/', // 目标服务器地址
|
||||
changeOrigin: true, // 是否修改请求头中的 Origin 字段
|
||||
rewrite: (path) => path, // 重写路径
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
plugins: [vue()],
|
||||
optimizeDeps: {
|
||||
include: ['ant-design-vue/es/locale/zh_CN', 'dayjs/locale/zh-cn', 'ant-design-vue/es/locale/en_US'],
|
||||
exclude: ['vue-demi'],
|
||||
},
|
||||
build: {
|
||||
// 清除console和debugger
|
||||
terserOptions: {
|
||||
compress: {
|
||||
drop_console: true,
|
||||
drop_debugger: true,
|
||||
},
|
||||
},
|
||||
rollupOptions: {
|
||||
output: {
|
||||
//配置这个是让不同类型文件放在不同文件夹,不会显得太乱
|
||||
chunkFileNames: 'js/[name]-[hash].js',
|
||||
entryFileNames: 'js/[name]-[hash].js',
|
||||
assetFileNames: '[ext]/[name]-[hash].[ext]',
|
||||
manualChunks(id) {
|
||||
//静态资源分拆打包
|
||||
if (id.includes('node_modules')) {
|
||||
return id.toString().split('node_modules/')[1].split('/')[0].toString();
|
||||
}
|
||||
host: '0.0.0.0',
|
||||
port: 8081,
|
||||
server: {
|
||||
proxy: {
|
||||
// 代理路径
|
||||
'/': {
|
||||
target: env.VITE_APP_API_URL, // 目标服务器地址
|
||||
changeOrigin: true, // 是否修改请求头中的 Origin 字段
|
||||
rewrite: (path) => path, // 重写路径
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
target: 'esnext',
|
||||
outDir: 'dist', // 指定输出路径
|
||||
assetsDir: 'assets', // 指定生成静态文件目录
|
||||
assetsInlineLimit: '4096', // 小于此阈值的导入或引用资源将内联为 base64 编码
|
||||
chunkSizeWarningLimit: 500, // chunk 大小警告的限制
|
||||
minify: 'terser', // 混淆器,terser构建后文件体积更小
|
||||
emptyOutDir: true, //打包前先清空原有打包文件
|
||||
},
|
||||
css: {
|
||||
preprocessorOptions: {
|
||||
less: {
|
||||
modifyVars: customVariables,
|
||||
javascriptEnabled: true,
|
||||
plugins: [vue()],
|
||||
optimizeDeps: {
|
||||
include: ['ant-design-vue/es/locale/zh_CN', 'dayjs/locale/zh-cn', 'ant-design-vue/es/locale/en_US'],
|
||||
exclude: ['vue-demi'],
|
||||
},
|
||||
build: {
|
||||
// 清除console和debugger
|
||||
terserOptions: {
|
||||
compress: {
|
||||
drop_console: true,
|
||||
drop_debugger: true,
|
||||
},
|
||||
},
|
||||
rollupOptions: {
|
||||
output: {
|
||||
//配置这个是让不同类型文件放在不同文件夹,不会显得太乱
|
||||
chunkFileNames: 'js/[name]-[hash].js',
|
||||
entryFileNames: 'js/[name]-[hash].js',
|
||||
assetFileNames: '[ext]/[name]-[hash].[ext]',
|
||||
manualChunks(id) {
|
||||
//静态资源分拆打包
|
||||
if (id.includes('node_modules')) {
|
||||
return id.toString().split('node_modules/')[1].split('/')[0].toString();
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
target: 'esnext',
|
||||
outDir: 'dist', // 指定输出路径
|
||||
assetsDir: 'assets', // 指定生成静态文件目录
|
||||
assetsInlineLimit: '4096', // 小于此阈值的导入或引用资源将内联为 base64 编码
|
||||
chunkSizeWarningLimit: 500, // chunk 大小警告的限制
|
||||
minify: 'terser', // 混淆器,terser构建后文件体积更小
|
||||
emptyOutDir: true, //打包前先清空原有打包文件
|
||||
},
|
||||
css: {
|
||||
preprocessorOptions: {
|
||||
less: {
|
||||
modifyVars: customVariables,
|
||||
javascriptEnabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
define: {
|
||||
__INTLIFY_PROD_DEVTOOLS__: false,
|
||||
'process.env': process.env,
|
||||
},
|
||||
define: {
|
||||
__INTLIFY_PROD_DEVTOOLS__: false,
|
||||
'process.env': process.env,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -17,14 +17,14 @@ module.exports = {
|
||||
ecmaVersion: 12, // 默认情况下,ESLint使用的是ECMAScript5语法,此处我们设置的选项是 es12
|
||||
sourceType: 'module', // 指定js导入的方式
|
||||
},
|
||||
extends: ['plugin:vue/vue3-essential', 'eslint:recommended', 'plugin:vue/base'],
|
||||
extends: ['plugin:vue/vue3-essential', 'eslint:recommended', 'plugin:vue/base', 'prettier'],
|
||||
globals: {
|
||||
defineProps: 'readonly',
|
||||
defineEmits: 'readonly',
|
||||
defineExpose: 'readonly',
|
||||
withDefaults: 'readonly',
|
||||
},
|
||||
plugins: ['vue'],
|
||||
plugins: ['vue', 'prettier'],
|
||||
rules: {
|
||||
'no-unused-vars': [
|
||||
'error',
|
||||
@@ -62,5 +62,6 @@ module.exports = {
|
||||
],
|
||||
// Enable vue/script-setup-uses-vars rule
|
||||
'vue/script-setup-uses-vars': 'error',
|
||||
'prettier/prettier': 'error',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -6,12 +6,16 @@
|
||||
* @LastEditors: zhuoda
|
||||
-->
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html lang="en" id="htmlRoot">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title> %VITE_APP_TITLE%</title>
|
||||
<meta charset="UTF-8"/>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
|
||||
<meta name="renderer" content="webkit"/>
|
||||
<meta name="viewport"
|
||||
content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=0"
|
||||
/>
|
||||
<title> %VITE_APP_TITLE%</title>
|
||||
<link rel="icon" href="/favicon.ico"/>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
"axios": "1.6.8",
|
||||
"clipboard": "2.0.11",
|
||||
"crypto-js": "4.1.1",
|
||||
"dayjs": "1.11.13",
|
||||
"dayjs": "1.10.5",
|
||||
"decimal.js": "10.3.1",
|
||||
"default-passive-events": "^2.0.0",
|
||||
"diff": "5.2.0",
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
import { useAppConfigStore } from '/@/store/modules/system/app-config';
|
||||
import { useSpinStore } from '/@/store/modules/system/spin';
|
||||
import { Popover, theme } from 'ant-design-vue';
|
||||
import { themeColors } from '/@/theme/color.js';
|
||||
import { themeColors } from '/@/theme/color';
|
||||
import SmartCopyIcon from '/@/components/framework/smart-copy-icon/index.vue';
|
||||
|
||||
const antdLocale = computed(() => messages[useAppConfigStore().language].antdLocale);
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { useDictStore } from '/@/store/modules/system/dict.js';
|
||||
import { useDictStore } from '/@/store/modules/system/dict';
|
||||
|
||||
const props = defineProps({
|
||||
value: [Array, String, Number],
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user