refactor: 使用通用TaskCard组件重构任务大厅各个Tab
This commit is contained in:
parent
c2f6aef463
commit
ab77e7486f
@ -1,123 +1,86 @@
|
||||
<template>
|
||||
<div :class="['task-card', 'chart-card', statusClass]" @click="$emit('click', task)">
|
||||
<!-- 任务标题 -->
|
||||
<div class="task-header">
|
||||
<h3 class="task-title">{{ task.title }}</h3>
|
||||
<div class="task-badges">
|
||||
<span :class="['badge', badgeClass]">
|
||||
<Icon :name="badgeIcon" size="sm" :color="badgeIconColor" style="margin-right: 4px; vertical-align: middle;" />
|
||||
{{ badgeText }}
|
||||
</span>
|
||||
<span v-if="task.isUrgent" class="badge badge-urgent">
|
||||
<Icon name="fire" size="sm" color="#F59E0B" style="margin-right: 4px; vertical-align: middle;" />
|
||||
紧急
|
||||
</span>
|
||||
<div class="task-card-container" @click="$emit('click', task)">
|
||||
<!-- 预览图区域 -->
|
||||
<div v-if="task.previewImage" class="preview-cover">
|
||||
<img :src="task.previewImage" alt="预览图" loading="lazy" @error="handleImageError" />
|
||||
<div class="preview-overlay"></div>
|
||||
<!-- 悬浮标签 -->
|
||||
<div class="floating-badges">
|
||||
<span v-if="task.isSubTask" class="badge-mini subtask">子任务</span>
|
||||
<span v-if="isNewTask" class="badge-mini new">新发布</span>
|
||||
<span v-if="task.isUrgent" class="badge-mini urgent">紧急</span>
|
||||
<span v-if="mode === 'in_progress'" class="badge-mini progress">进行中</span>
|
||||
<span v-if="mode === 'review'" class="badge-mini review">审核中</span>
|
||||
<span v-if="mode === 'completed'" class="badge-mini completed">已完成</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 任务信息 -->
|
||||
<div class="task-info">
|
||||
<div class="info-item">
|
||||
<span class="label">任务编号</span>
|
||||
<span class="value">{{ task.taskNo }}</span>
|
||||
<div class="card-body">
|
||||
<!-- 头部标签(无预览图时显示) -->
|
||||
<div v-if="!task.previewImage" class="header-badges">
|
||||
<span v-if="task.isSubTask" class="badge-tag subtask">子任务</span>
|
||||
<span v-if="isNewTask" class="badge-tag new">新发布</span>
|
||||
<span v-if="task.isUrgent" class="badge-tag urgent">紧急</span>
|
||||
<span v-if="isHighScoreTask" class="badge-tag high-score">高分</span>
|
||||
<span v-if="mode === 'in_progress'" class="badge-tag progress">进行中</span>
|
||||
<span v-if="mode === 'review'" class="badge-tag review">待审核</span>
|
||||
<span v-if="mode === 'completed'" class="badge-tag completed">已完成</span>
|
||||
<span v-if="mode === 'published'" class="badge-tag published">我发起的</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="label">任务类型</span>
|
||||
<span class="value">{{ getTaskTypeName(task.taskTypeId) }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="label">{{ scoreLabel }}</span>
|
||||
<span class="value text-brand">{{ task.score }} 分</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="label">数量</span>
|
||||
<span class="value">{{ task.quantity || '--' }} 个</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 任务详情 -->
|
||||
<div class="task-details">
|
||||
<div v-if="showPublisher" class="info-item">
|
||||
<span class="label">发起人</span>
|
||||
<span class="value">{{ task.publisherName || '--' }}</span>
|
||||
</div>
|
||||
<div v-if="showReceiver" class="info-item">
|
||||
<span class="label">{{ receiverLabel }}</span>
|
||||
<span class="value">{{ task.receiverName || '--' }}</span>
|
||||
</div>
|
||||
<div v-if="task.brandName" class="detail-item">
|
||||
<span class="icon">
|
||||
<Icon name="tag" size="sm" color="#31f08b" />
|
||||
</span>
|
||||
<span>品牌: {{ task.brandName }}</span>
|
||||
</div>
|
||||
<div v-if="task.categoryName" class="detail-item">
|
||||
<span class="icon">
|
||||
<Icon name="folder" size="sm" color="#31f08b" />
|
||||
</span>
|
||||
<span>类目: {{ task.categoryName }}</span>
|
||||
</div>
|
||||
<div v-if="task.platformName" class="detail-item">
|
||||
<span class="icon">
|
||||
<Icon name="monitor" size="sm" color="#31f08b" />
|
||||
</span>
|
||||
<span>平台: {{ task.platformName }}</span>
|
||||
</div>
|
||||
<div v-if="task.spuInfo" class="detail-item">
|
||||
<span class="icon">
|
||||
<Icon name="package" size="sm" color="#31f08b" />
|
||||
</span>
|
||||
<span>SPU: {{ task.spuInfo }}</span>
|
||||
</div>
|
||||
<div v-if="task.taskSubType" class="detail-item">
|
||||
<span class="icon">
|
||||
<Icon name="list" size="sm" color="#31f08b" />
|
||||
</span>
|
||||
<span>子类型: {{ task.taskSubType }}</span>
|
||||
</div>
|
||||
<div v-if="task.deliveryPath" class="detail-item">
|
||||
<span class="icon">
|
||||
<Icon name="link" size="sm" color="#31f08b" />
|
||||
</span>
|
||||
<span>交付: {{ task.deliveryPath }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 标题 -->
|
||||
<h3 class="card-title" :title="task.title">{{ task.title }}</h3>
|
||||
|
||||
<!-- 进度条(仅进行中任务显示) -->
|
||||
<div v-if="showProgress" class="task-progress">
|
||||
<div class="progress-label">
|
||||
<span>任务进度</span>
|
||||
<span>{{ task.progress || 0 }}%</span>
|
||||
<!-- 核心指标 -->
|
||||
<div class="metrics-row">
|
||||
<div class="metric">
|
||||
<span class="label">类型</span>
|
||||
<span class="value type" :title="taskTypeName">
|
||||
{{ taskTypeName }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="metric divider"></div>
|
||||
<div class="metric">
|
||||
<span class="label">{{ scoreLabel }}</span>
|
||||
<span class="value score">{{ task.score || '--' }}</span>
|
||||
</div>
|
||||
<div class="metric divider"></div>
|
||||
<div class="metric">
|
||||
<span class="label">数量</span>
|
||||
<span class="value">{{ task.quantity || 1 }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<a-progress
|
||||
:percent="task.progress || 0"
|
||||
:stroke-color="{ '0%': '#31f08b', '100%': '#31f08b' }"
|
||||
:show-info="false"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<div v-if="$slots.actions" class="task-actions">
|
||||
<slot name="actions" :task="task"></slot>
|
||||
</div>
|
||||
<!-- 详细信息 -->
|
||||
<div class="details-grid">
|
||||
<div class="detail-item" v-if="task.brandName || task.categoryName">
|
||||
<Icon name="tag" size="xs" class="icon" />
|
||||
<span class="text text-ellipsis">
|
||||
{{ task.brandName }} <span v-if="task.brandName && task.categoryName">·</span> {{ task.categoryName }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="detail-item" v-if="task.platformName || task.spuInfo">
|
||||
<Icon name="monitor" size="xs" class="icon" />
|
||||
<span class="text text-ellipsis">
|
||||
{{ task.platformName || '--' }} <span v-if="task.spuInfo">· {{ task.spuInfo }}</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- 时间信息 -->
|
||||
<div class="task-footer">
|
||||
<div class="footer-item">
|
||||
<Icon name="calendar" size="sm" color="#31f08b" />
|
||||
<span style="color: #ffffff;">发布: {{ formatDate(task.createTime) }}</span>
|
||||
<div class="detail-item">
|
||||
<Icon name="user" size="xs" class="icon" />
|
||||
<span class="text text-ellipsis">{{ personName }}</span>
|
||||
</div>
|
||||
|
||||
<div class="detail-item">
|
||||
<Icon name="clock" size="xs" class="icon" />
|
||||
<span class="text">{{ timeLabel }}: {{ timeValue }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="task.deadline && task.status === 'in_progress'" class="footer-item">
|
||||
<Icon name="clock" size="sm" color="#31f08b" />
|
||||
<span style="color: #ffffff;">截止: {{ formatDate(task.deadline) }}</span>
|
||||
</div>
|
||||
<div v-if="task.completeTime && task.status === 'completed'" class="footer-item">
|
||||
<Icon name="check" size="sm" color="#31f08b" />
|
||||
<span style="color: #ffffff;">完成: {{ formatDate(task.completeTime) }}</span>
|
||||
</div>
|
||||
<div v-if="task.submitTime && task.status === 'review'" class="footer-item">
|
||||
<Icon name="upload" size="sm" color="#31f08b" />
|
||||
<span style="color: #ffffff;">提交: {{ formatDate(task.submitTime) }}</span>
|
||||
|
||||
<!-- 底部操作栏 -->
|
||||
<div class="card-actions">
|
||||
<slot name="actions"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -125,29 +88,33 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import Icon from './Icon.vue'
|
||||
import Icon from '@/components/Icon.vue'
|
||||
import dayjs from 'dayjs'
|
||||
import { useConfigStore } from '@/stores/config'
|
||||
|
||||
// 任务数据接口
|
||||
interface Task {
|
||||
id: number
|
||||
taskNo: string
|
||||
title: string
|
||||
taskTypeId: number
|
||||
taskTypeName?: string
|
||||
taskSubType?: string
|
||||
score: number
|
||||
quantity?: number
|
||||
status: string
|
||||
status?: string
|
||||
progress?: number
|
||||
isUrgent?: number
|
||||
isSubTask?: boolean
|
||||
publisherName?: string
|
||||
receiverName?: string
|
||||
brandName?: string
|
||||
categoryName?: string
|
||||
platformName?: string
|
||||
spuInfo?: string
|
||||
taskSubType?: string
|
||||
deliveryPath?: string
|
||||
createTime: string
|
||||
previewImage?: string
|
||||
estimatedHours?: number
|
||||
createTime?: string
|
||||
deadline?: string
|
||||
completeTime?: string
|
||||
submitTime?: string
|
||||
@ -155,288 +122,428 @@ interface Task {
|
||||
|
||||
interface Props {
|
||||
task: Task
|
||||
showPublisher?: boolean
|
||||
showReceiver?: boolean
|
||||
showProgress?: boolean
|
||||
mode?: 'available' | 'in_progress' | 'review' | 'completed' | 'published'
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
showPublisher: true,
|
||||
showReceiver: true,
|
||||
showProgress: false
|
||||
mode: 'available'
|
||||
})
|
||||
|
||||
defineEmits(['click'])
|
||||
|
||||
const configStore = useConfigStore()
|
||||
|
||||
// 状态相关计算属性
|
||||
const statusClass = computed(() => {
|
||||
const statusMap: Record<string, string> = {
|
||||
'pending': 'available',
|
||||
'in_progress': 'in-progress',
|
||||
'review': 'review',
|
||||
'completed': 'completed',
|
||||
'rejected': 'rejected'
|
||||
}
|
||||
return statusMap[props.task.status] || ''
|
||||
})
|
||||
|
||||
const badgeClass = computed(() => {
|
||||
const classMap: Record<string, string> = {
|
||||
'pending': 'badge-new',
|
||||
'in_progress': 'badge-progress',
|
||||
'review': 'badge-review',
|
||||
'completed': 'badge-completed',
|
||||
'rejected': 'badge-rejected'
|
||||
}
|
||||
return classMap[props.task.status] || 'badge-new'
|
||||
})
|
||||
|
||||
const badgeIcon = computed(() => {
|
||||
const iconMap: Record<string, string> = {
|
||||
'pending': 'sparkles',
|
||||
'in_progress': 'clock',
|
||||
'review': 'file-text',
|
||||
'completed': 'check-circle',
|
||||
'rejected': 'x-circle'
|
||||
}
|
||||
return iconMap[props.task.status] || 'sparkles'
|
||||
})
|
||||
|
||||
const badgeIconColor = computed(() => {
|
||||
const colorMap: Record<string, string> = {
|
||||
'pending': '#31f08b',
|
||||
'in_progress': '#3B82F6',
|
||||
'review': '#F59E0B',
|
||||
'completed': '#31f08b',
|
||||
'rejected': '#EF4444'
|
||||
}
|
||||
return colorMap[props.task.status] || '#31f08b'
|
||||
})
|
||||
|
||||
const badgeText = computed(() => {
|
||||
const textMap: Record<string, string> = {
|
||||
'pending': '待接单',
|
||||
'in_progress': '进行中',
|
||||
'review': '待审核',
|
||||
'completed': '已完成',
|
||||
'rejected': '已驳回'
|
||||
}
|
||||
return textMap[props.task.status] || '新发布'
|
||||
})
|
||||
|
||||
const scoreLabel = computed(() => {
|
||||
return props.task.status === 'completed' ? '获得积分' : '预估积分'
|
||||
})
|
||||
|
||||
const receiverLabel = computed(() => {
|
||||
return props.task.status === 'completed' ? '完成人' : '接单人'
|
||||
})
|
||||
|
||||
// 获取任务类型名称
|
||||
const getTaskTypeName = (taskTypeId: number) => {
|
||||
const type = configStore.demandTypes.find(t => t.id === taskTypeId)
|
||||
return type?.name || '--'
|
||||
const taskTypeName = computed(() => {
|
||||
// 优先显示任务子类型
|
||||
if (props.task.taskSubType) return props.task.taskSubType
|
||||
// 其次显示后端返回的任务类型名称
|
||||
if (props.task.taskTypeName) return props.task.taskTypeName
|
||||
|
||||
// 最后尝试从 store 中查找
|
||||
const findInTree = (items: any[], id: number): any => {
|
||||
for (const item of items) {
|
||||
if (item.id === id) return item
|
||||
if (item.children) {
|
||||
const found = findInTree(item.children, id)
|
||||
if (found) return found
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
const type = findInTree(configStore.demandTypes, props.task.taskTypeId)
|
||||
return type?.name || '未知类型'
|
||||
})
|
||||
|
||||
// 是否为新发布任务(24小时内)
|
||||
const isNewTask = computed(() => {
|
||||
if (!props.task.createTime) return false
|
||||
const createTime = new Date(props.task.createTime).getTime()
|
||||
const now = new Date().getTime()
|
||||
return (now - createTime) < 24 * 60 * 60 * 1000
|
||||
})
|
||||
|
||||
// 是否为高分任务
|
||||
const isHighScoreTask = computed(() => {
|
||||
return (props.task.score || 0) >= 10
|
||||
})
|
||||
|
||||
// 积分标签
|
||||
const scoreLabel = computed(() => {
|
||||
return props.mode === 'completed' ? '获得' : '积分'
|
||||
})
|
||||
|
||||
// 人员显示
|
||||
const personName = computed(() => {
|
||||
if (props.mode === 'available' || props.mode === 'published') {
|
||||
return props.task.publisherName || '未知发布人'
|
||||
}
|
||||
return props.task.receiverName || '未知接单人'
|
||||
})
|
||||
|
||||
// 时间标签
|
||||
const timeLabel = computed(() => {
|
||||
switch (props.mode) {
|
||||
case 'available': return '时效'
|
||||
case 'in_progress': return '截止'
|
||||
case 'review': return '提交'
|
||||
case 'completed': return '完成'
|
||||
case 'published': return '发布'
|
||||
default: return '时间'
|
||||
}
|
||||
})
|
||||
|
||||
// 时间值
|
||||
const timeValue = computed(() => {
|
||||
switch (props.mode) {
|
||||
case 'available':
|
||||
return formatEstimatedTime(props.task.estimatedHours)
|
||||
case 'in_progress':
|
||||
return formatDeadline(props.task.deadline)
|
||||
case 'review':
|
||||
return formatDate(props.task.submitTime)
|
||||
case 'completed':
|
||||
return formatDate(props.task.completeTime)
|
||||
case 'published':
|
||||
return formatDate(props.task.createTime)
|
||||
default:
|
||||
return '--'
|
||||
}
|
||||
})
|
||||
|
||||
// 格式化工时
|
||||
const formatEstimatedTime = (hours: number | undefined) => {
|
||||
if (!hours) return '--'
|
||||
const WORK_HOURS_PER_DAY = 8
|
||||
const days = Math.floor(hours / WORK_HOURS_PER_DAY)
|
||||
const remainingHours = Math.round((hours % WORK_HOURS_PER_DAY) * 100) / 100
|
||||
|
||||
if (days === 0) {
|
||||
return Number.isInteger(remainingHours) ? `${remainingHours}小时` : `${remainingHours.toFixed(1)}小时`
|
||||
} else if (remainingHours === 0) {
|
||||
return `${days}天`
|
||||
} else {
|
||||
const hourStr = Number.isInteger(remainingHours) ? `${remainingHours}小时` : `${remainingHours.toFixed(1)}小时`
|
||||
return `${days}天${hourStr}`
|
||||
}
|
||||
}
|
||||
|
||||
// 格式化截止时间
|
||||
const formatDeadline = (deadline: string | undefined) => {
|
||||
if (!deadline) return '--'
|
||||
const deadlineTime = dayjs(deadline)
|
||||
const now = dayjs()
|
||||
const diffHours = deadlineTime.diff(now, 'hour')
|
||||
|
||||
if (diffHours < 0) {
|
||||
return `已逾期`
|
||||
} else if (diffHours < 24) {
|
||||
return `${diffHours}小时后`
|
||||
} else {
|
||||
const diffDays = Math.floor(diffHours / 24)
|
||||
return `${diffDays}天后`
|
||||
}
|
||||
}
|
||||
|
||||
// 格式化日期
|
||||
const formatDate = (date?: string) => {
|
||||
if (!date) return '--'
|
||||
return dayjs(date).format('YYYY-MM-DD HH:mm')
|
||||
return dayjs(date).format('MM-DD HH:mm')
|
||||
}
|
||||
|
||||
// 图片加载错误处理
|
||||
const handleImageError = (e: Event) => {
|
||||
const img = e.target as HTMLImageElement
|
||||
img.style.display = 'none' // 隐藏错误的图片
|
||||
// 或者可以设置为默认图片
|
||||
// img.src = '/default-preview.png'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.task-card {
|
||||
background: $bg-card;
|
||||
border: 1px solid $border-primary;
|
||||
border-radius: $radius-lg;
|
||||
padding: $space-lg;
|
||||
<style scoped lang="scss">
|
||||
.task-card-container {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
background: #1e1e1e;
|
||||
border: 1px solid #333;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
|
||||
cursor: pointer;
|
||||
transition: all $transition-base;
|
||||
|
||||
&:hover {
|
||||
background: $bg-card-hover;
|
||||
border-color: #31f08b;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(49, 240, 139, 0.15);
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.4);
|
||||
border-color: #444;
|
||||
|
||||
.card-title {
|
||||
color: #31f08b;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.preview-cover {
|
||||
position: relative;
|
||||
height: 160px;
|
||||
overflow: hidden;
|
||||
background: #141414;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
transition: transform 0.5s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.task-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: $space-md;
|
||||
padding-bottom: $space-md;
|
||||
border-bottom: 1px solid $border-primary;
|
||||
.preview-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: linear-gradient(to bottom, rgba(0,0,0,0.1) 0%, rgba(0,0,0,0.4) 100%);
|
||||
opacity: 0.8;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.task-title {
|
||||
&:hover {
|
||||
img {
|
||||
transform: scale(1.08);
|
||||
}
|
||||
.preview-overlay {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.floating-badges {
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
left: 12px;
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
flex-wrap: wrap;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.badge-mini {
|
||||
padding: 4px 10px;
|
||||
border-radius: 6px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
background: rgba(0, 0, 0, 0.65);
|
||||
backdrop-filter: blur(8px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
letter-spacing: 0.5px;
|
||||
|
||||
&.subtask { background: rgba(24, 144, 255, 0.85); border-color: rgba(24, 144, 255, 0.3); }
|
||||
&.new { background: rgba(49, 240, 139, 0.85); color: #000; border-color: rgba(49, 240, 139, 0.3); }
|
||||
&.urgent { background: rgba(245, 34, 45, 0.85); border-color: rgba(245, 34, 45, 0.3); }
|
||||
&.progress { background: rgba(59, 130, 246, 0.85); border-color: rgba(59, 130, 246, 0.3); }
|
||||
&.review { background: rgba(245, 158, 11, 0.85); border-color: rgba(245, 158, 11, 0.3); }
|
||||
&.completed { background: rgba(16, 185, 129, 0.85); border-color: rgba(16, 185, 129, 0.3); }
|
||||
}
|
||||
|
||||
.card-body {
|
||||
padding: 20px;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.header-badges {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-bottom: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.badge-tag {
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.3px;
|
||||
|
||||
&.subtask { background: rgba(24, 144, 255, 0.1); color: #40a9ff; border: 1px solid rgba(24, 144, 255, 0.2); }
|
||||
&.new { background: rgba(49, 240, 139, 0.1); color: #31f08b; border: 1px solid rgba(49, 240, 139, 0.2); }
|
||||
&.urgent { background: rgba(245, 34, 45, 0.1); color: #ff4d4f; border: 1px solid rgba(245, 34, 45, 0.2); }
|
||||
&.high-score { background: rgba(250, 173, 20, 0.1); color: #ffc53d; border: 1px solid rgba(250, 173, 20, 0.2); }
|
||||
&.progress { background: rgba(59, 130, 246, 0.1); color: #60A5FA; border: 1px solid rgba(59, 130, 246, 0.2); }
|
||||
&.review { background: rgba(245, 158, 11, 0.1); color: #FBBF24; border: 1px solid rgba(245, 158, 11, 0.2); }
|
||||
&.completed { background: rgba(16, 185, 129, 0.1); color: #34D399; border: 1px solid rgba(16, 185, 129, 0.2); }
|
||||
&.published { background: rgba(139, 92, 246, 0.1); color: #A78BFA; border: 1px solid rgba(139, 92, 246, 0.2); }
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
line-height: 1.5;
|
||||
margin: 0 0 20px;
|
||||
// 限制2行
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
min-height: 48px;
|
||||
transition: color 0.3s ease;
|
||||
letter-spacing: 0.2px;
|
||||
}
|
||||
|
||||
.metrics-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 14px 16px;
|
||||
background: #262626;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 20px;
|
||||
border: 1px solid #333;
|
||||
}
|
||||
|
||||
.metric {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
position: relative;
|
||||
|
||||
&.divider {
|
||||
width: 1px;
|
||||
height: 24px;
|
||||
background: #3a3a3a;
|
||||
flex: 0 0 1px;
|
||||
margin: 0 12px;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 11px;
|
||||
color: #888;
|
||||
margin-bottom: 6px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.value {
|
||||
font-size: 13px;
|
||||
color: #e0e0e0;
|
||||
font-weight: 500;
|
||||
max-width: 90px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
line-height: 1.2;
|
||||
|
||||
&.score {
|
||||
color: #31f08b;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: $text-primary;
|
||||
margin: 0;
|
||||
flex: 1;
|
||||
margin-right: $space-md;
|
||||
font-weight: 700;
|
||||
text-shadow: 0 0 10px rgba(49, 240, 139, 0.2);
|
||||
}
|
||||
|
||||
.task-badges {
|
||||
display: flex;
|
||||
gap: $space-xs;
|
||||
flex-shrink: 0;
|
||||
|
||||
&.type {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.task-info {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: $space-sm;
|
||||
margin-bottom: $space-md;
|
||||
.details-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 12px 16px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
.detail-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
min-width: 0; // 防止溢出
|
||||
|
||||
.icon {
|
||||
color: #555;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.text {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.text-ellipsis {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 12px;
|
||||
color: $text-secondary;
|
||||
.card-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
margin-top: auto;
|
||||
|
||||
:deep(button) {
|
||||
flex: 1;
|
||||
height: 40px;
|
||||
border-radius: 6px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
border: none;
|
||||
letter-spacing: 0.5px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&.secondary {
|
||||
background: #2a2a2a;
|
||||
color: #ccc;
|
||||
border: 1px solid #3a3a3a;
|
||||
|
||||
&:hover {
|
||||
background: #333;
|
||||
color: #fff;
|
||||
border-color: #444;
|
||||
}
|
||||
|
||||
.value {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: $text-primary;
|
||||
|
||||
&.text-brand {
|
||||
color: #31f08b;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
&.primary {
|
||||
background: #31f08b;
|
||||
color: #000;
|
||||
box-shadow: 0 2px 8px rgba(49, 240, 139, 0.2);
|
||||
|
||||
&:hover {
|
||||
background: #4de19b;
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(49, 240, 139, 0.3);
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
background: #333;
|
||||
color: #666;
|
||||
cursor: not-allowed;
|
||||
border: 1px solid #444;
|
||||
box-shadow: none;
|
||||
|
||||
&:hover {
|
||||
transform: none;
|
||||
background: #333;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.task-details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $space-xs;
|
||||
margin-bottom: $space-md;
|
||||
padding: $space-sm;
|
||||
background: $bg-secondary;
|
||||
border-radius: $radius-md;
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 13px;
|
||||
|
||||
.label {
|
||||
color: $text-secondary;
|
||||
}
|
||||
|
||||
.value {
|
||||
color: $text-primary;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.detail-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: $space-xs;
|
||||
font-size: 13px;
|
||||
color: $text-secondary;
|
||||
|
||||
.icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.task-progress {
|
||||
margin-bottom: $space-md;
|
||||
|
||||
.progress-label {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: $space-xs;
|
||||
font-size: 13px;
|
||||
|
||||
span:first-child {
|
||||
color: $text-secondary;
|
||||
}
|
||||
|
||||
span:last-child {
|
||||
color: #31f08b;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.task-actions {
|
||||
display: flex;
|
||||
gap: $space-sm;
|
||||
margin-bottom: $space-md;
|
||||
|
||||
button {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.task-footer {
|
||||
display: flex;
|
||||
gap: $space-md;
|
||||
padding-top: $space-md;
|
||||
border-top: 1px solid $border-primary;
|
||||
|
||||
.footer-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: $space-xs;
|
||||
font-size: 12px;
|
||||
color: $text-secondary;
|
||||
}
|
||||
}
|
||||
|
||||
.badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 4px 12px;
|
||||
border-radius: $radius-full;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
|
||||
&.badge-new {
|
||||
background: rgba(49, 240, 139, 0.1);
|
||||
color: #31f08b;
|
||||
}
|
||||
|
||||
&.badge-progress {
|
||||
background: rgba(59, 130, 246, 0.1);
|
||||
color: #3B82F6;
|
||||
}
|
||||
|
||||
&.badge-review {
|
||||
background: rgba(245, 158, 11, 0.1);
|
||||
color: #F59E0B;
|
||||
}
|
||||
|
||||
&.badge-completed {
|
||||
background: rgba(49, 240, 139, 0.1);
|
||||
color: #31f08b;
|
||||
}
|
||||
|
||||
&.badge-rejected {
|
||||
background: rgba(239, 68, 68, 0.1);
|
||||
color: #EF4444;
|
||||
}
|
||||
|
||||
&.badge-urgent {
|
||||
background: rgba(245, 158, 11, 0.1);
|
||||
color: #F59E0B;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
@ -739,157 +739,25 @@
|
||||
</div>
|
||||
|
||||
<div v-else class="task-grid">
|
||||
<div
|
||||
<TaskCard
|
||||
v-for="task in inProgressTaskList"
|
||||
:key="task.id"
|
||||
class="task-card chart-card in-progress"
|
||||
:task="task"
|
||||
mode="in_progress"
|
||||
@click="handleTaskClick(task)"
|
||||
>
|
||||
<!-- 任务标题 -->
|
||||
<div class="task-header">
|
||||
<h3 class="task-title">{{ task.title }}</h3>
|
||||
<div class="task-badges">
|
||||
<span v-if="task.isSubTask" class="badge badge-subtask">
|
||||
<Icon name="layers" size="sm" color="#1890ff" style="margin-right: 4px; vertical-align: middle;" />
|
||||
子任务
|
||||
</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 class="badge badge-in-progress">
|
||||
<Icon name="clock" size="sm" color="#31f08b" style="margin-right: 4px; vertical-align: middle;" />
|
||||
进行中
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 流程阶段 -->
|
||||
<div v-if="getProcessStage(task)" class="process-stage">
|
||||
<span class="stage-icon">
|
||||
<Icon name="clock" size="sm" color="#31f08b" />
|
||||
</span>
|
||||
<span class="stage-text">{{ getProcessStage(task) }}</span>
|
||||
</div>
|
||||
|
||||
<!-- 任务信息 -->
|
||||
<div class="task-info">
|
||||
<div class="info-item">
|
||||
<span class="label">任务编号</span>
|
||||
<span class="value">{{ task.taskNo }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="label">任务类型</span>
|
||||
<span class="value">{{ getTaskTypeName(task.taskTypeId) }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="label">预估积分</span>
|
||||
<span class="value text-brand">{{ task.score }} 分</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="label">数量</span>
|
||||
<span class="value">{{ task.quantity || '--' }} 个</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 任务详情 -->
|
||||
<div class="task-details">
|
||||
<div class="detail-item">
|
||||
<span class="icon">
|
||||
<Icon name="user" size="sm" color="#31f08b" />
|
||||
</span>
|
||||
<span>发起人: {{ task.publisherName || '--' }}</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span class="icon">
|
||||
<Icon name="user" size="sm" color="#31f08b" />
|
||||
</span>
|
||||
<span>接单人: {{ task.receiverName || '--' }}</span>
|
||||
</div>
|
||||
<div v-if="task.brandName" class="detail-item">
|
||||
<span class="icon">
|
||||
<Icon name="tag" size="sm" color="#31f08b" />
|
||||
</span>
|
||||
<span>品牌: {{ task.brandName }}</span>
|
||||
</div>
|
||||
<div v-if="task.categoryName" class="detail-item">
|
||||
<span class="icon">
|
||||
<Icon name="folder" size="sm" color="#31f08b" />
|
||||
</span>
|
||||
<span>类目: {{ task.categoryName }}</span>
|
||||
</div>
|
||||
<div v-if="task.platformName" class="detail-item">
|
||||
<span class="icon">
|
||||
<Icon name="monitor" size="sm" color="#31f08b" />
|
||||
</span>
|
||||
<span>平台: {{ task.platformName }}</span>
|
||||
</div>
|
||||
<div v-if="task.spuInfo" class="detail-item">
|
||||
<span class="icon">
|
||||
<Icon name="package" size="sm" color="#31f08b" />
|
||||
</span>
|
||||
<span>SPU: {{ task.spuInfo }}</span>
|
||||
</div>
|
||||
<div v-if="task.taskSubType" class="detail-item">
|
||||
<span class="icon">
|
||||
<Icon name="list" size="sm" color="#31f08b" />
|
||||
</span>
|
||||
<span>子类型: {{ task.taskSubType }}</span>
|
||||
</div>
|
||||
<div v-if="task.attachmentUrl" class="detail-item">
|
||||
<span class="icon">
|
||||
<Icon name="paperclip" size="sm" color="#31f08b" />
|
||||
</span>
|
||||
<span>附件:
|
||||
<a
|
||||
v-for="(url, index) in (task.attachmentUrl || '').split(',').filter(u => u.trim())"
|
||||
:key="index"
|
||||
:href="url.trim()"
|
||||
target="_blank"
|
||||
class="attachment-link"
|
||||
@click.stop
|
||||
>
|
||||
附件{{ index + 1 }}
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 进度条 -->
|
||||
<div class="task-progress">
|
||||
<div class="progress-label">
|
||||
<span>任务进度</span>
|
||||
<span>{{ task.progress || 0 }}%</span>
|
||||
</div>
|
||||
<a-progress
|
||||
:percent="task.progress || 0"
|
||||
:stroke-color="{ '0%': '#31f08b', '100%': '#31f08b' }"
|
||||
:show-info="false"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<div class="task-actions">
|
||||
<a-button size="small" @click="handleViewTaskDetail(task)">查看详情</a-button>
|
||||
<a-button v-if="canUpdateProgress(task)" type="primary" size="small" @click="handleUpdateProgress(task)">
|
||||
更新进度
|
||||
</a-button>
|
||||
<a-button v-if="canCompleteTask(task)" type="primary" size="small" @click="handleCompleteTask(task)">
|
||||
完结任务
|
||||
</a-button>
|
||||
</div>
|
||||
|
||||
<!-- 时间信息 -->
|
||||
<div class="task-footer">
|
||||
<div class="footer-item">
|
||||
<Icon name="calendar" size="sm" color="#31f08b" />
|
||||
<span style="color: #ffffff;">发布: {{ formatDate(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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<template #actions>
|
||||
<button class="action-btn secondary" @click.stop="handleTaskClick(task)">
|
||||
查看详情
|
||||
</button>
|
||||
<button
|
||||
class="action-btn primary"
|
||||
@click.stop="handleSubmitTask(task)"
|
||||
>
|
||||
提交任务
|
||||
</button>
|
||||
</template>
|
||||
</TaskCard>
|
||||
</div>
|
||||
|
||||
<!-- 分页 -->
|
||||
@ -1063,146 +931,19 @@
|
||||
</div>
|
||||
|
||||
<div v-else class="task-grid">
|
||||
<div
|
||||
<TaskCard
|
||||
v-for="task in completedTaskList"
|
||||
:key="task.id"
|
||||
class="task-card chart-card completed"
|
||||
:task="task"
|
||||
mode="completed"
|
||||
@click="handleTaskClick(task)"
|
||||
style="cursor: pointer;"
|
||||
>
|
||||
<!-- 任务标题 -->
|
||||
<div class="task-header">
|
||||
<h3 class="task-title">{{ task.title }}</h3>
|
||||
<div class="task-badges">
|
||||
<span class="badge badge-completed">
|
||||
<Icon name="check-circle" size="sm" color="#31f08b" style="margin-right: 4px; vertical-align: middle;" />
|
||||
已完成
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 流程阶段 -->
|
||||
<div v-if="getProcessStage(task)" class="process-stage">
|
||||
<span class="stage-icon">
|
||||
<Icon name="clock" size="sm" color="#31f08b" />
|
||||
</span>
|
||||
<span class="stage-text">{{ getProcessStage(task) }}</span>
|
||||
</div>
|
||||
|
||||
<!-- 任务信息 -->
|
||||
<div class="task-info">
|
||||
<div class="info-item">
|
||||
<span class="label">任务编号</span>
|
||||
<span class="value">{{ task.taskNo }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="label">任务类型</span>
|
||||
<span class="value">{{ getTaskTypeName(task.taskTypeId) }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="label">获得积分</span>
|
||||
<span class="value text-brand">{{ task.score }} 分</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="label">数量</span>
|
||||
<span class="value">{{ task.quantity || '--' }} 个</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 任务详情 -->
|
||||
<div class="task-details">
|
||||
<div class="detail-item">
|
||||
<span class="icon">
|
||||
<Icon name="user" size="sm" color="#31f08b" />
|
||||
</span>
|
||||
<span>发起人: {{ task.publisherName || '--' }}</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span class="icon">
|
||||
<Icon name="user" size="sm" color="#31f08b" />
|
||||
</span>
|
||||
<span>完成人: {{ task.receiverName || '--' }}</span>
|
||||
</div>
|
||||
<div v-if="task.brandName" class="detail-item">
|
||||
<span class="icon">
|
||||
<Icon name="tag" size="sm" color="#31f08b" />
|
||||
</span>
|
||||
<span>品牌: {{ task.brandName }}</span>
|
||||
</div>
|
||||
<div v-if="task.categoryName" class="detail-item">
|
||||
<span class="icon">
|
||||
<Icon name="folder" size="sm" color="#31f08b" />
|
||||
</span>
|
||||
<span>类目: {{ task.categoryName }}</span>
|
||||
</div>
|
||||
<div v-if="task.platformName" class="detail-item">
|
||||
<span class="icon">
|
||||
<Icon name="monitor" size="sm" color="#31f08b" />
|
||||
</span>
|
||||
<span>平台: {{ task.platformName }}</span>
|
||||
</div>
|
||||
<div v-if="task.spuInfo" class="detail-item">
|
||||
<span class="icon">
|
||||
<Icon name="package" size="sm" color="#31f08b" />
|
||||
</span>
|
||||
<span>SPU: {{ task.spuInfo }}</span>
|
||||
</div>
|
||||
<div v-if="task.taskSubType" class="detail-item">
|
||||
<span class="icon">
|
||||
<Icon name="list" size="sm" color="#31f08b" />
|
||||
</span>
|
||||
<span>子类型: {{ task.taskSubType }}</span>
|
||||
</div>
|
||||
<div v-if="task.deliveryPath" class="detail-item">
|
||||
<span class="icon">
|
||||
<Icon name="link" size="sm" color="#31f08b" />
|
||||
</span>
|
||||
<span>交付: {{ task.deliveryPath }}</span>
|
||||
</div>
|
||||
<div v-if="task.attachmentUrl" class="detail-item">
|
||||
<span class="icon">
|
||||
<Icon name="paperclip" size="sm" color="#31f08b" />
|
||||
</span>
|
||||
<span>附件:
|
||||
<a
|
||||
v-for="(url, index) in (task.attachmentUrl || '').split(',').filter(u => u.trim())"
|
||||
:key="index"
|
||||
:href="url.trim()"
|
||||
target="_blank"
|
||||
class="attachment-link"
|
||||
@click.stop
|
||||
>
|
||||
附件{{ index + 1 }}
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 进度条(已完成任务显示100%) -->
|
||||
<div class="task-progress">
|
||||
<div class="progress-label">
|
||||
<span>任务进度</span>
|
||||
<span>{{ task.progress || 100 }}%</span>
|
||||
</div>
|
||||
<a-progress
|
||||
:percent="task.progress || 100"
|
||||
:stroke-color="{ '0%': '#31f08b', '100%': '#31f08b' }"
|
||||
:show-info="false"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 时间信息 -->
|
||||
<div class="task-footer">
|
||||
<div class="footer-item">
|
||||
<Icon name="calendar" size="sm" color="#31f08b" />
|
||||
<span style="color: #ffffff;">发布: {{ formatDate(task.createTime) }}</span>
|
||||
</div>
|
||||
<div class="footer-item">
|
||||
<Icon name="check" size="sm" color="#31f08b" />
|
||||
<span style="color: #ffffff;">完成: {{ formatDate(task.completeTime) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<template #actions>
|
||||
<button class="action-btn secondary" @click.stop="handleTaskClick(task)">
|
||||
查看详情
|
||||
</button>
|
||||
</template>
|
||||
</TaskCard>
|
||||
</div>
|
||||
|
||||
<!-- 分页 -->
|
||||
@ -1383,190 +1124,33 @@
|
||||
</div>
|
||||
|
||||
<div v-else class="task-grid">
|
||||
<div
|
||||
<TaskCard
|
||||
v-for="task in publishedTaskList"
|
||||
:key="task.id"
|
||||
class="task-card chart-card"
|
||||
:class="[`status-${task.status}`]"
|
||||
:task="task"
|
||||
mode="published"
|
||||
@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.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;" />
|
||||
紧急
|
||||
</span>
|
||||
</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">
|
||||
<Icon name="clock" size="sm" color="#31f08b" />
|
||||
</span>
|
||||
<span class="stage-text">{{ getProcessStage(task) }}</span>
|
||||
</div>
|
||||
|
||||
<!-- 任务信息 -->
|
||||
<div class="task-info">
|
||||
<div class="info-item">
|
||||
<span class="label">任务编号</span>
|
||||
<span class="value">{{ task.taskNo }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="label">任务类型</span>
|
||||
<span class="value">{{ getTaskTypeName(task.taskTypeId) }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="label">积分</span>
|
||||
<span class="value text-brand">{{ task.score }} 分</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="label">数量</span>
|
||||
<span class="value">{{ task.quantity || '--' }} 个</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 任务详情 -->
|
||||
<div class="task-details">
|
||||
<div class="detail-item">
|
||||
<span class="icon">
|
||||
<Icon name="user" size="sm" color="#31f08b" />
|
||||
</span>
|
||||
<span>处理人: {{ task.receiverName || '待接单' }}</span>
|
||||
</div>
|
||||
<!-- 子任务进度(流水线任务) -->
|
||||
<div v-if="task.isPipeline === 1 && task.subTaskTotal" class="detail-item subtask-progress">
|
||||
<span class="icon">
|
||||
<Icon name="layers" size="sm" color="#31f08b" />
|
||||
</span>
|
||||
<span class="subtask-stats">
|
||||
子任务:
|
||||
<span class="stat completed">已完成 {{ task.subTaskCompleted || 0 }}</span>
|
||||
<span class="stat in-progress">进行中 {{ task.subTaskInProgress || 0 }}</span>
|
||||
<span class="stat pending">待接单 {{ task.subTaskPending || 0 }}</span>
|
||||
<span class="stat total">/ 共 {{ task.subTaskTotal }}</span>
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="task.brandName" class="detail-item">
|
||||
<span class="icon">
|
||||
<Icon name="tag" size="sm" color="#31f08b" />
|
||||
</span>
|
||||
<span>品牌: {{ task.brandName }}</span>
|
||||
</div>
|
||||
<div v-if="task.categoryName" class="detail-item">
|
||||
<span class="icon">
|
||||
<Icon name="folder" size="sm" color="#31f08b" />
|
||||
</span>
|
||||
<span>类目: {{ task.categoryName }}</span>
|
||||
</div>
|
||||
<div v-if="task.platformName" class="detail-item">
|
||||
<span class="icon">
|
||||
<Icon name="monitor" size="sm" color="#31f08b" />
|
||||
</span>
|
||||
<span>平台: {{ task.platformName }}</span>
|
||||
</div>
|
||||
<div v-if="task.spuInfo" class="detail-item">
|
||||
<span class="icon">
|
||||
<Icon name="package" size="sm" color="#31f08b" />
|
||||
</span>
|
||||
<span>SPU: {{ task.spuInfo }}</span>
|
||||
</div>
|
||||
<div v-if="task.taskSubType" class="detail-item">
|
||||
<span class="icon">
|
||||
<Icon name="list" size="sm" color="#31f08b" />
|
||||
</span>
|
||||
<span>子类型: {{ task.taskSubType }}</span>
|
||||
</div>
|
||||
<div v-if="task.attachmentUrl" class="detail-item">
|
||||
<span class="icon">
|
||||
<Icon name="paperclip" size="sm" color="#31f08b" />
|
||||
</span>
|
||||
<span>附件:
|
||||
<a
|
||||
v-for="(url, index) in (task.attachmentUrl || '').split(',').filter(u => u.trim())"
|
||||
:key="index"
|
||||
:href="url.trim()"
|
||||
target="_blank"
|
||||
class="attachment-link"
|
||||
@click.stop
|
||||
>
|
||||
附件{{ index + 1 }}
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 进度条(进行中和已完成的任务显示) -->
|
||||
<div v-if="task.status === 'in_progress' || task.status === 'completed'" class="task-progress">
|
||||
<div class="progress-label">
|
||||
<span>任务进度</span>
|
||||
<span>{{ task.progress || 0 }}%</span>
|
||||
</div>
|
||||
<a-progress
|
||||
:percent="task.progress || 0"
|
||||
:stroke-color="{ '0%': '#31f08b', '100%': '#31f08b' }"
|
||||
:show-info="false"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 时间信息 -->
|
||||
<div class="task-footer">
|
||||
<div class="footer-item">
|
||||
<Icon name="calendar" size="sm" color="#31f08b" />
|
||||
<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;">截止: {{ 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) || 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"
|
||||
<template #actions>
|
||||
<!-- 被驳回或草稿任务的操作按钮 -->
|
||||
<button
|
||||
v-if="['rejected', 'draft'].includes(task.status) || task.approvalStatus?.includes('rejected')"
|
||||
class="action-btn primary"
|
||||
@click.stop="handleEditRejectedTask(task)"
|
||||
style="width: 100%;">
|
||||
<Icon name="edit" size="sm" style="margin-right: 4px;" />
|
||||
>
|
||||
重新编辑提交
|
||||
</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"
|
||||
</button>
|
||||
|
||||
<!-- 其他状态:查看详情 -->
|
||||
<button
|
||||
v-else
|
||||
class="action-btn secondary"
|
||||
@click.stop="handleTaskClick(task)"
|
||||
style="width: 100%;">
|
||||
<Icon name="eye" size="sm" style="margin-right: 4px;" />
|
||||
>
|
||||
查看详情
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</template>
|
||||
</TaskCard>
|
||||
</div>
|
||||
|
||||
<!-- 分页 -->
|
||||
@ -2198,6 +1782,7 @@ import { useRouter } from 'vue-router'
|
||||
import { message, Modal, InputNumber, Textarea, Radio, RadioGroup, Button, Select, Space, Tag } from 'ant-design-vue'
|
||||
import DefaultLayout from '@/layouts/DefaultLayout.vue'
|
||||
import Icon from '@/components/Icon.vue'
|
||||
import TaskCard from '@/components/TaskCard.vue'
|
||||
import * as taskApi from '@/api/task'
|
||||
import * as userApi from '@/api/user'
|
||||
import type { Task, TaskApprovalDTO, TaskProduct } from '@/api/task'
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user