Files
gt-agent-company/src/pages/agent-system/asset-detail/index.vue
sexygoat 4226824be9
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 1m6s
fix: 单卡设备显示字段
2026-05-21 14:43:11 +08:00

1373 lines
62 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.
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { resolveAsset, getAssetRealtimeStatus, getCurrentPackage, getAssetPackages } from '@/api/assets'
import type { AssetRealtimeStatus, AssetPackage, AssetResolveResponse } from '@/api/assets'
import { formatTime, formatDataSize } from '@/utils/format'
import { formatPackageDataSize, formatPackagePrice, getPackageRemainMb, getPackageTotalMb, getPackageUsagePercent, getPackageUsedMb } from '../package-display'
// 搜索关键词
const searchKeyword = ref('')
// 资产类型
const assetType = ref<'card' | 'device' | null>(null)
// 资产信息
const assetInfo = ref<any>(null)
// 资产实时状态
const realtimeStatus = ref<AssetRealtimeStatus | null>(null)
// 当前套餐
const currentPackage = ref<AssetPackage | null>(null)
const packageList = ref<AssetPackage[]>([])
const showPackageList = ref(true)
// 套餐列表
// 是否展开套餐列表
// 是否为卡片
const isCard = computed(() => assetType.value === 'card')
// 是否为设备
const isDevice = computed(() => assetType.value === 'device')
// 卡片信息
const cardInfo = computed(() => isCard.value ? assetInfo.value : null)
// 设备信息
const deviceInfo = computed(() => isDevice.value ? assetInfo.value : null)
type DeviceBoundCard = NonNullable<AssetResolveResponse['cards']>[number]
const deviceBoundCards = computed<DeviceBoundCard[]>(() => Array.isArray(deviceInfo.value?.cards) ? deviceInfo.value.cards : [])
const activeDeviceCard = computed<DeviceBoundCard | null>(() => {
if (deviceBoundCards.value.length === 1) {
return deviceBoundCards.value[0]
}
if (deviceBoundCards.value.length > 1) {
return deviceBoundCards.value.find(card => card.is_current) ?? null
}
return null
})
const deviceRealNameStatus = computed(() => activeDeviceCard.value?.real_name_status ?? deviceInfo.value?.real_name_status)
// 搜索资产
async function handleSearch() {
if (!searchKeyword.value.trim()) {
return
}
try {
const result = await resolveAsset(searchKeyword.value.trim())
assetType.value = result.asset_type
assetInfo.value = result
// 加载实时状态和当前套餐 (使用 identifier)
if (searchKeyword.value.trim()) {
loadRealtimeStatus(searchKeyword.value.trim())
loadCurrentPackage(searchKeyword.value.trim())
loadPackageList(searchKeyword.value.trim())
}
} catch (error : any) {
console.error('查询失败:', error)
assetType.value = null
assetInfo.value = null
realtimeStatus.value = null
currentPackage.value = null
packageList.value = []
}
}
// 加载实时状态
async function loadRealtimeStatus(identifier : string) {
try {
const status = await getAssetRealtimeStatus(identifier)
realtimeStatus.value = status
} catch (error : any) {
console.error('加载实时状态失败:', error)
}
}
// 加载当前套餐
async function loadCurrentPackage(identifier : string) {
try {
console.log('开始加载当前套餐:', identifier)
const pkg = await getCurrentPackage(identifier)
console.log('当前套餐数据:', pkg)
currentPackage.value = pkg
} catch (error : any) {
console.error('加载当前套餐失败:', error)
// 如果没有当前套餐404这是正常的不需要提示用户
currentPackage.value = null
}
}
// 加载套餐列表
async function loadPackageList(identifier : string) {
try {
console.log('开始加载套餐列表:', identifier)
const response = await getAssetPackages(identifier, { page: 1, page_size: 50 })
console.log('套餐列表数据:', response)
packageList.value = response?.items || []
} catch (error : any) {
console.error('加载套餐列表失败:', error)
packageList.value = []
}
}
// 切换套餐列表显示
async function togglePackageList() {
showPackageList.value = !showPackageList.value
// 第一次展开时加载套餐列表
if (showPackageList.value && packageList.value.length === 0 && searchKeyword.value.trim()) {
await loadPackageList(searchKeyword.value.trim())
}
}
// 获取卡片状态文本
function getCardStatusText(status ?: number) {
const map : Record<number, string> = {
0: '未激活',
1: '正常',
2: '停机',
3: '已停用',
}
return status !== undefined ? map[status] || '未知' : '未知'
}
// 获取实名状态文本
function getRealNameStatusText(status ?: number) {
const map : Record<number, string> = {
0: '未实名',
1: '已实名',
}
return status !== undefined ? map[status] || '未知' : '未知'
}
function getRealNameStatusTagClass(status ?: number | null) {
return status === 1
? 'bg-[#e8f5e9] text-[#2e7d32]'
: 'bg-[#fff1f0] text-[#cf1322]'
}
// 获取网络状态文本 (卡片)
function getNetworkStatusText(status ?: number) {
const map : Record<number, string> = {
0: '停机',
1: '正常',
}
return status !== undefined ? map[status] || '未知' : '未知'
}
// 格式化字节数
function formatBytes(bytes ?: string | number | null) {
if (!bytes) return '0B'
const num = typeof bytes === 'string' ? parseInt(bytes) : bytes
if (num < 1024) return `${num}B`
if (num < 1024 * 1024) return `${(num / 1024).toFixed(2)}KB`
if (num < 1024 * 1024 * 1024) return `${(num / 1024 / 1024).toFixed(2)}MB`
return `${(num / 1024 / 1024 / 1024).toFixed(2)}GB`
}
function isEmptyDisplayValue(value ?: string | number | null) {
return value === null || value === undefined || (typeof value === 'string' && value.trim() === '')
}
function getDisplayValue(value ?: string | number | null) {
return isEmptyDisplayValue(value) ? '-' : String(value)
}
function formatBytesDisplay(bytes ?: string | number | null) {
return isEmptyDisplayValue(bytes) ? '-' : formatBytes(bytes)
}
// 格式化时长(秒)
function formatDuration(seconds ?: string | number) {
if (!seconds) return '0秒'
const num = typeof seconds === 'string' ? parseInt(seconds) : seconds
const hours = Math.floor(num / 3600)
const minutes = Math.floor((num % 3600) / 60)
const secs = num % 60
if (hours > 0) return `${hours}小时${minutes}分钟`
if (minutes > 0) return `${minutes}分钟${secs}`
return `${secs}`
}
// 获取在线状态文本
function getOnlineStatusText(status ?: number) {
return status === 1 ? '在线' : '离线'
}
// 获取套餐状态颜色
function getPackageStatusColor(status : number) {
const map : Record<number, { bg : string, text : string }> = {
0: { bg: '#fff3e0', text: '#e65100' }, // 待生效
1: { bg: '#e8f5e9', text: '#2e7d32' }, // 生效中
2: { bg: '#f5f5f5', text: '#999' }, // 已用完
3: { bg: '#ffebee', text: '#c62828' }, // 已过期
4: { bg: '#f5f5f5', text: '#999' }, // 已失效
}
return map[status] || { bg: '#f5f5f5', text: '#999' }
}
function getPackageTypeText(type ?: AssetPackage['package_type']) {
const map : Record<string, string> = {
formal: '正式套餐',
addon: '加油包',
}
return type ? map[type] || type : '-'
}
function formatPackageEffectivePeriod(activatedAt ?: string | null, expiresAt ?: string | null) {
return `生效期:${formatTime(activatedAt || undefined, 'date')}${formatTime(expiresAt || undefined, 'date')}`
}
function formatCurrentPackagePeriodRange(activatedAt ?: string | null, expiresAt ?: string | null) {
return `生效期:${formatTime(activatedAt || undefined, 'date')} - ${formatTime(expiresAt || undefined, 'date')}`
}
function getDeviceLastOnlineTime() {
const lastOnlineTime = realtimeStatus.value?.device_realtime?.last_online_time || deviceInfo.value?.last_online_time
return lastOnlineTime ? formatTime(lastOnlineTime) : '-'
}
// 获取信号质量等级 (基于 RSRP)
function getSignalQuality(rsrp ?: number) {
if (rsrp === undefined) return { level: '未知', text: '无信号', color: '#999' }
if (rsrp >= -80) {
return { level: '优秀', text: '信号极好', color: '#2e7d32' }
} else if (rsrp >= -90) {
return { level: '良好', text: '信号良好', color: '#66bb6a' }
} else if (rsrp >= -100) {
return { level: '一般', text: '信号一般', color: '#ffa726' }
} else if (rsrp >= -110) {
return { level: '较差', text: '信号较差', color: '#ff6b6b' }
} else {
return { level: '很差', text: '信号很差', color: '#d32f2f' }
}
}
// 获取信号条数1-5条
function getSignalBars(rsrp ?: number) {
if (rsrp === undefined || rsrp < -110) return 0
if (rsrp >= -80) return 5
if (rsrp >= -90) return 4
if (rsrp >= -100) return 3
if (rsrp >= -110) return 2
return 1
}
// 获取卡片状态颜色
function getCardStatusColor(status ?: number) {
const map : Record<number, { bg : string, text : string }> = {
0: { bg: '#f5f5f5', text: '#999' },
1: { bg: '#e8f5e9', text: '#2e7d32' },
2: { bg: '#ffebee', text: '#c62828' },
3: { bg: '#f5f5f5', text: '#999' },
}
return status !== undefined ? map[status] : { bg: '#f5f5f5', text: '#999' }
}
// 获取设备状态文本
function getDeviceStatusText(status ?: number) {
const map : Record<number, string> = {
0: '未激活',
1: '正常',
2: '停机',
3: '已停用',
}
return status !== undefined ? map[status] || '未知' : '未知'
}
// 获取设备状态颜色
function getDeviceStatusColor(status ?: number) {
const map : Record<number, { bg : string, text : string }> = {
0: { bg: '#f5f5f5', text: '#999' },
1: { bg: '#e8f5e9', text: '#2e7d32' },
2: { bg: '#ffebee', text: '#c62828' },
3: { bg: '#f5f5f5', text: '#999' },
}
return status !== undefined ? map[status] : { bg: '#f5f5f5', text: '#999' }
}
onMounted(() => {
const pages = getCurrentPages()
const currentPage = pages[pages.length - 1] as any
const options = currentPage.options || {}
// 如果有 iccid 或 virtual_no 参数,自动搜索
if (options.iccid) {
searchKeyword.value = options.iccid
handleSearch()
} else if (options.virtual_no) {
searchKeyword.value = options.virtual_no
handleSearch()
}
})
</script>
<template>
<view class="bg-page min-h-screen pb-safe">
<!-- IoT卡详情 -->
<view v-if="isCard && cardInfo" class="px-4 pt-4 pb-4">
<!-- 卡片基本信息卡 -->
<view class="bg-white rounded-16px shadow-[0_2px_12px_rgba(0,0,0,0.06)] overflow-hidden">
<!-- 顶部状态栏 -->
<view class="px-4 pt-4 pb-3 border-b-1px border-[#f5f5f5]">
<view v-if="false" class="flex items-center justify-between mb-2">
<view class="flex items-center gap-2">
<text class="text-16px font-700 text-[#212121]">
IoT卡
</text>
</view>
<view :style="{
backgroundColor: getCardStatusColor(cardInfo.status).bg,
color: getCardStatusColor(cardInfo.status).text,
}" class="px-3 py-1 rounded-8px text-12px font-600">
{{ getCardStatusText(cardInfo.status) }}
</view>
</view>
<text class="text-12px text-[#999]">
{{ cardInfo.current_package || '无套餐' }}
</text>
</view>
<!-- 卡片信息 -->
<view class="px-4 py-3">
<view class="space-y-2">
<!-- ICCID -->
<view class="flex items-start justify-between">
<text class="text-12px text-[#999] w-70px">ICCID</text>
<text class="flex-1 text-13px text-[#212121] text-right break-all">
{{ cardInfo.iccid }}
</text>
</view>
<!-- 虚拟号 -->
<view v-if="cardInfo.virtual_no" class="flex items-start justify-between">
<text class="text-12px text-[#999] w-70px">虚拟号</text>
<text class="flex-1 text-13px text-[#212121] text-right">
{{ cardInfo.virtual_no }}
</text>
</view>
<!-- msisdn -->
<view v-if="cardInfo.msisdn" class="flex items-start justify-between">
<text class="text-12px text-[#999] w-70px">msisdn</text>
<text class="flex-1 text-13px text-[#212121] text-right">
{{ cardInfo.msisdn }}
</text>
</view>
<!-- 卡类型 -->
<view v-if="cardInfo.card_category" class="flex items-start justify-between">
<text class="text-12px text-[#999] w-70px">卡类型</text>
<text class="flex-1 text-13px text-[#212121] text-right">
{{ cardInfo.card_category === 'normal' ? '普通卡' : cardInfo.card_category }}
</text>
</view>
<!-- 网络状态 -->
<view v-if="cardInfo.network_status !== undefined" class="flex items-start justify-between">
<text class="text-12px text-[#999] w-70px">网络状态</text>
<text class="flex-1 text-13px text-[#212121] text-right">
{{ getNetworkStatusText(cardInfo.network_status) }}
</text>
</view>
<!-- 实名状态 -->
<view v-if="cardInfo.real_name_status !== undefined" class="flex items-start justify-between">
<text class="text-12px text-[#999] w-70px">实名状态</text>
<text class="flex-1 text-13px text-[#212121] text-right">
{{ getRealNameStatusText(cardInfo.real_name_status) }}
</text>
</view>
<!-- 累计充值 -->
<view v-if="cardInfo.accumulated_recharge !== undefined" class="flex items-start justify-between">
<text class="text-12px text-[#999] w-70px">累计充值</text>
<text class="flex-1 text-13px font-600 text-[var(--brand-primary)] text-right">
¥{{ cardInfo.accumulated_recharge }}
</text>
</view>
</view>
</view>
<!-- 套餐使用情况 -->
<view v-if="cardInfo.package_total_mb !== undefined && cardInfo.package_total_mb > 0" class="px-4 pb-4">
<view class="bg-[#f5f5f5] rounded-12px p-3">
<view class="flex items-center justify-between mb-2">
<text class="text-12px text-[#999]">
套餐流量
</text>
<text class="text-12px font-600 text-[#212121]">
{{ formatDataSize(cardInfo.package_used_mb) }} / {{ formatDataSize(cardInfo.package_total_mb) }}
</text>
</view>
<!-- 进度条 -->
<view class="h-6px bg-[#e0e0e0] rounded-full overflow-hidden">
<view :style="{
width: `${((cardInfo.package_used_mb || 0) / cardInfo.package_total_mb) * 100}%`,
}" :class="[
'h-full rounded-full transition-all duration-300',
((cardInfo.package_used_mb || 0) / cardInfo.package_total_mb) > 0.9
? 'bg-[#ff6b6b]'
: 'bg-[#3ed268]',
]" />
</view>
</view>
<view class="flex items-center justify-between mt-2">
<text class="text-11px text-[#999]">
剩余: {{ formatDataSize(cardInfo.package_remain_mb) }}
</text>
</view>
</view>
</view>
<!-- 实时状态 -->
<view v-if="realtimeStatus && isCard" class="bg-white rounded-16px shadow-[0_2px_12px_rgba(0,0,0,0.06)] overflow-hidden mt-2 p-2 px-4 pb-3">
<text class="text-13px font-600 text-[#212121] block mb-2">实时状态</text>
<view class="bg-[#f5f5f5] rounded-12px p-3">
<view class="space-y-2">
<!-- 本月用量 -->
<view class="flex items-center justify-between">
<text class="text-12px text-[#666]">本月用量</text>
<text class="text-13px font-600 text-[var(--brand-primary)]">
{{
realtimeStatus.current_month_usage_mb !== null && realtimeStatus.current_month_usage_mb !== undefined
? formatDataSize(realtimeStatus.current_month_usage_mb)
: '0MB'
}}
</text>
</view>
<!-- 实时网络状态 -->
<view class="flex items-center justify-between">
<text class="text-12px text-[#666]">实时网络</text>
<view :class="[
'px-2 py-0.5 rounded text-11px font-600',
realtimeStatus.network_status === 1
? 'bg-[#e8f5e9] text-[#2e7d32]'
: 'bg-[#ffebee] text-[#c62828]',
]">
{{
realtimeStatus.network_status !== null && realtimeStatus.network_status !== undefined
? getNetworkStatusText(realtimeStatus.network_status)
: '未知'
}}
</view>
</view>
<!-- 实名状态 -->
<view class="flex items-center justify-between">
<text class="text-12px text-[#666]">实名状态</text>
<text class="text-13px text-[#212121]">
{{
realtimeStatus.real_name_status !== null && realtimeStatus.real_name_status !== undefined
? getRealNameStatusText(realtimeStatus.real_name_status)
: '未知'
}}
</text>
</view>
<!-- 最后同步时间 -->
<view
class="flex items-center justify-between pt-1 border-t-1px border-[#e5e7eb]">
<text class="text-11px text-[#999]">最后同步</text>
<text class="text-11px text-[#999]">
{{ formatTime(realtimeStatus.last_sync_time) }}
</text>
</view>
</view>
</view>
</view>
<!-- 套餐信息 -->
<view class="bg-white rounded-16px shadow-[0_2px_12px_rgba(0,0,0,0.06)] overflow-hidden mt-2 p-2 px-4 pb-3">
<view class="flex items-center justify-between mb-2">
<text class="text-13px font-600 text-[#212121]">当前生效套餐</text>
<view v-if="false && currentPackage" class="flex items-center gap-1 px-2 py-1 rounded-8px bg-[#f5f5f5]"
@click="togglePackageList">
<text class="text-11px text-[#666]">全部套餐</text>
<i :class="[
'text-14px text-[#999] transition-transform',
showPackageList ? 'i-mdi-chevron-up' : 'i-mdi-chevron-down',
]" />
</view>
</view>
<!-- 当前套餐 -->
<view v-if="currentPackage" class="bg-[#f5f5f5] rounded-12px p-3">
<view class="flex items-start justify-between gap-3 mb-3">
<view class="min-w-0 flex-1">
<text class="text-14px font-600 text-[#212121] block break-all">
{{ currentPackage.package_name || '-' }}
</text>
<text class="text-11px text-[#64748b] block mt-1 break-all">
{{ formatCurrentPackagePeriodRange(currentPackage.activated_at, currentPackage.expires_at) }}
</text>
</view>
<view :style="{
backgroundColor: getPackageStatusColor(currentPackage.status).bg,
color: getPackageStatusColor(currentPackage.status).text,
}" class="px-2 py-0.5 rounded text-10px font-600">
{{ currentPackage.status_name || '-' }}
</view>
</view>
<view class="grid grid-cols-3 gap-2 mb-3">
<view class="bg-white rounded-8px p-2 text-center">
<text class="text-10px text-[#999] block mb-1">总量</text>
<text class="text-12px font-600 text-[#212121]">{{ formatPackageDataSize(getPackageTotalMb(currentPackage)) }}</text>
</view>
<view class="bg-white rounded-8px p-2 text-center">
<text class="text-10px text-[#999] block mb-1">已使用</text>
<text class="text-12px font-600 text-[var(--brand-primary)]">{{ formatPackageDataSize(getPackageUsedMb(currentPackage)) }}</text>
</view>
<view class="bg-white rounded-8px p-2 text-center">
<text class="text-10px text-[#999] block mb-1">剩余</text>
<text class="text-12px font-600 text-[#2e7d32]">{{ formatPackageDataSize(getPackageRemainMb(currentPackage)) }}</text>
</view>
</view>
<view class="bg-white rounded-8px p-2 mb-3">
<view class="flex items-center justify-between mb-1">
<text class="text-11px text-[#666]">已使用</text>
<text class="text-11px font-600 text-[#212121]">
{{ formatPackageDataSize(getPackageUsedMb(currentPackage)) }} /
{{ formatPackageDataSize(getPackageTotalMb(currentPackage)) }}
</text>
</view>
<view class="h-4px bg-[#e0e0e0] rounded-full overflow-hidden">
<view :style="{ width: `${getPackageUsagePercent(currentPackage)}%` }" :class="[
'h-full rounded-full transition-all',
getPackageUsagePercent(currentPackage) > 90 ? 'bg-[#ff6b6b]' : 'bg-[#3ed268]',
]" />
</view>
</view>
<view class="space-y-1 mb-1">
<view class="flex items-center justify-between text-11px">
<text class="text-[#666]">套餐价格</text>
<text class="text-[#212121]">{{ formatPackagePrice(currentPackage.paid_amount) }}</text>
</view>
<view class="flex items-center justify-between text-11px">
<text class="text-[#666]">套餐类型</text>
<text class="text-[#212121]">{{ getPackageTypeText(currentPackage.package_type) }}</text>
</view>
<view class="flex items-center justify-between text-11px">
<text class="text-[#666]">下单时间</text>
<text class="text-[#212121]">{{ formatTime(currentPackage.created_at) }}</text>
</view>
</view>
<view v-if="false" class="flex items-start justify-between gap-3 mb-3">
<view class="min-w-0 flex-1">
<text class="text-14px font-600 text-[#212121] block break-all">
{{ currentPackage.package_name || '-' }}
</text>
<text class="text-11px text-[#999] block mt-1 break-all">
订单号{{ currentPackage.order_no || '-' }}
</text>
</view>
<view :style="{
backgroundColor: getPackageStatusColor(currentPackage.status).bg,
color: getPackageStatusColor(currentPackage.status).text,
}" class="px-2 py-0.5 rounded text-10px font-600">
{{ currentPackage.status_name || '-' }}
</view>
</view>
<view v-if="false" class="grid grid-cols-3 gap-2 mb-3">
<view class="bg-white rounded-8px p-2 text-center">
<text class="text-10px text-[#999] block mb-1">总量</text>
<text class="text-12px font-600 text-[#212121]">{{ formatPackageDataSize(getPackageTotalMb(currentPackage)) }}</text>
</view>
<view class="bg-white rounded-8px p-2 text-center">
<text class="text-10px text-[#999] block mb-1">已使用</text>
<text class="text-12px font-600 text-[var(--brand-primary)]">{{ formatPackageDataSize(getPackageUsedMb(currentPackage)) }}</text>
</view>
<view class="bg-white rounded-8px p-2 text-center">
<text class="text-10px text-[#999] block mb-1">剩余</text>
<text class="text-12px font-600 text-[#2e7d32]">{{ formatPackageDataSize(getPackageRemainMb(currentPackage)) }}</text>
</view>
</view>
<view v-if="false" class="bg-white rounded-8px p-2 mb-3">
<view class="flex items-center justify-between mb-1">
<text class="text-11px text-[#666]">已使用</text>
<text class="text-11px font-600 text-[#212121]">
{{ formatPackageDataSize(getPackageUsedMb(currentPackage)) }} /
{{ formatPackageDataSize(getPackageTotalMb(currentPackage)) }}
</text>
</view>
<view class="h-4px bg-[#e0e0e0] rounded-full overflow-hidden">
<view :style="{ width: `${getPackageUsagePercent(currentPackage)}%` }" :class="[
'h-full rounded-full transition-all',
getPackageUsagePercent(currentPackage) > 90 ? 'bg-[#ff6b6b]' : 'bg-[#3ed268]',
]" />
</view>
</view>
<view v-if="false" class="space-y-1 mb-1">
<view class="flex items-center justify-between text-11px">
<text class="text-[#666]">套餐价格</text>
<text class="text-[#212121]">{{ formatPackagePrice(currentPackage.paid_amount) }}</text>
</view>
<view class="flex items-center justify-between text-11px">
<text class="text-[#666]">套餐类型</text>
<text class="text-[#212121]">{{ getPackageTypeText(currentPackage.package_type) }}</text>
</view>
<view class="flex items-center justify-between text-11px">
<text class="text-[#666]">开始时间</text>
<text class="text-[#212121]">{{ formatTime(currentPackage.activated_at) }}</text>
</view>
<view class="flex items-center justify-between text-11px">
<text class="text-[#666]">到期时间</text>
<text class="text-[#212121]">{{ formatTime(currentPackage.expires_at) }}</text>
</view>
<view class="flex items-center justify-between text-11px">
<text class="text-[#666]">下单时间</text>
<text class="text-[#212121]">{{ formatTime(currentPackage.created_at) }}</text>
</view>
</view>
<view v-if="false" class="flex items-start justify-between gap-3 mb-3">
<view class="min-w-0 flex-1">
<text class="text-14px font-600 text-[#212121] block break-all">
{{ currentPackage.package_name || '-' }}
</text>
<text class="text-11px text-[#999] block mt-1 break-all">
订单号{{ currentPackage.order_no || '-' }}
</text>
</view>
<view :style="{
backgroundColor: getPackageStatusColor(currentPackage.status).bg,
color: getPackageStatusColor(currentPackage.status).text,
}" class="px-2 py-0.5 rounded text-10px font-600">
{{ currentPackage.status_name || '-' }}
</view>
</view>
<view v-if="false" class="grid grid-cols-3 gap-2 mb-3">
<view class="bg-white rounded-8px p-2 text-center">
<text class="text-10px text-[#999] block mb-1">总量</text>
<text class="text-12px font-600 text-[#212121]">{{ formatPackageDataSize(getPackageTotalMb(currentPackage)) }}</text>
</view>
<view class="bg-white rounded-8px p-2 text-center">
<text class="text-10px text-[#999] block mb-1">已使用</text>
<text class="text-12px font-600 text-[var(--brand-primary)]">{{ formatPackageDataSize(getPackageUsedMb(currentPackage)) }}</text>
</view>
<view class="bg-white rounded-8px p-2 text-center">
<text class="text-10px text-[#999] block mb-1">剩余</text>
<text class="text-12px font-600 text-[#2e7d32]">{{ formatPackageDataSize(getPackageRemainMb(currentPackage)) }}</text>
</view>
</view>
<view v-if="false" class="bg-white rounded-8px p-2 mb-3">
<view class="flex items-center justify-between mb-1">
<text class="text-11px text-[#666]">已使用</text>
<text class="text-11px font-600 text-[#212121]">
{{ formatPackageDataSize(getPackageUsedMb(currentPackage)) }} /
{{ formatPackageDataSize(getPackageTotalMb(currentPackage)) }}
</text>
</view>
<view class="h-4px bg-[#e0e0e0] rounded-full overflow-hidden">
<view :style="{ width: `${getPackageUsagePercent(currentPackage)}%` }" :class="[
'h-full rounded-full transition-all',
getPackageUsagePercent(currentPackage) > 90 ? 'bg-[#ff6b6b]' : 'bg-[#3ed268]',
]" />
</view>
</view>
<view v-if="false" class="space-y-1 mb-1">
<view class="flex items-center justify-between text-11px">
<text class="text-[#666]">套餐价格</text>
<text class="text-[#212121]">{{ formatPackagePrice(currentPackage.paid_amount) }}</text>
</view>
<view class="flex items-center justify-between text-11px">
<text class="text-[#666]">套餐类型</text>
<text class="text-[#212121]">{{ getPackageTypeText(currentPackage.package_type) }}</text>
</view>
<view class="flex items-center justify-between text-11px">
<text class="text-[#666]">开始时间</text>
<text class="text-[#212121]">{{ formatTime(currentPackage.activated_at) }}</text>
</view>
<view class="flex items-center justify-between text-11px">
<text class="text-[#666]">到期时间</text>
<text class="text-[#212121]">{{ formatTime(currentPackage.expires_at) }}</text>
</view>
<view class="flex items-center justify-between text-11px">
<text class="text-[#666]">下单时间</text>
<text class="text-[#212121]">{{ formatTime(currentPackage.created_at) }}</text>
</view>
</view>
<!-- 套餐名称和状态 -->
<view v-if="false" class="flex items-center justify-between mb-2">
<view class="flex items-center gap-2">
<i :class="[
'text-16px',
currentPackage.package_type === 'formal' ? 'i-mdi-package-variant text-[var(--brand-primary)]' : 'i-mdi-gas-station text-[#2e7d32]',
]" />
<text class="text-14px font-600 text-[#212121]">
{{ currentPackage.package_name }}
</text>
</view>
<view :style="{
backgroundColor: getPackageStatusColor(currentPackage.status).bg,
color: getPackageStatusColor(currentPackage.status).text,
}" class="px-2 py-0.5 rounded text-10px font-600">
{{ currentPackage.status_name }}
</view>
</view>
<!-- 流量使用进度 -->
<view v-if="false" class="bg-white rounded-8px p-2 mb-2">
<view class="flex items-center justify-between mb-1">
<text class="text-11px text-[#666]">虚拟流量</text>
<text class="text-11px font-600 text-[#212121]">
{{ formatDataSize(currentPackage.virtual_used_mb) }} /
{{ formatDataSize(currentPackage.virtual_limit_mb) }}
</text>
</view>
<!-- 进度条 -->
<view class="h-4px bg-[#e0e0e0] rounded-full overflow-hidden">
<view :style="{ width: `${getPackageUsagePercent(currentPackage)}%` }" :class="[
'h-full rounded-full transition-all',
getPackageUsagePercent(currentPackage) > 90 ? 'bg-[#ff6b6b]' : 'bg-[#3ed268]',
]" />
</view>
</view>
</view>
<!-- 无套餐提示 -->
<view v-else class="bg-[#f5f5f5] rounded-12px p-4 flex flex-col items-center">
<i class="i-mdi-package-variant-closed text-40px text-[#ddd] mb-2" />
<text class="text-12px text-[#999]">暂无生效套餐</text>
</view>
<!-- 套餐列表 -->
<view v-if="packageList.length > 0" class="mt-3">
<text class="text-13px font-600 text-[#212121] block mb-2">套餐列表 ({{ packageList.length }})</text>
<view class="space-y-2">
<view v-for="pkg in packageList" :key="pkg.package_usage_id" class="bg-[#f5f5f5] rounded-12px p-3">
<!-- 套餐头部 -->
<view class="flex items-center justify-between mb-2">
<view class="flex items-center gap-1.5">
<i :class="[
'text-14px',
pkg.package_type === 'formal' ? 'i-mdi-package-variant text-[var(--brand-primary)]' : 'i-mdi-gas-station text-[#2e7d32]',
]" />
<text class="text-13px font-600 text-[#212121]">{{ pkg.package_name }}</text>
<text v-if="pkg.master_usage_id" class="text-10px text-[#999]">(加油包)</text>
</view>
<view :style="{
backgroundColor: getPackageStatusColor(pkg.status).bg,
color: getPackageStatusColor(pkg.status).text,
}" class="px-2 py-0.5 rounded text-10px font-600">
{{ pkg.status_name }}
</view>
</view>
<!-- 流量信息 -->
<view class="space-y-0.5">
<view class="flex items-center justify-between text-11px">
<text class="text-[#666]">已使用</text>
<text class="text-[#212121]">
{{ formatPackageDataSize(getPackageUsedMb(pkg)) }} / {{ formatPackageDataSize(getPackageTotalMb(pkg)) }}
</text>
</view>
<view class="flex items-center justify-between text-11px">
<text class="text-[#666]">剩余</text>
<text class="text-[#212121]">{{ formatPackageDataSize(getPackageRemainMb(pkg)) }}</text>
</view>
<view class="flex items-center justify-between text-11px">
<text class="text-[#666]">金额</text>
<text class="text-[#212121]">{{ formatPackagePrice(pkg.paid_amount) }}</text>
</view>
<view
v-if="pkg.status === 1"
class="mt-2 flex items-start gap-2 rounded-10px border border-[#ffd591] bg-[#fff7e6] px-3 py-2"
>
<i class="i-mdi-calendar-clock mt-2px shrink-0 text-14px text-[#d46b08]" />
<text class="text-12px font-700 leading-5 text-[#ad4e00]">
{{ formatPackageEffectivePeriod(pkg.activated_at, pkg.expires_at) }}
</text>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
<!-- 设备详情 -->
<view v-else-if="isDevice && deviceInfo" class="px-4 pt-4 pb-4">
<!-- 设备基本信息卡 -->
<view class="bg-white rounded-16px shadow-[0_2px_12px_rgba(0,0,0,0.06)] overflow-hidden">
<!-- 顶部状态栏 -->
<view class="px-4 pt-4 pb-3 border-b-1px border-[#f5f5f5]">
<view v-if="false" class="flex items-center justify-between mb-2">
<view class="flex items-center gap-2">
<view>
<text class="text-16px font-700 text-[#212121] block mb-0.5">
{{ deviceInfo.device_name }}
</text>
<!-- 信号条和信号质量 -->
<view v-if="realtimeStatus?.device_realtime?.rsrp !== undefined" class="flex items-center gap-1.5">
<view class="flex items-end gap-0.5">
<view v-for="i in 5" :key="i" class="w-3px rounded-full transition-all"
:class="i <= getSignalBars(realtimeStatus.device_realtime.rsrp) ? 'opacity-100' : 'opacity-20'"
:style="{
height: `${i * 2 + 4}px`,
backgroundColor:
i <= getSignalBars(realtimeStatus.device_realtime.rsrp)
? getSignalQuality(realtimeStatus.device_realtime.rsrp).color
: '#ddd',
}" />
</view>
<text class="text-11px font-600"
:style="{ color: getSignalQuality(realtimeStatus.device_realtime.rsrp).color }">
{{ getSignalQuality(realtimeStatus.device_realtime.rsrp).level }}
</text>
</view>
</view>
</view>
<view :style="{
backgroundColor: getDeviceStatusColor(deviceInfo.status).bg,
color: getDeviceStatusColor(deviceInfo.status).text,
}" class="px-3 py-1 rounded-8px text-12px font-600">
{{ getDeviceStatusText(deviceInfo.status) }}
</view>
</view>
<text class="text-12px text-[#999]">
{{ deviceInfo.current_package || '无套餐' }}
</text>
</view>
<!-- 设备基本信息 -->
<view class="px-4 py-3">
<view class="space-y-2">
<!-- 虚拟号 -->
<view v-if="deviceInfo.virtual_no" class="flex items-start justify-between">
<text class="text-12px text-[#999] w-80px">虚拟号</text>
<text class="flex-1 text-13px text-[#212121] text-right">
{{ deviceInfo.virtual_no }}
</text>
</view>
<!-- IMEI -->
<view v-if="deviceInfo.imei" class="flex items-start justify-between">
<text class="text-12px text-[#999] w-80px">IMEI</text>
<text class="flex-1 text-13px text-[#212121] text-right break-all">
{{ deviceInfo.imei }}
</text>
</view>
<!-- 设备型号 -->
<view v-if="deviceInfo.device_model" class="flex items-start justify-between">
<text class="text-12px text-[#999] w-80px">设备型号</text>
<text class="flex-1 text-13px text-[#212121] text-right">
{{ deviceInfo.device_model }}
</text>
</view>
<!-- 最大卡槽数 -->
<view v-if="deviceInfo.max_sim_slots" class="flex items-start justify-between">
<text class="text-12px text-[#999] w-80px">最大卡槽</text>
<text class="flex-1 text-13px text-[#212121] text-right">
{{ deviceInfo.max_sim_slots }}
</text>
</view>
<!-- 绑定卡数 -->
<view v-if="deviceInfo.bound_card_count !== undefined" class="flex items-start justify-between">
<text class="text-12px text-[#999] w-80px">绑定卡数</text>
<text class="flex-1 text-13px font-600 text-[var(--brand-primary)] text-right">
{{ deviceInfo.bound_card_count }}
</text>
</view>
<!-- 店铺信息 -->
<view v-if="deviceInfo.shop_name" class="flex items-start justify-between">
<text class="text-12px text-[#999] w-80px">所属店铺</text>
<text class="flex-1 text-13px text-[#212121] text-right">
{{ deviceInfo.shop_name }}
</text>
</view>
<!-- 套餐系列 -->
<view v-if="deviceInfo.series_name" class="flex items-start justify-between">
<text class="text-12px text-[#999] w-80px">套餐系列</text>
<text class="flex-1 text-13px text-[#212121] text-right">
{{ deviceInfo.series_name }}
</text>
</view>
<!-- 实名状态 -->
<view v-if="deviceRealNameStatus !== undefined" class="flex items-start justify-between">
<text class="text-12px text-[#999] w-80px">实名状态</text>
<text class="flex-1 text-13px text-[#212121] text-right">
{{ getRealNameStatusText(deviceRealNameStatus) }}
</text>
</view>
<!-- 累计充值 -->
<view v-if="deviceInfo.accumulated_recharge !== undefined" class="flex items-start justify-between">
<text class="text-12px text-[#999] w-80px">累计充值</text>
<text class="flex-1 text-13px font-600 text-[var(--brand-primary)] text-right">
¥{{ deviceInfo.accumulated_recharge }}
</text>
</view>
</view>
</view>
<!-- 套餐使用情况 -->
<view v-if="deviceInfo.package_total_mb !== undefined && deviceInfo.package_total_mb > 0" class="px-4 pb-4">
<view class="bg-[#f5f5f5] rounded-12px p-3">
<view class="flex items-center justify-between mb-2">
<text class="text-12px text-[#999]">
套餐流量
</text>
<text class="text-12px font-600 text-[#212121]">
{{ formatDataSize(deviceInfo.package_used_mb) }} / {{ formatDataSize(deviceInfo.package_total_mb) }}
</text>
</view>
<!-- 进度条 -->
<view class="h-6px bg-[#e0e0e0] rounded-full overflow-hidden">
<view :style="{
width: `${((deviceInfo.package_used_mb || 0) / deviceInfo.package_total_mb) * 100}%`,
}" :class="[
'h-full rounded-full transition-all duration-300',
((deviceInfo.package_used_mb || 0) / deviceInfo.package_total_mb) > 0.9
? 'bg-[#ff6b6b]'
: 'bg-[#3ed268]',
]" />
</view>
</view>
<view class="flex items-center justify-between mt-2">
<text class="text-11px text-[#999]">
剩余: {{ formatDataSize(deviceInfo.package_remain_mb) }}
</text>
</view>
</view>
</view>
<!-- 绑定卡片列表 -->
<view v-if="deviceBoundCards.length > 0" class="bg-white rounded-16px shadow-[0_2px_12px_rgba(0,0,0,0.06)] overflow-hidden mt-2 p-2 px-4 pb-3">
<text class="text-13px font-600 text-[#212121] block mb-2">绑定IoT卡</text>
<view class="space-y-2">
<view v-for="card in deviceBoundCards" :key="card.card_id" class="bg-[#f5f5f5] rounded-12px p-3">
<view class="flex items-center justify-between mb-1">
<view class="flex items-center gap-2 min-w-0">
<text class="text-12px text-[#212121]">卡槽 {{ card.slot_position }}</text>
<text :class="[
'px-2 py-0.5 rounded-full text-11px font-600',
getRealNameStatusTagClass(card.real_name_status),
]">
{{ getRealNameStatusText(card.real_name_status) }}
</text>
</view>
<text v-if="card.is_current" class="text-11px text-white bg-[#3ed268] px-2 py-0.5 rounded">
当前
</text>
</view>
<view class="space-y-1">
<text class="text-11px text-[#666] block">ICCID: {{ card.iccid }}</text>
<text class="text-11px text-[#666] block">MSISDN: {{ card.msisdn || '-' }}</text>
<text class="text-11px text-[#666] block">运营商: {{ card.carrier_name || '-' }}</text>
<view class="flex items-center justify-between">
<text class="text-11px text-[#666]">
网络: {{ getNetworkStatusText(card.network_status) }}
</text>
</view>
</view>
</view>
</view>
</view>
<!-- 设备实时状态 -->
<view v-if="realtimeStatus?.device_realtime" class="bg-white rounded-16px shadow-[0_2px_12px_rgba(0,0,0,0.06)] overflow-hidden mt-2 p-2 px-4 pb-3">
<text class="text-13px font-600 text-[#212121] block mb-2">设备实时状态</text>
<!-- 在线状态 -->
<view class="bg-[#f5f5f5] rounded-12px p-3 mb-2">
<view class="flex items-center justify-between mb-2">
<text class="text-12px text-[#666]">在线状态</text>
<view :class="[
'px-2 py-0.5 rounded text-11px font-600',
realtimeStatus.device_realtime.online_status === 1 ? 'bg-[#e8f5e9] text-[#2e7d32]' : 'bg-[#f5f5f5] text-[#999]',
]">
{{ getOnlineStatusText(realtimeStatus.device_realtime.online_status) }}
</view>
</view>
<view class="space-y-1">
<!-- 电池电量 -->
<view v-if="realtimeStatus.device_realtime.battery_level !== undefined"
class="flex items-center justify-between text-11px">
<text class="text-[#666]">电池电量</text>
<text class="text-[#212121]">{{ realtimeStatus.device_realtime.battery_level }}%</text>
</view>
<!-- 连接客户端 -->
<view v-if="realtimeStatus.device_realtime.client_number !== undefined"
class="flex items-center justify-between text-11px">
<text class="text-[#666]">连接客户端</text>
<text class="text-[#212121]">
{{ realtimeStatus.device_realtime.client_number }} /
{{ realtimeStatus.device_realtime.max_clients }}
</text>
</view>
<!-- 连接时长 -->
<view v-if="realtimeStatus.device_realtime.connect_time"
class="flex items-center justify-between text-11px">
<text class="text-[#666]">连接时长</text>
<text class="text-[#212121]">{{ formatDuration(realtimeStatus.device_realtime.connect_time) }}</text>
</view>
<!-- 运行时长 -->
<view v-if="realtimeStatus.device_realtime.run_time" class="flex items-center justify-between text-11px">
<text class="text-[#666]">运行时长</text>
<text class="text-[#212121]">{{ formatDuration(realtimeStatus.device_realtime.run_time) }}</text>
</view>
<view class="flex items-center justify-between text-11px">
<text class="text-[#666]">最后上线时间</text>
<text class="text-[#212121]">{{ getDeviceLastOnlineTime() }}</text>
</view>
<!-- 信号 -->
<view class="flex items-center justify-between text-11px">
<text class="text-[#666]">信号</text>
<text class="text-[#212121]">{{ getDisplayValue(realtimeStatus.device_realtime.signal_quality) }}</text>
</view>
<!-- 信号差原因 -->
<view class="flex items-center justify-between text-11px">
<text class="text-[#666]">信号差原因</text>
<text class="text-[#212121]">{{ getDisplayValue(realtimeStatus.device_realtime.signal_bad_reason) }}</text>
</view>
</view>
</view>
<!-- 流量统计 -->
<view class="bg-[#f5f5f5] rounded-12px p-3 mb-2">
<text class="text-12px text-[#666] block mb-2">流量统计</text>
<view class="space-y-1">
<!-- 今日用量 -->
<view class="flex items-center justify-between text-11px">
<text class="text-[#666]">今日用量</text>
<text class="text-[var(--brand-primary)] font-600">{{ formatBytesDisplay(realtimeStatus.device_realtime.daily_usage) }}</text>
</view>
<!-- 限速 -->
<view class="flex items-center justify-between text-11px">
<text class="text-[#666]">限速</text>
<text class="text-[#212121]">
{{ isEmptyDisplayValue(realtimeStatus.device_realtime.limit_speed) ? '-' : `${realtimeStatus.device_realtime.limit_speed} Mbps` }}
</text>
</view>
</view>
</view>
<!-- 网络信息 -->
<view class="bg-[#f5f5f5] rounded-12px p-3 mb-2">
<text class="text-12px text-[#666] block mb-2">网络信息</text>
<view class="space-y-1">
<!-- IP地址 -->
<view v-if="realtimeStatus.device_realtime.ip_address" class="flex items-center justify-between text-11px">
<text class="text-[#666]">IP地址</text>
<text class="text-[#212121]">{{ realtimeStatus.device_realtime.ip_address }}</text>
</view>
<!-- LAN IP -->
<view v-if="realtimeStatus.device_realtime.lan_ip" class="flex items-center justify-between text-11px">
<text class="text-[#666]">LAN IP</text>
<text class="text-[#212121]">{{ realtimeStatus.device_realtime.lan_ip }}</text>
</view>
<!-- WAN IP -->
<view v-if="realtimeStatus.device_realtime.wan_ip" class="flex items-center justify-between text-11px">
<text class="text-[#666]">WAN IP</text>
<text class="text-[#212121]">{{ realtimeStatus.device_realtime.wan_ip }}</text>
</view>
<!-- MAC地址 -->
<view v-if="realtimeStatus.device_realtime.mac_address" class="flex items-center justify-between text-11px">
<text class="text-[#666]">MAC地址</text>
<text class="text-[#212121]">{{ realtimeStatus.device_realtime.mac_address }}</text>
</view>
</view>
</view>
<!-- WiFi信息 -->
<view v-if="realtimeStatus.device_realtime.ssid" class="bg-[#f5f5f5] rounded-12px p-3 mb-2">
<text class="text-12px text-[#666] block mb-2">WiFi信息</text>
<view class="space-y-1">
<!-- WiFi名称 -->
<view class="flex items-center justify-between text-11px">
<text class="text-[#666]">WiFi名称</text>
<text class="text-[#212121]">{{ realtimeStatus.device_realtime.ssid }}</text>
</view>
<!-- WiFi状态 -->
<view class="flex items-center justify-between text-11px">
<text class="text-[#666]">WiFi状态</text>
<text :class="realtimeStatus.device_realtime.wifi_enabled ? 'text-[#2e7d32]' : 'text-[#999]'">
{{ realtimeStatus.device_realtime.wifi_enabled ? '已开启' : '已关闭' }}
</text>
</view>
<!-- WiFi密码 -->
<view v-if="realtimeStatus.device_realtime.wifi_password"
class="flex items-center justify-between text-11px">
<text class="text-[#666]">WiFi密码</text>
<text class="text-[#212121]">{{ realtimeStatus.device_realtime.wifi_password }}</text>
</view>
</view>
</view>
<!-- 其他信息 -->
<view class="bg-[#f5f5f5] rounded-12px p-3">
<view class="space-y-1">
<!-- 软件版本 -->
<view v-if="realtimeStatus.device_realtime.software_version"
class="flex items-center justify-between text-11px">
<text class="text-[#666]">软件版本</text>
<text class="text-[#212121]">{{ realtimeStatus.device_realtime.software_version }}</text>
</view>
<!-- IMSI -->
<view v-if="realtimeStatus.device_realtime.imsi" class="flex items-center justify-between text-11px">
<text class="text-[#666]">IMSI</text>
<text class="text-[#212121]">{{ realtimeStatus.device_realtime.imsi }}</text>
</view>
<!-- 当前ICCID -->
<view v-if="realtimeStatus.device_realtime.current_iccid"
class="flex items-center justify-between text-11px">
<text class="text-[#666]">当前ICCID</text>
<text class="text-[#212121]">{{ realtimeStatus.device_realtime.current_iccid }}</text>
</view>
<!-- 同步间隔 -->
<view v-if="realtimeStatus.device_realtime.sync_interval"
class="flex items-center justify-between text-11px">
<text class="text-[#666]">同步间隔</text>
<text class="text-[#212121]">{{ realtimeStatus.device_realtime.sync_interval }}</text>
</view>
<!-- 最后更新 -->
<view v-if="realtimeStatus.device_realtime.last_update_time"
class="flex items-center justify-between text-11px">
<text class="text-[#666]">最后更新</text>
<text class="text-[#999]">{{ formatTime(realtimeStatus.device_realtime.last_update_time) }}</text>
</view>
</view>
</view>
</view>
<!-- 套餐信息 -->
<view class="bg-white rounded-16px shadow-[0_2px_12px_rgba(0,0,0,0.06)] overflow-hidden mt-2 p-2 px-4 pb-3">
<view class="flex items-center justify-between mb-2">
<text class="text-13px font-600 text-[#212121]">当前生效套餐</text>
<view v-if="false && currentPackage" class="flex items-center gap-1 px-2 py-1 rounded-8px bg-[#f5f5f5]"
@click="togglePackageList">
<text class="text-11px text-[#666]">全部套餐</text>
<i :class="[
'text-14px text-[#999] transition-transform',
showPackageList ? 'i-mdi-chevron-up' : 'i-mdi-chevron-down',
]" />
</view>
</view>
<!-- 当前套餐 -->
<view v-if="currentPackage" class="bg-[#f5f5f5] rounded-12px p-3">
<view class="flex items-start justify-between gap-3 mb-3">
<view class="min-w-0 flex-1">
<text class="text-14px font-600 text-[#212121] block break-all">
{{ currentPackage.package_name || '-' }}
</text>
<text class="text-11px text-[#64748b] block mt-1 break-all">
{{ formatCurrentPackagePeriodRange(currentPackage.activated_at, currentPackage.expires_at) }}
</text>
</view>
<view :style="{
backgroundColor: getPackageStatusColor(currentPackage.status).bg,
color: getPackageStatusColor(currentPackage.status).text,
}" class="px-2 py-0.5 rounded text-10px font-600">
{{ currentPackage.status_name || '-' }}
</view>
</view>
<view class="grid grid-cols-3 gap-2 mb-3">
<view class="bg-white rounded-8px p-2 text-center">
<text class="text-10px text-[#999] block mb-1">总量</text>
<text class="text-12px font-600 text-[#212121]">{{ formatPackageDataSize(getPackageTotalMb(currentPackage)) }}</text>
</view>
<view class="bg-white rounded-8px p-2 text-center">
<text class="text-10px text-[#999] block mb-1">已使用</text>
<text class="text-12px font-600 text-[var(--brand-primary)]">{{ formatPackageDataSize(getPackageUsedMb(currentPackage)) }}</text>
</view>
<view class="bg-white rounded-8px p-2 text-center">
<text class="text-10px text-[#999] block mb-1">剩余</text>
<text class="text-12px font-600 text-[#2e7d32]">{{ formatPackageDataSize(getPackageRemainMb(currentPackage)) }}</text>
</view>
</view>
<view class="bg-white rounded-8px p-2 mb-3">
<view class="flex items-center justify-between mb-1">
<text class="text-11px text-[#666]">已使用</text>
<text class="text-11px font-600 text-[#212121]">
{{ formatPackageDataSize(getPackageUsedMb(currentPackage)) }} /
{{ formatPackageDataSize(getPackageTotalMb(currentPackage)) }}
</text>
</view>
<view class="h-4px bg-[#e0e0e0] rounded-full overflow-hidden">
<view :style="{ width: `${getPackageUsagePercent(currentPackage)}%` }" :class="[
'h-full rounded-full transition-all',
getPackageUsagePercent(currentPackage) > 90 ? 'bg-[#ff6b6b]' : 'bg-[#3ed268]',
]" />
</view>
</view>
<view class="space-y-1 mb-1">
<view class="flex items-center justify-between text-11px">
<text class="text-[#666]">套餐价格</text>
<text class="text-[#212121]">{{ formatPackagePrice(currentPackage.paid_amount) }}</text>
</view>
<view class="flex items-center justify-between text-11px">
<text class="text-[#666]">套餐类型</text>
<text class="text-[#212121]">{{ getPackageTypeText(currentPackage.package_type) }}</text>
</view>
<view class="flex items-center justify-between text-11px">
<text class="text-[#666]">下单时间</text>
<text class="text-[#212121]">{{ formatTime(currentPackage.created_at) }}</text>
</view>
</view>
<!-- 套餐名称和状态 -->
<view v-if="false" class="flex items-center justify-between mb-2">
<view class="flex items-center gap-2">
<i :class="[
'text-16px',
currentPackage.package_type === 'formal' ? 'i-mdi-package-variant text-[var(--brand-primary)]' : 'i-mdi-gas-station text-[#2e7d32]',
]" />
<text class="text-14px font-600 text-[#212121]">
{{ currentPackage.package_name }}
</text>
</view>
<view :style="{
backgroundColor: getPackageStatusColor(currentPackage.status).bg,
color: getPackageStatusColor(currentPackage.status).text,
}" class="px-2 py-0.5 rounded text-10px font-600">
{{ currentPackage.status_name }}
</view>
</view>
<!-- 流量使用进度 -->
<view v-if="false" class="bg-white rounded-8px p-2 mb-2">
<view class="flex items-center justify-between mb-1">
<text class="text-11px text-[#666]">虚拟流量</text>
<text class="text-11px font-600 text-[#212121]">
{{ formatDataSize(currentPackage.virtual_used_mb) }} /
{{ formatDataSize(currentPackage.virtual_limit_mb) }}
</text>
</view>
<!-- 进度条 -->
<view class="h-4px bg-[#e0e0e0] rounded-full overflow-hidden">
<view :style="{ width: `${getPackageUsagePercent(currentPackage)}%` }" :class="[
'h-full rounded-full transition-all',
getPackageUsagePercent(currentPackage) > 90 ? 'bg-[#ff6b6b]' : 'bg-[#3ed268]',
]" />
</view>
</view>
</view>
<!-- 无套餐提示 -->
<view v-else class="bg-[#f5f5f5] rounded-12px p-4 flex flex-col items-center">
<i class="i-mdi-package-variant-closed text-40px text-[#ddd] mb-2" />
<text class="text-12px text-[#999]">暂无生效套餐</text>
</view>
<!-- 套餐列表 -->
<view v-if="packageList.length > 0" class="mt-3">
<text class="text-13px font-600 text-[#212121] block mb-2">套餐列表 ({{ packageList.length }})</text>
<view class="space-y-2">
<view v-for="pkg in packageList" :key="pkg.package_usage_id" class="bg-[#f5f5f5] rounded-12px p-3">
<!-- 套餐头部 -->
<view class="flex items-center justify-between mb-2">
<view class="flex items-center gap-1.5">
<i :class="[
'text-14px',
pkg.package_type === 'formal' ? 'i-mdi-package-variant text-[var(--brand-primary)]' : 'i-mdi-gas-station text-[#2e7d32]',
]" />
<text class="text-13px font-600 text-[#212121]">{{ pkg.package_name }}</text>
<text v-if="pkg.master_usage_id" class="text-10px text-[#999]">(加油包)</text>
</view>
<view :style="{
backgroundColor: getPackageStatusColor(pkg.status).bg,
color: getPackageStatusColor(pkg.status).text,
}" class="px-2 py-0.5 rounded text-10px font-600">
{{ pkg.status_name }}
</view>
</view>
<!-- 流量信息 -->
<view class="space-y-0.5">
<view class="flex items-center justify-between text-11px">
<text class="text-[#666]">已使用</text>
<text class="text-[#212121]">
{{ formatPackageDataSize(getPackageUsedMb(pkg)) }} / {{ formatPackageDataSize(getPackageTotalMb(pkg)) }}
</text>
</view>
<view class="flex items-center justify-between text-11px">
<text class="text-[#666]">剩余</text>
<text class="text-[#212121]">{{ formatPackageDataSize(getPackageRemainMb(pkg)) }}</text>
</view>
<view class="flex items-center justify-between text-11px">
<text class="text-[#666]">金额</text>
<text class="text-[#212121]">{{ formatPackagePrice(pkg.paid_amount) }}</text>
</view>
<view
v-if="pkg.status === 1"
class="mt-2 flex items-start gap-2 rounded-10px border border-[#ffd591] bg-[#fff7e6] px-3 py-2"
>
<i class="i-mdi-calendar-clock mt-2px shrink-0 text-14px text-[#d46b08]" />
<text class="text-12px font-700 leading-5 text-[#ad4e00]">
{{ formatPackageEffectivePeriod(pkg.activated_at, pkg.expires_at) }}
</text>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
<!-- 空状态 -->
<view v-else class="px-4 pt-20 flex flex-col items-center">
<i class="i-mdi-package-variant-closed text-6xl text-[#cbd5e1] mb-3" />
<text class="text-base text-[#64748b]">暂无数据</text>
</view>
<!-- 底部留白 -->
<view class="h-8" />
</view>
</template>
<style scoped>
.space-y-2>view:not(:last-child) {
margin-bottom: 8px;
}
.break-all {
word-break: break-all;
}
</style>