vue后台管理系统
1、项目准备
1、使用vue-cli 脚手架创建项目
2、项目是vue-cli +element-ui+axios+less 搭建的项目
3、安装项目element-ui
npm i element-ui -S
//在main.js中 引入element-ui
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI);
4、在main.js中引入字体图标
//在main.js中 iconfont注册
import './assets/iconfont/iconfont.css'
5、安装mock 用来生成模拟数据,用来模拟后台接口
npm install mockjs --save -dev
//在main.js 中引入mock
import './mock'
6、安装js-cookie 函数,该插件封装了cookie 对应的操作方法
npm install js-cookie
7、在src 目录下的utils文件中,定义request.js 文件,用来封装axios请求。
import { getCookie } from './cookie.js';
import Vue from 'vue'
import axios from 'axios';
//2。创建server
const instance = axios.create({
baseURL: '',// index/index http://kumanxuan1.f3322.net:8001
timeout: 10000 //超时链接
})
//3.请求拦截 登陆放token的地方
instance.interceptors.request.use(config => {
config.headers['My_ToKen'] = getCookie('token')
return config
})
//4.响应拦截 解码加密 公共逻辑判断 项目中所有的错误 都可以在这个位置进行处理
instance.interceptors.response.use(res => {
console.log(res)
//全局错误提示
if (res.status === 200 || res.data.code == 200) {
return res.data
} else {
Vue.prototype.$message({
message: '网络不通',
type: 'error'
});
}
})
export default instance
8、配置项目路由 在src目录下,新建router目录,在该目录下新建index.js 文件。路由配置如下:
//公共权限
const routes = [
{
path: '/',
redirect: '/layout'
},
{
path: '/layout',
name: 'Layout',
component: Layout,
children: [
{
path: '',
component: Home,
name: 'Home',
meta: {
title: '首页',
icon: 'el-icon-attract'
}
}, {
path: 'user',
name: 'User',
component: () => import('@/views/user/User.vue'),
meta: {
title: '用户管理',
icon: 'el-icon-coordinate'
}
}, {
path: 'msg', // 信息管理功能
name: 'Msg',
component: () => import('@/views/msg/Msg.vue'),
meta: {
title: '信息管理功能',
icon: 'el-icon-wallet'
},
children: [
{
path: 'mymsg', //个人信息
name: 'Mymsg',
component: () => import('@/views/msg/Mymsg.vue'),
meta: {
title: '个人信息',
icon: 'el-icon-set-up'
},
}
]
}
]
},
{
path: '/login',
name: 'Login',
component: Login
}
]
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
9、定义登录接口,在src 目录下新建mock 目录,在mock目录下,创建index.js 文件,该文件中定义所有的接口请求如下:
import Mock from 'mockjs'
import loginMock from './loginMock.js'
Mock.setup({
timeout: 400 //表示 400 毫秒 后才会返回响应内容
})
//mock语法:
//Mock.mock( rurl, rtype, function( options ) )
//记录用于生成响应数据的函数。当拦截到匹配 rurl 和 rtype 的 Ajax 请求时,函数 function(options)
//将被执行,并把执行结果作为响应数据返回。
// 定义所有接口
//1. 登陆接口
Mock.mock('/login', 'post', loginMock.login)
//2.角色获取权限列表
Mock.mock('/getPress', 'post', loginMock.getPress)
10、在src目录的mock文件中,新建loginMock.js文件,定义用于生成响应数据的函数
//Mock.mock( rurl, rtype, function( options ) )
//记录用于生成响应数据的函数。当拦截到匹配 rurl 和 rtype 的 Ajax 请求时,函数 function(options)
//将被执行,并把执行结果作为响应数据返回。
export default {
login: config => {
console.log(110, config)// config 含有 url、type 和 body 三个属性,body为参数
let { name, pwd } = JSON.parse(config.body) // 获取参数
let token = '' //token是就是用户账号和密码按规则转化而来
let role = ''
// 自定义如下2个账号
if (name === 'admin' && pwd == '123456') { //管理员账号
token = 'admin---token--XXXXX'
role = '管理员'
} else if (name === 'user' && pwd == '123456') { //大壮的账号
token = 'user---token--XXXXX'
role = '普通用户'
} else {
return {
code: 101,
msg: '账号密码不存在',
data: null
}
}
// 如果是admin或user 账户,则返回如下
return {
code: 200,
msg: '登陆成功',
data: {
token: token,
role: role
}
}
},
getPress: config => {
//admin---管理员--导航菜单[审批管理,请假审批,我要请假]
//user---普通用户---导航菜单[我要请假]
let { role } = JSON.parse(config.body) //管理员 普通用户
if (role == '管理员') {
return {
code: 200,
msg: '成功',
data: [
{
path: 'shenpi', //审批管理
meta: {
title: '审批管理',
icon: 'el-icon-bangzhu'
},
name: 'Shenpi' //componet
},
{
path: 'qingjia', //请假审批
meta: {
title: '请假审批',
icon: 'el-icon-bangzhu'
},
name: 'Qingjia' //componet
}, {
path: 'woqingjia', //我要请假
meta: {
title: '我要请假',
icon: 'el-icon-bangzhu'
},
name: 'Woqingjia' //componet
}
]
}
} else if (role == '普通用户') {
return {
code: 200,
msg: '成功',
data: [{
path: 'woqingjia', //我要请假
meta: {
title: '我要请假',
icon: 'el-icon-bangzhu'
},
name: 'Woqingjia' //componet
}
]
}
}
}
}
- src目录下的http目录中定义所有接口请求
// 存放所有的接口请求
import instance from "../utils/request";
// 登录接口
export function loginApi(params) {
return instance({
url: '/login',
method: 'post',
data: params //axios是就是promise封装的ajax 工具类
})
}
//根据角色获取权限菜单列表接口
export function getPressApi(params) {
return instance({
url: '/getPress',
method: 'post',
data: params
})
}
2、登录页
在src文件下的views目录中创建Login.vue 文件,代码如下:
<!-- 登录页面 -->
<template>
<div class="container">
<div class="form">
<div class="title">
<span>千锋科技后台管理</span>
</div>
<el-form
:model="ruleForm"
:rules="rules"
ref="ruleForm"
label-width="100px"
class="demo-ruleForm"
>
<el-form-item label="用户名" prop="name">
<el-input v-model="ruleForm.name"></el-input>
</el-form-item>
<el-form-item label="密码" prop="pwd">
<el-input v-model="ruleForm.pwd"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitForm('ruleForm')"
>提交</el-button
>
<el-button @click="resetForm('ruleForm')">重置</el-button>
</el-form-item>
</el-form>
</div>
</div>
</template>
<script>
import { loginApi } from "@/http/http";
import { setCookie } from "../utils/cookie.js";
export default {
data() {
return {
ruleForm: {
// 表单数据
name: "",
pwd: "",
},
rules: {
// 验证规则
name: [{ required: true, message: "用户名不能为空", trigger: "blur" }],
pwd: [{ required: true, message: "密码不能为空", trigger: "blur" }],
},
};
},
methods: {
submitForm(formName) {
this.$refs[formName].validate((valid) => {
if (valid) {
// alert("submit!");
//验证通过
loginApi(this.ruleForm).then((res) => {
if (res.code === 200) {
//1.将token 存到cookie中
setCookie("token", res.data.token);
//2.将角色role保存到localStorage
localStorage.setItem("role", res.data.role);
//3. 跳转到首页
this.$router.push("/");
} else {
this.$message.error("账号不存在");
}
});
} else {
this.$message.error("账号密码错误");
return false;
}
});
},
resetForm(formName) {
this.$refs[formName].resetFields();
},
},
};
</script>
<style scoped lang='less'>
/* @import url(); 引入css类 */
.container {
width: 100%;
height: 100%;
background: url("../assets/bg9.jpg") no-repeat center;
background-size: cover;
.form {
width: 370px;
height: 298px;
padding: 5px 10px;
background-color: #fff;
border-radius: 10px;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
.title {
width: 100%;
line-height: 50px;
text-align: center;
font-size: 18px;
font-weight: bold;
}
/deep/.ivu-form {
width: 300px;
}
/deep/.ivu-btn {
width: 300px;
height: 30px;
}
}
}
</style>
3、在router目录下的index.js 文件中,设置全局前置导航守卫,在登录成功后,页面跳转到首页前,对首页的左侧菜单栏根据角色接口返回的数据,动态修改左侧菜单栏,代码如下:
console.log(router);
//后台全局前置路由守卫
router.beforeEach((to, from, next) => {
const token = getCookie('token');
console.log(token);
if (token) {
// 说明已登录
if (to.path === '/login') {
// 如果当前路由为login,已存在token 直接跳转到首页
next('/')
} else {
//用户不同-角色不同--权限不同(菜单)
//admin---管理员--导航菜单[审批管理,请假审批,我要请假]
//user---普通用户---导航菜单[我要请假]
if (store.state.routes.length == 0) {
// 第一次登录
const role = localStorage.getItem('role') // store.state.role
getPressApi({ role: role }).then(res => {
if (res.code == 200) {
//把数据转成路由对象 放在二级路由
res.data.forEach(item => {
// console.log('item', item);
routes[1].children.push({
path: item.path,
name: item.name,
meta: {
title: item.meta.title,
icon: item.meta.icon
},
component: () => import('@/views/' + item.name)
})
})
store.dispatch('setRoleAction', routes).then(() => {
// console.log('动态生成的路由1', routes);
//在addRoutes()之后第一次访问被添加的路由会白屏,这是因为刚刚addRoutes()就立刻访问被添加的路由,然而此时addRoutes()没有执行结束,因而找不到刚刚被添加的路由导致白屏。因此需要从新访问一次路由才行。
router.addRoutes(routes) //动态添加路由
next({ ...to, replace: true }) //解决动态添加路由白屏问题 bug
})
}
})
} else {
next() // 有路由菜单直接放行
}
}
} else if (!token && to.path != '/login') {
console.log('没有token');
// 没有token 说明未登录,跳转到登录页
next('/login');
} else {
next()
}
})
export default router
3、首页
1、在views目录下,新建layout 目录,layout目录下新建layout.vue 文件,该页面是首页整体布局。代码如下:
<!--首页-->
<template>
<el-container>
<!-- 头部组件 -->
<el-header>
<Header></Header>
</el-header>
<!-- 下方主体部分 -->
<el-container>
<!-- 左侧边栏 -->
<el-aside style="width: 200px">
<Aside></Aside>
</el-aside>
<!-- 右侧主体 -->
<el-container>
<!-- 面包屑导航 -->
<Bread></Bread>
<el-main>
<!-- 二级路由坑 -->
<router-view></router-view>
</el-main>
<el-footer>Footer</el-footer>
</el-container>
</el-container>
</el-container>
</template>
<script>
// 引入头部
import Header from "./Header";
//引入左侧导航栏
import Aside from "./Aside.vue";
//引入面包屑导航
import Bread from "./Bread.vue";
export default {
data() {
return {};
},
components: {
Header,
Aside,
Bread,
},
};
</script>
<style scoped lang='less'>
/* @import url(); 引入css类 */
@mainColor: #eaebec; //正确的
.el-container {
width: 100%;
height: 100%;
}
.el-main {
background: @mainColor;
}
.el-footer {
background: lightblue;
}
</style>
2、在layout目录下,同时再新建header.vue头部组件,代码如下:
<!--layout中的头部header组件 -->
<template>
<div class="container">
<div class="left">
<img :src="img" alt="" />
<span>千锋后台管理</span>
</div>
<ul class="right">
<li class="dropdow">
<el-dropdown @command="handleCommand">
<span class="el-dropdown-link">
<span>你好,管理员</span>
<img :src="avatar2" alt="" />
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="a">个人信息</el-dropdown-item>
<el-dropdown-item command="b">修改信息</el-dropdown-item>
<el-dropdown-item command="c">退出</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</li>
</ul>
</div>
</template>
<script>
import logo from "../../assets/logo.png"; //推荐使用 vue
import avatar2 from "../../assets/avatar-2.jpg"; //推荐使用 vue
import { removeCookie } from "@/utils/cookie.js";
export default {
data() {
return {
img: logo,
avatar2: avatar2,
};
},
methods: {
handleCommand(command) {
// 退出登录
console.log(command);
if (command == "c") {
//退出 1.清除cookie 跳转登陆
removeCookie("token");
//强制刷新
this.$router.go(0);
}
},
},
};
</script>
<style lang="less" scoped>
.container {
display: flex;
justify-content: space-between;
height: 60px;
width: 100%;
align-items: center;
.left {
width: 180px;
height: 100%;
display: flex;
align-items: center;
img {
height: 36px;
width: 36px;
}
span {
font-size: 24px;
font-weight: bold;
}
}
.right {
flex: 1;
height: 60px;
display: flex;
justify-content: flex-end;
align-items: center;
i {
fill: #51c332;
}
img {
width: 24px;
height: 24px;
}
.icon {
width: 55px;
text-align: center;
.wxicon {
width: 24px;
height: 24px;
fill: #51c332;
}
}
.tabEle {
width: 36px;
}
.dropdow {
width: 124px;
/deep/.el-dropdown-link {
display: flex;
align-items: center;
color: #909399;
}
}
}
}
.el-dropdown-link {
cursor: pointer;
color: #409eff;
}
.el-icon-arrow-down {
font-size: 12px;
}
</style>
3、在layout目录下,新建Aside.vue侧边栏组件,由于显示首页左侧菜单导航,代码如下:
<!-- 侧边栏导航 -->
<template>
<div>
<el-menu
default-active="2"
class="el-menu-vertical-demo"
@open="handleOpen"
@close="handleClose"
>
<template v-for="(item, index) in menu">
<!-- 一级导航 -->
<router-link :to="'/layout/' + item.path" :key="item.path">
<el-menu-item
:index="item.path + index"
:key="item.path"
v-if="!item.children"
>
<i :class="item.meta.icon"></i>
<span slot="title">{{ item.meta.title }}</span>
</el-menu-item>
</router-link>
<!-- 二级导航 -->
<el-submenu
:index="item.path + index"
:key="item.path + index"
v-if="item.children"
>
<template slot="title">
<i :class="item.meta.icon"></i>
<span slot="title">{{ item.meta.title }}</span>
</template>
<template v-for="(child, cindex) in item.children">
<router-link
:to="'/layout/' + item.path + '/' + child.path"
:key="child.path + cindex"
>
<el-menu-item :index="child.path + cindex">
<i :class="child.meta.icon"></i>
<span>{{ child.meta.title }}</span>
</el-menu-item>
</router-link>
</template>
</el-submenu>
</template>
</el-menu>
</div>
</template>
<script>
export default {
data() {
return {};
},
computed: {
menu() {
console.log("所有菜单", this.$store.getters.getRoutes);
return this.$store.getters.getRoutes[1].children;
},
},
methods: {
handleOpen() {},
handleClose() {},
},
};
</script>
<style scoped>
/* @import url(); 引入css类 */
</style>
4、在layout目录下,新建Bread.vue面包屑导航组件,引入到layout文件中
<!-- 面包屑导航 -->
<template>
<div class="container">
<i class="el-icon-s-fold"></i>
<el-breadcrumb separator-class="el-icon-arrow-right">
<el-breadcrumb-item v-for="(item, index) in breadArr" :key="index">
{{ item.name }}
</el-breadcrumb-item>
</el-breadcrumb>
</div>
</template>
<script>
export default {
data() {
return {
breadArr: [], // 面包屑数组
};
},
watch: {
// $route(to, from) {
// // console.log(to)
// // 当前路由赋值
// this.currentPath = to.path;
// console.log(1, this.$route);
// this.breadcrumbArr = this.$route.matched; // 获取当前的路由记录
// },
$route: {
handler(val) {
// console.log(11, val); // val 即为监听的$route对象
// 先清空 breadArr,要不然越push 越多
this.breadArr = [];
val.matched.forEach((item, index) => {
if (index > 0) {
this.breadArr.push({
path: item.path,
name: item.meta.title,
});
}
});
//console.log(22, this.breadArr);
},
immediate: true, // 第一次页面加载就监听 而不是该对象发生变化才去监听
deep: true,
},
},
};
</script>
<style scoped lang='less'>
/* @import url(); 引入css类 */
@mainColor: #eaebec; //正确的
.container {
height: 30px;
width: 100%;
display: flex;
align-items: center;
background: @mainColor; // @mainColor;
i {
line-height: 30px;
font-size: 20px;
cursor: pointer;
padding-right: 20px;
}
}
</style>