本文已参与「新人创作礼」活动,一起开启掘金创作之路。 个人主页:juejin.cn/user/180285…
项目:使用vue.js2.5 + cube-ui 实现饿了么商家页面
以 APP 外卖商家页面为主线,开发包括商品购买页(加入购物车)、商品详情(滚动列表查看商品、添加购物车、结算)、商家 介绍(商家详情、查看评分、收藏商家)、评论(过滤显示评论)等模块。
使用 vue-cli 初始化项目、mock 数据;使用了 cube-ui 开源的七个组件和一个create-api模块。采用组件化思想搭建整个项目,从而使组件高度复用,代码简洁。
解决的问题:购物车小球下落过渡动画设计实现,使用create-api模块解决全屏弹层类组件问题、用购物车 sticky 组件解决create-api层级较高组件遮挡问题,使用 html5 的 localstorage 接口实现收藏页面的本地缓存,五星评级组件和评论组件的封装复用等。
头部区:组件v-header、头部弹层组件header-detail(包括star组件)
内容区: (1)页面切换tab组件、
(2)商品goods组件(主要进行商品浏览购买、包含购物车shop-cart组件、购物车添加商品cat-control组件、购物详情shop-cart-list组件、shop-cart-sticky组件)
(3)评价ratings组件(包括rating-select复用的评价选项组件)
(4)商家详情seller组件 (店家信息展示 + 收藏页面本地缓存)
1. v-header组件
主要由内容区和公告区组成,核心要点在于公告栏要有不折行+显示缩略的效果。实现方法为设置属性:white-space: nowrap; overflow: hidden;text-overflow: ellipsis。还有就是对于整个header部分显示半透明模糊效果的图片,模糊效果使用filter。
2. header-datail浮层组件
整个组件效果是全屏效果,下部还有个固定的关闭按钮,由于本身高度可能超过手机高度,因此会滚,不能使用position fixed布局,如果内容比较长,则会盖住X。这里使用到了经典的css sticky footers 布局。尝试过把head-detail组件放在header组件中,为了防止父元素的嵌套样式效果影响组件,更好的方法是把该部分被创建为cube-ui中的create-API组件。(cube-ui提供了一个很好的API就是creat-api模块,让我们把一个模块从声明式的写(),而变为API调用(creatAPI(vue,component))即当一个组件执行createAPI之后,我们可以在组件内部通过$ceate(Star)来调用组件,并且可以动态挂载到body下面。因此就很适合这种全屏弹窗组件动态挂载到body下,而移除时可以从body下卸载。)在调用时,使用驼峰形式引入组件,传入props,创建之后通过组件调用show()方法控制显示,使用hide()方法控制浮层关闭。
3. star组件
显示是由三组图片组成(全星,半星,空),使用props接收size和score两个参数。计算属性依赖size实现三种大小的样式展现,通过计算score在数组中放入响应的星星状态,使用v-for遍历数组显示评分。
4. tab组件
使用cube-ui提供的tab-bar来实现页面切换。写入change事件(cube-ui的slide的)在slide页面切换时触发,并且派发当前页面的索引值,从而实现页面切换。为了优化体验,希望tab滑动时下划线可以跟着页面实现流畅滚动,可以根据tab占比和slide占比相同实时计算tab滚动位置。由于开始tab标签是写死的,个数也是固定的,因此希望这个tab可以接收一个数组,包含tab个数,tab名称,对应的组件,而tab只是用来维护这些内容,因此v-for遍历,使用动态提供组件。并在app.vue中添加默认的tab数组。
5. shop-cart组件
购物车shop-cart组件有多种状态:(1)没有购买商品时,购物车全部灰色(2)当购买商品时,购物车右上角回有商品件数,并且会按照商品计算出价格,(3)当达到配送金额会显示“去结算”,不然就显示“还差XX元起送”。其中对于购物车的设计主要使用了flex布局主要维护选择的商品。 Seller会在goods中声明成计算属性。Goods组件会接收一些tab组件传入的props,而tab的数据也是从app.vue中传入的。因此当动态获取seller之后props的获取是一个响应式的,可以一层层通过tab达到goods组件。
6. shop-control组件
主要分为三个部分(减号 数字 加号),其中点击加号会触发两个动画:(1)cart-control组件的渐出,(2)购物车小球下落动画。
对于(1):首先,使用v-show控制组件只有商品件数大于零显示。每个按钮维护一个food,并且对于按钮点击绑定add()和decease()事件用于记录商品的数量count。同时在父组件goods中维护计算属性selectFood(),用于计算商品数量,父组件将selectfood作为props传入shop-cart,从而实现两个兄弟组件之间的数据交互。对于过渡动画,使用组件通过v-show驱动。外层偏移内层rotate滚动,从而实现一边偏移一边滚动的渐出效果显示。
对于(2):想象购物车里面有很多隐藏的小球,点击按钮,小球会变到当前点击位置并显示,然后经过一个动画飞入购物车,并且支持多个小球同时在动画。首先定义balls数组用于存放所有小球,属性show默认设置为false。并在shop-cart中定义小球容器,v-show遍历小球,拿到true的小球做动画。并监听几个vue的钩子函数before-enter ,enter ,after-enter来做动画。在beforedrop中拿到点击的位置,并将购物车中的小球显示在该位置。在dropping中触发重绘reflow,并让做动画回到原来位置,从而实现小球下落,动画之后监听transtionend事件,触发afterDrop,修改小球show显示状态为false,回收小球,并将小球元素display设置为null。从而实现了一次小球下落。
7. shop-cart-list组件+shop-cart-sticky组件
当购物车中有商品时,点击购物车会弹出购物车详细内容,列表限制高度并可以滚动。(起初一期时没有将这一部分抽离出来,因此就在shop-cart组件的后半部分,通过一个标记来控制区块显示)二期,对于这种弹层类组件选择使用cube-ui的组件实现,并将其设置为API调用,设置hide() show()方法来控制蒙层显示。
但是有个问题:shop-cart-list是API组件,会动态的向body中挂载结点,因此层级较高,高过内层组件层级。但是购物车组件是一个子组件,因此会遮盖到原来的购物车组件(这个组件有个向上突出的部分)。由于无法调整层级,解决方法就是:使用shop-cart-sticky组件。动态向body中挂载一个购物车组件的副本。并且这一部分还要加入的功能有:(加入购物车的小球动画,结算,清空购物车)对于小球下落动画,可以调用cart-control的drop方法,两个弹出dialog的操作都使用了cube-ui中的dialog组件。而对于切换tab的购物车联动问题,只需要在请求数据之前判断是否已经拿到数据。
8. 商品详情
在商品页面中点击某一个商品,可以跳到商品详情页查看详情,包括:商品图片、名称价格、商品评价、加入购物车。该组件为全屏弹层组件,如果单纯作为goods的子组件调用会出现不能全屏显示的问题。因此将其注册为create-API模块,让good组件动态挂载到body下。(这样就可以使用fixed全屏布局以及层级的z-index来控制是否盖住下面的header部分。)(原因:food是fixed布局,外部有一个transform,fixed布局会受到transform影响,因此对于全屏类弹层组件,组件的样式很容易受到父元素影响,(fixed布局是全屏类的))同样的,该页面也需要控制shop-cart-sticky购物车组件显隐。
9. 评论部分
有两个维度实现过滤:首先可以查看评价类型(全部、推荐、吐槽)还有“只看有内容的评价”,将其该部分设置为rating-select组件,接收props(ratings是个数组(通过ratings可以计算各部分的评价个数);selectType是选择类型(有三种类型,定义三种常量POSITIVE、NEGATIVE、ALL)使用常量增加可读性;onlycontent是否有内容;desc描述(对象默认描述:all:"全部" positive:“满意” negative:“不满意”) )外部可以传入标签,从而实现复用。在food中添加计算属性computedRatings,定义数组并遍历将符合的ratings放入(当选中所有时push(rating)或者当前选择类型和这条评价类型相同,也push(rating))从而按情况显示ratings
10. 收藏页面本地缓存
设计思想:首先收藏的效果就是,点击按钮切换图标的显示状态,但是问题是,当再次刷新页面,状态会被重新初始化。由于项目只提供了一个商家的信息,并且不包含id。因此,希望通过url解析出包含id值的一个对象,在每次请求时加上id进行模拟收藏效果。为了实现本地存储,使用了封装了sessionstorage的storage库,提供了set get接口,和直接调用原生api 需要对对象和字符串进行转换相比,使用起来更简单,可以直接对对现象操作。导出两个接口saveLocal和loadLocal,来实现本地缓存。saveload中构造一个复杂对象,第一层是id,它还是一个对象,包含收藏和其他需要存储的项,然后返回该对象。最后的效果就是:点击收藏之后会在localstorage中添加一个__seller__,会显示id,以及favorite的true false。
11. create-api分析
执行create-api会向vue原型上挂载createHeadDetail);在header组件实例中调用this.createheadDetail而将header看作为其父组件。它的结果是一个组件实例apiComponent,因此就可以调用组件的方法。整个create-api模块的设计也使用了vue的钩子函数实例方法完成api组件的生命周期,像vue组件生命周期一样,它也有创建,更新和销毁过程。
创建过程:根据传入的参数做一些处理,如processProps。之后就会新建一个vue实例,它的child就是实例组件。在执行vue实例初始化的过程中首先执行init()方法(append to body将组件的DOM添加到body下,这也是它可以动态挂载到body的原因)
更新过程:(如props数据更新)组件会重新渲染,一般使用的组件来说props就是响应式的,一旦改变就会触发组件重新渲染。但是create-api组件会对更新的props进行watch(调用方watch传入的props)当调用方数据发生变化,就会执行回调函数(触发vue的forceupdate,从而使api组件重新渲染)
销毁过程:当调用方销毁,api组件会做相应处理:监听before Destroy钩子函数,当我们api组件的父实例销毁,那么apiComponent 会执行remove方法(清理并执行destroy方法,从而把DOM从body上移除)
项目详情章节传送:
【Vue项目实战】vue.js2.5 饿了么APP(1)概述+项目准备 juejin.cn/post/708900…
【Vue项目实战】vue.js2.5饿了么APP(2)主要组件实现 - 头部相关组件 - 【速看】面试教你有话可说 juejin.cn/post/708932…
【Vue项目实战】vue.js2.5饿了么APP(3)主要组件实现 - 购物车相关组件(上)juejin.cn/post/708900…
【Vue项目实战】vue.js2.5饿了么APP(4)主要组件实现 - 购物车相关组件(下)juejin.cn/post/709075…
【Vue项目实战】vue.js2.5饿了么APP(5)主要组件实现 - 商品详情页部分 juejin.cn/post/709627…
【Vue项目实战】vue.js2.5饿了么APP(6)主要组件实现 - 评价页+商家页部分【速看】面试教你有话可说 juejin.cn/post/709701…