Vue+webpack+axios项目实战

Vue项目开发

环境:

1、安装 node.js、git等

2、创建仓库

码云网站或者GitHub创建一个仓库

在这里插入图片描述

SSH公钥

git bush中: 将单引号部分替换为邮箱(最好为码云注册邮箱)

ssh-keygen -t rsa -C "[email protected]"

运行下面代码:出现公钥

cat ~/.ssh/id_rsa.pub

SSH生成

复制公钥到码云

在这里插入图片描述

克隆项目:

复制项目SSH

在这里插入图片描述

进入你想把项目要放入文件目录,打开git bush,运行git clone [email protected]:xxxx.git (git clone加上覆制的SSH) ,即目录位置就多出来了一个travel-test(码云仓库名)文件夹

在这里插入图片描述

3、初始化本地仓库

运行 git install --global vue-cli 安装

打开项目所在目录(注意一定要在项目所在文件夹而不是项目文件夹,在travel-test的外层目录不是travel目录下)

一下windows环境最好在cmd运行

运行 vue init webpack travel-test travel-test为项目文件名

根据提示(可在git bush或者cmd)

cd travelnpm run dev或者npm run start

项目已在8080端口跑起来

4、本地和线上项目同步

查看项目发现本地项目比码云线上项目多出许多文件夹,同步线上线下:

关掉服务器,确保git bush在项目目录下(travel-test下)

1、运行 git status 查看状态

2、运行 git add . 将所有文件增加到本地缓冲区

3、运行 git commit -m 'project init' 提交项目

4、运行 git push 将提交代码推到线上仓库

开发开始

文件引入

项目index.hteml下

<meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">

在src下新建assets目录,再在其目录下新建styles目录,将reset.css和normalize.css放入styles文件夹下,打开build目录下的webpack.base.conf.js里,配置style目录简写方式使(src/assets/styles)可简写为styles目录

加入'styles': resolve('src/assets/styles')

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

入口文件mian.js导入reset.css或者normalize.css,重置浏览器默认样式

import 'styles/reset.css'

入口文件mian.js导入border.css解决移动端1px边框问题

import 'styles/border.css'

安装fastclick ,解决在移动端在某些设备和浏览器上click事件会延迟300ms执行,体验不太好

npm install fastclick --save

入口文件mian.js导入

import fastClick from 'fastclick'

fastClick.attach(document.body)

导入iconfont

import 'styles/iconfont.css'

安装stylus,stylus-loader

npm install stylus --save
npm install stylus-loader --save

在这里插入图片描述

初始化完成提交项目

1、运行 git status 查看状态

2、运行 git add . 将所有文件增加到本地缓冲区

3、运行 git commit -m 'project init' 提交项目

4、运行 git push 将提交代码推送到线上仓库

开发开始

参考app:

http://piao.qunar.com/touch/

项目码云:

https://gitee.com/GueYue/events

home页面编写

1、header部分
开始

home页面路由配置

在这里插入图片描述
src下新建pages文件夹,新建home文件夹,home文件夹下,新建Home.vue,并写下如下代码

<template>
  <div>
	Home
  </div>
</template>
<script>
export default {
  name: 'Home'
</script>

<style>
</style>

router下的index.js中

导入模板

import Home from '@/pages/home/Home'
routes: [
    {
      path: '/',
      name: 'Home',
      component: Home
    }]
内容

第一个功能的开发可以就在master分支开发,不用修改git分支

在home文件夹下新建components文件夹,components下新建Header.vue 文件,写下

<template>
  <div class="header">
    <div class="header-left">
      <div class="iconfont back-icon">&#xe624;</div>
    </div>
    <div class="header-input">
      <span class="iconfont">&#xe632;</span>
      输入城市/景点/游玩主题
    </div>
    <router-link to='/city'>
      <div class="header-right">
        {{this.city}}
        <span class="iconfont arrow">&#xe6aa;</span>
      </div>
    </router-link>
  </div>
</template>

<script>
export default {
  name: 'HomeHeader',
}
</script>

<style lang="stylus" scoped>
  @import '~styles/varibles.styl'
  .header
    line-height: $headerHeight
    display: flex
    background-color: $bgColor
    /* 具体样式略 */
	......
</style>

修改Home.vue使用Header组件

<template>
  <div>
    <home-header></home-header>
  </div>
</template>
<script>
import HomeHeader from './components/Header'
export default {
  name: 'Home',
  components: {
    HomeHeader
  }
</script>

<style>
</style>

2、轮播图
开始

在码云上创建分支home-swiper

项目git bush里运行如下代码

1、git pull 线上分支同步到线下

2、git checkout home-swiper 切换到home-siwper分支开发

3、npm run start 或者 npm run dev 启动项目

安装vue-awesome-swiper 2.6.7版本

[email protected] --save

mian.js导入

import VueAwesomeSwiper from 'vue-awesome-swiper'
import 'swiper/dist/css/swiper.css'

Vue.use(VueAwesomeSwiper)
内容

home目录下的components下新建Swier.vue 多余代码并为删除

<template>
  <div class="swiper">
    <swiper :options="swiperOption" v-if="showSiwper">
      <swiper-slide v-for="item of list" :key="item.id">
        <img class="swiper-img" :src="item.imgUrl" />
      </swiper-slide>
      <div class="swiper-pagination" slot="pagination"></div>
    </swiper>
  </div>
</template>
<script>
export default {
  name: 'HomeSwiper',
  props: {
    list: Array
  },
  data () {
    return {
      swiperOption: {
        pagination: '.swiper-pagination',
        loop: true
      }
    }
  },
  computed: {
    showSiwper () {
      return this.list.length
    }
  }
}
</script>
<style lang="stylus" scoped>
.swiper >>> .swiper-pagination-bullet-active
  background: #fff
.swiper
  overflow: hidden
  width: 100%
  height: 0
  padding-bottom: 26.66%
  .swiper-img
    width: 100%
</style>

Home.vue

<template>
  <div>
    <home-header></home-header>
    <home-swiper :list="swiperList"></home-swiper>
    <home-icons :list="iconList"></home-icons>
    <home-recommend :list="recommendList"></home-recommend>
    <home-weekend :list="weekendList"></home-weekend>
  </div>
</template>
<script>
import HomeHeader from './components/Header'
import HomeSwiper from './components/Swiper'
import HomeIcons from './components/Icons'
import HomeRecommend from './components/Recommend'
import HomeWeekend from './components/Weekend'
import axios from 'axios'
import { mapState } from 'vuex'

export default {
  name: 'Home',
  components: {
    HomeHeader,
    HomeSwiper,
    HomeIcons,
    HomeRecommend,
    HomeWeekend
  },
  data () {
    return {
      swiperList: [],
      iconList: [],
      recommendList: [],
      weekendList: [],
      lastCity: ''
    }
  },
  computed: {
    ...mapState(['city'])
  },
  methods: {
    getHomeInfo () {
      axios.get('api/index.json?city=' + this.city)
        .then(this.getHomeInfoSucc)
    },
    getHomeInfoSucc (res) {
      res = res.data
      if (res.ret && res.data) {
        const data = res.data
        this.swiperList = data.swiperList
        this.iconList = data.iconList
        this.recommendList = data.recommendList
        this.weekendList = data.weekendList
      }
    }
  },
  mounted () {
    this.lastCity = this.city
    this.getHomeInfo()
  },
  activated () {
    if (this.lastCity !== this.city) {
      this.lastCity = this.city
      this.getHomeInfo()
    }
  }
}
</script>

<style>
</style>

结束(提交和合并分支)

提交代码

1、运行 git status 查看状态

2、运行 git add . 将所有文件增加到本地缓冲区

3、运行 git commit -m 'XXXXX' 提交项目

4、运行 git push 将提交代码推送到线上仓库

合并分支

1、运行 git checkout master 切换到主分支

2、运行 git merge home-swiper 合并wiper分支到主分支

3、运行 git push 将提交代码推送到线上仓库

Ajax传值

安装axios

npm install axios --save

更改你配置项

在config目录下的index.js找到dev(开发环境)配置proxyTbale

当我们请求api目录的时候把请求转发到http://localhost:8080 上,并把以api开头的请求,则把路径替换到本地的/static/mock文件夹下

module.exports = {
  dev: {

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

请求数据

接收到数据后通过组件传值把数据分发到各个模块

请求的是static/mock/index.json

<template>
  <div>
    <home-header></home-header>
    <home-swiper :list="swiperList"></home-swiper>
    <home-icons :list="iconList"></home-icons>
    <home-recommend :list="recommendList"></home-recommend>
    <home-weekend :list="weekendList"></home-weekend>
  </div>
</template>
<script>
import HomeHeader from './components/Header'
import HomeSwiper from './components/Swiper'
import HomeIcons from './components/Icons'
import HomeRecommend from './components/Recommend'
import HomeWeekend from './components/Weekend'
import axios from 'axios'
import { mapState } from 'vuex'

export default {
  name: 'Home',
  components: {
    HomeHeader,
    HomeSwiper,
    HomeIcons,
    HomeRecommend,
    HomeWeekend
  },
  data () {
    return {
      swiperList: [],
      iconList: [],
      recommendList: [],
      weekendList: [],
      lastCity: ''
    }
  },
  computed: {
    ...mapState(['city'])
  },
  methods: {
    getHomeInfo () {
      axios.get('api/index.json?city=' + this.city)
        .then(this.getHomeInfoSucc)
    },
    getHomeInfoSucc (res) {
      res = res.data
      if (res.ret && res.data) {
        const data = res.data
        this.swiperList = data.swiperList
        this.iconList = data.iconList
        this.recommendList = data.recommendList
        this.weekendList = data.weekendList
      }
    }
  },
  mounted () {
    this.lastCity = this.city
    this.getHomeInfo()
  },
  activated () {
    if (this.lastCity !== this.city) {
      this.lastCity = this.city
      this.getHomeInfo()
    }
  }
}
</script>

<style>
</style>

better-scroll插件的使用

GitHub地址:

https://github.com/ustbhuangyi/better-scroll

安装

npm install better-scroll --save

使用

<template>
  <div class="list" ref="wrapper">
    <div>
      <div class="area">
        <div class="title">当前城市</div>
        <div class="button-list">
          <div class="button-wrapper">
            <div class="button">{{this.currentCity}}</div>
          </div>
        </div>
      </div>
      <div class="area">
        <div class="title">热门城市</div>
        <div class="button-list">
          <div class="button-wrapper" v-for="item of hot" :key="item.id" @click="handleCityClick(item.name)">
            <div class="button">{{item.name}}</div>
          </div>
        </div>
      </div>
      <div class="area" v-for="(item, key) of cities" :key="key" :ref="key">
        <div class="title">{{key}}</div>
        <div class="item-list">
          <div class="item border-bottom" v-for="innerItem of item" :key="innerItem.id" @click="handleCityClick(innerItem.name)">{{innerItem.name}}</div>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
import Bscroll from 'better-scroll'
import { mapState, mapMutations } from 'vuex'
export default {
  name: 'CityList',
  props: {
    hot: Array,
    cities: Object,
    letter: String
  },
  mounted () {
    this.scroll = new Bscroll(this.$refs.wrapper)
  },
  computed: {
    ...mapState({
      currentCity: 'city'
    })
  },
  watch: {
    letter () {
      if (this.letter) {
        const element = this.$refs[this.letter][0]
        this.scroll.scrollToElement(element)
      }
    }
  },
  methods: {
    handleCityClick (city) {
      // this.$store.commit('changeCity', city)
      this.changeCity(city)
      this.$router.push('/')
    },
    ...mapMutations(['changeCity'])
  }
}
</script>

<style lang="stylus" scoped>
@import '~styles/varibles.styl'
@import '~styles/mixins.styl'
.list
  position: absolute
  .title
    title-common()
    /* 省略css*/
  ......
</style>

灵活的timer实现数据节流提高性能

<template>
  <div>
    <div class="search">
      <input type="text" class="search-input" placeholder="输入城市名或拼音" v-model="keyword">
    </div>
    <div class="search-content" ref="search" v-show="keyword">
      <ul>
        <li class="search-item" v-for="item of list" :key="item.id" @click="handleCityClick(item.name)">{{item.name}}</li>
        <li class="search-item" v-show="hasNoData">没有找到匹配数据</li>
      </ul>
    </div>
  </div>
</template>

<script>
import Bscroll from 'better-scroll'
import { mapMutations } from 'vuex'
export default {
  name: 'CitySearch',
  props: {
    cities: Object
  },
  data () {
    return {
      keyword: '',
      list: [],
      timer: null
    }
  },
  computed: {
    hasNoData () {
      return !this.list.length
    }
  },
  watch: {
    keyword () {
      if (this.timer) {
        clearTimeout(this.timer)
      }
      if (!this.keyword) {
        this.list = []
        return
      }
      this.timer = setTimeout(() => {
        const result = []
        for (let i in this.cities) {
          this.cities[i].forEach((value) => {
            if (value.spell.indexOf(this.keyword) > -1 || value.name.indexOf(this.keyword) > -1) {
              result.push(value)
            }
          })
        }
        this.list = result
      }, 100)
    }
  },
  mounted () {
    this.scroll = new Bscroll(this.$refs.search)
  },
  methods: {
    handleCityClick (city) {
      // this.$store.commit('changeCity', city)
      this.changeCity(city)
      this.$router.push('/')
    },
    ...mapMutations(['changeCity'])
  }
}
</script>

<style lang="stylus" scoped>
  ......
</style>

Vuex实现数据渲染

vuex

每一个 Vuex 应用的核心就是 store(仓库)。“store”基本上就是一个容器,它包含着你的应用中大部分的状态 (state)。Vuex 和单纯的全局对象有以下两点不同:

  1. Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
  2. 你不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。

简单的使用:

src下创建store文件夹在store文件夹下创建index.js

导出的是通过Vuex创建的store仓库,并设置仓库中state里设置数据city默认值为北京

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)
export default new Vuex.Store({
  state:{
  	city: '北京'
  }
})

main.js中

import store from './store'

new Vue({
  el: '#app',
  router,
  store,
  components: { App },
  template: '<App/>'
})

在这里插入图片描述

因为在创建根实例的时候把store传入了

{{this.$store.state.city}}

修改city

<div class="item border-bottom" @click="handleCityClick(innerItem.name)">{{innerItem.name}}</div>
methods: {
    handleCityClick (city) {
    	this.$store.dispatch('change', city)
    },
  }

修改store下index.js

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)
export default new Vuex.Store({
  state: {
  	city: '北京'
  },
  actions: {
  	changeCity (ctx,city) {
  		ctx.commit('changeCity', city)
  	}
  },
  mutations: {
  	changeCity (state, city){
  		state.city = city
  	} 
  }
})

无异步操作简化

若无异步操作,则Actions并无必要,组件可以直接调用mutations

即上述代码简单改为:

methods: {
    handleCityClick (city) {
    	this.$store.dispatch('change', 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
  	} 
  }
})

增加缓存操作(类似cookie)

使用localStorage最好添加try…catch 因为用户可能会关闭本地存储或者为隐身模式可能会导致抛出异常

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

改:

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

let defaultCity = '上海'
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) {}
  }
  }
})

拆分

当项目有些大时,index.js太杂乱,不易于维护,所以需要拆分

在store中创建state.js文件

let defaultCity = '上海'
try {
  if (localStorage.city) {
    defaultCity = localStorage.city
  }
} catch (e) {}

export default {
  city: defaultCity
}

在store中创建mutations.js文件

export default {
  changeCity (state, city) {
    state.city = city
    try {
      localStorage.city = city
    } catch (e) {}
  }
}

store中index.js修改

import Vue from 'vue'
import Vuex from 'vuex'
import state from './state'
import mutations from './mutations'

Vue.use(Vuex)

export default new Vuex.Store({
  state,
  mutations
})

store使用代码的优化

import { mapState } from 'vuex'
  computed: {
    ...mapState(['city'])
  },

import { mapState, mapMutations } from 'vuex'
<div class="button">{{this.currentCity}}</div>

将Vuex中公用数据映射到这个组件的计算属性里,映射过来的名字叫做currentCity

computed: {
    ...mapState({
      currentCity: 'city'
    })
  },

有一个mutation叫changeCity,然后我把这个mutation映射到这个组件的一个叫做changeCity的方法里

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

最后代码

仓库代码

store文件夹下index.js中

import Vue from 'vue'
import Vuex from 'vuex'
import state from './state'
import mutations from './mutations'

Vue.use(Vuex)

export default new Vuex.Store({
  state,
  mutations
})

store文件夹下state.js中

let defaultCity = '上海'
try {
  if (localStorage.city) {
    defaultCity = localStorage.city
  }
} catch (e) {}

export default {
  city: defaultCity
}

store文件夹下mutations.js中

export default {
  changeCity (state, city) {
    state.city = city
    try {
      localStorage.city = city
    } catch (e) {}
  }
}
使用代码
<div class="button">{{this.currentCity}}</div>
computed: {
    ...mapState({
      currentCity: 'city'
    })
  },
methods: {
    handleCityClick (city) {
      // this.$store.commit('changeCity', city)
      this.changeCity(city)
      this.$router.push('/')
    },
    ...mapMutations(['changeCity'])
  }

总结

若用到非常复杂的应用系统,比如后台管理等、经常会有很多共用数据在Vuex中进行存储那么我们需要用到模块,详细学习到Vuex下的Module文档

keepalive性能优化

加keepalive

  methods: {
    getCityInfo () {
      axios.get('api/city.json')
        .then(this.handleGetCityInfoSucc)
    },
    handleGetCityInfoSucc (res) {
      res = res.data
      if (res.ret && res.data) {
        const data = res.data
        this.cities = data.cities
        this.hotCities = data.hotCities
      }
    },
    handleLetterChange (letter) {
      this.letter = letter
    }
  },
  mounted () {
    this.getCityInfo()
  }

由上述代码可知,当每次加载页面即这个组建时,就会发送一次请求,若频繁的打开而不更改页面,那么性能将会很低,所以我么可以在程序的入口组件里使用keepalive

他的意思是当路由的内容被加载过一次之后,他就将路由的内容放到内存之中,下一次在进这个路由的时候不需要你去重新渲染这个组件,重新执行钩子函数,只需要从内存里把以前的内容拿出来就可

<keep-alive>
      <router-view></router-view>
</keep-alive>

在这里插入图片描述

解决需要重新加载页面内容时的问题

有时候我们在这个页面点击、做出其他改变影响页面内容时需要重新请求数据重新渲染内容,那么我们就需要使用到加上keepalive后组件里会多出两个个生命周期函数activated,unactivated,他在页面重新渲染的时候会执行,我们只需要在activated里判断数据是否需要重新请求,若需要则重新请求数据即可

 activated () {
    if (this.lastCity !== this.city) {
      this.lastCity = this.city
      this.getHomeInfo()
    }
 }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章