132 lines
2.9 KiB
Vue
132 lines
2.9 KiB
Vue
<template>
|
|
<view class="home-page">
|
|
<!-- 分类筛选 -->
|
|
<scroll-view class="category-bar" scroll-x>
|
|
<view
|
|
v-for="cat in categories"
|
|
:key="cat.id"
|
|
class="category-item"
|
|
:class="{ 'category-item--active': activeCategoryId === cat.id }"
|
|
@click="selectCategory(cat.id)"
|
|
>
|
|
{{ cat.name }}
|
|
</view>
|
|
</scroll-view>
|
|
|
|
<!-- 商品列表 -->
|
|
<view class="product-grid">
|
|
<view v-for="product in products" :key="product.id" class="product-grid__item">
|
|
<ProductCard :product="product" />
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 空状态 -->
|
|
<view v-if="!loading && products.length === 0" class="empty-tip">
|
|
<text>暂无商品</text>
|
|
</view>
|
|
|
|
<!-- 加载更多 -->
|
|
<view v-if="loading" class="loading-tip">
|
|
<text>加载中...</text>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, onMounted } from 'vue'
|
|
import type { Product, Category } from '../../types'
|
|
import { getProducts, getCategories } from '../../api/product'
|
|
import ProductCard from '../../components/ProductCard.vue'
|
|
|
|
const products = ref<Product[]>([])
|
|
const categories = ref<Category[]>([])
|
|
const activeCategoryId = ref<number | undefined>(undefined)
|
|
const loading = ref(false)
|
|
const page = ref(1)
|
|
const pageSize = 20
|
|
|
|
async function loadCategories() {
|
|
try {
|
|
const data = await getCategories()
|
|
categories.value = [{ id: 0, name: '全部', sort: 0 } as Category, ...data]
|
|
} catch {
|
|
// 错误已在 request 中统一处理
|
|
}
|
|
}
|
|
|
|
async function loadProducts(reset = false) {
|
|
if (reset) {
|
|
page.value = 1
|
|
products.value = []
|
|
}
|
|
loading.value = true
|
|
try {
|
|
const params: Record<string, unknown> = { page: page.value, pageSize }
|
|
if (activeCategoryId.value && activeCategoryId.value !== 0) {
|
|
params.categoryId = activeCategoryId.value
|
|
}
|
|
const data = await getProducts(params as { categoryId?: number; page?: number; pageSize?: number })
|
|
if (reset) {
|
|
products.value = data.list
|
|
} else {
|
|
products.value.push(...data.list)
|
|
}
|
|
} catch {
|
|
// 错误已在 request 中统一处理
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
function selectCategory(id: number) {
|
|
activeCategoryId.value = id
|
|
loadProducts(true)
|
|
}
|
|
|
|
onMounted(() => {
|
|
loadCategories()
|
|
loadProducts(true)
|
|
})
|
|
</script>
|
|
|
|
<style scoped>
|
|
.home-page {
|
|
min-height: 100vh;
|
|
background: #f5f5f5;
|
|
}
|
|
.category-bar {
|
|
white-space: nowrap;
|
|
background: #fff;
|
|
padding: 20rpx 16rpx;
|
|
}
|
|
.category-item {
|
|
display: inline-block;
|
|
padding: 12rpx 28rpx;
|
|
margin-right: 16rpx;
|
|
font-size: 26rpx;
|
|
color: #666;
|
|
background: #f5f5f5;
|
|
border-radius: 28rpx;
|
|
}
|
|
.category-item--active {
|
|
color: #fff;
|
|
background: #e4393c;
|
|
}
|
|
.product-grid {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
padding: 16rpx;
|
|
gap: 16rpx;
|
|
}
|
|
.product-grid__item {
|
|
width: calc(50% - 8rpx);
|
|
}
|
|
.empty-tip,
|
|
.loading-tip {
|
|
text-align: center;
|
|
padding: 60rpx 0;
|
|
color: #999;
|
|
font-size: 28rpx;
|
|
}
|
|
</style>
|