企业微信 => 接入第三方vue应用 第二阶段:优化成无感授权登录 前端部分

219 阅读5分钟
  • 第一阶段的登录虽然也算完成了,但是并不是最优的登陆方式
  • 后面有了新的思路可以在路由守卫里面进行操作
  • 下面先记录一下思路,回顾一下

1.重构一下router模块

在router模块下面有一个index.js

import Vue from 'vue'
import VueRouter from 'vue-router'
import store from '@/store'

import homeRouter from './modules/home'
import clientRouter from './modules/client'
import couponRouter from './modules/coupon'
import seaRouter from './modules/sea'
import projectRouter from './modules/project'
import userRouter from './modules/user'

import { axUrl, appid } from '@/utils/api_env'
import { getToken, setToken, removeToken } from '@/utils/auth'


// 导入组件
const Login = () => import('@/views/Login/Login.vue')
const WeChatLogin = () => import('@/views/Login/WeChatLogin.vue')
const Layout = () => import('@/views/Layout/Layout.vue')

const Home = () => import('@/views/Home/Home.vue')
const User = () => import('@/views/User/User.vue')
const HighSea = () => import('@/views/HighSea/index.vue')
const Client = () => import('@/views/Client/index.vue')
const Project = () => import('@/views/Project/index.vue')
const Data = () => import('@/views/Data/index.vue')

Vue.use(VueRouter)
const routes = [
  // 登录组件的路由规则
  { path: '/login', component: Login, name: 'login' },
  { path: '/weChatLogin', component: WeChatLogin, name: 'weChatLogin' },
  {
    path: '/',
    component: Layout,
    // redirect: '/home',
    children: [
      // 默认子路由
      { path: '/home', component: Home, name: 'home' },
      { path: '/user', component: User, name: 'user' },
      { path: '/highSea', component: HighSea, name: 'highSea' },
      { path: '/client', component: Client, name: 'client' },
      { path: '/project', component: Project, name: 'project' },
      { path: '/data', component: Data, name: 'data' },
    ],
  },
  ...homeRouter,
  ...clientRouter,
  ...couponRouter,
  ...seaRouter,
  ...projectRouter,
  ...userRouter
]

const router = new VueRouter({
  routes,
})
function isIos () {
  const u = navigator.userAgent
  return u.indexOf('iPhone') > -1 || u.indexOf('Mac OS') > -1
}
// 获取真实有效微信签名URL encodeURIComponent
function getWechatSignUrl (to) {
  if (!isIos()) {
    return axUrl + '#' + to.path
  } else {
    // 此处$appHost需要自行处理
    return axUrl + '#' + to.path
  }
}

const getQuery = name => {
  var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i')
  var r = window.location.search.substr(1).match(reg)
  if (r != null) {
    return unescape(r[2])
  }
  return null
}

const pathNext = (to, next) => {
  if (to.path === '/') {
    store.commit('setWechatSignUrl/setWechatSignUrl', getWechatSignUrl(to))
    next('/home')
  } else {
    store.commit('setWechatSignUrl/setWechatSignUrl', getWechatSignUrl(to))
    next()
  }
}

let suite_access_token = null
let open_userid = null
let user_ticket = null
let userToken = null
// 路由守卫
router.beforeEach(async (to, from, next) => {
  if (to.meta.title) {
    document.title = to.meta.title
  }
  window.console.log('我是无感授权第60版', 1)
  if (to.path === '/login') {
    next()
  } else {
    userToken = getToken()
    if (!userToken) {
      window.console.log('Token', userToken)
      if (!store.state.setWechatSignUrl.wechatOptions.userid) {
        // 第一次获取code
        if (getQuery('code')) {
          const { data, code } = await store.dispatch('setWechatSignUrl/getAuth', {
            code: getQuery('code')
          })
          window.console.log('我是无感授权第60版', 2, data, code)
          suite_access_token = data.suite_access_token
          if (code == 200) {
            const { data, code } = await store.dispatch('setWechatSignUrl/getUserInfo', {
              code: getQuery('code'),
              suite_access_token: suite_access_token
            })
            // 在授权账户没有绑定手机号时候,先设置一下数据
            store.commit('setWechatSignUrl/SET_USER_INFO', data)
            window.console.log('我是无感授权第60版', 3, data, code)
            open_userid = data.open_userid
            user_ticket = data.user_ticket
            if (code == 200) {
              const { data, code } = await store.dispatch('setWechatSignUrl/getUserCheck', open_userid)
              window.console.log('我是无感授权第60版code', 4, data, code)
              userToken = data.userToken
              // const ErrCode = 10003 // TODO:这里测试完要删掉的 和request里面的要关联起来
              if (code == 10003) {
                window.console.log('我是哪个,code,data', data, code);
                next('/login')
              } else {
                store.commit('setWechatSignUrl/SET_TOKEN', userToken)
                setToken(userToken)
                const { data, code } = await store.dispatch('setWechatSignUrl/getUserDetail', {
                  suite_access_token: suite_access_token,
                  user_ticket: user_ticket
                })
                window.console.log('userInfoData', data, code);
                store.commit('setWechatSignUrl/SET_USER_INFO', data)
              }
            }
          }
          pathNext(to, next)
          window.console.log('我走了login2');
        } else {
          const { data: res} = await store.dispatch('setWechatSignUrl/getCode')
          window.console.log('我是无感授权第60版', 5, res)
          window.location.href = res.redirectUrl
        }
      } else {
        pathNext(to, next)
      }
    } else {
      pathNext(to, next)
    }
  }
})
export default router

2.在utils/api_env里面定义一下我们的环境

const baseApiEnv = () => {
  switch (process.env.NODE_ENV) {
    case 'development':
      return {
        baseUrl: '/api', // 填写开发环境代理地址
        axUrl: 'https://xxxxxx.cn/', // 域名
        appid: 'wwxxxx' // xx
      }
    case 'production':
      return {
        baseUrl: '/prod-api', // 填写生产环境代理地址
        axUrl: 'https://tesxxxx.cn/', // 域名
        appid: 'ww2xxx7' // xx
      }
    case 'test':
      return {
        baseUrl: '/prod-api', // 填写测试环境代理地址
        axUrl: 'https://testxxx.cn/', // 域名
        appid: 'wwxxxx' // x
      }
    default:
      return {
        baseUrl: '',
        axUrl: '',
        appid: ''
      }
  }
}
export const { baseUrl, axUrl, appid } = baseApiEnv()

3.在utils/auth里面定义一下我们的token设置和获取

  • 注意这里需要去下载cookie依赖
import Cookies from 'js-cookie'

const TokenKey = 'userToken'

export function getToken() {
  return Cookies.get(TokenKey)
}

export function setToken(token) {
  // return Cookies.set(TokenKey, token)
  Cookies.set(TokenKey, token)
}

export function removeToken() {
  Cookies.remove(TokenKey)
}

4.在utils/request里面重构一下我们的响应拦截器和请求拦截器

import axios from 'axios'
import store from '@/store/index'
import JSONbig from 'json-bigint'
import router from '@/router/index'
import { Toast, Dialog} from 'vant';
import { baseUrl, axUrl, appid } from '@/utils/api_env'

import { getToken, removeToken } from '@/utils/auth'

// 处理大数问题
const transBigInt = data => {
  if (!data) return ''
  try {
    return JSONbig.parse(data)
  } catch {
    return JSON.parse(data)
  }
}

const instance = axios.create({
  // 请求根路径
  baseURL: process.env.VUE_APP_BASE_API,
  transformResponse: [transBigInt],
  // 设置超时时间
  timeout: 10000,
  headers: {
    'Cache-Control': 'no-cache'
  }
})

// post请求的时候,我们需要加上一个请求头,所以可以在这里进行一个默认的设置
// 即设置post的请求头为application/x-www-form-urlencoded;charset=UTF-8
instance.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8'

let toast = null
// 请求拦截器
instance.interceptors.request.use(
  (config) => {
    // 获取 token 值
    const token = getToken()

    // const token = store.state.setWechatSignUrl.token ? store.state.setWechatSignUrl.token : false
    if (token) config.headers['userToken'] = token
    if (config.headers.loading !== false) {
      toast = Toast.loading({
        duration: 3000, // 持续展示 toast
        message: '加载中...',
        forbidClick: true,
        loadingType: 'spinner'
      })
    }
    if (config.method === 'get') {
      config.params = {
        ...config.params,
        _t: Date.parse(new Date()) / 1000,
      }
    }
    return config
  },
  function(error) {
    toast && toast.clear()
    return Promise.reject(error)
  }
)


// 响应拦截器
instance.interceptors.response.use(
  res => {
    toast && toast.clear()
    // const ErrCode = 10003
    // if (ErrCode == 10003) { // TODO:这里测试完要删掉的 和route里面的要关联起来
    if (res.data.code == 10003) {
      removeToken()
      store.commit('setWechatSignUrl/SET_USER_INFO', {})
      // 清除token
      router.push({
        path: '/login',
      })
    } else if (res.data.code == 500) {
      errorMsg(res.data.msg)
    } else {
      if (res.data.code != 200) {
        Dialog.confirm({
          title: '提示',
          message: '请重新授权'
        })
          .then(() => {
          // on confirm
          // token失效,清除本地token, 跳转授权重新取code
            store.dispatch('setWechatSignUrl/clearToken')
            removeToken()
            const wechatUrl = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=xxxxxx&redirect_uri=https%3A%2F%2F你的地址.xxx.cn&response_type=code&scope=snsapi_privateinfo&state=STATE#wechat_redirect`
            window.location.href = wechatUrl
          })
          .catch(() => {
            console.log('取消操作')
          })
      } else {
        return Promise.resolve(res.data || {})
      }
    }
  },
  (error) => {
    // 服务器返回不是 2 开头的情况,会进入这个回调
    // 可以根据后端返回的状态码进行不同的操作
    const responseCode = error.response.status
    switch (responseCode) {
      // 401:未登录
      case 401:
        // 跳转登录页
        router.replace({
          path: '/login',
          query: {
            redirect: router.currentRoute.fullPath,
          },
        })
        break
      // 403: token过期
      case 403:
        // 弹出错误信息
        // errorMsg("登录信息过期,请重新登录");
        // 清除token
        localStorage.removeItem('token')
        // 跳转登录页面,并将要浏览的页面fullPath传过去,登录成功后跳转需要访问的页面
        setTimeout(() => {
          router.replace({
            path: '/login',
            query: {
              redirect: router.currentRoute.fullPath,
            },
          })
        }, 1000)
        break
      // 404请求不存在
      case 404:
        errorMsg('网络请求不存在')
        break
        // 500:yuangongbu9cunzai
      case 500:
        errorMsg(error.response.data.msg)
        break
      // 其他错误,直接抛出错误提示
      default:
        errorMsg(error.response.data.msg)
    }
    return Promise.reject(error.data || error)
  }
)
const errorMsg = (msg) => {
  const dom = document.getElementsByClassName('el-message--error') // TODO:要改的提示信息
  if (dom.length <= 0) {
    Toast({
      message: msg || '网络异常',
      type: 'error',
    })
  }
}
export default instance

5.在store/index.js里面重构一下我们的Vuex

import Vue from 'vue'
import Vuex from 'vuex'
import getters from './getters'

Vue.use(Vuex)

const modulesFiles = require.context('./modules', true, /\.js$/)

// it will auto require all vuex module from modules file
const modules = modulesFiles.keys().reduce((modules, modulePath) => {
  // set './xx.js' => 'xx'
  const moduleName = modulePath.replace(/^\.\/(.*)\.\w+$/, '$1')
  const value = modulesFiles(modulePath)
  modules[moduleName] = value.default
  return modules
}, {})

const store = new Vuex.Store({
  modules,
  getters
})

export default store
  • 设置modules
  • 在这里插入图片描述
  • 定义一个setWechatSignUrl.js

import {getLogin, getSuiteAccessToken, getCompanyUserInfo, userCheck, getCompanyUserDetail, bindMobileByCode} from '@/api/wechat'
import { getToken, setToken, removeToken } from '@/utils/auth'

// 全局判断是否IOS方法
function isIos () {
  const u = navigator.userAgent
  return u.indexOf('iPhone') > -1 || u.indexOf('Mac OS') > -1
}
const state = {
  wechatOptions: {
    corpid: '',
    userid: '',
    name: '',
    gender: '',
    avatar: '',
    qr_code: ''
  },
  token: '',
  wechatSignUrl: '',
  isAndroid:
    navigator.userAgent.indexOf('Android') > -1 ||
    navigator.userAgent.indexOf('Linux') > -1,
  isIOS: !!navigator.userAgent.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/) // ios终端
}
const mutations = {
  setWechatSignUrl: (state, wxSignUrl) => {
    // 关键点
    // IOS仅记录第一次进入页面时的URL
    // IOS微信切换路由实际URL不变,只能使用第一进入页面的URL进行签名
    // if (isIos() && state.wechatSignUrl !== '') {
    //   return
    // }
    state.wechatSignUrl = wxSignUrl
  },
  SET_USER_INFO: (state, params) => {
    state.wechatOptions = Object.assign({}, state.wechatOptions, params)
  },
  SET_TOKEN: (state, params) => {
    state.token = params
  },
  CLEAR_USER: state => {
    state.wechatOptions = {}
  }
}
const actions = {
  // 1.获取授权链接
  getCode ({ commit, state, dispatch }, params) {
    return getLogin(params)
  },
  // 2.授权token
  getAuth ({ commit, state, dispatch }, params) {
    return getSuiteAccessToken(params)
  },
  // 3.获取信息
  getUserInfo({ commit, state, dispatch }, params) {
    return getCompanyUserInfo({params})
  },
  // 4.user login userCheck
  getUserCheck({ commit }, userInfo) {
    const open_userid = userInfo
    return userCheck({open_userid})
  },
  // 5.获取访问用户敏感信息
  getUserDetail({ commit, state, dispatch }, params) {
    return getCompanyUserDetail({params})
  },
  // 6.如果是没有绑定过手机号就走这里 user wechatlogin
  wechatLogin({ commit, state, dispatch }, userInfo) {
    return bindMobileByCode(userInfo)
  },
}

export default {
  namespaced: true,
  state,
  mutations,
  actions
}
  • 然后为了便捷操作设置getters.js
const getters = {
  sidebar: state => state.app.sidebar,
  device: state => state.app.device,
  token: state => state.user.token,
  avatar: state => state.user.avatar,
  name: state => state.user.user_name,
  part_id: state => state.user.part_id,
  btnName: state => state.app.btnName,
  user_id: state => state.user.id,
  pre_path: state => state.path.pre_path,
  getWechatUrl: state => state.setWechatSignUrl.wechatSignUrl,
  wechattoken: state => state.setWechatSignUrl.token,
  wechatOptions: state => state.setWechatSignUrl.wechatOptions
}
export default getters

6.这样我们无感授权前端部分基本ok了

7.第三阶段就是完成一系列调用jssdk去实现企微互联功能