فهرست منبع

Merge branch 'master' of http://smartcost.f3322.net:3000/SmartCost/YangHuOperation

zhongzewei 6 سال پیش
والد
کامیت
c1cb8a9541

+ 1 - 1
config/config.js

@@ -15,7 +15,7 @@ module.exports = {
             useMongoClient: true
         }
     },
-    pp:{ server: "172.18.111.231",
+    pp:{ server: "112.74.42.187",
         port: "27017",
         options:{
             user:'smartcost',

+ 17 - 0
modules/all_models/online_logs.js

@@ -0,0 +1,17 @@
+/**
+ * Created by zhang on 2019/4/12.
+ */
+//用户在线时长统计
+
+let mongoose = require("mongoose");
+let Schema = mongoose.Schema;
+
+// 表结构
+let schema = {
+    userID: String,
+    compilationID: String,
+    dateString: String,
+    dateTime:Number,//dateString转换回毫秒数对应的数值,方便查询
+    online_times: Number
+};
+mongoose.model("online_logs", new Schema(schema, {versionKey: false}),"online_logs");

+ 3 - 1
modules/all_models/rpt_cfg.js

@@ -11,7 +11,9 @@ let FormatSchema = new Schema({
     "ShowZero" : String,
     "Horizon" : String,
     "Vertical" : String,
-    "Wrap" : String
+    "Wrap" : String,
+    "ShrinkFirst" : String,
+    "CloseOutput" : String
 });
 let FontSchema = new Schema({
     "ID" : String,

+ 7 - 2
modules/reports/facade/rpt_tpl_data_facade.js

@@ -11,7 +11,8 @@ let projectFacade = require('../../../modules/main/facade/project_facade');
 
 module.exports = {
     prepareProjectData: prepareProjectData,
-    getBudgetSummayDatas: getBudgetSummayDatas
+    getBudgetSummayDatas: getBudgetSummayDatas,
+    getGLJSummayDatas: getGLJSummayDatas
 };
 
 function prepareProjectData(userId, prjId, filter, callback) {
@@ -44,4 +45,8 @@ function prepareProjectData(userId, prjId, filter, callback) {
 
 async function getBudgetSummayDatas(prjIds) {
     return await projectFacade.getBudgetSummayDatas(prjIds);
-}
+}
+
+async function getGLJSummayDatas(prjIds) {
+    return await projectFacade.getGLJSummayDatas(prjIds);
+}

+ 46 - 18
modules/reports/rpt_component/jpc_cross_tab.js

@@ -123,16 +123,19 @@ JpcCrossTabSrv.prototype.createNew = function(){
                     for (let j = 1; j < dataSeq[i].length; j++) {
                         b1 = false;
                         for (let k = 0; k < rstFieldsIdx.length; k++) {
-                            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 (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;
+                                    }
                                 }
                             }
                         }
@@ -195,6 +198,7 @@ JpcCrossTabSrv.prototype.createNew = function(){
         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 = [];
@@ -207,6 +211,7 @@ JpcCrossTabSrv.prototype.createNew = function(){
         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);
@@ -452,8 +457,8 @@ JpcCrossTabSrv.prototype.createNew = function(){
             //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, unitFactor, controls, $CURRENT_RPT, customizeCfg));
-            tabRstLst.push(me.outputRowTabCommon(rptTpl, dataObj, page, bands, JV.NODE_CROSS_ROW_AD_HOC, 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
@@ -473,7 +478,7 @@ JpcCrossTabSrv.prototype.createNew = function(){
         }
         return rst;
     };
-    JpcCrossTabResult.outputRowTabCommon = function(rptTpl, dataObj, page, bands, tabStr, unitFactor, controls, $CURRENT_RPT, customizeCfg) {
+    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;
@@ -485,13 +490,13 @@ JpcCrossTabSrv.prototype.createNew = function(){
                 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 < me.row_fields_idx.length; i++) {
+                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 me.row_fields_idx[i] !== 'object') {
-                            data_field = data_details[me.row_fields_idx[i]];
+                        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];
@@ -534,6 +539,7 @@ JpcCrossTabSrv.prototype.createNew = function(){
                 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') {
@@ -558,20 +564,35 @@ JpcCrossTabSrv.prototype.createNew = function(){
                             } else {
                                 if (colIdx === 0) JpcFieldHelper.resetFormat(tab_field, map_data_field, customizeCfg);
                             }
-                            rst.push(me.outputTabField(band, tab_field, data_field, valuesIdx[colIdx], -1, 1, 0, cols, colIdx, unitFactor, false, controls));
+                            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);
                     }
                 }
             }
@@ -591,8 +612,15 @@ JpcCrossTabSrv.prototype.createNew = function(){
                 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 = data_details[me.content_fields_idx[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++) {

+ 23 - 7
modules/reports/rpt_component/jpc_ex.js

@@ -249,6 +249,18 @@ JpcExSrv.prototype.createNew = function(){
         rst.items = [];
         let bands = JpcBand.createNew(rptTpl, defProperties);
         try {
+            function getPageMergeBorder() {
+                let mergeRst = null;
+                if (bands[JV.BAND_PROP_MERGE_BAND]) {
+                    let 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;
+            }
             //1.
             let rstPage = {};
             rstPage[JV.PROP_PAGE_SEQ] = 1;
@@ -259,6 +271,10 @@ JpcExSrv.prototype.createNew = function(){
             } else if (me.billTab) {
                 rstPage[JV.PROP_CELLS] = me.billTab.outputAsPreviewPage(rptTpl, bands, rst[JV.NODE_CONTROL_COLLECTION], me);
             }
+            let pageMergeBorder = getPageMergeBorder();
+            if (pageMergeBorder) {
+                rstPage[JV.PROP_PAGE_MERGE_BORDER] = pageMergeBorder;
+            }
             rst.items.push(rstPage);
             //2.
             if (bands[JV.BAND_PROP_MERGE_BAND]) {
@@ -315,16 +331,16 @@ JpcExSrv.prototype.createNew = function(){
     JpcResult.outputAsSimpleJSONPage = function(rptTpl, dataObj, bands, page, controls, customizeCfg) {
         let me = this, rst = null;
         function getPageMergeBorder() {
-            let rst = null;
+            let mergeRst = null;
             if (bands[JV.BAND_PROP_MERGE_BAND]) {
                 let mergedBand = bands[JV.BAND_PROP_MERGE_BAND];
-                rst = {};
-                rst[JV.PROP_LEFT] = parseInt(mergedBand[JV.PROP_LEFT].toFixed(0));
-                rst[JV.PROP_RIGHT] = parseInt(mergedBand[JV.PROP_RIGHT].toFixed(0));
-                rst[JV.PROP_TOP] = parseInt(mergedBand[JV.PROP_TOP].toFixed(0));
-                rst[JV.PROP_BOTTOM] = parseInt(mergedBand[JV.PROP_BOTTOM].toFixed(0));
+                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 rst;
+            return mergeRst;
         }
         if (me.totalPages >= page) {
             rst = {};

+ 51 - 6
modules/reports/rpt_component/jpc_flow_tab.js

@@ -821,6 +821,10 @@ JpcFlowTabSrv.prototype.createNew = function(){
     };
     JpcFlowTabResult.combinePageCells = function (rstPageCells, verticalCombinePos, horizonCombinePos) {
         // let me = this;
+        //备注:纵向合并要考虑以下因素:
+        //     如果有多个column纵向合并,需要总体考虑分割,
+        //     假如:第一列的前3个数据(1、2、3)是相同的,第二列中第2、3、4行数据相同,那么第二列只能合并2、3行的数据,不能合并到第四行
+        //     同理如此类推第三列...n列
         if (verticalCombinePos.length > 0 || horizonCombinePos.length > 1) {
             let cacheObj = {vCache:{}, hCache: {}, hCacheStr: []};
             let removeCellIds = [];
@@ -848,19 +852,56 @@ JpcFlowTabSrv.prototype.createNew = function(){
                 }
             }
             if (verticalCombinePos.length > 0) {
-                for (let vPosArr of verticalCombinePos) {
+                let preColMergePosArr = []; //见上面备注描述,纪录当前列的分割坐标集合情况
+                let private_chk_in_pre_merge = function (preIdx, newCell) {
+                    let rst = true;
+                    if (preIdx >= 0 && preIdx < preColMergePosArr.length) {
+                        for (let mergeArea of preColMergePosArr[preIdx]) {
+                            if (newCell[JV.PROP_AREA][JV.PROP_TOP] >= mergeArea[0] && newCell[JV.PROP_AREA][JV.PROP_TOP] < mergeArea[1]) {
+                                rst = (newCell[JV.PROP_AREA][JV.PROP_BOTTOM] <= mergeArea[1]);
+                                break;
+                            }
+                        }
+                    }
+                    return rst;
+                };
+                for (let vidx = 0; vidx < verticalCombinePos.length; vidx++) {
+                    let vPosArr = verticalCombinePos[vidx];
                     let pStr = "_" + vPosArr[0] + "_" + vPosArr[1];
                     //rstPageCells的结果已经是按顺序排列了,这里不用再排序
                     if (cacheObj.vCache[pStr] && cacheObj.vCache[pStr].length > 0) {
                         let preCell = rstPageCells[cacheObj.vCache[pStr][0]];
+                        if (vidx === 0) {
+                            //这里要处理下
+                            let minY = 10000, maxY = 0;
+                            for (let preCIdx = 0; preCIdx < cacheObj.vCache[pStr].length; preCIdx++) {
+                                if (minY > rstPageCells[cacheObj.vCache[pStr][preCIdx]][JV.PROP_AREA][JV.PROP_TOP]) minY = rstPageCells[cacheObj.vCache[pStr][preCIdx]][JV.PROP_AREA][JV.PROP_TOP];
+                                if (maxY < rstPageCells[cacheObj.vCache[pStr][preCIdx]][JV.PROP_AREA][JV.PROP_BOTTOM]) maxY = rstPageCells[cacheObj.vCache[pStr][preCIdx]][JV.PROP_AREA][JV.PROP_BOTTOM];
+                            }
+                            preColMergePosArr.push([[minY, maxY]]);
+                        }
+                        let dtlColMergePosArr = [];
+                        preColMergePosArr.push(dtlColMergePosArr);
                         for (let cIdx = 1; cIdx < cacheObj.vCache[pStr].length; cIdx++) {
                             if (preCell.Value === "") {
+                                dtlColMergePosArr.push([preCell[JV.PROP_AREA][JV.PROP_TOP], preCell[JV.PROP_AREA][JV.PROP_BOTTOM]]);
                                 preCell = rstPageCells[cacheObj.vCache[pStr][cIdx]];
                             } else {
                                 if (preCell.Value === rstPageCells[cacheObj.vCache[pStr][cIdx]].Value) {
+                                    let bkBottom = preCell[JV.PROP_AREA][JV.PROP_BOTTOM];
                                     preCell[JV.PROP_AREA][JV.PROP_BOTTOM] = rstPageCells[cacheObj.vCache[pStr][cIdx]][JV.PROP_AREA][JV.PROP_BOTTOM];
-                                    removeCellIds.push(cacheObj.vCache[pStr][cIdx]);
+                                    if (private_chk_in_pre_merge(vidx, preCell)) {
+                                        removeCellIds.push(cacheObj.vCache[pStr][cIdx]);
+                                        if (cIdx === cacheObj.vCache[pStr].length - 1) {
+                                            dtlColMergePosArr.push([preCell[JV.PROP_AREA][JV.PROP_TOP], preCell[JV.PROP_AREA][JV.PROP_BOTTOM]]);
+                                        }
+                                    } else {
+                                        preCell[JV.PROP_AREA][JV.PROP_BOTTOM] = bkBottom;
+                                        dtlColMergePosArr.push([preCell[JV.PROP_AREA][JV.PROP_TOP], preCell[JV.PROP_AREA][JV.PROP_BOTTOM]]);
+                                        preCell = rstPageCells[cacheObj.vCache[pStr][cIdx]];
+                                    }
                                 } else {
+                                    dtlColMergePosArr.push([preCell[JV.PROP_AREA][JV.PROP_TOP], preCell[JV.PROP_AREA][JV.PROP_BOTTOM]]);
                                     preCell = rstPageCells[cacheObj.vCache[pStr][cIdx]];
                                 }
                             }
@@ -892,8 +933,10 @@ JpcFlowTabSrv.prototype.createNew = function(){
                 }
             }
             if (removeCellIds.length > 0) {
-                //这次真的要排序了
-                removeCellIds.sort(); //默认方式即可
+                //排序,保证一定的顺序,不能用默认的方式(默认方式是针对字符串的简单排序)
+                removeCellIds.sort(function (idx1, idx2) {
+                    return idx1 - idx2;
+                });
                 for (let idx = removeCellIds.length - 1; idx >= 0; idx--) {
                     rstPageCells.splice(removeCellIds[idx], 1);
                 }
@@ -1303,16 +1346,18 @@ function setupControl(mergeCell, controls) {
         orgCtrl = controls[mergeCell[JV.PROP_CONTROL]];
         mergeCell[JV.PROP_CONTROL] = {
             "Shrink": "T",
+            "ShrinkFirst": orgCtrl.ShrinkFirst,
+            "CloseOutput": orgCtrl.CloseOutput,
             "ShowZero": orgCtrl.ShowZero,
             "Horizon": orgCtrl.Horizon,
             "Vertical": orgCtrl.Vertical,
-            "Wrap": "T",
+            "Wrap": (mergeCell[JV.PROP_IS_AUTO_HEIGHT])?'T':'F',
             "VerticalForExcel": "center"
         };
     } else {
         mergeCell[JV.PROP_CONTROL].Shrink = "T";
         mergeCell[JV.PROP_CONTROL].Vertical = "top";
-        mergeCell[JV.PROP_CONTROL].Wrap = "T";
+        mergeCell[JV.PROP_CONTROL].Wrap = (mergeCell[JV.PROP_IS_AUTO_HEIGHT])?'T':'F',
         mergeCell[JV.PROP_CONTROL].VerticalForExcel = "center";
         orgCtrl = mergeCell[JV.PROP_CONTROL];
     }

+ 4 - 0
modules/reports/rpt_component/jpc_rte.js

@@ -109,10 +109,14 @@ let JE = {
         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;
             }
         }
     }

+ 2 - 2
modules/reports/util/rpt_excel_util.js

@@ -208,7 +208,7 @@ function writeStyles(stylesObj){
         let textRotation = 0;
         let newHorizontal = excelStyle[JV.CONTROL_PROPS[2]];
         let newVertical = excelStyle[JV.CONTROL_PROPS[3]];
-        if (excelStyle[JV.CONTROL_PROPS[5]]) {
+        if (excelStyle[JV.CONTROL_PROPS[5]] && !strUtil.convertStrToBoolean(excelStyle[JV.CONTROL_PROPS[JV.CONTROL_PROP_IDX_CLOSE_OUTPUT]])) {
             newVertical = excelStyle[JV.CONTROL_PROPS[5]];
         }
         if (parseInt(excelStyle.fontAngle) !== 0) {
@@ -255,7 +255,7 @@ function writeStyles(stylesObj){
         if (strUtil.convertStrToBoolean(excelStyle[JV.CONTROL_PROPS[0]])) {
             alignStr += ' shrinkToFit="1"';
         }
-        if (strUtil.convertStrToBoolean(excelStyle[JV.CONTROL_PROPS[4]])) {
+        if (strUtil.convertStrToBoolean(excelStyle[JV.CONTROL_PROPS[4]]) && !strUtil.convertStrToBoolean(excelStyle[JV.CONTROL_PROPS[6]])) {
             alignStr += ' wrapText="1"';
         }
         if (textRotation !== 0) {

+ 23 - 4
modules/reports/util/rpt_pdf_util.js

@@ -84,7 +84,7 @@ function export_pdf_file (pageData, paperSize, fName, callback) {
 
     function private_drawCell(cell, fonts, styles, controls, mergedBand) {
         doc.save();
-        //doc.translate(0.5,0.5);
+        // doc.translate(0.5,0.5); //跟H5的canvas不同,不需要这样切换
         let style = styles[cell[JV.PROP_STYLE]];
         if (style) {
             let isNeedMergeBand = private_chkIfInMergedBand(mergedBand, cell);
@@ -133,9 +133,28 @@ function export_pdf_file (pageData, paperSize, fName, callback) {
             }
             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 * (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);
             }
         }
@@ -227,7 +246,7 @@ function export_pdf_file (pageData, paperSize, fName, callback) {
             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)?1:lines;
+                lines = (lines === 0 || (control.Shrink === 'T' && control.ShrinkFirst === 'T'))?1:lines;
                 let actLines = private_splitString(val, w, doc);
                 if (actLines.length > lines && dftFontHeight >= 6) {
                     dftFontHeight--;

+ 52 - 12
modules/reports/util/rpt_yanghu_data_util.js

@@ -240,7 +240,7 @@ class Rpt_Data_Extractor {
         let tpl = this.rptTpl;
         this.COMMON.initialize(tpl, rawDataObj);
         $PROJECT.COMMON = this.COMMON;
-        if (rawDataObj.hasOwnProperty(`prj`)) {
+        if (rawDataObj.hasOwnProperty(`prj`) && rawDataObj.hasOwnProperty(`prjData`)) {
             setupMainFunc($PROJECT, `MAIN`, rawDataObj.prj._doc);
             // $PROJECT.MAIN["myOwnRawDataObj"] = rawDataObj.prj._doc;
             // $PROJECT.MAIN.getProperty = ext_mainGetPropety;
@@ -270,7 +270,8 @@ class Rpt_Data_Extractor {
             }
         }
         //还有汇总的...
-        if (rawDataObj.hasOwnProperty(`Construct`) || rawDataObj.hasOwnProperty(`ConstructDetail`) || rawDataObj.hasOwnProperty(`Segment`) || rawDataObj.hasOwnProperty(`SegmentDetail`)) {
+        if (rawDataObj.hasOwnProperty(`Construct`) || rawDataObj.hasOwnProperty(`ConstructDetail`) || rawDataObj.hasOwnProperty(`Segment`) || rawDataObj.hasOwnProperty(`SegmentDetail`)
+            || rawDataObj.hasOwnProperty(`SummaryAudit`) || rawDataObj.hasOwnProperty(`SummaryAuditDetail`)) {
             $PROJECT.SUMMARY = {};
             if (rawDataObj.Construct) {
                 setupMainFunc($PROJECT.SUMMARY, `Construct`, rawDataObj.Construct);
@@ -284,6 +285,12 @@ class Rpt_Data_Extractor {
             if (rawDataObj.SegmentDetail) {
                 setupFunc($PROJECT.SUMMARY, `SegmentDetail`, {"data": rawDataObj.SegmentDetail});
             }
+            if (rawDataObj.SummaryAudit) {
+                setupMainFunc($PROJECT.SUMMARY, `SummaryAudit`, rawDataObj.SummaryAudit);
+            }
+            if (rawDataObj.SummaryAuditDetail) {
+                setupFunc($PROJECT.SUMMARY, `SummaryAuditDetail`, {"data": rawDataObj.SummaryAuditDetail});
+            }
         }
         //综合费率
         let feeRate = getModuleDataByKey(rawDataObj.prjData, "feeRate");
@@ -330,7 +337,7 @@ class Rpt_Data_Extractor {
             }
         }
         //一些计算(不保存数据,需要动态计算的)
-        if (rawDataObj.hasOwnProperty(`prj`)) {
+        if (rawDataObj.hasOwnProperty(`prj`) && rawDataObj.hasOwnProperty(`prjData`)) {
             let calcOptions = rawDataObj.prj._doc.property.calcOptions;
             let decimalObj = rawDataObj.prj._doc.property.decimal;
             let labourCoeDatas =  getModuleDataByKey(rawDataObj.prjData, "labour_coe");
@@ -357,9 +364,11 @@ class Rpt_Data_Extractor {
         rptDataObj[JV.DATA_DETAIL_DATA] = [];
         rptDataObj[JV.DATA_MASTER_DATA_EX] = [];
         rptDataObj[JV.DATA_DETAIL_DATA_EX] = [];
-        rptDataObj.DecimalObj = {};
-        rptDataObj.DecimalObj.prjDecimal = $PROJECT.MAIN["myOwnRawDataObj"].decimal; //为函数 P_REF() 准备数据
-        rptDataObj.DecimalObj.unitDecimal = getUnitDecimal($PROJECT.MAIN["myOwnRawDataObj"].billsQuantityDecimal); //为函数 U_REF() 准备数据
+        if (rawDataObj.hasOwnProperty(`prj`) && rawDataObj.hasOwnProperty(`prjData`)) {
+            rptDataObj.DecimalObj = {};
+            rptDataObj.DecimalObj.prjDecimal = $PROJECT.MAIN["myOwnRawDataObj"].decimal; //为函数 P_REF() 准备数据
+            rptDataObj.DecimalObj.unitDecimal = getUnitDecimal($PROJECT.MAIN["myOwnRawDataObj"].billsQuantityDecimal); //为函数 U_REF() 准备数据
+        }
         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]);
@@ -402,10 +411,12 @@ function getUnitDecimal(unitDecimalArr) {
 
 function getModuleDataByKey(prjData, key) {
     let rst = null;
-    for (let item of prjData) {
-        if (item.moduleName === key) {
-            rst = item;
-            break;
+    if (prjData) {
+        for (let item of prjData) {
+            if (item.moduleName === key) {
+                rst = item;
+                break;
+            }
         }
     }
     return rst;
@@ -1230,6 +1241,7 @@ function setupFunc(obj, prop, ownRawObj) {
     obj[prop]["myOwnRawDataObj"] = ownRawObj;
     obj[prop]["myOwnOrgRawDataObj"] = ownRawObj.data;
     obj[prop].getProperty = ext_getProperty;
+    obj[prop].getGljFreightProperty = ext_getGljFreightProperty;
     obj[prop].getPropertyByRefId = ext_getPropertyByRefId;
     obj[prop].getFee = ext_getFee;
     obj[prop].getPropertyByForeignId = ext_getPropertyByForeignId;
@@ -1441,6 +1453,30 @@ function ext_getRatioDataPriceMapProperty(propKey) {
     return rst;
 }
 
+function ext_getAdHocGljProperty(adhocDataKey, propKey) {
+    let rst = [], parentObj = this;
+    let dtObj = parentObj["myOwnRawDataObj"];
+    if (propKey && dtObj) {
+        // for (let dItem of getActDataArr(dtObj)) {
+        //     let doc = (dItem._doc === null || dItem._doc === undefined)?dItem:dItem._doc;
+        //     pri_push_property(propKey, doc, rst);
+        // }
+    }
+    return rst;
+}
+
+function ext_getGljFreightProperty(propKey) {
+    let rst = [], parentObj = this;
+    let dtObj = parentObj["myOwnRawDataObj"];
+    if (propKey && dtObj) {
+        for (let dItem of getActDataArr(dtObj, "freightList")) {
+            let doc = (dItem._doc === null || dItem._doc === undefined)?dItem:dItem._doc;
+            pri_push_property(propKey, doc, rst);
+        }
+    }
+    return rst;
+}
+
 function ext_getProperty(propKey) {
     let rst = [], parentObj = this;
     let dtObj = parentObj["myOwnRawDataObj"];
@@ -1787,12 +1823,16 @@ function ext_getPropertyByForeignId(foreignIdVal, adHocIdKey, propKey, dftValIfN
     return rst;
 }
 
-function getActDataArr(dtObj) {
+function getActDataArr(dtObj, adhocKey) {
     let rst = null;
     if (dtObj) {
         rst = dtObj.data;
         if (dtObj.moduleName === "projectGLJ") {
-            rst = dtObj.data.gljList;
+            if (adhocKey) {
+                rst = dtObj.data[adhocKey];
+            } else {
+                rst = dtObj.data.gljList;
+            }
         }
         if (dtObj.moduleName === "calc_program") {
             rst = dtObj.data.templates;

+ 23 - 0
modules/users/controllers/user_controller.js

@@ -11,6 +11,7 @@ import Config from "../../../config/config";
 import CompilationModel from "../models/compilation_model";
 let config = require("../../../config/config.js");
 let _ = require("lodash");
+let online_facade = require('../facade/online_facade')
 
 class UserController extends BaseController {
 
@@ -66,6 +67,8 @@ class UserController extends BaseController {
             // console.log(condition);
             // 获取用户列表
             userList = await userModel.getList(condition, page, Config.pageSize);
+
+            await online_facade.setOnlineTimes(userList,condition);
             // console.log(userList)
         } catch (error) {
             console.log(error);
@@ -225,6 +228,26 @@ class UserController extends BaseController {
         response.json(responseData);
     }
 
+    async getOnlineInfo(request, response){
+        let filter = JSON.parse(request.body.data);
+        let responseData = {
+            error: 0,
+            msg: '',
+            data: null
+        };
+        try{
+            let info = await online_facade.getOnlineInfo(filter);
+            responseData.data = info;
+        } catch (error) {
+            console.log(error);
+            responseData.error = error.code;
+            responseData.msg = error.err;
+        }
+        response.json(responseData);
+
+
+
+    }
 
     async updateUser(request, response) {
         let data = JSON.parse(request.body.data);

+ 60 - 0
modules/users/facade/online_facade.js

@@ -0,0 +1,60 @@
+/**
+ * Created by zhang on 2019/4/16.
+ */
+
+module.exports = {
+    setOnlineTimes:setOnlineTimes,
+    getOnlineInfo:getOnlineInfo
+};
+
+let mongoose = require("mongoose");
+let logs_model = mongoose.model("online_logs");
+
+async function getOnlineInfo(filter) {
+    let datas = [];
+    let logs = await logs_model.find(filter);
+    let less = 0;
+    for(let l of logs){
+        let d = getTimeString(l.online_times);
+        let online_times = d.s;
+        less += d.less;
+        if(online_times!=""){
+            datas.push({dateString:l.dateString,dateTime:l.online_times,online_times:online_times})
+        }
+    }
+    if(datas.length>1){//大于2个才把不够1分钟的累积到最后一条记录
+        let last =  datas[datas.length - 1];
+        last.online_times = getTimeString(last.dateTime + less).s;
+    }
+
+    return datas;
+}
+
+
+async function setOnlineTimes(userList,condition){
+    for(let u of userList){
+        let filter = {'userID':u._id.toString()};
+        if(u.latest_used) filter["compilationID"] = u.latest_used;
+        if(condition.latest_login && condition.latest_login == ""){
+            let startTime = condition.latest_login['$gte']; //- 24*60*60*1000 //往前推一天  {'$gte': startTime, '$lt': endTime}latest_login
+            filter['dateTime'] = {'$gte': startTime, '$lt':  condition.latest_login['$lt']}
+        }
+        let result =  await logs_model.aggregate([
+            {$match: filter},
+            {$group: {_id: "$userID", total: {$sum: "$online_times"}}}
+        ]);
+        u._doc.filter = JSON.stringify(filter);
+        if(result.length > 0)  u._doc.online_times = getTimeString(result[0].total).s;
+    }
+}
+
+
+function getTimeString(times) {
+    let s = "",perHour = 1000 * 60 * 60,perMin = 1000 * 60;
+    let hour = parseInt(times/perHour);
+    let min = parseInt((times % perHour)/perMin);
+    let less =  (times % perHour)%perMin;//不够一分钟的时间
+    if(hour > 0) s = s+`${hour}小时`;
+    if(min > 0)  s= s+`${min}分钟`;
+    return {s:s,less:less}
+}

+ 1 - 1
modules/users/models/manager_model.js

@@ -203,7 +203,7 @@ class ManagerModel extends BaseModel {
             password: password,
             time: postTime,
             token: encryptToken,
-            app: 'yanghu'
+            app: 'scConstruct'
         };
 
         let postOption = {

+ 1 - 0
modules/users/routes/user_route.js

@@ -17,6 +17,7 @@ module.exports =function (app) {
     router.get('/testUser', userController.auth, userController.init, userController.testUsers);
     router.get('/search', userController.auth, userController.init, userController.search);
     router.post('/findByID', userController.auth, userController.init, userController.findByID);
+    router.post('/getOnlineInfo', userController.auth, userController.init, userController.getOnlineInfo);
     router.post('/getUserList', userController.auth, userController.init, userController.getUserList);
     router.post('/updateUser', userController.auth, userController.init, userController.updateUser);
     router.post('/deleteUser', userController.auth, userController.init, userController.deleteUser);

+ 10 - 1
public/web/rpt_value_define.js

@@ -121,6 +121,7 @@ const JV = {
     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",
@@ -206,13 +207,15 @@ const JV = {
 
     PAGE_STATUS: ["EveryPage","FirstPage", "LastPage", "SegmentStart", "SegmentEnd", "Group", "CrossRowEnd", "CrossColEnd"],
 
-    CONTROL_PROPS: ["Shrink", "ShowZero", "Horizon", "Vertical", "Wrap", "VerticalForExcel"],
+    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",
@@ -247,6 +250,12 @@ const JV = {
         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,

+ 17 - 8
web/maintain/report/html/rpt_tpl_dtl_field_loc.html

@@ -51,11 +51,11 @@
                 </div>
             </div>
             <div class="row" id="element_font">
-                <div class="form-group col-md-3">
+                <div class="form-group col-md-2">
                     <label>字体选择</label>
                     <select class="form-control" id="elementFonts" onchange="rpt_tpl_cfg_helper.fontChange(this)" disabled></select>
                 </div>
-                <div class="form-group col-md-3">
+                <div class="form-group col-md-2">
                     <label>字体名称</label>
                     <input class="form-control" id="eleFontName" value="" onchange="rpt_tpl_cfg_helper.fontNameChange(this)" disabled>
                 </div>
@@ -82,7 +82,7 @@
                 </div>
             </div>
             <div class="row" id="element_border">
-                <div class="form-group col-md-3">
+                <div class="form-group col-md-2">
                     <label>边框样式</label>
                     <select class="form-control" id="elementBorders" onchange="rpt_tpl_cfg_helper.borderChange(this)"></select>
                 </div>
@@ -104,7 +104,7 @@
                 </div>
             </div>
             <div class="row" id="element_control">
-                <div class="form-group col-md-3">
+                <div class="form-group col-md-2">
                     <label>控制选择</label>
                     <select class="form-control" id="elementControls" onchange="rpt_tpl_cfg_helper.controlChange(this)"></select>
                 </div>
@@ -131,6 +131,15 @@
                             <input type="checkbox" class="form-check-input" id="eleAutoWrap" onchange="" disabled>
                             自动折行
                         </label>
+                        <label class="form-check-label"> </label>
+                        <label class="form-check-label">
+                            <input type="checkbox" class="form-check-input" id="eleIsShrinkFirst" onchange="" disabled>
+                            缩放优先
+                        </label>
+                        <label class="form-check-label">
+                            <input type="checkbox" class="form-check-input" id="eleCloseOutput" onchange="" disabled>
+                            紧密输出
+                        </label>
                     </div>
                 </div>
             </div>
@@ -178,19 +187,19 @@
                 </div>
             </div>
             <div class="row" id="element_pre_suff">
-                <div class="input-group col-3">
+                <div class="input-group col-2">
                     <div class="input-group-addon">前缀</div>
                     <input class="form-control" id="elePrefix" value="" onkeyup="rpt_tpl_cfg_helper.changePreSuff(this, 'Prefix')">
                 </div>
-                <div class="input-group col-3">
+                <div class="input-group col-2">
                     <div class="input-group-addon">后缀</div>
                     <input class="form-control" id="eleSuffix" value="" onkeyup="rpt_tpl_cfg_helper.changePreSuff(this, 'Suffix')">
                 </div>
-                <div class="input-group col-3">
+                <div class="input-group col-2">
                     <div class="input-group-addon">格式</div>
                     <input class="form-control" id="eleFormat" value="" onkeyup="rpt_tpl_cfg_helper.changeFormat(this)">
                 </div>
-                <div class="input-group col-3">
+                <div class="input-group col-2">
                     <div class="input-group-addon">默认值</div>
                     <input class="form-control" id="eleDftValue" value="" onkeyup="rpt_tpl_cfg_helper.changeDftValue(this)">
                 </div>

+ 4 - 0
web/maintain/report/html/rpt_tpl_dtl_info.html

@@ -58,6 +58,10 @@
                     <div class="input-group-addon">工程类型</div>
                     <select class="form-control input-sm" id="element_prjFlags_select" onchange="zTreeOprObj.onChangeFlag('valuationType', this)"><option value ="NA">N/A</option><option value ="bill">预算</option><option value ="ration">工程量清单</option></select>
                 </div>
+                <div class="input-group col-3">
+                    <div class="input-group-addon">报表汇总类型</div>
+                    <select class="form-control input-sm" id="element_rptFlags_select" onchange="zTreeOprObj.onChangeFlag('reportType', this)"><option value ="NA">N/A</option><option value ="billSummary">工程清单汇总</option><option value ="gljSummary">工程工料机汇总</option></select>
+                </div>
             </div>
         </div>
     </div>

+ 34 - 8
web/maintain/report/js/jpc_output.js

@@ -139,9 +139,12 @@ let JpcCanvasOutput = {
                 }
             };
             inner_setupControl(area, dftFontHeight, output);
-            let w = area[JV.IDX_RIGHT] - JV.OUTPUT_OFFSET[JV.OFFSET_IDX_RIGHT] - area[JV.IDX_LEFT] - JV.OUTPUT_OFFSET[JV.OFFSET_IDX_RIGHT];
+            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) {
-                w = area[JV.IDX_BOTTOM] - JV.OUTPUT_OFFSET[JV.OFFSET_IDX_BOTTOM] - area[JV.IDX_TOP] - JV.OUTPUT_OFFSET[JV.OFFSET_IDX_TOP];
+                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() {
@@ -194,13 +197,14 @@ let JpcCanvasOutput = {
             } else if (font.FontAngle === JV.ANTI_VERTICAL_ANGLE) {
                 ctx.rotate(-Math.PI/2);
             }
-            if (w >= ctx.measureText(val).width || (control && control.Shrink !== 'T') ) {
+            if (validAreaTxtWidth >= ctx.measureText(val).width ||
+                (control && control.Shrink !== 'T' && validTxtLines < private_splitString(val, validAreaTxtWidth, ctx)) ) {
                 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)?1:lines;
-                    let actLines = private_splitString(val, w, ctx);
+                    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];
@@ -259,9 +263,28 @@ let JpcCanvasOutput = {
                 }
                 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 * (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);
                 }
             }
@@ -400,7 +423,10 @@ let JpcCanvasOutput = {
         ctx.fillRect(10 + me.offsetX,size[1] + me.offsetY,size[0],10);
     },
     getReportSizeInPixel: function(rptTpl, resolution) {
-        let rst = rptTpl[JV.NODE_PAGE_INFO][JV.NODE_PAGE_SIZE].slice(0);
+        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;

+ 18 - 1
web/maintain/report/js/jpc_output_value_define.js

@@ -5,6 +5,7 @@
 let JV = {
     NODE_MAIN_INFO: "主信息",
     NODE_PAGE_INFO: "打印页面_信息",
+    NODE_MAIN_INFO_RPT_NAME: "报表名称",
     NODE_PAGE_SIZE: "纸张宽高",
     NODE_MARGINS: "页边距",
 
@@ -32,12 +33,28 @@ let JV = {
     IDX_RIGHT: 2,
     IDX_BOTTOM: 3,
 
-    CONTROL_PROPS: ["Shrink", "ShowZero", "Horizon", "Vertical", "Wrap"],
+    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,

+ 5 - 3
web/maintain/report/js/rpt_tpl_cfg_helper.js

@@ -343,9 +343,11 @@ let rpt_tpl_cfg_helper = {
 
             $("#elementAlignmentHorizon").get(0).selectedIndex = JV.OUTPUT_ALIGN.H.indexOf(ctrl[JV.CONTROL_PROPS[2]]);
             $("#elementAlignmentVertical").get(0).selectedIndex = JV.OUTPUT_ALIGN.V.indexOf(ctrl[JV.CONTROL_PROPS[3]]);
-            $("#eleShrink").get(0).checked = stringUtil.convertStrToBoolean(ctrl[JV.CONTROL_PROPS[0]]);
-            $("#eleShowZero").get(0).checked = stringUtil.convertStrToBoolean(ctrl[JV.CONTROL_PROPS[1]]);
-            $("#eleAutoWrap").get(0).checked = stringUtil.convertStrToBoolean(ctrl[JV.CONTROL_PROPS[4]]);
+            $("#eleShrink").get(0).checked = stringUtil.convertStrToBoolean(ctrl[JV.CONTROL_PROPS[JV.CONTROL_PROP_IDX_SHRINK]]);
+            $("#eleShowZero").get(0).checked = stringUtil.convertStrToBoolean(ctrl[JV.CONTROL_PROPS[JV.CONTROL_PROP_IDX_SHOW_ZERO]]);
+            $("#eleAutoWrap").get(0).checked = stringUtil.convertStrToBoolean(ctrl[JV.CONTROL_PROPS[JV.CONTROL_PROP_IDX_WRAP]]);
+            $("#eleIsShrinkFirst").get(0).checked = stringUtil.convertStrToBoolean(ctrl[JV.CONTROL_PROPS[JV.CONTROL_PROP_IDX_SHRINK_FIRST]]);
+            $("#eleCloseOutput").get(0).checked = stringUtil.convertStrToBoolean(ctrl[JV.CONTROL_PROPS[JV.CONTROL_PROP_IDX_CLOSE_OUTPUT]]);
         }
     },
     changeAlignment: function(dom, type) {

+ 28 - 2
web/maintain/report/js/rpt_tpl_data_map.js

@@ -409,6 +409,7 @@ let dataInfoMapTreeOprObj = {
         if (source[JV.PROP_IS_SERIAL]) destination[JV.PROP_IS_SERIAL] = true;
         if (source[JV.TAB_FIELD_PROP_SORT]) destination[JV.TAB_FIELD_PROP_SORT] = source[JV.TAB_FIELD_PROP_SORT];
         if (source[JV.PROP_COMBINE_TYPE]) destination[JV.PROP_COMBINE_TYPE] = source[JV.PROP_COMBINE_TYPE];
+        if (source.hasOwnProperty(JV.PROP_IS_MERGE)) destination[JV.PROP_IS_MERGE] = source[JV.PROP_IS_MERGE];
         if (source[JV.PROP_AREA]) {
             destination[JV.PROP_AREA] = {};
             me.private_copy_area(source[JV.PROP_AREA], destination[JV.PROP_AREA]);
@@ -642,6 +643,10 @@ let dataInfoMapTreeOprObj = {
         let rst = (treeNode.getParentNode() !== null && treeNode.getParentNode().Name === `流水式表_数据`);
         return rst;
     },
+    checkIfCanAddColFieldCombine: function (treeNode) {
+        let rst = (treeNode.getParentNode() !== null && treeNode.getParentNode().getParentNode() !== null && treeNode.getParentNode().getParentNode().Name === `交叉列`);
+        return rst;
+    },
     bandSelectChange: function(dom, treeNodeId) {
         let me = dataInfoMapTreeOprObj, node = me.treeObj.getNodeByTId(treeNodeId);
         if (dom.selectedIndex > 0) {
@@ -658,6 +663,14 @@ let dataInfoMapTreeOprObj = {
             node[JV.PROP_COMBINE_TYPE] = null;
         }
     },
+    combineColSelectChange: function(dom, treeNodeId) {
+        let me = dataInfoMapTreeOprObj, node = me.treeObj.getNodeByTId(treeNodeId);
+        if (dom.selectedIndex > 0) {
+            node[JV.PROP_IS_MERGE] = true;
+        } else {
+            node[JV.PROP_IS_MERGE] = false;
+        }
+    },
     addDiyDom: function (treeId, treeNode) {
         let me = dataInfoMapTreeOprObj, aObj = $("#" + treeNode.tId + IDMark_A);
         //加band selection
@@ -666,8 +679,8 @@ let dataInfoMapTreeOprObj = {
             let addBandStr = "<select class='selDemo' id='diySelect_" +treeNode.tId+ "' onchange='dataInfoMapTreeOprObj.bandSelectChange(this, \"" + treeNode.tId + "\")'></select>";
             aObj.after(addBandStr);
         } else if (me.checkIfCanAddFieldCombine(treeNode)) {
-            let addBandStr = "<select class='selDemo' id='diyCombineSelect_" +treeNode.tId+ "' onchange='dataInfoMapTreeOprObj.combineSelectChange(this, \"" + treeNode.tId + "\")'><option value='none'>请选择</option><option value='vertical'>纵向合并</option><option value='horizon'>横向合并</option></select>";
-            aObj.after(addBandStr);
+            let addCombineFieldStr = "<select class='selDemo' id='diyCombineSelect_" +treeNode.tId+ "' onchange='dataInfoMapTreeOprObj.combineSelectChange(this, \"" + treeNode.tId + "\")'><option value='none'>请选择</option><option value='vertical'>纵向合并</option><option value='horizon'>横向合并</option></select>";
+            aObj.after(addCombineFieldStr);
             let selectCombineObj = $("#diyCombineSelect_" +treeNode.tId);
             if (treeNode.hasOwnProperty(JV.PROP_COMBINE_TYPE)) {
                 if (treeNode[JV.PROP_COMBINE_TYPE] === 'vertical') {
@@ -680,6 +693,19 @@ let dataInfoMapTreeOprObj = {
             } else {
                 selectCombineObj[0].selectedIndex = 0;
             }
+        } else if (me.checkIfCanAddColFieldCombine(treeNode)) {
+            let addCombineColFieldStr = "<select class='selDemo' id='diyCombineColFieldSelect_" +treeNode.tId+ "' onchange='dataInfoMapTreeOprObj.combineColSelectChange(this, \"" + treeNode.tId + "\")'><option>请选择</option><option>数据合并</option>";
+            aObj.after(addCombineColFieldStr);
+            let selectCombineObj = $("#diyCombineColFieldSelect_" +treeNode.tId);
+            if (treeNode.hasOwnProperty(JV.PROP_IS_MERGE)) {
+                if (treeNode[JV.PROP_IS_MERGE]) {
+                    selectCombineObj[0].selectedIndex = 1;
+                } else {
+                    selectCombineObj[0].selectedIndex = 0;
+                }
+            } else {
+                selectCombineObj[0].selectedIndex = 0;
+            }
         }
     },
     addHoverDom: function(treeId, treeNode) {

+ 7 - 0
web/maintain/report/js/rpt_tpl_main.js

@@ -887,6 +887,13 @@ let zTreeOprObj = {
                             } else {
                                 $("#element_prjFlags_select")[0].selectedIndex = 0;
                             }
+                            if (me.currentNode.flags.hasOwnProperty('reportType')) {
+                                let rType = me.currentNode.flags['reportType'];
+                                if (rType === 'billSummary') $("#element_rptFlags_select")[0].selectedIndex = 1
+                                else $("#element_rptFlags_select")[0].selectedIndex = 0;
+                            } else {
+                                $("#element_rptFlags_select")[0].selectedIndex = 0;
+                            }
                         } else {
                             $("#element_flags_select")[0].selectedIndex = 0;
                             $("#element_prjFlags_select")[0].selectedIndex = 0;

+ 2 - 0
web/maintain/report/js/rpt_tpl_pre_handle.js

@@ -219,6 +219,7 @@ const exposed_ration_glj_properties = [
     ,{Name: "定额人材机_所属工程ID", Title: "", Key: "projectID", Order: "ascend"}
     ,{Name: "定额人材机_代码", Title: "", Key: "code", Order: "ascend"}
     ,{Name: "定额人材机_类型", Title: "", Key: "type", Order: "ascend", individualType: fixed_glj_types}
+    ,{Name: "定额人材机_数量", Title: "", Key: "quantity", Order: "ascend"}
     ,{Name: "定额人材机_是否暂估", Title: "", Key: "ref_join(projectGLJID,projectGLJ,id).is_evaluate", Order: "ascend", isBoolean: true, booleanOptions: [1,0]}
     ,{Name: "定额人材机_供货方式", Title: "", Key: "ref_join(projectGLJID,projectGLJ,id).supply", Order: "ascend", individualType: fixed_supply_types}
     ,{Name: "定额人材机_三材类别", Title: "", Key: "ref_join(projectGLJID,projectGLJ,id).materialType", Order: "ascend", individualType: fixed_material_types}
@@ -230,6 +231,7 @@ const exposed_prj_glj_properties = [
     ,{Name: "项目人材机_类型", Title: "", Key: "type", Order: "ascend", individualType: fixed_glj_types}
     ,{Name: "项目人材机_数量", Title: "", Key: "quantity", Order: "ascend"}
     ,{Name: "项目人材机_是否暂估", Title: "", Key: "is_evaluate", Order: "ascend", isBoolean: true, booleanOptions: [1,0]}
+    ,{Name: "项目人材机_是否材料计算", Title: "", Key: "unit_price.calcMaterial", Order: "ascend", isBoolean: true, booleanOptions: [1,0]}
     ,{Name: "项目人材机_供货方式", Title: "", Key: "supply", Order: "ascend", individualType: fixed_supply_types}
     ,{Name: "项目人材机_三材类别", Title: "", Key: "materialType", Order: "ascend", individualType: fixed_material_types}
 ];

+ 16 - 0
web/users/js/user.js

@@ -43,6 +43,22 @@ $(document).ready(function() {
 });
 let cacheUser = null;
 
+
+async function getOnlineInfo(filter) {
+    try {
+        let htmlString = `<tbody><tr><th>日期/时间</th><th>在线时长</th></tr>`;
+        let info = await ajaxPost("/user/getOnlineInfo",JSON.parse(filter));
+        for(let i of info){
+            htmlString += `<tr><td>${i.dateString}</td><td>${i.online_times}</td></tr>`
+        }
+        htmlString +="<tbody>";
+        $("#time-detail-table").html(htmlString);
+    }catch (e){
+        console.log(e)
+    }
+
+}
+
 async function getUserInfo(ID) {
     let user = await ajaxPost("/user/findByID",{ID:ID});
     let infoString = `<tr><th>注册时间</th><td>${user.create_time}</td><th>最近登录</th><td>${user.last_login}</td></tr>

+ 2 - 2
web/users/views/layout/layout.html

@@ -2,7 +2,7 @@
 <html lang=zh-cn>
 <head>
     <meta charset=utf-8>
-    <title>建筑造价后台</title>
+    <title>养护云版后台</title>
     <meta name=description content="纵横变更令审批系统">
     <meta http-equiv="X-UA-Compatible" content="IE=edge">
     <meta name="renderer" content="webkit">
@@ -29,7 +29,7 @@
 </head>
 <body>
 <div class="header">
-    <h1 class="logo"><a>建筑造价后台</a></h1>
+    <h1 class="logo"><a>养护云版后台</a></h1>
     <div class="header-box">
         <div class="header-nav"></div>
         <div class="header-user pull-right">

+ 2 - 2
web/users/views/login/index.html

@@ -2,7 +2,7 @@
 <html lang=zh-cn>
 <head>
     <meta charset=utf-8>
-    <title>建筑造价后台</title>
+    <title>养护云版后台</title>
     <meta name=description content=纵横变更令审批系统>
     <meta http-equiv="X-UA-Compatible" content="IE=edge">
     <meta name="renderer" content="webkit">
@@ -19,7 +19,7 @@
 <body class="loginbody">
 <div class="wraplogin">
     <div class="loginTop">
-        <a title="返回首页" href="#">建筑造价后台</a>
+        <a title="返回首页" href="#">养护云版后台</a>
     </div>
     <div class="loginItem  center-block">
         <div class="loginForm">

+ 26 - 0
web/users/views/user/index.html

@@ -98,6 +98,7 @@
                     <th>企业名称</th>
                     <th>企业地区</th>
                     <th>最近使用版本</th>
+                    <th>在线时长(<%= filter.loginMsg === undefined ? '所有' : filter.loginMsg %>)</th>
                     <th width="180">注册时间 / 最近登录</th>
                     <% if (manager.superAdmin == 1) { %>
                     <th>操作</th>
@@ -113,6 +114,7 @@
                     <td><%= user.company %></td>
                     <td><%= model.province[user.province] %></td>
                     <td><%= compilationMap[user.latest_used]?compilationMap[user.latest_used].name:""%></td>
+                    <td><a onclick="getOnlineInfo('<%= user._doc.filter%>')" href="#time-detail" data-toggle="modal" data-target="#time-detail"><%= user._doc.online_times %></a></td>
                     <td><%= moment(user.create_time).format('YYYY-MM-DD HH:mm:ss') %><br><%= user.latest_login?moment(user.latest_login).format('YYYY-MM-DD HH:mm:ss'):"" %></td>
                     <% if (manager.superAdmin == 1) { %>
                     <td><a onclick='deleteUser("<%= user._id.toString()%>")' data-toggle="modal" data-target="#delUser" class="btn btn-link btn-sm" style="padding: 0px">删除</a></td>
@@ -196,6 +198,30 @@
     </div>
 </div>
 
+<!-- 弹窗在线时长-->
+<div class="modal fade" id="time-detail" tabindex="-1" role="dialog">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
+                <h4 class="modal-title" >在线时长详情</h4>
+            </div>
+            <div class="modal-body">
+                <table class="table table-bordered" id = 'time-detail-table'>
+                    <tr><th>日期/时间</th><th>在线时长</th></tr>
+                    <!--<tr><th>日期/时间</th><th>在线时长</th></tr>
+                    <tr><td>04-10</td><td>2小时10分钟</td></tr>
+                    <tr><td>04-09</td><td>1小时7分钟</td></tr>
+                    <tr><td>04-08</td><td>2小时35分钟</td></tr>-->
+                </table>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-default" data-dismiss="modal">关闭</button>
+            </div>
+        </div>
+    </div>
+</div>
+
 <script type="text/javascript">
     let compilationList = JSON.parse('<%- compilationString %>');
     let adminName = '<%- adminName %>';