Browse Source

归档基本功能实现

TonyKang 4 years ago
parent
commit
12482c73c6

+ 275 - 0
app/controller/report_archive_controller.js

@@ -0,0 +1,275 @@
+'use strict';
+
+/**
+ * Created by Tony on 2021/3/31.
+ */
+
+const path = require('path');
+const uuidV1 = require('uuid').v1;
+const fs = require('fs');
+const MAX_ARCHIVE = 3;
+const tenderMenu = require('../../config/menu').tenderMenu;
+const measureType = require('../const/tender').measureType;
+const fsUtil = require('../public/js/fsUtil');
+const auditConst = require('../const/audit');
+
+module.exports = app => {
+    class ReportController extends app.BaseController {
+
+        async index(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 = [];
+            // 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);
+                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);
+                }
+            } 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);
+                // 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);
+                }
+            }
+            let rpt_tpl_items = '{ customize: [], common: [] }';
+            if (custTreeNodes.length > 0) {
+                rpt_tpl_items = custTreeNodes[0].rpt_tpl_items;
+            }
+            //
+            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,
+            };
+            await this.layout('report/index_archive.ejs', renderData);
+        }
+
+        async getReportArchive(ctx) {
+            const params = JSON.parse(ctx.request.body.params);
+            // ctx.body = await this._getReport(ctx, params);
+
+            const archives = await ctx.service.rptArchive.getPrjStgArchive(params.prjId, params.stgId);
+            let archiveList = [];
+            if (archives.length > 0) {
+                archiveList = JSON.parse(archives[0].content);
+            }
+            ctx.body = {
+                data: archiveList,
+            };
+        }
+
+        async addReportArchive(ctx) {
+            try {
+                const stream = await ctx.getFileStream();
+                const prjId = ctx.params.prjId;
+                const stgId = ctx.params.stgId;
+                const rptId = ctx.params.rptId;
+                const newName = uuidV1();
+                const fileName = newName + '.PDF';
+                // console.log('adding fileName: ' + fileName);
+                await ctx.helper.saveStreamFile(stream, path.join(this.app.baseDir, 'app', 'public/archive', fileName));
+                const updateDate = new Date();
+                const montStr = (updateDate.getMonth() + 1) < 10 ? ('0' + (updateDate.getMonth() + 1)) : ((updateDate.getMonth() + 1));
+                const dateStr = (updateDate.getDate()) < 10 ? ('0' + (updateDate.getDate())) : ((updateDate.getDate()));
+                const dtStr = updateDate.getFullYear() + '-' + montStr + '-' + dateStr;
+                const orgArchiveList = await ctx.service.rptArchive.getPrjStgArchive(prjId, stgId);
+                if (orgArchiveList.length > 0) {
+                    const contentArr = JSON.parse(orgArchiveList[0].content);
+                    let hasArchive = false;
+                    for (const item of contentArr) {
+                        if (item.rpt_id === rptId) {
+                            hasArchive = true;
+                            if (item.items.length >= MAX_ARCHIVE) {
+                                // 超出界限,需要删除时间最旧的那个
+                                let rmIdx = 0;
+                                for (let idx = 1; idx < item.items.length; idx++) {
+                                    if (item.items[rmIdx].updateDate_time < item.items[idx].updateDate_time) {
+                                        rmIdx = idx;
+                                    }
+                                }
+                                item.items.splice(rmIdx, 1);
+                            }
+                            const newItem = { uuid: newName, updateDate_time: dtStr };
+                            item.items.push(newItem);
+                            break;
+                        }
+                    }
+                    if (!hasArchive) {
+                        // 表示有新的模板需要添加
+                        contentArr.push({ rpt_id: rptId, items: [{ uuid: newName, updateDate_time: dtStr }] });
+                    }
+                    const updatedRst = await ctx.service.rptArchive.updateArchive(orgArchiveList[0].id, prjId, stgId, contentArr);
+                    // console.log(updatedRst);
+                    ctx.body = { err: 0, msg: '', data: { fileName, updateDate, addedRst: contentArr } };
+                } else {
+                    // 需要增加
+                    const archiveArr = [];
+                    archiveArr.push({ rpt_id: rptId, items: [{ uuid: newName, updateDate_time: dtStr }] });
+                    const addedRst = await ctx.service.rptArchive.createArchive(prjId, stgId, archiveArr);
+                    // console.log(addedRst);
+                    ctx.body = { err: 0, msg: '', data: { fileName, updateDate, addedRst: archiveArr } };
+                }
+            } catch (err) {
+                this.log(err);
+                ctx.body = { err: 1, msg: err.toString(), data: null };
+            }
+        }
+
+        async updateReportArchive(ctx) {
+            try {
+                const stream = await ctx.getFileStream();
+                const prjId = ctx.params.prjId;
+                const stgId = ctx.params.stgId;
+                const rptId = ctx.params.rptId;
+                const orgName = ctx.params.orgName;
+                const fileName = orgName + '.PDF';
+                console.log('updating fileName: ' + fileName);
+                await ctx.helper.saveStreamFile(stream, path.join(this.app.baseDir, 'app', 'public/archive', fileName));
+                const updateDate = new Date();
+                const montStr = (updateDate.getMonth() + 1) < 10 ? ('0' + (updateDate.getMonth() + 1)) : ((updateDate.getMonth() + 1));
+                const dateStr = (updateDate.getDate()) < 10 ? ('0' + (updateDate.getDate())) : ((updateDate.getDate()));
+                const dtStr = updateDate.getFullYear() + '-' + montStr + '-' + dateStr;
+                const orgArchiveList = await ctx.service.rptArchive.getPrjStgArchive(prjId, stgId);
+                if (orgArchiveList.length > 0) {
+                    const contentArr = JSON.parse(orgArchiveList[0].content);
+                    for (const item of contentArr) {
+                        if (item.rpt_id === rptId) {
+                            if (item.items && item.items.length > 0) {
+                                for (const subItem of item.items) {
+                                    if (subItem.uuid === orgName) {
+                                        subItem.updateDate_time = dtStr;
+                                        break;
+                                    }
+                                }
+                            } else {
+                                item.items = [{ uuid: orgName, updateDate_time: dtStr }];
+                            }
+                            break;
+                        }
+                    }
+                    const updatedRst = await ctx.service.rptArchive.updateArchive(orgArchiveList[0].id, prjId, stgId, contentArr);
+                    // console.log(updatedRst);
+                    ctx.body = { err: 0, msg: '', data: { fileName, updateDate, updatedRst: contentArr } };
+                } else {
+                    // 需要增加
+                    const archiveArr = [];
+                    archiveArr.push({ rpt_id: rptId, items: [{ uuid: orgName, updateDate_time: dtStr }] });
+                    const updatedRst = await ctx.service.rptArchive.createArchive(prjId, stgId, archiveArr);
+                    // console.log(updatedRst);
+                    ctx.body = { err: 0, msg: '', data: { fileName, updateDate, updatedRst: archiveArr } };
+                }
+            } catch (err) {
+                this.log(err);
+                ctx.body = { err: 1, msg: err.toString(), data: null };
+            }
+        }
+
+        async removeReportArchive(ctx) {
+            try {
+                const prjId = ctx.params.prjId;
+                const stgId = ctx.params.stgId;
+                const rptId = ctx.params.rptId;
+                const orgName = ctx.params.orgName;
+                const fileName = orgName + '.PDF';
+                console.log('removing fileName: ' + fileName);
+                const fullName = path.join(this.app.baseDir, 'app', 'public/archive', fileName);
+                // await ctx.helper.saveStreamFile(stream, path.join(this.app.baseDir, 'app', 'public/archive', fileName));
+                fs.stat(fullName, function(err, data) {
+                    if (err) {
+                        console.log(err);
+                    } else {
+                        fs.unlink(fullName, function(err) {
+                            if (err) {
+                                console.log(err);
+                            }
+                        });
+                    }
+                });
+                const orgArchiveList = await ctx.service.rptArchive.getPrjStgArchive(prjId, stgId);
+                if (orgArchiveList.length > 0) {
+                    const contentArr = JSON.parse(orgArchiveList[0].content);
+                    for (const item of contentArr) {
+                        if (item.rpt_id === rptId) {
+                            if (item.items && item.items.length > 0) {
+                                for (const subIdx in item.items) {
+                                    if (item.items[subIdx].uuid === orgName) {
+                                        item.items.splice(subIdx, 1);
+                                        break;
+                                    }
+                                }
+                            }
+                            break;
+                        }
+                    }
+                    const updatedRst = await ctx.service.rptArchive.updateArchive(prjId, stgId, contentArr);
+                    ctx.body = { err: 0, msg: '', data: { fileName, updatedRst } };
+                } else {
+                    ctx.body = { err: 0, msg: '', data: { fileName, updatedRst: null } };
+                }
+            } catch (err) {
+                this.log(err);
+                ctx.body = { err: 1, msg: err.toString(), data: null };
+            }
+        }
+
+        async addMultiReportArchive(ctx, params) {
+            // 暂时不支持
+        }
+
+        async getArchivedFileByUUID(ctx) {
+            // console.log('downloading : ' + ctx.params.uuid);
+            const uuid = ctx.params.uuid;
+            const rptName = ctx.params.rptName;
+            const suffix = '.PDF';
+            try {
+                const rptNameURI = encodeURI(rptName);
+                const filePath = this.app.baseDir + '/app/public/archive/';
+                // console.log('filePath: ' + filePath);
+                // await this.ctx.helper.recursiveMkdirSync(this.app.baseDir + '/app/public/download');
+                ctx.set({
+                    'Content-Type': 'application/vnd.openxmlformats',
+                    'Content-Disposition': 'attachment; filename="' + rptNameURI + suffix + "\"; filename*=utf-8''" + rptNameURI + suffix,
+                });
+                ctx.body = await fs.readFileSync(filePath + uuid + suffix);
+            } catch (e) {
+                console.log(e);
+            }
+        }
+
+    }
+    return ReportController;
+};
+

+ 31 - 1
app/controller/report_controller.js

@@ -69,7 +69,7 @@ module.exports = app => {
                 await this._getStageAuditViewData(ctx);
                 const pageShow = ctx.session.sessionProject.page_show;
                 // console.log(pageShow);
-                // pageShow.showArchive = 1;
+                pageShow.showArchive = 1;
                 const tender = ctx.tender;
                 const stage = ctx.stage;
                 let stage_id = -1;
@@ -81,6 +81,10 @@ module.exports = app => {
                 const custTreeNodes = await ctx.service.rptTreeNodeCust.getCustFoldersByUserId(this.ctx.session.sessionUser.accountId);
                 const custCfg = await ctx.service.rptCustomizeCfg.getCustomizeCfgByUserId('Administrator');
                 const stageList = await ctx.service.stage.getValidStagesShort(tender.id);
+                // console.log(stage);
+                // console.log(stageList);
+                // console.log('ctx.stage.id: ' + ctx.stage.id);
+                // console.log('ctx.stage.stage_id: ' + ctx.stage.stage_id);
                 // console.log('this.ctx.session.sessionUser.accountId: ' + this.ctx.session.sessionUser.accountId);
                 if (stage && stage.status === auditConst.stage.status.uncheck) {
                     // 得判断账号是否在审核人列表中(不含原报)
@@ -88,6 +92,8 @@ module.exports = app => {
                         stage.auditorList = await ctx.service.stageAudit.getAuditors(ctx.stage.id, ctx.stage.times);
                     }
                     let isAudit = false;
+                    // console.log('stage.auditorList');
+                    // console.log(stage.auditorList);
                     for (const audit of stage.auditorList) {
                         if (audit.aid === this.ctx.session.sessionUser.accountId) {
                             isAudit = true;
@@ -105,6 +111,8 @@ module.exports = app => {
                                 stageList[idx].auditorList = await ctx.service.stageAudit.getAuditors(stageList[idx].id, stageList[idx].times);
                             }
                             let isAudit = false;
+                            // console.log('stageList[idx].auditorList');
+                            // console.log(stageList[idx].auditorList);
                             for (const audit of stageList[idx].auditorList) {
                                 if (audit.aid === this.ctx.session.sessionUser.accountId) {
                                     isAudit = true;
@@ -117,6 +125,26 @@ module.exports = app => {
                         }
                     }
                 }
+                let lastAuditor = null;
+                if (stage) {
+                    lastAuditor = await ctx.service.stageAudit.getLastestAuditor(ctx.stage.id, ctx.stage.times, auditConst.stage.status.checked);
+                } else {
+                    lastAuditor = await ctx.service.stageAudit.getLastestAuditor(stageList[0].id, stageList[0].times, auditConst.stage.status.checked);
+                }
+                let archiveList = [];
+                if (stage) {
+                    const archives = await ctx.service.rptArchive.getPrjStgArchive(tender.data.project_id, ctx.stage.id);
+                    if (archives.length > 0) {
+                        archiveList = JSON.parse(archives[0].content);
+                    }
+                } else {
+                    const archives = await ctx.service.rptArchive.getPrjStgArchive(tender.data.project_id, stageList[stageList.length - 1].id);
+                    // console.log(archives);
+                    if (archives && archives.length > 0) {
+                        archiveList = JSON.parse(archives[0].content);
+                    }
+                }
+                // const archiveList = await ctx.service.rptArchive.getPrjStgArchive(tender.data.project_id, ctx.stage.id);
                 const prjAccList = await ctx.service.projectAccount.getAllAccountByProjectId(tender.data.project_id);
                 const roleList = await ctx.service.signatureRole.getSignatureRolesByTenderId(tender.id);
                 const usedList = await ctx.service.signatureUsed.getSignatureUsedByTenderId(tender.id);
@@ -260,6 +288,8 @@ module.exports = app => {
                     pageShow,
                     authMobile: accountInfo.auth_mobile,
                     shenpiConst,
+                    archiveList,
+                    lastAuditor,
                 };
                 await this.layout('report/index.ejs', renderData, 'report/rpt_all_popup.ejs');
                 // await this.layout('report/index.ejs', renderData);

+ 311 - 0
app/public/report/js/rpt_archive.js

@@ -0,0 +1,311 @@
+/**
+ * Created by Tony on 2021/4/2.
+ */
+
+let rptArchiveObj = {
+    treeObj: null,
+    currentNode: null,
+    currentArchiveUuid: null,
+    currentArchiveDateStr: null,
+    iniPage: function() {
+        //初始化页面的归档信息
+        let me = rptArchiveObj;
+        const archivedRptIds = [];
+        for (let aItem of ARCHIVE_LIST) {
+            archivedRptIds.push(parseInt(aItem.rpt_id));
+        }
+        let private_remove_hide_item = function (items, nlv) {
+            if (items && items.length > 0) {
+                for (let i = items.length - 1; i >= 0; i--) {
+                    if (!(items[i].released) && items[i].nodeType === 2) {
+                        items.splice(i, 1);
+                    } else {
+                        if (items[i].items && items[i].items.length > 0) {
+                            private_remove_hide_item(items[i].items, nlv + 1);
+                            if (items[i].items.length === 0 && nlv > 0) {
+                                items.splice(i, 1);
+                            }
+                        }
+                    }
+                }
+            }
+        };
+        const private_remove_un_archive_item = function(items, nlv) {
+            if (items && items.length > 0) {
+                for (let i = items.length - 1; i >= 0; i--) {
+                    if (items[i].nodeType === 2) {
+                        if (archivedRptIds.indexOf(items[i].refId) < 0) {
+                            items.splice(i, 1);
+                        }
+                    } else {
+                        if (items[i].items && items[i].items.length > 0) {
+                            private_remove_un_archive_item(items[i].items, nlv + 1);
+                            if (items[i].items.length === 0 && nlv > 0) {
+                                items.splice(i, 1);
+                            }
+                        } else {
+                            //items[i]是目录,但又没有items子项,
+                            items.splice(i, 1);
+                        }
+                    }
+                }
+            }
+        };
+        let nodeLv = 0;
+        private_remove_hide_item(TOP_TREE_NODES, nodeLv);
+        private_remove_un_archive_item(TOP_TREE_NODES, nodeLv);
+        zTreeHelper.createTreeDirectly(TOP_TREE_NODES, rpt_prj_folder_setting, "rptTplTree", me);
+        me.treeObj.expandAll(true);
+        me.refreshNodes();
+    },
+    toggleBtn: function (enabled) {
+        if (current_stage_status === 3 && enabled) {
+            $('#btnArchiveRpt').removeAttr('disabled');
+            $('#btnArchiveList').removeAttr('disabled');
+        } else {
+            $('#btnArchiveRpt').attr('disabled', '');
+            $('#btnArchiveList').attr('disabled', '');
+        }
+    },
+    refreshNodes: function() {
+        let me = this;
+        let private_setupIsParent = function(node){
+            node.isParent = (node.nodeType === RT.NodeType.NODE || node.level === 0);
+            if (node.items && node.items.length) {
+                for (let i = 0; i < node.items.length; i++) {
+                    private_setupIsParent(node.items[i]);
+                }
+            }
+        };
+        let topNodes = me.treeObj.getNodes();
+        for (let i = 0; i < topNodes.length; i++) {
+            private_setupIsParent(topNodes[i]);
+        }
+        me.treeObj.refresh();
+    },
+    onCheck: function(event, treeId, treeNode) {
+        rptArchiveObj._countChkedRptTpl();
+        if (treeNode.isParent) {
+            rptArchiveObj.treeObj.expandNode(treeNode, true, true, false);
+        }
+    },
+    onClick: function(event,treeId,treeNode) {
+        let me = rptArchiveObj;
+        if (treeNode && treeNode.nodeType === TPL_TYPE_TEMPLATE && treeNode.refId > 0) {
+            me.currentNode = treeNode;
+            for (let aItem of ARCHIVE_LIST) {
+                if (treeNode.refId === parseInt(aItem.rpt_id)) {
+                    me.currentArchiveUuid = null;
+                    me.currentArchiveDateStr = null;
+                    if (aItem.items && aItem.items.length > 0) {
+                        me.currentArchiveUuid = aItem.items[0].uuid;
+                        me.currentArchiveDateStr = aItem.items[0].updateDate_time;
+                    }
+                    break;
+                }
+            }
+            me._countChkedRptTpl();
+            me._buildeArchiveDateSelect();
+            me._requestArchiveReport();
+        }
+    },
+
+    _requestArchiveReport: function () {
+        let me = rptArchiveObj;
+        if (me.currentNode && me.currentArchiveUuid) {
+            try {
+                let uuIdUrl =  "/getArchivedFileByUUID/" + me.currentArchiveUuid + "/" + stringUtil.replaceAll(me.currentNode.name, "#", "_");
+                // window.location = uuIdUrl;
+            } catch (ex) {
+                console.log(ex.toString());
+            }
+        }
+    },
+
+    _changeArchiveDateSelect: function (dom) {
+        let me = rptArchiveObj;
+        // me.currentArchiveUuid = dom.uuid; //在dom的onclick时已经设置过了
+        me.currentArchiveDateStr = dom.innerHTML;
+        me._buildeArchiveDateSelect();
+    },
+
+    _buildeArchiveDateSelect: function () {
+        let me = rptArchiveObj;
+        if (me.currentNode && me.currentArchiveUuid && me.currentArchiveDateStr) {
+            let targetDom = document.getElementById("currentDrpArchiveSelect");
+            targetDom.innerHTML = me.currentArchiveDateStr;
+            let drpDom = $("#drpArchiveSelect");
+            drpDom.empty();
+            for (let aItem of ARCHIVE_LIST) {
+                if (me.currentNode.refId === parseInt(aItem.rpt_id)) {
+                    for (let item of aItem.items) {
+                        if (item.uuid !== me.currentArchiveUuid) {
+                            const str = '<a class="dropdown-item" href="javascript: void(0);" onclick="rptArchiveObj.currentArchiveUuid = \'' + item.uuid + '\'; rptArchiveObj._changeArchiveDateSelect(this)">' + item.updateDate_time + '</a>';
+                            drpDom.append(str);
+                        }
+                    }
+                }
+            }
+        }
+    },
+
+    _countChkedRptTpl: function () {
+        let me = rptArchiveObj;
+        if (me.treeObj) {
+            me.checkedRptTplNodes = [];
+            let chkNodes = me.treeObj.getCheckedNodes(true), cnt = 0, hasCurrentNode = false;
+            for (let node of chkNodes) {
+                if (node.nodeType === TPL_TYPE_TEMPLATE) {
+                    cnt++;
+                    me.checkedRptTplNodes.push(node);
+                    if (me.currentNode === node) hasCurrentNode = true;
+                }
+            }
+            if (!hasCurrentNode && cnt === 0 && me.currentNode !== null) {
+                //这里根据实际需求再做处理
+                cnt++;
+                me.checkedRptTplNodes.push(me.currentNode);
+            }
+            $("#print_div").find("span").each(function(cIdx,elementSpan){
+                elementSpan.innerText = cnt;
+            });
+            $("#export_div").find("span").each(function(cIdx,elementSpan){
+                elementSpan.innerText = cnt;
+            });
+        }
+    },
+
+    showArchivedItem: function(currentNode) {
+        //初始化当前报表已经归档的信息
+        //ARCHIVE_LIST结构:[{rpt_id, items: [{uuid, update_time, is_common}...最多3个]}...] (当前项目、当前期的所有报表归档信息)
+        if (currentNode) {
+            let cardArchiveInfo = $('#cardArchiveInfo');
+            cardArchiveInfo.empty();
+            let auditDate = null;
+            let achivedAmt = 0;
+            let achivedItem = null;
+            for (let item of ARCHIVE_LIST) {
+                if (parseInt(item.rpt_id) === currentNode.refId) {
+                    auditDate = new Date(LAST_AUDITOR.begin_time);
+                    achivedAmt = item.items?item.items.length:0;
+                    achivedItem = item;
+                    break;
+                }
+            }
+            if (auditDate) {
+                cardArchiveInfo.append('<h6>第' + current_stage_order + '期,审批通过时间:' + auditDate.getFullYear() + '-' + (auditDate.getMonth() + 1) + '-' + auditDate.getDate() + '。</h6>');
+            } else {
+                cardArchiveInfo.append('<h6>第' + current_stage_order + '期');
+            }
+            cardArchiveInfo.append('<h6>本张报表第' + current_stage_order + '期,已归档' + achivedAmt + '份文件。</h6>');
+            if (achivedItem && achivedItem.items && achivedItem.items.length === 3) {
+                cardArchiveInfo.append('<h6>本次归档将会覆盖最旧那次归档。</h6>');
+            }
+            cardArchiveInfo.append('<div class="form-group" id="archived_frm_grp">');
+            if (achivedAmt > 0) {
+                for (let idx = 0; idx < achivedItem.items.length; idx++) {
+                    cardArchiveInfo.append('<div class="form-check">');
+                    // if (achivedAmt === 3) {
+                    //     cardArchiveInfo.append('<input class="form-check-input" type="radio" name="exampleRadios" id="exampleRadios' + idx + '" value="option' + idx + '" ' + ((idx === 0)?'checked':'') + '>');
+                    // }
+                    cardArchiveInfo.append('<label class="form-check-label" for="exampleRadios' + idx + '">');
+                    // let ad = new Date(achivedItem.items[idx].update_time);
+                    // cardArchiveInfo.append('#' + (idx + 1) + ' ' + ad.getFullYear() + '-' + (ad.getMonth() + 1) + '-' + ad.getDate() + ' 归档');
+                    cardArchiveInfo.append('#' + (idx + 1) + ' ' + achivedItem.items[idx].updateDate_time + ' 归档');
+                    cardArchiveInfo.append('</label>');
+                    cardArchiveInfo.append('</div>');
+                }
+            }
+            cardArchiveInfo.append('</div>');
+        }
+    },
+    _getCurrentArchives: function (currentNode) {
+        let rst = null;
+        if (ARCHIVE_LIST.length > 0 && currentNode) {
+            for (let aItem of ARCHIVE_LIST) {
+                if (parseInt(aItem.rpt_id) === currentNode.refId) {
+                    rst = aItem;
+                    break;
+                }
+            }
+        }
+        return rst;
+    },
+    _chkIfFullArchives: function(currentNode) {
+        let aItem = this._getCurrentArchives(currentNode);
+        let rst = (aItem && aItem.items && aItem.items.length === 3);
+        return rst;
+    },
+    archiveCurrentReport: function(currentRptPageRst, currentNode) {
+        // 归档当前报表
+        if (currentRptPageRst !== null) {
+            try {
+                let doc = JpcJsPDFHelper._createPdf(currentRptPageRst, rptControlObj.getCurrentPageSize(), ROLE_REL_LIST, STAGE_AUDIT);
+                let formData = new FormData();
+                formData.append('file', doc.output('blob'), 'upload.pdf'); //上传单个文件的添加方式
+                if (!rptArchiveObj._chkIfFullArchives(currentNode)) {
+                    postDataWithFile('/tender/report_api/addArchive/' + PROJECT_ID + '/' + current_stage_id + '/' + currentNode.refId, formData, function (result) {
+                        // 成功后,更新当前页面
+                        if (result.addedRst) {
+                            // console.log(result);
+                            ARCHIVE_LIST = result.addedRst;
+                            rptArchiveObj.showArchivedItem(currentNode);
+                        } else {
+                            // 有冲突,需要删除
+                            CommonAjax.postXsrfEx('/tender/report_api/removeArchive/' + PROJECT_ID + '/' + current_stage_id + '/' + currentNode.refId + result.fileName, '', 3000, true, getCookie('csrfToken'),
+                                function(result){
+                                    //
+                                }
+                            );
+                        }
+                    }, function (error){
+                        // alert(error);
+                    });
+                } else {
+                    let aItem = this._getCurrentArchives(currentNode);
+                    if (aItem && aItem.items && aItem.items.length > 0) {
+                        let orgName = aItem.items[0].uuid;
+                        let compStr = aItem.items[0].updateDate_time;
+                        for (let idx = 1; idx < aItem.items.length; idx++) {
+                            if (aItem.items[idx].updateDate_time < compStr) {
+                                compStr = aItem.items[idx].updateDate_time;
+                                orgName = aItem.items[idx].uuid;
+                            }
+                        }
+                        postDataWithFile('/tender/report_api/updateArchive/' + PROJECT_ID + '/' + current_stage_id + '/' + currentNode.refId + '/' + orgName, formData, function (result) {
+                            // 成功后,更新当前页面
+                            if (result.updatedRst) {
+                                // console.log(result);
+                                ARCHIVE_LIST = result.updatedRst;
+                                rptArchiveObj.showArchivedItem();
+                            } else {
+                                // 有冲突,需要删除
+                                CommonAjax.postXsrfEx('/tender/report_api/removeArchive/' + PROJECT_ID + '/' + current_stage_id + '/' + currentNode.refId + result.fileName, '', 3000, true, getCookie('csrfToken'),
+                                    function(result){
+                                        //
+                                    }
+                                );
+                            }
+                        }, function (error){
+                            // alert(error);
+                        });
+                    }
+                }
+            } catch (ex) {
+                console.log(ex.toString());
+            }
+        } else {
+            alert('请选择打开一个报表!');
+        }
+    }
+};
+
+function _dataURLtoFile(dataurl, filename) {
+    var arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1],
+        bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);
+    while(n--){
+        u8arr[n] = bstr.charCodeAt(n);
+    }
+    return new File([u8arr], filename, {type:mime});
+};
+

+ 3 - 2
app/public/report/js/rpt_cfg_const.js

@@ -4,6 +4,7 @@
 
 const   TPL_TYPE_NODE = 1,
         TPL_TYPE_TEMPLATE = 2;
+
 const rpt_tpl_setting = {
     view: {
         selectedMulti: false
@@ -51,7 +52,7 @@ const rpt_prj_folder_setting = {
         }
     },
     callback: {
-        // onCheck: zTreeOprObj.onCheck,
-        // onClick: zTreeOprObj.onClick
+        onCheck: rptArchiveObj.onCheck,
+        onClick: rptArchiveObj.onClick
     }
 };

+ 7 - 2
app/public/report/js/rpt_jspdf.js

@@ -31,10 +31,14 @@ let JpcJsPDFHelper = {
         // doc.save('Test.pdf');
     },
     outputAsPdf: function (pageData, paperSize, pdfName, signatureRelArr, signAuditArr) {
+        let doc = this._createPdf(pageData, paperSize, signatureRelArr, signAuditArr);
+        doc.save(pdfName + '.pdf');
+    },
+    _createPdf: function (pageData, paperSize, signatureRelArr, signAuditArr) {
         let me = this;
         let offsetX = 0;
         let offsetY = 0;
-        let newName = pdfName;
+        // let newName = pdfName;
         let pageObj = pageData;
         let paperSizeIdx = JV.PAGES_SIZE_STR.indexOf(paperSize);
         let size = JV.PAGES_SIZE[paperSizeIdx];
@@ -109,7 +113,8 @@ let JpcJsPDFHelper = {
                 }
             }
         }
-        doc.save(newName + '.pdf');
+        // doc.save(newName + '.pdf');
+        return doc;
 
         function private_chkIfInMergedBand(mergedBand, cell) {
             let rst = false;

+ 4 - 0
app/public/report/js/rpt_main.js

@@ -269,6 +269,7 @@ let zTreeOprObj = {
             }
 
 
+            rptArchiveObj.toggleBtn(true);
             me.requestNormalReport(params);
             me.countChkedRptTpl();
             rptCustomObj.showMaterialSelect();
@@ -404,6 +405,7 @@ let zTreeOprObj = {
                     rptSignatureHelper.buildSelectableAccountUsed();
                     rptSignatureHelper.buildRoleDom(ROLE_LIST);
                     me.showPage(1, canvas);
+                    rptArchiveObj.showArchivedItem(me.currentNode);
                 } else {
                     //返回了无数据表
                     JpcCanvasOutput.cleanCanvas(canvas);
@@ -425,8 +427,10 @@ let zTreeOprObj = {
                 } catch(err) {
                 }
             }, function(err){
+                if (me.currentRptPageRst === null) rptArchiveObj.toggleBtn(false);
                 $.bootstrapLoading.end();
             }, function(ex){
+                if (me.currentRptPageRst === null) rptArchiveObj.toggleBtn(false);
                 $.bootstrapLoading.end();
             }
         );

+ 4 - 0
app/public/report/js/rpt_preview_common.js

@@ -4,6 +4,8 @@
 
 let G_OFFSET_X = 0, G_OFFSET_Y = 0;
 let STAGE_AUDIT = []; //注意这个,与rpt_main.js不要混了
+let STAGE_LIST = [];
+let STAGE_AUDIT_ORG = [];
 // 设置Date对象Format函数
 // -- 打印预览需要重新设置一遍 ------------------------------------------------
 Date.prototype.Format = function(fmt) {
@@ -28,6 +30,8 @@ function printPageLoading() {
     let _current_stage_status = parseInt(sessionStorage.current_stage_status);
     let closeWaterMark = parseInt(sessionStorage.closeWaterMark);
     let refRptTplIds = JSON.parse(sessionStorage.refRptTplIds);
+    STAGE_LIST = JSON.parse(sessionStorage.STAGE_LIST);
+    STAGE_AUDIT_ORG = JSON.parse(sessionStorage.STAGE_AUDIT_ORG);
     let scaleFactor = 1;
     CommonAjax.postXsrfEx("/tender/report_api/getMultiReports", params, 60000, true, getCookie('csrfToken'),
         function(result){

+ 2 - 0
app/public/report/js/rpt_print.js

@@ -25,6 +25,8 @@ let rptPrintHelper = {
             sessionStorage.pageSize = rptControlObj.getCurrentPageSize();
             sessionStorage.waterMarkStr = COMMON_WATER_MARK_PIC_DATA;
             sessionStorage.refRptTplIds = JSON.stringify(refRptTplIds);
+            sessionStorage.STAGE_LIST = JSON.stringify(STAGE_LIST);
+            sessionStorage.STAGE_AUDIT_ORG = JSON.stringify(STAGE_AUDIT_ORG);
             // sessionStorage.STAGE_AUDIT = JSON.stringify(STAGE_AUDIT);
             if (sessionStorage.pageSize === 'A3') {
                 window.open('/printReport/A3');

+ 7 - 0
app/router.js

@@ -300,6 +300,10 @@ module.exports = app => {
     // 报表
     app.get('/tender/:id/report', sessionAuth, tenderCheck, uncheckTenderCheck, 'reportController.index');
     app.get('/tender/:id/measure/stage/:order/report', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'reportController.index');
+    app.get('/tender/:id/archiveReport', sessionAuth, tenderCheck, uncheckTenderCheck, 'reportArchiveController.index');
+    app.post('/tender/report_api/getReportArchive', sessionAuth, 'reportArchiveController.getReportArchive');
+    app.get('/getArchivedFileByUUID/:uuid/:rptName', sessionAuth, 'reportArchiveController.getArchivedFileByUUID');
+    app.get('/tender/:id/measure/stage/:order/archiveReport', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'reportArchiveController.index');
     app.get('/printReport/:size', sessionAuth, 'reportController.showPrintPage');
     app.post('/tender/report_api/getReport', sessionAuth, 'reportController.getReport');
     app.post('/tender/report_api/getMultiReports', sessionAuth, 'reportController.getMultiReportsEx');
@@ -315,6 +319,9 @@ module.exports = app => {
     app.post('/tender/report_api/createRoleRelationship', sessionAuth, 'signatureController.createRoleRel');
     app.post('/tender/report_api/updateCustNode', sessionAuth, 'reportController.updateCustNode');
     app.post('/report/cDefine', sessionAuth, 'reportController.setCustomDefine');
+    app.post('/tender/report_api/addArchive/:prjId/:stgId/:rptId', sessionAuth, 'reportArchiveController.addReportArchive');
+    app.post('/tender/report_api/updateArchive/:prjId/:stgId/:rptId/:orgName', sessionAuth, 'reportArchiveController.updateReportArchive');
+    app.post('/tender/report_api/removeArchive/:prjId/:stgId/:rptId/:orgName', sessionAuth, 'reportArchiveController.removeReportArchive');
 
     // 变更管理
     app.get('/tender/:id/change', sessionAuth, tenderCheck, uncheckTenderCheck, 'changeController.index');

+ 32 - 9
app/service/rpt_archive.js

@@ -1,3 +1,5 @@
+'use strict';
+
 /**
  * Created by Tony on 2021/3/30.
  */
@@ -17,16 +19,16 @@ module.exports = app => {
         constructor(ctx) {
             super(ctx);
             this.tableName = 'rpt_archive';
-            this.dataId = 'archive_id';
+            this.dataId = 'id';
         }
 
         async getArchiveById(id) {
             this.initSqlBuilder();
-            this.sqlBuilder.setAndWhere('archive_id', {
+            this.sqlBuilder.setAndWhere('id', {
                 value: id,
                 operate: '=',
             });
-            this.sqlBuilder.columns = ['archive_id', 'prj_id', 'stage_id', 'content'];
+            this.sqlBuilder.columns = ['id', 'prj_id', 'stage_id', 'content'];
             const [sql, sqlParam] = this.sqlBuilder.build(this.tableName);
             const list = await this.db.query(sql, sqlParam);
             return list;
@@ -42,19 +44,19 @@ module.exports = app => {
                 value: stgId,
                 operate: '=',
             });
-            this.sqlBuilder.columns = ['archive_id', 'prj_id', 'stage_id', 'content'];
+            this.sqlBuilder.columns = ['id', 'prj_id', 'stage_id', 'content'];
             const [sql, sqlParam] = this.sqlBuilder.build(this.tableName);
-            const list = await this.db.query(sql, sqlParam);
-            return list;
+            const rstList = await this.db.query(sql, sqlParam);
+            return rstList;
         }
 
-        async createArchive(prj_id, stg_id, archiveArr) {
+        async createArchive(prj_id, stage_id, archiveArr) {
             let rst = null;
             this.transaction = await this.db.beginTransaction();
             try {
                 const data = {
                     prj_id,
-                    stg_id,
+                    stage_id,
                     content: JSON.stringify(archiveArr),
                 };
                 // console.log(data);
@@ -67,7 +69,28 @@ module.exports = app => {
             }
             return rst;
         }
-        //
+
+        async updateArchive(id, prj_id, stage_id, archiveArr) {
+            let rst = null;
+            this.transaction = await this.db.beginTransaction();
+            try {
+                const data = {
+                    id,
+                    prj_id,
+                    stage_id,
+                    content: JSON.stringify(archiveArr),
+                };
+                // console.log(data);
+                rst = await this.transaction.update(this.tableName, data);
+                await this.transaction.commit();
+            } catch (ex) {
+                console.log(ex);
+                // 回滚
+                await this.transaction.rollback();
+            }
+            return rst;
+        }
+
         // async addInitialStageData(tender_id, stage, preStage) {
         //     // 在加stage的时候需要挂上这个,copy之前的签名人
         //     const rst = [];

+ 7 - 2
app/view/report/index.ejs

@@ -68,14 +68,16 @@
                             <div class="panel" <% if (pageShow === null || parseInt(pageShow.showArchive) === 0 || isNaN(parseInt(pageShow.showArchive))) { %> style="display:none" <% } %>>
                                 <div class="panel-body">
                                     <div class="btn-group" role="group">
-                                        <button type="button" class="btn btn-outline-primary btn-sm" data-toggle="modal" data-target="#guidang"><i class="fa fa-archive"></i> 归档报表</button>
+                                        <button id="btnArchiveRpt" type="button" class="btn btn-outline-primary btn-sm" data-toggle="modal" data-target="#guidang" disabled><i class="fa fa-archive"></i> 归档报表</button>
                                         <div class="btn-group" role="group">
-                                            <button id="btnGroupDrop1" type="button" class="btn btn-success btn-sm dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+                                            <button id="btnArchiveList" type="button" class="btn btn-success btn-sm dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" disabled>
                                                 已归档
                                             </button>
                                             <div class="dropdown-menu" aria-labelledby="btnGroupDrop1" style="min-width:112px">
+                                                <!--
                                                 <a class="dropdown-item" href="#">#1 2021-03-01</a>
                                                 <a class="dropdown-item" href="#">#2 2021-02-28</a>
+                                                -->
                                             </div>
                                         </div>
                                     </div>
@@ -246,6 +248,8 @@
 </script>
 <script>
     const tenders = JSON.parse('<%- JSON.stringify(tenderList) %>');
+    let ARCHIVE_LIST = JSON.parse('<%- JSON.stringify(archiveList) %>');
+    const LAST_AUDITOR = JSON.parse('<%- JSON.stringify(lastAuditor) %>');
     const category = JSON.parse('<%- JSON.stringify(categoryData) %>');
     const auditConst = JSON.parse('<%- JSON.stringify(auditConst) %>');
     const ledgerAuditConst = JSON.parse('<%- JSON.stringify(ledgerAuditConst) %>');
@@ -277,6 +281,7 @@
 <script type="text/javascript" src="/public/js/storageUtil.js"></script>
 
 <script type="text/javascript" src="/public/report/js/rpt_main.js"></script>
+<script type="text/javascript" src="/public/report/js/rpt_archive.js"></script>
 <script type="text/javascript" src="/public/report/js/rpt_cfg_const.js"></script>
 <script type="text/javascript" src="/public/report/js/jpc_output_value_define.js"></script>
 <script type="text/javascript" src="/public/report/js/jpc_output.js"></script>

+ 304 - 0
app/view/report/index_archive.ejs

@@ -0,0 +1,304 @@
+<% 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">
+                        <div class="print-toolsbar">
+                            <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">
+                                            <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">
+                                            <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>
+                        </div>
+                    </div>                    <div class="sjs-height-4">
+                        <div class="print-view form-view">
+                            <div class="pageContainer">
+                                <canvas id="rptCanvas" height="820" width="920"></canvas>
+                            </div>
+                        </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 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 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 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'),
+            function(result){
+                $.bootstrapLoading.end();
+                // console.log(result);
+                ARCHIVE_LIST = result.data;
+                TOP_TREE_NODES = JSON.parse(JSON.stringify(ORG_TOP_TREE_NODES));
+                rptArchiveObj.iniPage();
+            }, function(err){
+                $.bootstrapLoading.end();
+            }, function(ex){
+                $.bootstrapLoading.end();
+            }
+        );
+    }
+
+
+    $(function () {
+        // 多层modal关闭后的滚动bug修复
+        $('#add-sign').on('hidden.bs.modal', function (e) {
+            $(document.body).addClass('modal-open');
+        });
+    })
+
+    rptArchiveObj.iniPage();
+
+</script>

+ 27 - 0
app/view/report/rpt_all_popup.ejs

@@ -485,6 +485,33 @@
         </div>
     </div>
 </div>
+<!--弹出归档-->
+<div class="modal fade" id="guidang" data-backdrop="static">
+    <div class="modal-dialog" 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">
+                <h6>归档后,本期报表将自动导出PDF格式,不再受新一期数据影响;</h6>
+                <h6>请确认报表数据无误(正确添加电子签名、设置页面格式等),继续归档操作。</h6>
+
+                <div class="card">
+                    <div class="card-body" id="cardArchiveInfo">
+                    </div>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">取消</button>
+                <a href="javascript: void(0);" class="btn btn-sm btn-primary" onclick="rptArchiveObj.archiveCurrentReport(zTreeOprObj.currentRptPageRst, zTreeOprObj.currentNode)">确定归档</a>
+            </div>
+        </div>
+    </div>
+</div>
+
 <% include ../stage/audit_modal.ejs %>
 
 <script>

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

@@ -66,6 +66,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>
         <% if (ctx.session.sessionUser.is_admin) { %>
         <div class="nav-box">
             <ul class="nav-list list-unstyled">

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

@@ -58,6 +58,11 @@
                 <li <% if (ctx.url === '/tender/' + ctx.tender.id + '/report') { %>class="active"<% } %>><a href="/tender/<%- ctx.tender.id %>/report"><i class="fa fa-file-text-o"></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"></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>