基于 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...

相关文章
相关标签/搜索