npm install --global vue-cli
创建项目vue init webpack ‘文件夹名称’
运行项目npm run dev
/main.js 项目的入口文件
import Vue from 'vue' import App from './App' import router from './router' new Vue({ el: '#app', router, components: { App }, template: '<App/>' })/App.vue 项目默认的渲染文件
<template> <div id="app"> <keep-alive exclude="Detail"> <router-view/> //显示当前地址所对应的内容 </keep-alive> </div> </template> <script> export default { name: 'App' } </script> <style> </style>router/main.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 } ], })1./index.html
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no"> //页面的比例始终是一比一,用户端不可以进行窗口的扩大缩小 <title>Travel</title> </head> <body> <div id="app"></div> </body> </html> 引入重置页面的样式表 reset.css和一像素边框border.css和‘300毫秒延迟点击问题’fastclick库 将文件添加进来在main.js中添加代码import ‘./assets/styles/reset.css’ import ‘./assets/styles/border.css’
关于fastckick库 npm install fastclick --save引入import fastclick from ‘fastckick’
使用fastclick.attach(document.body)
import ‘./assets/styles/iconfont.css’
使用 在需要使用的地方图标代码 图表代码可以在购物车中的图表上查看
install stylus --save install stylus-loader --save
设计布局 home.vue 作为展示的容器,内部使用各个组件来搭页面在同一级创建目录‘components’,内部新建header.vue在 home.vue中引入并在components这个区域中命名它设置变量 $bgColor = #00bcd4
在的首行进行引入import ‘~styles/assets/styles/varibles.styl’
使用的时候,在值的部分输入$bgColor
将常用的路径设置为常量(符号表示) 在build/webpack.base.conf.js中,resolve: 中的alias:中添加一个键值对‘styles’ : resolve(‘src/assets/styles’),
import ‘swiper/dist/css/swiper.css’ import VueAwesomeSwiper from ‘vue-awesome-swiper’ Vue.use(VueAwesomeSwiper,‘可以给个参数’)
使用,建立swiper.vue <template> <div class="wrapper"> <div class="iconw"> <div class="iconw-img"> <swiper :options="swiperOption" v-if="showSwiper" > //应用轮播插件 <swiper-slide v-for="item of list" :key="item.id"> <img class="swipper-img" :src="item.imgUrl" /> </swiper-slide> <div class="swiper-pagination" slot="pagination"></div> //应用下面的选择点 </swiper> </div> </div> </div> </template> <script> export default { name: 'HomeSwiper', props: { list: Array }, data () { return { swiperOption: { pagination: '.swiper-pagination', loop: true //可以从最好一个滚回到第一个 } } }, computed: { showSwiper () { return this.list.length } } } </script> <style lang="stylus" scoped> .wrapper >>> .swiper-pagination-bullet-active //样式穿透,将设置应用在非当前页面的class样式中,不受scoped的限制 background: #fff .wrapper overflow: hidden height: 0 padding-bottom: 30.9% //将高度设置为宽度的30.9% background: green .iconw position: relative .iconw-img position: absolute top: 0 left: 0 right: 0 .swipper-img width: 100% </style><li class="item **border-bottom** ">
npm install axios --save
处理请求的api在开发环境进行重定向(转发) 开发中只有static中的文件可以被访问到,故在static文件夹中新建mock文件夹,然后建立index.json在config/index.js中的dev中的proxyTable中添加对象 proxyTable: { '/api': { target: 'http://localhost:8080', pathRewrite: { '^/api': '/static/mock' } } 在Home.vue进行使用 首先引入import axios from ‘axios’
在mounted进行使用mounted () { this.getHomeInfo() }, 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 this.cities = data.cities } } } 调用其他vue页面的时候将参数传递进去<city-al :citise="cities"><city-al>
被调用的页面进行接收props:{ cities:Object }
被调用的页面在接收后进行使用 <li class="item" v-for="item of letters" :key="item" :ref="item">{{item}}</li>/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} //页面切换后从顶部开始显示内容 } })Header中点击城市名,将页面跳转到城市选择页面
<router-link to='/city'> <div class="header-right"> {{this.city}} <span class="iconfont arrow-icon"></span> </div> </router-link>搜索框样式
<style lang="stylus" scoped> @import '~styles/varibles.styl' .search height: .72rem padding: 0 .1rem background: $bgColor .search-input box-sizing: border-box width: 100% height: .62rem padding: 0 .1rem line-height: .62rem text-align: center border-radius: .06rem color: #666 .search-content z-index: 1 overflow: hidden position: absolute top: 1.58rem left: 0 right: 0 bottom: 0 background: #eee .search-item line-height: .62rem padding-left: .2rem color: #666 background: #fff </style>npm install better-scroll --save
使用规范 查看Git官网的项目,来认识他的使用规范两层div里面放需要的内容,如其他的div,或者li等标签 使用 利用ref来获取dom 在需要使用scroll的div中添加<div class="list" ref="wrapper">
引入import Bscroll from ‘better-scroll’
在mounted中实例化mounted () { this.scroll = new Bscroll(this.$refs.wrapper) },
console.log(e.target.innerText)
样式 <style lang="stylus" scoped> @import '~styles/varibles.styl' .list display: flex list-style:none flex-direction: column justify-content: center position: absolute top: 1.58rem right: 0 bottom: 0 width: .4rem .item line-height: .44rem text-align: center color: $bgColor </style>目的:实现点击一个字母(alphabet.vue),将城市选择(cityList.vue)跳转到字母对应的区域
首先在字母的li上绑定一个事件
<li @click="handleLetterClick" >{{key}}</li>
实现这个方法
methods: { handleLetterClick: function (e) { this.$emit(‘change’, e.target.innerText) },
主页面中对这个组件进行监听
<city-alphabet :cities=“cities” @change=“handleLetterChange”>
实现handleLetterChange这个方法
handleLetterChange (letter) { this.letter = letter }
将letter传递给cityList.vue组件
<city-list :cities="cities" :hot="hotCities" :letter="letter"></city-list>
cityList.vue中进行接收
props: { hot: Array, cities: Object, letter: String },
利用侦听器进行侦听,若letter改变,则显示对应的区域
watch: { letter () { if (this.letter) { const element = this.$refs[this.letter][0] this.scroll.scrollToElement(element) } } }在li标签中进行显示
<div class="area" v-for="(item,key) of cities" :key="key" :ref="key"> <div class="title border-topbottom">{{key}}</div> //key中的值为A,B,C,D... <div class="item-list"> <div class="item border-bottom" v-for="innerItem of item" :key="innerItem.id" > {{innerItem.name}} //为具体的城市名称 </div> </div> </div>目的:实现在字母表(alphabet.vue)上滑动,将城市选择(cityList.vue)跳转到字母对应的区域
在li标签中绑定一个事件<li class="item" v-for="item of letters" :key="item" :ref="item" @touchstart.prevent="handleTouchStart" @touchmove="handleTouchMove" @touchend="handleTouchEnd">{{item}}</li>
实现对应的三个方法 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 } }, updated () { //当页面的数据被更新,页面完成了渲染 this.startY = this.$refs['A'][0].offsetTop //A距离顶部的高度 }, methods: { handleTouchStart () { this.touchStatus = true }, handleTouchMove (e) { if (this.touchStatus) { if (this.timer) { //提高效率,避免过于频繁的刷新 clearTimeout(this.timer) } this.timer = setTimeout(() => { const touchY = e.touches[0].clientY - 79 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 } }Search.vue接收来自主页面的cities这个参数,并设置一个div,在查询内容不为空的条件下(v-show)显示,展示查询结果。
此时city与home,没有公用的组件,数据传递可以适用bus或者vuex。vuex是传递大量数据的数据框架
npm install vuex --save
新建src/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: state, mutations: mutations, getters: { doubleCity (state) { return state.city + ' ' + state.city } } }) 在main.js中引入import store from ‘./store’
new Vue({ el: '#app', store: store, router, components: { App }, template: '<App/>' }) 在Header.vue中进行操作,将显示城市的内容改为this. s t o r e . s t a t e . c i t y 之 所 以 每 个 页 面 都 可 以 使 用 store.state.city 之所以每个页面都可以使用 store.state.city之所以每个页面都可以使用store,是因为他在主js中引入,之后被派发到每一个页面
在城市选择页面List.vue,将当前城市改为this.$store.state.city
实现点击一个城市名,则将首页的城市显示为该城市名 methods: { handleCityClick (city) { this.changeCitys(city) this.$router.push('/') }, ...mapMutations(['changeCitys']) }, store/mutations.js export default { changeCitys (state, city) { state.city = city try { localStorage.city = city } catch (e) {} } } store/state.js let defaultCity = '上海' try { if (localStorage.city) { defaultCity = localStorage.city } } catch (e) { } export default { city: defaultCity }默认首页的城市为上海,当用户切换后,刷新页面会重新变为上海,
先引入 <script> import { mapState, mapGetters } from 'vuex'
this.$store.state.city 的写法优化
computed: { …mapState([‘city’]), } 使用时 {{this.city}}
this.$store.state.city 的写法优化2
computed: { …mapState({ currentCity: ‘city’ }) 使用时 {{this.currentCity}}
2.vuex中的getters
类似于computed的作用,可以使用数据计算数据
axios.get(’/api/index.json?city=’ + this.city).then(this.getHomeInfoSucc)
引入city这个参数的方法是vuex
import { mapState } from ‘vuex’computed: { …mapState([‘city’]) },此时由于包含在keep-alive中并不会重新发送请求,可以使用activated,他在页面每次重新显示的时候被加载
activated () { if (this.lastCity !== this.city) { this.lastCity = this.city this.getHomeInfo() //这个方法发送ajax请求 } },<router-link tag="li" :to=" '/detail/'+ item.id">
在index.js中加入该路由信息(带有参数) { path: '/detail/:id', name: 'Detail', component: Detail } 实现文字区域背景图片的渐变色background-image: linear-gradient(top, rgba(0, 0, 0, 0),rgba(0, 0, 0, 0.8))
detail/banner.vue
<template> <div> <div class="banner" @click="handleBannerClick"> <img class="banner-img" :src="bannerImg"> <div class="banner-info" > <div class="banner-title">{{this.sightName}}</div> <div class="banner-number"><span class="iconfont banner-icon"></span>{{this.bannerImgs.length}}</div> </div> </div> <fade-animation> <common-gallary :imgs="bannerImgs" v-show="showGallary" @close="handleGallaryClose"></common-gallary> </fade-animation> </div> </template> <script> import commonGallary from 'common/gallary/Gallary' import FadeAnimation from 'common/fade/Fade' export default { name: 'DetailBanner', props: { sightName: String, bannerImg: String, bannerImgs: Array }, data () { return { showGallary: false } }, methods: { handleBannerClick () { this.showGallary = true }, handleGallaryClose () { this.showGallary = false } }, components: { commonGallary, FadeAnimation } } </script> <style lang="stylus" scoped> .banner position: relative overflow: hidden height: 0 padding-bottom: 51% .banner-img width: 100% .banner-info display: flex position: absolute left: 0 right: 0 bottom: 0 line-height: .6rem color: #fff background-image: linear-gradient(top, rgba(0, 0, 0, 0),rgba(0, 0, 0, 0.8)) .banner-title flex:1 font-size: .32rem padding: 0 .2rem .banner-number margin-top: .14rem padding: 0 .4rem line-height: .32rem height: .32rem border-radius: .2rem background: rgba(0, 0, 0, .8) font-size: .24rem .banner-icon font-size: .24rem </style>common/gallary.vue
<template> <div class="container" @click='handleGallaryClick'> <div class="wrapper"> <swiper :options="swiperOptions"> <swiper-slide v-for="(item,index) in imgs" :key="index"> <img class="gallary-img" :src="item" /> </swiper-slide> <div class="swiper-pagination" slot="pagination"></div> </swiper> </div> </div> </template> <script> export default { name: 'CommonGallery', props: { imgs: { type: Array, default () { return ['https://img1.qunarzz.com/vs_ceph_vs_tts/4d217b8b-a26b-4c50-80ef-c37bcbb8005a.jpg', 'http://img1.qunarzz.com/vs_ceph_vs_tts/b60743c9-5a9c-407a-8e4f-a73344f63ffc.jpg_r_500x333x90_5a5cead1.jpg'] } } }, data () { return { swiperOptions: { pagination: '.swiper-pagination', paginationType: 'fraction', observeParents: true, observer: true } } }, methods: { handleGallaryClick () { this.$emit('close') } } } </script> <style lang="stylus" scoped> .container >>> .swiper-container overflow: inherit .container display: flex z-index: 99 position: fixed flex-direction: column justify-content: center left: 0 right: 0 top: 0 bottom: 0 background: #000 .wrapper height: 0 width: 100% padding-bottom: 100% .gallary-img width: 100% .swiper-pagination color: #fff bottom: -1rem </style>detail/header.vue
<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, opacityStyle: { opacity: 0 } } }, activated () { window.addEventListener('scroll', this.handleScroll) }, deactivated () { window.removeEventListener('scroll', this.handleScroll) }, 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 } } } } </script> <style lang="stylus" scoped> @import '~styles/varibles.styl' .header-abs position: absolute left: .2rem top: .2rem width: .8rem height: .8rem line-height: .8rem text-align: center border-radius: .4rem background: rgba(0,0,0,.8) .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 text-align: center color: #fff 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>window.removeEventListener(‘scroll’, this.handleScroll) 该方法是对全局的影响,使用deactivated()钩子进行消除影响
组件的name值,常用于递归组件的调用
<template> <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> </template> <script> export default { name: 'DetailList', props: { list: Array } } </script> <style lang="stylus" scoped> .item-title-icon position: relative left: .06rem top: .06rem display: inline-block width: .36rem height: .36rem background: url(http://s.qunarzz.com/piao/image/touch/sight/detail.png) 0 -.45rem no-repeat margin-right: .1rem background-size: .4rem 3rem .item-title line-height: .8rem font-size: .32rem padding: 0 .2rem .item-children padding: 0 .2rem </style>detail.vue中写了一些模拟数据
将detail/list的数据通过Ajax获取 detail.vue
<template> <div> <detail-banner :sightName="sightName" :bannerImg="bannerImg" :bannerImgs='gallaryImg' ></detail-banner> <detail-header></detail-header> <div class="content" > <detail-list :list="list"></detail-list> </div> </div> </template> <script> import detailBanner from './components/Banner' import DetailHeader from './components/Header' import DetailList from './components/List' import axios from 'axios' export default { name: 'Detail', components: { detailBanner, DetailHeader, DetailList }, data () { return { sightName: '', bannerImg: '', gallaryImg: [], list: [] } }, methods: { getDetailInfo () { // axios.get('/api/detail.json?id=' + this.$route.params.id) axios.get('/api/detail.json', { params: { id: this.$route.params.id } }).then(this.handleGetDataSucc) }, handleGetDataSucc (res) { res = res.data if (res.ret && res.data) { const data = res.data this.sightName = data.sightName this.bannerImg = data.bannerImg this.gallaryImg = data.gallaryImgs this.list = data.categoryList } } }, mounted () { this.getDetailInfo() } } </script> <style lang="stylus" scoped> .content height: 50rem </style>在router/index.js中添加scrollBahavior
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} } })