饿了么项目---13、模块化编程,实现商品详情页面

本节重点:
- 父组件到子组件的数据传递
- 子组件到父组件的数据传递
- js的模块化,按需引入
- 组件的模块化,重复使用
- 数组过滤与过滤器的使用

一、goods商品界面点击到food详情界面的实现

  • 父组件good.vue界面:
    这里写图片描述
  • 与子组件的关联
    • 点击商品时,更改传入的商品数据this.selectedfood = food
    • 根据子组件的DOM节点this.$refs.thisfood,调用子组件的showthisfood方法,显示子组件
    • 代码部分如下:
<template>
<food @cartAddEvent='_drop' ref='thisfood' :selectedfood = 'selectedfood'></food>
</template>

点击商品时触发事件调用该方法:

//点击展示商品详情界面
showfood(food,event){
    //better scroll中阻止浏览器默认的点击事件
    if(!event._constructed) return false;
    this.selectedfood = food;
    this.$refs.thisfood.showthisfood()
}

二、food详情界面的实现

  • 界面实现结构图
    这里写图片描述
  • 详情界面切入、切出使用过渡
  • 详情界面内容过长时,使用better scroll滑动组件
  • 加入购物车cartcontrol组件的复用
    • 详情界面中第一次添加商品到购物车使用 加入购物车按钮,并添加小球抛入到购物车的效果
    • 当该商品已经被添加过,引入加入购物车cartcontrol组件,实现商品累加与累减功能
    • 这个功能实现时,遇到一个的问题,点击加入购物车按钮,该按钮消失,加入购物车cartcontrol组件展示。此时实现小球抛入效果,无法取得该DOM节点的坐标,此DOM节点已经display:none .解决方法:给加入购物车按钮的消失一个过渡效果,巧妙的在小球抛入过渡时取到改DOM节点
  • 商品评价中,筛选条件在另一个模块可以被复用,抽象为ratingSelecte组件,详细组件代码见本章尾部
<!--
-- ratings: 该商品的评论数据 Array ;
-- suggestion:商品评价的中文条件 Object
-- selectType:商品评价的中文条件选中的值,默认为全部 Number
-- onlyContent:是否只看内容的值 ,默认为false ,Boolean
-- 事件 onlyshow,selectCon: 点击筛选条件时派发的事件,更新父组件的数据
-->
<ratingSelecte :ratings = 'selectedfood.ratings' :suggestion ='suggestion' :selectType = 'selectType' :onlyContent='onlyContent' @onlyshow='onlyshowcon' @selectCon = 'selectContent'></ratingSelecte>
  • 根据ratingSelecte组件的筛选条件,过滤 评论内容
    • 每一行评论是否显示的控制v-show=’showRating(rating)’,showRating方法根据筛选条件来进行过滤
Template模板部分:
<div class='select-content'>
                            <ul v-if='selectedfood.ratings'> 
                                <li v-show='showRating(rating)' class='ratingGroup' v-for='rating in selectedfood.ratings'>
                                    <div class='ratingH clearfix'>
                                        <div class='time'>{{rating.rateTime | dateString}}</div>
                                        <div class='user'><span class='username'>{{rating.username}}</span><img class='userImg' :src='rating.avatar'></div>
                                    </div>
                                    <div class='ratingC'><span class='iconThumb' :class='{"icon-thumb_up":rating.rateType===0,"icon-thumb_down":rating.rateType===1}'></span><span class='text'>{{rating.text}}</span></div>
                                </li>
                            </ul>
                        </div>

JS代码部分

//是否显示评价信息
            showRating(rating){
                //只看评价时,不显示的
                if(this.onlyContent && rating.text==''){
                    return false;
                }
                //点击三个评价筛选
                if(this.selectType===All){
                    return true;
                }else{
                    return this.selectType ===rating.rateType;
                }
            }

三、js模块化

将需要重复使用的js 方法抽象成一个公共的js,在需要使用到的组件中,import引入方法

import {dateToString} from '../../common/js/common.js';

需要引入多个方法时,例如需要引入a,b,c方法

import {a,b,c} from '../../common/js/common.js';

common.js:

// 将时间对象转换成字符串 “yyyy-mm-dd hh:mm”
export function dateToString(date) {
    if (date == null || date == '') {
        date = '';
        return date;
    }
    var year = date.getFullYear();
    var month =parseInt(date.getMonth())+1;
    var day = parseInt(date.getDate());
    var hours = date.getHours();
    var Minutes = date.getMinutes();
    if (Minutes < 10) {
        Minutes = Minutes + "0";
    }
    date =year+'-'+month+'-'+day+' '+ hours + ':' + Minutes;
    return date;
}

涉及的完整代码

food组件

<template>
        <transition name='food'>
            <div class="food" v-show='showthis' ref='food'>
                <!-- 为bettter scroll 添加div层 -->
                <div>
                    <!-- 商品图片 -->
                    <div class='foodImg'>
                        <img :src="selectedfood.image">
                    </div>
                    <!-- 返回到商品foods.vue页面 -->
                    <div class='backGoods' @click='hidethisfood'><span class='icon-arrow_lift'></span></div>
                    <!-- 商品信息 -->
                    <div class='foodInfo'>
                        <div class='goodsName'>{{selectedfood.name}}</div>
                        <div class='goodsxs'><span>月售{{selectedfood.sellCount}}</span><span>好评率{{selectedfood.rating}}%</span></div>
                        <div class='foodsPrice'><span>{{selectedfood.price}}</span><span class='old' v-show='selectedfood.oldPrice!=""'>{{selectedfood.oldPrice}}</span>
                        </div>
                        <div class='cartcontrol-wrapper'>
                            <cartcontrol @cartAdd ='cartAddcontrol' :food ='selectedfood'></cartcontrol>
                        </div>
                        <transition name='firstAdd'>
                        <div class='addtocart' v-show='selectedfood.count===0||!selectedfood.count' @click='addCount($event)'>加入购物车</div>
                        </transition>
                    </div>
                    <!-- 商品介绍 -->
                    <div class='foodDetail'>
                        <h1 class='food-header'>商品介绍</h1>
                        <div class='food-content'>{{selectedfood.info}}</div>
                    </div>
                    <div class='foodAsses'>
                        <h1 class='Asses-header'>商品评价</h1>
                        <ratingSelecte :ratings = 'selectedfood.ratings' :suggestion ='suggestion' :selectType = 'selectType' :onlyContent='onlyContent' @onlyshow='onlyshowcon' @selectCon = 'selectContent'></ratingSelecte>
                        <div class='select-content'>
                            <ul v-if='selectedfood.ratings'> 
                                <li v-show='showRating(rating)' class='ratingGroup' v-for='rating in selectedfood.ratings'>
                                    <div class='ratingH clearfix'>
                                        <div class='time'>{{rating.rateTime | dateString}}</div>
                                        <div class='user'><span class='username'>{{rating.username}}</span><img class='userImg' :src='rating.avatar'></div>
                                    </div>
                                    <div class='ratingC'><span class='iconThumb' :class='{"icon-thumb_up":rating.rateType===0,"icon-thumb_down":rating.rateType===1}'></span><span class='text'>{{rating.text}}</span></div>
                                </li>
                            </ul>
                        </div>
                    </div>
                </div>
            </div>
        </transition>
</template>
<script>
import BScroll from 'better-scroll';
import cartcontrol from '../cartcontrol/cartcontrol.vue';
import ratingSelecte from '../ratingSelecte/ratingSelecte.vue';
import {dateToString} from '../../common/js/common.js';
//商品评价的筛选状态
const SATISFY=0; //推荐
const UNSATISFY=1; //吐槽
const All=2; //全部
    export default{
        name:'food',
        props:{
            selectedfood:{
                type:Object
            }
        },
        data(){
            return{
                showthis:false,//显示或隐藏改组件
                suggestion:{
                    all:'全部',
                    satisfy:'推荐',
                    unsatisfy:'吐槽'
                },
                selectType:All,
                onlyContent:false//只看内容
            }
        },
        methods:{
            showthisfood(){
                this.showthis = true;
                //初始化筛选内容
                this.selectType=All,
                this.onlyContent=false//只看内容
                this.$nextTick(()=>{
                    //better scroll需要更新DOM对象
                    if(!this.scroll){
                        this.scroll = new BScroll(this.$refs.food, {
                            click:true
                        });

                    }else{
                        this.scroll.refresh();
                    }
                })
            },
            hidethisfood(){
                this.showthis = false;
            },
            cartAddcontrol(el){
                this.$emit('cartAddEvent',el);
            },
            addCount(el){
                //为food数量置1
                this.$set(this.selectedfood,'count',1)
                //派发小球点击事件,做抛物动画
                this.$emit('cartAddEvent',el.target);
            },
            //切换评价筛选条件
            selectContent(type){
                this.selectType = type
                //切换筛选条件时,更新了DOM节点,要更新better scroll
                this.$nextTick(()=>{
                    this.scroll.refresh()
                })
            },
            onlyshowcon(){
                this.onlyContent = !this.onlyContent
                this.$nextTick(()=>{
                    this.scroll.refresh()
                })
            },
            //是否显示评价信息
            showRating(rating){
                //只看评价时,不显示的
                if(this.onlyContent && rating.text==''){
                    return false;
                }
                //点击三个评价筛选
                if(this.selectType===All){
                    return true;
                }else{
                    return this.selectType ===rating.rateType;
                }
            }
        },
        components:{cartcontrol,ratingSelecte}
        ,
        filters:{
            //将字符串时间转换成时间格式
            dateString(datenumber) {
                let date = new Date(datenumber);
                return dateToString(date);
            }

        }
    }
</script>
<style lang="stylus" rel="stylesheet/stylus" scope>
@import '../../common/stylus/mixin.styl'
.food
    position:fixed
    top:0
    left:0
    bottom:48px
    width:100%
    background-color:#f3f5f7
    z-index:30
    &.food-enter-active,&.food-leave-active
        transition:all 0.4s
    &.food-enter,&.food-leave-to
        transform:translate3d(100%,0,0)
    .foodImg
        position:relative
        width:100%
        height:0
        padding-top:100%
        img
            position:absolute
            left:0
            top:0
            width:100%
            height:100%
    .backGoods
        position:absolute
        left:10px
        top:10px
        .icon-arrow_lift
            color:#fff
    .foodInfo
        position:relative
        padding:18px;
        background-color:#fff
        border-1px(rgba(7,17,27,0.1))
        .goodsName 
           font-size:14px
           font-weight:700
           color:rgb(7,17,27)
           line-heigth:14px
        .goodsxs
            margin:8px 0 18px 0
            line-height:10px
            font-size:10px
            color:rgb(147,153,159)
            span:first-child
                margin-right:12px
        .foodsPrice
            font-size:10px
            color:rgb(240,20,20)
            font-weight: normal
            line-height: 14px
            margin-top:8px
            .old
                text-decoration:line-through
                margin-left:8px
                color:rgb(147,153,159)
        .cartcontrol-wrapper,
        .addtocart
            position:absolute
            bottom:18px
            right:18px
        .addtocart
            width:74px
            height:24px
            color:#fff
            font-size:10px
            line-height:24px
            text-align:center
            border-radius:12px
            background-color:rgb(0,160,220)
            &.firstAdd-enter-active,&.firstAdd-leave-active
                transition:opacity 0.6s
            &.firstAdd-enter,&.firstAdd-leave-to
                opacity:0
    .foodDetail
        margin:16px 0
        padding:18px
        background-color:#fff
        border-1px(rgba(7,17,27,0.1))
        borderTop-1px(rgba(7,17,27,0.1))
        .food-header
            font-size:14px
            font-weight:500
        .food-content
            margin-top:6px
            padding:0 8px
            font-size:12px
            font-weight:200
            line-height:24px
            color:rgb(77,85,93)
    .foodAsses
        background-color:#fff
        border-1px(rgba(7,17,27,0.1))
        borderTop-1px(rgba(7,17,27,0.1))
        .Asses-header
            padding:18px 18px 6px 18px
            font-size:14px
            font-weight:500
        .select-content
            padding:0 18px
            .ratingGroup:last-child
                border-none() 
            .ratingGroup
                padding:16px 0
                border-1px(rgba(7,17,27,0.1)) 
                .ratingH
                    margin-bottom:6px
                    .time
                        float:left
                        line-height:12px
                        color:rgb(147,153,159)
                        font-size:10px
                    .user
                        float:right
                        height:12px
                        .username
                            display:inline-block
                            vertical-align:top
                            line-height:12px
                            color:rgb(147,153,159)
                            font-size:10px  
                            margin-right:6px
                        .userImg
                            display:inline-block
                            vertical-align:top
                            width:12px
                            height:12px
                            border-radius:50%
                            img
                                width:100%
                                height:100%
                .ratingC
                    line-height: 24px;
                    height: 24px;
                    .iconThumb
                        font-size:12px 
                        display:inline-block
                        vertical-align:middle
                        margin-right:4px
                    .icon-thumb_down
                        color:rgb(147,153,159)
                    .icon-thumb_up
                        color:rgb(0,160,222)
                    .text
                        font-size:12px
</style>

ratingSelect组件

<template>
    <div class='ratingSelect'>
        <div class='select-header'>
            <div v-if='ratings' class='suggestion'>
                <span class='su' :class="{on:selectType == 2}" @click='_selectContent(2)'>{{suggestion.all}}&nbsp;{{ratings.length}}</span>
                <span class='su' :class="{on:selectType == 0}" @click='_selectContent(0)'>{{suggestion.satisfy}}&nbsp;{{satisfyNumber.length}}</span>
                <span class='no' :class="{on:selectType == 1}" @click='_selectContent(1)'>{{suggestion.unsatisfy}}&nbsp;{{unsatisfyNumber.length}}</span>
            </div>
            <div class='onlyContent' @click='_onlyshowcon'>
                <span class='icon-check_circle' :class='{oncheck_circle:onlyContent===true}'></span><span class='text'>只看有内容的评价</span>
            </div>
        </div>
    </div>
</template>
<script>
const SATISFY=0; //推荐
const UNSATISFY=1; //吐槽
const All=2; //全部
    export default{
        name:'ratingSelect',
        props:{
            ratings:{
                type:Array
            },
            suggestion:{
                type:Object
            },
            //筛选条件之一
            selectType:{
                type:Number,
                default:All
            },
            //是否只看内容
            onlyContent:{
                type:Boolean,
                default:false
            }
        },
        computed:{
            satisfyNumber(){
                return this.ratings.filter((rating)=>{
                    return rating.rateType === SATISFY
                })
            },
            unsatisfyNumber(){
                return this.ratings.filter((rating)=>{
                    return rating.rateType === UNSATISFY
                })
            }
        },
        methods:{
            _selectContent(type){
                if (!event._constructed) {
                  return;
                }
                // this.selectType = type
                this.$emit('selectCon',type)
            },
            _onlyshowcon(){
                if (!event._constructed) {
                  return;
                }
                // this.onlyContent = !this.onlyContent
                this.$emit('onlyshow')
            }
        }
    }
</script>
<style lang="stylus" rel="stylesheet/stylus" scope>
    @import '../../common/stylus/mixin.styl'
    .ratingSelect
        .select-header
            .suggestion
                padding:12px 0 18px 0
                margin:0 18px
                border-1px(rgba(7,17,27,0.1))
                font-size:0
                .su,.no
                    font-size:12px
                    padding:8px 12px
                    border-radius:2px
                .su
                    margin-right:8px
                    background-color:rgba(0,160,220,0.2)
                .no
                    background-color:rgba(77,85,93,0.2)
                .on
                    background-color:rgb(0,160,220)
                    color:#fff
            .onlyContent
                padding:12px 18px
                border-1px(rgba(7,17,27,0.1))
                .icon-check_circle
                    display:inline-block
                    vertical-align:middle
                    font-size:24px
                    color:rgb(147,153,159)
                    margin-right:4px
                .oncheck_circle
                    color: #00c850
                .text
                    display:inline-block
                    color:rgb(147,153,159)
                    font-size:12px
                    line-height:24px

</style>
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值