Преглед на файлове

feat: 在项目表/清单范本中,新增添加选用功能,用于批量添加清单

vian преди 5 години
родител
ревизия
6929b718a2

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

@@ -603,4 +603,8 @@ input.text-right {
 
 .top-msg{
   top:35px !important;
-  }
+  }
+
+.add-select-bills-btn {
+  margin: 5px 0;
+}

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

@@ -490,6 +490,7 @@
                                       </div>
                                   </div>
                                   <div class="top-content" style="overflow: hidden">
+                                    <a id="add-select-bills" href="javascript:;" class="btn btn-xs btn-primary add-select-bills-btn">添加选用</a>
                                       <div class="main-data-side-zb" id="billsGuidance_bills">
                                       </div>
                                   </div>

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

@@ -92,6 +92,13 @@ ProjectController = {
     } else {
       projectObj.project.Bills.tree.insertByDatas(treeData);
       newNodes = projectObj.project.mainTree.insertByDatas(treeData);
+      const updateData = postData.filter(item => item.updateType === 'update');
+      updateData.forEach(item => {
+        const node = projectObj.project.Bills.tree.findNode(item.updateData.ID);
+        if (node) {
+          node.data.NextSiblingID = item.updateData.NextSiblingID;
+        }
+      });
     }
     for (const node of newNodes) {
       node.source = projectObj.project.Bills.tree.nodes[projectObj.project.Bills.tree.prefix + node.getID()];

+ 6 - 5
web/building_saas/main/js/models/project.js

@@ -532,7 +532,7 @@ var PROJECT = {
               });
           });
       };
-        project.prototype.updateNodesCache =function (datas) {
+        project.prototype.updateNodesCache =function (datas, reCalc=true) {
           let refreshNode = [];
           let reclacQuantity = false;
           let deleteNode=[],addNodeDatas=[];
@@ -585,7 +585,7 @@ var PROJECT = {
           });
 
 
-          if(reclacQuantity) this.projectGLJ.calcQuantity();
+          if(reCalc && reclacQuantity) this.projectGLJ.calcQuantity();
           return refreshNode;
 
 
@@ -613,12 +613,13 @@ var PROJECT = {
               let Bill = projectObj.project.Bills;
               let newAddNode = [];
               for(let nr of newDatas){
-                  let nextID = -1;
+                  let nextID = nr.data && nr.data.NextSiblingID ||  -1;
+                  let parentID = nr.parentID || (nr.data && nr.data.ParentID || -1);
                   let preNode = projectObj.project.mainTree.getNodeByID(nr.preSiblingID);
                   if(preNode) nextID = preNode.getNextSiblingID();
-                  let newNode = projectObj.project.mainTree.insert(nr.parentID, nextID, nr.data.ID);
+                  let newNode = projectObj.project.mainTree.insert(parentID, nextID, nr.data.ID);
                   if(nr.type == ModuleNames.bills){
-                      let newSource = Bill.tree.insertByData(nr.data, nr.ParentID, nextID, true);
+                      let newSource = Bill.tree.insertByData(nr.data, parentID, nextID, true);
                       newNode.source = newSource;
                   }else {
                       newNode.source = nr.data;

+ 231 - 142
web/building_saas/main/js/views/std_billsGuidance_lib.js

@@ -32,75 +32,119 @@ const billsGuidance = (function () {
         treeSetting: {
             emptyRowHeader: true,
             rowHeaderWidth: 15,
-            treeCol: 0,
+            treeCol: 1,
             emptyRows: 0,
             headRows: 1,
             headRowHeight: [40],
             defaultRowHeight: 21,
-            cols: [{
-                width: 105,
-                readOnly: true,
-                showHint: true,
-                head: {
-                    titleNames: ["项目编码"],
-                    spanCols: [1],
-                    spanRows: [1],
-                    vAlign: [1],
-                    hAlign: [1],
-                    font: ["Arial"]
+            cols: [
+                {
+                    width: 35,
+                    readOnly: false,
+                    showHint: false,
+                    head: {
+                        titleNames: ["选用"],
+                        spanCols: [1],
+                        spanRows: [1],
+                        vAlign: [1],
+                        hAlign: [1],
+                        font: ["Arial"]
+                    },
+                    data: {
+                        field: "select",
+                        vAlign: 1,
+                        hAlign: 1,
+                        font: "Arial"
+                    }
                 },
-                data: {
-                    field: "code",
-                    vAlign: 1,
-                    hAlign: 0,
-                    font: "Arial"
-                }
-            }, {
-                width: 190,
-                readOnly: true,
-                head: {
-                    titleNames: ["项目名称"],
-                    spanCols: [1],
-                    spanRows: [1],
-                    vAlign: [1],
-                    hAlign: [1],
-                    font: ["Arial"]
+                {
+                    width: 105,
+                    readOnly: true,
+                    showHint: true,
+                    head: {
+                        titleNames: ["项目编码"],
+                        spanCols: [1],
+                        spanRows: [1],
+                        vAlign: [1],
+                        hAlign: [1],
+                        font: ["Arial"]
+                    },
+                    data: {
+                        field: "code",
+                        vAlign: 1,
+                        hAlign: 0,
+                        font: "Arial"
+                    }
+                }, {
+                    width: 190,
+                    readOnly: true,
+                    head: {
+                        titleNames: ["项目名称"],
+                        spanCols: [1],
+                        spanRows: [1],
+                        vAlign: [1],
+                        hAlign: [1],
+                        font: ["Arial"]
+                    },
+                    data: {
+                        field: "name",
+                        vAlign: 1,
+                        hAlign: 0,
+                        font: "Arial"
+                    }
                 },
-                data: {
-                    field: "name",
-                    vAlign: 1,
-                    hAlign: 0,
-                    font: "Arial"
+                {
+                    width: 60,
+                    readOnly: true,
+                    head: {
+                        titleNames: ["计量单位"],
+                        spanCols: [1],
+                        spanRows: [1],
+                        vAlign: [1],
+                        hAlign: [1],
+                        font: ["Arial"]
+                    },
+                    data: {
+                        field: "unit",
+                        vAlign: 1,
+                        hAlign: 1,
+                        font: "Arial"
+                    }
                 }
-            },
-               {
-                width: 60,
-                readOnly: true,
-                 head: {
-                     titleNames: ["计量单位"],
-                     spanCols: [1],
-                     spanRows: [1],
-                     vAlign: [1],
-                     hAlign: [1],
-                     font: ["Arial"]
-                 },
-                 data: {
-                     field: "unit",
-                     vAlign: 1,
-                     hAlign: 1,
-                     font: "Arial"
-                 }
-            }
             ]
         },
         headers: [
+            {name: '选用', dataCode: 'select', width: 35, vAlign: 'center', hAlign: 'center', formatter: '@'},
             {name: '项目编码', dataCode: 'code', width: 105, vAlign: 'center', hAlign: 'left', formatter: '@'},
             {name: '项目名称', dataCode: 'name', width: 190, vAlign: 'center', hAlign: 'left', formatter: '@'},
             {name: '单位', dataCode: 'unit', width: 60, vAlign: 'center', hAlign: 'center', formatter: '@'},
         ],
         rowHeaderWidth:1,
         events: {
+            ButtonClicked: function (sender, args) {
+                if(args.sheet.isEditing()){
+                    args.sheet.endEdit(true);
+                }
+                if (args.col !== 0) {
+                    return;
+                }
+                const selectedNode = bills.tree.items[args.row];
+                const curValue = args.sheet.getValue(args.row, args.col);
+                let count = selectedNode.posterityCount();
+                if (count) {
+                    renderSheetFunc(args.sheet, () => {
+                        let row = args.row;
+                        while (count--) {
+                            row++;
+                            args.sheet.setValue(row, args.col, curValue);
+                        }
+                    }); 
+                }
+            },
             CellDoubleClick: function (sender, args) {
+                if (args.col === 0) {
+                    return;
+                }
                 if(!bills.tree){
                     return;
                 }
@@ -109,7 +153,7 @@ const billsGuidance = (function () {
                     return;
                 }
                 //展开收起(非最底层节点且双击的是第一列)
-                if (args.col === 0 && node.children.length > 0) {
+                if (args.col === 1 && node.children.length > 0) {
                     node.setExpanded(!node.expanded);
                     //设置展开收起状态
                     sessionStorage.setItem('stdBillsGuidanceExpState', bills.tree.getExpState(bills.tree.items));
@@ -122,7 +166,7 @@ const billsGuidance = (function () {
                         args.sheet.invalidateLayout();
                     });
                     args.sheet.repaint();
-                } else if (!projectReadOnly && !isInserting && !projectObj.project.isBillsLocked() && (args.col !== 0 || node.children.length === 0)) {
+                } else if (!projectReadOnly && !isInserting && !projectObj.project.isBillsLocked() && (![1].includes(args.col) || node.children.length === 0)) {
                     //选中部分的最底层(只是选中部分的最底)
                     let lowestNodes = [bills.tree.items[args.row]];
                     insertBills(lowestNodes);
@@ -137,6 +181,35 @@ const billsGuidance = (function () {
             return { parent: null, mainTreeFragment: projectObj.project.Bills.tree.roots }
         }
     };
+
+    // 反转插入顺序
+    // 正常顺序插入时,next数据可能还没插入,会造成错误,需要反过来插入
+    // insertData为正向排好序的数据
+    function reverseInsertData(insertData) {
+        const reverseData = [];
+        const parentMap = {};
+        const IDMap = {};
+        insertData.forEach(item => {
+            IDMap[item.data.ID] = item;
+            (parentMap[item.data.ParentID] || (parentMap[item.data.ParentID] = [])).push(item);
+        });
+        // 找到顶层数据: 数据中没有找不到对应parent的数据
+        const topLevelData = insertData
+            .filter(item => !IDMap[item.data.ParentID])
+            .reverse();
+        function getReverseData(items) {
+            items.forEach(item => {
+                reverseData.push(item);
+                const children = parentMap[item.data.ID];
+                if (children && children.length) {
+                    getReverseData(children.reverse());
+                }
+            })
+        }
+        getReverseData(topLevelData);
+        return reverseData;
+    }
+
     //插入清单
     async function insertBills(lowestNodes) {
         try {
@@ -166,8 +239,27 @@ const billsGuidance = (function () {
                     }
                 }
                 isInserting = true;
-                const newNodes = await ProjectController.addBillsByData(compareData.postData);
-                row = newNodes[newNodes.length - 1].serialNo();
+                await ajaxPost('/bills/insertBills', { postData: compareData.postData });
+                // 更新前端树
+                const toReverseData = [];
+                const cacheData = [];
+                compareData.postData.forEach(item => {
+                    const cacheItem = {
+                        type: ModuleNames.bills,
+                        action: item.updateType === updateType.create ? 'add' : 'update',
+                        data: item.updateData,
+                    }
+                    if (item.updateType === updateType.create) {
+                        toReverseData.push(cacheItem);
+                    } else {
+                        cacheData.push(cacheItem);
+                    }
+                });
+                cacheData.push(...reverseInsertData(toReverseData));
+                console.log(cacheData);
+                const nodes = projectObj.project.updateNodesCache(cacheData, false);
+                projectObj.mainController.refreshTreeNode(nodes);
+                row = nodes[nodes.length - 1].serialNo();
                 //有新的节点插入,也有可能定位至旧节点(批量选用的情况下)
                 if (compareData.locateNode) {
                     //该清单节点在主树的位置
@@ -186,70 +278,9 @@ const billsGuidance = (function () {
             }
         } finally {
             isInserting = false;
+            $.bootstrapLoading.end();
         }
     }
-    /* function insertBills(lowestNodes) {
-        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;
-            CommonAjax.post('/bills/insertBills', {postData: compareData.postData}, function () {
-                //插入
-                let insertData = _.filter(compareData.postData, {updateType: updateType.create});
-                let treeData = [];
-                for (let data of insertData) {
-                    treeData.push(data.updateData);
-                }
-                //插入清单节点
-                projectObj.project.Bills.tree.insertByDatas(treeData);
-                projectObj.project.Bills.datas = projectObj.project.Bills.datas.concat(treeData);
-                //插入主树节点
-                let newNodes = projectObj.project.mainTree.insertByDatas(treeData);
-                for (let 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);
-                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);
-                isInserting = false;
-            }, function () {
-                isInserting = false;
-            });
-        } else if (compareData.locateNode) {
-            //该清单节点在主树的位置
-            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]);//触发树节点选中事件
@@ -274,7 +305,7 @@ const billsGuidance = (function () {
                 node = node.parent;
             }
         }
-        //根据原节点serialNo排序,排序后,后一项节点只可能前节点的后兄弟项,或子项(根据原节点ID-ParentID判定)
+        // 根据原节点serialNo排序,排序后,后一项节点只可能前节点的后兄弟项,或子项
         allNodes.sort(function (a, b) {
             let aV = a.serialNo(),
                 bV = b.serialNo();
@@ -289,24 +320,26 @@ const billsGuidance = (function () {
         let treeData = [];
         //旧ID与新ID映射
         let IDMapping = {};
+        const ParentIDMap = {};
         for (let i = 0; i < allNodes.length; i++) {
             //原节点
-            let preN = allNodes[i - 1],
-                thisN = allNodes[i];
-            let newNodeData = {ID: uuid.v1(), NextSiblingID: -1, ParentID: -1, orgID: thisN.data.ID};
+            let thisN = allNodes[i];
+            let newNodeData = {  code: thisN.data.code, name: thisN.data.name, ID: uuid.v1(), NextSiblingID: -1, ParentID: -1, orgID: thisN.data.ID };
             IDMapping[newNodeData.orgID] = newNodeData.ID;
+            (ParentIDMap[thisN.data.ParentID] || (ParentIDMap[thisN.data.ParentID] = [])).push(newNodeData); // 兄弟节点已经拍好序(serialNo排序)
             treeData.push(newNodeData);
-            //新树数据
-            let preData = treeData[i - 1],
-                thisData = treeData[i];
-            //节点与上节点为同层节点,则此节点设为上节点的后兄弟
-            if (preN && preN.data.ParentID === thisN.data.ParentID) {
-                preData.NextSiblingID = thisData.ID;
-                //节点与上节点不为同层节点,则此节点为某上节点的子几点
-            } else if (preN && preN.data.ParentID !== thisN.data.ParentID) {
-                let parentID = IDMapping[thisN.data.ParentID];
-                thisData.ParentID = parentID;
-            }
+        }
+        // 重新设置ParentID、NextSiblingID
+        for (const orgParentID in ParentIDMap) {
+            const newNodeDatas = ParentIDMap[orgParentID];
+            const newParentID = orgParentID == -1 ? -1 : IDMapping[orgParentID];
+            newNodeDatas.forEach((newNodeData, index) => {
+                newNodeData.ParentID = newParentID;
+                const preNewNodeData = newNodeDatas[index - 1];
+                if (preNewNodeData) {
+                    preNewNodeData.NextSiblingID = newNodeData.ID;
+                }
+            });
         }
         let selTree = idTree.createNew({id: 'ID', pid: 'ParentID', nid: 'NextSiblingID', rootId: -1, autoUpdate: true});
         selTree.loadDatas(treeData);
@@ -317,14 +350,15 @@ const billsGuidance = (function () {
     * 找到的清单树中清单子项含有非清单项或输入了计算基数,则中止该清单的对比,不可插入数据
     * */
 
-    const oneSevenBillsSymbol = Symbol('固定清单{第100章至700章清单}');
+    const firstBillsSymbol = Symbol('第一行固定清单');
     //获取节点的匹配数据:编码-名称-单位
     //@param {Object}node @return {String}
     function getMatchContent(node) {
-        // 标准清单第一个根节点与项目清单(固定类别为100-700章清单)写死能对应匹配上
+        // 标准清单第一个根节点与项目清单第一个根节点写死能对应匹配上
         const firstStdNode = bills.tree.roots[0];
-        if (node === firstStdNode || node.getFlag() === commonConstants.fixedFlag.ONE_SEVEN_BILLS) {
-            return oneSevenBillsSymbol;
+        const firstProjectNode = projectObj.project.Bills.tree.roots[0];
+        if (node === firstStdNode || node === firstProjectNode) {
+            return firstBillsSymbol;
         }
         return `${node.data.code ? node.data.code : '*'}-${node.data.name ? node.data.name : '*'}-${node.data.unit ? node.data.unit : '*'}`;
     }
@@ -386,16 +420,50 @@ const billsGuidance = (function () {
                 return mathNext(orgNode, peerNodes);
                 //return peerNodes.length;
             }
+            // 比较编码大小,可能需要对比xx-xx xx-xx-xx ....
+            function compareCode(codeA, codeB) {
+                const codeASplits = codeA.split('-');
+                const codeBSplits = codeB.split('-');
+                const count = Math.max(codeASplits.length, codeBSplits.length);
+                for (let i = 0; i < count; i++) {
+                    const perA = codeASplits[i];
+                    const perB = codeBSplits[i];
+                    if (perA && !perB) {
+                        return 1;
+                    } else if (!perA && perB) {
+                        return -1;
+                    } else if (perA && perB) {
+                        const numberCompare = !isNaN(perA) && !isNaN(perB);
+                        const compareRst = numberCompare ? +perA - +perB : perA.localeCompare(perB);
+                        if (compareRst === 0) {
+                            continue;
+                        } else {
+                            return compareRst;
+                        }
+                    }
+                }
+            }
             for (let i = 0; i < peerNodes.length; i++) {
                 let thisNode = peerNodes[i];
                 let thisCode = thisNode.data.code ? thisNode.data.code : '';
                 //确定同层节点的顺序,编码小于等于在前,大于在后
+                const compareRst = compareCode(insertCode, thisCode);
+                if (compareRst <= 0) {
+                    return i;
+                } else {
+                    continue;
+                }
+            }
+            /* for (let i = 0; i < peerNodes.length; i++) {
+                let thisNode = peerNodes[i];
+                let thisCode = thisNode.data.code ? thisNode.data.code : '';
+                //确定同层节点的顺序,编码小于等于在前,大于在后
                 if (insertCode <= thisCode) {
                     return i;
                 } else if (insertCode > thisCode) {
                     continue;
                 }
-            }
+            } */
             return peerNodes.length;
         }
         //获取插入清单数据
@@ -862,13 +930,15 @@ const billsGuidance = (function () {
             initExpandStat();
         }
         module.controller.showTreeData();
-        if(module === bills){
+        if (module === bills) {
             module.workBook.getSheet(0).options.rowHeaderVisible = true;
             setBillsHint(bills.tree.items, stdBillsJobData, stdBillsFeatureData);
             renderSheetFunc(sheet, function () {
-                for(let i = 0; i < bills.tree.items.length; i++){
-                    sheet.setCellType(i, 1, TREE_SHEET_HELPER.getQuestionCellType(initRechargeModal, hasRechargeRuleText));
-        }
+                const checkBoxType = new GC.Spread.Sheets.CellTypes.CheckBox();
+                for (let i = 0; i < bills.tree.items.length; i++) {
+                    sheet.setCellType(i, 0, checkBoxType);
+                    sheet.setCellType(i, 2, TREE_SHEET_HELPER.getQuestionCellType(initRechargeModal, hasRechargeRuleText));
+                }
             });
         }
     }
@@ -1220,6 +1290,25 @@ const billsGuidance = (function () {
             autoFlashHeight();
             refreshWorkBook();
         });
+        // 添加选用
+        $('#add-select-bills').click(function() {
+            if (isInserting) {
+                return;
+            }
+            const sheet = bills.controller.sheet;
+            const lowestNodes = [];
+            for (let i = 0; i < bills.tree.items.length; i++) {
+                const checked = sheet.getValue(i, 0);
+                const node = bills.tree.items[i];
+                if (checked && !node.children.length) {
+                    lowestNodes.push(node);
+                }
+            }
+            if (lowestNodes.length) {
+                $.bootstrapLoading.start();
+                insertBills(lowestNodes);
+            }
+        });
     }
     //刷新表
     //@return {void}