This commit is contained in:
492
src/views/polling-management/priority-queue/index.vue
Normal file
492
src/views/polling-management/priority-queue/index.vue
Normal file
@@ -0,0 +1,492 @@
|
||||
<template>
|
||||
<ArtTableFullScreen>
|
||||
<div class="priority-queue-page" id="table-full-screen">
|
||||
<!-- 搜索栏 -->
|
||||
<ArtSearchBar
|
||||
v-model:filter="searchForm"
|
||||
:items="searchFormItems"
|
||||
:show-expand="false"
|
||||
@reset="handleReset"
|
||||
@search="handleSearch"
|
||||
/>
|
||||
|
||||
<el-card shadow="never" class="art-table-card">
|
||||
<!-- 表格头部 -->
|
||||
<ArtTableHeader
|
||||
:columnList="columnOptions"
|
||||
v-model:columns="columnChecks"
|
||||
@refresh="handleRefresh"
|
||||
>
|
||||
<template #left>
|
||||
<el-button type="primary" @click="showCreateDialog">人工优先入队</el-button>
|
||||
</template>
|
||||
</ArtTableHeader>
|
||||
|
||||
<!-- 表格 -->
|
||||
<ArtTable
|
||||
ref="tableRef"
|
||||
row-key="id"
|
||||
:loading="loading"
|
||||
:data="tableData"
|
||||
:currentPage="pagination.page"
|
||||
:pageSize="pagination.page_size"
|
||||
:total="pagination.total"
|
||||
:marginTop="10"
|
||||
:actions="getActions"
|
||||
:actionsWidth="100"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
>
|
||||
<template #default>
|
||||
<el-table-column v-for="col in columns" :key="col.prop || col.type" v-bind="col" />
|
||||
</template>
|
||||
</ArtTable>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<!-- 人工优先入队 -->
|
||||
<el-dialog
|
||||
v-model="createDialogVisible"
|
||||
title="人工优先入队"
|
||||
width="520px"
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<el-form ref="createFormRef" :model="createForm" :rules="createRules" label-width="100px">
|
||||
<el-form-item label="卡ICCID" prop="card_id">
|
||||
<el-select
|
||||
v-model="createForm.card_id"
|
||||
placeholder="请输入ICCID搜索"
|
||||
filterable
|
||||
remote
|
||||
:remote-method="remoteSearchCard"
|
||||
clearable
|
||||
no-data-text="暂无卡数据,请输入ICCID搜索"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="card in cardList"
|
||||
:key="card.id"
|
||||
:label="card.iccid"
|
||||
:value="card.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="入队原因" prop="reason">
|
||||
<el-input
|
||||
v-model="createForm.reason"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
maxlength="500"
|
||||
show-word-limit
|
||||
placeholder="请输入入队原因(最多500字)"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="createDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="createLoading" @click="handleCreateSubmit">
|
||||
确定
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 入队结果 -->
|
||||
<el-dialog v-model="resultDialogVisible" title="入队结果" width="620px">
|
||||
<div class="result-summary">
|
||||
<el-tag type="success" class="result-item">新建 {{ createResult?.created_count ?? 0 }} 条</el-tag>
|
||||
<el-tag type="warning" class="result-item">合并 {{ createResult?.merged_count ?? 0 }} 条</el-tag>
|
||||
<span class="result-item">
|
||||
任务类型:{{ (createResult?.task_type_names || []).join('、') || '-' }}
|
||||
</span>
|
||||
</div>
|
||||
<el-table :data="createResult?.items || []" border stripe size="small" max-height="360">
|
||||
<el-table-column prop="iccid" label="卡ICCID" min-width="160" show-overflow-tooltip />
|
||||
<el-table-column prop="task_type_name" label="任务类型" min-width="120" />
|
||||
<el-table-column prop="status_name" label="状态" width="100" />
|
||||
<el-table-column prop="trigger_count" label="触发次数" width="100" />
|
||||
</el-table>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 优先轮询项详情 -->
|
||||
<el-dialog
|
||||
v-model="detailDialogVisible"
|
||||
title="优先轮询项详情"
|
||||
width="640px"
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<el-descriptions v-if="detailData" :column="2" border>
|
||||
<el-descriptions-item label="ID">{{ detailData.id }}</el-descriptions-item>
|
||||
<el-descriptions-item label="卡ICCID">{{ detailData.iccid }}</el-descriptions-item>
|
||||
<el-descriptions-item label="任务类型">
|
||||
{{ detailData.task_type_name }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="状态">
|
||||
<el-tag :type="getStatusTagType(detailData.status)">{{
|
||||
detailData.status_name
|
||||
}}</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="触发类型">
|
||||
{{ detailData.trigger_type_name }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="触发次数">{{ detailData.trigger_count }}</el-descriptions-item>
|
||||
<el-descriptions-item label="尝试次数">{{ detailData.attempt_count }}</el-descriptions-item>
|
||||
<el-descriptions-item label="执行结果">
|
||||
{{ detailData.result_name }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="失败原因" :span="2">
|
||||
{{ detailData.failure_reason || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="人工操作者">
|
||||
{{ detailData.manual_operator_name || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="入队原因">
|
||||
{{ detailData.manual_reason || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="认领时间">
|
||||
{{ formatDateTime(detailData.claimed_at) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="最后触发时间">
|
||||
{{ formatDateTime(detailData.last_triggered_at) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">
|
||||
{{ formatDateTime(detailData.created_at) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="更新时间">
|
||||
{{ formatDateTime(detailData.updated_at) }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-dialog>
|
||||
</ArtTableFullScreen>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, onMounted, h } from 'vue'
|
||||
import { ElMessage, ElTag, type FormInstance, type FormRules } from 'element-plus'
|
||||
import { PollingPriorityQueueService, CardService } from '@/api/modules'
|
||||
import type {
|
||||
PollingPriorityItem,
|
||||
PollingPriorityItemQueryParams,
|
||||
CreatePollingPriorityItemResponse
|
||||
} from '@/types/api'
|
||||
import type { SearchFormItem } from '@/types'
|
||||
import { formatDateTime } from '@/utils/business/format'
|
||||
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
||||
import {
|
||||
POLLING_PRIORITY_TASK_TYPE_OPTIONS,
|
||||
POLLING_PRIORITY_STATUS_OPTIONS,
|
||||
POLLING_PRIORITY_TRIGGER_TYPE_OPTIONS,
|
||||
getPollingPriorityResultName
|
||||
} from '@/config/constants'
|
||||
|
||||
defineOptions({ name: 'PollingPriorityQueue' })
|
||||
|
||||
const loading = ref(false)
|
||||
const tableData = ref<PollingPriorityItem[]>([])
|
||||
const tableRef = ref()
|
||||
|
||||
// 分页数据
|
||||
const pagination = reactive({
|
||||
page: 1,
|
||||
page_size: 20,
|
||||
total: 0
|
||||
})
|
||||
|
||||
// 搜索表单
|
||||
const searchForm = reactive({
|
||||
card_id: undefined as number | undefined,
|
||||
task_type: undefined as string | undefined,
|
||||
status: undefined as string | undefined,
|
||||
trigger_type: undefined as string | undefined
|
||||
})
|
||||
|
||||
// 搜索表单配置
|
||||
const searchFormItems: SearchFormItem[] = [
|
||||
{
|
||||
label: '卡ID',
|
||||
prop: 'card_id',
|
||||
type: 'input',
|
||||
config: { clearable: true, placeholder: '请输入卡ID' }
|
||||
},
|
||||
{
|
||||
label: '任务类型',
|
||||
prop: 'task_type',
|
||||
type: 'select',
|
||||
options: POLLING_PRIORITY_TASK_TYPE_OPTIONS,
|
||||
config: { clearable: true, placeholder: '请选择任务类型' }
|
||||
},
|
||||
{
|
||||
label: '状态',
|
||||
prop: 'status',
|
||||
type: 'select',
|
||||
options: POLLING_PRIORITY_STATUS_OPTIONS,
|
||||
config: { clearable: true, placeholder: '请选择状态' }
|
||||
},
|
||||
{
|
||||
label: '触发类型',
|
||||
prop: 'trigger_type',
|
||||
type: 'select',
|
||||
options: POLLING_PRIORITY_TRIGGER_TYPE_OPTIONS,
|
||||
config: { clearable: true, placeholder: '请选择触发类型' }
|
||||
}
|
||||
]
|
||||
|
||||
const getStatusTagType = (status: string) => {
|
||||
const types: Record<string, 'success' | 'info' | 'warning' | 'danger'> = {
|
||||
pending: 'info',
|
||||
processing: 'warning',
|
||||
completed: 'success',
|
||||
failed: 'danger'
|
||||
}
|
||||
return types[status] || 'info'
|
||||
}
|
||||
|
||||
const getResultTagType = (result: string) => {
|
||||
if (result === 'success') return 'success'
|
||||
if (result === 'failed') return 'danger'
|
||||
return 'info'
|
||||
}
|
||||
|
||||
// 动态列配置
|
||||
const { columnChecks, columns } = useCheckedColumns(() => [
|
||||
{
|
||||
prop: 'id',
|
||||
label: 'ID',
|
||||
width: 80
|
||||
},
|
||||
{
|
||||
prop: 'iccid',
|
||||
label: '卡ICCID',
|
||||
minWidth: 170,
|
||||
showOverflowTooltip: true
|
||||
},
|
||||
{
|
||||
prop: 'task_type_name',
|
||||
label: '任务类型',
|
||||
width: 130,
|
||||
formatter: (row: PollingPriorityItem) => row.task_type_name
|
||||
},
|
||||
{
|
||||
prop: 'status',
|
||||
label: '状态',
|
||||
width: 100,
|
||||
slots: {
|
||||
default: ({ row }: any) =>
|
||||
h(ElTag, { type: getStatusTagType(row.status) }, () => row.status_name)
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: 'trigger_type_name',
|
||||
label: '触发类型',
|
||||
width: 130,
|
||||
formatter: (row: PollingPriorityItem) => row.trigger_type_name
|
||||
},
|
||||
{
|
||||
prop: 'trigger_count',
|
||||
label: '触发次数',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
prop: 'attempt_count',
|
||||
label: '尝试次数',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
prop: 'result',
|
||||
label: '执行结果',
|
||||
width: 110,
|
||||
slots: {
|
||||
default: ({ row }: any) =>
|
||||
h(ElTag, { type: getResultTagType(row.result) }, () =>
|
||||
getPollingPriorityResultName(row.result)
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: 'manual_operator_name',
|
||||
label: '操作者',
|
||||
width: 120,
|
||||
formatter: (row: PollingPriorityItem) => row.manual_operator_name || '-'
|
||||
},
|
||||
{
|
||||
prop: 'created_at',
|
||||
label: '创建时间',
|
||||
width: 180,
|
||||
formatter: (row: PollingPriorityItem) => formatDateTime(row.created_at)
|
||||
}
|
||||
])
|
||||
|
||||
const columnOptions = computed(() =>
|
||||
columns.value.map((col) => ({
|
||||
label: col.label,
|
||||
prop: col.prop || col.label,
|
||||
visible: true
|
||||
}))
|
||||
)
|
||||
|
||||
const getActions = () => [{ label: '详情', handler: showDetail, type: 'primary' as const }]
|
||||
|
||||
const loadData = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const queryParams: PollingPriorityItemQueryParams = {
|
||||
page: pagination.page,
|
||||
page_size: pagination.page_size,
|
||||
card_id: searchForm.card_id,
|
||||
task_type: searchForm.task_type as never,
|
||||
status: searchForm.status as never,
|
||||
trigger_type: searchForm.trigger_type as never
|
||||
}
|
||||
const { data } = await PollingPriorityQueueService.getPriorityItems(queryParams)
|
||||
tableData.value = data.items || []
|
||||
pagination.total = data.total || 0
|
||||
} catch (error) {
|
||||
console.error('加载优先轮询项列表失败', error)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleSearch = () => {
|
||||
pagination.page = 1
|
||||
loadData()
|
||||
}
|
||||
|
||||
const handleReset = () => {
|
||||
Object.assign(searchForm, {
|
||||
card_id: undefined,
|
||||
task_type: undefined,
|
||||
status: undefined,
|
||||
trigger_type: undefined
|
||||
})
|
||||
pagination.page = 1
|
||||
loadData()
|
||||
}
|
||||
|
||||
const handleRefresh = () => {
|
||||
loadData()
|
||||
}
|
||||
|
||||
const handleSizeChange = (size: number) => {
|
||||
pagination.page_size = size
|
||||
loadData()
|
||||
}
|
||||
|
||||
const handleCurrentChange = (page: number) => {
|
||||
pagination.page = page
|
||||
loadData()
|
||||
}
|
||||
|
||||
// 人工优先入队
|
||||
const createDialogVisible = ref(false)
|
||||
const createLoading = ref(false)
|
||||
const createFormRef = ref<FormInstance>()
|
||||
const createResult = ref<CreatePollingPriorityItemResponse | null>(null)
|
||||
const resultDialogVisible = ref(false)
|
||||
const cardList = ref<Array<{ id: number; iccid: string }>>([])
|
||||
|
||||
const createForm = reactive({
|
||||
card_id: undefined as number | undefined,
|
||||
reason: ''
|
||||
})
|
||||
|
||||
const createRules: FormRules = {
|
||||
card_id: [{ required: true, message: '请选择卡', trigger: 'change' }],
|
||||
reason: [
|
||||
{ required: true, message: '请输入入队原因', trigger: 'blur' },
|
||||
{ max: 500, message: '入队原因不能超过500个字符', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
|
||||
const showCreateDialog = () => {
|
||||
createForm.card_id = undefined
|
||||
createForm.reason = ''
|
||||
createResult.value = null
|
||||
createDialogVisible.value = true
|
||||
cardList.value = []
|
||||
loadCards()
|
||||
}
|
||||
|
||||
const loadCards = async (iccid?: string) => {
|
||||
try {
|
||||
const { data } = await CardService.getStandaloneIotCards({
|
||||
page: 1,
|
||||
page_size: 20,
|
||||
iccid
|
||||
})
|
||||
cardList.value = data.items || []
|
||||
} catch (error) {
|
||||
console.error('加载卡列表失败', error)
|
||||
}
|
||||
}
|
||||
|
||||
const remoteSearchCard = (query: string) => {
|
||||
loadCards(query)
|
||||
}
|
||||
|
||||
const handleCreateSubmit = async () => {
|
||||
if (!createFormRef.value) return
|
||||
const valid = await createFormRef.value.validate().catch(() => false)
|
||||
if (!valid) return
|
||||
if (createForm.card_id === undefined) {
|
||||
ElMessage.warning('请选择卡')
|
||||
return
|
||||
}
|
||||
createLoading.value = true
|
||||
try {
|
||||
const { data } = await PollingPriorityQueueService.createPriorityItems({
|
||||
card_id: createForm.card_id,
|
||||
reason: createForm.reason.trim()
|
||||
})
|
||||
createResult.value = data
|
||||
createDialogVisible.value = false
|
||||
resultDialogVisible.value = true
|
||||
await loadData()
|
||||
} catch (error) {
|
||||
console.error('人工优先入队失败', error)
|
||||
} finally {
|
||||
createLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 详情
|
||||
const detailDialogVisible = ref(false)
|
||||
const detailLoading = ref(false)
|
||||
const detailData = ref<PollingPriorityItem | null>(null)
|
||||
|
||||
const showDetail = async (row: PollingPriorityItem) => {
|
||||
detailData.value = null
|
||||
detailDialogVisible.value = true
|
||||
detailLoading.value = true
|
||||
try {
|
||||
const { data } = await PollingPriorityQueueService.getPriorityItemDetail(row.id)
|
||||
detailData.value = data
|
||||
} catch (error) {
|
||||
console.error('加载优先轮询项详情失败', error)
|
||||
} finally {
|
||||
detailLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.priority-queue-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
|
||||
.result-summary {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 12px;
|
||||
|
||||
.result-item {
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user