任务3-4操作步骤:优惠券管理

任务三 工单4

本工单最终效果图:


3.4.1 买茶送包界面效果图.png

3.4.2 券包商品界面效果图.png

3.4.3 购买券包界面效果图.png

1、在“首页展示”界面,用户可以参与许多优惠活动,如“买茶送包”、“会员券包”、“优质茶礼盒”等活动。通过活动可获得相关奖励、分享好友、打折券等好处。点击如图3.4.4区域,进入“买茶送包”界面,在该界面可点亮进度条,获得买茶送包券。
3.4.4 点击买茶送包区域.png

新建“买茶送包”界面并命名invite.vue,完成pages.json中样式设置并设置@tap="invite"跳转,代码如下:
    {
        "path" : "pages/invite/invite",
        "style" : 
        {
            "navigationBarTitleText": "买茶送包",
            "navigationBarTextStyle": "black",
            "navigationBarBackgroundColor": "#ffffff"
        }
    }

按照UI设计师标准完成invite.vue的界面(图3.4.1)搭建,通过设置界面background属性为一张url图片保证界面美观。其次,分为两个view(样式为invite-box、my-award)一个image完成剩下布局。布局样式如图3.4.5。

3.4.5 view样式效果图.png

“买茶送包”界面代码invite.vue

<template>
    <!-- 买茶送包 -->
    <view class="container">
        <view class="invite-box">
            <view class="w-100 d-flex justify-content-end align-items-baseline font-size-sm">
                <view class="text-color-danger font-size-lg">0</view>
                <view class="text-color-assist">/3</view>
            </view>
            <view class="progress-box">
                <progress :percent="1/3 * 100" backgroundColor="#FEEBD5" activeColor="#FEDC5F" stroke-width="30"
                    border-radius="30"></progress>
            </view>
            <view class="d-flex flex-column text-color-danger font-size-sm align-items-center mb-40">
                <view>每邀3位新用户点亮,得买茶送包劵1张</view>
                <view>新用户可得30元权益劵包</view>
            </view>
            <view class="d-flex just-content-center" style="opacity: 0.8;" hover-class="opacity-1">
                <view class="w-90 position-relative">
                    <image src="https://s3.uuu.ovh/imgs/2024/12/12/1cc837e93d6a074f.png" class="w-100"
                        mode="widthFix"></image>
                </view>
                <view class="font-size-lg text-color-danger position-absolute d-flex align-items-center mt-30">
                    <view>立即找好友助力</view>
                    <image class="to-invite-img"
                        src="https://s3.uuu.ovh/imgs/2024/12/12/f5541573e4c26619.png" mode="widthFix">
                    </image>
                </view>
            </view>
        </view>
        <view class="my-award">
            <image src="https://s3.uuu.ovh/imgs/2024/12/12/bfe3e7ba232f2c57.png" class="my-award-img"
                mode="widthFix"></image>
            <view class="item">
                <view class="font-size-lg text-color-danger">0</view>
                <view class="font-size-sm text-color-assist">已成功邀请(位)</view>
            </view>
            <view class="item">
                <view class="font-size-lg text-color-danger">0</view>
                <view class="font-size-sm text-color-assist">已获得奖励(次)</view>
            </view>
        </view>
        <image src='https://s3.uuu.ovh/imgs/2024/12/12/d90cb073a37869a4.png' class="w-100 firend-award"
            mode="widthFix"></image>
    </view>
</template>

<script>
    export default {
        data() {
            return {

            };
        }
    }
</script>

<style lang="scss" scoped>
    page {
        background: url('https://s3.uuu.ovh/imgs/2024/12/12/c57f2423e1f976f9.png') no-repeat;
        background-size: 100% auto;
        padding: 576rpx 30rpx 0;
        height: auto;
    }

    .invite-box {
        padding: 40rpx 70rpx;
        background-color: #FFFFFF;
        border-radius: 8rpx;
        display: flex;
        flex-direction: column;
        position: relative;
        margin-bottom: 30rpx;
    }

    .progress-box {
        width: 100%;
        margin: 20rpx 0;
    }

    .to-invite-img {
        width: 16rpx;
        margin-left: 10rpx;
    }

    .opacity-1 {
        opacity: 1 !important;
    }

    .my-award {
        padding: 60rpx 0;
        display: flex;
        justify-content: center;
        align-items: center;
        background-color: #FFFFFF;
        border-radius: 8rpx;
        margin-bottom: 30rpx;
        position: relative;

        .my-award-img {
            width: 291rpx;
            position: absolute;
            top: -15rpx;
        }

        .item {
            flex: 1;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            position: relative;

            &::after {
                position: absolute;
                content: ' ';
                background-color: #ccc;
                width: 2rpx;
                height: 100%;
                right: 0;
                top: 0;
                transform: scaleY(0.5);
            }

            &:nth-last-child(1) {
                &::after {
                    width: 0;
                }
            }
        }
    }
</style>

★2、点击会员券包区域,通过index.vue界面通过@tap="packages"方法跳转至“券包商品”界面。新建packages.vue页面,该页面为“券包商品”界面,其中该页面含有下拉刷新功能,可以通过pages.json中的"enablePullDownRefresh": true进行设置,代码如下:

{
        "path" : "pages/packages/packages",
        "style" : 
        {
            "navigationBarTitleText": "券包商品",
            "navigationBarTextStyle": "black",
            "navigationBarBackgroundColor": "#ffffff",
            "enablePullDownRefresh": true
        }
    }

在packages.vue界面中通过以下方法完成对下拉刷新后的回调操作,完成刷新后利用uni.stopPullDownRefresh()方法停止下拉刷新效果,效果如图3.4.6所示。

        async onPullDownRefresh() {
            this.packages = []//下拉刷新重置数据
            await this.getPackages()//获取新数据
            // 当刷新操作完成后,停止下拉刷新效果
            uni.stopPullDownRefresh();
        },
3.4.6 下拉刷新功能.png

页面中间优惠券卡片列表使用v-for循环完成布局,调用this.$api('packages')获取优惠券数据,通过packages数组完成数据绑定。效果如图3.4.7,代码如下:

    <view class="d-flex align-items-center bg-white"
        style="padding: 30rpx; height: 220rpx; margin-bottom: 34rpx; border-radius: 8rpx;"
        v-for="(item, index) in packages" :key="index">
        <image :src="item.image" style="width: 200rpx; height: 160rpx; margin-right: 20rpx;"></image>
        <view class="d-flex flex-fill flex-column justify-content-between" style="height: 160rpx;">
            <view class="font-size-lg text-color-base">{{ item.title }}</view>
            <view class="d-flex justify-content-between align-items-center">
                <view class="font-size-sm">¥{{ item.amount }}</view>
                <button type="primary" size="mini" plain class="pay-btn">去购买</button>
            </view>
        </view>
    </view>
3.4.7 优惠券列表.png
3.4.8 引入loading组件.png

由于后端工程师并为将真是api开发出来,所以该页面模拟网络请求,通过引入自定义loading组件(图3.4.8)模拟请求时加载状态。使用setTimeout()函数延迟1秒钟获取数据,loading组件为components文件夹下新建vue文件,中间为一张image图片,通过命名name: "Loading"将组件维护起来后续复用,效果如图3.4.9,可见代码如下:
3.4.9 loading动图.png
<template>
    <view class="loading">
        <image src="/static/images/loading.gif"></image>
    </view>
</template>
<script>
    export default {
        name: 'Loading'
    }
</script>
<style lang="scss">
    .loading {
        width: 100%;
        height: 100%;
        display: flex;
        align-items: center;
        justify-content: center;

        image {
            width: 260rpx;
            height: 260rpx;
            position: relative;
            margin-top: 200rpx;
        }
    }
</style>

在data中设置loading布尔参数,并通过v-if、v-else完成loading组件的显示与消失,代码如下,最终效果图如图3.4.10。


3.4.10 券包商品界面效果图.png
<!-- 券包商品 -->
<view class="container w-100 h-100" v-if="!loading">
...
...
<loading v-else></loading>

index.vue跳转代码

            ...
            ...
                <!-- ringt 买茶送包  会员劵包 -->
                <view class="right">
                    <view class="tea-activity"  @tap="invite">
                        <image src="/static/images/index/mcsb.png" class="mark-img"></image>
                        <view>买茶送包</view>
                        <view class="right-img">
                            <image src="/static/images/index/mcsb_bg.png" mode="widthFix"></image>
                        </view>
                    </view>
                    <view class="member-gifts" @tap="packages">
                        <image src="/static/images/index/hyjb.png" class="mark-img"></image>
                        <view>会员劵包</view>
                        <view class="right-img">
                            <image src="/static/images/index/hyjb_bg.png" mode="widthFix"></image>
                        </view>
                    </view>
                </view>
            </view>
            ...
            ...
<script>
    import {mapGetters,mapState} from 'vuex'
    export default {
            ...
        },
        methods: {
            ...
            ...
            ...
            invite() {//买茶送包
                uni.navigateTo({
                    url: '/pages/invite/invite'
                })
            },
            packages() {//跳转券包商品
                uni.navigateTo({
                    url: '/pages/packages/packages'
                })
            },
        }
    }
</script>

“券包商品”界面代码packages.vue

<template>
    <!-- 券包商品 -->
    <view class="container w-100 h-100" v-if="!loading">
        <!-- 卡片 -->
        <view class="d-flex flex-column w-100" style="padding: 30rpx; padding-bottom: -34rpx; margin-bottom: 150rpx;">
            <view class="d-flex align-items-center bg-white"
                style="padding: 30rpx; height: 220rpx; margin-bottom: 34rpx; border-radius: 8rpx;"
                v-for="(item, index) in packages" :key="index">
                <image :src="item.image" style="width: 200rpx; height: 160rpx; margin-right: 20rpx;"></image>
                <view class="d-flex flex-fill flex-column justify-content-between" style="height: 160rpx;">
                    <view class="font-size-lg text-color-base">{{ item.title }}</view>
                    <view class="d-flex justify-content-between align-items-center">
                        <view class="font-size-sm">¥{{ item.amount }}</view>
                        <button type="primary" size="mini" plain class="pay-btn">去购买</button>
                    </view>
                </view>
            </view>
        </view>
        <!-- 底部 -->
        <view
            class="d-flex position-fixed bg-base fixed-bottom text-color-primary font-size-base align-items-center just-content-center w-100"
            style="height: 150rpx;">
            <text>购买记录</text>
        </view>
    </view>
    <loading v-else></loading>
</template>

<script>
    import loading from '@/components/loading'
    export default {
        components: {
            loading
        },
        data() {
            return {
                loading: true, //是否在异步获取数据
                packages: [] //券包数据对象
            }
        },
        async onLoad() {
            await this.getPackages()
        },
        async onPullDownRefresh() {
            this.packages = []//下拉刷新
            await this.getPackages()//获取数据
            // 当刷新操作完成后,停止下拉刷新效果
            uni.stopPullDownRefresh();
        },
        methods: {
            async getPackages() {
                this.loading = true
                setTimeout(() => {
                    // 这里是2秒后需要执行的代码
                    this.$api('packages').then((result) => {
                        this.packages = result
                        console.log(result); // 输出 '数据'
                    }); //调用api,获取券包数据
                    this.loading = false
                }, 500);
            },
        }
    }
</script>
<style>
    .container {
        padding-bottom: -150rpx;
    }
    
    .pay-btn {
        height: 50rpx;
        width: 120rpx;
        font-size: $font-size-sm;
        border-radius: 50rpx;
        padding: 0;
        line-height: 50rpx;
        text-align: center;
    }
</style>

★3、通过在packages.vue界面中通过对每个优惠券item设置点击事件 @tap="pay(item.id)",跳转时传入id参数到进到“购买券包”界面,该界面在packages文件夹下命名为detail.vue,并设置stlye,样式如下。注意这里是在同级文件夹下创建页面。

        "path" : "pages/packages/detail",
        "style" : 
        {
            "navigationBarTitleText": "购买劵包",
            "navigationBarTextStyle": "black",
            "navigationBarBackgroundColor": "#ffffff"
        }
    }

按照UI设计师效果图编写“购买券包”界面,界面分为三个模块研发,如图3.4.11。第一模块为一张图片,可以使用CSS的视口单位vh来设置元素的高度。vh单位表示视口高度的百分比,100vh就是整个视口的高度,图片设置height: 40vh。
3.4.11 购买券包三个板块.png

第二模块为优惠券详细列表,通过调用this.$api('packages')获取所有优惠券列表,“券包商品”界面跳转至本页面传入了id参数,利用id参数过滤找到本券包的优惠券数据信息。

<!-- 过滤数据 -->
this.package = packages.filter(item => item.id == option.id)[0]; //过滤数据

获取到package对象中的coupons为优惠券数据,使用v-for对coupons循环操作可完成列表展示。其中,coupon.detail.image为优惠券图片,coupon.coupon_num为优惠券数量,coupon.detail.coupon_title为优惠券作用,coupon.detail.expire为领券当日开始有效期,coupon.detail.coupon_use_time[0].use_time_start、use_time_end为使用开始时段和结束时段,如图3.4.12。
3.4.12 coupon对应item位置.png

第三模块为用户须知信息提示,使用view完成文字提示即可,代码如下:

    <view class="font-size-extra-lg" style="margin-bottom: 40rpx;">购买须知</view>
    <view class="font-size-base text-color-base">
        售卖时间:{{ package.start_at.split(' ')[0] }}~{{ package.end_at.split(' ')[0] }}
    </view>
    <view class="font-size-base text-color-base" style="margin-bottom: 30rpx;">
        购买限制:无限制
    </view>
    <view class="font-size-sm text-color-assist pre-line">
        {{ package.content }}
    </view>

完成三个模块的界面搭建后,最终效果如图3.4.3。
每个优惠券列表通过@tap="openCouponDetailModal(coupon)"完成交互,点击后弹出modal框做优惠券详细信息介绍,效果图如图3.4.13。引入'@/components/modal/modal'组件,通过couponDetailModalShow布尔参数判断modal框的显示与消失。部分代码如下:

    <modal custom :show="couponDetailModalShow" @cancel="closeCouponDetailModal">
        <view class="d-flex flex-column">
            <view class="text-center font-size-extra-lg text-color-base" style="margin-bottom: 30rpx;">
                {{ coupon.detail.coupon_title }}
            </view>
            <view class="text-center font-size-sm text-color-assist" style="margin-bottom: 40rpx;">
                {{ coupon.detail.expire }}
            </view>
            <view class="text-color-assist font-size-sm pre-line">
                {{ coupon.detail.desc }}
            </view>
        </view>
    </modal>

3.4.13 详情modal框.png

packages.vue跳转代码

            ...
            ...
    <!-- 卡片 -->
        <view class="d-flex flex-column w-100" style="padding: 30rpx; padding-bottom: -34rpx; margin-bottom: 150rpx;">
            <view class="d-flex align-items-center bg-white"
                style="padding: 30rpx; height: 220rpx; margin-bottom: 34rpx; border-radius: 8rpx;"
                v-for="(item, index) in packages" :key="index" @tap="pay(item.id)">
                <image :src="item.image" style="width: 200rpx; height: 160rpx; margin-right: 20rpx;"></image>
            ...
            ...
            ...
methods: {
            ...
            ...
            ...
            pay(id) {
                uni.navigateTo({
                    url: '/pages/packages/detail?id=' + id
                })
            }
        }
    }

“购买券包”界面detail.vue

<template>
    <!-- 购买劵包 -->
    <view class="w-100 h-100">
        <!-- 上方图片 -->
        <image :src="package.image" class="w-100" style="height: 40vh;"></image>
        <view style="padding: 30rpx 40rpx; padding-bottom: 100rpx;">
            <view class="d-flex justify-content-between align-items-center" style="margin-bottom: 20rpx;">
                <view class="font-size-lg">{{ package.title }}</view>
                <view class="text-color-primary font-size-sm">购买记录</view>
            </view>
            <view class="text-color-assist font-size-sm" style="margin-bottom: 20rpx;">
                共{{couponNum}}张优惠券
            </view>
            <!-- 优惠券列表 begin -->
            <view class="d-flex flex-column w-100">
                <view class="coupon d-flex flex-column bg-white" v-for="(coupon, index) in package.coupons" :key="index"
                    @tap="openCouponDetailModal(coupon)">
                    <view class="d-flex flex-fill overflow-hidden" style="margin-bottom: 20rpx;">
                        <image :src="coupon.detail.image" style="margin-right: 40rpx;width: 150rpx; height: 150rpx;">
                        </image>
                        <view class="flex-fill flex-column justify-content-start overflow-hidden">
                            <view class="text-right text-color-assist">x{{ coupon.coupon_num }}</view>
                            <view class="text-truncate font-size-extra-lg text-color-base w-80">
                                {{ coupon.detail.coupon_title }}
                            </view>
                            <view class="font-size-sm text-color-assist">{{ coupon.detail.expire }}</view>
                        </view>
                    </view>
                    <view class="bottom d-flex font-size-sm justify-content-between align-items-center">
                        <view class="text-color-assist">
                            使用时段:{{ coupon.detail && coupon.detail.coupon_use_time[0].use_time_start}}-{{coupon.detail && coupon.detail.coupon_use_time[0].use_time_end}}
                        </view>
                        <view class="text-color-primary">
                            查看详情
                        </view>
                    </view>
                </view>
            </view>
            <!-- 优惠券列表 end -->
            <view class="font-size-extra-lg" style="margin-bottom: 40rpx;">购买须知</view>
            <view class="font-size-base text-color-base">
                售卖时间:{{ package.start_at.split(' ')[0] }}~{{ package.end_at.split(' ')[0] }}
            </view>
            <view class="font-size-base text-color-base" style="margin-bottom: 30rpx;">
                购买限制:无限制
            </view>
            <view class="font-size-sm text-color-assist pre-line">
                {{ package.content }}
            </view>
        </view>
        <!-- 下方按钮 -->
        <view class="pay-box d-flex just-content-center align-items-center position-fixed fixed-bottom bg-white">
            <button type="primary" class="pay-btn font-size-base text-color-white rounded-pill"
                style="width: 90%;height: 80rpx; line-height: 80rpx;">
                ¥{{ package.amount }}购买
            </button>
        </view>
    <modal custom :show="couponDetailModalShow" @cancel="closeCouponDetailModal">
        <view class="d-flex flex-column">
            <view class="text-center font-size-extra-lg text-color-base" style="margin-bottom: 30rpx;">
                {{ coupon.detail.coupon_title }}
            </view>
            <view class="text-center font-size-sm text-color-assist" style="margin-bottom: 40rpx;">
                {{ coupon.detail.expire }}
            </view>
            <view class="text-color-assist font-size-sm pre-line">
                {{ coupon.detail.desc }}
            </view>
        </view>
    </modal>
    </view>
</template>
<script>
    import modal from '@/components/modal/modal'

    export default {
        components: {
            modal
        },
        data() {
            return {
                coupon: {
                    detail: {} //用这个对象来装
                },
                package: {
                    start_at: '', //将时间类型转化字符串
                    end_at: '' //将时间类型转化字符串
                },
                couponDetailModalShow: false
            };
        },
        computed: {
            couponNum() { //求和,通过对象
                return this.package.coupons && this.package.coupons.reduce((acc, coupon) => acc + coupon.coupon_num,
                    0) //计算和操作数组元素
            }
        },
        async onLoad(option) {
            const packages = await this.$api('packages');
            this.package = packages.filter(item => item.id == option.id)[0]; //过滤数据
        },
        methods: {
            openCouponDetailModal(coupon) { //dialog点击
                this.coupon = coupon
                this.couponDetailModalShow = true
            },
            closeCouponDetailModal() { //dialog关闭
                this.couponDetailModalShow = false
            }
        }
    }
</script>
<style lang="scss" scoped>
    .coupon {
        border-radius: 6rpx;
        padding: 20rpx 40rpx;
        margin-bottom: 40rpx;
        box-shadow: $box-shadow;
        position: relative;

        &::before {
            content: " ";
            position: absolute;
            background-color: $bg-color;
            width: 30rpx;
            height: 30rpx;
            bottom: 70rpx;
            left: -15rpx;
            border-radius: 100%;
        }

        &::after {
            content: " ";
            position: absolute;
            background-color: $bg-color;
            width: 30rpx;
            height: 30rpx;
            bottom: 70rpx;
            right: -15rpx;
            border-radius: 100%;
        }

        .bottom {
            height: 70rpx;
            position: relative;

            &::before {
                content: '';
                border-top: 2rpx dashed #E2E2E2;
                position: absolute;
                left: 0;
                top: 0;
                right: 0;
                transform: scaleY(0.2);
            }
        }
    }

    .pay-box {
        padding: 10rpx 0;
        height: 100rpx;
        background-color: #00000000;
    }
</style>

4、在完成本任务工单的研发工作后,团队成员应使用SourceTree工具执行版本提交,以创建此工单研发代码的历史版本记录。

工单效果成功!

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

推荐阅读更多精彩内容