如何在表单上显示手写签名（项目示例）?
李小翔
2014-01-03 20:12

内容列表

供稿人：许庆洋
维护员工信息时，可以上传由配置 bropen.framework.osm.employee.signature.img.size 制定大小的手写签名图片，签名图片可以通过配置 p:opinion 标签的 signature 属性，显示在意见栏中，或者调用相应的API实现Word或WPS套打。
在下面的项目需求中，需要在表单的上方显示所有审批人的手写签名，并按照一定规则排序：
流程处于起草状态时，不显示签名；
如果流程被退回，则前面的签名都擦除；
按照分组显示意见；
如果任务列表中存在相同任务环节名，并且他们的任务实际办理人相同，且存在已完成的任务，则擦除已完成任务的环节签名；
签名显示分3类：审批环节的签名、审议环节的签名、总裁/董事长的签名：
集团领导的审批任务，放到总裁、董事长审批行；
关键任务列表，按时间顺序排列、根据审批人查重，放到审批行；
其他任务列表，按照级别（员工title）排序、根据审批人查重，放到审议行；
每行最多显示8条签名，签名图片大小为100*40；
当为已办、或代理人打开待办、或者非任一人处理时，签名的标题处显示“代理”二字；
超时自动提交的任务签名处显示“系统提交”；
否决的任务签名处显示“否决”；
决定性意见为“不同意”的任务签名处显示“不同意”；
其他办理完毕的任务签名不存在，签名处则显示“已处理”。
签名栏显示效果如下图所示：
由于这是一个共性的需求，可以自定义一个显示手写签名栏的taglib标签，以下实现代码：
/**
 * 手写签名栏，放在表单的<h1>标签前
 * 例如：<boe:signature bean=”${ xxxxInstance} ” />
 * @attr bean REQUIRED
 */
def signature = { attrs->
   if ( !attrs.bean?.id ) return;
    ProcessInstance flowInst = attrs.bean.workflowInstance;
   def proc = flowInst.processDefinition();
   // 流程处于起草状态时，不显示签名
    if ( flowInst.startNode() == flowInst.nodesName ) return;
   //
    List tasks = [], criticalNodes = [], otherNodes = [], leaderTasks, criticalTasks, otherTasks;
   // 需要显示签名的任务列表：非起草环节的待办
    Task lastDraftTask = flowInst.tasks[0];        // 计算最后一次起草环节的任务：签名从这里开始，一旦退回，签名擦除
    for ( t in flowInst.tasks ) if (t!=lastDraftTask && t.type==Task.TYPE_TODO && t.node==lastDraftTask.node && t.status!=’terminated’) lastDraftTask = t;
   for ( t in flowInst.tasks ) if (t.type==Task.TYPE_TODO && t.createTime>lastDraftTask.createTime && t.status!=’terminated’) tasks << t;
   // 如果退回到环节A，则不显示原来在环节A的任务-被退回来的环节A的任务（不含）
    // 例如路径 A-B-C-A，其中C-A是退回，则不显示前面三个任务（A-B-C）的签名
    tasks = CollectionUtils.sort(tasks, ’asc’, ’id’);
   for ( int i=0; i<tasks.size(); i++ ) { 
       if ( !tasks[i] ) continue;
        String node = tasks[i].node;
       for ( int j=i+1; j<tasks.size(); j++ ) { 
           if ( tasks[j].node==node && tasks[j].sendType==Task.SEND_TYPE_BACK ) { 
                List prevs = tasks[j].prevAllTasks();
               for ( int k=i; k<j; k++ ) if ( prevs.contains(tasks[k]) ) tasks[k] = null;
               break;
           } 
       } 
   } 
   // 如果任务列表中存在相同任务环节名，并且他们的任务实际办理人相同，且存在已完成的任务，则擦除已完成任务的环节签名
    for ( int i=0; i<tasks.size(); i++ ) { 
       if ( tasks[i] == null || tasks[i].completeTime ) continue;
       for ( int j=0; j<i; j++ ) { 
           if ( tasks[j] && tasks[j].completeTime && tasks[j].node==tasks[i].node && tasks[j].actorId==tasks[i].actorId ) tasks[j] = null;
       } 
   } 
    tasks -= null;
   // 集团领导的审批任务，放到总裁、董事长审批行
    Closure uniqueClosure = { it.node + ”@” + it.actorId} ;
    Closure sortSwapper = {  List valList, Date completeTime, Task task->
       return completeTime ?: task.createTime;
   } 
    leaderTasks = CollectionUtils.findAll(tasks, ”actorTitle”, [”总裁”,”董事长”]);    // 后面再查重，下面两段代码还用这个list变量
    // 关键任务列表，按时间顺序排列、根据审批人查重，放到审批行
    for ( n in proc.values() ) if (n instanceof Map && n.node && n.critical ) criticalNodes << n.node;
    criticalTasks = CollectionUtils.sort(
            CollectionUtils.sort(CollectionUtils.findAll(tasks, ”node”, criticalNodes)-leaderTasks,
           ”desc”, ”completeTime”).unique(uniqueClosure), ”asc”, ”completeTime”, sortSwapper);
   // 其他任务列表，按照级别（员工title）排序、根据审批人查重，放到审议行
    for ( n in proc.values() ) if (n instanceof Map && n.node && !n.critical ) otherNodes << n.node;
    otherTasks = CollectionUtils.sort(
            CollectionUtils.findAll(tasks, ”node”, otherNodes)-leaderTasks, ”desc”,
           ”completeTime”, sortSwapper).unique(uniqueClosure);
    otherTasks = otherTasks.sort{ -app.AppUtils.getLevelByTitle(it.actorTitle)} ;
    leaderTasks = leaderTasks.unique(uniqueClosure);    // 集团领导的审批任务查重
    // 渲染签名表格
    StringBuilder sb = new StringBuilder();
    sb << ’<table style=”padding: 5px 0; border-collapse: collapse;”>’
    renderSignature(”审批”, criticalTasks, sb);
    renderSignature(”审议”, otherTasks, sb);
    renderSignature(”总裁、董事长审批”, leaderTasks, sb);
    sb << ’</table>’;
    out << sb
} 
private renderSignature(String caption, List tasks, StringBuilder sb) { 
   if ( !tasks ) return;
    sb << ’<tr class=”prop”><td class=”nameX” style=”white-space:normal; text-align:center; vertical-align:middle; width:100px” rowspan=”’
    sb << (Math.ceil(tasks.size()/8) * 2) << ‘”>’ << caption << ‘</td>’
    // 每行最多显示8条签名，签名图片大小为100*40
    int max = 8;
    for ( int i=0; i<tasks.size(); i+=max ) { 
        if ( i!=0 ) sb << ‘<tr class=”prop”>’;
        renderSignature( tasks.subList(i,[i+max,tasks.size()].min()), sb );
        sb << ‘</tr>’
    } 
} 
private renderSignature(List tasks, StringBuilder sb) { 
    for ( t in tasks ) { 
        sb << ‘<td class=”nameX” style=”white-space:normal; text-align:center; vertical-align:middle; width:100px; padding:4px”>’
        if ( t.actorSubstituteId ) { 
            // 仅当为已办、或代理人打开待办、或者非任一人处理时，才显示“代理”二字
            if ( t.completeTime || t.substituteType==Substitute.TYPE_MOVE || t.actorSubstituteId==session.employeeId ) { 
                sb << ‘(代理)<br/>’;
            } 
        } 
        //sb << DateUtils.formatDatetime(t.createTime) << “<br/>”
        sb << bropen.framework.core.osm.Organization.splitName(t.organizationFullName,1) << ‘/’ << (t.actorTitle?:”员工”) << ‘<br/>’ << t.actor << ‘</td>’
    } 
    sb << ‘</tr><tr class=”prop” style=”height:40px”>’
    for ( t in tasks ) { 
        // 计算员工签名：超时自动提交的显示“系统提交”，签名不存在则显示“已处理”
        String sigid = null;
        if ( t.prevStatus == “timeout” ) { 
            sigid = “wf_timeout”
        }  else if ( t.decisiveOpinion == “不同意” ) { 
            sigid = “wf_disagree”;
        }  else if ( t.transition == “否决” ) { 
            sigid = “wf_reject”;
        }  else if ( t.actorSubstituteId ) { 
            sigid = t.actorSubstitute?.isSignatureExists() ? t.actorSubstitute.id : “wf_done”;
        }  else { 
            sigid = t.actor?.isSignatureExists() ? t.actor.id : “wf_done”;
        } 
        // 渲染单元格
        sb << ‘<td class=”valueX” style=”text-align:center; vertical-align:middle; padding:0px; width:100px”>’
        if ( t.status==”completed” ) { 
            sb << ‘<img width=”100” height=”40” src=”’ << createLink(controller:’app’, action:’signatureImage’, id:sigid) << ‘”/>’
        }  else { 
            sb << “&nbsp;”
        } 
        sb << ‘</td>’
   } 
} 
上述代码中，显示签名图片的是一个app控制器的 signatureImage 操作，代码如下：
/**
 * 显示签名图片（不加水印），用于签名栏
 */
def signatureImage() { 
    String filename = Employee.signatureFilename(params.id);
    File file = new File(filename);
   if ( file.exists() && file.canRead() ) { 
       // 渲染图片，并设置1天的浏览器缓存
        osmEmployeeService.renderSignature( response, filename, false,
           [validFor:86400, shared:true, store:true, lastModified:file.lastModified()] );
   }  else render ””;
} 
此外，也可以直接使用产品中显示签名图片的控制器操作 osm/signatureImage，该操作将返回一个添加了干扰水印的签名图片。
定义完手写签名的taglib后，在form、edit页面上面添加foo:signature标签即可。代码如下：
<div class=”body”>
   <foo:signature bean=”${ xxxInstance} ” />
   <h1>${ entityName} </h1>
    ....
</div>
总之，根据需求的不同，我们可以很灵活的定义不同的规则，展示出不同显示效果的签名栏。