feat: 完善抢单权限控制、草稿状态编辑、群通知新模板等功能
- 后端: 子任务抢单权限校验(部门类型、任务类型限制) - 后端: 子任务组驳回联动主任务逻辑 - 后端: 群通知新模板样式 - 后端: 个人钉钉通知自动绑定 - 前端: 抢单按钮状态判断优化 - 前端: 草稿状态显示及编辑功能 - 前端: 驳回状态样式 - 前端: 指定设计师功能增强
This commit is contained in:
parent
64f3e25714
commit
17d3799870
@ -195,6 +195,26 @@ public class TaskItemController {
|
||||
return Result.success(assignedReceiverId != null ? "子任务组已指派" : "子任务组已发布到大厅");
|
||||
}
|
||||
|
||||
/**
|
||||
* 驳回子任务组
|
||||
*/
|
||||
@Operation(summary = "驳回子任务组")
|
||||
@PostMapping("/groups/reject")
|
||||
public Result<String> rejectTaskGroup(
|
||||
@RequestBody Map<String, Object> 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("子任务组已驳回");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取待审核的子任务组列表
|
||||
*/
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -74,5 +74,10 @@ public class PipelineTaskDTO {
|
||||
* 排序
|
||||
*/
|
||||
private Integer sortOrder;
|
||||
|
||||
/**
|
||||
* 指定接收人ID
|
||||
*/
|
||||
private Long receiverId;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<String, Object> request = new HashMap<>();
|
||||
request.put("mobile", mobile);
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
HttpEntity<Map<String, Object>> entity = new HttpEntity<>(request, headers);
|
||||
|
||||
ResponseEntity<Map> response = restTemplate.postForEntity(url, entity, Map.class);
|
||||
Map<String, Object> 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<String, Object> result = (Map<String, Object>) 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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送工作通知
|
||||
*/
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
// 构建钉钉消息内容
|
||||
|
||||
@ -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());
|
||||
|
||||
@ -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<TaskItem>()
|
||||
.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);
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -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<TaskGroup[]>('/tasks/items/pending-approval-groups')
|
||||
return request.get<TaskGroup[]>('/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<any>(`/tasks/items/groups/${groupId}/reject`, { reason })
|
||||
export const rejectTaskGroup = (taskId: number, groupNo: number, reason: string) => {
|
||||
return request.post<any>('/tasks/items/groups/reject', { taskId, groupNo, reason })
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -78,6 +78,27 @@
|
||||
@change="calculateScore"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-if="formData.releaseType === 'assigned'" class="task-type-receiver">
|
||||
<a-select
|
||||
v-model:value="item.receiverId"
|
||||
placeholder="指定设计师"
|
||||
style="width: 200px"
|
||||
show-search
|
||||
allow-clear
|
||||
size="large"
|
||||
:filter-option="filterOption"
|
||||
@focus="loadDesignersForItem(item)"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="designer in getDesignersForItem(item)"
|
||||
:key="designer.id"
|
||||
:value="designer.id"
|
||||
>
|
||||
{{ designer.realName }} (进行中: {{ designer.inProgressCount || 0 }})
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</div>
|
||||
<div class="task-type-actions">
|
||||
<a-button
|
||||
v-if="formData.taskTypes.length > 1"
|
||||
@ -293,8 +314,7 @@
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<!-- 招募任务和指定任务模式暂时隐藏 -->
|
||||
<!--
|
||||
<!-- 招募任务和指定任务模式 -->
|
||||
<button
|
||||
type="button"
|
||||
:class="['release-type-button', { active: formData.releaseType === 'recruitment' }]"
|
||||
@ -328,33 +348,9 @@
|
||||
<Icon name="checkCircle" size="sm" />
|
||||
</div>
|
||||
</button>
|
||||
-->
|
||||
</div>
|
||||
</a-form-item>
|
||||
|
||||
<!-- 指定接收人(仅当选择指定任务时显示) -->
|
||||
<a-form-item
|
||||
v-if="formData.releaseType === 'assigned'"
|
||||
label="指定接收人"
|
||||
name="receiverId"
|
||||
required
|
||||
>
|
||||
<a-select
|
||||
v-model:value="formData.receiverId"
|
||||
placeholder="请选择接收人"
|
||||
size="large"
|
||||
show-search
|
||||
:filter-option="filterOption"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="designer in designers"
|
||||
:key="designer.id"
|
||||
:value="designer.id"
|
||||
>
|
||||
{{ designer.name }} ({{ designer.deptName }})
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
|
||||
<!-- 是否紧急 -->
|
||||
<a-form-item label="优先级">
|
||||
@ -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<Record<number, any[]>>({})
|
||||
|
||||
// 表单数据
|
||||
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)
|
||||
|
||||
@ -1568,8 +1568,8 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 被驳回任务的操作按钮 -->
|
||||
<div v-if="task.status === 'rejected'" class="task-actions" style="margin-top: 16px; padding-top: 16px; border-top: 1px solid rgba(49, 240, 139, 0.2);">
|
||||
<!-- 被驳回或草稿任务的操作按钮 -->
|
||||
<div v-if="['rejected', 'draft'].includes(task.status)" class="task-actions" style="margin-top: 16px; padding-top: 16px; border-top: 1px solid rgba(49, 240, 139, 0.2);">
|
||||
<a-button
|
||||
type="primary"
|
||||
size="small"
|
||||
@ -2417,7 +2417,7 @@ const loadTasks = 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
|
||||
@ -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;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user