Files
one-pipe-system/src/components/business/polling/PollingStateDialog.vue
luo 06684e1fc0
All checks were successful
构建并部署前端到测试环境 / deploy (push) Successful in 55s
fix: 轮询按运营商账户限流
2026-10-26 14:31:15 +08:00

148 lines
4.8 KiB
Vue

<template>
<ElDialog
:model-value="true"
title="单卡轮询状态"
width="min(1100px, 96vw)"
@update:model-value="
(value) => {
if (!value) emit('close')
}
"
>
<ElButton :loading="loading" @click="load">刷新状态</ElButton>
<ElAlert v-if="error" type="error" :title="error" :closable="false" />
<template v-if="state">
<ElDescriptions :column="width < 640 ? 1 : 2" border class="state-overview"
><ElDescriptionsItem v-for="item in fields" :key="item[0]" :label="item[1]">{{
gatewayDisplayValue(state[item[0] as keyof typeof state], item[0])
}}</ElDescriptionsItem></ElDescriptions
>
<h3>各检查类型状态</h3
><ArtTable :data="state.types" row-key="check_type" :pagination="false"
><ElTableColumn v-for="item in typeFields" :key="item[0]" :label="item[1]" min-width="170"
><template #default="{ row }">{{
gatewayDisplayValue(row[item[0]], item[0])
}}</template></ElTableColumn
></ArtTable
>
<h3>停复机指令</h3
><ElDescriptions v-if="state.stop_resume_command" :column="width < 640 ? 1 : 2" border
><ElDescriptionsItem v-for="item in commandFields" :key="item[0]" :label="item[1]">{{
gatewayDisplayValue(commandValue(item[0]), item[0])
}}</ElDescriptionsItem></ElDescriptions
><ElEmpty v-else description="当前没有停复机指令" />
</template>
</ElDialog>
</template>
<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount, onDeactivated, watch } from 'vue'
import { useUserStore } from '@/store/modules/user'
import {
pollingScope,
schedulerDisplayValue as gatewayDisplayValue
} from '@/utils/business/pollingScheduler'
import { useWindowSize } from '@vueuse/core'
import { PollingSchedulerService } from '@/api/modules/pollingScheduler'
import { useAuth } from '@/composables/useAuth'
import {
gatewayData,
gatewayError,
requireGatewayAccess,
validateGatewayInteger
} from '@/utils/business/gateway'
import type { CardPollingState } from '@/types/api/pollingScheduler'
const props = defineProps<{ cardId: number; permission: string }>()
const emit = defineEmits<{ close: [] }>()
const user = useUserStore()
watch(
() => pollingScope(user),
() => {
++sequence
state.value = null
emit('close')
},
{ flush: 'sync' }
)
const { width } = useWindowSize(),
{ hasAuth } = useAuth()
const loading = ref(false),
error = ref(''),
state = ref<CardPollingState | null>(null)
const fields = [
['iccid', 'ICCID'],
['carrier_id', '运营商账户 ID'],
['scheduler_mode', '调度模式'],
['enable_polling', '是否轮询'],
['queue', '所在队列'],
['queue_score_at', '下次可领取时间'],
['running', '执行中'],
['claim_expire_at', '处理中标记到期'],
['priority_mark', '优先标记'],
['stop_resume_queue_enabled', '停复机出口队列'],
['last_stop_resume_call_at', '最近停复机网关调用']
]
const typeFields = [
['check_type_name', '检查类型'],
['next_due_at', '下次到期'],
['last_success_at', '最近成功'],
['fail_count', '连续失败'],
['first_failed_at', '首次失败'],
['last_error', '最近错误'],
['pending_confirm', '待确认延迟(秒)']
]
const commandFields = [
['action', '动作'],
['reason', '原因'],
['scenes', '场景'],
['source', '来源'],
['version', '版本'],
['fail_count', '连续失败'],
['last_error', '最近错误'],
['pending', '待确认'],
['unknown', '连续未知'],
['written_at', '写入时间'],
['next_at', '下次可执行时间']
]
let sequence = 0
const commandValue = (key: string) =>
state.value?.stop_resume_command?.[
key as keyof NonNullable<CardPollingState['stop_resume_command']>
]
async function load() {
const scope = pollingScope(user)
const current = ++sequence
loading.value = true
error.value = ''
state.value = null
try {
requireGatewayAccess(hasAuth(props.permission))
validateGatewayInteger(props.cardId, 1, Number.MAX_SAFE_INTEGER, '卡 ID')
const data = gatewayData(await PollingSchedulerService.getCardState(props.cardId))
if (current === sequence && scope === pollingScope(user) && hasAuth(props.permission))
state.value = data
} catch (cause) {
if (current === sequence) error.value = gatewayError(cause)
} finally {
if (current === sequence) loading.value = false
}
}
onMounted(load)
onDeactivated(() => {
++sequence
emit('close')
})
onBeforeUnmount(() => {
++sequence
})
</script>
<style scoped>
.state-overview {
margin-top: 16px;
}
h3 {
margin: 20px 0 12px;
font-size: 16px;
}
</style>