first
This commit is contained in:
623
src/pages/agent-system/asset-detail/index.vue
Normal file
623
src/pages/agent-system/asset-detail/index.vue
Normal file
@@ -0,0 +1,623 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { getAssetDetail, getAssetPackages, getCurrentPackage, getAssetHistory, stopCard, startCard, stopDevice, startDevice } from '@/api/assets'
|
||||
import type { AssetType, CardInfo, DeviceInfo, AssetPackage, HistoryRecord } from '@/api/assets'
|
||||
|
||||
// 资产类型
|
||||
const assetType = ref<AssetType>('card')
|
||||
|
||||
// 资产ID
|
||||
const assetId = ref('')
|
||||
|
||||
// 资产详情数据
|
||||
const assetDetail = ref<CardInfo | DeviceInfo | null>(null)
|
||||
|
||||
// 实时状态数据
|
||||
const realtimeStatus = ref<any>(null)
|
||||
|
||||
// 套餐列表
|
||||
const packageList = ref<AssetPackage[]>([])
|
||||
|
||||
// 历史记录列表
|
||||
const historyList = ref<HistoryRecord[]>([])
|
||||
|
||||
// 当前套餐
|
||||
const currentPackage = ref<AssetPackage | null>(null)
|
||||
|
||||
// 加载状态
|
||||
const loading = ref(false)
|
||||
const refreshing = ref(false)
|
||||
const operating = ref(false)
|
||||
|
||||
// 当前Tab
|
||||
const activeTab = ref<'info' | 'package' | 'history'>('info')
|
||||
|
||||
// Tab选项
|
||||
const tabs = [
|
||||
{ key: 'info', label: '基本信息', icon: 'i-mdi-information' },
|
||||
{ key: 'package', label: '套餐信息', icon: 'i-mdi-package-variant' },
|
||||
{ key: 'history', label: '操作历史', icon: 'i-mdi-history' },
|
||||
]
|
||||
|
||||
// 状态文本
|
||||
const statusText = computed(() => {
|
||||
if (!realtimeStatus.value)
|
||||
return '未知'
|
||||
return realtimeStatus.value.status === 'active' ? '正常' : '停机'
|
||||
})
|
||||
|
||||
// 状态颜色
|
||||
const statusColor = computed(() => {
|
||||
if (!realtimeStatus.value)
|
||||
return { bg: '#f5f5f5', text: '#999' }
|
||||
return realtimeStatus.value.status === 'active'
|
||||
? { bg: '#e8f5e9', text: '#2e7d32' }
|
||||
: { bg: '#ffebee', text: '#c62828' }
|
||||
})
|
||||
|
||||
// 页面加载
|
||||
onMounted(() => {
|
||||
// 从路由参数获取 assetType 和 assetId
|
||||
const pages = getCurrentPages()
|
||||
const currentPage = pages[pages.length - 1] as any
|
||||
assetType.value = currentPage.options.type as AssetType
|
||||
assetId.value = currentPage.options.id
|
||||
|
||||
loadAssetDetail()
|
||||
})
|
||||
|
||||
// 加载资产详情
|
||||
async function loadAssetDetail() {
|
||||
loading.value = true
|
||||
|
||||
try {
|
||||
// 调用资产详情接口
|
||||
const detailData = await getAssetDetail(assetType.value, assetId.value)
|
||||
|
||||
// 根据类型提取详情
|
||||
if (assetType.value === 'card' && detailData.card) {
|
||||
assetDetail.value = detailData.card
|
||||
realtimeStatus.value = {
|
||||
status: detailData.card.status,
|
||||
data_usage: detailData.card.data_usage,
|
||||
data_total: detailData.card.data_total,
|
||||
last_update: new Date().toLocaleString('zh-CN'),
|
||||
}
|
||||
}
|
||||
else if (assetType.value === 'device' && detailData.device) {
|
||||
assetDetail.value = detailData.device
|
||||
realtimeStatus.value = {
|
||||
status: detailData.device.status,
|
||||
card_count: detailData.device.card_count,
|
||||
last_online_time: detailData.device.last_online_time,
|
||||
last_update: new Date().toLocaleString('zh-CN'),
|
||||
}
|
||||
}
|
||||
|
||||
// 加载套餐列表
|
||||
await loadPackages()
|
||||
|
||||
// 加载历史记录
|
||||
if (activeTab.value === 'history') {
|
||||
await loadHistory()
|
||||
}
|
||||
}
|
||||
catch (error: any) {
|
||||
console.error('加载资产详情失败:', error)
|
||||
// 如果是前端逻辑错误(非 API 错误),需要手动显示提示
|
||||
if (error instanceof Error && error.message && !error.statusCode) {
|
||||
uni.$u.toast(error.message)
|
||||
}
|
||||
// API 请求错误已由拦截器统一处理
|
||||
}
|
||||
finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 加载套餐列表
|
||||
async function loadPackages() {
|
||||
try {
|
||||
// 1. 调用当前生效套餐接口
|
||||
try {
|
||||
const current = await getCurrentPackage(assetType.value, Number(assetId.value))
|
||||
currentPackage.value = current
|
||||
} catch (error) {
|
||||
console.warn('获取当前套餐失败:', error)
|
||||
currentPackage.value = null
|
||||
}
|
||||
|
||||
// 2. 调用套餐列表接口
|
||||
const packagesData = await getAssetPackages(assetType.value, Number(assetId.value), {
|
||||
page: 1,
|
||||
page_size: 50
|
||||
})
|
||||
packageList.value = packagesData.items || []
|
||||
|
||||
// 如果没有获取到当前套餐,从列表中查找生效中的套餐
|
||||
if (!currentPackage.value && packageList.value.length > 0) {
|
||||
currentPackage.value = packageList.value.find(pkg => pkg.status === 1) || null
|
||||
}
|
||||
}
|
||||
catch (error: any) {
|
||||
console.error('加载套餐列表失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 加载历史记录
|
||||
async function loadHistory() {
|
||||
try {
|
||||
const historyData = await getAssetHistory(assetType.value, assetId.value, {
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
})
|
||||
historyList.value = historyData.list
|
||||
}
|
||||
catch (error: any) {
|
||||
console.error('加载历史记录失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 刷新状态
|
||||
async function refreshStatus() {
|
||||
refreshing.value = true
|
||||
|
||||
try {
|
||||
// 重新加载数据
|
||||
await loadAssetDetail()
|
||||
|
||||
uni.showToast({
|
||||
title: '刷新成功',
|
||||
icon: 'success',
|
||||
})
|
||||
}
|
||||
catch (error: any) {
|
||||
// 如果是前端逻辑错误(非 API 错误),需要手动显示提示
|
||||
if (error instanceof Error && error.message && !error.statusCode) {
|
||||
uni.$u.toast(error.message)
|
||||
}
|
||||
// API 请求错误已由拦截器统一处理
|
||||
}
|
||||
finally {
|
||||
refreshing.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 切换Tab时加载对应数据
|
||||
async function handleTabChange(tab: 'info' | 'package' | 'history') {
|
||||
activeTab.value = tab
|
||||
|
||||
if (tab === 'history' && historyList.value.length === 0) {
|
||||
await loadHistory()
|
||||
}
|
||||
}
|
||||
|
||||
// 停机
|
||||
async function stopAsset() {
|
||||
uni.showModal({
|
||||
title: '确认停机',
|
||||
content: '停机后将无法使用网络服务,确定要停机吗?',
|
||||
success: async (res) => {
|
||||
if (res.confirm) {
|
||||
operating.value = true
|
||||
try {
|
||||
// 调用停机接口
|
||||
if (assetType.value === 'card') {
|
||||
await stopCard({ id: assetId.value })
|
||||
}
|
||||
else {
|
||||
await stopDevice({ id: assetId.value })
|
||||
}
|
||||
|
||||
uni.showToast({
|
||||
title: '停机成功',
|
||||
icon: 'success',
|
||||
})
|
||||
|
||||
await loadAssetDetail()
|
||||
}
|
||||
catch (error: any) {
|
||||
console.error('停机失败:', error)
|
||||
uni.showToast({
|
||||
title: error.msg || '停机失败',
|
||||
icon: 'none',
|
||||
})
|
||||
}
|
||||
finally {
|
||||
operating.value = false
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// 复机
|
||||
async function startAsset() {
|
||||
uni.showModal({
|
||||
title: '确认复机',
|
||||
content: '复机后将恢复网络服务,确定要复机吗?',
|
||||
success: async (res) => {
|
||||
if (res.confirm) {
|
||||
operating.value = true
|
||||
try {
|
||||
// 调用复机接口
|
||||
if (assetType.value === 'card') {
|
||||
await startCard({ id: assetId.value })
|
||||
}
|
||||
else {
|
||||
await startDevice({ id: assetId.value })
|
||||
}
|
||||
|
||||
uni.showToast({
|
||||
title: '复机成功',
|
||||
icon: 'success',
|
||||
})
|
||||
|
||||
await loadAssetDetail()
|
||||
}
|
||||
catch (error: any) {
|
||||
console.error('复机失败:', error)
|
||||
uni.showToast({
|
||||
title: error.msg || '复机失败',
|
||||
icon: 'none',
|
||||
})
|
||||
}
|
||||
finally {
|
||||
operating.value = false
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// 查看钱包 (仅代理端)
|
||||
function viewWallet() {
|
||||
// 跳转到钱包页面
|
||||
uni.navigateTo({
|
||||
url: `/pages/agent-system/asset-wallet/index?type=${assetType.value}&id=${assetId.value}`,
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="bg-[#fafafa] min-h-screen pb-safe">
|
||||
<!-- 加载中 -->
|
||||
<view v-if="loading" class="pt-20 center">
|
||||
<i class="i-mdi-loading animate-spin text-40px text-[#999]" />
|
||||
</view>
|
||||
|
||||
<!-- 内容区域 -->
|
||||
<view v-else-if="assetDetail" class="px-4 pt-4">
|
||||
<!-- 状态卡片 -->
|
||||
<view class="bg-white rounded-16px shadow-[0_2px_12px_rgba(0,0,0,0.06)] overflow-hidden mb-3">
|
||||
<!-- 顶部信息 -->
|
||||
<view class="px-4 pt-4 pb-3 border-b-1px border-[#f5f5f5]">
|
||||
<view class="flex items-center justify-between mb-3">
|
||||
<view class="flex items-center gap-2">
|
||||
<i
|
||||
:class="[
|
||||
'text-28px',
|
||||
assetType === 'card' ? 'i-mdi-sim' : 'i-mdi-devices',
|
||||
'text-[#212121]',
|
||||
]"
|
||||
/>
|
||||
<view>
|
||||
<text class="text-18px font-700 text-[#212121] block">
|
||||
{{ assetType === 'card' ? '物联网卡' : '物联网设备' }}
|
||||
</text>
|
||||
<text class="text-12px text-[#999]">
|
||||
ID: {{ assetDetail.id || '-' }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
:style="{
|
||||
backgroundColor: statusColor.bg,
|
||||
color: statusColor.text,
|
||||
}"
|
||||
class="px-3 py-1 rounded-8px text-13px font-600"
|
||||
>
|
||||
{{ statusText }}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<text class="text-13px text-[#999]">
|
||||
{{ assetDetail.operator || '-' }}
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<!-- 实时状态 -->
|
||||
<view class="px-4 py-4">
|
||||
<text class="text-14px font-600 text-[#212121] block mb-3">
|
||||
实时状态
|
||||
</text>
|
||||
|
||||
<view class="grid grid-cols-2 gap-3">
|
||||
<!-- 流量使用 -->
|
||||
<view class="bg-[#f5f5f5] rounded-12px p-3">
|
||||
<text class="text-12px text-[#999] block mb-1">
|
||||
流量使用
|
||||
</text>
|
||||
<text class="text-16px font-700 text-[#212121] block">
|
||||
{{ realtimeStatus?.data_usage || '-' }}
|
||||
</text>
|
||||
<text class="text-11px text-[#999]">
|
||||
/ {{ realtimeStatus?.data_total || '-' }}
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<!-- 语音使用 -->
|
||||
<view class="bg-[#f5f5f5] rounded-12px p-3">
|
||||
<text class="text-12px text-[#999] block mb-1">
|
||||
语音使用
|
||||
</text>
|
||||
<text class="text-16px font-700 text-[#212121] block">
|
||||
{{ realtimeStatus?.voice_usage || '-' }}
|
||||
</text>
|
||||
<text class="text-11px text-[#999]">
|
||||
/ {{ realtimeStatus?.voice_total || '-' }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<text class="text-11px text-[#999] block mt-3">
|
||||
最后更新: {{ realtimeStatus?.last_update || '-' }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- Tab切换 -->
|
||||
<view class="flex items-center gap-2 mb-3">
|
||||
<view
|
||||
v-for="tab in tabs"
|
||||
:key="tab.key"
|
||||
:class="[
|
||||
'flex-1 py-3 rounded-12px center flex-col gap-1',
|
||||
'transition-all duration-200',
|
||||
activeTab === tab.key
|
||||
? 'bg-[#212121] shadow-[0_2px_12px_rgba(0,0,0,0.1)]'
|
||||
: 'bg-white shadow-[0_2px_12px_rgba(0,0,0,0.06)]',
|
||||
]"
|
||||
@click="handleTabChange(tab.key)"
|
||||
>
|
||||
<i
|
||||
:class="[
|
||||
tab.icon,
|
||||
'text-20px',
|
||||
activeTab === tab.key ? 'text-white' : 'text-[#666]',
|
||||
]"
|
||||
/>
|
||||
<text
|
||||
:class="[
|
||||
'text-12px font-600',
|
||||
activeTab === tab.key ? 'text-white' : 'text-[#666]',
|
||||
]"
|
||||
>
|
||||
{{ tab.label }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 基本信息 -->
|
||||
<view v-if="activeTab === 'info'" class="bg-white rounded-16px shadow-[0_2px_12px_rgba(0,0,0,0.06)] p-4 space-y-3">
|
||||
<view class="flex items-start justify-between">
|
||||
<text class="text-13px text-[#999] w-90px">ICCID</text>
|
||||
<text class="flex-1 text-14px text-[#212121] text-right break-all">
|
||||
{{ assetDetail.iccid || '-' }}
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="flex items-start justify-between">
|
||||
<text class="text-13px text-[#999] w-90px">虚拟号</text>
|
||||
<text class="flex-1 text-14px text-[#212121] text-right">
|
||||
{{ assetDetail.virtual_number || '-' }}
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="flex items-start justify-between">
|
||||
<text class="text-13px text-[#999] w-90px">IMEI</text>
|
||||
<text class="flex-1 text-14px text-[#212121] text-right break-all">
|
||||
{{ assetDetail.imei || '-' }}
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="flex items-start justify-between">
|
||||
<text class="text-13px text-[#999] w-90px">运营商</text>
|
||||
<text class="flex-1 text-14px text-[#212121] text-right">
|
||||
{{ assetDetail.operator || '-' }}
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="flex items-start justify-between">
|
||||
<text class="text-13px text-[#999] w-90px">创建时间</text>
|
||||
<text class="flex-1 text-14px text-[#212121] text-right">
|
||||
{{ assetDetail.create_time || '-' }}
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="flex items-start justify-between">
|
||||
<text class="text-13px text-[#999] w-90px">激活时间</text>
|
||||
<text class="flex-1 text-14px text-[#212121] text-right">
|
||||
{{ assetDetail.activate_time || '-' }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 套餐信息 -->
|
||||
<view v-if="activeTab === 'package'" class="space-y-3">
|
||||
<!-- 当前套餐 -->
|
||||
<view v-if="currentPackage" class="bg-white rounded-16px shadow-[0_2px_12px_rgba(0,0,0,0.06)] p-4">
|
||||
<view class="flex items-center justify-between mb-3">
|
||||
<text class="text-14px font-600 text-[#212121]">
|
||||
当前套餐
|
||||
</text>
|
||||
<view class="px-3 py-1 rounded-8px bg-[#e8f5e9] text-12px font-600 text-[#2e7d32]">
|
||||
{{ currentPackage.status_name || '-' }}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<text class="text-16px font-700 text-[#212121] block mb-2">
|
||||
{{ currentPackage.package_name || '-' }}
|
||||
</text>
|
||||
<text class="text-14px text-[#ff6700] font-600 block mb-3">
|
||||
已用: {{ currentPackage.virtual_used_mb || 0 }}MB / 总量: {{ currentPackage.virtual_limit_mb || 0 }}MB
|
||||
</text>
|
||||
|
||||
<view class="flex items-center justify-between text-12px text-[#999]">
|
||||
<text v-if="currentPackage.activated_at && currentPackage.expires_at">
|
||||
{{ currentPackage.activated_at }} ~ {{ currentPackage.expires_at }}
|
||||
</text>
|
||||
<text v-else>-</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 所有套餐列表 -->
|
||||
<view v-if="packageList.length > 0" class="bg-white rounded-16px shadow-[0_2px_12px_rgba(0,0,0,0.06)] p-4">
|
||||
<text class="text-14px font-600 text-[#212121] block mb-3">
|
||||
套餐列表
|
||||
</text>
|
||||
|
||||
<view
|
||||
v-for="pkg in packageList"
|
||||
:key="pkg.package_usage_id"
|
||||
class="py-3 border-t-1px border-[#f5f5f5] first:border-t-0 first:pt-0"
|
||||
>
|
||||
<view class="flex items-center justify-between mb-2">
|
||||
<text class="text-15px font-600 text-[#212121]">
|
||||
{{ pkg.package_name || '-' }}
|
||||
</text>
|
||||
<view
|
||||
class="px-2 py-0.5 rounded text-11px"
|
||||
:style="{
|
||||
backgroundColor: pkg.status === 1 ? '#e8f5e9' : '#f5f5f5',
|
||||
color: pkg.status === 1 ? '#2e7d32' : '#999'
|
||||
}"
|
||||
>
|
||||
{{ pkg.status_name || '-' }}
|
||||
</view>
|
||||
</view>
|
||||
<view class="flex items-center justify-between mb-1">
|
||||
<text class="text-12px text-[#666]">
|
||||
已用: {{ pkg.virtual_used_mb || 0 }}MB / {{ pkg.virtual_limit_mb || 0 }}MB
|
||||
</text>
|
||||
<text class="text-11px text-[#999]">
|
||||
优先级: {{ pkg.priority || '-' }}
|
||||
</text>
|
||||
</view>
|
||||
<text class="text-11px text-[#999]">
|
||||
{{ pkg.activated_at || '-' }} ~ {{ pkg.expires_at || '-' }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 暂无套餐 -->
|
||||
<view v-else class="bg-white rounded-16px shadow-[0_2px_12px_rgba(0,0,0,0.06)] p-4 py-12 center flex-col">
|
||||
<i class="i-mdi-package-variant-closed text-60px text-[#ddd] mb-2" />
|
||||
<text class="text-13px text-[#999]">
|
||||
暂无套餐数据
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 操作历史 -->
|
||||
<view v-if="activeTab === 'history'" class="bg-white rounded-16px shadow-[0_2px_12px_rgba(0,0,0,0.06)] p-4">
|
||||
<text class="text-14px font-600 text-[#212121] block mb-3">
|
||||
操作记录
|
||||
</text>
|
||||
|
||||
<!-- 暂无数据 -->
|
||||
<view class="py-8 center flex-col">
|
||||
<i class="i-mdi-history text-60px text-[#ddd] mb-2" />
|
||||
<text class="text-13px text-[#999]">
|
||||
暂无操作记录
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 操作按钮区 -->
|
||||
<view class="mt-4 space-y-3">
|
||||
<!-- 停复机按钮 -->
|
||||
<view class="flex gap-3">
|
||||
<view
|
||||
v-if="realtimeStatus.status === 'active'"
|
||||
class="
|
||||
flex-1 py-3 rounded-12px center
|
||||
bg-[#ffebee]
|
||||
transition-all duration-200
|
||||
active:opacity-80
|
||||
"
|
||||
@click="stopAsset"
|
||||
>
|
||||
<i class="i-mdi-pause-circle text-18px text-[#c62828] mr-1" />
|
||||
<text class="text-15px font-600 text-[#c62828]">
|
||||
停机
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view
|
||||
v-else
|
||||
class="
|
||||
flex-1 py-3 rounded-12px center
|
||||
bg-[#e8f5e9]
|
||||
transition-all duration-200
|
||||
active:opacity-80
|
||||
"
|
||||
@click="startAsset"
|
||||
>
|
||||
<i class="i-mdi-play-circle text-18px text-[#2e7d32] mr-1" />
|
||||
<text class="text-15px font-600 text-[#2e7d32]">
|
||||
复机
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view
|
||||
class="
|
||||
flex-1 py-3 rounded-12px center
|
||||
bg-[#212121]
|
||||
transition-all duration-200
|
||||
active:bg-[#000]
|
||||
"
|
||||
@click="viewWallet"
|
||||
>
|
||||
<i class="i-mdi-wallet text-18px text-white mr-1" />
|
||||
<text class="text-15px font-600 text-white">
|
||||
查看钱包
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部留白 -->
|
||||
<view class="h-8" />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.center {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.space-y-3 > view:not(:last-child) {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.break-all {
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.animate-spin {
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user