feat: 添加流水线任务子任务处理人展开显示功能

- 在任务卡片中添加展开/折叠功能,显示每个子任务的详细处理人信息
- 支持显示子任务编号、处理人姓名和状态
- 更新TaskCard组件和TaskHall页面,添加相应的样式和交互逻辑
This commit is contained in:
Your Name 2026-01-13 16:01:53 +08:00
parent 75a0783379
commit 744ac08a91
2 changed files with 245 additions and 7 deletions

View File

@ -52,7 +52,25 @@
</div>
<div v-if="showReceiver" class="info-item">
<span class="label">{{ receiverLabel }}</span>
<span class="value">{{ task.receiverName || '--' }}</span>
<span class="value">
<span>{{ task.receiverName || '--' }}</span>
<span v-if="task.isPipeline === 1 && task.subTasks && task.subTasks.length > 0"
class="expand-subtasks"
@click.stop="toggleSubtasks">
<Icon :name="showSubtasks ? 'arrowUp' : 'arrowDown'" size="xs" style="margin-left: 4px; cursor: pointer;" />
</span>
</span>
</div>
<!-- 流水线任务子任务详细信息(可展开) -->
<div v-if="showReceiver && task.isPipeline === 1 && task.subTasks && task.subTasks.length > 0 && showSubtasks"
class="subtasks-detail">
<div v-for="(subTask, index) in task.subTasks" :key="subTask.id || index" class="subtask-item">
<span class="subtask-label">{{ subTask.itemNo || `子任务${index + 1}` }}:</span>
<span class="subtask-receiver">{{ subTask.receiverName || '待接单' }}</span>
<span :class="['subtask-status', getSubTaskStatusClass(subTask.status)]">
{{ getSubTaskStatusText(subTask.status) }}
</span>
</div>
</div>
<div v-if="task.brandName" class="detail-item">
<span class="icon">
@ -133,12 +151,19 @@
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { computed, ref } from 'vue'
import { message } from 'ant-design-vue'
import Icon from './Icon.vue'
import dayjs from 'dayjs'
import { useConfigStore } from '@/stores/config'
interface TaskItem {
id?: number
itemNo?: string
receiverName?: string
status: string
}
interface Task {
id: number
taskNo: string
@ -161,6 +186,8 @@ interface Task {
deadline?: string
completeTime?: string
submitTime?: string
isPipeline?: number
subTasks?: TaskItem[]
}
interface Props {
@ -179,6 +206,11 @@ const props = withDefaults(defineProps<Props>(), {
defineEmits(['click'])
const configStore = useConfigStore()
const showSubtasks = ref(false)
const toggleSubtasks = () => {
showSubtasks.value = !showSubtasks.value
}
// 状态相关计算属性
const statusClass = computed(() => {
@ -278,6 +310,30 @@ const handleCopyTaskNo = async (taskNo: string) => {
document.body.removeChild(textarea)
}
}
// 获取子任务状态文本
const getSubTaskStatusText = (status: string) => {
const statusMap: Record<string, string> = {
'locked': '已锁定',
'pending': '待接单',
'in_progress': '进行中',
'completed': '已完成',
'rejected': '已驳回'
}
return statusMap[status] || status
}
// 获取子任务状态样式类
const getSubTaskStatusClass = (status: string) => {
const classMap: Record<string, string> = {
'locked': 'status-locked',
'pending': 'status-pending',
'in_progress': 'status-in-progress',
'completed': 'status-completed',
'rejected': 'status-rejected'
}
return classMap[status] || ''
}
</script>
<style lang="scss" scoped>
@ -340,6 +396,9 @@ const handleCopyTaskNo = async (taskNo: string) => {
font-size: 14px;
font-weight: 500;
color: $text-primary;
display: inline-flex;
align-items: center;
gap: 4px;
&.text-brand {
color: #31f08b;
@ -362,6 +421,19 @@ const handleCopyTaskNo = async (taskNo: string) => {
}
}
}
.expand-subtasks {
display: inline-flex;
align-items: center;
cursor: pointer;
opacity: 0.7;
transition: opacity 0.2s;
&:hover {
opacity: 1;
color: #31f08b;
}
}
}
}
}
@ -402,6 +474,66 @@ const handleCopyTaskNo = async (taskNo: string) => {
align-items: center;
}
}
.subtasks-detail {
margin-top: $space-xs;
padding: $space-sm;
background: rgba(49, 240, 139, 0.05);
border-radius: $radius-sm;
border: 1px solid rgba(49, 240, 139, 0.1);
.subtask-item {
display: flex;
align-items: center;
gap: $space-xs;
font-size: 12px;
padding: 4px 0;
color: $text-secondary;
.subtask-label {
color: $text-primary;
font-weight: 500;
min-width: 80px;
}
.subtask-receiver {
color: $text-primary;
flex: 1;
}
.subtask-status {
padding: 2px 8px;
border-radius: $radius-sm;
font-size: 11px;
font-weight: 500;
&.status-completed {
background: rgba(49, 240, 139, 0.2);
color: #31f08b;
}
&.status-in-progress {
background: rgba(59, 130, 246, 0.2);
color: #3B82F6;
}
&.status-pending {
background: rgba(156, 163, 175, 0.2);
color: #9CA3AF;
}
&.status-locked {
background: rgba(107, 114, 128, 0.2);
color: #6B7280;
}
&.status-rejected {
background: rgba(239, 68, 68, 0.2);
color: #EF4444;
}
}
}
}
}
.task-progress {

View File

@ -1715,7 +1715,27 @@
<span class="icon">
<Icon name="user" size="sm" color="#31f08b" />
</span>
<span>处理人: {{ task.receiverName || '待接单' }}</span>
<span>
处理人: {{ task.receiverName || '待接单' }}
<span
v-if="task.isPipeline === 1 && task.subTasks && task.subTasks.length > 0 && task.id"
class="expand-subtasks"
@click.stop="toggleTaskSubtasks(task.id!)"
>
<Icon :name="expandedTaskSubtasks[task.id!] ? 'arrowUp' : 'arrowDown'" size="xs" style="margin-left: 4px; cursor: pointer;" />
</span>
</span>
</div>
<!-- 流水线任务子任务详细信息(可展开) -->
<div v-if="task.isPipeline === 1 && task.subTasks && task.subTasks.length > 0 && task.id && expandedTaskSubtasks[task.id]"
class="subtasks-detail">
<div v-for="(subTask, index) in task.subTasks" :key="subTask.id || index" class="subtask-item">
<span class="subtask-label">{{ getSubTaskLabel(subTask, index) }}:</span>
<span class="subtask-receiver">{{ subTask.receiverName || '待接单' }}</span>
<span :class="['subtask-status', getSubTaskStatusClass(subTask.status)]">
{{ getSubTaskStatusText(subTask.status) }}
</span>
</div>
</div>
<div v-if="task.brandName" class="detail-item">
<span class="icon">
@ -2842,11 +2862,20 @@ const getSubTaskLabel = (subTask: any, index: number) => {
}
const getSubTaskStatusText = (status: string | undefined) => {
switch (status) {
case 'completed': return '已完成'
case 'in_progress': return '进行中'
default: return '待接单'
const statusMap: Record<string, string> = {
'locked': '已锁定',
'pending': '待接单',
'in_progress': '进行中',
'completed': '已完成',
'rejected': '已驳回'
}
return statusMap[status || ''] || status || '未知'
}
// 展开/折叠任务子任务详情
const expandedTaskSubtasks = ref<Record<number, boolean>>({})
const toggleTaskSubtasks = (taskId: number) => {
expandedTaskSubtasks.value[taskId] = !expandedTaskSubtasks.value[taskId]
}
// 判断任务是否有子任务在进行中
@ -6631,6 +6660,83 @@ onUnmounted(() => {
text-decoration: underline;
}
}
// 展开/折叠图标
.expand-subtasks {
display: inline-flex;
align-items: center;
cursor: pointer;
opacity: 0.7;
transition: opacity 0.2s;
margin-left: 4px;
&:hover {
opacity: 1;
color: #31f08b;
}
}
// 子任务详情展开样式
.subtasks-detail {
grid-column: 1 / -1;
margin-top: $space-xs;
padding: $space-sm;
background: rgba(49, 240, 139, 0.05);
border-radius: $radius-sm;
border: 1px solid rgba(49, 240, 139, 0.1);
.subtask-item {
display: flex;
align-items: center;
gap: $space-sm;
font-size: 12px;
padding: 4px 0;
color: $text-secondary;
.subtask-label {
color: $text-primary;
font-weight: 500;
min-width: 80px;
}
.subtask-receiver {
color: $text-primary;
flex: 1;
}
.subtask-status {
padding: 2px 8px;
border-radius: $radius-sm;
font-size: 11px;
font-weight: 500;
&.status-completed {
background: rgba(49, 240, 139, 0.2);
color: #31f08b;
}
&.status-in-progress {
background: rgba(59, 130, 246, 0.2);
color: #3B82F6;
}
&.status-pending {
background: rgba(156, 163, 175, 0.2);
color: #9CA3AF;
}
&.status-locked {
background: rgba(107, 114, 128, 0.2);
color: #6B7280;
}
&.status-rejected {
background: rgba(239, 68, 68, 0.2);
color: #EF4444;
}
}
}
}
}
/* 审批弹窗暗黑主题适配 */