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

来源:互联网 发布:wto国际贸易统计数据库 编辑:程序博客网 时间:2024/05/16 15:52

本节重点:
- 父组件到子组件的数据传递
- 子组件到父组件的数据传递
- 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>
阅读全文
0 0