Explorar o código

自定义数量插入清单右键

vian %!s(int64=5) %!d(string=hai) anos
pai
achega
b3ee93cca7

+ 75 - 3
public/web/id_tree.js

@@ -573,7 +573,7 @@ var idTree = {
             }
             return node;
         };
-        Tree.prototype.insertByID = function (newID, parentID, nextSiblingID) {
+        Tree.prototype.insertByID = function (newID, parentID, nextSiblingID, resort = true) {
             var node = null, data = {};
             var parent = parentID == -1 ? null : this.nodes[this.prefix + parentID];
             var nextSibling = nextSiblingID == -1 ? null: this.nodes[this.prefix + nextSiblingID];
@@ -611,7 +611,7 @@ var idTree = {
             }
             return data;
         };
-        Tree.prototype.insertByData = function (data, parentID, nextSiblingID, uid = null) {
+        Tree.prototype.insertByData = function (data, parentID, nextSiblingID, uid = null, resort = true) {
             var parent = parentID == -1 ? null : this.nodes[this.prefix + parentID];
             var nextSibling = nextSiblingID == -1 ? null : this.nodes[this.prefix + nextSiblingID];
             var node = this.nodes[this.prefix + data[this.setting.id]];
@@ -625,13 +625,45 @@ var idTree = {
                     tools.addNodes(this, parent, [node]);
                 }
                 this.nodes[this.prefix +  data[this.setting.id]] = node;
-                tools.sortTreeItems(this);
+                if (resort) {
+                    tools.sortTreeItems(this);
+                }
                 if(!uid){
                     this.maxNodeID( data[this.setting.id]);
                 }
                 return node;
             }
         };
+        Tree.prototype.multiInsert = function (datas, preID) {
+            const newNodes = [];
+            for (const data of datas) {
+                this.nodes[this.prefix + data.ID] = new Node(this, data.ID);
+                this.nodes[this.prefix + data.ID]['data'] = data;
+                newNodes.push(this.nodes[this.prefix + data.ID]);
+            }
+            const fisrtNode = this.nodes[this.prefix + datas[0].ID];
+            const firstPre = this.nodes[this.prefix + preID];
+            if (firstPre) {
+                firstPre.nextSibling = fisrtNode;
+                firstPre.data.NextSiblingID = fisrtNode.getID();
+            }
+            const parent = this.nodes[this.prefix + datas[0].ParentID] || null;
+            const parentChildren = parent ? parent.children : this.roots;
+            let baseIndex = firstPre ? parentChildren.indexOf(firstPre) + 1 : 0;
+            datas.forEach(data => {
+                const node = this.nodes[this.prefix + data.ID];
+                node.parent = parent;
+                parentChildren.splice(baseIndex++, 0, node);
+                const next = this.nodes[this.prefix + data.NextSiblingID] || null;
+                node.nextSibling = next;
+                if (next) {
+                    next.preSibling = node;
+                }
+            });
+            this.roots = tools.reSortNodes(this.roots, true);
+            tools.sortTreeItems(this);
+            return newNodes;
+        }
         //批量新增节点,节点已有树结构数据
         Tree.prototype.insertByDatas = function (datas) {
             for(let data of datas){
@@ -897,6 +929,46 @@ var idTree = {
             tools.addUpdateDataForNextSibling(data, lastNode.nextSibling, firstNode.getID());
             return data;
         };
+          //检查树结构数据有没问题
+        Tree.prototype.check = function (roots) {
+            return isValid(roots);
+            function isValid(nodes) {
+                for (let node of nodes) {
+                    if (node.data.ParentID !== -1 &&
+                        (!node.parent || node.parent.data.ID !== node.data.ParentID)) {
+                        console.log(`${node.serialNo() + 1}:${node.data.name} parent对应错误`);
+                        return false;
+                    }
+                    if (node.data.ParentID === -1 && node.parent) {
+                        console.log(`${node.serialNo() + 1}:${node.data.name} 不应有parent`);
+                        return false;
+                    }
+                    if (node.data.NextSiblingID !== -1 &&
+                        (!node.nextSibling || node.nextSibling.data.ID !== node.data.NextSiblingID)) {
+                        console.log(`${node.serialNo() + 1}:${node.data.name} next对应错误`);
+                        return false;
+                    }
+                    if (node.data.NextSiblingID === -1 && node.nextSibling) {
+                        console.log(`${node.serialNo() + 1}:${node.data.name} 不应有next`);
+                        return false;
+                    }
+                    let sameDepthNodes = node.parent ? node.parent.children : roots,
+                        nodeIdx = sameDepthNodes.indexOf(node),
+                        nextIdx = sameDepthNodes.indexOf(node.nextSibling);
+                    if (nodeIdx !== -1 && nextIdx !== -1 && nodeIdx > nextIdx) {
+                        console.log(`${node.serialNo() + 1}:${node.data.name} node索引大于next索引`);
+                        return false;
+                    }
+                    if (node.children.length) {
+                        let v = isValid(node.children);
+                        if (!v) {
+                            return false;
+                        }
+                    }
+                }
+                return true;
+            }
+        };
         return new Tree(setting);
     },
     updateType: {update: 'update', new: 'new', delete: 'delete'}

+ 84 - 0
public/web/sheet/sheet_common.js

@@ -1248,5 +1248,89 @@ var sheetCommonObj = {
             sheet.getCell(row, col).wordWrap(wordWrap);
             sheet.autoFitRow(row);
         });
+    },
+    // 获取带输入框的右键子目
+    registerInputContextMenuItem(name, html, icon, callback) {
+        $.contextMenu.types[name] = function (item, opt, root) {
+            // 因为contextMenu有自检的键盘事件处理,因此输入框的键盘控制光标需要自己定义事件实现覆盖。
+            const Direction = {
+                BACKWARD: 'backward',
+                FORWARD: 'forward'
+            };
+            function moveLeft(input, isShifting) {
+                const start = input.selectionStart;
+                const end = input.selectionEnd;
+                let direction = end === start ? Direction.BACKWARD : input.selectionDirection;
+                if (isShifting) {
+                    if (direction === Direction.FORWARD) {
+                        const curEnd = end - 1;
+                        input.setSelectionRange(start, curEnd);
+                    } else {
+                        const curStart = start - 1 < 0 ? 0 : start - 1;
+                        input.setSelectionRange(curStart, end, Direction.BACKWARD);
+                    }
+                } else {
+                    let idx = end > start
+                        ? start
+                        : start - 1 < 0 
+                            ? 0 
+                            : start - 1;
+                    input.setSelectionRange(idx, idx);
+                }
+            }
+            function moveRight(input, isShifting) {
+                const start = input.selectionStart;
+                const end = input.selectionEnd;
+                const direction = start === end ? Direction.FORWARD : input.selectionDirection;
+                if (isShifting) {
+                    if (direction === Direction.BACKWARD) {
+                        const curStart = start + 1;
+                        input.setSelectionRange(curStart, end);
+                    } else {
+                        const curEnd = end + 1;
+                        input.setSelectionRange(start, curEnd, Direction.FORWARD);
+                    }
+                } else {
+                    let idx = start < end
+                        ? end
+                        : end + 1
+                    input.setSelectionRange(idx, idx);
+                }
+            }
+            $(html)
+                .appendTo(this)
+                .on('input', 'input', function () {
+                    const number = +$(this).val();
+                    if (isNaN(number)) {
+                        $(this).val(1);
+                    } else if (number > 99) {
+                        $(this).val(99);
+                    }
+                })
+                .on('keydown', 'input', function (e) {
+                    const key = e.key;
+                    if (key === 'ArrowUp' || key === 'ArrowDown') {
+                        return false;
+                    }
+                    const input = $(this)[0];
+                    if (key === 'ArrowLeft') {
+                        moveLeft(input, e.shiftKey);
+                    } else if (key === 'ArrowRight') {
+                        moveRight(input, e.shiftKey);
+                    }
+                })
+                .parent().on('click', function (e) {
+                    if (e.target.tagName === 'INPUT') {
+                        return false;
+                    }
+                    if (callback) {
+                        callback();
+                    }
+                    root.$menu.trigger('contextmenu:hide');
+                });
+
+            this.addClass(`context-menu-icon context-menu-icon--fa fa ${icon}`);
+        };
+        return name;
     }
 }

+ 16 - 1
web/building_saas/css/custom.css

@@ -460,4 +460,19 @@ input.text-right{
     left: 50% !important;
     transform: translateX(-50%) !important;
     width: 123% !important;
-}
+}
+
+/* 右键菜单input */
+.menu-input {
+    width: 2.5rem;
+    border: 1px solid rgb(221, 221, 221);
+    border-radius: 2px;
+    height: 1.3rem;
+    text-align: center;
+}
+.menu-input:focus {
+    outline: none;
+}
+/* .menu-input::selection {
+    background-color: rgb(41, 128, 185);
+} */

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

@@ -54,6 +54,84 @@ ProjectController = {
             cbTools.refreshFormulaNodes();
         });
     },
+    /* addBillsByData: async function (postData, insertFunc) {
+        await ajaxPost('/bills/insertBills', { postData });
+        // 插入
+        const insertData = postData.filter(item => item.updateType === 'create');
+        const treeData = insertData.map(item => item.updateData);
+        // 插入清单节点
+        if (!insertFunc) {
+            insertFunc = projectObj.project.Bills.tree.insertByDatas;
+        }
+        projectObj.project.Bills.tree.insertByDatas(treeData);
+        projectObj.project.Bills.datas = projectObj.project.Bills.datas.concat(treeData);
+        // 插入主树节点
+        const newNodes = projectObj.project.mainTree.insertByDatas(treeData);
+        for (const node of newNodes) {
+            node.source = projectObj.project.Bills.tree.nodes[projectObj.project.Bills.tree.prefix + node.getID()];
+            node.data = node.source.data;
+            node.sourceType = projectObj.project.Bills.getSourceType();
+        }
+        ProjectController.syncDisplayNewNodes(projectObj.mainController, newNodes, true);
+        return newNodes;
+    }, */
+    addBillsByData: async function (postData, isSameDepth = false) {
+        await ajaxPost('/bills/insertBills', { postData });
+        // 插入
+        const insertData = postData.filter(item => item.updateType === 'create');
+        const treeData = insertData.map(item => item.updateData);
+        // 插入清单节点和主树节点
+        projectObj.project.Bills.datas = projectObj.project.Bills.datas.concat(treeData);
+        let newNodes;
+        if (isSameDepth) {
+            const pre = postData.find(item => item.updateType === 'update');
+            const preID = pre && pre.updateData.ID || null;
+            projectObj.project.Bills.tree.multiInsert(treeData, preID);
+            newNodes = projectObj.project.mainTree.multiInsert(treeData, preID);
+        } else {
+            projectObj.project.Bills.tree.insertByDatas(treeData);
+            newNodes = projectObj.project.mainTree.insertByDatas(treeData);
+        }
+        for (const node of newNodes) {
+            node.source = projectObj.project.Bills.tree.nodes[projectObj.project.Bills.tree.prefix + node.getID()];
+            node.data = node.source.data;
+            node.sourceType = projectObj.project.Bills.getSourceType();
+        }
+        ProjectController.syncDisplayNewNodes(projectObj.mainController, newNodes, true);
+        return newNodes;
+    },
+    getBillsPostData: function (number) {
+        const project = projectObj.project;
+        const target = project.getParentTarget(project.mainTree.selected, 'sourceType', project.Bills.getSourceType());
+        const baseParentID = target.depth() === 0 ? target.source.getID() : target.source.getParentID();
+        const baseNextID = target.depth() === 0 ? -1 : target.source.getNextSiblingID();
+        const updateNode = target.depth() === 0 ? target.source.children[target.source.children.length - 1] : target;
+        const insertData = [];
+        for (let i = 0; i < number; i++) {
+            const data = {
+                type: billType.BILL,
+                projectID: project.ID(),
+                ID: uuid.v1(),
+                ParentID: baseParentID,
+            };
+            const pre = insertData[i - 1];
+            if (pre) {
+                pre.NextSiblingID = data.ID;
+            }
+            if (i === number - 1) {
+                data.NextSiblingID = baseNextID;
+            }
+            insertData.push(data);
+        }
+        const postData = insertData.map(item => ({
+            updateType: 'create',
+            updateData: item
+        }));
+        if (updateNode) {
+            postData.push({ updateType: 'update', updateData: { ID: updateNode.getID(), NextSiblingID: insertData[0].ID } });
+        }
+        return postData;
+    },
     addBills: function (project, sheetController, std) {
         if (!project || !sheetController) { return null; }
         let target = project.getParentTarget(project.mainTree.selected, 'sourceType', project.Bills.getSourceType());

+ 47 - 3
web/building_saas/main/js/models/cache_tree.js

@@ -399,16 +399,60 @@ var cacheTree = {
             return iCount;
         };
 
-        Tree.prototype.insert = function (parentID, nextSiblingID, id) {
+        Tree.prototype.insert = function (parentID, nextSiblingID, id, resort = true) {
             var parent = !parentID || parentID === -1 ? null : this.nodes[this.prefix + parentID];
             var nextSibling = !nextSiblingID || nextSiblingID === -1 ? null: this.nodes[this.prefix + nextSiblingID];
 
             var newNode = this.addNode(parent, nextSibling, id);
-            this.sortTreeItems();
+            if (resort) {
+                this.sortTreeItems();
+            }
 
             return newNode;
         };
-        Tree.prototype.insertByDatas = function (datas) {
+        Tree.prototype.multiInsert = function (datas) {
+            debugger;
+            const newNodes = [];
+            for (const data of datas) {
+                const node = this.insert(data.ParentID, data.NextSiblingID, data.ID, true);
+                newNodes.push(node);
+            }
+            this.roots = tools.reSortNodes(this.roots, true);
+            this.sortTreeItems();
+            return newNodes;
+        }
+        //  一次性插入多个连续的同层节点
+        Tree.prototype.multiInsert = function (datas, preID) {
+            const newNodes = [];
+            for (const data of datas) {
+                this.nodes[this.prefix + data.ID] = new Node(this, data.ID);
+                newNodes.push(this.nodes[this.prefix + data.ID]);
+            }
+            const fisrtNode = this.nodes[this.prefix + datas[0].ID];
+            const firstPre = this.nodes[this.prefix + preID];
+            if (firstPre) {
+                firstPre.nextSibling = fisrtNode;
+                firstPre.data.NextSiblingID = fisrtNode.getID();
+            }
+            const parent = this.nodes[this.prefix + datas[0].ParentID] || null;
+            const parentChildren = parent ? parent.children : this.roots;
+            let baseIndex = firstPre ? parentChildren.indexOf(firstPre) + 1 : 0;
+            datas.forEach(data => {
+                const node = this.nodes[this.prefix + data.ID];
+                node.parent = parent;
+                parentChildren.splice(baseIndex++, 0, node);
+                const next = this.nodes[this.prefix + data.NextSiblingID] || null;
+                node.nextSibling = next;
+                if (next) {
+                    next.preSibling = node;
+                }
+            });
+            this.roots = tools.reSortNodes(this.roots, true);
+            this.sortTreeItems();
+            return newNodes;
+        }
+        // 插入某一完整片段到某节点的子项中
+        Tree.prototype.insertByDatas = function (datas, preID) {
             let rst = [];
             for(let data of datas){
                 this.nodes[this.prefix + data.ID] = new Node(this, data.ID);

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

@@ -1163,11 +1163,41 @@ var projectObj = {
       } 
       return true
     },
-
+    // 注册自定义插入清单数量
+    registerFlexibleInsertBillMenu: function (insertBillsName) {
+        const project = projectObj.project;
+        const insertBillsHtml = `<span>${insertBillsName}&nbsp;&nbsp;<input id='insert-bills-number' class="menu-input" type="text" value="1" onfocus="this.select()">&nbsp;&nbsp;行</span>`;
+        return sheetCommonObj.registerInputContextMenuItem('insertBills', insertBillsHtml, 'fa-sign-in', async function () {
+            if (project.mainTree.selected.data.type == billType.DXFY) {
+                if (project.mainTree.selected.data.calcBase && project.mainTree.selected.data.calcBase != "") {
+                    alert("当前有基数计算,不能插入子项。");
+                    return;
+                }
+            }
+            try {
+                const number = +$('#insert-bills-number').val();
+                if (!number) {
+                    return;
+                }
+                $.bootstrapLoading.start();
+                const postData = ProjectController.getBillsPostData(number);
+                const newNodes = await ProjectController.addBillsByData(postData, true);
+                projectObj.mainController.setTreeSelected(newNodes[0]);
+                projectObj.selectColAndFocus(project.mainTree.selected);
+            } catch (err) {
+                console.log(err);
+                if (!$('hintBox_form').is(':visible')) {
+                    alert(err);
+                }
+            } finally {
+                $.bootstrapLoading.end();
+            }
+        });
+    },
     loadMainSpreadContextMenu: function () {
         var project = this.project, spread = this.mainSpread, controller = this.mainController;
         let insertBillsName = project.projectInfo.property && project.projectInfo.property.valuationType == commonConstants.ValuationType.BUDGET?"插入项目节":"插入清单";//右键“插入清单”改文字为“插入项目节”,工程量清单中保持不变。
-            $.contextMenu({
+        $.contextMenu({
             selector: '#billsSpread',
             selectableSubMenu: true,
             build: function ($trigger, e) {
@@ -1187,8 +1217,8 @@ var projectObj = {
                     callback: function (key, opt) {
                         ProjectController.addRootBill(project, controller);
                     },
-                    visible: function(key, opt){
-                        return project.mainTree.selected&&project.mainTree.selected.parent==null;
+                    visible: function (key, opt) {
+                        return project.mainTree.selected && project.mainTree.selected.parent == null;
                     }
                 },
                 "insertFB": {
@@ -1199,14 +1229,14 @@ var projectObj = {
                             return true;
                         }
                         let selected = project.mainTree.selected;
-                        if(projectObj.project.isBillsLocked()== false&&selected&&selected.sourceType==project.Bills.getSourceType()){
-                            if(selected.data.type==billType.FB){
+                        if (projectObj.project.isBillsLocked() == false && selected && selected.sourceType == project.Bills.getSourceType()) {
+                            if (selected.data.type == billType.FB) {
                                 return false;
                             }
-                            if(isFlag(selected.data)&&selected.data.flagsIndex.fixed.flag==fixedFlag.SUB_ENGINERRING){//焦点行是分部分项工程
-                                if(selected.children.length>0){
-                                   return selected.children[0].data.type==billType.FX ||selected.children[0].data.type==billType.BX;//焦点行是分部分项工程,且子项是分项或补项
-                                }else {
+                            if (isFlag(selected.data) && selected.data.flagsIndex.fixed.flag == fixedFlag.SUB_ENGINERRING) {//焦点行是分部分项工程
+                                if (selected.children.length > 0) {
+                                    return selected.children[0].data.type == billType.FX || selected.children[0].data.type == billType.BX;//焦点行是分部分项工程,且子项是分项或补项
+                                } else {
                                     return false
                                 }
                             }
@@ -1217,10 +1247,10 @@ var projectObj = {
                         ProjectController.addFB(project, controller);
                         projectObj.selectColAndFocus(project.mainTree.selected);
                     },
-                    visible: function(key, opt){
-                        if(project.mainTree.selected){
-                            return project.Bills.isFBFX(project.mainTree.selected );//不属于分部分项的话隐藏
-                        }else {
+                    visible: function (key, opt) {
+                        if (project.mainTree.selected) {
+                            return project.Bills.isFBFX(project.mainTree.selected);//不属于分部分项的话隐藏
+                        } else {
                             return false;
                         }
                     }
@@ -1233,21 +1263,21 @@ var projectObj = {
                             return true;
                         }
                         let selected = project.mainTree.selected;
-                        if(projectObj.project.isBillsLocked()== false&& selected&&selected.sourceType==project.Bills.getSourceType()){
-                            if(selected.data.type==billType.FX||selected.data.type==billType.BX){//焦点行是分项,有效显示
+                        if (projectObj.project.isBillsLocked() == false && selected && selected.sourceType == project.Bills.getSourceType()) {
+                            if (selected.data.type == billType.FX || selected.data.type == billType.BX) {//焦点行是分项,有效显示
                                 return false
                             }
-                            if(selected.data.type==billType.FB){//点行是分部,
-                                if(selected.children.length>0){//且有子项,子项是分部,灰显。
+                            if (selected.data.type == billType.FB) {//点行是分部,
+                                if (selected.children.length > 0) {//且有子项,子项是分部,灰显。
                                     return selected.children[0].data.type == billType.FB
-                                }else {
+                                } else {
                                     return false;
                                 }
                             }
-                            if(isFlag(selected.data)&&selected.data.flagsIndex.fixed.flag==fixedFlag.SUB_ENGINERRING){//焦点行是分部分项工程
-                                if(selected.children.length>0){
-                                    return selected.children[0].data.type==billType.FB;//焦点行是分部分项工程,且子项是分部时灰显
-                                }else {
+                            if (isFlag(selected.data) && selected.data.flagsIndex.fixed.flag == fixedFlag.SUB_ENGINERRING) {//焦点行是分部分项工程
+                                if (selected.children.length > 0) {
+                                    return selected.children[0].data.type == billType.FB;//焦点行是分部分项工程,且子项是分部时灰显
+                                } else {
                                     return false
                                 }
                             }
@@ -1258,44 +1288,44 @@ var projectObj = {
                         ProjectController.addFX(project, controller);
                         projectObj.selectColAndFocus(project.mainTree.selected);
                     },
-                    visible: function(key, opt){
-                        if(project.mainTree.selected){
-                            return project.Bills.isFBFX(project.mainTree.selected );//不属于分部分项的话隐藏
-                        }else {
+                    visible: function (key, opt) {
+                        if (project.mainTree.selected) {
+                            return project.Bills.isFBFX(project.mainTree.selected);//不属于分部分项的话隐藏
+                        } else {
                             return false;
                         }
                     }
                 },
                 "insertBills": {
-                    name: insertBillsName,
+                    type: projectObj.registerFlexibleInsertBillMenu(insertBillsName),
                     icon: 'fa-sign-in',
                     disabled: function () {
                         if (projectReadOnly) {
                             return true;
                         }
                         let selected = project.mainTree.selected;
-                        if(!(projectObj.project.isBillsLocked()== true && project.withinBillsLocked(selected)) && selected && selected.sourceType === project.Bills.getSourceType()){
+                        if (!(projectObj.project.isBillsLocked() == true && project.withinBillsLocked(selected)) && selected && selected.sourceType === project.Bills.getSourceType()) {
                             return false
                         }
                         return true;
                     },
-                    callback: function (key, opt) {
-                        if(project.mainTree.selected.data.type == billType.DXFY){
-                            if(project.mainTree.selected.data.calcBase&&project.mainTree.selected.data.calcBase!=""){
+                    /* callback: function (key, opt) {
+                        if (project.mainTree.selected.data.type == billType.DXFY) {
+                            if (project.mainTree.selected.data.calcBase && project.mainTree.selected.data.calcBase != "") {
                                 alert("当前有基数计算,不能插入子项。");
                                 return;
                             }
                         }
                         ProjectController.addBills(project, controller);
                         projectObj.selectColAndFocus(project.mainTree.selected);
-                    },
-                     visible: function(key, opt){
-                         if(project.mainTree.selected){
-                             return  project.Bills.isFBFX(project.mainTree.selected)==true?false:true;
-                         }else {
-                             return false;
-                         }
-                     }
+                    }, */
+                    visible: function (key, opt) {
+                        if (project.mainTree.selected) {
+                            return project.Bills.isFBFX(project.mainTree.selected) == true ? false : true;
+                        } else {
+                            return false;
+                        }
+                    }
                 },
                 "spr1": '--------',
                 "insertRation": {
@@ -1311,8 +1341,8 @@ var projectObj = {
                         return !project.Ration.canAdd(project.mainTree.selected);
                     },
                     callback: function (key, opt) {
-                        project.Ration.addNewRation(null,rationType.ration,projectObj.selectColAndFocus,false);
-                       // ProjectController.addRation(project, controller, rationType.ration);
+                        project.Ration.addNewRation(null, rationType.ration, projectObj.selectColAndFocus, false);
+                        // ProjectController.addRation(project, controller, rationType.ration);
                     }/*,
                     visible: function(key, opt){
                         var selected = project.mainTree.selected;
@@ -1320,27 +1350,27 @@ var projectObj = {
                     }*/
                 },
                 "insertGLJ": {
-                  name: "插入工料机",
-                  icon: 'fa-sign-in',
-                  disabled: function () {
-                      if (projectReadOnly) {
-                          return true;
-                      }
-                      // var selected = project.mainTree.selected;
-                      // return project.Ration.addRationChecking(selected);  // Vincent, 2018-01-02
-                      return !project.Ration.canAdd(project.mainTree.selected);
-                  },
-                  callback: function (key, opt) {
-                    let selected = project.mainTree.selected;
-                    if(selected.data.calcBase&&selected.data.calcBase!=""){
-                        alert("当前有基数计算,不能插入定额/量价/工料机。");
-                        return;
+                    name: "插入工料机",
+                    icon: 'fa-sign-in',
+                    disabled: function () {
+                        if (projectReadOnly) {
+                            return true;
+                        }
+                        // var selected = project.mainTree.selected;
+                        // return project.Ration.addRationChecking(selected);  // Vincent, 2018-01-02
+                        return !project.Ration.canAdd(project.mainTree.selected);
+                    },
+                    callback: function (key, opt) {
+                        let selected = project.mainTree.selected;
+                        if (selected.data.calcBase && selected.data.calcBase != "") {
+                            alert("当前有基数计算,不能插入定额/量价/工料机。");
+                            return;
+                        }
+                        getGLJData('insert');// ProjectController.addRation(project, controller, rationType.volumePrice);
+                    },
+                    visible: function (key, opt) {
+                        return false
                     }
-                    getGLJData('insert');// ProjectController.addRation(project, controller, rationType.volumePrice);
-                  },
-                  visible: function(key, opt){
-                    return false
-                  }
                 },
                 "insertLJ": {
                     name: "插入量价",//插入量价不需要自动定位到编号列
@@ -1352,29 +1382,29 @@ var projectObj = {
                         return !project.Ration.canAdd(project.mainTree.selected);
                     },
                     callback: function (key, opt) {
-                       /* project.Ration.addNewRation(null,rationType.volumePrice,function (newNode) {
-                            projectObj.selectColAndFocus(newNode,null);
-                        },true);*/
+                        /* project.Ration.addNewRation(null,rationType.volumePrice,function (newNode) {
+                             projectObj.selectColAndFocus(newNode,null);
+                         },true);*/
                     },
-                    items:{
-                        insertLabour:{
+                    items: {
+                        insertLabour: {
                             name: "人工",
                             icon: 'fa-sign-in',
-                            callback:function(key){
+                            callback: function (key) {
                                 project.Ration.insertVolumePrice(gljType.LABOUR);
                             }
                         },
-                        insertMaterial:{
-                           name:"材料" ,
+                        insertMaterial: {
+                            name: "材料",
                             icon: 'fa-sign-in',
-                            callback:function(key){
+                            callback: function (key) {
                                 project.Ration.insertVolumePrice(gljType.GENERAL_MATERIAL);
                             }
                         },
-                        insertMachine:{
-                            name:"机械" ,
+                        insertMachine: {
+                            name: "机械",
                             icon: 'fa-sign-in',
-                            callback:function(key){
+                            callback: function (key) {
                                 project.Ration.insertVolumePrice(gljType.GENERAL_MACHINE);
                             }
                         }
@@ -1391,13 +1421,13 @@ var projectObj = {
                     },
                     callback: function (key, opt) {
                         let selected = project.mainTree.selected;
-                        if(selected.data.calcBase&&selected.data.calcBase!=""){
+                        if (selected.data.calcBase && selected.data.calcBase != "") {
                             alert("当前有基数计算,不能插入定额/量价/工料机。");
                             return;
                         }
                         getGLJData('insertEquipment');// ProjectController.addRation(project, controller, rationType.volumePrice);
                     },
-                    visible: function(key, opt){//2018-11-08  新需求,这个按钮先隐藏,有需要再放开
+                    visible: function (key, opt) {//2018-11-08  新需求,这个按钮先隐藏,有需要再放开
                         let selected = project.mainTree.selected;
                         return projectObj.isInsertEquipmentVisable(selected);
                     }
@@ -1415,18 +1445,18 @@ var projectObj = {
                     callback: function (key, opt) {
                         installationFeeObj.showCalcInstallSettingDiv();
                     },
-                    visible: function(key, opt){
+                    visible: function (key, opt) {
                         return projectObj.project.isInstall();
                     }
                 },
-                "cleanzmhs":{
-                    name:'清空定额调整',
+                "cleanzmhs": {
+                    name: '清空定额调整',
                     icon: 'fa-remove',
                     callback: function (key, opt) {
                         let selected = project.mainTree.selected;
-                        projectObj.project.Ration.updateRationCodes([{'node':selected, value:selected.data.code}],true);
+                        projectObj.project.Ration.updateRationCodes([{ 'node': selected, value: selected.data.code }], true);
                     },
-                    visible: function(key, opt){
+                    visible: function (key, opt) {
                         let selected = project.mainTree.selected;
                         if (selected && selected.sourceType == ModuleNames.ration) {
                             return true;
@@ -1438,7 +1468,7 @@ var projectObj = {
                         if (projectReadOnly) {
                             return true;
                         }
-                        if(selected && selected.data.type != rationType.ration){
+                        if (selected && selected.data.type != rationType.ration) {
                             return true;
                         }
                         return false;
@@ -1538,10 +1568,10 @@ var projectObj = {
                     },
                     callback: function () {
                         var selected = project.mainTree.selected;
-                        if(selected.sourceType == project.Bills.getSourceType()&&selected.data.type==billType.FB&&selected.children.length<=0){//选中的是分部,并且没有子项,直接删除
+                        if (selected.sourceType == project.Bills.getSourceType() && selected.data.type == billType.FB && selected.children.length <= 0) {//选中的是分部,并且没有子项,直接删除
                             project.Bills.deleteSelectedNodes();//project.Bills.deleteSelectedNode();
-                        }else {
-                            $("#delete_row").modal({show:true});//弹出删除提示框;
+                        } else {
+                            $("#delete_row").modal({ show: true });//弹出删除提示框;
                         }
                     }
                 },
@@ -1556,11 +1586,11 @@ var projectObj = {
                             return true;
                         }
                     },
-                    visible:function(key, opt){//2019-11-11 新需求重新开放右键“造价计算”。
+                    visible: function (key, opt) {//2019-11-11 新需求重新开放右键“造价计算”。
                         return true;
                     }
                 },
-                "spr4":'--------',
+                "spr4": '--------',
                 "copyBlock": {
                     name: '复制整块',
                     icon: 'fa-copy',
@@ -1570,10 +1600,10 @@ var projectObj = {
                         }
                         let selection = projectObj.mainSpread.getActiveSheet().getSelections()[0];
                         let firstNode = projectObj.project.mainTree.items[selection.row];//当多选的情况,用mainTree.selected判断不正确,要用第一个选中的节点
-                        for(let i = 0;i< selection.rowCount;i++){ //多选的时候判断所有与第一个节点同级的节点
+                        for (let i = 0; i < selection.rowCount; i++) { //多选的时候判断所有与第一个节点同级的节点
                             let temNode = projectObj.project.mainTree.items[selection.row + i];
-                            if(firstNode.getParentID() == temNode.getParentID()){
-                                if(BlockController.copyBtnDisable(temNode) == true){
+                            if (firstNode.getParentID() == temNode.getParentID()) {
+                                if (BlockController.copyBtnDisable(temNode) == true) {
                                     return true;
                                 }
                             }
@@ -1587,14 +1617,14 @@ var projectObj = {
                         setTimeout(function () {
                             BlockController.copyBlock(selections[0]);
                             $.bootstrapLoading.end();
-                        },100)
+                        }, 100)
 
                     }
                 },
                 "pasteBlock": {
                     name: '粘贴整块',
                     icon: 'fa-paste',
-                    disabled: function (){
+                    disabled: function () {
                         if (projectReadOnly) {
                             return true;
                         }
@@ -1607,7 +1637,7 @@ var projectObj = {
                 "recoverDeletedNodes": {
                     name: '恢复删除节点',
                     icon: 'fa-undo',
-                    disabled: function (){
+                    disabled: function () {
                         return BlockController.recoverBlockDisabled();
                     },
                     callback: function () {
@@ -1620,14 +1650,14 @@ var projectObj = {
                     disabled: function () {
                         return projectReadOnly;
                     },
-                    visible: function(key, opt){
-                         let  selected = project.mainTree.selected;
-                         return selected.sourceType==ModuleNames.bills&&project.Bills.isEngineerEst(selected);//当焦点行是“专业工程暂估价”时,右键可见并有效。
+                    visible: function (key, opt) {
+                        let selected = project.mainTree.selected;
+                        return selected.sourceType == ModuleNames.bills && project.Bills.isEngineerEst(selected);//当焦点行是“专业工程暂估价”时,右键可见并有效。
                     },
                     callback: function () {
                         let node = project.mainTree.selected;//project.Bills.getNodeByFlag(project.mainTree.selected,fixedFlag.ENGINEERING_ESITIMATE);
-                        if(node){
-                            projectObj.editContent(node,'engineeringContent');
+                        if (node) {
+                            projectObj.editContent(node, 'engineeringContent');
                         }
                     }
                 },
@@ -1637,14 +1667,14 @@ var projectObj = {
                     disabled: function () {
                         return projectReadOnly;
                     },
-                    visible: function(key, opt){
-                        let  selected = project.mainTree.selected;
-                        return selected.sourceType==ModuleNames.bills&&project.Bills.isTotalService(selected);//当焦点行是“总承包服务费”时,右键可见并有效。
+                    visible: function (key, opt) {
+                        let selected = project.mainTree.selected;
+                        return selected.sourceType == ModuleNames.bills && project.Bills.isTotalService(selected);//当焦点行是“总承包服务费”时,右键可见并有效。
                     },
                     callback: function () {
                         let node = project.mainTree.selected;//project.Bills.getNodeByFlag(project.mainTree.selected,fixedFlag.TURN_KEY_CONTRACT);
-                        if(node){
-                            projectObj.editContent(node,'serviceContent');
+                        if (node) {
+                            projectObj.editContent(node, 'serviceContent');
                         }
                     }
                 },
@@ -1654,48 +1684,48 @@ var projectObj = {
                     disabled: function () {
                         return projectReadOnly;
                     },
-                    visible: function(key, opt){
-                        let  selected = project.mainTree.selected;
-                        return selected.sourceType==ModuleNames.bills&&project.Bills.isClaimVisa(selected);//当焦点行是“签证及索赔计价”时,右键可见并有效。
+                    visible: function (key, opt) {
+                        let selected = project.mainTree.selected;
+                        return selected.sourceType == ModuleNames.bills && project.Bills.isClaimVisa(selected);//当焦点行是“签证及索赔计价”时,右键可见并有效。
                     },
                     callback: function () {
                         let node = project.mainTree.selected;//project.Bills.getNodeByFlag(project.mainTree.selected,fixedFlag.CLAIM_VISA);
-                        if(node){
-                            projectObj.editContent(node,'claimVisa');
+                        if (node) {
+                            projectObj.editContent(node, 'claimVisa');
                         }
                     }
                 },
-                "replaceMaterial":{
-                    name:'智能材料替换',
+                "replaceMaterial": {
+                    name: '智能材料替换',
                     icon: 'fa-edit',
-                    disabled:function (key,opt) {
+                    disabled: function (key, opt) {
                         if (projectReadOnly) {
                             return true;
                         }
-                        let  selected = project.mainTree.selected;
-                        return selected.sourceType==ModuleNames.bills ?!(project.Bills.isFXorBX(selected)||selected.source.children.length ==0):true//是分项、补项或叶子清单才有效;
+                        let selected = project.mainTree.selected;
+                        return selected.sourceType == ModuleNames.bills ? !(project.Bills.isFXorBX(selected) || selected.source.children.length == 0) : true//是分项、补项或叶子清单才有效;
                     },
-                    callback:function(){
+                    callback: function () {
                         MaterialController.replaceMaterial([project.mainTree.selected]);
                     },
-                    visible: function(key, opt){//2018-11-15 暂时隐藏
+                    visible: function (key, opt) {//2018-11-15 暂时隐藏
                         return false
                     }
                 },
-                "createBlocks":{
-                    name:'生成组价模板',
+                "createBlocks": {
+                    name: '生成组价模板',
                     icon: 'fa-puzzle-piece',
-                    disabled:function (key,opt) {
+                    disabled: function (key, opt) {
                         if (projectReadOnly) {
                             return true;
                         }
                         let selected = project.mainTree.selected;
                         return selected.sourceType != ModuleNames.bills;
                     },
-                    callback: function(){
+                    callback: function () {
                         blockLibObj.checkShow();
                     },
-                    visible: function(key, opt){
+                    visible: function (key, opt) {
                         return G_SHOW_BLOCK_LIB;
                     }
                 }

+ 52 - 2
web/building_saas/main/js/views/std_billsGuidance_lib.js

@@ -138,7 +138,57 @@ const billsGuidance = (function () {
         }
     };
     //插入清单
-    function insertBills(lowestNodes) {
+    async function insertBills(lowestNodes) {
+        try {
+            let selTree = getSelTree(lowestNodes);
+            const { errMsg, parent, mainTreeFragment } = overwrite.getFragment();
+            if (errMsg) {
+                alert(errMsg);
+                return;
+            }
+            let compareData = compareTree(parent, mainTreeFragment, selTree.roots);
+            let sheet = projectObj.mainSpread.getActiveSheet(),
+                row = sheet.getActiveColumnIndex(),
+                col = sheet.getActiveColumnIndex();
+            if (compareData.postData.length > 0) {
+                //如果插入的是固定清单,则需要判断该固定清单在造价书中是否已存在,造价书中不可存在相同的固定清单
+                let fixedDatas = compareData.postData.filter((data) =>
+                data.updateType === updateType.create && Array.isArray(data.updateData.flags));
+                if (fixedDatas.length > 0) {
+                    //提示已存在此固定清单并且定位
+                    let firstFixed = fixedDatas[0].updateData;
+                    let existNode = projectObj.project.mainTree.items.find((node) =>
+                    node.data && node.data.flagsIndex && node.data.flagsIndex.fixed && node.data.flagsIndex.fixed.flag === firstFixed.flags[0].flag);
+                    if (existNode) {
+                        alert(`固定清单<strong>“${firstFixed.name}”</strong>已被第${existNode.serialNo() + 1}行清单占用。`);
+                        locateAtSpread(sheet, existNode.serialNo(), col);
+                        return;
+                    }
+                }
+                isInserting = true;
+                const newNodes = await ProjectController.addBillsByData(compareData.postData);
+                row = newNodes[newNodes.length - 1].serialNo();
+                //有新的节点插入,也有可能定位至旧节点(批量选用的情况下)
+                if (compareData.locateNode) {
+                    //该清单节点在主树的位置
+                    row = projectObj.project.mainTree.nodes[projectObj.project.mainTree.prefix + compareData.locateNode.data.ID].serialNo();
+                }
+                locateAtSpread(sheet, row, col);
+            } else if (compareData.locateNode) {
+                //该清单节点在主树的位置
+                row = projectObj.project.mainTree.nodes[projectObj.project.mainTree.prefix + compareData.locateNode.data.ID].serialNo();
+                locateAtSpread(sheet, row, col);
+            }
+        } catch (err) {
+            console.log(err);
+            if (!$('hintBox_form').is(':visible')) {
+                alert(err);
+            }
+        } finally {
+            isInserting = false;
+        }
+    }
+    /* function insertBills(lowestNodes) {
         let selTree = getSelTree(lowestNodes);
         const { errMsg, parent, mainTreeFragment } = overwrite.getFragment();
         if (errMsg) {
@@ -199,7 +249,7 @@ const billsGuidance = (function () {
             row = projectObj.project.mainTree.nodes[projectObj.project.mainTree.prefix + compareData.locateNode.data.ID].serialNo();
             locateAtSpread(sheet, row, col);
         }
-    }
+    } */
     function locateAtSpread(sheet, row, col) {
         sheet.setSelection(row, col, 1, 1);
         projectObj.mainController.setTreeSelected(projectObj.mainController.tree.items[row]);//触发树节点选中事件