428 lines
13 KiB
Vue
428 lines
13 KiB
Vue
<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>
|