文章目录
项目目录:
项目地址
https://gitee.com/sansan533/bk2115_pc
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生成模拟数据
安装mock 用来生成模拟数据,用来模拟后台接口
npm install mockjs --save -dev
//在main.js 中引入mock
import './mock'
6、安装js-cookie 函数,
该插件封装了cookie 对应的操作方法
npm install js-cookie
7、封装axios请求
在src 目录下的utils文件中,定义request.js 文件,用来封装axios请求。
src\utils\request.js
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、配置项目路由 router
在src目录下,新建router目录,在该目录下新建index.js 文件。路由配置如下:
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 文件,该文件中定义所有的接口请求如下:
src\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文件,定义用于生成响应数据的函数
后端根据不同的role角色,返回不同的导航菜单
- admin—管理员–导航菜单[审批管理,请假审批,我要请假]
- user—普通用户—导航菜单[我要请假]
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
}
}
},
// 后端根据不同的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
}
]
}
}
}
}
11. 前端定义所有接口请求 http.js
src\http\http.js
// 存放所有的接口请求
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、登录页 Login.vue
登陆成功,在cookie中存放token,在localstorage中存储role(管理员或者普通用户)
在src文件下的views目录中创建Login.vue 文件,代码如下:
src\views\Login.vue
模仿下面这个写的
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、使用vuex 存储路由和角色
1、在用户登录成功到跳转到首页之前,需要根据用户的角色role 请求角色菜单接口getPress,将请求返回的路由菜单添加到路由路由对象中,然后将更新后的路由数组存到vuex,在首页中从vuex 中获取路由数组,这样实现数据的全局获取和操作。可以将role 和 route数组 存到vuex全局变量中
- 安装vuex
npm install vuex --save
- 在src目录下新建store 目录,该目录下新建index.js 文件 代码如下:
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
routes: [], //打算存routes这个数组
role: localStorage.getItem('role')//保存角色信息
},
getters: {
getRoutes(state) {
return state.routes
}
},
mutations: {
setRoutes(state, routes) {
state.routes = routes
},
clearRoute(state) {
state.routes = []
},
setRole(state, role) {
state.role = role
}
},
actions: {
setRoleAction({ commit }, role) {
commit('setRoutes', role)
}
},
modules: {
}
})
4、 设置全局前置导航守卫
在router目录下的index.js 文件中,设置全局前置导航守卫,在登录成功后,页面跳转到首页前,对首页的左侧菜单栏根据角色接口返回的数据,动态修改左侧菜单栏,代码如下:
后端根据不同的role角色,返回不同的导航菜单
- admin—管理员–导航菜单[审批管理,请假审批,我要请假]
- user—普通用户—导航菜单[我要请假]
至于为什么要把添加后的路由添加到vuex的store的state中,这是在router/index.js文件中生成了路由,存到store中
在下面的MyAside.vue中,需要遍历动态生成的路由,渲染页面
src\router\index.js
import Vue from 'vue';
import VueRouter from 'vue-router';
Vue.use(VueRouter)
import {
getCookie
} from '@/utils/cookie'
import {
getPressApi
} from '@/http/http'
// 导入store
import store from '@/store'
//解决vue-router在3.0版本以上重复点菜单报错的问题
const originalPush = VueRouter.prototype.push
VueRouter.prototype.push = function push(location) {
return originalPush.call(this, location).catch(err => err)
}
//公共权限
const routes = [{
path: '/',
redirect: '/layout'
},
{
path: '/layout',
name: 'Layout',
component: () => import('@/views/layout/Layout.vue'),
children: [{
path: '',
component: () => import('@/views/home/Home.vue'),
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: () => import('@/views/Login.vue')
}
]
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
router.beforeEach((to, from, next) => {
let token = getCookie('token')
// console.log('333', token);
// 已经登录了
if (token) {
// 登录了,访问的是登录页
if (to.path === '/login') {
// 直接跳转到首页
next('/')
}
// 登录了,访问的不是登录页 动态添加路由
else {
console.log('查看', store.state.routes); //切换用户 没有清空
// 第一次,store.state.routes里没有值
if (store.state.routes.length === 0) {
//用户不同-角色不同--权限不同(菜单)
//admin---管理员--导航菜单[审批管理,请假审批,我要请假]
//user---普通用户---导航菜单[我要请假]
let role = localStorage.getItem('role') //获取角色
// 发送请求,获取不同角色,所对应的导航菜单
getPressApi({role: role}).then((res) => {
if (res.code == 200) {
res.data.forEach(item=>{
console.log('添加前的routes',routes);
routes[1].children.push({
path: item.path,
name: item.name,
meta: {
title: item.meta.title,
icon: item.meta.icon
},
component: () => import('@/views/' + item.name)
})
})
console.log('添加后的route',routes);
store.dispatch('setRoleAction', routes).then(() => {
console.log('动态生成的路由', routes);
//在addRoutes()之后第一次访问被添加的路由会白屏,这是因为刚刚addRoutes()就立刻访问被添加的路由,然而此时addRoutes()没有执行结束,因而找不到刚刚被添加的路由导致白屏。因此需要从新访问一次路由才行。
router.addRoutes(routes) //动态添加路由
next({ ...to, replace: true }) //解决动态添加路由白屏问题 bug
})
}
})
}else{
next() //放行
console.log('store.state.routes.',store.state.routes);
}
}
}
// 没登录,
else {
// 没登录,访问的不是登录页,直接去登录页
if (to.path != '/login') {
next('/login')
}
// 没登录,跳转的是登录页,直接放行
else {
next()
}
}
})
export default router
5、首页
1、Layout.vue 首页整体布局
在views目录下,新建layout 目录,layout目录下新建layout.vue 文件,该页面是首页整体布局。代码如下
一级路由想要显示得在APP.vue那留个坑
二级路由想要显示得在一级路由那留个坑
src\views\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、header.vue头部组件
在layout目录下,同时再新建header.vue头部组件,代码如下:
<template>
<div class="head-box">
<div class="logo">
<img src="../../assets/logo.png" alt="" />
<h3>千锋后台管理</h3>
</div>
<div class="right">
<el-dropdown @command="handleCommand">
<span class="el-dropdown-link">
你好,{{ role }}
<img :src="avatar2" alt="" />
<i class="el-icon-arrow-down el-icon--right"></i>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="info">个人信息</el-dropdown-item>
<el-dropdown-item command="change">修改</el-dropdown-item>
<el-dropdown-item command="logout">退出</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
</div>
</template>
<script>
import { removeCookie } from '@/utils/cookie'
import avatar2 from "@/assets/avatar-2.jpg"; //推荐使用
export default {
name: 'myheader',
data() {
return {
role: '',
avatar2:avatar2
}
},
created() {
console.log('8888',);
this.role = localStorage.getItem('role')
},
methods: {
handleCommand(command) {
if (command === 'logout') {
// 删除cookie
removeCookie('token')
// 刷新页面
this.$router.go(0)
}
}
}
}
</script>
<style lang="less" scoped>
.head-box {
height: 60px;
.logo {
float: left;
width: 180px;
height: 100%;;
display: flex;
align-items: center;
img {
width: 36px;
height: 36px;
}
h3 {
}
}
.right {
float: right;
line-height: 60px;
img{
width: 24px;
height: 24px;
vertical-align: middle;
}
}
}
</style>
3、Aside.vue侧边栏
在layout目录下,新建Aside.vue侧边栏组件,由于显示首页左侧菜单导航,代码如下:
src\views\layout\MyAside.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、Bread.vue面包屑导航
在layout目录下,新建Bread.vue面包屑导航组件,引入到layout文件中
matched 顾名思义 就是 匹配,假如我们目前的路由是/a/aa-01
,那么此时 this.$route.matched
匹配到的会是一个数组,包含'/','/a','/a/aa-01'
,这三个path的路由信息。然后我们可以直接利用路由信息渲染我们的面包屑导航。
src\views\layout\Bread.vue
<!-- 面包屑导航 -->
<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>
5、首页右侧主体
1、home页
效果展示
1、定义首页数据接口(后端),mock/index.js
在mock文件夹下的index.js文件中,定义接口如下:
//6.柱形图接口
Mock.mock('/getBarData', 'get', home.barData)
//7.饼形图接口
Mock.mock('/getPieData', 'get', home.pieData)
2、mock/homeMock.js
在mock文件夹下,创建homeMock.js文件,用来定义接口的数据返回:代码如下:
export default {
barData: () => {
// 返回柱形图数据
return {
code: 200,
msg: "柱形图数据",
data: {
name: "出勤率",
xAxisData: ["Mon", "Tue", "wed", "Thu", "Fri", "Sat"],
seriesData: [90, 85, 50, 100, 95, 92]
}
}
},
pieData: () => {
// 返回饼形图数据
return {
code: 200,
msg: "饼形图数据",
data: {
name: "",
seriesData: [{
// 数据项的名称
name: "HTML5",
// 数据项值8
value: 500,
},
{
// 数据项的名称
name: "JAVA",
// 数据项值8
value: 200,
},
{
// 数据项的名称
name: "大数据",
// 数据项值8
value: 100,
},
{
// 数据项的名称
name: "unity",
// 数据项值8
value: 50,
},
{
// 数据项的名称
name: "UI",
// 数据项值8
value: 50,
}]
}
}
}
}
3、http/http.js 定义首页的数据请求
在http目录下的http.js文件中,定义首页的数据请求,代码如下:
// 获取首页柱形图接口数据
export function getBarData(params) {
return instance({
url: '/getBarData',
method: 'get',
params: params
})
}
// 获取首页饼形图接口数据
export function getPieData(params) {
return instance({
url: '/getPieData',
method: 'get',
params: params
})
}
4、home/home.vue
在home目录下的home.vue中,引入接口请求,然后渲染页面,代码如下:
<!-- 首页 -->
<template>
<div class="home">
<el-row>
<el-col :span="12">
<BarEchart></BarEchart>
</el-col>
<el-col :span="12">
<PieEchart></PieEchart>
</el-col>
</el-row>
</div>
</template>
<script>
// 引入柱形图组件
import BarEchart from "@/components/BarEchart";
// 引入饼形图
import PieEchart from "@/components/PieEchart";
export default {
data() {
return {};
},
mounted() {},
components: {
BarEchart,
PieEchart,
},
};
</script>
<style scoped lang='less'>
/* @import url(); 引入css类 */
.home {
width: 100%;
height: 500px;
}
</style>
5、components/BarEchart.vue 柱形图组件
在components目录下,将饼形图和柱状图分别定义成单独的组件。BarEchart.vue和PieEchart.vue如下:
BarEchart.vue 代码如下:
<!-- BarEchart.vue 柱形图组件 -->
<template>
<div id="Barbox">
<div id="barwrap"></div>
</div>
</template>
<script>
// 引入echarts
import * as echarts from "echarts";
// 引入请求接口
import { getBarData } from "@/http/http";
export default {
data() {
return {
myChart: "",
};
},
mounted() {
// 基于准备好的dom,初始化echarts实例
this.myChart = echarts.init(document.getElementById("barwrap"));
// 绘制图表
getBarData().then((res) => {
if (res.code == 200) {
this.myChart.setOption(this.options(res.data));
}
});
},
methods: {
options(val) {
let option = {
title: {
text: "2115班每天出勤统计",
},
tooltip: {
formatter: "{b0}: {c0}%",
},
xAxis: {
data: val.xAxisData,
},
grid: {},
yAxis: {
name: "出勤率",
axisLine: {
show: true, // 显示y轴轴线
lineStyle: {
type: "solid",
},
},
},
series: [
{
name: "出勤率",
type: "bar",
data: val.seriesData,
},
],
};
return option;
},
},
};
</script>
<style scoped lang='less'>
/* @import url(); 引入css类 */
#barwrap {
width: 100%;
height: 500px;
}
</style>
6、components/PieEchart.vue 饼形图组件
echart饼图示例: https://echarts.apache.org/examples/zh/editor.html?c=pie-simple
<!-- 饼形图组件 -->
<template>
<div class="pie_box">
<div id="pie_wrap"></div>
</div>
</template>
<script>
// 引入 echarts
import * as echarts from "echarts";
// 引入 接口请求
import { getPieData } from "@/http/http";
export default {
data() {
return {
myechart: "",
};
},
mounted() {
this.myechart = echarts.init(document.getElementById("pie_wrap"));
getPieData().then((res) => {
if (res.code == 200) {
//给图表传参
this.myechart.setOption(this.options(res.data.seriesData));
}
});
},
methods: {
options(val) {
let option = {
title: {
text: "千锋各学科人数占比",
},
legend: {
right: 0,
top: "30px",
data: val.map((item) => {
return item.name;
}),
},
tooltip: {},
series: [
{
type: "pie",
radius: [0, "50%"],
data: val,
},
],
};
return option;
},
},
};
</script>
<style scoped>
/* @import url(); 引入css类 */
#pie_wrap {
width: 100%;
height: 500px;
}
</style>
2、user页
效果展示
1、定义首页数据接口 mock/index.js
在mock下的index.js 文件中,定义首页后台数据接口
// 3.定义首页学生数据列表接口
// RegExp('/userList.*')
// 发现 Mockjs 本身对 GET 请求的支持并不是很友好。举个例子,使用 Mock.mock("/user/getUserInfo", "get", mockData) 的时候,它只会拦截url等于 /user/getUserInfo 的请求,而对于带参数的请求,如/user/getUserInfo?id=12,因为不等于 /user/getUserInfo 就拦截不到,报404错误。
// 使用正则进行匹配,Mock.mock(new RegExp(url + ".*"), "get", mockData)
// 其中表达式正则 .* 表示任意长度的任意字符;
Mock.mock(new RegExp('/getStudentList.*'), 'get', user.getTable)
2、mock/userMock.js
在mock 目录下,新建userMock.js文件,定义该接口数据返回。
import Mock from 'mockjs'
let List = [] //全局list 保存所有的数据
let id = 1;
for (var i = 0; i < 100; i++) {
List.push(Mock.mock({
id: id++,
name: Mock.Random.cname(),
age: Mock.Random.integer(18, 30),
major: 'html5',
address: Mock.Random.county(true),
pay: Mock.Random.integer(0, 1),
graduateTime: Mock.Random.date('yyyy-MM-dd')
}))
}
// console.log(44, List)
export default {
getTable: (config) => {
// console.log(JSON.parse(config.body))
//搜索逻辑
const { searchVal, page, pageSize } = JSON.parse(config.body)
//分页逻辑 mock 56
// page=1 pageSize=10
// page=2 pageSiz=10
// page=3 pageSiz=10
//公式: (page-1)*pageSize <= index< page* pageSize 0----9
let newList1 = List.filter(item => {
return item.name.indexOf(searchVal) != -1
})
let newList2 = newList1.filter((item, index) => {
return (page - 1) * pageSize <= index && index < page * pageSize
}) //10 ----- 19
// 20 ---- 29
return {
code: 200,
msg: '',
data: newList2, //10条数
total: List.length
}
},
editData: (config) => {
console.log(config)
let { id, name, age, pay, major, address, graduateTime } = JSON.parse(config.body)
List.forEach(item => {
if (item.id == id) {
item.name = name;
item.age = age;
item.pay = pay;
item.major = major;
item.address = address;
item.graduateTime = graduateTime
}
})
return {
code: 200,
msg: '修改成功',
data: null
}
},
deleteData: (config) => {
//console.log(config);
let id = config.url.split('?')[1].split('=')[1]
let newList = List.filter((item) => {
return item.id != id
})
List = newList
return {
code: 200,
msg: "删除成功",
data: null
}
}
}
3、http/http.js 定义user 页的数据请求
//获取用户管理页列表数据
export function getStudentList(params) {
return instance({
url: '/getStudentList',
method: 'get',
data: params
})
}
// 编辑用户接口
export function editUser(params) {
return instance({
url: '/editUser',
method: 'post',
data: params
})
}
// 删除用户接口
export function deleteUser(params) {
return instance({
url: '/deleteUser',
method: 'get',
params: params
})
}
4、views /user.vue 首页右侧主体部分对应的首页页面。
在views 目录下,新建user.vue组件,用于展示首页右侧主体部分对应的首页页面。
<!-- user页 -->
<!-- 用户管理 -->
<template>
<div class="user">
<div class="search-container">
<div class="search-input">
<el-input v-model="search" placeholder="用户名"></el-input>
<el-button type="primary" @click="searchFn">筛选</el-button>
</div>
<div class="search-input">
<el-button type="primary">批量删除</el-button>
<el-button type="primary">添加</el-button>
</div>
</div>
<template>
<el-table
ref="multipleTable"
:data="tableData"
tooltip-effect="dark"
style="width: 100%"
>
<el-table-column type="selection" width="55"> </el-table-column>
<el-table-column label="ID" width="50">
<template slot-scope="scope">{{ scope.row.id }}</template>
</el-table-column>
<el-table-column label="姓名" width="100">
<template slot-scope="scope">{{ scope.row.name }}</template>
</el-table-column>
<el-table-column label="年龄" width="80">
<template slot-scope="scope">{{ scope.row.age }}</template>
</el-table-column>
<el-table-column label="专业" width="120">
<template slot-scope="scope">{{ scope.row.major }}</template>
</el-table-column>
<el-table-column label="地址" width="200">
<template slot-scope="scope">{{ scope.row.address }}</template>
</el-table-column>
<el-table-column label="缴费" width="80">
<template slot-scope="scope">{{ scope.row.pay }}</template>
</el-table-column>
<el-table-column label="毕业时间" width="100">
<template slot-scope="scope">{{ scope.row.graduateTime }}</template>
</el-table-column>
<el-table-column width="200">
<template slot-scope="scope">
<el-button type="success" @click="editFn(scope.row)"
>编辑</el-button
>
<el-button type="warning" @click="delFn(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</template>
<!-- 定义模态框 -->
<el-dialog title="提示" :visible.sync="dialogVisible" width="30%">
<div class="content">
<el-input v-model="editInfo.name" placeholder="姓名"></el-input>
<el-input v-model="editInfo.age" placeholder="年龄"></el-input>
<el-input v-model="editInfo.major" placeholder="专业"></el-input>
<el-input v-model="editInfo.address" placeholder="地址"></el-input>
<el-select v-model="editInfo.pay" placeholder="是否缴费">
<el-option label="是" value="1"></el-option>
<el-option label="否" value="0"></el-option>
</el-select>
<el-date-picker
v-model="editInfo.graduateTime"
type="date"
placeholder="毕业时间"
>
</el-date-picker>
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">取 消</el-button>
<el-button type="primary" @click="editSure">确 定</el-button>
</span>
</el-dialog>
<!-- 分页 -->
<el-pagination
background
layout="prev, pager, next"
:total="total"
:page-size="pageSize"
:pager-count="5"
@current-change="currentchange"
@prev-click="prev"
@next-click="next"
>
</el-pagination>
</div>
</template>
<script>
import { getStudentList, editUser, deleteUser } from "@/http/http";
export default {
data() {
return {
tableData: [], // 表格数组
page: 1, // 页数 第几页
pageSize: 10, // 每页条数
search: "", // 搜索值
dialogVisible: false, // 是否显示模态框
editInfo: {}, // 编辑用户信息
total: 0, // 总数据条数
};
},
methods: {
searchFn() {
// 根据搜索条件进行筛选
this.getlist();
},
getlist() {
// 获取学生列表数据
getStudentList({
searchVal: this.search,
page: this.page,
pageSize: this.pageSize,
}).then((res) => {
// console.log(11, res);
this.tableData = res.data;
this.total = res.total;
});
},
editFn(item) {
// console.log(item);
//编辑按钮
this.dialogVisible = true;
// 数据回填
this.editInfo = item;
},
editSure() {
// 编辑确认按钮
// console.log(this.editInfo);
// 将获取的日期格式转换成yyyy-mm-dd
let graduateTime = new Date(this.editInfo.graduateTime);
let month =
graduateTime.getMonth() + 1 < 10
? "0" + (graduateTime.getMonth() + 1)
: graduateTime.getMonth() + 1;
let year = graduateTime.getFullYear();
let day =
graduateTime.getDate() < 10
? "0" + graduateTime.getDate()
: graduateTime.getDate();
let selectDay = year + "-" + month + "-" + day;
// console.log(selectDay);
this.editInfo.graduateTime = selectDay;
//调用编辑用户接口
editUser(this.editInfo).then((res) => {
if (res.code == 200) {
// 隐藏模态框
this.dialogVisible = false;
// 修改成功提示
this.$message({
message: "修改成功",
type: "success",
duration: 2000,
});
// 重新调用列表接口
this.getlist();
}
});
},
delFn(item) {
// 删除按钮
console.log(item);
deleteUser({ id: item.id }).then((res) => {
if (res.code == 200) {
// 重新调用学生列表
// 修改成功提示
this.$message({
message: "修改成功",
type: "success",
duration: 2000,
});
this.getlist();
}
});
},
currentchange(val) {
// 点击页码时触发
// console.log(val);
this.page = val;
// 重新调用学生列表
this.getlist();
},
prev(val) {
// 点击上一页触发
// console.log(val);
this.page = val;
// 重新调用学生列表
this.getlist();
},
next(val) {
// 点击下一页触发
// console.log(val);
this.page = val;
// 重新调用学生列表
this.getlist();
},
},
created() {
// 初始化调用
this.getlist();
},
};
</script>
<style scoped lang='less'>
.user {
overflow: auto;
}
/* @import url(); 引入css类 */
.search-container {
height: 66px;
margin: 0 0 20px;
padding: 0px 18px 0px;
background-color: #fff;
display: flex;
justify-content: space-between;
.search-input {
display: flex;
align-items: center;
}
/deep/.el-input__inner {
width: 163px;
height: 30px;
margin-right: 15px;
}
}
.content .el-input {
margin: 5px 0;
}
</style>