Compare commits

..

No commits in common. "master" and "v3.25.0" have entirely different histories.

141 changed files with 343 additions and 869 deletions

View File

@ -4,9 +4,9 @@
**<font color="#DC143C">国内首个满足《网络安全-三级等保》、《数据安全》</font>** 功能要求,支持登录限制、接口国产加解密、数据脱敏等一系列安全要求。 **<font color="#DC143C">国内首个满足《网络安全-三级等保》、《数据安全》</font>** 功能要求,支持登录限制、接口国产加解密、数据脱敏等一系列安全要求。
**<font color="#DC143C">支持国产数据库:达梦、金仓、南大通用、海量数据、神州通用、OceanBase、GaussDB 高斯、阿里PolarDB、GoldenDB】等主流数据库【Mysql, PostgreSQL】等</font>** **<font color="#DC143C">支持国产数据库达梦、金仓、南大通用、OceanBase、GaussDB 高斯、阿里PolarDB、GoldenDB</font>**
**<font color="#DC143C">前端提供JavaScript和TypeScript双版本后端提供Java8+SpringBoot2.X和Java17+SpringBoot3.X 双版本</font>**。 前端提供 **<font color="#DC143C">JavaScript和TypeScript双版本</font>**,后端提供 **<font color="#DC143C">Java8+SpringBoot2.X和Java17+SpringBoot3.X 双版本</font>**。
同时 **<font color="#DC143C">重磅开源</font>** 开源六年来 **<font color="#DC143C">千余家企业验证过且正在使用</font>** 的代码规范: **<font color="#DC143C">《高质量代码思想》、《Vue3规范》、《Java规范》</font>** ,让大家在这浮躁的世界里感受到一股把代码写好的清流!同时又能节省大量时间,减少加班,快乐工作,保持谦逊,保持学习,**<font color="#DC143C">热爱代码,更热爱生活</font>** 同时 **<font color="#DC143C">重磅开源</font>** 开源六年来 **<font color="#DC143C">千余家企业验证过且正在使用</font>** 的代码规范: **<font color="#DC143C">《高质量代码思想》、《Vue3规范》、《Java规范》</font>** ,让大家在这浮躁的世界里感受到一股把代码写好的清流!同时又能节省大量时间,减少加班,快乐工作,保持谦逊,保持学习,**<font color="#DC143C">热爱代码,更热爱生活</font>**
### **技术体系** ### **技术体系**

View File

@ -67,7 +67,7 @@ public class AdminInterceptor implements HandlerInterceptor {
Method method = ((HandlerMethod) handler).getMethod(); Method method = ((HandlerMethod) handler).getMethod();
NoNeedLogin noNeedLogin = ((HandlerMethod) handler).getMethodAnnotation(NoNeedLogin.class); NoNeedLogin noNeedLogin = ((HandlerMethod) handler).getMethodAnnotation(NoNeedLogin.class);
if (noNeedLogin != null) { if (noNeedLogin != null) {
updateActiveTimeout(requestEmployee); checkActiveTimeout(requestEmployee);
SmartRequestUtil.setRequestUser(requestEmployee); SmartRequestUtil.setRequestUser(requestEmployee);
return true; return true;
} }
@ -77,8 +77,8 @@ public class AdminInterceptor implements HandlerInterceptor {
return false; return false;
} }
// 更新活跃 // 检测token 活跃频率
updateActiveTimeout(requestEmployee); checkActiveTimeout(requestEmployee);
// --------------- 第三步 校验 权限 --------------- // --------------- 第三步 校验 权限 ---------------
@ -123,12 +123,15 @@ public class AdminInterceptor implements HandlerInterceptor {
/** /**
* 更新活跃时间 * 检测token 最低活跃频率单位如果 token 超过此时间没有访问系统就会被冻结
*/ */
private void updateActiveTimeout(RequestEmployee requestEmployee) { private void checkActiveTimeout(RequestEmployee requestEmployee) {
// 用户不在线也不用检测
if (requestEmployee == null) { if (requestEmployee == null) {
return; return;
} }
StpUtil.checkActiveTimeout();
StpUtil.updateLastActiveToNow(); StpUtil.updateLastActiveToNow();
} }

View File

@ -153,19 +153,8 @@ public class LoginManager {
} }
/** @CacheEvict(value = {AdminCacheConst.Login.USER_PERMISSION, AdminCacheConst.Login.REQUEST_EMPLOYEE}, allEntries = true)
* 清除用户权限 public void clear(){
*/
@CacheEvict(value = AdminCacheConst.Login.USER_PERMISSION)
public void clearUserPermission(Long employeeId) {
}
/**
* 清除用户登录信息
*/
@CacheEvict(value = AdminCacheConst.Login.REQUEST_EMPLOYEE)
public void clearUserLoginInfo(Long employeeId) {
} }

View File

@ -317,8 +317,8 @@ public class LoginService implements StpInterface {
// sa token 登出 // sa token 登出
StpUtil.logout(); StpUtil.logout();
// 除用户登录信息缓存和权限信息 // 空登录信息缓存
this.clearLoginEmployeeCache(requestUser.getUserId()); loginManager.clear();
//保存登出日志 //保存登出日志
LoginLogEntity loginEntity = LoginLogEntity.builder() LoginLogEntity loginEntity = LoginLogEntity.builder()
@ -474,7 +474,6 @@ public class LoginService implements StpInterface {
} }
public void clearLoginEmployeeCache(Long employeeId) { public void clearLoginEmployeeCache(Long employeeId) {
loginManager.clearUserPermission(employeeId); loginManager.clear();
loginManager.clearUserLoginInfo(employeeId);
} }
} }

View File

@ -20,25 +20,17 @@ public class LongJsonSerializer extends JsonSerializer<Long> {
public static final LongJsonSerializer INSTANCE = new LongJsonSerializer(); public static final LongJsonSerializer INSTANCE = new LongJsonSerializer();
/**
* JS 安全整数范围
* 根据 JS Number.MIN_SAFE_INTEGER Number.MAX_SAFE_INTEGER 得来
*/
private static final long JS_MIN_SAFE_INTEGER = -9007199254740991L;
private static final long JS_MAX_SAFE_INTEGER = 9007199254740991L;
@Override @Override
public void serialize(Long value, JsonGenerator gen, SerializerProvider serializerProvider) throws IOException, JsonProcessingException { public void serialize(Long value, JsonGenerator gen, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
if (null == value) { if (null == value) {
gen.writeNull(); gen.writeNull();
return; return;
} }
// 如果超出了 JavaScript 安全整数范围则序列化为字符串 // js中最大安全整数16位 Number.MAX_SAFE_INTEGER
if (value < JS_MIN_SAFE_INTEGER || value > JS_MAX_SAFE_INTEGER) { String longStr = String.valueOf(value);
gen.writeString(Long.toString(value)); if (longStr.length() > 16) {
gen.writeString(longStr);
} else { } else {
// 否则序列化为数字
gen.writeNumber(value); gen.writeNumber(value);
} }
} }

View File

@ -5,19 +5,18 @@ import jakarta.annotation.Resource;
import net.lab1024.sa.base.module.support.cache.CacheService; import net.lab1024.sa.base.module.support.cache.CacheService;
import net.lab1024.sa.base.module.support.cache.CaffeineCacheServiceImpl; import net.lab1024.sa.base.module.support.cache.CaffeineCacheServiceImpl;
import net.lab1024.sa.base.module.support.cache.RedisCacheServiceImpl; import net.lab1024.sa.base.module.support.cache.RedisCacheServiceImpl;
import net.lab1024.sa.base.module.support.redis.CustomRedisCacheManager;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration; import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.RedisSerializationContext; import org.springframework.data.redis.serializer.RedisSerializationContext;
/** /**
* 缓存配置 * 缓存配置
* *
* @author zhoumingfa
* @date 2025/03/28
*/ */
@Configuration @Configuration
public class CacheConfig { public class CacheConfig {
@ -27,44 +26,27 @@ public class CacheConfig {
@Resource @Resource
private RedisConnectionFactory redisConnectionFactory; private RedisConnectionFactory factory;
/**
* 创建自定义Redis缓存管理器Bean 整合spring-cache
* Redis连接工厂用于建立与Redis服务器的连接
*
* @return CacheManager Redis缓存管理器实例
*/
@Bean @Bean
@ConditionalOnProperty(prefix = "spring.cache", name = {"type"}, havingValue = REDIS_CACHE) @ConditionalOnProperty(prefix = "spring.cache", name = {"type"}, havingValue = REDIS_CACHE)
public CacheManager cacheManager() { public RedisCacheConfiguration redisCacheConfiguration() {
// 使用非阻塞模式的缓存写入器适用于大多数高并发场景 return RedisCacheConfiguration.defaultCacheConfig()
RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory);
// 构建默认缓存配置
RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig()
// 禁止缓存 null 避免缓存穿透
.disableCachingNullValues() .disableCachingNullValues()
.computePrefixWith(name -> "cache:" + name + ":") .computePrefixWith(name -> "cache:" + name + ":")
// 使用 FastJSON 序列化缓存值支持复杂对象 .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericFastJsonRedisSerializer()));
.serializeValuesWith(RedisSerializationContext.SerializationPair
.fromSerializer(new GenericFastJsonRedisSerializer()));
// 返回自定义缓存管理器支持 cacheName#ttl 格式与永久缓存#-1
return new CustomRedisCacheManager(redisCacheWriter, defaultCacheConfig);
} }
@Bean
@ConditionalOnProperty(prefix = "spring.cache", name = {"type"}, havingValue = REDIS_CACHE)
public CacheService redisCacheService() {
return new RedisCacheServiceImpl();
}
@Bean @Bean
@ConditionalOnProperty(prefix = "spring.cache", name = {"type"}, havingValue = REDIS_CACHE) @ConditionalOnProperty(prefix = "spring.cache", name = {"type"}, havingValue = CAFFEINE_CACHE)
public CacheService redisCacheService() { public CacheService caffeineCacheService() {
return new RedisCacheServiceImpl(); return new CaffeineCacheServiceImpl();
} }
@Bean
@ConditionalOnProperty(prefix = "spring.cache", name = {"type"}, havingValue = CAFFEINE_CACHE)
public CacheService caffeineCacheService() {
return new CaffeineCacheServiceImpl();
}
} }

View File

@ -81,7 +81,7 @@ public class FileConfig implements WebMvcConfigurer {
StaticCredentialsProvider.create( StaticCredentialsProvider.create(
AwsBasicCredentials.create(accessKey, secretKey))) AwsBasicCredentials.create(accessKey, secretKey)))
.serviceConfiguration(S3Configuration.builder() .serviceConfiguration(S3Configuration.builder()
.pathStyleAccessEnabled(true) .pathStyleAccessEnabled(false)
.chunkedEncodingEnabled(false) .chunkedEncodingEnabled(false)
.build()) .build())
.build(); .build();

View File

@ -2,7 +2,6 @@ package net.lab1024.sa.base.config;
import cn.hutool.core.date.DatePattern; import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.LocalDateTimeUtil; import cn.hutool.core.date.LocalDateTimeUtil;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer; import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer; import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
@ -14,8 +13,6 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.Converter;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.format.DateTimeParseException; import java.time.format.DateTimeParseException;
@ -40,9 +37,6 @@ public class JsonConfig {
builder.serializers(new LocalDateSerializer(DatePattern.NORM_DATE_FORMAT.getDateTimeFormatter())); builder.serializers(new LocalDateSerializer(DatePattern.NORM_DATE_FORMAT.getDateTimeFormatter()));
builder.serializers(new LocalDateTimeSerializer(DatePattern.NORM_DATETIME_FORMAT.getDateTimeFormatter())); builder.serializers(new LocalDateTimeSerializer(DatePattern.NORM_DATETIME_FORMAT.getDateTimeFormatter()));
builder.serializerByType(Long.class, LongJsonSerializer.INSTANCE); builder.serializerByType(Long.class, LongJsonSerializer.INSTANCE);
builder.serializerByType(Long.TYPE, LongJsonSerializer.INSTANCE);
builder.serializerByType(BigInteger.class, ToStringSerializer.instance);
builder.serializerByType(BigDecimal.class, ToStringSerializer.instance);
}; };
} }

View File

@ -30,7 +30,7 @@ public class DictDataVO implements Serializable {
private String dictName; private String dictName;
@Schema(description = "字典禁用状态") @Schema(description = "字典禁用状态")
private Boolean dictDisabledFlag; private Integer dictDisabledFlag;
@Schema(description = "字典项值") @Schema(description = "字典项值")
private String dataValue; private String dataValue;

View File

@ -30,7 +30,7 @@ public class DictVO {
private String remark; private String remark;
@Schema(description = "禁用状态") @Schema(description = "禁用状态")
private Boolean disabledFlag; private Integer disabledFlag;
@Schema(description = "创建时间") @Schema(description = "创建时间")
private LocalDateTime createTime; private LocalDateTime createTime;

View File

@ -101,18 +101,7 @@ public class FileStorageCloudServiceImpl implements IFileStorageService {
userMetadata.put(USER_METADATA_FILE_FORMAT, fileType); userMetadata.put(USER_METADATA_FILE_FORMAT, fileType);
userMetadata.put(USER_METADATA_FILE_SIZE, String.valueOf(file.getSize())); userMetadata.put(USER_METADATA_FILE_SIZE, String.valueOf(file.getSize()));
// 根据文件路径获取并设置访问权限 PutObjectRequest putObjectRequest = PutObjectRequest.builder().bucket(cloudConfig.getBucketName()).key(fileKey).metadata(userMetadata).contentLength(file.getSize()).contentType(this.getContentType(fileType)).contentEncoding(StandardCharsets.UTF_8.name()).contentDisposition("attachment;filename=" + urlEncoderFilename).build();
ObjectCannedACL acl = this.getACL(path);
PutObjectRequest putObjectRequest = PutObjectRequest.builder()
.bucket(cloudConfig.getBucketName())
.key(fileKey)
.metadata(userMetadata)
.contentLength(file.getSize())
.contentType(this.getContentType(fileType))
.contentEncoding(StandardCharsets.UTF_8.name())
.contentDisposition("attachment;filename=" + urlEncoderFilename)
.acl(acl)
.build();
InputStream inputStream = null; InputStream inputStream = null;
try { try {
inputStream = file.getInputStream(); inputStream = file.getInputStream();
@ -123,6 +112,10 @@ public class FileStorageCloudServiceImpl implements IFileStorageService {
} finally { } finally {
IOUtils.closeQuietly(inputStream); IOUtils.closeQuietly(inputStream);
} }
// 根据文件路径获取并设置访问权限
ObjectCannedACL acl = this.getACL(path);
PutObjectAclRequest aclRequest = PutObjectAclRequest.builder().bucket(cloudConfig.getBucketName()).key(fileKey).acl(this.getACL(path)).build();
s3Client.putObjectAcl(aclRequest);
// 返回上传结果 // 返回上传结果
FileUploadVO uploadVO = new FileUploadVO(); FileUploadVO uploadVO = new FileUploadVO();
uploadVO.setFileName(originalFileName); uploadVO.setFileName(originalFileName);

View File

@ -11,7 +11,6 @@ import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import net.lab1024.sa.base.common.constant.StringConst; import net.lab1024.sa.base.common.constant.StringConst;
import net.lab1024.sa.base.common.domain.RequestUser; import net.lab1024.sa.base.common.domain.RequestUser;
import net.lab1024.sa.base.common.domain.ResponseDTO;
import net.lab1024.sa.base.common.util.SmartIpUtil; import net.lab1024.sa.base.common.util.SmartIpUtil;
import net.lab1024.sa.base.common.util.SmartRequestUtil; import net.lab1024.sa.base.common.util.SmartRequestUtil;
import net.lab1024.sa.base.module.support.operatelog.OperateLogDao; import net.lab1024.sa.base.module.support.operatelog.OperateLogDao;
@ -47,7 +46,7 @@ import java.util.concurrent.ThreadPoolExecutor;
* @Date 2021-12-08 20:48:52 * @Date 2021-12-08 20:48:52
* @Wechat zhuoda1024 * @Wechat zhuoda1024
* @Email lab1024@163.com * @Email lab1024@163.com
* @Copyright <a href="https://1024lab.net">1024创新实验室</a> * @Copyright <a href="https://1024lab.net">1024创新实验室</a>
*/ */
@Slf4j @Slf4j
@Aspect @Aspect
@ -72,14 +71,14 @@ public abstract class OperateLogAspect {
public void logPointCut() { public void logPointCut() {
} }
@AfterReturning(pointcut = "logPointCut()", returning = "responseDTO") @AfterReturning(pointcut = "logPointCut()")
public void doAfterReturning(JoinPoint joinPoint, Object responseDTO) { public void doAfterReturning(JoinPoint joinPoint) {
handleLog(joinPoint, null, responseDTO); handleLog(joinPoint, null);
} }
@AfterThrowing(value = "logPointCut()", throwing = "e") @AfterThrowing(value = "logPointCut()", throwing = "e")
public void doAfterThrowing(JoinPoint joinPoint, Exception e) { public void doAfterThrowing(JoinPoint joinPoint, Exception e) {
handleLog(joinPoint, e, null); handleLog(joinPoint, e);
} }
/** /**
@ -110,15 +109,16 @@ public abstract class OperateLogAspect {
taskExecutor.setWaitForTasksToCompleteOnShutdown(true); taskExecutor.setWaitForTasksToCompleteOnShutdown(true);
} }
protected void handleLog(final JoinPoint joinPoint, final Exception e, Object responseDTO) { protected void handleLog(final JoinPoint joinPoint, final Exception e) {
try { try {
OperateLog operateLog = this.getAnnotationLog(joinPoint); OperateLog operateLog = this.getAnnotationLog(joinPoint);
if (operateLog == null) { if (operateLog == null) {
return; return;
} }
this.submitLog(joinPoint, e, responseDTO); this.submitLog(joinPoint, e);
} catch (Exception exp) { } catch (Exception exp) {
log.error("保存操作日志异常:{}", exp.getMessage()); log.error("保存操作日志异常:{}", exp.getMessage());
exp.printStackTrace();
} }
} }
@ -173,8 +173,11 @@ public abstract class OperateLogAspect {
/** /**
* 提交存储操作日志 * 提交存储操作日志
* *
* @param joinPoint
* @param e
* @throws Exception
*/ */
private void submitLog(final JoinPoint joinPoint, final Throwable e, Object responseDTO) { private void submitLog(final JoinPoint joinPoint, final Throwable e) throws Exception {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
//设置用户信息 //设置用户信息
RequestUser user = SmartRequestUtil.getRequestUser(); RequestUser user = SmartRequestUtil.getRequestUser();
@ -188,7 +191,7 @@ public abstract class OperateLogAspect {
String methodName = joinPoint.getSignature().getName(); String methodName = joinPoint.getSignature().getName();
String operateMethod = className + "." + methodName; String operateMethod = className + "." + methodName;
String failReason = null; String failReason = null;
boolean successFlag = true; Boolean successFlag = true;
if (e != null) { if (e != null) {
successFlag = false; successFlag = false;
failReason = getExceptionString(e); failReason = getExceptionString(e);
@ -207,32 +210,15 @@ public abstract class OperateLogAspect {
.userAgent(user.getUserAgent()) .userAgent(user.getUserAgent())
.failReason(failReason) .failReason(failReason)
.successFlag(successFlag).build(); .successFlag(successFlag).build();
Operation apiOperation = this.getApiOperation(joinPoint); Operation apiOperation = this.getApiOperation(joinPoint);
if (apiOperation != null) { if (apiOperation != null) {
operateLogEntity.setContent(apiOperation.summary()); operateLogEntity.setContent(apiOperation.summary());
} }
Tag api = this.getApi(joinPoint); Tag api = this.getApi(joinPoint);
if (api != null) { if (api != null) {
String name = api.name(); String name = api.name();
operateLogEntity.setModule(StrUtil.join(",", name)); operateLogEntity.setModule(StrUtil.join(",", name));
} }
// 处理返回值 ResponseDTO
if(responseDTO instanceof ResponseDTO) {
ResponseDTO response = (ResponseDTO) responseDTO;
ResponseDTO logResponseDTO = new ResponseDTO(
response.getCode(),
response.getLevel(),
response.getOk(),
response.getMsg(),
null
);
logResponseDTO.setDataType(response.getDataType());
operateLogEntity.setResponse(JSON.toJSONString(logResponseDTO));
}
taskExecutor.execute(() -> { taskExecutor.execute(() -> {
this.saveLog(operateLogEntity); this.saveLog(operateLogEntity);
}); });

View File

@ -71,11 +71,6 @@ public class OperateLogEntity {
*/ */
private String param; private String param;
/**
* 返回值
*/
private String response;
/** /**
* 客户ip * 客户ip
*/ */

View File

@ -47,9 +47,6 @@ public class OperateLogVO {
@Schema(description = "请求参数") @Schema(description = "请求参数")
private String param; private String param;
@Schema(description = "返回值")
private String response;
@Schema(description = "客户ip") @Schema(description = "客户ip")
private String ip; private String ip;

View File

@ -1,145 +0,0 @@
package net.lab1024.sa.base.module.support.redis;
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.convert.DurationStyle;
import org.springframework.data.redis.cache.*;
import java.time.Duration;
import static net.lab1024.sa.base.common.constant.StringConst.COLON;
/**
* 自定义 RedisCacheManager支持在 cacheName 中通过 '#' 指定 TTL过期时间
*
* @Author CoderKK
* @Date 2025-08-15 13:01:01
* <p>
* 支持格式{@code cacheName#ttl}其中 ttl 支持 Spring Duration 格式
* 特殊值{@code -1} 表示永久缓存永不过期
* </p>
*
* <h3>使用示例</h3>
* <pre>
* // 10 秒后过期
* &#64;Cacheable(value = "user#10s", key = "#id")
* // 2 小时后过期
* &#64;Cacheable(value = "report#2h", key = "#date")
* // 30 分钟后过期
* &#64;Cacheable(value = "session#30m", key = "#token")
* // 永不过期永久缓存适用于极少变化的配置数据
* &#64;Cacheable(value = "appConfig#-1", key = "'globalSettings'")
* // TTL使用全局默认过期时间 7
* &#64;Cacheable(value = "product", key = "#productId")
* </pre>
*
* <h3>生成的 Redis Key 格式</h3>
* <pre>
* cache:cacheName:key
* 例如cache:user:123
* cache:appConfig:globalSettings
* </pre>
*
* <h3>支持的 TTL 单位</h3>
* <ul>
* <li>{@code ms} / {@code millis} / {@code milliseconds} - 毫秒</li>
* <li>{@code s} / {@code secs} / {@code seconds} - </li>
* <li>{@code m} / {@code mins} / {@code minutes} - 分钟</li>
* <li>{@code h} / {@code hrs} / {@code hours} - 小时</li>
* <li>{@code d} / {@code days} - </li>
* </ul>
*
* <h3>注意事项</h3>
* <ul>
* <li>不写单位默认为毫秒</li>
* <li>永久缓存#-1不会自动过期请配合 &#64;CacheEvict 手动清理</li>
* <li>避免对频繁更新的数据使用永久缓存防止数据陈旧</li>
* <li>cacheName 中的 '#' 只解析第一个后续字符将作为 TTL 处理</li>
* </ul>
*/
@Slf4j
public class CustomRedisCacheManager extends RedisCacheManager {
/**
* 缓存全局前缀
*/
private static final String CACHE_PREFIX = "cache";
/**
* 自定义 TTL 分隔符用于在 cacheName 后附加过期时间
*/
private static final String CUSTOM_TTL_SEPARATOR = "#";
/**
* 默认缓存过期时间7
*/
private static final Duration DEFAULT_TTL = Duration.ofDays(7);
public CustomRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration) {
super(cacheWriter, defaultCacheConfiguration);
}
/**
* 创建 RedisCache 实例支持从 cacheName 解析 TTL
*
* @param name 缓存名称支持 name#ttl 格式
* @param cacheConfig 默认缓存配置
* @return RedisCache
*/
@Override
protected RedisCache createRedisCache(String name, RedisCacheConfiguration cacheConfig) {
Duration ttl = parseTtlFromCacheName(name);
if (ttl == null) {
ttl = DEFAULT_TTL;
}
CacheKeyPrefix keyPrefix = cacheName -> {
if (StrUtil.isBlank(cacheName)) {
return CACHE_PREFIX + COLON;
}
String[] parts = cacheName.split(CUSTOM_TTL_SEPARATOR, 2);
String cleanName = StrUtil.trim(parts[0]);
return CACHE_PREFIX + COLON + cleanName + COLON;
};
// 构建最终缓存配置设置 key 前缀 + TTL
RedisCacheConfiguration config = cacheConfig.computePrefixWith(keyPrefix).entryTtl(ttl);
return super.createRedisCache(name, config);
}
/**
* cacheName 中解析 TTL
*
* @param name 缓存名称格式如users#10m, products#2h, config#-1永久
* @return 解析出的 Duration若无效则返回 null若为 -1则返回 Duration.ofMillis(-1) 表示永久缓存
*/
private Duration parseTtlFromCacheName(String name) {
if (StrUtil.isBlank(name)) {
return null;
}
String[] parts = name.split(CUSTOM_TTL_SEPARATOR, 2);
if (parts.length < 2) {
return null; // TTL 部分
}
String ttlStr = StrUtil.trim(parts[1]);
if (StrUtil.isBlank(ttlStr)) {
return null;
}
// 特殊处理-1 表示永久缓存
if ("-1".equals(ttlStr)) {
return Duration.ofMillis(-1); // Spring Redis 中负数 Duration 表示永不过期
}
try {
Duration ttl = DurationStyle.detectAndParse(ttlStr);
return ttl.getSeconds() > 0 ? ttl : null;
} catch (IllegalArgumentException e) {
log.error("解析缓存 TTL 失败cacheName='{}', ttl='{}', 错误: {}", name, ttlStr, e);
return null;
}
}
}

View File

@ -48,7 +48,7 @@
#end #end
#if($field.frontComponent == "DictSelect") #if($field.frontComponent == "DictSelect")
<a-form-item label="$codeGeneratorTool.removeEnumDesc($!{field.label})" name="${field.fieldName}"> <a-form-item label="$codeGeneratorTool.removeEnumDesc($!{field.label})" name="${field.fieldName}">
<DictSelect width="100%" v-model:value="form.${field.fieldName}" :dict-code="DICT_CODE_ENUM.$!{field.dict} || '$!{field.dict}'" placeholder="$!{field.label}"/> <DictSelect width="100%" v-model:value="form.${field.fieldName}" dict-code="DICT_CODE_ENUM.$!{field.dict} || '$!{field.dict}'" placeholder="$!{field.label}"/>
</a-form-item> </a-form-item>
#end #end
#if($field.frontComponent == "Date") #if($field.frontComponent == "Date")
@ -106,7 +106,7 @@
#end #end
#if($field.frontComponent == "DictSelect") #if($field.frontComponent == "DictSelect")
<a-form-item label="$codeGeneratorTool.removeEnumDesc($!{field.label})" name="${field.fieldName}"> <a-form-item label="$codeGeneratorTool.removeEnumDesc($!{field.label})" name="${field.fieldName}">
<DictSelect width="100%" v-model:value="form.${field.fieldName}" :dict-code="DICT_CODE_ENUM.$!{field.dict} || '$!{field.dict}'" placeholder="$!{field.label}"/> <DictSelect width="100%" v-model:value="form.${field.fieldName}" dict-code="DICT_CODE_ENUM.$!{field.dict} || '$!{field.dict}'" placeholder="$!{field.label}"/>
</a-form-item> </a-form-item>
#end #end
#if($field.frontComponent == "Date") #if($field.frontComponent == "Date")

View File

@ -22,7 +22,7 @@
#end #end
#if($field.queryTypeEnum == "Dict") #if($field.queryTypeEnum == "Dict")
<a-form-item label="${field.label}" class="smart-query-form-item"> <a-form-item label="${field.label}" class="smart-query-form-item">
<DictSelect :dict-code="DICT_CODE_ENUM.$!{field.dict} || '$!{field.dict}'" placeholder="${field.label}" v-model:value="queryForm.${field.fieldName}" width="${field.width}" /> <DictSelect dict-code="DICT_CODE_ENUM.$!{field.dict} || '$!{field.dict}'" placeholder="${field.label}" v-model:value="queryForm.${field.fieldName}" width="${field.width}" />
</a-form-item> </a-form-item>
#end #end
#if($field.queryTypeEnum == "Enum") #if($field.queryTypeEnum == "Enum")

View File

@ -48,7 +48,7 @@
#end #end
#if($field.frontComponent == "DictSelect") #if($field.frontComponent == "DictSelect")
<a-form-item label="$codeGeneratorTool.removeEnumDesc($!{field.label})" name="${field.fieldName}"> <a-form-item label="$codeGeneratorTool.removeEnumDesc($!{field.label})" name="${field.fieldName}">
<DictSelect width="100%" v-model:value="form.${field.fieldName}" :dict-code="DICT_CODE_ENUM.$!{field.dict} || '$!{field.dict}'" placeholder="$!{field.label}"/> <DictSelect width="100%" v-model:value="form.${field.fieldName}" dict-code="DICT_CODE_ENUM.$!{field.dict} || '$!{field.dict}'" placeholder="$!{field.label}"/>
</a-form-item> </a-form-item>
#end #end
#if($field.frontComponent == "Date") #if($field.frontComponent == "Date")
@ -106,7 +106,7 @@
#end #end
#if($field.frontComponent == "DictSelect") #if($field.frontComponent == "DictSelect")
<a-form-item label="$codeGeneratorTool.removeEnumDesc($!{field.label})" name="${field.fieldName}"> <a-form-item label="$codeGeneratorTool.removeEnumDesc($!{field.label})" name="${field.fieldName}">
<DictSelect width="100%" v-model:value="form.${field.fieldName}" :dict-code="DICT_CODE_ENUM.$!{field.dict} || '$!{field.dict}'" placeholder="$!{field.label}"/> <DictSelect width="100%" v-model:value="form.${field.fieldName}" dict-code="DICT_CODE_ENUM.$!{field.dict} || '$!{field.dict}'" placeholder="$!{field.label}"/>
</a-form-item> </a-form-item>
#end #end
#if($field.frontComponent == "Date") #if($field.frontComponent == "Date")

View File

@ -22,7 +22,7 @@
#end #end
#if($field.queryTypeEnum == "Dict") #if($field.queryTypeEnum == "Dict")
<a-form-item label="${field.label}" class="smart-query-form-item"> <a-form-item label="${field.label}" class="smart-query-form-item">
<DictSelect :dict-code="DICT_CODE_ENUM.$!{field.dict} || '$!{field.dict}'" placeholder="${field.label}" v-model:value="queryForm.${field.fieldName}" width="${field.width}" /> <DictSelect dict-code="DICT_CODE_ENUM.$!{field.dict} || '$!{field.dict}'" placeholder="${field.label}" v-model:value="queryForm.${field.fieldName}" width="${field.width}" />
</a-form-item> </a-form-item>
#end #end
#if($field.queryTypeEnum == "Enum") #if($field.queryTypeEnum == "Enum")

View File

@ -26,7 +26,7 @@
AND (INSTR(module,#{query.keywords}) OR INSTR(content,#{query.keywords})) AND (INSTR(module,#{query.keywords}) OR INSTR(content,#{query.keywords}))
</if> </if>
<if test="query.requestKeywords != null and query.requestKeywords != ''"> <if test="query.requestKeywords != null and query.requestKeywords != ''">
AND (INSTR(url,#{query.requestKeywords}) OR INSTR(method,#{query.requestKeywords}) OR INSTR(param,#{query.requestKeywords}) OR INSTR(response,#{query.requestKeywords})) AND (INSTR(url,#{query.requestKeywords}) OR INSTR(method,#{query.requestKeywords}) OR INSTR(param,#{query.requestKeywords}))
</if> </if>
<if test="query.successFlag != null"> <if test="query.successFlag != null">
AND success_flag = #{query.successFlag} AND success_flag = #{query.successFlag}

View File

@ -48,7 +48,7 @@
<ip2region.version>2.7.0</ip2region.version> <ip2region.version>2.7.0</ip2region.version>
<bcprov.version>1.80</bcprov.version> <bcprov.version>1.80</bcprov.version>
<smartdb.version>1.2.0</smartdb.version> <smartdb.version>1.2.0</smartdb.version>
<redisson.version>3.25.0</redisson.version> <redisson.version>3.50.0</redisson.version>
<snakeyaml.version>2.4</snakeyaml.version> <snakeyaml.version>2.4</snakeyaml.version>
<freemarker.version>2.3.34</freemarker.version> <freemarker.version>2.3.34</freemarker.version>
<jsoup.version>1.21.1</jsoup.version> <jsoup.version>1.21.1</jsoup.version>

View File

@ -67,7 +67,7 @@ public class AdminInterceptor implements HandlerInterceptor {
Method method = ((HandlerMethod) handler).getMethod(); Method method = ((HandlerMethod) handler).getMethod();
NoNeedLogin noNeedLogin = ((HandlerMethod) handler).getMethodAnnotation(NoNeedLogin.class); NoNeedLogin noNeedLogin = ((HandlerMethod) handler).getMethodAnnotation(NoNeedLogin.class);
if (noNeedLogin != null) { if (noNeedLogin != null) {
updateActiveTimeout(requestEmployee); checkActiveTimeout(requestEmployee);
SmartRequestUtil.setRequestUser(requestEmployee); SmartRequestUtil.setRequestUser(requestEmployee);
return true; return true;
} }
@ -77,8 +77,8 @@ public class AdminInterceptor implements HandlerInterceptor {
return false; return false;
} }
// 更新活跃 // 检测token 活跃频率
updateActiveTimeout(requestEmployee); checkActiveTimeout(requestEmployee);
// --------------- 第三步 校验 权限 --------------- // --------------- 第三步 校验 权限 ---------------
@ -123,12 +123,15 @@ public class AdminInterceptor implements HandlerInterceptor {
/** /**
* 更新活跃时间 * 检测token 最低活跃频率单位如果 token 超过此时间没有访问系统就会被冻结
*/ */
private void updateActiveTimeout(RequestEmployee requestEmployee) { private void checkActiveTimeout(RequestEmployee requestEmployee) {
// 用户不在线也不用检测
if (requestEmployee == null) { if (requestEmployee == null) {
return; return;
} }
StpUtil.checkActiveTimeout();
StpUtil.updateLastActiveToNow(); StpUtil.updateLastActiveToNow();
} }

View File

@ -153,19 +153,8 @@ public class LoginManager {
} }
/** @CacheEvict(value = {AdminCacheConst.Login.USER_PERMISSION, AdminCacheConst.Login.REQUEST_EMPLOYEE}, allEntries = true)
* 清除用户权限 public void clear(){
*/
@CacheEvict(value = AdminCacheConst.Login.USER_PERMISSION)
public void clearUserPermission(Long employeeId) {
}
/**
* 清除用户登录信息
*/
@CacheEvict(value = AdminCacheConst.Login.REQUEST_EMPLOYEE)
public void clearUserLoginInfo(Long employeeId) {
} }

View File

@ -319,8 +319,8 @@ public class LoginService implements StpInterface {
// sa token 登出 // sa token 登出
StpUtil.logout(); StpUtil.logout();
// 除用户登录信息缓存和权限信息 // 空登录信息缓存
this.clearLoginEmployeeCache(requestUser.getUserId()); loginManager.clear();
//保存登出日志 //保存登出日志
LoginLogEntity loginEntity = LoginLogEntity.builder() LoginLogEntity loginEntity = LoginLogEntity.builder()
@ -476,7 +476,6 @@ public class LoginService implements StpInterface {
} }
public void clearLoginEmployeeCache(Long employeeId) { public void clearLoginEmployeeCache(Long employeeId) {
loginManager.clearUserPermission(employeeId); loginManager.clear();
loginManager.clearUserLoginInfo(employeeId);
} }
} }

View File

@ -20,25 +20,17 @@ public class LongJsonSerializer extends JsonSerializer<Long> {
public static final LongJsonSerializer INSTANCE = new LongJsonSerializer(); public static final LongJsonSerializer INSTANCE = new LongJsonSerializer();
/**
* JS 安全整数范围
* 根据 JS Number.MIN_SAFE_INTEGER Number.MAX_SAFE_INTEGER 得来
*/
private static final long JS_MIN_SAFE_INTEGER = -9007199254740991L;
private static final long JS_MAX_SAFE_INTEGER = 9007199254740991L;
@Override @Override
public void serialize(Long value, JsonGenerator gen, SerializerProvider serializerProvider) throws IOException, JsonProcessingException { public void serialize(Long value, JsonGenerator gen, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
if (null == value) { if (null == value) {
gen.writeNull(); gen.writeNull();
return; return;
} }
// 如果超出了 JavaScript 安全整数范围则序列化为字符串 // js中最大安全整数16位 Number.MAX_SAFE_INTEGER
if (value < JS_MIN_SAFE_INTEGER || value > JS_MAX_SAFE_INTEGER) { String longStr = String.valueOf(value);
gen.writeString(Long.toString(value)); if (longStr.length() > 16) {
gen.writeString(longStr);
} else { } else {
// 否则序列化为数字
gen.writeNumber(value); gen.writeNumber(value);
} }
} }

View File

@ -4,13 +4,10 @@ import com.alibaba.fastjson.support.spring.GenericFastJsonRedisSerializer;
import net.lab1024.sa.base.module.support.cache.CacheService; import net.lab1024.sa.base.module.support.cache.CacheService;
import net.lab1024.sa.base.module.support.cache.CaffeineCacheServiceImpl; import net.lab1024.sa.base.module.support.cache.CaffeineCacheServiceImpl;
import net.lab1024.sa.base.module.support.cache.RedisCacheServiceImpl; import net.lab1024.sa.base.module.support.cache.RedisCacheServiceImpl;
import net.lab1024.sa.base.module.support.redis.CustomRedisCacheManager;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration; import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.RedisSerializationContext; import org.springframework.data.redis.serializer.RedisSerializationContext;
@ -19,6 +16,8 @@ import javax.annotation.Resource;
/** /**
* 缓存配置 * 缓存配置
* *
* @author zhoumingfa
* @date 2025/03/28
*/ */
@Configuration @Configuration
public class CacheConfig { public class CacheConfig {
@ -28,44 +27,27 @@ public class CacheConfig {
@Resource @Resource
private RedisConnectionFactory redisConnectionFactory; private RedisConnectionFactory factory;
/**
* 创建自定义Redis缓存管理器Bean 整合spring-cache
* Redis连接工厂用于建立与Redis服务器的连接
*
* @return CacheManager Redis缓存管理器实例
*/
@Bean @Bean
@ConditionalOnProperty(prefix = "spring.cache", name = {"type"}, havingValue = REDIS_CACHE) @ConditionalOnProperty(prefix = "spring.cache", name = {"type"}, havingValue = REDIS_CACHE)
public CacheManager cacheManager() { public RedisCacheConfiguration redisCacheConfiguration() {
// 使用非阻塞模式的缓存写入器适用于大多数高并发场景 return RedisCacheConfiguration.defaultCacheConfig()
RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory);
// 构建默认缓存配置
RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig()
// 禁止缓存 null 避免缓存穿透
.disableCachingNullValues() .disableCachingNullValues()
.computePrefixWith(name -> "cache:" + name + ":") .computePrefixWith(name -> "cache:" + name + ":")
// 使用 FastJSON 序列化缓存值支持复杂对象 .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericFastJsonRedisSerializer()));
.serializeValuesWith(RedisSerializationContext.SerializationPair
.fromSerializer(new GenericFastJsonRedisSerializer()));
// 返回自定义缓存管理器支持 cacheName#ttl 格式与永久缓存#-1
return new CustomRedisCacheManager(redisCacheWriter, defaultCacheConfig);
} }
@Bean
@ConditionalOnProperty(prefix = "spring.cache", name = {"type"}, havingValue = REDIS_CACHE)
public CacheService redisCacheService() {
return new RedisCacheServiceImpl();
}
@Bean @Bean
@ConditionalOnProperty(prefix = "spring.cache", name = {"type"}, havingValue = REDIS_CACHE) @ConditionalOnProperty(prefix = "spring.cache", name = {"type"}, havingValue = CAFFEINE_CACHE)
public CacheService redisCacheService() { public CacheService caffeineCacheService() {
return new RedisCacheServiceImpl(); return new CaffeineCacheServiceImpl();
} }
@Bean
@ConditionalOnProperty(prefix = "spring.cache", name = {"type"}, havingValue = CAFFEINE_CACHE)
public CacheService caffeineCacheService() {
return new CaffeineCacheServiceImpl();
}
} }

View File

@ -81,7 +81,7 @@ public class FileConfig implements WebMvcConfigurer {
StaticCredentialsProvider.create( StaticCredentialsProvider.create(
AwsBasicCredentials.create(accessKey, secretKey))) AwsBasicCredentials.create(accessKey, secretKey)))
.serviceConfiguration(S3Configuration.builder() .serviceConfiguration(S3Configuration.builder()
.pathStyleAccessEnabled(true) .pathStyleAccessEnabled(false)
.chunkedEncodingEnabled(false) .chunkedEncodingEnabled(false)
.build()) .build())
.build(); .build();

View File

@ -2,7 +2,6 @@ package net.lab1024.sa.base.config;
import cn.hutool.core.date.DatePattern; import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.LocalDateTimeUtil; import cn.hutool.core.date.LocalDateTimeUtil;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer; import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer; import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
@ -14,8 +13,6 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.Converter;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.format.DateTimeParseException; import java.time.format.DateTimeParseException;
@ -40,9 +37,6 @@ public class JsonConfig {
builder.serializers(new LocalDateSerializer(DatePattern.NORM_DATE_FORMAT.getDateTimeFormatter())); builder.serializers(new LocalDateSerializer(DatePattern.NORM_DATE_FORMAT.getDateTimeFormatter()));
builder.serializers(new LocalDateTimeSerializer(DatePattern.NORM_DATETIME_FORMAT.getDateTimeFormatter())); builder.serializers(new LocalDateTimeSerializer(DatePattern.NORM_DATETIME_FORMAT.getDateTimeFormatter()));
builder.serializerByType(Long.class, LongJsonSerializer.INSTANCE); builder.serializerByType(Long.class, LongJsonSerializer.INSTANCE);
builder.serializerByType(Long.TYPE, LongJsonSerializer.INSTANCE);
builder.serializerByType(BigInteger.class, ToStringSerializer.instance);
builder.serializerByType(BigDecimal.class, ToStringSerializer.instance);
}; };
} }

View File

@ -30,7 +30,7 @@ public class DictDataVO implements Serializable {
private String dictName; private String dictName;
@Schema(description = "字典禁用状态") @Schema(description = "字典禁用状态")
private Boolean dictDisabledFlag; private Integer dictDisabledFlag;
@Schema(description = "字典项值") @Schema(description = "字典项值")
private String dataValue; private String dataValue;

View File

@ -30,7 +30,7 @@ public class DictVO {
private String remark; private String remark;
@Schema(description = "禁用状态") @Schema(description = "禁用状态")
private Boolean disabledFlag; private Integer disabledFlag;
@Schema(description = "创建时间") @Schema(description = "创建时间")
private LocalDateTime createTime; private LocalDateTime createTime;

View File

@ -106,18 +106,7 @@ public class FileStorageCloudServiceImpl implements IFileStorageService {
userMetadata.put(USER_METADATA_FILE_FORMAT, fileType); userMetadata.put(USER_METADATA_FILE_FORMAT, fileType);
userMetadata.put(USER_METADATA_FILE_SIZE, String.valueOf(file.getSize())); userMetadata.put(USER_METADATA_FILE_SIZE, String.valueOf(file.getSize()));
// 根据文件路径获取并设置访问权限 PutObjectRequest putObjectRequest = PutObjectRequest.builder().bucket(cloudConfig.getBucketName()).key(fileKey).metadata(userMetadata).contentLength(file.getSize()).contentType(this.getContentType(fileType)).contentEncoding(StandardCharsets.UTF_8.name()).contentDisposition("attachment;filename=" + urlEncoderFilename).build();
ObjectCannedACL acl = this.getACL(path);
PutObjectRequest putObjectRequest = PutObjectRequest.builder()
.bucket(cloudConfig.getBucketName())
.key(fileKey)
.metadata(userMetadata)
.contentLength(file.getSize())
.contentType(this.getContentType(fileType))
.contentEncoding(StandardCharsets.UTF_8.name())
.contentDisposition("attachment;filename=" + urlEncoderFilename)
.acl(acl)
.build();
InputStream inputStream = null; InputStream inputStream = null;
try { try {
inputStream = file.getInputStream(); inputStream = file.getInputStream();
@ -128,6 +117,10 @@ public class FileStorageCloudServiceImpl implements IFileStorageService {
} finally { } finally {
IOUtils.closeQuietly(inputStream); IOUtils.closeQuietly(inputStream);
} }
// 根据文件路径获取并设置访问权限
ObjectCannedACL acl = this.getACL(path);
PutObjectAclRequest aclRequest = PutObjectAclRequest.builder().bucket(cloudConfig.getBucketName()).key(fileKey).acl(this.getACL(path)).build();
s3Client.putObjectAcl(aclRequest);
// 返回上传结果 // 返回上传结果
FileUploadVO uploadVO = new FileUploadVO(); FileUploadVO uploadVO = new FileUploadVO();
uploadVO.setFileName(originalFileName); uploadVO.setFileName(originalFileName);

View File

@ -3,12 +3,11 @@ package net.lab1024.sa.base.module.support.operatelog.core;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import net.lab1024.sa.base.common.constant.StringConst; import net.lab1024.sa.base.common.constant.StringConst;
import net.lab1024.sa.base.common.domain.RequestUser; import net.lab1024.sa.base.common.domain.RequestUser;
import net.lab1024.sa.base.common.domain.ResponseDTO;
import net.lab1024.sa.base.common.util.SmartIpUtil; import net.lab1024.sa.base.common.util.SmartIpUtil;
import net.lab1024.sa.base.common.util.SmartRequestUtil; import net.lab1024.sa.base.common.util.SmartRequestUtil;
import net.lab1024.sa.base.module.support.operatelog.OperateLogDao; import net.lab1024.sa.base.module.support.operatelog.OperateLogDao;
@ -21,6 +20,7 @@ import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature; import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.bind.BindResult; import org.springframework.boot.context.properties.bind.BindResult;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.annotation.AnnotationUtils;
@ -36,6 +36,7 @@ import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.io.StringWriter; import java.io.StringWriter;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.net.BindException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.ThreadPoolExecutor;
@ -47,7 +48,7 @@ import java.util.concurrent.ThreadPoolExecutor;
* @Date 2021-12-08 20:48:52 * @Date 2021-12-08 20:48:52
* @Wechat zhuoda1024 * @Wechat zhuoda1024
* @Email lab1024@163.com * @Email lab1024@163.com
* @Copyright <a href="https://1024lab.net">1024创新实验室</a> * @Copyright <a href="https://1024lab.net">1024创新实验室</a>
*/ */
@Slf4j @Slf4j
@Aspect @Aspect
@ -72,14 +73,14 @@ public abstract class OperateLogAspect {
public void logPointCut() { public void logPointCut() {
} }
@AfterReturning(pointcut = "logPointCut()", returning = "responseDTO") @AfterReturning(pointcut = "logPointCut()")
public void doAfterReturning(JoinPoint joinPoint, Object responseDTO) { public void doAfterReturning(JoinPoint joinPoint) {
handleLog(joinPoint, null, responseDTO); handleLog(joinPoint, null);
} }
@AfterThrowing(value = "logPointCut()", throwing = "e") @AfterThrowing(value = "logPointCut()", throwing = "e")
public void doAfterThrowing(JoinPoint joinPoint, Exception e) { public void doAfterThrowing(JoinPoint joinPoint, Exception e) {
handleLog(joinPoint, e, null); handleLog(joinPoint, e);
} }
/** /**
@ -110,15 +111,16 @@ public abstract class OperateLogAspect {
taskExecutor.setWaitForTasksToCompleteOnShutdown(true); taskExecutor.setWaitForTasksToCompleteOnShutdown(true);
} }
protected void handleLog(final JoinPoint joinPoint, final Exception e, Object responseDTO) { protected void handleLog(final JoinPoint joinPoint, final Exception e) {
try { try {
OperateLog operateLog = this.getAnnotationLog(joinPoint); OperateLog operateLog = this.getAnnotationLog(joinPoint);
if (operateLog == null) { if (operateLog == null) {
return; return;
} }
this.submitLog(joinPoint, e, responseDTO); this.submitLog(joinPoint, e);
} catch (Exception exp) { } catch (Exception exp) {
log.error("保存操作日志异常:{}", exp.getMessage()); log.error("保存操作日志异常:{}", exp.getMessage());
exp.printStackTrace();
} }
} }
@ -173,8 +175,11 @@ public abstract class OperateLogAspect {
/** /**
* 提交存储操作日志 * 提交存储操作日志
* *
* @param joinPoint
* @param e
* @throws Exception
*/ */
private void submitLog(final JoinPoint joinPoint, final Throwable e, Object responseDTO) { private void submitLog(final JoinPoint joinPoint, final Throwable e) throws Exception {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
//设置用户信息 //设置用户信息
RequestUser user = SmartRequestUtil.getRequestUser(); RequestUser user = SmartRequestUtil.getRequestUser();
@ -188,7 +193,7 @@ public abstract class OperateLogAspect {
String methodName = joinPoint.getSignature().getName(); String methodName = joinPoint.getSignature().getName();
String operateMethod = className + "." + methodName; String operateMethod = className + "." + methodName;
String failReason = null; String failReason = null;
boolean successFlag = true; Boolean successFlag = true;
if (e != null) { if (e != null) {
successFlag = false; successFlag = false;
failReason = getExceptionString(e); failReason = getExceptionString(e);
@ -207,32 +212,15 @@ public abstract class OperateLogAspect {
.userAgent(user.getUserAgent()) .userAgent(user.getUserAgent())
.failReason(failReason) .failReason(failReason)
.successFlag(successFlag).build(); .successFlag(successFlag).build();
Operation apiOperation = this.getApiOperation(joinPoint); Operation apiOperation = this.getApiOperation(joinPoint);
if (apiOperation != null) { if (apiOperation != null) {
operateLogEntity.setContent(apiOperation.summary()); operateLogEntity.setContent(apiOperation.summary());
} }
Tag api = this.getApi(joinPoint); Tag api = this.getApi(joinPoint);
if (api != null) { if (api != null) {
String name = api.name(); String name = api.name();
operateLogEntity.setModule(StrUtil.join(",", name)); operateLogEntity.setModule(StrUtil.join(",", name));
} }
// 处理返回值 ResponseDTO
if(responseDTO instanceof ResponseDTO) {
ResponseDTO response = (ResponseDTO) responseDTO;
ResponseDTO logResponseDTO = new ResponseDTO(
response.getCode(),
response.getLevel(),
response.getOk(),
response.getMsg(),
null
);
logResponseDTO.setDataType(response.getDataType());
operateLogEntity.setResponse(JSON.toJSONString(logResponseDTO));
}
taskExecutor.execute(() -> { taskExecutor.execute(() -> {
this.saveLog(operateLogEntity); this.saveLog(operateLogEntity);
}); });

View File

@ -71,11 +71,6 @@ public class OperateLogEntity {
*/ */
private String param; private String param;
/**
* 返回值
*/
private String response;
/** /**
* 客户ip * 客户ip
*/ */

View File

@ -47,9 +47,6 @@ public class OperateLogVO {
@Schema(description = "请求参数") @Schema(description = "请求参数")
private String param; private String param;
@Schema(description = "返回值")
private String response;
@Schema(description = "客户ip") @Schema(description = "客户ip")
private String ip; private String ip;

View File

@ -1,145 +0,0 @@
package net.lab1024.sa.base.module.support.redis;
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.convert.DurationStyle;
import org.springframework.data.redis.cache.*;
import java.time.Duration;
import static net.lab1024.sa.base.common.constant.StringConst.COLON;
/**
* 自定义 RedisCacheManager支持在 cacheName 中通过 '#' 指定 TTL过期时间
*
* @Author CoderKK
* @Date 2025-08-15 13:01:01
* <p>
* 支持格式{@code cacheName#ttl}其中 ttl 支持 Spring Duration 格式
* 特殊值{@code -1} 表示永久缓存永不过期
* </p>
*
* <h3>使用示例</h3>
* <pre>
* // 10 秒后过期
* &#64;Cacheable(value = "user#10s", key = "#id")
* // 2 小时后过期
* &#64;Cacheable(value = "report#2h", key = "#date")
* // 30 分钟后过期
* &#64;Cacheable(value = "session#30m", key = "#token")
* // 永不过期永久缓存适用于极少变化的配置数据
* &#64;Cacheable(value = "appConfig#-1", key = "'globalSettings'")
* // TTL使用全局默认过期时间 7
* &#64;Cacheable(value = "product", key = "#productId")
* </pre>
*
* <h3>生成的 Redis Key 格式</h3>
* <pre>
* cache:cacheName:key
* 例如cache:user:123
* cache:appConfig:globalSettings
* </pre>
*
* <h3>支持的 TTL 单位</h3>
* <ul>
* <li>{@code ms} / {@code millis} / {@code milliseconds} - 毫秒</li>
* <li>{@code s} / {@code secs} / {@code seconds} - </li>
* <li>{@code m} / {@code mins} / {@code minutes} - 分钟</li>
* <li>{@code h} / {@code hrs} / {@code hours} - 小时</li>
* <li>{@code d} / {@code days} - </li>
* </ul>
*
* <h3>注意事项</h3>
* <ul>
* <li>不写单位默认为毫秒</li>
* <li>永久缓存#-1不会自动过期请配合 &#64;CacheEvict 手动清理</li>
* <li>避免对频繁更新的数据使用永久缓存防止数据陈旧</li>
* <li>cacheName 中的 '#' 只解析第一个后续字符将作为 TTL 处理</li>
* </ul>
*/
@Slf4j
public class CustomRedisCacheManager extends RedisCacheManager {
/**
* 缓存全局前缀
*/
private static final String CACHE_PREFIX = "cache";
/**
* 自定义 TTL 分隔符用于在 cacheName 后附加过期时间
*/
private static final String CUSTOM_TTL_SEPARATOR = "#";
/**
* 默认缓存过期时间7
*/
private static final Duration DEFAULT_TTL = Duration.ofDays(7);
public CustomRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration) {
super(cacheWriter, defaultCacheConfiguration);
}
/**
* 创建 RedisCache 实例支持从 cacheName 解析 TTL
*
* @param name 缓存名称支持 name#ttl 格式
* @param cacheConfig 默认缓存配置
* @return RedisCache
*/
@Override
protected RedisCache createRedisCache(String name, RedisCacheConfiguration cacheConfig) {
Duration ttl = parseTtlFromCacheName(name);
if (ttl == null) {
ttl = DEFAULT_TTL;
}
CacheKeyPrefix keyPrefix = cacheName -> {
if (StrUtil.isBlank(cacheName)) {
return CACHE_PREFIX + COLON;
}
String[] parts = cacheName.split(CUSTOM_TTL_SEPARATOR, 2);
String cleanName = StrUtil.trim(parts[0]);
return CACHE_PREFIX + COLON + cleanName + COLON;
};
// 构建最终缓存配置设置 key 前缀 + TTL
RedisCacheConfiguration config = cacheConfig.computePrefixWith(keyPrefix).entryTtl(ttl);
return super.createRedisCache(name, config);
}
/**
* cacheName 中解析 TTL
*
* @param name 缓存名称格式如users#10m, products#2h, config#-1永久
* @return 解析出的 Duration若无效则返回 null若为 -1则返回 Duration.ofMillis(-1) 表示永久缓存
*/
private Duration parseTtlFromCacheName(String name) {
if (StrUtil.isBlank(name)) {
return null;
}
String[] parts = name.split(CUSTOM_TTL_SEPARATOR, 2);
if (parts.length < 2) {
return null; // TTL 部分
}
String ttlStr = StrUtil.trim(parts[1]);
if (StrUtil.isBlank(ttlStr)) {
return null;
}
// 特殊处理-1 表示永久缓存
if ("-1".equals(ttlStr)) {
return Duration.ofMillis(-1); // Spring Redis 中负数 Duration 表示永不过期
}
try {
Duration ttl = DurationStyle.detectAndParse(ttlStr);
return ttl.getSeconds() > 0 ? ttl : null;
} catch (IllegalArgumentException e) {
log.error("解析缓存 TTL 失败cacheName='{}', ttl='{}', 错误: {}", name, ttlStr, e);
return null;
}
}
}

View File

@ -48,7 +48,7 @@
#end #end
#if($field.frontComponent == "DictSelect") #if($field.frontComponent == "DictSelect")
<a-form-item label="$codeGeneratorTool.removeEnumDesc($!{field.label})" name="${field.fieldName}"> <a-form-item label="$codeGeneratorTool.removeEnumDesc($!{field.label})" name="${field.fieldName}">
<DictSelect width="100%" v-model:value="form.${field.fieldName}" :dict-code="DICT_CODE_ENUM.$!{field.dict} || '$!{field.dict}'" placeholder="$!{field.label}"/> <DictSelect width="100%" v-model:value="form.${field.fieldName}" dict-code="DICT_CODE_ENUM.$!{field.dict} || '$!{field.dict}'" placeholder="$!{field.label}"/>
</a-form-item> </a-form-item>
#end #end
#if($field.frontComponent == "Date") #if($field.frontComponent == "Date")
@ -106,7 +106,7 @@
#end #end
#if($field.frontComponent == "DictSelect") #if($field.frontComponent == "DictSelect")
<a-form-item label="$codeGeneratorTool.removeEnumDesc($!{field.label})" name="${field.fieldName}"> <a-form-item label="$codeGeneratorTool.removeEnumDesc($!{field.label})" name="${field.fieldName}">
<DictSelect width="100%" v-model:value="form.${field.fieldName}" :dict-code="DICT_CODE_ENUM.$!{field.dict} || '$!{field.dict}'" placeholder="$!{field.label}"/> <DictSelect width="100%" v-model:value="form.${field.fieldName}" dict-code="DICT_CODE_ENUM.$!{field.dict} || '$!{field.dict}'" placeholder="$!{field.label}"/>
</a-form-item> </a-form-item>
#end #end
#if($field.frontComponent == "Date") #if($field.frontComponent == "Date")

View File

@ -22,7 +22,7 @@
#end #end
#if($field.queryTypeEnum == "Dict") #if($field.queryTypeEnum == "Dict")
<a-form-item label="${field.label}" class="smart-query-form-item"> <a-form-item label="${field.label}" class="smart-query-form-item">
<DictSelect :dict-code="DICT_CODE_ENUM.$!{field.dict} || '$!{field.dict}'" placeholder="${field.label}" v-model:value="queryForm.${field.fieldName}" width="${field.width}" /> <DictSelect dict-code="DICT_CODE_ENUM.$!{field.dict} || '$!{field.dict}'" placeholder="${field.label}" v-model:value="queryForm.${field.fieldName}" width="${field.width}" />
</a-form-item> </a-form-item>
#end #end
#if($field.queryTypeEnum == "Enum") #if($field.queryTypeEnum == "Enum")

View File

@ -48,7 +48,7 @@
#end #end
#if($field.frontComponent == "DictSelect") #if($field.frontComponent == "DictSelect")
<a-form-item label="$codeGeneratorTool.removeEnumDesc($!{field.label})" name="${field.fieldName}"> <a-form-item label="$codeGeneratorTool.removeEnumDesc($!{field.label})" name="${field.fieldName}">
<DictSelect width="100%" v-model:value="form.${field.fieldName}" :dict-code="DICT_CODE_ENUM.$!{field.dict} || '$!{field.dict}'" placeholder="$!{field.label}"/> <DictSelect width="100%" v-model:value="form.${field.fieldName}" dict-code="DICT_CODE_ENUM.$!{field.dict} || '$!{field.dict}'" placeholder="$!{field.label}"/>
</a-form-item> </a-form-item>
#end #end
#if($field.frontComponent == "Date") #if($field.frontComponent == "Date")
@ -106,7 +106,7 @@
#end #end
#if($field.frontComponent == "DictSelect") #if($field.frontComponent == "DictSelect")
<a-form-item label="$codeGeneratorTool.removeEnumDesc($!{field.label})" name="${field.fieldName}"> <a-form-item label="$codeGeneratorTool.removeEnumDesc($!{field.label})" name="${field.fieldName}">
<DictSelect width="100%" v-model:value="form.${field.fieldName}" :dict-code="DICT_CODE_ENUM.$!{field.dict} || '$!{field.dict}'" placeholder="$!{field.label}"/> <DictSelect width="100%" v-model:value="form.${field.fieldName}" dict-code="DICT_CODE_ENUM.$!{field.dict} || '$!{field.dict}'" placeholder="$!{field.label}"/>
</a-form-item> </a-form-item>
#end #end
#if($field.frontComponent == "Date") #if($field.frontComponent == "Date")

View File

@ -22,7 +22,7 @@
#end #end
#if($field.queryTypeEnum == "Dict") #if($field.queryTypeEnum == "Dict")
<a-form-item label="${field.label}" class="smart-query-form-item"> <a-form-item label="${field.label}" class="smart-query-form-item">
<DictSelect :dict-code="DICT_CODE_ENUM.$!{field.dict} || '$!{field.dict}'" placeholder="${field.label}" v-model:value="queryForm.${field.fieldName}" width="${field.width}" /> <DictSelect dict-code="DICT_CODE_ENUM.$!{field.dict} || '$!{field.dict}'" placeholder="${field.label}" v-model:value="queryForm.${field.fieldName}" width="${field.width}" />
</a-form-item> </a-form-item>
#end #end
#if($field.queryTypeEnum == "Enum") #if($field.queryTypeEnum == "Enum")

View File

@ -26,7 +26,7 @@
AND (INSTR(module,#{query.keywords}) OR INSTR(content,#{query.keywords})) AND (INSTR(module,#{query.keywords}) OR INSTR(content,#{query.keywords}))
</if> </if>
<if test="query.requestKeywords != null and query.requestKeywords != ''"> <if test="query.requestKeywords != null and query.requestKeywords != ''">
AND (INSTR(url,#{query.requestKeywords}) OR INSTR(method,#{query.requestKeywords}) OR INSTR(param,#{query.requestKeywords}) OR INSTR(response,#{query.requestKeywords})) AND (INSTR(url,#{query.requestKeywords}) OR INSTR(method,#{query.requestKeywords}) OR INSTR(param,#{query.requestKeywords}))
</if> </if>
<if test="query.successFlag != null"> <if test="query.successFlag != null">
AND success_flag = #{query.successFlag} AND success_flag = #{query.successFlag}

View File

@ -24,7 +24,7 @@
"axios": "1.6.8", "axios": "1.6.8",
"clipboard": "2.0.11", "clipboard": "2.0.11",
"crypto-js": "4.1.1", "crypto-js": "4.1.1",
"dayjs": "1.11.13", "dayjs": "1.10.5",
"decimal.js": "10.3.1", "decimal.js": "10.3.1",
"diff": "5.2.0", "diff": "5.2.0",
"diff2html": "3.4.47", "diff2html": "3.4.47",
@ -43,7 +43,7 @@
"vue": "3.4.27", "vue": "3.4.27",
"vue-i18n": "9.13.1", "vue-i18n": "9.13.1",
"vue-router": "4.3.2", "vue-router": "4.3.2",
"vue3-json-viewer": "2.3.1" "vue3-json-viewer": "2.2.2"
}, },
"devDependencies": { "devDependencies": {
"@vitejs/plugin-vue": "5.0.4", "@vitejs/plugin-vue": "5.0.4",

View File

@ -51,6 +51,7 @@
v-model:pageSize="queryForm.pageSize" v-model:pageSize="queryForm.pageSize"
:total="total" :total="total"
@change="ajaxQuery" @change="ajaxQuery"
@showSizeChange="ajaxQuery"
:show-total="(total) => `共${total}条`" :show-total="(total) => `共${total}条`"
/> />
<a-modal v-model:open="visibleDiff" width="90%" title="数据比对" :footer="null"> <a-modal v-model:open="visibleDiff" width="90%" title="数据比对" :footer="null">

View File

@ -71,6 +71,7 @@
v-model:pageSize="params.pageSize" v-model:pageSize="params.pageSize"
:total="total" :total="total"
@change="queryEmployee" @change="queryEmployee"
@showSizeChange="queryEmployee"
:show-total="(total) => `共${total}条`" :show-total="(total) => `共${total}条`"
/> />
</div> </div>

View File

@ -27,7 +27,7 @@ export const appDefaultConfig = {
// 圆角 // 圆角
borderRadius: 6, borderRadius: 6,
// 菜单展开模式 // 菜单展开模式
menuSingleExpandFlag: true, flatPattern: false,
// 标签页 // 标签页
pageTagFlag: true, pageTagFlag: true,
// 标签页样式: default、 antd、chrome // 标签页样式: default、 antd、chrome

View File

@ -21,11 +21,11 @@ export default {
'setting.menu.layout': 'Menu Layout', 'setting.menu.layout': 'Menu Layout',
'setting.menu.width': 'Menu Width', 'setting.menu.width': 'Menu Width',
'setting.menu.theme': 'Menu Theme', 'setting.menu.theme': 'Menu Theme',
'setting.menu.expand': 'Menu Expand',
'setting.page.width': 'Page Width', 'setting.page.width': 'Page Width',
'setting.border.radius': 'Border Radius', 'setting.border.radius': 'Border Radius',
'setting.compact': 'Page Compact', 'setting.compact': 'Page Compact',
'setting.bread': 'Show Bread', 'setting.bread': 'Show Bread',
'setting.flatPattern': 'Flat Pattern',
'setting.pagetag': 'Show PageTag', 'setting.pagetag': 'Show PageTag',
'setting.pagetag.style': 'PageTag Style', 'setting.pagetag.style': 'PageTag Style',
'setting.footer': 'Show Footer', 'setting.footer': 'Show Footer',

View File

@ -21,11 +21,11 @@ export default {
'setting.menu.layout': '菜单布局', 'setting.menu.layout': '菜单布局',
'setting.menu.width': '菜单宽度', 'setting.menu.width': '菜单宽度',
'setting.menu.theme': '菜单主题', 'setting.menu.theme': '菜单主题',
'setting.menu.expand': '菜单展开',
'setting.compact': '页面紧凑', 'setting.compact': '页面紧凑',
'setting.border.radius': '页面圆角', 'setting.border.radius': '页面圆角',
'setting.page.width': '页面宽度', 'setting.page.width': '页面宽度',
'setting.bread': '面包屑', 'setting.bread': '面包屑',
'setting.flatPattern': '菜单展开模式',
'setting.pagetag': '标签页', 'setting.pagetag': '标签页',
'setting.pagetag.style': '标签页样式', 'setting.pagetag.style': '标签页样式',
'setting.footer': '页脚', 'setting.footer': '页脚',

View File

@ -83,8 +83,8 @@
<a-radio-button value="chrome">Chrome</a-radio-button> <a-radio-button value="chrome">Chrome</a-radio-button>
</a-radio-group> </a-radio-group>
</a-form-item> </a-form-item>
<a-form-item :label="$t('setting.menu.expand')" v-if="formState.layout === LAYOUT_ENUM.SIDE.value"> <a-form-item :label="$t('setting.flatPattern')" v-if="formState.layout === LAYOUT_ENUM.SIDE.value">
<a-switch @change="changeMenuExpandFlag" v-model:checked="formState.menuSingleExpandFlag" checked-children="单个" un-checked-children="" /> <a-switch @change="changeFlatPattern" v-model:checked="formState.flatPattern" checked-children="多个" un-checked-children="" />
</a-form-item> </a-form-item>
<a-form-item :label="$t('setting.pagetag')"> <a-form-item :label="$t('setting.pagetag')">
<a-switch @change="changePageTagFlag" v-model:checked="formState.pageTagFlag" checked-children="显示" un-checked-children="隐藏" /> <a-switch @change="changePageTagFlag" v-model:checked="formState.pageTagFlag" checked-children="显示" un-checked-children="隐藏" />
@ -213,8 +213,8 @@
borderRadius: appConfigStore.borderRadius, borderRadius: appConfigStore.borderRadius,
// //
pageTagFlag: appConfigStore.pageTagFlag, pageTagFlag: appConfigStore.pageTagFlag,
// //
menuSingleExpandFlag: appConfigStore.menuSingleExpandFlag, flatPattern: appConfigStore.flatPattern,
// //
pageTagStyle: appConfigStore.pageTagStyle, pageTagStyle: appConfigStore.pageTagStyle,
// //
@ -303,9 +303,9 @@
}); });
} }
function changeMenuExpandFlag(e) { function changeFlatPattern(e) {
appConfigStore.$patch({ appConfigStore.$patch({
menuSingleExpandFlag: e, flatPattern: e,
}); });
} }

View File

@ -36,7 +36,7 @@
import { useUserStore } from '/@/store/modules/system/user'; import { useUserStore } from '/@/store/modules/system/user';
const theme = computed(() => useAppConfigStore().$state.sideMenuTheme); const theme = computed(() => useAppConfigStore().$state.sideMenuTheme);
const menuSingleExpandFlag = computed(() => useAppConfigStore().$state.menuSingleExpandFlag); const flatPattern = computed(() => useAppConfigStore().$state.flatPattern);
const props = defineProps({ const props = defineProps({
collapsed: { collapsed: {
@ -46,7 +46,8 @@
}); });
const menuTree = computed(() => useUserStore().getMenuTree || []); const menuTree = computed(() => useUserStore().getMenuTree || []);
const rootSubmenuKeys = computed(() => menuTree.value.map((item) => item.menuId)); const rootSubmenuKeys = computed(()=>menuTree.value.map(item=>item.menuId));
// //
let currentRoute = useRoute(); let currentRoute = useRoute();
@ -75,15 +76,9 @@
let parentList = menuParentIdListMap.get(currentRoute.name) || []; let parentList = menuParentIdListMap.get(currentRoute.name) || [];
// openkey // openkey
if (props.collapsed) { if (!props.collapsed) {
return;
}
let needOpenKeys = _.map(parentList, 'name').map(Number);
if (menuSingleExpandFlag.value) {
openKeys.value = [...needOpenKeys];
} else {
// 使lodashunion // 使lodashunion
let needOpenKeys = _.map(parentList, 'name').map(Number);
openKeys.value = _.union(openKeys.value, needOpenKeys); openKeys.value = _.union(openKeys.value, needOpenKeys);
} }
} }
@ -97,18 +92,17 @@
immediate: true, immediate: true,
} }
); );
function onOpenChange(openKeysParams){
function onOpenChange(openKeysParams) { if(flatPattern.value){
if (!menuSingleExpandFlag.value) { return;
return;
}
const latestOpenKey = openKeysParams.find((key) => openKeys.value.indexOf(key) === -1);
if (rootSubmenuKeys.value.indexOf(latestOpenKey) === -1) {
openKeys.value = openKeysParams;
} else {
openKeys.value = latestOpenKey ? [latestOpenKey] : [];
}
} }
const latestOpenKey = openKeysParams.find(key => openKeys.value.indexOf(key) === -1);
if (rootSubmenuKeys.value.indexOf(latestOpenKey) === -1) {
openKeys.value = openKeysParams;
} else {
openKeys.value = latestOpenKey ? [latestOpenKey] : [];
}
};
defineExpose({ defineExpose({
updateOpenKeysAndSelectKeys, updateOpenKeysAndSelectKeys,
}); });

View File

@ -8,7 +8,6 @@
* @Copyright 1024创新实验室 https://1024lab.net Since 2012 * @Copyright 1024创新实验室 https://1024lab.net Since 2012
*/ */
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import { smartSentry } from '/@/lib/smart-sentry.js';
export const useSpinStore = defineStore({ export const useSpinStore = defineStore({
id: 'spin', id: 'spin',
@ -19,27 +18,13 @@ export const useSpinStore = defineStore({
actions: { actions: {
hide() { hide() {
this.loading = false; this.loading = false;
// 安全的DOM操作避免null引用错误 let spins = document.querySelector('.ant-spin-nested-loading');
try { spins.style.zIndex = 999;
const spins = document.querySelector('.ant-spin-nested-loading');
if (spins) {
spins.style.zIndex = '999';
}
} catch (error) {
smartSentry.captureError('Spin hide操作失败:', error);
}
}, },
show() { show() {
this.loading = true; this.loading = true;
// 安全的DOM操作避免null引用错误 let spins = document.querySelector('.ant-spin-nested-loading');
try { spins.style.zIndex = 1001;
const spins = document.querySelector('.ant-spin-nested-loading');
if (spins) {
spins.style.zIndex = '1001';
}
} catch (error) {
smartSentry.captureError('Spin hide操作失败:', error);
}
}, },
}, },
}); });

View File

@ -149,6 +149,7 @@
v-model:pageSize="queryForm.pageSize" v-model:pageSize="queryForm.pageSize"
:total="total" :total="total"
@change="queryData" @change="queryData"
@showSizeChange="queryData"
:show-total="(total) => `共${total}条`" :show-total="(total) => `共${total}条`"
/> />
</div> </div>

View File

@ -78,6 +78,7 @@
v-model:pageSize="queryForm.pageSize" v-model:pageSize="queryForm.pageSize"
:total="total" :total="total"
@change="ajaxQuery" @change="ajaxQuery"
@showSizeChange="ajaxQuery"
:show-total="(total) => `共${total}条`" :show-total="(total) => `共${total}条`"
/> />
</div> </div>

View File

@ -63,6 +63,7 @@
v-model:pageSize="queryForm.pageSize" v-model:pageSize="queryForm.pageSize"
:total="total" :total="total"
@change="queryEmployee" @change="queryEmployee"
@showSizeChange="queryEmployee"
:show-total="showTableTotal" :show-total="showTableTotal"
/> />
</div> </div>

View File

@ -75,6 +75,7 @@
v-model:pageSize="queryForm.pageSize" v-model:pageSize="queryForm.pageSize"
:total="total" :total="total"
@change="ajaxQuery" @change="ajaxQuery"
@showSizeChange="ajaxQuery"
:show-total="(total) => `共${total}条`" :show-total="(total) => `共${total}条`"
/> />
</div> </div>

View File

@ -102,6 +102,7 @@
v-model:pageSize="queryForm.pageSize" v-model:pageSize="queryForm.pageSize"
:total="total" :total="total"
@change="ajaxQuery" @change="ajaxQuery"
@showSizeChange="ajaxQuery"
:show-total="(total) => `共${total}条`" :show-total="(total) => `共${total}条`"
/> />
</div> </div>

View File

@ -53,6 +53,7 @@
v-model:pageSize="queryForm.pageSize" v-model:pageSize="queryForm.pageSize"
:total="total" :total="total"
@change="queryViewRecord" @change="queryViewRecord"
@showSizeChange="queryViewRecord"
:show-total="(total) => `共${total}条`" :show-total="(total) => `共${total}条`"
/> />
</div> </div>

View File

@ -72,6 +72,7 @@
v-model:pageSize="queryForm.pageSize" v-model:pageSize="queryForm.pageSize"
:total="total" :total="total"
@change="queryNoticeList" @change="queryNoticeList"
@showSizeChange="queryNoticeList"
:show-total="(total) => `共${total}条`" :show-total="(total) => `共${total}条`"
/> />
</div> </div>

View File

@ -122,6 +122,7 @@
v-model:pageSize="queryForm.pageSize" v-model:pageSize="queryForm.pageSize"
:total="total" :total="total"
@change="queryNoticeList" @change="queryNoticeList"
@showSizeChange="queryNoticeList"
:show-total="(total) => `共${total}条`" :show-total="(total) => `共${total}条`"
/> />
</div> </div>

View File

@ -107,6 +107,7 @@
v-model:pageSize="queryForm.pageSize" v-model:pageSize="queryForm.pageSize"
:total="total" :total="total"
@change="queryData" @change="queryData"
@showSizeChange="queryData"
:show-total="(total) => `共${total}条`" :show-total="(total) => `共${total}条`"
/> />
</div> </div>

View File

@ -74,6 +74,7 @@
v-model:pageSize="queryForm.pageSize" v-model:pageSize="queryForm.pageSize"
:total="total" :total="total"
@change="ajaxQuery" @change="ajaxQuery"
@showSizeChange="ajaxQuery"
:show-total="(total) => `共${total}条`" :show-total="(total) => `共${total}条`"
/> />
</div> </div>

View File

@ -3,16 +3,12 @@ import { convertUpperCamel } from '/@/utils/str-util';
// -------------------------------- java 类型 -------------------------------- // -------------------------------- java 类型 --------------------------------
export const JavaTypeMap = new Map(); export const JavaTypeMap = new Map();
JavaTypeMap.set('bit', 'Boolean'); JavaTypeMap.set('bit', 'Boolean');
JavaTypeMap.set('bool', 'Boolean');
JavaTypeMap.set('int', 'Integer'); JavaTypeMap.set('int', 'Integer');
JavaTypeMap.set('int2', 'Integer');
JavaTypeMap.set('int4', 'Integer');
JavaTypeMap.set('tinyint', 'Integer'); JavaTypeMap.set('tinyint', 'Integer');
JavaTypeMap.set('smallint', 'Integer'); JavaTypeMap.set('smallint', 'Integer');
JavaTypeMap.set('integer', 'Integer'); JavaTypeMap.set('integer', 'Integer');
JavaTypeMap.set('year', 'Integer'); JavaTypeMap.set('year', 'Integer');
JavaTypeMap.set('bigint', 'Long'); JavaTypeMap.set('bigint', 'Long');
JavaTypeMap.set('int8', 'Long');
JavaTypeMap.set('float', 'BigDecimal'); JavaTypeMap.set('float', 'BigDecimal');
JavaTypeMap.set('double', 'BigDecimal'); JavaTypeMap.set('double', 'BigDecimal');
JavaTypeMap.set('decimal', 'BigDecimal'); JavaTypeMap.set('decimal', 'BigDecimal');
@ -24,8 +20,6 @@ JavaTypeMap.set('longtext', 'String');
JavaTypeMap.set('blob', 'String'); JavaTypeMap.set('blob', 'String');
JavaTypeMap.set('date', 'LocalDate'); JavaTypeMap.set('date', 'LocalDate');
JavaTypeMap.set('datetime', 'LocalDateTime'); JavaTypeMap.set('datetime', 'LocalDateTime');
JavaTypeMap.set('timestamp', 'LocalDateTime');
JavaTypeMap.set('timestamp without time zone', 'LocalDateTime');
export const JavaTypeList = [ export const JavaTypeList = [
'Boolean', // 'Boolean', //
@ -45,11 +39,7 @@ export function getJavaType(dataType) {
// -------------------------------- js 类型 -------------------------------- // -------------------------------- js 类型 --------------------------------
export const JsTypeMap = new Map(); export const JsTypeMap = new Map();
JsTypeMap.set('bit', 'Boolean'); JsTypeMap.set('bit', 'Boolean');
JsTypeMap.set('bool', 'Boolean');
JsTypeMap.set('int', 'Number'); JsTypeMap.set('int', 'Number');
JsTypeMap.set('int2', 'Number');
JsTypeMap.set('int4', 'Number');
JsTypeMap.set('int8', 'Number');
JsTypeMap.set('tinyint', 'Number'); JsTypeMap.set('tinyint', 'Number');
JsTypeMap.set('smallint', 'Number'); JsTypeMap.set('smallint', 'Number');
JsTypeMap.set('integer', 'Number'); JsTypeMap.set('integer', 'Number');
@ -60,15 +50,12 @@ JsTypeMap.set('double', 'Number');
JsTypeMap.set('decimal', 'Number'); JsTypeMap.set('decimal', 'Number');
JsTypeMap.set('char', 'String'); JsTypeMap.set('char', 'String');
JsTypeMap.set('varchar', 'String'); JsTypeMap.set('varchar', 'String');
JsTypeMap.set('character', 'String');
JsTypeMap.set('tinytext', 'String'); JsTypeMap.set('tinytext', 'String');
JsTypeMap.set('text', 'String'); JsTypeMap.set('text', 'String');
JsTypeMap.set('longtext', 'String'); JsTypeMap.set('longtext', 'String');
JsTypeMap.set('blob', 'String'); JsTypeMap.set('blob', 'String');
JsTypeMap.set('date', 'Date'); JsTypeMap.set('date', 'Date');
JsTypeMap.set('datetime', 'Date'); JsTypeMap.set('datetime', 'Date');
JsTypeMap.set('timestamp', 'Date');
JsTypeMap.set('timestamp without time zone', 'Date');
export const JsTypeList = [ export const JsTypeList = [
'Boolean', // 'Boolean', //
@ -85,30 +72,23 @@ export function getJsType(dataType) {
export const FrontComponentMap = new Map(); export const FrontComponentMap = new Map();
FrontComponentMap.set('bit', 'BooleanSelect'); FrontComponentMap.set('bit', 'BooleanSelect');
FrontComponentMap.set('bool', 'BooleanSelect');
FrontComponentMap.set('int', 'InputNumber'); FrontComponentMap.set('int', 'InputNumber');
FrontComponentMap.set('int2', 'InputNumber');
FrontComponentMap.set('int4', 'InputNumber');
FrontComponentMap.set('int8', 'InputNumber');
FrontComponentMap.set('tinyint', 'InputNumber'); FrontComponentMap.set('tinyint', 'InputNumber');
FrontComponentMap.set('smallint', 'InputNumber'); FrontComponentMap.set('smallint', 'InputNumber');
FrontComponentMap.set('integer', 'InputNumber'); FrontComponentMap.set('integer', 'InputNumber');
FrontComponentMap.set('year', 'Date'); FrontComponentMap.set('year', 'Date');
FrontComponentMap.set('timestamp', 'Date');
FrontComponentMap.set('bigint', 'InputNumber'); FrontComponentMap.set('bigint', 'InputNumber');
FrontComponentMap.set('float', 'InputNumber'); FrontComponentMap.set('float', 'InputNumber');
FrontComponentMap.set('double', 'InputNumber'); FrontComponentMap.set('double', 'InputNumber');
FrontComponentMap.set('decimal', 'InputNumber'); FrontComponentMap.set('decimal', 'InputNumber');
FrontComponentMap.set('char', 'Input'); FrontComponentMap.set('char', 'Input');
FrontComponentMap.set('varchar', 'Input'); FrontComponentMap.set('varchar', 'Input');
FrontComponentMap.set('character', 'Input');
FrontComponentMap.set('tinytext', 'Input'); FrontComponentMap.set('tinytext', 'Input');
FrontComponentMap.set('text', 'Textarea'); FrontComponentMap.set('text', 'Textarea');
FrontComponentMap.set('longtext', 'Textarea'); FrontComponentMap.set('longtext', 'Textarea');
FrontComponentMap.set('blob', 'FileUpload'); FrontComponentMap.set('blob', 'FileUpload');
FrontComponentMap.set('date', 'Date'); FrontComponentMap.set('date', 'Date');
FrontComponentMap.set('datetime', 'DateTime'); FrontComponentMap.set('datetime', 'DateTime');
FrontComponentMap.set('timestamp without time zone', 'DateTime');
export function getFrontComponent(dataType) { export function getFrontComponent(dataType) {
return FrontComponentMap.get(dataType); return FrontComponentMap.get(dataType);

View File

@ -66,6 +66,7 @@
v-model:pageSize="queryForm.pageSize" v-model:pageSize="queryForm.pageSize"
:total="total" :total="total"
@change="ajaxQuery" @change="ajaxQuery"
@showSizeChange="ajaxQuery"
:show-total="(total) => `共${total}条`" :show-total="(total) => `共${total}条`"
/> />
</div> </div>

View File

@ -98,6 +98,7 @@
v-model:pageSize="queryForm.pageSize" v-model:pageSize="queryForm.pageSize"
:total="total" :total="total"
@change="ajaxQuery" @change="ajaxQuery"
@showSizeChange="ajaxQuery"
:show-total="(total) => `共${total}条`" :show-total="(total) => `共${total}条`"
/> />
</div> </div>

View File

@ -64,6 +64,7 @@
v-model:pageSize="queryForm.pageSize" v-model:pageSize="queryForm.pageSize"
:total="total" :total="total"
@change="queryList" @change="queryList"
@showSizeChange="queryList"
:show-total="(total) => `共${total}条`" :show-total="(total) => `共${total}条`"
/> />
</div> </div>

View File

@ -103,6 +103,7 @@
v-model:pageSize="queryForm.pageSize" v-model:pageSize="queryForm.pageSize"
:total="total" :total="total"
@change="queryData" @change="queryData"
@showSizeChange="queryData"
:show-total="(total) => `共${total}条`" :show-total="(total) => `共${total}条`"
/> />
</div> </div>

View File

@ -79,6 +79,7 @@
v-model:pageSize="queryForm.pageSize" v-model:pageSize="queryForm.pageSize"
:total="total" :total="total"
@change="ajaxQuery" @change="ajaxQuery"
@showSizeChange="ajaxQuery"
:show-total="(total) => `共${total}条`" :show-total="(total) => `共${total}条`"
/> />
</div> </div>

View File

@ -88,6 +88,7 @@
v-model:pageSize="queryForm.pageSize" v-model:pageSize="queryForm.pageSize"
:total="total" :total="total"
@change="queryHelpDocList" @change="queryHelpDocList"
@showSizeChange="queryHelpDocList"
:show-total="(total) => `共${total}条`" :show-total="(total) => `共${total}条`"
/> />
</div> </div>

View File

@ -49,6 +49,7 @@
v-model:pageSize="queryForm.pageSize" v-model:pageSize="queryForm.pageSize"
:total="total" :total="total"
@change="queryViewRecord" @change="queryViewRecord"
@showSizeChange="queryViewRecord"
:show-total="(total) => `共${total}条`" :show-total="(total) => `共${total}条`"
/> />
</div> </div>

View File

@ -117,6 +117,7 @@
v-model:pageSize="queryForm.pageSize" v-model:pageSize="queryForm.pageSize"
:total="total" :total="total"
@change="queryJobList" @change="queryJobList"
@showSizeChange="queryJobList"
:show-total="(total) => `共${total}条`" :show-total="(total) => `共${total}条`"
/> />
</div> </div>

View File

@ -71,6 +71,7 @@
v-model:pageSize="queryForm.pageSize" v-model:pageSize="queryForm.pageSize"
:total="total" :total="total"
@change="queryLogList" @change="queryLogList"
@showSizeChange="queryLogList"
:show-total="(total) => `共${total}条`" :show-total="(total) => `共${total}条`"
/> />
</div> </div>

View File

@ -136,6 +136,7 @@
v-model:pageSize="queryForm.pageSize" v-model:pageSize="queryForm.pageSize"
:total="total" :total="total"
@change="queryJobList" @change="queryJobList"
@showSizeChange="queryJobList"
:show-total="(total) => `共${total}条`" :show-total="(total) => `共${total}条`"
/> />
</div> </div>

View File

@ -102,6 +102,7 @@
v-model:pageSize="queryForm.pageSize" v-model:pageSize="queryForm.pageSize"
:total="total" :total="total"
@change="onSearch" @change="onSearch"
@showSizeChange="onSearch"
:show-total="(total) => `共${total}条`" :show-total="(total) => `共${total}条`"
/> />
</div> </div>

View File

@ -89,6 +89,7 @@
v-model:pageSize="queryForm.pageSize" v-model:pageSize="queryForm.pageSize"
:total="total" :total="total"
@change="ajaxQuery" @change="ajaxQuery"
@showSizeChange="ajaxQuery"
:show-total="(total) => `共${total}条`" :show-total="(total) => `共${total}条`"
/> />
</div> </div>

View File

@ -47,6 +47,7 @@
v-model:pageSize="queryParam.pageSize" v-model:pageSize="queryParam.pageSize"
:total="total" :total="total"
@change="queryList" @change="queryList"
@showSizeChange="queryList"
:show-total="(total) => `共${total}条`" :show-total="(total) => `共${total}条`"
/> />
</div> </div>

View File

@ -78,6 +78,7 @@
v-model:pageSize="queryForm.pageSize" v-model:pageSize="queryForm.pageSize"
:total="total" :total="total"
@change="queryData" @change="queryData"
@showSizeChange="queryData"
:show-total="(total) => `共${total}条`" :show-total="(total) => `共${total}条`"
/> />
</div> </div>

View File

@ -12,10 +12,6 @@
<div class="info-box"> <div class="info-box">
<a-row class="smart-margin-top10"> <a-row class="smart-margin-top10">
<a-col :span="16"> <a-col :span="16">
<a-row class="detail-info">
<a-col :span="12"> 用户id{{ detail.operateUserId }}</a-col>
<a-col :span="12"> 用户名称 {{ detail.operateUserName }}</a-col>
</a-row>
<a-row class="detail-info"> <a-row class="detail-info">
<a-col :span="12"> 请求url {{ detail.url }}</a-col> <a-col :span="12"> 请求url {{ detail.url }}</a-col>
<a-col :span="12"> 请求日期 {{ detail.createTime }}</a-col> <a-col :span="12"> 请求日期 {{ detail.createTime }}</a-col>
@ -25,7 +21,8 @@
<a-col :span="12"> IP地区 {{ detail.ipRegion }}</a-col> <a-col :span="12"> IP地区 {{ detail.ipRegion }}</a-col>
</a-row> </a-row>
<a-row class="detail-info"> <a-row class="detail-info">
<a-col :span="12"> 客户端 {{ detail.os }} / {{ detail.browser }} {{ detail.device ? '/' + detail.device : detail.device }}</a-col> <a-col :span="12"> 用户id{{ detail.operateUserId }}</a-col>
<a-col :span="12"> 用户名称 {{ detail.operateUserName }}</a-col>
</a-row> </a-row>
</a-col> </a-col>
<a-col :span="8"> <a-col :span="8">
@ -35,26 +32,21 @@
</a-typography-text> </a-typography-text>
</a-col> </a-col>
</a-row> </a-row>
<a-row class="detail-info"> </div>
<a-col :span="24"> 方法 {{ detail.method }}</a-col> <div class="info-box">
</a-row> <h4>请求明细</h4>
<a-row class="detail-info"> <a-col :span="24"> 方法 {{ detail.method }}</a-col>
<a-col :span="24"> 说明 {{ detail.module }} - {{ detail.content }}</a-col> <a-col :span="24"> 说明 {{ detail.module }} - {{ detail.content }}</a-col>
</a-row>
</div> </div>
<div class="info-box"> <div class="info-box">
<h4>请求参数</h4> <h4>请求参数</h4>
<JsonViewer :value="detail.param ? JSON.parse(detail.param) : ''" :expanded="true" :expandDepth="10" copyable boxed sort theme="light" /> <JsonViewer :value="detail.param ? JSON.parse(detail.param) : ''" theme="jv-dark" copyable boxed sort />
</div>
<div class="info-box" v-if="detail.successFlag">
<h4>返回结果</h4>
<JsonViewer :value="detail.response ? JSON.parse(detail.response) : ''" :expanded="true" :expandDepth="10" copyable boxed sort theme="light" />
</div> </div>
<div class="info-box" v-if="detail.failReason"> <div class="info-box" v-if="detail.failReason">
<h4>请求失败原因</h4> <h4>请求失败原因</h4>
<a-card> <div>
{{ detail.failReason }} {{ detail.failReason }}
</a-card> </div>
</div> </div>
</a-modal> </a-modal>
</template> </template>
@ -65,7 +57,6 @@
import { operateLogApi } from '/@/api/support/operate-log-api'; import { operateLogApi } from '/@/api/support/operate-log-api';
import { smartSentry } from '/@/lib/smart-sentry'; import { smartSentry } from '/@/lib/smart-sentry';
import { SmartLoading } from '/@/components/framework/smart-loading'; import { SmartLoading } from '/@/components/framework/smart-loading';
import uaparser from 'ua-parser-js';
defineExpose({ defineExpose({
show, show,
@ -96,16 +87,11 @@
param: '', param: '',
url: '', url: '',
}); });
async function getDetail(operateLogId) { async function getDetail(operateLogId) {
try { try {
SmartLoading.show(); SmartLoading.show();
let res = await operateLogApi.detail(operateLogId); let res = await operateLogApi.detail(operateLogId);
detail = Object.assign(detail, res.data); detail = Object.assign(detail, res.data);
let ua = uaparser(res.data.userAgent);
detail.browser = ua.browser.name;
detail.os = ua.os.name;
detail.device = ua.device.vendor ? ua.device.vendor + ua.device.model : '';
} catch (e) { } catch (e) {
smartSentry.captureError(e); smartSentry.captureError(e);
} finally { } finally {
@ -121,11 +107,10 @@
font-size: 20px; font-size: 20px;
font-weight: bold; font-weight: bold;
} }
.info-box { .info-box {
border-bottom: 1px solid #f0f0f0;
padding: 10px 8px; padding: 10px 8px;
} }
.detail-info { .detail-info {
.ant-col { .ant-col {
line-height: 1.46; line-height: 1.46;
@ -133,7 +118,6 @@
padding-right: 5px; padding-right: 5px;
} }
} }
.detail-right-title { .detail-right-title {
text-align: right; text-align: right;
color: grey; color: grey;

View File

@ -14,7 +14,7 @@
<a-input style="width: 150px" v-model:value="queryForm.keywords" placeholder="模块/操作内容" /> <a-input style="width: 150px" v-model:value="queryForm.keywords" placeholder="模块/操作内容" />
</a-form-item> </a-form-item>
<a-form-item label="请求关键字" class="smart-query-form-item"> <a-form-item label="请求关键字" class="smart-query-form-item">
<a-input style="width: 270px" v-model:value="queryForm.requestKeywords" placeholder="请求地址/请求方法/请求参数/返回结果" /> <a-input style="width: 220px" v-model:value="queryForm.requestKeywords" placeholder="请求地址/请求方法/请求参数" />
</a-form-item> </a-form-item>
<a-form-item label="用户名称" class="smart-query-form-item"> <a-form-item label="用户名称" class="smart-query-form-item">
<a-input style="width: 100px" v-model:value="queryForm.userName" placeholder="用户名称" /> <a-input style="width: 100px" v-model:value="queryForm.userName" placeholder="用户名称" />
@ -24,7 +24,7 @@
<a-range-picker @change="changeCreateDate" v-model:value="createDateRange" :presets="defaultChooseTimeRange" style="width: 240px" /> <a-range-picker @change="changeCreateDate" v-model:value="createDateRange" :presets="defaultChooseTimeRange" style="width: 240px" />
</a-form-item> </a-form-item>
<a-form-item label="状态:" class="smart-query-form-item"> <a-form-item label="快速筛选" class="smart-query-form-item">
<a-radio-group v-model:value="queryForm.successFlag" @change="onSearch"> <a-radio-group v-model:value="queryForm.successFlag" @change="onSearch">
<a-radio-button :value="undefined">全部</a-radio-button> <a-radio-button :value="undefined">全部</a-radio-button>
<a-radio-button :value="true">成功</a-radio-button> <a-radio-button :value="true">成功</a-radio-button>
@ -51,23 +51,18 @@
</a-row> </a-row>
</a-form> </a-form>
<a-card size="small" :bordered="false" :hoverable="true" > <a-card size="small" :bordered="false" :hoverable="true" style="height: 100%">
<a-row justify="end"> <a-row justify="end">
<TableOperator class="smart-margin-bottom5" v-model="columns" :tableId="TABLE_ID_CONST.SUPPORT.CONFIG" :refresh="ajaxQuery" /> <TableOperator class="smart-margin-bottom5" v-model="columns" :tableId="TABLE_ID_CONST.SUPPORT.CONFIG" :refresh="ajaxQuery" />
</a-row> </a-row>
<a-table size="small" :loading="tableLoading" :dataSource="tableData" :columns="columns" bordered rowKey="operateLogId" :pagination="false"> <a-table size="small" :loading="tableLoading" :dataSource="tableData" :columns="columns" bordered rowKey="operateLogId" :pagination="false">
<template #bodyCell="{ text, record, column }"> <template #bodyCell="{ text, record, column }">
<template v-if="column.dataIndex === 'response'">
<a-typography-text v-if="text && text.ok">{{ text ? text.msg : '-' }}</a-typography-text>
<a-typography-text v-else type="warning">{{ text ? text.msg : '-' }}</a-typography-text>
</template>
<template v-if="column.dataIndex === 'successFlag'"> <template v-if="column.dataIndex === 'successFlag'">
<a-tag :color="text ? 'success' : 'error'">{{ text ? '成功' : '报错' }}</a-tag> <a-tag :color="text ? 'success' : 'error'">{{ text ? '成功' : '失败' }}</a-tag>
</template> </template>
<template v-if="column.dataIndex === 'userAgent'"> <template v-if="column.dataIndex === 'userAgent'">
<div>{{ record.os }} / {{ record.browser }} {{ record.device ? '/' + record.device : record.device }}</div> <div>{{ record.browser }} / {{ record.os }} / {{ record.device }}</div>
</template> </template>
<template v-if="column.dataIndex === 'operateUserType'"> <template v-if="column.dataIndex === 'operateUserType'">
@ -93,6 +88,7 @@
v-model:pageSize="queryForm.pageSize" v-model:pageSize="queryForm.pageSize"
:total="total" :total="total"
@change="ajaxQuery" @change="ajaxQuery"
@showSizeChange="ajaxQuery"
:show-total="(total) => `共${total}条`" :show-total="(total) => `共${total}条`"
/> />
</div> </div>
@ -139,15 +135,14 @@
ellipsis: true, ellipsis: true,
}, },
{ {
title: '返回结果', title: 'IP',
dataIndex: 'response', dataIndex: 'ip',
ellipsis: true, ellipsis: true,
}, },
{ {
title: 'IP地区', title: 'IP地区',
dataIndex: 'ipRegion', dataIndex: 'ipRegion',
ellipsis: true, ellipsis: true,
width: 150,
}, },
{ {
title: '客户端', title: '客户端',
@ -155,14 +150,19 @@
ellipsis: true, ellipsis: true,
}, },
{ {
title: '操作时间', title: '请求方法',
dataIndex: 'createTime', dataIndex: 'method',
width: 150, ellipsis: true,
}, },
{ {
title: '状态', title: '请求结果',
dataIndex: 'successFlag', dataIndex: 'successFlag',
width: 60, width: 80,
},
{
title: '时间',
dataIndex: 'createTime',
width: 150,
}, },
{ {
title: '操作', title: '操作',
@ -212,10 +212,6 @@
let responseModel = await operateLogApi.queryList(queryForm); let responseModel = await operateLogApi.queryList(queryForm);
for (const e of responseModel.data.list) { for (const e of responseModel.data.list) {
if(e.response){
e.response = JSON.parse(e.response);
}
if (!e.userAgent) { if (!e.userAgent) {
continue; continue;
} }

View File

@ -32,6 +32,7 @@
v-model:pageSize="queryForm.pageSize" v-model:pageSize="queryForm.pageSize"
:total="total" :total="total"
@change="ajaxQuery" @change="ajaxQuery"
@showSizeChange="ajaxQuery"
:show-total="(total) => `共${total}条`" :show-total="(total) => `共${total}条`"
/> />
</div> </div>

View File

@ -234,21 +234,12 @@
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
.center-container { .center-container {
height: 100%;
display: flex;
flex-direction: column;
.header-title { .header-title {
font-size: 20px; font-size: 20px;
flex-shrink: 0;
} }
.center-form-area { .center-form-area {
margin-top: 20px; margin-top: 20px;
flex: 1;
overflow-y: auto;
min-height: 0;
.avatar-container { .avatar-container {
position: relative; position: relative;

View File

@ -63,6 +63,7 @@
v-model:pageSize="queryForm.pageSize" v-model:pageSize="queryForm.pageSize"
:total="total" :total="total"
@change="ajaxQuery" @change="ajaxQuery"
@showSizeChange="ajaxQuery"
:show-total="(total) => `共${total}条`" :show-total="(total) => `共${total}条`"
/> />
</div> </div>

View File

@ -75,6 +75,7 @@
v-model:pageSize="queryForm.pageSize" v-model:pageSize="queryForm.pageSize"
:total="total" :total="total"
@change="ajaxQuery" @change="ajaxQuery"
@showSizeChange="ajaxQuery"
:show-total="(total) => `共${total}条`" :show-total="(total) => `共${total}条`"
/> />
</div> </div>

View File

@ -68,6 +68,7 @@
v-model:pageSize="queryForm.pageSize" v-model:pageSize="queryForm.pageSize"
:total="total" :total="total"
@change="ajaxQuery" @change="ajaxQuery"
@showSizeChange="ajaxQuery"
:show-total="(total) => `共${total}条`" :show-total="(total) => `共${total}条`"
/> />
</div> </div>

View File

@ -94,6 +94,7 @@
v-model:pageSize="params.pageSize" v-model:pageSize="params.pageSize"
:total="total" :total="total"
@change="queryEmployee" @change="queryEmployee"
@showSizeChange="queryEmployee"
:show-total="showTableTotal" :show-total="showTableTotal"
/> />
</div> </div>

View File

@ -8,24 +8,23 @@
* @Copyright 1024创新实验室 https://1024lab.net Since 2012 * @Copyright 1024创新实验室 https://1024lab.net Since 2012
--> -->
<template> <template>
<a-modal :open="visible" width="600px" :bodyStyle="{height:'360px'}" title="" :closable="false" :maskClosable="true"> <a-modal :open="visible" width="600px" :bodyStyle="{height:'480px'}" 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: 0 auto;font-size: 16px">助力卓大抖音1000个粉丝开播写代码🎉🎉</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: 20px auto;font-size: 15px">和1024创新实验室一起热爱代码热爱生活永远年轻永远前行🎉🎉</div> </a-row>
<br /> <br />
<div class="app-qr-box"> <div class="app-qr-box">
<div class="app-qr"> <div class="app-qr">
<a-image <a-image
:width="200" :width="300"
style="border-radius: 15px;" style="border-radius: 15px;"
src="https://img.smartadmin.1024lab.net/wechat/zhuoda-wechat.jpg" src="https://img.smartadmin.1024lab.net/wechat/douyin.png"
/> />
<span class="qr-desc strong"> 添加卓大微信备注对应数据库 达梦</span> <span class="qr-desc strong"> 打开抖音APP-点击左上角侧边栏-点击扫一扫-进行关注</span>
</div> </div>
</div> </div>
<template #footer> <template #footer>
<a-button type="default" @click="hide">知道了</a-button> <a-button type="primary" @click="hide">知道了</a-button>
<a-button danger type="default" @click="goto" target="_blank" href="https://smartadmin.vip/views/other/china-db/">去看看国产数据库了解一下</a-button>
</template> </template>
</a-modal> </a-modal>
</template> </template>
@ -37,9 +36,6 @@ defineExpose({
}); });
const visible = ref(true); const visible = ref(true);
function goto(){
}
function show() { function show() {
visible.value = true; visible.value = true;
} }
@ -68,7 +64,8 @@ defineExpose({
display: flex; display: flex;
margin-top: 20px; margin-top: 20px;
align-items: center; align-items: center;
font-size: 15px; font-size: 13px;
color: red;
text-align: center; text-align: center;
overflow-x: hidden; overflow-x: hidden;
> img { > img {

View File

@ -108,7 +108,7 @@
let lunarMonth = lunar.getMonthInChinese(); let lunarMonth = lunar.getMonthInChinese();
let lunarDay = lunar.getDayInChinese(); let lunarDay = lunar.getDayInChinese();
// //
let jieqi = lunar.getJieQi(); let jieqi = lunar.getPrevJieQi().getName();
let next = lunar.getNextJieQi(); let next = lunar.getNextJieQi();
let nextJieqi = next.getName() + ' ' + next.getSolar().toYmd(); let nextJieqi = next.getName() + ' ' + next.getSolar().toYmd();

View File

@ -52,11 +52,11 @@
<a-col :span="24"> <a-col :span="24">
<OfficialAccountCard /> <OfficialAccountCard />
</a-col> </a-col>
<!--更新日志--> <!--待办已办-->
<a-col :span="24"> <a-col :span="24">
<ChangelogCard /> <ChangelogCard />
</a-col> </a-col>
<!--待办已办--> <!--更新日志-->
<a-col :span="24"> <a-col :span="24">
<ToBeDoneCard /> <ToBeDoneCard />
</a-col> </a-col>

View File

@ -86,6 +86,7 @@
v-model:pageSize="queryForm.pageSize" v-model:pageSize="queryForm.pageSize"
:total="total" :total="total"
@change="queryData" @change="queryData"
@showSizeChange="queryData"
:show-total="(total) => `共${total}条`" :show-total="(total) => `共${total}条`"
/> />
</div> </div>

View File

@ -68,6 +68,7 @@
v-model:pageSize="queryForm.pageSize" v-model:pageSize="queryForm.pageSize"
:total="total" :total="total"
@change="queryRoleEmployee" @change="queryRoleEmployee"
@showSizeChange="queryRoleEmployee"
:show-total="showTableTotal" :show-total="showTableTotal"
/> />
</div> </div>

View File

@ -24,7 +24,7 @@
"axios": "1.6.8", "axios": "1.6.8",
"clipboard": "2.0.11", "clipboard": "2.0.11",
"crypto-js": "4.1.1", "crypto-js": "4.1.1",
"dayjs": "1.11.13", "dayjs": "1.10.5",
"decimal.js": "10.3.1", "decimal.js": "10.3.1",
"default-passive-events": "^2.0.0", "default-passive-events": "^2.0.0",
"diff": "5.2.0", "diff": "5.2.0",
@ -44,7 +44,7 @@
"vue": "3.4.27", "vue": "3.4.27",
"vue-i18n": "9.13.1", "vue-i18n": "9.13.1",
"vue-router": "4.3.2", "vue-router": "4.3.2",
"vue3-json-viewer": "2.3.1" "vue3-json-viewer": "2.2.2"
}, },
"devDependencies": { "devDependencies": {
"@vitejs/plugin-vue": "5.1.4", "@vitejs/plugin-vue": "5.1.4",

View File

@ -51,6 +51,7 @@
v-model:pageSize="queryForm.pageSize" v-model:pageSize="queryForm.pageSize"
:total="total" :total="total"
@change="ajaxQuery" @change="ajaxQuery"
@showSizeChange="ajaxQuery"
:show-total="(total) => `共${total}条`" :show-total="(total) => `共${total}条`"
/> />
<a-modal v-model:open="visibleDiff" width="90%" title="数据比对" :footer="null"> <a-modal v-model:open="visibleDiff" width="90%" title="数据比对" :footer="null">

View File

@ -71,6 +71,7 @@
v-model:pageSize="params.pageSize" v-model:pageSize="params.pageSize"
:total="total" :total="total"
@change="queryEmployee" @change="queryEmployee"
@showSizeChange="queryEmployee"
:show-total="(total) => `共${total}条`" :show-total="(total) => `共${total}条`"
/> />
</div> </div>

View File

@ -27,7 +27,7 @@ export const appDefaultConfig = {
// 圆角 // 圆角
borderRadius: 6, borderRadius: 6,
// 菜单展开模式 // 菜单展开模式
menuSingleExpandFlag: true, flatPattern: false,
// 标签页 // 标签页
pageTagFlag: true, pageTagFlag: true,
// 标签页样式: default、 antd、chrome // 标签页样式: default、 antd、chrome

View File

@ -21,11 +21,11 @@ export default {
'setting.menu.layout': 'Menu Layout', 'setting.menu.layout': 'Menu Layout',
'setting.menu.width': 'Menu Width', 'setting.menu.width': 'Menu Width',
'setting.menu.theme': 'Menu Theme', 'setting.menu.theme': 'Menu Theme',
'setting.menu.expand': 'Menu Expand',
'setting.page.width': 'Page Width', 'setting.page.width': 'Page Width',
'setting.border.radius': 'Border Radius', 'setting.border.radius': 'Border Radius',
'setting.compact': 'Page Compact', 'setting.compact': 'Page Compact',
'setting.bread': 'Show Bread', 'setting.bread': 'Show Bread',
'setting.flatPattern': 'Flat Pattern',
'setting.pagetag': 'Show PageTag', 'setting.pagetag': 'Show PageTag',
'setting.pagetag.style': 'PageTag Style', 'setting.pagetag.style': 'PageTag Style',
'setting.footer': 'Show Footer', 'setting.footer': 'Show Footer',

View File

@ -21,11 +21,11 @@ export default {
'setting.menu.layout': '菜单布局', 'setting.menu.layout': '菜单布局',
'setting.menu.width': '菜单宽度', 'setting.menu.width': '菜单宽度',
'setting.menu.theme': '菜单主题', 'setting.menu.theme': '菜单主题',
'setting.menu.expand': '菜单展开',
'setting.compact': '页面紧凑', 'setting.compact': '页面紧凑',
'setting.border.radius': '页面圆角', 'setting.border.radius': '页面圆角',
'setting.page.width': '页面宽度', 'setting.page.width': '页面宽度',
'setting.bread': '面包屑', 'setting.bread': '面包屑',
'setting.flatPattern': '菜单展开模式',
'setting.pagetag': '标签页', 'setting.pagetag': '标签页',
'setting.pagetag.style': '标签页样式', 'setting.pagetag.style': '标签页样式',
'setting.footer': '页脚', 'setting.footer': '页脚',

View File

@ -83,8 +83,8 @@
<a-radio-button value="chrome">Chrome</a-radio-button> <a-radio-button value="chrome">Chrome</a-radio-button>
</a-radio-group> </a-radio-group>
</a-form-item> </a-form-item>
<a-form-item :label="$t('setting.menu.expand')" v-if="formState.layout === LAYOUT_ENUM.SIDE.value"> <a-form-item :label="$t('setting.flatPattern')" v-if="formState.layout === LAYOUT_ENUM.SIDE.value">
<a-switch @change="changeMenuExpandFlag" v-model:checked="formState.menuSingleExpandFlag" checked-children="单个" un-checked-children="" /> <a-switch @change="changeFlatPattern" v-model:checked="formState.flatPattern" checked-children="多个" un-checked-children="" />
</a-form-item> </a-form-item>
<a-form-item :label="$t('setting.pagetag')"> <a-form-item :label="$t('setting.pagetag')">
<a-switch @change="changePageTagFlag" v-model:checked="formState.pageTagFlag" checked-children="显示" un-checked-children="隐藏" /> <a-switch @change="changePageTagFlag" v-model:checked="formState.pageTagFlag" checked-children="显示" un-checked-children="隐藏" />
@ -213,8 +213,8 @@
borderRadius: appConfigStore.borderRadius, borderRadius: appConfigStore.borderRadius,
// //
pageTagFlag: appConfigStore.pageTagFlag, pageTagFlag: appConfigStore.pageTagFlag,
// //
menuSingleExpandFlag: appConfigStore.menuSingleExpandFlag, flatPattern: appConfigStore.flatPattern,
// //
pageTagStyle: appConfigStore.pageTagStyle, pageTagStyle: appConfigStore.pageTagStyle,
// //
@ -303,9 +303,9 @@
}); });
} }
function changeMenuExpandFlag(e) { function changeFlatPattern(e) {
appConfigStore.$patch({ appConfigStore.$patch({
menuSingleExpandFlag: e, flatPattern: e,
}); });
} }

View File

@ -36,7 +36,7 @@
import { useUserStore } from '/@/store/modules/system/user'; import { useUserStore } from '/@/store/modules/system/user';
const theme = computed(() => useAppConfigStore().$state.sideMenuTheme); const theme = computed(() => useAppConfigStore().$state.sideMenuTheme);
const menuSingleExpandFlag = computed(() => useAppConfigStore().$state.menuSingleExpandFlag); const flatPattern = computed(() => useAppConfigStore().$state.flatPattern);
const props = defineProps({ const props = defineProps({
collapsed: { collapsed: {
@ -46,7 +46,8 @@
}); });
const menuTree = computed(() => useUserStore().getMenuTree || []); const menuTree = computed(() => useUserStore().getMenuTree || []);
const rootSubmenuKeys = computed(() => menuTree.value.map((item) => item.menuId)); const rootSubmenuKeys = computed(()=>menuTree.value.map(item=>item.menuId));
// //
let currentRoute = useRoute(); let currentRoute = useRoute();
@ -75,15 +76,9 @@
let parentList = menuParentIdListMap.get(currentRoute.name) || []; let parentList = menuParentIdListMap.get(currentRoute.name) || [];
// openkey // openkey
if (props.collapsed) { if (!props.collapsed) {
return;
}
let needOpenKeys = _.map(parentList, 'name').map(Number);
if (menuSingleExpandFlag.value) {
openKeys.value = [...needOpenKeys];
} else {
// 使lodashunion // 使lodashunion
let needOpenKeys = _.map(parentList, 'name').map(Number);
openKeys.value = _.union(openKeys.value, needOpenKeys); openKeys.value = _.union(openKeys.value, needOpenKeys);
} }
} }
@ -97,18 +92,17 @@
immediate: true, immediate: true,
} }
); );
function onOpenChange(openKeysParams){
function onOpenChange(openKeysParams) { if(flatPattern.value){
if (!menuSingleExpandFlag.value) { return;
return;
}
const latestOpenKey = openKeysParams.find((key) => openKeys.value.indexOf(key) === -1);
if (rootSubmenuKeys.value.indexOf(latestOpenKey) === -1) {
openKeys.value = openKeysParams;
} else {
openKeys.value = latestOpenKey ? [latestOpenKey] : [];
}
} }
const latestOpenKey = openKeysParams.find(key => openKeys.value.indexOf(key) === -1);
if (rootSubmenuKeys.value.indexOf(latestOpenKey) === -1) {
openKeys.value = openKeysParams;
} else {
openKeys.value = latestOpenKey ? [latestOpenKey] : [];
}
};
defineExpose({ defineExpose({
updateOpenKeysAndSelectKeys, updateOpenKeysAndSelectKeys,
}); });

View File

@ -8,7 +8,6 @@
* @Copyright 1024 https://1024lab.net Since 2012 * @Copyright 1024 https://1024lab.net Since 2012
*/ */
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import { smartSentry } from '/@/lib/smart-sentry.js';
export const useSpinStore = defineStore({ export const useSpinStore = defineStore({
id: 'spin', id: 'spin',
@ -19,27 +18,13 @@ export const useSpinStore = defineStore({
actions: { actions: {
hide() { hide() {
this.loading = false; this.loading = false;
// 安全的DOM操作避免null引用错误 let spins = document.querySelector('.ant-spin-nested-loading');
try { spins.style.zIndex = 999;
const spins = document.querySelector('.ant-spin-nested-loading');
if (spins) {
spins.style.zIndex = '999';
}
} catch (error) {
smartSentry.captureError('Spin hide操作失败:', error);
}
}, },
show() { show() {
this.loading = true; this.loading = true;
// 安全的DOM操作避免null引用错误 let spins = document.querySelector('.ant-spin-nested-loading');
try { spins.style.zIndex = 1001;
const spins = document.querySelector('.ant-spin-nested-loading');
if (spins) {
spins.style.zIndex = '1001';
}
} catch (error) {
smartSentry.captureError('Spin hide操作失败:', error);
}
}, },
}, },
}); });

View File

@ -149,6 +149,7 @@
v-model:pageSize="queryForm.pageSize" v-model:pageSize="queryForm.pageSize"
:total="total" :total="total"
@change="queryData" @change="queryData"
@showSizeChange="queryData"
:show-total="(total) => `共${total}条`" :show-total="(total) => `共${total}条`"
/> />
</div> </div>

View File

@ -78,6 +78,7 @@
v-model:pageSize="queryForm.pageSize" v-model:pageSize="queryForm.pageSize"
:total="total" :total="total"
@change="ajaxQuery" @change="ajaxQuery"
@showSizeChange="ajaxQuery"
:show-total="(total) => `共${total}条`" :show-total="(total) => `共${total}条`"
/> />
</div> </div>

View File

@ -63,6 +63,7 @@
v-model:pageSize="queryForm.pageSize" v-model:pageSize="queryForm.pageSize"
:total="total" :total="total"
@change="queryEmployee" @change="queryEmployee"
@showSizeChange="queryEmployee"
:show-total="showTableTotal" :show-total="showTableTotal"
/> />
</div> </div>

View File

@ -75,6 +75,7 @@
v-model:pageSize="queryForm.pageSize" v-model:pageSize="queryForm.pageSize"
:total="total" :total="total"
@change="ajaxQuery" @change="ajaxQuery"
@showSizeChange="ajaxQuery"
:show-total="(total) => `共${total}条`" :show-total="(total) => `共${total}条`"
/> />
</div> </div>

Some files were not shown because too many files have changed in this diff Show More