1.瀏覽設計圖、產品原型
2.須要用到分享功能
3.怎麼和Android和iOS原生方法互掉
4.網頁嵌入到APP中怎麼調試
5.手機屏幕適配
6.若是出現Loading chunk xx failed該怎麼處理
複製代碼
1.使用vue-cli直接建立項目,vue-router、vuex都有用到
2.劃分目錄
api- 將項目的api抽離出來單獨放置;
assets- 放置img、css、font等靜態文件;
components- 放置組件文件,我在當中新建了一個global文件夾放置全局組件
plugins- 第三方插件、或者本身封裝的插件
router- 項目的路由配置
store- 項目的vuex數據存儲
view- 項目視圖,可根據項目模塊再劃分相應的目錄
3.公用css仍是須要的,在assets中弄一份pubilc.css,重置樣式;css預處理用的是scss
4.適配手機屏幕,用了最經常使用的rem適配方案,動態計算的js用的是[adaptive.js](https://github.com/finance-sh/adaptive);
5.使用axios來請求數據,axios的攔截器能夠幹不少事情;
複製代碼
下面貼一份個人axios配置代碼javascript
/** * http 配置 */
import Vue from 'vue'
import axios from 'axios'
import router from '@/router'
import store from '@/store'
import Qs from 'qs'//序列化參數
// axios默認配置
axios.defaults.timeout = 20000; //請求超時時間
axios.defaults.withCredentials = false;
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded'; //設置請求頭
axios.defaults.baseURL = '/api'; //baseurl
// http request 攔截器
axios.interceptors.request.use(
config => {
let method = config.method;
let TOKRN = store.state.access_token;
//判斷是否顯示loading
if(config.isLoading == true && !store.state.isLoading){
store.commit('updateLoadingStatus', true)
}
//在請求中統一帶上token,token從vuex中取
if (config.data) {
config.data.access_token = TOKRN;
config.data = Qs.stringify(config.data);
} else {
let url = config.url;
config.url = (url.indexOf("?") != -1)
? url + '&access_token='+TOKRN
: url + '?access_token='+TOKRN
}
return config;
},
error => {
store.commit('updateLoadingStatus', false);
return Promise.reject(error);
}
)
//http respone 攔截器
axios.interceptors.response.use(
response => {
let result = response.data;
let resCode = result.code;
//對後臺返回的狀態碼進行處理
switch (Number(resCode)) {
case 9004:
//...
break;
case 12000:
break;
case 12001:
//沒有實名認證
break;
case 9000 || 9001 || 9002:
break;
default:
break;
}
setTimeout(() => {
if(store.state.isLoading) {
store.commit('updateLoadingStatus', false);
}
}, 300);
return response.data;
},
error => {
if (error.response) {
//請求出錯,根據http狀態碼作相應處理
switch (error.response.status) {
case 400:
console.log('service 400 操做失敗!')
break;
case 404:
// router.push({name:'404'})
console.log('service 404 請求不存在!')
break;
case 408:
router.push({name:'500',query:{code:408}})
console.log('service 408 請求超時')
break;
case 500:
router.push({name:'500',query:{code:500}})
console.log('service 500 內部服務器錯誤')
break;
}
}
store.commit('updateLoadingStatus', false);
return Promise.reject(error);
}
)
export default axios;
複製代碼
1.將UI設計師提供的控件圖做爲公共組件實現,如header、footer、經常使用btn等; 可是彈窗部分是一個高頻使用的組件,每次使用組件又略顯麻煩,因而借鑑vux的作法,將彈窗部分作成了vue插件,能夠經過this調用,方便省事了很多;我將它發佈在了npm,有須要的歡迎來使用v-m-layer;我貼一個示例代碼,你們也許會以爲好用css
<!--alert.vue組件-->
<template>
<div>
<transition name="overlay"><div class="mv-modal-overlay" v-show="show"></div></transition>
<transition name="modal">
<div class="mv-modal" id="alert" v-show="show">
<div class="mv-modal-inner">
<div class="mv-modal-text" v-if="text" v-html="text"></div>
<div class="mv-modal-text" v-else><slot></slot></div>
<span class="alert-btn" @click="_onOk">肯定</span>
</div>
</div>
</transition>
</div>
</template>
<script> export default { props: { text: String, value: { type: Boolean, default: false } }, data() { return { show: false } }, created() { if(this.value) { this.show = true; } }, methods: { _onCancle() { this.$emit('onCancle') this.show = false; }, _onOk() { this.$emit('onOk'); this.show = false; } }, watch: { show(val) { this.$emit('input', val) }, value(val, oldVal) { this.show = val } } } </script>
<style scoped> @import url('../../../assets/css/layer.css'); .alert-btn{ display: block; width: 80%; height: 40px; line-height: 40px; margin-left: 10%; margin-bottom: 15px; text-align: center; font-size: 16px; background: #FFD00D; color: #242832; border-radius: 4px; } </style>
複製代碼
//將alert.vue封裝成插件
import AlertComponent from '../../components/layer/alert/alert'
import { mergeOptions } from '../helper'
let $vm;
const plugin = {
install(vue, options) {
const Alert = vue.extend(AlertComponent);
if(!$vm){
$vm = new Alert({
el: document.createElement('div')
})
document.body.appendChild($vm.$el)
}
const alert = function(text, onOk) {
let opt = {
text,
onOk
}
mergeOptions($vm, opt)
this.watcher && this.watcher();
this.watcher = $vm.$watch('show', (val) => {
if(val == false){
opt.onOk && opt.onOk($vm)
this.watcher && this.watcher();
}
})
$vm.show = true
}
if(!vue.$layer){
vue.$layer = {
alert
}
} else{
vue.$layer.alert = alert;
}
vue.mixin({
created: function () {
this.$layer = vue.$layer
}
})
}
}
export default plugin
export const install = plugin.install
複製代碼
2.因爲登陸是原生實現的,因此在登陸完成跳轉到h5時要傳遞相關參數; 開始的作法是原生調用咱們h5定義的全局方法,咱們在方法中將參數存儲到vuex中html
window.GET_AUTHENTICATION = function(token,userId) {
store.commit('refreshToken', token);//存儲token
store.commit('USER_ID', userId);//存儲用戶ID
}
複製代碼
可是這種作法會存在異步的問題,好比進入頁面須要用token去獲取數據,可是token還沒來得及被存儲就很差玩了;因此使用第二種方法,讓APP跳轉時將參數攜帶在url中,咱們在APP.vue入口文件中將url中的參數都存到vuex中,這樣就好使了。vue
<script> //好比APP跳轉過來的url是http://192.168.3.56:8081/#/index?token=123456&userid=2&from=ios import { mapMutations,mapState } from 'vuex' export default { name: "App", data(){ return{ } }, created() { let url = window.location.href; let arr,Json={}; let str = null; let iterms = null; if(url.indexOf("?") != -1) { str = url.split("?")[1]; iterms = str.split("&"); for(var i=0;i<iterms.length;i++){ arr=iterms[i].split("="); Json[arr[0]]=arr[1]; } } if(Json.token) { this.refreshToken(Json.token) window.sessionStorage.setItem('token',Json.token) console.log('Token => '+Json.token) } if(Json.userid) { this.USER_ID(Json.userid) console.log('userid => '+Json.userid) } if(Json.from) { this.PLATFORM(Json.from) console.log('platform => '+Json.from) } }, methods: { ...mapMutations(['refreshToken','SAVE_MSGCOUNT','USER_INFO','USER_ID','PLATFORM']) } }; </script>
複製代碼
3.web和app須要互調方法; 開始想去看看JSBridge怎麼使用的,後面APP說他們提供簡單的調用方法;java
//h5調用APP的方法,webkit.messageHandlers是原生的方法前綴,MOVIE_JSBRIDGE_MESSAGEHANDLE_NAME_OPEN_UPLOADIDCARD是方法名,postMessage是固定的調用函數,能夠傳參
webkit.messageHandlers.MOVIE_JSBRIDGE_MESSAGEHANDLE_NAME_OPEN_UPLOADIDCARD.postMessage(type)
//APP調用h5的方法,只須要h5將方法掛在到window對象便可
window.getToken = function(token) {
//....
}
複製代碼
可是咱們Android和iOS兩個平臺的互調方法不同,因此須要判斷不一樣的平臺執行不一樣的方法,webpack
<script> export default { mounted() { const _this = this; //上傳完成後APP返回給H5資源地址,參數({code:'',imgUrl:'',videoUrl:'',msg:''}) window.RETURN_RESOURCES = function(data) { if(data.code == 1) { _this.params.avatar = data.imgUrl; } else{ _this.$layer.toast(data.msg ? data.msg : '未知錯誤!') } } }, methods: { //上傳頭像 openAppFile(type){ const platform = this.$store.state.platform;//區分是iOS仍是Android try { platform == 'ios' ? webkit.messageHandlers.MOVIE_JSBRIDGE_MESSAGEHANDLE_NAME_OPEN_UPLOADFILE.postMessage(type) : movie_js_app_tool.MOVIE_JSBRIDGE_MESSAGEHANDLE_NAME_OPEN_UPLOADFILE(type) } catch(err) { console.error(err); } } } } </script>
複製代碼
4.當頁面在手機上運行時,出現錯誤咱們很差查看錯誤,很差去追蹤;可是好在有vconsole這個插件,可使咱們在手機上查看控制檯信息。 ios
5.在iOS上點擊事件是有300ms延遲的,能夠引入fastclick來解決git
//main.js
import FastClick from 'fastclick'
FastClick.attach(document.body);
複製代碼
6.爲了看起來像APP,在頁面切換時須要有切換動畫;想了半天沒有什麼好的方案,在逛GitHub時發現了一個還不錯的方案。 在vuex中存一個變量isBack:false,只要isBack爲false就是執行前進動畫,爲true就執行後退動畫;但什麼時候爲false,什麼時候爲true呢? https://github.com/zhengguorong/pageAinimategithub
// 只要頁面切換,而且執行了300ms的動畫就設置爲false
router.afterEach((to, from, next) => {
setTimeout(() => {
store.commit('SAVE_BACK',false);
}, 300);
});
//監聽返回事件,只要用戶點擊了返回就設置爲true,這樣就執行了返回動畫,根據上面的代碼,300ms後就會自動設置爲false;
//以此推,只要沒有監聽到返回事件,執行的都是前進動畫;監聽到了返回事件就執行後退動畫,後退動畫執行完就會300ms後就會自動設置爲false
//router.back()和router.go(-1)會觸發返回事件
window.addEventListener('popstate', function (e) { //監聽返回事件
store.commit('SAVE_BACK',true);
}, false)
複製代碼
在APP.vue中設置動畫web
<template>
<div id="app">
<transition :name="viewTransition" >
<router-view v-if="!$route.meta.keepAlive" class="child-view"></router-view>
</transition>
<loading v-show="isLoading">加載中...</loading>
</div>
</template>
<script> import { loading,confirm } from '@/components/layer' import { mapMutations,mapState } from 'vuex' export default { name: "App", data(){ return{ transitionName: 'slide-left', } }, methods: { ...mapMutations(['refreshToken','SAVE_MSGCOUNT','USER_INFO','USER_ID','PLATFORM']) }, computed:{ ...mapState({ isBack: state => state.isBack, isLoading: state => state.isLoading, route: state => state.route }), viewTransition() { if (this.route.meta && typeof this.route.meta.index === 'number') {return ''}; return this.isBack ? 'slide-right' : 'slide-left'; } }, components:{ loading, confirm }, }; </script>
<style> @import url('./assets/css/public'); #app{ display: block; width: 100%; } .child-view { position: absolute; top: 0; bottom: 0; left: 0; right: 0; transition: transform 300ms; will-change: transform; background: #181B22; -webkit-backface-visibility: hidden; backface-visibility:hidden; perspective: 1000; } .slide-left-enter, .slide-right-leave-active { -webkit-transform: translate3d(100%,0,0); transform: translate3d(100%,0,0); z-index: 1; } .slide-left-leave-active, .slide-right-enter { -webkit-transform: translate3d(0,0,0); transform: translate3d(0,0,0); z-index: -1; } </style>
複製代碼
router.beforeEach(function (to, from, next) {
if(to.name == null) {
next({name:'404'})
}
next()
})
複製代碼
6.用戶點擊過的A模塊被瀏覽器緩存了,當再從新打包上線後,用戶在A模塊依然是讀取的緩存能夠正常瀏覽;若是從A模塊中點擊連接到B模塊中,因爲每次打包的文件hash值不一樣,致使從服務器中找不到該模塊,因此就拋出了Loading chunk xx failed的錯誤。因此須要捕捉模塊加載的錯誤
//routerUtils.js
import router from '../router'
import store from '../store'
export default {
catchImport(err) {
try {
console.log('我已經捕捉到了router Loading chunk fail錯誤');
let routeName = store.state.route.name;
if(routeName && routeName.indexOf('recruit') != -1) {
router.push({name:'recruitIndex'});
} else{
router.push({name:'index'});
}
setTimeout(() => {
window.location.reload();
}, 500);
} catch (error) {
console.log('router:'+error)
}
}
}
複製代碼
import routerUtils from '../plugins/routerUtils'
//一個模塊設置一個捕獲
const index = () => import(/* webpackChunkName: "index" */ '@/view/home/index/').catch(routerUtils.catchImport)
const artistResume = () => import(/* webpackChunkName: "index" */ '@/view/home/artistResume')
複製代碼