vue-element-adminV4.0版本遇到的一些問題及解決方案

V4.0與之前版本的差異

在vue-element-admin V4.0之前的版本,根目錄下存在2個文件夾:

  • build文件夾包含構建相關的build及webpack等文件。
  • config文件夾包含各種環境下的配置。

通常地,各種基礎設置都能在這2個文件夾下的配置文件中完成。
然而,V4.0版本變化較大:

  • build文件夾保留,但其下只剩了一個index.js文件。
  • config文件夾去掉。
  • 根目錄下多了2種重要文件:.env.xxx,vue.config.js。

其中,.env.xxx爲開發/生產環境的配置文件,vue.config.js爲整個工程的全局配置文件。

修改服務端地址

要修改服務端地址,打開.env.development,這是開發環境的配置文件。其下有個配置是:

VUE_APP_BASE_API = '/dev-api'

將該處地址修改爲正確的服務端地址:

VUE_APP_BASE_API = 'http://192.168.15.98:9098/template'

即可。

服務端地址前被附加http://localhost:9527/

有時,按上述更改設置後請求,會發現在路徑前附加了http://localhost:9527/,類似:

http://localhost:9527/192.168.15.98:9098/template/test

曾經一度排查了很久,將Mock屏蔽也不起作用。有兩種解決方法:

  1. 修改vue.config.js中的devServer,配置proxy指向服務端。
  2. 從github上重新下載最新的vue-element-admin。

然而在採用這兩種方法的同時也修改了其他的設置,且上述問題後續沒有再遇到,故而個人不太確定是否是這兩種解決方案起作用了。
同時,不知道是否與運行環境爲VS Code有關。

啓動工程測試時會打開2個頁面

每次運行npm run dev,會啓動默認瀏覽器並打開2個頁面。
解決方法爲:

  1. 打開vue.config.js,其中devServer.open默認爲true,更改爲false
  2. 打開package.json,其中scripts.dev默認爲vue-cli-service serve,將其修改爲:vue-cli-service serve --open

自定義token

接收token並保存

打開 /src/store/modules/user.js,其中actions下的login()爲調用用戶自定義登錄接口並進行回調處理。
在這裏獲取到的response即爲服務端返回的數據。從中即可獲取token值。
在下方會調用commit()setToken()來將這個token值保存起來。

將token附加到請求的header

打開 /src/utils/request.js,有這樣一段代碼:

// request interceptor
service.interceptors.request.use(
  config => {
    // do something before request is sent

    if (store.getters.token) {
      // let each request carry token
      // ['X-Token'] is a custom headers key
      // please modify it according to the actual situation
      config.headers['X-Token'] = getToken()
    }
    return config
  },
  error => {
    // do something with request error
    console.log(error) // for debug
    return Promise.reject(error)
  }
)

其中config.headers['X-Token'] = getToken()這行代碼的作用就是取出token值並附加到header的X-Token屬性中。若要更改屬性名,或者在token值前附加特定的字符串,可以在這裏進行修改。
另外,若需要對特定的返回code做攔截,也是在該文件中進行

動態路由

vue-element-admin的動態路由方案是將所有路由都在web端寫好,然後根據服務端返回的roles來決定顯示哪些。
實際情況往往需要將路由存儲在服務端,web端請求到數據後動態生成路由。

思路

vue-element-admin的動態路由流程爲:

  1. 用戶輸入用戶名和密碼後點擊登錄,調用login()接口獲取token並緩存。
    其代碼位於 /store/user.js
login({ commit }, userInfo) {
  const { username, password } = userInfo
  return new Promise((resolve, reject) => {
    login({ username: username.trim(), password: password }).then(response => {
      const { data } = response
      commit('SET_TOKEN', data.token)
      setToken(data.token)
      resolve()
    }).catch(error => {
      reject(error)
    })
  })
},
  1. 調用getInfo()接口獲取用戶信息並緩存,其中包含roles屬性。
    其代碼位於 /store/user.js
getInfo({ commit, state }) {
  return new Promise((resolve, reject) => {
    getInfo(state.token).then(response => {
      const { data } = response

      if (!data) {
        reject('Verification failed, please Login again.')
      }

      const { roles, name, avatar, introduction } = data

      // roles must be a non-empty array
      if (!roles || roles.length <= 0) {
        reject('getInfo: roles must be a non-null array!')
      }

      commit('SET_ROLES', roles)
      commit('SET_NAME', name)
      commit('SET_AVATAR', avatar)
      commit('SET_INTRODUCTION', introduction)
      resolve(data)
    }).catch(error => {
      reject(error)
    })
  })
}
  1. 根據roles屬性對本地所有的路由進行過濾,只保留允許roles查看的路由,掛到router中。
    其代碼位於 /src/permission.js
try {
  // get user info
  // note: roles must be a object array! such as: ['admin'] or ,['developer','editor']
  const { roles } = await store.dispatch('user/getInfo')

  // generate accessible routes map based on roles
  const accessRoutes = await store.dispatch('permission/generateRoutes', roles)

  // dynamically add accessible routes
  router.addRoutes(accessRoutes)

  // hack method to ensure that addRoutes is complete
  // set the replace: true, so the navigation will not leave a history record
  next({ ...to, replace: true })
} catch (error) {
  // remove token and go to login page to re-login
  await store.dispatch('user/resetToken')
  Message.error(error || 'Has Error')
  next(`/login?redirect=${to.path}`)
  NProgress.done()
}

可以看到相關邏輯位於try塊中。

根據此流程,可知要滿足需求,需要更改其中的2和3兩步。設登錄的賬戶都有個roleId屬性,可通過請求服務端相關接口並傳入roleId來獲取一個路由數據數組:

  1. 用戶輸入用戶名和密碼後點擊登錄,調用login()接口獲取token並緩存。
  2. 調用getInfo()接口獲取用戶信息並緩存,其中包含roleId屬性。
  3. 調用服務端接口並傳入roleId,來獲取路由數據數組。
  4. 根據路由數據數組來動態生成路由並掛到router上。

其中第3和第4步的邏輯應在 /src/permission.jstry中實現。提供一段參考代碼:

try {
  // get user info
  const { roleId } = await store.dispatch('user/getInfo')

  // 根據roleId從服務端獲取addRoutes
  const addRoutes = await store.dispatch('myRoute/generateRoutes', { roleId })
  // 必須設置router.options.routes後,router.addRoutes纔會生效
  router.options.routes = constantRoutes.concat(addRoutes)
   // 動態添加可訪問路由表。注意已有的靜態路由不要設置,否則會提示重複
  router.addRoutes(addRoutes)
  // hack method to ensure that addRoutes is complete
  // set the replace: true, so the navigation will not leave a history record
  next({ ...to, replace: true })
} catch (error) {
  // remove token and go to login page to re-login
  await store.dispatch('user/resetToken')
  Message.error(error || '驗證失敗,請重新登錄')
  next(`/login?redirect=${to.path}`)
  NProgress.done()
}

這裏在 /src/modules/ 文件夾下自定義了myRoute.js負責路由數據的請求及獲取到數據後路由結構的生成,並將myRoute設置到 /store/getter.js/store/index.js 中。

404攔截

需要注意的一點是,需要將 /src/router/index.jsconstantRoutes的最後兩個404路由單獨導出,加載動態路由的時候,作爲動態路由的一部分獨立掛到動態路由的最後。

// 404 頁面一定要最後加載,如果放在constantRouterMap一同聲明瞭404,後面的所以頁面都會被攔截到404
export const errorRouterMap = [
  { path: '/404', component: () => import('@/views/404'), hidden: true },
  { path: '*', redirect: '/404', hidden: true }
]

也就是說,對於動態路由而言,其構成爲:

靜態路由部分 + 動態路由部分 + 2個404路由

靜態路由(constantRouterMap)即無論哪個賬號登錄都可訪問的路由,直接存儲在前端,通常至少有2個路由:登錄(/login)和首頁(/)。
動態路由即從服務端獲取到路由數據後,拼裝出的路由數組。

webpackEmptyContext:動態加載失敗

路由的結構官方文檔有詳細說明,一個簡單的路由結構爲:

{
  path: '/icon',
  component: Layout,
  children: [
    {
      path: 'index',
      component: () => import('@/views/icons/index'),
      name: 'Icons',
      meta: { title: 'Icons', icon: 'icon', noCache: true }
    }
  ]
}

其中非常重要的一個屬性是component,其設置爲() => import('@/views/icons/index')
靜態路由這樣寫是沒問題的。然而,若動態路由需要在import()中使用變量,明明組件文件都在,路由地址也對,但瀏覽器就會提示:

vue-router.esm.js:1921 Error: Cannot find module ‘@/views/icons/index’ at webpackEmptyContext

這是webpack導致的,不能在import()中使用變量。解決方法爲將import()更改爲require()。即:

// 定義組件變量
const newComponent = 'icons/index'
// 無法運行的寫法
component: () => import('@/views/' + newComponent)
// 正常運行的寫法
component: resolve => require([`@/views/` + newComponent], resolve)

如上,即可解決。

命令與配置文件

新版本下的配置文件直接放在了根目錄下,默認有3個:

  • .env.development
  • .env.prodution
  • .env.staging

而package.json中主要的運行和打包命令也有3個:

  • “dev”: “vue-cli-service serve”
  • “build:prod”: “vue-cli-service build”
  • “build:stage”: “vue-cli-service build --mode staging”

這3個命令,分別對應上面的3個配置文件。
配置文件的格式統一爲:

.env. + 名稱

引用時,在命令後加:--mode 名稱即可。
特殊地,若運行的是serve命令且沒有加--mode 名稱,則調用 .env.development ;若運行的是build命令且沒有加--mode 名稱,則調用 .env.prodution
即:
vue-cli-service serve等同於vue-cli-service serve --mode development
vue-cli-service build等同於vue-cli-service build --mode prodution

這樣可以針對不同的環境創建多個配置文件,然後運行對應的命令即可使用對應的配置來運行/打包,而不需要每次都修改配置文件。

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