Files
one-pipe-system/src/views/card-management/single-card/index.vue
sexygoat dccad819cf
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 5m26s
修改工单
2026-02-25 16:14:38 +08:00

574 lines
16 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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)
// 格式化显示的ICCID4位一组用横杠分隔
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>