fix: 修复8个bug + 确保任务必须审核通过后才能发布到大厅

Bug修复:
1. 驳回任务卡片显示'草稿'改为'驳回',显示驳回时间
2. 重新编辑被驳回任务时加载原有信息
3. 任务详情页关联信息显示不全
4. 任务卡片发布时间显示到时分
5. 被指定任务卡片增加'指定'标识
6. 设计师在已完成任务中看不到完成的任务
7. 设计师更新进度后需求方看到的进度还是0%
8. 完结任务卡片无法查看交付路径和填写商品ID

审核流程修复:
- TaskMapper: 任务大厅查询添加 approval_status='approved' 条件
- TaskItemMapper: 子任务查询添加 t.approval_status='approved' 条件
- 确保只有审核通过的任务才能发布到任务大厅
This commit is contained in:
Your Name 2026-01-05 10:41:44 +08:00
parent 80241a394d
commit 3ba4a5f7f7
4 changed files with 204 additions and 18 deletions

View File

@ -44,7 +44,7 @@ public interface TaskItemMapper extends BaseMapper<TaskItem> {
"LEFT JOIN category c ON t.category_id = c.id " +
"LEFT JOIN platform p ON t.platform_id = p.id " +
"LEFT JOIN demand_type dt ON ti.task_type_id = dt.id " +
"WHERE ti.status = 'pending' AND ti.is_locked = 0 AND t.deleted = 0 " +
"WHERE ti.status = 'pending' AND ti.is_locked = 0 AND t.deleted = 0 AND t.approval_status = 'approved' " +
"<if test='taskTypeId != null'> AND ti.task_type_id = #{taskTypeId}</if> " +
"<if test='keyword != null and keyword != \"\"'> " +
" AND (t.title LIKE CONCAT('%', #{keyword}, '%') " +

View File

@ -36,7 +36,7 @@ public interface TaskMapper extends BaseMapper<Task> {
"LEFT JOIN category c ON t.category_id = c.id " +
"LEFT JOIN platform p ON t.platform_id = p.id " +
"LEFT JOIN sys_user approver ON t.current_approver_id = approver.id " +
"WHERE t.status = 'pending' AND t.release_type = 'recruitment' AND t.deleted = 0" +
"WHERE t.status = 'pending' AND t.release_type = 'recruitment' AND t.approval_status = 'approved' AND t.deleted = 0" +
"<if test='taskTypeId != null'> AND t.task_type_id = #{taskTypeId}</if>" +
"<if test='demandTypeId != null'> AND t.demand_type_id = #{demandTypeId}</if>" +
"<if test='brandId != null'> AND t.brand_id = #{brandId}</if>" +

View File

@ -376,7 +376,7 @@
:loading="submitting"
@click="handleSubmit"
>
发起任务
{{ editMode ? '重新提交' : '发起任务' }}
</a-button>
</div>
</a-form>
@ -387,8 +387,8 @@
</template>
<script setup lang="ts">
import { ref, reactive, computed, onMounted, onUnmounted } from 'vue'
import { useRouter } from 'vue-router'
import { ref, reactive, computed, onMounted, onUnmounted, watch } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { message } from 'ant-design-vue'
import type { UploadFile, UploadProps } from 'ant-design-vue'
import DefaultLayout from '@/layouts/DefaultLayout.vue'
@ -404,8 +404,13 @@ import { useAuthStore } from '@/stores/auth'
import { useConfigStore } from '@/stores/config'
const router = useRouter()
const route = useRoute()
const authStore = useAuthStore()
// 编辑模式
const editMode = ref(false)
const editTaskId = ref<number | null>(null)
// 全局配置store
const configStore = useConfigStore()
@ -797,13 +802,32 @@ const handleSubmit = async () => {
receiverId: undefined // 主任务不设接收人,由子任务指定
}
const response = await taskApi.createPipelineTask(pipelineData)
let response: any
if (editMode.value && editTaskId.value) {
// 编辑模式:更新任务并重新提交审核
const updateData = {
...pipelineData,
id: editTaskId.value
}
response = await taskApi.updateTask(editTaskId.value, updateData)
if (response.code === 200) {
message.success('任务已重新提交审核')
setTimeout(() => {
router.push('/tasks')
}, 1000)
}
} else {
// 新建模式
response = await taskApi.createPipelineTask(pipelineData)
if (response.code === 200) {
message.success('流水线任务创建成功')
setTimeout(() => {
router.push('/tasks')
}, 1000)
if (response.code === 200) {
message.success('流水线任务创建成功')
setTimeout(() => {
router.push('/tasks')
}, 1000)
}
}
} catch (error: any) {
console.error('发起任务失败:', error)
@ -1010,6 +1034,59 @@ const handleRemove = (file: UploadFile) => {
return true
}
// 加载任务数据用于编辑
const loadTaskForEdit = async (taskId: number) => {
try {
loading.value = true
const response = await taskApi.getTaskById(taskId)
const task = response.data
if (task) {
// 填充表单数据
formData.title = task.title || ''
formData.taskSubType = task.taskSubType || ''
formData.brandId = task.brandId
formData.platformId = task.platformId
formData.categoryId = task.categoryId
formData.spuInfo = task.spuInfo || ''
formData.remark = task.remark || ''
formData.releaseType = task.releaseType || 'recruitment'
// 处理任务类型
if (task.taskTypeIds) {
const typeIds = task.taskTypeIds.split(',').map((id: string) => parseInt(id.trim()))
formData.taskTypes = typeIds.map((typeId: number, index: number) => ({
taskTypeId: typeId,
quantity: index === 0 ? (task.quantity || 1) : 1,
receiverId: task.receiverId
}))
}
// 处理附件
if (task.attachmentUrl) {
formData.attachmentUrl = task.attachmentUrl
// 将附件URL转换为fileList格式
const urls = task.attachmentUrl.split(',').filter((u: string) => u.trim())
fileList.value = urls.map((url: string, index: number) => ({
uid: `existing-${index}`,
name: `附件${index + 1}`,
status: 'done',
url: url.trim()
}))
}
editMode.value = true
editTaskId.value = taskId
console.log('任务数据加载成功:', task)
}
} catch (error) {
console.error('加载任务数据失败:', error)
message.error('加载任务数据失败')
} finally {
loading.value = false
}
}
// 组件挂载
onMounted(async () => {
// 先刷新用户信息,确保部门信息最新
@ -1027,6 +1104,12 @@ onMounted(async () => {
loadTaskSubTypes()
])
// 检查是否是编辑模式
const editId = route.query.editId
if (editId) {
await loadTaskForEdit(Number(editId))
}
// 监听配置更新事件
window.addEventListener('configUpdated', handleConfigUpdate as EventListener)
})

View File

@ -1430,13 +1430,19 @@
:key="task.id"
class="task-card chart-card"
:class="[`status-${task.status}`]"
@click="handleTaskClick(task)"
style="cursor: pointer;"
>
<!-- 任务标题 -->
<div class="task-header">
<h3 class="task-title">{{ task.title }}</h3>
<div class="task-badges">
<span :class="['badge', `status-${task.status}`]">
{{ getTaskStatusText(task.status) }}
<span :class="['badge', `status-${task.approvalStatus?.includes('rejected') ? 'rejected' : task.status}`]">
{{ getTaskStatusText(task.status, task) }}
</span>
<span v-if="task.releaseType === 'assigned'" class="badge badge-assigned">
<Icon name="target" size="sm" color="#1890ff" style="margin-right: 4px; vertical-align: middle;" />
指定
</span>
<span v-if="task.isUrgent" class="badge badge-urgent">
<Icon name="fire" size="sm" color="#31f08b" style="margin-right: 4px; vertical-align: middle;" />
@ -1445,6 +1451,12 @@
</div>
</div>
<!-- 驳回原因显示 -->
<div v-if="task.approvalStatus?.includes('rejected') && task.rejectReason" class="reject-info">
<span class="reject-label">驳回原因:</span>
<span class="reject-reason">{{ task.rejectReason }}</span>
</div>
<!-- 流程阶段 -->
<div v-if="getProcessStage(task)" class="process-stage">
<span class="stage-icon">
@ -1560,16 +1572,21 @@
<div class="task-footer">
<div class="footer-item">
<Icon name="calendar" size="sm" color="#31f08b" />
<span style="color: #ffffff;">发布: {{ formatDate(task.createTime) }}</span>
<span style="color: #ffffff;">发布: {{ formatTime(task.createTime) }}</span>
</div>
<div class="footer-item" v-if="task.deadline">
<Icon name="clock" size="sm" color="#31f08b" />
<span style="color: #ffffff;">截止: {{ formatDate(task.deadline) }}</span>
<span style="color: #ffffff;">截止: {{ formatTime(task.deadline) }}</span>
</div>
<!-- 驳回时间 -->
<div class="footer-item" v-if="task.approvalStatus?.includes('rejected') && task.updateTime">
<Icon name="x-circle" size="sm" color="#f5222d" />
<span style="color: #f5222d;">驳回: {{ formatTime(task.updateTime) }}</span>
</div>
</div>
<!-- 被驳回或草稿任务的操作按钮 -->
<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);">
<div v-if="['rejected', 'draft'].includes(task.status) || task.approvalStatus?.includes('rejected')" 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"
@ -1579,6 +1596,18 @@
重新编辑提交
</a-button>
</div>
<!-- 已完成任务:查看详情和交付信息 -->
<div v-if="task.status === 'completed'" 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"
@click.stop="handleTaskClick(task)"
style="width: 100%;">
<Icon name="eye" size="sm" style="margin-right: 4px;" />
查看详情
</a-button>
</div>
</div>
</div>
@ -1992,6 +2021,24 @@
</div>
</div>
<!-- 交付路径(已完成或待审核任务显示) -->
<div v-if="selectedTask.deliveryPath && (selectedTask.status === 'completed' || selectedTask.status === 'review')" class="detail-section">
<h4>
<Icon name="link" size="sm" style="margin-right: 6px;" />
交付路径
</h4>
<div class="delivery-path">
<a v-if="selectedTask.deliveryPath.startsWith('http')"
:href="selectedTask.deliveryPath"
target="_blank"
class="delivery-link">
<Icon name="external-link" size="sm" style="margin-right: 4px;" />
{{ selectedTask.deliveryPath }}
</a>
<p v-else class="delivery-text">{{ selectedTask.deliveryPath }}</p>
</div>
</div>
<!-- 商品ID管理(已完成任务显示) -->
<div v-if="selectedTask.status === 'completed'" class="detail-section product-section">
<h4>
@ -3838,8 +3885,15 @@ const handleConfigUpdate = async (event: CustomEvent) => {
}
// 组件挂载
// 获取任务状态文本
const getTaskStatusText = (status: string) => {
// 获取任务状态文本(增强版,支持区分驳回和草稿)
const getTaskStatusText = (status: string, task?: any) => {
// 如果有task对象,检查approvalStatus来区分驳回和草稿
if (task && status === 'draft') {
if (task.approvalStatus === 'rejected_by_demand' || task.approvalStatus === 'rejected_by_receiving') {
return '已驳回'
}
}
const statusMap: Record<string, string> = {
'pending': '待接单',
'in_progress': '进行中',
@ -4359,6 +4413,55 @@ onUnmounted(() => {
color: #31f08b;
}
.badge-assigned {
background: rgba(24, 144, 255, 0.1);
border: 1px solid rgba(24, 144, 255, 0.2);
color: #1890ff;
}
// 驳回信息显示
.reject-info {
margin: 12px 0;
padding: 10px 12px;
background: rgba(245, 34, 45, 0.08);
border: 1px solid rgba(245, 34, 45, 0.2);
border-radius: 6px;
font-size: 13px;
.reject-label {
color: #f5222d;
font-weight: 500;
}
.reject-reason {
color: #ff7875;
margin-left: 4px;
}
}
// 交付路径
.delivery-path {
padding: 12px;
background: rgba(49, 240, 139, 0.05);
border-radius: 6px;
.delivery-link {
color: #31f08b;
text-decoration: none;
word-break: break-all;
&:hover {
text-decoration: underline;
}
}
.delivery-text {
color: #e0e0e0;
margin: 0;
word-break: break-all;
}
}
.badge-completed {
background: rgba(49, 240, 139, 0.1);
color: #31f08b;