This commit is contained in:
24
src/api/modules/carrierTrafficThresholdHit.ts
Normal file
24
src/api/modules/carrierTrafficThresholdHit.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
/** 运营商通道阈值命中记录查询 API */
|
||||
|
||||
import { BaseService } from '../BaseService'
|
||||
import type { BaseResponse } from '@/types/api'
|
||||
import type {
|
||||
CarrierTrafficThresholdHitPageResult,
|
||||
CarrierTrafficThresholdHitQueryParams
|
||||
} from '@/types/api/carrierTrafficThresholdHit'
|
||||
|
||||
export class CarrierTrafficThresholdHitService extends BaseService {
|
||||
/**
|
||||
* 查询通道阈值命中记录
|
||||
* GET /api/admin/carrier-traffic-threshold-hits
|
||||
* 仅超级管理员和平台账号可访问,后端负责卡数据范围过滤。
|
||||
*/
|
||||
static getHits(
|
||||
params?: CarrierTrafficThresholdHitQueryParams
|
||||
): Promise<BaseResponse<CarrierTrafficThresholdHitPageResult>> {
|
||||
return this.get<BaseResponse<CarrierTrafficThresholdHitPageResult>>(
|
||||
'/api/admin/carrier-traffic-threshold-hits',
|
||||
params
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ export { StorageService } from './storage'
|
||||
export { AuthorizationService } from './authorization'
|
||||
export { DeviceService } from './device'
|
||||
export { CarrierService } from './carrier'
|
||||
export { CarrierTrafficThresholdHitService } from './carrierTrafficThresholdHit'
|
||||
export { PackageSeriesService } from './packageSeries'
|
||||
export { PackageManageService } from './packageManage'
|
||||
export { ShopSeriesGrantService } from './shopSeriesGrant'
|
||||
|
||||
@@ -356,6 +356,8 @@
|
||||
"role": "Role Manage",
|
||||
"permission": "Permission Manage",
|
||||
"carrierManagement": "Carrier Management",
|
||||
"carrierDetail": "Carrier Details",
|
||||
"carrierTrafficThresholdHits": "Carrier Threshold Hit Records",
|
||||
"userCenter": "User Center",
|
||||
"menu": "Menu Manage",
|
||||
"nested": "Nested Menu",
|
||||
|
||||
@@ -355,6 +355,8 @@
|
||||
"role": "角色管理",
|
||||
"permission": "权限管理",
|
||||
"carrierManagement": "运营商管理",
|
||||
"carrierDetail": "运营商详情",
|
||||
"carrierTrafficThresholdHits": "通道阈值命中记录",
|
||||
"userCenter": "个人中心"
|
||||
},
|
||||
"packageManagement": {
|
||||
|
||||
@@ -238,7 +238,7 @@ async function processFrontendMenu(router: Router): Promise<void> {
|
||||
throw new Error('获取用户角色失败')
|
||||
}
|
||||
|
||||
const filteredMenuList = filterMenuByRoles(menuList, roles)
|
||||
const filteredMenuList = filterMenuByRoles(menuList, roles, Number(userStore.info.user_type))
|
||||
await new Promise((resolve) => setTimeout(resolve, 300))
|
||||
await registerAndStoreMenu(router, filteredMenuList, closeLoading)
|
||||
} catch (error) {
|
||||
@@ -264,7 +264,11 @@ async function processBackendMenu(router: Router): Promise<void> {
|
||||
}
|
||||
|
||||
// 普通用户:使用后端返回的菜单
|
||||
const backendMenus = userStore.menus || []
|
||||
let backendMenus = userStore.menus || []
|
||||
if (Number(userStore.info.user_type) === 2) {
|
||||
backendMenus = ensureCarrierManagementMenuEntries(backendMenus)
|
||||
userStore.setMenus(backendMenus)
|
||||
}
|
||||
const routeMap = buildRouteMap(asyncRoutes)
|
||||
|
||||
const menuList = backendMenus
|
||||
@@ -279,6 +283,46 @@ async function processBackendMenu(router: Router): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Backend menu mode may return the existing System menu without the newly added
|
||||
* carrier detail and threshold-hit entries. Add those as sibling routes under System
|
||||
* for platform accounts, without introducing a nested RouterView layout.
|
||||
*/
|
||||
function ensureCarrierManagementMenuEntries(menus: any[]): any[] {
|
||||
const requiredEntries = [
|
||||
{
|
||||
url: `${RoutesAlias.CarrierDetail}/:id`,
|
||||
name: 'menus.system.carrierDetail'
|
||||
},
|
||||
{
|
||||
url: RoutesAlias.CarrierTrafficThresholdHits,
|
||||
name: 'menus.system.carrierTrafficThresholdHits'
|
||||
}
|
||||
]
|
||||
let systemMenuFound = false
|
||||
|
||||
const visit = (items: any[]): any[] =>
|
||||
items.map((item) => {
|
||||
const children = Array.isArray(item.children) ? visit(item.children) : []
|
||||
const itemUrl = item.url || item.path
|
||||
|
||||
if (itemUrl === '/system') {
|
||||
systemMenuFound = true
|
||||
const existingUrls = new Set(children.map((child) => child.url || child.path))
|
||||
const missingEntries = requiredEntries.filter((entry) => !existingUrls.has(entry.url))
|
||||
return { ...item, children: [...children, ...missingEntries] }
|
||||
}
|
||||
|
||||
return Array.isArray(item.children) ? { ...item, children } : item
|
||||
})
|
||||
|
||||
const enrichedMenus = visit(menus)
|
||||
if (systemMenuFound) return enrichedMenus
|
||||
|
||||
const systemMenu = { url: '/system', name: 'menus.system.title', children: requiredEntries }
|
||||
return [...enrichedMenus, systemMenu]
|
||||
}
|
||||
|
||||
/**
|
||||
* 合并默认菜单
|
||||
* 将配置的默认菜单合并到后端返回的菜单中
|
||||
@@ -448,15 +492,21 @@ function handleMenuError(error: unknown): void {
|
||||
/**
|
||||
* 根据角色过滤菜单
|
||||
*/
|
||||
const filterMenuByRoles = (menu: AppRouteRecord[], roles: string[]): AppRouteRecord[] => {
|
||||
const filterMenuByRoles = (
|
||||
menu: AppRouteRecord[],
|
||||
roles: string[],
|
||||
userType: number
|
||||
): AppRouteRecord[] => {
|
||||
return menu.reduce((acc: AppRouteRecord[], item) => {
|
||||
const itemRoles = item.meta?.roles
|
||||
const hasPermission = !itemRoles || itemRoles.some((role) => roles?.includes(role))
|
||||
const allowedUserTypes = item.meta?.allowedUserTypes as number[] | undefined
|
||||
const hasRole = !itemRoles || itemRoles.some((role) => roles?.includes(role))
|
||||
const hasAllowedUserType = !allowedUserTypes || allowedUserTypes.includes(userType)
|
||||
|
||||
if (hasPermission) {
|
||||
if (hasRole && hasAllowedUserType) {
|
||||
const filteredItem = { ...item }
|
||||
if (filteredItem.children?.length) {
|
||||
filteredItem.children = filterMenuByRoles(filteredItem.children, roles)
|
||||
filteredItem.children = filterMenuByRoles(filteredItem.children, roles, userType)
|
||||
}
|
||||
acc.push(filteredItem)
|
||||
}
|
||||
|
||||
@@ -205,4 +205,3 @@ export const buildLoginRedirect = (currentPath: string): string => {
|
||||
}
|
||||
return `/auth/login?redirect=${encodeURIComponent(currentPath)}`
|
||||
}
|
||||
|
||||
|
||||
@@ -118,6 +118,26 @@ export const asyncRoutes: AppRouteRecord[] = [
|
||||
keepAlive: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'carrier-management/detail/:id',
|
||||
name: 'CarrierDetail',
|
||||
component: RoutesAlias.CarrierDetail,
|
||||
meta: {
|
||||
title: 'menus.system.carrierDetail',
|
||||
isHide: true,
|
||||
keepAlive: false
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'carrier-traffic-threshold-hits',
|
||||
name: 'CarrierTrafficThresholdHits',
|
||||
component: RoutesAlias.CarrierTrafficThresholdHitsPage,
|
||||
meta: {
|
||||
title: 'menus.system.carrierTrafficThresholdHits',
|
||||
keepAlive: true,
|
||||
allowedUserTypes: [1, 2]
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'user-center',
|
||||
name: 'UserCenter',
|
||||
|
||||
@@ -21,6 +21,9 @@ export enum RoutesAlias {
|
||||
RoleDetail = '/system/role/detail', // 角色详情
|
||||
Permission = '/system/permission', // 权限管理
|
||||
CarrierManagement = '/system/carrier-management', // 运营商管理
|
||||
CarrierDetail = '/system/carrier-management/detail', // 运营商详情
|
||||
CarrierTrafficThresholdHits = '/system/carrier-traffic-threshold-hits', // 通道阈值命中记录
|
||||
CarrierTrafficThresholdHitsPage = '/system/carrier-management/traffic-threshold-hits', // 通道阈值命中记录组件
|
||||
UserCenter = '/system/user-center', // 用户中心
|
||||
|
||||
// 审计中心
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
店铺编码,操作类型,业务员登录账号,备注
|
||||
店铺编码,操作类型,业务员登录账号,备注
|
||||
,仅支持「换绑」或「清空」,注意: 清空不要填写业务员登录账号,
|
||||
|
||||
|
1
src/template/订单批量作废模板.csv
Normal file
1
src/template/订单批量作废模板.csv
Normal file
@@ -0,0 +1 @@
|
||||
order_no
|
||||
|
@@ -12,16 +12,22 @@ export enum CarrierType {
|
||||
CBN = 'CBN' // 中国广电
|
||||
}
|
||||
|
||||
export type CarrierTrafficThresholdEnabled = 0 | 1
|
||||
export type CarrierTrafficThresholdUnit = 'MB' | 'GB'
|
||||
|
||||
// 运营商响应数据
|
||||
export interface Carrier {
|
||||
id: number
|
||||
carrier_code: string
|
||||
carrier_name: string
|
||||
carrier_type: CarrierType
|
||||
description?: string
|
||||
description?: string | null
|
||||
data_reset_day?: number | null // 上游流量重置日(1-28)
|
||||
realname_link_type?: string // 实名链接类型
|
||||
realname_link_template?: string // 实名链接模板
|
||||
realname_link_type?: string | null // 实名链接类型
|
||||
realname_link_template?: string | null // 实名链接模板
|
||||
traffic_threshold_enabled?: CarrierTrafficThresholdEnabled | null // 通道流量阈值开关
|
||||
traffic_threshold_unit?: CarrierTrafficThresholdUnit | null // 通道流量阈值单位
|
||||
traffic_threshold_value?: number | null // 通道流量阈值数值
|
||||
status: number
|
||||
created_at: string
|
||||
updated_at: string
|
||||
@@ -39,19 +45,25 @@ export interface CreateCarrierParams {
|
||||
carrier_code: string
|
||||
carrier_name: string
|
||||
carrier_type: CarrierType
|
||||
description?: string
|
||||
description?: string | null
|
||||
data_reset_day?: number | null // 上游流量重置日(1-28)
|
||||
realname_link_type?: string // 实名链接类型
|
||||
realname_link_template?: string // 实名链接模板
|
||||
realname_link_type?: string | null // 实名链接类型
|
||||
realname_link_template?: string | null // 实名链接模板
|
||||
traffic_threshold_enabled?: CarrierTrafficThresholdEnabled | null // 通道流量阈值开关
|
||||
traffic_threshold_unit?: CarrierTrafficThresholdUnit | null // 通道流量阈值单位
|
||||
traffic_threshold_value?: number | null // 通道流量阈值数值
|
||||
}
|
||||
|
||||
// 更新运营商请求参数
|
||||
export interface UpdateCarrierParams {
|
||||
carrier_name?: string
|
||||
description?: string
|
||||
description?: string | null
|
||||
data_reset_day?: number | null // 上游流量重置日(1-28)
|
||||
realname_link_type?: string // 实名链接类型
|
||||
realname_link_template?: string // 实名链接模板
|
||||
realname_link_type?: string | null // 实名链接类型
|
||||
realname_link_template?: string | null // 实名链接模板
|
||||
traffic_threshold_enabled?: CarrierTrafficThresholdEnabled | null // 通道流量阈值开关
|
||||
traffic_threshold_unit?: CarrierTrafficThresholdUnit | null // 通道流量阈值单位
|
||||
traffic_threshold_value?: number | null // 通道流量阈值数值
|
||||
}
|
||||
|
||||
// 更新运营商状态参数
|
||||
|
||||
35
src/types/api/carrierTrafficThresholdHit.ts
Normal file
35
src/types/api/carrierTrafficThresholdHit.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
/** 运营商通道阈值命中记录 */
|
||||
|
||||
export interface CarrierTrafficThresholdHitQueryParams {
|
||||
page?: number
|
||||
page_size?: number
|
||||
carrier_id?: number
|
||||
card_id?: number
|
||||
period_start?: string
|
||||
start_time?: string
|
||||
end_time?: string
|
||||
}
|
||||
|
||||
export interface CarrierTrafficThresholdHitItem {
|
||||
id: number
|
||||
card_id: number
|
||||
carrier_id: number
|
||||
hit_traffic_mb: number | null
|
||||
hit_threshold_value: number | null
|
||||
hit_threshold_unit: string
|
||||
period_start: string
|
||||
judged_at: string
|
||||
trigger_source: string
|
||||
status: string
|
||||
unlocked_at: string | null
|
||||
resume_result: string
|
||||
failure_reason: string
|
||||
created_at: string
|
||||
}
|
||||
|
||||
export interface CarrierTrafficThresholdHitPageResult {
|
||||
items: CarrierTrafficThresholdHitItem[] | null
|
||||
page: number
|
||||
size: number
|
||||
total: number
|
||||
}
|
||||
@@ -66,6 +66,7 @@ export * from './enterpriseDevice'
|
||||
|
||||
// 运营商相关
|
||||
export * from './carrier'
|
||||
export * from './carrierTrafficThresholdHit'
|
||||
|
||||
// 订单相关
|
||||
export * from './order'
|
||||
|
||||
1
src/types/components.d.ts
vendored
1
src/types/components.d.ts
vendored
@@ -133,6 +133,7 @@ declare module 'vue' {
|
||||
ElRow: typeof import('element-plus/es')['ElRow']
|
||||
ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
|
||||
ElSelect: typeof import('element-plus/es')['ElSelect']
|
||||
ElSkeleton: typeof import('element-plus/es')['ElSkeleton']
|
||||
ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
|
||||
ElSwitch: typeof import('element-plus/es')['ElSwitch']
|
||||
ElTable: typeof import('element-plus/es')['ElTable']
|
||||
|
||||
@@ -56,20 +56,27 @@
|
||||
>
|
||||
<ElForm ref="createFormRef" :model="createForm" :rules="createRules">
|
||||
<ElFormItem label="CSV文件" prop="file">
|
||||
<VoucherUpload
|
||||
ref="csvUploadRef"
|
||||
v-model="csvFileKeys"
|
||||
voucher-name="CSV文件"
|
||||
:max-count="1"
|
||||
purpose="attachment"
|
||||
:max-size-mb="10"
|
||||
single-column-csv
|
||||
:max-csv-rows="1000"
|
||||
accept=".csv"
|
||||
tip="仅支持 UTF-8 单列 CSV,每行一个订单号,最多 1000 行,最大 10MB"
|
||||
@uploading-change="csvUploading = $event"
|
||||
@files-change="handleCsvFilesChange"
|
||||
/>
|
||||
<div class="csv-upload-field">
|
||||
<div class="template-download-row">
|
||||
<ElButton link type="primary" :icon="Download" @click="downloadTemplate">
|
||||
下载模板
|
||||
</ElButton>
|
||||
</div>
|
||||
<VoucherUpload
|
||||
ref="csvUploadRef"
|
||||
v-model="csvFileKeys"
|
||||
voucher-name="CSV文件"
|
||||
:max-count="1"
|
||||
purpose="attachment"
|
||||
:max-size-mb="10"
|
||||
single-column-csv
|
||||
:max-csv-rows="1000"
|
||||
accept=".csv"
|
||||
tip="仅支持 UTF-8 单列 CSV,每行一个订单号,最多 1000 行,最大 10MB"
|
||||
@uploading-change="csvUploading = $event"
|
||||
@files-change="handleCsvFilesChange"
|
||||
/>
|
||||
</div>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="凭证附件">
|
||||
<VoucherUpload
|
||||
@@ -123,7 +130,7 @@
|
||||
ElMessage,
|
||||
ElTag
|
||||
} from 'element-plus'
|
||||
import { Upload } from '@element-plus/icons-vue'
|
||||
import { Download, Upload } from '@element-plus/icons-vue'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import { OrderPackageInvalidateTaskService } from '@/api/modules'
|
||||
import type {
|
||||
@@ -390,6 +397,21 @@
|
||||
getTableData()
|
||||
}
|
||||
|
||||
const downloadTemplate = () => {
|
||||
try {
|
||||
const link = document.createElement('a')
|
||||
link.href = new URL('@/template/订单批量作废模板.csv', import.meta.url).href
|
||||
link.download = '订单批量作废模板.csv'
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
ElMessage.success('模板下载成功')
|
||||
} catch (error) {
|
||||
console.error('下载作废任务模板失败:', error)
|
||||
ElMessage.error('模板下载失败')
|
||||
}
|
||||
}
|
||||
|
||||
const handleCsvFilesChange = (files: Array<{ file_name?: string }>) => {
|
||||
csvFileName.value = files[0]?.file_name || ''
|
||||
createForm.file = csvFileName.value
|
||||
@@ -449,6 +471,16 @@
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.csv-upload-field {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.template-download-row {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.drawer-footer {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
|
||||
140
src/views/system/carrier-management/detail.vue
Normal file
140
src/views/system/carrier-management/detail.vue
Normal file
@@ -0,0 +1,140 @@
|
||||
<template>
|
||||
<ArtTableFullScreen>
|
||||
<div class="carrier-detail-page" id="table-full-screen">
|
||||
<ElCard shadow="never" class="art-table-card">
|
||||
<template #header>
|
||||
<div class="detail-header">
|
||||
<span>运营商详情</span>
|
||||
<ElButton @click="goBack">返回列表</ElButton>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<ElSkeleton v-if="loading" :rows="8" animated />
|
||||
<ElResult v-else-if="!carrier" icon="error" title="暂无运营商详情">
|
||||
<template #extra>
|
||||
<ElButton type="primary" @click="goBack">返回运营商列表</ElButton>
|
||||
</template>
|
||||
</ElResult>
|
||||
<ElDescriptions v-else :column="descriptionColumns" border>
|
||||
<ElDescriptionsItem label="运营商编码">
|
||||
{{ carrier.carrier_code || '-' }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="运营商名称">
|
||||
{{ carrier.carrier_name || '-' }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="运营商类型">
|
||||
{{ getCarrierTypeLabel(carrier.carrier_type) }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="状态">
|
||||
{{ getStatusText(carrier.status) }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="运营商描述" :span="descriptionColumns">
|
||||
{{ carrier.description || '未配置' }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="流量重置日">
|
||||
{{ carrier.data_reset_day ?? '未配置' }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="实名链接类型">
|
||||
{{ carrier.realname_link_type || '未配置' }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="实名链接模板" :span="descriptionColumns">
|
||||
{{ carrier.realname_link_template || '未配置' }}
|
||||
</ElDescriptionsItem>
|
||||
<template v-if="canConfigureTrafficThreshold">
|
||||
<ElDescriptionsItem label="通道阈值状态">
|
||||
{{ getThresholdEnabledLabel(carrier.traffic_threshold_enabled) }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="通道阈值单位">
|
||||
{{ carrier.traffic_threshold_unit || '未配置' }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="通道阈值数值">
|
||||
{{ formatThresholdValue(carrier.traffic_threshold_value) }}
|
||||
</ElDescriptionsItem>
|
||||
</template>
|
||||
<ElDescriptionsItem label="创建时间">
|
||||
{{ formatDateTime(carrier.created_at) }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="更新时间">
|
||||
{{ formatDateTime(carrier.updated_at) }}
|
||||
</ElDescriptionsItem>
|
||||
</ElDescriptions>
|
||||
</ElCard>
|
||||
</div>
|
||||
</ArtTableFullScreen>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useWindowSize } from '@vueuse/core'
|
||||
import { CarrierService } from '@/api/modules'
|
||||
import { usePermission } from '@/composables/usePermission'
|
||||
import { RoutesAlias } from '@/router/routesAlias'
|
||||
import { formatDateTime } from '@/utils/business/format'
|
||||
import { getStatusText, getCarrierTypeLabel } from '@/config/constants'
|
||||
import type { Carrier } from '@/types/api'
|
||||
|
||||
defineOptions({ name: 'CarrierDetail' })
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const { isPlatformAccount } = usePermission()
|
||||
const canConfigureTrafficThreshold = computed(() => isPlatformAccount.value)
|
||||
const { width } = useWindowSize()
|
||||
const descriptionColumns = computed(() => (width.value < 768 ? 1 : 2))
|
||||
const loading = ref(false)
|
||||
const carrier = ref<Carrier | null>(null)
|
||||
|
||||
const getThresholdEnabledLabel = (enabled?: 0 | 1 | null) => {
|
||||
if (enabled === 1) return '启用'
|
||||
if (enabled === 0) return '停用'
|
||||
return '未配置'
|
||||
}
|
||||
|
||||
const formatThresholdValue = (value?: number | null) =>
|
||||
value === null || value === undefined ? '未配置' : String(value)
|
||||
|
||||
const loadCarrier = async () => {
|
||||
const id = Number(route.params.id)
|
||||
if (!Number.isSafeInteger(id) || id < 1) {
|
||||
carrier.value = null
|
||||
return
|
||||
}
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await CarrierService.getCarrierDetail(id)
|
||||
if (res.code === 0 && res.data) {
|
||||
carrier.value = res.data
|
||||
} else {
|
||||
carrier.value = null
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取运营商详情失败:', error)
|
||||
carrier.value = null
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const goBack = () => {
|
||||
void router.push(RoutesAlias.CarrierManagement)
|
||||
}
|
||||
|
||||
watch(
|
||||
() => route.params.id,
|
||||
() => void loadCarrier()
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
void loadCarrier()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.detail-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
</style>
|
||||
@@ -45,9 +45,15 @@
|
||||
<ElDialog
|
||||
v-model="dialogVisible"
|
||||
:title="dialogType === 'add' ? '新增运营商' : '编辑运营商'"
|
||||
width="30%"
|
||||
width="min(94vw, 760px)"
|
||||
>
|
||||
<ElForm ref="formRef" :model="form" :rules="rules" label-width="120px">
|
||||
<ElForm
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
label-width="120px"
|
||||
class="carrier-form-grid"
|
||||
>
|
||||
<ElFormItem label="运营商编码" prop="carrier_code">
|
||||
<ElInput
|
||||
v-model="form.carrier_code"
|
||||
@@ -80,7 +86,7 @@
|
||||
/>
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="运营商描述" prop="description">
|
||||
<ElFormItem label="运营商描述" prop="description" class="carrier-form-span">
|
||||
<ElInput
|
||||
v-model="form.description"
|
||||
type="textarea"
|
||||
@@ -102,6 +108,42 @@
|
||||
运营商每月清零网关计数器的日期(1-28日),默认为1日
|
||||
</div>
|
||||
</ElFormItem>
|
||||
<ElFormItem v-if="canConfigureTrafficThreshold" label="通道阈值状态">
|
||||
<ElSelect
|
||||
v-model="form.traffic_threshold_mode"
|
||||
placeholder="请选择,留空表示未配置"
|
||||
style="width: 100%"
|
||||
@change="handleTrafficThresholdModeChange"
|
||||
>
|
||||
<ElOption label="未配置" value="unconfigured" />
|
||||
<ElOption label="停用" value="disabled" />
|
||||
<ElOption label="启用" value="enabled" />
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
<template v-if="canConfigureTrafficThreshold">
|
||||
<ElFormItem label="通道阈值单位" prop="traffic_threshold_unit">
|
||||
<ElSelect
|
||||
v-model="form.traffic_threshold_unit"
|
||||
placeholder="请选择阈值单位"
|
||||
style="width: 100%"
|
||||
:disabled="form.traffic_threshold_mode !== 'enabled'"
|
||||
>
|
||||
<ElOption label="MB" value="MB" />
|
||||
<ElOption label="GB" value="GB" />
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="通道阈值数值" prop="traffic_threshold_value">
|
||||
<ElInput
|
||||
v-model="form.traffic_threshold_value"
|
||||
type="number"
|
||||
min="0"
|
||||
step="any"
|
||||
:disabled="form.traffic_threshold_mode !== 'enabled'"
|
||||
placeholder="请输入大于0的阈值数值"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</ElFormItem>
|
||||
</template>
|
||||
<ElFormItem label="实名链接类型" prop="realname_link_type">
|
||||
<ElSelect
|
||||
v-model="form.realname_link_type"
|
||||
@@ -117,6 +159,7 @@
|
||||
v-if="form.realname_link_type === 'template'"
|
||||
label="实名链接模板"
|
||||
prop="realname_link_template"
|
||||
class="carrier-form-span"
|
||||
>
|
||||
<ElInput
|
||||
v-model="form.realname_link_template"
|
||||
@@ -145,13 +188,16 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { h, watch, nextTick } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { CarrierService } from '@/api/modules'
|
||||
import { ElMessage, ElMessageBox, ElTag, ElSwitch, ElButton } from 'element-plus'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import type { Carrier, CarrierType } from '@/types/api'
|
||||
import type { Carrier, CarrierType, CreateCarrierParams } from '@/types/api'
|
||||
import type { SearchFormItem } from '@/types'
|
||||
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
||||
import { useAuth } from '@/composables/useAuth'
|
||||
import { usePermission } from '@/composables/usePermission'
|
||||
import { RoutesAlias } from '@/router/routesAlias'
|
||||
import { generateCarrierCode } from '@/utils/codeGenerator'
|
||||
import { formatDateTime } from '@/utils/business/format'
|
||||
import {
|
||||
@@ -164,7 +210,10 @@
|
||||
|
||||
defineOptions({ name: 'CarrierManagement' })
|
||||
|
||||
const router = useRouter()
|
||||
const { hasAuth } = useAuth()
|
||||
const { isPlatformAccount } = usePermission()
|
||||
const canConfigureTrafficThreshold = computed(() => isPlatformAccount.value)
|
||||
const canUpdateCarrierStatus = hasAuth('carrier:update_status')
|
||||
const dialogVisible = ref(false)
|
||||
const loading = ref(false)
|
||||
@@ -235,6 +284,13 @@
|
||||
{ label: '运营商名称', prop: 'carrier_name' },
|
||||
{ label: '运营商类型', prop: 'carrier_type' },
|
||||
{ label: '运营商描述', prop: 'description' },
|
||||
...(canConfigureTrafficThreshold.value
|
||||
? [
|
||||
{ label: '通道阈值状态', prop: 'traffic_threshold_enabled' },
|
||||
{ label: '通道阈值单位', prop: 'traffic_threshold_unit' },
|
||||
{ label: '通道阈值数值', prop: 'traffic_threshold_value' }
|
||||
]
|
||||
: []),
|
||||
...(canUpdateCarrierStatus ? [{ label: '状态', prop: 'status' }] : []),
|
||||
{ label: '创建时间', prop: 'created_at' }
|
||||
]
|
||||
@@ -296,6 +352,9 @@
|
||||
carrier_type: null,
|
||||
description: '',
|
||||
data_reset_day: 1,
|
||||
traffic_threshold_mode: 'unconfigured',
|
||||
traffic_threshold_unit: null,
|
||||
traffic_threshold_value: null,
|
||||
realname_link_type: 'none',
|
||||
realname_link_template: ''
|
||||
})
|
||||
@@ -314,7 +373,17 @@
|
||||
prop: 'carrier_name',
|
||||
label: '运营商名称',
|
||||
minWidth: 150,
|
||||
showOverflowTooltip: true
|
||||
showOverflowTooltip: true,
|
||||
formatter: (row: Carrier) =>
|
||||
h(
|
||||
ElButton,
|
||||
{
|
||||
type: 'primary',
|
||||
link: true,
|
||||
onClick: () => void router.push(`${RoutesAlias.CarrierDetail}/${row.id}`)
|
||||
},
|
||||
() => row.carrier_name || '-'
|
||||
)
|
||||
},
|
||||
{
|
||||
prop: 'carrier_type',
|
||||
@@ -335,6 +404,28 @@
|
||||
minWidth: 200,
|
||||
showOverflowTooltip: true
|
||||
},
|
||||
...(canConfigureTrafficThreshold.value
|
||||
? [
|
||||
{
|
||||
prop: 'traffic_threshold_enabled',
|
||||
label: '通道阈值状态',
|
||||
width: 130,
|
||||
formatter: (row: Carrier) => getThresholdEnabledLabel(row.traffic_threshold_enabled)
|
||||
},
|
||||
{
|
||||
prop: 'traffic_threshold_unit',
|
||||
label: '通道阈值单位',
|
||||
width: 120,
|
||||
formatter: (row: Carrier) => row.traffic_threshold_unit || '未配置'
|
||||
},
|
||||
{
|
||||
prop: 'traffic_threshold_value',
|
||||
label: '通道阈值数值',
|
||||
width: 140,
|
||||
formatter: (row: Carrier) => formatThresholdValue(row.traffic_threshold_value)
|
||||
}
|
||||
]
|
||||
: []),
|
||||
...(canUpdateCarrierStatus
|
||||
? [
|
||||
{
|
||||
@@ -482,6 +573,9 @@
|
||||
form.carrier_type = row.carrier_type
|
||||
form.description = row.description
|
||||
form.data_reset_day = row.data_reset_day || 1
|
||||
form.traffic_threshold_mode = getThresholdMode(row.traffic_threshold_enabled)
|
||||
form.traffic_threshold_unit = row.traffic_threshold_unit ?? null
|
||||
form.traffic_threshold_value = row.traffic_threshold_value ?? null
|
||||
form.realname_link_type = row.realname_link_type || 'none'
|
||||
form.realname_link_template = row.realname_link_template || ''
|
||||
} else {
|
||||
@@ -491,6 +585,9 @@
|
||||
form.carrier_type = null
|
||||
form.description = ''
|
||||
form.data_reset_day = 1
|
||||
form.traffic_threshold_mode = 'unconfigured'
|
||||
form.traffic_threshold_unit = null
|
||||
form.traffic_threshold_value = null
|
||||
form.realname_link_type = 'none'
|
||||
form.realname_link_template = ''
|
||||
}
|
||||
@@ -501,6 +598,49 @@
|
||||
})
|
||||
}
|
||||
|
||||
const getThresholdEnabledLabel = (enabled?: 0 | 1 | null) => {
|
||||
if (enabled === 1) return '启用'
|
||||
if (enabled === 0) return '停用'
|
||||
return '未配置'
|
||||
}
|
||||
|
||||
const getThresholdMode = (enabled?: 0 | 1 | null) => {
|
||||
if (enabled === 1) return 'enabled'
|
||||
if (enabled === 0) return 'disabled'
|
||||
return 'unconfigured'
|
||||
}
|
||||
|
||||
const handleTrafficThresholdModeChange = (mode: string) => {
|
||||
if (mode === 'unconfigured') {
|
||||
form.traffic_threshold_unit = null
|
||||
form.traffic_threshold_value = null
|
||||
}
|
||||
}
|
||||
|
||||
const formatThresholdValue = (value?: number | null) =>
|
||||
value === null || value === undefined ? '未配置' : String(value)
|
||||
|
||||
const getTrafficThresholdPayload = () => {
|
||||
if (!canConfigureTrafficThreshold.value) return {}
|
||||
if (form.traffic_threshold_mode === 'unconfigured') {
|
||||
return {
|
||||
traffic_threshold_enabled: null,
|
||||
traffic_threshold_unit: null,
|
||||
traffic_threshold_value: null
|
||||
}
|
||||
}
|
||||
|
||||
if (form.traffic_threshold_mode === 'disabled') {
|
||||
return { traffic_threshold_enabled: 0 as const }
|
||||
}
|
||||
|
||||
return {
|
||||
traffic_threshold_enabled: 1 as const,
|
||||
traffic_threshold_unit: form.traffic_threshold_unit,
|
||||
traffic_threshold_value: Number(form.traffic_threshold_value)
|
||||
}
|
||||
}
|
||||
|
||||
// 处理编码生成
|
||||
const handleGenerateCarrierCode = () => {
|
||||
form.carrier_code = generateCarrierCode()
|
||||
@@ -536,11 +676,22 @@
|
||||
const handleSubmit = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return
|
||||
|
||||
if (
|
||||
canConfigureTrafficThreshold.value &&
|
||||
form.traffic_threshold_mode === 'enabled' &&
|
||||
(!form.traffic_threshold_unit ||
|
||||
!Number.isFinite(Number(form.traffic_threshold_value)) ||
|
||||
Number(form.traffic_threshold_value) <= 0)
|
||||
) {
|
||||
ElMessage.warning('启用通道阈值时,请填写阈值单位和大于0的阈值数值')
|
||||
return
|
||||
}
|
||||
|
||||
await formEl.validate(async (valid) => {
|
||||
if (valid) {
|
||||
submitLoading.value = true
|
||||
try {
|
||||
const data = {
|
||||
const data: CreateCarrierParams = {
|
||||
carrier_code: form.carrier_code,
|
||||
carrier_name: form.carrier_name,
|
||||
carrier_type: form.carrier_type as CarrierType,
|
||||
@@ -548,7 +699,8 @@
|
||||
data_reset_day: form.data_reset_day || null,
|
||||
realname_link_type: form.realname_link_type,
|
||||
realname_link_template:
|
||||
form.realname_link_type === 'template' ? form.realname_link_template : undefined
|
||||
form.realname_link_type === 'template' ? form.realname_link_template : undefined,
|
||||
...getTrafficThresholdPayload()
|
||||
}
|
||||
|
||||
if (dialogType.value === 'add') {
|
||||
@@ -561,7 +713,8 @@
|
||||
data_reset_day: form.data_reset_day || null,
|
||||
realname_link_type: form.realname_link_type,
|
||||
realname_link_template:
|
||||
form.realname_link_type === 'template' ? form.realname_link_template : undefined
|
||||
form.realname_link_type === 'template' ? form.realname_link_template : undefined,
|
||||
...getTrafficThresholdPayload()
|
||||
}
|
||||
await CarrierService.updateCarrier(form.id, updateData)
|
||||
ElMessage.success('修改成功')
|
||||
@@ -599,4 +752,28 @@
|
||||
.carrier-page {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.carrier-form-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
column-gap: 24px;
|
||||
|
||||
:deep(.el-form-item) {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.carrier-form-span {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.carrier-form-grid {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
}
|
||||
|
||||
.carrier-form-span {
|
||||
grid-column: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<CarrierTrafficThresholdHitsPage />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import CarrierTrafficThresholdHitsPage from '@/views/system/carrier-traffic-threshold-hits/index.vue'
|
||||
</script>
|
||||
421
src/views/system/carrier-traffic-threshold-hits/index.vue
Normal file
421
src/views/system/carrier-traffic-threshold-hits/index.vue
Normal file
@@ -0,0 +1,421 @@
|
||||
<template>
|
||||
<ArtTableFullScreen>
|
||||
<div class="carrier-threshold-hit-page" id="table-full-screen">
|
||||
<ElResult
|
||||
v-if="!canAccess"
|
||||
icon="warning"
|
||||
title="无访问权限"
|
||||
sub-title="仅超级管理员和平台账号可查看通道阈值命中记录"
|
||||
/>
|
||||
|
||||
<template v-else>
|
||||
<ArtSearchBar
|
||||
v-model:filter="searchForm"
|
||||
:items="searchFormItems"
|
||||
label-width="105"
|
||||
:show-expand="false"
|
||||
@reset="handleReset"
|
||||
@search="handleSearch"
|
||||
/>
|
||||
|
||||
<ElCard shadow="never" class="art-table-card">
|
||||
<ArtTableHeader
|
||||
:columnList="columnOptions"
|
||||
v-model:columns="columnChecks"
|
||||
@refresh="handleRefresh"
|
||||
/>
|
||||
|
||||
<ArtTable
|
||||
:loading="loading"
|
||||
:data="hitList"
|
||||
:currentPage="pagination.page"
|
||||
:pageSize="pagination.page_size"
|
||||
:total="pagination.total"
|
||||
:marginTop="10"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
>
|
||||
<template #default>
|
||||
<ElTableColumn v-for="col in columns" :key="col.prop || col.type" v-bind="col" />
|
||||
</template>
|
||||
</ArtTable>
|
||||
</ElCard>
|
||||
</template>
|
||||
</div>
|
||||
</ArtTableFullScreen>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, h, onMounted, reactive, ref } from 'vue'
|
||||
import { ElMessage, ElTag } from 'element-plus'
|
||||
import { CarrierService, CardService, CarrierTrafficThresholdHitService } from '@/api/modules'
|
||||
import { usePermission } from '@/composables/usePermission'
|
||||
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
||||
import { formatDateTime } from '@/utils/business/format'
|
||||
import type { SearchFormItem } from '@/types'
|
||||
import type {
|
||||
Carrier,
|
||||
CarrierTrafficThresholdHitItem,
|
||||
CarrierTrafficThresholdHitQueryParams,
|
||||
StandaloneCardQueryParams,
|
||||
StandaloneIotCard
|
||||
} from '@/types/api'
|
||||
|
||||
defineOptions({ name: 'CarrierTrafficThresholdHits' })
|
||||
|
||||
const { isPlatformAccount } = usePermission()
|
||||
const canAccess = computed(() => isPlatformAccount.value)
|
||||
const loading = ref(false)
|
||||
const carrierOptionsLoading = ref(false)
|
||||
const cardOptionsLoading = ref(false)
|
||||
const carrierOptions = ref<Carrier[]>([])
|
||||
const cardOptions = ref<StandaloneIotCard[]>([])
|
||||
const hitList = ref<CarrierTrafficThresholdHitItem[]>([])
|
||||
|
||||
const pagination = reactive({
|
||||
page: 1,
|
||||
page_size: 20,
|
||||
total: 0
|
||||
})
|
||||
|
||||
const initialSearchState = {
|
||||
carrier_id: undefined as number | undefined,
|
||||
card_id: undefined as number | undefined,
|
||||
period_start: '',
|
||||
judged_at_range: [] as string[],
|
||||
start_time: '',
|
||||
end_time: ''
|
||||
}
|
||||
|
||||
const searchForm = ref({ ...initialSearchState })
|
||||
|
||||
const preserveSelectedOption = <T extends { id: number }>(
|
||||
results: T[],
|
||||
currentOptions: T[],
|
||||
selectedId?: number
|
||||
): T[] => {
|
||||
if (!selectedId || results.some((item) => item.id === selectedId)) return results
|
||||
const selectedOption = currentOptions.find((item) => item.id === selectedId)
|
||||
return selectedOption ? [selectedOption, ...results.slice(0, 19)] : results
|
||||
}
|
||||
|
||||
const searchCarriers = async (keyword = '') => {
|
||||
if (!canAccess.value) return
|
||||
carrierOptionsLoading.value = true
|
||||
try {
|
||||
const res = await CarrierService.getCarriers({
|
||||
page: 1,
|
||||
page_size: 20,
|
||||
carrier_name: keyword.trim() || undefined
|
||||
})
|
||||
if (res.code === 0) {
|
||||
carrierOptions.value = preserveSelectedOption(
|
||||
res.data.items || [],
|
||||
carrierOptions.value,
|
||||
searchForm.value.carrier_id
|
||||
)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('搜索运营商通道失败:', error)
|
||||
} finally {
|
||||
carrierOptionsLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const searchCards = async (keyword = '') => {
|
||||
if (!canAccess.value) return
|
||||
cardOptionsLoading.value = true
|
||||
try {
|
||||
const params: StandaloneCardQueryParams = {
|
||||
page: 1,
|
||||
page_size: 20,
|
||||
iccid: keyword.trim() || undefined
|
||||
}
|
||||
const res = await CardService.getStandaloneIotCards(params)
|
||||
if (res.code === 0) {
|
||||
cardOptions.value = preserveSelectedOption(
|
||||
res.data.items || [],
|
||||
cardOptions.value,
|
||||
searchForm.value.card_id
|
||||
)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('搜索IoT卡失败:', error)
|
||||
} finally {
|
||||
cardOptionsLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const searchFormItems = computed<SearchFormItem[]>(() => [
|
||||
{
|
||||
label: '运营商通道',
|
||||
prop: 'carrier_id',
|
||||
type: 'select',
|
||||
config: {
|
||||
clearable: true,
|
||||
filterable: true,
|
||||
remote: true,
|
||||
reserveKeyword: true,
|
||||
remoteMethod: searchCarriers,
|
||||
loading: carrierOptionsLoading.value,
|
||||
placeholder: '输入运营商名称搜索'
|
||||
},
|
||||
options: () =>
|
||||
carrierOptions.value.map((carrier) => ({
|
||||
label: carrier.carrier_name,
|
||||
value: carrier.id
|
||||
}))
|
||||
},
|
||||
{
|
||||
label: 'IoT卡',
|
||||
prop: 'card_id',
|
||||
type: 'select',
|
||||
config: {
|
||||
clearable: true,
|
||||
filterable: true,
|
||||
remote: true,
|
||||
reserveKeyword: true,
|
||||
remoteMethod: searchCards,
|
||||
loading: cardOptionsLoading.value,
|
||||
placeholder: '输入ICCID搜索'
|
||||
},
|
||||
options: () =>
|
||||
cardOptions.value.map((card) => ({
|
||||
label: card.iccid,
|
||||
value: card.id
|
||||
}))
|
||||
},
|
||||
{
|
||||
label: '计费周期起点',
|
||||
prop: 'period_start',
|
||||
type: 'datetime',
|
||||
config: {
|
||||
type: 'datetime',
|
||||
format: 'YYYY-MM-DD HH:mm:ss',
|
||||
valueFormat: 'YYYY-MM-DDTHH:mm:ssZ',
|
||||
placeholder: '请选择周期起点'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '判定时间',
|
||||
prop: 'judged_at_range',
|
||||
type: 'datetimerange',
|
||||
config: {
|
||||
type: 'datetimerange',
|
||||
format: 'YYYY-MM-DD HH:mm:ss',
|
||||
valueFormat: 'YYYY-MM-DDTHH:mm:ssZ',
|
||||
rangeSeparator: '至',
|
||||
startPlaceholder: '开始时间',
|
||||
endPlaceholder: '结束时间'
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
const formatNullableNumber = (value: number | null | undefined, unit = '') => {
|
||||
if (value === null || value === undefined) return '无'
|
||||
const formatted = Number(value).toLocaleString('zh-CN', { maximumFractionDigits: 2 })
|
||||
return unit ? `${formatted} ${unit}` : formatted
|
||||
}
|
||||
|
||||
const formatThresholdValue = (row: CarrierTrafficThresholdHitItem) => {
|
||||
if (row.hit_threshold_value === null || row.hit_threshold_value === undefined) return '无'
|
||||
const value = formatNullableNumber(row.hit_threshold_value)
|
||||
return row.hit_threshold_unit ? `${value} ${row.hit_threshold_unit}` : `${value}(单位:无)`
|
||||
}
|
||||
|
||||
const statusMap: Record<string, { label: string; type: 'warning' | 'success' | 'info' }> = {
|
||||
locked: { label: '持锁中', type: 'warning' },
|
||||
unlocked: { label: '已跨期解锁', type: 'success' }
|
||||
}
|
||||
|
||||
const triggerSourceMap: Record<string, string> = {
|
||||
polling: '周期轮询',
|
||||
manual_sync: '手动同步',
|
||||
manual_override: '人工纠偏',
|
||||
carrier_callback: '运营商回调',
|
||||
business_event: '业务成功后的观测'
|
||||
}
|
||||
|
||||
const resumeResultMap: Record<string, string> = {
|
||||
resumed: '已发起复机',
|
||||
skipped: '仅解锁未复机',
|
||||
manual: '转人工核对'
|
||||
}
|
||||
|
||||
const { columnChecks, columns } = useCheckedColumns(() => [
|
||||
{ prop: 'id', label: '记录ID', width: 100 },
|
||||
{ prop: 'card_id', label: 'IoT卡ID', width: 110 },
|
||||
{ prop: 'carrier_id', label: '运营商通道ID', width: 140 },
|
||||
{
|
||||
prop: 'hit_traffic_mb',
|
||||
label: '命中流量',
|
||||
width: 140,
|
||||
formatter: (row: CarrierTrafficThresholdHitItem) =>
|
||||
formatNullableNumber(row.hit_traffic_mb, 'MB')
|
||||
},
|
||||
{
|
||||
prop: 'hit_threshold_value',
|
||||
label: '命中阈值',
|
||||
width: 140,
|
||||
formatter: formatThresholdValue
|
||||
},
|
||||
{
|
||||
prop: 'period_start',
|
||||
label: '计费周期起点',
|
||||
width: 180,
|
||||
formatter: (row: CarrierTrafficThresholdHitItem) => formatDateTime(row.period_start)
|
||||
},
|
||||
{
|
||||
prop: 'judged_at',
|
||||
label: '判定时间',
|
||||
width: 180,
|
||||
formatter: (row: CarrierTrafficThresholdHitItem) => formatDateTime(row.judged_at)
|
||||
},
|
||||
{
|
||||
prop: 'trigger_source',
|
||||
label: '触发来源',
|
||||
minWidth: 160,
|
||||
formatter: (row: CarrierTrafficThresholdHitItem) =>
|
||||
row.trigger_source ? triggerSourceMap[row.trigger_source] || row.trigger_source : '无'
|
||||
},
|
||||
{
|
||||
prop: 'status',
|
||||
label: '锁状态',
|
||||
width: 120,
|
||||
formatter: (row: CarrierTrafficThresholdHitItem) => {
|
||||
const status = statusMap[row.status]
|
||||
return h(ElTag, { type: status?.type || 'info' }, () => status?.label || row.status || '无')
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: 'unlocked_at',
|
||||
label: '解锁时间',
|
||||
width: 180,
|
||||
formatter: (row: CarrierTrafficThresholdHitItem) =>
|
||||
row.unlocked_at ? formatDateTime(row.unlocked_at) : '无'
|
||||
},
|
||||
{
|
||||
prop: 'resume_result',
|
||||
label: '复机结果',
|
||||
minWidth: 150,
|
||||
formatter: (row: CarrierTrafficThresholdHitItem) =>
|
||||
row.resume_result
|
||||
? resumeResultMap[row.resume_result] || row.resume_result
|
||||
: '未处理或历史记录'
|
||||
},
|
||||
{
|
||||
prop: 'failure_reason',
|
||||
label: '安全原因',
|
||||
minWidth: 180,
|
||||
showOverflowTooltip: true,
|
||||
formatter: (row: CarrierTrafficThresholdHitItem) => row.failure_reason || '无'
|
||||
},
|
||||
{
|
||||
prop: 'created_at',
|
||||
label: '记录创建时间',
|
||||
width: 180,
|
||||
formatter: (row: CarrierTrafficThresholdHitItem) => formatDateTime(row.created_at)
|
||||
}
|
||||
])
|
||||
|
||||
const columnOptions = [
|
||||
{ label: '记录ID', prop: 'id' },
|
||||
{ label: 'IoT卡ID', prop: 'card_id' },
|
||||
{ label: '运营商通道ID', prop: 'carrier_id' },
|
||||
{ label: '命中流量', prop: 'hit_traffic_mb' },
|
||||
{ label: '命中阈值', prop: 'hit_threshold_value' },
|
||||
{ label: '计费周期起点', prop: 'period_start' },
|
||||
{ label: '判定时间', prop: 'judged_at' },
|
||||
{ label: '触发来源', prop: 'trigger_source' },
|
||||
{ label: '锁状态', prop: 'status' },
|
||||
{ label: '解锁时间', prop: 'unlocked_at' },
|
||||
{ label: '复机结果', prop: 'resume_result' },
|
||||
{ label: '安全原因', prop: 'failure_reason' },
|
||||
{ label: '记录创建时间', prop: 'created_at' }
|
||||
]
|
||||
|
||||
const syncJudgedAtRange = () => {
|
||||
if (
|
||||
Array.isArray(searchForm.value.judged_at_range) &&
|
||||
searchForm.value.judged_at_range.length === 2
|
||||
) {
|
||||
searchForm.value.start_time = searchForm.value.judged_at_range[0]
|
||||
searchForm.value.end_time = searchForm.value.judged_at_range[1]
|
||||
} else {
|
||||
searchForm.value.start_time = ''
|
||||
searchForm.value.end_time = ''
|
||||
}
|
||||
}
|
||||
|
||||
const getTableData = async () => {
|
||||
if (!canAccess.value) return
|
||||
|
||||
const params: CarrierTrafficThresholdHitQueryParams = {
|
||||
carrier_id: searchForm.value.carrier_id ?? undefined,
|
||||
card_id: searchForm.value.card_id ?? undefined,
|
||||
period_start: searchForm.value.period_start || undefined,
|
||||
start_time: searchForm.value.start_time || undefined,
|
||||
end_time: searchForm.value.end_time || undefined,
|
||||
page: pagination.page,
|
||||
page_size: pagination.page_size
|
||||
}
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await CarrierTrafficThresholdHitService.getHits(params)
|
||||
if (res.code === 0 && res.data) {
|
||||
hitList.value = res.data.items || []
|
||||
pagination.page = res.data.page || pagination.page
|
||||
pagination.page_size = res.data.size || pagination.page_size
|
||||
pagination.total = res.data.total || 0
|
||||
} else {
|
||||
ElMessage.error(res.msg || '查询通道阈值命中记录失败')
|
||||
}
|
||||
} catch (error: any) {
|
||||
if (error?.status === 403 || error?.response?.status === 403) {
|
||||
ElMessage.error('当前账号无权查看通道阈值命中记录')
|
||||
} else {
|
||||
ElMessage.error('查询通道阈值命中记录失败,请稍后重试')
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleSearch = () => {
|
||||
syncJudgedAtRange()
|
||||
pagination.page = 1
|
||||
void getTableData()
|
||||
}
|
||||
|
||||
const handleReset = () => {
|
||||
searchForm.value = { ...initialSearchState, judged_at_range: [] }
|
||||
pagination.page = 1
|
||||
void getTableData()
|
||||
}
|
||||
|
||||
const handleRefresh = () => void getTableData()
|
||||
const handleSizeChange = (pageSize: number) => {
|
||||
pagination.page_size = Math.min(pageSize, 100)
|
||||
pagination.page = 1
|
||||
void getTableData()
|
||||
}
|
||||
const handleCurrentChange = (page: number) => {
|
||||
pagination.page = page
|
||||
void getTableData()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (canAccess.value) {
|
||||
void getTableData()
|
||||
void searchCarriers()
|
||||
void searchCards()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.carrier-threshold-hit-page {
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user