瀏覽代碼

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

zhangweicheng 7 年之前
父節點
當前提交
ee8cbf94bf
共有 44 個文件被更改,包括 1548 次插入322 次删除
  1. 3 2
      .gitignore
  2. 3 0
      config/gulpConfig.js
  3. 2 0
      modules/main/models/ration.js
  4. 8 10
      modules/pm/models/project_model.js
  5. 0 51
      modules/pm/models/project_property_bills_quantity_decimal.js
  6. 122 0
      modules/pm/models/project_property_template.js
  7. 29 1
      modules/reports/controllers/rpt_controller.js
  8. 1 1
      modules/reports/routes/report_router.js
  9. 39 17
      modules/reports/rpt_component/jpc_bill_tab.js
  10. 19 13
      modules/reports/rpt_component/jpc_data.js
  11. 5 2
      modules/reports/rpt_component/jpc_ex.js
  12. 2 1
      modules/reports/rpt_component/jpc_flow_tab.js
  13. 12 5
      modules/reports/util/rpt_construct_data_util.js
  14. 22 8
      modules/reports/util/rpt_excel_util.js
  15. 2 0
      modules/volume_price/models/volume_price_model.js
  16. 6 0
      modules/volume_price/models/volume_price_schema.js
  17. 1 1
      public/fsUtil.js
  18. 4 1
      public/web/rpt_value_define.js
  19. 1 1
      public/web/tree_sheet/tree_sheet_helper.js
  20. 16 8
      test/tmp_data/test_ration_calc/ration_calc_base.js
  21. 116 0
      test/unit/reports/test_cover_01.js
  22. 6 6
      test/unit/reports/test_tpl_09_1.js
  23. 3 0
      tmp/forDeployment.js
  24. 二進制
      web/building_saas/img/FirstPageSimple.cur
  25. 二進制
      web/building_saas/img/LastPageSimple.cur
  26. 二進制
      web/building_saas/img/NextPageSimple.cur
  27. 二進制
      web/building_saas/img/PreviousPageSimple.cur
  28. 二進制
      web/building_saas/img/baobiao.png
  29. 8 7
      web/building_saas/main/html/main.html
  30. 8 14
      web/building_saas/main/js/controllers/project_controller.js
  31. 95 62
      web/building_saas/main/js/models/calc_program.js
  32. 1 1
      web/building_saas/main/js/models/ration.js
  33. 2 2
      web/building_saas/main/js/models/volume_price.js
  34. 17 1
      web/building_saas/main/js/views/main_tree_col.js
  35. 2 0
      web/building_saas/main/js/views/project_info.js
  36. 401 0
      web/building_saas/main/js/views/project_property_basicInfo.js
  37. 409 0
      web/building_saas/main/js/views/project_property_projFeature.js
  38. 31 51
      web/building_saas/main/js/views/project_view.js
  39. 1 5
      web/building_saas/main/js/views/sub_view.js
  40. 2 1
      web/building_saas/pm/js/pm_main.js
  41. 1 1
      web/building_saas/report/html/rpt_main.html
  42. 85 26
      web/building_saas/report/js/jpc_output.js
  43. 3 1
      web/building_saas/report/js/jpc_output_value_define.js
  44. 60 22
      web/building_saas/report/js/rpt_main.js

+ 3 - 2
.gitignore

@@ -2,5 +2,6 @@ node_modules/
 .git/
 dist/
 .idea/
-tmp/
-test/unit/logs
+tmp/*.xlsx
+test/unit/logs
+*.log

+ 3 - 0
config/gulpConfig.js

@@ -87,7 +87,10 @@ module.exports = {
         'web/building_saas/main/js/views/project_info.js',
         'web/building_saas/main/js/views/project_view.js',
         'web/building_saas/main/js/views/options_view.js',
+        'web/building_saas/main/js/views/project_property_bills_quantity_decimal.js',
         'web/building_saas/main/js/views/project_property_decimal_view.js',
+        'web/building_saas/main/js/views/project_property_basicInfo.js',
+        'web/building_saas/main/js/views/project_property_projFeature.js',
         'web/building_saas/main/js/main_ajax.js',
         'web/building_saas/main/js/main.js',
         'web/building_saas/main/js/controllers/project_controller.js',

+ 2 - 0
modules/main/models/ration.js

@@ -38,6 +38,8 @@ let rationSchema = new Schema({
     caption: String,
     unit: String,
     quantity: String, // Decimal
+    marketUnitFee: String,
+    marketTotalFee: String,
     isFromDetail:{type: Number,default:0},  //1 true 2 false
     programID: Number,
     adjustState: String,

+ 8 - 10
modules/pm/models/project_model.js

@@ -5,7 +5,7 @@ import mongoose from 'mongoose';
 import async_c from 'async';
 import UnitPriceFileModel from "../../glj/models/unit_price_file_model";
 import UnitPriceFiles from '../../glj/models/schemas/unit_price_file';
-import billsQuantityDecimal from './project_property_bills_quantity_decimal';
+import {defaultDecimal, billsQuantityDecimal, basicInformation, projectFeature} from './project_property_template';
 let FeeRateFiles = mongoose.model('fee_rate_file');
 let counter = require("../../../public/counter/counter.js");
 
@@ -27,15 +27,6 @@ let fileType = {
     unitPriceFile: 'UnitPriceFile',
     feeRateFile: 'FeeRateFile'
 };
-//默认的小数位数,用于定义用户可编辑的字段(入库),用户不可编辑的字段在前端defaultDecima._def中定义即可
-const defaultDecimal = {
-    bills: {unitPrice: 2, totalPrice: 2},
-    ration: {quantity: 3, unitPrice: 2, totalPrice: 2},
-    glj: {quantity: 3, unitPrice: 2},
-    feeRate: 2,
-    quantity_detail: 4,
-    process: 6
-};
 
 let ProjectsDAO = function(){};
 
@@ -104,8 +95,15 @@ ProjectsDAO.prototype.updateUserProjects = async function(userId, datas, callbac
                     data.updateData.property.unitPriceFile.id = addResult.id;
                 }
                 if(data.updateData.projType === projectType.tender){
+                    //小数位数
                     data.updateData.property.decimal = defaultDecimal;
+                    //清单工程量精度
                     data.updateData.property.billsQuantityDecimal = billsQuantityDecimal;
+                    //基本信息
+                    basicInformation[0]['items'][1]['value'] = data.updateData.property.engineeringName || '';
+                    data.updateData.property.basicInformation = basicInformation;
+                    //工程特征
+                    data.updateData.property.projectFeature = projectFeature;
                 }
                 newProject = new Projects(data.updateData);
                 // 查找同级是否存在同名数据

+ 0 - 51
modules/pm/models/project_property_bills_quantity_decimal.js

@@ -1,51 +0,0 @@
-/**
- * Created by Zhong on 2017/11/16.
- */
-/*
-* 单位工程清单工程量精度模板
-* */
-const billsQuantityDecimal = [
-    {unit: '其他未列单位', decimal: 2},
-    {unit: 't', decimal: 3},
-    {unit: '吨', decimal: 3},
-    {unit: 'km', decimal: 3},
-    {unit: '千米', decimal: 3},
-    {unit: 'm', decimal: 2},
-    {unit: '米', decimal: 2},
-    {unit: 'm2', decimal: 2},
-    {unit: '平方米', decimal: 2},
-    {unit: 'm3', decimal: 2},
-    {unit: '立方米', decimal: 2},
-    {unit: 'kg', decimal: 2},
-    {unit: '千克', decimal: 2},
-    {unit: '公斤', decimal: 2},
-    {unit: '元', decimal: 2},
-    {unit: '工日', decimal: 0},
-    {unit: '台班', decimal: 0},
-    {unit: '个', decimal: 0},
-    {unit: '件', decimal: 0},
-    {unit: '根', decimal: 0},
-    {unit: '组', decimal: 0},
-    {unit: '宗', decimal: 0},
-    {unit: '付', decimal: 0},
-    {unit: '处', decimal: 0},
-    {unit: '系统', decimal: 0},
-    {unit: '部', decimal: 0},
-    {unit: '台', decimal: 0},
-    {unit: '辆', decimal: 0},
-    {unit: '套', decimal: 0},
-    {unit: '株', decimal: 0},
-    {unit: '从', decimal: 0},
-    {unit: '缸', decimal: 0},
-    {unit: '支', decimal: 0},
-    {unit: '只', decimal: 0},
-    {unit: '块', decimal: 0},
-    {unit: '座', decimal: 0},
-    {unit: '对', decimal: 0},
-    {unit: '份', decimal: 0},
-    {unit: '樘', decimal: 0},
-    {unit: '攒', decimal: 0},
-    {unit: '榀', decimal: 0}
-];
-
-export default billsQuantityDecimal;

+ 122 - 0
modules/pm/models/project_property_template.js

@@ -0,0 +1,122 @@
+/**
+ * Created by Zhong on 2017/11/16.
+ */
+
+//默认的小数位数,用于定义用户可编辑的字段(入库),用户不可编辑的字段在前端defaultDecima._def中定义即可
+const defaultDecimal = {
+    bills: {unitPrice: 2, totalPrice: 2},
+    ration: {quantity: 3, unitPrice: 2, totalPrice: 2},
+    glj: {quantity: 3, unitPrice: 2},
+    feeRate: 2,
+    quantity_detail: 4,
+    process: 6
+};
+
+/*
+* 单位工程清单工程量精度模板
+* */
+const billsQuantityDecimal = [
+    {unit: '其他未列单位', decimal: 2},
+    {unit: 't', decimal: 3},
+    {unit: '吨', decimal: 3},
+    {unit: 'km', decimal: 3},
+    {unit: '千米', decimal: 3},
+    {unit: 'm', decimal: 2},
+    {unit: '米', decimal: 2},
+    {unit: 'm2', decimal: 2},
+    {unit: '平方米', decimal: 2},
+    {unit: 'm3', decimal: 2},
+    {unit: '立方米', decimal: 2},
+    {unit: 'kg', decimal: 2},
+    {unit: '千克', decimal: 2},
+    {unit: '公斤', decimal: 2},
+    {unit: '元', decimal: 2},
+    {unit: '工日', decimal: 0},
+    {unit: '台班', decimal: 0},
+    {unit: '个', decimal: 0},
+    {unit: '件', decimal: 0},
+    {unit: '根', decimal: 0},
+    {unit: '组', decimal: 0},
+    {unit: '宗', decimal: 0},
+    {unit: '付', decimal: 0},
+    {unit: '处', decimal: 0},
+    {unit: '系统', decimal: 0},
+    {unit: '部', decimal: 0},
+    {unit: '台', decimal: 0},
+    {unit: '辆', decimal: 0},
+    {unit: '套', decimal: 0},
+    {unit: '株', decimal: 0},
+    {unit: '从', decimal: 0},
+    {unit: '缸', decimal: 0},
+    {unit: '支', decimal: 0},
+    {unit: '只', decimal: 0},
+    {unit: '块', decimal: 0},
+    {unit: '座', decimal: 0},
+    {unit: '对', decimal: 0},
+    {unit: '份', decimal: 0},
+    {unit: '樘', decimal: 0},
+    {unit: '攒', decimal: 0},
+    {unit: '榀', decimal: 0}
+];
+
+//项目属性基本信息
+const basicInformation = [
+    {dispName: '基本信息', key: 'basicInfo', items: [
+        {dispName: '合同号', key: 'contractNum', value: ''},
+        {dispName: '工程专业', key: 'engineering', value: ''},//只读,用户新建单位工程时选择的值
+        {dispName: '建设地点', key: 'constructionPlace', value: ''},
+        {dispName: '质量标准', key: 'qualityStandard', value: ''},
+        {dispName: '工程类别', key: 'projectCategory', value: ''},
+        {dispName: '建设日期', key: 'constructionDate', value: ''},
+        {dispName: '工程规模', key: 'projectScale', value: ''},
+        {dispName: '建设单位', key: 'constructionUnit', value: ''},
+        {dispName: '设计单位', key: 'designUnit', value: ''},
+        {dispName: '施工单位', key: 'buildingUnit', value: ''},
+        {dispName: '监理单位', key: 'supervisingUnit', value: ''},
+        {dispName: '审核单位', key: 'auditUnit', value: ''},
+        {dispName: '建设单位审核人', key: 'constructionUnitAudit', value: ''},
+        {dispName: '设计单位负责人', key: 'designUnitPrincipal', value: ''},
+        {dispName: '施工单位编制人', key: 'buildingUnitAuthor', value: ''},
+        {dispName: '监理单位审核人', key: 'supervisingUnitAuditor', value: ''},
+        {dispName: '审核单位审核人', key: 'auditUnitAuditor', value: ''},
+        {dispName: '编制时间', key: 'establishDate', value: ''}
+    ]},
+    {dispName: '招标信息', key: 'biddingInfo', items: [
+        {dispName: '招标人', key: 'bidInviter', value: ''},
+        {dispName: '法定代表人或其他授权人', key: 'representative', value: ''},
+        {dispName: '造价工程师', key: 'costEngineer', value: ''}
+    ]},
+    {dispName: '投标信息', key: 'bidInfo', items: [
+        {dispName: '投标人', key: 'bidder', value: ''},
+        {dispName: '法定代表人或其授权人', key: 'representative', value: ''},
+        {dispName: '造价工程师', key: 'costEngineer', value: ''}
+    ]},
+    {dispName: '工程造价咨询信息', key: 'engineeringCostConsultationInfo', items: [
+        {dispName: '工程造价咨询人', key: 'consultant', value: ''},
+        {dispName: '法定代表人或其授权人', key: 'representative', value: ''},
+        {dispName: '造价工程师', key: 'costEngineer', value: ''}
+    ]}
+];
+
+const projectFeature = [
+    {dispName: '工程类型', key: 'projType', value: ''},
+    {dispName: '结构类型', key: 'structureType', value: ''},
+    {dispName: '基础类型', key: 'baseType', value: ''},
+    {dispName: '建筑特征', key: 'buildingFeature', value: ''},
+    {dispName: '建筑面积(m2)', key: 'buildingArea', value: ''},
+    {dispName: '总层数', key: 'totalFloors', value: ''},
+    {dispName: '地下室层数(+/-0.00以下)', key: 'basementFloors', value: ''},
+    {dispName: '建筑层数(+/-0.00以下)', key: 'buildingFloors', value: ''},
+    {dispName: '建筑物总高度(m)', key: 'buildingHeight', value: ''},
+    {dispName: '地下室总高度(m)', key: 'basementHeight', value: ''},
+    {dispName: '首层高度(m)', key: 'firstFloorHeight', value: ''},
+    {dispName: '裙楼高度(m)', key: 'podiumBuildingHeight', value: ''},
+    {dispName: '标准层高度(m)', key: 'standardFloorHeight', value: ''},
+    {dispName: '基础材料及装饰', key: 'baseMaterial', value: ''},
+    {dispName: '楼地面材料及装饰', key: 'flooringMaterial', value: ''},
+    {dispName: '外墙材料及装饰', key: 'exteriorWallMaterial', value: ''},
+    {dispName: '屋面材料及装饰', key: 'roofingMaterial', value: ''},
+    {dispName: '门窗材料及装饰', key: 'doorsWindowsMaterial', value: ''}
+];
+
+export {defaultDecimal, billsQuantityDecimal, basicInformation, projectFeature};

+ 29 - 1
modules/reports/controllers/rpt_controller.js

@@ -92,7 +92,7 @@ function getAllPagesCommon(user_id, prj_id, rpt_id, pageSize, option, cb) {
                     let maxPages = printCom.totalPages;
                     let pageRst = printCom.outputAsSimpleJSONPageArray(rptTpl, tplData, 1, maxPages, defProperties);
                     if (pageRst) {
-                        //fsUtil.wirteObjToFile(pageRst, "D:/GitHome/ConstructionCost/tmp/testBuiltPageResult.js");
+                        //fsUtil.writeObjToFile(pageRst, "D:/GitHome/ConstructionCost/tmp/testBuiltPageResult.js");
                         cb(null, pageRst);
                     } else {
                         cb('Have errors while on going...', null);
@@ -115,6 +115,7 @@ module.exports = {
             prj_id = params.prj_id,
             user_id = params.user_id,
             pageSize = params.pageSize;
+        // req.session.sessionUser.ssoId
         getAllPagesCommon(user_id, prj_id, rpt_id, pageSize, null, function (err, pageRst) {
             callback(req, res, err, pageRst);
         });
@@ -128,6 +129,33 @@ module.exports = {
             callback(req, res, err, pageRst);
         })
     },
+    getExcel: function(req, res) {
+        let prj_id = req.params.prj_id,
+            rpt_id = req.params.rpt_id,
+            pageSize = req.params.size,
+            rptName = req.params.rptName,
+            isOneSheet = req.params.isOneSheet,
+            option = req.params.option;
+        let user_id = req.session.sessionUser.ssoId;
+        let dftOption = option||JV.PAGING_OPTION_NORMAL;
+        getAllPagesCommon(user_id, prj_id, rpt_id, pageSize, dftOption, function(err, pageRst){
+            try {
+                rpt_xl_util.exportExcel(pageRst, pageSize, rptName, isOneSheet, null, function(newName){
+                    res.setHeader('Content-Type', 'application/vnd.openxmlformats');
+                    res.setHeader("Content-Disposition", "attachment; filename=" + strUtil.getPinYinCamelChars(rptName) + ".xlsx");
+                    let filestream = fs.createReadStream(__dirname.slice(0, __dirname.length - 28) + '/tmp/' + newName + '.xlsx');
+                    filestream.on('data', function(chunk) {
+                        res.write(chunk);
+                    });
+                    filestream.on('end', function() {
+                        res.end();
+                    });
+                });
+            } catch (e) {
+                console.log(e);
+            }
+        })
+    },
     getTestExcel: function(req, res) {
         let rpt_id = req.params.id,
             pageSize = req.params.size,

+ 1 - 1
modules/reports/routes/report_router.js

@@ -25,7 +25,7 @@ module.exports =function (app) {
     rptRouter.get('/getTestPDF/:id/:size/:rptName', reportController.getTestPDF);
     //now is the real:
     rptRouter.post('/getReport', reportController.getReportAllPages);
-    // rptRouter.get('/getExcel/:id/:size/:rptName/:isOneSheet/:option', reportController.getExcel);
+    rptRouter.get('/getExcel/:prj_id/:rpt_id/:size/:rptName/:isOneSheet/:option', reportController.getExcel);
     // rptRouter.get('/getExcelInOneBook/:ids/:size/:rptName/:option', reportController.getExcelInOneBook);
     // rptRouter.get('/getPDF/:id/:size/:rptName', reportController.getPDF);
 

+ 39 - 17
modules/reports/rpt_component/jpc_bill_tab.js

@@ -1,8 +1,6 @@
 let JV = require('./jpc_value_define');
 let JpcFieldHelper = require('./helper/jpc_helper_field');
 let JpcBandHelper = require('./helper/jpc_helper_band');
-let JpcBand = require('./jpc_band');
-let JpcFlowTabHelper = require('./helper/jpc_helper_flow_tab');
 let JpcCommonHelper = require('./helper/jpc_helper_common');
 let JpcDiscreteHelper = require('./helper/jpc_helper_discrete');
 let JpcTextHelper = require('./helper/jpc_helper_text');
@@ -20,11 +18,24 @@ JpcBillTabSrv.prototype.createNew = function(){
         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) {
-        let me = this, rst = 0;
-        let detail_fields = rptTpl[JV.NODE_FIELD_MAP][JV.NODE_DETAIL_FIELDS];
-        if (detail_fields && detail_fields.length > 0) {
-            rst = detail_fields[0].length;
+    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;
     };
@@ -36,31 +47,42 @@ JpcBillTabSrv.prototype.createNew = function(){
         //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));
+        tabRstLst.push(me.outputContent(rptTpl, dataObj, page, bands, unitFactor, controls, pageStatus, $CURRENT_RPT));
         //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));
-    }
-    JpcBillTabResult.outputContent = function(rptTpl, dataObj, page, bands, unitFactor, controls, pageStatus) {
+        for (let i = 0; i < tabRstLst.length; i++) {
+            rst = rst.concat(tabRstLst[i]);
+            tabRstLst[i] = null;
+        }
+        return rst;
+    };
+    JpcBillTabResult.outputContent = function(rptTpl, dataObj, page, bands, unitFactor, controls, pageStatus, $CURRENT_RPT) {
         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]] == true) {
+            if (pageStatus[band[JV.BAND_PROP_DISPLAY_TYPE]]) {
                 let tab_fields = tab[JV.PROP_BILL_FIELDS];
-                let data_details = dataObj[JV.DATA_MASTER_DATA];
+                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];
+                }
                 for (let i = 0; i < tab_fields.length; i++) {
                     let tab_field = tab_fields[i];
                     let data_field = null;
-                    if (me.disp_fields_idx[i] != JV.BLANK_FIELD_INDEX) {
+                    if (me.disp_fields_idx.length > i && me.disp_fields_idx[i] !== JV.BLANK_FIELD_INDEX) {
                         data_field = data_details[me.disp_fields_idx[i]];
                     } else {
-                        data_field = JE.F(tab_field[JV.PROP_FIELD_ID]);
+                        data_field = JE.F(tab_field[JV.PROP_FIELD_ID], $CURRENT_RPT);
                         if (data_field) {
                             data_field = data_field[JV.PROP_AD_HOC_DATA];
                         }
                     }
                     if (!(tab_field[JV.PROP_HIDDEN])) {
-                        let cellItem = JpcCommonOutputHelper.createCommonOutput(tab_field, JpcFieldHelper.getValue(data_field, page - 1), controls);
+                        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, true, false);
                         rst.push(cellItem);
                     }
@@ -79,8 +101,8 @@ JpcBillTabSrv.prototype.createNew = function(){
             }
         }
         return rst;
-    }
+    };
     return JpcBillTabResult;
-}
+};
 
 module.exports = new JpcBillTabSrv();

+ 19 - 13
modules/reports/rpt_component/jpc_data.js

@@ -10,17 +10,21 @@ let JpcData = {
             let private_analyse = function(MASTER_FIELD_STR, DETAIL_FIELD_STR, MASTER_DATA_STR, DETAIL_DATA_STR, dataSeqArr) {
                 //1. get ID fields
                 let masterIDs = [];
-                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]});
+                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 = [];
-                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]});
+                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
@@ -84,11 +88,13 @@ let JpcData = {
                         }
                     }
                 } else { //if no master data
-                    let field = dataObj[DETAIL_DATA_STR][0];
-                    //dataSeqArr = [[]];
-                    dataSeqArr.push([]);
-                    for (let i = 0; i < field.length; i++) {
-                        dataSeqArr[0].push(i);
+                    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);
+                        }
                     }
                 }
             };

+ 5 - 2
modules/reports/rpt_component/jpc_ex.js

@@ -125,6 +125,9 @@ JpcExSrv.prototype.createNew = function(){
                 me.flowTabEx.sorting(rptTpl, dataObj, dataHelper.exDataSeq.slice(0));
             }
         }
+        if (me.billTab) {
+            me.billTab.sorting(rptTpl, dataObj, dataHelper.dataSeq.slice(0));
+        }
         if (me.crossTab) {
             me.crossTab.sorting(rptTpl, dataObj, dataHelper.dataSeq.slice(0));
         }
@@ -155,7 +158,7 @@ JpcExSrv.prototype.createNew = function(){
             //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();
+            me.totalPages = me.billTab.paging(rptTpl, dataObj);
         }
     };
     JpcResult.executeFormulas = function(runType, $CURRENT_TEMPLATE, $CURRENT_DATA, $CURRENT_RPT) {
@@ -244,7 +247,7 @@ JpcExSrv.prototype.createNew = function(){
             } else if (me.crossTab) {
                 rst[JV.PROP_CELLS] = me.crossTab.outputAsSimpleJSONPage(rptTpl, dataObj, page, bands, controls, me);
             } else if (me.billTab) {
-                //
+                rst[JV.PROP_CELLS] = me.billTab.outputAsSimpleJSONPage(rptTpl, dataObj, page, bands, controls, me);
             }
             if (!(me.flowTab && me.flowTab.paging_option === JV.PAGING_OPTION_INFINITY)) {
                 let pageMergeBorder = getPageMergeBorder();

+ 2 - 1
modules/reports/rpt_component/jpc_flow_tab.js

@@ -370,7 +370,8 @@ JpcFlowTabSrv.prototype.createNew = function(){
                         }
                     }
                     //检测是否可退出
-                    if (currentRecAmt >= ttlSegRecAmt) {
+                    if ((currentRecAmt >= ttlSegRecAmt) && (pageIdx % me.multiCols === 0)) {
+                        //备注:这里必须得考虑多栏的情况,否则会造成pageStatus出界的问题
                         break;
                     }
                     //控制阀值,超过阀值则强制退出,防止死循环

+ 12 - 5
modules/reports/util/rpt_construct_data_util.js

@@ -143,6 +143,9 @@ class Rpt_Data_Extractor {
         pri_setup_filter(JV.NODE_DETAIL_FIELDS);
         pri_setup_filter(JV.NODE_MASTER_FIELDS_EX);
         pri_setup_filter(JV.NODE_DETAIL_FIELDS_EX);
+        if (rst.length === 0) {
+            rst.push(projectConst.RATION_ASS);
+        }
         return rst;
     };
 
@@ -263,7 +266,7 @@ function summaryData(sourceData, handleCfg, prjData){
     }
     delete sourceData.data;
     sourceData.data = rstArr;
-    // fsUtil.wirteObjToFile(sourceData.data, "D:/GitHome/ConstructionCost/tmp/sumRst.js");
+    // fsUtil.writeObjToFile(sourceData.data, "D:/GitHome/ConstructionCost/tmp/sumRst.js");
 }
 
 function filterData(sourceData, handleCfg, prjData) {
@@ -331,7 +334,7 @@ function filterData(sourceData, handleCfg, prjData) {
     }
     delete sourceData.data;
     sourceData.data = rstArr;
-    // fsUtil.wirteObjToFile(sourceData.data, "D:/GitHome/ConstructionCost/tmp/filteredRst.js");
+    // fsUtil.writeObjToFile(sourceData.data, "D:/GitHome/ConstructionCost/tmp/filteredRst.js");
 }
 
 function adjustData(sourceData, adjustCfg) {
@@ -527,13 +530,13 @@ function sortData(sourceData, sortCfg, prjData) {
             treeUtil.getFlatArray(rst, destArr);
             delete sourceData.data;
             sourceData.data = destArr;
-            // fsUtil.wirteObjToFile(sourceData.data, "D:/GitHome/ConstructionCost/tmp/sortedAndFlattedRst.js");
+            // fsUtil.writeObjToFile(sourceData.data, "D:/GitHome/ConstructionCost/tmp/sortedAndFlattedRst.js");
             break;
         case "normal":
             private_normal_sort(tempRstArr, sortCfg[JV.PROP_SORT_KEYS]);
             delete sourceData.data;
             sourceData.data = tempRstArr;
-            // fsUtil.wirteObjToFile(sourceData.data, "D:/GitHome/ConstructionCost/tmp/normalSortedRst.js");
+            // fsUtil.writeObjToFile(sourceData.data, "D:/GitHome/ConstructionCost/tmp/normalSortedRst.js");
             break;
         case "accord_to_parent":
             let pcKey = sortCfg[JV.PROP_PARENT_CHILD_SORT_KEY];
@@ -625,7 +628,11 @@ function ext_mainGetPropety(propKey) {
     let dtObj = parentObj["myOwnRawDataObj"];
     if (propKey && dtObj) {
         if (dtObj.hasOwnProperty("property")) {
-            rst.push(dtObj["property"][propKey]);
+            if (!dtObj["property"][propKey] && dtObj[propKey]) {
+                rst.push(dtObj[propKey]);
+            } else {
+                rst.push(dtObj["property"][propKey]);
+            }
         } else  {
             rst.push(dtObj[propKey]);
         }

+ 22 - 8
modules/reports/util/rpt_excel_util.js

@@ -158,6 +158,9 @@ function writeStyles(stylesObj){
         if (strUtil.convertStrToBoolean(font[JV.FONT_PROPS[3]])) {
             rst.push('<b/>');
         }
+        if (strUtil.convertStrToBoolean(font[JV.FONT_PROPS[5]])) {
+            rst.push('<u/>');
+        }
         rst.push('<sz val="' + font.size + '"/>');
         rst.push('<color indexed="' + font.colorIdx + '"/>');
         rst.push('<name val="' + font[JV.FONT_PROPS[0]] + '"/>');
@@ -270,7 +273,13 @@ function writeSharedString(sharedStrList){
         for (let i = 0; i < sharedStrList.length; i++) {
             //rst.push('<si><t>' + sharedStrList[i] + '</t></si>');
             if (typeof sharedStrList[i] === 'string') {
-                rst.push('<si><t>' + sharedStrList[i].replace('|','\r\n') + '</t></si>');
+                if (sharedStrList[i].indexOf('|') >= 0) {
+                    //rst.push('<si><t>' + sharedStrList[i].split('|').join('\r\n') + '</t></si>');
+                    rst.push('<si><t>' + sharedStrList[i].split('|').join('\n') + '</t></si>');
+                } else {
+                    rst.push('<si><t>' + sharedStrList[i] + '</t></si>');
+                }
+                // rst.push('<si><t>' + sharedStrList[i].replace('|','\r\n') + '</t></si>');
             } else {
                 rst.push('<si><t>' + sharedStrList[i] + '</t></si>');
             }
@@ -332,7 +341,7 @@ function writeSheets(pageData, paperSize, sharedStrList, stylesObj, isSinglePage
 function writeSheet(pageData, sheetData, paperSize, sharedStrList, stylesObj){
     let rst = [], xPos = [], yPos = [], yMultiPos = [], headerStr = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
     let cacheBorderCell = {};
-    let currentPageMergePos = null;
+    let currentPageMergePos = null; //在 JV.PAGING_OPTION_INFINITY 场合应用
     let private_pre_analyze_pos = function(){
         let cell, pos;
         let self_analyze_sheet_pos = function (theShtData, theXPos, theYPos) {
@@ -420,7 +429,8 @@ function writeSheet(pageData, sheetData, paperSize, sharedStrList, stylesObj){
         for (let i = 0; i < stylesObj.fonts.length; i++) {
             let font = stylesObj.fonts[i];
             if (sheetFont) {
-                if (font[JV.FONT_PROPS[0]] === sheetFont[JV.FONT_PROPS[0]] && font.size === Math.round(sheetFont[JV.FONT_PROPS[1]] * 3 / 4) && font[JV.FONT_PROPS[3]] == sheetFont[JV.FONT_PROPS[3]]) {
+                if (font[JV.FONT_PROPS[0]] === sheetFont[JV.FONT_PROPS[0]] && font.size === Math.round(sheetFont[JV.FONT_PROPS[1]] * 3 / 4)
+                    && font[JV.FONT_PROPS[3]] === sheetFont[JV.FONT_PROPS[3]] && font[JV.FONT_PROPS[5]] === sheetFont[JV.FONT_PROPS[5]]) {
                     hasFont = true;
                     rst = i;
                     break;
@@ -436,6 +446,7 @@ function writeSheet(pageData, sheetData, paperSize, sharedStrList, stylesObj){
             font.charset = 134;
             font.colorIdx = "8";
             font[JV.FONT_PROPS[3]] = sheetFont[JV.FONT_PROPS[3]]; //font bold
+            font[JV.FONT_PROPS[5]] = sheetFont[JV.FONT_PROPS[5]]; //font underline
             stylesObj.fonts.push(font);
             rst = stylesObj.fonts.length - 1;
         }
@@ -476,7 +487,10 @@ function writeSheet(pageData, sheetData, paperSize, sharedStrList, stylesObj){
         return rst;
     };
     let private_chkAndGetMergeLine = function(cell, sheetBorder, borderStr, needFurtherChk) {
-        let rst = 0, mergeBorder = pageData[JV.BAND_PROP_MERGE_BAND];
+        let rst = 0,
+            mergeBorder = (sheetData[JV.PROP_PAGE_MERGE_BORDER])?sheetData[JV.PROP_PAGE_MERGE_BORDER]:pageData[JV.BAND_PROP_MERGE_BAND],
+            mergeBand = pageData[JV.BAND_PROP_MERGE_BAND]
+        ;
         if (sheetBorder[borderStr] && sheetBorder[borderStr][JV.PROP_LINE_WEIGHT] !== undefined) {
             rst = sheetBorder[borderStr][JV.PROP_LINE_WEIGHT];
         }
@@ -489,7 +503,7 @@ function writeSheet(pageData, sheetData, paperSize, sharedStrList, stylesObj){
                     for (let i = 0; i < topSide.length; i++) {
                         if (cell[JV.PROP_AREA][JV.PROP_TOP] >= topSide[i]) {
                             if (cell[JV.PROP_AREA][JV.PROP_BOTTOM] <= bottomSide[i]) {
-                                let destStyle = pageData[JV.NODE_STYLE_COLLECTION][mergeBorder[JV.PROP_STYLE][JV.PROP_ID]];
+                                let destStyle = pageData[JV.NODE_STYLE_COLLECTION][mergeBand[JV.PROP_STYLE][JV.PROP_ID]];
                                 rst = destStyle[borderStr][JV.PROP_LINE_WEIGHT];
                                 break;
                             }
@@ -498,13 +512,13 @@ function writeSheet(pageData, sheetData, paperSize, sharedStrList, stylesObj){
                         }
                     }
                 } else {
-                    let destStyle = pageData[JV.NODE_STYLE_COLLECTION][mergeBorder[JV.PROP_STYLE][JV.PROP_ID]];
+                    let destStyle = pageData[JV.NODE_STYLE_COLLECTION][mergeBand[JV.PROP_STYLE][JV.PROP_ID]];
                     rst = destStyle[borderStr][JV.PROP_LINE_WEIGHT];
                 }
             }
         } else {
             if (cell[JV.PROP_AREA][borderStr] === mergeBorder[borderStr]) {
-                let destStyle = pageData[JV.NODE_STYLE_COLLECTION][mergeBorder[JV.PROP_STYLE][JV.PROP_ID]];
+                let destStyle = pageData[JV.NODE_STYLE_COLLECTION][mergeBand[JV.PROP_STYLE][JV.PROP_ID]];
                 if (needFurtherChk) {
                     if (cell[JV.PROP_AREA][JV.PROP_TOP] >= mergeBorder[JV.PROP_TOP] &&
                         cell[JV.PROP_AREA][JV.PROP_BOTTOM] <= mergeBorder[JV.PROP_BOTTOM]) {
@@ -983,7 +997,7 @@ module.exports = {
             }
             //3. everything is ok, then call me
             me.exportExcel(newPageData, paperSize, fName, 'false', sheetNames, callback);
-            fsUtil.wirteObjToFile(newPageData, 'D:/GitHome/ConstructionOperation/tmp/combinedHeader.js');
+            fsUtil.writeObjToFile(newPageData, 'D:/GitHome/ConstructionOperation/tmp/combinedHeader.js');
         } catch (e) {
             console.log(e);
         }

+ 2 - 0
modules/volume_price/models/volume_price_model.js

@@ -30,6 +30,7 @@ class volumePriceModel extends baseModel {
             if(err){
                 callback(1, '', null);
             }else {
+                // console.log(JSON.stringify(datas));
                 callback(0, consts.projectConst.VOLUMEPRICE, datas);
             }
         })
@@ -39,6 +40,7 @@ class volumePriceModel extends baseModel {
         let funs = [];
 
         function saveOne(doc) {
+            // console.log('-----------------------------------------------------------------------------' + JSON.stringify(doc));
             return function (cb) {
                 switch (doc.updateType) {
                     case commonConsts.UT_UPDATE:

+ 6 - 0
modules/volume_price/models/volume_price_schema.js

@@ -20,12 +20,18 @@ let volumePriceSchema = new Schema({
     serialNo: Number,
     // 编号
     code: String,
+    // 量价类型:人工、材料、机械、主材、设备
+    type: String,
+    programID: Number,
     // 名称
     name: String,
     // 单位
     unit: String,
     // 数量
     quantity: Number,
+    marketUnitFee: String,
+    marketTotalFee: String,
+
     // 费用字段
     fees: [subSchema.feesSchema],
     // 是否删除

+ 1 - 1
public/fsUtil.js

@@ -25,7 +25,7 @@ module.exports = {
             });
         }
     },
-    wirteObjToFile: function(obj, filePath) {
+    writeObjToFile: function(obj, filePath) {
         if (obj) {
             let arr = [];
             arr.push(JSON.stringify(obj));

+ 4 - 1
public/web/rpt_value_define.js

@@ -136,6 +136,7 @@ const JV = {
     PROP_CALCULATION: "CalculationType",
     PROP_H_CALCULATION: "H_CalculationType",
     PROP_V_CALCULATION: "V_CalculationType",
+    PROP_FIT_AREA: "isFitArea",
 
     IDX_LEFT: 0,
     IDX_TOP: 1,
@@ -236,7 +237,7 @@ const JV = {
     SIZE_16K: [7.75, 10.75],
     SIZE_EXECUTIVE: [7.25, 10.5],
 
-    OUTPUT_OFFSET: [2,1,2,3],
+    OUTPUT_OFFSET: [2,2,1,3],
     OFFSET_IDX_LEFT: 0,
     OFFSET_IDX_RIGHT: 1,
     OFFSET_IDX_TOP: 2,
@@ -265,6 +266,8 @@ const JV = {
 
     VERTICAL_ANGLE: "90",
     ANTI_VERTICAL_ANGLE: "-90",
+    VERTICAL_ANGLE_INT: 90,
+    ANTI_VERTICAL_ANGLE_INT: -90,
 
     LAST_DEF: ""
 };

+ 1 - 1
public/web/tree_sheet/tree_sheet_helper.js

@@ -137,7 +137,7 @@ var TREE_SHEET_HELPER = {
                     cell.value(getFieldText2());
                 }
                 if (colSetting.data.cellType && Object.prototype.toString.apply(colSetting.data.cellType) !== "[object String]") {
-                    cell.cellType(colSetting.data.cellType);
+                    cell.cellType(colSetting.data.cellType(node));
                 }
                 if (colSetting.readOnly) {
                     if (Object.prototype.toString.apply(colSetting.readOnly) === "[object Function]") {

+ 16 - 8
test/tmp_data/test_ration_calc/ration_calc_base.js

@@ -42,42 +42,50 @@ let rationCalcBase = [
         'calcFun': 'base',
         'calcType': baseCalc,
         'gljTypes': [gljType.LABOUR]
-    },{
+    },
+    {
         'dispName': '定额基价材料费',
         'calcFun': 'base',
         'calcType': baseCalc,
         'gljTypes': [gljType.GENERAL_MATERIAL, gljType.CONCRETE, gljType.MORTAR, gljType.MIX_RATIO, gljType.COMMERCIAL_CONCRETE, gljType.COMMERCIAL_MORTAR]
-    },{
+    },
+    {
         'dispName': '定额基价机械费',
         'calcFun': 'base',
         'calcType': baseCalc,
         'gljTypes': [gljType.GENERAL_MACHINE]
-    },{
+    },
+    {
         'dispName': '定额基价机上人工费',
         'calcFun': 'base',
         'calcType': baseCalc,
         'gljTypes': [gljType.MACHINE_LABOUR]
-    },{
+    },
+    {
         'dispName': '人工费价差',
         'calcFun': 'diff',
         'calcType': diffCalc,
         'gljTypes': [gljType.LABOUR]
-    },{
+    },
+    {
         'dispName': '材料费价差',
         'calcFun': 'diff',
         'calcType': diffCalc,
         'gljTypes': [gljType.GENERAL_MATERIAL, gljType.CONCRETE, gljType.MORTAR, gljType.MIX_RATIO, gljType.COMMERCIAL_CONCRETE, gljType.COMMERCIAL_MORTAR]
-    },{
+    },
+    {
         'dispName': '机械费价差',
         'calcFun': 'diff',
         'calcType': diffCalc,
         'gljTypes': [gljType.GENERAL_MACHINE]
-    },{
+    },
+    {
         'dispName': '主材费',
         'calcFun': 'budget',
         'calcType': budgetCalc,
         'gljTypes': [gljType.MAIN_MATERIAL]
-    },{
+    },
+    {
         'dispName': '设备费',
         'calcFun': 'budget',
         'calcType': budgetCalc,

+ 116 - 0
test/unit/reports/test_cover_01.js

@@ -0,0 +1,116 @@
+/**
+ * Created by Tony on 2017/11/21.
+ */
+
+let test = require('tape');
+import JpcEx from "../../../modules/reports/rpt_component/jpc_ex";
+import JV from "../../../modules/reports/rpt_component/jpc_value_define";
+let mongoose = require("mongoose");
+let fileUtils = require("../../../modules/common/fileUtils");
+let path = require('path');
+let dbm = require("../../../config/db/db_manager");
+let rpt_cfg = require('./rpt_cfg');
+dbm.connect();
+let consts = require('../../../modules/main/models/project_consts');
+let projectConsts = consts.projectConst;
+fileUtils.getGlobbedFiles('../../../modules/complementary_glj_lib/models/*.js').forEach(function(modelPath) {
+    require(path.resolve(modelPath));
+});
+
+fileUtils.getGlobbedFiles('../../../modules/ration_glj/models/*.js').forEach(function(modelPath) {
+    require(path.resolve(modelPath));
+});
+
+//引入报表模块
+fileUtils.getGlobbedFiles('../../../modules/reports/models/*.js').forEach(function(modelPath) {
+    require(path.resolve(modelPath));
+})
+
+//暂时引入其它模块的model
+require('../../../modules/fee_rates/models/fee_rates');
+// 引入人工系数模块
+require('../../../modules/main/models/labour_coe_model');
+require('../../../modules/main/models/calc_program_model');
+
+let fsUtil = require("../../../public/fsUtil");
+
+let prjMdl = require('../../../modules/pm/models/project_model');
+let projectDataMdl = require('../../../modules/main/models/project');
+let demoPrjId = - 1;
+let demoRptId = 223, pagesize = "A4";
+
+let userId_Leng = 1142; //小冷User Id
+// demoPrjId = 720; //QA: DW3
+demoPrjId = 838; //QA:
+/*/
+ let userId_Dft = userId_Leng;
+ /*/
+let userId_Dft = 76075;
+//*/
+
+let rptTplFacade = require("../../../modules/reports/facade/rpt_template_facade");
+let rptTplDataFacade = require("../../../modules/reports/facade/rpt_tpl_data_facade");
+
+import rptDataExtractor from "../../../modules/reports/util/rpt_construct_data_util";
+
+let fs = require('fs');
+//设置Date Format函数
+fs.readFile(__dirname.slice(0, __dirname.length - 18) + '/public/web/date_util.js', 'utf8', 'r', function (err, data) {
+    eval(data);
+});
+
+//*
+test('测试 - 打开模板: 封-1 ', function (t) {
+    rptTplFacade.getRptTemplate(demoRptId).then(function(rptTpl) {
+        let rptDataUtil = new rptDataExtractor();
+        rptDataUtil.initialize(rptTpl._doc);
+        let filter = rptDataUtil.getDataRequestFilter();
+        // console.log(filter);
+        //正常应该根据报表模板定义的数据类型来请求数据
+        rptTplDataFacade.prepareProjectData(userId_Dft, demoPrjId, filter, function (err, msg, rawDataObj) {
+            if (!err) {
+                try {
+                    // fsUtil.writeObjToFile(rawDataObj, "D:/GitHome/ConstructionCost/tmp/rptTplRawDataObject.js");
+                    let tplData = rptDataUtil.assembleData(rawDataObj);
+                    //build the report
+                    let printCom = JpcEx.createNew();
+                    rptTpl[JV.NODE_MAIN_INFO][JV.NODE_PAGE_INFO][JV.PROP_PAGE_SIZE] = pagesize;
+                    let defProperties = rpt_cfg;
+                    let dftOption = JV.PAGING_OPTION_NORMAL;
+                    printCom.initialize(rptTpl);
+                    printCom.analyzeData(rptTpl, tplData, defProperties, dftOption);
+                    let maxPages = printCom.totalPages;
+                    let pageRst = printCom.outputAsSimpleJSONPageArray(rptTpl, tplData, 1, maxPages, defProperties);
+                    if (pageRst) {
+                        fsUtil.writeObjToFile(pageRst, "D:/GitHome/ConstructionCost/tmp/testBuiltPageResult.js");
+                    } else {
+                        console.log("oh! no pages were created!");
+                    }
+                } catch (ex) {
+                    console.log(ex);
+                    t.pass('pass with exception!');
+                    t.end();
+                }
+
+                t.pass('pass succeeded!');
+                t.end();
+            } else {
+                console.log(msg);
+                t.pass('pass with error!');
+                t.end();
+            }
+        })
+    });
+});
+//*/
+
+test('close the connection', function (t) {
+    setTimeout(function () {
+        mongoose.disconnect();
+        t.pass('closing db connection');
+        t.end();
+    }, 8000);
+    // mongoose.disconnect();
+    // t.pass('closing db connection');
+    // t.end();
+});

+ 6 - 6
test/unit/reports/test_tpl_09_1.js

@@ -62,7 +62,7 @@ fs.readFile(__dirname.slice(0, __dirname.length - 18) + '/public/web/date_util.j
 test('测试 - 获取project数据: ', function (t) {
     projectDataMdl.getData(demoPrjId, function (err, message, result) {
         if (!err) {
-            fsUtil.wirteObjToFile(result, "D:/GitHome/ConstructionCost/tmp/ProjectDataFullObject.js");
+            fsUtil.writeObjToFile(result, "D:/GitHome/ConstructionCost/tmp/ProjectDataFullObject.js");
             t.pass('pass succeeded!');
             t.end();
         } else {
@@ -91,8 +91,8 @@ test('测试 - 获取project部分数据: ', function (t) {
                     //     newData.push(JSON.stringify(item));
                     // }
                     // fsUtil.writeArrayToFile(newData, "D:/GitHome/ConstructionCost/tmp/getProjectData_partial.js");
-                    // fsUtil.wirteObjToFile(prjObj, "D:/GitHome/ConstructionCost/tmp/getProjectObjectNew.js");
-                    fsUtil.wirteObjToFile(results, "D:/GitHome/ConstructionCost/tmp/getProjectData_partialNew.js");
+                    // fsUtil.writeObjToFile(prjObj, "D:/GitHome/ConstructionCost/tmp/getProjectObjectNew.js");
+                    fsUtil.writeObjToFile(results, "D:/GitHome/ConstructionCost/tmp/getProjectData_partialNew.js");
                     t.pass('pass succeeded!');
                     t.end();
                 } else {
@@ -121,7 +121,7 @@ test('测试 - 测试模板啦: ', function (t) {
             if (!err) {
                 try {
                     let tplData = rptDataUtil.assembleData(rawDataObj);
-                    // fsUtil.wirteObjToFile(rawDataObj, "D:/GitHome/ConstructionCost/tmp/rptTplRawDataObject.js");
+                    // fsUtil.writeObjToFile(rawDataObj, "D:/GitHome/ConstructionCost/tmp/rptTplRawDataObject.js");
                     //it's time to build the report!!!
                     let printCom = JpcEx.createNew();
                     rptTpl[JV.NODE_MAIN_INFO][JV.NODE_PAGE_INFO][JV.PROP_PAGE_SIZE] = pagesize;
@@ -134,7 +134,7 @@ test('测试 - 测试模板啦: ', function (t) {
                     if (pageRst) {
                         // fsUtil.wirteObjToFile(pageRst, "D:/GitHome/ConstructionCost/tmp/testBuiltPageResult.js");
                     } else {
-                        console.log("oh! no pages were created!")
+                        console.log("oh! no pages were created!");
                     }
                 } catch (ex) {
                     console.log(ex);
@@ -180,7 +180,7 @@ test('测试 - 显示保存小数位数问题: ', function (t) {
     rpt_decimal_mdl.find({}).then(function (rst) {
         //console.log(rst);
         if (rst.length > 0) {
-            fsUtil.wirteObjToFile(rst, "D:/GitHome/ConstructionCost/tmp/testDecimalResult.js");
+            fsUtil.writeObjToFile(rst, "D:/GitHome/ConstructionCost/tmp/testDecimalResult.js");
         }
         t.pass('pass get decimal ok!');
         t.end();

+ 3 - 0
tmp/forDeployment.js

@@ -0,0 +1,3 @@
+/**
+ * Created by Tony on 2017/11/28.
+ */

二進制
web/building_saas/img/FirstPageSimple.cur


二進制
web/building_saas/img/LastPageSimple.cur


二進制
web/building_saas/img/NextPageSimple.cur


二進制
web/building_saas/img/PreviousPageSimple.cur


二進制
web/building_saas/img/baobiao.png


+ 8 - 7
web/building_saas/main/html/main.html

@@ -260,8 +260,8 @@
                     <div class="row">
                         <div class="col-3">
                             <ul class="nav flex-column nav-pills" role="tablist">
-                                <li class="nav-item"><a class="nav-link active" data-toggle="pill" href="#poj-settings-1" role="tab">基本信息</a></li>
-                                <li class="nav-item"><a class="nav-link" data-toggle="pill" href="#poj-settings-2" role="tab">工程特征</a></li>
+                                <li class="nav-item"><a class="nav-link active" data-toggle="pill" href="#poj-settings-basicInfo" role="tab" id="tab_poj-settings-basicInfo">基本信息</a></li>
+                                <li class="nav-item"><a class="nav-link" data-toggle="pill" href="#poj-settings-projFeature" id="tab_poj-settings-projFeature" role="tab">工程特征</a></li>
                                 <li class="nav-item"><a class="nav-link" data-toggle="pill" href="#poj-settings-3" role="tab">指标信息</a></li>
                                 <li class="nav-item"><a class="nav-link" data-toggle="pill" href="#poj-settings-4" role="tab">关于计算</a></li>
                                 <li class="nav-item"><a class="nav-link" data-toggle="pill" href="#poj-settings-billsQuanDecimal" id="tab_poj-settings-bqDecimal" role="tab">清单工程量精度</a></li>
@@ -272,14 +272,13 @@
                         <div class="col-9">
                             <div class="tab-content">
                                 <!--基本信息-->
-                                <div class="tab-pane fade show active" id="poj-settings-1" role="tabpanel">
-                                    <div class="modal-auto-height">
-                                        基本信息
+                                <div class="tab-pane fade show active" id="poj-settings-basicInfo" role="tabpanel">
+                                    <div class="modal-auto-height" style="overflow: hidden;" id="basicInfoSpread">
                                     </div>
                                 </div>
                                 <!--工程特征-->
-                                <div class="tab-pane fade" id="poj-settings-2" role="tabpanel">
-                                    <div class="modal-auto-height">
+                                <div class="tab-pane fade" id="poj-settings-projFeature" role="tabpanel">
+                                    <div class="modal-auto-height" style="overflow: hidden" id="projFeatureSpread">
                                         工程特征
                                     </div>
                                 </div>
@@ -695,6 +694,8 @@
         <script type="text/javascript" src="/web/building_saas/main/js/views/options_view.js"></script>
         <script type="text/javascript" src="/web/building_saas/main/js/views/project_property_bills_quantity_decimal.js"></script>
         <script type="text/javascript" src="/web/building_saas/main/js/views/project_property_decimal_view.js"></script>
+        <script type="text/javascript" src="/web/building_saas/main/js/views/project_property_basicInfo.js"></script>
+        <script type="text/javascript" src="/web/building_saas/main/js/views/project_property_projFeature.js"></script>
         <script type="text/javascript" src="/web/building_saas/main/js/main_ajax.js"></script>
         <script type="text/javascript" src="/web/building_saas/main/js/main.js"></script>
         <script type="text/javascript" src="/web/building_saas/main/js/controllers/project_controller.js"></script>

+ 8 - 14
web/building_saas/main/js/controllers/project_controller.js

@@ -59,20 +59,16 @@ ProjectController = {
             } else if (false) {
                 alert('当前清单已有公式计算,不能套用定额。');
             } else {
-                let firstChild = selected.firstChild();
-                if (firstChild && firstChild.sourceType === project.VolumePrice.getSourceType()) {
-                    alert('当前位置已有量价,不能套用定额。');
+                if (std) {
+                    newSource = project.Ration.insertStdRation(selected.source.getID(), null, std);
+                    project.ration_glj.addRationGLJ(newSource,std);
                 } else {
-                    if (std) {
-                        newSource = project.Ration.insertStdRation(selected.source.getID(), null, std);
-                        project.ration_glj.addRationGLJ(newSource,std);
-                    } else {
-                        newSource = project.Ration.insertRation(selected.source.getID());
-                    }
+                    newSource = project.Ration.insertRation(selected.source.getID());
                 }
+
                 newNode = project.mainTree.insert(selected.getID(), selected.tree.rootID());
             }
-        } else if (selected.sourceType === project.Ration.getSourceType()) {
+        } else if (selected.sourceType === project.Ration.getSourceType() || selected.sourceType === project.VolumePrice.getSourceType()) {
             if (std) {
                 newSource = project.Ration.insertStdRation(selected.source[project.masterField.ration], selected.source, std);
                 project.ration_glj.addRationGLJ(newSource,std);
@@ -80,9 +76,7 @@ ProjectController = {
                 newSource = project.Ration.insertRation(selected.source[project.masterField.ration], selected.source);
             }
             newNode = project.mainTree.insert(selected.getParentID(), selected.getNextSiblingID());
-        } else if (selected.sourceType === project.VolumePrice.getSourceType()) {
-            alert('当前位置已有量价,不能套用定额。');
-        }
+        };
         if (newNode) {
             newNode.source = newSource;
             newNode.sourceType = project.Ration.getSourceType();
@@ -117,7 +111,7 @@ ProjectController = {
         if (selected.sourceType === project.Bills.getSourceType() && selected.source.children.length === 0) {
             newSource = project.VolumePrice.insertVolumePrice(selected.source.getID());
             newNode = project.mainTree.insert(selected.getID(), selected.tree.rootID());
-        } else if (selected.sourceType === project.VolumePrice.getSourceType()) {
+        } else if (selected.sourceType === project.Ration.getSourceType() || selected.sourceType === project.VolumePrice.getSourceType()) {
             newSource = project.VolumePrice.insertVolumePrice(selected.source[project.masterField.volumePrice], selected.source);
             newNode = project.mainTree.insert(selected.getParentID(), selected.getNextSiblingID());
         }

+ 95 - 62
web/building_saas/main/js/models/calc_program.js

@@ -255,23 +255,19 @@ let executeObj = {
     },
     base: function(calcBaseName) {
         let me = executeObj, rst = 0,
-            //base = getRationCalcBase(calcBaseName);
             base = me.calcBase[calcBaseName];
 
         if (base != null) {
-            let price = 0, aprice = 0, mprice = 0, tmpSum = 0, mdSum = 0;
-
             function isSubset(sub, arr){
-                // if(!(sub instanceof Array) || !(arr instanceof Array)) return false;
-                // if(sub.length > arr.length) return false;
                 for(var i = 0, len = sub.length; i < len; i++){
                     if(arr.indexOf(sub[i]) == -1) return false;
                 }
                 return true;
-            }
-
+            };
             // 机上人工费:多一层
-            if (isSubset(base.gljTypes, [gljType.MACHINE_LABOUR])) {
+            function machineLabourFee() {
+                if (!me.treeNode.data.gljList) return 0;
+                let result = 0, mdSum = 0;
                 for (let glj of me.treeNode.data.gljList) {
                     if (glj.type == gljType.GENERAL_MACHINE) {
                         // 获取机械组成物
@@ -279,47 +275,69 @@ let executeObj = {
                         if (!mds) mds = [];
                         for (let md of mds){
                             if (base.gljTypes.indexOf(md.glj_type) >= 0) {
-                                price = md["base_price"];
-                                if (!price) price = 0;
-                                mdSum = mdSum + (md["consumption"] * price).toDecimal(me.digit);
+                                let q = md["consumption"] ? md["consumption"] : 0;
+                                let p = md["base_price"] ? md["base_price"] : 0;
+                                mdSum = mdSum + (q * p).toDecimal(me.digit);
                                 mdSum = (mdSum).toDecimal(me.digitDefault);
                             }
                         };
-                        tmpSum = tmpSum + (glj["quantity"] * mdSum).toDecimal(me.digitDefault);
-                        tmpSum = (tmpSum).toDecimal(me.digitDefault);
-                    }
+                        result = result + (glj["quantity"] * mdSum).toDecimal(me.digitDefault);
+                        result = (result).toDecimal(me.digitDefault);
+                    };
                 };
-            }else{
-                if (!me.treeNode.data.gljList) tmpSum = 0
-                else{
-                    for (let glj of me.treeNode.data.gljList) {
-                        if (base.gljTypes.indexOf(glj.type) >= 0) {
-                            if (base.calcType == baseCalc){ price = glj["basePrice"];}
-                            else if (base.calcType == adjustCalc){price = glj["adjustPrice"];}
-                            else if (base.calcType == budgetCalc){price = glj["marketPrice"];}
-                            else if (base.calcType == diffCalc){
-                                aprice = glj["adjustPrice"];
-                                if (!aprice) aprice = 0;
-                                mprice = glj["marketPrice"];
-                                if (!mprice) mprice = 0;
-                                price = mprice - aprice;
-                            };
-                            if (!price) price = 0;
-                            tmpSum = tmpSum + (glj["quantity"] * price).toDecimal(me.digitDefault);
-                            tmpSum = (tmpSum).toDecimal(me.digitDefault);
+                return result;
+            };
+            function commonGLJFee(){
+                if (!me.treeNode.data.gljList) return 0;
+                let result = 0;
+                for (let glj of me.treeNode.data.gljList) {
+                    let price = 0;
+                    if (base.gljTypes.indexOf(glj.type) >= 0) {
+                        if (base.calcType == baseCalc){ price = parseFloat(glj["basePrice"]);}
+                        else if (base.calcType == adjustCalc){price = parseFloat(glj["adjustPrice"]);}
+                        else if (base.calcType == budgetCalc){price = parseFloat(glj["marketPrice"]);}
+                        else if (base.calcType == diffCalc){
+                            let aprice = glj["adjustPrice"] ? glj["adjustPrice"] : 0;
+                            let mprice = glj["marketPrice"] ? glj["marketPrice"] : 0;
+                            price = (parseFloat(mprice) - parseFloat(aprice)).toDecimal(me.digitDefault);
                         };
+                        result = result + (glj["quantity"] * price).toDecimal(me.digitDefault);
+                        result = (result).toDecimal(me.digitDefault);
                     };
                 };
+                return result;
+            };
+            // 量价没有具体的工料机类型,但仍然要用定额的计算程序,所以要给计算基数直接指定。
+            function volumePriceFee() {
+                let result = 0;
+                if (
+                    ( me.treeNode.data.type === '人工' && base.dispName === '定额基价人工费') ||
+                    ( me.treeNode.data.type === '材料' && base.dispName === '定额基价材料费') ||
+                    ( me.treeNode.data.type === '机械' && base.dispName === '定额基价机械费') ||
+                    ( me.treeNode.data.type === '主材' && base.dispName === '主材费') ||
+                    ( me.treeNode.data.type === '设备' && base.dispName === '设备费')
+                ) result = me.treeNode.data.marketUnitFee;
+
+                return result;
             };
 
-            rst = (tmpSum).toDecimal(me.digitDefault);
+            if (me.treeNode.sourceType === projectObj.project.VolumePrice.getSourceType()){
+                rst = volumePriceFee();
+            }
+            else{
+                if (isSubset(base.gljTypes, [gljType.MACHINE_LABOUR]))
+                    rst = machineLabourFee()
+                else
+                    rst = commonGLJFee();
+            }
         };
 
         return rst;
     },
     HJ: function () {
         let me = this;
-        return me.treeNode.calcBaseValue;
+        let rst = me.treeNode.data.marketUnitFee ? me.treeNode.data.marketUnitFee : 0;
+        return rst;
     }
 };
 
@@ -521,7 +539,7 @@ class CalcProgram {
         };
     };
 
-    // 仅内部调用。注意:外部不能直接使用
+    // 仅内部调用。注意:外部不能直接使用,因为这里传入的树节点必须有一定的初始化。
     InnerCalc(treeNode){
         let me = this;
         let project = me.project;
@@ -602,14 +620,18 @@ class CalcProgram {
                     treeNode.data.programID = defaultBillTemplate.ID;
                 };
             }
-            else {
-                if (treeNode.sourceType === project.Ration.getSourceType()) {
-                    treeNode.data.gljList = project.ration_glj.getGljArrByRation(treeNode.data.ID);
-                }
-                else if (treeNode.sourceType === project.Bills.getSourceType()) {
-                    let rations = project.Ration.getBillsSortRation(treeNode.source.getID());
-                    treeNode.data.gljList = project.ration_glj.getGatherGljArrByRations(rations);
+            else if (treeNode.calcType == treeNodeCalcType.ctRationCalcProgram) {
+                let muf = treeNode.data.marketUnitFee ? treeNode.data.marketUnitFee : 0;
+                let q = treeNode.data.quantity ? treeNode.data.quantity : 0;
+                treeNode.data.marketTotalFee = (muf * q).toDecimal(me.digit);
+                treeNode.data.gljList = me.project.ration_glj.getGljArrByRation(treeNode.data.ID);
+                if (treeNode.data.programID == undefined){
+                    treeNode.data.programID = 1;
                 };
+            }
+            else if (treeNode.calcType == treeNodeCalcType.ctBillCalcProgram) {
+                let rations = project.Ration.getBillsSortRation(treeNode.source.getID());
+                treeNode.data.gljList = project.ration_glj.getGatherGljArrByRations(rations);
 
                 if (treeNode.data.programID == undefined){
                     treeNode.data.programID = 1;
@@ -649,34 +671,25 @@ class CalcProgram {
         let me = this;
 
         let isRation = treeNode.sourceType === me.project.Ration.getSourceType();
+        let isVolumePrice = treeNode.sourceType === me.project.VolumePrice.getSourceType();
         let isBill = treeNode.sourceType === me.project.Bills.getSourceType();
-        let isLeafBill = isBill && treeNode.source.children && treeNode.source.children.length === 0;
+        let isLeafBill = isBill && treeNode.source.children && treeNode.source.children.length === 0;   // 是清单且其下没有子清单
         let isBillPriceCalc = me.project.projSetting.billsCalcMode === billsPrice;
 
-        if (isRation)
-            treeNode.calcType = treeNodeCalcType.ctRationCalcProgram
+        if (isRation || isVolumePrice){
+            treeNode.calcType = treeNodeCalcType.ctRationCalcProgram;
+        }
         else if (isLeafBill) {
             if (treeNode.children && treeNode.children.length > 0){
-                if (treeNode.children[0].sourceType == me.project.Ration.getSourceType()){
-                    if (isBillPriceCalc)                   // 清单单价计算模式下的叶子清单:取自己的计算程序ID,找到自己的计算程序计算
-                        treeNode.calcType = treeNodeCalcType.ctBillCalcProgram;
-                    else                                  // 前三种计算模式下的叶子清单:汇总定额的计算程序的费用类别
-                        treeNode.calcType = treeNodeCalcType.ctGatherRations;
-                }
-                else if (treeNode.children[0].sourceType == me.project.VolumePrice.getSourceType()){
-                    let value = 20000;
-                    // if (treeNode.data.feesIndex && treeNode.data.feesIndex.common && treeNode.data.feesIndex.common.unitFee != 0)
-                    //     value = treeNode.data.feesIndex.common.unitFee;
-                    treeNode.calcType = treeNodeCalcType.ctCalcBaseValue;
-                    treeNode.calcBaseValue = value;
-                };
+                me.calcLeafBillChildren(treeNode);
+
+                if (isBillPriceCalc)                        // 清单单价计算模式下的叶子清单:取自己的计算程序ID,找到自己的计算程序计算
+                    treeNode.calcType = treeNodeCalcType.ctBillCalcProgram;
+                else                                        // 前三种计算模式下的叶子清单:汇总定额的计算程序的费用类别
+                    treeNode.calcType = treeNodeCalcType.ctGatherRations;
             }
             else{                                          // 公式计算
-                let value = 20000;
-                // if (treeNode.data.feesIndex && treeNode.data.feesIndex.common && treeNode.data.feesIndex.common.unitFee != 0)
-                //     value = treeNode.data.feesIndex.common.unitFee;
                 treeNode.calcType = treeNodeCalcType.ctCalcBaseValue;
-                treeNode.calcBaseValue = value;
             };
         }
         else if (isBill)                                 // 父清单:汇总子清单的费用类别
@@ -716,8 +729,11 @@ class CalcProgram {
                 let data = {
                     ID: node.data.ID,
                     projectID: me.project.ID(),
+                    type: node.data.type,
                     quantity: node.data.quantity,
                     programID: node.data.programID,
+                    marketUnitFee: node.data.marketUnitFee,
+                    marketTotalFee: node.data.marketTotalFee,
                     fees: node.data.fees
                 };
                 let newData = {'updateType': 'ut_update', 'updateData': data};
@@ -728,6 +744,10 @@ class CalcProgram {
 
         for (let node of treeNodes){delete node.changed};
         projectObj.mainController.refreshTreeNode(treeNodes);
+
+        if (activeSubSheetIs(subSheetIndex.ssiCalcProgram)) {
+            calcProgramObj.showData(me.project.mainTree.selected, false);
+        };
     };
 
 /*    计算所有树结点(分3种情况),并将发生计算改动的结点入库存储。
@@ -755,4 +775,17 @@ class CalcProgram {
         calcNodes(me.project.mainTree.roots);
         me.saveNodes(needSaveNodes);
     };
+
+    // 重新计算叶子清单下的所有子结点:如定额、工料机定额等(calculate算法基于定额、工料机定额的计算结果是正确的,实际上有时它们的计算结果并不是最新的)
+    calcLeafBillChildren(treeNode){
+        let me = this;
+        if (treeNode.children && treeNode.children.length > 0) {
+            let needSaveNodes = [];
+            for (let child of treeNode.children){
+                me.calculate(child, false);
+                if (child.changed) needSaveNodes.push(child);
+            };
+            me.saveNodes(needSaveNodes);
+        };
+    };
 }

+ 1 - 1
web/building_saas/main/js/models/ration.js

@@ -154,7 +154,7 @@ var Ration = {
         ration.prototype.getInsertRationData = function (billsID, preRation) {
             var br = this.getBillsSortRation(billsID);
             var updateData = [];
-            if (preRation) {
+            if (preRation && br.indexOf(preRation) > -1) {  // CSL, 2017-11-28  如果preIndex是-1,表明preRation 是量价。
                 var preIndex = br.indexOf(preRation), i;
                 updateData.push({updateType: 'ut_create', updateData: this.getTempRationData(this.maxRationID() + 1, billsID, preIndex < br.length - 1 ? br[preIndex + 1].serialNo : br[preIndex].serialNo + 1)});
                 for (i = preIndex + 1; i < br.length; i++) {

+ 2 - 2
web/building_saas/main/js/models/volume_price.js

@@ -75,7 +75,7 @@ var VolumePrice = {
             getInsertVolumePriceData (billsID, pre) {
                 let bv = this.getBillsSortVolumePrice(billsID);
                 let updateData = [];
-                if (pre) {
+                if (pre && bv.indexOf(pre) > -1) {
                     let preIndex = bv.indexOf(pre), i;
                     updateData.push({updateType: 'ut_create', updateData: this.getTempVolumePrice(this.maxID() + 1, billsID, preIndex < bv.length - 1 ? bv[preIndex + 1].serialNo : bv[preIndex].serialNo + 1)});
                     for (i = preIndex + 1; i < bv.length; i++) {
@@ -90,7 +90,7 @@ var VolumePrice = {
                 tools.owner.pushNow('insertVolumePrice', [this.getSourceType(), this.getProject().projCounter()], [this.getInsertVolumePriceData(billsID, pre), this.getCounterData()]);
 
                 let bv = this.getBillsSortVolumePrice(billsID), newVP = null;
-                if (pre) {
+                if (pre && bv.indexOf(pre) > -1) {
                     let preIndex = bv.indexOf(pre);
                     newVP = this.getTempVolumePrice(this.getNewID(), billsID, preIndex < bv.length - 1 ? bv[preIndex + 1].serialNo : bv[preIndex].serialNo + 1);
                     this.datas.push(newVP);

+ 17 - 1
web/building_saas/main/js/views/main_tree_col.js

@@ -10,7 +10,11 @@ let MainTreeCol = {
             } else if (node.sourceType === projectObj.project.Ration.getSourceType()) {
                 return '定';
             } else if (node.sourceType === projectObj.project.VolumePrice.getSourceType()) {
-                return '量';
+                if (node.data.type === '人工') return '量人'
+                else if (node.data.type === '材料') return '量材'
+                else if (node.data.type === '机械') return '量机'
+                else if (node.data.type === '主材') return '量主'
+                else if (node.data.type === '设备') return '量设';
             } else if (node.sourceType === projectObj.project.ration_glj.getSourceType()) {
                 return '主';
             }
@@ -23,6 +27,10 @@ let MainTreeCol = {
         }
     },
     readOnly: {
+        type: function (node){
+            return node.sourceType !== projectObj.project.VolumePrice.getSourceType();
+        },
+
         bills: function (node) {
             return node.sourceType === projectObj.project.Bills.getSourceType();
         },
@@ -81,6 +89,14 @@ let MainTreeCol = {
             var names = new GC.Spread.Sheets.CellTypes.ComboBox();
             names.items(projectObj.project.calcProgram.compiledTemplateNames);
             return names;
+        },
+
+        type: function (node) {
+            if (node.sourceType === projectObj.project.VolumePrice.getSourceType()) {
+                let VPType = new GC.Spread.Sheets.CellTypes.ComboBox();
+                VPType.items(["人工","材料","机械","主材","设备"]);
+                return VPType;
+            };
         }
      },
     getEvent: function (eventName) {

+ 2 - 0
web/building_saas/main/js/views/project_info.js

@@ -29,6 +29,8 @@ var projectInfoObj = {
                 //init decimal
                 setDecimal(decimalObj, data.property.decimal);
                 billsQuanDecimal.datas = data.property.billsQuantityDecimal || [billsDecimalView.angleDecimal];
+                basicInfoView.orgDatas = data.property.basicInformation ? basicInfoView.toViewDatas(data.property.basicInformation) : [];
+                projFeatureView.orgDatas = data.property.projectFeature ? projFeatureView.toViewDatas(data.property.projectFeature) : [];
                 $('#fullpath').html(that.getFullPathHtml(that.projectInfo));
             }
         });

+ 401 - 0
web/building_saas/main/js/views/project_property_basicInfo.js

@@ -0,0 +1,401 @@
+/**
+ * Created by Zhong on 2017/11/23.
+ */
+let basicInfoView = {
+    orgDatas: [],//for compare
+    datas: [],//just for view
+    workBook: null,
+    setting:{
+        header: [
+            {name: '属性', dataCode: 'dispName', width: 200, vAlign: 'center', hAlign: 'left'},
+            {name: '值', dataCode: 'value', width: 120, vAlign: 'center', hAlign: 'left'}
+        ],
+        options: {
+            tabStripVisible:  false,
+            allowCopyPasteExcelStyle : false,
+            allowExtendPasteRange: false,
+            allowUserDragDrop : false,
+            allowUserDragFill: false,
+            scrollbarMaxAlign : true
+        },
+        dateRows: [6, 18],
+        locked: {
+            rows: [0, 2, 19, 23, 27],
+            cols: [0]
+        }
+    },
+
+    renderSheetFuc: function (sheet, fuc) {
+        sheet.suspendPaint();
+        sheet.suspendEvent();
+        fuc();
+        sheet.resumePaint();
+        sheet.resumeEvent();
+    },
+
+    setOptions: function (workbook, opts) {
+        for(let opt in opts){
+            workbook.options[opt] = opts[opt];
+        }
+    },
+
+    buildHeader: function (sheet, headers) {
+        let me = basicInfoView;
+        let fuc = function () {
+            sheet.setColumnCount(headers.length);
+            sheet.setRowHeight(0, 40, GC.Spread.Sheets.SheetArea.colHeader);
+            for(let i = 0, len = headers.length; i < len; i++){
+                sheet.setValue(0, i, headers[i].name, GC.Spread.Sheets.SheetArea.colHeader);
+                sheet.setColumnWidth(i, headers[i].width, GC.Spread.Sheets.SheetArea.colHeader);
+            }
+        };
+        me.renderSheetFuc(sheet, fuc);
+    },
+
+    buildSheet: function () {
+        if(!this.workBook){
+            this.workBook = new GC.Spread.Sheets.Workbook($('#basicInfoSpread')[0], {sheetCount: 1});
+            this.setOptions(this.workBook, this.setting.options);
+            this.buildHeader(this.workBook.getActiveSheet(), this.setting.header);
+            this.bindEvent(this.workBook);
+        }
+    },
+
+    bindEvent: function (workBook) {
+        const _events = GC.Spread.Sheets.Events;
+        let sheet = workBook.getActiveSheet();
+        sheet.bind(_events.EditStarting, this.onEditStarting);
+        sheet.bind(_events.EditEnded, this.onEditEnded);
+        sheet.bind(_events.ClipboardPasting, this.onClipboardPasting);
+        sheet.bind(_events.ClipboardPasted, this.onClipboardPasted);
+    },
+
+    showData(datas){
+        let me = basicInfoView;
+        let sheet = this.workBook.getActiveSheet();
+        let cols = this.setting.header;
+        let fuc = function () {
+            sheet.setRowCount(datas.length);
+            me.initTree(sheet, true, datas);
+            sheet.setFormatter(-1, 1, '@');
+            for(let col = 0, cLen = cols.length; col < cLen; col++){
+                sheet.getRange(-1, col, -1, 1).hAlign(GC.Spread.Sheets.HorizontalAlign[cols[col]['hAlign']]);
+                sheet.getRange(-1, col, -1, 1).hAlign(GC.Spread.Sheets.VerticalAlign[cols[col]['vAlign']]);
+                for(let row = 0, rLen = datas.length; row < rLen; row++){
+                    sheet.setValue(row, col, datas[row][cols[col]['dataCode']]);
+                }
+            }
+        };
+        this.renderSheetFuc(sheet, fuc);
+    },
+
+    onEditStarting: function (sender, args) {
+        let me = basicInfoView;
+        if(me.setting.locked.cols.indexOf(args.col) !== -1){
+            args.cancel = true;
+        }
+        //工程专业
+        if(args.col === 1 && me.setting.locked.rows.indexOf(args.row) !== -1){
+            args.cancel = true;
+        }
+    },
+
+    onEditEnded: function (sender, args) {
+        let me = basicInfoView;
+        let v =  args.editingText ? args.editingText.toString().trim() : '';
+        if(args.row < me.datas.length){
+            //date
+            if(me.setting.dateRows.indexOf(args.row) !== -1){
+                if(v.length > 0){
+                    v = me.filtDate(v);
+                    if(!v){
+                        alert('请输入正确的日期格式yyyy-mm-dd');
+                        args.sheet.setValue(args.row, args.col, me.datas[args.row].value ? me.datas[args.row].value : '');
+                        return;
+                    }
+                }
+            }
+            me.datas[args.row].value = v;
+        }
+    },
+
+    onClipboardPasting: function (sender, args) {
+        let me = basicInfoView;
+        if(me.setting.locked.cols.indexOf(args.cellRange.col) !== -1){
+            args.cancel = true;
+        }
+    },
+
+    onClipboardPasted: function (sender, args) {
+        let me = basicInfoView;
+        let items = sheetCommonObj.analyzePasteData(me.setting, args);
+        let recRows = [];
+        for(let i = 0, len = items.length; i < len; i++){
+            let row = i + args.cellRange.row;
+            if(me.setting.locked.rows.indexOf(row) !== -1){
+                recRows.push(row);
+            }
+            else if(me.setting.dateRows.indexOf(row) !== -1){
+                items[i].value = me.filtDate(items[i].value);
+                if(!items[i].value){
+                    recRows.push(row);
+                }
+                else {
+                    me.datas[row].value = items[i].value;
+                }
+            }
+            else {
+                me.datas[row].value = items[i].value;
+            }
+        }
+        if(recRows.length > 0){
+            me.renderSheetFuc(args.sheet, function () {
+                for(let i = 0, len = recRows.length; i < len; i++){
+                    let staticV = me.datas[recRows[i]].value || '';
+                    args.sheet.setValue(recRows[i], args.cellRange.col, staticV);
+                }
+            })
+        }
+    },
+
+    isDef: function (v) {
+        return v !== undefined && v !== null;
+    },
+
+    filtDate: function (v) {
+        let re = /([0-9]{3}[1-9]|[0-9]{2}[1-9][0-9]{1}|[0-9]{1}[1-9][0-9]{2}|[1-9][0-9]{3})-(((0[13578]|1[02])-(0[1-9]|[12][0-9]|3[01]))|((0[469]|11)-(0[1-9]|[12][0-9]|30))|(02-(0[1-9]|[1][0-9]|2[0-8])))/
+        let reDate = re.exec(v);
+        let rst = reDate ? reDate[0] : null;
+        return rst;
+    },
+
+    copyObj: function(obj){
+        let newObj = {};
+        for(let attr in obj){
+            newObj[attr] = obj[attr];
+        }
+        return newObj;
+    },
+
+    initDatas: function (datas) {
+        this.datas = [];
+        for(let i = 0, len = datas.length; i < len; i++){
+            this.datas.push(this.copyObj(datas[i]));
+        }
+    },
+
+    //数据库读到的数据转换为展示的结构
+    toViewDatas: function (datas) {
+        let rst = [];
+        for(let i = 0, len = datas.length; i < len; i++){
+            let items = datas[i].items || null;
+            if(items){
+                rst.push(datas[i]);
+                for(let j = 0, jLen = items.length; j < jLen; j++){
+                    rst.push(items[j]);
+                }
+            }
+        }
+        return rst;
+    },
+
+    //展示的结构转换为入库的结构
+    toSaveDatas: function (datas) {
+        let rst = [];
+        let index = -1;
+        for(let i = 0, len = datas.length; i < len; i++){
+            let items = datas[i].items || null;
+            if(items){
+                delete datas[i].collapsed;
+                datas[i].items = [];
+                rst.push(datas[i]);
+                index++;
+            }
+            else {
+                rst[index]['items'].push(datas[i]);
+            }
+        }
+        return rst;
+    },
+
+    toUpdate: function (orgDatas, newDatas) {
+        if(orgDatas.length !== newDatas.length){
+            return true;
+        }
+        for(let i = 0, len = orgDatas.length; i < len; i++){
+            let orgObj = orgDatas[i], newObj = newDatas[i];
+            if(orgObj.value !== newObj.value){
+                return true;
+            }
+        }
+        return false;
+    },
+
+
+    a_updateInfo: function (datas) {
+        let me = this;
+        let url = '/pm/api/updateProjects',
+            updateData = {
+                updateType: 'update',
+                updateData: {ID: parseInt(scUrlUtil.GetQueryString('project')), 'property.basicInformation': datas}
+            },
+            postData = {
+                user_id: userID,
+                updateData: [updateData]
+            };
+        CommonAjax.post(url, postData, function (rstData) {
+            me.orgDatas = me.toViewDatas(datas);
+        });
+    },
+
+    initTree:function (sheet, init, datas) {
+        sheet.getRange(-1, 0, -1, 1).cellType(this.getTreeNodeCellType(datas));
+        for(let i =0, len = datas.length; i < len; i++){
+            if(datas[i].hasOwnProperty('items')){
+                let collapsed = false;
+                if(init){
+                    datas[i].collapsed=true;
+                    collapsed = true;
+                }else {
+                    collapsed = datas[i].collapsed == undefined ? true : datas[i].collapsed;
+                }
+                sheet.getRange(i+1, -1, datas[i].items.length, -1).visible(!collapsed);
+            }
+        }
+    },
+
+    getTreeNodeCellType:function (data) {
+        var ns = GC.Spread.Sheets;
+        var rectW = 10;
+        var rectH = 10;
+        var margin = 3;
+        function TreeNodeCellType() {
+        }
+
+        function drowRect(ctx,x,y,w,h) {
+            ctx.save();
+            ctx.strokeStyle = "gray";
+            ctx.translate(0.5,0.5);
+            ctx.beginPath();
+            var rectX = x+margin;
+            var rectY =  y+ Math.round(h/2)-rectH/2;
+            ctx.moveTo(rectX, rectY);
+            ctx.lineTo(rectX, rectY+rectH);
+            ctx.lineTo(rectX+rectW, rectY+rectH);
+            ctx.lineTo(rectX+rectW, rectY);
+            ctx.lineTo(rectX, rectY);
+            ctx.moveTo(rectX+rectW, y+Math.round(h/2));
+            ctx.lineTo(rectX+rectW+5, y+Math.round(h/2));
+            ctx.stroke();
+            ctx.restore();
+        }
+
+        function drowSymbol(ctx,x,y,w,h,collapsed) {
+            ctx.save();
+            ctx.strokeStyle = "#000000";
+            ctx.translate(0.5, 0.5);
+            ctx.beginPath();
+            ctx.moveTo(x+margin+2, y+Math.round(h/2));
+            ctx.lineTo(x+margin+8, y+Math.round(h/2));
+            var rectY =  y+ Math.round(h/2)-rectH/2;
+            if(collapsed){
+                ctx.moveTo(x+margin+rectW/2,rectY+2);
+                ctx.lineTo(x+margin+rectW/2,rectY+2+6);
+            }
+            ctx.stroke();
+            ctx.restore();
+        }
+
+        function drowSubItem(ctx,x,y,w,h,offset,nextItem) {
+            offset+=6;
+            ctx.save();
+            ctx.strokeStyle = "gray";
+            ctx.translate(0.5, 0.5);
+            ctx.beginPath();
+            ctx.moveTo(x+offset, y);
+            ctx.lineTo(x+offset, y+Math.round(h/2));
+            offset+=9;
+            ctx.lineTo(x+offset, y+Math.round(h/2));
+            if(nextItem&&!nextItem.hasOwnProperty('items')){
+                ctx.moveTo(x+offset-9, y+Math.round(h/2));
+                ctx.lineTo(x+offset-9, y+h);
+            }
+            ctx.stroke();
+            ctx.restore();
+            return offset;
+        }
+
+        TreeNodeCellType.prototype = new ns.CellTypes.Text();
+        TreeNodeCellType.prototype.paint = function (ctx, value, x, y, w, h, style, options) {
+            if(value!=null){
+                var offset = margin+rectW+6;
+                var recode = data[options.row];
+                if(recode&&recode.hasOwnProperty('items')){
+                    drowRect(ctx,x,y,w,h);
+                    var collapsed = recode.collapsed==undefined?true:recode.collapsed;//options.sheet.getTag(options.row,options.col);
+                    drowSymbol(ctx,x,y,w,h,collapsed);
+                }else if(recode&&!recode.hasOwnProperty('items')){
+                    offset= drowSubItem(ctx,x,y,w,h,offset,data[options.row+1]);
+                    offset+=1;
+                }
+                ctx.fillText(value,x+offset+ctx.measureText(value).width,y+h-5);
+            }
+        };
+        // override getHitInfo to allow cell type get mouse messages
+        TreeNodeCellType.prototype.getHitInfo = function (x, y, cellStyle, cellRect, context) {
+            return {
+                x: x,
+                y: y,
+                row: context.row,
+                col: context.col,
+                cellStyle: cellStyle,
+                cellRect: cellRect,
+                sheetArea: context.sheetArea
+            };
+        }
+        TreeNodeCellType.prototype.processMouseDown = function (hitinfo) {
+            var recode = data[hitinfo.row];
+            if(recode&&recode.hasOwnProperty('items')){
+                var hoffset= hitinfo.cellRect.x+3;
+                if (hitinfo.x > hoffset && hitinfo.x < hoffset + 10){
+                    var collapsed = recode.collapsed==undefined?true:recode.collapsed;
+                    collapsed = !collapsed
+                    recode.collapsed=collapsed;
+                    //hitinfo.sheet.setTag(hitinfo.row,hitinfo.col,collapsed);
+                    hitinfo.sheet.getRange(hitinfo.row+1, -1, recode.items.length, -1).visible(!collapsed);
+                    hitinfo.sheet.invalidateLayout();
+                    hitinfo.sheet.repaint();
+                }
+            }
+        };
+        return new TreeNodeCellType()
+    }
+};
+
+$(document).ready(function () {
+    $('#poj-set').on('shown.bs.modal', function (e) {
+        //init Spread
+        basicInfoView.initDatas(basicInfoView.orgDatas);
+        basicInfoView.buildSheet();
+        basicInfoView.showData(basicInfoView.datas);
+    });
+
+    $('#poj-set').on('hidden.bs.modal', function (e) {
+        //destroy Spread
+        if(basicInfoView.workBook){
+            basicInfoView.workBook.destroy();
+            basicInfoView.workBook = null;
+        }
+        basicInfoView.datas = [];
+    });
+
+    $('#tab_poj-settings-basicInfo').on('shown.bs.tab', function () {
+        basicInfoView.workBook.refresh();
+    });
+
+    $('#property_ok').bind('click', function () {
+        if(basicInfoView.toUpdate(basicInfoView.orgDatas, basicInfoView.datas)){
+            basicInfoView.a_updateInfo(basicInfoView.toSaveDatas(basicInfoView.datas));
+        }
+    });
+});

+ 409 - 0
web/building_saas/main/js/views/project_property_projFeature.js

@@ -0,0 +1,409 @@
+/**
+ * Created by Zhong on 2017/11/24.
+ */
+let projFeatureView = {
+    orgDatas: [],//for compare
+    datas: [],//just for view
+    workBook: null,
+    firstData: {dispName: '工程特征', key: 'projFeature', items: []},//for show
+    setting:{
+        header: [
+            {name: '属性', dataCode: 'dispName', width: 200, vAlign: 'center', hAlign: 'left'},
+            {name: '值', dataCode: 'value', width: 120, vAlign: 'center', hAlign: 'left'}
+        ],
+        options: {
+            tabStripVisible:  false,
+            allowCopyPasteExcelStyle : false,
+            allowExtendPasteRange: false,
+            allowUserDragDrop : false,
+            allowUserDragFill: false,
+            scrollbarMaxAlign : true
+        },
+        combos: [
+            {row: 1, key: 'projType', items: ['住宅', '公共建筑', '厂房', '办公楼']},
+            {row: 2, key: 'structureType', items: ['排架结构', '框架结构', '现浇、框架结构', '预制、砖混结构', '外砖内模', '内浇外挂', '钢结构']},
+            {row: 3, key: 'baseType', items: ['带基', '框排架柱距6m以内', '框排架柱距6m以外', '满基筏式', '满基板式', '满基箱式', '独立基础']},
+            {row: 4, key: 'buildingFeature', items: ['点式', '凹式', '凸式', 'Y式', '其他']}
+        ],
+        numRows: [5, 6, 7, 8, 9, 10, 11, 12, 13, 14],
+        dateRows: [],
+        locked: {
+            rows: [0],
+            cols: [0]
+        }
+    },
+
+    renderSheetFuc: function (sheet, fuc) {
+        sheet.suspendPaint();
+        sheet.suspendEvent();
+        fuc();
+        sheet.resumePaint();
+        sheet.resumeEvent();
+    },
+
+    setOptions: function (workbook, opts) {
+        for(let opt in opts){
+            workbook.options[opt] = opts[opt];
+        }
+    },
+
+    setCombo: function (sheet, row, items) {
+        let combo = new GC.Spread.Sheets.CellTypes.ComboBox();
+        combo.items(items);
+        combo.editable(false);
+        sheet.getCell(row, 1).cellType(combo);
+    },
+
+    getComboItemsByRow: function (row) {
+        for(let i = 0, len = this.setting.combos.length; i < len; i++){
+            if(this.setting.combos[i].row === row){
+                return this.setting.combos[i].items;
+            }
+        }
+        return null;
+    },
+
+    buildHeader: function (sheet, headers) {
+        let me = projFeatureView;
+        let fuc = function () {
+            sheet.options.clipBoardOptions = GC.Spread.Sheets.ClipboardPasteOptions.values;
+            sheet.setColumnCount(headers.length);
+            sheet.setRowHeight(0, 40, GC.Spread.Sheets.SheetArea.colHeader);
+            for(let i = 0, len = headers.length; i < len; i++){
+                sheet.setValue(0, i, headers[i].name, GC.Spread.Sheets.SheetArea.colHeader);
+                sheet.setColumnWidth(i, headers[i].width, GC.Spread.Sheets.SheetArea.colHeader);
+            }
+        };
+        me.renderSheetFuc(sheet, fuc);
+    },
+
+    buildSheet: function () {
+        if(!this.workBook){
+            this.workBook = new GC.Spread.Sheets.Workbook($('#projFeatureSpread')[0], {sheetCount: 1});
+            this.setOptions(this.workBook, this.setting.options);
+            this.buildHeader(this.workBook.getActiveSheet(), this.setting.header);
+            this.bindEvent(this.workBook);
+        }
+    },
+
+    bindEvent: function (workBook) {
+        const _events = GC.Spread.Sheets.Events;
+        let sheet = workBook.getActiveSheet();
+        sheet.bind(_events.EditStarting, this.onEditStarting);
+        sheet.bind(_events.EditEnded, this.onEditEnded);
+        sheet.bind(_events.ClipboardPasting, this.onClipboardPasting);
+        sheet.bind(_events.ClipboardPasted, this.onClipboardPasted);
+    },
+
+    showData(datas){
+        let me = projFeatureView;
+        let sheet = this.workBook.getActiveSheet();
+        let cols = this.setting.header;
+        let fuc = function () {
+            sheet.setRowCount(datas.length);
+            me.initTree(sheet, true, datas);
+            sheet.setFormatter(-1, 1, '@');
+            //setCombo
+            for(let i = 0, len = me.setting.combos.length; i < len; i++){
+                me.setCombo(sheet, me.setting.combos[i].row, me.setting.combos[i].items);
+            }
+            for(let col = 0, cLen = cols.length; col < cLen; col++){
+                sheet.getRange(-1, col, -1, 1).hAlign(GC.Spread.Sheets.HorizontalAlign[cols[col]['hAlign']]);
+                sheet.getRange(-1, col, -1, 1).hAlign(GC.Spread.Sheets.VerticalAlign[cols[col]['vAlign']]);
+                for(let row = 0, rLen = datas.length; row < rLen; row++){
+                    sheet.setValue(row, col, datas[row][cols[col]['dataCode']]);
+                }
+            }
+        };
+        this.renderSheetFuc(sheet, fuc);
+    },
+
+    onEditStarting: function (sender, args) {
+        let me = projFeatureView;
+        if(me.setting.locked.cols.indexOf(args.col) !== -1){
+            args.cancel = true;
+        }
+        if(args.col === 1 && me.setting.locked.rows.indexOf(args.row) !== -1){
+            args.cancel = true;
+        }
+    },
+
+    onEditEnded: function (sender, args) {
+        let me = projFeatureView;
+        let v =  args.editingText ? args.editingText.toString().trim() : '';
+        if(args.row < me.datas.length){
+            if(me.setting.numRows.indexOf(args.row) !== -1){//控制数值
+                if(!me.isNum(v)){
+                    alert('只能输入数值');
+                    args.sheet.setValue(args.row, args.col, me.datas[args.row].value ? me.datas[args.row].value : '');
+                }
+            }
+            me.datas[args.row].value = v;
+        }
+    },
+
+    onClipboardPasting: function (sender, args) {
+        let me = projFeatureView;
+        if(me.setting.locked.cols.indexOf(args.cellRange.col) !== -1){
+            args.cancel = true;
+        }
+    },
+
+    onClipboardPasted: function (sender, args) {
+        let me = projFeatureView;
+        let items = sheetCommonObj.analyzePasteData(me.setting, args);
+        let recRows = [];
+        console.log(args);
+        console.log(items);
+        console.log('enter');
+        if(items.length === 0){
+            return;
+        }
+        for(let i = 0, len = items.length; i < len; i++){
+            let row = i + args.cellRange.row;
+            let comboItems = me.getComboItemsByRow(row);
+            if(me.setting.locked.rows.indexOf(row) !== -1){
+                recRows.push(row);
+            }
+            //粘贴下拉框数据过滤
+            else if(comboItems && comboItems.indexOf(items[i].value) !== -1){
+                recRows.push(row);
+            }
+            else if(me.setting.numRows.indexOf(row) !== -1 && !me.isNum(items[i].value)){
+                recRows.push(row);
+            }
+            else {
+                me.datas[row].value = items[i].value;
+            }
+        }
+        if(recRows.length > 0){
+            me.renderSheetFuc(args.sheet, function () {
+                for(let i = 0, len = recRows.length; i < len; i++){
+                    let staticV = me.datas[recRows[i]].value || '';
+                    args.sheet.setValue(recRows[i], args.cellRange.col, staticV);
+                }
+            })
+        }
+    },
+
+    isDef: function (v) {
+        return v !== undefined && v !== null;
+    },
+    isNum: function(v){
+        return this.isDef(v) && !isNaN(v);
+    },
+
+    copyObj: function(obj){
+        let newObj = {};
+        for(let attr in obj){
+            newObj[attr] = obj[attr];
+        }
+        return newObj;
+    },
+
+    initDatas: function (datas) {
+        this.datas = [];
+        for(let i = 0, len = datas.length; i < len; i++){
+            this.datas.push(this.copyObj(datas[i]));
+        }
+    },
+
+//数据库读到的数据转换为展示的结构
+    toViewDatas: function (datas) {
+        let rst = [];
+        this.firstData.items = datas;
+        rst.push(this.firstData);
+        rst = rst.concat(datas);
+       return rst;
+    },
+
+//展示的结构转换为入库的结构
+    toSaveDatas: function (datas) {
+        let rst = [].concat(datas);
+        rst.splice(0, 1);
+        return rst;
+    },
+
+    toUpdate: function (orgDatas, newDatas) {
+        if(orgDatas.length !== newDatas.length){
+            return true;
+        }
+        for(let i = 0, len = orgDatas.length; i < len; i++){
+            let orgObj = orgDatas[i], newObj = newDatas[i];
+            if(orgObj.value !== newObj.value){
+                return true;
+            }
+        }
+        return false;
+    },
+
+
+    a_updateInfo: function (datas) {
+        let me = this;
+        let url = '/pm/api/updateProjects',
+            updateData = {
+                updateType: 'update',
+                updateData: {ID: parseInt(scUrlUtil.GetQueryString('project')), 'property.projectFeature': datas}
+            },
+            postData = {
+                user_id: userID,
+                updateData: [updateData]
+            };
+        CommonAjax.post(url, postData, function (rstData) {
+            me.orgDatas = me.toViewDatas(datas);
+        });
+    },
+
+    initTree:function (sheet, init, datas) {
+        sheet.getRange(-1, 0, -1, 1).cellType(this.getTreeNodeCellType(datas));
+        for(let i =0, len = datas.length; i < len; i++){
+            if(datas[i].hasOwnProperty('items')){
+                let collapsed = false;
+                if(init){
+                    datas[i].collapsed=true;
+                    collapsed = true;
+                }else {
+                    collapsed = datas[i].collapsed == undefined ? true : datas[i].collapsed;
+                }
+                sheet.getRange(i+1, -1, datas[i].items.length, -1).visible(!collapsed);
+            }
+        }
+    },
+
+    getTreeNodeCellType:function (data) {
+        var ns = GC.Spread.Sheets;
+        var rectW = 10;
+        var rectH = 10;
+        var margin = 3;
+        function TreeNodeCellType() {
+        }
+
+        function drowRect(ctx,x,y,w,h) {
+            ctx.save();
+            ctx.strokeStyle = "gray";
+            ctx.translate(0.5,0.5);
+            ctx.beginPath();
+            var rectX = x+margin;
+            var rectY =  y+ Math.round(h/2)-rectH/2;
+            ctx.moveTo(rectX, rectY);
+            ctx.lineTo(rectX, rectY+rectH);
+            ctx.lineTo(rectX+rectW, rectY+rectH);
+            ctx.lineTo(rectX+rectW, rectY);
+            ctx.lineTo(rectX, rectY);
+            ctx.moveTo(rectX+rectW, y+Math.round(h/2));
+            ctx.lineTo(rectX+rectW+5, y+Math.round(h/2));
+            ctx.stroke();
+            ctx.restore();
+        }
+
+        function drowSymbol(ctx,x,y,w,h,collapsed) {
+            ctx.save();
+            ctx.strokeStyle = "#000000";
+            ctx.translate(0.5, 0.5);
+            ctx.beginPath();
+            ctx.moveTo(x+margin+2, y+Math.round(h/2));
+            ctx.lineTo(x+margin+8, y+Math.round(h/2));
+            var rectY =  y+ Math.round(h/2)-rectH/2;
+            if(collapsed){
+                ctx.moveTo(x+margin+rectW/2,rectY+2);
+                ctx.lineTo(x+margin+rectW/2,rectY+2+6);
+            }
+            ctx.stroke();
+            ctx.restore();
+        }
+
+        function drowSubItem(ctx,x,y,w,h,offset,nextItem) {
+            offset+=6;
+            ctx.save();
+            ctx.strokeStyle = "gray";
+            ctx.translate(0.5, 0.5);
+            ctx.beginPath();
+            ctx.moveTo(x+offset, y);
+            ctx.lineTo(x+offset, y+Math.round(h/2));
+            offset+=9;
+            ctx.lineTo(x+offset, y+Math.round(h/2));
+            if(nextItem&&!nextItem.hasOwnProperty('items')){
+                ctx.moveTo(x+offset-9, y+Math.round(h/2));
+                ctx.lineTo(x+offset-9, y+h);
+            }
+            ctx.stroke();
+            ctx.restore();
+            return offset;
+        }
+
+        TreeNodeCellType.prototype = new ns.CellTypes.Text();
+        TreeNodeCellType.prototype.paint = function (ctx, value, x, y, w, h, style, options) {
+            if(value!=null){
+                var offset = margin+rectW+6;
+                var recode = data[options.row];
+                if(recode&&recode.hasOwnProperty('items')){
+                    drowRect(ctx,x,y,w,h);
+                    var collapsed = recode.collapsed==undefined?true:recode.collapsed;//options.sheet.getTag(options.row,options.col);
+                    drowSymbol(ctx,x,y,w,h,collapsed);
+                }else if(recode&&!recode.hasOwnProperty('items')){
+                    offset= drowSubItem(ctx,x,y,w,h,offset,data[options.row+1]);
+                    offset+=1;
+                }
+                ctx.fillText(value,x+offset+ctx.measureText(value).width,y+h-5);
+            }
+        };
+        // override getHitInfo to allow cell type get mouse messages
+        TreeNodeCellType.prototype.getHitInfo = function (x, y, cellStyle, cellRect, context) {
+            return {
+                x: x,
+                y: y,
+                row: context.row,
+                col: context.col,
+                cellStyle: cellStyle,
+                cellRect: cellRect,
+                sheetArea: context.sheetArea
+            };
+        }
+        TreeNodeCellType.prototype.processMouseDown = function (hitinfo) {
+            var recode = data[hitinfo.row];
+            if(recode&&recode.hasOwnProperty('items')){
+                var hoffset= hitinfo.cellRect.x+3;
+                if (hitinfo.x > hoffset && hitinfo.x < hoffset + 10){
+                    var collapsed = recode.collapsed==undefined?true:recode.collapsed;
+                    collapsed = !collapsed
+                    recode.collapsed=collapsed;
+                    //hitinfo.sheet.setTag(hitinfo.row,hitinfo.col,collapsed);
+                    hitinfo.sheet.getRange(hitinfo.row+1, -1, recode.items.length, -1).visible(!collapsed);
+                    hitinfo.sheet.invalidateLayout();
+                    hitinfo.sheet.repaint();
+                }
+            }
+        };
+        return new TreeNodeCellType()
+    },
+
+};
+
+$(document).ready(function () {
+
+    $('#poj-set').on('shown.bs.modal', function (e) {
+        //init Spread
+        projFeatureView.initDatas(projFeatureView.orgDatas);
+        projFeatureView.buildSheet();
+        projFeatureView.showData(projFeatureView.datas);
+    });
+
+    $('#poj-set').on('hidden.bs.modal', function (e) {
+        //destroy Spread
+        if(projFeatureView.workBook){
+            projFeatureView.workBook.destroy();
+            projFeatureView.workBook = null;
+        }
+        projFeatureView.datas = [];
+    });
+
+    $('#tab_poj-settings-projFeature').on('shown.bs.tab', function () {
+        projFeatureView.workBook.refresh();
+    });
+
+    $('#property_ok').bind('click', function () {
+        if(projFeatureView.toUpdate(projFeatureView.orgDatas, projFeatureView.datas)){
+           projFeatureView.a_updateInfo(projFeatureView.toSaveDatas(projFeatureView.datas));
+        }
+    });
+});

+ 31 - 51
web/building_saas/main/js/views/project_view.js

@@ -19,11 +19,7 @@ var projectObj = {
 
         // CSL.2017.07.25
         if (activeSubSheetIs(subSheetIndex.ssiCalcProgram)) {
-            if (node.sourceType === project.Bills.getSourceType() || node.sourceType === project.Ration.getSourceType()) {
-                calcProgramObj.showData(node);
-            } else {
-                calcProgramObj.clearData();
-            };
+            calcProgramObj.showData(node);
         };
         //zhong 2017-9-1 特征及内容
         if(pageCCOprObj.active){
@@ -267,25 +263,23 @@ var projectObj = {
         if (value !== calcFees.getFee(node.data, fieldName)) {
             if (fieldName === 'code') {
                 projectObj.updateCode(node, value);
-            } else if (fieldName === 'quantity' && project.quantity_detail.quantityEditChecking(value,node,fieldName)) {
-                if (value) {value = value.toDecimal(projectObj.project.Decimal.common.quantity);};
-                node.data.quantity = value;
-                project.calcProgram.calculate(node);
-                project.calcProgram.saveNode(node);
-                // projectObj.updateAndReCalculate(node, fieldName, value);
-            } else if (fieldName === 'feesIndex.common.unitFee') {
-                if (value) {value = value.toDecimal(projectObj.project.Decimal.common.unitFee);};
-                node.data.feesIndex.common.unitFee = value;
-                project.calcProgram.calculate(node);
-                project.calcProgram.saveNode(node);
-                // projectObj.updateAndReCalculate(node, fieldName, value);
-            } else if (fieldName === 'programID') {
-                node.data.programID = value;
+            }
+            else if(fieldName ==='feeRate'){
+                project.FeeRate.updateFeeRateFromBills(value,node,fieldName);
+            }
+            else if (fieldName === 'quantity' || fieldName === 'marketUnitFee' || fieldName === 'programID' || fieldName === 'type'){
+                if (fieldName === 'quantity' && project.quantity_detail.quantityEditChecking(value,node,fieldName)) {
+                    if (value) {value = value.toDecimal(projectObj.project.Decimal.common.quantity)};
+                } else if (fieldName === 'marketUnitFee') {
+                    if (value) {value = parseFloat(value).toDecimal(projectObj.project.Decimal.common.unitFee)};
+                };
+
+                node.changed = true;
+                node.data[fieldName] = value;
                 project.calcProgram.calculate(node);
                 project.calcProgram.saveNode(node);
-            } else if(fieldName ==='feeRate'){
-                project.FeeRate.updateFeeRateFromBills(value,node,fieldName);
-            }else {
+            }
+            else {
                 if (node.sourceType === project.Bills.getSourceType()) {
                     project.Bills.updateField(node.source, fieldName, value, true);
                 } else if (node.sourceType === project.Ration.getSourceType()) {
@@ -367,7 +361,7 @@ var projectObj = {
                     }
                     if (col.data.cellType && Object.prototype.toString.apply(col.data.cellType) === "[object String]") {
                         let getCellType = MainTreeCol.getEvent(col.data.cellType);
-                        col.data.cellType = getCellType();
+                        col.data.cellType = getCellType;
                     }
                     if (col.data.digit && Object.prototype.toString.apply(col.data.digit) === "[object String]") {
                         col.data.decimal = that.project.getDecimal(col.data.digit);
@@ -420,20 +414,13 @@ var projectObj = {
                     disabled: function () {
                         var selected = project.mainTree.selected;
                         if (selected) {
-                            if (selected.sourceType === project.Ration.getSourceType()) {
-                                return false;
-                            } else if (selected.sourceType === project.Bills.getSourceType()) {
-                                if (selected.source.children.length === 0) {
-                                    return selected.children.length !== 0 ? selected.firstChild().sourceType !== project.Ration.getSourceType() : false;
-                                } else {
-                                    return true;
-                                }
-                            } else if (selected.sourceType === project.VolumePrice.getSourceType()) {
-                                return true;
-                            };
-                        } else {
-                            return true;
-                        }
+                            if (            // CSL, 2017-11-28
+                                selected.sourceType === project.Ration.getSourceType() ||
+                                selected.sourceType === project.VolumePrice.getSourceType() ||
+                                (selected.sourceType === project.Bills.getSourceType() && selected.source.children.length === 0)
+                               ) return false
+                            else return true
+                        } else return true
                     },
                     callback: function (key, opt) {
                         ProjectController.addRation(project, controller);
@@ -445,20 +432,13 @@ var projectObj = {
                     disabled: function () {
                         var selected = project.mainTree.selected;
                         if (selected) {
-                            if (selected.sourceType === project.Ration.getSourceType()) {
-                                return true;
-                            } else if (selected.sourceType === project.Bills.getSourceType()) {
-                                if (selected.source.children.length === 0) {
-                                    return selected.children.length !== 0 ? selected.firstChild().sourceType !== project.VolumePrice.getSourceType() : false;
-                                } else {
-                                    return true;
-                                }
-                            } else if (selected.sourceType === project.VolumePrice.getSourceType()) {
-                                return false;
-                            };
-                        } else {
-                            return true;
-                        }
+                            if (            // CSL, 2017-11-28
+                            selected.sourceType === project.Ration.getSourceType() ||
+                            selected.sourceType === project.VolumePrice.getSourceType() ||
+                            (selected.sourceType === project.Bills.getSourceType() && selected.source.children.length === 0)
+                            ) return false
+                            else return true
+                        } else return true
                     },
                     callback: function (key, opt) {
                         ProjectController.addVolumePrice(project, controller);

+ 1 - 5
web/building_saas/main/js/views/sub_view.js

@@ -92,11 +92,7 @@ $("#linkJSCX").click(function(){        // 计算程序
     if (!projectObj.mainController.tree.selected)
         projectObj.mainController.tree.selected = projectObj.mainController.tree.firstNode();
     let sel = projectObj.mainController.tree.selected;
-    if (sel.sourceType === projectObj.project.Bills.getSourceType() || sel.sourceType === projectObj.project.Ration.getSourceType()) {
-        calcProgramObj.showData(sel);
-    } else {
-        calcProgramObj.clearData();
-    };
+    calcProgramObj.showData(sel);
     gljOprObj.activeTab='#linkJSCX';
 });
 

+ 2 - 1
web/building_saas/pm/js/pm_main.js

@@ -83,7 +83,8 @@ let ProjTreeSetting = {
                     if (node.data.projType === projectType.tender) {
                         $('a:eq(1)', td).bind('click', function () {
                             BeforeOpenProject(node.id(), {'fullFolder': GetFullFolder(node.parent)}, function () {
-                                window.location.href = '/main?project=' + node.id();
+                                //window.location.href = '/main?project=' + node.id();
+                                window.open('/main?project=' + node.id());
                             });
                             return false;
                         });

+ 1 - 1
web/building_saas/report/html/rpt_main.html

@@ -16,7 +16,7 @@
                 <div class="print-toolsbar">
                     <div class="panel">
                         <div class="panel-body">
-                            <button class="btn btn-secondary btn-sm" type="button">
+                            <button class="btn btn-secondary btn-sm" type="button" onclick="rptHeaderObj.getExcel();">
                                 <i class="fa fa-print"></i><br>
                                 打印 <span id="checkCount" class="badge badge-primary">5</span>
                             </button>

+ 85 - 26
web/building_saas/report/js/jpc_output.js

@@ -17,26 +17,26 @@ let JpcCanvasOutput = {
 
         function private_setupAreaH(area, type, fontAngle, dftFontHeight, outputPoint) {
             let lType = type;
-            if (type != "left" && type != "right" && type != "center") lType = "left";
+            if (type !== "left" && type !== "right" && type !== "center") lType = "left";
             switch (lType) {
                 case "left":
-                    if (fontAngle == JV.VERTICAL_ANGLE) {
+                    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) {
+                    } 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) {
+                    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) {
+                    } 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 || fontAngle == JV.ANTI_VERTICAL_ANGLE) {
+                    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";
@@ -45,26 +45,26 @@ let JpcCanvasOutput = {
         }
         function private_setupAreaV(area, type, fontAngle, dftFontHeight, outputPoint) {
             let lType = type;
-            if (type != "top" && type != "bottom" && type != "center") lType = "top";
+            if (type !== "top" && type !== "bottom" && type !== "center") lType = "top";
             switch (lType) {
                 case "top":
-                    if (fontAngle == JV.VERTICAL_ANGLE) {
+                    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) {
+                    } 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) {
+                    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) {
+                    } 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];
                     break;
                 case "center":
-                    if (fontAngle == JV.VERTICAL_ANGLE) {
+                    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) {
+                    } 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;
                     break;
@@ -74,31 +74,75 @@ let JpcCanvasOutput = {
             let dftFontHeight = 12;
             let output = [];
             if (font) {
-                dftFontHeight = 1 * font[JV.FONT_PROPS[1]];
+                dftFontHeight = parseFloat(font[JV.FONT_PROPS[1]]);
                 let dftOthers = "";
                 let dftFontBold = font[JV.FONT_PROPS[3]];
-                if (dftFontBold && dftFontBold == 'T') {
+                if (dftFontBold && dftFontBold === 'T') {
                     dftOthers = "bold " + dftOthers ;
                 }
                 let dftFontItalic = font[JV.FONT_PROPS[4]];
-                if (dftFontItalic && dftFontItalic == 'T') {
+                if (dftFontItalic && dftFontItalic === 'T') {
                     dftOthers = dftOthers + "italic ";
                 }
                 ctx.font = dftOthers + dftFontHeight + "px " + font[JV.PROP_NAME];
             }
             if (control) {
-                private_setupAreaH(area, control.Horizon, font.FontAngle, dftFontHeight, output);
-                private_setupAreaV(area, control.Vertical, font.FontAngle, dftFontHeight, output);
+                private_setupAreaH(area, control.Horizon, parseInt(font.FontAngle), dftFontHeight, output);
+                private_setupAreaV(area, control.Vertical, parseInt(font.FontAngle), dftFontHeight, output);
             } else {
-                private_setupAreaH(area, "left", font.FontAngle, dftFontHeight, output);
-                private_setupAreaV(area, "bottom", font.FontAngle, dftFontHeight, output);
+                private_setupAreaH(area, "left", parseInt(font.FontAngle), dftFontHeight, output);
+                private_setupAreaV(area, "bottom", parseInt(font.FontAngle), 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];
-            if (font.FontAngle != "0") {
+            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];
             }
+
+            function private_drawUnderline() {
+                //A. 暂不支持角度; B. 坐标已经translate
+                //1. 计算下划线的相关坐标
+                let width = ctx.measureText(val).width;
+                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();
+            }
             if (font.FontAngle === JV.VERTICAL_ANGLE) {
                 ctx.rotate(Math.PI/2);
             } else if (font.FontAngle === JV.ANTI_VERTICAL_ANGLE) {
@@ -121,8 +165,19 @@ let JpcCanvasOutput = {
         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 control = controls[cell[JV.PROP_CONTROL]];
+                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 (parseFloat(cell[JV.PROP_VALUE]) === 0.0) {
                         values = [""];
@@ -207,9 +262,7 @@ let JpcCanvasOutput = {
     },
     drawPageBorder: function(rptTpl, canvas, resolution) {
         let me = this;
-        let size = rptTpl[JV.NODE_PAGE_INFO][JV.NODE_PAGE_SIZE].slice(0);
-        size[0] = Math.round(resolution[0] * size[0]);
-        size[1] = Math.round(resolution[0] * size[1]);
+        let size = me.getReportSizeInPixel(rptTpl, resolution);
 
         let ctx = canvas.getContext("2d");
         ctx.save();
@@ -227,5 +280,11 @@ let JpcCanvasOutput = {
         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 = 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;
     }
 };

+ 3 - 1
web/building_saas/report/js/jpc_output_value_define.js

@@ -39,12 +39,14 @@ let JV = {
     PROP_COLOR: "Color",
     FONT_PROPS: ["Name", "FontHeight", "FontColor", "FontBold", "FontItalic", "FontUnderline", "FontStrikeOut", "FontAngle"],
 
-    OUTPUT_OFFSET: [2,1,2,3],
+    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"
 };

+ 60 - 22
web/building_saas/report/js/rpt_main.js

@@ -67,34 +67,36 @@ let zTreeOprObj = {
         let canvas = document.getElementById("rptCanvas");
         if (treeNode.nodeType === TPL_TYPE_TEMPLATE && treeNode.refId > 0) {
             let params = {};
-            let pageSize = "A4";
+            let pageSize = rptHeaderObj.getCurrentPageSize();
             params.user_id = userID;
             params.pageSize = pageSize;
             params.rpt_tpl_id = treeNode.refId;
             params.prj_id = projectInfoObj.projectInfo.ID;
-            CommonAjax.postEx("report_api/getReport", params, 5000, true, function(result){
+            me.currentNode = treeNode;
+            CommonAjax.postEx("report_api/getReport", params, 5000, true,
+                function(result){
                     let pageRst = result;
                     if (pageRst) {
                         me.currentRptPageRst = pageRst;
                         me.maxPages = pageRst.items.length;
                         me.currentPage = 1;
-                        if (pageSize === "A4") {
-                            canvas.width = 1200;
-                            canvas.height = 900;
-                        } else if (pageSize === "A3") {
-                            canvas.width = 1880;
-                            canvas.height = 1200;
+                        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(0, canvas);
+                        me.showPage(1, canvas);
                     }
                 }, null, null
             );
         }
     },
-    showPage: function (pageStep, canvas) {
+    showPage: function (pageNum, canvas) {
         let me = zTreeOprObj;
-        if (me.currentPage + pageStep >= 1 && me.currentPage + pageStep <= me.maxPages) {
-            me.currentPage = me.currentPage + pageStep;
+        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);
@@ -104,13 +106,17 @@ let zTreeOprObj = {
 
 let canvasOprObj = {
     canvasOnMouseMove: function (event) {
-        let x = event.offsetX - JpcCanvasOutput.offsetX,
-            canvas = event.originalTarget
-        ;
-        if (x < 300) {
-            canvas.style.cursor = "e-resize";
-        } else if ((canvas.width - x) < 300) {
-            canvas.style.cursor = "w-resize";
+        let x = event.offsetX - JpcCanvasOutput.offsetX, canvas = event.originalTarget;
+        if (x < 80) {
+            canvas.style.cursor = "url(/web/building_saas/img/FirstPageSimple.cur), auto";
+        } else if (x < 200) {
+            // canvas.style.cursor = "e-resize";
+            canvas.style.cursor = "url(/web/building_saas/img/PreviousPageSimple.cur), auto";
+        } else if ((canvas.width - x) < 80) {
+            canvas.style.cursor = "url(/web/building_saas/img/LastPageSimple.cur), auto";
+        } else if ((canvas.width - x) < 200) {
+            // canvas.style.cursor = "w-resize";
+            canvas.style.cursor = "url(/web/building_saas/img/NextPageSimple.cur), auto";
         } else {
             canvas.style.cursor = "";
         }
@@ -119,10 +125,42 @@ let canvasOprObj = {
         let x = event.offsetX - JpcCanvasOutput.offsetX,
             //y = event.offsetY - JpcCanvasOutput.offsetY,
             canvas = event.originalTarget;
-        if (x < 300) {
-            zTreeOprObj.showPage(-1, canvas);
-        } else if ((canvas.width - x) < 300) {
+        if (x < 80) {
             zTreeOprObj.showPage(1, canvas);
+        } else if (x < 200) {
+            zTreeOprObj.showPage(zTreeOprObj.currentPage - 1, canvas);
+        } else if ((canvas.width - x) < 80) {
+            zTreeOprObj.showPage(zTreeOprObj.maxPages, canvas);
+        } else if ((canvas.width - x) < 200) {
+            zTreeOprObj.showPage(zTreeOprObj.currentPage + 1, canvas);
+        }
+    }
+};
+
+let rptHeaderObj = {
+    getCurrentPageSize: function() {
+        let rst = "A4";
+        //
+        return rst;
+    },
+    getCurrentOrientation: function() {
+        let rst = "横向";
+        //
+        return rst;
+    },
+    getCurrentReportOption: function() {
+        //
+    },
+    getExcel: function() {
+        let me = rptHeaderObj;
+        //目前只支持当前打开报表
+        //zTreeOprObj.currentRptPageRst
+        if (zTreeOprObj.currentNode && zTreeOprObj.currentNode.refId) {
+            let orgRptName = zTreeOprObj.currentNode.name;
+            orgRptName = orgRptName.replace('【', '').replace('】','').replace('-','_');
+            let url =  "/report_api/getExcel/" + projectInfoObj.projectInfo.ID + "/" + zTreeOprObj.currentNode.refId + "/" +
+                me.getCurrentPageSize() + "/" + orgRptName + "/" + false + "/" + 'normal';
+            window.location = url;//这里不能使用get方法跳转,否则下载不成功
         }
     }
 };