2025-09-23 19:00:32 +08:00

679 lines
16 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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>
<uni-nav-bar
left-icon="left"
title="积分商城"
@clickLeft="goBack"
fixed
color="#8B2316"
height="140rpx"
:border="false"
backgroundColor="#eeeeee"
></uni-nav-bar>
<view class="point-mall-page">
<!-- 促销横幅 - 固定在顶部 -->
<view class="promo-banner-fixed">
<view class="swipemask">
<view class="banner-subtitle">111111111</view>
<view class="dotbox">
<view class="circle" :class="{active:currentBannerIndex==index} "v-for="(banner, index) in bannerList"
:key="banner.id"></view>
</view>
</view>
<swiper
class="banner-swiper"
:indicator-dots="true"
:autoplay="true"
:interval="3000"
:duration="500"
indicator-color="rgba(255,255,255,0.3)"
indicator-active-color="#ffffff"
@change="onBannerChange"
>
<swiper-item v-for="(banner, index) in bannerList" :key="index">
<view class="banner-slide" :style="{ background: banner.gradient }">
<view class="banner-top">
<text class="dashed-line">{{ banner.topText }}</text>
</view>
<view class="banner-main">
<text class="main-text">{{ banner.mainText }}</text>
<text class="sub-text">{{ banner.subText }}</text>
</view>
<view class="banner-bottom">
<text class="mall-name">{{ banner.bottomText }}</text>
</view>
<!-- 装饰元素 -->
<view class="decorative-elements">
<view class="golden-coin" v-for="i in 6" :key="i">积分</view>
<view class="gift-box" v-for="i in 3" :key="i">🎁</view>
</view>
</view>
</swiper-item>
</swiper>
</view>
<!-- 筛选和排序栏 -->
<view class="filter-sort-bar">
<view class="filter-section" @click="showFilter">
<text class="filter-text">筛选</text>
<text class="arrow-down"></text>
</view>
<view class="divider"></view>
<view class="sort-section" @click="showSort">
<text class="sort-text">排序</text>
<text class="arrow-down"></text>
</view>
</view>
<!-- 筛选弹层 -->
<view v-if="filterVisible" class="filter-overlay" @click="hideFilter">
<view class="filter-panel" @click.stop>
<view class="filter-row" v-for="(cat, idx) in categories" :key="idx" @click="selectCategory(cat)">
<text class="row-text">{{ cat }}</text>
</view>
</view>
</view>
<!-- 排序弹层 -->
<view v-if="sortVisible" class="sort-overlay" @click="hideSort">
<view class="sort-panel" @click.stop>
<view class="sort-row" v-for="(opt, idx) in sortOptions" :key="idx" @click="selectSort(opt)">
<text class="row-text">{{ opt }}</text>
</view>
</view>
</view>
<!-- 商品网格列表 -->
<view class="productbox">
<scroll-view
class="products-grid"
scroll-y="true"
:show-scrollbar="false"
refresher-enabled="true"
:refresher-triggered="refreshing"
@refresherrefresh="onRefresh"
@scrolltolower="onLoadMore"
lower-threshold="80"
>
<view class="grid-container">
<view
class="product-item"
v-for="(product, index) in productsList"
:key="product.id || index"
@click="viewProduct(product)"
>
<view class="product-image">
<image :src="product.image" mode="aspectFit" />
</view>
<view class="product-info">
<text class="product-title">{{ product.title }}</text>
<text class="product-points">{{ product.points }}积分</text>
</view>
</view>
</view>
<!-- 加载更多提示 -->
<view v-if="loading" class="loading-more">
<text class="loading-text">加载中...</text>
</view>
<!-- 没有更多数据提示 -->
<view v-if="noMore" class="no-more">
<text class="no-more-text">没有更多了</text>
</view>
</scroll-view>
</view>
<!-- 底部导航栏 - 固定在底部 -->
<view class="bottom-nav-fixed">
<view class="nav-item" @click="goMyRedemption">
<view class="nav-icon">🎫</view>
<text class="nav-text">我的兑换</text>
</view>
<view class="divider"></view>
<view class="nav-item" @click="goBuyPoints">
<view class="nav-icon">🛍</view>
<text class="nav-text">购买积分</text>
</view>
</view>
</view>
</template>
<script setup>
import { ref } from 'vue';
// 轮播横幅数据
const bannerList = ref([
{
topText: '・积分商城・',
mainText: '领积分兑奖品',
subText: '今天你兑了吗?',
bottomText: '肝胆相照 积分商城',
gradient: 'linear-gradient(135deg, #ab47bc, #e91e63)'
},
{
topText: '・限时特惠・',
mainText: '积分大放送',
subText: '新用户专享福利',
bottomText: '肝胆相照 积分商城',
gradient: 'linear-gradient(135deg, #ff6b6b, #ee5a52)'
},
{
topText: '・新品上架・',
mainText: '精选好物',
subText: '积分兑换更划算',
bottomText: '肝胆相照 积分商城',
gradient: 'linear-gradient(135deg, #4ecdc4, #44a08d)'
}
]);
const currentBannerIndex = ref(0);
const onBannerChange = (e) => {
currentBannerIndex.value = (e?.detail?.current) ?? 0;
};
// 商品列表数据
const initialProducts = [
{ id: 1, title: '第11-12-13届全国疑难及重症肝病学术会议论文集', points: 4490, image: '/static/product1.png' },
{ id: 2, title: '第13届全国疑难及重症肝病学术会议论文集', points: 2450, image: '/static/product2.png' },
{ id: 3, title: '常见肝胆疾病 影像学诊断图谱', points: 4000, image: '/static/product3.png' },
{ id: 4, title: '肝恶性肿瘤钇[90Y]微球选择性内放射治疗典型案例集', points: 12560, image: '/static/product4.png' },
{ id: 5, title: '临床真菌病 诊疗手册', points: 3200, image: '/static/product5.png' },
{ id: 6, title: '肝肺综合征微创介入诊疗', points: 3800, image: '/static/product6.png' }
];
const productsList = ref([...initialProducts]);
// 列表状态
const refreshing = ref(false);
const loading = ref(false);
const noMore = ref(false);
const page = ref(1);
const pageSize = ref(6);
// 筛选弹层
const filterVisible = ref(false);
const categories = ref(['医学书籍', '知识U盘', '京东E卡', '日常用品']);
const selectedCategory = ref('');
// 排序弹层
const sortVisible = ref(false);
const sortOptions = ref(['综合排序', '积分从低到高', '积分从高到低', '最新上架']);
const selectedSort = ref('综合排序');
// 模拟更多数据(真实项目中替换为接口分页返回)
const mockMore = [
{ id: 7, title: '肝胆疾病护理规范', points: 2890, image: '/static/product7.png' },
{ id: 8, title: '肝癌多学科诊治专家共识', points: 5200, image: '/static/product8.png' },
{ id: 9, title: '介入治疗图解手册', points: 6800, image: '/static/product9.png' },
{ id: 10, title: '肝胆外科病例精选', points: 4300, image: '/static/product10.png' }
];
// 方法
const goBack = () => {
uni.navigateBack({
fail() {
uni.redirectTo({
url: '/pages/index/index'
});
}
});
};
const showSearch = () => {
uni.showToast({
title: '搜索功能',
icon: 'none'
});
};
const showFilter = () => {
// toggle并与排序互斥
filterVisible.value = !filterVisible.value;
if (filterVisible.value) {
sortVisible.value = false;
}
};
const hideFilter = () => {
filterVisible.value = false;
};
const selectCategory = (cat) => {
selectedCategory.value = cat;
filterVisible.value = false;
uni.showToast({ title: `已选择:${cat}`, icon: 'none' });
};
const showSort = () => {
// toggle并与筛选互斥
sortVisible.value = !sortVisible.value;
if (sortVisible.value) {
filterVisible.value = false;
}
};
const hideSort = () => {
sortVisible.value = false;
};
const selectSort = (opt) => {
selectedSort.value = opt;
sortVisible.value = false;
uni.showToast({ title: `已选择:${opt}`, icon: 'none' });
};
const viewProduct = (product) => {
uni.showToast({
title: `查看商品: ${product.title}`,
icon: 'none'
});
};
// 下拉刷新
const onRefresh = async () => {
if (refreshing.value) return;
refreshing.value = true;
try {
await new Promise(r => setTimeout(r, 800));
page.value = 1;
noMore.value = false;
productsList.value = [...initialProducts];
uni.showToast({ title: '刷新成功', icon: 'none' });
} finally {
refreshing.value = false;
}
};
// 上拉加载
const onLoadMore = async () => {
if (loading.value || noMore.value) return;
loading.value = true;
try {
await new Promise(r => setTimeout(r, 800));
const start = (page.value - 1) * pageSize.value;
const chunk = mockMore.slice(start, start + pageSize.value);
if (chunk.length === 0) {
noMore.value = true;
} else {
productsList.value.push(...chunk);
page.value += 1;
}
} finally {
loading.value = false;
}
};
</script>
<style lang="scss" scoped>
// 变量定义
$bg-color: #f5f6f7;
$text-primary: #333333;
$text-secondary: #666666;
$text-light: #999999;
$border-color: #e5e5e5;
$white: #ffffff;
$theme-color: #e74c3c;
$divider-color: #cccccc;
$teal-color: #00cbc0;
$nav-height: 140rpx; // 导航栏高度
$banner-height: 300rpx; // 横幅高度
$filter-height: 80rpx; // 筛选栏高度
$bottom-nav-height: 100rpx; // 底部导航高度
// 混合器
@mixin flex-center {
display: flex;
align-items: center;
justify-content: center;
}
@mixin flex-between {
display: flex;
align-items: center;
justify-content: space-between;
}
@mixin shadow {
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.1);
}
.point-mall-page {
min-height: 100vh;
background-color: $bg-color;
padding-top: $nav-height; // 为固定导航栏预留空间
}
// 固定在顶部的促销横幅
.promo-banner-fixed {
position: fixed;
top: $nav-height;
left: 0;
right: 0;
z-index: 10;
height: $banner-height;
.swipemask{
width:100%;
z-index:3;
box-sizing:border-box;
position: absolute;
bottom:0;
padding:0 20rpx;
height: 80rpx;
display: flex;
align-items: center;
justify-content: space-between;
background-color: rgba(0,0,0,0.7);
.banner-subtitle{
color:#fff;
}
.dotbox{
display:flex;
align-items: center;
.circle{
margin:0 4rpx;
width:16rpx;
height:16rpx;
background-color: #eee;
border-radius: 50%;
}
.circle.active{
background-color: #8B2316;
}
}
}
.banner-swiper {
height: 100%;
.banner-slide {
height: 100%;
position: relative;
overflow: hidden;
padding: 40rpx 30rpx;
display: flex;
flex-direction: column;
justify-content: space-between;
.banner-top {
@include flex-center;
.dashed-line {
color: $white;
font-size: 28rpx;
opacity: 0.8;
}
}
.banner-main {
@include flex-center;
flex-direction: column;
gap: 20rpx;
.main-text {
color: $white;
font-size: 48rpx;
font-weight: bold;
}
.sub-text {
color: $white;
font-size: 32rpx;
opacity: 0.9;
}
}
.banner-bottom {
.mall-name {
color: $white;
font-size: 24rpx;
opacity: 0.8;
}
}
.decorative-elements {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
pointer-events: none;
.golden-coin {
position: absolute;
width: 40rpx;
height: 40rpx;
background: linear-gradient(45deg, #ffd700, #ffed4e);
border-radius: 50%;
@include flex-center;
font-size: 20rpx;
color: #b8860b;
font-weight: bold;
animation: float 3s ease-in-out infinite;
&:nth-child(1) { top: 20%; left: 10%; animation-delay: 0s; }
&:nth-child(2) { top: 30%; right: 15%; animation-delay: 0.5s; }
&:nth-child(3) { top: 60%; left: 20%; animation-delay: 1s; }
&:nth-child(4) { top: 70%; right: 25%; animation-delay: 1.5s; }
&:nth-child(5) { top: 40%; left: 60%; animation-delay: 2s; }
&:nth-child(6) { top: 80%; left: 70%; animation-delay: 2.5s; }
}
.gift-box {
position: absolute;
font-size: 40rpx;
bottom: 20rpx;
&:nth-child(7) { left: 20%; }
&:nth-child(8) { left: 50%; }
&:nth-child(9) { right: 20%; }
}
}
}
}
}
@keyframes float {
0%, 100% { transform: translateY(0px); }
50% { transform: translateY(-20px); }
}
.filter-sort-bar {
position: fixed;
top: calc(#{$nav-height} + #{$banner-height});
left: 0;
right: 0;
z-index: 9;
height: $filter-height;
background-color: $white;
display: flex;
align-items: center;
padding: 0 30rpx;
border-bottom: 1rpx solid $border-color;
.filter-section,
.sort-section {
flex: 1;
@include flex-center;
gap: 10rpx;
cursor: pointer;
.filter-text,
.sort-text {
font-size: 28rpx;
color: $text-primary;
}
.arrow-down {
font-size: 20rpx;
color: $text-light;
}
}
.divider {
width: 2rpx;
height: 40rpx;
background-color: $divider-color;
}
}
// 筛选弹层样式
.filter-overlay {
position: fixed;
top: calc(#{$nav-height} + #{$banner-height} + #{$filter-height} + 20rpx);
left: 0;
right: 0;
bottom: $bottom-nav-height;
z-index: 100;
background: rgba(0, 0, 0, 0.3);
.filter-panel {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: #fff;
.filter-row {
height:90rpx;
border-bottom: 2rpx solid #e6e6e6;
@include flex-center;
.row-text {
font-size:30rpx;
color: #9b9b9b;
}
}
}
}
// 排序弹层样式
.sort-overlay {
position: fixed;
top: calc(#{$nav-height} + #{$banner-height} + #{$filter-height} + 20rpx);
left: 0;
right: 0;
bottom: $bottom-nav-height;
z-index: 100;
background: rgba(0, 0, 0, 0.3);
.sort-panel {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: #fff;
.sort-row {
height:90rpx;
border-bottom: 2rpx solid #e6e6e6;
@include flex-center;
.row-text {
font-size:30rpx;
color: #9b9b9b;
}
}
}
}
.productbox{
position: fixed;
top: calc(#{$nav-height} + #{$banner-height} + #{$filter-height});
left: 30rpx;
right: 30rpx;
bottom: $bottom-nav-height;
overflow-y: auto;
}
.products-grid {
padding:30rpx 0rpx;
// 加载状态
.loading-more, .no-more {
@include flex-center;
padding: 28rpx 0;
.loading-text, .no-more-text {
font-size: 26rpx;
color: $text-light;
}
}
.grid-container {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 30rpx;
.product-item {
background: $white;
border-radius: 16rpx;
overflow: hidden;
@include shadow;
.product-image {
width: 100%;
height: 200rpx;
background-color: #f0f0f0;
@include flex-center;
image {
width: 100%;
height: 100%;
}
}
.product-info {
padding: 20rpx;
.product-title {
font-size: 26rpx;
color: $text-primary;
line-height: 1.4;
display: block;
height: 30rpx;
margin:0 0rpx 16rpx;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.product-points {
font-size: 28rpx;
color: $theme-color;
}
}
}
}
}
// 固定在底部的导航栏
.bottom-nav-fixed {
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: 10;
height: $bottom-nav-height;
background-color: $teal-color;
display: flex;
align-items: center;
justify-content: space-around;
padding: 0 40rpx;
.nav-item {
@include flex-center;
flex-direction: row;
gap: 8rpx;
.nav-icon {
font-size: 40rpx;
color: $white;
}
.nav-text {
font-size: 24rpx;
color: $white;
}
}
.divider {
width: 2rpx;
height: 40rpx;
background-color: rgba(255, 255, 255, 1);
}
}
</style>