feat: 用户操作日志.
This commit is contained in:
@@ -53,13 +53,13 @@ export interface ${vue.featureEntity}QueryRequest extends Pagination {
|
|||||||
export interface ${vue.featureEntity}QueryResponse extends TableData {
|
export interface ${vue.featureEntity}QueryResponse extends TableData {
|
||||||
#foreach($field in ${table.fields})
|
#foreach($field in ${table.fields})
|
||||||
#if("$field.propertyType" == "String")
|
#if("$field.propertyType" == "String")
|
||||||
${field.propertyName}?: string;
|
${field.propertyName}: string;
|
||||||
#elseif("$field.propertyType" == "Integer" || "$field.propertyType" == "Long" || "$field.propertyType" == "Date")
|
#elseif("$field.propertyType" == "Integer" || "$field.propertyType" == "Long" || "$field.propertyType" == "Date")
|
||||||
${field.propertyName}?: number;
|
${field.propertyName}: number;
|
||||||
#elseif("$field.propertyType" == "Boolean")
|
#elseif("$field.propertyType" == "Boolean")
|
||||||
${field.propertyName}?: boolean;
|
${field.propertyName}: boolean;
|
||||||
#else
|
#else
|
||||||
${field.propertyName}?: any;
|
${field.propertyName}: any;
|
||||||
#end
|
#end
|
||||||
#end
|
#end
|
||||||
createTime: number;
|
createTime: number;
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import org.springframework.data.redis.core.RedisTemplate;
|
|||||||
import org.springframework.data.redis.core.ScanOptions;
|
import org.springframework.data.redis.core.ScanOptions;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -92,7 +92,7 @@ public class RedisUtils {
|
|||||||
*
|
*
|
||||||
* @param keys keys
|
* @param keys keys
|
||||||
*/
|
*/
|
||||||
public static void delete(List<String> keys) {
|
public static void delete(Collection<String> keys) {
|
||||||
redisTemplate.delete(keys);
|
redisTemplate.delete(keys);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -161,7 +161,7 @@ orion:
|
|||||||
# 下面引用了 需要注意
|
# 下面引用了 需要注意
|
||||||
field:
|
field:
|
||||||
ignore:
|
ignore:
|
||||||
- password,beforePassword,newPassword,useNewPassword,publicKey,privateKey
|
- password,beforePassword,newPassword,useNewPassword,publicKey,privateKey,token
|
||||||
- metrics
|
- metrics
|
||||||
desensitize:
|
desensitize:
|
||||||
storage:
|
storage:
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import com.orion.ops.framework.log.core.annotation.IgnoreLog;
|
|||||||
import com.orion.ops.framework.log.core.enums.IgnoreLogMode;
|
import com.orion.ops.framework.log.core.enums.IgnoreLogMode;
|
||||||
import com.orion.ops.framework.web.core.annotation.RestWrapper;
|
import com.orion.ops.framework.web.core.annotation.RestWrapper;
|
||||||
import com.orion.ops.module.infra.define.operator.AuthenticationOperatorType;
|
import com.orion.ops.module.infra.define.operator.AuthenticationOperatorType;
|
||||||
import com.orion.ops.module.infra.entity.request.user.OfflineUserSessionRequest;
|
import com.orion.ops.module.infra.entity.request.user.UserSessionOfflineRequest;
|
||||||
import com.orion.ops.module.infra.entity.request.user.SystemUserUpdateRequest;
|
import com.orion.ops.module.infra.entity.request.user.SystemUserUpdateRequest;
|
||||||
import com.orion.ops.module.infra.entity.request.user.UserUpdatePasswordRequest;
|
import com.orion.ops.module.infra.entity.request.user.UserUpdatePasswordRequest;
|
||||||
import com.orion.ops.module.infra.entity.vo.LoginHistoryVO;
|
import com.orion.ops.module.infra.entity.vo.LoginHistoryVO;
|
||||||
@@ -79,12 +79,11 @@ public class MineController {
|
|||||||
@IgnoreLog(IgnoreLogMode.RET)
|
@IgnoreLog(IgnoreLogMode.RET)
|
||||||
@PutMapping("/offline-session")
|
@PutMapping("/offline-session")
|
||||||
@Operation(summary = "下线当前用户会话")
|
@Operation(summary = "下线当前用户会话")
|
||||||
public HttpWrapper<?> offlineCurrentUserSession(@Validated @RequestBody OfflineUserSessionRequest request) {
|
public HttpWrapper<?> offlineCurrentUserSession(@Validated @RequestBody UserSessionOfflineRequest request) {
|
||||||
mineService.offlineCurrentUserSession(request);
|
mineService.offlineCurrentUserSession(request);
|
||||||
return HttpWrapper.ok();
|
return HttpWrapper.ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
// fixme 全部用户接口进行 设置缓存
|
|
||||||
// fixme 操作日志
|
// fixme 操作日志
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,6 +99,7 @@ public class SystemRoleController {
|
|||||||
|
|
||||||
@GetMapping("/get-menu-id")
|
@GetMapping("/get-menu-id")
|
||||||
@Operation(summary = "获取角色菜单id")
|
@Operation(summary = "获取角色菜单id")
|
||||||
|
@Parameter(name = "roleId", description = "roleId", required = true)
|
||||||
@PreAuthorize("@ss.hasPermission('infra:system-role:query')")
|
@PreAuthorize("@ss.hasPermission('infra:system-role:query')")
|
||||||
public List<Long> getRoleMenuIdList(@RequestParam("roleId") Long roleId) {
|
public List<Long> getRoleMenuIdList(@RequestParam("roleId") Long roleId) {
|
||||||
return systemRoleMenuService.getRoleMenuIdList(roleId);
|
return systemRoleMenuService.getRoleMenuIdList(roleId);
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ public class SystemUserController {
|
|||||||
@Operation(summary = "查询所有用户")
|
@Operation(summary = "查询所有用户")
|
||||||
@PreAuthorize("@ss.hasPermission('infra:system-user:query')")
|
@PreAuthorize("@ss.hasPermission('infra:system-user:query')")
|
||||||
public List<SystemUserVO> getSystemUserList() {
|
public List<SystemUserVO> getSystemUserList() {
|
||||||
return systemUserService.getSystemUserByIdList();
|
return systemUserService.getSystemUserList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@IgnoreLog(IgnoreLogMode.RET)
|
@IgnoreLog(IgnoreLogMode.RET)
|
||||||
@@ -150,7 +150,7 @@ public class SystemUserController {
|
|||||||
@IgnoreLog(IgnoreLogMode.RET)
|
@IgnoreLog(IgnoreLogMode.RET)
|
||||||
@PutMapping("/offline-session")
|
@PutMapping("/offline-session")
|
||||||
@Operation(summary = "下线用户会话")
|
@Operation(summary = "下线用户会话")
|
||||||
public HttpWrapper<?> offlineUserSession(@Validated @RequestBody OfflineUserSessionRequest request) {
|
public HttpWrapper<?> offlineUserSession(@Validated @RequestBody UserSessionOfflineRequest request) {
|
||||||
systemUserService.offlineUserSession(request);
|
systemUserService.offlineUserSession(request);
|
||||||
return HttpWrapper.ok();
|
return HttpWrapper.ok();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.orion.ops.module.infra.convert;
|
|||||||
|
|
||||||
import com.orion.ops.framework.common.security.LoginUser;
|
import com.orion.ops.framework.common.security.LoginUser;
|
||||||
import com.orion.ops.module.infra.entity.domain.SystemUserDO;
|
import com.orion.ops.module.infra.entity.domain.SystemUserDO;
|
||||||
|
import com.orion.ops.module.infra.entity.dto.UserInfoDTO;
|
||||||
import com.orion.ops.module.infra.entity.request.user.SystemUserCreateRequest;
|
import com.orion.ops.module.infra.entity.request.user.SystemUserCreateRequest;
|
||||||
import com.orion.ops.module.infra.entity.request.user.SystemUserQueryRequest;
|
import com.orion.ops.module.infra.entity.request.user.SystemUserQueryRequest;
|
||||||
import com.orion.ops.module.infra.entity.request.user.SystemUserUpdateRequest;
|
import com.orion.ops.module.infra.entity.request.user.SystemUserUpdateRequest;
|
||||||
@@ -35,10 +36,14 @@ public interface SystemUserConvert {
|
|||||||
|
|
||||||
SystemUserVO to(SystemUserDO domain);
|
SystemUserVO to(SystemUserDO domain);
|
||||||
|
|
||||||
|
SystemUserVO to(UserInfoDTO user);
|
||||||
|
|
||||||
List<SystemUserVO> to(List<SystemUserDO> list);
|
List<SystemUserVO> to(List<SystemUserDO> list);
|
||||||
|
|
||||||
LoginUser toLoginUser(SystemUserDO domain);
|
LoginUser toLoginUser(SystemUserDO domain);
|
||||||
|
|
||||||
|
UserInfoDTO toUserInfo(SystemUserDO domain);
|
||||||
|
|
||||||
UserCollectInfoVO toCollectInfo(LoginUser user);
|
UserCollectInfoVO toCollectInfo(LoginUser user);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import com.orion.lang.define.cache.CacheKeyBuilder;
|
|||||||
import com.orion.lang.define.cache.CacheKeyDefine;
|
import com.orion.lang.define.cache.CacheKeyDefine;
|
||||||
import com.orion.ops.framework.common.security.LoginUser;
|
import com.orion.ops.framework.common.security.LoginUser;
|
||||||
import com.orion.ops.module.infra.entity.dto.LoginTokenDTO;
|
import com.orion.ops.module.infra.entity.dto.LoginTokenDTO;
|
||||||
|
import com.orion.ops.module.infra.entity.dto.UserInfoDTO;
|
||||||
|
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
@@ -22,9 +23,17 @@ public interface UserCacheKeyDefine {
|
|||||||
.type(LoginUser.class)
|
.type(LoginUser.class)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
CacheKeyDefine USER_LIST = new CacheKeyBuilder()
|
||||||
|
.key("user:list:{}")
|
||||||
|
.desc("用户列表")
|
||||||
|
.type(UserInfoDTO.class)
|
||||||
|
.timeout(1, TimeUnit.DAYS)
|
||||||
|
.build();
|
||||||
|
|
||||||
CacheKeyDefine LOGIN_FAILED_COUNT = new CacheKeyBuilder()
|
CacheKeyDefine LOGIN_FAILED_COUNT = new CacheKeyBuilder()
|
||||||
.key("user:failed:{}")
|
.key("user:failed:{}")
|
||||||
.desc("用户登录失败次数 ${username}")
|
.desc("用户登录失败次数 ${username}")
|
||||||
|
.type(Integer.class)
|
||||||
.timeout(3, TimeUnit.DAYS)
|
.timeout(3, TimeUnit.DAYS)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package com.orion.ops.module.infra.entity.dto;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户信息 缓存对象
|
||||||
|
*
|
||||||
|
* @author Jiahang Li
|
||||||
|
* @version 1.0.0
|
||||||
|
* @since 2023-7-13 18:42
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Schema(name = "UserInfoDTO", description = "用户信息 缓存对象")
|
||||||
|
public class UserInfoDTO implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Schema(description = "id")
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Schema(description = "用户名")
|
||||||
|
private String username;
|
||||||
|
|
||||||
|
@Schema(description = "花名")
|
||||||
|
private String nickname;
|
||||||
|
|
||||||
|
@Schema(description = "头像地址")
|
||||||
|
private String avatar;
|
||||||
|
|
||||||
|
@Schema(description = "手机号")
|
||||||
|
private String mobile;
|
||||||
|
|
||||||
|
@Schema(description = "邮箱")
|
||||||
|
private String email;
|
||||||
|
|
||||||
|
@Schema(description = "用户状态 0停用 1启用 2锁定")
|
||||||
|
private Integer status;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -6,15 +6,15 @@ import lombok.Data;
|
|||||||
import javax.validation.constraints.NotNull;
|
import javax.validation.constraints.NotNull;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户下线请求
|
* 用户会话下线请求
|
||||||
*
|
*
|
||||||
* @author Jiahang Li
|
* @author Jiahang Li
|
||||||
* @version 1.0.0
|
* @version 1.0.0
|
||||||
* @since 2023/7/17 12:19
|
* @since 2023/7/17 12:19
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
@Schema(name = "OfflineUserSessionRequest", description = "用户下线请求")
|
@Schema(name = "UserSessionOfflineRequest", description = "用户会话下线请求")
|
||||||
public class OfflineUserSessionRequest {
|
public class UserSessionOfflineRequest {
|
||||||
|
|
||||||
@Schema(description = "userId")
|
@Schema(description = "userId")
|
||||||
private Long userId;
|
private Long userId;
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
package com.orion.ops.module.infra.service;
|
package com.orion.ops.module.infra.service;
|
||||||
|
|
||||||
import com.orion.ops.module.infra.entity.request.user.OfflineUserSessionRequest;
|
import com.orion.ops.module.infra.entity.request.user.UserSessionOfflineRequest;
|
||||||
import com.orion.ops.module.infra.entity.request.user.SystemUserUpdateRequest;
|
import com.orion.ops.module.infra.entity.request.user.SystemUserUpdateRequest;
|
||||||
import com.orion.ops.module.infra.entity.request.user.UserUpdatePasswordRequest;
|
import com.orion.ops.module.infra.entity.request.user.UserUpdatePasswordRequest;
|
||||||
import com.orion.ops.module.infra.entity.vo.LoginHistoryVO;
|
import com.orion.ops.module.infra.entity.vo.LoginHistoryVO;
|
||||||
@@ -59,6 +59,6 @@ public interface MineService {
|
|||||||
*
|
*
|
||||||
* @param request request
|
* @param request request
|
||||||
*/
|
*/
|
||||||
void offlineCurrentUserSession(OfflineUserSessionRequest request);
|
void offlineCurrentUserSession(UserSessionOfflineRequest request);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ public interface SystemUserService {
|
|||||||
*
|
*
|
||||||
* @return rows
|
* @return rows
|
||||||
*/
|
*/
|
||||||
List<SystemUserVO> getSystemUserByIdList();
|
List<SystemUserVO> getSystemUserList();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 分页查询用户
|
* 分页查询用户
|
||||||
@@ -74,9 +74,10 @@ public interface SystemUserService {
|
|||||||
/**
|
/**
|
||||||
* 删除 id 删除用户拓展信息
|
* 删除 id 删除用户拓展信息
|
||||||
*
|
*
|
||||||
* @param id id
|
* @param id id
|
||||||
|
* @param username username
|
||||||
*/
|
*/
|
||||||
void deleteSystemUserRel(Long id);
|
void deleteSystemUserRel(Long id, String username);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 重置密码
|
* 重置密码
|
||||||
@@ -98,6 +99,6 @@ public interface SystemUserService {
|
|||||||
*
|
*
|
||||||
* @param request request
|
* @param request request
|
||||||
*/
|
*/
|
||||||
void offlineUserSession(OfflineUserSessionRequest request);
|
void offlineUserSession(UserSessionOfflineRequest request);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import com.orion.ops.framework.common.utils.Valid;
|
|||||||
import com.orion.ops.framework.security.core.utils.SecurityUtils;
|
import com.orion.ops.framework.security.core.utils.SecurityUtils;
|
||||||
import com.orion.ops.module.infra.dao.SystemUserDAO;
|
import com.orion.ops.module.infra.dao.SystemUserDAO;
|
||||||
import com.orion.ops.module.infra.entity.domain.SystemUserDO;
|
import com.orion.ops.module.infra.entity.domain.SystemUserDO;
|
||||||
import com.orion.ops.module.infra.entity.request.user.OfflineUserSessionRequest;
|
import com.orion.ops.module.infra.entity.request.user.UserSessionOfflineRequest;
|
||||||
import com.orion.ops.module.infra.entity.request.user.SystemUserUpdateRequest;
|
import com.orion.ops.module.infra.entity.request.user.SystemUserUpdateRequest;
|
||||||
import com.orion.ops.module.infra.entity.request.user.UserResetPasswordRequest;
|
import com.orion.ops.module.infra.entity.request.user.UserResetPasswordRequest;
|
||||||
import com.orion.ops.module.infra.entity.request.user.UserUpdatePasswordRequest;
|
import com.orion.ops.module.infra.entity.request.user.UserUpdatePasswordRequest;
|
||||||
@@ -81,7 +81,7 @@ public class MineServiceImpl implements MineService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void offlineCurrentUserSession(OfflineUserSessionRequest request) {
|
public void offlineCurrentUserSession(UserSessionOfflineRequest request) {
|
||||||
request.setUserId(SecurityUtils.getLoginUserId());
|
request.setUserId(SecurityUtils.getLoginUserId());
|
||||||
systemUserService.offlineUserSession(request);
|
systemUserService.offlineUserSession(request);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,10 +6,12 @@ import com.orion.lang.define.wrapper.DataGrid;
|
|||||||
import com.orion.lang.utils.collect.Lists;
|
import com.orion.lang.utils.collect.Lists;
|
||||||
import com.orion.lang.utils.crypto.Signatures;
|
import com.orion.lang.utils.crypto.Signatures;
|
||||||
import com.orion.ops.framework.biz.operator.log.core.uitls.OperatorLogs;
|
import com.orion.ops.framework.biz.operator.log.core.uitls.OperatorLogs;
|
||||||
|
import com.orion.ops.framework.common.constant.Const;
|
||||||
import com.orion.ops.framework.common.constant.ErrorCode;
|
import com.orion.ops.framework.common.constant.ErrorCode;
|
||||||
import com.orion.ops.framework.common.constant.ErrorMessage;
|
import com.orion.ops.framework.common.constant.ErrorMessage;
|
||||||
import com.orion.ops.framework.common.security.LoginUser;
|
import com.orion.ops.framework.common.security.LoginUser;
|
||||||
import com.orion.ops.framework.common.utils.Valid;
|
import com.orion.ops.framework.common.utils.Valid;
|
||||||
|
import com.orion.ops.framework.redis.core.utils.RedisMaps;
|
||||||
import com.orion.ops.framework.redis.core.utils.RedisStrings;
|
import com.orion.ops.framework.redis.core.utils.RedisStrings;
|
||||||
import com.orion.ops.framework.redis.core.utils.RedisUtils;
|
import com.orion.ops.framework.redis.core.utils.RedisUtils;
|
||||||
import com.orion.ops.framework.security.core.utils.SecurityUtils;
|
import com.orion.ops.framework.security.core.utils.SecurityUtils;
|
||||||
@@ -21,9 +23,11 @@ import com.orion.ops.module.infra.define.cache.TipsCacheKeyDefine;
|
|||||||
import com.orion.ops.module.infra.define.cache.UserCacheKeyDefine;
|
import com.orion.ops.module.infra.define.cache.UserCacheKeyDefine;
|
||||||
import com.orion.ops.module.infra.entity.domain.SystemUserDO;
|
import com.orion.ops.module.infra.entity.domain.SystemUserDO;
|
||||||
import com.orion.ops.module.infra.entity.dto.LoginTokenDTO;
|
import com.orion.ops.module.infra.entity.dto.LoginTokenDTO;
|
||||||
|
import com.orion.ops.module.infra.entity.dto.UserInfoDTO;
|
||||||
import com.orion.ops.module.infra.entity.request.user.*;
|
import com.orion.ops.module.infra.entity.request.user.*;
|
||||||
import com.orion.ops.module.infra.entity.vo.SystemUserVO;
|
import com.orion.ops.module.infra.entity.vo.SystemUserVO;
|
||||||
import com.orion.ops.module.infra.entity.vo.UserSessionVO;
|
import com.orion.ops.module.infra.entity.vo.UserSessionVO;
|
||||||
|
import com.orion.ops.module.infra.enums.LoginTokenStatusEnum;
|
||||||
import com.orion.ops.module.infra.enums.UserStatusEnum;
|
import com.orion.ops.module.infra.enums.UserStatusEnum;
|
||||||
import com.orion.ops.module.infra.service.AuthenticationService;
|
import com.orion.ops.module.infra.service.AuthenticationService;
|
||||||
import com.orion.ops.module.infra.service.FavoriteService;
|
import com.orion.ops.module.infra.service.FavoriteService;
|
||||||
@@ -31,7 +35,6 @@ import com.orion.ops.module.infra.service.PreferenceService;
|
|||||||
import com.orion.ops.module.infra.service.SystemUserService;
|
import com.orion.ops.module.infra.service.SystemUserService;
|
||||||
import com.orion.spring.SpringHolder;
|
import com.orion.spring.SpringHolder;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.data.redis.core.RedisTemplate;
|
|
||||||
import org.springframework.scheduling.annotation.Async;
|
import org.springframework.scheduling.annotation.Async;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
@@ -68,9 +71,6 @@ public class SystemUserServiceImpl implements SystemUserService {
|
|||||||
@Resource
|
@Resource
|
||||||
private PreferenceService preferenceService;
|
private PreferenceService preferenceService;
|
||||||
|
|
||||||
@Resource
|
|
||||||
private RedisTemplate<String, String> redisTemplate;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Long createSystemUser(SystemUserCreateRequest request) {
|
public Long createSystemUser(SystemUserCreateRequest request) {
|
||||||
// 转换
|
// 转换
|
||||||
@@ -82,6 +82,8 @@ public class SystemUserServiceImpl implements SystemUserService {
|
|||||||
// 插入
|
// 插入
|
||||||
int effect = systemUserDAO.insert(record);
|
int effect = systemUserDAO.insert(record);
|
||||||
log.info("SystemUserService-createSystemUser effect: {}, record: {}", effect, JSON.toJSONString(record));
|
log.info("SystemUserService-createSystemUser effect: {}, record: {}", effect, JSON.toJSONString(record));
|
||||||
|
// 删除用户列表缓存
|
||||||
|
RedisUtils.delete(UserCacheKeyDefine.USER_LIST);
|
||||||
return record.getId();
|
return record.getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,6 +106,8 @@ public class SystemUserServiceImpl implements SystemUserService {
|
|||||||
RedisStrings.<LoginUser>processSetJson(UserCacheKeyDefine.USER_INFO, s -> {
|
RedisStrings.<LoginUser>processSetJson(UserCacheKeyDefine.USER_INFO, s -> {
|
||||||
s.setNickname(request.getNickname());
|
s.setNickname(request.getNickname());
|
||||||
}, id);
|
}, id);
|
||||||
|
// 删除用户列表缓存
|
||||||
|
RedisUtils.delete(UserCacheKeyDefine.USER_LIST);
|
||||||
return effect;
|
return effect;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,12 +135,14 @@ public class SystemUserServiceImpl implements SystemUserService {
|
|||||||
log.info("SystemUserService-updateUserStatus effect: {}, updateRecord: {}", effect, JSON.toJSONString(updateRecord));
|
log.info("SystemUserService-updateUserStatus effect: {}, updateRecord: {}", effect, JSON.toJSONString(updateRecord));
|
||||||
// 如果之前是锁定则删除登录失败次数缓存
|
// 如果之前是锁定则删除登录失败次数缓存
|
||||||
if (UserStatusEnum.LOCKED.getStatus().equals(record.getStatus())) {
|
if (UserStatusEnum.LOCKED.getStatus().equals(record.getStatus())) {
|
||||||
redisTemplate.delete(UserCacheKeyDefine.LOGIN_FAILED_COUNT.format(record.getUsername()));
|
RedisUtils.delete(UserCacheKeyDefine.LOGIN_FAILED_COUNT.format(record.getUsername()));
|
||||||
}
|
}
|
||||||
// 更新缓存中的status
|
// 更新用户缓存中的 status
|
||||||
RedisStrings.<LoginUser>processSetJson(UserCacheKeyDefine.USER_INFO, s -> {
|
RedisStrings.<LoginUser>processSetJson(UserCacheKeyDefine.USER_INFO, s -> {
|
||||||
s.setStatus(request.getStatus());
|
s.setStatus(request.getStatus());
|
||||||
}, id);
|
}, id);
|
||||||
|
// 删除用户列表缓存
|
||||||
|
RedisUtils.delete(UserCacheKeyDefine.USER_LIST);
|
||||||
return effect;
|
return effect;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,14 +156,28 @@ public class SystemUserServiceImpl implements SystemUserService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<SystemUserVO> getSystemUserByIdList() {
|
public List<SystemUserVO> getSystemUserList() {
|
||||||
// 查询
|
// fixme test
|
||||||
List<SystemUserDO> records = systemUserDAO.selectList(null);
|
// 查询用户列表
|
||||||
if (records.isEmpty()) {
|
List<UserInfoDTO> list = RedisMaps.valuesJson(UserCacheKeyDefine.USER_LIST);
|
||||||
return Lists.empty();
|
if (list.isEmpty()) {
|
||||||
|
// 查询数据库
|
||||||
|
list = systemUserDAO.of().list(SystemUserConvert.MAPPER::toUserInfo);
|
||||||
|
// 添加默认值 防止穿透
|
||||||
|
if (list.isEmpty()) {
|
||||||
|
list.add(UserInfoDTO.builder()
|
||||||
|
.id(Const.NONE_ID)
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
// 设置缓存
|
||||||
|
RedisMaps.putAllJson(UserCacheKeyDefine.USER_LIST.getKey(), s -> s.getId().toString(), list);
|
||||||
|
RedisMaps.setExpire(UserCacheKeyDefine.USER_LIST);
|
||||||
}
|
}
|
||||||
// 转换
|
// 删除默认值
|
||||||
return SystemUserConvert.MAPPER.to(records);
|
return list.stream()
|
||||||
|
.filter(s -> !s.getId().equals(Const.NONE_ID))
|
||||||
|
.map(SystemUserConvert.MAPPER::to)
|
||||||
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -190,21 +210,26 @@ public class SystemUserServiceImpl implements SystemUserService {
|
|||||||
int effect = systemUserDAO.deleteById(id);
|
int effect = systemUserDAO.deleteById(id);
|
||||||
log.info("SystemUserService-deleteSystemUserById id: {}, effect: {}", id, effect);
|
log.info("SystemUserService-deleteSystemUserById id: {}, effect: {}", id, effect);
|
||||||
// 异步删除额外信息
|
// 异步删除额外信息
|
||||||
SpringHolder.getBean(SystemUserService.class).deleteSystemUserRel(id);
|
SpringHolder.getBean(SystemUserService.class).deleteSystemUserRel(id, record.getUsername());
|
||||||
return effect;
|
return effect;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Async("asyncExecutor")
|
@Async("asyncExecutor")
|
||||||
public void deleteSystemUserRel(Long id) {
|
public void deleteSystemUserRel(Long id, String username) {
|
||||||
log.info("SystemUserService-deleteSystemUserRel id: {}", id);
|
log.info("SystemUserService-deleteSystemUserRel id: {}", id);
|
||||||
|
// 删除用户列表缓存
|
||||||
|
// FIXME test
|
||||||
|
RedisMaps.delete(UserCacheKeyDefine.USER_LIST, id);
|
||||||
// 删除用户缓存 需要扫描的 key 让其自动过期
|
// 删除用户缓存 需要扫描的 key 让其自动过期
|
||||||
redisTemplate.delete(Lists.of(
|
RedisUtils.delete(
|
||||||
// 用户缓存
|
// 用户缓存
|
||||||
UserCacheKeyDefine.USER_INFO.format(id),
|
UserCacheKeyDefine.USER_INFO.format(id),
|
||||||
|
// 登录失败次数
|
||||||
|
UserCacheKeyDefine.LOGIN_FAILED_COUNT.format(username),
|
||||||
// 用户提示
|
// 用户提示
|
||||||
TipsCacheKeyDefine.TIPS.format(id)
|
TipsCacheKeyDefine.TIPS.format(id)
|
||||||
));
|
);
|
||||||
// 删除角色关联
|
// 删除角色关联
|
||||||
systemUserRoleDAO.deleteByUserId(id);
|
systemUserRoleDAO.deleteByUserId(id);
|
||||||
// 删除操作日志
|
// 删除操作日志
|
||||||
@@ -230,19 +255,19 @@ public class SystemUserServiceImpl implements SystemUserService {
|
|||||||
int effect = systemUserDAO.updateById(update);
|
int effect = systemUserDAO.updateById(update);
|
||||||
log.info("SystemUserService-resetPassword record: {}, effect: {}", JSON.toJSONString(update), effect);
|
log.info("SystemUserService-resetPassword record: {}, effect: {}", JSON.toJSONString(update), effect);
|
||||||
// 删除登录失败次数缓存
|
// 删除登录失败次数缓存
|
||||||
redisTemplate.delete(UserCacheKeyDefine.LOGIN_FAILED_COUNT.format(record.getUsername()));
|
RedisUtils.delete(UserCacheKeyDefine.LOGIN_FAILED_COUNT.format(record.getUsername()));
|
||||||
// 删除登录缓存
|
// 删除登录缓存
|
||||||
String loginKey = UserCacheKeyDefine.LOGIN_TOKEN.format(id, "*");
|
String loginKey = UserCacheKeyDefine.LOGIN_TOKEN.format(id, "*");
|
||||||
Set<String> loginKeyList = RedisUtils.scanKeys(loginKey);
|
Set<String> loginKeyList = RedisUtils.scanKeys(loginKey);
|
||||||
if (!loginKeyList.isEmpty()) {
|
if (!loginKeyList.isEmpty()) {
|
||||||
redisTemplate.delete(loginKeyList);
|
RedisUtils.delete(loginKeyList);
|
||||||
}
|
}
|
||||||
// 删除续签信息
|
// 删除续签信息
|
||||||
if (AuthenticationService.allowRefresh) {
|
if (AuthenticationService.allowRefresh) {
|
||||||
String refreshKey = UserCacheKeyDefine.LOGIN_REFRESH.format(id, "*");
|
String refreshKey = UserCacheKeyDefine.LOGIN_REFRESH.format(id, "*");
|
||||||
Set<String> refreshKeyList = RedisUtils.scanKeys(refreshKey);
|
Set<String> refreshKeyList = RedisUtils.scanKeys(refreshKey);
|
||||||
if (!refreshKeyList.isEmpty()) {
|
if (!refreshKeyList.isEmpty()) {
|
||||||
redisTemplate.delete(refreshKeyList);
|
RedisUtils.delete(refreshKeyList);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -259,22 +284,25 @@ public class SystemUserServiceImpl implements SystemUserService {
|
|||||||
if (Lists.isEmpty(tokens)) {
|
if (Lists.isEmpty(tokens)) {
|
||||||
return Lists.empty();
|
return Lists.empty();
|
||||||
}
|
}
|
||||||
|
final boolean isCurrentUser = userId.equals(SecurityUtils.getLoginUserId());
|
||||||
// 返回
|
// 返回
|
||||||
return tokens.stream()
|
return tokens.stream()
|
||||||
|
.filter(s -> LoginTokenStatusEnum.OK.getStatus().equals(s.getStatus()))
|
||||||
.map(LoginTokenDTO::getOrigin)
|
.map(LoginTokenDTO::getOrigin)
|
||||||
.map(s -> UserSessionVO.builder()
|
.map(s -> UserSessionVO.builder()
|
||||||
.current(s.getLoginTime().equals(SecurityUtils.getLoginTimestamp()))
|
.current(isCurrentUser && s.getLoginTime().equals(SecurityUtils.getLoginTimestamp()))
|
||||||
.address(s.getAddress())
|
.address(s.getAddress())
|
||||||
.location(s.getLocation())
|
.location(s.getLocation())
|
||||||
.userAgent(s.getUserAgent())
|
.userAgent(s.getUserAgent())
|
||||||
.loginTime(new Date(s.getLoginTime()))
|
.loginTime(new Date(s.getLoginTime()))
|
||||||
.build())
|
.build())
|
||||||
.sorted(Comparator.comparing(UserSessionVO::getLoginTime).reversed())
|
.sorted(Comparator.comparing(UserSessionVO::getCurrent).reversed()
|
||||||
|
.thenComparing(Comparator.comparing(UserSessionVO::getLoginTime).reversed()))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void offlineUserSession(OfflineUserSessionRequest request) {
|
public void offlineUserSession(UserSessionOfflineRequest request) {
|
||||||
Long userId = Valid.notNull(request.getUserId());
|
Long userId = Valid.notNull(request.getUserId());
|
||||||
Long timestamp = request.getTimestamp();
|
Long timestamp = request.getTimestamp();
|
||||||
RedisStrings.delete(
|
RedisStrings.delete(
|
||||||
|
|||||||
@@ -35,11 +35,11 @@ export interface HostIdentityQueryRequest extends Pagination {
|
|||||||
* 主机身份查询响应
|
* 主机身份查询响应
|
||||||
*/
|
*/
|
||||||
export interface HostIdentityQueryResponse extends TableData {
|
export interface HostIdentityQueryResponse extends TableData {
|
||||||
id?: number;
|
id: number;
|
||||||
name?: string;
|
name: string;
|
||||||
username?: string;
|
username: string;
|
||||||
password?: string;
|
password: string;
|
||||||
keyId?: number;
|
keyId: number;
|
||||||
createTime: number;
|
createTime: number;
|
||||||
updateTime: number;
|
updateTime: number;
|
||||||
creator: string;
|
creator: string;
|
||||||
|
|||||||
@@ -35,11 +35,11 @@ export interface HostKeyQueryRequest extends Pagination {
|
|||||||
* 主机秘钥查询响应
|
* 主机秘钥查询响应
|
||||||
*/
|
*/
|
||||||
export interface HostKeyQueryResponse extends TableData {
|
export interface HostKeyQueryResponse extends TableData {
|
||||||
id?: number;
|
id: number;
|
||||||
name?: string;
|
name: string;
|
||||||
publicKey?: string;
|
publicKey: string;
|
||||||
privateKey?: string;
|
privateKey: string;
|
||||||
password?: string;
|
password: string;
|
||||||
createTime: number;
|
createTime: number;
|
||||||
updateTime: number;
|
updateTime: number;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,10 +38,10 @@ export interface HostQueryRequest extends Pagination {
|
|||||||
* 主机查询响应
|
* 主机查询响应
|
||||||
*/
|
*/
|
||||||
export interface HostQueryResponse extends TableData {
|
export interface HostQueryResponse extends TableData {
|
||||||
id?: number;
|
id: number;
|
||||||
name?: string;
|
name: string;
|
||||||
code?: string;
|
code: string;
|
||||||
address?: string;
|
address: string;
|
||||||
createTime: number;
|
createTime: number;
|
||||||
updateTime: number;
|
updateTime: number;
|
||||||
creator: string;
|
creator: string;
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ export interface HistoryValueQueryRequest extends Pagination {
|
|||||||
* 历史归档查询响应
|
* 历史归档查询响应
|
||||||
*/
|
*/
|
||||||
export interface HistoryValueQueryResponse extends TableData {
|
export interface HistoryValueQueryResponse extends TableData {
|
||||||
id?: number;
|
id: number;
|
||||||
beforeValue?: string;
|
beforeValue: string;
|
||||||
afterValue?: string;
|
afterValue: string;
|
||||||
createTime: number;
|
createTime: number;
|
||||||
creator: string;
|
creator: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,11 +34,11 @@ export interface DictKeyQueryRequest extends Pagination {
|
|||||||
* 字典配置项查询响应
|
* 字典配置项查询响应
|
||||||
*/
|
*/
|
||||||
export interface DictKeyQueryResponse extends TableData {
|
export interface DictKeyQueryResponse extends TableData {
|
||||||
id?: number;
|
id: number;
|
||||||
keyName?: string;
|
keyName: string;
|
||||||
valueType?: string;
|
valueType: string;
|
||||||
extraSchema?: string;
|
extraSchema: string;
|
||||||
description?: string;
|
description: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -48,14 +48,14 @@ export interface DictValueQueryRequest extends Pagination {
|
|||||||
* 字典配置值查询响应
|
* 字典配置值查询响应
|
||||||
*/
|
*/
|
||||||
export interface DictValueQueryResponse extends TableData {
|
export interface DictValueQueryResponse extends TableData {
|
||||||
id?: number;
|
id: number;
|
||||||
keyId?: number;
|
keyId: number;
|
||||||
keyName?: string;
|
keyName: string;
|
||||||
keyDescription?: string;
|
keyDescription: string;
|
||||||
value?: string;
|
value: string;
|
||||||
label?: string;
|
label: string;
|
||||||
extra?: string;
|
extra: string;
|
||||||
sort?: number;
|
sort: number;
|
||||||
createTime: number;
|
createTime: number;
|
||||||
updateTime: number;
|
updateTime: number;
|
||||||
creator: string;
|
creator: string;
|
||||||
|
|||||||
@@ -37,25 +37,25 @@ export interface MenuQueryRequest {
|
|||||||
* 菜单查询响应
|
* 菜单查询响应
|
||||||
*/
|
*/
|
||||||
export interface MenuQueryResponse extends TableData {
|
export interface MenuQueryResponse extends TableData {
|
||||||
id?: number;
|
id: number;
|
||||||
parentId?: number;
|
parentId: number;
|
||||||
name?: string;
|
name: string;
|
||||||
permission?: string;
|
permission: string;
|
||||||
type?: number;
|
type: number;
|
||||||
sort?: number;
|
sort: number;
|
||||||
visible?: number;
|
visible: number;
|
||||||
status?: number;
|
status: number;
|
||||||
cache?: number;
|
cache: number;
|
||||||
icon?: string;
|
icon: string;
|
||||||
path?: string;
|
path: string;
|
||||||
component?: string;
|
component: string;
|
||||||
children?: Array<MenuQueryResponse>;
|
children: Array<MenuQueryResponse>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询菜单列表
|
* 查询菜单列表
|
||||||
*/
|
*/
|
||||||
export function getMenuList(request?: MenuQueryRequest) {
|
export function getMenuList(request: MenuQueryRequest) {
|
||||||
return axios.post<MenuQueryResponse[]>('/infra/system-menu/list', request);
|
return axios.post<MenuQueryResponse[]>('/infra/system-menu/list', request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { LoginHistoryQueryResponse } from './operator-log';
|
import type { LoginHistoryQueryResponse } from './operator-log';
|
||||||
import type { UserQueryResponse, UserUpdateRequest } from './user';
|
import type { UserQueryResponse, UserSessionQueryResponse, UserSessionOfflineRequest, UserUpdateRequest } from './user';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -38,3 +38,16 @@ export function getCurrentLoginHistory() {
|
|||||||
return axios.get<LoginHistoryQueryResponse[]>('/infra/mine/login-history');
|
return axios.get<LoginHistoryQueryResponse[]>('/infra/mine/login-history');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前用户会话列表
|
||||||
|
*/
|
||||||
|
export function getCurrentUserSessionList() {
|
||||||
|
return axios.get<UserSessionQueryResponse[]>('/infra/mine/user-session');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下线当前用户会话
|
||||||
|
*/
|
||||||
|
export function offlineCurrentUserSession(request: UserSessionOfflineRequest) {
|
||||||
|
return axios.put('/infra/mine/offline-session', request);
|
||||||
|
}
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ export interface OperatorLogQueryResponse {
|
|||||||
module: string;
|
module: string;
|
||||||
type: string;
|
type: string;
|
||||||
logInfo: string;
|
logInfo: string;
|
||||||
|
originLogInfo: string;
|
||||||
extra: string;
|
extra: string;
|
||||||
result: number;
|
result: number;
|
||||||
errorMessage: string;
|
errorMessage: string;
|
||||||
|
|||||||
@@ -40,10 +40,10 @@ export interface RoleQueryRequest extends Pagination {
|
|||||||
* 角色查询响应
|
* 角色查询响应
|
||||||
*/
|
*/
|
||||||
export interface RoleQueryResponse extends TableData {
|
export interface RoleQueryResponse extends TableData {
|
||||||
id?: number;
|
id: number;
|
||||||
name?: string;
|
name: string;
|
||||||
code?: string;
|
code: string;
|
||||||
status?: number;
|
status: number;
|
||||||
createTime: number;
|
createTime: number;
|
||||||
updateTime: number;
|
updateTime: number;
|
||||||
creator: string;
|
creator: string;
|
||||||
|
|||||||
@@ -42,13 +42,13 @@ export interface UserQueryRequest extends Pagination {
|
|||||||
* 用户查询响应
|
* 用户查询响应
|
||||||
*/
|
*/
|
||||||
export interface UserQueryResponse extends TableData {
|
export interface UserQueryResponse extends TableData {
|
||||||
id?: number;
|
id: number;
|
||||||
username?: string;
|
username: string;
|
||||||
nickname?: string;
|
nickname: string;
|
||||||
avatar?: string;
|
avatar: string;
|
||||||
mobile?: string;
|
mobile: string;
|
||||||
email?: string;
|
email: string;
|
||||||
status?: number;
|
status: number;
|
||||||
lastLoginTime?: number;
|
lastLoginTime?: number;
|
||||||
createTime: number;
|
createTime: number;
|
||||||
updateTime: number;
|
updateTime: number;
|
||||||
@@ -56,6 +56,26 @@ export interface UserQueryResponse extends TableData {
|
|||||||
updater: string;
|
updater: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户会话查询响应
|
||||||
|
*/
|
||||||
|
export interface UserSessionQueryResponse {
|
||||||
|
visible: boolean;
|
||||||
|
current: boolean;
|
||||||
|
address: string;
|
||||||
|
location: string;
|
||||||
|
userAgent: string;
|
||||||
|
loginTime: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户会话下线请求
|
||||||
|
*/
|
||||||
|
export interface UserSessionOfflineRequest {
|
||||||
|
userId?: number;
|
||||||
|
timestamp: number;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建用户
|
* 创建用户
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -122,6 +122,18 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx0 {
|
||||||
|
margin: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx2 {
|
||||||
|
margin: 0 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx4 {
|
||||||
|
margin: 0 4px;
|
||||||
|
}
|
||||||
|
|
||||||
.ml4 {
|
.ml4 {
|
||||||
margin-left: 4px;
|
margin-left: 4px;
|
||||||
}
|
}
|
||||||
|
|||||||
61
orion-ops-ui/src/components/user/role/user-selector.vue
Normal file
61
orion-ops-ui/src/components/user/role/user-selector.vue
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
<template>
|
||||||
|
<a-select v-model:model-value="value as any"
|
||||||
|
:options="optionData()"
|
||||||
|
:allow-search="true"
|
||||||
|
:multiple="multiple"
|
||||||
|
:loading="loading"
|
||||||
|
:disabled="loading"
|
||||||
|
:filter-option="filterOption"
|
||||||
|
placeholder="请选择用户" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
name: 'user-selector'
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { PropType } from 'vue';
|
||||||
|
import type { SelectOptionData } from '@arco-design/web-vue';
|
||||||
|
import { computed } from 'vue';
|
||||||
|
import { useCacheStore } from '@/store';
|
||||||
|
import { RoleStatus } from '@/views/user/role/types/const';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: [Number, Array] as PropType<number | Array<number>>,
|
||||||
|
loading: Boolean,
|
||||||
|
multiple: Boolean,
|
||||||
|
});
|
||||||
|
|
||||||
|
const emits = defineEmits(['update:modelValue']);
|
||||||
|
|
||||||
|
const value = computed({
|
||||||
|
get() {
|
||||||
|
return props.modelValue;
|
||||||
|
},
|
||||||
|
set(e) {
|
||||||
|
emits('update:modelValue', e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 选项数据
|
||||||
|
const cacheStore = useCacheStore();
|
||||||
|
const optionData = (): SelectOptionData[] => {
|
||||||
|
return cacheStore.users.map(s => {
|
||||||
|
return {
|
||||||
|
label: `${s.nickname} (${s.username})`,
|
||||||
|
value: s.id,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 搜索
|
||||||
|
const filterOption = (searchValue: string, option: { label: string; }) => {
|
||||||
|
return option.label.toLowerCase().indexOf(searchValue.toLowerCase()) > -1;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<a-modal v-model:visible="visible"
|
<a-modal v-model:visible="visible"
|
||||||
title-align="start"
|
title-align="start"
|
||||||
width="60%"
|
:width="width"
|
||||||
:body-style="{padding: '16px 8px'}"
|
:body-style="{padding: '16px 8px'}"
|
||||||
:top="80"
|
:top="80"
|
||||||
:title="title"
|
:title="title"
|
||||||
@@ -11,59 +11,57 @@
|
|||||||
:unmount-on-close="true"
|
:unmount-on-close="true"
|
||||||
:footer="false"
|
:footer="false"
|
||||||
@close="handleClose">
|
@close="handleClose">
|
||||||
<a-spin :loading="loading" style="width: 100%; height: calc(100vh - 240px)">
|
<div :style="{width: '100%', 'height': height}">
|
||||||
<editor v-model="value" readonly />
|
<editor v-model="value" readonly />
|
||||||
</a-spin>
|
</div>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export default {
|
export default {
|
||||||
name: 'dict-key-view-modal'
|
name: 'json-view-modal'
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import useLoading from '@/hooks/loading';
|
|
||||||
import useVisible from '@/hooks/visible';
|
import useVisible from '@/hooks/visible';
|
||||||
import { getDictValueList } from '@/api/system/dict-value';
|
import { isString } from '@/utils/is';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
width: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: '60%'
|
||||||
|
},
|
||||||
|
height: {
|
||||||
|
type: String,
|
||||||
|
default: 'calc(100vh - 240px)'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const { visible, setVisible } = useVisible();
|
const { visible, setVisible } = useVisible();
|
||||||
const { loading, setLoading } = useLoading();
|
|
||||||
|
|
||||||
const title = ref<string>();
|
const title = ref<string>();
|
||||||
const value = ref<string>();
|
const value = ref<string | any>();
|
||||||
|
|
||||||
// 打开新增
|
|
||||||
const open = (e: any) => {
|
|
||||||
title.value = e.keyName;
|
|
||||||
value.value = undefined;
|
|
||||||
render(e.keyName);
|
|
||||||
setVisible(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 渲染
|
// 打开
|
||||||
const render = async (keyName: string) => {
|
const open = (editorValue: string | any, editorTitle = 'json') => {
|
||||||
try {
|
title.value = editorTitle;
|
||||||
setLoading(true);
|
if (isString(editorValue)) {
|
||||||
// 查看
|
value.value = editorValue;
|
||||||
const { data } = await getDictValueList([keyName]);
|
} else {
|
||||||
value.value = JSON.stringify(data[keyName], undefined, 4);
|
value.value = JSON.stringify(editorValue, undefined, 4);
|
||||||
} catch (e) {
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
}
|
||||||
|
setVisible(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
defineExpose({ open });
|
defineExpose({ open });
|
||||||
|
|
||||||
// 关闭
|
// 关闭
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
setLoading(false);
|
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
@@ -21,6 +21,11 @@ const USER: AppRouteRecordRaw = {
|
|||||||
path: '/user/info',
|
path: '/user/info',
|
||||||
component: () => import('@/views/user/info/index.vue'),
|
component: () => import('@/views/user/info/index.vue'),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'userOperatorLog',
|
||||||
|
path: '/user/operator-log',
|
||||||
|
component: () => import('@/views/user/operator-log/index.vue'),
|
||||||
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import type { CacheState } from './types';
|
import type { CacheState } from './types';
|
||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
|
|
||||||
export type CacheType = 'menus' | 'roles' | 'hostTags' | 'hostKeys' | 'hostIdentities' | 'dictKeys' | string
|
export type CacheType = 'users' | 'menus' | 'roles' | 'hostTags' | 'hostKeys' | 'hostIdentities' | 'dictKeys' | string
|
||||||
|
|
||||||
export default defineStore('cache', {
|
export default defineStore('cache', {
|
||||||
state: (): CacheState => ({
|
state: (): CacheState => ({
|
||||||
|
users: [],
|
||||||
menus: [],
|
menus: [],
|
||||||
roles: [],
|
roles: [],
|
||||||
hostTags: [],
|
hostTags: [],
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import type { UserQueryResponse } from '@/api/user/user';
|
||||||
import type { MenuQueryResponse } from '@/api/system/menu';
|
import type { MenuQueryResponse } from '@/api/system/menu';
|
||||||
import type { RoleQueryResponse } from '@/api/user/role';
|
import type { RoleQueryResponse } from '@/api/user/role';
|
||||||
import type { TagQueryResponse } from '@/api/meta/tag';
|
import type { TagQueryResponse } from '@/api/meta/tag';
|
||||||
@@ -6,6 +7,7 @@ import type { HostIdentityQueryResponse } from '@/api/asset/host-identity';
|
|||||||
import type { DictKeyQueryResponse } from '@/api/system/dict-key';
|
import type { DictKeyQueryResponse } from '@/api/system/dict-key';
|
||||||
|
|
||||||
export interface CacheState {
|
export interface CacheState {
|
||||||
|
users: UserQueryResponse[];
|
||||||
menus: MenuQueryResponse[];
|
menus: MenuQueryResponse[];
|
||||||
roles: RoleQueryResponse[];
|
roles: RoleQueryResponse[];
|
||||||
hostTags: TagQueryResponse[];
|
hostTags: TagQueryResponse[];
|
||||||
|
|||||||
@@ -15,11 +15,11 @@ export const openWindow = (
|
|||||||
url,
|
url,
|
||||||
target,
|
target,
|
||||||
Object.entries(others)
|
Object.entries(others)
|
||||||
.reduce((preValue: string[], curValue) => {
|
.reduce((preValue: string[], curValue) => {
|
||||||
const [key, value] = curValue;
|
const [key, value] = curValue;
|
||||||
return [...preValue, `${key}=${value}`];
|
return [...preValue, `${key}=${value}`];
|
||||||
}, [])
|
}, [])
|
||||||
.join(',')
|
.join(',')
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -161,10 +161,10 @@ export function replaceNumber(value: string) {
|
|||||||
*/
|
*/
|
||||||
export const resetObject = (obj: any, ignore: string[] = []) => {
|
export const resetObject = (obj: any, ignore: string[] = []) => {
|
||||||
Object.keys(obj)
|
Object.keys(obj)
|
||||||
.filter(s => !ignore.includes(s))
|
.filter(s => !ignore.includes(s))
|
||||||
.forEach(k => {
|
.forEach(k => {
|
||||||
obj[k] = undefined;
|
obj[k] = undefined;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -172,11 +172,11 @@ export const resetObject = (obj: any, ignore: string[] = []) => {
|
|||||||
*/
|
*/
|
||||||
export const objectTruthKeyCount = (obj: any, ignore: string[] = []) => {
|
export const objectTruthKeyCount = (obj: any, ignore: string[] = []) => {
|
||||||
return Object.keys(obj)
|
return Object.keys(obj)
|
||||||
.filter(s => !ignore.includes(s))
|
.filter(s => !ignore.includes(s))
|
||||||
.reduce(function(acc, curr) {
|
.reduce(function(acc, curr) {
|
||||||
const currVal = obj[curr];
|
const currVal = obj[curr];
|
||||||
return acc + ~~(currVal !== undefined && currVal !== null && currVal?.length !== 0 && currVal !== '');
|
return acc + ~~(currVal !== undefined && currVal !== null && currVal?.length !== 0 && currVal !== '');
|
||||||
}, 0);
|
}, 0);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -213,4 +213,52 @@ export function getUUID() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清除 xss
|
||||||
|
*/
|
||||||
|
export function cleanXss(s: string) {
|
||||||
|
return s.replaceAll('&', '&')
|
||||||
|
.replaceAll('<', '<')
|
||||||
|
.replaceAll('>', '>')
|
||||||
|
.replaceAll('\'', ''')
|
||||||
|
.replaceAll('"', '"')
|
||||||
|
.replaceAll('\n', '<br/>')
|
||||||
|
.replaceAll('\t', ' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 替换 html 标签
|
||||||
|
*/
|
||||||
|
export function replaceHtmlTag(message: string) {
|
||||||
|
return cleanXss(message)
|
||||||
|
.replaceAll('<sb 0>', '<span class="span-blue mx0">')
|
||||||
|
.replaceAll('<sb 2>', '<span class="span-blue mx2">')
|
||||||
|
.replaceAll('<sb>', '<span class="span-blue mx4">')
|
||||||
|
.replaceAll('</sb>', '</span>')
|
||||||
|
.replaceAll('<sr 0>', '<span class="span-red mx0">')
|
||||||
|
.replaceAll('<sr 2>', '<span class="span-red mx2">')
|
||||||
|
.replaceAll('<sr>', '<span class="span-red mx4">')
|
||||||
|
.replaceAll('</sr>', '</span>')
|
||||||
|
.replaceAll('<b>', '<b>')
|
||||||
|
.replaceAll('</b>', '</b>');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清除 html 标签
|
||||||
|
*/
|
||||||
|
export function clearHtmlTag(message: string) {
|
||||||
|
return cleanXss(message)
|
||||||
|
.replaceAll('<sb 0>', '')
|
||||||
|
.replaceAll('<sb 2>', '')
|
||||||
|
.replaceAll('<sb>', '')
|
||||||
|
.replaceAll('</sb>', '')
|
||||||
|
.replaceAll('<sr 0>', '')
|
||||||
|
.replaceAll('<sr 2>', '')
|
||||||
|
.replaceAll('<sr>', '')
|
||||||
|
.replaceAll('</sr>', '')
|
||||||
|
.replaceAll('<b>', '')
|
||||||
|
.replaceAll('</b>', '')
|
||||||
|
.replaceAll('<br/>', '\n');
|
||||||
|
}
|
||||||
|
|
||||||
export default null;
|
export default null;
|
||||||
|
|||||||
@@ -51,3 +51,10 @@ export function isExist(obj: any): boolean {
|
|||||||
export function isWindow(el: any): el is Window {
|
export function isWindow(el: any): el is Window {
|
||||||
return el === window;
|
return el === window;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否为移动端 ua
|
||||||
|
*/
|
||||||
|
export function isMobile(userAgent: string) {
|
||||||
|
return /Mobi|Android|iPhone/i.test(userAgent);
|
||||||
|
}
|
||||||
|
|||||||
@@ -100,7 +100,7 @@
|
|||||||
<!-- 查看 -->
|
<!-- 查看 -->
|
||||||
<a-button type="text"
|
<a-button type="text"
|
||||||
size="mini"
|
size="mini"
|
||||||
@click="emits('openView', record)">
|
@click="openView(record)">
|
||||||
查看
|
查看
|
||||||
</a-button>
|
</a-button>
|
||||||
<!-- 修改 -->
|
<!-- 修改 -->
|
||||||
@@ -145,6 +145,7 @@
|
|||||||
import { dictValueTypeKey } from '../types/const';
|
import { dictValueTypeKey } from '../types/const';
|
||||||
import useCopy from '@/hooks/copy';
|
import useCopy from '@/hooks/copy';
|
||||||
import { useDictStore } from '@/store';
|
import { useDictStore } from '@/store';
|
||||||
|
import { getDictValueList } from '@/api/system/dict-value';
|
||||||
|
|
||||||
const tableRenderData = ref<DictKeyQueryResponse[]>([]);
|
const tableRenderData = ref<DictKeyQueryResponse[]>([]);
|
||||||
const emits = defineEmits(['openAdd', 'openUpdate', 'openView']);
|
const emits = defineEmits(['openAdd', 'openUpdate', 'openView']);
|
||||||
@@ -191,6 +192,19 @@
|
|||||||
addedCallback, updatedCallback
|
addedCallback, updatedCallback
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 打开查看视图
|
||||||
|
const openView = async (record: DictKeyQueryResponse) => {
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
// 查看
|
||||||
|
const { data } = await getDictValueList([record.keyName]);
|
||||||
|
emits('openView', data[record.keyName], `${record.keyName} - ${record.description}`);
|
||||||
|
} catch (e) {
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 刷新缓存
|
// 刷新缓存
|
||||||
const doRefreshCache = async () => {
|
const doRefreshCache = async () => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -4,13 +4,13 @@
|
|||||||
<dict-key-table ref="table"
|
<dict-key-table ref="table"
|
||||||
@openAdd="() => modal.openAdd()"
|
@openAdd="() => modal.openAdd()"
|
||||||
@openUpdate="(e) => modal.openUpdate(e)"
|
@openUpdate="(e) => modal.openUpdate(e)"
|
||||||
@openView="(e) => view.open(e)" />
|
@openView="(v, t) => view.open(v, t)" />
|
||||||
<!-- 添加修改模态框 -->
|
<!-- 添加修改模态框 -->
|
||||||
<dict-key-form-modal ref="modal"
|
<dict-key-form-modal ref="modal"
|
||||||
@added="modalAddCallback"
|
@added="modalAddCallback"
|
||||||
@updated="modalUpdateCallback" />
|
@updated="modalUpdateCallback" />
|
||||||
<!-- json 查看器模态框 -->
|
<!-- json 查看器模态框 -->
|
||||||
<dict-key-view-modal ref="view" />
|
<json-view-modal ref="view" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
import { ref, onBeforeMount } from 'vue';
|
import { ref, onBeforeMount } from 'vue';
|
||||||
import DictKeyTable from './components/dict-key-table.vue';
|
import DictKeyTable from './components/dict-key-table.vue';
|
||||||
import DictKeyFormModal from './components/dict-key-form-modal.vue';
|
import DictKeyFormModal from './components/dict-key-form-modal.vue';
|
||||||
import DictKeyViewModal from './components/dict-key-view-modal.vue';
|
import JsonViewModal from '@/components/view/json/json-view-modal.vue';
|
||||||
import { useDictStore } from '@/store';
|
import { useDictStore } from '@/store';
|
||||||
import { dictKeys } from './types/const';
|
import { dictKeys } from './types/const';
|
||||||
|
|
||||||
|
|||||||
@@ -7,16 +7,17 @@
|
|||||||
<!-- 图标 -->
|
<!-- 图标 -->
|
||||||
<template #dot>
|
<template #dot>
|
||||||
<div class="icon-container">
|
<div class="icon-container">
|
||||||
<icon-desktop />
|
<icon-mobile v-if="isMobile(item.userAgent)" />
|
||||||
|
<icon-desktop v-else />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<!-- 日志行 -->
|
<!-- 日志行 -->
|
||||||
<div class="log-line">
|
<div class="log-line">
|
||||||
<!-- 地址行 -->
|
<!-- 地址行 -->
|
||||||
<span class="address-line">
|
<a-space class="address-line">
|
||||||
<span class="mr8">{{ item.address }}</span>
|
<span class="mr8">{{ item.address }}</span>
|
||||||
<span>{{ item.location }}</span>
|
<span>{{ item.location }}</span>
|
||||||
</span>
|
</a-space>
|
||||||
<!-- 错误信息行 -->
|
<!-- 错误信息行 -->
|
||||||
<span class="error-line" v-if="item.result === ResultStatus.FAILED">
|
<span class="error-line" v-if="item.result === ResultStatus.FAILED">
|
||||||
登录失败: {{ item.errorMessage }}
|
登录失败: {{ item.errorMessage }}
|
||||||
@@ -45,14 +46,13 @@
|
|||||||
import type { LoginHistoryQueryResponse } from '@/api/user/operator-log';
|
import type { LoginHistoryQueryResponse } from '@/api/user/operator-log';
|
||||||
import useLoading from '@/hooks/loading';
|
import useLoading from '@/hooks/loading';
|
||||||
import { ref, onMounted } from 'vue';
|
import { ref, onMounted } from 'vue';
|
||||||
import { useUserStore } from '@/store';
|
|
||||||
import { ResultStatus } from '../types/const';
|
import { ResultStatus } from '../types/const';
|
||||||
import { getCurrentLoginHistory } from '@/api/user/mine';
|
import { getCurrentLoginHistory } from '@/api/user/mine';
|
||||||
import { dateFormat } from '@/utils';
|
import { dateFormat } from '@/utils';
|
||||||
|
import { isMobile } from '@/utils/is';
|
||||||
|
|
||||||
const list = ref<LoginHistoryQueryResponse[]>([]);
|
const list = ref<LoginHistoryQueryResponse[]>([]);
|
||||||
|
|
||||||
const userStore = useUserStore();
|
|
||||||
const { loading, setLoading } = useLoading();
|
const { loading, setLoading } = useLoading();
|
||||||
|
|
||||||
// 查询操作日志
|
// 查询操作日志
|
||||||
@@ -78,7 +78,7 @@
|
|||||||
|
|
||||||
.extra-message {
|
.extra-message {
|
||||||
margin-bottom: 38px;
|
margin-bottom: 38px;
|
||||||
margin-left: -20px;
|
margin-left: -24px;
|
||||||
display: block;
|
display: block;
|
||||||
color: var(--color-text-3);
|
color: var(--color-text-3);
|
||||||
user-select: none;
|
user-select: none;
|
||||||
@@ -112,8 +112,9 @@
|
|||||||
|
|
||||||
.address-line {
|
.address-line {
|
||||||
color: var(--color-text-1);
|
color: var(--color-text-1);
|
||||||
font-size: 16px;
|
font-size: 15px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
margin-bottom: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.time-line, .ua-line, .error-line {
|
.time-line, .ua-line, .error-line {
|
||||||
|
|||||||
@@ -0,0 +1,234 @@
|
|||||||
|
<template>
|
||||||
|
<!-- 搜索 -->
|
||||||
|
<a-card class="general-card table-search-card">
|
||||||
|
<a-query-header :model="formModel"
|
||||||
|
label-align="left"
|
||||||
|
@submit="fetchTableData"
|
||||||
|
@reset="fetchTableData"
|
||||||
|
@keyup.enter="() => fetchTableData()">
|
||||||
|
<!-- 角色名称 -->
|
||||||
|
<a-form-item field="name" label="角色名称" label-col-flex="50px">
|
||||||
|
<a-input v-model="formModel.name" placeholder="请输入角色名称" allow-clear />
|
||||||
|
</a-form-item>
|
||||||
|
<!-- 角色编码 -->
|
||||||
|
<a-form-item field="code" label="角色编码" label-col-flex="50px">
|
||||||
|
<a-input v-model="formModel.code" placeholder="请输入角色编码" allow-clear />
|
||||||
|
</a-form-item>
|
||||||
|
<!-- 角色状态 -->
|
||||||
|
<a-form-item field="status" label="角色状态" label-col-flex="50px">
|
||||||
|
<a-select v-model="formModel.status"
|
||||||
|
placeholder="请选择角色状态"
|
||||||
|
:options="toOptions(roleStatusKey)"
|
||||||
|
allow-clear />
|
||||||
|
</a-form-item>
|
||||||
|
</a-query-header>
|
||||||
|
</a-card>
|
||||||
|
<!-- 表格 -->
|
||||||
|
<a-card class="general-card table-card">
|
||||||
|
<template #title>
|
||||||
|
<!-- 左侧操作 -->
|
||||||
|
<div class="table-left-bar-handle">
|
||||||
|
<!-- 标题 -->
|
||||||
|
<div class="table-title">
|
||||||
|
角色列表
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 右侧操作 -->
|
||||||
|
<div class="table-right-bar-handle">
|
||||||
|
<a-space>
|
||||||
|
<!-- 新增 -->
|
||||||
|
<a-button type="primary"
|
||||||
|
v-permission="['infra:system-role:create']"
|
||||||
|
@click="emits('openAdd')">
|
||||||
|
新增
|
||||||
|
<template #icon>
|
||||||
|
<icon-plus />
|
||||||
|
</template>
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<!-- table -->
|
||||||
|
<a-table row-key="id"
|
||||||
|
class="table-wrapper-8"
|
||||||
|
ref="tableRef"
|
||||||
|
label-align="left"
|
||||||
|
:loading="loading"
|
||||||
|
:columns="columns"
|
||||||
|
:data="tableRenderData"
|
||||||
|
:pagination="pagination"
|
||||||
|
@page-change="(page) => fetchTableData(page, pagination.pageSize)"
|
||||||
|
@page-size-change="(size) => fetchTableData(1, size)"
|
||||||
|
:bordered="false">
|
||||||
|
<!-- 编码 -->
|
||||||
|
<template #code="{ record }">
|
||||||
|
<a-tag>{{ record.code }}</a-tag>
|
||||||
|
</template>
|
||||||
|
<!-- 状态 -->
|
||||||
|
<template #status="{ record }">
|
||||||
|
<span class="circle" :style="{
|
||||||
|
background: getDictValue(roleStatusKey, record.status, 'color')
|
||||||
|
}" />
|
||||||
|
{{ getDictValue(roleStatusKey, record.status) }}
|
||||||
|
</template>
|
||||||
|
<!-- 操作 -->
|
||||||
|
<template #handle="{ record }">
|
||||||
|
<div class="table-handle-wrapper">
|
||||||
|
<!-- 修改状态 -->
|
||||||
|
<a-popconfirm :content="`确定要${toggleDictValue(roleStatusKey, record.status, 'label')}当前角色吗?`"
|
||||||
|
position="left"
|
||||||
|
type="warning"
|
||||||
|
@ok="toggleRoleStatus(record)">
|
||||||
|
<a-button v-permission="['infra:system-role:delete']"
|
||||||
|
:disabled="record.code === 'admin'"
|
||||||
|
:status="toggleDictValue(roleStatusKey, record.status, 'status')"
|
||||||
|
type="text"
|
||||||
|
size="mini">
|
||||||
|
{{ toggleDictValue(roleStatusKey, record.status, 'label') }}
|
||||||
|
</a-button>
|
||||||
|
</a-popconfirm>
|
||||||
|
<!-- 分配菜单 -->
|
||||||
|
<a-button v-permission="['infra:system-role:grant-menu']"
|
||||||
|
:disabled="record.code === 'admin'"
|
||||||
|
type="text"
|
||||||
|
size="mini"
|
||||||
|
@click="emits('openGrant', record)">
|
||||||
|
分配菜单
|
||||||
|
</a-button>
|
||||||
|
<!-- 修改 -->
|
||||||
|
<a-button v-permission="['infra:system-role:update']"
|
||||||
|
type="text"
|
||||||
|
size="mini"
|
||||||
|
@click="emits('openUpdate', record)">
|
||||||
|
修改
|
||||||
|
</a-button>
|
||||||
|
<!-- 删除 -->
|
||||||
|
<a-popconfirm content="确认删除这条记录吗?"
|
||||||
|
position="left"
|
||||||
|
type="warning"
|
||||||
|
@ok="deleteRow(record)">
|
||||||
|
<a-button v-permission="['infra:system-role:delete']"
|
||||||
|
:disabled="record.code === 'admin'"
|
||||||
|
type="text"
|
||||||
|
size="mini"
|
||||||
|
status="danger">
|
||||||
|
删除
|
||||||
|
</a-button>
|
||||||
|
</a-popconfirm>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
</a-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
name: 'operator-log-list'
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { RoleQueryRequest, RoleQueryResponse } from '@/api/user/role';
|
||||||
|
import { reactive, ref, onMounted } from 'vue';
|
||||||
|
import { deleteRole, getRolePage, updateRoleStatus } from '@/api/user/role';
|
||||||
|
import { Message } from '@arco-design/web-vue';
|
||||||
|
import useLoading from '@/hooks/loading';
|
||||||
|
import columns from '../../role/types/table.columns';
|
||||||
|
import { roleStatusKey } from '../../role/types/const';
|
||||||
|
import { usePagination } from '@/types/table';
|
||||||
|
import { useDictStore } from '@/store';
|
||||||
|
|
||||||
|
const emits = defineEmits(['openAdd', 'openUpdate', 'openGrant']);
|
||||||
|
|
||||||
|
const tableRenderData = ref<RoleQueryResponse[]>([]);
|
||||||
|
|
||||||
|
const pagination = usePagination();
|
||||||
|
const { loading, setLoading } = useLoading();
|
||||||
|
const { toOptions, getDictValue, toggleDictValue, toggleDict } = useDictStore();
|
||||||
|
|
||||||
|
const formModel = reactive<RoleQueryRequest>({
|
||||||
|
id: undefined,
|
||||||
|
name: undefined,
|
||||||
|
code: undefined,
|
||||||
|
status: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 修改状态
|
||||||
|
const toggleRoleStatus = async (record: any) => {
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
const toggleStatus = toggleDict(roleStatusKey, record.status);
|
||||||
|
// 调用修改接口
|
||||||
|
await updateRoleStatus({
|
||||||
|
id: record.id,
|
||||||
|
status: toggleStatus.value as number
|
||||||
|
});
|
||||||
|
Message.success(`${toggleStatus.label}成功`);
|
||||||
|
// 修改行状态
|
||||||
|
record.status = toggleStatus.value;
|
||||||
|
} catch (e) {
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 删除当前行
|
||||||
|
const deleteRow = async ({ id }: {
|
||||||
|
id: number
|
||||||
|
}) => {
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
// 调用删除接口
|
||||||
|
await deleteRole(id);
|
||||||
|
Message.success('删除成功');
|
||||||
|
// 重新加载数据
|
||||||
|
fetchTableData();
|
||||||
|
} catch (e) {
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 添加后回调
|
||||||
|
const addedCallback = () => {
|
||||||
|
fetchTableData();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 更新后回调
|
||||||
|
const updatedCallback = () => {
|
||||||
|
fetchTableData();
|
||||||
|
};
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
addedCallback, updatedCallback
|
||||||
|
});
|
||||||
|
|
||||||
|
// 加载数据
|
||||||
|
const doFetchTableData = async (request: RoleQueryRequest) => {
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
const { data } = await getRolePage(request);
|
||||||
|
tableRenderData.value = data.rows;
|
||||||
|
pagination.total = data.total;
|
||||||
|
pagination.current = request.page;
|
||||||
|
pagination.pageSize = request.limit;
|
||||||
|
} catch (e) {
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 切换页码
|
||||||
|
const fetchTableData = (page = 1, limit = pagination.pageSize, form = formModel) => {
|
||||||
|
doFetchTableData({ page, limit, ...form });
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
fetchTableData();
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
150
orion-ops-ui/src/views/user/info/components/user-session.vue
Normal file
150
orion-ops-ui/src/views/user/info/components/user-session.vue
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
<template>
|
||||||
|
<a-spin :loading="loading" class="main-container">
|
||||||
|
<span class="extra-message">所有登录设备的会话列表</span>
|
||||||
|
<a-timeline>
|
||||||
|
<template v-for="item in list"
|
||||||
|
:key="item.loginTime">
|
||||||
|
<a-timeline-item v-if="item.visible">
|
||||||
|
<!-- 图标 -->
|
||||||
|
<template #dot>
|
||||||
|
<div class="icon-container">
|
||||||
|
<icon-mobile v-if="isMobile(item.userAgent)" />
|
||||||
|
<icon-desktop v-else />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<!-- 会话行 -->
|
||||||
|
<div class="session-line">
|
||||||
|
<!-- 地址行 -->
|
||||||
|
<a-space class="address-line">
|
||||||
|
<span>{{ item.address }}</span>
|
||||||
|
<span>{{ item.location }}</span>
|
||||||
|
<a-tag v-if="item.current" color="arcoblue">当前会话</a-tag>
|
||||||
|
<a-button v-else
|
||||||
|
style="font-weight: 600;"
|
||||||
|
type="text"
|
||||||
|
size="mini"
|
||||||
|
status="danger"
|
||||||
|
@click="offline(item)">
|
||||||
|
下线
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
|
<!-- 时间行 -->
|
||||||
|
<span class="time-line">
|
||||||
|
{{ dateFormat(new Date(item.loginTime)) }}
|
||||||
|
</span>
|
||||||
|
<!-- ua -->
|
||||||
|
<span class="ua-line">
|
||||||
|
{{ item.userAgent }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</a-timeline-item>
|
||||||
|
</template>
|
||||||
|
</a-timeline>
|
||||||
|
</a-spin>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
name: 'user-session'
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { UserSessionQueryResponse } from '@/api/user/user';
|
||||||
|
import useLoading from '@/hooks/loading';
|
||||||
|
import { ref, onMounted } from 'vue';
|
||||||
|
import { getCurrentUserSessionList, offlineCurrentUserSession } from '@/api/user/mine';
|
||||||
|
import { dateFormat } from '@/utils';
|
||||||
|
import { isMobile } from '@/utils/is';
|
||||||
|
import { Message } from '@arco-design/web-vue';
|
||||||
|
|
||||||
|
const list = ref<UserSessionQueryResponse[]>([]);
|
||||||
|
|
||||||
|
const { loading, setLoading } = useLoading();
|
||||||
|
|
||||||
|
// 下线
|
||||||
|
const offline = async (item: UserSessionQueryResponse) => {
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
await offlineCurrentUserSession({
|
||||||
|
timestamp: item.loginTime
|
||||||
|
});
|
||||||
|
Message.success('操作成功');
|
||||||
|
item.visible = false;
|
||||||
|
} catch (e) {
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 查询登录会话
|
||||||
|
onMounted(async () => {
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
const { data } = await getCurrentUserSessionList();
|
||||||
|
data.forEach(s => s.visible = true);
|
||||||
|
list.value = data;
|
||||||
|
} catch (e) {
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.main-container {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 200px;
|
||||||
|
padding-left: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.extra-message {
|
||||||
|
margin-bottom: 38px;
|
||||||
|
margin-left: -24px;
|
||||||
|
display: block;
|
||||||
|
color: var(--color-text-3);
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-container {
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 56px;
|
||||||
|
height: 56px;
|
||||||
|
background: var(--color-fill-4);
|
||||||
|
font-size: 28px;
|
||||||
|
color: #FFFFFF;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.arco-timeline-item-content-wrapper) {
|
||||||
|
position: relative;
|
||||||
|
margin-left: 44px;
|
||||||
|
margin-top: -22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.arco-timeline-item) {
|
||||||
|
padding-bottom: 36px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.session-line {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.address-line {
|
||||||
|
color: var(--color-text-1);
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-line, .ua-line {
|
||||||
|
color: var(--color-text-3);
|
||||||
|
font-size: 14px;
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -15,10 +15,11 @@
|
|||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<!-- 登录设备 -->
|
<!-- 登录设备 -->
|
||||||
<a-tab-pane key="3" title="登录设备">
|
<a-tab-pane key="3" title="登录设备">
|
||||||
<login-history />
|
<user-session />
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<!-- 操作日志 -->
|
<!-- 操作日志 -->
|
||||||
<a-tab-pane key="4" title="操作日志">
|
<a-tab-pane key="4" title="操作日志">
|
||||||
|
<operator-log-list />
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
</a-tabs>
|
</a-tabs>
|
||||||
</div>
|
</div>
|
||||||
@@ -33,6 +34,8 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import UserInfo from './components/user-info.vue';
|
import UserInfo from './components/user-info.vue';
|
||||||
import LoginHistory from './components/login-history.vue';
|
import LoginHistory from './components/login-history.vue';
|
||||||
|
import UserSession from './components/user-session.vue';
|
||||||
|
import OperatorLogList from './components/operator-log-list.vue';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
|||||||
@@ -0,0 +1,134 @@
|
|||||||
|
<template>
|
||||||
|
<a-query-header :model="formModel"
|
||||||
|
label-align="left"
|
||||||
|
@submit="submit"
|
||||||
|
@reset="reset"
|
||||||
|
@keyup.enter="submit">
|
||||||
|
<!-- 操作用户 -->
|
||||||
|
<a-form-item v-if="visibleUser"
|
||||||
|
field="userId"
|
||||||
|
label="操作用户"
|
||||||
|
label-col-flex="50px">
|
||||||
|
<user-selector v-model="formModel.userId"
|
||||||
|
placeholder="请选择操作用户"
|
||||||
|
allow-clear />
|
||||||
|
</a-form-item>
|
||||||
|
<!-- 操作模块 -->
|
||||||
|
<a-form-item field="module" label="操作模块" label-col-flex="50px">
|
||||||
|
<a-select v-model="formModel.module"
|
||||||
|
:options="toOptions(operatorLogModuleKey)"
|
||||||
|
placeholder="请选择操作模块"
|
||||||
|
@change="selectedModule"
|
||||||
|
allow-clear />
|
||||||
|
</a-form-item>
|
||||||
|
<!-- 操作类型 -->
|
||||||
|
<a-form-item field="type" label="操作类型" label-col-flex="50px">
|
||||||
|
<a-select v-model="formModel.type"
|
||||||
|
:options="typeOptions"
|
||||||
|
placeholder="请选择操作类型"
|
||||||
|
allow-clear />
|
||||||
|
</a-form-item>
|
||||||
|
<!-- 风险等级 -->
|
||||||
|
<a-form-item field="riskLevel" label="风险等级" label-col-flex="50px">
|
||||||
|
<a-select v-model="formModel.riskLevel"
|
||||||
|
:options="toOptions(operatorRiskLevelKey)"
|
||||||
|
placeholder="请选择风险等级"
|
||||||
|
allow-clear />
|
||||||
|
</a-form-item>
|
||||||
|
<!-- 执行结果 -->
|
||||||
|
<a-form-item field="result" label="执行结果" label-col-flex="50px">
|
||||||
|
<a-select v-model="formModel.result"
|
||||||
|
:options="toOptions(operatorLogResultKey)"
|
||||||
|
placeholder="请选择执行结果"
|
||||||
|
allow-clear />
|
||||||
|
</a-form-item>
|
||||||
|
<!-- 执行时间 -->
|
||||||
|
<a-form-item field="startTime" label="执行时间" label-col-flex="50px">
|
||||||
|
<a-range-picker v-model="timeRange"
|
||||||
|
:time-picker-props="{ defaultValue: ['00:00:00', '23:59:59'] }"
|
||||||
|
show-time
|
||||||
|
format="YYYY-MM-DD HH:mm:ss"
|
||||||
|
@ok="timeRangePicked" />
|
||||||
|
</a-form-item>
|
||||||
|
</a-query-header>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
name: 'operator-log-query-header'
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { OperatorLogQueryRequest } from '@/api/user/operator-log';
|
||||||
|
import type { SelectOptionData } from '@arco-design/web-vue/es/select/interface';
|
||||||
|
import { reactive, ref } from 'vue';
|
||||||
|
import useLoading from '@/hooks/loading';
|
||||||
|
import { useDictStore } from '@/store';
|
||||||
|
import UserSelector from '@/components/user/role/user-selector.vue';
|
||||||
|
import { operatorLogModuleKey, operatorLogTypeKey, operatorRiskLevelKey, operatorLogResultKey } from '../types/const';
|
||||||
|
|
||||||
|
const emits = defineEmits(['submit']);
|
||||||
|
const props = defineProps({
|
||||||
|
visibleUser: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const { loading, setLoading } = useLoading();
|
||||||
|
const { $state: dictState, toOptions } = useDictStore();
|
||||||
|
|
||||||
|
const timeRange = ref<string[]>([]);
|
||||||
|
const typeOptions = ref<SelectOptionData[]>(toOptions(operatorLogTypeKey));
|
||||||
|
const formModel = reactive<OperatorLogQueryRequest>({
|
||||||
|
userId: undefined,
|
||||||
|
module: undefined,
|
||||||
|
type: undefined,
|
||||||
|
riskLevel: undefined,
|
||||||
|
result: undefined,
|
||||||
|
startTimeStart: undefined,
|
||||||
|
startTimeEnd: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 选择时间
|
||||||
|
const timeRangePicked = (e: string[]) => {
|
||||||
|
formModel.startTimeStart = e[0];
|
||||||
|
formModel.startTimeEnd = e[1];
|
||||||
|
};
|
||||||
|
|
||||||
|
// 选择类型
|
||||||
|
const selectedModule = (module: string) => {
|
||||||
|
if (!module) {
|
||||||
|
// 不选择则重置 options
|
||||||
|
typeOptions.value = toOptions(operatorLogTypeKey);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const moduleArr = module.split(':');
|
||||||
|
const modulePrefix = moduleArr[moduleArr.length - 1] + ':';
|
||||||
|
// 渲染 options
|
||||||
|
typeOptions.value = dictState[operatorLogTypeKey].filter(s => (s.value as string).startsWith(modulePrefix));
|
||||||
|
// 渲染输入框
|
||||||
|
if (formModel.type && !formModel.type.startsWith(modulePrefix)) {
|
||||||
|
formModel.type = undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 重置
|
||||||
|
const reset = () => {
|
||||||
|
timeRange.value = [];
|
||||||
|
formModel.startTimeStart = undefined;
|
||||||
|
formModel.startTimeEnd = undefined;
|
||||||
|
submit();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 切换页码
|
||||||
|
const submit = () => {
|
||||||
|
emits('submit', { ...formModel });
|
||||||
|
};
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
<template>
|
||||||
|
<a-table row-key="id"
|
||||||
|
class="table-wrapper-8"
|
||||||
|
ref="tableRef"
|
||||||
|
label-align="left"
|
||||||
|
:loading="loading"
|
||||||
|
:columns="tableColumns"
|
||||||
|
:data="tableRenderData"
|
||||||
|
:pagination="pagination"
|
||||||
|
@page-change="(page) => fetchTableData(page, pagination.pageSize)"
|
||||||
|
@page-size-change="(size) => fetchTableData(1, size)"
|
||||||
|
:bordered="false">
|
||||||
|
<!-- 操作模块 -->
|
||||||
|
<template #module="{ record }">
|
||||||
|
{{ getDictValue(operatorLogModuleKey, record.module) }}
|
||||||
|
</template>
|
||||||
|
<!-- 操作类型 -->
|
||||||
|
<template #type="{ record }">
|
||||||
|
{{ getDictValue(operatorLogTypeKey, record.type) }}
|
||||||
|
</template>
|
||||||
|
<!-- 风险等级 -->
|
||||||
|
<template #riskLevel="{ record }">
|
||||||
|
<a-tag :color="getDictValue(operatorRiskLevelKey, record.riskLevel, 'color')">
|
||||||
|
{{ getDictValue(operatorRiskLevelKey, record.riskLevel) }}
|
||||||
|
</a-tag>
|
||||||
|
</template>
|
||||||
|
<!-- 执行结果 -->
|
||||||
|
<template #result="{ record }">
|
||||||
|
<a-tag :color="getDictValue(operatorLogResultKey, record.result, 'color')">
|
||||||
|
{{ getDictValue(operatorLogResultKey, record.result) }}
|
||||||
|
</a-tag>
|
||||||
|
</template>
|
||||||
|
<!-- 操作日志 -->
|
||||||
|
<template #originLogInfo="{ record }">
|
||||||
|
<span v-html="replaceHtmlTag(record.logInfo)" />
|
||||||
|
</template>
|
||||||
|
<!-- 操作 -->
|
||||||
|
<template #handle="{ record }">
|
||||||
|
<div class="table-handle-wrapper">
|
||||||
|
<!-- 详情 -->
|
||||||
|
<a-button type="text" size="mini" @click="viewDetail(record)">
|
||||||
|
详情
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
name: 'operator-log-table'
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { OperatorLogQueryRequest, OperatorLogQueryResponse } from '@/api/user/operator-log';
|
||||||
|
import { ref, onMounted } from 'vue';
|
||||||
|
import { operatorLogModuleKey, operatorLogTypeKey, operatorRiskLevelKey, operatorLogResultKey } from '../types/const';
|
||||||
|
import columns from '../types/table.columns';
|
||||||
|
import useLoading from '@/hooks/loading';
|
||||||
|
import { usePagination } from '@/types/table';
|
||||||
|
import { useDictStore } from '@/store';
|
||||||
|
import { getOperatorLogPage } from '@/api/user/operator-log';
|
||||||
|
import { replaceHtmlTag, clearHtmlTag, dateFormat } from '@/utils';
|
||||||
|
import { pick } from 'lodash';
|
||||||
|
|
||||||
|
|
||||||
|
const emits = defineEmits(['viewDetail']);
|
||||||
|
const props = defineProps({
|
||||||
|
visibleUser: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const tableColumns = ref();
|
||||||
|
const tableRenderData = ref<OperatorLogQueryResponse[]>([]);
|
||||||
|
|
||||||
|
const pagination = usePagination();
|
||||||
|
const { loading, setLoading } = useLoading();
|
||||||
|
const { getDictValue } = useDictStore();
|
||||||
|
|
||||||
|
// 查看详情
|
||||||
|
const viewDetail = (record: OperatorLogQueryResponse) => {
|
||||||
|
try {
|
||||||
|
const detail = Object.assign({} as Record<string, any>,
|
||||||
|
pick(record, 'traceId', 'address', 'location',
|
||||||
|
'userAgent', 'errorMessage'));
|
||||||
|
detail.duration = `${record.duration} ms`;
|
||||||
|
detail.startTime = dateFormat(new Date(record.startTime));
|
||||||
|
detail.endTime = dateFormat(new Date(record.endTime));
|
||||||
|
detail.extra = JSON.parse(record?.extra);
|
||||||
|
detail.returnValue = JSON.parse(record?.returnValue);
|
||||||
|
emits('viewDetail', detail);
|
||||||
|
} catch (e) {
|
||||||
|
emits('viewDetail', record);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 加载数据
|
||||||
|
const doFetchTableData = async (request: OperatorLogQueryRequest) => {
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
const { data } = await getOperatorLogPage(request);
|
||||||
|
tableRenderData.value = data.rows.map(s => {
|
||||||
|
return { ...s, originLogInfo: clearHtmlTag(s.logInfo) };
|
||||||
|
});
|
||||||
|
pagination.total = data.total;
|
||||||
|
pagination.current = request.page;
|
||||||
|
pagination.pageSize = request.limit;
|
||||||
|
} catch (e) {
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 切换页码
|
||||||
|
const fetchTableData = (page = 1, limit = pagination.pageSize, form = {}) => {
|
||||||
|
doFetchTableData({ page, limit, ...form });
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (props.visibleUser) {
|
||||||
|
tableColumns.value = columns;
|
||||||
|
} else {
|
||||||
|
tableColumns.value = columns.filter(s => s.dataIndex !== 'username');
|
||||||
|
}
|
||||||
|
fetchTableData();
|
||||||
|
});
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
fetchTableData
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
71
orion-ops-ui/src/views/user/operator-log/index.vue
Normal file
71
orion-ops-ui/src/views/user/operator-log/index.vue
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
<template>
|
||||||
|
<div class="layout-container" v-if="render">
|
||||||
|
<!-- 查询头 -->
|
||||||
|
<a-card class="general-card table-search-card">
|
||||||
|
<!-- 查询头组件 -->
|
||||||
|
<operator-log-query-header @submit="(e) => table.fetchTableData(undefined, undefined, e)" />
|
||||||
|
</a-card>
|
||||||
|
<!-- 表格 -->
|
||||||
|
<a-card class="general-card table-card">
|
||||||
|
<template #title>
|
||||||
|
<!-- 左侧操作 -->
|
||||||
|
<div class="table-left-bar-handle">
|
||||||
|
<!-- 标题 -->
|
||||||
|
<div class="table-title">
|
||||||
|
操作日志
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<!-- 表格组件 -->
|
||||||
|
<operator-log-table ref="table" @viewDetail="(e) => view.open(e)" />
|
||||||
|
</a-card>
|
||||||
|
<!-- json 查看器模态框 -->
|
||||||
|
<json-view-modal ref="view" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
name: 'userOperatorLog'
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, onBeforeMount, onUnmounted } from 'vue';
|
||||||
|
import { useCacheStore, useDictStore } from '@/store';
|
||||||
|
import { dictKeys } from './types/const';
|
||||||
|
import { getUserList } from '@/api/user/user';
|
||||||
|
import OperatorLogQueryHeader from './components/operator-log-query-header.vue';
|
||||||
|
import OperatorLogTable from './components/operator-log-table.vue';
|
||||||
|
import JsonViewModal from '@/components/view/json/json-view-modal.vue';
|
||||||
|
|
||||||
|
const cacheStore = useCacheStore();
|
||||||
|
|
||||||
|
const render = ref();
|
||||||
|
const table = ref();
|
||||||
|
const view = ref();
|
||||||
|
|
||||||
|
// 加载全部用户列表
|
||||||
|
const fetchUserList = async () => {
|
||||||
|
const { data } = await getUserList();
|
||||||
|
cacheStore.set('users', data);
|
||||||
|
};
|
||||||
|
|
||||||
|
onBeforeMount(async () => {
|
||||||
|
// 加载字典值
|
||||||
|
const dictStore = useDictStore();
|
||||||
|
await dictStore.loadKeys(dictKeys);
|
||||||
|
// 加载用户列表
|
||||||
|
await fetchUserList();
|
||||||
|
render.value = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 卸载时清除 cache
|
||||||
|
onUnmounted(() => {
|
||||||
|
cacheStore.reset('users');
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
</style>
|
||||||
24
orion-ops-ui/src/views/user/operator-log/types/const.ts
Normal file
24
orion-ops-ui/src/views/user/operator-log/types/const.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
// 结果状态
|
||||||
|
export const ResultStatus = {
|
||||||
|
// 失败
|
||||||
|
FAILED: 0,
|
||||||
|
// 成功
|
||||||
|
SUCCESS: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 操作日志模块 字典项
|
||||||
|
export const operatorLogModuleKey = 'operatorLogModule';
|
||||||
|
|
||||||
|
// 操作日志类型 字典项
|
||||||
|
export const operatorLogTypeKey = 'operatorLogType';
|
||||||
|
|
||||||
|
// 操作风险等级 字典项
|
||||||
|
export const operatorRiskLevelKey = 'operatorRiskLevel';
|
||||||
|
|
||||||
|
// 操作日志结果 字典项
|
||||||
|
export const operatorLogResultKey = 'operatorLogResult';
|
||||||
|
|
||||||
|
// 加载的字典值
|
||||||
|
export const dictKeys = [operatorLogModuleKey, operatorLogTypeKey, operatorRiskLevelKey, operatorLogResultKey];
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
import type { TableColumnData } from '@arco-design/web-vue/es/table/interface';
|
||||||
|
import { dateFormat } from '@/utils';
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: 'id',
|
||||||
|
dataIndex: 'id',
|
||||||
|
slotName: 'id',
|
||||||
|
width: 70,
|
||||||
|
align: 'left',
|
||||||
|
fixed: 'left',
|
||||||
|
}, {
|
||||||
|
title: '操作用户',
|
||||||
|
dataIndex: 'username',
|
||||||
|
slotName: 'username',
|
||||||
|
width: 120,
|
||||||
|
ellipsis: true,
|
||||||
|
tooltip: true,
|
||||||
|
}, {
|
||||||
|
title: '操作模块',
|
||||||
|
dataIndex: 'module',
|
||||||
|
slotName: 'module',
|
||||||
|
width: 120,
|
||||||
|
ellipsis: true,
|
||||||
|
tooltip: true,
|
||||||
|
}, {
|
||||||
|
title: '操作类型',
|
||||||
|
dataIndex: 'type',
|
||||||
|
slotName: 'type',
|
||||||
|
width: 150,
|
||||||
|
ellipsis: true,
|
||||||
|
tooltip: true,
|
||||||
|
}, {
|
||||||
|
title: '风险等级',
|
||||||
|
dataIndex: 'riskLevel',
|
||||||
|
slotName: 'riskLevel',
|
||||||
|
width: 90,
|
||||||
|
align: 'center',
|
||||||
|
}, {
|
||||||
|
title: '执行结果',
|
||||||
|
dataIndex: 'result',
|
||||||
|
slotName: 'result',
|
||||||
|
width: 90,
|
||||||
|
align: 'center',
|
||||||
|
}, {
|
||||||
|
title: '操作日志',
|
||||||
|
dataIndex: 'originLogInfo',
|
||||||
|
slotName: 'originLogInfo',
|
||||||
|
ellipsis: true,
|
||||||
|
tooltip: true,
|
||||||
|
}, {
|
||||||
|
title: '创建时间',
|
||||||
|
dataIndex: 'createTime',
|
||||||
|
slotName: 'createTime',
|
||||||
|
align: 'center',
|
||||||
|
width: 180,
|
||||||
|
render: ({ record }) => {
|
||||||
|
return dateFormat(new Date(record.createTime));
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
title: '操作',
|
||||||
|
slotName: 'handle',
|
||||||
|
width: 90,
|
||||||
|
align: 'center',
|
||||||
|
fixed: 'right',
|
||||||
|
},
|
||||||
|
] as TableColumnData[];
|
||||||
|
|
||||||
|
export default columns;
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["src/*"]
|
"@/*": ["src/*"]
|
||||||
},
|
},
|
||||||
"lib": ["es2020", "dom"],
|
"lib": ["es2021", "dom"],
|
||||||
"skipLibCheck": true
|
"skipLibCheck": true
|
||||||
},
|
},
|
||||||
"include": ["src/**/*", "src/**/*.vue"],
|
"include": ["src/**/*", "src/**/*.vue"],
|
||||||
|
|||||||
Reference in New Issue
Block a user