2
0
Files
fe.wfc.user/src/views/userInfo/package/index.vue
2025-03-19 17:36:45 +08:00

253 lines
6.2 KiB
Vue

<script setup lang="ts">
import { ref, onMounted, h } from 'vue';
import { useI18n } from 'vue-i18n';
import { fetchUserPackages } from '@/service/api/auth';
import dayjs from 'dayjs';
import { Tag } from 'ant-design-vue';
const { t } = useI18n();
// 表格数据
const packageData = ref([]);
const pagination = ref({
current: 1,
pageSize: 8,
total: 0,
showSizeChanger: true,
showTotal: (total: number) => `${t('page.package.total')} ${total} `
});
const loading = ref(false);
// 格式化流量
const formatTrafficValue = (bytes: number): string => {
if (!bytes) return '0 B';
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
let value = bytes;
let unitIndex = 0;
while (value >= 1024 && unitIndex < units.length - 1) {
value /= 1024;
unitIndex++;
}
return `${value.toFixed(2)} ${units[unitIndex]}`;
};
// 格式化带宽
const formatBandwidthValue = (kbps: number): string => {
if (!kbps) return '0 Kbps';
if (kbps < 1024) return `${kbps} Kbps`;
if (kbps < 1024 * 1024) return `${(kbps / 1024).toFixed(2)} Mbps`;
return `${(kbps / (1024 * 1024)).toFixed(2)} Gbps`;
};
// 格式化时间周期
const formatPeriod = (num: number, type: number): string => {
const typeMap = {
1: t('page.package.day'),
2: t('page.package.month'),
3: t('page.package.year')
};
return `${num}${typeMap[type] || ''}`;
};
// 获取套餐列表
const fetchPackageList = async () => {
try {
loading.value = true;
const params = {
pageNum: pagination.value.current,
pageSize: pagination.value.pageSize
};
const res = await fetchUserPackages(params);
if (res.data) {
packageData.value = res.data.rows;
pagination.value.total = res.data.total;
}
} catch (error) {
console.error('Failed to fetch package list:', error);
} finally {
loading.value = false;
}
};
// 处理分页变化
const handlePageChange = (page: number, pageSize: number) => {
pagination.value.current = page;
pagination.value.pageSize = pageSize;
fetchPackageList();
};
onMounted(() => {
fetchPackageList();
});
</script>
<template>
<div class="package-list-container">
<a-card :bordered="false">
<template #title>
<div class="card-title">
<span>{{ t('page.package.myPackages') }}</span>
</div>
</template>
<a-spin :spinning="loading">
<div class="package-grid">
<a-card
v-for="item in packageData"
:key="item.id"
class="package-card"
:bordered="true"
>
<div class="package-header">
<h3 class="package-name">{{ item.packageName }}</h3>
<a-tag :color="item.status === 1 ? 'success' : 'error'" class="package-status">
{{ item.status === 1 ? t('page.package.active') : t('page.package.expired') }}
</a-tag>
</div>
<div class="package-info">
<div class="info-item">
<span class="label">{{ t('page.package.price') }}:</span>
<span class="value price">¥{{ Number(item.price).toFixed(2) }}</span>
</div>
<div class="info-item">
<span class="label">{{ t('page.package.period') }}:</span>
<span class="value">{{ formatPeriod(item.periodNum, item.periodType) }}</span>
</div>
<div class="info-item">
<span class="label">{{ t('page.package.traffic') }}:</span>
<span class="value">{{ item.trafficEnable ? formatTrafficValue(item.traffic) : t('page.package.unlimited') }}</span>
</div>
<div class="info-item">
<span class="label">{{ t('page.package.devices') }}:</span>
<span class="value">{{ item.clientNumEnable ? item.clientNum : t('page.package.unlimited') }}</span>
</div>
<div class="info-item">
<span class="label">{{ t('page.package.upLimit') }}:</span>
<span class="value">{{ !item.rateLimitEnable ? t('page.package.unlimited') : (item.upLimitEnable ? formatBandwidthValue(item.upLimit) : t('page.package.unlimited')) }}</span>
</div>
<div class="info-item">
<span class="label">{{ t('page.package.downLimit') }}:</span>
<span class="value">{{ !item.rateLimitEnable ? t('page.package.unlimited') : (item.downLimitEnable ? formatBandwidthValue(item.downLimit) : t('page.package.unlimited')) }}</span>
</div>
</div>
</a-card>
</div>
<div class="pagination-container">
<a-pagination
v-model:current="pagination.current"
v-model:pageSize="pagination.pageSize"
:total="pagination.total"
:show-size-changer="pagination.showSizeChanger"
:show-total="pagination.showTotal"
@change="handlePageChange"
/>
</div>
</a-spin>
</a-card>
</div>
</template>
<style scoped>
.package-list-container {
padding: 16px;
}
.card-title {
display: flex;
align-items: center;
gap: 8px;
font-size: 16px;
font-weight: 500;
}
.package-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 16px;
margin-bottom: 24px;
}
.package-card {
transition: all 0.3s;
}
.package-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
}
.package-name {
margin: 0;
font-size: 16px;
font-weight: 500;
}
.package-status {
margin: 0;
}
.package-info {
display: flex;
flex-direction: column;
gap: 12px;
}
.info-item {
display: flex;
justify-content: space-between;
align-items: center;
}
.value {
font-size: 14px;
}
.value.price {
font-size: 16px;
font-weight: 500;
color: #ff4d4f;
}
.pagination-container {
margin-top: 24px;
display: flex;
justify-content: center;
}
:deep(.ant-card-head) {
padding: 0 12px;
}
:deep(.ant-card-body) {
padding: 12px;
}
/* 移动端适配 */
@media screen and (max-width: 768px) {
.package-list-container {
padding: 8px;
}
.package-grid {
grid-template-columns: 1fr;
gap: 12px;
}
.package-card {
margin-bottom: 0;
}
.info-item {
font-size: 13px;
}
}
</style>