Files
one-pipe-system/src/views/asset-management/asset-information/components/CurrentPackageCard.vue
sexygoat 81155fd8e4
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 4m44s
fix:进度条
2026-04-30 17:01:00 +08:00

428 lines
13 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<ElCard shadow="never" class="info-card current-package-card">
<template #header>
<div class="card-header">
<span class="header-title">当前生效套餐</span>
<div class="header-actions">
<slot name="header-actions" />
</div>
</div>
</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-else-if="errorMsg" :title="errorMsg" type="warning" :closable="false" />
<template v-else-if="currentPackage">
<!-- 流量使用情况 - 横向进度条卡片 -->
<div class="flow-progress-card">
<!-- 管理员视角始终显示真流量和虚流量 -->
<template v-if="isAdminOrPlatform">
<div class="flow-stats-container">
<!-- 真流量 -->
<div class="flow-stat-section">
<div class="flow-stat-label">真流量使用</div>
<div class="flow-stat-row">
<span class="stat-item">
<span class="stat-label">已使用</span>
<span class="stat-value">{{
formatDataSize(currentPackage.real_used_mb || 0)
}}</span>
</span>
<span class="stat-item">
<span class="stat-label">总量</span>
<span class="stat-value">{{
formatDataSize(currentPackage.real_total_mb || 0)
}}</span>
</span>
<span class="stat-item">
<span class="stat-label">剩余</span>
<span class="stat-value">{{
formatDataSize(
(currentPackage.real_total_mb || 0) - (currentPackage.real_used_mb || 0)
)
}}</span>
</span>
</div>
<ElProgress
:percentage="
getUsageProgress(
currentPackage.real_used_mb || 0,
currentPackage.real_total_mb || 0
)
"
:color="
getProgressColorByPercentage(
getUsageProgress(
currentPackage.real_used_mb || 0,
currentPackage.real_total_mb || 0
)
)
"
:stroke-width="10"
/>
</div>
<!-- 虚流量 -->
<div class="flow-stat-section">
<div class="flow-stat-label">虚流量使用</div>
<div class="flow-stat-row">
<span class="stat-item">
<span class="stat-label">已使用</span>
<span class="stat-value">{{
formatDataSize(currentPackage.virtual_used_mb || 0)
}}</span>
</span>
<span class="stat-item">
<span class="stat-label">停机阈值</span>
<span class="stat-value">{{
formatDataSize(currentPackage.virtual_total_mb || 0)
}}</span>
</span>
</div>
<div class="progress-wrapper">
<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
)
)
}}
</span>
</div>
</div>
</div>
</template>
<!-- 非管理员 未启用虚流量 -->
<template v-else>
<div class="flow-stats-row">
<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
)
}}
</span>
</div>
<div class="flow-stat-item">
<span class="flow-stat-label">总量</span>
<span class="flow-stat-value">
{{ formatDataSize(currentPackage.real_total_mb || 0) }}
</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)
)
}}
</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
)
"
:color="
getProgressColorByPercentage(
getUsageProgress(
currentPackage.enable_virtual_data
? currentPackage.virtual_used_mb || 0
: currentPackage.real_used_mb || 0,
currentPackage.real_total_mb || 0
)
)
"
:stroke-width="14"
/>
</div>
</template>
</div>
<!-- 套餐详情表格 -->
<ElDescriptions :column="4" border class="package-info-table">
<ElDescriptionsItem label="订单号">
{{ currentPackage.order_no || '-' }}
</ElDescriptionsItem>
<ElDescriptionsItem label="套餐名称">
{{ currentPackage.package_name || '-' }}
</ElDescriptionsItem>
<ElDescriptionsItem label="套餐价格">
{{ formatAmount(currentPackage.paid_amount || currentPackage.package_price || 0) }}
</ElDescriptionsItem>
<ElDescriptionsItem label="套餐类型">
{{ getPackageTypeName(currentPackage.package_type) }}
</ElDescriptionsItem>
<ElDescriptionsItem label="开始时间">
{{ formatDateTime(currentPackage.start_time) || '-' }}
</ElDescriptionsItem>
<ElDescriptionsItem label="到期时间">
{{ formatDateTime(currentPackage.expire_time) || '-' }}
</ElDescriptionsItem>
<ElDescriptionsItem label="下单时间">
{{ formatDateTime(currentPackage.created_at) || '-' }}
</ElDescriptionsItem>
<ElDescriptionsItem v-if="currentPackage.duration_days" label="套餐时长">
{{ currentPackage.duration_days }}
</ElDescriptionsItem>
</ElDescriptions>
</template>
<!-- 空状态 -->
<ElEmpty v-else-if="!loading" :description="errorMsg || '暂无当前生效套餐'" :image-size="100" />
</ElCard>
</template>
<script setup lang="ts">
import {
ElCard,
ElButton,
ElDescriptions,
ElDescriptionsItem,
ElProgress,
ElEmpty,
ElAlert
} from 'element-plus'
import { useAssetFormatters } from '../composables/useAssetFormatters'
import { useUserStore } from '@/store/modules/user'
import { storeToRefs } from 'pinia'
import { formatDateTime } from '@/utils/business/format'
import type { PackageInfo } from '../types'
const {
formatAmount,
formatDataSize,
getUsageProgress,
getProgressColorByPercentage,
formatProgressPercentage,
getBreakpointMarkerStyle
} = useAssetFormatters()
const userStore = useUserStore()
const { info: userInfo } = storeToRefs(userStore)
const isAdminOrPlatform = computed(
() => userInfo.value.user_type === 1 || userInfo.value.user_type === 2
)
interface Props {
currentPackage: PackageInfo | null
loading?: boolean
errorMsg?: string
boundDeviceId?: number
boundDeviceNo?: string
boundDeviceName?: string
}
const props = withDefaults(defineProps<Props>(), {
loading: false,
errorMsg: '',
boundDeviceId: undefined,
boundDeviceNo: '',
boundDeviceName: ''
})
const emit = defineEmits<{
showRecharge: []
navigateToDevice: [deviceNo: string]
}>()
const getPackageTypeName = (type?: string) => {
const typeMap: Record<string, string> = {
formal: '正式套餐',
addon: '加油包'
}
return typeMap[type || ''] || '-'
}
</script>
<style scoped lang="scss">
.current-package-card {
.card-header {
display: flex;
align-items: center;
justify-content: space-between;
.header-title {
font-size: 16px;
font-weight: 500;
}
}
.flow-progress-card {
padding: 20px;
background: var(--el-fill-color-light);
border-radius: 8px;
margin-bottom: 20px;
.flow-stats-container {
display: flex;
gap: 40px;
.flow-stat-section {
flex: 1;
min-width: 0;
.flow-stat-label {
font-size: 13px;
color: var(--el-text-color-secondary);
margin-bottom: 8px;
font-weight: 500;
}
.flow-stat-row {
display: flex;
gap: 16px;
margin-bottom: 8px;
.stat-item {
display: flex;
align-items: baseline;
gap: 4px;
.stat-label {
font-size: 12px;
color: var(--el-text-color-secondary);
}
.stat-value {
font-size: 14px;
font-weight: 600;
color: var(--el-text-color-primary);
}
}
}
}
}
.flow-stats-row {
display: flex;
gap: 32px;
margin-bottom: 16px;
.flow-stat-item {
display: flex;
align-items: baseline;
gap: 8px;
.flow-stat-label {
font-size: 13px;
color: var(--el-text-color-secondary);
}
.flow-stat-value {
font-size: 16px;
font-weight: 600;
color: var(--el-text-color-primary);
}
}
}
.flow-progress-bar {
padding: 0 4px;
.progress-label {
font-size: 12px;
color: var(--el-text-color-secondary);
margin-bottom: 4px;
}
}
.progress-wrapper {
display: flex;
align-items: center;
gap: 1px;
.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;
}
}
}
.package-info-table {
margin-top: 0;
}
}
</style>