fix: some
Some checks failed
构建并部署前端到测试环境 / build-and-deploy (push) Has been cancelled

This commit is contained in:
luo
2026-09-17 18:37:20 +08:00
parent 068d0c4ac2
commit 68d8f769d1
21 changed files with 1281 additions and 85 deletions

View 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>