components/like
思路:css
like 默認爲 false,顯示空心小愛心html
觸摸執行tap:onLike
方法,由於 this.setData({count:count,like:!like})
是異步的,先執行count = like ? count - 1 : count + 1
,這時like仍是false,執行count+1。而後在執行this.setData()方法,將like變爲true,顯示實心小愛心。vue
let behavior = like ? 'like' : 'cancel' //自定義事件 this.triggerEvent('like', { behavior: behavior }, {})
自定義事件like,當like爲真時,behavior爲like,在models/like.js
中,let url = behavior === 'like' ? 'like/cancel' : 'like'
,由於behavior === 'like'
爲真,就調用服務器接口'like/cancel',相反就調用like接口。android
剛開始實心就調用'like/cancel'接口,空心就調用'like'接口css3
components/navi
思路:git
navi/index.js
中:先定義哪些數據是外部傳來的數據,哪些數據是私有數據 properties: {//外部傳來的數據 title: String, first: Boolean, //若是是第一期向右的箭頭就禁用,默認是false latest: Boolean //若是是最新的一期向左的箭頭就禁用,默認是false }, data: {//私有數據 disLeftSrc: './images/triangle.dis@left.png', leftSrc: './images/triangle@left.png', disRightSrc: './images/triangle.dis@right.png', rightSrc: './images/triangle@right.png' },
在navi/index.wxml中
<image bind:tap="onLeft" class="icon" src="{{latest?disLeftSrc:leftSrc}}"/>
githubsrc顯示圖片規則:若是是最新的期刊,就顯示向左禁用狀態disLeftSrc箭頭;若是不是最新一期的期刊,就顯示向左可用狀態leftSrc箭頭web
爲圖片綁定觸摸事件onLeft,在
navi/index.js
中:json在 methods 中:若是不是最新的期刊,就繼續綁定自定義事件left onLeft: function(event) { //不是最新一期 if (!this.properties.latest) { this.triggerEvent('left', {}, {}) } },
在navi/index.wxml中
<image bind:tap="onRight" class="icon" src="{{first?disRightSrc:rightSrc}}"/>
小程序src顯示圖片規則:若是是第一期的期刊,就顯示向右禁用狀態disRightSrc箭頭;若是不是第一期的期刊,就顯示向右可用狀態rightSrc箭頭
爲圖片綁定觸摸事件onRight,在
navi/index.js
中:在 methods 中:若是不是第一期的期刊,就繼續綁定自定義事件right onRight: function(event) { //不是第一期 if (!this.properties.first) { this.triggerEvent('right', {}, {}) } }
pages/classic
中:1:在 classic.json 中,註冊使用navi自定義組件 { "usingComponents": { "v-like": "/components/like/index", "v-movie": "/components/classic/movie/index", "v-episode": "/components/episode/index", "v-navi": "/components/navi/index" } } 2:在 classic.wxml 中:綁定自定義事件left, 獲取當前一期的下一期;綁定自定義事件right,獲取當前一期的上一期 <v-navi bind:left="onNext" bind:right="onPrevious" class="nav" title="{{classic.title}}" first="{{first}}" latest="{{latest}}"/> 3:在 classic.js 中: // 獲取當前一期的下一期,左箭頭 onNext: function(evevt) {this._updateClassic('next') }, // 獲取當前一期的上一期,右箭頭 onPrevious: function(evevt) { this._updateClassic('previous') }, // 重複代碼過多,利用函數封裝的思想,新建一個函數抽取公共代碼 // 發送請求,獲取當前頁的索引,更新數據 _updateClassic: function(nextOrPrevious) { let index = this.data.classic.index classicModel.getClassic(index, nextOrPrevious, (res) => { // console.log(res) this.setData({ classic: res, latest: classicModel.isLatest(res.index), first: classicModel.isFirst(res.index) }) }) }, 4:在 models/classic.js 中: // 當前的期刊是否爲第一期,first就變爲true,右箭頭就顯示禁用 isFirst(index) { return index === 1 ? true : false } // 當前的期刊是否爲最新的一期,latest就變爲TRUE,左箭頭就顯示禁用 // 因爲服務器數據還會更新,肯定不了最新期刊的索引,因此就要利用緩存機制,將最新期刊的索引存入到緩存中,若是外界傳進來的索引和緩存的最新期刊的索同樣,latest就變爲TRUE,左箭頭就顯示禁用 isLatest(index) { let latestIndex = this._getLatestIndex() return latestIndex === index ? true : false } // 將最新的期刊index存入緩存 _setLatestIndex(index) { wx.setStorageSync('latest', index) } // 在緩存中獲取最新期刊的index _getLatestIndex() { let index = wx.getStorageSync('latest') return index }
1: // 設置緩存中的key 的樣式,classic-1這種樣式 _getKey(index) { let key = `classic-${index}` return key } 2: // 由於getPrevious,getNext實現代碼類似,因此爲了簡化代碼能夠合併爲一個函數 // 緩存思路:在緩存中尋找key,找不到就發送請求 API,將key寫入到緩存中。解決每次都調用Api向服務器發請求,耗費性能 // 在緩存中,肯定key getClassic(index, nextOrPrevious, sCallback) { //0: 是next,觸摸向左箭頭獲取下一期,觸摸向右箭頭不然獲取上一期 let key = nextOrPrevious === 'next' ? this._getKey(index + 1) : this._getKey(index - 1) //1:在緩存中尋找key let classic = wx.getStorageSync(key) //2:若是緩存中找不到key,就調用服務器API發送請求獲取數據 if (!classic) { this.request({ url: `classic/${index}/${nextOrPrevious}`, success: (res) => { //將獲取到的數據設置到緩存中 wx.setStorageSync(this._getKey(res.index), res) //再把獲取到的數據返回,供用戶調取使用 sCallback(res) } }) } else { //3:若是在緩存中有找到key,將緩存中key對應的value值,返回給用戶,供用戶調取使用 sCallback(classic) } } -------------------------------------------------------------------------------- // 獲取最新的期刊利用緩存機制進一步優化 //獲取最新的期刊 getLatest(cb) { this.request({ url: 'classic/latest', success: (res) => { //將最新的期刊index存入緩存,防止觸摸向左箭頭時,沒有設置latest的值,左箭頭會一直觸發發送請求找不到最新的期刊報錯 this._setLatestIndex(res.index) //再把獲取到的數據返回,供用戶調取使用 cb(res) // 將最新的期刊設置到緩存中,先調取 this._getKey() 方法,爲最新獲取的期刊設置key值,調用微信設置緩存方法將key,和對應的value值res存進去 let key = this._getKey(res.index) wx.setStorageSync(key, res) } }) }
處理是否點贊小愛心組件的緩存問題:他不須要緩存,須要實時獲取最新數據
//編寫一個獲取點贊信息的方法,從服務器獲取最新點贊信息的數據 // 獲取點贊信息 getClassicLikeStatus(artID, category, cb) { this.request({ url: `classic/${category}/${artID}/favor`, success: cb }) }
//設置私有數據初始值 data: { classic: null, latest: true, first: false, likeCount: 0,//點讚的數量 likeStatus: false //點讚的狀態 }, // 在classic.wxml中: <v-like class="like" bind:like="onLike" like="{{likeStatus}}" count="{{likeCount}}"/> // 編寫一個私有方法獲取點贊信息 // 獲取點贊信息 _getLikeStatus: function(artID, category) { likeModel.getClassicLikeStatus(artID, category, (res) => { this.setData({ likeCount: res.fav_nums, likeStatus: res.like_status }) }) }, //生命週期函數--監聽頁面加載 onLoad: function(options) { classicModel.getLatest((res) => { console.log(res) // this._getLikeStatus(res.id, res.type) //不能這樣寫,會多發一次favor請求,消耗性能 this.setData({ classic: res, likeCount: res.fav_nums, likeStatus: res.like_status }) })
解決切換期刊時,其餘期刊也都是播放狀態的問題。應該是,切換期刊時音樂就中止播放,回到默認不播放狀態
利用組件事件的通訊機制,小程序中只有父子組件
在 components/classic/music/inddex.js 中:
//利用組件生命週期,只有 wx:if 才能夠從頭掉起組件生命週期 // 組件卸載的生命週期函數 // 組件卸載音樂中止播放,但這時不生效是由於,在classic.wxml中用的是hidden,應改成if detached: function(event) { mMgr.stop() }, // 在 pages/classic/classic.wxml 中 // <v-music wx:if="{{classic.type===200}}" img="{{classic.image}}" content="{{classic.content}}" src="{{classic.url}}" title="{{classic.title}}"/>
wx:if vs hidden,和Vue框架的v-if和v-show 指令同樣:
wx:if 》他是惰性的,若是初始值爲false框架什麼也不作,若是初始值爲true框架纔會局部渲染。true或false的切換就是從頁面中局部加入或移除的過程。wx:if 有更高的切換消耗,若是在運行時條件不大可能改變則 wx:if 較好。生命週期會從新執行。
hidden 》組件始終會被渲染,只是簡單的控制顯示與隱藏。hidden 有更高的初始渲染消耗。若是須要頻繁切換的情景下,用 hidden 更好。生命週期不會從新執行。
解決切換期刊時音樂能夠當作背景音樂一直播放,而其餘的期刊是默認是不播放狀態
//爲了保證期刊在切換時,背景音樂能夠一直播放,就要去除掉 mMgr.stop() 事件方法 detached: function(event) { // mMgr.stop() //爲了保證背景音樂的持續播放就不能加stop }, // 監聽音樂的播放狀態,若是當前頁面沒有播放的音樂,就設置playing爲false。若是當前頁面的音樂地址classic.url和當前正在播放的音樂的地址同樣,就讓播放狀態爲true _recoverStatus: function() { if (mMgr.paused) { this.setData({ playing: false }) return } if (mMgr.src === this.properties.src) { this.setData({ playing: true }) } }, // 監聽播放狀態,總控開關就能夠控制播放狀態,結局總控開關和頁面不一樣步問題 _monitorSwitch: function() { console.log('monitorSwitch背景音頻', '觸發3') // 監聽背景音頻播放事件 mMgr.onPlay(() => { this._recoverStatus() console.log('onPlay ' + this.data.playing) }) // 監聽背景音頻暫停事件 mMgr.onPause(() => { this._recoverStatus() console.log('onPause ' + this.data.playing) }) // 關閉音樂控制檯,監聽背景音頻中止事件 mMgr.onStop(() => { this._recoverStatus() console.log('onStop ' + this.data.playing) }) // 監聽背景音頻天然播放結束事件 mMgr.onEnded(() => { this._recoverStatus() console.log('onEnded ' + this.data.playing) }) }, //調用生命週期函數,每次切換都會觸發attached生命週期 // 在組件實例進入頁面節點樹時執行 // hidden,ready,created都觸發不了生命週期函數 attached: function(event) { console.log('attach實例進入頁面', '觸發1') this._monitorSwitch() this._recoverStatus() },
//定義幀動畫用CSS3 .rotation { -webkit-transform: rotate(360deg); animation: rotation 12s linear infinite; -moz-animation: rotation 12s linear infinite; -webkit-animation: rotation 12s linear infinite; -o-animation: rotation 12s linear infinite; } @-webkit-keyframes rotation { from { -webkit-transform: rotate(0deg); } to { -webkit-transform: rotate(360deg); } }
》使用CSS3開啓GPU硬件加速提高網站動畫渲染性能:
爲動畫DOM元素添加CSS3樣式-webkit-transform:transition3d(0,0,0)或-webkit-transform:translateZ(0);,這兩個屬性都會開啓GPU硬件加速模式,從而讓瀏覽器在渲染動畫時從CPU轉向GPU,其實說白了這是一個小伎倆,也能夠算是一個Hack,-webkit-transform:transition3d和-webkit-transform:translateZ實際上是爲了渲染3D樣式,但咱們設置值爲0後,並無真正使用3D效果,但瀏覽器卻所以開啓了GPU硬件加速模式。
》這種GPU硬件加速在當今PC機及移動設備上都已普及,在移動端的性能提高是至關顯著地,因此建議你們在作動畫時能夠嘗試一下開啓GPU硬件加速。》適用狀況
經過-webkit-transform:transition3d/translateZ開啓GPU硬件加速的適用範圍:使用不少大尺寸圖片(尤爲是PNG24圖)進行動畫的頁面。
頁面有不少大尺寸圖片而且進行了css縮放處理,頁面能夠滾動時。
使用background-size:cover設置大尺寸背景圖,而且頁面能夠滾動時。(詳見:https://coderwall.com/p/j5udlw)
編寫大量DOM元素進行CSS3動畫時(transition/transform/keyframes/absTop&Left)
使用不少PNG圖片拼接成CSS Sprite時
》總結
經過開啓GPU硬件加速雖然能夠提高動畫渲染性能或解決一些棘手問題,但使用仍需謹慎,使用前必定要進行嚴謹的測試,不然它反而會大量佔用瀏覽網頁用戶的系統資源,尤爲是在移動端,肆無忌憚的開啓GPU硬件加速會致使大量消耗設備電量,下降電池壽命等問題。
//爲圖片加上播放就旋轉的類,不播放 就就爲空字符串 <image class="classic-img {{playing?'rotation':''}}" src="{{img}}"></image>
用 slot 插槽,解決在公用組件中能夠加入其餘修飾內容問題。其實就是,在定義公用組件時,用 slot 命名插槽佔位,在父組件調用時能夠傳遞須要的內容補位。和Vue的指令 v-slot 類似。
//在 Component 中加入 // 啓用slot options: { multipleSlots: true },
//定義幾個命名插槽,供父元素佔位使用 <view class="container tag-class"> <slot name="before"></slot> <text>{{text}}</text> <slot name="after"></slot> </view>
//註冊並使用組件 { "usingComponents": { "v-tag": "/components/tag/index" } }
//使用組件v-tag,補位命名插槽 <v-tag tag-class="{{index===0?'ex-tag1':''||index===1?'ex-tag2':''}}" text="{{item.content}}"> <text class="num" slot="after">{{'+'+item.nums}}</text> </v-tag>
pages/detail/detail
中,解決評論內容自定義組件 v-tag 評論前兩條顯示兩種顏色的作法:/* v-tag是自定義組件,不能使用css3,在微信小程序中,只有內置組件才能夠用css3 */ /*用CSS hack方式給自定義組件加樣式*/ .comment-container>v-tag:nth-child(1)>view { background-color: #fffbdd; } .comment-container>v-tag:nth-child(2)>view { background-color: #eefbff; }
定義外部樣式方法,像父子組件傳遞屬性同樣,傳遞樣式類
/* 定義外部樣式 */ .ex-tag1 { background-color: #fffbdd !important; } .ex-tag2 { background-color: #eefbff !important; }
/*將自定義的樣式類經過屬性傳值的方式傳遞給自定義子組件v-tag */ <v-tag tag-class="{{index===0?'ex-tag1':''||index===1?'ex-tag2':''}}" text="{{item.content}}"> <text class="num" slot="after">{{'+'+item.nums}}</text> </v-tag>
//將外部傳進來的樣式寫在Component中,聲明一下 // 外部傳進來的css,樣式 externalClasses: ['tag-class'],
// 把父組件傳遞過來的類 tag-calss 寫在 class 類上 <view class="container tag-class"> <slot name="before"></slot> <text>{{text}}</text> <slot name="after"></slot> </view>
緣由:
是由於服務器返回的原始數據 是\\n
,通過轉義就變成\n
而
\n
在text文本標籤中默認轉義爲換行
解決方法:
WXS:WXS(WeiXin Script)是小程序的一套腳本語言,結合 WXML
,能夠構建出頁面的結構。和Vue 中的 Vue.filter(過濾器名,過濾器方法) 很類似。WXS 與 JavaScript 是不一樣的語言,有本身的語法,並不和 JavaScript 一致。因爲運行環境的差別,在 iOS 設備上小程序內的 WXS 會比 JavaScript 代碼快 2 ~ 20 倍。在 android 設備上兩者運行效率無差別。
// 定義過濾器函數,處理服務器返回的數據,將 \\n 變成 \n // 會打印兩次,undefined和請求獲得的數據,由於第一次初始時text爲null,發送請求獲得數據後調用setData更新數據一次 var format = function(text) { console.log(text) if (!text) { return } var reg = getRegExp('\\\\n', 'g') return text.replace(reg, '\n') } module.exports.format = format
//引入 <wxs src="../../utils/filter.wxs" module="util"/> //在須要過濾的數據中使用 <text class="content">{{util.format(book.summary)}}</text>
//對須要縮進的段落前加如下的類,但這時只有第一段縮進 .content { text-indent: 58rpx; font-weight: 500; }
//用轉義字符 做爲空格,但這時小程序會以 樣式輸出,不是咱們想要的效果 var format = function(text) { if (!text) { return } var reg = getRegExp('\\\\n', 'g') return text.replace(reg, '\n ') } module.exports.format = format
//加入屬性 decode="{{true}}",首行縮進問題解決 <text class="content" decode="{{true}}">{{util.format(book.summary)}}</text>
//添加一個限制短評長度的過濾器,並導出 // 限制短評的長度的過濾器 var limit = function(array, length) { return array.slice(0, length) } module.exports = { format: format, limit: limit };
<wxs src="../../utils/filter.wxs" module="util"/> <view class="sub-container"> <text class="headline">短評</text> <view class="comment-container"> <block wx:for="{{util.limit(comments,10)}}" wx:key="content"> <v-tag tag-class="{{index===0?'ex-tag1':''||index===1?'ex-tag2':''}}" text="{{item.content}}"> <text class="num" slot="after">{{'+'+item.nums}}</text> </v-tag> </block> </view> </view>
// 因爲 <v-tag tag-class="{{index===0?'ex-tag1':''||index===1?'ex-tag2':''}}" text="{{item.content}}"> 過於亂,改寫成wxs形式: //先定義wxs過濾器 <wxs module="tool"> var highlight = function(index){ if(index===0){ return 'ex-tag1' } else if(index===1){ return 'ex-tag2' } return '' } module.exports={ highlight:highlight } </wxs> //改寫爲: <v-tag tag-class="{{tool.highlight(index)}}" text="{{item.content}}">
用戶提交評論內容:
點擊標籤向服務器提交評論內容:
//爲短評組件綁定出沒事件 onTap <view bind:tap="onTap" class="container tag-class"> <slot name="before"></slot> <text>{{text}}</text> <slot name="after"></slot> </view>
// 當觸摸短評小標籤時,觸發一個自定義事件,將短評內容傳進去,公父組件調用自定義事件tapping methods: { // 觸摸短評小標籤時,觸發的事件,觸發一個自定義事件 onTap(event) { this.triggerEvent('tapping', { text: this.properties.text }) } }
//在父組件中調用子組件的自定義tapping事件,而且觸發事件onPost <v-tag bind:tapping="onPost" tag-class="{{tool.highlight(index)}}" text="{{item.content}}"> <text class="num" slot="after">{{'+'+item.nums}}</text> </v-tag>
//調取新增短評的接口 // 新增短評 postComment(bid, comment) { return this.request({ url: '/book/add/short_comment', method: 'POST', data: { book_id: bid, content: comment } }) }
// 觸摸tag組件會觸發,input輸入框也會觸發事件onPost // 獲取用戶的輸入內容或觸發tag裏的內容,而且對用戶輸入的評論作校驗,若是評論的內容長度大於12就彈出警告不向服務器發送請求 //若是評論內容符合規範,就調用新增短評接口並將最新的評論插到comments數組的第一位,更新數據,而且把蒙層mask關閉 onPost(event) { // 獲取觸發tag裏的內容 const comment = event.detail.text // 對用戶輸入的評論作校驗 if (comment.length > 12) { wx.showToast({ title: '短評最多12個字', icon: 'none' }) return } // 調用新增短評接口並將最新的評論插到comments數組的第一位 bookModel.postComment(this.data.book.id, comment).then(res => { wx.showToast({ title: '+1', icon: 'none' }) this.data.comments.unshift({ content: comment, nums: 1 //這是後面的數量顯示 }) this.setData({ comments: this.data.comments, posting: false }) }) },
// input有本身的綁定事件bindconfirm,會調用手機鍵盤完成按鍵 <input bindconfirm="onPost" type="text" class="post" placeholder="短評最多12個字"/>
點擊標籤向服務器提交評論內容完成:
// 觸摸tag組件會觸發,input輸入框也會觸發事件onPost事件;而後獲取觸發tag裏的內容或獲取用戶input輸入的內容;對tag裏的內容或對用戶輸入的評論作校驗而且輸入的內容不能爲空;最後調用新增短評接口並將最新的評論插到comments數組的第一位,而且把蒙層mask關閉 onPost(event) { const comment = event.detail.text||event.detail.value console.log('comment'+comment) console.log('commentInput'+comment) if (comment.length > 12|| !comment) { wx.showToast({ title: '短評最多12個字', icon: 'none' }) return } bookModel.postComment(this.data.book.id, comment).then(res => { wx.showToast({ title: '+1', icon: 'none' }) this.data.comments.unshift({ content: comment, nums: 1 //這是後面的數量顯示 }) this.setData({ comments: this.data.comments, posting: false }) }) },
若是沒有短評顯示問題:
//在短評後加上尚未短評標籤,若是沒有comments短評就不顯示尚未短評標籤 <text class="headline">短評</text> <text class="shadow" wx:if="{{!comments.length}}">尚未短評</text> //在儘可點擊標籤+1後加上暫無評論標籤,若是沒有comments短評就不顯示暫無評論標籤 <text wx:if="{{!comments.length}}">儘可點擊標籤+1</text> <text wx:else>暫無短評</text>
因爲須要加載的數據較多,爲了提升用戶體驗,須要加一個loading提示數據正在加載中,數據加載完成後就消失;
因爲都是利用promise異步加載數據,這時取消loading顯示應該加到每一個promise後,顯然不符合需求。若是利用回調函數機制,先加載1在一的回調函數裏在加載2依次順序加載,在最後一個回調函數中寫取消loading操做,這樣的方式雖然能夠實現,但很是耗時間,請求是串行的,假如一個請求須要花費2s中,發三個請求就要花費6秒,很是耗時,並且還會出現回調地獄的現象,不推薦使用。
解決方法:在Promise中,有一個Promise.all()方法就能夠解決。
Promise.all(iterable) 方法返回一個Promise
實例,此實例在iterable
參數內全部的promise
都「完成(resolved)」或參數中不包含promise
時回調完成(resolve);若是參數中promise
有一個失敗(rejected),此實例回調失敗(reject),失敗緣由的是第一個失敗promise
的結果。簡單來講就是:只要有一個數組裏的promise獲取失敗就調用reject回調,只有所有數組裏的promise都成功才調用resolve回調。Promise.race(iterable) 方法返回一個 promise,一旦迭代器中的某個promise解決或拒絕,返回的 promise就會解決或拒絕。
race
函數返回一個Promise
,它將與第一個傳遞的 promise 相同的完成方式被完成。它能夠是完成( resolves),也能夠是失敗(rejects),這要取決於第一個完成的方式是兩個中的哪一個。若是傳的迭代是空的,則返回的 promise 將永遠等待。若是迭代包含一個或多個非承諾值和/或已解決/拒絕的承諾,則Promise.race
將解析爲迭代中找到的第一個值。簡單來講就是:不論成功仍是失敗的回調,哪個快就執行哪一個。
//用了 Promise.all(iterable) 方法就不用寫三個Promise方法分別來更新數據了,能夠簡寫成一個all方法再返回的成功的promise中調用setData(),更新請求回的數據 onLoad: function(options) { // 數據加載時顯示loading效果 wx.showLoading() const bid = options.bid console.log(bid) // 獲取書籍詳細信息 const detail = bookModel.getDetail(bid) // 獲取書籍點贊狀況 const likeStatus = bookModel.getLikeStatus(bid) // 獲取書籍短評 const comments = bookModel.getComments(bid) // 數據加載完成時取消顯示loading效果 Promise.all([detail, comments, likeStatus]).then(res => { console.log(res) this.setData({ book: res[0], comments: res[1].comments, likeStatus: res[2].like_status, likeCount: res[2].fav_nums }) wx.hideLoading() }) },
高階組件:若是一個組件裏面的內容比較複雜,包含大量的業務
工做中咱們一般把業務處理邏輯寫在models中:能夠寫在單個公用組件裏,只供本身寫業務邏輯調取使用;
能夠寫在components中,只供components內的組件調取使用,若是想把components發佈出去給其餘項目用或者提供給其餘開發者使用;
能夠寫在項目根目錄models下,供整個項目調取使用寫業務邏輯,若是隻是作個項目建議寫在這裏不會亂。
歷史搜索:將歷史搜索關鍵字寫入緩存中,在從緩存中獲取歷史搜索關鍵字。熱門搜索:調取服務器API
GET /book/hot_list
//首先從緩存中獲取歷史搜索關鍵字數組,判斷獲取的數組是否爲空,若是爲空,爲了防止報錯就返回空數組;若是不爲空就直接返回獲取的數組。 //將搜索關鍵字寫入緩存中,先從緩存中獲取歷史關鍵字的數組,判斷是否包含這次輸入的關鍵字,若是沒有此關鍵字,若是獲取的長度大於最大長度,就將數組的最後一項刪除;若是獲取數組的長度小於最大長度,就將這次輸入的關鍵字加到數組的第一位,而且設置到緩存中。 class KeywordModel { constructor() { // 把key屬性掛載到當前實例上,供實例調取使用 this.key = 'q', this.maxLength = 10 //搜索關鍵字的數組最大長度爲10 } //從緩存中,獲取歷史搜索關鍵字數組,若是緩存中沒有直接返回空數組 getHistory() { const words = wx.getStorageSync(this.key) if (!words) { return [] } return words } // 將歷史搜索關鍵字寫入緩存中。先從緩存中獲取歷史關鍵字的數組,判斷數組中是否已經有此關鍵字。若是沒有,而且獲取數組的長度大於最大長度,就將數組最後一項刪除。獲取數組的長度小於最大長度就將這次輸入的關鍵字加到數組第一位,而且設置到緩存中; addToHistory(keyword) { let words = this.getHistory() const has = words.includes(keyword) if (!has) { const length = words.length if (length >= this.maxLength) { words.pop() } words.unshift(keyword) wx.setStorageSync(this.key, words) } } // 獲取熱門搜素搜關鍵字 getHot() { } } export {KeywordModel}
//爲input輸入框綁定onConfirm事件 <input bind:confirm="onConfirm" type="text" class="bar" placeholder-class="in-bar" placeholder="書籍名" auto-focus="true"/>
// onConfirm事件執行,調用將輸入的內容添加到緩存中的方法Keywordmodel.addToHistory(word),就能夠將歷史關鍵字添加到緩存中 methods: { // 點擊取消將搜索組件關掉,有兩種方法:一是,在本身的內部建立一個變量控制顯隱,不推薦,由於萬一還有其餘操做擴展性很差。二是,建立一個自定義事件,將自定義事件傳給父級,讓父級觸發 onCancel(event) { this.triggerEvent('cancel', {}, {}) }, // 在input輸入框輸入完成將輸入的內容加到緩存中 onConfirm(event) { const word = event.detail.value Keywordmodel.addToHistory(word) } }
//將歷史搜索的內容從緩存中取出來 data: { historyWords: [] //歷史搜索關鍵字 }, // 組件初始化時,小程序默認調用的生命週期函數 attached() { const historywords = Keywordmodel.getHistory() this.setData({ historyWords: historywords }) },
//註冊引用小標籤 tag 組件,組件中也能夠引入其餘組件 "usingComponents": { "v-tag": "/components/tag/index" }
// 遍歷historyWords數組中的每一項,呈如今頁面中 <view class="history"> <view class="title"> <view class="chunk"></view> <text>歷史搜索</text> </view> <view class="tags"> <block wx:for="{{historyWords}}" wx:key="item"> <v-tag text='{{item}}'/> </block> </view> </view>
// 引入本身封裝的API請求方法 import { HTTP } from '../utils/http-promise' // 獲取熱門搜素搜關鍵字 getHot() { return this.request({ url: '/book/hot_keyword' }) }
//定義組件初始值,經過調用傳進來的getHot方法獲取熱門搜索關鍵字,並更新到初始值hotWords中 data: { historyWords: [], //歷史搜索關鍵字 hotWords: [] //熱門搜索關鍵字 }, // 組件初始化時,小程序默認調用的生命週期函數 attached() { const historywords = Keywordmodel.getHistory() const hotword = Keywordmodel.getHot() this.setData({ historyWords: historywords }) hotword.then(res => { this.setData({ hotWords: res.hot }) }) },
//將從服務器獲取到的hotWords數組遍歷,呈現到頁面中 <view class="history hot-search"> <view class="title"> <view class="chunk"></view> <text>熱門搜索</text> </view> <view class="tags"> <block wx:for="{{hotWords}}" wx:key="item"> <v-tag text='{{item}}'/> </block> </view> </view>
因爲在 componentssearchindex.js 調用了 Keywordmodel.getHot()方法,這個方法是和服務器相關聯的,這樣作,會使組件複用性下降。若是要想讓search組件複用性變高,應該在 componentssearchindex.js 的 properties 中開放一個屬性,而後再引用search組件的pages頁面裏調用models裏的方法,再把數據經過屬性傳遞給search組件,而後再作數據綁定,這樣就讓search組件具有了複用性
//定義search函數,封裝向服務器發送請求功能 // 書籍搜索 search(start, q) { return this.request({ url: '/book/search?summary=1', data: { q: q, start: start } }) }
// 導入並實例化BookModel類,負責向服務器發送搜索圖書的請求;在data中聲明私有變量 dataArray 數組,爲搜索圖書當summary=1,返回概要數據。在用戶輸入完成點擊完成時,調用bookmodel.search方法,並更新數據到dataArray中。 //注意點:不能用戶輸入什麼都保存在緩存中,只有用戶搜索到有效的關鍵字時才保存到緩存中 import { BookModel } from '../../models/book' const bookmodel = new BookModel() data: { historyWords: [], //歷史搜索關鍵字 hotWords: [], //熱門搜索關鍵字 dataArray: [] //搜索圖書當summary=1,返回概要數據 }, // 在input輸入框輸入完成將輸入的內容加到緩存中 onConfirm(event) { const word = event.detail.value // 獲取搜索的關鍵詞q,調取search方法返回當summary=1,返回概要數據:並更新數據到dataArray中 const q = event.detail.value bookmodel.search(0, q).then(res => { this.setData({ dataArray: res.books }) // 不能用戶輸入什麼都保存在緩存中,只有用戶搜索到有效的關鍵字時才保存到緩存中 Keywordmodel.addToHistory(word) }) }
解析獲得的搜索數據,並遍歷呈現到頁面中:
//搜索獲得的數據和熱門搜索,歷史搜索是不能一塊兒顯示的,一個顯示,另外一個就得隱藏,搜索獲得的結果頁面是默認不顯示的,須要定義searching一個變量來控制顯隱 <view wx:if="{{searching}}" class="books-container"> <block wx:for="{{dataArray}}" wx:key="{{item.id}}"> <v-book book="{{item}}" class="book"></v-book> </block> </view>
//在data私有屬性中定義searching變量來控制顯隱,默認爲false;在觸發onConfirm事件中, 爲了用戶體驗好,應該點擊完當即顯示搜索頁面,並將searching改成true,讓其搜索的內容顯示到頁面上 data: { historyWords: [], //歷史搜索關鍵字 hotWords: [], //熱門搜索關鍵字 dataArray: [], //搜索圖書當summary=1,返回概要數據 searching: false //控制搜索到的圖書數據的顯隱,默認不顯示 }, // 在input輸入框輸入完成將輸入的內容加到緩存中 onConfirm(event) { // 爲了用戶體驗好,應該點擊完當即顯示搜索頁面 this.setData({ searching: true }) // 獲取搜索的關鍵詞q,調取search方法返回當summary=1,返回概要數據:並更新數據到dataArray中 const q = event.detail.value bookmodel.search(0, q).then(res => { this.setData({ dataArray: res.books }) // 不能用戶輸入什麼都保存在緩存中,只有用戶搜索到有效的關鍵字時才保存到緩存中 Keywordmodel.addToHistory(q) }) }
// 在 components\search\index.wxml 中,爲 x 圖片綁定觸摸時觸發的 onDelete 事件<image bind:tap="onDelete" class="cancel-img" src="images/cancel.png"/> // 觸摸搜索圖片裏的x回到原來輸入搜索的頁面 onDelete(event){ this.setData({ searching: false }) },
// 在 components\search\index.wxml 中:綁定v-tag組件自定事件tapping觸發onConfirm事件:`<v-tag bind:tapping="onConfirm" text='{{item}}'/>` // 在input輸入框輸入完成將輸入的內容加到緩存中 onConfirm(event) { // 爲了用戶體驗好,應該點擊完當即顯示搜索頁面 this.setData({ searching: true }) // 獲取搜索的關鍵詞q:一種是用戶輸入的內容或是經過調用tag組件的自定義事件tapping,裏面有攜帶的text文本;調取search方法返回當summary=1,返回概要數據:並更新數據到dataArray中 const q = event.detail.value || event.detail.text bookmodel.search(0, q).then(res => { this.setData({ dataArray: res.books }) // 不能用戶輸入什麼都保存在緩存中,只有用戶搜索到有效的關鍵字時才保存到緩存中 Keywordmodel.addToHistory(q) }) }
//經過數據綁定給input輸入框綁定value="{{q}}" //`<input value="{{q}}" bind:confirm="onConfirm" type="text" class="bar" placeholder-class="in-bar" placeholder="書籍名" auto-focus="true"/>` //先在data中定義私有數據 q: '' 表明輸入框中要顯示的內容,當數據請求完成後把點擊標籤的內容q賦值給私有數據q並更新 data: { historyWords: [], //歷史搜索關鍵字 hotWords: [], //熱門搜索關鍵字 dataArray: [], //搜索圖書當summary=1,返回概要數據 searching: false, //控制搜索到的圖書數據的顯隱,默認不顯示 q: '' //輸入框中要顯示的內容 }, // 在input輸入框輸入完成將輸入的內容加到緩存中 onConfirm(event) { // 爲了用戶體驗好,應該點擊完當即顯示搜索頁面 this.setData({ searching: true }) // 獲取搜索的關鍵詞q,調取search方法返回當summary=1,返回概要數據:並更新數據到dataArray中 const q = event.detail.value || event.detail.text bookmodel.search(0, q).then(res => { this.setData({ dataArray: res.books, q: q }) // 不能用戶輸入什麼都保存在緩存中,只有用戶搜索到有效的關鍵字時才保存到緩存中 Keywordmodel.addToHistory(q) }) }
第一種方法:用微信小程序提供的內置組件 scroll-view
。
第二種方法:用 pages 裏的 頁面上拉觸底事件的處理函數 onReachBottom。:
//在 data裏設置私有變量more爲false,表明的是是否須要加載更多數據,默認是不加載 data: { books: [], searching: false, //控制搜索框組件search顯隱,默認不顯示 more: false //是否須要加載更多數據,默認是不加載 }, //用pages裏自帶的 頁面上拉觸底事件的處理函數 onReachBottom 監聽頁面是否到底了,若是到底了就會就會將more改變爲true,就能夠實現加載更多數據方法 onReachBottom: function() { console.log('到底了') this.setData({ more: true }) }, //因爲 search 組件不是頁面級組件,沒有 onReachBottom 函數,就須要經過屬性傳值的方式將more私有變量控制是否加載更多數據傳給子組件search // 在pages\book\book.wxml中: `<v-search more="{{more}}" bind:cancel="onCancel" wx:if="{{searching}}"/>` //而後在search組件裏接收父級傳遞過來的屬性more,並利用監聽函數observer,只要外界傳來的數據改變就會觸發此函數執行 properties: { more: { type: String, observer: '_load_more' } //從pages/book/book.js 傳來的屬性,監聽滑到底步操做.只要外界傳來的屬性改變就會觸發observer函數執行 }, methods: { // 只要外界傳來的屬性改變就會觸發observer函數執行 _load_more() { console.log('監聽函數觸發到底了') }, }
但如今存在一個問題就是:observer只會觸發一次,由於下拉到底會把more變爲true,以後就都是true不會再發生變化了,就不會再觸發監聽函數observer執行。
解決方法:用隨機字符串觸發observer函數,由於observer函數的執行必須是監聽的數據發生改變纔會執行此函數。和Vue中的watch很類似。
//將私有數據data中的more改成空字符串 data: { books: [], searching: false, //控制搜索框組件search顯隱,默認不顯示 more: '' //是否須要加載更多數據,默認是不加載 }, //觸發 頁面上拉觸底事件的處理函數,將more變爲隨機數,導入random自定義隨機處理函數,問題解決 onReachBottom: function() { console.log('到底了') this.setData({ more: random(16) }) }, // 在 utils\common.js 中: // 定義隨機生成字符串處理函數,n是生成隨機字符串的位數 const charts = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'] const random = function generateMixed(n) { var res = '' for (var i = 0; i < n; i++) { var id = Math.ceil(Math.random() * 35) res += charts[id] } return res } export { random }
// 和onConfirm同樣都須要調取search方法返回當summary=1,返回概要數據:並更新數據到dataArray中 // 先判斷已獲得的搜索數據的長度,在調用search方法將最新獲取的數據和原來的數據拼接到一塊兒更新數據而後呈現到頁面中 _load_more() { console.log('監聽函數觸發到底了') const length = this.data.dataArray.length bookmodel.search(length, this.data.q).then(res => { const tempArray = this.data.dataArray.concat(res.books) this.setData({ dataArray: tempArray }) }) },
//若是關鍵字q初始沒有值就直接返回什麼也不作 _load_more() { console.log('監聽函數觸發到底了') if (!this.data.q) { return } const length = this.data.dataArray.length bookmodel.search(length, this.data.q).then(res => { const tempArray = this.data.dataArray.concat(res.books) this.setData({ dataArray: tempArray }) }) }, //問題:當下拉刷新沒有更多數據時,還會繼續向服務器發送請求很是耗性能;還有就是用戶操做過快沒等第一次請求的結果回來,就又發送一次相同的請求,會加載重複的數據,很是耗性能 //解決:使用鎖的概念解決重複加載數據的問題 //其實就是事件節流操做,在data中定義一個loading:false,表示是否正在發送請求,默認是沒有發送請求,在_load_more中,判斷若是正在發送請求就什麼也不作,若是沒有正在發送請求就將loading變爲true,調用search方法向服務器發送請求,待請求完畢並返回結果時將loading變爲false。 data:{ loading: false //表示是否正在發送請求,默認是沒有發送請求 }, _load_more() { console.log('監聽函數觸發到底了') if (!this.data.q) { return } // 若是是正在發送請求就什麼也不作 if (this.data.loading) { return } const length = this.data.dataArray.length bookmodel.search(length, this.data.q).then(res => { const tempArray = this.data.dataArray.concat(res.books) this.setData({ dataArray: tempArray, loading: false }) }) },
在 components中,建立並封裝一個公用行爲和方法的組件pagination:
//封裝一個公用行爲和方法的類paginationBev const paginationBev = Behavior({ data: { dataArray: [], //分頁不斷加載的數據 total: 0 //數據的總數 }, methods: { // 加載更多拼接更多數據到數組中;新加載的數據合併到dataArray中 setMoreData(dataArray) { const tempArray = this.data.dataArray.concat(dataArray) this.setData({ dataArray: tempArray }) }, // 調用search方法時返回起始的記錄數 getCurrentStart() { return this.data.dataArray.length }, // 獲取設置從服務器獲得數據的 總長度 setTotal(total) { this.data.total = total }, // 是否還有更多的數據須要加載。若是獲得數據的長度大於服務器返回的總長度,表明沒有更多數據了,就中止發請求 hasMore() { if (this.data.dataArray.length >= this.data.total) { return false } else { return true } } } }) export { paginationBev }
// 先導入封裝的公用行爲方法,再進一步改寫_load_more和onConfirm方法,將寫好的公用方法用上 import { paginationBev } from '../behaviors/pagination' _load_more() { console.log('監聽函數觸發到底了') if (!this.data.q) { return } // 若是是正在發送請求就什麼也不作 if (this.data.loading) { return } if (this.hasMore()) { this.data.loading = true//必須放在hasMore()裏 bookmodel.search(this.getCurrentStart(), this.data.q).then(res => { this.setMoreData(res.books) this.setData({ loading: false }) }) } }, onConfirm(event) { // 爲了用戶體驗好,應該點擊完當即顯示搜索頁面 this.setData({ searching: true }) // 獲取搜索的關鍵詞q,調取search方法返回當summary=1,返回概要數據:並更新數據到dataArray中 const q = event.detail.value || event.detail.text bookmodel.search(0, q).then(res => { this.setMoreData(res.books) this.setTotal(res.total) this.setData({ q: q }) // 不能用戶輸入什麼都保存在緩存中,只有用戶搜索到有效的關鍵字時才保存到緩存中 Keywordmodel.addToHistory(q) }) }
解決方法:就是在每次點x時,清空本次搜索的數據也就是Behavior裏面的數據狀態 ,上一次搜索的數據纔不會影響本次搜索
//加入清空數據,設置初始值的方法 initialize() { this.data.dataArray = [] this.data.total = null }
//在觸發onConfirm函數時調用this.initialize()方法先清空上一次搜索的數據在加載 onConfirm(event) { // 爲了用戶體驗好,應該點擊完當即顯示搜索頁面 this.setData({ searching: true }) // 先清空上一次搜索的數據在加載 this.initialize() // 獲取搜索的關鍵詞q,調取search方法返回當summary=1,返回概要數據:並更新數據到dataArray中 const q = event.detail.value || event.detail.text bookmodel.search(0, q).then(res => { this.setMoreData(res.books) this.setTotal(res.total) this.setData({ q: q }) // 不能用戶輸入什麼都保存在緩存中,只有用戶搜索到有效的關鍵字時才保存到緩存中 Keywordmodel.addToHistory(q) }) }
//多封裝一些小的函數 import { KeywordModel } from '../../models/keyword' import { BookModel } from '../../models/book' import { paginationBev } from '../behaviors/pagination' const Keywordmodel = new KeywordModel() const bookmodel = new BookModel() // components/search/index.js Component({ // 組件使用行爲需加 behaviors: [paginationBev], /** * 組件的屬性列表 */ properties: { more: { type: String, observer: 'loadMore' } //從pages/book/book.js 傳來的屬性,監聽滑到底步操做.只要外界傳來的屬性改變就會觸發observer函數執行 }, /** * 組件的初始數據 */ data: { historyWords: [], //歷史搜索關鍵字 hotWords: [], //熱門搜索關鍵字 // dataArray: [], //搜索圖書當summary=1,返回概要數據 searching: false, //控制搜索到的圖書數據的顯隱,默認不顯示 q: '', //輸入框中要顯示的內容 loading: false //表示是否正在發送請求,默認是沒有發送請求 }, // 組件初始化時,小程序默認調用的生命週期函數 attached() { // const historywords = Keywordmodel.getHistory() // const hotword = Keywordmodel.getHot() this.setData({ historyWords: Keywordmodel.getHistory() }) Keywordmodel.getHot().then(res => { this.setData({ hotWords: res.hot }) }) }, /** * 組件的方法列表 */ methods: { // 只要外界傳來的屬性改變就會觸發observer函數執行 loadMore() { console.log('監聽函數觸發到底了') // 和onConfirm同樣都須要調取search方法返回當summary=1,返回概要數據:並更新數據到dataArray中 // 先判斷已獲得的搜索數據的長度,在調用search方法將最新獲取的數據和原來的數據拼接到一塊兒更新數據而後呈現到頁面中 if (!this.data.q) { return } // 若是是正在發送請求就什麼也不作 if (this._isLocked()) { return } // const length = this.data.dataArray.length if (this.hasMore()) { this._addLocked() bookmodel.search(this.getCurrentStart(), this.data.q).then(res => { this.setMoreData(res.books) this._unLocked() }) } }, // 點擊取消將搜索組件關掉,有兩種方法:一是,在本身的內部建立一個變量控制顯隱,不推薦,由於萬一還有其餘操做擴展性很差。二是,建立一個自定義事件,將自定義事件傳給父級,讓父級觸發 onCancel(event) { this.triggerEvent('cancel', {}, {}) }, // 觸摸搜索圖片裏的x回到原來輸入搜索的頁面 onDelete(event) { this._hideResult() }, // 在input輸入框輸入完成將輸入的內容加到緩存中 onConfirm(event) { // 爲了用戶體驗好,應該點擊完當即顯示搜索頁面 this._showResult() // 先清空上一次搜索的數據在加載 this.initialize() // 獲取搜索的關鍵詞q,調取search方法返回當summary=1,返回概要數據:並更新數據到dataArray中 const q = event.detail.value || event.detail.text bookmodel.search(0, q).then(res => { this.setMoreData(res.books) this.setTotal(res.total) this.setData({ q: q }) // 不能用戶輸入什麼都保存在緩存中,只有用戶搜索到有效的關鍵字時才保存到緩存中 Keywordmodel.addToHistory(q) }) }, // 更新變量的狀態,顯示搜索框 _showResult() { this.setData({ searching: true }) }, // 更新變量的狀態,隱藏搜索框 _hideResult() { this.setData({ searching: false }) }, // 事件節流機制,判斷是否加鎖 _isLocked() { return this.data.loading ? true : false }, // 加鎖 _addLocked() { this.data.loading = true }, // 解鎖 _unLocked() { this.data.loading = false }, } })
解決方法:
//只要在請求失敗的回調函數里加上解鎖就能夠了 if (this.hasMore()) { this._addLocked() bookmodel.search(this.getCurrentStart(), this.data.q).then(res => { this.setMoreData(res.books) this._unLocked() }, () => { this._unLocked() }) }
先建立一個loading公共組件,只需寫簡單的樣式效果就行,在search組件中註冊並使用。
// 在 components\search\index.wxml 中:加入兩個loading組件。 第一個在中間顯示,獲取搜獲數據中;第二個在底部顯示,數據加載更多時顯示 //<v-loading class="loading-center" wx:if="{{loadingCenter}}"/> // <v-loading class="loading" wx:if="{{loading}}"/> //在data中添加一個loadingCenter變量控制loading效果是否在中間顯示,而且加兩個私有函數控制loading的顯隱。在onConfirm函數中調用this._showLoadingCenter()函數,顯示loading效果,在 數據加載完成,調取this._hideLoadingCenter(),取消顯示loading效果, data: {loadingCenter: false}, // 改變loadingCenter的值 _showLoadingCenter() { this.setData({ loadingCenter: true }) }, // 改變loadingCenter的值 _hideLoadingCenter() { this.setData({ loadingCenter: false }) } onConfirm(event) { // 爲了用戶體驗好,應該點擊完當即顯示搜索頁面 this._showResult() // 顯示loading效果 this._showLoadingCenter() this.initialize() const q = event.detail.value || event.detail.text bookmodel.search(0, q).then(res => { this.setMoreData(res.books) this.setTotal(res.total) this.setData({ q: q }) Keywordmodel.addToHistory(q) // 數據加載完成,取消顯示loading效果 this._hideLoadingCenter() }) },
特別注意setData與直接賦值的區別:setData:調用setData函數更新的數據會觸發頁面從新渲染,和REACT裏的setState類似。
而直接賦值,只是在內存中改變的狀態,並無更新到頁面中
//在公共行爲中加入noneResult:false,控制是否顯示沒有獲得想要的搜索結果,在setTotal方法中,若是返回的結果爲0,就是沒有獲得想要的搜索結果。將noneResult:true顯示出來。在initialize設置初始值並清空數據函數,再將noneResult:false,取消顯示。 data: { dataArray: [], //請求返回的數組 total: null, //數據的總數 noneResult: false //沒有獲得想要的搜索結果 }, // 獲取設置數據的 總長度 // 若是返回的結果爲0,就說明沒有獲得搜索結果,將提示內容顯示出來 setTotal(total) { this.data.total = total if (total === 0) { this.setData({ noneResult: true }) } }, // 清空數據,設置初始值,將提示隱藏 initialize() { this.setData({ dataArray: [], noneResult: false }) this.data.total = null }
//加入空搜索顯示的結果結構 <text wx:if="{{ noneResult}}" class="empty-tip">沒有搜索到書籍</text>
// 觸摸搜索圖片裏的x回到原來輸入搜索的頁面,先回到初始值,再將搜索組件隱藏。在onConfirm中,不用等數據加載完,輸入完成後就把輸入的內容顯示在輸入框中。 onDelete(event) { this.initialize() this._hideResult() }, onConfirm(event) { this._showResult() this._showLoadingCenter() const q = event.detail.value || event.detail.text // 不用等數據加載完,輸入完成後就把輸入的內容顯示在輸入框中。 this.setData({ q: q }) bookmodel.search(0, q).then(res => { this.setMoreData(res.books) this.setTotal(res.total) Keywordmodel.addToHistory(q) this._hideLoadingCenter() }) },
//在 components\search\index.wxml 中: //經過搜索組件搜索顯示的書籍都不顯示喜歡字樣,經過屬性傳值的方式將喜歡字樣去掉,把false傳遞給子組件,子組件經過showLike變量接收,經過數據控制顯隱將喜歡字樣去掉。 <v-book book="{{item}}" class="book" show-like="{{false}}"></v-book> //添加一個showLike屬性,表明每本書裏面的喜歡字樣是否顯示 properties: { book: Object, showLike: { //控制每本書下面有個喜歡字樣的顯示與隱藏 type: Boolean, value: true } }, //在 components\book\index.wxml 中: //showLike屬性的顯示和隱藏控制喜歡字樣的顯示和隱藏 <view class="foot" wx:if="{{showLike}}"> <text class="footer">{{book.fav_nums}} 喜歡</text> </view>
//把在components\search\index.js中的三個鎖方法提取到公用行爲方法中,在公用行爲方法中,在data裏添加loading:false屬性。在initialize函數中,把loading:false也加進去便可 // 事件節流機制,判斷是否加鎖 isLocked() { return this.data.loading ? true : false }, // 加鎖 addLocked() { this.setData({ loading: true }) }, // 解鎖 unLocked() { this.setData({ loading: false }) },
scroll-view 或 Pages 裏的 onReachBottom。若是要想用scroll-view把view組件換成scroll-view就能夠。
微信open-data顯示用戶信息:https://developers.weixin.qq.com/miniprogram/dev/api/wx.getUserInfo.html
用戶受權,須要使用 button 來受權登陸。
不少時候咱們須要把獲得信息保存到咱們本身的服務器上去,須要經過js代碼中操做用戶頭像等信息。封裝一個image-button通用組件就能夠,改變他的圖片,而且能夠在不一樣的方式中調用,只須要改變open-type屬性就能夠。
分享的實現:自定義分享button:
小程序之間的跳轉:這兩個小程序都必須關聯同一個公衆號
==============================================================
在components/episode/index.js
中
報錯RangeError: Maximum call stack size exceeded
:
緣由:
//錯誤寫法 properties: { index: { //默認顯示爲0 type: String, observer: function(newVal, oldVal, changedPath) { let val = newVal < 10 ? '0' + newVal : newVal this.setData({ index: val }) } } }, //小程序的observer,至關於vue中的watch監聽函數,只要監聽的index數據改變,就會調用observer方法,會造成死循環,就會報錯RangeError: Maximum call stack size exceeded
解決:
//第一種解決方法 this.setData({ _index: val }) data: { year: 0, month: '', _index: '' },
//第二種解決方法 (推薦)
在components/music/index.js
中:
報錯:setBackgroundAudioState:fail title is nil!;at pages/classic/classic onReady function;at api setBackgroundAudioState fail callback function
Error: setBackgroundAudioState:fail title is nil!
緣由:
少 title 外傳屬性
解決:
//在`components/music/index.js`中: properties: { src: String, title: String }, methods: { // 爲圖片綁定觸摸播放事件 onPlay: function() { //圖片切換 this.setData({ playing: true }) mMgr.src = this.properties.src mMgr.title = this.properties.title } } ----------------------------------------------------------------------------- //在 app.json 中加上: "requiredBackgroundModes": [ "audio" ],
============================================================================
components/navi
中:點擊的左右小三角要足夠大,用戶觸摸時才能點擊到。兩種方法,第一種是再切圖時,切得大一些;第二種是,本身編寫代碼控制操做區域
完成效果展現: