系列文章目录

对于开发一个vue项目来说通常要经过以下几个步骤 :

一、前端项目初始化


安装Vue脚手架:
要在全局环境中安装Vue这个脚手架,安装步骤如下:
1.首先:从nodejs.org中下载nodejs->然后全局安装vue。

npm install vue-cli -g

2.利用vue-cli初始化项目:在cmd中输入vue init初始化创建一个基于vue的项目。`

vue init webpack xxx

3.安装依赖的资源
将文件拖入vscode,npm install 下载所依赖的资源
4.运行项目的命令

npm run serve

二、login组件

对于一个组件的路由,可以这样设置:

import Vue from 'vue'
import Router from 'vue-router'
import Login from '../components/Login.vue'

Vue.use(Router)

export default new Router({
  routes: [
    { path: '/login', component: Login }
  ]
})

在App.vue中放置一个路由占位符

<template>
  <div id="app">
  <router-view></router-view>
  </div>
</template>   //router-view中去展示

通过路由匹配的组件都会被渲染到rouer-view中。
当用户访问到首页的时候,我们希望用户可以看到login的界面,所以我们要让其重定位到该页面当中,即在router.js这个文件当中规定路由规则:

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

设置全局样式:在assets文件夹中新建global.css文件,在其中设置全局样式:

html,
body,
#app{
    height: 100%;
    margin:0;
    padding: 0;
}

设置好后,引入此样式到入口文件main.js当中去:

import './assets/css/global.css'

对于login组件 我们可以将其划分为三个区域:
1.template (模板区)
2.script(行为区)
3.style(样式区)

// 模板区域
<template>
    <div>
        登录组件
    </div>
</template>

<script>
export default {}
</script>

<style lang="less" scoped>
</style>

2.1template区域

在template设置组件模板,到element-ui官网引入相应所需要的组件,并在plugin文件夹下的element.js中按需引入

<el-form ref="form" :model="form" label-width="80px">
  <el-form-item label="活动名称">
    <el-input v-model="form.name"></el-input>
  </el-form-item>
<template>
    <div class="login_container">
        <div class="login_box">
            <div class="avatar_box">
                <img src="../assets/logo.png" alt="">
            </div>
        </div>
    </div>
</template>

2.2script区域

在script设置行为
①为el-form设置属性,进行双向数据绑定。首先在template中的模板区为form设置:model="loginForm"在从属的用户名、密码中将其属性设置为loginForm.username或者是loginForm.password

:model="loginForm"
v-model="loginForm.username"
v-model="loginForm.password"

然后进入script区域中,为其设置data:

  data () {
    return {
      loginForm: {
        username: '',
        password: ''
      }
    }
  }

最后因为密码应该是隐藏的所以可以为其添加一个type属性为password。
②实现表单的数据验证:
绑定一个rules规则指定校验对象,在data数据中定义这个rules(他当中的每一个属性都是一组规则),为不同的表单item项通过不同prop指定不同的验证规则。
比如说对于用户名这个框:

        username: [
          { required: true, message: '请输入登录名称', trigger: 'blur' },
          { min: 3, max: 10, message: '长度在3-10个字符', trigger: 'blur' }
        ]

trigger表示何时触发该message的显示,此时即指失焦时显示;min和max则指示长度。
然后到template中为该组件绑定此项规则。:rules=“loginFormRules” (要注意的是rules对应的函数应该写在data里)

  <el-form :rules="loginFormRules" :model="loginForm" class="login_form" >
   <el-form-item prop='username'>

③实现表单的数据重置:若想获得组件的实例对象,只需要对组件添加一个ref属性,获取到他(名称任取,最好以Ref为后缀表明)。因为要实现的功能为点击重置按钮,实现表单的重置,查看文档可以发现它自身拥有一个methods:resetLoginForm
在这里插入图片描述
所以想要使用这个方法的时候,就要获取到这个实例对象,然后调用它身上的该方法。

    ref="loginFormRef"
    resetLoginForm () {
    //   console.log(this)
      this.$refs.loginFormRef.resetFields()
    }

④实现表单的数据预验证:依旧是自带一个方法validate,所以和上述过程一样,只要通过实例对象调用该方法即可,通过阅读文档可知,这个方法参数为一个回调函数,回调函数的第一个参数为布尔类型,表明其验证结果是否正确。

 login () {
      this.$refs.loginFormRef.validate((valid, object) => {
        if (!valid) return
        console.log(valid, object)
      })
    }
  }

⑤实现网络请求的发起:
在main.js中引入axios,并挂载到vue的原型当中。所以挂载到全局vue的module前都要加$符号,这样写后每个组件都能够通过this访问到此功能

import axios from 'axios'
axios.defaults.baseURL = 'https://www.liulongbin.top:8888/api/private/v1/'
Vue.prototype.$http = axios

回到Vue中对于设置登陆函数:

    login () {
      this.$refs.loginFormRef.validate((valid, object) => {
        if (!valid) return
        this.$http.post('login', this.loginForm)
        if (res.meta.status !== 200) return console.log('失败')
        console.log('失败')
      })
    }

⑥消息提示功能:
首先要引入Message,并将它加载到vue.prototype中:

Vue.prototype.$message = Message

接下来替换上面利用console去打印提示信息的部分,当为true时则调用该message

        if (res.meta.status !== 200) return this.$message.error('失败')
        else return this.$message.success('成功')

⑦细节处理部分
7.1将登陆成功之后的token,保存到客户端的sessionId中去:此处直接在请求成功后获取到这个sessionId这个值,并保存到session中去即可

 window.sessionStorage.setItem('token', res.data.token)

7.2最后直接跳转到home这个主页面:

  this.$router.push('home')

⑧路由导航守卫功能实现:实现只有登录成功才能看到页面。router.beforeEach即是一个导航守卫,
在这里插入图片描述

2.3style区域

在style设置页面样式:
值得注意的是,若为style标签加上scoped属性则该节点样式只在该组件内生效,否则全局生效。 所以单文件组件+scoped 防止样式冲突
为盒子和头像框设置样式:

.login_container{
    background-color: #2b4b6b ;
    height:100%;
}
.login_box{
    width: 450px;
    height: 300px;
    background-color: white;
    border-radius: 3px;
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50% ,-50%);
    .avatar_box{
        width: 130px;
        height: 130px;
        border: 1px solid #eee;
        border-radius: 50%;
        padding: 10px;
        box-shadow: 0 0 10px #ddd;
        position: absolute;
        left: 50%;
        transform: translate(-50% ,-50%);
        background-color: #fff;
        img{
            width:100%;
            height: 100%;
            border-radius: 50%;
            background-color: #eee;
        }
    }
}

在这里插入图片描述
设置登录框样式:

.login_form {
    width: 100%;
    padding: 0 20px;
    box-sizing: border-box;
    position: absolute;
    bottom: 0;
}
.btns {
    display: flex;
    justify-content: flex-end;
}

在这里插入图片描述
绘制图标:
为input标签添加属性:

 prefix-icon="xxx"

若要使用阿里图标库,则下载对应文件,然后再main.js中引入后正常使用即可:

 <el-input prefix-icon="iconfont icon-yonghuming"></el-input>

三、Home页面

3.1template区域:

布局上直接引用element-ui中组件即可:

<template>
  <el-container class="home-container" >
    <!-- 头部区域 -->
  <el-header>
    <div>
      <!-- <img src="../assets/heima.png" alt=""> -->
      <span>电商后台管理系统</span>
    </div>
    <el-button type='info' @click='logout'>退出</el-button>
  </el-header>
  <!-- 页面主体区域 -->
  <el-container>
    <!-- 侧边栏 -->
    <el-aside width="200px">
      <!-- 侧边栏菜单区域 -->
        <el-menu background-color="#545c64" text-color="#fff" active-text-color="#ffd04b">
          <!-- 这是一级菜单 -->
      <el-submenu index="1">
        <!-- 一级菜单的模板区 -->
        <template slot="title">
          <!-- 图标 -->
          <i class="el-icon-location"></i>
          <!-- 文本 -->
          <span>导航一</span>
        </template>
        <!-- 二级菜单 -->
        <el-menu-item index="1-4-1">
         <template slot="title">
          <!-- 图标 -->
          <i class="el-icon-location"></i>
          <!-- 文本 -->
          <span>导航一</span>
        </template>
        </el-menu-item>
      </el-submenu>
    </el-menu>
    </el-aside>
    <!-- 右侧内容主体 -->
    <el-main>Main</el-main>
  </el-container>
</el-container>
</template>

3.2script区域:

①后端服务器是除了login接口其他接口都需要有一定的权限才可以正常的使用,所以这里要利用axios拦截器去添加token验证:
在这里axios自带一个interceptors属性,属性内部有.request这个请求拦截器,可以使用.use为它挂载一个拦截的回调函数实现在请求到达之前先进性一个请求头的预处理,处理后才发回到服务器,这里需要在入口文件main.js文件当中进行设置:

axios.interceptors.request.use(config=>
  {
    config.headers.Authorization=window.sessionStorage.getItem('token')
    return config
  })

这就意味着为该headers加了一个Authorization属性,属性值为token,这样有权限的API就可以正常调用成功。
②调用后端数据接口:此过程应在created()这个生命周期中发生,

  created() {
    this.getMenuList()
  },

在methods中定义该函数,获取到数据后应该将它挂载到vue的property数据中去:

  data() {
    return{
      // 左侧菜单数据
     menulist=[]
    }
  },
     async getMenuList() {
    //  解构赋值
    const { data:res } =  await this.$http.get('menu')
    if( res.meta.status!==200) 
    return this.$message.error(res.meta.msg)
    this.menulist=res.data
    }

拉取到数据后回到template去渲染对应的内容,利用v-for实现此过程,但要注意的是:如果这样直接渲染后点击一个菜单全部都和他一起展开,所以我们应该将他的index值变为唯一的,即可。

          <el-submenu :index="item.id+''" v-for="item in menuList" :key="item.id" >
            <!-- 一级菜单的模板区域 -->
            <template slot="title">
              <i :class="iconObj[item.id]"></i>
              <span>{{ item.authName}}</span>
            </template>
         </el-submenu>

在这里插入图片描述

继续利用数据渲染二级菜单。

         <el-menu-item :index="'/' + subItem.path" v-for="subItem in item.children" :key="subItem.id" @click="saveNavState('/' + subItem.path)">
              <!-- 导航开启路由模式:
                将index值作为导航路由 -->
              <!-- 二级菜单的模板区域 -->
              <template slot="title">
                <i class="el-icon-menu"></i>
                <span>{{ subItem.authName}}</span>
              </template>
            </el-menu-item>

在这里插入图片描述
修改每个图标在循环中:在数据中添加一个icon项:

      iconObj: {
        '125': 'iconfont icon-user',
        '103': 'iconfont icon-tijikongjian',
        '101': 'iconfont icon-shangpin',
        '102': 'iconfont icon-danju',
        '145': 'iconfont icon-baobiao'
      },

模板更改为:

<i :class="iconObj[item.id]"></i>

每次只打开一个菜单的话可以直接利用组件内部属性:

 <el-menu background-color="#545c64" text-color="#fff" active-text-color="#409Eff" unique-opened>

为按钮条绑定一个单击事件,点击按钮切换菜单的折叠与展开:

  <el-menu background-color="#545c64" text-color="#fff" active-text-color="#409Eff" unique-opened :collapse="isCollapse" :collapse-transition='false'>

根据动态数据决定此过程的变化
让他的宽度也随之改变:

    <el-aside :width="isCollapse ?'64px':'200px'">

②在组件内部实现组件的跳转,首先要在组件内部定义相应的规则,比如要在home组件中加载一个welcome组件。那就要将他加载到路由规则中作为home的children路由,这样就能实现相应的加载:

    { path: '/home', component: Home,
     redirect:'/welcome',
     children:[
      { path: '/welcome',component:Welcome }
    ] }

③直接为侧边菜单栏开启路由模式(加一个属性router,它的跳转路径即为我们接下来传入的这个activePath), 那么这个路径我们该如何得到呢?有两种方法一是直接 :

:default-active='$route.path

或者只需要在点击这个菜单栏的时候,函数将这个path存到sessionStorage中去,

   saveNavState(activePath) {
     window.sessionStorage.setItem('activePath',activePath)
     this.activePath=activePath
   }

然后在created这个生命周期内部将值从sessionStorage中取出来,赋值给prop中的activePath即可

created() {
 this.activePath=window.sessionStorage.getItem('activePath')
 }
  

此外,为了保证高亮效果的实现,我们还需要在savePath这个函数内部:

 this.activePath=activePath

3.3style区域:

为其设置相应的样式:依旧利用scoped控制样式的作用域范围

<style lang="less" scoped>
.el-container {
  height: 100%;
}
.el-header {
  background-color: #373f41;
  display: flex;
  justify-content: space-between;
  padding-left: 0;
  align-items: center;
  color: #fff;
  font-size: 20px;
  > div {
    display: flex;
    align-items: center;
    img {
      height: 40px;
    }
    span {
      margin-left: 15px;
    }
  }
}
.el-aside {
  background-color: #333744;

  .el-menu {
    border: none;
  }
}
.el-main {
  background-color: #eaedf1;
}
.iconfont{
  margin-right: 10px;
}
.toggle-button {
  background-color: #4A5064;
  font-size: 10px;
  line-height: 24px;
  color: #fff;
  text-align: center;
  letter-spacing: 0.2em;
  // 鼠标放上去变成小手
  cursor: pointer;
}
</style>

3.4退出Button:退出功能实现

1.实现退出功能:基于token的退出功能实现方式比较简单,只需要销毁本地的token这样以后再发送请求的时候就不会再携带token,必须重新登录生成一个新的token之后才可以访问页面:在这里插入图片描述
首先template表明html模板

<template>
  <div class="test">
    <el-button type='info' @click='logout'>退出</el-button>
  </div>
</template>

在script内声明挂载的方法,即清除token,跳转到login页面

export default {
  methods:{
    logout() {
      window.sessionStorage.clear()
      this.$router.push('/login')
    }
  }
}

四、用户列表页面

4.1template区域

依旧是利用element-ui模板

4.2script区域

1.添加用户
①拉取后台数据接口,获取用户列表,即在created这个生命周期中去进行数据的请求,然后将数据存储再data中去,在template中去渲染这个界面,向后台请求数据的函数如下:

    async getUserList () {
      const { data: res } = await this.$http.get('users', {
        params: this.queryInfo
      })//将data重命名为res
      if (res.meta.status !== 200) {
        return this.$message.error('获取用户列表失败!')
      }
      this.userlist = res.data.users //更新数据
      this.totle = res.data.totle
    },

①改变组件状态的按钮,这里需要用到的就是作用域插槽这一概念了,为什么要用作用域插槽呢?这是因为在element-ui中设定如果想在table布局当中自定义一个列的话,就必须使用template并且为他绑定一个slot,在里面在进行相应的组件添加,并根据slot的属性值,进行相应的渲染。根据element-ui文档,在vue中可以使用v-slot表示绑定一个插槽,v-slot="scope"则表示插槽的属性绑定为scope,那么通过scope.row.xxx就可以获取到当时绑定在table上的所有数据啦, 那么因为此处是一个状态切换,根据boolean值切换,所以scope.row.mg_state绑定这个switch的开关管控即可表示,并且要把改变之后的状态保存到数据库当中。

<template v-slot="scope">          
<el-switch v-model="scope.row.mg_state" 
@change="userStateChanged(scope.row)"></el-switch>
</template>

将change事件绑定一个函数,userStateChanged,利用它向后台接口发起一个请求,改变数据库中的用户的状态(发生修改大多都是put请求)并且更新失败的话也要注意前台也需要没有变化,所以需要取反。

    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('更新用户状态成功!')
    },

②实现搜索功能:首先要对这个搜索框实现一个双向绑定,即对这个搜索框input进行数据绑定,然后对于这个搜索按钮绑定一个点击事件,只要点击它,就触发相应的事件——查询用户的请求依旧是getUserList。然后进行进一步优化的话即为这个input添加一个一键清空的按钮,实现所有数据的重置,这也是自带的一个功能直接为输入框添加一个cleartable属性。并且我还想要实现一点击清空数据,所有数据就回来了,查看文档可以看到输入框有一个@clear事件,对此事件绑定getUserList即可实现此功能。
③各种对话框功能的实现,这个组件在element-ui中也是存在的,我们只要直接引入,然后在模板区域内,直接对于点击事件将此值的状态进行改变即可。

  <el-dialog title="添加用户" :visible.sync="addDialogVisible" width="50%" @close="addDialogClosed">
  <el-button type="primary" @click="addDialogVisible = true">添加用户</el-button>

添加用户对话框的进一步实现:直接在上述的弹窗对话框中将包含的内容,更改成为一个表单,在这个表单里面实现数据的提交。对于这个表单验证方法如上:为这个表单直接绑定一个rules,然后在data函数中定义相应的验证规则:

      addUserFormRules: {
        username: [
          { required: true, message: '请输入用户名', trigger: 'blur' },
          {
            min: 2,
            max: 10,
            message: '用户名的长度在2~10个字',
            trigger: 'blur'
          }
        ],
        password: [
          { required: true, message: '请输入用户密码', trigger: 'blur' },
          {
            min: 6,
            max: 18,
            message: '用户密码的长度在6~18个字',
            trigger: 'blur'
          }
        ],
        email: [
          { required: true, message: '请输入邮箱', trigger: 'blur' },
          { validator: checkEmail, trigger: 'blur' }
        ],
        mobile: [
          { required: true, message: '请输入手机号码', trigger: 'blur' },
          { validator: checkMobile, trigger: 'blur' }
        ]
      },

并且对于数据绑定,利用model将其绑定。

      <el-form
        :model="addUserForm"
        ref="addUserFormRef"
        :rules="addUserFormRules"
        label-width="100px"
      >
        <el-form-item label="用户名" prop="username">
          <el-input v-model="addUserForm.username"></el-input>
        </el-form-item>
        <el-form-item label="密码" prop="password">
          <el-input v-model="addUserForm.password"></el-input>
        </el-form-item>
        <el-form-item label="邮箱" prop="email">
          <el-input v-model="addUserForm.email"></el-input>
        </el-form-item>
        <el-form-item label="手机" prop="mobile">
          <el-input v-model="addUserForm.mobile"></el-input>
        </el-form-item>
      </el-form>

④自定义校验规则:在官方提供的校验规则当中,我们并不能进行具体的一些校验,我们只能去校验其是否为必选项,以及长度之类的,那这时候我们就需要进行自定义校验了,但相应的校验格式在element-ui中也已经为我们提供了,我们只需要依照模板,去定义函数,就可以实现相应的校验,文档中指出,只要通过vaildator指定校验规则的函数名,在data中指明函数是什么,然后通过trigger去指定校验时机,即可完成相应的校验:邮箱:在data中声明一个函数 checkEmail:

    let checkEmail = (rule, value, callback) => {
      const regEmail = /^\w+@\w+(\.\w+)+$/
      //通过test就能查看是否匹配,匹配则为true
      if (regEmail.test(value)) {
        // 合法邮箱
        return callback()
      }
      callback(new Error('请输入合法邮箱'))
    }

手机:在data中声明一个函数checkPhone,过程同理:

    let checkMobile = (rule, value, callback) => {
      const regMobile = /^1[34578]\d{9}$/
      if (regMobile.test(value)) {
        return callback()
      }
      // 返回一个错误提示
      callback(new Error('请输入合法的手机号码'))
    }

最后将他们添加到rules中去:

  { validator: checkEmail, trigger: 'blur' }
  { validator: checkMobile, trigger: 'blur' }

⑤重置表单的操作,即在表单中实现关闭表单后在打开表单上次输入的信息都已被清空:思路为监听对话框的状态,若为关闭状态则将其数据清空。实现过程为:为其绑定一个close事件,事件对应一个函数,在函数中访问这个表单的实例,在实例中调用表单自带的一个清空方法。

<el-dialog @close="addDialogClosed">
    addDialogClosed () {
      this.$refs.addUserFormRef.resetFields()
    },

⑥实现添加用户前的表单预校验:也就是在点击确定添加按钮前,校验一下表单填写的完整性,那实现思路也很简单咯,就是为确定按钮监听点击事件并绑定一个函数,在函数中实现表单的预校验,函数中也是要拿到整个表单实例,然后对此实例通过addUserFormRef去调用validate方法:

 this.$refs.addUserFormRef.validate(async valid => {
 if (!valid) return
})

校验成功之后我们就可以通过post请求将我们的数据加入到API当中(根据API去定义即可)并且不要忘记添加之后要把对话框隐藏,并重新调用getUserList方法。

const { data: res } = await this.$http.post('users', this.addUserForm)
        if (res.meta.status !== 201) {
          this.$message.error('添加用户失败!')
        }
        this.$message.success('添加用户成功!')
        // 隐藏添加用户对话框
        this.addDialogVisible = false
        this.getUserList()

2.修改用户
在这里我们想要实现的功能是,点击修改按钮,弹出用户信息, 其中用户名是只读的,我们在这里只可以修改手机和邮箱。
实现过程:监听修改按钮的监听事件,并为该事件绑定一个函数,在函数中修改按钮绑定一个函数,并在模板区添加一个dialog…实现方法和添加用户同 不再赘述。不同的是,我们在点击修改的同时,查询到旧数据,并将它渲染到表单中,那这里我们还是可以通过scope.row.id去获取到id并将它传入到修改函数当中从而发起一个后台数据请求,然后将查询到的用户信息保存到data中,并将dialog的显示态改为true。

            <el-button
              type="primary"
              icon="el-icon-edit"
              size="mini"
              circle
              @click="showEditDialog(scope.row.id)"
            ></el-button>
    async showEditDialog (id) {
      const { data: res } = await this.$http.get('users/' + id)
      if (res.meta.status !== 200) {
        return this.$message.error('查询用户信息失败!')
      }
      this.editUserForm = res.data
      this.editDialogVisible = true
    }

现在数据已经有了,然后渲染到表单中,即双向绑定一下这个获取到的数据,然后为其添加相应的校验规则:

      editUserFormRules: {
        email: [
          { required: true, message: '请输入邮箱', trigger: 'blur' },
          { validator: checkEmail, trigger: 'blur' }
        ],
        mobile: [
          { required: true, message: '请输入手机号码', trigger: 'blur' },
          { validator: checkMobile, trigger: 'blur' }
        ]
      }

然后也要进行表单的预校验+向后台发送数据,这个过程和添加用户是一样的不再赘述。
3.删除用户
实现功能:点击删除时弹出一个消息框让用户确定是否要真的删除信息。
这里也是用elementui中的组件即可实现。messageBox组件挂载到全局,然后再触发删除时直接利用this.$confirm就可以实现啦。并且根据文档我们也可以看到相应的返回形式,在该方法中会返回一个promise对象。

    async removeUserById (id) {
      const confirmResult = await this.$confirm(
        '此操作将永久删除该用户, 是否继续?',
        '提示',
        {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          type: 'warning'
        }
      ).catch(err => err)
      // 点击确定 返回值为:confirm
      // 点击取消 返回值为: cancel
      if (confirmResult !== 'confirm') {
        return this.$message.info('已取消删除')
      }
      const { data: res } = await this.$http.delete('users/' + id)
      if (res.meta.status !== 200) return this.$message.error('删除用户失败!')
      this.$message.success('删除用户成功!')
      this.getUserList()
    },

5.分配角色功能实现
点击按钮触发dialog界面展示出来,然后我们要获取到当前点击的用户的信息,并将其展示在页面当中,所以我们在data中声明一个{},在这里面保存用户的相关信息,一点击按钮,将相关数据信息用slot插槽作为参数传递到函数中去,然后将信息保存到data中去,然后利用插值表达式在页面中相应区域渲染出来即可,并且还有一个下拉框,在此方法也要找到相应的数据,并且利用elementui中的select组件,将数据渲染出来。

   async showSetRole (role) {
      this.userInfo = role
      // 展示对话框之前,获取所有角色列表
      const { data: res } = await this.$http.get('roles')
      if (res.meta.status !== 200) {
        return this.$message.error('获取角色列表失败!')
      }
      this.rolesLsit = res.data
      this.setRoleDialogVisible = true
    }

最后要完成确定选择角色的功能

    async saveRoleInfo () {
      if (!this.selectRoleId) {
        return this.$message.error('请选择要分配的角色')
      }
      const { data: res } = await this.$http.put(`users/${this.userInfo.id}/role`, { rid: this.selectRoleId })
      if (res.meta.status !== 200) {
        return this.$message.error('更新用户角色失败!')
      }
      this.$message.success('更新角色成功!')
      this.getUserList()
      this.setRoleDialogVisible = false
    },

4.3style区域

无特殊样式,无需设定。
结果如下:
在这里插入图片描述

五、权限管理页面

4.1template区域

依旧是利用element-ui模板:
Rights.vue:


    <!-- 面包屑导航区 -->
    <el-breadcrumb separator-class="el-icon-arrow-right">
      <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" label="#"></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="danger" v-else>三级</el-tag>
          </template>
        </el-table-column>
      </el-table>
    </el-card>

4.2script区域

Rights.vue:
首先要把我们新建的这个组件加载到路由中去,因为它依旧从属于home组件,所以要相应的把他加入到我们的home路由中去:

      path: '/home',
      component: Home,
      redirect: '/welcome',
      children: [
        { path: '/welcome', component: Welcome },
        { path: '/users', component: Users },
        { path: '/rights', component: Rights }

1.调用API获取数据:
在这里获取数据的时候后台将数据存储为两种格式一个是树形结构,一个是数组结构(各项彼此独立)
在Rights.vue中应用的数据是数组结构的数据,这里还是和之前的获取数据部分一样,直接在created中发起请求,在methods中定义相应的请求函数:

  created () {
    this.getRightsList()
  },
  methods: {
    async getRightsList () {
      const { data: res } = await this.$http.get('rights/list')
      if (res.meta.status !== 200) {
        return this.$message.error('获取权限列表失败!')
      }
      this.rightsList = res.data
    }
  }

2.通过数据渲染列表
依旧是table组件,所以直接将数据绑定到这上面即可,对于每一列,prop制定相应的渲染项对应的数据名称即可,然后我们想要在这一列中添加各个等级的权限,在这里依旧是利用作用于插槽的形式,将el-tag组件放于此,但是要注意的是我们希望的是每个用户都能够对应的展示出相应的所处权限等级,所以这里我们要利用v-if 系列语句,对其进行判断:

<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="danger" v-else>三级</el-tag>

用户权限管理业务分析
在这里插入图片描述
在这里的逻辑增加了一个中间角色,使得用户只要拥有相应的角色,那么就拥有这个角色所拥有的所有权限。
Roles.vue
刚才已经说过了这里需要一个roles组件作为中间的部分。所以我们这里新定义一个vue文件,进行相应的角色分配。
在这里插入图片描述
依旧第一步加到路由当中去,依旧是子路由规则:

 { path: '/roles', component: Roles }
 

1.调用API获取数据:
在这里和上述过程依旧一样,直接贴代码了:

  created () {
    this.getRolesList()
  },
  methods: {
    async getRolesList () {
      const { data: res } = await this.$http.get('roles')
      if (res.meta.status !== 200) {
        return this.$message.error('获取角色列表失败!')
      }
      this.rolesList = res.data
    }

2.通过数据渲染列表
也是和上述过程一样滴(索引列)

  <el-table :data="rolesList" border stripe>
 <el-table-column type="index" label="#"></el-table-column>
        <el-table-column label="角色名称" prop="roleName"></el-table-column>
        <el-table-column label="角色描述" prop="roleDesc"></el-table-column>
        <el-table-column label="操作" width="300px">
          </el-table>

在这里插入图片描述
接下来要有相应的展开列展示区域,就是直接在索引列前再添加一列
在这里插入图片描述

<el-table-column type="expand">

这样就是一个展开列了。
3.展开列细节
这里因为我们的API数据中是有三层权限的,所以我们还是通过自带的作用域插槽属性去获取我们的数据,这个数据结构比较特殊,每一层权限都还存在一层children每一层children都表示一层权限。都是数组结构,所以用for循环遍历即可完成此过程:
在这里插入图片描述
这里我们要放一个栅格系统,完成三层列表:
一级权限我们是占五列,二级三级权限占十九列,直接指定即可,然

            <el-row
              :class="['bdbottom', i1 === 0 ? 'bdtop' : '', 'vcenter']"
              v-for="(item1, i1) in scope.row.children"
              :key="item1.id"
            >
              <!-- 一级权限 -->
              <el-col :span="5">
                <el-tag closable @close="removeRightById(scope.row, item1.id)">{{ item1.authName}}</el-tag>
                <i class="el-icon-caret-right"></i>
              </el-col>
              <!-- 二级和三级 -->
              <el-col :span="19">
                <!-- 通过for循环 渲染二级权限 -->
                <el-row
                  :class="[i2 === 0 ? '' : 'bdtop', 'vcenter']"
                  v-for="(item2, i2) in item1.children"
                  :key="item2.id"
                >
                  <el-col :span="6 ">
                    <el-tag
                      type="success"
                      closable
                      @close="removeRightById(scope.row, item2.id)"
                    >{{ item2.authName }}</el-tag>
                    <i class="el-icon-caret-right"></i>
                  </el-col>
                  <el-col :span="18">
                    <el-tag
                      type="warning"
                      v-for="(item3) in item2.children"
                      :key="item3.id"
                      closable
                      @close="removeRightById(scope.row, item3.id)"
                    >{{ item3.authName}}</el-tag>
                  </el-col>
                </el-row>
              </el-col>
            </el-row>

4.删除功能的实现
对于组件展示,我们依旧利用自带的@close事件即可,对于此事件去绑定一个函数,实现删除功能和刚才所表示的还是同理,我们通过插槽将相应的id传入函数中。

 @close="removeRightById(scope.row, item2.id)"

接下来的操作也和上面的过程是一样的了…

  async removeRightById (role, rightId) {
      // 弹框提示 删除
      const confirmResult = await this.$confirm(
        '此操作将永久删除该权限, 是否继续?',
        '提示',
        {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          type: 'warning'
        }
      ).catch(err => err)
      // 点击确定 返回值为:confirm
      // 点击取消 返回值为: cancel
      if (confirmResult !== 'confirm') {
        return this.$message.info('已取消权限删除')
      }
      const { data: res } = await this.$http.delete(
        `roles/${role.id}/rights/${rightId}`
      )
      if (res.meta.status !== 200) {
        return this.$message.error('删除权限失败!')
      }
      role.children = res.data
      //   不建议使用
      // this.getRolesList()
    }

那这里删除之后如果我们重新拉取数据进行渲染的话会发生下拉列表关闭的情况,所以我们只需要把这个数据重新赋值就可以了 无需在再次发起请求。
5.分配权限功能实现
点击分配权限按钮绑定一个click事件对应一个函数,函数去展示dialog,还是和上述的dialog展示过程一样的(插槽传id,函数发请求,更改data)…不再赘述,但是不一样的是,这里应用了一个树形组件,看element-ui即可。
点击分配权限,将默认已存在的权限加载到组件上,这也是element-UI的自带属性,:default-checked-keys 绑定一个数组,数组中的值即为已选的那些值,那么这个数组该如何创建呢?答案也很简单,就是在点击的时候获取所有的三级权限存入到数组中即可,所以问题的关键就是定义这个获取所有数据的数组,因为要获取所有的以保存项,所以这里利用递归的方式进行遍历,代码如下:

    getLeafkeys (node, arr) {
      // 没有children属性,则是三级节点
      if (!node.children) {
        return arr.push(node.id)
      }
      node.children.forEach(item => this.getLeafkeys(item, arr))
    }

那这里的node则应该是传入当前的角色,arr则是定义的保存数据的空数组。那如何知道我们现在点击的是哪个角色呢?这个也是利用插槽中scope.row的方法进行的,只需要将他当作参数传入到此函数中去即可。
前端写好,就要进行API的调用了,点击像后台发送请求,调用相应的接口,这里就是获取所有的已选中状态的数组,然后做为参数发起请求:

    async allotRights (roleId) {
      // 获得当前选中和半选中的Id
      const keys = [
        ...this.$refs.treeRef.getCheckedKeys(),
        ...this.$refs.treeRef.getHalfCheckedKeys()
      ]
      // join() 方法用于把数组中的所有元素放入一个字符串
      const idStr = keys.join(',')
      const { data: res } = await this.$http.post(`roles/${this.roleId}/rights`, { rids: idStr })
      if (res.meta.status !== 200) { return this.$message.error('分配权限失败!') }
      this.$message.success('分配权限成功!')
      this.getRolesList()
      this.setRightDialogVisible = false
    }

这里的roleId再点击的时候直接保存进来就可以了。

六、商品分类

4.1template区域

依旧是利用element-ui模板:
cate.vue:

<template>
  <div>
    <!-- 面包屑导航区 -->
    <el-breadcrumb separator-class="el-icon-arrow-right">
      <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" @click="showAddCateDialog">添加分类</el-button>
        </el-col>
      </el-row>
      <!-- 表格 -->
      <tree-table
        class="treeTable"
        :data="cateList"
        :columns="columns"
        :selection-type="false"
        :expand-type="false"
        index-text="#"
        :show-row-hover="false"
        show-index
        border
      >
        <!-- 是否有效 -->
        <template slot="isOk" slot-scope="scope">
          <i
            class="el-icon-success"
            style="color: lightgreen"
            v-if="scope.row.cat_deleted === false"
          ></i>
        </template>
        <!-- 排序 -->
        <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" slot-scope="scope">
          <el-button type="primary" icon="el-icon-edit" size="mini" @click="showEditCateDialog(scope.row.cat_id)">编辑</el-button>
          <el-button type="danger" icon="el-icon-delete" size="mini" @click="removeCate(scope.row.cat_id)">删除</el-button>
        </template>
      </tree-table>
      <!-- 分页 -->
      <el-pagination
        @size-change="handleSizeChange"
        @current-change="handleCurrentChange"
        :current-page="queryInfo.pagenum"
        :page-sizes="[3, 5, 10, 15]"
        :page-size="queryInfo.pagesize"
        layout="total, sizes, prev, pager, next, jumper"
        :total="total"
      ></el-pagination>
    </el-card>
    <!-- 添加分类的对话框 -->
    <el-dialog title="添加分类" :visible.sync="addCateDialogVisible" width="50%" @close="addCateDialogClosed">
      <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-item label="父级分类:">
          <!-- options:数据源 -->
          <!-- props:指定配置对象 -->
          <el-cascader
            v-model="selectedKeys"
            :options="parentCateList"
            :props="cascaderProps"
            @change="parentCateChanged"
            clearable
            filterable
            style="width: 100%"
          ></el-cascader>
        </el-form-item>
      </el-form>
      <span slot="footer" class="dialog-footer">
        <el-button @click="addCateDialogVisible = false">取 消</el-button>
        <el-button type="primary" @click="addCate">确 定</el-button>
      </span>
    </el-dialog>
    <!-- 编辑分类的对话框 -->
    <el-dialog title="编辑分类" :visible.sync="editCateDialogVisible" width="50%">
      <el-form
        :model="editCateForm"
        :rules="editCateFormRules"
        ref="editCateFormRef"
        label-width="100px"
      >
        <el-form-item label="分类名称:" prop="cat_name">
          <el-input v-model="editCateForm.cat_name"></el-input>
        </el-form-item>
      </el-form>
      <span slot="footer" class="dialog-footer">
        <el-button @click="editCateDialogVisible = false">取 消</el-button>
        <el-button type="primary" @click="eidtCate">确 定</el-button>
      </span>
    </el-dialog>
  </div>
</template>

4.2script区域

cate.vue:
首先要把我们新建的这个组件加载到路由中去,因为它依旧从属于home组件,所以要相应的把他加入到我们的home路由中去:

      path: '/home',
      component: Home,
      redirect: '/welcome',
      children: [
{ path: '/categories', component: Cate }
]

1.调用API获取数据:
在这里获取数据的时候后台将数据存储为有一个type参数,参数为1;2;3,可以分别获取相应的1,2,3级别分类的商品,不指定默认获取所有;还有一个pagenum表示当前的页码值,不传递时获取所有分类;pagesize则表示每页展示多少条数据。
在cate.vue中应用的数据是数组结构的数据,这里还是和之前的获取数据部分一样,直接在created中发起请求,但这里我们还要传入一些参数,来指定查询的数据在methods中定义相应的请求函数:

 data () {
    return {
      // 商品分类数据
      cateList: [],
      // 查询条件
      queryInfo: {
        type: 3,
        pagenum: 1,
        pagesize: 5
      },
    }

在method中定义相关的方法,在created中调用:

    async getCateList () {
      const { data: res } = await this.$http.get('categories', {
        params: this.queryInfo
      })
      if (res.meta.status !== 200) {
        return this.$message.error('获取商品分类失败!')
      }
      // 给数据列表赋值
      this.cateList = res.data.result
      // 总数据条数
      this.total = res.data.total
    }

树形组件库的引入:npm安装

 npm i vue-table-with-tree-grid --save

安装后全局导入:

import TreeTable from 'vue-table-with-tree-grid'
Vue.component('tree-table', TreeTable)

依旧文档配置各属性即可。
2.通过数据渲染列表
依旧是table组件,所以直接将数据绑定到这上面即可,对于每一列,prop制定相应的渲染项对应的数据名称即可,然后我们想要在这一列中添加各个等级的权限,在这里依旧是利用作用于插槽的形式,将el-tag组件放于此,但是要注意的是我们希望的是每个用户都能够对应的展示出相应的所处权限等级,所以这里我们要利用v-if 系列语句,对其进行判断:

<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="danger" v-else>三级</el-tag>

用户权限管理业务分析
在这里插入图片描述
在这里的逻辑增加了一个中间角色,使得用户只要拥有相应的角色,那么就拥有这个角色所拥有的所有权限。
Roles.vue
刚才已经说过了这里需要一个roles组件作为中间的部分。所以我们这里新定义一个vue文件,进行相应的角色分配。
在这里插入图片描述
依旧第一步加到路由当中去,依旧是子路由规则:

 { path: '/roles', component: Roles }
 

1.调用API获取数据:
在这里和上述过程依旧一样,直接贴代码了:

  created () {
    this.getRolesList()
  },
  methods: {
    async getRolesList () {
      const { data: res } = await this.$http.get('roles')
      if (res.meta.status !== 200) {
        return this.$message.error('获取角色列表失败!')
      }
      this.rolesList = res.data
    }

2.通过数据渲染列表
也是和上述过程一样滴(索引列)

  <el-table :data="rolesList" border stripe>
 <el-table-column type="index" label="#"></el-table-column>
        <el-table-column label="角色名称" prop="roleName"></el-table-column>
        <el-table-column label="角色描述" prop="roleDesc"></el-table-column>
        <el-table-column label="操作" width="300px">
          </el-table>

在这里插入图片描述
接下来要有相应的展开列展示区域,就是直接在索引列前再添加一列
在这里插入图片描述

<el-table-column type="expand">

这样就是一个展开列了。
3.展开列细节
这里因为我们的API数据中是有三层权限的,所以我们还是通过自带的作用域插槽属性去获取我们的数据,这个数据结构比较特殊,每一层权限都还存在一层children每一层children都表示一层权限。都是数组结构,所以用for循环遍历即可完成此过程:
在这里插入图片描述
这里我们要放一个栅格系统,完成三层列表:
一级权限我们是占五列,二级三级权限占十九列,直接指定即可,然

            <el-row
              :class="['bdbottom', i1 === 0 ? 'bdtop' : '', 'vcenter']"
              v-for="(item1, i1) in scope.row.children"
              :key="item1.id"
            >
              <!-- 一级权限 -->
              <el-col :span="5">
                <el-tag closable @close="removeRightById(scope.row, item1.id)">{{ item1.authName}}</el-tag>
                <i class="el-icon-caret-right"></i>
              </el-col>
              <!-- 二级和三级 -->
              <el-col :span="19">
                <!-- 通过for循环 渲染二级权限 -->
                <el-row
                  :class="[i2 === 0 ? '' : 'bdtop', 'vcenter']"
                  v-for="(item2, i2) in item1.children"
                  :key="item2.id"
                >
                  <el-col :span="6 ">
                    <el-tag
                      type="success"
                      closable
                      @close="removeRightById(scope.row, item2.id)"
                    >{{ item2.authName }}</el-tag>
                    <i class="el-icon-caret-right"></i>
                  </el-col>
                  <el-col :span="18">
                    <el-tag
                      type="warning"
                      v-for="(item3) in item2.children"
                      :key="item3.id"
                      closable
                      @close="removeRightById(scope.row, item3.id)"
                    >{{ item3.authName}}</el-tag>
                  </el-col>
                </el-row>
              </el-col>
            </el-row>

4.删除功能的实现
对于组件展示,我们依旧利用自带的@close事件即可,对于此事件去绑定一个函数,实现删除功能和刚才所表示的还是同理,我们通过插槽将相应的id传入函数中。

 @close="removeRightById(scope.row, item2.id)"

接下来的操作也和上面的过程是一样的了…

  async removeRightById (role, rightId) {
      // 弹框提示 删除
      const confirmResult = await this.$confirm(
        '此操作将永久删除该权限, 是否继续?',
        '提示',
        {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          type: 'warning'
        }
      ).catch(err => err)
      // 点击确定 返回值为:confirm
      // 点击取消 返回值为: cancel
      if (confirmResult !== 'confirm') {
        return this.$message.info('已取消权限删除')
      }
      const { data: res } = await this.$http.delete(
        `roles/${role.id}/rights/${rightId}`
      )
      if (res.meta.status !== 200) {
        return this.$message.error('删除权限失败!')
      }
      role.children = res.data
      //   不建议使用
      // this.getRolesList()
    }

那这里删除之后如果我们重新拉取数据进行渲染的话会发生下拉列表关闭的情况,所以我们只需要把这个数据重新赋值就可以了 无需在再次发起请求。
5.分配权限功能实现
点击分配权限按钮绑定一个click事件对应一个函数,函数去展示dialog,还是和上述的dialog展示过程一样的(插槽传id,函数发请求,更改data)…不再赘述,但是不一样的是,这里应用了一个树形组件,看element-ui即可。
点击分配权限,将默认已存在的权限加载到组件上,这也是element-UI的自带属性,:default-checked-keys 绑定一个数组,数组中的值即为已选的那些值,那么这个数组该如何创建呢?答案也很简单,就是在点击的时候获取所有的三级权限存入到数组中即可,所以问题的关键就是定义这个获取所有数据的数组,因为要获取所有的以保存项,所以这里利用递归的方式进行遍历,代码如下:

    getLeafkeys (node, arr) {
      // 没有children属性,则是三级节点
      if (!node.children) {
        return arr.push(node.id)
      }
      node.children.forEach(item => this.getLeafkeys(item, arr))
    }

那这里的node则应该是传入当前的角色,arr则是定义的保存数据的空数组。那如何知道我们现在点击的是哪个角色呢?这个也是利用插槽中scope.row的方法进行的,只需要将他当作参数传入到此函数中去即可。
前端写好,就要进行API的调用了,点击像后台发送请求,调用相应的接口,这里就是获取所有的已选中状态的数组,然后做为参数发起请求:

    async allotRights (roleId) {
      // 获得当前选中和半选中的Id
      const keys = [
        ...this.$refs.treeRef.getCheckedKeys(),
        ...this.$refs.treeRef.getHalfCheckedKeys()
      ]
      // join() 方法用于把数组中的所有元素放入一个字符串
      const idStr = keys.join(',')
      const { data: res } = await this.$http.post(`roles/${this.roleId}/rights`, { rids: idStr })
      if (res.meta.status !== 200) { return this.$message.error('分配权限失败!') }
      this.$message.success('分配权限成功!')
      this.getRolesList()
      this.setRightDialogVisible = false
    }

这里的roleId再点击的时候直接保存进来就可以了。

2.一些报错

利用less-loader设置样式,通过npm install less-loader --save安装依赖包,但发现总是报错:
在这里插入图片描述
这个时候降低npm版本后重新安装即可。

npm install npm@6.14.10 -g   

然后重新安装

npm install less-loader --save

然后下载相关的一系列依赖

npm install

发现仍然报错,报错信息如下:
在这里插入图片描述
这是因为less版本过高:

 npm install less-loader@5.0.0 -s
 npm install less@3.9.0 -s  

成功解决!

2.一些报错

利用less-loader设置样式,通过npm install less-loader --save安装依赖包,但发现总是报错:
在这里插入图片描述
这个时候降低npm版本后重新安装即可。

npm install npm@6.14.10 -g   

然后重新安装

npm install less-loader --save

然后下载相关的一系列依赖

npm install

发现仍然报错,报错信息如下:
在这里插入图片描述
这是因为less版本过高:

 npm install less-loader@5.0.0 -s
 npm install less@3.9.0 -s  

成功解决!

Logo

前往低代码交流专区

更多推荐