任务六 工单2
效果图:
1、在“我的管理”界面中,通过图6.2.3所示的@tap="coupons"交互,可以进入“我的卡券”界面。因此,在pages文件夹下创建一个新的页面,命名为coupons.vue,并根据既定要求对其进行样式设计。
{
"path" : "pages/coupons/coupons",
"style" : {
"navigationBarTitleText": "我的卡券",
"navigationBarTextStyle": "black",
"navigationBarBackgroundColor": "#ffffff"
}
}
2、“我的卡券”界面的研发布局参照图6.2.4进行拆分。第一区域的布局通过input和button元素实现,具体代码如下:
<view class="exchange-box">
<view class="input-box">
<input type="text" placeholder="请输入兑换码" placeholder-class="text-color-assist font-size-base" />
<button type="primary">兑换</button>
</view>
<view class="font-size-sm text-color-primary line-height-2">查看兑换规则</view>
</view>
对于第二区域,必须引入tabs参数,该参数用于定义卡券的种类。参数的具体样式请参照以下示例:
tabs: [
{title: '全部', value: 'all'},
{title: '茶饮券', value: '1'},
{title: '酒屋券', value: '2'}
]
通过设定activeTabIndex参数,用以追踪当前选中的卡券类别。该参数旨在从下方列表中筛选出相应的卡券类别。若当前类别为全部卡券,则activeTabIndex的值设为0;若为茶饮券,则设为1;若为酒屋券,则设为2。通过调用handleTab(index)方法,可以实现activeTabIndex值的变更。页面中的相关代码如下:
<view class="tabbar">
<view class="tab" :class="{active: activeTabIndex == index}"
v-for="(item, index) in tabs" :key="index" @tap="handleTab(index)">
<view class="title">{{ item.title }}</view>
</view>
</view>
通过调用await this.$api('customerCoupons')方法,可以获取到所有的coupons卡券数据。利用onShow()方法以及watch对activeTabIndex属性变化的监听,结合getCoupons()方法,实现列表的切换功能。
具体代码实现如下:
...
...
onShow() {
this.activeTabIndex = 0
},
watch: {
activeTabIndex: async function(val) {
const type = this.tabs[val].value
await this.getCoupons(type)
}
},
methods: {
handleTab(index) {
this.activeTabIndex = index
},
async getCoupons(type) {
const coupons = await this.$api('customerCoupons')
if(type == 'all') {
this.coupons = coupons
} else {
this.coupons = coupons.filter(item => item.couponType == type)
}
},
...
...
...
onShow()方法用于监听页面的显示事件,每当页面呈现于屏幕时即被触发。这包括从子页面通过返回操作而显示当前页面的情况(例如,从二级页面返回至本页面时,onLoad不会被重新执行,但onShow会被再次调用)。因此,每次访问本页面时,应将activeTabIndex的值设定为0。
watch属性的作用在于侦测数据的变动,并在数据发生改变时执行既定的操作。上述代码展示了如何对activeTabIndex进行监控,一旦检测到变化,便将tabs中的value值赋予type变量,并调用getCoupons函数将type作为参数传递,以实现数据的筛选。当type的值为'all'时,将筛选出所有卡券数据;若type为其他值,则通过filter函数进行数据筛选并赋值。通过这三个函数的协同工作,实现了点击交互,并能够切换页面上的数据展示,具体效果可参见图6.2.5。
在获取到精确分类的数据之后,必须通过使用v-for循环来完成列表的展示,将优惠券逐一呈现。
4、引入了modal和jyfParser两个控件,以开展第三区域的研发工作。通过与每个列表中的item进行交互,触发弹出的modal模态框以展示当前item的卡券详情(具体代码如下所示)。
<modal custom :show="detailModalVisible" @cancel="closeDetailModal" width="90%">
<view class="modal-content">
<view class="d-flex font-size-extra-lg text-color-base just-content-center mb-20">{{ coupon.title }}</view>
<view class="d-flex font-size-sm text-color-base mb-20">
有效期:{{ coupon.beginAt }}至{{ coupon.endAt }}
</view>
<pre class="pre-line font-size-sm text-color-assist mb-30">
<jyf-parser ref="couponExplain"></jyf-parser>
</pre>
<view class="d-flex align-items-center just-content-center">
<button type="primary" @tap="useCoupon" class="use-coupon-btn">立即使用</button>
</view>
</view>
</modal>
定义一个布尔型参数detailModalVisible,通过调用openDetailModal()和closeDetailModal()方法实现模态框的显示与隐藏,以完成相应的交互操作。
若需使用卡券,请点击“立即使用”按钮以完成卡券使用流程,具体操作请参见图6.2.6,相关代码如下:
5、至此,本工单的相关功能开发工作结束。团队成员应运用SourceTree工具执行版本控制的提交操作,以便为本次工单的开发代码建立历史版本的记录。
coupons.vue完整代码
<template>
<!-- 我的卡券 -->
<view class="container position-relative w-100 h-100 overflow-hidden">
<view class="exchange-box">
<view class="input-box">
<input type="text" placeholder="请输入兑换码" placeholder-class="text-color-assist font-size-base" />
<button type="primary">兑换</button>
</view>
<view class="font-size-sm text-color-primary line-height-2">查看兑换规则</view>
</view>
<view class="tabbar">
<view class="tab" :class="{active: activeTabIndex == index}"
v-for="(item, index) in tabs" :key="index" @tap="handleTab(index)">
<view class="title">{{ item.title }}</view>
</view>
</view>
<view class="flex-fill">
<scroll-view scroll-y class="coupon-list">
<view class="wrapper">
<view class="coupon" v-for="(item, index) in coupons" :key="index" @tap="openDetailModal(item)">
<view class="d-flex align-items-center detail">
<image :src="item.imageUrl" class="coupon-img"></image>
<view class="flex-fill d-flex flex-column just-content-center overflow-hidden">
<view class="font-size-lg text-color-base text-truncate mb-10">{{ item.title }}</view>
<view class="font-size-sm text-color-base">有效期至{{ item.endAt }}</view>
</view>
</view>
<view class="d-flex align-items-center justify-content-end" style="height: 80rpx;">
<view class="font-size-sm text-color-primary">查看详情</view>
</view>
</view>
</view>
</scroll-view>
</view>
<view class="bottom-box d-flex align-items-center just-content-center font-size-sm text-color-primary">
<view class="item">历史卡券</view>
<view class="item" @tap="showTip1">赠送记录</view>
<view class="item" @tap="showTip2">第三方权益</view>
</view>
<modal custom :show="detailModalVisible" @cancel="closeDetailModal" width="90%">
<view class="modal-content">
<view class="d-flex font-size-extra-lg text-color-base just-content-center mb-20">{{ coupon.title }}</view>
<view class="d-flex font-size-sm text-color-base mb-20">
有效期:{{ coupon.beginAt }}至{{ coupon.endAt }}
</view>
<pre class="pre-line font-size-sm text-color-assist mb-30">
<jyf-parser ref="couponExplain"></jyf-parser>
</pre>
<view class="d-flex align-items-center just-content-center">
<button type="primary" @tap="useCoupon" class="use-coupon-btn">立即使用</button>
</view>
</view>
</modal>
</view>
</template>
<script>
import modal from '@/components/modal/modal'
import jyfParser from "@/components/jyf-parser/jyf-parser"
export default {
components: {
modal,
jyfParser
},
data() {
return {
tabs: [
{title: '全部', value: 'all'},
{title: '茶饮券', value: '1'},
{title: '酒屋券', value: '2'}
],
activeTabIndex: '',//控制tabs的index
coupons: [],//卡券数据
detailModalVisible: false,//模态框显示参数
coupon: {}//单个卡券数据
}
},
onShow() {
this.activeTabIndex = 0
},
watch: {
activeTabIndex: async function(val) {
const type = this.tabs[val].value //定义类型传入方法
await this.getCoupons(type)
}
},
methods: {
handleTab(index) { //点击类型
this.activeTabIndex = index
},
async getCoupons(type) { //获取卡券类型数据
const coupons = await this.$api('customerCoupons')
if(type == 'all') {
this.coupons = coupons
} else {
this.coupons = coupons.filter(item => item.couponType == type) //通过筛选类型数据
}
},
openDetailModal(coupon) { //打开模态框
this.coupon = coupon
this.$refs['couponExplain'].setContent(this.coupon.couponExplain || '')
this.detailModalVisible = true
},
closeDetailModal() { //关闭模态框
this.detailModalVisible = false
this.coupon = {}
},
useCoupon() { //使用卡券
uni.switchTab({
url: '/pages/menu/menu'
})
},
showTip1() {
uni.showToast({
title: '您暂时还没有赠送中卡券哦~',
icon: 'none'
})
},
showTip2() {
uni.showToast({
title: '您暂时还没有券码哦~',
icon: 'none'
})
}
}
}
</script>
<style lang="scss" scoped>
/* #ifdef H5 */
page {
height: 100%;
}
/* #endif */
.container {
display: flex;
flex-direction: column;
}
.exchange-box {
flex-shrink: 0;
height: 200rpx;
background-color: #ffffff;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.input-box {
display: flex;
align-items: stretch;
width: 70%;
flex-shrink: 0;
input {
flex: 1;
height: 80rpx;
border: 1rpx solid #eee;
border-right: 0;
border-radius: 8rpx 0 0 8rpx;
padding: 20rpx;
font-size: $font-size-base;
color: $text-color-base;
}
button {
border-radius: 0 8rpx 8rpx 0;
display: flex;
align-items: center;
}
}
}
.tabbar {
flex-shrink: 0;
width: 100%;
height: 120rpx;
display: flex;
align-items: center;
justify-content: center;
.tab {
flex: 1;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
font-size: $font-size-base;
color: $text-color-base;
position: relative;
.title {
padding: 15rpx 0;
}
&.active {
color: $color-primary;
.title {
border-bottom: 5rpx solid $color-primary;
}
}
}
}
.bottom-box {
height: 80rpx;
flex-shrink: 0;
.item {
padding: 0 20rpx;
position: relative;
&::after {
content: " ";
border-right: 1rpx solid $text-color-assist;
height: 100%;
position: absolute;
right: 0;
top: 0;
transform: scaleX(0.5) scaleY(0.8);
}
&:nth-last-child(1)::after {
border-right: 0;
}
}
}
.coupon-list {
height: calc(100vh - 80rpx - 120rpx - 200rpx);
/* #ifdef H5 */
height: calc(100vh - 80rpx - 120rpx - 200rpx - 44px);
/* #endif */
}
.wrapper {
padding: 0 40rpx;
display: flex;
flex-direction: column;
.coupon {
display: flex;
flex-direction: column;
background-color: #FFFFFF;
margin-bottom: 30rpx;
padding: 0 30rpx;
border-radius: 6rpx;
box-shadow: 0 10rpx 10rpx -10rpx rgba(15, 15, 15, 0.1);
position: relative;
&::before {
content: "";
position: absolute;
background-color: $bg-color;
width: 30rpx;
height: 30rpx;
bottom: 65rpx;
left: -15rpx;
border-radius: 100%;
}
&::after {
content: "";
position: absolute;
background-color: $bg-color;
width: 30rpx;
height: 30rpx;
bottom: 65rpx;
right: -15rpx;
border-radius: 100%;
}
.detail {
padding: 20rpx 0;
position: relative;
&::after {
content: '';
position: absolute;
left: 0;
right: 0;
bottom: 0;
border-bottom: 1rpx dashed #c6c6c6;
transform: scaleY(0.5);
}
.coupon-img {
width: 150rpx;
height: 150rpx;
margin-right: 40rpx;
}
}
}
}
.use-coupon-btn {
width: 95%;
border-radius: 50rem !important;
}
</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'
})
},
...
...
...
userinfo() {//用户信息
if(!this.isLogin) {
this.login()
return
}
uni.navigateTo({
url: '/pages/mine/userinfo'
})
},
services() {//更多服务
uni.navigateTo({
url: '/pages/services/services'
})
},
coupons() {//我的卡券
if(!this.isLogin) {
this.login()
return
}
uni.navigateTo({
url: '/pages/coupons/coupons'
})
},
}
}
</script>