Преглед изворни кода

提供统一的image-utils前端工具,完成批量加载图片,按配置渲染到canvas的逻辑,取代原有分散的渲染逻辑;提供加载模式的开关,用于控制是否严格加载,是否允许配置中约定的图片不存在也能正常加载显示

luoshi пре 4 година
родитељ
комит
82cfa2bb93

+ 6 - 6
stmms-web/src/main/java/cn/com/qmth/stmms/admin/exam/ScoreController.java

@@ -260,15 +260,16 @@ public class ScoreController extends BaseExamController {
                     list.add(new ExamStudentDTO(student));
                 }
             }
-            if (query.getSubjectCode() != null && !list.isEmpty()) {
+            if (query.getSubjectCode() != null) {
                 List<String> headerList = getOptionHeader(query.getExamId(), query.getSubjectCode(), paperType);
                 ExportStudentExcel excel = new ExportStudentExcel("成绩单", headerList, ExamStudentDTO.class);
                 excel.setDataList(list, true);
                 excel.write(response, fileName).dispose();
                 return null;
+            } else {
+                new ExportExcel("成绩单", ExamStudentDTO.class).setDataList(list).write(response, fileName).dispose();
+                return null;
             }
-            new ExportExcel("成绩单", ExamStudentDTO.class).setDataList(list).write(response, fileName).dispose();
-            return null;
         } catch (Exception e) {
             addMessage(redirectAttributes, "导出成绩失败!" + e.getMessage());
             return "redirect:/admin/exam/score";
@@ -403,7 +404,7 @@ public class ScoreController extends BaseExamController {
             return subjectCode + "评卷未完成";
         }
 
-        if (checkStudentService.countByExamIdAndSubjectCodeAndChecked(examId, subjectCode, false) != 0) {
+        if (checkStudentService.countByExamIdAndSubjectCodeAndChecked(examId, subjectCode, false) > 0) {
             return subjectCode + "人工确认未完成";
         }
 
@@ -412,8 +413,7 @@ public class ScoreController extends BaseExamController {
         query.setUpload(false);
         query.setManualAbsent(false);
         query.setSubjectCode(subjectCode);
-        long unUploadManualAbsentCount = studentService.countByQuery(query);
-        if (unUploadManualAbsentCount > 0) {
+        if (studentService.countByQuery(query) > 0) {
             return "未上传考生必须人工指定缺考";
         }
         return null;

+ 74 - 113
stmms-web/src/main/webapp/WEB-INF/views/include/trackView.jsp

@@ -1,4 +1,5 @@
-<%@ page contentType="text/html;charset=UTF-8"%>
+<%@ page contentType="text/html;charset=UTF-8" %>
+<script type="text/javascript" src="${ctxStatic}/utils/image-utils.js"></script>
 
 <script type="text/javascript" src="${ctxStatic}/mark-new/js/jquery-ui.min.js"></script>
 <script type="text/javascript" src="${ctxStatic}/mark-new/js/jquery.mousewheel.min.js"></script>
@@ -10,129 +11,89 @@
 <script type="text/javascript" src="${ctxStatic}/iviewer/jquery.iviewer.js"></script>
 
 <div id="track-view-content" class="container-fluid" style="display:none">
-	<div class="row-fluid">
-		<div id="right-track-div" >
-			<ul id="right-track-nav" class="nav nav-tabs">
-			</ul>
-			<div id="right-track-content" class="tab-content">
-			</div>
-		</div>
-	</div>
+    <div class="row-fluid">
+        <div id="right-track-div">
+            <ul id="right-track-nav" class="nav nav-tabs">
+            </ul>
+            <div id="right-track-content" class="tab-content">
+            </div>
+        </div>
+    </div>
 </div>
 
 <script type="text/javascript">
-var imageModal = new jBox('Modal');
+    var imageModal = new jBox('Modal');
 
-$(document).ready(function() {
-	$('#left-answer-div').find('iframe').height($(window).height()*0.83);
-});
-
-function initTrackPopover(title,url) {
-	initTrackPopoverContent(url); 
-	imageModal.setWidth($(window).width()*0.9);
-	imageModal.setHeight($(window).height()*0.85);
-	imageModal.setTitle(title);
-	imageModal.open();
-}
-function initTrackPopoverContent(url){
+    $(document).ready(function () {
+        $('#left-answer-div').find('iframe').height($(window).height() * 0.83);
+    });
 
-	$('#right-track-nav, #right-track-content').empty();
-	$.ajax({  
-        type:"GET",  
-        url:url,  
-        dataType:"json",  
-        success:function(data){  
-        	for (var i = 0; i < data.list.length; i++) {
-            	var group = data.list[i];
-            	var groupNumber = group.groupNumber;
-            	var title = group.groupNumber+'-'+group.groupTitle;
-        		$('#right-track-nav').append('<li><a href="#track_'+groupNumber+'" data-toggle="tab">'+title+'</a></li>');
-        		var pane = $('<div class="tab-pane fade" id="track_'+groupNumber+'" style="overflow: hidden;"><canvas id="track-builder-canvas_'+groupNumber+'"></canvas></div>').appendTo($('#right-track-content'));
-        		var canvas = document.getElementById('track-builder-canvas_'+groupNumber);
-        		var ctx = canvas.getContext('2d');
-	        	buildImages(data.imageServer,group.picUrls,group.pictureConfig,canvas,ctx,group.markTracks,group.markSpecialTagList); 
-        	}
-        	$('#right-track-nav a:first').trigger('click');
-			imageModal.setContent($('#track-view-content'));
-        } 
-    });  
+    function initTrackPopover(title, url) {
+        initTrackPopoverContent(url);
+        imageModal.setWidth($(window).width() * 0.9);
+        imageModal.setHeight($(window).height() * 0.85);
+        imageModal.setTitle(title);
+        imageModal.open();
+    }
 
-}
+    function initTrackPopoverContent(url) {
+        $('#right-track-nav, #right-track-content').empty();
+        $.ajax({
+            type: "GET",
+            url: url,
+            dataType: "json",
+            success: function (data) {
+                for (var i = 0; i < data.list.length; i++) {
+                    var group = data.list[i];
+                    var groupNumber = group.groupNumber;
+                    var title = group.groupNumber + '-' + group.groupTitle;
+                    $('#right-track-nav').append('<li><a href="#track_' + groupNumber + '" data-toggle="tab">' + title + '</a></li>');
+                    var pane = $('<div class="tab-pane fade" id="track_' + groupNumber + '" style="overflow: hidden;"><canvas id="track-builder-canvas_' + groupNumber + '"></canvas></div>').appendTo($('#right-track-content'));
+                    var canvas = document.getElementById('track-builder-canvas_' + groupNumber);
+                    var ctx = canvas.getContext('2d');
+                    buildImages(data.imageServer, group.picUrls, group.pictureConfig, canvas, ctx, group.markTracks, group.markSpecialTagList);
+                }
+                $('#right-track-nav a:first').trigger('click');
+                imageModal.setContent($('#track-view-content'));
+            }
+        });
 
-function buildImages (imageServer,picUrls,config,canvas,ctx,markTracks,markSpecialTagList) {
-    var indexSet = {};
-    for(var i=0;i<config.length;i++){
-        indexSet[config[i].i-1] = true;
     }
-    //调用图片预加载函数,实现回调函数
-    var imageObjects = [];
-    loadImages(imageObjects, imageServer, indexSet, picUrls, 0,function(images) {
-        var maxWidth = 0;
-        var totalHeight = 0;
-        for (var i = 0; i < config.length; i++) {
-            //计算最大宽度与合计高度
-            if(config[i].w<=0){
-                config[i].w=images[config[i].i-1].width;
-            }
-            if(config[i].h<=0){
-                config[i].h=images[config[i].i-1].height;
-            }
-            maxWidth = Math.max(maxWidth, config[i].w);
-            totalHeight += config[i].h;
-        }
-        if (maxWidth > 0 && totalHeight > 0) {
-            //设置画布大小及背景颜色
-            canvas.width = maxWidth;
-            canvas.height = totalHeight;
-            ctx.fillStyle = "#FFFFFF";
-            ctx.fillRect(0, 0, maxWidth, totalHeight);
-            //绘画到画布
-            var height = 0;
-            for (var i = 0; i < config.length; i++) {
-                var image = images[config[i].i-1];
-                ctx.drawImage(image, config[i].x, config[i].y, config[i].w, config[i].h, 0, height, config[i].w, config[i].h);
-                height += config[i].h;
-            }
-        }
-        //阅卷轨迹
-        if(markTracks!=undefined && markTracks.length>0){
-    		ctx.font ="60px Arial";
-    		ctx.fillStyle ='red';
-    		for (var j = 0; j < markTracks.length; j++) {
-    			ctx.fillText(
-    					markTracks[j].score,
-    					markTracks[j].positionX*canvas.width,
-    					markTracks[j].positionY*canvas.height);
-    		}
-        }
-		for (var i = 0; i < markSpecialTagList.length; i++) {
-		    ctx.font ="60px Arial";
-            ctx.fillStyle ='red';
-    		if(markSpecialTagList[i].positionX > 0 && markSpecialTagList[i].positionY > 0){
-    			ctx.fillText(markSpecialTagList[i].tagName,markSpecialTagList[i].positionX*canvas.width ,markSpecialTagList[i].positionY*canvas.height);
-        	}
-		}
-    });
-}
 
-function loadImages(images, imageServer, indexSet, urls, number, callback) {
-    if (urls != undefined && number < urls.length) {
-        if(indexSet[number]==true) {
-            var img = new Image();
-            img.onload = function() {
-                images.push(img);
-                loadImages(images, imageServer, indexSet, urls, number + 1, callback);
+    function buildImages(imageServer, picUrls, config, canvas, ctx, markTracks, markSpecialTagList) {
+        new ImageLoader({
+            server: imageServer,
+            flush: true,
+            strict: false
+        }).combine(picUrls, canvas, config, function () {
+            //阅卷轨迹
+            if (markTracks != undefined && markTracks.length > 0) {
+                ctx.font = "60px Arial";
+                ctx.fillStyle = 'red';
+                for (var j = 0; j < markTracks.length; j++) {
+                    if (markTracks[j].positionX > 0 && markTracks[j].positionY > 0) {
+                        ctx.fillText(
+                            markTracks[j].score,
+                            markTracks[j].positionX * canvas.width,
+                            markTracks[j].positionY * canvas.height);
+                    }
+                }
             }
-            img.src = imageServer + urls[number] + '?' + new Date().getTime();
-        }else {
-            images.push({});
-            loadImages(images, imageServer, indexSet, urls, number + 1, callback);
-        }
-    } else {
-        callback.call(this, images);
+            if (markSpecialTagList != undefined && markSpecialTagList.length > 0) {
+                ctx.font = "60px Arial";
+                ctx.fillStyle = 'red';
+                for (var i = 0; i < markSpecialTagList.length; i++) {
+                    if (markSpecialTagList[i].positionX > 0 && markSpecialTagList[i].positionY > 0) {
+                        ctx.fillText(markSpecialTagList[i].tagName,
+                            markSpecialTagList[i].positionX * canvas.width,
+                            markSpecialTagList[i].positionY * canvas.height);
+                    }
+                }
+            }
+        }, function (error) {
+            console.log(error)
+        })
     }
-}
-
 </script>
 
 

+ 252 - 270
stmms-web/src/main/webapp/WEB-INF/views/modules/exam/inspected.jsp

@@ -1,189 +1,201 @@
-<%@ page language="java" pageEncoding="utf-8"%>
-<%@ include file="/WEB-INF/views/include/taglib.jsp"%>
+<%@ page language="java" pageEncoding="utf-8" %>
+<%@ include file="/WEB-INF/views/include/taglib.jsp" %>
 <!DOCTYPE html>
 <html lang="en">
 <head>
-<meta charset="UTF-8">
-<title>云阅卷</title>
-<meta name="viewport" content="width=device-width,initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no"/>
-<link rel="stylesheet" href="${ctxStatic}/inspected/css/style.css"/>
-<script type="text/javascript" src="${ctxStatic}/mark-new/js/jquery.min.js"></script>
-<script type="text/javascript" src="${ctxStatic}/mark-new/js/jquery-ui.min.js "></script>
-<script type="text/javascript" src="${ctxStatic}/mark-new/js/jquery.mousewheel.min.js"></script>
-
-<script type="text/javascript" src="${ctxStatic}/answer-check/js/common.js"></script>
-
-<link rel="stylesheet" href="${ctxStatic}/iviewer/jquery.iviewer.css"/>
-<script type="text/javascript" src="${ctxStatic}/iviewer/jquery.iviewer.js"></script>
-
-<script type="text/javascript" src="${ctxStatic}/mark-new/js/jquery-ui.min.js"></script>
-<script type="text/javascript" src="${ctxStatic}/mark-new/js/jquery.mousewheel.min.js"></script>
+    <meta charset="UTF-8">
+    <title>云阅卷</title>
+    <meta name="viewport" content="width=device-width,initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no"/>
+    <link rel="stylesheet" href="${ctxStatic}/inspected/css/style.css"/>
+    <script type="text/javascript" src="${ctxStatic}/mark-new/js/jquery.min.js"></script>
+    <script type="text/javascript" src="${ctxStatic}/mark-new/js/jquery-ui.min.js "></script>
+    <script type="text/javascript" src="${ctxStatic}/mark-new/js/jquery.mousewheel.min.js"></script>
+    
+    <script type="text/javascript" src="${ctxStatic}/answer-check/js/common.js"></script>
+    
+    <link rel="stylesheet" href="${ctxStatic}/iviewer/jquery.iviewer.css"/>
+    <script type="text/javascript" src="${ctxStatic}/iviewer/jquery.iviewer.js"></script>
+    
+    <script type="text/javascript" src="${ctxStatic}/mark-new/js/jquery-ui.min.js"></script>
+    <script type="text/javascript" src="${ctxStatic}/mark-new/js/jquery.mousewheel.min.js"></script>
 
 </head>
 <body id="index">
 <div class="wp">
     <div id="top" class="top cl">
-        <div class="z"><div>
-            <span class="b">编号:</span><span class="i" id="studentId"></span><span class="pipe">|</span>
-            <span class="b">科目:</span><span class="i" id="subject"></span><span class="pipe">|</span>
-            <span class="b">评卷员:</span><span class="i" id="markerName"></span><span class="pipe">|</span>
-            <span class="b">未复核:</span><span class="i" id="progress"></span><span class="pipe">|</span>
-        </div></div>
+        <div class="z">
+            <div>
+                <span class="b">编号:</span><span class="i" id="studentId"></span><span class="pipe">|</span>
+                <span class="b">科目:</span><span class="i" id="subject"></span><span class="pipe">|</span>
+                <span class="b">评卷员:</span><span class="i" id="markerName"></span><span class="pipe">|</span>
+                <span class="b">未复核:</span><span class="i" id="progress"></span><span class="pipe">|</span>
+            </div>
+        </div>
         <div class="y">
-      		<div><span class="user">${web_user.name}</span></div>
-    	</div>
-    </div></div>
-    <div class="middle">
-        <div class="middle-bg"></div>
-        <div class="box">
-			<div class="cont cl">
-	        <div class="left">
-<!-- 	          <div class="iviewer_zoom cl"> -->
-<!-- 	            <div class="iviewer_zoom_in" id="zoom-in-button"></div> -->
-<!--             	<div class="iviewer_zoom_out" id="zoom-out-button"></div> -->
-<!--             	<div class="iviewer_zoom_zero" id="zoom-origin-button"></div> -->
-<!--             	<div class="iviewer_zoom_fit" id="zoom-fit-button"></div> -->
-<!-- 	          </div> -->
-	          <div id="image-holder-track" class="image-content" style="position: relative; " ></div>
-	        </div>
-	        <div class="right">
-	          <div class="formbox">
-	            <div class="form-t">
-	              <h1><span>评分:</span><span class="i" id="markerScore"></span></h1>
-	            </div>
-	            <div class="form-m">
-	              <table class="c-table" cellpadding="0" cellspacing="0" width="100%">
-	                <thead>
-	                  <tr>
-	                    <th>小题</th>
-	                    <td>给分</td>
-	                  </tr>
-	                </thead>
-	                <tbody id="question-content">
-<!-- 	                  <tr> -->
-<!-- 	                    <th>网络教育学院2015年下半年本科学士学位主干课程考试01</th> -->
-<!-- 	                    <td>3.5</td> -->
-<!-- 	                  </tr> -->
-	                </tbody>
-	              </table>
-	            </div>
-	            <div class="form-b cl"> 
-	            	<input type="button" id="save-button" value="确认"/>
-	           		<input type="button" class="grey" id="back-button" value="打回"/>
-	            </div>
-	          </div>
-	        </div>
-		    <div class="tipsbox">
-		      	<p><span class="icon error"></span><br>当前没有可复核的试卷</p>
-		    </div>
+            <div><span class="user">${web_user.name}</span></div>
+        </div>
+    </div>
+</div>
+<div class="middle">
+    <div class="middle-bg"></div>
+    <div class="box">
+        <div class="cont cl">
+            <div class="left">
+                <!-- 	          <div class="iviewer_zoom cl"> -->
+                <!-- 	            <div class="iviewer_zoom_in" id="zoom-in-button"></div> -->
+                <!--             	<div class="iviewer_zoom_out" id="zoom-out-button"></div> -->
+                <!--             	<div class="iviewer_zoom_zero" id="zoom-origin-button"></div> -->
+                <!--             	<div class="iviewer_zoom_fit" id="zoom-fit-button"></div> -->
+                <!-- 	          </div> -->
+                <div id="image-holder-track" class="image-content" style="position: relative; "></div>
+            </div>
+            <div class="right">
+                <div class="formbox">
+                    <div class="form-t">
+                        <h1><span>评分:</span><span class="i" id="markerScore"></span></h1>
+                    </div>
+                    <div class="form-m">
+                        <table class="c-table" cellpadding="0" cellspacing="0" width="100%">
+                            <thead>
+                            <tr>
+                                <th>小题</th>
+                                <td>给分</td>
+                            </tr>
+                            </thead>
+                            <tbody id="question-content">
+                            <!-- 	                  <tr> -->
+                            <!-- 	                    <th>网络教育学院2015年下半年本科学士学位主干课程考试01</th> -->
+                            <!-- 	                    <td>3.5</td> -->
+                            <!-- 	                  </tr> -->
+                            </tbody>
+                        </table>
+                    </div>
+                    <div class="form-b cl">
+                        <input type="button" id="save-button" value="确认"/>
+                        <input type="button" class="grey" id="back-button" value="打回"/>
+                    </div>
+                </div>
+            </div>
+            <div class="tipsbox">
+                <p><span class="icon error"></span><br>当前没有可复核的试卷</p>
+            </div>
         </div>
     </div>
 </div>
 </body>
 <script type="text/javascript">
-var message = '${message}';
-var ids = [${ids}];
-var current;
-var student;
-var iviewer;
-var regex = /^[a-z]+$/ig;
-$(document).ready(function() {
-	if(message!=undefined && message!=''){
-		alert(message);
-	}
-	
-    $('#save-button').click(save);
-    $('#back-button').click(back);
-    
-    $('#zoom-in-button').click(function(){
-        if(iviewer!=undefined) {
-            iviewer.iviewer('zoom_by', 1);
+    var message = '${message}';
+    var ids = [${ids}];
+    var current;
+    var student;
+    var iviewer;
+    var regex = /^[a-z]+$/ig;
+    $(document).ready(function () {
+        if (message != undefined && message != '') {
+            alert(message);
         }
-    });
-    $('#zoom-out-button').click(function(){
-        if(iviewer!=undefined) {
-            iviewer.iviewer('zoom_by', -1);
+
+        $('#save-button').click(save);
+        $('#back-button').click(back);
+
+        $('#zoom-in-button').click(function () {
+            if (iviewer != undefined) {
+                iviewer.iviewer('zoom_by', 1);
+            }
+        });
+        $('#zoom-out-button').click(function () {
+            if (iviewer != undefined) {
+                iviewer.iviewer('zoom_by', -1);
+            }
+        });
+        $('#zoom-origin-button').click(function () {
+            if (iviewer != undefined) {
+                iviewer.iviewer('set_zoom', 100);
+            }
+        });
+        $('#zoom-fit-button').click(function () {
+            if (iviewer != undefined) {
+                iviewer.iviewer('fit');
+            }
+        });
+        if (ids.length == 0) {
+            $(".cont cl").css("display", "none");
+            return;
         }
-    });
-    $('#zoom-origin-button').click(function(){
-        if(iviewer!=undefined) {
-            iviewer.iviewer('set_zoom', 100);
+        $(".tipsbox").css("display", "none");
+
+        window.onbeforeunload = function () {
+            $.post('${ctx}/admin/exam/inspected/clear', {
+                libraryId: student.id
+            }, function (result) {
+
+            });
         }
+
+        $('#progress').html(ids.length);
+        process(1);
     });
-    $('#zoom-fit-button').click(function(){
-        if(iviewer!=undefined) {
-            iviewer.iviewer('fit');
+
+    function process(index) {
+
+        if (index < 1) {
+            return;
         }
-    });
-    if(ids.length==0){
-    	$(".cont cl").css("display","none");
-        return;
-    }
-    $(".tipsbox").css("display","none");
-    
-    window.onbeforeunload = function (){
-    	$.post('${ctx}/admin/exam/inspected/clear', {
-        	libraryId: student.id
-        }, function(result){
-            
+        if (index > ids.length) {
+            alert('所有考生已处理完毕,请返回重新搜索');
+            window.location.reload();
+            return;
+        }
+        current = index;
+        $('#answer-content').empty();
+        $('#image-holder-track').hide();
+        $.post('${ctx}/admin/exam/inspected/info', {
+            libraryId: ids[index - 1]
+        }, function (result) {
+            if (result.success == true) {
+                student = result;
+                render();
+            } else {
+                //$('#progress').html(ids.length-current);
+                process(current + 1);
+            }
+        }).error(function () {
+            alert('获取考生信息出错');
+            onProcessFinish(true);
         });
     }
-    
-	$('#progress').html(ids.length);
-    process(1);
-});
 
-function process(index){
-	
-    if(index<1){
-        return;
-    }
-    if(index > ids.length){
-        alert('所有考生已处理完毕,请返回重新搜索');
-        window.location.reload();
-        return;
+    function onProcessFinish(error) {
+        if (!error) {
+            $('#save-button').removeAttr("disabled");
+        }
     }
-    current = index;
-    $('#answer-content').empty();
-    $('#image-holder-track').hide();
-    $.post('${ctx}/admin/exam/inspected/info', {
-        libraryId: ids[index-1]
-    }, function(result){
-    	if(result.success==true){
-        	student = result;
-        	render();
-    	}else{
-    		//$('#progress').html(ids.length-current);
-            process(current+1);
-    	}
-    }).error(function() {
-        alert('获取考生信息出错');
-        onProcessFinish(true);
-    });
-}
 
-function onProcessFinish(error) {
-    if(!error) {
-        $('#save-button').removeAttr("disabled");
-    }
-}
+    function render() {
 
-function render(){
-    
-    $('#studentId').html(student.studentId);
-    $('#subject').html(student.subjectCode+'_'+student.subjectName);
-    $('#markerName').html(student.markerName);
-    $('#markerScore').html(student.markerScore);
-    $("#question-content").empty();
-    for(var i=0;i<student.questions.length;i++){
-        var q = student.questions[i];
-        var dom = '<tr><th>'+q.questionNumber+'</th><td>'+q.score+'</td></tr>';
-		$("#question-content").append(dom)	;
-    }
-    
-/*     if(iviewer==undefined){
-    	  iviewer = $('#image-holder-track').iviewer({
-              src: '${sheetServer}' + student.sheetUrls[0],
+        $('#studentId').html(student.studentId);
+        $('#subject').html(student.subjectCode + '_' + student.subjectName);
+        $('#markerName').html(student.markerName);
+        $('#markerScore').html(student.markerScore);
+        $("#question-content").empty();
+        for (var i = 0; i < student.questions.length; i++) {
+            var q = student.questions[i];
+            var dom = '<tr><th>' + q.questionNumber + '</th><td>' + q.score + '</td></tr>';
+            $("#question-content").append(dom);
+        }
+
+        /*     if(iviewer==undefined){
+                  iviewer = $('#image-holder-track').iviewer({
+                      src: '
+        
+        
+        
+        
+        
+        
+        
+        
+        ${sheetServer}' + student.sheetUrls[0],
               zoom_delta: 1.2,
               zoom: 'fit',
               zoom_min: 10,
@@ -200,125 +212,95 @@ function render(){
               }
           });
     }else{
-        iviewer.iviewer('loadImage', '${sheetServer}' + student.sheetUrls[0]);
+        iviewer.iviewer('loadImage', '
+        
+        
+        
+        
+        
+        
+        
+        
+        ${sheetServer}' + student.sheetUrls[0]);
     } */
-    var pane = $('<canvas id="track-builder-canvas"></canvas>').appendTo($('#image-holder-track'));
-    var canvas = document.getElementById('track-builder-canvas');
-	var ctx = canvas.getContext('2d');
-    buildImages('${sliceServer}',student.picUrls,student.pictureConfig,canvas,ctx,student.markTracks,student.markSpecialTagList); 
-    $('#image-holder-track').show();
-    onProcessFinish(false);
-}
-
-function save(){
-    if(student==undefined){
-        return;
+        var pane = $('<canvas id="track-builder-canvas"></canvas>').appendTo($('#image-holder-track'));
+        var canvas = document.getElementById('track-builder-canvas');
+        var ctx = canvas.getContext('2d');
+        buildImages('${sliceServer}', student.picUrls, student.pictureConfig, canvas, ctx, student.markTracks, student.markSpecialTagList);
+        $('#image-holder-track').show();
+        onProcessFinish(false);
     }
-    $.post('${ctx}/admin/exam/inspected/save', {
-    	libraryId: student.id
-    }, function(result){
-        if(result==true){
-        	 $('#progress').html(ids.length-current);
-            process(current+1);
-        }else{
-            alert('保存失败,请稍后重试');
-        }
-    });
-}
 
-function back(){
-    if(student==undefined){
-        return;
-    }
-    if(!confirm('确定要打回该评卷任务吗?')){
-	    return;
-	}
-	$.post('${ctx}/admin/exam/library/back', {
-		id: student.id
-	}, function(result){
-	    if(result.success==true){
-	    	$('#progress').html(ids.length-current);
-            process(current+1);
-        }else{
-            alert(result.message);
+    function save() {
+        if (student == undefined) {
+            return;
         }
-	});
-
-}
-
-function buildImages (imageServer,picUrls,config,canvas,ctx,markTracks,markSpecialTagList) {
-    var indexSet = {};
-    for(var i=0;i<config.length;i++){
-        indexSet[config[i].i-1] = true;
-    }
-    //调用图片预加载函数,实现回调函数
-    var imageObjects = [];
-    loadImages(imageObjects, imageServer, indexSet, picUrls, 0,function(images) {
-        var maxWidth = 0;
-        var totalHeight = 0;
-        for (var i = 0; i < config.length; i++) {
-            //计算最大宽度与合计高度
-            if(config[i].w<=0){
-                config[i].w=images[config[i].i-1].width;
-            }
-            if(config[i].h<=0){
-                config[i].h=images[config[i].i-1].height;
-            }
-            maxWidth = Math.max(maxWidth, config[i].w);
-            totalHeight += config[i].h;
-        }
-        if (maxWidth > 0 && totalHeight > 0) {
-            //设置画布大小及背景颜色
-            canvas.width = maxWidth;
-            canvas.height = totalHeight;
-            ctx.fillStyle = "#FFFFFF";
-            ctx.fillRect(0, 0, maxWidth, totalHeight);
-            //绘画到画布
-            var height = 0;
-            for (var i = 0; i < config.length; i++) {
-                var image = images[config[i].i-1];
-                ctx.drawImage(image, config[i].x, config[i].y, config[i].w, config[i].h, 0, height, config[i].w, config[i].h);
-                height += config[i].h;
+        $.post('${ctx}/admin/exam/inspected/save', {
+            libraryId: student.id
+        }, function (result) {
+            if (result == true) {
+                $('#progress').html(ids.length - current);
+                process(current + 1);
+            } else {
+                alert('保存失败,请稍后重试');
             }
+        });
+    }
+
+    function back() {
+        if (student == undefined) {
+            return;
         }
-        //阅卷轨迹
-        if(markTracks!=undefined && markTracks.length>0){
-    		ctx.font ="60px Arial";
-    		ctx.fillStyle ='red';
-    		for (var j = 0; j < markTracks.length; j++) {
-    			ctx.fillText(
-    					markTracks[j].score,
-    					markTracks[j].positionX*canvas.width,
-    					markTracks[j].positionY*canvas.height);
-    		}
+        if (!confirm('确定要打回该评卷任务吗?')) {
+            return;
         }
-		for (var i = 0; i < markSpecialTagList.length; i++) {
-		    ctx.font ="60px Arial";
-            ctx.fillStyle ='red';
-    		if(markSpecialTagList[i].positionX > 0 && markSpecialTagList[i].positionY > 0){
-    			ctx.fillText(markSpecialTagList[i].tagName,markSpecialTagList[i].positionX*canvas.width ,markSpecialTagList[i].positionY*canvas.height);
-        	}
-		}
-    });
-}
+        $.post('${ctx}/admin/exam/library/back', {
+            id: student.id
+        }, function (result) {
+            if (result.success == true) {
+                $('#progress').html(ids.length - current);
+                process(current + 1);
+            } else {
+                alert(result.message);
+            }
+        });
 
-function loadImages(images, imageServer, indexSet, urls, number, callback) {
-    if (urls != undefined && number < urls.length) {
-        if(indexSet[number]==true) {
-            var img = new Image();
-            img.onload = function() {
-                images.push(img);
-                loadImages(images, imageServer, indexSet, urls, number + 1, callback);
+    }
+
+    function buildImages(imageServer, picUrls, config, canvas, ctx, markTracks, markSpecialTagList) {
+        new ImageLoader({
+            server: imageServer,
+            flush: true,
+            strict: false
+        }).combine(picUrls, canvas, config, function () {
+            //阅卷轨迹
+            if (markTracks != undefined && markTracks.length > 0) {
+                ctx.font = "60px Arial";
+                ctx.fillStyle = 'red';
+                for (var j = 0; j < markTracks.length; j++) {
+                    if (markTracks[j].positionX > 0 && markTracks[j].positionY > 0) {
+                        ctx.fillText(
+                            markTracks[j].score,
+                            markTracks[j].positionX * canvas.width,
+                            markTracks[j].positionY * canvas.height);
+                    }
+                }
             }
-            img.src = imageServer + urls[number] + '?' + new Date().getTime();
-        }else {
-            images.push({});
-            loadImages(images, imageServer, indexSet, urls, number + 1, callback);
-        }
-    } else {
-        callback.call(this, images);
+            if (markSpecialTagList != undefined && markSpecialTagList.length > 0) {
+                ctx.font = "60px Arial";
+                ctx.fillStyle = 'red';
+                for (var i = 0; i < markSpecialTagList.length; i++) {
+                    if (markSpecialTagList[i].positionX > 0 && markSpecialTagList[i].positionY > 0) {
+                        ctx.fillText(markSpecialTagList[i].tagName,
+                            markSpecialTagList[i].positionX * canvas.width,
+                            markSpecialTagList[i].positionY * canvas.height);
+                    }
+                }
+            }
+        }, function (error) {
+            console.log(error)
+        })
     }
-}
 
 </script>
 </html>

+ 6 - 6
stmms-web/src/main/webapp/WEB-INF/views/modules/mark/markNew.jsp

@@ -6,22 +6,22 @@
     <title>云阅卷高校版</title>
     <link href="${ctxStatic}/mark-new/css/bootstrap.css" rel="stylesheet" type="text/css"/>
     <link href="${ctxStatic}/mark-new/css/style.css" rel="stylesheet" type="text/css"/>
-
+    <script type="text/javascript" src="${ctxStatic}/utils/image-utils.js"></script>
     <script type="text/javascript" src="${ctxStatic}/mark-new/js/json2.js"></script>
-
+    
     <script type="text/javascript" src="${ctxStatic}/mark-new/js/jquery.min.js"></script>
     <script type="text/javascript" src="${ctxStatic}/mark-new/js/jquery-ui.min.js "></script>
     <script type="text/javascript" src="${ctxStatic}/mark-new/js/jquery.mousewheel.min.js"></script>
-
+    
     <script type="text/javascript" src="${ctxStatic}/iviewer/jquery.iviewer.js"></script>
     <link rel="stylesheet" href="${ctxStatic}/iviewer/jquery.iviewer.css" rel="stylesheet"/>
-
+    
     <script src="${ctxStatic}/perfect-scrollbar/min/perfect-scrollbar.min.js"></script>
     <link href="${ctxStatic}/perfect-scrollbar/min/perfect-scrollbar.min.css" rel="stylesheet">
-
+    
     <script src="${ctxStatic}/i18n/jquery.i18n.properties.js" type="text/javascript"></script>
     <script src="${ctxStatic}/i18n/load.js" type="text/javascript"></script>
-
+    
     <script type="text/javascript" src="${ctxStatic}/mark-new/js/mark-control.js"></script>
     <script type="text/javascript" src="${ctxStatic}/mark-new/js/task-control.js"></script>
     <script type="text/javascript" src="${ctxStatic}/mark-new/js/modules/answer-view.js"></script>

+ 32 - 6
stmms-web/src/main/webapp/static/mark-new/js/modules/image-builder.js

@@ -18,6 +18,19 @@ ImageBuilder.prototype.init = function () {
 }
 
 ImageBuilder.prototype.build = function (task, callback) {
+    new ImageLoader({
+        server: this.markControl.context.imageServer,
+        flush: true,
+        strict: false
+    }).merge(task.pictureUrls, this.canvas, task.pictureConfig, function (image) {
+        task.imageData = image
+        callback()
+    }, function (error) {
+        callback('image load error: ' + error)
+    })
+}
+
+ImageBuilder.prototype.buildBak = function (task, callback) {
     var self = this;
     //初始化图片拼接配置,过滤实际需要加载的图片
     var config = task.pictureConfig;
@@ -33,10 +46,10 @@ ImageBuilder.prototype.build = function (task, callback) {
         for (var i = 0; i < config.length; i++) {
             //计算最大宽度与合计高度
             if (config[i].w <= 0) {
-                config[i].w = images[config[i].i - 1].width;
+                config[i].w = images[config[i].i - 1].width || 0;
             }
             if (config[i].h <= 0) {
-                config[i].h = images[config[i].i - 1].height;
+                config[i].h = images[config[i].i - 1].height || 0;
             }
             maxWidth = Math.max(maxWidth, config[i].w);
             totalHeight += config[i].h;
@@ -51,8 +64,10 @@ ImageBuilder.prototype.build = function (task, callback) {
             var height = 0;
             for (var i = 0; i < config.length; i++) {
                 var image = images[config[i].i - 1];
-                self.ctx.drawImage(image, config[i].x, config[i].y, config[i].w, config[i].h, 0, height, config[i].w, config[i].h);
-                height += config[i].h;
+                if (image.loaded === true) {
+                    self.ctx.drawImage(image, config[i].x, config[i].y, config[i].w, config[i].h, 0, height, config[i].w, config[i].h);
+                    height += config[i].h;
+                }
             }
             //生成合并后的图像数据
             task.imageData = this.canvas.toDataURL("image/jpeg");
@@ -83,17 +98,28 @@ ImageBuilder.prototype.loadImages = function (images, indexSet, urls, number, ca
         if (indexSet[number] == true) {
             var img = new Image();
             img.onload = function () {
+                img.loaded = true;
                 images.push(img);
                 self.loadImages(images, indexSet, urls, number + 1, callback, exception);
             }
             img.onerror = function () {
                 img.onerror = undefined;
-                exception('load error for: ' + img.src);
+                //exception('load error for: ' + img.src);
+                images.push({
+                    loaded: false,
+                    width: 0,
+                    height: 0
+                });
+                self.loadImages(images, indexSet, urls, number + 1, callback, exception);
             }
             img.crossOrigin = '';
             img.src = imageServer + urls[number] + '?' + new Date().getTime();
         } else {
-            images.push({});
+            images.push({
+                loaded: false,
+                width: 0,
+                height: 0
+            });
             self.loadImages(images, indexSet, urls, number + 1, callback, exception);
         }
     } else {

+ 135 - 116
stmms-web/src/main/webapp/static/utils/image-utils.js

@@ -1,7 +1,9 @@
+"use strict"
+
 /**
  * 兼容高分辨率屏幕高像素比的情况
- * 
- * @param {*} context 
+ *
+ * @param {*} context
  */
 function getPixelRatio(context) {
     let backingStore = context.backingStorePixelRatio ||
@@ -13,115 +15,50 @@ function getPixelRatio(context) {
     return (window.devicePixelRatio || 1) / backingStore
 }
 
-/**
- * 使用指定画布,按照云阅卷拼接规则,将已加载好的图片拼接成新的图片对象
- * 
- * @param {canvas} canvas 
- * @param {array} images 
- * @param {array} config 
- * @param {function} onSuccess 
- * @param {function} onError 
- */
-function mergeImages(canvas, images, config, onSuccess, onError) {
-    if (config == undefined || config.length == 0) {
-        //不指定拼接配置时,默认按顺序显示所有图片
-        config = []
-        for (let i = 0; i < images.length; i++) {
-            config.push({
-                i: i + 1,
-                x: 0,
-                y: 0,
-                w: 0,
-                h: 0
-            })
-        }
-    }
-    let maxWidth = 0
-    let totalHeight = 0
-    for (let i = 0; i < config.length; i++) {
-        //计算最大宽度与合计高度
-        if (config[i].w <= 0) {
-            config[i].w = images[config[i].i - 1].width
-        }
-        if (config[i].h <= 0) {
-            config[i].h = images[config[i].i - 1].height
-        }
-        maxWidth = Math.max(maxWidth, config[i].w)
-        totalHeight += config[i].h
-    }
-    let context = canvas.getContext("2d")
-    //获取设备像素比
-    //暂时只用原始尺寸,避免转换图片过大导致性能问题
-    //let ratio = getPixelRatio(context)
-    let ratio = 1
-    //设置画布大小
-    canvas.width = maxWidth * ratio
-    canvas.height = totalHeight * ratio
-    context.clearRect(0, 0, canvas.width, canvas.height)
-    context.fillStyle = "white";
-    context.fillRect(0, 0, canvas.width, canvas.height);
-    //绘画到画布
-    //高度位置与图片配置对应关系
-    let array = []
-    let top = 0
-    let height = 0
-    for (let i = 0; i < config.length; i++) {
-        context.drawImage(images[config[i].i - 1], config[i].x, config[i].y, config[i].w, config[i].h, 0, height, config[i].w * ratio, config[i].h * ratio)
-        height += config[i].h * ratio
-        top += config[i].h
-        array.push({
-            offsetY: top,
-            config: config[i]
-        })
-    }
-    let image = new Image()
-    if (onSuccess) {
-        image.onload = function() {
-            onSuccess(image)
-        }
-    }
-    if (onError) {
-        image.onerror = function() {
-            onError('image merge error')
-        }
-    }
-    image.config = array
-    // image.origin = {
-    //     width: maxWidth,
-    //     height: totalHeight
-    // }
-    image.src = canvas.toDataURL()
-    return image
-}
-
 /**
  * 针对云阅卷场景多图片处理工具
- * 1. 支持指定服务器地址与是否强制刷新
+ * 1. 支持指定服务器地址、是否强制刷新、是否严格模式
  * 2. 支持按云阅卷规则筛选实际需要加载的图片
  * 3. 支持动态使用画布,按云阅卷规则垂直拼接出新的图片
- * 
- * @param {*} option 
+ *
+ * @param {{server: *, flush: boolean, strict: boolean}} option
  */
 function ImageLoader(option) {
     option = option || {}
     this.server = option.server || ''
     this.flush = option.flush === true
+    this.strict = option.strict === true
 }
 
-ImageLoader.prototype.load = function(urls, config, onSuccess, onError) {
+ImageLoader.prototype.load = function (urls, config, onSuccess, onError) {
+    onSuccess = onSuccess || (function () {
+    })
+    onError = onError || (function (error) {
+        console.log(error)
+    })
+    urls = (urls != undefined && Array.isArray(urls)) ? urls : []
     let promises = []
     let images = []
+    let self = this
     for (let i = 0; i < urls.length; i++) {
         let url = this.server + urls[i] + (this.flush ? ('?' + new Date().getTime()) : '')
         let image = new Image()
+        image.index = i + 1
+        image.loaded = false
         images.push(image)
         promises.push(new Promise((resolve, reject) => {
-            if (this.contain(i + 1, config)) {
-                image.onload = function() {
+            if (self.contain(image.index, config)) {
+                image.onload = function () {
+                    image.loaded = true
                     resolve()
                 }
-                image.onerror = function() {
-                    reject(url + ' load error')
+                image.onerror = function () {
+                    this.onerror = undefined
+                    if (self.strict) {
+                        reject(url + ' load error')
+                    } else {
+                        resolve()
+                    }
                 }
                 image.crossOrigin = ''
                 image.src = url
@@ -132,19 +69,19 @@ ImageLoader.prototype.load = function(urls, config, onSuccess, onError) {
     }
 
     Promise.all(promises).then(() => {
-        onSuccess ? onSuccess(images) : (function() {})
+        onSuccess(images)
     }).catch(error => {
-        onError ? onError(error) : (function() {})
+        onError(error)
     })
 }
 
 /**
  * 判断指定序号的图片是否在拼接规则中存在,默认返回true
- * 
- * @param {number} number 
- * @param {array} config 
+ *
+ * @param {number} number
+ * @param {array} config
  */
-ImageLoader.prototype.contain = function(number, config) {
+ImageLoader.prototype.contain = function (number, config) {
     if (config != undefined && config.length > 0) {
         let find = false
         for (let i = 0; i < config.length; i++) {
@@ -159,24 +96,105 @@ ImageLoader.prototype.contain = function(number, config) {
     }
 }
 
-ImageLoader.prototype.merge = function(urls, config, onSuccess, onError) {
-    if (this.canvas == undefined) {
-        this.canvas = document.createElement('canvas')
-    }
-    this.canvas.style = 'display:none'
 
-    let canvas = this.canvas
-    this.load(urls, config, function(images) {
-        mergeImages(canvas, images, config, onSuccess, onError)
+/**
+ * 使用指定画布,按照云阅卷拼接规则,加载图片并组合
+ *
+ * @param {array} images
+ * @param {canvas} canvas
+ * @param {array} config
+ * @param {function} onSuccess
+ * @param {function} onError
+ */
+ImageLoader.prototype.combine = function (urls, canvas, config, onSuccess, onError) {
+    onSuccess = onSuccess || (function () {
+    })
+    onError = onError || (function (error) {
+        console.log(error)
+    })
+    this.load(urls, config, function (images) {
+        config = (config != undefined && Array.isArray(config)) ? config : []
+        if (config.length == 0) {
+            //不指定拼接配置时,默认按顺序显示所有图片
+            for (let i = 0; i < images.length; i++) {
+                config.push({
+                    i: i + 1,
+                    x: 0,
+                    y: 0,
+                    w: 0,
+                    h: 0
+                })
+            }
+        }
+        let maxWidth = 0
+        let totalHeight = 0
+        for (let i = 0; i < config.length; i++) {
+            let image = images[config[i] - 1]
+            //必须图片存在且正常加载配置才生效
+            if (image != undefined && image.loaded === true) {
+                //计算最大宽度与合计高度
+                if (config[i].w <= 0) {
+                    config[i].w = image.width
+                }
+                if (config[i].h <= 0) {
+                    config[i].h = image.height
+                }
+                maxWidth = Math.max(maxWidth, config[i].w)
+                totalHeight += config[i].h
+            }
+        }
+        let context = canvas.getContext("2d")
+        //获取设备像素比
+        //暂时只用原始尺寸,避免转换图片过大导致性能问题
+        //let ratio = getPixelRatio(context)
+        let ratio = 1
+        //设置画布大小
+        canvas.width = maxWidth * ratio
+        canvas.height = totalHeight * ratio
+        context.clearRect(0, 0, canvas.width, canvas.height)
+        context.fillStyle = "white";
+        context.fillRect(0, 0, canvas.width, canvas.height);
+        //绘画到画布
+        //高度位置与图片配置对应关系
+        let array = []
+        let top = 0
+        let height = 0
+        for (let i = 0; i < config.length; i++) {
+            let image = images[config[i] - 1]
+            if (image != undefined && image.loaded === true) {
+                context.drawImage(image, config[i].x, config[i].y, config[i].w, config[i].h, 0, height, config[i].w * ratio, config[i].h * ratio)
+                height += config[i].h * ratio
+                top += config[i].h
+                array.push({
+                    offsetY: top,
+                    config: config[i]
+                })
+            }
+        }
+        onSuccess(array)
     }, onError)
 }
 
+ImageLoader.prototype.merge = function (urls, canvas, config, onSuccess, onError) {
+    this.combine(urls, canvas, config, function (array) {
+        let image = new Image()
+        image.onload = function () {
+            onSuccess(image)
+        }
+        image.onerror = function () {
+            onError('image merge error')
+        }
+        image.config = array
+        image.src = canvas.toDataURL()
+    })
+}
+
 /**
  * 针对画布的辅助工具类
  * 1. 灵活调整大小与视网膜屏幕下的高清显示
  * 2. 按照云阅卷的图片拼接规则绘制图片内容
- * 
- * @param {} option 
+ *
+ * @param {} option
  */
 function EasyCanvas(option) {
     this.canvas = option.canvas
@@ -204,10 +222,11 @@ function EasyCanvas(option) {
         color: option.lineColor || 'red'
     }
 
-    this.onClick = option.onClick || (function() {})
+    this.onClick = option.onClick || (function () {
+    })
 
     let self = this
-    this.canvas.onclick = function(event) {
+    this.canvas.onclick = function (event) {
         if (self.originSize.width > 0 && self.originSize.height > 0) {
             self.onClick({
                 left: event.offsetX,
@@ -217,7 +236,7 @@ function EasyCanvas(option) {
     }
 }
 
-EasyCanvas.prototype.changeBoxSize = function(width, height) {
+EasyCanvas.prototype.changeBoxSize = function (width, height) {
     if (width != undefined) {
         this.boxSize.width = width
         this.boxSize.height = undefined
@@ -227,11 +246,11 @@ EasyCanvas.prototype.changeBoxSize = function(width, height) {
     }
 }
 
-EasyCanvas.prototype.clear = function() {
+EasyCanvas.prototype.clear = function () {
     this.context.clearRect(0, 0, this.canvas.width, this.canvas.heigth)
 }
 
-EasyCanvas.prototype.reset = function(width, height) {
+EasyCanvas.prototype.reset = function (width, height) {
     this.originSize.width = Math.max((width || 0), 0)
     this.originSize.height = Math.max((height || 0), 0)
     this.styleSize.width = this.originSize.width
@@ -255,11 +274,11 @@ EasyCanvas.prototype.reset = function(width, height) {
     this.clear()
 }
 
-EasyCanvas.prototype.drawImage = function(image, scale) {
+EasyCanvas.prototype.drawImage = function (image, scale) {
     this.drawImages([image], undefined, scale)
 }
 
-EasyCanvas.prototype.drawImages = function(images, config, scale) {
+EasyCanvas.prototype.drawImages = function (images, config, scale) {
     scale = scale || 1.0
     if (config == undefined || config.length == 0) {
         //不指定拼接配置时,默认按顺序显示所有图片
@@ -302,13 +321,13 @@ EasyCanvas.prototype.drawImages = function(images, config, scale) {
     }
 }
 
-EasyCanvas.prototype.drawText = function(text, left, top) {
+EasyCanvas.prototype.drawText = function (text, left, top) {
     this.context.font = this.fontConfig.size * this.ratio + 'px ' + this.fontConfig.name
     this.context.fillStyle = this.fontConfig.color
     this.context.fillText(text, left * this.ratio, top * this.ratio)
 }
 
-EasyCanvas.prototype.drawShape = function(trace) {
+EasyCanvas.prototype.drawShape = function (trace) {
     if (trace && trace.length > 0) {
         this.context.lineWidth = this.lineConfig.width
         this.context.strokeStyle = this.lineConfig.color