feat(settings): add offboarding record list and trace detail
This commit is contained in:
parent
eae8cbb8e8
commit
f041543f02
@ -1,20 +1,25 @@
|
||||
package com.designhub.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.designhub.dto.UserOffboardingRecordQuery;
|
||||
import com.designhub.common.Result;
|
||||
import com.designhub.dto.UserOffboardingRequest;
|
||||
import com.designhub.entity.User;
|
||||
import com.designhub.service.UserOffboardingService;
|
||||
import com.designhub.service.UserService;
|
||||
import com.designhub.util.JwtUtil;
|
||||
import com.designhub.vo.UserOffboardingRecordDetailVO;
|
||||
import com.designhub.vo.UserOffboardingRecordVO;
|
||||
import com.designhub.vo.UserOffboardingExecuteVO;
|
||||
import com.designhub.vo.UserOffboardingPreviewVO;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@ -196,4 +201,43 @@ public class UserController {
|
||||
Long operatorId = jwtUtil.getUserIdFromToken(token);
|
||||
return Result.success(userOffboardingService.execute(request, operatorId));
|
||||
}
|
||||
|
||||
@Operation(summary = "分页查询离职交接记录")
|
||||
@GetMapping("/offboarding/records")
|
||||
public Result<Page<UserOffboardingRecordVO>> getOffboardingRecords(
|
||||
@RequestHeader("Authorization") String authHeader,
|
||||
@RequestParam(defaultValue = "1") Integer pageNum,
|
||||
@RequestParam(defaultValue = "10") Integer pageSize,
|
||||
@RequestParam(required = false) Long fromUserId,
|
||||
@RequestParam(required = false) Long toUserId,
|
||||
@RequestParam(required = false) Long operatorId,
|
||||
@RequestParam(required = false) String keyword,
|
||||
@RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime startTime,
|
||||
@RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime endTime
|
||||
) {
|
||||
String token = jwtUtil.extractTokenFromHeader(authHeader);
|
||||
Long currentUserId = jwtUtil.getUserIdFromToken(token);
|
||||
UserOffboardingRecordQuery query = new UserOffboardingRecordQuery();
|
||||
query.setPageNum(pageNum);
|
||||
query.setPageSize(pageSize);
|
||||
query.setFromUserId(fromUserId);
|
||||
query.setToUserId(toUserId);
|
||||
query.setOperatorId(operatorId);
|
||||
query.setKeyword(keyword);
|
||||
query.setStartTime(startTime);
|
||||
query.setEndTime(endTime);
|
||||
Page<UserOffboardingRecordVO> page = userOffboardingService.getRecordPage(query, currentUserId);
|
||||
return Result.success(page);
|
||||
}
|
||||
|
||||
@Operation(summary = "获取离职交接记录详情")
|
||||
@GetMapping("/offboarding/records/{id}")
|
||||
public Result<UserOffboardingRecordDetailVO> getOffboardingRecordDetail(
|
||||
@RequestHeader("Authorization") String authHeader,
|
||||
@PathVariable Long id
|
||||
) {
|
||||
String token = jwtUtil.extractTokenFromHeader(authHeader);
|
||||
Long currentUserId = jwtUtil.getUserIdFromToken(token);
|
||||
return Result.success(userOffboardingService.getRecordDetail(id, currentUserId));
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,28 @@
|
||||
package com.designhub.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 离职交接记录查询参数
|
||||
*/
|
||||
@Data
|
||||
public class UserOffboardingRecordQuery {
|
||||
|
||||
private Integer pageNum = 1;
|
||||
|
||||
private Integer pageSize = 10;
|
||||
|
||||
private Long fromUserId;
|
||||
|
||||
private Long toUserId;
|
||||
|
||||
private Long operatorId;
|
||||
|
||||
private String keyword;
|
||||
|
||||
private LocalDateTime startTime;
|
||||
|
||||
private LocalDateTime endTime;
|
||||
}
|
||||
@ -1,9 +1,65 @@
|
||||
package com.designhub.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.designhub.dto.UserOffboardingRecordQuery;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.designhub.entity.TaskTransferLog;
|
||||
import com.designhub.vo.UserOffboardingRecordDetailVO;
|
||||
import com.designhub.vo.UserOffboardingRecordVO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
|
||||
@Mapper
|
||||
public interface TaskTransferLogMapper extends BaseMapper<TaskTransferLog> {
|
||||
|
||||
@Select("<script>" +
|
||||
"SELECT l.id, l.task_id, t.task_no, t.title AS task_title, t.status AS task_status, " +
|
||||
" l.from_user_id, COALESCE(fu.real_name, fu.username) AS from_user_name, " +
|
||||
" l.to_user_id, COALESCE(tu.real_name, tu.username) AS to_user_name, " +
|
||||
" l.operator_id, COALESCE(ou.real_name, ou.username) AS operator_name, " +
|
||||
" l.reason, l.remark, l.old_deadline, l.new_deadline, l.old_priority, l.new_priority, " +
|
||||
" l.delay_days, l.penalty_score, l.create_time " +
|
||||
"FROM task_transfer_log l " +
|
||||
"LEFT JOIN task t ON t.id = l.task_id " +
|
||||
"LEFT JOIN sys_user fu ON fu.id = l.from_user_id " +
|
||||
"LEFT JOIN sys_user tu ON tu.id = l.to_user_id " +
|
||||
"LEFT JOIN sys_user ou ON ou.id = l.operator_id " +
|
||||
"WHERE l.transfer_type = 'transfer' " +
|
||||
" AND l.remark = '离职交接' " +
|
||||
"<if test='query.fromUserId != null'> AND l.from_user_id = #{query.fromUserId}</if> " +
|
||||
"<if test='query.toUserId != null'> AND l.to_user_id = #{query.toUserId}</if> " +
|
||||
"<if test='query.operatorId != null'> AND l.operator_id = #{query.operatorId}</if> " +
|
||||
"<if test='query.startTime != null'> AND l.create_time >= #{query.startTime}</if> " +
|
||||
"<if test='query.endTime != null'> AND l.create_time <= #{query.endTime}</if> " +
|
||||
"<if test='query.keyword != null and query.keyword != \"\"'> " +
|
||||
"AND (t.task_no LIKE CONCAT('%', #{query.keyword}, '%') " +
|
||||
" OR t.title LIKE CONCAT('%', #{query.keyword}, '%') " +
|
||||
" OR l.reason LIKE CONCAT('%', #{query.keyword}, '%') " +
|
||||
" OR COALESCE(fu.real_name, fu.username, '') LIKE CONCAT('%', #{query.keyword}, '%') " +
|
||||
" OR COALESCE(tu.real_name, tu.username, '') LIKE CONCAT('%', #{query.keyword}, '%') " +
|
||||
" OR COALESCE(ou.real_name, ou.username, '') LIKE CONCAT('%', #{query.keyword}, '%')) " +
|
||||
"</if> " +
|
||||
"ORDER BY l.create_time DESC, l.id DESC" +
|
||||
"</script>")
|
||||
IPage<UserOffboardingRecordVO> selectOffboardingRecordPage(
|
||||
Page<UserOffboardingRecordVO> page,
|
||||
@Param("query") UserOffboardingRecordQuery query
|
||||
);
|
||||
|
||||
@Select("SELECT l.id, l.transfer_type, l.task_id, t.task_no, t.title AS task_title, t.status AS task_status, " +
|
||||
" t.spu_info AS task_spu_info, l.from_user_id, COALESCE(fu.real_name, fu.username) AS from_user_name, " +
|
||||
" l.to_user_id, COALESCE(tu.real_name, tu.username) AS to_user_name, " +
|
||||
" l.operator_id, COALESCE(ou.real_name, ou.username) AS operator_name, " +
|
||||
" l.reason, l.remark, l.old_deadline, l.new_deadline, l.old_priority, l.new_priority, " +
|
||||
" l.delay_days, l.penalty_score, l.create_time " +
|
||||
"FROM task_transfer_log l " +
|
||||
"LEFT JOIN task t ON t.id = l.task_id " +
|
||||
"LEFT JOIN sys_user fu ON fu.id = l.from_user_id " +
|
||||
"LEFT JOIN sys_user tu ON tu.id = l.to_user_id " +
|
||||
"LEFT JOIN sys_user ou ON ou.id = l.operator_id " +
|
||||
"WHERE l.id = #{id} AND l.transfer_type = 'transfer' AND l.remark = '离职交接' " +
|
||||
"LIMIT 1")
|
||||
UserOffboardingRecordDetailVO selectOffboardingRecordDetail(@Param("id") Long id);
|
||||
}
|
||||
|
||||
@ -2,6 +2,8 @@ package com.designhub.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.designhub.dto.UserOffboardingRecordQuery;
|
||||
import com.designhub.dto.UserOffboardingRequest;
|
||||
import com.designhub.entity.Task;
|
||||
import com.designhub.entity.TaskItem;
|
||||
@ -12,6 +14,8 @@ import com.designhub.mapper.TaskItemMapper;
|
||||
import com.designhub.mapper.TaskMapper;
|
||||
import com.designhub.mapper.TaskTransferLogMapper;
|
||||
import com.designhub.mapper.UserMapper;
|
||||
import com.designhub.vo.UserOffboardingRecordDetailVO;
|
||||
import com.designhub.vo.UserOffboardingRecordVO;
|
||||
import com.designhub.vo.UserOffboardingExecuteVO;
|
||||
import com.designhub.vo.UserOffboardingPreviewVO;
|
||||
import lombok.Builder;
|
||||
@ -54,16 +58,7 @@ public class UserOffboardingService {
|
||||
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public UserOffboardingExecuteVO execute(UserOffboardingRequest request, Long operatorId) {
|
||||
if (operatorId == null) {
|
||||
throw new BusinessException("操作人不能为空");
|
||||
}
|
||||
User operator = userMapper.selectById(operatorId);
|
||||
if (operator == null) {
|
||||
throw new BusinessException("操作人不存在");
|
||||
}
|
||||
if (operator.getIsAdmin() == null || operator.getIsAdmin() != 1) {
|
||||
throw new BusinessException("仅管理员可执行离职交接");
|
||||
}
|
||||
assertAdminOperator(operatorId, "仅管理员可执行离职交接");
|
||||
|
||||
OffboardingContext context = buildContext(request);
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
@ -96,6 +91,61 @@ public class UserOffboardingService {
|
||||
.build();
|
||||
}
|
||||
|
||||
public Page<UserOffboardingRecordVO> getRecordPage(UserOffboardingRecordQuery query, Long operatorId) {
|
||||
assertAdminOperator(operatorId, "仅管理员可查看离职交接记录");
|
||||
UserOffboardingRecordQuery safeQuery = normalizeRecordQuery(query);
|
||||
Page<UserOffboardingRecordVO> page = new Page<>(safeQuery.getPageNum(), safeQuery.getPageSize());
|
||||
taskTransferLogMapper.selectOffboardingRecordPage(page, safeQuery);
|
||||
return page;
|
||||
}
|
||||
|
||||
public UserOffboardingRecordDetailVO getRecordDetail(Long id, Long operatorId) {
|
||||
assertAdminOperator(operatorId, "仅管理员可查看离职交接记录");
|
||||
if (id == null) {
|
||||
throw new BusinessException("记录ID不能为空");
|
||||
}
|
||||
UserOffboardingRecordDetailVO detail = taskTransferLogMapper.selectOffboardingRecordDetail(id);
|
||||
if (detail == null) {
|
||||
throw new BusinessException("离职交接记录不存在");
|
||||
}
|
||||
return detail;
|
||||
}
|
||||
|
||||
private User assertAdminOperator(Long operatorId, String forbiddenMessage) {
|
||||
if (operatorId == null) {
|
||||
throw new BusinessException("操作人不能为空");
|
||||
}
|
||||
User operator = userMapper.selectById(operatorId);
|
||||
if (operator == null) {
|
||||
throw new BusinessException("操作人不存在");
|
||||
}
|
||||
if (operator.getIsAdmin() == null || operator.getIsAdmin() != 1) {
|
||||
throw new BusinessException(forbiddenMessage);
|
||||
}
|
||||
return operator;
|
||||
}
|
||||
|
||||
private UserOffboardingRecordQuery normalizeRecordQuery(UserOffboardingRecordQuery query) {
|
||||
UserOffboardingRecordQuery safeQuery = query == null ? new UserOffboardingRecordQuery() : query;
|
||||
if (safeQuery.getPageNum() == null || safeQuery.getPageNum() < 1) {
|
||||
safeQuery.setPageNum(1);
|
||||
}
|
||||
if (safeQuery.getPageSize() == null || safeQuery.getPageSize() < 1) {
|
||||
safeQuery.setPageSize(10);
|
||||
}
|
||||
if (safeQuery.getPageSize() > 100) {
|
||||
safeQuery.setPageSize(100);
|
||||
}
|
||||
if (StringUtils.hasText(safeQuery.getKeyword())) {
|
||||
safeQuery.setKeyword(safeQuery.getKeyword().trim());
|
||||
}
|
||||
if (safeQuery.getStartTime() != null && safeQuery.getEndTime() != null
|
||||
&& safeQuery.getStartTime().isAfter(safeQuery.getEndTime())) {
|
||||
throw new BusinessException("开始时间不能晚于结束时间");
|
||||
}
|
||||
return safeQuery;
|
||||
}
|
||||
|
||||
private OffboardingContext buildContext(UserOffboardingRequest request) {
|
||||
UserOffboardingRequest safeRequest = normalizeAndValidate(request);
|
||||
User fromUser = userMapper.selectById(safeRequest.getFromUserId());
|
||||
|
||||
@ -0,0 +1,39 @@
|
||||
package com.designhub.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 离职交接记录详情
|
||||
*/
|
||||
@Data
|
||||
public class UserOffboardingRecordDetailVO {
|
||||
|
||||
private Long id;
|
||||
private String transferType;
|
||||
private Long taskId;
|
||||
private String taskNo;
|
||||
private String taskTitle;
|
||||
private String taskStatus;
|
||||
private String taskSpuInfo;
|
||||
|
||||
private Long fromUserId;
|
||||
private String fromUserName;
|
||||
private Long toUserId;
|
||||
private String toUserName;
|
||||
private Long operatorId;
|
||||
private String operatorName;
|
||||
|
||||
private String reason;
|
||||
private String remark;
|
||||
|
||||
private LocalDateTime oldDeadline;
|
||||
private LocalDateTime newDeadline;
|
||||
private Integer oldPriority;
|
||||
private Integer newPriority;
|
||||
private Integer delayDays;
|
||||
private Integer penaltyScore;
|
||||
|
||||
private LocalDateTime createTime;
|
||||
}
|
||||
@ -0,0 +1,37 @@
|
||||
package com.designhub.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 离职交接记录
|
||||
*/
|
||||
@Data
|
||||
public class UserOffboardingRecordVO {
|
||||
|
||||
private Long id;
|
||||
private Long taskId;
|
||||
private String taskNo;
|
||||
private String taskTitle;
|
||||
private String taskStatus;
|
||||
|
||||
private Long fromUserId;
|
||||
private String fromUserName;
|
||||
private Long toUserId;
|
||||
private String toUserName;
|
||||
private Long operatorId;
|
||||
private String operatorName;
|
||||
|
||||
private String reason;
|
||||
private String remark;
|
||||
|
||||
private LocalDateTime oldDeadline;
|
||||
private LocalDateTime newDeadline;
|
||||
private Integer oldPriority;
|
||||
private Integer newPriority;
|
||||
private Integer delayDays;
|
||||
private Integer penaltyScore;
|
||||
|
||||
private LocalDateTime createTime;
|
||||
}
|
||||
@ -242,3 +242,56 @@ export const executeUserOffboarding = async (
|
||||
const response = await request.post('/users/offboarding/execute', payload);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export interface UserOffboardingRecordItem {
|
||||
id: number;
|
||||
taskId: number;
|
||||
taskNo: string;
|
||||
taskTitle: string;
|
||||
taskStatus: string;
|
||||
fromUserId: number;
|
||||
fromUserName: string;
|
||||
toUserId: number;
|
||||
toUserName: string;
|
||||
operatorId: number;
|
||||
operatorName: string;
|
||||
reason: string;
|
||||
remark: string;
|
||||
oldDeadline?: string;
|
||||
newDeadline?: string;
|
||||
oldPriority?: number;
|
||||
newPriority?: number;
|
||||
delayDays?: number;
|
||||
penaltyScore?: number;
|
||||
createTime: string;
|
||||
}
|
||||
|
||||
export interface UserOffboardingRecordDetail extends UserOffboardingRecordItem {
|
||||
transferType: string;
|
||||
taskSpuInfo?: string;
|
||||
}
|
||||
|
||||
export interface UserOffboardingRecordQuery {
|
||||
pageNum?: number;
|
||||
pageSize?: number;
|
||||
fromUserId?: number;
|
||||
toUserId?: number;
|
||||
operatorId?: number;
|
||||
keyword?: string;
|
||||
startTime?: string;
|
||||
endTime?: string;
|
||||
}
|
||||
|
||||
export const getUserOffboardingRecords = async (
|
||||
query: UserOffboardingRecordQuery
|
||||
): Promise<PageResult<UserOffboardingRecordItem>> => {
|
||||
const response = await request.get('/users/offboarding/records', { params: query });
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const getUserOffboardingRecordDetail = async (
|
||||
id: number
|
||||
): Promise<UserOffboardingRecordDetail> => {
|
||||
const response = await request.get(`/users/offboarding/records/${id}`);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
1
frontend/src/types/components.d.ts
vendored
1
frontend/src/types/components.d.ts
vendored
@ -41,6 +41,7 @@ declare module 'vue' {
|
||||
ARadio: typeof import('ant-design-vue/es')['Radio']
|
||||
ARadioButton: typeof import('ant-design-vue/es')['RadioButton']
|
||||
ARadioGroup: typeof import('ant-design-vue/es')['RadioGroup']
|
||||
ARangePicker: typeof import('ant-design-vue/es')['RangePicker']
|
||||
ARate: typeof import('ant-design-vue/es')['Rate']
|
||||
ARow: typeof import('ant-design-vue/es')['Row']
|
||||
ASelect: typeof import('ant-design-vue/es')['Select']
|
||||
|
||||
@ -677,6 +677,136 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-card offboarding-records">
|
||||
<div class="table-header offboarding-records-header">
|
||||
<h3 class="chart-title">离职交接记录</h3>
|
||||
<a-space>
|
||||
<a-button size="small" @click="resetOffboardingRecordFilters">重置</a-button>
|
||||
<a-button type="primary" size="small" @click="loadOffboardingRecords(true)">
|
||||
<template #icon><Icon name="search" size="sm" /></template>
|
||||
查询
|
||||
</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
|
||||
<div class="offboarding-record-filters">
|
||||
<a-input
|
||||
v-model:value="offboardingRecordFilters.keyword"
|
||||
allow-clear
|
||||
placeholder="任务编号/标题/操作人/原因"
|
||||
style="width: 240px;"
|
||||
@press-enter="loadOffboardingRecords(true)"
|
||||
/>
|
||||
<a-select
|
||||
v-model:value="offboardingRecordFilters.fromUserId"
|
||||
allow-clear
|
||||
show-search
|
||||
:filter-option="filterOffboardingUserOption"
|
||||
placeholder="离职成员"
|
||||
style="width: 180px;"
|
||||
>
|
||||
<a-select-option v-for="item in offboardingUsers" :key="item.id" :value="item.id">
|
||||
{{ item.realName || item.username }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<a-select
|
||||
v-model:value="offboardingRecordFilters.toUserId"
|
||||
allow-clear
|
||||
show-search
|
||||
:filter-option="filterOffboardingUserOption"
|
||||
placeholder="接替成员"
|
||||
style="width: 180px;"
|
||||
>
|
||||
<a-select-option v-for="item in offboardingUsers" :key="item.id" :value="item.id">
|
||||
{{ item.realName || item.username }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<a-select
|
||||
v-model:value="offboardingRecordFilters.operatorId"
|
||||
allow-clear
|
||||
show-search
|
||||
:filter-option="filterOffboardingUserOption"
|
||||
placeholder="操作人"
|
||||
style="width: 180px;"
|
||||
>
|
||||
<a-select-option v-for="item in offboardingUsers" :key="item.id" :value="item.id">
|
||||
{{ item.realName || item.username }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<a-range-picker
|
||||
v-model:value="offboardingRecordDateRange"
|
||||
show-time
|
||||
format="YYYY-MM-DD HH:mm"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
style="width: 340px;"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<a-table
|
||||
size="small"
|
||||
row-key="id"
|
||||
:columns="offboardingRecordColumns"
|
||||
:data-source="offboardingRecords"
|
||||
:loading="offboardingRecordLoading"
|
||||
:pagination="{
|
||||
current: offboardingRecordPage.current,
|
||||
pageSize: offboardingRecordPage.pageSize,
|
||||
total: offboardingRecordPage.total,
|
||||
showSizeChanger: true,
|
||||
showTotal: (total: number) => `共 ${total} 条`,
|
||||
onChange: handleOffboardingRecordPageChange,
|
||||
onShowSizeChange: handleOffboardingRecordPageChange
|
||||
}"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'taskStatus'">
|
||||
{{ getTaskStatusLabel(record.taskStatus) }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'createTime'">
|
||||
{{ formatDateTime(record.createTime) }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'actions'">
|
||||
<a-button type="link" size="small" @click="openOffboardingRecordDetail(record)">
|
||||
详情
|
||||
</a-button>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
|
||||
<a-modal
|
||||
v-model:open="offboardingDetailVisible"
|
||||
title="离职交接记录详情"
|
||||
:footer="null"
|
||||
width="760px"
|
||||
>
|
||||
<a-spin :spinning="offboardingDetailLoading">
|
||||
<a-empty v-if="!offboardingRecordDetail" description="暂无详情" />
|
||||
<a-descriptions v-else :column="2" bordered size="small">
|
||||
<a-descriptions-item label="记录ID">{{ offboardingRecordDetail.id }}</a-descriptions-item>
|
||||
<a-descriptions-item label="时间">{{ formatDateTime(offboardingRecordDetail.createTime) }}</a-descriptions-item>
|
||||
<a-descriptions-item label="任务编号">{{ offboardingRecordDetail.taskNo || '--' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="任务状态">{{ getTaskStatusLabel(offboardingRecordDetail.taskStatus) }}</a-descriptions-item>
|
||||
<a-descriptions-item label="任务标题" :span="2">{{ offboardingRecordDetail.taskTitle || '--' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="SPU信息" :span="2">{{ offboardingRecordDetail.taskSpuInfo || '--' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="离职成员">{{ offboardingRecordDetail.fromUserName || '--' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="接替成员">{{ offboardingRecordDetail.toUserName || '--' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="操作人">{{ offboardingRecordDetail.operatorName || '--' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="变更类型">{{ offboardingRecordDetail.transferType || '--' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="原截止时间">{{ formatDateTime(offboardingRecordDetail.oldDeadline) }}</a-descriptions-item>
|
||||
<a-descriptions-item label="新截止时间">{{ formatDateTime(offboardingRecordDetail.newDeadline) }}</a-descriptions-item>
|
||||
<a-descriptions-item label="原优先级">{{ offboardingRecordDetail.oldPriority ?? '--' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="新优先级">{{ offboardingRecordDetail.newPriority ?? '--' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="原因" :span="2">
|
||||
<div class="offboarding-detail-text">{{ offboardingRecordDetail.reason || '--' }}</div>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="备注" :span="2">
|
||||
<div class="offboarding-detail-text">{{ offboardingRecordDetail.remark || '--' }}</div>
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-spin>
|
||||
</a-modal>
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
|
||||
@ -2991,9 +3121,13 @@ import {
|
||||
setAdmin,
|
||||
previewUserOffboarding,
|
||||
executeUserOffboarding,
|
||||
getUserOffboardingRecords,
|
||||
getUserOffboardingRecordDetail,
|
||||
type User,
|
||||
type UserOffboardingPreview,
|
||||
type UserOffboardingRequest
|
||||
type UserOffboardingRequest,
|
||||
type UserOffboardingRecordItem,
|
||||
type UserOffboardingRecordDetail
|
||||
} from '@/api/user'
|
||||
import {
|
||||
getAllS3Configs,
|
||||
@ -3141,6 +3275,23 @@ const offboardingPreviewLoading = ref(false)
|
||||
const offboardingExecuting = ref(false)
|
||||
const offboardingPreview = ref<UserOffboardingPreview | null>(null)
|
||||
const offboardingPreviewDigest = ref('')
|
||||
const offboardingRecordLoading = ref(false)
|
||||
const offboardingRecords = ref<UserOffboardingRecordItem[]>([])
|
||||
const offboardingRecordPage = reactive({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: 0
|
||||
})
|
||||
const offboardingRecordDateRange = ref<string[]>([])
|
||||
const offboardingRecordFilters = reactive({
|
||||
keyword: '',
|
||||
fromUserId: undefined as number | undefined,
|
||||
toUserId: undefined as number | undefined,
|
||||
operatorId: undefined as number | undefined
|
||||
})
|
||||
const offboardingDetailVisible = ref(false)
|
||||
const offboardingDetailLoading = ref(false)
|
||||
const offboardingRecordDetail = ref<UserOffboardingRecordDetail | null>(null)
|
||||
const offboardingForm = reactive<UserOffboardingRequest>({
|
||||
fromUserId: undefined,
|
||||
toUserId: undefined,
|
||||
@ -3387,6 +3538,17 @@ const offboardingTaskColumns = [
|
||||
}
|
||||
]
|
||||
|
||||
const offboardingRecordColumns = [
|
||||
{ title: '时间', dataIndex: 'createTime', key: 'createTime', width: 170 },
|
||||
{ title: '任务编号', dataIndex: 'taskNo', key: 'taskNo', width: 170 },
|
||||
{ title: '任务标题', dataIndex: 'taskTitle', key: 'taskTitle', ellipsis: true },
|
||||
{ title: '离职成员', dataIndex: 'fromUserName', key: 'fromUserName', width: 130 },
|
||||
{ title: '接替成员', dataIndex: 'toUserName', key: 'toUserName', width: 130 },
|
||||
{ title: '操作人', dataIndex: 'operatorName', key: 'operatorName', width: 130 },
|
||||
{ title: '任务状态', dataIndex: 'taskStatus', key: 'taskStatus', width: 110 },
|
||||
{ title: '操作', key: 'actions', width: 80, fixed: 'right' }
|
||||
]
|
||||
|
||||
// 过滤出当前部门的用户(用于负责人选择)
|
||||
const departmentUsers = computed(() => {
|
||||
// 部门负责人应该可以选择任何用户,不限制于该部门的成员
|
||||
@ -4341,7 +4503,7 @@ const handleExecuteOffboarding = async () => {
|
||||
`日志${result.transferLogCount || 0}条`
|
||||
].join(',')
|
||||
message.success(`交接执行成功:${summary}`)
|
||||
await Promise.all([loadDepartmentUsers(), loadDepartments()])
|
||||
await Promise.all([loadDepartmentUsers(), loadDepartments(), loadOffboardingRecords(true)])
|
||||
} catch (error: any) {
|
||||
console.error('离职交接执行失败:', error)
|
||||
message.error(error?.response?.data?.message || error?.message || '执行失败,请联系管理员')
|
||||
@ -4350,6 +4512,67 @@ const handleExecuteOffboarding = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
const loadOffboardingRecords = async (resetPage = false) => {
|
||||
if (resetPage) {
|
||||
offboardingRecordPage.current = 1
|
||||
}
|
||||
offboardingRecordLoading.value = true
|
||||
try {
|
||||
const [startTime, endTime] = offboardingRecordDateRange.value || []
|
||||
const data = await getUserOffboardingRecords({
|
||||
pageNum: offboardingRecordPage.current,
|
||||
pageSize: offboardingRecordPage.pageSize,
|
||||
keyword: offboardingRecordFilters.keyword?.trim() || undefined,
|
||||
fromUserId: offboardingRecordFilters.fromUserId,
|
||||
toUserId: offboardingRecordFilters.toUserId,
|
||||
operatorId: offboardingRecordFilters.operatorId,
|
||||
startTime: startTime || undefined,
|
||||
endTime: endTime || undefined
|
||||
})
|
||||
offboardingRecords.value = data.records || []
|
||||
offboardingRecordPage.total = data.total || 0
|
||||
offboardingRecordPage.current = data.current || offboardingRecordPage.current
|
||||
offboardingRecordPage.pageSize = data.size || offboardingRecordPage.pageSize
|
||||
} catch (error: any) {
|
||||
console.error('加载离职交接记录失败:', error)
|
||||
message.error(error?.response?.data?.message || error?.message || '加载离职交接记录失败')
|
||||
} finally {
|
||||
offboardingRecordLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleOffboardingRecordPageChange = (page: number, pageSize: number) => {
|
||||
offboardingRecordPage.current = page
|
||||
offboardingRecordPage.pageSize = pageSize
|
||||
loadOffboardingRecords()
|
||||
}
|
||||
|
||||
const resetOffboardingRecordFilters = () => {
|
||||
offboardingRecordFilters.keyword = ''
|
||||
offboardingRecordFilters.fromUserId = undefined
|
||||
offboardingRecordFilters.toUserId = undefined
|
||||
offboardingRecordFilters.operatorId = undefined
|
||||
offboardingRecordDateRange.value = []
|
||||
loadOffboardingRecords(true)
|
||||
}
|
||||
|
||||
const openOffboardingRecordDetail = async (record: UserOffboardingRecordItem) => {
|
||||
if (!record?.id) {
|
||||
return
|
||||
}
|
||||
offboardingDetailVisible.value = true
|
||||
offboardingRecordDetail.value = null
|
||||
offboardingDetailLoading.value = true
|
||||
try {
|
||||
offboardingRecordDetail.value = await getUserOffboardingRecordDetail(record.id)
|
||||
} catch (error: any) {
|
||||
console.error('加载离职交接详情失败:', error)
|
||||
message.error(error?.response?.data?.message || error?.message || '加载详情失败')
|
||||
} finally {
|
||||
offboardingDetailLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 部门搜索处理
|
||||
const onDepartmentSearch = () => {
|
||||
// 搜索逻辑通过 computed 属性 filteredDepartments 自动处理
|
||||
@ -6272,6 +6495,7 @@ watch(activeTab, (value) => {
|
||||
if (!usersLoaded.value) {
|
||||
loadDepartmentUsers()
|
||||
}
|
||||
loadOffboardingRecords()
|
||||
}
|
||||
// 切换到部门管理 tab 时,刷新部门数据以获取最新成员数
|
||||
if (value === 'departments') {
|
||||
@ -6408,6 +6632,32 @@ watch(activeTab, (value) => {
|
||||
}
|
||||
}
|
||||
|
||||
.offboarding-records {
|
||||
margin-top: 16px;
|
||||
|
||||
.offboarding-records-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
|
||||
.offboarding-record-filters {
|
||||
margin-bottom: 12px;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.offboarding-detail-text {
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
color: $text-primary;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.balance-display {
|
||||
font-size: $font-size-xl;
|
||||
color: $color-info;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user