Explorar o código

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

TonyKang %!s(int64=7) %!d(string=hai) anos
pai
achega
cf2a4532a4
Modificáronse 33 ficheiros con 943 adicións e 155 borrados
  1. 3 3
      lib/bootstrap/bootstrap.min.js
  2. 4 4
      lib/bootstrap/css/bootstrap.min.css
  3. 1 0
      modules/all_models/calc_program_model.js
  4. 19 2
      modules/glj/controllers/glj_controller.js
  5. 49 6
      modules/glj/models/unit_price_model.js
  6. 1 0
      modules/glj/routes/glj_router.js
  7. 270 1
      modules/main/controllers/bills_controller.js
  8. 71 0
      modules/main/models/bills.js
  9. 1 0
      modules/main/routes/bills_route.js
  10. 2 0
      modules/pm/models/project_property_template.js
  11. 3 1
      package.json
  12. 26 1
      public/web/id_tree.js
  13. 1 1
      public/web/sheet/sheet_common.js
  14. 4 1
      public/web/tree_sheet/tree_sheet_controller.js
  15. 8 0
      public/web/tree_sheet/tree_sheet_helper.js
  16. 0 10
      web/building_saas/css/main.css
  17. 40 4
      web/building_saas/main/html/main.html
  18. 23 0
      web/building_saas/main/js/controllers/project_controller.js
  19. 28 0
      web/building_saas/main/js/models/cache_tree.js
  20. 44 54
      web/building_saas/main/js/models/calc_program.js
  21. 4 3
      web/building_saas/main/js/models/fee_rate.js
  22. 63 3
      web/building_saas/main/js/models/project_glj.js
  23. 6 2
      web/building_saas/main/js/models/ration.js
  24. 30 14
      web/building_saas/main/js/views/calc_base_view.js
  25. 6 2
      web/building_saas/main/js/views/calc_program_manage.js
  26. 2 1
      web/building_saas/main/js/views/fee_rate_view.js
  27. 10 0
      web/building_saas/main/js/views/main_tree_col.js
  28. 50 15
      web/building_saas/main/js/views/project_glj_view.js
  29. 9 4
      web/building_saas/main/js/views/project_property_display_view.js
  30. 150 21
      web/building_saas/main/js/views/project_view.js
  31. 12 0
      web/building_saas/main/js/views/std_ration_lib.js
  32. 1 1
      web/building_saas/pm/js/pm_newMain.js
  33. 2 1
      web/common/html/header.html

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 3 - 3
lib/bootstrap/bootstrap.min.js


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 4 - 4
lib/bootstrap/css/bootstrap.min.css


+ 1 - 0
modules/all_models/calc_program_model.js

@@ -10,6 +10,7 @@ let calcItemSchema = new Schema({
     name: String,
     fieldName: String,
     dispExpr: String,
+    dispExprUser: String,
     expression: String,
     compiledExpr: String,
     statement: String,

+ 19 - 2
modules/glj/controllers/glj_controller.js

@@ -538,7 +538,7 @@ class GLJController extends BaseController {
         response.end('success');
     }
 
-    async  updateUnitPrice(req, res){
+    async updateUnitPrice(req, res){
         let result={
             error:0
         }
@@ -556,7 +556,24 @@ class GLJController extends BaseController {
         }
         res.json(result);
     }
-
+    async batchUpdatePrices(req, res){
+        let result={
+            error:0
+        }
+        try {
+            let data = req.body.data;
+            data = JSON.parse(data);
+            let unitPriceModel = new UnitPriceModel();
+            // 更新数据
+            let datas = await unitPriceModel.batchUpdatePrices(data);
+            result.data=datas;
+        }catch (err){
+            logger.err(err);
+            result.error=1;
+            result.message = err.message;
+        }
+        res.json(result);
+    }
     async modifyKeyValue(req,res){//修改工料机关键的属性:名称、类型、规格、型号等
         let result={
             error:0

+ 49 - 6
modules/glj/models/unit_price_model.js

@@ -250,12 +250,18 @@ class UnitPriceModel extends BaseModel {
 
     async updateUnitPrice(data){
         //查找并更新单价
-        let doc={};
+        let doc={},newValueMap={};
         doc[data.field]=data.newval;
+        newValueMap[data.id]=doc;
         let unitPrice = await this.db.findAndModify({id:data.id},doc);
         if(!unitPrice){
             throw "没有找到对应的单价";
         }
+        let rList= this.checkAndUpdateParent(unitPrice,data.field,data.project_id,newValueMap);
+        return rList;
+    }
+
+    async checkAndUpdateParent(unitPrice,field,project_id,newValueMap,batchUpdate=false){//检查是否属于某个工料机的组成物,如果是,并且不是批量更新的情况下,直接更新,如果是批量更新,返回更新任务
         //查找是否是属于某个项目工料机的组成物
         let mixRatioModel = new MixRatioModel();
         let condition = {unit_price_file_id:unitPrice.unit_price_file_id, code:unitPrice.code,name: unitPrice.name, specs: unitPrice.specs,unit:unitPrice.unit,type:unitPrice.type};
@@ -266,15 +272,34 @@ class UnitPriceModel extends BaseModel {
         if(mixRatioList&&mixRatioList.length>0){
             for(let m of mixRatioList){
                 if(!connectKeyMap.hasOwnProperty(m.connect_key)){//为了去重复,组成物会与其它项目同步,所以有可能重复。
-                    rList.push(await this.updateParentUnitPrice(m,data.field,data.project_id));
+                    rList.push(await this.updateParentUnitPrice(m,field,project_id,newValueMap,batchUpdate));
                     connectKeyMap[m.connect_key]=true;
                 }
             }
         }
         return rList;
     }
+    async batchUpdatePrices(data){//批量更新
+        let tasks = [];
+        let parentTask = [];
+        let newValueMap = {};
+        for(let d of data){//第一次循环生成更新提交的记录,并生成一个新值的映射表,为更新父节点使用
+            let condition = {id:d.unit_price.id};
+            let doc = {};
+            doc[d.field]=d.newval;
+            newValueMap[d.unit_price.id] = doc;
+            tasks.push(this.generateUpdateTask(condition,doc));
+        }
+        for(let d of data){//第二次更新父节点
+           let rList = await this.checkAndUpdateParent(d.unit_price,d.field,d.project_id,newValueMap,true);
+           parentTask = parentTask.concat(rList);
+        }
+        tasks = tasks.concat(parentTask);
+        tasks.length>0?this.model.bulkWrite(tasks):'';
+        return parentTask;
+    }
 
-    async updateParentUnitPrice(mixRatio,fieid,project_id){
+    async updateParentUnitPrice(mixRatio,fieid,project_id,newValueMap,batchUpdate){//batchUpdate 批量更新标记,如果true,只生成task
         let  decimalObject =await decimal_facade.getProjectDecimal(project_id);
         let quantity_decimal = (decimalObject&&decimalObject.glj&&decimalObject.glj.quantity)?decimalObject.glj.quantity:3;
         let price_decimal = (decimalObject&&decimalObject.glj&&decimalObject.glj.unitPrice)?decimalObject.glj.unitPrice:2;
@@ -301,6 +326,9 @@ class UnitPriceModel extends BaseModel {
         for(let pk in priceMap){
             let price = scMathUtil.roundForObj(priceMap[pk][fieid],price_decimal);
             let consumption = scMathUtil.roundForObj(mixRatioMap[pk].consumption,quantity_decimal);
+            if(newValueMap[priceMap[pk].id]){//是需要更新的记录,取当前新的值
+                price = scMathUtil.roundForObj(newValueMap[priceMap[pk].id][fieid],price_decimal);
+            }
             sumPrice +=scMathUtil.roundForObj(price*consumption,price_decimal);
         }
         sumPrice= scMathUtil.roundForObj(sumPrice,price_decimal);
@@ -320,10 +348,25 @@ class UnitPriceModel extends BaseModel {
         }
         let doc={};
         doc[fieid]=sumPrice;
-        let uprice =  await this.db.findAndModify(pcondition,doc);
-        uprice[fieid]=sumPrice;
-        return uprice;
+        if(batchUpdate == true){
+            return this.generateUpdateTask(pcondition,doc);
+        }else {
+            let uprice =  await this.db.findAndModify(pcondition,doc,{new: true});
+            //uprice[fieid]=sumPrice;
+            return uprice;
+        }
     }
+
+    generateUpdateTask(condition,doc) {
+        let task = {
+            updateOne:{
+                filter:condition,
+                update:doc
+            }
+        };
+        return task
+}
+
     /**
      * 复制单价文件数据
      *

+ 1 - 0
modules/glj/routes/glj_router.js

@@ -22,6 +22,7 @@ router.post('/change-file', gljController.init, gljController.changeUnitPriceFil
 router.post('/save-as', gljController.init, gljController.unitPriceSaveAs);
 router.post('/get-composition', gljController.init, gljController.getComposition);
 router.post('/updatePrice', gljController.init, gljController.updateUnitPrice);
+router.post('/batchUpdatePrices', gljController.init, gljController.batchUpdatePrices);
 router.post('/modifyKeyValue',gljController.init, gljController.modifyKeyValue);
 
 router.get('/test', gljController.init, gljController.test);

+ 270 - 1
modules/main/controllers/bills_controller.js

@@ -11,6 +11,20 @@ let bill_detail = require("../facade/bill_facade");
 let ration_glj = mongoose.model('ration_glj');
 let ration_coe = mongoose.model('ration_coe');
 let rationInstallationModel = mongoose.model('ration_installation');
+import fixedFlag from  '../../common/const/bills_fixed';
+const uuidV1 = require('uuid/v1');
+const billType ={
+    DXFY:1,//大项费用
+    FB:2,//分部
+    FX:3,//分项
+    BILL:4,//清单
+    BX:5//补项
+};
+// 上传控件
+const multiparty = require("multiparty");
+const fs = require("fs");
+// excel解析
+const excel = require("node-xlsx");
 //统一回调函数
 var callback = function(req, res, err, message, data){
     res.json({error: err, message: message, data: data});
@@ -147,10 +161,265 @@ module.exports = {
             result.message = err.message;
         }
         res.json(result);
+    },
+    upload: async function(req, res){
+        let responseData = {
+            err: 0,
+            msg: '',
+            data: null
+        };
+        const allowHeader = ['application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'];
+        const uploadOption = {
+            uploadDir: './public'
+        };
+        const form = new multiparty.Form(uploadOption);
+        form.parse(req, async function(err, fields, files) {
+            try{
+                const projectID = fields.projectID !== undefined && fields.projectID.length > 0 ?
+                    parseInt(fields.projectID[0]) : 0;
+                if (projectID <= 0) {
+                    throw '参数错误';
+                }
+                const file = files.file !== undefined ? files.file[0] : null;
+                if (err || file === null) {
+                    throw '上传失败';
+                }
+                // 判断类型
+                if (file.headers['content-type'] === undefined || allowHeader.indexOf(file.headers['content-type']) < 0) {
+                    throw '不支持该类型';
+                }
+                // 重命名文件名
+                const uploadFullName = uploadOption.uploadDir + '/' + file.originalFilename;
+                fs.renameSync(file.path, uploadFullName);
+
+                const sheet = excel.parse(uploadFullName);
+                if (sheet[0] === undefined || sheet[0].data === undefined) {
+                    throw 'excel没有对应数据';
+                }
+                //导入的数据是否含有固定行(分部分项、施工技术措施项目、施工组织措施项目)
+                let flag = getImportFlag(file.originalFilename);
+                if(!flag){
+                    throw 'excel数据错误';
+                }
+                let fixedBill = await billsData.model.findOne({projectID: projectID, 'flags.flag': flag, deleteInfo: null});
+                let insertFixedBill = null;
+                //导入xx措施项目,若不存在此固定清单,则先插入相关固定清单
+                if(!fixedBill){
+                    //分部分项工程(不可删除)应存在
+                    if(flag === fixedFlag.SUB_ENGINERRING){
+                        throw '项目不存在分部分项工程'
+                    }
+                    //措施项目是否存在
+                    let csxm = await billsData.model.findOne({projectID: projectID, 'flags.flag': fixedFlag.MEASURE, deleteInfo: null});
+                    if(!csxm){
+                        throw '项目不存在措施项目'
+                    }
+                    //插入清单固定行(施工技术措施项目、施工组织措施项目可删除)
+                    insertFixedBill = {projectID: projectID, name: flag === fixedFlag.CONSTRUCTION_TECH ? '施工技术措施项目' : '施工组织措施项目', code: '1',
+                        ID: uuidV1(), NextSiblingID: -1, ParentID: csxm.ID, flags: [{fieldName: 'fixed', flag: flag}], type: billType.BILL};
+                    //更新前节点
+                    let preDatas = await billsData.model.find({projectID: projectID, ParentID: csxm.ID, deleteInfo: null});
+                    for(let preData of preDatas){
+                        if(preData.NextSiblingID == -1){
+                            await billsData.model.update({ID: preData.ID}, {$set: {NextSiblingID: insertFixedBill.ID}});
+                            break;
+                        }
+                    }
+                    await billsData.model.create(insertFixedBill);
+                    fixedBill = insertFixedBill;
+                }
+                //将excel数据转换成清单树结构数据
+                let insertDatas = parseToBillData(getValidImportData(sheet[0].data), getColMapping(sheet[0].data), fixedBill, projectID);
+                //删除相关数据
+                let deleteDatas = await billsData.deepDeleteBill([fixedBill], req.session.sessionUser.ssoId);
+                //新增清单数据
+                await billsData.importBills(insertDatas);
+                //返回数据以更新前端
+                if(insertFixedBill){
+                    insertDatas.push(insertFixedBill);
+                }
+                responseData.data = {fixedBill: fixedBill, insert: {bill: insertDatas, ration: []}, remove: {bill: deleteDatas.bill, ration: deleteDatas.ration}};
+                //删除暂存文件
+                fs.unlink(uploadFullName);
+                res.json(responseData);
+            }
+            catch (error){
+                responseData.err = 1;
+                responseData.msg = error;
+                res.json(responseData);
+            }
+
+        });
     }
 
 };
 
+//提取excel表头列对应数据
+function getColMapping(sheetData){
+    //获取表头
+    let headRow = [];
+    for(let rData of sheetData) {
+        if (rData[0] && rData[0].toString().replace(/\s/g, '') === '序号') {
+            headRow = rData;
+            break;
+        }
+    }
+    //获取表头列与列号对应关系
+    let colMapping = {};
+    for(let c = 0; c < headRow.length; c++){
+        if(headRow[c]){
+            headRow[c] = headRow[c].toString().replace(/\s/g, '');
+            switch(headRow[c]){
+                case '序号': colMapping.serialNo = c; break;
+                case '项目编码': colMapping.code = c; break;
+                case '项目名称': colMapping.name = c; break;
+                case '项目特征': colMapping.itemCharacterText = c; break;
+                case '计量单位': colMapping.unit = c; break;
+                case '工程量': colMapping.quantity = c; break;
+            }
+
+        }
+    }
+    return colMapping;
+}
+function rowExistData(rowData){
+    for(let cData of rowData){
+        if(cData !== undefined && cData !== ''){
+            return true;
+        }
+    }
+    return false;
+}
+//提取excel表数据中的有效数据(去表头表尾,提取其中的excel数据)
+function getValidImportData(sheetData){
+    let withingD = false;
+    let validData = [];
+    for(let r = 0; r < sheetData.length; r++){
+        let rData = sheetData[r];
+        if(rData[0]){
+            //首列去空格
+            rData[0] = rData[0].toString().replace(/\s/g, '');
+            //表头
+            if(rData[0] === '序号'){
+                withingD = true;
+                r++;
+                continue;
+            }
+            //表尾
+            else if(rData[0] === '本页小计' || rData[0] === '合计'){
+                withingD = false;
+            }
+        }
+        if(withingD && rowExistData(rData)){
+            validData.push(rData);
+        }
+    }
+    return validData;
+}
+
+function getImportFlag(sheetName){
+    const fixedItem = {'分部分项': fixedFlag.SUB_ENGINERRING, '施工技术措施项目': fixedFlag.CONSTRUCTION_TECH, '施工组织措施项目': fixedFlag.CONSTRUCTION_ORGANIZATION};
+    for(let flag in fixedItem){
+        if(sheetName.indexOf(flag) > 0){
+            return fixedItem[flag];
+        }
+    }
+    return null;
+}
+//excel数据转换成清单数据
+function parseToBillData(validData, colMapping, fixedBill, projectID){
+    let rst = [];
+    let billIdx = {};
+    let preRootID = -1,
+        preLeafID = -1,
+        preID = -1;
+    //合并了项目特征,且行有数据
+    function isRoot(rData){
+        return rData[colMapping.itemCharacterText] !== undefined && rData[colMapping.itemCharacterText] === '' && rowExistData(rData);
+    }
+    //不合并且有序号
+    function isLeaf(rData){
+        return (rData[colMapping] === undefined || rData[colMapping] !== '') && rData[colMapping.serialNo] && rData[colMapping.serialNo] !== '';
+    }
+    //续数据,上一行数据是有效节点且无序号
+    function isExtend(preData, rData){
+        return preData && (isRoot(preData) || isLeaf(preData)) && (rData[colMapping.serialNo] === undefined || rData[colMapping.serialNo] === '');
+    }
+    function getBillType(rData, flag){
+        if(flag === fixedFlag.CONSTRUCTION_TECH || flag === fixedFlag.CONSTRUCTION_ORGANIZATION){
+            return billType.BILL;
+        }
+        else if(flag === fixedFlag.SUB_ENGINERRING){
+            return isLeaf(rData) ? billType.FX : billType.FB;
+        }
+        return null;
+    }
+    for(let r = 0; r < validData.length; r++){
+        let preData = validData[r-1],
+            rData = validData[r];
+        if(fixedBill.flags[0].flag == fixedFlag.CONSTRUCTION_TECH && rData[colMapping.name] === '施工技术措施项目'
+            || fixedBill.flags[0].flag == fixedFlag.CONSTRUCTION_ORGANIZATION && rData[colMapping.name] === '施工组织措施项目'){
+            continue;
+        }
+        //过滤无效数据
+        if(!isRoot(rData) && !isLeaf(rData) && !isExtend(preData, rData)){
+            continue;
+        }
+        if(isExtend(preData, rData)){
+            let preBill = billIdx[preID];
+            //合并续数据
+            if(preBill){
+                preBill.code += rData[colMapping.code] ? rData[colMapping.code] : '';
+                preBill.name += rData[colMapping.name] ? rData[colMapping.name] : '';
+                preBill.itemCharacterText += rData[colMapping.itemCharacterText] ? rData[colMapping.itemCharacterText] : '';
+                preBill.unit += rData[colMapping.unit] ? rData[colMapping.unit] : '';
+                preBill.quantity += rData[colMapping.quantity] ? rData[colMapping.quantity] : '';
+            }
+        }
+        else {
+            let newID = uuidV1();
+            let pID = -1;
+            let preBill = null;
+            if(isRoot(rData)){
+                pID = fixedBill.ID;
+                preBill = billIdx[preRootID];
+            }
+            else if(isLeaf(rData)){
+                pID = preRootID !== -1 ? preRootID : fixedBill.ID;
+                preBill = billIdx[preLeafID];
+            }
+            //set bill data
+            billIdx[newID] = {
+                ID: newID, ParentID: pID, NextSiblingID: -1,
+                code: rData[colMapping.code] ? rData[colMapping.code] : '',
+                name: rData[colMapping.name] ? rData[colMapping.name] : '',
+                itemCharacterText: rData[colMapping.itemCharacterText] ? rData[colMapping.itemCharacterText] : '',
+                itemCharacter: [],
+                jobContentText: '',
+                jobContent: [],
+                unit: rData[colMapping.unit] ? rData[colMapping.unit] : '',
+                quantity: rData[colMapping.quantity] ? rData[colMapping.quantity] : '',
+                flags: fixedBill.flags[0].flag === fixedFlag.CONSTRUCTION_ORGANIZATION && rData[colMapping.name] === '安全文明施工专项费用' ?
+                    [{fieldName: 'fixed', flag: fixedBill.flags[0].flag}] : [],
+                fees: [],
+                projectID: projectID,
+                type: getBillType(rData, fixedBill.flags[0].flag)};
+            //update preBill NextSibling
+            if(preBill){
+                preBill.NextSiblingID = newID;
+            }
+            //set new preID
+            preID = newID;
+            preRootID = isRoot(rData) ? newID : preRootID;
+            preLeafID = isLeaf(rData) ? newID : preLeafID;
+        }
+    }
+    for(let i in billIdx){
+        rst.push(billIdx[i]);
+    }
+    return rst;
+}
+
 async function doBillsOrRationsDelete(data) {
     let billTask = [];
     let deleteBillIDs = [];
@@ -200,7 +469,7 @@ async function doBillsOrRationsDelete(data) {
         await rationInstallationModel.deleteMany(sub_query);//删除安装增加费
     }
     if(rationTask.length>0){
-        await  ration_model.model.bulkWrite(rationTask);//删除定额
+        await ration_model.model.bulkWrite(rationTask);//删除定额
     }
     if(billTask.length>0){
         await billsData.model.bulkWrite(billTask);//删除清单

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

@@ -9,9 +9,23 @@ let projectConsts = consts.projectConst;
 let commonConsts = consts.commonConst;
 let quantity_detial = require('../facade/quantity_detail_facade');
 let projectModel = mongoose.model('projects');
+let rationModel = mongoose.model('ration');
+let rationGljModel = mongoose.model('ration_glj');
+let rationCoeModel = mongoose.model('ration_coe');
+let rationInstModel = mongoose.model('ration_installation');
+let quantityDelModel = mongoose.model('quantity_detail');
+const fixedFlag = require('../../common/const/bills_fixed');
 
 let bills = mongoose.model("bills");
 let baseModel = require('./base_model');
+const uuidV1 = require('uuid/v1');
+const billType ={
+    DXFY:1,//大项费用
+    FB:2,//分部
+    FX:3,//分项
+    BILL:4,//清单
+    BX:5//补项
+};
 
 class billsModel extends baseModel {
     constructor () {
@@ -123,6 +137,63 @@ class billsModel extends baseModel {
 
         return bills.update(findSet, update);
     };
+    async importBills(bills){
+        let operations = [];
+        for(let bill of bills){
+            operations.push({insertOne: {document: bill}});
+        }
+        return await this.model.bulkWrite(operations);
+    }
+    //删除清单节点的所有子节点及其他附带数据
+    async deepDeleteBill(bills, userID){
+        let bill_ids = [],
+            ration_ids = [];
+        let me = this;
+        async function findBillsChildren(bills){
+            if(bills.length > 0){
+                let ids = getIDs(bills);
+                bill_ids = bill_ids.concat(ids);
+                let findBills = await me.model.find({ParentID: {$in: ids}, deleteInfo: null});
+                await findBillsChildren(findBills);
+            }
+        }
+        function getIDs(datas){
+            let ids = [];
+            for(let data of datas){
+                ids.push(data.ID);
+            }
+            return ids;
+        }
+        await findBillsChildren(bills);
+        //剔除第一个节点
+        bill_ids = bill_ids.slice(1);
+        //获取删除清单下的所有定额
+        let rations = await rationModel.find({billsItemID: {$in: bill_ids}, deleteInfo: null}, 'ID');
+        ration_ids = ration_ids.concat(getIDs(rations));
+        //deep delete datas
+        let deleteInfo = {deleted: true, deleteDateTime: new Date(), deleteBy: userID};
+        console.log(`bill_ids`);
+        console.log(bill_ids);
+        if(bill_ids.length > 0){
+            //删除bills
+            await me.model.updateMany({ID: {$in: bill_ids}, deleteInfo: null}, {$set: {deleteInfo: deleteInfo}});
+            //删除bill-quantity_detail
+            await quantityDelModel.deleteMany({billID: {$in: bill_ids}});
+        }
+        if(ration_ids.length > 0){
+            //删除rations
+            await rationModel.updateMany({ID: {$in: ration_ids}, deleteInfo: null}, {$set: {deleteInfo: deleteInfo}});
+            //删除ration-glj
+            await rationGljModel.deleteMany({rationID: {$in: ration_ids}});
+            //删除ration-coe
+            await rationCoeModel.deleteMany({rationID: {$in: ration_ids}});
+            //删除ration-installation
+            await rationInstModel.deleteMany({rationID: {$in: ration_ids}});
+            //删除ration-quantity_detail
+            await quantityDelModel.deleteMany({rationID: {$in: ration_ids}});
+        }
+        return {bill: bill_ids, ration: ration_ids};
+    }
 }
 
 module.exports = new billsModel();

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

@@ -17,6 +17,7 @@ module.exports = function (app) {
     billsRouter.post('/multiDelete',billsController.multiDelete);
     billsRouter.post('/getSectionInfo', billsController.getSectionInfo);
     billsRouter.post('/reorganizeFBFX', billsController.reorganizeFBFX);
+    billsRouter.post('/upload', billsController.upload);
     app.use('/bills', billsRouter);
 };
 

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

@@ -13,6 +13,8 @@ const defaultDecimal = {
 };
 const displaySetting = {
     autoHeight:true,
+    billsAutoHeight:true,
+    rationAutoHeight:false,
     disPlayMainMaterial:true
 }
 

+ 3 - 1
package.json

@@ -49,7 +49,9 @@
     "gulp-concat": "^2.6.1",
     "main-bower-files": "^2.5.0",
     "wiredep": "^2.2.2",
-    "gulp-uglify-es":"^0.1.3"
+    "gulp-uglify-es":"^0.1.3",
+    "multiparty": "^4.1.3",
+    "node-xlsx": "^0.11.2"
   },
   "scripts": {
     "start": "C:\\Users\\mai\\AppData\\Roaming\\npm\\babel-node.cmd server.js"

+ 26 - 1
public/web/id_tree.js

@@ -562,7 +562,32 @@ var idTree = {
                 return node;
             }
         };
-
+        //批量新增节点,节点已有树结构数据
+        Tree.prototype.insertByDatas = function (datas) {
+            for(let data of datas){
+                this.nodes[this.prefix + data.ID] = new Node(this, data.ID);
+                this.nodes[this.prefix + data.ID]['data'] = data;
+            }
+            for(let data of datas){
+                let node = this.nodes[this.prefix + data.ID];
+                let parent = data.ParentID == -1 ? null : this.nodes[this.prefix + data.ParentID];
+                node.parent = parent;
+                if(!parent){
+                    this.roots.push(node);
+                }
+                else {
+                    parent.children.push(node);
+                }
+                let next = data.NextSiblingID == -1 ? null : this.nodes[this.prefix + data.NextSiblingID];
+                node.nextSibling = next;
+                if(next){
+                    next.preSibling = node;
+                }
+            }
+            //resort
+            this.roots = tools.reSortNodes(this.roots, true);
+            tools.sortTreeItems(this);
+        };
         Tree.prototype.delete = function (node) {
             var success = false;
             success=this.cascadeRemove(node);

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

@@ -159,7 +159,7 @@ var sheetCommonObj = {
                     val =scMathUtil.roundToString(val,decimal);
                     sheet.setFormatter(-1, col,getFormatter(decimal), GC.Spread.Sheets.SheetArea.viewport);
                 }else {
-                    val =scMathUtil.roundToString(val,2);
+                    val =val+'';
                 }
             }
             if(val!=null&&setting.header[col].cellType === "checkBox"){

+ 4 - 1
public/web/tree_sheet/tree_sheet_controller.js

@@ -56,7 +56,7 @@ var TREE_SHEET_CONTROLLER = {
                 }
             }
         };
-        controller.prototype.m_delete = function (nodes) {//删除选中的多行节点
+        controller.prototype.m_delete = function (nodes, beginRow = null) {//删除选中的多行节点
             var that = this, sels = this.sheet.getSelections();
             if (this.tree.selected) {
                 if (this.tree.m_delete(nodes)) {
@@ -65,6 +65,9 @@ var TREE_SHEET_CONTROLLER = {
                         for(let node of nodes){
                             rowCount = rowCount+node.posterityCount() + 1;
                         }
+                        if(beginRow){
+                            sels[0].row = beginRow;
+                        }
                         that.sheet.deleteRows(sels[0].row, rowCount);
                         that.setTreeSelected(that.tree.items[sels[0].row]);
                         that.sheet.setSelection(sels[0].row,sels[0].col,1,sels[0].colCount);

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

@@ -156,6 +156,9 @@ var TREE_SHEET_HELPER = {
                 if (colSetting.data.cellType && Object.prototype.toString.apply(colSetting.data.cellType) !== "[object String]") {
                     cell.cellType(colSetting.data.cellType(node));
                 }
+                if(colSetting.data.autoHeight == true){
+                    colSetting.setAutoHeight(cell,node);
+                }
                 if(sheet.name()=='mainSheet'&&gljOprObj.isInstallationNode(node)){//如果是通过安装增加费自动生成的,都是只读类型
                     cell.locked(true);
                 }else if (colSetting.readOnly) {
@@ -343,10 +346,15 @@ var TREE_SHEET_HELPER = {
             let textLength = this.getAutoFitWidth(value, text, acStyle, zoom, {sheet: hitinfo.sheet, row: hitinfo.row, col: hitinfo.col, sheetArea: GC.Spread.Sheets.SheetArea.viewport});
             let cellWidth = hitinfo.sheet.getCell(-1, hitinfo.col).width();
             let dataField = setting.cols[hitinfo.col].data.field;
+
+            if(hitinfo.sheet.getCell(hitinfo.row,hitinfo.col).wordWrap()==true){
+                return;
+            }
             if(dataField === 'name' || dataField === 'itemCharacterText' || dataField === 'jobContentText' || dataField === 'adjustState'){
                 if(hitinfo.sheet.getParent() === projectObj.mainSpread && textLength <= cellWidth)
                  return;
             }
+
             if(dataField=="quantity"){
                 text = tag;
             }else if(tag !== undefined && tag) {

+ 0 - 10
web/building_saas/css/main.css

@@ -33,7 +33,6 @@ body {
     width:100%;
     z-index: 999
 }
-/*add*/
 .in-1{padding-left:0px!important}
 .in-2{padding-left:21px!important}
 .in-3{padding-left:42px!important}
@@ -277,12 +276,10 @@ body {
 .poj-list, .side-content ,.form-view{
     overflow: auto;
 }
-/*add*/
 .poj-list span.poj-icon {
     padding-right:7px;
     color:#ccc
 }
-/*add*/
 .poj-list a.tree-open,.poj-list a.tree-close{
     width:15px;
     display: inline-block;
@@ -349,33 +346,26 @@ body {
 .dropdown-item.disabled, .dropdown-item:disabled{
     pointer-events:none
 }
-/*add*/
 .text-green{
     color: #172a30
 }
-/*add*/
 label.title{
     display: inline-block;
     width: 100px;
 }
-/*add*/
 .modal-feeRate {max-width: 550px}
-/*add*/
 .gc-column-header-cell{
     text-align: center!important;
 }
-/*add*/
 div.resize{
     height: 4px;
     background: #f7f7f9;
     width: 100%;
     cursor: s-resize;
 }
-/*add*/
 .zlfb-check{
     margin-left: 0;
 }
-/*add*/
 legend.legend{
     display:block;
     width:auto;

+ 40 - 4
web/building_saas/main/html/main.html

@@ -480,11 +480,23 @@
                                 <div class="tab-pane fade" id="display-setting" role="tabpanel">
                                     <div class="modal-auto-height" style="overflow: hidden">
                                         <fieldset class="form-group">
-                                            <div class="form-check">
+                                           <!-- <div class="form-check">
                                                 <label class="form-check-label">
                                                     <input class="form-check-input" id="autoHeight" type="checkbox">
                                                     造价书表格自动调整行高
                                                 </label>
+                                            </div>-->
+                                            <div class="form-check">
+                                                <label class="form-check-label">
+                                                    <input class="form-check-input" id="billsAutoHeight" type="checkbox">
+                                                    工程量清单自动调整行高
+                                                </label>
+                                            </div>
+                                            <div class="form-check">
+                                                <label class="form-check-label">
+                                                    <input class="form-check-input" id="rationAutoHeight" type="checkbox">
+                                                    定额子目自动调整行高
+                                                </label>
                                             </div>
                                             <div class="form-check">
                                                 <label class="form-check-label">
@@ -1045,6 +1057,33 @@
             </div>
         </div>
     </div>
+
+    <!--弹出 导入-->
+    <div class="modal fade" id="import" data-backdrop="static">
+        <div class="modal-dialog" role="document">
+            <div class="modal-content">
+                <div class="modal-header">
+                    <h5 class="modal-title">导入Excel清单</h5>
+                    <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                        <span aria-hidden="true">&times;</span>
+                    </button>
+                </div>
+                <div class="modal-body">
+                    <div class="custom-file">
+                        <input type="file" class="custom-file-input" id="customFile" lang="zh" accept=".xls,.xlsx">
+                        <label class="custom-file-label" for="customFile">请选择上传文件</label>
+                    </div>
+                    <div class="alert alert-success mt-3" id="uploadAlert" role="alert" style="display: none;">
+                        广东XXXX项目清单.xlsx 准备导入上传
+                    </div>
+                </div>
+                <div class="modal-footer">
+                    <button type="button" class="btn btn-secondary" data-dismiss="modal">关闭</button>
+                    <a href="javascript:void(0);" class="btn btn-primary" id="uploadConfirm">确定导入</a>
+                </div>
+            </div>
+        </div>
+    </div>
         <!-- JS. -->
         <script type="text/javascript" src="/lib/spreadjs/sheets/gc.spread.sheets.all.10.0.1.min.js"></script>
 
@@ -1225,9 +1264,6 @@
 
             $(document).ready(function(){
                 //createTree();
-
-
-
               /*  document.onkeydown=keydown;
                 function keydown(e){
                    alert('aa');

+ 23 - 0
web/building_saas/main/js/controllers/project_controller.js

@@ -15,6 +15,29 @@ ProjectController = {
             cbTools.refreshFormulaNodes();
         });
     },
+    syncDisplayNewNodes: function (sc, newNodes) {
+        TREE_SHEET_HELPER.massOperationSheet(sc.sheet, function () {
+            var sels = sc.sheet.getSelections();
+            newNodes.sort(function (a, b) {
+                let rst = 0;
+                if(a.serialNo() > b.serialNo()){
+                    rst = 1;
+                }
+                else {
+                    rst = -1;
+                }
+                return rst;
+            });
+            for(let newNode of newNodes){
+                sc.sheet.addRows(newNode.serialNo(), 1);
+                TREE_SHEET_HELPER.refreshTreeNodeData(sc.setting, sc.sheet, [newNode], false);
+                sc.setTreeSelected(newNode);
+                sc.sheet.setSelection(newNode.serialNo(), sels[0].col, 1, 1);
+                sc.sheet.showRow(newNode.serialNo(), GC.Spread.Sheets.VerticalPosition.center);
+            }
+            cbTools.refreshFormulaNodes();
+        });
+    },
     syncDisplayNewRationGljNode:function (sc,newNode) {
         TREE_SHEET_HELPER.massOperationSheet(sc.sheet, function () {
             sc.sheet.addRows(newNode.serialNo(), 1);

+ 28 - 0
web/building_saas/main/js/models/cache_tree.js

@@ -368,6 +368,34 @@ var cacheTree = {
 
             return newNode;
         };
+        Tree.prototype.insertByDatas = function (datas) {
+            let rst = [];
+            for(let data of datas){
+                this.nodes[this.prefix + data.ID] = new Node(this, data.ID);
+                rst.push(this.nodes[this.prefix + data.ID]);
+            }
+            for(let data of datas){
+                let node = this.nodes[this.prefix + data.ID];
+                let parent = data.ParentID == -1 ? null : this.nodes[this.prefix + data.ParentID];
+                node.parent = parent;
+                if(!parent){
+                    this.roots.push(node);
+                }
+                else {
+                    parent.children.push(node);
+                }
+                let next = data.NextSiblingID == -1 ? null : this.nodes[this.prefix + data.NextSiblingID];
+                node.nextSibling = next;
+                if(next){
+                    next.preSibling = node;
+                }
+            }
+            //resort
+            this.roots = tools.reSortNodes(this.roots, true);
+            this.sortTreeItems();
+            console.log(this);
+            return rst;
+        };
         Tree.prototype.delete = function (node) {
             var success = false;
             success=this.cascadeRemove(node);

+ 44 - 54
web/building_saas/main/js/models/calc_program.js

@@ -728,30 +728,23 @@ let analyzer = {
         let idx = FName.slice(1) - 1;
         return calcTemplate.calcItems[idx];
     },
-    isCycleCalc: function (expression, calcTemplate) {     // 这里判断expression,是ID引用: @5+@6
-        let me = analyzer;
-        
-        function isCycle(nodeExpr, template) {
-            let atIDArr = me.getAtIDArr(nodeExpr);
-            for (let atID of atIDArr){
-                let ID = atID.slice(1);
-                let item = template.compiledCalcItems[ID];
-                if (item.expression.includes(atID)) {
-                    return true;
-                }
-                else {
-                    isCycle(item.expression, template);
-                }
-            };
-            return false;
-        };
+    isCycleCalc: function (expression, ID, template) {     // 这里判断expression,是ID引用: @5+@6
+        if (expression.includes(`@${ID}`))
+            return true;
+
+        let atIDs = analyzer.getAtIDArr(expression);
+        for (let atID of atIDs) {
+            let item = template.compiledCalcItems[atID.slice(1)];
+            if (analyzer.isCycleCalc(item.expression, ID, template))
+                return true;
+        }
 
-        return isCycle(expression, calcTemplate);
+        return false;
     },
-    isLegal: function (dispExpr, calcTemplate) {  // 检测包括:无效字符、基数是否中括号、基数是否定义、行引用、循环计算
+    isLegal: function (dispExpr, itemID, calcTemplate) {  // 检测包括:无效字符、基数是否中括号、基数是否定义、行引用、循环计算
         let me = analyzer;
         let expr = me.standard(dispExpr);
-        let invalidChars = /[^0-9\u4e00-\u9fa5\+\-\*\/\(\)\.\[\]F%]/g;
+        let invalidChars = /[^0-9\u4e00-\u9fa5\+\-\*\/\(\)\.\[\]FL%]/g;
         if (invalidChars.test(expr)){
             alert('表达式中含有无效的字符!');
             return false;
@@ -761,7 +754,11 @@ let analyzer = {
         let arrCn = expr.match(pattCn);
         let pattBase = new RegExp(/\[[\u4E00-\u9FA5]+\]/gi);
         let arrBase = expr.match(pattBase);
-        if (arrCn.length != arrBase.length){
+        if (arrCn && !arrBase){
+            alert('定额基数必须用中括号[]括起来!');
+            return false;
+        };
+        if (arrCn && arrBase && (arrCn.length != arrBase.length)){
             for (let cn of arrCn){
                 let tempBase = `[${cn}]`;
                   if (!arrBase.includes(tempBase)){
@@ -773,13 +770,14 @@ let analyzer = {
             alert('定额基数必须用中括号[]括起来!');
             return false;
         };
-
-        for (let base of arrBase){
-            let baseName = base.slice(1, -1);
-            if (!rationCalcBases[baseName]){
-                alert('定额基数 [' + baseName + '] 末定义!');
-                return false;
-            }
+        if (arrBase){
+            for (let base of arrBase){
+                let baseName = base.slice(1, -1);
+                if (!rationCalcBases[baseName]){
+                    alert('定额基数 [' + baseName + '] 末定义!');
+                    return false;
+                }
+            };
         };
 
         let arrF = me.getFArr(expr);
@@ -792,26 +790,13 @@ let analyzer = {
         };
 
         let expression = me.getExpression(expr, calcTemplate);
-        if (me.isCycleCalc(expression, calcTemplate)){
+        if (me.isCycleCalc(expression, itemID, calcTemplate)){
             alert('表达式中有循环计算!');
             return false;
         }
 
-        return true;
-    },
-    analyzeUserExpr: function(calcItem, calcTemplate){
-        let me = analyzer;
-        let expr = calcItem.dispExpr;
-        expr = me.standard(expr);
-        calcItem.dispExpr = expr;
-
-        if (me.isLegal(expr, calcTemplate)){
-            calcItem.expression = expr;
-            return true;
-        }else
-            return false;
+        return true;  // 表达式合法
     },
-
     getExpression: function (dispExpr, calcTemplate) {
         function refLineToID(expr, template) {
             let rst = expr;
@@ -833,7 +818,13 @@ let analyzer = {
         let expr = me.standard(dispExpr);
         return refLineToID(expr, calcTemplate);
     },
-    getCompiledExpr: function (expression) {
+    getDispExprUser: function (dispExpr, labourCoe) {   // labourCoe 是 str 类型
+        let me = analyzer;
+        let expr = me.standard(dispExpr);
+        expr = expr.replace(/L/g, labourCoe);
+        return expr;
+    },
+    getCompiledExpr: function (expression, labourCoe) {   // labourCoe 是 str 类型
         let me = analyzer;
         let rst = expression;
         let atIDArr = me.getAtIDArr(rst);
@@ -847,6 +838,7 @@ let analyzer = {
         };
         rst = rst.replace(/\[/g, "$CE.base('");
         rst = rst.replace(/\]/g, "')");
+        rst = rst.replace(/L/g, labourCoe);
         return rst;
     }
 };
@@ -1060,22 +1052,20 @@ class CalcProgram {
         let private_compile_items = function() {
             for (let idx of template.compiledSeq) {
                 let item = template.calcItems[idx];
-                item.dispExprUser = item.dispExpr;    // 用于界面显示。disExpr是公式模板,不允许修改:人工系数占位符被修改后变成数值,第二次无法正确替换。
+
+                let lc = 0;
+                if (item.labourCoeID) lc = me.compiledLabourCoes[item.labourCoeID].coe.toString();
+                // 用于界面显示。disExpr是公式模板,不允许修改:人工系数占位符被修改后变成数值,第二次无法正确替换。
+                item.dispExprUser = analyzer.getDispExprUser(item.dispExpr, lc);
                 if (item.expression == 'HJ')
                     item.compiledExpr = '$CE.HJ()'
                 else
-                    item.compiledExpr = analyzer.getCompiledExpr(item.expression);
-
-                if (item.labourCoeID){
-                    let lc = me.compiledLabourCoes[item.labourCoeID].coe;
-                    item.dispExprUser = item.dispExpr.replace(/L/gi, lc.toString());
-                    item.compiledExpr = item.compiledExpr.replace(/L/gi, lc.toString());
-                };
+                    item.compiledExpr = analyzer.getCompiledExpr(item.expression, lc);
 
                 if (item.feeRateID) {
                     let orgFeeRate = item.feeRate;
-                    let cmf = me.compiledFeeRates[item.feeRateID];
-                    item.feeRate = cmf?cmf.rate:100;
+                    let cfr = me.compiledFeeRates[item.feeRateID];
+                    item.feeRate = cfr ? cfr.rate : 100;
 
                     if (!orgFeeRate || (orgFeeRate && orgFeeRate != item.feeRate)){
                         me.saveForReports.push({templatesID: template.ID, calcItem: item});

+ 4 - 3
web/building_saas/main/js/models/fee_rate.js

@@ -209,6 +209,7 @@ var FeeRate = {
             if(socketObject.roomInfo){
                 //判断费率文件ID是否改变了
                 if(socketObject.roomInfo.feeRate == this.getActivateFeeRateFileID()){//如果没变,则是重选了标准
+                    project.markUpdateProject({projectID:project.ID(),feeRateID:this.getActivateFeeRateFileID()},"feeRate");
                     socket.emit('feeRateChangeNotify', socketObject.roomInfo.feeRate);
                 }else {
                     let data ={
@@ -216,7 +217,7 @@ var FeeRate = {
                         oldRoom:socketObject.roomInfo.feeRate,
                         newRoom:this.getActivateFeeRateFileID(),
                         name:'feeRate'
-                    }
+                    };
                     socket.emit('changeNewRoom',data);
                     socketObject.roomInfo.feeRate = this.getActivateFeeRateFileID();
                 }
@@ -274,14 +275,14 @@ var FeeRate = {
             calcProgramManage.refreshDetailSheet();
         };
         FeeRate.prototype.refreshBillsByRateID=function(rateID,value){
-            var nodes = _.filter(projectObj.project.mainTree.items,function (n) {
+            let nodes = _.filter(projectObj.project.mainTree.items,function (n) {
                 if(n.sourceType==ModuleNames.bills&&n.data.feeRateID==rateID){
                     n.data.feeRate=number_util.roundToString(value,getDecimal("feeRate"));
                     return true;
                 }else {
                     return false;
                 }
-            })
+            });
             if(nodes.length>0){
                 projectObj.mainController.refreshTreeNode(nodes)
             }

+ 63 - 3
web/building_saas/main/js/models/project_glj.js

@@ -270,7 +270,7 @@ ProjectGLJ.prototype.updatePrice = function (recode, updateField, newval,from,cb
             }
             //更新回传的父节点项目工料机价格
            let gljs = me.getProjectGLJs(data);
-            me.refreshRationGLJPrice(glj);//刷新定额工料机列表的记录
+          //  me.refreshRationGLJPrice(glj);//刷新定额工料机列表的记录
             projectObj.project.projectGLJ.loadCacheData();//更新工料机汇总缓存和显示
             gljOprObj.showRationGLJSheetData();
             me.refreshTreeNodePriceIfNeed(glj);//刷新造价书中主树上的定额工料机;
@@ -293,6 +293,66 @@ ProjectGLJ.prototype.updatePrice = function (recode, updateField, newval,from,cb
     }
 };
 
+ProjectGLJ.prototype.batchUpdatePrice = function (changeInfo,callback) {
+    let me = this;
+    let projectGljs = me.datas.gljList;
+    let decimal = getDecimal('glj.unitPrice');
+    let updateData = [];
+    let newValueMap = {};
+    let gljs=[];
+    for(let ci of changeInfo){
+        let dataCode = projectGljObject.projectGljSetting.header[ci.col].dataCode;
+        let recode = projectGljObject.projectGljSheetData[ci.row];
+        if(dataCode=='basePrice'||dataCode=='marketPrice'){
+            let editField = dataCode === 'basePrice'?"base_price":"market_price";
+            let newValue= scMathUtil.roundForObj(ci.value,decimal);
+            let glj = _.find(projectGljs, {'id': recode.id});
+            if(glj&&glj.unit_price[editField]!=newValue){
+                updateData.push({unit_price: glj.unit_price, field: editField, newval: newValue,project_id:glj.project_id});
+                newValueMap[glj.id]={field:editField,value:newValue};
+                gljs.push(glj);
+            }
+        }
+    }
+    console.log(updateData);
+    if(updateData.length > 0){
+        $.bootstrapLoading.start();
+        CommonAjax.post("/glj/batchUpdatePrices", updateData, function (result) {
+            let parentData = [];
+            //更新缓存
+            for(let g of gljs){
+                g.unit_price[newValueMap[g.id].field] = newValueMap[g.id].value;
+                me.refreshTreeNodePriceIfNeed(g);//刷新造价书中主树上的定额工料机;
+            }
+            //更新父工料机价格
+            for(let r of result){
+                let pdata = r.updateOne.filter;
+                let set = r.updateOne.update.$set;
+                for(let skey in set){
+                    pdata[skey] = set[skey];
+                }
+                parentData.push(pdata);
+            }
+            let pgljs = me.getProjectGLJs(parentData);
+            gljs = gljs.concat(pgljs);
+            let nodes = me.getImpactRationNodes(gljs);//取到因为改变工料机价格而受影响的定额
+            projectObj.project.calcProgram.calcRationsAndSave(nodes);//触发计算程序
+            gljOprObj.showRationGLJSheetData();
+            socket.emit('unitFileChangeNotify', JSON.stringify(gljs));
+            projectObj.project.markUpdateProject({projectID:projectObj.project.ID(),'unitFileID':socketObject.getUnitFileRoomID()},"unitFile");
+            if(callback){
+                callback(gljs);
+            }
+            $.bootstrapLoading.end();
+        }, function (err) {
+            $.bootstrapLoading.end();
+        });
+    }
+
+
+};
+
+
 ProjectGLJ.prototype.pGljUpdate= function (data,callback) {
     let me = this;
     $.bootstrapLoading.start();
@@ -476,8 +536,8 @@ ProjectGLJ.prototype.getProjectGLJs = function (data,refreshPrice=true) {
             let glj = _.find(projectGljs, condition);
             if (glj) {
                 if(refreshPrice==true){
-                    glj.unit_price.base_price = d.base_price;
-                    glj.unit_price.market_price = d.market_price;
+                    d.base_price?glj.unit_price.base_price = d.base_price:'';
+                    d.market_price?glj.unit_price.market_price = d.market_price:'';
                     this.setAdjustPrice(glj);
                     this.refreshRationGLJPrice(glj);
                     this.refreshTreeNodePriceIfNeed(glj);

+ 6 - 2
web/building_saas/main/js/models/ration.js

@@ -374,18 +374,22 @@ var Ration = {
             }
         };
         ration.prototype.updateRationCodes = function (recodes) {
-            let libID = projectInfoObj.projectInfo.engineeringInfo.ration_lib[0].id;
+            let libID =  rationLibObj.getCurrentStdRationLibID();
             let engineering = projectInfoObj.projectInfo.property.engineering;
             let projectID = projectInfoObj.projectInfo.ID;
             let project = projectObj.project;
             let mainTree = project.mainTree;
             let nodeInfo =[];
             let refershNodes = [];
+            if(libID == null){
+                return;
+            }
             for(let r of recodes){
                 let needInstall = false;
                 if(engineering==engineeringType.BUILD_IN) {//如果是安装工程,要看需不需要生成安装增加费
                     needInstall = project.Bills.isFBFX(r.node);
                 }
+                r.value = r.value.replace(/[\s\r\n]/g, "");//去掉空格回车换行等字符
                 nodeInfo.push({ID:r.node.data.ID,billsItemID:r.node.data.billsItemID,newCode:r.value,needInstall:needInstall});
                 refershNodes.push(r.node);
             }
@@ -503,7 +507,7 @@ var Ration = {
             projectObj.project.ration_glj.deleteByRation(ration);
             projectObj.project.ration_coe.deleteByRation(ration);
             projectObj.project.quantity_detail.deleteByRation(ration);
-            //to do 删除安装增加费
+            projectObj.project.ration_installation.deleteByRation(ration);
         };
         ration.prototype.replaceRation = function (ration, std) {
             this.project.beginUpdate('replaceRation');

+ 30 - 14
web/building_saas/main/js/views/calc_base_view.js

@@ -271,22 +271,38 @@ let calcBaseView = {
                 }
             }
             else if (me.curType === me.type.ration) {
+                let expr = me.inputExpr.val();
+                expr = analyzer.standard(expr);
+                me.inputExpr.val(expr);
+
+                let template = calcProgramManage.getSelectionInfo().template;
                 let calcItem = calcProgramManage.getSelectionInfo().calcItem;
-                if (calcItem.dispExprUser != me.inputExpr.val()){
-                    calcItem.dispExprUser = me.inputExpr.val();
-                    calcItem.dispExpr = me.inputExpr.val();
-                    let data = {
-                        'projectID': projectObj.project.ID(),
-                        'templatesID': calcProgramManage.getSelectionInfo().template.ID,
-                        'calcItem': calcItem
+
+                if (calcItem.dispExpr != expr){
+                    if (analyzer.isLegal(expr, calcItem.ID, template)){
+                        let cp = projectObj.project.calcProgram;
+                        let lc = 0;
+                        if (calcItem.labourCoeID)
+                            lc = cp.compiledLabourCoes[calcItem.labourCoeID].coe.toString();
+                        calcItem.dispExpr = expr;
+                        calcItem.dispExprUser = analyzer.getDispExprUser(expr, lc);
+                        calcItem.expression = analyzer.getExpression(expr, template);
+                        calcItem.compiledExpr = analyzer.getCompiledExpr(calcItem.expression, lc);
+
+                        let data = {
+                            'projectID': projectObj.project.ID(),
+                            'templatesID': template.ID,
+                            'calcItem': calcItem
+                        };
+                        calcProgramManage.saveCalcItem(data, function (rst) {
+                            if (rst){
+                                cp.compileTemplate(template);
+                                cp.calcAllNodesAndSave();
+                                calcProgramManage.refreshDetailSheet();
+                                $('#qd-jsjs').modal('hide');
+                            }
+                        });
                     };
-                    calcProgramManage.saveCalcItem(data, function (rst) {
-                        if (rst){
-                            projectObj.project.calcProgram.calcAllNodesAndSave();
-                            calcProgramManage.refreshDetailSheet();
-                            $('#qd-jsjs').modal('hide');
-                        }
-                    });
                 }
                 else
                     $('#qd-jsjs').modal('hide');

+ 6 - 2
web/building_saas/main/js/views/calc_program_manage.js

@@ -116,10 +116,14 @@ let calcProgramManage = {
         }
     },
     onEnterCell: function (sender, args) {
-/*        let t = calcProgramManage.getSelectionInfo().template;
+       /* let t = calcProgramManage.getSelectionInfo().template;
         let c = calcProgramManage.getSelectionInfo().calcItem;
         c.expression = analyzer.getExpression(c.dispExpr, t);
-        let e = c.dispExpr + '  ' + c.expression;
+        let lc = 0;
+        if (c.labourCoeID) lc = projectObj.project.calcProgram.compiledLabourCoes[c.labourCoeID].coe.toString();
+        c.compiledExpr = analyzer.getCompiledExpr(c.expression, lc);
+
+        let e = c.dispExpr + '  ' + c.expression + '  ' + c.compiledExpr;
         projectObj.testDisplay('', e);*/
     },
     saveCalcItem: function (data,callback) {//data

+ 2 - 1
web/building_saas/main/js/views/fee_rate_view.js

@@ -422,7 +422,8 @@ var feeRateObject={
         let fieldID = me.mainFeeRateSetting.header[info.col].dataCode;
         let value = info.newValue;
         if(fieldID == 'rate'&&value != null){
-            if(number_util.isNumber(parseFloat(value))){
+            eval('1+a');
+            if(number_util.isNumber(Number(value))){
                 value = scMathUtil.roundForObj(value,getDecimal("feeRate"));
             }else {
                 alert('当前输入的数据类型不正确,请重新输入。');

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

@@ -269,6 +269,16 @@ let MainTreeCol = {
                 return new GC.Spread.Sheets.CellTypes.CheckBox();
         }
     },
+    setAutoHeight:function (cell,node) {//设置自动行高
+        let displaySetting = projectObj.project.property.displaySetting;
+        let billsAutoH = displaySetting && displaySetting.billsAutoHeight?displaySetting.billsAutoHeight:false;
+        let rationAutoH = displaySetting && displaySetting.rationAutoHeight?displaySetting.rationAutoHeight:false;
+        if(node.sourceType === projectObj.project.Bills.getSourceType()){
+            cell.wordWrap(billsAutoH);
+        }else {
+            cell.wordWrap(rationAutoH);
+        }
+    },
     getEvent: function (eventName) {
         let names = eventName.split('.');
         let event = this;

+ 50 - 15
web/building_saas/main/js/views/project_glj_view.js

@@ -18,7 +18,7 @@ projectGljObject={
             {headerName: "市场价", headerWidth: 70, dataCode: "marketPrice", hAlign: "right", dataType: "Number",decimalField:"glj.unitPrice",validator:"number"},
             {headerName: "是否暂估", headerWidth: 60, dataCode: "is_evaluate", hAlign: "center", dataType: "String",cellType:'checkBox'},
             {headerName: "供货方式", headerWidth: 80, dataCode: "supply", hAlign: "center", dataType: "String",cellType:'comboBox',editorValueType:true,options:supplyComboMap},
-            {headerName: "甲供数量", headerWidth: 100, dataCode: "supply_quantity", hAlign: "right", dataType: "String",validator:"number"},
+            {headerName: "甲供数量", headerWidth: 100, dataCode: "supply_quantity", hAlign: "right", dataType: "Number",validator:"number"},
             {headerName: "交货方式", headerWidth: 90, dataCode: "delivery", hAlign: "left", dataType: "String"},
             {headerName: "送达地点", headerWidth: 100, dataCode: "delivery_address", hAlign: "left", dataType: "String"},
             {headerName: "不调价", headerWidth: 55, dataCode: "is_adjust_price", dataType: "String",cellType: "checkBox"}
@@ -56,6 +56,7 @@ projectGljObject={
         this.initSheet(this.projectGljSheet,this.projectGljSetting);
         this.projectGljSheet.bind(GC.Spread.Sheets.Events.SelectionChanged,this.onProjectGljSelectionChange);
         this.projectGljSheet.bind(GC.Spread.Sheets.Events.EditStarting,this.onProjectGljEditStarting);
+        this.projectGljSpread.bind(GC.Spread.Sheets.Events.RangeChanged, this.onProjectGljRangeChange);
         this.projectGljSheet.name('projectGljSheet');
     },
     initMixRatio:function () {
@@ -133,37 +134,71 @@ projectGljObject={
         let me = projectGljObject;
         let row = args.row;
         let col = args.col;
-        let dataCode = me.projectGljSetting.header[col].dataCode;
-        if(dataCode=='is_adjust_price'||dataCode=='is_evaluate'){
+        if(me.projectGljEditChecking(row,col)==false){
             args.cancel = true;
         }
     },
-    onProjectGljSelectionChange:function (sender, args) {
+    projectGljEditChecking:function (row,col) {//return false表示不能编码
         let me = projectGljObject;
-        let row = args.newSelections[0].row;
-        let col = args.newSelections[0].col;
         let data = me.projectGljSheetData[row];
         let dataCode = me.projectGljSetting.header[col].dataCode;
+        if(dataCode=='is_adjust_price'||dataCode=='is_evaluate'){
+            return false;
+        }
         if(dataCode=='basePrice'||dataCode=='marketPrice'||dataCode=='supply'){//有组成物时,市场单价、定额价、供货方式不能修改
-            let priceCell = false;
             if (data.ratio_data  && data.ratio_data.length > 0){
-                priceCell = true;
+               return false;
             }
-            if(priceCell==false&&dataCode=='basePrice'&&data.is_add!=1){//如果不是新增,定额价不可修改。
-                priceCell = true;
+            if(dataCode=='basePrice'&&data.is_add!=1){//如果不是新增,定额价不可修改。
+                return false;
             }
-            me.projectGljSheet.getCell(row, col,  GC.Spread.Sheets.SheetArea.viewport).locked(priceCell);
         }
         if(dataCode == 'supply_quantity'){
-            if (data.supply == 1) {// 如果为部分甲供则甲供数量需要可编辑
-                me.projectGljSheet.getCell(row, col,  GC.Spread.Sheets.SheetArea.viewport).locked(false);
-            }else {
-                me.projectGljSheet.getCell(row, col,  GC.Spread.Sheets.SheetArea.viewport).locked(true);
+            if (data.supply != 1) {// 如果为部分甲供则甲供数量需要可编辑,其它的都不能编辑
+               return false;
             }
         }
+        return true;
+    },
+
+    onProjectGljSelectionChange:function (sender, args) {
+        let me = projectGljObject;
         me.showMixRatioData();
         me.projectGljSheet.repaint();
     },
+    onProjectGljRangeChange:function (sender,info) {
+        let me = projectGljObject;
+        let changeInfo=[];
+        let canChange = true;
+        for(let c of info.changedCells){
+            let value=  info.sheet.getCell(c.row, c.col).text();
+            changeInfo.push({row:c.row,col:c.col,value:value});
+            if(me.projectGljEditChecking(c.row,c.col)==false){//如果不能编辑
+                canChange = false;
+             }
+            if (canChange==true&&!me.checkData(c.col,me.projectGljSetting,value)) {
+                alert('输入的数据类型不对,请重新输入!');
+                canChange = false;
+            }
+        }
+        if(canChange == false){//恢复原来的值
+            console.log('1');
+            me.showProjectGljData();
+        }else {
+             me.batchUpdatePrice(changeInfo);
+        }
+    },
+    batchUpdatePrice(changeInfo){
+        let projectGLJ = projectObj.project.projectGLJ;
+        let me = projectGljObject;
+        projectGLJ.batchUpdatePrice(changeInfo,function (impactList) {
+            let selected = me.projectGljSheet.getSelections()[0];
+            me.showProjectGljData();
+            me.projectGljSheet.setSelection(selected.row,selected.col,selected.rowCount,selected.colCount);
+        });
+    },
+
+
     showProjectGljData:function () {
         let projectGljSheetData = [];
         let gljList = projectObj.project.projectGLJ.datas.gljList;

+ 9 - 4
web/building_saas/main/js/views/project_property_display_view.js

@@ -6,7 +6,9 @@ let projDisplayView = {
     init: function () {
         this.datas = projectInfoObj.projectInfo.property.displaySetting;
         this.datas = this.datas === undefined ? {autoHeight: true, disPlayMainMaterial: true} : this.datas;
-        $("#autoHeight").prop("checked", this.datas.autoHeight);
+        //$("#autoHeight").prop("checked", this.datas.autoHeight);
+        $("#billsAutoHeight").prop("checked", this.datas.billsAutoHeight);
+        $("#rationAutoHeight").prop("checked", this.datas.rationAutoHeight);
         $("#disPlayMainMaterial").prop("checked", this.datas.disPlayMainMaterial);
         //$('#disPlayMainMateria').prop('checked')
     },
@@ -22,10 +24,13 @@ let projDisplayView = {
         if (this.datas == null) {
             return;
         }
-        let autoHeight = $('#autoHeight').prop('checked');
+       // let autoHeight = $('#autoHeight').prop('checked');
+        let billsAutoHeight =  $("#billsAutoHeight").prop("checked");
+        let rationAutoHeight =  $("#rationAutoHeight").prop("checked");
         let disPlayMainMaterial = $('#disPlayMainMaterial').prop('checked');
-        if (this.datas.autoHeight !== autoHeight || this.datas.disPlayMainMaterial !== disPlayMainMaterial) {
-            this.datas.autoHeight = autoHeight;
+        if (this.datas.rationAutoHeight !== rationAutoHeight || this.datas.billsAutoHeight !== billsAutoHeight || this.datas.disPlayMainMaterial !== disPlayMainMaterial) {
+            this.datas.billsAutoHeight = billsAutoHeight;
+            this.datas.rationAutoHeight = rationAutoHeight;
             this.datas.disPlayMainMaterial = disPlayMainMaterial;
             // let updateData = {sourceType: 'properties', updateType: 'update', updateData: {ID: projectID, 'property.displaySetting':this.datas}};
             properties['property.displaySetting'] = this.datas;

+ 150 - 21
web/building_saas/main/js/views/project_view.js

@@ -40,13 +40,6 @@ var projectObj = {
         // for test interface.  CSLAAAAA
         // projectObj.testDisplay('前四项累计值排除当前选中项' + projectObj.project.calcProgram.getBeforeTaxTotalFee([node]));
 
-        // 公式结果
-        /*        let t = projectObj.project.calcProgram.compiledTemplates[node.data.programID];
-                let c = t.calcItems[7];
-                c.dispExpr = '[定额基价人工费] + [定额基价材料费]  + F6 + [主材费]';
-                let rst = analyzer.analyzeUserExpr(t, c);
-                projectObj.testDisplay('结果', rst);*/
-
         // 基数
         // node.data.isSubcontract = true;
         // node.data.gljList = project.ration_glj.getGljArrByRation(node.data.ID);
@@ -516,6 +509,8 @@ var projectObj = {
         let startTime = +new Date();
         console.log("开始加载-----"+startTime);
         this.project.loadDatas(function (err) {
+            let mTime = +new Date();
+            console.log(`get data时间——${mTime - startTime}`);
             if (!err) {
                 that.project.projectGLJ.calcQuantity();//计算分部分项和技术措施项目消耗量;
                 that.project.property = projectInfoObj.projectInfo.property;
@@ -527,8 +522,6 @@ var projectObj = {
                 that.project.projSetting.mainGridSetting = JSON.parse(str);
                 that.project.projSetting.mainGridSetting.frozenCols = 4;
                 TREE_SHEET_HELPER.initSetting($('#billsSpread')[0], that.project.projSetting.mainGridSetting);
-                const autoHeight = that.project.property.displaySetting !== undefined ?
-                    that.project.property.displaySetting.autoHeight : false;
                 that.project.projSetting.mainGridSetting.cols.forEach(function (col) {
                     col.data.splitFields = col.data.field.split('.');
                     if (col.data.getText && Object.prototype.toString.apply(col.data.getText) === "[object String]") {
@@ -548,17 +541,12 @@ var projectObj = {
                     if (col.data.field === 'code') {
                         col.data.formatter = '@';
                     }
-
-                    // 根据配置设置自动行高
+                    col.setAutoHeight = MainTreeCol.getEvent("setAutoHeight");
+                    // 根据配置设置自动行高,在这里先做个标记,然后对每个单元格单独配置
                     if (col.data.field === 'name' || col.data.field === 'itemCharacterText' ||
                         col.data.field === 'jobContentText' || col.data.field === 'adjustState') {
-                        if (!autoHeight) {
-                            col.showHint = true;
-                            col.data.wordWrap = false;
-                        } else {
-                            col.showHint = false;
-                            col.data.wordWrap = true;
-                        }
+                        col.data.autoHeight = true;
+                        col.showHint = true;
                     }
                     if(col.data.field ==='quantity'){
                         col.showHint = true;
@@ -573,9 +561,11 @@ var projectObj = {
                     // }
 
                 });
-
+                let startShowTime = +new Date();
                 that.mainController = TREE_SHEET_CONTROLLER.createNew(that.project.mainTree, that.mainSpread.getActiveSheet(), that.project.projSetting.mainGridSetting);
                 that.mainController.showTreeData();
+                let endShowTime = +new Date();
+                console.log(`show data时间——${endShowTime - startShowTime}`);
                 that.mainController.bind('refreshBaseActn', that.refreshBaseActn);
                 that.mainController.bind(TREE_SHEET_CONTROLLER.eventName.beforeTreeSelectedChange, that.beforeMainTreeSelectedChange);
                 that.mainController.bind(TREE_SHEET_CONTROLLER.eventName.treeSelectedChanged, that.treeSelectedChanged);
@@ -591,9 +581,11 @@ var projectObj = {
                 that.mainSpread.bind(GC.Spread.Sheets.Events.ClipboardChanged, that.msClipboardChanged);
                 that.mainSpread.bind(GC.Spread.Sheets.Events.ButtonClicked, that.onButtonClick);
                 that.mainSpread.bind(GC.Spread.Sheets.Events.CellDoubleClick, that.onCellDoubleClick);
+                let loadOtherStartTime = +new Date();
                 that.loadMainSpreadContextMenu();
                 that.loadFocusLocation();
                 let endTime = +new Date();
+                console.log(`其它时间——${endTime - loadOtherStartTime}`);
                 socketObject.connect();//连接socket服务器
                 console.log("加载完成-----"+endTime);
                 console.log(`时间——${endTime - startTime}`);
@@ -1070,10 +1062,10 @@ var projectObj = {
     },
     //大项费用则字体加粗,String 15px, Number 13px
     getBoldFontStyle: function (node, colSetting) {
+        let style = new GC.Spread.Sheets.Style();
         if(node.sourceType !== this.project.Bills.getSourceType() || node.data.type !== billType.DXFY){
-            return null;
+            return null
         }
-        let style = new GC.Spread.Sheets.Style();
         //备注暂无字段
         let stringFields = [
             'code',
@@ -1087,6 +1079,7 @@ var projectObj = {
             'programID',
             'ruleText'
         ];
+
         if(stringFields.indexOf(colSetting.data.field) > 0){
             style.font = 'bold 15px Arial';
         }
@@ -1523,4 +1516,140 @@ function ifCanDelete() {
         }
         return true
     };
+}
+
+//选择要导入的excel文件
+$('#customFile').change(function () {
+    let file = $(this)[0];
+    if(file.files.length > 0){
+        $('#uploadAlert').text(`${file.files[0].name} 准备导入上传`);
+        $('#uploadAlert').show();
+    }
+    else{
+        $('#uploadAlert').hide();
+    }
+});
+//从excel导入清单
+$('#uploadConfirm').click(function () {
+    let me = this;
+    $(me).addClass('disabled');
+    try{
+        let formData = new FormData();
+        let file = $('#customFile')[0];
+        if(file.files.length <= 0){
+            throw '未选择文件';
+        }
+        formData.append('file', file.files[0]);
+        let projectID = scUrlUtil.GetQueryString('project');
+        if(!projectID || projectID <= 0){
+            throw '项目数据出错';
+        }
+        formData.append('projectID', projectID);
+        $.ajax({
+            url: '/bills/upload',
+            type: 'POST',
+            data: formData,
+            cache: false,
+            contentType: false,
+            processData: false,
+            beforeSend: function() {
+                $.bootstrapLoading.start();
+            },
+            success: function(response){
+                if (response.err === 0) {
+                    const message = response.msg !== undefined ? response.msg : '';
+                    if (message !== '') {
+                        alert(message);
+                    }
+                    // 成功则关闭窗体
+                    $('#import').modal("hide");
+                    //更新前端
+                    doAfterImport(response.data);
+                } else {
+                    const message = response.msg !== undefined ? response.msg : '上传失败!';
+                    $.bootstrapLoading.end();
+                    alert(message);
+                }
+                $(me).removeClass('disabled');
+            },
+            error: function(){
+                alert("与服务器通信发生错误");
+                $.bootstrapLoading.end();
+                $(me).removeClass('disabled');
+            }
+        });
+    }
+    catch (err){
+        alert(err);
+    }
+});
+//导入后更新操作
+function doAfterImport(resData){
+    if(resData){
+        let nodes = projectObj.project.mainTree.nodes;
+        let bills_datas = projectObj.project.Bills.datas;
+        let ration_datas = projectObj.project.Ration.datas;
+        let quantity_detail_datas = projectObj.project.quantity_detail.datas;
+        let ration_glj_datas = projectObj.project.ration_glj.datas;
+        let ration_coe_datas = projectObj.project.ration_coe.datas;
+        let ration_install_datas = projectObj.project.ration_installation.datas;
+        let delNodes = [];
+        for(let billID of resData.remove.bill){
+            if(nodes['id_' + billID]){
+                delNodes.push(nodes['id_' + billID]);
+            }
+            //清除清单数据
+            _.remove(bills_datas, {ID: billID});
+            //清除清单工程量明细数据
+            _.remove(quantity_detail_datas, {billID: billID});
+        }
+        for(let rationID of resData.remove.ration){
+            //清除定额数据
+            _.remove(ration_datas, {ID: rationID});
+            //清除定额下的相关数据
+            _.remove(ration_glj_datas, {rationID: rationID});
+            _.remove(ration_coe_datas, {rationID: rationID});
+            _.remove(ration_install_datas, {rationID: rationID});
+            _.remove(quantity_detail_datas, {rationID: rationID});
+
+        }
+        //删除节点
+        if(delNodes.length > 0){
+            //通过固定清单获得删除节点的起始行索引
+            let beginRow = null;
+            if(resData.fixedBill){
+                let fixedNode = nodes['id_' + resData.fixedBill.ID];
+                if(fixedNode){
+                    beginRow = projectObj.project.mainTree.items.indexOf(fixedNode) + 1;
+                }
+            }
+            //删除主树节点
+            projectObj.mainController.m_delete(delNodes, beginRow);
+            //删除清单树节点
+            let delIdNodes = [];
+            for(let delNode of delNodes){
+                delIdNodes.push(delNode.source);
+            }
+            projectObj.project.Bills.tree.m_delete(delIdNodes);
+        }
+        if(resData.insert.bill.length > 0){
+            //插入清单节点
+            projectObj.project.Bills.tree.insertByDatas(resData.insert.bill);
+            //插入主树节点
+            let newNodes = projectObj.project.mainTree.insertByDatas(resData.insert.bill);
+            for(let node of newNodes){
+                node.source = projectObj.project.Bills.tree.nodes['id_' + node.getID()];
+                node.data = node.source.data;
+                node.sourceType = projectObj.project.Bills.getSourceType();
+            }
+            ProjectController.syncDisplayNewNodes(projectObj.mainController, newNodes);
+        }
+        $.bootstrapLoading.end();
+        //重算
+        projectObj.project.calcProgram.calcAllNodesAndSave(calcAllType.catAll, function () {
+            projectObj.project.projectGLJ.loadData(function () {
+                $.bootstrapLoading.end();
+            });
+        });
+    }
 }

+ 12 - 0
web/building_saas/main/js/views/std_ration_lib.js

@@ -256,7 +256,19 @@ var rationLibObj = {
                 "font":"Arial"
             }
         }]
+    },
+    getCurrentStdRationLibID:function () {
+        if(projectInfoObj.projectInfo.engineeringInfo.ration_lib.length === 0){
+            alert('当前项目无定额库,请添加定额库。');
+            return null;
+        }
+        if($('#stdRationLibSelect').val()){
+            return parseInt($('#stdRationLibSelect').val());
+        }else {
+            return projectInfoObj.projectInfo.engineeringInfo.ration_lib[0].id;
+        }
     }
+
 };
 
 addEventOnResize(rationLibObj.refreshSettingForHint);

+ 1 - 1
web/building_saas/pm/js/pm_newMain.js

@@ -2207,7 +2207,7 @@ function bindEvents_file_table(jqS, usedObj, targetBody, type){
     $($(jqS)[0].nextSibling).popover({
             placement:"bottom",
             html:true,
-            trigger:"hover", //| focus
+            trigger:"hover", //| focus.
             content: usedObj.usedInfo ? usedObj.usedInfo : ''
         }
     );

+ 2 - 1
web/common/html/header.html

@@ -5,12 +5,13 @@
             <a class="nav-link" href="#" aria-expanded="false" data-toggle="modal" data-target="#poj-set"><i class="fa fa-cube"></i> 项目属性</a>
         </li>
         <li class="nav-item">
-            <a class="nav-link" href="#" aria-expanded="false" data-toggle="modal" data-target="#opts-set"><i class="fa fa-sliders"></i> 选项</a>
+            <a class="nav-link" href="#import" data-toggle="modal" data-target="#import"><i class="fa fa-cloud-upload"></i> 导入</a>
         </li>
         <li class="nav-item dropdown">
             <a class="nav-link dropdown-toggle" href="#" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"><i class="fa fa-wrench"></i> 工具</a>
             <div class="dropdown-menu">
                 <!--<a class="dropdown-item" href="/complementaryRation/main">定额库编辑器</a>-->
+                <a class="dropdown-item" href="javascript:void(0);" aria-expanded="false" data-toggle="modal" data-target="#opts-set"><i class="fa fa-sliders"></i> 选项</a>
                 <a class="dropdown-item" href="javascript:void(0);" data-toggle="modal" data-target="#comple-ration">定额库编辑器</a>
                 <a class="dropdown-item" href="/complementaryGlj">工料机库编辑器</a>
             </div>