vue-element-admin整合spring-boot实现权限控制之用户管理篇

vue-element-admin整合spring-boot实现权限控制之用户管理篇
0 引言

距离笔者上次利用vue-element-admin项目整合后台spring-boot项目打通前后端权限控制首页左侧菜单功能过去一个半月了。最近换了项目组,用的都是华为的自研新技术,而且华为的IT系统集成了非常多的自研系统,很长一段时间竟然让笔者感觉摸不清门路,尤其是以灵雀系统为代表的低代码平台,前段都不需要怎么写代码了,只需配置模型和对服务进行编排,然后页面绑定模型和服务就OK了。代码量是少了很多,但是入门门口却更高了。所以这一个多月笔者因为加班太多,也没有太多时间开发自己的开源项目。但是最近总算腾出时间实现了之前承诺要实现的功能。本文笔者将带领大家一起实现如何使用element-ui开源UI框架调用spring-boot项目实现后台接口实现分页查询用户信息功能及查看用户下的角色等功能,末尾还会展示页面效果。

注意:本文的功能实现在上一篇文章vue-element-admin整合SpringBoot实现动态渲染基于角色的菜单资源踩坑录(前后端整合篇)

1 谈谈需求的原型设计

在IT行业,几乎所有的开发都是基于一定的需求开发出来的,产品经理一般会在与客户对接后形成一个需求文档,需求文档里不仅有详细需求规格说明和描述, 更少不了原型设计出来的低保真高保真图。低保真图一般由产品尽力借助mockplussketch等原型设计工具来完成,而高保真则由专门的UCD人员来完成。UCD是User-Centered Design 的缩写,翻译成中文就是:以用户为中心的设计。

首先我们根据原型设计图需求来完成后台的两个接口,分别是分页查询用户信息数据接口根据用户ID查询用户角色列表。第一个接口对应前端UI功能为点击左侧菜单权限管理下的用户管理时显示默认的分页查询用户信息列表,同时 还可以通过form表单查询用户列表 ;第二个接口对应点击每行用户数据操作栏中的查看已有角色链接时弹框显示选中用户已有的角色列表。
用户管理界面原型设计图
图 1 用户管理界面

在这里插入图片描述

图 2 点击查看已有角色链接弹框显示选中用户已有的角色列表

说明:由于笔者对于产品设计工具的使用并不熟练,因此使用了截取部分效果图作为原型图

2 后端接口开发

根据原型界面拆分的需求完成两个后台接口的开发,按照分层设计的思想完成两个接口的编码。

2.1 完成分页查询用户信息接口

2.1.1 Dao层代码

UserMapper.java

// 查询符合条件的用户数量
int queryUserCountsByCondition(@Param("userParam") QueryUserParam userParam);
// 分页查询
    List<User> queryPageUsersByCondition(@Param("startIndex") int startIndex,@Param("endIndex") int endIndex,
                                         @Param("userParam") QueryUserParam userParam);

UserMapper.xml

<select id="queryUserCountsByCondition" resultType="int">
        select count(*) from user
        <include refid="userConditionSql" />
    </select>
    <!--根据查询条件过滤sql片段-->
    <sql id="userConditionSql">
        where enabled=1
        <if test="userParam.username!=null and userParam.username!=''">
            and  username like  concat('%',#{userParam.username,jdbcType=VARCHAR},'%')
        </if>
        <if test="userParam.nickname!=null and userParam.nickname!=''">
            and  nickname like concat('%',#{userParam.nickname,jdbcType=VARCHAR},'%')
        </if>
        <if test="userParam.email!=null and userParam.email!=''">
            and email like concat('%',#{userParam.email,jdbcType=VARCHAR},'%')
        </if>
        <if test="userParam.regStartTime!=null and userParam.regStartTime!=''">
            and regTime <![CDATA[>=]]> #{userParam.regStartTime}
        </if>
        <if test="userParam.regEndTime!=null and userParam.regEndTime!=''">
            and regTime <![CDATA[<=]]> #{userParam.regEndTime}
        </if>
    </sql>
    <!--分页用户查询sql-->
    <select id="queryPageUsersByCondition" resultType="org.sang.pojo.User">
        select t.id,t.username,t.nickname,t.enabled,t.email,t.regTime
        from
        (select id, username,nickname,enabled,email,regTime
        from user
        <include refid="userConditionSql" />
        ) t limit #{startIndex,jdbcType=INTEGER},#{endIndex,jdbcType=INTEGER}
    </select>

2.1.2 Service层代码

UserService.java

public RespBean queryPageUsersByCondition(PageVo<User> pageVo, QueryUserParam userParam){
        logger.info("currPage={},pageSize={},queryUserParam={}",
                pageVo.getCurrPage(),pageVo.getPageSize(), JSON.toJSON(userParam));
        int totalRows = userMapper.queryUserCountsByCondition(userParam);
        pageVo.setTotalRows(totalRows);
    // 若结束下标大于总的数量,则将总的数量作为下标赋值给结束下标
        if(pageVo.getEndIndex()>totalRows){
            pageVo.setEndIndex(totalRows);
        }
    // 设置总页数
        pageVo.setTotalPage((totalRows/pageVo.getPageSize())+1);
        List<User> pageUsers = userMapper.queryPageUsersByCondition(pageVo.getStartIndex(),pageVo.getEndIndex(),userParam);
        pageVo.setResult(pageUsers);
        RespBean respBean = new RespBean(200,"success");
        respBean.setData(pageVo);
        return respBean;
    }

用户对象日期属性添加@JsonFormat注解,方面前台日期以字符串的格式展示

User.java

@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
    private Date regTime;

2.1.3 Controller层代码

UserController.java

@RequestMapping(value = "/pageQuery/users/{currPage}/{pageSize}",method = RequestMethod.POST)
    public RespBean queryPageUsersByCondition(@PathVariable("currPage") int currPage, @PathVariable("pageSize") int pageSize,
                                              @RequestBody QueryUserParam userParam){
        PageVo<User> pageVo = new PageVo<>(currPage,pageSize);
        RespBean respBean = userService.queryPageUsersByCondition(pageVo,userParam);
        return respBean;
    }
2 .2 完成根据用户ID查询用户角色列表接口

2.2.1 Dao层代码

RoleMapper.java

List<Role> getRolesByUid(Long uid);

RoleMapper

    <select id="getRolesByUid" parameterType="java.lang.Long" resultType="org.sang.pojo.Role">
        SELECT r.id,
               r.role_code as roleCode,
               r.role_name as roleName
        FROM roles r
        where r.id in
        (select rid
         from roles_user
         where uid = #{uid,jdbcType=BIGINT}
         )
    </select>

2.2.2 Service层代码

RoleService.java

    @Autowired
    private RolesMapper rolesMapper;

    public List<Role> getRolesByUid(Long uid){

        List<Role> roles = rolesMapper.getRolesByUid(uid);
        // 按ID升序排列
        if(roles.size()>1){
            roles.sort(Comparator.comparing(Role::getId));
        }
        return roles;
    }

2.2.3 Controller层代码

RoleController.java

@GetMapping("/getUserRoles")
    public RespBean getUserRoles(@RequestParam("uid") Long uid) {
        logger.info("uid={}",uid);
        List<Role> roles = roleService.getRolesByUid(uid);
        RespBean respBean = new RespBean(200, "success");
        respBean.setData(roles);
        return respBean;
    }

接口开发完成后,启动后台服务后就可以通过postman对接口进行测试,之前的文章中有过很多关于如何使用postman这款接口UI工具对开发出来的API进行测试, 这里笔者就不演示了。

3 前端界面及按钮功能实现
3.1 增加两个后台接口的API导出配置

src/api/user.js

export function queryUserInfosByPage(queryParam, pageParam) {
  return request({
    url: `pageQuery/users/${pageParam.currPage}/${pageParam.pageSize}`,
    method: 'post',
    data: queryParam
  })
}

src/api/role.js

export function getRolesByUid(uid) {
  return request({
    url: `/role/getUserRoles?uid=${uid}`,
    method: 'get'
  })
}
3.2 完成用户管理vue组件编码

根据原型设计图,我们需要开发一个用户管理的组件,我们可以在我们的前端项目vue-element-adminsrc/views/permission目录下新建一个UserManage.vue文件。笔者参考了element-ui官网的组件demo源码完成了用户管理组件编码,其源码如下:

<!--组件html模板-->
<template>
  <div id="userAccoutTemplate">
      <!--查询表单-->
    <el-form :model="searchForm" label-width="120px" ref="searchForm" class="search-ruleForm">
      <div class="form-line">
        <el-form-item label="账号:" class="user-account" prop="username">
          <el-input v-model="searchForm.username"></el-input>
        </el-form-item>
        <el-form-item label="用户昵称:" class="nickname" prop="nickname">
            <el-input v-model="searchForm.nickname"></el-input>
        </el-form-item>
        <el-form-item label="邮箱:" class="email" prop="email">
            <el-input v-model="searchForm.email"></el-input>
        </el-form-item>
        </div>
        <div class="date-time-line form-line">
            <!--日期时间范围组件-->
          <el-form-item label="时间范围" class="timeRange" prop="timeRange">
              <el-date-picker v-model="searchForm.dateTimes" type="datetimerange"
                range-separator="至" start-placeholder="开始时间" end-placeholde="结束时间" align="right" 
               format="yyyy-MM-dd HH:mm:ss" value-format="yyyy-MM-dd HH:mm:ss" :picker-options="pickOptions">
              </el-date-picker>
          </el-form-item>
        </div>
        <div class="button-line">
            <el-button type="primary" @click="submitForm('searchForm')">查询</el-button>
            <el-button @click="resetForm('searchForm')">重置</el-button>
        </div>
    </el-form>
      <!--数据列表-->
    <el-table border :data="userAccounts" style="width: 100%">
        <el-table-column prop="username" label="用户账号" width="200"></el-table-column>
        <el-table-column prop="nickname" label="用户名称" width="200"></el-table-column>
        <el-table-column prop="email" label="邮箱" width="250"></el-table-column>
        <el-table-column prop="regTime" label="注册时间" width="250"></el-table-column>
        <el-table-column width="350"
        label="操作">
        <template slot-scope="scope">
          <el-button @click="openQueryDialog(scope.row)" type="text" size="small">查看已有角色</el-button>
          <el-button type="text" @click="openEditDialog(scope.row)" size="small">分配角色</el-button>  
        </template>
      </el-table-column>
    </el-table>
    <!-- 分页组件-->
    <el-pagination @size-change="hadleSizeChange" @current-change="handleCurrentChange"
      :current-page="currentPage" :page-sizes="pageSizes" :page-size="pageSize"
       layout="total, sizes, prev, pager, next, jumper" :total="totalCounts">    
    </el-pagination>
     <!--用于显示用户角色列表的弹框-->
    <el-dialog class="query-dialog" title="用户已有角色" :visible.sync="queryDialogVisible" 
      width="35%">
      <el-table ref="qeuryDialogTable" :data="userRoles" highlight-current-row 
        @current-change="handleCurrentRowChange" style="width: 100%"
      >
         <el-table-column property="id" label="角色ID" width="80">
         </el-table-column>
         <el-table-column property="roleCode" label="角色编码" width="120">
         </el-table-column>
         <el-table-column property="roleName" label="角色名称" width="120">
         </el-table-column>
      </el-table>
      <span slot="footer" class="dialog-footer">
        <el-button @click="closeQueryDialog">取 消</el-button>
        <el-button type="primary" @click="closeQueryDialog">确 定</el-button>
      </span>
    </el-dialog>
  </div>
</template>
<!--javascript脚本-->
<script>
import {queryUserInfosByPage} from '@/api/user'
import {getRolesByUid} from '@/api/role'
import { Message } from 'element-ui'

export default {
    // 数据模型
  data() {
    let startDate = new Date()
    // 默认选中最近一周的时间
    startDate.setTime(startDate.getTime() -  3600 * 1000 * 24 * 7)
    const endDate =new Date()
    const startYear = startDate.getFullYear()
    const startMonth = startDate.getMonth()>8?''+(startDate.getMonth()+1):'0'+(startDate.getMonth()+1)
    const startMonthDay = startDate.getDate()>9?''+startDate.getDate():'0'+startDate.getDate()
    const startHours = startDate.getHours()>9?''+startDate.getHours():'0'+startDate.getHours()
    const startMinutes = startDate.getMinutes()>9?''+startDate.getMinutes():'0'+startDate.getMinutes()
    const startSeconds = startDate.getSeconds()>9?''+startDate.getSeconds():'0'+startDate.getSeconds()
    const startDateTimeStr = startYear+'-'+startMonth+'-'+startMonthDay+' '+startHours+':'+startMinutes+':'+startSeconds
    const endYear = endDate.getFullYear()
    const endMonth = endDate.getMonth()>8?''+(endDate.getMonth()+1):'0'+(endDate.getMonth()+1)
    const endMonthDay = endDate.getDate()>9?''+endDate.getDate():'0'+endDate.getDate()
    const endHours = endDate.getHours()>9?''+endDate.getHours():'0'+endDate.getHours()
    const endMinutes = endDate.getMinutes()>9?''+endDate.getMinutes():'0'+endDate.getMinutes()
    const endSeconds = endDate.getSeconds()>9?''+endDate.getSeconds():'0'+endDate.getSeconds()
    const endDateTimeStr = endYear+'-'+endMonth+'-'+endMonthDay+' '+endHours+':'+endMinutes+':'+endSeconds
    return {
      searchForm: {
        username: '',
        nickname: '',
        email: '',
        dateTimes: [startDateTimeStr,endDateTimeStr]
      },
      userAccounts: [{ id: 1, username: 'zhangsan', nickname: '张三', email: 'zhangsan2021@163.com', regTime: '2021-06-20 23:58:48' },
        { id: 2, username: 'heshengfu', nickname: '程序员阿福', email: 'heshengfu2018@163.com', regTime: '2021-06-21 00:00:13' },
        { id: 3, username: 'lisi', nickname: '李四', email: 'lisi1998@163.com', regTime: '2021-08-04 00:45:38' }],
      pickOptions: {
        shortcuts: [{
            text: '最近一周',
            onClick(picker) {
              const end = new Date();
              const start = new Date();
              start.setTime(start.getTime() - 3600 * 1000 * 24 * 7);
              picker.$emit('pick', [start, end]);
            }
          }, {
            text: '最近一个月',
            onClick(picker) {
              const end = new Date();
              const start = new Date();
              start.setTime(start.getTime() - 3600 * 1000 * 24 * 30);
              picker.$emit('pick', [start, end]);
            }
          }, {
            text: '最近三个月',
            onClick(picker) {
              const end = new Date();
              const start = new Date();
              start.setTime(start.getTime() - 3600 * 1000 * 24 * 90);
              picker.$emit('pick', [start, end]);
            }
          }]
      },
      currentPage: 1,
      pageSizes: [10,20,50,100,500],
      pageSize: 10,
      totalCounts: 120,
      queryDialogVisible: false,
      currentRoleRow: 0,
      userRoles: [
        {id:1,roleCode:'admin',roleName:'管理员'},
        {id:2,roleCode:'user',roleName:'普通用户'}
        ]
    }
      
  },
  methods: {
      // 提交查询表单
      submitForm(formName) {
      console.log(formName)
      const formData = this.searchForm
      const queyParam = {
        username: formData.username,
        nickname: formData.nickname,
        email: formData.email,
        regStartTime: formData.dateTimes[0],
        regEndTime: formData.dateTimes[1]
      }
      console.log(queyParam)
      // 分页参数
      const pageParam = {
        currPage: this.currentPage,
        pageSize: this.pageSize
      }
      console.log(pageParam)
      // 发送axios请求查询用户数据    
      queryUserInfosByPage(queyParam, pageParam).then(res=>{
          if(res.status===200 && res.data.status===200){
            const pageData = res.data.data
            this.totalCounts = pageData.totalRows
            this.userAccounts = pageData.result
          }else{
            Message.error('queryUserInfosByPage, status:'+res.data.status+', message:'+res.data.msg)
          }
      }).catch(err=>{
        Message.error(err)
      })
    },
    // 表单重置时间   
    resetForm(formName) {
      this.$refs[formName].resetFields()
    },
   // 分页条数改变事件   
    hadleSizeChange(val) {
      console.log(val);
      this.pageSize = val
      this.submitForm('searchForm')
    },
    // 当前页改变事件  
    handleCurrentChange(val){
       console.log(val);
       this.currentPage = val
       this.submitForm('searchForm')
    },
    // 打开查询用户已有角色对话框事件
    openQueryDialog(row) {
      console.log(row)
      this.queryDialogVisible = true
      const uid = row.id
      getRolesByUid(uid).then(res=>{
        if(res.status===200 && res.data.status===200){
          this.userRoles = res.data.data
        }else{
          this.userRoles = []
           Message.error('getRolesByUid, status:'+res.data.status+', message:'+res.data.msg)
        }
      }).catch(err=>{
         console.error(err)
      })
    },
    openEditDialog(row){
      console.log(row)
    },
    closeQueryDialog(){
      this.queryDialogVisible = false
    },
    // 当前行改变事件  
    handleCurrentRowChange(row){
      this.currentRoleRow = row
      this.$refs['qeuryDialogTable'].setCurrentRow(row)
    }
  }
}
</script>
<!--页面样式,通过调试模式在浏览器中调试出效果后复制过来-->
<style lang="scss" scoped>
  .search-ruleForm{
    width: 100%;
    height: 180px;
    .el-form-item__label {
      text-align: right;
    }
    .form-line {
      height: 32px;
      width: 100%;
      margin-bottom: 20px;
      .el-form-item {
        width: 32%;
        float: left;
      }
    }
    .button-line {
      height: 40px;
      width: 100%;
      margin-top: 25px;
    }
  }
</style>
3.3 修改权限管理入口组件源码

src/views/page.vue

<template>
  <div class="app-container">
    <user-manage></user-manage>
  </div>
</template>
<script>
// import SwitchRoles from './components/SwitchRoles'
import UserManage from './components/UserManage.vue'

export default {
  name: 'PagePermission',
  components: { UserManage },
  methods: {
    // handleRolesChange() {
    //   this.$router.push({ path: '/permission/index' })
    // }
  }
}
</script>

这里需要在权限管理入口中加入用户管理组件,并屏蔽掉原来的角色切换组件

3.4 修改路由数组

src/router/index.js

export const asyncRoutes = [
  {
    id: '15',
    path: '/permission',
    component: Layout,
    redirect: '/permission/page',
    alwaysShow: true, // will always show the root menu
    name: 'Permission',
    meta: {
      title: '权限管理',
      icon: 'lock'
    //  roles: ['admin', 'editor']
    },
    children: [
      {
        id: '16',
        path: 'page',
        component: () => import('@/views/permission/page'),
        name: 'PagePermission',
        meta: {
          title: '用户管理'
        }
      },
      {
        id: '17',
        path: 'directive',
        component: () => import('@/views/permission/directive'),
        name: 'DirectivePermission',
        meta: {
          title: 'Directive Permission'
          // if do not set roles, means: this page does not require permission
        }
      },
      {
        id: '18',
        path: 'role',
        component: () => import('@/views/permission/role'),
        name: 'RolePermission',
        meta: {
          title: '角色管理',
          roles: ['admin']
        }
      }
    ]
  },
  // ......其他路由组件
]

为了让权限控制菜单以中文的形式显示,这里修改了路由组件数据对应权限相关数据的title字段数据

4 效果体验

vue-element-admin项目根目录下通过git bash或者cmd的方式打开控制台,执行npm run dev命令启动项目脚本。

控制台出现如下日志表示前端启动成功:

98% after emitting CopyPlugin DONE  Compiled successfully in 1072ms上午8:04:51

  App running at:
  - Local:   http://localhost:3000/
  - Network: http://192.168.1.235:3000/

在谷歌浏览器中输入http://localhost:3000/进入登录界面,登录成功后进入项目首页,然后点击左侧菜单栏中的权限管理->用户管理可以看到下面的界面效果图

用户管理界面

​ 图 3 用户管理界面效果图

点击事件范围选则框中的快捷选择最近三个月,然后点击查询按钮,可以看到界面显示了从后台数据库查询出来的用户信息数据,并按每页10条显示。用户也可以输入账号、用户昵称和邮箱等信息作为查询条件查询符合搜索条件的数据,也可以点击切换当前页和页条数,从而在界面上看到自己想要的数据。

查询表单数据
​ 图 4 显示form表单分页查询数据

点击每条用户信息记录操作栏中的查看已有角色链接弹出一个对话框显示用户已经分配的角色

查看用户已有角色
​ 图 5 查看用户已有角色

5 小结

本文紧接我的上一篇原创文章vue-element-admin整合SpringBoot实现动态渲染基于角色的菜单资源踩坑录(前后端整合篇)开发了自定义权限设计模块中的用户管理界面功能,涉及到分页查看用户信息弹框显示用户已有角色等两项功能。功能虽少,但却能帮助读者快速掌握前后端分离开发的技能,也能快速掌握element-ui这款UI框架开发出优美简约的Web界面功能。在笔者的下一篇文章中将尽快推出给用户分配角色、以及给角色分配页面路由资源等功能,敬请期待!

6 参考链接

element-ui官网组件demo

本人首发笔者的个人微信公众号“阿福谈Web编程”,欢迎读者朋友们在微信公众号里搜索“阿福谈Web编程”,让我们一起在技术的路上共同成长!

  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Spring Boot、Vue-Element-Admin和MyBatis是一种常见的技术组合,用于构建现代化的Web应用程序。下面是一个简要的入门教程。 1. 首先,我们需要设置Spring Boot项目。可以使用Spring Initializr(https://start.spring.io/)来初始化一个基本的Spring Boot项目。在依赖项中添加Spring Web、Spring Data JPA和MyBatis等必要的依赖项。 2. 在Spring Boot项目中,我们需要创建实体类和数据库表的映射。使用JPA注解来定义实体类,并使用MyBatis注解来指定数据库表的映射。 3. 接下来,我们需要创建一个数据访问层(DAO)来处理与数据库的交互。使用MyBatis的注解或XML映射文件来定义SQL查询和操作。 4. 在Service层,编写业务逻辑代码来处理DAO返回的数据,并与其他组件进行交互。 5. 在Controller层,处理HTTP请求和响应,将数据传递给前端页面或接收前端发送的数据。使用Spring MVC注解来定义请求映射和参数解析。 6. 在前端方面,可以使用Vue-Element-Admin来构建用户界面。Vue是一种流行的JavaScript框架,用于构建灵活的单页面应用程序。Element-Admin是一个基于Vue的组件库,提供了丰富的UI组件和布局。 7. 在Vue-Element-Admin中,我们可以使用Vue Router来实现页面之间的导航和路由。使用axios来发送HTTP请求与后端进行数据交互。 8. 在Vue组件中,我们可以通过调用后端的API来获取数据并渲染到前端页面上。使用Element-Admin提供的布局和UI组件来美化页面。 通过上述步骤,我们可以实现一个基本的Spring Boot、Vue-Element-Admin和MyBatis的入门教程。这个教程可以帮助初学者了解如何搭建和使用这个技术组合来构建现代化的Web应用程序。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

heshengfu1211

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值