zhongzewei 7 年之前
父節點
當前提交
cd21b7a622

File diff suppressed because it is too large
+ 3 - 3
lib/bootstrap/bootstrap.min.js


File diff suppressed because it is too large
+ 4 - 4
lib/bootstrap/css/bootstrap.min.css


+ 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);
 };
 

+ 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);

+ 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);

+ 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;

+ 27 - 3
web/building_saas/main/html/main.html

@@ -1045,6 +1045,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 +1252,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);

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

@@ -1068,10 +1068,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',
@@ -1085,6 +1085,7 @@ var projectObj = {
             'programID',
             'ruleText'
         ];
+
         if(stringFields.indexOf(colSetting.data.field) > 0){
             style.font = 'bold 15px Arial';
         }
@@ -1521,4 +1522,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();
+            });
+        });
+    }
 }

+ 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>