574 lines
16 KiB
Vue
574 lines
16 KiB
Vue
<template>
|
||
<ArtTableFullScreen>
|
||
<div class="single-card-page" id="table-full-screen">
|
||
<!-- ICCID查询区域 -->
|
||
<ElCard shadow="never" class="search-card" style="margin-bottom: 16px">
|
||
<template #header>
|
||
<span>ICCID查询</span>
|
||
</template>
|
||
<div class="iccid-search">
|
||
<ElInput
|
||
v-model="searchIccid"
|
||
placeholder="请输入ICCID"
|
||
clearable
|
||
style="width: 400px; margin-right: 16px"
|
||
@keyup.enter="handleSearchCard"
|
||
>
|
||
<template #prepend>ICCID</template>
|
||
</ElInput>
|
||
<ElButton type="primary" @click="handleSearchCard" :loading="loading">查询</ElButton>
|
||
</div>
|
||
<!-- 格式化显示的ICCID -->
|
||
<div v-if="formattedIccid" class="formatted-iccid">
|
||
{{ formattedIccid }}
|
||
</div>
|
||
</ElCard>
|
||
|
||
<!-- 网卡信息卡片 -->
|
||
<ElCard shadow="never" class="card-info-card" style="margin-bottom: 16px">
|
||
<template #header>
|
||
<span>网卡信息</span>
|
||
</template>
|
||
<div v-if="!hasSearched" class="empty-state">
|
||
<p style="text-align: center; color: var(--el-text-color-secondary); padding: 40px">
|
||
请在上方输入ICCID进行查询
|
||
</p>
|
||
</div>
|
||
<ElForm v-else :model="cardInfo" label-width="120px" :inline="false">
|
||
<ElRow :gutter="24">
|
||
<ElCol :span="8">
|
||
<ElFormItem label="ICCID:">
|
||
<span>{{ cardInfo.iccid || '-' }}</span>
|
||
</ElFormItem>
|
||
</ElCol>
|
||
<ElCol :span="8">
|
||
<ElFormItem label="IMSI:">
|
||
<span>{{ cardInfo.imsi || '-' }}</span>
|
||
</ElFormItem>
|
||
</ElCol>
|
||
<ElCol :span="8">
|
||
<ElFormItem label="手机号码:">
|
||
<span>{{ cardInfo.msisdn || '-' }}</span>
|
||
</ElFormItem>
|
||
</ElCol>
|
||
</ElRow>
|
||
<ElRow :gutter="24">
|
||
<ElCol :span="8">
|
||
<ElFormItem label="运营商:">
|
||
<ElTag v-if="cardInfo.operatorName" :type="getOperatorTagType(cardInfo.operator)">
|
||
{{ cardInfo.operatorName }}
|
||
</ElTag>
|
||
<span v-else>-</span>
|
||
</ElFormItem>
|
||
</ElCol>
|
||
<ElCol :span="8">
|
||
<ElFormItem label="网络类型:">
|
||
<span>{{ cardInfo.networkType || '-' }}</span>
|
||
</ElFormItem>
|
||
</ElCol>
|
||
<ElCol :span="8">
|
||
<ElFormItem label="状态:">
|
||
<ElTag v-if="cardInfo.statusName" :type="getStatusTagType(cardInfo.status)">
|
||
{{ cardInfo.statusName }}
|
||
</ElTag>
|
||
<span v-else>-</span>
|
||
</ElFormItem>
|
||
</ElCol>
|
||
</ElRow>
|
||
<ElRow :gutter="24">
|
||
<ElCol :span="8">
|
||
<ElFormItem label="激活时间:">
|
||
<span>{{ cardInfo.activatedDate || '-' }}</span>
|
||
</ElFormItem>
|
||
</ElCol>
|
||
<ElCol :span="8">
|
||
<ElFormItem label="过期时间:">
|
||
<span>{{ cardInfo.expiryDate || '-' }}</span>
|
||
</ElFormItem>
|
||
</ElCol>
|
||
</ElRow>
|
||
</ElForm>
|
||
</ElCard>
|
||
|
||
<!-- 操作区域 -->
|
||
<ElCard v-if="hasSearched" shadow="never" class="operation-card" style="margin-bottom: 16px">
|
||
<template #header>
|
||
<span>操作区域</span>
|
||
</template>
|
||
<div class="operation-buttons">
|
||
<ElButton type="primary" @click="activateCard" :disabled="cardInfo.status === '1'"
|
||
>激活网卡</ElButton
|
||
>
|
||
<ElButton type="warning" @click="suspendCard" :disabled="cardInfo.status === '2'"
|
||
>停用网卡</ElButton
|
||
>
|
||
<ElButton type="success" @click="showRechargeDialog">充值</ElButton>
|
||
<ElButton type="info" @click="queryTraffic">流量查询</ElButton>
|
||
<ElButton type="danger" @click="resetCard">重置网卡</ElButton>
|
||
<ElButton @click="diagnoseCard">网卡诊断</ElButton>
|
||
</div>
|
||
</ElCard>
|
||
|
||
<!-- 流量信息 -->
|
||
<ElCard v-if="hasSearched" shadow="never" class="traffic-card" style="margin-bottom: 16px">
|
||
<template #header>
|
||
<span>流量信息</span>
|
||
</template>
|
||
<ElRow :gutter="24">
|
||
<ElCol :span="6">
|
||
<div class="traffic-item">
|
||
<div class="traffic-value">{{ trafficInfo.totalTraffic }}</div>
|
||
<div class="traffic-label">总流量</div>
|
||
</div>
|
||
</ElCol>
|
||
<ElCol :span="6">
|
||
<div class="traffic-item">
|
||
<div class="traffic-value used">{{ trafficInfo.usedTraffic }}</div>
|
||
<div class="traffic-label">已用流量</div>
|
||
</div>
|
||
</ElCol>
|
||
<ElCol :span="6">
|
||
<div class="traffic-item">
|
||
<div class="traffic-value remaining">{{ trafficInfo.remainingTraffic }}</div>
|
||
<div class="traffic-label">剩余流量</div>
|
||
</div>
|
||
</ElCol>
|
||
<ElCol :span="6">
|
||
<div class="traffic-item">
|
||
<div class="traffic-value percentage">{{ trafficInfo.usagePercentage }}%</div>
|
||
<div class="traffic-label">使用率</div>
|
||
</div>
|
||
</ElCol>
|
||
</ElRow>
|
||
<ElProgress :percentage="parseInt(trafficInfo.usagePercentage)" style="margin-top: 16px" />
|
||
</ElCard>
|
||
|
||
<!-- 使用记录 -->
|
||
<ElCard v-if="hasSearched" shadow="never" class="art-table-card">
|
||
<template #header>
|
||
<span>使用记录</span>
|
||
<ElButton style="float: right" @click="refreshUsageRecords">刷新</ElButton>
|
||
</template>
|
||
|
||
<ArtTable
|
||
ref="tableRef"
|
||
row-key="id"
|
||
:loading="loading"
|
||
:data="usageRecords"
|
||
:currentPage="pagination.currentPage"
|
||
:pageSize="pagination.pageSize"
|
||
:total="pagination.total"
|
||
@size-change="handleSizeChange"
|
||
@current-change="handleCurrentChange"
|
||
>
|
||
<template #default>
|
||
<ElTableColumn v-for="col in usageColumns" :key="col.prop || col.type" v-bind="col" />
|
||
</template>
|
||
</ArtTable>
|
||
</ElCard>
|
||
|
||
<!-- 充值对话框 -->
|
||
<ElDialog v-model="rechargeDialogVisible" title="网卡充值" width="400px" align-center>
|
||
<ElForm
|
||
ref="rechargeFormRef"
|
||
:model="rechargeForm"
|
||
:rules="rechargeRules"
|
||
label-width="100px"
|
||
>
|
||
<ElFormItem label="充值金额" prop="amount">
|
||
<ElInput v-model="rechargeForm.amount" placeholder="请输入充值金额">
|
||
<template #append>元</template>
|
||
</ElInput>
|
||
</ElFormItem>
|
||
<ElFormItem label="充值方式" prop="method">
|
||
<ElSelect v-model="rechargeForm.method" placeholder="请选择充值方式">
|
||
<ElOption label="支付宝" value="alipay" />
|
||
<ElOption label="微信支付" value="wechat" />
|
||
<ElOption label="银联支付" value="unionpay" />
|
||
</ElSelect>
|
||
</ElFormItem>
|
||
</ElForm>
|
||
<template #footer>
|
||
<ElButton @click="rechargeDialogVisible = false">取消</ElButton>
|
||
<ElButton type="primary" @click="handleRecharge">确认充值</ElButton>
|
||
</template>
|
||
</ElDialog>
|
||
</div>
|
||
</ArtTableFullScreen>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { h } from 'vue'
|
||
import { ElTag, ElMessageBox, ElMessage, FormInstance } from 'element-plus'
|
||
import { useRoute } from 'vue-router'
|
||
import type { FormRules } from 'element-plus'
|
||
import { CardService } from '@/api/modules'
|
||
|
||
defineOptions({ name: 'SingleCard' })
|
||
|
||
const loading = ref(false)
|
||
const rechargeDialogVisible = ref(false)
|
||
const route = useRoute()
|
||
|
||
// ICCID搜索相关
|
||
const searchIccid = ref('')
|
||
const hasSearched = ref(false)
|
||
|
||
// 格式化显示的ICCID(4位一组,用横杠分隔)
|
||
const formattedIccid = computed(() => {
|
||
if (!cardInfo.iccid) return ''
|
||
return cardInfo.iccid.replace(/(\d{4})(?=\d)/g, '$1-')
|
||
})
|
||
|
||
// 网卡信息 - 默认为空
|
||
const cardInfo = reactive({
|
||
iccid: '',
|
||
imsi: '',
|
||
msisdn: '',
|
||
operator: '',
|
||
operatorName: '',
|
||
networkType: '',
|
||
status: '',
|
||
statusName: '',
|
||
activatedDate: '',
|
||
expiryDate: ''
|
||
})
|
||
|
||
// 流量信息 - 默认为空
|
||
const trafficInfo = reactive({
|
||
totalTraffic: '0MB',
|
||
usedTraffic: '0MB',
|
||
remainingTraffic: '0MB',
|
||
usagePercentage: '0'
|
||
})
|
||
|
||
const pagination = reactive({
|
||
currentPage: 1,
|
||
pageSize: 10,
|
||
total: 0
|
||
})
|
||
|
||
// 使用记录 - 默认为空
|
||
const usageRecords = ref([])
|
||
|
||
// 充值表单
|
||
const rechargeForm = reactive({
|
||
amount: '',
|
||
method: ''
|
||
})
|
||
|
||
const rechargeFormRef = ref<FormInstance>()
|
||
|
||
// 表格列配置
|
||
const usageColumns = [
|
||
{
|
||
prop: 'date',
|
||
label: '日期',
|
||
width: 120
|
||
},
|
||
{
|
||
prop: 'time',
|
||
label: '时间',
|
||
width: 100
|
||
},
|
||
{
|
||
prop: 'dataUsage',
|
||
label: '流量使用量',
|
||
width: 120
|
||
},
|
||
{
|
||
prop: 'fee',
|
||
label: '费用(元)',
|
||
width: 100,
|
||
formatter: (row: any) => `¥${row.fee}`
|
||
},
|
||
{
|
||
prop: 'location',
|
||
label: '位置',
|
||
minWidth: 140
|
||
}
|
||
]
|
||
|
||
// 获取运营商标签类型
|
||
const getOperatorTagType = (operator: string) => {
|
||
switch (operator) {
|
||
case 'mobile':
|
||
return 'success'
|
||
case 'unicom':
|
||
return 'primary'
|
||
case 'telecom':
|
||
return 'warning'
|
||
default:
|
||
return 'info'
|
||
}
|
||
}
|
||
|
||
// 获取状态标签类型
|
||
const getStatusTagType = (status: string) => {
|
||
switch (status) {
|
||
case '1':
|
||
return 'success'
|
||
case '2':
|
||
return 'danger'
|
||
case '3':
|
||
return 'warning'
|
||
case '4':
|
||
return 'info'
|
||
default:
|
||
return 'info'
|
||
}
|
||
}
|
||
|
||
// 激活网卡
|
||
const activateCard = () => {
|
||
ElMessageBox.confirm('确定要激活该网卡吗?', '激活网卡', {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'info'
|
||
}).then(() => {
|
||
cardInfo.status = '1'
|
||
cardInfo.statusName = '激活'
|
||
ElMessage.success('网卡激活成功')
|
||
})
|
||
}
|
||
|
||
// 停用网卡
|
||
const suspendCard = () => {
|
||
ElMessageBox.confirm('确定要停用该网卡吗?', '停用网卡', {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'warning'
|
||
}).then(() => {
|
||
cardInfo.status = '2'
|
||
cardInfo.statusName = '停用'
|
||
ElMessage.success('网卡停用成功')
|
||
})
|
||
}
|
||
|
||
// 显示充值对话框
|
||
const showRechargeDialog = () => {
|
||
rechargeDialogVisible.value = true
|
||
if (rechargeFormRef.value) {
|
||
rechargeFormRef.value.resetFields()
|
||
}
|
||
}
|
||
|
||
// 流量查询
|
||
const queryTraffic = () => {
|
||
ElMessage.info('正在查询流量信息...')
|
||
// 这里可以调用API查询最新的流量信息
|
||
}
|
||
|
||
// 重置网卡
|
||
const resetCard = () => {
|
||
ElMessageBox.confirm('确定要重置该网卡吗?重置后网卡需要重新激活。', '重置网卡', {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'error'
|
||
}).then(() => {
|
||
ElMessage.success('网卡重置成功')
|
||
})
|
||
}
|
||
|
||
// 网卡诊断
|
||
const diagnoseCard = () => {
|
||
ElMessage.info('正在进行网卡诊断...')
|
||
setTimeout(() => {
|
||
ElMessage.success('网卡诊断完成,网卡状态正常')
|
||
}, 2000)
|
||
}
|
||
|
||
// 充值验证规则
|
||
const rechargeRules: FormRules = {
|
||
amount: [
|
||
{ required: true, message: '请输入充值金额', trigger: 'blur' },
|
||
{ pattern: /^\d+(\.\d{1,2})?$/, message: '请输入正确的金额格式', trigger: 'blur' }
|
||
],
|
||
method: [{ required: true, message: '请选择充值方式', trigger: 'change' }]
|
||
}
|
||
|
||
// 处理充值
|
||
const handleRecharge = async () => {
|
||
if (!rechargeFormRef.value) return
|
||
|
||
await rechargeFormRef.value.validate((valid) => {
|
||
if (valid) {
|
||
ElMessage.success('充值成功')
|
||
rechargeDialogVisible.value = false
|
||
}
|
||
})
|
||
}
|
||
|
||
// 刷新使用记录
|
||
const refreshUsageRecords = () => {
|
||
ElMessage.info('正在刷新使用记录...')
|
||
// 这里可以调用API获取最新的使用记录
|
||
}
|
||
|
||
// 处理表格分页变化
|
||
const handleSizeChange = (newPageSize: number) => {
|
||
pagination.pageSize = newPageSize
|
||
// 重新获取数据
|
||
}
|
||
|
||
const handleCurrentChange = (newCurrentPage: number) => {
|
||
pagination.currentPage = newCurrentPage
|
||
// 重新获取数据
|
||
}
|
||
|
||
onMounted(() => {
|
||
// 检查是否有传递的ICCID参数
|
||
const iccidFromQuery = route.query.iccid as string
|
||
if (iccidFromQuery) {
|
||
// 如果有ICCID参数,更新卡片信息
|
||
cardInfo.iccid = iccidFromQuery
|
||
// 可以在这里根据ICCID获取完整的卡片信息
|
||
console.log('从网卡明细跳转,ICCID:', iccidFromQuery)
|
||
// 模拟根据ICCID获取卡片详细信息
|
||
loadCardInfoByIccid(iccidFromQuery)
|
||
}
|
||
|
||
// 初始化数据
|
||
pagination.total = usageRecords.value.length
|
||
})
|
||
|
||
// 处理ICCID搜索
|
||
const handleSearchCard = async () => {
|
||
if (!searchIccid.value.trim()) {
|
||
ElMessage.warning('请输入ICCID')
|
||
return
|
||
}
|
||
await loadCardInfoByIccid(searchIccid.value.trim())
|
||
}
|
||
|
||
// 根据ICCID加载卡片信息
|
||
const loadCardInfoByIccid = async (iccid: string) => {
|
||
loading.value = true
|
||
try {
|
||
const response = await CardService.getIotCardDetailByIccid(iccid)
|
||
|
||
if (response.code === 200 && response.data) {
|
||
const data = response.data
|
||
hasSearched.value = true
|
||
|
||
// 更新网卡基本信息
|
||
Object.assign(cardInfo, {
|
||
iccid: data.iccid || '',
|
||
imsi: data.imsi || '',
|
||
msisdn: data.msisdn || '',
|
||
operator: data.carrier_type || '',
|
||
operatorName: data.carrier_name || '',
|
||
networkType: data.network_type || '',
|
||
status: String(data.status || ''),
|
||
statusName: getStatusName(data.status),
|
||
activatedDate: data.activated_at || '',
|
||
expiryDate: data.expire_at || ''
|
||
})
|
||
|
||
// 更新流量信息
|
||
const totalMB = data.data_usage_mb || 0
|
||
const usedMB = data.current_month_usage_mb || 0
|
||
const remainingMB = totalMB - usedMB
|
||
const percentage = totalMB > 0 ? Math.round((usedMB / totalMB) * 100) : 0
|
||
|
||
Object.assign(trafficInfo, {
|
||
totalTraffic: formatDataSize(totalMB),
|
||
usedTraffic: formatDataSize(usedMB),
|
||
remainingTraffic: formatDataSize(remainingMB),
|
||
usagePercentage: String(percentage)
|
||
})
|
||
|
||
ElMessage.success('查询成功')
|
||
} else {
|
||
ElMessage.error(response.message || '查询失败')
|
||
}
|
||
} catch (error: any) {
|
||
console.error('获取卡片信息失败:', error)
|
||
ElMessage.error(error?.message || '获取卡片信息失败')
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
// 获取状态名称
|
||
const getStatusName = (status: number) => {
|
||
const statusMap: Record<number, string> = {
|
||
0: '未激活',
|
||
1: '激活',
|
||
2: '停用',
|
||
3: '已过期',
|
||
4: '已注销'
|
||
}
|
||
return statusMap[status] || '未知'
|
||
}
|
||
|
||
// 格式化数据大小
|
||
const formatDataSize = (mb: number) => {
|
||
if (mb >= 1024) {
|
||
return `${(mb / 1024).toFixed(2)}GB`
|
||
}
|
||
return `${mb.toFixed(2)}MB`
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.single-card-page {
|
||
.search-card {
|
||
.iccid-search {
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
.formatted-iccid {
|
||
margin-top: 16px;
|
||
padding: 16px;
|
||
background-color: var(--el-fill-color-light);
|
||
border-radius: 4px;
|
||
font-size: 32px;
|
||
font-weight: 600;
|
||
letter-spacing: 2px;
|
||
text-align: center;
|
||
color: var(--el-color-primary);
|
||
font-family: 'Courier New', Courier, monospace;
|
||
}
|
||
}
|
||
|
||
.operation-buttons {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 12px;
|
||
}
|
||
|
||
.traffic-card {
|
||
.traffic-item {
|
||
padding: 16px;
|
||
text-align: center;
|
||
|
||
.traffic-value {
|
||
font-size: 24px;
|
||
font-weight: bold;
|
||
color: var(--el-color-primary);
|
||
|
||
&.used {
|
||
color: var(--el-color-warning);
|
||
}
|
||
|
||
&.remaining {
|
||
color: var(--el-color-success);
|
||
}
|
||
|
||
&.percentage {
|
||
color: var(--el-color-info);
|
||
}
|
||
}
|
||
|
||
.traffic-label {
|
||
margin-top: 8px;
|
||
font-size: 14px;
|
||
color: var(--el-text-color-regular);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</style>
|