2
0
Files
fe.wfc.user/src/views/home/modules/line-chart.vue
2024-12-25 18:51:47 +08:00

390 lines
8.8 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.
<script setup lang="ts">
import { useI18n } from 'vue-i18n';
import { ref, onMounted, inject } from 'vue';
import { fetchPackageList, submitOrder } from '@/service/api/auth';
import { message } from 'ant-design-vue';
const { t } = useI18n();
// 注入更新仪表盘的方法
const updateDashboard = inject('updateDashboard') as () => Promise<void>;
interface PackageOption {
id: string;
packageName: string;
price: number;
clientNum: number;
clientNumEnable: boolean;
traffic: number;
trafficEnable: boolean;
trafficDisplay: string;
durationEnable: boolean;
isRecommended?: boolean;
promotion?: string;
periodNum: number;
periodType: number;
validityPeriod: string;
}
// 添加有效期类型枚举
const PERIOD_TYPE = {
HOUR: 0,
DAY: 1,
MONTH: 2,
YEAR: 3
} as const;
// 添加有效期单位映射
const PERIOD_UNIT = {
[PERIOD_TYPE.HOUR]: '小时',
[PERIOD_TYPE.DAY]: '天',
[PERIOD_TYPE.MONTH]: '月',
[PERIOD_TYPE.YEAR]: '年'
} as const;
// 格式化有效期显示
const formatValidityPeriod = (num: number, type: number): string => {
const unit = PERIOD_UNIT[type as keyof typeof PERIOD_UNIT] || '未知';
return `${num}${unit}`;
};
// 流量单位转换函数
const formatTraffic = (trafficKB: number): string => {
const KB_TO_MB = 1024;
const KB_TO_GB = 1024 * 1024;
const KB_TO_TB = 1024 * 1024 * 1024;
if (trafficKB >= KB_TO_TB) {
// KB -> TB (除以 1024^3)
return `${(trafficKB / KB_TO_TB).toFixed(2)}TB`;
}
if (trafficKB >= KB_TO_GB) {
// KB -> GB (除以 1024^2)
return `${(trafficKB / KB_TO_GB).toFixed(2)}GB`;
}
if (trafficKB >= KB_TO_MB) {
// KB -> MB (除以 1024)
return `${(trafficKB / KB_TO_MB).toFixed(2)}MB`;
}
// 小于1MB的情况保持KB单位
return `${trafficKB.toFixed(2)}KB`;
};
const packageOptions = ref<PackageOption[]>([]);
const selectedPackage = ref<PackageOption>({
id: '1',
packageName: '',
price: 0,
clientNum: 0,
clientNumEnable: false,
traffic: 0,
trafficEnable: false,
trafficDisplay: '0GB',
durationEnable: false,
isRecommended: false,
promotion: '',
periodNum: 0,
periodType: PERIOD_TYPE.MONTH,
validityPeriod: '0月'
});
const fetchPackages = async () => {
try {
const response = await fetchPackageList();
if (response.data && Array.isArray(response.data)) {
packageOptions.value = response.data.map(pkg => ({
id: pkg.id,
packageName: pkg.packageName,
price: parseFloat(pkg.price),
clientNum: Number(pkg.clientNum),
clientNumEnable: pkg.clientNumEnable,
traffic: Number(pkg.traffic),
trafficEnable: pkg.trafficEnable,
trafficDisplay: pkg.trafficEnable ? formatTraffic(Number(pkg.traffic)) : '无限制',
durationEnable: pkg.durationEnable,
isRecommended: pkg.isRecommended || false,
promotion: pkg.promotion || '',
periodNum: Number(pkg.periodNum),
periodType: Number(pkg.periodType),
validityPeriod: pkg.durationEnable ? formatValidityPeriod(Number(pkg.periodNum), Number(pkg.periodType)) : '无限制'
}));
if (packageOptions.value.length > 0) {
selectedPackage.value = packageOptions.value[0];
}
}
} catch (error) {
console.error('Failed to fetch packages:', error);
}
};
const selectPackage = (option: PackageOption) => {
selectedPackage.value = option;
};
// 添加办理套餐的方法
const handleSubmitOrder = async () => {
try {
await submitOrder({
type: 0,
packageId: selectedPackage.value.id
});
message.success('套餐办理成功!');
// 更新仪表盘数据
await updateDashboard();
} catch (error) {
message.error('套餐办理失败,请重试!');
console.error('Failed to submit order:', error);
}
};
onMounted(async () => {
await fetchPackages();
});
</script>
<template>
<div class="package-container">
<!-- 顶部价格展示 -->
<div class="price-header">
<div class="price">
<span class="currency">¥</span>
<span class="amount">{{ selectedPackage.price }}</span>
<span class="period"></span>
</div>
<div class="subtitle">{{ selectedPackage.packageName }}</div>
</div>
<!-- 套餐选项 -->
<div class="package-options">
<h3 class="section-title">{{ t('page.setmeal.changablelevel') }}</h3>
<div class="options-grid">
<div
v-for="option in packageOptions"
:key="option.id"
:class="[
'option-card',
{
recommended: option.isRecommended,
selected: selectedPackage.id === option.id
}
]"
@click="selectPackage(option)"
>
<div v-if="option.isRecommended" class="recommended-tag">
{{ t('page.setmeal.highlyrecommended') }}
</div>
<div class="package-name">{{ option.packageName }}</div>
<div class="price">¥{{ option.price }}</div>
<div class="traffic">{{ option.trafficEnable ? option.trafficDisplay : '无限制' }}</div>
<div class="device-count">
{{ option.clientNumEnable ? `${option.clientNum}台设备` : '无限制' }}
</div>
</div>
</div>
</div>
<!-- 套餐详情 -->
<div class="package-details">
<h3 class="section-title">{{ t('page.setmeal.mealdetail') }}</h3>
<div class="details-list">
<div class="detail-item">
<div class="label">套餐名称</div>
<div class="value">{{ selectedPackage.packageName }}</div>
</div>
<div class="detail-item">
<div class="label">{{ t('page.setmeal.GeneralPurposeTraffic') }}</div>
<div class="value">
{{ selectedPackage.trafficEnable ? `${selectedPackage.trafficDisplay},当月有效` : '无限制' }}
</div>
</div>
<div class="detail-item">
<div class="label">设备数量</div>
<div class="value">
{{ selectedPackage.clientNumEnable ? `最多${selectedPackage.clientNum}台设备同时在线` : '无限制' }}
</div>
</div>
<div class="detail-item">
<div class="label">有效期限</div>
<div class="value">
{{ selectedPackage.durationEnable ? selectedPackage.validityPeriod : '无限制' }}
</div>
</div>
<div class="bottom-bar">
<button
class="btn-primary"
@click="handleSubmitOrder"
:disabled="!selectedPackage.id"
>
{{ t('page.setmeal.Applynow') }}
</button>
</div>
</div>
</div>
</div>
</template>
<style scoped>
.package-container {
min-height: 100vh;
padding: 16px 16px 80px;
}
.price-header {
background: #fff1f0;
padding: 20px;
border-radius: 12px;
margin-bottom: 16px;
}
.price {
color: #ff4d4f;
margin-bottom: 8px;
}
.currency {
font-size: 20px;
}
.amount {
font-size: 32px;
font-weight: bold;
}
.period {
font-size: 16px;
}
.subtitle {
font-size: 14px;
color: #666;
}
.section-title {
font-size: 16px;
font-weight: 500;
margin-bottom: 16px;
color: #333;
position: relative;
padding-left: 12px;
}
.section-title::before {
content: '';
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 4px;
height: 16px;
background: #1890ff;
border-radius: 2px;
}
.options-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 12px;
margin-bottom: 16px;
}
.option-card {
background: white;
padding: 16px;
border-radius: 8px;
text-align: center;
position: relative;
border: 1px solid #e8e8e8;
cursor: pointer;
transition: all 0.3s;
}
.option-card:hover {
transform: translateY(-2px);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.option-card.selected {
border-color: #ff4d4f;
background: #fff1f0;
}
.recommended-tag {
position: absolute;
top: 0;
left: 0;
background: #ff4d4f;
color: white;
font-size: 12px;
padding: 2px 8px;
border-radius: 0 0 8px 0;
}
.package-details {
background: white;
padding: 16px;
border-radius: 12px;
}
.detail-item {
display: flex;
padding: 12px 0;
border-bottom: 1px solid #f0f0f0;
}
.detail-item:last-child {
border-bottom: none;
}
.label {
width: 80px;
color: #666;
}
.value {
flex: 1;
color: #333;
}
.value.highlight {
color: #ff4d4f;
}
.bottom-bar {
padding: 12px 16px;
display: flex;
justify-content: center;
align-items: center;
height: 60px;
}
.btn-primary {
background: #ff4d4f;
color: white;
border: none;
padding: 12px 32px;
border-radius: 24px;
font-size: 16px;
width: 90%;
max-width: 400px;
}
.package-name {
font-size: 16px;
font-weight: 500;
margin-bottom: 8px;
color: #333;
}
.traffic {
font-size: 14px;
color: #666;
margin-top: 8px;
}
.device-count {
font-size: 14px;
color: #666;
margin-top: 4px;
}
</style>