From 9a6f085cdead297d5b73bc567307db42cfdb20e2 Mon Sep 17 00:00:00 2001 From: sexygoat <1538832180@qq.com> Date: Fri, 10 Apr 2026 09:50:00 +0800 Subject: [PATCH] fix(operator): fix operator edit issue --- src/types/api/carrier.ts | 6 + .../components/AssetSearchCard.vue | 189 + .../components/BasicInfoCard.vue | 604 ++++ .../components/CurrentPackageCard.vue | 226 ++ .../components/PackageListCard.vue | 220 ++ .../components/WalletOverviewCard.vue | 140 + .../components/WalletTransactionCard.vue | 326 ++ .../components/dialogs/DailyRecordsDialog.vue | 205 ++ .../components/dialogs/OrderHistoryDialog.vue | 232 ++ .../dialogs/PackageRechargeDialog.vue | 237 ++ .../components/dialogs/SpeedLimitDialog.vue | 94 + .../components/dialogs/SwitchCardDialog.vue | 165 + .../components/dialogs/WiFiConfigDialog.vue | 117 + .../components/dialogs/index.ts | 6 + .../composables/useAssetFormatters.ts | 249 ++ .../composables/useAssetInfo.ts | 352 ++ .../composables/useAssetOperations.ts | 319 ++ .../asset-information/index.vue | 3026 ++--------------- .../asset-information/types.ts | 141 + src/views/system/carrier-management/index.vue | 10 +- 20 files changed, 4082 insertions(+), 2782 deletions(-) create mode 100644 src/views/asset-management/asset-information/components/AssetSearchCard.vue create mode 100644 src/views/asset-management/asset-information/components/BasicInfoCard.vue create mode 100644 src/views/asset-management/asset-information/components/CurrentPackageCard.vue create mode 100644 src/views/asset-management/asset-information/components/PackageListCard.vue create mode 100644 src/views/asset-management/asset-information/components/WalletOverviewCard.vue create mode 100644 src/views/asset-management/asset-information/components/WalletTransactionCard.vue create mode 100644 src/views/asset-management/asset-information/components/dialogs/DailyRecordsDialog.vue create mode 100644 src/views/asset-management/asset-information/components/dialogs/OrderHistoryDialog.vue create mode 100644 src/views/asset-management/asset-information/components/dialogs/PackageRechargeDialog.vue create mode 100644 src/views/asset-management/asset-information/components/dialogs/SpeedLimitDialog.vue create mode 100644 src/views/asset-management/asset-information/components/dialogs/SwitchCardDialog.vue create mode 100644 src/views/asset-management/asset-information/components/dialogs/WiFiConfigDialog.vue create mode 100644 src/views/asset-management/asset-information/components/dialogs/index.ts create mode 100644 src/views/asset-management/asset-information/composables/useAssetFormatters.ts create mode 100644 src/views/asset-management/asset-information/composables/useAssetInfo.ts create mode 100644 src/views/asset-management/asset-information/composables/useAssetOperations.ts create mode 100644 src/views/asset-management/asset-information/types.ts diff --git a/src/types/api/carrier.ts b/src/types/api/carrier.ts index 94f4323..20e7ca4 100644 --- a/src/types/api/carrier.ts +++ b/src/types/api/carrier.ts @@ -20,6 +20,8 @@ export interface Carrier { carrier_type: CarrierType description?: string data_reset_day?: number | null // 上游流量重置日(1-28) + realname_link_type?: string // 实名链接类型 + realname_link_template?: string // 实名链接模板 status: number created_at: string updated_at: string @@ -39,6 +41,8 @@ export interface CreateCarrierParams { carrier_type: CarrierType description?: string data_reset_day?: number | null // 上游流量重置日(1-28) + realname_link_type?: string // 实名链接类型 + realname_link_template?: string // 实名链接模板 } // 更新运营商请求参数 @@ -46,6 +50,8 @@ export interface UpdateCarrierParams { carrier_name?: string description?: string data_reset_day?: number | null // 上游流量重置日(1-28) + realname_link_type?: string // 实名链接类型 + realname_link_template?: string // 实名链接模板 } // 更新运营商状态参数 diff --git a/src/views/asset-management/asset-information/components/AssetSearchCard.vue b/src/views/asset-management/asset-information/components/AssetSearchCard.vue new file mode 100644 index 0000000..07893ad --- /dev/null +++ b/src/views/asset-management/asset-information/components/AssetSearchCard.vue @@ -0,0 +1,189 @@ + + + + + diff --git a/src/views/asset-management/asset-information/components/BasicInfoCard.vue b/src/views/asset-management/asset-information/components/BasicInfoCard.vue new file mode 100644 index 0000000..9a4674b --- /dev/null +++ b/src/views/asset-management/asset-information/components/BasicInfoCard.vue @@ -0,0 +1,604 @@ + + + + + diff --git a/src/views/asset-management/asset-information/components/CurrentPackageCard.vue b/src/views/asset-management/asset-information/components/CurrentPackageCard.vue new file mode 100644 index 0000000..317ae9c --- /dev/null +++ b/src/views/asset-management/asset-information/components/CurrentPackageCard.vue @@ -0,0 +1,226 @@ + + + + + diff --git a/src/views/asset-management/asset-information/components/PackageListCard.vue b/src/views/asset-management/asset-information/components/PackageListCard.vue new file mode 100644 index 0000000..79838e8 --- /dev/null +++ b/src/views/asset-management/asset-information/components/PackageListCard.vue @@ -0,0 +1,220 @@ + + + + + diff --git a/src/views/asset-management/asset-information/components/WalletOverviewCard.vue b/src/views/asset-management/asset-information/components/WalletOverviewCard.vue new file mode 100644 index 0000000..a67bca0 --- /dev/null +++ b/src/views/asset-management/asset-information/components/WalletOverviewCard.vue @@ -0,0 +1,140 @@ + + + + + diff --git a/src/views/asset-management/asset-information/components/WalletTransactionCard.vue b/src/views/asset-management/asset-information/components/WalletTransactionCard.vue new file mode 100644 index 0000000..2dc8126 --- /dev/null +++ b/src/views/asset-management/asset-information/components/WalletTransactionCard.vue @@ -0,0 +1,326 @@ + + + + + diff --git a/src/views/asset-management/asset-information/components/dialogs/DailyRecordsDialog.vue b/src/views/asset-management/asset-information/components/dialogs/DailyRecordsDialog.vue new file mode 100644 index 0000000..ecf0276 --- /dev/null +++ b/src/views/asset-management/asset-information/components/dialogs/DailyRecordsDialog.vue @@ -0,0 +1,205 @@ + + + + + diff --git a/src/views/asset-management/asset-information/components/dialogs/OrderHistoryDialog.vue b/src/views/asset-management/asset-information/components/dialogs/OrderHistoryDialog.vue new file mode 100644 index 0000000..65653bd --- /dev/null +++ b/src/views/asset-management/asset-information/components/dialogs/OrderHistoryDialog.vue @@ -0,0 +1,232 @@ + + + + + diff --git a/src/views/asset-management/asset-information/components/dialogs/PackageRechargeDialog.vue b/src/views/asset-management/asset-information/components/dialogs/PackageRechargeDialog.vue new file mode 100644 index 0000000..ed3c99e --- /dev/null +++ b/src/views/asset-management/asset-information/components/dialogs/PackageRechargeDialog.vue @@ -0,0 +1,237 @@ + + + + + diff --git a/src/views/asset-management/asset-information/components/dialogs/SpeedLimitDialog.vue b/src/views/asset-management/asset-information/components/dialogs/SpeedLimitDialog.vue new file mode 100644 index 0000000..140e708 --- /dev/null +++ b/src/views/asset-management/asset-information/components/dialogs/SpeedLimitDialog.vue @@ -0,0 +1,94 @@ + + + + + diff --git a/src/views/asset-management/asset-information/components/dialogs/SwitchCardDialog.vue b/src/views/asset-management/asset-information/components/dialogs/SwitchCardDialog.vue new file mode 100644 index 0000000..605cf1b --- /dev/null +++ b/src/views/asset-management/asset-information/components/dialogs/SwitchCardDialog.vue @@ -0,0 +1,165 @@ + + + + + diff --git a/src/views/asset-management/asset-information/components/dialogs/WiFiConfigDialog.vue b/src/views/asset-management/asset-information/components/dialogs/WiFiConfigDialog.vue new file mode 100644 index 0000000..b7be13f --- /dev/null +++ b/src/views/asset-management/asset-information/components/dialogs/WiFiConfigDialog.vue @@ -0,0 +1,117 @@ + + + + + diff --git a/src/views/asset-management/asset-information/components/dialogs/index.ts b/src/views/asset-management/asset-information/components/dialogs/index.ts new file mode 100644 index 0000000..1bd3cef --- /dev/null +++ b/src/views/asset-management/asset-information/components/dialogs/index.ts @@ -0,0 +1,6 @@ +export { default as SpeedLimitDialog } from './SpeedLimitDialog.vue' +export { default as SwitchCardDialog } from './SwitchCardDialog.vue' +export { default as WiFiConfigDialog } from './WiFiConfigDialog.vue' +export { default as PackageRechargeDialog } from './PackageRechargeDialog.vue' +export { default as DailyRecordsDialog } from './DailyRecordsDialog.vue' +export { default as OrderHistoryDialog } from './OrderHistoryDialog.vue' diff --git a/src/views/asset-management/asset-information/composables/useAssetFormatters.ts b/src/views/asset-management/asset-information/composables/useAssetFormatters.ts new file mode 100644 index 0000000..3d82d71 --- /dev/null +++ b/src/views/asset-management/asset-information/composables/useAssetFormatters.ts @@ -0,0 +1,249 @@ +/** + * 资产信息格式化工具函数 + */ + +import type { Ref } from 'vue' + +export function useAssetFormatters(deviceRealtime?: Ref) { + // 格式化数据大小(MB转GB/MB) + const formatDataSize = (mb: number) => { + if (mb >= 1024) { + return `${(mb / 1024).toFixed(2)}GB` + } + return `${mb.toFixed(2)}MB` + } + + // 格式化金额(分转元) + const formatAmount = (amount: number | undefined) => { + if (amount === undefined || amount === null) return '¥0.00' + const yuan = amount / 100 + return `¥${yuan.toFixed(2)}` + } + + // 格式化时长(秒转为时分秒) + const formatDuration = (seconds?: string) => { + if (!seconds) return '-' + const sec = Number(seconds) + if (isNaN(sec)) return '-' + + const hours = Math.floor(sec / 3600) + const minutes = Math.floor((sec % 3600) / 60) + const secs = sec % 60 + + if (hours > 0) { + return `${hours}小时${minutes}分${secs}秒` + } else if (minutes > 0) { + return `${minutes}分${secs}秒` + } else { + return `${secs}秒` + } + } + + // 格式化ICCID为4位一组,用横杠分隔 + const formatIccidWithDashes = (iccid: string) => { + if (!iccid) return '' + return iccid.replace(/(\d{4})(?=\d)/g, '$1-') + } + + // 获取实名状态名称 + const getRealNameStatusName = (status?: number) => { + if (status === undefined || status === null) return '未知' + const statusMap: Record = { + 0: '未实名', + 1: '已实名' + } + return statusMap[status] || '未知' + } + + // 获取实名状态标签类型 + const getRealNameStatusType = (status?: number): 'info' | 'success' | 'warning' | 'danger' => { + if (status === undefined || status === null) return 'info' + const map: Record = { + 0: 'info', + 1: 'success' + } + return map[status] || 'info' + } + + // 获取资产状态名称 + const getAssetStatusName = (status?: number) => { + if (status === undefined || status === null) return '未知' + const map: Record = { + 1: '在库', + 2: '已分销', + 3: '已激活', + 4: '已停用' + } + return map[status] || '未知' + } + + // 获取资产状态标签类型 + const getAssetStatusType = ( + status?: number + ): 'primary' | 'success' | 'warning' | 'info' | 'danger' => { + if (status === undefined || status === null) return 'info' + const map: Record = { + 1: 'info', // 在库 + 2: 'success', // 已分销 + 3: 'success', // 已激活 + 4: 'danger' // 已停用 + } + return map[status] || 'info' + } + + // 获取在线状态名称 + const getOnlineStatusName = (status?: number) => { + const map: Record = { + 0: '未知', + 1: '在线', + 2: '离线' + } + return map[status ?? 0] || '未知' + } + + // 获取在线状态标签类型 + const getOnlineStatusType = ( + status?: number + ): 'primary' | 'success' | 'warning' | 'info' | 'danger' => { + const map: Record = { + 0: 'info', // 未知 + 1: 'success', // 在线 + 2: 'danger' // 离线 + } + return map[status ?? 0] || 'info' + } + + // 获取钱包状态标签类型 + const getWalletStatusType = (status?: number) => { + const map: Record = { + 1: 'success', // 正常 + 2: 'warning', // 冻结 + 3: 'danger' // 关闭 + } + return map[status || 1] || 'info' + } + + // 获取套餐状态标签类型 + const getPackageStatusTagType = ( + status?: number + ): 'primary' | 'success' | 'warning' | 'info' | 'danger' => { + if (status === undefined || status === null) return 'info' + const map: Record = { + 0: 'info', // 待生效 + 1: 'success', // 生效中 + 2: 'warning', // 已用完 + 3: 'danger', // 已过期 + 4: 'info' // 已失效 + } + return map[status] || 'info' + } + + // 计算信号强度等级(基于RSSI、RSRP、RSRQ) + const calculateSignalStrength = () => { + if (!deviceRealtime?.value) return '-' + + const rssi = deviceRealtime.value.rssi ? Number(deviceRealtime.value.rssi) : null + const rsrp = deviceRealtime.value.rsrp ? Number(deviceRealtime.value.rsrp) : null + + // 优先使用RSRP判断(更准确) + // RSRP范围通常在-140到-44 dBm之间,0或正值表示无效数据 + if (rsrp !== null && !isNaN(rsrp) && rsrp < 0) { + if (rsrp >= -80) return '优秀' + if (rsrp >= -90) return '良好' + if (rsrp >= -100) return '一般' + if (rsrp >= -110) return '较差' + return '极差' + } + + // 其次使用RSSI + // RSSI范围通常在-113到-51 dBm之间,0或正值表示无效数据 + if (rssi !== null && !isNaN(rssi) && rssi < 0) { + if (rssi >= -70) return '优秀' + if (rssi >= -85) return '良好' + if (rssi >= -100) return '一般' + if (rssi >= -110) return '较差' + return '极差' + } + + return '-' + } + + // 获取信号强度标签类型 + const getSignalStrengthType = (): 'primary' | 'success' | 'warning' | 'info' | 'danger' => { + const strength = calculateSignalStrength() + const map: Record = { + 优秀: 'success', + 良好: 'success', + 一般: 'warning', + 较差: 'danger', + 极差: 'danger' + } + return map[strength] || 'info' + } + + // 获取切卡模式名称 + const getSwitchModeName = (mode?: string) => { + const map: Record = { + '0': '自动', + '1': '手动' + } + return map[mode || ''] || '-' + } + + // 获取交易类型标签颜色 + const getTransactionTypeTag = (type: string | undefined) => { + const map: Record = { + recharge: 'success', // 充值 + deduct: 'danger', // 扣款 + refund: 'warning' // 退款 + } + return map[type || ''] || 'info' + } + + // 获取支付状态标签类型 + const getPaymentStatusType = (status: number): 'success' | 'warning' | 'danger' | 'info' => { + const statusMap: Record = { + 1: 'warning', // 待支付 + 2: 'success', // 已支付 + 3: 'danger', // 已取消 + 4: 'info' // 已关闭 + } + return statusMap[status] || 'info' + } + + // 计算使用进度百分比 + const getUsageProgress = (cumulative: number, total: number): number => { + if (!total || total <= 0) return 0 + const percentage = (cumulative / total) * 100 + return Math.min(Math.round(percentage * 100) / 100, 100) + } + + // 根据百分比获取进度条颜色 + const getProgressColorByPercentage = (percentage: number): string => { + if (percentage < 50) return '#67c23a' + if (percentage < 80) return '#e6a23c' + return '#f56c6c' + } + + return { + formatDataSize, + formatAmount, + formatDuration, + formatIccidWithDashes, + getRealNameStatusName, + getRealNameStatusType, + getAssetStatusName, + getAssetStatusType, + getOnlineStatusName, + getOnlineStatusType, + getWalletStatusType, + getPackageStatusTagType, + calculateSignalStrength, + getSignalStrengthType, + getSwitchModeName, + getTransactionTypeTag, + getPaymentStatusType, + getUsageProgress, + getProgressColorByPercentage + } +} diff --git a/src/views/asset-management/asset-information/composables/useAssetInfo.ts b/src/views/asset-management/asset-information/composables/useAssetInfo.ts new file mode 100644 index 0000000..da9237b --- /dev/null +++ b/src/views/asset-management/asset-information/composables/useAssetInfo.ts @@ -0,0 +1,352 @@ +/** + * 资产信息数据管理 Composable + * 负责资产详情数据的加载和状态管理 + */ + +import { ref } from 'vue' +import { ElMessage } from 'element-plus' +import { AssetService } from '@/api/modules' +import type { AssetWalletResponse } from '@/types/api' + +/** + * 格式化数据大小(MB -> GB/MB) + */ +const formatDataSize = (mb: number) => { + if (mb >= 1024) { + return `${(mb / 1024).toFixed(2)}GB` + } + return `${mb.toFixed(2)}MB` +} + +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) + + /** + * 获取资产详情 + * @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 + + // 映射API数据到页面显示格式 + cardInfo.value = { + // 基本信息 + asset_type: data.asset_type, // card 或 device + asset_id: data.asset_id, + 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, + 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, + 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(data.package_total_mb || 0), + usedFlow: formatDataSize(data.package_used_mb || 0), + remainFlow: formatDataSize(data.package_remain_mb || 0), + usedFlowPercentage: + data.package_total_mb > 0 + ? `${((data.package_used_mb / data.package_total_mb) * 100).toFixed(2)}%` + : '0.00%', + packageList: [] // 套餐列表需要单独调用API获取 + } + + // 获取资产标识符并加载其他数据 + const assetIdentifier = data.asset_type === 'card' ? data.iccid : data.virtual_no + if (assetIdentifier) { + // 并行调用多个接口: 当前生效套餐、套餐列表、实时状态、钱包概况 + Promise.all([ + loadCurrentPackage(assetIdentifier), + loadPackageList(assetIdentifier), + loadRealtimeStatus(assetIdentifier, data.asset_type), + loadAssetWallet(assetIdentifier) + ]) + } + } else { + ElMessage.error(response.msg || '查询失败') + cardInfo.value = null + } + } 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 || '获取卡片信息失败') + } + cardInfo.value = null + } finally { + loading.value = false + } + } + + /** + * 加载套餐列表 + * @param identifier - 资产标识符 + */ + const loadPackageList = async (identifier: string) => { + try { + const response = await AssetService.getAssetPackages(identifier, { + page: 1, + page_size: 50 + }) + if (response.code === 0 && response.data) { + // 使用分页响应中的 items 数组 + cardInfo.value.packageList = response.data.items || [] + } + } catch (error) { + console.error('获取套餐列表失败:', error) + } + } + + /** + * 加载当前生效套餐 + * @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 + // 保存完整的当前套餐数据 + cardInfo.value.currentPackageDetail = { + package_usage_id: pkg.package_usage_id, + package_id: pkg.package_id, + package_name: pkg.package_name, + package_type: pkg.package_type, + status: pkg.status, + status_name: pkg.status_name, + data_limit_mb: pkg.data_limit_mb, + data_usage_mb: pkg.data_usage_mb, + virtual_limit_mb: pkg.virtual_limit_mb, + virtual_used_mb: pkg.virtual_used_mb, + virtual_remain_mb: pkg.virtual_remain_mb, + virtual_ratio: pkg.virtual_ratio, + activated_at: pkg.activated_at, + expires_at: pkg.expires_at, + created_at: pkg.created_at, + master_usage_id: pkg.master_usage_id, + priority: pkg.priority, + usage_type: pkg.usage_type + } + // 同时更新流量显示信息 + cardInfo.value.current_package = pkg.package_name + cardInfo.value.packageTotalFlow = formatDataSize(pkg.virtual_limit_mb || 0) + cardInfo.value.usedFlow = formatDataSize(pkg.virtual_used_mb || 0) + cardInfo.value.remainFlow = formatDataSize(pkg.virtual_remain_mb || 0) + cardInfo.value.usedFlowPercentage = + (pkg.virtual_limit_mb || 0) > 0 + ? `${(((pkg.virtual_used_mb || 0) / (pkg.virtual_limit_mb || 1)) * 100).toFixed(2)}%` + : '0.00%' + } else { + // code 为 0 但 data 为空,表示暂无当前生效套餐(正常情况) + currentPackageErrorMsg.value = '暂无当前生效套餐' + } + } else { + // 接口返回 code 不为 0,保存错误信息 + currentPackageErrorMsg.value = response.msg || '获取当前套餐失败' + } + } catch (error: any) { + // 404表示无当前生效套餐,这是正常情况 + 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 { + 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 (data.last_sync_time !== undefined) { + cardInfo.value.last_sync_time = data.last_sync_time + } + if (data.current_month_usage_mb !== undefined) { + cardInfo.value.current_month_usage_mb = data.current_month_usage_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) { + cardInfo.value.cards = data.cards + } + 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 + console.log('设备实时信息:', deviceRealtime.value) + } + } + } + } catch (error) { + console.error('获取实时状态失败:', error) + } + } + + /** + * 加载钱包概况 + * @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) + */ + const refreshAsset = async (identifier: string, assetType: string) => { + try { + await AssetService.refreshAsset(identifier) + ElMessage.success('刷新成功') + + // 刷新成功后重新加载实时状态数据 + if (identifier) { + await loadRealtimeStatus(identifier, assetType) + } + } catch (error: any) { + // 检查429错误(冷却期) + if (error?.response?.status === 429) { + ElMessage.warning('刷新过于频繁,请稍后再试') + } else { + ElMessage.error(error?.message || '刷新失败') + } + throw error + } + } + + return { + // State + loading, + cardInfo, + deviceRealtime, + currentPackage, + currentPackageLoading, + currentPackageErrorMsg, + packageList, + walletInfo, + walletLoading, + + // Methods + fetchAssetDetail, + loadPackageList, + loadCurrentPackage, + loadRealtimeStatus, + loadAssetWallet, + refreshAsset + } +} diff --git a/src/views/asset-management/asset-information/composables/useAssetOperations.ts b/src/views/asset-management/asset-information/composables/useAssetOperations.ts new file mode 100644 index 0000000..30c5a8d --- /dev/null +++ b/src/views/asset-management/asset-information/composables/useAssetOperations.ts @@ -0,0 +1,319 @@ +import { ref } from 'vue' +import type { Ref } from 'vue' +import { ElMessage, ElMessageBox } from 'element-plus' +import { AssetService, DeviceService } from '@/api/modules' + +/** + * Asset operations composable + * Manages asset operation methods (enable/disable cards, device operations, etc.) + */ +export function useAssetOperations(cardInfo: Ref, refreshAssetFn?: () => Promise) { + // Loading states + const enableCardLoading = ref(false) + const disableCardLoading = ref(false) + const manualDeactivateCardLoading = ref(false) + const rebootDeviceLoading = ref(false) + const resetDeviceLoading = ref(false) + const speedLimitLoading = ref(false) + const switchCardLoading = ref(false) + const setWiFiLoading = ref(false) + const pollingLoading = ref(false) + + /** + * Enable card + */ + const enableCard = async () => { + try { + await ElMessageBox.confirm('确认要启用此卡吗?', '提示', { + type: 'warning' + }) + + enableCardLoading.value = true + const res = await AssetService.startAsset(cardInfo.value.iccid) + if (res.code === 0) { + ElMessage.success('启用成功') + if (refreshAssetFn) { + await refreshAssetFn() + } + } + } catch (error: any) { + if (error !== 'cancel') { + console.error('启用失败:', error) + ElMessage.error(error?.message || '启用失败') + } + } finally { + enableCardLoading.value = false + } + } + + /** + * Disable card with confirmation + */ + const disableCard = async () => { + try { + await ElMessageBox.confirm('确认要停用此卡吗?', '提示', { + type: 'warning' + }) + + disableCardLoading.value = true + const res = await AssetService.stopAsset(cardInfo.value.iccid) + if (res.code === 0) { + ElMessage.success('停用成功') + if (refreshAssetFn) { + await refreshAssetFn() + } + } + } catch (error: any) { + if (error !== 'cancel') { + console.error('停用失败:', error) + ElMessage.error(error?.message || '停用失败') + } + } finally { + disableCardLoading.value = false + } + } + + /** + * Manually deactivate card with strong warning confirmation + */ + const manualDeactivateCard = async () => { + try { + await ElMessageBox.confirm( + '手动停用后卡片将永久停用,无法再次使用。此操作不可逆,请谨慎操作。确认要手动停用吗?', + '警告', + { + type: 'error', + confirmButtonText: '确认停用', + cancelButtonText: '取消' + } + ) + + manualDeactivateCardLoading.value = true + const identifier = + cardInfo.value.asset_type === 'card' ? cardInfo.value.iccid : cardInfo.value.virtual_no + const res = await AssetService.deactivateAsset(identifier) + if (res.code === 0) { + ElMessage.success('手动停用成功') + if (refreshAssetFn) { + await refreshAssetFn() + } + } + } catch (error: any) { + if (error !== 'cancel') { + console.error('手动停用失败:', error) + ElMessage.error(error?.message || '手动停用失败') + } + } finally { + manualDeactivateCardLoading.value = false + } + } + + /** + * Reboot device with confirmation + */ + const rebootDevice = async () => { + try { + await ElMessageBox.confirm('确认要重启设备吗?', '提示', { + type: 'warning' + }) + + rebootDeviceLoading.value = true + const res = await DeviceService.rebootDevice(cardInfo.value.imei) + if (res.code === 0) { + ElMessage.success(res.data?.message || '重启指令已发送') + if (refreshAssetFn) { + await refreshAssetFn() + } + } + } catch (error: any) { + if (error !== 'cancel') { + console.error('重启失败:', error) + ElMessage.error(error?.message || '重启失败') + } + } finally { + rebootDeviceLoading.value = false + } + } + + /** + * Factory reset device with strong warning confirmation + */ + const resetDevice = async () => { + try { + await ElMessageBox.confirm('恢复出厂设置将清除设备所有数据和配置,确认要继续吗?', '警告', { + type: 'error', + confirmButtonText: '确认恢复', + cancelButtonText: '取消' + }) + + resetDeviceLoading.value = true + const res = await DeviceService.resetDevice(cardInfo.value.imei) + if (res.code === 0) { + ElMessage.success(res.data?.message || '恢复出厂设置指令已发送') + if (refreshAssetFn) { + await refreshAssetFn() + } + } + } catch (error: any) { + if (error !== 'cancel') { + console.error('恢复出厂失败:', error) + ElMessage.error(error?.message || '恢复出厂失败') + } + } finally { + resetDeviceLoading.value = false + } + } + + /** + * Set device speed limit + */ + const setSpeedLimit = async (form: { download_speed: number; upload_speed: number }) => { + try { + speedLimitLoading.value = true + const res = await DeviceService.setSpeedLimit(cardInfo.value.imei, { + download_speed: form.download_speed, + upload_speed: form.upload_speed + }) + if (res.code === 0) { + ElMessage.success('限速设置成功') + if (refreshAssetFn) { + await refreshAssetFn() + } + return true + } else { + ElMessage.error(res.msg || '设置失败') + return false + } + } catch (error: any) { + console.error('设置限速失败:', error) + ElMessage.error(error?.message || '设置失败') + return false + } finally { + speedLimitLoading.value = false + } + } + + /** + * Switch to different card + */ + const switchCard = async (form: { target_iccid: string }) => { + try { + switchCardLoading.value = true + const res = await DeviceService.switchCard(cardInfo.value.imei, { + target_iccid: form.target_iccid + }) + if (res.code === 0) { + ElMessage.success('切换SIM卡指令已发送') + if (refreshAssetFn) { + await refreshAssetFn() + } + return true + } else { + ElMessage.error(res.msg || '切换失败') + return false + } + } catch (error: any) { + console.error('切换SIM卡失败:', error) + ElMessage.error(error?.message || '切换失败') + return false + } finally { + switchCardLoading.value = false + } + } + + /** + * Configure WiFi settings + */ + const setWiFi = async (form: { enabled: number; ssid: string; password: string }) => { + try { + setWiFiLoading.value = true + const res = await DeviceService.setWiFi(cardInfo.value.imei, { + enabled: form.enabled, + ssid: form.ssid, + password: form.password + }) + if (res.code === 0) { + ElMessage.success('WiFi设置成功') + if (refreshAssetFn) { + await refreshAssetFn() + } + return true + } else { + ElMessage.error(res.msg || '设置失败') + return false + } + } catch (error: any) { + console.error('设置WiFi失败:', error) + ElMessage.error(error?.message || '设置WiFi失败') + return false + } finally { + setWiFiLoading.value = false + } + } + + /** + * Update polling status + */ + const updatePollingStatus = async (enabled: boolean) => { + if (!cardInfo.value) { + ElMessage.warning('无法更新轮询状态:缺少资产信息') + return false + } + + try { + pollingLoading.value = true + const identifier = + cardInfo.value.asset_type === 'card' ? cardInfo.value.iccid : cardInfo.value.virtual_no + + const response = await AssetService.updatePollingStatus(identifier, { + enable_polling: enabled + }) + + if (response.code === 0) { + ElMessage.success(enabled ? '已开启自动轮询' : '已关闭自动轮询') + // Update local state + if (cardInfo.value) { + cardInfo.value.enable_polling = enabled + } + return true + } else { + ElMessage.error(response.msg || '更新轮询状态失败') + return false + } + } catch (error: any) { + console.error('更新轮询状态失败:', error) + ElMessage.error(error?.message || '更新轮询状态失败') + return false + } finally { + pollingLoading.value = false + } + } + + return { + // Loading states + enableCardLoading, + disableCardLoading, + manualDeactivateCardLoading, + rebootDeviceLoading, + resetDeviceLoading, + speedLimitLoading, + switchCardLoading, + setWiFiLoading, + pollingLoading, + + // IoT card operations + enableCard, + disableCard, + manualDeactivateCard, + + // Device operations + rebootDevice, + resetDevice, + setSpeedLimit, + switchCard, + setWiFi, + + // Polling + updatePollingStatus + } +} diff --git a/src/views/asset-management/asset-information/index.vue b/src/views/asset-management/asset-information/index.vue index 0b5c13d..7c792f2 100644 --- a/src/views/asset-management/asset-information/index.vue +++ b/src/views/asset-management/asset-information/index.vue @@ -1,2025 +1,329 @@