讲解请假审批UI原型

webapp下新建audit.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <!-- 引入样式 -->
    <link rel="stylesheet" type="text/css" href="/assets/element-plus/index.css">
    <!-- 引入组件库 -->
    <script src="/assets/vue/vue.global.js"></script>
    <script src="/assets/element-plus/index.full.js"></script>
    <script src="/assets/axios/axios.js"></script>
    <style >
 
        .info .el-col,.info .el-select ,.info .el-input{
            padding-top: 5px;
            padding-bottom: 5px;
        }
    </style>
</head>
<body>
<div id="app">
    <h2>请假审批</h2>
    <el-table
            ref="singleTable"
            :data="tableData"
            highlight-current-row
            @current-change="handleCurrentChange"
            style="width: 100%">
        <el-table-column
                type="index"
                width="50">
        </el-table-column>
        <el-table-column
                property="ctime"
                label="申请时间"
                width="180">
        </el-table-column>
        <el-table-column
                property="ftype"
                label="类型"
                width="120">
        </el-table-column>
        <el-table-column
                property="department_name"
                label="部门"
                width="120">
        </el-table-column>
        <el-table-column
                property="name"
                label="员工"
                width="120">
        </el-table-column>
        <el-table-column
                property="stime"
                label="起始时间"
                width="180">
        </el-table-column>
        <el-table-column
                property="etime"
                label="结束时间"
                width="180">
        </el-table-column>
        <el-table-column
                property="reason"
                label="请假原因">
        </el-table-column>
    </el-table>
 
    <el-dialog title="请假审批" v-model="dialogFormVisible" width="500px" center>
        <el-descriptions  :column="2" border>
            <el-descriptions-item label="部门">{{currentRow.department_name}}</el-descriptions-item>
            <el-descriptions-item label="姓名">{{currentRow.name}}</el-descriptions-item>
            <el-descriptions-item label="起始时间" >{{currentRow.stime}}</el-descriptions-item>
            <el-descriptions-item label="结束时间" >{{currentRow.etime}}</el-descriptions-item>
            <el-descriptions-item label="请假原因" :span="2">
                {{currentRow.reason}}
            </el-descriptions-item>
        </el-descriptions>
 
 
        <div class="info" >
            <el-form :model="form" ref="auditForm">
                <el-select v-model="form.result" placeholder="是否同意" style="width: 100%">
                    <el-option label="同意" value="approved"></el-option>
                    <el-option label="驳回" value="refused"></el-option>
                </el-select>
                <el-input v-model="form.reason" placeholder="请输入审批意见" autocomplete="off"></el-input>
            </el-form>
            <span class="dialog-footer">
              <el-button type="primary" v-on:click="onSubmit('auditForm')" style="width: 100%">确认提交</el-button>
            </span>
        </div>
    </el-dialog>
</div>
 
<script>
    function formatDate(time){
        var newDate = new Date(time);
        return newDate.getFullYear() + "-" +
            (newDate.getMonth() + 1) + "-" + newDate.getDate()
            + " " + newDate.getHours() + "时";
    }
 
    var Main = {
        data() {
            return {
                dialogFormVisible: false,
                form: {
                    result:"approved",
                    reason:""
                },
                formLabelWidth: '120px',
                tableData: [{
                    ctime:"2021-5-29 18时",
                    ftype:"事假",
                    stime:"2021-5-31 0时",
                    etime:"2021-6-3 0时",
                    department_name:"研发部",
                    name:"王美美",
                    reason:"测试数据"
                }],
                currentRow: null
            }
        }
        ,methods: {
            handleCurrentChange(val) {
                this.currentRow = val;
                console.info(val);
                this.dialogFormVisible = true;
            }
        }
    };
    const app = Vue.createApp(Main);
    app.use(ElementPlus);
    app.mount("#app")
</script>
 
</body>
</html>

tableData是我们添加的静态数据,el-table是element-plus封装的 this.dialogFormVisible = true;表示是否弹出对话框

开发待审批表单Model层

将上面的静态数据改为动态

打开leave_form.xml文件,新增方法

<select id="selectByParams" parameterType="java.util.Map" resultType="java.util.LinkedHashMap">
        select f.*,e.name , d.*
        from
            adm_leave_form f,adm_process_flow pf , adm_employee e , adm_department d
        where
            f.form_id = pf.form_id
            and f.employee_id = e.employee_id
            and e.department_id = d.department_id
            <if test="pf_state != null">
                and pf.state = #{pf_state}
            </if>
            <if test="pf_operator_id != null">
                and pf.operator_id = #{pf_operator_id}
            </if>
    </select>

请假单表和员工表是主外键关联,和流程表也是主外键关联,与部门表也是主外键关联,相当于多表查询,无法使用一个实体类存储下来,所以使用集合LinkedHashMap,采用键值对方式存储 引入流程表是因为我们需要通过pf_state、pf_operator_id才能拿到当前登陆的经理待审批请假单数据 LeaveFormMapper.java新增接口拓展

/*
        map.put("pf_operator_id" , xxxx)
        map.put("pf_state" , xxxx)
     */
    public List<Map> selectByParams(@Param("pf_state") String pfState
            , @Param("pf_operator_id") Long pfOperatorId);

使用@Param简化put的操作 进行单元测试LeaveFormMapperTest.java

@Test
    public void testSelectByParams(){
        MybatisUtils.executeQuery(sqlSession -> {
            LeaveFormMapper mapper = sqlSession.getMapper(LeaveFormMapper.class);
            List<Map> list = mapper.selectByParams("process", 2l);
            System.out.println(list);
            return list;
        });
    }

向上推进,在LeaveFormService.java中添加

/**
     * 获取指定任务状态及指定经办人对应的请假单列表
     * @param pfState ProcessFlow任务状态
     * @param operatorId 经办人编号
     * @return 请假单及相关数据列表
     */
    public List<Map> getLeaveFormList(String pfState, Long operatorId){
        return (List<Map>) MybatisUtils.executeQuery(sqlSession -> {
            LeaveFormMapper mapper = sqlSession.getMapper(LeaveFormMapper.class);
            List<Map> maps = mapper.selectByParams(pfState, operatorId);
            return maps;
        });
    }

实现查询待审批请假单

上一节开发了请假单获取工作getLeaveFormList(),向上开发controller层 在LeaveFormServlet.java中添加与查询有关方法,像create方法一样添加list查询方法

//    URI带有list时执行
    private void list(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String employeeId = request.getParameter("eid");
        ResponseUtils resp = null;
        try {
            List<Map> formList = leaveFormService.getLeaveFormList("process", Long.parseLong(employeeId));
            resp = new ResponseUtils().put("list", formList);
        } catch (Exception e) {
            e.printStackTrace();
            resp = new ResponseUtils(e.getClass().getSimpleName(), e.getMessage());
        }
        response.getWriter().println(resp.toJsonString());
    }

登陆王美美账号t6,密码test,申请原因为事假,申请成功后打开部门经理齐紫陌账号localhost/api/leave/list?eid=2 得到请假单

查看数据库,是同一部门

下一步进行界面绑定输出,在audit.html下进行绑定

,mounted(){
            const objApp = this;
            const $message = this.$message;
            axios.get("/api/leave/list?eid=" + sessionStorage.eid)
                .then(function(response){
                    const json = response.data;
                    if(json.code == '0'){
                        objApp.tableData.splice(0, objApp.tableData.length);
                        const formList = json.data.list;
                        formList.forEach(function(item){
                            switch (item.form_type){
                                case 1:
                                    item.ftype = "事假";
                                    break;
                                case 2:
                                    item.ftype = "病假";
                                    break;
                                case 3:
                                    item.ftype = "工伤假";
                                    break;
                                case 4:
                                    item.ftype = "婚假";
                                    break;
                                case 5:
                                    item.ftype = "产假";
                                    break;
                                case 6:
                                    item.ftype = "丧假";
                                    break;
                            }
                            item.stime = formatDate(item.start_time);
                            item.etime = formatDate(item.end_time);
                            item.ctime = formatDate(item.create_time);
                            objApp.tableData.push(item);
                        })
                    }else{
                        $message.error({message:json.message,offset:100})
                    }
                })
        }

objApp.tableData.splice(0, objApp.tableData.length);表示删除掉我们的原始静态数据 objApp.tableData.push(item);将数据重新压入到tableData中 switch是为了将数字类型转化为对应的文本 然后通过调用audit.html里函数完成日期格式化

function formatDate(time){
        var newDate = new Date(time);
        return newDate.getFullYear() + "-" +
            (newDate.getMonth() + 1) + "-" + newDate.getDate()
            + " " + newDate.getHours() + "时";
    }

进行格式化

item.stime = formatDate(item.start_time);
item.etime = formatDate(item.end_time);
item.ctime = formatDate(item.create_time);

输入齐紫陌账号t7,密码test登陆 然后修改url为http://localhost/audit.html回车访问

可以看到,上面我们添加的申请原因为事假的数据显示出来了,而静态数据没有显示 点击数据,可以看到数据也回填到弹出窗口上

开发请假审批Mapper层

审核过程就是一个数据更新过程,核心是update语句

在我们申请请假单时,审核流程已经根据时间是否大于72小时创建好流程表了,接下来的审批只是在这流程表上进行更新操作而已

打开leave_form.xml,添加语句,根据id号更新请假单

<update id="update" parameterType="com.imooc.oa.entity.LeaveForm">
        UPDATE adm_leave_form SET employee_id = #{employeeId} , form_type = #{formType}, start_time = #{startTime}, end_time = #{endTime}, reason = #{reason}, state = #{state} ,create_time = #{createTime} WHERE form_id = #{formId}
    </update>

向上推到接口LeaveFormMapper.java中,继续添加方法语句

public void update(LeaveForm form);
 
//补充
public LeaveForm selectById(Long formId);

处理流程process_flow..xml也要添加update

<update id="update" parameterType="com.imooc.oa.entity.ProcessFlow">
        UPDATE adm_process_flow SET form_id = #{formId}, operator_id = #{operatorId}, action = #{action}, result = #{result}, reason = #{reason}, create_time = #{createTime}, audit_time = #{auditTime}, order_no = #{orderNo}, state = #{state}, is_last = #{isLast}
        WHERE process_id = #{processId}
    </update>

打开ProcessFlowMapper.java添加对应方法

public void update(ProcessFlow processFlow);
//补充
public List selectByFormId(Long formId);

为了实现请假单审批,还需要添加查询方法 leave_form.xml中

<select id="selectById" parameterType="Long" resultType="com.imooc.oa.entity.LeaveForm">
       select * from adm_leave_form where form_id = #{value}
    </select>

process_flow.xml中

<select id="selectByFormId" parameterType="Long" resultType="com.imooc.oa.entity.ProcessFlow">
        select * from adm_process_flow where form_id = #{value} order by order_no
    </select>

剩下mapper接口代码在上面给出代码进行补充

开发请假审批Service层

LeaveFormService.java添加

/**
     * 审核请假单
     * @param formId 表单编号
     * @param operatorId 经办人(当前登录员工)
     * @param result 审批结果
     * @param reason 审批意见
     */
    public void audit(Long formId , Long operatorId , String result , String reason){
        MybatisUtils.executeUpdate(sqlSession -> {
            //无论同意/驳回,当前任务状态变更为complete
            ProcessFlowMapper processFlowMapper = sqlSession.getMapper(ProcessFlowMapper.class);
            List<ProcessFlow> flowList = processFlowMapper.selectByFormId(formId);
            if(flowList.size() == 0){
                throw new LeaveFormException("无效的审批流程");
            }
            //获取当前任务ProcessFlow对象
            List<ProcessFlow> processList = flowList.stream().filter(p -> p.getOperatorId() == operatorId && p.getState().equals("process")).collect(Collectors.toList());
            ProcessFlow process = null;
            if(processList.size() == 0){
                throw new LeaveFormException("未找到待处理任务节点");
            }else{
                process = processList.get(0);
                process.setState("complete");
                process.setResult(result);
                process.setReason(reason);
                process.setAuditTime(new Date());
                processFlowMapper.update(process);
            }
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd-HH时");
            LeaveFormMapper leaveFormMapper = sqlSession.getMapper(LeaveFormMapper.class);
            NoticeMapper noticeMapper = sqlSession.getMapper(NoticeMapper.class);
            LeaveForm form = leaveFormMapper.selectById(formId);
            Employee operator = employeeService.selectById(operatorId);
            Employee employee = employeeService.selectById(form.getEmployeeId());
            //如果当前任务是最后一个节点,代表流程结束,更新请假单状态为对应的approved/refused
            if(process.getIsLast() == 1){
                form.setState(result); //approved / refused
                leaveFormMapper.update(form);
                String strResult = null;
                if(result.equals("approved")){
                    strResult = "批准";
                }else if(result.equals("refused")){
                    strResult = "驳回";
                }
 
            }else{
                ////readyList包含所有后续任务节点
                List<ProcessFlow> readyList = flowList.stream().filter(p -> p.getState().equals("ready")).collect(Collectors.toList());
                //如果当前任务不是最后一个节点且审批通过,那下一个节点的状态从ready变为process
                if(result.equals("approved")){
                    ProcessFlow readyProcess = readyList.get(0);
                    readyProcess.setState("process");
                    processFlowMapper.update(readyProcess);
 
 
                }else if(result.equals("refused")){
                    //如果当前任务不是最后一个节点且审批驳回,则后续所有任务状态变为cancel,请假单状态变为refused
                    for(ProcessFlow p:readyList){
                        p.setState("cancel");
                        processFlowMapper.update(p);
                    }
                    form.setState("refused");
                    leaveFormMapper.update(form);
 
                }
 
            }
            return null;
        });
    }

如果表单没有对应流程数据时,processList.size() == 0需要我们自定义异常,中断流程,在service下的exception文件下新建LeaveFormException.java

package com.imooc.oa.service.exception;
 
public class LeaveFormException extends RuntimeException{
    public LeaveFormException(String message){
        super(message);
    }
}

进行测试

/**
     * 情况1: 72小时以上请假,部门经理同意,总经理同意,流程结束
     * @throws ParseException
     */
    @Test
    public void audit1() throws ParseException {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHH");
        LeaveForm form = new LeaveForm();
        form.setEmployeeId(3l);
        form.setStartTime(sdf.parse("2020032608"));
        form.setEndTime(sdf.parse("2020040118"));
        form.setFormType(1);
        form.setReason("研发部员工王美美请假单(72小时以上)");
        form.setCreateTime(new Date());
        LeaveForm savedForm = leaveFormService.createLeaveForm(form);
        System.out.println(savedForm.getFormId());
        leaveFormService.audit(savedForm.getFormId(),2l,"approved","部门经理同意");
        leaveFormService.audit(savedForm.getFormId(),1l,"approved","总经理同意");
    }
 
    /**
     * 情况2: 72小时以上请假,部门经理拒绝,流程结束
     * @throws ParseException
     */
    @Test
    public void audit2() throws ParseException {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHH");
        LeaveForm form = new LeaveForm();
        form.setEmployeeId(3l);
        form.setStartTime(sdf.parse("2020032608"));
        form.setEndTime(sdf.parse("2020040118"));
        form.setFormType(1);
        form.setReason("研发部员工王美美请假单(72小时以上)");
        form.setCreateTime(new Date());
        LeaveForm savedForm = leaveFormService.createLeaveForm(form);
        leaveFormService.audit(savedForm.getFormId(),2l,"refused","部门经理拒绝");
    }
 
    /**
     * 情况3: 72小时以内请假,部门经理同意,流程结束
     * @throws ParseException
     */
    @Test
    public void audit3() throws ParseException {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHH");
        LeaveForm form = new LeaveForm();
        form.setEmployeeId(3l);
        form.setStartTime(sdf.parse("2020032608"));
        form.setEndTime(sdf.parse("2020032718"));
        form.setFormType(1);
        form.setReason("研发部员工王美美请假单(72小时以内)");
        form.setCreateTime(new Date());
        LeaveForm savedForm = leaveFormService.createLeaveForm(form);
        System.out.println(savedForm.getFormId());
        leaveFormService.audit(savedForm.getFormId(),2l,"approved","部门经理同意");
    }

情况一运行 查看请假单与请假状态

查看审批流程

三种情况运行请假单结果

以及审批流程

实现请假审批功能

完成sevlet层和view层 LeaveFormServlet.java下新增方法

//    URI带有audit时执行
    private void audit(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String formId = request.getParameter("formId");
        String result = request.getParameter("result");
        String reason = request.getParameter("reason");
        String eid = request.getParameter("eid");
        ResponseUtils resp = null;
        try {
            leaveFormService.audit(Long.parseLong(formId), Long.parseLong(eid), result, reason);
            resp = new ResponseUtils();
        }catch (Exception e){
            e.printStackTrace();
            resp = new ResponseUtils(e.getClass().getSimpleName(), e.getMessage());
        }
        response.getWriter().println(resp.toJsonString());
    }

继续向上推进,打开audit.html页面 实现提交表单按钮,在method里面书写

,onSubmit(formName){
                const objApp = this;
                this.$refs[formName].validate(function(valid){
                    if(valid){
                        const params = new URLSearchParams();
                        params.append("formId", objApp.currentRow.form_id);
                        params.append("result", objApp.form.result);
                        params.append("reason", objApp.form.reason);
                        params.append("eid", sessionStorage.eid);
                        axios.post("/api/leave/audit" , params)
                            .then(function(response){
                                const json = response.data;
                                console.info(json);
                                if(json.code=="0"){
                                    objApp.$alert("请假已审批完毕" , {
                                        callback:function(){
                                            window.location.href = "/notice.html";
                                        }
                                    })
                                }else{
                                    objApp.$message.error({message:json.message,offset:100})
                                }
                            })
                    }
                })
            }

运行tomcat,登陆王美美账号创建大于72小时的请假单 然后登陆部门经理齐紫陌账号进行审批

再登陆总经理账号,进行审批即可

查看数据库流程信息

开发系统通知Service功能

我们已将开发了NoticeMapper,系统通知主要用来创建请假单后和审核后

在LeaveFormService,java中,添加创建请假单通知 一条用来通知自己创建成功,一条通知别人等待审核

package com.imooc.oa.service;
 
import com.imooc.oa.entity.Employee;
import com.imooc.oa.entity.LeaveForm;
import com.imooc.oa.entity.Notice;
import com.imooc.oa.entity.ProcessFlow;
import com.imooc.oa.mapper.EmployeeMapper;
import com.imooc.oa.mapper.LeaveFormMapper;
import com.imooc.oa.mapper.NoticeMapper;
import com.imooc.oa.mapper.ProcessFlowMapper;
import com.imooc.oa.service.exception.LeaveFormException;
import com.imooc.oa.utils.MybatisUtils;
 
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
 
public class LeaveFormService {
    private EmployeeService employeeService = new EmployeeService();
    /**
     * 创建请假单
     * @param form 前端输入的请假单数据
     * @return 持久化后的请假单对象
     */
    public LeaveForm createLeaveForm(LeaveForm form){
        LeaveForm f = (LeaveForm) MybatisUtils.executeUpdate(sqlSession -> {
            //1.持久化form表单数据,8级以下员工表单状态为processing,8级(总经理)状态为approved
            EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
            Employee employee = employeeMapper.selectById(form.getEmployeeId());
            if(employee.getLevel() == 8){
                form.setState("approved");
            }else{
                form.setState("processing");
            }
            LeaveFormMapper leaveFormMapper = sqlSession.getMapper(LeaveFormMapper.class);
            leaveFormMapper.insert(form);
            NoticeMapper noticeMapper = sqlSession.getMapper(NoticeMapper.class);
            //2.增加第一条流程数据,说明表单已提交,状态为complete
            ProcessFlowMapper processFlowMapper = sqlSession.getMapper(ProcessFlowMapper.class);
            ProcessFlow flow1 = new ProcessFlow();
            flow1.setFormId(form.getFormId());
            flow1.setOperatorId(employee.getEmployeeId());
            flow1.setAction("apply");
            flow1.setCreateTime(new Date());
            flow1.setOrderNo(1);
            flow1.setState("complete");
            flow1.setIsLast(0);
            processFlowMapper.insert(flow1);
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd-HH时");
            //3.分情况创建其余流程数据
            //3.1 7级以下员工,生成部门经理审批任务,请假时间大于等于72小时,还需生成总经理审批任务
            if(employee.getLevel() < 7){
                Employee dmanager = employeeService.selectLeader(employee.getEmployeeId());
                ProcessFlow flow2 = new ProcessFlow();
                flow2.setFormId(form.getFormId());
                flow2.setOperatorId(dmanager.getEmployeeId());
                flow2.setAction("audit");
                flow2.setCreateTime(new Date());
                flow2.setOrderNo(2);
                flow2.setState("process");
                long diff = form.getEndTime().getTime() - form.getStartTime().getTime();
                float hours = diff/(1000*60*60) * 1f;
                if(hours >= 72){
                    flow2.setIsLast(0);
                    processFlowMapper.insert(flow2);
                    Employee manager = employeeService.selectLeader(dmanager.getEmployeeId());
                    ProcessFlow flow3 = new ProcessFlow();
                    flow3.setFormId(form.getFormId());
                    flow3.setOperatorId(manager.getEmployeeId());
                    flow3.setAction("audit");
                    flow3.setCreateTime(new Date());
                    flow3.setState("ready");
                    flow3.setOrderNo(3);
                    flow3.setIsLast(1);
                    processFlowMapper.insert(flow3);
                }else {
                    flow2.setIsLast(1);
                    processFlowMapper.insert(flow2);
                }
                String notice1 = String.format("您的请假申请[%s-%s]已提交,请等待上级审批.", sdf.format(form.getStartTime()), sdf.format(form.getEndTime()));
                noticeMapper.insert(new Notice(employee.getEmployeeId(),notice1));
                String notice2 = String.format("%s-%s提起请假申请[%s-%s],请尽快审批", employee.getTitle(), employee.getName(), sdf.format(form.getStartTime()), sdf.format(form.getEndTime()));
                noticeMapper.insert(new Notice(dmanager.getEmployeeId(),notice2));
            }else if(employee.getLevel() == 7){
                //3.2 7级员工,仅生成总经理审批任务
                Employee manager = employeeService.selectLeader(employee.getEmployeeId());
                ProcessFlow flow2 = new ProcessFlow();
                flow2.setFormId(form.getFormId());
                flow2.setOperatorId(manager.getEmployeeId());
                flow2.setAction("audit");
                flow2.setCreateTime(new Date());
                flow2.setState("process");
                flow2.setOrderNo(2);
                flow2.setIsLast(1);
                processFlowMapper.insert(flow2);
                //请假单已提交消息
                String notice1 = String.format("您的请假申请[%s-%s]已提交,请等待上级审批."
                        , sdf.format(form.getStartTime()), sdf.format(form.getEndTime()));
                noticeMapper.insert(new Notice(employee.getEmployeeId(),notice1));
 
                //通知总经理审批消息
                String notice2 = String.format("%s-%s提起请假申请[%s-%s],请尽快审批",
                        employee.getTitle() , employee.getName() ,sdf.format(form.getStartTime()),sdf.format(form.getEndTime()));
                noticeMapper.insert(new Notice(manager.getEmployeeId(),notice2));
            }else if(employee.getLevel() == 8){
                //3.3 8级员工,生成总经理审批任务,系统自动通过
                ProcessFlow flow2 = new ProcessFlow();
                flow2.setFormId(form.getFormId());
                flow2.setOperatorId(employee.getEmployeeId());
                flow2.setAction("audit");
                flow2.setResult("approved");
                flow2.setReason("自动通过");
                flow2.setCreateTime(new Date());
                flow2.setAuditTime(new Date());
                flow2.setState("complete");
                flow2.setOrderNo(2);
                flow2.setIsLast(1);
                processFlowMapper.insert(flow2);
                String noticeContent = String.format("您的请假申请[%s-%s]系统已自动批准通过." ,
                        sdf.format(form.getStartTime()) , sdf.format(form.getEndTime()));
                noticeMapper.insert(new Notice(employee.getEmployeeId(),noticeContent));
            }
 
 
            return form;
        });
        return f;
    }
    /**
     * 获取指定任务状态及指定经办人对应的请假单列表
     * @param pfState ProcessFlow任务状态
     * @param operatorId 经办人编号
     * @return 请假单及相关数据列表
     */
    public List<Map> getLeaveFormList(String pfState, Long operatorId){
        return (List<Map>) MybatisUtils.executeQuery(sqlSession -> {
            LeaveFormMapper mapper = sqlSession.getMapper(LeaveFormMapper.class);
            List<Map> maps = mapper.selectByParams(pfState, operatorId);
            return maps;
        });
    }
    /**
     * 审核请假单
     * @param formId 表单编号
     * @param operatorId 经办人(当前登录员工)
     * @param result 审批结果
     * @param reason 审批意见
     */
    public void audit(Long formId , Long operatorId , String result , String reason){
        MybatisUtils.executeUpdate(sqlSession -> {
            //无论同意/驳回,当前任务状态变更为complete
            ProcessFlowMapper processFlowMapper = sqlSession.getMapper(ProcessFlowMapper.class);
            List<ProcessFlow> flowList = processFlowMapper.selectByFormId(formId);
            if(flowList.size() == 0){
                throw new LeaveFormException("无效的审批流程");
            }
            //获取当前任务ProcessFlow对象
            List<ProcessFlow> processList = flowList.stream().filter(p -> p.getOperatorId() == operatorId && p.getState().equals("process")).collect(Collectors.toList());
            ProcessFlow process = null;
            if(processList.size() == 0){
                throw new LeaveFormException("未找到待处理任务节点");
            }else{
                process = processList.get(0);
                process.setState("complete");
                process.setResult(result);
                process.setReason(reason);
                process.setAuditTime(new Date());
                processFlowMapper.update(process);
            }
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd-HH时");
            LeaveFormMapper leaveFormMapper = sqlSession.getMapper(LeaveFormMapper.class);
            NoticeMapper noticeMapper = sqlSession.getMapper(NoticeMapper.class);
            LeaveForm form = leaveFormMapper.selectById(formId);
            Employee operator = employeeService.selectById(operatorId);
            Employee employee = employeeService.selectById(form.getEmployeeId());
            //如果当前任务是最后一个节点,代表流程结束,更新请假单状态为对应的approved/refused
            if(process.getIsLast() == 1){
                form.setState(result); //approved / refused
                leaveFormMapper.update(form);
                String strResult = null;
                if(result.equals("approved")){
                    strResult = "批准";
                }else if(result.equals("refused")){
                    strResult = "驳回";
                }
                String notice1 = String.format("您的请假申请[%s-%s]%s%s已%s,审批意见:%s,审批流程已结束"
                        ,sdf.format(form.getStartTime()),sdf.format(form.getEndTime())
                        ,operator.getTitle(),operator.getName() ,strResult,reason
                );
                noticeMapper.insert(new Notice(form.getEmployeeId(), notice1));
 
                String notice2 = String.format("%s-%s提起请假申请[%s-%s]您已%s,审批意见:%s,审批流程已结束" ,
                        employee.getTitle() , employee.getName() , sdf.format( form.getStartTime()) , sdf.format(form.getEndTime()),
                        strResult , reason);//发给审批人的通知
                noticeMapper.insert(new Notice(operator.getEmployeeId(),notice2));
 
            }else{
                ////readyList包含所有后续任务节点
                List<ProcessFlow> readyList = flowList.stream().filter(p -> p.getState().equals("ready")).collect(Collectors.toList());
                //如果当前任务不是最后一个节点且审批通过,那下一个节点的状态从ready变为process
                if(result.equals("approved")){
                    ProcessFlow readyProcess = readyList.get(0);
                    readyProcess.setState("process");
                    processFlowMapper.update(readyProcess);
                    //消息1: 通知表单提交人,部门经理已经审批通过,交由上级继续审批
                    String notice1 = String.format("您的请假申请[%s-%s]%s%s已批准,审批意见:%s ,请继续等待上级审批" ,
                            sdf.format(form.getStartTime()) , sdf.format(form.getEndTime()),
                            operator.getTitle() , operator.getName(),reason);
                    noticeMapper.insert(new Notice(form.getEmployeeId(),notice1));
 
                    //消息2: 通知总经理有新的审批任务
                    String notice2 = String.format("%s-%s提起请假申请[%s-%s],请尽快审批" ,
                            employee.getTitle() , employee.getName() , sdf.format( form.getStartTime()) , sdf.format(form.getEndTime()));
                    noticeMapper.insert(new Notice(readyProcess.getOperatorId(),notice2));
 
                    //消息3: 通知部门经理(当前经办人),员工的申请单你已批准,交由上级继续审批
                    String notice3 = String.format("%s-%s提起请假申请[%s-%s]您已批准,审批意见:%s,申请转至上级领导继续审批" ,
                            employee.getTitle() , employee.getName() , sdf.format( form.getStartTime()) , sdf.format(form.getEndTime()), reason);
                    noticeMapper.insert(new Notice(operator.getEmployeeId(),notice3));
 
                }else if(result.equals("refused")){
                    //如果当前任务不是最后一个节点且审批驳回,则后续所有任务状态变为cancel,请假单状态变为refused
                    for(ProcessFlow p:readyList){
                        p.setState("cancel");
                        processFlowMapper.update(p);
                    }
                    form.setState("refused");
                    leaveFormMapper.update(form);
                    //消息1: 通知申请人表单已被驳回
                    String notice1 = String.format("您的请假申请[%s-%s]%s%s已驳回,审批意见:%s,审批流程已结束" ,
                            sdf.format(form.getStartTime()) , sdf.format(form.getEndTime()),
                            operator.getTitle() , operator.getName(),reason);
                    noticeMapper.insert(new Notice(form.getEmployeeId(),notice1));
 
                    //消息2: 通知经办人表单"您已驳回"
                    String notice2 = String.format("%s-%s提起请假申请[%s-%s]您已驳回,审批意见:%s,审批流程已结束" ,
                            employee.getTitle() , employee.getName() , sdf.format( form.getStartTime()) , sdf.format(form.getEndTime()), reason);
                    noticeMapper.insert(new Notice(operator.getEmployeeId(),notice2));
                }
 
            }
            return null;
        });
    }
}

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd-HH时");表示对时间进行格式化

然后运行三种情况的测试用例

实现系统通知功能

打开接口NoticeMapper.java,新增方法,根据接收人不同显示通知

public List<Notice> selectByReceiverId(Long receiverId);

打开notice.xml

<select id="selectByReceiverId" parameterType="Long" resultType="com.imooc.oa.entity.Notice">
        select * from sys_notice where receiver_id= #{value} order by create_time desc limit 0,30
    </select>

显示30条即可,没必要全部显示出来,消息太多 向上推进,新建NoticeService类

package com.imooc.oa.service;
 
import com.imooc.oa.entity.Notice;
import com.imooc.oa.mapper.NoticeMapper;
import com.imooc.oa.utils.MybatisUtils;
 
import java.util.List;
 
public class NoticeService {
    public List<Notice> getNoticeList(Long receiverId){
        return (List)MybatisUtils.executeQuery(sqlSession -> {
            NoticeMapper mapper = sqlSession.getMapper(NoticeMapper.class);
            return mapper.selectByReceiverId(receiverId);
        });
    }
}

继续向上推进,新建NoticeServlet.java文件

package com.imooc.oa.controller;
 
import com.imooc.oa.entity.Notice;
import com.imooc.oa.service.NoticeService;
import com.imooc.oa.utils.ResponseUtils;
 
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
 
@WebServlet("/api/notice/list")
public class NoticeServlet extends HttpServlet {
    private NoticeService noticeService = new NoticeService();
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String employeeId = request.getParameter("eid");
        ResponseUtils resp;
        try {
            List<Notice> noticeList = noticeService.getNoticeList(Long.parseLong(employeeId));
            resp = new ResponseUtils().put("list", noticeList);
        }catch (Exception e){
            e.printStackTrace();
            resp = new ResponseUtils(e.getClass().getSimpleName(), e.getMessage());
        }
        response.setContentType("application/json;charset=utf-8");
        response.getWriter().println(resp.toJsonString());
    }
}

后台工作完成,切换到前台上 webapp下新建notice.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>系统通知</title>
    <!-- 引入样式 -->
    <link rel="stylesheet" type="text/css" href="/assets/element-plus/index.css">
    <!-- 引入组件库 -->
    <script src="/assets/vue/vue.global.js"></script>
    <script src="/assets/element-plus/index.full.js"></script>
    <script src="/assets/axios/axios.js"></script>
 
</head>
<body>
<div id="app">
    <h2>系统通知</h2>
    <el-table
            ref="singleTable"
            :data="tableData"
            highlight-current-row
            style="width: 100%">
        <el-table-column
                property="index"
                label="序号"
                width="50">
        </el-table-column>
        <el-table-column
                property="ctime"
                label="通知时间"
                width="180">
        </el-table-column>
 
        <el-table-column
                property="content"
                label="通知内容">
        </el-table-column>
    </el-table>
 
</div>
 
<script>
 
    var Main = {
        data() {
            return {
                tableData: []
            }
        }
        ,mounted() {
            const objApp = this;
            axios.get("/api/notice/list?eid=" + sessionStorage.eid)
                .then(function (response) {
                    objApp.tableData.splice(0, objApp.tableData.length);
                    response.data.data.list.forEach(function (item,index) {
                        var date = new Date(item.createTime);
                        item.ctime = date.getFullYear() + "-" +
                            (date.getMonth() + 1) + "-" + date.getDate()
                            + " " + date.getHours() + ":" + date.getMinutes() + ":" + date.getSeconds();
                        item.index = index + 1;
                        objApp.tableData.push(item);
                    });
                })
                .catch(function (error) {
                    console.log(error);
                });
        }
    };
    const app = Vue.createApp(Main);
    app.use(ElementPlus);
    app.mount("#app")
</script>
 
</body>
</html>

记得在index.html下将我们原来添加的哔哩哔哩网页改掉,改成通知页面

运行tomcat 记住点击redeploy,而不是restart server,点击redeploy才能将java代码进行编译输出到target文件夹字节流文件夹下

实现系统登陆拦截功能

输入后台的某一网址,可以不登陆而直接进入 在每个页面添加验证,若没登陆过需要先登陆 在webapp的assets的oa目录下新建security.js文件

if(sessionStorage.eid == null || sessionStorage.uid==null){
    window.location.href = "/login.html";
}

然后在每个页面进行引入,记住登陆界面不需要,登陆界面是产生这两个值的地方 每个html界面前进行引入

<script src="/assets/oa/security.js"></script>

关闭浏览器(不是关闭网页,为了清空登陆数据),重新打开输入链接访问

目前还有一个问题,就是设计的接口可以访问到我们的敏感数据,比如localhost/api/leave/list?eid=2 这个现在我们暂时无法解决,需要学习架构层面的东西,后面会有所涉猎,这里暂时不做处理