基於 Vue 的後臺管理系統前端實踐

初始化項目

使用 Vue-cli3 初始化項目1javascript

安裝 Element UI
安裝 Vue-i18n,作相關配置2,3html

原則上須要對 Element 也作 I18n 的處理,可是我以爲 Element 中已經有很完善的多語言翻譯,而 Element 自身尚不支持 Vue-i18n 7+ 版本,要特殊處理,因此就算了,這裏直接使用了 Element 自帶的方法。

項目結構

.
|-- babel.config.js
|-- package.json
|-- public
|   `-- index.html
|-- src
|   |-- App.vue
|   |-- assets 
|   |   |-- override-element-ui.less      // 覆蓋 Element 默認樣式
|   |   `-- image                         // 圖片文件目錄
|   |-- components  // 全部非 `<router-view />` 中顯示的頁面的 Vue 組件
|   |   |-- mainMenu.vue    // 左側導航
|   |   |-- pureTitle.vue   // 通用的 Title 組件,項目中有多個頁面會用到
|   |   |-- search.vue      // 通用的 Search 頭部組件,一樣會用到
|   |   `-- userCenter.vue  // 字面意思用戶中心,目前只須要顯示用戶名,handle 用戶退出操做。可預見:下拉菜單操做、頭像顯示等...
|   |-- main.js     // Vue-cli 生成的項目基本文件
|   |-- router.js   // Vue-cli 生成,路由
|   |-- store   // Vuex相關
|   |   |-- index.js
|   |   |-- mutationTypes.js
|   |   `-- module  // 這裏按照路由劃分了 module,不必定對
|   |   |   |-- someModule.js
|   |   |   `-- elseModule.js
|   |-- utils
|   |   |-- i18n  // 國際化支持
|   |   |   |-- index.js
|   |   |   `-- lang  // 語言字符串文件
|   |   |       |-- en.json
|   |   |       `-- zh.json
|   |   |-- request  // 處理全部 ajax 請求
|   |   |   |-- index.js    // 基於 axios,封裝了須要用到的方法,例如 token 的處理
|   |   |   |-- someComponentName.js  // 基於組件分割的請求,在 Vuex action 中調用獨立方法
|   |   |   `-- elseComponentName.js
|   |   |-- timeHelper.js  // 時間戳轉換幫助函數
|   |   `-- userHelper.js  // 保存用戶信息幫助函數
|   `-- views  // `<router-view /> 下內容顯示組件`
|       |-- Home.vue
|       |-- Login.vue
|       |-- MainPage.vue
|       |-- Manage.vue
|       `-- Verify.vue
|-- .env.development // 環境變量 process.env
|-- .env.production  // 生產環境下的環境變量
`-- vue.config.js    // 更改 Vue-cli 默認配置

Element

按需加載

先看一張項目初始狀態下,全局引入 Element & 按需引入打包大小的對比:vue

全局引入

按需引入

How

參考:按需引入java

須要注意的地方是:ios

  1. 不用新建 .babelrc 文件,只須要更改 babel.config.js
  2. 使用 Vue-cli 初始化項目後, presets 中爲 ['@vue/app'],不須要更改成 element 官網中的 es2015 。緣由是 babel 的目前版本已經不推薦按照 JavaScript 版原本區分編譯目標。(// todo:添加相關連接)

可用的 babel.config.js 文件以下:git

module.exports = {
  presets: [
    ['@vue/app'],
  ],
  plugins: [
    [
      'component',
      {
        'libraryName': 'element-ui',
        "styleLibraryName": "theme-chalk"
      }
    ]
  ]
}

覆蓋默認樣式

正常狀況下,能夠經過直接修改對應 class 的內容修改。但含有 foo__bar 相似格式的樣式不會應用 scoped(由於不是同一個 Vue 實例),所以修改不會生效。github

能夠經過在 *.vue 中不使用 scope 來解決這個問題,Element 在 Issue 中也吐槽過。雖然我不認爲 scope 不是一個好的解決方案。ajax

所以,我選擇了在 main.js 中引入外部樣式表,例如override-element-ui.less,在外部樣式表中修改 foo__bar 類名的樣式。element-ui

路由設計

route.js 主要設計以下:json

routes: [
    {
      path: '/login',
      name: 'login',
      component: Login
    },
    {
      path: '/',
      name: 'home',
      component: Home,
      children: [
        {
          path: '/index',
          component: mainPage
        },
        {
          path: '/manage',
          component: manage,
          children: [
            {
              path: 'user/:uumsid&:userid',
              component: userDetail
            },
            {
              path: 'enterprise/:id',
              component: insDetail
            },
            {
              path: '/',
              component: manageList
            },
          ]
        },
        {
          path: '/verify',
          component: Verify,
        },
        {
          path: '/verify/:id',
          component: VerifyDetail
        },
        {
          path: '*',
          redirect: '/index'
        },
      ]
    },
  ]

555...不太想貼代碼,感受太亂、太長。

但不貼又說不清楚。

簡單來講,項目只分爲兩個部分。登錄頁和功能頁。

根路由綁定到了登錄後的首頁。

首頁包含頭部標題、左側菜單還有顯示內容的 <router-view />

所以,若是用戶僅輸入 location.host 訪問網站,會被帶到 ${host}/index 頁面。另外,訪問任意未被匹配的 path,都會重定向到 index 頁面。

這個時候會出現兩個分支:

判斷本地是否存在用戶信息是否存在 ? 進行正常操做 :重定向到登陸頁

如何保存用戶信息和登錄狀態

整個系統只須要分紅須要登陸和不需登錄兩個部分。

當前所開發的系統除登陸頁外,都須要登陸後訪問。

這個部分和路由設計強相關。什麼時候判斷用戶是否登陸在上一部分已經解釋過。

使用 localstorage 來持久化保存用戶信息,處理用戶刷新頁面以及一段時間內關閉瀏覽器後不用從新登陸的需求。(一段時間爲30min)

Home.vue 的 created() 鉤子中作以下操做:

created() {
      let userInfo = getUserFromLocal('userinfo')
      let verifiedUserInfo = (info) => {
        let now = new Date().getTime()
        const PASS_TIME = 1000 * 60 * 30 // 30min
        if (!info) return false
        if (now - info.time < PASS_TIME) return true
        return false
      }
      if (!!verifiedUserInfo(userInfo)) {
        this.onRefresh(userInfo)
      } else {
        this.$router.push({path: '/login'})
      }
    }

其中,getUserFromLocal 是在 userHelper.js 中寫的從 localstorage 中獲取數據的方法。經過判斷用戶信息是否存在以及是否過時來決定是跳轉到登陸頁仍是直接使用當前已有的用戶信息。

// 我不以爲這是一個很完美的方案,但根據我搜索的資料來看,確實沒有詳細說過這方面內容的文章。因此,期待能看到更好的方案。

如何優雅的觸發表單驗證

Element表單驗證方法

但沒有給出 async-validator 支持的表單驗證觸發方式。

通過查找,支持的觸發方式有: submit, blur, input

我選擇的方案是在規則中使用 submit 時驗證(指表單提交時觸發,所以實際狀況中永遠不會觸發,但能知足需求,即手動觸發驗證。)

同時,在點擊登陸或者發送請求按鈕時,調用 this.$refs.[formEl].validate((boolean) => { // callback})

清除表單驗證結果: <form-item @focus='clearValidate'>...

當表單項不少時,也能夠在<form >...中調用clearValidate(prop), prop 指表單項的 name 值。

輸入框內容過濾

產品有一個需求是,在搜索用戶信息時,只能經過郵箱搜索,而且只能輸入字母、數字以及@。所以,咱們須要對用戶輸入數據時即時進行過濾。(別問我爲何不在用戶輸入完成後提示錯誤,這是需求。

我選擇了 watch 輸入框 value 的值:

value(val) {
  this.$nextTick(() => {
    this.value = this.reg ? val.replace(this.reg, '') : val
  })
}

這裏的坑就是須要在 $nextTick() 中更新 value 值,由於 DOM 元素這時才刷新。

這個需求有許多 blog 都給出過不一樣的解決方案,能夠多看看選擇一下。

獲取數據時的細節問題

ajax 的封裝

基於 axios

./utils/request/index.js:

import axios from 'axios'

// BASE_HOST 經過 .env.[development|production] 配置。注意:每一個變量都要用 VUE_APP做爲前綴,不然不能識別
const BASE_HOST = process.env.VUE_APP_API_BASE
const TIME_OUT = 1000 * 10

/**
 *
 * @param URL {string}
 * @param params {Object}
 */
export function POST(URL, params) {
  if (!URL.includes(BASE_HOST)) URL = BASE_HOST + URL
  return axios({
    method: 'post',
    url: URL,
    data: params,
    timeout: TIME_OUT
  })
}

export function GET(URL, data) {
  // like POST
}

/**
 * @param URL {string}
 * @param token {string}
 * @param params {Object}
 */
export function GET_WITH_TOKEN(URL, token, params) {
  if (!URL.includes(BASE_HOST)) URL = BASE_HOST + URL
  return GET(URL, {
    params,
    headers: {
      token: token
    },
    timeout: TIME_OUT
  })
}

封裝了基本的 GET,POST等方法,沒想到什麼特別的做用,也沒有想到很差的地方,留着爲了以防萬一。(事實上,也用上了。好比超時處理。可是,超時處理在下一層也能作。但這樣的話登錄就須要單獨設置超時了。)

GET_WITH_TOKEN(以及 POST_WITH_TOKEN 等) 用於須要登陸鑑權的接口

顯示 Loading 狀態

在等待api返回數據時能夠用loading告訴用戶頁面正在加載。Element 提供的 Loading組件

通常切換路由後,會在組件的 created() 方法中發送請求。這種狀況下應該在 nextTick()(或者mounted()中) 中調用 loading ,避免頁面切換時找不到 DOM,出現全屏 loading 或頁面閃爍。

尚不明確(梗

  1. I18n 的方案不夠完善,如今作到了語言文件的熱切換,可是對業務來說彷佛沒有什麼必要。沒有作語言字符串的按需加載(事實上是作了但沒有實現),但這個彷佛比熱切更重要一點。
  2. 按照 Vue-router 實現了懶加載及文件命名,但瀏覽器彷佛仍然會下載全部 JS 文件,在 JS Tab 中能夠看到只下載了當前頁用到的文件。
  3. Vuex 是否有必定要用的必要?相似的管理系統涉及到不一樣頁面之間的交互都不多。在該系統中徹底沒有,雖然有不一樣組件的交互,但都能較簡單的把狀態提高到其父組件。所以,Vuex 惟一必需要用的理由就是保存用戶狀態。由於登錄後的接口都須要 token 校驗,保存在 Vuex 中能夠很是方便的使用。

參考資料

  1. Vue CLI 3
  2. Vue I18n
  3. 如何讓一個vue項目支持多語言(vue-i18n)

同步在博客:https://blog.life1st.me/artic...

相關文章
相關標籤/搜索