任务三 工单2
6、在integrals.vue界面中,点击签到按钮会触发一个跳转事件,该事件通过指令@tap="attendance"实现,随后跳转至“签到”界面。为了实现这一功能,需要新建一个名为attendance.vue的页面,并在pages.json文件中对相关样式进行调整,样式细节如下:
{
"path" : "pages/attendance/attendance",//“签到”界面
"style" :
{
"navigationStyle":"custom"
}
}
通过使用uni-calendar日历组件,用户能够浏览日期并选择特定范围内的日期进行标记。特别需要注意的是,selected属性的格式,它包含了当日签到的所有信息。签到数据是通过调用this.$api('attendanceList')获取的,在组件的onLoad()生命周期方法中,将attendanceList数组作为参数传递,以便将签到信息与selected属性绑定。将能够实现图3.2.12所示的效果。涉及modal的关键代码如下,完成本步骤后,将获得“签到”界面的最终效果图,如图3.2.15所示。
<!-- 自定义弹出框控件 -->
<modal custom :show="attendanceModalVisible">
<view class="attendance-modal">
<view class="modal-header">
<image src="/static/images/attendance/cup.png" mode="widthFix"></image>
</view>
<view class="modal-content d-flex align-items-center just-content-center flex-column font-size-sm text-color-base">
<view>赠送的1积分已发到您的账户中</view>
<view>连续签到1天可额外获得1积分</view>
</view>
<view class="d-flex align-items-center just-content-center">
<button type="primary" class="btn" @tap="attendanceModalVisible=false">我知道了</button>
</view>
</view>
</modal>
最终代码如下:
integrals.vue 页面 跳转代码
<view class="d-flex flex-column align-items-center just-content-center" style="margin: 20rpx 0;">
<button type="primary" class="sign-in-btn" @tap="attendance">签到</button>
<view class="font-size-base text-color-primary" style="margin-top: 20rpx;">查看签到日历</view>
</view>
......
......
......
attendance() {
uni.navigateTo({
url: '/pages/attendance/attendance'//签到页面
})
},
attendance.vue 签到页面
<template>
<!-- 签到页面 -->
<view class="container">
<!-- 头部 -->
<navbar-back-button></navbar-back-button>
<view class="header">
<image src="/static/images/attendance/bg.png" mode="scaleToFill"></image>
<view class="user-box">
<view class="avatar">
<image :src="member.avatar"></image>
</view>
<view class="nickname">
{{ member.nickname }}
</view>
<view class="rule">
签到规则
</view>
</view>
</view>
<!-- 中间布局 -->
<view style="padding: 0 30rpx;">
<view class="integral-box">
<view class="title">当前积分</view>
<view class="value">{{ customPoints.totalPoints }}</view>
<button type="primary" class="btn" @tap="attendance">签到</button>
</view>
<!-- 为了方便演示,这里设置了startDate和enddate属性 -->
<uni-calendar :show-month="false" :start-date="startDate" :selected="attendanceList">
</uni-calendar>
</view>
<!-- 自定义弹出框控件 -->
<modal custom :show="attendanceModalVisible">
<view class="attendance-modal">
<view class="modal-header">
<image src="/static/images/attendance/cup.png" mode="widthFix"></image>
</view>
<view class="modal-content d-flex align-items-center just-content-center flex-column font-size-sm text-color-base">
<view>赠送的1积分已发到您的账户中</view>
<view>连续签到1天可额外获得1积分</view>
</view>
<view class="d-flex align-items-center just-content-center">
<button type="primary" class="btn" @tap="attendanceModalVisible=false">我知道了</button>
</view>
</view>
</modal>
</view>
</template>
<script>
import {
mapState
} from 'vuex'
import navbarBackButton from '@/components/navbar-back-button'
export default {
components: {
navbarBackButton,
},
data() {
return {
customPoints: {},
attendanceList: [],//签到列表
startDate: '',//开始时间
endDate: '',//结束时间
attendanceModalVisible: false,//diglog是否弹出
}
},
async onLoad() {
this.customPoints = await this.$api('customPoints') //积分
this.attendanceList = await this.$api('attendanceList') //签到列表
const date = new Date()
let year = date.getFullYear()
let month = date.getMonth()
this.startDate = `${year}-${month}-01` //日历开始时间月份
},
computed: {
...mapState(['member'])
},
methods: {
attendance() {//dialog弹出
this.attendanceModalVisible = true
}
}
}
</script>
<style lang="scss" scoped>
.header {
width: 100%;
height: 33.333vh;
position: relative;
image {
width: 100%;
height: 100%;
}
.user-box {
position: absolute;
width: 100%;
top: var(--status-bar-height);
bottom: 0;
left: 0;
display: flex;
align-items: center;
color: #FFFFFF;
.avatar {
margin-left: 30rpx;
background-color: #FFFFFF;
padding: 6rpx;
border-radius: 100%;
display: flex;
align-items: center;
justify-content: center;
margin-right: 20rpx;
image {
width: 100rpx;
height: 100rpx;
border-radius: 100%;
}
}
.nickname {
font-size: $font-size-lg;
flex: 1;
}
.rule {
font-size: $font-size-sm;
border-radius: 50rem 0 0 50rem;
background-color: rgba($color: #ffffff, $alpha: 0.3);
padding: 10rpx 30rpx;
}
}
}
.integral-box {
position: relative;
background-color: #FFFFFF;
border-radius: 8rpx;
padding: 30rpx;
display: flex;
flex-direction: column;
align-items: center;
margin-top: -120rpx;
margin-bottom: 30rpx;
box-shadow: 0 0 20rpx rgba($color: #000000, $alpha: 0.1);
.title {
font-size: $font-size-sm;
color: $text-color-base;
margin: 20rpx 0 30rpx;
}
.value {
font-size: 46rpx;
font-weight: bold;
margin-bottom: 30rpx;
}
.btn {
font-size: $font-size-lg;
color: #FFFFFF;
border-radius: 50rem !important;
width: 70%;
}
}
.uni-calendar {
margin-bottom: 30rpx;
border-radius: 8rpx;
box-shadow: 0 0 20rpx rgba($color: #000000, $alpha: 0.1);
}
//dialog样式
.attendance-modal {
.modal-header {
width: 100%;
margin-top: -180rpx;
position: relative;
image {
width: 100%;
}
}
.modal-content {
height: 200rpx;
}
.btn {
width: 100%;
border-radius: 50rem;
font-size: $font-size-lg;
}
}
</style>
7、在integrals.vue积分兑换界面的下方,商品被分类陈列,供用户选择。点击任一商品,用户将被引导至对应的“商品详情”界面。为此,在integrals文件夹内创建一个名为detail.vue的“商品详情”页面,并在pages.json文件中配置其样式。
{
"path" : "pages/integrals/detail",
"style" :
{
"navigationBarTitleText": "兑换详情",
"navigationBarTextStyle": "black",
"navigationBarBackgroundColor": "#ffffff"
}
}
在跳转过程中,通过URL传递参数实现如下:“/pages/integrals/detail?cate=” + cate + “&id=” + id,其中cate代表种类,id代表兑换货物的唯一标识符。具体代码示例如下:
<!-- 每一个物品列表 做循环 -->
<block v-for="(item, key) in items" :key="key">
<view class="d-flex bg-white flex-column align-items-stretch point-box" @tap="detail(cate, item.id)">
...
...
...
detail(cate, id) {
uni.navigateTo({
url: '/pages/integrals/detail?cate=' + cate + '&id=' + id//跳转商品详情
})
}
在“商品详情”界面中,兑换物品被分为两大类:一类是兑换券,其goods_type标识为1;另一类则是物品,其goods_type标识为2。基于goods_type的这一区分,编写相应的布局样式。具体的布局效果可参见图3.2.16。在跳转后,首先应编写针对goods_type为2的物品兑换详情,即引入@/api/points-mall数据。在onLoad({cate, id})生命周期方法中,通过cate和id这两个参数获取当前兑换物品的详细信息以及用户的积分数据,并将这些信息存储在pointGood和customPoints对象中。接着,将pointGood对象中的exchange_desc内容赋值给富文本编辑器。以下是相关代码:
async onLoad({cate, id}) {
this.pointGood = pointsMall[cate].find(item => item.id == id)//获取商品详情数据
this.$nextTick(() => this.$refs['desc'].setContent(this.pointGood.exchange_desc || ''))//富文本处理 商品详情
this.customPoints = await this.$api('customPoints')//获取积分
}
引入jyf-parser富文本组件,对商品详情区域进行富文本描述,如图3.2.17所示。在根据设计图完善商品详情页面后,得到如图3.2.18所示的效果。通过使用v-else布局,我们为goods_type为1的样式创建了明显的区分,样式1与样式2之间的差异被清晰地编写在代码中,最终呈现出图3.2.19所示的界面。部分关键代码块展示在图3.2.20中。
最终商品详情代码如下:
integrals.vue 页面 跳转代码
<!-- 每一个物品列表 做循环 -->
<block v-for="(item, key) in items" :key="key">
<view class="d-flex bg-white flex-column align-items-stretch point-box" @tap="detail(cate, item.id)">
<!-- 商品图片 -->
<image :src="item.img.length ? item.img[0] : '/static/images/integrals/ticket.png'"
class="w-100" mode="widthFix"></image>
......
......
......
detail(cate, id) {
uni.navigateTo({
url: '/pages/integrals/detail?cate=' + cate + '&id=' + id
})
}
detail.vue 商品详情页面
<template>
<!-- 商品详情 -->
<view container position-relative>
<!-- 根据goods_type==2 为物品兑换详情 goods_type== 1 券兑换 -->
<view style="padding-bottom: 150rpx;">
<!-- 根据goods_type==2 为物品兑换详情 -->
<view class="d-flex flex-column" v-if="pointGood.goods_type == 2">
<!-- 上方 -->
<view class="bg-white mb-30">
<!-- 图片 -->
<image :src="pointGood.img[0]" class="w-100" mode="widthFix"></image>
<view class="d-flex flex- column" style="padding: 30rpx;">
<view class="d-flex align-items-center mb-10">
<!-- 需要积分 -->
<view class="d-flex align-items-baseline">
<view class="font-size-extra-lg text-color-primary mr-10 font-weight-bold">{{ pointGood.points_price }}</view>
<view class="font-size-base text-color-base">积分</view>
</view>
<!-- 需要补差价 -->
<view v-if="pointGood.amount > 0" class="font-size-sm text-color-base" style="margin: 0 10rpx;">+</view>
<view class="d-flex align-items-baseline" v-if="pointGood.amount > 0">
<view class="font-size-extra-lg text-color-primary mr-10 font-weight-bold">{{ pointGood.amount }}</view>
<view class="font-size-base text-color-base">元</view>
</view>
</view>
<!-- 商品名称 余额 -->
<view class="d-flex justify-content-between align-items-center">
<view class="font-size-extra-lg text-color-base font-weight-bold">
{{ pointGood.goods_name }}
</view>
<view class="font-size-sm text-color-base">
剩余<text class="text-color-primary">{{ pointGood.goods_stock }}</text>件
</view>
</view>
</view>
</view>
<!-- 下方 商品详情介绍 -->
<view class="bg-white mb-20" style="padding: 30rpx;">
<view class="d-flex align-items-center font-size-lg font-weight-bold line-height-2">商品详情</view>
<view class="font-size-base text-color-assist">
<jyf-parser ref="desc"></jyf-parser>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
import pointsMall from '@/api/points-mall'
import jyfParser from '@/components/jyf-parser/jyf-parser'
export default {
components: {
jyfParser
},
data() {
return {
customPoints: {},//积分
pointGood: {},//商品详情
useTips: [
{title: '使用条件', value: '无门槛'},
{title: '优惠形式', value: '免最高1件'},
{title: '有效期', value: '领券当日开始90天内有效'},
{title: '使用时段', value: '00:00:00~23:59:59'},
{title: '使用限制', value: '不限制'},
{title: '兑换限制', value: '不限制'},
{title: '活动商品', value: '限部分商品'},
{title: '是否与其他优惠共享', value: '否'},
{title: '使用场景', value: '仅外卖、堂食可用'},
]
};
},
async onLoad({cate, id}) {
this.pointGood = pointsMall[cate].find(item => item.id == id)//获取商品详情数据
this.$nextTick(() => this.$refs['desc'].setContent(this.pointGood.exchange_desc || ''))//富文本处理 商品详情
this.customPoints = await this.$api('customPoints')//获取积分
}
}
</script>
<style lang="scss">
.btn-box {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 150rpx;
padding: 30rpx;
z-index: 10;
background-color: #FFFFFF;
box-shadow: 0 0 20rpx rgba($color: #000000, $alpha: 0.1);
button {
border-radius: 50rem !important;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
}
.coupon-box {
width: 100%;
padding: 30rpx 50rpx;
.coupon {
background-color: #FFFFFF;
padding: 40rpx;
border-radius: 10rpx;
display: flex;
align-items: center;
position: relative;
image {
width: 180rpx;
height: 120rpx;
margin-right: 30rpx;
}
.intro {
flex: 1;
display: flex;
flex-direction: column;
.goods-name {
font-size: $font-size-lg;
color: $text-color-base;
margin-bottom: 10rpx;
}
.expire {
font-size: 22rpx;
color: $text-color-assist;
}
}
@mixin arch {
content: " ";
position: absolute;
background-color: $bg-color;
width: 40rpx;
height: 40rpx;
z-index: 10;
border-radius: 100%;
}
&::before {
@include arch;
left: -20rpx;
}
&::after {
@include arch;
right: -20rpx;
}
}
}
.points-and-stocks {
padding: 0 50rpx;
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30rpx;
.points {
display: flex;
align-items: baseline;
}
}
</style>