Files
one-pipe-system/src/views/card-management/single-card/index.vue
sexygoat f4ccf9ed24
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 4m33s
修改bug
2026-03-17 09:31:37 +08:00

894 lines
29 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">
<!-- 标识符查询区域 -->
<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 ''
// 如果是ICCID19-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>