fix(operator): fix operator edit issue
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 6m25s
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 6m25s
This commit is contained in:
@@ -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">
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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">支持 jpg、png 格式,文件大小不超过 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>
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user