[vue项目] pc后台管理系统 (vue-cli +element-ui+axios+less) --- element-ui axios请求 权限管理面包屑导航

项目目录:

在这里插入图片描述

项目地址

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
在这里插入图片描述

element表单form
在这里插入图片描述

模仿下面这个写的
在这里插入图片描述

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>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值