这是一篇写给自己的笔记,如果不是和我一样从负开始学习Vue的同学,还是不要看了。。。
才毕业,之前在学校一直在学习移动端开发的知识(现在也快忘了。。)。
如今工作,需要学一些移动端网页的制作。从零学习前端,一个月的时间,每天下班回来学一点。先对这段时间的学习做个回顾。(主要是怕自己忘。。)
以前和大神合作过Asp.Net的网页,基本划水,自己做过html的简单页面,仅仅限于了解了html的尖括号语法。。
硬着头皮接触Vue发现知识空缺太大,不谈H5的语法,js和css这两块注定是需要投入大力气去了解的。
真的是从零开始,甚至会是从负开始的学习笔记了。。。
目录
5.1.4.Vue中的Js动画与Velocity.js的结合:
言归正传:
从零学习Vue,一开始当然是学习基本的Vue语法。https://cn.vuejs.org/v2/guide/installation.html
我差不多是看完条件渲染以后就开始跟着教程做一个小网页了。
对于最开始的部分,推荐下载https://vuejs.org/js/vue.js 有了这个js文件后,放在与html文件同一目录,就可以直接script引入
<script src="./vue.js"></script>
然后就可以来一个Hello World!了。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Hello World</title>
<script src="./vue.js"></script>
</head>
<body>
<div id="app">
<p>{{ message }}</p>
</div>
<script>
var vm = new Vue({
el: '#app',
data: {
message: 'Hello World!'
}
})
</script>
</body>
</html>
Vue中一个很有意思的地方,可以创建很多对象,为不同的对象赋值,需要使用的时候可以直接调用对象,最最重要的一点,Vue是我们的数据和视图绑定,一旦数据变化,页面上会实时改变的。响应式系统(当然这局限于写在data中的元素,看过Vue官网教程一定明白(数据是否是响应式的?))
这种绑定的关系像是MVVM模型,Vue很大程度上受其启发。因此官网教程上创建Vue对象的时候,起名都是vm。
每个 Vue 应用都是通过用 Vue
函数创建一个新的Vue 实例开始的。
在上面的代码中,vm是我们新创建的对象,el属性是绑定需要被渲染的元素,例如这里的el选择了app这个元素,在上面的div中,就写明了这个div的id是app这就建立起了页面元素与Vue实例之间的关系。而data则是这个Vue实例中存放的数据。
之后在绑定好的div中使用Vue的插值表达式({{在这里选择需要被输出的属性名}})就可以在页面上输出Hello World了。
建议在这种环境下学习官网的基础教学,学会基本的Vue语法。(才上手不推荐使用Vue-cli)。例如
- 生命周期钩子(在合适的时候调用的一系列方法)
- Vue的模板语法
- Vue中的指令v-xxx(常用的bind与on的用法与简写)
按照教程的顺序来吧,基础的方法没有什么捷径,都打一遍留个印象,之后做个网页,遇到需要实现的功能能想起来之前练过的某个方法就行了。
为什么说一开始不推荐Vue-cli,我一开始找到很多教程都是Vue环境的搭建,环境搭好直接告诉我使用Vue-cli搭个框架,过程倒也不难,都有提示,但是搭完直接教程结束。对比一下路径大概就明白差别了:
使用script引入Vue.js学习基本语法,路径如下:
其实只有一个Vue.js文件和一个html文件就够了,我只是把知识点分开写了。
然而。。使用Vue-cli搭建项目框架如下:
第一次看到真的一脸懵逼,最初还是推荐直接引入Vue.js文件来学习。
关于目录结构可以参考这篇文章。https://blog.csdn.net/weixin_36185028/article/details/78637528
我的目录比上文多两个文件夹,
dist是项目写完后使用npm run build命令将整个项目打包后生成的文件路径。
static 是存放静态文件的路径,例如图片,json数据等
之后总结一下这个月做的小网页涉及到的知识点。做的网页是根据网上的教程,对着去哪儿网的界面仿照了一个粗劣版。。
一、界面:
主界面:
城市选择页面:
推荐栏目详情页面:
二、制作步骤:
2.1.首页:
2.1.1.首页Header:
最初第一步完成的是首页的制作,首先在主页面index.html中加入
<meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum=1.0,user-scalable=no">
目的是针对移动端不同分辨路的设备做适配,并且限制了用户缩放。Vue-cli框架搭好后默认有这个属性,只需要加入后面两句就好。
由于Vue是组件化开发,因此如果你想,页面上的每一个元素都可以单独拿出来做成一个组件,在任何你需要的时候引入即可。
当然也没必要那么麻烦,按照区域分成不同的模块即可,从上到下,一开始需要做首页的header部分,如下图:
中间的搜索栏是假的,左侧的箭头也是假的。具有功能的只有右侧的城市选择按钮。
这里涉及到的知识点有两个:
一个是页面上添加图标的新方法,项目中采用了阿里的图标库iconfont,具体使用方法放在后面写。
第二个是页面的跳转,vue中使用router-link做跳转:
<router-link to="/foo">Go to Foo</router-link>
但是如果这样使用的话,这个控件会被渲染成一个a标签,功能正常但是显示效果会发生变化。
这时就可以为这个router-link加入tag属性
<router-link tag="li" to="/foo">
<a>/foo</a>
</router-link>
这个代码中将这个router-link声明为一个li标签(当然也可以改成别的标签),就不会发生变色了。
或者直接使用css修改颜色#fff变白。
我的每一个组件都是分开写的,如果代码需要维护,修改起来也方便。(自己的代码练习,几乎不用维护。。。)
目录就像这样:
写好了以后只需要在Home.vue这个组件中调用相应的组件就可以了,调用方法如下:
1.添加引用
import HomeHeader from './components/Header'
2.声明变量,由于我需要定义一个叫做HomeHeader的组件,同时我也引用了一个叫做HomeHeader的组件,名字相同可以简写。写一个就好了。
export default {
name: 'Home',
components: {
HomeHeader
}
}
3.在template中插入定义好的组件,完成使用:(由于引入的组件不止一个,因此外侧需要包裹一个div,Vue中要求template模板中只能包含一个组件,所以把引入的所有组件放在一个div中即可)
<template>
<div>
<home-header></home-header>
</div>
</template>
2.1.2.首页轮播图:
请忽略这副优质的轮播图。。。
这里使用了一个非常好用的第三方插件,叫做vue-awesome-swiper
直接npm install vue-awosome-swiper --save安装即可,加上--save的目的在于
不论在开发环境中还是打包生成线上版本的代码,都需要使用这个库。
因此使用--save,将其存入项目中package.json文件中的dependencies里面。
之后需要在main.js中引入这个包以及相应的css文件,这样引入一次,全局都可以使用了。
import VueAwesomeSwiper from 'vue-awesome-swiper'
import 'swiper/dist/css/swiper.css'
Vue.use(VueAwesomeSwiper)
使用的方法如下:
<swiper :options="swiperOption" v-if="showSwiper">
<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>
这里用到了v-for列表渲染的知识,以及v-if条件渲染的知识。很简单,看一下vue.js官网教程就明白了。
开始的时候home组件给swiper组件传入了一个轮播图数组,存放图片url,这里检测图片是否存在使用了一个计算属性
showSwiper,在其中判断了传入的list长度,如果为0,则不显示这个轮播图控件
之后循环list,取出每一张图片创建swiper-slide 就可以实现轮播图效果了。
swiperOption用来控制这个轮播器的很多属性,项目里我用到了两个:
data () {
return {
swiperOption: {
// 通过此属性加入图片轮播次序点
pagination: '.swiper-pagination',
// 支持循环轮播
loop: true
}
}
}
轮播图下面的蓝色圆点就是pagination实现的效果,默认圆点,当然不止圆点这一种显示效果,还有很多种。例如另一处轮播图我用到了
pagination: '.swiper-pagination',
paginationType: 'fraction'
这个的显示效果是1 / 2 、 2 / 2 这样的看你的需要,可以改变不同的效果。
2.1.3.项目推荐控件:
这两个组件一样,都是单纯的css设计的知识,这次用到的关于css布局的一些知识点,我之后写在下面。
2.2.城市选择页面:
当前这个页面第一次进入有可能会出现点击没有反应的情况,刷新一次就好了,还没有查清原因。
2.2.1.添加路由设置:
在根目录下创建router文件夹,其中创建index.js文件设置路径,目的是之后可以跳转。
import Vue from 'vue'
import Router from 'vue-router'
import Home from '@/pages/home/Home'
import City from '@/pages/city/City'
import Detail from '@/pages/detail/Detail'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/',
name: 'Home',
component: Home
}, {
path: '/city',
name: 'City',
component: City
}, {
// 动态路由
path: '/detail/:id',
name: 'Detail',
component: Detail
}
],
// 路由切换时新进入的页面回到顶部
scrollBehavior (to, from, savedPosition) {
return { x: 0, y: 0 }
}
})
最后的scrollBehavior方法是我项目最后才加上的,因为会出现有时主页拉的位置比较低,再跳转到其它页面就没有默认在页面顶部,加入这个方法,每次跳转的时候,都会默认显示页面顶部的内容。相当于给了一个x,y的默认坐标,都是0.
之后需要在首页根目录下的main.js中添加这个index.js的引用。
import router from './router'
2.2.2.Header部分:
这次的左箭头和搜索栏都是真的了,有实际功能,可以搜索、也可以返回,返回依旧是router-link不再赘述。
输入汉字和拼音都可以找到对应城市,因为预设了一个关于城市的json文件,城市选择默认列表也是从json文件中导入的,对应城市名拼音拼写等信息。
只需要侦听输入的数据,每次检测是否在预设数据中有重复的项目显示即可。
代码如下,需要注意加入了一个小方法,设置了一个timer,如果输入过快,可以有个小延时,防止快速调用搜索方法。数据节流。
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) => {
// 在每一个数据中,包含城市名:name 还有城市拼音spell
// 在其中搜索与用户输入keyword的对应项添加进result中
if (value.spell.indexOf(this.keyword) > -1 || value.name.indexOf(this.keyword) > -1) {
result.push(value)
}
})
}
this.list = result
}, 100)
}
}
2.2.3.城市列表:
当前城市没有定位。只是根据用户上次选择的城市改变的。
热门城市都是预设数据,v-for循环上去的。
这个页面主要需要提到的是加入了better-scroll插件,添加了一个滚动效果,可以实现果冻式的回弹效果。
使用方法:
第一步:还是npm install better-scroll --save
第二步:在需要的页面引用better-scroll
import Bscroll from 'better-scroll'
第三步:在挂载的时候创建一个对象,并且指定一个需要滑动的页面对象
mounted () {
this.scroll = new Bscroll(this.$refs.wrapper)
}
// 页面上元素添加属性ref
<div class="list" ref="wrapper"></div>
2.2.4.快速搜索条:
说起来有点复杂,直接看源代码吧。注释写了很多。
最值得一提的,现在很多浏览器有手势设定,因此如果在搜索条上滑动,有可能会触发手势,因此在@touchstart属性设置的时候,加上.prevent,就可以解决拖动搜索条,页面也跟着一同滑动的问题了。
.prevent 叫做事件修饰符,可以阻止touchstart的默认行为 有效防止页面发生移动
<template>
<ul class="list">
<li
class="item"
v-for="item of letters"
:key="item"
:ref="item"
@touchstart.prevent="handleTouchStart"
@touchmove="handleTouchMove"
@touchend="handleTouchEnd"
@click="handleLetterClick"
>
{{item}}
</li>
</ul>
</template>
<script>
export default {
// .prevent 叫做事件修饰符,可以阻止touchstart的默认行为 有效防止页面发生移动
name: 'CityAlphabet',
props: {
cities: Object
},
computed: {
letters () {
const letters = []
for (let i in this.cities) {
letters.push(i)
}
return letters
}
},
data () {
return {
touchStatus: false,
startY: 0,
timer: null
}
},
// 当页面被更新同时页面完成渲染时会执行此生命周期钩子
// 一开始渲染页面时,用的数据cities是空值
// 当ajax数据传入,页面重新渲染,此时获取A标签高度才是正确值
updated () {
// 获取到A元素的高度
this.startY = this.$refs['A'][0].offsetTop
},
methods: {
handleLetterClick (e) {
// console.log(e.target.innerText)
// 通过上一行可以看到每次点击获取到了被点击的字母,因此这时需要把被点击的数据传给list组件
// 兄弟组件传值需要通过bus总线传值
this.$emit('change', e.target.innerText)
},
// 滑动事件,完成
handleTouchStart () {
this.touchStatus = true
},
handleTouchMove (e) {
if (this.touchStatus) {
// 判断timer是否存在,存在就清空
if (this.timer) {
clearTimeout(this.timer)
}
// 不存在就设置一个时间间隔,16毫秒
// 否则页面滚动太快,增多handleTouchMove执行频率
// 影响性能,因此需要函数节流
this.timer = setTimeout(() => {
// 获取触摸位置的高度
// 测量页面上方header区域以及serach区域共长79像素,因此把获取到的触摸高度减去79就是距白边的高度了
const touchY = e.touches[0].clientY - 79
// 用距顶部的距离减去A的高度,除以每个元素的高度20,再取整Math.floor 就可以知道目前手指的触摸位置了
const index = Math.floor((touchY - this.startY) / 20)
if (index >= 0 && index <= this.letters.length) {
this.$emit('change', this.letters[index])
}
}, 16)
}
},
handleTouchEnd () {
this.touchStatus = false
}
}
}
</script>
<style lang="stylus" scoped>
@import '~styles/varibles.styl'
.list
// 这三行控制此元素在垂直方向做居中
display: flex
flex-direction: column
justify-content: center
position: absolute
top: 1.58rem
right: 0
bottom: 0
width: .4rem
.item
line-height: .4rem
text-align: center
color: $bgColor
</style>
2.3.项目详情页面:
2.3.1.递归调用组件:
这个页面没有什么特别的地方,新知识点只有递归调用组件
<div>
<div
class="item"
v-for="(item, index) of list"
:key="index"
>
<div class="item-title border-bottom">
<span class="item-title-icon"></span>
{{item.title}}
</div>
<!-- vue递归组件 -->
<div v-if="item.children" class="item-children">
<detail-list :list="item.children"></detail-list>
</div>
</div>
</div>
判断item节点是否含有children节点,如果有则会按照item-children这个样式再次调用detail-list这个组件,传入的数据就是item.children。然而,当前这个Vue组件就是detail-list,这样就达到了递归调用的效果。
这里预设的json数据是这样的,放上来便于理解层级关系
"categoryList": [{
"title": "成人票",
"children": [{
"title": "成人三馆联票",
"children": [{
"title": "成人三馆联票 - 某一连锁店销售"
}]
},{
"title": "成人五馆联票"
}]
}, {
"title": "学生票"
}, {
"title": "儿童票"
}, {
"title": "特惠票"
}]
显示出来的效果就和上面那张截图一样。
三、额外知识点:
3.1.使用ajax动态获取数据:
由于页面一般不会直接将数据完全写在html中,直接写死的数据之后想修改就很麻烦,因此这此开发中使用ajax获取页面数据,之后如果有需要修改的,更新json数据就行,无需再变更页面。
使用方法:
第一步:安装Vue当前推荐的第三方模块axios,npm install axios --save
第二步:引入axios
import axios from 'axios'
第三步:从页面的根组件中发送ajax请求,比如Home页面下,有轮播图,有分类图标,有推荐模块。这些组件都需要请求数据,如果每一个组件都发送请求,那请求的次数太多了,因此在Home.vue这个根组件中发送一次ajax请求,将返回的数据分类传递给各个子组件就好。
methods: {
getHomeInfo () {
// 只有static文件夹下的数据,静态数据,才可以直接被访问,别的路径会被跳转到首页,因此把模拟数据放到static目录下
// ret: true 服务器正常响应请求
// 因为使用了keep-alive因此每次回到首页的时候,需要根据当前城市获取到首页的数据
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
}
}
}
在页面挂载的时候,调用getHomeInfo方法就好。
额外补充:没有后端服务器支持的时候,模拟数据的方法:
在static静态文件目录中放入需要的json文件
这时就可以直接在axios.get方法中请求本地路径了。
但是这样在项目上线的时候需要改成服务器上的地址,容易出问题。
因此写法不变,添加一个转发机制就行了,webpack-dev-server这个工具提供这个功能。
在config - index.js 中 在dev开发环境下,默认提供一个proxyTable配置项。
在其中配置:(改变项目配置项后需要重启服务器)
proxyTable: {
// 项目上线之前再改代码,容易出现问题,因此一开始设置获取数据的地址都是api路径
// 但是一开始模拟数据资源都在本地,因此需要先做一个跳转,日后好改。
'/api': {
target: 'http://localhost:8080',
pathRewrite: {
// 一旦请求的地址是以api开头的,会自动取static/ajax中的内容
'^/api': '/static/ajax'
}
注:今天往服务器上部署的时候,发现把这里的地址改为服务器地址,配套路径都没有办法获取到数据,还没有查清原因。
如图:
只好在代码里修改了获取数据的方式。
3.2.防止swiper显示效果出错:
在详情界面,点击图片会在最上层显示图片轮播界面,再次点击会消失。
但是在显隐改变的过程中,显示效果会出现问题,不太清楚为什么,但是重新刷新控件就好了。因此需要加入这个属性。(后两行)
swiperOptions: {
pagination: '.swiper-pagination',
paginationType: 'fraction',
// 防止显隐改变时滚动效果出错,swiper插件监听到自身或父级元素变化时会自我刷新一次
observeParents: true,
observer: true
}
3.3.iconfont数量图标库:
页面中有很多小的图标,如果能做到图标匹配,会很大程度上的提升页面的效果。
在iconfont这个网站上就能很方便的找到各式各样的图标,使用方法简单,官网教程也说的非常清楚。
第一步:找到喜欢的图标添加到项目中(iconfont中可以建立自己的项目)并下载,顺便解压一下。。
第二步:提取这四个文件
放到src/assets/styles/iconfont下,(个人习惯)
再把iconfont.css放到src/assets/styles目录
第三步:在iconfont.css中修改前面几行的引用代码,涉及到那四个文件,只需要改前面的路径就好。
src: url('./iconfont/iconfont.eot?t=1537099246601'); /* IE9*/
src: url('./iconfont/iconfont.eot?t=1537099246601#iefix') format('embedded-opentype'), /* IE6-IE8 */
url('./iconfont/iconfont.ttf?t=1537099246601') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/
url('./iconfont/iconfont.svg?t=1537099246601#iconfont') format('svg'); /* iOS 4.1- */
第四步:在iconfont中 ”我的项目“中找到需要的图标,鼠标移动到图标上,点击复制代码。
第五步:在main.js中引入iconfont
import 'styles/iconfont.css'
第六步:正常使用:直接粘贴刚才复制的代码。class需要设置为iconfont,别的可以再加。
<div class="iconfont back-icon"></div>
图标大小可以通过font-size这个css样式来改变。
3.4.使用keep-alive优化性能:
简单地说就是缓存。。
这个功能是Vue内置的,防止每次路由切换到某页面的时候,都重新获取数据,如果是静态页面,没必要每次都重新获取数据。
因此使用keep-alive标签,包裹不需要每次都重新请求的内容。
<!-- keep-alive是vue自带的标签 -->
<!-- 不使用keep-alive每次进入新页面都会重新渲染内容,执行钩子函数 -->
<!-- 加入之后,每次进入新页面后,都会将数据存入内存,下次再进入此页面的时候不用重新渲染,提升性能 -->
<!-- 不用activated生命周期钩子,避免keep-alive缓存的办法 -->
<keep-alive>
<!-- 显示的是当前路由地址所对应的内容 -->
<router-view/>
</keep-alive>
额外补充:如何修改缓存中的数据?
方法一:
每当页面加载都会执行mounted函数,但是如果使用了keep-alive,第二次访问这个页面就不会再执行这个函数了,因此一些修改信息的方法不能卸载mounted函数中。
不过一旦使用了keep-alive,就会多一个生命周期函数,activated函数,每次进入页面都会执行这个方法。
如果需要修改一些小内容,可以把修改方法写在其中。
方法二:
在keep-alive标签中直接加入不希望被缓存的vue组件
例如:
<keep-alive exclude="Detail">
<router-view/>
</keep-alive>
这里的Detail对应的是我详情页面的根组件,其中的name属性就是Detail.
3.5.解决低版本手机页面白屏:
没有遇到这种情况,但是开发过程的老师提到了,似乎比较常见,记录在此:
npm install babel-polyfill --save
之后在main.js中引用import 'babel-polyfill'即可
3.6.修复1px边框问题:
1px指的是css像素,但是有的屏幕因为像素比较高,在二倍屏或者多倍屏上对应的不是一个物理像素的高度,而是两个或者多个物理像素的高度。
很多时候定义的1px边框会因为屏幕倍数被放大,只需要在main.js中引入一个css文件即可
文件在我的项目中src/assets/styles/border.css
我把码云的地址发上来,有需要的同志可以下载。
传到csdn上还要c币,一个css文件5个c币我自己都觉得亏。。。
https://gitee.com/AsherMa/Travel
引入后只用在class中添加border-topbottom(上下边框)或者border-bottom(下边框)
这样就有1px边框了。
3.7.reset.css重置页面样式表:
在不同的手机浏览器上默认的样式是不统一的,因此需要把这些样式做一个统一。
其实只需要引入一个css文件就可以做到了。
如同border.css,在我的项目中src/assets/styles/reset.css
3.8.fastclick:
在移动端项目中会有一个300毫秒点击延迟的问题,某些机型某些浏览器上,使用click事件时会存在这样的问题。
这样会影响用户体验,因此解决这个问题需要引入fastclick库。
解决问题需要做这几步:
第一步:npm install fastclick --save
第二步:main.js中
import fastClick from 'fastclick'
第三步:依旧是main.js attach是fastclick自带的方法,将其绑定到body上就好了。
fastClick.attach(document.body)
3.9.页面跳转:
每一个Vue实例中都有这样一个实例属性,router
this.$router.push('/')
可以直接通过其push方法完成页面跳转,根据地址跳转,/跳转到主页。
3.10.Vuex实现数据共享:
先说应用场景,在本次项目中,首页右上角有城市选择按钮,默认显示当前城市,如果点进去修改,回到主页会变为新选择的城市。
这时这两个页面之间就需要数据传递了,学完vue的基本语法应该已经学过兄弟组件或者父子组件之间传值了。
不过项目中的主页Home.vue和城市选择页面City.vue之间没有一个公用的父级组件,无法做一个数据中转。
不过这里可以使用一个数据共享来实现这个功能,就是标题上说的Vuex。
Vue中推荐的数据框架就是Vuex。
可以这么理解,如果项目中数据比较复杂需要共享的时候,可以把这些数据放到一个公用的数据存储空间统一存储。
一个组件改变了这个公用空间的数据,其他组件就可以感知到。
上图右侧虚线区域就可以理解为一个仓库store,state中存放所有公用的数据,组建如果想要使用state中的数据,直接调用即可。
如果需要改变state的数据,组件无法直接修改,需要走一个流程。
这个流程就是组件先调用Actions做一些异步处理或者批量的同步操作,之后Actions再调用Mutations,Mutations中存放的是一个个同步的对State的修改。
有时也可以略过Actions,让组件直接调用Mutations去修改数据。
组件调用Actions时调用的是Dispatch方法,Actions调用Mutations的时候调用的是Commit这个方法。
Vuex实际上就是一个单向数据传递的流程。
具体使用Vuex的方法:
第一步:安装Vuex
npm install Vuex --save
第二步:创建Vuex目录
因为后期可能使用的数据比较复杂,因此有可能需要对Vuex的三部分进行拆分。就需要先建立目录
在src目录创建一个文件夹名为store
在store文件夹下创建index.js文件,我把Actions,Mutations,State三部分做了拆分,当前这三部分涉及到的公用数据与方法只是针对城市选择页面与主页之间的城市数据共享,因此代码量很少,但是如果数据量大的时候,拆分还是易于维护的。
// 本来应该直接写在根目录下的main.js,但由于store的目录还是比较复杂的,因此另建一个文件
import Vue from 'vue'
import Vuex from 'vuex'
import state from './state'
import mutations from './mutations'
import actions from './actions'
// Vuex是一个插件,因此需要use来使用插件
Vue.use(Vuex)
// 导出的不是Vuex而是Vuex创建的仓库
export default new Vuex.Store({
state,
actions,
mutations
})
actions.js:
export default {
// actions方法第一个参数ctx是上下文,第二个参数就是传入的参数
changeCity (ctx, city) {
ctx.commit('changeCity', city)
}
}
mutations.js:
export default {
changeCity (state, city) {
state.city = city
try {
localStorage.city = city
} catch (error) { }
}
}
state.js:
let defualtCity = '城市'
// 使用localStorage时推荐使用trycatch
// 原因见vuex笔记21行
try {
if (localStorage.city) {
defualtCity = localStorage.city
}
} catch (error) {}
export default {
// 默认从localStorage中取值,如果取不到再选择‘城市’
city: defualtCity
}
在main.js中引入store
import store from './store'
之后创建根Vue实例的时候把store传入进去。
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
store,
// ES6 相当于 components: { App: App }
components: { App },
template: '<App/>'
})
// 路由就是根据网址的不同,返回不同的页面给用户
第三步:组件中使用:
{{this.$store.state.city}}
就可以直接访问到city这个数据了。
额外补充:
1.数据的修改方法:
this.$store.dispatch('changeCity', city)
意为调用一个名为changeCity的Actions,并将city这个数据传入
这就需要提前在actions.js中写好方法,在上面的代码里已经写好了。
Actions就会调用Mutations,因此这时mutations.js中对应的方法也应该写好。
这样就可以修改了。
当然组件也可以直接调用commit方法直接修改数据:
this.$store.commit('changeCity', city)
2.Vuex的简写方法:
第一步:引入
// 访问Vuex store中的数据需要写这么多{{this.$store.state.city}}
// Vuex提供了一个高级的api 使用方法如下:
import { mapState } from 'vuex'
第二步:加入计算属性:
// 意思是mapState是值,把vuex里面的数据映射到此组件的计算属性里
// 把city这个数据映射到名为city的计算属性中
// 这样在使用的时候就不用写之前那么长的代码,直接this.city就可以直接使用了
computed: {
...mapState(['city'])
}
第三步:使用:
{{this.city}}
再额外补充。。。
1.传入数据多样
computed: {
// 传入的值可以是一个数组[],也可以是一个对象
// 这样写的意思是,把state中city数据映射到此组件里,映射过来的名字就是currentCity
...mapState({
currentCity: 'city'
})
}
2.简写不仅能用于state,还可以用于actions以及mutations,需要在引入的时候加入
import { mapState, mapActions } from 'vuex'
使用方法:在methods中加入映射关系
// 意为有一个mutation叫changeCity,把这个mutation映射到这个组件中一个名为changeCity的方法
// 再直接调用mutation的时候可以直接这么使用this.changeCity(city)
// ...mapMutations(['changeCity']),
...mapActions(['changeCity'])
具体使用方法与state简写一样:
this.changeCity(city)
3.Vuex中还有两个概念:
getters
类似computed计算属性
module
当遇到非常复杂的应用场景,数据很多
都放在mutations时,会非常难以管理
可以将公用数据分模块拆分
创建store的时候进行一个整合就可以了,易于维护
3.11.localStorage本地存储:
举一个实际的应用场景,每次用户选择玩城市后,下次进入这个页面就希望能记住上一次选择的城市。因此这时需要localStorage api。
这个api类似cookies但是比cookies更简单。
在上一步的mutations中,每次改变数据的时候,在localStorage中也存储一下用户修改的地址
changeCity (state, city) {
state.city = city
try {
localStorage.city = city
} catch (error) { }
}
每次取值的时候也先从localStorage中取,如果取不到再设置为默认值。
try {
if (localStorage.city) {
defualtCity = localStorage.city
}
} catch (error) {}
使用localStorage时推荐使用trycatch
因为有的用户浏览器 关闭了本地存储功能或者使用隐身模式,则会直接抛出异常,代码无法运行
3.12.动画效果(简单的):
这里不知道怎么总结了。。。我直接放我当时学习过程中的代码吧。放在页面最后。
3.13.真机测试:
如果需要在开发过程中进行真机测试,手机和电脑连接在同一局域网内,在命令行中ipconfig查到电脑端ip地址,是无法直接在手机上访问页面的,需要在项目根目录下的package.json文件内找到"scripts" -> "npm run dev" 在webpack-dev-server后面加入--host 0.0.0.0即可,实际上这里控制的是我们输入npm run dev后系统自动帮我们执行的命令。加入这句之后,重启服务器即可在手机上直接测试我们的网站了。
四、css相关知识点:
4.1.stylus:
在style标签上加入lang="stylus"即可,之后就可以使用stylus的简便语法了。
不再需要花括号,用缩进表明层级关系
4.2.解决不同组件中的style样式相互影响:
为了限制style样式只在当前组件中生效,需要在style标签中加入scoped关键字
4.3.rem:
在统一样式(3.7)中加入的reset.css中对于html的font-size已经设置为50px
rem是相对于这个50px的设定的。
1rem = html font-size = 50px
举个实际的例子,如果有一张图片的height需要86px,但是目前由于屏幕分辨率都比较高,图片资源中常见二倍尺寸的图片。
所以这里设计时写43px就够了。这时换算成rem就是0.86rem 可以简写成.86rem
正好对应要求高度86px,除以100直接是对应的rem,这也是html中font-size被设置成50px的目的所在,方便使用。
4.4.自定义别名:
有时对于一些自定义的路径,比较常用且路径又比较复杂的时候,可以使用自定义别名对这个路径进行定义。之后引用的时候就不用输入一长串路径,直接输入别名即可。
自定义别名修改路径: bulid/webpack.base.conf.js resolve -> alias
alias: {
'vue$': 'vue/dist/vue.esm.js',
'@': resolve('src'),
'styles': resolve('src/assets/styles'),
'common': resolve('src/common'),
}
这时我如果再需要引入一个src/assets/styles下的文件,就可以直接
// css中引入其它css需要加~
@import '~styles/varibles.styl'
4.5.超出文本限制长度后用...省略:
如图:
使用方法:
第一步:
自定义文件,名字随意,我写的是mixins.styl
ellipsis()
overflow: hidden
white-space: nowrap
text-overflow: ellipsis
第二步:
在需要的组件styles中引入
@import '~styles/mixins.styl'
第三步:
在需要加入省略的class外侧class中加入这一行
// 不加这一行,ellipsis省略的效果显示不出来
min-width: 0
第四步:
在需要加入省略功能的class中加入:
ellipsis()
4.6.零碎的css知识点:
1.overflow:hidden的意思就是超出部分隐藏
2.css中引入其它css需要加~
3.padding: 0 .2rem //两个参数意为左右不给间距,上下给0.2rem的间距
4.占位以及css穿透:
// 在wrapper的外层加入div设置class如下,可以为未加载的图片轮播器占位,防止因为图片加载慢导致的页面抖动问题
// 图片宽高比=高度/宽度实例中是640*200的图片,因此宽高比设置为31.25% 高度设置为0 通过padding-bottom设置高度。
// 不论网速如何,先加载出的内容都会在图片下方。
// 图片未加载出来时加入一个灰色背景
// 图片轮播次序点不在此组建中设置css,在这里修改颜色无效
// 需要通过 >>> 做穿透,就不受scoped限制
.wrapper >>> .swiper-pagination-bullet-active
background: #00bcd4
.wrapper
overflow: hidden
width: 100%
height: 0
padding-bottom: 31.25%
background: #eee
.swiper-img
width: 100%
5.垂直居中:
// 这三行控制此元素在垂直方向做居中
display: flex
flex-direction: column
justify-content: center
6.有时图标未显示,需要加入绝对定位限制:
// 图标未显示 加入绝对定位解决
position: absolute
top: 0
left: 0
7.边框颜色:
// 通过这两个before和after元素就可以控制页面上一像素边框的颜色
.border-topbottom
&:before
border-color: #ccc
&:after
border-color: #ccc
// 下边框只有before属性
.border-bottom
&:before
border-color: #ccc
8.文本框留左右间隙:
// 加入这两行,可以在输入框输入满了以后左右预留间隙,整体更美观
padding: 0 .1rem
box-sizing: border-box
9.z-index:
z-index 属性设置元素的堆叠顺序。
数值越大,排列位置越前(Z坐标上方,视图层级关系)
拥有更高堆叠顺序的元素总是会处于堆叠顺序较低的元素的前面。
元素z-index可以为负值
只针对有定位的元素 例如position: absolute
10.渐变色:
background-image: linear-gradient(top, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.8))
五、结尾:
本次笔记可以说是很凌乱了。。第一次写笔记,如果看到的各位大神有任何意见,欢迎直接提出,我会做相应修改的。
最后,这次的教学源自慕课网的Vue2.0去哪儿网开发课程,有需要视频课程的同学可以去官网学习。
5.1.附简单的动画效果代码&笔记:
5.1.1.Vue中的css动画原理:
<!DOCTYPE html>
<head>
<meta charset="UTF-8">
<title>Vue中的css动画原理</title>
<script src="./vue.js"></script>
<style>
/* 为什么以.fade开头 因为需要渲染的transition的name属性为fade 如果没有name属性 这里可以用v-enter等等 */
.fade-enter,
.fade-leave-to {
opacity: 0;
}
.fade-enter-active,
.fade-leave-active {
transition: opacity 1s;
}
</style>
</head>
<body>
<div id="root">
<!-- 自动向标签上添加class属性以增加动画效果 -->
<!-- transition 过渡 -->
<!-- 支持v-show v-if 动态组件 -->
<!-- 动画开始的时候 自动添加了v-enter v-enter-active -->
<!-- 开始的第二秒加入 v-enter-to 同时去掉v-enter -->
<!-- transition一直监控opacity的变化,默认是1 变成0以后开始动画,淡出同理 -->
<!-- 直到动画结束自动去除这些自动添加的class -->
<transition name="fade">
<div v-if="show">
hello World
</div>
</transition>
<button @click="handleClick">切换</button>
</div>
<script>
var vm = new Vue({
el: "#root",
methods: {
handleClick: function () {
this.show = !this.show;
}
},
data: {
show: true,
}
})
</script>
</body>
</html>
5.1.2.Vue中使用animate.css库:
<!DOCTYPE html>
<head>
<meta charset="UTF-8">
<title>Vue中使用animate.css库</title>
<script src="./vue.js"></script>
<link rel="stylesheet" type="text/css" href="./animate.css">
<style>
@keyframes bounce-in {
0% {
/* @keyframes与使用animate库没有区别都是封装 */
transform: scale(0)
}
50% {
transform: scale(1.5)
}
100% {
transform: scale(1)
}
}
.active {
/* 不加这一行,动画会跑偏。不懂时去掉此行代码观察 */
/* 默认名称是.fade-enter-active 下面同理 为了自定义名字修改 */
transform-origin: left center;
animation: bounce-in 1s;
}
.leave {
transform-origin: left center;
animation: bounce-in 1s reverse;
}
</style>
</head>
<body>
<div id="root">
<!-- 使用animated动画库三大要点 -->
<!-- 1、必须使用自定义active-class的形式 -->
<!-- 2、class类中必须包含animated这个类 -->
<!-- 3、必须将动画效果名称写在后面 -->
<transition
name="fade"
enter-active-class="animated swing"
leave-active-class="animated shake"
>
<div v-if="show">
hello World
</div>
</transition>
<button @click="handleClick">切换</button>
</div>
<script>
var vm = new Vue({
el: "#root",
methods: {
handleClick: function () {
this.show = !this.show;
}
},
data: {
show: true,
}
})
</script>
</body>
</html>
5.1.3.Vue中同时使用过渡和动画:
<!DOCTYPE html>
<head>
<meta charset="UTF-8">
<title>Vue中同时使用过渡和动画</title>
<script src="./vue.js"></script>
<link rel="stylesheet" type="text/css" href="./animate.css">
<style>
.fade-enter,
.fade-leave-to {
opacity: 0;
}
.fade-enter-active,
.fade-leave-active {
transition: opacity 3s;
}
</style>
</head>
<body>
<!-- animate 动画叫keyframe 动画 是 css3的动画 -->
<!-- transition是过渡动画 -->
<div id="root">
<!-- 需要在页面第一次加载的时候调用animate动画特效 需要加入appear属性 -->
<!-- 定义type声明两种动画时常以transition时长为主 type="transition" -->
<!-- 也可以自定义动画时常 使用duration="毫秒数" -->
<!-- duration控制class持续的时间 -->
<!-- 或者更复杂的设置出场和入场动画 -->
<transition :duration="{enter: 5000, leave: 10000}"
name="fade"
appear
enter-active-class="animated swing fade-enter-active"
leave-active-class="animated shake fade-leave-active"
appear-active-class="animated shake"
>
<div v-if="show">
Hello World
</div>
</transition>
<button @click="handleClick">切换</button>
</div>
<script>
var vm = new Vue({
el: "#root",
methods: {
handleClick: function () {
this.show = !this.show;
}
},
data: {
show: true,
}
})
</script>
</body>
</html>
5.1.4.Vue中的Js动画与Velocity.js的结合:
<!DOCTYPE html>
<head>
<meta charset="UTF-8">
<title>Vue中的Js动画与Velocity.js的结合</title>
<script src="./vue.js"></script>
<link rel="stylesheet" type="text/css" href="./animate.css">
<script src="./velocity.min.js"></script>
<style>
</style>
</head>
<body>
<div id="root">
<!-- 钩子函数 -->
<!-- before-enter 在动画开始之前调用 -->
<!-- enter 动画调用结束后调用 -->
<!-- @after-enter done之后调用 -->
<!-- enter换成leave就是出场动画 -->
<transition
name="fade"
@before-enter="handleBeforeEnter"
@enter="handleEnter"
@after-enter="handleAfterEnter"
>
<div v-if="show">
Hello World
</div>
</transition>
<button @click="handleClick">切换</button>
</div>
<script>
var vm = new Vue({
el: "#root",
methods: {
handleClick: function () {
this.show = !this.show;
},
// el 这个参数 是指被动画效果包裹的div标签
handleBeforeEnter: function(el) {
// el.style.color = 'red'
el.style.opacity = 0;
},
// done是回调函数,当动画执行完后需要手动调用done函数
// 相当于告诉后台动画执行完毕
handleEnter: function(el, done) {
// // 延时两秒
// setTimeout(() => {
// el.style.color = 'green'
// }, 2000);
// // 四秒后调用done
// setTimeout(() => {
// done()
// }, 4000);
// complete配置表示动画执行完毕
Velocity(el, {opacity: 1}, {duration: 1000, complete: done})
},
handleAfterEnter: function(el) {
// el.style.color = 'black'
alert("done")
}
},
data: {
show: true,
}
})
</script>
</body>
</html>
5.1.5.Vue中多个元素或组件的过度:
<!DOCTYPE html>
<head>
<meta charset="UTF-8">
<title>Vue中多个元素或组件的过度</title>
<script src="./vue.js"></script>
<link rel="stylesheet" type="text/css" href="./animate.css">
<style>
.fade-enter,
.fade-leave-to {
opacity: 0;
}
.fade-enter-active,
.fade-leave-active {
transition: opacity 1s;
}
</style>
</head>
<body>
<div id="root">
<!-- 不加key属性 由于vue默认dom复用,不会出现出现与显示的特效。因此建立key属性 -->
<!-- mode="in-out" 动画元素先进来再隐藏 -->
<!-- mode="out-in" 动画元素先隐藏再显示 -->
<transition name="fade" mode="out-in">
<!-- 动态组件 -->
<component :is="type"></component>
<!-- 不同组件 -->
<!-- <child v-if="show"></child>
<child-one v-else></child-one> -->
<!-- 普通元素 -->
<!-- <div v-if="show" key="hello">
Hello World
</div>
<div v-else key="bye">
Bye World
</div> -->
</transition>
<button @click="handleClick">切换</button>
</div>
<script>
Vue.component('child', {
template: '<div>child</div>'
})
Vue.component('child-one', {
template: '<div>child-one</div>'
})
var vm = new Vue({
el: "#root",
methods: {
handleClick: function() {
// this.show = !this.show
this.type = this.type === "child" ? "child-one" : "child"
}
},
data: {
// show: true,
type: "child"
}
})
</script>
</body>
</html>
5.1.6.Vue中的列表过渡:
<!DOCTYPE html>
<head>
<meta charset="UTF-8">
<title>Vue中的列表过渡</title>
<script src="./vue.js"></script>
<link rel="stylesheet" type="text/css" href="./animate.css">
<script src="./velocity.min.js"></script>
<style>
.v-enter, .v-leave-to {
opacity: 0;
}
.v-enter-active,
.v-leave-active {
transition: opacity 1s;
}
</style>
</head>
<body>
<div id="root">
<!-- transition-group相当于在每一个div外侧加上transition -->
<transition-group>
<!-- 能不用index作为key值 就不用 会影响效率 -->
<div v-for="item of list" :key="item.id">
{{item.title}}
</div>
</transition-group>
<button @click="handleBtnClick">add</button>
</div>
<script>
var count = 0;
var vm = new Vue({
el: "#root",
methods: {
handleBtnClick: function () {
this.list.push({
id: count++,
title: 'hello World'
})
},
},
data: {
show: true,
list: []
}
})
</script>
</body>
</html>
5.1.7.Vue中的动画封装:
<!DOCTYPE html>
<head>
<meta charset="UTF-8">
<title>Vue中的动画封装</title>
<script src="./vue.js"></script>
<link rel="stylesheet" type="text/css" href="./animate.css">
<script src="./velocity.min.js"></script>
<!-- <style>
.v-enter,
.v-leave-to {
opacity: 0;
}
.v-enter-active,
.v-leave-active {
transition: opacity 1s;
}
</style> -->
</head>
<body>
<div id="root">
<fade :show="show">
<div v-show="show">hello world</div>
</fade>
<fade :show="show">
<h1 v-show="show">hello world</h1>
</fade>
<button @click="handleBtnClick">切换</button>
</div>
<script>
// 完全封装就使用js样式。
Vue.component('fade', {
props: ['show'],
template: `
<transition @before-enter="handleBeforeEnter"
@enter="handleEnter">
<slot v-show="show"></slot>
</transition>
`,
methods: {
handleBeforeEnter: function(el) {
el.style.color = 'red'
},
handleEnter: function(el, done) {
setTimeout(() => {
el.style.color = 'green'
done()
}, 2000);
}
}
})
var vm = new Vue({
el: "#root",
methods: {
handleBtnClick: function () {
this.show = !this.show;
}
},
data: {
show: true,
}
})
</script>
</body>
</html>
其中涉及到的两个文件网上都可以下
animate.css velocity.min.js
最后放一个随滚动标题栏渐隐渐现的效果代码
但是电脑端效果无误,手机端出不来效果。还没有查出原因。
<template>
<div>
<router-link
tag="div"
to="/"
class="header-abs"
v-show="showAbs"
>
<div class="iconfont header-abs-back"></div>
</router-link>
<div
class="header-fixed"
v-show="!showAbs"
:style="opacityStyle"
>
<router-link to="/">
<div class="iconfont header-fixed-back"></div>
</router-link>
景点详情
</div>
</div>
</template>
<script>
export default {
name: 'DetailHeader',
data () {
return {
showAbs: true,
topp: 0,
opacityStyle: {
opacity: 0
}
}
},
methods: {
handleScroll () {
const top = document.documentElement.scrollTop
if (top > 60) {
let opacity = top / 140
opacity = opacity > 1 ? 1 : opacity
this.opacityStyle = {
opacity
}
this.showAbs = false
} else {
this.showAbs = true
}
}
},
activated () {
window.addEventListener('scroll', this.handleScroll)
},
// 页面被隐藏时执行
deactivated () {
// 对全局事件解绑,不影响首页使用
window.removeEventListener('scroll', this.handleScroll)
}
}
</script>
<style lang="stylus" scoped>
@import '~styles/varibles.styl'
.header-abs
position: absolute
left: .2rem
top: .2rem
width: .8rem
height: .8rem
line-height: .8rem
border-radius: .4rem
background: rgba(0, 0, 0, .8)
text-align: center
.header-abs-back
color: #fff
font-size: .4rem
.header-fixed
z-index: 2
position: fixed
top: 0
left: 0
right: 0
height: $headerHeight
line-height: $headerHeight
color: #fff
text-align: center
background: $bgColor
font-size: .32rem
.header-fixed-back
// 图标未显示 加入绝对定位解决
position: absolute
top: 0
left: 0
width: .64rem
text-align: center
font-size: .4rem
color: #fff
</style>