Vue全家桶+ElementUI+Mock.Js实现的后台管理系统:学习笔记

独自空忆成欢 提交于 2020-04-29 03:18:24

 

-----写在前面-----

虽然自己写了个仿站,但小林心里十分有数,这个玩意到时候可能都不好意思写到简历上,果然前端越往后面学越觉得自己之前是弟弟。

现在在看6月份的简历,突然明白为啥公司们都不要我...就干两个月,技术还没多好,打扰了(;′⌒`)

把ElementUI的官网文档过了一遍,感叹作者也太🐮了...大神们造的轮子属实给劲

正好看到了一个用标题所列的技术栈搭建的一个后台管理系统,clone下来看了一下发现属实有点脑壳疼,不过问题不大,一天看懂一些,总归是能吃的透透的嗷

 Vue-Admin-Demo

-----2019.08.24(Day 1):目录梳理、路由配置详解、login.vue-----

和直接使用vue-cli以webpack模板生成的工程文件不同

作者把路由组件放在views文件夹下

api文件夹用于接收mock文件夹模拟出的数据并作为axios的请求地址(这个后面会详解)

路由配置文件:

//省略路由的引入

let routes = [
  {
    path: '/login',
    component: Login,
    name: '',
    hidden: true
  },
  {
    path: '/404',
    component: NotFound,
    name: '',
    hidden: true
  },
  //{ path: '/main', component: Main },
  {
    path: '/',
    component: Home,
    name: '',
    iconCls: 'el-icon-menu',
    leaf: true, //只有一个节点
    children: [{ path: '/Index', component: Index, name: '首页' }]
  },
  {
    path: '/',
    component: Home,
    name: '',
    iconCls: 'el-icon-menu', //图标样式class
    leaf: true, //只有一个节点
    children: [
      { path: '/Application', component: Application, name: '应用管理' }
    ]
  },
  {
    path: '/',
    component: Home,
    name: '日志管理',
    iconCls: 'el-icon-menu',
    children: [
      { path: '/SysLog', component: SysLog, name: '系统日志' },
      { path: '/AdminLog', component: AdminLog, name: '管理员操作日志' },
      { path: '/UserLog', component: UserLog, name: '用户使用日志' }
    ]
  },
  {
    path: '/',
    component: Home,
    name: '配置管理',
    iconCls: 'el-icon-menu',
    children: [
      {
        path: '/GuidePageConfig',
        component: GuidePageConfig,
        name: '引导页配置'
      },
      {
        path: '/IntegralConfig',
        component: IntegralConfig,
        name: '积分规则配置'
      },
      {
        path: '/SubscribeConfig',
        component: SubscribeConfig,
        name: '订阅栏目配置'
      }
    ]
  },
  {
    path: '/',
    component: Home,
    name: '权限管理',
    iconCls: 'el-icon-menu',
    children: [
      { path: '/page4', component: Page4, name: '权限组管理' },
      { path: '/Main', component: Main, name: '权限组权限管理' },
      { path: '/Form', component: Form, name: '权限组人员管理' }
    ]
  },
  {
    path: '/',
    component: Home,
    name: '账号管理',
    iconCls: 'el-icon-menu',
    children: [
      { path: '/user', component: user, name: '页面4' },
      { path: '/page5', component: Page5, name: '页面5' }
    ]
  },
  {
    path: '*',
    hidden: true,
    redirect: { path: '/404' }
  }
]

export default routes
Routes.js

/login:登陆界面

/404:无效界面

 

/:通过指定不同的name属性以及不同的嵌套组件来执行不同的跳转(至子页面)

这样写的好处是url不会是/page1/page1-1/page1-1-1,而是直接将最后一级路由拼上去。即/page1-1-1

 

iconCls: ElementUI为路由指定图标的方式

 

hidden&leaf属性:自定义属性,用于后面配合指定路由是否渲染

 

login.vue

<el-form
    :model="ruleForm2"
    :rules="rules2"
    ref="ruleForm2"
    label-position="left"
    label-width="0px"
    class="demo-ruleForm login-container"
  >
    <h3 class="title">系统登录</h3>
    <el-form-item prop="account">
      <el-input type="text" v-model="ruleForm2.account" auto-complete="off" placeholder="账号"></el-input>
    </el-form-item>
    <el-form-item prop="checkPass">
      <el-input type="password" v-model="ruleForm2.checkPass" auto-complete="off" placeholder="密码"></el-input>
    </el-form-item>
    <el-checkbox v-model="checked" checked class="remember">记住密码</el-checkbox>
    <el-form-item style="width:100%;">
      <el-button
        type="primary"
        style="width:100%;"
        @click.native.prevent="handleSubmit2"
        :loading="logining"
      >登录</el-button>
      <!--<el-button @click.native.prevent="handleReset2">重置</el-button>-->
    </el-form-item>
  </el-form>
login.vue

 

 

model:表单的数据对象,使用方式:

 

rules:验证规则

 

label-position、label-width:表单域标签的位置、表单域标签的宽度

详见 ElementUI-表单

 

 登录按钮:使用native修饰符,否则无法监听到原生click事件(被封装过的),再阻止默认事件提交,并调用handleSubmit2()事件:

 

 

-----2019-08-26 Home.Vue、Index.Vue------

 

 <el-dropdown>

 

 trigger:触发菜单展开的行为

divided:显示分割线

更多下拉菜单相关

 

<el-breadcrumb>面包屑导航

在这里是利用跟踪路由变化来进行渲染新的面包屑导航项,例如进入一个二级路由界面后,$route.matched会被推入新的项,此时面包屑导航会多渲染出新的一项

 

 菜单
<aside>-<el-menu>-<el-submenu>

菜单也是通过v-for渲染的,不渲染hidden为true的路由项(login.vue、404.vue等),为leaf属性为true的一级菜单路由渲染其子路由

同时通过前面为路由自定义的iconCls属性来添加菜单图标

 注意有两个图标

 

 

 

Index.Vue

没啥好分析的,就是这里的echarts到时候需要看看文档

 

 

-----2019.08.27 Mock逻辑(获取用户列表、登陆验证逻辑) -----

获取用户列表的MOCK逻辑见这篇博文:Vue & Axios & Mock & Axios-Mock-Adapter:在Vue项目中模拟接口、请求以及数据

登陆验证逻辑

api.js

export const requestLogin = params => { return axios.post(`${base}/login`, params).then(res => res.data); };

 

login.vue 中点击登录按钮后的处理逻辑

handleSubmit2(ev) {
      var _this = this
      this.$refs.ruleForm2.validate(valid => {
        if (valid) {
          this.logining = true
          var loginParams = {
            username: this.ruleForm2.account,
            password: this.ruleForm2.checkPass
          }//即传过去的参数包含填入的帐号与密码
          requestLogin(loginParams).then(data => {
            console.log(data);//{code: 200, msg: "请求成功", user: {…}}
            this.logining = false
            //NProgress.done();
            let { msg, code, user } = data
            if (code !== 200) {
              this.$message({
                message: msg,
                type: 'error'
              })
            } else {
              sessionStorage.setItem('user', JSON.stringify(user))
              this.$router.push({ path: '/Index' })
            }
          })
        } else {
          console.log('error submit!!')
          return false
        }
      })
    }

 

Mock.js中的登陆相关逻辑

mock.onPost('/login').reply(config => {
      let { username, password } = JSON.parse(config.data); //解构赋值
      return new Promise((resolve, reject) => {
        let user = null;
        setTimeout(() => {
          let hasUser = LoginUsers.some(u => {
            //LoginUsers是写死的嗷
            //some方法检测数组中的元素是否符合指定条件,如果有一个满足就会返回true
            if (u.username === username && u.password === password) {
              user = JSON.parse(JSON.stringify(u));
              user.password = undefined;
              return true;
            }
          });
          //如果验证通过
          if (hasUser) {
            resolve([200, { code: 200, msg: '请求成功', user }]);
            //调用resolve方法,Promise变为操作成功状态(fulfilled),执行then方法里面onfulfilled里的操作
            //Promise.resolve方法会将这个对象转为 Promise 对象,然后就立即执行thenable对象的then方法。
          } else {
            resolve([200, { code: 500, msg: '账号或密码错误' }]);
          }
        }, 1000);
      });
    });

 

大概的思路是这样的:

(login.vue)点击按钮后将调用requestLogin()方法,账号密码以POST方式发送至代理的接口地址('/login')

(mock.js)mock拦截到请求,在onPost()方法中进行验证,并根据验证结果调用resolve()方法(传入的参数不同),reply()中返回的这个Promise对象状态变为成功(fulfilled)

随后会调用login.vue中、定义在requestLogin()方法后的then()方法,解构赋值后根据code值判断验证的成功还是失败

 

 

-----2019-08-28:查询、新增、编辑、删除操作、slot-scope&slot-----

⚠slot-scope是被废弃的API(现在建议使用v-slot),但由于原作者在完成这个项目时使用的是2.2.2版本的Vue,正好这个用法我之前也没学过就顺便了解一下。

官方文档地址:slot-scope  v-slot

注意这个表格的代码,借助ElementUI的帮助,可以直接在data属性注入数据,然后在<el-table-column>中通过为prop设定对应键名来填充数据。

表格里的template就是每一行都相同的【编辑】、【删除】按钮,如何在点击时直到是哪一行(的哪一项)需要进行操作?就是靠slot-scope

我们这里把scope整个打印出来看看

$idnex:0  这里点击的是第一行的数据

column:该按钮的列属性,即第?列

row:

行属性,包括行头与单元格值

store:表格内部的状态管理,这里略过

ElementUI官网的demo:Table-ElementUI

 

接下来是各个操作的详解:

删除:

关于this.$confirm:

具体参见MessageBox弹框 

 

Mock.js中负责删除逻辑的部分:

实际上就是把数据用fliter()方法走了一遍,并修改了全局的_Users变量--->然后再次调用getUser()方法--->用新的_Users渲染列表

⬆这个逻辑可以看前两天的分析

 

编辑:这个复杂一些

首先是显示编辑界面,并且将这一行数据的所有属性复制一份保存下来

Object.assign()方法(MDN)

 

注意这里的 :visible.sync="editFormVisible",建议去学一下.sync的用法,这里指稍微提一下

这个写法表达了对visible赋新值的意图,跳过了使用$emit通知组件去修改的过程(ElementUI的组件都是被封装过的哈记得)

 

提交编辑的逻辑函数:

在Mock.js中的功能函数:(逻辑就不用多说了吧)

新增的逻辑实现类似编辑,最后来看一哈 批量删除 这个功能

 

首先列表项前的单选框被勾选会触发selesChange()方法(见下行使用方式)
<el-table :data="users" highlight-current-row v-loading="listLoading" @selection-change="selsChange" style="width: 100%;">

 

注意!这里的sels是所有当前被选中的项!

创建一个ids数组,并将被选中的项的id填入进去,并化为字符串形式,发送给mock.js中的batchRemoveUser()进行处理

 

 

-----2019-08-31 积分配置   订阅栏目配置-----

喵喵喵??我发现这个作者后面几个子页面就是在偷懒了... 所有子界面有意义的就只有用户列表和积分配置页面,

其他不是复制的用户列表就是无意义字符...但这个项目我还是学到了很有分量的东西的。

把积分配置界面魔改了一下

 

 

 现在保存配置后会把积分的配置规则发到服务器端(模拟的)~

改动如下:

1.

 

 

 这样会把新的积分配置作为一个数组直接发送出去~

 

在api.js中新增一个接口:

export const IntegralConfig = params => { return axios.post(`${base}/user/IntegralConfig`, { params: params }) }

在mock.js中修改代理配置:

mock.onPost('/user/IntegralConfig').reply(config => {
      console.log(JSON.parse(config.data).params.nums);
      var [step1, step2, step3, step4] = JSON.parse(config.data).params.nums;
      console.log(step1, step2, step3, step4);
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve([
            200,
            {
              code: 200,
              msg: '积分规则变更成功'
            }
          ]);
        }, 500);
      });
    });

 

引导页、宣传页配置(这两个模式是一样的,就以宣传页配置为例)

 

<h1>宣传页配置
            <el-switch
                    style="display: block"
                    v-model="GuidePageConfig"
                    active-color="#13ce66"
                    inactive-color="#ff4949"
                    active-text="打开宣传页"
                    inactive-text="关闭宣传页">
            </el-switch>
            </h1>
        </el-col>
        <el-col>

        </el-col>
        <el-form>
            <el-upload
                    action="https://jsonplaceholder.typicode.com/posts/"
                    list-type="picture-card"
                    :on-preview="handlePictureCardPreview"
                    :on-remove="handleRemove"
                    :file-list="fileList2">
                <i class="el-icon-plus"></i>
            </el-upload>
            <el-dialog :visible.sync="dialogVisible">
                <img width="100%" :src="dialogImageUrl" alt="">
            </el-dialog>

关于el-upload的使用参见官网:el-upload

 

 

 

 

action:上传到的服务器地址

list-type:文件列表类型

on-preview:点击文件列表中已上传的文件时的钩子

 

 

 

 在这里会使得对话框可见并展示大尺寸的图片

on-remove:文件列表移除文件时的钩子

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!