This commit is contained in:
65
src/components/business/gateway/GatewayDynamicData.vue
Normal file
65
src/components/business/gateway/GatewayDynamicData.vue
Normal file
@@ -0,0 +1,65 @@
|
||||
<template>
|
||||
<div>
|
||||
<ArtTableHeader v-model:columns="columnChecks" @refresh="$emit('refresh')" />
|
||||
<ArtTable
|
||||
:data="rows"
|
||||
:pagination="false"
|
||||
empty-text="暂无统计数据"
|
||||
max-height="460"
|
||||
:index="false"
|
||||
>
|
||||
<ElTableColumn
|
||||
v-for="key in visibleKeys"
|
||||
:key="key"
|
||||
:label="key"
|
||||
min-width="180"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<template #default="{ row }">{{ gatewayDisplayValue(row[key], key) }}</template>
|
||||
</ElTableColumn>
|
||||
</ArtTable>
|
||||
<ElCollapse>
|
||||
<ElCollapseItem title="查看完整响应数据(时间已格式化)" name="raw">
|
||||
<pre class="gateway-data">{{ responseText }}</pre>
|
||||
</ElCollapseItem>
|
||||
</ElCollapse>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { gatewayRows, gatewayDisplayValue } from '@/utils/business/gateway'
|
||||
defineEmits<{ refresh: [] }>()
|
||||
const props = defineProps<{ values: unknown[]; data: unknown }>()
|
||||
const rows = computed(() => gatewayRows(props.values))
|
||||
const keys = computed(() => [...new Set(rows.value.flatMap((row) => Object.keys(row)))])
|
||||
const responseText = computed(() => {
|
||||
const text = gatewayDisplayValue(props.data)
|
||||
if (props.data == null || typeof props.data !== 'object') return text
|
||||
return JSON.stringify(JSON.parse(text), null, 2)
|
||||
})
|
||||
const columnChecks = ref<{ prop: string; label: string; checked: boolean }[]>([])
|
||||
watch(
|
||||
keys,
|
||||
(values) => {
|
||||
columnChecks.value = values.map((key) => ({
|
||||
prop: key,
|
||||
label: key,
|
||||
checked: columnChecks.value.find((column) => column.prop === key)?.checked ?? true
|
||||
}))
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
const visibleKeys = computed(() =>
|
||||
columnChecks.value.filter((column) => column.checked).map((column) => column.prop)
|
||||
)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.gateway-data {
|
||||
max-height: 400px;
|
||||
overflow: auto;
|
||||
overflow-wrap: anywhere;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
</style>
|
||||
22
src/components/business/gateway/GatewayOptionsNotice.vue
Normal file
22
src/components/business/gateway/GatewayOptionsNotice.vue
Normal file
@@ -0,0 +1,22 @@
|
||||
<template>
|
||||
<ElAlert
|
||||
v-if="error"
|
||||
type="error"
|
||||
:closable="false"
|
||||
show-icon
|
||||
:title="title || '网关枚举加载失败'"
|
||||
>
|
||||
<template #default>
|
||||
{{ error }}
|
||||
<ElButton link type="primary" :loading="loading" @click="$emit('retry')">重新加载</ElButton>
|
||||
</template>
|
||||
</ElAlert>
|
||||
<div v-else-if="loading" role="status">{{
|
||||
title ? '正在加载运营商账户…' : '正在加载网关枚举…'
|
||||
}}</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineProps<{ error: string; loading: boolean; title?: string }>()
|
||||
defineEmits<{ retry: [] }>()
|
||||
</script>
|
||||
41
src/components/business/gateway/GatewayRuleFields.vue
Normal file
41
src/components/business/gateway/GatewayRuleFields.vue
Normal file
@@ -0,0 +1,41 @@
|
||||
<template>
|
||||
<ElForm label-width="130px" :disabled="disabled">
|
||||
<ElFormItem label="错误分类" required>
|
||||
<ElSelect v-model="model.category"
|
||||
><ElOption v-for="opt in options.error_categories" :key="opt.value" v-bind="opt"
|
||||
/></ElSelect>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="关键字或正则" required
|
||||
><ElInput v-model="model.keyword" maxlength="200" show-word-limit
|
||||
/></ElFormItem>
|
||||
<ElFormItem label="匹配方式" required>
|
||||
<ElSelect v-model="model.match_type"
|
||||
><ElOption v-for="opt in options.match_types" :key="opt.value" v-bind="opt"
|
||||
/></ElSelect>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="适用操作">
|
||||
<ElSelect v-model="model.operations" multiple clearable placeholder="留空表示全部操作"
|
||||
><ElOption v-for="opt in options.gateway_operations" :key="opt.value" v-bind="opt"
|
||||
/></ElSelect>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="上游可能已受理"><ElSwitch v-model="model.maybe_accepted" /></ElFormItem>
|
||||
<ElFormItem label="匹配优先级"
|
||||
><ElInputNumber v-model="model.priority" :min="1" :max="10000" :precision="0" /><span
|
||||
>数值越小越先匹配</span
|
||||
></ElFormItem
|
||||
>
|
||||
<ElFormItem label="状态"
|
||||
><ElSelect v-model="model.status"
|
||||
><ElOption label="启用" :value="1" /><ElOption label="停用" :value="0" /></ElSelect
|
||||
></ElFormItem>
|
||||
<ElFormItem label="备注"
|
||||
><ElInput v-model="model.remark" type="textarea" maxlength="500" show-word-limit
|
||||
/></ElFormItem>
|
||||
</ElForm>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { GatewayRuleInput, GatewayOptions } from '@/types/api/gateway'
|
||||
const model = defineModel<GatewayRuleInput>({ required: true })
|
||||
defineProps<{ options: GatewayOptions; disabled?: boolean }>()
|
||||
</script>
|
||||
268
src/components/business/gateway/StopResumeRecords.vue
Normal file
268
src/components/business/gateway/StopResumeRecords.vue
Normal file
@@ -0,0 +1,268 @@
|
||||
<template>
|
||||
<div class="gateway-page" :id="cardId == null ? 'table-full-screen' : undefined">
|
||||
<GatewayOptionsNotice :error="optionsError" :loading="optionsLoading" @retry="loadOptions" />
|
||||
<GatewayOptionsNotice
|
||||
v-if="cardId == null"
|
||||
title="运营商账户加载失败"
|
||||
:error="carrierError"
|
||||
:loading="carrierLoading"
|
||||
@retry="searchCarriers()"
|
||||
/>
|
||||
<ArtSearchBar
|
||||
:filter="searchFilters"
|
||||
:items="searchItems"
|
||||
:show-expand="searchItems.length >= 4"
|
||||
label-width="100px"
|
||||
@update:filter="updateFilters"
|
||||
@search="search"
|
||||
@reset="reset"
|
||||
/>
|
||||
<ElCard shadow="never" class="art-table-card">
|
||||
<ArtTableHeader v-model:columns="columnChecks" @refresh="load">
|
||||
<template #left
|
||||
><span class="gateway-help"
|
||||
>时间跨度不超过 31 天;单卡清空时间时查询最近 31 天。</span
|
||||
></template
|
||||
>
|
||||
</ArtTableHeader>
|
||||
<ElAlert
|
||||
v-if="error"
|
||||
class="gateway-error"
|
||||
type="error"
|
||||
:title="error"
|
||||
:closable="false"
|
||||
show-icon
|
||||
/>
|
||||
<ArtTable
|
||||
:loading="loading"
|
||||
:data="rows"
|
||||
row-key="id"
|
||||
:current-page="pagination.page"
|
||||
:page-size="pagination.page_size"
|
||||
:total="total"
|
||||
:page-sizes="[20, 50, 100]"
|
||||
:margin-top="10"
|
||||
empty-text="暂无停复机记录"
|
||||
@current-change="changePage"
|
||||
@size-change="changeSize"
|
||||
>
|
||||
<ElTableColumn
|
||||
v-for="col in columns"
|
||||
:key="col.prop"
|
||||
:prop="col.prop"
|
||||
:label="col.label"
|
||||
:min-width="col.width || 140"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<template #default="{ row }">{{ recordValue(row, col.prop) }}</template>
|
||||
</ElTableColumn>
|
||||
</ArtTable>
|
||||
</ElCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, reactive, watch } from 'vue'
|
||||
import { GatewayService } from '@/api/modules/gateway'
|
||||
import { useAuth } from '@/composables/useAuth'
|
||||
import {
|
||||
useGatewayList,
|
||||
useGatewayOptions,
|
||||
useGatewayColumns,
|
||||
useGatewayCarriers
|
||||
} from '@/composables/useGateway'
|
||||
import type { SearchFormItem } from '@/types'
|
||||
import type { StopResumeQuery, StopResumeRecord } from '@/types/api/gateway'
|
||||
import {
|
||||
compactGatewayQuery,
|
||||
gatewayTimeRange,
|
||||
recentGatewayRange,
|
||||
gatewayDisplayValue,
|
||||
gatewayOptionalId,
|
||||
validateGatewayInteger
|
||||
} from '@/utils/business/gateway'
|
||||
import GatewayOptionsNotice from './GatewayOptionsNotice.vue'
|
||||
|
||||
const props = defineProps<{ cardId?: number; permission: string }>()
|
||||
const { hasAuth } = useAuth()
|
||||
const {
|
||||
options: carrierOptions,
|
||||
loading: carrierLoading,
|
||||
error: carrierError,
|
||||
search: searchCarriers,
|
||||
label: carrierLabel,
|
||||
select: selectCarrier
|
||||
} = useGatewayCarriers(props.cardId == null)
|
||||
const {
|
||||
options,
|
||||
error: optionsError,
|
||||
loading: optionsLoading,
|
||||
ready: optionsReady,
|
||||
load: loadOptions,
|
||||
label
|
||||
} = useGatewayOptions()
|
||||
const range = ref<[Date | string, Date | string] | null>(recentGatewayRange())
|
||||
const filters = reactive({
|
||||
iccid: '',
|
||||
carrier_id: undefined as number | undefined,
|
||||
action: '',
|
||||
source: '',
|
||||
result: '',
|
||||
error_category: '',
|
||||
range
|
||||
})
|
||||
const { rows, total, loading, error, pagination, load, search, clear } =
|
||||
useGatewayList<StopResumeRecord>(
|
||||
(page) => {
|
||||
const time = gatewayTimeRange(range.value, props.cardId == null)
|
||||
if (props.cardId != null) {
|
||||
validateGatewayInteger(props.cardId, 0, Number.MAX_SAFE_INTEGER, '卡 ID')
|
||||
return GatewayService.getCardRecords(props.cardId, { ...page, ...time })
|
||||
}
|
||||
return GatewayService.getRecords(
|
||||
compactGatewayQuery({
|
||||
...filters,
|
||||
range: undefined,
|
||||
carrier_id: gatewayOptionalId(filters.carrier_id),
|
||||
...page,
|
||||
...time
|
||||
}) as StopResumeQuery
|
||||
)
|
||||
},
|
||||
() => hasAuth(props.permission)
|
||||
)
|
||||
const columnDefinitions: { prop: keyof StopResumeRecord; label: string; width?: number }[] = [
|
||||
{ prop: 'created_at', label: '创建时间', width: 200 },
|
||||
{ prop: 'iccid', label: 'ICCID', width: 220 },
|
||||
{ prop: 'action', label: '动作' },
|
||||
{ prop: 'result', label: '结果' },
|
||||
{ prop: 'error_category', label: '错误类别' },
|
||||
{ prop: 'provider_message', label: '上游返回信息', width: 240 },
|
||||
{ prop: 'reason', label: '原因', width: 200 },
|
||||
{ prop: 'source', label: '来源' },
|
||||
{ prop: 'scene', label: '场景' },
|
||||
{ prop: 'attempt', label: '尝试次数' },
|
||||
{ prop: 'duration_ms', label: '耗时(毫秒)' },
|
||||
{ prop: 'provider_code', label: '上游返回码' },
|
||||
{ prop: 'command_id', label: '命令 ID' },
|
||||
{ prop: 'card_id', label: '卡 ID' },
|
||||
{ prop: 'carrier_id', label: '运营商账户' },
|
||||
{ prop: 'id', label: '记录 ID' }
|
||||
]
|
||||
const { columns, columnChecks } = useGatewayColumns(columnDefinitions)
|
||||
const searchFilters = filters
|
||||
function updateFilters(value: Record<string, unknown>) {
|
||||
const { range: time, ...rest } = value
|
||||
Object.assign(filters, rest)
|
||||
range.value = time as typeof range.value
|
||||
}
|
||||
const searchItems = computed<SearchFormItem[]>(() => [
|
||||
...(props.cardId == null
|
||||
? [
|
||||
{
|
||||
label: 'ICCID',
|
||||
prop: 'iccid',
|
||||
type: 'input' as const,
|
||||
config: { clearable: true, maxlength: 30, placeholder: '请输入 ICCID' }
|
||||
},
|
||||
{
|
||||
label: '运营商账户',
|
||||
prop: 'carrier_id',
|
||||
type: 'select' as const,
|
||||
options: carrierOptions.value,
|
||||
onChange: ({ val }: { val: unknown }) => selectCarrier(val),
|
||||
config: {
|
||||
clearable: true,
|
||||
filterable: true,
|
||||
remote: true,
|
||||
remoteMethod: searchCarriers,
|
||||
loading: carrierLoading.value,
|
||||
placeholder: '请输入运营商名称搜索'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '动作',
|
||||
prop: 'action',
|
||||
type: 'select' as const,
|
||||
options: options.value.stop_resume_actions,
|
||||
config: { clearable: true, disabled: !optionsReady.value }
|
||||
},
|
||||
{
|
||||
label: '来源',
|
||||
prop: 'source',
|
||||
type: 'input' as const,
|
||||
config: { clearable: true, maxlength: 50 }
|
||||
},
|
||||
{
|
||||
label: '结果',
|
||||
prop: 'result',
|
||||
type: 'select' as const,
|
||||
options: options.value.stop_resume_results,
|
||||
config: { clearable: true, disabled: !optionsReady.value }
|
||||
},
|
||||
{
|
||||
label: '错误类别',
|
||||
prop: 'error_category',
|
||||
type: 'select' as const,
|
||||
options: options.value.error_categories,
|
||||
config: { clearable: true, disabled: !optionsReady.value }
|
||||
}
|
||||
]
|
||||
: []),
|
||||
{
|
||||
label: '记录时间',
|
||||
prop: 'range',
|
||||
type: 'datetimerange',
|
||||
config: {
|
||||
type: 'datetimerange',
|
||||
rangeSeparator: '至',
|
||||
startPlaceholder: '开始时间',
|
||||
endPlaceholder: '结束时间',
|
||||
valueFormat: 'YYYY-MM-DDTHH:mm:ssZ',
|
||||
clearable: true
|
||||
}
|
||||
}
|
||||
])
|
||||
function changePage(page: number) {
|
||||
pagination.page = page
|
||||
void load()
|
||||
}
|
||||
function changeSize(size: number) {
|
||||
pagination.page_size = size
|
||||
pagination.page = 1
|
||||
void load()
|
||||
}
|
||||
function recordValue(row: StopResumeRecord, prop: keyof StopResumeRecord) {
|
||||
if (prop === 'carrier_id') return carrierLabel(row.carrier_id)
|
||||
if (prop === 'action') return row.action_name || label('stop_resume_actions', row.action)
|
||||
if (prop === 'result') return label('stop_resume_results', row.result)
|
||||
if (prop === 'error_category')
|
||||
return row.error_category_name || label('error_categories', row.error_category)
|
||||
return gatewayDisplayValue(row[prop], prop)
|
||||
}
|
||||
function reset() {
|
||||
Object.assign(filters, {
|
||||
iccid: '',
|
||||
carrier_id: undefined,
|
||||
action: '',
|
||||
source: '',
|
||||
result: '',
|
||||
error_category: ''
|
||||
})
|
||||
range.value = recentGatewayRange()
|
||||
void search()
|
||||
}
|
||||
watch(
|
||||
() => [props.cardId, props.permission],
|
||||
() => {
|
||||
clear()
|
||||
pagination.page = 1
|
||||
void load()
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@use '@/views/polling-management/gateway';
|
||||
</style>
|
||||
Reference in New Issue
Block a user