基于 vue-element-admin 的项目总结

Posted ༺初辰༻

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了基于 vue-element-admin 的项目总结相关的知识,希望对你有一定的参考价值。

一、项目环境搭建

1.vue-element-admin的了解和介绍

vue-element-admin 是一个后台前端解决方案,它基于 vue 和 element-ui实现。它使用了最新的前端技术栈,内置了 i18 国际化解决方案,动态路由,权限验证,提炼了典型的业务模型,提供了丰富的功能组件,它可以帮助你快速搭建企业级中后台产品原型。

2.项目模板启动和目录介绍

2.1 git拉取基础项目模板

$ git clone https://github.com/PanJiaChen/vue-admin-template.git shop

2.2 安装项目依赖(定位到项目目录下)

npm install #安装依赖

2.3 启动项目

npm run dev #启动开发模式的服务

在这里插入图片描述
项目运行完毕,浏览器会自动打开基础模板的登录页,如上图

2.4 目录结构

├── build                      # 构建相关
├── mock                       # 项目mock 模拟数据
├── public                     # 静态资源
│   │── favicon.ico            # favicon图标
│   └── index.html             # html模板
├── src                        # 源代码
│   ├── api                    # 所有请求
│   ├── assets                 # 主题 字体等静态资源
│   ├── components             # 全局公用组件
│   ├── icons                  # 项目所有 svg icons
│   ├── layout                 # 全局 layout
│   ├── router                 # 路由
│   ├── store                  # 全局 store管理
│   ├── styles                 # 全局样式
│   ├── utils                  # 全局公用方法
│   ├── vendor                 # 公用vendor
│   ├── views                  # views 所有页面
│   ├── App.vue                # 入口页面
│   ├── main.js                # 入口文件 加载组件 初始化等
│   └── permission.js          # 权限管理
│   └── settings.js          # 配置文件
├── tests                      # 测试
├── .env.xxx                   # 环境变量配置
├── .eslintrc.js               # eslint 配置项
├── .babelrc                   # babel-loader 配置
├── .travis.yml                # 自动化CI配置
├── vue.config.js              # vue-cli 配置
├── postcss.config.js          # postcss 配置
└── package.json               # package.json

我们在做项目时 其中最关注的就是src目录, 里面是所有的源代码和资源, 至于其他目录, 都是对项目的环境和工具的配置。

3.API模块和请求封装模块介绍

axios的拦截器原理如下
在这里插入图片描述

3.1 axios拦截器

const service = axios.create({
  baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
  // withCredentials: true, // send cookies when cross-domain requests
  timeout: 5000 // 超时时间
})

3.2 请求拦截器

service.interceptors.request.use(
  config => {
    if (store.getters.token) {
      config.headers['X-Token'] = getToken()
    }
    return config
  },
  error => {
    return Promise.reject(error)
  }
)

3.3 响应拦截器

service.interceptors.response.use(
  response => {
    const res = response.data
    if (res.code !== 20000) {
      Message({
        message: res.message || 'Error',
        type: 'error',
        duration: 5 * 1000
      })
      if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
        MessageBox.confirm('You have been logged out, you can cancel to stay on this page, or log in again', 'Confirm logout', {
          confirmButtonText: 'Re-Login',
          cancelButtonText: 'Cancel',
          type: 'warning'
        }).then(() => {
          store.dispatch('user/resetToken').then(() => {
            location.reload()
          })
        })
      }
      return Promise.reject(new Error(res.message || 'Error'))
    } else {
      return res
    }
  },
  error => {
    Message({
      message: error.message,
      type: 'error',
      duration: 5 * 1000
    })
    return Promise.reject(error)
  }
)

3.4 api模块的单独封装

import request from '@/utils/request'
export function login(data) {
  return request({
    url: '/vue-admin-template/user/login',
    method: 'post',
    data
  })
}
export function logout() {
  return request({
    url: '/vue-admin-template/user/logout',
    method: 'post'
  })
}

二、登录模块

1.设置固定的本地访问端口和网站名称

1.1 本地服务端口:在 vue.config.js 中进行设置

vue.config.js 就是vue项目相关的编译,配置,打包,启动服务相关的配置文件,它的核心在于webpack,但是又不同于webpack,相当于改良版的webpack。
在这里插入图片描述
在项目下, 我们发现了 .env.development 和 .env.production 两个文件
development => 开发环境
production => 生产环境
当我们运行npm run dev进行开发调试的时候,此时会加载执行 .env.development 文件内容
当我们运行npm run build:prod进行生产环境打包的时候,会加载执行 .env.production 文件内容

1.2 网站名称

网站名称实际在configureWebpack选项中的name选项,通过阅读代码,我们会发现name实际上来源于src目录下的 settings.js 文件。

module.exports = {
  title: '小优电商后台管理系统',
  sidebarLogo: true
}

2.登录页面的基础布局

2.1 设置头部背景

<div class="title-container">
  <h3 class="title">
    <img src="@/assets/common/login-logo.png" alt="">
  </h3>
</div>

2.2 设置背景图片

.login-container {
  background-image: url("https://img0.baidu.com/it/u=3612597965,1770541226&fm=26&fmt=auto&gp=0.jpg");
  background-position: center;
  background-size: 100% 100%;
}

在这里插入图片描述

3.登录表单的校验

3.1 用户名和密码的校验

loginRules: {
  username: [{ required: true, trigger: 'blur', validator: validateUsername }],
  password: [
    { required: true, trigger: 'blur' },
    { min: 6, max: 12, trigger: 'blur', message: '密码长度应该在6-12位之间' }
  ]
},

3.2 关于修饰符

@keyup.enter属于按键修饰符,如果我们想监听在按回车键的时候触发,可以如下编写

<input v-on:keyup.enter="submit">

4.Vue-Cl.i配置跨域代理

4.1 为什么会出现跨域?

当下,最流行的就是前后分离项目,也就是前端项目和后端接口并不在一个域名之下,那么前端项目访问后端接口必然存在跨域的行为.

4.2 解决开发环境的跨域问题

开发环境的跨域,也就是在vue-cli脚手架环境下开发启动服务时,我们访问接口所遇到的跨域问题,vue-cli为我们在本地开启了一个服务,可以通过这个服务帮我们代理请求,解决跨域问题

proxy: {
  '/api/private/v1/': {
    target: 'http://127.0.0.1:8888', // 跨域请求的地址
    changeOrigin: true // 只有这个值为true的情况下 才表示开启跨域
  }
}

5.封装单独的登录接口

在这里插入图片描述

export function login(data) {
  return request({
    url: 'login',
    method: 'post',
    data
  })
}

6.封装Vuex的登录Action并处理token

在这里插入图片描述

6.1 实现store/modules/user.js基本配置

// 状态
const state = {}
// 修改状态
const mutations = {}
// 执行异步
const actions = {}
export default {
  namespaced: true,
  state,
  mutations,
  actions
}

6.2 设置 token 的共享状态

const state = {
  token: null
}

6.3 操作 token

在 utils/auth.js 中,基础模板已经为我们提供了获取 token ,设置 token ,删除 token 的方法,可以直接使用

const TokenKey = 'xiaoyou'
export function getToken() {
  return localStorage.getItem(TokenKey)
}
export function setToken(token) {
  return localStorage.setItem(TokenKey, token)
}
export function removeToken() {
  return localStorage.removeItem(TokenKey)
}

6.4 初始化token状态 - store/modules/user.js

import { getToken, setToken, removeToken } from '@/utils/auth'
const state = {
  token: getToken() // 设置token初始状态   token持久化 => 放到缓存中
}

6.5 提供修改token的mutations

const mutations = {
  setToken(state, token) {
    state.token = token // 设置token  只是修改state的数据  123 =》 1234
    setToken(token) // vuex和 缓存数据的同步
  },
  removeToken(state) {
    state.token = null // 删除vuex的token
    removeToken() // 先清除 vuex  再清除缓存 vuex和 缓存数据的同步
  }
}

6.6 封装登录的Action

登录action要做的事情,调用登录接口,成功后设置token到vuex,失败则返回失败

const actions = {
  async login(context, data) {
    const result = await login(data) // 实际上就是一个promise  result就是执行的结果
    if (result.data.success) {
      context.commit('setToken', result.data.data)
    }
  }
}

6.7 区分axios在不同环境中的请求基础地址

为什么会有环境变量之分? 如图
在这里插入图片描述
可以在 .env.development 和 .env.production 定义变量,变量自动就为当前环境的值
基础模板在以上文件定义了变量VUE_APP_BASE_API,该变量可以作为axios请求的baseURL
在模板中,两个值分别为/dev-api 和 /prod-api

6.8 处理axios的响应拦截器

service.interceptors.response.use(response => {
  const { success, message, data } = response.data
  if (success) {
    return data
  } else {
    Message.error(message) // 提示错误消息
    return Promise.reject(new Error(message))
  }
}, error => {
  Message.error(error.message) // 提示错误信息
  return Promise.reject(error) // 返回执行错误 让当前的执行链跳出成功 直接进入 catch
})

三、主页模块

在这里插入图片描述

1.主页的左侧导航样式

左侧导航组件的样式文件 styles/siderbar.scss

1.1 设置左侧导航背景图片

.scrollbar-wrapper { 
    background: url('~@/assets/common/leftnavBg.png') no-repeat 0 100%;
}

1.2 显示左侧logo图片 src/setttings.js

module.exports = {
  title: '小优电商后台管理系统',
  fixedHeader: false,
  sidebarLogo: true // 显示logo
}

1.3 设置头部图片结构 src/layout/components/Sidebar/Logo.vue

<div class="sidebar-logo-container" :class="{'collapse':collapse}">
  <transition name="sidebarLogoFade">
    <router-link key="collapse" class="sidebar-logo-link" to="/">
      <img src="@/assets/common/logo.png" class="sidebar-logo  ">
    </router-link>
  </transition>
</div>

2.设置头部内容的布局和样式

需要把页面设置成如图样式
在这里插入图片描述

2.1 头部组件位置 layout/components/Navbar.vue

<div class="app-breadcrumb">
   北京小优智慧城市科技有限公司
   <span class="breadBtn">V1.0</span>
</div>

2.2 右侧下拉菜单设置

<div class="right-menu">
  <el-dropdown class="avatar-container" trigger="click">
    <div class="avatar-wrapper">
      <img src="@/assets/common/bigUserHeader.png" class="user-avatar">
      <span class="name">管理员</span>
      <i class="el-icon-caret-bottom" style="color:#fff" />
    </div>
    <el-dropdown-menu slot="dropdown" class="user-dropdown">
      <router-link to="/">
        <el-dropdown-item>
          首页
        </el-dropdown-item>
      </router-link>
      <a target="_blank" href="https://github.com/PanJiaChen/vue-admin-template/">
        <el-dropdown-item>项目地址</el-dropdown-item>
      </a>
      <el-dropdown-item divided @click.native="logout">
        <span style="display:block;">退出登录</span>
      </el-dropdown-item>
    </el-dropdown-menu>
  </el-dropdown>
</div>

3. 存储用户信息

3.1 新增变量 userInfo action src/store/modules/user.js

state: {
  token: getToken(),
  userInfo: {} // 用于存储用户对象信息
},

3.2 设置和删除用户资料 mutations

// 设置用户信息
setUserInfo(state, userInfo) {
  state.userInfo = userInfo
},
// 删除用户信息
removeUserInfo(state) {
  state.userInfo = {}
}

3.3 建立用户名的映射 src/store/getters.js

const getters = {  
  token: state => state.user.token,
  username: state => state.user.userInfo.username
}
export default getters

3.4 将头部菜单中的名称换成真实的用户名

<div class="avatar-wrapper">
    <img src="@/assets/common/bigUserHeader.png" class="user-avatar" />
    <span class="name">{{ username }}</span>
    <i class="el-icon-caret-bottom" style="color: #fff" />
</div>

4.自定义指令

全局注册自定义指令语法 - 获取焦点指令

Vue.directive('focus', {
  inserted: function (el) {
    console.log(el.children[0])
    el.children[0].focus()
  }
})

在登录组件中使用此指令

<el-input
   ref="mobile"
   v-model="loginForm.username"
   v-focus
   placeholder="手机号"
   name="mobile"
   type="text"
   tabindex="1"
/>

5. 实现登出功能

在这里插入图片描述

5.1 登出 action src/store/modules/user.js

logout(context) {
  // 删除token
  context.commit('removeToken') // 不仅仅删除了vuex中的 还删除了缓存中的
  // 删除用户资料
  context.commit('removeUserInfo') // 删除用户信息
}

5.2 mutation

removeToken(state) {
  state.token = null // 将vuex的数据置空
  removeToken() // 同步到缓存
},
removeUseInfo(state) {
  state.userInfo = {}
}

5.3 头部菜单调用 action src/layout/components/Navbar.vue

async logout() {
  await this.$store.dispatch('user/logout') // 这里不论写不写 await 登出方法都是同步的
  this.$router.push(`/login`) // 跳到登录
}

6.Token失效的主动介入

在这里插入图片描述

6.1 流程图转化代码

src/utils/request.js

const timeKey = 'hrsaas-timestamp-key' // 设置一个独一无二的key
// 获取时间戳
export function getTimeStamp() {
  return Cookies.get(timeKey)
}
// 设置时间戳
export function setTimeStamp() {
  Cookies.set(timeKey, Date.now())
}

src/utils/request.js

import axios from 'axios'
import store from '@/store'
import router from '@/router'
import { Message } from 'element-ui'
import { getTimeStamp } from '@/utils/auth'
const TimeOut = 3600 // 定义超时时间
const service = axios.create({
// 当执行 npm run dev  => .evn.development => /api => 跨域代理
  baseURL: process.env.VUE_APP_BASE_API, // npm  run dev  => /api npm run build =>  /prod-api
  timeout: 5000 // 设置超时时间
})
// 请求拦截器
service.interceptors.request.use(config => {
  // config 是请求的配置信息
  // 注入token
  if (store.getters.token) {
    // 只有在有token的情况下 才有必要去检查时间戳是否超时
    if (IsCheckTimeOut()) {
      // 如果它为true表示 过期了
      // token没用了 因为超时了
      store.dispatch('user/logout') // 登出操作
      // 跳转到登录页
      router.push('/login')
      return Promise.reject(new Error('token超时了'))
    }
    config.headers['Authorization'] = store.getters.token
  }
  return config // 必须要返回的
}, error => {
  return Promise.reject(error)
})
// 响应拦截器
service.interceptors.response.use(response => {
  // axios默认加了一层data
  const { success, message, data } = response.data
  // 要根据success的成功与否决定下面的操作
  if (success) {
    return data
  } else {
    // 业务已经错误了 还能进then ? 不能 ! 应该进catch
    Message.error(message) // 提示错误消息
    return Promise.reject(new Error(message))
  }
}, error => {
  Message.error(error.message) // 提示错误信息
  return Promise.reject(error) // 返回执行错误 让当前的执行链跳出成功 直接进入 catch
})
// 超时逻辑  (当前时间  - 缓存中的时间) 是否大于 时间差
function IsCheckTimeOut() {
  var currentTime = Date.now() // 当前时间戳
  var timeStamp = getTimeStamp() // 缓存时间戳
  return (currentTime - timeStamp) / 1000 > TimeOut
}
export default service

6.2 同理,在登录的时候,如果登录成功,设置时间戳

async login(context, data) {
  const result = await login(data) // 实际上就是一个promise  result就是执行的结果
  context.commit('setToken', result)
  setTimeStamp() // 将当前的最新时间写入缓存
}

四、路由和页面

在这里插入图片描述
因为复杂中台项目的页面众多,不可能把所有的业务都集中在一个文件上进行管理和维护,并且还有最重要的,前端的页面中主要分为两部分,一部分是所有人都可以访问的, 一部分是只有有权限的人才可以访问的,拆分多个模块便于更好的控制
在这里插入图片描述

1.新建路由

在 router 目录下新建目录 modules,在此目录中新建各个路由模块
路由模块目录结构
在这里插入图片描述

2.设置每个模块的路由规则

// 导出属于用户的路由规则
import Layout from '@/layout'
export default {
  path: '/user', // 路径
  name: '', // 给路由规则加一个name
  component: Layout, // 组件
  // 配置二级路的路由表
  children: [{
    path: '', // 这里当二级路由的path什么都不写的时候 表示该路由为当前二级路由的默认路由
    name: 'user', // 给路由规则加一个name
    component: () => import('@/views/Users'),
    // 路由元信息  其实就是存储数据的对象 我们可以在这里放置一些信息
    meta: {
      title: '用户管理' // meta属性的里面的属性 随意定义
    }
  }]
}

3.静态路由和动态路由临时合并,形成左侧菜单

什么叫临时合并?
动态路由是需要权限进行访问的,但是权限的动态路由访问是很复杂的,我们可以先将 静态路由和动态路由进行合并,不考虑权限问题,后面再解决这个问题
路由主文件 src/router/index.js

// 引入多个模块的规则
import Layout from '@/layout'
import userRouter from './modules/user'
import roleRouter from './modules/role'
import rightsRouter from './modules/right'
import goodsRouter from './modules/goods'
import categoryRouter from './modules/category'
import reportsRouter from './modules/report'
// 动态路由
export const asyncRoutes = [
  userRouter, roleRouter, rightsRouter, goodsRouter, categoryRouter, reportsRouter
]
const createRouter = () => new Router({
  scrollBehavior: () => ({ y: 0 }), // 管理滚动行为 如果出现滚动 切换就让 让页面回到顶部
  routes: [...constantRoutes, ...asyncRoutes]
})

五、内容模块

1.过滤器解决时间格式的处理

Vue.js 允许你自定义过滤器,可被用于一些常见的文本格式化。过滤器可以用在两个地方:双花括号插值和 v-bind 表达式 (后者从 2.1.0+ 开始支持)。过滤器应该被添加在 javascript 表达式的尾部,由“管道”符号指示:

<el-table-column label="入职时间" sortable prop="timeOfEntry">
    <template slot-scope="obj">{{obj.row.timeOfEntry | 过滤器}}</template>
</el-table-column>

安装 moment

npm i moment

编写过滤器函数

import moment from 'moment'
export function formatTime(value) {
  return moment(value * 1000).format('YYYY-MM-DD HH:mm:ss')
}

在 main.js 中全局注册过滤器

import * as filters from '@/filters'
Object.keys(filters).forEach(key => {
  Vue.filter(key, filters[key])
})

2.新增用户

在 views/user 目录下新建一个弹层组件 src/views/user/components/add-user.vue

<template>
  <el-dialog title="新增用户" :visible.sync="dialogVisible" width="50%">
    <el-form ref="form" :rules="rules" :model="userForm" label-width="80px">
      <el-form-item label="用户名" prop="username">
        <el-input v-model="userForm.username" />
      </el-form-item>
      <el-form-item label="密码" prop="password">
        <el-input v-model="userForm.password" />
      </el-form-item>
      <el-form-item label="手机号" prop="mobile">
        <el-input v-model="userForm.mobile" />
      </el-form-item>
      <el-form-item label="邮箱" prop="email">
        <el-input v-model="userForm.email" />
      </el-form-item>
      <el-form-item label="部门" prop="department_title">
        <el-input v-model="userForm.department_title" @focus="getAllDepartment"/>
        <el-tree v-if="showTree" v-loading="loading" :data="treeData" :props="{ label: 'department_title' }" @node-click="handleNodeClick"/>
      </el-form-item>
    </el-form>
    <span slot="footer" class="dialog-footer">
      <el-button @click="btnCancel">取 消</el-button>
      <el-button type="primary" @click="saveUser">确 定</el-button>
    </span>
  </el-dialog>
</template>
<script>
import { getDepartMent } from '@/api/department'
import { tranListToTreeData } from '@/utils'
import { addUser } from '@/api/user'
export default {
  data() {
    return {
      treeData: [], // 存储部门的树形数据
      showTree: false, // 部门文本框获取焦点时,设置为true,展示部门信息
      loading: false, // 显示或隐藏进度
      dialogVisible: false,
      userForm: {
        username: '',
        password: '',
        email: '',
        mobile: '',
        department_id: '',
        department_title: ''
      },
      rules: {
        username: [
          { required: true, trigger: 'blur', message: '用户名不能为空' },
          { min: 6, max: 10, trigger: 'blur', message: '长度在6-10位之间' }
        ],
        password: [
          { required: true, trigger: 'blur', message: '密码不能为空' },
          { min: 6, max: 12, trigger: 'blur', message: '长度在6-12位之间' }
        ],
        mobile: [
          { required: true, trigger: 'blur', message: '手机号不能为空' },
          { pattern: /^1[3-9]\\d{9}$/, trigger: 'blur', message: '手机号格式不正确' }
        ],
        email: [
          { required: true, trigger以上是关于基于 vue-element-admin 的项目总结的主要内容,如果未能解决你的问题,请参考以下文章

vue-element-admin初窥

vue-element-admin快捷导航(标签栏导航)

vue-element-admin 登出切换用户后重新登录跳转404页面Bug-解决记录

Day05-项目讲师管理模块前端开发

在线教育_Day05-项目讲师管理模块前端开发

vue-element-admin项目配置运行