手把手教你構建vue項目(微信h5以及hybrid混合開發)(四)

這一篇主要是指令、過濾器、路由、Store的配置、axios的二次封裝以及使用

1.過濾器的配置

1)在filters目錄新建filters/index.js,目錄結構如下:

└─src
    │  filters
    │      index.js

filters/index.js

/**
 * @description 過濾時間格式,傳入時間戳, 根據參數返回不同格式
 */
// 過濾日期格式,傳入時間戳,根據參數返回不同格式
const formatTimer = function(val, hours) {
  if (val) {
    var dateTimer = new Date(val * 1000)
    var y = dateTimer.getFullYear()
    var M = dateTimer.getMonth() + 1
    var d = dateTimer.getDate()
    var h = dateTimer.getHours()
    var m = dateTimer.getMinutes()
    M = M >= 10 ? M : '0' + M
    d = d >= 10 ? d : '0' + d
    h = h >= 10 ? h : '0' + h
    m = m >= 10 ? m : '0' + m
    if (hours) {
      return y + '-' + M + '-' + d + ' ' + h + ':' + m
    } else {
      return y + '-' + M + '-' + d
    }
  }
}

/**
 *@description 格式化支付方式
 *  */

const formatPayWay = function(val) {
  switch (val) {
    case 1:
      return '微信'
      break
    case 2:
      return '支付寶'
      break
    case 3:
      return 'apple pay'
      break
    case 4:
      return '銀聯支付'
      break
    default:
      break
  }
}
/**
 * 根據key過濾值 returnkey要返回的值的key
 */
const findValue = function(val, key, filterArr, rerunkey) {
  let findItem = filterArr.find(item => {
    return item[key] === val
  })
  if (findItem) {
    return findItem[rerunkey]
  }
}

/**
 * 文字超出就省略
 * @param {String} text 文本
 * @param {number} length 截取長度
 */
const textEllipsis = function(text, length) {
  return text.length > length ? text.slice(0, length) + '...' : text
}

export default {
  formatTimer,
  formatPayWay,
  findValue,
  textEllipsis
}

  1. 在main.js中引入

src/main.js

import filters from '@/filters'

// 注入全局過濾器
Object.keys(filters).forEach(item => {
  Vue.filter(item, filters[item])
})
  1. 文件中使用
    那findValue 方法舉例
 <div class="class-hour-tag">
         {{coursewareItem.type | findValue('value', coursewareStateArr, 'text')}}
   </div>

2.指令的配置

1)在directives目錄新建directives/index.js,目錄結構如下:

└─src
    │  directives
    │      index.js

directives/index.js

/**
 * @description fitIphoneX 主要是爲了適配iphoneX自適配的問題,可以設置padding,maring,bottom
 * @params setValue 需要設置的值  | type 設置的類型,比如說padding
 * @useage  v-fitIphoneX="{ type: 'padding', pxNum: 10 }"
 */
function judgeIPhoneX() {
  // 判斷是否是iphoneX
  let ua = window.navigator.userAgent
  let isIos = !!ua.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/)
  return isIos && window.screen.height === 812 && window.screen.width === 375
    ? true
    : false
}

export const fitIphoneX = {
  bind(el, binding) {
    let isIPhoneX = judgeIPhoneX()
    let designWidth = 375 // 設計稿高度
    let pxNum = binding.value.pxNum
    let iphoneXNum = (binding.value.pxNum || 30) + 34
    let setValue = isIPhoneX
      ? (100 / designWidth) * iphoneXNum
      : (100 / designWidth) * pxNum // 轉化成vw
    switch (binding.value.type) {
      case 'padding':
        el.style.paddingBottom = `${setValue}vw`
        break
      case 'margin':
        el.style.marginBottom = `${setValue}vw`
        break
      default:
        el.style.bottom = `${setValue}vw`
        break
    }
  }
}

/**
 * @description 修復ios手機失去焦點頁面未還原問題
 * @params
 * @useage v-reset-page
 */
export const resetPage = {
  inserted(el) {
    // 監聽鍵盤收起事件
    document.body.addEventListener('focusout', () => {
      if (/(iPhone|iPad|iPod|iOS)/i.test(navigator.userAgent)) {
        // 軟鍵盤收起處理
        setTimeout(() => {
          const scrollHeight =
            document.documentElement.scrollTop || document.body.scrollTop || 0
          window.scrollTo({
            left: 0,
            top: Math.max(scrollHeight - 1, 0),
            behavior: 'smooth'
          })
        }, 100)
      }
    })
  }
}

/**
 * @description input輸入框只能輸入數字
 * @params
 * @useage v-number-only
 */
export const numberOnly = {
  bind(el, binding) {
    el.handler = function() {
      let val = el.value
      val = val.replace(/[^\d]/g, '')
      if (el.value.length > binding.value) {
        el.value = val.slice(0, binding.value)
        console.log('el.value---', el.value)
      }
    }
    el.addEventListener('input', el.handler, false)
  },
  unbind(el) {
    el.removeEventListener('input', el.handler)
  }
}


  1. 在main.js中引入

src/main.js

import * as directives from './directives'

// 注入全局指令
Object.keys(directives).forEach(item => {
  Vue.directive(item, directives[item])
})

3)在文件中使用
拿resetPage 方法舉例

<input
  type="number"
  class="phone"
  placeholder="請輸入手機號"
  v-model.trim="phoneNumber"
  oninput="if (value.length > 11) value = value.slice(0, 11).replace(/[^\d]/g, '')"

  v-reset-page

/>

3.路由的配置

1)src/router/index.js

import Vue from 'vue'
import VueRouter from 'vue-router'

/*  解決vue項目路由出現message: "Navigating to current location (XXX) is not allowed"的問題*/
const routerPush = VueRouter.prototype.push
VueRouter.prototype.push = function push(location) {
  return routerPush.call(this, location).catch(error => error)
}

Vue.use(VueRouter)

const routes = [
  {
    path: '/',
    name: 'Home',
    component: () => import('@/views/Home'),
    meta: {
      title: '首頁',
      keepAlive: true
    }
  },
  {
    path: '/404',
    name: '404',
    component: () => import('@/views/404'),
    meta: {
      title: '404',
      keepAlive: true
    }
  },
  { path: '*', redirect: '/404', hidden: true }
]

const router = new VueRouter({
  mode: 'history',
  base: process.env.BASE_URL,
  scrollBehavior: () => ({ y: 0 }),
  routes
})

export default router

4.Store的配置

1)store的文件目錄結構如下:

└─src     
    ├─store   // vuex
    │  │  getters.js // vuex中的getters 相當於computed
    │  │  index.js // store的入口文件
    │  │  
    │  └─modules  // 將soter分爲多個模塊
    │          user.js   

在這裏插入圖片描述
同時在utils目錄下新建constant.js,這裏主要是是放置一些常量

└─src     
    ├─utils
    │   constant.js

以下是主要文件的示例代碼
src/store/index.js

import Vue from 'vue'
import Vuex from 'vuex'
import getters from './getters'
Vue.use(Vuex)

//  自動化引入modules下的所有js文件 https://webpack.docschina.org/guides/dependency-management/#require-context
const modulesFiles = require.context('./modules', true, /\.js$/)
const modules = modulesFiles.keys().reduce((modules, modulePath) => {
  // 設置 user.js => user
  const moduleName = modulePath.replace(/^\.\/(.*)\.\w+$/, '$1')

  //將數據集成爲  modules: {a: moduleA,b: moduleB}的格式
  const value = modulesFiles(modulePath)
  modules[moduleName] = value.default
  return modules
}, {})

export default new Vuex.Store({
  getters,
  modules
})

src/store/getters.js

const getters = {
  token: state => state.user.token,
  userInfo: state => state.user.userInfo,
  inviteUserInfo: state => state.study.inviteUserInfo,
  openId: state => state.user.openId,
  device: state => state.page.device,
  subjects: state => state.practice.examInfo.subjectList,
  subjectSum: state => state.practice.examInfo.subjectSum,
  practiceModel: state => state.practice.examInfo.model
}

export default getters

src/store/modules/user.js

import {
  LOGIN,
  LOGOUT,
  USERINFO,
  SET_USERINFO,
  SET_OPENID,
  OPENID,
  USER_TOKEN,
  SET_TOKEN
} from '@/utils/constant'
import { loginByPhone, fetchUserInfo, logout } from '@/api/user'
import { getToken, setToken, removeToken } from '@/utils/auth'
import { resetRouter } from '@/router'
// import { Toast } from 'vant'

const state = {
  token: getToken(USER_TOKEN) || '', // 權限驗證
  userInfo: JSON.parse(localStorage.getItem(USERINFO)),
  openId: getToken(OPENID) || '' // openId
}

const mutations = {
  [LOGIN](state, token) {
    state.token = token
    setToken(USER_TOKEN, token)
  },
  [LOGOUT](state) {
    state.userInfo = null
    state.token = ''
    removeToken(USER_TOKEN)
    sessionStorage.removeItem(USERINFO)
    resetRouter()
  },
  [SET_TOKEN](state, token) {
    state.token = token
    setToken(USER_TOKEN, token)
  },
  [SET_USERINFO](state, userInfo = {}) {
    state.userInfo = { ...userInfo }
    localStorage.setItem(USERINFO, JSON.stringify(userInfo))
  },
  [SET_OPENID](state, openId) {
    state.openId = openId
    setToken(OPENID, openId)
  }
}

const actions = {
  async loginByPhone({ commit }, data) {
    // 登錄,登出會根據業務場景實現
    try {
      let res = await login({
        phoneNumber: data.phoneNumber,
        password: data.password
      })
      commit(LOGIN, res)
      Toast({
        message: '登錄成功',
        position: 'middle',
        duration: 1500
      })
      setTimeout(() => {
        const redirect = data.$route.query.redirect || '/'
        data.$router.replace({
          path: redirect
        })
      }, 1500)
    } catch (error) {
      return error
    }
  },
  getUserInfo({ commit, state }) {
    return new Promise((resolve, reject) => {
      fetchUserInfo()
        .then(data => {
          if (!data) {
            reject('Verification failed, please Login again.')
          }
          commit(SET_USERINFO, data)
          resolve(data)
        })
        .catch(err => {
          reject(err)
        })
    })
  },
  resetToken({ commit, state }) {
    return new Promise(resolve => {
      commit(SET_TOKEN, '')
      removeToken(USER_TOKEN)
      resolve()
    })
  }
}

export default {
  namespaced: true,
  state,
  mutations,
  actions
}

5.axios的二次封裝以及使用

1)安裝axios

yarn add axios -D

2)在src/utils 目錄新建request.js
request.js

/* 對axios根據業務需求再次封裝 */
import axios from 'axios'
import { Toast, Dialog } from 'vant'
import { getToken } from './auth'
import store from '@/store'
import { USER_TOKEN } from '@/utils/constant'
import Router from '@/router'
import defaultSettings from '@/settings'
// 創建axios實例
const service = axios.create({
  baseURL: process.env.VUE_APP_BASE_API,
  timeout: 10000
})

// 請求攔截器
service.interceptors.request.use(
  config => {
    if (store.getters.token) {
      // 讓請求攜帶token
      config.headers['token'] = getToken(USER_TOKEN)
    }
    return config
  },
  error => {
    // console.log('request-error:', error)

    return Promise.reject(error)
  }
)

// 響應攔截器
service.interceptors.response.use(
  response => {
    // 攔截文件流
    const headers = response.headers
    if (headers['content-type'] === 'application/octet-stream') {
      return response.data
    }

    const res = response.data
    if (res.code === 200) {
      //響應成功
      return res.data
    } else {
      // 2004:  token 無效; 2005:  token 過期;
      if (res.code === 2004 || res.code === 2005) {
        // to re-login 不在白名單中就提示重新登錄並且刷新當前頁面
        if (!defaultSettings.whiteList.includes(Router.history.current.path)) {
          Dialog.alert({
            message: '您必須重新登錄!'
          }).then(() => {
            console.log('重新登錄確定')
            store.dispatch('user/resetToken').then(() => {
              location.reload()
            })
          })
        }
      } else if (res.code === 2001 || res.code === 2003) {
        // 不需要彈窗的情況
        // 微信沒有綁定手機號的情況下
        return Promise.reject(res)
      } else { 
        /* 其他的情況 */
        Toast({
          message: res.msg || 'response error',
          duration: 5 * 1000
        })
        return Promise.reject(res)
      }
    }
  },
  error => {
    if (error.response.status > 500 && error.response.status < 506) {
      Toast('服務器錯誤')
    } else {
      Toast(error.msg)
    }
    return Promise.reject(error)
  }
)

export default service

請求的response攔截器那部分需要根據自身業務來,自己稍微改寫下就可以用。

  1. 在src下新建api目錄, 並且新建user.js,目錄結構如下

在這裏插入圖片描述
src/api/user.js

/* 用戶相關 */
import request from '@/utils/request'

/**
 * @export
 * @param {*}
 * @returns
 */
export function test() {
  return request({
    url: '/test',
    method: 'get'
  })
}

/**
 * @description 手機驗證碼登錄
 * @export
 * @param {*} data
 * @returns
 */
export function loginByPhone(data) {
  return request({
    url: '/loginByPhone',
    method: 'post',
    data
  })
}

好的今天主要配置就這麼多。下一篇主要是配置路由攔截器,以及微信登錄的設計邏輯,其中包含微信測試授權登錄的小技巧

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章