/** * 资产信息数据管理 Composable * 负责资产详情数据的加载和状态管理 */ import { ref } from 'vue' import { ElMessage } from 'element-plus' import { AssetService } from '@/api/modules' import type { AssetBoundCard, AssetWalletResponse, DeviceGatewayInfo, AssetPackageUsageRecord } from '@/types/api' type TrafficDisplaySource = { real_total_mb?: number | null real_used_mb?: number | null virtual_used_mb?: number | null enable_virtual_data?: boolean | null } const normalizeOptionalText = (value?: string | null) => typeof value === 'string' ? value.trim() : '' const NULLABLE_REALTIME_CARD_FIELDS = new Set([ 'last_data_check_at', 'last_real_name_check_at', 'last_card_status_check_at' ]) /** * 格式化数据大小(MB -> GB/MB) */ const formatDataSize = (mb: number) => { if (mb >= 1024) { return `${(mb / 1024).toFixed(2)}GB` } return `${mb.toFixed(2)}MB` } const getDisplayedTotalMb = (traffic?: TrafficDisplaySource | null) => traffic?.real_total_mb || 0 const getDisplayedUsedMb = (traffic?: TrafficDisplaySource | null) => traffic?.enable_virtual_data ? traffic?.virtual_used_mb || 0 : traffic?.real_used_mb || 0 const getDisplayedRemainingMb = (traffic?: TrafficDisplaySource | null) => getDisplayedTotalMb(traffic) - getDisplayedUsedMb(traffic) const getDisplayedUsagePercentage = (traffic?: TrafficDisplaySource | null) => { const totalMb = getDisplayedTotalMb(traffic) const usedMb = getDisplayedUsedMb(traffic) return totalMb > 0 ? `${((usedMb / totalMb) * 100).toFixed(2)}%` : '0.00%' } const mergeAssetBoundCards = ( existingCards: AssetBoundCard[] = [], realtimeCards: AssetBoundCard[] = [] ): AssetBoundCard[] => realtimeCards.map((realtimeCard) => { const existingCard = existingCards.find((card) => card.iccid === realtimeCard.iccid) if (!existingCard) return realtimeCard const mergedCard = { ...existingCard } as unknown as Record for (const [key, value] of Object.entries(realtimeCard)) { if (value !== undefined && (value !== null || NULLABLE_REALTIME_CARD_FIELDS.has(key))) { mergedCard[key] = value } } return mergedCard as unknown as AssetBoundCard }) export function useAssetInfo() { // 状态管理 const loading = ref(false) const cardInfo = ref(null) // 使用 any 以保持与原始实现一致 const deviceRealtime = ref(null) const currentPackage = ref(null) const currentPackageLoading = ref(false) const currentPackageErrorMsg = ref('') const packageList = ref([]) const walletInfo = ref(null) const walletLoading = ref(false) const packageLoading = ref(false) const realtimeLoading = ref(false) /** * 获取资产详情 * @param identifier - 资产标识符(ICCID、IMEI、SN、虚拟号或MSISDN) */ const fetchAssetDetail = async (identifier: string) => { try { loading.value = true const response = await AssetService.resolveAsset(identifier) if (response.code === 0 && response.data) { const data = response.data const resolvedGatewayCardImei = normalizeOptionalText(data.gateway_card_imei) // 获取资产标识符 const assetIdentifier = data.asset_type === 'card' ? data.iccid : data.virtual_no // 映射API数据到页面显示格式 cardInfo.value = { // 基本信息 asset_type: data.asset_type, // card 或 device asset_id: data.asset_id, identifier: assetIdentifier || '', // 资产标识符(卡用iccid,设备用virtual_no) virtual_no: data.virtual_no, status: data.status, batch_no: data.batch_no, shop_id: data.shop_id, shop_name: data.shop_name, series_id: data.series_id, series_name: data.series_name, real_name_status: data.real_name_status, realname_policy: data.realname_policy, real_name_at: data.real_name_at, activation_status_name: data.activation_status_name || '', activated_at: data.activated_at, created_at: data.created_at, updated_at: data.updated_at, accumulated_recharge: data.accumulated_recharge, first_commission_paid: data.first_commission_paid, // 卡专属字段 (asset_type === 'card') iccid: data.iccid || '', msisdn: data.msisdn || '', imsi: data.imsi || '', carrier_id: data.carrier_id, carrier_type: data.carrier_type || '', carrier_name: data.carrier_name || '', supplier: data.supplier || '', network_status: data.network_status, gateway_card_imei: resolvedGatewayCardImei, resolved_gateway_card_imei: resolvedGatewayCardImei, gateway_extend: data.gateway_extend ?? '', bound_device_id: data.bound_device_id, bound_device_no: data.bound_device_no || '', bound_device_name: data.bound_device_name || '', card_category: data.card_category || '', activation_status: data.activation_status, enable_polling: data.enable_polling, // 设备专属字段 (asset_type === 'device') device_name: data.device_name || '', device_model: data.device_model || '', device_type: data.device_type || '', manufacturer: data.manufacturer || '', imei: data.imei || '', sn: data.sn || '', max_sim_slots: data.max_sim_slots || 0, bound_card_count: data.bound_card_count || 0, cards: data.cards || [], device_protect_status: data.device_protect_status, online_status: data.online_status, last_online_time: data.last_online_time, software_version: data.software_version, switch_mode: data.switch_mode, last_gateway_sync_at: data.last_gateway_sync_at, // 流量/套餐信息 current_package: data.current_package || '', packageSeries: data.series_name || '-', packageTotalFlow: formatDataSize(getDisplayedTotalMb(data)), usedFlow: formatDataSize(getDisplayedUsedMb(data)), remainFlow: formatDataSize(getDisplayedRemainingMb(data)), usedFlowPercentage: getDisplayedUsagePercentage(data), packageList: [] // 套餐列表需要单独调用API获取 } // 加载其他数据 if (assetIdentifier) { // 并行调用多个接口: 当前生效套餐、实时状态、钱包概况 // 套餐列表不在这里调用,由父组件调用以便管理分页状态 await Promise.all([ loadCurrentPackage(assetIdentifier), loadRealtimeStatus(assetIdentifier, data.asset_type), loadAssetWallet(assetIdentifier) ]) } } else { ElMessage.error(response.msg || '查询失败') cardInfo.value = null } } catch (error: any) { cardInfo.value = null } finally { loading.value = false } } /** * 加载套餐列表 * @param identifier - 资产标识符 * @param page - 页码 * @param pageSize - 每页条数 */ const loadPackageList = async (identifier: string, page: number = 1, pageSize: number = 10) => { try { packageLoading.value = true const response = await AssetService.getAssetPackages(identifier, { page, page_size: pageSize }) if (response.code === 0 && response.data) { // 使用分页响应中的 items 数组 packageList.value = response.data.items || [] // 返回分页信息供外部使用 return { items: response.data.items || [], total: response.data.total || 0, page: response.data.page || 1, pageSize: response.data.page_size || pageSize } } return { items: [], total: 0, page: 1, pageSize } } catch (error) { console.error('获取套餐列表失败:', error) return { items: [], total: 0, page: 1, pageSize } } finally { packageLoading.value = false } } /** * 加载当前生效套餐 * @param identifier - 资产标识符 */ const loadCurrentPackage = async (identifier: string) => { try { currentPackageLoading.value = true currentPackageErrorMsg.value = '' const response = await AssetService.getCurrentPackage(identifier) if (response.code === 0) { if (response.data) { const pkg = response.data currentPackage.value = { id: pkg.package_usage_id || 0, package_id: pkg.package_id || 0, order_id: pkg.order_id || 0, order_no: pkg.order_no || '', package_name: pkg.package_name || '', paid_amount: pkg.paid_amount || 0, retail_amount: pkg.retail_amount || 0, real_total_mb: pkg.real_total_mb || 0, real_used_mb: pkg.real_used_mb || 0, real_remaining_mb: (pkg.real_total_mb || 0) - (pkg.real_used_mb || 0), virtual_total_mb: pkg.virtual_total_mb || 0, virtual_used_mb: pkg.virtual_used_mb || 0, virtual_remaining_mb: (pkg.virtual_total_mb || 0) - (pkg.virtual_used_mb || 0), reduction_pct: pkg.reduction_pct || 0, start_time: pkg.activated_at || '', expire_time: pkg.expires_at || '', expiry_base: pkg.expiry_base ?? null, status: pkg.status || 0, status_name: pkg.status_name, duration_days: undefined, package_type: pkg.package_type, enable_virtual_data: pkg.enable_virtual_data, created_at: pkg.created_at || '' } // 同时更新 cardInfo 中的流量显示信息(根据是否启用虚流量选择数据源) cardInfo.value.current_package = pkg.package_name cardInfo.value.packageTotalFlow = formatDataSize(getDisplayedTotalMb(pkg)) cardInfo.value.usedFlow = formatDataSize(getDisplayedUsedMb(pkg)) cardInfo.value.remainFlow = formatDataSize(getDisplayedRemainingMb(pkg)) cardInfo.value.usedFlowPercentage = getDisplayedUsagePercentage(pkg) } else { // code 为 0 但 data 为空,表示暂无当前生效套餐(正常情况) currentPackage.value = null currentPackageErrorMsg.value = '' } } else { // 接口返回 code 不为 0,保存错误信息 currentPackage.value = null currentPackageErrorMsg.value = response.msg || '获取当前套餐失败' } } catch (error: any) { // 404表示无当前生效套餐,这是正常情况 currentPackage.value = null if (error?.response?.status === 404) { currentPackageErrorMsg.value = '' } else { console.error('获取当前套餐失败:', error) currentPackageErrorMsg.value = error?.message || '获取当前套餐失败' } } finally { currentPackageLoading.value = false } } /** * 加载实时状态 * @param identifier - 资产标识符 * @param assetType - 资产类型(card/device) */ const loadRealtimeStatus = async (identifier: string, assetType: string) => { try { realtimeLoading.value = true const response = await AssetService.getRealtimeStatus(identifier) if (response.code === 0 && response.data) { const data = response.data // 更新实时状态数据 if (assetType === 'card') { // 卡的实时状态 if (data.network_status !== undefined) { cardInfo.value.network_status = data.network_status } if (data.real_name_status !== undefined) { cardInfo.value.real_name_status = data.real_name_status } if ('gateway_card_imei' in data) { const resolvedGatewayCardImei = normalizeOptionalText( cardInfo.value?.resolved_gateway_card_imei ) cardInfo.value.gateway_card_imei = resolvedGatewayCardImei || normalizeOptionalText(data.gateway_card_imei) } if (data.last_sync_time !== undefined) { cardInfo.value.last_sync_time = data.last_sync_time } if (data.last_data_check_at !== undefined) { cardInfo.value.last_data_check_at = data.last_data_check_at } if (data.last_real_name_check_at !== undefined) { cardInfo.value.last_real_name_check_at = data.last_real_name_check_at } if (data.last_card_status_check_at !== undefined) { cardInfo.value.last_card_status_check_at = data.last_card_status_check_at } if (data.current_month_usage_mb !== undefined) { cardInfo.value.current_month_usage_mb = data.current_month_usage_mb } if (data.last_gateway_reading_mb !== undefined) { cardInfo.value.last_gateway_reading_mb = data.last_gateway_reading_mb } } else if (assetType === 'device') { // 设备的实时状态 if (data.device_protect_status !== undefined) { cardInfo.value.device_protect_status = data.device_protect_status } if (data.cards && data.cards.length > 0) { // 合并实时卡数据与原始卡数据,保留 real_name_at 等静态字段。 // 设备详情里的实名状态、实名时间依赖选中的绑定卡,因此不能在实时刷新时丢掉这些字段。 cardInfo.value.cards = mergeAssetBoundCards(cardInfo.value.cards || [], data.cards) } if (data.last_data_check_at !== undefined) { cardInfo.value.last_data_check_at = data.last_data_check_at } if (data.last_real_name_check_at !== undefined) { cardInfo.value.last_real_name_check_at = data.last_real_name_check_at } if (data.last_card_status_check_at !== undefined) { cardInfo.value.last_card_status_check_at = data.last_card_status_check_at } if (data.online_status !== undefined) { cardInfo.value.online_status = data.online_status } if (data.last_online_time !== undefined) { cardInfo.value.last_online_time = data.last_online_time } if (data.software_version !== undefined) { cardInfo.value.software_version = data.software_version } if (data.switch_mode !== undefined) { cardInfo.value.switch_mode = data.switch_mode } if (data.last_gateway_sync_at !== undefined) { cardInfo.value.last_gateway_sync_at = data.last_gateway_sync_at } // 设备实时信息(Gateway 数据) if (data.device_realtime !== undefined) { deviceRealtime.value = data.device_realtime } } } } catch (error) { console.error('获取实时状态失败:', error) } finally { realtimeLoading.value = false } } /** * 加载钱包概况 * @param identifier - 资产标识符 */ const loadAssetWallet = async (identifier: string) => { try { walletLoading.value = true const response = await AssetService.getAssetWallet(identifier) if (response.code === 0 && response.data) { walletInfo.value = response.data } } catch (error: any) { if (error?.response?.status === 403) { // 企业账号禁止查看钱包信息,不显示错误 walletInfo.value = null } else { console.error('获取钱包概况失败:', error) } } finally { walletLoading.value = false } } /** * 刷新资产数据(调用refresh接口后重新加载) * @param identifier - 资产标识符 * @param assetType - 资产类型(card/device) * @returns true if refresh was successful, false if rate limited (429) */ const refreshAsset = async (identifier: string, assetType: string): Promise => { try { await AssetService.refreshAsset(identifier) ElMessage.success('刷新成功') // 刷新成功后重新加载实时状态数据 if (identifier) { await loadRealtimeStatus(identifier, assetType) } return true } catch (error: any) { console.log(error) // Check if it's a 429 rate limit error if (error?.response?.status === 429) { ElMessage.warning('操作过于频繁,请30秒后再试') return false } return false } } return { // State loading, cardInfo, deviceRealtime, currentPackage, currentPackageLoading, currentPackageErrorMsg, packageList, packageLoading, walletInfo, walletLoading, realtimeLoading, // Methods fetchAssetDetail, loadPackageList, loadCurrentPackage, loadRealtimeStatus, loadAssetWallet, refreshAsset } }