详情添加权限
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 3m35s

This commit is contained in:
sexygoat
2026-03-07 11:59:16 +08:00
parent e73992d253
commit bd45f7a224
4 changed files with 10 additions and 2553 deletions

View File

@@ -424,10 +424,6 @@
"enterpriseCards": "企业卡管理", "enterpriseCards": "企业卡管理",
"customerCommission": "客户账号佣金" "customerCommission": "客户账号佣金"
}, },
"deviceManagement": {
"title": "设备管理",
"devices": "设备管理"
},
"product": { "product": {
"title": "商品管理", "title": "商品管理",
"simCard": "号卡管理", "simCard": "号卡管理",

View File

@@ -803,12 +803,16 @@
// 跳转到设备详情页面 // 跳转到设备详情页面
const goToDeviceSearchDetail = (deviceNo: string) => { const goToDeviceSearchDetail = (deviceNo: string) => {
if (hasAuth('device:view_detail')) {
router.push({ router.push({
path: '/asset-management/device-detail', path: '/asset-management/device-detail',
query: { query: {
device_no: deviceNo device_no: deviceNo
} }
}) })
} else {
ElMessage.warning('您没有查看详情的权限')
}
} }
// 查看设备绑定的卡片 // 查看设备绑定的卡片

View File

@@ -1,495 +0,0 @@
<template>
<div class="device-detail-page">
<!-- 页面头部 -->
<ElPageHeader :icon="ArrowLeft" title="返回" @back="handleBack">
<template #content>
<span class="page-title">设备详情</span>
</template>
<template #extra>
<ElButton type="primary" @click="handleRefresh" :loading="loading">
<Icon name="refresh" /> 刷新
</ElButton>
</template>
</ElPageHeader>
<!-- 加载状态 -->
<div v-if="loading && !deviceDetail" class="loading-container">
<ElIcon class="is-loading" :size="60"><Loading /></ElIcon>
<div class="loading-text">加载中...</div>
</div>
<!-- 详情内容 -->
<div v-else-if="deviceDetail" class="detail-content">
<!-- 设备基本信息卡片 -->
<ElCard shadow="never" class="info-card">
<template #header>
<div class="card-header">
<span class="header-title">
<Icon name="mobile" /> 设备基本信息
</span>
</div>
</template>
<ElDescriptions :column="3" border>
<ElDescriptionsItem label="当前卡号">
<span class="code-text">{{ deviceDetail.currentCardNumber }}</span>
</ElDescriptionsItem>
<ElDescriptionsItem label="当前运营商">
<ElTag :type="getOperatorType(deviceDetail.currentOperator)">
{{ deviceDetail.currentOperator }}
</ElTag>
</ElDescriptionsItem>
<ElDescriptionsItem label="当前IMEI">
<span class="code-text">{{ deviceDetail.currentIMEI }}</span>
</ElDescriptionsItem>
<ElDescriptionsItem label="设备号码">
<span class="code-text">{{ deviceDetail.deviceNumber }}</span>
</ElDescriptionsItem>
<ElDescriptionsItem label="设备代理">
{{ deviceDetail.deviceAgent || '--' }}
</ElDescriptionsItem>
<ElDescriptionsItem label="过期时间">
<span :class="{ 'expired-date': isExpired(deviceDetail.expiryTime) }">
{{ deviceDetail.expiryTime }}
</span>
</ElDescriptionsItem>
<ElDescriptionsItem label="实名状态">
<ElTag :type="getStatusType(deviceDetail.realNameStatus)">
{{ deviceDetail.realNameStatus }}
</ElTag>
</ElDescriptionsItem>
<ElDescriptionsItem label="运营商状态">
<ElTag :type="getStatusType(deviceDetail.operatorStatus)">
{{ deviceDetail.operatorStatus }}
</ElTag>
</ElDescriptionsItem>
<ElDescriptionsItem label="设备状态">
<ElTag :type="getStatusType(deviceDetail.deviceStatus)">
{{ deviceDetail.deviceStatus }}
</ElTag>
</ElDescriptionsItem>
</ElDescriptions>
</ElCard>
<!-- 流量统计卡片 -->
<ElCard shadow="never" class="info-card">
<template #header>
<div class="card-header">
<span class="header-title">
<Icon name="chart-bar" /> 流量统计
</span>
</div>
</template>
<div class="traffic-stats-grid">
<div class="stat-item total">
<div class="stat-label">总计流量</div>
<div class="stat-value">{{ deviceDetail.totalTraffic }}</div>
</div>
<div class="stat-item used">
<div class="stat-label">已用流量</div>
<div class="stat-value">{{ deviceDetail.usedTraffic }}</div>
<ElProgress
:percentage="getUsagePercentage(deviceDetail.usedTraffic, deviceDetail.totalTraffic)"
:color="
getTrafficColor(
getUsagePercentage(deviceDetail.usedTraffic, deviceDetail.totalTraffic)
)
"
:stroke-width="8"
/>
</div>
<div class="stat-item remaining">
<div class="stat-label">剩余流量</div>
<div class="stat-value">{{ deviceDetail.remainingTraffic }}</div>
</div>
</div>
</ElCard>
<!-- 双卡信息卡片 -->
<ElCard shadow="never" class="info-card">
<template #header>
<div class="card-header">
<span class="header-title">
<Icon name="credit-card" /> 双卡信息
</span>
</div>
</template>
<div class="dual-cards-container">
<!-- 卡1信息 -->
<div class="card-column">
<div class="card-title">卡1信息</div>
<ElDescriptions :column="1" border>
<ElDescriptionsItem label="ICCID">
<span class="code-text">{{ deviceDetail.card1.iccid }}</span>
</ElDescriptionsItem>
<ElDescriptionsItem label="接入号">
<span class="code-text">{{ deviceDetail.card1.accessNumber }}</span>
</ElDescriptionsItem>
<ElDescriptionsItem label="虚拟号">
<span class="code-text">{{ deviceDetail.card1.virtualNumber }}</span>
</ElDescriptionsItem>
<ElDescriptionsItem label="IMEI">
<span class="code-text">{{ deviceDetail.card1.imei }}</span>
</ElDescriptionsItem>
<ElDescriptionsItem label="代理商">
{{ deviceDetail.card1.agent || '--' }}
</ElDescriptionsItem>
<ElDescriptionsItem label="实名状态">
<ElTag :type="getStatusType(deviceDetail.card1.realNameStatus)">
{{ deviceDetail.card1.realNameStatus }}
</ElTag>
</ElDescriptionsItem>
<ElDescriptionsItem label="运营商">
<ElTag :type="getOperatorType(deviceDetail.card1.operator)">
{{ deviceDetail.card1.operator }}
</ElTag>
</ElDescriptionsItem>
<ElDescriptionsItem label="网卡状态">
<ElTag :type="getStatusType(deviceDetail.card1.cardStatus)">
{{ deviceDetail.card1.cardStatus }}
</ElTag>
</ElDescriptionsItem>
<ElDescriptionsItem label="过期时间">
{{ deviceDetail.card1.expiryTime }}
</ElDescriptionsItem>
<ElDescriptionsItem label="套餐系列">
{{ deviceDetail.card1.packageSeries || '--' }}
</ElDescriptionsItem>
<ElDescriptionsItem label="总流量">
{{ deviceDetail.card1.totalTraffic }}
</ElDescriptionsItem>
<ElDescriptionsItem label="已使用流量">
<span class="traffic-info used">{{ deviceDetail.card1.usedTraffic }}</span>
</ElDescriptionsItem>
<ElDescriptionsItem label="剩余流量">
<span class="traffic-info remaining">{{ deviceDetail.card1.remainingTraffic }}</span>
</ElDescriptionsItem>
</ElDescriptions>
</div>
<!-- 卡2信息 -->
<div class="card-column">
<div class="card-title">卡2信息</div>
<ElDescriptions :column="1" border>
<ElDescriptionsItem label="ICCID">
<span class="code-text">{{ deviceDetail.card2.iccid }}</span>
</ElDescriptionsItem>
<ElDescriptionsItem label="接入号">
<span class="code-text">{{ deviceDetail.card2.accessNumber }}</span>
</ElDescriptionsItem>
<ElDescriptionsItem label="虚拟号">
<span class="code-text">{{ deviceDetail.card2.virtualNumber || '--' }}</span>
</ElDescriptionsItem>
<ElDescriptionsItem label="IMEI">
<span class="code-text">{{ deviceDetail.card2.imei }}</span>
</ElDescriptionsItem>
<ElDescriptionsItem label="代理商">
{{ deviceDetail.card2.agent || '--' }}
</ElDescriptionsItem>
<ElDescriptionsItem label="实名状态">
<ElTag :type="getStatusType(deviceDetail.card2.realNameStatus)">
{{ deviceDetail.card2.realNameStatus }}
</ElTag>
</ElDescriptionsItem>
<ElDescriptionsItem label="运营商">
<ElTag :type="getOperatorType(deviceDetail.card2.operator)">
{{ deviceDetail.card2.operator }}
</ElTag>
</ElDescriptionsItem>
<ElDescriptionsItem label="网卡状态">
<ElTag :type="getStatusType(deviceDetail.card2.cardStatus)">
{{ deviceDetail.card2.cardStatus }}
</ElTag>
</ElDescriptionsItem>
<ElDescriptionsItem label="过期时间">
{{ deviceDetail.card2.expiryTime || '--' }}
</ElDescriptionsItem>
<ElDescriptionsItem label="套餐系列">
{{ deviceDetail.card2.packageSeries || '--' }}
</ElDescriptionsItem>
<ElDescriptionsItem label="总流量">
{{ deviceDetail.card2.totalTraffic }}
</ElDescriptionsItem>
<ElDescriptionsItem label="已使用流量">
<span class="traffic-info used">{{ deviceDetail.card2.usedTraffic }}</span>
</ElDescriptionsItem>
<ElDescriptionsItem label="剩余流量">
<span class="traffic-info remaining">{{ deviceDetail.card2.remainingTraffic }}</span>
</ElDescriptionsItem>
</ElDescriptions>
</div>
</div>
</ElCard>
</div>
<!-- 未找到设备 -->
<ElCard v-else shadow="never" class="empty-card">
<ElEmpty description="未找到该设备信息" />
</ElCard>
</div>
</template>
<script setup lang="ts">
import { useRouter, useRoute } from 'vue-router'
import { DeviceService } from '@/api/modules'
import { ElMessage, ElIcon } from 'element-plus'
import { ArrowLeft, Loading } from '@element-plus/icons-vue'
defineOptions({ name: 'DeviceDetail' })
const router = useRouter()
const route = useRoute()
const loading = ref(false)
const deviceDetail = ref<any>(null)
const iccid = ref<string>('')
onMounted(() => {
iccid.value = (route.query.iccid as string) || ''
if (iccid.value) {
loadDeviceDetail()
} else {
ElMessage.error('缺少ICCID参数')
}
})
// 加载设备详情
const loadDeviceDetail = async () => {
loading.value = true
try {
const res = await DeviceService.getDeviceByIccid(iccid.value)
if (res.code === 0) {
deviceDetail.value = res.data
} else {
ElMessage.error(res.msg || '获取设备详情失败')
}
} catch (error) {
console.error('获取设备详情失败:', error)
ElMessage.error('获取设备详情失败')
} finally {
loading.value = false
}
}
// 返回上一页
const handleBack = () => {
router.back()
}
// 刷新
const handleRefresh = () => {
loadDeviceDetail()
}
// 获取运营商标签类型
const getOperatorType = (operator: string) => {
switch (operator) {
case '中国移动':
case 'gs联动1':
return 'success'
case '中国联通':
return 'primary'
case '中国电信':
case 'gs电信':
return 'warning'
default:
return 'info'
}
}
// 获取状态标签类型
const getStatusType = (status: string) => {
switch (status) {
case '正常':
case '已实名':
case '在线':
return 'success'
case '停机':
case '离线':
return 'danger'
case '未实名':
return 'warning'
case '接口异常':
return 'info'
default:
return 'info'
}
}
// 判断是否过期
const isExpired = (expiryTime: string) => {
return new Date(expiryTime) < new Date()
}
// 计算流量使用百分比
const getUsagePercentage = (used: string, total: string) => {
const usedGB = parseFloat(used.replace('GB', ''))
const totalGB = parseFloat(total.replace('GB', ''))
return Math.round((usedGB / totalGB) * 100)
}
// 获取流量进度条颜色
const getTrafficColor = (percentage: number) => {
if (percentage < 50) return 'var(--el-color-success)'
if (percentage < 80) return 'var(--el-color-warning)'
return 'var(--el-color-danger)'
}
</script>
<style lang="scss" scoped>
.device-detail-page {
padding: 20px;
.page-title {
font-size: 18px;
font-weight: 600;
color: var(--el-text-color-primary);
}
.loading-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 400px;
text-align: center;
.loading-text {
margin-top: 16px;
font-size: 14px;
color: var(--el-text-color-secondary);
}
}
.detail-content {
margin-top: 20px;
.info-card {
margin-bottom: 20px;
.card-header {
display: flex;
align-items: center;
justify-content: space-between;
.header-title {
display: flex;
gap: 8px;
align-items: center;
font-size: 16px;
font-weight: 600;
color: var(--el-text-color-primary);
}
}
.code-text {
padding: 3px 8px;
font-family: 'SF Mono', Monaco, Inconsolata, 'Roboto Mono', monospace;
font-size: 13px;
font-weight: 500;
color: var(--el-text-color-regular);
background: var(--el-fill-color-light);
border: 1px solid var(--el-border-color-lighter);
border-radius: 4px;
}
.traffic-info {
font-weight: 600;
&.used {
color: var(--el-color-warning);
}
&.remaining {
color: var(--el-color-success);
}
}
}
}
.traffic-stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 16px;
.stat-item {
padding: 20px;
text-align: center;
background: var(--el-fill-color-extra-light);
border: 1px solid var(--el-border-color-lighter);
border-radius: 8px;
.stat-label {
margin-bottom: 10px;
font-size: 14px;
font-weight: 500;
color: var(--el-text-color-secondary);
}
.stat-value {
margin-bottom: 12px;
font-size: 18px;
font-weight: 600;
}
&.total .stat-value {
color: var(--el-color-primary);
}
&.used .stat-value {
color: var(--el-color-warning);
}
&.remaining .stat-value {
color: var(--el-color-success);
}
}
}
.dual-cards-container {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
.card-column {
.card-title {
display: inline-block;
padding-bottom: 10px;
margin-bottom: 16px;
font-size: 16px;
font-weight: 600;
color: var(--el-text-color-primary);
border-bottom: 2px solid var(--el-color-primary);
}
}
}
.empty-card {
min-height: 400px;
display: flex;
align-items: center;
justify-content: center;
}
.expired-date {
font-weight: 600;
color: var(--el-color-danger);
}
}
:deep(.el-page-header) {
margin-bottom: 20px;
}
:deep(.el-descriptions__label) {
font-weight: 500;
}
// 响应式设计
@media (width <= 1200px) {
.dual-cards-container {
grid-template-columns: 1fr;
}
}
</style>

File diff suppressed because it is too large Load Diff