[vue] 项目 --- pc后台管理系统

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
                }
                ]
            }
        }
    }

}
  1. 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>
上一篇:剑指 Offer 40. 最小的k个数


下一篇:数据结构实验五