任务六 工单4
效果图
1、“礼品卡列表”界面为顾客提供了多种活动相关的优惠卡片。这些卡片基于当前的活动及节气而设计,并提供相应的折扣优惠。用户可在该界面浏览并选择需要使用的卡片进行购买。通过如图6.4.2所示的功能入口,用户能够轻松进入该界面,开始他们的卡片消费体验。
在 pages 文件夹中创建并部署 giftcard.vue 按钮位于功能列表的左侧侧边栏中。遵循项目统一的样式规范,在 pages.json 文件中完成相关配置设置。设置完成后,跳转至 主界面中的 giftCards 按钮下方,用户即可通过 @tap="giftCards" 交互进入该页面。
mine.vue 值得注意的是,此页面已启用 disableScroll 属性,具体效果为:disableScroll 设置为 true 时,页面将彻底禁止任何上下方向的滚动操作,这一设置仅在页面配置中有效。
{
"path" : "pages/giftcard/giftcard",
"style" : {
"navigationBarTitleText": "礼品卡列表",
"navigationBarTextStyle": "black",
"navigationBarBackgroundColor": "#ffffff",
"disableScroll": true
}
}
2、“礼品卡列表”界面内部较为复杂,可分为“购买礼品卡”功能、“我的礼品卡”功能两个板块进行研发,每个块功能通过swiper、swiper-item进行展示。购买礼品卡为第一个swiper-item,其中包含scroll-view、image、v-for循环列表。
“礼品卡列表”界面的设计较为复杂,主要包含两个核心功能板块:一是“购买礼品卡”功能,二是“我的礼品卡”管理功能。其中,“购买礼品卡”模块采用swiper组件作为交互式展示框架,具体包括滚动视图(scroll-view)、图片展示(image)和基于v-for循环的数据展示(v-for)。第一个swiper-item中包含滚动视图、图片展示以及基于v-for循环的数据展示。效果如图6.4.3。
通过@tap="switchTab(0)"的方式,用户点击卡片即可切换子页面,默认情况下参数为0对应“购买礼品卡”子页面,参数为1则切换至“我的礼品卡”子页面。充分利用组件库和动态class类管理功能,确保界面交互流畅且操作便捷,并通过合理的布局优化和性能调优提升了整体用户体验。
<view class="tabbar">
<view class="item" :class="{'active': !currentTab}" @tap="switchTab(0)">购买礼品卡</view>
<view class="item" :class="{'active': currentTab}" @tap="switchTab(1)">我的礼品卡</view>
</view>
“礼品卡列表”选项卡的设计通过调用onShow生命周期阶段(onShow),在该阶段的初始阶段(construct)会启动数据获取流程。具体而言,在选项卡的显示生命周期阶段(onShow)开始时,将执行await this.$api('giftCards')的方法,以获取卡片数据集合(giftCards.category_list)。
通过v-for循环遍历该数据集,每个卡片信息将被分解展示:卡片名称作为主标题,卡片图片和相关礼品卡活动信息作为副标题。具体来说,图片组件将根据theme.imageUrls展示卡片图片,同时配合theme.activityName字段显示相关信息。这一设计实现了卡片数据的高效获取与动态呈现,最终呈现出的效果如图6.4.4所示。
<scroll-view scroll-y="true" class="h-100" style="padding-bottom: 80rpx;">
<image :src="giftCards.img" class="w-100" mode="widthFix"/>
<view class="pl-20 pr-20 mb-30">
<view class="category-list mt-40" v-for="(category, index) in giftCards.category_list" :key="index">
<view class="font-size-lg font-weight-bold">{{ category.name }}</view>
<view class="themes-list">
<view class="theme" v-for="(theme, key) in category.themesList" :key="key">
<image :src="theme.imageUrls" class="w-100" mode="widthFix"/>
<view class="activity-name">{{ theme.activityName }}</view>
</view>
</view>
</view>
</view>
</scroll-view>
按照设计图构建“购买礼品卡”子页面的布局方案,并结合UniApp平台的技术特性,实现卡片数据的动态获取与展示功能。
3、在“我的礼品卡”子页面中,嵌套了一个swiper组件,用于实现可使用和不可使用的卡片信息切换。该嵌套组件支持左右滑动操作,并通过@change事件监听功能,根据滑动后的结果动态切换至对应的子页面。
当用户对嵌套swiper进行滑动操作时,会触发一个名为switchGiftCardType的事件处理函数,该函数将决定显示的是可使用的卡片还是不可使用的卡片信息(效果如图6.4.5所示)。
switchGiftCardType(index) {
if(this.giftCardType == index) return
this.giftCardType = index
}
此外,考虑到可能需要展示多个子页面的情况(如n个子页面),在每个子页面之间嵌套一个swiper-item,从而实现多维度的信息呈现和切换效果(效果如图6.4.6所示)。
<swiper-item>
<template v-if="!myCards.length">
<view class="d-flex flex-column align-items-center">
<image src="https://s3.uuu.ovh/imgs/2025/02/08/36ca6ba99601c912.png" style="width: 300rpx;margin-top: 100rpx;" mode="widthFix"/>
<view class="font-size-sm text-color-assist">您暂时还没有礼品卡哦~</view>
</view>
</template>
</swiper-item>
按照设计图完成“我的礼品卡”子页面的布局及功能。
4、至此,本工单的相关功能开发工作结束。团队成员应运用SourceTree工具执行版本控制的提交操作,以便为本次工单的开发代码建立历史版本的记录。
giftcard.vue完整代码
<template>
<!-- 礼品卡列表 -->
<view class="container">
<view class="tabbar">
<view class="item" :class="{'active': !currentTab}" @tap="switchTab(0)">购买礼品卡</view>
<view class="item" :class="{'active': currentTab}" @tap="switchTab(1)">我的礼品卡</view>
</view>
<swiper :duration="400" :disable-touch="true" class="swiper" :current="currentTab">
<!-- 购买礼品卡 begin -->
<swiper-item class="swiper-item-1" @touchmove.stop="handleSwiperItemChange">
<scroll-view scroll-y="true" class="h-100" style="padding-bottom: 80rpx;">
<image :src="giftCards.img" class="w-100" mode="widthFix"/>
<view class="pl-20 pr-20 mb-30">
<view class="category-list mt-40" v-for="(category, index) in giftCards.category_list" :key="index">
<view class="font-size-lg font-weight-bold">{{ category.name }}</view>
<view class="themes-list">
<view class="theme" v-for="(theme, key) in category.themesList" :key="key">
<image :src="theme.imageUrls" class="w-100" mode="widthFix"/>
<view class="activity-name">{{ theme.activityName }}</view>
</view>
</view>
</view>
</view>
</scroll-view>
<view class="footer">
<view>购买须知</view>
<view class="divider"></view>
<view>礼品卡章程</view>
</view>
</swiper-item>
<!-- 购买礼品卡 end -->
<!-- 我的礼品卡 begin -->
<swiper-item class="swiper-item-2" @touchmove.stop="handleSwiperItemChange">
<view class="header">
<view class="gift-card-types">
<view class="item" :class="{'active': !giftCardType}" @tap="switchGiftCardType(0)">可使用(0)</view>
<view class="item" :class="{'active': giftCardType}" @tap="switchGiftCardType(1)">不可使用(0)</view>
</view>
<view class="d-flex align-items-center text-color-primary">兑换礼品卡</view>
</view>
<swiper :duration="400" class="flex-fill" :current="giftCardType" @change="e => switchGiftCardType(e.detail.current)">
<swiper-item>
<template v-if="!myCards.length">
<view class="d-flex flex-column align-items-center">
<image src="https://s3.uuu.ovh/imgs/2025/02/08/36ca6ba99601c912.png" style="width: 300rpx;margin-top: 100rpx;" mode="widthFix"/>
<view class="font-size-sm text-color-assist">您暂时还没有礼品卡哦~</view>
</view>
</template>
</swiper-item>
<swiper-item>
<template v-if="!myCards.length">
<view class="d-flex flex-column align-items-center">
<image src="https://s3.uuu.ovh/imgs/2025/02/08/36ca6ba99601c912.png" style="width: 300rpx;margin-top: 100rpx;" mode="widthFix"/>
<view class="font-size-sm text-color-assist">您暂时还没有礼品卡哦~</view>
</view>
</template>
</swiper-item>
</swiper>
<view class="footer">
<button type="primary" plain>获取记录</button>
<button type="primary">赠送记录</button>
</view>
</swiper-item>
<!-- 我的礼品卡 end -->
</swiper>
</view>
</template>
<script>
export default {
data() {
return {
giftCards: {},
currentTab: 0,
giftCardType: 0,
myCards: []
}
},
async onShow() {
this.giftCards = await this.$api('giftCards')
},
methods: {
switchTab(index) {
if(this.currentTab == index) return
this.currentTab = index
},
switchGiftCardType(index) {
if(this.giftCardType == index) return
this.giftCardType = index
},
handleSwiperItemChange() {
return true
}
}
}
</script>
<style lang="scss" scoped>
.container {
display: flex;
flex-direction: column;
}
.tabbar {
width: 100%;
height: 100rpx;
border-top: 1rpx solid $border-color;
background-color: $text-color-white;
display: flex;
align-items: stretch;
font-size: $font-size-lg;
.item {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
position: relative;
&.active {
color: $color-primary;
&:after {
content: '';
position: absolute;
width: 40rpx;
height: 10rpx;
background-color: $color-primary;
border-radius: 50rem !important;
bottom: 0;
left: calc((100% - 40rpx) / 2);
}
}
}
}
.swiper {
flex: 1;
}
.swiper-item-1 {
height: 100%;
.themes-list {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
.theme {
width: 340rpx;
margin-top: 30rpx;
border-radius: 24rpx;
background-color: $text-color-white;
display: flex;
flex-direction: column;
box-shadow: $box-shadow;
image {
border-radius: 24rpx 24rpx 0 0;
max-height: 300rpx;
}
}
.activity-name {
font-size: $font-size-base;
padding: 20rpx 0;
text-align: center;
}
}
.footer {
z-index: 10;
background-color: $bg-color;
position: fixed;
bottom: 0;
width: 100%;
height: 80rpx;
display: flex;
justify-content: center;
align-items: center;
font-size: $font-size-sm;
color: $color-primary;
.divider {
height: 0.6rem;
width: 4rpx;
background-color: $color-primary;
margin: 0 20rpx;
}
}
}
.swiper-item-2 {
height: 100%;
display: flex;
flex-direction: column;
.header {
height: 110rpx;
display: flex;
justify-content: space-between;
align-items: stretch;
padding: 30rpx;
font-size: $font-size-sm;
.gift-card-types {
display: flex;
align-items: stretch;
.item {
display: flex;
justify-content: center;
align-items: center;
padding: 0 20rpx;
&.active {
background-color: #FFFFFF;
box-shadow: $box-shadow;
border-radius: 50rem !important;
color: $color-primary;
}
}
}
}
.footer {
z-index: 10;
background-color: $text-color-white;
position: fixed;
bottom: 0;
width: 100%;
height: 100rpx;
display: flex;
justify-content: space-between;
align-items: center;
padding: 10rpx 40rpx;
box-shadow: 0 -10rpx 10rpx rgba($color: #eee, $alpha: 0.1);
button {
width: 310rpx;
height: 100%;
line-height: 80rpx;
border-radius: 50rem !important;
font-size: $font-size-lg;
}
}
}
</style>
mine.vue 部分跳转代码
...
...
...
<view class="w-100 d-flex align-items-center just-content-center">
<!-- 通过三目运算符判断 -->
<view class="user-grid" @tap="coupons">
<view class="value font-size-extra-lg font-weight-bold text-color-base">
{{ isLogin ? member.couponNum : '***' }}
</view>
<view class="font-size-sm text-color-assist">云鲤券</view>
</view>
<view class="user-grid" @tap="integrals">
<view class="value font-size-extra-lg font-weight-bold text-color-base">
{{ isLogin ? member.pointNum : '***' }}
</view>
<view class="font-size-sm text-color-assist">积分商城</view>
</view>
...
...
...
<script>
import {
mapState,
mapGetters
} from 'vuex'
export default {
data() {
return {
// isLogin: false,//false为未登录,true为登录
newIcon: 'https://s3.uuu.ovh/imgs/2024/12/05/6b3856fe6d711a0a.png'
}
},
computed: {
...mapState(['member']),
...mapGetters(['isLogin']),
growthValue() { //计算成长属性
if (!this.isLogin) return 0
const {
currentValue,
needValue
} = this.member
return currentValue / (currentValue + needValue) * 100
}
},
methods: {
login() { //登录
uni.navigateTo({
url: '/pages/login/login'
})
},
...
...
...
services() {//更多服务
uni.navigateTo({
url: '/pages/services/services'
})
},
coupons() {//我的卡券
if(!this.isLogin) {
this.login()
return
}
uni.navigateTo({
url: '/pages/coupons/coupons'
})
},
balance() { //进入会员充值界面
if (!this.isLogin) {
this.login()
return
}
uni.navigateTo({
url: '/pages/balance/balance'
})
},
giftCards() { //礼品卡
if (!this.isLogin) {
this.login()
return
}
uni.navigateTo({
url: '/pages/giftcard/giftcard'
})
}
}
}
</script>