Tony Kang пре 1 дан
родитељ
комит
4c10bee7e2

+ 73 - 3
app/controller/report_controller.js

@@ -423,6 +423,43 @@ module.exports = app => {
             }
         }
 
+        async indexForFormatSetup(ctx) {
+            try {
+                await this._getStageAuditViewData(ctx);
+                const isAdmin = ctx.session.sessionUser.is_admin;
+                const tender = ctx.tender;
+                const custTreeFolders = await this._getCustFolder(ctx, this.ctx.session.sessionUser.accountId, tender.id);
+                const { treeNodes, allTreeItems, allIndivTreeItems } = await this._createNodes(ctx, sourceTypeConst.sourceType.tender, tender.data.project_id);
+                const rpt_tpl_items = { customize: [], common: [] };
+                if (custTreeFolders.length > 0) {
+                    const cust_select_item = JSON.parse(custTreeFolders[0].rpt_tpl_items);
+                    if (cust_select_item.common) rpt_tpl_items.common = cust_select_item.common;
+                    if (cust_select_item.customize) rpt_tpl_items.customize = cust_select_item.customize;
+                }
+                // const accountInfo = await this.ctx.service.projectAccount.getDataById(this.ctx.session.sessionUser.accountId);
+
+                const renderData = {
+                    rpt_tpl_data: JSON.stringify(treeNodes),
+                    cust_tpl_data: JSON.stringify(rpt_tpl_items),
+                    all_common_tpl_data: JSON.stringify(allTreeItems),
+                    all_indivi_tpl_data: JSON.stringify(allIndivTreeItems),
+                    isAdmin,
+                    stg_id: -1,
+                    stages: [],
+                    materialList: [],
+                    // authMobile: accountInfo.auth_mobile,
+                    // auditConst: auditConst.stage,
+                    // auditType: auditConst.auditType,
+                    // preUrl: '',
+                    // otherHintName: '',
+                };
+                await this.layout('report/rpt_format_setup.ejs', renderData, 'report/rpt_format_popup.ejs');
+            } catch (err) {
+                this.log(err);
+                ctx.redirect('/tender/' + ctx.tender.id + '/measure/stage');
+            }
+        }
+
         async indexForPaymentSafe(ctx) {
             // 安全生产费入口
             try {
@@ -1243,9 +1280,16 @@ module.exports = app => {
             }
         }
 
-        async _getReport(ctx, params) {
-            // console.log('in getReport');
-            // console.log(params);
+        async getReportTemplate(ctx) {
+            const params = JSON.parse(ctx.request.body.params);
+            const rptTpl = await this._getReportTemplate(ctx, params);
+            ctx.body = { data: rptTpl };
+            ctx.status = 201;
+            return rptTpl;
+        }
+
+        async _getReportTemplate(ctx, params) {
+            // const params = JSON.parse(ctx.request.body.params);
             let rptTpl = await ctx.service.rptTpl.getTplById(params.rpt_tpl_id);
             if (!rptTpl || rptTpl.length !== 1) {
                 throw '获取模板失败';
@@ -1255,6 +1299,32 @@ module.exports = app => {
             rptTpl.source_type = source_type || params.source_type || sourceTypeConst.defaultSourceType;
             rptTpl.id = params.rpt_tpl_id;
             // console.log('get the template!');
+            return rptTpl;
+        }
+
+        async updateReportTemplate(ctx) {
+            const params = JSON.parse(ctx.request.body.params);
+            const tplId = params.tplId;
+            const tplName = params.tplName;
+            const tplJsonObj = params.tplObj;
+            const msg = await ctx.service.rptTpl.updateRptTpl(tplId, tplName, tplJsonObj);
+            ctx.body = { msg };
+            ctx.status = 201;
+        }
+
+        async getPreviewReport(ctx) {
+            const params = JSON.parse(ctx.request.body.params);
+            const tplJsonObj = params.tplObj;
+            const rawRptTpl = { rpt_content: tplJsonObj };
+            const pageRst = await this.ctx.service.jpcReport.getAllPreviewPagesCommon(rawRptTpl, 'A4');
+            ctx.body = { pageRst };
+            ctx.status = 201;
+        }
+
+        async _getReport(ctx, params) {
+            // console.log('in getReport');
+            // console.log(params);
+            const rptTpl = await this._getReportTemplate(ctx, params);
             const customSelect = rptTpl[JV.NODE_CUSTOM_DEFINE] && rptTpl[JV.NODE_CUSTOM_DEFINE][JV.NODE_CUS_AUDIT_SELECT].enable
                 ? await ctx.service.rptCustomDefine.getCustomDefine(params.tender_id, params.stage_id, params.rpt_tpl_id)
                 : await ctx.service.rptCustomDefine.getCustomDefine(params.tender_id, -1, params.rpt_tpl_id);

+ 1 - 1
app/public/js/rpt_tpl_def.js

@@ -2,7 +2,7 @@
  * Created by Tony on 2017/6/1.
  */
 
-var RT = {
+const RT = {
     GrpType: {CONSTRUCT: 1, ROAD: 2},
     NodeType: {NODE: 1, TEMPLATE: 2},
     TplType: {ALL: 1, TENDER: 2, BILLS: 3, BUDGET: 4, OTHERS: 99}

+ 563 - 0
app/public/report/js/rpt_format_setup.js

@@ -0,0 +1,563 @@
+const reportFormatSetupObj = {
+    treeObj: null,
+    templateObj: {},
+    canvas: null,
+    fieldParamOptions: null,
+    onCheck: (event, treeId, treeNode) => {
+        //
+    },
+    onClick: (event,treeId,treeNode) => {
+        const me = reportFormatSetupObj;
+        if (treeNode.nodeType === RT.NodeType.TEMPLATE) {
+            me.initialize(treeNode);
+        }
+    },
+    onDeleteInfo: (dom) => {
+        // 删除表眉、表脚项
+        // console.log(dom.parentElement.parentElement);
+        $(dom.parentElement.parentElement).remove();
+    },
+    onAddInfo: (dom, lv) => {
+        // 增加表眉、表脚细项
+        const me = reportFormatSetupObj;
+        const domStrs = [];
+        domStrs.push('<tr class="text-center">');
+        domStrs.push('<td></td>');
+        domStrs.push(`<td>${me._createTypeSelection(0)}</td>`);
+        domStrs.push(`<td class="text-left"><input class="form-control form-control-sm" value="" type="text"></td>`);
+        domStrs.push(`<td class="text-left"><input class="form-control form-control-sm" value="0" type="text"></td>`);
+        domStrs.push(`<td class="text-left"><input class="form-control form-control-sm" value="0" type="text"></td>`);
+        domStrs.push(`<td class="text-left"><input class="form-control form-control-sm" value="" type="text"></td>`);
+        domStrs.push(`<td class="text-left"><input class="form-control form-control-sm" value="" type="text"></td>`);
+        domStrs.push('<td><a href="#" onclick="reportFormatSetupObj.onDeleteInfo(this)" class="text-danger mr-2" title="删除">删除</a></td>');
+        domStrs.push('</tr>');
+        $(dom.parentElement.parentElement).after(domStrs.join(''));
+    },
+    onAddLvInfo: (dom) => {
+        // 增加表眉、表脚层次项
+        console.log(dom);
+        let maxCnt = 0;
+        for (const td of dom.parentElement.parentElement.nextElementSibling.children[0].children[1].children) {
+            if (td.cells[0].innerText !== '') {
+                const lv = parseInt(td.cells[0].innerText) || 0;
+                if (lv > maxCnt) maxCnt = lv;
+            }
+        }
+        // dom.parentElement.parentElement.nextElementSibling.children[0].children[1]
+        const lvDtl = [];
+        lvDtl.push('<tr class="text-center">');
+        lvDtl.push(`<td>${maxCnt + 1}</td>`);
+        lvDtl.push('<td></td>');
+        lvDtl.push('<td></td>');
+        lvDtl.push('<td></td>');
+        lvDtl.push('<td></td>');
+        lvDtl.push('<td></td>');
+        lvDtl.push('<td></td>');
+        lvDtl.push('<td><a href="#" onclick="reportFormatSetupObj.onAddInfo(this)" class="text-primary mr-2" title="新增插入子项">新增</a></td>');
+        lvDtl.push('</tr>');
+        $(dom.parentElement.parentElement.nextElementSibling.children[0].children[1]).append(lvDtl.join(''));
+    },
+    onTypeChange: (dom) => {
+        const me = reportFormatSetupObj;
+        console.log(dom);
+        // 根据新选项清理、初始化
+        if (dom.selectedIndex === 0) {
+            // 文本
+            $(dom.parentElement.nextSibling).html('<input class="form-control form-control-sm" value="" type="text">');
+            dom.parentElement.nextSibling.nextSibling.nextSibling.nextSibling.children[0].value = '';
+            dom.parentElement.nextSibling.nextSibling.nextSibling.nextSibling.nextSibling.children[0].value = '';
+        } else {
+            // 指标
+            const curNodes = me.treeObj.getSelectedNodes();
+            if (curNodes.length > 0) {
+                const rptTemplate = me.templateObj[`${curNodes[0].ID}`];
+                if (rptTemplate) {
+                    $(dom.parentElement.nextSibling).html(`${me._createSelectionFields(-1, 5010)}`);
+                    dom.parentElement.nextSibling.nextSibling.nextSibling.nextSibling.children[0].value = '';
+                    dom.parentElement.nextSibling.nextSibling.nextSibling.nextSibling.nextSibling.children[0].value = '';
+                }
+            }           
+        }
+    },
+    initialize: (rptNode) => {
+        const me = reportFormatSetupObj;
+        me.canvas = document.getElementById("rptSetupPreviewCanvas");
+        // 1. 先清理
+        me.clearTplProperties();
+        // 2. 再加表标题、表眉、表脚、电子签名内容
+        const params = { rpt_tpl_id: rptNode.refId };
+        const url = '/report_api/getReportTemplate';
+        $.bootstrapLoading.start();
+        CommonAjax.postXsrfEx(url, params, 300000, true, getCookie('csrfToken_j'),
+            async function(result){
+                console.log(result);
+                me.templateObj[`${rptNode.refId}`] = result.data;
+                me.fieldParamOptions = me.initialSelectionFields(result.data);
+                me.initialTitle(result.data);
+                me.initialHeader(result.data);
+                me.initialFooter(result.data);
+                me.initialSignature(result.data);
+                $.bootstrapLoading.end();
+            }, function(err){
+                console.log(err);
+                $.bootstrapLoading.end();
+            }, function(ex){
+                console.log(ex);
+                $.bootstrapLoading.end();
+            }
+        );
+    },
+    clearTplProperties: () => {
+        // 表眉
+        $("#rpt_header_div").empty();
+        const str1 = '<div>表眉</div><div><a href="#" onclick="reportFormatSetupObj.onAddLvInfo(this)" class="btn btn-sm btn-light text-primary">新增行</a></div>';
+        $("#rpt_header_div").append(str1);
+        $("#rpt_header_table_div").empty();
+        // 表脚
+        $("#rpt_footer_div").empty();
+        const str2 = '<div>表脚</div><div><a href="#" onclick="reportFormatSetupObj.onAddLvInfo(this)" class="btn btn-sm btn-light text-primary">新增行</a></div>';
+        $("#rpt_footer_div").append(str2);
+        $("#rpt_footer_table_div").empty();
+        // 表标题
+        $("#rpt_title_input")[0].value = '';
+    },
+    initialHeader: (rptTpl) => {
+        const me = reportFormatSetupObj;
+        const bandInfo = me._getRightBandInfoObj(me._getRightInfoObj(rptTpl), 'HeaderBand');
+        if (bandInfo) {
+            const posArr = me._checkLevelInfo(bandInfo);
+            const html = me._buildHeaderFooterHTML(rptTpl, bandInfo, posArr);
+            // console.log(html);
+            // $("#rpt_header_parent_div").empty();
+            $("#rpt_header_table_div").append(html.join(''));
+            // rpt_header_div
+        }
+    },
+    initialFooter: (rptTpl) => {
+        const me = reportFormatSetupObj;
+        const bandInfo = me._getRightBandInfoObj(me._getRightInfoObj(rptTpl), 'FooterBand');
+        if (bandInfo) {
+            const posArr = me._checkLevelInfo(bandInfo);
+            const html = me._buildHeaderFooterHTML(rptTpl, bandInfo, posArr);
+            console.log(html);
+            // $("#rpt_header_parent_div").empty();
+            $("#rpt_footer_table_div").append(html.join(''));
+            // rpt_header_div
+        }
+        //
+    },
+    initialTitle: (rptTpl) => {
+        const me = reportFormatSetupObj;
+        $("#rpt_title_input")[0].value = me._getTitle(rptTpl);
+        const curNodes = me.treeObj.getSelectedNodes();
+        if (curNodes.length > 0) {
+            $("#rename_rpt_confirm")[0].innerText=`确认更新报表模板「${curNodes[0].name}」?`;
+        }
+    },
+    initialSignature: (rptTpl) => {
+        //
+    },
+    _getTitle: (rptTpl) => {
+        const me = reportFormatSetupObj;
+        let rst = '';
+        const bandInfo = me._getRightBandInfoObj(me._getRightInfoObj(rptTpl), 'TitleBand');
+        if (bandInfo && bandInfo.text_s) {
+            const titleTxt = bandInfo.text_s.find(item => item.font === 'ReportTitle_Main');
+            if (titleTxt) {
+                rst = titleTxt.Label;
+            } else {
+                rst = bandInfo.text_s[0].Label;
+            }
+        }
+        return rst;
+    },
+    _setTitle: (rptTpl, newTitle) => {
+        const me = reportFormatSetupObj;
+        const bandInfo = me._getRightBandInfoObj(me._getRightInfoObj(rptTpl), 'TitleBand');
+        if (bandInfo && bandInfo.text_s) {
+            const titleTxt = bandInfo.text_s.find(item => item.font === 'ReportTitle_Main');
+            if (titleTxt) {
+                titleTxt.Label = newTitle;
+            } else {
+                bandInfo.text_s[0].Label = newTitle;
+            }
+        }
+    },
+    _getRightInfoObj: (rptTpl) => {
+        let rst = null;
+        if (rptTpl) {
+            if (rptTpl['流水式表_信息']) {
+                rst = rptTpl['流水式表_信息'];
+            } else if (rptTpl['账单式表_信息']) {
+                rst = rptTpl['账单式表_信息'];
+            } else if (rptTpl['交叉表_信息']) {
+                rst = rptTpl['交叉表_信息'];
+            }
+        }
+        return rst;
+    },
+    _getRightBandInfoObj: (infoObj, bandName) => {
+        let rst = null;
+        if (infoObj) {
+            for (let info of infoObj['离散信息']) {
+                if (info['BandName'] === bandName) {
+                    rst = info;
+                    break;
+                }
+            }
+        }
+        return rst;
+    },
+    _checkLevelInfo: (bandInfo) => {
+        let rst = [];
+        if (bandInfo) {
+            if (bandInfo.discrete_field_s) {
+                for (const field of bandInfo.discrete_field_s) {
+                    const bt = +field.area.Bottom;
+                    if (!rst.includes(bt)) rst.push(bt);
+                }
+            }
+            if (bandInfo.text_s) {
+                for (const txt of bandInfo.text_s) {
+                    const bt = +txt.area.Bottom;
+                    if (!rst.includes(bt)) rst.push(bt);
+                }
+            }
+        }
+        return rst;
+    },
+    _createTypeSelection: (dftSelection = 0) => {
+        const rst = [];
+        rst.push('<select class="form-control form-control-sm" onchange="reportFormatSetupObj.onTypeChange(this)">');
+        rst.push(`<option${dftSelection === 0 ? ' selected' : ''}>文本</option>`);
+        rst.push(`<option${dftSelection !== 0 ? ' selected' : ''}>指标</option>`);
+        rst.push('</select>');
+        return rst.join('');
+    },
+    initialSelectionFields: (rptTpl) => {
+        // 这里统一收集可选指标范围(有默认范围 + 报表本身特别的指标)
+        const me = reportFormatSetupObj;
+        const rst = { fields_collection: [], params_collection: [] };
+        for (const param of rptTpl['离散参数_集合']) {
+            rst.params_collection.push({ PID: +param.ID, name: param.Name, dftValue: param.Default_Value || '' });
+        }
+        for (const fd of rptTpl['指标_数据_映射']['离散指标_集合']) {
+            rst.fields_collection.push({ FID: +fd.ID, name: fd.Name });
+        }
+        const collectAdhocFields = (hfBandInfo) => {
+            if (hfBandInfo && hfBandInfo.discrete_field_s) {
+                for (const field of hfBandInfo.discrete_field_s) {
+                    if (!field.ParamID) {
+                        const selField = rst.fields_collection.find(item => item.FID === +field.FieldID);
+                        if (!selField) {
+                            const ahocSelField = me._scanField(rptTpl, field.FieldID);
+                            rst.fields_collection.push({ FID: +ahocSelField.ID, name: ahocSelField.Name });
+                        }
+                    }
+                }
+            }
+        };
+        const headerBandInfo = me._getRightBandInfoObj(me._getRightInfoObj(rptTpl), 'HeaderBand');
+        collectAdhocFields(headerBandInfo);
+        const footerBandInfo = me._getRightBandInfoObj(me._getRightInfoObj(rptTpl), 'FooterBand');
+        collectAdhocFields(footerBandInfo);
+        return rst;
+    },
+    _createSelectionFields_BK: (rptTpl, currentFieldID, ParamID) => {
+        const me = reportFormatSetupObj;
+        const rst = [];
+        let hasSelected = false;
+        rst.push('<select class="form-control form-control-sm">');
+        for (const fd of rptTpl['指标_数据_映射']['离散指标_集合']) {
+            if (!hasSelected) {
+                hasSelected = fd.ID === currentFieldID;
+            }
+            const selectedStr = fd.ID === currentFieldID ? ' selected' : '';
+            rst.push(`<option${selectedStr} FID="${fd.ID}">${fd.Name}</option>`);
+        }
+        if (!hasSelected) {
+            // 如果没有在选项中,那表示是一个额外的指标(可能在: a. '电子签名离散指标_集合' b. '无映射离散指标_集合'中 etc...),需要在模板中全扫描一把
+            // 有2种情况,一种是离散的指标,另一种是特殊参数(页码)
+            if (ParamID) {
+                for (const pd of rptTpl['离散参数_集合']) {
+                    if (`${pd.ID}` === `${ParamID}`) {
+                        rst.push(`<option selected PID="${ParamID}">${pd.Name}</option>`);
+                        break;
+                    }
+                }
+            } else {
+                const selField = me._scanField(rptTpl, currentFieldID);
+                if (selField) {
+                    rst.push(`<option selected FID="${currentFieldID}">${selField.Name}</option>`);
+                }
+            }
+        }
+        rst.push('</select>');
+        return rst.join('');
+    },
+    _createSelectionFields: (currentFieldID, ParamID) => {
+        const me = reportFormatSetupObj;
+        const rst = [];
+        let hasSelected = false;
+        rst.push('<select class="form-control form-control-sm">');
+        for (const fd of reportFormatSetupObj.fieldParamOptions.fields_collection) {
+            const selectedStr = `${fd.FID}` === `${currentFieldID}` ? ' selected' : '';
+            rst.push(`<option${selectedStr} FID="${fd.FID}">${fd.name}</option>`);
+        }
+        for (const pd of reportFormatSetupObj.fieldParamOptions.params_collection) {
+            const selectedStr = `${pd.PID}` === `${ParamID}` ? ' selected' : '';
+            rst.push(`<option${selectedStr} PID="${pd.PID}">${pd.name}</option>`);
+        }
+        rst.push('</select>');
+        return rst.join('');
+    },
+    _scanField: (rptTpl, curFieldId) => {
+        let rst = null;
+        rst = rptTpl['无映射离散指标_集合'].find(item => item.ID === curFieldId);
+        if (!rst) rst = rptTpl['电子签名离散指标_集合'].find(item => `${item.ID}` === `${curFieldId}`);
+        if (!rst) rst = rptTpl['动态日期离散参数_集合'].find(item => `${item.ID}` === `${curFieldId}`);
+        if (!rst) rst = rptTpl['电子签名审核意见指标_集合'].find(item => `${item.ID}` === `${curFieldId}`);
+        if (!rst) rst = rptTpl['指标_数据_映射']['从数据指标_集合'].find(item => `${item.ID}` === `${curFieldId}`);
+        if (!rst) rst = rptTpl['指标_数据_映射']['主数据指标_集合'].find(item => `${item.ID}` === `${curFieldId}`);
+        return rst;
+    },
+    _buildHeaderFooterHTML: (rptTpl, hfBandInfo, posArr) => {
+        const me = reportFormatSetupObj;
+        const rst = [];
+        if (hfBandInfo && posArr.length > 0) {
+            // 1. 增加一些必要html
+            rst.push('  <table class="table table-bordered">')
+            rst.push('    <thead>');
+            rst.push('       <tr>');
+            rst.push('          <th width="5%" style="text-align: center;">行号</th>');
+            rst.push('          <th width="10%" style="text-align: center;">类型</th>');
+            rst.push('          <th width="20%" style="text-align: center;">值</th>');
+            rst.push('          <th width="10%" style="text-align: center;">左位置</th>');
+            rst.push('          <th width="10%" style="text-align: center;">右位置</th>');
+            rst.push('          <th width="15%" style="text-align: center;">前缀</th>');
+            rst.push('          <th width="15%" style="text-align: center;">后缀</th>');
+            rst.push('          <th width="15%" style="text-align: center;">操作</th>');
+            rst.push('       </tr>');
+            rst.push('    </thead>');
+            rst.push('     <tbody>')
+            // --------
+            const lvs = [];
+            for (let idx = 0; idx < posArr.length; idx++) {
+                const lvDtl = [];
+                lvDtl.push('<tr class="text-center">');
+                lvDtl.push(`<td>${idx + 1}</td>`);
+                lvDtl.push('<td></td>');
+                lvDtl.push('<td></td>');
+                lvDtl.push('<td></td>');
+                lvDtl.push('<td></td>');
+                lvDtl.push('<td></td>');
+                lvDtl.push('<td></td>');
+                lvDtl.push('<td><a href="#" onclick="reportFormatSetupObj.onAddInfo(this)" class="text-primary mr-2" title="新增插入子项">新增</a></td>');
+                lvDtl.push('</tr>');
+                lvs.push(lvDtl);
+            }
+            if (hfBandInfo.text_s) {
+                for (const txt of hfBandInfo.text_s) {
+                    const bt = +txt.area.Bottom;
+                    const btIdx = posArr.indexOf(bt);
+                    if (btIdx >= 0) {
+                        lvs[btIdx].push('<tr class="text-center">');
+                        lvs[btIdx].push('<td></td>');
+                        lvs[btIdx].push(`<td>${me._createTypeSelection(0)}</td>`);
+                        lvs[btIdx].push(`<td class="text-left"><input class="form-control form-control-sm" value="${txt.Label || ''}" type="text"></td>`);
+                        lvs[btIdx].push(`<td class="text-left"><input class="form-control form-control-sm" value="${txt.area.Left || '0'}" type="text"></td>`);
+                        lvs[btIdx].push(`<td class="text-left"><input class="form-control form-control-sm" value="${txt.area.Right || '0'}" type="text"></td>`);
+                        lvs[btIdx].push(`<td class="text-left"><input class="form-control form-control-sm" value="" type="text"></td>`);
+                        lvs[btIdx].push(`<td class="text-left"><input class="form-control form-control-sm" value="" type="text"></td>`);
+                        // lvs[btIdx].push('<td></td>');
+                        // lvs[btIdx].push('<td></td>');
+                        lvs[btIdx].push('<td><a href="#" onclick="reportFormatSetupObj.onDeleteInfo(this)" class="text-danger mr-2" title="删除">删除</a></td>');
+                        lvs[btIdx].push('</tr>');
+                    }
+                }
+            }
+            if (hfBandInfo.discrete_field_s) {
+                for (const field of hfBandInfo.discrete_field_s) {
+                    const bt = +field.area.Bottom;
+                    const btIdx = posArr.indexOf(bt);
+                    if (btIdx >= 0) {
+                        lvs[btIdx].push('<tr class="text-center">');
+                        lvs[btIdx].push('<td></td>');
+                        lvs[btIdx].push(`<td>${me._createTypeSelection(1)}</td>`);
+                        // lvs[btIdx].push(`<td>${me._createSelectionFields_BK(rptTpl, field.FieldID, field.ParamID)}</td>`);
+                        lvs[btIdx].push(`<td>${me._createSelectionFields(field.FieldID, field.ParamID)}</td>`);
+                        lvs[btIdx].push(`<td class="text-left"><input class="form-control form-control-sm" value="${field.area.Left || '0'}" type="text"></td>`);
+                        lvs[btIdx].push(`<td class="text-left"><input class="form-control form-control-sm" value="${field.area.Right || '0'}" type="text"></td>`);
+                        lvs[btIdx].push(`<td class="text-left"><input class="form-control form-control-sm" value="${field.Prefix || ''}" type="text"></td>`);
+                        lvs[btIdx].push(`<td class="text-left"><input class="form-control form-control-sm" value="${field.Suffix || ''}" type="text"></td>`);
+                        lvs[btIdx].push('<td><a href="#" onclick="reportFormatSetupObj.onDeleteInfo(this)" class="text-danger mr-2" title="删除">删除</a></td>');
+                        lvs[btIdx].push('</tr>');
+                    }
+                }
+            }
+            //...
+            for (const lv of lvs) {
+                rst.push(...lv);
+            }
+            rst.push('     </tbody>')
+            rst.push('  </table>')
+        }
+        return rst;
+    },
+    updateReportTemplate: () => {
+        const me = reportFormatSetupObj;
+        const rptTemplate = me._prepareReportTemplate();
+        if (rptTemplate) {
+            const params = { tplId: curNodes[0].ID, tplName: curNodes[0].name, tplObj: JSON.stringify(rptTemplate) };
+            const url = '/report_api/updateReportTemplate';
+            $.bootstrapLoading.start();
+            CommonAjax.postXsrfEx(url, params, 300000, true, getCookie('csrfToken_j'),
+                async function(result){
+                    console.log(result);
+                    $.bootstrapLoading.end();
+                }, function(err){
+                    console.log(err);
+                    $.bootstrapLoading.end();
+                }, function(ex){
+                    console.log(ex);
+                    $.bootstrapLoading.end();
+                }
+            );
+        }
+    },
+    preview: () => {
+        // alert('hi');
+        const me = reportFormatSetupObj;
+        const rptTemplate = me._prepareReportTemplate();
+        if (rptTemplate) {
+            const params = { tplObj: JSON.stringify(rptTemplate) };
+            const url = '/report_api/getPreviewReport';
+            $.bootstrapLoading.start();
+            CommonAjax.postXsrfEx(url, params, 300000, true, getCookie('csrfToken_j'),
+                async function(result){
+                    // console.log(result);
+                    JpcCanvasOutput.cleanCanvas(me.canvas);
+                    JpcCanvasOutput.drawPageBorder(result.pageRst, me.canvas, getScreenDPI());
+                    JpcCanvasOutput.highlightConflictArea(result.pageRst, 1);
+                    JpcCanvasOutput.drawToCanvas(result.pageRst, me.canvas, 1);
+                    $.bootstrapLoading.end();
+                }, function(err){
+                    console.log(err);
+                    $.bootstrapLoading.end();
+                }, function(ex){
+                    console.log(ex);
+                    $.bootstrapLoading.end();
+                }
+            );
+        }
+    },
+    _prepareReportTemplate: () => {
+        let rst = null;
+        const me = reportFormatSetupObj;
+        const curNodes = me.treeObj.getSelectedNodes();
+        if (curNodes.length > 0) {
+            const headers = me._createCommonCollctInfo('#rpt_header_table_div');
+            const footers = me._createCommonCollctInfo('#rpt_footer_table_div');
+            const rptTemplate = me.templateObj[`${curNodes[0].ID}`];
+            if (rptTemplate) {
+                me._setupTemplate(rptTemplate, headers, footers, null);
+                rst = rptTemplate;
+            }
+        }
+        return rst;
+    },
+    _createCommonCollctInfo: (containerId) => {
+        let lvCnt = 0;
+        const rst = { texts: [], fields: [], levels: 0 };
+        const createAreaFromDom = (firstDom, lv) => {
+            const rst = { Top: lv, Left: 0, Right: 0, Bottom: lv + 1, H_CalculationType: "percentage", V_CalculationType: "percentage" };
+            rst.Left = firstDom.cells[3].childNodes[0].value || 0;
+            rst.Right = firstDom.cells[4].childNodes[0].value || 0;
+            return rst;
+        };
+        const ParamDftValMap = { '5010': '第 X 页', '5011': '共 X 页' };
+        const domObj = $(containerId);
+        for (let idx = 0; idx < domObj[0].childNodes[1].childNodes[3].childNodes.length; idx++) {
+            const hDom = domObj[0].childNodes[1].childNodes[3].childNodes[idx];
+            if (hDom.innerText && hDom.innerText.includes('新增')) {
+                let dtlCnt = 0;
+                for (let idxN = idx + 1; idxN < domObj[0].childNodes[1].childNodes[3].childNodes.length; idxN++) {
+                    // 开始创建文本对象或指标对象
+                    const hnDom = domObj[0].childNodes[1].childNodes[3].childNodes[idxN];
+                    if (hnDom.innerText && !hnDom.innerText.includes('新增')) {
+                        dtlCnt++;
+                        const areaObj = createAreaFromDom(hnDom, lvCnt);
+                        if (hnDom.cells[1].childNodes[0][0].selected) {
+                            // 创建文本对象
+                            const txtObj = { area: areaObj, font: 'Header', control: 'Header', style: 'Default_None', Label: ''};
+                            txtObj.Label = hnDom.cells[2].childNodes[0].value || '';
+                            rst.texts.push(txtObj);
+                        } else {
+                            // 创建指标对象
+                            const fieldObj = { area: areaObj, font: 'Header', control: 'Header', style: 'Default_None', Prefix: '', Suffix: ''};
+                            fieldObj.Prefix = hnDom.cells[5].childNodes[0].value || '';
+                            fieldObj.Suffix = hnDom.cells[6].childNodes[0].value || '';
+                            for (const fIdOpt of hnDom.cells[2].childNodes[0]) {
+                                if (fIdOpt.selected) {
+                                    if (fIdOpt.attributes['fid']) {
+                                        fieldObj.FieldID = +fIdOpt.attributes[fIdOpt.attributes.length - 1].nodeValue;
+                                    } else {
+                                        fieldObj.ParamID = fIdOpt.attributes[fIdOpt.attributes.length - 1].nodeValue;
+                                        fieldObj.Default_Value = ParamDftValMap[fieldObj.ParamID] || '';
+                                    }
+                                    break;
+                                }
+                            }
+                            rst.fields.push(fieldObj);
+                        }
+                    } else {
+                        break;
+                    }
+                }
+                if (dtlCnt > 0) lvCnt++; // 有子项的才会被统计进来
+                
+                // lvCnt++; // 不能无条件的统计增加层次
+            }
+        }
+        if (lvCnt > 0) {
+            // 所有rst的text、field的纵坐标都要重新整理一遍
+            rst.levels = lvCnt;
+            const lvH = +(100 / lvCnt).toFixed(3);
+            for (const txt of rst.texts) {
+                txt.area.Top = lvH * txt.area.Top;
+                txt.area.Bottom = lvH * txt.area.Bottom;
+            }
+            for (const fld of rst.fields) {
+                fld.area.Top = lvH * fld.area.Top;
+                fld.area.Bottom = lvH * fld.area.Bottom;
+            }
+        }
+        return rst;
+    },
+    _setupTemplate: (rptTpl, headersInfo, footersInfo, signatureInfo = null) => {
+        const me = reportFormatSetupObj;
+        me._setTitle(rptTpl, $("#rpt_title_input")[0].value);
+        const headerBandInfo = me._getRightBandInfoObj(me._getRightInfoObj(rptTpl), 'HeaderBand');
+        if (headerBandInfo) {
+            const headerBand = rptTpl['布局框_集合'].find(item => item.Name === 'HeaderBand');
+            // 1. 高度
+            if (headerBand) {
+                headerBand.Height = 0.6 * headersInfo.levels;
+            }
+            // 2.
+            headerBandInfo.text_s = headersInfo.texts;
+            headerBandInfo.discrete_field_s = headersInfo.fields;
+        }
+        const footerBandInfo = me._getRightBandInfoObj(me._getRightInfoObj(rptTpl), 'FooterBand');
+        if (footerBandInfo) {
+            const footerBand = rptTpl['布局框_集合'].find(item => item.Name === 'FooterBand');
+            // 1. 高度
+            if (footerBand) {
+                footerBand.Height = 0.6 * footersInfo.levels;
+            }
+            // 2.
+            footerBandInfo.text_s = footersInfo.texts;
+            footerBandInfo.discrete_field_s = footersInfo.fields;
+        }
+    },
+};

+ 69 - 0
app/public/report/js/rpt_public.js

@@ -326,3 +326,72 @@ function restoreSignCells(pageObj, bkSignCells, bkTxtSignCells, bkPicSignCells)
         bkPicSignCells.splice(0);
     }
 }
+
+function filterUnchkTplTreeNode(topNode, srcData) {
+    const _chkAndSpliceItem = function(pNode, pStr) {
+        let rst = false;
+        if (srcData.indexOf(pStr + pNode.name) < 0) {
+            if (pNode.items && pNode.items.length > 0) {
+                for (let subIdx = pNode.items.length - 1; subIdx >= 0; subIdx--) {
+                    if (!_chkAndSpliceItem(pNode.items[subIdx], pStr + pNode.name + FOLDER_SEPERATER)) {
+                        pNode.items.splice(subIdx, 1);
+                    } else {
+                        rst = true;
+                    }
+                }
+            }
+        } else {
+            rst = true;
+        }
+        return rst;
+    };
+    for (let rIdx = topNode.items.length - 1; rIdx >= 0; rIdx--) {
+        if (!_chkAndSpliceItem(topNode.items[rIdx], '')) {
+            topNode.items.splice(rIdx, 1);
+        }
+    }
+}
+
+function filterReportsByType(allCommonTreeNodes, allIndiTreeNodes, current_biz_type, topTreeNodes, custTreeNodes) {
+    function _filterBySourceType(nodes, srcType) {
+        for (let idx = nodes.length - 1; idx >= 0; idx--) {
+            if (!nodes[idx].hasOwnProperty('source_type')) {
+                nodes[idx].source_type = 1;
+            }
+            if (nodes[idx].source_type !== srcType) {
+                nodes.splice(idx, 1);
+            }
+        }
+    }
+    const newCommonTreeNodes = JSON.parse(JSON.stringify(allCommonTreeNodes));
+    const newCustomTreeNodes = JSON.parse(JSON.stringify(allIndiTreeNodes));
+    let bizType = 1;
+    if (current_biz_type === 'stage') bizType = 1; // 计量期
+    if (current_biz_type === 'change_prepay') bizType = 10; // 预付款
+    if (current_biz_type === 'change_material_adjustment') bizType = 30; // 材差
+    _filterBySourceType(newCommonTreeNodes, bizType);
+    _filterBySourceType(newCustomTreeNodes, bizType);
+    topTreeNodes[0].items = [];
+    topTreeNodes[0].items.push(...newCustomTreeNodes);
+    topTreeNodes[1].items = [];
+    topTreeNodes[1].items.push(...newCommonTreeNodes);
+    if (bizType === 1) {
+        filterUnchkTplTreeNode(topTreeNodes[0], custTreeNodes.customize);
+        filterUnchkTplTreeNode(topTreeNodes[1], custTreeNodes.common);
+    }
+    // zTreeOprObj.getReportTemplateTree();
+}
+
+function chkAndSetNode(parentItem) {
+    parentItem.title = '';
+    if (parentItem.nodeType === 1) {
+        parentItem.isParent = true;
+    } else {
+        parentItem.title = parentItem.name + '(' + parentItem.refId + ')';
+    }
+    if (parentItem.items) {
+        for (let dtlItem of parentItem.items) {
+            chkAndSetNode(dtlItem);
+        }
+    }
+}

+ 1 - 1
app/reports/rpt_component/jpc_cross_tab.js

@@ -846,7 +846,7 @@ JpcCrossTabSrv.prototype.createNew = function() {
                                 } else {
                                     if (colIdx === 0) JpcFieldHelper.resetFormat(tab_field, map_data_field, customizeCfg);
                                 }
-                                rst.push(me.outputTabField(band, tab_field, data_field, contentValuesIdx[rowIdx][colIdx], -1, rows, rowIdx, cols, colIdx, unitFactor, true, controls));
+                                rst.push(me.outputTabField(band, tab_field, data_field, contentValuesIdx[rowIdx][colIdx], -1, rows, rowIdx, cols, colIdx, unitFactor, false, controls));
                             }
                         }
                     }

+ 4 - 0
app/router.js

@@ -822,6 +822,7 @@ module.exports = app => {
 
     // 报表
     app.get('/tender/:id/report', sessionAuth, tenderCheck, subProjectCheck, 'reportController.index');
+    app.get('/tender/:id/report/format/setup', sessionAuth, tenderCheck, subProjectCheck, 'reportController.indexForFormatSetup');
     app.get('/tender/:id/change/:cid/report', sessionAuth, tenderCheck, subProjectCheck, uncheckTenderCheck, 'reportController.indexForChange');
     app.get('/tender/:id/change/plan/:cplnid/report', sessionAuth, tenderCheck, subProjectCheck, uncheckTenderCheck, 'reportController.indexForChangePlan');
     app.get('/tender/:id/change/project/:cprjid/report', sessionAuth, tenderCheck, subProjectCheck, uncheckTenderCheck, 'reportController.indexForChangeProject');
@@ -844,6 +845,9 @@ module.exports = app => {
     app.post('/tender/report_api/getReport', sessionAuth, subProjectCheck, 'reportController.getReport');
     app.post('/contract/report_api/getReport', sessionAuth, 'reportController.getReport');
     app.post('/report_api/getReport', sessionAuth, 'reportController.getReport');
+    app.post('/report_api/getReportTemplate', sessionAuth, 'reportController.getReportTemplate');
+    app.post('/report_api/updateReportTemplate', sessionAuth, 'reportController.updateReportTemplate');
+    app.post('/report_api/getPreviewReport', sessionAuth, 'reportController.getPreviewReport');
     app.post('/tender/report_api/getDirectReport', sessionAuth, 'reportController.createExcelFilesFromDirectData');
     app.post('/tender/report_api/getMultiReports', sessionAuth, subProjectCheck, 'reportController.getMultiReportsEx');
     // app.post('/contract/report_api/getMultiReports', sessionAuth, 'reportController.getMultiReportsEx');

+ 4 - 4
app/service/rpt_tpl.js

@@ -54,7 +54,7 @@ module.exports = app => {
                     id: 0,
                     rpt_type: refTpl.rpt_type,
                     rpt_tpl_name: refTpl.rpt_tpl_name,
-                    rpt_content: refTpl.rpt_content
+                    rpt_content: refTpl.rpt_content,
                 };
                 rst = await this.transaction.insert(this.tableName, data);
                 await this.transaction.commit();
@@ -74,7 +74,7 @@ module.exports = app => {
                     id: refTpl.id,
                     rpt_type: refTpl.rpt_type,
                     rpt_tpl_name: refTpl.rpt_tpl_name,
-                    rpt_content: refTpl.rpt_content
+                    rpt_content: refTpl.rpt_content,
                 };
                 rst = await this.transaction.insert(this.tableName, data);
                 await this.transaction.commit();
@@ -86,11 +86,11 @@ module.exports = app => {
             return rst;
         }
 
-        async updateRptTpl(tplId, tplName, tplObj) {
+        async updateRptTpl(tplId, tplName, JsonTplObj) {
             let rst = null;
             this.transaction = await this.db.beginTransaction();
             try {
-                const data = { id: tplId, rpt_type: 0, rpt_tpl_name: tplName, rpt_content: JSON.stringify(tplObj) };
+                const data = { id: tplId, rpt_tpl_name: tplName, rpt_content: JsonTplObj };
                 rst = await this.transaction.update(this.tableName, data);
                 await this.transaction.commit();
             } catch (ex) {

+ 11 - 73
app/view/report/index.ejs

@@ -126,6 +126,11 @@
                 </div>
                 <% } %>
                 <% } %>
+                <% if (isAdmin) { %>
+                <div class="d-inline-block">
+                    <a href="/tender/<%- tender_id %>/report/format/setup" class=" btn btn-light btn-sm  text-primary">设置</a>
+                </div>
+                <% } %>
             </div>
             <div>
             </div>
@@ -391,7 +396,8 @@
             }
         });
         buildAdvancePaySelection(adList);
-        filterReportsByType();
+        filterReportsByType(ALL_COMMON_TREE_NODES, ALL_INDIVI_TREE_NODES, CURRENT_SELECTED_BIZ_TYPE, TOP_TREE_NODES, CUST_TREE_NODES);
+        zTreeOprObj.getReportTemplateTree();
     }
     $('#prepay-select-item a').on('click', function (){
         advancePayClick(this);
@@ -423,7 +429,8 @@
             // 还有归档按钮处理
             // current_stage_id
             rptArchiveObj.toggleBtn(true);
-            filterReportsByType();
+            filterReportsByType(ALL_COMMON_TREE_NODES, ALL_INDIVI_TREE_NODES, CURRENT_SELECTED_BIZ_TYPE, TOP_TREE_NODES, CUST_TREE_NODES);
+            zTreeOprObj.getReportTemplateTree();
         } else if (type === 'change_prepay') {
             // 预付款
             $('#divSelectableStages').hide();
@@ -457,9 +464,9 @@
             current_stage_id = -500;
             rptArchiveObj.toggleBtn(true);
             buildMaterialSelection();
-            filterReportsByType();
+            filterReportsByType(ALL_COMMON_TREE_NODES, ALL_INDIVI_TREE_NODES, CURRENT_SELECTED_BIZ_TYPE, TOP_TREE_NODES, CUST_TREE_NODES);
+            zTreeOprObj.getReportTemplateTree();
         }
-        // filterReportsByType();
     });
 
     $.divResizer({
@@ -722,77 +729,8 @@
     }
     buildTplTree();
 
-    function chkAndSetNode(parentItem) {
-        parentItem.title = '';
-        if (parentItem.nodeType === 1) {
-            parentItem.isParent = true;
-        } else {
-            parentItem.title = parentItem.name + '(' + parentItem.refId + ')';
-        }
-        if (parentItem.items) {
-            for (let dtlItem of parentItem.items) {
-                chkAndSetNode(dtlItem);
-            }
-        }
-    }
-
     const SCREEN_DPI = [];
 
-    function filterReportsByType() {
-        function _filterBySourceType(nodes, srcType) {
-            for (let idx = nodes.length - 1; idx >= 0; idx--) {
-                if (!nodes[idx].hasOwnProperty('source_type')) {
-                    nodes[idx].source_type = 1;
-                }
-                if (nodes[idx].source_type !== srcType) {
-                    nodes.splice(idx, 1);
-                }
-            }
-        }
-        const newCommonTreeNodes = JSON.parse(JSON.stringify(ALL_COMMON_TREE_NODES));
-        const newCustomTreeNodes = JSON.parse(JSON.stringify(ALL_INDIVI_TREE_NODES));
-        let bizType = 1;
-        if (CURRENT_SELECTED_BIZ_TYPE === 'stage') bizType = 1; // 计量期
-        if (CURRENT_SELECTED_BIZ_TYPE === 'change_prepay') bizType = 10; // 预付款
-        if (CURRENT_SELECTED_BIZ_TYPE === 'change_material_adjustment') bizType = 30; // 材差
-        _filterBySourceType(newCommonTreeNodes, bizType);
-        _filterBySourceType(newCustomTreeNodes, bizType);
-        TOP_TREE_NODES[0].items = [];
-        TOP_TREE_NODES[0].items.push(...newCustomTreeNodes);
-        TOP_TREE_NODES[1].items = [];
-        TOP_TREE_NODES[1].items.push(...newCommonTreeNodes);
-        if (bizType === 1) {
-            filterUnchkTplTreeNode(TOP_TREE_NODES[0], CUST_TREE_NODES.customize);
-            filterUnchkTplTreeNode(TOP_TREE_NODES[1], CUST_TREE_NODES.common);
-        }
-        zTreeOprObj.getReportTemplateTree();
-    }
-
-    function filterUnchkTplTreeNode(topNode, srcData) {
-        const _chkAndSpliceItem = function(pNode, pStr) {
-            let rst = false;
-            if (srcData.indexOf(pStr + pNode.name) < 0) {
-                if (pNode.items && pNode.items.length > 0) {
-                    for (let subIdx = pNode.items.length - 1; subIdx >= 0; subIdx--) {
-                        if (!_chkAndSpliceItem(pNode.items[subIdx], pStr + pNode.name + FOLDER_SEPERATER)) {
-                            pNode.items.splice(subIdx, 1);
-                        } else {
-                            rst = true;
-                        }
-                    }
-                }
-            } else {
-                rst = true;
-            }
-            return rst;
-        };
-        for (let rIdx = topNode.items.length - 1; rIdx >= 0; rIdx--) {
-            if (!_chkAndSpliceItem(topNode.items[rIdx], '')) {
-                topNode.items.splice(rIdx, 1);
-            }
-        }
-    }
-
     function buildTplTree() {
         if (TOP_TREE_NODES.length > 0) {
             //1. 整理模板树 (原始状态的TOP_TREE_NODES包含了推荐报表与定制表,需要分割)

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

@@ -813,6 +813,7 @@
         </div>
     </div>
 </div>
+
 <% if (![-100, -200, -300, -301, -302, -303, -600].includes(stg_id)) { %>
 <% include ../stage/audit_modal.ejs %>
 <% } %>

+ 38 - 0
app/view/report/rpt_format_popup.ejs

@@ -0,0 +1,38 @@
+<!--更新报表模板-->
+<div class="modal fade" id="updaterp" data-backdrop="static">
+    <div class="modal-dialog" role="document" >
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">确认更新提示</h5>
+            </div>
+            <div class="modal-body">
+                <h6 id="rename_rpt_confirm">确认更新报表模板「模板名称」?</h6>
+                <h6>确定后,原有模板将被替换,且无法撤销,请谨慎操作。</h6>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">关闭</button>
+                <button type="button" class="btn btn-sm btn-primary" data-dismiss="modal" onclick="reportFormatSetupObj.updateReportTemplate()">确认</button>
+            </div>
+        </div>
+    </div>
+</div> 
+<!--弹出抽屉预览报表-->
+<div class="modal fade" id="setup_view-bb" data-backdrop="static">
+    <div class="modal-dialog modal-xl modal-dw-xl modal-dialog-scrollable" role="document" >
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">预览报表</h5>
+            </div>
+            <div class="modal-body">
+                <div class="pageContainer">
+                    <div class="page" style="width: 100%; height: auto;">
+                        <canvas id="rptSetupPreviewCanvas" height="820" width="1150"></canvas>
+                    </div>
+                </div>
+            </div>
+            <div class="modal-footer">
+            <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">关闭</button>
+            </div>
+        </div>
+    </div>
+</div> 

+ 391 - 0
app/view/report/rpt_format_setup.ejs

@@ -0,0 +1,391 @@
+<% if (stg_id === -1) {%>
+    <% include ../tender/tender_sub_menu.ejs %>
+<% } else if (stg_id === -100) { %>
+    <% include ../payment_safe/sub_menu.ejs %>
+<% } else if (stg_id === -200) { %>
+    <% include ../budget/sub_menu.ejs %>
+<% } else if ([-300, -301, -302, -303].includes(stg_id)) { %>
+    <% include ../tender/tender_sub_menu.ejs %>
+<% } else if ([-600].includes(stg_id)) { %>
+    <% include ../contract/sub_menu.ejs %>
+<% } else { %>
+    <% include ../stage/stage_sub_menu.ejs %>
+<% } %>
+<div class="panel-content">
+    <div class="panel-title">
+        <div class="title-main">
+            <h2>报表设置</h2>
+        </div>
+    </div>
+    <div class="content-wrap">
+        <div class="c-header p-0"></div>
+        <div class="c-body">
+            <div class="sjs-height-0 row">
+                <div class="col-auto pr-0" id="format-tree-view" style="width: 20%">
+                    <div class="d-inline-block">
+                        <div class="dropdown">
+                        <button class="btn btn-sm btn-light dropdown-toggle text-primary" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> 显示层级</button>
+                        <div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
+                            <a class="dropdown-item" name="showLevel" tag="1" href="javascript: void(0);">第一层</a>
+                            <a class="dropdown-item" name="showLevel" tag="2" href="javascript: void(0);">第二层</a>
+                            <a class="dropdown-item" name="showLevel" tag="3" href="javascript: void(0);">第三层</a>
+                            <a class="dropdown-item" name="showLevel" tag="4" href="javascript: void(0);">第四层</a>
+                            <a class="dropdown-item" name="showLevel" tag="last" href="javascript: void(0);">最底层</a>
+                        </div>
+                        </div>
+                    </div>
+                    <div class="d-inline-block  ml-auto">
+                        <div id="xmj-search">                                
+                            <div class="ml-2">
+                            <div class="input-group input-group-sm">
+                                <input type="text" class="form-control" placeholder="输入名称查找" id="pos-keyword">
+                                <div class="input-group-append">
+                                    <span class="input-group-text" id="pos-search-hint">结果:0</span>
+                                </div>
+                                <div class="input-group-append">
+                                    <button class="btn btn-outline-secondary" type="button" title="上一个" id="search-pre-pos"><i class="fa fa-angle-double-left"></i></button>
+                                    <button class="btn btn-outline-secondary" type="button" title="下一个" id="search-next-pos"><i class="fa fa-angle-double-right"></i></button>
+                                </div>
+                            </div>
+                        </div>
+                        </div>
+                    </div>                    
+                    <div class="sjs-height-1" style="overflow: auto">
+                        <div class="text-center"></div>
+                        <ul id="rptFormatSetupTplTree" class="ztree"></ul>
+                    </div>
+                </div>
+                <div class="col-auto" id="format-main-view" style="width: 80%">
+                    <nav class="nav nav-tabs m-3" role="tablist">
+                        <a class="nav-item nav-link active" data-toggle="tab" data-target="#format-setup-edit" href="#format-setup-edit" role="tab" aria-selected="true">修改报表模板</a>
+                        <a class="nav-item nav-link" data-toggle="tab" data-target="#format-setup-private" href="#format-setup-private" role="tab" aria-selected="false">报表私有化</a>
+                    </nav>
+                    <div class="tab-content m-3">
+                        <div id="format-setup-edit" class="tab-pane active">
+                            <div class="d-inline-block">
+                                <ul class="nav nav-pills m-0">
+                                    <li class="nav-item mr-1"><a onclick="reportFormatSetupObj.preview()" data-toggle="modal" data-target="#setup_view-bb" class=" btn btn-light btn-sm text-primary">预览报表</a></li>
+                                    <!-- <li class="nav-item mr-1"><a href="#view-sign" data-toggle="modal" data-target="#view-sign" class=" btn btn-light btn-sm  text-primary">预览电子签名</a></li> -->
+                                    <li class="nav-item"><button onclick="$('#show_confirm_format_setup').trigger('click')" class="btn btn-outline-primary btn-sm">更新报表模板</button></li>
+                                    <li class="nav-item"><a id="show_confirm_format_setup" href="#" class=" btn btn-sm btn-primary" data-toggle="modal" data-target="#updaterp" style="display:none"></a></li>
+                                </ul>
+                            </div>
+                            <div class="mt-3">
+                                <label class="form-text alert alert-danger">请先点击「预览报表/预览电子签名」,确定修改无误后,再点击「更新报表模板」(一旦更新无法撤销)。</label>
+                            </div>
+                            <div class="sjs-height-0">
+                                <div class="row">
+                                    <div class="col-9">
+                                        <div class="card mb-3" id="rpt_header_parent_div">
+                                            <div class="card-header d-flex justify-content-between" id="rpt_header_div">
+                                                <div>表眉</div>
+                                                <div><a href="" class="btn btn-sm btn-light text-primary">新增行</a></div>
+                                            </div>
+                                            <div class="card-body" id="rpt_header_table_div"></div>
+                                        </div>
+                                        <div class="card mb-3">
+                                            <div class="card-header d-flex justify-content-between" id="rpt_footer_div">
+                                                <div>表脚</div>
+                                                <div><a href="" class="btn btn-sm btn-light text-primary">新增行</a></div>
+                                            </div>
+                                            <div class="card-body" id="rpt_footer_table_div"></div>
+                                        </div>
+                                    </div>
+                                    <div class="col">
+                                        <div class="card mb-3">
+                                            <div class="card-header d-flex justify-content-between">
+                                                <div>表标题</div>
+                                            </div>
+                                            <div class="card-body">
+                                                <div class="form-group">
+                                                    <input class="form-control form-control-sm" id="rpt_title_input" placeholder="" type="text">
+                                                </div>
+                                            </div>                                                
+                                        </div>
+                                        <!-- <div class="card mb-3">
+                                            <div class="card-header d-flex justify-content-between">
+                                                <div>电子签名</div>
+                                            </div>
+                                            <div class="card-body">
+                                                <ul class="list-unstyled">
+                                                    <li class="pl-3"><a href=""><i class="fa fa-plus"></i> 添加新签字人</a></li>
+                                                </ul>
+                                            </div>
+                                        </div> -->
+                                    </div>
+                                </div>
+                            </div>
+                        </div>
+                        <div id="format-setup-private" class="tab-pane">
+                            <div class="sjs-height-0 row" style="height: 479px;">
+                                <div class="col">
+                                    <div class="d-flex">
+                                        <div class="d-inline-block dropdown ">
+                                            <button class="btn btn-outline-primary btn-sm dropdown-toggle" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+                                                添加用户
+                                            </button>
+                                            <div class="dropdown-menu" aria-labelledby="dropdownMenuButton" style="width:220px">
+												<div class="mb-2 p-2"><input class="form-control form-control-sm" placeholder="姓名/手机 检索"></div>
+												<dl class="list-unstyled book-list">
+													<dt><a href=""><i class="fa fa-minus-square-o"></i></a> 建设单位</dt>
+														<dd class="border-bottom p-2 mb-0">
+															<p class="mb-0 d-flex"><span class="text-primary">李旭</span><span class="ml-auto">15800000003</span></p>
+															<span class="text-muted">中交第一公路工程局有限公司国道311线满别公路施工一分部</span>
+														</dd>
+													<dt><a href=""><i class="fa fa-minus-square-o"></i></a> 监理单位</dt>
+														<dd class="border-bottom p-2 mb-0">
+															<p class="mb-0 d-flex"><span class="text-primary">李旭</span><span class="ml-auto">15800000003</span></p>
+															<span class="text-muted">中交第一公路工程局有限公司国道311线满别公路施工一分部</span>
+														</dd>
+													<dt><a href=""><i class="fa fa-plus-square"></i></a> 施工单位</dt>
+													<dt><a href=""><i class="fa fa-plus-square"></i></a> 设计单位</dt>
+												</dl>
+											</div>
+                                        </div>
+                                    </div>
+                                </div>
+                            </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 type="text/javascript">  autoFlashHeight();</script>
+<script type="text/javascript" src="/public/report/js/rpt_public.js"></script>
+<script type="text/javascript" src="/public/js/ztree_common.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/report/js/rpt_format_setup.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/rpt_move_signature.js"></script>
+<script type="text/javascript" src="/public/report/js/jpc_output.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">
+    const SCREEN_DPI = [];
+    const rpt_format_setting = {
+        view: {
+            selectedMulti: false
+        },
+        check: {
+            enable: true
+        },
+        data: {
+            keep: {
+                parent:true,
+                leaf:true
+            },
+            key: {
+                children: "items",
+                title: "title",
+                isHidden: "hidden"
+            },
+            simpleData: {
+                enable: true,
+                rootPId: -1
+            }
+        },
+        callback: {
+            onCheck: reportFormatSetupObj.onCheck,
+            onClick: reportFormatSetupObj.onClick
+        }
+    };
+
+    let TOP_TREE_NODES = <%- rpt_tpl_data %>;
+    const CUST_TREE_NODES = <%- cust_tpl_data %>;
+    const ALL_COMMON_TREE_NODES = [];
+    const ALL_INDIVI_TREE_NODES = <%- all_indivi_tpl_data %>;
+    const FOLDER_SEPERATER = '->';
+    const PAGE_SHOW = {closeWatermark: 0, closeExportPdf: 0, closeExportExcel: 0, showArchive: 0, closeShowAllCustomized: 0, isTextSignature: 0, closeArchiveSignature: 0};
+    let CURRENT_SELECTED_BIZ_TYPE = 'stage';
+    //
+    for (let item of TOP_TREE_NODES) {
+        if (item.name === '通用报表') {
+            item.name = '推荐报表';
+            item.items = '[]'; // 推荐报表不能调
+        }
+    }
+    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);
+            }
+        }
+    }
+    for (let item of ALL_COMMON_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 ALL_INDIVI_TREE_NODES) {
+        // item.items = JSON.parse(item.items);
+        if (item.items && item.items.length > 0) {
+            for (let dtlItem of item.items) {
+                chkAndSetNode(dtlItem);
+            }
+        }
+    }
+
+    // 初始化报表模板数
+    buildTplTree();
+
+    // 初始化报表树
+    filterReportsByType(ALL_COMMON_TREE_NODES, ALL_INDIVI_TREE_NODES, CURRENT_SELECTED_BIZ_TYPE, TOP_TREE_NODES, CUST_TREE_NODES);
+    getReportTemplateTree();
+    refreshNodes();
+
+    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);
+                }
+            }
+            if (TOP_TREE_NODES.length > 0) {
+                // 1.1 移除未被选择的模板
+                filterUnchkTplTreeNode(TOP_TREE_NODES[0], CUST_TREE_NODES.common);
+                TOP_TREE_NODES.unshift(individualNode); //定制在前
+                if (CUST_TREE_NODES.customize && CUST_TREE_NODES.customize.length > 0) {
+                    // 优先过滤用户选择
+                    filterUnchkTplTreeNode(TOP_TREE_NODES[0], CUST_TREE_NODES.customize);
+                } else if (PAGE_SHOW['closeShowAllCustomized'] === 1) {
+                    // 如果没有用户选择 且 关闭显示所有定制表
+                    filterUnchkTplTreeNode(TOP_TREE_NODES[0], []);
+                }
+            }
+            //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 getReportTemplateTree() {
+        const _chkIfShouldFilter = function(rptItem, currentRptType = 'normal') {
+            let rst = (!(rptItem.released) && rptItem.nodeType === 2 || rptItem.hidden); //未发布判断 或 故意隐藏
+            if (!rst) {
+                // !!!因判断逻辑调整,所有报表类型的判断是在目录级别,不单独判断报表了 XXX
+                // 预付款、材料调差等业务是需要以下的的判断的,上面的情况后经过仔细检查,属于报表模板数据问题,先忽略
+                // 根据当前业务类型判断及检测其他非同类报表,如动态决算类型、支付审批类型
+                switch(currentRptType) {
+                    case 'normal':
+                        if (rptItem.flags) {
+                            if (rptItem.flags.dynamicType || rptItem.flags.payAuditType) {
+                                if (rptItem.flags.dynamicType && rptItem.flags.dynamicType !== 'N/A') {
+                                    rst = true;
+                                    break;
+                                }
+                                if (rptItem.flags.payAuditType && rptItem.flags.payAuditType !== 'N/A') {
+                                    rst = true;
+                                    break;
+                                }
+                            }
+                        }
+                        // 检测:预付款
+                        if (CURRENT_SELECTED_BIZ_TYPE === 'change_prepay' && rptItem.nodeType === 2) {
+                            rst = true;
+                            if (rptItem.flags && rptItem.flags.rptTplType) {
+                                if (current_advance_id > 0) {
+                                    rst = !((rptItem.flags.rptTplType || '') === getAdvanceType());
+                                }
+                            }
+                        }
+                        break;
+                    case 'juesuan':
+                        break;
+                    case 'zhifushenpi':
+                        break;
+                    default:
+                        break;
+                }
+            }
+            return rst;
+        };
+        const private_remove_hide_item = function (items, nlv) {
+            if (items && items.length > 0) {
+                for (let i = items.length - 1; i >= 0; i--) {
+                    if (_chkIfShouldFilter(items[i])) {
+                        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 _changeSourceType = function(items, newType = 1) {
+            if (items && items.length > 0) {
+                for (let i = items.length - 1; i >= 0; i--) {
+                    items[i].source_type = newType;
+                    if (items[i].items && items[i].items.length > 0) {
+                        _changeSourceType(items[i].items);
+                    }
+                }
+            }
+        };
+        let nodeLv = 0;
+        private_remove_hide_item(TOP_TREE_NODES, nodeLv);
+        zTreeHelper.createTreeDirectly(TOP_TREE_NODES, rpt_format_setting, "rptFormatSetupTplTree", reportFormatSetupObj);
+    }
+
+    function refreshNodes() {
+        let me = reportFormatSetupObj;
+        const _set_archive_icon = function (tplNode) {
+            if (!tplNode.isParent) {
+                tplNode.icon = "/public/css/ztree/img/diy/11.png"; // 躺枪,ico_docu已换成其他图标,调整的代价大,在这里调整
+            }
+        };
+        let private_setupIsParent = function(node){
+            node.isParent = (node.nodeType === RT.NodeType.NODE || node.level === 0);
+            _set_archive_icon(node);
+            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();
+    }
+
+</script>

+ 3 - 0
rpt_format_setup.js

@@ -0,0 +1,3 @@
+const reportFormatSetupObj = {
+    treeObj: null,
+};