vue做混合式app_Vue+原生App混合开发手记#1
項(xiàng)目的大致需求就是做一個(gè)App,里面集成各種功能供用戶使用,其中涉及到很多Vue的使用方法,單獨(dú)總結(jié)太麻煩,所以通過(guò)這幾篇筆記來(lái)梳理一下。原型圖如下:
路由配置
主界面會(huì)用到一些原生App方法,比如驗(yàn)證用戶身份等,故由原生App完成,進(jìn)去的每個(gè)模塊則全部都是HTML頁(yè)面(有一種后端工作好輕松的感覺(jué) ̄へ ̄)。由于傳統(tǒng)的HTML頁(yè)面開(kāi)發(fā)起來(lái)效率太低,所以我選擇了Vue來(lái)實(shí)現(xiàn)。每一個(gè)功能對(duì)應(yīng)一個(gè)路由,比如電腦報(bào)修對(duì)應(yīng)/repair,repair這個(gè)路由下的子頁(yè)面都放進(jìn)子路由里。
│
├─repair
│ apply.vue
│ index.vue
│ payment.vue
│ repairList.vue
│
└─teambuilding
apply.vue
index.vue
const router = newRouter({
mode:'history',
routes: [
path:'/repair',
component: repair,
children: [
{path:'apply', component: repairApply}, //電腦報(bào)修申請(qǐng)
{
path:':id',//報(bào)修單詳情
component: repairDetail,
children: [
{name:'evaluate', path: 'evaluate', component: repairEvaluate}, //服務(wù)評(píng)價(jià)
{name: 'evaluatePay', path: 'pay', component: pay} //支付
]
}
]
]
})
為了減小打包時(shí)的體積,在加載組件的時(shí)候采用了以下形式:
const repair = resolve => import('views/repair/index').then(module =>resolve(module))
const repairApply= resolve => import('views/repair/apply').then(module =>resolve(module))
const repairDetail= resolve => import('views/repair/detail').then(module => resolve(module))
這是按照官方文檔提供的路由懶加載技術(shù)寫的,這樣就能實(shí)現(xiàn)當(dāng)路由被訪問(wèn)的時(shí)候才加載對(duì)應(yīng)組件。以上是項(xiàng)目中關(guān)于路由的一些用法。
注冊(cè)全局組件
接下來(lái)是全局組件的用法,比如頭部,等待加載,彈出層之類的組件,幾乎每個(gè)頁(yè)面都有,全局注冊(cè)能省去不少事。
import header from 'components/header/header'import loading from'components/loading/loading'Vue.component('v-header', header)
Vue.component('loading', loading)
之后在每個(gè)頁(yè)面中敲入就能直接使用了,不用每次都去import。
處理返回鍵
還有一個(gè)比較常見(jiàn)的問(wèn)題,由于Vue做出來(lái)的頁(yè)面是一個(gè)SPA,在Android機(jī)中如果按下了物理返回鍵,整個(gè)應(yīng)用都會(huì)退出,解決方法是重寫物理返回鍵,這樣就能按路由一級(jí)一級(jí)地返回了。因?yàn)橹鹘缑媸怯稍鷮?shí)現(xiàn)的,所以Vue只能返回到對(duì)應(yīng)模塊的首頁(yè),比如從 /repair/apply -> /repair -> null ,想要回到原生主界面,需要后端向前端注入一段腳本,在模塊首頁(yè)的后退按鈕被點(diǎn)擊時(shí),執(zhí)行一段方法告知Android調(diào)用自身的邏輯,然后Android關(guān)閉當(dāng)前頁(yè)面并回到主界面,例如:
//在main.js中加入該方法
window.AndroidMethod = function(msg) {if (window.android !== null && typeof(window.android) !== "undefined") {
window.android.callAndroid(msg);
}
}
在頭部組件header.vue中,可以使用如下方式:
methods:{
goback() {
window.history.length> 1 ? this.$router.go(-1) : this.$router.push('/')
},
backToHomePage() {
AndroidMethod('backToHomePage')
}
}
這樣可以將模塊首頁(yè)的返回和子路由的返回區(qū)分開(kāi)來(lái)。
如果使用其他的打包工具,比如apiCloud或者HBuilder,它們都有各自的阻止物理返回按鍵的方法:
//apiCloud
api.addEventListener({
name:'keyback'},function(ret, err){
});
});//HBuilder
//https://blog.csdn.net/qq_25252769/article/details/76913083
document.addEventListener('plusready', function() {var webview =plus.webview.currentWebview();
plus.key.addEventListener('backbutton', function() {
webview.canBack(function(e) {if(e.canBack) {
webview.back();
}else{
webview.close();
}
})
});
});
同樣的,把這些代碼放在main.js中即可,打包后在真機(jī)里運(yùn)行時(shí)會(huì)執(zhí)行這些方法,普通環(huán)境是不存在這些變量的。
接收后端返回的數(shù)據(jù)
有時(shí)候,我們希望在Vue初始化時(shí)就能設(shè)置一些從服務(wù)器獲取的常量,比如userID等,之后在各個(gè)組件中就能很方便地訪問(wèn)。設(shè)置全局變量很簡(jiǎn)單,直接掛載在Vue.prototype后面即可:
axios.get('http://localhost/index.php').then(res =>{
Vue.prototype.uid=res.data.uid
Vue.prototype.appid=res.data.appidnewVue({
el:'#app',
router,
store,
render: h=>h(App)
})
})
在組件中使用this.uid、this.appid就能訪問(wèn)到從服務(wù)器獲取的常量了。如果是普通的js文件(比如api,utils等等),可以通過(guò)
import Vue from 'vue'Vue.prototype.uid
來(lái)訪問(wèn)。我們可能還希望這些數(shù)據(jù)在初始化時(shí)也能同時(shí)保存到Vuex中,先來(lái)看一下最初的Store/index.js文件:
import Vue from 'vue'import Vuex from'vuex'import* as actions from './actions'import* as getters from './getters'import state from'./state'import mutations from'./mutations'exportdefault newVuex.Store({
actions,
getters,
state,
mutations
})
但這樣就沒(méi)有往Vuex中存入數(shù)據(jù)的機(jī)會(huì),這時(shí)就需要對(duì)Store文件夾中的index.js做一些小的封裝,使其返回一個(gè)方法:
functionbuidler(data) {return newVuex.Store({
actions,
getters,
state: data,
mutations
})
}
exportdefault buidler
然后修改main.js中調(diào)用Vuex的方式,最初的代碼如下:
import store from './store'
newVue({
el:'#app',
router,
store,
render: h=>h(App)
})
修改后的代碼如下:
import store from './store'axios.get('http://localhost/index.php').then(res =>{
Vue.prototype.uid=res.data.uid
Vue.prototype.appid=res.data.appidnewVue({
el:'#app',
router,
store:store(res.data),
render: h=>h(App)
})
})
在組件的created方法中用MapGetters輸出一下uid和appid,發(fā)現(xiàn)值可以被打印出來(lái),說(shuō)明這種實(shí)現(xiàn)方式是可以采用的。
更新:在后續(xù)的測(cè)試中,發(fā)現(xiàn)一些機(jī)型,特別是華為機(jī)(實(shí)測(cè)iOS沒(méi)有此問(wèn)題),對(duì)這種延后初始化Vue的方式兼容不好,表現(xiàn)在所有路由的切換動(dòng)畫全部失效,頁(yè)面后退時(shí)會(huì)重新渲染頁(yè)面(執(zhí)行組件created方法中的內(nèi)容),設(shè)置keep-alive也沒(méi)有效果。不過(guò)水平有限,實(shí)在弄不懂為什么會(huì)這樣。為了兼容,就不能采用上面的方式了。最后使用了在請(qǐng)求頭中攜帶cookie的方式,具體為webview加載vue頁(yè)面時(shí),在requestURL中注入cookie,在cookie中設(shè)置需要傳遞的值,下面是用PHP模擬的一個(gè)小例子,PHP加載HTML頁(yè),并注入cookie,在HTML加載時(shí)取到cookie。
PHP:
HTML:
這樣就能在main.js里同步拿到userID,Vue也不用延遲初始化了,Android機(jī)的表現(xiàn)效果和iOS一致。拿到userID后,可以保存到配置文件config中,在每個(gè)組件中訪問(wèn)config.uid就能拿到。
better-scroll
App中最常見(jiàn)的組件就是滾動(dòng)數(shù)據(jù)列表,由此又很容易聯(lián)想到better-scroll這個(gè)插件。better-scroll雖然好用,但如果使用不當(dāng)還是會(huì)造成不小的麻煩,一些錯(cuò)誤甚至無(wú)從排查。這里主要記錄一下下拉刷新和上拉加載更多的實(shí)現(xiàn)。容器結(jié)構(gòu)如下:
最外層的div限制滾動(dòng)內(nèi)容的位置,srcoll是官網(wǎng)提供的已經(jīng)封裝好的組件,里面正常置入ul>li形式的列表就行了,ul和li都不需要特殊的樣式。由于官網(wǎng)提供的例子中整合了許多文件,查閱起來(lái)不是很方便,于是將其剝離出來(lái),寫了一個(gè)只有上拉加載和下拉刷新的Demo,方便以后使用。使用scroll時(shí)要慎用v-show指令,比如我希望使用下面的代碼來(lái)控制沒(méi)有數(shù)據(jù)時(shí)容器的顯示與隱藏,由于數(shù)據(jù)是異步加載,剛開(kāi)始時(shí)容器不顯示直到數(shù)據(jù)加載好為止。
data() {return{
dataList: []
}
}
但這樣會(huì)造成scroll組件內(nèi)部高度計(jì)算錯(cuò)誤(offsetHeight被計(jì)算成0,這是由于容器處于display:none狀態(tài)),如果此時(shí)列表的數(shù)據(jù)沒(méi)有達(dá)到滾動(dòng)要求,上拉和下拉的提示文字會(huì)顯示在列表下方,網(wǎng)速慢時(shí)也無(wú)法使用上拉下拉功能。解決方法是使用v-if指令,這樣容器的min-height高度就能被正確計(jì)算了。
圖片上傳
另外一個(gè)功能是圖片上傳,這個(gè)功能并非由前端完成,而是和上面一樣,通過(guò)后臺(tái)返回的一段函數(shù)體拿到上傳圖片的路徑并展示出來(lái)。
相冊(cè)和拍攝都由后臺(tái)調(diào)起,前端只需要進(jìn)行簡(jiǎn)單的傳值就行了:
AndroidMethod('photo')
AndroidMethod('video')
代碼是和后端約定好的,所以不需要操心。真正需要關(guān)注的是從后臺(tái)返回的圖片上傳路徑,拿到這個(gè)路徑后要在前臺(tái)展示,并且保存時(shí)要帶上一個(gè)或多個(gè)路徑組成的字符串。
這個(gè)方法同樣是和后臺(tái)約定好的方法:
window.getUpload = function(path) {//這里要將path保存起來(lái)拿到組件里使用
}
這里就需要使用全局變量將path保存起來(lái),假定這個(gè)全局變量叫做uploadImgUrl,初始化時(shí)是一個(gè)空數(shù)組,只有當(dāng)用戶從相冊(cè)里選擇圖片上傳后才將拿到的路徑賦給這個(gè)全局變量。Vue組件中要監(jiān)聽(tīng)這個(gè)全局變量的變化,就不能使用Vue.prototype.uploadImgUrl這種方式了,因?yàn)閂ue要監(jiān)聽(tīng)某個(gè)變量的變化,必須將這個(gè)變量放在data中,改進(jìn)一下之前的代碼:
import store from './store'axios.get('http://localhost/index.php').then(res =>{
Vue.prototype.uid=res.data.uid
Vue.prototype.appid=res.data.appid
let vm= newVue({
el:'#app',
router,
data() {return{
uploadImgUrl: []
}
},
store:store(res.data),
render: h=>h(App)
})
window.getUpload= function(path) {
vm.uploadImgUrl=path
}
})
上面將變量存放在根組件的data中,在其它組件內(nèi)就可以通過(guò)以下形式訪問(wèn)到
this.$root.$data.uploadImgUrl
雖然拿到了路徑,但問(wèn)題還沒(méi)有結(jié)束,因?yàn)檫@個(gè)值是動(dòng)態(tài)變化的,需要使用計(jì)算屬性來(lái)監(jiān)測(cè)它的變化,下面是核心代碼:
data() {return{
loadedImgs:[]//保存已上傳的圖片
}
},
methods: {
deleteImg(index) {for (let i = 0; i < this.loadedImgs.length; i++) {if (index ===i) {this.loadedImgs.splice(i, 1)break}
}
},
computed: {
imgsList() {this.loadedImgs = this.loadedImgs.concat(this.$root.$data.uploadImgUrl)this.$root.$data.uploadImgUrl =[] //每次合并完重置一下return this.loadedImgs
}
},
created() {this.$root.$data.uploadImgUrl =[] //組件創(chuàng)建時(shí)先重置一下之前的值
}
}
}
通過(guò)computed計(jì)算屬性,無(wú)論是添加圖片或者刪除圖片都能正確展示了。
真機(jī)調(diào)試
在真機(jī)上調(diào)試非常不方便,很多調(diào)試信息看不到,不過(guò)vconsole這個(gè)插件解決了這個(gè)問(wèn)題,安裝方法非常簡(jiǎn)單,在依賴?yán)?開(kāi)發(fā)環(huán)境或正式環(huán)境均可)安裝vconsole,然后在main.js中
import Vconsole from 'vconsole'
new Vconsole()
打開(kāi)頁(yè)面就能看到右下角多出了一個(gè)vConsole的圖標(biāo),項(xiàng)目中所有console.log的信息都會(huì)輸出到這個(gè)vConsole面板里。
結(jié)束
知識(shí)點(diǎn)比較繁雜,所以文章有點(diǎn)亂,暫時(shí)先總結(jié)到這,后續(xù)還有很多坑待填。
總結(jié)
以上是生活随笔為你收集整理的vue做混合式app_Vue+原生App混合开发手记#1的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 校验json格式_格式化展示,校验错误,
- 下一篇: vue变量传值_Vue各类组件之间传值的