任务3-2操作步骤2:积分系统研发

任务三 工单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所示的效果。
3.2.12 签到日历组件.png

引入自定义弹出框modal组件,如图3.2.13。点击中间的签到按钮,将触发交互事件@tap="attendance",向用户显示“赠送的1积分已发到您的账户中”的提示信息。modal组件的显示与隐藏由attendanceModalVisible布尔类型参数控制,具体如图3.2.14所示。
3.2.13 引入modal组件进components.png
3.2.14 modal弹出框样式.png

涉及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>
3.2.15 签到页面效果图.png

最终代码如下:

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。
3.2.16 两种兑换详情样式.png

在跳转后,首先应编写针对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.17 引入富文本组件jyf-parser.png

在根据设计图完善商品详情页面后,得到如图3.2.18所示的效果。通过使用v-else布局,我们为goods_type为1的样式创建了明显的区分,样式1与样式2之间的差异被清晰地编写在代码中,最终呈现出图3.2.19所示的界面。部分关键代码块展示在图3.2.20中。
3.2.18 商品详情界面type为2.png
3.2.19 优惠券详情界面type为1.png
3.2.20 部分代码块.png

最终商品详情代码如下:

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>
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,084评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,623评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,450评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,322评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,370评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,274评论 1 300
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,126评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,980评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,414评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,599评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,773评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,470评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,080评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,713评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,852评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,865评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,689评论 2 354

推荐阅读更多精彩内容