Files
WAI_Project_UNIX/pages/index/shop.uvue
2026-01-21 01:37:34 +08:00

364 lines
7.4 KiB
Plaintext

<template>
<cl-page>
<view class="shop-page">
<!-- 自定义顶部导航栏 (Fixed Top) -->
<view class="custom-navbar" :style="{ paddingTop: statusBarHeight + 'px' }">
<view class="navbar-content">
<text class="page-title">农场店</text>
</view>
</view>
<!-- 顶部占位 (Status + NavHeight 44) -->
<view :style="{ height: (statusBarHeight + 44) + 'px' }"></view>
<!-- 搜索栏与购物车 (Flows normally below navbar) -->
<view class="search-row">
<view class="search-bar" @click="goSearch">
<cl-icon name="search-line" :size="32" color="#999" />
<text class="search-placeholder">搜索农特产品</text>
</view>
<view class="cart-btn-wrap" @click="goCart">
<view class="cart-btn">
<cl-icon name="shopping-cart-line" :size="40" color="#333" />
</view>
<view class="badge" v-if="cartCount > 0">
<text class="badge-text">{{ cartCount }}</text>
</view>
</view>
</view>
<!-- 分类导航 -->
<scroll-view scroll-x class="category-scroll" :show-scrollbar="false">
<view class="category-list">
<view
v-for="cat in categories"
:key="cat.id"
class="category-item"
:class="{ active: currentCategory === cat.id }"
@click="selectCategory(cat.id)"
>
<text class="category-text" :class="{ 'active-text': currentCategory === cat.id }">{{ cat.name }}</text>
</view>
</view>
</scroll-view>
<!-- 商品列表 -->
<scroll-view scroll-y class="product-scroll" @scrolltolower="loadMore">
<view class="product-list">
<view class="product-row">
<view
v-for="(product, index) in productList"
:key="product.id"
class="product-card"
@click="goProductDetail(product.id)"
>
<image class="product-image" :src="product.mainImage || '/static/images/product.png'" mode="aspectFill" />
<view class="product-info">
<text class="product-name">{{ product.name }}</text>
<text class="product-origin">{{ product.origin }}</text>
<view class="product-footer">
<text class="product-price">¥{{ product.price }}</text>
<view class="add-btn" @click.stop="addToCart(product)">
<cl-icon name="add-line" :size="28" color="#fff" />
</view>
</view>
</view>
</view>
</view>
</view>
<cl-empty v-if="productList.length === 0" text="暂无商品" />
</scroll-view>
</view>
<!-- 底部TabBar -->
<custom-tabbar :current="2" />
</cl-page>
</template>
<script setup lang="uts">
import { ref, onMounted } from 'vue';
import { router, useCool } from '@/cool';
import CustomTabbar from '@/components/tabbar.uvue';
const { service } = useCool();
const statusBarHeight = ref(uni.getWindowInfo().statusBarHeight);
const categories = ref<any[]>([
{ id: 0, name: '全部' }
]);
const currentCategory = ref(0);
const productList = ref<any[]>([]);
const cartCount = ref(2);
// 加载分类
async function loadCategories() {
try {
const res = await service.nongchuang.category.list();
if (Array.isArray(res)) {
categories.value = [{ id: 0, name: '全部' }, ...res];
}
} catch (e) {
console.error('加载分类失败', e);
}
}
async function loadProducts() {
try {
const params: any = {};
if (currentCategory.value > 0) {
params.categoryId = currentCategory.value;
}
const res = await service.nongchuang.product.list(params);
if (res != null) {
// 后端直接返回数组或包含list的对象
if (Array.isArray(res)) {
productList.value = res;
} else if (res.list != null) {
productList.value = res.list as any[];
} else {
productList.value = [];
}
}
} catch (e) {
console.error('加载商品失败', e);
productList.value = [];
}
}
function selectCategory(id: number) {
currentCategory.value = id;
loadProducts();
}
function loadMore() {
// 加载更多
}
function goSearch() {
router.push({ path: '/pages/mall/search' });
}
function goCart() {
router.push({ path: '/pages/mall/cart' });
}
function goProductDetail(id: number) {
router.push({ path: '/pages/mall/detail', query: { id } });
}
async function addToCart(product: any) {
try {
await service.nongchuang.cart.add({ productId: product.id, quantity: 1 });
cartCount.value++;
uni.showToast({ title: '已加入购物车', icon: 'success' });
} catch (e: any) {
const msg = e.message;
uni.showToast({ title: msg != null ? msg : '添加失败', icon: 'none' });
}
}
onMounted(() => {
loadCategories();
loadProducts();
});
</script>
<style lang="scss" scoped>
.shop-page {
flex: 1;
background-color: #f5f5f5;
padding-bottom: 120rpx;
}
.custom-navbar {
position: fixed;
top: 0;
left: 0;
width: 100%;
z-index: 999;
background-color: #fff;
}
.navbar-content {
height: 44px; /* Standard Nav Height */
display: flex;
align-items: center;
padding-left: 30rpx;
}
.page-title {
font-size: 34rpx;
font-weight: bold;
color: #333;
}
.search-row {
display: flex;
flex-direction: row;
align-items: center;
padding: 20rpx 30rpx;
background-color: #fff;
/* 避免胶囊按钮遮挡,如果是放在顶部 */
}
.cart-btn-wrap {
position: relative;
margin-left: 20rpx;
}
.cart-btn {
width: 72rpx;
height: 72rpx;
display: flex;
align-items: center;
justify-content: center;
background: #fff;
border-radius: 50%;
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.08); /* 增加阴影提升质感 */
border: 1rpx solid #f0f0f0;
}
.badge {
position: absolute;
top: -6rpx;
right: -6rpx;
min-width: 32rpx;
height: 32rpx;
background-color: #ff4d4f;
border-radius: 16rpx;
display: flex;
align-items: center;
justify-content: center;
padding: 0 8rpx;
z-index: 10;
border: 2rpx solid #fff; /* White border for separation */
}
.badge-text {
font-size: 20rpx;
color: #fff;
line-height: 1;
font-weight: bold;
}
.search-bar {
flex: 1;
display: flex;
flex-direction: row;
align-items: center;
padding: 16rpx 30rpx;
background-color: #f5f5f5;
border-radius: 40rpx;
height: 72rpx;
}
.search-placeholder {
font-size: 28rpx;
color: #999;
margin-left: 16rpx;
}
.category-scroll {
background-color: #fff;
}
.category-list {
display: flex;
flex-direction: row;
padding: 20rpx;
}
.category-item {
padding: 16rpx 30rpx;
margin-right: 16rpx;
background-color: #f5f5f5;
border-radius: 30rpx;
}
.category-item.active {
background-color: #52c41a;
}
.category-text {
font-size: 26rpx;
color: #666;
}
.active-text {
color: #fff;
}
.product-scroll {
flex: 1;
padding: 20rpx;
}
.product-list {
display: flex;
flex-direction: column;
}
.product-row {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-between;
}
.product-card {
width: 48%;
background-color: #fff;
border-radius: 16rpx;
overflow: hidden;
margin-bottom: 20rpx;
}
.product-image {
width: 100%;
height: 280rpx;
}
.product-info {
padding: 16rpx;
}
.product-name {
font-size: 28rpx;
font-weight: bold;
color: #333;
}
.product-origin {
font-size: 22rpx;
color: #999;
margin-top: 6rpx;
}
.product-footer {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
margin-top: 16rpx;
}
.product-price {
font-size: 32rpx;
font-weight: bold;
color: #ff4d4f;
}
.add-btn {
width: 48rpx;
height: 48rpx;
background-color: #52c41a;
border-radius: 24rpx;
display: flex;
align-items: center;
justify-content: center;
}
</style>