Files
one-pipe-system/src/views/asset-management/task-management/task-detail/index.vue
sexygoat 1990a3ccba
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 4m4s
fix: bug
2026-05-11 15:51:50 +08:00

296 lines
9.1 KiB
Vue

<template>
<ArtTableFullScreen>
<div class="task-detail-page" id="table-full-screen">
<ElCard shadow="never" class="art-table-card">
<!-- 页面头部 -->
<div class="detail-header">
<ElButton @click="goBack">
<template #icon>
<ElIcon><ArrowLeft /></ElIcon>
</template>
返回
</ElButton>
<h2 class="detail-title">任务详情</h2>
</div>
<!-- 使用 DetailPage 组件显示任务信息 -->
<DetailPage v-if="taskDetail" :sections="detailSections" :data="taskDetail" />
<!-- 失败记录 -->
<div class="failure-section" v-if="taskDetail?.fail_count && taskDetail.fail_count > 0">
<ElDivider content-position="left">
<span class="section-title">失败记录 ({{ taskDetail.fail_count }})</span>
</ElDivider>
<ElTable :data="taskDetail.failed_items" border style="width: 100%">
<ElTableColumn prop="line" label="行号" width="100" />
<ElTableColumn v-if="taskType === 'card'" prop="iccid" label="ICCID" min-width="180" />
<ElTableColumn v-else prop="virtual_no" label="设备号" min-width="180" />
<ElTableColumn prop="reason" label="失败原因" min-width="300" />
</ElTable>
</div>
<!-- 跳过记录 -->
<div class="skipped-section" v-if="taskDetail?.skip_count && taskDetail.skip_count > 0">
<ElDivider content-position="left">
<span class="section-title">跳过记录 ({{ taskDetail.skip_count }})</span>
</ElDivider>
<ElTable :data="taskDetail.skipped_items" border style="width: 100%">
<ElTableColumn prop="line" label="行号" width="100" />
<ElTableColumn v-if="taskType === 'card'" prop="iccid" label="ICCID" min-width="180" />
<ElTableColumn v-else prop="virtual_no" label="设备号" min-width="180" />
<ElTableColumn prop="reason" label="跳过原因" min-width="300" />
</ElTable>
</div>
</ElCard>
</div>
</ArtTableFullScreen>
</template>
<script setup lang="ts">
import { computed, h } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { ref, onMounted } from 'vue'
import { CardService, DeviceService } from '@/api/modules'
import { ElDivider, ElIcon, ElMessage, ElTable, ElTableColumn, ElTag } from 'element-plus'
import { ArrowLeft } from '@element-plus/icons-vue'
import { formatDateTime } from '@/utils/business/format'
import type { IotCardImportTaskDetail, IotCardImportTaskStatus } from '@/types/api/card'
import type { DeviceImportTaskDetail } from '@/types/api/device'
import type { DetailSection } from '@/components/common/DetailPage.vue'
import DetailPage from '@/components/common/DetailPage.vue'
defineOptions({ name: 'TaskDetail' })
const router = useRouter()
const route = useRoute()
type TaskType = 'card' | 'device'
type TaskDetail = IotCardImportTaskDetail | DeviceImportTaskDetail
const taskDetail = ref<TaskDetail | null>(null)
const loading = ref(false)
const taskType = ref<TaskType>('card')
// 获取状态标签类型
const getStatusType = (status?: IotCardImportTaskStatus) => {
if (!status) return 'info'
switch (status) {
case 1:
return 'info'
case 2:
return 'warning'
case 3:
return 'success'
case 4:
return 'danger'
default:
return 'info'
}
}
// 详情页配置
const detailSections = computed((): DetailSection[] => {
return [
{
title: '任务基本信息',
fields: [
{ label: '任务编号', prop: 'task_no', formatter: (value: string) => value || '-' },
{
label: '任务类型',
render: () => {
return h(
ElTag,
{ type: taskType.value === 'device' ? 'warning' : 'primary', size: 'small' },
() => (taskType.value === 'device' ? '设备导入' : 'ICCID导入')
)
}
},
{ label: '批次号', prop: 'batch_no', formatter: (value: string) => value || '-' },
{ label: '文件名', prop: 'file_name', formatter: (value: string) => value || '-' },
{
label: '任务状态',
render: (data: TaskDetail) => {
return h(ElTag, { type: getStatusType(data.status) }, () => data.status_text)
}
},
...(taskType.value === 'card'
? [
{
label: '运营商',
prop: 'carrier_name',
formatter: (value: any) => value || '-'
},
{
label: '卡业务类型',
render: (data: any) => {
if (!data.card_category) return h('span', '-')
return h(
ElTag,
{
type: data.card_category === 'industry' ? 'warning' : 'success',
size: 'small'
},
() =>
data.card_category === 'normal'
? '普通卡'
: data.card_category === 'industry'
? '行业卡'
: data.card_category
)
}
}
]
: []),
{
label: '创建时间',
prop: 'created_at',
formatter: (value: Date) => (value ? formatDateTime(value) : '-')
},
{
label: '开始处理时间',
prop: 'started_at',
formatter: (value: Date) => (value ? formatDateTime(value) : '-')
},
{
label: '完成时间',
prop: 'completed_at',
formatter: (value: Date) => (value ? formatDateTime(value) : '-')
},
...(taskDetail.value?.error_message
? [
{
label: '错误信息',
prop: 'error_message',
fullWidth: true,
render: (data: TaskDetail) => {
return h(
'span',
{ style: { color: 'var(--el-color-danger)' } },
data.error_message || ''
)
}
}
]
: [])
]
},
{
title: '统计信息',
columns: 2,
fields: [
{
label: '总数',
render: (data: TaskDetail) => {
return h(
'span',
{
style: {
fontSize: '16px',
fontWeight: 'bold',
color: 'var(--el-color-primary)'
}
},
String(data.total_count || 0)
)
}
},
{
label: '成功数',
render: (data: TaskDetail) => {
return h(ElTag, { type: 'success' }, () => String(data.success_count || 0))
}
},
{
label: '失败数',
render: (data: TaskDetail) => {
return h(ElTag, { type: 'danger' }, () => String(data.fail_count || 0))
}
},
{
label: '跳过数',
render: (data: TaskDetail) => {
return h(ElTag, { type: 'warning' }, () => String(data.skip_count || 0))
}
}
]
}
]
})
// 返回列表
const goBack = () => {
router.back()
}
// 获取任务详情
const getTaskDetail = async () => {
const taskId = route.query.id
const queryTaskType = route.query.task_type as TaskType | undefined
if (!taskId) {
ElMessage.error('缺少任务ID参数')
goBack()
return
}
// 设置任务类型
if (queryTaskType) {
taskType.value = queryTaskType
}
loading.value = true
try {
if (taskType.value === 'device') {
// 获取设备导入任务详情
const res = await DeviceService.getImportTaskDetail(Number(taskId))
if (res.code === 0) {
taskDetail.value = res.data
}
} else {
// 获取ICCID导入任务详情
const res = await CardService.getIotCardImportTaskDetail(Number(taskId))
if (res.code === 0) {
taskDetail.value = res.data
}
}
} catch (error) {
console.error(error)
console.log('获取任务详情失败')
} finally {
loading.value = false
}
}
onMounted(() => {
getTaskDetail()
})
</script>
<style lang="scss" scoped>
.task-detail-page {
.detail-header {
display: flex;
gap: 16px;
align-items: center;
padding-bottom: 16px;
.detail-title {
margin: 0;
font-size: 20px;
font-weight: 600;
color: var(--el-text-color-primary);
}
}
.failure-section,
.skipped-section {
margin-top: 20px;
.section-title {
font-size: 16px;
font-weight: bold;
}
}
}
</style>