fix:进度条
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 4m44s

This commit is contained in:
sexygoat
2026-04-30 17:01:00 +08:00
parent 78a41513bd
commit 81155fd8e4
12 changed files with 457 additions and 178 deletions

View File

@@ -114,11 +114,6 @@
<ElDescriptionsItem label="切卡模式">{{
getSwitchModeName(cardInfo?.switch_mode)
}}</ElDescriptionsItem>
<ElDescriptionsItem label="信号强度">
<ElTag :type="getSignalStrengthType()" size="small">
{{ calculateSignalStrength() }}
</ElTag>
</ElDescriptionsItem>
<ElDescriptionsItem label="已绑定卡数">{{
cardInfo?.bound_card_count || 0
}}</ElDescriptionsItem>
@@ -266,44 +261,16 @@
</ElDescriptionsItem>
<!-- 信号信息 -->
<ElDescriptionsItem label="信号强度(RSSI)">
{{
deviceRealtime.rssi !== undefined &&
deviceRealtime.rssi !== null &&
deviceRealtime.rssi !== ''
? deviceRealtime.rssi
: '-'
}}
<ElDescriptionsItem label="信号综合判断">
<ElTag :type="getSignalQualityType(deviceRealtime.signal_quality)" size="small">
{{ getSignalQualityText(deviceRealtime.signal_quality) }}
</ElTag>
</ElDescriptionsItem>
<ElDescriptionsItem label="接收功率(RSRP)">
{{
deviceRealtime.rsrp !== undefined &&
deviceRealtime.rsrp !== null &&
deviceRealtime.rsrp !== ''
? `${deviceRealtime.rsrp} dBm`
: '-'
}}
</ElDescriptionsItem>
<ElDescriptionsItem label="接收质量(RSRQ)">
{{
deviceRealtime.rsrq !== undefined &&
deviceRealtime.rsrq !== null &&
deviceRealtime.rsrq !== ''
? `${deviceRealtime.rsrq} dB`
: '-'
}}
</ElDescriptionsItem>
<ElDescriptionsItem label="信噪比(SINR)">
{{
deviceRealtime.sinr !== undefined &&
deviceRealtime.sinr !== null &&
deviceRealtime.sinr !== ''
? `${deviceRealtime.sinr} dB`
: '-'
}}
<ElDescriptionsItem label="信号较弱原因" :span="3">
{{ getSignalBadReasonText(deviceRealtime.signal_bad_reason) }}
</ElDescriptionsItem>
<ElDescriptionsItem label="已连接设备数">
{{ deviceRealtime.client_number }} / {{ deviceRealtime.max_clients }}
{{ deviceRealtime.client_number ?? '-' }} / {{ deviceRealtime.max_clients ?? '-' }}
</ElDescriptionsItem>
<!-- WiFi 信息 -->
@@ -437,7 +404,7 @@
</template>
<script setup lang="ts">
import { computed, toRef } from 'vue'
import { computed } from 'vue'
import {
ElCard,
ElDescriptions,
@@ -479,6 +446,7 @@
msisdn?: string
carrier_name?: string
real_name_status?: number
realname_policy?: string
network_status?: number
current_month_usage_mb?: number
last_gateway_reading_mb?: number
@@ -510,16 +478,15 @@
interface DeviceRealtimeInfo {
online_status?: number
status?: number
battery_level?: number
battery_level?: number | null
run_time?: string
connect_time?: string
last_online_time?: string
rssi?: string
rsrp?: string
rsrq?: string
sinr?: string
client_number?: number
max_clients?: number
current_iccid?: string
signal_quality?: string
signal_bad_reason?: string
wifi_enabled?: boolean
ssid?: string
wifi_password?: string
@@ -590,7 +557,6 @@
const emit = defineEmits<Emits>()
// 使用格式化工具
const deviceRealtimeRef = toRef(props, 'deviceRealtime')
const {
formatDataSize,
formatDuration,
@@ -601,10 +567,11 @@
getAssetStatusType,
getOnlineStatusName,
getOnlineStatusType,
calculateSignalStrength,
getSignalStrengthType,
getSignalQualityText,
getSignalQualityType,
getSignalBadReasonText,
getSwitchModeName
} = useAssetFormatters(deviceRealtimeRef)
} = useAssetFormatters()
// 排序后的设备卡列表(按卡槽位置排序,当前卡排在最前面)
const sortedDeviceCards = computed(() => {
@@ -628,8 +595,8 @@
})
// 处理轮询开关变化
const handlePollingChange = (value: boolean) => {
emit('update:pollingEnabled', value)
const handlePollingChange = (value: string | number | boolean) => {
emit('update:pollingEnabled', Boolean(value))
}
// 跳转到卡片信息

View File

@@ -96,28 +96,46 @@
</span>
</div>
<div class="progress-wrapper">
<ElProgress
:percentage="
getUsageProgress(
currentPackage.virtual_used_mb || 0,
currentPackage.virtual_total_mb || 0
)
"
:color="
getProgressColorByPercentage(
<div class="progress-track">
<ElProgress
:show-text="false"
:percentage="
getUsageProgress(
currentPackage.virtual_used_mb || 0,
currentPackage.virtual_total_mb || 0
)
"
:color="
getProgressColorByPercentage(
getUsageProgress(
currentPackage.virtual_used_mb || 0,
currentPackage.virtual_total_mb || 0
)
)
"
:stroke-width="10"
/>
<div
class="breakpoint-marker"
:style="
getBreakpointMarkerStyle(
currentPackage.virtual_total_mb || 0,
currentPackage.real_total_mb || 0
)
"
:title="`停机阈值/真流量总量: ${formatDataSize(currentPackage.virtual_total_mb || 0)} / ${formatDataSize(currentPackage.real_total_mb || 0)}`"
></div>
</div>
<span class="progress-percentage">
{{
formatProgressPercentage(
getUsageProgress(
currentPackage.virtual_used_mb || 0,
currentPackage.virtual_total_mb || 0
)
)
"
:stroke-width="10"
/>
<div
class="breakpoint-marker"
:style="{ left: getBreakpointPosition(currentPackage) + '%' }"
:title="`停机阈值/真流量总量: ${formatDataSize(currentPackage.virtual_total_mb || 0)} / ${formatDataSize(currentPackage.real_total_mb || 0)}`"
></div>
}}
</span>
</div>
</div>
</div>
@@ -231,8 +249,14 @@
import { formatDateTime } from '@/utils/business/format'
import type { PackageInfo } from '../types'
const { formatAmount, formatDataSize, getUsageProgress, getProgressColorByPercentage } =
useAssetFormatters()
const {
formatAmount,
formatDataSize,
getUsageProgress,
getProgressColorByPercentage,
formatProgressPercentage,
getBreakpointMarkerStyle
} = useAssetFormatters()
const userStore = useUserStore()
const { info: userInfo } = storeToRefs(userStore)
@@ -269,11 +293,6 @@
}
return typeMap[type || ''] || '-'
}
const getBreakpointPosition = (pkg: PackageInfo) => {
if (!pkg.real_total_mb || pkg.real_total_mb === 0) return 0
return (pkg.virtual_total_mb / pkg.real_total_mb) * 100
}
</script>
<style scoped lang="scss">
@@ -369,18 +388,34 @@
}
.progress-wrapper {
position: relative;
display: flex;
align-items: center;
gap: 1px;
.breakpoint-marker {
position: absolute;
top: 50%;
width: 8px;
height: 8px;
background-color: var(--el-color-danger);
border-radius: 50%;
transform: translate(-50%, -50%);
z-index: 1;
cursor: pointer;
.progress-track {
position: relative;
flex: 1;
min-width: 0;
.breakpoint-marker {
position: absolute;
top: 50%;
width: 8px;
height: 8px;
background-color: var(--el-color-danger);
border-radius: 50%;
transform: translate(-50%, -50%);
z-index: 1;
cursor: pointer;
}
}
.progress-percentage {
min-width: 44px;
font-size: 14px;
color: var(--el-text-color-secondary);
text-align: right;
white-space: nowrap;
}
}
}

View File

@@ -169,6 +169,8 @@
{ label: '设备重置', value: 'device_reset' },
{ label: '设备分配', value: 'device_allocate' },
{ label: '设备回收', value: 'device_recall' },
{ label: '设备绑卡', value: 'device_bind_card' },
{ label: '设备解绑卡', value: 'device_unbind_card' },
{ label: '卡分配', value: 'card_allocate' },
{ label: '卡回收', value: 'card_recall' },
{ label: '卡停机', value: 'card_stop' },
@@ -195,6 +197,8 @@
device_reset: 'warning',
device_allocate: 'primary',
device_recall: 'warning',
device_bind_card: 'primary',
device_unbind_card: 'warning',
card_allocate: 'primary',
card_recall: 'warning',
card_stop: 'danger',
@@ -223,17 +227,26 @@
asset_type: '资产类型',
batch_no: '批次号',
card_category: '卡类型',
device_imei: '设备IMEI',
device_sn: '设备SN',
device_virtual_no: '设备虚拟号',
device_virtual_nos: '设备虚拟号',
enable_polling: '轮询开关',
file_key: '文件存储键',
first_realname_at: '首次实名时间',
iccid: 'ICCID',
iccids: 'ICCID',
network_status: '网络状态',
real_name_status: '实名状态',
realname_policy: '实名认证策略',
shop_name: '店铺',
source_service: '来源服务',
source_shop_name: '来源店铺',
status: '资产状态',
stop_reason: '停机原因',
switch_mode: '切卡模式'
switch_mode: '切卡模式',
target_shop_name: '目标店铺',
to_shop_name: '目标店铺'
}
const realnamePolicyMap: Record<string, string> = {
@@ -371,6 +384,13 @@
if (typeof value === 'boolean') return value ? '是' : '否'
if (typeof value === 'number') return String(value)
if (typeof value === 'string') return value || '空'
if (Array.isArray(value)) {
if (value.length === 0) return '空'
if (value.every((item) => ['string', 'number', 'boolean'].includes(typeof item))) {
return value.map((item) => formatValue(item)).join('、')
}
return JSON.stringify(value)
}
if (typeof value === 'object') return JSON.stringify(value)
return String(value)
}

View File

@@ -4,7 +4,10 @@
<div class="card-header">
<span>套餐列表</span>
<div class="card-header-right">
<ElButton type="primary" :disabled="props.boundDeviceId" @click="handleShowRecharge"
<ElButton
type="primary"
:disabled="Boolean(props.boundDeviceId)"
@click="handleShowRecharge"
>套餐充值</ElButton
>
</div>
@@ -97,28 +100,46 @@
>
</div>
<div class="progress-wrapper">
<ElProgress
style="margin-top: 4px"
:percentage="
getUsageProgress(
scope.row.virtual_used_mb || 0,
scope.row.virtual_total_mb || 0
)
"
:color="
getProgressColorByPercentage(
<div class="progress-track">
<ElProgress
style="margin-top: 4px"
:show-text="false"
:percentage="
getUsageProgress(
scope.row.virtual_used_mb || 0,
scope.row.virtual_total_mb || 0
)
"
:color="
getProgressColorByPercentage(
getUsageProgress(
scope.row.virtual_used_mb || 0,
scope.row.virtual_total_mb || 0
)
)
"
/>
<div
class="breakpoint-marker"
:style="
getBreakpointMarkerStyle(
scope.row.virtual_total_mb || 0,
scope.row.real_total_mb || 0
)
"
:title="`停机阈值/真流量总量: ${formatDataSize(scope.row.virtual_total_mb || 0)} / ${formatDataSize(scope.row.real_total_mb || 0)}`"
></div>
</div>
<span class="progress-percentage">
{{
formatProgressPercentage(
getUsageProgress(
scope.row.virtual_used_mb || 0,
scope.row.virtual_total_mb || 0
)
)
"
/>
<div
class="breakpoint-marker"
:style="{ left: getBreakpointPosition(scope.row) + '%' }"
:title="`停机阈值/真流量总量: ${formatDataSize(scope.row.virtual_total_mb || 0)} / ${formatDataSize(scope.row.real_total_mb || 0)}`"
></div>
}}
</span>
</div>
</template>
<template v-else>
@@ -346,14 +367,11 @@
formatAmount,
getPackageStatusTagType,
getUsageProgress,
getProgressColorByPercentage
getProgressColorByPercentage,
formatProgressPercentage,
getBreakpointMarkerStyle
} = useAssetFormatters()
const getBreakpointPosition = (pkg: PackageInfo) => {
if (!pkg.real_total_mb || pkg.real_total_mb === 0) return 0
return (pkg.virtual_total_mb / pkg.real_total_mb) * 100
}
// 套餐名称点击
const handlePackageNameClick = (packageRow: PackageInfo) => {
if (!hasAuth('package_usage:daily_records')) {
@@ -459,18 +477,34 @@
}
.progress-wrapper {
position: relative;
display: flex;
align-items: center;
gap: 1px;
.breakpoint-marker {
position: absolute;
top: 50%;
width: 8px;
height: 8px;
background-color: var(--el-color-danger);
border-radius: 50%;
transform: translate(-50%, -50%);
z-index: 1;
cursor: pointer;
.progress-track {
position: relative;
flex: 1;
min-width: 0;
.breakpoint-marker {
position: absolute;
top: 50%;
width: 8px;
height: 8px;
background-color: var(--el-color-danger);
border-radius: 50%;
transform: translate(-50%, -50%);
z-index: 1;
cursor: pointer;
}
}
.progress-percentage {
min-width: 44px;
font-size: 14px;
color: var(--el-text-color-secondary);
text-align: right;
white-space: nowrap;
}
}
}