894 lines
29 KiB
Vue
894 lines
29 KiB
Vue
<template>
|
||
<ArtTableFullScreen>
|
||
<div class="single-card-page" id="table-full-screen">
|
||
<!-- 标识符查询区域 -->
|
||
<ElCard shadow="never" class="search-card" style="margin-bottom: 16px">
|
||
<template #header>
|
||
<span>资产查询</span>
|
||
</template>
|
||
<div class="search-box">
|
||
<ElInput
|
||
v-model="searchIdentifier"
|
||
placeholder="请输入虚拟号、ICCID、IMEI、SN或MSISDN"
|
||
clearable
|
||
style="width: 500px; margin-right: 16px"
|
||
@keyup.enter="handleSearchAsset"
|
||
>
|
||
<template #prepend>标识符</template>
|
||
</ElInput>
|
||
<ElButton type="primary" @click="handleSearchAsset" :loading="loading">查询</ElButton>
|
||
<ElButton
|
||
@click="handleRefreshAsset"
|
||
:loading="refreshing"
|
||
:disabled="!assetInfo.asset_id"
|
||
>
|
||
<ElIcon><Refresh /></ElIcon>
|
||
刷新数据
|
||
</ElButton>
|
||
</div>
|
||
<!-- 格式化显示的标识符 -->
|
||
<div v-if="formattedIdentifier" class="formatted-identifier">
|
||
{{ formattedIdentifier }}
|
||
</div>
|
||
</ElCard>
|
||
|
||
<!-- 资产信息卡片 -->
|
||
<ElCard shadow="never" class="asset-info-card" style="margin-bottom: 16px">
|
||
<template #header>
|
||
<div class="card-header">
|
||
<span>资产信息</span>
|
||
<ElTag
|
||
v-if="assetInfo.asset_type"
|
||
:type="assetInfo.asset_type === 'card' ? 'success' : 'primary'"
|
||
>
|
||
{{ assetInfo.asset_type === 'card' ? 'IoT卡' : '设备' }}
|
||
</ElTag>
|
||
</div>
|
||
</template>
|
||
<div v-if="!hasSearched" class="empty-state">
|
||
<p style="text-align: center; color: var(--el-text-color-secondary); padding: 40px">
|
||
请在上方输入标识符进行查询
|
||
</p>
|
||
</div>
|
||
<ElForm v-else :model="assetInfo" label-width="140px" :inline="false">
|
||
<!-- 通用信息 -->
|
||
<ElRow :gutter="24">
|
||
<ElCol :span="8">
|
||
<ElFormItem label="资产ID:">
|
||
<span>{{ assetInfo.asset_id || '-' }}</span>
|
||
</ElFormItem>
|
||
</ElCol>
|
||
<ElCol :span="8">
|
||
<ElFormItem label="虚拟号:">
|
||
<span>{{ assetInfo.virtual_no || '-' }}</span>
|
||
</ElFormItem>
|
||
</ElCol>
|
||
<ElCol :span="8">
|
||
<ElFormItem label="状态:">
|
||
<ElTag v-if="assetInfo.status" :type="getStatusTagType(assetInfo.status)">
|
||
{{ getStatusName(assetInfo.status) }}
|
||
</ElTag>
|
||
<span v-else>-</span>
|
||
</ElFormItem>
|
||
</ElCol>
|
||
</ElRow>
|
||
<ElRow :gutter="24">
|
||
<ElCol :span="8">
|
||
<ElFormItem label="所属店铺:">
|
||
<span>{{ assetInfo.shop_name || '-' }}</span>
|
||
</ElFormItem>
|
||
</ElCol>
|
||
<ElCol :span="8">
|
||
<ElFormItem label="套餐系列:">
|
||
<span>{{ assetInfo.series_name || '-' }}</span>
|
||
</ElFormItem>
|
||
</ElCol>
|
||
<ElCol :span="8">
|
||
<ElFormItem label="批次号:">
|
||
<span>{{ assetInfo.batch_no || '-' }}</span>
|
||
</ElFormItem>
|
||
</ElCol>
|
||
</ElRow>
|
||
<ElRow :gutter="24">
|
||
<ElCol :span="8">
|
||
<ElFormItem label="实名状态:">
|
||
<ElTag :type="getRealNameStatusTagType(assetInfo.real_name_status)">
|
||
{{ getRealNameStatusName(assetInfo.real_name_status) }}
|
||
</ElTag>
|
||
</ElFormItem>
|
||
</ElCol>
|
||
<ElCol :span="8" v-if="assetInfo.asset_type === 'card'">
|
||
<ElFormItem label="网络状态:">
|
||
<ElTag :type="assetInfo.network_status === 1 ? 'success' : 'danger'">
|
||
{{ assetInfo.network_status === 1 ? '开机' : '停机' }}
|
||
</ElTag>
|
||
</ElFormItem>
|
||
</ElCol>
|
||
<ElCol :span="8" v-if="assetInfo.asset_type === 'device'">
|
||
<ElFormItem label="保护期状态:">
|
||
<ElTag :type="getProtectStatusTagType(assetInfo.device_protect_status)">
|
||
{{ getProtectStatusName(assetInfo.device_protect_status) }}
|
||
</ElTag>
|
||
</ElFormItem>
|
||
</ElCol>
|
||
<ElCol :span="8">
|
||
<ElFormItem label="激活时间:">
|
||
<span>{{ assetInfo.activated_at || '-' }}</span>
|
||
</ElFormItem>
|
||
</ElCol>
|
||
</ElRow>
|
||
|
||
<!-- 卡专属字段 -->
|
||
<template v-if="assetInfo.asset_type === 'card'">
|
||
<ElDivider content-position="left">卡信息</ElDivider>
|
||
<ElRow :gutter="24">
|
||
<ElCol :span="8">
|
||
<ElFormItem label="ICCID:">
|
||
<span>{{ assetInfo.iccid || '-' }}</span>
|
||
</ElFormItem>
|
||
</ElCol>
|
||
<ElCol :span="8">
|
||
<ElFormItem label="IMSI:">
|
||
<span>{{ assetInfo.imsi || '-' }}</span>
|
||
</ElFormItem>
|
||
</ElCol>
|
||
<ElCol :span="8">
|
||
<ElFormItem label="手机号:">
|
||
<span>{{ assetInfo.msisdn || '-' }}</span>
|
||
</ElFormItem>
|
||
</ElCol>
|
||
</ElRow>
|
||
<ElRow :gutter="24">
|
||
<ElCol :span="8">
|
||
<ElFormItem label="运营商:">
|
||
<span>{{ assetInfo.carrier_name || '-' }}</span>
|
||
</ElFormItem>
|
||
</ElCol>
|
||
<ElCol :span="8">
|
||
<ElFormItem label="供应商:">
|
||
<span>{{ assetInfo.supplier || '-' }}</span>
|
||
</ElFormItem>
|
||
</ElCol>
|
||
<ElCol :span="8">
|
||
<ElFormItem label="绑定设备:">
|
||
<span>{{ assetInfo.bound_device_name || '-' }}</span>
|
||
</ElFormItem>
|
||
</ElCol>
|
||
</ElRow>
|
||
</template>
|
||
|
||
<!-- 设备专属字段 -->
|
||
<template v-if="assetInfo.asset_type === 'device'">
|
||
<ElDivider content-position="left">设备信息</ElDivider>
|
||
<ElRow :gutter="24">
|
||
<ElCol :span="8">
|
||
<ElFormItem label="设备名称:">
|
||
<span>{{ assetInfo.device_name || '-' }}</span>
|
||
</ElFormItem>
|
||
</ElCol>
|
||
<ElCol :span="8">
|
||
<ElFormItem label="设备型号:">
|
||
<span>{{ assetInfo.device_model || '-' }}</span>
|
||
</ElFormItem>
|
||
</ElCol>
|
||
<ElCol :span="8">
|
||
<ElFormItem label="设备类型:">
|
||
<span>{{ assetInfo.device_type || '-' }}</span>
|
||
</ElFormItem>
|
||
</ElCol>
|
||
</ElRow>
|
||
<ElRow :gutter="24">
|
||
<ElCol :span="8">
|
||
<ElFormItem label="制造商:">
|
||
<span>{{ assetInfo.manufacturer || '-' }}</span>
|
||
</ElFormItem>
|
||
</ElCol>
|
||
<ElCol :span="8">
|
||
<ElFormItem label="IMEI:">
|
||
<span>{{ assetInfo.imei || '-' }}</span>
|
||
</ElFormItem>
|
||
</ElCol>
|
||
<ElCol :span="8">
|
||
<ElFormItem label="序列号:">
|
||
<span>{{ assetInfo.sn || '-' }}</span>
|
||
</ElFormItem>
|
||
</ElCol>
|
||
</ElRow>
|
||
<ElRow :gutter="24">
|
||
<ElCol :span="8">
|
||
<ElFormItem label="最大插槽数:">
|
||
<span>{{ assetInfo.max_sim_slots || '-' }}</span>
|
||
</ElFormItem>
|
||
</ElCol>
|
||
<ElCol :span="8">
|
||
<ElFormItem label="已绑定卡数:">
|
||
<span>{{ assetInfo.bound_card_count || 0 }}</span>
|
||
</ElFormItem>
|
||
</ElCol>
|
||
</ElRow>
|
||
</template>
|
||
</ElForm>
|
||
</ElCard>
|
||
|
||
<!-- 操作区域 -->
|
||
<ElCard
|
||
v-if="hasSearched && assetInfo.asset_id"
|
||
shadow="never"
|
||
class="operation-card"
|
||
style="margin-bottom: 16px"
|
||
>
|
||
<template #header>
|
||
<span>操作区域</span>
|
||
</template>
|
||
<div class="operation-buttons">
|
||
<!-- 卡操作 -->
|
||
<template v-if="assetInfo.asset_type === 'card'">
|
||
<ElButton
|
||
type="success"
|
||
@click="handleStartCard"
|
||
:disabled="assetInfo.network_status === 1"
|
||
>
|
||
复机
|
||
</ElButton>
|
||
<ElButton
|
||
type="warning"
|
||
@click="handleStopCard"
|
||
:disabled="assetInfo.network_status === 0"
|
||
>
|
||
停机
|
||
</ElButton>
|
||
</template>
|
||
<!-- 设备操作 -->
|
||
<template v-if="assetInfo.asset_type === 'device'">
|
||
<ElButton
|
||
type="success"
|
||
@click="handleStartDevice"
|
||
:disabled="assetInfo.device_protect_status === 'stop'"
|
||
>
|
||
批量复机
|
||
</ElButton>
|
||
<ElButton
|
||
type="warning"
|
||
@click="handleStopDevice"
|
||
:disabled="assetInfo.device_protect_status === 'start'"
|
||
>
|
||
批量停机
|
||
</ElButton>
|
||
</template>
|
||
<ElButton type="primary" @click="loadPackages">查看套餐</ElButton>
|
||
</div>
|
||
</ElCard>
|
||
|
||
<!-- 流量/套餐信息 -->
|
||
<ElCard
|
||
v-if="hasSearched && currentPackage"
|
||
shadow="never"
|
||
class="traffic-card"
|
||
style="margin-bottom: 16px"
|
||
>
|
||
<template #header>
|
||
<span>当前套餐信息</span>
|
||
</template>
|
||
<ElRow :gutter="24">
|
||
<ElCol :span="6">
|
||
<div class="info-item">
|
||
<div class="info-label">套餐名称</div>
|
||
<div class="info-value">{{ assetInfo.current_package || '-' }}</div>
|
||
</div>
|
||
</ElCol>
|
||
<ElCol :span="6">
|
||
<div class="info-item">
|
||
<div class="info-label">总流量</div>
|
||
<div class="info-value">{{ formatMB(assetInfo.package_total_mb) }}</div>
|
||
</div>
|
||
</ElCol>
|
||
<ElCol :span="6">
|
||
<div class="info-item">
|
||
<div class="info-label">已用流量</div>
|
||
<div class="info-value used">{{ formatMB(assetInfo.package_used_mb) }}</div>
|
||
</div>
|
||
</ElCol>
|
||
<ElCol :span="6">
|
||
<div class="info-item">
|
||
<div class="info-label">剩余流量</div>
|
||
<div class="info-value remaining">{{ formatMB(assetInfo.package_remain_mb) }}</div>
|
||
</div>
|
||
</ElCol>
|
||
</ElRow>
|
||
<ElProgress
|
||
:percentage="getUsagePercentage()"
|
||
:color="getProgressColor()"
|
||
style="margin-top: 16px"
|
||
/>
|
||
</ElCard>
|
||
|
||
<!-- 绑定卡列表(仅设备) -->
|
||
<ElCard
|
||
v-if="
|
||
hasSearched &&
|
||
assetInfo.asset_type === 'device' &&
|
||
assetInfo.cards &&
|
||
assetInfo.cards.length
|
||
"
|
||
shadow="never"
|
||
style="margin-bottom: 16px"
|
||
>
|
||
<template #header>
|
||
<span>绑定卡列表</span>
|
||
</template>
|
||
<ElTable :data="assetInfo.cards" border stripe>
|
||
<ElTableColumn prop="iccid" label="ICCID" min-width="180" />
|
||
<ElTableColumn prop="msisdn" label="手机号" width="120" />
|
||
<ElTableColumn prop="slot_position" label="插槽位置" width="100" align="center" />
|
||
<ElTableColumn prop="network_status" label="网络状态" width="100" align="center">
|
||
<template #default="{ row }">
|
||
<ElTag :type="row.network_status === 1 ? 'success' : 'danger'">
|
||
{{ row.network_status === 1 ? '开机' : '停机' }}
|
||
</ElTag>
|
||
</template>
|
||
</ElTableColumn>
|
||
<ElTableColumn prop="real_name_status" label="实名状态" width="100" align="center">
|
||
<template #default="{ row }">
|
||
<ElTag :type="getRealNameStatusTagType(row.real_name_status)">
|
||
{{ getRealNameStatusName(row.real_name_status) }}
|
||
</ElTag>
|
||
</template>
|
||
</ElTableColumn>
|
||
</ElTable>
|
||
</ElCard>
|
||
|
||
<!-- 套餐列表对话框 -->
|
||
<ElDialog v-model="packagesDialogVisible" title="套餐列表" width="90%" align-center>
|
||
<ElTable :data="packages" border stripe v-loading="packagesLoading">
|
||
<ElTableColumn prop="package_usage_id" label="使用记录ID" width="110" align="center" />
|
||
<ElTableColumn prop="package_id" label="套餐ID" width="90" align="center" />
|
||
<ElTableColumn
|
||
prop="package_name"
|
||
label="套餐名称"
|
||
min-width="150"
|
||
show-overflow-tooltip
|
||
/>
|
||
<ElTableColumn prop="package_type" label="套餐类型" width="100" align="center">
|
||
<template #default="{ row }">
|
||
<ElTag :type="row.package_type === 'formal' ? 'primary' : 'warning'" size="small">
|
||
{{ row.package_type === 'formal' ? '正式套餐' : '加油包' }}
|
||
</ElTag>
|
||
</template>
|
||
</ElTableColumn>
|
||
<ElTableColumn prop="usage_type" label="使用类型" width="110" align="center">
|
||
<template #default="{ row }">
|
||
<ElTag :type="row.usage_type === 'single_card' ? 'success' : 'info'" size="small">
|
||
{{ row.usage_type === 'single_card' ? '单卡' : '设备' }}
|
||
</ElTag>
|
||
</template>
|
||
</ElTableColumn>
|
||
<ElTableColumn prop="status_name" label="状态" width="100" align="center">
|
||
<template #default="{ row }">
|
||
<ElTag :type="getPackageStatusTagType(row.status)" size="small">
|
||
{{ row.status_name }}
|
||
</ElTag>
|
||
</template>
|
||
</ElTableColumn>
|
||
<ElTableColumn prop="data_limit_mb" label="真流量总量" width="120" align="right">
|
||
<template #default="{ row }">
|
||
{{ formatMB(row.data_limit_mb) }}
|
||
</template>
|
||
</ElTableColumn>
|
||
<ElTableColumn prop="data_usage_mb" label="真流量已用" width="120" align="right">
|
||
<template #default="{ row }">
|
||
{{ formatMB(row.data_usage_mb) }}
|
||
</template>
|
||
</ElTableColumn>
|
||
<ElTableColumn prop="virtual_limit_mb" label="虚流量总量" width="120" align="right">
|
||
<template #default="{ row }">
|
||
{{ formatMB(row.virtual_limit_mb) }}
|
||
</template>
|
||
</ElTableColumn>
|
||
<ElTableColumn prop="virtual_used_mb" label="虚流量已用" width="120" align="right">
|
||
<template #default="{ row }">
|
||
{{ formatMB(row.virtual_used_mb) }}
|
||
</template>
|
||
</ElTableColumn>
|
||
<ElTableColumn prop="virtual_remain_mb" label="虚流量剩余" width="120" align="right">
|
||
<template #default="{ row }">
|
||
{{ formatMB(row.virtual_remain_mb) }}
|
||
</template>
|
||
</ElTableColumn>
|
||
<ElTableColumn prop="virtual_ratio" label="虚流量比例" width="100" align="center">
|
||
<template #default="{ row }">
|
||
{{ row.virtual_ratio?.toFixed(2) || '1.00' }}
|
||
</template>
|
||
</ElTableColumn>
|
||
<ElTableColumn prop="priority" label="优先级" width="80" align="center" />
|
||
<ElTableColumn prop="master_usage_id" label="主套餐ID" width="100" align="center">
|
||
<template #default="{ row }">
|
||
{{ row.master_usage_id || '-' }}
|
||
</template>
|
||
</ElTableColumn>
|
||
<ElTableColumn prop="activated_at" label="激活时间" width="170" show-overflow-tooltip>
|
||
<template #default="{ row }">
|
||
{{ formatDateTime(row.activated_at) }}
|
||
</template>
|
||
</ElTableColumn>
|
||
<ElTableColumn prop="expires_at" label="到期时间" width="170" show-overflow-tooltip>
|
||
<template #default="{ row }">
|
||
{{ formatDateTime(row.expires_at) }}
|
||
</template>
|
||
</ElTableColumn>
|
||
<ElTableColumn prop="created_at" label="创建时间" width="170" show-overflow-tooltip>
|
||
<template #default="{ row }">
|
||
{{ formatDateTime(row.created_at) }}
|
||
</template>
|
||
</ElTableColumn>
|
||
</ElTable>
|
||
</ElDialog>
|
||
</div>
|
||
</ArtTableFullScreen>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { Refresh } from '@element-plus/icons-vue'
|
||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||
import { useRoute } from 'vue-router'
|
||
import { AssetService } from '@/api/modules'
|
||
import { formatDateTime } from '@/utils/business/format'
|
||
import type {
|
||
AssetResolveResponse,
|
||
AssetPackageUsageRecord,
|
||
AssetCurrentPackageResponse
|
||
} from '@/types/api'
|
||
|
||
defineOptions({ name: 'SingleCard' })
|
||
|
||
const loading = ref(false)
|
||
const refreshing = ref(false)
|
||
const packagesDialogVisible = ref(false)
|
||
const packagesLoading = ref(false)
|
||
const route = useRoute()
|
||
|
||
// 标识符搜索相关
|
||
const searchIdentifier = ref('')
|
||
const hasSearched = ref(false)
|
||
|
||
// 资产信息
|
||
const assetInfo = ref<Partial<AssetResolveResponse>>({})
|
||
|
||
// 当前套餐
|
||
const currentPackage = ref<AssetCurrentPackageResponse | null>(null)
|
||
|
||
// 套餐列表
|
||
const packages = ref<AssetPackageUsageRecord[]>([])
|
||
|
||
// 格式化显示的标识符
|
||
const formattedIdentifier = computed(() => {
|
||
const identifier = assetInfo.value.virtual_no || assetInfo.value.iccid || searchIdentifier.value
|
||
if (!identifier) return ''
|
||
// 如果是ICCID(19-20位数字),进行格式化
|
||
if (/^\d{19,20}$/.test(identifier)) {
|
||
return identifier.replace(/(\d{4})(?=\d)/g, '$1-')
|
||
}
|
||
return identifier
|
||
})
|
||
|
||
// 获取状态标签类型
|
||
const getStatusTagType = (status: number) => {
|
||
const map: Record<number, any> = {
|
||
1: 'info',
|
||
2: 'success',
|
||
3: 'success',
|
||
4: 'danger'
|
||
}
|
||
return map[status] || 'info'
|
||
}
|
||
|
||
// 获取状态名称
|
||
const getStatusName = (status: number) => {
|
||
const map: Record<number, string> = {
|
||
1: '在库',
|
||
2: '已分销',
|
||
3: '已激活',
|
||
4: '已停用'
|
||
}
|
||
return map[status] || '未知'
|
||
}
|
||
|
||
// 获取实名状态标签类型
|
||
const getRealNameStatusTagType = (status: number) => {
|
||
const map: Record<number, any> = {
|
||
0: 'info',
|
||
1: 'warning',
|
||
2: 'success'
|
||
}
|
||
return map[status] || 'info'
|
||
}
|
||
|
||
// 获取实名状态名称
|
||
const getRealNameStatusName = (status: number) => {
|
||
const map: Record<number, string> = {
|
||
0: '未实名',
|
||
1: '实名中',
|
||
2: '已实名'
|
||
}
|
||
return map[status] || '未知'
|
||
}
|
||
|
||
// 获取保护期状态标签类型
|
||
const getProtectStatusTagType = (status?: string) => {
|
||
const map: Record<string, any> = {
|
||
none: 'info',
|
||
stop: 'warning',
|
||
start: 'success'
|
||
}
|
||
return map[status || 'none'] || 'info'
|
||
}
|
||
|
||
// 获取保护期状态名称
|
||
const getProtectStatusName = (status?: string) => {
|
||
const map: Record<string, string> = {
|
||
none: '无保护期',
|
||
stop: '停机保护期',
|
||
start: '复机保护期'
|
||
}
|
||
return map[status || 'none'] || '无'
|
||
}
|
||
|
||
// 获取套餐状态标签类型
|
||
const getPackageStatusTagType = (status: number) => {
|
||
const map: Record<number, any> = {
|
||
0: 'info',
|
||
1: 'success',
|
||
2: 'warning',
|
||
3: 'danger',
|
||
4: 'info'
|
||
}
|
||
return map[status] || 'info'
|
||
}
|
||
|
||
// 格式化MB
|
||
const formatMB = (mb: number | undefined) => {
|
||
if (mb === undefined || mb === null) return '0MB'
|
||
if (mb >= 1024) {
|
||
return `${(mb / 1024).toFixed(2)}GB`
|
||
}
|
||
return `${mb.toFixed(2)}MB`
|
||
}
|
||
|
||
// 获取使用百分比
|
||
const getUsagePercentage = () => {
|
||
const total = assetInfo.value.package_total_mb || 0
|
||
const used = assetInfo.value.package_used_mb || 0
|
||
if (total === 0) return 0
|
||
return Math.min(Math.round((used / total) * 100), 100)
|
||
}
|
||
|
||
// 获取进度条颜色
|
||
const getProgressColor = () => {
|
||
const percentage = getUsagePercentage()
|
||
if (percentage >= 90) return '#F56C6C'
|
||
if (percentage >= 70) return '#E6A23C'
|
||
return '#67C23A'
|
||
}
|
||
|
||
// 处理资产搜索
|
||
const handleSearchAsset = async () => {
|
||
if (!searchIdentifier.value.trim()) {
|
||
ElMessage.warning('请输入资产标识符')
|
||
return
|
||
}
|
||
await loadAssetInfo(searchIdentifier.value.trim())
|
||
}
|
||
|
||
// 加载资产信息
|
||
const loadAssetInfo = async (identifier: string) => {
|
||
loading.value = true
|
||
hasSearched.value = false
|
||
currentPackage.value = null
|
||
try {
|
||
const response = await AssetService.resolveAsset(identifier)
|
||
if (response.code === 200 && response.data) {
|
||
assetInfo.value = response.data
|
||
hasSearched.value = true
|
||
|
||
// 如果有当前套餐,尝试加载套餐详情
|
||
if (response.data.current_package) {
|
||
await loadCurrentPackage()
|
||
}
|
||
|
||
ElMessage.success('查询成功')
|
||
} else {
|
||
ElMessage.error(response.message || '查询失败')
|
||
}
|
||
} catch (error: any) {
|
||
console.error('获取资产信息失败:', error)
|
||
if (error?.response?.status === 404) {
|
||
ElMessage.error('未找到该资产')
|
||
} else if (error?.response?.status === 403) {
|
||
ElMessage.error('无权限访问该资产')
|
||
} else {
|
||
ElMessage.error(error?.message || '获取资产信息失败')
|
||
}
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
// 刷新资产数据
|
||
const handleRefreshAsset = async () => {
|
||
if (!assetInfo.value.asset_type || !assetInfo.value.asset_id) {
|
||
ElMessage.warning('请先查询资产')
|
||
return
|
||
}
|
||
|
||
refreshing.value = true
|
||
try {
|
||
const response = await AssetService.refreshAsset(
|
||
assetInfo.value.asset_type,
|
||
assetInfo.value.asset_id
|
||
)
|
||
if (response.code === 200) {
|
||
ElMessage.success('数据刷新成功')
|
||
// 刷新完成后重新加载资产信息
|
||
if (assetInfo.value.virtual_no) {
|
||
await loadAssetInfo(assetInfo.value.virtual_no)
|
||
}
|
||
} else {
|
||
ElMessage.error(response.message || '刷新失败')
|
||
}
|
||
} catch (error: any) {
|
||
console.error('刷新数据失败:', error)
|
||
if (error?.response?.status === 429) {
|
||
ElMessage.warning('刷新太频繁,请稍后再试(30秒冷却)')
|
||
} else {
|
||
ElMessage.error(error?.message || '刷新数据失败')
|
||
}
|
||
} finally {
|
||
refreshing.value = false
|
||
}
|
||
}
|
||
|
||
// 加载当前套餐
|
||
const loadCurrentPackage = async () => {
|
||
if (!assetInfo.value.asset_type || !assetInfo.value.asset_id) return
|
||
|
||
try {
|
||
const response = await AssetService.getCurrentPackage(
|
||
assetInfo.value.asset_type,
|
||
assetInfo.value.asset_id
|
||
)
|
||
if (response.code === 200 && response.data) {
|
||
currentPackage.value = response.data
|
||
}
|
||
} catch (error: any) {
|
||
// 404表示无生效套餐,不显示错误
|
||
if (error?.response?.status !== 404) {
|
||
console.error('获取当前套餐失败:', error)
|
||
}
|
||
}
|
||
}
|
||
|
||
// 加载套餐列表
|
||
const loadPackages = async () => {
|
||
if (!assetInfo.value.asset_type || !assetInfo.value.asset_id) {
|
||
ElMessage.warning('请先查询资产')
|
||
return
|
||
}
|
||
|
||
packagesDialogVisible.value = true
|
||
packagesLoading.value = true
|
||
try {
|
||
const response = await AssetService.getAssetPackages(
|
||
assetInfo.value.asset_type,
|
||
assetInfo.value.asset_id
|
||
)
|
||
if (response.code === 200 && response.data) {
|
||
packages.value = response.data
|
||
} else {
|
||
ElMessage.error(response.message || '获取套餐列表失败')
|
||
}
|
||
} catch (error: any) {
|
||
console.error('获取套餐列表失败:', error)
|
||
ElMessage.error(error?.message || '获取套餐列表失败')
|
||
} finally {
|
||
packagesLoading.value = false
|
||
}
|
||
}
|
||
|
||
// 单卡复机
|
||
const handleStartCard = async () => {
|
||
if (!assetInfo.value.iccid) return
|
||
|
||
try {
|
||
await ElMessageBox.confirm('确定要复机该卡吗?', '复机确认', {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'warning'
|
||
})
|
||
|
||
await AssetService.startCard(assetInfo.value.iccid)
|
||
ElMessage.success('复机成功')
|
||
|
||
// 重新加载资产信息
|
||
if (assetInfo.value.virtual_no) {
|
||
await loadAssetInfo(assetInfo.value.virtual_no)
|
||
}
|
||
} catch (error: any) {
|
||
if (error !== 'cancel') {
|
||
if (error?.response?.status === 403) {
|
||
ElMessage.error('该卡绑定的设备在保护期内,无法复机')
|
||
} else {
|
||
ElMessage.error(error?.message || '复机失败')
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 单卡停机
|
||
const handleStopCard = async () => {
|
||
if (!assetInfo.value.iccid) return
|
||
|
||
try {
|
||
await ElMessageBox.confirm('确定要停机该卡吗?', '停机确认', {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'warning'
|
||
})
|
||
|
||
await AssetService.stopCard(assetInfo.value.iccid)
|
||
ElMessage.success('停机成功')
|
||
|
||
// 重新加载资产信息
|
||
if (assetInfo.value.virtual_no) {
|
||
await loadAssetInfo(assetInfo.value.virtual_no)
|
||
}
|
||
} catch (error: any) {
|
||
if (error !== 'cancel') {
|
||
if (error?.response?.status === 403) {
|
||
ElMessage.error('该卡绑定的设备在保护期内,无法停机')
|
||
} else {
|
||
ElMessage.error(error?.message || '停机失败')
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 设备批量复机
|
||
const handleStartDevice = async () => {
|
||
if (!assetInfo.value.asset_id) return
|
||
|
||
try {
|
||
await ElMessageBox.confirm(
|
||
'确定要批量复机设备下所有已实名卡吗?复机成功后将设置1小时复机保护期。',
|
||
'批量复机确认',
|
||
{
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'warning'
|
||
}
|
||
)
|
||
|
||
await AssetService.startDevice(assetInfo.value.asset_id)
|
||
ElMessage.success('批量复机成功')
|
||
|
||
// 重新加载资产信息
|
||
if (assetInfo.value.virtual_no) {
|
||
await loadAssetInfo(assetInfo.value.virtual_no)
|
||
}
|
||
} catch (error: any) {
|
||
if (error !== 'cancel') {
|
||
ElMessage.error(error?.message || '批量复机失败')
|
||
}
|
||
}
|
||
}
|
||
|
||
// 设备批量停机
|
||
const handleStopDevice = async () => {
|
||
if (!assetInfo.value.asset_id) return
|
||
|
||
try {
|
||
await ElMessageBox.confirm(
|
||
'确定要批量停机设备下所有已实名卡吗?停机成功后将设置1小时停机保护期。',
|
||
'批量停机确认',
|
||
{
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'warning'
|
||
}
|
||
)
|
||
|
||
const response = await AssetService.stopDevice(assetInfo.value.asset_id)
|
||
if (response.code === 200 && response.data) {
|
||
const { success_count, failed_cards } = response.data
|
||
if (failed_cards && failed_cards.length > 0) {
|
||
ElMessage.warning(`停机部分成功,成功${success_count}张,失败${failed_cards.length}张`)
|
||
} else {
|
||
ElMessage.success(`批量停机成功,共停机${success_count}张卡`)
|
||
}
|
||
}
|
||
|
||
// 重新加载资产信息
|
||
if (assetInfo.value.virtual_no) {
|
||
await loadAssetInfo(assetInfo.value.virtual_no)
|
||
}
|
||
} catch (error: any) {
|
||
if (error !== 'cancel') {
|
||
ElMessage.error(error?.message || '批量停机失败')
|
||
}
|
||
}
|
||
}
|
||
|
||
onMounted(() => {
|
||
// 检查是否有传递的标识符参数
|
||
const identifierFromQuery = (route.query.iccid || route.query.identifier) as string
|
||
if (identifierFromQuery) {
|
||
searchIdentifier.value = identifierFromQuery
|
||
loadAssetInfo(identifierFromQuery)
|
||
}
|
||
})
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.single-card-page {
|
||
.search-card {
|
||
.search-box {
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
.formatted-identifier {
|
||
margin-top: 16px;
|
||
padding: 16px;
|
||
background-color: var(--el-fill-color-light);
|
||
border-radius: 4px;
|
||
font-size: 28px;
|
||
font-weight: 600;
|
||
letter-spacing: 2px;
|
||
text-align: center;
|
||
color: var(--el-color-primary);
|
||
font-family: 'Courier New', Courier, monospace;
|
||
}
|
||
}
|
||
|
||
.card-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
}
|
||
|
||
.operation-buttons {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 12px;
|
||
}
|
||
|
||
.traffic-card,
|
||
.asset-info-card {
|
||
.info-item {
|
||
padding: 16px;
|
||
text-align: center;
|
||
border-radius: 4px;
|
||
background-color: var(--el-fill-color-light);
|
||
|
||
.info-label {
|
||
font-size: 14px;
|
||
color: var(--el-text-color-regular);
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.info-value {
|
||
font-size: 20px;
|
||
font-weight: bold;
|
||
color: var(--el-color-primary);
|
||
|
||
&.used {
|
||
color: var(--el-color-warning);
|
||
}
|
||
|
||
&.remaining {
|
||
color: var(--el-color-success);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</style>
|