• 会议OA之我的审批


    目录

    一、会议查询 

    二、审批签字 


    上次与大家分享了会议排座以及会议送审,按照项目流程来,今天与大家分享会议审批。

    会议审批包含了会议查询和审批签字两个功能。

    会议查询:在我的审批界面,当前用户点击查询,显示的是他还未进行审批的会议数据

    审批签字:当审批人浏览完该会议内容后,可以选择驳回或者通过该会议,如果通过该会议则进行签字,驳回则不需要签字。审批签字运用到了在线绘画工具的一个插件以及签字截图功能,类似于ps美化签字图片。在后端运用到了批处理方法,同时处理将被审批的会议新增入库以及修改会议状态的事务,业务处理结果必须保持一致。

    一、会议查询 

    实现思路:由当前登录账号作为审批人字段值,点击查询,显示该账号下还未审批的会议数据

    后端:

    1.Dao层方法

    sql语句2和3分别是查询审批人和查询会议状态的语句

    1. public ListString, Object>> myAudit(MeetingInfo info, PageBean pageBean) throws Exception, Exception, Exception {
    2. String sql = getSQL();
    3. //会议标题查询
    4. String title = info.getTitle();
    5. if(StringUtils.isNotBlank(title)) {
    6. sql += " and title like '%"+title+"%'";
    7. }
    8. //当前登陆账号是会议信息表中的审批人字段值
    9. sql += " and a.auditor = " + info.getAuditor();
    10. //只查询会议状态为2 的 待审核的会议
    11. sql += " and a.state = 2 ";
    12. //排序按照降序展示
    13. sql += " order by a.id desc ";
    14. return super.executeQuery(sql, pageBean);
    15. }

    2、Web层 

    1. //我的审批
    2. public String myAudit(HttpServletRequest req, HttpServletResponse resp) {
    3. try {
    4. PageBean pageBean = new PageBean();
    5. pageBean.setRequest(req);
    6. List<Map<String, Object>> infos = infoDao.myAudit(info, pageBean);
    7. ResponseUtil.writeJson(resp, R.ok(0, "会议信息查询成功" , pageBean.getTotal(), infos));
    8. } catch (Exception e) {
    9. e.printStackTrace();
    10. try {
    11. ResponseUtil.writeJson(resp, R.error(0, "会议信息查询失败"));
    12. } catch (Exception e1) {
    13. e1.printStackTrace();
    14. }
    15. }
    16. return null;
    17. }

     前端:

    1、Jsp页面

    1. <%@ page language="java" contentType="text/html; charset=UTF-8"
    2. pageEncoding="UTF-8"%>
    3. <%@include file="/common/header.jsp"%>
    4. html>
    5. <html>
    6. <head>
    7. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    8. <script type="text/javascript" src="${pageContext.request.contextPath }/static/js/meeting/myAudit.js">script>
    9. head>
    10. <style>
    11. body{
    12. margin:15px;
    13. }
    14. .layui-table-cell {height: inherit;}
    15. .layui-layer-page .layui-layer-content { overflow: visible !important;}
    16. style>
    17. <body>
    18. <div class="layui-form-item" style="margin:15px 0px;">
    19. <div class="layui-inline">
    20. <label class="layui-form-label">会议标题label>
    21. <div class="layui-input-inline">
    22. <input type="hidden" id="auditor" value="${user.id }"/>
    23. <input type="text" id="title" autocomplete="off" class="layui-input">
    24. div>
    25. div>
    26. <div class="layui-inline">
    27. <button id="btn_search" type="button" class="layui-btn"><i class="layui-icon layui-icon-search">i> 查询button>
    28. div>
    29. div>
    30. <table id="tb" lay-filter="tb" class="layui-table" style="margin-top:-15px">table>
    31. <script type="text/html" id="tbar">
    32. <a class="layui-btn layui-btn-xs" lay-event="edit">审批a>
    33. script>
    34. body>
    35. html>

    2、Js代码

    1. let layer,table,$,form;
    2. var row;
    3. layui.use(['layer','table','jquery','form'],function(){
    4. layer=layui.layer,
    5. table=layui.table,
    6. form=layui.form,
    7. $=layui.jquery;
    8. initTable();
    9. //查询事件
    10. $('#btn_search').click(function(){
    11. query();
    12. });
    13. });
    14. //初始化数据表格(我的审批)
    15. function initTable(){
    16. table.render({ //执行渲染
    17. elem: '#tb', //指定原始表格元素选择器(推荐id选择器)
    18. height: 400, //自定义高度
    19. loading: false, //是否显示加载条(默认 true)
    20. cols: [[ //设置表头
    21. {field: 'id', title: '会议编号', width: 90},
    22. {field: 'title', title: '会议标题', width: 120},
    23. {field: 'location', title: '会议地点', width: 140},
    24. {field: 'startTime', title: '开始时间', width: 120},
    25. {field: 'endTime', title: '结束时间', width: 120},
    26. {field: 'meetingState', title: '会议状态', width: 120},
    27. {field: 'seatPic', title: '会议排座', width: 120,
    28. templet: function(d){
    29. if(d.seatPic==null || d.seatPic=="")
    30. return "尚未排座";
    31. else
    32. return "";
    33. }
    34. },
    35. {field: '', title: '操作', width: 200,toolbar:'#tbar'},
    36. ]]
    37. });
    38. }
    39. //点击查询
    40. function query(){
    41. table.reload('tb', {
    42. url: $("#ctx").val()+'/info.action', //请求地址
    43. method: 'POST', //请求方式,GET或者POST
    44. loading: true, //是否显示加载条(默认 true)
    45. page: true, //是否分页
    46. where: { //设定异步数据接口的额外参数,任意设
    47. 'methodName':'myAudit',
    48. 'auditor':$('#auditor').val(),
    49. 'title':$('#title').val(),
    50. },
    51. request: { //自定义分页请求参数名
    52. pageName: 'page', //页码的参数名称,默认:page
    53. limitName: 'rows' //每页数据量的参数名,默认:limit
    54. },
    55. done: function (res, curr, count) {
    56. console.log(res);
    57. }
    58. });
    59. //工具条事件
    60. table.on('tool(tb)', function(obj){ //注:tool 是工具条事件名,test 是 table 原始容器的属性 lay-filter="对应的值"
    61. row = obj.data; //获得当前行数据
    62. var layEvent = obj.event; //获得 lay-event 对应的值(也可以是表头的 event 参数对应的值)
    63. var tr = obj.tr; //获得当前行 tr 的 DOM 对象(如果有的话)
    64. console.log(row);
    65. if(layEvent === 'edit'){ //审批
    66. openLayer(row.id);
    67. } else {
    68. }
    69. });
    70. }
    71. // 打开审批页面
    72. function openLayer(id){
    73. layer.open({
    74. type: 2, //layer提供了5种层类型。可传入的值有:0(信息框,默认)1(页面层)2(iframe层)3(加载层)4(tips层)
    75. title: '审批', //对话框标题
    76. area: ['600px', '500px'], //宽高
    77. skin: 'layui-layer-rim', //样式类名
    78. content: $("#ctx").val()+'/jsp/meeting/addMeetingAudit.jsp', //弹出内容。可以传入普通的html内容,还可以指定DOM,更可以随着type的不同而不同
    79. btn:['审批通过','审批驳回'],
    80. yes:function(index,layero){
    81. //layer.msg('保存');
    82. //调用子页面中提供的getData方法,快速获取子页面的form表单数据
    83. let data= $(layero).find("iframe")[0].contentWindow.save();
    84. data['meetingId']=id;
    85. data['auditor']=$('#auditor').val();
    86. addMeetingAudit(data);
    87. },
    88. btn2:function(){
    89. let data={};
    90. data['sign']=null;
    91. data['meetingId']=id;
    92. data['auditor']=$('#auditor').val();
    93. addMeetingAudit(data);
    94. return false;
    95. }
    96. });
    97. }
    98. // 添加审批意见
    99. function addMeetingAudit(params){
    100. params['methodName']="add";
    101. console.log(params);
    102. $.post($("#ctx").val()+'/audit.action',params,function(rs){
    103. if(rs.success){
    104. layer.closeAll();
    105. query();
    106. }else{
    107. layer.msg(rs.msg,{icon:5},function(){});
    108. }
    109. },'json');
    110. }

    界面效果: 

     

     

    二、审批签字 

    1、审批签字我们需要借助一个在线绘图工具的插件,改造后引入我们的项目中。

     

     接下来导入我们的项目中

     界面效果:

    2、 审批签字界面是会议审批界面的子界面,在会议审批的js方法中我们使用了find("iframe")的方法来调用子页面的data方法,快速获取子页面的表单数据

    父页面js

     

    子页面js

     3、批处理:将审批人的意见数据入库并且修改会议状态

    如图所示:

    1、我们根据审批人点击通过按钮或者驳回按钮来判断 该会议为待开状态(也就是已通过)或者驳回状态。不论审批人点击的是哪个按钮我们都需要将该会议的最新状态添加进数据库中,因为这时该会议已经成为被审批过的会议了。当我们再次查询未审批会议时就不应该出现。

    2、只有当会议状态为通过时才需要进行签字,驳回则无需签字。所以我们可以根据审批人是否签字来判断该会议的最新状态为待开或者驳回状态。同时我们也可以查看该会议入库时Sign字段也就是签字图片存放的路径是否为空,为空则是驳回状态,非空则是待开状态。

    3、批处理:当审批人审批该会议后,该会议作为一条被审批过的会议添加进数据库中与修改会议状态的两条sql语句应当同时进行。就比如说A给B转账1000元,A的账户中减少1000元,B的账户中增加1000元,这两个事务应该同时进行,并且业务处理结果保持一致,要么转账成功A少B加,要么转账失败A,B账户余额都不发生改变。同理,如果某一条会议作为待开状态被加入数据库中,那么同时修改会议状态的这个事务处理该会议也同样为待开状态。

    1. //批处理
    2. public int add(MeetingAudit audit) {
    3. // 将审批人的意见数据入库t_oa_meeting_audit
    4. String sql1 = "insert into t_oa_meeting_audit(meetingid,auditor,sign)"
    5. + "values("+audit.getMeetingId()+","+audit.getAuditor()+",'"+audit.getSign()+"')";
    6. //state状态有两种 审批通过即待开4,审批驳回即驳回状态3
    7. //判断是否签字
    8. boolean flag = StringUtils.isNotBlank(audit.getSign());
    9. int state = flag?4:3;
    10. //修改会议的状态 t_oa_meeting_info
    11. String sql2 = " update t_oa_meeting_info set state=? where id ="+audit.getMeetingId();
    12. //同时执行多个sql语句,其目的在于事务的一致性,要么业务处理同时成功,要么同时失败
    13. return super.executeUpdateBatch(new String[] {sql1,sql2});
    14. }

     4、签字图片裁剪

    考虑到用户体验感以及美观度,在审批人签完字图片生成的过程中进行裁剪,使签字图片更美观,提升用户体验感。

    1. */
    2. if(StringUtils.isNotBlank(audit.getSign())) {
    3. String serverPathSign=PropertiesUtil.getValue("serverPathSign");
    4. String dirPath=PropertiesUtil.getValue("dirPath");
    5. //原图 图片名
    6. String fileName1=UUID.randomUUID().toString().replace("-", "")+".png";
    7. //裁剪后的图片名
    8. String fileName2 = UUID.randomUUID().toString().replace("-", "")+".png";
    9. //生成签字图片
    10. Base64ImageUtils.GenerateImage(audit.getSign().replace("data:image/png;base64,",""),dirPath+fileName1 );
    11. //将原图进行裁剪
    12. ImageUtils.shearImg(dirPath+fileName1 , dirPath+fileName2);
    13. //删除掉原图
    14. File file = new File(dirPath+fileName1);
    15. if(file.exists())
    16. file.delete();
    17. audit.setSign(serverPathSign+fileName2);
    18. }

  • 相关阅读:
    暑假加餐|有钱人和你想的不一样(第8天)+多目标金鹰优化算法(Matlab代码实现)
    Python 代码混淆工具概述
    导入bertopic报错问题:
    专题 | 同步 异步
    Java并发编程—线程详解
    【建议收藏】ヾ(^▽^*)))全网最全输入输出格式符整理
    【路径规划】基于Dijkstra实现机器人二维路径规划附完整matlab代码
    【博客447】使用open vswitch构建虚拟网络
    Litestar 4D:道路照明
    T1094 与7无关的数(信息学一本通C++)
  • 原文地址:https://blog.csdn.net/m0_67477525/article/details/126005317