历经了接近两个月的摸索滚爬,抓头吃瓜,各类优化(单押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!