从vue到elementUI项目(五)

文章目录

  • 用户管理理页及详解权限管理
    • 用户管理理页介绍及页面实现思路讲解
    • 更完善的表单组件封装及思路讲解
    • 通用表格组件封装思路以及完成表格组件的封装
    • 用户管理页面功能实现
      • 实现表格加载状态
      • 实现分页功能
      • 实现编辑功能
      • 实现新增功能
      • 实现删除功能
    • 企业开发之权限管理思路路讲解
    • 权限管理之动态返回菜单的实现
    • 权限管理之路由守卫判断用户登录状态
    • 最后再来补充一些未完成的
      • 为这些接口加入模拟api
      • 保存taglist到cookie中
      • 完成搜索功能
  • 项目总结
  • 全文所涉及的代码下载地址

用户管理理页及详解权限管理

用户管理理页介绍及页面实现思路讲解

管理页功能

  • 新增用户
  • 搜索用户
  • 更新用户
  • 删除用户
  • 分页展示用户列表

先给出用户管理响应的mock接口数据,在mock文件夹下新建一个user.js,内容如下

import Mock from 'mockjs'

// get请求从config.url获取参数,post从config.body中获取参数
function param2Obj(url) {
    const search = url.split('?')[1]
    if (!search) {
        return {}
    }
    return JSON.parse(
        '{"' +
        decodeURIComponent(search)
        .replace(/"/g, '\\"')
        .replace(/&/g, '","')
        .replace(/=/g, '":"') +
        '"}'
    )
}

let List = []
const count = 200

for (let i = 0; i < count; i++) {
    List.push(
        Mock.mock({
            id: Mock.Random.guid(),
            name: Mock.Random.cname(),
            addr: Mock.mock('@county(true)'),
            'age|18-60': 1,
            birth: Mock.Random.date(),
            sex: Mock.Random.integer(0, 1)
        })
    )
}

export default {
    /**
     * 获取列表
     * 要带参数 name, page, limt; name可以不填, page,limit有默认值。
     * @param name, page, limit
     * @return {
    {code: number, count: number, data: *[]}}
     */
    getUserList: config => {
        const {
            name,
            page = 1,
            limit = 20
        } = param2Obj(config.url)
        console.log('name:' + name, 'page:' + page, '分页大小limit:' + limit)
        const mockList = List.filter(user => {
            if (name && user.name.indexOf(name) === -1 && user.addr.indexOf(name) === -1) return false
            return true
        })
        const pageList = mockList.filter((item, index) => index < limit * page && index >= limit * (page - 1))
        return {
            code: 20000,
            count: mockList.length,
            list: pageList
        }
    },
    /**
     * 增加用户
     * @param name, addr, age, birth, sex
     * @return {
    {code: number, data: {message: string}}}
     */
    createUser: config => {
        const {
            name,
            addr,
            age,
            birth,
            sex
        } = JSON.parse(config.body)
        console.log(JSON.parse(config.body))
        List.unshift({
            id: Mock.Random.guid(),
            name: name,
            addr: addr,
            age: age,
            birth: birth,
            sex: sex
        })
        return {
            code: 20000,
            data: {
                message: '添加成功'
            }
        }
    },
    /**
     * 删除用户
     * @param id
     * @return {*}
     */
    deleteUser: config => {
        const {
            id
        } = param2Obj(config.url)
        if (!id) {
            return {
                code: -999,
                message: '参数不正确'
            }
        } else {
            List = List.filter(u => u.id !== id)
            return {
                code: 20000,
                message: '删除成功'
            }
        }
    },
    /**
     * 批量删除
     * @param config
     * @return {
    {code: number, data: {message: string}}}
     */
    batchremove: config => {
        let {
            ids
        } = param2Obj(config.url)
        ids = ids.split(',')
        List = List.filter(u => !ids.includes(u.id))
        return {
            code: 20000,
            data: {
                message: '批量删除成功'
            }
        }
    },
    /**
     * 修改用户
     * @param id, name, addr, age, birth, sex
     * @return {
    {code: number, data: {message: string}}}
     */
    updateUser: config => {
        const {
            id,
            name,
            addr,
            age,
            birth,
            sex
        } = JSON.parse(config.body)
        const sex_num = parseInt(sex)
        List.some(u => {
            if (u.id === id) {
                u.name = name
                u.addr = addr
                u.age = age
                u.birth = birth
                u.sex = sex_num
                return true
            }
        })
        return {
            code: 20000,
            data: {
                message: '编辑成功'
            }
        }
    }
}

在mock下的index.js就要去引入这些返回

import Mock from 'mockjs'
import homeApi from './home.js'

// 设置200-2000毫秒延时请求数据
Mock.setup({
  timeout: '200-2000',
})

// 首页相关
// 拦截的是 /home/getData
Mock.mock(/\/home\/getData/, 'get', homeApi.getStatisticalData)

// 用户相关
Mock.mock(/\/user\/getUser/, 'get', userApi.getUserList)
Mock.mock(/\/user\/del/, 'get', userApi.deleteUser)
Mock.mock(/\/user\/batchremove/, 'get', userApi.batchremove)
Mock.mock(/\/user\/add/, 'post', userApi.createUser)
Mock.mock(/\/user\/edit/, 'post', userApi.updateUser)
Mock.mock(/\/home\/getData/, 'get', homeApi.getStatisticalData)

考虑到用户管理需要两个组件来完成,一个是表头部分,另一个是表单内容部分,所以新建两个组件,分别为CommonForm.vue(提交新建和搜索),内容如下






CommonTable.vue(显示用户信息),内容如下




新建一个UserManage.vue页面的样式,在scss文件夹下新建common.scss,内容如下

.manage {
    background-color: yellow;
}

在UserManage.vue中引入这两个组件,并新建一个scss来作为他的样式






目前看到占坑的效果
从vue到elementUI项目(五)_第1张图片

更完善的表单组件封装及思路讲解

分析表单组成

  • 输⼊框
  • 下拉框
  • 日期选择器
  • 单选列表
  • 多选列表
  • 。。。

考虑基本参数

  • 绑定的表单字段
  • 表单描述文本

插槽拓展组件

  • 按钮组

在CommonForm.vue中完成我们表单的初步封装,在页面那种加入了三种控件,分别为input、select和switch、date-picker,需要传入对应的formLabel才会显示出来,比如我们要显示swith与input

  data() {
    return {
      searchFrom: {
        keyword: ''
      },
      formLabel: [
        {
          model: 'keyword',
          label: '' // 组件描述的内容
        },
        {
          type: 'switch', // 用在项目中,是去掉这个的
          label: '' // 组件描述的内容
        }
      ]
    }
  }

现在CommonForm.vue的代码如下,






我们到UserManage.vue中去正式使用他






现在使用额度common.scss样式,只是为了更加符合表单的效果

.manage {
    height: 90%;
    padding-bottom: 20px;
    overflow: hidden;

    &-header {
        display: flex;
        justify-content: space-between;
        align-items: flex-start;
    }
}

目前的效果如下
从vue到elementUI项目(五)_第2张图片

通用表格组件封装思路以及完成表格组件的封装

表格基本参数分析

  • data: 传⼊的数据列表
  • prop: 传入的数据字段
  • label: 列名

表格可选参数分析

  • width:列宽
  • type:类型

表格扩展

  • 分页参数
    • total: 数据条数总计
    • page: 当前页数
  • 加载状态
    • loading:布尔值

修改CommonTable.vue控件,将需要的表格属性渲染的table-column定义出来




在UserManage.vue页面中调用时,需要传入tableData和tableLabel还有config


...

tableLabel在data数据区域中直接定义,tableData先定义出来,在methods中去通过访问接口进行请求。config为存放的一些表格参数,预先定义,也是通过接口数据返回后进行设定




...

目前的效果如下
从vue到elementUI项目(五)_第3张图片

加入表格的序号,序号是通过分页加上当前index得到的


表格中的最后一列是操作按钮,他们的click事件在后面补充

...

      
  
    
  

...

下来还有一个分页控件


现在的效果为
从vue到elementUI项目(五)_第4张图片

调整一下common-table和pager控件的样式


效果就出来了
从vue到elementUI项目(五)_第5张图片

用户管理页面功能实现

  • 表格加载状态
  • 分页功能
  • 编辑功能
  • 删除功能

实现表格加载状态

表格加载状态只需要在el-table中绑定属性v-loading即可,config.loading是我们后台传过来的


...
export default {
...
  methods: {
    getList() {
      this.config.loading = true
      this.$http
        .get('/api/user/getUser', {
          params: {
            page: this.config.page,
            name
          }
        })
        .then(res => {
          this.tableData = res.data.list.map(item => { // 处理一下sex的中文显示
            item.sexLabel = item.sex === 0 ? '女' : '男'
            return item
          })
          this.config.total = res.data.count
          this.config.loading = false
        })
    },
  },
  created() {
    this.getList()
  }
}

...

现在也是第二页的显示
从vue到elementUI项目(五)_第7张图片

实现编辑功能

编辑功能比较好实现,编辑按钮是在CommonTable.vue组件中的,只需要在,只需要对两个按钮进行事件绑定,然后通过消息传递到UserManage.vue中,顺带把删除的消息传递一块写了




...

在页面UserManage.vue中,去响应传过来的消息,在editUser与delUser中先建个坑


...
    editUser(row) {
      console.log("editUser")
      console.log(row)
    },
    delUser(row) {
      console.log("delUser")
    },
...

实现新增功能

新增功能需要做的只是新建一个form,填入对应的内容,这个form可以重复利用之前的组件CommonForm.vue,为此我们需要在data部分新增需要的内容,大概也就这几个

  • operateType:我们需要一个类型,在后面提交数据的时候还需要区分是编辑还是新增
  • isShow:表单是否显示出来
  • operateForm:定义表单需要提交的字段(与接口中返回的内容需要一致)
  • operateFormLabel:表示在CommonForm.vue组件上面我们需要出现的控件

下面是这些data数据的数据部分

  data() {
    return {
...
      operateType: 'add',
      isShow: false,
      // 定义表单需要编辑的字段
      operateForm: {
        name: '',
        addr: '',
        age: '',
        birth: '',
        sex: ''
      },
      operateFormLabel: [
        {
          model: 'name',
          label: '姓名'
        },
        {
          model: 'age',
          label: '年龄'
        },
        {
          model: 'sex',
          label: '性别',
          type: 'select',
          opts:
            [
              {
                label: '男',
                value: 1
              },
              {
                label: '女',
                value: 0
              }
            ]
        },
        {
          model: 'birth',
          label: '出生日期',
          type: 'date'
        },
        {
          model: 'addr',
          label: '地址'
        }
      ],
...
    }
  },

现在来加入表单的部分,因为想把新增和编辑是在同一个表单中进行,所以就只用一个el-dialog来显示,所以UserManage.vue现在的网页部分代码为


接着来改editUser部分代码、增加addUser代码,以及实现confirm的提交,先用输出占坑

...
    addUser() {
      // this.operateType = 'edit'
      // this.isShow = true
      // this.operateForm = row
      // console.log("addUser")
      console.log("addUser")
      this.operateType = 'add'
      this.isShow = true
    },
    editUser(row) {
      // console.log("editUser")
      // console.log(row)
      // 显示编辑框
      this.operateType = 'edit'
      this.isShow = true
      this.operateForm = row
    },
...
    confirm() {
      // console.log(row)
      if (this.operateType === 'edit') {
        console.log("edit", this.operateForm)
      }
      else {
        console.log("add", this.operateForm)
      }
      // 无论是新增还是编辑都需要重新刷新表格
      this.getList()
    }
  },
...

现在所能看到的新增点击事件如下
从vue到elementUI项目(五)_第8张图片

编辑后可以看到的内容
从vue到elementUI项目(五)_第9张图片

实现删除功能

之前给删除留了坑,我们想要在删除的时候,进行一下消息提示,也就是使用eleme组件的"Message Box弹框"

delUser(row) {
      // console.log("editUser")
      console.log(row)
      this.$confirm('此操作将永久删除该记录, 是否继续?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
        this.$message({
          type: 'success',
          message: '删除成功!'
        });
        // 提交给后台进行删除
        console.log("提交给后台进行删除")
        // 删除后进行刷新
        this.getList()
      }).catch(() => {
        this.$message({
          type: 'info',
          message: '已取消删除'
        });
      });
    },

企业开发之权限管理思路路讲解

什么是权限管理理

  • 根据不同用户,返回不同菜单
  • 严格控制用户权限
  • 实现思路
    • 动态路由
    • 后端返回的数据格式要求
    • 触发时机
      • 登陆成功的时候触发操作
      • Cookie中存在对应数据,首次进入页面时

为了实现权限管理,需要先加入一个登陆页面,只有当登陆成功后,才能进行后续的操作

修改之前views/Login下的Login.vue代码






在mock中新建一个permission.js,响应这个登陆请求

import Mock from 'mockjs'
export default {
  getMenu: config => {
    const { username, password } = JSON.parse(config.body)
    console.log(JSON.parse(config.body))
    // 先判断用户是否存在
    if (username === 'admin' || username === 'wp') {
      // 判断账号和密码是否对应
      if (username === 'admin' && password === '123456') {
        return {
          code: 20000,
          data: {
            menu: [
              {
                path: '/',
                name: 'home',
                label: '首页',
                icon: 's-home',
                url: 'Home/Home'
              },
              {
                path: '/video',
                name: 'video',
                label: '视频管理页',
                icon: 'video-play',
                url: 'VideoManage/VideoManage'
              },
              {
                path: '/user',
                name: 'user',
                label: '用户管理页',
                icon: 'user',
                url: 'UserManage/UserManage'
              },
              {
                label: '其他',
                icon: 'location',
                children: [
                  {
                    path: '/page1',
                    name: 'page1',
                    label: '页面1',
                    icon: 'setting',
                    url: 'Other/PageOne'
                  },
                  {
                    path: '/page2',
                    name: 'page2',
                    label: '页面2',
                    icon: 'setting',
                    url: 'Other/PageTwo'
                  }
                ]
              }
            ],
            message: '获取成功'
          }
        }
      } else if (username === 'wp' && password === '123456') {
        return {
          code: 20000,
          data: {
            menu: [
              {
                path: '/',
                name: 'home',
                label: '首页',
                icon: 's-home',
                url: 'Home/Home'
              },
              {
                path: '/video',
                name: 'video',
                label: '视频管理页',
                icon: 'video-play',
                url: 'VideoManage/VideoManage'
              }
            ],
            message: '获取成功'
          }
        }
      } else {
        return {
          code: -999,
          data: {
            message: '密码错误'
          }
        }
      }
    } else {
      return {
        code: -999,
        data: {
          message: '用户不存在'
        }
      }
    }
  }
}

在router/index.js引入这个permission.js

import Mock from 'mockjs'
import homeApi from './home.js'
import userApi from './user.js'
import permissionApi from './permission.js'
...
// 权限相关
Mock.mock(/\/permission\/getMenu/, 'post', permissionApi.getMenu)

在路由中定义一个/login,找到router/index.js,加入如下新增内容

import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter)

const routes = [{
    path: '/',
    component: () => import('@/views/Main.vue'),
    children: [{
     ...
    ]
  },
  {
    path: '/login',
    name: 'login',
    component: () => import('@/views/Login/Login')
  }
]
...

测试一下登陆,访问链接http://localhost:3333/#/login,可以看到如下
从vue到elementUI项目(五)_第10张图片

输入admin、123456后提交,可以看到后台输出
从vue到elementUI项目(五)_第11张图片

权限管理之动态返回菜单的实现

更改路由表

  • 根据是否需要权限的路由分类

vuex里补充mutation

  • 保存菜单
  • 动态添加菜单

生成路由的时机

  • 登录时
  • 刷新时

点击退出时,清除cookie后,刷新下页面

先要安装js-cookie

yarn add js-cookie -S

之前的左侧菜单项都是在CommonAside.vue中的data写死的,现在想让在登录后自动维护一下动态菜单,之前在tab.js中定义的menu这个时候就能用起来了

先来屡一下,我们一共需要这几个操作

  • clearMenu:在退出登录的时候情况菜单
  • setMenu:当接口回复菜单的json数据时,保存到cookie中
  • addMenu:从cookie中获取菜单的数据,通过router.addRoutes添加到路由中
...
  state: {
    isCollapse: false,
    menu: [],
    currentMenu: {},
 ...
    clearMenu(state) {
      state.menu = []
      Cookie.remove('menu')
    },
    setMenu(state, val) {
      state.menu = val
      Cookie.set('menu', JSON.stringify(val))
    },
    addMenu(state, router) {
      // 从cookie中取出来menu数据,将这个数据添加到router中
      if (!Cookie.get('menu')) {
        return
      }
      let menu = JSON.parse(Cookie.get('menu'))
      state.menu = menu
      let currentMenu = [
        {
          path: '/',
          component: () => import(`@/views/Main`),
          children: [],
        },
      ]
      menu.forEach((item) => {
        if (item.children) {
          item.children = item.children.map((item) => {
            item.component = () => import(`@/views/${item.url}`)
            return item
          })
          currentMenu[0].children.push(...item.children)
        } else {
          item.component = () => import(`@/views/${item.url}`)
          currentMenu[0].children.push(item)
        }
      })
      router.addRoutes(currentMenu) // 动态添加到路由中
    },
...

在CommonAside.vue中,我们需要用这个store中的menu替换下之前使用的asideMenu

...

...

在浏览器中做一下测试http://localhost:3333/#/user
从vue到elementUI项目(五)_第14张图片

保存taglist到cookie中

如果不把taglist保存到cookie中,每当刷新页面,这些信息就会丢失,总是vuex中的数据感觉总是那么不靠谱,因为之前在main.js中已经在路由守卫中使用了getMenu,但之前没有保存

...
router.beforeEach((to, from, next) => {
  // 防止刷新后vuex里丢失token
  store.commit('getToken')
  // 防止刷新后vuex里丢失标签列表tagList
  store.commit('getMenu')
  let token = store.state.user.token
  // 过滤登录页,防止死循环
...

所以我们只需要在store/tab.js中selectMenu选择是保存到Cookie中

...
  mutations: {
    selectMenu(state, val) {
      if (val.name !== 'home') {
        state.currentMenu = val
        let result = state.tabsList.findIndex((item) => item.name === val.name)
        result === -1 ? state.tabsList.push(val) : ''
        Cookie.set('tagList', JSON.stringify(state.tabsList))
      } else {
        state.currentMenu = null
      }
    },
...
    getMenu(state) {
      if (Cookie.get('tagList')) {
        let tagList = JSON.parse(Cookie.get('tagList'))
        state.tabsList = tagList
      }
    },
  },
...
}

这时再刷新就不会丢失tag了

完成搜索功能

最后还差一下UserManage.vue页面中的搜索功能,我们之前已经为搜索文本框绑定了keyword属性,只需要利用这个值,传入到接口getList即可