歷經了接近兩個月的摸索滾爬,抓頭吃瓜,各類優化(單押X3),我主導開發的第一個小程序終於要上線了(SKR SKR!)!固然首先要感謝老闆沒有殺了我——由於在6月初我剛拿到小程序PRD的時候老闆問我多久能夠作好,我看了看以後說「兩週」吧,咳咳,而後。。。一直到如今,我還能活着很Amazing有沒有???固然這其中也有一些爲了追求「精品小程序」而一改再改所用的時間。好了,廢話仍是很少說了,下面就開始總結下吧~html
原生的小程序我本人並無學習過,更別提拿來開發一款商用的小程序了,恰好還在前公司時,當時的前端團隊在提到小程序的解決方案時有分享了mpvue,到了新公司以後技術老大也有提到mpvue,而我本人過去一年多也一直在寫vue,對vue寫法比較熟悉,並且新公司團隊對小程序期待已久,但願儘快上架,因此選擇mpvue來開發也是最快最合理的了!前端
看了mpvue的官方文檔,項目的搭建天然也選擇了官方推薦的vue-cli, 在看了五分鐘上手教程後,使用命令
vue init mpvue/mpvue-quickstart my-project
生成了基本的項目,在後來的開發中,項目的配置基本沒作改動,只是添加了less-loader。vue
基本上是vue-cli生成的目錄結構,加了部分文件夾,主要是與後臺進行數據交互所使用的框架flyio的配置文件夾(api文件夾),以及整個項目數據管理所使用的vuex(store文件夾),總體目錄結構以下:node
project
└───build
└───config
└───dist
└───node_modules
└───src
└───api
| ajax.js // flyio請求與響應攔截器的配置文件
| config.js // 請求的配置文件
| index.js // 生成請求api實例文件
| Server.js // 項目的數據請求統一管理文件
└───components
└───pages
└───store
└───modules // vuex模塊文件夾
| index.js // vuex處理文件
| App.vue
| config.js
| main.js
└───static
└───images
└───lib
└───weui
│ README.md
│ package.json
│ package-lock.json
複製代碼
相信不少使用過mpvue的同窗都或多或少猜到了一些坑,我也是踩到了很多的坑浪費了很多的寶貴時間,雖然網上關於mpvue的踩坑的文章一搜一籮筐,但我仍是要寫一下。。。下面就是我在本次小程序開發過程當中遇到的坑(們)以及針對它們的解決方案:git
在配置小程序原生的底部tabBar時,遇到了第一個問題:在將設計師給個人圖標icon路徑設置正確的狀況下,開發者工具上的tabBar的圖標老是會很大,並且幾乎佔滿了整個高度,至關難看,搜了不少博客都沒有找到解決辦法,期間還嘗試了本身實現tabBar,可是在看到那使人嘔嘔嘔的效果以後,我仍是放棄了,又回到原生的tabBar,而後靜下心來想了想,最後在對比github上的一些mpvue的項目以後,發現原來是圖標icon的問題,最後成功解決:就是icon尺寸保持不變,而後四周留出合適的透明(?)空白...很簡單有木有?就這浪費我不少腦細胞,原諒個人愚鈍(智障臉)。。。固然了,原生的tabBar其實還有一個問題就是,tabBar的標題文字在真機上會離底部特別特別近,這個我沒找到解決辦法,除了本身實現tabBar。。。github
這個問題我想不少同窗都遇到過了,主要是由於mpvue中頁面跳轉後並無銷燬頁面實例,而是將其推入頁面棧中,因此會保存以前的舊的數據,並且我看到mpvue github上的issues裏面有不少人都遇到了這個問題而且都在持續關注,足以說明這是個痛點問題,誰讓它會影響小程序的用戶體驗呢。。。到目前爲止看到的比較統一的解決辦法就是:在(詳情)頁面onLoad的時候,將要在本頁面展現的數據初始化而且進行新的賦值,舉🌰以下:ajax
<template>
<html-text :text="htmltext"></html-text>
</template>
<script>
import htmlText from xxxxx
export default {
components: {
htmlText
},
data () {
return {
htmltext: ''
}
},
onLoad () {
this.htmltext = ''
this.$http.get('xxxxxxxx').then((res) => {
this.htmltext = res.htmltext
})
}
}
</script>
複製代碼
其餘數組或者對象類型的處理可能會麻煩一些,可是方法相似,在數據請求返回以前的這段時間內不想留空白尬對用戶的話就本身作一些loading,老是要強過用戶先面對舊數據再一閃跳到新數據的體驗。。。vuex
這個我想應該是mpvue的一個bug吧?該鉤子函數在頁面內仍是不要隨便用的好。。。vue-cli
這個問題不能甩鍋給mpvue,對於展現「至關複雜」的富文本(內容較長,且由多張圖片甚至多張動圖)的需求,通常不會有不少用戶會遇到,可是很不巧的是,我遇到了。。。誰讓咱們致力於作一個有逼格的品牌呢?有需求了就要解決,光能展現遠遠不夠,還得展現的優雅,目前的mpvue-wxParse 其實已經能解決大部分問題了,也有一些github上的項目基於該項目開發獲得了數百star,可是我用該項目作出來的效果老闆和技術老大都至關不滿意,圖片無法優雅的加載,並且因爲htmltext太長在圖片所有解析顯示出來以前有着至關長的白屏尷尬時間,因此最後仍是放棄該方案。
而後在github上找到了另外一個在mpvue-wxParse 的基礎上改進的針對複雜富文本的項目mpvue-htmlParse,試了下稍微好了點,但離老闆的要求仍是差很遠,最後不得已在此項目基礎上fork出一份代碼針對老闆的需求親自來改,最終得以過關,項目地址mpvue-htmlParse,該項目裏主要針對圖片的加載作了改進,在第一張圖片加載完成後,通知主頁能夠關閉preLoading效果,而後給每張圖片添加了菊花的加載效果,在圖片徹底加載完成以前會顯示菊花圖,而後再根據設備屏幕寬度和圖片信息對圖片進行適當放大或者縮小,這樣總體下來的效果基本能夠達到「破產版」微信公衆號推文的效果,該項目適用範圍有限,有須要的同窗能夠本身在此基礎上改進。npm
對於參數的傳遞,我也遇到過相似於舊數據的問題,最後不得已藉助於vuex才得以解決。另外小程序的頁面棧個數實在有限,因此在開發時必定要注意頁面棧的管理。
要記得該鉤子函數裏的js代碼不僅是剛進入頁面時會執行,在息屏後再次點亮後也將會執行。
對於mpvue的坑忽然能想起來的很少了,目前就先寫這麼多,後面想起來了再來更新吧。
在小程序的開發中,並無使用小程序原生的wx.request()來進行數據交互,而是選擇了mpvue文檔裏推薦使用的Flyio,Flyio的介紹就很少作介紹,打架能夠本身看文檔,這裏我主要說一下的請求和響應攔截器的構造:
文檔裏其實有很詳細的介紹以及代碼,可是我根據代碼寫下來以後在遇到登陸失效的問題時並無按照預想的解決:先鎖住請求而後從新請求拿到新的cookie以後再從新進行以前的請求,再和其餘人討論以後使用promise解決了這一問題,具體可見代碼:
src/api/ajax.js:
/**
* http請求攔截器
*/
const Fly = require('flyio/dist/npm/wx')
const config = require('./config')
const ajaxUrl =
process.env.NODE_ENV === 'development'
? config.Host.development
: process.env.NODE_ENV === 'production'
? config.Host.production
: config.Host.test
let fly = new Fly()
let loginFly = new Fly()
// 定義公共headers
const headers = {
...
}
Object.assign(fly.config, {
headers: headers,
baseURL: ajaxUrl,
timeout: 10000,
withCredentials: true
})
loginFly.config = fly.config
// session失效後本地從新登陸
const login = () => {
return new Promise((resolve, reject) => {
wx.login({
success: res => {
let loginParams = {
...
}
loginFly.post('/api/locallogin/url', loginParams).then(d => {
if (d.headers && typeof d.headers['set-cookie'] !== 'undefined') {
// 更新session
wx.setStorageSync('sessionid', d.headers['set-cookie'])
}
resolve()
}).catch(error => {
log(error)
reject(res.data)
})
},
fail: res => {
console.error(res.errMsg)
},
complete: res => {}
})
})
}
// 請求攔截器
fly.interceptors.request.use(request => {
if (wx.getStorageSync('sessionid')) {
request.headers.cookie = wx.getStorageSync('sessionid')
}
return request
})
// 響應攔截器
fly.interceptors.response.use(
response => {
// session已經失效,須要從新登陸小程序
if (response.data.errCode === 100009) {
// log('session失效,根據以前存儲在本地的用戶信息從新請求session...')
// 鎖定響應攔截器
fly.lock()
return login().then(() => {
fly.unlock()
// log(`從新請求:path:${response.request.url},baseURL:${response.request.baseURL}`)
return fly.request(response.request)
}).catch(err => {
log(err)
})
} else {
return response.data.data
}
},
err => {
log('error-interceptor', err)
if (err.status) {
wx.showToast({
title: '出現未知錯誤',
icon: 'none',
duration: 3000
})
}
}
)
export default fly
複製代碼
// 建立api實例
src/api/index.js:
import Server from './Server.js'
class Api {
constructor () {
Object.assign(this, ...Array.from(arguments))
}
}
export default new Api(Server)
複製代碼
由於是生活購物類小程序,涉及到購物車+地址選擇等較爲複雜的邏輯,不少地方都須要數據共用,在本期項目中vuex起了很大的做用,由於模塊較多,若是將全部數據寫在一個文件裏無疑會爲後期維護帶來巨大困難,因此將各模塊的數據單獨劃分寫在各自的文件裏,這樣總體流程就清晰了不少,下面是劃分模塊的主文件的代碼
src/api/Server.js:
/**
* 本模塊主要用於與服務端進行交互
*/
import ajax from './ajax.js'
async function requestFunction ({params}) {
let res = await ajax.get('/request/url/', {params})
...
return res.data
}
export default {
requestFunction
}
複製代碼
src/store/index.js:
import Vue from 'vue'
import Vuex from 'vuex'
import modules1 from './modules/modules1'
import modules2 from './modules/modules2'
import modules3 from './modules/modules3'
...
Vue.use(Vuex)
export default new Vuex.Store({
// 作模塊化處理,每一個功能一個store.js文件,而後統一在這邊引入
modules: {
modules1,
modules2,
modules3,
...
}
})
複製代碼
src/store/modules/modules1.js:
import api from '@/api' // actions裏請求用到
const state = {
aaaa,
...
}
const getters = {
aaaa (state) {
return state.aaaa
},
bbbb (state, getters, rootState) {
return getters.aaaa
},
...
}
// actions裏可進行異步操做
const actions = {
async anExample ({state, getters, dispatch, commit}, {params}) {
let res = await api.requestFunction({params})
...
return res
},
...
}
const mutations = {
setStateX (state, Y) {
state.X = Y
},
...
}
export default {
namespaced: true, // 很重要
state,
getters,
actions,
mutations
}
複製代碼
在.vue文件中調用
src/pages/xxx.vue
<script>
import { mapState, mapGetters } from 'vuex'
export default {
computed: {
// 調用getters
...mapGetters('modules', [
'aaaa',
'bbbb'
])
},
methods: {
// 調用action
funcA () {
this.$store.dispatch('modules1/anExample', {params}).then(res => {
...
})
},
// 調用mutation
funcB () {
this.$store.commit('modules1/setStateX', Y)
}
}
}
</script>
複製代碼
本次總結目前先寫這麼多吧,主要介紹了項目結構,遇到的坑(項目中遇到的問題不少,可是寫的時候忽然以爲那些都不是問題了?),Flyio的使用(重點爲攔截器的配置),以及vuex的簡單介紹。其實項目開發完成以後想了想也沒那麼多東西,只是期間走了很多的彎路,作了不少「無用功」,其實說是無用功,但也從中收穫了至關多,畢竟本身從無到有從0到1構建一個項目,其中的煩惱不少,可是真的能讓人成長不少,也讓我以爲至關充實。因爲小程序是公司商用的不是我我的的項目,因此項目代碼就無法開源了,若是有問題的話能夠聯繫我,爲防廣告之嫌這裏也不說明小程序的名字了,想來體驗下的能夠私信我,也歡迎你們來指正!
老闆來找我過第二期的需求了,Incoming!