fix(operator): fix operator edit issue
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 5m51s

This commit is contained in:
sexygoat
2026-04-10 16:54:53 +08:00
parent 8b30341d50
commit 90ae585651
10 changed files with 556 additions and 426 deletions

37
opencode.json Normal file
View 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"
]
}
}
}

View File

@@ -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加油包时有值

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>()

View File

@@ -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;
} }
} }
} }

View File

@@ -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)
} }

View File

@@ -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

View File

@@ -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参数自动加载
*/ */

View File

@@ -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)