2025-09-03 17:13:10 +08:00

556 lines
12 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>
<view class="point-mall-container">
<!-- 顶部导航栏 -->
<uni-nav-bar
left-icon="left"
title="积分商城"
@clickLeft="goBack"
fixed
color="#e74c3c"
height="140rpx"
:border="false"
backgroundColor="#ffffff"
>
<template v-slot:right>
<text class="search-btn" @click="goToSearch">搜索</text>
</template>
</uni-nav-bar>
<!-- 轮播图横幅 -->
<view class="banner-section">
<swiper class="banner-swiper" :indicator-dots="true" :autoplay="true" :interval="3000" :duration="500">
<swiper-item v-for="(banner, index) in bannerList" :key="banner.uuid">
<view class="banner-item" @click="goToBannerDetail(banner)">
<image class="banner-image" :src="banner.headImg" mode="aspectFill"></image>
<view class="banner-content">
<view class="banner-text green">{{ banner.title }}</view>
<view class="banner-text orange">{{ banner.create_date }}</view>
</view>
</view>
</swiper-item>
<!-- 如果没有数据显示默认轮播图 -->
<swiper-item v-if="bannerList.length === 0">
<view class="banner-item">
<image class="banner-image" src="/static/banner-bg.jpg" mode="aspectFill"></image>
<view class="banner-content">
<view class="banner-text green">第二届京津冀感染肝病高峰论坛&</view>
<view class="banner-text green">第九届河北省感染科医师培训班</view>
<view class="banner-text orange">2016.10.14-2016.10.16</view>
<view class="banner-text orange">河北石家庄</view>
</view>
</view>
</swiper-item>
</swiper>
</view>
<!-- 筛选排序栏 -->
<view class="filter-bar">
<view class="filter-item" @click="showFilter">
<text class="filter-text">{{ selectedTagName || '筛选' }}</text>
<text class="filter-icon"></text>
</view>
<view class="filter-divider"></view>
<view class="filter-item" @click="showSort">
<text class="filter-text">{{ selectedSortLabel || '排序' }}</text>
<text class="filter-icon"></text>
</view>
</view>
<!-- 筛选下拉 -->
<view v-if="showFilterDropdown" class="dropdown">
<view
class="dropdown-item"
:class="{ active: selectedTagId === null }"
@click="onTagSelect({ id: null, name: '全部' })">
全部
</view>
<view
v-for="tag in tagList"
:key="tag.id"
class="dropdown-item"
:class="{ active: selectedTagId === tag.id }"
@click="onTagSelect(tag)">
{{ tag.name }}
</view>
</view>
<!-- 排序下拉 -->
<view v-if="showSortDropdown" class="dropdown">
<view
v-for="opt in sortOptions"
:key="opt.value"
class="dropdown-item"
:class="{ active: selectedSortValue === opt.value }"
@click="onSortSelect(opt)">
{{ opt.label }}
</view>
</view>
<!-- 商品网格上拉加载 -->
<scroll-view class="product-grid" scroll-y @scrolltolower="getGoodsList" :lower-threshold="100">
<view class="grid-wrap">
<view class="product-item" v-for="(product, index) in products" :key="product.uuid || index" @click="goToProductDetail(product)">
<view class="product-image-container">
<image class="product-image" :src="product.image" mode="aspectFill"></image>
</view>
<view class="product-info">
<text class="product-title">{{ product.title }}</text>
<text class="product-price">{{ product.price }}积分</text>
</view>
</view>
</view>
</scroll-view>
<!-- 加载与无更多提示 -->
<view class="list-footer" v-if="isLoadingMore">
<text>加载中...</text>
</view>
<view class="list-footer" v-else-if="noMore && products.length > 0">
<text>没有更多了</text>
</view>
<!-- 底部导航栏 -->
<view class="bottom-nav">
<view class="nav-item active" @click="goToMyRedemption">
<view class="nav-icon">💰</view>
<text class="nav-text">我的兑换</text>
</view>
<view class="nav-divider"></view>
<view class="nav-item" @click="goToBuyPoints">
<view class="nav-icon">🛒</view>
<text class="nav-text">购买积分</text>
</view>
</view>
</view>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import goods_api from '@/api/goods_api'
import docUrl from '@/utils/docUrl'
// 轮播图数据
const bannerList = ref([])
// 商品数据
const products = ref([])
const page = ref(1)
const pageSize = ref(10)
const isLoadingMore = ref(false)
const noMore = ref(false)
// 筛选相关
const tagList = ref([])
const selectedTagId = ref(null)
const selectedTagName = ref('')
const showFilterDropdown = ref(false)
// 排序相关
const showSortDropdown = ref(false)
const selectedSortValue = ref(null)
const selectedSortLabel = ref('')
const sortOptions = ref([
{ label: '积分从小到大', value: 4 },
{ label: '积分从大到小', value: 3 },
{ label: '兑换从多到少', value: 5 },
{ label: '兑换从少到多', value: 6 }
])
// 方法
const goBack = () => {
uni.navigateBack()
}
const goToSearch = () => {
uni.navigateTo({
url: '/pages_app/search/search'
})
}
const showFilter = () => {
showFilterDropdown.value = !showFilterDropdown.value
}
const showSort = () => {
showSortDropdown.value = !showSortDropdown.value
}
const onTagSelect = (tag) => {
selectedTagId.value = tag.id
selectedTagName.value = tag.name
showFilterDropdown.value = false
// 重置并拉取
products.value = []
page.value = 1
noMore.value = false
getGoodsList()
}
const onSortSelect = (opt) => {
selectedSortValue.value = opt.value
selectedSortLabel.value = opt.label
showSortDropdown.value = false
products.value = []
page.value = 1
noMore.value = false
getGoodsList()
}
const goToProductDetail = (product) => {
const id = product.uuid || product.id
uni.navigateTo({
url: `/pages_goods/productDetail/productDetail?id=${id}`
})
}
const goToMyRedemption = () => {
uni.navigateTo({
url: '/pages_goods/myRedemption/myRedemption'
})
}
const goToBuyPoints = () => {
uni.navigateTo({
url: '/pages_app/buyPoint/buyPoint'
})
}
const goToBannerDetail = (banner) => {
// 跳转到轮播图详情页面
uni.navigateTo({
url: `/pages_app/webview/webview?url=${encodeURIComponent(banner.path)}&title=${encodeURIComponent(banner.title)}`
})
}
const getGoodsNewsList = () => {
goods_api.goodsNewsList().then(res => {
console.log('轮播图数据:', res)
if (res.code === 200 && res.data && res.data.length > 0) {
// 处理轮播图数据
bannerList.value = res.data.map(item => ({
uuid: item.uuid,
title: item.title,
headImg: docUrl + item.headImg,
create_date: item.create_date,
path: item.path,
agreenum: item.agreenum,
readnum: item.readnum
}))
} else {
console.log('轮播图数据为空或请求失败')
}
}).catch(err => {
console.error('获取轮播图数据失败:', err)
uni.showToast({
title: '获取数据失败',
icon: 'none'
})
})
}
const getGoodsList = () => {
if (noMore.value || isLoadingMore.value) return
isLoadingMore.value = true
const tagParam = selectedTagId.value ? selectedTagId.value : ""
const sortParam = selectedSortValue.value ? selectedSortValue.value : 1
goods_api.goodsList({ name: '', page: page.value, sort: sortParam, tag_type: tagParam }).then(res => {
console.log('商品数据:', res)
if ((res.code === 200 || res.code === '200') && res.data && res.data.list) {
const list = res.data.list.map(item => ({
uuid: item.uuid,
title: item.name,
price: item.bonuspoints,
image: docUrl + item.img,
times: item.times,
type: item.type,
upan: item.upan
}))
products.value = products.value.concat(list)
const { isLastPage, pageNum } = res.data
page.value = pageNum + 1
noMore.value = !!isLastPage
} else {
console.log('商品列表为空或请求失败')
}
}).catch(err => {
console.error('获取商品列表失败:', err)
uni.showToast({ title: '获取商品失败', icon: 'none' })
}).finally(() => {
isLoadingMore.value = false
})
}
const getGoodsTagList = () => {
goods_api.goodsTagList({}).then(res => {
console.log('商品类型数据:', res)
if (res.code === 200 && res.data) {
tagList.value = res.data.map(i => ({ id: i.id, name: i.name }))
}
})
}
onMounted(() => {
getGoodsNewsList()
// 初始化分页并加载第一页
page.value = 1
noMore.value = false
products.value = []
getGoodsList()
getGoodsTagList()
})
</script>
<style scoped>
.point-mall-container {
width: 100%;
min-height: 100vh;
background-color: #f5f5f5;
position: relative;
padding-bottom: 80px;
}
/* 搜索按钮样式 */
.search-btn {
color: #e74c3c;
font-size: 28rpx;
padding: 10rpx;
}
/* 轮播图样式 */
.banner-section {
margin: 0;
position: relative;
}
.banner-swiper {
height: 200px;
}
.banner-item {
position: relative;
width: 100%;
height: 100%;
}
.banner-image {
width: 100%;
height: 100%;
background: linear-gradient(135deg, #87CEEB 0%, #98FB98 50%, #F0E68C 100%);
}
.banner-content {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 20px;
}
.banner-text {
background-color: rgba(255, 255, 255, 0.95);
padding: 6px 12px;
margin: 3px 0;
border-radius: 6px;
font-size: 13px;
font-weight: bold;
text-align: center;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.banner-text.green {
color: #27ae60;
}
.banner-text.orange {
color: #e67e22;
}
/* 轮播图指示器样式 */
.banner-swiper ::v-deep .uni-swiper-dots {
bottom: 10px;
}
.banner-swiper ::v-deep .uni-swiper-dot {
width: 8px;
height: 8px;
background-color: rgba(255, 255, 255, 0.5);
border-radius: 50%;
margin: 0 4px;
}
.banner-swiper ::v-deep .uni-swiper-dot-active {
background-color: #fff;
}
/* 筛选排序栏样式 */
.filter-bar {
display: flex;
align-items: center;
background-color: #fff;
padding: 12px 16px;
border-bottom: 1px solid #eee;
}
.dropdown {
background: #fff;
border-top: 1px solid #eee;
border-bottom: 1px solid #eee;
padding: 8px 0;
}
.dropdown-item {
padding: 12px 16px;
font-size: 14px;
color: #333;
}
.dropdown-item.active {
color: #e74c3c;
font-weight: bold;
background: #fff7f7;
}
.filter-item {
display: flex;
align-items: center;
flex: 1;
justify-content: center;
padding: 8px;
}
.filter-text {
font-size: 14px;
color: #333;
margin-right: 4px;
}
.filter-icon {
font-size: 10px;
color: #666;
}
.filter-divider {
width: 1px;
height: 20px;
background-color: #ddd;
}
/* 商品网格样式 */
.product-grid {
height: calc(100vh - 320rpx); /* 预留顶部导航/筛选及底部栏 */
background-color: #f5f5f5;
}
.grid-wrap {
display: flex;
flex-wrap: wrap;
padding: 16px;
gap: 16px;
}
.list-footer {
text-align: center;
color: #999;
padding: 12px 0 80px; /* 避开底部吸底栏 */
}
.product-item {
width: calc(50% - 8px);
background-color: #fff;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
transition: transform 0.2s ease;
}
.product-item:active {
transform: scale(0.98);
}
.product-image-container {
width: 100%;
height: 120px;
overflow: hidden;
position: relative;
}
.product-image {
width: 100%;
height: 100%;
background-color: #f0f0f0;
}
.product-info {
padding: 12px;
}
.product-title {
font-size: 12px;
color: #333;
line-height: 1.4;
margin-bottom: 8px;
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.product-price {
font-size: 14px;
color: #e74c3c;
font-weight: bold;
}
/* 底部导航栏样式 */
.bottom-nav {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 60px;
background-color: #20b2aa;
display: flex;
align-items: center;
z-index: 1000;
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.1);
}
.nav-item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 8px;
transition: background-color 0.2s ease;
}
.nav-item.active {
background-color: rgba(255, 255, 255, 0.15);
border-radius: 8px;
margin: 4px;
}
.nav-icon {
font-size: 20px;
margin-bottom: 2px;
}
.nav-text {
font-size: 12px;
color: #fff;
font-weight: 500;
}
.nav-divider {
width: 1px;
height: 40px;
background-color: rgba(255, 255, 255, 0.3);
}
</style>