前言

该项目是一款对公司员工及商品管理的后台系统,主要实现功能:公司角色的增删改查,和商品的增删改查,项目的主要模块有,登录,主页,员工管理,权限管理,商品管理,该项目的亮点是权限管理,不同角色登录进入首页,看到的菜单和可操作的按钮是不一样的,如系统管理员可查看和操作所有模块。

1.初始化项目

涉及的前端技术栈:

Vue
Vue是一个用于创建用户界面的开源JavaScript框架
Vue-router
vue-router是Vue官方推出的路由管理器
elementUi
element是基于VUE的一套UI组件库
Axios
Axios,是一个基于promise网络请求库,作用于node.js和浏览器中
Echarts
“ECharts是一款基于JavaScript的数据可视化图表库
Vuex
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式 + 库

2.项目功能模块划分

  • 登录/退出功能
  • 主页布局
  • 用户管理模块
  • 权限管理模块
  • 分类管理模块
  • 商品列表模块
  • 订单管理模块
  • 数据统计模块

3.各模块功能实现

3.1封装axios请求

import axios from 'axios'
//因为我们将tokne存入的vuex,所以这里需要引入vuex
import vuex from '../store'

var api = axios.create({
  baseURL: 'http://127.0.0.1:8888/api/private/v1/',
  timeout: 5000
})

api.interceptors.request.use((config) => {
  if (vuex.state.token) {
  //这里是进行判断有没有token,如果有token的话通过请求头将token携带过去请求参数
    config.headers.Authorization = vuex.state.token
  }
  return config
})

api.interceptors.response.use((res) => {
	//这里是为了token过期之后是否回退到登陆页面
  let code = res.data.meta.status
  //获取返回的状态码
  if (res.data.meta.msg == "无效token") {
  //判断返回的msg如果为无效token的话,就说明token过期了,此时弹出一个提示框。
    MessageBox.confirm('token过期, 是否跳转到登录页面?', '提示', {
      confirmButtonText: '重新登录',
      //若用户点击重新登录则回到登录页
      cancelButtonText: '取消',
      //若用户点击取消,则留在当前页面,但什么也做不了
      type: 'warning'
    }).then(() => {
      //通过原生js的方法跳转到登录页
      location.href = '/login'

    }).catch((err) => {
    err
    })
  }
  return res.data
})
//最后导出出去
export default api

3.1.1二次封装
这里拿权限的接口为例

// 权限列表
export const menus = () => api.get("/menus")

3.1.2调用
在需要的页面就可以通过.then获取数据

getData() {
      menus().then((res) => {
        this.menusList = res.data;
      });
    },

3.2登录/退出功能

对于后台管理这个项目来说呢,所有的功能和页面要求用户必须登录之后才可以查看,再后面的各个模块中,需要发送大量请求来获取数据,再进行页面的渲染,那么这个时候就需要进行哦按段用户的登录状态,登录成功后,才可以拿到相应的数据

3.2.1实现登录功能
这个很简单,只需将用户填写的用户名和密码通过接口发送到服务器验证即可,在服务器返回数据后将token存入Vuex

3.2.2
基于elementUi的表单验证

通过:rules绑定表单验证规则的对象,在表单上通过ref绑定,再通过$refs拿到对应的表单对象,然后调用validate方法进行验证

 ruleValidate: {
        username: [
          { required: true, message: "用户名不能为空", trigger: "blur" },
          { min: 3, max: 10, message: "用户名长度在3到10个字符",trigger:'blur'},
        ],
        password: [
          { required: true, message: "密码不能为空", trigger: "blur" },
          { min: 6, max: 10, message: "用户名长度在6到10个字符",trigger:'blur'},
        ],
      },

登录校验并将token存入本地

 handleSubmit(name) {
      let tant = this;
      this.$refs[name].validate((valid) => {
        if (valid) {
          login(this.formValidate).then((res) => {
          //这里是将token存入vuex
            this.$store.commit("token", res.data.token);
            this.$Notice.success({
              title: "登录成功",
              duration: 1,
            });
            this.$router.push("/");
          });
        } else {
          this.$Message.error("表单验证失败!");
        }
      });
    },

3.2.3路由守卫控制访问权限

router.beforeEach((to, from, next) => {
//to,from,next分别代表要去的地方,从哪里来,和放行或者重定向
  let token = vuex.state.token
  //先判断有没有token
  if (token) {
  //有的话就一路畅通
    next()
  } else {
  //没有的话判断是不是去登录页
    if (to.path == '/login') {
    //是的话放行
      next()
    } else {
    //不是的话就强行让他回到登录页
      next('/login')
    }
  }
})

3.2.4退出功能
因为是基于token实现的登录,所以退出只需将存在vuex中的token销毁并跳转到登录页即可

3.3主页布局

这里是使用elementUi的菜单栏组件实现的,只需替换数据即可。
菜单栏的原本数据是树状结构,这里可以在router文件内挨个添加并引入本地路径,但稍显繁琐且代码量增多,那么这里呢就可以使用动态路由模式。
在这里插入图片描述

因为我们获取的数据是树状的,所以需要先通过递归获取最下层的结构并处理成列表结构,代码如下


export function fn(data) {
//先声明一个空数组,用来存放最终的列表结构
  let arr = [];
  //递归函数
  function deep(data) {
  //循环每一项
    data.forEach((item) => {
    	//判断有没有children
      if (item.children.length) {
      //如果有的话,说明不是底层数据,开始递归
        deep(item.children);
      } else {
      //如果没有的话则证明已经是底层数据了
        arr.push({
        //路径名
          path: "/" + item.path,
          //name名
          name: item.authName,
          //引入,注意   这里的文件还是需要手动配置的
          component: () => import("@/views/homeList/" + item.path[0].toUpperCase() + item.path.substring(1) + ".vue"),

        });
      }
    });
  }
  deep(data);
  //最终return底层数据的数组
  return arr;
}

这里是之后的列表数据
在这里插入图片描述
最后再使用this.$router.addRoute添加到首页下的children里边就可以了,代码如下

 function loadRoute() {
          let token = tant.$store.state.token;
          let menus = JSON.parse(localStorage.getItem("menus"));
          if (token && menus) {
            let newList = fn(menus);
            newList.forEach((item) => {
              tant.$router.addRoute("Home", item);
            });
          }
        }
        loadRoute();

3.4用户管理模块

3.4.1

这个用户管理模块,就是由最基本的增删改查来实现的,首先通过接口请求用户列表的数据,然后通过elementUi的table表格渲染就可以了,大部分功能都是操作接口完成的,如根据ID搜索用户,就是传一个id值给后端,后端进行筛选之后返回符合id的用户,我们拿到数据之后进行渲染就可以。还有状态,也是通过接口像后端发送需要修改状态的用户id和修改后的布尔值,即可修改状态。这里值得说一下的有,添加用户和编辑用户可以公用一个模态框,代码如下。

//点击添加将判断的布尔值改为true,点击编辑则改为false
    handleSubmit(name) {
    //这里根据布尔值判断此次执行函数为编辑还是添加
      if (this.deilOrAdd) {
      //如果是添加的话
        this.$refs[name].validate((valid) => {
          if (valid) {
          //通过表单验证
            this.isShow = false;
            //通过接口传递参数给后端
            usersAdd(this.formValidate).then((res) => {
              if (res.meta.status == 201) {
              //创建成功后重新获取数据
                this.getData();
                this.$Notice.success({
                  title: "创建成功",
                });
              }
            });
          } else {
          //表单验证失败
            this.$Message.error("表单验证失败!");
          }
        });
      } else {
      //如果为编辑的话
        let obj = {
          id: this.userId,
          obj1: this.formValidate,
        };
        //先回填当前编辑用户的数据
        usersDeil(obj).then((res) => {
        //通过接口传递参数给后端进行修改
          if (res.meta.status == 200) {
            this.$Notice.success({
              title: "更新成功",
            });
            //修改完成重新刷新
            this.getData();
            //将模态框隐藏
            this.isShow = false;
          }
        });
      }
    },

3.4.2分页
值得一说的还有分页的逻辑了,分页所有的功能都是围绕这currentpagesizetoken来实现的,我这里自己封装了一个分页的组件,代码如下

<template>
  <div class="myPage">
    <span class="total">{{ total }}</span>
    <div class="before">
      <div @click="sub" class="arrows beforeArrows"></div>
    </div>
    <span
      @click="item == '...' ? false : $emit('change-current', item)"
      :class="current == item ? 'pageItem pageItemACur' : 'pageItem'"
      v-for="(item, index) in pagenum"
      :key="index"
      >{{ item }}</span
    >
    <div class="aftter">
      <div @click="add" class="arrows aftterArrows"></div>
    </div>
    <p class="toPage">
      前往<input
        class="inp"
        type="text"
        v-model.number="toPage"
        @keyup.enter="toPageNum"
      /></p>
  </div>
</template>
<script>
export default {
  name: "demo",
  props: {
  //接收父组件传递过来的总条数
    total: {
      type: Number,
      default: 50,
    },
  //接收父组件传递过来的每页条数
    pagesize: {
      type: Number,
      default: 5,
    },
  //接收父组件传递过来的当前页
    current: {
      type: Number,
      default: 1,
    },
  },
  components: {},
  data() {
    return {
      num: 0,
      toPage: this.current,
    };
  },
  created() {},
  computed: {
  //这里使用计算属性完成
    pagenum() {
    //先声明一个存放按钮内容的空数组
      let pagenumArr = [];
      //遍历token每页条数就可以获得
      for (var i = 0; i < Math.ceil(this.total / this.pagesize); i++) {
      //push到数组
        pagenumArr.push(i + 1);
      }
      //此时需要判断按钮的数量
      //以这里为例,我希望按钮最大数量为9,所以加了个>9的if判断
      if (pagenumArr.length > 9) {
      //再进行判断  当前值是否为5以下
        if (this.current <= 5) {
        //为5以下的显示前7个按钮,中间用...代替,最后一个值为数组的长度
          pagenumArr = [1, 2, 3, 4, 5, 6, 7, "...", pagenumArr.length];
        } else if (this.current >= pagenumArr[pagenumArr.length - 1] - 4) {
        //反之判断是否当前值是否是在数组的末尾
          pagenumArr = [
            1,
            "...",
            pagenumArr.length - 6,
            pagenumArr.length - 5,
            pagenumArr.length - 4,
            pagenumArr.length - 3,
            pagenumArr.length - 2,
            pagenumArr.length - 1,
            pagenumArr.length,
          ];
         // 如果是在数组的末尾则将1后边的省略为...,最后的值为length--就能得出后边的值
        } else {
        //再进行判断是否是在中间点,如果是在中间,则两边以...显示
          pagenumArr = [
            1,
            "...",
            this.current - 2,
            this.current - 1,
            this.current,
            this.current + 1,
            this.current + 2,
            "...",
            pagenumArr.length,
          ];
        }
      }
      //return出去
      return pagenumArr;
    },
  },
  methods: {
  //去第几页的函数
    toPageNum() {
    //如果跳转的页码大于总页数,则跳到最后一页
      if (this.toPage > this.pagenum[this.pagenum.length - 1]) {
        this.$emit("change-current", this.pagenum[this.pagenum.length - 1]);
      } else {
      //反之则跳转到相应页面
        this.$emit("change-current", this.toPage);
      }
      //跳转完input失焦
      document.querySelector(".inp").blur();
    },
    add() {
    //点击判断是否是最后一位
      if (this.current >= this.pagenum[this.pagenum.length - 1]) {
      //是的话return
        return;
      } else {
      //不是则跳转
        this.$emit("change-current", this.current + 1);
      }
    },
    sub() {
    //判断是不是第一位
      if (this.current <= 1) {
      //是就return
        return;
      } else {
      //不是则跳转
        this.$emit("change-current", this.current - 1);
      }
    },
  },
  mounted() {}
};
</script>

3.4.3面包屑导航
我这里的面包屑导航是使用了自己封装的一个函数,代码如下;

 goItem(e) {
 //在点击路由跳转时触发
      this.menusList.forEach((item) => {
      //遍历menus
        item.children.forEach((ele) => {
        //遍历menus里边的每一个children
          if (ele.path == e) {
          //如果children的path等于路由要跳转的路径的时候
            this.bread = { Fname: item.authName, Cname: ele.authName };
            //将data里边的面包屑对象更换,第一个值是父亲的name,第二个值是儿子的name,这样就可以拿这个对象去渲染面包屑了
          }
        });
      });
      //同时将当前路径存储到data中
      this.name = e;
      //进行跳转,这里catch是vue重复调换报错的问题,捕获报错但不发不出来,蛮有趣的。
      this.$router.push("/" + e).catch((err) => {
        err;
      });
    },
breadShow() {
//判断刚刚存入的路径名称
      this.name = this.name + "";
      //是不是等于欢迎页
      if (window.location.hash != "#/welcome") {
      //是的话不显示面包屑
        return true;
      } else {
      //不是则显示面包屑
        return false;
      }
    },

因为后台管理项目只有两层,所以我这里没用递归,只使用了双层for循环即可完成面包屑效果。

3.4.3还有给用户分配角色在这里插入图片描述
这一步需要在模态框回填该角色的数据,并获取所有角色列表,渲染到下拉菜单里边,选中之后发送请求,携带当前用户id和角色ID就可以给用户分配角色

3.5权限管理模块

权限管理模块,顾名思义,就是给不同的用户分配不同的操作和查阅的权限,而这个权限与用户中间的,又添加了角色这个概念,即给用户分配角色,给角色分配权限,这样会使得进权限分配更加的方便且灵活

3.5.1角色列表

在这里插入图片描述
首先需要在点击添加弹出的模态框添加角色,只需填写角色名和角色描述即可通过接口提交就可以了,如图在这里插入图片描述
添加完成之后就可以给这个角色分配相应的权限
在这里插入图片描述
我这里也封装了一个tree树状组件,代码如下

 <div class="myTree">
    <div class="treeP">
      <b v-if="listFlag" @click="change">{{ isShow ? "-" : "+" }}</b>
      <h4>{{ treeList.label }}</h4>
    </div>
    <div class="treeBox" v-show="isShow">
      <my-tree
        v-for="(item, index) in treeList.children"
        :key="index"
        :treeList="item"
      ></my-tree>
    </div>
  </div>

这个主要是使用的递归组件的思路,在组件里再使用一次当前的组件,第二次使用组件时就不需要再引入了,然后通过父传子一层一层的向下传递,即可实现递归显示所有树形数据,通过props接收的时候可以定义两种数据类型为Array,Object


  props: {
    treeList: {
      type: [Object, Array],
      required: true,
    },
  },

而树形组件是否显示呢则是通过计算属性实现的

 listFlag() {
      return this.treeList.children && this.treeList.children.length;
    },

判断他是否有children且children有没有长度,这里的返回值就是判断是否隐藏的布尔值。

3.5.2权限列表
这个权限列表只需渲染即可,唯一要注意的点呢就是tag标签通过v-if或者v-show判断以下显示某个颜色的标签即可。

3.6商品管理

3.6.1商品列表
商品列表的增删改查的功能同用户管理的增删改查,都是操作接口即可,而添加商品呢,需要跳转到另外的页面,这里说一下,如果使用的是动态路由的话,那么像welcom欢迎页,还有这个添加商品是需要自己另外配置路由的,因为请求menus列表的时候返回的路径是不包含这两个页面的路径的,所以需要手动配置。
那么现在开始添加商品的操作。
点击添加按钮跳转到添加页,如图所示在这里插入图片描述
这个选择商品分类呢,是一个级联菜单,这里需要将第二级的id通过接口发送给商品添加分类
在这里插入图片描述
3.6.2上传图片
然后在商品图片中呢,使用的是elementUi的上传图片的组件。

<el-upload
            class="upload-demo"
            :action="actionUrl"
            :headers="headers"
            :on-preview="handlePreview"
            :on-remove="handleRemove"
            :on-success="handleSuccess"
            list-type="picture"
          >
            <el-button size="small" type="primary">点击上传</el-button>
          </el-upload>

:action图片上传的地址
:headers请求头的配置
:on-success上传成功的钩子函数
:on-remove删除的钩子函数
:on-preview——点击列表中已上传的图片的钩子
上传成功后,需要把上传的图片的信息追加到数组中

3.6.3最后还有富文本编辑器

<quill-editor v-model="ruleForm.goods_introduce"> </quill-editor>

3.6.4分类参数
分类参数首先需要选择商品的分类,也是一个级联菜单,选择完之后可以给他们配置参数和属性,也是通过接口进行一些增删改查。

3.6.5商品分类
同用户管理的增删改查,这里不再过多赘述。

3.7数据报表

数据报表我们是使用echarts实现的,echarts的使用方法呢,和elementUi差不多,只需引入之后替换数据即可,需要注意的是呢,这个echarts的模板呢是需要通过ui给的图来选择的。像我刚开始自己学习echarts的时候是哪个好看用哪个,结果跟后台 返回的数据根本对不上,所以写了好久都没写出来,而当我替换了与效果图类似的模板,效果便直接出来了。还有一点,如果你的项目需要打包的话,不建议使用5.0以上的echarts,因为这个版本以上打包时需要在名称前添加* as在打包抽离时不好根据名字去匹配,所以建议使用4.9.0左右的版本。下面是效果
在这里插入图片描述

至此,后台管理项目结束。

Logo

前往低代码交流专区

更多推荐