feat: 接单部门审核时添加指定设计师接单功能,显示设计师工作量

This commit is contained in:
Your Name 2026-01-02 19:32:53 +08:00
parent b44a7e744f
commit e86b5a45f3
9 changed files with 335 additions and 39 deletions

View File

@ -62,7 +62,8 @@ public class TaskApprovalController {
) {
String token = jwtUtil.extractTokenFromHeader(authHeader);
Long approverId = jwtUtil.getUserIdFromToken(token);
taskApprovalService.approveReceivingLevel(id, approverId, dto);
// 从dto中获取指定的接收人ID
taskApprovalService.approveReceivingLevel(id, approverId, dto, dto.getAssignedReceiverId());
return Result.success();
}

View File

@ -125,5 +125,14 @@ public class UserController {
log.info("调整用户部门结果: userId={}, deptId={}, result={}", id, deptId, result);
return Result.success(result);
}
@Operation(summary = "获取设计师列表及工作量", description = "获取部门内的设计师列表,包含每个设计师当前进行中的任务数量")
@GetMapping("/designers-with-workload")
public Result<List<Map<String, Object>>> getDesignersWithWorkload(
@RequestParam(required = false) Long deptId
) {
log.info("获取设计师列表及工作量: deptId={}", deptId);
return Result.success(userService.getDesignersWithWorkload(deptId));
}
}

View File

@ -17,6 +17,11 @@ public class TaskApprovalDTO {
*/
private TaskUpdateDTO taskUpdates;
/**
* 指定的接收人ID(可选,用于接单部门审核时指定设计师)
*/
private Long assignedReceiverId;
// Getters and Setters
public String getComment() {
return comment;
@ -33,4 +38,12 @@ public class TaskApprovalDTO {
public void setTaskUpdates(TaskUpdateDTO taskUpdates) {
this.taskUpdates = taskUpdates;
}
public Long getAssignedReceiverId() {
return assignedReceiverId;
}
public void setAssignedReceiverId(Long assignedReceiverId) {
this.assignedReceiverId = assignedReceiverId;
}
}

View File

@ -202,5 +202,12 @@ public interface TaskMapper extends BaseMapper<Task> {
IPage<Task> selectDeptReceivingTaskPage(Page<Task> page,
@Param("deptId") Long deptId,
@Param("status") String status);
/**
* 查询设计师进行中的子任务数量
*/
@Select("SELECT COUNT(*) FROM task_item WHERE receiver_id = #{receiverId} " +
"AND status = 'in_progress' AND is_locked = 0")
Long countSubTasksByReceiverId(@Param("receiverId") Long receiverId);
}

View File

@ -189,9 +189,13 @@ public class TaskApprovalService {
/**
* 接单部门负责人审核通过
* @param taskId 任务ID
* @param approverId 审核人ID
* @param dto 审核数据(包含备注和任务修改)
* @param assignedReceiverId 指定的接收人ID(可选)
*/
@Transactional(rollbackFor = Exception.class)
public void approveReceivingLevel(Long taskId, Long approverId, TaskApprovalDTO dto) {
public void approveReceivingLevel(Long taskId, Long approverId, TaskApprovalDTO dto, Long assignedReceiverId) {
Task task = getTaskForApproval(taskId, "pending_receiving_approval");
validateApprover(task, approverId, "receiving_leader");
@ -201,13 +205,31 @@ public class TaskApprovalService {
logApproval(taskId, approverId, 2, "modify", dto.getComment());
}
// 如果指定了接收人,验证接收人是否存在并设置
if (assignedReceiverId != null) {
com.designhub.entity.User receiver = userMapper.selectById(assignedReceiverId);
if (receiver == null) {
throw new BusinessException(ResultCode.PARAM_ERROR, "指定的接收人不存在");
}
task.setReceiverId(assignedReceiverId);
task.setReceiverDeptId(receiver.getDeptId());
task.setReleaseType("assigned"); // 设置为指定模式
log.info("任务【{}】已指定给设计师【{}】", task.getTitle(), receiver.getRealName());
}
// 判断是否为流水线任务
if (task.getIsPipeline() != null && task.getIsPipeline() == 1) {
// 流水线任务:拆分子任务
splitIntoPipelineSubTasks(task);
} else {
// 普通任务:直接发布到任务大厅
task.setStatus("pending");
// 普通任务:根据是否指定接收人设置状态
if (assignedReceiverId != null) {
task.setStatus("in_progress"); // 指定接单直接进入进行中状态
task.setStartTime(LocalDateTime.now());
task.setActualStartTime(LocalDateTime.now());
} else {
task.setStatus("pending"); // 未指定则发布到大厅
}
}
// 更新审核状态
@ -226,13 +248,33 @@ public class TaskApprovalService {
}
// 记录审核日志
logApproval(taskId, approverId, 2, "approve", dto.getComment());
String logComment = dto.getComment();
if (assignedReceiverId != null) {
com.designhub.entity.User receiver = userMapper.selectById(assignedReceiverId);
logComment = (logComment != null ? logComment + ";" : "") +
"已指定给设计师:" + (receiver != null ? receiver.getRealName() : assignedReceiverId);
}
logApproval(taskId, approverId, 2, "approve", logComment);
// TODO: 通知任务大厅(所有接单部门成员)
// 暂时不实现群发通知功能,后续可以通过WebSocket或轮询方式实现
log.info("任务【{}】已发布到任务大厅", task.getTitle());
// 如果指定了接收人,发送通知
if (assignedReceiverId != null) {
// TODO: 通知指定的设计师
log.info("任务【{}】已指定给设计师", task.getTitle());
} else {
// TODO: 通知任务大厅(所有接单部门成员)
log.info("任务【{}】已发布到任务大厅", task.getTitle());
}
log.info("接单部门负责人审核通过,任务ID:{},审核人:{}", taskId, approverId);
log.info("接单部门负责人审核通过,任务ID:{},审核人:{},指定接收人:{}",
taskId, approverId, assignedReceiverId);
}
/**
* 接单部门负责人审核通过(重载方法,保持向后兼容)
*/
@Transactional(rollbackFor = Exception.class)
public void approveReceivingLevel(Long taskId, Long approverId, TaskApprovalDTO dto) {
approveReceivingLevel(taskId, approverId, dto, null);
}
/**

View File

@ -19,6 +19,7 @@ import com.designhub.vo.UserInfoVO;
import com.designhub.vo.UserDepartmentVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@ -48,6 +49,9 @@ public class UserService {
private final DepartmentService departmentService;
private final JwtUtil jwtUtil;
private final BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
@Autowired(required = false)
private com.designhub.mapper.TaskMapper taskMapper;
private Department getDepartmentOrThrow(Long deptId) {
if (deptId == null) {
@ -1195,4 +1199,67 @@ public class UserService {
return matchedDeptIds;
}
/**
* 获取设计师列表及工作量
* @param deptId 部门ID(可选,如果不传则返回所有设计师)
* @return 设计师列表,包含每个设计师的进行中任务数量
*/
public List<Map<String, Object>> getDesignersWithWorkload(Long deptId) {
log.info("获取设计师列表及工作量: deptId={}", deptId);
// 查询用户列表
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(User::getStatus, 1); // 只查询启用的用户
queryWrapper.eq(User::getDeleted, 0); // 未删除
if (deptId != null) {
queryWrapper.eq(User::getDeptId, deptId);
}
queryWrapper.orderByAsc(User::getRealName);
List<User> users = userMapper.selectList(queryWrapper);
// 构建返回结果
List<Map<String, Object>> result = new ArrayList<>();
for (User user : users) {
Map<String, Object> designerInfo = new HashMap<>();
designerInfo.put("id", user.getId());
designerInfo.put("username", user.getUsername());
designerInfo.put("realName", user.getRealName());
designerInfo.put("deptId", user.getDeptId());
// 查询进行中的任务数量(包括普通任务和子任务)
int inProgressCount = 0;
if (taskMapper != null) {
// 查询普通任务
Long normalTaskCount = taskMapper.selectCount(
new LambdaQueryWrapper<com.designhub.entity.Task>()
.eq(com.designhub.entity.Task::getReceiverId, user.getId())
.eq(com.designhub.entity.Task::getStatus, "in_progress")
.eq(com.designhub.entity.Task::getDeleted, 0)
);
// 查询子任务(TaskItem)
Long subTaskCount = taskMapper.countSubTasksByReceiverId(user.getId());
inProgressCount = (normalTaskCount != null ? normalTaskCount.intValue() : 0) +
(subTaskCount != null ? subTaskCount.intValue() : 0);
}
designerInfo.put("inProgressCount", inProgressCount);
// 获取部门名称
if (user.getDeptId() != null) {
Department dept = departmentMapper.selectById(user.getDeptId());
if (dept != null) {
designerInfo.put("deptName", dept.getDeptName());
}
}
result.add(designerInfo);
}
log.info("查询到{}个设计师", result.size());
return result;
}
}

View File

@ -452,6 +452,7 @@ export const getTaskItemsProgress = (taskId: number) => {
export interface TaskApprovalDTO {
comment?: string // 审核意见
taskUpdates?: TaskUpdateDTO // 任务修改(可选)
assignedReceiverId?: number // 指定的接收人ID(用于接单部门审核时指定设计师)
}
/**

View File

@ -139,3 +139,25 @@ export const updateUserDepartment = async (id: number, deptId: number | null): P
return response.data;
};
/**
* 设计师及工作量信息
*/
export interface DesignerWithWorkload {
id: number;
username: string;
realName: string;
deptId?: number;
deptName?: string;
inProgressCount: number; // 进行中的任务数量
}
/**
* 获取设计师列表及工作量
*/
export const getDesignersWithWorkload = async (deptId?: number): Promise<DesignerWithWorkload[]> => {
const params: any = {};
if (deptId) params.deptId = deptId;
const response = await request.get('/users/designers-with-workload', { params });
return response.data || [];
};

View File

@ -1875,11 +1875,13 @@
<script setup lang="ts">
import { ref, reactive, computed, onMounted, onUnmounted, h } from 'vue'
import { useRouter } from 'vue-router'
import { message, Modal, InputNumber, Textarea } from 'ant-design-vue'
import { message, Modal, InputNumber, Textarea, Radio, RadioGroup } from 'ant-design-vue'
import DefaultLayout from '@/layouts/DefaultLayout.vue'
import Icon from '@/components/Icon.vue'
import * as taskApi from '@/api/task'
import * as userApi from '@/api/user'
import type { Task, TaskApprovalDTO } from '@/api/task'
import type { DesignerWithWorkload } from '@/api/user'
import { useConfigStore } from '@/stores/config'
import dayjs from 'dayjs'
@ -2358,40 +2360,172 @@ const getApprovalStatusText = (status: string) => {
// 两级审核:审核通过
const handleApproveTwoLevelTask = async (task: Task) => {
Modal.confirm({
title: '确认审核通过',
content: `确定通过任务"${task.title}"的审核吗?`,
okText: '确认通过',
cancelText: '取消',
onOk: async () => {
try {
approvalLoading.value = true
const approvalData: TaskApprovalDTO = {
comment: '审核通过'
}
// 根据当前审核角色调用不同的API
if (task.currentApproverRole === 'demand_leader') {
// 如果是接单部门负责人审核,显示指定设计师选项
if (task.currentApproverRole === 'receiving_leader') {
showAssignDesignerModal(task)
} else {
// 需求部门负责人审核,直接通过
Modal.confirm({
title: '确认审核通过',
content: `确定通过任务"${task.title}"的审核吗?`,
okText: '确认通过',
cancelText: '取消',
onOk: async () => {
try {
approvalLoading.value = true
const approvalData: TaskApprovalDTO = {
comment: '审核通过'
}
await taskApi.approveDemandLevel(task.id!, approvalData)
message.success('需求部门审核通过')
} else if (task.currentApproverRole === 'receiving_leader') {
await taskApi.approveReceivingLevel(task.id!, approvalData)
message.success('接单部门审核通过,任务已发布到大厅')
// 刷新列表和数量
loadPendingApprovalTasks()
loadAllTabCounts()
loadDashboardStats()
} catch (error: any) {
console.error('审核失败:', error)
message.error(error.msg || error.message || '审核失败')
} finally {
approvalLoading.value = false
}
// 刷新列表和数量
loadPendingApprovalTasks()
loadAllTabCounts()
loadDashboardStats()
} catch (error: any) {
console.error('审核失败:', error)
message.error(error.msg || error.message || '审核失败')
} finally {
approvalLoading.value = false
}
})
}
}
// 显示指定设计师弹窗(接单部门审核时)
const showAssignDesignerModal = async (task: Task) => {
try {
// 获取当前部门的设计师列表
const userInfo = localStorage.getItem('userInfo')
let deptId: number | undefined
if (userInfo) {
const user = JSON.parse(userInfo)
deptId = user.deptId
}
})
// 加载设计师列表及工作量
const designers = await userApi.getDesignersWithWorkload(deptId)
if (designers.length === 0) {
message.warning('当前部门没有可用的设计师')
return
}
let assignType = 'auto' // 'auto' 或 'manual'
let selectedDesignerId: number | null = null
const modalContent = h('div', { style: 'padding: 10px 0;' }, [
h('p', { style: 'margin-bottom: 16px; color: #666;' },
'选择任务接单方式:'
),
h(RadioGroup, {
value: assignType,
onChange: (e: any) => {
assignType = e.target.value
},
style: { width: '100%', marginBottom: '16px' }
}, [
h(Radio, { value: 'auto', style: { display: 'block', marginBottom: '12px' } },
'发布到任务大厅(设计师自由抢单)'
),
h(Radio, { value: 'manual', style: { display: 'block' } },
'指定设计师接单'
)
]),
// 设计师列表(仅在指定接单时显示)
h('div', {
style: {
marginTop: '16px',
display: assignType === 'manual' ? 'block' : 'none'
}
}, [
h('p', { style: { marginBottom: '12px', color: '#666', fontSize: '13px' } },
'选择设计师(显示当前进行中的任务数量):'
),
h(RadioGroup, {
value: selectedDesignerId,
onChange: (e: any) => {
selectedDesignerId = e.target.value
},
style: { width: '100%' }
}, designers.map(designer =>
h(Radio, {
value: designer.id,
style: {
display: 'flex',
alignItems: 'center',
marginBottom: '8px',
padding: '8px',
border: '1px solid #f0f0f0',
borderRadius: '4px'
}
}, [
h('span', { style: { flex: 1 } }, designer.realName),
h('span', {
style: {
color: designer.inProgressCount > 5 ? '#ff4d4f' :
designer.inProgressCount > 3 ? '#faad14' : '#52c41a',
fontSize: '12px',
marginLeft: '12px'
}
}, `进行中: ${designer.inProgressCount} 个任务`)
])
))
])
])
Modal.confirm({
title: '接单部门审核通过',
content: modalContent,
width: 600,
okText: '确认通过',
cancelText: '取消',
onOk: async () => {
try {
approvalLoading.value = true
// 检查是否需要指定设计师
if (assignType === 'manual' && !selectedDesignerId) {
message.error('请选择要指定的设计师')
return Promise.reject()
}
const approvalData: TaskApprovalDTO = {
comment: assignType === 'manual' ?
`指定设计师接单` : '审核通过,发布到任务大厅',
assignedReceiverId: assignType === 'manual' ? selectedDesignerId : undefined
}
await taskApi.approveReceivingLevel(task.id!, approvalData)
if (assignType === 'manual') {
const designer = designers.find(d => d.id === selectedDesignerId)
message.success(`已指定设计师 ${designer?.realName} 接单`)
} else {
message.success('审核通过,任务已发布到大厅')
}
// 刷新列表和数量
loadPendingApprovalTasks()
loadAllTabCounts()
loadDashboardStats()
} catch (error: any) {
console.error('审核失败:', error)
message.error(error.msg || error.message || '审核失败')
return Promise.reject(error)
} finally {
approvalLoading.value = false
}
}
})
} catch (error: any) {
console.error('获取设计师列表失败:', error)
message.error('获取设计师列表失败')
}
}
// 两级审核:驳回任务