Sfoglia il codice sorgente

Merge branch 'master' of http://192.168.1.12:3000/SmartCost/ConstructionCost

TonyKang 8 anni fa
parent
commit
710a0f3aee
33 ha cambiato i file con 1928 aggiunte e 114 eliminazioni
  1. 22 0
      logs/log4js.json
  2. 80 0
      logs/log_helper.js
  3. 314 14
      modules/fee_rates/controllers/feerate_template.js
  4. 30 3
      modules/fee_rates/facade/fee_rates_facade.js
  5. 9 0
      modules/main/controllers/bills_controller.js
  6. 22 0
      modules/main/models/bills.js
  7. 21 1
      modules/main/models/bills_sub_schemas.js
  8. 1 0
      modules/main/routes/bills_route.js
  9. 2 2
      modules/pm/controllers/new_proj_controller.js
  10. 32 1
      modules/pm/controllers/pm_controller.js
  11. 2 2
      modules/pm/models/project_model.js
  12. 5 1
      modules/pm/models/project_schema.js
  13. 6 4
      modules/pm/models/templates/bills_template_model.js
  14. 3 1
      modules/pm/models/templates/schemas/bills_template.js
  15. 1 19
      modules/pm/routes/pm_route.js
  16. 97 0
      modules/users/models/engineering_lib_model.js
  17. 12 17
      modules/users/models/schema/compilation.js
  18. 47 0
      modules/users/models/schema/engineering_lib.js
  19. 1 0
      package.json
  20. 146 3
      public/calc_util.js
  21. 2 2
      public/web/sheet/sheet_common.js
  22. 3 0
      public/web/tree_sheet/tree_sheet_helper.js
  23. 2 0
      server.js
  24. 210 0
      test/calculation/test_analyzer.js
  25. 6 5
      test/tmp_data/bills_grid_setting.js
  26. 2 2
      web/building_saas/complementary_glj_lib/js/gljComponent.js
  27. 16 0
      web/building_saas/main/html/main.html
  28. 695 0
      web/building_saas/main/js/views/character_content_view.js
  29. 8 0
      web/building_saas/main/js/views/main_tree_col.js
  30. 17 2
      web/building_saas/main/js/views/project_view.js
  31. 42 1
      web/building_saas/main/js/views/sub_view.js
  32. 1 1
      web/building_saas/pm/html/project-management.html
  33. 71 33
      web/building_saas/pm/js/pm_main.js

+ 22 - 0
logs/log4js.json

@@ -0,0 +1,22 @@
+{
+  "customBaseDir" :"../logs/",
+  "customDefaultAtt" :{
+    "type": "dateFile",
+    "alwaysIncludePattern": false,
+    "pattern":".yyyy-MM-dd hhhr"
+  },
+  "appenders": {
+    "console": { "type": "console" },
+    "logInfo":{"filename":"info/ConstructionCost.log"},
+    "logDebug":{"filename":"debug/ConstructionCost.log"},
+    "logWarn":{"filename":"warn/ConstructionCost.log"},
+    "logErr":{"filename":"err/ConstructionCost.log"}
+  },
+  "categories": {
+    "default": { "appenders": ["console"], "level": "debug"},
+    "logInfo":{ "appenders": ["logInfo","console"], "level": "debug"},
+    "logDebug":{ "appenders": ["logDebug","console"], "level": "debug"},
+    "logWarn":{ "appenders": ["logWarn"], "level": "debug"},
+    "logErr":{ "appenders": ["logErr"], "level": "debug"}
+  }
+}

+ 80 - 0
logs/log_helper.js

@@ -0,0 +1,80 @@
+/**
+ * Created by chen on 2017/9/4.
+ */
+let helper = {};
+module.exports.logger = helper;
+
+let log4js = require('log4js');
+let fs = require("fs");
+let path = require("path");
+
+// 加载配置文件
+let objConfig = JSON.parse(fs.readFileSync(__dirname+"/log4js.json", "utf8"));
+
+// 加载基础配置
+if(objConfig.appenders){
+    let baseDir = objConfig["customBaseDir"];
+    let defaultAtt = objConfig["customDefaultAtt"];
+    for(let key in objConfig.appenders){
+        var item = objConfig.appenders[key];
+        if(item["type"] == "console")
+            continue;
+        if(defaultAtt != null){
+            for(let att in defaultAtt){
+                if(item[att] == null)
+                    item[att] = defaultAtt[att];
+            }
+        }
+        if(baseDir != null){
+            if(item["filename"] == null)
+                item["filename"] = baseDir;
+            else
+                item["filename"] = baseDir + item["filename"];
+        }
+    }
+}
+
+
+log4js.configure(objConfig);
+
+let logDebug = log4js.getLogger('logDebug');
+let logInfo = log4js.getLogger('logInfo');
+let logWarn = log4js.getLogger('logWarn');
+let logErr = log4js.getLogger('logErr');
+
+console.log = logInfo.info.bind(logInfo);//把控制台信息输出到文件中
+
+helper.debug = function(msg){
+    if(msg == null)
+        msg = "";
+    logDebug.debug(msg);
+};
+
+helper.info = function(msg){
+    if(msg == null)
+        msg = "";
+    logInfo.info(msg);
+};
+
+helper.warn = function(msg){
+    if(msg == null)
+        msg = "";
+    logWarn.warn(msg);
+};
+
+helper.err = function(msg, exp){
+    if(msg == null)
+        msg = "";
+    if(exp != null)
+        msg += "\r\n" + exp;
+    logErr.error(msg);
+};
+
+// 配合express用的方法
+module.exports.use = function(app) {
+    //页面请求日志, level用auto时,默认级别是WARN
+    app.use(log4js.connectLogger(logDebug, {level:'debug', format:':method :url'}));
+}
+
+
+

+ 314 - 14
modules/fee_rates/controllers/feerate_template.js

@@ -238,21 +238,138 @@ module.exports={
                 "ParentID": 1,
                 "name": "机械土石方",
                 "rate": 10.82,
-                "memo": ""
+                "memo": "",
+                "subFeeRate": {
+                    "recodes": [
+                        {
+                            "ID": 1,
+                            "name": "地区",
+                            "optionList": [
+                                {
+                                    "name": "在市区",
+                                    "value": "005",
+                                    "selected": true
+                                },
+                                {
+                                    "name": "县城镇",
+                                    "value": "006",
+                                    "selected": false
+                                },
+                                {
+                                    "name": "不在市区、县城镇",
+                                    "value": "007",
+                                    "selected": false
+                                }
+                            ]
+                        }
+                    ],
+                    "valueMaps": [
+                        {
+                            "ID": "005",
+                            "value": 10.82
+                        },
+                        {
+                            "ID": "006",
+                            "value": 10.56
+                        },
+                        {
+                            "ID": "007",
+                            "value": 10.07
+                        }
+                    ]
+                }
             },
             {
                 "ID": 104,
                 "ParentID": 1,
                 "name": "仿古建筑工程",
                 "rate": 12,
-                "memo": ""
+                "memo": "",
+                "subFeeRate": {
+                    "recodes": [
+                        {
+                            "ID": 1,
+                            "name": "地区",
+                            "optionList": [
+                                {
+                                    "name": "在市区",
+                                    "value": "005",
+                                    "selected": true
+                                },
+                                {
+                                    "name": "县城镇",
+                                    "value": "006",
+                                    "selected": false
+                                },
+                                {
+                                    "name": "不在市区、县城镇",
+                                    "value": "007",
+                                    "selected": false
+                                }
+                            ]
+                        }
+                    ],
+                    "valueMaps": [
+                        {
+                            "ID": "005",
+                            "value": 10.82
+                        },
+                        {
+                            "ID": "006",
+                            "value": 10.56
+                        },
+                        {
+                            "ID": "007",
+                            "value": 10.07
+                        }
+                    ]
+                }
             },
             {
                 "ID": 105,
                 "ParentID": 1,
                 "name": "建筑修缮工程",
                 "rate": 12.47,
-                "memo": ""
+                "memo": "",
+                "subFeeRate": {
+                    "recodes": [
+                        {
+                            "ID": 1,
+                            "name": "地区",
+                            "optionList": [
+                                {
+                                    "name": "在市区",
+                                    "value": "005",
+                                    "selected": true
+                                },
+                                {
+                                    "name": "县城镇",
+                                    "value": "006",
+                                    "selected": false
+                                },
+                                {
+                                    "name": "不在市区、县城镇",
+                                    "value": "007",
+                                    "selected": false
+                                }
+                            ]
+                        }
+                    ],
+                    "valueMaps": [
+                        {
+                            "ID": "005",
+                            "value": 10.82
+                        },
+                        {
+                            "ID": "006",
+                            "value": 10.56
+                        },
+                        {
+                            "ID": "007",
+                            "value": 10.07
+                        }
+                    ]
+                }
             },
             {
                 "ID": 106,
@@ -375,14 +492,110 @@ module.exports={
                 "ParentID": 2,
                 "name": "建筑工程",
                 "rate": 0.45,
-                "memo": ""
+                "memo": "",
+                "subFeeRate": {
+                    "recodes": [
+                        {
+                            "ID": 1,
+                            "name": "工程分类",
+                            "optionList": [
+                                {
+                                    "name": "一类工程",
+                                    "value": "001",
+                                    "selected": true
+                                },
+                                {
+                                    "name": "二类工程",
+                                    "value": "002",
+                                    "selected": false
+                                },
+                                {
+                                    "name": "三类工程",
+                                    "value": "003",
+                                    "selected": false
+                                },
+                                {
+                                    "name": "四类工程",
+                                    "value": "004",
+                                    "selected": false
+                                }
+                            ]
+                        }
+                    ],
+                    "valueMaps": [
+                        {
+                            "ID": "001",
+                            "value": 0.45
+                        },
+                        {
+                            "ID": "002",
+                            "value": 0.4
+                        },
+                        {
+                            "ID": "003",
+                            "value": 0.35
+                        },
+                        {
+                            "ID": "004",
+                            "value": 0.3
+                        }
+                    ]
+                }
             },
             {
                 "ID": 202,
                 "ParentID": 2,
                 "name": "市政工程",
                 "rate": 0.4,
-                "memo": ""
+                "memo": "",
+                "subFeeRate": {
+                    "recodes": [
+                        {
+                            "ID": 1,
+                            "name": "工程分类",
+                            "optionList": [
+                                {
+                                    "name": "一类工程",
+                                    "value": "001",
+                                    "selected": true
+                                },
+                                {
+                                    "name": "二类工程",
+                                    "value": "002",
+                                    "selected": false
+                                },
+                                {
+                                    "name": "三类工程",
+                                    "value": "003",
+                                    "selected": false
+                                },
+                                {
+                                    "name": "四类工程",
+                                    "value": "004",
+                                    "selected": false
+                                }
+                            ]
+                        }
+                    ],
+                    "valueMaps": [
+                        {
+                            "ID": "001",
+                            "value": 0.4
+                        },
+                        {
+                            "ID": "002",
+                            "value": 0.35
+                        },
+                        {
+                            "ID": "003",
+                            "value": 0.3
+                        },
+                        {
+                            "ID": "004",
+                            "value": 0.25
+                        }
+                    ]
+                }
             },
             {
                 "ID": 203,
@@ -410,7 +623,55 @@ module.exports={
                 "ParentID": 2,
                 "name": "炉窑砌筑工程",
                 "rate": 0.4,
-                "memo": ""
+                "memo": "",
+                "subFeeRate": {
+                    "recodes": [
+                        {
+                            "ID": 1,
+                            "name": "工程分类",
+                            "optionList": [
+                                {
+                                    "name": "一类工程",
+                                    "value": "001",
+                                    "selected": true
+                                },
+                                {
+                                    "name": "二类工程",
+                                    "value": "002",
+                                    "selected": false
+                                },
+                                {
+                                    "name": "三类工程",
+                                    "value": "003",
+                                    "selected": false
+                                },
+                                {
+                                    "name": "四类工程",
+                                    "value": "004",
+                                    "selected": false
+                                }
+                            ]
+                        }
+                    ],
+                    "valueMaps": [
+                        {
+                            "ID": "001",
+                            "value": 0.4
+                        },
+                        {
+                            "ID": "002",
+                            "value": 0.35
+                        },
+                        {
+                            "ID": "003",
+                            "value": 0.3
+                        },
+                        {
+                            "ID": "004",
+                            "value": 0.25
+                        }
+                    ]
+                }
             },
             {
                 "ID": 3,
@@ -423,42 +684,42 @@ module.exports={
                 "ID": 301,
                 "ParentID": 3,
                 "name": "建筑工程",
-                "rate": 16.03,
+                "rate": 4.87,
                 "memo": ""
             },
             {
                 "ID": 302,
                 "ParentID": 3,
                 "name": "市政工程",
-                "rate": 16.33,
+                "rate": 3.61,
                 "memo": ""
             },
             {
                 "ID": 303,
                 "ParentID": 3,
                 "name": "机械土石方",
-                "rate": 15.5,
+                "rate": 2.15,
                 "memo": ""
             },
             {
                 "ID": 304,
                 "ParentID": 3,
                 "name": "仿古建筑工程",
-                "rate": 12,
+                "rate": 2.84,
                 "memo": ""
             },
             {
                 "ID": 305,
                 "ParentID": 3,
                 "name": "建筑修缮工程",
-                "rate": 12.47,
+                "rate": 2.84,
                 "memo": ""
             },
             {
                 "ID": 306,
                 "ParentID": 3,
                 "name": "炉窑砌筑工程",
-                "rate": 10.8,
+                "rate": 3.61,
                 "memo": ""
             },
             {
@@ -604,7 +865,46 @@ module.exports={
                 "ParentID": 4,
                 "name": "炉窑砌筑工程",
                 "rate": 4.9,
-                "memo": ""
+                "memo": "",
+                "subFeeRate": {
+                    "recodes": [
+                        {
+                            "ID": 1,
+                            "name": "工程分类",
+                            "optionList": [
+                                {
+                                    "name": "一类工程",
+                                    "value": "001",
+                                    "selected": false
+                                },
+                                {
+                                    "name": "二类工程",
+                                    "value": "002",
+                                    "selected": false
+                                },
+                                {
+                                    "name": "三类工程",
+                                    "value": "003",
+                                    "selected": true
+                                }
+                            ]
+                        }
+                    ],
+                    "valueMaps": [
+                        {
+                            "ID": "001",
+                            "value": 8.94
+                        },
+                        {
+                            "ID": "002",
+                            "value": 7.72
+                        },
+                        {
+                            "ID": "003",
+                            "value": 4.9
+                        }
+                    ]
+                }
             },
             {
                 "ID": 5,
@@ -616,7 +916,7 @@ module.exports={
                     "recodes": [
                         {
                             "ID": 1,
-                            "name": "工程分类",
+                            "name": "纳税方式",
                             "optionList": [
                                 {
                                     "name": "一般纳税人",

+ 30 - 3
modules/fee_rates/facade/fee_rates_facade.js

@@ -11,6 +11,7 @@ let project_feerate_temp = mongoose.model('project_feerate_temp');
 let consts = require('../../main/models/project_consts');
 let _=require("lodash");
 let template = require('../controllers/feerate_template');
+let logger = require("../../../logs/log_helper").logger;
 const uuidV1 = require('uuid/v1');
 
 module.exports={
@@ -21,7 +22,8 @@ module.exports={
     checkFeeRateName:checkFeeRateName,
     getChangeInfo:getChangeInfo,
     changeFeeRateFileFromCurrent:changeFeeRateFileFromCurrent,
-    changeFeeRateFileFromOthers:changeFeeRateFileFromOthers
+    changeFeeRateFileFromOthers:changeFeeRateFileFromOthers,
+    newFeeRateFile:newFeeRateFile
 };
 let operationMap={
     'ut_create':create_fee_rate,
@@ -184,7 +186,7 @@ async function getFeeRateData(projectID) {
     }
     try {
         //
-        let tem = await project_feerate_temp.findOne({projectID:projectID});//暂时取tem表的记录,写死为99
+        let tem = await project_feerate_temp.findOne({projectID:projectID});//暂时取tem表的记录
         if (tem){
             let feeRateData = await feeRateFileModel.findOne({'ID':tem.feeRateFileID,deleteInfo:null});
             let feeRate = await feeRateModel.findOne({ID:feeRateData.feeRateID});
@@ -195,7 +197,7 @@ async function getFeeRateData(projectID) {
         }else {
             result.datas=[];
         }
-
+        logger.info("get fee rate data");
 
     }catch (err){
         console.log(err);
@@ -252,6 +254,31 @@ async function changeFeeRateStandard(jdata){
     return doc;
 }
 
+async function newFeeRateFile(data){
+  /*  data = {
+   libID:2,
+   rootProjectID:113,
+   name:'费率1'
+    }*/
+    let template = await std_fee_rate_lib_model.findOne({"ID":data.libID});
+    let newFeeRate = {};
+    newFeeRate.ID =uuidV1();
+    newFeeRate.rates=template.rates;
+    await feeRateModel.create(newFeeRate);
+    let doc={
+        libID:data.libID,
+        libName:template.libName,
+        feeRateID: newFeeRate.ID,
+        rootProjectID:data.rootProjectID,
+        name:data.name
+    };
+    doc.ID = uuidV1();
+    await feeRateFileModel.create(doc);
+    return doc;
+}
+
+
+
 async function checkFeeRateName(jdata) {
     let data = JSON.parse(jdata);
     let count = await feeRateFileModel.count({rootProjectID:data.rootProjectID,name:data.name,deleteInfo:null});

+ 9 - 0
modules/main/controllers/bills_controller.js

@@ -39,6 +39,15 @@ module.exports = {
                 callback(req, res, err, message, null);
             }
         });
+    },
+    //zhong 2017-9-1
+    updateCharacterContent: function (req, res) {
+        let findSet = JSON.parse(req.body.findSet),
+            updateObj = JSON.parse(req.body.updateObj),
+            txtObj = JSON.parse(req.body.txtObj);
+        billsData.updateCharacterContent(findSet, updateObj, txtObj, function (err, message) {
+            callback(req, res, err, message, null);
+        });
     }
 
 };

+ 22 - 0
modules/main/models/bills.js

@@ -43,6 +43,12 @@ let billsSchema = new Schema({
     tenderTargetPrice: String, // Decimal
     tenderTargetUnitPrice: String, // Decimal
     tenderTargetUnitPrice: String, // Decimal
+    //工作内容//zhong 2017-8-31
+    jobContentText: String, //清单工作内容列显示文本, 减少第一次拉数据时的循环次数
+    jobContent: [subSchema.jobContentSchema],
+    //项目特征
+    itemCharacterText: String,//清单项目特征列显示文本
+    itemCharacter: [subSchema.itemCharacterSchema],
     // 费用字段
     fees: [subSchema.feesSchema],
     // 标记字段
@@ -108,6 +114,22 @@ class billsModel extends baseModel {
             callback(0, '', {lowID: lowID, highID: highID});
         });
     };
+    //zhong 2017-9-1
+    updateCharacterContent(findSet, updateObj, txtObj, callback) {
+        let updateSet = {};
+        updateSet[updateObj.field] = updateObj.updateArr;
+        if(txtObj && typeof txtObj !== 'undefined'){
+            updateSet[txtObj.field] = txtObj.text;
+        }
+       bills.update(findSet, updateSet, function (err) {
+           if(err){
+               callback(1, '更新失败');
+           }
+           else{
+               callback(0, '更新成功');
+           }
+       });
+    };
 };
 
 module.exports = new billsModel();

+ 21 - 1
modules/main/models/bills_sub_schemas.js

@@ -25,4 +25,24 @@ var changesSchema = new Schema({
     deleteGLJ:String
 });
 
-module.exports = {feesSchema: feesSchema, flagsSchema: flagsSchema};
+//add by Zhong2017-8-30
+//清单工作内容字段
+let jobContentSchema = new Schema({
+    content: String, //工作内容
+    serialNo: Number, //排序用
+    isChecked: Boolean //是否勾选(输出)
+}, {_id: false});
+//特征值字段
+let eigenvalueSchema = new Schema({
+    value: String,
+    isSelected: Boolean //判断哪个特征值被选中
+}, {_id: false});
+//项目特征字段
+let itemCharacterSchema = new Schema({
+    character: String, //特征
+    serialNo: Number, //排序用
+    eigenvalue: [eigenvalueSchema],//特征值
+    isChecked: Boolean //是否勾选(输出)
+}, {_id: false});
+
+module.exports = {feesSchema: feesSchema, flagsSchema: flagsSchema, jobContentSchema: jobContentSchema, itemCharacterSchema: itemCharacterSchema};

+ 1 - 0
modules/main/routes/bills_route.js

@@ -9,6 +9,7 @@ module.exports = function (app) {
     billsRouter.post('/getData', billsController.getData);
     billsRouter.post('/getItemTemplate', billsController.getItemTemplate);
     billsRouter.post('/allocIDs', billsController.allocIDs);
+    billsRouter.post('/updateCharacterContent', billsController.updateCharacterContent)//特征及内容更新 zhong 2017-9-1
     app.use('/bills', billsRouter);
 };
 

+ 2 - 2
modules/pm/controllers/new_proj_controller.js

@@ -9,12 +9,12 @@ let async = require('async');
 import BillsTemplateModel from "../models/templates/bills_template_model";
 
 module.exports = {
-    copyTemplateData: async function (valuationId, newProjID, callback) {
+    copyTemplateData: async function (property, newProjID, callback) {
         async.parallel([
             async function (cb) {
                 // 获取清单模板数据
                 let billsTemplateModel = new BillsTemplateModel();
-                let templateData = JSON.stringify(await billsTemplateModel.getTemplateDataForNewProj(valuationId));
+                let templateData = JSON.stringify(await billsTemplateModel.getTemplateDataForNewProj(property.valuation, property.engineering));
                 let billsDatas = JSON.parse(templateData);
                 billsDatas.forEach(function (template) {
                     template.projectID = newProjID;

+ 32 - 1
modules/pm/controllers/pm_controller.js

@@ -3,6 +3,8 @@
  */
 let ProjectsData = require('../models/project_model').project;
 let projType = require('../models/project_model').projType;
+const engineering = require("../../common/const/engineering");
+let EngineeringLibModel = require("../../users/models/engineering_lib_model");
 
 //统一回调函数
 let callback = function(req, res, err, message, data){
@@ -10,7 +12,6 @@ let callback = function(req, res, err, message, data){
 };
 
 
-
 module.exports = {
     checkRight: function (req, res) {
         let data = JSON.parse(req.body.data);
@@ -91,5 +92,35 @@ module.exports = {
         ProjectsData.getNewProjectID(data.count, function (err, message, data) {
             callback(req, res, err, message, data);
         });
+    },
+    // 项目管理首页
+    index: async function(request, response) {
+        // 获取编办信息
+        let sessionCompilation = request.session.sessionCompilation;
+
+
+        // 清单计价
+        let billValuation = sessionCompilation.bill_valuation !== undefined ?
+            sessionCompilation.bill_valuation : [];
+
+        // 获取标准库数据
+        let engineeringLibModel = new EngineeringLibModel();
+        billValuation = await engineeringLibModel.getLib(billValuation);
+
+        // 定额计价
+        let rationValuation = sessionCompilation.ration_valuation !== undefined ?
+            sessionCompilation.ration_valuation : [];
+        rationValuation = await engineeringLibModel.getLib(rationValuation);
+
+        let renderData = {
+            userAccount: request.session.userAccount,
+            userID: request.session.sessionUser.ssoId,
+            compilationData: sessionCompilation,
+            billValuation: JSON.stringify(billValuation),
+            rationValuation: JSON.stringify(rationValuation),
+            engineeringList: JSON.stringify(engineering.List)
+        };
+
+        response.render('building_saas/pm/html/project-management.html', renderData);
     }
 }

+ 2 - 2
modules/pm/models/project_model.js

@@ -37,7 +37,7 @@ ProjectsDAO.prototype.getUserProject = function (userId, ProjId, callback) {
 }
 
 ProjectsDAO.prototype.updateUserProjects = function(userId, datas, callback){
-    let data, project, updateLength = 0, hasError = false, deleteInfo = null, valuationId = "599256ba700b1b340c03805e", i, newProject;
+    let data, project, updateLength = 0, hasError = false, deleteInfo = null, i, newProject;
     let updateAll = function (err) {
             if (!err){
                 updateLength += 1;
@@ -61,7 +61,7 @@ ProjectsDAO.prototype.updateUserProjects = function(userId, datas, callback){
                 newProject = new Projects(data.updateData);
                 newProject.save(function (err, result) {
                     if (!err && result._doc.projType === projectType.tender) {
-                        newProjController.copyTemplateData(valuationId, newProject.ID, updateAll);
+                        newProjController.copyTemplateData(data.updateData.property, newProject.ID, updateAll);
                     } else {
                         updateAll(err);
                     }

+ 5 - 1
modules/pm/models/project_schema.js

@@ -17,7 +17,11 @@ let ProjectSchema = new Schema({
     "recentDateTime": Date,
     "createDateTime": Date,
     "deleteInfo": deleteSchema,
-    'fullFolder': Array
+    'fullFolder': Array,
+    "property": {
+        type: Schema.Types.Mixed,
+        default: {}
+    }
 });
 
 module.exports = mongoose.model(collectionName, ProjectSchema);

+ 6 - 4
modules/pm/models/templates/bills_template_model.js

@@ -20,12 +20,13 @@ class BillsTemplateModel extends BaseModel {
     /**
      * 获取计价类别对应的清单模板
      * @param valuationId
+     * @param engineering
      * @returns {*}
      */
-    async getTemplateData (valuationId) {
+    async getTemplateData (valuationId, engineering) {
         // 筛选字段
         let field = {_id: 1, valuationId: 1, ID: 1, ParentID: 1, NextSiblingID: 1, code: 1, name: 1, unit: 1, flags: 1};
-        let data = await this.findDataByCondition({valuationId: valuationId}, field, false);
+        let data = await this.findDataByCondition({valuationId: valuationId, engineering: engineering}, field, false);
 
         return data === null ? [] : data;
     }
@@ -33,12 +34,13 @@ class BillsTemplateModel extends BaseModel {
     /**
      * 新建项目时,获取计价类别对应的清单模板
      * @param valuationId
+     * @param engineering
      * @returns {*}
      */
-    async getTemplateDataForNewProj (valuationId) {
+    async getTemplateDataForNewProj (valuationId, engineering) {
         // 筛选字段
         let field = {_id: 0, ID: 1, ParentID: 1, NextSiblingID: 1, code: 1, name: 1, unit: 1, flags: 1};
-        let data = await this.findDataByCondition({valuationId: valuationId}, field, false);
+        let data = await this.findDataByCondition({valuationId: valuationId, engineering: engineering}, field, false);
 
         return data === null ? [] : data;
     }

+ 3 - 1
modules/pm/models/templates/schemas/bills_template.js

@@ -28,7 +28,9 @@ let BillsTemplateSchema = {
         default: []
     },
     // 所属计价ID
-    valuationId: String
+    valuationId: String,
+    // 工程专业
+    engineering: Number
 };
 
 let model = mongoose.model(collectionName, new Schema(BillsTemplateSchema, {versionKey: false, collection: collectionName}));

+ 1 - 19
modules/pm/routes/pm_route.js

@@ -5,28 +5,10 @@
 
 let express = require('express');
 let pmController = require('./../controllers/pm_controller');
-const engineering = require("../../common/const/engineering");
 
 module.exports = function (app) {
 
-    app.get('/pm', function(req, res){
-        // 获取编办信息
-        let sessionCompilation = req.session.sessionCompilation;
-        // 清单计价
-        let billValuation = sessionCompilation.bill_valuation !== undefined ? sessionCompilation.bill_valuation : [];
-
-        // 定额计价
-        let rationValuation = sessionCompilation.ration_valuation !== undefined ? sessionCompilation.ration_valuation : [];
-        let renderData = {
-            userAccount: req.session.userAccount,
-            userID: req.session.sessionUser.ssoId,
-            compilationData: sessionCompilation,
-            billValuation: JSON.stringify(billValuation),
-            rationValuation: JSON.stringify(rationValuation),
-            engineeringList: JSON.stringify(engineering.List)
-        };
-        res.render('building_saas/pm/html/project-management.html', renderData);
-    });
+    app.get('/pm', pmController.index);
 
     let pmRouter = express.Router();
 

+ 97 - 0
modules/users/models/engineering_lib_model.js

@@ -0,0 +1,97 @@
+/**
+ * 计价规则标准库业务逻辑
+ *
+ * @author CaiAoLin
+ * @date 2017/8/31
+ * @version
+ */
+import BaseModel from "../../common/base/base_model";
+import EngineeringLibSchema from "./schema/engineering_lib";
+
+class EngineeringLibModel extends BaseModel {
+    /**
+     * 构造函数
+     *
+     * @return {void}
+     */
+    constructor() {
+        let parent = super();
+        parent.model = EngineeringLibSchema;
+        parent.init();
+    }
+
+    /**
+     * 获取标准库数据
+     *
+     * @param {Object} data
+     * @return {Promise}
+     */
+    async getLib(data) {
+        if (data.length <= 0) {
+            return data;
+        }
+
+        let engineeringTemp = {};
+        let engineeringLibIdList = [];
+        // 整理出数据库需要查找的id,一次查找
+        for(let valuation of data) {
+            if (valuation === null) {
+                continue;
+            }
+            for(let engineering of valuation.engineering_list) {
+                if (engineeringTemp[valuation._id] === undefined) {
+                    engineeringTemp[valuation._id] = [engineering.engineering_id];
+                }else {
+                    engineeringTemp[valuation._id].push(engineering.engineering_id);
+                }
+                engineeringLibIdList.push(engineering.engineering_id);
+            }
+        }
+        // console.log(data);
+        // console.log(engineeringTemp);
+
+        // 查找对应的id数据
+        let condition = {_id: {"$in": engineeringLibIdList}};
+        let engineeringLibList = await this.findDataByCondition(condition, null, false, '_id');
+
+        // 组合计价规则id对应专业工程标准库数据
+        let engineeringLib = {};
+        for(let index in engineeringTemp) {
+            if (engineeringTemp[index].length <= 0) {
+                continue;
+            }
+            for(let libId of engineeringTemp[index]) {
+                if (engineeringLibList[libId] === undefined) {
+                    continue;
+                }
+                if (engineeringLib[index] === undefined) {
+                    engineeringLib[index] = [engineeringLibList[libId] ];
+                }else {
+                    engineeringLib[index].push(engineeringLibList[libId] );
+                }
+            }
+        }
+
+        // 替换计价规则数据
+        let valuationData = JSON.parse(JSON.stringify(data));
+        for(let valuation of valuationData) {
+            if (valuation === null) {
+                continue;
+            }
+            for(let engineering of valuation.engineering_list) {
+                if (engineering === null || engineeringLib[valuation._id] === undefined) {
+                    continue;
+                }
+                for (let tmp of engineeringLib[valuation._id]) {
+                    if (tmp._id.toString() === engineering.engineering_id.toString()) {
+                        engineering.lib = tmp;
+                    }
+                }
+            }
+        }
+        return valuationData;
+    }
+
+}
+
+module.exports = EngineeringLibModel;

+ 12 - 17
modules/users/models/schema/compilation.js

@@ -9,27 +9,22 @@ import mongoose from "mongoose";
 
 let Schema = mongoose.Schema;
 let collectionName = 'compilation';
-let childrenSchema = new Schema({
-    // 计价名称
-    name: String,
-    // 工程专业
+let engineeringListSchema = new Schema({
+    // 工程专业id
     engineering: {
-        type: Number,
-        default: ''
-    },
-    // 标准清单
-    bill_lib: {
-        type: Schema.Types.Mixed,
-        default: []
+        type: Number
     },
-    // 定额库
-    ration_lib: {
+    engineering_id: {
         type: Schema.Types.Mixed,
         default: []
-    },
-    // 工料机库
-    glj_lib: {
-        type: Schema.Types.Mixed,
+    }
+}, {_id: false});
+let childrenSchema = new Schema({
+    // 计价名称
+    name: String,
+    // 工程专业列表
+    engineering_list: {
+        type: [engineeringListSchema],
         default: []
     },
     // 是否启用

+ 47 - 0
modules/users/models/schema/engineering_lib.js

@@ -0,0 +1,47 @@
+/**
+ * 计价规则数据结构
+ *
+ * @author CaiAoLin
+ * @date 2017/8/31
+ * @version
+ */
+import mongoose from "mongoose";
+
+let Schema = mongoose.Schema;
+let collectionName = 'engineering_lib';
+let modelSchema = {
+    // 标准清单
+    bill_lib: {
+        type: Schema.Types.Mixed,
+        default: []
+    },
+    // 定额库
+    ration_lib: {
+        type: Schema.Types.Mixed,
+        default: []
+    },
+    // 工料机库
+    glj_lib: {
+        type: Schema.Types.Mixed,
+        default: []
+    },
+    // 列设置
+    main_tree_col: {
+        type: Schema.Types.Mixed,
+        default: {
+            "emptyRows":3,
+            "headRows":0,
+            "treeCol": 0,
+            "headRowHeight":[],
+            "cols":[]
+        }
+    },
+    // 费率标准库
+    fee_lib: {
+        type: Schema.Types.Mixed,
+        default: []
+    }
+};
+let model = mongoose.model(collectionName, new Schema(modelSchema, {versionKey: false, collection: collectionName}));
+export {model as default, collectionName as collectionName};
+

+ 1 - 0
package.json

@@ -29,6 +29,7 @@
     "ua-parser-js": "^0.7.14",
     "uuid": "^3.1.0",
     "ioredis":"^3.1.4",
+    "log4js":"~2.3.3",
     "pdfkit": "^0.8.2"
   },
   "scripts": {

+ 146 - 3
public/calc_util.js

@@ -1,7 +1,7 @@
 /**
  * Created by Tony on 2017/6/21.
- * Modified by CSL, 2017-08-01
- * 引入多套计算程序、费率同步、人工系数同步、改进基数计算、费字段映射等
+ * Modified by CSL, 2017-08-01 引入多套计算程序、费率同步、人工系数同步、改进基数计算、费字段映射等。
+ * added by CSL, 2017-09-01 增加公式解析对象analyzer,用于解析用户修改公式、自定义表达式
  */
 
 let executeObj = {
@@ -39,6 +39,147 @@ let executeObj = {
     }
 };
 
+let analyzer = {
+    calcTemplate: null,
+    success: true,
+
+    standard: function(expr){
+        let str = expr;
+        str = str.replace(/\s/g, "");               // 去空格、去中文空格
+        str = str.replace(/(/g, "(");              // 中文括号"("换成英文括号"("
+        str = str.replace(/)/g, ")");              // 中文括号")"换成英文括号")"
+        str = str.replace(/f/g, "F");               // f换成F
+        return str;
+    },
+
+    analyzeCalcBase: function(expr){
+        // 前提:必须无空格、无特殊符号
+        function getCalcBase(expr){
+            let base = '',
+                iPos1 = -1, iPos2 = -1;
+            for (let i = 0; i < expr.length; i++) {
+                if (expr[i] === '['){
+                    iPos1 = i;
+                }
+                else if (iPos1 != -1 && expr[i]===']'){
+                    iPos2 = i;
+                };
+
+                if (iPos1 != -1 && iPos2 != -1){
+                    base = expr.slice(iPos1, iPos2 + 1);
+                    break;
+                }
+            };
+            return base;
+        };
+        function calcBaseToIDExpr(base){
+            /*// for test. 公路模式,基数到ID
+            let id = -1;
+            if (base == '[人工费]'){
+                id = 111;
+            }
+            else if (base == '[材料费]'){
+                id = 222;
+            }
+            else if (base == '[机械费]'){
+                id = 333;
+            }
+            else id = "错误";
+
+            return "@('" + id + "')";*/
+            let baseValue = base.slice(1, -1);
+            return "base('" + baseValue + "')";
+        };
+
+        while (expr.indexOf('[') > 0) {
+            let base = getCalcBase(expr);
+            let id = calcBaseToIDExpr(base);
+            let baseValue = base.slice(1, -1);   // []会给下面的正则带来干扰,这里去掉
+            var pattBase =new RegExp(baseValue, "g");
+            expr = expr.replace(pattBase, id);
+            expr = expr.replace(/\[base\('/g, "base('");      // [@('       [base('
+            expr = expr.replace(/'\)\]/g, "')");        // ')]
+        };
+
+        return expr;
+    },
+
+    analyzeLineRef: function(expr){
+        let me = this;
+        function isOperator(char){
+            var operator = "+-*/()";
+            return operator.indexOf(char) > -1;
+        };
+        function lineNumToID(lineNum){
+            if (lineNum > me.calcTemplate.calcItems.length){
+                me.success = false;
+                return '越界';
+            }
+            else{
+                let id = me.calcTemplate.calcItems[lineNum - 1].ID;
+                return id;
+            }
+        };
+        // 前提:必须无空格、无特殊符号、标准大写F
+        function getSection(expr){
+            let section = '',
+                iPos1 = -1, iPos2 = -1;
+            for (let i = 0; i < expr.length; i++) {
+                if (expr[i] === 'F'){
+                    iPos1 = i;
+                }
+                else if (iPos1 != -1 && isOperator(expr[i])){
+                    iPos2 = i;
+                }
+                else if (iPos1 != -1 && i == expr.length - 1){
+                    iPos2 = i + 1;
+
+                };
+                if (iPos1 != -1 && iPos2 != -1){
+                    section = expr.slice(iPos1, iPos2);
+                    break;
+                }
+            };
+            return section;
+        };
+        function sectionToIDExpr(section){
+            if (section){
+                let lineNum = section.slice(1);
+                if (isNaN(lineNum)){
+                    me.success = false;
+                    return '错误';      // 这里的返回提示不能加上section,因为会无限循环
+                }
+                else
+                    return "@('" + lineNumToID(lineNum) + "')";
+            }
+            else return '';
+        };
+
+        while (expr.indexOf('F') > 0) {
+            let sec = getSection(expr);
+            let id = sectionToIDExpr(sec);
+            var pattSec =new RegExp(sec, "g");
+            expr = expr.replace(pattSec, id);
+        };
+        return expr;
+    },
+
+    analyzeUserExpr: function(calcTemplate, calcItem){
+        let me = this;
+        me.calcTemplate = calcTemplate;
+        let expr = calcItem.dispExpr;
+        // 标准化:处理特殊字符、中文符号、大小写
+        expr = me.standard(expr);
+        calcItem.dispExpr = expr;
+        // 先换掉计算基数
+        expr = me.analyzeCalcBase(expr);
+        // 再换掉行引用
+        expr = me.analyzeLineRef(expr);
+        calcItem.expression = expr;
+        return me.success;
+    }
+};
+
 class Calculation {
     // 先编译公用的基础数据
     compilePublics(feeRates, labourCoes, feeTypes, calcBases){
@@ -234,4 +375,6 @@ class Calculation {
             }
         }
     }
-}
+};
+
+export default analyzer;

+ 2 - 2
public/web/sheet/sheet_common.js

@@ -141,10 +141,10 @@ var sheetCommonObj = {
         //me.shieldAllCells(sheet);
     },
     analyzePasteData: function(setting, pastedInfo) {
-        var rst = [], propId = 0, preStrIdx = 0, itemObj = {};
+        var rst = [], propId = pastedInfo.cellRange.col, preStrIdx = 0, itemObj = {};//propId = 0 to proId = pastedInfo.cellRange.col, update by zhong
         for (var i = 0; i < pastedInfo.pasteData.text.length; i++) {
             if (pastedInfo.pasteData.text[i] === "\n") {
-                propId = 0;
+                propId = pastedInfo.cellRange.col;//propId = 0 to proId = pastedInfo.cellRange.col, update by zhong
                 preStrIdx = i + 1;
                 rst.push(itemObj);
                 if (i < pastedInfo.pasteData.text.length - 1) {

+ 3 - 0
public/web/tree_sheet/tree_sheet_helper.js

@@ -112,6 +112,9 @@ var TREE_SHEET_HELPER = {
                 } else {
                     cell.value(getFieldText2());
                 }
+                if (colSetting.data.cellType) {
+                    cell.cellType(colSetting.data.cellType);
+                }
                 if (colSetting.readOnly) {
                     if (Object.prototype.toString.apply(colSetting.readOnly) === "[object Function]") {
                         cell.locked(colSetting.readOnly(node));

+ 2 - 0
server.js

@@ -3,6 +3,7 @@ let express = require('express');
 let config = require("./config/config.js");
 let fileUtils = require("./modules/common/fileUtils");
 let dbm = require("./config/db/db_manager");
+let log = require("./logs/log_helper");
 ///config.setToLocalDb();
 config.setupDb(process.env.NODE_ENV);
 
@@ -32,6 +33,7 @@ cfgCacheUtil.setupDftCache();
 
 let app = express();
 let _rootDir = __dirname;
+log.use(app);
 app.use(express.static(_rootDir));
 
 app.set('views', path.join(__dirname, 'web'));

+ 210 - 0
test/calculation/test_analyzer.js

@@ -0,0 +1,210 @@
+/**
+ * Created by CSL on 2017-09-01.
+ */
+var test = require('tape');
+import analyzer from '../../public/calc_util';
+
+test('解析测试', function(t){
+    let calcTemplate = {
+        ID: 1,
+        name: "建筑工程",
+        calcItems: [
+            {
+                ID: "101",
+                code: "1",
+                name: "基价直接工程费",
+                fieldName: "baseDirect",
+                dispExpr: "F2+F5+F6+F10",
+                expression: "@('2') + @('5') + @('6') + @('10')",
+                compiledExpr: "",
+                statement: "基价人工费+基价材料费+基价机械费+未计价材料费"
+            },
+            {
+                ID: "102",
+                code: "1.1",
+                name: "基价人工费",
+                fieldName: "baseLabour",
+                dispExpr: "F3+F4",
+                expression: "@('3') + @('4')",
+                compiledExpr: "",
+                statement: "定额基价人工费+定额人工单价(基价)调整"
+            },
+            {
+                ID: "103",
+                code: "1.1.1",
+                name: "定额基价人工费",
+                fieldName: "rationBaseLabour",
+                dispExpr: "[定额基价人工费]",
+                expression: "base('定额基价人工费').toFixed(2)",
+                compiledExpr: "",
+                statement: "定额基价人工费"
+            },
+            {
+                ID: "104",
+                code: "1.1.2",
+                name: "定额人工单价(基价)调整",
+                fieldName: "rationLabourFixed",
+                dispExpr: "F3*(L-1)",
+                expression: "@('3') * (L-1)",
+                labourCoeID: 22,
+                compiledExpr: "",
+                statement: "定额基价人工费*[定额人工单价(基价)调整系数-1]",
+                memo: "渝建发(2013)51"
+            },
+            {
+                ID: "105",
+                code: "1.2",
+                name: "基价材料费",
+                fieldName: "baseMaterial",
+                dispExpr: "[定额基价材料费]",
+                expression: "base('定额基价材料费')",
+                compiledExpr: "",
+                statement: "定额基价材料费"
+            },
+            {
+                ID: "106",
+                code: "1.3",
+                name: "基价机械费",
+                fieldName: "baseMachine",
+                dispExpr: "F7+F9",
+                expression: "@('7') + @('9')",
+                compiledExpr: "",
+                statement: "定额基价机械费+定额机上人工单价(基价)调整"
+            },
+            {
+                ID: "107",
+                code: "1.3.1",
+                name: "定额基价机械费",
+                fieldName: "rationBaseMachine",
+                dispExpr: "[定额基价机械费]",
+                expression: "base('定额基价机械费')",
+                compiledExpr: "",
+                statement: "定额基价机械费"
+            },
+            {
+                ID: "108",
+                code: "1.3.1.1",
+                name: "其中:定额基价机上人工费",
+                fieldName: "rationBaseMachineLabour",
+                dispExpr: "[定额基价机上人工费]",
+                expression: "base('定额基价机上人工费')",
+                compiledExpr: "",
+                statement: "定额基价机上人工费"
+            },
+            {
+                ID: "109",
+                code: "1.3.2",
+                name: "定额机上人工单价(基价)调整",
+                fieldName: "rationBaseMachineLabourFixed",
+                dispExpr: "F8*(L-1)",
+                expression: "@('8') * (L-1)",
+                labourCoeID: 24,
+                compiledExpr: "",
+                statement: "定额基价机上人工费*[定额机上人工单价(基价)调整系数-1]"
+            },
+            {
+                ID: "110",
+                code: "1.4",
+                name: "未计价材料费",
+                fieldName: "unPriceMaterial",
+                dispExpr: "[主材费]+[设备费]",
+                expression: "base('主材费') + base('设备费')",
+                compiledExpr: "",
+                statement: "主材费+设备费"
+            },
+            {
+                ID: "111",
+                code: "2",
+                name: "企业管理费",
+                fieldName: "manageFee",
+                dispExpr: "F3+F5+F7",
+                feeRateID: 101,
+                expression: "@('3') + @('5') + @('7')",
+                compiledExpr: "",
+                statement: "定额基价人工费",
+                memo: "渝建发[2014]27号"
+            },
+            {
+                ID: "112",
+                code: "3",
+                name: "利润",
+                fieldName: "profit",
+                dispExpr: "F3+F5+F7",
+                feeRateID: 301,
+                expression: "@('3') + @('5') + @('7')",
+                compiledExpr: "",
+                statement: "定额基价人工费"
+            },
+            {
+                ID: "113",
+                code: "4",
+                name: "风险因素",
+                fieldName: "risk",
+                dispExpr: "F3+F5+F7",
+                feeRateID: 701,
+                expression: "@('3') + @('5') + @('7')",
+                compiledExpr: "",
+                statement: "定额基价人工费",
+                memo: "同定额包干费"
+            },
+            {
+                ID: "114",
+                code: "5",
+                name: "人材机价差",
+                fieldName: "lmmDiff",
+                dispExpr: "F15+F16+F17",
+                expression: "@('15') + @('16') + @('17')",
+                compiledExpr: "",
+                statement: "人工费价差+材料费价差+机械费价差"
+            },
+            {
+                ID: "115",
+                code: "5.1",
+                name: "人工费价差",
+                fieldName: "labourDiff",
+                dispExpr: "[人工费价差]",
+                expression: "base('市场价格人工费') - base('定额基价人工费(调整后)')",
+                compiledExpr: "",
+                statement: "市场价格人工费-调整后的定额人工费(基价)"
+            },
+            {
+                ID: "116",
+                code: "5.2",
+                name: "材料费价差",
+                fieldName: "materialDiff",
+                dispExpr: "[材料费价差]",
+                expression: "base('市场价格材料费') - base('定额基价材料费(调整后)')",
+                compiledExpr: "",
+                statement: "市场价格材料费-定额基价材料费"
+            },
+            {
+                ID: "117",
+                code: "5.3",
+                name: "机械费价差",
+                fieldName: "machineDiff",
+                dispExpr: "[机械费价差]",
+                expression: "base('市场价格机械费') - base('定额基价机械费(调整后)')",
+                compiledExpr: "",
+                statement: "市场价格机械费-调整后的定额基价机械费(基价)"
+            },
+            {
+                ID: "118",
+                code: "6",
+                name: "综合单价",
+                fieldName: "common",
+                dispExpr: "F1+F11+F12+F13+F14",
+                expression: "@('1') + @('11') + @('12') + @('13') + @('14')",
+                compiledExpr: "",
+                statement: "基价直接工程费+企业管理费+利润+风险因素+人材机价差"
+            }
+        ]
+    };
+    let calcItem = {dispExpr: "12 +[人工费]*1.2+f13+ (F6+ f10) +F16+[人工费] + f6+[材料费]"};
+    let target = "12+base('人工费')*1.2+@('113')+(@('106')+@('110'))+@('116')+base('人工费')+@('106')+base('材料费')";
+    let rst = analyzer.analyzeUserExpr(calcTemplate, calcItem);
+    console.log('用户表达式是否正确:' + rst);
+    console.log(calcItem.dispExpr);
+    console.log(calcItem.expression);
+    t.equal(calcItem.expression, target);
+    t.end();
+});

+ 6 - 5
test/tmp_data/bills_grid_setting.js

@@ -102,8 +102,8 @@ var BillsGridSetting ={
             }
         },
         {
-            "width":40,
-            "readOnly":false,
+            "width":60,
+            "readOnly":'readOnly.ration',
             "head":{
                 "titleNames":[
                     "计量\n单位"
@@ -131,7 +131,8 @@ var BillsGridSetting ={
                 "field":"unit",
                 "vAlign":1,
                 "hAlign":1,
-                "font":"Arial"
+                "font":"Arial",
+                "cellType": 'cellType.unit'
             }
         },
         {
@@ -158,7 +159,7 @@ var BillsGridSetting ={
                 ]
             },
             "data":{
-                "field":"",
+                "field":"itemCharacterText",
                 "vAlign":1,
                 "hAlign":0,
                 "font":"Arial"
@@ -188,7 +189,7 @@ var BillsGridSetting ={
                 ]
             },
             "data":{
-                "field":"",
+                "field":"jobContentText",
                 "vAlign":1,
                 "hAlign":0,
                 "font":"Arial"

+ 2 - 2
web/building_saas/complementary_glj_lib/js/gljComponent.js

@@ -83,7 +83,7 @@ let gljComponentOprObj = {
                     return {
                         callback: function(){},
                         items: {
-                            "insert": {name: "插入", disabled: insertDis, callback: function (key, opt) {
+                            "insert": {name: "插入", disabled: insertDis, icon: "fa-sign-in", callback: function (key, opt) {
                                 //默认radio所有工料机
                                 co.initRadio();
                                 co.gljCurTypeId = null;
@@ -95,7 +95,7 @@ let gljComponentOprObj = {
                                 //弹出窗口
                                 $('#componentBtn').click();
                             }},
-                            "delete": {name: "删除", disabled: delDis, callback: function (key, opt) {
+                            "delete": {name: "删除", disabled: delDis, icon: "fa-remove", callback: function (key, opt) {
                                 //删除
                                 let deleteObj = that.currentComponent[target.row];
                                 let gljComponent = that.currentGlj.component;

+ 16 - 0
web/building_saas/main/html/main.html

@@ -154,12 +154,27 @@
                                   <li class="nav-item">
                                       <a class="nav-link" id="linkDESM" data-toggle="tab" href="#subSpread" role="tab">定额说明</a>
                                   </li>
+                                  <li class="nav-item">
+                                      <a class="nav-link" id="linkTZJNR" data-toggle="tab" href="#subSpread" role="tab">特征及内容</a>
+                                  </li>
                               </ul>
                               <!-- Tab panes -->
                               <div class="tab-content">
                                   <div class="tab-pane active" id="gl" role="tabpanel">
                                       <div class="main-data-bottom ovf-hidden" id="subSpread">
                                       </div>
+                                      <div id="tzjnrCon" class="row" style="display: none">
+                                          <div class="col-6">
+                                              <div class="row">
+                                                  <div class="main-data-bottom ovf-hidden col-5" id="jobSpread">
+                                                  </div>
+                                                  <div class="main-data-bottom ovf-hidden col-7" id="itemSpread">
+                                                  </div>
+                                              </div>
+                                          </div>
+                                          <div class="col-6">
+                                          </div>
+                                      </div>
                                   </div>
                               </div>
                           </div>
@@ -547,6 +562,7 @@
     <script type="text/javascript" src="/public/web/rpt_tpl_def.js"></script>
     <script type="text/javascript" src="/web/building_saas/main/js/rpt/rpt_main.js"></script>
     <script type="text/javascript" src="/web/building_saas/main/js/rpt/rpt_cfg_const.js"></script>
+    <script type="text/javascript" src="/web/building_saas/main/js/views/character_content_view.js"></script>
     <script type="text/javascript" src="/web/building_saas/main/js/views/glj_view.js"></script>
     <script type="text/javascript" src="/web/building_saas/main/js/views/sub_view.js"></script>
     <script type="text/javascript" src="/web/building_saas/main/js/views/fee_rate_view.js"></script>

+ 695 - 0
web/building_saas/main/js/views/character_content_view.js

@@ -0,0 +1,695 @@
+/**
+ * Created by Zhong on 2017/8/31.
+ * 特征及内容
+ */
+//todo:清单库相关联的操作
+let contentOprObj = {
+    workBook: null,
+    currentCache: [],//按照serialNo排序
+    setting: {
+        header: [
+            {headerName:"工作内容",headerWidth:160,dataCode:"content", dataType: "String", hAlign: "center", vAlign: "center"},
+            {headerName:"输出",headerWidth:80,dataCode:"isChecked", cellType:"checkBox", hAlign: "center", vAlign: "center"}
+        ]
+    },
+    buildSheet: function(container) {
+        let me = contentOprObj;
+        me.workBook = sheetCommonObj.buildSheet(container, me.setting, 30);
+        me.workBook.getSheet(0).setColumnWidth(0, 20, GC.Spread.Sheets.SheetArea.rowHeader);
+        me.workBook.options.allowCopyPasteExcelStyle = false;
+        me.onContextmenuOpr();//右键菜单
+        me.bindEvents(me.workBook);
+    },
+    bindEvents: function (workBook) {
+        let sheet = workBook.getActiveSheet(), me = contentOprObj;
+        const EVENTS = GC.Spread.Sheets.Events;
+        workBook.bind(EVENTS.ButtonClicked, me.onButtonClicked);
+        sheet.bind(EVENTS.EditEnded, me.onEditEnded);
+        sheet.bind(EVENTS.ClipboardPasting, me.onClipboardPasting);
+        sheet.bind(EVENTS.ClipboardPasted, me.onClipboardPasted);
+    },
+    //显示在jobSpread的数据
+    showContentData: function (sheet, setting, datas) {
+        sheetCommonObj.showData(sheet, setting, datas);
+        sheet.suspendPaint();
+        sheet.suspendEvent();
+        for(let i = 0, len = datas.length; i < len; i++){
+            sheet.getCell(i, 0).locked(true);
+        }
+        sheet.resumePaint();
+        sheet.suspendEvent();
+    },
+    //显示到清单工作内容列的数据
+    getColData: function (jobsArr) {
+        let me = contentOprObj;
+        let rstStr = "", count = 0;
+        for(let i = 0, len = jobsArr.length; i < len; i++){
+            if(jobsArr[i].isChecked === true){
+                count ++;
+                if(count === 1){
+                    rstStr += "“";
+                }
+                else{
+                    rstStr += " ";
+                }
+                rstStr += count + " " + jobsArr[i].content + "\n";
+            }
+        }
+        if(rstStr.trim().length > 0){
+            let reg = /\n+$/g;
+            let newStr = rstStr.replace(reg, "”");
+            return newStr;
+        }
+        return null;
+    },
+    //新增行
+    addRow: function (sheet) {
+        let checkBox = new GC.Spread.Sheets.CellTypes.CheckBox();
+        checkBox.isThreeState = false;
+        sheet.addRows(sheet.getRowCount(), 1);
+        sheet.getCell(sheet.getRowCount() - 1, 1).cellType(checkBox);
+    },
+    upMove: function (rowIdx) {
+        let me = contentOprObj;
+        let thisObj = me.currentCache[rowIdx],
+            preObj = me.currentCache[rowIdx - 1],
+            temp, contentTxt;
+        temp = thisObj.serialNo;
+        thisObj.serialNo = preObj.serialNo;
+        preObj.serialNo = temp;
+        me.sortCache(me.currentCache);
+        me.save();
+    },
+    downMove: function (rowIdx) {
+        let me = contentOprObj;
+        let thisObj = me.currentCache[rowIdx],
+            nextObj = me.currentCache[rowIdx + 1],
+            temp, contentTxt;
+        temp = thisObj.serialNo;
+        thisObj.serialNo = nextObj.serialNo;
+        nextObj.serialNo = temp;
+        me.sortCache(me.currentCache);
+        me.save();
+    },
+    deleteContent: function (rowIdx) {
+        let me = contentOprObj;
+        me.currentCache.splice(rowIdx, 1);
+        me.save();
+    },
+    //更新
+    updateContent: function (job, newContent) {
+        job.content = newContent;
+    },
+    //新增
+    insertContent: function (content) {
+        let me = contentOprObj;
+        let preObj = me.currentCache.length > 0 ?  me.currentCache[me.currentCache.length - 1] : null;
+        let newObj = {content: content, isCheceked: false, serialNo: preObj ? preObj.serialNo + 1 : 1};
+        me.currentCache.push(newObj);
+
+    },
+    save: function () {
+        let me = contentOprObj;
+        let contentTxt = me.getColData(me.currentCache);
+        let txtObj =  {field: 'jobContentText', text: contentTxt ? contentTxt : ''};
+        pageCCOprObj.updateCharacterContent(pageCCOprObj.currentFindSet, {field: 'jobContent', updateArr: me.currentCache}, txtObj, contentOprObj);
+    },
+    onEditEnded: function (sender, args) {
+        let me = contentOprObj;
+        let preObj = me.currentCache.length > 0 ?  me.currentCache[me.currentCache.length - 1] : null;
+        let contentTxt;
+        if(args.editingText && args.editingText.toString().trim().length > 0){
+            //更新
+            if(args.row < me.currentCache.length ){
+                me.updateContent(me.currentCache[args.row], args.editingText);
+            }
+            //新增
+            else{
+                me.insertContent(args.editingText);
+            }
+            //保存
+            me.save();
+        }
+        else{
+            //恢复
+            args.sheet.setValue(args.row, args.col, me.currentCache.length > args.row ? me.currentCache[args.row].content + '' : '');
+        }
+    },
+    //复选框控制输出
+    onButtonClicked: function (sender, args) {
+        let me = contentOprObj, contentTxt;
+        if(args.sheet.isEditing()){
+            args.sheet.endEdit(true);
+        }
+        let isChecked = args.sheet.getValue(args.row, args.col);
+        if(me.currentCache.length > args.row){
+            me.currentCache[args.row].isChecked = isChecked;
+            me.save();
+        }
+        //else的情况就是新增
+        else{
+            //还没数据清空复选框
+            args.sheet.setValue(args.row, args.col, 0);
+        }
+    },
+    //复制粘贴
+    onClipboardPasting: function (sender, args) {
+        if(args.cellRange.colCount > 1 || args.cellRange.col !== 0){
+            args.cancel = true;
+        }
+    },
+    onClipboardPasted: function (sender, args) {
+        let me = contentOprObj;
+        let items = sheetCommonObj.analyzePasteData(me.setting, args);
+        for(let i = 0, len = items.length; i < len; i++){
+            let rowIdx = args.cellRange.row + i;
+            //更新
+            if(rowIdx < me.currentCache.length){
+                me.updateContent(me.currentCache[rowIdx], items[i].content);
+            }
+            //新增
+            else{
+                me.insertContent(items[i].content);
+            }
+        }
+        me.save();
+    },
+    sortCache: function (cacheArr) {
+        cacheArr.sort(function (a, b) {
+            let rst = 0;
+            if(a.serialNo > b.serialNo){
+                rst = 1;
+            }
+            else if(a.serialNo < b.serialNo){
+                rst = -1;
+            }
+            return rst;
+        });
+    },
+    onContextmenuOpr: function () {//右键菜单
+        let me = contentOprObj;
+        $.contextMenu({
+            selector: '#jobSpread',
+            build: function($triggerElement, e){
+                //控制允许右键菜单在哪个位置出现
+                let clientX = e.originalEvent.clientX,
+                 clientY = e.originalEvent.clientY;
+                 let sheet = me.workBook.getSheet(0);
+                 let offset = $("#jobSpread").offset(),
+                 x = clientX - offset.left,
+                 y = clientY - offset.top;
+                 let target = sheet.hitTest(x, y);
+                if(target.hitTestType === 3){//在表格内&& typeof target.row !== 'undefined' && typeof target.col !== 'undefined'
+                    let insertDis = false, delDis = false, upDis = false, downDis = false;
+                    if(typeof target.row !== 'undefined'){
+                        //控制按钮是否可用
+                        sheet.setActiveCell(target.row, target.col);
+                        if(!me.currentCache ||target.row >= me.currentCache.length){//右键定位在有数据的行,删除键才显示可用
+                            delDis = true;
+                            downDis = true;
+                            upDis = true;
+                        }
+                        else{//有数据
+                            if(target.row === me.currentCache.length -1){//定位在最后一行,不可下移
+                                downDis = true;
+                            }
+                            if(target.row === 0){//定位在第一行,不可上移
+                                upDis = true;
+                            }
+                        }
+                    }
+                    else{
+                        delDis = true;
+                        downDis = true;
+                        upDis = true;
+                    }
+                    return {
+                        callback: function(){},
+                        items: {
+                            "insert": {name: "插入", disabled: insertDis, icon: "fa-sign-in", callback: function (key, opt) {
+                                //插入空行
+                                me.addRow(sheet);
+                            }},
+                            "delete": {name: "删除", disabled: delDis, icon: "fa-remove", callback: function (key, opt) {
+                                me.deleteContent(target.row);
+                            }},
+                            "upMove": {name: "上移", disabled: upDis, icon: "fa-arrow-up", callback: function (key, opt) {
+                                me.upMove(target.row);
+
+                            }},
+                            "downMove": {name: "下移", disabled: downDis, icon: "fa-arrow-down", callback: function (key, opt) {
+                                me.downMove(target.row);
+                            }}
+                        }
+                    };
+                }
+                else{
+                    return false;
+                }
+            }
+        });
+    }
+};
+
+
+let characterOprObj = {
+    workBook: null,
+    currentCache: [],
+    setting: {
+        header: [
+            {headerName:"项目特征",headerWidth:160,dataCode:"character", dataType: "String", hAlign: "center", vAlign: "center"},
+            {headerName:"特征值",headerWidth:160,dataCode:"eigenvalue", dataType: "String", cellType: "comboBox", hAlign: "center", vAlign: "center"},
+            {headerName:"输出",headerWidth:80,dataCode:"isChecked", cellType:"checkBox", hAlign: "center", vAlign: "center"}
+        ]
+    },
+    buildSheet: function(container) {
+        let me = characterOprObj;
+        me.workBook = sheetCommonObj.buildSheet(container, me.setting, 30);
+        me.workBook.getSheet(0).setColumnWidth(0, 20, GC.Spread.Sheets.SheetArea.rowHeader);
+        me.workBook.options.allowCopyPasteExcelStyle = false;
+        me.onContextmenuOpr();
+        me.bindEvents(me.workBook);
+    },
+    bindEvents: function (workBook) {
+        let sheet = workBook.getActiveSheet(), me = characterOprObj;
+        const EVENTS = GC.Spread.Sheets.Events;
+        workBook.bind(EVENTS.ButtonClicked, me.onButtonClicked);
+        sheet.bind(EVENTS.EditEnded, me.onEditEnded);
+        sheet.bind(EVENTS.EditStarting, me.onEditStart);
+        sheet.bind(EVENTS.ClipboardPasting, me.onClipboardPasting);
+        sheet.bind(EVENTS.ClipboardPasted, me.onClipboardPasted);
+    },
+    //显示在itemSpread的数据
+    showCharacterData: function (sheet, setting, datas) {
+        let me = characterOprObj;
+        sheetCommonObj.showData(sheet, setting, datas);
+        sheet.suspendPaint();
+        sheet.suspendEvent();
+        for(let i = 0, len = datas.length; i < len; i++){
+            let comboObj = me.getComboBox(datas[i]);
+            comboObj.combo.editable(false);//不可编辑
+            sheet.getCell(i, 1).cellType(comboObj.combo).value(typeof comboObj.selectedValue !== 'undefined' ? comboObj.selectedValue : '');
+            sheet.getCell(i, 0).locked(true);
+        }
+        sheet.resumePaint();
+        sheet.suspendEvent();
+    },
+    //获得项目特征特征值comboBox
+    getComboBox: function (characterItem) {
+        let rst = {};
+        let combo = new GC.Spread.Sheets.CellTypes.ComboBox();
+        let comboItems = [], eigenvalues = characterItem.eigenvalue;
+        for(let i = 0, len = eigenvalues.length; i < len; i++){
+            comboItems.push(eigenvalues[i].value);
+            if(eigenvalues[i].isSelected){
+                rst.selectedValue = eigenvalues[i].value;
+            }
+        }
+        combo.items(comboItems);
+        rst.combo = combo;
+        return rst;
+    },
+    //获得当前行选中的特征值
+    getCurrentSelected: function (item) {
+        let rst = null;
+        for(let i = 0, len = item.eigenvalue.length; i < len; i++){
+            if(item.eigenvalue[i].isSelected){
+                rst = item.eigenvalue[i].value;
+                break;
+            }
+        }
+        return rst;
+    },
+    //显示到清单项目特征列的数据
+    getColData: function (itemsArr) {
+        let me = characterOprObj;
+        let rstStr = "", count = 0;
+        for(let i = 0, len = itemsArr.length; i < len; i++){
+            if(itemsArr[i].isChecked === true){
+                //获取选中的特征值
+                let eigenvalueStr = "";
+                let eigenvalue = itemsArr[i].eigenvalue;
+                for(let j = 0, vLen = eigenvalue.length; j < vLen; j++){
+                    if(eigenvalue[j].isSelected){
+                        eigenvalueStr += eigenvalue[j].value;
+                        break;
+                    }
+                }
+                count ++;
+                if(count === 1){
+                    rstStr += "“";
+                }
+                else{
+                    rstStr += " ";
+                }
+                rstStr += count + " " + itemsArr[i].character + ": " + eigenvalueStr + "\n";
+            }
+        }
+        if(rstStr.trim().length > 0){
+            let reg = /\n+$/g;
+            let newStr = rstStr.replace(reg, "”");
+            return newStr;
+        }
+        return null;
+    },
+    addRow: function (sheet) {
+        let checkBox = new GC.Spread.Sheets.CellTypes.CheckBox(),
+            combo = new GC.Spread.Sheets.CellTypes.ComboBox();
+        checkBox.isThreeState = false;
+        combo.editable(true);
+        let rowIdx = sheet.getRowCount();
+        sheet.addRows(rowIdx, 1);
+        sheet.getCell(rowIdx, 1).cellType(combo);
+        sheet.getCell(rowIdx, 2).cellType(checkBox);
+    },
+    upMove: function (rowIdx) {
+        let me = characterOprObj;
+        let thisObj = me.currentCache[rowIdx],
+            preObj = me.currentCache[rowIdx - 1],
+            temp;
+        temp = thisObj.serialNo;
+        thisObj.serialNo = preObj.serialNo;
+        preObj.serialNo = temp;
+        contentOprObj.sortCache(me.currentCache);
+        me.save();
+    },
+    downMove: function (rowIdx) {
+        let me = characterOprObj;
+        let thisObj = me.currentCache[rowIdx],
+            nextObj = me.currentCache[rowIdx + 1],
+            temp;
+        temp = thisObj.serialNo;
+        thisObj.serialNo = nextObj.serialNo;
+        nextObj.serialNo = temp;
+        contentOprObj.sortCache(me.currentCache);
+        me.save();
+    },
+    deleteCharacter: function (rowIdx) {
+        let me = characterOprObj;
+        me.currentCache.splice(rowIdx, 1);
+        me.save();
+    },
+    //取消选择的特征值
+    unsetSelected: function (item) {
+        for(let i = 0, len = item.eigenvalue.length; i < len; i++){
+            if(item.eigenvalue[i].isSelected){
+                item.eigenvalue[i].isSelected = false;
+            }
+        }
+    },
+    //设置选中的特征值
+    setSelected: function (item, value) {
+        for(let i = 0, len = item.eigenvalue.length; i < len; i++){
+            if(item.eigenvalue[i].value === value){
+                item.eigenvalue[i].isSelected = true;
+            }
+        }
+    },
+    //改变选择特征值
+    changeSelected: function (item, value) {
+        let me = characterOprObj;
+        me.unsetSelected(item);
+        me.setSelected(item, value);
+    },
+    insertValue: function (item, value) {
+        let me = characterOprObj;
+        me.unsetSelected(item);
+        let newValue = {value: value, isSelected: true};
+        item.eigenvalue.push(newValue);
+    },
+    updateCharacter: function (item, character, value) {
+        let me = characterOprObj;
+        if(character){
+            item.character = character;
+        }
+        if(value){
+            let isExist = false;
+            for(let i = 0, len = item.eigenvalue.length; i < len; i++){
+                if(item.eigenvalue[i].value === value){
+                    isExist = true;
+                    break;
+                }
+            }
+            //不存在,新增进eigenvalue
+            if(!isExist){
+                //更新selected
+                me.insertValue(item, value);
+            }
+            //存在,选择特征值
+            else{
+                me.changeSelected(item, value);
+            }
+        }
+    },
+    insertCharacter: function (character, value) {
+        let me = characterOprObj;
+        let preObj = me.currentCache.length > 0 ?  me.currentCache[me.currentCache.length - 1] : null;
+        if(character && !value){
+            let newCharacter = {character: character, eigenvalue: [], isChecked: false, serialNo: preObj ? preObj.serialNo + 1 : 1};
+            me.currentCache.push(newCharacter);
+        }
+        else if(!character && value){
+            let newValue = {value: value, isSelected: true};
+            let newCharacter = {character: '', eigenvalue: [newValue], isChecked: false, serialNo: preObj? preObj.serialNo + 1 : 1};
+            me.currentCache.push(newCharacter);
+        }
+        else if(character && value){
+            let newValue = {value: value, isSelected: true};
+            let newCharacter = {character:character , eigenvalue: [newValue], isChecked: false, serialNo: preObj? preObj.serialNo + 1 : 1};
+            me.currentCache.push(newCharacter);
+        }
+    },
+    save: function () {
+        let me = characterOprObj;
+        let characterTxt = me.getColData(me.currentCache);
+        let txtObj =  {field: 'itemCharacterText', text: characterTxt ? characterTxt : ''};
+        pageCCOprObj.updateCharacterContent(pageCCOprObj.currentFindSet, {field: 'itemCharacter', updateArr: me.currentCache}, txtObj, characterOprObj);
+    },
+    onEditStart: function (sender, args) {
+        let me = characterOprObj;
+        if(args.col === 1){//改变选择的特征值
+            me.currentSelectedValue = me.currentCache.length > args.row ?  me.getCurrentSelected(me.currentCache[args.row]) : null;
+        }
+    },
+    onEditEnded: function (sender, args) {
+        let me = characterOprObj, characterTxt;
+        let preObj = me.currentCache.length > 0 ?  me.currentCache[me.currentCache.length - 1] : null;
+        if(args.editingText && args.editingText.toString().trim().length > 0){
+            //更新
+            if(args.row < me.currentCache.length){
+                let thisCha = me.currentCache[args.row];
+                if(args.col === 0){//特征
+                   me.updateCharacter(thisCha, args.editingText, null);
+                }
+                else if(args.col === 1){//特征值
+                    me.updateCharacter(thisCha, null, args.editingText);
+                }
+            }
+            //新增
+            else{
+                if(args.col === 0){//特征
+                    me.insertCharacter(args.editingText, null);
+                }
+                else if(args.col === 1){//特征值
+                    me.insertCharacter(null, args.editingText);
+                }
+            }
+            //保存
+            me.save();
+        }
+        else{//恢复
+            if(args.col === 0){
+                args.sheet.setValue(args.row, args.col, me.currentCache.length > args.row ? me.currentCache[args.row].character + '' : '');
+            }
+            else if(args.col === 1){
+                args.sheet.setValue(args.row, args.col, me.currentSelectedValue ? me.currentSelectedValue + '' : '');
+            }
+        }
+    },
+    onClipboardPasting: function (sender, args) {
+        if(args.cellRange.col + args.colCount - 1 > 1){
+            args.cancel = true;
+        }
+    },
+    onClipboardPasted: function (sender, args) {
+        let me = characterOprObj;
+        let items = sheetCommonObj.analyzePasteData(me.setting, args);
+        for(let i = 0, len = items.length; i < len; i++){
+            //更新
+            let rowIdx = args.cellRange.row + i;
+            if(me.currentCache.length > rowIdx){
+               me.updateCharacter(me.currentCache[rowIdx], items[i].character, items[i].eigenvalue);
+            }
+            //新增
+            else{
+                me.insertCharacter(items[i].character, items[i].eigenvalue);
+            }
+        }
+        me.save();
+    },
+    //复选框控制输出
+    onButtonClicked: function (sender, args) {
+        let me = characterOprObj, characterTxt;
+        if(args.sheet.isEditing()){
+            args.sheet.endEdit(true);
+        }
+        let isChecked = args.sheet.getValue(args.row, args.col);
+        if(me.currentCache.length > args.row){
+            me.refreshColData = true;
+            me.currentCache[args.row].isChecked = isChecked;
+            me.save();
+        }
+        //else的情况就是新增
+        else{
+            args.sheet.setValue(args.row, args.col, 0);
+        }
+    },
+    onContextmenuOpr: function () {//右键菜单
+        let me = characterOprObj;
+        $.contextMenu({
+            selector: '#itemSpread',
+            build: function($triggerElement, e){
+                //控制允许右键菜单在哪个位置出现
+                let clientX = e.originalEvent.clientX,
+                    clientY = e.originalEvent.clientY;
+                let sheet = me.workBook.getSheet(0);
+                let offset = $("#itemSpread").offset(),
+                    x = clientX - offset.left,
+                    y = clientY - offset.top;
+                let target = sheet.hitTest(x, y);
+                if(target.hitTestType === 3){//在表格内 && typeof target.row !== 'undefined' && typeof target.col !== 'undefined'
+                    let insertDis = false, delDis = false, upDis = false, downDis = false;
+                    if(typeof target.row !== 'undefined'){
+                        //控制按钮是否可用
+                        sheet.setActiveCell(target.row, target.col);
+                        if(!me.currentCache ||target.row >= me.currentCache.length){//右键定位在有数据的行,删除键才显示可用
+                            delDis = true;
+                            downDis = true;
+                            upDis = true;
+                        }
+                        else{//有数据
+                            if(target.row === me.currentCache.length -1){//定位在最后一行,不可下移
+                                downDis = true;
+                            }
+                            if(target.row === 0){//定位在第一行,不可上移
+                                upDis = true;
+                            }
+                        }
+                    }
+                    else{
+                        delDis = true;
+                        downDis = true;
+                        upDis = true;
+                    }
+                    return {
+                        callback: function(){},
+                        items: {
+                            "insert": {name: "插入", disabled: insertDis, icon: "fa-sign-in", callback: function (key, opt) {
+                                me.addRow(sheet);
+                            }},
+                            "delete": {name: "删除", disabled: delDis, icon: "fa-remove", callback: function (key, opt) {
+                                me.deleteCharacter(target.row);
+                            }},
+                            "upMove": {name: "上移", disabled: upDis, icon: "fa-arrow-up", callback: function (key, opt) {
+                                me.upMove(target.row);
+                            }},
+                            "downMove": {name: "下移", disabled: downDis, icon: "fa-arrow-down", callback: function (key, opt) {
+                                me.downMove(target.row);
+                            }}
+                        }
+                    };
+                }
+                else{
+                    return false;
+                }
+            }
+        });
+    }
+};
+
+let pageCCOprObj = {
+    currentFindSet: null,
+    mainActiveCell: null,//mainSpread焦点单元格
+    //设置特征及内容currentCache
+    setCacheAndShow: function (node) {
+        let theCont = contentOprObj, theCha = characterOprObj;
+        theCont.currentCache = node && typeof node.data.jobContent !== 'undefined' ? node.data.jobContent : [];
+        theCha.currentCache = node && typeof node.data.itemCharacter !== 'undefined' ? node.data.itemCharacter : [];
+        this.currentFindSet = node && typeof node.data.ID !== 'undefined' && typeof node.data.projectID ? {ID: node.data.ID, projectID: node.data.projectID} : null;
+        this.showData(theCont.workBook.getSheet(0), theCont.setting, theCont.currentCache);
+        this.showData(theCha.workBook.getSheet(0), theCha.setting, theCha.currentCache);
+    },
+    //contentSpread、itemSpread展示数据用
+    showData: function(sheet, setting, data) {
+        let me = this, ch = GC.Spread.Sheets.SheetArea.viewport;
+        sheet.suspendPaint();
+        sheet.suspendEvent();
+        sheet.clear(0, 0, sheet.getRowCount(), sheet.getColumnCount(), GC.Spread.Sheets.SheetArea.viewport, GC.Spread.Sheets.StorageType.data);
+        sheet.setRowCount(data.length);
+        for (let col = 0; col < setting.header.length; col++) {
+            var hAlign = "left", vAlign = "center";
+            if (setting.header[col].hAlign) {
+                hAlign = setting.header[col].hAlign;
+            } else if (setting.header[col].dataType !== "String"){
+                hAlign = "right";
+            }
+            vAlign = setting.header[col].vAlign?setting.header[col].vAlign:vAlign;
+            sheetCommonObj.setAreaAlign(sheet.getRange(-1, col, -1, 1), hAlign, vAlign);
+            if (setting.header[col].formatter) {
+                sheet.setFormatter(-1, col, setting.header[col].formatter, GC.Spread.Sheets.SheetArea.viewport);
+            }
+            for (let row = 0; row < data.length; row++) {
+                sheet.getCell(row, 0).locked(true);//locked
+                let val = data[row][setting.header[col].dataCode];
+                if(setting.header[col].cellType === "checkBox"){
+                    let checkBox = new GC.Spread.Sheets.CellTypes.CheckBox();
+                    checkBox.isThreeState(false);
+                    sheet.getCell(row, col).cellType(checkBox).value(val);
+                }
+                else if(setting.header[col].cellType === 'comboBox'){
+                    let comboObj = characterOprObj.getComboBox(data[row]);
+                    comboObj.combo.editable(true);//不可编辑
+                    sheet.getCell(row, 1).cellType(comboObj.combo).value(typeof comboObj.selectedValue !== 'undefined' ? comboObj.selectedValue : '');
+                }
+                else{
+                    sheet.setValue(row, col, val, ch);
+                }
+            }
+        }
+        sheet.resumeEvent();
+        sheet.resumePaint();
+    },
+    clearData: function () {
+        let theCon = contentOprObj, theCha = characterOprObj;
+        sheetCommonObj.cleanSheet(theCon.workBook.getSheet(0), theCon.setting, -1);
+        sheetCommonObj.cleanSheet(theCha.workBook.getSheet(0), theCha.setting, -1);
+    },
+    //更新特征及内容数据
+    updateCharacterContent: function (findSet, updateObj, txtObj, oprObj) {
+        let me = pageCCOprObj, updateCol;
+        if(txtObj){
+            updateCol = txtObj.field === 'itemCharacterText' ? 4 : 5;//更新清单行特征列或内容列
+        }
+        else{
+            updateCol = null;
+        }
+        $.ajax({
+            type: 'post',
+            url: '/bills/updateCharacterContent',
+            dataType: 'json',
+            data: {findSet: JSON.stringify(findSet), updateObj: JSON.stringify(updateObj), txtObj: JSON.stringify(txtObj)},
+            success: function (result) {
+                if(!result.error){
+                    //更新节点数据
+                    let selectedNode = projectObj.mainController.tree.selected;
+                    selectedNode.data[updateObj.field] = updateObj.updateArr;
+                    selectedNode.data[txtObj.field] = txtObj.text;
+                    me.showData(oprObj.workBook.getSheet(0), oprObj.setting, oprObj.currentCache);//刷新特征及内容Spread
+                    if(updateCol){
+                        projectObj.mainSpread.getActiveSheet().setValue(me.mainActiveCell.row, updateCol, txtObj.text + ''); //刷新输出显示
+                    }
+                }
+            }
+        });
+    }
+}

+ 8 - 0
web/building_saas/main/js/views/main_tree_col.js

@@ -43,6 +43,14 @@ let MainTreeCol = {
             return MainTreeCol.readOnly.billsParent && MainTreeCol.readOnly.non_bills;
         }
     },
+    cellType: {
+        unit: function () {
+            let combo = new GC.Spread.Sheets.CellTypes.ComboBox();
+            combo.itemHeight(10).items(['m', 'm2', 'm3', 'km', 't', 'kg', '台班', '工日', '昼夜', '元', '项', '处', '个', '件',
+                '根', '组', '系统', '台', '套', '株', '丛', '缸', '支', '只', '块', '座', '对', '份', '樘', '攒', '榀']);
+            return combo;
+        }
+    },
     getEvent: function (eventName) {
         let names = eventName.split('.');
         let event = this;

+ 17 - 2
web/building_saas/main/js/views/project_view.js

@@ -94,6 +94,10 @@ var projectObj = {
                     if (col.readOnly && Object.prototype.toString.apply(col.readOnly) === "[object String]") {
                         col.readOnly = MainTreeCol.getEvent(col.readOnly);
                     }
+                    if (col.data.cellType && Object.prototype.toString.apply(col.data.cellType) === "[object String]") {
+                        let getCellType = MainTreeCol.getEvent(col.data.cellType);
+                        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);
                         col.data.formatter = MainTreeCol.getNumberFormatter(col.data.decimal);
@@ -155,6 +159,8 @@ var projectObj = {
                     that.loadGLJSpreadContextMenu();
                 }*/
 
+                that.project.calcProgram = new CalcProgram(that.project);
+                that.project.calcProgram.compileAllTemps();
 
                 that.mainController.bind(TREE_SHEET_CONTROLLER.eventName.treeSelectedChanged, function (node) {
                     gljOprObj.showDataIfRationSelect(node);
@@ -167,6 +173,16 @@ var projectObj = {
                             calcProgramObj.clearData();
                         };
                     };
+                    //zhong 2017-9-1 特征及内容
+                    if(pageCCOprObj.active){
+                        pageCCOprObj.mainActiveCell = projectObj.mainSpread.getActiveSheet().getSelections()[0];//mainSpread焦点单元格
+                        if(node.sourceType === that.project.Bills.getSourceType()){
+                            pageCCOprObj.setCacheAndShow(node);
+                        }
+                        else{
+                            pageCCOprObj.clearData();
+                        }
+                    }
                 });
 
                 that.mainSpread.bind(GC.Spread.Sheets.Events.EditEnded, that.mainSpreadEditEnded);
@@ -177,8 +193,7 @@ var projectObj = {
 
             }
         });
-        this.project.calcProgram = new CalcProgram(this.project);
-        this.project.calcProgram.compileAllTemps();
+
     },
     loadMainSpreadContextMenu: function () {
         var project = this.project, spread = this.mainSpread, controller = this.mainController;

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

@@ -1,11 +1,17 @@
 /**
  * Created by CSL on 2017-07-04.
  */
-
+//modified by zhong on 2017-08-30
 // Tab panes 下有多个Spread时,相互之间不能正确显示。改成一个Spread下多个Sheet。
 var subSpread = sheetCommonObj.createSpread($("#subSpread")[0], 7);
 subSpread.getSheet(4).name('JSCX');
 
+//特征及内容spread,解决不能正确显示spread
+$("#jobSpread").width($("#subSpread").width() *0.5* 0.4);
+$("#itemSpread").width($("#subSpread").width() *0.5 - $("#jobSpread").width()-20);
+contentOprObj.buildSheet($("#jobSpread")[0]);
+characterOprObj.buildSheet($("#itemSpread")[0]);
+pageCCOprObj.active = false;
 
 // 工料机
 gljOprObj.initSheet(subSpread.getSheet(0));
@@ -27,6 +33,9 @@ SheetDataHelper.protectdSheet(subSpread.getSheet(3));
 
 
 $("#linkGLJ").click(function(){
+    $("#tzjnrCon").hide();//控制显示subSpread,隐藏特征及内容spread
+    $("#subSpread").show();
+    pageCCOprObj.active = false;
     subSpread.setActiveSheetIndex(0);
     $.contextMenu( 'destroy', "#subSpread" );
     gljContextMenu.loadGLJSpreadContextMenu();
@@ -35,6 +44,9 @@ $("#linkGLJ").click(function(){
 });
 
 $("#linkFZDE").click(function(){
+    $("#tzjnrCon").hide();
+    $("#subSpread").show();
+    pageCCOprObj.active = false;
     subSpread.setActiveSheetIndex(1);
     $.contextMenu( 'destroy', "#subSpread" );
     // for test
@@ -42,6 +54,9 @@ $("#linkFZDE").click(function(){
 });
 
 $("#linkFZTJ").click(function(){
+    $("#tzjnrCon").hide();
+    $("#subSpread").show();
+    pageCCOprObj.active = false;
     subSpread.setActiveSheetIndex(2);
     $.contextMenu( 'destroy', "#subSpread" );
     // for test
@@ -49,6 +64,9 @@ $("#linkFZTJ").click(function(){
 });
 
 $("#linkGCLMX").click(function(){
+    $("#tzjnrCon").hide();
+    $("#subSpread").show();
+    pageCCOprObj.active = false;
     subSpread.setActiveSheetIndex(3);
     $.contextMenu( 'destroy', "#subSpread" );
     gljContextMenu.loadQuantityDetailMenu();
@@ -57,6 +75,9 @@ $("#linkGCLMX").click(function(){
 });
 
 $("#linkJSCX").click(function(){        // 计算程序
+    $("#tzjnrCon").hide();
+    $("#subSpread").show();
+    pageCCOprObj.active = false;
     subSpread.setActiveSheetIndex(4);
     calcProgramObj.initSheet(subSpread.getSheet(4));
     let sel = projectObj.mainController.tree.selected;
@@ -69,17 +90,37 @@ $("#linkJSCX").click(function(){        // 计算程序
 });
 
 $("#linkFXSM").click(function(){
+    $("#tzjnrCon").hide();
+    $("#subSpread").show();
+    pageCCOprObj.active = false;
     subSpread.setActiveSheetIndex(5);
     // for test
     subSpread.getActiveSheet().setValue(0, 0, "分项说明");
 });
 
 $("#linkDESM").click(function(){
+    $("#tzjnrCon").hide();
+    $("#subSpread").show();
+    pageCCOprObj.active = false;
     subSpread.setActiveSheetIndex(6);
     // for test
     subSpread.getActiveSheet().setValue(0, 0, "定额说明");
 });
 
+//特征及内容
+$("#linkTZJNR").click(function () {
+    $("#subSpread").hide();
+    $("#tzjnrCon").show();
+    pageCCOprObj.active = true;
+    let selectedNode = projectObj.mainController.tree.selected;
+    pageCCOprObj.mainActiveCell = projectObj.mainSpread.getActiveSheet().getSelections()[0];
+   // if(selectedNode && selectedNode.sourceType === projectObj.project.Bills.getSourceType()){
+        pageCCOprObj.setCacheAndShow(selectedNode);
+   // }
+    //else{
+       // pageCCOprObj.clearData();
+    //}
+});
 function SubActiveSheetNameIs(sheetName){
     let rst = subSpread.getActiveSheet().name() == sheetName;
     return rst;

+ 1 - 1
web/building_saas/pm/html/project-management.html

@@ -336,7 +336,7 @@
                     </div>
                     <div class="form-group">
                         <label>费率文件</label>
-                        <select class="form-control"><option>新建费率文件</option></select>
+                        <select class="form-control" id="tender-fee-rate"><option>请选择费率文件</option></select>
                     </div>
                     <div class="form-group">
                         <label>计价方式</label>

+ 71 - 33
web/building_saas/pm/js/pm_main.js

@@ -9,6 +9,7 @@ let Tree = null;
 let movetoZTree = null;
 let copytoZTree = null;
 let engineering = [];
+let feeRateData = [];
 let projectType = {
     folder: 'Folder',
     tender: 'Tender',
@@ -165,13 +166,14 @@ $(document).ready(function() {
     $("input[name='valuation_type']").click(function() {
         let type = $(this).val();
         let targetData = type === 'bill' ? JSON.parse(billValuation) : JSON.parse(rationValuation);
+
         let html = '<option value="">请选择计划规则</option>';
 
         for(let valuation of targetData) {
             if (valuation === null) {
                 continue;
             }
-            html += '<option value="'+ valuation._id +'" data-engineering="'+ valuation.engineering +'">'+ valuation.name +'</option>';
+            html += '<option value="'+ valuation._id +'">'+ valuation.name +'</option>';
         }
         $("#valuation").html(html);
     });
@@ -228,20 +230,26 @@ $(document).ready(function() {
         }
         if (projectInfo !== null) {
             let savedProjectData = localStorage.getItem(projectInfo.data.name);
+            console.log(savedProjectData);
             savedProjectData = JSON.parse(savedProjectData);
             // 填入计价规则
             let valuationHtml = '<option value="'+ savedProjectData.valuation +'">'+ savedProjectData.valuationName +'</option>';
             $("#tender-valuation").html(valuationHtml);
 
             // 填入工程专业
-            let engineeringString = getEngineeringName(savedProjectData.engineering);
-            let engineeringHtml = '<option value="'+ savedProjectData.engineering +'">'+ engineeringString +'</option>';
+            let engineeringHtml = getEngineeringHtml(savedProjectData.engineeringList);
             $("#tender-engineering").html(engineeringHtml);
 
             $("input[name='tender_valuation_type']").attr('disabled', 'disabled').removeAttr('checked', 'checked');
             $("input[name='tender_valuation_type'][value='"+ savedProjectData.valuationType +"']")
                 .attr("checked", "checked").removeAttr('disabled', 'disabled');
 
+            // 填入费率文件
+            let feeHtml = '<option>请选择费率文件</option>';
+            // for (let fee of savedProjectData.feeLib) {
+            //     feeHtml += '<option value="'+ fee.id +'">'+ fee.name +'</option>';
+            // }
+            $("#tender-fee-rate").html(feeHtml);
         }
 
     });
@@ -394,7 +402,7 @@ $(document).ready(function() {
                 updateType: 'update',
                 projectType: null
             };
-            updateData = GetUpdateData(parent, next, '', Tree.selected().id(), typeInfo);
+            updateData = GetUpdateData(parent, next, '', null, Tree.selected().id(), typeInfo);
             UpdateProjectData(updateData, function (data) {
                 dialog.modal('hide');
                 Tree.move(Tree.selected(), parent, next);
@@ -458,7 +466,7 @@ $(document).ready(function() {
                     updateType: 'copy',
                     projType: cur.data.projectType
                 };
-                let updateData = GetUpdateData(parent, next, cur.data.name, IDs.lowID, typeInfo);
+                let updateData = GetUpdateData(parent, next, cur.data.name, cur.data.property, IDs.lowID, typeInfo);
                 updateData['srcProjectId'] = cur.id();
                 pre = GetNeedUpdatePreNode(parent, next);
                 if (pre) {
@@ -523,24 +531,34 @@ function AddProject() {
     }
     let valuationName = $("#valuation").children("option:selected").text();
     let valuationType = $("input[name='valuation_type']:checked").val();
-    let engineering = $("#valuation").children("option:selected").data("engineering");
+    let engineeringList = [];
+
+    let valuationData = valuationType === 'bill' ? JSON.parse(billValuation) : JSON.parse(rationValuation);
+    for(let tmp of valuationData) {
+        if (tmp._id === valuation) {
+            engineeringList = tmp.engineering_list;
+            break;
+        }
+    }
+    let projectInfo = {
+        valuation: valuation,
+        valuationType: valuationType,
+        valuationName: valuationName,
+        engineeringList: engineeringList
+    };
+
     let callback = function() {
         $("#add-project-dialog").modal("hide");
         // 记录选择后的信息
-        let projectInfo = {
-            valuation: valuation,
-            valuationType: valuationType,
-            valuationName: valuationName,
-            engineering: engineering
-        };
         localStorage.setItem(name, JSON.stringify(projectInfo));
     };
     let selectedItem = Tree.selected();
+
     // 如果选择的是建设项目则新增同级数据
     if (selectedItem !== null && selectedItem.data.projType === projectType.project) {
-        AddSiblingsItem(name, projectType.project, callback);
+        AddSiblingsItem(name, projectInfo, projectType.project, callback);
     } else {
-        AddChildrenItem(name, projectType.project, callback);
+        AddChildrenItem(name, projectInfo, projectType.project, callback);
     }
 }
 
@@ -552,7 +570,7 @@ function AddProject() {
  * @param {function} callback
  * @return {void}
  */
-function AddChildrenItem(name, type, callback) {
+function AddChildrenItem(name, property, type, callback) {
     let parent = Tree.selected() ? Tree.selected() : Tree._root;
     let next = Tree.selected() ? Tree.selected().firstChild() : Tree.firstNode();
     GetNewProjectId(function(IDs) {
@@ -560,7 +578,7 @@ function AddChildrenItem(name, type, callback) {
             updateType: 'new',
             projectType: type
         };
-        let updateData = GetUpdateData(parent, next, name, IDs.lowID, typeInfo);
+        let updateData = GetUpdateData(parent, next, name, property, IDs.lowID, typeInfo);
         Tree.maxNodeId(IDs.lowID - 1);
         UpdateProjectData(updateData, function(datas){
             datas.forEach(function (data) {
@@ -581,7 +599,7 @@ function AddChildrenItem(name, type, callback) {
  * @param {function} callback
  * @return {void}
  */
-function AddSiblingsItem(name, type, callback) {
+function AddSiblingsItem(name, property, type, callback) {
     let selected = Tree.selected();
     let parent = selected ? selected.parent : Tree._root;
     let next = selected ? selected.nextSibling : Tree.firstNode();
@@ -590,7 +608,7 @@ function AddSiblingsItem(name, type, callback) {
             updateType: 'new',
             projectType: type
         };
-        let updateData = GetUpdateData(parent, next, name, IDs.lowID, typeInfo);
+        let updateData = GetUpdateData(parent, next, name, property, IDs.lowID, typeInfo);
         Tree.maxNodeId(IDs.lowID - 1);
         UpdateProjectData(updateData, function(datas){
             datas.forEach(function (data) {
@@ -620,9 +638,9 @@ function AddEngineering() {
     let selectedItem = Tree.selected();
     // 如果选择的是单项工程则新增同级数据
     if (selectedItem !== null && selectedItem.data.projType === projectType.engineering) {
-        AddSiblingsItem(name, projectType.engineering, callback);
+        AddSiblingsItem(name, null, projectType.engineering, callback);
     } else {
-        AddChildrenItem(name, projectType.engineering, callback);
+        AddChildrenItem(name, null, projectType.engineering, callback);
     }
 }
 
@@ -637,15 +655,29 @@ function AddTender() {
         alert('请填写单位工程名称');
         return false;
     }
+
+    let valuation = $("#tender-valuation").val();
+    let valuationName = $("#tender-valuation").children("option:selected").text();
+    let valuationType = $("input[name='tender_valuation_type']:checked").val();
+    let engineering = $("#tender-engineering").val();
+    let enginerringName = $('#tender-engineering').children("option:selected").text();
+
     let callback = function() {
         $("#add-tender-dialog").modal("hide");
     };
     let selectedItem = Tree.selected();
+    let tenderInfo = {
+        valuation: valuation,
+        valuationType: valuationType,
+        valuationName: valuationName,
+        engineering: engineering,
+        engineeringName: enginerringName
+    };
     // 如果选择的是单项工程则新增同级数据
     if (selectedItem !== null && selectedItem.data.projType === projectType.tender) {
-        AddSiblingsItem(name, projectType.tender, callback);
+        AddSiblingsItem(name, tenderInfo, projectType.tender, callback);
     } else {
-        AddChildrenItem(name, projectType.tender, callback);
+        AddChildrenItem(name, tenderInfo, projectType.tender, callback);
     }
 }
 
@@ -669,11 +701,11 @@ function AddFolder() {
             alert("文件夹不能超过3层");
             return false;
         }
-        AddChildrenItem(name, projectType.folder, function() {
+        AddChildrenItem(name, null, projectType.folder, function() {
             $("#add-folder-dialog").modal("hide");
         });
     } else {
-        AddSiblingsItem(name, projectType.folder, function() {
+        AddSiblingsItem(name, null, projectType.folder, function() {
             $("#add-folder-dialog").modal("hide");
         });
     }
@@ -688,7 +720,7 @@ function AddFolder() {
  * @param {Object} type
  * @return {Object}
  */
-function GetUpdateData(parent, next, name, newId, type) {
+function GetUpdateData(parent, next, name, property, newId, type) {
     let data = [];
     let updateData = {};
     updateData['updateType'] = type.updateType === undefined ? 'new' : type.updateType;
@@ -701,6 +733,9 @@ function GetUpdateData(parent, next, name, newId, type) {
     if (name !== '') {
         updateData['updateData']['name'] = name;
     }
+    if (property !== undefined) {
+        updateData['updateData']['property'] = property;
+    }
     if (type !== null && type.projectType !== null) {
         updateData['updateData']['projType'] = type.projectType !== undefined ? type.projectType : 'Tender';
     }
@@ -875,19 +910,22 @@ function GetTargetTreeNode(zTreeObj) {
 /**
  * 根据指定id获取对应的工程专业
  *
- * @param {Number} id
+ * @param {Array} engineeringList
  * @return {String}
  */
-function getEngineeringName(id) {
-    let result = '';
-    if (engineering.length <= 0) {
+function getEngineeringHtml(engineeringList) {
+    let result = '<option>请选择对应的工程专业</option>';
+    if (engineeringList.length <= 0) {
         return result;
     }
-
+    let engineeringObject = {};
     for(let tmp of engineering) {
-        if (tmp.value === id) {
-            result = tmp.name;
-            break;
+        engineeringObject[tmp.value] = tmp.name;
+    }
+
+    for(let tmp of engineeringList) {
+        if (engineeringObject[tmp.engineering] !== undefined) {
+            result += '<option value="'+ tmp.engineering +'">'+ engineeringObject[tmp.engineering] +'</option>';
         }
     }