Browse Source

code sync for report

TonyKang 7 năm trước cách đây
mục cha
commit
4eea7fb4bf
82 tập tin đã thay đổi với 16044 bổ sung2701 xóa
  1. 17 0
      app.js
  2. 354 0
      app/controller/report_controller.js
  3. 196 0
      app/public/js/common_ajax.js
  4. 35 0
      app/public/js/fsUtil.js
  5. 9 0
      app/public/js/rpt_tpl_def.js
  6. 326 0
      app/public/js/rpt_value_define.js
  7. 228 0
      app/public/js/scMathUtil.js
  8. 36 0
      app/public/js/storageUtil.js
  9. 284 0
      app/public/js/stringUtil.js
  10. 31 0
      app/public/js/string_util_light.js
  11. 173 0
      app/public/js/treeDataHelper.js
  12. 30 0
      app/public/js/ztree_common.js
  13. 416 0
      app/public/report/js/jpc_output.js
  14. 69 0
      app/public/report/js/jpc_output_value_define.js
  15. 57 0
      app/public/report/js/rpt_cfg_const.js
  16. 906 0
      app/public/report/js/rpt_main.js
  17. 108 0
      app/public/report/js/rpt_preview_common.js
  18. 343 0
      app/public/report/js/rpt_print.js
  19. 35 0
      app/reports/public/fsUtil.js
  20. 208 0
      app/reports/public/scMathUtil.js
  21. 284 0
      app/reports/public/stringUtil.js
  22. 165 0
      app/reports/public/treeUtil.js
  23. 87 0
      app/reports/rpt_component/helper/jpc_helper_area.js
  24. 121 0
      app/reports/rpt_component/helper/jpc_helper_band.js
  25. 200 0
      app/reports/rpt_component/helper/jpc_helper_common.js
  26. 83 0
      app/reports/rpt_component/helper/jpc_helper_common_output.js
  27. 257 0
      app/reports/rpt_component/helper/jpc_helper_cross_tab.js
  28. 132 0
      app/reports/rpt_component/helper/jpc_helper_discrete.js
  29. 158 0
      app/reports/rpt_component/helper/jpc_helper_field.js
  30. 64 0
      app/reports/rpt_component/helper/jpc_helper_flow_tab.js
  31. 20 0
      app/reports/rpt_component/helper/jpc_helper_text.js
  32. 58 0
      app/reports/rpt_component/jpc_band.js
  33. 162 0
      app/reports/rpt_component/jpc_bill_tab.js
  34. 851 0
      app/reports/rpt_component/jpc_cross_tab.js
  35. 112 0
      app/reports/rpt_component/jpc_data.js
  36. 32 0
      app/reports/rpt_component/jpc_event.js
  37. 395 0
      app/reports/rpt_component/jpc_ex.js
  38. 68 0
      app/reports/rpt_component/jpc_field.js
  39. 1468 0
      app/reports/rpt_component/jpc_flow_tab.js
  40. 19 0
      app/reports/rpt_component/jpc_function.js
  41. 26 0
      app/reports/rpt_component/jpc_param.js
  42. 143 0
      app/reports/rpt_component/jpc_rte.js
  43. 325 0
      app/reports/rpt_component/jpc_value_define.js
  44. 281 0
      app/reports/util/excel_base_files/theme1.xml
  45. BIN
      app/reports/util/pdf_base_files/Smart.ttf
  46. BIN
      app/reports/util/pdf_base_files/Smart_bold.ttf
  47. BIN
      app/reports/util/pdf_base_files/Smart_bold_italic.ttf
  48. BIN
      app/reports/util/pdf_base_files/Smart_italic.ttf
  49. BIN
      app/reports/util/pdf_base_files/arial.ttf
  50. BIN
      app/reports/util/pdf_base_files/simhei.ttf
  51. BIN
      app/reports/util/pdf_base_files/simhei_bold.ttf
  52. BIN
      app/reports/util/pdf_base_files/simhei_bold_italic.ttf
  53. BIN
      app/reports/util/pdf_base_files/simhei_italic.ttf
  54. BIN
      app/reports/util/pdf_base_files/simkai.ttf
  55. BIN
      app/reports/util/pdf_base_files/simkai_bold.ttf
  56. BIN
      app/reports/util/pdf_base_files/simkai_bold_italic.ttf
  57. BIN
      app/reports/util/pdf_base_files/simkai_italic.ttf
  58. 875 0
      app/reports/util/rpt_calculation_data_util.js
  59. 1035 0
      app/reports/util/rpt_excel_util.js
  60. 28 0
      app/reports/util/rpt_font_util.js
  61. 391 0
      app/reports/util/rpt_pdf_util.js
  62. 288 0
      app/reports/util/rpt_svg_util.js
  63. 53 0
      app/reports/util/rpt_tmp_file_sweep.js
  64. 3 1
      app/router.js
  65. 22 0
      app/service/project.js
  66. 38 0
      app/service/rpt_customize_cfg.js
  67. 39 0
      app/service/rpt_mapping_field.js
  68. 38 0
      app/service/rpt_pre_define_cfg.js
  69. 74 0
      app/service/rpt_tpl.js
  70. 109 0
      app/service/rpt_tree_node.js
  71. 11 0
      app/service/stage.js
  72. 19 0
      app/service/stage_bills_final.js
  73. 234 0
      app/view/report/index.ejs
  74. 47 0
      app/view/report/rpt_content_format.html
  75. 38 0
      app/view/report/rpt_export.html
  76. 46 0
      app/view/report/rpt_export_excel.html
  77. 37 0
      app/view/report/rpt_margins.html
  78. 42 0
      app/view/report/rpt_print.html
  79. 26 0
      app/view/report/rpt_printA3.html
  80. 37 0
      app/view/report/rpt_select_projects.html
  81. 3141 2700
      package-lock.json
  82. 1 0
      package.json

+ 17 - 0
app.js

@@ -96,4 +96,21 @@ module.exports = app => {
         };
         app.minify(['spreadjs_rela/spreadjs_zh.js', 'path_tree.js']);
     }
+    // 设置Date对象Format函数
+    Date.prototype.Format = function(fmt) {
+        const o = {
+            'M+': this.getMonth() + 1, // 月份
+            'd+': this.getDate(), // 日
+            'h+': this.getHours(), // 小时
+            'm+': this.getMinutes(), // 分
+            's+': this.getSeconds(), // 秒
+            'q+': Math.floor((this.getMonth() + 3) / 3), // 季度
+            'S': this.getMilliseconds(), // 毫秒
+        };
+        if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (this.getFullYear() + '').substr(4 - RegExp.$1.length));
+        for (const k in o) {
+            if (new RegExp('(' + k + ')').test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? (o[k]) : (('00' + o[k]).substr(('' + o[k]).length)));
+        }
+        return fmt;
+    };
 };

+ 354 - 0
app/controller/report_controller.js

@@ -0,0 +1,354 @@
+'use strict';
+
+/**
+ * Created by Tony on 2019/7/5.
+ */
+
+const tenderMenu = require('../../config/menu').tenderMenu;
+const measureType = require('../const/tender').measureType;
+const JpcEx = require('../reports/rpt_component/jpc_ex');
+const JV = require('../reports/rpt_component/jpc_value_define');
+const rptDataExtractor = require('../reports/util/rpt_calculation_data_util');
+const fsUtil = require('../public/js/fsUtil');
+
+module.exports = app => {
+    class ReportController extends app.BaseController {
+        /**
+         * 报表显示页面
+         *
+         * @param {Object} ctx - egg全局context
+         * @return {void}
+         */
+        async index(ctx) {
+            try {
+                const tender = ctx.tender;
+                const stage = ctx.stage;
+                // console.log(tender.data);
+                // console.log(tender.data.project_id);
+                let stage_id = -1;
+                let stage_order = -1;
+                let stage_times = -1;
+                const treeNodes = await ctx.service.rptTreeNode.getNodesByProjectId([-1, tender.data.project_id]);
+                const custCfg = await ctx.service.rptCustomizeCfg.getCustomizeCfgByUserId('Administrator');
+                const stageList = await ctx.service.stage.getValidStageShort(tender.id);
+                // console.log(maxStageAmt[0].maxAmt);
+                if (stage !== null && stage !== undefined) {
+                    stage_id = stage.id;
+                    stage_order = stage.order;
+                    stage_times = stage.times;
+                }
+                const renderData = {
+                    tender: tender.data,
+                    tenderInfo: tender.info,
+                    rpt_tpl_data: JSON.stringify(treeNodes),
+                    cust_cfg: JSON.stringify(custCfg),
+                    project_id: tender.data.project_id,
+                    tender_id: tender.id,
+                    stg_id: stage_id,
+                    stg_order: stage_order,
+                    stg_times: stage_times,
+                    stage_list: JSON.stringify(stageList),
+                    tenderMenu,
+                    preUrl: '/tender/' + tender.id,
+                    measureType,
+                    // jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.ledger.explode),
+                };
+                // await this.layout('report/index.ejs', renderData, 'ledger/audit_modal.ejs');
+                await this.layout('report/index.ejs', renderData);
+            } catch (err) {
+                this.log(err);
+                console.log(err);
+                // ctx.redirect('/tender/' + ctx.tender.id);
+            }
+        }
+
+        /**
+         * 获取报表数据
+         *
+         * @param {Object} ctx - egg全局context
+         * @return {void}
+         */
+        async getReport(ctx) {
+            try {
+                // console.log('in getReport');
+                const params = JSON.parse(ctx.request.body.params);
+                // console.log(params);
+                let rptTpl = await ctx.service.rptTpl.getTplById(params.rpt_tpl_id);
+                if (!rptTpl || rptTpl.length !== 1) {
+                    throw '获取模板失败';
+                }
+                rptTpl = JSON.parse(rptTpl[0].rpt_content);
+                // console.log('get the template!');
+                // test
+                const pageRst = await getAllPagesCommon(ctx, rptTpl, params, JV.PAGING_OPTION_NORMAL, JV.OUTPUT_TYPE_NORMAL);
+                // console.log(pageRst);
+                /*
+                // --------------------------
+                const printCom = JpcEx.createNew();
+                const defProperties = await ctx.service.rptPreDefineCfg.getCfgById('Administrator');
+                // console.log(rptTpl);
+                rptTpl[JV.NODE_MAIN_INFO][JV.NODE_PAGE_INFO][JV.PROP_PAGE_SIZE] = pageSize;
+                printCom.initialize(rptTpl);
+                // console.log('initialize the template!');
+                const pageRst = printCom.outputAsPreviewPage(rptTpl, JSON.parse(defProperties[0].defined_content));
+                // console.log(pageRst);
+                */
+                ctx.body = { data: pageRst };
+                // ctx.body = { data: { msg: 'test the network' } };
+                ctx.status = 201;
+            } catch (ex) {
+                this.setMessage(ex.toString(), this.messageType.ERROR);
+            }
+        }
+    }
+
+    return ReportController;
+};
+
+async function getReportData(ctx, params, filters) {
+    const rst = {};
+    const runnableRst = [];
+    const runnableKey = []; // 这个配合runnableRst用,未来考虑并行查询优化
+    for (const filter of filters) {
+        switch (filter) {
+            case 'project' :
+                runnableRst.push(ctx.service.project.getProjectById(params.project_id));
+                runnableKey.push('project');
+                break;
+            case 'tender_info' :
+                runnableRst.push(ctx.service.tenderInfo.getTenderInfo(params.tender_id));
+                runnableKey.push('tender_info');
+                break;
+            case 'ledger' :
+                runnableRst.push(ctx.service.ledger.getDataByTenderId(params.tender_id, 0));
+                runnableKey.push('ledger');
+                break;
+            case 'stage_bills':
+                runnableRst.push(ctx.service.stageBills.getLastestStageData(params.tender_id, params.stage_id));
+                runnableKey.push('stage_bills');
+                break;
+            case 'stage_bills_final':
+                runnableRst.push(ctx.service.stageBillsFinal.getFinalDataEx(params.tender_id, params.stage_order));
+                runnableKey.push('stage_bills_final');
+                break;
+            case 'stage':
+                // runnableRst.push(ctx.service.stageBillsFinal.getFinalDataEx(params.tender_id, params.stage_order));
+                // runnableKey.push('pay');
+                break;
+            case 'stage_pay':
+                // runnableRst.push(ctx.service.stageBillsFinal.getFinalDataEx(params.tender_id, params.stage_order));
+                // runnableKey.push('stage_pay');
+                break;
+            default:
+                break;
+        }
+    }
+    const queryRst = await Promise.all(runnableRst);
+    for (let idx = 0; idx < runnableKey.length; idx++) {
+        rst[runnableKey[idx]] = queryRst[idx];
+        // console.log(runnableKey[idx]);
+        // if (rst[runnableKey[idx]] instanceof Array) console.log('is Array')
+        // else console.log('is not Array');
+    }
+    return rst;
+}
+
+async function getAllPagesCommon(ctx, rptTpl, params, option, outputType) {
+    // let rptTpl = null;
+    const rptDataUtil = new rptDataExtractor();
+    rptDataUtil.initialize(rptTpl);
+    // console.log(rptTpl);
+    const filter = rptDataUtil.getDataRequestFilter();
+    // console.log(filter.tables);
+    const rawDataObj = await getReportData(ctx, params, filter.tables);
+    // console.log(rawDataObj);
+    try {
+        // console.log('before assemble');
+        const tplData = rptDataUtil.assembleData(rawDataObj);
+        // console.log(tplData);
+        const printCom = JpcEx.createNew();
+        if (params.pageSize) rptTpl[JV.NODE_MAIN_INFO][JV.NODE_PAGE_INFO][JV.PROP_PAGE_SIZE] = params.pageSize;
+        if (params.orientation && (params.orientation !== 'null')) rptTpl[JV.NODE_MAIN_INFO][JV.NODE_PAGE_INFO][JV.PROP_ORIENTATION] = params.orientation;
+        let defProperties = await ctx.service.rptPreDefineCfg.getCfgById('Administrator');
+        // console.log('defProperties: ');
+        // console.log(defProperties[0].defined_content);
+        defProperties = JSON.parse(defProperties[0].defined_content);
+        if (params.custCfg) {
+            setupCustomizeCfg(params.custCfg, rptTpl, defProperties);
+        } else {
+            // setupCustomizeCfg(defProperties, rptTpl, defProperties);
+        }
+        const dftOption = params.option || JV.PAGING_OPTION_NORMAL;
+        printCom.initialize(rptTpl);
+        printCom.analyzeData(rptTpl, tplData, defProperties, dftOption, outputType);
+        const maxPages = printCom.totalPages;
+        let pageRst = null;
+        if (maxPages > 0) {
+            pageRst = printCom.outputAsSimpleJSONPageArray(rptTpl, tplData, 1, maxPages, defProperties, params.custCfg);
+        } else {
+            pageRst = printCom.outputAsPreviewPage(rptTpl, defProperties);
+        }
+        if (pageRst) {
+            // fsUtil.writeObjToFile(pageRst, 'D:/GitHome/temp/testBuiltPageResult.jsp');
+        } else {
+            //
+        }
+        // console.log(pageRst);
+        return pageRst;
+    } catch (ex) {
+        // console.log("报表数据异常: userId " + user_id + ", project id: " + prj_id);
+        console.log(ex);
+    }
+}
+
+function setupCustomizeCfg(customizeCfg, rptTpl, defProperties) {
+    const tmpObj = {};
+    // 1. 字体
+    const newFonts = [];
+    for (const font of defProperties.fonts) {
+        const copyFont = {};
+        copyFont.ID = font.ID;
+        for (const fontProp of JV.FONT_PROPS) {
+            copyFont[fontProp] = font[fontProp];
+        }
+        newFonts.push(copyFont);
+        tmpObj[font.ID] = copyFont;
+    }
+    const private_setup_font = function(propStr, newFont) {
+        if (tmpObj[propStr]) {
+            tmpObj[propStr].Name = newFont.Name;
+            tmpObj[propStr].FontHeight = String(newFont.FontHeight);
+            tmpObj[propStr].FontBold = newFont.FontBold;
+            tmpObj[propStr].FontItalic = newFont.FontItalic;
+            tmpObj[propStr].FontUnderline = newFont.FontUnderline;
+        }
+    };
+    for (const custFont of customizeCfg.fonts) {
+        switch (custFont.CfgDispName) {
+            case '表标题':
+                private_setup_font('ReportTitle_Main', custFont);
+                break;
+            case '列标题':
+                private_setup_font('HeaderColumn', custFont);
+                private_setup_font('FooterColumn', custFont);
+                break;
+            case '正文内容':
+                private_setup_font('Content', custFont);
+                break;
+            case '合计':
+                private_setup_font('GrandTotal', custFont);
+                private_setup_font('SectionTotal', custFont);
+                break;
+            case '表眉/表脚':
+                private_setup_font('Header', custFont);
+                private_setup_font('Footer', custFont);
+                break;
+            default:
+                break;
+        }
+    }
+    // 1.1 窄体
+    if (tmpObj.Content_Narrow) {
+        if (customizeCfg.isNarrow) {
+            tmpObj.Content_Narrow.Name = 'Arial Narrow';
+        } else {
+            if (tmpObj.Content) {
+                tmpObj.Content_Narrow.Name = tmpObj.Content.Name;
+            } else {
+                tmpObj.Content_Narrow.Name = '宋体';
+            }
+        }
+    }
+    defProperties.fonts = newFonts;
+    // 2. 页边距
+    rptTpl[JV.NODE_MAIN_INFO][JV.NODE_MARGINS][JV.PROP_LEFT] = customizeCfg.margins[JV.PROP_LEFT] / 10;
+    rptTpl[JV.NODE_MAIN_INFO][JV.NODE_MARGINS][JV.PROP_RIGHT] = customizeCfg.margins[JV.PROP_RIGHT] / 10;
+    rptTpl[JV.NODE_MAIN_INFO][JV.NODE_MARGINS][JV.PROP_TOP] = customizeCfg.margins[JV.PROP_TOP] / 10;
+    rptTpl[JV.NODE_MAIN_INFO][JV.NODE_MARGINS][JV.PROP_BOTTOM] = customizeCfg.margins[JV.PROP_BOTTOM] / 10;
+    // 3. 边框竖线
+    if (!(customizeCfg.showVerticalLine)) {
+        const private_copy_border = function(src) {
+            const rst = {};
+            rst.Position = src.Position;
+            rst.LineWeight = src.LineWeight;
+            rst.DashStyle = src.DashStyle;
+            rst.Color = src.Color;
+            return rst;
+        };
+        const newStyles = [];
+        for (let i = 0; i < defProperties.styles.length; i++) {
+            const style = defProperties.styles[i];
+            newStyles.push(style);
+            if (style.ID === 'BORDER_ALL_AROUND') {
+                const newStyle = {};
+                newStyle.ID = style.ID;
+                newStyle.CfgDispName = style.CfgDispName;
+                newStyle.border_style = [];
+                for (const border of style.border_style) {
+                    const newBorder = private_copy_border(border);
+                    newStyle.border_style.push(newBorder);
+                    if (border.Position === 'Left' || border.Position === 'Right') {
+                        newBorder.LineWeight = 0;
+                    }
+                }
+                newStyles[newStyles.length - 1] = newStyle;
+            }
+        }
+        defProperties.styles = newStyles;
+    }
+    // 4. 补0
+    const private_Setup_Format = function(tabFields) {
+        if (tabFields) {
+            for (const tabField of tabFields) {
+                if (tabField[JV.PROP_FORMAT]) {
+                    tabField[JV.PROP_FORMAT] = tabField[JV.PROP_FORMAT].replace(new RegExp('#', 'gm'), '0');
+                }
+            }
+        }
+    };
+    if (customizeCfg.fillZero) {
+        if (rptTpl[JV.NODE_FLOW_INFO]) {
+            if (rptTpl[JV.NODE_FLOW_INFO][JV.NODE_FLOW_CONTENT]) private_Setup_Format(rptTpl[JV.NODE_FLOW_INFO][JV.NODE_FLOW_CONTENT][JV.PROP_FLOW_FIELDS]);
+            if (rptTpl[JV.NODE_FLOW_INFO][JV.NODE_FLOW_PAGE_SUM]) private_Setup_Format(rptTpl[JV.NODE_FLOW_INFO][JV.NODE_FLOW_PAGE_SUM][JV.PROP_SUM_FIELDS]);
+            if (rptTpl[JV.NODE_FLOW_INFO][JV.NODE_FLOW_SEG_SUM]) private_Setup_Format(rptTpl[JV.NODE_FLOW_INFO][JV.NODE_FLOW_SEG_SUM][JV.PROP_SUM_FIELDS]);
+            if (rptTpl[JV.NODE_FLOW_INFO][JV.NODE_FLOW_GROUP] && rptTpl[JV.NODE_FLOW_INFO][JV.NODE_FLOW_GROUP][JV.PROP_GROUP_LINES]) private_Setup_Format(rptTpl[JV.NODE_FLOW_INFO][JV.NODE_FLOW_GROUP][JV.PROP_GROUP_LINES][JV.PROP_SUM_FIELDS]);
+            if (rptTpl[JV.NODE_FLOW_INFO][JV.NODE_DISCRETE_INFO]) {
+                for (const discrete of rptTpl[JV.NODE_FLOW_INFO][JV.NODE_DISCRETE_INFO]) {
+                    private_Setup_Format(discrete[JV.PROP_DISCRETE_FIELDS]);
+                }
+            }
+            if (rptTpl[JV.NODE_FLOW_INFO_EX]) {
+                if (rptTpl[JV.NODE_FLOW_INFO_EX][JV.NODE_FLOW_CONTENT]) private_Setup_Format(rptTpl[JV.NODE_FLOW_INFO_EX][JV.NODE_FLOW_CONTENT][JV.PROP_FLOW_FIELDS]);
+                if (rptTpl[JV.NODE_FLOW_INFO_EX][JV.NODE_FLOW_PAGE_SUM]) private_Setup_Format(rptTpl[JV.NODE_FLOW_INFO_EX][JV.NODE_FLOW_PAGE_SUM][JV.PROP_SUM_FIELDS]);
+                if (rptTpl[JV.NODE_FLOW_INFO_EX][JV.NODE_FLOW_SEG_SUM]) private_Setup_Format(rptTpl[JV.NODE_FLOW_INFO_EX][JV.NODE_FLOW_SEG_SUM][JV.PROP_SUM_FIELDS]);
+                if (rptTpl[JV.NODE_FLOW_INFO_EX][JV.NODE_FLOW_GROUP]) private_Setup_Format(rptTpl[JV.NODE_FLOW_INFO_EX][JV.NODE_FLOW_GROUP][JV.PROP_SUM_FIELDS]);
+                if (rptTpl[JV.NODE_FLOW_INFO_EX][JV.NODE_FLOW_GROUP] && rptTpl[JV.NODE_FLOW_INFO_EX][JV.NODE_FLOW_GROUP][JV.PROP_GROUP_LINES]) private_Setup_Format(rptTpl[JV.NODE_FLOW_INFO_EX][JV.NODE_FLOW_GROUP][JV.PROP_GROUP_LINES][JV.PROP_SUM_FIELDS]);
+                if (rptTpl[JV.NODE_FLOW_INFO_EX][JV.NODE_DISCRETE_INFO]) {
+                    for (const discrete of rptTpl[JV.NODE_FLOW_INFO_EX][JV.NODE_DISCRETE_INFO]) {
+                        private_Setup_Format(discrete[JV.PROP_DISCRETE_FIELDS]);
+                    }
+                }
+            }
+        } else if (rptTpl[JV.NODE_BILL_INFO]) {
+            if (rptTpl[JV.NODE_BILL_INFO][JV.NODE_BILL_CONTENT]) private_Setup_Format(rptTpl[JV.NODE_BILL_INFO][JV.NODE_BILL_CONTENT][JV.PROP_BILL_FIELDS]);
+            if (rptTpl[JV.NODE_BILL_INFO][JV.NODE_DISCRETE_INFO]) {
+                for (const discrete of rptTpl[JV.NODE_BILL_INFO][JV.NODE_DISCRETE_INFO]) {
+                    private_Setup_Format(discrete[JV.PROP_DISCRETE_FIELDS]);
+                }
+            }
+        } else if (rptTpl[JV.NODE_CROSS_INFO]) {
+            if (rptTpl[JV.NODE_CROSS_INFO][JV.NODE_CROSS_CONTENT]) private_Setup_Format(rptTpl[JV.NODE_CROSS_INFO][JV.NODE_CROSS_CONTENT][JV.TAB_CROSS_FIELDS]);
+            if (rptTpl[JV.NODE_CROSS_INFO][JV.NODE_CROSS_ROW]) private_Setup_Format(rptTpl[JV.NODE_CROSS_INFO][JV.NODE_CROSS_ROW][JV.TAB_CROSS_FIELDS]);
+            if (rptTpl[JV.NODE_CROSS_INFO][JV.NODE_CROSS_COL]) private_Setup_Format(rptTpl[JV.NODE_CROSS_INFO][JV.NODE_CROSS_COL][JV.TAB_CROSS_FIELDS]);
+            if (rptTpl[JV.NODE_CROSS_INFO][JV.NODE_CROSS_COL_SUM]) private_Setup_Format(rptTpl[JV.NODE_CROSS_INFO][JV.NODE_CROSS_COL_SUM][JV.TAB_CROSS_FIELDS]);
+            if (rptTpl[JV.NODE_CROSS_INFO][JV.NODE_CROSS_ROW_EXT]) private_Setup_Format(rptTpl[JV.NODE_CROSS_INFO][JV.NODE_CROSS_ROW_EXT][JV.TAB_CROSS_FIELDS]);
+            if (rptTpl[JV.NODE_CROSS_INFO][JV.NODE_CROSS_ROW_SUM_EXT]) private_Setup_Format(rptTpl[JV.NODE_CROSS_INFO][JV.NODE_CROSS_ROW_SUM_EXT][JV.TAB_CROSS_FIELDS]);
+            if (rptTpl[JV.NODE_CROSS_INFO][JV.NODE_DISCRETE_INFO]) {
+                for (const discrete of rptTpl[JV.NODE_CROSS_INFO][JV.NODE_DISCRETE_INFO]) {
+                    private_Setup_Format(discrete[JV.PROP_DISCRETE_FIELDS]);
+                }
+            }
+        }
+    }
+}
+

+ 196 - 0
app/public/js/common_ajax.js

@@ -0,0 +1,196 @@
+/**
+ * Created by Mai on 2017/4/20.
+ */
+
+/**
+ * 设置全局的AJAX请求默认选项
+ * 主要设置了AJAX请求遇到Session过期的情况
+ */
+$.ajaxSetup({
+    complete: function (data) {
+        if (data.responseJSON&&data.responseJSON.ret_code && data.responseJSON.ret_code == 99) {
+            alert(data.responseJSON.ret_msg);
+            var top = getTopWindow();
+            setTimeout('top.location.href = "/login";', 300);
+        }
+    }
+});
+
+/**
+ * 在页面中任何嵌套层次的窗口中获取顶层窗口
+ * @return 当前页面的顶层窗口对象
+ */
+function getTopWindow() {
+    var p = window;
+    while (p != p.parent) {
+        p = p.parent;
+    }
+    return p;
+}
+
+var CommonAjax = {
+    get:function (url,data,cb,dataType) {
+        $.get(url,data,cb,dataType)
+    },
+    post: function (url, data, successCallback, errorCallback) {
+        $.ajax({
+            type:"POST",
+            url: url,
+            data: {'data': JSON.stringify(data)},
+            dataType: 'json',
+            cache: false,
+            timeout: 150000,
+            success: function(result){
+                if (result.error === 0) {
+                    if (successCallback) {
+                        successCallback(result.data);
+                    }
+                } else {
+                    alert('error: ' + result.message);
+                    if (errorCallback) {
+                        errorCallback(result.message);
+                    }
+                }
+            },
+            error: function(jqXHR, textStatus, errorThrown){
+                alert('url: ' + url +' error ' + textStatus + " " + errorThrown);
+                if (errorCallback) {
+                    errorCallback();
+                }
+            }
+        });
+    },
+    postRationLib: function (url, data, successCallback, errorCallback) {
+        $.ajax({
+            type:"POST",
+            url: url,
+            data: data,
+            dataType: 'json',
+            cache: false,
+            timeout: 50000,
+            success: function(result){
+                if (!result.error) {
+                    if (successCallback) {
+                        successCallback(result.data);
+                    }
+                } else {
+                    alert('error: ' + result.message);
+                    if (errorCallback) {
+                        errorCallback();
+                    }
+                }
+            },
+            error: function(jqXHR, textStatus, errorThrown){
+                alert('error ' + textStatus + " " + errorThrown);
+                if (errorCallback) {
+                    errorCallback();
+                }
+            }
+        });
+    },
+    postEx: function(url, params, dftTimeOutMilSec, isAsync, successCallback, failCallback, exceptionCallback){
+        $.ajax({
+            type:"POST",
+            url: url,
+            data: {'params': JSON.stringify(params)},
+            dataType: 'json',
+            cache: false,
+            async: isAsync,
+            timeout: dftTimeOutMilSec,
+            success: function(result){
+                if (!result.error) {
+                    if (successCallback) {
+                        successCallback(result.data);
+                    }
+                } else {
+                    alert('error: ' + result.message);
+                    if (failCallback) {
+                        failCallback();
+                    }
+                }
+            },
+            error: function(jqXHR, textStatus, errorThrown){
+                alert('url: ' + url +', error: ' + textStatus + ", " + errorThrown);
+                if (exceptionCallback) {
+                    exceptionCallback();
+                }
+            }
+        });
+    },
+    postXsrfEx: function(url, params, dftTimeOutMilSec, isAsync, csrfToken, successCallback, failCallback, exceptionCallback){
+        $.ajax({
+            type:"POST",
+            url: url,
+            data: {'params': JSON.stringify(params)},
+            dataType: 'json',
+            cache: false,
+            async: isAsync,
+            timeout: dftTimeOutMilSec,
+            beforeSend: function (xhr) {
+                xhr.setRequestHeader('x-csrf-token', csrfToken);
+            },
+            success: function(result){
+                if (result) {
+                    if (successCallback) {
+                        successCallback(result);
+                    }
+                } else {
+                    if (failCallback) {
+                        failCallback();
+                    }
+                }
+            },
+            error: function(jqXHR, textStatus, errorThrown){
+                alert('url: ' + url +', error: ' + textStatus + ", " + errorThrown);
+                if (exceptionCallback) {
+                    exceptionCallback();
+                }
+            }
+        });
+    }
+};
+
+async function ajaxPost(url, data) {
+    return new Promise(function (resolve, reject) {
+        $.ajax({
+            type:"POST",
+            url: url,
+            data: {'data': JSON.stringify(data)},
+            dataType: 'json',
+            cache: false,
+            timeout: 50000,
+            success: function(result){
+                if (result.error === 0 ||result.error ===false) {
+                    resolve(result.data);
+                } else {
+                    alert('error: ' + result.message);
+                    reject(result.message);
+                }
+            },
+            error: function(jqXHR, textStatus, errorThrown){
+                ajaxErrorInfo(jqXHR, textStatus, errorThrown);
+                reject("请求错误");
+            }
+        });
+
+    });
+}
+
+
+function ajaxErrorInfo(jqXHR, textStatus, errorThrown) {
+    if(textStatus == 'timeout'){
+        alert('网络连接超时,请刷新您的网页。');
+    }else {
+        alert('url: ' + url +' error ' + textStatus + " " + errorThrown);
+    }
+}
+
+function getCookie(name) {
+    let arr,reg=new RegExp("(^| )"+name+"=([^;]*)(;|$)");
+
+    if(arr=document.cookie.match(reg))
+        return unescape(arr[2]);
+    else
+        return null;
+}
+

+ 35 - 0
app/public/js/fsUtil.js

@@ -0,0 +1,35 @@
+/**
+ * Created by Tony on 2017/4/10.
+ */
+
+let fs = require('fs');
+
+module.exports = {
+    writeArrayToFile: function(arr, filePath) {
+        if (arr && filePath && Array.isArray(arr)) {
+            let chunks = [], len = 0;
+            for (let i = 0; i < arr.length; i++) {
+                let buffer = new Buffer(arr[i]);
+                chunks.push(buffer);
+                len += buffer.length;
+                //
+            }
+            let resultBuffer = new Buffer(len);
+            for(let i=0,size=chunks.length,pos=0;i<size;i++){
+                chunks[i].copy(resultBuffer,pos);
+                pos += chunks[i].length;
+            }
+            fs.writeFile(filePath, resultBuffer, function(err){
+                if(err) throw err;
+                //console.log('Write file: ' + filePath + ' ok!');
+            });
+        }
+    },
+    writeObjToFile: function(obj, filePath) {
+        if (obj) {
+            let arr = [];
+            arr.push(JSON.stringify(obj));
+            this.writeArrayToFile(arr, filePath);
+        }
+    }
+};

+ 9 - 0
app/public/js/rpt_tpl_def.js

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

+ 326 - 0
app/public/js/rpt_value_define.js

@@ -0,0 +1,326 @@
+/**
+ * Created by Tony on 2017/6/7.
+ */
+const JV = {
+    NODE_CROSS_INFO: "交叉表_信息",
+    NODE_CROSS_ROW: "交叉行",
+    NODE_CROSS_COL: "交叉列",
+    NODE_CROSS_CONTENT: "交叉数据",
+    NODE_CROSS_ROW_SUM: "交叉行合计",
+    NODE_CROSS_COL_SUM: "交叉列合计",
+    NODE_CROSS_ROW_EXT: "交叉行拓展",
+    NODE_CROSS_ROW_SUM_EXT: "交叉行拓展合计",
+    NODE_CROSS_ROW_AD_HOC: "交叉行AD_HOC",
+    NODE_FIELD_MAP: "指标_数据_映射",
+    NODE_DISCRETE_FIELDS: "离散指标_集合",
+    NODE_NO_MAPPING_FIELDS: "无映射离散指标_集合",
+    NODE_DISCRETE_PARAMS: "离散参数_集合",
+    NODE_MASTER_FIELDS: "主数据指标_集合",
+    NODE_MASTER_FIELDS_EX: "主数据指标_拓展集合",
+    NODE_DETAIL_FIELDS: "从数据指标_集合",
+    NODE_DETAIL_FIELDS_EX: "从数据指标_拓展集合",
+    NODE_BAND_COLLECTION: "布局框_集合",
+    NODE_FORMULAS: "计算式_集合",
+    NODE_EVENTS: "事件_集合",
+    NODE_DISCRETE_INFO: "离散信息",
+    NODE_BILL_INFO: "账单式表_信息",
+    NODE_BILL_CONTENT : "账单式表_数据",
+    NODE_FLOW_INFO: "流水式表_信息",
+    NODE_FLOW_INFO_EX: "流水式表_拓展信息",
+    NODE_FLOW_GROUP: "流水式表_分组信息",
+    NODE_FLOW_SEG_SUM: "流水式表_段统计信息",
+    NODE_FLOW_PAGE_SUM: "流水式表_页统计信息",
+    NODE_FLOW_COLUMN : "流水式表_列",
+    NODE_FLOW_CONTENT : "流水式表_数据",
+    PROP_MULTI_COLUMN: "多列显示数量",
+    PROP_FLOW_EX_DISPLAY_MODE: "流水拓展显示模式",
+    DISPLAY_MODE_INDEPENDENT: "单独模式",
+    DISPLAY_MODE_FOLLOW: "紧随模式",
+
+    NODE_MAIN_INFO: "主信息",
+    NODE_MAIN_INFO_RPT_NAME: "报表名称",
+    NODE_PAGE_INFO: "打印页面_信息",
+    NODE_PAGE_SIZE: "纸张宽高",
+    NODE_MARGINS: "页边距",
+    NODE_RPT_BIZ_TYPE: "报表业务类型",
+    NODE_BIZ_TYPE_AUDI: "审核类型",
+    NODE_BIZ_TYPE_SUM: "汇总类型",
+    NODE_BIZ_TYPE_DETAIL: "明细类型",
+
+    NODE_MAP_DATA_HANDLE_INFO: "映射数据预处理",
+    PROP_DATA_KEY: "映射数据对象",
+    PROP_PARENT_DATA_KEY: "父映射数据对象",
+    PROP_PARENT_CHILD_SORT_KEY: "父子排序键",
+    PROP_PARENT_SORT_KEYS: "父排序键值集",
+    PROP_CHILD_SORT_KEYS: "子排序键值集",
+    PROP_OTHER_SUB_SORT: "其他子排序",
+    PROP_OTHER_SUB_FILTER: "其他子过滤",
+    PROP_HANDLE_TYPE: "预处理类型",
+    PROP_FILTER_KEYS: "过滤键值集",
+    PROP_FILTER_TOP_BILLS_NODES: "清单顶节点集",
+    PROP_FILTER_OTHER_BILLS_NODES: "其他清单节点集",
+    PROP_FILTER_COMPARE_OBJ: "compareObjKey",
+    PROP_FILTER_COMPARE_OBJ_KEY: "compareObjIdKey",
+    PROP_FILTER_COMPARE_VAL: "compareValue",
+    PROP_FILTER_CONDITION: "判断条件",
+    PROP_HANDLE_TYPE_FILTER: "过滤",
+    PROP_HANDLE_TYPE_SUM: "合计",
+    PROP_HANDLE_TYPE_SORT: "排序",
+    PROP_HANDLE_TYPE_ADD_DUMMY: "增加Dummy数据",
+    PROP_HANDLE_TYPE_ADJUST: "数据调整",
+    PROP_HANDLE_TYPE_BILLS_DATA_MOVE: "量材数据转移",
+    PROP_HANDLE_TYPE_COMPONENT_MOVE: "组成物数据转移",
+    PROP_HANDLE_TYPE_COMPONENT_REPLACEMENT: "组成物替换",
+    PROP_HANDLE_TYPE_PRECISION: "合计精度",
+
+    PROP_ADJUST_COLLECTION: "数据调整集",
+    PROP_ADJUST_ACTION: "action",
+    PROP_ADJUST_ACTION_VAL: "actionValue",
+    PROP_DUMMY_COLLECTION: "Dummy数据集",
+    PROP_DUMMY_VAL: "Dummy数据对象值",
+    PROP_FREQUENCY: "频率",
+    PROP_GRP_KEYS: "GrpKeyIds",
+    PROP_SORT_TYPE: "排序方式",
+    PROP_SORT_TYPE_SELF_DEFINE_LOGIC: "自定义逻辑",
+    PROP_SORT_KEYS: "排序键值集",
+    PROP_SUM_GROUP_KEYS: "分组键值集",
+    PROP_SUM_SUM_KEYS: "统计键值集",
+    PROP_SUM_CALC_AHEAD: "统计前计算",
+    PROP_SUM_CACL_TYPE: "计算类型",
+    PROP_FIELD_EXP_MAP: "mapExpression",
+    PROP_PRECISION: "Precision",
+    PROP_FIXED_PRECISION_AMT: "fixedPrecisionNum",
+    PROP_FIELD_EXP_FIXED_MAP: "fixedMapExpression",
+    PROP_FIELD_EXP_FLEXIBLE_MAP: "flexibleMapExpression",
+    PROP_FLEXIBLE_REF_FILED_ID: "flexibleRefFieldID",
+
+    NODE_FONT_COLLECTION: "font_collection",
+    NODE_STYLE_COLLECTION: "style_collection",
+    NODE_CONTROL_COLLECTION: "control_collection",
+
+    PROP_ID: "ID",
+    PROP_AD_HOC_DATA: "data_field",
+    PROP_CMN_HEIGHT: "CommonHeight",
+    PROP_CMN_WIDTH: "CommonWidth",
+    PROP_BAND_NAME: "BandName",
+    PROP_BAND_NORMAL_ONLY: "normalOnly",
+    PROP_BAND_EX_ONLY: "exOnly",
+    PROP_BAND_EX_JOIN_AFTER: "isJoinAfter",
+
+    PROP_UNITS: "单位",
+    PROP_PAGE_SIZE: "页规格",
+    PROP_ORIENTATION: "方向",
+    PROP_LEFT: "Left",
+    PROP_RIGHT: "Right",
+    PROP_TOP: "Top",
+    PROP_BOTTOM: "Bottom",
+    PROP_DATA_TYPE: "DataType",
+    PROP_NAME: "Name",
+    PROP_DFT_VALUE: "Default_Value",
+    PROP_EXPRESSION: "expression",
+    PROP_RUN_TYPE: "run_type",
+    PROP_BORDER_STYLE: "border_style",
+    PROP_POSITION: "Position",
+    PROP_HIDDEN: "Hidden",
+    PROP_IS_SERIAL: "isSerial",
+    PROP_IS_MERGE: "isMerge",
+    PROP_COMBINE_TYPE: "combineType",
+    PROP_IS_AUTO_HEIGHT: "isAutoHeight",
+    PROP_FONT: "font",
+    PROP_CONTROL: "control",
+    PROP_STYLE: "style",
+    PROP_VALUE: "Value",
+    PROP_LABEL: "Label",
+    PROP_AREA: "area",
+    PROP_DISCRETE_FIELDS: "discrete_field_s",
+    PROP_FLOW_FIELDS: "flow_field_s",
+    PROP_BILL_FIELDS: "bill_field_s",
+    PROP_CROSS_FIELDS: "cross_field_s",
+    PROP_CROSS_DISPLAY_ORDER: "cross_display_order",
+    PROP_GROUP_FIELDS: "group_field_s", //用来分组的指标(如按清单、定额etc...)
+    PROP_GROUP_LINES: "group_lines",    //显示分组行,因分组的特殊性,分组的数据当成流水数据一样(行高相同),group_lines里的每一条数据占用流水的一整行,里面再细分(指标/text)
+    PROP_GROUP_SUM_KEYS: "SumKey_S",
+    PROP_SUM_KEY: "SumKey",
+    PROP_SUM_FIELDS: "sum_field_s",
+    PROP_TEXTS: "text_s",
+    PROP_TEXT: "text",
+    PROP_PARAMS: "param_s",
+    PROP_FIELD_ID: "FieldID",
+    PROP_PARAM_ID: "ParamID",
+    PROP_PREFIX: "Prefix",
+    PROP_SUFFIX: "Suffix",
+    PROP_FORMAT: "Format",
+
+    PROP_SHOW_ZERO: "ShowZero",
+    PROP_EXTENSION_TYPE: "ExtType",
+
+    PROP_CALCULATION: "CalculationType",
+    PROP_H_CALCULATION: "H_CalculationType",
+    PROP_V_CALCULATION: "V_CalculationType",
+    PROP_FIT_AREA: "isFitArea",
+
+    IDX_LEFT: 0,
+    IDX_TOP: 1,
+    IDX_RIGHT: 2,
+    IDX_BOTTOM: 3,
+
+    BAND_PROP_NAME: "Name",
+    BAND_PROP_MERGE_BAND: "MergeBand",
+    BAND_PROP_STYLE: "style",
+    BAND_PROP_CONTROL: "control",
+    BAND_PROP_HEIGHT: "Height",
+    BAND_PROP_WIDTH: "Width",
+    BAND_PROP_DISPLAY_TYPE: "DisplayType",
+    BAND_PROP_ALIGNMENT: "Alignment",
+    BAND_PROP_MERGE_BORDER: "MergeBorder",
+    BAND_PROP_SUB_BANDS: "band_s",
+
+    MEASUREMENT: {
+        PIXEL:["像素点", "象素点", "PIXEL"],
+        CM: ["厘米", "CM"],
+        INCH: ["英寸","INCH"]
+    },
+
+    PROP_IS_ID: "isID",
+    PROP_ID_SEQ: "IDSeq",
+
+    TAB_FIELD_PROP_SORT: "Sort",
+    TAB_FIELD_PROP_SORT_VAL_NOSORT: "no_sort",
+    TAB_FIELD_PROP_SORT_VAL_ASC: "ascend",
+    TAB_FIELD_PROP_SORT_VAL_DESC: "descend",
+
+    DATA_DISCRETE_DATA: "discrete_data",
+    DATA_MASTER_DATA: "master_data",
+    DATA_DETAIL_DATA: "detail_data",
+    DATA_MASTER_DATA_EX: "master_data_ex",
+    DATA_DETAIL_DATA_EX: "detail_data_ex",
+
+    BLANK_FIELD_INDEX: -10,
+    BLANK_VALUE_INDEX: -100,
+    BLANK_PAGE_VALUE_INDEX: -200,
+
+    PROP_SEG_GRP_IDX: "segGrpRecStartIdx",
+    PROP_PRE_ADD_GRP_REC_INFO: "preAddPageGrpInfo",
+    PROP_INSERTED_GRP_REC: "insertedGrpRecAmt",
+    PROP_GRP_LINES: "group_lines_amt",
+
+    RUN_TYPE_BEFORE_ANALYZING: "before_analyzing",
+    RUN_TYPE_BEFORE_PAGING: "before_paging",
+    RUN_TYPE_BEFORE_OUTPUT: "before_output",
+
+    PAGE_STATUS: ["EveryPage","FirstPage", "LastPage", "SegmentStart", "SegmentEnd", "Group", "CrossRowEnd", "CrossColEnd"],
+
+    CONTROL_PROPS: ["Shrink", "ShowZero", "Horizon", "Vertical", "Wrap", "VerticalForExcel", "ShrinkFirst", "CloseOutput"],
+    CONTROL_PROP_IDX_SHRINK: 0,
+    CONTROL_PROP_IDX_SHOW_ZERO: 1,
+    CONTROL_PROP_IDX_HORIZON: 2,
+    CONTROL_PROP_IDX_VERTICAL: 3,
+    CONTROL_PROP_IDX_WRAP: 4,
+    CONTROL_PROP_IDX_VERTICAL_EXCEL: 5,
+    CONTROL_PROP_IDX_SHRINK_FIRST: 6,
+    CONTROL_PROP_IDX_CLOSE_OUTPUT: 7,
+    BORDER_STYLE_PROPS: ["LineWeight", "DashStyle", "Color"],
+    PROP_LINE_WEIGHT: "LineWeight",
+    PROP_DASH_STYLE: "DashStyle",
+    PROP_COLOR: "Color",
+    FONT_PROPS: ["Name", "FontHeight", "FontColor", "FontBold", "FontItalic", "FontUnderline", "FontStrikeOut", "FontAngle"],
+    FONT_PROP_IDX_NAME: 0,
+    FONT_PROP_IDX_HEIGHT: 1,
+    FONT_PROP_IDX_COLOR: 2,
+    FONT_PROP_IDX_BOLD: 3,
+    FONT_PROP_IDX_ITALIC: 4,
+    FONT_PROP_IDX_UNDERLINE: 5,
+    FONT_PROP_IDX_STRIKEOUT: 6,
+    FONT_PROP_IDX_ANGLE: 7,
+
+    STATUS_NORMAL: 0,
+    STATUS_REPORT_START: 1,
+    STATUS_REPORT_END: 2,
+    STATUS_SEGMENT_START: 3,
+    STATUS_SEGMENT_END: 4,
+    STATUS_GROUP: 5,
+    STATUS_CROSS_ROW_END: 6,
+    STATUS_CROSS_COL_END: 7,
+
+    LAYOUT: ["Top","Bottom", "Left", "Right", "Fulfill"],
+    LAYOUT_TOP: 0,
+    LAYOUT_BOTTOM: 1,
+    LAYOUT_LEFT: 2,
+    LAYOUT_RIGHT: 3,
+    LAYOUT_FULFILL: 4,
+
+    OUTPUT_ALIGN: {
+        H: ["left", "center", "right"],
+        V: ["top", "center", "bottom"]
+    },
+    H_ALIGN_IDX_LEFT : 0,
+    H_ALIGN_IDX_CENTER : 1,
+    H_ALIGN_IDX_RIGHT : 2,
+    V_ALIGN_IDX_TOP : 0,
+    V_ALIGN_IDX_CENTER : 1,
+    V_ALIGN_IDX_BOTTOM : 2,
+
+    CAL_TYPE:["percentage","abstract"],
+    CAL_TYPE_PERCENTAGE: 0,
+    CAL_TYPE_ABSTRACT: 1,
+
+    EVENT_TYPE: ["GRP_ON_CREATE", "FLOW_CONTENT_ON_CREATE"],
+    EVENT_IDX_GRP_ON_CREATE: 0,
+    EVENT_IDX_FLOW_CONTENT_ON_CREATE: 1,
+
+    PAGE_ORIENTATION_V_FIRST: 0,
+    PAGE_ORIENTATION_H_FIRST: 1,
+
+    ORIENTATION_PORTRAIT: "PORTRAIT",
+    ORIENTATION_LANDSCAPE: "LANDSCAPE",
+    ORIENTATION_PORTRAIT_CHN: "纵向",
+    ORIENTATION_LANDSCAPE_CHN: "横向",
+    SIZE_A3: [11.69, 16.54],
+    SIZE_A4: [8.27, 11.69],
+    SIZE_A5: [5.83, 8.27],
+    SIZE_B5: [6.93, 9.84],
+    SIZE_LETTER: [8.5, 11.0],
+    SIZE_LEGAL: [8.5, 14.0],
+    SIZE_16K: [7.75, 10.75],
+    SIZE_EXECUTIVE: [7.25, 10.5],
+
+    OUTPUT_OFFSET: [2,2,1,3],
+    OFFSET_IDX_LEFT: 0,
+    OFFSET_IDX_RIGHT: 1,
+    OFFSET_IDX_TOP: 2,
+    OFFSET_IDX_BOTTOM: 3,
+
+    PROP_PAGE_SEQ: "page_seq",
+    PROP_PAGE_MERGE_BORDER: "page_merge_border",
+    PROP_CELLS: "cells",
+
+    PAGING_OPTION_NORMAL: 'normal',
+    PAGING_OPTION_INFINITY: 'infinity',
+
+    OUTPUT_TYPE_NORMAL: 'normal',
+    OUTPUT_TYPE_SVG: 'svg',
+    OUTPUT_TYPE_PDF: 'pdf',
+    OUTPUT_TYPE_EXCEL: 'excel',
+
+    DISPLAY_VAL_TYPE_NORMAL: 0,
+    DISPLAY_VAL_TYPE_GROUP: 1,
+    DISPLAY_VAL_TYPE_AUTO_HEIGHT: 2,
+
+    TYPE_FOLLOW_MODE: 1,
+
+    PAGE_SELF_DEFINE: "自定义",
+    PAGE_SPECIAL_MERGE_POS: "page_merge_pos",
+
+    PAGES_SIZE_STR: ["A3", "A4", "A5", "B5", "LETTER", "LEGAL", "EXECUTIVE", "16K"],
+    PAGES_SIZE_IDX: [8, 9, 11, 13, 1, 5, 7, 93],
+    PAGES_SIZE: [[11.69, 16.54], [8.27, 11.69], [5.83, 8.27], [6.93, 9.84], [8.5, 11.0], [8.5, 14.0], [7.25, 10.5], [7.25, 10.5]],
+
+    HUNDRED_PERCENT : 100.0,
+
+    VERTICAL_ANGLE: "90",
+    ANTI_VERTICAL_ANGLE: "-90",
+    VERTICAL_ANGLE_INT: 90,
+    ANTI_VERTICAL_ANGLE_INT: -90,
+
+    LAST_DEF: ""
+};

+ 228 - 0
app/public/js/scMathUtil.js

@@ -0,0 +1,228 @@
+/**
+ * Created by jimiz on 2017/3/28.
+ * 经验证:10000次四舍五入,用num.toFixed为15毫秒,用roundTo为47毫秒,速度低一些,但可以接受
+ * 另:经手工验证,用num.toString(2)将十进制浮点数转换为二进制浮点数时,最后一位有错误的情况出现,例子(10.0311)
+ *
+ * zhangyin 2018-02-28
+ * 采用重复一次四舍五入解决浮点精度误差后,10000次roundTo的时间为94毫秒。
+ *
+ * zhangyin 2018-04-28
+ * 因为js中的浮点数本身就有误差,经过运算后误差被放大,用加尾数的方式也不能消除。所以必须采用另一种思路。
+ * 考虑误差会被放大,因此放弃一位有效位数来消除误差,二进制有效位数50位,十进制有效位数15位。
+ * 原重复四舍五入的方法有缺陷,放弃。
+ * 10000次roundTo时间恢复到47毫秒
+ */
+
+let scMathUtil = {
+    innerRoundTo: function(num, digit){
+        let lFactor = Math.pow(10, digit);
+        let fOffSet = 0.5;
+        let sign = '';
+        // 处理符号
+        if (num < 0){
+            sign = '-';
+            num = Math.abs(num);
+        }
+        // 计算
+        let result = Math.floor((num / lFactor) + fOffSet).toString();
+        let iLength = result.length;
+        // 因为数值被转为整数计算,当目标位数在小数点后,且数值小于0,需要补齐前面的位数
+        if (iLength < -digit){
+            result = this.zeroString(-digit) + result;
+        }
+        // 当目标位数在小数点前,需要补齐后面的位数
+        else if ((digit > 0) && (iLength < digit)){
+            result = result + this.zeroString(digit);
+        }
+        iLength = result.length;
+        // 获得小数点前的数字
+        let r1 = result.substring(0, iLength + digit);
+        // 获得小数点后的数字
+        let r2 = result.substring(iLength + digit, iLength);
+        // 拼出完整结果
+        return Number(sign + r1 + '.' + r2);
+    },
+    // 原来直接用num.toString(2),如果小数段最后位数是0,会被舍掉,导致进位计算bug
+    // 改为自己计算二进制,固定为53位。
+    // 经验证速度没有差别
+    // 另:经手工验证,用num.toString(2)将十进制浮点数转换为二进制浮点数时,最后一位有错误的情况出现,例子(10.0311)
+    floatToBin: function(num) {
+        let sign = '';
+        let dNum = num;
+        // 符号位
+        if (num < 0) {
+            sign = '-';
+            dNum = -num;
+        };
+        // 解析整数段
+        let iNum = Math.floor(dNum);
+        let iFactor;
+        let sResult1 = '';
+        // 计算二进制整数段
+        while (iNum > 0){
+            iFactor = iNum % 2;
+            iNum = Math.floor(iNum / 2);
+            sResult1 = iFactor + sResult1;
+        }
+        // 判断是否有整数段
+        let bIntZero = sResult1 === '';
+        if (bIntZero){
+            sResult1 = '0';
+        }
+        // 解析小数段
+        let fNum = dNum - Math.floor(dNum);
+        let sResult2 = '';
+        if (fNum > 0){
+            // 双精度浮点数,尾数总长52位,因为第一位总是1,存储时已经被隐藏,所以有效位数为53位
+            // 由于js未对浮点数做优化,所以在有运算的情况下,误差会被放大,因此放弃一位有效位数来消除误差,二进制有效位数50位,十进制有效位数15位
+            const floatLength = 50;
+
+            let iLength;
+            // js的bug,浮点数直接取小数可能不能获得精确值,只有转成字符串,截取字符串中的小数段
+            let sNum = dNum.toString(10);
+            let iDot = sNum.indexOf('.');
+            sNum = '0' + sNum.substring(iDot, sNum.length);
+            fNum = Number(sNum);
+            // 有整数段,则小数位数为全部位数-整数位数
+            if (!bIntZero){
+                iLength = floatLength - sResult1.length;
+            }
+            else{
+                iLength = floatLength;
+            }
+            // 计算二进制小数段
+            while (iLength > 0){
+                fNum = fNum * 2;
+                iFactor = Math.floor(fNum);
+                fNum = fNum % 1;
+                sResult2 = sResult2 + iFactor;
+                if (iFactor > 0){
+                    bIntZero = false;
+                }
+                if (bIntZero && (iFactor === 0)){
+                    continue;
+                }
+                iLength--;
+            }
+        }
+        return sign + sResult1 + '.' + sResult2;
+    },
+    binToFloat: function(bin) {
+        let result = 0;
+        let iLength = bin.length;
+        let sign = '';
+        if (iLength > 0 && bin[0]==='-'){
+            sign = '-';
+            bin = bin.substring(1, iLength);
+        }
+        iLength = bin.length;
+        let iDot = bin.indexOf('.');
+        if (iDot >= 0) {
+            for (let i = 0; i < iLength; i++) {
+                let num = Number(bin[i]);
+                let idx = iDot - i;
+                if (idx === 0) {
+                    continue
+                };
+                if (idx > 0) {
+                    idx -= 1
+                };
+                let r = Math.pow(2, idx);
+                result += num * r;
+            }
+        }
+        else {
+            result = parseInt(bin, 2);
+        };
+        return sign + result;
+    },
+    zeroString: function(length){
+        let result = '';
+        for (let i = 0; i < length; i++){
+            result = result + '0';
+        };
+        return result;
+    },    
+    incMantissa: function(bin){
+        let result = bin;
+        let iDot = bin.indexOf('.');
+        if (iDot < 0){return result};
+        let iLength = bin.length;
+        iLength = bin.length;
+        for (let i = iLength - 1; i > iDot; i--){
+            let num = Number(bin[i]);
+            if (num === 0){
+                num = 1;
+                let bin1 = bin.substring(0, i);
+                let bin2 = this.zeroString(iLength - (i + 1));//bin.substring(i + 1, iLength);
+                result = bin1 + num.toString() + bin2;
+                break;
+            }
+
+        };
+        return result;
+    },
+
+    roundTo: function(num, digit){
+        let me = this;
+        return me.innerRoundTo(me.binToFloat(me.incMantissa(me.floatToBin(num))), digit);
+    },
+    isNumber : function (obj) {
+        return obj === +obj;
+    },
+    roundForObj:function(obj,decimal){
+        let me = this;
+        let value;
+        if(me.isNumber(obj)){
+            value = me.roundTo(obj,-decimal)
+        }else {
+            value = me.roundTo(Number(obj),-decimal);
+        }
+        return value
+    },
+    roundToString:function(obj,decimal){
+        let me = this;
+        let value;
+        if(me.isNumber(obj)){
+            value = me.roundTo(obj,-decimal)
+        }else {
+            value = me.roundTo(Number(obj),-decimal);
+        }
+        return value.toFixed(decimal);
+    },
+    isNumOrFormula:function (text) {
+        let value = Number(text);
+        if(value ==0 ) return value;
+        if (!value) {
+            try {
+                let exp = new Expression('');
+                exp.Expression(text);
+                value = Number(exp.Evaluate());
+            } catch (error) {
+                value = null;
+            }
+        }
+        if(text==null||text==""){
+            value = null;
+        }
+        return value;
+    },
+    isDef:function (v) {
+        return v !== undefined && v !== null;
+    },
+    //获取ID引用
+    getFIDArr: function (exp) {
+        let fidRex = /@[\d,a-z,A-Z,-]{36}/g;
+        let fidArr = exp.match(fidRex);
+        return this.isDef(fidArr) ? fidArr : [];
+    }
+};
+
+Number.prototype.toDecimal = function (ADigit) {
+    //return parseFloat(this.toFixed(ADigit));
+    digit = ((ADigit!=null||ADigit!=undefined) && typeof(ADigit) === 'number' && Number.isInteger(ADigit) && ADigit >= 0) ? -ADigit : -2;
+    // var s = scMathUtil.roundTo(this, digit);
+    // console.log('Number: ' + this + '   Digit: ' + digit + '    Result: ' + s);
+    // return parseFloat(s);
+    return scMathUtil.roundTo(this, digit);
+};

+ 36 - 0
app/public/js/storageUtil.js

@@ -0,0 +1,36 @@
+/**
+ * Created by Tony on 2017/5/15.
+ */
+storageUtil = {
+    browserFlag: {isIE: false, isFF: false, isChrome: false, isOpera: false, other: false},
+    iniStorage: function() {
+        var me = this;
+        me.browserFlag.isIE = (!!window.ActiveXObject || "ActiveXObject" in window);
+        if (!me.browserFlag.isIE) {
+            if (userAgent.indexOf("Firefox") > -1) {
+                me.browserFlag.isFF = true;
+            } else if (userAgent.indexOf("Chrome") > -1){
+                me.browserFlag.isChrome = true;
+            } else if (userAgent.indexOf("Opera") > -1) {
+                me.browserFlag.isOpera = true;
+            } else {
+                me.browserFlag.other = true;
+            }
+        }
+    },
+    getSessionCache: function(groupKey, itemKey) {
+        return sessionStorage[groupKey + "_" + itemKey];
+    },
+    setSessionCache: function(groupKey, itemKey, value) {
+        sessionStorage[groupKey + "_" + itemKey] = value;
+    },
+    getLocalCache: function(groupKey, itemKey) {
+        return localStorage[groupKey + "_" + itemKey];
+    },
+    setLocalCache: function(groupKey, itemKey, value) {
+        localStorage[groupKey + "_" + itemKey] = value;
+    },
+    clearSessionCache: function(groupKey, itemKey) {
+        delete sessionStorage[groupKey + "_" + itemKey];
+    }
+}

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 284 - 0
app/public/js/stringUtil.js


+ 31 - 0
app/public/js/string_util_light.js

@@ -0,0 +1,31 @@
+/**
+ * Created by Tony on 2017/7/5.
+ * 字符串工具类简化版本
+ */
+
+let stringUtil = {
+    isEmptyString: function(str) {
+        let rst = false;
+        if (str === null || str === undefined) {
+            rst = true;
+        } else if (typeof str) {
+            let reg = /^\s*$/;
+            rst = reg.test(str);
+        }
+        return rst;
+    },
+    replaceAll: function (targetStr, FindText, RepText) {
+        let regExp = new RegExp(FindText, "gm");
+        return targetStr.replace(regExp, RepText);
+    },
+    convertStrToBoolean: function(str) {
+        let rst = false, me = this;
+        if (!me.isEmptyString(str)) {
+            let upperStr = str.toUpperCase();
+            if (upperStr === 'T' || upperStr === 'Y' || upperStr === 'YES' || upperStr === 'TRUE') {
+                rst = true;
+            }
+        }
+        return rst;
+    }
+};

+ 173 - 0
app/public/js/treeDataHelper.js

@@ -0,0 +1,173 @@
+/**
+ * Created by Tony on 2017/1/23.
+ */
+const NODE_ID = "ID", P_ID = "ParentID", NEXT_ID = "NextSiblingID", ADHOC_PRE_ID="Previous_ID", CHILDREN_NODE = "items", SUB_ID = "sub_ids",
+      EMPTY_ID_VAL = -1, TREE_LEVEL = 'treeLevel', TOP_BILL_ID = "topBillID";
+
+let tree_Data_Helper = {
+    buildTreeNodeDirectly: function(data, addLevel) {
+        let topArr = [], rst = [], tmpNodes = {}, prefix = "id_";
+        let private_getStartNode = function (idArr) {
+            let tmpNodeRst = null;
+            for (let i = 0; i < idArr.length; i++) {
+                if (parseInt(tmpNodes[prefix + idArr[i]][ADHOC_PRE_ID]) === EMPTY_ID_VAL) {
+                    tmpNodeRst = tmpNodes[prefix + idArr[i]];
+                    break;
+                }
+            }
+            return tmpNodeRst;
+        };
+        let private_buildNodeData = function(parentItem, idArr, treeLevel, tbID) {
+            let iter = [], nextNode = private_getStartNode(idArr), pushedIds = [];
+            while (nextNode !== null && nextNode !== undefined ) {
+                if (parentItem) {
+                    parentItem[CHILDREN_NODE].push(nextNode);
+                } else {
+                    rst.push(nextNode);
+                }
+                iter.push(nextNode);
+                pushedIds.push(nextNode[NODE_ID]);
+                nextNode[TOP_BILL_ID] = tbID;
+                if (parentItem === null) {
+                    nextNode[TOP_BILL_ID] = nextNode[NODE_ID];
+                    if (nextNode.flags && nextNode.flags.length > 0) {
+                        for (let flag of nextNode.flags) {
+                            if (flag.fieldName === "fixed") {
+                                nextNode[TOP_BILL_ID] = flag.flag;
+                                break;
+                            }
+                        }
+                    }
+                }
+                if (addLevel) nextNode[TREE_LEVEL] = treeLevel;
+                nextNode = tmpNodes[prefix + nextNode[NEXT_ID]];
+                if (nextNode === null || nextNode === undefined) {
+                    //备注: 考虑到实际数据的健壮性,有些节点会掉链子,需要用 parentItem[SUB_ID] 比对已经加上的节点,如发现加上的节点数量不够,那就得在这里补充上去
+                    if (parentItem) {
+                        if (parentItem[SUB_ID].length > iter.length) {
+                            for (let subId of parentItem[SUB_ID]) {
+                                if (pushedIds.indexOf(subId) < 0) {
+                                    let restNode = tmpNodes[prefix + subId];
+                                    if (addLevel) restNode[TREE_LEVEL] = treeLevel;
+                                    restNode[TOP_BILL_ID] = tbID;
+                                    parentItem[CHILDREN_NODE].push(restNode);
+                                    iter.push(restNode);
+                                }
+                            }
+                        }
+                    } else {
+                        if (idArr.length > iter.length) {
+                            for (let topId of idArr) {
+                                if (pushedIds.indexOf(topId) < 0) {
+                                    let restNode = tmpNodes[prefix + topId];
+                                    if (addLevel) restNode[TREE_LEVEL] = treeLevel;
+                                    restNode[TOP_BILL_ID] = restNode[NODE_ID];
+                                    if (restNode.flags && restNode.flags.length > 0) {
+                                        for (let flag of restNode.flags) {
+                                            if (flag.fieldName === "fixed") {
+                                                restNode[TOP_BILL_ID] = flag.flag;
+                                                break;
+                                            }
+                                        }
+                                    }
+                                    rst.push(restNode);
+                                    iter.push(restNode);
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+            pushedIds = [];
+            for (let i = 0; i < iter.length; i++) {
+                let rtbID = tbID;
+                if (parentItem === null) {
+                    rtbID = iter[i][TOP_BILL_ID];
+                }
+                private_buildNodeData(iter[i], iter[i][SUB_ID], (treeLevel + 1), rtbID);
+            }
+        };
+        //1. 给每个节点设置key, 顺便找Top Node
+        for (let i = 0; i < data.length; i++) {
+            tmpNodes[prefix + data[i][NODE_ID]] = data[i];
+            data[i][ADHOC_PRE_ID] = EMPTY_ID_VAL;
+            data[i][SUB_ID] = [];
+            data[i][CHILDREN_NODE] = [];
+            if (parseInt(data[i][P_ID]) === EMPTY_ID_VAL) {
+                topArr.push(data[i][NODE_ID]);
+            }
+        }
+        //2. 通过key,设置好兄弟/父子关系
+        for (let i = 0; i < data.length; i++) {
+            if (parseInt(data[i][NEXT_ID]) !== EMPTY_ID_VAL) {
+                if (tmpNodes[prefix + data[i][NEXT_ID]] !== undefined){
+                    if (tmpNodes[prefix + data[i][NEXT_ID]][P_ID] === data[i][P_ID]) {
+                        tmpNodes[prefix + data[i][NEXT_ID]][ADHOC_PRE_ID] = data[i][NODE_ID];
+                    } else {
+                        tmpNodes[prefix + data[i][NEXT_ID]][ADHOC_PRE_ID] = EMPTY_ID_VAL;
+                        data[i][NEXT_ID] = EMPTY_ID_VAL;
+                    }
+                }
+            }
+            if (parseInt(data[i][P_ID]) !== EMPTY_ID_VAL) {
+                tmpNodes[prefix + data[i][P_ID]][SUB_ID].push(data[i][NODE_ID]);
+            }
+        }
+        //3. 开build
+        private_buildNodeData(null, topArr, 0, -1);
+        //try to release and return
+        tmpNodes = null;
+        topArr.length = 0;
+        return rst;
+    },
+
+    getFlatArray: function (srcArr, destArr) {
+        let private_put = function (parentItem) {
+            destArr.push(parentItem);
+            if (parentItem.items) {
+                for (let subItem of parentItem.items) {
+                    private_put(subItem);
+                }
+            }
+        }
+        for (let node of srcArr) {
+            private_put(node);
+        }
+        for (let item of destArr) {
+            delete item[CHILDREN_NODE];
+            delete item[SUB_ID];
+            delete item[ADHOC_PRE_ID];
+        }
+    }
+
+    // reSettleRelatedID: function(treeData) {
+    //     if (treeData && treeData.length > 0) {
+    //         for (let i = 0; i < treeData[CHILDREN_NODE].length; i++) {
+    //             let nI = null;
+    //             if (i < treeData[CHILDREN_NODE].length - 1) {
+    //                 nI = treeData[CHILDREN_NODE][i+1];
+    //             }
+    //             settleNodeRelatedID(null, nI, treeData[CHILDREN_NODE][i]);
+    //         }
+    //     }
+    // },
+    // settleNodeRelatedID : function(parentItem, nextItem, node) {
+    //     if (parentItem) {
+    //         node[P_ID] = parentItem[NODE_ID];
+    //     } else {
+    //         node[P_ID] = EMPTY_ID_VAL;
+    //     }
+    //     if (nextItem) {
+    //         node[NEXT_ID] = nextItem[NODE_ID];
+    //     } else {
+    //         node[NEXT_ID] = EMPTY_ID_VAL;
+    //     }
+    //     for (let i = 0; i < node[CHILDREN_NODE].length; i++) {
+    //         let nI = null;
+    //         if (i < node[CHILDREN_NODE].length - 1) {
+    //             nI = node[CHILDREN_NODE][i+1];
+    //         }
+    //         private_settleNodeRelatedID(node, nI, node[CHILDREN_NODE][i]);
+    //     }
+    // }
+};

+ 30 - 0
app/public/js/ztree_common.js

@@ -0,0 +1,30 @@
+/**
+ * Created by Tony on 2017/5/12.
+ */
+zTreeHelper = {
+    createTree: function(sourceData, setting, containerId, caller){
+        let me = this, treeArr = tree_Data_Helper.buildTreeNodeDirectly(sourceData);
+        me.createTreeDirectly(treeArr, setting, containerId, caller);
+    },
+    createTreeDirectly: function (treeArr, setting, containerId, caller) {
+        let private_build_parentNodeIds = function(pNodeId, nodesArr){
+            let rst = [];
+            for (let i = 0; i < nodesArr.length; i++) {
+                if (nodesArr[i].items.length > 0) {
+                    rst = rst.concat(private_build_parentNodeIds(nodesArr[i].ID, nodesArr[i].items));
+                } else {
+                    rst.push(nodesArr[i].ID);
+                }
+            }
+            if (pNodeId && rst.length > 0) {
+                caller.parentNodeIds["_pNodeId_" + pNodeId] = rst;
+            }
+            return rst;
+        };
+        if (caller.parentNodeIds) {
+            private_build_parentNodeIds(null, treeArr);
+        }
+        caller.treeObj = $.fn.zTree.init($("#" + containerId), setting, treeArr);
+        caller.treeObj.expandAll(true);
+    }
+};

+ 416 - 0
app/public/report/js/jpc_output.js

@@ -0,0 +1,416 @@
+/**
+ * Created by Tony on 2017/11/2.
+ */
+let JpcCanvasOutput = {
+    offsetX: 10,
+    offsetY: 10,
+    scaleFactor: 1,
+    cleanCanvas: function (canvas) {
+        let ctx = canvas.getContext("2d");
+        ctx.save();
+        ctx.fillStyle="white";
+        ctx.clearRect(0,0, canvas.width, canvas.height);
+        ctx.restore();
+    },
+    resetFonts: function(rptFonts) {
+        let me = JpcCanvasOutput;
+        if (me.scaleFactor !== 1) {
+            for (let key in rptFonts) {
+                rptFonts[key]["FontHeight"] = me.scaleFactor * rptFonts[key]["FontHeight"];
+            }
+        }
+    },
+    drawToCanvas : function(pageObj, canvas, pageIdx) {
+        let me = this;
+        let ctx = canvas.getContext("2d");
+        let floatReg = /^(-?\d+)(\.\d+)?$/;
+
+        function private_setupAreaH(area, type, fontAngle, dftFontHeight, outputPoint) {
+            let lType = type;
+            if (type !== "left" && type !== "right" && type !== "center") lType = "left";
+            switch (lType) {
+                case "left":
+                    if (fontAngle === JV.VERTICAL_ANGLE_INT) {
+                        outputPoint[1] = 1 * area[JV.IDX_TOP] + JV.OUTPUT_OFFSET[JV.OFFSET_IDX_LEFT];
+                    } else if (fontAngle === JV.ANTI_VERTICAL_ANGLE_INT) {
+                        outputPoint[1] = 1 * area[JV.IDX_BOTTOM] - JV.OUTPUT_OFFSET[JV.OFFSET_IDX_LEFT];
+                    } else outputPoint[0] = 1 * area[JV.IDX_LEFT] + JV.OUTPUT_OFFSET[JV.OFFSET_IDX_LEFT];
+                    ctx.textAlign="start";
+                    break;
+                case "right":
+                    if (fontAngle === JV.VERTICAL_ANGLE_INT) {
+                        outputPoint[1] = 1 * area[JV.IDX_BOTTOM] - JV.OUTPUT_OFFSET[JV.OFFSET_IDX_RIGHT];
+                    } else if (fontAngle === JV.ANTI_VERTICAL_ANGLE_INT) {
+                        outputPoint[1] = 1 * area[JV.IDX_TOP] + JV.OUTPUT_OFFSET[JV.OFFSET_IDX_RIGHT];
+                    } else outputPoint[0] = 1 * area[JV.IDX_RIGHT] - JV.OUTPUT_OFFSET[JV.OFFSET_IDX_RIGHT];
+                    ctx.textAlign="end";
+                    break;
+                case "center":
+                    if (fontAngle === JV.VERTICAL_ANGLE_INT || fontAngle === JV.ANTI_VERTICAL_ANGLE_INT) {
+                        outputPoint[1] = (1 * area[JV.IDX_TOP] + 1 * area[JV.IDX_BOTTOM]) / 2;
+                    } else outputPoint[0] = (1 * area[JV.IDX_LEFT] + 1 * area[JV.IDX_RIGHT]) / 2;
+                    ctx.textAlign="center";
+                    break;
+            }
+        }
+        function private_setupAreaV(area, type, fontAngle, fontName, dftFontHeight, outputPoint) {
+            let lType = type;
+            if (type !== "top" && type !== "bottom" && type !== "center") lType = "top";
+            switch (lType) {
+                case "top":
+                    if (fontAngle === JV.VERTICAL_ANGLE_INT) {
+                        outputPoint[0] = 1 * area[JV.IDX_RIGHT] - dftFontHeight - JV.OUTPUT_OFFSET[JV.OFFSET_IDX_TOP];
+                    } else if (fontAngle === JV.ANTI_VERTICAL_ANGLE_INT) {
+                        outputPoint[0] = 1 * area[JV.IDX_LEFT] + dftFontHeight + JV.OUTPUT_OFFSET[JV.OFFSET_IDX_TOP];
+                    } else outputPoint[1] = 1 * area[JV.IDX_TOP] + dftFontHeight + JV.OUTPUT_OFFSET[JV.OFFSET_IDX_TOP];
+                    break;
+                case "bottom":
+                    if (fontAngle === JV.VERTICAL_ANGLE_INT) {
+                        outputPoint[0] = 1 * area[JV.IDX_LEFT] + JV.OUTPUT_OFFSET[JV.OFFSET_IDX_BOTTOM];
+                    } else if (fontAngle === JV.ANTI_VERTICAL_ANGLE_INT) {
+                        outputPoint[0] = 1 * area[JV.IDX_RIGHT] - JV.OUTPUT_OFFSET[JV.OFFSET_IDX_BOTTOM];
+                    } else {
+                        outputPoint[1] = 1 * area[JV.IDX_BOTTOM] - JV.OUTPUT_OFFSET[JV.OFFSET_IDX_BOTTOM];
+                        if (fontName === "宋体") outputPoint[1] = outputPoint[1] - 1;
+                    }
+                    break;
+                case "center":
+                    if (fontAngle === JV.VERTICAL_ANGLE_INT) {
+                        outputPoint[0] = (1 * area[JV.IDX_LEFT] + 1 * area[JV.IDX_RIGHT] - dftFontHeight) / 2;
+                    } else if (fontAngle === JV.ANTI_VERTICAL_ANGLE_INT) {
+                        outputPoint[0] = (1 * area[JV.IDX_LEFT] + 1 * area[JV.IDX_RIGHT] + dftFontHeight) / 2;
+                    } else {
+                        outputPoint[1] = (1 * area[JV.IDX_TOP] + 1 * area[JV.IDX_BOTTOM] + dftFontHeight) / 2;
+                        if (fontName === "宋体") outputPoint[1] = outputPoint[1] - 1;
+                    }
+                    break;
+            }
+        }
+        function private_splitString(strVal, areaWidth, ctx) {
+            let rst = [];
+            if (strVal) {
+                let preSIdx = 0, txtWidth = 0;
+                let currentW = 0;
+                let chnW = ctx.measureText('一').width, otherW = ctx.measureText('_').width;
+                for (let sIdx = 0; sIdx < strVal.length; sIdx++) {
+                    currentW = (strVal.charCodeAt(sIdx) > 127)?chnW:otherW;
+                    txtWidth += currentW;
+                    if (txtWidth > areaWidth) {
+                        if (preSIdx < sIdx) {
+                            rst.push(strVal.substr(preSIdx, sIdx - preSIdx));
+                            preSIdx = sIdx;
+                        } else {
+                            rst.push(strVal.substr(preSIdx, 1));
+                            preSIdx = sIdx + 1;
+                        }
+                        txtWidth = currentW;
+                    }
+                    if (sIdx === strVal.length - 1) {
+                        rst.push(strVal.substr(preSIdx, strVal.length - preSIdx));
+                    }
+                }
+            }
+            if (rst.length === 0) rst.push(''); //什么都没有,也得整个空串
+            return rst;
+        }
+        function private_drawText(val, area, font, control) {
+            let dftFontHeight = 12;
+            let output = [];
+            if (font) {
+                dftFontHeight = parseFloat(font[JV.FONT_PROPS[1]]);
+                let dftOthers = "";
+                let dftFontBold = font[JV.FONT_PROPS[3]];
+                if (dftFontBold && dftFontBold === 'T') {
+                    dftOthers = "bold " + dftOthers ;
+                }
+                let dftFontItalic = font[JV.FONT_PROPS[4]];
+                if (dftFontItalic && dftFontItalic === 'T') {
+                    dftOthers = dftOthers + "italic ";
+                }
+                ctx.font = dftOthers + dftFontHeight + "px " + font[JV.PROP_NAME];
+            }
+            let inner_setupControl = function (inArea, inFontHeight, inOutput) {
+                if (control) {
+                    private_setupAreaH(inArea, control.Horizon, parseInt(font.FontAngle), inFontHeight, inOutput);
+                    private_setupAreaV(inArea, control.Vertical, parseInt(font.FontAngle), font.Name, inFontHeight, inOutput);
+                } else {
+                    private_setupAreaH(inArea, "left", parseInt(font.FontAngle), inFontHeight, inOutput);
+                    private_setupAreaV(inArea, "bottom", parseInt(font.FontAngle), font.Name, inFontHeight, inOutput);
+                }
+            };
+            inner_setupControl(area, dftFontHeight, output);
+            let validAreaTxtWidth = area[JV.IDX_RIGHT] - JV.OUTPUT_OFFSET[JV.OFFSET_IDX_RIGHT] - area[JV.IDX_LEFT] - JV.OUTPUT_OFFSET[JV.OFFSET_IDX_LEFT];
+            //let validAreaTxtHeight = area[JV.IDX_BOTTOM] - JV.OUTPUT_OFFSET[JV.OFFSET_IDX_BOTTOM] - area[JV.IDX_TOP] - JV.OUTPUT_OFFSET[JV.OFFSET_IDX_TOP];
+            let validTxtLines = Math.floor((area[JV.IDX_BOTTOM] - area[JV.IDX_TOP]) / (dftFontHeight + JV.OUTPUT_OFFSET[JV.OFFSET_IDX_BOTTOM] + JV.OUTPUT_OFFSET[JV.OFFSET_IDX_TOP] + 4));
+            if ( parseInt(font.FontAngle) !== 0) {
+                validAreaTxtWidth = area[JV.IDX_BOTTOM] - JV.OUTPUT_OFFSET[JV.OFFSET_IDX_BOTTOM] - area[JV.IDX_TOP] - JV.OUTPUT_OFFSET[JV.OFFSET_IDX_TOP];
+                validTxtLines = Math.floor((area[JV.IDX_RIGHT] - area[JV.IDX_LEFT]) / (dftFontHeight + JV.OUTPUT_OFFSET[JV.OFFSET_IDX_RIGHT] + JV.OUTPUT_OFFSET[JV.OFFSET_IDX_LEFT] + 4));
+            }
+
+            function private_drawUnderline(underLineVal) {
+                //A. 暂不支持角度; B. 坐标已经translate
+                //1. 计算下划线的相关坐标
+                let width = ctx.measureText(underLineVal).width;
+                if (width > area[JV.IDX_RIGHT] - area[JV.IDX_LEFT]) {
+                    width = area[JV.IDX_RIGHT] - area[JV.IDX_LEFT];
+                }
+                let height = dftFontHeight;
+                // let startX = area[JV.IDX_LEFT], startY = area[JV.IDX_TOP], endX = area[JV.IDX_RIGHT], endY = area[JV.IDX_BOTTOM];
+                let startX = 0, startY = 0, endX = width, endY = startY;
+                if (control.Horizon === "left") {
+                    // 无变化;
+                } else if (control.Horizon === "right") {
+                    startX = Math.round(startX - width);
+                } else {
+                    startX = Math.round(startX - width / 2);
+                }
+                endX = Math.round(startX + width);
+
+                if (control.Vertical === "top") {
+                    startY += JV.OUTPUT_OFFSET[JV.IDX_TOP] + JV.OUTPUT_OFFSET[JV.IDX_BOTTOM];
+                } else if (control.Vertical === "bottom") {
+                    // startY = Math.round(startY);
+                    startY += JV.OUTPUT_OFFSET[JV.IDX_TOP] + JV.OUTPUT_OFFSET[JV.IDX_BOTTOM];
+                } else {
+                    startY = Math.round(height / 2) - JV.OUTPUT_OFFSET[JV.IDX_TOP] - JV.OUTPUT_OFFSET[JV.IDX_BOTTOM];
+                    // startY += JV.OUTPUT_OFFSET[JV.IDX_TOP] + JV.OUTPUT_OFFSET[JV.IDX_BOTTOM];
+                }
+                endY = Math.round(startY);
+                //2. 画线
+                // ctx.save();
+                if ( output[1] !== Math.round(output[1])) {
+                    ctx.translate(0,0.5);
+                }
+                ctx.beginPath();
+                ctx.moveTo(startX, startY);
+                ctx.lineWidth = 1;
+                ctx.strokeStyle = "BLACK";
+                ctx.lineTo(endX, endY);
+                ctx.stroke();
+                // ctx.restore();
+            }
+
+            ctx.save();
+            ctx.translate(output[0], output[1]);
+            // if (font[JV.FONT_PROPS[5]] === 'T' && parseInt(font.FontAngle) === 0) {
+            //     private_drawUnderline(val);
+            // }
+            if (font.FontAngle === JV.VERTICAL_ANGLE) {
+                ctx.rotate(Math.PI/2);
+            } else if (font.FontAngle === JV.ANTI_VERTICAL_ANGLE) {
+                ctx.rotate(-Math.PI/2);
+            }
+            if (validAreaTxtWidth >= ctx.measureText(val).width ||
+                (control && control.Shrink !== 'T' && validTxtLines < private_splitString(val, validAreaTxtWidth, ctx)) ) {
+                if (font[JV.FONT_PROPS[5]] === 'T' && parseInt(font.FontAngle) === 0) {
+                    private_drawUnderline(val);
+                }
+                ctx.fillText(val, 0, 0);
+            } else {
+                while (true) {
+                    let lines = Math.floor((area[JV.IDX_BOTTOM] - area[JV.IDX_TOP]) / (dftFontHeight + JV.OUTPUT_OFFSET[JV.OFFSET_IDX_BOTTOM] + JV.OUTPUT_OFFSET[JV.OFFSET_IDX_TOP] + 4));
+                    lines = (lines === 0 || (control.Shrink === 'T' && control.ShrinkFirst === 'T'))?1:lines;
+                    let actLines = private_splitString(val, validAreaTxtWidth, ctx);
+                    if (actLines.length > lines && dftFontHeight >= 6) {
+                        dftFontHeight--;
+                        ctx.font = "" + dftFontHeight + "px " + font[JV.PROP_NAME];
+                    } else {
+                        let aH = dftFontHeight + JV.OUTPUT_OFFSET[JV.OFFSET_IDX_BOTTOM] + JV.OUTPUT_OFFSET[JV.OFFSET_IDX_TOP] + 4;
+                        if ((aH * actLines.length) < (area[JV.IDX_BOTTOM] - area[JV.IDX_TOP]) && (control && control.Vertical !== 'top')) {
+                            if (control.Vertical === 'bottom') {
+                                area[JV.IDX_TOP] = area[JV.IDX_BOTTOM] - (aH * actLines.length);
+                            } else {
+                                area[JV.IDX_TOP] = (area[JV.IDX_TOP] + area[JV.IDX_BOTTOM]) / 2 - (aH * actLines.length) / 2
+                                area[JV.IDX_BOTTOM] = area[JV.IDX_TOP] + (aH * actLines.length);
+                            }
+                        }
+                        let newArea = [], baseTop = area[JV.IDX_TOP];
+                        for (let ai = 0; ai < area.length; ai++) {
+                            newArea[ai] = area[ai];
+                        }
+                        for (let lIdx = 0; lIdx < actLines.length; lIdx++) {
+                            newArea[JV.IDX_TOP] = Math.round(aH * lIdx + baseTop);
+                            newArea[JV.IDX_BOTTOM] = Math.round(aH * (lIdx + 1) + baseTop);
+                            ctx.translate(-output[0], -output[1]);
+                            inner_setupControl(newArea, dftFontHeight, output);
+                            ctx.translate(output[0], output[1]);
+                            if (font[JV.FONT_PROPS[5]] === 'T' && parseInt(font.FontAngle) === 0) {
+                                private_drawUnderline(actLines[lIdx]);
+                            }
+                            ctx.fillText(actLines[lIdx], 0, 0);
+                        }
+                        break;
+                    }
+                }
+            }
+            ctx.restore();
+        }
+        function private_drawCellText(cell, fonts, controls) {
+            if (cell[JV.PROP_VALUE] !== null && cell[JV.PROP_VALUE] !== undefined) {
+                let values = ("" + cell[JV.PROP_VALUE]).split('|');
+                let font = null;
+                if (typeof cell[JV.PROP_FONT] === "string") {
+                    font = fonts[cell[JV.PROP_FONT]];
+                } else {
+                    font = cell[JV.PROP_FONT];
+                }
+                //let control = controls[cell[JV.PROP_CONTROL]];
+                let control = null;
+                if (typeof cell[JV.PROP_CONTROL] === "string") {
+                    control = controls[cell[JV.PROP_CONTROL]];
+                } else {
+                    control = cell[JV.PROP_CONTROL];
+                }
+                if (control.ShowZero === "F") {
+                    if (typeof cell[JV.PROP_VALUE] === "string") {
+                        if (floatReg.test(cell[JV.PROP_VALUE]) && parseFloat(cell[JV.PROP_VALUE]) === 0.0) {
+                            values = [""];
+                        }
+                    } else if (parseFloat(cell[JV.PROP_VALUE]) === 0.0) {
+                        values = [""];
+                    }
+                }
+                let height = cell[JV.PROP_AREA][JV.PROP_BOTTOM] - cell[JV.PROP_AREA][JV.PROP_TOP];
+                let area = [cell[JV.PROP_AREA][JV.PROP_LEFT] + me.offsetX, cell[JV.PROP_AREA][JV.PROP_TOP] + me.offsetY, cell[JV.PROP_AREA][JV.PROP_RIGHT] + me.offsetX, cell[JV.PROP_AREA][JV.PROP_BOTTOM] + me.offsetY];
+                let ah = height;
+                let restTopH = 0, restBottomH = 0;
+                if (control[JV.CONTROL_PROPS[JV.CONTROL_PROP_IDX_CLOSE_OUTPUT]] === 'T') {
+                    ah = (parseFloat(font[JV.FONT_PROPS[1]]) + JV.OUTPUT_OFFSET[JV.OFFSET_IDX_TOP] + JV.OUTPUT_OFFSET[JV.OFFSET_IDX_BOTTOM]) * values.length;
+                    let restH = height - ah;
+                    if (control[JV.CONTROL_PROPS[JV.CONTROL_PROP_IDX_VERTICAL]] === 'center') {
+                        restTopH = restH / 2;
+                        restBottomH = restH / 2;
+                    } else if (control[JV.CONTROL_PROPS[JV.CONTROL_PROP_IDX_VERTICAL]] === 'bottom') {
+                        restBottomH = restH;
+                    } else {
+                        restTopH = restH;
+                    }
+                }
+                for (let i = 0; i < values.length; i++) {
+                    // area[JV.IDX_TOP] = cell[JV.PROP_AREA][JV.PROP_TOP] + i * (height / values.length) + me.offsetY;
+                    // area[JV.IDX_BOTTOM] = cell[JV.PROP_AREA][JV.PROP_TOP] + (i + 1) * (height / values.length) + me.offsetY;
+                    area[JV.IDX_TOP] = cell[JV.PROP_AREA][JV.PROP_TOP] + i * (ah / values.length) + me.offsetY + restTopH;
+                    area[JV.IDX_BOTTOM] = cell[JV.PROP_AREA][JV.PROP_TOP] + (i + 1) * (ah / values.length) + me.offsetY + restTopH;
+                    if (values[i] === null || values[i] === undefined || values[i] === 'null') {
+                        values[i] = "";
+                    }
+                    private_drawText(values[i], area, font, control);
+                }
+            }
+        }
+        function private_drawLine(cell, ctx, style, styleBorderDest, startP, destP, mergedBand, styles, isNeedMergeBand) {
+            ctx.beginPath();
+            let destStyle = style;
+            if (mergedBand) {
+                if (isNeedMergeBand && parseFloat(mergedBand[styleBorderDest]) === parseFloat(cell[JV.PROP_AREA][styleBorderDest])) {
+                    destStyle = styles[mergedBand[JV.PROP_STYLE][JV.PROP_ID]];
+                }
+            }
+            ctx.moveTo(cell[JV.PROP_AREA][startP[0]] + me.offsetX, cell[JV.PROP_AREA][startP[1]] + me.offsetY);
+            if (destStyle[styleBorderDest] && parseFloat(destStyle[styleBorderDest][JV.PROP_LINE_WEIGHT]) !== 0.0) {
+                ctx.lineWidth = 1.0 * destStyle[styleBorderDest][JV.PROP_LINE_WEIGHT];
+                ctx.strokeStyle = destStyle[styleBorderDest][JV.PROP_COLOR];
+                ctx.lineTo(cell[JV.PROP_AREA][destP[0]] + me.offsetX, cell[JV.PROP_AREA][destP[1]] + me.offsetY);
+            }
+            ctx.stroke();
+        }
+        function private_chkIfInMergedBand(mergedBand, cell) {
+            let rst = false;
+            if (mergedBand && cell) {
+                rst = mergedBand[JV.PROP_TOP] <= cell[JV.PROP_AREA][JV.PROP_TOP] && mergedBand[JV.PROP_BOTTOM] >= cell[JV.PROP_AREA][JV.PROP_BOTTOM] &&
+                    mergedBand[JV.PROP_LEFT] <= cell[JV.PROP_AREA][JV.PROP_LEFT] && mergedBand[JV.PROP_RIGHT] >= cell[JV.PROP_AREA][JV.PROP_RIGHT];
+            }
+            return rst;
+        }
+        function private_drawCell(cell, fonts, styles, controls, mergedBand) {
+            ctx.save();
+            ctx.translate(0.5,0.5);
+            let style = styles[cell[JV.PROP_STYLE]];
+            if (style) {
+                let isNeedMergeBand = private_chkIfInMergedBand(mergedBand, cell);
+                private_drawLine(cell, ctx, style, JV.PROP_TOP, [JV.PROP_LEFT, JV.PROP_TOP],[JV.PROP_RIGHT, JV.PROP_TOP], mergedBand, styles, isNeedMergeBand);
+                private_drawLine(cell, ctx, style, JV.PROP_RIGHT, [JV.PROP_RIGHT, JV.PROP_TOP],[JV.PROP_RIGHT, JV.PROP_BOTTOM], mergedBand, styles, isNeedMergeBand);
+                private_drawLine(cell, ctx, style, JV.PROP_BOTTOM, [JV.PROP_RIGHT, JV.PROP_BOTTOM],[JV.PROP_LEFT, JV.PROP_BOTTOM], mergedBand, styles, isNeedMergeBand);
+                private_drawLine(cell, ctx, style, JV.PROP_LEFT, [JV.PROP_LEFT, JV.PROP_BOTTOM],[JV.PROP_LEFT, JV.PROP_TOP], mergedBand, styles, isNeedMergeBand);
+            }
+            private_drawCellText(cell, fonts, controls);
+            ctx.restore();
+        }
+        function getIniPageMergeBorder(mergedBand) {
+            let rst = {};
+            if (mergedBand) {
+                rst[JV.PROP_LEFT] = mergedBand[JV.PROP_LEFT];
+                rst[JV.PROP_RIGHT] = mergedBand[JV.PROP_RIGHT];
+                rst[JV.PROP_TOP] = mergedBand[JV.PROP_TOP];
+                rst[JV.PROP_BOTTOM] = mergedBand[JV.PROP_BOTTOM];
+                rst[JV.PROP_STYLE] = mergedBand[JV.PROP_STYLE];
+            }
+            return rst;
+        }
+        function private_resetArea(area) {
+            area[JV.PROP_LEFT] = me.scaleFactor * area[JV.PROP_LEFT];
+            area[JV.PROP_RIGHT] = me.scaleFactor * area[JV.PROP_RIGHT];
+            area[JV.PROP_TOP] = me.scaleFactor * area[JV.PROP_TOP];
+            area[JV.PROP_BOTTOM] = me.scaleFactor * area[JV.PROP_BOTTOM];
+        }
+        function private_resetCellFont(cell) {
+            if (typeof cell.font !== "string") {
+                cell.font["FontHeight"] = me.scaleFactor * cell.font["FontHeight"];
+            }
+        }
+
+        if (pageObj && pageObj.items.length > 0 && canvas && pageObj.items.length >= pageIdx) {
+            let page = pageObj.items[pageIdx - 1],
+                fonts = pageObj[JV.NODE_FONT_COLLECTION],
+                styles = pageObj[JV.NODE_STYLE_COLLECTION],
+                controls = pageObj[JV.NODE_CONTROL_COLLECTION],
+                mergedBand = pageObj[JV.BAND_PROP_MERGE_BAND];
+            let newPageMergeBand = getIniPageMergeBorder(mergedBand);
+            if (page[JV.PROP_PAGE_MERGE_BORDER]) {
+                newPageMergeBand[JV.PROP_LEFT] = page[JV.PROP_PAGE_MERGE_BORDER][JV.PROP_LEFT];
+                newPageMergeBand[JV.PROP_RIGHT] = page[JV.PROP_PAGE_MERGE_BORDER][JV.PROP_RIGHT];
+                newPageMergeBand[JV.PROP_TOP] = page[JV.PROP_PAGE_MERGE_BORDER][JV.PROP_TOP];
+                newPageMergeBand[JV.PROP_BOTTOM] = page[JV.PROP_PAGE_MERGE_BORDER][JV.PROP_BOTTOM];
+            }
+            private_resetArea(newPageMergeBand);
+            for (let j = 0; j < page.cells.length; j++) {
+                let cell = page.cells[j];
+                if (me.scaleFactor !== 1) {
+                    private_resetCellFont(cell);
+                    private_resetArea(cell[JV.PROP_AREA]);
+                }
+                private_drawCell(cell, fonts, styles, controls, newPageMergeBand);
+            }
+        }
+    },
+    drawPageBorder: function(rptTpl, canvas, resolution) {
+        let me = this;
+        let size = me.getReportSizeInPixel(rptTpl, resolution);
+
+        let ctx = canvas.getContext("2d");
+        ctx.save();
+        ctx.beginPath();
+        ctx.translate(0.5,0.5);
+        ctx.lineWidth = 1;
+        ctx.moveTo(me.offsetX, me.offsetY);
+        ctx.lineTo(size[0] + me.offsetX, me.offsetY);
+        ctx.lineTo(size[0] + me.offsetX, size[1] + me.offsetY);
+        ctx.lineTo(me.offsetX, size[1] + me.offsetY);
+        ctx.lineTo(me.offsetX, me.offsetY);
+        ctx.stroke();
+        ctx.restore();
+
+        ctx.fillStyle="black";
+        ctx.fillRect(size[0] + me.offsetX,10 + me.offsetY,10,size[1]);
+        ctx.fillRect(10 + me.offsetX,size[1] + me.offsetY,size[0],10);
+    },
+    getReportSizeInPixel: function(rptTpl, resolution) {
+        let rst = [8.27, 11.69];
+        if (rptTpl && rptTpl[JV.NODE_PAGE_INFO] && rptTpl[JV.NODE_PAGE_INFO][JV.NODE_PAGE_SIZE]) {
+            rst = rptTpl[JV.NODE_PAGE_INFO][JV.NODE_PAGE_SIZE].slice(0);
+        }
+        rst[0] = Math.round(resolution[0] * rst[0]);
+        rst[1] = Math.round(resolution[0] * rst[1]);
+        return rst;
+    }
+};

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

@@ -0,0 +1,69 @@
+/**
+ * Created by Tony on 2017/1/4.
+ */
+
+let JV = {
+    NODE_MAIN_INFO: "主信息",
+    NODE_PAGE_INFO: "打印页面_信息",
+    NODE_MAIN_INFO_RPT_NAME: "报表名称",
+    NODE_PAGE_SIZE: "纸张宽高",
+    NODE_MARGINS: "页边距",
+
+    NODE_FONT_COLLECTION: "font_collection",
+    NODE_STYLE_COLLECTION: "style_collection",
+    NODE_CONTROL_COLLECTION: "control_collection",
+
+    BAND_PROP_MERGE_BAND: "MergeBand",
+    PROP_PAGE_MERGE_BORDER: "page_merge_border",
+
+    PROP_NAME: "Name",
+    PROP_VALUE: "Value",
+    PROP_FONT: "font",
+    PROP_CONTROL: "control",
+    PROP_STYLE: "style",
+    PROP_AREA: "area",
+    PROP_ID: "ID",
+    PROP_LEFT: "Left",
+    PROP_RIGHT: "Right",
+    PROP_TOP: "Top",
+    PROP_BOTTOM: "Bottom",
+
+    IDX_LEFT: 0,
+    IDX_TOP: 1,
+    IDX_RIGHT: 2,
+    IDX_BOTTOM: 3,
+
+    CONTROL_PROPS: ["Shrink", "ShowZero", "Horizon", "Vertical", "Wrap", "VerticalForExcel", "ShrinkFirst", "CloseOutput"],
+    CONTROL_PROP_IDX_SHRINK: 0,
+    CONTROL_PROP_IDX_SHOW_ZERO: 1,
+    CONTROL_PROP_IDX_HORIZON: 2,
+    CONTROL_PROP_IDX_VERTICAL: 3,
+    CONTROL_PROP_IDX_WRAP: 4,
+    CONTROL_PROP_IDX_VERTICAL_EXCEL: 5,
+    CONTROL_PROP_IDX_SHRINK_FIRST: 6,
+    CONTROL_PROP_IDX_CLOSE_OUTPUT: 7,
+    BORDER_STYLE_PROPS: ["LineWeight", "DashStyle", "Color"],
+    PROP_LINE_WEIGHT: "LineWeight",
+    PROP_DASH_STYLE: "DashStyle",
+    PROP_COLOR: "Color",
+    FONT_PROPS: ["Name", "FontHeight", "FontColor", "FontBold", "FontItalic", "FontUnderline", "FontStrikeOut", "FontAngle"],
+    FONT_PROP_IDX_NAME: 0,
+    FONT_PROP_IDX_HEIGHT: 1,
+    FONT_PROP_IDX_COLOR: 2,
+    FONT_PROP_IDX_BOLD: 3,
+    FONT_PROP_IDX_ITALIC: 4,
+    FONT_PROP_IDX_UNDERLINE: 5,
+    FONT_PROP_IDX_STRIKEOUT: 6,
+    FONT_PROP_IDX_ANGLE: 7,
+
+    OUTPUT_OFFSET: [2,2,1,3],
+    OFFSET_IDX_LEFT: 0,
+    OFFSET_IDX_RIGHT: 1,
+    OFFSET_IDX_TOP: 2,
+    OFFSET_IDX_BOTTOM: 3,
+
+    VERTICAL_ANGLE_INT: 90,
+    ANTI_VERTICAL_ANGLE_INT: -90,
+    VERTICAL_ANGLE: "90",
+    ANTI_VERTICAL_ANGLE: "-90"
+};

+ 57 - 0
app/public/report/js/rpt_cfg_const.js

@@ -0,0 +1,57 @@
+/**
+ * Created by Tony on 2017/6/26.
+ */
+
+const   TPL_TYPE_NODE = 1,
+        TPL_TYPE_TEMPLATE = 2;
+let rpt_tpl_setting = {
+    view: {
+        selectedMulti: false
+    },
+    check: {
+        enable: true
+    },
+    data: {
+        keep: {
+            parent:true,
+            leaf:true
+        },
+        key: {
+            children: "items"
+        },
+        simpleData: {
+            enable: true,
+            rootPId: -1
+        }
+    },
+    callback: {
+        onCheck: zTreeOprObj.onCheck,
+        onClick: zTreeOprObj.onClick
+    }
+};
+
+let rpt_prj_folder_setting = {
+    view: {
+        selectedMulti: false
+    },
+    check: {
+        enable: true
+    },
+    data: {
+        keep: {
+            parent:true,
+            leaf:true
+        },
+        key: {
+            children: "items"
+        },
+        simpleData: {
+            enable: true,
+            rootPId: -1
+        }
+    },
+    callback: {
+        // onCheck: zTreeOprObj.onCheck,
+        // onClick: zTreeOprObj.onClick
+    }
+};

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

@@ -0,0 +1,906 @@
+/**
+ * Created by Tony on 2017/6/26.
+ */
+'use strict'
+
+const PRE_PAGE_OFFSET = 150;
+const NEXT_PAGE_OFFSET = 160;
+const FIRST_PAGE_OFFSET = 50;
+const LAST_PAGE_OFFSET = 60;
+
+const WAIT_TIME_EXPORT = 12000;
+
+let fontSuffixMapObj = {"表标题": "title", "列标题": "column", "正文内容": "content", "合计": "summary", "表眉/表脚": "header_footer"};
+
+let rptTplObj = {
+    hasInitialized: false,
+    iniPage: function() {
+        let me = this;
+        if (!me.hasInitialized) {
+            // zTreeOprObj.getCustomerCfg();
+            zTreeOprObj.getReportTemplateTree();
+            zTreeOprObj.selectedPrjIDs = [];
+            me.hasInitialized = true;
+            // let canvas = document.getElementById("rptCanvas");
+            // canvas.onclick = canvasOprObj.canvasOnClick;
+            // canvas.onmousemove = canvasOprObj.canvasOnMouseMove;
+        }
+    }
+}
+
+let zTreeOprObj = {
+    treeObj: null,
+    prjFolderTreeObj: null,
+    currentNode: null,
+    checkedRptTplNodes: null,
+    currentRptPageRst: null,
+    defReportPageCfg: null,
+    currentPage: 1,
+    maxPages: 0,
+    selectedPrjIDs: [],
+    countChkedRptTpl: function () {
+        let me = zTreeOprObj;
+        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);
+            }
+            $("#export_div").find("span").each(function(cIdx,elementSpan){
+                elementSpan.innerText = cnt;
+            });
+            $("#print_div").find("span").each(function(cIdx,elementSpan){
+                elementSpan.innerText = cnt;
+            });
+        }
+    },
+    getReportTemplateTree: function() {
+        let me = zTreeOprObj;
+        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);
+                            }
+                        }
+                    }
+                }
+            }
+        };
+        let nodeLv = 0;
+        private_remove_hide_item(topTreeNodesValue, nodeLv);
+        zTreeHelper.createTreeDirectly(topTreeNodesValue, rpt_tpl_setting, "rptTplTree", me);
+        me.refreshNodes();
+    },
+    iniFontCfgDom: function (cfg) {
+        for (let font of cfg.fonts) {
+            let domArrs = [];
+            let fontPropSuffix = fontSuffixMapObj[font.CfgDispName];
+            domArrs.push("<div class='row mb-1'>");
+            //1. label
+            domArrs.push("<div class='col-3'>" + font.CfgDispName + "</div>");
+            //2. font name
+            domArrs.push("<div class='col-3'>");
+            domArrs.push("<select class='form-control input-sm' id='fontName_" + fontPropSuffix + "' onchange='rptControlObj.changeFontMain(\"" + font.CfgDispName + "\", \"Name\", this)'>");
+            domArrs.push("<option>宋体</option><option>楷体</option><option>黑体</option>");
+            domArrs.push("</select>");
+            domArrs.push("</div>");
+            //3. font height
+            domArrs.push("<div class='col-3'>");
+            domArrs.push("<input class='form-control input-sm' id='fontHeight_" + fontPropSuffix + "' type='number' value='30' step='1' min='6' max='66' " +
+                "onchange='rptControlObj.changeFontMain(\"" + font.CfgDispName + "\", \"FontHeight\", this)' " +
+                "onkeyup='rptControlObj.changeFontMain(\"" + font.CfgDispName + "\", \"FontHeight\", this)'>");
+            domArrs.push("</div>");
+            //4. font bold italic underline
+            domArrs.push("<div class='col-3'>");
+            domArrs.push("<a id='font_bold_" + fontPropSuffix + "' class='btn btn-sm btn-outline-secondary' title='加粗' onclick='rptControlObj.changeFontAdhoc(\"" + font.CfgDispName + "\", \"FontBold\", this)'><i class='fa fa-bold'></i></a>");
+            domArrs.push("<a id='font_italic_" + fontPropSuffix + "' class='btn btn-sm btn-outline-secondary' title='斜体' onclick='rptControlObj.changeFontAdhoc(\"" + font.CfgDispName + "\", \"FontItalic\", this)'><i class='fa fa-italic'></i></a>");
+            domArrs.push("<a id='font_underline_" + fontPropSuffix + "' class='btn btn-sm btn-outline-secondary' title='下划线' onclick='rptControlObj.changeFontAdhoc(\"" + font.CfgDispName + "\", \"FontUnderline\", this)'><i class='fa fa-underline'></i></a>");
+            domArrs.push("</div>");
+            //
+            domArrs.push("</div>");
+            $(domArrs.join("")).insertBefore($("#font_cfg_blank_flag"));
+        }
+    },
+    renderRptCfg: function (cfg) {
+        $("#elementMargin_Left").get(0).value = cfg.margins.Left;
+        $("#elementMargin_Right").get(0).value = cfg.margins.Right;
+        $("#elementMargin_Top").get(0).value = cfg.margins.Top;
+        $("#elementMargin_Bottom").get(0).value = cfg.margins.Bottom;
+
+        for (let font of cfg.fonts) {
+            let fontPropSuffix = fontSuffixMapObj[font.CfgDispName];
+            document.getElementById("fontName_" + fontPropSuffix).value = font.Name;
+            document.getElementById("fontHeight_" + fontPropSuffix).value = font.FontHeight;
+            document.getElementById("font_bold_" + fontPropSuffix).className = (font.FontBold === "T")?"btn btn-sm btn-outline-secondary active":"btn btn-sm btn-outline-secondary";
+            document.getElementById("font_italic_" + fontPropSuffix).className = (font.FontItalic === "T")?"btn btn-sm btn-outline-secondary active":"btn btn-sm btn-outline-secondary";
+            document.getElementById("font_underline_" + fontPropSuffix).className = (font.FontUnderline === "T")?"btn btn-sm btn-outline-secondary active":"btn btn-sm btn-outline-secondary";
+        }
+        document.getElementById("rpt_vertical_line").checked = cfg.showVerticalLine;
+        document.getElementById("rpt_narrow").checked = cfg.isNarrow;
+        // document.getElementById("rpt_narrow").checked = false;
+        document.getElementById("rpt_fill_zero").checked = cfg.fillZero;
+    },
+    extractRptCfg: function (cfg) {
+        cfg.margins.Left = $("#elementMargin_Left").get(0).value;
+        cfg.margins.Right = $("#elementMargin_Right").get(0).value;
+        cfg.margins.Top = $("#elementMargin_Top").get(0).value;
+        cfg.margins.Bottom = $("#elementMargin_Bottom").get(0).value;
+
+        for (let font of cfg.fonts) {
+            let fontPropSuffix = fontSuffixMapObj[font.CfgDispName];
+            font.Name = document.getElementById("fontName_" + fontPropSuffix).value;
+            font.FontHeight = document.getElementById("fontHeight_" + fontPropSuffix).value;
+            font.FontBold = (document.getElementById("font_bold_" + fontPropSuffix).className === "btn btn-sm btn-outline-secondary active")?"T":"F";
+            font.FontItalic = (document.getElementById("font_italic_" + fontPropSuffix).className === "btn btn-sm btn-outline-secondary active")?"T":"F";
+            font.FontUnderline = (document.getElementById("font_underline_" + fontPropSuffix).className === "btn btn-sm btn-outline-secondary active")?"T":"F";
+        }
+        cfg.showVerticalLine = document.getElementById("rpt_vertical_line").checked;
+        cfg.isNarrow = document.getElementById("rpt_narrow").checked;
+        cfg.fillZero = document.getElementById("rpt_fill_zero").checked;
+    },
+    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) {
+        zTreeOprObj.countChkedRptTpl();
+    },
+    onClick: function(event,treeId,treeNode) {
+        let me = zTreeOprObj;
+        if (treeNode.nodeType === TPL_TYPE_TEMPLATE && treeNode.refId > 0) {
+            me.currentNode = treeNode;
+            let params = {};
+            let pageSize = rptControlObj.getCurrentPageSize();
+            params.pageSize = pageSize;
+            params.rpt_tpl_id = treeNode.refId;
+            params.project_id = PROJECT_ID;
+            params.tender_id = TENDER_ID;
+            params.stage_id = getStageId();
+            params.stage_order = getStageOrder();
+            params.stage_times = getStageTimes();
+            params.custCfg = CUST_CFG;
+            me.requestNormalReport(params);
+            me.countChkedRptTpl();
+        }
+    },
+    changePageSize: function(dom) {
+        let me = zTreeOprObj,
+            targetDom = document.getElementById("btnRptPageSize");
+        let tmpStr = targetDom.innerHTML.trim();
+        targetDom.innerHTML = dom.innerHTML.trim();
+        dom.innerHTML = tmpStr;
+        me.changeCfg();
+
+    },
+    changeOrientation: function(dom) {
+        let me = zTreeOprObj,
+            targetDom = document.getElementById("btnRptOrientation");
+        let tmpStr = targetDom.innerHTML.trim();
+        targetDom.innerHTML = dom.innerHTML.trim();
+        dom.innerHTML = tmpStr;
+        me.changeCfg();
+    },
+    changeCfg: function() {
+        let me = zTreeOprObj;
+        if (me.currentNode) {
+            let params = {};
+            params.pageSize = rptControlObj.getCurrentPageSize();
+            params.orientation = rptControlObj.getCurrentOrientation();
+            params.rpt_tpl_id = me.currentNode.refId;
+            params.custCfg = CUST_CFG;
+            params.project_id = PROJECT_ID;
+            params.tender_id = TENDER_ID;
+            params.stage_id = getStageId();
+            params.stage_order = getStageOrder();
+            params.stage_times = getStageTimes();
+            me.requestNormalReport(params);
+        }
+    },
+    resetAfter: function (pageRst) {
+        let size = pageRst[JV.NODE_PAGE_INFO][JV.NODE_PAGE_SIZE].slice(0);
+        if (size[0] > size[1]) {
+            document.getElementById("btnRptOrientation").innerHTML = "横向";
+            document.getElementById("hrefRptOrientation").innerHTML = "纵向";
+        } else {
+            document.getElementById("btnRptOrientation").innerHTML = "纵向";
+            document.getElementById("hrefRptOrientation").innerHTML = "横向";
+        }
+    },
+    requestNormalReport: function (params) {
+        let me = zTreeOprObj;
+        // hintBox.waitBox();
+        CommonAjax.postXsrfEx("/tender/report_api/getReport", params, 60000, true, getCookie('csrfToken'),
+            function(result){
+                // hintBox.unWaitBox();
+                let pageRst = result.data;
+                let canvas = document.getElementById("rptCanvas");
+                if (pageRst && pageRst.items && pageRst.items.length > 0) {
+                    me.resetAfter(pageRst);
+                    me.currentRptPageRst = pageRst;
+                    me.maxPages = pageRst.items.length;
+                    me.currentPage = 1;
+                    me.displayPageValue();
+                    let size = JpcCanvasOutput.getReportSizeInPixel(me.currentRptPageRst, getScreenDPI());
+                    canvas.width = size[0] + 20;
+                    if (size[1] > size[0]) {
+                        canvas.height = size[1] + 100;
+                    } else {
+                        canvas.height = size[1] + 50;
+                    }
+                    me.showPage(1, canvas);
+                } else {
+                    //返回了无数据表
+                    JpcCanvasOutput.cleanCanvas(canvas);
+                    JpcCanvasOutput.drawPageBorder(me.currentRptPageRst, canvas, getScreenDPI());
+                }
+            }, function(err){
+                // hintBox.unWaitBox();
+            }, function(ex){
+                // hintBox.unWaitBox();
+            }
+        );
+    },
+    requestPrjFolderCommon: function () {
+        // let me = zTreeOprObj;
+        // hintBox.waitBox();
+        // $.ajax({
+        //     type:"POST",
+        //     url: '/pm/api/getProjects',
+        //     data: {'data': JSON.stringify({"user_id": userID, "compilation": projectObj.project.projectInfo.compilation})},
+        //     dataType: 'json',
+        //     cache: false,
+        //     timeout: 15000,
+        //     success: function(result){
+        //         hintBox.unWaitBox();
+        //         if (result.error === 0) {
+        //             //console.log(result.data);
+        //             let currPrjParentID = projectObj.project.projectInfo.ParentID;
+        //             let selectedProjects = [];
+        //             for (let prj of result.data) {
+        //                 if (currPrjParentID === prj.ParentID) {
+        //                     selectedProjects.push({name: prj.name, ID: prj.ID});
+        //                 }
+        //             }
+        //             $("#show_project_folder").trigger("click");
+        //             me.prjFolderTreeObj = $.fn.zTree.init($("#prjFolderTree"), rpt_prj_folder_setting, selectedProjects);
+        //             me.prjFolderTreeObj.expandAll(true);
+        //         } else {
+        //             alert('error: ' + result.message);
+        //         }
+        //     },
+        //     error: function(jqXHR, textStatus, errorThrown){
+        //         hintBox.unWaitBox();
+        //         alert('error ' + textStatus + " " + errorThrown);
+        //     }
+        // });
+    },
+    requestBillsSummaryRpt: function () {
+        let me = zTreeOprObj;
+        let nodes = me.prjFolderTreeObj.getCheckedNodes(true);
+        if (nodes.length > 0) {
+            hintBox.waitBox();
+            let params = {};
+            params.pageSize = rptControlObj.getCurrentPageSize();
+            params.rpt_tpl_id = me.currentNode.refId;
+            params.custCfg = CUST_CFG;
+            params.prjIds = [];
+            me.selectedPrjIDs = [];
+            for (let node of nodes) {
+                params.prjIds.push(node.ID);
+                me.selectedPrjIDs.push(node.ID);
+            }
+            CommonAjax.postEx("report_api/getBillsSummaryReport", params, 26000, true,
+                function(result){
+                    hintBox.unWaitBox();
+                    let pageRst = result;
+                    let canvas = document.getElementById("rptCanvas");
+                    if (pageRst && pageRst.items && pageRst.items.length > 0) {
+                        me.resetAfter(pageRst);
+                        me.currentRptPageRst = pageRst;
+                        me.maxPages = pageRst.items.length;
+                        me.currentPage = 1;
+                        me.displayPageValue();
+                        let size = JpcCanvasOutput.getReportSizeInPixel(me.currentRptPageRst, getScreenDPI());
+                        canvas.width = size[0] + 20;
+                        if (size[1] > size[0]) {
+                            canvas.height = size[1] + 100;
+                        } else {
+                            canvas.height = size[1] + 50;
+                        }
+                        me.showPage(1, canvas);
+                    } else {
+                        //返回了无数据表
+                        JpcCanvasOutput.cleanCanvas(canvas);
+                        JpcCanvasOutput.drawPageBorder(me.currentRptPageRst, canvas, getScreenDPI());
+                    }
+                }, function(err){
+                    hintBox.unWaitBox();
+                }, function(ex){
+                    hintBox.unWaitBox();
+                }
+            );
+        }
+    },
+    requestGljSummaryRpt: function () {
+        let me = zTreeOprObj;
+        let nodes = me.prjFolderTreeObj.getCheckedNodes(true);
+        if (nodes.length > 0) {
+            hintBox.waitBox();
+            let params = {};
+            params.pageSize = rptControlObj.getCurrentPageSize();
+            params.rpt_tpl_id = me.currentNode.refId;
+            params.custCfg = CUST_CFG;
+            params.prjIds = [];
+            zTreeOprObj.selectedPrjIDs = [];
+            for (let node of nodes) {
+                params.prjIds.push(node.ID);
+                zTreeOprObj.selectedPrjIDs.push(node.ID);
+            }
+            CommonAjax.postEx("report_api/getGljSummaryReport", params, 26000, true,
+                function(result){
+                    hintBox.unWaitBox();
+                    let pageRst = result;
+                    let canvas = document.getElementById("rptCanvas");
+                    if (pageRst && pageRst.items && pageRst.items.length > 0) {
+                        me.resetAfter(pageRst);
+                        me.currentRptPageRst = pageRst;
+                        me.maxPages = pageRst.items.length;
+                        me.currentPage = 1;
+                        me.displayPageValue();
+                        let size = JpcCanvasOutput.getReportSizeInPixel(me.currentRptPageRst, getScreenDPI());
+                        canvas.width = size[0] + 20;
+                        if (size[1] > size[0]) {
+                            canvas.height = size[1] + 100;
+                        } else {
+                            canvas.height = size[1] + 50;
+                        }
+                        me.showPage(1, canvas);
+                    } else {
+                        //返回了无数据表
+                        JpcCanvasOutput.cleanCanvas(canvas);
+                        JpcCanvasOutput.drawPageBorder(me.currentRptPageRst, canvas, getScreenDPI());
+                    }
+                }, function(err){
+                    hintBox.unWaitBox();
+                }, function(ex){
+                    hintBox.unWaitBox();
+                }
+            );
+        }
+    },
+    requestSumAndNormalRptForPDF: function () {
+        let rpt_names = [], bill_rpt_names = [], glj_rpt_names = [];
+        let refRptTplIds = [], refBillSumPrjsIds = [], refGljSumPrjsIds = [];
+        rptControlObj.getTplIdsCommon(refRptTplIds, refBillSumPrjsIds, refGljSumPrjsIds, rpt_names, bill_rpt_names, glj_rpt_names);
+        let params = rptControlObj.creatCommonExportParam(refRptTplIds, refBillSumPrjsIds, refGljSumPrjsIds);
+        params.sum_rpt_names = bill_rpt_names.concat(glj_rpt_names);
+        params.rpt_names = rpt_names;
+        params.isOneSheet = true;
+
+        CommonAjax.postEx("report_api/createPdfFiles", params, WAIT_TIME_EXPORT, true, function(result){
+                if (result) {
+                    let uuIdUrls = [];
+                    for (let uuIdObj of result) {
+                        let uuIdUrl =  "/report_api/getFileByUUID/" + uuIdObj.uuid + "/" + stringUtil.replaceAll(uuIdObj.reportName, "#", "_") + "/pdf";
+                        uuIdUrls.push(uuIdUrl);
+                    }
+                    downloadReport(uuIdUrls);
+                } else {
+                    //
+                }
+            }, null, null
+        );
+    },
+    requestSumAndNormalRptForMultiExcel: function () {
+        let rpt_names = [], bill_rpt_names = [], glj_rpt_names = [];
+        let refRptTplIds = [], refBillSumPrjsIds = [], refGljSumPrjsIds = [];
+        rptControlObj.getTplIdsCommon(refRptTplIds, refBillSumPrjsIds, refGljSumPrjsIds, rpt_names, bill_rpt_names, glj_rpt_names);
+        let params = rptControlObj.creatCommonExportParam(refRptTplIds, refBillSumPrjsIds, refGljSumPrjsIds);
+        params.sum_rpt_names = bill_rpt_names.concat(glj_rpt_names);
+        params.rpt_names = rpt_names;
+        params.isOneSheet = true;
+
+        CommonAjax.postEx("report_api/createExcelFiles", params, WAIT_TIME_EXPORT, true, function(result){
+                if (result) {
+                    let uuIdUrls = [];
+                    for (let uuIdObj of result) {
+                        let uuIdUrl =  "/report_api/getFileByUUID/" + uuIdObj.uuid + "/" + stringUtil.replaceAll(uuIdObj.reportName, "#", "_") + "/xlsx";
+                        uuIdUrls.push(uuIdUrl);
+                    }
+                    downloadReport(uuIdUrls);
+                } else {
+                    //
+                }
+            }, null, null
+        );
+    },
+    requestSumAndNormalRptForAllInOneExcel: function () {
+        let orgRptName = projectObj.project.projectInfo.name;
+        let refRptTplIds = [], refBillSumPrjsIds = [], refGljSumPrjsIds = [];
+        rptControlObj.getTplIdsCommon(refRptTplIds, refBillSumPrjsIds, refGljSumPrjsIds, null, null, null);
+        let params = rptControlObj.creatCommonExportParam(refRptTplIds, refBillSumPrjsIds, refGljSumPrjsIds);
+        params.rptName = orgRptName;
+
+        CommonAjax.postEx("report_api/createExcelFilesInOneBook", params, WAIT_TIME_EXPORT, true, function(result){
+                if (result) {
+                    let uuIdUrls = [];
+                    let uuIdUrl =  "/report_api/getFileByUUID/" + result.uuid + "/" + stringUtil.replaceAll(result.reportName, "#", "_") + "/xlsx";
+                    uuIdUrls.push(uuIdUrl);
+                    downloadReport(uuIdUrls);
+                } else {
+                    //
+                }
+            }, null, null
+        );
+    },
+    showPage: function (pageNum, canvas) {
+        let me = zTreeOprObj;
+        if (pageNum >= 1 && pageNum <= me.maxPages) {
+            me.currentPage = pageNum;
+            JpcCanvasOutput.cleanCanvas(canvas);
+            JpcCanvasOutput.drawPageBorder(me.currentRptPageRst, canvas, getScreenDPI());
+            JpcCanvasOutput.drawToCanvas(me.currentRptPageRst, canvas, me.currentPage);
+        }
+        me.displayPageValue();
+    },
+    displayPageValue: function() {
+        let me = zTreeOprObj;
+        $("#rpt_page_num").get(0).value = me.currentPage + "/" + me.maxPages;
+    }
+};
+
+let canvasOprObj = {
+    canvasOnMouseMove: function (event) {
+        if (zTreeOprObj.currentNode) {
+            let x = event.offsetX - JpcCanvasOutput.offsetX, canvas = event.originalTarget;
+            if (!(canvas)) canvas = event.target; //chrome浏览器不认event.originalTarget,只认event.target或event.currentTarget
+            if (x < FIRST_PAGE_OFFSET) {
+                canvas.style.cursor = "url(/web/building_saas/img/FirstPageSimple.cur), auto";
+            } else if (x < PRE_PAGE_OFFSET) {
+                canvas.style.cursor = "url(/web/building_saas/img/PreviousPageSimple.cur), auto";
+            } else if ((canvas.width - x) < LAST_PAGE_OFFSET) {
+                canvas.style.cursor = "url(/web/building_saas/img/LastPageSimple.cur), auto";
+            } else if ((canvas.width - x) < NEXT_PAGE_OFFSET) {
+                canvas.style.cursor = "url(/web/building_saas/img/NextPageSimple.cur), auto";
+            } else {
+                canvas.style.cursor = "";
+            }
+        }
+    },
+    canvasOnClick: function(event){
+        if (zTreeOprObj.currentNode) {
+            let x = event.offsetX - JpcCanvasOutput.offsetX, canvas = event.originalTarget;
+            if (!(canvas)) canvas = event.target; //chrome浏览器不认event.originalTarget,只认event.target或event.currentTarget
+            if (x < FIRST_PAGE_OFFSET) {
+                zTreeOprObj.showPage(1, canvas);
+            } else if (x < PRE_PAGE_OFFSET) {
+                zTreeOprObj.showPage(zTreeOprObj.currentPage - 1, canvas);
+            } else if ((canvas.width - x) < LAST_PAGE_OFFSET) {
+                zTreeOprObj.showPage(zTreeOprObj.maxPages, canvas);
+            } else if ((canvas.width - x) < NEXT_PAGE_OFFSET) {
+                zTreeOprObj.showPage(zTreeOprObj.currentPage + 1, canvas);
+            }
+        }
+    }
+};
+
+let rptControlObj = {
+    currentOutputType: "Excel",
+    currentDownloadUrl: null,
+    currentDownloadIdx: 0,
+    getCurrentPageSize: function() {
+        // let rst = "A4";
+        let rst = document.getElementById("btnRptPageSize").innerHTML.trim();
+        //btnRptPageSize
+        return rst;
+    },
+    getCurrentOrientation: function() {
+        // let rst = "横向";
+        let rst = document.getElementById("btnRptOrientation").innerHTML.trim();
+        return rst;
+    },
+    getCurrentReportOption: function() {
+        //
+    },
+    changeType: function(newType) {
+        let me = rptControlObj;
+        let excelDom = document.getElementById("EXCEL_TYPE");
+        let pdfDom = document.getElementById("PDF_TYPE");
+        if (newType === "Excel") {
+            excelDom.className = "btn btn-block btn-primary";
+            pdfDom.className = "btn btn-block btn-outline-secondary";
+            me.currentOutputType = newType;
+        } else if (newType === "PDF") {
+            excelDom.className = "btn btn-block btn-outline-secondary";
+            pdfDom.className = "btn btn-block btn-primary";
+            me.currentOutputType = newType;
+        } else {
+            //me.currentOutputType = newType;
+        }
+    },
+    outputRpt: function() {
+        let me = rptControlObj;
+        if (me.currentOutputType === "Excel") {
+            me.getExcel();
+        } else if (me.currentOutputType === "PDF") {
+            me.getPDF();
+        } else {
+            //other types if needed.
+        }
+    },
+    getTplIdsCommon: function (refRptTplIds, refBillSumPrjsIds, refGljSumPrjsIds, rpt_names, bill_rpt_names, glj_rpt_names) {
+        for (let node of zTreeOprObj.checkedRptTplNodes) {
+            if (node.hasOwnProperty('flags') && node.flags.hasOwnProperty('reportType') && node['flags']['reportType'] !== 'NA') {
+                if (node['flags']['reportType'] === 'billSummary') {
+                    refBillSumPrjsIds.push(node.refId);
+                    if (bill_rpt_names) bill_rpt_names.push(node.name);
+                } else if (node['flags']['reportType'] === 'gljSummary') {
+                    refGljSumPrjsIds.push(node.refId);
+                    if (glj_rpt_names) glj_rpt_names.push(node.name);
+                }
+            } else {
+                refRptTplIds.push(node.refId);
+                if (rpt_names) rpt_names.push(node.name);
+            }
+        }
+    },
+    creatCommonExportParam: function (refRptTplIds, refBillSumPrjsIds, refGljSumPrjsIds) {
+        let nodes = (zTreeOprObj.prjFolderTreeObj === null)?[]:zTreeOprObj.prjFolderTreeObj.getCheckedNodes(true);
+        let rst = {};
+        rst.prj_id = projectObj.project.projectInfo.ID;
+        rst.rpt_ids = refRptTplIds;
+        rst.rpt_bill_tpl_ids = refBillSumPrjsIds;
+        rst.rpt_glj_tpl_ids = refGljSumPrjsIds;
+        rst.prjIds = [];
+        zTreeOprObj.selectedPrjIDs = [];
+        for (let node of nodes) {
+            rst.prjIds.push(node.ID);
+            zTreeOprObj.selectedPrjIDs.push(node.ID);
+        }
+        // rst.sum_rpt_names = bill_rpt_names.concat(glj_rpt_names);
+        // rst.rpt_names = rpt_names;
+        // rst.isOneSheet = true;
+        rst.pageSize = rptControlObj.getCurrentPageSize();
+        rst.orientation = ((zTreeOprObj.checkedRptTplNodes.length > 1)?null:rptControlObj.getCurrentOrientation());
+        rst.custCfg = CUST_CFG;
+        rst.option = "normal";
+        return rst;
+    },
+    getAllInOneBook: function () {
+        if (zTreeOprObj.checkedRptTplNodes && zTreeOprObj.checkedRptTplNodes.length > 0) {
+            let me = rptControlObj;
+            let orgRptName = projectObj.project.projectInfo.name;
+            let refRptTplIds = [], refBillSumPrjsIds = [], refGljSumPrjsIds = [];
+            rptControlObj.getTplIdsCommon(refRptTplIds, refBillSumPrjsIds, refGljSumPrjsIds);
+            if (zTreeOprObj.selectedPrjIDs.length > 0 && (refBillSumPrjsIds.length > 0 || refGljSumPrjsIds.length > 0)) {
+                let params = rptControlObj.creatCommonExportParam(refRptTplIds, refBillSumPrjsIds, refGljSumPrjsIds);
+                params.rptName = orgRptName;
+
+                CommonAjax.postEx("report_api/createExcelFilesInOneBook", params, WAIT_TIME_EXPORT, true, function(result){
+                        if (result) {
+                            let uuIdUrls = [];
+                            let uuIdUrl =  "/report_api/getFileByUUID/" + result.uuid + "/" + stringUtil.replaceAll(result.reportName, "#", "_") + "/xlsx";
+                            uuIdUrls.push(uuIdUrl);
+                            downloadReport(uuIdUrls);
+                        } else {
+                            //
+                        }
+                    }, null, null
+                );
+            } else {
+                let params = {};
+                params.prj_id = PROJECT_ID;
+                params.rpt_ids = refRptTplIds;
+                params.rptName = orgRptName;
+                params.pageSize = me.getCurrentPageSize();
+                params.orientation = ((zTreeOprObj.checkedRptTplNodes.length > 1)?null:me.getCurrentOrientation());
+                params.custCfg = CUST_CFG;
+                params.option = "normal";
+                CommonAjax.postEx("report_api/createExcelFilesInOneBook", params, WAIT_TIME_EXPORT, true, function(result){
+                        if (result) {
+                            let uuIdUrls = [];
+                            let uuIdUrl =  "/report_api/getFileByUUID/" + result.uuid + "/" + stringUtil.replaceAll(result.reportName, "#", "_") + "/xlsx";
+                            uuIdUrls.push(uuIdUrl);
+                            downloadReport(uuIdUrls);
+                        } else {
+                            //
+                        }
+                    }, null, null
+                );
+            }
+        }
+    },
+    getAllIndividualExcelBook: function () {
+        let me = rptControlObj;
+        if (zTreeOprObj.checkedRptTplNodes && zTreeOprObj.checkedRptTplNodes.length > 0) {
+            let refRptTplIds = [], refBillSumPrjsIds = [], refGljSumPrjsIds = [];
+            let rpt_names = [], bill_rpt_names = [], glj_rpt_names = [];
+            rptControlObj.getTplIdsCommon(refRptTplIds, refBillSumPrjsIds, refGljSumPrjsIds, rpt_names, bill_rpt_names, glj_rpt_names);
+            if (zTreeOprObj.selectedPrjIDs.length > 0 && (glj_rpt_names.length > 0 || bill_rpt_names.length > 0)) {
+                let params = rptControlObj.creatCommonExportParam(refRptTplIds, refBillSumPrjsIds, refGljSumPrjsIds);
+                params.prj_id = PROJECT_ID;
+                params.sum_rpt_names = bill_rpt_names.concat(glj_rpt_names);
+                params.rpt_names = rpt_names;
+                params.isOneSheet = true;
+                params.rptName = projectObj.project.projectInfo.name;
+                CommonAjax.postEx("report_api/createExcelFiles", params, WAIT_TIME_EXPORT, true, function(result){
+                        if (result) {
+                            let uuIdUrls = [];
+                            for (let uuIdObj of result) {
+                                let uuIdUrl =  "/report_api/getFileByUUID/" + uuIdObj.uuid + "/" + stringUtil.replaceAll(uuIdObj.reportName, "#", "_") + "/xlsx";
+                                uuIdUrls.push(uuIdUrl);
+                            }
+                            downloadReport(uuIdUrls);
+                        } else {
+                            //
+                        }
+                    }, null, null
+                );
+            } else {
+                if (refBillSumPrjsIds.length > 0 || refGljSumPrjsIds.length > 0) {
+                    $("#divReqBillSummary")[0].style.display = "none";
+                    $("#divReqGljSummary")[0].style.display = "none";
+                    $("#divReqCommonSummaryExcel")[0].style.display = "none";
+                    $("#divReqCommonSummaryMultiExcel")[0].style.display = "";
+                    $("#divReqCommonSummaryPDF")[0].style.display = "none";
+                    zTreeOprObj.requestPrjFolderCommon(); //先处理需要汇总的报表,走另外一个分支
+                } else if (refRptTplIds.length > 0) {
+                    let params = {};
+                    params.prj_id = PROJECT_ID;
+                    params.rpt_ids = refRptTplIds;
+                    params.rpt_names = rpt_names;
+                    params.pageSize = me.getCurrentPageSize();
+                    params.orientation = ((zTreeOprObj.checkedRptTplNodes.length > 1)?null:me.getCurrentOrientation());
+                    params.isOneSheet = true;
+                    params.custCfg = CUST_CFG;
+                    params.option = "normal";
+                    CommonAjax.postEx("report_api/createExcelFiles", params, WAIT_TIME_EXPORT, true, function(result){
+                            if (result) {
+                                let uuIdUrls = [];
+                                for (let uuIdObj of result) {
+                                    let uuIdUrl =  "/report_api/getFileByUUID/" + uuIdObj.uuid + "/" + stringUtil.replaceAll(uuIdObj.reportName, "#", "_") + "/xlsx";
+                                    uuIdUrls.push(uuIdUrl);
+                                }
+                                downloadReport(uuIdUrls);
+                            } else {
+                                //
+                            }
+                        }, null, null
+                    );
+                }
+            }
+        }
+    },
+    checkAndGetExcel: function () {
+        if (zTreeOprObj.treeObj) {
+            let chkNodes = zTreeOprObj.treeObj.getCheckedNodes(true);
+            if (chkNodes.length > 0) {
+                $("#show_excel_output_cfg").trigger("click");
+            } else {
+                rptControlObj.getAllIndividualExcelBook();
+            }
+        }
+    },
+    getExcel: function () {
+        let me = rptControlObj;
+        if ($("#excelExportType_AllInOneBook").get(0).checked) {
+            me.getAllInOneBook();
+        } else if ($("#excelExportType_IndividualBook").get(0).checked) {
+            me.getAllIndividualExcelBook();
+        }
+    },
+    getPDF: function () {
+        let me = rptControlObj;
+        if (zTreeOprObj.checkedRptTplNodes && zTreeOprObj.checkedRptTplNodes.length > 0) {
+            let refRptTplIds = [], refBillSumPrjsIds = [], refGljSumPrjsIds = [];
+            let rpt_names = [], bill_rpt_names = [], glj_rpt_names = [];
+            rptControlObj.getTplIdsCommon(refRptTplIds, refBillSumPrjsIds, refGljSumPrjsIds, rpt_names, bill_rpt_names, glj_rpt_names);
+            if (zTreeOprObj.selectedPrjIDs.length > 0 && (glj_rpt_names.length > 0 || bill_rpt_names.length > 0)) {
+                let params = rptControlObj.creatCommonExportParam();
+                params.prj_id = PROJECT_ID;
+                params.sum_rpt_names = bill_rpt_names.concat(glj_rpt_names);
+                params.rpt_ids = refRptTplIds;
+                params.rpt_bill_tpl_ids = refBillSumPrjsIds;
+                params.rpt_glj_tpl_ids = refGljSumPrjsIds;
+                params.rpt_names = rpt_names;
+                params.isOneSheet = true;
+                params.rptName = projectObj.project.projectInfo.name;
+                CommonAjax.postEx("report_api/createPdfFiles", params, WAIT_TIME_EXPORT, true, function(result){
+                        if (result) {
+                            let uuIdUrls = [];
+                            for (let uuIdObj of result) {
+                                let uuIdUrl =  "/report_api/getFileByUUID/" + uuIdObj.uuid + "/" + stringUtil.replaceAll(uuIdObj.reportName, "#", "_") + "/pdf";
+                                uuIdUrls.push(uuIdUrl);
+                            }
+                            downloadReport(uuIdUrls);
+                        } else {
+                            //
+                        }
+                    }, null, null
+                );
+            } else {
+                if (refBillSumPrjsIds.length > 0 || refGljSumPrjsIds.length > 0) {
+                    $("#divReqBillSummary")[0].style.display = "none";
+                    $("#divReqGljSummary")[0].style.display = "none";
+                    $("#divReqCommonSummaryExcel")[0].style.display = "none";
+                    $("#divReqCommonSummaryMultiExcel")[0].style.display = "none";
+                    $("#divReqCommonSummaryPDF")[0].style.display = "";
+                    zTreeOprObj.requestPrjFolderCommon(); //先处理需要汇总的报表,走另外一个分支
+                } else if (refRptTplIds.length > 0) {
+                    let params = {};
+                    params.prj_id = PROJECT_ID;
+                    params.rpt_ids = refRptTplIds;
+                    params.rpt_names = rpt_names;
+                    params.pageSize = me.getCurrentPageSize();
+                    params.orientation = ((zTreeOprObj.checkedRptTplNodes.length > 1)?null:me.getCurrentOrientation());
+                    params.custCfg = CUST_CFG;
+                    params.option = "normal";
+                    CommonAjax.postEx("report_api/createPdfFiles", params, WAIT_TIME_EXPORT, true, function(result){
+                            if (result) {
+                                let uuIdUrls = [];
+                                for (let uuIdObj of result) {
+                                    let uuIdUrl =  "/report_api/getFileByUUID/" + uuIdObj.uuid + "/" + stringUtil.replaceAll(uuIdObj.reportName, "#", "_") + "/pdf";
+                                    uuIdUrls.push(uuIdUrl);
+                                }
+                                downloadReport(uuIdUrls);
+                            } else {
+                                //
+                            }
+                        }, null, null
+                    );
+                }
+            }
+        }
+    },
+    firstPage: function(dom) {
+        let canvas = document.getElementById("rptCanvas");
+        zTreeOprObj.showPage(1, canvas);
+    },
+    prePage: function(dom) {
+        let canvas = document.getElementById("rptCanvas");
+        zTreeOprObj.showPage(zTreeOprObj.currentPage - 1, canvas);
+    },
+    nextPage: function(dom) {
+        let canvas = document.getElementById("rptCanvas");
+        zTreeOprObj.showPage(zTreeOprObj.currentPage + 1, canvas);
+    },
+    lastPage: function(dom) {
+        let me = zTreeOprObj;
+        let canvas = document.getElementById("rptCanvas");
+        zTreeOprObj.showPage(me.maxPages, canvas);
+    },
+    onKeydown: function (event, dom) {
+        let me = zTreeOprObj, keyPressed = null;
+        if (window.event) {
+            keyPressed = window.event.keyCode; // IE/Chrome
+        } else {
+            keyPressed = event.which; // Firefox
+        }
+        if (keyPressed === 13) {
+            let pageNum = 1;
+            try {
+                pageNum = parseInt(dom.value);
+            } catch (e) {
+                pageNum = 1;
+            }
+            let canvas = document.getElementById("rptCanvas");
+            if (pageNum < 1) {
+                pageNum = 1;
+            } else if (pageNum > me.maxPages) {
+                pageNum = me.maxPages;
+            }
+            zTreeOprObj.showPage(pageNum, canvas);
+            return false;
+        }
+    },
+    changeMargin: function(marginPropStr, marginDom) {
+        CUST_CFG.margins[marginPropStr] = marginDom.value;
+    },
+    changeFontMain: function(CfgDispName, fontProperty, fontDom) {
+        for (let font of CUST_CFG.fonts) {
+            if (font["CfgDispName"] === CfgDispName) {
+                font[fontProperty] = fontDom.value;
+                break;
+            }
+        }
+    },
+    changeCfgOption: function (optStr, dom) {
+        CUST_CFG[optStr] = dom.checked;
+    },
+    changeFontAdhoc: function(CfgDispName, fontProperty, fontDom) {
+        for (let font of CUST_CFG.fonts) {
+            if (font["CfgDispName"] === CfgDispName) {
+                if (font[fontProperty] === 'T') {
+                    font[fontProperty] = 'F';
+                    fontDom.className = "btn btn-sm btn-outline-secondary";
+                } else {
+                    font[fontProperty] = 'T';
+                    fontDom.className = "btn btn-sm btn-outline-secondary active";
+                }
+                break;
+            }
+        }
+    },
+    restoreCustCFG: function () {
+        let me = this;
+        zTreeOprObj.renderRptCfg(zTreeOprObj.defReportPageCfg);
+        zTreeOprObj.extractRptCfg(CUST_CFG);
+        me.saveCustCfg();
+    },
+    saveCustCfg: function() {
+        let params = {};
+        params.custCfg = CUST_CFG;
+        CommonAjax.postEx("report_tpl_api/saveCustomerCfg", params, 20000, true, function(result){
+                // alert("Save successfully!");
+                $("#update_msg_response")[0].style.color = "green";
+                $("#update_msg_response")[0].innerHTML = " (保存成功!)";
+                setTimeout(function(){
+                    $("#update_msg_response")[0].innerHTML = "";
+                }, 1000);
+            }, function (failRst) {
+                $("#update_msg_response")[0].style.color = "red";
+                $("#update_msg_response")[0].innerHTML = " (保存失败!)";
+                setTimeout(function(){
+                    $("#update_msg_response")[0].innerHTML = "";
+                }, 1000);
+            }, null
+        );
+    },
+    confirmCfgChange: function() {
+        zTreeOprObj.changeCfg();
+    }
+};
+
+function downloadReport(urls) {
+    //考虑到多个报表下载,一些浏览器(如chrome)不允许一下子下载多个文件,得缓缓处理,统一在这处理
+    rptControlObj.currentDownloadUrl = null;
+    rptControlObj.currentDownloadIdx = 0;
+    let private_download = function() {
+        if (rptControlObj.currentDownloadIdx >= 0 && rptControlObj.currentDownloadIdx < urls.length) {
+            rptControlObj.currentDownloadUrl = urls[rptControlObj.currentDownloadIdx];
+            rptControlObj.currentDownloadIdx++;
+            window.location = rptControlObj.currentDownloadUrl;
+            setTimeout(private_download, 2000);
+        }
+    }
+    private_download();
+}
+
+function getStageId() {
+    return current_stage_id;
+}
+
+function getStageOrder() {
+    return current_stage_order;
+}
+
+function getStageTimes() {
+    return current_stage_times;
+}

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

@@ -0,0 +1,108 @@
+/**
+ * Created by Tony on 2019/4/10.
+ */
+
+let G_OFFSET_X = 0, G_OFFSET_Y = 0;
+function loading() {
+    if (sessionStorage.multiRptsData) {
+        let multiRptData = JSON.parse(sessionStorage.multiRptsData);
+        let scaleFactor = parseInt(sessionStorage.scaleFactor);
+        for (let idx = 0; idx < multiRptData.length; idx++) {
+            let pageData = multiRptData[idx];
+            if (idx === 0) {
+                $(document).attr("title", pageData[JV.NODE_PAGE_INFO][JV.NODE_MAIN_INFO_RPT_NAME]);
+            }
+            let orgPixelSize = getPixelSize(pageData);
+            let actArea = getActualArea(pageData);
+            let svgArr = rptPrintHelper.buildSvgArr(pageData, actArea, G_OFFSET_X, G_OFFSET_Y);
+            //let orientation = (pageData[JV.NODE_PAGE_INFO][JV.NODE_PAGE_SIZE][0] < pageData[JV.NODE_PAGE_INFO][JV.NODE_PAGE_SIZE][1])?"纵向":"横向";
+            let orientation = "纵向";
+            showPreviewData(svgArr, actArea, scaleFactor, sessionStorage.pageSize, orientation, orgPixelSize);
+        }
+        window.print();
+    } else if (sessionStorage.currentPageData) {
+        let pageData = JSON.parse(sessionStorage.currentPageData);
+        let scaleFactor = parseInt(sessionStorage.scaleFactor);
+        let orgPixelSize = getPixelSize(pageData);
+        let actArea = getActualArea(pageData);
+        let svgArr = rptPrintHelper.buildSvgArr(pageData, actArea, G_OFFSET_X, G_OFFSET_Y);
+        $(document).attr("title", pageData[JV.NODE_PAGE_INFO][JV.NODE_MAIN_INFO_RPT_NAME]);
+        //showPreviewData(svgArr, scaleFactor, sessionStorage.pageSize, sessionStorage.orientation);
+        showPreviewData(svgArr, actArea, scaleFactor, sessionStorage.pageSize, "纵向", orgPixelSize);
+        window.print();
+    } else if (sessionStorage.currentPageSvgData) {
+        let svgArr = JSON.parse(sessionStorage.currentPageSvgData);
+        let scaleFactor = 1;
+        //showPreviewData(svgArr, scaleFactor, sessionStorage.pageSize, sessionStorage.orientation);
+        showPreviewData(svgArr, null, scaleFactor, sessionStorage.pageSize, "纵向", null);
+        window.print();
+    } else {
+        //alert("没有报表数据!");
+    }
+}
+
+function showPreviewData(svgArr, actAreaArr, scaleFactor, pageSize, orientation, orgPixelSize) {
+    let orgHeight = 793, orgWidth = 1122;
+    let DFT_MARGIN = 5;
+    if (pageSize === 'A3') {
+        orgHeight = 1122;
+        orgWidth = 793 * 2;
+    } else if (pageSize.indexOf('自定义') >= 0) {
+        //自定义
+    } else {
+        //其他size(LEGAL, 16K etc.)
+    }
+    let pageHeight = orgHeight * scaleFactor, pageWidth = orgWidth * scaleFactor;
+    if (orientation === "纵向") {
+        pageHeight = orgWidth * scaleFactor;
+        pageWidth = orgHeight * scaleFactor;
+    }
+
+    for (let i = 0; i < svgArr.length; i++) {
+        let offsetHeight = 0, offsetWidth = 0;
+        if (actAreaArr) {
+            offsetWidth = actAreaArr[i].Left + (pageWidth - actAreaArr[i].Right) - DFT_MARGIN;
+            offsetHeight = actAreaArr[i].Top + (pageHeight - actAreaArr[i].Bottom) - DFT_MARGIN;
+            if (orgPixelSize[0] > orgPixelSize[1]) {
+                //横向强制改纵向(系统是以纵向为准),那么计算offset的方式会有所不同
+                offsetWidth = actAreaArr[i].Top + (pageWidth - actAreaArr[i].Bottom) - DFT_MARGIN;
+                //横向转纵向时,还得考虑左上角转右上角的Left与Top之间的差,否则坐标还是会有偏差
+                offsetHeight = actAreaArr[i].Left + (pageHeight - actAreaArr[i].Right) - (actAreaArr[i].Left - actAreaArr[i].Top) - DFT_MARGIN;
+            }
+        }
+        let div = $('<div class="pageBreak"></div>');
+        div.append($(svgArr[i].join("")));
+        $(div).find("svg").each(function(cIdx,elementSvg){
+            elementSvg.setAttribute('height', pageHeight - offsetHeight);
+            elementSvg.setAttribute('width', pageWidth - offsetWidth);
+        });
+        $("body").append(div);
+    }
+}
+
+function getActualArea(pageData) {
+    let rst = [];
+    for (let item of pageData.items) {
+        let area = {Left: 10000, Right: 0, Top: 10000, Bottom: 0};
+        for (let cell of item.cells) {
+            if (cell.area.Left < area.Left) {
+                area.Left = cell.area.Left;
+            }
+            if (cell.area.Right > area.Right) {
+                area.Right = cell.area.Right;
+            }
+            if (cell.area.Top < area.Top) {
+                area.Top = cell.area.Top;
+            }
+            if (cell.area.Bottom > area.Bottom) {
+                area.Bottom = cell.area.Bottom;
+            }
+        }
+        rst.push(area);
+    }
+    return rst;
+}
+function closing() {
+    //
+}
+

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

@@ -0,0 +1,343 @@
+/**
+ * Created by Tony on 2018/4/24.
+ */
+
+let rptPrintHelper = {
+    preview: function () {
+        if (zTreeOprObj.checkedRptTplNodes && zTreeOprObj.checkedRptTplNodes.length > 0) {
+            let refRptTplIds = [], refBillSumPrjsIds = [], refGljSumPrjsIds = [];
+            rptControlObj.getTplIdsCommon(refRptTplIds, refBillSumPrjsIds, refGljSumPrjsIds, null, null, null);
+            let params = rptControlObj.creatCommonExportParam(refRptTplIds, refBillSumPrjsIds, refGljSumPrjsIds);
+            params.rpt_ids = refRptTplIds;
+            params.rpt_bill_tpl_ids = refBillSumPrjsIds;
+            params.rpt_glj_tpl_ids = refGljSumPrjsIds;
+            CommonAjax.postEx("report_api/getMultiReports", params, 10000, true,
+                function(result){
+                    //sessionStorage.currentPageData = JSON.stringify(zTreeOprObj.currentRptPageRst);
+                    sessionStorage.multiRptsData = JSON.stringify(result);
+                    sessionStorage.pageSize = rptControlObj.getCurrentPageSize();
+                    sessionStorage.orientation = rptControlObj.getCurrentOrientation();
+                    sessionStorage.scaleFactor = 1;
+                    if (sessionStorage.pageSize === 'A3') {
+                        window.open('/rpt_printA3');
+                    } else {
+                        window.open('/rpt_print');
+                    }
+                },
+                function(failRst){
+                    sessionStorage.currentPageData = null;
+                    console.log(failRst);
+                },
+                function(exceptionRst){
+                    sessionStorage.currentPageData = null;
+                    console.log(exceptionRst);
+                }
+            );
+        } else {
+            //不可能的branch
+        }
+        // if (zTreeOprObj.currentRptPageRst) {
+        //     sessionStorage.currentPageData = JSON.stringify(zTreeOprObj.currentRptPageRst);
+        //     sessionStorage.pageSize = rptControlObj.getCurrentPageSize();
+        //     sessionStorage.orientation = rptControlObj.getCurrentOrientation();
+        //     sessionStorage.scaleFactor = 1;
+        //     window.open('/rpt_print');
+        // } else {
+        //     sessionStorage.currentPageData = null;
+        // }
+    },
+    previewSvgData: function() {
+        //
+    },
+    buildSvgArr: function (pagesData, actAreaArr, offsetX, offsetY) {
+        let styles = pagesData[JV.NODE_STYLE_COLLECTION],
+            fonts = pagesData[JV.NODE_FONT_COLLECTION],
+            controls = pagesData[JV.NODE_CONTROL_COLLECTION]
+        ;
+        let rst = [];
+        let canvas = document.getElementById("chkCanvas");
+        let pixelSize = getPixelSize(pagesData);
+        let isHtoV = false;
+        if (pixelSize[0] > pixelSize[1]) {
+            // changeHtoV(pagesData, pixelSize);
+            isHtoV = true;
+        }
+        for (let idx = 0; idx < pagesData.items.length; idx++) {
+            let page = pagesData.items[idx];
+            let actAreaOffsetX = 0, actAreaOffsetY = 0;
+            if (actAreaArr) {
+                actAreaOffsetX = actAreaArr[idx].Left;
+                actAreaOffsetY = actAreaArr[idx].Top;
+                if (pixelSize[0] > pixelSize[1]) {
+                }
+            }
+            let svgPageArr = [];
+            console.log(pixelSize);
+            svgPageArr.push("<svg width='" + pixelSize[0] + "' height='" + pixelSize[1] + "'>");
+            // let adjustY = 0.5 * ((idx + 1) % 2);
+            let adjustY = 0.5;
+            for (let cell of page.cells) {
+                svgPageArr.push(buildCellSvg(cell, fonts, styles, controls, page[JV.PROP_PAGE_MERGE_BORDER], pagesData[JV.BAND_PROP_MERGE_BAND],
+                    offsetX - actAreaOffsetX, offsetY - actAreaOffsetY, adjustY, canvas, isHtoV, pixelSize, actAreaArr[idx]));
+            }
+            svgPageArr.push("</svg>");
+            rst.push(svgPageArr);
+        }
+        return rst;
+    }
+};
+
+function getActualBorderStyle(cell, styles, mergeBorderStyle, pageBorderArea, borderStr) {
+    let rst = styles[cell[JV.PROP_STYLE]][borderStr];
+    if (mergeBorderStyle) {
+        if (parseFloat(cell[JV.PROP_AREA][borderStr]) === parseFloat(pageBorderArea[borderStr])) {
+            if (borderStr === JV.PROP_LEFT || borderStr === JV.PROP_RIGHT) {
+                if (parseFloat(cell[JV.PROP_AREA][JV.PROP_TOP]) >= parseFloat(pageBorderArea[JV.PROP_TOP]) &&
+                    parseFloat(cell[JV.PROP_AREA][JV.PROP_BOTTOM]) <= parseFloat(pageBorderArea[JV.PROP_BOTTOM])) {
+                    rst = mergeBorderStyle[borderStr];
+                }
+            } else if (borderStr === JV.PROP_TOP || borderStr === JV.PROP_BOTTOM) {
+                if (parseFloat(cell[JV.PROP_AREA][JV.PROP_LEFT]) >= parseFloat(pageBorderArea[JV.PROP_LEFT]) &&
+                    parseFloat(cell[JV.PROP_AREA][JV.PROP_RIGHT]) <= parseFloat(pageBorderArea[JV.PROP_RIGHT])) {
+                    rst = mergeBorderStyle[borderStr];
+                }
+            }
+        }
+    }
+    return rst;
+}
+
+function buildCellSvg(cell, fonts, styles, controls, pageMergeBorder, rptMergeBorder, offsetX, offsetY, adjustY, canvas, isHtoV, pixelSize, actArea) {
+    let rst = [];
+    let style = styles[cell[JV.PROP_STYLE]];
+    let mergeBandStyle = null;
+    if (rptMergeBorder) {
+        mergeBandStyle = styles[rptMergeBorder[JV.PROP_STYLE][JV.PROP_ID]];
+    }
+    let font = cell[JV.PROP_FONT];
+    if (typeof font === 'string') {
+        font = fonts[cell[JV.PROP_FONT]];
+    }
+    let left = parseInt(cell[JV.PROP_AREA][JV.PROP_LEFT]) + offsetX + 0.5,
+        right = parseInt(cell[JV.PROP_AREA][JV.PROP_RIGHT]) + offsetX + 0.5,
+        top = parseInt(cell[JV.PROP_AREA][JV.PROP_TOP]) + offsetY + adjustY,
+        bottom = parseInt(cell[JV.PROP_AREA][JV.PROP_BOTTOM]) + offsetY + adjustY
+    ;
+    let HtoVStr = "";
+    if (isHtoV) {
+        //HtoVStr = ` transform="translate(`+ pixelSize[1] + `,0) rotate(90)"`;
+        // HtoVStr = ` transform="translate(`+ (actArea.Bottom - actArea.Top + 5) + `,0) rotate(90)"`;
+        HtoVStr = ` transform="translate(`+ (actArea.Bottom - actArea.Top + 5) + `,` + (actArea.Left - actArea.Top ) + `) rotate(90)"`;
+        //console.log(actArea);
+    }
+    if (style) {
+        let leftBS = getActualBorderStyle(cell, styles, mergeBandStyle, (pageMergeBorder)?pageMergeBorder:rptMergeBorder[JV.PROP_AREA], JV.PROP_LEFT);
+        // if (style[JV.PROP_LEFT] && parseFloat(style[JV.PROP_LEFT][JV.PROP_LINE_WEIGHT]) > 0) {
+        if (leftBS && parseFloat(leftBS[JV.PROP_LINE_WEIGHT]) > 0) {
+            rst.push("<line x1='" + left + "' y1='" + top +
+                "' x2='" + left + "' y2='" + bottom +
+                "' style='stroke:rgb(0,0,0);stroke-width:" + leftBS[JV.PROP_LINE_WEIGHT] + "'" + HtoVStr + "/>")
+        }
+        let rightBS = getActualBorderStyle(cell, styles, mergeBandStyle, (pageMergeBorder)?pageMergeBorder:rptMergeBorder[JV.PROP_AREA], JV.PROP_RIGHT);
+        // if (style[JV.PROP_RIGHT] && parseFloat(style[JV.PROP_RIGHT][JV.PROP_LINE_WEIGHT]) > 0) {
+        if (rightBS && parseFloat(rightBS[JV.PROP_LINE_WEIGHT]) > 0) {
+            rst.push("<line x1='" + right + "' y1='" + top +
+                "' x2='" + right + "' y2='" + bottom +
+                "' style='stroke:rgb(0,0,0);stroke-width:" + rightBS[JV.PROP_LINE_WEIGHT] +"'" + HtoVStr + "/>")
+        }
+        let topBS = getActualBorderStyle(cell, styles, mergeBandStyle, (pageMergeBorder)?pageMergeBorder:rptMergeBorder[JV.PROP_AREA], JV.PROP_TOP);
+        // if (style[JV.PROP_TOP] && parseFloat(style[JV.PROP_TOP][JV.PROP_LINE_WEIGHT]) > 0) {
+        if (topBS && parseFloat(topBS[JV.PROP_LINE_WEIGHT]) > 0) {
+            rst.push("<line x1='" + left + "' y1='" + top +
+                "' x2='" + right + "' y2='" + top +
+                "' style='stroke:rgb(0,0,0);stroke-width:" + topBS[JV.PROP_LINE_WEIGHT] +"'" + HtoVStr + "/>")
+        }
+        let bottomBS = getActualBorderStyle(cell, styles, mergeBandStyle, (pageMergeBorder)?pageMergeBorder:rptMergeBorder[JV.PROP_AREA], JV.PROP_BOTTOM);
+        // if (style[JV.PROP_BOTTOM] && parseFloat(style[JV.PROP_BOTTOM][JV.PROP_LINE_WEIGHT]) > 0) {
+        if (bottomBS && parseFloat(bottomBS[JV.PROP_LINE_WEIGHT]) > 0) {
+            rst.push("<line x1='" + left + "' y1='" + bottom +
+                "' x2='" + right + "' y2='" + bottom +
+                "' style='stroke:rgb(0,0,0);stroke-width:" + bottomBS[JV.PROP_LINE_WEIGHT] +"'" + HtoVStr + "/>")
+        }
+    }
+    let control = cell[JV.PROP_CONTROL];
+    if (typeof control === 'string') {
+        control = controls[cell[JV.PROP_CONTROL]];
+    }
+    buildText(rst, cell, font, control, offsetX, offsetY, adjustY, canvas, isHtoV, HtoVStr);
+
+    return rst.join("");
+}
+
+function buildText(destRst, cell, font, control, offsetX, offsetY, adjustY, canvas, isHtoV, HtoVStr) {
+    let orgFontHeight = parseInt(font[JV.FONT_PROPS[JV.FONT_PROP_IDX_HEIGHT]]);
+    let fontWeight = (font[JV.FONT_PROPS[JV.FONT_PROP_IDX_BOLD]] === 'T')?"bold":"normal";
+    let fontStyle = (font[JV.FONT_PROPS[JV.FONT_PROP_IDX_ITALIC]] === 'T')?"italic":"normal";
+    let fontUnderline = (font[JV.FONT_PROPS[JV.FONT_PROP_IDX_UNDERLINE]] === 'T')?"underline":"normal";
+    let left = parseInt(cell[JV.PROP_AREA][JV.PROP_LEFT]) + offsetX + 0.5,
+        right = parseInt(cell[JV.PROP_AREA][JV.PROP_RIGHT]) + offsetX + 0.5,
+        top = parseInt(cell[JV.PROP_AREA][JV.PROP_TOP]) + offsetY + adjustY,
+        bottom = parseInt(cell[JV.PROP_AREA][JV.PROP_BOTTOM]) + offsetY + adjustY,
+        x = left, y = top,
+        text_anchor = "start"
+    ;
+    let value = cell[JV.PROP_VALUE];
+    if (!(value)) {
+        value = "";
+    }
+    let values = null;
+    if (typeof value === "string") {
+        values = value.split("|");
+    } else {
+        values = [value];
+    }
+    // let stepHeight = (parseInt(cell[JV.PROP_AREA][JV.PROP_BOTTOM]) - parseInt(cell[JV.PROP_AREA][JV.PROP_TOP])) / values.length;
+    if (control) {
+        if (control[JV.CONTROL_PROPS[JV.CONTROL_PROP_IDX_HORIZON]] === "left") {
+            text_anchor = "start";
+            x = left + JV.OUTPUT_OFFSET[JV.OFFSET_IDX_LEFT];
+        } else if (control[JV.CONTROL_PROPS[JV.CONTROL_PROP_IDX_HORIZON]] === "right") {
+            text_anchor = "end";
+            x = right - JV.OUTPUT_OFFSET[JV.OFFSET_IDX_RIGHT];
+        } else if (control[JV.CONTROL_PROPS[JV.CONTROL_PROP_IDX_HORIZON]] === "center") {
+            text_anchor = "middle";
+            x = Math.round((left + right) / 2);
+        }
+    }
+
+    let area = [0,0,0,0];
+    area[JV.IDX_TOP] = top;
+    area[JV.IDX_BOTTOM] = bottom;
+    area[JV.IDX_LEFT] = left;
+    area[JV.IDX_RIGHT] = right;
+    let height = bottom - top;
+    let ctx = canvas.getContext("2d");
+    let inner_draw_text = function (textValue) {
+        let dftFontHeight = orgFontHeight;
+        ctx.font = ((font[JV.FONT_PROPS[JV.FONT_PROP_IDX_BOLD]] === 'T')?"bold ":"") + ((font[JV.FONT_PROPS[JV.FONT_PROP_IDX_ITALIC]] === 'T')?"italic":"") + dftFontHeight + "px " + font[JV.PROP_NAME];
+        function inner_build_text(innerTxt, innerArea) {
+            let innerDftFontHeight = (dftFontHeight * 3 / 4); //SVG的字体与canvas的字体大小的切换, 不用考虑取整
+            if (control) {
+                if (control[JV.CONTROL_PROPS[JV.CONTROL_PROP_IDX_VERTICAL]] === "top") {
+                    y = innerArea[JV.IDX_TOP] + JV.OUTPUT_OFFSET[JV.IDX_TOP];
+                } else if (control[JV.CONTROL_PROPS[JV.CONTROL_PROP_IDX_VERTICAL]] === "bottom") {
+                    y = innerArea[JV.IDX_BOTTOM] - JV.OUTPUT_OFFSET[JV.IDX_BOTTOM];
+                } else if (control[JV.CONTROL_PROPS[JV.CONTROL_PROP_IDX_VERTICAL]] === "center") {
+                    y = Math.round((innerArea[JV.IDX_TOP] + innerArea[JV.IDX_BOTTOM] + innerDftFontHeight) / 2 );
+                }
+            } else {
+                y = innerArea[JV.IDX_TOP] + JV.OUTPUT_OFFSET[JV.IDX_TOP];
+            }
+            if (font[JV.PROP_NAME] === "宋体") {
+                y--;
+            }
+            destRst.push("<text style='fill:black;font-family:" + font[JV.PROP_NAME] +
+                ";font-weight:" + fontWeight +
+                ";font-style:" + fontStyle +
+                ";text-decoration:" + fontUnderline +
+                // ";text-decoration:normal" +
+                ";font-size:" + innerDftFontHeight + "pt' x='" +
+                x +"' y='" + y + "' text-anchor='" + text_anchor + "' xml:space='preserve'" + HtoVStr + ">" + innerTxt + "</text>");
+        }
+        let actLines = private_splitString(textValue, (area[JV.IDX_RIGHT] - area[JV.IDX_LEFT] - JV.OUTPUT_OFFSET[JV.IDX_LEFT] - JV.OUTPUT_OFFSET[JV.IDX_RIGHT]), ctx);
+        let validTxtLines = Math.floor((area[JV.IDX_BOTTOM] - area[JV.IDX_TOP]) / (dftFontHeight + JV.OUTPUT_OFFSET[JV.OFFSET_IDX_BOTTOM] + JV.OUTPUT_OFFSET[JV.OFFSET_IDX_TOP] + 4));
+        if (actLines.length === 1  || (control && control.Shrink !== 'T' && validTxtLines < actLines)) {
+            inner_build_text(textValue, area);
+        } else {
+            while (true) {
+                if (dftFontHeight > 6) {
+                    let lines = Math.floor((area[JV.IDX_BOTTOM] - area[JV.IDX_TOP]) / (dftFontHeight + JV.OUTPUT_OFFSET[JV.IDX_BOTTOM] + JV.OUTPUT_OFFSET[JV.IDX_TOP] + 4));
+                    lines = (lines === 0 || (control.Shrink === 'T' && control.ShrinkFirst === 'T'))?1:lines;
+                    actLines = private_splitString(textValue, (area[JV.IDX_RIGHT] - area[JV.IDX_LEFT] - JV.OUTPUT_OFFSET[JV.IDX_LEFT] - JV.OUTPUT_OFFSET[JV.IDX_RIGHT]), ctx);
+                    if (lines >= actLines.length) {
+                        let aH = dftFontHeight + JV.OUTPUT_OFFSET[JV.IDX_BOTTOM] + JV.OUTPUT_OFFSET[JV.IDX_TOP] + 4;
+                        if ((aH * actLines.length) < (area[JV.IDX_BOTTOM] - area[JV.IDX_TOP]) && (control && control.Vertical !== 'top')) {
+                            if (control.Vertical === 'bottom') {
+                                area[JV.IDX_TOP] = area[JV.IDX_BOTTOM] - (aH * actLines.length);
+                            } else {
+                                area[JV.IDX_TOP] = (area[JV.IDX_TOP] + area[JV.IDX_BOTTOM]) / 2 - (aH * actLines.length) / 2
+                                area[JV.IDX_BOTTOM] = area[JV.IDX_TOP] + (aH * actLines.length);
+                            }
+                        }
+                        let newArea = [], baseTop = area[JV.IDX_TOP];
+                        for (let ai = 0; ai < area.length; ai++) {
+                            newArea[ai] = area[ai];
+                        }
+                        for (let lIdx = 0; lIdx < actLines.length; lIdx++) {
+                            newArea[JV.IDX_TOP] = Math.round(aH * lIdx + baseTop);
+                            newArea[JV.IDX_BOTTOM] = Math.round(aH * (lIdx + 1) + baseTop);
+                            inner_build_text(actLines[lIdx], newArea);
+                        }
+                        break;
+                    } else {
+                        dftFontHeight--;
+                        ctx.font = ((font[JV.FONT_PROPS[JV.FONT_PROP_IDX_BOLD]] === 'T')?"bold ":"") + ((font[JV.FONT_PROPS[JV.FONT_PROP_IDX_ITALIC]] === 'T')?"italic":"") + dftFontHeight + "px " + font[JV.PROP_NAME];
+                    }
+                } else {
+                    inner_build_text(textValue, area);
+                    break;
+                }
+            }
+        }
+    };
+    let ah = height;
+    let restTopH = 0, restBottomH = 0;
+    if (control[JV.CONTROL_PROPS[JV.CONTROL_PROP_IDX_CLOSE_OUTPUT]] === 'T') {
+        ah = (parseFloat(font[JV.FONT_PROPS[1]]) + JV.OUTPUT_OFFSET[JV.OFFSET_IDX_TOP] + JV.OUTPUT_OFFSET[JV.OFFSET_IDX_BOTTOM]) * values.length;
+        let restH = height - ah;
+        if (control[JV.CONTROL_PROPS[JV.CONTROL_PROP_IDX_VERTICAL]] === 'center') {
+            restTopH = restH / 2;
+            restBottomH = restH / 2;
+        } else if (control[JV.CONTROL_PROPS[JV.CONTROL_PROP_IDX_VERTICAL]] === 'bottom') {
+            restBottomH = restH;
+        } else {
+            restTopH = restH;
+        }
+    }
+    for (let vidx = 0; vidx < values.length; vidx++) {
+        // area[JV.IDX_TOP] = top + vidx * (height / values.length);
+        // area[JV.IDX_BOTTOM] = top + (vidx + 1) * (height / values.length);
+        area[JV.IDX_TOP] = top + vidx * (ah / values.length) + restTopH;
+        area[JV.IDX_BOTTOM] = top + (vidx + 1) * (ah / values.length) + restTopH;
+        inner_draw_text(values[vidx]);
+    }
+}
+
+function private_splitString(strVal, areaWidth, ctx) {
+    let rst = [];
+    if (strVal) {
+        let preSIdx = 0, txtWidth = 0;
+        let currentW = 0;
+        let chnW = ctx.measureText('一').width, otherW = ctx.measureText('_').width;
+        for (let sIdx = 0; sIdx < strVal.length; sIdx++) {
+            currentW = (strVal.charCodeAt(sIdx) > 127)?chnW:otherW;
+            txtWidth += currentW;
+            if (txtWidth > areaWidth) {
+                if (preSIdx < sIdx) {
+                    rst.push(strVal.substr(preSIdx, sIdx - preSIdx));
+                    preSIdx = sIdx;
+                } else {
+                    rst.push(strVal.substr(preSIdx, 1));
+                    preSIdx = sIdx + 1;
+                }
+                txtWidth = currentW;
+            }
+            if (sIdx === strVal.length - 1) {
+                rst.push(strVal.substr(preSIdx, strVal.length - preSIdx));
+            }
+        }
+    }
+    if (rst.length === 0) rst.push(''); //什么都没有,也得整个空串
+    return rst;
+}
+
+
+function getPixelSize(pagesData) {
+    let rst = [793,1122];
+    let SCREEN_DPI = [96,96];
+    if (pagesData[JV.NODE_PAGE_INFO] && pagesData[JV.NODE_PAGE_INFO][JV.NODE_PAGE_SIZE]) {
+        rst[0] = Math.round(SCREEN_DPI[0] * pagesData[JV.NODE_PAGE_INFO][JV.NODE_PAGE_SIZE][0]);
+        rst[1] = Math.round(SCREEN_DPI[1] * pagesData[JV.NODE_PAGE_INFO][JV.NODE_PAGE_SIZE][1]);
+    }
+    return rst;
+}

+ 35 - 0
app/reports/public/fsUtil.js

@@ -0,0 +1,35 @@
+/**
+ * Created by Tony on 2017/4/10.
+ */
+
+let fs = require('fs');
+
+module.exports = {
+    writeArrayToFile: function(arr, filePath) {
+        if (arr && filePath && Array.isArray(arr)) {
+            let chunks = [], len = 0;
+            for (let i = 0; i < arr.length; i++) {
+                let buffer = new Buffer(arr[i]);
+                chunks.push(buffer);
+                len += buffer.length;
+                //
+            }
+            let resultBuffer = new Buffer(len);
+            for(let i=0,size=chunks.length,pos=0;i<size;i++){
+                chunks[i].copy(resultBuffer,pos);
+                pos += chunks[i].length;
+            }
+            fs.writeFile(filePath, resultBuffer, function(err){
+                if(err) throw err;
+                //console.log('Write file: ' + filePath + ' ok!');
+            });
+        }
+    },
+    writeObjToFile: function(obj, filePath) {
+        if (obj) {
+            let arr = [];
+            arr.push(JSON.stringify(obj));
+            this.writeArrayToFile(arr, filePath);
+        }
+    }
+};

+ 208 - 0
app/reports/public/scMathUtil.js

@@ -0,0 +1,208 @@
+/**
+ * Created by Tony on 2017/4/14.
+ */
+
+exports.getUtil = {
+    innerRoundTo: function(num, digit){
+        let lFactor = Math.pow(10, digit);
+        let fOffSet = 0.5;
+        let sign = '';
+        // 处理符号
+        if (num < 0){
+            sign = '-';
+            num = Math.abs(num);
+        }
+        // 计算
+        let result = Math.floor((num / lFactor) + fOffSet).toString();
+        let iLength = result.length;
+        // 因为数值被转为整数计算,当目标位数在小数点后,且数值小于0,需要补齐前面的位数
+        if (iLength < -digit){
+            result = this.zeroString(-digit) + result;
+        }
+        // 当目标位数在小数点前,需要补齐后面的位数
+        else if ((digit > 0) && (iLength < digit)){
+            result = result + this.zeroString(digit);
+        }
+        iLength = result.length;
+        // 获得小数点前的数字
+        let r1 = result.substring(0, iLength + digit);
+        // 获得小数点后的数字
+        let r2 = result.substring(iLength + digit, iLength);
+        // 拼出完整结果
+        return Number(sign + r1 + '.' + r2);
+    },
+    // 原来直接用num.toString(2),如果小数段最后位数是0,会被舍掉,导致进位计算bug
+    // 改为自己计算二进制,固定为53位。
+    // 经验证速度没有差别
+    // 另:经手工验证,用num.toString(2)将十进制浮点数转换为二进制浮点数时,最后一位有错误的情况出现,例子(10.0311)
+    floatToBin: function(num) {
+        let sign = '';
+        let dNum = num;
+        // 符号位
+        if (num < 0) {
+            sign = '-';
+            dNum = -num;
+        };
+        // 解析整数段
+        let iNum = Math.floor(dNum);
+        let iFactor;
+        let sResult1 = '';
+        // 计算二进制整数段
+        while (iNum > 0){
+            iFactor = iNum % 2;
+            iNum = Math.floor(iNum / 2);
+            sResult1 = iFactor + sResult1;
+        }
+        // 判断是否有整数段
+        let bIntZero = sResult1 === '';
+        if (bIntZero){
+            sResult1 = '0';
+        }
+        // 解析小数段
+        let fNum = dNum - Math.floor(dNum);
+        let sResult2 = '';
+        if (fNum > 0){
+            // 双精度浮点数,尾数总长52位,因为第一位总是1,存储时已经被隐藏,所以有效位数为53位
+            // 由于js未对浮点数做优化,所以在有运算的情况下,误差会被放大,因此放弃一位有效位数来消除误差,二进制有效位数50位,十进制有效位数15位
+            const floatLength = 50;
+
+            let iLength;
+            // js的bug,浮点数直接取小数可能不能获得精确值,只有转成字符串,截取字符串中的小数段
+            let sNum = dNum.toString(10);
+            let iDot = sNum.indexOf('.');
+            sNum = '0' + sNum.substring(iDot, sNum.length);
+            fNum = Number(sNum);
+            // 有整数段,则小数位数为全部位数-整数位数
+            if (!bIntZero){
+                iLength = floatLength - sResult1.length;
+            }
+            else{
+                iLength = floatLength;
+            }
+            // 计算二进制小数段
+            while (iLength > 0){
+                fNum = fNum * 2;
+                iFactor = Math.floor(fNum);
+                fNum = fNum % 1;
+                sResult2 = sResult2 + iFactor;
+                if (iFactor > 0){
+                    bIntZero = false;
+                }
+                if (bIntZero && (iFactor === 0)){
+                    continue;
+                }
+                iLength--;
+            }
+        }
+        return sign + sResult1 + '.' + sResult2;
+    },
+    binToFloat: function(bin) {
+        let result = 0;
+        let iLength = bin.length;
+        let sign = '';
+        if (iLength > 0 && bin[0]==='-'){
+            sign = '-';
+            bin = bin.substring(1, iLength);
+        }
+        iLength = bin.length;
+        let iDot = bin.indexOf('.');
+        if (iDot >= 0) {
+            for (let i = 0; i < iLength; i++) {
+                let num = Number(bin[i]);
+                let idx = iDot - i;
+                if (idx === 0) {
+                    continue
+                };
+                if (idx > 0) {
+                    idx -= 1
+                };
+                let r = Math.pow(2, idx);
+                result += num * r;
+            }
+        }
+        else {
+            result = parseInt(bin, 2);
+        };
+        return sign + result;
+    },
+    zeroString: function(length){
+        let result = '';
+        for (let i = 0; i < length; i++){
+            result = result + '0';
+        };
+        return result;
+    },
+    incMantissa: function(bin){
+        let result = bin;
+        let iDot = bin.indexOf('.');
+        if (iDot < 0){return result};
+        let iLength = bin.length;
+        iLength = bin.length;
+        for (let i = iLength - 1; i > iDot; i--){
+            let num = Number(bin[i]);
+            if (num === 0){
+                num = 1;
+                let bin1 = bin.substring(0, i);
+                let bin2 = this.zeroString(iLength - (i + 1));//bin.substring(i + 1, iLength);
+                result = bin1 + num.toString() + bin2;
+                break;
+            }
+
+        };
+        return result;
+    },
+
+    roundTo: function(num, digit){
+        let me = this;
+        return me.innerRoundTo(me.binToFloat(me.incMantissa(me.floatToBin(num))), digit);
+    },
+    isNumber : function (obj) {
+        return obj === +obj;
+    },
+    roundForObj:function(obj,decimal){
+        let me = this;
+        let value;
+        if(me.isNumber(obj)){
+            value = me.roundTo(obj,-decimal)
+        }else {
+            value = me.roundTo(Number(obj),-decimal);
+        }
+        return value
+    },
+    roundToString:function(obj,decimal){
+        let me = this;
+        let value;
+        if(me.isNumber(obj)){
+            value = me.roundTo(obj,-decimal)
+        }else {
+            value = me.roundTo(Number(obj),-decimal);
+        }
+        return value.toFixed(decimal);
+    },
+    isNumOrFormula:function (text) {
+        let value = Number(text);
+        if(value ==0 ) return value;
+        if (!value) {
+            try {
+                let exp = new Expression('');
+                exp.Expression(text);
+                value = Number(exp.Evaluate());
+            } catch (error) {
+                value = null;
+            }
+        }
+        if(text==null||text==""){
+            value = null;
+        }
+        return value;
+    },
+    isDef:function (v) {
+        return v !== undefined && v !== null;
+    },
+    //获取ID引用
+    getFIDArr: function (exp) {
+        let fidRex = /@[\d,a-z,A-Z,-]{36}/g;
+        let fidArr = exp.match(fidRex);
+        return this.isDef(fidArr) ? fidArr : [];
+    }
+};

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 284 - 0
app/reports/public/stringUtil.js


+ 165 - 0
app/reports/public/treeUtil.js

@@ -0,0 +1,165 @@
+/**
+ * Created by Tony on 2017/3/14.
+ */
+
+// import fs from 'fs';
+//
+// function getTreeNodeUtil() {
+//     let tmpRst = null;
+//     let data = fs.readFileSync(__dirname + '/web/treeDataHelper.js', 'utf8', 'r');
+//     //console.log(data);
+//     eval(data + ' ; tmpRst = tree_Data_Helper; ');
+//     return tmpRst;
+// }
+//
+// const treeNodeUtil = getTreeNodeUtil();
+
+
+const NODE_ID = "ID", P_ID = "ParentID", NEXT_ID = "NextSiblingID", ADHOC_PRE_ID="Previous_ID", CHILDREN_NODE = "items", SUB_ID = "sub_ids",
+    EMPTY_ID_VAL = -1, TREE_LEVEL = 'treeLevel', TOP_BILL_ID = "topBillID", TREE_FLAT_SERIAL_ORDER = "treeFlatSerialOrder";
+
+let tree_Data_Helper = {
+    buildTreeNodeDirectly: function(data, addLevel) {
+        let topArr = [], rst = [], tmpNodes = {}, prefix = "id_";
+        let private_getStartNode = function (idArr) {
+            let tmpNodeRst = null;
+            for (let i = 0; i < idArr.length; i++) {
+                if (parseInt(tmpNodes[prefix + idArr[i]][ADHOC_PRE_ID]) === EMPTY_ID_VAL) {
+                    tmpNodeRst = tmpNodes[prefix + idArr[i]];
+                    break;
+                }
+            }
+            return tmpNodeRst;
+        };
+        let private_buildNodeData = function(parentItem, idArr, treeLevel, tbID) {
+            let iter = [], nextNode = private_getStartNode(idArr), pushedIds = [];
+            while (nextNode !== null && nextNode !== undefined ) {
+                if (parentItem) {
+                    parentItem[CHILDREN_NODE].push(nextNode);
+                } else {
+                    rst.push(nextNode);
+                }
+                iter.push(nextNode);
+                pushedIds.push(nextNode[NODE_ID]);
+                nextNode[TOP_BILL_ID] = tbID;
+                if (parentItem === null) {
+                    nextNode[TOP_BILL_ID] = nextNode[NODE_ID];
+                    if (nextNode.flags && nextNode.flags.length > 0) {
+                        for (let flag of nextNode.flags) {
+                            if (flag.fieldName === "fixed") {
+                                nextNode[TOP_BILL_ID] = flag.flag;
+                                break;
+                            }
+                        }
+                    }
+                }
+                if (addLevel) nextNode[TREE_LEVEL] = treeLevel;
+                nextNode = tmpNodes[prefix + nextNode[NEXT_ID]];
+                if (nextNode === null || nextNode === undefined) {
+                    //备注: 考虑到实际数据的健壮性,有些节点会掉链子,需要用 parentItem[SUB_ID] 比对已经加上的节点,如发现加上的节点数量不够,那就得在这里补充上去
+                    if (parentItem) {
+                        if (parentItem[SUB_ID].length > iter.length) {
+                            for (let subId of parentItem[SUB_ID]) {
+                                if (pushedIds.indexOf(subId) < 0) {
+                                    let restNode = tmpNodes[prefix + subId];
+                                    if (addLevel) restNode[TREE_LEVEL] = treeLevel;
+                                    restNode[TOP_BILL_ID] = tbID;
+                                    parentItem[CHILDREN_NODE].push(restNode);
+                                    iter.push(restNode);
+                                }
+                            }
+                        }
+                    } else {
+                        if (idArr.length > iter.length) {
+                            for (let topId of idArr) {
+                                if (pushedIds.indexOf(topId) < 0) {
+                                    let restNode = tmpNodes[prefix + topId];
+                                    if (addLevel) restNode[TREE_LEVEL] = treeLevel;
+                                    restNode[TOP_BILL_ID] = restNode[NODE_ID];
+                                    if (restNode.flags && restNode.flags.length > 0) {
+                                        for (let flag of restNode.flags) {
+                                            if (flag.fieldName === "fixed") {
+                                                restNode[TOP_BILL_ID] = flag.flag;
+                                                break;
+                                            }
+                                        }
+                                    }
+                                    rst.push(restNode);
+                                    iter.push(restNode);
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+            pushedIds = [];
+            for (let i = 0; i < iter.length; i++) {
+                let rtbID = tbID;
+                if (parentItem === null) {
+                    rtbID = iter[i][TOP_BILL_ID];
+                }
+                private_buildNodeData(iter[i], iter[i][SUB_ID], (treeLevel + 1), rtbID);
+            }
+        };
+        //1. 给每个节点设置key, 顺便找Top Node
+        for (let i = 0; i < data.length; i++) {
+            tmpNodes[prefix + data[i][NODE_ID]] = data[i];
+            data[i][ADHOC_PRE_ID] = EMPTY_ID_VAL;
+            data[i][SUB_ID] = [];
+            data[i][CHILDREN_NODE] = [];
+            if (parseInt(data[i][P_ID]) === EMPTY_ID_VAL) {
+                topArr.push(data[i][NODE_ID]);
+            }
+        }
+        //2. 通过key,设置好兄弟/父子关系
+        for (let psi = 0; psi < data.length; psi++) {
+            if (parseInt(data[psi][NEXT_ID]) !== EMPTY_ID_VAL) {
+                if (tmpNodes[prefix + data[psi][NEXT_ID]] !== undefined){
+                    if (tmpNodes[prefix + data[psi][NEXT_ID]][P_ID] === data[psi][P_ID]) {
+                        tmpNodes[prefix + data[psi][NEXT_ID]][ADHOC_PRE_ID] = data[psi][NODE_ID];
+                    } else {
+                        tmpNodes[prefix + data[psi][NEXT_ID]][ADHOC_PRE_ID] = EMPTY_ID_VAL;
+                        data[psi][NEXT_ID] = EMPTY_ID_VAL;
+                    }
+                }
+            }
+            if (parseInt(data[psi][P_ID]) !== EMPTY_ID_VAL) {
+                tmpNodes[prefix + data[psi][P_ID]][SUB_ID].push(data[psi][NODE_ID]);
+            }
+        }
+        //3. 开build
+        private_buildNodeData(null, topArr, 0, -1);
+        //try to release and return
+        tmpNodes = null;
+        topArr.length = 0;
+        return rst;
+    },
+
+    getFlatArray: function (srcArr, destArr, addSerialOrder) {
+        let serialStartOrder = 0;
+        let private_put = function (parentItem) {
+            if (addSerialOrder) {
+                parentItem[TREE_FLAT_SERIAL_ORDER] = serialStartOrder;
+                serialStartOrder++;
+                //说明:当清单通过树排序后,为了后续的排序方便,有必要加这个序号,并作为指标提供
+            }
+            destArr.push(parentItem);
+            if (parentItem.items) {
+                for (let subItem of parentItem.items) {
+                    private_put(subItem);
+                }
+            }
+        }
+        for (let node of srcArr) {
+            private_put(node);
+        }
+        for (let item of destArr) {
+            delete item[CHILDREN_NODE];
+            delete item[SUB_ID];
+            delete item[ADHOC_PRE_ID];
+        }
+    }
+};
+
+
+module.exports = tree_Data_Helper;

+ 87 - 0
app/reports/rpt_component/helper/jpc_helper_area.js

@@ -0,0 +1,87 @@
+let JV = require('../jpc_value_define');
+
+let JpcAreaHelper = {
+    outputArea: function(areaNode, band, unitFactor, rowAmount, rowIdx, colAmount, colIdx, multipleDispCol, multipleColIdx,syncHeight, syncWidth) {
+        let rst = {}, maxMultiColumns = 3;
+        if (multipleDispCol > 0 && multipleDispCol <= maxMultiColumns) {
+            //1. calculate left/right
+            let areaWidth = 1.0 * (band[JV.PROP_RIGHT] - band[JV.PROP_LEFT]) / multipleDispCol;
+            areaWidth = areaWidth / colAmount;
+            let innerLeft = 0.0, innerRight = areaWidth;
+            //
+            if (typeof areaNode[JV.PROP_H_CALCULATION]  === "string") {
+                switch (areaNode[JV.PROP_H_CALCULATION]) {
+                    case JV.CAL_TYPE[JV.CAL_TYPE_PERCENTAGE] :
+                        innerLeft = (1.0 * areaNode[JV.PROP_LEFT] * areaWidth / JV.HUNDRED_PERCENT);
+                        innerRight = (1.0 * areaNode[JV.PROP_RIGHT] * areaWidth / JV.HUNDRED_PERCENT);
+                        break;
+                    case JV.CAL_TYPE[JV.CAL_TYPE_ABSTRACT] :
+                        innerLeft = 1.0 * areaNode[JV.PROP_LEFT] * unitFactor;
+                        innerRight = 1.0 * areaNode[JV.PROP_RIGHT] * unitFactor;
+                        break;
+                }
+            } else {
+                //颗粒度更加细化的控制,可能左右两边的计算坐标方式都不同
+                if (areaNode[JV.PROP_H_CALCULATION][JV.PROP_LEFT] === JV.CAL_TYPE[JV.CAL_TYPE_PERCENTAGE]) {
+                    innerLeft = (1.0 * areaNode[JV.PROP_LEFT] * areaWidth / JV.HUNDRED_PERCENT);
+                } else {
+                    innerLeft = 1.0 * areaNode[JV.PROP_LEFT] * unitFactor;
+                }
+                if (areaNode[JV.PROP_H_CALCULATION][JV.PROP_RIGHT] === JV.CAL_TYPE[JV.CAL_TYPE_PERCENTAGE]) {
+                    innerRight = (1.0 * areaNode[JV.PROP_RIGHT] * areaWidth / JV.HUNDRED_PERCENT);
+                } else {
+                    innerRight = 1.0 * areaNode[JV.PROP_RIGHT] * unitFactor;
+                }
+            }
+            //2. calculate top/bottom
+            let  areaHeight = 1.0 * (band[JV.PROP_BOTTOM] - band[JV.PROP_TOP]);
+            areaHeight = areaHeight / rowAmount;
+            let innerTop = 0.0, innerBottom = areaHeight;
+            if (typeof areaNode[JV.PROP_V_CALCULATION]  === "string") {
+                switch (areaNode[JV.PROP_V_CALCULATION]) {
+                    case JV.CAL_TYPE[JV.CAL_TYPE_PERCENTAGE] :
+                        innerTop = (1.0 * areaNode[JV.PROP_TOP] * areaHeight / JV.HUNDRED_PERCENT);
+                        innerBottom = (1.0 * areaNode[JV.PROP_BOTTOM] * areaHeight / JV.HUNDRED_PERCENT);
+                        break;
+                    case JV.CAL_TYPE[JV.CAL_TYPE_ABSTRACT] :
+                        innerTop = 1.0 * areaNode[JV.PROP_TOP] * unitFactor;
+                        innerBottom = 1.0 * areaNode[JV.PROP_BOTTOM] * unitFactor;
+                        break;
+                }
+            } else {
+                //颗粒度更加细化的控制,可能上下两边的计算坐标方式都不同
+                if (areaNode[JV.PROP_V_CALCULATION][JV.PROP_TOP] === JV.CAL_TYPE[JV.CAL_TYPE_PERCENTAGE]) {
+                    innerTop = (1.0 * areaNode[JV.PROP_TOP] * areaHeight / JV.HUNDRED_PERCENT);
+                } else {
+                    innerTop = 1.0 * areaNode[JV.PROP_TOP] * unitFactor;
+                }
+                if (areaNode[JV.PROP_V_CALCULATION][JV.PROP_BOTTOM] === JV.CAL_TYPE[JV.CAL_TYPE_PERCENTAGE]) {
+                    innerBottom = (1.0 * areaNode[JV.PROP_BOTTOM] * areaHeight / JV.HUNDRED_PERCENT);
+                } else {
+                    innerBottom = 1.0 * areaNode[JV.PROP_BOTTOM] * unitFactor;
+                }
+            }
+            //
+            let rstLeft = 0.0, rstRight = 0.0, rstTop = 0.0, rstBottom = 0.0;
+            if (syncHeight) {
+                rstBottom = Math.round(1.0 * band[JV.PROP_TOP] + areaHeight * (rowIdx + 1) + innerTop);
+            } else {
+                rstBottom = Math.round(1.0 * band[JV.PROP_TOP] + areaHeight * rowIdx + innerBottom);
+            }
+            if (syncWidth) {
+                rstRight = Math.round(1.0 * band[JV.PROP_LEFT] + areaWidth * (colIdx + 1) + innerLeft + multipleColIdx * areaWidth);
+            } else {
+                rstRight = Math.round(1.0 * band[JV.PROP_LEFT] + areaWidth * colIdx + innerRight + multipleColIdx * areaWidth);
+            }
+            rstLeft = Math.round(1.0 * band[JV.PROP_LEFT] + areaWidth * colIdx + innerLeft + multipleColIdx * areaWidth);
+            rstTop = Math.round(1.0 * band[JV.PROP_TOP] + areaHeight * rowIdx + innerTop);
+            rst[JV.PROP_LEFT] = rstLeft;
+            rst[JV.PROP_RIGHT] = rstRight;
+            rst[JV.PROP_TOP] = rstTop;
+            rst[JV.PROP_BOTTOM] = rstBottom;
+        }
+        return rst;
+    }
+};
+
+module.exports = JpcAreaHelper;

+ 121 - 0
app/reports/rpt_component/helper/jpc_helper_band.js

@@ -0,0 +1,121 @@
+let JV = require('../jpc_value_define');
+let JpcCommonHelper = require('./jpc_helper_common');
+
+let JpcBandHelper = {
+    getBandTypeValByString: function(bandType) {
+        let rst = JV.PAGE_STATUS.indexOf(bandType);
+        if (rst < 0) rst = JV.STATUS_NORMAL;
+        return rst;
+    },
+    setBandArea: function(bands, rptTpl, pageStatus, isOnlyNormalStatus, isOnlyExStatus) {
+        let me = this;
+        if (rptTpl[JV.NODE_BAND_COLLECTION]) {
+            isOnlyNormalStatus = isOnlyNormalStatus||false;
+            isOnlyExStatus = isOnlyExStatus||false;
+
+            let unitFactor = JpcCommonHelper.getUnitFactor(rptTpl);
+            let orgArea = JpcCommonHelper.getReportArea(rptTpl, unitFactor);
+            for (let i = 0; i < rptTpl[JV.NODE_BAND_COLLECTION].length; i++) {
+                me.setBandPos(bands, rptTpl[JV.NODE_BAND_COLLECTION][i], orgArea, unitFactor, pageStatus, isOnlyNormalStatus, isOnlyExStatus);
+            }
+        }
+    },
+    setBandPos: function(bands, bandNode, orgArea, unitFactor, pageStatus, isOnlyNormalStatus, isOnlyExStatus) {
+        let me = this, band = bands[bandNode[JV.BAND_PROP_NAME]];
+        //0. for multi flow purpose
+        if (isOnlyNormalStatus) {
+            if (bandNode.hasOwnProperty(JV.PROP_BAND_EX_ONLY) && JpcCommonHelper.getBoolean(bandNode[JV.PROP_BAND_EX_ONLY])) {
+                return;
+            }
+        }
+        if (isOnlyExStatus) {
+            if (bandNode.hasOwnProperty(JV.PROP_BAND_NORMAL_ONLY) && !(JpcCommonHelper.getBoolean(bandNode[JV.PROP_BAND_NORMAL_ONLY]))) {
+                return;
+            }
+        }
+        //1. initialize
+        band[JV.PROP_LEFT] = orgArea[JV.IDX_LEFT];
+        band[JV.PROP_TOP] = orgArea[JV.IDX_TOP];
+        band[JV.PROP_RIGHT] = orgArea[JV.IDX_RIGHT];
+        band[JV.PROP_BOTTOM] = orgArea[JV.IDX_BOTTOM];
+        //2. set this band
+        if (pageStatus[band[JV.BAND_PROP_DISPLAY_TYPE]]) {
+            switch (band[JV.BAND_PROP_ALIGNMENT]) {
+                case JV.LAYOUT_TOP:
+                    if (band[JV.PROP_CALCULATION] === JV.CAL_TYPE_ABSTRACT) {
+                        band.Bottom = band.Top + unitFactor * band[JV.BAND_PROP_HEIGHT];
+                    } else {
+                        band.Bottom = band.Top + (band.Bottom - band.Top) * band[JV.BAND_PROP_HEIGHT] / 100;
+                    }
+                    orgArea[JV.IDX_TOP] = band.Bottom;
+                    break;
+                case JV.LAYOUT_BOTTOM:
+                    if (band[JV.PROP_CALCULATION] === JV.CAL_TYPE_ABSTRACT) {
+                        band.Top = band.Bottom - unitFactor * band[JV.BAND_PROP_HEIGHT];
+                    } else {
+                        band.Top = band.Bottom - (band.Bottom - band.Top) * band[JV.BAND_PROP_HEIGHT] / 100;
+                    }
+                    orgArea[JV.IDX_BOTTOM] = band.Top;
+                    break;
+                case JV.LAYOUT_LEFT:
+                    if (band[JV.PROP_CALCULATION] === JV.CAL_TYPE_ABSTRACT) {
+                        band.Right = band.Left + unitFactor * band[JV.BAND_PROP_WIDTH];
+                    } else {
+                        band.Right = band.Left + (band.Right - band.Left) * band[JV.BAND_PROP_WIDTH] / 100;
+                    }
+                    orgArea[JV.IDX_LEFT] = band.Right;
+                    break;
+                case JV.LAYOUT_RIGHT:
+                    if (band[JV.PROP_CALCULATION] === JV.CAL_TYPE_ABSTRACT) {
+                        band.Left = band.Right - unitFactor * band[JV.BAND_PROP_WIDTH];
+                    } else {
+                        band.Left = band.Right - (band.Right - band.Left) * band[JV.BAND_PROP_WIDTH] / 100;
+                    }
+                    orgArea[JV.IDX_RIGHT] = band.Left;
+                    break;
+            }
+            //3. set sub-bands
+            if (bandNode[JV.BAND_PROP_SUB_BANDS]) {
+                let bandArea = [band.Left, band.Top, band.Right, band.Bottom];
+                for (let i = 0; i < bandNode[JV.BAND_PROP_SUB_BANDS].length; i++) {
+                    me.setBandPos(bands, bandNode[JV.BAND_PROP_SUB_BANDS][i], bandArea, unitFactor, pageStatus, isOnlyNormalStatus, isOnlyExStatus);
+                }
+            }
+        }
+    },
+    resetBandPos: function (bandCollection, bands, contentBand, offsetX, offsetY) {
+        let orgX = contentBand.Right, orgY = contentBand.Bottom;
+        function chkAndResetPos(targetBand) {
+            let band = bands[targetBand.Name];
+            if (band) {
+                if (band === contentBand) {
+                    band.Bottom += offsetY;
+                    band.Right += offsetX;
+                } else {
+                    if (band.Top >= orgY) {
+                        band.Top += offsetY;
+                        band.Bottom += offsetY;
+                    } else if (band.Bottom >= orgY && band.Top < orgY) {
+                        band.Bottom += offsetY;
+                    }
+                    if (band.Left >= orgX) {
+                        band.Left += offsetX;
+                        band.Right += offsetX;
+                    } else if (band.Right >= orgX && band.Le < orgX) {
+                        band.Right += offsetX;
+                    }
+                }
+                if (targetBand[JV.BAND_PROP_SUB_BANDS]) {
+                    for (let i = 0; i < targetBand[JV.BAND_PROP_SUB_BANDS].length; i++) {
+                        chkAndResetPos(targetBand[JV.BAND_PROP_SUB_BANDS][i]);
+                    }
+                }
+            }
+        }
+        for (let i = 0; i < bandCollection.length; i++) {
+            chkAndResetPos(bandCollection[i]);
+        }
+    }
+};
+
+module.exports = JpcBandHelper;

+ 200 - 0
app/reports/rpt_component/helper/jpc_helper_common.js

@@ -0,0 +1,200 @@
+'use strict';
+
+const JV = require('../jpc_value_define');
+
+const JpcCommonHelper = {
+    commonConstant: {},
+    getResultByID: function(KeyID, collectionList) {
+        let rst = null;
+        if (KeyID) {
+            for (let i = 0; i < collectionList.length; i++) {
+                const collection = collectionList[i];
+                if (collection && collection instanceof Array) {
+                    for (let j = 0; j < collection.length; j++) {
+                        if (collection[j][JV.PROP_ID] === KeyID) {
+                            rst = collection[j];
+                            break;
+                        }
+                    }
+                    if (rst) break;
+                }
+            }
+        }
+        return rst;
+    },
+    getFont: function(fontName, dftFonts, rptTpl) {
+        const me = this;
+        const list = [];
+        if (rptTpl) list.push(rptTpl[JV.NODE_FONT_COLLECTION]);
+        list.push(dftFonts);
+        return me.getResultByID(fontName, list);
+    },
+    getStyle: function(styleName, dftStyles, rptTpl) {
+        const me = this;
+        const list = [];
+        if (rptTpl) list.push(rptTpl[JV.NODE_STYLE_COLLECTION]);
+        list.push(dftStyles);
+        return me.getResultByID(styleName, list);
+    },
+    getControl: function(controlName, dftControls, rptTpl) {
+        const me = this;
+        const list = [];
+        if (rptTpl) list.push(rptTpl[JV.NODE_CONTROL_COLLECTION]);
+        list.push(dftControls);
+        return me.getResultByID(controlName, list);
+    },
+    getLayoutAlignment: function(alignStr) {
+        let rst = JV.LAYOUT.indexOf(alignStr);
+        if (rst < 0) rst = JV.LAYOUT_FULFILL;
+        return rst;
+    },
+    getPosCalculationType: function (typeStr) {
+        let rst = JV.CAL_TYPE.indexOf(typeStr);
+        if (rst < 0) rst = JV.CAL_TYPE_ABSTRACT;
+        return rst;
+    },
+    getBoolean: function(bStr) {
+        let rst = false;
+        if (bStr !== null && bStr !== undefined) {
+            const valType = typeof bStr;
+            if (valType === 'boolean') {
+                rst = bStr;
+            } else if (valType === 'string') {
+                const tS = bStr.toUpperCase();
+                rst = (tS === 'T' || tS === 'TRUE' || tS === 'YES' || tS === 'Y');
+            } else if (valType === 'number') {
+                rst = (bStr === 1);
+            }
+        }
+        return rst;
+    },
+    getScreenDPI: function() {
+        const me = this;
+        let arrDPI = [];
+        if (!me.commonConstant.resolution) {
+            arrDPI = [96,96];
+            // arrDPI = [100,100];
+            me.commonConstant.resolution = arrDPI;
+        } else {
+            arrDPI = me.commonConstant.resolution;
+        }
+        return arrDPI;
+    },
+    getUnitFactor: function(rptTpl) {
+        const me = this;
+        return me.translateUnit(rptTpl[JV.NODE_MAIN_INFO][JV.PROP_UNITS]);
+    },
+    translateUnit: function(unitStr) {
+        const me = this;
+        let rst = 1.0;
+        if (unitStr) {
+            const resolution = me.getScreenDPI();
+            if (JV.MEASUREMENT.PIXEL.indexOf(unitStr) >= 0) {
+                rst = 1.0;
+            } else if (JV.MEASUREMENT.CM.indexOf(unitStr) >= 0) {
+                rst = 1.0 * resolution[0] / 2.54;
+            } else if (JV.MEASUREMENT.INCH.indexOf(unitStr) >= 0) {
+                rst = 1.0 * resolution[0];
+            }
+        }
+        return rst;
+    },
+    getPageSize: function(rptTpl) {
+        let size = null;
+        const sizeStr = rptTpl[JV.NODE_MAIN_INFO][JV.NODE_PAGE_INFO][JV.PROP_PAGE_SIZE];
+        const sizeIdx = JV.PAGES_SIZE_STR.indexOf(sizeStr);
+        if (sizeIdx >= 0) {
+            size = JV.PAGES_SIZE[sizeIdx].slice(0);
+        } else if (sizeStr === JV.PAGE_SELF_DEFINE) {
+            //
+        } else {
+            size = JV.SIZE_A4.slice(0);
+        }
+        const page_orientation = rptTpl[JV.NODE_MAIN_INFO][JV.NODE_PAGE_INFO][JV.PROP_ORIENTATION];
+        if (page_orientation === JV.ORIENTATION_LANDSCAPE || page_orientation === JV.ORIENTATION_LANDSCAPE_CHN) {
+            //swap x,y
+            const tmp = size[0];
+            size[0] = size[1];
+            size[1] = tmp;
+        }
+        return size;
+    },
+    getReportArea: function(rptTpl, unitFactor) {
+        const me = this;
+        const resolution = me.getScreenDPI();
+        const rst = [];
+        const size = me.getPageSize(rptTpl);
+        size[0] = resolution[0] * size[0];
+        size[1] = resolution[0] * size[1];
+        rst.push(unitFactor * rptTpl[JV.NODE_MAIN_INFO][JV.NODE_MARGINS][JV.PROP_LEFT]);
+        rst.push(unitFactor * rptTpl[JV.NODE_MAIN_INFO][JV.NODE_MARGINS][JV.PROP_TOP]);
+        rst.push(size[0] - unitFactor * rptTpl[JV.NODE_MAIN_INFO][JV.NODE_MARGINS][JV.PROP_RIGHT]);
+        rst.push(size[1] - unitFactor * rptTpl[JV.NODE_MAIN_INFO][JV.NODE_MARGINS][JV.PROP_BOTTOM]);
+        return rst;
+    },
+    getSegIdxByPageIdx: function(page, page_seg_map) {
+        let rst = -1;
+        for (let pIdx = 0; pIdx < page_seg_map.length; pIdx++) {
+            if (page_seg_map[pIdx][0] === page) {
+                rst = page_seg_map[pIdx][1];
+                break;
+            }
+        }
+        return rst;
+    },
+    getStringLinesInArea: function(area, strVal, pdfDoc, chnW, otherW) {
+        // 备注: 因后台的pdf kit判断字符串长度与前端的不一样,需要做些调整,不一次性地判断字符串长度。
+        //      分2种字符:中文与非中文,按照各种字符的数量分别乘以相关一个字符的宽度再累计。
+        //      另判断行数还不能直接用总长度除以宽度来计算,因每一行都会有不同的余量,所以得一行行走过来判断。
+        let rst = 0;
+        if (strVal) {
+            const areaWidth = area[JV.PROP_RIGHT] - area[JV.PROP_LEFT] - JV.OUTPUT_OFFSET[JV.OFFSET_IDX_RIGHT] - JV.OUTPUT_OFFSET[JV.OFFSET_IDX_LEFT] - 1;
+            let txtWidth = 0;
+            let currentW = 0;
+            for (let sIdx = 0; sIdx < strVal.length; sIdx++) {
+                currentW = (strVal.charCodeAt(sIdx) > 127)?chnW:otherW;
+                txtWidth += currentW;
+                if (txtWidth > areaWidth) {
+                    rst++;
+                    txtWidth = currentW;
+                }
+                if (sIdx === strVal.length - 1) {
+                    rst++;
+                }
+            }
+        }
+        if (rst === 0) rst = 1; // 即使是空字符串,也得有一行啊
+        return rst;
+        // 备注: 其实是想用canvas的,但node canvas装起来麻烦,暂时用PDF Kit来顶用一下,以后有更好的再换
+    },
+    splitString: function(area, strVal, pdfDoc, chnW, otherW) {
+        const rst = [];
+        if (strVal) {
+            const areaWidth = area[JV.PROP_RIGHT] - area[JV.PROP_LEFT] - JV.OUTPUT_OFFSET[JV.OFFSET_IDX_RIGHT] - JV.OUTPUT_OFFSET[JV.OFFSET_IDX_LEFT] - 1;
+            let preSIdx = 0;
+            let txtWidth = 0;
+            let currentW = 0;
+            for (let sIdx = 0; sIdx < strVal.length; sIdx++) {
+                currentW = (strVal.charCodeAt(sIdx) > 127) ? chnW : otherW;
+                txtWidth += currentW;
+                if (txtWidth > areaWidth) {
+                    if (preSIdx < sIdx) {
+                        rst.push(strVal.substr(preSIdx, sIdx - preSIdx));
+                        preSIdx = sIdx;
+                    } else {
+                        rst.push(strVal.substr(preSIdx, 1));
+                        preSIdx = sIdx + 1;
+                    }
+                    txtWidth = currentW;
+                }
+                if (sIdx === strVal.length - 1) {
+                    rst.push(strVal.substr(preSIdx, strVal.length - preSIdx));
+                }
+            }
+        }
+        if (rst.length === 0) rst.push(''); // 什么都没有,也得整个空串
+        return rst;
+    },
+};
+
+module.exports = JpcCommonHelper;

+ 83 - 0
app/reports/rpt_component/helper/jpc_helper_common_output.js

@@ -0,0 +1,83 @@
+let JV = require('../jpc_value_define');
+let JpcFieldHelper = require('./jpc_helper_field');
+
+let JpcCommonOutputHelper = {
+    createCommonOutputWithoutDecorate: function (node, value, forceCombine) {
+        let me = this, rst = {};
+        //1. font/style/control
+        rst[JV.PROP_FONT] = node[[JV.PROP_FONT]];
+        rst[JV.PROP_CONTROL] = node[[JV.PROP_CONTROL]];
+        rst[JV.PROP_STYLE] = node[[JV.PROP_STYLE]];
+        //2. value
+        rst[JV.PROP_VALUE] = value;
+        me.formatCell(node[JV.PROP_FORMAT], rst);
+        // innerFormat(node[JV.PROP_FORMAT], rst);
+        if (node[JV.PROP_PREFIX] && rst[JV.PROP_VALUE] !== null) {
+            rst[JV.PROP_VALUE] = node[JV.PROP_PREFIX] + rst[JV.PROP_VALUE];
+        } else if (node[JV.PROP_PREFIX] && forceCombine) {
+            rst[JV.PROP_VALUE] = node[JV.PROP_PREFIX];
+        }
+        if (node[JV.PROP_SUFFIX] && rst[JV.PROP_VALUE] !== null) {
+            rst[JV.PROP_VALUE] = rst[JV.PROP_VALUE] + node[JV.PROP_SUFFIX];
+        } else if (node[JV.PROP_SUFFIX] && forceCombine) {
+            rst[JV.PROP_VALUE] = node[JV.PROP_SUFFIX];
+        }
+        return rst;
+    },
+    createCommonOutput: function (node, value, controls) {
+        let me = this, rst = {};
+        //1. font/style/control
+        rst[JV.PROP_FONT] = node[[JV.PROP_FONT]];
+        rst[JV.PROP_CONTROL] = node[[JV.PROP_CONTROL]];
+        rst[JV.PROP_STYLE] = node[[JV.PROP_STYLE]];
+        //2. value
+        rst[JV.PROP_VALUE] = value;
+        JpcFieldHelper.decorateValue(rst, controls);
+        me.formatCell(node[JV.PROP_FORMAT], rst);
+        // innerFormat(node[JV.PROP_FORMAT], rst);
+        if (node[JV.PROP_PREFIX] && rst[JV.PROP_VALUE] !== null && rst[JV.PROP_VALUE] !== "") {
+            rst[JV.PROP_VALUE] = node[JV.PROP_PREFIX] + rst[JV.PROP_VALUE];
+        }
+        if (node[JV.PROP_SUFFIX] && rst[JV.PROP_VALUE] !== null && rst[JV.PROP_VALUE] !== "") {
+            rst[JV.PROP_VALUE] = rst[JV.PROP_VALUE] + node[JV.PROP_SUFFIX];
+        }
+        return rst;
+    },
+    formatCell: function (formatStr, rstCell) {
+        if (formatStr) {
+            if (!(isNaN(parseFloat(rstCell[JV.PROP_VALUE])))) {
+                let dotIdx = formatStr.indexOf(".");
+                if (dotIdx >= 0) {
+                    let tmpStr = parseFloat(rstCell[JV.PROP_VALUE]).toFixed(formatStr.length - dotIdx - 1);
+                    let digStr = formatStr.substr(dotIdx + 1, formatStr.length - dotIdx);
+                    for (let sIdx = digStr.length - 1; sIdx >= 0; sIdx--) {
+                        if (digStr[sIdx] === '#') {
+                            if (tmpStr.length > 0 && tmpStr[tmpStr.length - 1] === '0') {
+                                tmpStr = tmpStr.substr(0, tmpStr.length - 1);
+                            } else {
+                                break;
+                            }
+                        } else {
+                            break;
+                        }
+                    }
+                    if (tmpStr[tmpStr.length - 1] === '.') tmpStr = tmpStr.substr(0, tmpStr.length - 1);
+                    rstCell[JV.PROP_VALUE] = tmpStr;
+                } else {
+                    rstCell[JV.PROP_VALUE] = parseFloat(rstCell[JV.PROP_VALUE]).toFixed(0);
+                }
+                let commaIdx = formatStr.indexOf(",");
+                if (commaIdx >= 0) {
+                    rstCell[JV.PROP_VALUE] = comdify(rstCell[JV.PROP_VALUE].toString());
+                }
+            }
+        }
+    }
+};
+
+function comdify(numStr){
+    let re = /\d{1,3}(?=(\d{3})+$)/g;
+    return numStr.replace(/^(\d+)((\.\d+)?)$/,function(s,s1,s2){return s1.replace(re,"$&,")+s2;});
+}
+
+module.exports = JpcCommonOutputHelper;

+ 257 - 0
app/reports/rpt_component/helper/jpc_helper_cross_tab.js

@@ -0,0 +1,257 @@
+let JV = require('../jpc_value_define');
+let JE = require('../jpc_rte');
+let JpcCommonHelper = require('./jpc_helper_common');
+
+let JpcCrossTabHelper = {
+    getColIDX: function(cl, val) {
+        let rst = -1;
+        for (let i = 0; i < cl.length; i++) {
+            let ca = cl[i];
+            for (let j = 0; j < ca.length; j++) {
+                if (ca[j] == val) {
+                    rst = i;
+                    break;
+                }
+            }
+            if (rst != -1) {
+                break;
+            }
+        }
+        return rst;
+    },
+    pushToSeg: function(segArr, dataSeq, segIdx, sIdx, eIdx) {
+        let arrIdx = [];
+        for (let k = sIdx; k < eIdx; k++) {
+            arrIdx.push(dataSeq[segIdx][k]);
+        }
+        segArr.push(arrIdx);
+    },
+    sortFieldValue: function(sIDX, eIDX, sortOrder, dataField, dataValSeq) {
+        let tmpSeq = [];
+        if ((sortOrder) && (sortOrder !== JV.TAB_FIELD_PROP_SORT_VAL_NOSORT)) {
+            if (sIDX >= 0 && eIDX >= sIDX && dataValSeq.length > eIDX) {
+                let reversed = 1;
+                if (sortOrder === JV.TAB_FIELD_PROP_SORT_VAL_DESC) {
+                    reversed = -1;
+                }
+                for (let i = sIDX; i <= eIDX; i++) {
+                    tmpSeq.push(dataValSeq[i]);
+                }
+                tmpSeq.sort(function(idx1, idx2) {
+                    let rst = 0;
+                    // if (isNaN(parseFloat(dataField[idx1])) || isNaN(parseFloat(dataField[idx1]))) {
+                    if (typeof (dataField[idx1]) === 'string' || typeof (dataField[idx1]) === 'string' ) {
+                        if (dataField[idx1] > dataField[idx2]) {
+                            rst = reversed;
+                        } else if (dataField[idx1] < dataField[idx2]) {
+                            rst = -reversed;
+                        }
+                    } else {
+                        if ((1.0 * dataField[idx1]) > (1.0 * dataField[idx2])) {
+                            rst = reversed;
+                        } else if ((1.0 * dataField[idx1]) < (1.0 * dataField[idx2])) {
+                            rst = -reversed;
+                        }
+                    }
+                    return rst;
+                });
+            }
+        }
+        if (tmpSeq.length > 0) {
+            for (let i = sIDX; i <= eIDX; i++) {
+                dataValSeq[i] = tmpSeq[i - sIDX];
+            }
+        }
+        return tmpSeq;
+    },
+    checkIfEqual: function(dataFields, seq1, seq2) {
+        let rst = true;
+        for (let i = 0; i < dataFields.length; i++) {
+            if ((dataFields[i][seq1] !== dataFields[i][seq2])) {
+                rst = false;
+                break;
+            }
+        }
+        return rst;
+    },
+    sortTabFields: function(tabFields, fieldSeqs, data_details, dataSeq, $CURRENT_RPT) {
+        let me = this;
+        let sIDX = 0, eIDX = -1, isFirstSort = true;
+        for (let i = 0; i < tabFields.length; i++) {
+            let tabField = tabFields[i];
+            if (tabField[JV.TAB_FIELD_PROP_SORT] !== JV.TAB_FIELD_PROP_SORT_VAL_NOSORT) {
+                if (isFirstSort) {
+                    isFirstSort = false;
+                    //first field, should sort all data items
+                    for (let j = 0; j < dataSeq.length; j++) {
+                        sIDX = 0;
+                        eIDX = dataSeq[j].length - 1;
+                        //sort the field value here
+                        if (typeof(fieldSeqs[i]) === "object") {
+                            let exFirstField = JE.F(fieldSeqs[i][JV.PROP_ID], $CURRENT_RPT);
+                            if (exFirstField) {
+                                me.sortFieldValue(sIDX, eIDX, tabField[JV.TAB_FIELD_PROP_SORT], exFirstField[JV.PROP_AD_HOC_DATA], dataSeq[j]);
+                            } else {
+                                //不排序(健壮性)
+                            }
+                        } else {
+                            me.sortFieldValue(sIDX, eIDX, tabField[JV.TAB_FIELD_PROP_SORT],data_details[fieldSeqs[i]], dataSeq[j]);
+                        }
+                        // me.sortFieldValue(sIDX, eIDX, tabField[JV.TAB_FIELD_PROP_SORT],data_details[fieldSeqs[i]], dataSeq[j]);
+                    }
+                } else {
+                    //then sort the rest fields one by one
+                    for (let j = 0; j < dataSeq.length; j++) {
+                        let chkFields = [];
+                        for (let k = 0; k < i; k++) {
+                            if (typeof(fieldSeqs[k]) === "object") {
+                                let exField = JE.F(fieldSeqs[k][JV.PROP_ID], $CURRENT_RPT);
+                                if (exField) {
+                                    chkFields.push(exField[JV.PROP_AD_HOC_DATA]);
+                                } else {
+                                    chkFields.push(null);
+                                }
+                            } else {
+                                chkFields.push(data_details[fieldSeqs[k]]);
+                            }
+                            // chkFields.push(data_details[fieldSeqs[k]]);
+                        }
+                        sIDX = 0, eIDX = -1;
+                        for (let m = 1; m < dataSeq[j].length; m++) {
+                            if (!(me.checkIfEqual(chkFields, dataSeq[j][m - 1], dataSeq[j][m]))) {
+                                eIDX = m - 1;
+                            } else if (m == dataSeq[j].length - 1) {
+                                eIDX = m;
+                            };
+                            if (eIDX >= sIDX) {
+                                if (eIDX != sIDX) {
+                                    if (typeof(fieldSeqs[i]) === "object") {
+                                        let exOtherField = JE.F(fieldSeqs[i][JV.PROP_ID], $CURRENT_RPT);
+                                        if (exOtherField) {
+                                            me.sortFieldValue(sIDX, eIDX, tabField[JV.TAB_FIELD_PROP_SORT], exOtherField[JV.PROP_AD_HOC_DATA], dataSeq[j]);
+                                        } else {
+                                            //不排序(健壮性)
+                                        }
+                                    } else {
+                                        me.sortFieldValue(sIDX, eIDX, tabField[JV.TAB_FIELD_PROP_SORT],data_details[fieldSeqs[i]], dataSeq[j]);
+                                    }
+                                    // me.sortFieldValue(sIDX, eIDX, tabField[JV.TAB_FIELD_PROP_SORT],data_details[fieldSeqs[i]], dataSeq[j]);
+                                }
+                                sIDX = m;
+                                eIDX = m - 1; //for protection purpose
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    },
+    getMaxRowsPerPage: function(bands, rptTpl) {
+        let rst = 1;
+        let band = bands[rptTpl[JV.NODE_CROSS_INFO][JV.NODE_CROSS_ROW][JV.PROP_BAND_NAME]];
+        if (band) {
+            rst =  getMaxTabCntPerPage(rptTpl, JV.NODE_CROSS_ROW, JV.PROP_CMN_HEIGHT, band.Bottom - band.Top);
+        }
+        return rst;
+    },
+    getMaxColsPerPage: function(bands, rptTpl) {
+        let rst = 1;
+        let band = bands[rptTpl[JV.NODE_CROSS_INFO][JV.NODE_CROSS_COL][JV.PROP_BAND_NAME]];
+        if (band) {
+            rst =  getMaxTabCntPerPage(rptTpl, JV.NODE_CROSS_COL, JV.PROP_CMN_WIDTH, band.Right - band.Left);
+        }
+        return rst;
+    },
+    getActualRowsHeight: function(bands, rptTpl, segments, page) {
+        let rst = 1;
+        let band = bands[rptTpl[JV.NODE_CROSS_INFO][JV.NODE_CROSS_ROW][JV.PROP_BAND_NAME]];
+        if (band) {
+            rst =  getActualContentAreaMeasurement(rptTpl, JV.NODE_CROSS_ROW, JV.PROP_CMN_HEIGHT, band.Bottom - band.Top, segments, page);
+        }
+        return rst;
+    },
+    getActualColsWidth: function(bands, rptTpl, segments, page) {
+        let rst = 1;
+        let band = bands[rptTpl[JV.NODE_CROSS_INFO][JV.NODE_CROSS_COL][JV.PROP_BAND_NAME]];
+        if (band) {
+            rst =  getActualContentAreaMeasurement(rptTpl, JV.NODE_CROSS_COL, JV.PROP_CMN_WIDTH, band.Right - band.Left, segments, page);
+        }
+        return rst;
+    },
+    chkTabEnd: function(tabType, rptTpl, bands, sortedSequence, segIdx, preRec, nextRec) {
+        let me = this, rst = true;
+        let remainAmt = preRec + nextRec - sortedSequence[segIdx].length;
+        rst = me.hasEnoughSpace(tabType, rptTpl, bands, remainAmt);
+        return rst;
+    },
+    hasEnoughSpace: function (tabType, rptTpl, bands, remainAmt) {
+        if (remainAmt < 0) return false;
+        let rst = true, measurement = 1.0, douDiffForCompare = 0.00001;
+        let unitFactor = JpcCommonHelper.getUnitFactor(rptTpl);
+        let band = null;
+        if (rptTpl[JV.NODE_CROSS_INFO][tabType]) {
+            band = bands[rptTpl[JV.NODE_CROSS_INFO][tabType][JV.PROP_BAND_NAME]];
+        }
+        if (band != null && band != undefined) {
+            if (tabType === JV.NODE_CROSS_ROW_SUM || tabType === JV.NODE_CROSS_ROW_EXT) {
+                measurement = 1.0 * rptTpl[JV.NODE_CROSS_INFO][JV.NODE_CROSS_ROW][JV.PROP_CMN_HEIGHT] * unitFactor;
+                let spareHeight = measurement * remainAmt;
+                let douH = 1.0 * (band.Bottom - band.Top);
+                rst = (spareHeight >= douH) || (spareHeight - douH <= douDiffForCompare);
+            } else if (tabType === JV.NODE_CROSS_COL_SUM) {
+                measurement = 1.0 * rptTpl[JV.NODE_CROSS_INFO][JV.NODE_CROSS_COL][JV.PROP_CMN_WIDTH] * unitFactor;
+                let spareWidth = measurement * remainAmt;
+                let douW = 1.0 * (band.Right - band.Left);
+                rst = (spareWidth >= douW) || (spareWidth - douW <= douDiffForCompare);
+            }
+        }
+        return rst;
+    },
+    initialPageStatus: function (pageStatus) {
+        pageStatus[JV.STATUS_NORMAL] = true;
+        pageStatus[JV.STATUS_REPORT_START] = false;
+        pageStatus[JV.STATUS_REPORT_END] = false;
+        pageStatus[JV.STATUS_SEGMENT_START] = false;
+        pageStatus[JV.STATUS_SEGMENT_END] = false;
+        pageStatus[JV.STATUS_GROUP] = false;
+        pageStatus[JV.STATUS_CROSS_ROW_END] = false;
+        pageStatus[JV.STATUS_CROSS_COL_END] = false;
+    }
+};
+
+function getMaxTabCntPerPage(rptTpl, tabNodeName, tabMeasurePropName, measureForCal) {
+    let rst = 1;
+    if (rptTpl[JV.NODE_CROSS_INFO][tabNodeName]) {
+        let tab = rptTpl[JV.NODE_CROSS_INFO][tabNodeName];
+        let maxFieldMeasure = 1.0;
+        if (JV.CAL_TYPE_ABSTRACT === JpcCommonHelper.getPosCalculationType(tab[JV.PROP_CALCULATION])) {
+            let unitFactor = JpcCommonHelper.getUnitFactor(rptTpl);
+            maxFieldMeasure = 1.0 * rptTpl[JV.NODE_CROSS_INFO][tabNodeName][tabMeasurePropName] * unitFactor;
+        } else {
+            maxFieldMeasure = measureForCal * rptTpl[JV.NODE_CROSS_INFO][tabNodeName][tabMeasurePropName] / JV.HUNDRED_PERCENT;
+        }
+        rst = Math.floor(measureForCal / maxFieldMeasure);
+    }
+    if (rst <= 0) rst = 1;
+    return rst;
+};
+
+function getActualContentAreaMeasurement(rptTpl, tabNodeName, tabMeasurePropName, measureForCal, segments, page) {
+    let rst = 1;
+    if (rptTpl[JV.NODE_CROSS_INFO][tabNodeName]) {
+        let tab = rptTpl[JV.NODE_CROSS_INFO][tabNodeName];
+        let maxFieldMeasure = 1.0;
+        if (JV.CAL_TYPE_ABSTRACT === JpcCommonHelper.getPosCalculationType(tab[JV.PROP_CALCULATION])) {
+            let unitFactor = JpcCommonHelper.getUnitFactor(rptTpl);
+            maxFieldMeasure = 1.0 * rptTpl[JV.NODE_CROSS_INFO][tabNodeName][tabMeasurePropName] * unitFactor;
+        } else {
+            maxFieldMeasure = measureForCal * rptTpl[JV.NODE_CROSS_INFO][tabNodeName][tabMeasurePropName] / JV.HUNDRED_PERCENT;
+        }
+        if (segments.length >= page) {
+            rst = segments[page - 1].length * maxFieldMeasure;
+        }
+    }
+    return rst;
+};
+
+module.exports = JpcCrossTabHelper;

+ 132 - 0
app/reports/rpt_component/helper/jpc_helper_discrete.js

@@ -0,0 +1,132 @@
+let JV = require('../jpc_value_define');
+let JE = require('../jpc_rte');
+let JpcTextHelper = require('./jpc_helper_text');
+let JpcCommonOutputHelper = require('./jpc_helper_common_output');
+let JpcAreaHelper = require('./jpc_helper_area');
+let JpcFieldHelper = require('./jpc_helper_field');
+
+let JpcDiscreteHelper = {
+    outputDiscreteInfo: function (discreteArray, bands, dataObj, unitFactor, pageStatus, segIdx, multiCols, multiColIdx, $CURRENT_RPT, customizeCfg) {
+        let rst = [];
+        if (discreteArray && dataObj) {
+            for (let i = 0; i < discreteArray.length; i++) {
+                let band = bands[discreteArray[i][JV.PROP_BAND_NAME]];
+                if (band && pageStatus[band[JV.BAND_PROP_DISPLAY_TYPE]] === true) {
+                    if (discreteArray[i][JV.PROP_TEXT]) {
+                        rst.push(JpcTextHelper.outputText(discreteArray[i][JV.PROP_TEXT], band, unitFactor, 1, 0, 1, 0, multiCols, multiColIdx));
+                    }
+                    if (discreteArray[i][JV.PROP_TEXTS]) {
+                        for (let j = 0; j < discreteArray[i][JV.PROP_TEXTS].length; j++) {
+                            rst.push(JpcTextHelper.outputText(discreteArray[i][JV.PROP_TEXTS][j], band, unitFactor, 1, 0, 1, 0, multiCols, multiColIdx));
+                        }
+                    }
+                    if (discreteArray[i][JV.PROP_DISCRETE_FIELDS]) {
+                        let flexiblePrecisionRefObj = null, flexibleRefField = null;
+                        for (let j = 0; j < discreteArray[i][JV.PROP_DISCRETE_FIELDS].length; j++) {
+                            let df = discreteArray[i][JV.PROP_DISCRETE_FIELDS][j];
+                            let value = "";
+                            if (df[JV.PROP_FIELD_ID]) {
+                                // let map_data_field = JE.F(df[JV.PROP_FIELD_ID], $CURRENT_RPT);
+                                let map_data_field = JE.F(df[JV.PROP_FIELD_ID], $CURRENT_RPT);
+                                if (map_data_field.DataSeq !== JV.BLANK_FIELD_INDEX) {
+                                    let data = dataObj[map_data_field.DataNodeName][map_data_field.DataSeq];
+                                    if (data && data.length > 0) {
+                                        if (data.length > segIdx) {
+                                            value = data[segIdx];
+                                        } else {
+                                            value = data[0];
+                                        }
+                                    }
+                                } else {
+                                    if (map_data_field[JV.PROP_AD_HOC_DATA] && map_data_field[JV.PROP_AD_HOC_DATA].length > 0) {
+                                        value = map_data_field[JV.PROP_AD_HOC_DATA][0]
+                                    } else value = "";
+                                }
+                                if (map_data_field && map_data_field[JV.PROP_PRECISION] && map_data_field.flexiblePrecisionRefObj) {
+                                    let pre_ref_data = null;
+                                    if (flexiblePrecisionRefObj === null) {
+                                        flexiblePrecisionRefObj = {};
+                                        flexibleRefField = JE.F(map_data_field[JV.PROP_PRECISION][JV.PROP_FLEXIBLE_REF_FILED_ID], $CURRENT_RPT);
+                                        pre_ref_data = dataObj[map_data_field.DataNodeName][flexibleRefField.DataSeq]; //这里的引用指标必须得存在,哪怕不显示,也要存放在指标集合中!
+                                        for (let decimalObj of map_data_field.flexiblePrecisionRefObj) {
+                                            flexiblePrecisionRefObj["refUnit_" + decimalObj.unit] = decimalObj.decimal;
+                                        }
+                                    }
+                                    JpcFieldHelper.resetFlexibleFormat(df, pre_ref_data, flexiblePrecisionRefObj, segIdx, customizeCfg);
+                                } else {
+                                    JpcFieldHelper.resetFormat(df, map_data_field, customizeCfg);
+                                }
+                            } else if (df[JV.PROP_PARAM_ID]) {
+                                let param = JE.P(df[JV.PROP_PARAM_ID], $CURRENT_RPT);
+                                value = param[JV.PROP_DFT_VALUE];
+                            }
+                            let item = JpcCommonOutputHelper.createCommonOutputWithoutDecorate(df, value, true);
+                            //position
+                            item[JV.PROP_AREA] = JpcAreaHelper.outputArea(df[JV.PROP_AREA], band, unitFactor, 1, 0, 1, 0, multiCols, multiColIdx, false, false);
+                            rst.push(item);
+                        }
+                    }
+                }
+            }
+        }
+        return rst;
+    },
+    outputPreviewDiscreteInfo: function (discreteArray, bands, unitFactor, pageStatus) {
+        let rst = [];
+        for (let i = 0; i < discreteArray.length; i++) {
+            let band = bands[discreteArray[i][JV.PROP_BAND_NAME]];
+            if (band && pageStatus[band[JV.BAND_PROP_DISPLAY_TYPE]] === true) {
+                if (discreteArray[i][JV.PROP_TEXT]) {
+                    rst.push(JpcTextHelper.outputText(discreteArray[i][JV.PROP_TEXT], band, unitFactor, 1, 0, 1, 0, 1, 0));
+                }
+                if (discreteArray[i][JV.PROP_TEXTS]) {
+                    for (let j = 0; j < discreteArray[i][JV.PROP_TEXTS].length; j++) {
+                        rst.push(JpcTextHelper.outputText(discreteArray[i][JV.PROP_TEXTS][j], band, unitFactor, 1, 0, 1, 0, 1, 0));
+                    }
+                }
+                if (discreteArray[i][JV.PROP_DISCRETE_FIELDS]) {
+                    for (let j = 0; j < discreteArray[i][JV.PROP_DISCRETE_FIELDS].length; j++) {
+                        let df = discreteArray[i][JV.PROP_DISCRETE_FIELDS][j];
+                        let value = "";
+                        if (df[JV.PROP_DFT_VALUE]) {
+                            value = df[JV.PROP_DFT_VALUE];
+                        }
+                        let item = JpcCommonOutputHelper.createCommonOutputWithoutDecorate(df, value, null);
+                        //position
+                        item[JV.PROP_AREA] = JpcAreaHelper.outputArea(df[JV.PROP_AREA], band, unitFactor, 1, 0, 1, 0, 1, 0, false, false);
+                        rst.push(item);
+                    }
+                }
+
+            }
+        }
+        return rst;
+    },
+    outputPreviewSumInfoOutput: function (band, unitFactor, tab) {
+        let rst = [];
+        if (tab[JV.PROP_TEXT]) {
+            rst.push(JpcTextHelper.outputText(tab[JV.PROP_TEXT], band, unitFactor, 1, 0, 1, 0, 1, 0));
+        }
+        if (tab[JV.PROP_TEXTS]) {
+            for (let j = 0; j < tab[JV.PROP_TEXTS].length; j++) {
+                rst.push(JpcTextHelper.outputText(tab[JV.PROP_TEXTS][j], band, unitFactor, 1, 0, 1, 0, 1, 0));
+            }
+        }
+        if (tab[JV.PROP_SUM_FIELDS]) {
+            for (let i = 0; i < tab[JV.PROP_SUM_FIELDS].length; i++) {
+                let tab_field = tab[JV.PROP_SUM_FIELDS][i];
+                if (!(tab_field[JV.PROP_HIDDEN])) {
+                    // rst.push(me.outputTabField(band, tab_field, null, -1, -1, 1, 0, 1, 0, unitFactor, true, controls, 0));
+                    let value = "";
+                    let item = JpcCommonOutputHelper.createCommonOutputWithoutDecorate(tab_field, value, null);
+                    //position
+                    item[JV.PROP_AREA] = JpcAreaHelper.outputArea(tab_field[JV.PROP_AREA], band, unitFactor, 1, 0, 1, 0, 1, 0, false, false);
+                    rst.push(item);
+                }
+            }
+        }
+        return rst;
+    }
+}
+
+module.exports = JpcDiscreteHelper;

+ 158 - 0
app/reports/rpt_component/helper/jpc_helper_field.js

@@ -0,0 +1,158 @@
+'use strict';
+
+const JV = require('../jpc_value_define');
+const strUtil = require('../../public/stringUtil');
+
+const JpcFieldHelper = {
+    getValue: function(dataField, valueIdx) {
+        let rst = '';
+        if (dataField && (dataField.length > valueIdx) && (valueIdx >= 0)) {
+            rst = dataField[valueIdx];
+        }
+        return rst;
+    },
+    setValue: function(dataField, valueIdx, newValue) {
+        if (dataField && (dataField.length > valueIdx) && (valueIdx >= 0)) {
+            dataField[valueIdx] = newValue;
+        }
+    },
+    resetSumFormat: function(ref_tab_fields, sum_tab_field) {
+        let rst = false;
+        if (ref_tab_fields && ref_tab_fields.length > 0) {
+            for (const tab_field of ref_tab_fields) {
+                if (tab_field[JV.PROP_FIELD_ID] === sum_tab_field[JV.PROP_FIELD_ID]) {
+                    rst = true;
+                    if (tab_field[JV.PROP_FORMAT]) {
+                        sum_tab_field[JV.PROP_FORMAT] = tab_field[JV.PROP_FORMAT];
+                    }
+                    break;
+                }
+            }
+        }
+        return rst;
+    },
+    resetFormat: function(tab_field, map_field, customizeCfg) {
+        let rst = false;
+        if (map_field && map_field[JV.PROP_PRECISION] && map_field[JV.PROP_PRECISION].type === 'fixed') {
+            const formatStrs = ['#'];
+            const ffStr = (customizeCfg && customizeCfg.fillZero) ? '0' : '#';
+            for (let idx = 0; idx < parseInt(map_field[JV.PROP_FIXED_PRECISION_AMT]); idx++) {
+                if (idx === 0) {
+                    formatStrs.push('.');
+                }
+                formatStrs.push(ffStr);
+            }
+            if (tab_field[JV.PROP_FORMAT] && tab_field[JV.PROP_FORMAT].indexOf(',') >= 0) {
+                tab_field[JV.PROP_FORMAT] = ',' + formatStrs.join('');
+            } else {
+                tab_field[JV.PROP_FORMAT] = formatStrs.join('');
+            }
+            map_field[JV.PROP_FORMAT] = tab_field[JV.PROP_FORMAT]; // 这里顺手把format也赋给map_field,后期统计用得着
+            rst = true;
+        } else if (tab_field[JV.PROP_FORMAT] && tab_field[JV.PROP_FORMAT].indexOf('#') >= 0 && (customizeCfg && customizeCfg.fillZero)) {
+            tab_field[JV.PROP_FORMAT] = strUtil.replaceAll(tab_field[JV.PROP_FORMAT], '#', '0');
+            map_field[JV.PROP_FORMAT] = tab_field[JV.PROP_FORMAT];
+            rst = true;
+        }
+        return rst;
+    },
+    resetFlexibleFormat: function(tab_field, ref_field_data, flexiblePrecisionRefObj, valueIdx, customizeCfg) {
+        let precisionAmt = 2;
+        if (ref_field_data && flexiblePrecisionRefObj && valueIdx >= 0) {
+            precisionAmt = flexiblePrecisionRefObj['refUnit_' + ref_field_data[valueIdx]];
+            if (precisionAmt === null || precisionAmt === undefined) precisionAmt = flexiblePrecisionRefObj['refUnit_其他未列单位'];
+        }
+        const formatStrs = ['#.'];
+        const ffStr = (customizeCfg && customizeCfg.fillZero)?'0':'#';
+        for (let idx = 0; idx < precisionAmt; idx++) {
+            formatStrs.push(ffStr);
+        }
+        if (tab_field[JV.PROP_FORMAT] && tab_field[JV.PROP_FORMAT].indexOf(',') >= 0) {
+            tab_field[JV.PROP_FORMAT] = ',' + formatStrs.join('');
+        } else {
+            tab_field[JV.PROP_FORMAT] = formatStrs.join('');
+        }
+    },
+    decorateValue: function(cell, controls) {
+        if (controls) {
+            const val = cell[JV.PROP_VALUE];
+            const showZero = controls[cell[JV.PROP_CONTROL]][JV.PROP_SHOW_ZERO];
+            if (showZero && showZero === 'F') {
+                if (1.0 * (0 + val) === 0) {
+                    cell[JV.PROP_VALUE] = '';
+                }
+            }
+        }
+    },
+    findAndPutDataFieldIdx: function (rptTpl, tab_fields, rstFields, rstFieldsIdx, isEx) {
+        // 通过FieldID找到相关映射指标的位置IDX并记录下来,方便后续引用
+        if (tab_fields) {
+            const DTL_STR = isEx ? JV.NODE_DETAIL_FIELDS_EX : JV.NODE_DETAIL_FIELDS;
+            const detail_fields = rptTpl[JV.NODE_FIELD_MAP][DTL_STR];
+            for (let i = 0; i < tab_fields.length; i++) {
+                let isFounded = false;
+                for (let j = 0; j < detail_fields.length; j++) {
+                    if (tab_fields[i][JV.PROP_FIELD_ID] === detail_fields[j][JV.PROP_ID]) {
+                        isFounded = true;
+                        if (rstFields) rstFields.push(tab_fields[i]);
+                        if (rstFieldsIdx) rstFieldsIdx.push(j);
+                        break;
+                    }
+                }
+                if (!isFounded) {
+                    if (rstFields) rstFields.push(tab_fields[i]);
+                    if (rstFieldsIdx) {
+                        if (rptTpl[JV.NODE_NO_MAPPING_FIELDS] && rptTpl[JV.NODE_NO_MAPPING_FIELDS].length > 0) {
+                            for (const discretField of rptTpl[JV.NODE_NO_MAPPING_FIELDS]) {
+                                if (discretField[JV.PROP_ID] === tab_fields[i][JV.PROP_FIELD_ID]) {
+                                    rstFieldsIdx.push(discretField);
+                                    isFounded = true;
+                                    break;
+                                }
+                            }
+                        } else {
+                            rstFieldsIdx.push(JV.BLANK_FIELD_INDEX);
+                        }
+                    }
+                }
+            }
+        }
+    },
+    findAutoHeightFieldIdx: function(rptTpl, tab_fields, rstFieldsIdx, isEx) {
+        if (tab_fields) {
+            const DTL_STR = isEx ? JV.NODE_DETAIL_FIELDS_EX : JV.NODE_DETAIL_FIELDS;
+            const detail_fields = rptTpl[JV.NODE_FIELD_MAP][DTL_STR];
+            for (let i = 0; i < tab_fields.length; i++) {
+                if (tab_fields[i][JV.PROP_IS_AUTO_HEIGHT]) {
+                    for (let j = 0; j < detail_fields.length; j++) {
+                        if (tab_fields[i][JV.PROP_FIELD_ID] === detail_fields[j][JV.PROP_ID]) {
+                            const item = [];
+                            item[0] = j;
+                            item[1] = tab_fields[i];
+                            rstFieldsIdx.push(item);
+                            break;
+                        }
+                    }
+                }
+            }
+            if (!isEx) {
+                const dis_detail_fields = rptTpl[JV.NODE_NO_MAPPING_FIELDS];
+                for (let i = 0; i < tab_fields.length; i++) {
+                    if (tab_fields[i][JV.PROP_IS_AUTO_HEIGHT]) {
+                        for (let j = 0; j < dis_detail_fields.length; j++) {
+                            if (tab_fields[i][JV.PROP_FIELD_ID] === dis_detail_fields[j][JV.PROP_ID]) {
+                                const item = [];
+                                item[0] = JV.BLANK_FIELD_INDEX;
+                                item[1] = tab_fields[i];
+                                rstFieldsIdx.push(item);
+                                break;
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    },
+};
+
+module.exports = JpcFieldHelper;

+ 64 - 0
app/reports/rpt_component/helper/jpc_helper_flow_tab.js

@@ -0,0 +1,64 @@
+let JV = require('../jpc_value_define');
+let JpcCommonHelper = require('./jpc_helper_common');
+
+let JpcFlowTabHelper = {
+    getMaxRowsPerPage: function(bands, rptTpl, isEx) {
+        let rst = 1;
+        let FLOW_INFO_STR = (!isEx)?JV.NODE_FLOW_INFO:JV.NODE_FLOW_INFO_EX;
+        let tab = rptTpl[FLOW_INFO_STR][JV.NODE_FLOW_CONTENT];
+        let band = bands[tab[JV.PROP_BAND_NAME]];
+        if (band) {
+            let maxFieldMeasure = 1.0;
+            if (JV.CAL_TYPE_ABSTRACT === JpcCommonHelper.getPosCalculationType(tab[JV.PROP_CALCULATION])) {
+                let unitFactor = JpcCommonHelper.getUnitFactor(rptTpl);
+                maxFieldMeasure = 1.0 * rptTpl[FLOW_INFO_STR][JV.NODE_FLOW_CONTENT][JV.PROP_CMN_HEIGHT] * unitFactor;
+            } else {
+                maxFieldMeasure = (band.Bottom - band.Top) * rptTpl[FLOW_INFO_STR][JV.NODE_FLOW_CONTENT][JV.PROP_CMN_HEIGHT] / JV.HUNDRED_PERCENT;
+            }
+            rst = Math.floor((band.Bottom - band.Top) / maxFieldMeasure);
+        }
+        return rst;
+    },
+    getActualContentAreaHeight: function(bands, rptTpl, segments, page, isEx) {
+        let rst = 1;
+        let FLOW_INFO_STR = (!isEx)?JV.NODE_FLOW_INFO:JV.NODE_FLOW_INFO_EX;
+        let tab = rptTpl[FLOW_INFO_STR][JV.NODE_FLOW_CONTENT];
+        let band = bands[tab[JV.PROP_BAND_NAME]];
+        if (band) {
+            let maxFieldMeasure = 1.0;
+            if (JV.CAL_TYPE_ABSTRACT === JpcCommonHelper.getPosCalculationType(tab[JV.PROP_CALCULATION])) {
+                let unitFactor = JpcCommonHelper.getUnitFactor(rptTpl);
+                maxFieldMeasure = 1.0 * rptTpl[FLOW_INFO_STR][JV.NODE_FLOW_CONTENT][JV.PROP_CMN_HEIGHT] * unitFactor;
+            } else {
+                maxFieldMeasure = (band.Bottom - band.Top) * rptTpl[FLOW_INFO_STR][JV.NODE_FLOW_CONTENT][JV.PROP_CMN_HEIGHT] / JV.HUNDRED_PERCENT;
+            }
+            if (segments.length >= page) {
+                rst = segments[page - 1].length * maxFieldMeasure;
+            }
+        }
+        return rst;
+    },
+    chkSegEnd: function (bands, rptTpl, segmentLength, preRec, pageRecAmt, isEx) {
+        let me = this, rst = false;
+        let remainAmt = preRec + pageRecAmt - segmentLength;
+        rst = me.hasEnoughSpace(rptTpl, bands, remainAmt, isEx);
+        return rst;
+    },
+    hasEnoughSpace: function (rptTpl, bands, remainAmt, isEx) {
+        if (remainAmt < 0) return false;
+        let rst = true, measurement = 1.0, douDiffForCompare = 0.00001;
+        let unitFactor = JpcCommonHelper.getUnitFactor(rptTpl);
+        let FLOW_INFO_STR = (!isEx)?JV.NODE_FLOW_INFO:JV.NODE_FLOW_INFO_EX;
+        let tab = rptTpl[FLOW_INFO_STR][JV.NODE_FLOW_CONTENT];
+        let band = bands[tab[JV.PROP_BAND_NAME]];
+        if (band !== null && band !== undefined) {
+            measurement = 1.0 * tab[JV.PROP_CMN_HEIGHT] * unitFactor;
+            let spareHeight = measurement * remainAmt;
+            let douH = band.Bottom - band.Top;
+            rst = ((spareHeight >= douH) || ((spareHeight - douH) <= douDiffForCompare));
+        }
+        return rst;
+    }
+};
+
+module.exports = JpcFlowTabHelper;

+ 20 - 0
app/reports/rpt_component/helper/jpc_helper_text.js

@@ -0,0 +1,20 @@
+let JV = require('../jpc_value_define');
+let JpcCommonOutputHelper = require('./jpc_helper_common_output');
+let JpcAreaHelper = require('./jpc_helper_area');
+
+let JpcTextHelper = {
+    outputText: function (textNode, band, unitFactor, rows, rowIdx, cols, colIdx, multiCols, multiColIdx) {
+        let rst = JpcCommonOutputHelper.createCommonOutput(textNode, textNode[JV.PROP_LABEL], null);
+        //position
+        rst[JV.PROP_AREA] = JpcAreaHelper.outputArea(textNode[JV.PROP_AREA], band, unitFactor, rows, rowIdx, cols, colIdx, multiCols, multiColIdx, false, false);
+        return rst;
+    },
+    outputDirectValue: function (textNode, value, band, unitFactor, rows, rowIdx, cols, colIdx, multiCols, multiColIdx) {
+        let rst = JpcCommonOutputHelper.createCommonOutput(textNode, value, null);
+        //position
+        rst[JV.PROP_AREA] = JpcAreaHelper.outputArea(textNode[JV.PROP_AREA], band, unitFactor, rows, rowIdx, cols, colIdx, multiCols, multiColIdx, false, false);
+        return rst;
+    }
+};
+
+module.exports = JpcTextHelper;

+ 58 - 0
app/reports/rpt_component/jpc_band.js

@@ -0,0 +1,58 @@
+let JV = require('./jpc_value_define');
+let JpcCommonHelper = require('./helper/jpc_helper_common');
+let JpcBandHelper = require('./helper/jpc_helper_band');
+
+let JpcBand = {
+    createNew: function(rptTpl, defProperties) {
+        let me = this;
+        let JpcBandResult = {};
+        if (rptTpl[JV.NODE_BAND_COLLECTION]) {
+            for (let i = 0; i < rptTpl[JV.NODE_BAND_COLLECTION].length; i++) {
+                me.createSingle(rptTpl[JV.NODE_BAND_COLLECTION][i], JpcBandResult, rptTpl, defProperties);
+            }
+        }
+        return JpcBandResult;
+    },
+    createSingle: function(bandNode, parentObj, rptTpl, defProperties) {
+        let me = this;
+        if (bandNode && bandNode[JV.BAND_PROP_NAME]) {
+            let item = {Left:0, Right:0, Top:0, Bottom:0};
+            item[JV.BAND_PROP_STYLE] = JpcCommonHelper.getStyle(bandNode[JV.BAND_PROP_STYLE], defProperties.styles, null);
+            item[JV.BAND_PROP_CONTROL] = JpcCommonHelper.getControl(bandNode[JV.BAND_PROP_CONTROL], defProperties.ctrls, null);
+            if (bandNode[JV.BAND_PROP_HEIGHT]) {
+                item[JV.BAND_PROP_HEIGHT] = 1.0 * bandNode[JV.BAND_PROP_HEIGHT];
+            } else {
+                item[JV.BAND_PROP_HEIGHT] = 0.0;
+            }
+            if (bandNode[JV.BAND_PROP_WIDTH]) {
+                item[JV.BAND_PROP_WIDTH] = 1.0 * bandNode[JV.BAND_PROP_WIDTH];
+            } else {
+                item[JV.BAND_PROP_WIDTH] = 0.0;
+            }
+            item[JV.BAND_PROP_DISPLAY_TYPE] = JpcBandHelper.getBandTypeValByString(bandNode[JV.BAND_PROP_DISPLAY_TYPE]);
+            item[JV.BAND_PROP_ALIGNMENT] = JpcCommonHelper.getLayoutAlignment(bandNode[JV.BAND_PROP_ALIGNMENT]);
+            item[JV.PROP_CALCULATION] = JpcCommonHelper.getPosCalculationType(bandNode[JV.PROP_CALCULATION]);
+            if (bandNode.hasOwnProperty(JV.PROP_BAND_NORMAL_ONLY)) {
+                item[JV.PROP_BAND_NORMAL_ONLY] = JpcCommonHelper.getBoolean(bandNode[JV.PROP_BAND_NORMAL_ONLY]);
+            }
+            if (bandNode.hasOwnProperty(JV.PROP_BAND_EX_ONLY)) {
+                item[JV.PROP_BAND_EX_ONLY] = JpcCommonHelper.getBoolean(bandNode[JV.PROP_BAND_EX_ONLY]);
+            }
+
+            if (bandNode[JV.BAND_PROP_MERGE_BORDER]) {
+                item[JV.BAND_PROP_MERGE_BORDER] = bandNode[JV.BAND_PROP_MERGE_BORDER];
+            }
+            if (bandNode[JV.BAND_PROP_SUB_BANDS]) {
+                for (let i = 0; i < bandNode[JV.BAND_PROP_SUB_BANDS].length; i++) {
+                    me.createSingle(bandNode[JV.BAND_PROP_SUB_BANDS][i], parentObj, rptTpl, defProperties);
+                }
+            }
+            parentObj[bandNode[JV.BAND_PROP_NAME]] = item;
+            if (item[JV.BAND_PROP_MERGE_BORDER] !== null && item[JV.BAND_PROP_MERGE_BORDER] !== undefined && item[JV.BAND_PROP_MERGE_BORDER] === 'T') {
+                parentObj[JV.BAND_PROP_MERGE_BAND] = item;
+            }
+        }
+    }
+};
+
+module.exports = JpcBand;

+ 162 - 0
app/reports/rpt_component/jpc_bill_tab.js

@@ -0,0 +1,162 @@
+let JV = require('./jpc_value_define');
+let JE = require('./jpc_rte');
+let JpcFieldHelper = require('./helper/jpc_helper_field');
+let JpcBandHelper = require('./helper/jpc_helper_band');
+let JpcCommonHelper = require('./helper/jpc_helper_common');
+let JpcDiscreteHelper = require('./helper/jpc_helper_discrete');
+let JpcTextHelper = require('./helper/jpc_helper_text');
+let JpcCommonOutputHelper = require('./helper/jpc_helper_common_output');
+let JpcAreaHelper = require('./helper/jpc_helper_area');
+
+let JpcBillTabSrv = function(){};
+JpcBillTabSrv.prototype.createNew = function(){
+    let JpcBillTabResult = {};
+    JpcBillTabResult.initialize = function() {
+        let me = this;
+        me.disp_fields_idx = [];
+    };
+    JpcBillTabResult.sorting = function(rptTpl) {
+        let me = this;
+        JpcFieldHelper.findAndPutDataFieldIdx(rptTpl, rptTpl[JV.NODE_BILL_INFO][JV.NODE_BILL_CONTENT][JV.PROP_BILL_FIELDS], null, me.disp_fields_idx);
+    };
+    JpcBillTabResult.paging = function(rptTpl, dataObj) {
+        let rst = 0;
+        function getDataLength(fields_str) {
+            let dataFields = dataObj[fields_str];
+            if (dataFields && dataFields.length > 0) {
+                rst = dataFields[0].length;
+            }
+        }
+        if (rptTpl[JV.NODE_FIELD_MAP]) {
+            if (rptTpl[JV.NODE_FIELD_MAP][JV.NODE_DETAIL_FIELDS]) {
+                getDataLength(JV.DATA_DETAIL_DATA);
+            } else if (rptTpl[JV.NODE_FIELD_MAP][JV.NODE_MASTER_FIELDS]) {
+                getDataLength(JV.DATA_MASTER_DATA);
+            } else if (rptTpl[JV.NODE_FIELD_MAP][JV.NODE_DISCRETE_FIELDS]) {
+                getDataLength(JV.DATA_DISCRETE_DATA);
+            } else if (rptTpl[JV.NODE_FIELD_MAP][JV.NODE_DISCRETE_PARAMS]) {
+                rst = 1;
+            }
+        }
+        return rst;
+    };
+    JpcBillTabResult.outputAsPreviewPage = function (rptTpl, bands, controls, $CURRENT_RPT) {
+        let me = this, rst = [], vIdx = [];
+        //只预览第一页的数据
+        let pageStatus = [true, true, false, true, false, false, false, false];
+        JpcBandHelper.setBandArea(bands, rptTpl, pageStatus, true, false);
+        let unitFactor = JpcCommonHelper.getUnitFactor(rptTpl);
+        rst = rst.concat(me.outputPreviewContent(rptTpl, bands, unitFactor, controls, pageStatus));
+        rst = rst.concat(JpcDiscreteHelper.outputPreviewDiscreteInfo(rptTpl[JV.NODE_BILL_INFO][JV.NODE_DISCRETE_INFO], bands, unitFactor, pageStatus));
+        return rst;
+    };
+    JpcBillTabResult.outputAsSimpleJSONPage = function (rptTpl, dataObj, page, bands, controls, $CURRENT_RPT, customizeCfg) {
+        let me = this, rst = [], tabRstLst = [];
+        //1 calculate the band position
+        let pageStatus = [true, false, false, false, false, false, false, false];
+        JpcBandHelper.setBandArea(bands, rptTpl, pageStatus);
+        //2. start to output detail-part
+        let unitFactor = JpcCommonHelper.getUnitFactor(rptTpl);
+        //2.1 output content
+        tabRstLst.push(me.outputContent(rptTpl, dataObj, page, bands, unitFactor, controls, pageStatus, $CURRENT_RPT, customizeCfg));
+        //2.2 output discrete
+        tabRstLst.push(JpcDiscreteHelper.outputDiscreteInfo(rptTpl[JV.NODE_BILL_INFO][JV.NODE_DISCRETE_INFO], bands, dataObj, unitFactor, pageStatus, page - 1, 1, 0, $CURRENT_RPT, customizeCfg));
+        for (let i = 0; i < tabRstLst.length; i++) {
+            rst = rst.concat(tabRstLst[i]);
+            tabRstLst[i] = null;
+        }
+        return rst;
+    };
+    JpcBillTabResult.outputPreviewContent = function(rptTpl, bands, unitFactor, controls, pageStatus) {
+        let rst = [];
+        let tab = rptTpl[JV.NODE_BILL_INFO][JV.NODE_BILL_CONTENT];
+        let band = bands[tab[JV.PROP_BAND_NAME]];
+        if (band) {
+            if (pageStatus[band[JV.BAND_PROP_DISPLAY_TYPE]]) {
+                let tab_fields = tab[JV.PROP_BILL_FIELDS];
+                for (let i = 0; i < tab_fields.length; i++) {
+                    let tab_field = tab_fields[i];
+                    if (!(tab_field[JV.PROP_HIDDEN])) {
+                        let cellItem = JpcCommonOutputHelper.createCommonOutput(tab_field, "", controls);
+                        cellItem[JV.PROP_AREA] = JpcAreaHelper.outputArea(tab_field[JV.PROP_AREA], band, unitFactor, 1, 0, 1, 0, 1, 0, false, false);
+                        rst.push(cellItem);
+                    }
+                }
+                if (tab[JV.PROP_TEXT]) {
+                    rst.push(JpcTextHelper.outputText(tab[JV.PROP_TEXT], band, unitFactor, 1, 0, 1, 0, 1, 0));
+                }
+                if (tab[JV.PROP_TEXTS]) {
+                    for (let j = 0; j < tab[JV.PROP_TEXTS].length; j++) {
+                        rst.push(JpcTextHelper.outputText(tab[JV.PROP_TEXTS][j], band, unitFactor, 1, 0, 1, 0, 1, 0));
+                    }
+                }
+                if (tab[JV.NODE_DISCRETE_INFO]) {
+                    rst = rst.concat(JpcDiscreteHelper.outputPreviewDiscreteInfo(tab[JV.NODE_DISCRETE_INFO], bands, unitFactor, pageStatus));
+                }
+            }
+        }
+        return rst;
+    };
+    JpcBillTabResult.outputContent = function(rptTpl, dataObj, page, bands, unitFactor, controls, pageStatus, $CURRENT_RPT, customizeCfg) {
+        let me = this, rst = [];
+        let tab = rptTpl[JV.NODE_BILL_INFO][JV.NODE_BILL_CONTENT];
+        let band = bands[tab[JV.PROP_BAND_NAME]];
+        if (band) {
+            if (pageStatus[band[JV.BAND_PROP_DISPLAY_TYPE]]) {
+                let tab_fields = tab[JV.PROP_BILL_FIELDS];
+                let data_details = null;
+                if (rptTpl[JV.NODE_FIELD_MAP][JV.NODE_DETAIL_FIELDS]) {
+                    data_details = dataObj[JV.DATA_DETAIL_DATA];
+                } else if (rptTpl[JV.NODE_FIELD_MAP][JV.NODE_MASTER_FIELDS]) {
+                    data_details = dataObj[JV.DATA_MASTER_DATA];
+                }
+                let flexiblePrecisionRefObj = null, flexibleRefField = null, precision_ref_data = null;
+                for (let i = 0; i < tab_fields.length; i++) {
+                    let tab_field = tab_fields[i];
+                    let data_field = null, map_data_field = JE.F(tab_field[JV.PROP_FIELD_ID], $CURRENT_RPT);
+                    if (me.disp_fields_idx.length > i && me.disp_fields_idx[i] !== JV.BLANK_FIELD_INDEX && (typeof me.disp_fields_idx[i] !== 'object')) {
+                        data_field = data_details[me.disp_fields_idx[i]];
+                    } else {
+                        if (map_data_field) {
+                            data_field = map_data_field[JV.PROP_AD_HOC_DATA];
+                        }
+                    }
+                    if (map_data_field && map_data_field[JV.PROP_PRECISION] && map_data_field.flexiblePrecisionRefObj) {
+                        if (flexiblePrecisionRefObj === null) {
+                            flexiblePrecisionRefObj = {};
+                            flexibleRefField = JE.F(map_data_field[JV.PROP_PRECISION][JV.PROP_FLEXIBLE_REF_FILED_ID], $CURRENT_RPT);
+                            precision_ref_data = dataObj[map_data_field.DataNodeName][flexibleRefField.DataSeq];
+                            for (let decimalObj of map_data_field.flexiblePrecisionRefObj) {
+                                flexiblePrecisionRefObj["refUnit_" + decimalObj.unit] = decimalObj.decimal;
+                            }
+                        }
+                        JpcFieldHelper.resetFlexibleFormat(tab_field, precision_ref_data, flexiblePrecisionRefObj, page - 1, customizeCfg);
+                    } else {
+                        if (page === 1) JpcFieldHelper.resetFormat(tab_field, map_data_field, customizeCfg);
+                    }
+                    if (!(tab_field[JV.PROP_HIDDEN])) {
+                        let val = JpcFieldHelper.getValue(data_field, page - 1);
+                        let cellItem = JpcCommonOutputHelper.createCommonOutput(tab_field, val, controls);
+                        cellItem[JV.PROP_AREA] = JpcAreaHelper.outputArea(tab_field[JV.PROP_AREA], band, unitFactor, 1, 0, 1, 0, 1, 0, false, false);
+                        rst.push(cellItem);
+                    }
+                }
+                if (tab[JV.PROP_TEXT]) {
+                    rst.push(JpcTextHelper.outputText(tab[JV.PROP_TEXT], band, unitFactor, 1, 0, 1, 0, 1, 0));
+                }
+                if (tab[JV.PROP_TEXTS]) {
+                    for (let j = 0; j < tab[JV.PROP_TEXTS].length; j++) {
+                        rst.push(JpcTextHelper.outputText(tab[JV.PROP_TEXTS][j], band, unitFactor, 1, 0, 1, 0, 1, 0));
+                    }
+                }
+                if (tab[JV.NODE_DISCRETE_INFO]) {
+                    rst = rst.concat(JpcDiscreteHelper.outputDiscreteInfo(tab[JV.NODE_DISCRETE_INFO], bands, dataObj, unitFactor, pageStatus, page - 1, 1, 0, customizeCfg));
+                }
+            }
+        }
+        return rst;
+    };
+    return JpcBillTabResult;
+};
+
+module.exports = new JpcBillTabSrv();

+ 851 - 0
app/reports/rpt_component/jpc_cross_tab.js

@@ -0,0 +1,851 @@
+let JV = require('./jpc_value_define');
+let JE = require('./jpc_rte');
+let JpcFieldHelper = require('./helper/jpc_helper_field');
+let JpcBandHelper = require('./helper/jpc_helper_band');
+let JpcBand = require('./jpc_band');
+let JpcCrossTabHelper = require('./helper/jpc_helper_cross_tab');
+let JpcCommonHelper = require('./helper/jpc_helper_common');
+let JpcDiscreteHelper = require('./helper/jpc_helper_discrete');
+let JpcTextHelper = require('./helper/jpc_helper_text');
+let JpcCommonOutputHelper = require('./helper/jpc_helper_common_output');
+let JpcAreaHelper = require('./helper/jpc_helper_area');
+
+let JpcCrossTabSrv = function(){};
+JpcCrossTabSrv.prototype.createNew = function(){
+    function private_addTabValue(tabValuedIdxLst, sortedSequence, segIdx, preRec, nextRec, dispSerialIdxLst, sorted_sum_value_Lst, rst_sum_value_Lst) {
+        if (tabValuedIdxLst) {
+            let serial1stTier = null;
+            if (dispSerialIdxLst) serial1stTier = [];
+            let pgseg1stTier = [];
+            let sumVal = [];
+            let sumValL = 1;
+            if (sortedSequence) {
+                let arrDupVals = sortedSequence[segIdx];
+                let arrDupSumVals = null;
+                if (sorted_sum_value_Lst !== null) {
+                    arrDupSumVals = sorted_sum_value_Lst[segIdx];
+                    sumValL = arrDupSumVals[0].length;
+                }
+
+                for (let i = 0; i < nextRec; i++) {
+                    if (arrDupVals.length <= preRec + i) {
+                        pgseg1stTier[i] = JV.BLANK_VALUE_INDEX;
+                        sumVal[i] = [];
+                        for (let ei = 0; ei < sumValL; ei++) {
+                            sumVal[i][ei] = null;
+                        }
+                        if (serial1stTier !== null) {
+                            serial1stTier[i] = JV.BLANK_VALUE_INDEX;
+                        }
+                        continue;
+                    }
+                    let duplicateValueArr = arrDupVals[preRec + i];
+                    pgseg1stTier[i] = duplicateValueArr[0];
+                    if (arrDupSumVals !== null) sumVal[i] = arrDupSumVals[preRec + i];
+
+                    if (serial1stTier !== null) {
+                        serial1stTier[i] = preRec + i;
+                    }
+                }
+                tabValuedIdxLst.push(pgseg1stTier);
+                if (dispSerialIdxLst !== null) {
+                    dispSerialIdxLst.push(serial1stTier);
+                }
+                if (sorted_sum_value_Lst !== null && rst_sum_value_Lst !== null) {
+                    rst_sum_value_Lst.push(sumVal);
+                }
+            } else {
+                //should push blank value index rather than null
+                for (let i = 0; i < nextRec; i++) {
+                    pgseg1stTier[i] = JV.BLANK_VALUE_INDEX;
+                    sumVal[i] = null;
+                    if (serial1stTier !== null) {
+                        serial1stTier[i] = JV.BLANK_VALUE_INDEX;
+                    }
+                }
+                tabValuedIdxLst.push(pgseg1stTier);
+                if (dispSerialIdxLst !== null) {
+                    dispSerialIdxLst.push(serial1stTier);
+                }
+                if (sorted_sum_value_Lst !== null && rst_sum_value_Lst !== null) {
+                    rst_sum_value_Lst.push(sumVal);
+                }
+            }
+        }
+    }
+    function private_addContentValue(dispValueIdxLst_Content, sortedContentSequence, segIdx, counterRowRec, maxRowRec, counterColRec, maxColRec, page_seg_map, pageIdx) {
+        if (dispValueIdxLst_Content !== null) {
+            page_seg_map.push([pageIdx,segIdx]);
+            let arrContents = [];
+            if (sortedContentSequence !== null) {
+                let arrAllContent = sortedContentSequence[segIdx];
+                for (let i = 0; i < maxRowRec; i++) {
+                    arrContents.push([]);
+                    for (let j = 0; j < maxColRec; j++) {
+                        if (arrAllContent.length <= counterRowRec + i || arrAllContent[counterRowRec + i].length <= counterColRec + j) {
+                            arrContents[i][j] = JV.BLANK_VALUE_INDEX;
+                        } else {
+                            arrContents[i][j] = arrAllContent[counterRowRec + i][counterColRec + j];
+                        }
+                    }
+                }
+                dispValueIdxLst_Content.push(arrContents);
+            } else {
+                //should push blank value index rather than null
+                for (let i = 0; i < maxRowRec; i++) {
+                    arrContents.push([]);
+                    for (let j = 0; j < maxColRec; j++) {
+                        arrContents[i][j] = JV.BLANK_VALUE_INDEX;
+                    }
+                }
+                dispValueIdxLst_Content.push(arrContents);
+            }
+        }
+    }
+    function private_SortAndOptimize(rptTpl, dataObj, dataSeq, sortTab, rstFieldsIdx, $CURRENT_RPT) {
+        let result = [];
+        let tab = rptTpl[JV.NODE_CROSS_INFO][sortTab];
+        if (tab) {
+            let sIDX = 0;
+            //1. prepare and sort by tab-field
+            let fields = [];
+            JpcFieldHelper.findAndPutDataFieldIdx(rptTpl, tab[JV.PROP_CROSS_FIELDS], fields, rstFieldsIdx);
+            let data_details = dataObj[JV.DATA_DETAIL_DATA];
+            JpcCrossTabHelper.sortTabFields(fields, rstFieldsIdx, data_details, dataSeq, $CURRENT_RPT);
+            //2. distinguish sort tab fields value
+            let b1 = false;
+            for (let i = 0; i < dataSeq.length; i++) {
+                sIDX = 0;
+                let segArr = [];
+                if (dataSeq[i].length === 1) {
+                    JpcCrossTabHelper.pushToSeg(segArr, dataSeq, i, 0, 1);
+                } else {
+                    for (let j = 1; j < dataSeq[i].length; j++) {
+                        b1 = false;
+                        for (let k = 0; k < rstFieldsIdx.length; k++) {
+                            if (fields[k].hasOwnProperty(JV.TAB_FIELD_PROP_SORT)) {
+                                //只有被选择了作为排序字段的才进入排序及优化
+                                if (typeof(rstFieldsIdx[k]) === 'object') {
+                                    let map_data_field = JE.F(rstFieldsIdx[k][JV.PROP_ID], $CURRENT_RPT);
+                                    if (map_data_field[JV.PROP_AD_HOC_DATA][dataSeq[i][j - 1]] !== map_data_field[JV.PROP_AD_HOC_DATA][dataSeq[i][j]]) {
+                                        b1 = true;
+                                        break;
+                                    }
+                                } else {
+                                    if (data_details[rstFieldsIdx[k]][dataSeq[i][j - 1]] !== data_details[rstFieldsIdx[k]][dataSeq[i][j]]) {
+                                        b1 = true;
+                                        break;
+                                    }
+                                }
+                            }
+                        }
+                        if (b1) {
+                            JpcCrossTabHelper.pushToSeg(segArr, dataSeq, i, sIDX, j);
+                            sIDX = j;
+                            if (j === dataSeq[i].length - 1) {
+                                JpcCrossTabHelper.pushToSeg(segArr, dataSeq, i, j, dataSeq[i].length);
+                            }
+                        } else if (j === dataSeq[i].length - 1) {
+                            JpcCrossTabHelper.pushToSeg(segArr, dataSeq, i, sIDX, dataSeq[i].length);
+                        }
+                    }
+                }
+                if (segArr.length > 0) result.push(segArr);
+            }
+        }
+        return result;
+    }
+    function private_SortForDisplayContent(rptTpl, rowSeq, colSeq, rstFieldsIdx){
+        let result = [];
+        let tab = rptTpl[JV.NODE_CROSS_INFO][JV.NODE_CROSS_CONTENT];
+        if (tab) {
+            JpcFieldHelper.findAndPutDataFieldIdx(rptTpl, tab[JV.PROP_CROSS_FIELDS], null, rstFieldsIdx);
+        }
+        for (let i = 0; i < rowSeq.length; i++) {
+            let rl = rowSeq[i], cl = colSeq[i];
+            let ds = [];
+            //1. initialize to blank value index
+            for (let j = 0; j < rl.length; j++) {
+                ds.push([]);
+                for (let k = 0; k < cl.length; k++) {
+                    ds[j].push(JV.BLANK_VALUE_INDEX);
+                }
+            }
+            //2. then fill up the right index
+            for (let j = 0; j < rl.length; j++) {
+                let ra = rl[j];
+                for (let k = 0; k < ra.length; k++) {
+                    let colIdx = JpcCrossTabHelper.getColIDX(cl, ra[k]);
+                    if (colIdx >= 0) {
+                        ds[j][colIdx] = ra[k];
+                    }
+                }
+            }
+            result.push(ds);
+        }
+        return result;
+    }
+
+    let JpcCrossTabResult = {};
+    JpcCrossTabResult.initialize = function() {
+        let me = this;
+        me.dispValueIdxLst_Row = [];
+        me.dispValueIdxLst_Col = [];
+        me.dispValueIdxLst_Content = [];
+        me.dispSerialIdxLst_Row = [];
+        me.col_sum_fields_idx = [];
+        me.col_sum_fields_value_total = [];
+        me.dispSumValueLst_Col = [];
+        me.page_seg_map = [];
+        me.row_fields_idx = [];
+        me.row_fields_adhoc_idx = [];
+        me.col_fields_idx = [];
+        me.content_fields_idx = [];
+        me.row_extension_fields_idx = [];
+        me.row_sum_extension_fields_idx = [];
+        me.crsOrient = JV.PAGE_ORIENTATION_V_FIRST;
+        me.pageStatusLst = [];
+        me.paging_option = JV.PAGING_OPTION_NORMAL;
+    };
+    JpcCrossTabResult.sorting = function(rptTpl, dataObj, dataSeq, $CURRENT_RPT) {
+        let me = this;
+        //IMPORTANT: the data should be sorted in SQL/NoSQL level!
+        me.sortedRowSequence = private_SortAndOptimize(rptTpl, dataObj, dataSeq, JV.NODE_CROSS_ROW, me.row_fields_idx, $CURRENT_RPT);
+        private_SortAndOptimize(rptTpl, dataObj, dataSeq, JV.NODE_CROSS_ROW_AD_HOC, me.row_fields_adhoc_idx, $CURRENT_RPT);
+        me.sortedColSequence = private_SortAndOptimize(rptTpl, dataObj, dataSeq, JV.NODE_CROSS_COL, me.col_fields_idx, $CURRENT_RPT);
+        me.sortedContentSequence = private_SortForDisplayContent(rptTpl, me.sortedRowSequence, me.sortedColSequence, me.content_fields_idx);
+        JpcFieldHelper.findAndPutDataFieldIdx(rptTpl, rptTpl[JV.NODE_CROSS_INFO][JV.NODE_CROSS_COL_SUM][JV.PROP_CROSS_FIELDS], null, me.col_sum_fields_idx);
+        //pre-sum the data(for col sum display)
+        let data_details = dataObj[JV.DATA_DETAIL_DATA],
+            data_fields = [];
+        for (let i = 0; i < me.col_sum_fields_idx.length; i++) {
+            let data_field = null;
+            if (typeof me.col_sum_fields_idx[i] === 'object') {
+                let exField = JE.F(me.col_sum_fields_idx[i][JV.PROP_ID], $CURRENT_RPT);
+                if (exField) {
+                    data_field = exField[JV.PROP_AD_HOC_DATA];
+                }
+            } else {
+                data_field = data_details[me.col_sum_fields_idx[i]];
+            }
+            data_fields.push(data_field);
+        }
+        for (let i = 0; i < me.sortedRowSequence.length; i++) { //seg level
+            if (me.sortedRowSequence[i].length > 0) {
+                me.col_sum_fields_value_total.push([]);
+                for (let j = 0; j < me.sortedRowSequence[i].length; j++) {
+                    let rowGrandTotal = [];
+                    for (let di = 0; di < data_fields.length; di++) {
+                        rowGrandTotal.push(0.0);
+                        for (let k = 0; k < me.sortedRowSequence[i][j].length; k++) {
+                            //3. start to sum
+                            rowGrandTotal[di] = rowGrandTotal[di] + 1.0 * JpcFieldHelper.getValue(data_fields[di], me.sortedRowSequence[i][j][k]);
+                        }
+                    }
+                    me.col_sum_fields_value_total[i].push(rowGrandTotal);
+                }
+            }
+
+        }
+    };
+    JpcCrossTabResult.preSetupPages = function(rptTpl, defProperties, option) {
+        let me = this, rst = 0;
+        me.paging_option = option||JV.PAGING_OPTION_NORMAL;
+        //1. original initialize
+        let maxRowRec = 1, maxColRec = 1, counterRowRec = 0, counterColRec = 0, pageIdx = 0, segCnt = me.sortedContentSequence.length;
+        let pageStatus = [true, true, false, true, false, false, false, false];
+        //2. calculate the page info one by one
+        let bands = JpcBand.createNew(rptTpl, defProperties);
+        function private_resetBandArea() {
+            JpcBandHelper.setBandArea(bands, rptTpl, pageStatus);
+            maxRowRec = JpcCrossTabHelper.getMaxRowsPerPage(bands, rptTpl);
+            maxColRec = JpcCrossTabHelper.getMaxColsPerPage(bands, rptTpl);
+        }
+        JpcFieldHelper.findAndPutDataFieldIdx(rptTpl, rptTpl[JV.NODE_CROSS_INFO][JV.NODE_CROSS_ROW_EXT][JV.PROP_CROSS_FIELDS], null, me.row_extension_fields_idx);
+        JpcFieldHelper.findAndPutDataFieldIdx(rptTpl, rptTpl[JV.NODE_CROSS_INFO][JV.NODE_CROSS_ROW_SUM_EXT][JV.PROP_CROSS_FIELDS], null, me.row_sum_extension_fields_idx);
+        if (me.paging_option === JV.PAGING_OPTION_INFINITY) {
+            /*
+             rst = segCnt;
+             pageStatus[JV.STATUS_SEGMENT_START] = true;
+             pageStatus[JV.STATUS_SEGMENT_END] = true;
+             pageStatus[JV.STATUS_CROSS_ROW_END] = true;
+             pageStatus[JV.STATUS_CROSS_COL_END] = true;
+             for (let segIdx = 0; segIdx < segCnt; segIdx++) {
+             if (segIdx === segCnt - 1) {
+             pageStatus[JV.STATUS_REPORT_END] = true;
+             }
+             if (segIdx > 0) {
+             pageStatus[JV.STATUS_REPORT_START] = false;
+             }
+             me.pageStatusLst.push(pageStatus.slice(0));
+             pageIdx++;
+             private_addTabValue(me.dispValueIdxLst_Row, me.sortedRowSequence, segIdx, 0, me.sortedRowSequence[segIdx].length, me.dispSerialIdxLst_Row, me.col_sum_fields_value_total, me.dispSumValueLst_Col);
+             private_addTabValue(me.dispValueIdxLst_Col, me.sortedColSequence, segIdx, 0, me.sortedColSequence[segIdx].length, null, null, null);
+             private_addContentValue(me.dispValueIdxLst_Content, me.sortedContentSequence, segIdx, 0, me.sortedRowSequence[segIdx].length, 0, me.sortedColSequence[segIdx].length, me.page_seg_map, pageIdx);
+             }
+             //*/
+        } else {
+            for (let segIdx = 0; segIdx < segCnt; segIdx++) {
+                //2.1. seg level initialize
+                private_resetBandArea();
+                let orgMaxRowRec = maxRowRec, orgMaxColRec = maxColRec;
+                let rowSplitCnt = Math.ceil(1.0 * me.sortedRowSequence[segIdx].length / maxRowRec);
+                let colSplitCnt = Math.ceil(1.0 * me.sortedColSequence[segIdx].length / maxColRec);
+                pageStatus[JV.STATUS_CROSS_ROW_END] = true;
+                private_resetBandArea();
+                let hasAdHocRow = !JpcCrossTabHelper.chkTabEnd(JV.NODE_CROSS_ROW_SUM, rptTpl, bands, me.sortedRowSequence, segIdx, (rowSplitCnt - 1) * orgMaxRowRec, maxRowRec);
+                if (hasAdHocRow) {
+                    hasAdHocRow = !JpcCrossTabHelper.chkTabEnd(JV.NODE_CROSS_ROW_EXT, rptTpl, bands, me.sortedRowSequence, segIdx, (rowSplitCnt - 1) * orgMaxRowRec, maxRowRec);
+                }
+                pageStatus[JV.STATUS_CROSS_ROW_END] = false;
+                pageStatus[JV.STATUS_CROSS_COL_END] = true;
+                private_resetBandArea();
+                let hasAdHocCol = !JpcCrossTabHelper.chkTabEnd(JV.NODE_CROSS_COL_SUM, rptTpl, bands, me.sortedColSequence, segIdx, (colSplitCnt - 1) * orgMaxColRec, maxColRec);
+                pageStatus[JV.STATUS_CROSS_COL_END] = false;
+                private_resetBandArea();
+                if (hasAdHocRow) rowSplitCnt++;
+                if (hasAdHocCol) colSplitCnt++;
+                //2.2
+                if (rptTpl[JV.NODE_MAIN_INFO][JV.NODE_PAGE_INFO][JV.PROP_CROSS_DISPLAY_ORDER] === JV.PAGE_ORIENTATION_H_FIRST) {
+                    for (let rowIdx = 0; rowIdx < rowSplitCnt; rowIdx++) {
+                        pageStatus[JV.STATUS_CROSS_ROW_END] = (rowIdx === (rowSplitCnt - 1));
+                        private_resetBandArea();
+                        counterRowRec = orgMaxRowRec * rowIdx;
+                        let currentSortedRowSequence = me.sortedRowSequence;
+                        let currentSortedContentSequence = me.sortedContentSequence;
+                        if (hasAdHocRow && rowIdx === (rowSplitCnt - 1)) {
+                            currentSortedRowSequence = null;
+                            currentSortedContentSequence = null;
+                            counterRowRec = 0;
+                        }
+                        for (let colIdx = 0; colIdx < colSplitCnt; colIdx++) {
+                            pageStatus[JV.STATUS_CROSS_COL_END] = (colIdx === (colSplitCnt - 1));
+                            private_resetBandArea();
+                            counterColRec = orgMaxColRec * colIdx;
+                            let currentSortedColSequence = me.sortedColSequence;
+                            if (hasAdHocCol && colIdx === (colSplitCnt - 1)) {
+                                currentSortedColSequence = null;
+                                currentSortedContentSequence = null;
+                                counterColRec = 0;
+                            }
+                            me.pageStatusLst.push(pageStatus.slice(0));
+                            pageIdx++;
+                            private_addTabValue(me.dispValueIdxLst_Row, currentSortedRowSequence, segIdx, counterRowRec, maxRowRec, me.dispSerialIdxLst_Row, me.col_sum_fields_value_total, me.dispSumValueLst_Col);
+                            private_addTabValue(me.dispValueIdxLst_Col, currentSortedColSequence, segIdx, counterColRec, maxColRec, null, null, null);
+                            private_addContentValue(me.dispValueIdxLst_Content, currentSortedContentSequence, segIdx, counterRowRec, maxRowRec, counterColRec, maxColRec, me.page_seg_map, pageIdx);
+                        }
+                    }
+                } else {
+                    for (let colIdx = 0; colIdx < colSplitCnt; colIdx++) {
+                        pageStatus[JV.STATUS_CROSS_COL_END] = (colIdx === (colSplitCnt - 1));
+                        private_resetBandArea();
+                        counterColRec = orgMaxColRec * colIdx;
+                        let currentSortedContentSequence = me.sortedContentSequence;
+                        let currentSortedColSequence = me.sortedColSequence;
+                        if (hasAdHocCol && colIdx === (colSplitCnt - 1)) {
+                            currentSortedColSequence = null;
+                            currentSortedContentSequence = null;
+                            counterColRec = 0;
+                        }
+                        for (let rowIdx = 0; rowIdx < rowSplitCnt; rowIdx++) {
+                            pageStatus[JV.STATUS_CROSS_ROW_END] = (rowIdx === (rowSplitCnt - 1));
+                            private_resetBandArea();
+                            me.pageStatusLst.push(pageStatus.slice(0));
+                            pageIdx++;
+                            counterRowRec = orgMaxRowRec * rowIdx;
+                            let currentSortedRowSequence = me.sortedRowSequence;
+                            if (hasAdHocRow && rowIdx === (rowSplitCnt - 1)) {
+                                currentSortedRowSequence = null;
+                                currentSortedContentSequence = null;
+                                counterRowRec = 0;
+                            }
+                            private_addTabValue(me.dispValueIdxLst_Row, currentSortedRowSequence, segIdx, counterRowRec, maxRowRec, me.dispSerialIdxLst_Row, me.col_sum_fields_value_total, me.dispSumValueLst_Col);
+                            private_addTabValue(me.dispValueIdxLst_Col, currentSortedColSequence, segIdx, counterColRec, maxColRec, null, null, null);
+                            private_addContentValue(me.dispValueIdxLst_Content, currentSortedContentSequence, segIdx, counterRowRec, maxRowRec, counterColRec, maxColRec, me.page_seg_map, pageIdx);
+                        }
+                    }
+                }
+                JpcCrossTabHelper.initialPageStatus(pageStatus);
+            }
+            //3. set pageSeq and return the result
+            rst = pageIdx;
+        }
+        bands = null;
+        return rst;
+    };
+    JpcCrossTabResult.outputAsPreviewPage = function (rptTpl, bands, controls, $CURRENT_RPT, customizeCfg) {
+        let me = this, rst = [];
+        let pageStatus = [true, true, true, true, true, true, true, true];
+        me.pageStatusLst.push(pageStatus);
+        // JpcFieldHelper.findAndPutDataFieldIdx(rptTpl, rptTpl[JV.NODE_FLOW_INFO][JV.NODE_FLOW_CONTENT][JV.PROP_FLOW_FIELDS], null, me.disp_fields_idx, false);
+        JpcBandHelper.setBandArea(bands, rptTpl, pageStatus, true, false);
+        let maxRowRec = JpcCrossTabHelper.getMaxRowsPerPage(bands, rptTpl);
+        let maxColRec = JpcCrossTabHelper.getMaxColsPerPage(bands, rptTpl);
+        let unitFactor = JpcCommonHelper.getUnitFactor(rptTpl);
+        //1. 交叉行
+        rst = rst.concat(me.outputPreviewRowTab(rptTpl, bands, controls, $CURRENT_RPT, customizeCfg, maxRowRec, unitFactor));
+        //2. 交叉列
+        rst = rst.concat(me.outputPreviewColTab(rptTpl, bands, controls, $CURRENT_RPT, customizeCfg, maxColRec, unitFactor));
+        //3. 交叉数据
+        rst = rst.concat(me.outputPreviewContent(rptTpl, bands, controls, $CURRENT_RPT, customizeCfg, maxRowRec, maxColRec, unitFactor));
+        //4. 交叉行拓展
+        rst = rst.concat(me.outputPreviewTabExt(rptTpl, bands, controls, $CURRENT_RPT, customizeCfg, maxColRec, unitFactor));
+        //5. 交叉行拓展合计
+        rst = rst.concat(me.outputPreviewSumTabExt(rptTpl, bands, controls, $CURRENT_RPT, customizeCfg, unitFactor));
+        //6. 交叉列合计
+        rst = rst.concat(me.outputPreviewTabSum(rptTpl, bands, controls, $CURRENT_RPT, customizeCfg, maxRowRec, JV.NODE_CROSS_COL_SUM, unitFactor));
+        //7. 离散
+        rst = rst.concat(JpcDiscreteHelper.outputPreviewDiscreteInfo(rptTpl[JV.NODE_CROSS_INFO][JV.NODE_DISCRETE_INFO], bands, unitFactor, pageStatus));
+        return rst;
+    };
+    JpcCrossTabResult.outputPreviewRowTab = function(rptTpl, bands, controls, $CURRENT_RPT, customizeCfg, maxRowRec, unitFactor) {
+        let rst = this.private_OutputPreviewCommon(rptTpl, bands, controls, $CURRENT_RPT, customizeCfg, maxRowRec, 1, rptTpl[JV.NODE_CROSS_INFO][JV.NODE_CROSS_ROW], unitFactor);
+        rst = rst.concat(this.private_OutputPreviewCommon(rptTpl, bands, controls, $CURRENT_RPT, customizeCfg, maxRowRec, 1, rptTpl[JV.NODE_CROSS_INFO][JV.NODE_CROSS_ROW_AD_HOC], unitFactor));
+        return rst;
+    };
+    JpcCrossTabResult.outputPreviewColTab = function(rptTpl, bands, controls, $CURRENT_RPT, customizeCfg, maxColRec, unitFactor) {
+        return this.private_OutputPreviewCommon(rptTpl, bands, controls, $CURRENT_RPT, customizeCfg, 1, maxColRec, rptTpl[JV.NODE_CROSS_INFO][JV.NODE_CROSS_COL], unitFactor);
+    };
+    JpcCrossTabResult.outputPreviewContent = function(rptTpl, bands, controls, $CURRENT_RPT, customizeCfg, maxRowRec, maxColRec, unitFactor) {
+        return this.private_OutputPreviewCommon(rptTpl, bands, controls, $CURRENT_RPT, customizeCfg, maxRowRec, maxColRec, rptTpl[JV.NODE_CROSS_INFO][JV.NODE_CROSS_CONTENT], unitFactor);
+    };
+    JpcCrossTabResult.outputPreviewTabSum = function(rptTpl, bands, controls, $CURRENT_RPT, customizeCfg, maxRowRec, tabNodeName, unitFactor) {
+        return this.private_OutputPreviewCommon(rptTpl, bands, controls, $CURRENT_RPT, customizeCfg, maxRowRec, 1, rptTpl[JV.NODE_CROSS_INFO][tabNodeName], unitFactor);
+    };
+    JpcCrossTabResult.outputPreviewTabExt = function(rptTpl, bands, controls, $CURRENT_RPT, customizeCfg, maxColRec, unitFactor) {
+        //交叉行拓展
+        return this.private_OutputPreviewCommon(rptTpl, bands, controls, $CURRENT_RPT, customizeCfg, 1, maxColRec, rptTpl[JV.NODE_CROSS_INFO][JV.NODE_CROSS_ROW_EXT], unitFactor);
+    };
+    JpcCrossTabResult.outputPreviewSumTabExt = function(rptTpl, bands, controls, $CURRENT_RPT, customizeCfg, unitFactor) {
+        //交叉行拓展合计
+        return this.private_OutputPreviewCommon(rptTpl, bands, controls, $CURRENT_RPT, customizeCfg, 1, 1, rptTpl[JV.NODE_CROSS_INFO][JV.NODE_CROSS_ROW_SUM_EXT], unitFactor);
+    };
+    JpcCrossTabResult.private_OutputPreviewCommon = function(rptTpl, bands, controls, $CURRENT_RPT, customizeCfg, maxRowRec, maxColRec, tab, unitFactor) {
+        let me = this, rst = [];
+        let band = (tab)?bands[tab[JV.PROP_BAND_NAME]]:null;
+        if (band) {
+            let tab_fields = tab[JV.PROP_CROSS_FIELDS];
+            for (let rowIdx = 0; rowIdx < maxRowRec; rowIdx++) {
+                for (let i = 0; i < tab_fields.length; i++) {
+                    let tab_field = tab_fields[i];
+                    if (!(tab_field[JV.PROP_HIDDEN])) {
+                        for (let colIdx = 0; colIdx < maxColRec; colIdx++) {
+                            if (tab_field[JV.PROP_IS_SERIAL]) {
+                                rst.push(me.outputTabField(band, tab_field, [rowIdx + 1], 0, -1, maxRowRec, rowIdx, maxColRec, colIdx, unitFactor, false, controls));
+                            } else {
+                                rst.push(me.outputTabField(band, tab_field, null, -1, -1, maxRowRec, rowIdx, maxColRec, colIdx, unitFactor, false, controls));
+                            }
+                        }
+                    }
+                }
+                if (tab[JV.PROP_TEXTS]) {
+                    for (let j = 0; j < tab[JV.PROP_TEXTS].length; j++) {
+                        for (let colIdx = 0; colIdx < maxColRec; colIdx++) {
+                            rst.push(JpcTextHelper.outputText(tab[JV.PROP_TEXTS][j], band, unitFactor, maxRowRec, rowIdx, maxColRec, colIdx, 1, 0));
+                        }
+                    }
+                }
+            }
+        }
+        return rst;
+    };
+    JpcCrossTabResult.outputAsSimpleJSONPage = function(rptTpl, dataObj, page, bands, controls, $CURRENT_RPT, customizeCfg) {
+        let me = this, rst = [], tabRstLst = [];
+        let unitFactor = JpcCommonHelper.getUnitFactor(rptTpl);
+        if (me.paging_option === JV.PAGING_OPTION_INFINITY) {
+            /*
+             let segIdx = page - 1;
+             //1 calculate the band position
+             JpcBandHelper.setBandArea(bands, rptTpl, me.pageStatusLst[page - 1]);
+             //2. then reset the band height
+             let tab = rptTpl[JV.NODE_CROSS_INFO][JV.NODE_CROSS_CONTENT];
+             let crossContentBand = bands[tab[JV.PROP_BAND_NAME]];
+             let actH = JpcCrossTabHelper.getActualRowsHeight(bands, rptTpl, me.sortedRowSequence, page);
+             let actW = JpcCrossTabHelper.getActualColsWidth(bands, rptTpl, me.sortedColSequence, page);
+             let offsetY = actH - (crossContentBand.Bottom - crossContentBand.Top);
+             let offsetX = actW - (crossContentBand.Right - crossContentBand.Left);
+             JpcBandHelper.resetBandPos(rptTpl[JV.NODE_BAND_COLLECTION], bands, crossContentBand, offsetX, offsetY);
+
+             //2.1 Row-Tab
+             tabRstLst.push(me.outputRowTab(rptTpl, dataObj, page, bands, unitFactor, controls));
+             //2.2 Col-Tab
+             tabRstLst.push(me.outputColTab(rptTpl, dataObj, page, bands, unitFactor, controls));
+             //2.3 Content-Tab
+             tabRstLst.push(me.outputContent(rptTpl, dataObj, page, bands, unitFactor, controls));
+             //2.4 Sum-Tab Row
+             //2.4 Sum-tab Col
+             tabRstLst.push(me.outputTabSum(rptTpl, dataObj, page, bands, unitFactor, JV.NODE_CROSS_COL_SUM, controls));
+             //2.x row tab ext
+             tabRstLst.push(me.outputTabExt(rptTpl, dataObj, page, bands, unitFactor, controls));
+             tabRstLst.push(me.outputSumTabExt(rptTpl, dataObj, page, bands, unitFactor, segIdx, controls));
+             //2.5 Discrete
+             tabRstLst.push(JpcDiscreteHelper.outputDiscreteInfo(rptTpl[JV.NODE_CROSS_INFO][JV.NODE_DISCRETE_INFO], bands, dataObj, unitFactor, me.pageStatusLst[page - 1], segIdx, 1, 0, $CURRENT_RPT, customizeCfg));
+             //*/
+        } else {
+            let segIdx = JpcCommonHelper.getSegIdxByPageIdx(page, me.page_seg_map);
+            //1 calculate the band position
+            JpcBandHelper.setBandArea(bands, rptTpl, me.pageStatusLst[page - 1]);
+            //2. start to output detail-part
+            //2.1 Row-Tab
+            //tabRstLst.push(me.outputRowTab(rptTpl, dataObj, page, bands, unitFactor, controls, $CURRENT_RPT, customizeCfg));
+            tabRstLst.push(me.outputRowTabCommon(rptTpl, dataObj, page, bands, JV.NODE_CROSS_ROW, me.row_fields_idx, unitFactor, controls, $CURRENT_RPT, customizeCfg));
+            tabRstLst.push(me.outputRowTabCommon(rptTpl, dataObj, page, bands, JV.NODE_CROSS_ROW_AD_HOC, me.row_fields_adhoc_idx, unitFactor, controls, $CURRENT_RPT, customizeCfg));
+            //2.2 Col-Tab
+            tabRstLst.push(me.outputColTab(rptTpl, dataObj, page, bands, unitFactor, controls, $CURRENT_RPT, customizeCfg));
+            //2.3 Content-Tab
+            tabRstLst.push(me.outputContent(rptTpl, dataObj, page, bands, unitFactor, controls, $CURRENT_RPT, customizeCfg));
+            //2.4 Sum-Tab Row
+            //2.4 Sum-tab Col
+            tabRstLst.push(me.outputTabSum(rptTpl, dataObj, page, bands, unitFactor, JV.NODE_CROSS_COL_SUM, controls, $CURRENT_RPT, customizeCfg));
+            //2.x row tab ext
+            tabRstLst.push(me.outputTabExt(rptTpl, dataObj, page, bands, unitFactor, controls, $CURRENT_RPT, customizeCfg));
+            tabRstLst.push(me.outputSumTabExt(rptTpl, dataObj, page, bands, unitFactor, segIdx, controls, $CURRENT_RPT, customizeCfg));
+            //2.5 Discrete
+            tabRstLst.push(JpcDiscreteHelper.outputDiscreteInfo(rptTpl[JV.NODE_CROSS_INFO][JV.NODE_DISCRETE_INFO], bands, dataObj, unitFactor, me.pageStatusLst[page - 1], segIdx, 1, 0, $CURRENT_RPT, customizeCfg));
+        }
+        for (let i = 0; i < tabRstLst.length; i++) {
+            rst = rst.concat(tabRstLst[i]);
+            tabRstLst[i] = null;
+        }
+        return rst;
+    };
+    JpcCrossTabResult.outputRowTabCommon = function(rptTpl, dataObj, page, bands, tabStr, rowFieldsIdxArr, unitFactor, controls, $CURRENT_RPT, customizeCfg) {
+        let me = this, rst = [];
+        let tab = rptTpl[JV.NODE_CROSS_INFO][tabStr];
+        let band = (tab)?bands[tab[JV.PROP_BAND_NAME]]:null;
+        if (band) {
+            let pageStatus = me.pageStatusLst[page - 1];
+            if (pageStatus[band[JV.BAND_PROP_DISPLAY_TYPE]] === true) {
+                let tab_fields = tab[JV.PROP_CROSS_FIELDS];
+                let data_details = dataObj[JV.DATA_DETAIL_DATA];
+                let valuesIdx = me.dispValueIdxLst_Row[page - 1];
+                let serialsIdx = me.dispSerialIdxLst_Row[page - 1];
+                let flexiblePrecisionRefObj = null, flexibleRefField = null, precision_ref_data = null;
+                for (let i = 0; i < rowFieldsIdxArr.length; i++) {
+                    let tab_field = tab_fields[i];
+                    if (!(tab_field[JV.PROP_HIDDEN])) {
+                        let data_field = null;
+                        let map_data_field = JE.F(tab_field[JV.PROP_FIELD_ID], $CURRENT_RPT);
+                        if (typeof rowFieldsIdxArr[i] !== 'object') {
+                            data_field = data_details[rowFieldsIdxArr[i]];
+                        } else {
+                            if (map_data_field) {
+                                data_field = map_data_field[JV.PROP_AD_HOC_DATA];
+                            }
+                        }
+                        let rows = valuesIdx.length;
+                        for (let rowIdx = 0; rowIdx < rows; rowIdx++) {
+                            if (map_data_field && map_data_field[JV.PROP_PRECISION] && map_data_field.flexiblePrecisionRefObj) {
+                                if (flexiblePrecisionRefObj === null) {
+                                    flexiblePrecisionRefObj = {};
+                                    flexibleRefField = JE.F(map_data_field[JV.PROP_PRECISION][JV.PROP_FLEXIBLE_REF_FILED_ID], $CURRENT_RPT);
+                                    precision_ref_data = dataObj[map_data_field.DataNodeName][flexibleRefField.DataSeq];
+                                    for (let decimalObj of map_data_field.flexiblePrecisionRefObj) {
+                                        flexiblePrecisionRefObj["refUnit_" + decimalObj.unit] = decimalObj.decimal;
+                                    }
+                                }
+                                JpcFieldHelper.resetFlexibleFormat(tab_field, precision_ref_data, flexiblePrecisionRefObj, valuesIdx[rowIdx], customizeCfg);
+                            } else {
+                                if (rowIdx === 0) JpcFieldHelper.resetFormat(tab_field, map_data_field, customizeCfg);
+                            }
+                            rst.push(me.outputTabField(band, tab_field, data_field, valuesIdx[rowIdx], serialsIdx[rowIdx], rows, rowIdx, 1, 0, unitFactor, true, controls));
+                        }
+                    }
+                }
+            }
+        }
+        return rst;
+    };
+    JpcCrossTabResult.outputColTab = function(rptTpl, dataObj, page, bands, unitFactor, controls, $CURRENT_RPT, customizeCfg) {
+        let me = this, rst = [], firstTextOutput = true;
+        let tab = rptTpl[JV.NODE_CROSS_INFO][JV.NODE_CROSS_COL];
+        let band = bands[tab[JV.PROP_BAND_NAME]];
+        if (band) {
+            let pageStatus = me.pageStatusLst[page - 1];
+            if (pageStatus[band[JV.BAND_PROP_DISPLAY_TYPE]] === true) {
+                let tab_fields = tab[JV.PROP_CROSS_FIELDS];
+                let data_details = dataObj[JV.DATA_DETAIL_DATA];
+                let valuesIdx = me.dispValueIdxLst_Col[page - 1];
+                let flexiblePrecisionRefObj = null, flexibleRefField = null, precision_ref_data = null;
+                for (let i = 0; i < me.col_fields_idx.length; i++) {
+                    let tab_field = tab_fields[i];
+                    if (!(tab_field[JV.PROP_HIDDEN])) {
+                        let mergedRst = [];
+                        let data_field = null;
+                        let map_data_field = JE.F(tab_field[JV.PROP_FIELD_ID], $CURRENT_RPT);
+                        if (typeof me.col_fields_idx[i] !== 'object') {
+                            data_field = data_details[me.col_fields_idx[i]];
+                        } else {
+                            if (map_data_field) {
+                                data_field = map_data_field[JV.PROP_AD_HOC_DATA];
+                            }
+                        }
+                        let cols = valuesIdx.length;
+                        for (let colIdx = 0; colIdx < cols; colIdx++) {
+                            if (map_data_field && map_data_field[JV.PROP_PRECISION] && map_data_field.flexiblePrecisionRefObj) {
+                                if (flexiblePrecisionRefObj === null) {
+                                    flexiblePrecisionRefObj = {};
+                                    flexibleRefField = JE.F(map_data_field[JV.PROP_PRECISION][JV.PROP_FLEXIBLE_REF_FILED_ID], $CURRENT_RPT);
+                                    precision_ref_data = dataObj[map_data_field.DataNodeName][flexibleRefField.DataSeq];
+                                    for (let decimalObj of map_data_field.flexiblePrecisionRefObj) {
+                                        flexiblePrecisionRefObj["refUnit_" + decimalObj.unit] = decimalObj.decimal;
+                                    }
+                                }
+                                JpcFieldHelper.resetFlexibleFormat(tab_field, precision_ref_data, flexiblePrecisionRefObj, valuesIdx[colIdx], customizeCfg);
+                            } else {
+                                if (colIdx === 0) JpcFieldHelper.resetFormat(tab_field, map_data_field, customizeCfg);
+                            }
+                            mergedRst.push(me.outputTabField(band, tab_field, data_field, valuesIdx[colIdx], -1, 1, 0, cols, colIdx, unitFactor, false, controls));
+                            //rst.push(me.outputTabField(band, tab_field, data_field, valuesIdx[colIdx], -1, 1, 0, cols, colIdx, unitFactor, false, controls));
+                            //2. output texts
+                            if (firstTextOutput) {
+                                if (tab[JV.PROP_TEXT]) {
+                                    // mergedRst.push(JpcTextHelper.outputText(tab[JV.PROP_TEXT], band, unitFactor, 1, 0, cols, colIdx, 1, 0));
+                                    rst.push(JpcTextHelper.outputText(tab[JV.PROP_TEXT], band, unitFactor, 1, 0, cols, colIdx, 1, 0));
+                                }
+                                if (tab[JV.PROP_TEXTS]) {
+                                    for (let j = 0; j < tab[JV.PROP_TEXTS].length; j++) {
+                                        // mergedRst.push(JpcTextHelper.outputText(tab[JV.PROP_TEXTS][j], band, unitFactor, 1, 0, cols, colIdx, 1, 0));
+                                        rst.push(JpcTextHelper.outputText(tab[JV.PROP_TEXTS][j], band, unitFactor, 1, 0, cols, colIdx, 1, 0));
+                                    }
+                                }
+                            }
+                        }
+                        firstTextOutput = false;
+                        //判断是否需要合并
+                        if (tab_field[JV.PROP_IS_MERGE] && mergedRst.length > 1) {
+                            let lastCell = mergedRst[mergedRst.length - 1];
+                            for (let mergeIdx = mergedRst.length - 2; mergeIdx >= 0; mergeIdx--) {
+                                if (lastCell[JV.PROP_VALUE] === mergedRst[mergeIdx][JV.PROP_VALUE]) {
+                                    mergedRst[mergeIdx][JV.PROP_AREA][JV.PROP_RIGHT] = lastCell[JV.PROP_AREA][JV.PROP_RIGHT];
+                                    mergedRst.splice(mergeIdx + 1, 1);
+                                }
+                                lastCell = mergedRst[mergeIdx];
+                            }
+                        }
+                        rst = rst.concat(mergedRst);
+                    }
+                }
+            }
+        }
+        return rst;
+    };
+    JpcCrossTabResult.outputContent = function (rptTpl, dataObj, page, bands, unitFactor, controls, $CURRENT_RPT, customizeCfg) {
+        let me = this, rst = [];
+        let tab = rptTpl[JV.NODE_CROSS_INFO][JV.NODE_CROSS_CONTENT];
+        let band = bands[tab[JV.PROP_BAND_NAME]];
+        if (band) {
+            let pageStatus = me.pageStatusLst[page - 1];
+            if (pageStatus[band[JV.BAND_PROP_DISPLAY_TYPE]] === true) {
+                let tab_fields = tab[JV.PROP_CROSS_FIELDS];
+                let data_details = dataObj[JV.DATA_DETAIL_DATA];
+                let contentValuesIdx = me.dispValueIdxLst_Content[page - 1];
+                let flexiblePrecisionRefObj = null, flexibleRefField = null, precision_ref_data = null;
+                for (let i = 0; i < tab_fields.length; i++) {
+                    let tab_field = tab_fields[i];
+                    let data_field = null;
+                    let map_data_field = JE.F(tab_field[JV.PROP_FIELD_ID], $CURRENT_RPT);
+                    if (typeof me.content_fields_idx[i] !== 'object') {
+                        data_field = data_details[me.content_fields_idx[i]];
+                    } else {
+                        if (map_data_field) {
+                            data_field = map_data_field[JV.PROP_AD_HOC_DATA];
+                        }
+                    }
+                    if (!(tab_field[JV.PROP_HIDDEN])) {
+                        let rows = contentValuesIdx.length;
+                        for (let rowIdx = 0; rowIdx < rows; rowIdx++) {
+                            let cols = contentValuesIdx[rowIdx].length;
+                            for (let colIdx = 0; colIdx < cols; colIdx++) {
+                                if (map_data_field && map_data_field[JV.PROP_PRECISION] && map_data_field.flexiblePrecisionRefObj) {
+                                    if (flexiblePrecisionRefObj === null) {
+                                        flexiblePrecisionRefObj = {};
+                                        flexibleRefField = JE.F(map_data_field[JV.PROP_PRECISION][JV.PROP_FLEXIBLE_REF_FILED_ID], $CURRENT_RPT);
+                                        precision_ref_data = dataObj[map_data_field.DataNodeName][flexibleRefField.DataSeq];
+                                        for (let decimalObj of map_data_field.flexiblePrecisionRefObj) {
+                                            flexiblePrecisionRefObj["refUnit_" + decimalObj.unit] = decimalObj.decimal;
+                                        }
+                                    }
+                                    JpcFieldHelper.resetFlexibleFormat(tab_field, precision_ref_data, flexiblePrecisionRefObj, contentValuesIdx[rowIdx][colIdx], customizeCfg);
+                                } 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));
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        return rst;
+    };
+    JpcCrossTabResult.outputTabSum = function (rptTpl, dataObj, page, bands, unitFactor, tabNodeName, controls, $CURRENT_RPT, customizeCfg) {
+        let me = this, rst = [],
+            tab = rptTpl[JV.NODE_CROSS_INFO][tabNodeName],
+            band = bands[tab[JV.PROP_BAND_NAME]];
+        if (band) {
+            let pageStatus = me.pageStatusLst[page - 1];
+            if (pageStatus[band[JV.BAND_PROP_DISPLAY_TYPE]] === true) {
+                let tab_fields = tab[JV.PROP_CROSS_FIELDS];
+                for (let i = 0; i < me.dispSumValueLst_Col[page - 1].length; i++) {
+                    if (i === 0) {
+                        for (let tfIdx = 0; tfIdx < tab_fields.length; tfIdx++) {
+                            let map_data_field = JE.F(tab_fields[tfIdx][JV.PROP_FIELD_ID], $CURRENT_RPT);
+                            JpcFieldHelper.resetFormat(tab_fields[tfIdx], map_data_field, customizeCfg);
+                        }
+                    }
+                    if (me.dispSumValueLst_Col[page - 1][i] !== null) {
+                        for (let j = 0; j < me.dispSumValueLst_Col[page - 1][i].length; j++) {
+                            let tab_field = tab_fields[j];
+                            let val = me.dispSumValueLst_Col[page - 1][i][j];
+                            let cellItem = JpcCommonOutputHelper.createCommonOutput(tab_field, val, controls);
+                            cellItem[JV.PROP_AREA] = JpcAreaHelper.outputArea(tab_field[JV.PROP_AREA], band, unitFactor, me.dispSumValueLst_Col[page - 1].length, i, 1, 0, 1, 0, true, false);
+                            rst.push(cellItem);
+                        }
+                    } else {
+                        let sumL = 1;
+                        for (let si = 0; si < me.dispSumValueLst_Col.length; si++) {
+                            if (me.dispSumValueLst_Col[si][0] !== null) {
+                                sumL = me.dispSumValueLst_Col[si][0].length;
+                                break;
+                            }
+                        }
+                        for (let j = 0; j < sumL; j++) {
+                            let tab_field = tab_fields[j];
+                            let val = null;
+                            let cellItem = JpcCommonOutputHelper.createCommonOutput(tab_field, val, controls);
+                            cellItem[JV.PROP_AREA] = JpcAreaHelper.outputArea(tab_field[JV.PROP_AREA], band, unitFactor, me.dispSumValueLst_Col[page - 1].length, i, 1, 0, 1, 0, true, false);
+                            rst.push(cellItem);
+                        }
+                    }
+                }
+            }
+        }
+        return rst;
+    };
+    JpcCrossTabResult.outputTabExt = function (rptTpl, dataObj, page, bands, unitFactor, controls, $CURRENT_RPT, customizeCfg) {
+        let me = this, rst = [], firstTextOutput = true,
+            tab = rptTpl[JV.NODE_CROSS_INFO][JV.NODE_CROSS_ROW_EXT];
+        let band = bands[tab[JV.PROP_BAND_NAME]];
+        if (band) {
+            let pageStatus = me.pageStatusLst[page - 1];
+            if (pageStatus[band[JV.BAND_PROP_DISPLAY_TYPE]] === true) {
+                let tab_fields = tab[JV.PROP_CROSS_FIELDS],
+                    data_details = dataObj[JV.DATA_DETAIL_DATA],
+                    valuesIdx = me.dispValueIdxLst_Col[page - 1];
+                for (let i = 0; i < me.row_extension_fields_idx.length; i++) {
+                    let tab_field = tab_fields[i];
+                    let data_field = null;
+                    let map_data_field = JE.F(tab_field[JV.PROP_FIELD_ID], $CURRENT_RPT);
+                    if (typeof me.row_extension_fields_idx[i] !== 'object') {
+                        data_field = data_details[me.row_extension_fields_idx[i]];
+                    } else {
+                        if (map_data_field) {
+                            data_field = map_data_field[JV.PROP_AD_HOC_DATA];
+                        }
+                    }
+
+                    if (!(tab_field[JV.PROP_HIDDEN])) {
+                        let cols = valuesIdx.length;
+                        for (let colIdx = 0; colIdx < cols; colIdx++) {
+                            if (colIdx === 0) JpcFieldHelper.resetFormat(tab_field, map_data_field, customizeCfg);
+                            rst.push(me.outputTabField(band, tab_field, data_field, valuesIdx[colIdx], -1, 1, 0, cols, colIdx, unitFactor, false, controls));
+                            //2. output texts if has
+                            if (firstTextOutput) {
+                                if (tab[JV.PROP_TEXT]) {
+                                    rst.push(JpcTextHelper.outputText(tab[JV.PROP_TEXT], band, unitFactor, 1, 0, cols, colIdx, 1, 0));
+                                }
+                                if (tab[JV.PROP_TEXTS]) {
+                                    for (let j = 0; j < tab[JV.PROP_TEXTS].length; j++) {
+                                        rst.push(JpcTextHelper.outputText(tab[JV.PROP_TEXTS][j], band, unitFactor, 1, 0, cols, colIdx, 1, 0));
+                                    }
+                                }
+                            }
+                        }
+                        firstTextOutput = false;
+                    }
+                }
+            }
+        }
+        return rst;
+    };
+    JpcCrossTabResult.outputSumTabExt = function (rptTpl, dataObj, page, bands, unitFactor, segIdx, controls, $CURRENT_RPT, customizeCfg) {
+        let me = this, rst = [],
+            tab = rptTpl[JV.NODE_CROSS_INFO][JV.NODE_CROSS_ROW_SUM_EXT];
+        let band = bands[tab[JV.PROP_BAND_NAME]];
+        if (band) {
+            let pageStatus = me.pageStatusLst[page - 1];
+            if (pageStatus[band[JV.BAND_PROP_DISPLAY_TYPE]] === true && pageStatus[JV.STATUS_CROSS_ROW_END] === true) {
+                let tab_fields = tab[JV.PROP_CROSS_FIELDS],
+                    data_details = dataObj[JV.DATA_DETAIL_DATA],
+                    data_fields = [];
+                for (let i = 0; i < me.row_sum_extension_fields_idx.length; i++) {
+                    // let data_field = data_details[me.row_sum_extension_fields_idx[i]];
+                    let tab_field = tab_fields[i];
+                    let data_field = null;
+                    let map_data_field = JE.F(tab_field[JV.PROP_FIELD_ID], $CURRENT_RPT);
+                    if (typeof me.row_sum_extension_fields_idx[i] !== 'object') {
+                        data_field = data_details[me.row_sum_extension_fields_idx[i]];
+                    } else {
+                        if (map_data_field) {
+                            data_field = map_data_field[JV.PROP_AD_HOC_DATA];
+                        }
+                    }
+                    JpcFieldHelper.resetFormat(tab_field, map_data_field, customizeCfg);
+                    data_fields.push(data_field);
+                }
+                //2. initialize grand total value
+                let rowGrandTotal = [];
+                for (let di = 0; di < data_fields.length; di++) {
+                    rowGrandTotal[di] = 0.0;
+                    //3. start to sum
+                    for (let i = 0; i < me.sortedColSequence[segIdx].length; i++) {
+                        //me.sortedColSequence[segIdx][i][0] //this is the data field value index!
+                        rowGrandTotal[di] = rowGrandTotal[di] + 1.0 * JpcFieldHelper.getValue(data_fields[di], me.sortedColSequence[segIdx][i][0]);
+                    }
+                }
+                //4. output
+                for (let di = 0; di < tab_fields.length; di++) {
+                    let tab_field = tab_fields[di];
+                    if (!tab_field[JV.PROP_HIDDEN]) {
+                        let val = rowGrandTotal[di];
+                        let cellItem = JpcCommonOutputHelper.createCommonOutput(tab_field, val, controls);
+                        cellItem[JV.PROP_AREA] = JpcAreaHelper.outputArea(tab_field[JV.PROP_AREA], band, unitFactor, 1, 0, 1, 0, 1, 0, false, false);
+                        rst.push(cellItem);
+                    }
+                }
+                //output texts if has
+                if (tab[JV.PROP_TEXT]) {
+                    rst.push(JpcTextHelper.outputText(tab[JV.PROP_TEXT], band, unitFactor, 1, 0, 1, 0, 1, 0));
+                }
+                if (tab[JV.PROP_TEXTS]) {
+                    for (let j = 0; j < tab[JV.PROP_TEXTS].length; j++) {
+                        rst.push(JpcTextHelper.outputText(tab[JV.PROP_TEXTS][j], band, unitFactor, 1, 0, 1, 0, 1, 0));
+                    }
+                }
+            }
+        }
+        return rst;
+    };
+    JpcCrossTabResult.outputTabField = function (band, tab_field, data_field, valueIdx, serialIdx, rows, rowIdx, cols, colIdx, unitFactor, isRow, controls) {
+        let rst = null;
+        if (isRow === true && tab_field[JV.PROP_IS_SERIAL] && tab_field[JV.PROP_IS_SERIAL] === true) {
+            if (serialIdx >= 0) {
+                rst = JpcCommonOutputHelper.createCommonOutput(tab_field, serialIdx + 1)
+            } else rst = JpcCommonOutputHelper.createCommonOutput(tab_field, "", controls);
+        } else {
+            rst = JpcCommonOutputHelper.createCommonOutput(tab_field, JpcFieldHelper.getValue(data_field, valueIdx), controls);
+        }
+        //position
+        if (isRow === true) {
+            rst[JV.PROP_AREA] = JpcAreaHelper.outputArea(tab_field[JV.PROP_AREA], band, unitFactor, rows, rowIdx, cols, colIdx, 1, 0, true, false);
+        } else {
+            rst[JV.PROP_AREA] = JpcAreaHelper.outputArea(tab_field[JV.PROP_AREA], band, unitFactor, rows, rowIdx, cols, colIdx, 1, 0, false, false);
+        }
+        return rst;
+    };
+
+    return JpcCrossTabResult;
+};
+
+module.exports = new JpcCrossTabSrv();

+ 112 - 0
app/reports/rpt_component/jpc_data.js

@@ -0,0 +1,112 @@
+let JV = require('./jpc_value_define');
+let jpc_common_helper = require("./helper/jpc_helper_common");
+let JpcData = {
+    createNew: function() {
+        let JpcDataRst = {};
+        JpcDataRst.dataSeq = [];
+        JpcDataRst.exDataSeq = [];
+        JpcDataRst.analyzeData = function(rptTpl, dataObj) {
+            let me = this;
+            let private_analyse = function(MASTER_FIELD_STR, DETAIL_FIELD_STR, MASTER_DATA_STR, DETAIL_DATA_STR, dataSeqArr) {
+                //1. get ID fields
+                let masterIDs = [];
+                if (rptTpl[JV.NODE_FIELD_MAP][MASTER_FIELD_STR]) {
+                    for (let i = 0; i < rptTpl[JV.NODE_FIELD_MAP][MASTER_FIELD_STR].length; i++) {
+                        let mstFieldObj = rptTpl[JV.NODE_FIELD_MAP][MASTER_FIELD_STR][i];
+                        if (jpc_common_helper.getBoolean(mstFieldObj[JV.PROP_IS_ID])) {
+                            masterIDs.push({"idx": i, "seq": mstFieldObj[JV.PROP_ID_SEQ]});
+                        }
+                    }
+                }
+                let detailIDs = [];
+                if (rptTpl[JV.NODE_FIELD_MAP][DETAIL_FIELD_STR]) {
+                    for (let i = 0; i < rptTpl[JV.NODE_FIELD_MAP][DETAIL_FIELD_STR].length; i++) {
+                        let dtlFieldObj = rptTpl[JV.NODE_FIELD_MAP][DETAIL_FIELD_STR][i];
+                        if (jpc_common_helper.getBoolean(dtlFieldObj[JV.PROP_IS_ID])) {
+                            detailIDs.push({"idx": i, "seq": dtlFieldObj[JV.PROP_ID_SEQ]});
+                        }
+                    }
+                }
+                //2. sort the ID fields
+                if (masterIDs.length > 1) {
+                    masterIDs.sort(function(a, b) {
+                        return 1*a["seq"] - 1*b["seq"];
+                    })
+                }
+                if (detailIDs.length > 1) {
+                    detailIDs.sort(function(a, b) {
+                        return 1*a["seq"] - 1*b["seq"];
+                    })
+                }
+                //3. prepare data sequence
+                if (masterIDs.length > 0) {
+                    let mst_dt_len = 0, dtl_dt_len = 0, mst_fields = [];
+                    for (let i = 0; i < masterIDs.length; i++) {
+                        mst_fields.push(dataObj[MASTER_DATA_STR][masterIDs[i]["idx"]]);
+                        mst_dt_len = dataObj[MASTER_DATA_STR][masterIDs[i]["idx"]].length;
+                    }
+                    let dtl_fields = [];
+                    for (let i = 0; i < detailIDs.length; i++) {
+                        dtl_fields.push(dataObj[DETAIL_DATA_STR][detailIDs[i]["idx"]]);
+                        dtl_dt_len = dataObj[DETAIL_DATA_STR][detailIDs[i]["idx"]].length;
+                    }
+                    let sIdx = 0;
+                    let isEqual = true;
+                    for (let i = 0; i < mst_dt_len; i++) {
+                        dataSeqArr.push([]);
+                        //then compare the master/detail ID-field value
+                        for (let j = sIdx; j < dtl_dt_len; j++) {
+                            isEqual = true;
+                            for (let k = 0; k < mst_fields.length; k++) {
+                                if (!(mst_fields[k][i] === dtl_fields[k][j])) {
+                                    isEqual = false;
+                                    break;
+                                }
+                            }
+                            if (isEqual) {
+                                dataSeqArr[i].push(j);
+                            } else {
+                                sIdx = j;
+                                //below logic is for the data robustness purpose, to avoid those strange record(detail level) which could not match even one of the master record!
+                                if (i < mst_dt_len - 1 && j < dtl_dt_len - 1) {
+                                    for (let j1 = j; j1 < dtl_dt_len; j1++) {
+                                        isEqual = true;
+                                        for (let k = 0; k < mst_fields.length; k++) {
+                                            if (!(mst_fields[k][i + 1] === dtl_fields[k][j1])) {
+                                                isEqual = false;
+                                                break;
+                                            }
+                                        }
+                                        if (isEqual) {
+                                            sIdx = j1;
+                                            break;
+                                        }
+                                    }
+                                }
+                                break;
+                            }
+                        }
+                    }
+                } else { //if no master data
+                    if (dataObj && dataObj[DETAIL_DATA_STR] && dataObj[DETAIL_DATA_STR].length > 0) {
+                        //may be bill type report which may only have discrete fields!
+                        let field = dataObj[DETAIL_DATA_STR][0];
+                        dataSeqArr.push([]);
+                        for (let i = 0; i < field.length; i++) {
+                            dataSeqArr[0].push(i);
+                        }
+                    }
+                }
+            };
+            if ((rptTpl) && (dataObj)) {
+                private_analyse(JV.NODE_MASTER_FIELDS, JV.NODE_DETAIL_FIELDS, JV.DATA_MASTER_DATA, JV.DATA_DETAIL_DATA, me.dataSeq);
+                if (rptTpl[JV.NODE_FLOW_INFO_EX]) {
+                    private_analyse(JV.NODE_MASTER_FIELDS_EX, JV.NODE_DETAIL_FIELDS_EX, JV.DATA_MASTER_DATA_EX, JV.DATA_DETAIL_DATA_EX, me.exDataSeq);
+                }
+            }
+        };
+        return JpcDataRst;
+    }
+};
+
+module.exports = JpcData;

+ 32 - 0
app/reports/rpt_component/jpc_event.js

@@ -0,0 +1,32 @@
+/**
+ * Created by Tony on 2019/5/17.
+ */
+let JV = require('./jpc_value_define');
+let JpcEvent = {
+    createNew: function (rptTpl) {
+        let rst = {};
+        if (rptTpl[JV.NODE_EVENTS]) {
+            for (let i = 0; i < rptTpl[JV.NODE_EVENTS].length; i++) {
+                let item = {};
+                let propArr = Object.getOwnPropertyNames(rptTpl[JV.NODE_EVENTS][i]);
+                for (let key of propArr) {
+                    item[key] = rptTpl[JV.NODE_EVENTS][i][key];
+                }
+                switch (JV.EVENT_TYPE.indexOf(item.type)) {
+                    case JV.EVENT_IDX_GRP_ON_CREATE:
+                        rst[JV.EVENT_TYPE[JV.EVENT_IDX_GRP_ON_CREATE]] = item;
+                        break;
+                    case JV.EVENT_IDX_FLOW_CONTENT_ON_CREATE:
+                        rst[JV.EVENT_TYPE[JV.EVENT_IDX_FLOW_CONTENT_ON_CREATE]] = item;
+                        break;
+                    default :
+                        break;
+                }
+                //rst.push(item);
+            }
+        }
+        return rst;
+    }
+};
+
+module.exports = JpcEvent;

+ 395 - 0
app/reports/rpt_component/jpc_ex.js

@@ -0,0 +1,395 @@
+'use strict';
+
+const JV = require('./jpc_value_define');
+const JpcBand = require('./jpc_band');
+const JpcFlowTab = require('./jpc_flow_tab');
+const JpcBillTab = require('./jpc_bill_tab');
+const JpcCrossTab = require('./jpc_cross_tab');
+const JpcField = require('./jpc_field');
+const JpcParam = require('./jpc_param');
+const JpcFunc = require('./jpc_function');
+const JpcEvent = require('./jpc_event');
+const JpcData = require('./jpc_data');
+const JpcCommonHelper = require('./helper/jpc_helper_common');
+const $JE = require('./jpc_rte'); // Important: for self-define function execution purpose
+
+const JpcExSrv = function() {};
+JpcExSrv.prototype.createNew = function() {
+    function private_buildDftItems(rptTpl, dftCollection, propArray, nodeName) {
+        const rst = {};
+        if (dftCollection) {
+            for (let i = 0; i < dftCollection.length; i++) {
+                const item = {};
+                for (let j = 0; j < propArray.length; j++) {
+                    item[propArray[j]] = dftCollection[i][propArray[j]];
+                }
+                rst[dftCollection[i][JV.PROP_ID]] = item;
+            }
+            if (rptTpl && rptTpl[nodeName] && rptTpl[nodeName].length > 0) {
+                for (let i = 0; i < rptTpl[nodeName].length; i++) {
+                    const rptDftItem = rptTpl[nodeName][i];
+                    if (rst[rptDftItem[JV.PROP_ID]] === undefined) {
+                        const item = {};
+                        for (let j = 0; j < propArray.length; j++) {
+                            item[propArray[j]] = rptDftItem[propArray[j]];
+                        }
+                        rst[rptDftItem[JV.PROP_ID]] = item;
+                    }
+                }
+            }
+        }
+        return rst;
+    }
+    function private_buildDftControls(rptTpl, dftControlCollection) {
+        return private_buildDftItems(rptTpl, dftControlCollection, JV.CONTROL_PROPS, JV.NODE_CONTROL_COLLECTION);
+    }
+    function private_buildDftFonts(rptTpl, dftFontCollection) {
+        return private_buildDftItems(rptTpl, dftFontCollection, JV.FONT_PROPS, JV.NODE_FONT_COLLECTION);
+    }
+    function private_buildDftStyles(rptTpl, dftStyleCollection) {
+        const rst = {};
+        function private_CopyBorder(destItem, srcItem) {
+            destItem[JV.PROP_LINE_WEIGHT] = srcItem[JV.PROP_LINE_WEIGHT];
+            destItem[JV.PROP_DASH_STYLE] = srcItem[JV.PROP_DASH_STYLE];
+            destItem[JV.PROP_COLOR] = srcItem[JV.PROP_COLOR];
+        }
+        if (dftStyleCollection) {
+            for (let i = 0; i < dftStyleCollection.length; i++) {
+                const item = {};
+                if (dftStyleCollection[i][JV.PROP_BORDER_STYLE] && dftStyleCollection[i][JV.PROP_BORDER_STYLE].length > 0) {
+                    for (let j = 0; j < dftStyleCollection[i][JV.PROP_BORDER_STYLE].length; j++) {
+                        const borderItem = {};
+                        private_CopyBorder(borderItem, dftStyleCollection[i][JV.PROP_BORDER_STYLE][j]);
+                        item[dftStyleCollection[i][JV.PROP_BORDER_STYLE][j][JV.PROP_POSITION]] = borderItem;
+                    }
+                }
+                rst[dftStyleCollection[i][JV.PROP_ID]] = item;
+            }
+            if (rptTpl && rptTpl[JV.NODE_STYLE_COLLECTION] && rptTpl[JV.NODE_STYLE_COLLECTION].length > 0) {
+                for (let i = 0; i < rptTpl[JV.NODE_STYLE_COLLECTION].length; i++) {
+                    const rptDftItem = rptTpl[JV.NODE_STYLE_COLLECTION][i];
+                    if (rst[rptDftItem[JV.PROP_ID]] === undefined) {
+                        if (rptDftItem[JV.PROP_ID].indexOf('_AutoHeightMerge_Top') > 0) {
+                            const key = rptDftItem[JV.PROP_ID].substr(0, rptDftItem[JV.PROP_ID].indexOf('_AutoHeightMerge_Top'));
+                            if (rst[key]) {
+                                const item = {};
+                                if (rst[key][JV.PROP_LEFT]) {
+                                    item[JV.PROP_LEFT] = {};
+                                    private_CopyBorder(item[JV.PROP_LEFT], rst[key][JV.PROP_LEFT]);
+                                }
+                                if (rst[key][JV.PROP_RIGHT]) {
+                                    item[JV.PROP_RIGHT] = {};
+                                    private_CopyBorder(item[JV.PROP_RIGHT], rst[key][JV.PROP_RIGHT]);
+                                }
+                                if (rst[key][JV.PROP_TOP]) {
+                                    item[JV.PROP_TOP] = {};
+                                    private_CopyBorder(item[JV.PROP_TOP], rst[key][JV.PROP_TOP]);
+                                }
+                                rst[rptDftItem[JV.PROP_ID]] = item;
+                            }
+                        } else if (rptDftItem[JV.PROP_ID].indexOf('_AutoHeightMerge_Middle') > 0) {
+                            const key = rptDftItem[JV.PROP_ID].substr(0, rptDftItem[JV.PROP_ID].indexOf('_AutoHeightMerge_Middle'));
+                            if (rst[key]) {
+                                const item = {};
+                                if (rst[key][JV.PROP_LEFT]) {
+                                    item[JV.PROP_LEFT] = {};
+                                    private_CopyBorder(item[JV.PROP_LEFT], rst[key][JV.PROP_LEFT]);
+                                }
+                                if (rst[key][JV.PROP_RIGHT]) {
+                                    item[JV.PROP_RIGHT] = {};
+                                    private_CopyBorder(item[JV.PROP_RIGHT], rst[key][JV.PROP_RIGHT]);
+                                }
+                                rst[rptDftItem[JV.PROP_ID]] = item;
+                            }
+                        } else if (rptDftItem[JV.PROP_ID].indexOf('_AutoHeightMerge_Bottom') > 0) {
+                            const key = rptDftItem[JV.PROP_ID].substr(0, rptDftItem[JV.PROP_ID].indexOf('_AutoHeightMerge_Bottom'));
+                            if (rst[key]) {
+                                const item = {};
+                                if (rst[key][JV.PROP_LEFT]) {
+                                    item[JV.PROP_LEFT] = {};
+                                    private_CopyBorder(item[JV.PROP_LEFT], rst[key][JV.PROP_LEFT]);
+                                }
+                                if (rst[key][JV.PROP_RIGHT]) {
+                                    item[JV.PROP_RIGHT] = {};
+                                    private_CopyBorder(item[JV.PROP_RIGHT], rst[key][JV.PROP_RIGHT]);
+                                }
+                                if (rst[key][JV.PROP_BOTTOM]) {
+                                    item[JV.PROP_BOTTOM] = {};
+                                    private_CopyBorder(item[JV.PROP_BOTTOM], rst[key][JV.PROP_BOTTOM]);
+                                }
+                                rst[rptDftItem[JV.PROP_ID]] = item;
+                            }
+                        } else {
+                            const item = {};
+                            for (let j = 0; j < rptDftItem[JV.PROP_BORDER_STYLE].length; j++) {
+                                const borderItem = {};
+                                private_CopyBorder(borderItem, rptDftItem[JV.PROP_BORDER_STYLE][j]);
+                                item[rptDftItem[JV.PROP_BORDER_STYLE][j][JV.PROP_POSITION]] = borderItem;
+                            }
+                            rst[rptDftItem[JV.PROP_ID]] = item;
+                        }
+                    }
+                }
+            }
+        }
+        return rst;
+    }
+    const JpcResult = {};
+    JpcResult.initialize = function(rptTpl) {
+        const me = this;
+        if (rptTpl[JV.NODE_FLOW_INFO]) {
+            me.flowTab = JpcFlowTab.createNew();
+            me.flowTab.initialize(false);
+            me.isFollowMode = false;
+        }
+        if (rptTpl[JV.NODE_FLOW_INFO_EX]) {
+            me.flowTabEx = JpcFlowTab.createNew();
+            me.flowTabEx.initialize(true);
+            if (rptTpl[JV.NODE_FLOW_INFO_EX][JV.PROP_FLOW_EX_DISPLAY_MODE] === JV.DISPLAY_MODE_FOLLOW) {
+                me.isFollowMode = true;
+            }
+        }
+        if (rptTpl[JV.NODE_BILL_INFO]) {
+            me.billTab = JpcBillTab.createNew();
+            me.billTab.initialize();
+        }
+        if (rptTpl[JV.NODE_CROSS_INFO]) {
+            me.crossTab = JpcCrossTab.createNew();
+            me.crossTab.initialize();
+        }
+        me.totalPages = 0;
+        me.exTotalPages = 0;
+        me.runTimePageData = {};
+        me.fields = JpcField.createNew(rptTpl);
+        me.params = JpcParam.createNew(rptTpl);
+        me.formulas = JpcFunc.createNew(rptTpl);
+        me.events = JpcEvent.createNew(rptTpl);
+    };
+
+    JpcResult.analyzeData = function(rptTpl, dataObj, defProperties, option, outputType) {
+        const me = this;
+        const dftPagingOption = option || JV.PAGING_OPTION_NORMAL;
+        // 1. data object
+        let dataHelper = JpcData.createNew();
+        me.executeFormulas(JV.RUN_TYPE_BEFORE_ANALYZING, rptTpl, dataObj, me); // 在分析前运行,主要是增加灵活性,比如:重新编排数据的主从关系
+        if (me.crossTab) {
+            me.executeFormulas(JV.RUN_TYPE_BEFORE_PAGING, rptTpl, dataObj, me);
+            dataHelper.analyzeData(rptTpl, dataObj);
+            me.crossTab.sorting(rptTpl, dataObj, dataHelper.dataSeq.slice(0), me);
+        } else {
+            dataHelper.analyzeData(rptTpl, dataObj);
+            // 2. tab object
+            // pre-condition: the data should be sorted in SQL/NoSQL level!
+            // let dt1 = new Date();
+            if (me.flowTab) {
+                me.flowTab.sorting(rptTpl, dataObj, dataHelper.dataSeq.slice(0), me);
+                if (me.flowTabEx) {
+                    me.flowTabEx.sorting(rptTpl, dataObj, dataHelper.exDataSeq.slice(0), me);
+                }
+            }
+            if (me.billTab) {
+                me.billTab.sorting(rptTpl, dataObj, dataHelper.dataSeq.slice(0), me);
+            }
+            // let dt2 = new Date();
+            // alert(dt2 - dt1);
+            // 3. formulas
+            me.executeFormulas(JV.RUN_TYPE_BEFORE_PAGING, rptTpl, dataObj, me);
+        }
+        // 4. paging
+        me.paging(rptTpl, dataObj, defProperties, dftPagingOption, outputType);
+        // alert('analyzeData was completed!');
+        // for garbage collection:
+        dataHelper = null;
+    };
+    JpcResult.paging = function(rptTpl, dataObj, defProperties, option, outputType) {
+        const me = this;
+        const dftPagingOption = option || JV.PAGING_OPTION_NORMAL;
+        if (me.flowTab) {
+            if (me.isFollowMode) {
+                me.totalPages = me.flowTab.preSetupPages(rptTpl, dataObj, defProperties, dftPagingOption, me, me.flowTabEx, outputType);
+            } else {
+                me.totalPages = me.flowTab.preSetupPages(rptTpl, dataObj, defProperties, dftPagingOption, me, null, outputType);
+                if (me.flowTabEx) {
+                    me.exTotalPages = me.flowTabEx.preSetupPages(rptTpl, dataObj, defProperties, dftPagingOption, me, null, outputType);
+                    // console.log('ad-hoc flow pages: ' + me.exTotalPages);
+                }
+                me.totalPages += me.exTotalPages;
+            }
+        } else if (me.crossTab) {
+            // me.totalPages = me.crossTab.preSetupPages(rptTpl, defProperties, dftPagingOption);
+            me.totalPages = me.crossTab.preSetupPages(rptTpl, defProperties, JV.PAGING_OPTION_NORMAL); // infinity对交叉表来说无意义
+        } else if (me.billTab) {
+            me.totalPages = me.billTab.paging(rptTpl, dataObj);
+        }
+    };
+    JpcResult.executeFormulas = function(runType, $CURRENT_TEMPLATE, $CURRENT_DATA, $CURRENT_RPT) {
+        const execFmlMe = this;
+        for (let execFmlIdx = 0; execFmlIdx < execFmlMe.formulas.length; execFmlIdx++) {
+            // remark: 搞这么复杂的变量名是为了防止与表达式起冲突(如循环变量i,j,k,容易造成变量冲突且不容易看出问题)
+            if (execFmlMe.formulas[execFmlIdx][JV.PROP_RUN_TYPE] === runType) {
+                const expression = execFmlMe.formulas[execFmlIdx][JV.PROP_EXPRESSION];
+                if (expression) {
+                    const $ME = execFmlMe.formulas[execFmlIdx];
+                    // console.log("current expression idx: " + execFmlIdx);
+                    // console.log(expression);
+                    try {
+                        eval(expression);
+                    } catch (ex) {
+                        console.log(ex);
+                    }
+                }
+            }
+        }
+    };
+    JpcResult.outputAsPreviewPage = function(rptTpl, defProperties) {
+        const me = this;
+        const rst = {};
+        rst[JV.NODE_CONTROL_COLLECTION] = private_buildDftControls(rptTpl, (defProperties === null) ? null : defProperties.ctrls);
+        rst[JV.NODE_STYLE_COLLECTION] = private_buildDftStyles(rptTpl, (defProperties === null) ? null : defProperties.styles);
+        rst[JV.NODE_FONT_COLLECTION] = private_buildDftFonts(rptTpl, (defProperties === null) ? null : defProperties.fonts);
+        rst[JV.NODE_PAGE_INFO] = {};
+        rst[JV.NODE_PAGE_INFO][JV.NODE_MAIN_INFO_RPT_NAME] = rptTpl[JV.NODE_MAIN_INFO][JV.NODE_MAIN_INFO_RPT_NAME];
+        rst[JV.NODE_PAGE_INFO][JV.NODE_PAGE_SIZE] = JpcCommonHelper.getPageSize(rptTpl);
+        rst[JV.NODE_PAGE_INFO][JV.NODE_MARGINS] = rptTpl[JV.NODE_MAIN_INFO][JV.NODE_MARGINS];
+        rst.items = [];
+        let bands = JpcBand.createNew(rptTpl, defProperties);
+        function getPageMergeBorder() {
+            let mergeRst = null;
+            if (bands[JV.BAND_PROP_MERGE_BAND]) {
+                const mergedBand = bands[JV.BAND_PROP_MERGE_BAND];
+                mergeRst = {};
+                mergeRst[JV.PROP_LEFT] = parseInt(mergedBand[JV.PROP_LEFT].toFixed(0));
+                mergeRst[JV.PROP_RIGHT] = parseInt(mergedBand[JV.PROP_RIGHT].toFixed(0));
+                mergeRst[JV.PROP_TOP] = parseInt(mergedBand[JV.PROP_TOP].toFixed(0));
+                mergeRst[JV.PROP_BOTTOM] = parseInt(mergedBand[JV.PROP_BOTTOM].toFixed(0));
+            }
+            return mergeRst;
+        }
+        try {
+            // 1.
+            const rstPage = {};
+            rstPage[JV.PROP_PAGE_SEQ] = 1;
+            if (me.flowTab) {
+                rstPage[JV.PROP_CELLS] = me.flowTab.outputAsPreviewPage(rptTpl, bands, rst[JV.NODE_CONTROL_COLLECTION], me);
+            } else if (me.crossTab) {
+                rstPage[JV.PROP_CELLS] = me.crossTab.outputAsPreviewPage(rptTpl, bands, rst[JV.NODE_CONTROL_COLLECTION], me);
+            } else if (me.billTab) {
+                rstPage[JV.PROP_CELLS] = me.billTab.outputAsPreviewPage(rptTpl, bands, rst[JV.NODE_CONTROL_COLLECTION], me);
+            }
+            const pageMergeBorder = getPageMergeBorder();
+            if (pageMergeBorder) {
+                rstPage[JV.PROP_PAGE_MERGE_BORDER] = pageMergeBorder;
+            }
+            rst.items.push(rstPage);
+            // 2.
+            if (bands[JV.BAND_PROP_MERGE_BAND]) {
+                const mergedBand = {};
+                const band = bands[JV.BAND_PROP_MERGE_BAND];
+                mergedBand[JV.PROP_LEFT] = parseInt(band[JV.PROP_LEFT].toFixed(0));
+                mergedBand[JV.PROP_RIGHT] = parseInt(band[JV.PROP_RIGHT].toFixed(0));
+                mergedBand[JV.PROP_TOP] = parseInt(band[JV.PROP_TOP].toFixed(0));
+                mergedBand[JV.PROP_BOTTOM] = parseInt(band[JV.PROP_BOTTOM].toFixed(0));
+                mergedBand[JV.BAND_PROP_STYLE] = band[JV.BAND_PROP_STYLE];
+                rst[JV.BAND_PROP_MERGE_BAND] = mergedBand;
+            }
+        } catch (exception) {
+            console.log(exception);
+        } finally {
+            bands = null;
+        }
+        return rst;
+    };
+    JpcResult.outputAsSimpleJSONPageArray = function(rptTpl, dataObj, startPage, endPage, defProperties, customizeCfg) {
+        const me = this;
+        const rst = {};
+        if ((startPage > 0) && (startPage <= endPage) && (endPage <= me.totalPages)) {
+            rst[JV.NODE_CONTROL_COLLECTION] = private_buildDftControls(rptTpl, (defProperties === null) ? null : defProperties.ctrls);
+            // console.log('controls: ');
+            // console.log(rst[JV.NODE_CONTROL_COLLECTION]);
+            rst[JV.NODE_STYLE_COLLECTION] = private_buildDftStyles(rptTpl, (defProperties === null) ? null : defProperties.styles);
+            rst[JV.NODE_FONT_COLLECTION] = private_buildDftFonts(rptTpl, (defProperties === null) ? null : defProperties.fonts);
+            rst[JV.NODE_PAGE_INFO] = {};
+            rst[JV.NODE_PAGE_INFO][JV.NODE_MAIN_INFO_RPT_NAME] = rptTpl[JV.NODE_MAIN_INFO][JV.NODE_MAIN_INFO_RPT_NAME];
+            rst[JV.NODE_PAGE_INFO][JV.NODE_PAGE_SIZE] = JpcCommonHelper.getPageSize(rptTpl);
+            rst[JV.NODE_PAGE_INFO][JV.NODE_MARGINS] = rptTpl[JV.NODE_MAIN_INFO][JV.NODE_MARGINS];
+            rst.items = [];
+            let bands = JpcBand.createNew(rptTpl, defProperties);
+            try {
+                for (let page = startPage; page <= endPage; page++) {
+                    me.runTimePageData.currentPage = page;
+                    me.executeFormulas(JV.RUN_TYPE_BEFORE_OUTPUT, rptTpl, dataObj, me);
+                    rst.items.push(me.outputAsSimpleJSONPage(rptTpl, dataObj, bands, page, rst[JV.NODE_CONTROL_COLLECTION], customizeCfg));
+                }
+                if (bands[JV.BAND_PROP_MERGE_BAND]) {
+                    const mergedBand = {};
+                    const band = bands[JV.BAND_PROP_MERGE_BAND];
+                    mergedBand[JV.PROP_LEFT] = parseInt(band[JV.PROP_LEFT].toFixed(0));
+                    mergedBand[JV.PROP_RIGHT] = parseInt(band[JV.PROP_RIGHT].toFixed(0));
+                    mergedBand[JV.PROP_TOP] = parseInt(band[JV.PROP_TOP].toFixed(0));
+                    mergedBand[JV.PROP_BOTTOM] = parseInt(band[JV.PROP_BOTTOM].toFixed(0));
+                    mergedBand[JV.BAND_PROP_STYLE] = band[JV.BAND_PROP_STYLE];
+                    rst[JV.BAND_PROP_MERGE_BAND] = mergedBand;
+                }
+            } catch (exception) {
+                console.log(exception);
+            } finally {
+                bands = null;
+            }
+        }
+        return rst;
+    };
+    JpcResult.outputAsSimpleJSONPage = function(rptTpl, dataObj, bands, page, controls, customizeCfg) {
+        const me = this;
+        let rst = null;
+        function getPageMergeBorder() {
+            let mergeRst = null;
+            if (bands[JV.BAND_PROP_MERGE_BAND]) {
+                const mergedBand = bands[JV.BAND_PROP_MERGE_BAND];
+                mergeRst = {};
+                mergeRst[JV.PROP_LEFT] = parseInt(mergedBand[JV.PROP_LEFT].toFixed(0));
+                mergeRst[JV.PROP_RIGHT] = parseInt(mergedBand[JV.PROP_RIGHT].toFixed(0));
+                mergeRst[JV.PROP_TOP] = parseInt(mergedBand[JV.PROP_TOP].toFixed(0));
+                mergeRst[JV.PROP_BOTTOM] = parseInt(mergedBand[JV.PROP_BOTTOM].toFixed(0));
+            }
+            return mergeRst;
+        }
+        if (me.totalPages >= page) {
+            rst = {};
+            rst[JV.PROP_PAGE_SEQ] = page;
+            // rst.cells = [];
+            let adHocMergePos = null;
+            if (me.flowTab) {
+                if (me.totalPages - me.exTotalPages >= page) {
+                    if (me.flowTab.paging_option === JV.PAGING_OPTION_INFINITY) {
+                        adHocMergePos = {};
+                    }
+                    rst[JV.PROP_CELLS] = me.flowTab.outputAsSimpleJSONPage(rptTpl, dataObj, page, bands, controls, adHocMergePos, me, customizeCfg);
+                    if (adHocMergePos) {
+                        adHocMergePos[JV.NODE_PAGE_SIZE] = JpcCommonHelper.getPageSize(rptTpl);
+                        rst[JV.PAGE_SPECIAL_MERGE_POS] = adHocMergePos;
+                    }
+
+                } else {
+                    if (!me.isFollowMode) {
+                        rst[JV.PROP_CELLS] = me.flowTabEx.outputAsSimpleJSONPage(rptTpl, dataObj, page - (me.totalPages - me.exTotalPages), bands, controls, adHocMergePos, me, customizeCfg);
+                    }
+                }
+            } else if (me.crossTab) {
+                rst[JV.PROP_CELLS] = me.crossTab.outputAsSimpleJSONPage(rptTpl, dataObj, page, bands, controls, me, customizeCfg);
+            } else if (me.billTab) {
+                rst[JV.PROP_CELLS] = me.billTab.outputAsSimpleJSONPage(rptTpl, dataObj, page, bands, controls, me, customizeCfg);
+            }
+            if (!(me.flowTab && me.flowTab.paging_option === JV.PAGING_OPTION_INFINITY)) {
+                const pageMergeBorder = getPageMergeBorder();
+                if (pageMergeBorder) {
+                    rst[JV.PROP_PAGE_MERGE_BORDER] = pageMergeBorder;
+                }
+            }
+        }
+        return rst;
+    };
+    // JpcEx.rte.currentRptObj = JpcResult;
+    return JpcResult;
+};
+
+module.exports = new JpcExSrv();

+ 68 - 0
app/reports/rpt_component/jpc_field.js

@@ -0,0 +1,68 @@
+let JV = require('./jpc_value_define');
+let JpcField = {
+    createNew: function(rptTpl) {
+        let JpcFieldResult = {};
+        let me = this;
+        JpcFieldResult[JV.NODE_DISCRETE_FIELDS] = {};
+        if (rptTpl[JV.NODE_FIELD_MAP] && rptTpl[JV.NODE_FIELD_MAP][JV.NODE_DISCRETE_FIELDS]) {
+            for (let i = 0; i < rptTpl[JV.NODE_FIELD_MAP][JV.NODE_DISCRETE_FIELDS].length; i++) {
+                me.createSingle(rptTpl[JV.NODE_FIELD_MAP][JV.NODE_DISCRETE_FIELDS][i], JpcFieldResult[JV.NODE_DISCRETE_FIELDS], rptTpl, JV.DATA_DISCRETE_DATA, i);
+            }
+        }
+        JpcFieldResult[JV.NODE_MASTER_FIELDS] = {};
+        if (rptTpl[JV.NODE_FIELD_MAP] && rptTpl[JV.NODE_FIELD_MAP][JV.NODE_MASTER_FIELDS]) {
+            for (let i = 0; i < rptTpl[JV.NODE_FIELD_MAP][JV.NODE_MASTER_FIELDS].length; i++) {
+                me.createSingle(rptTpl[JV.NODE_FIELD_MAP][JV.NODE_MASTER_FIELDS][i], JpcFieldResult[JV.NODE_MASTER_FIELDS], rptTpl, JV.DATA_MASTER_DATA, i);
+            }
+        }
+        JpcFieldResult[JV.NODE_DETAIL_FIELDS] = {};
+        if (rptTpl[JV.NODE_FIELD_MAP] && rptTpl[JV.NODE_FIELD_MAP][JV.NODE_DETAIL_FIELDS]) {
+            for (let i = 0; i < rptTpl[JV.NODE_FIELD_MAP][JV.NODE_DETAIL_FIELDS].length; i++) {
+                me.createSingle(rptTpl[JV.NODE_FIELD_MAP][JV.NODE_DETAIL_FIELDS][i], JpcFieldResult[JV.NODE_DETAIL_FIELDS], rptTpl, JV.DATA_DETAIL_DATA, i);
+            }
+        }
+        JpcFieldResult[JV.NODE_NO_MAPPING_FIELDS] = {};
+        if (rptTpl[JV.NODE_NO_MAPPING_FIELDS]) {
+            for (let i = 0; i < rptTpl[JV.NODE_NO_MAPPING_FIELDS].length; i++) {
+                me.createSingle(rptTpl[JV.NODE_NO_MAPPING_FIELDS][i], JpcFieldResult[JV.NODE_NO_MAPPING_FIELDS], rptTpl, "NA", JV.BLANK_FIELD_INDEX);
+            }
+        }
+        JpcFieldResult[JV.NODE_MASTER_FIELDS_EX] = {};
+        if (rptTpl[JV.NODE_FIELD_MAP] && rptTpl[JV.NODE_FIELD_MAP][JV.NODE_MASTER_FIELDS_EX]) {
+            for (let i = 0; i < rptTpl[JV.NODE_FIELD_MAP][JV.NODE_MASTER_FIELDS_EX].length; i++) {
+                me.createSingle(rptTpl[JV.NODE_FIELD_MAP][JV.NODE_MASTER_FIELDS_EX][i], JpcFieldResult[JV.NODE_MASTER_FIELDS_EX], rptTpl, JV.DATA_MASTER_DATA_EX, i);
+            }
+        }
+        JpcFieldResult[JV.NODE_DETAIL_FIELDS_EX] = {};
+        if (rptTpl[JV.NODE_FIELD_MAP] && rptTpl[JV.NODE_FIELD_MAP][JV.NODE_DETAIL_FIELDS_EX]) {
+            for (let i = 0; i < rptTpl[JV.NODE_FIELD_MAP][JV.NODE_DETAIL_FIELDS_EX].length; i++) {
+                me.createSingle(rptTpl[JV.NODE_FIELD_MAP][JV.NODE_DETAIL_FIELDS_EX][i], JpcFieldResult[JV.NODE_DETAIL_FIELDS_EX], rptTpl, JV.DATA_DETAIL_DATA_EX, i);
+            }
+        }
+        //NODE_MASTER_FIELDS_EX
+        return JpcFieldResult;
+    },
+    createSingle: function(fieldNode, parentObj, rptTpl, dataNodeName, sequence) {
+        if (fieldNode && fieldNode[JV.PROP_ID]) {
+            let item = {};
+            item[JV.PROP_ID] = fieldNode[JV.PROP_ID];
+            item[JV.PROP_NAME] = fieldNode[JV.PROP_NAME];
+            item[JV.PROP_DATA_TYPE] = fieldNode[JV.PROP_DATA_TYPE];
+            if (fieldNode[JV.PROP_PRECISION]) {
+                item[JV.PROP_PRECISION] = {};
+                item[JV.PROP_PRECISION].type = fieldNode[JV.PROP_PRECISION].type;
+                if (fieldNode[JV.PROP_PRECISION].type === 'fixed') {
+                    item[JV.PROP_FIXED_PRECISION_AMT] = fieldNode[JV.PROP_FIXED_PRECISION_AMT];
+                } else {
+                    item[JV.PROP_PRECISION][JV.PROP_FLEXIBLE_REF_FILED_ID] = fieldNode[JV.PROP_PRECISION][JV.PROP_FLEXIBLE_REF_FILED_ID];
+                    item.flexiblePrecisionRefObj = fieldNode.flexiblePrecisionRefObj;
+                }
+            }
+            item.DataNodeName = dataNodeName;
+            item.DataSeq = sequence;
+            parentObj[JV.PROP_ID + "_" + fieldNode[JV.PROP_ID]] = item;
+        }
+    }
+};
+
+module.exports = JpcField;

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 1468 - 0
app/reports/rpt_component/jpc_flow_tab.js


+ 19 - 0
app/reports/rpt_component/jpc_function.js

@@ -0,0 +1,19 @@
+let JV = require('./jpc_value_define');
+let JpcFunc = {
+    createNew: function (rptTpl) {
+        let rst = [];
+        if (rptTpl[JV.NODE_FORMULAS]) {
+            for (let i = 0; i < rptTpl[JV.NODE_FORMULAS].length; i++) {
+                let item = {};
+                let propArr = Object.getOwnPropertyNames(rptTpl[JV.NODE_FORMULAS][i]);
+                for (let key of propArr) {
+                    item[key] = rptTpl[JV.NODE_FORMULAS][i][key];
+                }
+                rst.push(item);
+            }
+        }
+        return rst;
+    }
+};
+
+module.exports = JpcFunc;

+ 26 - 0
app/reports/rpt_component/jpc_param.js

@@ -0,0 +1,26 @@
+let JV = require('./jpc_value_define');
+let JpcParam = {
+    createNew: function(rptTpl) {
+        let JpcParamResult = {};
+        let me = this;
+        if (rptTpl[JV.NODE_DISCRETE_PARAMS]) {
+            for (let i = 0; i < rptTpl[JV.NODE_DISCRETE_PARAMS].length; i++) {
+                me.createSingle(rptTpl[JV.NODE_DISCRETE_PARAMS][i], JpcParamResult, rptTpl, i);
+            }
+        }
+        return JpcParamResult;
+    },
+    createSingle: function(paramNode, parentObj, rptTpl, sequence) {
+        if (paramNode && paramNode[JV.PROP_ID]) {
+            let item = {};
+            item[JV.PROP_ID] = paramNode[JV.PROP_ID];
+            item[JV.PROP_NAME] = paramNode[JV.PROP_NAME];
+            item[JV.PROP_DATA_TYPE] = paramNode[JV.PROP_DATA_TYPE];
+            if (paramNode[JV.PROP_DFT_VALUE]) item[JV.PROP_DFT_VALUE] = paramNode[JV.PROP_DFT_VALUE];
+            item.DataSeq = sequence;
+            parentObj[JV.PROP_ID + "_" + paramNode[JV.PROP_ID]] = item;
+        }
+    }
+};
+
+module.exports = JpcParam;

+ 143 - 0
app/reports/rpt_component/jpc_rte.js

@@ -0,0 +1,143 @@
+/**
+ * Created by Tony on 2016/12/28.
+ */
+let strUtil = require('../public/stringUtil');
+let JV = require('./jpc_value_define');
+let JE = {
+    $STR_UTIL: strUtil,
+    F: function(fID, $CURRENT_RPT) {
+        let rst = null;
+        if ($CURRENT_RPT && ($CURRENT_RPT.fields[JV.NODE_DETAIL_FIELDS][JV.PROP_ID + "_" + fID])) {
+            rst = $CURRENT_RPT.fields[JV.NODE_DETAIL_FIELDS][JV.PROP_ID + "_" + fID];
+        } else if ($CURRENT_RPT && ($CURRENT_RPT.fields[JV.NODE_MASTER_FIELDS][JV.PROP_ID + "_" + fID])) {
+            rst = $CURRENT_RPT.fields[JV.NODE_MASTER_FIELDS][JV.PROP_ID + "_" + fID];
+        } else if ($CURRENT_RPT && ($CURRENT_RPT.fields[JV.NODE_MASTER_FIELDS_EX][JV.PROP_ID + "_" + fID])) {
+            rst = $CURRENT_RPT.fields[JV.NODE_MASTER_FIELDS_EX][JV.PROP_ID + "_" + fID];
+        } else if ($CURRENT_RPT && ($CURRENT_RPT.fields[JV.NODE_DETAIL_FIELDS_EX][JV.PROP_ID + "_" + fID])) {
+            rst = $CURRENT_RPT.fields[JV.NODE_DETAIL_FIELDS_EX][JV.PROP_ID + "_" + fID];
+        } else if ($CURRENT_RPT && ($CURRENT_RPT.fields[JV.NODE_DISCRETE_FIELDS][JV.PROP_ID + "_" + fID])) {
+            rst = $CURRENT_RPT.fields[JV.NODE_DISCRETE_FIELDS][JV.PROP_ID + "_" + fID];
+        } else if ($CURRENT_RPT && ($CURRENT_RPT.fields[JV.NODE_NO_MAPPING_FIELDS][JV.PROP_ID + "_" + fID])) {
+            rst = $CURRENT_RPT.fields[JV.NODE_NO_MAPPING_FIELDS][JV.PROP_ID + "_" + fID];
+        } else {
+            rst = {msg: "the Field-ID is not valid, no result could be found!"};
+        }
+        return rst;
+    },
+    P: function(pID, $CURRENT_RPT) {
+        let rst = null;
+        if ($CURRENT_RPT && ($CURRENT_RPT.params[JV.PROP_ID + "_" + pID])) {
+            rst = $CURRENT_RPT.params[JV.PROP_ID + "_" + pID];
+        } else {
+            rst = {msg: "the Param-ID is not valid, no result was found!"};
+        }
+        return rst;
+    },
+    getCurrentPage: function ($CURRENT_RPT) {
+        let rst = 0;
+        if ($CURRENT_RPT) {
+            rst = $CURRENT_RPT.runTimePageData.currentPage;
+        }
+        return rst;
+    },
+    getTotalPage: function ($CURRENT_RPT) {
+        let rst = 0;
+        if ($CURRENT_RPT) {
+            rst = $CURRENT_RPT.totalPages;
+        }
+        return rst;
+    },
+    getFieldDataLen: function(field, dataObj) {
+        let rst = 0;
+        if (field.DataNodeName === "NA") {
+            if (!field[JV.PROP_AD_HOC_DATA]) {
+                field[JV.PROP_AD_HOC_DATA] = [];
+            }
+            rst = field[JV.PROP_AD_HOC_DATA].length;
+        } else {
+            if (!field.DataNodeName) {
+                // that means this is a self-defined discrete field!
+                field.DataNodeName = JV.DATA_DISCRETE_DATA;
+                field.DataSeq = dataObj[JV.DATA_DISCRETE_DATA];
+                dataObj[JV.DATA_DISCRETE_DATA].push([]);
+            }
+            rst = dataObj[field.DataNodeName][field.DataSeq].length;
+        }
+        return rst;
+    },
+    setFieldValue: function (field, dataObj, valIdx, newValue) {
+        if (field.DataNodeName === "NA") {
+            if (!field[JV.PROP_AD_HOC_DATA]) {
+                field[JV.PROP_AD_HOC_DATA] = [];
+            }
+            field[JV.PROP_AD_HOC_DATA][valIdx] = newValue;
+        } else if (!field.DataNodeName) {
+            //that means this is a self-defined discrete field!
+            field.DataNodeName = JV.DATA_DISCRETE_DATA;
+            field.DataSeq = dataObj[JV.DATA_DISCRETE_DATA].length;
+            dataObj[JV.DATA_DISCRETE_DATA].push([]);
+            dataObj[field.DataNodeName][field.DataSeq][valIdx] = newValue;
+        } else {
+            dataObj[field.DataNodeName][field.DataSeq][valIdx] = newValue;
+        }
+    },
+    getFieldValue: function (field, dataObj, valIdx, newVal) {
+        let rst = null;
+        if (field.DataNodeName === "NA") {
+            if (!field[JV.PROP_AD_HOC_DATA]) {
+                field[JV.PROP_AD_HOC_DATA] = [];
+            }
+            if (field[JV.PROP_AD_HOC_DATA].length > valIdx) {
+                rst = field[JV.PROP_AD_HOC_DATA][valIdx];
+            } else {
+                if (newVal === null && field[JV.PROP_AD_HOC_DATA].length > 0) {
+                    rst = field[JV.PROP_AD_HOC_DATA][field[JV.PROP_AD_HOC_DATA].length - 1];
+                }
+            }
+        } else {
+            if (!field.DataNodeName) {
+                //that means this is a self-defined discrete field!
+                field.DataNodeName = JV.DATA_DISCRETE_DATA;
+                field.DataSeq = dataObj[JV.DATA_DISCRETE_DATA];
+                dataObj[JV.DATA_DISCRETE_DATA].push([]);
+            }
+            if (dataObj[field.DataNodeName][field.DataSeq].length > valIdx) {
+                rst = dataObj[field.DataNodeName][field.DataSeq][valIdx];
+            } else {
+                if (newVal === null && dataObj[field.DataNodeName][field.DataSeq].length > 0) {
+                    rst = dataObj[field.DataNodeName][field.DataSeq][dataObj[field.DataNodeName][field.DataSeq].length - 1];
+                }
+            }
+        }
+        if (rst === null || rst === undefined) rst = newVal;
+        return rst;
+    },
+    removeFieldValue: function (field, dataObj, valIdx) {
+        if (field.DataNodeName === "NA") {
+            if (field[JV.PROP_AD_HOC_DATA].length > valIdx && valIdx >= 0) {
+                field[JV.PROP_AD_HOC_DATA].splice(valIdx, 1);
+            }
+        } else {
+            if (dataObj[field.DataNodeName][field.DataSeq].length > valIdx && valIdx >= 0) {
+                dataObj[field.DataNodeName][field.DataSeq].splice(valIdx, 1);
+            }
+        }
+    },
+    insertFieldValue: function (field, dataObj, valIdx, newValue) {
+        if (field.DataNodeName === "NA") {
+            if (field[JV.PROP_AD_HOC_DATA].length > valIdx && valIdx >= 0) {
+                field[JV.PROP_AD_HOC_DATA].splice(valIdx, 0, newValue);
+            } else if (field[JV.PROP_AD_HOC_DATA].length <= valIdx) {
+                field[JV.PROP_AD_HOC_DATA][field[JV.PROP_AD_HOC_DATA].length] = newValue;
+            }
+        } else {
+            if (dataObj[field.DataNodeName][field.DataSeq].length > valIdx && valIdx >= 0) {
+                dataObj[field.DataNodeName][field.DataSeq].splice(valIdx, 0, newValue);
+            } else if (dataObj[field.DataNodeName][field.DataSeq].length <= valIdx) {
+                dataObj[field.DataNodeName][field.DataSeq][dataObj[field.DataNodeName][field.DataSeq].length] = newValue;
+            }
+        }
+    }
+};
+
+module.exports = JE;

+ 325 - 0
app/reports/rpt_component/jpc_value_define.js

@@ -0,0 +1,325 @@
+'use strict';
+
+module.exports = {
+    NODE_CROSS_INFO: '交叉表_信息',
+    NODE_CROSS_ROW: '交叉行',
+    NODE_CROSS_COL: '交叉列',
+    NODE_CROSS_CONTENT: '交叉数据',
+    NODE_CROSS_ROW_SUM: '交叉行合计',
+    NODE_CROSS_COL_SUM: '交叉列合计',
+    NODE_CROSS_ROW_EXT: '交叉行拓展',
+    NODE_CROSS_ROW_SUM_EXT: '交叉行拓展合计',
+    NODE_CROSS_ROW_AD_HOC: '交叉行AD_HOC',
+    NODE_FIELD_MAP: '指标_数据_映射',
+    NODE_DISCRETE_FIELDS: '离散指标_集合',
+    NODE_NO_MAPPING_FIELDS: '无映射离散指标_集合',
+    NODE_DISCRETE_PARAMS: '离散参数_集合',
+    NODE_MASTER_FIELDS: '主数据指标_集合',
+    NODE_MASTER_FIELDS_EX: '主数据指标_拓展集合',
+    NODE_DETAIL_FIELDS: '从数据指标_集合',
+    NODE_DETAIL_FIELDS_EX: '从数据指标_拓展集合',
+    NODE_BAND_COLLECTION: '布局框_集合',
+    NODE_FORMULAS: '计算式_集合',
+    NODE_EVENTS: '事件_集合',
+    NODE_DISCRETE_INFO: '离散信息',
+    NODE_BILL_INFO: '账单式表_信息',
+    NODE_BILL_CONTENT: '账单式表_数据',
+    NODE_FLOW_INFO: '流水式表_信息',
+    NODE_FLOW_INFO_EX: '流水式表_拓展信息',
+    NODE_FLOW_GROUP: '流水式表_分组信息',
+    NODE_FLOW_SEG_SUM: '流水式表_段统计信息',
+    NODE_FLOW_PAGE_SUM: '流水式表_页统计信息',
+    NODE_FLOW_COLUMN: '流水式表_列',
+    NODE_FLOW_CONTENT: '流水式表_数据',
+    PROP_MULTI_COLUMN: '多列显示数量',
+    PROP_FLOW_EX_DISPLAY_MODE: '流水拓展显示模式',
+    DISPLAY_MODE_INDEPENDENT: '单独模式',
+    DISPLAY_MODE_FOLLOW: '紧随模式',
+
+    NODE_MAIN_INFO: '主信息',
+    NODE_MAIN_INFO_RPT_NAME: '报表名称',
+    NODE_PAGE_INFO: '打印页面_信息',
+    NODE_PAGE_SIZE: '纸张宽高',
+    NODE_MARGINS: '页边距',
+    NODE_RPT_BIZ_TYPE: '报表业务类型',
+    NODE_BIZ_TYPE_AUDI: '审核类型',
+    NODE_BIZ_TYPE_SUM: '汇总类型',
+    NODE_BIZ_TYPE_DETAIL: '明细类型',
+
+    NODE_MAP_DATA_HANDLE_INFO: '映射数据预处理',
+    PROP_DATA_KEY: '映射数据对象',
+    PROP_PARENT_DATA_KEY: '父映射数据对象',
+    PROP_PARENT_CHILD_SORT_KEY: '父子排序键',
+    PROP_PARENT_SORT_KEYS: '父排序键值集',
+    PROP_CHILD_SORT_KEYS: '子排序键值集',
+    PROP_OTHER_SUB_SORT: '其他子排序',
+    PROP_OTHER_SUB_FILTER: '其他子过滤',
+    PROP_HANDLE_TYPE: '预处理类型',
+    PROP_FILTER_KEYS: '过滤键值集',
+    PROP_FILTER_TOP_BILLS_NODES: '清单顶节点集',
+    PROP_FILTER_OTHER_BILLS_NODES: '其他清单节点集',
+    PROP_FILTER_COMPARE_OBJ: 'compareObjKey',
+    PROP_FILTER_COMPARE_OBJ_KEY: 'compareObjIdKey',
+    PROP_FILTER_COMPARE_VAL: 'compareValue',
+    PROP_FILTER_CONDITION: '判断条件',
+    PROP_HANDLE_TYPE_FILTER: '过滤',
+    PROP_HANDLE_TYPE_SUM: '合计',
+    PROP_HANDLE_TYPE_SORT: '排序',
+    PROP_HANDLE_TYPE_ADD_DUMMY: '增加Dummy数据',
+    PROP_HANDLE_TYPE_ADJUST: '数据调整',
+    PROP_HANDLE_TYPE_BILLS_DATA_MOVE: '量材数据转移',
+    PROP_HANDLE_TYPE_COMPONENT_MOVE: '组成物数据转移',
+    PROP_HANDLE_TYPE_COMPONENT_REPLACEMENT: '组成物替换',
+    PROP_HANDLE_TYPE_PRECISION: '合计精度',
+
+    PROP_ADJUST_COLLECTION: '数据调整集',
+    PROP_ADJUST_ACTION: 'action',
+    PROP_ADJUST_ACTION_VAL: 'actionValue',
+    PROP_DUMMY_COLLECTION: 'Dummy数据集',
+    PROP_DUMMY_VAL: 'Dummy数据对象值',
+    PROP_FREQUENCY: '频率',
+    PROP_GRP_KEYS: 'GrpKeyIds',
+    PROP_SORT_TYPE: '排序方式',
+    PROP_SORT_TYPE_SELF_DEFINE_LOGIC: '自定义逻辑',
+    PROP_SORT_KEYS: '排序键值集',
+    PROP_SUM_GROUP_KEYS: '分组键值集',
+    PROP_SUM_SUM_KEYS: '统计键值集',
+    PROP_SUM_CALC_AHEAD: '统计前计算',
+    PROP_SUM_CACL_TYPE: '计算类型',
+    PROP_FIELD_EXP_MAP: 'mapExpression',
+    PROP_PRECISION: 'Precision',
+    PROP_FIXED_PRECISION_AMT: 'fixedPrecisionNum',
+    PROP_FIELD_EXP_FIXED_MAP: 'fixedMapExpression',
+    PROP_FIELD_EXP_FLEXIBLE_MAP: 'flexibleMapExpression',
+    PROP_FLEXIBLE_REF_FILED_ID: 'flexibleRefFieldID',
+
+    NODE_FONT_COLLECTION: 'font_collection',
+    NODE_STYLE_COLLECTION: 'style_collection',
+    NODE_CONTROL_COLLECTION: 'control_collection',
+
+    PROP_ID: 'ID',
+    PROP_AD_HOC_DATA: 'data_field',
+    PROP_CMN_HEIGHT: 'CommonHeight',
+    PROP_CMN_WIDTH: 'CommonWidth',
+    PROP_BAND_NAME: 'BandName',
+    PROP_BAND_NORMAL_ONLY: 'normalOnly',
+    PROP_BAND_EX_ONLY: 'exOnly',
+    PROP_BAND_EX_JOIN_AFTER: 'isJoinAfter',
+
+    PROP_UNITS: '单位',
+    PROP_PAGE_SIZE: '页规格',
+    PROP_ORIENTATION: '方向',
+    PROP_LEFT: 'Left',
+    PROP_RIGHT: 'Right',
+    PROP_TOP: 'Top',
+    PROP_BOTTOM: 'Bottom',
+    PROP_DATA_TYPE: 'DataType',
+    PROP_NAME: 'Name',
+    PROP_DFT_VALUE: 'Default_Value',
+    PROP_EXPRESSION: 'expression',
+    PROP_RUN_TYPE: 'run_type',
+    PROP_BORDER_STYLE: 'border_style',
+    PROP_POSITION: 'Position',
+    PROP_HIDDEN: 'Hidden',
+    PROP_IS_SERIAL: 'isSerial',
+    PROP_IS_MERGE: 'isMerge',
+    PROP_COMBINE_TYPE: 'combineType',
+    PROP_IS_AUTO_HEIGHT: 'isAutoHeight',
+    PROP_FONT: 'font',
+    PROP_CONTROL: 'control',
+    PROP_STYLE: 'style',
+    PROP_VALUE: 'Value',
+    PROP_LABEL: 'Label',
+    PROP_AREA: 'area',
+    PROP_DISCRETE_FIELDS: 'discrete_field_s',
+    PROP_FLOW_FIELDS: 'flow_field_s',
+    PROP_BILL_FIELDS: 'bill_field_s',
+    PROP_CROSS_FIELDS: 'cross_field_s',
+    PROP_CROSS_DISPLAY_ORDER: 'cross_display_order',
+    PROP_GROUP_FIELDS: 'group_field_s', // 用来分组的指标(如按清单、定额etc...)
+    PROP_GROUP_LINES: 'group_lines', // 显示分组行,因分组的特殊性,分组的数据当成流水数据一样(行高相同),group_lines里的每一条数据占用流水的一整行,里面再细分(指标/text)
+    PROP_GROUP_SUM_KEYS: 'SumKey_S',
+    PROP_SUM_KEY: 'SumKey',
+    PROP_SUM_FIELDS: 'sum_field_s',
+    PROP_TEXTS: 'text_s',
+    PROP_TEXT: 'text',
+    PROP_PARAMS: 'param_s',
+    PROP_FIELD_ID: 'FieldID',
+    PROP_PARAM_ID: 'ParamID',
+    PROP_PREFIX: 'Prefix',
+    PROP_SUFFIX: 'Suffix',
+    PROP_FORMAT: 'Format',
+
+    PROP_SHOW_ZERO: 'ShowZero',
+    PROP_EXTENSION_TYPE: 'ExtType',
+
+    PROP_CALCULATION: 'CalculationType',
+    PROP_H_CALCULATION: 'H_CalculationType',
+    PROP_V_CALCULATION: 'V_CalculationType',
+    PROP_FIT_AREA: 'isFitArea',
+
+    IDX_LEFT: 0,
+    IDX_TOP: 1,
+    IDX_RIGHT: 2,
+    IDX_BOTTOM: 3,
+
+    BAND_PROP_NAME: 'Name',
+    BAND_PROP_MERGE_BAND: 'MergeBand',
+    BAND_PROP_STYLE: 'style',
+    BAND_PROP_CONTROL: 'control',
+    BAND_PROP_HEIGHT: 'Height',
+    BAND_PROP_WIDTH: 'Width',
+    BAND_PROP_DISPLAY_TYPE: 'DisplayType',
+    BAND_PROP_ALIGNMENT: 'Alignment',
+    BAND_PROP_MERGE_BORDER: 'MergeBorder',
+    BAND_PROP_SUB_BANDS: 'band_s',
+
+    MEASUREMENT: {
+        PIXEL: ['像素点', '象素点', 'PIXEL'],
+        CM: ['厘米', 'CM'],
+        INCH: ['英寸', 'INCH'],
+    },
+
+    PROP_IS_ID: 'isID',
+    PROP_ID_SEQ: 'IDSeq',
+
+    TAB_FIELD_PROP_SORT: 'Sort',
+    TAB_FIELD_PROP_SORT_VAL_NOSORT: 'no_sort',
+    TAB_FIELD_PROP_SORT_VAL_ASC: 'ascend',
+    TAB_FIELD_PROP_SORT_VAL_DESC: 'descend',
+
+    DATA_DISCRETE_DATA: 'discrete_data',
+    DATA_MASTER_DATA: 'master_data',
+    DATA_DETAIL_DATA: 'detail_data',
+    DATA_MASTER_DATA_EX: 'master_data_ex',
+    DATA_DETAIL_DATA_EX: 'detail_data_ex',
+
+    BLANK_FIELD_INDEX: -10,
+    BLANK_VALUE_INDEX: -100,
+    BLANK_PAGE_VALUE_INDEX: -200,
+
+    PROP_SEG_GRP_IDX: 'segGrpRecStartIdx',
+    PROP_PRE_ADD_GRP_REC_INFO: 'preAddPageGrpInfo',
+    PROP_INSERTED_GRP_REC: 'insertedGrpRecAmt',
+    PROP_GRP_LINES: 'group_lines_amt',
+
+    RUN_TYPE_BEFORE_ANALYZING: 'before_analyzing',
+    RUN_TYPE_BEFORE_PAGING: 'before_paging',
+    RUN_TYPE_BEFORE_OUTPUT: 'before_output',
+
+    PAGE_STATUS: ['EveryPage', 'FirstPage', 'LastPage', 'SegmentStart', 'SegmentEnd', 'Group', 'CrossRowEnd', 'CrossColEnd'],
+
+    CONTROL_PROPS: ['Shrink', 'ShowZero', 'Horizon', 'Vertical', 'Wrap', 'VerticalForExcel', 'ShrinkFirst', 'CloseOutput'],
+    CONTROL_PROP_IDX_SHRINK: 0,
+    CONTROL_PROP_IDX_SHOW_ZERO: 1,
+    CONTROL_PROP_IDX_HORIZON: 2,
+    CONTROL_PROP_IDX_VERTICAL: 3,
+    CONTROL_PROP_IDX_WRAP: 4,
+    CONTROL_PROP_IDX_VERTICAL_EXCEL: 5,
+    CONTROL_PROP_IDX_SHRINK_FIRST: 6,
+    CONTROL_PROP_IDX_CLOSE_OUTPUT: 7,
+    BORDER_STYLE_PROPS: ['LineWeight', 'DashStyle', 'Color'],
+    PROP_LINE_WEIGHT: 'LineWeight',
+    PROP_DASH_STYLE: 'DashStyle',
+    PROP_COLOR: 'Color',
+    FONT_PROPS: ['Name', 'FontHeight', 'FontColor', 'FontBold', 'FontItalic', 'FontUnderline', 'FontStrikeOut', 'FontAngle'],
+    FONT_PROP_IDX_NAME: 0,
+    FONT_PROP_IDX_HEIGHT: 1,
+    FONT_PROP_IDX_COLOR: 2,
+    FONT_PROP_IDX_BOLD: 3,
+    FONT_PROP_IDX_ITALIC: 4,
+    FONT_PROP_IDX_UNDERLINE: 5,
+    FONT_PROP_IDX_STRIKEOUT: 6,
+    FONT_PROP_IDX_ANGLE: 7,
+
+    STATUS_NORMAL: 0,
+    STATUS_REPORT_START: 1,
+    STATUS_REPORT_END: 2,
+    STATUS_SEGMENT_START: 3,
+    STATUS_SEGMENT_END: 4,
+    STATUS_GROUP: 5,
+    STATUS_CROSS_ROW_END: 6,
+    STATUS_CROSS_COL_END: 7,
+
+    LAYOUT: ['Top', 'Bottom', 'Left', 'Right', 'Fulfill'],
+    LAYOUT_TOP: 0,
+    LAYOUT_BOTTOM: 1,
+    LAYOUT_LEFT: 2,
+    LAYOUT_RIGHT: 3,
+    LAYOUT_FULFILL: 4,
+
+    OUTPUT_ALIGN: {
+        H: ['left', 'center', 'right'],
+        V: ['top', 'center', 'bottom'],
+    },
+    H_ALIGN_IDX_LEFT: 0,
+    H_ALIGN_IDX_CENTER: 1,
+    H_ALIGN_IDX_RIGHT: 2,
+    V_ALIGN_IDX_TOP: 0,
+    V_ALIGN_IDX_CENTER: 1,
+    V_ALIGN_IDX_BOTTOM: 2,
+
+    CAL_TYPE: ['percentage', 'abstract'],
+    CAL_TYPE_PERCENTAGE: 0,
+    CAL_TYPE_ABSTRACT: 1,
+
+    EVENT_TYPE: ['GRP_ON_CREATE', 'FLOW_CONTENT_ON_CREATE'],
+    EVENT_IDX_GRP_ON_CREATE: 0,
+    EVENT_IDX_FLOW_CONTENT_ON_CREATE: 1,
+
+    PAGE_ORIENTATION_V_FIRST: 0,
+    PAGE_ORIENTATION_H_FIRST: 1,
+
+    ORIENTATION_PORTRAIT: 'PORTRAIT',
+    ORIENTATION_LANDSCAPE: 'LANDSCAPE',
+    ORIENTATION_PORTRAIT_CHN: '纵向',
+    ORIENTATION_LANDSCAPE_CHN: '横向',
+    SIZE_A3: [11.69, 16.54],
+    SIZE_A4: [8.27, 11.69],
+    SIZE_A5: [5.83, 8.27],
+    SIZE_B5: [6.93, 9.84],
+    SIZE_LETTER: [8.5, 11.0],
+    SIZE_LEGAL: [8.5, 14.0],
+    SIZE_16K: [7.75, 10.75],
+    SIZE_EXECUTIVE: [7.25, 10.5],
+
+    OUTPUT_OFFSET: [2, 2, 1, 3],
+    OFFSET_IDX_LEFT: 0,
+    OFFSET_IDX_RIGHT: 1,
+    OFFSET_IDX_TOP: 2,
+    OFFSET_IDX_BOTTOM: 3,
+
+    PROP_PAGE_SEQ: 'page_seq',
+    PROP_PAGE_MERGE_BORDER: 'page_merge_border',
+    PROP_CELLS: 'cells',
+
+    PAGING_OPTION_NORMAL: 'normal',
+    PAGING_OPTION_INFINITY: 'infinity',
+
+    OUTPUT_TYPE_NORMAL: 'normal',
+    OUTPUT_TYPE_SVG: 'svg',
+    OUTPUT_TYPE_PDF: 'pdf',
+    OUTPUT_TYPE_EXCEL: 'excel',
+
+    DISPLAY_VAL_TYPE_NORMAL: 0,
+    DISPLAY_VAL_TYPE_GROUP: 1,
+    DISPLAY_VAL_TYPE_AUTO_HEIGHT: 2,
+
+    TYPE_FOLLOW_MODE: 1,
+
+    PAGE_SELF_DEFINE: '自定义',
+    PAGE_SPECIAL_MERGE_POS: 'page_merge_pos',
+
+    PAGES_SIZE_STR: ['A3', 'A4', 'A5', 'B5', 'LETTER', 'LEGAL', 'EXECUTIVE', '16K'],
+    PAGES_SIZE_IDX: [8, 9, 11, 13, 1, 5, 7, 93],
+    PAGES_SIZE: [[11.69, 16.54], [8.27, 11.69], [5.83, 8.27], [6.93, 9.84], [8.5, 11.0], [8.5, 14.0], [7.25, 10.5], [7.25, 10.5]],
+
+    HUNDRED_PERCENT: 100.0,
+
+    VERTICAL_ANGLE: '90',
+    ANTI_VERTICAL_ANGLE: '-90',
+    VERTICAL_ANGLE_INT: 90,
+    ANTI_VERTICAL_ANGLE_INT: -90,
+
+    LAST_DEF: '',
+};

+ 281 - 0
app/reports/util/excel_base_files/theme1.xml

@@ -0,0 +1,281 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<a:theme xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" name="Office 主题">
+	<a:themeElements>
+		<a:clrScheme name="Office">
+			<a:dk1>
+				<a:sysClr val="windowText" lastClr="000000"/>
+			</a:dk1>
+			<a:lt1>
+				<a:sysClr val="window" lastClr="FFFFFF"/>
+			</a:lt1>
+			<a:dk2>
+				<a:srgbClr val="1F497D"/>
+			</a:dk2>
+			<a:lt2>
+				<a:srgbClr val="EEECE1"/>
+			</a:lt2>
+			<a:accent1>
+				<a:srgbClr val="4F81BD"/>
+			</a:accent1>
+			<a:accent2>
+				<a:srgbClr val="C0504D"/>
+			</a:accent2>
+			<a:accent3>
+				<a:srgbClr val="9BBB59"/>
+			</a:accent3>
+			<a:accent4>
+				<a:srgbClr val="8064A2"/>
+			</a:accent4>
+			<a:accent5>
+				<a:srgbClr val="4BACC6"/>
+			</a:accent5>
+			<a:accent6>
+				<a:srgbClr val="F79646"/>
+			</a:accent6>
+			<a:hlink>
+				<a:srgbClr val="0000FF"/>
+			</a:hlink>
+			<a:folHlink>
+				<a:srgbClr val="800080"/>
+			</a:folHlink>
+		</a:clrScheme>
+		<a:fontScheme name="Office">
+			<a:majorFont>
+				<a:latin typeface="Cambria"/>
+				<a:ea typeface=""/>
+				<a:cs typeface=""/>
+				<a:font script="Jpan" typeface="MS Pゴシック"/>
+				<a:font script="Hang" typeface="맑은 고딕"/>
+				<a:font script="Hans" typeface="宋体"/>
+				<a:font script="Hant" typeface="新細明體"/>
+				<a:font script="Arab" typeface="Times New Roman"/>
+				<a:font script="Hebr" typeface="Times New Roman"/>
+				<a:font script="Thai" typeface="Tahoma"/>
+				<a:font script="Ethi" typeface="Nyala"/>
+				<a:font script="Beng" typeface="Vrinda"/>
+				<a:font script="Gujr" typeface="Shruti"/>
+				<a:font script="Khmr" typeface="MoolBoran"/>
+				<a:font script="Knda" typeface="Tunga"/>
+				<a:font script="Guru" typeface="Raavi"/>
+				<a:font script="Cans" typeface="Euphemia"/>
+				<a:font script="Cher" typeface="Plantagenet Cherokee"/>
+				<a:font script="Yiii" typeface="Microsoft Yi Baiti"/>
+				<a:font script="Tibt" typeface="Microsoft Himalaya"/>
+				<a:font script="Thaa" typeface="MV Boli"/>
+				<a:font script="Deva" typeface="Mangal"/>
+				<a:font script="Telu" typeface="Gautami"/>
+				<a:font script="Taml" typeface="Latha"/>
+				<a:font script="Syrc" typeface="Estrangelo Edessa"/>
+				<a:font script="Orya" typeface="Kalinga"/>
+				<a:font script="Mlym" typeface="Kartika"/>
+				<a:font script="Laoo" typeface="DokChampa"/>
+				<a:font script="Sinh" typeface="Iskoola Pota"/>
+				<a:font script="Mong" typeface="Mongolian Baiti"/>
+				<a:font script="Viet" typeface="Times New Roman"/>
+				<a:font script="Uigh" typeface="Microsoft Uighur"/>
+			</a:majorFont>
+			<a:minorFont>
+				<a:latin typeface="Calibri"/>
+				<a:ea typeface=""/>
+				<a:cs typeface=""/>
+				<a:font script="Jpan" typeface="MS Pゴシック"/>
+				<a:font script="Hang" typeface="맑은 고딕"/>
+				<a:font script="Hans" typeface="宋体"/>
+				<a:font script="Hant" typeface="新細明體"/>
+				<a:font script="Arab" typeface="Arial"/>
+				<a:font script="Hebr" typeface="Arial"/>
+				<a:font script="Thai" typeface="Tahoma"/>
+				<a:font script="Ethi" typeface="Nyala"/>
+				<a:font script="Beng" typeface="Vrinda"/>
+				<a:font script="Gujr" typeface="Shruti"/>
+				<a:font script="Khmr" typeface="DaunPenh"/>
+				<a:font script="Knda" typeface="Tunga"/>
+				<a:font script="Guru" typeface="Raavi"/>
+				<a:font script="Cans" typeface="Euphemia"/>
+				<a:font script="Cher" typeface="Plantagenet Cherokee"/>
+				<a:font script="Yiii" typeface="Microsoft Yi Baiti"/>
+				<a:font script="Tibt" typeface="Microsoft Himalaya"/>
+				<a:font script="Thaa" typeface="MV Boli"/>
+				<a:font script="Deva" typeface="Mangal"/>
+				<a:font script="Telu" typeface="Gautami"/>
+				<a:font script="Taml" typeface="Latha"/>
+				<a:font script="Syrc" typeface="Estrangelo Edessa"/>
+				<a:font script="Orya" typeface="Kalinga"/>
+				<a:font script="Mlym" typeface="Kartika"/>
+				<a:font script="Laoo" typeface="DokChampa"/>
+				<a:font script="Sinh" typeface="Iskoola Pota"/>
+				<a:font script="Mong" typeface="Mongolian Baiti"/>
+				<a:font script="Viet" typeface="Arial"/>
+				<a:font script="Uigh" typeface="Microsoft Uighur"/>
+			</a:minorFont>
+		</a:fontScheme>
+		<a:fmtScheme name="Office">
+			<a:fillStyleLst>
+				<a:solidFill>
+					<a:schemeClr val="phClr"/>
+				</a:solidFill>
+				<a:gradFill rotWithShape="1">
+					<a:gsLst>
+						<a:gs pos="0">
+							<a:schemeClr val="phClr">
+								<a:tint val="50000"/>
+								<a:satMod val="300000"/>
+							</a:schemeClr>
+						</a:gs>
+						<a:gs pos="35000">
+							<a:schemeClr val="phClr">
+								<a:tint val="37000"/>
+								<a:satMod val="300000"/>
+							</a:schemeClr>
+						</a:gs>
+						<a:gs pos="100000">
+							<a:schemeClr val="phClr">
+								<a:tint val="15000"/>
+								<a:satMod val="350000"/>
+							</a:schemeClr>
+						</a:gs>
+					</a:gsLst>
+					<a:lin ang="16200000" scaled="1"/>
+				</a:gradFill>
+				<a:gradFill rotWithShape="1">
+					<a:gsLst>
+						<a:gs pos="0">
+							<a:schemeClr val="phClr">
+								<a:shade val="51000"/>
+								<a:satMod val="130000"/>
+							</a:schemeClr>
+						</a:gs>
+						<a:gs pos="80000">
+							<a:schemeClr val="phClr">
+								<a:shade val="93000"/>
+								<a:satMod val="130000"/>
+							</a:schemeClr>
+						</a:gs>
+						<a:gs pos="100000">
+							<a:schemeClr val="phClr">
+								<a:shade val="94000"/>
+								<a:satMod val="135000"/>
+							</a:schemeClr>
+						</a:gs>
+					</a:gsLst>
+					<a:lin ang="16200000" scaled="0"/>
+				</a:gradFill>
+			</a:fillStyleLst>
+			<a:lnStyleLst>
+				<a:ln w="9525" cap="flat" cmpd="sng" algn="ctr">
+					<a:solidFill>
+						<a:schemeClr val="phClr">
+							<a:shade val="95000"/>
+							<a:satMod val="105000"/>
+						</a:schemeClr>
+					</a:solidFill>
+					<a:prstDash val="solid"/>
+				</a:ln>
+				<a:ln w="25400" cap="flat" cmpd="sng" algn="ctr">
+					<a:solidFill>
+						<a:schemeClr val="phClr"/>
+					</a:solidFill>
+					<a:prstDash val="solid"/>
+				</a:ln>
+				<a:ln w="38100" cap="flat" cmpd="sng" algn="ctr">
+					<a:solidFill>
+						<a:schemeClr val="phClr"/>
+					</a:solidFill>
+					<a:prstDash val="solid"/>
+				</a:ln>
+			</a:lnStyleLst>
+			<a:effectStyleLst>
+				<a:effectStyle>
+					<a:effectLst>
+						<a:outerShdw blurRad="40000" dist="20000" dir="5400000" rotWithShape="0">
+							<a:srgbClr val="000000">
+								<a:alpha val="38000"/>
+							</a:srgbClr>
+						</a:outerShdw>
+					</a:effectLst>
+				</a:effectStyle>
+				<a:effectStyle>
+					<a:effectLst>
+						<a:outerShdw blurRad="40000" dist="23000" dir="5400000" rotWithShape="0">
+							<a:srgbClr val="000000">
+								<a:alpha val="35000"/>
+							</a:srgbClr>
+						</a:outerShdw>
+					</a:effectLst>
+				</a:effectStyle>
+				<a:effectStyle>
+					<a:effectLst>
+						<a:outerShdw blurRad="40000" dist="23000" dir="5400000" rotWithShape="0">
+							<a:srgbClr val="000000">
+								<a:alpha val="35000"/>
+							</a:srgbClr>
+						</a:outerShdw>
+					</a:effectLst>
+					<a:scene3d>
+						<a:camera prst="orthographicFront">
+							<a:rot lat="0" lon="0" rev="0"/>
+						</a:camera>
+						<a:lightRig rig="threePt" dir="t">
+							<a:rot lat="0" lon="0" rev="1200000"/>
+						</a:lightRig>
+					</a:scene3d>
+					<a:sp3d>
+						<a:bevelT w="63500" h="25400"/>
+					</a:sp3d>
+				</a:effectStyle>
+			</a:effectStyleLst>
+			<a:bgFillStyleLst>
+				<a:solidFill>
+					<a:schemeClr val="phClr"/>
+				</a:solidFill>
+				<a:gradFill rotWithShape="1">
+					<a:gsLst>
+						<a:gs pos="0">
+							<a:schemeClr val="phClr">
+								<a:tint val="40000"/>
+								<a:satMod val="350000"/>
+							</a:schemeClr>
+						</a:gs>
+						<a:gs pos="40000">
+							<a:schemeClr val="phClr">
+								<a:tint val="45000"/>
+								<a:shade val="99000"/>
+								<a:satMod val="350000"/>
+							</a:schemeClr>
+						</a:gs>
+						<a:gs pos="100000">
+							<a:schemeClr val="phClr">
+								<a:shade val="20000"/>
+								<a:satMod val="255000"/>
+							</a:schemeClr>
+						</a:gs>
+					</a:gsLst>
+					<a:path path="circle">
+						<a:fillToRect l="50000" t="-80000" r="50000" b="180000"/>
+					</a:path>
+				</a:gradFill>
+				<a:gradFill rotWithShape="1">
+					<a:gsLst>
+						<a:gs pos="0">
+							<a:schemeClr val="phClr">
+								<a:tint val="80000"/>
+								<a:satMod val="300000"/>
+							</a:schemeClr>
+						</a:gs>
+						<a:gs pos="100000">
+							<a:schemeClr val="phClr">
+								<a:shade val="30000"/>
+								<a:satMod val="200000"/>
+							</a:schemeClr>
+						</a:gs>
+					</a:gsLst>
+					<a:path path="circle">
+						<a:fillToRect l="50000" t="50000" r="50000" b="50000"/>
+					</a:path>
+				</a:gradFill>
+			</a:bgFillStyleLst>
+		</a:fmtScheme>
+	</a:themeElements>
+	<a:objectDefaults/>
+	<a:extraClrSchemeLst/>
+</a:theme>

BIN
app/reports/util/pdf_base_files/Smart.ttf


BIN
app/reports/util/pdf_base_files/Smart_bold.ttf


BIN
app/reports/util/pdf_base_files/Smart_bold_italic.ttf


BIN
app/reports/util/pdf_base_files/Smart_italic.ttf


BIN
app/reports/util/pdf_base_files/arial.ttf


BIN
app/reports/util/pdf_base_files/simhei.ttf


BIN
app/reports/util/pdf_base_files/simhei_bold.ttf


BIN
app/reports/util/pdf_base_files/simhei_bold_italic.ttf


BIN
app/reports/util/pdf_base_files/simhei_italic.ttf


BIN
app/reports/util/pdf_base_files/simkai.ttf


BIN
app/reports/util/pdf_base_files/simkai_bold.ttf


BIN
app/reports/util/pdf_base_files/simkai_bold_italic.ttf


BIN
app/reports/util/pdf_base_files/simkai_italic.ttf


+ 875 - 0
app/reports/util/rpt_calculation_data_util.js

@@ -0,0 +1,875 @@
+'use strict';
+
+/**
+ * Created by Tony on 2019/6/18.
+ * 计量报表数据提取class,是协助报表模板里指标字段自主提取数据的工具类
+ */
+const JV = require('../rpt_component/jpc_value_define');
+const $JE = require('../rpt_component/jpc_rte');
+
+const fsUtil = require('../../public/js/fsUtil');
+const stringUtil = require('../../public/js/stringUtil');
+const scMathUtil = require('../../public/js/scMathUtil');
+const _ = require('lodash');
+
+const treeUtil = require('../public/treeUtil');
+
+const RELATION_TABLES_KEYS = {
+    project: { main_key: 'id' },
+    tender: { main_key: 'id', foreign_key: { project: 'project_id' } },
+    tender_info: { main_key: 'id', foreign_key: { project: 'pid', tender: 'tid' } },
+    ledger: { main_key: 'id', isTree: true, tree_pid: 'ledger_pid', tree_id: 'ledger_id', foreign_key: { tender: 'tender_id' } },
+};
+
+class Rpt_Common {
+    initialize(rpt_tpl, currentDataObj) {
+        this.template = rpt_tpl;
+        this.currentDataObj = currentDataObj;
+    }
+    Multiply(val1, val2, fixFormat) {
+        const rst = [];
+        let maxLen = val1.length;
+        let minLen = val2.length;
+        if (minLen > maxLen) {
+            maxLen = maxLen + minLen; minLen = maxLen - minLen; maxLen = maxLen - minLen;
+        }
+        for (let i = 0; i < maxLen; i++) {
+            let value = ((i < val1.length) ? val1[i] : val1[minLen - 1]) * ((i < val2.length) ? val2[i] : val2[minLen - 1]);
+            if (value === null || value === undefined) {
+                value = '0';
+            }
+            if (fixFormat) value = value.toFixed(fixFormat);
+            rst.push(value);
+        }
+        return rst;
+    }
+    Divide(val1, val2, fixFormat) {
+        const rst = [];
+        let maxLen = val1.length;
+        let minLen = val2.length;
+        if (minLen > maxLen) {
+            maxLen = maxLen + minLen; minLen = maxLen - minLen; maxLen = maxLen - minLen;
+        }
+        for (let i = 0; i < maxLen; i++) {
+            let value = ((i < val1.length) ? val1[i] : val1[minLen - 1]) / ((i < val2.length) ? val2[i] : val2[minLen - 1]);
+            if (fixFormat) value = value.toFixed(fixFormat);
+            rst.push(value);
+        }
+        return rst;
+    }
+    Plus(val1, val2, fixFormat) {
+        const rst = [];
+        let maxLen = val1.length;
+        let minLen = val2.length;
+        if (minLen > maxLen) {
+            maxLen = maxLen + minLen; minLen = maxLen - minLen; maxLen = maxLen - minLen;
+        }
+        for (let i = 0; i < maxLen; i++) {
+            let value = ((i < val1.length) ? val1[i] : val1[minLen - 1]) + ((i < val2.length) ? val2[i] : val2[minLen - 1]);
+            if (fixFormat) value = value.toFixed(fixFormat);
+            rst.push(value);
+        }
+        return rst;
+    }
+    MultiPlus(arrVal, fixFormat) {
+        const rst = [];
+        for (let i = 0; i < arrVal.length; i++) {
+            const valItem = arrVal[i];
+            if (i === 0) {
+                for (const dtl of valItem) {
+                    let value = parseFloat(dtl);
+                    if (fixFormat) value = value.toFixed(fixFormat);
+                    rst.push(value);
+                }
+            } else {
+                for (let j = 0; j < valItem.length; j++) {
+                    if (j < rst.length) {
+                        let value = rst[j] + valItem[j];
+                        if (fixFormat) value = value.toFixed(fixFormat);
+                        rst[j] = value;
+                    } else {
+                        let value = parseFloat(valItem[j]);
+                        if (fixFormat) value = value.toFixed(fixFormat);
+                        rst.push(value);
+                    }
+                }
+            }
+        }
+        return rst;
+    }
+    Minus(val1, val2, fixFormat) {
+        const rst = [];
+        let maxLen = val1.length;
+        let minLen = val2.length;
+        if (minLen > maxLen) {
+            maxLen = maxLen + minLen; minLen = maxLen - minLen; maxLen = maxLen - minLen;
+        }
+        for (let i = 0; i < maxLen; i++) {
+            let value = ((i < val1.length) ? val1[i] : val1[minLen - 1]) - ((i < val2.length) ? val2[i] : val2[minLen - 1]);
+            if (fixFormat) value = value.toFixed(fixFormat);
+            rst.push(value);
+        }
+        return rst;
+    }
+    FormatString(arrVal, formatStr) {
+        const rst = [];
+        for (const val of arrVal) {
+            rst.push(stringUtil.replaceAll(formatStr, '%S', val));
+        }
+        return rst;
+    }
+}
+
+class Rpt_Data_Extractor {
+    constructor() {
+        this.COMMON = new Rpt_Common();
+    }
+
+    initialize(tpl) {
+        this.rptTpl = tpl;
+    }
+
+    // 因为计量是采用传统的关系数据库(MySQL), 有一些表关系是需要先约定(如上下级关系,外键关系等)
+
+    // 根据报表模板映射指标(非离散指标)的定义,罗列出所有需要用到的data对象key,作为数据请求的过滤依据
+    getDataRequestFilter() {
+        const rst = {};
+        const tables = []; // 记录需要查询哪些表
+        // const filters = []; // 记录过滤条件(预处理中的过滤),目前先不处理
+        const tpl = this.rptTpl;
+        const pri_func_chk_filter = function(field) {
+            // 计量的机制有所不同,数据分开保存在不同的表里,
+            if (field.TableName && tables.indexOf(field.TableName) < 0) {
+                tables.push(field.TableName);
+            }
+        };
+        const pri_setup_filter = function(FIELD_LIST_KEY) {
+            // console.log(tpl[JV.NODE_FIELD_MAP][FIELD_LIST_KEY]);
+            if (tpl[JV.NODE_FIELD_MAP][FIELD_LIST_KEY]) {
+                for (const field of tpl[JV.NODE_FIELD_MAP][FIELD_LIST_KEY]) {
+                    pri_func_chk_filter(field);
+                }
+            }
+        };
+        pri_setup_filter(JV.NODE_DISCRETE_FIELDS);
+        pri_setup_filter(JV.NODE_MASTER_FIELDS);
+        pri_setup_filter(JV.NODE_DETAIL_FIELDS);
+        pri_setup_filter(JV.NODE_MASTER_FIELDS_EX);
+        pri_setup_filter(JV.NODE_DETAIL_FIELDS_EX);
+        if (tpl[JV.NODE_MAP_DATA_HANDLE_INFO] && tpl[JV.NODE_MAP_DATA_HANDLE_INFO].length > 0) {
+            // 计量的预处理会有所变化,没有那么多方式,这里只记录过滤
+            for (const preHandle of tpl[JV.NODE_MAP_DATA_HANDLE_INFO]) {
+                if (preHandle[JV.PROP_HANDLE_TYPE] === JV.PROP_HANDLE_TYPE_FILTER) {
+                    if (tables.indexOf(preHandle[JV.PROP_DATA_KEY]) < 0) {
+                        tables.push(preHandle[JV.PROP_DATA_KEY]);
+                    }
+                    // 目前还没想好有哪些过滤,骑驴找马吧
+                }
+            }
+        }
+        rst.tables = tables;
+        return rst;
+    }
+
+    // 装配数据(把收集到的数据,依据报表模板的指示,预处理(如:排序、过滤、合计)及装配到相关指标)
+    assembleData(rawDataObj) {
+        const $PROJECT = { REPORT: {} };
+        const tpl = this.rptTpl;
+        this.COMMON.initialize(tpl, rawDataObj);
+        $PROJECT.COMMON = this.COMMON;
+
+        // console.log(rawDataObj);
+        setupFunc($PROJECT.REPORT, rawDataObj);
+
+        if (tpl[JV.NODE_MAP_DATA_HANDLE_INFO]) {
+            for (const preHandle of tpl[JV.NODE_MAP_DATA_HANDLE_INFO]) {
+                const srcData = getModuleDataByKey(rawDataObj.prjData, preHandle[JV.PROP_DATA_KEY]);
+                switch (preHandle[JV.PROP_HANDLE_TYPE]) {
+                    case JV.PROP_HANDLE_TYPE_SORT:
+                        sortData(srcData, preHandle, rawDataObj.prjData);
+                        break;
+                    case JV.PROP_HANDLE_TYPE_FILTER:
+                        filterData(srcData, preHandle, rawDataObj.prjData);
+                        break;
+                    case JV.PROP_HANDLE_TYPE_ADJUST:
+                        adjustData(srcData, preHandle);
+                        break;
+                    default:
+                        break;
+                }
+            }
+        }
+
+        const rptDataObj = {};
+        rptDataObj[JV.DATA_DISCRETE_DATA] = [];
+        rptDataObj[JV.DATA_MASTER_DATA] = [];
+        rptDataObj[JV.DATA_DETAIL_DATA] = [];
+        rptDataObj[JV.DATA_MASTER_DATA_EX] = [];
+        rptDataObj[JV.DATA_DETAIL_DATA_EX] = [];
+
+        assembleFields(tpl[JV.NODE_FIELD_MAP][JV.NODE_DISCRETE_FIELDS], rptDataObj[JV.DATA_DISCRETE_DATA], $PROJECT);
+        // console.log(JV.DATA_DISCRETE_DATA);
+        // console.log(rptDataObj[JV.DATA_DISCRETE_DATA]);
+        assembleFields(tpl[JV.NODE_FIELD_MAP][JV.NODE_MASTER_FIELDS], rptDataObj[JV.DATA_MASTER_DATA], $PROJECT);
+        // console.log(JV.DATA_MASTER_DATA);
+        // console.log(rptDataObj[JV.DATA_MASTER_DATA]);
+        assembleFields(tpl[JV.NODE_FIELD_MAP][JV.NODE_DETAIL_FIELDS], rptDataObj[JV.DATA_DETAIL_DATA], $PROJECT);
+        // console.log(JV.DATA_DETAIL_DATA);
+        // console.log(rptDataObj[JV.DATA_DETAIL_DATA]);
+        assembleFields(tpl[JV.NODE_FIELD_MAP][JV.NODE_MASTER_FIELDS_EX], rptDataObj[JV.DATA_MASTER_DATA_EX], $PROJECT);
+        // console.log(JV.DATA_MASTER_DATA_EX);
+        // console.log(rptDataObj[JV.DATA_MASTER_DATA_EX]);
+        assembleFields(tpl[JV.NODE_FIELD_MAP][JV.NODE_DETAIL_FIELDS_EX], rptDataObj[JV.DATA_DETAIL_DATA_EX], $PROJECT);
+        // console.log(JV.DATA_DETAIL_DATA_EX);
+        // console.log(rptDataObj[JV.DATA_DETAIL_DATA_EX]);
+        // fsUtil.writeObjToFile(rptDataObj, 'D:/GitHome/temp/insertedOriginalData.jsp');
+        // fsUtil.writeObjToFile(rawDataObj, 'D:/GitHome/temp/insertedRawDataData.jsp');
+        // fsUtil.writeObjToFile($PROJECT, 'D:/GitHome/temp/$PROJECTData.jsp');
+        // fsUtil.writeObjToFile(tpl[JV.NODE_FIELD_MAP][JV.NODE_MASTER_FIELDS], 'D:/GitHome/temp/masterFieldsAfterAssemble.jsp');
+        // fsUtil.writeObjToFile(tpl[JV.NODE_FIELD_MAP][JV.NODE_DETAIL_FIELDS], 'D:/GitHome/temp/detailFieldsAfterAssemble.jsp');
+        // fsUtil.writeObjToFile(tpl[JV.NODE_FIELD_MAP][JV.NODE_DISCRETE_FIELDS], 'D:/GitHome/temp/discreteFieldsAfterAssemble.jsp');
+        return rptDataObj;
+    }
+
+}
+
+function getModuleDataByKey(prjData, key) {
+    let rst = null;
+    if (prjData) {
+        for (const item of prjData) {
+            if (item.moduleName === key) {
+                rst = item;
+                break;
+            }
+        }
+    }
+    return rst;
+}
+
+function filterData(sourceData, handleCfg, prjData) {
+    let rstArr = [];
+    const tempRstArr = [];
+    for (const item of getActDataArr(sourceData)) {
+        if (item._doc) {
+            tempRstArr.push(item._doc);
+        } else {
+            tempRstArr.push(item);
+        }
+    }
+    const private_chkVal = function(src, compVal, compStr) {
+        let rst = true;
+        switch (compStr) {
+            case '==' :
+                rst = (src == compVal);
+                break;
+            case '===' :
+                rst = (src === compVal);
+                break;
+            case '>' :
+                rst = (src > compVal);
+                break;
+            case '>=' :
+                rst = (src >= compVal);
+                break;
+            case '<' :
+                rst = (src < compVal);
+                break;
+            case '<=' :
+                rst = (src <= compVal);
+                break;
+            case '!=' :
+                rst = (src != compVal);
+                break;
+            case '!==' :
+                rst = (src !== compVal);
+                break;
+            case 'in' :
+                if (compVal instanceof Array) {
+                    rst = compVal.indexOf(src) >= 0;
+                } else {
+                    // string,需要转类型
+                    const newInCv = JSON.parse(compVal);
+                    if (newInCv instanceof Array) {
+                        rst = newInCv.indexOf(src) >= 0;
+                    } else {
+                        rst = false;
+                    }
+                }
+                break;
+            case 'not in':
+                if (compVal instanceof Array) {
+                    rst = compVal.indexOf(src) < 0;
+                } else {
+                    // string,需要转类型
+                    const newNotInCv = JSON.parse(compVal);
+                    if (newNotInCv instanceof Array) {
+                        rst = (newNotInCv.indexOf(src) < 0);
+                    } else {
+                        rst = true;
+                    }
+                }
+                break;
+            default:
+                rst = true;
+        }
+        return rst;
+    };
+    const private_chkArrVal = function(arr, key, compVal, compStr) {
+        let rst = false;
+        if (arr.length > 0) {
+            for (const arrItem of arr) {
+                if (arrItem[key]) {
+                    rst = private_chkVal(arrItem[key], compVal, compStr);
+                }
+                if (rst) {
+                    break;
+                }
+            }
+        } else {
+            // 在某些判断条件下(含有'非'判断),如arr没有数组项,默认结果反而是true
+            switch (compStr) {
+                case '!=' :
+                case '!==' :
+                case 'not in':
+                    rst = true;
+                    break;
+                default:
+                    break;
+            }
+        }
+        return rst;
+    };
+    const private_filter_compare = function(item, filterCfg) {
+        const compareObj = {};
+        let compRst = true;
+        let curComparePrjData = null;
+        let startIdx = 0;
+        const private_ref_join = function(refKey, targetDataKey, targetPropertyKey) {
+            let rst = null;
+            let objDataArr = null;
+            curComparePrjData = getModuleDataByKey(prjData, targetDataKey);
+            try {
+                if (curComparePrjData !== null) {
+                    objDataArr = getActDataArr(curComparePrjData);
+                    for (const dtl of objDataArr) {
+                        if (item[refKey] === dtl[targetPropertyKey]) {
+                            rst = dtl;
+                            break;
+                        }
+                    }
+                }
+            } finally {
+                curComparePrjData = null;
+            }
+            return rst;
+        };
+        for (const cfg of filterCfg[JV.PROP_FILTER_KEYS]) {
+            if (cfg[JV.PROP_FILTER_COMPARE_VAL]) {
+                // 比较key值
+                const keys = cfg.key.split('.');
+                if (keys.length > 1) {
+                    let lastObj = item;
+                    for (let i = 0; i < keys.length - 1; i++) {
+                        if (keys[i].indexOf('ref_join(') === 0) {
+                            const params = keys[i].slice(9, keys[i].length - 1).split(',');
+                            if (params.length === 3) {
+                                lastObj = private_ref_join(params[0], params[1], params[2]);
+                            }
+                            if (!(lastObj)) {
+                                compRst = false;
+                                break;
+                            }
+                        } else {
+                            lastObj = item[keys[i]];
+                            if (!(lastObj)) {
+                                compRst = false;
+                                break;
+                            }
+                        }
+                    }
+                    if (lastObj) {
+                        if (lastObj instanceof Array) {
+                            compRst = private_chkArrVal(lastObj, keys[keys.length - 1], cfg[JV.PROP_FILTER_COMPARE_VAL], cfg[JV.PROP_FILTER_CONDITION]);
+                        } else {
+                            compRst = private_chkVal(lastObj[keys[keys.length - 1]], cfg[JV.PROP_FILTER_COMPARE_VAL], cfg[JV.PROP_FILTER_CONDITION]);
+                        }
+                    }
+                } else {
+                    compRst = private_chkVal(item[cfg.key], cfg[JV.PROP_FILTER_COMPARE_VAL], cfg[JV.PROP_FILTER_CONDITION]);
+                }
+            } else if (cfg[JV.PROP_FILTER_COMPARE_OBJ] && cfg[JV.PROP_FILTER_COMPARE_OBJ_KEY]) {
+                // 通过其他对象来过滤
+                if (!curComparePrjData) {
+                    curComparePrjData = getModuleDataByKey(prjData, cfg[JV.PROP_FILTER_COMPARE_OBJ]);
+                }
+                if (cfg[JV.PROP_FILTER_CONDITION] === 'in' || cfg[JV.PROP_FILTER_CONDITION] === 'not in') {
+                    let compareArr = null;
+                    if (!compareObj.hasOwnProperty(cfg[JV.PROP_FILTER_COMPARE_OBJ_KEY] + startIdx.toString())) {
+                        compareObj[cfg[JV.PROP_FILTER_COMPARE_OBJ_KEY] + startIdx.toString()] = [];
+                        compareArr = compareObj[cfg[JV.PROP_FILTER_COMPARE_OBJ_KEY] + startIdx.toString()];
+                        for (const data of getActDataArr(curComparePrjData)) {
+                            if (compareArr.indexOf(data[cfg[JV.PROP_FILTER_COMPARE_OBJ_KEY]]) < 0) {
+                                compareArr.push(data[cfg[JV.PROP_FILTER_COMPARE_OBJ_KEY]]);
+                            }
+                        }
+                    } else {
+                        compareArr = compareObj[cfg[JV.PROP_FILTER_COMPARE_OBJ_KEY] + startIdx.toString()];
+                    }
+                    compRst = private_chkVal(item[cfg.key], compareArr, cfg[JV.PROP_FILTER_CONDITION]);
+                } else {
+                    for (const data of getActDataArr(curComparePrjData)) {
+                        compRst = private_chkVal(item[cfg.key], data[cfg[JV.PROP_FILTER_COMPARE_OBJ_KEY]], cfg[JV.PROP_FILTER_CONDITION]);
+                        if (compRst) break;
+                    }
+                }
+            }
+            startIdx++;
+            if (!compRst) {
+                break; // 有不符合条件的数据则退出(这里的判断条件是and关系)
+            }
+        }
+        return compRst;
+    };
+    const private_sub_filter_compare = function(dtlItem, subFilters) {
+        let cmpRst = false;
+        for (const dtlCfg of subFilters) {
+            cmpRst = private_filter_compare(dtlItem, dtlCfg);
+            if (cmpRst) {
+                if (dtlCfg[JV.PROP_OTHER_SUB_FILTER] && dtlCfg[JV.PROP_OTHER_SUB_FILTER].length > 0) {
+                    cmpRst = private_sub_filter_compare(dtlItem, dtlCfg[JV.PROP_OTHER_SUB_FILTER]);
+                    if (cmpRst) break;
+                } else {
+                    break;
+                }
+            }
+        }
+        return cmpRst;
+    };
+
+    for (const item of tempRstArr) {
+        if (private_filter_compare(item, handleCfg)) {
+            rstArr.push(item);
+        }
+    }
+    if (handleCfg[JV.PROP_OTHER_SUB_FILTER] && handleCfg[JV.PROP_OTHER_SUB_FILTER].length > 0) {
+        const newRstArr = [];
+        for (const dtlItem of rstArr) {
+            const cmpRst = private_sub_filter_compare(dtlItem, handleCfg[JV.PROP_OTHER_SUB_FILTER]);
+            if (cmpRst) {
+                newRstArr.push(dtlItem);
+            }
+        }
+        rstArr = newRstArr;
+    }
+    replaceActDataArr(sourceData, rstArr);
+    // fsUtil.writeObjToFile(sourceData.data, 'D:/GitHome/ConstructionCost/tmp/filteredRst.jsp');
+}
+
+function adjustData(sourceData, adjustCfg) {
+    const rstArr = [];
+    for (const item of getActDataArr(sourceData)) {
+        if (item._doc) {
+            rstArr.push(item._doc);
+        } else {
+            rstArr.push(item);
+        }
+    }
+    for (const item of adjustCfg[JV.PROP_ADJUST_COLLECTION]) {
+        for (const rec of rstArr) {
+            if (item[JV.PROP_ADJUST_ACTION] === 'prefix') {
+                rec[item.key] = item[JV.PROP_ADJUST_ACTION_VAL] + rec[item.key];
+            } else if (item[JV.PROP_ADJUST_ACTION] === 'suffix') {
+                rec[item.key] = rec[item.key] + item[JV.PROP_ADJUST_ACTION_VAL];
+            }
+        }
+    }
+    replaceActDataArr(sourceData, rstArr);
+}
+
+function sortData(sourceData, sortCfg, prjData) {
+    let rst = getActDataArr(sourceData), tempRstArr = [];
+    let sortType = sortCfg[JV.PROP_SORT_TYPE];
+    let srcData = getActDataArr(sourceData);
+    for (let item of srcData) {
+        if (item._doc) {
+            tempRstArr.push(item._doc);
+        } else {
+            tempRstArr.push(item);
+        }
+    }
+    function private_normal_sort(destArr, sortKeys) {
+        destArr.sort(function(a, b){
+            let compRst = 0;
+            for (let comp of sortKeys) {
+                let reverse = (comp.order === 'ascend')?1:(-1);
+                //
+                if (a[comp.key] > b[comp.key]) {
+                    compRst = reverse;
+                    break;
+                } else if (a[comp.key] < b[comp.key]) {
+                    compRst = -reverse;
+                    break;
+                }
+            }
+            return compRst;
+        });
+    }
+    function private_parent_sort(parentArr, parentKeys, childArr, childKeys) {
+        let tmpRst = {}, rst = [];
+        for (let pItem of parentArr) {
+            let pKey = 'key';
+            for (let key of parentKeys) {
+                pKey += '_' + pItem[key];
+            }
+            tmpRst[pKey] = [];
+        }
+        for (let cItem of childArr) {
+            let cKey = 'key';
+            for (let key of childKeys) {
+                cKey += '_' + cItem[key];
+            }
+            if (tmpRst[cKey]) {
+                tmpRst[cKey].push(cItem);
+            } else {
+                //unknown child value! should be filtered!
+            }
+        }
+        // childArr.splice(0);
+        for (let pItem of parentArr) {
+            let pKey = 'key';
+            for (let key of parentKeys) {
+                pKey += '_' + pItem[key];
+            }
+            rst.push(tmpRst[pKey]);
+            // for (let rItem of tmpRst[pKey]) {
+            //     childArr.push(rItem);
+            // }
+        }
+        return rst;
+    }
+    switch (sortType) {
+        case 'tree':
+            let addLevel = true;
+            rst = treeUtil.buildTreeNodeDirectly(tempRstArr, addLevel);
+            let newTopArr = [];
+            if ((sortCfg[JV.PROP_FILTER_TOP_BILLS_NODES] && sortCfg[JV.PROP_FILTER_TOP_BILLS_NODES].length > 0) ||
+                (sortCfg[JV.PROP_FILTER_OTHER_BILLS_NODES] && sortCfg[JV.PROP_FILTER_OTHER_BILLS_NODES].length > 0)) {
+                let local_check_bills = function(tItem) {
+                    let chkDtl = false;
+                    if (tItem.flags && tItem.flags.length > 0) {
+                        for (let flagItem of tItem.flags) {
+                            if (sortCfg[JV.PROP_FILTER_OTHER_BILLS_NODES].indexOf(flagItem.flag) >= 0) {
+                                newTopArr.push(tItem);
+                                chkDtl = true;
+                                break;
+                            }
+                        }
+                    }
+                    if (!chkDtl && tItem.items && tItem.items.length > 0) {
+                        for (let dtlItem of tItem.items) {
+                            local_check_bills(dtlItem);
+                        }
+                    }
+                };
+                for (let topItem of rst) {
+                    let chkTop = false;
+                    if (topItem.flags && topItem.flags.length > 0) {
+                        for (let flagItem of topItem.flags) {
+                            if (sortCfg[JV.PROP_FILTER_TOP_BILLS_NODES].indexOf(flagItem.flag) >= 0) {
+                                newTopArr.push(topItem);
+                                chkTop = true;
+                                break;
+                            }
+                        }
+                    }
+                    if (!chkTop && sortCfg[JV.PROP_FILTER_OTHER_BILLS_NODES] && sortCfg[JV.PROP_FILTER_OTHER_BILLS_NODES].length > 0) {
+                        local_check_bills(topItem);
+                    }
+                }
+            } else {
+                newTopArr = rst;
+            }
+            let destArr = [];
+            // fsUtil.writeObjToFile(newTopArr, 'D:/GitHome/ConstructionCost/tmp/sortedAndFlattedRstBefore.jsp');
+            treeUtil.getFlatArray(newTopArr, destArr, true);
+            // console.log(destArr);
+            replaceActDataArr(sourceData, destArr);
+            // fsUtil.writeObjToFile(sourceData.data, 'D:/GitHome/ConstructionCost/tmp/sortedAndFlattedRst.jsp');
+            break;
+        case 'normal':
+            private_normal_sort(tempRstArr, sortCfg[JV.PROP_SORT_KEYS]);
+            replaceActDataArr(sourceData, tempRstArr);
+            // fsUtil.writeObjToFile(sourceData.data, 'D:/GitHome/ConstructionCost/tmp/normalSortedRst.jsp');
+            break;
+        case 'accord_to_parent':
+            let pcKey = sortCfg[JV.PROP_PARENT_CHILD_SORT_KEY];
+            let parentSrcData = getModuleDataByKey(prjData, pcKey[JV.PROP_PARENT_DATA_KEY]);
+            if (parentSrcData) {
+                let tempParentArr = [];
+                for (let item of getActDataArr(parentSrcData)) {
+                    if (item._doc) {
+                        tempParentArr.push(item._doc);
+                    } else {
+                        tempParentArr.push(item);
+                    }
+                }
+                let sortedRstArr = private_parent_sort(tempParentArr, pcKey[JV.PROP_PARENT_SORT_KEYS], tempRstArr, pcKey[JV.PROP_CHILD_SORT_KEYS]);
+                if (sortCfg[JV.PROP_OTHER_SUB_SORT] && sortCfg[JV.PROP_OTHER_SUB_SORT].length > 0) {
+                    for (let sort of sortCfg[JV.PROP_OTHER_SUB_SORT]) {
+                        if (sort[JV.PROP_SORT_TYPE] === 'normal') {
+                            for (let subArr of sortedRstArr) {
+                                private_normal_sort(subArr, sort[JV.PROP_SORT_KEYS]);
+                            }
+                        } else if (sort[JV.PROP_SORT_TYPE] === 'self_define') {
+                            for (let subArr of sortedRstArr) {
+                                // console.log(subArr);
+                                let selfDefFunc = null;
+                                eval('selfDefFunc = ' + sort[JV.PROP_SORT_TYPE_SELF_DEFINE_LOGIC]);
+                                subArr.sort(selfDefFunc);
+                                // console.log(subArr);
+                            }
+                        }
+                    }
+                }
+                tempRstArr.splice(0);
+                for (let item of sortedRstArr) {
+                    for (let subItem of item) {
+                        tempRstArr.push(subItem);
+                    }
+                }
+            }
+            replaceActDataArr(sourceData, tempRstArr);
+            break;
+        case 'self_define':
+            if (sortCfg[JV.PROP_SORT_TYPE_SELF_DEFINE_LOGIC]) {
+                let selfDefFuncA = null;
+                eval('selfDefFuncA = ' + sortCfg[JV.PROP_SORT_TYPE_SELF_DEFINE_LOGIC]);
+                if (selfDefFuncA !== null) {
+                    tempRstArr.sort(selfDefFuncA);
+                } else {
+                    console.log('sorting function is null!!!');
+                }
+            }
+            replaceActDataArr(sourceData, tempRstArr);
+            break;
+        default:
+            //
+    }
+    return rst;
+}
+
+function setupFunc(obj, ownRawObj) {
+    obj.myOwnRawDataObj = ownRawObj;
+    obj.getProperty = ext_getProperty;
+    // obj.getPropertyByForeignId = ext_getPropertyByForeignId;
+    obj.getArrayProperty = ext_getArrayValues;
+    obj.getBlank = ext_getBlank;
+}
+
+function assembleFields(fieldList, rstDataArr, $PROJECT) {
+    if (fieldList) {
+        for (const field of fieldList) {
+            shielded_exec_env($PROJECT, field, rstDataArr);
+            if ('Precision' in field) {
+                if (field.Precision.type === 'fixed') {
+                    const vrst = eval(field.Precision.fixedMapExpression);
+                    if (vrst && vrst.length === 1) {
+                        field.fixedPrecisionNum = vrst[0];
+                        vrst.splice(0,1);
+                    }
+                } else if (field.Precision.type === 'flexible') {
+                    const vrst = eval(field.Precision.flexibleMapExpression);
+                    if (vrst && vrst.length === 1) {
+                        field.flexiblePrecisionRefObj =vrst[0];
+                        vrst.splice(0,1);
+                    }
+                }
+            }
+        }
+    }
+}
+
+function shielded_exec_env($PROJECT, $ME, rptDataItemObj) {
+    if ($ME[JV.PROP_FIELD_EXP_MAP]) {
+        rptDataItemObj.push(eval($ME[JV.PROP_FIELD_EXP_MAP]));
+    }
+}
+
+function getActPropertyVal(firstPropKey, secPropKey, orgObj) {
+    let rst = null;
+    if (orgObj[firstPropKey]) {
+        rst = orgObj[firstPropKey];
+    } else if (orgObj[secPropKey]) {
+        rst = orgObj[secPropKey];
+    }
+    return rst;
+}
+
+function getDeepProperty(propKey, orgObj, destArr) {
+    const keys = propKey.split('.');
+    const dftPropKey = 'key';
+    const dftPropVal = 'value';
+    const secDftPropVal = 'items';
+    let parent = orgObj;
+    let lastVal = null;
+    for (const key of keys) {
+        if (parent instanceof Array) {
+            for (const item of parent) {
+                if (item[dftPropKey] === key) {
+                    lastVal = getActPropertyVal(dftPropVal, secDftPropVal, item);
+                    break;
+                }
+            }
+        } else {
+            lastVal = null;
+            if (parent[key] !== undefined) {
+                lastVal = parent[key];
+            } else if (parent[secDftPropVal]) {
+                for (const item of parent[secDftPropVal]) {
+                    if (item[dftPropKey] === key) {
+                        // lastVal = item[dftPropVal];
+                        lastVal = getActPropertyVal(dftPropVal, secDftPropVal, item);
+                        break;
+                    }
+                }
+            }
+        }
+        parent = lastVal;
+        if (parent === null) break;
+    }
+    if (destArr && destArr instanceof Array) {
+        destArr.push(lastVal);
+    }
+}
+
+function ext_getProperty(dataKey, propKey) {
+    const rst = [];
+    const parentObj = this;
+    const dtObj = parentObj.myOwnRawDataObj[dataKey];
+    // console.log('dataKey: ' + dataKey);
+    // console.log(dtObj);
+    if (propKey && dtObj) {
+        // console.log('---- dtObj[' + dataKey + '] ----');
+        // console.log(dtObj[dataKey]);
+        const da = getActDataArr(dtObj);
+        // console.log(da);
+        for (const dItem of da) {
+            // const doc = (dItem._doc === null || dItem._doc === undefined) ? dItem : dItem._doc;
+            pri_push_property(propKey, dItem, rst);
+        }
+    }
+    // console.log('---- result ----');
+    // console.log(rst);
+    return rst;
+}
+
+function ext_getArrayValues(dataKey, itemKey) {
+    const rst = [];
+    const parentObj = this;
+    const dtObj = parentObj.myOwnRawDataObj[dataKey];
+    // 计量需要重写
+    const keysArr = itemKey.split('.');
+    const da = getActDataArr(dtObj);
+    // console.log(keysArr);
+    for (const dataItem of da) {
+        let itemArr = [];
+        if (keysArr.length <= 2) {
+            if (dataItem[keysArr[0]] instanceof Array) {
+                if (keysArr.length === 2) {
+                    for (const item of dataItem[keysArr[0]]) {
+                        itemArr.push(item[keysArr[1]]);
+                    }
+                } else {
+                    itemArr = itemArr.concat(dataItem[keysArr[0]]);
+                }
+            } else {
+                if (keysArr.length === 2) {
+                    const subProperty = dataItem[keysArr[0]][keysArr[1]];
+                    if (subProperty instanceof Array) {
+                        itemArr = itemArr.concat(subProperty);
+                    } else {
+                        itemArr.push(subProperty);
+                    }
+                } else {
+                    itemArr.push(dataItem[keysArr[0]]);
+                }
+            }
+        }
+        rst.push(itemArr);
+        // console.log(itemArr);
+    }
+    return rst;
+}
+
+function ext_getBlank(dftVal) {
+    const rst = [];
+    // 计量需要重写
+    return rst;
+}
+
+function ext_getPropertyByForeignId(foreignIdVal, adHocIdKey, propKey, dftValIfNotFound) {
+    const rst = [];
+    // 计量需要重写
+    return rst;
+}
+
+function getActDataArr(dtObj) {
+    let rst = [];
+    if (dtObj) {
+        if (dtObj instanceof Array) {
+            rst = rst.concat(dtObj);
+        } else {
+            rst.push(dtObj);
+        }
+    }
+    return rst;
+}
+
+function replaceActDataArr(dtObj, newArr) {
+    if (dtObj.moduleName === 'projectGLJ') {
+        delete dtObj.data.gljList;
+        dtObj.data.gljList = newArr;
+    } else {
+        delete dtObj.data;
+        dtObj.data = newArr;
+    }
+}
+
+function pri_push_property(propKey, doc, rst) {
+    if (propKey instanceof Array) {
+        //备注:这里的key数组表示取value的优先级
+        for (let pi = 0; pi < propKey.length; pi++) {
+            if (doc.hasOwnProperty('property')) {
+                if (doc['property'].hasOwnProperty(propKey[pi])) {
+                    rst.push(doc['property'][propKey[pi]]);
+                    break;
+                }
+            } else if (doc.hasOwnProperty(propKey[pi])) {
+                rst.push(doc[propKey[pi]]);
+                break;
+            } else {
+                let lenBefore = rst.length;
+                getDeepProperty(propKey[pi], doc, rst);
+                if (rst.length === (lenBefore + 1)) {
+                    if (rst[lenBefore] !== null && rst[lenBefore] !== undefined && rst[lenBefore] !== '') {
+                        break;
+                    } else {
+                        rst.splice(-1, 1); //删除末尾一条数据,给后面留空间
+                    }
+                }
+            }
+            if (pi === propKey.length - 1) rst.push('');
+        }
+    } else {
+        if (doc.hasOwnProperty('property') && doc['property'].hasOwnProperty(propKey)) {
+            rst.push(doc['property'][propKey]);
+        } else if (doc.hasOwnProperty(propKey)) {
+            rst.push(doc[propKey]);
+        } else {
+            getDeepProperty(propKey, doc, rst);
+        }
+    }
+}
+
+// export default Rpt_Data_Extractor;
+module.exports = Rpt_Data_Extractor;

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 1035 - 0
app/reports/util/rpt_excel_util.js


+ 28 - 0
app/reports/util/rpt_font_util.js

@@ -0,0 +1,28 @@
+/**
+ * Created by Tony on 2018/8/2.
+ */
+
+let fontMapObj = {
+    "宋体": "Smart"
+    ,"楷体": "simkai"
+    ,"黑体": "simhei"
+};
+//下划线在option中支持
+//另注意:PDFkit设置字体的时候会检测是否同源,也就是说,如果是同一种字体转换不同的特性(如粗体、斜体),那么在设置的时候会无效
+//      比如前一种是普通的字体,后来想设置这种字体的斜体,实际上这种设置会失效
+
+module.exports = {
+    getActualFont: getActualFont
+}
+
+function getActualFont(mapName, isBold, isItalic) {
+    let rst = ["Smart"];
+    if (fontMapObj[mapName]) rst[0] = fontMapObj[mapName];
+    if (isBold) {
+        rst.push("_bold");
+    }
+    if (isItalic) {
+        rst.push("_italic");
+    }
+    return rst.join("");
+}

+ 391 - 0
app/reports/util/rpt_pdf_util.js

@@ -0,0 +1,391 @@
+/**
+ * Created by chen on 2017/8/16.
+ */
+/**
+ * Created by zhang on 2017/8/14.
+ */
+
+const pdf = require('pdfkit');
+const PDF_SCALE = 0.75;
+const fs = require('fs');
+const jpcCmnHelper = require('../rpt_component/helper/jpc_helper_common');
+const DPI = jpcCmnHelper.getScreenDPI()[0] * PDF_SCALE;
+const JV = require('../rpt_component/jpc_value_define');
+const uuidV1 = require('uuid/v1');
+
+let fontUtil = require('./rpt_font_util');
+
+// 目前不支持下划线
+
+module.exports = {
+    export_pdf_file:export_pdf_file
+}
+
+function export_pdf_file (pageData, paperSize, fName, callback) {
+    let offsetX= 10;
+    let offsetY=10;
+    let doc = new pdf({autoFirstPage: false});
+    // let newName = '' + (new Date()).valueOf();
+    let newName = uuidV1();
+    let stream = doc.pipe(fs.createWriteStream(__dirname.slice(0, __dirname.length - 21) + '/tmp/'+newName+'.pdf'));
+    let pageObj = pageData;
+    // doc.rect(5,5,1190,890).lineWidth(1).strokeColor('black').stroke();//边框
+    let paperSizeIdx = JV.PAGES_SIZE_STR.indexOf(paperSize);
+    let size = JV.PAGES_SIZE[paperSizeIdx];
+
+    function private_getIniPageMergeBorder(mergedBand) {
+        let rst = {};
+        rst[JV.PROP_LEFT] = mergedBand[JV.PROP_LEFT];
+        rst[JV.PROP_RIGHT] = mergedBand[JV.PROP_RIGHT];
+        rst[JV.PROP_TOP] = mergedBand[JV.PROP_TOP];
+        rst[JV.PROP_BOTTOM] = mergedBand[JV.PROP_BOTTOM];
+        rst[JV.PROP_STYLE] = mergedBand[JV.PROP_STYLE];
+        return rst;
+    }
+
+    let newPageMergeBand = private_getIniPageMergeBorder(pageObj[JV.BAND_PROP_MERGE_BAND]);
+    if (pageObj && pageObj.items.length > 0 ) {
+        for(let i=0;i<pageObj.items.length;i++){
+            if (pageData[JV.NODE_PAGE_INFO][JV.NODE_PAGE_SIZE][0] > pageData[JV.NODE_PAGE_INFO][JV.NODE_PAGE_SIZE][1]) {
+                doc.addPage({size:[size[1]*DPI,size[0]*DPI]});
+            } else {
+                doc.addPage({size:[size[0]*DPI,size[1]*DPI]});
+            }
+            let page = pageObj.items[i],
+                fonts = pageObj[JV.NODE_FONT_COLLECTION],
+                styles = pageObj[JV.NODE_STYLE_COLLECTION],
+                controls = pageObj[JV.NODE_CONTROL_COLLECTION];
+
+            if (page[JV.PROP_PAGE_MERGE_BORDER]) {
+                newPageMergeBand[JV.PROP_LEFT] = page[JV.PROP_PAGE_MERGE_BORDER][JV.PROP_LEFT];
+                newPageMergeBand[JV.PROP_RIGHT] = page[JV.PROP_PAGE_MERGE_BORDER][JV.PROP_RIGHT];
+                newPageMergeBand[JV.PROP_TOP] = page[JV.PROP_PAGE_MERGE_BORDER][JV.PROP_TOP];
+                newPageMergeBand[JV.PROP_BOTTOM] = page[JV.PROP_PAGE_MERGE_BORDER][JV.PROP_BOTTOM];
+            }
+            for (let j = 0; j < page.cells.length; j++) {
+                let cell = page.cells[j];
+                private_drawCell(cell, fonts, styles, controls, newPageMergeBand);
+            }
+        }
+    }
+    doc.end();
+    stream.on('finish',function () {
+        console.log(newName + ".pdf was written.");
+        callback(newName);
+    });
+
+    function private_chkIfInMergedBand(mergedBand, cell) {
+        let rst = false;
+        if (mergedBand && cell) {
+            rst = mergedBand[JV.PROP_TOP] <= cell[JV.PROP_AREA][JV.PROP_TOP] && mergedBand[JV.PROP_BOTTOM] >= cell[JV.PROP_AREA][JV.PROP_BOTTOM] &&
+                mergedBand[JV.PROP_LEFT] <= cell[JV.PROP_AREA][JV.PROP_LEFT] && mergedBand[JV.PROP_RIGHT] >= cell[JV.PROP_AREA][JV.PROP_RIGHT];
+        }
+        return rst;
+    }
+
+    function private_drawCell(cell, fonts, styles, controls, mergedBand) {
+        doc.save();
+        // doc.translate(0.5,0.5); //跟H5的canvas不同,不需要这样切换
+        let style = styles[cell[JV.PROP_STYLE]];
+        if (style) {
+            let isNeedMergeBand = private_chkIfInMergedBand(mergedBand, cell);
+            private_drawLine(cell, doc, style, JV.PROP_TOP, [JV.PROP_LEFT, JV.PROP_TOP],[JV.PROP_RIGHT, JV.PROP_TOP], mergedBand, styles, isNeedMergeBand);
+            private_drawLine(cell, doc, style, JV.PROP_RIGHT, [JV.PROP_RIGHT, JV.PROP_TOP],[JV.PROP_RIGHT, JV.PROP_BOTTOM], mergedBand, styles, isNeedMergeBand);
+            private_drawLine(cell, doc, style, JV.PROP_BOTTOM, [JV.PROP_RIGHT, JV.PROP_BOTTOM],[JV.PROP_LEFT, JV.PROP_BOTTOM], mergedBand, styles, isNeedMergeBand);
+            private_drawLine(cell, doc, style, JV.PROP_LEFT, [JV.PROP_LEFT, JV.PROP_BOTTOM],[JV.PROP_LEFT, JV.PROP_TOP], mergedBand, styles, isNeedMergeBand);
+        }
+        private_drawCellText(cell, fonts, controls);
+        doc.restore();
+
+    }
+
+    function private_drawLine(cell, doc, style, styleBorderDest, startP, destP, mergedBand, styles, isNeedMergeBand) {
+        //doc.beginPath();
+        let destStyle = style;
+        if (mergedBand) {
+            if (isNeedMergeBand && parseFloat(mergedBand[styleBorderDest]) === parseFloat(cell[JV.PROP_AREA][styleBorderDest])) {
+                destStyle = styles[mergedBand[JV.PROP_STYLE][JV.PROP_ID]];
+            }
+        }
+        // doc.moveTo(cell[JV.PROP_AREA][startP[0]] + offsetX, cell[JV.PROP_AREA][startP[1]] + offsetY);
+        doc.moveTo( (cell[JV.PROP_AREA][startP[0]] + offsetX) * PDF_SCALE, (cell[JV.PROP_AREA][startP[1]] + offsetY) * PDF_SCALE);
+        if (destStyle[styleBorderDest] && parseFloat(destStyle[styleBorderDest][JV.PROP_LINE_WEIGHT]) !== 0) {
+            doc.lineWidth(1.0 * destStyle[styleBorderDest][JV.PROP_LINE_WEIGHT]);
+            // doc.lineTo(cell[JV.PROP_AREA][destP[0]] + offsetX, cell[JV.PROP_AREA][destP[1]] + offsetY);
+            doc.lineTo((cell[JV.PROP_AREA][destP[0]] + offsetX) * PDF_SCALE, (cell[JV.PROP_AREA][destP[1]] + offsetY) * PDF_SCALE);
+            doc.strokeColor(destStyle[styleBorderDest][JV.PROP_COLOR]);
+        }
+        doc.stroke();
+    }
+    function private_drawCellText(cell, fonts, controls) {
+        if (cell[JV.PROP_VALUE]) {
+            let values = ("" + cell[JV.PROP_VALUE]).split('|');
+            // let font = fonts[cell[JV.PROP_FONT]];
+            let font = null;
+            if (typeof cell[JV.PROP_FONT] === "string") {
+                font = fonts[cell[JV.PROP_FONT]];
+            } else {
+                font = cell[JV.PROP_FONT];
+            }
+            // let control = controls[cell[JV.PROP_CONTROL]];
+            let control = null;
+            if (typeof cell[JV.PROP_CONTROL] === "string") {
+                control = controls[cell[JV.PROP_CONTROL]];
+            } else {
+                control = cell[JV.PROP_CONTROL];
+            }
+            let height = cell[JV.PROP_AREA][JV.PROP_BOTTOM] - cell[JV.PROP_AREA][JV.PROP_TOP];
+            let area = [cell[JV.PROP_AREA][JV.PROP_LEFT] + offsetX, cell[JV.PROP_AREA][JV.PROP_TOP] + offsetY, cell[JV.PROP_AREA][JV.PROP_RIGHT] + offsetX, cell[JV.PROP_AREA][JV.PROP_BOTTOM] + offsetY];
+            let ah = height;
+            let restTopH = 0, restBottomH = 0;
+            if (control[JV.CONTROL_PROPS[JV.CONTROL_PROP_IDX_CLOSE_OUTPUT]] === 'T') {
+                ah = (parseFloat(font[JV.FONT_PROPS[1]]) + JV.OUTPUT_OFFSET[JV.OFFSET_IDX_TOP] + JV.OUTPUT_OFFSET[JV.OFFSET_IDX_BOTTOM]) * values.length;
+                let restH = height - ah;
+                if (control[JV.CONTROL_PROPS[JV.CONTROL_PROP_IDX_VERTICAL]] === 'center') {
+                    restTopH = restH / 2;
+                    restBottomH = restH / 2;
+                } else if (control[JV.CONTROL_PROPS[JV.CONTROL_PROP_IDX_VERTICAL]] === 'bottom') {
+                    restBottomH = restH;
+                } else {
+                    restTopH = restH;
+                }
+            }
+            for (let i = 0; i < values.length; i++) {
+                // area[JV.IDX_TOP] = cell[JV.PROP_AREA][JV.PROP_TOP] + i * (height / values.length) + offsetY;
+                // area[JV.IDX_BOTTOM] = cell[JV.PROP_AREA][JV.PROP_TOP] + (i + 1) * (height / values.length) + offsetY;
+                area[JV.IDX_TOP] = cell[JV.PROP_AREA][JV.PROP_TOP] + i * (ah / values.length) + offsetY + restTopH;
+                area[JV.IDX_BOTTOM] = cell[JV.PROP_AREA][JV.PROP_TOP] + (i + 1) * (ah / values.length) + offsetY + restTopH;
+                if (values[i] === null || values[i] === undefined || values[i] === 'null') {
+                    values[i] = "";
+                }
+                private_drawText(values[i], area, font, control);
+            }
+        }
+    }
+
+    function private_drawText(val, area, font, control) {
+        let dftFontHeight = 12;
+        let output = [];
+        let fontFile = __dirname + '/pdf_base_files/simkai.ttf';
+        if (font) {
+            dftFontHeight = 1 * font[JV.FONT_PROPS[1]];
+            fontFile = __dirname + '/pdf_base_files/' + fontUtil.getActualFont(font[JV.FONT_PROPS[0]], (font[JV.FONT_PROPS[3]] === 'T'), (font[JV.FONT_PROPS[4]] === 'T')) + '.ttf';
+            doc.fontSize(dftFontHeight);
+        }
+        doc.font(fontFile);
+        let options={};
+        let inner_setupControl = function (inArea, inFontHeight, inOutput) {
+            if (control) {
+                private_setupAreaH(inArea, control.Horizon, font.FontAngle, inFontHeight, inOutput, options);
+                private_setupAreaV(inArea, control.Vertical, font.FontAngle, inFontHeight, inOutput);
+            } else {
+                private_setupAreaH(inArea, "left", parseInt(font.FontAngle), inFontHeight, inOutput, options);
+                private_setupAreaV(inArea, "bottom", parseInt(font.FontAngle), inFontHeight, inOutput);
+            }
+        };
+        inner_setupControl(area, dftFontHeight, output);
+        let validAreaTxtWidth = area[JV.IDX_RIGHT] - JV.OUTPUT_OFFSET[JV.OFFSET_IDX_RIGHT] - area[JV.IDX_LEFT] - JV.OUTPUT_OFFSET[JV.OFFSET_IDX_RIGHT];
+        let validTxtLines = Math.floor((area[JV.IDX_BOTTOM] - area[JV.IDX_TOP]) / (dftFontHeight + JV.OUTPUT_OFFSET[JV.OFFSET_IDX_BOTTOM] + JV.OUTPUT_OFFSET[JV.OFFSET_IDX_TOP] + 4));
+        if (parseInt(font.FontAngle) !== 0) {
+            validAreaTxtWidth = area[JV.IDX_BOTTOM] - JV.OUTPUT_OFFSET[JV.OFFSET_IDX_BOTTOM] - area[JV.IDX_TOP] - JV.OUTPUT_OFFSET[JV.OFFSET_IDX_TOP];
+            validTxtLines = Math.floor((area[JV.IDX_RIGHT] - area[JV.IDX_LEFT]) / (dftFontHeight + JV.OUTPUT_OFFSET[JV.OFFSET_IDX_RIGHT] + JV.OUTPUT_OFFSET[JV.OFFSET_IDX_LEFT] + 4));
+        }
+
+        function private_drawUnderline(underLineVal, underLineArea) {
+            //A. 暂不支持角度; B. PDF输出时,坐标没有translate
+            let ctx = doc;
+            //1. 计算下划线的相关坐标
+            let width = ctx.widthOfString(underLineVal);
+            if (width > underLineArea[JV.IDX_RIGHT] - underLineArea[JV.IDX_LEFT]) {
+                width = underLineArea[JV.IDX_RIGHT] - underLineArea[JV.IDX_LEFT];
+            }
+            let height = dftFontHeight;
+            let startX = underLineArea[JV.IDX_LEFT], startY = underLineArea[JV.IDX_TOP], endX = underLineArea[JV.IDX_RIGHT], endY = underLineArea[JV.IDX_BOTTOM];
+            // let startX = 0, startY = 0, endX = width, endY = startY;
+            if (control.Horizon === "left") {
+                startX = Math.round(underLineArea[JV.IDX_LEFT] + JV.OUTPUT_OFFSET[JV.IDX_LEFT]);
+            } else if (control.Horizon === "right") {
+                startX = Math.round(underLineArea[JV.IDX_RIGHT] - width - JV.OUTPUT_OFFSET[JV.IDX_RIGHT]);
+            } else {
+                startX = Math.round( underLineArea[JV.IDX_LEFT] + (underLineArea[JV.IDX_RIGHT] - underLineArea[JV.IDX_LEFT] - width) / 2);
+            }
+            endX = Math.round(startX + width);
+
+            if (control.Vertical === "top") {
+                startY = Math.round(underLineArea[JV.IDX_TOP] + JV.OUTPUT_OFFSET[JV.IDX_TOP] + JV.OUTPUT_OFFSET[JV.IDX_BOTTOM] + height);
+            } else if (control.Vertical === "bottom") {
+                startY = Math.round(underLineArea[JV.IDX_BOTTOM] + JV.OUTPUT_OFFSET[JV.IDX_BOTTOM]);
+            } else {
+                startY = Math.round( underLineArea[JV.IDX_TOP] + (underLineArea[JV.IDX_BOTTOM] - underLineArea[JV.IDX_TOP] + height) / 2) + JV.OUTPUT_OFFSET[JV.IDX_TOP] + JV.OUTPUT_OFFSET[JV.IDX_BOTTOM];
+            }
+            endY = Math.round(startY);
+            //2. 画线
+            // ctx.save();
+            if ( output[1] !== Math.round(output[1])) {
+                ctx.translate(0,0.5);
+            }
+            // ctx.beginPath();
+            // ctx.moveTo(startX, startY);
+            ctx.moveTo(startX * PDF_SCALE, startY * PDF_SCALE);
+            ctx.lineWidth(1);
+            ctx.strokeStyle = "BLACK";
+            // ctx.lineTo(endX, endY);
+            ctx.lineTo(endX * PDF_SCALE, endY * PDF_SCALE);
+            ctx.stroke();
+            // ctx.restore();
+        }
+
+        let rotateOptions;
+        // if (font[JV.FONT_PROPS[5]] === 'T' && parseInt(font.FontAngle) === 0) {
+        //     private_drawUnderline(val);
+        // }
+        if (parseInt(font.FontAngle) !== 0) {
+            if (control){
+                rotateOptions = private_setupAreaRotateOption(area,validAreaTxtWidth,control.Vertical,dftFontHeight, output);
+            } else {
+                rotateOptions = private_setupAreaRotateOption(area,validAreaTxtWidth,"bottom",dftFontHeight, output);
+            }
+            doc.rotate(font.FontAngle,rotateOptions);
+        }
+        if (validAreaTxtWidth >= doc.widthOfString(val) ||
+            (control && control.Shrink !== 'T' && validTxtLines < private_splitString(val, validAreaTxtWidth, doc)) ) {
+            options.width = validAreaTxtWidth * PDF_SCALE;
+            options.height = dftFontHeight * PDF_SCALE;
+            doc.fontSize(dftFontHeight);
+            if (font[JV.FONT_PROPS[5]] === 'T' && parseInt(font.FontAngle) === 0) {
+                private_drawUnderline(val, area);
+            }
+            doc.fontSize(dftFontHeight * PDF_SCALE);
+            doc.text(val,output[0] * PDF_SCALE, output[1] * PDF_SCALE, options);
+            doc.font(__dirname + '/pdf_base_files/simhei_bold_italic.ttf');
+        } else {
+            while (true) {
+                //*/
+                let lines = Math.floor((area[JV.IDX_BOTTOM] - area[JV.IDX_TOP]) / (dftFontHeight + JV.OUTPUT_OFFSET[JV.OFFSET_IDX_BOTTOM] + JV.OUTPUT_OFFSET[JV.OFFSET_IDX_TOP] + 4));
+                lines = (lines === 0 || (control.Shrink === 'T' && control.ShrinkFirst === 'T'))?1:lines;
+                let actLines = private_splitString(val, validAreaTxtWidth, doc);
+                if (actLines.length > lines && dftFontHeight >= 6) {
+                    dftFontHeight--;
+                    doc.fontSize(dftFontHeight);
+                    options.width = validAreaTxtWidth * PDF_SCALE;
+                    options.height = dftFontHeight * PDF_SCALE;
+                    // doc.text(val,output[0], output[1], options);
+                } else {
+                    let aH = dftFontHeight + JV.OUTPUT_OFFSET[JV.OFFSET_IDX_BOTTOM] + JV.OUTPUT_OFFSET[JV.OFFSET_IDX_TOP] + 4;
+                    if ((aH * actLines.length) < (area[JV.IDX_BOTTOM] - area[JV.IDX_TOP]) && (control && control.Vertical !== 'top')) {
+                        if (control.Vertical === 'bottom') {
+                            area[JV.IDX_TOP] = area[JV.IDX_BOTTOM] - (aH * actLines.length);
+                        } else {
+                            area[JV.IDX_TOP] = (area[JV.IDX_TOP] + area[JV.IDX_BOTTOM]) / 2 - (aH * actLines.length) / 2
+                            area[JV.IDX_BOTTOM] = area[JV.IDX_TOP] + (aH * actLines.length);
+                        }
+                    }
+                    let newArea = [], baseTop = area[JV.IDX_TOP];
+                    for (let ai = 0; ai < area.length; ai++) {
+                        newArea[ai] = area[ai];
+                    }
+                    options.width = validAreaTxtWidth * PDF_SCALE;
+                    options.height = dftFontHeight * PDF_SCALE;
+                    for (let lIdx = 0; lIdx < actLines.length; lIdx++) {
+                        newArea[JV.IDX_TOP] = Math.round(aH * lIdx + baseTop);
+                        newArea[JV.IDX_BOTTOM] = Math.round(aH * (lIdx + 1) + baseTop);
+                        inner_setupControl(newArea, dftFontHeight, output);
+                        doc.fontSize(dftFontHeight);
+                        if (font[JV.FONT_PROPS[5]] === 'T' && parseInt(font.FontAngle) === 0) {
+                            private_drawUnderline(actLines[lIdx], newArea);
+                        }
+                        doc.fontSize(dftFontHeight * PDF_SCALE);
+                        doc.text(actLines[lIdx], output[0] * PDF_SCALE, output[1] * PDF_SCALE, options);
+                    }
+                    break;
+                }
+                /*/
+                dftFontHeight--;
+                doc.fontSize(dftFontHeight);
+                if (validAreaTxtWidth >= doc.widthOfString(val) || dftFontHeight < 6) {
+                    options.width = validAreaTxtWidth;
+                    options.height = dftFontHeight;
+                    doc.text(val,output[0], output[1], options);
+                    doc.font(__dirname + '/pdf_base_files/simhei_bold_italic.ttf');
+                    break;
+                }
+                //*/
+            }
+        }
+        // doc.text(val,output[0], output[1], options);
+        doc.font(__dirname + '/pdf_base_files/simhei_bold_italic.ttf');
+        // doc.restore();
+    }
+
+    function private_setupAreaH(area, type, fontAngle, dftFontHeight, outputPoint,options) {
+        let lType = type;
+        if (type !== "left" && type !== "right" && type !== "center") lType = "left";
+        options.align=lType;
+        outputPoint[0]=1 * area[JV.IDX_LEFT]+ JV.OUTPUT_OFFSET[JV.OFFSET_IDX_LEFT];
+    }
+
+    function private_setupAreaV(area, type, fontAngle, dftFontHeight, outputPoint) {
+        let lType = type;
+        if (type !== "top" && type !== "bottom" && type !== "center") lType = "top";
+        switch (lType) {
+            case "top":
+                outputPoint[1] = 1 * area[JV.IDX_TOP] + JV.OUTPUT_OFFSET[JV.OFFSET_IDX_TOP];
+                break;
+            case "bottom":
+                outputPoint[1] = 1 * area[JV.IDX_BOTTOM] - JV.OUTPUT_OFFSET[JV.OFFSET_IDX_BOTTOM]-dftFontHeight;
+                break;
+            case "center":
+                outputPoint[1] = 1 * area[JV.IDX_TOP] + (1 * area[JV.IDX_BOTTOM]-1 * area[JV.IDX_TOP]- dftFontHeight) / 2;
+                break;
+        }
+    }
+
+    function private_splitString(strVal, areaWidth, doc) {
+        let rst = [];
+        if (strVal) {
+            let preSIdx = 0, txtWidth = 0;
+            let currentW = 0;
+            let chnW = doc.widthOfString('一'), otherW = doc.widthOfString('_');
+            for (let sIdx = 0; sIdx < strVal.length; sIdx++) {
+                currentW = (strVal.charCodeAt(sIdx) > 127)?chnW:otherW;
+                txtWidth += currentW;
+                if (txtWidth > areaWidth) {
+                    if (preSIdx < sIdx) {
+                        rst.push(strVal.substr(preSIdx, sIdx - preSIdx));
+                        preSIdx = sIdx;
+                    } else {
+                        rst.push(strVal.substr(preSIdx, 1));
+                        preSIdx = sIdx + 1;
+                    }
+                    txtWidth = currentW;
+                }
+                if (sIdx === strVal.length - 1) {
+                    rst.push(strVal.substr(preSIdx, strVal.length - preSIdx));
+                }
+            }
+        }
+        if (rst.length === 0) rst.push(''); //什么都没有,也得整个空串
+        return rst;
+    }
+
+    function private_setupAreaRotateOption(area,w, type="top",dftFontHeight,outputPoint){
+        let x = (area[JV.IDX_RIGHT] - area[JV.IDX_LEFT])/2+area[JV.IDX_LEFT];
+        let y =(area[JV.IDX_BOTTOM] - area[JV.IDX_TOP])/2+ area[JV.IDX_TOP];
+        let rotateOptions = {origin:[x,y]};
+        let h = area[JV.IDX_RIGHT] - area[JV.IDX_LEFT];
+        outputPoint[0]=x-w/2+JV.OUTPUT_OFFSET[JV.OFFSET_IDX_LEFT];
+        switch (type) {
+            case "top":
+                outputPoint[1] = y- h/2+ JV.OUTPUT_OFFSET[JV.OFFSET_IDX_TOP];
+                break;
+            case "bottom":
+                outputPoint[1] = y+ h/2-JV.OUTPUT_OFFSET[JV.OFFSET_IDX_BOTTOM]-dftFontHeight;
+                break;
+            case "center":
+                outputPoint[1] = y+dftFontHeight/2;
+                break;
+        }
+        return rotateOptions;
+    }
+
+}

+ 288 - 0
app/reports/util/rpt_svg_util.js

@@ -0,0 +1,288 @@
+/**
+ * Created by Tony on 2018/6/27.
+ * 报表直接打印需要
+ */
+
+let JV = require('../rpt_component/jpc_value_define');
+let pdf = require('pdfkit');
+let jpcCmnHelper = require('../rpt_component/helper/jpc_helper_common');
+let SCREEN_DPI = jpcCmnHelper.getScreenDPI();
+let fontUtil = require('./rpt_font_util');
+
+module.exports = {
+    exportSvgStr: function (pagesData, offsetX, offsetY) {
+        let rst = [];
+        let styles = pagesData[JV.NODE_STYLE_COLLECTION],
+            fonts = pagesData[JV.NODE_FONT_COLLECTION],
+            controls = pagesData[JV.NODE_CONTROL_COLLECTION]
+        ;
+        let pdf_doc = new pdf({autoFirstPage: false});
+        for (let idx = 0; idx < pagesData.items.length; idx++) {
+            let page = pagesData.items[idx];
+            let svgPageArr = [], pixelSize = getPixelSize(pagesData);
+            svgPageArr.push("<svg width='" + pixelSize[0] + "' height='" + pixelSize[1] + "'>");
+            let adjustY = 0.5 * ((idx + 1) % 2);
+            // let cnt = 0;
+            for (let cell of page.cells) {
+                svgPageArr.push(buildCellSvg(cell, fonts, styles, controls, page[JV.PROP_PAGE_MERGE_BORDER], pagesData[JV.BAND_PROP_MERGE_BAND], offsetX, offsetY, adjustY, pdf_doc));
+                // cnt++;
+                // console.log(cnt);
+            }
+            svgPageArr.push("</svg>");
+            rst.push(svgPageArr);
+        }
+        return rst;
+    }
+}
+
+function getActualBorderStyle(cell, styles, mergeBorderStyle, pageBorderArea, borderStr) {
+    let rst = styles[cell[JV.PROP_STYLE]][borderStr];
+    if (mergeBorderStyle) {
+        if (parseFloat(cell[JV.PROP_AREA][borderStr]) === parseFloat(pageBorderArea[borderStr])) {
+            if (borderStr === JV.PROP_LEFT || borderStr === JV.PROP_RIGHT) {
+                if (parseFloat(cell[JV.PROP_AREA][JV.PROP_TOP]) >= parseFloat(pageBorderArea[JV.PROP_TOP]) &&
+                    parseFloat(cell[JV.PROP_AREA][JV.PROP_BOTTOM]) <= parseFloat(pageBorderArea[JV.PROP_BOTTOM])) {
+                    rst = mergeBorderStyle[borderStr];
+                }
+            } else if (borderStr === JV.PROP_TOP || borderStr === JV.PROP_BOTTOM) {
+                if (parseFloat(cell[JV.PROP_AREA][JV.PROP_LEFT]) >= parseFloat(pageBorderArea[JV.PROP_LEFT]) &&
+                    parseFloat(cell[JV.PROP_AREA][JV.PROP_RIGHT]) <= parseFloat(pageBorderArea[JV.PROP_RIGHT])) {
+                    rst = mergeBorderStyle[borderStr];
+                }
+            }
+        }
+    }
+    return rst;
+}
+
+function buildCellSvg(cell, fonts, styles, controls, pageMergeBorder, rptMergeBorder, offsetX, offsetY, adjustY, pdf_doc) {
+    let rst = [];
+    let style = styles[cell[JV.PROP_STYLE]];
+    let mergeBandStyle = null;
+    if (rptMergeBorder) {
+        mergeBandStyle = styles[rptMergeBorder[JV.PROP_STYLE][JV.PROP_ID]];
+    }
+    let font = cell[JV.PROP_FONT];
+    if (typeof font === 'string') {
+        font = fonts[cell[JV.PROP_FONT]];
+    }
+    let left = parseInt(cell[JV.PROP_AREA][JV.PROP_LEFT]) + offsetX + 0.5,
+        right = parseInt(cell[JV.PROP_AREA][JV.PROP_RIGHT]) + offsetX + 0.5,
+        top = parseInt(cell[JV.PROP_AREA][JV.PROP_TOP]) + offsetY + adjustY,
+        bottom = parseInt(cell[JV.PROP_AREA][JV.PROP_BOTTOM]) + offsetY + adjustY
+    ;
+    if (style) {
+        let leftBS = getActualBorderStyle(cell, styles, mergeBandStyle, (pageMergeBorder)?pageMergeBorder:rptMergeBorder[JV.PROP_AREA], JV.PROP_LEFT);
+        // if (style[JV.PROP_LEFT] && parseFloat(style[JV.PROP_LEFT][JV.PROP_LINE_WEIGHT]) > 0) {
+        if (leftBS && parseFloat(leftBS[JV.PROP_LINE_WEIGHT]) > 0) {
+            rst.push("<line x1='" + left + "' y1='" + top +
+                "' x2='" + left + "' y2='" + bottom +
+                "' style='stroke:rgb(0,0,0);stroke-width:" + leftBS[JV.PROP_LINE_WEIGHT] +"'/>")
+        }
+        let rightBS = getActualBorderStyle(cell, styles, mergeBandStyle, (pageMergeBorder)?pageMergeBorder:rptMergeBorder[JV.PROP_AREA], JV.PROP_RIGHT);
+        // if (style[JV.PROP_RIGHT] && parseFloat(style[JV.PROP_RIGHT][JV.PROP_LINE_WEIGHT]) > 0) {
+        if (rightBS && parseFloat(rightBS[JV.PROP_LINE_WEIGHT]) > 0) {
+            rst.push("<line x1='" + right + "' y1='" + top +
+                "' x2='" + right + "' y2='" + bottom +
+                "' style='stroke:rgb(0,0,0);stroke-width:" + rightBS[JV.PROP_LINE_WEIGHT] +"'/>")
+        }
+        let topBS = getActualBorderStyle(cell, styles, mergeBandStyle, (pageMergeBorder)?pageMergeBorder:rptMergeBorder[JV.PROP_AREA], JV.PROP_TOP);
+        // if (style[JV.PROP_TOP] && parseFloat(style[JV.PROP_TOP][JV.PROP_LINE_WEIGHT]) > 0) {
+        if (topBS && parseFloat(topBS[JV.PROP_LINE_WEIGHT]) > 0) {
+            rst.push("<line x1='" + left + "' y1='" + top +
+                "' x2='" + right + "' y2='" + top +
+                "' style='stroke:rgb(0,0,0);stroke-width:" + topBS[JV.PROP_LINE_WEIGHT] +"'/>")
+        }
+        let bottomBS = getActualBorderStyle(cell, styles, mergeBandStyle, (pageMergeBorder)?pageMergeBorder:rptMergeBorder[JV.PROP_AREA], JV.PROP_BOTTOM);
+        // if (style[JV.PROP_BOTTOM] && parseFloat(style[JV.PROP_BOTTOM][JV.PROP_LINE_WEIGHT]) > 0) {
+        if (bottomBS && parseFloat(bottomBS[JV.PROP_LINE_WEIGHT]) > 0) {
+            rst.push("<line x1='" + left + "' y1='" + bottom +
+                "' x2='" + right + "' y2='" + bottom +
+                "' style='stroke:rgb(0,0,0);stroke-width:" + bottomBS[JV.PROP_LINE_WEIGHT] +"'/>")
+        }
+    }
+    buildText(rst, cell, font, controls[cell[JV.PROP_CONTROL]], offsetX, offsetY, adjustY, pdf_doc);
+
+    return rst.join("");
+}
+
+function buildText(destRst, cell, font, control, offsetX, offsetY, adjustY, pdf_doc) {
+    let orgFontHeight = parseInt(font[JV.FONT_PROPS[JV.FONT_PROP_IDX_HEIGHT]]);
+    let fontWeight = (font[JV.FONT_PROPS[JV.FONT_PROP_IDX_BOLD]] === 'T')?"bold":"normal";
+    let fontStyle = (font[JV.FONT_PROPS[JV.FONT_PROP_IDX_ITALIC]] === 'T')?"italic":"normal";
+    let dftFontBold = font[JV.FONT_PROPS[3]];
+    let dftFontItalic = font[JV.FONT_PROPS[4]];
+    let fontFile = __dirname + '/pdf_base_files/' + fontUtil.getActualFont(font[JV.FONT_PROPS[0]], (dftFontBold === 'T'), (dftFontItalic === 'T')) + '.ttf';
+    let left = parseInt(cell[JV.PROP_AREA][JV.PROP_LEFT]) + offsetX + 0.5,
+        right = parseInt(cell[JV.PROP_AREA][JV.PROP_RIGHT]) + offsetX + 0.5,
+        top = parseInt(cell[JV.PROP_AREA][JV.PROP_TOP]) + offsetY + adjustY,
+        bottom = parseInt(cell[JV.PROP_AREA][JV.PROP_BOTTOM]) + offsetY + adjustY,
+        x = left, y = top,
+        text_anchor = "start"
+    ;
+    let value = cell[JV.PROP_VALUE];
+    if (!(value)) {
+        value = "";
+    }
+    let values = null;
+    if (typeof value === "string") {
+        values = value.split("|");
+    } else {
+        values = [value];
+    }
+    let stepHeight = (parseInt(cell[JV.PROP_AREA][JV.PROP_BOTTOM]) - parseInt(cell[JV.PROP_AREA][JV.PROP_TOP])) / values.length;
+    if (control) {
+        if (control[JV.CONTROL_PROPS[JV.CONTROL_PROP_IDX_HORIZON]] === "left") {
+            text_anchor = "start";
+            x = left + JV.OUTPUT_OFFSET[JV.OFFSET_IDX_LEFT];
+        } else if (control[JV.CONTROL_PROPS[JV.CONTROL_PROP_IDX_HORIZON]] === "right") {
+            text_anchor = "end";
+            x = right - JV.OUTPUT_OFFSET[JV.OFFSET_IDX_RIGHT];
+        } else if (control[JV.CONTROL_PROPS[JV.CONTROL_PROP_IDX_HORIZON]] === "center") {
+            text_anchor = "middle";
+            x = Math.round((left + right) / 2);
+        }
+    }
+    //*/
+    let height = cell[JV.PROP_AREA][JV.PROP_BOTTOM] - cell[JV.PROP_AREA][JV.PROP_TOP];
+    let area = [cell[JV.PROP_AREA][JV.PROP_LEFT] + offsetX, cell[JV.PROP_AREA][JV.PROP_TOP] + offsetY, cell[JV.PROP_AREA][JV.PROP_RIGHT] + offsetX, cell[JV.PROP_AREA][JV.PROP_BOTTOM] + offsetY];
+    let inner_draw_text = function (textValue) {
+        let dftFontHeight = orgFontHeight;
+        pdf_doc.font(fontFile);
+        pdf_doc.fontSize(dftFontHeight);
+        let actLines = private_splitString(textValue, (area[JV.PROP_RIGHT] - area[JV.PROP_LEFT]), pdf_doc);
+        function inner_build_text(innerTxt, innerArea) {
+            let innerDftFontHeight = (dftFontHeight * 3 / 4); //SVG的字体与canvas的字体大小的切换, 不用考虑取整
+            if (control) {
+                if (control[JV.CONTROL_PROPS[JV.CONTROL_PROP_IDX_VERTICAL]] === "top") {
+                    y = innerArea[JV.PROP_TOP] + JV.OUTPUT_OFFSET[JV.OFFSET_IDX_TOP];
+                } else if (control[JV.CONTROL_PROPS[JV.CONTROL_PROP_IDX_VERTICAL]] === "bottom") {
+                    y = innerArea[JV.PROP_BOTTOM] - JV.OUTPUT_OFFSET[JV.OFFSET_IDX_BOTTOM];
+                } else if (control[JV.CONTROL_PROPS[JV.CONTROL_PROP_IDX_VERTICAL]] === "center") {
+                    y = Math.round((innerArea[JV.PROP_TOP] + innerArea[JV.PROP_BOTTOM] + innerDftFontHeight) / 2 );
+                }
+            }
+            if (font[JV.PROP_NAME] === "宋体") {
+                y--;
+            }
+            destRst.push("<text style='fill:black;font-family:" + font[JV.PROP_NAME] +
+                ";font-weight:" + fontWeight +
+                ";font-style:" + fontStyle +
+                ";font-size:" + innerDftFontHeight + "pt' x='" +
+                x +"' y='" + y + "' text-anchor='" + text_anchor + "' xml:space='preserve'>" + innerTxt + "</text>");
+        }
+        if (actLines.length === 1 || (control && control.Shrink !== 'T')) {
+            inner_build_text(textValue, area);
+        } else {
+            while (true) {
+                if (dftFontHeight > 6) {
+                    dftFontHeight--;
+                    pdf_doc.fontSize(dftFontHeight);
+                    let lines = Math.floor((area[JV.IDX_BOTTOM] - area[JV.IDX_TOP]) / (dftFontHeight + JV.OUTPUT_OFFSET[JV.OFFSET_IDX_BOTTOM] + JV.OUTPUT_OFFSET[JV.OFFSET_IDX_TOP] + 4));
+                    lines = (lines === 0)?1:lines;
+                    actLines = private_splitString(textValue, (area[JV.PROP_RIGHT] - area[JV.PROP_LEFT]), pdf_doc);
+                    if (lines >= actLines.length) {
+                        let aH = dftFontHeight + JV.OUTPUT_OFFSET[JV.OFFSET_IDX_BOTTOM] + JV.OUTPUT_OFFSET[JV.OFFSET_IDX_TOP] + 4;
+                        if ((aH * actLines.length) < (area[JV.IDX_BOTTOM] - area[JV.IDX_TOP]) && (control && control.Vertical !== 'top')) {
+                            if (control.Vertical === 'bottom') {
+                                area[JV.IDX_TOP] = area[JV.IDX_BOTTOM] - (aH * actLines.length);
+                            } else {
+                                area[JV.IDX_TOP] = (area[JV.IDX_TOP] + area[JV.IDX_BOTTOM]) / 2 - (aH * actLines.length) / 2
+                                area[JV.IDX_BOTTOM] = area[JV.IDX_TOP] + (aH * actLines.length);
+                            }
+                        }
+                        let newArea = [], baseTop = area[JV.IDX_TOP];
+                        for (let ai = 0; ai < area.length; ai++) {
+                            newArea[ai] = area[ai];
+                        }
+                        for (let lIdx = 0; lIdx < actLines.length; lIdx++) {
+                            newArea[JV.IDX_TOP] = Math.round(aH * lIdx + baseTop);
+                            newArea[JV.IDX_BOTTOM] = Math.round(aH * (lIdx + 1) + baseTop);
+                            inner_build_text(actLines[lIdx], newArea);
+                        }
+                        break;
+                    }
+                } else {
+                    inner_build_text(textValue, area);
+                    break;
+                }
+            }
+        }
+    };
+    for (let vidx = 0; vidx < values.length; vidx++) {
+        area[JV.IDX_TOP] = cell[JV.PROP_AREA][JV.PROP_TOP] + vidx * (height / values.length);
+        area[JV.IDX_BOTTOM] = cell[JV.PROP_AREA][JV.PROP_TOP] + (vidx + 1) * (height / values.length);
+        inner_draw_text(values[vidx], area, font, control);
+    }
+    /*/
+    for (let vidx = 0; vidx < values.length; vidx++) {
+        //check whether need to adjust the font size
+        let dftFontHeight = orgFontHeight;
+        pdf_doc.font(fontFile);
+        pdf_doc.fontSize(dftFontHeight);
+        while ((right - left) <= pdf_doc.widthOfString(values[vidx])) {
+            if (dftFontHeight > 6) {
+                dftFontHeight--;
+                pdf_doc.fontSize(dftFontHeight);
+            } else {
+                break;
+            }
+        }
+        dftFontHeight = (dftFontHeight * 3 / 4); //SVG的字体与canvas的字体大小的切换, 不用考虑取整
+        if (control) {
+            if (control[JV.CONTROL_PROPS[JV.CONTROL_PROP_IDX_VERTICAL]] === "top") {
+                y = Math.round((top + vidx * stepHeight) + JV.OUTPUT_OFFSET[JV.OFFSET_IDX_TOP]);
+            } else if (control[JV.CONTROL_PROPS[JV.CONTROL_PROP_IDX_VERTICAL]] === "bottom") {
+                y = Math.round((top + (vidx + 1) * stepHeight) - JV.OUTPUT_OFFSET[JV.OFFSET_IDX_BOTTOM]);
+            } else if (control[JV.CONTROL_PROPS[JV.CONTROL_PROP_IDX_VERTICAL]] === "center") {
+                y = Math.round(((top + vidx * stepHeight) + (top + (vidx + 1) * stepHeight) + dftFontHeight) / 2 );
+            }
+        }
+        if (font[JV.PROP_NAME] === "宋体") {
+            y--;
+        }
+        destRst.push("<text style='fill:black;font-family:" + font[JV.PROP_NAME] +
+            ";font-weight:" + fontWeight +
+            ";font-style:" + fontStyle +
+            ";font-size:" + dftFontHeight + "pt' x='" +
+            x +"' y='" + y + "' text-anchor='" + text_anchor + "' xml:space='preserve'>" + values[vidx] + "</text>");
+    }
+    //*/
+}
+
+function private_splitString(strVal, areaWidth, doc) {
+    let rst = [];
+    if (strVal) {
+        let preSIdx = 0, txtWidth = 0;
+        let currentW = 0;
+        let chnW = doc.widthOfString('一'), otherW = doc.widthOfString('_');
+        for (let sIdx = 0; sIdx < strVal.length; sIdx++) {
+            currentW = (strVal.charCodeAt(sIdx) > 127)?chnW:otherW;
+            txtWidth += currentW;
+            if (txtWidth > areaWidth) {
+                if (preSIdx < sIdx) {
+                    rst.push(strVal.substr(preSIdx, sIdx - preSIdx));
+                    preSIdx = sIdx;
+                } else {
+                    rst.push(strVal.substr(preSIdx, 1));
+                    preSIdx = sIdx + 1;
+                }
+                txtWidth = currentW;
+            }
+            if (sIdx === strVal.length - 1) {
+                rst.push(strVal.substr(preSIdx, strVal.length - preSIdx));
+            }
+        }
+    }
+    if (rst.length === 0) rst.push(''); //什么都没有,也得整个空串
+    return rst;
+}
+
+function getPixelSize(pagesData) {
+    let rst = [793,1122];
+    if (pagesData[JV.NODE_PAGE_INFO] && pagesData[JV.NODE_PAGE_INFO][JV.NODE_PAGE_SIZE]) {
+        rst[0] = Math.round(SCREEN_DPI[0] * pagesData[JV.NODE_PAGE_INFO][JV.NODE_PAGE_SIZE][0]);
+        rst[1] = Math.round(SCREEN_DPI[1] * pagesData[JV.NODE_PAGE_INFO][JV.NODE_PAGE_SIZE][1]);
+    }
+    return rst;
+}

+ 53 - 0
app/reports/util/rpt_tmp_file_sweep.js

@@ -0,0 +1,53 @@
+/**
+ * Created by Tony on 2018/7/27.
+ */
+
+let fs = require('fs');
+let cron = require('node-schedule');
+let remove_file_types = [".xlsx", ".pdf", ".jsp"];
+function chkIsRemoveType(file) {
+    let rst = false;
+    for (let fType of remove_file_types) {
+        if (file.indexOf(fType) === (file.length - fType.length)) {
+            rst = true;
+            break;
+        }
+    }
+    return rst;
+}
+let jobObj = {
+    createJob: function (rule, rootPath) {
+        let localRule = rule;
+        if (!localRule) {
+            // setup schedule rule
+            localRule = new cron.RecurrenceRule();
+            // 3:15AM, everyday
+            localRule.dayOfWeek = [1,2,3,4,5,6,0];
+            localRule.hour = 3;
+            localRule.minute = 15;
+        }
+        cron.scheduleJob(localRule, function(){
+            let path = rootPath;
+            fs.exists(path, function (exists) {
+                if (exists) {
+                    fs.readdir(path, function (err, files) {
+                        let currentTime = (new Date()).valueOf();
+                        let timeGap = currentTime - (1000 * 60 * 60 * 24 * 0.5); //half day before
+                        files.forEach(function(file,index){
+                            if (chkIsRemoveType(file)) {
+                                let curPath = path + "/" + file;
+                                fs.stat(curPath,function(err,data){
+                                    if (timeGap > data.mtime) {
+                                        fs.unlink(curPath);
+                                    }
+                                });
+                            }
+                        });
+                    })
+                }
+            })
+        });
+    }
+};
+
+export default jobObj;

+ 3 - 1
app/router.js

@@ -157,7 +157,9 @@ module.exports = app => {
     app.get('/tender/:id/measure/stage/:order/compare', sessionAuth, tenderCheck, stageCheck, 'stageController.compare');
     app.post('/tender/:id/measure/stage/:order/compare/load', sessionAuth, tenderCheck, stageCheck, 'stageController.compareAuditor');
     // 报表
-    app.get('/tender/:id/measure/stage/:order/report', sessionAuth, tenderCheck, 'stageController.report');
+    app.get('/tender/:id/report', sessionAuth, tenderCheck, 'reportController.index');
+    app.get('/tender/:id/measure/stage/:order/report', sessionAuth, tenderCheck, stageCheck, 'reportController.index');
+    app.post('/tender/report_api/getReport', sessionAuth, 'reportController.getReport');
     // 变更管理
     app.get('/tender/:id/change', sessionAuth, tenderCheck, 'changeController.index');
     app.get('/tender/:id/change/status/:status', sessionAuth, tenderCheck, 'changeController.status');

+ 22 - 0
app/service/project.js

@@ -72,6 +72,28 @@ module.exports = app => {
         }
 
         /**
+         * 根据项目code获取项目数据
+         *
+         * @param {int} prjId - id
+         * @return {Object} - 返回项目数据
+         */
+        async getProjectById(prjId) {
+            // 获取项目状态为非禁止的项目
+            this.initSqlBuilder();
+            this.sqlBuilder.setAndWhere('id', {
+                value: this.db.escape(prjId),
+                operate: '=',
+            });
+            this.sqlBuilder.setAndWhere('status', {
+                value: this.status.DISABLE,
+                operate: '<',
+            });
+            const [sql, sqlParam] = this.sqlBuilder.build(this.tableName);
+            const projectData = await this.db.queryOne(sql, sqlParam);
+            return projectData;
+        }
+
+        /**
          * 切换项目
          *
          * @param {Number} projectId - 项目id

+ 38 - 0
app/service/rpt_customize_cfg.js

@@ -0,0 +1,38 @@
+'use strict';
+
+/**
+ * Created by Tony on 2019/8/11.
+ */
+
+const BaseService = require('../base/base_service');
+module.exports = app => {
+
+    class RptCustomizeCfg extends BaseService {
+
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'rpt_customize_cfg';
+            this.dataId = 'id';
+        }
+
+        async getCustomizeCfgByUserId(UserId) {
+            this.initSqlBuilder();
+            this.sqlBuilder.setAndWhere('userId', {
+                value: '"' + UserId + '"',
+                operate: '=',
+            });
+            this.sqlBuilder.columns = ['userId', 'cfg_content'];
+            const [sql, sqlParam] = this.sqlBuilder.build(this.tableName);
+            const list = await this.db.query(sql, sqlParam);
+            return list;
+        }
+    }
+
+    return RptCustomizeCfg;
+};

+ 39 - 0
app/service/rpt_mapping_field.js

@@ -0,0 +1,39 @@
+'use strict';
+
+/**
+ * Created by Tony on 2019/6/3.
+ */
+
+const BaseService = require('../base/base_service');
+module.exports = app => {
+
+    class RptMappingField extends BaseService {
+
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'rpt_mapping_field';
+            this.dataId = 'id';
+        }
+
+        async getMappingFields() {
+            this.initSqlBuilder();
+            // this.sqlBuilder.setAndWhere('id', {
+            //     value: id,
+            //     operate: '=',
+            // });
+            // this.sqlBuilder.orderBy = ['seq'];
+            this.sqlBuilder.columns = ['id', 'Name', 'remark', 'seq', 'items'];
+            const [sql, sqlParam] = this.sqlBuilder.build(this.tableName);
+            const list = await this.db.query(sql, sqlParam);
+            return list;
+        }
+    }
+
+    return RptMappingField;
+};

+ 38 - 0
app/service/rpt_pre_define_cfg.js

@@ -0,0 +1,38 @@
+'use strict';
+
+/**
+ * Created by Tony on 2019/5/28.
+ */
+
+const BaseService = require('../base/base_service');
+module.exports = app => {
+
+    class RptPreDefineCfg extends BaseService {
+
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'rpt_cfg';
+            this.dataId = 'userId';
+        }
+
+        async getCfgById(id) {
+            this.initSqlBuilder();
+            this.sqlBuilder.setAndWhere('userId', {
+                value: '"' + id + '"',
+                operate: '=',
+            });
+            this.sqlBuilder.columns = ['userId', 'defined_content'];
+            const [sql, sqlParam] = this.sqlBuilder.build(this.tableName);
+            const list = await this.db.query(sql, sqlParam);
+            return list;
+        }
+    }
+
+    return RptPreDefineCfg;
+};

+ 74 - 0
app/service/rpt_tpl.js

@@ -0,0 +1,74 @@
+'use strict';
+
+/**
+ * Created by Tony on 2019/5/13.
+ */
+
+const BaseService = require('../base/base_service');
+
+module.exports = app => {
+
+    class RptTpl extends BaseService {
+
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'rpt_tpl';
+            this.dataId = 'id';
+        }
+
+        async getTplById(id) {
+            this.initSqlBuilder();
+            this.sqlBuilder.setAndWhere('id', {
+                value: id,
+                operate: '=',
+            });
+            this.sqlBuilder.columns = ['id', 'rpt_type', 'rpt_tpl_name', 'rpt_content'];
+            const [sql, sqlParam] = this.sqlBuilder.build(this.tableName);
+            const list = await this.db.query(sql, sqlParam);
+            return list;
+        }
+
+        async copyAndCreateTemplate(refTpl) {
+            let rst = null;
+            this.transaction = await this.db.beginTransaction();
+            try {
+                const data = {
+                    id: 0,
+                    rpt_type: refTpl.rpt_type,
+                    rpt_tpl_name: refTpl.rpt_tpl_name,
+                    rpt_content: refTpl.rpt_content
+                };
+                rst = await this.transaction.insert(this.tableName, data);
+                await this.transaction.commit();
+            } catch (ex) {
+                console.log(ex);
+                // 回滚
+                await this.transaction.rollback();
+            }
+            return rst;
+        }
+
+        async updateRptTpl(tplId, tplName, tplObj) {
+            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) };
+                rst = await this.transaction.update(this.tableName, data);
+                this.transaction.commit();
+            } catch (ex) {
+                console.log(ex);
+                // 回滚
+                await this.transaction.rollback();
+            }
+            return rst;
+        }
+
+    }
+    return RptTpl;
+};

+ 109 - 0
app/service/rpt_tree_node.js

@@ -0,0 +1,109 @@
+'use strict';
+
+/**
+ * 报表模板组织架构模型
+ *
+ * @author TonyKang
+ * @date 2019/05/05
+ * @version
+ */
+
+const BaseService = require('../base/base_service');
+module.exports = app => {
+
+    class RptTreeNode extends BaseService {
+
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'rpt_tree_node';
+            this.dataId = 'id';
+        }
+
+        async getNodesByType(typeArr) {
+            this.initSqlBuilder();
+            this.sqlBuilder.setAndWhere('rpt_type', {
+                value: typeArr,
+                operate: 'in',
+            });
+            this.sqlBuilder.columns = ['id', 'name', 'rpt_type', 'pid', 'items', 'last_update_time'];
+            const [sql, sqlParam] = this.sqlBuilder.build(this.tableName);
+
+            const list = await this.db.query(sql, sqlParam);
+            return list;
+        }
+
+        async getNodesByProjectId(prjIdArr) {
+            this.initSqlBuilder();
+            this.sqlBuilder.setAndWhere('pid', {
+                value: prjIdArr,
+                operate: 'in',
+            });
+            this.sqlBuilder.columns = ['id', 'name', 'rpt_type', 'pid', 'items', 'last_update_time'];
+            const [sql, sqlParam] = this.sqlBuilder.build(this.tableName);
+
+            const list = await this.db.query(sql, sqlParam);
+            return list;
+        }
+
+        async getNodeById(id) {
+            this.initSqlBuilder();
+            this.sqlBuilder.setAndWhere('id', {
+                value: id,
+                operate: '=',
+            });
+            this.sqlBuilder.columns = ['id', 'name', 'rpt_type', 'pid', 'items', 'last_update_time'];
+            const [sql, sqlParam] = this.sqlBuilder.build(this.tableName);
+
+            const list = await this.db.query(sql, sqlParam);
+            return list;
+        }
+
+        async createNode(prjId, name) {
+            let rst = null;
+            this.transaction = await this.db.beginTransaction();
+            try {
+                const data = {
+                    // id: 0,
+                    rpt_type: 0,
+                    name: name,
+                    pid: prjId,
+                    items: '[]',
+                    last_update_time: (new Date()).getTime().toString(),
+                };
+                console.log('createNode:');
+                console.log(data);
+                rst = await this.transaction.insert(this.tableName, data);
+                await this.transaction.commit();
+            } catch (ex) {
+                console.log(ex);
+                // 回滚
+                await this.transaction.rollback();
+            }
+            return rst;
+        }
+
+        async updateTopNode(topNode) {
+            try {
+                this.transaction = await this.db.beginTransaction();
+                await this.transaction.update(this.tableName, topNode);
+                // const operate = await this.db.update(this.tableName, topNode);
+                // console.log(operate.affectedRows);
+                // return operate.affectedRows === 1;
+                this.transaction.commit();
+                return true;
+            } catch (ex) {
+                console.log(ex);
+                this.transaction.rollback();
+                return false;
+            }
+        }
+    }
+
+    return RptTreeNode;
+};

+ 11 - 0
app/service/stage.js

@@ -70,6 +70,17 @@ module.exports = app => {
         }
 
         /**
+         * 获取标段下的期最大数量(报表选择用,只需要一些key信息,不需要计算详细数据)
+         * @param tenderId
+         * @returns {Promise<void>}
+         */
+        async getValidStageShort(tenderId) {
+            const sql = 'select id, zh_stage.order from zh_stage where tid = ? order by zh_stage.order';
+            const sqlParam = [tenderId];
+            return await this.db.query(sql, sqlParam);
+        }
+
+        /**
          * 获取标段下的全部计量期,按倒序
          * @param tenderId
          * @returns {Promise<void>}

+ 19 - 0
app/service/stage_bills_final.js

@@ -43,6 +43,25 @@ module.exports = app => {
         }
 
         /**
+         * 获取截止本期数据
+         * @param {Object} tender - 标段id
+         * @param {Object} stage - 本期id
+         * @returns {Promise<void>}
+         */
+        async getFinalDataEx(tender_id, stage_order) {
+            const sql = 'SELECT * FROM ' + this.tableName + ' As Bills' +
+                '  INNER JOIN ( ' +
+                '    SELECT MAX(`sorder`) As `sorder`, `lid` From ' + this.tableName +
+                '      WHERE tid = ? AND sorder < ?' +
+                '      GROUP BY `lid`' +
+                '  ) As MaxFilter ' +
+                '  ON Bills.sorder = MaxFilter.sorder And Bills.lid = MaxFilter.lid' +
+                '  WHERE Bills.tid = ?';
+            const sqlParam = [tender_id, stage_order, tender_id];
+            return await this.db.query(sql, sqlParam);
+        }
+
+        /**
          * 生成本期最终数据
          * @param transaction - 所属事务
          * @param {Object} tender - 标段

+ 234 - 0
app/view/report/index.ejs

@@ -0,0 +1,234 @@
+<% include ../tender/tender_sub_menu.ejs %>
+<div class="panel-content">
+    <div class="panel-title">
+        <div class="title-main d-flex justify-content-between">
+            <div>
+                <div class="d-inline-block">
+                    <div class="dropdown">
+                        <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 class="d-inline-block">
+                    <ul class="nav nav-pills m-0">
+                        <li class="nav-item mr-1"><a href="#man-c" data-toggle="modal" data-target="#man-c" class=" btn btn-outline-primary btn-sm"><i class="fa fa-cog"></i> 通用报表</a></li>
+                        <li class="nav-item"><a href="#add-c" data-toggle="modal" data-target="#add-c" class=" btn btn-outline-primary btn-sm"><i class="fa fa-plus"></i> 定制报表</a></li>
+                    </ul>
+                </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-2">
+                    <div class="sjs-height-1">
+                        <div class="text-center"></div>
+                        <ul id="rptTplTree" class="ztree"></ul>
+                    </div>
+                </div>
+                <div class="col-10">
+                    <div class="toolsbar-f d-flex justify-content-between">
+                        <div class="print-toolsbar">
+                            <div class="panel">
+                                <div class="panel-body">
+                                    <button class="btn btn-secondary btn-sm" type="button">
+                                        <i class="fa fa-print"></i><br>
+                                        打印 <span id="checkCount" class="badge badge-light">5</span>
+                                    </button>
+                                    <button class="btn btn-secondary btn-sm" type="button" href="#export" data-toggle="modal" data-target="#export">
+                                        <i class="fa fa-share-square-o"></i><br>
+                                        导出 <span id="checkCount" class="badge badge-light">5</span>
+                                    </button>
+                                </div>
+                            </div>
+                            <div class="panel">
+                                <div class="panel-body">
+                                    <div class="btn-group" role="group" aria-label="Button group with nested dropdown">
+                                        <div class="btn-group" role="group">
+                                            <button id="btnRptOrientation" type="button" class="btn btn-secondary btn-sm dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">纵向</button>
+                                            <div class="dropdown-menu" aria-labelledby="btnGroupDrop1">
+                                                <a class="dropdown-item" id="hrefRptOrientation" style="cursor:pointer" onclick="zTreeOprObj.changeOrientation(this)">横向</a>
+                                            </div>
+                                        </div>
+                                        <div class="btn-group" role="group">
+                                            <button id="btnRptPageSize"  type="button" class="btn btn-secondary btn-sm dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">A4</button>
+                                            <div class="dropdown-menu" aria-labelledby="btnGroupDrop1">
+                                                <a class="dropdown-item" id="hrefRptPageSize" style="cursor:pointer" onclick="zTreeOprObj.changePageSize(this)">A3</a>
+                                            </div>
+                                        </div>
+                                    </div>
+                                </div>
+                                <div class="panel-foot text-muted">
+                                    预览设置
+                                </div>
+                            </div>
+                            <div class="panel">
+                                <div class="panel-body">
+                                    <div class="btn-group" role="group">
+                                        <button type="button" class="btn btn-secondary btn-sm" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="缩小">-</button>
+                                        <button class="btn btn-secondary btn-sm" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="重置默认大小">1000%</button>
+                                        <button type="button" class="btn btn-secondary btn-sm" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="放大">+</button>
+                                    </div>
+                                </div>
+                                <div class="panel-foot text-muted">
+                                    缩放
+                                </div>
+                            </div>
+                            <div class="panel">
+                                <div class="panel-body">
+                                    <div class="btn-group" role="group" aria-label="Button group with nested dropdown">
+                                        <button type="button" class="btn btn-secondary btn-sm" data-toggle="modal" data-target="#paper"><i class="fa fa-file-o"></i> 纸张</button>
+                                        <button type="button" class="btn btn-secondary btn-sm" data-toggle="modal" data-target="#format"><i class="fa fa-bold"></i> 格式</button>
+                                        <button type="button" class="btn btn-secondary btn-sm" data-toggle="modal" data-target="#content"><i class="fa fa-file-text-o"></i> 内容</button>
+                                    </div>
+                                </div>
+                                <div class="panel-foot text-muted">
+                                    报表设置
+                                </div>
+                            </div>
+                            <div class="panel">
+                                <div class="panel-body">
+                                    <button class="btn btn-secondary btn-sm" type="button" data-toggle="modal" data-target="#sign">
+                                        <i class="fa fa-pencil"></i><br>
+                                        电子签名
+                                    </button>
+                                </div>
+                            </div>
+                            <div class="panel">
+                                <div class="panel-body">
+                                    <div class="input-group input-group-sm">
+                                        <div class="input-group-prepend">
+                                            <button type="button" class="btn btn-secondary btn-sm" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="上一页" onclick="rptControlObj.prePage(this)"><i class="fa fa-chevron-left"></i></button>
+                                        </div>
+                                        <input class="form-control" id="rpt_page_num" value="1/10" onKeydown="rptControlObj.onKeydown(event, this)" style="width:60px" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="输入页码按回车键,快速跳转">
+                                        <div class="input-group-append">
+                                            <button type="button" class="btn btn-secondary btn-sm" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="下一页" onclick="rptControlObj.nextPage(this)"><i class="fa fa-chevron-right"></i></button>
+                                        </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 type="text/javascript">  autoFlashHeight();</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/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/string_util_light.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_main.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>
+<script type="text/javascript" src="/public/report/js/rpt_print.js"></script>
+
+<script type="text/javascript">
+    const topTreeNodesValue = <%- rpt_tpl_data %>;
+    let CUST_CFG = <%- cust_cfg %>;
+    CUST_CFG = JSON.parse(CUST_CFG[0].cfg_content);
+    const PROJECT_ID = <%- project_id %>;
+    const TENDER_ID = <%- tender_id %>;
+    const STAGE_ID = <%- stg_id %>;
+    const STAGE_ORDER = <%- stg_order %>;
+    const STAGE_TIMES = <%- stg_times %>;
+    const STAGE_LIST = <%- stage_list %>;
+    let current_stage_order = -1;
+    let current_stage_id = -1;
+    let current_stage_times = -1;
+
+    if (STAGE_ORDER > 0) {
+        current_stage_order = STAGE_ORDER;
+        current_stage_id = STAGE_ID;
+        current_stage_times = STAGE_TIMES;
+    } 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_times = STAGE_LIST[STAGE_LIST.length - 1].times;
+    }
+    buildStageSelection();
+
+    for (let item of topTreeNodesValue) {
+        item.items = JSON.parse(item.items);
+        if (item.items && item.items.length > 0) {
+            for (let dtlItem of item.items) {
+                chkAndSetNode(dtlItem);
+            }
+        }
+    }
+
+    function chkAndSetNode(parentItem) {
+        if (parentItem.nodeType === 1) {
+            parentItem.isParent = true;
+        }
+        if (parentItem.items) {
+            for (let dtlItem of parentItem.items) {
+                chkAndSetNode(dtlItem);
+            }
+        }
+    }
+
+    const SCREEN_DPI = [];
+    function getScreenDPI() {
+        if (SCREEN_DPI.length === 0) {
+            if (window.screen.deviceXDPI != undefined) {
+                SCREEN_DPI.push(window.screen.deviceXDPI);
+                SCREEN_DPI.push(window.screen.deviceYDPI);
+            } else {
+                let tmpNode = document.createElement("DIV");
+                tmpNode.style.cssText = "width:1in;height:1in;position:absolute;left:0px;top:0px;z-index:99;visibility:hidden";
+                document.body.appendChild(tmpNode);
+                SCREEN_DPI.push(parseInt(tmpNode.offsetWidth));
+                SCREEN_DPI.push(parseInt(tmpNode.offsetHeight));
+                tmpNode.parentNode.removeChild(tmpNode);
+            }
+        }
+        return SCREEN_DPI;
+    }
+
+    function buildStageSelection() {
+        $("#btnCurrentStage")[0].innerText = '第' + current_stage_order + '期';
+        $("#optionSelectableStages").empty();
+        if (STAGE_LIST.length > 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);
+                }
+            }
+        }
+    }
+
+    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);
+        buildStageSelection();
+        //还有必要触发报表刷新!
+    }
+
+    rptTplObj.iniPage();
+</script>

+ 47 - 0
app/view/report/rpt_content_format.html

@@ -0,0 +1,47 @@
+<div class="modal fade" id="content_format" data-backdrop="static">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">格式</h5><h5 class="modal-title" id="update_msg_response"></h5>
+                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                    <span aria-hidden="true">&times;</span>
+                </button>
+            </div>
+            <div class="modal-body">
+                <div class="row mb-1" id="font_cfg_blank_flag"></div>
+                <div class="row mb-1">
+                    <div class="col-4">
+                        <div class="form-check">
+                            <label class="form-check-label">
+                                <input id="rpt_vertical_line" type="checkbox" class="form-check-input" onchange="rptControlObj.changeCfgOption('showVerticalLine', this)">
+                                边框竖线
+                            </label>
+                        </div>
+                    </div>
+                    <div class="col-4" style="display: none">
+                        <div class="form-check">
+                            <label class="form-check-label">
+                                <input id="rpt_narrow" type="checkbox" class="form-check-input" onchange="rptControlObj.changeCfgOption('isNarrow', this)">
+                                窄体输出
+                            </label>
+                        </div>
+                    </div>
+                    <div class="col-4">
+                        <div class="form-check">
+                            <label class="form-check-label">
+                                <input id="rpt_fill_zero" type="checkbox" class="form-check-input" onchange="rptControlObj.changeCfgOption('fillZero', this)">
+                                小数补0
+                            </label>
+                        </div>
+                    </div>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-link float-left" onclick="rptControlObj.restoreCustCFG()">恢复默认值</button>
+                <button type="button" class="btn btn-link float-left" onclick="rptControlObj.saveCustCfg()">存为默认值</button>
+                <button type="button" class="btn btn-primary" data-dismiss="modal" onclick="rptControlObj.confirmCfgChange()">确定</button>
+                <button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
+            </div>
+        </div>
+    </div>
+</div>

+ 38 - 0
app/view/report/rpt_export.html

@@ -0,0 +1,38 @@
+<div class="modal fade" id="export" 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">
+                <div class="row">
+                    <div class="col-6">
+                        <a class="btn btn-block btn-outline-secondary" id="PDF_TYPE" onclick="rptControlObj.changeType('PDF')">
+                            <!-- <i class="fa fa-check-square pull-right"></i> -->
+                            <div class="card-body text-center">
+                                <h1 class="display-3"><i class="fa fa-file-pdf-o"></i></h1>
+                                <h1>PDF</h1>
+                            </div>
+                        </a>
+                    </div>
+                    <div class="col-6">
+                        <a class="btn btn-block btn-primary" id="EXCEL_TYPE" onclick="rptControlObj.changeType('Excel')">
+                            <!-- <i class="fa fa-check-square pull-right"></i> -->
+                            <div class="card-body text-center">
+                                <h1 class="display-3"><i class="fa fa-file-excel-o"></i></h1>
+                                <h1>Excel</h1>
+                            </div>
+                        </a>
+                    </div>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
+                <a onclick="rptControlObj.outputRpt();" class="btn btn-primary">确定</a>
+            </div>
+        </div>
+    </div>
+</div>

+ 46 - 0
app/view/report/rpt_export_excel.html

@@ -0,0 +1,46 @@
+<div class="modal fade" id="export_excel" data-backdrop="static">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">批量导出Excel</h5>
+                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                    <span aria-hidden="true">&times;</span>
+                </button>
+            </div>
+            <div class="modal-body">
+                <div class="row">
+                    <div class="col-6">
+                        <div class="card">
+                            <img class="card-img-top" src="/web/building_saas/img/p2.png">
+                            <div class="card-body px-3">
+                                <div class="form-check">
+                                    <input class="form-check-input" type="radio" name="excelExportTypeRadio" id="excelExportType_AllInOneBook" value="option1" checked>
+                                    <label class="form-check-label" for="excelExportType_AllInOneBook">
+                                        多个表导出一个Excel文件
+                                    </label>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                    <div class="col-6">
+                        <div class="card">
+                            <img class="card-img-top" src="/web/building_saas/img/p1.png">
+                            <div class="card-body px-3">
+                                <div class="form-check">
+                                    <input class="form-check-input" type="radio" name="excelExportTypeRadio" id="excelExportType_IndividualBook" value="option2" checked>
+                                    <label class="form-check-label" for="excelExportType_IndividualBook">
+                                        每个表导出一个Excel文件
+                                    </label>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <a onclick="rptControlObj.getExcel()" class="btn btn-primary" data-dismiss="modal">确定导出</a>
+                <button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
+            </div>
+        </div>
+    </div>
+</div>

+ 37 - 0
app/view/report/rpt_margins.html

@@ -0,0 +1,37 @@
+<div class="modal fade" id="paper_margins" 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">
+                <div class="row" id="element_area_1">
+                    <div class="form-group col-md-3">
+                        <label>页边距-左</label>
+                        <input class="form-control input-sm" id="elementMargin_Left" type="number" value="10" step="1" min="0" max="100" onchange="rptControlObj.changeMargin('Left', this)" onkeyup="rptControlObj.changeMargin('Left', this)">
+                    </div>
+                    <div class="form-group col-md-3">
+                        <label>页边距-右</label>
+                        <input class="form-control input-sm" id="elementMargin_Right" type="number" value="10" step="1" min="0" max="100" onchange="rptControlObj.changeMargin('Right', this)" onkeyup="rptControlObj.changeMargin('Right', this)">
+                    </div>
+                    <div class="form-group col-md-3">
+                        <label>页边距-上</label>
+                        <input class="form-control input-sm" id="elementMargin_Top" type="number" value="10" step="1" min="0" max="100" onchange="rptControlObj.changeMargin('Top', this)" onkeyup="rptControlObj.changeMargin('Top', this)">
+                    </div>
+                    <div class="form-group col-md-3">
+                        <label>页边距-下</label>
+                        <input class="form-control input-sm" id="elementMargin_Bottom" type="number" value="10" step="1" min="0" max="100" onchange="rptControlObj.changeMargin('Bottom', this)" onkeyup="rptControlObj.changeMargin('Bottom', this)">
+                    </div>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-link float-left" data-dismiss="modal" onclick="rptControlObj.saveCustCfg()">存为默认值</button>
+                <button type="button" class="btn btn-primary" data-dismiss="modal" onclick="rptControlObj.confirmCfgChange()">确定</button>
+                <button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
+            </div>
+        </div>
+    </div>
+</div>

+ 42 - 0
app/view/report/rpt_print.html

@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <title></title>
+</head>
+<style type="text/css">
+    .pageBreakOrg {
+        page-break-before: auto;
+        page-break-after: always;
+    }
+    .pageBreak {
+        page-break-before: auto;
+        page-break-after: auto;
+    }
+    @page {size: A4 portrait;}
+    body {page: page}
+    div {page: page}
+</style>
+<!--
+    .printPage {
+        page: page
+    }
+    @page {size: A4 landscape;}
+    div { page: page }
+@page {margin: auto;}
+@page horizon {size: A4 landscape;}
+@page vertical1 {size: A4 portrait;}
+    @page horizon1 {size: A4 landscape;}
+    @page vertical1 {size: A4 portrait;}
+-->
+<script type="text/javascript" src="/web/building_saas/report/js/jpc_output_value_define.js"></script>
+<script src="/lib/jquery/jquery-3.2.1.min.js"></script>
+<script type="text/javascript" src="/web/building_saas/report/js/jpc_output.js"></script>
+<script type="text/javascript" src="/web/building_saas/report/js/rpt_print.js"></script>
+<script type="text/javascript" src="/web/building_saas/report/js/rpt_preview_common.js"></script>
+<body onload="loading()" onbeforeunload="closing()">
+    <canvas id="chkCanvas" style="display:none"></canvas>
+</body>
+<SCRIPT type="text/javascript">
+</SCRIPT>
+</html>

+ 26 - 0
app/view/report/rpt_printA3.html

@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <title></title>
+</head>
+<style type="text/css">
+    .pageBreak {
+        page-break-before: auto;
+        page-break-after: auto;
+    }
+    @page {size: A3 portrait;}
+    body {page: page}
+    div {page: page}
+</style>
+<script type="text/javascript" src="/web/building_saas/report/js/jpc_output_value_define.js"></script>
+<script src="/lib/jquery/jquery-3.2.1.min.js"></script>
+<script type="text/javascript" src="/web/building_saas/report/js/jpc_output.js"></script>
+<script type="text/javascript" src="/web/building_saas/report/js/rpt_print.js"></script>
+<script type="text/javascript" src="/web/building_saas/report/js/rpt_preview_common.js"></script>
+<body onload="loading()" onbeforeunload="closing()">
+<canvas id="chkCanvas" style="display:none"></canvas>
+</body>
+<SCRIPT type="text/javascript">
+</SCRIPT>
+</html>

+ 37 - 0
app/view/report/rpt_select_projects.html

@@ -0,0 +1,37 @@
+<div class="modal fade" id="cpoj" 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">
+                <div class="modal-auto-height">
+                    <ul id="prjFolderTree" class="ztree"></ul>
+                </div>
+            </div>
+            <div class="modal-footer" id="divReqBillSummary" style="display:none">
+                <button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
+                <a onclick="zTreeOprObj.requestBillsSummaryRpt()" class="btn btn-primary" data-dismiss="modal">确定</a>
+            </div>
+            <div class="modal-footer" id="divReqGljSummary" style="display:none">
+                <button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
+                <a onclick="zTreeOprObj.requestGljSummaryRpt()" class="btn btn-primary" data-dismiss="modal" >确定</a>
+            </div>
+            <div class="modal-footer" id="divReqCommonSummaryExcel" style="display:none">
+                <button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
+                <a onclick="zTreeOprObj.requestSumAndNormalRptForAllInOneExcel()" class="btn btn-primary" data-dismiss="modal">确定</a>
+            </div>
+            <div class="modal-footer" id="divReqCommonSummaryMultiExcel" style="display:none">
+                <button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
+                <a onclick="zTreeOprObj.requestSumAndNormalRptForMultiExcel()" class="btn btn-primary" data-dismiss="modal">确定</a>
+            </div>
+            <div class="modal-footer" id="divReqCommonSummaryPDF" style="display:none">
+                <button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
+                <a onclick="zTreeOprObj.requestSumAndNormalRptForPDF()" class="btn btn-primary" data-dismiss="modal">确定</a>
+            </div>
+        </div>
+    </div>
+</div>

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 3141 - 2700
package-lock.json


+ 1 - 0
package.json

@@ -41,6 +41,7 @@
     "egg-mock": "^3.14.0",
     "eslint": "^4.17.0",
     "eslint-config-egg": "^5.0.0",
+    "pdfkit": "^0.8.2",
     "webstorm-disable-index": "^1.2.0"
   },
   "engines": {