Files
one-pipe-system/src/views/asset-management/asset-information/composables/useAssetInfo.ts
sexygoat e04a283319
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 4m38s
fix: 新增代理系列授权-建议售价使用套餐
2026-06-12 10:41:37 +08:00

462 lines
17 KiB
TypeScript
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.
/**
* 资产信息数据管理 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<string, unknown>
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<any>(null) // 使用 any 以保持与原始实现一致
const deviceRealtime = ref<DeviceGatewayInfo | null>(null)
const currentPackage = ref<AssetPackageUsageRecord | null>(null)
const currentPackageLoading = ref(false)
const currentPackageErrorMsg = ref('')
const packageList = ref<AssetPackageUsageRecord[]>([])
const walletInfo = ref<AssetWalletResponse | null>(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<boolean> => {
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
}
}