生活少不了趣味,趣味活在人間。每一天都是快樂的,happy every day.
前言
各位掘友,又是新的一週,你們週末過得很happy吧!週末,小編利用閒暇的時間,開源一個趣味生活小型vue專案。整個小專案做下來之後還真收穫不少呢。
- vue-cli2腳手架
- vue、vue-router(專案較小不使用vuex,但為後期做了些鋪墊)
- axios
- fastclick
- better-scroll
- stylus
- 阿里巴巴向量圖示庫-提供一些圖示icon
- vsCode
- 資料來源:阿凡達雲資料平臺
你將學到:
- 封裝axios
- 跨域
- 元件化程式設計
趣味生活專案實現過程
專案預期效果,小編帶大家瞅瞅!順便,隨手點贊支援下作者?
實現思路
- 階段一:實現【我的】及【tabBar】部分
- 階段二:實現【趣圖】部分 & axios封裝 & 跨域
- 階段三:實現【推薦】部分 & 跨域
階段一
在階段一中,主要難點設計是:在於tabBar和headerBar部分,因為你要考慮怎麼讓兩個導航欄同時在一個頁面出現吶而且還不會影響icon的顏色問題。
所以,此時你就需要想到使用 元件化 來解決 tabBar和headerBar共處問題。過程如下:
首先,將tabBar單獨拿出來做成一個元件,如果你覺得tabBar需要更靈活些,你可以在父元件中動態繫結資料傳值處理。具體tab.vue元件設計如下:
<template> <div class="tab"> <router-link to="/recommend" tag="div" class="tab-item"> <div class="icon"></div> <div class="tab-link">推薦</div> </router-link> <router-link to="/img" tag="div" class="tab-item"> <div class="icon"></div> <div class="tab-link">趣圖</div> </router-link> <router-link to="/mine" tag="div" class="tab-item"> <div class="icon"></div> <div class="tab-link">我的</div> </router-link> </div></template><script>export default {}</script><style lang="stylus" scoped>@import '../assets/css/function.styl'.tab display flex position absolute z-index 150 bottom 0 left 0 width 100% background-color #fff border-1px(#F5F5F5) font-size 12px height px2rem(140px) &-item flex 1 text-align center .icon margin-top px2rem(15px) .tab-link padding-bottom 5px color #000 &.router-link-active .tab-link color #1E90FF .icon color #1E90FF</style>複製程式碼
在父元件中使用如下:
<template> <div id="app"> <!-- tabBar --> <v-tab></v-tab> <router-view/> </div></template><script>import tab from '@/components/tab'export default { name: 'App', components: { 'v-tab': tab }}</script><style></style>複製程式碼
by the way,為啥tab.vue放在App.vue中呢?這裡當然是為了讓每個頁面都可以通過點選相應的tabBar進行跳轉啦。
我的部分
各位,這裡需要注意啦!,在mine.vue中也有一個headerBar,該欄是是什麼用途吶?如下圖:
在【我的】-的headerBar其作用就是為了,擴充套件在【我的】頁面中的子頁面跳轉,比如【設定】、【訊息】等功能模組。headerTab設計具體如下:
<template> <div class="mHeader"> <div class="mHeader-icon" @click="leftEvent"> <slot name="left-icon"></slot> </div> <div class="mHeader-cont"> <slot name="content"></slot> </div> <div class="mHeader-icon"> <slot name="right-icon"></slot> </div> </div></template><script>export default { name: 'hd', data () { return { } }, methods: { leftEvent () { this.$store.dispatch('setShowSidebar', true) } }}</script><style lang="stylus">@import "../assets/css/function" .mHeader height px2rem(88px) line-height px2rem(88px) display flex align-items center justify-content space-between color #746ca8 font-size px2rem(30px) &-icon flex 0 0 px2rem(88px) margin-top px2rem(6px) cursor pointer .icon font-size px2rem(48px) .left margin-left px2rem(20px)</style>複製程式碼
此外,需要再提到的一點就是:<slot name="left-icon"></slot>的具名使用,和不具名使用,當你元件中只有一塊內容需要父元件來填充時,你就是用不具名slot,當元件中需要填充多塊內容時,你就使用具名slot。案例如下:
父元件具名使用slot:
<!-- 頭部 --> <v-header> <i class="icon" slot="left-icon"></i> <span slot="content">我的音樂</span> <router-link to="/user" slot="right-icon"> <i class="icon"></i> </router-link> </v-header>複製程式碼
封裝的header元件的slot:
<template> <div class="header"> <div class="header-icon" @click="leftEvent"> <slot name="left-icon"></slot> </div> <div class="header-cont"> <slot name="content"></slot> </div> <div class="header-icon"> <slot name="right-icon"></slot> </div> </div></template>複製程式碼
階段二
在階段二中主要有兩個重點一個技巧內容,其一就是: axios封裝;其二就是:跨域資料請求;其三就是:藉助better-scroll下拉頁面載入新資料實現的技巧
axios封裝
為什麼做axios封裝技術封裝,不用問,問就是為了開發方便,以及統一化管理資料請求介面,而且方便後期維護專案。
那麼如何來封裝axios吶?很簡單的!
第一步,在專案的src下新建一個api資料夾,並且新建一個index.js檔案;第二步,匯入Vue和axios;第三步,new 一個 Vue例項;第四步配置axios具體配置如下:
// axios 配置axios.defaults.timeout = 10000// 請求不能超時10S// axios.defaults.baseURL = '/api'axios.defaults.headers.post['Content-Type'] = 'application/json';// 判斷返回狀態, 響應攔截axios.interceptors.response.use((res) => { // 請求不成功時 if (res.status !== 200) { alert('網路異常') // Promise 有兩個引數 reject, resolve return Promise.reject(res) } return res}, (error) => { alert('伺服器開小差了') return Promise.reject(error)})// 請求攔截 - 暫時不寫// ...複製程式碼
第五步,匯出你需要請求的資料api方法,具體邏輯如下:
export function fetchGet (url, param) { return new Promise((resolve, reject) => { axios.get(url, { params: param }) .then(response => { resolve(response.data) }, err => { reject(err) }) .catch((error) => { reject(error) }) })}export default { // 按更新時間查詢笑話 GetJokeByTime (params) { return fetchGet ('/api/Joke/QueryJokeByTime', params) }, // 最新笑話 NewJoke (params) { return fetchGet ('/api/Joke/NewstJoke', params) }, // 按更新時間查詢趣圖 GetImgByTime (params) { return fetchGet ('/api/Joke/QueryImgByTime', params) }, // 最新趣圖 NewImg (params) { return fetchGet ('/api/Joke/NewstImg', params) }, HeadLine (params) { return fetchGet ('/api//TouTiao/Query', params) },}複製程式碼
以上便完成了axios封裝,下面說下在元件中封裝後的使用
在你需要使用axios封裝的api的元件中,匯入,並直接通過api.某方法直接呼叫,具體如下:
<script>import api from '@/api...此處省略一萬行
methods: { _getNewJoke () { const params = { key: '991792145f62460bac35b1c92ee50cdb', page: this.page, rows: 20 } api.NewJoke(params) .then((res) => { console.log(res) if (res.error_code === 0) { this.result = res } }) } }...此處省略一萬行
</script>
複製程式碼
跨域資料請求
隨著前後端分離技術的越來越盛行,跨域問題也逐漸凸顯了出來。跨域問題的根本原因:因為瀏覽器收到同源策略的限制,當前域名的js只能讀取同域下的視窗屬性。什麼叫做同源策略?就是不同的域名, 不同埠, 不同的協議不允許共享資源的,保障瀏覽器安全。同源策略是針對瀏覽器設定的門檻。如果繞過瀏覽就能實現跨域,所以說早期的跨域都是打著安全路數的擦邊球,都可以認為是 hack 處理
網上有很多跨域請求得文章,小編就不多介紹,此次只介紹Vue的域名代理來解決跨域問題。想更深入學習跨域知識,小編推薦幾款平臺:掘金、思否、簡書、CSDN等。
廢話不多說,來看怎麼通過vue域名代理來解決跨域問題吧。首先找到你的vue專案的config資料夾,在找到該資料夾下的index.js檔案,使用Ctrl+F快速搜尋proxyTable,在該項進行域名代理配置具體配置如下:
proxyTable: { '/api': { target: 'http://api.avatardata.cn', changeOrigin: true, pathRewrite: { '^/api': '' } } // 第二個域名代理 // '/top': { // target: 'https://www.toutiao.com/', // changeOrigin: true, // pathRewrite: { // '^/top': '' // } // } },複製程式碼
其中,'/api'①和'/top'②表示你在資料請求的字首名,target表示你需要跨域的目標地址;changeOrigin表示是否進行跨域;
①:fetchGet ('/api/Joke/QueryJokeByTime', params)
②:fetchGet ('/top/News', params)
下拉載入資料
在實現拉下載入資料之前,你需要了解:better-scroll;那麼你會用到哪些方法和事件吶?你會使用到的方法:refresh(),scrollTo(),scrollToElement();你會使用到的事件:beforeScroll,scrol,scrollToEnd;至於其他方法和事件請見better-scroll官網;
那麼下面我們開始來實現下拉載入資料吧
首先,二話不說就來一個元件封裝掉你要使用的better-scroll,以此更方便你使用better-scroll;你可能很疑問,明明人家官方做得會很好封裝了直接調過來用不就完了嗎?你說的也沒錯,但是你仔細考慮一下,你需要更改某些效果或者做更多的邏輯判斷,你把一個頁面寫了幾千行程式碼,後期維護,誰願意來看這段程式碼,如果你做更進一步的封裝是不是節省了整個頁面的程式碼量,維護起來也方便。better-scroll的封裝如下:
<template> <div ref="wrapper"> <slot></slot> </div></template><script>import BScroll from 'better-scroll'import { debounce } from '@/common/utils'const DIRECTION_H = 'horizontal'const DIRECTION_V = 'vertical'export default { name: 'scroll', props: { /** * 1 滾動的時候會派發scroll事件,會節流。 * 2 滾動的時候實時派發scroll事件,不會節流。 * 3 除了實時派發scroll事件,在swipe的情況下仍然能實時派發scroll事件 */ probeType: { type: Number, default: 1 }, /** * 點選列表是否派發click事件 */ click: { type: Boolean, default: true }, /** * 是否開啟橫向滾動 */ scrollX: { type: Boolean, default: false }, /** * 是否派發滾動事件 */ listenScroll: { type: Boolean, default: false }, /** * 列表的資料 */ data: { type: Array, default: null }, pullup: { type: Boolean, default: false }, pulldown: { type: Boolean, default: false }, beforeScroll: { type: Boolean, default: false }, /** * 當資料更新後,重新整理scroll的延時。 */ refreshDelay: { type: Number, default: 10 }, direction: { type: String, default: DIRECTION_V } }, mounted () { setTimeout(() => { this._initScroll() }, 20) }, methods:{ _initScroll () { if (!this.$refs.wrapper){ return } this.scroll = new BScroll(this.$refs.wrapper, { click: this.click, probeType: this.probeType, eventPassthrough: this.direction === DIRECTION_V ? DIRECTION_H : DIRECTION_V }) // 監聽 滑動, 並丟擲其滾動距離 if (this.listenScroll) { debounce(this.scroll.on('scroll', (pos) => { // this.$emit('scrol', pos) // console.log(pos.y) if (pos.y > 100) { this.$emit('scrol') } }), 300) } // 派發 上拉 載入更多 if (this.pullup) { this.scroll.on('scrollEnd', ()=>{ if (this.scroll.y <= this.scroll.maxScrollY + 150) { this.$emit('scrollToEnd') } }) } // 派發下拉 重新整理 if (this.pulldown) { this.scroll.on('touchend', (pos) => { if (pos.y > 50) { this.$emit('pulldown') } }) } // 是否 派發列表滾動 開始事件 if (this.beforeScroll) { this.scroll.on('beforeScrollStart', () => { this.$emit('beforeScroll') }) } }, disable() { // 代理better-scroll的disable方法 this.scroll && this.scroll.disable() }, enable() { // 代理better-scroll的enable方法 this.scroll && this.scroll.enable() }, refresh() { // 代理better-scroll的refresh方法 this.scroll && this.scroll.refresh() }, scrollTo() { // 代理better-scroll的scrollTo方法 this.scroll && this.scroll.scrollTo.apply(this.scroll, arguments) }, scrollToElement() { // 代理better-scroll的scrollToElement方法 this.scroll && this.scroll.scrollToElement.apply(this.scroll, arguments) }, }, watch: { // 監聽資料變化,延時XX時間後重新整理better-scroll的效果,保證滾動效果正常 data () { setTimeout(() => { this.refresh() }, this.refreshDelay) } }}</script><style></style>複製程式碼
那麼,下小編這裡還是需要再談到一下slot的使用,因為它的靈活性讓程式編碼確實很方便,只要你父元件需要給元件填充內容使用它特別方便。
此外,這裡還使用到了 debounce的小知識,為啥吶?當然時為了防止你不斷的下拉,造成多次的資料請求,因為你意圖就是載入一次資料嗎,而且他也會減緩伺服器的壓力。debounce實現邏輯如下:
export function debounce (func, delay) { let timer return function (...args) { if (timer) { clearTimeout(timer) } timer = setTimeout(() => { func.apply(this, args) }, delay) }}複製程式碼
緊接著,我們來看下拉載入資料的思路: 在父元件中的使用better-scroll的封裝元件上,繫結data資料來源和scroll、beforeScroll方法。每當你向下拉頁面超過某個高度時,利用釋出-訂閱者模式的長處,通過scroll事件再給父元件派發this.$emit('scrol')方法。此時,父元件中編寫其對應的繫結的方法,去請求資料並更新頁面的資料。具體實現如下:
<template>
...此處省略一萬行 <v-scroll class="content-scroll" ref="contentScroll" :listenScroll="listenScroll" :data="result" :beforeScroll="beforeScroll" @scrol="searchMore"> <ul class="content-article-list"> <!-- loading --> <v-load class="loading-wrapper" v-show="display"></v-load> <li class="article-summary" v-for="(article, index) in result" :key="index"> <h3 class="article-title">{{article.content}}</h3> <div class="article-author">來源:{{article.hashId}}</div> <img :src="article.url" alt="" class="pic"> <!-- 尾部元件:用於點贊、分享、踩 --> </li> </ul> </v-scroll>
...此處省略一萬行
<script>import scroll from '@/components/scroll'...此處省略一萬行
methods: { _getNewImg () { const params = { key: '991792145f62460bac35b1c92ee50cdb', page: this.page, rows: 20 } api.NewImg(params) .then((res) => { // console.log(res) if (res.error_code === 0) { this.result = [...res.result, ...this.result] this.display = false // this._checkMore(res.result) console.log(this.result) } }) }, _checkMore (data) { if (data.length < 10) { this.display = false } }, searchMore () { this.display = true this.page++ this._getNewImg() } }, mounted () { this._getNewImg() setTimeout(() => { this.show = false }, 1500) }}</script>
複製程式碼
最後,需要注意的時元件v-scroll中有一個watch需要注意如下:
watch: { // 監聽資料變化,延時XX時間後重新整理better-scroll的效果,保證滾動效果正常 data () { setTimeout(() => { this.refresh() }, this.refreshDelay) } }複製程式碼
其目的就是,為了解決,當資料更新時,better-scroll重新渲染。
階段三
在階段三中需要學習的東西比較少,可能需要講解的知識點就只有,資料請求介面的key需要更好掉;在vue專案中怎麼進行url頁面跳轉了;以及還有一個css3函式編寫需要注意。
其一
資料請求key更換,為啥要提,因為在阿凡達雲資料平臺,不同的資料型別有不同的key值,你需要找到你記得key值,以及請求資料的關鍵路徑。配置如下:
其二
怎麼在vue專案中進行url跳轉,確實很簡單,你回一下h5專案的頁面跳轉js如何呼叫了瀏覽器的href。詳情如下:
<button class="item-button" @click="detail(headline.url)">檢視詳情</button>...此處省略一萬行
detail (url) { window.location.href = url },
...此處省略一萬行
複製程式碼
順便再提一下,vue專案的頁面切換直接再js中怎麼跳轉吶?簡單,直接呼叫this.$router.push,具體如下:
<button class="item-button" @click="detail(headline.key)">檢視詳情</button>
detail (id) { this.$router.push({path: '/detail', query: {id: key}}) },複製程式碼
其三
css3函式編寫,其意圖是為了解決統一管理樣式主題樣式,以及減少重複性css程式碼做到css的封裝複用。具體編寫如下(此處是css的stylus寫法):
px2rem($px) return ($px / 37.5px)remborder-1px($color) border-top 1px solid $color複製程式碼
最關鍵的??
小編已經明白大家的想法了,是不是看原始碼吶!在此,小編放個GitHub連線,掘友們多多使用你們的小星星哦✿ヽ(°▽°)ノ✿,點贊加關注,繼續我們的技術學習旅程!?
專案原始碼
結語
希望小編的Y式分享能幫助到你,傳播知識,分享快樂,與你們一起共同進步~?