该项目是一款对公司员工及商品管理的后台系统,主要实现功能:公司角色的增删改查,和商品的增删改查,项目的主要模块有,登录,主页,员工管理,权限管理,商品管理,该项目的亮点是权限管理,不同角色登录进入首页,看到的菜单和可 *** 作的按钮是不一样的,如系统管理员可查看和 *** 作所有模块。
1.初始化项目涉及的前端技术栈:
2.项目功能模块划分 登录/退出功能主页布局用户管理模块权限管理模块分类管理模块商品列表模块订单管理模块数据统计模块 3.各模块功能实现 3.1封装axios请求Vue
Vue是一个用于创建用户界面的开源JavaScript框架
Vue-router
vue-router是Vue官方推出的路由管理器
elementUi
element是基于VUE的一套UI组件库
Axios
Axios,是一个基于promise网络请求库,作用于node.js和浏览器中
Echarts
“ECharts是一款基于JavaScript的数据可视化图表库
Vuex
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式 + 库
import axios from 'axios'
//因为我们将tokne存入的vuex,所以这里需要引入vuex
import vuex from '../store'
var api = axios.create({
baseURL: 'http://127.0.0.1:8888/api/private/v1/',
timeout: 5000
})
api.interceptors.request.use((config) => {
if (vuex.state.token) {
//这里是进行判断有没有token,如果有token的话通过请求头将token携带过去请求参数
config.headers.Authorization = vuex.state.token
}
return config
})
api.interceptors.response.use((res) => {
//这里是为了token过期之后是否回退到登陆页面
let code = res.data.meta.status
//获取返回的状态码
if (res.data.meta.msg == "无效token") {
//判断返回的msg如果为无效token的话,就说明token过期了,此时d出一个提示框。
MessageBox.confirm('token过期, 是否跳转到登录页面?', '提示', {
confirmButtonText: '重新登录',
//若用户点击重新登录则回到登录页
cancelButtonText: '取消',
//若用户点击取消,则留在当前页面,但什么也做不了
type: 'warning'
}).then(() => {
//通过原生js的方法跳转到登录页
location.href = '/login'
}).catch((err) => {
err
})
}
return res.data
})
//最后导出出去
export default api
3.1.1二次封装
这里拿权限的接口为例
// 权限列表
export const menus = () => api.get("/menus")
3.1.2调用
在需要的页面就可以通过.then
获取数据
getData() {
menus().then((res) => {
this.menusList = res.data;
});
},
3.2登录/退出功能
对于后台管理这个项目来说呢,所有的功能和页面要求用户必须登录之后才可以查看,再后面的各个模块中,需要发送大量请求来获取数据,再进行页面的渲染,那么这个时候就需要进行哦按段用户的登录状态,登录成功后,才可以拿到相应的数据
3.2.1实现登录功能
这个很简单,只需将用户填写的用户名和密码通过接口发送到服务器验证即可,在服务器返回数据后将token存入Vuex
3.2.2
基于elementUi的表单验证
通过
:rules
绑定表单验证规则的对象,在表单上通过ref绑定,再通过$refs
拿到对应的表单对象,然后调用validate
方法进行验证
ruleValidate: {
username: [
{ required: true, message: "用户名不能为空", trigger: "blur" },
{ min: 3, max: 10, message: "用户名长度在3到10个字符",trigger:'blur'},
],
password: [
{ required: true, message: "密码不能为空", trigger: "blur" },
{ min: 6, max: 10, message: "用户名长度在6到10个字符",trigger:'blur'},
],
},
登录校验并将token存入本地
handleSubmit(name) {
let tant = this;
this.$refs[name].validate((valid) => {
if (valid) {
login(this.formValidate).then((res) => {
//这里是将token存入vuex
this.$store.commit("token", res.data.token);
this.$Notice.success({
title: "登录成功",
duration: 1,
});
this.$router.push("/");
});
} else {
this.$Message.error("表单验证失败!");
}
});
},
3.2.3路由守卫控制访问权限
router.beforeEach((to, from, next) => {
//to,from,next分别代表要去的地方,从哪里来,和放行或者重定向
let token = vuex.state.token
//先判断有没有token
if (token) {
//有的话就一路畅通
next()
} else {
//没有的话判断是不是去登录页
if (to.path == '/login') {
//是的话放行
next()
} else {
//不是的话就强行让他回到登录页
next('/login')
}
}
})
3.2.4退出功能
因为是基于token实现的登录,所以退出只需将存在vuex中的token销毁并跳转到登录页即可
这里是使用elementUi的菜单栏组件实现的,只需替换数据即可。
菜单栏的原本数据是树状结构,这里可以在router文件内挨个添加并引入本地路径,但稍显繁琐且代码量增多,那么这里呢就可以使用动态路由模式。
因为我们获取的数据是树状的,所以需要先通过递归获取最下层的结构并处理成列表结构,代码如下
export function fn(data) {
//先声明一个空数组,用来存放最终的列表结构
let arr = [];
//递归函数
function deep(data) {
//循环每一项
data.forEach((item) => {
//判断有没有children
if (item.children.length) {
//如果有的话,说明不是底层数据,开始递归
deep(item.children);
} else {
//如果没有的话则证明已经是底层数据了
arr.push({
//路径名
path: "/" + item.path,
//name名
name: item.authName,
//引入,注意 这里的文件还是需要手动配置的
component: () => import("@/views/homeList/" + item.path[0].toUpperCase() + item.path.substring(1) + ".vue"),
});
}
});
}
deep(data);
//最终return底层数据的数组
return arr;
}
这里是之后的列表数据
最后再使用this.$router.addRoute
添加到首页下的children
里边就可以了,代码如下
function loadRoute() {
let token = tant.$store.state.token;
let menus = JSON.parse(localStorage.getItem("menus"));
if (token && menus) {
let newList = fn(menus);
newList.forEach((item) => {
tant.$router.addRoute("Home", item);
});
}
}
loadRoute();
3.4用户管理模块
3.4.1
这个用户管理模块,就是由最基本的增删改查来实现的,首先通过接口请求用户列表的数据,然后通过elementUi的table表格渲染就可以了,大部分功能都是 *** 作接口完成的,如根据ID搜索用户,就是传一个id值给后端,后端进行筛选之后返回符合id的用户,我们拿到数据之后进行渲染就可以。还有状态,也是通过接口像后端发送需要修改状态的用户id和修改后的布尔值,即可修改状态。这里值得说一下的有,添加用户和编辑用户可以公用一个模态框,代码如下。
//点击添加将判断的布尔值改为true,点击编辑则改为false
handleSubmit(name) {
//这里根据布尔值判断此次执行函数为编辑还是添加
if (this.deilOrAdd) {
//如果是添加的话
this.$refs[name].validate((valid) => {
if (valid) {
//通过表单验证
this.isShow = false;
//通过接口传递参数给后端
usersAdd(this.formValidate).then((res) => {
if (res.meta.status == 201) {
//创建成功后重新获取数据
this.getData();
this.$Notice.success({
title: "创建成功",
});
}
});
} else {
//表单验证失败
this.$Message.error("表单验证失败!");
}
});
} else {
//如果为编辑的话
let obj = {
id: this.userId,
obj1: this.formValidate,
};
//先回填当前编辑用户的数据
usersDeil(obj).then((res) => {
//通过接口传递参数给后端进行修改
if (res.meta.status == 200) {
this.$Notice.success({
title: "更新成功",
});
//修改完成重新刷新
this.getData();
//将模态框隐藏
this.isShow = false;
}
});
}
},
3.4.2分页
值得一说的还有分页的逻辑了,分页所有的功能都是围绕这current
,pagesize
,token
来实现的,我这里自己封装了一个分页的组件,代码如下
<template>
<div class="myPage">
<span class="total">共{{ total }}条</span>
<div class="before">
<div @click="sub" class="arrows beforeArrows"></div>
</div>
<span
@click="item == '...' ? false : $emit('change-current', item)"
:class="current == item ? 'pageItem pageItemACur' : 'pageItem'"
v-for="(item, index) in pagenum"
:key="index"
>{{ item }}</span
>
<div class="aftter">
<div @click="add" class="arrows aftterArrows"></div>
</div>
<p class="toPage">
前往<input
class="inp"
type="text"
v-model.number="toPage"
@keyup.enter="toPageNum"
/>页
</p>
</div>
</template>
<script>
export default {
name: "demo",
props: {
//接收父组件传递过来的总条数
total: {
type: Number,
default: 50,
},
//接收父组件传递过来的每页条数
pagesize: {
type: Number,
default: 5,
},
//接收父组件传递过来的当前页
current: {
type: Number,
default: 1,
},
},
components: {},
data() {
return {
num: 0,
toPage: this.current,
};
},
created() {},
computed: {
//这里使用计算属性完成
pagenum() {
//先声明一个存放按钮内容的空数组
let pagenumArr = [];
//遍历token每页条数就可以获得
for (var i = 0; i < Math.ceil(this.total / this.pagesize); i++) {
//push到数组
pagenumArr.push(i + 1);
}
//此时需要判断按钮的数量
//以这里为例,我希望按钮最大数量为9,所以加了个>9的if判断
if (pagenumArr.length > 9) {
//再进行判断 当前值是否为5以下
if (this.current <= 5) {
//为5以下的显示前7个按钮,中间用...代替,最后一个值为数组的长度
pagenumArr = [1, 2, 3, 4, 5, 6, 7, "...", pagenumArr.length];
} else if (this.current >= pagenumArr[pagenumArr.length - 1] - 4) {
//反之判断是否当前值是否是在数组的末尾
pagenumArr = [
1,
"...",
pagenumArr.length - 6,
pagenumArr.length - 5,
pagenumArr.length - 4,
pagenumArr.length - 3,
pagenumArr.length - 2,
pagenumArr.length - 1,
pagenumArr.length,
];
// 如果是在数组的末尾则将1后边的省略为...,最后的值为length--就能得出后边的值
} else {
//再进行判断是否是在中间点,如果是在中间,则两边以...显示
pagenumArr = [
1,
"...",
this.current - 2,
this.current - 1,
this.current,
this.current + 1,
this.current + 2,
"...",
pagenumArr.length,
];
}
}
//return出去
return pagenumArr;
},
},
methods: {
//去第几页的函数
toPageNum() {
//如果跳转的页码大于总页数,则跳到最后一页
if (this.toPage > this.pagenum[this.pagenum.length - 1]) {
this.$emit("change-current", this.pagenum[this.pagenum.length - 1]);
} else {
//反之则跳转到相应页面
this.$emit("change-current", this.toPage);
}
//跳转完input失焦
document.querySelector(".inp").blur();
},
add() {
//点击判断是否是最后一位
if (this.current >= this.pagenum[this.pagenum.length - 1]) {
//是的话return
return;
} else {
//不是则跳转
this.$emit("change-current", this.current + 1);
}
},
sub() {
//判断是不是第一位
if (this.current <= 1) {
//是就return
return;
} else {
//不是则跳转
this.$emit("change-current", this.current - 1);
}
},
},
mounted() {}
};
</script>
3.4.3面包屑导航
我这里的面包屑导航是使用了自己封装的一个函数,代码如下;
goItem(e) {
//在点击路由跳转时触发
this.menusList.forEach((item) => {
//遍历menus
item.children.forEach((ele) => {
//遍历menus里边的每一个children
if (ele.path == e) {
//如果children的path等于路由要跳转的路径的时候
this.bread = { Fname: item.authName, Cname: ele.authName };
//将data里边的面包屑对象更换,第一个值是父亲的name,第二个值是儿子的name,这样就可以拿这个对象去渲染面包屑了
}
});
});
//同时将当前路径存储到data中
this.name = e;
//进行跳转,这里catch是vue重复调换报错的问题,捕获报错但不发不出来,蛮有趣的。
this.$router.push("/" + e).catch((err) => {
err;
});
},
breadShow() {
//判断刚刚存入的路径名称
this.name = this.name + "";
//是不是等于欢迎页
if (window.location.hash != "#/welcome") {
//是的话不显示面包屑
return true;
} else {
//不是则显示面包屑
return false;
}
},
因为后台管理项目只有两层,所以我这里没用递归,只使用了双层for循环即可完成面包屑效果。
3.4.3还有给用户分配角色
这一步需要在模态框回填该角色的数据,并获取所有角色列表,渲染到下拉菜单里边,选中之后发送请求,携带当前用户id和角色ID就可以给用户分配角色
权限管理模块,顾名思义,就是给不同的用户分配不同的 *** 作和查阅的权限,而这个权限与用户中间的,又添加了角色这个概念,即给用户分配角色,给角色分配权限,这样会使得进权限分配更加的方便且灵活
3.5.1角色列表
首先需要在点击添加d出的模态框添加角色,只需填写角色名和角色描述即可通过接口提交就可以了,如图
添加完成之后就可以给这个角色分配相应的权限
我这里也封装了一个tree树状组件,代码如下
<div class="myTree">
<div class="treeP">
<b v-if="listFlag" @click="change">{{ isShow ? "-" : "+" }}</b>
<h4>{{ treeList.label }}</h4>
</div>
<div class="treeBox" v-show="isShow">
<my-tree
v-for="(item, index) in treeList.children"
:key="index"
:treeList="item"
></my-tree>
</div>
</div>
这个主要是使用的递归组件的思路,在组件里再使用一次当前的组件,第二次使用组件时就不需要再引入了,然后通过父传子一层一层的向下传递,即可实现递归显示所有树形数据,通过props接收的时候可以定义两种数据类型为Array,Object
props: {
treeList: {
type: [Object, Array],
required: true,
},
},
而树形组件是否显示呢则是通过计算属性实现的
listFlag() {
return this.treeList.children && this.treeList.children.length;
},
判断他是否有children且children有没有长度,这里的返回值就是判断是否隐藏的布尔值。
3.5.2权限列表
这个权限列表只需渲染即可,唯一要注意的点呢就是tag标签通过v-if或者v-show判断以下显示某个颜色的标签即可。
3.6.1商品列表
商品列表的增删改查的功能同用户管理的增删改查,都是 *** 作接口即可,而添加商品呢,需要跳转到另外的页面,这里说一下,如果使用的是动态路由的话,那么像welcom欢迎页,还有这个添加商品是需要自己另外配置路由的,因为请求menus列表的时候返回的路径是不包含这两个页面的路径的,所以需要手动配置。
那么现在开始添加商品的 *** 作。
点击添加按钮跳转到添加页,如图所示
这个选择商品分类呢,是一个级联菜单,这里需要将第二级的id通过接口发送给商品添加分类
3.6.2上传图片
然后在商品图片中呢,使用的是elementUi的上传图片的组件。
<el-upload
class="upload-demo"
:action="actionUrl"
:headers="headers"
:on-preview="handlePreview"
:on-remove="handleRemove"
:on-success="handleSuccess"
list-type="picture"
>
<el-button size="small" type="primary">点击上传</el-button>
</el-upload>
:action图片上传的地址
:headers请求头的配置
:on-success上传成功的钩子函数
:on-remove删除的钩子函数
:on-preview——点击列表中已上传的图片的钩子
上传成功后,需要把上传的图片的信息追加到数组中
3.6.3最后还有富文本编辑器
<quill-editor v-model="ruleForm.goods_introduce"> </quill-editor>
3.6.4分类参数
分类参数首先需要选择商品的分类,也是一个级联菜单,选择完之后可以给他们配置参数和属性,也是通过接口进行一些增删改查。
3.6.5商品分类
同用户管理的增删改查,这里不再过多赘述。
数据报表我们是使用echarts实现的,echarts的使用方法呢,和elementUi差不多,只需引入之后替换数据即可,需要注意的是呢,这个echarts的模板呢是需要通过ui给的图来选择的。像我刚开始自己学习echarts的时候是哪个好看用哪个,结果跟后台 返回的数据根本对不上,所以写了好久都没写出来,而当我替换了与效果图类似的模板,效果便直接出来了。还有一点,如果你的项目需要打包的话,不建议使用5.0以上的echarts,因为这个版本以上打包时需要在名称前添加* as
在打包抽离时不好根据名字去匹配,所以建议使用4.9.0左右的版本。下面是效果
至此,后台管理项目结束。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)