Vue项目开发
环境:
1、安装 node.js、git等
2、创建仓库
码云网站或者GitHub创建一个仓库
SSH公钥
git bush中: 将单引号部分替换为邮箱(最好为码云注册邮箱)
ssh-keygen -t rsa -C "[email protected]"
运行下面代码:出现公钥
cat ~/.ssh/id_rsa.pub
复制公钥到码云
克隆项目:
复制项目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 travel
,npm 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:
项目码云:
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"></div>
</div>
<div class="header-input">
<span class="iconfont"></span>
输入城市/景点/游玩主题
</div>
<router-link to='/city'>
<div class="header-right">
{{this.city}}
<span class="iconfont arrow"></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 应用的核心就是 store(仓库)。“store”基本上就是一个容器,它包含着你的应用中大部分的状态 (state)。Vuex 和单纯的全局对象有以下两点不同:
- Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
- 你不能直接改变 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()
}
}