MIS 后台管理系统

713 阅读3分钟

项目描述: 该项目是一套电商后台管理系统,用于管理用户账号、商品分类、商品信息、订单、数据统计等业务功能

主要功能模块: 用户登录 用户退出 用户管理 用户列表 权限管理 角色权限和权限列表 商品管理 商品分类、分类管理和参数管理 订单管理 数据统计 数据报表

技术栈:Vue、Vue-router、Element UI、axios、Echarts 后端用到了 Node.js

项目初始化:

前端 1.安装 Vue 脚手架

通过 vue ui 

2.通过 Vue 脚手架创建项目 3.配置 Vue 路由 安装路由 4.配置 Element UI 组件库

添加插件 vue-cli-plugin-element

5.配置 axios 库

添加依赖 axios

6.初始化 git 远程仓库,托管到 github 或 码云

登录退出功能

  • token

登录页面输入用户名和密码进行登录,服务器验证通过后生成该用户的token并返回

客户端存储该token,后续所有的请求都携带该token发送请求,服务端验证token是否通过

  • 登录界面布局,通过element ui 组件实现 el-form el-form-item el-input el-button 字体图标
git-checkout -b login 创建分支,git branch 切换分支

-运行 serve 预览项目

yarn serve

main.js 入口文件 删除不必要文件

  • 创建登录组件 在 component文件夹下创建 Login.vue 组件
<template>
  <div>
  登录组件
  </div>
</template>

<script>
export default {
}
</script>

<style lang="less" scoped>

</style>

  • 在 router index.js 里面引入 Login from ./components/Login.vue
import Vue from 'vue'
import Router from 'vue-router'
import Login from '../components/Login.vue'

Vue.use(Router)

export default new Router({
  routes: [
    { path: '/', redirect: '/login' }, // 路由重定向
    { path: '/login', component: Login }
  ]
})

然后在App.vue根组件中放一个路由占位符 router-view

  <div id="app">
    <router-view></router-view>
  </div>
  • 登录组件表单 安装less和less-loader Element ui 在plugins里面的element.js 按需引入
import Vue from 'vue'
import { Button, Form, FormItem, Input } from 'element-ui'

Vue.use(Button)
Vue.use(Form)
Vue.use(FormItem)
Vue.use(Input)
  • 导入小图标 fonticon的使用

将图标下载的 fonts文件内容复制到 assets 在main.js中导入

import './assets/fonts/iconfont.css'

snore Toast 提示怎么关闭

type=“password” //

  • v-model数据绑定
:model="form"
v-model="form.name"
<script>
  export default {
    data() {
      return {
        form: {
          name: '',
          region: '',
          date1: '',
          date2: '',
          delivery: false,
          type: [],
          resource: '',
          desc: ''
        }
      }
    },
  • 表单数据验证
:rules="rules"
prop=“name”
 rules: {
          name: [
            { required: true, message: '请输入活动名称', trigger: 'blur' },
            { min: 3, max: 5, message: '长度在 3 到 5 个字符', trigger: 'blur' }
          ],
  • 表单的重置 resetFields

  • 登录组件登录前的预验证 validate

  • 登录组件根据预验证是否发起请求 valid

  • 登录组件配置弹窗提示 引入Message import {Message } from 'element-ui' Vue.prototype.$message = Message

this.message.error(登录失)this.message.error('登录失败') this.message.success('登录成功')

  • 登录组件登录成功后的行为 将登录成功之后的token,保存到客户端的sessionStorage中

  • 路由导航守卫控制访问权限 用户没有登录,直接通过url访问特定页面,需要重新导航到登录页面 为路由对象,添加beforeEach导航守卫 router.beforeEach((to,from,next)=>{ if (to.path === '/login') return next() const tokenStr = window.sessionStorage.getItem('token') if(!tokenStr) return next('/login') next() })

  • 退出功能 清空token methods:{ logout(){ window.sessionStorage.clear(); this.$router.push('/login') }

  • 处理语法警告问题

新建.prettierrc

{
  "semi": false,
  "singleQuote": true,
  "trailingComma": false
}

设置.eslintrc.js

module.exports = {
  root: true,
  env: {
    node: true
  },
  extends: [
    'plugin:vue/essential',
    '@vue/standard'
  ],
  parserOptions: {
    parser: 'babel-eslint'
  },
  rules: {
    'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
    'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
    'space-before-function-paren': 0
  },
}

用户管理

用户列表

  • 主页布局
    • 主页header布局
    • 左侧菜单栏
  • 通过接口获取菜单数据
    • 通过axios请求拦截器添加 token,保证拥有获取数据的权限
axios.interceptors.request.use(config => {
 console.log(config)
 config.headers.Authorization = window.sessionStorage.getItem('token')
 return config
})
  • 发起请求获取左侧菜单数据
     async getMenuList(){
      const {data: res} = await this.$http.get('menus')
      if(res.meta.status !==200) return this.$message.error(res.meta.msg)
      this.menulist = res.data
      console.log(res)
     }
  • 左侧菜单 UI 绘制

  • 左侧菜单格式美化,颜色,图标,unique-opened 是否只保持一个子菜单的展开

  • 实现左侧菜单的折叠和展开功能,动态绑定

  • 实现首页路由的重定向

   {
      path: '/home', component: Home,
      redirect: '/welcome',
      children: [{ path: '/welcome', component: Welcome }]
    }
  ]
  • 左侧菜单栏改造为路由模式 在菜单栏上加router

  • 用户列表开发

  • 绘制用户列表的基本UI结构

  • 获取用户列表数据

export default {
    data(){
      return{
        queryInfo:{
          query:'',
          pagenum:1,
          pagesize:2
        }, 
        userlist:[],
        total:0
      }
    },
    created(){
      this.getUserlist()
    },
    methods: {
        async getUserlist(){
         const {data: res} = await this.$http.get('users',{params:this.queryInfo})
         if(res.meta.status !==200){ 
           return this.$message.error('获取用户列表失败!')
           }
           this.userlist = res.data.users
           this.total = res.data.total
           console.log(res)
        }
      },
  }
  • 渲染用户列表数据
<el-table :data="userlist" border stripe>
    <el-table-column label="姓名" prop='username'></el-table-column>
    <el-table-column label="邮箱" prop='email'></el-table-column>
    <el-table-column label="电话" prop='mobil'></el-table-column>
    <el-table-column label="角色" prop='role_name'></el-table-column>
    <el-table-column label="状态" prop='mg_state'></el-table-column>
    <el-table-column label="操作"></el-table-column>
</el-table>
  • 为用户表格添加索引列
 <el-table-column type="index"></el-table-column>
  • 改造状态列的显示效果
<template slot-scope="scope">
     <el-switch v-model="scope.row.mg_state">
     </el-switch>
</template>
  • 插槽形式自定义渲染
  <template slot-scope="scope" >
     <el-button type="primary" icon="el-icon-edit" size="mini"></el-button>
     <el-button type="danger" icon="el-icon-delete" size="mini"></el-button>
       <el-tooltip effect="dark" content="分配角色" placement="top" :enterable="false">
     <el-button type="warning" icon="el-icon-setting" size="mini"></el-button>
       </el-tooltip>
    </template>
  • 实现数据分页效果 pagination

  • 实现用户数据的修改

async userStateChanged(userinfo){
          console.log(userinfo)
          const {data:res} = await this.$http.put(`users/${userinfo.id}/state/${userinfo.mg_state}`)
          if(res.meta.status !==200){
            userinfo.mg_state = !userinfo.mg_state
            return this.$message.error('更新用户状态失败!')
          }
          this.$message.success('更新用户状态成功!')
        }
  • 实现搜索的功能
v-model="queryInfo.query" 实现双向绑定
  • 实现用户添加功能
 <el-dialog
     title="提示"
     :visible.sync="addDialogVisible"
     width="50%">
       <span>这是一段信息</span>
       <span slot="footer" class="dialog-footer">
        <el-button @click="addDialogVisible = false">取 消</el-button>
        <el-button type="primary" @click="addDialogVisible = false">确 定</el-button>
       </span>
    </el-dialog>
  • 添加用户对话框中渲染一个添加用户的表单
 <el-dialog
     title="提示"
     :visible.sync="addDialogVisible"
     width="50%">
       <el-form :model="addForm" :rules="addFormRules" ref="addFormRef" label-width="70px">
         <el-form-item label="用户名" prop="username">
         <el-input v-model="addForm.username"></el-input>
        </el-form-item>
        <el-form-item label="密码" prop="password">
         <el-input v-model="addForm.password"></el-input>
        </el-form-item>
        <el-form-item label="邮箱" prop="email">
         <el-input v-model="addForm.email"></el-input>
        </el-form-item>
        <el-form-item label="手机" prop="mobile">
         <el-input v-model="addForm.mobile"></el-input>
        </el-form-item>
       </el-form>
       <span slot="footer" class="dialog-footer">
        <el-button @click="addDialogVisible = false">取 消</el-button>
        <el-button type="primary" @click="addDialogVisible = false">确 定</el-button>
       </span>
    </el-dialog>
 addForm:{
          username:'',
          password:'',
          email:'',
          mobile:''
        },
        addFormRules:{
          username:[
            {required: true,message:'请输入用户名', trigger:'blur'},{min:3,max:10,message:'用户名的长度在3-10个字符之间',trigger:'blur'}
          ],
           password:[
            {required: true,message:'请输入密码', trigger:'blur'},{min:6,max:15,message:'用户名的长度在6-15个字符之间',trigger:'blur'}
          ],
           email:[
            {required: true,message:'请输入邮箱', trigger:'blur'}
          ],
           mobile:[
            {required: true,message:'请输入手机', trigger:'blur'}
          ]
        }
  • 实现自定义规则 正则表达式验证手机和邮箱
  • 实现添加用户表单的重置功能
  • 添加用户的预验证功能
  • 发起请求添加一个新用户
  • 添加用户修改的操作
  • 绘制修改用户表单
  • 实现修改表单的关闭之后的重置操作
  • 提交修改前表单预验证操作
  • 修改用户信息的操作
  • 实现用户删除操作
  • 完成删除用户的操作

权限管理

  • 开发权限列表对应规格
  • 权限列表的基本页面布局
  • 请求权限列表数据,将获取到的数据渲染成 table 表格
<div>
   <el-breadcrumb separator="/">
     <el-breadcrumb-item :to="{ path: '/home' }">首页</el-breadcrumb-item>
     <el-breadcrumb-item>权限管理</el-breadcrumb-item>
     <el-breadcrumb-item>权限列表</el-breadcrumb-item>
   </el-breadcrumb>
   <el-card>
    <el-table :data="rightsList" border stripe>
       <el-table-column type="index"></el-table-column>
       <el-table-column label="权限名称" prop='authName'></el-table-column>
       <el-table-column label="路径" prop='path'></el-table-column>
       <el-table-column label="权限等级" prop='level'>
         <template slot-scope = "scope">
          <el-tag v-if="scope.row.level === '0'">一级</el-tag>
          <el-tag type="success" v-else-if="scope.row.level === '1'">二级</el-tag>
          <el-tag type="warning" v-else>三级</el-tag>
         </template>
       </el-table-column>
    </el-table>
   </el-card>
</div>
  • 角色列表路由的切换
  • 角色列表的基础布局
  • 渲染角色列表中的 table 数据
  • 美化权限 UI 结构
  • 分配权限的功能
  • 优化树形控件,实现复选框功能
<el-tree :data="rightslist" :props="treeProps" show-checkbox node-key="id" default-expand-all
  :default-checked-keys="defKeys"></el-tree>
  • 已有权限的默认勾选功能
  • 完成具体分配权限的功能
  • 用户分配角色的功能

商品管理

商品分类用于在购物时,快速找到所需要购买的商品,可以通过电商平台主页直观的看到 商品分类

  • 商品分类路由的加载 在components文件夹下面创建goods文件夹,创建Cate.vue 然后在路由router中引入:import Cate from '../components/goods/Cate.vue', 然后在子路由规则中添加: { path: '/categories', component: Cate }
  • 绘制商品分类页面的基本结构 头部面包屑导航和添加分类按钮
<div>
  <!--面包屑导航区域-->
     <el-breadcrumb separator="/">
     <el-breadcrumb-item :to="{ path: '/home' }">首页</el-breadcrumb-item>
     <el-breadcrumb-item>商品管理</el-breadcrumb-item>
     <el-breadcrumb-item>商品分类</el-breadcrumb-item>
    </el-breadcrumb>

  <!--卡片视图区域-->
    <el-card>
      <el-row>
         <el-col>
           <el-button type="primary">添加分类</el-button>
         </el-col>
      </el-row>

      <!--表格-->

      <!--分页区域-->
    </el-card>
  </div>
  • 获取商品分类的数据列表
<script>
  export default {
    data(){
      return {
        // 查询条件
        queryInfo:{
          type:3,
          pagenum:1,
          pagesize:5
        },
        // 商品分类的数据列表,默认为空
        catelist:[],
        // 总数据条数
        total:0
      }
    },
    created(){
      this.getCateList()
    },
    methods:{
     async getCateList(){
     const {data:res} = await this.$http.get('categories',{params:this.queryInfo})
      
     if(res.meta.status !==200){
       return this.$message.error('获取商品分类失败!')
       }
       console.log(res.data)
       // 把数据列表赋值给 catelist
       this.catelist = res.data.result
       // 为总数据条数赋值
       this.total = res.data.total
      }
    }
  }
</script>
  • 将商品分类的数据渲染为树形表格 先按照官网文档安装 vue-table-with-tree-grid 插件, 在入口文件main.js中导入
import TreeTable from 'vue-table-with-tree-grid' // 导入组件
Vue.component('tree-table', TreeTable) // 注册为全局可用的组件

在Cate.vue中使用

  <tree-table :data="catelist" :columns="columns" :selection-type="false" :expand-type="false" :show-index="true" index-text="#" border="true" :show-row-hover="false"></tree-table>

 columns:[{
            label: '分类名称',
            prop: 'cat_name',
        }]
  • 用自定义模板列的形式将后三列的模板渲染出来

用作用域插槽

<tree-table :data="catelist" :columns="columns" :selection-type="false" :expand-type="false" :show-index="true" index-text="#" border="true" :show-row-hover="false">
      <template slot="isok" slot-scope="scope">
        <i class="el-icon-success" v-if="scope.row.cat_deleted === false"  style="color:lightgreen"></i>
        <i class="el-icon-error" v-else style="color:red"></i>
      </template>
    </tree-table>

   columns:[{
            label: '分类名称',
            prop: 'cat_name',
        },
        {
          label:'是否有效',
          // 表示将当前列定义为模板列
          type:'template',
          // 表示当前这一列使用模板名称
          template:'isok'
        }
        ]
  • 渲染排序和操作这两列
  <!--排序-->
 <template slot="order" slot-scope="scope">
        <el-tag size="mini" v-if="scope.row.cat_level === 0">一级</el-tag>
        <el-tag size="mini" type="success" v-else-if="scope.row.cat_level === 1">二级</el-tag>
        <el-tag size="mini" type="warning" v-else>三级</el-tag>
      </template>
      <!--操作-->
      <template  slot="opt">
        <el-button size="mini" type="primary" icon="el-icon-edit"></el-button>
        <el-button size="mini" type="danger" icon="el-icon-delete"></el-button>
      </template>
{
          label:'排序',
          // 表示将当前列定义为模板列
          type:'template',
          // 表示当前这一列使用模板名称
          template:'order'
        },
          {
          label:'操作',
          // 表示将当前列定义为模板列
          type:'template',
          // 表示当前这一列使用模板名称
          template:'opt'
        }
  • 商品分类的分页效果
 <!--分页区域-->
     <el-pagination
      @size-change="handleSizeChange"
      @current-change="handleCurrentChange"
      :current-page="queryInfo.pagenum"
      :page-sizes="[1, 5, 10, 15]"
      :page-size="queryInfo.pagesize"
      layout="total, sizes, prev, pager, next, jumper"
      :total="total">
     </el-pagination>
在methods监听事件
// 监听pagesize的变化
      handleSizeChange(newSize){
       this.queryInfo.pagesize = newSize
        this.getCateList()
      },
      // 监听pagenum的变化
      handleCurrentChange(newPage){
        this.queryInfo.pagenum = newPage
        this.getCateList()
      }
  • 添加分类的操作,点击添加分类会出现对话框
  <!--添加分类的对话框-->
     <el-dialog
     title="添加分类"
     :visible.sync="addCateDialogVisible"
     width="50%">
     <!--添加分类的表单-->
     <el-form :model="addCateForm" :rules="addCateFormRules" ref="addCateFormRef" label-width="100px">
       <el-form-item label="分类名称" prop="cat_name">
        <el-input v-model="addCateForm.cat_name"></el-input>
       </el-form-item>
     </el-form>
     <span slot="footer" class="dialog-footer">
    <el-button @click="addCateDialogVisible = false">取 消</el-button>
    <el-button type="primary" @click="addCateDialogVisible = false">确 定</el-button>
  </span>
</el-dialog>
// 控制分类对话框的显示与隐藏
        addCateDialogVisible: false,
        // 添加分类的表单数据对象
        addCateForm:{
          // 将要添加分类的名称
          cat_name:'',
          // 父级分类的ID
          cat_id: 0,
          // 分类的等级默认要添加的是1级分类
          cat_level: 0
        },
        // 添加分类表单的验证规则对象
        addCateFormRules:{
         cat_name:[
           {
             required:true,message:'请输入分类名称',trigger:'blur'
           }
         ]
        }
 // 点击按钮,显示添加分类的对话框
      showAddCateDialog(){
        this.addCateDialogVisible = true
      }
  • 获取父级分类的数据列表
 // 获取父级分类的数据列表
      async getParentCateList(){
       const {data:res} = await this.$http.get('categories',{params:{type:2}})
      
       if(res.meta.status !==200){
         return this.$message.error('获取父级分类数据失败!')
       }
       console.log(res.data)
       this.parentCateList = res.data
      }
  • 渲染父级分类的级联选择器 Cascader级联选择器 先按需引入Cascader组件
 <el-cascader
       props.checkStrictly = true
       props.expandTrigger="hover"
       :options="parentCateList"
       :props="cascaderProps" v-model="selectedKeys"
       @change="parentCateChanged" clearable>
         </el-cascader
 // 指定级联选择器的配置对象
        cascaderProps:{
          value:'cat_id',
          label:'cat_name',
          children:'children',
        },
        // 选中的父级分类的ID数组
        selectedKeys:[]
      }

引入出现Cascader是出现选项框内的信息被遮住,在global.css引入下面代码即可

.el-cascader-panel{ 
  height: 200px; 
}
  • 处理添加分类的表单数据
// 点击按钮,添加新的分类
    addCate() {
      this.$refs.addCateFormRef.validate(async valid => {
        if (!valid) return
        const { data: res } = await this.$http.post(
          'categories',
          this.addCateForm
        )

        if (res.meta.status !== 201) {
          return this.$message.error('添加分类失败!')
        }

        this.$message.success('添加分类成功!')
        this.getCateList()
        this.addCateDialogVisible = false
      })
    },
    // 监听对话框的关闭事件,重置表单数据
    addCateDialogClosed() {
      this.$refs.addCateFormRef.resetFields()
      this.selectedKeys = []
      this.addCateForm.cat_level = 0
      this.addCateForm.cat_pid = 0
    }

参数管理 商品参数用于显示商品的固定的特征信息,可以通过电商平台商品详情页面直观的看到

商品列表