feat: 任务完结状态优化 + 商品ID确认功能 + 子任务交付路径汇总
1. 需求方完结任务显示待填写ID状态,填完商品ID后显示已完结 2. 添加确认商品ID填写完成功能 3. 母卡片展示所有子任务的交付路径汇总 4. 流水线任务:子任务按顺序完成,自动发起下一个子任务审核 5. 母任务编号作为子任务编号前缀 6. 所有子任务完成后母任务才能完结
This commit is contained in:
parent
344d26589c
commit
96da6c6673
@ -98,5 +98,16 @@ public class TaskProductController {
|
||||
long count = taskProductService.countByTaskId(taskId);
|
||||
return Result.success(count);
|
||||
}
|
||||
|
||||
@Operation(summary = "确认商品ID填写完成")
|
||||
@PostMapping("/confirm")
|
||||
public Result<Void> confirmProductIdFilled(
|
||||
@PathVariable Long taskId,
|
||||
@RequestHeader(value = "Authorization", required = false) String authHeader) {
|
||||
String token = jwtUtil.extractTokenFromHeader(authHeader);
|
||||
Long userId = jwtUtil.getUserIdFromToken(token);
|
||||
taskProductService.confirmProductIdFilled(taskId, userId);
|
||||
return Result.success();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -230,6 +230,12 @@ public class Task implements Serializable {
|
||||
*/
|
||||
private LocalDateTime rejectDeadline;
|
||||
|
||||
/**
|
||||
* 商品ID是否已填写确认:0-未填写 1-已填写
|
||||
* 只有当此字段为1时,任务才真正"完结"
|
||||
*/
|
||||
private Integer productIdFilled;
|
||||
|
||||
/**
|
||||
* 是否紧急:0-否 1-是
|
||||
*/
|
||||
@ -358,6 +364,12 @@ public class Task implements Serializable {
|
||||
@TableField(exist = false)
|
||||
private String currentApproverName;
|
||||
|
||||
/**
|
||||
* 所有子任务的交付路径汇总(非数据库字段,流水线任务专用)
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
private java.util.List<String> allDeliveryPaths;
|
||||
|
||||
// ==================== Getters and Setters (手动添加以解决Lombok编译问题) ====================
|
||||
|
||||
public Long getId() { return id; }
|
||||
@ -489,6 +501,9 @@ public class Task implements Serializable {
|
||||
public LocalDateTime getRejectDeadline() { return rejectDeadline; }
|
||||
public void setRejectDeadline(LocalDateTime rejectDeadline) { this.rejectDeadline = rejectDeadline; }
|
||||
|
||||
public Integer getProductIdFilled() { return productIdFilled; }
|
||||
public void setProductIdFilled(Integer productIdFilled) { this.productIdFilled = productIdFilled; }
|
||||
|
||||
public Integer getIsUrgent() { return isUrgent; }
|
||||
public void setIsUrgent(Integer isUrgent) { this.isUrgent = isUrgent; }
|
||||
|
||||
@ -566,5 +581,8 @@ public class Task implements Serializable {
|
||||
|
||||
public java.util.List<TaskItem> getSubTasks() { return subTasks; }
|
||||
public void setSubTasks(java.util.List<TaskItem> subTasks) { this.subTasks = subTasks; }
|
||||
|
||||
public java.util.List<String> getAllDeliveryPaths() { return allDeliveryPaths; }
|
||||
public void setAllDeliveryPaths(java.util.List<String> allDeliveryPaths) { this.allDeliveryPaths = allDeliveryPaths; }
|
||||
}
|
||||
|
||||
|
||||
@ -77,6 +77,9 @@ public class TaskProductService {
|
||||
taskProductMapper.insert(taskProduct);
|
||||
log.info("添加任务商品ID成功: taskId={}, productId={}, userId={}", taskId, productId, userId);
|
||||
|
||||
// 更新任务的商品ID填写状态
|
||||
updateTaskProductIdFilledStatus(taskId);
|
||||
|
||||
return taskProduct;
|
||||
}
|
||||
|
||||
@ -121,6 +124,9 @@ public class TaskProductService {
|
||||
|
||||
taskProductMapper.deleteById(id);
|
||||
log.info("删除任务商品ID成功: id={}, taskId={}, productId={}", id, product.getTaskId(), product.getProductId());
|
||||
|
||||
// 更新任务的商品ID填写状态
|
||||
updateTaskProductIdFilledStatus(product.getTaskId());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -166,5 +172,41 @@ public class TaskProductService {
|
||||
wrapper.eq(TaskProduct::getTaskId, taskId);
|
||||
return taskProductMapper.selectCount(wrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新任务的商品ID填写状态
|
||||
*/
|
||||
private void updateTaskProductIdFilledStatus(Long taskId) {
|
||||
long count = countByTaskId(taskId);
|
||||
Task task = taskMapper.selectById(taskId);
|
||||
if (task != null) {
|
||||
// 如果有商品ID记录,标记为已填写
|
||||
task.setProductIdFilled(count > 0 ? 1 : 0);
|
||||
taskMapper.updateById(task);
|
||||
log.info("更新任务商品ID填写状态: taskId={}, productIdFilled={}", taskId, count > 0 ? 1 : 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 确认商品ID填写完成(手动确认)
|
||||
*/
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void confirmProductIdFilled(Long taskId, Long userId) {
|
||||
Task task = taskMapper.selectById(taskId);
|
||||
if (task == null) {
|
||||
throw new BusinessException(ResultCode.NOT_FOUND, "任务不存在");
|
||||
}
|
||||
|
||||
// 检查是否有商品ID
|
||||
long count = countByTaskId(taskId);
|
||||
if (count == 0) {
|
||||
throw new BusinessException(ResultCode.PARAM_ERROR, "请先填写商品ID后再确认");
|
||||
}
|
||||
|
||||
// 更新状态
|
||||
task.setProductIdFilled(1);
|
||||
taskMapper.updateById(task);
|
||||
log.info("确认商品ID填写完成: taskId={}, userId={}", taskId, userId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -727,6 +727,15 @@ public class TaskService {
|
||||
.orderByAsc(TaskItem::getSortOrder)
|
||||
);
|
||||
|
||||
// 汇总所有子任务的交付路径
|
||||
if (subTasks != null && !subTasks.isEmpty()) {
|
||||
List<String> allDeliveryPaths = subTasks.stream()
|
||||
.filter(item -> item.getDeliveryPath() != null && !item.getDeliveryPath().isEmpty())
|
||||
.map(item -> "【" + item.getItemNo() + "】" + item.getDeliveryPath())
|
||||
.collect(Collectors.toList());
|
||||
task.setAllDeliveryPaths(allDeliveryPaths);
|
||||
}
|
||||
|
||||
if (subTasks != null && !subTasks.isEmpty()) {
|
||||
task.setSubTaskTotal(subTasks.size());
|
||||
|
||||
|
||||
9
database/add_product_id_filled.sql
Normal file
9
database/add_product_id_filled.sql
Normal file
@ -0,0 +1,9 @@
|
||||
-- 添加商品ID填写确认字段
|
||||
ALTER TABLE task ADD COLUMN product_id_filled TINYINT(1) DEFAULT 0 COMMENT '商品ID是否已填写确认:0-未填写 1-已填写' AFTER reject_deadline;
|
||||
|
||||
-- 更新现有已完成任务:如果已有商品ID记录则标记为已填写
|
||||
UPDATE task t
|
||||
SET t.product_id_filled = 1
|
||||
WHERE t.status = 'completed'
|
||||
AND EXISTS (SELECT 1 FROM task_product tp WHERE tp.task_id = t.id);
|
||||
|
||||
@ -64,6 +64,12 @@ export interface Task {
|
||||
subTasks?: TaskItem[]
|
||||
// 标记字段(前端使用)
|
||||
isSubTask?: boolean
|
||||
// 商品ID填写状态
|
||||
productIdFilled?: number // 0-未填写 1-已填写
|
||||
// 所有子任务交付路径汇总
|
||||
allDeliveryPaths?: string[]
|
||||
// 预览图
|
||||
previewImage?: string
|
||||
}
|
||||
|
||||
// 任务子项(流水线任务)
|
||||
@ -637,3 +643,10 @@ export const getTaskProductCount = (taskId: number) => {
|
||||
return request.get<number>(`/tasks/${taskId}/products/count`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 确认商品ID填写完成
|
||||
*/
|
||||
export const confirmTaskProductFilled = (taskId: number) => {
|
||||
return request.post<void>(`/tasks/${taskId}/products/confirm`)
|
||||
}
|
||||
|
||||
|
||||
@ -2133,6 +2133,42 @@
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 确认完结按钮:只有添加了商品ID且未确认时显示 -->
|
||||
<div v-if="canManageProducts(selectedTask) && taskProducts.length > 0 && !selectedTask.productIdFilled" class="confirm-complete-section">
|
||||
<a-alert
|
||||
message="请确认商品ID填写完成"
|
||||
description="确认后任务状态将从"待填写ID"变更为"已完结""
|
||||
type="warning"
|
||||
show-icon
|
||||
style="margin-bottom: 12px;"
|
||||
/>
|
||||
<a-button
|
||||
type="primary"
|
||||
block
|
||||
@click="handleConfirmProductIdFilled"
|
||||
:loading="confirmingProductId">
|
||||
<Icon name="checkCircle" size="sm" style="margin-right: 6px;" />
|
||||
确认完结任务
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 流水线任务:显示所有子任务交付路径 -->
|
||||
<div v-if="selectedTask.isPipeline === 1 && selectedTask.allDeliveryPaths && selectedTask.allDeliveryPaths.length > 0" class="detail-section all-delivery-paths">
|
||||
<h4>
|
||||
<Icon name="folder" size="sm" style="margin-right: 6px;" />
|
||||
所有子任务交付路径
|
||||
</h4>
|
||||
<div class="delivery-paths-list">
|
||||
<div v-for="(path, index) in selectedTask.allDeliveryPaths" :key="index" class="delivery-path-item">
|
||||
<a v-if="isUrl(path)" :href="extractUrl(path)" target="_blank" class="delivery-link">
|
||||
<Icon name="link" size="sm" style="margin-right: 4px;" />
|
||||
{{ path }}
|
||||
</a>
|
||||
<span v-else class="delivery-text">{{ path }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 根据任务状态显示不同的操作按钮 -->
|
||||
@ -2270,6 +2306,7 @@ const taskProducts = ref<TaskProduct[]>([])
|
||||
const newProductId = ref('')
|
||||
const batchProductIds = ref('')
|
||||
const addingProduct = ref(false)
|
||||
const confirmingProductId = ref(false)
|
||||
|
||||
// 已完成任务状态
|
||||
const completedLoading = ref(false)
|
||||
@ -3922,6 +3959,39 @@ const handleDeleteProduct = async (productId: number) => {
|
||||
})
|
||||
}
|
||||
|
||||
// 确认商品ID填写完成
|
||||
const handleConfirmProductIdFilled = async () => {
|
||||
if (!selectedTask.value || !selectedTask.value.id) return
|
||||
|
||||
confirmingProductId.value = true
|
||||
try {
|
||||
await taskApi.confirmTaskProductFilled(selectedTask.value.id)
|
||||
message.success('任务已确认完结')
|
||||
// 更新当前任务状态
|
||||
selectedTask.value.productIdFilled = 1
|
||||
// 刷新任务列表
|
||||
await loadCompletedTasks()
|
||||
await loadMyTasks()
|
||||
} catch (error: any) {
|
||||
message.error(error.message || '确认失败')
|
||||
} finally {
|
||||
confirmingProductId.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 判断是否是URL
|
||||
const isUrl = (str: string) => {
|
||||
// 从字符串中提取可能的URL部分
|
||||
const urlMatch = str.match(/https?:\/\/[^\s]+/)
|
||||
return urlMatch !== null
|
||||
}
|
||||
|
||||
// 从字符串中提取URL
|
||||
const extractUrl = (str: string) => {
|
||||
const urlMatch = str.match(/https?:\/\/[^\s]+/)
|
||||
return urlMatch ? urlMatch[0] : str
|
||||
}
|
||||
|
||||
// 监听配置更新事件
|
||||
const handleConfigUpdate = async (event: CustomEvent) => {
|
||||
console.log('配置已更新,刷新配置数据:', event.detail)
|
||||
@ -3967,6 +4037,15 @@ const getTaskStatusText = (status: string, task?: any) => {
|
||||
return '待接单'
|
||||
}
|
||||
}
|
||||
|
||||
// 5. 已完成任务:检查是否已填写商品ID
|
||||
if (status === 'completed') {
|
||||
// productIdFilled为0或undefined表示未填写
|
||||
if (!task.productIdFilled || task.productIdFilled === 0) {
|
||||
return '待填写ID'
|
||||
}
|
||||
return '已完结'
|
||||
}
|
||||
}
|
||||
|
||||
const statusMap: Record<string, string> = {
|
||||
@ -4568,6 +4647,58 @@ onUnmounted(() => {
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
// 待填写ID状态样式
|
||||
.status-pending_id {
|
||||
background: rgba(250, 173, 20, 0.1);
|
||||
border: 1px solid rgba(250, 173, 20, 0.3);
|
||||
color: #faad14;
|
||||
}
|
||||
|
||||
// 已完结状态样式
|
||||
.status-finished {
|
||||
background: rgba(49, 240, 139, 0.1);
|
||||
border: 1px solid rgba(49, 240, 139, 0.3);
|
||||
color: #31f08b;
|
||||
}
|
||||
|
||||
// 确认完结区域样式
|
||||
.confirm-complete-section {
|
||||
margin-top: 16px;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid $border-primary;
|
||||
}
|
||||
|
||||
// 所有子任务交付路径样式
|
||||
.all-delivery-paths {
|
||||
.delivery-paths-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
|
||||
.delivery-path-item {
|
||||
padding: 8px 12px;
|
||||
background: $bg-tertiary;
|
||||
border-radius: $radius-sm;
|
||||
font-size: 13px;
|
||||
|
||||
.delivery-link {
|
||||
color: #31f08b;
|
||||
text-decoration: none;
|
||||
word-break: break-all;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.delivery-text {
|
||||
color: $text-secondary;
|
||||
word-break: break-all;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 驳回信息显示
|
||||
.reject-info {
|
||||
margin: 12px 0;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user