后端功能增强:全局异常处理、API控制器、JSP视图和单元测试
- 添加 GlobalExceptionHandler 全局异常处理 - 添加 ApiController REST API 控制器 - 更新 WebConfig 跨域配置和 ProductRepository 查询方法 - 新增 monitor/product-detail/profile JSP 视图页面 - 添加 FlashSaleServiceTest 秒杀服务单元测试 - 更新 application.yml 配置
This commit is contained in:
227
flash-sale-frontend/src/pages/product/index.vue
Normal file
227
flash-sale-frontend/src/pages/product/index.vue
Normal file
@@ -0,0 +1,227 @@
|
||||
<template>
|
||||
<div class="products-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="text-blue-500 mr-2"><ShoppingBag /></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-select
|
||||
v-model="filters.category"
|
||||
placeholder="选择分类"
|
||||
clearable
|
||||
style="width: 150px"
|
||||
@change="loadProducts"
|
||||
>
|
||||
<el-option
|
||||
v-for="cat in categories"
|
||||
:key="cat"
|
||||
:label="cat"
|
||||
:value="cat"
|
||||
/>
|
||||
</el-select>
|
||||
|
||||
<!-- 价格区间 -->
|
||||
<div class="flex items-center gap-2">
|
||||
<el-input-number
|
||||
v-model="filters.minPrice"
|
||||
:min="0"
|
||||
placeholder="最低价"
|
||||
style="width: 120px"
|
||||
@change="loadProducts"
|
||||
/>
|
||||
<span>-</span>
|
||||
<el-input-number
|
||||
v-model="filters.maxPrice"
|
||||
:min="0"
|
||||
placeholder="最高价"
|
||||
style="width: 120px"
|
||||
@change="loadProducts"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 排序 -->
|
||||
<el-radio-group v-model="filters.sort" @change="loadProducts">
|
||||
<el-radio-button label="default">默认</el-radio-button>
|
||||
<el-radio-button label="price-asc">价格升序</el-radio-button>
|
||||
<el-radio-button label="price-desc">价格降序</el-radio-button>
|
||||
<el-radio-button label="sales">销量</el-radio-button>
|
||||
</el-radio-group>
|
||||
|
||||
<!-- 搜索 -->
|
||||
<el-input
|
||||
v-model="filters.keyword"
|
||||
placeholder="搜索商品"
|
||||
style="width: 200px"
|
||||
clearable
|
||||
@keyup.enter="loadProducts"
|
||||
>
|
||||
<template #suffix>
|
||||
<el-icon class="cursor-pointer" @click="loadProducts">
|
||||
<Search />
|
||||
</el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
</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="products.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">
|
||||
<ProductCard
|
||||
v-for="item in products"
|
||||
:key="item.id"
|
||||
:data="item"
|
||||
@add-to-cart="handleAddToCart"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div class="mt-8 flex justify-center">
|
||||
<el-pagination
|
||||
v-model:current-page="pagination.page"
|
||||
v-model:page-size="pagination.size"
|
||||
:total="pagination.total"
|
||||
:page-sizes="[12, 24, 36, 48]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@size-change="loadProducts"
|
||||
@current-change="loadProducts"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import ProductCard from '@/components/business/ProductCard.vue'
|
||||
import { productApi } from '@/api/modules/product'
|
||||
import { useCartStore } from '@/stores/cart'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import type { Product } from '@/types/api'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const cartStore = useCartStore()
|
||||
const userStore = useUserStore()
|
||||
|
||||
// 数据状态
|
||||
const loading = ref(false)
|
||||
const products = ref<Product[]>([])
|
||||
const categories = ref<string[]>([])
|
||||
|
||||
// 筛选条件
|
||||
const filters = reactive({
|
||||
keyword: '',
|
||||
category: '',
|
||||
minPrice: undefined as number | undefined,
|
||||
maxPrice: undefined as number | undefined,
|
||||
sort: 'default'
|
||||
})
|
||||
|
||||
// 分页
|
||||
const pagination = reactive({
|
||||
page: 1,
|
||||
size: 12,
|
||||
total: 0
|
||||
})
|
||||
|
||||
// 加载商品列表
|
||||
const loadProducts = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const params: any = {
|
||||
page: pagination.page - 1,
|
||||
size: pagination.size
|
||||
}
|
||||
|
||||
if (filters.keyword) params.keyword = filters.keyword
|
||||
if (filters.category) params.category = filters.category
|
||||
if (filters.minPrice !== undefined) params.minPrice = filters.minPrice
|
||||
if (filters.maxPrice !== undefined) params.maxPrice = filters.maxPrice
|
||||
|
||||
// 处理排序
|
||||
if (filters.sort === 'price-asc') {
|
||||
params.sort = 'price'
|
||||
params.order = 'asc'
|
||||
} else if (filters.sort === 'price-desc') {
|
||||
params.sort = 'price'
|
||||
params.order = 'desc'
|
||||
} else if (filters.sort === 'sales') {
|
||||
params.sort = 'sales'
|
||||
params.order = 'desc'
|
||||
}
|
||||
|
||||
const res = await productApi.getList(params)
|
||||
|
||||
if (res.success) {
|
||||
products.value = res.data.content
|
||||
pagination.total = res.data.totalElements
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载商品列表失败:', error)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 加载分类
|
||||
const loadCategories = async () => {
|
||||
try {
|
||||
const res = await productApi.getCategories()
|
||||
if (res.success) {
|
||||
categories.value = res.data
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载分类失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 添加到购物车
|
||||
const handleAddToCart = async (productId: number) => {
|
||||
if (!userStore.isLoggedIn) {
|
||||
ElMessage.warning('请先登录')
|
||||
router.push('/login')
|
||||
return
|
||||
}
|
||||
|
||||
await cartStore.addToCart(productId)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 从路由参数获取搜索关键词
|
||||
if (route.query.keyword) {
|
||||
filters.keyword = route.query.keyword as string
|
||||
}
|
||||
|
||||
loadCategories()
|
||||
loadProducts()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.products-page {
|
||||
min-height: calc(100vh - 60px);
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user