diff --git a/backend/src/main/java/com/designhub/service/DingTalkService.java b/backend/src/main/java/com/designhub/service/DingTalkService.java index 1e760a1..e4632ec 100644 --- a/backend/src/main/java/com/designhub/service/DingTalkService.java +++ b/backend/src/main/java/com/designhub/service/DingTalkService.java @@ -336,8 +336,8 @@ public class DingTalkService { } /** - * 发送机器人消息(卡片格式),支持@指定人(通过手机号) - * 使用整体跳转ActionCard样式,点击可跳转到官网 + * 发送机器人消息(Markdown格式),支持@指定人(通过手机号) + * 注意:群机器人只支持 text/link/markdown/feedCard 类型,不支持 action_card * @param title 消息标题 * @param content 消息内容(支持markdown格式) * @param atMobiles 要@的手机号列表,null或空表示不@任何人 @@ -363,17 +363,22 @@ public class DingTalkService { String siteUrl = getSiteUrl(); String jumpUrl = buildDingTalkAppLink(config, siteUrl); - // 构建卡片消息(ActionCard格式) + // 构建 Markdown 消息(群机器人支持的格式) Map message = new HashMap<>(); - message.put("msgtype", "action_card"); + message.put("msgtype", "markdown"); - Map actionCard = new HashMap<>(); - actionCard.put("title", title); + Map markdown = new HashMap<>(); + markdown.put("title", title); - // 构建 markdown 正文(卡片已有标题,内容中不再重复) + // 构建 markdown 正文 StringBuilder markdownBuilder = new StringBuilder(); + markdownBuilder.append("### ").append(title).append("\n\n"); markdownBuilder.append(content); + // 添加跳转链接 + markdownBuilder.append("\n\n---\n\n"); + markdownBuilder.append("[>>> 点击查看详情](").append(jumpUrl).append(")"); + // 如果有@人,在内容中添加@标记 if (atMobiles != null && !atMobiles.isEmpty()) { markdownBuilder.append("\n\n"); @@ -382,13 +387,11 @@ public class DingTalkService { } } - actionCard.put("markdown", markdownBuilder.toString()); - actionCard.put("single_title", "查看详情 »"); - actionCard.put("single_url", jumpUrl); + markdown.put("text", markdownBuilder.toString()); - message.put("action_card", actionCard); + message.put("markdown", markdown); - // 添加@人功能(对于action_card,@功能可能有限制) + // 添加@人功能 if (atMobiles != null && !atMobiles.isEmpty()) { Map at = new HashMap<>(); at.put("atMobiles", atMobiles); @@ -407,7 +410,7 @@ public class DingTalkService { if (body != null) { Object errcode = body.get("errcode"); if (errcode != null && (errcode.equals(0) || errcode.equals(0L) || "0".equals(errcode.toString()))) { - log.info("钉钉机器人卡片消息发送成功: title={}", title); + log.info("钉钉机器人Markdown消息发送成功: title={}", title); return true; } else { log.error("钉钉机器人消息发送失败: errcode={}, errmsg={}, body={}", diff --git a/backend/src/main/java/com/designhub/service/NotificationService.java b/backend/src/main/java/com/designhub/service/NotificationService.java index 6acb623..8e4ebd4 100644 --- a/backend/src/main/java/com/designhub/service/NotificationService.java +++ b/backend/src/main/java/com/designhub/service/NotificationService.java @@ -250,24 +250,23 @@ public class NotificationService { /** * 构建钉钉消息内容(Markdown格式) + * 注意:卡片消息的标题会单独显示,这里的内容不要再加标题 */ private String buildDingTalkContent(Notification notification) { StringBuilder content = new StringBuilder(); - content.append("### ").append(notification.getTitle()).append("\n\n"); - content.append(notification.getContent()).append("\n\n"); - // 根据通知类型添加不同的提示 - if ("task".equals(notification.getNotificationType())) { - content.append("> 📋 任务相关通知\n\n"); - } else if ("review".equals(notification.getNotificationType())) { - content.append("> ⭐ 评审相关通知\n\n"); - } else if ("score".equals(notification.getNotificationType())) { - content.append("> 💰 积分相关通知\n\n"); - } else if ("system".equals(notification.getNotificationType())) { - content.append("> 🔔 系统通知\n\n"); + // 如果内容已经是 Markdown 格式(包含 ** 或 ## 等),直接使用 + String originalContent = notification.getContent(); + if (originalContent != null && (originalContent.contains("**") || originalContent.contains("##"))) { + // 内容已经包含详细信息,直接使用 + content.append(originalContent); + } else { + // 简单内容,构建基础格式 + content.append(originalContent != null ? originalContent : ""); } - content.append("---\n\n"); + content.append(" \n \n"); + content.append("--- \n"); content.append("请登录系统查看详情"); return content.toString(); diff --git a/backend/src/main/java/com/designhub/service/TaskService.java b/backend/src/main/java/com/designhub/service/TaskService.java index d5b197b..bfda2db 100644 --- a/backend/src/main/java/com/designhub/service/TaskService.java +++ b/backend/src/main/java/com/designhub/service/TaskService.java @@ -2192,17 +2192,89 @@ public class TaskService { } /** - * 发送审核通知(两级审核流程) + * 发送审核通知(两级审核流程)- 包含丰富的任务详情 */ private void sendApprovalNotification(Task task) { try { if (task.getCurrentApproverId() != null) { User approver = userService.getUserById(task.getCurrentApproverId()); if (approver != null) { + // 构建丰富的通知内容 + StringBuilder contentBuilder = new StringBuilder(); + contentBuilder.append("**任务名称**:").append(task.getTitle()).append(" \n"); + contentBuilder.append("**任务编号**:").append(task.getTaskNo()).append(" \n"); + + // 品牌、平台、SPU信息 + if (task.getBrandId() != null) { + Brand brand = brandMapper.selectById(task.getBrandId()); + if (brand != null) { + contentBuilder.append("**品牌**:").append(brand.getName()).append(" \n"); + } + } + if (task.getPlatformId() != null) { + Platform platform = platformMapper.selectById(task.getPlatformId()); + if (platform != null) { + contentBuilder.append("**平台**:").append(platform.getName()).append(" \n"); + } + } + if (task.getSpuInfo() != null && !task.getSpuInfo().isEmpty()) { + contentBuilder.append("**SPU**:").append(task.getSpuInfo()).append(" \n"); + } + if (task.getTaskSubType() != null && !task.getTaskSubType().isEmpty()) { + contentBuilder.append("**任务类型**:").append(task.getTaskSubType()).append(" \n"); + } + + contentBuilder.append(" \n"); + + // 查询子任务获取任务类型明细 + List taskItems = taskItemMapper.selectList( + new LambdaQueryWrapper() + .eq(TaskItem::getTaskId, task.getId()) + .orderByAsc(TaskItem::getGroupNo) + ); + + if (!taskItems.isEmpty()) { + contentBuilder.append("**任务明细**: \n"); + for (TaskItem item : taskItems) { + // 获取接单部门名称 + String deptName = "未指定"; + if (item.getTaskTypeId() != null) { + DemandType demandType = configService.getDemandTypeById(item.getTaskTypeId()); + if (demandType != null && demandType.getAllowedDeptIds() != null) { + String[] deptIds = demandType.getAllowedDeptIds().split(","); + Long firstDeptId = Long.parseLong(deptIds[0].trim()); + Department dept = departmentService.getDepartmentById(firstDeptId); + if (dept != null) { + deptName = dept.getDeptName(); + } + } + } + contentBuilder.append("▸ ").append(deptName) + .append(" | ").append(item.getScore() != null ? item.getScore() : 0).append("分") + .append(" | ").append(formatHours(item.getEstimatedHours())).append(" \n"); + } + } + + contentBuilder.append(" \n"); + + // 发布人信息 + if (task.getPublisherId() != null) { + User publisher = userService.getUserById(task.getPublisherId()); + if (publisher != null) { + contentBuilder.append("**发起人**:").append(publisher.getRealName() != null ? publisher.getRealName() : publisher.getUsername()).append(" \n"); + } + } + if (task.getPublisherDeptId() != null) { + Department dept = departmentService.getDepartmentById(task.getPublisherDeptId()); + if (dept != null) { + contentBuilder.append("**发起部门**:").append(dept.getDeptName()).append(" \n"); + } + } + Notification notification = new Notification(); notification.setReceiverId(task.getCurrentApproverId()); notification.setTitle("新任务待审核"); - notification.setContent(String.format("任务【%s】需要您审核", task.getTitle())); + notification.setContent(contentBuilder.toString()); notification.setNotificationType("TASK_APPROVAL"); notification.setRelatedType("task"); notification.setRelatedId(task.getId()); @@ -2215,6 +2287,23 @@ public class TaskService { log.error("发送审核通知失败", e); } } + + /** + * 格式化工时(小时转天+小时,8小时=1天) + */ + private String formatHours(BigDecimal hours) { + if (hours == null) return "--"; + int totalHours = hours.intValue(); + int days = totalHours / 8; + int remainingHours = totalHours % 8; + if (days > 0 && remainingHours > 0) { + return days + "天" + remainingHours + "小时"; + } else if (days > 0) { + return days + "天"; + } else { + return totalHours + "小时"; + } + } /** * 计算任务总积分(用于审核时修改) diff --git a/frontend/src/views/TaskHall.vue b/frontend/src/views/TaskHall.vue index 86a6c4e..de970b6 100644 --- a/frontend/src/views/TaskHall.vue +++ b/frontend/src/views/TaskHall.vue @@ -2108,9 +2108,8 @@ {{ getTaskTypeName(selectedTask.taskTypeId) }} {{ selectedTask.score }} 分 - {{ selectedTask.estimatedHours || '--' }} 小时 + {{ formatEstimatedTime(selectedTask.estimatedHours) }} {{ selectedTask.quantity || '--' }} 个 - {{ formatDeadline(selectedTask.deadline) }}