first
This commit is contained in:
841
src/pages/agent-system/assets/index.vue
Normal file
841
src/pages/agent-system/assets/index.vue
Normal file
@@ -0,0 +1,841 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { getUserInfo } from '@/api/auth'
|
||||
import type { UserInfo } from '@/api/auth'
|
||||
import { getEnterpriseCards, getEnterpriseDevices } from '@/api/enterprise'
|
||||
import { getCarriers } from '@/api/carrier'
|
||||
import type { CarrierInfo } from '@/api/carrier'
|
||||
import { stopDeviceAsset, startDeviceAsset, stopCardByIccid, startCardByIccid } from '@/api/assets'
|
||||
import type { DeviceStopResponse } from '@/api/assets'
|
||||
import Skeleton from '@/components/Skeleton.vue'
|
||||
import SimplePicker from '@/components/SimplePicker.vue'
|
||||
import { formatTime, formatDataSize } from '@/utils/format'
|
||||
import { get } from '@/utils/request'
|
||||
|
||||
// 卡片信息接口
|
||||
interface CardInfo {
|
||||
id: number
|
||||
iccid: string
|
||||
imsi?: string
|
||||
msisdn?: string
|
||||
virtual_no?: string
|
||||
virtual_number?: string // 企业端字段
|
||||
status?: number
|
||||
status_name?: string
|
||||
activation_status?: number
|
||||
network_status?: number
|
||||
network_status_name?: string
|
||||
real_name_status?: number
|
||||
carrier_name?: string
|
||||
carrier_type?: string
|
||||
operator?: string // 企业端字段
|
||||
supplier?: string
|
||||
batch_no?: string
|
||||
series_name?: string
|
||||
shop_name?: string
|
||||
data_usage_mb?: number
|
||||
current_month_usage_mb?: number
|
||||
accumulated_recharge?: number
|
||||
created_at?: string
|
||||
updated_at?: string
|
||||
}
|
||||
|
||||
// 设备信息接口
|
||||
interface DeviceInfo {
|
||||
id?: number
|
||||
device_id?: number // 企业端字段
|
||||
device_name?: string
|
||||
device_model?: string
|
||||
device_type?: string
|
||||
manufacturer?: string
|
||||
imei?: string
|
||||
sn?: string
|
||||
virtual_no?: string
|
||||
status?: number
|
||||
status_name?: string
|
||||
online_status?: number
|
||||
bound_card_count?: number
|
||||
card_count?: number // 企业端字段
|
||||
max_sim_slots?: number
|
||||
switch_mode?: string
|
||||
series_name?: string
|
||||
shop_name?: string
|
||||
software_version?: string
|
||||
accumulated_recharge?: number
|
||||
activated_at?: string
|
||||
authorized_at?: string // 企业端字段
|
||||
last_online_time?: string
|
||||
created_at?: string
|
||||
updated_at?: string
|
||||
}
|
||||
|
||||
// 用户信息
|
||||
const userInfo = ref<UserInfo | null>(null)
|
||||
|
||||
// 获取单卡列表(代理端)
|
||||
function getStandaloneCards(params: any) {
|
||||
return get<{ items: CardInfo[], page: number, size: number, total: number }>('/api/admin/iot-cards/standalone', { params })
|
||||
}
|
||||
|
||||
// 获取设备列表(代理端)
|
||||
function getDevices(params: any) {
|
||||
return get<{ items: DeviceInfo[], page: number, size: number, total: number }>('/api/admin/devices', { params })
|
||||
}
|
||||
|
||||
// 企业ID (企业端使用)
|
||||
const enterpriseId = ref<number>(0)
|
||||
|
||||
// 资产类型
|
||||
type AssetType = 'card' | 'device'
|
||||
|
||||
// 统一的资产项
|
||||
interface AssetItem {
|
||||
id: string
|
||||
type: AssetType
|
||||
name: string
|
||||
status: string
|
||||
statusColor: string
|
||||
detail: string
|
||||
raw: CardInfo | DeviceInfo
|
||||
}
|
||||
|
||||
// 当前标签
|
||||
const activeTab = ref<'card' | 'device'>('card')
|
||||
// 资产列表
|
||||
const cardList = ref<CardInfo[]>([])
|
||||
const deviceList = ref<DeviceInfo[]>([])
|
||||
// 加载状态
|
||||
const loading = ref(false)
|
||||
// 分页
|
||||
const cardPage = ref(1)
|
||||
const devicePage = ref(1)
|
||||
const cardHasMore = ref(true)
|
||||
const deviceHasMore = ref(true)
|
||||
const pageSize = 20
|
||||
// 搜索和筛选
|
||||
const iccidKeyword = ref('')
|
||||
const virtualNoKeyword = ref('')
|
||||
const carrierFilter = ref<number | undefined>(undefined)
|
||||
const statusFilter = ref<number | undefined>(undefined)
|
||||
const showCarrierPicker = ref(false)
|
||||
const showStatusPicker = ref(false)
|
||||
// 运营商列表
|
||||
const carrierList = ref<CarrierInfo[]>([])
|
||||
// 运营商选择器选项
|
||||
const carrierOptions = computed(() => [
|
||||
{ label: '全部运营商', value: undefined },
|
||||
...carrierList.value.map(c => ({ label: c.carrier_name, value: c.id }))
|
||||
])
|
||||
// 状态选择器选项
|
||||
const statusOptions = [
|
||||
{ label: '全部状态', value: undefined },
|
||||
{ label: '在库', value: 1 },
|
||||
{ label: '已分销', value: 2 },
|
||||
{ label: '已激活', value: 3 },
|
||||
{ label: '已停用', value: 4 }
|
||||
]
|
||||
|
||||
// 合并后的资产列表
|
||||
const assetList = computed<AssetItem[]>(() => {
|
||||
const cards: AssetItem[] = cardList.value.map(card => ({
|
||||
id: String(card.id),
|
||||
type: 'card' as AssetType,
|
||||
name: card.iccid,
|
||||
// 兼容企业端和代理端的状态字段
|
||||
status: card.network_status_name || (card.network_status === 1 ? '开机' : card.network_status === 0 ? '停机' : card.status_name || '未知'),
|
||||
statusColor: (card.network_status === 1) ? '#52c41a' : (card.network_status === 0) ? '#999' : '#ff4d4f',
|
||||
// 兼容企业端和代理端的字段
|
||||
detail: `虚拟号: ${card.virtual_no || card.virtual_number || '-'} | 运营商: ${card.carrier_name || card.operator || '-'}`,
|
||||
raw: card,
|
||||
}))
|
||||
|
||||
const devices: AssetItem[] = deviceList.value.map(device => ({
|
||||
id: String(device.id || device.device_id),
|
||||
type: 'device' as AssetType,
|
||||
name: device.device_name || '-',
|
||||
// 兼容企业端和代理端的状态字段
|
||||
status: device.status_name || (device.authorized_at ? '已授权' : device.online_status === 1 ? '在线' : device.online_status === 2 ? '离线' : '未知'),
|
||||
statusColor: (device.authorized_at || device.online_status === 1) ? '#52c41a' : (device.online_status === 2) ? '#999' : '#ff4d4f',
|
||||
// 兼容企业端和代理端的字段
|
||||
detail: `虚拟号: ${device.virtual_no || '-'} | 型号: ${device.device_model || '-'} | 卡数: ${device.bound_card_count || device.card_count || 0}`,
|
||||
raw: device,
|
||||
}))
|
||||
|
||||
if (activeTab.value === 'card') return cards
|
||||
if (activeTab.value === 'device') return devices
|
||||
return cards
|
||||
})
|
||||
|
||||
|
||||
// 获取企业ID(企业端使用)
|
||||
async function getEnterpriseId() {
|
||||
if (enterpriseId.value) return enterpriseId.value
|
||||
|
||||
const cachedUserInfo = uni.getStorageSync('user_info')
|
||||
if (cachedUserInfo?.enterprise_id) {
|
||||
enterpriseId.value = cachedUserInfo.enterprise_id
|
||||
return enterpriseId.value
|
||||
}
|
||||
|
||||
const apiUserInfo = await getUserInfo()
|
||||
if (!apiUserInfo.enterprise_id) {
|
||||
throw new Error('未找到企业ID')
|
||||
}
|
||||
enterpriseId.value = apiUserInfo.enterprise_id
|
||||
return enterpriseId.value
|
||||
}
|
||||
|
||||
// 加载IoT卡列表(兼容企业端和代理端)
|
||||
async function loadCards(isRefresh = false) {
|
||||
if (loading.value || (!cardHasMore.value && !isRefresh)) return
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
// 如果是刷新,重置页码
|
||||
if (isRefresh) {
|
||||
cardPage.value = 1
|
||||
cardList.value = []
|
||||
cardHasMore.value = true
|
||||
}
|
||||
|
||||
const params: any = {
|
||||
page: cardPage.value,
|
||||
page_size: pageSize,
|
||||
}
|
||||
|
||||
// 添加筛选条件
|
||||
if (carrierFilter.value !== undefined) {
|
||||
params.carrier_id = carrierFilter.value
|
||||
}
|
||||
if (statusFilter.value !== undefined) {
|
||||
params.status = statusFilter.value
|
||||
}
|
||||
if (iccidKeyword.value) {
|
||||
params.iccid = iccidKeyword.value
|
||||
}
|
||||
if (virtualNoKeyword.value) {
|
||||
params.virtual_no = virtualNoKeyword.value
|
||||
}
|
||||
|
||||
let cardsData: any
|
||||
|
||||
// 根据用户类型调用不同接口
|
||||
if (userInfo.value?.user_type === 3) {
|
||||
// 代理端:调用代理接口
|
||||
cardsData = await getStandaloneCards(params)
|
||||
} else {
|
||||
// 企业端:调用企业接口
|
||||
const id = await getEnterpriseId()
|
||||
cardsData = await getEnterpriseCards(id, params)
|
||||
}
|
||||
|
||||
const newItems = cardsData?.items || []
|
||||
|
||||
if (cardPage.value === 1) {
|
||||
cardList.value = newItems
|
||||
} else {
|
||||
cardList.value.push(...newItems)
|
||||
}
|
||||
|
||||
// 判断是否还有更多数据
|
||||
cardHasMore.value = newItems.length >= pageSize
|
||||
cardPage.value++
|
||||
}
|
||||
catch (error: any) {
|
||||
console.error('加载IoT卡列表失败:', error)
|
||||
if (error instanceof Error && error.message && !error.statusCode) {
|
||||
uni.$u.toast(error.message)
|
||||
}
|
||||
}
|
||||
finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 加载设备列表(兼容企业端和代理端)
|
||||
async function loadDevices(isRefresh = false) {
|
||||
if (loading.value || (!deviceHasMore.value && !isRefresh)) return
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
// 如果是刷新,重置页码
|
||||
if (isRefresh) {
|
||||
devicePage.value = 1
|
||||
deviceList.value = []
|
||||
deviceHasMore.value = true
|
||||
}
|
||||
|
||||
const params: any = {
|
||||
page: devicePage.value,
|
||||
page_size: pageSize,
|
||||
}
|
||||
|
||||
if (virtualNoKeyword.value) {
|
||||
params.virtual_no = virtualNoKeyword.value
|
||||
}
|
||||
if (statusFilter.value !== undefined) {
|
||||
params.status = statusFilter.value
|
||||
}
|
||||
|
||||
let devicesData: any
|
||||
|
||||
// 根据用户类型调用不同接口
|
||||
if (userInfo.value?.user_type === 3) {
|
||||
// 代理端:调用代理接口
|
||||
devicesData = await getDevices(params)
|
||||
} else {
|
||||
// 企业端:调用企业接口
|
||||
const id = await getEnterpriseId()
|
||||
devicesData = await getEnterpriseDevices(id, params)
|
||||
}
|
||||
|
||||
const newItems = devicesData?.items || []
|
||||
|
||||
if (devicePage.value === 1) {
|
||||
deviceList.value = newItems
|
||||
} else {
|
||||
deviceList.value.push(...newItems)
|
||||
}
|
||||
|
||||
// 判断是否还有更多数据
|
||||
deviceHasMore.value = newItems.length >= pageSize
|
||||
devicePage.value++
|
||||
}
|
||||
catch (error: any) {
|
||||
console.error('加载设备列表失败:', error)
|
||||
if (error instanceof Error && error.message && !error.statusCode) {
|
||||
uni.$u.toast(error.message)
|
||||
}
|
||||
}
|
||||
finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 加载更多
|
||||
function loadMore() {
|
||||
if (activeTab.value === 'card') {
|
||||
loadCards()
|
||||
} else {
|
||||
loadDevices()
|
||||
}
|
||||
}
|
||||
|
||||
// 刷新列表
|
||||
function refreshList() {
|
||||
if (activeTab.value === 'card') {
|
||||
loadCards(true)
|
||||
} else {
|
||||
loadDevices(true)
|
||||
}
|
||||
}
|
||||
|
||||
// 加载运营商列表
|
||||
async function loadCarriers() {
|
||||
try {
|
||||
const response = await getCarriers({ page: 1, page_size: 100 })
|
||||
carrierList.value = response?.items || []
|
||||
} catch (error) {
|
||||
console.error('加载运营商列表失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 选择运营商
|
||||
function onCarrierConfirm(option: { label: string, value: any }) {
|
||||
carrierFilter.value = option.value
|
||||
refreshList()
|
||||
}
|
||||
|
||||
// 选择状态
|
||||
function onStatusConfirm(option: { label: string, value: any }) {
|
||||
statusFilter.value = option.value
|
||||
refreshList()
|
||||
}
|
||||
|
||||
// 获取当前选中的运营商名称
|
||||
const selectedCarrierName = computed(() => {
|
||||
if (carrierFilter.value === undefined) return '运营商'
|
||||
const option = carrierOptions.value.find(o => o.value === carrierFilter.value)
|
||||
return option?.label || '运营商'
|
||||
})
|
||||
|
||||
// 获取当前选中的状态名称
|
||||
const selectedStatusName = computed(() => {
|
||||
if (statusFilter.value === undefined) return '状态'
|
||||
const option = statusOptions.find(o => o.value === statusFilter.value)
|
||||
return option?.label || '状态'
|
||||
})
|
||||
|
||||
// 查看资产详情
|
||||
function viewAssetDetail(asset: AssetItem) {
|
||||
if (asset.type === 'card') {
|
||||
const card = asset.raw as CardInfo
|
||||
uni.navigateTo({
|
||||
url: `/pages/agent-system/asset-search/index?iccid=${card.iccid}`,
|
||||
})
|
||||
} else {
|
||||
const device = asset.raw as DeviceInfo
|
||||
uni.navigateTo({
|
||||
url: `/pages/agent-system/asset-search/index?virtual_no=${device.virtual_no}`,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 切换标签
|
||||
function changeTab(tab: 'card' | 'device') {
|
||||
if (activeTab.value === tab) return
|
||||
|
||||
activeTab.value = tab
|
||||
iccidKeyword.value = ''
|
||||
virtualNoKeyword.value = ''
|
||||
carrierFilter.value = undefined
|
||||
statusFilter.value = undefined
|
||||
|
||||
// 切换后加载对应数据
|
||||
if (tab === 'card' && cardList.value.length === 0) {
|
||||
loadCards(true)
|
||||
} else if (tab === 'device' && deviceList.value.length === 0) {
|
||||
loadDevices(true)
|
||||
}
|
||||
}
|
||||
|
||||
// 设备停机
|
||||
async function handleStopDevice(asset: AssetItem, event: Event) {
|
||||
event.stopPropagation()
|
||||
|
||||
const device = asset.raw as DeviceInfo
|
||||
const deviceId = device.id || device.device_id
|
||||
|
||||
if (!deviceId) {
|
||||
uni.$u.toast('设备ID不存在')
|
||||
return
|
||||
}
|
||||
|
||||
uni.showModal({
|
||||
title: '确认停机',
|
||||
content: `确定要停机设备"${device.device_name}"吗?这将停机设备下所有已实名卡。`,
|
||||
success: async (res) => {
|
||||
if (res.confirm) {
|
||||
uni.showLoading({ title: '停机中...' })
|
||||
try {
|
||||
const result = await stopDeviceAsset(Number(deviceId))
|
||||
uni.hideLoading()
|
||||
|
||||
// 显示停机结果
|
||||
const msg = `停机完成\n成功: ${result.success_count}张\n失败: ${result.fail_count}张\n跳过: ${result.skip_count}张`
|
||||
uni.showModal({
|
||||
title: '停机结果',
|
||||
content: msg,
|
||||
showCancel: false,
|
||||
success: () => {
|
||||
// 刷新列表
|
||||
refreshList()
|
||||
}
|
||||
})
|
||||
} catch (error: any) {
|
||||
uni.hideLoading()
|
||||
// 错误信息已经在拦截器中通过 toast 显示了,这里不需要重复显示
|
||||
console.error('设备停机失败:', error)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 设备复机
|
||||
async function handleStartDevice(asset: AssetItem, event: Event) {
|
||||
event.stopPropagation()
|
||||
|
||||
const device = asset.raw as DeviceInfo
|
||||
const deviceId = device.id || device.device_id
|
||||
|
||||
if (!deviceId) {
|
||||
uni.$u.toast('设备ID不存在')
|
||||
return
|
||||
}
|
||||
|
||||
uni.showModal({
|
||||
title: '确认复机',
|
||||
content: `确定要复机设备"${device.device_name}"吗?这将复机设备下所有已实名卡。`,
|
||||
success: async (res) => {
|
||||
if (res.confirm) {
|
||||
uni.showLoading({ title: '复机中...' })
|
||||
try {
|
||||
await startDeviceAsset(Number(deviceId))
|
||||
uni.hideLoading()
|
||||
uni.$u.toast('复机成功')
|
||||
// 刷新列表
|
||||
refreshList()
|
||||
} catch (error: any) {
|
||||
uni.hideLoading()
|
||||
// 错误信息已经在拦截器中通过 toast 显示了,这里不需要重复显示
|
||||
console.error('设备复机失败:', error)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 单卡停机
|
||||
async function handleStopCard(asset: AssetItem, event: Event) {
|
||||
event.stopPropagation()
|
||||
|
||||
const card = asset.raw as CardInfo
|
||||
|
||||
if (!card.iccid) {
|
||||
uni.$u.toast('ICCID不存在')
|
||||
return
|
||||
}
|
||||
|
||||
uni.showModal({
|
||||
title: '确认停机',
|
||||
content: `确定要停机卡"${card.iccid}"吗?`,
|
||||
success: async (res) => {
|
||||
if (res.confirm) {
|
||||
uni.showLoading({ title: '停机中...' })
|
||||
try {
|
||||
await stopCardByIccid(card.iccid)
|
||||
uni.hideLoading()
|
||||
uni.$u.toast('停机成功')
|
||||
// 刷新列表
|
||||
refreshList()
|
||||
} catch (error: any) {
|
||||
uni.hideLoading()
|
||||
// 错误信息已经在拦截器中通过 toast 显示了,这里不需要重复显示
|
||||
console.error('单卡停机失败:', error)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 单卡复机
|
||||
async function handleStartCard(asset: AssetItem, event: Event) {
|
||||
event.stopPropagation()
|
||||
|
||||
const card = asset.raw as CardInfo
|
||||
|
||||
if (!card.iccid) {
|
||||
uni.$u.toast('ICCID不存在')
|
||||
return
|
||||
}
|
||||
|
||||
uni.showModal({
|
||||
title: '确认复机',
|
||||
content: `确定要复机卡"${card.iccid}"吗?`,
|
||||
success: async (res) => {
|
||||
if (res.confirm) {
|
||||
uni.showLoading({ title: '复机中...' })
|
||||
try {
|
||||
await startCardByIccid(card.iccid)
|
||||
uni.hideLoading()
|
||||
uni.$u.toast('复机成功')
|
||||
// 刷新列表
|
||||
refreshList()
|
||||
} catch (error: any) {
|
||||
uni.hideLoading()
|
||||
// 错误信息已经在拦截器中通过 toast 显示了,这里不需要重复显示
|
||||
console.error('单卡复机失败:', error)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 滚动事件处理
|
||||
function onScroll(e: any) {
|
||||
const { scrollTop, scrollHeight, clientHeight } = e.target
|
||||
// 当滚动到底部附近时加载更多
|
||||
if (scrollHeight - scrollTop - clientHeight < 100) {
|
||||
loadMore()
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
// 1. 加载用户信息(判断是企业端还是代理端)
|
||||
try {
|
||||
const user = await getUserInfo()
|
||||
userInfo.value = user
|
||||
} catch (error) {
|
||||
console.error('获取用户信息失败:', error)
|
||||
}
|
||||
|
||||
// 2. 加载运营商列表
|
||||
loadCarriers()
|
||||
|
||||
// 3. 默认加载IoT卡列表
|
||||
loadCards()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="bg-page safe-area-bottom min-h-screen">
|
||||
<!-- 搜索和筛选 -->
|
||||
<view class="safe-area-top px-4 pt-4 pb-3">
|
||||
<!-- IoT卡搜索和筛选 -->
|
||||
<view v-if="activeTab === 'card'">
|
||||
<!-- ICCID搜索 -->
|
||||
<view class="bg-white rounded-full px-4 py-2 flex items-center mb-2">
|
||||
<i class="i-mdi-credit-card text-lg text-[#94a3b8] mr-2" />
|
||||
<input
|
||||
v-model="iccidKeyword"
|
||||
class="flex-1 text-sm text-[#1e293b]"
|
||||
placeholder="ICCID"
|
||||
/>
|
||||
<i
|
||||
v-if="iccidKeyword"
|
||||
class="i-mdi-close-circle text-lg text-[#94a3b8] ml-2"
|
||||
@click="iccidKeyword = ''"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 虚拟号搜索 -->
|
||||
<view class="bg-white rounded-full px-4 py-2 flex items-center mb-2">
|
||||
<i class="i-mdi-phone text-lg text-[#94a3b8] mr-2" />
|
||||
<input
|
||||
v-model="virtualNoKeyword"
|
||||
class="flex-1 text-sm text-[#1e293b]"
|
||||
placeholder="虚拟号"
|
||||
/>
|
||||
<i
|
||||
v-if="virtualNoKeyword"
|
||||
class="i-mdi-close-circle text-lg text-[#94a3b8] ml-2"
|
||||
@click="virtualNoKeyword = ''"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 筛选条件 -->
|
||||
<view class="flex items-center gap-2 mb-2">
|
||||
<!-- 运营商 -->
|
||||
<view class="flex-1 bg-white rounded-full px-3 py-2 flex items-center" @click="showCarrierPicker = true">
|
||||
<text class="flex-1 text-sm text-[#1e293b]">{{ selectedCarrierName }}</text>
|
||||
<i class="i-mdi-chevron-down text-lg text-[#94a3b8]" />
|
||||
</view>
|
||||
|
||||
<!-- 状态 -->
|
||||
<view class="flex-1 bg-white rounded-full px-3 py-2 flex items-center" @click="showStatusPicker = true">
|
||||
<text class="flex-1 text-sm text-[#1e293b]">{{ selectedStatusName }}</text>
|
||||
<i class="i-mdi-chevron-down text-lg text-[#94a3b8]" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 搜索按钮 -->
|
||||
<view class="bg-brand rounded-full py-2.5 text-center" @click="refreshList">
|
||||
<text class="text-sm font-500 text-white">搜索</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 设备搜索 -->
|
||||
<view v-else>
|
||||
<view class="bg-white rounded-full px-4 py-2 flex items-center mb-2">
|
||||
<i class="i-mdi-phone text-lg text-[#94a3b8] mr-2" />
|
||||
<input
|
||||
v-model="virtualNoKeyword"
|
||||
class="flex-1 text-sm text-[#1e293b]"
|
||||
placeholder="虚拟号"
|
||||
/>
|
||||
<i
|
||||
v-if="virtualNoKeyword"
|
||||
class="i-mdi-close-circle text-lg text-[#94a3b8] ml-2"
|
||||
@click="virtualNoKeyword = ''"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 搜索按钮 -->
|
||||
<view class="bg-brand rounded-full py-2.5 text-center" @click="refreshList">
|
||||
<text class="text-sm font-500 text-white">搜索</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 筛选标签 -->
|
||||
<view class="px-4 pb-3">
|
||||
<view class="bg-white rounded-full p-1 flex items-center">
|
||||
<!-- IoT卡 -->
|
||||
<view
|
||||
class="flex-1 py-2 rounded-full transition-fast text-center"
|
||||
:class="activeTab === 'card' ? 'bg-brand' : ''"
|
||||
@click="changeTab('card')"
|
||||
>
|
||||
<text class="text-sm font-500" :class="activeTab === 'card' ? 'text-white' : 'text-[#64748b]'">IoT卡</text>
|
||||
</view>
|
||||
|
||||
<!-- 设备 -->
|
||||
<view
|
||||
class="flex-1 py-2 rounded-full transition-fast text-center"
|
||||
:class="activeTab === 'device' ? 'bg-brand' : ''"
|
||||
@click="changeTab('device')"
|
||||
>
|
||||
<text class="text-sm font-500" :class="activeTab === 'device' ? 'text-white' : 'text-[#64748b]'">设备</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<Skeleton v-if="loading && assetList.length === 0" type="list" />
|
||||
|
||||
<!-- 资产列表 -->
|
||||
<view
|
||||
v-else
|
||||
class="overflow-y-auto"
|
||||
:style="{ height: activeTab === 'card' ? 'calc(100vh - 380px)' : 'calc(100vh - 280px)' }"
|
||||
@scroll="onScroll"
|
||||
>
|
||||
<view v-if="assetList.length > 0" class="px-4 pb-4">
|
||||
<view
|
||||
v-for="asset in assetList"
|
||||
:key="`${asset.type}-${asset.id}`"
|
||||
class="card shadow-sm mb-3 cursor-pointer transition-fast hover:shadow-md active:scale-98"
|
||||
@click="viewAssetDetail(asset)"
|
||||
>
|
||||
<!-- 头部区域 -->
|
||||
<view class="flex items-start justify-between mb-3">
|
||||
<!-- 左侧信息 -->
|
||||
<view class="flex items-start gap-2 flex-1 min-w-0">
|
||||
<!-- 状态指示点 -->
|
||||
<view
|
||||
class="w-2 h-2 rounded-full mt-1 flex-shrink-0"
|
||||
:style="{ backgroundColor: asset.statusColor }"
|
||||
/>
|
||||
|
||||
<!-- 资产名称 -->
|
||||
<view class="flex-1 min-w-0">
|
||||
<text class="text-base font-600 text-primary block truncate">
|
||||
{{ asset.name }}
|
||||
</text>
|
||||
<text class="text-xs text-tertiary mt-1 block">
|
||||
{{ asset.detail }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 类型徽章 -->
|
||||
<view
|
||||
class="px-2 py-1 radius-sm text-xs font-500 ml-2 flex-shrink-0"
|
||||
:style="{
|
||||
backgroundColor: asset.type === 'card' ? 'rgba(255, 103, 0, 0.1)' : 'rgba(51, 51, 51, 0.1)',
|
||||
color: asset.type === 'card' ? 'var(--brand-primary)' : 'var(--brand-secondary)',
|
||||
}"
|
||||
>
|
||||
{{ asset.type === 'card' ? 'IoT卡' : '设备' }}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部状态栏 -->
|
||||
<view class="flex items-center justify-between pt-3 border-t border-border-secondary">
|
||||
<!-- 状态标签 -->
|
||||
<view class="flex items-center gap-1">
|
||||
<view
|
||||
class="w-1.5 h-1.5 rounded-full"
|
||||
:style="{ backgroundColor: asset.statusColor }"
|
||||
/>
|
||||
<text class="text-sm font-500" :style="{ color: asset.statusColor }">
|
||||
{{ asset.status }}
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<!-- 操作按钮组 -->
|
||||
<view class="flex items-center gap-2">
|
||||
<!-- IoT卡操作按钮 -->
|
||||
<template v-if="asset.type === 'card'">
|
||||
<!-- 根据网络状态显示对应按钮 -->
|
||||
<template v-if="(asset.raw as CardInfo).network_status === 0">
|
||||
<!-- 已停机,只显示复机按钮 -->
|
||||
<view
|
||||
class="px-3 py-1 rounded-lg flex items-center justify-center"
|
||||
style="background: #52c41a; color: white;"
|
||||
@click="handleStartCard(asset, $event)"
|
||||
>
|
||||
<text class="text-xs font-500">复机</text>
|
||||
</view>
|
||||
</template>
|
||||
<template v-else-if="(asset.raw as CardInfo).network_status === 1">
|
||||
<!-- 正常运行,只显示停机按钮 -->
|
||||
<view
|
||||
class="px-3 py-1 rounded-lg flex items-center justify-center"
|
||||
style="background: #ff4d4f; color: white;"
|
||||
@click="handleStopCard(asset, $event)"
|
||||
>
|
||||
<text class="text-xs font-500">停机</text>
|
||||
</view>
|
||||
</template>
|
||||
<template v-else>
|
||||
<!-- 其他状态,显示两个按钮 -->
|
||||
<view
|
||||
class="px-3 py-1 rounded-lg flex items-center justify-center"
|
||||
style="background: #ff4d4f; color: white;"
|
||||
@click="handleStopCard(asset, $event)"
|
||||
>
|
||||
<text class="text-xs font-500">停机</text>
|
||||
</view>
|
||||
<view
|
||||
class="px-3 py-1 rounded-lg flex items-center justify-center"
|
||||
style="background: #52c41a; color: white;"
|
||||
@click="handleStartCard(asset, $event)"
|
||||
>
|
||||
<text class="text-xs font-500">复机</text>
|
||||
</view>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<!-- 设备操作按钮 - 始终显示两个按钮 -->
|
||||
<template v-else>
|
||||
<view
|
||||
class="px-3 py-1 rounded-lg flex items-center justify-center"
|
||||
style="background: #ff4d4f; color: white;"
|
||||
@click="handleStopDevice(asset, $event)"
|
||||
>
|
||||
<text class="text-xs font-500">停机</text>
|
||||
</view>
|
||||
<view
|
||||
class="px-3 py-1 rounded-lg flex items-center justify-center"
|
||||
style="background: #52c41a; color: white;"
|
||||
@click="handleStartDevice(asset, $event)"
|
||||
>
|
||||
<text class="text-xs font-500">复机</text>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<!-- 箭头 -->
|
||||
<i class="i-mdi-chevron-right text-xl text-quaternary" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 加载更多提示 -->
|
||||
<view v-if="loading" class="py-4 text-center">
|
||||
<i class="i-mdi-loading animate-spin text-xl text-brand mr-2" />
|
||||
<text class="text-sm text-[#64748b]">加载中...</text>
|
||||
</view>
|
||||
<view v-else-if="!((activeTab === 'card' && cardHasMore) || (activeTab === 'device' && deviceHasMore))" class="py-4 text-center">
|
||||
<text class="text-sm text-[#94a3b8]">没有更多了</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view v-else class="px-4">
|
||||
<view class="bg-white rounded-2xl py-12 flex flex-col items-center">
|
||||
<i class="i-mdi-package-variant-closed text-6xl text-[#cbd5e1] mb-3" />
|
||||
<text class="text-base text-[#64748b] mb-1">暂无数据</text>
|
||||
<text class="text-sm text-[#94a3b8]">{{ iccidKeyword || virtualNoKeyword ? '未找到相关结果' : '暂无资产数据' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 运营商选择器 -->
|
||||
<SimplePicker
|
||||
v-model:show="showCarrierPicker"
|
||||
:options="carrierOptions"
|
||||
title="选择运营商"
|
||||
@confirm="onCarrierConfirm"
|
||||
/>
|
||||
|
||||
<!-- 状态选择器 -->
|
||||
<SimplePicker
|
||||
v-model:show="showStatusPicker"
|
||||
:options="statusOptions"
|
||||
title="选择状态"
|
||||
@confirm="onStatusConfirm"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
Reference in New Issue
Block a user