Files
FlashSaleSystem/community-fresh-group-buy-frontend/src/pages/flashsale/index.vue
2026-05-06 23:30:54 +08:00

287 lines
7.6 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>
<div class="flashsale-page">
<div class="container mx-auto px-4 py-8">
<!-- 页面标题 -->
<div class="mb-8">
<h1 class="text-3xl font-bold mb-2 flex items-center">
<el-icon class="page-icon mr-2">
<Lightning/>
</el-icon>
限时活动
</h1>
<p class="text-gray-600">限时抢购先到先得</p>
</div>
<!-- 筛选栏 -->
<div class="bg-white rounded-lg shadow-sm p-4 mb-6">
<div class="flex flex-wrap gap-4 items-center">
<!-- 状态筛选 -->
<el-radio-group v-model="filters.status" @change="loadFlashSales">
<el-radio-button label="">全部</el-radio-button>
<el-radio-button label="UPCOMING">即将开始</el-radio-button>
<el-radio-button label="ACTIVE">进行中</el-radio-button>
<el-radio-button label="ENDED">已结束</el-radio-button>
</el-radio-group>
<!-- 排序 -->
<el-select
v-model="filters.sort"
placeholder="排序方式"
style="width: 150px"
@change="loadFlashSales"
>
<el-option label="开始时间" value="startTime"/>
<el-option label="结束时间" value="endTime"/>
<el-option label="价格从低到高" value="flashPrice"/>
<el-option label="折扣力度" value="discount"/>
</el-select>
<!-- 搜索 -->
<el-input
v-model="filters.keyword"
clearable
placeholder="搜索商品名称"
style="width: 200px"
@keyup.enter="loadFlashSales"
>
<template #suffix>
<el-icon class="cursor-pointer" @click="loadFlashSales">
<Search/>
</el-icon>
</template>
</el-input>
<!-- 刷新按钮 -->
<el-button @click="handleRefresh">
<el-icon class="mr-1">
<Refresh/>
</el-icon>
刷新
</el-button>
</div>
</div>
<!-- 统计信息 -->
<div class="grid grid-cols-1 md:grid-cols-4 gap-4 mb-6">
<div class="stat-card tone-1">
<div class="stat-value">{{ statistics.upcoming }}</div>
<div class="stat-label">即将开始</div>
<el-icon :size="30" class="stat-icon">
<Clock/>
</el-icon>
</div>
<div class="stat-card tone-2">
<div class="stat-value">{{ statistics.active }}</div>
<div class="stat-label">正在进行</div>
<el-icon :size="30" class="stat-icon">
<Lightning/>
</el-icon>
</div>
<div class="stat-card tone-3">
<div class="stat-value">{{ statistics.participated }}</div>
<div class="stat-label">我的参与</div>
<el-icon :size="30" class="stat-icon">
<Trophy/>
</el-icon>
</div>
<div class="stat-card tone-4">
<div class="stat-value">{{ statistics.success }}</div>
<div class="stat-label">抢购成功</div>
<el-icon :size="30" class="stat-icon">
<SuccessFilled/>
</el-icon>
</div>
</div>
<!-- 限时活动列表 -->
<div v-if="loading" class="text-center py-12">
<el-icon :size="40" class="animate-spin">
<Loading/>
</el-icon>
<p class="mt-2 text-gray-500">加载中...</p>
</div>
<div v-else-if="flashSales.length === 0" class="text-center py-12">
<el-empty description="暂无限时活动"/>
</div>
<div v-else>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
<FlashSaleCard
v-for="item in flashSales"
:key="item.id"
:data="item"
@participate="handleParticipate"
@refresh="loadFlashSales"
/>
</div>
<!-- 分页 -->
<div class="mt-8 flex justify-center">
<el-pagination
v-model:current-page="pagination.page"
v-model:page-size="pagination.size"
:page-sizes="[12, 24, 36, 48]"
:total="pagination.total"
layout="total, sizes, prev, pager, next, jumper"
@size-change="loadFlashSales"
@current-change="loadFlashSales"
/>
</div>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import {ref, reactive, onMounted} from 'vue'
import {useRouter} from 'vue-router'
import {ElMessage, ElMessageBox} from 'element-plus'
import FlashSaleCard from '@/components/business/FlashSaleCard.vue'
import {flashsaleApi} from '@/api/modules/flashsale'
import {useUserStore} from '@/stores/user'
import type {FlashSale} from '@/types/api'
const router = useRouter()
const userStore = useUserStore()
// 数据状态
const loading = ref(false)
const flashSales = ref<FlashSale[]>([])
// 筛选条件
const filters = reactive({
status: '',
sort: 'startTime',
keyword: ''
})
// 分页
const pagination = reactive({
page: 1,
size: 12,
total: 0
})
// 统计信息
const statistics = reactive({
upcoming: 0,
active: 0,
participated: 0,
success: 0
})
// 加载限时活动
const loadFlashSales = async () => {
loading.value = true
try {
const res = await flashsaleApi.getList({
...filters,
page: pagination.page - 1,
size: pagination.size
})
if (res.success) {
flashSales.value = res.data.content
pagination.total = res.data.totalElements
}
} catch (error) {
console.error('加载限时活动失败:', error)
} finally {
loading.value = false
}
}
// 加载统计信息(从后端获取真实数据)
const loadStatistics = async () => {
try {
const res = await flashsaleApi.getStatistics()
if (res.success) {
statistics.upcoming = res.data.upcoming ?? 0
statistics.active = res.data.active ?? 0
statistics.participated = res.data.participated ?? 0
statistics.success = res.data.success ?? 0
}
} catch (error) {
console.error('加载统计信息失败:', error)
}
}
// 参与限时
const handleParticipate = async (flashSaleId: number) => {
if (!userStore.isLoggedIn) {
ElMessage.warning('请先登录')
router.push('/login')
return
}
// 先检查资格
try {
const res = await flashsaleApi.checkEligibility(flashSaleId)
if (res.success && res.data.eligible) {
// 确认对话框
await ElMessageBox.confirm(
'确定要参与这个限时活动吗?',
'提示',
{
confirmButtonText: '立即抢购',
cancelButtonText: '取消',
type: 'warning',
}
)
// 跳转到详情页参与
router.push(`/flashsale/${flashSaleId}`)
} else {
ElMessage.warning(res.data.reason || '您暂时无法参与此活动')
}
} catch (error) {
// 用户取消或错误
}
}
// 刷新
const handleRefresh = () => {
loadFlashSales()
loadStatistics()
ElMessage.success('已刷新')
}
onMounted(() => {
loadFlashSales()
loadStatistics()
})
</script>
<style lang="scss" scoped>
.flashsale-page {
min-height: calc(100vh - 60px);
background: transparent;
}
.page-icon {
color: #44443f;
}
.stat-card {
@apply relative overflow-hidden rounded-lg p-4;
background: #fffaf2;
color: #171715;
border: 1px solid #d8cebf;
box-shadow: 0 10px 24px rgba(23, 22, 20, 0.04);
.stat-value {
@apply text-2xl font-bold;
}
.stat-label {
@apply text-sm mt-1;
}
.stat-icon {
@apply absolute right-4 bottom-4;
opacity: 0.2;
}
}
</style>