首页
美食搜索页(滑动,点击字母表到对应首字母列表)
食物搜索自动匹配
细节展示页
本次项目的收获:
- 彻底入门Vue应用,Vue各种工具的使用—Vuex,Router,localStorage
- 完整参与整个Vue项目的开发流程,上手中型Vue项目开发
- 移动端页面的布置技巧(width和padding-bottom预留空间,防止网速慢的抖动,首页banner图需要)
- Vue的设计模式—MVVM设计模式
- 组件化的概念,单文件组件的概念,公用组件的拆分
- iconfont,stylus——编写样式
难点:
1. 各种依赖的使用—Awesome-swiper,Bscroll
Awesome-swiper:我们想在首页实现banner图的轮播展示
- 在github上找到Vue-Awesome-swiper
按照文档:安装,重启项目,引入
本项目使用Swiper 3(2.6.7版本相对稳定)
-
使用—去新建我们的swiper组件,把需要的swiper代码粘贴到组件的模板里
可以看到粘贴来的swiper有一个绑定的属性swiperOption,所以我们需要在swiper组件里的data定义一个这样的数据(子组件定义data一定是个函数)
-
在父组件(首页组件)上引入,展示swiper组件
现在swiper组件就已经应用到项目中了
接下来就是把我们需要的内容放进swiper-slide标签里就可以
这里有一个问题,有时候swiper组件自带的样式不是我们想要的,比如滑动展示下面的小点,那应该如何修改?
在页面选中这个小点的元素发现他的样式是这个,但其实这个样式并不在我们写的组件里,而是再swiper本身自带的样式中,所以在这个组件里直接去改样式改不了——如何改它自带的样式——这就用到了样式穿透
解释:让这个wrapper下面只要出现的的swiper-pagination-bullet-active都给这样的样式(变成白色)这样就不受scope的限制
tips:可以在数据中的swiperOptions里加一个loop属性,赋值true。意思是让这个swiper组件支持循环轮播
Bscroll:想要实现食物选择页的滑动展示,使用better-scroll
- 去github上找到better-scroll插件
- 按照正确的方法去引入-创建better-scroll实例-使用
按照better-scroll官网的模板要求,在外层包裹一个div
给最外层div一个ref赋值为wrapper,
借助mounted属性使用插件
这样我们的页面就已经可以正常使用better-scroll了,现在页面上这个better-scroll的区域就变成滚动区域了!
2. 首页图标区域的翻页逻辑—根据数据项的不同,借助computed属性完成自动化构建页码
我们希望当首页的图标数量超过页面的展示范围时,可以自动展示在下一页,滑动可看到
既然希望可以滑动到下一页展示第九个图标,肯定先用swiper组件按照正确的方法把我们的图标包裹起来——使用swiper组件
这是我们的list数据
循环输出对应的数据,在img标签里绑定对应的imgUrl,p标签里绑定对应的desc
接下来就是js的魅力
——在computed属性中去监听,pages的变化,声明数组pages,遍历list数组,对每一项做除法并取整,并将值赋值给page变量,判断pages[page],前八个都是undefined,取反为true,每一项都变成一个长度为1的数组,第九项时!pages[page]为false,直接赋值斤pages[8](思考一下这个过程)
这就将一个一维的有九项数据的数组,转换成了一个页码和对应的数据项关联在一起的二维数组,这个二维数组pages,里面存放着我们的图标和des等数据
3. 美食选择页搜索框输入字符自动匹配食品名称的逻辑
如果能在foods数据中能搜索到到对应的spell或者name,那就把找到的项push进我们的result数组里,把这个数组给list,然后循环展示list就够了
但是现在如果返回的符合条件的数据较多,是无法滚动的——借助better-scroll去实现滚动,具体方法见上文
优化:当没有匹配项的时候显示‘没有找到匹配数据’
4. 美食选择页的点击/滑动字母表到对应首字母食品名称的逻辑(兄弟组件的数据传递)—watch属性,scrollToElement插件
Eg:点击D字母,自动滚动到D开头对应的区域
点击字母表滚动到相应区域的具体逻辑实现分为两部分: 1. 传值 2. 滚动
1. 传值:这里我们兄弟组件间的传值,由于是简单的兄弟组件关系(Alphabet组件向List组件传点击/滑动到的字母值)所以使用的是较为直接的,Alphabet组件(子)——>Food组件(父)——>List组件(子)的传值方式,这个过程就是正常的父子组件的传值,子组件通过emit触发事件,父组件监听事件,并把传来的值存在自己的data里;然后通过属性绑定的方式向另一个子组件传值,另一个子组件通过props接收这个值,并保存在自己的data中进行使用———better-scroll提供了一个scrollToElement这样的方法,可以让better-scroll的滚动区域自动的滚动到某一个元素上(懂我意思了吧)1. Alphabet组件中绑定事件通过$emit触发事件向父组件Food传它的innerText
父组件Food去监听事件,接收值,把传来的值自己的data中
父组件给子组件绑定属性传值,子组件接收值
2. 滚动:子组件List借助watch属性监听传来的letter的变化,借助scrollToElement方法,可以让better-scroll的滚动区域自动的滚动到传来的值所对应的元素上。
首先给需要滚动的(第三部分区域)绑定一个ref属性赋值为key,循环就会给每个首字母开头的item数组(数组A,数组B,数组C...)绑定对应的key值,这个key值就是json数据中开头的大写字母
const element = this.$refs[this.letter][0];
子组件List之前接收到了传来的字母letter,借助this.$refs我们就获取到了我们想要滚动到的首字母的dom(为什么要加【0】,因为在html标签中我们是使用循环获取到的数据,数据里其实是多个数组,这些数组里存放的是以这个首字母开头的各种的食品名称)
有了想要滚动到的dom,只需要借助scrollToElement方法,滚动到对应的区域就可以了
滚动字母表滚动到相应区域:我们想通过监听滚动事件,传值,并滚动到对应区域
逻辑:首先获取第一个字母A距离顶部的高度,然后获取滑动时,手指距离顶部的高度,做一个差值,再除以每个字母的高度,就可以得到当前手指对应字母的下标,接下来向外触发点击滚动事件的handleLetterChange事件,就可以实现滚动字母表至对应区域了
- 找到数组:首先想要根据下标找到对应下标的字母,那就要先有这个数组,我们借助computed监听letters的变化,通过循环遍历出这个数组
- 函数:在字母表组件的字母表上绑定3个函数touchstart,move,end,在data中准备一个touchStatus变量用来保存触摸状态,.offsetTop获取第一个字母A距离顶部header的高度startY,获取手指的高度touchY减去79(是header区域的高度,因为clientY获取的是到页面顶部的高度)
滚动优化1. 将startY放在updated函数中,只用计算一次就行了 2. 做一个截流函数,限制函数执行的频率
- startY
- 截流函数:声明一个变量timer初始值为null,如果这个值存在,那么我清除这个值,如果不存在,那我给他这个值一个函数(滑动函数)并延迟16ms去执行,在这16ms期间滑动,函数就不会计算,提高了性能
. vue中的路由原理(history 哈希 区别)
routes 是一组路由,把上面的每一条路由组合起来,形成一个数组。[{home 按钮 =>home内容 }, { about按钮 => about 内容}]
path——网址访问的目录
name——这个路由的名字,相当于要跳转的页面的名字
component——这个路由对应的组件的名字
在route里面配置路由信息(红框),在上方引入对应的路由组件(黄框),
希望点击header上食堂区域的时候,页面跳转到食品搜索页
在对应的div外包裹router-link标签,给它to属性赋值到对应的网址目录
在路由文件里去引入配置对应的路由,并引入路由对应的组件,这样就可以实现点击跳转了
axios的使用
在父组件中去发送ajax请求,再把请求到的数据,传递给需要数据的子组件是最合理的,性能较高(不需要每个子组件都去发送请求)
-
安装axios
-
在父组件引入,使用axios,借助生命周期函数发送axios请求
ApiUrl.api是为了避免在项目上线前改动代码(风险较大),vue中提供了一个proxy代理的功能,可以实现,在开发环境中,用api这样的路径,把api下的所有对json文件的请求,转发到本地的mock文件夹下,这就需要我们去vue项目的config文件夹下的index.js中proxTable配置对应的路径(这个功能实际上不是vue提供的,而是webpack-dev-server提供的)改动了配置项文件后要重启项目才会生效
ret代表正确服务器正确响应了axios请求
-
这样我们就请求到了需要的数据,接下来就是在父组件中把请求到的数据传给各个子组件
讲res。data赋值给res,如果res.ret和res.data都存在,就意味着正常返回了数据,那么把res.data赋值给新声明的变量data,然后把data中各项对应的数据赋值给这个服组件本身的data里提前声明好的变量里(这就把请求来的数据保存在了父组件自己的data里)
-
这样我们就成功的通过axios请求,获得了json文件的数据,接下来就是正常的组件间传值了
Vuex插件实现数据共享(localStorage,mapState)将食品选择页选择到的食品名称共享到首页的右上角和选择页的当前食物一栏里
vuex插件官方介绍
安装使用vuex插件
新建一个文件夹配置vuex,引入vue,引入vuex,Vue.use(Vuex),导出vuex实例
去main.js文件夹里引入vuex文件,在根vue实例中传入vuex
现在我们想要共享的数据就在vuex的store里的state中了,之前的food是axios获取到的json文件里的,现在我们不需要它再从后端传过来了,我们已经可以在前端自己存储一个默认的值了,那如何使用这个值?
因为之前在根vue实例中传入了vuex,所以在以后的每一个vue实例中都可以访问到vuex的内容,通过刀乐store.state可访问到需要的属性,这就OK了
我们需要当点击热门食物或其他食物的时候,也去改变vuex中state的值,
可以看到我们想要改变state的数据需要组件先通过dispatch派发actions,actions通过commit调用mutations,mutations才能去该改变state的值
所以首先去list组件中绑定事件,事件中用dispatch去派发changefood的action,并传去一个数据food
现在去vuex的store里写对应的actions和mutations就行,actions中之所以写一个xtc,是因为它可以借助ctx拿到commit这个方法,去调用mutations中对应的函数,mutations有一个参数是state,这样整个流程就走完了,state的共享数据就会被点击事件改变
优化逻辑: 使用vue-router的方法(之前在根vue实例中引入过router),让食品名被点击后返回首页并在右上角显示食品名称,借助刀乐router.push('想要跳转的页面')即可。这也算是实现了几个页面的联动
优化:localStorage
之前实现的页面间联动,实际上有点小bug,当食品名被点击后返回首页右上角显示食品名时,我们刷新一次,发现就不是上次点击的食品名,而是默认的食品名,这和实际应用不符(例如美团选择城市后,就会保持是那个城市,每次打开都是那个城市,我们需要刷新后他应该是上次选的食品名)这就用到了localStorage
这样就实现了localStorage保存上次点击的数据,state的数据会优先从localStorage中取,如果没有(也就是第一次打开页面),那就先使用默认数据——这就完成了我们的需求
当时用localStorage时,如果用户关闭了本地存储或者开了隐身模式这样的功能,我们的代码使用localStorage可能会直接导致浏览器跑出异常,代码就整个运行不了,所以最好在外层包裹一个try catch
实际生产中我们会把这个文件拆分开——index.js , mutaions.js , state.js便于后续的维护和管理
优化:mapState是指,把vuex中的state数据映射在vue组件的计算属性中(可以是数组也可以是对象)
引入mapState,在computed计算属性中使用mapState映射food属性,就可以在组件的模板里使用这个属性了
优化前是这样的
优化后模板就简单明了的多了(数组)
优化后模板就简单明了的多了(对象):意思是把vux种的food数据,映射在computed计算属性中,映射过来的名字是currentFood,模板上使用时就可以使用currentFood
优化:mapMutations:把vuex中的mutations映射在vue组件的计算属性中
递归组件——实现组件自己调用自己
快速使用样式的快捷方法
例如我们写一个样式,当超出当前宽度时,内容显示...的样式
在目录下创建一个xxx.styl如图
在需要的组件中去引入这个文件
在需要的地方直接使用就可以