Vue開(kāi)發(fā)旅游App

項(xiàng)目準(zhǔn)備
  1. 在碼云新建倉(cāng)庫(kù)travel
  2. 克隆到本地
  3. 在本地倉(cāng)庫(kù)所在目錄執(zhí)行
vue init webpack travel

選擇y表示繼續(xù)

樣式重置

index.html

<meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no" />
reset.css
  1. src/assets/styles目錄下存放reset.css
@charset "utf-8";
html {
  background-color: #fff;
  color: #000;
  font-size: 12px;
}

body,
ul,
ol,
dl,
dd,
h1,
h2,
h3,
h4,
h5,
h6,
figure,
form,
fieldset,
legend,
input,
textarea,
button,
p,
blockquote,
th,
td,
pre,
xmp {
  margin: 0;
  padding: 0;
}

body,
input,
textarea,
button,
select,
pre,
xmp,
tt,
code,
kbd,
samp {
  line-height: 1.5;
  font-family: tahoma, arial, 'Hiragino Sans GB', simsun, sans-serif;
}

h1,
h2,
h3,
h4,
h5,
h6,
small,
big,
input,
textarea,
button,
select {
  font-size: 100%;
}

h1,
h2,
h3,
h4,
h5,
h6 {
  font-family: tahoma, arial, 'Hiragino Sans GB', '微軟雅黑', simsun, sans-serif;
}

h1,
h2,
h3,
h4,
h5,
h6,
b,
strong {
  font-weight: normal;
}

address,
cite,
dfn,
em,
i,
optgroup,
var {
  font-style: normal;
}

table {
  border-collapse: collapse;
  border-spacing: 0;
  text-align: left;
}

caption,
th {
  text-align: inherit;
}

ul,
ol,
menu {
  list-style: none;
}

fieldset,
img {
  border: 0;
}

img,
object,
input,
textarea,
button,
select {
  vertical-align: middle;
}

article,
aside,
footer,
header,
section,
nav,
figure,
figcaption,
hgroup,
details,
menu {
  display: block;
}

audio,
canvas,
video {
  display: inline-block;
  *display: inline;
  *zoom: 1;
}

blockquote:before,
blockquote:after,
q:before,
q:after {
  content: '\0020';
}

textarea {
  overflow: auto;
  resize: vertical;
}

input,
textarea,
button,
select,
a {
  outline: 0 none;
  border: none;
}

button::-moz-focus-inner,
input::-moz-focus-inner {
  padding: 0;
  border: 0;
}

mark {
  background-color: transparent;
}

a,
ins,
s,
u,
del {
  text-decoration: none;
}

sup,
sub {
  vertical-align: baseline;
}

html {
  overflow-x: hidden;
  height: 100%;
  font-size: 50px;
  -webkit-tap-highlight-color: transparent;
}

body {
  font-family: Arial, 'Microsoft Yahei', 'Helvetica Neue', Helvetica, sans-serif;
  color: #333;
  font-size: 0.28em;
  line-height: 1;
  -webkit-text-size-adjust: none;
}

hr {
  height: 0.02rem;
  margin: 0.1rem 0;
  border: medium none;
  border-top: 0.02rem solid #cacaca;
}

a {
  color: #25a4bb;
  text-decoration: none;
}

  1. 在mian.js文件引入
import './assets/styles/reset.css'
解決移動(dòng)端1像素邊框問(wèn)題
  1. src/assets/styles目錄下存放border.css
  2. 在mian.js文件引入
import './assets/styles/border.css'
解決移動(dòng)端300ms點(diǎn)擊延遲

安裝fastclick

npm install fastclick --save

main.js

import fastClick from 'fastclick'
fastClick.attach(document.body)
字體圖標(biāo)

在iconfont新建項(xiàng)目

在項(xiàng)目中使用sass
npm install sass-loader node-sass --save-dev

注意sass-loader版本過(guò)高可能會(huì)報(bào)錯(cuò)

頁(yè)面組件化

將一個(gè)頁(yè)面拆分成多個(gè)組件
src/pages/home目錄下新建components目錄,然后新建Header.vue
引入
src/pages/home/home.vue

<template>
  <div><home-header></home-header></div>
</template>

<script>
import HomeHeader from './components/Header'
export default {
  name: 'Home',
  components: {
   //es6中鍵值相同可以省略值
    HomeHeader
  }
}
</script>
<style></style>

Vue自動(dòng)完成HomeHeader和小寫(xiě)的<home-header>的關(guān)聯(lián)

頁(yè)面元素高度問(wèn)題

由于移動(dòng)端一般使用雙倍像素肤晓,如果指定元素高度10px这刷,實(shí)際顯示為20px司草,所以實(shí)際指定高度應(yīng)為設(shè)計(jì)圖紙中的一半晴音。在實(shí)際開(kāi)發(fā)中哎甲,一般使用rem作為單位孝冒,我們可以指定html的font-size為50px洋幻,如果設(shè)計(jì)圖上某個(gè)元素高度65px,轉(zhuǎn)化為rem值為0.65rem(css中元素高度應(yīng)為設(shè)計(jì)圖上該元素高度的一半)

引入字體圖標(biāo)

在styles目錄新建iconfont,把從Iconfont下載的字體圖標(biāo)放進(jìn)去


iconfont.css在styles目錄下队秩,需要修改字體路徑笑旺,如

src: url('./iconfont/iconfont.eot?t=1584759696965');
main.js
import './assets/styles/iconfont.css'
使用圖標(biāo)
<span class="iconfont iconfanhui"></span>
使用scss變量

src/assets/styles目錄下新建_variables.scss,存放變量

// demo
$bgColor: #00bcd4;

在項(xiàng)目中引入
src/pages/home/components/Header.vue

<style lang="scss" scoped>
@import '~@/assets/styles/_variables';
...
</style>

注意@表示src目錄馍资,前面的~必須加上才不會(huì)報(bào)錯(cuò)

給路徑添加別名

給路徑添加別名好處是減少路徑長(zhǎng)度
build/webpack.base.conf.js

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

alias用于添加別名筒主,如果要讓styles指向assets/styles,只需添加

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

main.js導(dǎo)入樣式文件可以寫(xiě)成
main.js

import 'styles/reset.css'
import 'styles/border.css'
import 'styles/iconfont.css'
//import './assets/styles/iconfont.css'

組件中導(dǎo)入scss文件寫(xiě)成
src/pages/home/components/Header.vue

@import '~styles/_variables';

重啟服務(wù)器后生效

新建項(xiàng)目分支

在實(shí)際項(xiàng)目開(kāi)發(fā)過(guò)程中鸟蟹,每開(kāi)發(fā)一個(gè)新功能都會(huì)創(chuàng)建一個(gè)新的分支物舒,功能開(kāi)發(fā)完成之后再合并到主分支

//提交主分支
git add .
git commit -m 'header finished'
git push
//創(chuàng)建新分支
git checkout -b index-swiper
使用輪播插件
npm install swiper vue-awesome-swiper --save

main.js

import VueAwesomeSwiper from 'vue-awesome-swiper'

// import style
import 'swiper/css/swiper.css'

Vue.use(VueAwesomeSwiper, /* { default options with global component } */)
解決自動(dòng)輪播不生效問(wèn)題
swiperOptions: {
  observer: true, //修改swiper自己或子元素時(shí),自動(dòng)初始化swiper
  observeParents: true, //修改swiper的父元素時(shí)戏锹,自動(dòng)初始化swiper
  loop: true,
  autoplay: {
    delay: 3000
  },
  pagination: {
    el: '.pagination-home'
  }
  // Some Swiper option/callback...
},
解決頁(yè)面抖動(dòng)問(wèn)題

加載頁(yè)面時(shí)冠胯,圖片在文字之后加載,會(huì)造成文字剛開(kāi)始占用圖片的位置锦针,之后又被圖片擠開(kāi)荠察。
解決辦法:圖片外面加一層div

<div class="wrapper">
...
</div>
.wrapper {
  overflow: hidden;
  width: 100%;
  height: 0;
  //padding 百分比相對(duì)于父元素的寬度置蜀,這里是屏幕寬度,31.25%是圖片實(shí)際高度/圖片寬度
  padding-bottom: 31.25%;
  .swiper-img {
    width: 100%;
  }
}
上傳分支代碼
git add .
git commit -m 'swiper finished'
//第一次上傳分支
git push -u origin index-swiper
//git push
合并到主分支
//切換到主分支
git checkout master
//把線上分支合并到本地分支
git merge origin/index-swiper
//提交master分支
git push
文字超出部分顯示省略號(hào)

assets/styles文件夾下新建_mixin.scss悉盆,該文件主要寫(xiě)重復(fù)使用的樣式

@mixin ellipsis {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

使用

@import '~styles/mixin';
.icon-desc {
  @include ellipsis;
}

注意:1. 文件名以下劃線開(kāi)頭盯荤,但import時(shí)不帶下劃線。

  1. 如果是flex布局焕盟,且元素flex:1秋秤,加上min-width: 0;確保該子元素不超過(guò)外層容器
.item-info {
  flex: 1;
  padding: 0.1rem;
  min-width: 0;
}
發(fā)送ajax請(qǐng)求
npm install axios --save

一個(gè)頁(yè)面可能有多個(gè)組件組成,如果每個(gè)組件都需要獲取服務(wù)器的數(shù)據(jù)脚翘,這樣效率比較低下灼卢,推薦的做法是在父組件發(fā)送請(qǐng)求。
Home.vue

import axios from 'axios'

created() {
  this.getHomeInfo()
},
methods: {
//模擬請(qǐng)求數(shù)據(jù)
  getHomeInfo() {
    axios.get('/api/index.json').then(this.getHomeInfoSucc)
  },
  getHomeInfoSucc(res) {
    console.log(res)
  }
}
mock

在沒(méi)有后端支持情況下来农,需要axios模擬請(qǐng)求數(shù)據(jù)鞋真,這就要用到mock
在static文件夾下新建mock目錄,所有請(qǐng)求的數(shù)據(jù)放在該文件夾下沃于。
static/mock/index.json

{
  "status": "ok"
}

修改Home.vue

getHomeInfo() {
  axios.get('/static/mock/index.json').then(this.getHomeInfoSucc)
},

static目錄下的文件可以在瀏覽器通過(guò)路徑直接訪問(wèn)涩咖,一般我們不希望其發(fā)布到線上,可以為其添加gitignore
.gitignore

static/mock
配置轉(zhuǎn)發(fā)

需求:經(jīng)過(guò)上面的步驟繁莹,我們已經(jīng)可以通過(guò)獲得數(shù)據(jù)檩互,但也引入另一個(gè)問(wèn)題,即服務(wù)器端api地址和模擬請(qǐng)求的地址不一致咨演,項(xiàng)目上線時(shí)需要考慮api地址變更闸昨,webpack提供了解決這個(gè)問(wèn)題的辦法。
config/index.js

  dev: {
    // Paths
    assetsSubDirectory: 'static',
    assetsPublicPath: '/',
   //修改部分
    proxyTable: {
      '/api': {
        target: 'http://localhost:8080',
        pathRewrite: {
          // 請(qǐng)求以/api開(kāi)頭的轉(zhuǎn)發(fā)到/static/mock
          '^/api': '/static/mock'
        }
      }
    },
...
}

proxyTable下配置路由轉(zhuǎn)發(fā)
現(xiàn)在已經(jīng)可以通過(guò)/api/訪問(wèn)接口了

getHomeInfo() {
  axios.get('/api/index.json').then(this.getHomeInfoSucc)
}
解決組件渲染沒(méi)有數(shù)據(jù)問(wèn)題
<swiper :options="swiperOptions" v-if="swiperList.length">
  <swiper-slide v-for="item of swiperList" :key="item.id"><img class="swiper-img" :src="item.imgUrl" alt="" /> </swiper-slide>
  <div class="swiper-pagination" slot="pagination"></div>
</swiper>

swiper創(chuàng)建時(shí)數(shù)據(jù)還沒(méi)有生成雪标,這個(gè)時(shí)候需要加上v-if="swiperList.length"表示獲得數(shù)據(jù)之后才會(huì)渲染這個(gè)組件
改進(jìn):雖然這樣可以解決問(wèn)題零院,但我們建議模板中盡可能減少邏輯代碼的出現(xiàn)溉跃。使用computed來(lái)解決這個(gè)問(wèn)題

computed: {
  showSwiper() {
    return this.swiperList.length
  }
}

修改tempate

<swiper :options="swiperOptions" v-if="showSwiper">
...
</swiper>

優(yōu)化滑動(dòng)
npm install better-scroll --save

使用該組件需要dom結(jié)構(gòu)滿足一定條件

<div class="list" ref="wrapper">
  <div>
    <div></div>
    <div></div>
    <div></div>
  </div>
</div>
import BScroll from 'better-scroll'

mounted() {
  this.scroll = new BScroll(this.$refs.wrapper)
}
實(shí)現(xiàn)父子組件聯(lián)動(dòng)

gif

Alphabet.vue(子組件)

<template>
  <ul class="list">
    <li
      class="item"
      v-for="item of letters"
      :key="item"
      @click="handleLetterClick"
      @touchstart.stop.prevent="handleTouchStart"
      @touchmove.stop.prevent="handleTouchMove"
      @touchend.stop.prevent="handleTouchEnd"
      :ref="item"
    >
      {{ item }}
    </li>
  </ul>
</template>
  1. @click="handleLetterClick"監(jiān)聽(tīng)點(diǎn)擊了哪個(gè)letter村刨,通知父組件letter改變,父組件再通知city-list組件letter改變(借助父組件實(shí)現(xiàn)兄弟組件通信)
  2. touchstart撰茎、touchmove嵌牺、touchend監(jiān)聽(tīng)觸摸事件
updated() {
  // update在獲取數(shù)據(jù)后被執(zhí)行
  // 獲取'A'元素距離其父元素上邊緣的距離
  this.startY = this.$refs['A'][0].offsetTop
},
methods: {
  handleLetterClick(e) {
    this.$emit('change', e.target.innerText)
  },
  handleTouchStart() {
    this.touchStatus = true
  },
  handleTouchMove(e) {
    if (this.touchStatus) {
      // e.touches[0].clientY表示鼠標(biāo)指針距屏幕頂部的距離
      // 79為導(dǎo)航欄的高度
      const touchY = e.touches[0].clientY - 79
      // 鼠標(biāo)經(jīng)過(guò)字符下標(biāo)
      const index = Math.floor((touchY - this.startY) / 20)
      if (index >= 0 && index < this.letters.length) {
        this.$emit('change', this.letters[index])
      }
    }
  },
  handleTouchEnd() {
    this.touchStatus = false
  }
}

City.vue(父組件)

<city-list :cities="cities" :hotCities="hotCities" :letter="letter"></city-list>
<city-alphabet :cities="cities" @change="handleLetterChange"></city-alphabet>

handleLetterChange(letter) {
  this.letter = letter
}

List.vue(子組件)

watch: {
  letter(newVal, oldVal) {
    if (newVal) {
      const element = this.$refs[newVal][0]
      this.scroll.scrollToElement(element)
    }
  }
}

監(jiān)聽(tīng)父組件傳過(guò)來(lái)的letter的變化,滾動(dòng)到屏幕相應(yīng)位置
優(yōu)化:手指移動(dòng)的速度很快龄糊,可以通過(guò)設(shè)置定時(shí)函數(shù)延遲響應(yīng)滑動(dòng)事件

搜索

Search.vue

<template>
  <div>
    <div class="search">
      <input v-model="keyword" class="search-input" type="text" placeholder="輸入城市名或拼音" />
    </div>
    <!-- keyword有值時(shí)顯示 -->
    <div class="search-content" ref="search" v-show="keyword">
      <ul>
        <li class="serach-item" v-for="(item, index) of list" :key="index">{{ item.name }}</li>
        <!-- 沒(méi)有查詢結(jié)果時(shí)顯示 -->
        <li class="serach-item" v-show="hasNoData">沒(méi)有找到匹配數(shù)據(jù)</li>
      </ul>
    </div>
  </div>
</template>
computed: {
 //判斷是否有數(shù)據(jù)
  hasNoData() {
    return !this.list.length
  }
},
watch: {
  keyword(newVal, oldVal) {
    if (this.timer) {
      clearTimeout(this.timer)
    }
    // 關(guān)鍵字為空逆粹,關(guān)閉搜索結(jié)果頁(yè)
    if (!newVal) {
      this.list = []
      return
    }
    // 監(jiān)聽(tīng)關(guān)鍵字變化,顯示查詢
    const result = []
    for (let i in this.cities) {
      this.cities[i].forEach(value => {
        if (value.spell.indexOf(newVal) > -1 || value.name.indexOf(newVal) > -1) {
          result.push(value)
        }
      })
    }
    this.list = result

    // this.timer = setTimeout(() => {
    //   const result = []
    //   for (let i in this.cities) {
    //     this.cities[i].forEach(value => {
    //       if (value.spell.indexOf(newVal) > -1 || value.name.indexOf(newVal) > -1) {
    //         result.push(value)
    //       }
    //     })
    //   }
    //   this.list = result
    // }, 100)
  }
},
沒(méi)有共同父組件的組件通信Vuex
npm install vuex --save

main.js

//store
//import會(huì)自動(dòng)尋找目錄下的index.js
import store from './store'

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

src/store/index.js

import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
  state: {
    city: '北京'
  },
  // actions調(diào)用mutation改變數(shù)據(jù)
  actions: {
    changeCity(ctx, city) {
      // 調(diào)用mutations中changeCity的方法
      ctx.commit('changeCity', city)
    }
  },
  mutations: {
    changeCity(state, city) {
      state.city = city
    }
  }
})

獲取store中的數(shù)據(jù)

<div class="header-right">
  {{ this.$store.state.city }}
  <span class="iconfont iconjiantouxia arrow-icon"></span>
</div>

觸發(fā)改變store中的數(shù)據(jù)的函數(shù)

<div class="button-wrapper" v-for="item of hotCities" :key="item.id" @click="handleCityClick(item.name)">


handleCityClick(city) {
  // 派發(fā)changeCity的Action
  this.$store.dispatch('changeCity', city)
  // 不經(jīng)過(guò)action的寫(xiě)法
  this.$store.commit('changeCity', city)
}
頁(yè)面跳轉(zhuǎn)
  1. 鏈接式
<router-link to="/"></router-link>
  1. 編程式
this.$router.push('/')
router-link
<router-link :to="'/detail/' + item.id" tag="li" class="item" v-for="item of recommendList" :key="'recommend' + item.id">
...
</router-link>

<router-link>標(biāo)簽?zāi)J(rèn)情況下渲染為一個(gè)a標(biāo)簽炫惩,可以通過(guò)增加tag屬性指定希望渲染成的標(biāo)簽僻弹,如tag="li"會(huì)最終渲染為li標(biāo)簽

路由傳遞參數(shù)

router/index.js

export default new Router({
  routes: [
...
    {
      path: '/detail/:id',
      name: 'Detail',
      component: Detail
    }
  ]
})

Recommend.vue

<router-link :to="'/detail/' + item.id" tag="li" class="item" v-for="item of recommendList" :key="'recommend' + item.id">
...
</router-link>
全局組件

在src目錄下新建common目錄,用于存放全局公用組件
修改webpack.base.conf.js
增加common的別名

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

遇到組件不能覆蓋另一組件的樣式他嚷,就需要用樣式穿透蹋绽,在scss中可以用>>>或者/deep/

.container >>> .swiper-container {
  overflow: inherit;
}
props

props里的屬性的默認(rèn)值可以是函數(shù)

props: {
  imgs: {
    type: Array,
    default() {
      return ['a','b']
    }
  }
}
解決swiper插件顯示問(wèn)題

如圖芭毙,swiper不能顯示正確的下標(biāo),是因?yàn)殇秩具@個(gè)組件后其父元素是隱藏的卸耘,當(dāng)它顯示時(shí)就會(huì)出現(xiàn)這個(gè)問(wèn)題退敦,解決辦法是在讓swiper監(jiān)聽(tīng)父元素變化

data() {
  return {
    swiperOptions: {
      pagination: {
        el: '.swiper-pagination',
        type: 'fraction'
      },
    // 監(jiān)聽(tīng)父元素變化
      observeParents: true,
      observer: true,

      loop: true

      // Some Swiper option/callback...
    }
  }
}
Vue點(diǎn)擊事件
實(shí)現(xiàn)效果

我們要實(shí)現(xiàn)點(diǎn)擊黑色區(qū)域返回首頁(yè),而點(diǎn)擊圖片不做任何響應(yīng)

<div class="container" @click.self.prevent="handleGalleryClick">
...
</div>

@click.self.prevent表示只監(jiān)聽(tīng)元素自身的點(diǎn)擊事件蚣抗,@click.prevent.self剛好相反侈百,點(diǎn)擊自身無(wú)效,而點(diǎn)擊其子元素會(huì)被監(jiān)聽(tīng)到翰铡。

監(jiān)聽(tīng)屏幕滾動(dòng)事件
methods: {
  handleScroll() {
    // 滾動(dòng)的距離
    const top = document.documentElement.scrollTop

    // 距離頂部60開(kāi)始隱藏
    if (top > 60) {
      let opacity = top / 140
      opacity = opacity > 1 ? 1 : opacity
      this.showAbs = false
      this.opacityStyle = {
        opacity
      }
    } else {
      this.showAbs = true
    }
  }
},
mounted() {
  window.addEventListener('scroll', this.handleScroll)
}

監(jiān)聽(tīng)屏幕事件必須在mounted()方法中寫(xiě)window.addEventListener('scroll', this.handleScroll)表示監(jiān)聽(tīng)屏幕滾動(dòng)钝域,并指定處理函數(shù)

對(duì)全局事件的解綁(重要)

在我們編寫(xiě)代碼的過(guò)程中,如果沒(méi)有對(duì)事件進(jìn)行及時(shí)的解綁两蟀,可能影響程序性能或造成錯(cuò)誤网梢。如下所示,我們?cè)谝粋€(gè)組件監(jiān)聽(tīng)window對(duì)象赂毯,當(dāng)這個(gè)組件銷毀后战虏,這個(gè)監(jiān)聽(tīng)仍在繼續(xù)。這顯然是不合理的党涕。

mounted() {
  window.addEventListener('scroll', this.handleScroll)
}

改進(jìn):在組件銷毀前解綁事件

mounted() {
  window.addEventListener('scroll', this.handleScroll)
},
beforeDestroy() {
  window.removeEventListener('scroll', this.handleScroll)
}
使用遞歸組件

在組件內(nèi)部調(diào)用組件自身
使用遞歸組件必須滿足一個(gè)條件烦感,即組件需定義好了name屬性,根據(jù)name可以調(diào)用自身

<template>
  <div>
    <div class="item" v-for="(item, index) of list" :key="index">
      <div class="item-title">
        <span class="item-title-icon"></span>
        {{ item.title }}
      </div>
      <div v-if="item.children">
        <detail-list :list="item.children"></detail-list>
      </div>
    </div>
  </div>
</template>
export default {
  name: 'DetailList',
  props: {
    list: {
      type: Array
    }
  }
}
組件緩存

如果我們不希望某些頁(yè)面被緩存下來(lái)膛堤,而是每次進(jìn)入都重新獲取數(shù)據(jù)

<template>
  <div id="app">
    <keep-alive exclude="Detail">
      <router-view />
    </keep-alive>
  </div>
</template>

exclude="Detail"name=Detail的組件排除在緩存之外

小結(jié):組件name的三個(gè)用途
  1. 用于遞歸組件
  2. 用于Vue調(diào)試工具
  3. 用于keep-alive排除組件緩存
解決打開(kāi)頁(yè)面時(shí)頁(yè)面停留在底部

router/index.js
scrollBehavior函數(shù)會(huì)在每次切換頁(yè)面時(shí)執(zhí)行

export default new Router({
  routes: [
    {
      path: '/',
      name: 'Home',
      component: Home
    },
...
  ],
  // 當(dāng)進(jìn)行頁(yè)面切換時(shí)手趣,x、y軸初始是0
  scrollBehavior(to, form, savedPosition) {
    return { x: 0, y: 0 }
  }
})
添加動(dòng)畫(huà)

common目錄下新建fade目錄
common/fade/Fade.vue

<template>
  <transition>
    <slot></slot>
  </transition>
</template>

<script>
export default {
  name: 'Fade'
}
</script>

<style lang="scss" scoped>
.v-enter,
.v-leave-to {
  opacity: 0;
}
.v-enter-active,
.v-leave-active {
  transition: opacity 0.5s;
}
</style>

<transition>包裹需要?jiǎng)赢?huà)效果的元素肥荔,Vue會(huì)自動(dòng)添加一些類名
使用

<fade-animation>
  <common-gallery :imgs="bannerImgs" v-show="showGallery" @close="handleGalleryClose"></common-gallery>
</fade-animation>
Vue項(xiàng)目的接口聯(lián)調(diào)

config/index.js

  dev: {
    // Paths
    assetsSubDirectory: 'static',
    assetsPublicPath: '/',
    proxyTable: {
      '/api': {
        // target: 'http://localhost:8080',
        target: 'http://localhost',
        // pathRewrite: {
          // 請(qǐng)求以/api開(kāi)頭的轉(zhuǎn)發(fā)到/static/mock
          // '^/api': '/static/mock'
        // }
      }
    },
...
}

'/api':開(kāi)頭的請(qǐng)求會(huì)被轉(zhuǎn)發(fā)到本地80端口绿渣,localhost在上線階段應(yīng)更改為服務(wù)器地址或域名

Vue項(xiàng)目的測(cè)試

ifconfig

inet 192.168.1.103

該地址是本機(jī)在內(nèi)網(wǎng)的ip地址
假設(shè)我們的Vue項(xiàng)目運(yùn)行在8080端口,當(dāng)訪問(wèn)192.168.1.103:8080時(shí)燕耿,會(huì)訪問(wèn)失敗中符,因?yàn)閣ebpack屏蔽了通過(guò)ip地址訪問(wèn),如果要解除這個(gè)限制誉帅,修改package.json文件淀散,加上--host 0.0.0.0

"scripts": {
  "dev": "webpack-dev-server --host 0.0.0.0 --inline --progress --config build/webpack.dev.conf.js",
  "start": "npm run dev",
  "lint": "eslint --ext .js,.vue src",
  "build": "node build/build.js"
},

這個(gè)時(shí)候,如果手機(jī)和電腦在同一局域網(wǎng)下蚜锨,就可以通過(guò)192.168.1.103:8080訪問(wèn)頁(yè)面了

解決拖動(dòng)字母屏幕跟著滑動(dòng)

Alphabet.vue

@touchstart.prevent="handleTouchStart"

.prevent可以解決這個(gè)問(wèn)題

解決瀏覽器不支持promise
npm install babel-polyfill --save

main.js

import 'babel-polyfill'
Vue項(xiàng)目的打包上線
  1. 運(yùn)行打包命令
npm run build

在項(xiàng)目根目錄會(huì)自動(dòng)生成dist文件夾档插,該文件夾的內(nèi)容是項(xiàng)目最終上線放到服務(wù)器的內(nèi)容
如果不想生成map文件,修改config/index.js

productionSourceMap: false,

重新打包即可
2 . 將dist文件夾下內(nèi)容上傳至服務(wù)器
將dist文件夾下的內(nèi)容放到服務(wù)器網(wǎng)站目錄根路徑下


目錄結(jié)構(gòu)
  1. api是后端代碼
  2. static亚再、index.html是打包后dist文件夾的內(nèi)容

這個(gè)時(shí)候郭膛,網(wǎng)站可以通過(guò)localhost(本地配置了php或nginx)或服務(wù)器ip訪問(wèn)

通過(guò)ip加訪問(wèn)路徑訪問(wèn)

我們希望通過(guò)ip地址/project訪問(wèn)服務(wù)器,需要做如下修改

  1. 修改webpack配置assetsPublicPath: '/project',
    config/index.js
build: {
  // Template for index.html
  index: path.resolve(__dirname, '../dist/index.html'),

  // Paths
  assetsRoot: path.resolve(__dirname, '../dist'),
  assetsSubDirectory: 'static',
  // assetsPublicPath: '/',
  assetsPublicPath: '/project',
...
}
  1. 網(wǎng)站根目錄下新建project目錄
  2. 重新打包氛悬,把dist文件夾下的文件放到剛才新建的project目錄
異步組件

打包生成的app.js包含所有頁(yè)面的業(yè)務(wù)邏輯代碼则剃,默認(rèn)情況下它會(huì)在瀏覽器第一次請(qǐng)求頁(yè)面時(shí)全部加載凄诞,顯然這樣不是很合理,我們希望訪問(wèn)某個(gè)頁(yè)面時(shí)忍级,只加載這個(gè)頁(yè)面的邏輯

  1. router/index.js
export default new Router({
  routes: [
    {
      path: '/',
      name: 'Home',
      component: () => import('@/pages/home/Home')
    },
    {
      path: '/city',
      name: 'City',
      component: () => import('@/pages/city/City')
    },
    {
      path: '/detail/:id',
      name: 'Detail',
      component: () => import('@/pages/detail/Detail')
    }
  ],
  // 當(dāng)進(jìn)行頁(yè)面切換時(shí)帆谍,x、y軸初始是0
  scrollBehavior(to, form, savedPosition) {
    return { x: 0, y: 0 }
  }
})
  1. 組件中異步加載其他組件
components: {
  HomeHeader: () => import('./components/Header'),
...
},

component: () => import('@/pages/home/Home')解決了按需加載問(wèn)題
使用異步組件需要考慮的問(wèn)題:

  1. 異步組件可以減少首次加載時(shí)間轴咱,但在app.js文件本身較小時(shí)效果不明顯
  2. 異步組件缺點(diǎn)是會(huì)增加網(wǎng)絡(luò)請(qǐng)求

考慮以上因素汛蝙,在app.js文件較小時(shí)不使用異步組件

后續(xù)

Vue插件、自定義指令
vue-router路由守衛(wèi)等
vuex
Vue服務(wù)器端渲染
Vue插件
官方
第三方整理

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末朴肺,一起剝皮案震驚了整個(gè)濱河市窖剑,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌戈稿,老刑警劉巖西土,帶你破解...
    沈念sama閱讀 217,826評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異鞍盗,居然都是意外死亡需了,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)般甲,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)肋乍,“玉大人,你說(shuō)我怎么就攤上這事敷存∧乖欤” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,234評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵锚烦,是天一觀的道長(zhǎng)觅闽。 經(jīng)常有香客問(wèn)我,道長(zhǎng)涮俄,這世上最難降的妖魔是什么蛉拙? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,562評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮禽拔,結(jié)果婚禮上刘离,老公的妹妹穿的比我還像新娘室叉。我一直安慰自己睹栖,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,611評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布茧痕。 她就那樣靜靜地躺著野来,像睡著了一般。 火紅的嫁衣襯著肌膚如雪踪旷。 梳的紋絲不亂的頭發(fā)上曼氛,一...
    開(kāi)封第一講書(shū)人閱讀 51,482評(píng)論 1 302
  • 那天豁辉,我揣著相機(jī)與錄音,去河邊找鬼舀患。 笑死徽级,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的聊浅。 我是一名探鬼主播餐抢,決...
    沈念sama閱讀 40,271評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼低匙!你這毒婦竟也來(lái)了旷痕?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,166評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤顽冶,失蹤者是張志新(化名)和其女友劉穎欺抗,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體强重,經(jīng)...
    沈念sama閱讀 45,608評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡绞呈,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,814評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了间景。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片报强。...
    茶點(diǎn)故事閱讀 39,926評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖拱燃,靈堂內(nèi)的尸體忽然破棺而出秉溉,到底是詐尸還是另有隱情,我是刑警寧澤碗誉,帶...
    沈念sama閱讀 35,644評(píng)論 5 346
  • 正文 年R本政府宣布召嘶,位于F島的核電站,受9級(jí)特大地震影響哮缺,放射性物質(zhì)發(fā)生泄漏弄跌。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,249評(píng)論 3 329
  • 文/蒙蒙 一尝苇、第九天 我趴在偏房一處隱蔽的房頂上張望铛只。 院中可真熱鬧,春花似錦糠溜、人聲如沸淳玩。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,866評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)蜕着。三九已至,卻和暖如春红柱,著一層夾襖步出監(jiān)牢的瞬間承匣,已是汗流浹背蓖乘。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,991評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留韧骗,地道東北人嘉抒。 一個(gè)月前我還...
    沈念sama閱讀 48,063評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像袍暴,于是被迫代替她去往敵國(guó)和親众眨。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,871評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容