Fork me on GitHub
余鸢

饿了么45个页面重构(七):v-if与v-show的区别及shop组件开发

餐厅食品页面shop.vue

点击某个餐厅进入餐厅食品页面。简单的说这个页面分三个区域,分别是头部、导航、内容。

头部

头部主要讲当前餐厅的基本信息,如商家名称头像、商家配送方式及费用、商家公告、商家优惠活动。

获取餐厅的详细信息

首先要做的就是获取餐厅的详细信息。使用axios请求数据。

shop.js代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
export function shopDetails (shopId, extras, latitude, longitude) {
const url = '/restapi/shopping/restaurant/' + shopId
const data = Object.assign({}, {
extras,
terminal: 'h5',
latitude,
longitude
})
return axios.get(url, {
params: data
}).then((res) => {
return Promise.resolve(res.data)
})
}

上面代码发现获取餐厅详情的url时需要latitudelongitude两个参数,我通过vuex管理机制对这两个参数进行赋值操作继而获取到餐厅详情。

shop.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import { msiteAdress } from 'api/msite'
import { mapState, mapMutations } from 'vuex'
import { shopDetails } from 'api/shop'
data () {
return {
shopDetailData: null
}
},
mounted () {
msiteAdress().then(res => {
this.SET_LATITUDE(res.latitude)
this.SET_LONGITUDE(res.longitude)
// 获取商铺信息
shopDetails(this.shopId, this.extras, this.latitude, this.longitude).then(res => {
this.shopDetailData = res
this.img = res.image_path
})
})
},
computed: {
...mapState([
'latitude', 'longitude'
])
},
methods: {
...mapMutations([
'SET_LATITUDE', 'SET_LONGITUDE'
])
}

获取到餐厅详情shopDetailData,接着就可以对dom填充数据。

餐厅头像: <img :src="getImgPath(img)" class="header_cover_img">

餐厅名称:<h4 class="description_title ellipsis"></h4>

餐厅配送信息:<p class="description_text">商家配送/分钟送达/配送费¥</p>

餐厅公告:<p class="description_promotion ellipsis">公告:</p>。这里我使用计算函数promotionInfo对餐厅公告进行判断操作。

1
2
3
4
5
6
computed: {
//商铺公告
promotionInfo () {
return this.shopDetailData.promotion_info || '欢迎光临,用餐高峰期请提前下单,谢谢。'
}
}

商家活动:首先判断商家详情中是否有商家活动v-if="shopDetailData.activities.length"也就是判断它的length,创建个showActivitiesFun点击事件展示商家活动的具体信息,具体信息包括优惠信息和商家公告。定义数据showActivities表示是否显示活动详情。

1
2
3
4
5
6
7
8
9
10
data () {
return {
showActivities: false,
}
},
methods: {
showActivitiesFun () {
this.showActivities = !this.showActivities
},
}

展示商家活动:遍历优惠信息获取每个item <li v-for="item in shopDetailData.activities" :key="item.id">,使用transition添加动画效果,之前已经讲过,这里就不再复述。判断是否显示v-if="showActivities"

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<transition name="fade">
<section class="activities_details" v-if="showActivities">
<h2 class="activities_shopTitle">{{shopDetailData.name}}</h2>
<h3 class="activities_ratingStar">
<rating-star :rating="shopDetailData.rating"></rating-star>
</h3>
<section class="activities_list">
<header class="activities_title_style"><span>优惠信息</span></header>
<ul>
<li v-for="item in shopDetailData.activities" :key="item.id">
<span class="activities_icon"
:style="{backgroundColor: '#' + item.icon_color, borderColor: '#' + item.icon_color}">{{item.icon_name}}></span>
<span>{{item.description}}(APP专享)</span>
</li>
</ul>
</section>
<section class="activities_shopInfo">
<header class="activities_title_style"><span>商家公告</span></header>
<p>{{promotionInfo}}</p>
</section>
<svg width="60" height="60" class="close_activities" @click.stop="showActivitiesFun">
<circle cx="30" cy="30" r="25" stroke="#555" stroke-width="1" fill="none"/>
<line x1="22" y1="38" x2="38" y2="22" style="stroke:#999;stroke-width:2"/>
<line x1="22" y1="22" x2="38" y2="38" style="stroke:#999;stroke-width:2"/>
</svg>
</section>
</transition>

代码用到v-if比较频繁,在这说下v-show和v-if的区别

1
2
3
4
1. v-if 是“真正的”条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。
2. v-if 也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。
一般来说, v-if有更高的切换开销,而v-show有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用v-show较好;如果在运行时条件不太可能改变,则使用v-if较好。

完整的代码这里就不贴了,见代码shop.vue组件。

页面渲染:

e15

导航

导航分为商品和评价。点击商品时会显示食品列表,点击评价显示评价相关信息。data中定义changeShowType表示切换显示商品或评价,默认为food,创建点击事件使其点击商品时对应的food,点击评价则对应的是rating。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<section class="change_show_type" ref="chooseType">
<div>
<span :class='{activity_show: changeShowType =="food"}' @click="changeShowType='food'">商品</span>
</div>
<div>
<span :class='{activity_show: changeShowType =="rating"}' @click="changeShowType='rating'">评价</span>
</div>
</section>
data () {
return {
changeShowType: 'food'
}
}

:class='{activity_show: changeShowType =="food"}'绑定当前显示为food。vue绑定class的使用方法,通过绑定一个class变量来直接操作,并且这里的逻辑会跟js代码里面对应。

内容

内容区域分为菜单列表、食品列表、购物车。

菜单列表

菜单列表就是食品分类,每个分类会有对应的食品列表。话不多说,先请求数据。

shop.js

1
2
3
4
5
6
7
8
9
10
11
12
13
export function menuList (id) {
const url = '/restapi/shopping/v2/menu'
const data = Object.assign({}, {
restaurant_id: id
})
return axios.get(url, {
params: data
}).then((res) => {
return Promise.resolve(res.data)
})
}

shop.vue

1
2
3
menuList(this.shopId).then(res => {
this.menuList = res
})

data钩子中定义数据menuList,表示菜单分类列表,得到数据后填充dom。

1
2
3
4
5
6
7
8
9
10
11
12
<section class="menu_container">
<!--菜单分类-->
<section class="menu_left" id="wrapper_menu" ref="wrapperMenu">
<ul>
<li v-for="(item,index) in menuList" :key="index" class="menu_left_li">
<img :src="getImgPath(item.icon_url)" v-if="item.icon_url">
<span>{{item.name}}</span>
<span class="category_num" v-if="categoryNum[index]">{{categoryNum[index]}}</span>
</li>
</ul>
</section>
</section>

v-for="(item,index) in menuList" :key="index"遍历出每个菜单分类。

<img :src="getImgPath(item.icon_url)" v-if="item.icon_url">要有个判断icon_url是否存在。

<span class="category_num" v-if="categoryNum[index]"></span>菜单类型右上角已加入购物车的数量,同时也需要判断是否有值。data中定义categoryNum,表示菜单类型右上角已加入购物车的数量。

食品列表

食品列表区域的最上方显示的是当前菜单的标题详情。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<section class="menu_right">
<ul>
<li v-for="(item, index) in menuList" :key="index">
<header class="menu_detail_header">
<section class="menu_detail_header_left">
<strong class="menu_item_title">{{item.name}}</strong>
<span class="menu_item_description">{{item.description}}</span>
</section>
<span class="menu_detail_header_right" @click="showTitleDetail(index)"></span>
<p class="description_tip" v-if="index == titleDetailIndex">
<span>{{item.name}}</span>
{{item.description}}
</p>
</header>
</li>
</ul>
</section>

在食品列表区域的左上角有个省略号的图标,是显示当前菜单的标题详情,这需要个点击事件,点击后才会显示内容。先在data中定义titleDetailIndex默认为null,表示点击展示列表头部详情。创建点击事件showTitleDetail(index)来控制显示列表标题详情提示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
data () {
return {
titleDetailIndex: null
}
},
methods: {
showTitleDetail (index) {
if (this.titleDetailIndex === index) {
this.titleDetailIndex = null
} else {
this.titleDetailIndex = index
}
}
}

效果如图:

菜单列表显示控制

在遍历过menuList的基础上,再对每个item的item.foods进行遍历,获得每个food。

v-for="(food, foodIndex) in item.foods" :key="foodIndex"

对food设置路由,点击某个food就直接进入下个页面。

<router-link :to="{path: 'shop/foodDetail'}" tag="div" class="menu_detail_link"></router-link>

food头像设置:<img :src="getImgPath(food.image_path)">

food名称:<strong class="description_foodName"></strong>

attributes:需要先判断attributes是否存在v-if="food.attributes.length",如果有对它进行遍历<li v-for="(attribute, foodIndex) in food.attributes" :key="foodIndex">,同时设置它的显示样式 :style="{color: '#' + attribute.icon_color,borderColor:'#' +attribute.icon_color}",绑定当前class显示:class="{attribute_new: attribute.icon_name == '新'}"

food描述:<p class="food_description_content"></p>

food月销售量和好评率:<span>月售份</span> <span>好评率%</span>

food价格:<span>¥</span>

food规格:<span v-if="food.specifications.length">起</span>

完整代码太长就不贴了,上面每个dom显示数据都有讲到,具体代码见shop.vue组件。

完整效果如图:

e16

具体代码见:

https://github.com/kakajing/vue-elmm