官网:https://github.com/PanJiaChen
vue-element-admin是含有丰富的组件,vue-admin-template是一个基础的单页面应用的框架,适合在vue-admin-template上二次开发,开发需要的组件就可以直接的从vue-element-admin里面拷贝上去。适合于后台管理的中小型项目,内部的架构非常的完善,简单易上手。
vue-element-admin
官网
vue-admin-template
官网
本项目已经为你生成了一个基本的开发框架,提供了涵盖中后台开发的各类功能和坑位,下面是整个项目的目录结构。
├── 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
npm install vuex-persistedstate
import createPersistedState from 'vuex-persistedstate'
// 创建PERSIST_PATHS变量 存储要持久化的模块
const PERSIST_PATHS = ['user']
const store = new Vuex.Store({
state: {},
modules: {
app,
settings,
user,
permission,
tagsView
},
getters,
// 新增规则保存vuex的值
plugins: [createPersistedState({
storage: window.sessionStorage,
// 在此使用
paths: PERSIST_PATHS
})]
})
目标
在码云或者github上建立相应的远程仓库,并将代码分支提交
建立远程仓库
远程仓库建立只需要在网站上直接操作即可
本地项目提交
注意
: 由于我们之前的项目是直接从 vue-element-admin *_克隆
*而来,里面拥有原来的提交记录,为了避免冲突, 先将原来的_.git
**文件夹删除掉
并且对项目进行git初始化
$ git init #初始化项目
$ git add . #将修改添加到暂存
$ git commit -m '人资项目初始化' #将暂存提到本地仓库
查看版本日志
$ git log #查看版本日志
推送到远程仓库
推送到远程仓库一般先将**远程仓库地址
**用本地仓库别名代替
$ git remote add origin <远程仓库地址> #添加远程仓库地址
当我们不清楚自己的仓库对应的origin地址时, 我们可以通过命令查看当前的远程仓库地址
$ git remote -v #查看本地仓库的远程仓库地址映射
推送master分支到远程仓库
$ git push -u origin master #将master分支推送到origin所代表的远程仓库地址
目标
介绍API模块的单独请求和 request模块的封装
该项目采用了API的单独模块封装和axios拦截器的方式进行开发
axios的拦截器原理如下
axios拦截器
axios作为网络请求的第三方工具, 可以进行请求和响应的拦截
通过create创建了一个新的axios实例
// 创建了一个新的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 // 超时时间})
请求拦截器
请求拦截器主要处理 token的**统一注入问题
**
// axios的请求拦截器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)
})
响应拦截器
响应拦截器主要处理 返回的**数据异常
** 和**数据结构
**问题
// 响应拦截器service.interceptors.response.use( response => {
const res = response.data
// if the custom code is not 20000, it is judged as an error.
if (res.code !== 20000) {
Message({
message: res.message || 'Error',
type: 'error',
duration: 5 * 1000
})
if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
// to re-login
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 => {
console.log('err' + error) // for debug
Message({
message: error.message,
type: 'error',
duration: 5 * 1000
})
return Promise.reject(error)
})
这里为了后续更清楚的书写代码,我们将原有代码注释掉,换成如下代码
// 导出一个axios的实例 而且这个实例要有请求拦截器 响应拦截器
import axios from 'axios'const service = axios.create() // 创建一个axios的实例
service.interceptors.request.use()
// 请求拦截器service.interceptors.response.use()
// 响应拦截器export default service // 导出axios实例
我们习惯性的将所有的网络请求 放置在api目录下统一管理,按照模块进行划分
单独封装代码
import request from '@/utils/request'
export function login(data) {
return request({
url: '/vue-admin-template/user/login',
method: 'post',
data
})
}
export function getInfo(token) {
return request({
url: '/vue-admin-template/user/info',
method: 'get',
params: { token }
})
}
export function logout() {
return request({
url: '/vue-admin-template/user/logout',
method: 'post'
})
}
上面代码中,使用了封装的request工具,每个接口的请求都单独**导出
**了一个方法,这样做的好处就是,任何位置需要请求的话,可以直接引用我们导出的请求方法
为了后续更好的开发,我们可以先将user.js代码的方法设置为空,后续在进行更正
// import request from '@/utils/request'
export function login(data) {}
export function getInfo(token) {}
export function logout() {}
开发中,经常遇到图片挂掉,导致无法显示问题,比如公司的图片服务器宕机等
这时候可以显示一张默认图片,可以通过自定义指令解决这个问题
自定义指令 — Vue.js (vuejs.org)
全局注册自定义指令语法
Vue.directive('指令名称', {
// 会在当前指令作用的dom元素 插入之后执行
// options 里面是指令的表达式
inserted: function (dom,options) {
}
})
获取焦点指令
main.js 中加入如下代码,全局注册自定义指令 v-focus
Vue.directive('focus', {
inserted: function (el) {
console.log(el.children[0])
el.children[0].focus()
}
})
然后在登录组件中使用此指令
注意:一定要在当前浏览器窗口激活的情况下,才可以看到效果,比如 vs code 获取焦点的情况下,是看不到效果的,也就是代码编辑完成后,要用鼠标点一下浏览器窗口,才可以看到效果
统一管理
自定义指令可以采用统一的文件来管理 src/directives/index.js
,这个文件负责管理所有的自定义指令
首先定义第一个自定义指令 v-imagerror
export const imagerror = {
// 指令对象 会在当前的dom元素插入到节点之后执行
inserted(dom, options) {
// options是 指令中的变量的解释
其中有一个属性叫做 value
// dom 表示当前指令作用的dom对象
// dom认为此时就是图片
// 当图片有地址 但是地址没有加载成功的时候 会报错 会触发图片的一个事件 => onerror
dom.onerror = function() {
// 当图片出现异常的时候 会将指令配置的默认图片设置为该图片的内容
// dom可以注册error事件
dom.src = options.value // 这里不能写死
}
}}
然后,在**main.js
**中完成对于该文件中所有指令的全局注册
import * as directives from '@/directives'// 注册自定义指令// 遍历所有的导出的指令对象 完成自定义全局注册
Object.keys(directives).forEach(key => {
// 注册自定义指令 Vue.directive(key, directives[key]
)})
针对上面的引入语法 import * as 变量
得到的是一个对象**{ 变量1:对象1,变量2: 对象2 ... }
**, 所以可以采用对象遍历的方法进行处理
指令注册成功,可以在**navbar.vue
**中直接使用了
data() {
return {
defaultImg: require('@/assets/common/head.jpg')
}
},
目标
删除基础模板中附带的多余页面
基础模板帮我们提前内置了一些页面,本章节我们进行一下整理
首先,我们需要知道类似这种大型中台项目的页面路由是如何设置的。
简单项目
当前项目结构
为什么要拆成若干个路由模块呢?
因为复杂中台项目的页面众多,不可能把所有的业务都集中在一个文件上进行管理和维护,并且还有最重要的,前端的页面中主要分为两部分,一部分是所有人都可以访问的, 一部分是只有有权限的人才可以访问的,拆分多个模块便于更好的控制
静态路由和动态路由
注意
**这里的动态路由并不是 **路由传参的动态路由
了解完成路由设计之后,我们对当前的路由进行一下整理
删除多余的静态路由表 src/router/index.js
/** * constantRoutes * a base page that does not have permission requirements * all roles can be accessed */
export const constantRoutes = [
{
path: '/login',
component: () => import('@/views/login/index'),
hidden: true
},
{
path: '/404',
component: () => import('@/views/404'),
hidden: true
},
{
path: '/',
component: Layout,
redirect: '/dashboard',
children: [{
path: 'dashboard',
name: 'Dashboard',
component: () => import('@/views/dashboard/index'),
meta: { title: 'Dashboard', icon: 'dashboard' }
}]
},
// 404 page must be placed at the end !!! { path: '*', redirect: '/404', hidden: true }]
原来的八个路由记录,我们只对上面几个进行保留
同时删除对应的无用组件
左侧导航菜单的最终样子
同样的在api目录下,存在多余的api-table.js 一并删除
目标
: 掌握vue-admin-tempate 基础框架下新模块的创建
接下来,我们可以将小优电商后台关系系统需要做的模块快速搭建相应的页面和路由
├── dashboard
# 首页├── login
# 登录├── 404
# 404├── Users
# 用户├── Roles
# 角色├── Rights
# 权限├── Goods
# 商品├── Category
# 类别├── Report
# 报表
根据上图中的结构,在views目录下,建立对应的目录,给每个模块新建一个**index.vue
**,作为每个模块的主页
每个模块的内容,可以先按照标准的模板建立,如
用户
用户
根据以上的标准建立好对应页面之后,接下来建立每个模块的路由规则
在 router 目录下新建目录 modules
在此目录中新建各个路由模块
路由模块目录结构
每个模块导出的内容表示该模块下的路由规则
如用户 user.js
// 导出属于用户的路由规则
import Layout from '@/layout'
// { path: '', component: '' }// 每个子模块 其实 都是外层是layout 组件位于layout的二级路由里面
export default {
path: '/user', // 路径
name: '', // 给路由规则加一个name
component: Layout, // 组件 // 配置二级路的路由表
children: [{ path: '', // 这里当二级路由的path什么都不写的时候 表示该路由为当前二级路由的默认路由
name: 'user', // 给路由规则加一个name
component: () => import('@/views/Users'),
// 路由元信息 其实就是存储数据的对象 我们可以在这里放置一些信息
meta: {
title: '用户管理' // meta属性的里面的属性 随意定义 但是这里为什么要用title呢, 因为左侧导航会读取我们的路由里的meta里面的title作为显示菜单名称
}
}
]}// 当你的访问地址 是 /user的时候 layout组件会显示 此时 你的二级路由的默认组件 也会显示
上述代码中,我们用到了meta属性,该属性为一个对象,里面可放置自定义属性,主要用于读取一些配置和参数,并且值得**
注意
的是:我们的meta写了二级默认路由上面,而不是一级路由,因为当存在二级路由的时候,访问当前路由信息访问的就是二级默认路由
**
根据上面的路由规则,自己编写其他几个路由模块的路由规则
目标
: 将静态路由和动态路由的路由表进行临时合并
什么叫临时合并?
前面讲过,动态路由是需要权限进行访问的,但是权限的动态路由访问是很复杂的,我们可以先将 静态路由和动态路由进行合并,不考虑权限问题,后面再解决这个问题
路由主文件 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({
// mode: 'history',
// require service support scrollBehavior: () => ({ y: 0 }),
// 管理滚动行为 如果出现滚动 切换就让 让页面回到顶部
routes: [...constantRoutes, ...asyncRoutes] // 临时合并所有的路由
})
通过上面的操作,我们将静态路由和动态路由进行了合并
当我们合并权限完成,我们惊奇的发现页面效果已经左侧的导航菜单 =》 路由页面
这是之前基础模板中对于左侧导航菜单的封装
提交代码
本节任务
: 将静态路由和动态路由临时合并,形成左侧菜单
目标
解析左侧菜单的显示逻辑, 设置左侧导航菜单的图标内容
上小节中,我们集成了路由,菜单就显示内容了,这是为什么 ?
阅读左侧菜单代码
我们发现如图的逻辑
由于,该项目不需要二级菜单的显示,所以对代码进行一下处理,只保留一级菜单路由
src/layout/components/Sidebar/SidebarItem.vue
本节注意
**:通过代码发现,当路由中的属性hidden
**为true时,表示该路由不显示在左侧菜单中
与此同时,我们发现左侧菜单并不协调,是因为缺少图标。在本项目中,我们的图标采用了SVG的组件
左侧菜单的图标实际上读取的是meta属性的icon,这个icon需要我们提前放置在**src/icons/svg
**目录下
项目提供了一些 svg 图标,具体的icon名称可参考线上地址,如果没有找到合适的,可以到 iconfont 获取
模块对应icon
├── category
# category├── goods
# goods├── reports
# reports├── user
# account├── roles
# roles├── rights
# rights
针对用户创建时间,我们可以采用过滤器进行处理
Vue.js 允许你自定义过滤器,可被用于一些常见的文本格式化。过滤器可以用在两个地方:双花括号插值和
v-bind
表达式 (后者从 2.1.0+ 开始支持)。过滤器应该被添加在 JavaScript 表达式的尾部,由“管道”符号指示: