TODO NEXT ^_^
链接 -_-||
2> 经验记录
https://jiangjiesheng.gitee.io/qu-na-er
git合并到主干:
git checkout master
git merge [分支名称]
git push
node -v ==> v8.11.3
npm -v ==> 5.6.0
#使用淘宝镜像
npm config set registry https://registry.npm.taobao.org
[npm install -g cnpm --registry=https://registry.npm.taobao.org]
安装vue
npm install vue@^2.5.2 --save
使用脚手架命令行工具vue-cli创建vue项目
最好先初始化项目,再创建一些记录性文件
#全局安装 vue-cli
npm install --global vue-cli
#创建一个基于 webpack 模板的新项目
vue init webpack qu-na-er (去哪儿网-GitBash中执行没有反应)
#输入项目信息
>> Project name qu-na-er
>> Project description A Vue.js project
>> Author dev@jiangjiesheng.cn
>> Vue build (Use arrow keys)
>> Vue build standalone
>> Install vue-router? Yes
>> Use ESLint to lint your code? Yes
>> Pick an ESLint preset Standard
>> Set up unit tests No
>> Setup e2e tests with Nightwatch? No
>> Should we run `npm install` for you after the project has been created? (recommended) npm
vue-cli · Generated "qu-na-er".
# Installing project dependencies ...
# ========================
#安装依赖
cd qu-na-er
(这里是单独创建一个文件夹并初始化的,所以把项目复制到原始的创建的项目中)
npm install
npm run dev
reset.css
统一不同浏览器的默认样式,文件为src/assets/styles/reset.css,并在main.js中引用
【本项目使用的尺寸单位是rem,是相对于html的font-size: 50px的大小来设置的】
1rem = html font-size = 50px
即
43px = 0.86rem
// 统一不同浏览器的默认样式
import './assets/styles/reset.css'
1像素边框
可以显示一个类似垂直分割线的边框效果,文件为src/assets/styles/border.css,并在main.js中引用
// 1像素边框解决方案
import './assets/styles/border.css'
300毫秒点击延迟(在部分浏览器上)
安装并在main.js中引入并初始化
npm install fastclick --save
import fastclick from 'fastclick'
// 300毫秒点击延迟
fastclick.attach(document.body)
iconfont注册
使用微博登录 jiangjsheng@sina.cn
菜单 >> 图标管理 >> 我的项目
下载后注意修改iconfont.css中的引用路径
Header头
首先安装css插件
npm install stylus --save
npm install stylus-loader --save
stylus使用
//scoped 对局部样式有效
<style lang="stylus" scoped>
.header //不要有冒号
display: flex
height: .86rem
.header-left //不要有冒号
width: .64rem
float: left
.header-input //不要有冒号
flex: 1
.header-right //不要有冒号
width: 1.24rem
float: right
</style>
scoped的样式穿透
<style lang="stylus" scoped>
.wrapper >>> .swiper-pagination-bullet-active
background: red !important
</style>
style中引入注意
@表示src目录别名
别名配置在build/webpack.base.conf.js中的alias节点
注意在style使用要加上~
@import '~@/assets/styles/varibles.styl'
首页轮播图
首先在线上创建分支index-swiper,分支都相对于前一个分支,然后git pull 同步本地分支,并切换到分支index-swiper
https://www.npmjs.com/package/vue-awesome-swiper
https://github.com/surmon-china/vue-awesome-swiper
https://blog.csdn.net/mrliber/article/details/78819191 [配置参考]
安装
npm install vue-awesome-swiper@2.6.7 --save
html上的冒号是绑定属性,@是绑定事件 子组件中的属性需要在data() 中定义并返回对象
弱网测试:F12 > Network > 选择3G网络
设置轮播图的占位
.wrapper
overflow: hidden
width: 100%
height:0
padding-bottom: 25%
or, 但是可能有兼容问题
.wrapper
width: 100%
height:25vw
push代码后切换回master主干,并合并index-swiper分支代码
git checkout master
git merge index-swiper
git push
首页图标菜单组件
创建分支index-icons-3
设置基本占位效果
<template>
<div class="icons">
<div class="icon">
</div>
</div>
</template>
<script>
export default {
name: 'HomeIcons'
}
</script>
<style lang="stylus" scoped>
.icons
overflow: hidden
height: 0
padding-bottom: 50%
background: green
.icon
float:left
width:25%
padding-bottom: 25%
background: red
</style>
安装 vue dev tools Chrome浏览器插件,方便查看Vue的结构
Vuejs的计算属性
computed: { // 计算属性
pages () {
const pages = []
this.iconList.forEach((item, index) => {
const page = Math.floor(index / 8)
if (!pages[page]) {
pages[page] = []
}
pages[page].push(item)
})
return pages
}
}
stylus的样式封装(相当于方法)
// src/assets/styles/mixins.styl
ellipsis()
overflow: hidden
white-space: nowrap
text-overflow: ellipsis
// 应用
@import '~@/assets/styles/mixins.styl'
.icon-desc
height: 0.44rem
line-height: 0.44rem
text-align: center
ellipsis()
首页推荐组件
创建分支index-recommend-4
flex关键作用
//相当于安卓中的layout_weight:1 ,用于撑开剩余空间部分
flex:1
如果ellipsis的省略效果不出现,可以在父级设置min-width:0
注意: 子组件中定义的data需要使用return一个对象
export default {
name: 'HomeRecommend',
data () {
return {
recommendList: [
{
id: '0001',
imgUrl: 'http://img1.qunarzz.com/sight/p0/201406/04/4f597aad25208a233999238c65af9b06.jpg_200x200_d1ea2bd2.jpg',
title: '南京珍珠泉水上世界',
desc: '高品质天然泉水水上乐园'
}
]
}
}
}
使用ajax获取api数据
安装axios依赖
npm install axios --save
在首页Home.vue中mounted()生命周期函数(又称钩子函数)中获取整个首页的多个组件API数据。
基本用法
import axios from 'axios'
export default {
name: 'Home',
components: {
...
},
methods: {
getHomeInfo () {
axios.get('/api/index.json')
.then(this.getHomeInfoSucc)
},
getHomeInfoSucc (res) {
console.log(res)
}
},
mounted () {
this.getHomeInfo()
}
}
Mock API数据
/config/index.js >> dev >> proxyTable
proxyTable: {
'/api': {
target: 'http://localhost:8080',
pathRewrite: {
'^/api':'/static/mock'
}
}
}
另外关于资源和API请求在线上环境需要分离的处理见
《npm-资源路径-本地调试-线上环境的api配置-环境隔离-打包》
首页的父子组件数据传递
// 父组件
// 关键位置1 绑定city属性
<home-header :city="city"></home-header>
// 关键位置2 在data () 中定义city属性,并更新值
export default {
name: 'Home',
components: {
...
},
data () {
return {
city: ''
}
},
methods: {
getHomeInfo () {
axios.get('/api/index.json')
.then(this.getHomeInfoSucc)
},
getHomeInfoSucc (res) {
res = res.data
if (res.ret && res.data) {
const data.city = res.data
this.city = data.city
}
}
},
mounted () {
this.getHomeInfo()
}
}
// 子组件
// 在props中定义属性并指定类型
export default {
name: 'HomeHeader',
props: {
city: String
}
}
//取父组件传过来的值
{{this.city}}
CityHeader组件
创建分支city-router-6
新增city路由,/src/router/index.js
{
path: '/city',
name: 'City',
component: City
}
Header.vue
快速搜索组件
创建分支city-search-7
Search.vue
城市列表组件
创建分支city-list-8
List.vue
修改1像素默认的伪元素的属性
.border-topbottom
&:before
border-color: #ccc
&:after
border-color: #ccc
Better-Scroll区域滚动组件
https://github.com/ustbhuangyi/better-scroll
https://blog.csdn.net/qq_26632807/article/details/77856950 [参数设置]
不带参数的better-scroll初始化会导致在安卓手机上不能点击better-scroll区域中的click事件,配置方法见
安装Better-Scroll
npm install better-scroll --save
DOM结构
<div class="wrapper">
<ul class="content">
<li>...</li>
<li>...</li>
...
</ul>
<!-- you can put some other DOMs here, -->
<!-- it won't affect the scrolling -->
</div>
初始化
1> 最简单的初始化(废弃):
import BScroll from 'better-scroll'
const wrapper = document.querySelector('.wrapper')
const scroll = new BScroll(wrapper)
Better-Scroll提供一个类,当实例化时,其第一个参数是一个纯DOM对象。当然,Better-Scroll内部滚动将尝试使querySelector选择器来获取DOM对象,因此初始化代码也可以如下所示:
2> 带参数的初始化(必须使用)
import BScroll from 'better-scroll'
mounted () {
const options = {
// 处理在better-scroll在安卓手机上不能点击的问题
// 更多配置见
// https://ustbhuangyi.github.io/better-scroll/doc/zh-hans/options.html#click
click: true,
tap: true
}
this.scroll = new Bscroll(this.$refs.wrapper, options)
},
特别注意要使Better-Scroll生效,可能还需要目标滚动区域要有效果,即默认上下溢出时却不能滚动才可以
.list
overflow: hidden
position: absolute
top: 1.58rem
left: 0
right: 0
bottom: 0
background: red
获取DOM节点的方法
// 指定一个ref (不加s)
<div class="list" ref="wrapper">
// 获取dom (不加s)
this.$refs.wrapper
右侧字母表组件
创建分支city-alphabet-9
Alphabet.vue
flex又一用法
display: flex
flex-direction: column
justify-content: center
特别注意在微信或者QQ浏览器向下滑动会触发事件冒泡,导致不能通过字母表上下滑动选择字母
<li @touchmove="handleTouchMove">{{item}}</li>
methods: {
handleTouchMove (e) {
// 微信中处理 向上滑动时整个页面跟随滚动的问题
e.preventDefault()
...
}
},
ajax获取数据
创建分支 city-ajax-10
兄弟组件间联动
创建分支 city-components-11
基本过程: Alphabet.vue子组件通过$emit发出自定义事件changeLetter
methods: {
handleLetterClick (e) {
this.$emit('changeLetter', e.target.innerText)
}
}
City.vue父组件在子组件dom上绑定子组件发出事件的接收函数changeLetter,并通过父组件中的handleLetterChange接收值letter,
<template>
<div class="w">
<city-alphabet @changeLetter="handleLetterChange"></city-alphabet>
</div>
</template>
...
data () {
return {
letter: ''
}
},
methods: {
handleLetterChange (letter) {
this.letter = letter
}
},
City.vue父组件再通过给List.vue子组件绑定属性的方式,把值letter传递给子组件
:letter="letter"
List.vue子组件通过定义属性的方式接收传递过来的值,并通过来watch监听letter变化。
props: {
letter: String
},
watch: {
letter () {
console.log(this.letter)
}
}
城市列表页搜索逻辑处理
创建分支 city-search-logic-12
v-model实现数据的双向绑定
<input v-model="keyword" class="search-input" type="text" placeholder="输入城市名或拼音">
data () {
return {
keyword: '',
list: [],
timer: null
}
},
watch: { // 特别注意:这里冒号不是watch () { ... }
keyword () {
if (this.timer){
clearTimeout(this.timer)
}
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)
}
控制Dom是否显示
v-show="!list.length"
or better
v-show="hasNoData"
computed: {
hasNoData () {
return !this.list.length
}
},
vuex实现数据共享
创建分支city-vuex-13
官网文档 https://vuex.vuejs.org/zh/
基本使用步骤:
1> 安装vuex
npm install vuex --save
2> 新建/src/store/index.js, 并在main.js中引入
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
city: '南京'
}
})
3> 并在main.js中引入,注册在Vue实例中
import store from './store/'
new Vue({
el: '#app',
router,
store, // 看我看我
components: { App },
template: '<App/>'
})
4> 取值
{{this.$store.state.city}}
5> 给热门城市绑定点击事件,并在点击事件中分发存储事件通知,在/src/store/index.js接收事件,并继续处理数据更新。
也可以直接跳过dispatch,直接修改commit,即
// this.$store.dispatch('changeCity', city)
this.$store.commit('toChangeCity', city)
<div v-for="item of hot" :key="item.id" @click="handleCityClick(item.name)">
methods: {
handleCityClick (city) {
this.$store.dispatch('changeCity', city)
alert(city)
}
},
/src/store/index.js
export default new Vuex.Store({
state: {
city: '南京'
},
actions: { //看我看我
changeCity (ctx, city) {
console.log(city)
ctx.commit('toChangeCity', city)
}
},
mutations: {
toChangeCity (state, city) {
state.city = city
}
}
})
vuex还支持字段映射
import { mapState } from 'vuex'
computed: {
...mapState(['city'])
}
or
import { mapMutations } from 'vuex'
handleCityClick (city) {
...
this.toChangeCity(city)
},
...mapMutations(['toChangeCity'])
通过js来打开路由页面
this.$router.push('/')
使用keep-alive优化性能
创建分支 city-keepalive-14
修改App.vue
<template>
<div id="app">
<keep-alive> // 看我看我
<router-view/>
</keep-alive>
</div>
</template>
此时又需要处理数据动态改变时页面需要重新请求数据,例如当前城市改变时,需求加载当前城市的信息
使用keep-alive后,mounted周期函数只会被调用一次,但是activated周期函数会每次都会被调用。所以...
在Home.vue中使用vuex,并在computed计算属性中映射一个city对象,同时定义一个属性lastCity标记上一个城市。
然后在activated周期函数中判断当前选择城市和上一个城市是否相等,不相等则将当前城市作为参数重新请求一次ajax。
import { mapState } from 'vuex'
data () {
return {
lastCity: '',
}
},
computed: {
...mapState(['city'])
},
methods: {
getHomeInfo () {
axios.get('/api/index.json?city=' + this.city)
.then(this.getHomeInfoSucc)
}
},
mounted () { // 页面初始化
this.lastCity = this.city
console.log('mounted')
this.getHomeInfo()
},
activated () { // 页面可见时
if (this.lastCity !== this.city) {
this.lastCity = this.city
this.getHomeInfo()
}
console.log('activated')
}
另外也可以使用来排除一部分页面,使其不使用缓存,例如城市详情页,修改App.vue
<keep-alive exclude="Detail">
<router-view/>
</keep-alive>
特别注意: exclude会导致页面中不再回调activated (), 但是会调用created (),所以一些需要重新初始化的方法或者属性需要在created () 调用。
例如:src/pages/detail/components/Header.vue
Banner
创建分支detail-banner-15
将li标签换成router-link:
解决router-link默认会改成标签颜色问题
注意需要同时在router-link标签上增加tag属性,指定需要渲染成li标签,同时指定to属性。
<router-link tag="li" v-for="item of list" :key="item.id" :to="'/detail/' + item.id">
...
</router-link>
注册动态路由,获取路由参数
{
path: '/detail/:id',
name: 'Detail',
component: Detail
}
参考 http://touch.piao.qunar.com/touch/detail.htm?id=33782
创建DetailBanner组件
渐变效果
background-image: linear-gradient(top, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.8))
共用组件画廊
swiper 中文网
http://3.swiper.com.cn/api/pagination/2016/0126/299.html
代码见 /src/common/gallary/Gallary.vue
Header渐隐渐显效果
创建分支 detail-header-16
特别注意:获取滚动条滚动的垂直距离scrollTop的兼容问题
通过绑定:style的opacity属性来动态改变透明度
<router-link tag="div" to="/" class="header-abs"
v-show="showAbs" :style="opacityAbsStyle">
<div class="iconfont back-icon-back"></div>
</router-link>
<div class="header-fixed" v-show="!showAbs" :style="opacityFixedStyle">
<router-link to="/">
<div class="iconfont header-fixed-back"></div>
</router-link>
景点详情
</div>
data () {
return {
showAbs: true,
opacityAbsStyle: { // 返回键的渐变
opacity: 1
},
opacityFixedStyle: { // 固定标题栏的渐变
opacity: 0
}
}
},
methods: {
handleScroll () {
// 特别注意,scrollTop中的每一对()都是来自网络的一种取法。已兼容安卓浏览器和UA为苹果的浏览器
const scrollTop = (window.parent.document.documentElement.scrollTop || window.parent.document.body.scrollTop)
|| (document.body.scrollTop + document.documentElement.scrollTop)
|| (window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0)
if (top > 40) { // 固定标题栏的渐变
let opacity = top / 130
opacity = opacity > 1 ? 1 : opacity
this.opacityFixedStyle = {
opacity
}
this.showAbs = false
} else { // 返回键的渐变
this.showAbs = true
let opacity = top / 40
opacity = opacity > 1 ? 1 : opacity
this.opacityAbsStyle = {
opacity: (1 - opacity)
}
}
}
},
activated () {
window.addEventListener('scroll', this.handleScroll)
}
对全局事件解绑
处理上一节window.addEventListener
activated () {
window.addEventListener('scroll', this.handleScroll)
}
解绑事件:
deactivated () {
window.removeEventListener('scroll', this.handleScroll)
}
使用递归组件实现详情页列表
创建分支 detail-list-17
json数据
list: [{
title: '成人票',
children: [{
title: '成人三管联票',
children: [{
title: '成人三管联票 - 某一连锁店销售'
}]
}, {
title: '成人五管联票'
}]
}, {
title: '学生票'
}, {
title: '儿童票'
}, {
title: '特惠票'
}]
递归调用detail-list组件(List.vue)
<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>
// 看这里
<div v-if="item.children" class="item-children">
<detail-list :list="item.children"></detail-list>
</div>
</div>
</div>
ajax获取数据
创建分支 detail-ajax-18
注意一下,使用属性绑定时加上冒号
<img class="banner-img" :src="bannerImg">
显示文本,使用{{属性xxx}}即可
<div class="banner-title">{{this.sightName}}</div>
// 这里的this可以去掉,但是大部分js环境的this不能去
使用计算属性控制是否显示一个列表部分(避免按空数组初始化,导致有数据变化时的当前显示的索引为列表的最后一个)
<swiper :options="swiperOption" v-if="isShowGallary">
// 需要特别注意,这个不要使用v-show,而是v-if
···
</swiper>
computed: {
isShowGallary () {
return this.imgs.length > 0
}
}
路由行为
打开新页面显示到顶部,不能受到上一页上下滚动的距离影响
https://cn.vuejs.org/v2/guide/migration-vue-router.html#saveScrollPosition-替换
修改router/index.js
export default new Router({
routes: [{
path: '/',
name: 'HelloWorld',
component: Home
}], // 打开新页面显示到顶部,不能受到上一页上下滚动的距离影响
scrollBehavior: function (to, from, savedPosition) {
return savedPosition || { x: 0, y: 0 }
}
})
更多关于路由介绍
在项目中使用基本动画
创建分支 detail-animation-19
代码在src/common/fade/FadeAnimation.vue (淡入淡出效果)
<template>
<transition>
<!-- 插槽 调用方的包裹的子组件会填充到这里 -->
<slot></slot>
</transition>
</template>
<script>
export default {
name: 'FadeAnimation'
}
</script>
<style lang="stylus" scoped>
.v-enter, .v-leave-to
opacity: 0
.v-enter-active, .v-leave-active
transition: opacity .5s
</style>
应用在在src/pages/detail/components/Banner.vue中
<fade-animation>
<!-- 这里会填充到插槽 -->
<common-gallary :imgs="bannerImgs" v-show="showGallary"></common-gallary>
</fade-animation>
作者:江节胜
微信:767000122 (欢迎添加好友)
Q Q :596957738
个人网站:tech.jiangjiesheng.cn
联系邮箱:dev@jiangjiesheng.cn
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。