前端-Vuejs2.5開發去哪兒網
Vuejs開發去哪兒網
重要說明 -_-||
- 專案開始日期 2018-07-22,結束日期 2018-07-28
- 更換開發環境時,記得先npm install一下,再啟動(可能需要安裝新的依賴)
- 更換開發環境時,切換分支後,記得git pull(只pull分支節點是不夠的)
- 本專案新元件都在新分支開發,master主幹合併最新分支的的程式碼
- 開發工具SublimeText3 (調一下右下角的Tab鍵的設定)
- MarkDown中使用 > (>後啥也不寫)來實現一個格式分割問題(類似清除浮動)–似乎還有問題(使用3個空格可以分開上下連線的問題,有時不能使用>)
- MarkDown中好像必須在列表下才可能縮排
- 使用keep-alive元件後,可能導致F5不能載入最新的程式碼,直接重啟dev環境。keep-alive的exclude屬性會導致頁面中不再回調activated (),詳見本文第六節第9點的說明
TODO NEXT _
- 首頁banner圖下的圖示選單在小屏iPhone上有padding距離不正常的問題
- 首頁進入城市選擇頁面時,頁面從右向左轉場動畫效
連結 -_-||
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 [email protected]^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 [email protected] >> 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註冊
使用微博登入 [email protected]
選單 >> 圖示管理 >> 我的專案
下載後注意修改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 '[email protected]/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 [email protected] --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 '[email protected]/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
個人網站:www.jiangjiesheng.com
聯絡郵箱:[email protected]