feat: 任务卡片增加任务类型明细显示

This commit is contained in:
Your Name 2026-01-07 00:09:24 +08:00
parent 638d8e2451
commit 4838e17262
4 changed files with 130 additions and 3 deletions

View File

@ -41,6 +41,11 @@ public class TaskItem implements Serializable {
*/
private Long taskTypeId;
/**
* 任务类型明细 (JSON数组: [{typeId, typeName, quantity, unitScore, totalScore}])
*/
private String typeDetails;
/**
* 数量
*/

View File

@ -420,6 +420,8 @@ public class TaskService {
BigDecimal groupTotalHours = BigDecimal.ZERO;
int groupTotalQuantity = 0;
StringBuilder taskTypeIds = new StringBuilder();
// 构建任务类型明细 JSON 数组
java.util.List<java.util.Map<String, Object>> typeDetailsList = new java.util.ArrayList<>();
for (com.designhub.dto.PipelineTaskDTO.TaskItemDTO itemDto : groupItems) {
var demandType = configService.getDemandTypeById(itemDto.getTaskTypeId());
@ -436,6 +438,15 @@ public class TaskService {
taskTypeIds.append(",");
}
taskTypeIds.append(itemDto.getTaskTypeId());
// 添加任务类型明细
java.util.Map<String, Object> detail = new java.util.LinkedHashMap<>();
detail.put("typeId", itemDto.getTaskTypeId());
detail.put("typeName", demandType.getName());
detail.put("quantity", itemDto.getQuantity());
detail.put("unitScore", demandType.getUnitScore());
detail.put("totalScore", itemScore);
typeDetailsList.add(detail);
}
// 创建子任务
@ -444,6 +455,13 @@ public class TaskService {
item.setItemNo(generateTaskItemNo(task.getTaskNo(), itemIndex));
item.setGroupNo(groupNo);
item.setTaskTypeId(groupFirstTaskTypeId.get(groupNo)); // 使用第一个任务类型ID
// 保存任务类型明细 JSON
try {
com.fasterxml.jackson.databind.ObjectMapper objectMapper = new com.fasterxml.jackson.databind.ObjectMapper();
item.setTypeDetails(objectMapper.writeValueAsString(typeDetailsList));
} catch (Exception e) {
log.warn("序列化任务类型明细失败: {}", e.getMessage());
}
item.setQuantity(groupTotalQuantity);
item.setUnitScore(groupTotalScore / groupTotalQuantity); // 平均单价
item.setTotalScore(groupTotalScore);

View File

@ -0,0 +1,6 @@
-- V10__add_task_type_details.sql
-- 添加任务类型明细字段到子任务表
ALTER TABLE `task_item`
ADD COLUMN `type_details` JSON NULL COMMENT '任务类型明细(JSON数组: [{typeId, typeName, quantity, unitScore, totalScore}])' AFTER `task_type_id`;

View File

@ -240,7 +240,7 @@
</div>
<div class="info-item">
<span class="label">预估积分</span>
<span class="value text-brand">{{ task.score || '--' }} 分</span>
<span class="value text-brand">{{ task.totalScore || task.score || '--' }} 分</span>
</div>
<div class="info-item">
<span class="label">数量</span>
@ -256,6 +256,17 @@
</div>
</div>
<!-- 任务类型明细 -->
<div v-if="parseTypeDetails(task.typeDetails).length > 0" class="type-details-section">
<div class="type-details-title">任务类型明细</div>
<div class="type-details-list">
<div v-for="(detail, idx) in parseTypeDetails(task.typeDetails)" :key="idx" class="type-detail-item">
<span class="type-name">└ {{ detail.typeName }}</span>
<span class="type-score">x{{ detail.quantity }} = {{ detail.totalScore }}分</span>
</div>
</div>
</div>
<!-- 任务详情 -->
<div class="task-details">
<div v-if="task.brandName" class="detail-item">
@ -541,7 +552,7 @@
</div>
<div class="info-item">
<span class="label">预估积分</span>
<span class="value text-brand">{{ task.score }} 分</span>
<span class="value text-brand">{{ task.totalScore || task.score }} 分</span>
</div>
<div class="info-item">
<span class="label">数量</span>
@ -557,6 +568,17 @@
</div>
</div>
<!-- 任务类型明细 -->
<div v-if="parseTypeDetails(task.typeDetails).length > 0" class="type-details-section">
<div class="type-details-title">任务类型明细</div>
<div class="type-details-list">
<div v-for="(detail, idx) in parseTypeDetails(task.typeDetails)" :key="idx" class="type-detail-item">
<span class="type-name">└ {{ detail.typeName }}</span>
<span class="type-score">x{{ detail.quantity }} = {{ detail.totalScore }}分</span>
</div>
</div>
</div>
<!-- 任务详情 -->
<div class="task-details">
<div class="detail-item">
@ -850,7 +872,7 @@
</div>
<div class="info-item">
<span class="label">预估积分</span>
<span class="value text-brand">{{ task.score }} 分</span>
<span class="value text-brand">{{ task.totalScore || task.score }} 分</span>
</div>
<div class="info-item">
<span class="label">数量</span>
@ -866,6 +888,17 @@
</div>
</div>
<!-- 任务类型明细 -->
<div v-if="parseTypeDetails(task.typeDetails).length > 0" class="type-details-section">
<div class="type-details-title">任务类型明细</div>
<div class="type-details-list">
<div v-for="(detail, idx) in parseTypeDetails(task.typeDetails)" :key="idx" class="type-detail-item">
<span class="type-name">└ {{ detail.typeName }}</span>
<span class="type-score">x{{ detail.quantity }} = {{ detail.totalScore }}分</span>
</div>
</div>
</div>
<!-- 任务详情 -->
<div class="task-details">
<div class="detail-item">
@ -2341,6 +2374,14 @@ import { useConfigStore } from '@/stores/config'
import dayjs from 'dayjs'
// 扩展Task类型以支持子任务字段
interface TaskTypeDetail {
typeId: number
typeName: string
quantity: number
unitScore: number
totalScore: number
}
interface TaskOrSubTask extends Task {
isSubTask?: boolean // 是否为子任务
itemId?: number // 子任务ID
@ -2351,6 +2392,7 @@ interface TaskOrSubTask extends Task {
submitTime?: string // 提交时间
taskTypeName?: string // 任务类型名称(子任务直接返回)
prevDownloadUrl?: string // 前置子任务的素材下载链接
typeDetails?: string | TaskTypeDetail[] // 任务类型明细(JSON字符串或解析后的数组)
}
const router = useRouter()
@ -2540,6 +2582,18 @@ const getTaskTypeName = (typeId: number, taskTypeName?: string, taskSubType?: st
return type?.name || '未知类型'
}
// 解析任务类型明细
const parseTypeDetails = (typeDetails: string | TaskTypeDetail[] | undefined): TaskTypeDetail[] => {
if (!typeDetails) return []
if (Array.isArray(typeDetails)) return typeDetails
try {
return JSON.parse(typeDetails) as TaskTypeDetail[]
} catch (e) {
console.warn('解析任务类型明细失败:', e)
return []
}
}
// 获取任务类型单位积分
const getTaskTypeUnitScore = (typeId: number) => {
const type = demandTypes.value.find(t => t.id === typeId)
@ -5031,6 +5085,50 @@ onUnmounted(() => {
}
}
// 任务类型明细
.type-details-section {
background: rgba($brand-primary, 0.05);
border: 1px solid rgba($brand-primary, 0.2);
border-radius: 8px;
padding: $space-sm $space-md;
margin-bottom: $space-md;
.type-details-title {
font-size: $font-size-xs;
color: $text-tertiary;
margin-bottom: $space-xs;
}
.type-details-list {
display: flex;
flex-direction: column;
gap: 4px;
}
.type-detail-item {
display: flex;
justify-content: space-between;
align-items: center;
font-size: $font-size-sm;
padding: 2px 0;
.type-name {
color: $text-secondary;
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.type-score {
color: $brand-primary;
font-weight: $font-weight-medium;
margin-left: $space-md;
white-space: nowrap;
}
}
}
// 任务详情
.task-details {
display: grid;