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

This commit is contained in:
sexygoat
2026-04-10 15:06:28 +08:00
parent 2b3119c549
commit 8b30341d50
8 changed files with 320 additions and 86 deletions

View File

@@ -34,6 +34,12 @@
<ElDescriptionsItem label="套餐总量"> <ElDescriptionsItem label="套餐总量">
{{ formatDataSize(currentPackage.package_total_data || 0) }} {{ formatDataSize(currentPackage.package_total_data || 0) }}
</ElDescriptionsItem> </ElDescriptionsItem>
<ElDescriptionsItem label="套餐类型">
{{ getPackageTypeName(currentPackage.package_type) }}
</ElDescriptionsItem>
<ElDescriptionsItem label="虚流量比例">
{{ currentPackage.virtual_ratio || '-' }}
</ElDescriptionsItem>
<!-- 套餐时长 --> <!-- 套餐时长 -->
<ElDescriptionsItem v-if="currentPackage.duration_days" label="套餐时长" :span="3"> <ElDescriptionsItem v-if="currentPackage.duration_days" label="套餐时长" :span="3">
@@ -165,6 +171,15 @@
const handleShowRecharge = () => { const handleShowRecharge = () => {
emit('showRecharge') emit('showRecharge')
} }
// 套餐类型名称
const getPackageTypeName = (type?: string) => {
const typeMap: Record<string, string> = {
formal: '正式套餐',
addon: '加油包'
}
return typeMap[type || ''] || '-'
}
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">

View File

@@ -6,14 +6,13 @@
</div> </div>
</template> </template>
<div class="package-table-wrapper"> <div class="package-table-wrapper">
<ElTable <div v-if="packageList && packageList.length > 0" class="table-scroll-container">
v-if="packageList && packageList.length > 0" <ElTable
:data="packageList" :data="packageList"
class="package-table" class="package-table"
border border
max-height="400" >
> <ElTableColumn label="套餐名称" width="150">
<ElTableColumn label="套餐名称" min-width="150">
<template #default="scope"> <template #default="scope">
<ElButton <ElButton
type="primary" type="primary"
@@ -26,9 +25,9 @@
</ElButton> </ElButton>
</template> </template>
</ElTableColumn> </ElTableColumn>
<ElTableColumn prop="package_price" label="套餐价格" width="120"> <ElTableColumn label="套餐价格" width="120">
<template #default="scope"> <template #default="scope">
{{ formatAmount(scope.row.package_price) }} {{ formatAmount(scope.row.package_price || 0) }}
</template> </template>
</ElTableColumn> </ElTableColumn>
<ElTableColumn prop="status_name" label="套餐状态" width="100"> <ElTableColumn prop="status_name" label="套餐状态" width="100">
@@ -111,13 +110,25 @@
</ElButton> </ElButton>
</template> </template>
</ElTableColumn> </ElTableColumn>
</ElTable> </ElTable>
<ElPagination
v-model:current-page="pagination.page"
v-model:page-size="pagination.pageSize"
:total="pagination.total"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
class="pagination"
@current-change="handlePageChange"
@size-change="handleSizeChange"
/>
</div>
<ElEmpty v-else description="暂无套餐数据" :image-size="100" /> <ElEmpty v-else description="暂无套餐数据" :image-size="100" />
</div> </div>
</ElCard> </ElCard>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, watch } from 'vue'
import { import {
ElCard, ElCard,
ElTable, ElTable,
@@ -125,41 +136,55 @@
ElTag, ElTag,
ElButton, ElButton,
ElProgress, ElProgress,
ElEmpty ElEmpty,
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'
// Props // Props 使用 API 类型
interface PackageInfo { type PackageInfo = AssetPackageUsageRecord
id: number
package_id: number
package_name: string
package_price: number
data_limit_mb: number
data_usage_mb: number
virtual_limit_mb: number
virtual_used_mb: number
virtual_remain_mb: number
activated_at: string
expires_at: string
status: number
status_name: string
}
interface Props { interface Props {
packageList: PackageInfo[] packageList: PackageInfo[]
total?: number
page?: number
pageSize?: number
} }
defineProps<Props>() const props = withDefaults(defineProps<Props>(), {
total: 0,
page: 1,
pageSize: 10
})
// Emits // Emits
interface Emits { interface Emits {
(e: 'showDailyRecords', payload: { packageRow: PackageInfo }): void (e: 'showDailyRecords', payload: { packageRow: PackageInfo }): void
(e: 'page-change', page: number): void
(e: 'size-change', size: number): void
} }
const emit = defineEmits<Emits>() const emit = defineEmits<Emits>()
// 分页状态
const pagination = ref({
page: props.page,
pageSize: props.pageSize,
total: props.total
})
// 监听props变化更新分页
watch(
() => [props.page, props.pageSize, props.total],
([newPage, newPageSize, newTotal]) => {
pagination.value.page = newPage
pagination.value.pageSize = newPageSize
pagination.value.total = newTotal
}
)
// 使用格式化工具 // 使用格式化工具
const { const {
formatDataSize, formatDataSize,
@@ -173,12 +198,30 @@
const handleShowDailyRecords = (packageRow: PackageInfo) => { const handleShowDailyRecords = (packageRow: PackageInfo) => {
emit('showDailyRecords', { packageRow }) emit('showDailyRecords', { packageRow })
} }
// 页码变化
const handlePageChange = (page: number) => {
emit('page-change', page)
}
// 每页条数变化
const handleSizeChange = (size: number) => {
emit('size-change', size)
}
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.info-card { .info-card {
&.package-info { &.package-info {
width: 100%;
overflow: hidden; // 防止内容溢出
.package-table-wrapper { .package-table-wrapper {
.table-scroll-container {
overflow-x: auto;
width: 100%;
}
.package-table { .package-table {
:deep(.el-table__header) { :deep(.el-table__header) {
th { th {
@@ -215,6 +258,11 @@
} }
} }
} }
.pagination {
margin-top: 16px;
justify-content: flex-end;
}
} }
} }
</style> </style>

View File

@@ -47,6 +47,26 @@
</template> </template>
</div> </div>
</ElFormItem> </ElFormItem>
<ElFormItem
v-if="form.payment_method === 'offline'"
label="支付凭证"
prop="payment_voucher_key"
>
<ElUpload
ref="uploadRef"
:auto-upload="false"
:on-change="handleVoucherFileChange"
:on-remove="handleRemoveVoucher"
accept="image/*"
list-type="picture-card"
:limit="1"
>
<el-icon><Plus /></el-icon>
<template #tip>
<div class="el-upload__tip">支持 jpgpng 格式文件大小不超过 5MB</div>
</template>
</ElUpload>
</ElFormItem>
</ElForm> </ElForm>
<template #footer> <template #footer>
<div class="dialog-footer"> <div class="dialog-footer">
@@ -66,10 +86,13 @@
ElSelect, ElSelect,
ElOption, ElOption,
ElButton, ElButton,
ElMessage ElMessage,
ElUpload,
ElIcon
} from 'element-plus' } from 'element-plus'
import type { FormInstance, FormRules } from 'element-plus' import { Plus } from '@element-plus/icons-vue'
import { OrderService, PackageManageService } from '@/api/modules' import type { FormInstance, FormRules, UploadFile, UploadInstance } from 'element-plus'
import { OrderService, PackageManageService, StorageService } from '@/api/modules'
import type { CreateOrderRequest, PackageResponse } from '@/types/api' import type { CreateOrderRequest, PackageResponse } from '@/types/api'
import { useUserStore } from '@/store/modules/user' import { useUserStore } from '@/store/modules/user'
@@ -91,13 +114,15 @@
const userStore = useUserStore() const userStore = useUserStore()
const formRef = ref<FormInstance>() const formRef = ref<FormInstance>()
const uploadRef = ref<UploadInstance>()
const loading = ref(false) const loading = ref(false)
const packageSearchLoading = ref(false) const packageSearchLoading = ref(false)
const packageOptions = ref<PackageResponse[]>([]) const packageOptions = ref<PackageResponse[]>([])
const form = reactive({ const form = reactive({
package_id: undefined as number | undefined, package_id: undefined as number | undefined,
payment_method: 'wallet' as 'wallet' | 'offline' payment_method: 'wallet' as 'wallet' | 'offline',
payment_voucher_key: undefined as string | undefined
}) })
const rules: FormRules = { const rules: FormRules = {
@@ -172,6 +197,64 @@
} }
} }
// 处理支付凭证文件变化
const handleVoucherFileChange = async (uploadFile: UploadFile) => {
const file = uploadFile.raw
if (!file) return
// 验证文件类型
const isImage = file.type.startsWith('image/')
if (!isImage) {
ElMessage.error('只能上传图片文件!')
uploadRef.value?.clearFiles()
return
}
// 验证文件大小
const isLt5M = file.size / 1024 / 1024 < 5
if (!isLt5M) {
ElMessage.error('图片大小不能超过 5MB!')
uploadRef.value?.clearFiles()
return
}
// 开始上传
try {
ElMessage.info('正在上传支付凭证...')
// 1. 获取上传 URL
const uploadUrlRes = await StorageService.getUploadUrl({
file_name: file.name,
content_type: file.type,
purpose: 'attachment'
})
if (uploadUrlRes.code !== 0) {
ElMessage.error(uploadUrlRes.msg || '获取上传地址失败')
uploadRef.value?.clearFiles()
return
}
const { upload_url, file_key } = uploadUrlRes.data
// 2. 上传文件到对象存储
await StorageService.uploadFile(upload_url, file, file.type)
// 3. 保存 file_key
form.payment_voucher_key = file_key
ElMessage.success('支付凭证上传成功')
} catch (error: any) {
console.error('上传支付凭证失败:', error)
ElMessage.error(error?.message || '上传失败')
uploadRef.value?.clearFiles()
}
}
// 移除支付凭证
const handleRemoveVoucher = () => {
form.payment_voucher_key = undefined
}
const handleCancel = () => { const handleCancel = () => {
visible.value = false visible.value = false
} }
@@ -187,6 +270,12 @@
return return
} }
// 线下支付时验证支付凭证
if (form.payment_method === 'offline' && !form.payment_voucher_key) {
ElMessage.error('线下支付需要上传支付凭证')
return
}
loading.value = true loading.value = true
// 创建订单(后端接收数组,前端单选转为单元素数组) // 创建订单(后端接收数组,前端单选转为单元素数组)
@@ -196,6 +285,11 @@
payment_method: form.payment_method payment_method: form.payment_method
} }
// 线下支付时,添加支付凭证
if (form.payment_method === 'offline' && form.payment_voucher_key) {
data.payment_voucher_key = form.payment_voucher_key
}
await OrderService.createOrder(data) await OrderService.createOrder(data)
// 根据支付方式显示不同的成功消息 // 根据支付方式显示不同的成功消息
@@ -214,7 +308,6 @@
return return
} }
console.error('创建订单失败:', error) console.error('创建订单失败:', error)
ElMessage.error(error?.message || '创建订单失败')
} finally { } finally {
loading.value = false loading.value = false
} }
@@ -224,7 +317,9 @@
formRef.value?.resetFields() formRef.value?.resetFields()
form.package_id = undefined form.package_id = undefined
form.payment_method = 'wallet' form.payment_method = 'wallet'
form.payment_voucher_key = undefined
packageOptions.value = [] packageOptions.value = []
uploadRef.value?.clearFiles()
} }
</script> </script>

View File

@@ -50,11 +50,12 @@
interface Props { interface Props {
modelValue: boolean modelValue: boolean
cardNo?: string // 流量卡号ICCID
} }
interface Emits { interface Emits {
(e: 'update:modelValue', value: boolean): void (e: 'update:modelValue', value: boolean): void
(e: 'confirm', data: { enabled: number; ssid: string; password: string }): void (e: 'confirm', data: { card_no: string; enabled: number; ssid: string; password: string }): void
} }
const props = defineProps<Props>() const props = defineProps<Props>()
@@ -104,6 +105,7 @@
try { try {
await formRef.value.validate() await formRef.value.validate()
emit('confirm', { emit('confirm', {
card_no: props.cardNo || '',
enabled: form.enabled, enabled: form.enabled,
ssid: form.ssid, ssid: form.ssid,
password: form.password password: form.password

View File

@@ -42,11 +42,15 @@ export function useAssetInfo() {
if (response.code === 0 && response.data) { if (response.code === 0 && response.data) {
const data = response.data const data = response.data
// 获取资产标识符
const assetIdentifier = data.asset_type === 'card' ? data.iccid : data.virtual_no
// 映射API数据到页面显示格式 // 映射API数据到页面显示格式
cardInfo.value = { cardInfo.value = {
// 基本信息 // 基本信息
asset_type: data.asset_type, // card 或 device asset_type: data.asset_type, // card 或 device
asset_id: data.asset_id, asset_id: data.asset_id,
identifier: assetIdentifier || '', // 资产标识符卡用iccid设备用virtual_no
virtual_no: data.virtual_no, virtual_no: data.virtual_no,
status: data.status, status: data.status,
batch_no: data.batch_no, batch_no: data.batch_no,
@@ -107,13 +111,12 @@ export function useAssetInfo() {
packageList: [] // 套餐列表需要单独调用API获取 packageList: [] // 套餐列表需要单独调用API获取
} }
// 获取资产标识符并加载其他数据 // 加载其他数据
const assetIdentifier = data.asset_type === 'card' ? data.iccid : data.virtual_no
if (assetIdentifier) { if (assetIdentifier) {
// 并行调用多个接口: 当前生效套餐、套餐列表、实时状态、钱包概况 // 并行调用多个接口: 当前生效套餐、实时状态、钱包概况
// 套餐列表不在这里调用,由父组件调用以便管理分页状态
await Promise.all([ await Promise.all([
loadCurrentPackage(assetIdentifier), loadCurrentPackage(assetIdentifier),
loadPackageList(assetIdentifier),
loadRealtimeStatus(assetIdentifier, data.asset_type), loadRealtimeStatus(assetIdentifier, data.asset_type),
loadAssetWallet(assetIdentifier) loadAssetWallet(assetIdentifier)
]) ])
@@ -123,14 +126,6 @@ export function useAssetInfo() {
cardInfo.value = null cardInfo.value = null
} }
} catch (error: any) { } catch (error: any) {
console.error('获取卡片信息失败:', error)
if (error?.response?.status === 404) {
ElMessage.error('未找到该资产')
} else if (error?.response?.status === 403) {
ElMessage.error('无权限访问该资产')
} else {
ElMessage.error(error?.message || '获取卡片信息失败')
}
cardInfo.value = null cardInfo.value = null
} finally { } finally {
loading.value = false loading.value = false
@@ -140,19 +135,30 @@ export function useAssetInfo() {
/** /**
* 加载套餐列表 * 加载套餐列表
* @param identifier - 资产标识符 * @param identifier - 资产标识符
* @param page - 页码
* @param pageSize - 每页条数
*/ */
const loadPackageList = async (identifier: string) => { const loadPackageList = async (identifier: string, page: number = 1, pageSize: number = 10) => {
try { try {
const response = await AssetService.getAssetPackages(identifier, { const response = await AssetService.getAssetPackages(identifier, {
page: 1, page,
page_size: 50 page_size: pageSize
}) })
if (response.code === 0 && response.data) { if (response.code === 0 && response.data) {
// 使用分页响应中的 items 数组 // 使用分页响应中的 items 数组
cardInfo.value.packageList = response.data.items || [] packageList.value = response.data.items || []
// 返回分页信息供外部使用
return {
items: response.data.items || [],
total: response.data.total || 0,
page: response.data.page || 1,
pageSize: response.data.page_size || pageSize
}
} }
return { items: [], total: 0, page: 1, pageSize }
} catch (error) { } catch (error) {
console.error('获取套餐列表失败:', error) console.error('获取套餐列表失败:', error)
return { items: [], total: 0, page: 1, pageSize }
} }
} }
@@ -170,28 +176,30 @@ export function useAssetInfo() {
if (response.code === 0) { if (response.code === 0) {
if (response.data) { if (response.data) {
const pkg = response.data const pkg = response.data
// 保存完整的当前套餐数据
cardInfo.value.currentPackageDetail = { // 转换为组件期望的格式
package_usage_id: pkg.package_usage_id, currentPackage.value = {
package_id: pkg.package_id, id: pkg.package_usage_id || 0,
package_name: pkg.package_name, package_id: pkg.package_id || 0,
package_type: pkg.package_type, package_name: pkg.package_name || '',
status: pkg.status, package_price: 0, // API 没有返回价格
package_total_data: pkg.data_limit_mb || 0,
real_data_used: pkg.data_usage_mb || 0,
real_data_remaining: (pkg.data_limit_mb || 0) - (pkg.data_usage_mb || 0),
real_data_total: pkg.data_limit_mb || 0,
virtual_data_used: pkg.virtual_used_mb || 0,
virtual_data_remaining: pkg.virtual_remain_mb || 0,
virtual_data_total: pkg.virtual_limit_mb || 0,
start_time: pkg.activated_at || '',
expire_time: pkg.expires_at || '',
status: pkg.status || 0,
status_name: pkg.status_name, status_name: pkg.status_name,
data_limit_mb: pkg.data_limit_mb, duration_days: undefined, // API 没有返回时长
data_usage_mb: pkg.data_usage_mb, virtual_ratio: pkg.virtual_ratio, // 虚流量比例
virtual_limit_mb: pkg.virtual_limit_mb, package_type: pkg.package_type // 套餐类型
virtual_used_mb: pkg.virtual_used_mb,
virtual_remain_mb: pkg.virtual_remain_mb,
virtual_ratio: pkg.virtual_ratio,
activated_at: pkg.activated_at,
expires_at: pkg.expires_at,
created_at: pkg.created_at,
master_usage_id: pkg.master_usage_id,
priority: pkg.priority,
usage_type: pkg.usage_type
} }
// 同时更新流量显示信息
// 同时更新 cardInfo 中的流量显示信息
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(pkg.virtual_limit_mb || 0)
cardInfo.value.usedFlow = formatDataSize(pkg.virtual_used_mb || 0) cardInfo.value.usedFlow = formatDataSize(pkg.virtual_used_mb || 0)
@@ -202,14 +210,17 @@ export function useAssetInfo() {
: '0.00%' : '0.00%'
} else { } else {
// code 为 0 但 data 为空,表示暂无当前生效套餐(正常情况) // code 为 0 但 data 为空,表示暂无当前生效套餐(正常情况)
currentPackage.value = null
currentPackageErrorMsg.value = '暂无当前生效套餐' currentPackageErrorMsg.value = '暂无当前生效套餐'
} }
} else { } else {
// 接口返回 code 不为 0,保存错误信息 // 接口返回 code 不为 0,保存错误信息
currentPackage.value = null
currentPackageErrorMsg.value = response.msg || '获取当前套餐失败' currentPackageErrorMsg.value = response.msg || '获取当前套餐失败'
} }
} catch (error: any) { } catch (error: any) {
// 404表示无当前生效套餐,这是正常情况 // 404表示无当前生效套餐,这是正常情况
currentPackage.value = null
if (error?.response?.status === 404) { if (error?.response?.status === 404) {
currentPackageErrorMsg.value = '暂无当前生效套餐' currentPackageErrorMsg.value = '暂无当前生效套餐'
} else { } else {
@@ -319,11 +330,7 @@ export function useAssetInfo() {
await loadRealtimeStatus(identifier, assetType) await loadRealtimeStatus(identifier, assetType)
} }
} catch (error: any) { } catch (error: any) {
// 检查 429 错误(冷却期) console.log(error)
if (error?.response?.status === 429) {
ElMessage.warning('刷新过于频繁,请稍后再试')
}
throw error
} }
} }

View File

@@ -39,7 +39,6 @@ export function useAssetOperations(cardInfo: Ref<any>, refreshAssetFn?: () => Pr
} catch (error: any) { } catch (error: any) {
if (error !== 'cancel') { if (error !== 'cancel') {
console.error('启用失败:', error) console.error('启用失败:', error)
ElMessage.error(error?.message || '启用失败')
} }
} finally { } finally {
enableCardLoading.value = false enableCardLoading.value = false
@@ -66,7 +65,6 @@ export function useAssetOperations(cardInfo: Ref<any>, refreshAssetFn?: () => Pr
} catch (error: any) { } catch (error: any) {
if (error !== 'cancel') { if (error !== 'cancel') {
console.error('停用失败:', error) console.error('停用失败:', error)
ElMessage.error(error?.message || '停用失败')
} }
} finally { } finally {
disableCardLoading.value = false disableCardLoading.value = false
@@ -128,7 +126,6 @@ export function useAssetOperations(cardInfo: Ref<any>, refreshAssetFn?: () => Pr
} catch (error: any) { } catch (error: any) {
if (error !== 'cancel') { if (error !== 'cancel') {
console.error('重启失败:', error) console.error('重启失败:', error)
ElMessage.error(error?.message || '重启失败')
} }
} finally { } finally {
rebootDeviceLoading.value = false rebootDeviceLoading.value = false
@@ -157,7 +154,6 @@ export function useAssetOperations(cardInfo: Ref<any>, refreshAssetFn?: () => Pr
} catch (error: any) { } catch (error: any) {
if (error !== 'cancel') { if (error !== 'cancel') {
console.error('恢复出厂失败:', error) console.error('恢复出厂失败:', error)
ElMessage.error(error?.message || '恢复出厂失败')
} }
} finally { } finally {
resetDeviceLoading.value = false resetDeviceLoading.value = false
@@ -224,10 +220,16 @@ export function useAssetOperations(cardInfo: Ref<any>, refreshAssetFn?: () => Pr
/** /**
* Configure WiFi settings * Configure WiFi settings
*/ */
const setWiFi = async (form: { enabled: number; ssid: string; password: string }) => { const setWiFi = async (form: {
card_no: string
enabled: number
ssid: string
password: string
}) => {
try { try {
setWiFiLoading.value = true setWiFiLoading.value = true
const res = await DeviceService.setWiFi(cardInfo.value.imei, { const res = await DeviceService.setWiFi(cardInfo.value.imei, {
card_no: form.card_no,
enabled: form.enabled, enabled: form.enabled,
ssid: form.ssid, ssid: form.ssid,
password: form.password password: form.password

View File

@@ -48,7 +48,12 @@
<div class="col"> <div class="col">
<PackageListCard <PackageListCard
:package-list="packageList" :package-list="packageList"
:total="packagePagination.total"
:page="packagePagination.page"
:page-size="packagePagination.pageSize"
@show-daily-records="handleShowDailyRecords" @show-daily-records="handleShowDailyRecords"
@page-change="handlePackagePageChange"
@size-change="handlePackageSizeChange"
/> />
</div> </div>
</div> </div>
@@ -73,7 +78,11 @@
:device-id="cardInfo?.id" :device-id="cardInfo?.id"
@confirm="handleConfirmSwitchCard" @confirm="handleConfirmSwitchCard"
/> />
<WiFiConfigDialog v-model="setWiFiDialogVisible" @confirm="handleConfirmSetWiFi" /> <WiFiConfigDialog
v-model="setWiFiDialogVisible"
:card-no="cardInfo?.iccid"
@confirm="handleConfirmSetWiFi"
/>
<PackageRechargeDialog <PackageRechargeDialog
v-model="rechargeDialogVisible" v-model="rechargeDialogVisible"
:asset-identifier="cardInfo?.identifier" :asset-identifier="cardInfo?.identifier"
@@ -171,6 +180,13 @@
const orderHistoryDialogVisible = ref(false) const orderHistoryDialogVisible = ref(false)
const selectedPackageUsageId = ref<number | null>(null) const selectedPackageUsageId = ref<number | null>(null)
// ========== 套餐列表分页状态 ==========
const packagePagination = ref({
page: 1,
pageSize: 10,
total: 0
})
// ========== 轮询状态 ========== // ========== 轮询状态 ==========
const pollingEnabled = ref(false) const pollingEnabled = ref(false)
watch(pollingEnabled, async (val) => { watch(pollingEnabled, async (val) => {
@@ -188,6 +204,11 @@
// fetchAssetDetail 内部已经会并行加载所有相关数据,不需要重复调用 // fetchAssetDetail 内部已经会并行加载所有相关数据,不需要重复调用
await fetchAssetDetail(identifier) await fetchAssetDetail(identifier)
// 重置分页并加载套餐列表
packagePagination.value.page = 1
const result = await loadPackageList(identifier, 1, packagePagination.value.pageSize)
packagePagination.value.total = result.total
} }
/** /**
@@ -283,7 +304,6 @@
await handleRefresh() await handleRefresh()
} catch (error: any) { } catch (error: any) {
console.error('启用绑定卡失败:', error) console.error('启用绑定卡失败:', error)
ElMessage.error(error?.message || '启用失败')
} }
} }
@@ -300,7 +320,6 @@
await handleRefresh() await handleRefresh()
} catch (error: any) { } catch (error: any) {
console.error('停用绑定卡失败:', error) console.error('停用绑定卡失败:', error)
ElMessage.error(error?.message || '停用失败')
} }
} }
@@ -317,8 +336,15 @@
const handleRechargeSuccess = async () => { const handleRechargeSuccess = async () => {
rechargeDialogVisible.value = false rechargeDialogVisible.value = false
if (cardInfo.value) { if (cardInfo.value) {
// 重置分页并重新加载
packagePagination.value.page = 1
const result = await loadPackageList(
cardInfo.value.identifier,
packagePagination.value.page,
packagePagination.value.pageSize
)
packagePagination.value.total = result.total
await Promise.all([ await Promise.all([
loadPackageList(cardInfo.value.identifier),
loadCurrentPackage(cardInfo.value.identifier), loadCurrentPackage(cardInfo.value.identifier),
loadAssetWallet(cardInfo.value.identifier) loadAssetWallet(cardInfo.value.identifier)
]) ])
@@ -326,10 +352,36 @@
ElMessage.success('充值成功') ElMessage.success('充值成功')
} }
/**
* 套餐列表页码变化
*/
const handlePackagePageChange = async (page: number) => {
if (!cardInfo.value) return
packagePagination.value.page = page
const result = await loadPackageList(
cardInfo.value.identifier,
page,
packagePagination.value.pageSize
)
packagePagination.value.total = result.total
}
/**
* 套餐列表每页条数变化
*/
const handlePackageSizeChange = async (size: number) => {
if (!cardInfo.value) return
packagePagination.value.pageSize = size
packagePagination.value.page = 1 // 重置到第一页
const result = await loadPackageList(cardInfo.value.identifier, 1, size)
packagePagination.value.total = result.total
}
/** /**
* 显示流量详单 * 显示流量详单
*/ */
const handleShowDailyRecords = (packageRow: any) => { const handleShowDailyRecords = (payload: { packageRow: any }) => {
const { packageRow } = payload
selectedPackageUsageId.value = packageRow.package_usage_id selectedPackageUsageId.value = packageRow.package_usage_id
dailyRecordsDialogVisible.value = true dailyRecordsDialogVisible.value = true
} }
@@ -539,11 +591,20 @@
display: flex; display: flex;
flex: 1; flex: 1;
flex-direction: column; flex-direction: column;
min-width: 0; // 防止被内容撑开
.info-card { .info-card {
height: 100%; height: 100%;
margin-bottom: 0; margin-bottom: 0;
} }
&:first-child {
flex: 2;
}
&:last-child {
flex: 8;
}
} }
} }
} }

View File

@@ -84,7 +84,10 @@ export interface PackageInfo {
start_time: string start_time: string
expire_time: string expire_time: string
status: number status: number
status_name?: string
duration_days?: number duration_days?: number
virtual_ratio?: number // 虚流量比例
package_type?: string // 套餐类型: formal正式套餐/ addon加油包
} }
// 钱包信息接口 (使用已有的 AssetWalletResponse) // 钱包信息接口 (使用已有的 AssetWalletResponse)
@@ -108,6 +111,7 @@ export interface SwitchCardForm {
// WiFi配置表单接口 // WiFi配置表单接口
export interface WiFiForm { export interface WiFiForm {
card_no: string // 流量卡号ICCID
enabled: number enabled: number
ssid: string ssid: string
password: string password: string