From 17d379987036bb9035228bab0c3f0182915de629 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sat, 3 Jan 2026 18:31:49 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=8C=E5=96=84=E6=8A=A2=E5=8D=95?= =?UTF-8?q?=E6=9D=83=E9=99=90=E6=8E=A7=E5=88=B6=E3=80=81=E8=8D=89=E7=A8=BF?= =?UTF-8?q?=E7=8A=B6=E6=80=81=E7=BC=96=E8=BE=91=E3=80=81=E7=BE=A4=E9=80=9A?= =?UTF-8?q?=E7=9F=A5=E6=96=B0=E6=A8=A1=E6=9D=BF=E7=AD=89=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 后端: 子任务抢单权限校验(部门类型、任务类型限制) - 后端: 子任务组驳回联动主任务逻辑 - 后端: 群通知新模板样式 - 后端: 个人钉钉通知自动绑定 - 前端: 抢单按钮状态判断优化 - 前端: 草稿状态显示及编辑功能 - 前端: 驳回状态样式 - 前端: 指定设计师功能增强 --- .../controller/TaskItemController.java | 20 +++ .../designhub/controller/UserController.java | 12 +- .../com/designhub/dto/PipelineTaskDTO.java | 5 + .../designhub/service/DingTalkService.java | 40 ++++++ .../service/NotificationService.java | 21 ++- .../service/TaskApprovalService.java | 25 ++-- .../designhub/service/TaskItemService.java | 119 +++++++++++++++-- .../com/designhub/service/TaskService.java | 26 +++- frontend/src/api/task.ts | 7 +- frontend/src/components/NotificationBell.vue | 20 ++- frontend/src/views/CreateTask.vue | 120 ++++++++++++------ frontend/src/views/TaskHall.vue | 39 ++++-- 12 files changed, 359 insertions(+), 95 deletions(-) diff --git a/backend/src/main/java/com/designhub/controller/TaskItemController.java b/backend/src/main/java/com/designhub/controller/TaskItemController.java index a6cfe03..3a7214d 100644 --- a/backend/src/main/java/com/designhub/controller/TaskItemController.java +++ b/backend/src/main/java/com/designhub/controller/TaskItemController.java @@ -195,6 +195,26 @@ public class TaskItemController { return Result.success(assignedReceiverId != null ? "子任务组已指派" : "子任务组已发布到大厅"); } + /** + * 驳回子任务组 + */ + @Operation(summary = "驳回子任务组") + @PostMapping("/groups/reject") + public Result rejectTaskGroup( + @RequestBody Map request, + @RequestHeader("Authorization") String token) { + + String actualToken = token.replace("Bearer ", ""); + Long rejectorId = jwtUtil.getUserIdFromToken(actualToken); + + Long taskId = Long.valueOf(request.get("taskId").toString()); + Integer groupNo = Integer.valueOf(request.get("groupNo").toString()); + String reason = (String) request.get("reason"); + + taskItemService.rejectTaskGroup(taskId, groupNo, rejectorId, reason); + return Result.success("子任务组已驳回"); + } + /** * 获取待审核的子任务组列表 */ diff --git a/backend/src/main/java/com/designhub/controller/UserController.java b/backend/src/main/java/com/designhub/controller/UserController.java index b0d9810..90d8769 100644 --- a/backend/src/main/java/com/designhub/controller/UserController.java +++ b/backend/src/main/java/com/designhub/controller/UserController.java @@ -134,17 +134,13 @@ public class UserController { @RequestParam(required = false) Long deptId, @RequestHeader("Authorization") String authHeader ) { - // 如果没有传入deptId,则根据当前用户角色判断 + // 如果没有传入deptId,则默认为当前用户的部门 if (deptId == null) { String token = jwtUtil.extractTokenFromHeader(authHeader); Long userId = jwtUtil.getUserIdFromToken(token); - - // 如果不是管理员,强制使用自己的部门ID - if (!userService.isAdminUser(userId)) { - User currentUser = userService.getUserById(userId); - if (currentUser != null) { - deptId = currentUser.getDeptId(); - } + User currentUser = userService.getUserById(userId); + if (currentUser != null) { + deptId = currentUser.getDeptId(); } } diff --git a/backend/src/main/java/com/designhub/dto/PipelineTaskDTO.java b/backend/src/main/java/com/designhub/dto/PipelineTaskDTO.java index b7ecb71..5382548 100644 --- a/backend/src/main/java/com/designhub/dto/PipelineTaskDTO.java +++ b/backend/src/main/java/com/designhub/dto/PipelineTaskDTO.java @@ -74,5 +74,10 @@ public class PipelineTaskDTO { * 排序 */ private Integer sortOrder; + + /** + * 指定接收人ID + */ + private Long receiverId; } } diff --git a/backend/src/main/java/com/designhub/service/DingTalkService.java b/backend/src/main/java/com/designhub/service/DingTalkService.java index 4a69037..171cb1c 100644 --- a/backend/src/main/java/com/designhub/service/DingTalkService.java +++ b/backend/src/main/java/com/designhub/service/DingTalkService.java @@ -154,6 +154,46 @@ public class DingTalkService { } } + /** + * 根据手机号获取userId + */ + public String getUserIdByMobile(String mobile) { + DingTalkConfig config = getConfig(); + if (config == null) return null; + + String accessToken = getAccessToken(config); + + try { + String url = String.format("%s/topapi/v2/user/getbymobile?access_token=%s", + DINGTALK_API_BASE, accessToken); + + Map request = new HashMap<>(); + request.put("mobile", mobile); + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + HttpEntity> entity = new HttpEntity<>(request, headers); + + ResponseEntity response = restTemplate.postForEntity(url, entity, Map.class); + Map body = response.getBody(); + + if (body != null) { + Object errcode = body.get("errcode"); + if (errcode != null && (errcode.equals(0) || errcode.equals(0L) || "0".equals(errcode.toString()))) { + Map result = (Map) body.get("result"); + return (String) result.get("userid"); + } else { + log.warn("根据手机号获取userId失败: mobile={}, errcode={}, errmsg={}", + mobile, errcode, body.get("errmsg")); + } + } + return null; + } catch (Exception e) { + log.error("根据手机号获取userId异常", e); + return null; + } + } + /** * 发送工作通知 */ diff --git a/backend/src/main/java/com/designhub/service/NotificationService.java b/backend/src/main/java/com/designhub/service/NotificationService.java index 1473612..e842e44 100644 --- a/backend/src/main/java/com/designhub/service/NotificationService.java +++ b/backend/src/main/java/com/designhub/service/NotificationService.java @@ -187,9 +187,24 @@ public class NotificationService { // 检查用户是否绑定了钉钉ID if (receiver.getDingUserId() == null || receiver.getDingUserId().isEmpty()) { - log.info("用户未绑定钉钉ID,跳过推送: userId={}, username={}", - receiver.getId(), receiver.getUsername()); - return; + // 尝试通过手机号自动获取并绑定 + if (org.springframework.util.StringUtils.hasText(receiver.getPhone())) { + String dingUserId = dingTalkService.getUserIdByMobile(receiver.getPhone()); + if (dingUserId != null) { + receiver.setDingUserId(dingUserId); + userMapper.updateById(receiver); + log.info("通过手机号自动绑定钉钉ID: userId={}, phone={}, dingUserId={}", + receiver.getId(), receiver.getPhone(), dingUserId); + } else { + log.info("用户未绑定钉钉ID且无法通过手机号获取,跳过推送: userId={}, phone={}", + receiver.getId(), receiver.getPhone()); + return; + } + } else { + log.info("用户未绑定钉钉ID且无手机号,跳过推送: userId={}, username={}", + receiver.getId(), receiver.getUsername()); + return; + } } // 构建钉钉消息内容 diff --git a/backend/src/main/java/com/designhub/service/TaskApprovalService.java b/backend/src/main/java/com/designhub/service/TaskApprovalService.java index edcd1be..be9740c 100644 --- a/backend/src/main/java/com/designhub/service/TaskApprovalService.java +++ b/backend/src/main/java/com/designhub/service/TaskApprovalService.java @@ -275,25 +275,26 @@ public class TaskApprovalService { log.info("任务【{}】已发布到任务大厅,准备发送钉钉群通知", task.getTitle()); // 任务发布到大厅,发送钉钉群机器人通知 try { - String title = "🎯 新任务发布"; + String title = "新任务发布"; StringBuilder content = new StringBuilder(); - content.append("**任务标题:**").append(task.getTitle()).append("\n\n"); - content.append("**任务编号:**").append(task.getTaskNo()).append("\n\n"); - content.append("**积分:**").append(task.getScore()).append(" 分\n\n"); + content.append("--------------------------------\n"); + content.append(" 🎯 新 任 务 发 布 \n"); + content.append("--------------------------------\n\n"); + content.append("任务:").append(task.getTitle()).append("\n"); + content.append("编号:").append(task.getTaskNo()).append("\n"); + content.append("积分:").append(task.getScore()).append(" 分\n"); if (task.getBrandName() != null) { - content.append("**品牌:**").append(task.getBrandName()).append("\n\n"); + content.append("品牌:").append(task.getBrandName()).append("\n"); } if (task.getCategoryName() != null) { - content.append("**类目:**").append(task.getCategoryName()).append("\n\n"); - } - if (task.getPlatformName() != null) { - content.append("**平台:**").append(task.getPlatformName()).append("\n\n"); + content.append("类目:").append(task.getCategoryName()).append("\n"); } - content.append("**状态:**待接单\n\n"); - content.append("---\n\n"); - content.append("💡 赶快去任务大厅抢单吧!"); + content.append("状态:待接单\n\n"); + content.append("--------------------------------\n"); + content.append(" 💡 请前往任务大厅抢单 \n"); + content.append("--------------------------------"); log.info("准备调用钉钉机器人发送消息,title={}", title); boolean success = dingTalkService.sendRobotMessage(title, content.toString()); diff --git a/backend/src/main/java/com/designhub/service/TaskItemService.java b/backend/src/main/java/com/designhub/service/TaskItemService.java index cdee138..902bab3 100644 --- a/backend/src/main/java/com/designhub/service/TaskItemService.java +++ b/backend/src/main/java/com/designhub/service/TaskItemService.java @@ -34,6 +34,7 @@ public class TaskItemService { private final UserService userService; private final NotificationService notificationService; private final DingTalkService dingTalkService; + private final ConfigService configService; /** * 获取可接单的子任务列表 @@ -111,6 +112,50 @@ public class TaskItemService { throw new BusinessException(ResultCode.NOT_FOUND, "子任务不存在"); } + // 验证用户部门权限 + if (user.getDeptId() == null) { + throw new BusinessException(ResultCode.FORBIDDEN, "您未分配部门,无法接单"); + } + + com.designhub.entity.Department department = departmentMapper.selectById(user.getDeptId()); + if (department == null) { + throw new BusinessException(ResultCode.FORBIDDEN, "未找到用户所属部门,请联系管理员"); + } + + // 检查1:需求部门不能抢单 + if ("demand".equals(department.getDeptType())) { + throw new BusinessException(ResultCode.FORBIDDEN, "需求部门成员不能接单,只能发起需求"); + } + + // 检查2:接单部门类型验证 + if (!"receiving".equals(department.getDeptType())) { + throw new BusinessException(ResultCode.FORBIDDEN, "只有接单部门成员可以接收任务"); + } + + // 检查3:部门负责人不能抢单 + if (department.getLeaderId() != null && department.getLeaderId().equals(userId)) { + throw new BusinessException(ResultCode.FORBIDDEN, "部门负责人不能接单,请分配给设计师"); + } + + // 检查4:验证部门是否允许接该任务类型 + if (item.getTaskTypeId() != null) { + com.designhub.entity.DemandType demandType = configService.getDemandTypeById(item.getTaskTypeId()); + if (demandType != null && demandType.getAllowedDeptIds() != null && !demandType.getAllowedDeptIds().isEmpty()) { + // 检查用户部门是否在允许列表中 + String[] allowedIds = demandType.getAllowedDeptIds().split(","); + boolean isAllowed = false; + for (String allowedId : allowedIds) { + if (allowedId.trim().equals(String.valueOf(user.getDeptId()))) { + isAllowed = true; + break; + } + } + if (!isAllowed) { + throw new BusinessException(ResultCode.FORBIDDEN, "您的部门无权接收此类型的任务"); + } + } + } + // 验证状态 if (!"pending".equals(item.getStatus())) { throw new BusinessException(ResultCode.TASK_STATUS_ERROR, "该子任务不可接单"); @@ -471,20 +516,23 @@ public class TaskItemService { try { String title = "🎯 新子任务组发布"; StringBuilder content = new StringBuilder(); - content.append("**主任务:**").append(mainTask.getTitle()).append("\n\n"); - content.append("**子任务组:**第 ").append(groupNo).append(" 组\n\n"); - content.append("**子任务数量:**").append(groupItems.size()).append(" 个\n\n"); + content.append("--------------------------------\n"); + content.append(" 🎯 新 子 任 务 组 \n"); + content.append("--------------------------------\n\n"); + + content.append("主任务:").append(mainTask.getTitle()).append("\n"); + content.append("任务组:第 ").append(groupNo).append(" 组\n"); + content.append("数量:").append(groupItems.size()).append(" 个\n"); // 列出子任务类型 - content.append("**任务类型:**\n\n"); + content.append("类型:\n"); for (TaskItem item : groupItems) { content.append("- ").append(item.getItemNo()) - .append("(积分:").append(item.getTotalScore()).append(")\n\n"); + .append(" (").append(item.getTotalScore()).append("分)\n"); } - content.append("**状态:**待接单\n\n"); - content.append("---\n\n"); - content.append("💡 赶快去任务大厅抢单吧!"); + content.append("状态:待接单\n"); + content.append("\n💡 赶快去任务大厅抢单吧!"); dingTalkService.sendRobotMessage(title, content.toString()); log.info("第 {} 组 {} 个子任务已发布到任务大厅,已发送钉钉群通知", groupNo, groupItems.size()); @@ -495,6 +543,60 @@ public class TaskItemService { } } + /** + * 驳回子任务组(接单部门负责人审核不通过) + * 逻辑:驳回该组所有子任务,并将主任务及其所有子任务设为驳回状态 + */ + @Transactional(rollbackFor = Exception.class) + public void rejectTaskGroup(Long taskId, Integer groupNo, Long rejectorId, String reason) { + log.info("接单部门负责人 {} 驳回任务 {} 的第 {} 组子任务,原因:{}", rejectorId, taskId, groupNo, reason); + + // 1. 验证主任务 + Task mainTask = taskMapper.selectById(taskId); + if (mainTask == null) { + throw new BusinessException(ResultCode.NOT_FOUND, "主任务不存在"); + } + + // 2. 验证审核人权限(必须是接单部门负责人) + if (mainTask.getReceiverDeptId() == null) { + throw new BusinessException(ResultCode.FORBIDDEN, "任务未指定接单部门"); + } + com.designhub.entity.Department receiverDept = departmentMapper.selectById(mainTask.getReceiverDeptId()); + if (receiverDept == null || !rejectorId.equals(receiverDept.getLeaderId())) { + throw new BusinessException(ResultCode.FORBIDDEN, "只有接单部门负责人可以驳回子任务组"); + } + + // 3. 更新主任务状态为 rejected + mainTask.setStatus("rejected"); + mainTask.setRejectReason(reason); + mainTask.setUpdateTime(LocalDateTime.now()); + taskMapper.updateById(mainTask); + + // 4. 更新该任务下所有子任务状态为 rejected + taskItemMapper.update(null, + new com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper() + .eq(TaskItem::getTaskId, taskId) + .set(TaskItem::getStatus, "rejected") + .set(TaskItem::getUpdateTime, LocalDateTime.now()) + ); + + log.info("任务 {} 及其所有子任务已被驳回", taskId); + + // 5. 发送通知给任务发布者 + if (mainTask.getPublisherId() != null) { + Notification notification = new Notification(); + notification.setReceiverId(mainTask.getPublisherId()); + notification.setNotificationType("task_rejected"); + notification.setTitle("任务被驳回"); + notification.setContent("您的任务《" + mainTask.getTitle() + "》在第 " + groupNo + " 组子任务审核时被驳回。原因:" + reason + "。请重新编辑并提交。"); + notification.setRelatedId(taskId); + notification.setRelatedType("task"); + notification.setPriority(2); // 重要 + notification.setPushDingtalk(1); // 推送到钉钉 + notificationService.sendNotification(notification); + } + } + /** * 获取待审核的子任务组列表(按主任务分组) * @param receiverDeptId 接单部门ID @@ -518,6 +620,7 @@ public class TaskItemService { groupInfo.put("taskTitle", mainTask.getTitle()); groupInfo.put("taskNo", mainTask.getTaskNo()); groupInfo.put("groupNo", item.getGroupNo()); + groupInfo.put("groupId", item.getGroupNo()); // 前端使用groupId作为唯一标识 groupInfo.put("itemCount", 1); groupInfo.put("createTime", item.getCreateTime()); groupMap.put(key, groupInfo); diff --git a/backend/src/main/java/com/designhub/service/TaskService.java b/backend/src/main/java/com/designhub/service/TaskService.java index 10bbcdc..a960a9c 100644 --- a/backend/src/main/java/com/designhub/service/TaskService.java +++ b/backend/src/main/java/com/designhub/service/TaskService.java @@ -382,11 +382,25 @@ public class TaskService { item.setSortOrder(i + 1); // 恢复真实的排序号 item.setProgress(0); + // 设置子任务指定的接收人 + if (itemDto.getReceiverId() != null) { + item.setReceiverId(itemDto.getReceiverId()); + // 获取接收人名字 + User receiver = userService.getUserById(itemDto.getReceiverId()); + if (receiver != null) { + item.setReceiverName(receiver.getRealName()); + } + } + // 关键:根据流水线模式和分组设置初始状态 if ("sequential".equals(dto.getPipelineMode())) { if (itemGroupNo == 1) { - // 顺序流水线:第一组的所有子任务都是 pending(可接单) - item.setStatus("pending"); + // 顺序流水线:第一组 + if (item.getReceiverId() != null) { + item.setStatus("in_progress"); // 指定了人,直接进行中 + } else { + item.setStatus("pending"); // 没指定人,待接单 + } item.setIsLocked(0); item.setPrevItemId(null); } else { @@ -395,8 +409,12 @@ public class TaskService { item.setIsLocked(1); } } else { - // 并行流水线:所有子任务都是 pending(可接单) - item.setStatus("pending"); + // 并行流水线 + if (item.getReceiverId() != null) { + item.setStatus("in_progress"); + } else { + item.setStatus("pending"); + } item.setIsLocked(0); } diff --git a/frontend/src/api/task.ts b/frontend/src/api/task.ts index c641613..7771ff1 100644 --- a/frontend/src/api/task.ts +++ b/frontend/src/api/task.ts @@ -112,6 +112,7 @@ export interface PipelineTaskRequest { taskTypeId: number quantity: number sortOrder: number + receiverId?: number // 指定接收人 }> brandId?: number categoryId?: number @@ -561,7 +562,7 @@ export interface TaskGroup { * 获取待审核的子任务组列表 */ export const getPendingApprovalTaskGroups = () => { - return request.get('/tasks/items/pending-approval-groups') + return request.get('/tasks/items/groups/pending-approval') } /** @@ -574,7 +575,7 @@ export const approveTaskGroup = (groupId: number, data: { assignedReceiverId?: n /** * 驳回子任务组 */ -export const rejectTaskGroup = (groupId: number, reason: string) => { - return request.post(`/tasks/items/groups/${groupId}/reject`, { reason }) +export const rejectTaskGroup = (taskId: number, groupNo: number, reason: string) => { + return request.post('/tasks/items/groups/reject', { taskId, groupNo, reason }) } diff --git a/frontend/src/components/NotificationBell.vue b/frontend/src/components/NotificationBell.vue index 46af165..1039cde 100644 --- a/frontend/src/components/NotificationBell.vue +++ b/frontend/src/components/NotificationBell.vue @@ -261,25 +261,31 @@ onUnmounted(() => { display: flex; align-items: center; justify-content: center; - color: rgba(255, 255, 255, 0.75); + color: #ffffff !important; /* 强制白色 */ cursor: pointer; position: relative; transition: all $transition-base $ease-out; &:hover { - background: $bg-card; - color: rgba(255, 255, 255, 0.95); + background: rgba(255, 255, 255, 0.1); + color: #ffffff !important; } // 确保Icon组件继承颜色 :deep(.app-icon) { - color: inherit; + color: #ffffff !important; /* 强制白色 */ } - // 确保SVG元素继承颜色 + // 强制覆盖SVG元素及其路径的颜色 :deep(svg) { - color: inherit; - stroke: currentColor; + color: #ffffff !important; + stroke: #ffffff !important; + fill: none !important; + } + + :deep(path) { + stroke: #ffffff !important; + fill: none !important; } } diff --git a/frontend/src/views/CreateTask.vue b/frontend/src/views/CreateTask.vue index 132ff3e..4952d76 100644 --- a/frontend/src/views/CreateTask.vue +++ b/frontend/src/views/CreateTask.vue @@ -78,6 +78,27 @@ @change="calculateScore" /> + +
+ + + {{ designer.realName }} (进行中: {{ designer.inProgressCount || 0 }}) + + +
- -
- --> - - - - - {{ designer.name }} ({{ designer.deptName }}) - - - @@ -396,6 +392,7 @@ import { message } from 'ant-design-vue' import type { UploadFile, UploadProps } from 'ant-design-vue' import DefaultLayout from '@/layouts/DefaultLayout.vue' import Icon from '@/components/Icon.vue' +import * as userApi from '@/api/user' import * as taskApi from '@/api/task' import * as configApi from '@/api/config' import * as uploadApi from '@/api/upload' @@ -432,14 +429,18 @@ const uploadProgress = ref(0) interface TaskTypeItem { taskTypeId: number | undefined quantity: number + receiverId?: number // 指定接收人ID } +// 设计师列表缓存 { deptId: designers[] } +const designersCache = reactive>({}) + // 表单数据 const formData = reactive({ title: '', isPipeline: true, // 固定为流水线任务 pipelineMode: 'sequential' as 'sequential' | 'parallel', // 固定为顺序流水线 - taskTypes: [{ taskTypeId: undefined, quantity: 1 }] as TaskTypeItem[], // 任务类型数组,每个都有独立的数量 + taskTypes: [{ taskTypeId: undefined, quantity: 1, receiverId: undefined }] as TaskTypeItem[], // 任务类型数组 taskSubType: '', // 动态加载,初始为空 brandId: undefined, platformId: undefined, @@ -447,7 +448,7 @@ const formData = reactive({ spuInfo: '', attachmentUrl: '', releaseType: 'default', - receiverId: undefined, + // receiverId: undefined, // 已移除,改用子任务指定 isUrgent: false }) @@ -492,18 +493,51 @@ const brands = computed(() => configStore.brands) const platforms = computed(() => configStore.platforms) const categories = computed(() => configStore.categories) -// 设计师列表(TODO: 从用户API加载) -const designers = ref([ - { id: 1, name: '张设计', deptName: '设计部' }, - { id: 2, name: '李设计', deptName: '设计部' }, - { id: 3, name: '王设计', deptName: '设计部' } -]) +// 加载设计师列表 +const loadDesignersForItem = async (item: TaskTypeItem) => { + if (!item.taskTypeId) return + + const typeInfo = taskTypes.value.find(t => t.id === item.taskTypeId) + if (!typeInfo || !typeInfo.allowedDeptIds) return + + // 简化处理:取第一个允许的部门ID + const deptIds = typeInfo.allowedDeptIds.split(',') + if (deptIds.length === 0) return + + const deptId = Number(deptIds[0]) + + if (!designersCache[deptId]) { + try { + const res = await userApi.getDesignersWithWorkload(deptId) + if (res.code === 200) { + designersCache[deptId] = res.data + } + } catch (error) { + console.error('加载设计师失败:', error) + } + } +} + +// 获取当前任务类型对应的设计师列表 +const getDesignersForItem = (item: TaskTypeItem) => { + if (!item.taskTypeId) return [] + + const typeInfo = taskTypes.value.find(t => t.id === item.taskTypeId) + if (!typeInfo || !typeInfo.allowedDeptIds) return [] + + const deptIds = typeInfo.allowedDeptIds.split(',') + if (deptIds.length === 0) return [] + + const deptId = Number(deptIds[0]) + return designersCache[deptId] || [] +} // 添加任务类型 const addTaskType = () => { formData.taskTypes.push({ taskTypeId: undefined, - quantity: 1 + quantity: 1, + receiverId: undefined }) } @@ -668,9 +702,12 @@ const handleSubmit = async () => { } // 检查指定接收人 - if (formData.releaseType === 'assigned' && !formData.receiverId) { - message.error('请选择接收人') - return + if (formData.releaseType === 'assigned') { + const missingReceiver = validTaskTypes.some(item => !item.receiverId) + if (missingReceiver) { + message.error('指定模式下,所有任务类型都必须选择接收人') + return + } } submitting.value = true @@ -727,9 +764,10 @@ const handleSubmit = async () => { isPipeline: true, pipelineMode: 'sequential', // 固定为顺序流水线 items: validTaskTypes.map((item, index) => ({ - taskTypeId: item.taskTypeId, + taskTypeId: item.taskTypeId!, quantity: item.quantity, - sortOrder: index + 1 + sortOrder: index + 1, + receiverId: formData.releaseType === 'assigned' ? item.receiverId : undefined })), brandId: formData.brandId, platformId: formData.platformId, @@ -740,7 +778,7 @@ const handleSubmit = async () => { remark: '', priority: formData.isUrgent ? 3 : 1, // 0-低 1-普通 2-高 3-紧急 releaseType: formData.releaseType, // 发布类型 - receiverId: formData.releaseType === 'assigned' ? formData.receiverId : undefined // assigned模式时传递接收人ID + receiverId: undefined // 主任务不设接收人,由子任务指定 } const response = await taskApi.createPipelineTask(pipelineData) diff --git a/frontend/src/views/TaskHall.vue b/frontend/src/views/TaskHall.vue index be9a935..3fa009d 100644 --- a/frontend/src/views/TaskHall.vue +++ b/frontend/src/views/TaskHall.vue @@ -1568,8 +1568,8 @@ - -
+ +
{ ...item, id: item.id, // 保持子任务的ID itemId: item.id, // 额外保存子任务ID - title: item.taskTitle || item.title, // 使用主任务标题 + title: item.mainTaskTitle || item.taskTitle || item.title, // 使用主任务标题 subTaskNo: item.itemNo, // 子任务编号 isSubTask: true, // 标记为子任务 taskId: item.taskId, // 保存主任务ID @@ -2998,7 +2998,7 @@ const handleRejectTaskGroup = async (item: ApprovalItem) => { try { approvalLoading.value = true - await taskApi.rejectTaskGroup(item.id, rejectReason.trim()) + await taskApi.rejectTaskGroup(item.data.taskId, item.data.groupNo, rejectReason.trim()) message.success('已驳回子任务组') // 刷新列表和数量 @@ -3176,7 +3176,7 @@ const loadInProgressTasks = async () => { ...item, id: item.id, // 保持子任务的ID itemId: item.id, // 额外保存子任务ID - title: item.taskTitle || item.title, // 使用主任务标题 + title: item.mainTaskTitle || item.taskTitle || item.title, // 使用主任务标题 subTaskNo: item.itemNo, // 子任务编号 isSubTask: true, // 标记为子任务 taskId: item.taskId, // 保存主任务ID @@ -3580,9 +3580,17 @@ const canGrabTask = (task: TaskOrSubTask) => { } // 检查4:验证部门是否允许接该需求类型(如果任务有需求类型限制) - if (task.demandTypeId) { - // 这里需要检查任务的需求类型是否允许当前用户的部门接单 - // 由于前端没有完整的需求类型配置,暂时信任后端验证 + const taskTypeId = task.isSubTask ? task.taskTypeId : (task as any).taskTypeId + if (taskTypeId) { + // 从配置store中获取任务类型详情 + const demandType = configStore.demandTypes.find(t => t.id === taskTypeId) + if (demandType && demandType.allowedDeptIds) { + const allowedIds = demandType.allowedDeptIds.split(',') + // 检查当前用户部门ID是否在允许列表中 + if (userInfo.deptId && !allowedIds.includes(String(userInfo.deptId))) { + return { canGrab: false, reason: '您的部门无权接收此类型的任务' } + } + } } return { canGrab: true, reason: '' } @@ -3652,7 +3660,8 @@ const getTaskStatusText = (status: string) => { 'review': '待审核', 'completed': '已完成', 'rejected': '已驳回', - 'cancelled': '已取消' + 'cancelled': '已取消', + 'draft': '草稿' } return statusMap[status] || status } @@ -4413,6 +4422,18 @@ onUnmounted(() => { } // badge样式补充 - 统一使用绿色主题 +.status-draft { + background: rgba(250, 173, 20, 0.1); + color: #faad14; + border: 1px solid rgba(250, 173, 20, 0.2); +} + +.status-rejected { + background: rgba(245, 34, 45, 0.1); + color: #f5222d; + border: 1px solid rgba(245, 34, 45, 0.2); +} + .badge-in-progress { background: rgba(49, 240, 139, 0.1); color: #31f08b;