Procházet zdrojové kódy

Merge branch 'dev' of http://192.168.1.41:3000/maixinrong/Calculation into dev

TonyKang před 3 roky
rodič
revize
7c701d7d50

+ 19 - 0
app/const/sign.js

@@ -0,0 +1,19 @@
+'use strict';
+
+/**
+ * 签名及归档报表页面相关
+ *
+ * @author Ellisran
+ * @date
+ * @version
+ */
+
+const path = {
+    oss: 'https://measure-sign-pdf.oss-cn-shenzhen.aliyuncs.com/archive',
+    api: 'http://124.71.15.76:9091/eseal',
+};
+
+
+module.exports = {
+    path,
+};

+ 151 - 3
app/controller/report_archive_controller.js

@@ -12,6 +12,7 @@ const tenderMenu = require('../../config/menu').tenderMenu;
 const measureType = require('../const/tender').measureType;
 const fsUtil = require('../public/js/fsUtil');
 const auditConst = require('../const/audit');
+const signConst = require('../const/sign');
 
 module.exports = app => {
     class ReportArchiveController extends app.BaseController {
@@ -83,9 +84,10 @@ module.exports = app => {
                 auditConst: auditConst.stage,
                 archiveList,
                 archiveEncryptionList,
-                can_netcasign: ctx.session.sessionProject.page_show.openNetCaSign === 1,
+                can_netcasign: false,
+                ossPath: signConst.path.oss,
             };
-            await this.layout('report/index_archive.ejs', renderData, 'report/index_archive_modal.ejs');
+            await this.layout('report/index_archive.ejs', renderData);
         }
 
         async getReportArchive(ctx) {
@@ -376,10 +378,156 @@ module.exports = app => {
         }
 
         async pdfShow(ctx) {
+            // const renderData = {
+            //     can_netcasign: ctx.session.sessionProject.page_show.openNetCaSign === 1,
+            // };
+            await ctx.render('report/archive_pdf.ejs');
+        }
+
+        /*
+         *  网证通电子签名页,(和归档报表页高度相似)
+         */
+        async signReport(ctx) {
+            const tender = ctx.tender;
+            const stage = ctx.stage;
+            let stage_id = -1;
+            let stage_order = -1;
+            let stage_times = -1;
+            let stage_status = -1;
+            const treeNodes = await ctx.service.rptTreeNode.getNodesByProjectId([-1, tender.data.project_id]);
+            const custTreeNodes = await ctx.service.rptTreeNodeCust.getCustFoldersByUserId(this.ctx.session.sessionUser.accountId);
+            const stageList = await ctx.service.stage.getValidStagesShort(tender.id);
+            //
+            // 。。。
+            let archiveList = [];
+            let archiveEncryptionList = [];
+            // console.log('tender.data.project_id: ' + tender.data.project_id);
+            if (stage) {
+                // console.log('ctx.stage.id: ' + ctx.stage.id);
+                const archives = await ctx.service.rptArchive.getPrjStgArchive(tender.data.project_id, ctx.stage.id);
+                const archiveEncryptions = await ctx.service.rptArchiveEncryption.getPrjStgArchiveEncryption(tender.data.project_id, ctx.stage.id);
+                stage_id = stage.id;
+                stage_order = stage.order;
+                stage_times = stage.times;
+                stage_status = stage.status;
+                if (archives.length > 0) {
+                    archiveList = JSON.parse(archives[0].content);
+                }
+                if (archiveEncryptions.length > 0) {
+                    archiveEncryptionList = JSON.parse(archiveEncryptions[0].content);
+                }
+            } else {
+                // console.log('stageList[0].id: ' + stageList[0].id);
+                const archives = await ctx.service.rptArchive.getPrjStgArchive(tender.data.project_id, stageList[stageList.length - 1].id);
+                const archiveEncryptions = await ctx.service.rptArchiveEncryption.getPrjStgArchiveEncryption(tender.data.project_id, stageList[stageList.length - 1].id);
+                // stage_id = stageList[0].id;
+                // stage_order = stageList[0].order;
+                // stage_times = stageList[0].times;
+                // stage_status = stageList[0].status;
+                if (archives && archives.length > 0) {
+                    archiveList = JSON.parse(archives[0].content);
+                }
+                if (archiveEncryptions && archiveEncryptions.length > 0) {
+                    archiveEncryptionList = JSON.parse(archiveEncryptions[0].content);
+                }
+            }
+            let rpt_tpl_items = '{ customize: [], common: [] }';
+            if (custTreeNodes.length > 0) {
+                rpt_tpl_items = custTreeNodes[0].rpt_tpl_items;
+            }
+            // 获取ukey绑定数据
+            const netcaSignData = await ctx.service.netcasign.getDataByCondition({ uid: ctx.session.sessionUser.accountId });
+            // 获取已签名数据
+            const signLogList = await ctx.service.netcasignLog.getLogList(ctx.tender.id);
             const renderData = {
+                tender: tender.data,
+                rpt_tpl_data: JSON.stringify(treeNodes),
+                cust_tpl_data: rpt_tpl_items,
+                project_id: tender.data.project_id,
+                tender_id: tender.id,
+                stg_id: stage_id,
+                stg_order: stage_order,
+                stg_times: stage_times,
+                stg_status: stage_status,
+                stage_list: JSON.stringify(stageList),
+                tenderMenu,
+                measureType,
+                jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.report.main),
+                stages: stageList,
+                auditConst: auditConst.stage,
+                archiveList,
+                archiveEncryptionList,
+                netcaSignData,
                 can_netcasign: ctx.session.sessionProject.page_show.openNetCaSign === 1,
+                ossPath: signConst.path.oss,
+                signLogList,
             };
-            await ctx.render('report/archive_pdf.ejs', renderData);
+            await this.layout('report/index_sign.ejs', renderData, 'report/index_sign_modal.ejs');
+        }
+
+        /**
+         * 网证通电子签名接口
+         *
+         * @param {object} ctx - egg全局变量
+         * @return {void}
+         */
+        async signPost(ctx) {
+            const response = {
+                err: 0,
+                msg: '',
+            };
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                let signData;
+                const netcaSignApi = signConst.path.api;
+                switch (data.type) {
+                    case 'pdfIsExist':
+                        const pdfMsg = await ctx.oss.head('archive/sign/' + data.uuid + '.PDF');
+                        response.data = pdfMsg.res.status === 200;
+                        break;
+                    // 获取摘要值
+                    case 'assemblyDigest':
+                        const postData = {
+                            requestJson: JSON.stringify(data.requestJson),
+                        };
+                        const result = await ctx.helper.sendRequest(netcaSignApi + '/assemblyDigest', postData, 'POST');
+                        response.data = result;
+                        break;
+                    // 生成签名pdf
+                    case 'assemblyPdf':
+                        const postData2 = {
+                            requestJson: JSON.stringify(data.requestJson),
+                        };
+                        const result2 = await ctx.helper.sendRequest(netcaSignApi + '/assemblyPdf', postData2, 'POST');
+                        // 上传到oss
+                        if (result2.code === 0) {
+                            const result3 = await ctx.curl(netcaSignApi + result2.data);
+                            const oss_reuslt = await ctx.oss.put('archive/sign/' + data.requestJson.fileName + '.PDF', result3.data);
+                            if (oss_reuslt && oss_reuslt.res && oss_reuslt.res.status === 200) {
+                                const versionId = oss_reuslt.res.headers['x-oss-version-id'];
+                                // 记录签名和保存
+                                await ctx.service.netcasignLog.add(data.requestJson.fileName, data.role, ctx.session.sessionUser.accountId, versionId);
+                                const signLogList = await ctx.service.netcasignLog.getLogList(ctx.tender.id);
+                                response.data = signLogList;
+                            } else {
+                                throw '上传文件失败';
+                            }
+                        }
+
+                        break;
+                    default:throw '参数有误';
+                }
+            } catch (error) {
+                response.err = 1;
+                response.msg = error.toString();
+                const data = JSON.parse(ctx.request.body.data);
+                if (data && data.type === 'pdfIsExist') {
+                    response.err = 0;
+                    response.data = false;
+                }
+            }
+
+            ctx.body = response;
         }
     }
     return ReportArchiveController;

+ 2 - 2
app/controller/report_controller.js

@@ -72,8 +72,8 @@ module.exports = app => {
         async index(ctx) {
             try {
                 await this._getStageAuditViewData(ctx);
-                let pageShow = ctx.session.sessionProject.page_show;
-                pageShow.showArchive = 0;
+                const pageShow = ctx.session.sessionProject.page_show;
+                pageShow.showArchive = 1;
                 const tender = ctx.tender;
                 const stage = ctx.stage;
                 let stage_id = -1;

+ 37 - 63
app/public/js/measure_stage.js

@@ -38,8 +38,8 @@ $('a[data-target="#sp-list" ]').on('click', function () {
                 </li>`
             }
         })
-        $('#auditor-list').empty()
-        $('#auditor-list').append(auditorsHTML)
+        $('#auditor-list').empty();
+        $('#auditor-list').append(auditorsHTML);
         auditHistory.forEach((auditors, idx) => {
             if(idx === auditHistory.length - 1 && auditHistory.length !== 1) {
                 historyHTML += `<div class="text-right"><a href="javascript: void(0);" id="fold-btn" data-target="show"
@@ -75,29 +75,20 @@ $('a[data-target="#sp-list" ]').on('click', function () {
                     <li class="timeline-list-item pb-2">
                         <div class="timeline-item-date">
                             ${formatDate(auditor.end_time)}
-                        </div>`
-
-                        if(index < auditors.length - 1) {
-                            historyHTML += `<div class="timeline-item-tail"></div>`
-                        }
-                        if(auditor.status === auditConst.status.checked) {
-                            historyHTML += `<div class="timeline-item-icon bg-success text-light">
-                                <i class="fa fa-check"></i>
-                            </div>`
-
-                        } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.checkNoPre) {
-                            historyHTML += `<div class="timeline-item-icon bg-warning text-light">
-                                <i class="fa fa-level-up"></i>
-                            </div>`
-                        } else if(auditor.status === auditConst.status.checking) {
-                            historyHTML += `<div class="timeline-item-icon bg-warning text-light">
-                                <i class="fa fa-ellipsis-h"></i>
-                            </div>`
-                        } else {
-                            historyHTML += `<div class="timeline-item-icon bg-secondary text-light"></div>`
-
-                        }
-                        historyHTML += `<div class="timeline-item-content">
+                        </div>`;
+                    if (index < auditors.length - 1) {
+                        historyHTML += `<div class="timeline-item-tail"></div>`;
+                    }
+                    if (auditor.status === auditConst.status.checked) {
+                        historyHTML += `<div class="timeline-item-icon bg-success text-light"><i class="fa fa-check"></i></div>`;
+                    } else if (auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.checkNoPre) {
+                        historyHTML += `<div class="timeline-item-icon bg-warning text-light"><i class="fa fa-level-up"></i></div>`;
+                    } else if (auditor.status === auditConst.status.checking) {
+                        historyHTML += `<div class="timeline-item-icon bg-warning text-light"><i class="fa fa-ellipsis-h"></i></div>`;
+                    } else {
+                        historyHTML += `<div class="timeline-item-icon bg-secondary text-light"></div>`
+                    }
+                    historyHTML += `<div class="timeline-item-content">
                             <div class="card">
                                 <div class="card-body p-3">
                                     <div class="card-text">
@@ -106,67 +97,50 @@ $('a[data-target="#sp-list" ]').on('click', function () {
                                         </p>
                                         <p class="text-muted mb-0">${auditor.role}</p>
                                     </div>
-                                </div>`
-                        if (auditor.opinion) {
-                        historyHTML += `<div class="card-body p-3 border-top">
-                                <p style="margin: 0;">${auditor.opinion}</p>
-                            </div>`
-                        }
-                        historyHTML += `</div></div></li>`
+                                </div>`;
+                    if (auditor.opinion) {
+                        historyHTML += `<div class="card-body p-3 border-top"><p style="margin: 0;">${auditor.opinion}</p></div>`;
+                    }
+                    historyHTML += `</div></div></li>`
                 } else {
-                    historyHTML += `<li class="timeline-list-item pb-2">
-                    <div class="timeline-item-date">
-                        ${formatDate(auditor.end_time)}
-                    </div>`
-
+                    historyHTML += `<li class="timeline-list-item pb-2"><div class="timeline-item-date">${formatDate(auditor.end_time)}</div>`;
                     if(index < auditors.length - 1) {
                         historyHTML += `<div class="timeline-item-tail"></div>`
                     }
                     if(auditor.status === auditConst.status.checked) {
-                        historyHTML += `<div class="timeline-item-icon bg-success text-light">
-                            <i class="fa fa-check"></i>
-                        </div>`
+                        historyHTML += `<div class="timeline-item-icon bg-success text-light"><i class="fa fa-check"></i></div>`;
                     } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.checkNoPre) {
-                        historyHTML += `<div class="timeline-item-icon bg-warning text-light">
-                            <i class="fa fa-level-up"></i>
-                        </div>`
-
+                        historyHTML += `<div class="timeline-item-icon bg-warning text-light"><i class="fa fa-level-up"></i></div>`;
                     } else if(auditor.status === auditConst.status.checking) {
-                        historyHTML += `<div class="timeline-item-icon bg-warning text-light">
-                            <i class="fa fa-ellipsis-h"></i>
-                        </div>`
+                        historyHTML += `<div class="timeline-item-icon bg-warning text-light"><i class="fa fa-ellipsis-h"></i></div>`;
                     } else {
-                        historyHTML += `<div class="timeline-item-icon bg-secondary text-light"></div>`
+                        historyHTML += `<div class="timeline-item-icon bg-secondary text-light"></div>`;
                     }
                     historyHTML += `<div class="timeline-item-content">
                     <div class="card">
                         <div class="card-body p-3">
                             <div class="card-text">
                                 <p class="mb-1"><span class="h5">${auditor.name}</span>
-                                    <span
-                                        class="pull-right
-                                                        ${auditConst.statusClass[auditor.status]}">${auditor.status !== auditConst.status.uncheck ? auditConst.statusString[auditor.status] : ''}
+                                    <span class="pull-right ${auditConst.statusClass[auditor.status]}">
+                                        ${auditor.status !== auditConst.status.uncheck ? auditConst.statusString[auditor.status] : ''}
                                         ${auditor.status === auditConst.status.checkNo ? user.name : ''}
-                                        ${auditor.status === auditConst.status.checkNoPre ? auditors[index-1].name : ''}
+                                        ${auditor.status === auditConst.status.checkNoPre ? auditors[index+1].name : ''}
                                     </span>
                                 </p>
                                 <p class="text-muted mb-0">${auditor.role}</p>
                             </div>
-                        </div>`
+                        </div>`;
 
                     if (auditor.opinion) {
-                    historyHTML += `<div class="card-body p-3 border-top">
-                        <p style="margin: 0;">${auditor.opinion} </p>
-                    </div>`
+                        historyHTML += `<div class="card-body p-3 border-top"><p style="margin: 0;">${auditor.opinion} </p></div>`;
                     }
-                    historyHTML += `</div></div></li>`
+                    historyHTML += `</div></div></li>`;
                 }
-            })
-            historyHTML += '</ul></div>'
-
-        })
-        $('#audit-list').empty()
-        $('#audit-list').append(historyHTML)
+            });
+            historyHTML += '</ul></div>';
+        });
+        $('#audit-list').empty();
+        $('#audit-list').append(historyHTML);
     })
 
 });

+ 20 - 2
app/public/netcasign/js/appPackage.js

@@ -118,7 +118,7 @@ function getNetcaKeyId(certObj) {
     }
 }
 
-function getCertListSuccess(res)
+function getCertListSuccess(res, comefrom)
 {
 	if (res && res.deviceCount === 0) {
 		toastr.error('请插入UKey获取证书');
@@ -136,7 +136,7 @@ function getCertListSuccess(res)
 		$('#name').val(cert.subjectCN);
 		$('#keyId').val(getNetcaKeyId(cert));
 	} else if (res.deviceCount === 2) {
-		toastr.error('请拔出多个UKey,保留一个并设置绑定');
+		toastr.error('请拔出多个UKey,保留一个并获取证书信息');
 		return;
 	} else {
 		toastr.error('获取证书失败');
@@ -144,6 +144,24 @@ function getCertListSuccess(res)
 
 }
 
+function getUserCert() {
+
+	var selectType = "{\"UIFlag\":\"default\", \"InValidity\":true,\"Type\":\"signature\", \"Method\":\"device\",\"Value\":\"any\"}";
+	var selectCondition = "IssuerCN~'NETCA' && InValidity='True' && CertType='Signature'";
+
+	netca_getCertStringAttribute(null, selectType, selectCondition, -1, successGetCertEncodeCallBack,
+		failedGetCertEncodeCallBack);
+}
+function successGetCertEncodeCallBack(res) {
+	// alert("从key里面读取证书成功");
+	data.cert_base64 = res.certCode;
+}
+
+function failedGetCertEncodeCallBack(res) {
+	// alert(res.msg);
+	toastr.error(res.msg);
+}
+
 function getCertListFail(res)
 {
    // alert("获取证书列表成功\n" + res.msg);

+ 3 - 3
app/public/netcasign/js/netcasealpdf.js

@@ -1993,9 +1993,9 @@ var NetcaPDFSeal = (function(){
                             globalPDFViewerApplication.pdfDocument.getData().then(function(data){
                                 // var PDFBytes = NetcaUtils.arrayBufferToBase64(data);
                                 NetcaPDFSeal.setPDFBytes(data);
-                                if (can_netcasign) {
-                                    NetcaSignAPI.getSealConfigInfo(data);
-                                }
+                                // if (can_netcasign) {
+                                //     NetcaSignAPI.getSealConfigInfo(data);
+                                // }
 
                             })
                         }

+ 91 - 24
app/public/report/js/rpt_archive.js

@@ -7,12 +7,16 @@ let rptArchiveObj = {
     currentNode: null,
     currentArchiveUuid: null,
     currentArchiveDateStr: null,
+    currentEncryptionList: null,
+    currentArchivePdfPath: null,
     iniPage: function() {
         //初始化页面的归档信息
         let me = rptArchiveObj;
         me.currentNode = null;
         me.currentArchiveUuid = null;
         me.currentArchiveDateStr = null;
+        me.currentEncryptionList = null;
+        me.currentArchivePdfPath = null;
         const archivedRptIds = [];
         for (let aItem of ARCHIVE_LIST) {
             archivedRptIds.push(parseInt(aItem.rpt_id));
@@ -102,6 +106,7 @@ let rptArchiveObj = {
                 if (treeNode.refId === parseInt(aItem.rpt_id)) {
                     me.currentArchiveUuid = null;
                     me.currentArchiveDateStr = null;
+                    me.currentArchivePdfPath = null;
                     if (aItem.items && aItem.items.length > 0) {
                         // me.currentArchiveUuid = aItem.items[0].uuid;
                         me.currentArchiveUuid = aItem.items[aItem.items.length - 1].uuid;
@@ -121,30 +126,48 @@ let rptArchiveObj = {
         let me = rptArchiveObj;
         if (me.currentNode && me.currentArchiveUuid) {
             try {
-                const msgSign = _.find(ARCHIVE_ENCRYPTION_LIST, {rpt_id: me.currentNode.ID});
-                console.log(msgSign);
-                let html = '';
-                let pagetr = '';
-                if (msgSign) {
-                    const rows = 12/msgSign.encryption.length < 3 ? 'col-3' : 'col-' + 12/msgSign.encryption.length;
-                    console.log(rows);
-                    for (const [index,role] of msgSign.encryption.entries()) {
-                        html += '<div class="'+ rows +'">\n' +
-                            '                                <div class="custom-control custom-radio custom-control-inline">\n' +
-                            '                                    <input type="radio" value="'+ index +'" id="customRadioInline'+ index +'" name="customRadioInline" class="custom-control-input">\n' +
-                            '                                    <label class="custom-control-label" for="customRadioInline'+ index +'">'+ role.name +'</label>\n' +
-                            '                                </div>\n' +
-                            '                            </div>';
-                    }
-                    for (let i = 1; i <= msgSign.total_page; i++) {
-                        pagetr += '<tr><td>页'+ i +'</td><td>'+ me.currentNode.name +'</td><td></td></tr>';
+                if (can_netcasign) {
+                    const msgSign = _.find(ARCHIVE_ENCRYPTION_LIST, {rpt_id: me.currentNode.ID});
+                    me.currentEncryptionList = msgSign;
+                    const uSignLogList = _.filter(signLogList, { uuid: me.currentArchiveUuid });
+                    let html = '';
+                    let pagetr = '';
+                    if (msgSign) {
+                        const rows = 12/msgSign.encryption.length < 3 ? 'col-3' : 'col-' + 12/msgSign.encryption.length;
+                        for (const [index,role] of msgSign.encryption.entries()) {
+                            const disablehtml = _.find(uSignLogList, { role: role.name }) ? ' disabled' : '';
+                            html += '<div class="'+ rows +'">\n' +
+                                '                                <div class="custom-control custom-radio custom-control-inline">\n' +
+                                '                                    <input type="radio" value="'+ index +'" id="sign_role_'+ index +'"'+ disablehtml +' name="sign_role" class="custom-control-input">\n' +
+                                '                                    <label class="custom-control-label" for="sign_role_'+ index +'">'+ role.name +'</label>\n' +
+                                '                                </div>\n' +
+                                '                            </div>';
+                        }
+                        let uhtml = '';
+                        for (const us of uSignLogList) {
+                            uhtml += '<span class="text-success"><i class="fa fa-check"></i> '+ us.role +'('+ us.name +')</span>';
+                        }
+                        for (let i = 1; i <= msgSign.total_page; i++) {
+                            pagetr += '<tr><td>页'+ i +'</td><td>'+ me.currentNode.name +'</td><td>'+ uhtml +'</td></tr>';
+                        }
                     }
+                    $('#role-list').html(html);
+                    $('#page-list').html(pagetr);
+                    $('#sign_num').text(uSignLogList.length);
+                    postData('/tender/'+ TENDER_ID +'/signReport/post', {type: 'pdfIsExist', uuid: me.currentArchiveUuid}, function (result) {
+                        me.currentArchivePdfPath = result ? oss_path + '/sign/'+ me.currentArchiveUuid +'.PDF' : oss_path + '/'+ me.currentArchiveUuid +'.PDF';
+                        $('#iframe_made').html('<iframe src="/archive/pdf/show?file=' + me.currentArchivePdfPath + '" height="750px" width="100%" style="border: none;"></iframe>');
+                    }, function () {
+                        me.currentArchivePdfPath = oss_path + '/'+ me.currentArchiveUuid +'.PDF';
+                        $('#iframe_made').html('<iframe src="/archive/pdf/show?file=' + oss_path + '/'+ me.currentArchiveUuid +'.PDF" height="750px" width="100%" style="border: none;"></iframe>');
+                    })
+                } else {
+                    me.currentArchivePdfPath = oss_path + '/'+ me.currentArchiveUuid +'.PDF';
+                    $('#iframe_made').html('<iframe src="/archive/pdf/show?file=' + oss_path + '/'+ me.currentArchiveUuid +'.PDF" height="750px" width="100%" style="border: none;"></iframe>');
                 }
-                $('#role-list').html(html);
-                $('#page-list').html(pagetr);
                 // let uuIdUrl =  "/getArchivedFileByUUID/" + me.currentArchiveUuid + "/" + stringUtil.replaceAll(me.currentNode.name, "#", "_");
                 // console.log(uuIdUrl);
-                $('#iframe_made').html('<iframe src="/archive/pdf/show?file=https://measure-sign-pdf.oss-cn-shenzhen.aliyuncs.com/archive/'+ me.currentArchiveUuid +'.PDF" height="750px" width="100%" style="border: none;"></iframe>');
+                // $('#iframe_made').html('<iframe src="/archive/pdf/show?file=' + oss_path + '/'+ me.currentArchiveUuid +'.PDF" height="750px" width="100%" style="border: none;"></iframe>');
                 // NetcaPDFSeal.openPDFWithUrl(window.location.href);
                 // window.location = uuIdUrl;
             } catch (ex) {
@@ -157,8 +180,23 @@ let rptArchiveObj = {
         let me = rptArchiveObj;
         // me.currentArchiveUuid = dom.uuid; //在dom的onclick时已经设置过了
         me.currentArchiveDateStr = dom.innerHTML;
-        $('#iframe_made').html('<iframe src="/archive/pdf/show?file=https://measure-sign-pdf.oss-cn-shenzhen.aliyuncs.com/archive/'+ me.currentArchiveUuid +'.PDF" height="750px" width="100%" style="border: none;"></iframe>');
-        me._buildeArchiveDateSelect();
+        if(can_netcasign) {
+            postData('/tender/'+ TENDER_ID +'/signReport/post', {type: 'pdfIsExist', uuid: me.currentArchiveUuid}, function (result) {
+                me.currentArchivePdfPath = result ? oss_path + '/sign/'+ me.currentArchiveUuid +'.PDF' : oss_path + '/'+ me.currentArchiveUuid +'.PDF';
+                $('#iframe_made').html('<iframe src="/archive/pdf/show?file=' + me.currentArchivePdfPath + '" height="750px" width="100%" style="border: none;"></iframe>');
+                me._buildeArchiveDateSelect();
+            }, function () {
+                me.currentArchivePdfPath = oss_path + '/'+ me.currentArchiveUuid +'.PDF';
+                $('#iframe_made').html('<iframe src="/archive/pdf/show?file=' + oss_path + '/'+ me.currentArchiveUuid +'.PDF" height="750px" width="100%" style="border: none;"></iframe>');
+                me._buildeArchiveDateSelect();
+            });
+            me._updateSignHtmlAndFrame();
+        } else {
+            me.currentArchivePdfPath = oss_path + '/'+ me.currentArchiveUuid +'.PDF';
+            $('#iframe_made').html('<iframe src="/archive/pdf/show?file=' + oss_path + '/'+ me.currentArchiveUuid +'.PDF" height="750px" width="100%" style="border: none;"></iframe>');
+            me._buildeArchiveDateSelect();
+        }
+
     },
 
     _buildeArchiveDateSelect: function () {
@@ -181,6 +219,34 @@ let rptArchiveObj = {
         }
     },
 
+    _updateSignHtmlAndFrame: function (needFrame = false) {
+        const me = rptArchiveObj;
+        const uSignLogList = _.filter(signLogList, { uuid: me.currentArchiveUuid });
+        for (let i = 0; i < $('#role-list div').length; i++) {
+            if (_.find(uSignLogList, { role: $('#role-list div').eq(i).find('label').text() })) {
+                $('#role-list div').eq(i).find('input').attr('disabled', true);
+            } else {
+                $('#role-list div').eq(i).find('input').removeAttr('disabled');
+            }
+            $('#role-list div').eq(i).find('input').prop('checked', false);
+        }
+        let uhtml = '';
+        let pagetr = '';
+        const msgSign = _.find(ARCHIVE_ENCRYPTION_LIST, {rpt_id: me.currentNode.ID});
+        for (const us of uSignLogList) {
+            uhtml += '<span class="text-success"><i class="fa fa-check"></i> '+ us.role +'('+ us.name +')</span>';
+        }
+        for (let i = 1; i <= msgSign.total_page; i++) {
+            pagetr += '<tr><td>页'+ i +'</td><td>'+ me.currentNode.name +'</td><td>'+ uhtml +'</td></tr>';
+        }
+        $('#page-list').html(pagetr);
+        if (needFrame) {
+            me.currentArchivePdfPath = oss_path + '/sign/'+ me.currentArchiveUuid +'.PDF';
+            $('#iframe_made').html('<iframe src="/archive/pdf/show?file=' + me.currentArchivePdfPath + '" height="750px" width="100%" style="border: none;"></iframe>');
+        }
+        $('#sign_num').text(uSignLogList.length);
+    },
+
     _countChkedRptTpl: function () {
         let me = rptArchiveObj;
         if (me.treeObj) {
@@ -512,8 +578,9 @@ function download(url, filename) {
 
 $(function () {
     $('#download_file').click(function () {
-        if (rptArchiveObj.currentArchiveUuid) {
-            download(`https://measure-sign-pdf.oss-cn-shenzhen.aliyuncs.com/archive/${rptArchiveObj.currentArchiveUuid}.PDF`, `${rptArchiveObj.currentNode.name} ${rptArchiveObj.currentArchiveDateStr}.pdf`);
+        if (rptArchiveObj.currentArchiveUuid && rptArchiveObj.currentArchivePdfPath) {
+            console.log(rptArchiveObj.currentArchivePdfPath);
+            download(`${rptArchiveObj.currentArchivePdfPath}`, `${rptArchiveObj.currentNode.name} ${rptArchiveObj.currentArchiveDateStr}.pdf`);
         } else {
             alert('请选择打开一个报表!');
         }

+ 10 - 2
app/public/report/js/rpt_custom.js

@@ -268,7 +268,11 @@ const rptCustomObj = (function () {
         }
 
         // 初始化
-        $("#gather-select").modal('show');
+        if (resolve) {
+            setTimeout(() => { $("#gather-select").modal('show'); }, 500);
+        } else {
+            $("#gather-select").modal('show');
+        }
 
         $('#gather-select-ok').unbind('click');
         $('#gather-select-ok').bind('click', () => {rptCustomObj.resetGatherSelect(resolve);});
@@ -287,7 +291,11 @@ const rptCustomObj = (function () {
                 $('#stage-select-' + s)[0].checked = true;
             }
         }
-        $("#stage-select").modal('show');
+        if (resolve) {
+            setTimeout(() => { $("#stage-select").modal('show'); }, 500);
+        } else {
+            $("#stage-select").modal('show');
+        }
 
         $('#stage-select-ok').unbind('click');
         $('#stage-select-ok').bind('click', () => {rptCustomObj.resetStageSelect(resolve);});

+ 4 - 0
app/router.js

@@ -341,6 +341,10 @@ module.exports = app => {
     app.post('/tender/report_api/updateArchiveEncryption', sessionAuth, 'reportArchiveController.updateReportArchiveEncryption');
     app.post('/tender/report_api/removeArchiveEncryption', sessionAuth, 'reportArchiveController.removeReportArchiveEncryption');
 
+    // 电子签名
+    app.get('/tender/:id/signReport', sessionAuth, tenderCheck, uncheckTenderCheck, 'reportArchiveController.signReport');
+    app.post('/tender/:id/signReport/post', sessionAuth, tenderCheck, uncheckTenderCheck, 'reportArchiveController.signPost');
+
 
     // 变更管理
     app.get('/tender/:id/change', sessionAuth, tenderCheck, uncheckTenderCheck, 'changeController.index');

+ 47 - 0
app/service/netcasign_log.js

@@ -0,0 +1,47 @@
+'use strict';
+
+/**
+ * 网证通电子签名记录数据模型
+ *
+ * @author EllisRan
+ * @date 2021/7/15
+ * @version
+ */
+
+module.exports = app => {
+
+    class NetcasignLog extends app.BaseService {
+
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'netcasign_log';
+        }
+
+        async add(uuid, role, uid, vid) {
+            const insertData = {
+                tid: this.ctx.tender.id,
+                uid,
+                role,
+                uuid,
+                versionid: vid,
+                create_time: new Date(),
+            };
+            const operate = await this.db.insert(this.tableName, insertData);
+            return operate.affectedRows > 0;
+        }
+
+        async getLogList(tid) {
+            const sql = 'SELECT a.*, pa.name FROM ?? as a LEFT JOIN ?? as pa ON a.uid = pa.id WHERE tid = ?';
+            const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, tid];
+            return await this.db.query(sql, sqlParam);
+        }
+    }
+
+    return NetcasignLog;
+};

+ 0 - 3
app/view/ledger/audit_modal.ejs

@@ -360,9 +360,7 @@
                                                 <% } else { %>
                                                 <p style="margin: 0;"><%- auditor.opinion %></p>
                                                 <% } %>
-
                                             </div>
-
                                             <% } %>
                                         </div>
                                     </div>
@@ -536,7 +534,6 @@
                                                             class="pull-right
                                                                             <%- auditConst.statusClass[auditor.status] %>"><%- auditor.status !== auditConst.status.uncheck ? auditConst.statusString[auditor.status] : ''%>
                                                             <%- auditor.status === auditConst.status.checkNo ? user.name : '' %>
-                                                            <%- auditor.status === auditConst.status.checkNoPre ? auditors[index-1].name : '' %>
                                                         </span>
                                                     </p>
                                                     <p class="text-muted mb-0"><%- auditor.role %></p>

+ 0 - 1
app/view/profile/netcasign.ejs

@@ -135,7 +135,6 @@
             if ($(this).val()) {
                 const reader = new FileReader();
                 reader.onload = function (evt) {
-                    console.log(evt.target.result);
                     postData('/profile/sign/save', {type: 'savesign', sign_base64: evt.target.result.split(',')[1]}, function (result) {
                         toastr.success('签名图设置成功');
                         const html = '<img src="'+ evt.target.result +'" width="90">';

+ 1 - 1
app/view/profile/sign_modal.ejs

@@ -49,7 +49,7 @@
                 </button>
             </div>
             <div class="modal-body">
-                <h6>获取电子签章客户端配置信息失败:数字证书服务连接失败,请确认已安装最新的数字证书驱动</h6>
+                <h6>获取电子签章客户端配置信息失败:数字证书服务连接失败,请确认已安装最新的数字证书驱动并且开启了客户端</h6>
                 <div class="custom-control mb-2">请下载下列软件解压并依次安装,安装完成后刷新本页重新加载</div>
                 <div class="custom-control mb-2">网证通安全客户端:<a href="http://dl.cnca.net/Drivers/网证通安全客户端.zip" target="_blank">网证通安全客户端</a></div>
                 <div class="custom-control mb-2">网证通电子签章软件:<a href="http://dl.cnca.net/Drivers/NETCA电子签章软件.zip" target="_blank">网证通电子签章软件</a></div>

+ 32 - 35
app/view/report/archive_pdf.ejs

@@ -251,7 +251,7 @@
         <div class="toolbar">
             <div id="toolbarContainer">
                 <div id="toolbarViewer">
-                    <div style="width: 100%;text-align: center;color: #6c757d;">
+                    <div style="width: 100%;text-align: right;color: #6c757d;">
                         <span id="netcapage" class="netca_btn10">
                             <input type="number" id="pageNumber" class="toolbarField pageNumber" title="Page" value="1" size="4" min="1" tabindex="15" data-l10n-id="page">
                             <span id="numPages" class="toolbarLabel"></span>
@@ -491,23 +491,23 @@
 </div>
 </div>
 <!-- 弹窗内容开始 -->
-<div class="modal-background">
-    <div class="modal-content">
-        <div class="modal-header">
-            <span class="modal-close-button hide-modal">×</span>
-            <h5 class="modal-title">找不到签名驱动</h5>
-        </div>
-        <div class="modal-body">
-            <div class="form-group">
-                <!--<div class="custom-control mb-2">获取电子签章客户端配置信息失败:数字证书服务连接失败,请确认已安装最新的数字证书驱动!</div>-->
-                <div class="custom-control mb-2">请下载下列软件解压并依次安装,安装完成后刷新本页重新加载</div>
-                <div class="custom-control mb-2">网证通安全客户端:<a href="http://dl.cnca.net/Drivers/网证通安全客户端.zip" target="_blank">网证通安全客户端</a></div>
-                <div class="custom-control mb-2">网证通电子签章软件:<a href="http://dl.cnca.net/Drivers/NETCA电子签章软件.zip" target="_blank">网证通电子签章软件</a></div>
-            </div>
-        </div>
-        <div class="modal-footer"><button class="modal-button hide-modal">关闭</button></div>
-    </div>
-</div>
+<!--<div class="modal-background">-->
+    <!--<div class="modal-content">-->
+        <!--<div class="modal-header">-->
+            <!--<span class="modal-close-button hide-modal">×</span>-->
+            <!--<h5 class="modal-title">找不到签名驱动</h5>-->
+        <!--</div>-->
+        <!--<div class="modal-body">-->
+            <!--<div class="form-group">-->
+                <!--&lt;!&ndash;<div class="custom-control mb-2">获取电子签章客户端配置信息失败:数字证书服务连接失败,请确认已安装最新的数字证书驱动!</div>&ndash;&gt;-->
+                <!--<div class="custom-control mb-2">请下载下列软件解压并依次安装,安装完成后刷新本页重新加载</div>-->
+                <!--<div class="custom-control mb-2">网证通安全客户端:<a href="http://dl.cnca.net/Drivers/网证通安全客户端.zip" target="_blank">网证通安全客户端</a></div>-->
+                <!--<div class="custom-control mb-2">网证通电子签章软件:<a href="http://dl.cnca.net/Drivers/NETCA电子签章软件.zip" target="_blank">网证通电子签章软件</a></div>-->
+            <!--</div>-->
+        <!--</div>-->
+        <!--<div class="modal-footer"><button class="modal-button hide-modal">关闭</button></div>-->
+    <!--</div>-->
+<!--</div>-->
 </body>
 
 </html>
@@ -520,38 +520,35 @@
 <script src="/public/netcasign/js/viewer.js"></script>
 <!--<![endif]-->
 <script src="/public/netcasign/ui/layer/layer.js"></script>
-<% if (can_netcasign) { %>
-<script src="/public/netcasign/js/netcawebsocket.js"></script>
-<script src="/public/netcasign/js/netcaseal.js"></script>
-<% } %>
+<!--<script src="/public/netcasign/js/netcawebsocket.js"></script>-->
+<!--<script src="/public/netcasign/js/netcaseal.js"></script>-->
 <script src="/public/netcasign/js/netcasealpdf.js"></script>
 <script src="/public/netcasign/example/appPackage.js"></script>
 <script>
-    const can_netcasign = <%- can_netcasign %>;
     initPDFEvent();
     function  initPDFEvent(){
         try {
 
             NetcaPDFSeal.init();
             NetcaPDFSeal.openPDFBytes('');
-            if (can_netcasign) {
-                demonstrationFn();
-            }
+            // if (can_netcasign) {
+            //     demonstrationFn();
+            // }
             // NetcaPDFSeal.openPDFWithUrl('https://measure-sign-pdf.oss-cn-shenzhen.aliyuncs.com/archive/072e3a20-c824-11eb-9ea7-3d3c8164295f.PDF');
         }
         catch(e) {
             alert("异常:" + e);
         }
     }
-    function openNewFile(){
-        var val = $('#openNewFile').val();
-        NetcaPDFSeal.openPDFWithUrl(val);
-    }
+    // function openNewFile(){
+    //     var val = $('#openNewFile').val();
+    //     NetcaPDFSeal.openPDFWithUrl(val);
+    // }
 
-    $(function () {
-        $('.hide-modal').on('click', function () {
-            $('.modal-background').hide();
-        });
-    })
+    // $(function () {
+    //     $('.hide-modal').on('click', function () {
+    //         $('.modal-background').hide();
+    //     });
+    // })
 
 </script>

+ 2 - 20
app/view/report/index_archive.ejs

@@ -76,18 +76,6 @@
                                     历史归档
                                 </div>
                             </div>
-                            <% if (can_netcasign) { %>
-                            <div class="panel">
-                                <div class="panel-body">
-                                    <div class="btn-group" role="group">
-                                        <button class="btn btn-outline-primary btn-sm" type="button" data-toggle="modal" data-target="#sign">
-                                            <i class="fa fa-pencil"></i><br>
-                                            电子签名 <span class="badge badge-primary">0</span>
-                                        </button>
-                                    </div>
-                                </div>
-                            </div>
-                            <% } %>
                         </div>
                     </div>
                     <div class="print-view form-view">
@@ -171,6 +159,8 @@
     const STAGE_ID = <%- stg_id %>;
     const STAGE_ORDER = <%- stg_order %>;
     const STAGE_LIST = <%- stage_list %>;
+    const can_netcasign = <%- can_netcasign %>;
+    const oss_path = JSON.parse('<%- JSON.stringify(ossPath) %>');
 
     const FOLDER_SEPERATER = '->';
 
@@ -304,14 +294,6 @@
         );
     }
 
-
-    $(function () {
-        // 多层modal关闭后的滚动bug修复
-        $('#add-sign').on('hidden.bs.modal', function (e) {
-            $(document.body).addClass('modal-open');
-        });
-    })
-
     rptArchiveObj.iniPage();
 
 </script>

+ 0 - 63
app/view/report/index_archive_modal.ejs

@@ -1,63 +0,0 @@
-<!--弹出批量电子签名-->
-<div class="modal fade" id="sign" data-backdrop="static">
-    <div class="modal-dialog modal-lg" role="document">
-        <div class="modal-content">
-            <div class="modal-header">
-                <h5 class="modal-title">电子签名</h5>
-                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
-                    <span aria-hidden="true">&times;</span>
-                </button>
-            </div>
-            <div class="modal-body">
-                <p>当前使用 #3 2021-03-02 归档报表进行签名,签名后其余归档仅能查看。</p>
-                <div class="card mb-3">
-                    <div class="card-header">选择签名位置</div>
-                    <div class="card-body py-3">
-                        <style>
-                            #role-list div:nth-child(n+5) {
-                                margin-top: 1rem;
-                            }
-                        </style>
-                        <div class="row" id="role-list">
-                            <div class="col-auto">
-                                <div class="custom-control custom-radio custom-control-inline">
-                                    <input type="radio" id="customRadioInline1" name="customRadioInline" class="custom-control-input">
-                                    <label class="custom-control-label" for="customRadioInline1">编制</label>
-                                </div>
-                            </div>
-                            <div class="col-auto">
-                                <div class="custom-control custom-radio custom-control-inline">
-                                    <input type="radio" id="customRadioInline2" name="customRadioInline" class="custom-control-input">
-                                    <label class="custom-control-label" for="customRadioInline2">复核</label>
-                                </div>
-                            </div>
-                        </div>
-                    </div>
-                </div>
-                <div class="modal-height-300">
-                    <table class="table table-hover table-bordered">
-                        <thead>
-                        <tr><th colspan="4" class="text-center">签名页</th></tr>
-                        <tr><th width="60">页码</th>
-                            <th >名称</th>
-                            <th >签名</th>
-                            <!--<th width="40"><input type="checkbox"></td></th>-->
-                        </tr></thead>
-                        <tbody id="page-list">
-                        <tr><td>页1</td><td>工程费用表</td><td><span class="text-success"><i class="fa fa-check"></i> 编制(张三)</span></td><td><input type="checkbox"></td></tr>
-                        <tr><td>页2</td><td>工程费用表</td><td><span class="text-success"><i class="fa fa-check"></i> 编制(张三)</span></td><td><input type="checkbox"></td></tr>
-                        <tr><td>页3</td><td>工程费用表</td><td><span class="text-success"><i class="fa fa-check"></i> 编制(张三)</span><span class="text-success"><i class="fa fa-check"></i> 复核(李四)</span></td><td><input type="checkbox"></td></tr>
-                        <tr><td>页4</td><td>工程费用表</td><td></td><td><input type="checkbox"></td></tr>
-                        <tr><td>页5</td><td>工程费用表</td><td></td><td><input type="checkbox"></td></tr>
-                        </tbody>
-                    </table>
-                </div>
-            </div>
-            <div class="modal-footer">
-                <!--<a href="" class="btn btn-sm btn-danger mr-5" data-toggle="modal" data-target="#sign2" data-dismiss="modal">撤销签名</a>-->
-                <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">取消</button>
-                <a href="" class="btn btn-sm btn-primary">确定</a>
-            </div>
-        </div>
-    </div>
-</div>

+ 524 - 0
app/view/report/index_sign.ejs

@@ -0,0 +1,524 @@
+<% if (stg_id === -1) {%>
+<% include ../tender/tender_sub_menu.ejs %>
+<% } else { %>
+<% include ../stage/stage_sub_menu.ejs %>
+<% } %>
+<div class="panel-content">
+    <div class="panel-title">
+        <div class="title-main d-flex">
+            <% if(stg_id === -1) { %>
+            <% include ../tender/tender_sub_mini_menu.ejs %>
+            <% } else { %>
+            <% include ../stage/stage_sub_mini_menu.ejs %>
+            <% } %>
+            <div>
+                <div class="d-inline-block">
+                    <div class="dropdown" id="divSelectableStages">
+                        <button class="btn btn-sm btn-light dropdown-toggle text-primary" type="button" id="btnCurrentStage" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></button>
+                        <div class="dropdown-menu" aria-labelledby="dropdownMenuButton" id="optionSelectableStages"></div>
+                    </div>
+                </div>
+            </div>
+            <div>
+            </div>
+        </div>
+    </div>
+    <div class="content-wrap">
+        <div class="c-header p-0"></div>
+        <div class="c-body">
+            <div class="row">
+                <div class="col-auto pr-0" id="tree-view" style="width: 17%">
+                    <div class="sjs-height-1" style="overflow: auto">
+                        <div class="text-center"></div>
+                        <ul id="rptTplTree" class="ztree"></ul>
+                    </div>
+                </div>
+                <div class="col-auto" id="main-view" style="width: 83%">
+                    <div class="resize-x" id="right-spr" r-Type="width" div1="#tree-view" div2="#main-view" title="调整大小" a-type="percent"><!--调整左右高度条--></div>
+                    <div class="toolsbar-f d-flex justify-content-between position-absolute bg-light m-1">
+                        <div class="print-toolsbar p-0">
+                            <div class="panel">
+                                <div class="panel-body" id="print_div">
+                                    <div class="btn-group" role="group">
+                                        <button class="btn btn-outline-primary btn-sm" type="button" id="print_file">
+                                            <i class="fa fa-print"></i><br>
+                                            打印 <span class="badge badge-primary">0</span>
+                                        </button>
+                                    </div>
+                                </div>
+                            </div>
+                            <div class="panel">
+                                <div class="panel-body" id="export_div">
+                                    <div class="btn-group" role="group">
+                                        <button class="btn btn-outline-primary btn-sm" type="button" id="download_file">
+                                            <i class="fa fa-download"></i><br>
+                                            下载 <span class="badge badge-primary">0</span>
+                                        </button>
+                                    </div>
+                                </div>
+                            </div>
+                            <div class="panel">
+                                <div class="panel-body">
+                                    <div class="btn-group" role="group">
+                                        <div>
+                                            <button id="currentDrpArchiveSelect" type="button" class="btn btn-outline-primary btn-sm dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></button>
+                                            <div class="dropdown-menu" aria-labelledby="btnGroupDrop1" style="min-width:112px" id="drpArchiveSelect">
+                                                <!--
+                                                <a class="dropdown-item" href="#">#1 2021-02-01</a>
+                                                <a class="dropdown-item" href="#">#2 2021-02-27</a>
+                                                <a class="dropdown-item" href="javascript: void(0);" onclick="zTreeOprObj.changeOrientation(this)">横向</a>
+                                                -->
+                                            </div>
+                                        </div>
+                                    </div>
+                                </div>
+                                <div class="panel-foot text-muted">
+                                    历史归档
+                                </div>
+                            </div>
+                            <% if (can_netcasign) { %>
+                            <div class="panel">
+                                <div class="panel-body">
+                                    <div class="btn-group" role="group">
+                                        <button class="btn btn-outline-primary btn-sm" type="button" data-toggle="modal" data-target="#sign">
+                                            <i class="fa fa-pencil"></i><br>
+                                            电子签名 <span id="sign_num" class="badge badge-primary">0</span>
+                                        </button>
+                                    </div>
+                                </div>
+                            </div>
+                            <% } %>
+                        </div>
+                    </div>
+                    <div class="print-view form-view">
+                        <div class="pageContainer" id="iframe_made">
+                            <iframe src="/archive/pdf/show"  class="sjs-height-0 border-0" width="100%"></iframe>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+
+<script src="/public/js/sub_menu.js"></script>
+<script src="/public/js/div_resizer.js"></script>
+<script>
+    $.subMenu({
+        menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',
+        toMenu: '#to-menu', toMiniMenu: '#to-mini-menu',
+        key: 'menu.1.0.0',
+        miniHint: '#sub-mini-hint', hintKey: 'menu.hint.1.0.1',
+        callback: function (info) {
+            if (info.mini) {
+                $('.panel-title').addClass('fluid');
+                $('#sub-menu').removeClass('panel-sidebar');
+            } else {
+                $('.panel-title').removeClass('fluid');
+                $('#sub-menu').addClass('panel-sidebar');
+            }
+            autoFlashHeight();
+        }
+    });
+    $.divResizer({
+        select: '#right-spr',
+        callback: function () {
+            autoFlashHeight();
+        }
+    });
+</script>
+<script type="text/javascript">  autoFlashHeight();</script>
+<script src="/public/js/shares/cs_tools.js"></script>
+
+<!-- zTree -->
+<script type="text/javascript" src="/public/js/ztree/jquery.ztree.core.js"></script>
+<script type="text/javascript" src="/public/js/ztree/jquery.ztree.excheck.js"></script>
+<script type="text/javascript" src="/public/js/string_util_light.js"></script>
+<!--
+-->
+<script type="text/javascript" src="/public/js/rpt_tpl_def.js"></script>
+<script type="text/javascript" src="/public/js/common_ajax.js"></script>
+<script type="text/javascript" src="/public/js/treeDataHelper.js"></script>
+<script type="text/javascript" src="/public/js/ztree_common.js"></script>
+<script type="text/javascript" src="/public/js/storageUtil.js"></script>
+
+<script type="text/javascript" src="/public/report/js/rpt_archive.js"></script>
+<script type="text/javascript" src="/public/report/js/rpt_main.js"></script>
+<script type="text/javascript" src="/public/report/js/rpt_cfg_const.js"></script>
+
+<!--
+<script type="text/javascript" src="/public/report/js/rpt_custom.js"></script>
+-->
+
+<script src="/public/netcasign/js/base64.min.js"></script>
+<script src="/public/netcasign/js/netcawebsocket.js"></script>
+<script src="/public/netcasign/js/appPackage.js"></script>
+<script type="text/javascript">
+    let current_stage_order = -1;
+    let current_stage_id = -1;
+    let current_stage_status = -1;
+
+    let ARCHIVE_LIST = JSON.parse('<%- JSON.stringify(archiveList) %>');
+    let ARCHIVE_ENCRYPTION_LIST = JSON.parse('<%- JSON.stringify(archiveEncryptionList) %>');
+    let TOP_TREE_NODES = <%- rpt_tpl_data %>;
+
+    const CUST_TREE_NODES = <%- cust_tpl_data %>;
+    if (!(CUST_TREE_NODES.customize instanceof Array)) {
+        CUST_TREE_NODES.customize = [];
+    }
+    const ORG_CUST_TREE_NODES = JSON.parse(JSON.stringify(CUST_TREE_NODES));
+    //
+    const PROJECT_ID = <%- project_id %>;
+    const TENDER_ID = <%- tender_id %>;
+    const STAGE_ID = <%- stg_id %>;
+    const STAGE_ORDER = <%- stg_order %>;
+    const STAGE_LIST = <%- stage_list %>;
+    const can_netcasign = <%- can_netcasign %>;
+    const oss_path = JSON.parse('<%- JSON.stringify(ossPath) %>');
+    let signLogList = JSON.parse('<%- JSON.stringify(signLogList) %>');
+
+    const FOLDER_SEPERATER = '->';
+
+    if (STAGE_ORDER > 0) {
+        current_stage_order = STAGE_ORDER;
+        current_stage_id = STAGE_ID;
+//        current_stage_status = STAGE_STATUS;
+    } else if (STAGE_LIST.length > 0) {
+        current_stage_order = STAGE_LIST[STAGE_LIST.length - 1].order;
+        current_stage_id = STAGE_LIST[STAGE_LIST.length - 1].id;
+//        current_stage_status = STAGE_LIST[STAGE_LIST.length - 1].status;
+    }
+
+    buildStageSelection();
+
+    for (let item of TOP_TREE_NODES) {
+        if (item.name === '通用报表') {
+            item.name = '推荐报表';
+        }
+    }
+    const ORG_TOP_TREE_NODES = JSON.parse(JSON.stringify(TOP_TREE_NODES));
+    for (let item of TOP_TREE_NODES) {
+        item.items = JSON.parse(item.items);
+        if (item.items && item.items.length > 0) {
+            for (let dtlItem of item.items) {
+                chkAndSetNode(dtlItem);
+            }
+        }
+    }
+    for (let item of ORG_TOP_TREE_NODES) {
+        item.items = JSON.parse(item.items);
+        if (item.items && item.items.length > 0) {
+            for (let dtlItem of item.items) {
+                chkAndSetNode(dtlItem);
+            }
+        }
+    }
+    buildTplTree();
+
+    function chkAndSetNode(parentItem) {
+        if (parentItem.nodeType === 1) {
+            parentItem.isParent = true;
+        }
+        if (parentItem.items) {
+            for (let dtlItem of parentItem.items) {
+                chkAndSetNode(dtlItem);
+            }
+        }
+    }
+
+    function buildTplTree() {
+        if (TOP_TREE_NODES.length > 0) {
+            //1. 整理模板树 (原始状态的TOP_TREE_NODES包含了推荐报表与定制表,需要分割)
+            const individualNode = {id: 99999, name: '定制报表', pid: -1, rpt_type: 0, items: [], isParent: true};
+            for (let tnIdx = TOP_TREE_NODES.length - 1; tnIdx >= 0; tnIdx--) {
+                if (TOP_TREE_NODES[tnIdx].pid !== -1) {
+                    TOP_TREE_NODES[tnIdx].isParent = true;
+                    TOP_TREE_NODES[tnIdx].nodeType = 1;
+                    individualNode.items.unshift(TOP_TREE_NODES[tnIdx]);
+                    TOP_TREE_NODES.splice(tnIdx, 1);
+                }
+            }
+            // 1.1 移除未被选择的模板
+            // filterUnchkTplTreeNode(TOP_TREE_NODES[0], CUST_TREE_NODES.common);
+            TOP_TREE_NODES.unshift(individualNode); //定制在前
+            // filterUnchkTplTreeNode(TOP_TREE_NODES[0], CUST_TREE_NODES.customize);
+            //2. 原始的模板树(恢复用)
+            const individualNodeOrg = {id: 99999, name: '定制报表', pid: -1, rpt_type: 0, items: [], isParent: true};
+            for (let tnIdx = ORG_TOP_TREE_NODES.length - 1; tnIdx >= 0; tnIdx--) {
+                if (ORG_TOP_TREE_NODES[tnIdx].pid !== -1) {
+                    ORG_TOP_TREE_NODES[tnIdx].isParent = true;
+                    ORG_TOP_TREE_NODES[tnIdx].nodeType = 1;
+                    individualNodeOrg.items.unshift(ORG_TOP_TREE_NODES[tnIdx]);
+                    ORG_TOP_TREE_NODES.splice(tnIdx, 1);
+                }
+            }
+            //ORG_TOP_TREE_NODES.push(individualNode);
+            ORG_TOP_TREE_NODES.unshift(individualNodeOrg);
+        }
+    }
+
+    function buildStageSelection() {
+        if (STAGE_LIST.length === 0) {
+            $("#optionSelectableStages")[0].style.display = 'none';
+        } else {
+            $("#optionSelectableStages")[0].style.display = '';
+            $("#btnCurrentStage")[0].innerText = '第' + current_stage_order + '期';
+            $("#optionSelectableStages").empty();
+            if (STAGE_LIST.length > 0 && STAGE_ID < 0) {
+                for (let i = STAGE_LIST.length; i > 0; i--) {
+                    if (parseInt(STAGE_LIST[i - 1].order) !== current_stage_order) {
+                        const str = '<a class="dropdown-item" style="cursor:pointer" onclick="changeCurrentStage(this)" stg_id = "' + STAGE_LIST[i - 1].id + '" stg_order="' + STAGE_LIST[i - 1].order + '">第' + STAGE_LIST[i - 1].order + '期</a>';
+                        $("#optionSelectableStages").append(str);
+                    }
+                }
+            }
+        }
+        if (STAGE_ID > 0) {
+            $("#divSelectableStages")[0].style.display = 'none';
+        }
+    }
+
+    function changeCurrentStage(ele) {
+        // alert('you are selecting: ' + ele.innerText);
+        current_stage_order = parseInt(ele.attributes.stg_order.value);
+        current_stage_id = parseInt(ele.attributes.stg_id.value);
+        for (let i = STAGE_LIST.length; i > 0; i--) {
+            if (parseInt(STAGE_LIST[i - 1].order) === current_stage_order) {
+                current_stage_status = STAGE_LIST[i - 1].status;
+                break;
+            }
+        }
+        buildStageSelection();
+        //还有必要触发归档报表刷新!
+        //rptArchiveObj.onClick(null, null, rptArchiveObj.currentNode);
+        const params = {prjId: PROJECT_ID, stgId: current_stage_id};
+        $.bootstrapLoading.start();
+        CommonAjax.postXsrfEx("/tender/report_api/getReportArchive", params, 10000, true, getCookie('csrfToken_j'),
+            function(result){
+                $.bootstrapLoading.end();
+                // console.log(result);
+                ARCHIVE_LIST = result.data;
+                ARCHIVE_ENCRYPTION_LIST = result.encryptionData;
+                TOP_TREE_NODES = JSON.parse(JSON.stringify(ORG_TOP_TREE_NODES));
+                rptArchiveObj.iniPage();
+            }, function(err){
+                $.bootstrapLoading.end();
+            }, function(ex){
+                $.bootstrapLoading.end();
+            }
+        );
+    }
+
+
+    const netcaSignData = JSON.parse(unescape('<%- escape(JSON.stringify(netcaSignData)) %>'));
+    $(function () {
+        // 多层modal关闭后的滚动bug修复
+        $('#add-sign').on('hidden.bs.modal', function (e) {
+            $(document.body).addClass('modal-open');
+        });
+
+        let signDigest = '';
+
+        $('#sign_pdf').click(function () {
+            $(this).attr('disabled', true);
+            $(this).text('签名条件判断中...')
+            // 判断是否已选中签名位置
+            const val = $("input[name='sign_role']:checked").val();
+            if (!val) {
+                toastr.error('请选择签名位置');
+                $(this).attr('disabled', false);
+                $(this).text('确定')
+                return false;
+            }
+            console.log(val);
+            // 判断是否已绑定ukey
+            if (!netcaSignData || !netcaSignData.keyId) {
+                // toastr.error('未绑定网证通ukey,请前往个人页面-签字管理绑定后刷新页面再试');
+                $('#netcasign-link h6').html('未绑定网证通ukey,请前往<a href="/profile/sign/netca" target="_blank">个人页面-签字管理</a>绑定后刷新页面再试');
+                $('#netcasign-link').modal('show');
+                $(this).attr('disabled', false);
+                $(this).text('确定')
+                return false;
+            }
+            // 判断是否已上传签名图
+            if (!netcaSignData || !netcaSignData.sign_base64) {
+                // toastr.error('未设置签名图,请前往个人页面-签字管理设置后刷新页面再试');
+                $('#netcasign-link h6').html('未设置签名图,请前往<a href="/profile/sign/netca" target="_blank">个人页面-签字管理</a>设置后刷新页面再试');
+                $('#netcasign-link').modal('show');
+                $(this).attr('disabled', false);
+                $(this).text('确定');
+                return false;
+            }
+            // 判断是否有驱动,并禁止多个ukey插入
+
+            const _self = $(this);
+            const params={};
+            NetcaPKI.GetCertList(params)
+                .Then(function(res){
+                    if (res && res.deviceCount === 0) {
+                        toastr.error('请插入UKey获取证书');
+                        _self.attr('disabled', false);
+                        _self.text('确定');
+                        return false;
+                        return;
+                    } else if (res.deviceCount === 1) {
+                        const cert = res.certInfo[0];
+                        const keyId = getNetcaKeyId(cert);
+                        // data.name = cert.subjectCN;
+                        // 判断插入的ukey是否对应上已绑用户
+                        if (keyId !== netcaSignData.keyId) {
+                            toastr.error('检测到UKey与已绑定的值不相同,请插入对应用户的UKey');
+                            _self.attr('disabled', false);
+                            _self.text('确定');
+                            return false;
+                        }
+                        const selectType = "{\"UIFlag\":\"default\", \"InValidity\":true,\"Type\":\"signature\", \"Method\":\"device\",\"Value\":\"any\"}";
+                        const selectCondition = "IssuerCN~'NETCA' && InValidity='True' && CertType='Signature'";
+
+                        netca_getCertStringAttribute(null, selectType, selectCondition, -1, successGetCertEncodeCallBack,
+                            failedCallBack);
+
+                        function successGetCertEncodeCallBack(res) {
+                            if (signDigest !== '') {
+                                signPdf();
+                            } else {
+                                // 开始签名!
+                                const encrypt = rptArchiveObj.currentEncryptionList.encryption[val];
+                                const requestJson = {
+                                    width: encrypt.areas[0].width,
+                                    height: encrypt.areas[0].height,
+                                    x: encrypt.areas[0].Top,
+                                    y: encrypt.areas[0].Left,
+                                    page: rptArchiveObj.currentEncryptionList.total_page ? rptArchiveObj.currentEncryptionList.total_page : 1,
+                                    // page: 0,
+                                    // url: 'https://measure-sign-pdf.oss-cn-shenzhen.aliyuncs.com/archive/'+ rptArchiveObj.currentArchiveUuid + '.PDF',
+                                    url: rptArchiveObj.currentArchivePdfPath,
+                                    hashAlgo: 'SHA256',
+                                    documentId: new Date().getTime(),
+                                    imageBase64: netcaSignData.sign_base64,
+                                    certContent: res.certCode,
+                                }
+                                // 先获取摘要值
+                                postData('/tender/'+ TENDER_ID +'/signReport/post', { type: 'assemblyDigest', requestJson}, function (result) {
+                                    if(result.code === 0) {
+                                        signDigest = result.data;
+                                        signPdf();
+                                    } else {
+                                        failedCallBack(result);
+                                    }
+                                }, function () {
+                                    _self.attr('disabled', false);
+                                    _self.text('确定');
+                                    return false;
+                                })
+                            }
+                        }
+                        function signPdf() {
+                            const certEncode = "";
+                            const tbs = utf8_to_b64(signDigest);
+                            const _tsaURL = "http://tsa.cnca.net/NETCATimeStampServer/TSAServer.jsp";
+                            const _includeCertOption = 2;
+                            const params = {
+                                cert: { //证书(CertificateParams)
+                                    encode: certEncode, //可选字段但不能为空
+                                    type: selectType,
+                                    condition: selectCondition
+                                },
+                                data: { //数据(DataParams)
+                                    text: tbs
+                                },
+                                useSubjectKeyId: 0, //是否使用主体密钥标识符来标识证书,默认为true
+                                useQ7: false, //布尔值	是否使用国密Q7的方式,默认为false
+                                detached: 0,
+                                tsaURL: _tsaURL,
+                                includeCertOption: _includeCertOption//整数	包含证书的标识
+                            };
+                            NetcaPKI.signedDataSign(params)
+                                .Then(function (res) {
+                                    _self.text('签名中...');
+                                    let retSignValue = res.signValue;
+
+                                    //处理base填充问题,这里去掉Base64的填充内容
+                                    if(retSignValue!=null&&retSignValue.length>2)
+                                    {
+                                        var paddingCount=0;
+                                        if(retSignValue[retSignValue.length-1]=="=")
+                                        {
+                                            paddingCount++;
+                                        }
+
+                                        if(retSignValue[retSignValue.length-2]=="=")
+                                        {
+                                            paddingCount++;
+                                        }
+
+                                        if(paddingCount>0)
+                                        {
+                                            //裁剪后面的=号
+                                            retSignValue=retSignValue.substring(0,retSignValue.length-paddingCount);
+                                        }
+                                    }
+                                    if(res.result==-5||res.result==-81)
+                                    {
+                                        toastr.error("密码错误,密码重试次数为:"+res.retrynum);
+                                        _self.attr('disabled', false);
+                                        _self.text('确定');
+                                        return false;
+                                    }
+                                    // 签名pdf
+                                    const rJson = {
+                                        fileName: rptArchiveObj.currentArchiveUuid,
+                                        returnType: 2,
+                                        digest : signDigest,
+                                        p7Base64: retSignValue,
+                                    }
+                                    const encrypt = rptArchiveObj.currentEncryptionList.encryption[val];
+                                    // 生成pdf
+                                    postData('/tender/'+ TENDER_ID +'/signReport/post', { type: 'assemblyPdf', requestJson: rJson, role: encrypt.name }, function (result) {
+                                        toastr.success("已成功签名");
+                                        signLogList = result;
+                                        rptArchiveObj._updateSignHtmlAndFrame(true);
+                                        $('#sign').modal('hide');
+                                        _self.attr('disabled', false);
+                                        _self.text('确定');
+                                    }, function () {
+                                        _self.attr('disabled', false);
+                                        _self.text('确定');
+                                        return false;
+                                    })
+                                })
+                                .Catch(function (res) {
+                                    failedCallBack(res);
+                                })
+                        }
+                        function failedCallBack(res) {
+                            toastr.error(res.msg);
+                            _self.attr('disabled', false);
+                            _self.text('确定');
+                            return false;
+                        }
+                    } else if (res.deviceCount === 2) {
+                        toastr.error('请拔出多个UKey,保留一个并获取证书信息');
+                        _self.attr('disabled', false);
+                        _self.text('确定');
+                        return false;
+                    } else {
+                        toastr.error('获取证书失败');
+                        _self.attr('disabled', false);
+                        _self.text('确定');
+                        return false;
+                    }
+                })
+                .Catch(function (res) {
+                    getCertListFail(res);
+                    _self.attr('disabled', false);
+                    _self.text('确定');
+                    return false;
+                });
+        })
+    })
+
+    rptArchiveObj.iniPage();
+
+</script>
+

+ 104 - 0
app/view/report/index_sign_modal.ejs

@@ -0,0 +1,104 @@
+<!--弹出批量电子签名-->
+<div class="modal fade" id="sign" data-backdrop="static">
+    <div class="modal-dialog modal-lg" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">电子签名</h5>
+                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                    <span aria-hidden="true">&times;</span>
+                </button>
+            </div>
+            <div class="modal-body">
+                <p>当前使用 #3 2021-03-02 归档报表进行签名,签名后其余归档仅能查看。</p>
+                <div class="card mb-3">
+                    <div class="card-header">选择签名位置</div>
+                    <div class="card-body py-3">
+                        <style>
+                            #role-list div:nth-child(n+5) {
+                                margin-top: 1rem;
+                            }
+                        </style>
+                        <div class="row" id="role-list">
+                            <!--<div class="col-auto">-->
+                                <!--<div class="custom-control custom-radio custom-control-inline">-->
+                                    <!--<input type="radio" id="customRadioInline1" name="customRadioInline" class="custom-control-input">-->
+                                    <!--<label class="custom-control-label" for="customRadioInline1">编制</label>-->
+                                <!--</div>-->
+                            <!--</div>-->
+                            <!--<div class="col-auto">-->
+                                <!--<div class="custom-control custom-radio custom-control-inline">-->
+                                    <!--<input type="radio" id="customRadioInline2" name="customRadioInline" class="custom-control-input">-->
+                                    <!--<label class="custom-control-label" for="customRadioInline2">复核</label>-->
+                                <!--</div>-->
+                            <!--</div>-->
+                        </div>
+                    </div>
+                </div>
+                <div class="modal-height-300">
+                    <table class="table table-hover table-bordered">
+                        <thead>
+                        <tr><th colspan="4" class="text-center">签名页</th></tr>
+                        <tr><th width="60">页码</th>
+                            <th >名称</th>
+                            <th >签名</th>
+                            <!--<th width="40"><input type="checkbox"></td></th>-->
+                        </tr></thead>
+                        <tbody id="page-list">
+                        <!--<tr><td>页1</td><td>工程费用表</td><td><span class="text-success"><i class="fa fa-check"></i> 编制(张三)</span></td><td><input type="checkbox"></td></tr>-->
+                        <!--<tr><td>页2</td><td>工程费用表</td><td><span class="text-success"><i class="fa fa-check"></i> 编制(张三)</span></td><td><input type="checkbox"></td></tr>-->
+                        <!--<tr><td>页3</td><td>工程费用表</td><td><span class="text-success"><i class="fa fa-check"></i> 编制(张三)</span><span class="text-success"><i class="fa fa-check"></i> 复核(李四)</span></td><td><input type="checkbox"></td></tr>-->
+                        <!--<tr><td>页4</td><td>工程费用表</td><td></td><td><input type="checkbox"></td></tr>-->
+                        <!--<tr><td>页5</td><td>工程费用表</td><td></td><td><input type="checkbox"></td></tr>-->
+                        </tbody>
+                    </table>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <!--<a href="" class="btn btn-sm btn-danger mr-5" data-toggle="modal" data-target="#sign2" data-dismiss="modal">撤销签名</a>-->
+                <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">取消</button>
+                <button type="button" id="sign_pdf" class="btn btn-sm btn-primary">确定</button>
+            </div>
+        </div>
+    </div>
+</div>
+<div class="modal fade" id="netcasign-link" >
+    <div class="modal-dialog" role="document">
+        <form class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">签名设置</h5>
+                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                    <span aria-hidden="true">×</span>
+                </button>
+            </div>
+            <div class="modal-body">
+                <h6></h6>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">关闭</button>
+                <a href="/tender/<%- tender.id %>/signReport" class="btn btn-sm btn-primary">刷新页面</a>
+            </div>
+        </form>
+    </div>
+</div>
+<div class="modal fade" id="netcasign-drive">
+    <div class="modal-dialog" role="document">
+        <form class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">找不到签名驱动</h5>
+                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                    <span aria-hidden="true">×</span>
+                </button>
+            </div>
+            <div class="modal-body">
+                <h6>获取电子签章客户端配置信息失败:数字证书服务连接失败,请确认已安装最新的数字证书驱动并且开启了客户端!</h6>
+                <div class="custom-control mb-2">请下载下列软件解压并依次安装,安装完成后刷新本页重新加载</div>
+                <div class="custom-control mb-2">网证通安全客户端:<a href="http://dl.cnca.net/Drivers/网证通安全客户端.zip" target="_blank">网证通安全客户端</a></div>
+                <div class="custom-control mb-2">网证通电子签章软件:<a href="http://dl.cnca.net/Drivers/NETCA电子签章软件.zip" target="_blank">网证通电子签章软件</a></div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">关闭</button>
+                <a href="/tender/<%- tender.id %>/signReport" class="btn btn-sm btn-primary">刷新页面</a>
+            </div>
+        </form>
+    </div>
+</div>

+ 23 - 15
app/view/stage/audit_modal.ejs

@@ -112,7 +112,6 @@
                             </div>
                         </div>
                         <div class="col-8 modal-height-500" style="overflow: auto">
-
                             <% ctx.stage.auditHistory.forEach((auditors, idx) => { %>
                                 <!-- 展开/收起历史流程 -->
                                 <% if(idx === ctx.stage.auditHistory.length - 1 && ctx.stage.auditHistory.length !== 1) { %>
@@ -219,11 +218,10 @@
                                                     <div class="card-body p-3">
                                                         <div class="card-text">
                                                             <p class="mb-1"><span class="h5"><%- auditor.name %></span>
-                                                                <span
-                                                                    class="pull-right
-                                                                                    <%- auditConst.statusClass[auditor.status] %>"><%- auditor.status !== auditConst.status.uncheck ? auditConst.statusString[auditor.status] : ''%>
+                                                                <span class="pull-right <%- auditConst.statusClass[auditor.status] %>">
+                                                                    <%- auditor.status !== auditConst.status.uncheck ? auditConst.statusString[auditor.status] : ''%>
                                                                     <%- auditor.status === auditConst.status.checkNo ? ctx.stage.user.name : '' %>
-                                                                    <%- auditor.status === auditConst.status.checkNoPre ? auditors[index-1].name : '' %>
+                                                                    <%- auditor.status === auditConst.status.checkNoPre ? auditors[index+1].name : '' %>
                                                                 </span>
                                                             </p>
                                                             <p class="text-muted mb-0"><%- auditor.role %></p>
@@ -408,11 +406,10 @@
                                                 <div class="card-body p-3">
                                                     <div class="card-text">
                                                         <p class="mb-1"><span class="h5"><%- auditor.name %></span>
-                                                            <span
-                                                                class="pull-right
-                                                                                        <%- auditConst.statusClass[auditor.status] %>"><%- auditor.status !== auditConst.status.uncheck ? auditConst.statusString[auditor.status] : ''%>
+                                                            <span class="pull-right <%- auditConst.statusClass[auditor.status] %>">
+                                                                <%- auditor.status !== auditConst.status.uncheck ? auditConst.statusString[auditor.status] : ''%>
                                                                 <%- auditor.status === auditConst.status.checkNo ? ctx.stage.user.name : '' %>
-                                                                <%- auditor.status === auditConst.status.checkNoPre ? auditors[index-1].name : '' %>
+                                                                <%- auditor.status === auditConst.status.checkNoPre ? auditors[index+1].name : '' %>
                                                             </span>
                                                         </p>
                                                         <p class="text-muted mb-0"><%- auditor.role %></p>
@@ -572,7 +569,13 @@
                                                                 id="inlineRadio2"
                                                                 value="<%- auditConst.status.checkNoPre %>">
                                                             <label class="form-check-label" for="inlineRadio2">退回上一审批人
-                                                                <%- auditors[index-1].status === auditConst.status.checkAgain ? auditors[index-3].name : auditors[index-1].name %></label>
+                                                                <% for (const [i, a] of auditors.entries()) { %>
+                                                                    <% if (a.aid === ctx.stage.curAuditor.aid) { %>
+                                                                        <%- auditors[i-1].name %>
+                                                                        <% break; %>
+                                                                    <% } %>
+                                                                <% } %>
+                                                            </label>
                                                         </div>
                                                         <% } %>
                                                     </div>
@@ -614,11 +617,10 @@
                                                 <div class="card-body p-3">
                                                     <div class="card-text">
                                                         <p class="mb-1"><span class="h5"><%- auditor.name %></span>
-                                                            <span
-                                                                class="pull-right
-                                                                                        <%- auditConst.statusClass[auditor.status] %>"><%- auditor.status !== auditConst.status.uncheck ? auditConst.statusString[auditor.status] : ''%>
+                                                            <span class="pull-right <%- auditConst.statusClass[auditor.status] %>">
+                                                                <%- auditor.status !== auditConst.status.uncheck ? auditConst.statusString[auditor.status] : ''%>
                                                                 <%- auditor.status === auditConst.status.checkNo ? ctx.stage.user.name : '' %>
-                                                                <%- auditor.status === auditConst.status.checkNoPre ? auditors[index-1].name : '' %>
+                                                                <%- auditor.status === auditConst.status.checkNoPre ? auditors[index+1].name : '' %>
                                                             </span>
                                                         </p>
                                                         <p class="text-muted mb-0"><%- auditor.role %></p>
@@ -646,7 +648,13 @@
                                                                 id="inlineRadio2"
                                                                 value="<%- auditConst.status.checkNoPre %>">
                                                             <label class="form-check-label" for="inlineRadio2">退回上一审批人
-                                                                <%- auditors[index-1].status === auditConst.status.checkAgain || auditors[index-1].status === auditConst.status.checkNoPre ? auditors[index-3].name : auditors[index-1].name %></label>
+                                                                <% for (const [i, a] of auditors.entries()) { %>
+                                                                    <% if (a.aid === ctx.stage.curAuditor.aid) { %>
+                                                                        <%- auditors[i-1].name %>
+                                                                        <% break; %>
+                                                                    <% } %>
+                                                                <% } %>
+                                                            </label>
                                                         </div>
                                                         <% } %>
                                                     </div>

+ 7 - 7
app/view/stage/stage_sub_menu.ejs

@@ -68,13 +68,13 @@
                 </li>
             </ul>
         </div>
-        <!--<div class="nav-box">-->
-            <!--<ul class="nav-list list-unstyled">-->
-                <!--<li class="<% if (ctx.url === '/tender/' + ctx.tender.id + '/measure/stage/' + ctx.stage.order + '/archiveReport') { %>active<% } %>">-->
-                    <!--<a href="/tender/<%- ctx.tender.id %>/measure/stage/<%- ctx.stage.order %>/archiveReport"><span class="ml-3">归档报表</span></a>-->
-                <!--</li>-->
-            <!--</ul>-->
-        <!--</div>-->
+        <div class="nav-box">
+            <ul class="nav-list list-unstyled">
+                <li class="<% if (ctx.url === '/tender/' + ctx.tender.id + '/measure/stage/' + ctx.stage.order + '/archiveReport') { %>active<% } %>">
+                    <a href="/tender/<%- ctx.tender.id %>/measure/stage/<%- ctx.stage.order %>/archiveReport"><span class="ml-3">归档报表</span></a>
+                </li>
+            </ul>
+        </div>
         <% if (ctx.session.sessionUser.is_admin) { %>
         <div class="nav-box">
             <ul class="nav-list list-unstyled">

+ 7 - 7
app/view/stage/stage_sub_mini_menu.ejs

@@ -70,13 +70,13 @@
                 </li>
             </ul>
         </div>
-        <!--<div class="nav-box">-->
-            <!--<ul class="nav-list list-unstyled">-->
-                <!--<li class="<% if (ctx.url === '/tender/' + ctx.tender.id + '/measure/stage/' + ctx.stage.order + '/archiveReport') { %>active<% } %>">-->
-                    <!--<a href="/tender/<%- ctx.tender.id %>/measure/stage/<%- ctx.stage.order %>/archiveReport"><span class="ml-3">归档报表</span></a>-->
-                <!--</li>-->
-            <!--</ul>-->
-        <!--</div>-->
+        <div class="nav-box">
+            <ul class="nav-list list-unstyled">
+                <li class="<% if (ctx.url === '/tender/' + ctx.tender.id + '/measure/stage/' + ctx.stage.order + '/archiveReport') { %>active<% } %>">
+                    <a href="/tender/<%- ctx.tender.id %>/measure/stage/<%- ctx.stage.order %>/archiveReport"><span class="ml-3">归档报表</span></a>
+                </li>
+            </ul>
+        </div>
         <% if (ctx.session.sessionUser.is_admin) { %>
             <div class="nav-box">
                 <ul class="nav-list list-unstyled">

+ 12 - 5
app/view/tender/tender_sub_menu.ejs

@@ -58,11 +58,18 @@
                 <li <% if (ctx.url === '/tender/' + ctx.tender.id + '/report') { %>class="active"<% } %>><a href="/tender/<%- ctx.tender.id %>/report" class="h3"><i class="fa fa-file-text-o fa-fw"></i> <span>报表</span></a></li>
             </ul>
         </div>
-        <!--<div class="nav-box">-->
-            <!--<ul class="nav-list list-unstyled">-->
-                <!--<li <% if (ctx.url === '/tender/' + ctx.tender.id + '/archiveReport') { %>class="active"<% } %>><a href="/tender/<%- ctx.tender.id %>/archiveReport"><i class="fa fa-archive fa-fw"></i> <span>归档报表</span></a></li>-->
-            <!--</ul>-->
-        <!--</div>-->
+        <div class="nav-box">
+            <ul class="nav-list list-unstyled">
+                <li <% if (ctx.url === '/tender/' + ctx.tender.id + '/archiveReport') { %>class="active"<% } %>><a href="/tender/<%- ctx.tender.id %>/archiveReport" class="h3"><i class="fa fa-archive fa-fw"></i> <span>归档报表</span></a></li>
+            </ul>
+        </div>
+        <% if (ctx.session.sessionProject.page_show.openNetCaSign) { %>
+        <div class="nav-box">
+            <ul class="nav-list list-unstyled">
+                <li <% if (ctx.url === '/tender/' + ctx.tender.id + '/signReport') { %>class="active"<% } %>><a href="/tender/<%- ctx.tender.id %>/signReport" class="h3"><i class="fa fa-archive fa-fw"></i> <span>电子签名</span></a></li>
+            </ul>
+        </div>
+        <% } %>
         <div class="side-fold"><a href="javascript: void(0)" data-toggle="tooltip" data-placement="top" data-original-title="折叠侧栏" id="to-mini-menu"><i class="fa fa-upload fa-rotate-270"></i></a></div>
     </div>
 </div>

+ 7 - 5
app/view/tender/tender_sub_mini_menu.ejs

@@ -60,11 +60,13 @@
                 <li <% if (ctx.url === '/tender/' + ctx.tender.id + '/report') { %>class="active"<% } %>><a href="/tender/<%- ctx.tender.id %>/report" class="h3"><i class="fa fa-file-text-o fa-fw"></i> <span>报表</span></a></li>
             </ul>
         </div>
-        <!--<div class="nav-box">-->
-            <!--<ul class="nav-list list-unstyled">-->
-                <!--<li <% if (ctx.url === '/tender/' + ctx.tender.id + '/archiveReport') { %>class="active"<% } %>><a href="/tender/<%- ctx.tender.id %>/archiveReport"><i class="fa fa-archive fa-fw"></i> <span>归档报表</span></a></li>-->
-            <!--</ul>-->
-        <!--</div>-->
+        <% if (ctx.session.sessionProject.page_show.openNetCaSign) { %>
+            <div class="nav-box">
+                <ul class="nav-list list-unstyled">
+                    <li <% if (ctx.url === '/tender/' + ctx.tender.id + '/signReport') { %>class="active"<% } %>><a href="/tender/<%- ctx.tender.id %>/signReport" class="h3"><i class="fa fa-archive fa-fw"></i> <span>电子签名</span></a></li>
+                </ul>
+            </div>
+        <% } %>
         <div class="side-fold"><a href="javascript: void(0);" data-toggle="tooltip" data-placement="top" data-original-title="展开侧栏" id="to-menu"><i class="fa fa-upload fa-rotate-90"></i></a></div>
     </div>
 </div>