后台管理系统主要是我们内部人员使用的一款用来管理我们产品的一个系统,然后呢,我们今天写的呢是一个电商的后台管理系统。主要是可以用来管理我们的用户还有我们是商品的。
我们这个系统呢采用的是一个前后端分离的模式,主要是使用后端给我们的接口来实现的,我们前端呢是使用了 Vue 的框架,界面呢我们使用了 element-ui 的框架。
它里面呢包含有几个模块,有我们的登录页,用户页,角色页,主要的是商品页面,还有一个是图表的页面,那么下面呢,我么下面得我们来说一说基本功能得一个实现
对于axios的二次封装
因为我们在项目中会有一些错误的信息还要向我们的后台发送token来请求数据,还有可能会在网络不好是,出现多次请求的一个现象发生,如果都将他们写在我们的页面中,容易造成代码混乱,代码冗余,也会增加我们的一个工作量。
所以在项目开始前,我们可以对axios进行一个简单的二次封装
在请求拦截器中我们可以传递token值,响应拦截器可以处理错误的信息,还可以取消重复的请求
// 请求拦截器
serve.interceptors.request.use((config) => {
// 检查是否有重复的请求,如果有就删掉
rem(config)
// 把当前请求添加到pindingRequest对象中
req(config)
// 把token传到后台
config.headers.Authorization = localStorage.getItem("token")
console.log(config);
return config
}, (err) => {
return Promise.reject(err)
})
// 响应拦截器
serve.interceptors.response.use((config) => {
// 无效token,弹出提示框
if (flag) {
flag = false
if (config.data.meta.msg == "无效token") {
MessageBox.confirm('登录状态已过期, 是否重新登录?', '提示', {
confirmButtonText: '重新登录',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
// 原生js跳转页面,不能用element-ui的,this不能用
location.href = "/#/login"
}).catch(() => {});
}
}
console.log(config);
// 状态是以下这些,就提示错误信息
if (config.data.meta.status === 400 || config.data.meta.status === 401 || config.data.meta.status === 403 || config.data.meta.status === 404 || config.data.status === 500) {
Message.error({
duration: 1000,
message: config.data.meta.msg
})
}
return config.data
}, (err) => {
rem(err.config || {})
if (axios.isCancel(err)) {
Message.error("已取消重复请求")
} else {
}
console.log(err);
let { message } = err
if (message == "Network Error") {
message = "接口连接异常"
} else if (message.includes("timeout")) { //返回的错误信息,如果包含timeout,就赋值网络异常
message = "网络异常"
}
// 提示错误信息
Message.error({
duration: 2000,
message: message
})
return Promise.reject(err)
})
// 这是一个唯一的key值
function key1(config) {
let { methods, url, params, data } = config
return [methods, url, JSON.stringify(params), JSON.stringify(data)].join("&")
}
// 定义map结构
let map = new Map()
// 判断是否是第一次请求(去重)
function req(config) {
let key = key1(config)
config.cancelToken = config.cancelToken || new axios.CancelToken((cancel) => {
if (!map.has(key)) {
map.set(key, cancel)
}
})
}
// 取消重复的请求
function rem(config) {
let key = key1(config)
if (map.has(key)) {
let cancel = map.get(key)
cancel(key)
// 移除上一次请求
map.delete(key)
}
}
登录页面
登录页面呢我们可以使用element-ui组件来实现页面的布局,表单校验也可以使用它里面提供给我们的,然后我们在登录成功之后,它会返回给我们一个token值,我们在后续写其他页面请求数据的时候也是需要用到这个token,所以我们需要将他存储到本地中,方便我们使用。
登录鉴权
如果我们在没有登录的时候,是不能访问我们其他的页面的,所以我们需要实现一个登录鉴权的功能,如果你没有token,是不能访问我的页面的。
我们可以在main.js中定义一个全局的路由守卫,首先判断当前是否是登录页面,如果当前不是登陆页面,我们在判断本地中有没有token值,如果你既不是在登录页面,本地也没有token值,那么我就让你去登录页,这样我们可以实现一个登录鉴权的功能。
这里需要注意的是路由守卫中,如果我们不写next(),那么是无法进行跳转页面的
// 路由守卫
router.beforeEach((to, from, next) => {
// 判断当前是否是登录页面
if (to.path != "/login") {
// 判断有没有token值
if (!localStorage.getItem("token")) {
// 跳转到登录页面
next("/login")
}
}
// 跳转页面
next()
})
首页
当我们登录成功之后,需要跳转到我们的首页
左侧是我们的路由菜单,在这里我们可以使用静态路由和动态路由两种方式
静态路由
静态路由是路由的一种,他需要我们去手动的添加到我们的路由页面中,如果后端又添加了一个路由的话,我们还需要再手动将他加进去,如果后端修改完,没有告诉你的话,那么就芭比Q了,就找不到页面了。
静态路由的话,大家应该都会,这里就不多叙述了
//静态路由
{
path: '/admin/home',
name: 'admin_home',
component: () =>
import ('./views/admin/home.vue'),
children: [{
path: '/admin/welcome',
name: 'admin_welcome',
component: () =>
import ('./views/admin/welcome.vue')
}, {
path: '/admin/users',
name: 'admin_users',
component: () =>
import ('./views/admin/users.vue')
}, {
path: '/admin/roles',
name: 'admin_roles',
component: () =>
import ('./views/admin/roles.vue')
}, {
path: '/admin/rights',
name: 'admin_rights',
component: () =>
import ('./views/admin/rights.vue')
}, {
path: '/admin/goods',
name: 'admin_goods',
component: () =>
import ('./views/admin/goods.vue')
}]
}
动态路由
动态路由也是路由的一种,我们前端呢可以使用js将树状结构转为列表结构再将它动态的添加到我们的路由表中,这个时候如果后端再次新增路由,那么他就可以动态的添加进去,就不需要我们再次手动的添加了
动态路由的实现,主要是通过递归函数,将树状结构转为列表结构,然后通过router实例的addRoute方法,动态的添加到我们的二级路由中。
const routes = [{
path: "/home",
name: "Home",
component: Home,
redirect: "/welcome",
children: [{
path: "/welcome",
name: "welcome",
component: () =>
import ("./views/admin/welcome.vue")
}
]
}]
//递归函数遍历数据
function fn(data) {
let arr = []
function deep(data) {
data.forEach(item => { //遍历树状结构
if (item.children.length) { //如果他下面的children属性还有内容
deep(item.children) //就让他一直查找
} else {
arr.push({
path: "/" + item.path,
name: item.authName,
component: () =>
import (`@/views/admin/${item.path}.vue`)
})
}
})
}
deep(data)
return arr
}
const router = new Router() //router实例化
function loadRoute() {
let token = localStorage.getItem("token")
let menuList = JSON.parse(localStorage.getItem("menuList"))
if (token && menuList) { //如果本地有token和菜单列表
let menu = fn(menuList) //进行递归遍历
menu.forEach(item => {
router.addRoute("Home", item) //通过addRoute将他填进二级路由中
})
}
}
loadRoute()
那么走到这一步,我们的动态路由就基本是实现了。
然后我们的路由也不可能全部是动态的,里面还会有一些不需要动态添加的路由,比如说我们的登录页面,这里我们就可以将他写在我们的白名单中
const whiteList = ["/login"] //路由白名单
router.beforeEach((to, form, next) => { //前置路由守卫
let token = localStorage.getItem("token")
if (token) { //如果token值存在
next() //就直接进行跳转
} else {
if (whiteList.indexOf(to.path) != -1) { //路由白名单中的,查找到就跳转
next()
} else {
next("/login") //否则跳转到登录页面
}
}
})
这里需要注意的是我们在登录的时候也是需要去调用方法的,不然会出现找不到路由的问题,不管点击哪个,都会跳转到我们的登录页面,解决呢就是再次调用一下方法,再调用时可能会有同步和异步的问题,可能获取不到数据,需要将他改为同步,可以使用async await改为同步执行。
用户列表
用户列表页面主要是用来管理使用这个后台系统的用户的,可以给用户设置角色,不同的角色会有不同的权限。
基本的增删改查我们就不多说了。主要是有一个分配权限的功能,我们需要获取到当前用户的id和新增角色的id,发送到后台就可以返回响应的数据了
角色列表
然后是我们的角色列表,角色列表的主要功能是给我们的角色分配权限的,他里面是一个tree组件
我们在打开弹出框的同时获取到我们角色的权限 ,将他渲染出来
这里我们可以使用递归函数判断每一层数据有没有children属性,如果有就一直递归循环,如果没有了,就将它的id值添加到我们的一个数组中,再使用tree组件的方法default-checked-keys,将数组渲染到页面中。
在分配权限的时候呢,tree给我们提供了两个方法getCheckedKeys和getHalfCheckedKeys,这两个方法可以获取我们选中数据的id和半选中节点的id,拿到这两个id后,传递到后台那么就可以实现分配权限的一个功能了
商品管理
商品管理是我们这个后台项目中主要的模块,分为商品分类和商品列表
商品分类中我们主要是有一个级联选择器,需要将拿到的数据渲染在级联选择器中
在添加分类的时候需要获取到我们的父级id,还有需要添加到的层级id,拿到这两个id后,我们就可以向后台发送数据,将他添加到我们相应的分类中
分类参数是用来给我们的分类数据添加动态参数和静态属性的
这里注意只能为三级分类设置相关的参数,只有当我们选择了分类的时候,才可以设置相关的参数
当我们在点击动态参数和静态属性的时候会去请求相应的接口,来返回不同的数据,渲染在我们的页面中,当我们新增属性的时候,需要在后台返回给我们的数据中添加一个标识符,用来隐藏和显示输入框,如果绑定的是全局的标识符,就会把全部的输入框都改变状态。
这里需要注意一下,就是后台返回的数据是字符串的格式,如果我们要将他渲染到页面中,还需要使用字符串的split方法,将他转换为数组,转换完毕后渲染到页面上就可以了
商品列表中有一个添加商品功能,因为我们用的是动态路由的方式,会找不到这个页面,所以我们需要手动的添加一个路由
在这里我们需要先选择商品分类,才可以去渲染其他的数据,不然是左侧的tab是不能切换的,这里我们可以使用tab提供的 before-leave 方法,这个方法是在tab切换前执行的一个方法,如果返回的是false,那么就不会进行跳转
当我们点击商品参数和商品属性的时候需要请求不同的接口,将返回的数据渲染出来
这里我们使用 el-checkbox-group 需要给他绑定v-model,当我们选中一些参数时 ,会自动的添加到绑定的数组中,注意在请求接口时在在用join方法转换为字符串
然后是商品图片的功能,这里我们使用upload组件,这里需要注意的是图片上传时需要单独的请求一个接口,并且传递token值,当上传成功后,将tem_path以对象的形式添加到我们的数组中
商品内容区域是一个富文本,我们可以下载 vue-quill-editor 依赖,挂载到我们的vue中,然后使用的时候,需要使用v-model绑定一下
最后将所有数据上传到后台,那么商品的添加就完成了
数据统计
最后呢是一个数据统计,这个模块主要是给我们的领导看的,将统计好的数据绘制成图表,在渲染到页面中,那么这里就可以使用 echarts 图表来做,它里面给我们提供了很多的图表供我们使用,有折线图,柱状图等等
这里我们使用的是折线图
需要我们提前在页面上写好一个带有宽高的盒子,然后请求数据
在拿到接口返回的数据后,我们需要将对应的数据给到对应的字段上
option: {
title: {
text: "数据报表"
},
color: [
"#5470c6",
"#91cc75",
"#fac858",
"#ee6666",
"#73c0de",
"#3ba272",
"#fc8452",
"#9a60b4",
"#ea7ccc"
],
tooltip: {},
legend: {},
grid: {},
xAxis: [],
yAxis: [],
series: []
}
//基于准备好的dom,初始化echarts实例
let myChart = echarts.init(document.getElementById("main"));
let res = await getReports();
this.option.legend = res.data.legend;
this.option.series = res.data.series;
this.option.xAxis[0].data = res.data.xAxis[0].data;
this.option.yAxis = res.data.yAxis;
myChart.setOption(this.option);
当然我们可能会在大屏上显示我们图表,那么这个时候就需要给他自适应我们窗口的大小
// 窗口大小改变时,监听图表大小,自适应一下
window.onresize = function () {
myChart.resize();
};
大家也可以自己去封装一些组件来使用,本次项目中呢,也有一些自己封装的组件,比如说dialog组件和分页组件,那么这里就不多说了,毕竟我们要学会面向 百度 编程嘛
那么进行这里后,我们的项目就基本上是结束了,有什么不足,也希望大家多多指教!
在这里呢也预祝我们热爱编程的小伙伴们,更上一层楼,越来越棒!
这里呢是本次项目的源码,欢迎大家前来
335love/zhilei - Gitee.com