fix: 代理系列授权店铺
Some checks failed
构建并部署前端到测试环境 / build-and-deploy (push) Failing after 2m0s

This commit is contained in:
sexygoat
2026-05-09 16:13:11 +08:00
parent 3c71ae0004
commit 73ce9af11a
26 changed files with 833 additions and 807 deletions

View File

@@ -147,49 +147,32 @@
<div class="flow-stat-item">
<span class="flow-stat-label">已使用</span>
<span class="flow-stat-value">
{{
formatDataSize(
currentPackage.enable_virtual_data
? currentPackage.virtual_used_mb || 0
: currentPackage.real_used_mb || 0
)
}}
{{ formatDataSize(getDisplayedUsedMb(currentPackage)) }}
</span>
</div>
<div class="flow-stat-item">
<span class="flow-stat-label">总量</span>
<span class="flow-stat-value">
{{ formatDataSize(currentPackage.real_total_mb || 0) }}
{{ formatDataSize(getDisplayedTotalMb(currentPackage)) }}
</span>
</div>
<div class="flow-stat-item">
<span class="flow-stat-label">剩余</span>
<span class="flow-stat-value">
{{
formatDataSize(
(currentPackage.real_total_mb || 0) - (currentPackage.real_used_mb || 0)
)
}}
{{ formatDataSize(getDisplayedRemainingMb(currentPackage)) }}
</span>
</div>
</div>
<div class="flow-progress-bar" style="margin-top: 16px">
<ElProgress
:percentage="
getUsageProgress(
currentPackage.enable_virtual_data
? currentPackage.virtual_used_mb || 0
: currentPackage.real_used_mb || 0,
currentPackage.real_total_mb || 0
)
getUsageProgress(getDisplayedUsedMb(currentPackage), getDisplayedTotalMb(currentPackage))
"
:color="
getProgressColorByPercentage(
getUsageProgress(
currentPackage.enable_virtual_data
? currentPackage.virtual_used_mb || 0
: currentPackage.real_used_mb || 0,
currentPackage.real_total_mb || 0
getDisplayedUsedMb(currentPackage),
getDisplayedTotalMb(currentPackage)
)
)
"
@@ -286,6 +269,14 @@
navigateToDevice: [deviceNo: string]
}>()
const getDisplayedTotalMb = (pkg: AssetPackageUsageRecord) => pkg.real_total_mb || 0
const getDisplayedUsedMb = (pkg: AssetPackageUsageRecord) =>
pkg.enable_virtual_data ? pkg.virtual_used_mb || 0 : pkg.real_used_mb || 0
const getDisplayedRemainingMb = (pkg: AssetPackageUsageRecord) =>
getDisplayedTotalMb(pkg) - getDisplayedUsedMb(pkg)
const getPackageTypeName = (type?: string) => {
const typeMap: Record<string, string> = {
formal: '正式套餐',

View File

@@ -148,9 +148,7 @@
>已使用:
{{
formatDataSize(
scope.row.enable_virtual_data
? scope.row.virtual_used_mb || 0
: scope.row.real_used_mb || 0
getDisplayedUsedMb(scope.row)
)
}}</span
>
@@ -161,7 +159,7 @@
>剩余:
{{
formatDataSize(
(scope.row.real_total_mb || 0) - (scope.row.real_used_mb || 0)
getDisplayedRemainingMb(scope.row)
)
}}</span
>
@@ -170,19 +168,15 @@
style="margin-top: 8px"
:percentage="
getUsageProgress(
scope.row.enable_virtual_data
? scope.row.virtual_used_mb || 0
: scope.row.real_used_mb || 0,
scope.row.real_total_mb || 0
getDisplayedUsedMb(scope.row),
getDisplayedTotalMb(scope.row)
)
"
:color="
getProgressColorByPercentage(
getUsageProgress(
scope.row.enable_virtual_data
? scope.row.virtual_used_mb || 0
: scope.row.real_used_mb || 0,
scope.row.real_total_mb || 0
getDisplayedUsedMb(scope.row),
getDisplayedTotalMb(scope.row)
)
)
"
@@ -409,6 +403,14 @@
}
// 页码变化
const getDisplayedTotalMb = (pkg: PackageInfo) => pkg.real_total_mb || 0
const getDisplayedUsedMb = (pkg: PackageInfo) =>
pkg.enable_virtual_data ? pkg.virtual_used_mb || 0 : pkg.real_used_mb || 0
const getDisplayedRemainingMb = (pkg: PackageInfo) =>
getDisplayedTotalMb(pkg) - getDisplayedUsedMb(pkg)
const handlePageChange = (page: number) => {
emit('page-change', page)
}

View File

@@ -43,7 +43,7 @@
style="width: 360px"
/>
<ElButton type="primary" @click="handleQuery">查询</ElButton>
<ElButton @click="handleReset">重置</ElButton>
<ElButton @click="handleReset()">重置</ElButton>
<ElTag type="info" size="small"> {{ total }} </ElTag>
</div>
</div>
@@ -64,14 +64,8 @@
</template>
</ElEmpty>
<div v-else class="wallet-table-wrapper">
<ElTable
v-if="transactionList && transactionList.length > 0"
:data="transactionList"
class="wallet-table"
border
v-loading="loading"
>
<div v-else v-loading="loading" class="wallet-table-wrapper">
<ElTable v-if="hasTransactions" :data="transactionList" class="wallet-table" border>
<ElTableColumn label="交易类型" width="100" align="center">
<template #default="scope">
<ElTag :type="getTransactionTypeTag(scope.row.transaction_type)" size="small">
@@ -128,7 +122,9 @@
</template>
</ElTableColumn>
</ElTable>
<ElEmpty v-else-if="!loading" description="暂无钱包流水数据" :image-size="100" />
<div v-else class="wallet-empty-state">
<ElEmpty v-if="hasLoadedTransactions" description="暂无钱包流水数据" :image-size="100" />
</div>
<!-- 分页器 -->
<div v-if="total > 0" class="pagination-wrapper">
@@ -147,7 +143,7 @@
</template>
<script setup lang="ts">
import { ref, watch } from 'vue'
import { computed, ref, watch } from 'vue'
import {
ElCard,
ElTable,
@@ -166,6 +162,8 @@
import { formatDateTime } from '@/utils/business/format'
import { AssetService } from '@/api/modules'
import type {
AssetWalletTransactionItem,
AssetWalletTransactionListResponse,
AssetWalletResponse,
AssetWalletTransactionParams,
TransactionType
@@ -173,6 +171,7 @@
interface Props {
assetIdentifier: string
searchToken?: number
walletInfo: AssetWalletResponse | null
walletLoading?: boolean
boundDeviceId?: number | null
@@ -190,39 +189,80 @@
}>()
const { formatAmount, getTransactionTypeTag, getWalletStatusType } = useAssetFormatters()
const DEFAULT_PAGE_SIZE = 20
const createDefaultPagination = () => ({ page: 1, page_size: DEFAULT_PAGE_SIZE })
const createDefaultFilterForm = () => ({
transaction_type: null as TransactionType | null,
date_range: null as [Date, Date] | null
})
const createDefaultQueryParams = (): AssetWalletTransactionParams => ({
page: 1,
page_size: DEFAULT_PAGE_SIZE,
transaction_type: null,
start_time: null,
end_time: null
})
// State
const loading = ref(false)
const transactionList = ref<any[]>([])
const hasLoadedTransactions = ref(false)
const transactionList = ref<AssetWalletTransactionItem[]>([])
const total = ref(0)
const pagination = ref({ page: 1, page_size: 20 })
const filterForm = ref<{
transaction_type: TransactionType | null
date_range: [Date, Date] | null
}>({
transaction_type: null,
date_range: null
})
const queryParams = ref<AssetWalletTransactionParams>({
page: 1,
page_size: 20,
transaction_type: null as TransactionType | null,
start_time: null as string | null,
end_time: null as string | null
const hasTransactions = computed(() => transactionList.value.length > 0)
const pagination = ref(createDefaultPagination())
const filterForm = ref(createDefaultFilterForm())
const queryParams = ref<AssetWalletTransactionParams>(createDefaultQueryParams())
const clearTransactions = () => {
hasLoadedTransactions.value = false
transactionList.value = []
total.value = 0
pagination.value = createDefaultPagination()
}
const normalizeTransactionResponse = (data?: AssetWalletTransactionListResponse | null) => ({
items: data?.items ?? data?.list ?? [],
total: data?.total ?? 0,
page: data?.page ?? 1,
pageSize: data?.size ?? data?.page_size ?? queryParams.value.page_size ?? DEFAULT_PAGE_SIZE
})
const loadTransactions = async () => {
if (!props.assetIdentifier) return
const loadTransactions = async (preserveDisplayState: boolean = true) => {
if (!props.assetIdentifier || props.boundDeviceId) {
clearTransactions()
return
}
if (!preserveDisplayState) {
clearTransactions()
}
try {
loading.value = true
const response = await AssetService.getWalletTransactions(
props.assetIdentifier,
queryParams.value
)
if (response.code === 0 && response.data) {
transactionList.value = response.data.list || []
total.value = response.data.total || 0
const normalized = normalizeTransactionResponse(response.data)
transactionList.value = normalized.items
total.value = normalized.total
pagination.value = {
page: normalized.page,
page_size: normalized.pageSize
}
queryParams.value.page = normalized.page
queryParams.value.page_size = normalized.pageSize
} else {
transactionList.value = []
total.value = 0
pagination.value = createDefaultPagination()
}
hasLoadedTransactions.value = true
} catch (error: any) {
if (error?.response?.status === 403) {
ElMessage.warning('企业账号禁止查看钱包流水')
@@ -234,6 +274,12 @@
}
}
const resetQueryState = () => {
filterForm.value = createDefaultFilterForm()
queryParams.value = createDefaultQueryParams()
pagination.value = createDefaultPagination()
}
const handleQuery = () => {
queryParams.value.page = 1
pagination.value.page = 1
@@ -245,41 +291,45 @@
queryParams.value.start_time = null
queryParams.value.end_time = null
}
loadTransactions()
void loadTransactions(true)
}
const handleFilterChange = () => handleQuery()
const handleReset = () => {
filterForm.value = { transaction_type: null, date_range: null }
queryParams.value = {
page: 1,
page_size: 20,
transaction_type: null,
start_time: null,
end_time: null
const handleReset = (preserveDisplayState: boolean = true) => {
resetQueryState()
if (props.boundDeviceId) {
clearTransactions()
return
}
pagination.value = { page: 1, page_size: 20 }
loadTransactions()
void loadTransactions(preserveDisplayState)
}
const handlePageSizeChange = (size: number) => {
queryParams.value.page_size = size
queryParams.value.page = 1
pagination.value.page = 1
loadTransactions()
void loadTransactions(true)
}
const handlePageChange = (page: number) => {
queryParams.value.page = page
loadTransactions()
void loadTransactions(true)
}
// 仅监听 assetIdentifier 变化加载数据(去掉 onMounted 避免重复调用)
watch(
() => props.assetIdentifier,
(newVal) => {
if (newVal) handleReset()
() => [props.assetIdentifier, props.searchToken, props.boundDeviceId] as const,
([assetIdentifier], previousValue) => {
if (!assetIdentifier) {
resetQueryState()
clearTransactions()
return
}
const previousAssetIdentifier = previousValue?.[0]
const preserveDisplayState =
Boolean(previousAssetIdentifier) && assetIdentifier === previousAssetIdentifier
handleReset(preserveDisplayState)
},
{ immediate: true }
)
@@ -326,6 +376,13 @@
}
.wallet-table-wrapper {
.wallet-empty-state {
display: flex;
align-items: center;
justify-content: center;
min-height: 220px;
}
.wallet-table {
:deep(.el-table__header) {
th {
@@ -334,18 +391,6 @@
}
}
.reference-no-link {
color: var(--el-color-primary);
text-decoration: underline;
text-decoration-style: dashed;
text-underline-offset: 2px;
cursor: pointer;
&:hover {
color: var(--el-color-primary-light-3);
text-decoration-style: solid;
}
}
}
.pagination-wrapper {

View File

@@ -8,6 +8,13 @@ import { ElMessage } from 'element-plus'
import { AssetService } from '@/api/modules'
import type { AssetWalletResponse, DeviceGatewayInfo, AssetPackageUsageRecord } from '@/types/api'
type TrafficDisplaySource = {
real_total_mb?: number | null
real_used_mb?: number | null
virtual_used_mb?: number | null
enable_virtual_data?: boolean | null
}
/**
* 格式化数据大小(MB -> GB/MB)
*/
@@ -18,6 +25,20 @@ const formatDataSize = (mb: number) => {
return `${mb.toFixed(2)}MB`
}
const getDisplayedTotalMb = (traffic?: TrafficDisplaySource | null) => traffic?.real_total_mb || 0
const getDisplayedUsedMb = (traffic?: TrafficDisplaySource | null) =>
traffic?.enable_virtual_data ? traffic?.virtual_used_mb || 0 : traffic?.real_used_mb || 0
const getDisplayedRemainingMb = (traffic?: TrafficDisplaySource | null) =>
getDisplayedTotalMb(traffic) - getDisplayedUsedMb(traffic)
const getDisplayedUsagePercentage = (traffic?: TrafficDisplaySource | null) => {
const totalMb = getDisplayedTotalMb(traffic)
const usedMb = getDisplayedUsedMb(traffic)
return totalMb > 0 ? `${((usedMb / totalMb) * 100).toFixed(2)}%` : '0.00%'
}
export function useAssetInfo() {
// 状态管理
const loading = ref(false)
@@ -105,13 +126,10 @@ export function useAssetInfo() {
// 流量/套餐信息
current_package: data.current_package || '',
packageSeries: data.series_name || '-',
packageTotalFlow: formatDataSize(data.real_total_mb || 0),
usedFlow: formatDataSize(data.real_used_mb || 0),
remainFlow: formatDataSize((data.real_total_mb || 0) - (data.real_used_mb || 0)),
usedFlowPercentage:
data.real_total_mb > 0
? `${((data.real_used_mb / data.real_total_mb) * 100).toFixed(2)}%`
: '0.00%',
packageTotalFlow: formatDataSize(getDisplayedTotalMb(data)),
usedFlow: formatDataSize(getDisplayedUsedMb(data)),
remainFlow: formatDataSize(getDisplayedRemainingMb(data)),
usedFlowPercentage: getDisplayedUsagePercentage(data),
packageList: [] // 套餐列表需要单独调用API获取
}
@@ -209,23 +227,11 @@ export function useAssetInfo() {
}
// 同时更新 cardInfo 中的流量显示信息(根据是否启用虚流量选择数据源)
const useVirtual = pkg.enable_virtual_data
cardInfo.value.current_package = pkg.package_name
cardInfo.value.packageTotalFlow = formatDataSize(
useVirtual ? pkg.virtual_total_mb || 0 : pkg.real_total_mb || 0
)
cardInfo.value.usedFlow = formatDataSize(
useVirtual ? pkg.virtual_used_mb || 0 : pkg.real_used_mb || 0
)
cardInfo.value.remainFlow = formatDataSize(
useVirtual
? (pkg.virtual_total_mb || 0) - (pkg.virtual_used_mb || 0)
: (pkg.real_total_mb || 0) - (pkg.real_used_mb || 0)
)
const totalMb = useVirtual ? pkg.virtual_total_mb || 0 : pkg.real_total_mb || 0
const usedMb = useVirtual ? pkg.virtual_used_mb || 0 : pkg.real_used_mb || 0
cardInfo.value.usedFlowPercentage =
totalMb > 0 ? `${((usedMb / totalMb) * 100).toFixed(2)}%` : '0.00%'
cardInfo.value.packageTotalFlow = formatDataSize(getDisplayedTotalMb(pkg))
cardInfo.value.usedFlow = formatDataSize(getDisplayedUsedMb(pkg))
cardInfo.value.remainFlow = formatDataSize(getDisplayedRemainingMb(pkg))
cardInfo.value.usedFlowPercentage = getDisplayedUsagePercentage(pkg)
} else {
// code 为 0 但 data 为空,表示暂无当前生效套餐(正常情况)
currentPackage.value = null

View File

@@ -76,6 +76,7 @@
<div class="row full-width">
<WalletTransactionCard
:asset-identifier="cardInfo.identifier"
:search-token="walletTransactionSearchToken"
:wallet-info="walletInfo"
:wallet-loading="walletLoading"
:bound-device-id="cardInfo.bound_device_id"
@@ -295,6 +296,7 @@
pageSize: 5,
total: 0
})
const walletTransactionSearchToken = ref(0)
// ========== 轮询状态 ==========
const pollingEnabled = ref(false)
@@ -325,6 +327,10 @@
await fetchAssetDetail(deviceIdentifier)
}
if (cardInfo.value) {
walletTransactionSearchToken.value += 1
}
// 从接口数据初始化轮询开关,不触发更新接口
pollingInitializing = true
pollingEnabled.value = cardInfo.value?.enable_polling ?? false