fix(operator): fix operator edit issue
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 5m51s
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 5m51s
This commit is contained in:
37
opencode.json
Normal file
37
opencode.json
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://opencode.ai/config.json",
|
||||||
|
"provider": {
|
||||||
|
"anthropic": {
|
||||||
|
"options": {
|
||||||
|
"baseURL": "http://45.155.220.179:8317/v1",
|
||||||
|
"apiKey": "sk-ZBGcMXCdwtSK7G35s"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"google": {
|
||||||
|
"options": {
|
||||||
|
"baseURL": "http://45.155.220.179:8317/v1beta",
|
||||||
|
"apiKey": "sk-ZBGcMXCdwtSK7G35s"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mcp": {
|
||||||
|
"context7": {
|
||||||
|
"type": "remote",
|
||||||
|
"url": "https://mcp.context7.com/mcp",
|
||||||
|
"enabled": true,
|
||||||
|
"timeout": 10000
|
||||||
|
},
|
||||||
|
"dbhub": {
|
||||||
|
"type": "local",
|
||||||
|
"command": [
|
||||||
|
"npx",
|
||||||
|
"-y",
|
||||||
|
"@bytebase/dbhub@latest",
|
||||||
|
"--transport",
|
||||||
|
"stdio",
|
||||||
|
"--config",
|
||||||
|
".config/dbhub.toml"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -205,6 +205,8 @@ export interface AssetPackageUsageRecord {
|
|||||||
virtual_used_mb?: number // 已用虚流量 MB
|
virtual_used_mb?: number // 已用虚流量 MB
|
||||||
virtual_remain_mb?: number // 剩余虚流量 MB
|
virtual_remain_mb?: number // 剩余虚流量 MB
|
||||||
virtual_ratio?: number // 虚流量比例(real/virtual)
|
virtual_ratio?: number // 虚流量比例(real/virtual)
|
||||||
|
enable_virtual_data?: boolean // 是否启用虚流量
|
||||||
|
package_price?: number // 套餐价格(分)
|
||||||
activated_at?: string // 激活时间
|
activated_at?: string // 激活时间
|
||||||
expires_at?: string // 到期时间
|
expires_at?: string // 到期时间
|
||||||
master_usage_id?: number | null // 主套餐 ID(加油包时有值)
|
master_usage_id?: number | null // 主套餐 ID(加油包时有值)
|
||||||
|
|||||||
@@ -60,6 +60,21 @@
|
|||||||
{{ getAssetStatusName(cardInfo?.status) }}
|
{{ getAssetStatusName(cardInfo?.status) }}
|
||||||
</ElTag>
|
</ElTag>
|
||||||
</ElDescriptionsItem>
|
</ElDescriptionsItem>
|
||||||
|
|
||||||
|
<template v-if="cardInfo?.bound_device_id">
|
||||||
|
<ElDescriptionsItem label="绑定设备号">
|
||||||
|
<ElButton
|
||||||
|
type="primary"
|
||||||
|
link
|
||||||
|
@click="emit('navigateToDevice', cardInfo!.bound_device_no!)"
|
||||||
|
>
|
||||||
|
{{ cardInfo?.bound_device_no || '-' }}
|
||||||
|
</ElButton>
|
||||||
|
</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem label="绑定设备名称">
|
||||||
|
{{ cardInfo?.bound_device_name || '-' }}
|
||||||
|
</ElDescriptionsItem>
|
||||||
|
</template>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- 设备专属字段 -->
|
<!-- 设备专属字段 -->
|
||||||
@@ -329,17 +344,7 @@
|
|||||||
</ElDescriptionsItem>
|
</ElDescriptionsItem>
|
||||||
</ElDescriptions>
|
</ElDescriptions>
|
||||||
</div>
|
</div>
|
||||||
<ElAlert
|
<ElEmpty v-else description="暂无实时信息" :image-size="80" style="margin-top: 16px" />
|
||||||
v-else
|
|
||||||
title="设备实时信息不可用"
|
|
||||||
type="warning"
|
|
||||||
:closable="false"
|
|
||||||
style="margin-top: 16px"
|
|
||||||
>
|
|
||||||
<template #default>
|
|
||||||
<div>Gateway 不可达或设备离线,无法获取实时信息</div>
|
|
||||||
</template>
|
|
||||||
</ElAlert>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- IoT卡操作按钮 -->
|
<!-- IoT卡操作按钮 -->
|
||||||
@@ -377,7 +382,7 @@
|
|||||||
ElTableColumn,
|
ElTableColumn,
|
||||||
ElButton,
|
ElButton,
|
||||||
ElEmpty,
|
ElEmpty,
|
||||||
ElAlert
|
ElMessageBox
|
||||||
} from 'element-plus'
|
} from 'element-plus'
|
||||||
import { useAssetFormatters } from '../composables/useAssetFormatters'
|
import { useAssetFormatters } from '../composables/useAssetFormatters'
|
||||||
import { formatDateTime } from '@/utils/business/format'
|
import { formatDateTime } from '@/utils/business/format'
|
||||||
@@ -415,6 +420,9 @@
|
|||||||
series_name?: string
|
series_name?: string
|
||||||
real_name_at?: string
|
real_name_at?: string
|
||||||
supplier?: string
|
supplier?: string
|
||||||
|
bound_device_id?: number
|
||||||
|
bound_device_no?: string
|
||||||
|
bound_device_name?: string
|
||||||
bound_card_count?: number
|
bound_card_count?: number
|
||||||
max_sim_slots?: number
|
max_sim_slots?: number
|
||||||
activated_at?: string
|
activated_at?: string
|
||||||
@@ -481,6 +489,7 @@
|
|||||||
(e: 'enableBindingCard', payload: { card: BindingCard }): void
|
(e: 'enableBindingCard', payload: { card: BindingCard }): void
|
||||||
(e: 'disableBindingCard', payload: { card: BindingCard }): void
|
(e: 'disableBindingCard', payload: { card: BindingCard }): void
|
||||||
(e: 'navigateToCard', iccid: string): void
|
(e: 'navigateToCard', iccid: string): void
|
||||||
|
(e: 'navigateToDevice', deviceNo: string): void
|
||||||
}
|
}
|
||||||
|
|
||||||
const emit = defineEmits<Emits>()
|
const emit = defineEmits<Emits>()
|
||||||
@@ -565,11 +574,27 @@
|
|||||||
|
|
||||||
// 绑定卡操作
|
// 绑定卡操作
|
||||||
const handleEnableBindingCard = (card: BindingCard) => {
|
const handleEnableBindingCard = (card: BindingCard) => {
|
||||||
emit('enableBindingCard', { card })
|
ElMessageBox.confirm(`确定要启用此卡(${card.iccid})吗?`, '启用确认', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
emit('enableBindingCard', { card })
|
||||||
|
})
|
||||||
|
.catch(() => {})
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleDisableBindingCard = (card: BindingCard) => {
|
const handleDisableBindingCard = (card: BindingCard) => {
|
||||||
emit('disableBindingCard', { card })
|
ElMessageBox.confirm(`确定要停用此卡(${card.iccid})吗?`, '停用确认', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
emit('disableBindingCard', { card })
|
||||||
|
})
|
||||||
|
.catch(() => {})
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -12,114 +12,110 @@
|
|||||||
{{ currentPackage?.status_name || '-' }}
|
{{ currentPackage?.status_name || '-' }}
|
||||||
</ElTag>
|
</ElTag>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 中间:流量进度 -->
|
||||||
|
<div v-if="currentPackage && !props.boundDeviceId" class="header-progress">
|
||||||
|
<div class="header-progress-info">
|
||||||
|
<span class="progress-title">流量</span>
|
||||||
|
<span class="progress-text">
|
||||||
|
<template v-if="currentPackage.enable_virtual_data">
|
||||||
|
已用 {{ formatDataSize(currentPackage.virtual_data_used || 0) }} / 剩余
|
||||||
|
{{ formatDataSize(currentPackage.virtual_data_remaining || 0) }} / 总量
|
||||||
|
{{ formatDataSize(currentPackage.virtual_data_total || 0) }}
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
已用 {{ formatDataSize(currentPackage.real_data_used || 0) }} / 剩余
|
||||||
|
{{ formatDataSize(currentPackage.real_data_remaining || 0) }} / 总量
|
||||||
|
{{ formatDataSize(currentPackage.real_data_total || 0) }}
|
||||||
|
</template>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<ElProgress
|
||||||
|
:percentage="
|
||||||
|
currentPackage.enable_virtual_data
|
||||||
|
? getUsageProgress(
|
||||||
|
currentPackage.virtual_data_used || 0,
|
||||||
|
currentPackage.virtual_data_total || 0
|
||||||
|
)
|
||||||
|
: getUsageProgress(
|
||||||
|
currentPackage.real_data_used || 0,
|
||||||
|
currentPackage.real_data_total || 0
|
||||||
|
)
|
||||||
|
"
|
||||||
|
:color="
|
||||||
|
currentPackage.enable_virtual_data
|
||||||
|
? getProgressColorByPercentage(
|
||||||
|
getUsageProgress(
|
||||||
|
currentPackage.virtual_data_used || 0,
|
||||||
|
currentPackage.virtual_data_total || 0
|
||||||
|
)
|
||||||
|
)
|
||||||
|
: getProgressColorByPercentage(
|
||||||
|
getUsageProgress(
|
||||||
|
currentPackage.real_data_used || 0,
|
||||||
|
currentPackage.real_data_total || 0
|
||||||
|
)
|
||||||
|
)
|
||||||
|
"
|
||||||
|
:stroke-width="10"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div v-if="currentPackage" class="card-header-right">
|
<div v-if="currentPackage" class="card-header-right">
|
||||||
<ElButton type="primary" size="small" @click="handleShowRecharge"> 套餐充值 </ElButton>
|
<ElButton type="primary" size="small" @click="handleShowRecharge"> 套餐充值 </ElButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<!-- 绑定设备提示(优先于一切内容) -->
|
||||||
|
<template v-if="props.boundDeviceId">
|
||||||
|
<ElEmpty :image-size="100">
|
||||||
|
<template #description>
|
||||||
|
<span>该卡已绑定设备,请点击 </span>
|
||||||
|
<ElButton
|
||||||
|
type="primary"
|
||||||
|
link
|
||||||
|
style="font-size: 15px; font-weight: 600"
|
||||||
|
@click="emit('navigateToDevice', props.boundDeviceNo!)"
|
||||||
|
>{{ props.boundDeviceNo }}</ElButton
|
||||||
|
>
|
||||||
|
<span> 进行查看</span>
|
||||||
|
</template>
|
||||||
|
</ElEmpty>
|
||||||
|
</template>
|
||||||
|
|
||||||
<!-- 显示错误信息 -->
|
<!-- 显示错误信息 -->
|
||||||
<ElAlert v-if="errorMsg" :title="errorMsg" type="warning" :closable="false" />
|
<ElAlert v-else-if="errorMsg" :title="errorMsg" type="warning" :closable="false" />
|
||||||
|
|
||||||
<!-- 显示套餐详情 -->
|
<!-- 显示套餐详情 -->
|
||||||
<div v-else-if="currentPackage">
|
<ElDescriptions v-else-if="currentPackage" :column="3" border>
|
||||||
<ElDescriptions :column="3" border>
|
<ElDescriptionsItem label="套餐名称">
|
||||||
<!-- 套餐基本信息 -->
|
{{ currentPackage.package_name || '-' }}
|
||||||
<ElDescriptionsItem label="套餐名称">
|
</ElDescriptionsItem>
|
||||||
{{ currentPackage.package_name || '-' }}
|
<ElDescriptionsItem label="套餐价格">
|
||||||
</ElDescriptionsItem>
|
{{ formatAmount(currentPackage.package_price) }}
|
||||||
<ElDescriptionsItem label="套餐价格">
|
</ElDescriptionsItem>
|
||||||
{{ formatAmount(currentPackage.package_price) }}
|
<ElDescriptionsItem label="套餐类型">
|
||||||
</ElDescriptionsItem>
|
{{ getPackageTypeName(currentPackage.package_type) }}
|
||||||
<ElDescriptionsItem label="套餐总量">
|
</ElDescriptionsItem>
|
||||||
{{ formatDataSize(currentPackage.package_total_data || 0) }}
|
<ElDescriptionsItem v-if="currentPackage.enable_virtual_data" label="虚流量比例">
|
||||||
</ElDescriptionsItem>
|
{{ currentPackage.virtual_ratio || '-' }}
|
||||||
<ElDescriptionsItem label="套餐类型">
|
</ElDescriptionsItem>
|
||||||
{{ getPackageTypeName(currentPackage.package_type) }}
|
<ElDescriptionsItem v-if="currentPackage.duration_days" label="套餐时长" :span="3">
|
||||||
</ElDescriptionsItem>
|
{{ currentPackage.duration_days }}天
|
||||||
<ElDescriptionsItem label="虚流量比例">
|
</ElDescriptionsItem>
|
||||||
{{ currentPackage.virtual_ratio || '-' }}
|
<ElDescriptionsItem label="开始时间">
|
||||||
</ElDescriptionsItem>
|
{{ formatDateTime(currentPackage.start_time) || '-' }}
|
||||||
|
</ElDescriptionsItem>
|
||||||
<!-- 套餐时长 -->
|
<ElDescriptionsItem label="到期时间">
|
||||||
<ElDescriptionsItem v-if="currentPackage.duration_days" label="套餐时长" :span="3">
|
{{ formatDateTime(currentPackage.expire_time) || '-' }}
|
||||||
{{ currentPackage.duration_days }}天
|
</ElDescriptionsItem>
|
||||||
</ElDescriptionsItem>
|
<ElDescriptionsItem label="状态">
|
||||||
</ElDescriptions>
|
<ElTag :type="getPackageStatusTagType(currentPackage.status)" size="small">
|
||||||
|
{{ currentPackage.status_name || '-' }}
|
||||||
<!-- 真流量进度 -->
|
</ElTag>
|
||||||
<div class="traffic-progress-wrapper">
|
</ElDescriptionsItem>
|
||||||
<div class="progress-info">
|
</ElDescriptions>
|
||||||
<span class="progress-label">真流量</span>
|
|
||||||
<span class="progress-text">
|
|
||||||
已用 {{ formatDataSize(currentPackage.real_data_used || 0) }} / 剩余
|
|
||||||
{{ formatDataSize(currentPackage.real_data_remaining || 0) }} / 总量
|
|
||||||
{{ formatDataSize(currentPackage.real_data_total || 0) }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<ElProgress
|
|
||||||
:percentage="
|
|
||||||
getUsageProgress(
|
|
||||||
currentPackage.real_data_used || 0,
|
|
||||||
currentPackage.real_data_total || 0
|
|
||||||
)
|
|
||||||
"
|
|
||||||
:color="
|
|
||||||
getProgressColorByPercentage(
|
|
||||||
getUsageProgress(
|
|
||||||
currentPackage.real_data_used || 0,
|
|
||||||
currentPackage.real_data_total || 0
|
|
||||||
)
|
|
||||||
)
|
|
||||||
"
|
|
||||||
:stroke-width="20"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 虚流量进度 -->
|
|
||||||
<div class="traffic-progress-wrapper">
|
|
||||||
<div class="progress-info">
|
|
||||||
<span class="progress-label">虚流量</span>
|
|
||||||
<span class="progress-text">
|
|
||||||
已用 {{ formatDataSize(currentPackage.virtual_data_used || 0) }} / 剩余
|
|
||||||
{{ formatDataSize(currentPackage.virtual_data_remaining || 0) }} / 总量
|
|
||||||
{{ formatDataSize(currentPackage.virtual_data_total || 0) }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<ElProgress
|
|
||||||
:percentage="
|
|
||||||
getUsageProgress(
|
|
||||||
currentPackage.virtual_data_used || 0,
|
|
||||||
currentPackage.virtual_data_total || 0
|
|
||||||
)
|
|
||||||
"
|
|
||||||
:color="
|
|
||||||
getProgressColorByPercentage(
|
|
||||||
getUsageProgress(
|
|
||||||
currentPackage.virtual_data_used || 0,
|
|
||||||
currentPackage.virtual_data_total || 0
|
|
||||||
)
|
|
||||||
)
|
|
||||||
"
|
|
||||||
:stroke-width="20"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 时间信息 -->
|
|
||||||
<ElDescriptions :column="3" border style="margin-top: 16px">
|
|
||||||
<ElDescriptionsItem label="开始时间">
|
|
||||||
{{ formatDateTime(currentPackage.start_time) || '-' }}
|
|
||||||
</ElDescriptionsItem>
|
|
||||||
<ElDescriptionsItem label="到期时间">
|
|
||||||
{{ formatDateTime(currentPackage.expire_time) || '-' }}
|
|
||||||
</ElDescriptionsItem>
|
|
||||||
<ElDescriptionsItem label="状态">
|
|
||||||
<ElTag :type="getPackageStatusTagType(currentPackage.status)" size="small">
|
|
||||||
{{ currentPackage.status_name || '-' }}
|
|
||||||
</ElTag>
|
|
||||||
</ElDescriptionsItem>
|
|
||||||
</ElDescriptions>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 空状态 -->
|
<!-- 空状态 -->
|
||||||
<ElEmpty v-else-if="!loading" :description="errorMsg || '暂无当前生效套餐'" :image-size="100" />
|
<ElEmpty v-else-if="!loading" :description="errorMsg || '暂无当前生效套餐'" :image-size="100" />
|
||||||
@@ -155,16 +151,23 @@
|
|||||||
currentPackage: PackageInfo | null
|
currentPackage: PackageInfo | null
|
||||||
loading?: boolean
|
loading?: boolean
|
||||||
errorMsg?: string
|
errorMsg?: string
|
||||||
|
boundDeviceId?: number
|
||||||
|
boundDeviceNo?: string
|
||||||
|
boundDeviceName?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
loading: false,
|
loading: false,
|
||||||
errorMsg: ''
|
errorMsg: '',
|
||||||
|
boundDeviceId: undefined,
|
||||||
|
boundDeviceNo: '',
|
||||||
|
boundDeviceName: ''
|
||||||
})
|
})
|
||||||
|
|
||||||
// Emits
|
// Emits
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
showRecharge: []
|
showRecharge: []
|
||||||
|
navigateToDevice: [deviceNo: string]
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
// Methods
|
// Methods
|
||||||
@@ -208,34 +211,32 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.traffic-progress-wrapper {
|
.header-progress {
|
||||||
padding: 16px;
|
flex: 1;
|
||||||
margin-top: 16px;
|
min-width: 0;
|
||||||
background: var(--el-fill-color-light);
|
padding: 0 24px;
|
||||||
border-radius: 8px;
|
|
||||||
|
|
||||||
.progress-info {
|
.header-progress-info {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: baseline;
|
||||||
justify-content: space-between;
|
gap: 12px;
|
||||||
margin-bottom: 12px;
|
margin-bottom: 6px;
|
||||||
|
|
||||||
.progress-label {
|
.progress-title {
|
||||||
font-size: 15px;
|
flex-shrink: 0;
|
||||||
font-weight: 500;
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
color: var(--el-text-color-primary);
|
color: var(--el-text-color-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.progress-text {
|
.progress-text {
|
||||||
font-size: 13px;
|
font-size: 12px;
|
||||||
color: var(--el-text-color-regular);
|
color: var(--el-text-color-secondary);
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.package-actions {
|
|
||||||
margin-top: 16px;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -6,110 +6,117 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div class="package-table-wrapper">
|
<div class="package-table-wrapper">
|
||||||
<div v-if="packageList && packageList.length > 0" class="table-scroll-container">
|
<!-- 绑定设备提示 -->
|
||||||
<ElTable
|
<ElEmpty v-if="props.boundDeviceId" :image-size="100">
|
||||||
:data="packageList"
|
<template #description>
|
||||||
class="package-table"
|
<span>该卡已绑定设备,请点击 </span>
|
||||||
border
|
<ElButton
|
||||||
>
|
type="primary"
|
||||||
<ElTableColumn label="套餐名称" width="150">
|
link
|
||||||
<template #default="scope">
|
style="font-size: 15px; font-weight: 600"
|
||||||
<ElButton
|
@click="emit('navigateToDevice', props.boundDeviceNo!)"
|
||||||
type="primary"
|
>{{ props.boundDeviceNo }}</ElButton
|
||||||
link
|
>
|
||||||
class="package-name-link"
|
<span> 进行查看</span>
|
||||||
@click="handleShowDailyRecords(scope.row)"
|
</template>
|
||||||
v-permission="'package-usage:daily-records:view'"
|
</ElEmpty>
|
||||||
>
|
<div v-else-if="packageList && packageList.length > 0" class="table-scroll-container">
|
||||||
{{ scope.row.package_name }}
|
<ElTable :data="packageList" class="package-table" border>
|
||||||
</ElButton>
|
<ElTableColumn label="套餐名称" width="150">
|
||||||
</template>
|
<template #default="scope">
|
||||||
</ElTableColumn>
|
<ElButton
|
||||||
<ElTableColumn label="套餐价格" width="120">
|
type="primary"
|
||||||
<template #default="scope">
|
link
|
||||||
{{ formatAmount(scope.row.package_price || 0) }}
|
class="package-name-link"
|
||||||
</template>
|
@click="handleShowDailyRecords(scope.row)"
|
||||||
</ElTableColumn>
|
v-permission="'package-usage:daily-records:view'"
|
||||||
<ElTableColumn prop="status_name" label="套餐状态" width="100">
|
>
|
||||||
<template #default="scope">
|
{{ scope.row.package_name }}
|
||||||
<ElTag :type="getPackageStatusTagType(scope.row.status)" size="small">
|
</ElButton>
|
||||||
{{ scope.row.status_name }}
|
</template>
|
||||||
</ElTag>
|
</ElTableColumn>
|
||||||
</template>
|
<ElTableColumn label="套餐价格" width="120">
|
||||||
</ElTableColumn>
|
<template #default="scope">
|
||||||
<ElTableColumn prop="data_limit_mb" label="总流量" width="120">
|
{{ formatAmount(scope.row.package_price || 0) }}
|
||||||
<template #default="scope">
|
</template>
|
||||||
{{ formatDataSize(scope.row.data_limit_mb) }}
|
</ElTableColumn>
|
||||||
</template>
|
<ElTableColumn prop="status_name" label="套餐状态" width="100">
|
||||||
</ElTableColumn>
|
<template #default="scope">
|
||||||
<ElTableColumn label="真流量" width="280">
|
<ElTag :type="getPackageStatusTagType(scope.row.status)" size="small">
|
||||||
<template #default="scope">
|
{{ scope.row.status_name }}
|
||||||
<div class="traffic-progress">
|
</ElTag>
|
||||||
<div class="progress-text">
|
</template>
|
||||||
<span class="used">已用: {{ formatDataSize(scope.row.data_usage_mb) }}</span>
|
</ElTableColumn>
|
||||||
<span class="remaining">
|
<ElTableColumn prop="data_limit_mb" label="总流量" width="120">
|
||||||
剩余: {{ formatDataSize(scope.row.data_limit_mb - scope.row.data_usage_mb) }}
|
<template #default="scope">
|
||||||
</span>
|
{{ formatDataSize(scope.row.data_limit_mb) }}
|
||||||
<span class="total">总量: {{ formatDataSize(scope.row.data_limit_mb) }}</span>
|
</template>
|
||||||
|
</ElTableColumn>
|
||||||
|
<ElTableColumn label="流量" min-width="220">
|
||||||
|
<template #default="scope">
|
||||||
|
<div class="traffic-progress">
|
||||||
|
<div class="progress-text">
|
||||||
|
<template v-if="scope.row.enable_virtual_data">
|
||||||
|
<span class="used"
|
||||||
|
>已用: {{ formatDataSize(scope.row.virtual_used_mb || 0) }}</span
|
||||||
|
>
|
||||||
|
<span class="remaining"
|
||||||
|
>剩余: {{ formatDataSize(scope.row.virtual_remain_mb || 0) }}</span
|
||||||
|
>
|
||||||
|
<span class="total"
|
||||||
|
>总量: {{ formatDataSize(scope.row.virtual_limit_mb || 0) }}</span
|
||||||
|
>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<span class="used">已用: {{ formatDataSize(scope.row.data_usage_mb) }}</span>
|
||||||
|
<span class="remaining"
|
||||||
|
>剩余:
|
||||||
|
{{ formatDataSize(scope.row.data_limit_mb - scope.row.data_usage_mb) }}</span
|
||||||
|
>
|
||||||
|
<span class="total">总量: {{ formatDataSize(scope.row.data_limit_mb) }}</span>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<ElProgress
|
||||||
|
:percentage="
|
||||||
|
scope.row.enable_virtual_data
|
||||||
|
? getUsageProgress(scope.row.virtual_used_mb, scope.row.virtual_limit_mb)
|
||||||
|
: getUsageProgress(scope.row.data_usage_mb, scope.row.data_limit_mb)
|
||||||
|
"
|
||||||
|
:color="
|
||||||
|
scope.row.enable_virtual_data
|
||||||
|
? getProgressColorByPercentage(
|
||||||
|
getUsageProgress(scope.row.virtual_used_mb, scope.row.virtual_limit_mb)
|
||||||
|
)
|
||||||
|
: getProgressColorByPercentage(
|
||||||
|
getUsageProgress(scope.row.data_usage_mb, scope.row.data_limit_mb)
|
||||||
|
)
|
||||||
|
"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<ElProgress
|
</template>
|
||||||
:percentage="getUsageProgress(scope.row.data_usage_mb, scope.row.data_limit_mb)"
|
</ElTableColumn>
|
||||||
:color="
|
<ElTableColumn prop="activated_at" label="开始时间" width="170" show-overflow-tooltip>
|
||||||
getProgressColorByPercentage(
|
<template #default="scope">
|
||||||
getUsageProgress(scope.row.data_usage_mb, scope.row.data_limit_mb)
|
{{ formatDateTime(scope.row.activated_at) }}
|
||||||
)
|
</template>
|
||||||
"
|
</ElTableColumn>
|
||||||
/>
|
<ElTableColumn prop="expires_at" label="到期时间" width="170" show-overflow-tooltip>
|
||||||
</div>
|
<template #default="scope">
|
||||||
</template>
|
{{ formatDateTime(scope.row.expires_at) }}
|
||||||
</ElTableColumn>
|
</template>
|
||||||
<ElTableColumn label="虚流量" width="280">
|
</ElTableColumn>
|
||||||
<template #default="scope">
|
<ElTableColumn label="操作" width="100" align="center" fixed="right">
|
||||||
<div class="traffic-progress">
|
<template #default="scope">
|
||||||
<div class="progress-text">
|
<ElButton
|
||||||
<span class="used">已用: {{ formatDataSize(scope.row.virtual_used_mb || 0) }}</span>
|
type="primary"
|
||||||
<span class="remaining">
|
link
|
||||||
剩余: {{ formatDataSize(scope.row.virtual_remain_mb || 0) }}
|
@click="handleShowDailyRecords(scope.row)"
|
||||||
</span>
|
v-permission="'package-usage:daily-records:view'"
|
||||||
<span class="total">
|
>
|
||||||
总量: {{ formatDataSize(scope.row.virtual_limit_mb || 0) }}
|
流量详单
|
||||||
</span>
|
</ElButton>
|
||||||
</div>
|
</template>
|
||||||
<ElProgress
|
</ElTableColumn>
|
||||||
:percentage="
|
|
||||||
getUsageProgress(scope.row.virtual_used_mb, scope.row.virtual_limit_mb)
|
|
||||||
"
|
|
||||||
:color="
|
|
||||||
getProgressColorByPercentage(
|
|
||||||
getUsageProgress(scope.row.virtual_used_mb, scope.row.virtual_limit_mb)
|
|
||||||
)
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</ElTableColumn>
|
|
||||||
<ElTableColumn prop="activated_at" label="开始时间" width="170" show-overflow-tooltip>
|
|
||||||
<template #default="scope">
|
|
||||||
{{ formatDateTime(scope.row.activated_at) }}
|
|
||||||
</template>
|
|
||||||
</ElTableColumn>
|
|
||||||
<ElTableColumn prop="expires_at" label="到期时间" width="170" show-overflow-tooltip>
|
|
||||||
<template #default="scope">
|
|
||||||
{{ formatDateTime(scope.row.expires_at) }}
|
|
||||||
</template>
|
|
||||||
</ElTableColumn>
|
|
||||||
<ElTableColumn label="操作" width="100" align="center" fixed="right">
|
|
||||||
<template #default="scope">
|
|
||||||
<ElButton
|
|
||||||
type="primary"
|
|
||||||
link
|
|
||||||
@click="handleShowDailyRecords(scope.row)"
|
|
||||||
v-permission="'package-usage:daily-records:view'"
|
|
||||||
>
|
|
||||||
流量详单
|
|
||||||
</ElButton>
|
|
||||||
</template>
|
|
||||||
</ElTableColumn>
|
|
||||||
</ElTable>
|
</ElTable>
|
||||||
<ElPagination
|
<ElPagination
|
||||||
v-model:current-page="pagination.page"
|
v-model:current-page="pagination.page"
|
||||||
@@ -122,7 +129,7 @@
|
|||||||
@size-change="handleSizeChange"
|
@size-change="handleSizeChange"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<ElEmpty v-else description="暂无套餐数据" :image-size="100" />
|
<ElEmpty v-else-if="!props.boundDeviceId" description="暂无套餐数据" :image-size="100" />
|
||||||
</div>
|
</div>
|
||||||
</ElCard>
|
</ElCard>
|
||||||
</template>
|
</template>
|
||||||
@@ -139,6 +146,7 @@
|
|||||||
ElEmpty,
|
ElEmpty,
|
||||||
ElPagination
|
ElPagination
|
||||||
} from 'element-plus'
|
} from 'element-plus'
|
||||||
|
|
||||||
import { useAssetFormatters } from '../composables/useAssetFormatters'
|
import { useAssetFormatters } from '../composables/useAssetFormatters'
|
||||||
import { formatDateTime } from '@/utils/business/format'
|
import { formatDateTime } from '@/utils/business/format'
|
||||||
import type { AssetPackageUsageRecord } from '@/types/api'
|
import type { AssetPackageUsageRecord } from '@/types/api'
|
||||||
@@ -151,12 +159,16 @@
|
|||||||
total?: number
|
total?: number
|
||||||
page?: number
|
page?: number
|
||||||
pageSize?: number
|
pageSize?: number
|
||||||
|
boundDeviceId?: number
|
||||||
|
boundDeviceNo?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
total: 0,
|
total: 0,
|
||||||
page: 1,
|
page: 1,
|
||||||
pageSize: 10
|
pageSize: 10,
|
||||||
|
boundDeviceId: undefined,
|
||||||
|
boundDeviceNo: ''
|
||||||
})
|
})
|
||||||
|
|
||||||
// Emits
|
// Emits
|
||||||
@@ -164,6 +176,7 @@
|
|||||||
(e: 'showDailyRecords', payload: { packageRow: PackageInfo }): void
|
(e: 'showDailyRecords', payload: { packageRow: PackageInfo }): void
|
||||||
(e: 'page-change', page: number): void
|
(e: 'page-change', page: number): void
|
||||||
(e: 'size-change', size: number): void
|
(e: 'size-change', size: number): void
|
||||||
|
(e: 'navigateToDevice', deviceNo: string): void
|
||||||
}
|
}
|
||||||
|
|
||||||
const emit = defineEmits<Emits>()
|
const emit = defineEmits<Emits>()
|
||||||
|
|||||||
@@ -3,10 +3,26 @@
|
|||||||
<template #header>
|
<template #header>
|
||||||
<div class="card-header wallet-header">
|
<div class="card-header wallet-header">
|
||||||
<div class="header-left">
|
<div class="header-left">
|
||||||
<span>钱包流水</span>
|
<span class="wallet-title">钱包</span>
|
||||||
<ElTag type="info" size="small"> 共 {{ total }} 条 </ElTag>
|
<ElTag v-if="walletInfo" :type="getWalletStatusType(walletInfo.status)" size="small">
|
||||||
|
{{ walletInfo.status_text || '-' }}
|
||||||
|
</ElTag>
|
||||||
|
<template v-if="walletInfo">
|
||||||
|
<ElDivider direction="vertical" />
|
||||||
|
<span class="balance-item"
|
||||||
|
>💰 总余额 <strong>{{ formatAmount(walletInfo.balance) }}</strong></span
|
||||||
|
>
|
||||||
|
<ElDivider direction="vertical" />
|
||||||
|
<span class="balance-item"
|
||||||
|
>✅ 可用 <strong>{{ formatAmount(walletInfo.available_balance) }}</strong></span
|
||||||
|
>
|
||||||
|
<ElDivider direction="vertical" />
|
||||||
|
<span class="balance-item"
|
||||||
|
>🔒 冻结 <strong>{{ formatAmount(walletInfo.frozen_balance) }}</strong></span
|
||||||
|
>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<div class="filter-section">
|
<div v-if="!props.boundDeviceId" class="filter-section">
|
||||||
<ElSelect
|
<ElSelect
|
||||||
v-model="filterForm.transaction_type"
|
v-model="filterForm.transaction_type"
|
||||||
placeholder="交易类型"
|
placeholder="交易类型"
|
||||||
@@ -28,11 +44,27 @@
|
|||||||
/>
|
/>
|
||||||
<ElButton type="primary" @click="handleQuery">查询</ElButton>
|
<ElButton type="primary" @click="handleQuery">查询</ElButton>
|
||||||
<ElButton @click="handleReset">重置</ElButton>
|
<ElButton @click="handleReset">重置</ElButton>
|
||||||
|
<ElTag type="info" size="small">共 {{ total }} 条</ElTag>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div class="wallet-table-wrapper">
|
|
||||||
<!-- 流水表格 -->
|
<!-- 绑定设备提示 -->
|
||||||
|
<ElEmpty v-if="props.boundDeviceId" :image-size="100">
|
||||||
|
<template #description>
|
||||||
|
<span>该卡已绑定设备,请点击 </span>
|
||||||
|
<ElButton
|
||||||
|
type="primary"
|
||||||
|
link
|
||||||
|
style="font-size: 15px; font-weight: 600"
|
||||||
|
@click="emit('navigateToDevice', props.boundDeviceNo!)"
|
||||||
|
>{{ props.boundDeviceNo }}</ElButton
|
||||||
|
>
|
||||||
|
<span> 进行查看</span>
|
||||||
|
</template>
|
||||||
|
</ElEmpty>
|
||||||
|
|
||||||
|
<div v-else class="wallet-table-wrapper">
|
||||||
<ElTable
|
<ElTable
|
||||||
v-if="transactionList && transactionList.length > 0"
|
v-if="transactionList && transactionList.length > 0"
|
||||||
:data="transactionList"
|
:data="transactionList"
|
||||||
@@ -115,7 +147,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, watch, onMounted } from 'vue'
|
import { ref, watch } from 'vue'
|
||||||
import {
|
import {
|
||||||
ElCard,
|
ElCard,
|
||||||
ElTable,
|
ElTable,
|
||||||
@@ -127,30 +159,46 @@
|
|||||||
ElDatePicker,
|
ElDatePicker,
|
||||||
ElPagination,
|
ElPagination,
|
||||||
ElEmpty,
|
ElEmpty,
|
||||||
ElMessage
|
ElMessage,
|
||||||
|
ElDivider
|
||||||
} from 'element-plus'
|
} from 'element-plus'
|
||||||
import { useAssetFormatters } from '../composables/useAssetFormatters'
|
import { useAssetFormatters } from '../composables/useAssetFormatters'
|
||||||
import { formatDateTime } from '@/utils/business/format'
|
import { formatDateTime } from '@/utils/business/format'
|
||||||
import { AssetService } from '@/api/modules'
|
import { AssetService } from '@/api/modules'
|
||||||
|
|
||||||
// Props
|
interface WalletInfo {
|
||||||
interface Props {
|
balance: number
|
||||||
assetIdentifier: string // ICCID or virtual_no
|
available_balance: number
|
||||||
|
frozen_balance: number
|
||||||
|
status: number
|
||||||
|
status_text?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<Props>()
|
interface Props {
|
||||||
|
assetIdentifier: string
|
||||||
|
walletInfo: WalletInfo | null
|
||||||
|
walletLoading?: boolean
|
||||||
|
boundDeviceId?: number
|
||||||
|
boundDeviceNo?: string
|
||||||
|
}
|
||||||
|
|
||||||
// 使用格式化工具
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
const { formatAmount, getTransactionTypeTag } = useAssetFormatters()
|
walletLoading: false,
|
||||||
|
boundDeviceId: undefined,
|
||||||
|
boundDeviceNo: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'navigateToDevice', deviceNo: string): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const { formatAmount, getTransactionTypeTag, getWalletStatusType } = useAssetFormatters()
|
||||||
|
|
||||||
// State
|
// State
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const transactionList = ref<any[]>([])
|
const transactionList = ref<any[]>([])
|
||||||
const total = ref(0)
|
const total = ref(0)
|
||||||
const pagination = ref({
|
const pagination = ref({ page: 1, page_size: 20 })
|
||||||
page: 1,
|
|
||||||
page_size: 20
|
|
||||||
})
|
|
||||||
const filterForm = ref<{
|
const filterForm = ref<{
|
||||||
transaction_type: string | null
|
transaction_type: string | null
|
||||||
date_range: [Date, Date] | null
|
date_range: [Date, Date] | null
|
||||||
@@ -158,8 +206,6 @@
|
|||||||
transaction_type: null,
|
transaction_type: null,
|
||||||
date_range: null
|
date_range: null
|
||||||
})
|
})
|
||||||
|
|
||||||
// API调用参数
|
|
||||||
const queryParams = ref({
|
const queryParams = ref({
|
||||||
page: 1,
|
page: 1,
|
||||||
page_size: 20,
|
page_size: 20,
|
||||||
@@ -168,10 +214,8 @@
|
|||||||
end_time: null as string | null
|
end_time: null as string | null
|
||||||
})
|
})
|
||||||
|
|
||||||
// 加载钱包流水列表
|
|
||||||
const loadTransactions = async () => {
|
const loadTransactions = async () => {
|
||||||
if (!props.assetIdentifier) return
|
if (!props.assetIdentifier) return
|
||||||
|
|
||||||
try {
|
try {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
const response = await AssetService.getWalletTransactions(
|
const response = await AssetService.getWalletTransactions(
|
||||||
@@ -193,13 +237,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理查询按钮点击
|
|
||||||
const handleQuery = () => {
|
const handleQuery = () => {
|
||||||
queryParams.value.page = 1
|
queryParams.value.page = 1
|
||||||
pagination.value.page = 1
|
pagination.value.page = 1
|
||||||
queryParams.value.transaction_type = filterForm.value.transaction_type
|
queryParams.value.transaction_type = filterForm.value.transaction_type
|
||||||
|
|
||||||
// 处理日期范围
|
|
||||||
if (filterForm.value.date_range && filterForm.value.date_range.length === 2) {
|
if (filterForm.value.date_range && filterForm.value.date_range.length === 2) {
|
||||||
queryParams.value.start_time = filterForm.value.date_range[0].toISOString()
|
queryParams.value.start_time = filterForm.value.date_range[0].toISOString()
|
||||||
queryParams.value.end_time = filterForm.value.date_range[1].toISOString()
|
queryParams.value.end_time = filterForm.value.date_range[1].toISOString()
|
||||||
@@ -207,16 +248,13 @@
|
|||||||
queryParams.value.start_time = null
|
queryParams.value.start_time = null
|
||||||
queryParams.value.end_time = null
|
queryParams.value.end_time = null
|
||||||
}
|
}
|
||||||
|
|
||||||
loadTransactions()
|
loadTransactions()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理重置按钮点击
|
const handleFilterChange = () => handleQuery()
|
||||||
|
|
||||||
const handleReset = () => {
|
const handleReset = () => {
|
||||||
filterForm.value = {
|
filterForm.value = { transaction_type: null, date_range: null }
|
||||||
transaction_type: null,
|
|
||||||
date_range: null
|
|
||||||
}
|
|
||||||
queryParams.value = {
|
queryParams.value = {
|
||||||
page: 1,
|
page: 1,
|
||||||
page_size: 20,
|
page_size: 20,
|
||||||
@@ -224,14 +262,10 @@
|
|||||||
start_time: null,
|
start_time: null,
|
||||||
end_time: null
|
end_time: null
|
||||||
}
|
}
|
||||||
pagination.value = {
|
pagination.value = { page: 1, page_size: 20 }
|
||||||
page: 1,
|
|
||||||
page_size: 20
|
|
||||||
}
|
|
||||||
loadTransactions()
|
loadTransactions()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理分页大小变化
|
|
||||||
const handlePageSizeChange = (size: number) => {
|
const handlePageSizeChange = (size: number) => {
|
||||||
queryParams.value.page_size = size
|
queryParams.value.page_size = size
|
||||||
queryParams.value.page = 1
|
queryParams.value.page = 1
|
||||||
@@ -239,30 +273,19 @@
|
|||||||
loadTransactions()
|
loadTransactions()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理页码变化
|
|
||||||
const handlePageChange = (page: number) => {
|
const handlePageChange = (page: number) => {
|
||||||
queryParams.value.page = page
|
queryParams.value.page = page
|
||||||
loadTransactions()
|
loadTransactions()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 监听资产标识符变化
|
// 仅监听 assetIdentifier 变化加载数据(去掉 onMounted 避免重复调用)
|
||||||
watch(
|
watch(
|
||||||
() => props.assetIdentifier,
|
() => props.assetIdentifier,
|
||||||
(newVal) => {
|
(newVal) => {
|
||||||
if (newVal) {
|
if (newVal) handleReset()
|
||||||
// 重置状态
|
|
||||||
handleReset()
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{ immediate: true }
|
{ immediate: true }
|
||||||
)
|
)
|
||||||
|
|
||||||
// 组件挂载时加载数据
|
|
||||||
onMounted(() => {
|
|
||||||
if (props.assetIdentifier) {
|
|
||||||
loadTransactions()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
@@ -277,14 +300,30 @@
|
|||||||
|
|
||||||
.header-left {
|
.header-left {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
|
.wallet-title {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.balance-item {
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--el-text-color-regular);
|
||||||
|
|
||||||
|
strong {
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--el-text-color-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter-section {
|
.filter-section {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 12px;
|
gap: 10px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -315,7 +354,7 @@
|
|||||||
.pagination-wrapper {
|
.pagination-wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
margin-top: 20px;
|
margin-top: 16px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,56 +1,49 @@
|
|||||||
<template>
|
<template>
|
||||||
<ElDialog v-model="visible" title="切换SIM卡" width="600px">
|
<ElDialog v-model="visible" title="切换SIM卡" width="600px">
|
||||||
<div v-if="loading" style="padding: 40px; text-align: center">
|
<ElAlert
|
||||||
<ElIcon class="is-loading" :size="40"><Loading /></ElIcon>
|
v-if="bindingCards.length === 0"
|
||||||
<div style="margin-top: 16px">加载设备绑定的卡列表中...</div>
|
title="该设备暂无绑定的SIM卡"
|
||||||
</div>
|
type="warning"
|
||||||
|
:closable="false"
|
||||||
|
style="margin-bottom: 20px"
|
||||||
|
>
|
||||||
|
<template #default>
|
||||||
|
<div>当前设备没有绑定任何SIM卡,无法进行切换操作。</div>
|
||||||
|
<div>请先为设备绑定SIM卡后再进行切换。</div>
|
||||||
|
</template>
|
||||||
|
</ElAlert>
|
||||||
|
|
||||||
<template v-else>
|
<ElForm
|
||||||
<ElAlert
|
v-if="bindingCards.length > 0"
|
||||||
v-if="bindingCards.length === 0"
|
ref="formRef"
|
||||||
title="该设备暂无绑定的SIM卡"
|
:model="form"
|
||||||
type="warning"
|
:rules="rules"
|
||||||
:closable="false"
|
label-width="120px"
|
||||||
style="margin-bottom: 20px"
|
>
|
||||||
>
|
<ElFormItem label="目标ICCID" prop="target_iccid">
|
||||||
<template #default>
|
<ElSelect
|
||||||
<div>当前设备没有绑定任何SIM卡,无法进行切换操作。</div>
|
v-model="form.target_iccid"
|
||||||
<div>请先为设备绑定SIM卡后再进行切换。</div>
|
placeholder="请选择要切换到的目标ICCID"
|
||||||
</template>
|
style="width: 100%"
|
||||||
</ElAlert>
|
clearable
|
||||||
|
>
|
||||||
<ElForm
|
<ElOption
|
||||||
v-if="bindingCards.length > 0"
|
v-for="card in bindingCards"
|
||||||
ref="formRef"
|
:key="card.iccid"
|
||||||
:model="form"
|
:label="`${card.iccid} - 插槽${card.slot_position} - ${card.carrier_name || '-'}`"
|
||||||
:rules="rules"
|
:value="card.iccid"
|
||||||
label-width="120px"
|
|
||||||
>
|
|
||||||
<ElFormItem label="目标ICCID" prop="target_iccid">
|
|
||||||
<ElSelect
|
|
||||||
v-model="form.target_iccid"
|
|
||||||
placeholder="请选择要切换到的目标ICCID"
|
|
||||||
style="width: 100%"
|
|
||||||
clearable
|
|
||||||
>
|
>
|
||||||
<ElOption
|
<div style="display: flex; align-items: center; justify-content: space-between">
|
||||||
v-for="card in bindingCards"
|
<span>{{ card.iccid }}</span>
|
||||||
:key="card.iccid"
|
<ElTag size="small" style="margin-left: 10px">插槽{{ card.slot_position }}</ElTag>
|
||||||
:label="`${card.iccid} - 插槽${card.slot_position} - ${card.carrier_name}`"
|
</div>
|
||||||
:value="card.iccid"
|
</ElOption>
|
||||||
>
|
</ElSelect>
|
||||||
<div style="display: flex; align-items: center; justify-content: space-between">
|
<div style="margin-top: 8px; font-size: 12px; color: #909399">
|
||||||
<span>{{ card.iccid }}</span>
|
当前设备共绑定 {{ bindingCards.length }} 张SIM卡
|
||||||
<ElTag size="small" style="margin-left: 10px">插槽{{ card.slot_position }}</ElTag>
|
</div>
|
||||||
</div>
|
</ElFormItem>
|
||||||
</ElOption>
|
</ElForm>
|
||||||
</ElSelect>
|
|
||||||
<div style="margin-top: 8px; font-size: 12px; color: #909399">
|
|
||||||
当前设备共绑定 {{ bindingCards.length }} 张SIM卡
|
|
||||||
</div>
|
|
||||||
</ElFormItem>
|
|
||||||
</ElForm>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<ElButton @click="handleCancel">取消</ElButton>
|
<ElButton @click="handleCancel">取消</ElButton>
|
||||||
@@ -76,17 +69,20 @@
|
|||||||
ElOption,
|
ElOption,
|
||||||
ElButton,
|
ElButton,
|
||||||
ElAlert,
|
ElAlert,
|
||||||
ElIcon,
|
ElTag
|
||||||
ElTag,
|
|
||||||
ElMessage
|
|
||||||
} from 'element-plus'
|
} from 'element-plus'
|
||||||
import type { FormInstance, FormRules } from 'element-plus'
|
import type { FormInstance, FormRules } from 'element-plus'
|
||||||
import { Loading } from '@element-plus/icons-vue'
|
|
||||||
import { DeviceService } from '@/api/modules'
|
interface BindingCard {
|
||||||
|
iccid: string
|
||||||
|
slot_position?: number
|
||||||
|
carrier_name?: string
|
||||||
|
is_current?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
modelValue: boolean
|
modelValue: boolean
|
||||||
deviceId: number
|
cards?: BindingCard[]
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Emits {
|
interface Emits {
|
||||||
@@ -94,17 +90,15 @@
|
|||||||
(e: 'confirm', data: { target_iccid: string }): void
|
(e: 'confirm', data: { target_iccid: string }): void
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<Props>()
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
cards: () => []
|
||||||
|
})
|
||||||
const emit = defineEmits<Emits>()
|
const emit = defineEmits<Emits>()
|
||||||
|
|
||||||
const formRef = ref<FormInstance>()
|
const formRef = ref<FormInstance>()
|
||||||
const loading = ref(false)
|
|
||||||
const confirmLoading = ref(false)
|
const confirmLoading = ref(false)
|
||||||
const bindingCards = ref<any[]>([])
|
|
||||||
|
|
||||||
const form = reactive({
|
const form = reactive({ target_iccid: '' })
|
||||||
target_iccid: ''
|
|
||||||
})
|
|
||||||
|
|
||||||
const rules: FormRules = {
|
const rules: FormRules = {
|
||||||
target_iccid: [{ required: true, message: '请选择目标ICCID', trigger: 'change' }]
|
target_iccid: [{ required: true, message: '请选择目标ICCID', trigger: 'change' }]
|
||||||
@@ -115,47 +109,24 @@
|
|||||||
set: (value) => emit('update:modelValue', value)
|
set: (value) => emit('update:modelValue', value)
|
||||||
})
|
})
|
||||||
|
|
||||||
// 监听对话框打开,加载绑定卡列表
|
const bindingCards = computed(() => props.cards || [])
|
||||||
watch(visible, async (newVal) => {
|
|
||||||
|
// 打开时重置选中
|
||||||
|
watch(visible, (newVal) => {
|
||||||
if (newVal) {
|
if (newVal) {
|
||||||
form.target_iccid = ''
|
form.target_iccid = ''
|
||||||
bindingCards.value = []
|
|
||||||
await loadDeviceCards()
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const loadDeviceCards = async () => {
|
|
||||||
if (!props.deviceId) return
|
|
||||||
|
|
||||||
try {
|
|
||||||
loading.value = true
|
|
||||||
const res = await DeviceService.getDeviceCards(props.deviceId)
|
|
||||||
if (res.code === 0 && res.data) {
|
|
||||||
bindingCards.value = res.data.bindings || []
|
|
||||||
if (bindingCards.value.length === 0) {
|
|
||||||
ElMessage.warning('该设备暂无绑定的SIM卡')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('加载设备绑定卡列表失败:', error)
|
|
||||||
console.log('加载卡列表失败')
|
|
||||||
} finally {
|
|
||||||
loading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleCancel = () => {
|
const handleCancel = () => {
|
||||||
visible.value = false
|
visible.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleConfirm = async () => {
|
const handleConfirm = async () => {
|
||||||
if (!formRef.value) return
|
if (!formRef.value) return
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await formRef.value.validate()
|
await formRef.value.validate()
|
||||||
emit('confirm', {
|
emit('confirm', { target_iccid: form.target_iccid })
|
||||||
target_iccid: form.target_iccid
|
|
||||||
})
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('表单验证失败:', error)
|
console.error('表单验证失败:', error)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ export function useAssetInfo() {
|
|||||||
series_id: data.series_id,
|
series_id: data.series_id,
|
||||||
series_name: data.series_name,
|
series_name: data.series_name,
|
||||||
real_name_status: data.real_name_status,
|
real_name_status: data.real_name_status,
|
||||||
|
real_name_at: data.real_name_at,
|
||||||
activated_at: data.activated_at,
|
activated_at: data.activated_at,
|
||||||
created_at: data.created_at,
|
created_at: data.created_at,
|
||||||
updated_at: data.updated_at,
|
updated_at: data.updated_at,
|
||||||
@@ -196,22 +197,32 @@ export function useAssetInfo() {
|
|||||||
status_name: pkg.status_name,
|
status_name: pkg.status_name,
|
||||||
duration_days: undefined, // API 没有返回时长
|
duration_days: undefined, // API 没有返回时长
|
||||||
virtual_ratio: pkg.virtual_ratio, // 虚流量比例
|
virtual_ratio: pkg.virtual_ratio, // 虚流量比例
|
||||||
package_type: pkg.package_type // 套餐类型
|
package_type: pkg.package_type, // 套餐类型
|
||||||
|
enable_virtual_data: pkg.enable_virtual_data // 是否启用虚流量
|
||||||
}
|
}
|
||||||
|
|
||||||
// 同时更新 cardInfo 中的流量显示信息
|
// 同时更新 cardInfo 中的流量显示信息(根据是否启用虚流量选择数据源)
|
||||||
|
const useVirtual = pkg.enable_virtual_data
|
||||||
cardInfo.value.current_package = pkg.package_name
|
cardInfo.value.current_package = pkg.package_name
|
||||||
cardInfo.value.packageTotalFlow = formatDataSize(pkg.virtual_limit_mb || 0)
|
cardInfo.value.packageTotalFlow = formatDataSize(
|
||||||
cardInfo.value.usedFlow = formatDataSize(pkg.virtual_used_mb || 0)
|
useVirtual ? pkg.virtual_limit_mb || 0 : pkg.data_limit_mb || 0
|
||||||
cardInfo.value.remainFlow = formatDataSize(pkg.virtual_remain_mb || 0)
|
)
|
||||||
|
cardInfo.value.usedFlow = formatDataSize(
|
||||||
|
useVirtual ? pkg.virtual_used_mb || 0 : pkg.data_usage_mb || 0
|
||||||
|
)
|
||||||
|
cardInfo.value.remainFlow = formatDataSize(
|
||||||
|
useVirtual
|
||||||
|
? pkg.virtual_remain_mb || 0
|
||||||
|
: (pkg.data_limit_mb || 0) - (pkg.data_usage_mb || 0)
|
||||||
|
)
|
||||||
|
const totalMb = useVirtual ? pkg.virtual_limit_mb || 0 : pkg.data_limit_mb || 0
|
||||||
|
const usedMb = useVirtual ? pkg.virtual_used_mb || 0 : pkg.data_usage_mb || 0
|
||||||
cardInfo.value.usedFlowPercentage =
|
cardInfo.value.usedFlowPercentage =
|
||||||
(pkg.virtual_limit_mb || 0) > 0
|
totalMb > 0 ? `${((usedMb / totalMb) * 100).toFixed(2)}%` : '0.00%'
|
||||||
? `${(((pkg.virtual_used_mb || 0) / (pkg.virtual_limit_mb || 1)) * 100).toFixed(2)}%`
|
|
||||||
: '0.00%'
|
|
||||||
} else {
|
} else {
|
||||||
// code 为 0 但 data 为空,表示暂无当前生效套餐(正常情况)
|
// code 为 0 但 data 为空,表示暂无当前生效套餐(正常情况)
|
||||||
currentPackage.value = null
|
currentPackage.value = null
|
||||||
currentPackageErrorMsg.value = '暂无当前生效套餐'
|
currentPackageErrorMsg.value = ''
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 接口返回 code 不为 0,保存错误信息
|
// 接口返回 code 不为 0,保存错误信息
|
||||||
@@ -222,7 +233,7 @@ export function useAssetInfo() {
|
|||||||
// 404表示无当前生效套餐,这是正常情况
|
// 404表示无当前生效套餐,这是正常情况
|
||||||
currentPackage.value = null
|
currentPackage.value = null
|
||||||
if (error?.response?.status === 404) {
|
if (error?.response?.status === 404) {
|
||||||
currentPackageErrorMsg.value = '暂无当前生效套餐'
|
currentPackageErrorMsg.value = ''
|
||||||
} else {
|
} else {
|
||||||
console.error('获取当前套餐失败:', error)
|
console.error('获取当前套餐失败:', error)
|
||||||
currentPackageErrorMsg.value = error?.message || '获取当前套餐失败'
|
currentPackageErrorMsg.value = error?.message || '获取当前套餐失败'
|
||||||
@@ -263,7 +274,20 @@ export function useAssetInfo() {
|
|||||||
cardInfo.value.device_protect_status = data.device_protect_status
|
cardInfo.value.device_protect_status = data.device_protect_status
|
||||||
}
|
}
|
||||||
if (data.cards && data.cards.length > 0) {
|
if (data.cards && data.cards.length > 0) {
|
||||||
cardInfo.value.cards = data.cards
|
// 合并实时卡数据与原始卡数据,保留 real_name_at 等静态字段
|
||||||
|
// 仅用实时接口中非 null/undefined 的字段覆盖,避免清掉 resolve 接口返回的静态数据
|
||||||
|
const existingCards: any[] = cardInfo.value.cards || []
|
||||||
|
cardInfo.value.cards = data.cards.map((realtimeCard: any) => {
|
||||||
|
const existing = existingCards.find((c: any) => c.iccid === realtimeCard.iccid)
|
||||||
|
if (!existing) return realtimeCard
|
||||||
|
const merged = { ...existing }
|
||||||
|
for (const [key, value] of Object.entries(realtimeCard)) {
|
||||||
|
if (value !== null && value !== undefined) {
|
||||||
|
merged[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return merged
|
||||||
|
})
|
||||||
}
|
}
|
||||||
if (data.online_status !== undefined) {
|
if (data.online_status !== undefined) {
|
||||||
cardInfo.value.online_status = data.online_status
|
cardInfo.value.online_status = data.online_status
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
@enable-binding-card="handleEnableBindingCard"
|
@enable-binding-card="handleEnableBindingCard"
|
||||||
@disable-binding-card="handleDisableBindingCard"
|
@disable-binding-card="handleDisableBindingCard"
|
||||||
@navigate-to-card="handleNavigateToCard"
|
@navigate-to-card="handleNavigateToCard"
|
||||||
|
@navigate-to-device="handleNavigateToDevice"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -36,31 +37,40 @@
|
|||||||
:current-package="currentPackage"
|
:current-package="currentPackage"
|
||||||
:loading="currentPackageLoading"
|
:loading="currentPackageLoading"
|
||||||
:error-msg="currentPackageErrorMsg"
|
:error-msg="currentPackageErrorMsg"
|
||||||
|
:bound-device-id="cardInfo.bound_device_id"
|
||||||
|
:bound-device-no="cardInfo.bound_device_no"
|
||||||
|
:bound-device-name="cardInfo.bound_device_name"
|
||||||
@show-recharge="showRechargeDialog"
|
@show-recharge="showRechargeDialog"
|
||||||
|
@navigate-to-device="handleNavigateToDevice"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 第三行:钱包概况和套餐列表 -->
|
<!-- 第三行:套餐列表 -->
|
||||||
<div class="row two-columns">
|
<div class="row full-width">
|
||||||
<div class="col">
|
<PackageListCard
|
||||||
<WalletOverviewCard :wallet-info="walletInfo" :loading="walletLoading" />
|
:package-list="packageList"
|
||||||
</div>
|
:total="packagePagination.total"
|
||||||
<div class="col">
|
:page="packagePagination.page"
|
||||||
<PackageListCard
|
:page-size="packagePagination.pageSize"
|
||||||
:package-list="packageList"
|
:bound-device-id="cardInfo.bound_device_id"
|
||||||
:total="packagePagination.total"
|
:bound-device-no="cardInfo.bound_device_no"
|
||||||
:page="packagePagination.page"
|
@show-daily-records="handleShowDailyRecords"
|
||||||
:page-size="packagePagination.pageSize"
|
@page-change="handlePackagePageChange"
|
||||||
@show-daily-records="handleShowDailyRecords"
|
@size-change="handlePackageSizeChange"
|
||||||
@page-change="handlePackagePageChange"
|
@navigate-to-device="handleNavigateToDevice"
|
||||||
@size-change="handlePackageSizeChange"
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 第四行:钱包流水 -->
|
<!-- 第四行:钱包(概况+流水) -->
|
||||||
<div class="row full-width">
|
<div class="row full-width">
|
||||||
<WalletTransactionCard :asset-identifier="cardInfo.identifier" />
|
<WalletTransactionCard
|
||||||
|
:asset-identifier="cardInfo.identifier"
|
||||||
|
:wallet-info="walletInfo"
|
||||||
|
:wallet-loading="walletLoading"
|
||||||
|
:bound-device-id="cardInfo.bound_device_id"
|
||||||
|
:bound-device-no="cardInfo.bound_device_no"
|
||||||
|
@navigate-to-device="handleNavigateToDevice"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -75,12 +85,12 @@
|
|||||||
<!-- 对话框组件 -->
|
<!-- 对话框组件 -->
|
||||||
<SwitchCardDialog
|
<SwitchCardDialog
|
||||||
v-model="switchCardDialogVisible"
|
v-model="switchCardDialogVisible"
|
||||||
:device-id="cardInfo?.id"
|
:cards="cardInfo?.cards"
|
||||||
@confirm="handleConfirmSwitchCard"
|
@confirm="handleConfirmSwitchCard"
|
||||||
/>
|
/>
|
||||||
<WiFiConfigDialog
|
<WiFiConfigDialog
|
||||||
v-model="setWiFiDialogVisible"
|
v-model="setWiFiDialogVisible"
|
||||||
:card-no="cardInfo?.iccid"
|
:card-no="deviceRealtime?.current_iccid"
|
||||||
@confirm="handleConfirmSetWiFi"
|
@confirm="handleConfirmSetWiFi"
|
||||||
/>
|
/>
|
||||||
<PackageRechargeDialog
|
<PackageRechargeDialog
|
||||||
@@ -103,7 +113,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, watch, onMounted } from 'vue'
|
import { ref, watch, onMounted, nextTick } from 'vue'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
import { ElMessage, ElCard, ElEmpty } from 'element-plus'
|
import { ElMessage, ElCard, ElEmpty } from 'element-plus'
|
||||||
|
|
||||||
@@ -111,7 +121,6 @@
|
|||||||
import AssetSearchCard from './components/AssetSearchCard.vue'
|
import AssetSearchCard from './components/AssetSearchCard.vue'
|
||||||
import BasicInfoCard from './components/BasicInfoCard.vue'
|
import BasicInfoCard from './components/BasicInfoCard.vue'
|
||||||
import CurrentPackageCard from './components/CurrentPackageCard.vue'
|
import CurrentPackageCard from './components/CurrentPackageCard.vue'
|
||||||
import WalletOverviewCard from './components/WalletOverviewCard.vue'
|
|
||||||
import PackageListCard from './components/PackageListCard.vue'
|
import PackageListCard from './components/PackageListCard.vue'
|
||||||
import WalletTransactionCard from './components/WalletTransactionCard.vue'
|
import WalletTransactionCard from './components/WalletTransactionCard.vue'
|
||||||
|
|
||||||
@@ -189,7 +198,9 @@
|
|||||||
|
|
||||||
// ========== 轮询状态 ==========
|
// ========== 轮询状态 ==========
|
||||||
const pollingEnabled = ref(false)
|
const pollingEnabled = ref(false)
|
||||||
|
let pollingInitializing = false
|
||||||
watch(pollingEnabled, async (val) => {
|
watch(pollingEnabled, async (val) => {
|
||||||
|
if (pollingInitializing) return
|
||||||
await updatePollingStatus(val)
|
await updatePollingStatus(val)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -205,6 +216,12 @@
|
|||||||
// fetchAssetDetail 内部已经会并行加载所有相关数据,不需要重复调用
|
// fetchAssetDetail 内部已经会并行加载所有相关数据,不需要重复调用
|
||||||
await fetchAssetDetail(identifier)
|
await fetchAssetDetail(identifier)
|
||||||
|
|
||||||
|
// 从接口数据初始化轮询开关,不触发更新接口
|
||||||
|
pollingInitializing = true
|
||||||
|
pollingEnabled.value = cardInfo.value?.enable_polling ?? false
|
||||||
|
await nextTick()
|
||||||
|
pollingInitializing = false
|
||||||
|
|
||||||
// 重置分页并加载套餐列表
|
// 重置分页并加载套餐列表
|
||||||
packagePagination.value.page = 1
|
packagePagination.value.page = 1
|
||||||
const result = await loadPackageList(identifier, 1, packagePagination.value.pageSize)
|
const result = await loadPackageList(identifier, 1, packagePagination.value.pageSize)
|
||||||
@@ -224,7 +241,6 @@
|
|||||||
*/
|
*/
|
||||||
const handleEnableCard = async () => {
|
const handleEnableCard = async () => {
|
||||||
await enableCardOp()
|
await enableCardOp()
|
||||||
await handleRefresh()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -232,7 +248,6 @@
|
|||||||
*/
|
*/
|
||||||
const handleDisableCard = async () => {
|
const handleDisableCard = async () => {
|
||||||
await disableCardOp()
|
await disableCardOp()
|
||||||
await handleRefresh()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -240,7 +255,6 @@
|
|||||||
*/
|
*/
|
||||||
const handleManualDeactivate = async () => {
|
const handleManualDeactivate = async () => {
|
||||||
await manualDeactivateCardOp()
|
await manualDeactivateCardOp()
|
||||||
await handleRefresh()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -248,7 +262,6 @@
|
|||||||
*/
|
*/
|
||||||
const handleRebootDevice = async () => {
|
const handleRebootDevice = async () => {
|
||||||
await rebootDeviceOp()
|
await rebootDeviceOp()
|
||||||
await handleRefresh()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -256,7 +269,6 @@
|
|||||||
*/
|
*/
|
||||||
const handleResetDevice = async () => {
|
const handleResetDevice = async () => {
|
||||||
await resetDeviceOp()
|
await resetDeviceOp()
|
||||||
await handleRefresh()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -272,7 +284,6 @@
|
|||||||
const handleConfirmSwitchCard = async (form: SwitchCardForm) => {
|
const handleConfirmSwitchCard = async (form: SwitchCardForm) => {
|
||||||
await switchCardOp(form)
|
await switchCardOp(form)
|
||||||
switchCardDialogVisible.value = false
|
switchCardDialogVisible.value = false
|
||||||
await handleRefresh()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -288,7 +299,6 @@
|
|||||||
const handleConfirmSetWiFi = async (form: WiFiForm) => {
|
const handleConfirmSetWiFi = async (form: WiFiForm) => {
|
||||||
await setWiFiOp(form)
|
await setWiFiOp(form)
|
||||||
setWiFiDialogVisible.value = false
|
setWiFiDialogVisible.value = false
|
||||||
await handleRefresh()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -297,11 +307,11 @@
|
|||||||
const handleEnableBindingCard = async (payload: { card: any }) => {
|
const handleEnableBindingCard = async (payload: { card: any }) => {
|
||||||
const { card } = payload
|
const { card } = payload
|
||||||
try {
|
try {
|
||||||
const { CardService } = await import('@/api/modules')
|
const { AssetService } = await import('@/api/modules')
|
||||||
await CardService.enableIotCard(card.iccid)
|
await AssetService.startAsset(card.iccid)
|
||||||
ElMessage.success('启用成功')
|
ElMessage.success('启用成功')
|
||||||
// 刷新资产信息
|
if (cardInfo.value)
|
||||||
await handleRefresh()
|
await loadRealtimeStatus(cardInfo.value.identifier, cardInfo.value.asset_type)
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('启用绑定卡失败:', error)
|
console.error('启用绑定卡失败:', error)
|
||||||
}
|
}
|
||||||
@@ -313,11 +323,11 @@
|
|||||||
const handleDisableBindingCard = async (payload: { card: any }) => {
|
const handleDisableBindingCard = async (payload: { card: any }) => {
|
||||||
const { card } = payload
|
const { card } = payload
|
||||||
try {
|
try {
|
||||||
const { CardService } = await import('@/api/modules')
|
const { AssetService } = await import('@/api/modules')
|
||||||
await CardService.disableIotCard(card.iccid)
|
await AssetService.stopAsset(card.iccid)
|
||||||
ElMessage.success('停用成功')
|
ElMessage.success('停用成功')
|
||||||
// 刷新资产信息
|
if (cardInfo.value)
|
||||||
await handleRefresh()
|
await loadRealtimeStatus(cardInfo.value.identifier, cardInfo.value.asset_type)
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('停用绑定卡失败:', error)
|
console.error('停用绑定卡失败:', error)
|
||||||
}
|
}
|
||||||
@@ -393,6 +403,13 @@
|
|||||||
await handleSearch({ identifier: iccid })
|
await handleSearch({ identifier: iccid })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 跳转到绑定设备信息
|
||||||
|
*/
|
||||||
|
const handleNavigateToDevice = async (deviceNo: string) => {
|
||||||
|
await handleSearch({ identifier: deviceNo })
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* URL参数自动加载
|
* URL参数自动加载
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -88,6 +88,7 @@ export interface PackageInfo {
|
|||||||
duration_days?: number
|
duration_days?: number
|
||||||
virtual_ratio?: number // 虚流量比例
|
virtual_ratio?: number // 虚流量比例
|
||||||
package_type?: string // 套餐类型: formal(正式套餐)/ addon(加油包)
|
package_type?: string // 套餐类型: formal(正式套餐)/ addon(加油包)
|
||||||
|
enable_virtual_data?: boolean // 是否启用虚流量
|
||||||
}
|
}
|
||||||
|
|
||||||
// 钱包信息接口 (使用已有的 AssetWalletResponse)
|
// 钱包信息接口 (使用已有的 AssetWalletResponse)
|
||||||
|
|||||||
Reference in New Issue
Block a user