栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > Web开发 > Vue.js

Vue 2.5 开发 去哪儿

Vue.js 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Vue 2.5 开发 去哪儿

本文详细记录了所有 Vue 2.5 开发过程轨迹。根据每个节点 branch 总结归纳了项目开发过程中需要注意的细节和重点。便于以后查阅,同时进行开源分享。欢迎各位 Star 和 Fork。

项目 github 开源地址:https://github.com/evenyao/Travel

mytravel

Vue 2.5 开发移动端旅游网站项目整体流程与记录。


效果预览

扫描二维码:


项目涉及到技术栈:
  • Vue:Vue、Vue-router、Vuex、Vue-cli

  • 插件:vue-awesome-swiper、better-scroll、axios

  • CSS的预处理框架:stylus

  • api:后台数据接口

项目特点
  • 组件化自适应布局

  • 代码,简洁,易维护

  • 兼容大部分浏览器

  • 实现性能优化

项目具体结构首页部分
  • iconfont 的引入和使用

  • 图片轮播组件的使用

  • 图标区域轮播组件的使用

  • axios 获取接口数据

  • 组件间数据传递

城市选择页部分
  • 字母表布局

  • better-scroll 的使用

  • 函数节流实现列表性能优化

  • 搜索逻辑实现

  • Vuex 实现数据共享

  • LocalStorage 实现页面数据存储

  • keep-alive 优化路由性能

详情页部分
  • banner 布局

  • 动态路由配置

  • 公用画廊组件拆分

  • 实现 fixed header 渐隐渐显效果

  • 递归组件实现详情列表

  • transition slot 插槽实现 animation 简单动画效果


项目相关项目相关 npm 依赖包
  • fastClick: 用来处理移动端 click 事件 300毫秒延迟

  • stylus: CSS 预处理框架

  • stylus-loader

  • vue-awesome-swiper: 轮播插件

  • axios: 实现 ajax

  • better-scroll: scroll插件

设置样式变量

通过 variable.styl 设置样式变量,抽离出公用样式。以方便维护


首页HomeSwiper 组件使用 vue-awesome-swiper 轮播插件

使用 2.6.7 版本

npm install vue-awesome-swiper@2.6.7 --save

具体参考 vue-awesome-swiper

轮播图当中的 CSS 样式重点
该样式主要是防止网速过慢时页面布局的抖动,其含义是,wrapper 宽度 100%,高度由宽度的 27% 自动撑开。

.wrapper {  overflow: hidden;  width: 100%;  height: 0;  padding-bottom: 27%;  
}

或者写成

.wrapper {  width: 100%;  height: 27vw;
}


HomeIcons 组件iconsList 分页

同样使用 swiper 进行分页,并利用以下方式实现自动构建多页切换的功能

computed: {  //根据数据项目的不同,自动构建icons多页切换功能
  pages () {    const pages = []    this.iconsList.forEach((item, index) => {      const page = Math.floor(index / 8)      if (!pages[page]) {
        pages[page] = []
      }
      pages[page].push(item)
    })    return pages
  }
}
ellipsis()样式封装

将 ellipsis 封装在 mixins.styl 文件中

ellipsis()
  overflow: hidden
  white-space: nowrap
  text-overflow: ellipsis


Recommend / Weekend 组件

设置 min-width 是为了让 ellipsis() 生效

.item-info {  flex: 1;  padding: .1rem;  min-width: 0;
}


index-ajax

使用 axios 进行 ajax 请求

npm install axios --save
.gitignore 设置

添加 staitc/mock,防止被推送到仓库

设置 mock数据 开发环境转发代理

设置 config 文件夹下的 index.js

设置 module.exports 下 dev 的 proxyTable 代理

webpack-dev-server 工具会自动将 /api 替换成 /static/mock

proxyTable: {  '/api': {
    target: 'http://localhost:8080',
    pathRewrite: {      '^/api': '/static/mock'
    }
  }
}


城市页router-link

通过路由实现页面间跳转,在外层添加 router-link。to 后面跟需要跳转的 path 。

    
      
        {{this.city}}        
      
    

然后在 router 文件夹的相应 index.js 路由配置文件中进行 path、name 和 component 的声明,并进行 import from。即完成了路由配置。

import Vue from 'vue'import Router from 'vue-router'import Home from '@/pages/home/Home'import City from '@/pages/city/City'Vue.use(Router)export default new Router({  routes: [{      path: '/',      name: 'Home',      component: Home
    }, {      path: '/city',      name: 'City',      component: City
    }]
})


city-List

修改一像素边框 .border-topbottom 的颜色

.border-topbottom
  &:before
    border-color: #ccc
  &:after
    border-color: #ccc

将页面固定住,后续搭配 better-scroll 插件实现类似于原生 app 的页面上下拖动效果

.list {  overflow: hidden;  position: absolute;  top: 1.58rem;  left: 0;  right: 0;  bottom: 0;
}
better-scroll 插件
npm install better-scroll --save

将 HTML DOM 结构调整成文档中规定的结构,在外层取 wrapper,引用插件之后,在 mounted () 生命周期钩子里面新建一个这个 DOM 引用的实例。

import Bscroll from 'better-scroll'export default {  name: 'CityList',  //生命周期函数 挂载之后执行
  mounted () {    //引用 wrapper DOM
    this.scroll = new Bscroll(this.$refs.wrapper)
  }
}

具体用法,请查看文档  better-scroll

alphabet

是一个显示在右的 a-z 字母缩略指引

city-ajax

按照 index-ajax 一样的方式进行 axios 数据获取

  • 包括 热门城市、字母表排序城市列表、Alphabet 在内的部分都通过 axios 获取数据

在 v-for 循环输出 cities 的时候,需要注意,cities 是一个 Object

props: {  hot: Array,
  cities: Object
}

因此后面用 v-for="(item, key) of cities",和 v-for="innerItem of item" 做循环输出

  {{key}}
  
    {{innerItem.name}}
  


city-components

兄弟组件间联动,这里没有采用 bus。
而是采用 Alphabet.vue(子组件) 传递给 City.vue(父组件) ,然后再通过 City.vue(父组件) 传递给 List.vue(子组件)。

在 Alphabet.vue 的 template 的循环展示中绑定 @click ,并在 methods 中使用 $emit 向外( City.vue 父组件 )发送 change 事件。

methods: {
  handleLetterClick (e) {
    this.$emit('change', e.target.innerText)
  }
}



在 City.vue 的 template 中设置 @change="handleLetterClick" 监听 change 事件。



在 methods 中定义事件 handleLetterClick,传递 letter 参数。

methods: {
  handleLetterClick (letter) {    this.letter = letter
  }
}



并在 data 中定义数据 letter。

data () {  return {
    cities: {},
    hotCities: [],
    letter: ''  // Alphabet 通过 change 事件传递过来的数据
  }
}



并传递给 List.vue。



然后在 List.vue 子组件 props 接收 letter

props: {  hot: Array,  cities: Object,  letter: String  // 接收 letter}



通过侦听器 watch,侦听 letter 的变化。在此之前先用 ref 引用找到相应的 DOM

  {{key}}
  
    {{innerItem.name}}
  



使用 better-scroll 中的 scrollToElement 方法进行点击跳转效果的实现

watch: {
  letter () {    if (this.letter) {      const element = this.$refs[this.letter][0]      this.scroll.scrollToElement(element)
    }
  }
}


alphabet 滑动逻辑

上下滑动时,取字母位置逻辑:

  • 获取 A 字母距离顶部高度

  • 滑动时,取当前位置距离顶部高度

  • 计算差值,得到当前手指位置与 A 字母顶部差值

  • 除以每个字母高度,得出当前字母,触发 change 事件给外部

在 Alphabet.vue 中进行代码的编写



实现效果解析图


函数节流优化

使用函数节流优化 handleTouchMove,提高性能

handleTouchMove (e) {  if (this.touchStatus) {    // 使用函数节流优化性能
    if (this.timer) {
      clearTimeout(this.timer)
    }    this.timer = setTimeout(() => {      const startY = this.startY  
      const touchY = e.touches[0].clientY - 79  
      const index = Math.floor((touchY - startY) / 20)      if (index >= 0 && index < this.letters.length) {        this.$emit('change', this.letters[index])     
      }
    }, 16)
  }
}


city-search 搜索功能逻辑

在 template 的 input 中做 v-model="keyword" 双向绑定。



在 data () 中定义 keyword、list 和 timer。
在侦听器 watch 中侦听 keyword 的改变。
并使用函数节流进行优化。


输入逻辑优化清空 input

由于数据是双向绑定的,所以在 watch 当中添加条件判断,当 !this.keyword 时,清空 list。

if (!this.keyword) {  this.list = []  return}

这样就实现了清空 input 搜索栏时,同时清空下面搜索结果的逻辑。

没有找到匹配

添加 li ,其内容为 没有找到匹配 。同时用 v-show 指令,完成在没有匹配时候(!list.length)。显示该 li 内容,即 没有找到匹配 的功能。

没有找到匹配


search-content 显示与否

同样的使用 v-show 指令,决定是否显示 class="search-content" 这个 div 元素。决定的值为 keyword,这容易理解。

  
        {{item.name}}     没有找到匹配   


给 search-item 添加 better-scroll

给搜索结果页面也添加 better-scroll 使其多结果超出页面显示时,可以进行同样的 better-scroll 插件效果的滑动。

首先引入 better-scroll

import Bscroll from 'better-scroll'



使用 ref 引用 search-content 的元素

  
        {{item.name}}   



同样使用 mounted 生命周期钩子,传递的内容是 this.$refs.search

mounted () {  this.scroll = new Bscroll(this.$refs.search)
}

这样搜索结果页面结果过多超出页面时,也可以拥有 better-scroll 的滑动效果。

使用 Vuex 实现数据共享

需要实现 city 页面的数据传递给 index 首页。由于 City.vue 和 Home.vue 没有公用父级组件,这样就无法通过一个公用的父级组件进行数据的中转。这里我们使用 Vuex 数据层框架来实现。
Vuex官方文档

安装并配置 Vuex
npm install vuex --save

创建 store 文件夹,建立 index.js,state 里放置全局公用数据 city。

import Vue from 'vue'import Vuex from 'vuex'Vue.use(Vuex)export default new Vuex.Store({  state: {    city: '重庆'
  },  mutations: {
    changeCity (state, city) {
      state.city = city
    }
  }
})

在 main.js 的根实例下,将 store 传递进去。在其他子组件中使用 this.$store 进行派发。

import store from './store'  //引入 storenew Vue({  el: '#app',  router: router,  store: store,  //传递进入根实例的 store
  components: { App },  template: ''})

在 List.vue 和 Search.vue 组件中包含城市循环输出项的元素标签上定义 @click="handleCityClick(item.name)"。

并在相应的 methods 中执行 Vuex 的 commit 方法( 数据共享 ) 和 Vue-router 的 push 方法( 页面跳转 )

methods: {
  handleCityClick (city) {
    this.$store.commit('changeCity', city)
    this.$router.push('/')
  }
}
localStorage

使用 localStorage 实现城市保存的功能,在 store 的 index.js 文件中配置  localStorage

export default new Vuex.Store({  state: {    city: localStorage.city || '重庆'
  },  mutations: {
    changeCity (state, city) {
      state.city = city
      localStorage.city = city
    }
  }
})

有可能当用户使用隐身模式或禁用 localStorage,会导致浏览器报错。所以建议使用 try catch 进行优化

let defalutCity = '重庆'try {  if (localStorage.city) {
    defaultCity = localStorage.city
  }
} catch (e) {}export default new Vuex.Store({  state: {    city: defaultCity
  },  mutations: {
    changeCity (state, city) {
      state.city = city      try {
        localStorage.city = city
      } catch (e) {}
    }
  }
})
keep-alive 优化

当查看 network 时候,可以看到从首页到城市选择页切换过程中每次切换都会发送 ajax 请求。所以我们对此进行优化。


在 App.vue 中给 外部添加一个 标签。其含义是路由的内容被加载过一次之后,就把路由的内容放置到内存中,下一次再使用路由的时候,无需重新加载组件、执行钩子函数。只需要从内存中拿出以前的内容显示就可以了。

activated 生命周期钩子

结合 keep-alive 新增的 activated 生命周期钩子,实现每次点击曾经选中过的城市,不发送 ajax,城市选择变化的时候再进行 ajax 请求的优化。


详情页:to 实现动态路由

使用 tag 将 router-link 标签替换成 li,从而不用修改样式就可以达到之前样式的效果。

然后按照下图所示进行动态路由的实现。即点击相应的列表选择选择动态跳转页面。



Banner 布局.banner-info 渐变效果
.banner-info {  background-image: linear-gradient(top, rgba(0, 0, 0, 0), rgba(0, 0, 0, .8))
}


全局画廊组件

新建 common 用来放置全局组件,建立 gallary 的 Gallary.vue 画廊组件,并在 build/webpack.base.conf.js 中进行路径别名指向的设置

resolve: {  extensions: ['.js', '.vue', '.json'],
  alias: {    'vue$': 'vue/dist/vue.esm.js',    '@': resolve('src'),    'styles': resolve('src/assets/styles'),    'common': resolve('src/common'),
  }
}

在 Banner.vue 中引入画廊组件,并在 components 中进行注册

import CommonGallary from 'common/gallary/Gallary'
Gallary.vue

画廊组件内部也使用了 awesome-swiper ,所以同样使用 swiper 标签。swiperOption 设置的几个参数分别是,分页器样式,设置为分数形式的分页;还有解决点击进入画廊之后 swiper 无法进行滑动的 bug 问题。

      swiperOption: {
        pagination: '.swiper-pagination',
        paginationType: 'fraction',  //设置分页器 样式为分式
        observeParents: true,  //swiper 插件监听到自身或父级元素DOM变化时,自动自我刷新。解决 swiper 刷新宽度计算 bug 的问题
        observer: true
      }



使用 props 接收外部传递过来的 imgs 参数。默认为空。并设置相应点击事件,并使用 $emit 传出。

  methods: {    handleGallaryClick () {
      this.$emit('close')
    }
  }



其中还需要注意样式相关的问题。在 Gallary.vue 中的分页器会因为 .swiper 标签自带的 overflow: hidden 而隐藏。使用 >>> 让 .swiper-container 继承 .container 的 overflow 属性即可。


Banner.vue 调用全局画廊

使用 @close="handleGallaryClose" 接收 close 事件,订阅为 handleGallaryClose 事件。并在 banner 上创建 handleBannerClick 事件。实现点击进入画廊,再点击画廊退出的逻辑。


detail 页 header 渐变效果模板内容

逻辑实现

通过 showAbs 、 v-show 和 opacity 完成该效果的实现。
利用 activated 钩子监听 scroll 触发 this.handleScroll。并在 methods 的 handleScroll 中完成渐隐渐现的算法逻辑。(通过 document.documentElement.scrollTop 计算 opacity 属性即可实现该动画效果)


布局相关

.header-fixed 使用 fixed 定位到浏览器最上方。



对全局事件解绑

之前在 activated 中监听 scroll 实际上带来了一些问题。因为如果在一个组件内部模板的某个标签上使用 @click,不会给其他标签和组件带来任何影响。但如果在组件中使用 window 这个全局对象的属性绑定,就会出现诸多 bug。因为相当于这个事件并不是绑定在该组件之中,而是绑定到了全局的 window 对象上。所以对其他的组件也产生了影响。

这个时候使用 deactivated 这个生命周期钩子(页面即将被隐藏或替换成其他页面时)来解除全局事件的绑定。


递归组件实现详情列表

之所以在组件当中需要一个 name 属性,也是为了方便在组件自身调用自身出现递归的时候便于调用。下面可以看到,在下一个 div 标签中做一个 v-if 判断,如果存在 item.children。就把 item.children 当做 list 再传递给自身,进行递归调用。


在 Detaile.vue 中写入一些数据,分为三级。传入递归组件(子组件)中。



由于递归会自己调用自己,样式也会随之进行调整,可以看到以下效果。


detail - ajax

同理 Home 与 City 也 aixos 获取。在父组件进行 ajax 获取,再传递给每个子组件。





每个子组件则通过 props 获取到相应的数据。



Detail 页禁用 keepalive

在 App.vue 的根实例中,在 router-view 之外的 keep-alive 包裹上加上 exclude="Detail" 即可。所以这也是 name 属性的又一个用途。





解决 exclude 带来的 bug

由于在 App.vue 中使用了 keep-alive exclude="Detail",那么在 Detail 下的 Header.vue 中就不会执行 activated 钩子, 但是会执行 created 生命周期钩子。此时会出现Detail 页 header 头部渐隐渐现的效果不显现了。所以将监听 scroll 的事件写入到 created 中。修复此 bug。



解决滚动行为 bug

在 router 下面的 index.js 下添加。解决滚动行为的 bug。使每次做路由切换时,让新显示的页面回到最顶部。



animation 简单动画效果

在 common 公用组件当中新建 fade 文件夹,并创建 FadeAnimation.vue。用来实现简单的动画效果。



并在 Banner.vue 组件模板中的 common-gallary 外部加上 fade-animation 标签,相当于内部使用了插槽。从而实现 FadeAnimation.vue 中的动画效果。


调试相关接口联调

Vue 项目的联调,不需要使用类似于 fiddler 、charles 的抓包代理工具。只需要使用 proxyTable 配置项把需要请求的后端服务器地址配置好即可。

在 config 的 index.js 中,设置 dev 当中的 proxyTable。target 指向后端 api 的地址。并删除 static 下的 mock 文件夹。(然后 npm run dev 重启服务器)



线上调试

在 package.json 中配置 scripts 下的 dev 添加 --host 0.0.0.0 即可在同局域网下通过 IP 地址访问项目页面。



修改完毕之后需 npm run dev 重启服务器,然后通过 IP 地址 + 端口就可以访问项目页面,即可以通过局域网移动端手机或PAD进行真机调试了。


真机调试 bug 修复

在城市选择页面进行最右 Alphabet 字母表选择的时候,拉动字母表会出现整个页面也跟着上下拖动的 bug。
修复这个 bug 的方法是在 Alphabet.vue 中找到 @touchstart 事件,并在这个事件之后加上 .prevent 事件修饰符。阻止 @touchstart 的默认行为,就不会出现页面跟着上下拖动的效果了。





低版本浏览器白屏

可能情况:
手机浏览器不支持 promise
解决方法:安装 babel-polyfill 包。当 babel-polyfill 判断浏览器不支持 promise ,会自动向里面添加 es6 的新特性支持。

 npm install babel-polyfill --save

安装 npm 包完毕后,重启服务器。然后在 main.js 中引入。




其他优化

异步组件拆分,按需加载。

访问那一页,就加载那一页的逻辑文件。

当 app.js 文件不大的时候,不建议拆分异步加载。因为多次的 http 请求代价可能更大。


项目上线
  • 命令行打开目录,运行命令

npm run build

出现 Build complete. 即编译完成。



编译完成生成的目录的代码就是最终上线的代码。


将这些文件内容放置在后端目录,就完成了项目最基本的上线。

github 线上打包版预览

在进行 github 上传预览打包版代码的时候,由于 github 每个项目都自带一个 path 路径,导致 url 必须带一个 path 路径才可以正常浏览。所以在 npm run build 之前先在 config 下的 index.js 的 build 当中进行 assetsPublicPath 的设置。设置为 github 上预览版的项目名称即可。



作者:evenyao
链接:https://www.jianshu.com/p/9b69ce6d9e9e


转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/240654.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号