Selaa lähdekoodia

Merge branch 'master' of http://192.168.1.41:3000/SmartCost/ConstructionOperation

zhangweicheng 2 kuukautta sitten
vanhempi
commit
8f06d21482

+ 16 - 0
config/config.js

@@ -111,6 +111,22 @@ module.exports = {
             useMongoClient: true
         }
     },
+    local2prod_wc: {
+        title: "大司空V2.0",
+        startPort: 1002,
+        server: "112.74.42.187",//数据库ID
+        port: "28033",//数据库端口
+        dbName: 'stdBuilding',
+        options: {
+            user: 'wisecost',
+            pass: 'Smartcost3850888',
+            auth: {
+                "authSource": "admin"
+            },
+            connectTimeoutMS: 60000,
+            useMongoClient: true
+        }
+    },
     prod_wc: {
         title: "大司空V2.0",
         startPort: 1002,

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 7 - 0
lib/localforage/localforage.min.js


+ 1 - 0
lib/ztree/css/zTreeStyle.css

@@ -72,6 +72,7 @@ website:	http://code.google.com/p/jquerytree/
 
 .ztree li span.button.star {margin-right:2px; background-position:-126px -64px; vertical-align:top; *vertical-align:middle}
 .ztree li span.button.blue_core {margin-right:2px; background-position:-126px -16px; vertical-align:top; *vertical-align:middle}
+.ztree li span.button.right_arrow {margin-right:2px; background-position:-126px -48px; vertical-align:top; *vertical-align:middle}
 
 .ztree li span.button.ico_open{margin-right:2px; background-position:-110px -16px; vertical-align:top; *vertical-align:middle}
 .ztree li span.button.ico_close{margin-right:2px; background-position:-110px 0; vertical-align:top; *vertical-align:middle}

+ 3 - 3
modules/bills_lib/models/bills_lib_interfaces.js

@@ -12,7 +12,7 @@ let moment = require("moment");
 let billsGuidanceLib = mongoose.model("std_billsGuidance_lib");
 const engLibModel = mongoose.model("engineering_lib");
 let uuid = require("uuid");
-let billsLibDao = function () {};
+let billsLibDao = function () { };
 
 billsLibDao.prototype.copyLib = async function (
   userName,
@@ -406,13 +406,13 @@ billsLibDao.prototype.createBills = function (cbillsData, callback) {
   let newBills = [];
   for (let bill of insertBills) {
     newBills.push({
-      ...bill,
       code: "",
       name: "",
       unit: "",
       ruleText: "",
       Expression: "",
       recharge: "",
+      ...bill,
       deleted: false,
     });
   }
@@ -4142,7 +4142,7 @@ billsLibDao.prototype.importBills = async function (billsLibId, sheetData) {
       recharge: "",
     };
     let jobData =
-        typeof rowData[4] !== "undefined" ? getDivideData(rowData[4]) : [],
+      typeof rowData[4] !== "undefined" ? getDivideData(rowData[4]) : [],
       itemData =
         typeof rowData[5] !== "undefined" ? getDivideData(rowData[5]) : [];
     bills.jobData = jobData;

+ 6 - 23
modules/reports/controllers/rpt_tpl_controller.js

@@ -588,30 +588,13 @@ let mExport = {
       });
   },
   // 导出所有的报表数据
-  getAllBackupData: async function (req, res) {
+  async getAllBackupData(req, res) {
     try {
-      const filePath = "./public/building_reportBackup.zip";
-      let rptTemplate = [];
-      let rptTplTree = [];
-      let rptConfig = [];
-      let rptField = [];
-      await new Promise(async function (resolve) {
-        rptTemplate = await RptTplModel.find({}, "-_id");
-        resolve();
-      });
-
-      await new Promise(async function (resolve) {
-        rptTplTree = await TreeNodeModel.find({}, "-_id");
-        resolve();
-      });
-      await new Promise(async function (resolve) {
-        rptConfig = await rpt_cfg_mdl.find({}, "-_id");
-        resolve();
-      });
-      await new Promise(async function (resolve) {
-        rptField = await Rpt_Map_Fld_Mdl.find({}, "-_id");
-        resolve();
-      });
+      const filePath = "./public/highWay_reportBackup.zip";
+      const rptConfig = await rpt_cfg_mdl.find({}, "-_id");
+      const rptTemplate = await RptTplModel.find({}, "-_id");
+      const rptTplTree = await TreeNodeModel.find({}, "-_id");
+      const rptField = await Rpt_Map_Fld_Mdl.find({}, "-_id");
       var zip = new JSZip();
       zip.file(
         "报表模板备份.json",

+ 2 - 1
package.json

@@ -46,6 +46,7 @@
     "uat_server": "SET NODE_ENV=uat&& babel-node operation.js",
     "uat_WC_server": "SET NODE_ENV=uat_wc&& babel-node operation.js",
     "prod_server": "SET NODE_ENV=prod_s&& babel-node operation.js",
-    "prod_WC_server": "SET NODE_ENV=prod_wc&& babel-node operation.js"
+    "prod_WC_server": "SET NODE_ENV=prod_wc&& babel-node operation.js",
+    "local2prod_wc": "SET NODE_ENV=local2prod_wc&& babel-node operation.js"
   }
 }

+ 139 - 110
public/web/id_tree.js

@@ -122,12 +122,12 @@ var idTree = {
             },
             addUpdateDataForParent: function (datas, nodes, pid) {
                 nodes.forEach(function (node) {
-                    datas.push({type: 'update', data: node.tree.getDataTemplate(node.getID(), pid, node.getNextSiblingID())});
+                    datas.push({ type: 'update', data: node.tree.getDataTemplate(node.getID(), pid, node.getNextSiblingID()) });
                 });
             },
             addUpdateDataForNextSibling: function (datas, node, nid) {
                 if (node) {
-                    datas.push({type: 'update', data: node.tree.getDataTemplate(node.getID(), node.getParentID(), nid)});
+                    datas.push({ type: 'update', data: node.tree.getDataTemplate(node.getID(), node.getParentID(), nid) });
                 }
             }
         };
@@ -221,14 +221,14 @@ var idTree = {
 
 
         // 获取节点所有后代节点
-        Node.prototype.getPosterity = function() {
+        Node.prototype.getPosterity = function () {
             let posterity = [];
             getNodes(this.children);
             return posterity;
             function getNodes(nodes) {
                 for (let node of nodes) {
                     posterity.push(node);
-                    if (node.children.length > 0){
+                    if (node.children.length > 0) {
                         getNodes(node.children);
                     }
                 }
@@ -236,12 +236,12 @@ var idTree = {
         };
 
         // 担心链有问题,preSibling不靠谱的话,按照显示顺序算preSibling
-        Node.prototype.prevNode = function() {
+        Node.prototype.prevNode = function () {
             const parent = this.parent || this.tree.roots;
             if (!parent) {
                 return null;
             }
-            const children = parent === this.tree.roots ? this.tree.roots :  parent.children;
+            const children = parent === this.tree.roots ? this.tree.roots : parent.children;
             const index = children.indexOf(this);
             return children[index - 1] || null;
         }
@@ -296,12 +296,12 @@ var idTree = {
                     tools.addUpdateDataForNextSibling(data, this.preSibling, this.tree.setting.rootId);
                 }
                 tools.addUpdateDataForNextSibling(data, this.parent, this.getID());
-                data.push({type: 'update', data: this.tree.getDataTemplate(this.getID(), this.parent.getParentID(), this.parent.getNextSiblingID())});
+                data.push({ type: 'update', data: this.tree.getDataTemplate(this.getID(), this.parent.getParentID(), this.parent.getNextSiblingID()) });
             }
             return data;
         };
         Node.prototype.upLevel = function () {
-            var result = {success: false, updateDatas: []};
+            var result = { success: false, updateDatas: [] };
             var iIndex = this.parent.children.indexOf(this), orgParent = this.parent, newNextSibling = this.parent.nextSibling;
             if (this.canUpLevel) {
                 // NextSiblings become child
@@ -328,7 +328,7 @@ var idTree = {
                     tools.addUpdateDataForNextSibling(data, this.preSibling.lastChild(), this.getID());
                 }
                 tools.addUpdateDataForNextSibling(data, this.preSibling, this.getNextSiblingID());
-                data.push({type: 'update', data: this.tree.getDataTemplate(this.getID(), this.preSibling.getID(), this.tree.setting.rootId)});
+                data.push({ type: 'update', data: this.tree.getDataTemplate(this.getID(), this.preSibling.getID(), this.tree.setting.rootId) });
             }
             return data;
         };
@@ -532,7 +532,7 @@ var idTree = {
         Tree.prototype.insert = function (parentID, nextSiblingID) {
             var newID = this.newNodeID(), node = null, data = {};
             var parent = parentID == -1 ? null : this.nodes[this.prefix + parentID];
-            var nextSibling = nextSiblingID == -1 ? null: this.nodes[this.prefix + nextSiblingID];
+            var nextSibling = nextSiblingID == -1 ? null : this.nodes[this.prefix + nextSiblingID];
             if (newID !== -1) {
                 data = {};
                 data[this.setting.id] = newID;
@@ -550,24 +550,24 @@ var idTree = {
             }
             return node;
         };
-        Tree.prototype.m_insert = function (datas,parentID, nextSiblingID) {
-           // var newID = this.newNodeID(), node = null, data = {};
+        Tree.prototype.m_insert = function (datas, parentID, nextSiblingID) {
+            // var newID = this.newNodeID(), node = null, data = {};
             var parent = parentID == -1 ? null : this.nodes[this.prefix + parentID];
-            var nextSibling = nextSiblingID == -1 ? null: this.nodes[this.prefix + nextSiblingID];
-            let preInsertNode = null,nodes = [];
-            for(let d of datas){
-                let node = new Node(this,d.data);
-                if(preInsertNode == null){
+            var nextSibling = nextSiblingID == -1 ? null : this.nodes[this.prefix + nextSiblingID];
+            let preInsertNode = null, nodes = [];
+            for (let d of datas) {
+                let node = new Node(this, d.data);
+                if (preInsertNode == null) {
                     if (nextSibling) {
                         tools.addNodes(this, parent, [node], nextSibling.siblingIndex());
                     } else {
                         tools.addNodes(this, parent, [node]);
                     }
-                }else {
+                } else {
                     tools.addNodes(this, parent, [node], preInsertNode.siblingIndex());
                 }
                 this.nodes[this.prefix + d.data.ID] = node;
-                if(preInsertNode) node.setNextSibling(preInsertNode);
+                if (preInsertNode) node.setNextSibling(preInsertNode);
                 preInsertNode = node;
                 nodes.push(node);
             }
@@ -580,7 +580,7 @@ var idTree = {
         Tree.prototype.insertByID = function (newID, parentID, nextSiblingID) {
             var node = null, data = {};
             var parent = parentID == -1 ? null : this.nodes[this.prefix + parentID];
-            var nextSibling = nextSiblingID == -1 ? null: this.nodes[this.prefix + nextSiblingID];
+            var nextSibling = nextSiblingID == -1 ? null : this.nodes[this.prefix + nextSiblingID];
             if (newID) {
                 data = {};
                 data[this.setting.id] = newID;
@@ -601,9 +601,9 @@ var idTree = {
             var data = [];
             var newID = this.newNodeID();
             var parent = parentID == -1 ? null : this.nodes[this.prefix + parentID];
-            var nextSibling = nextSiblingID == -1 ? null: this.nodes[this.prefix + nextSiblingID];
+            var nextSibling = nextSiblingID == -1 ? null : this.nodes[this.prefix + nextSiblingID];
             if (newID !== -1) {
-                data.push({type: 'new', data: this.getDataTemplate(newID, parent ? parent.getID() : this.setting.rootId, nextSibling ? nextSibling.getID() : this.setting.rootId)});
+                data.push({ type: 'new', data: this.getDataTemplate(newID, parent ? parent.getID() : this.setting.rootId, nextSibling ? nextSibling.getID() : this.setting.rootId) });
 
                 if (nextSibling && nextSibling.preSibling) {
                     tools.addUpdateDataForNextSibling(data, nextSibling.preSibling, newID);
@@ -616,17 +616,17 @@ var idTree = {
             return data;
         };
         //插入多行
-        Tree.prototype.getInsertDatas = function (rowCount,parentID, nextSiblingID) {
-            let data = [],preInsertID = null,lastID;
+        Tree.prototype.getInsertDatas = function (rowCount, parentID, nextSiblingID) {
+            let data = [], preInsertID = null, lastID;
             let parent = parentID == -1 ? null : this.nodes[this.prefix + parentID];
-            let nextSibling = nextSiblingID == -1 ? null: this.nodes[this.prefix + nextSiblingID];
-            for(let i=0;i<rowCount ;i++){//先插入的在最后,后插的在最前
+            let nextSibling = nextSiblingID == -1 ? null : this.nodes[this.prefix + nextSiblingID];
+            for (let i = 0; i < rowCount; i++) {//先插入的在最后,后插的在最前
                 let newID = this.newNodeID();
-                if(newID !== -1){
-                    if(preInsertID == null){//说明是第一个插入的
-                        data.push({type: 'new', data: this.getDataTemplate(newID, parent ? parent.getID() : this.setting.rootId, nextSibling ? nextSibling.getID() : this.setting.rootId)});
-                    }else {//其它的下一节点ID取上一个插入的节点
-                        data.push({type: 'new', data: this.getDataTemplate(newID, parent ? parent.getID() : this.setting.rootId, preInsertID)});
+                if (newID !== -1) {
+                    if (preInsertID == null) {//说明是第一个插入的
+                        data.push({ type: 'new', data: this.getDataTemplate(newID, parent ? parent.getID() : this.setting.rootId, nextSibling ? nextSibling.getID() : this.setting.rootId) });
+                    } else {//其它的下一节点ID取上一个插入的节点
+                        data.push({ type: 'new', data: this.getDataTemplate(newID, parent ? parent.getID() : this.setting.rootId, preInsertID) });
                     }
                     this.maxNodeID(newID);
                     preInsertID = newID;
@@ -655,25 +655,36 @@ var idTree = {
                 } else {
                     tools.addNodes(this, parent, [node]);
                 }
-                this.nodes[this.prefix +  data[this.setting.id]] = node;
+                this.nodes[this.prefix + data[this.setting.id]] = node;
                 tools.sortTreeItems(this);
-                this.maxNodeID( data[this.setting.id]);
+                this.maxNodeID(data[this.setting.id]);
                 return node;
             }
         };
+
+        // 插入离散节点
+        Tree.prototype.insertByDatas = function (datas) {
+            const nodes = [];
+            datas.forEach(item => {
+                const node = this.insertByData(item, item.ParentID, item.NextSiblingID);
+                nodes.push(node);
+            });
+            return nodes;
+        };
+
         //批量新增节点到节点后项,节点已有树结构数据
         Tree.prototype.insertDatasTo = function (preData, datas) {
             let rst = [];
-            for(let data of 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;
                 rst.push(this.nodes[this.prefix + data.ID]);
             }
-            for(let data of datas){
+            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){
+                if (!parent) {
                     this.roots.push(node);
                 }
                 else {
@@ -681,14 +692,14 @@ var idTree = {
                 }
                 let next = data.NextSiblingID == -1 ? null : this.nodes[this.prefix + data.NextSiblingID];
                 node.nextSibling = next;
-                if(next){
+                if (next) {
                     next.preSibling = node;
                 }
             }
             let preNode = this.nodes[this.prefix + preData.ID];
-            if(preNode){
+            if (preNode) {
                 preNode.nextSibling = this.nodes[this.prefix + preData.NextSiblingID] ? this.nodes[this.prefix + preData.NextSiblingID] : null;
-                if(preNode.nextSibling){
+                if (preNode.nextSibling) {
                     preNode.nextSibling.preSibling = preNode;
                 }
             }
@@ -699,7 +710,7 @@ var idTree = {
         };
         Tree.prototype.delete = function (node) {
             var success = false, that = this;
-            if(node) success = that.m_delete([node]);
+            if (node) success = that.m_delete([node]);
             return success;
         };
         Tree.prototype.m_delete = function (nodes) {
@@ -710,7 +721,7 @@ var idTree = {
                     deleteIdIndex(node.children);
                 })
             };
-            for(let n of nodes){
+            for (let n of nodes) {
                 deleteIdIndex([n]);
                 if (n.preSibling) {
                     n.preSibling.setNextSibling(n.nextSibling);
@@ -733,76 +744,76 @@ var idTree = {
             let o_next = o_parent.nextSibling;//父节点的下一节点
             let o_pre = nodes[0].preSibling;
             let o_children = o_parent.children;//旧的所有兄弟节点
-            let children = o_parent.parent?o_parent.parent.children:this.roots;//新的兄弟节点
+            let children = o_parent.parent ? o_parent.parent.children : this.roots;//新的兄弟节点
             let last;
             let lastNext;//最后一个选中节点后面的所有兄弟节点变成最后一个节点的子节点
-            for(let i = 0; i<nodes.length;i++){
+            for (let i = 0; i < nodes.length; i++) {
                 let index = children.indexOf(o_parent) + 1;
-                children.splice(index + i,0,nodes[i]);//往新的父节点的子节点插入节点
+                children.splice(index + i, 0, nodes[i]);//往新的父节点的子节点插入节点
                 o_children.splice(nodes[i].siblingIndex(), 1);//旧的数组删除节点
-                if(i == 0){//第一个节点变成原来父节点的下一节点
+                if (i == 0) {//第一个节点变成原来父节点的下一节点
                     o_parent.setNextSibling(nodes[i]);
-                    if(o_pre) o_pre.setNextSibling(null); //第一个选中节点的前一节点的下一节点设置为空
+                    if (o_pre) o_pre.setNextSibling(null); //第一个选中节点的前一节点的下一节点设置为空
                 }
                 nodes[i].setParent(o_parent.parent);
                 last = nodes[i];
                 lastNext = last.nextSibling;
             }
             last.setNextSibling(o_next);//最后一个选中的节点的下一个节点设置为原父节点的下一节点
-            if(lastNext){
+            if (lastNext) {
                 let t_index = o_children.indexOf(lastNext);
-                for(let j = t_index;j <o_children.length;j++ ){//剩下的添加为最后一个选中节点的子节点
+                for (let j = t_index; j < o_children.length; j++) {//剩下的添加为最后一个选中节点的子节点
                     last.addChild(o_children[j]);
                 }
-                if(o_children.length > t_index)  o_children.splice(t_index, o_children.length - t_index);//从原先的children中移除
+                if (o_children.length > t_index) o_children.splice(t_index, o_children.length - t_index);//从原先的children中移除
             }
-            if (o_parent.parent&& !o_parent.parent.expanded)  o_parent.parent.setExpanded(true);
+            if (o_parent.parent && !o_parent.parent.expanded) o_parent.parent.setExpanded(true);
             tools.sortTreeItems(this);
             return true;
         };
         Tree.prototype.getUpLevelDatas = function (nodes) {
             //getParentID
-            let o_parentID =  nodes[0].getParentID();
+            let o_parentID = nodes[0].getParentID();
             let o_children = nodes[0].parent.children;//旧的所有兄弟节点
             let o_pre = nodes[0].preSibling;
             let new_parentID = nodes[0].parent.getParentID();
             let o_nextID = nodes[0].parent.getNextSiblingID();
-            let dataMap = {},updateDatas=[],lastID,lastNext;
-            for(let i = 0; i<nodes.length;i++){
-                if(i == 0){
-                    dataMap[o_parentID] = {"ID":o_parentID,"NextSiblingID":nodes[i].getID()};
-                    if(o_pre) dataMap[o_pre.getID()] = {"ID":o_pre.getID(),"NextSiblingID":-1}; //nodes[i].preSibling.setNextSibling(null);
+            let dataMap = {}, updateDatas = [], lastID, lastNext;
+            for (let i = 0; i < nodes.length; i++) {
+                if (i == 0) {
+                    dataMap[o_parentID] = { "ID": o_parentID, "NextSiblingID": nodes[i].getID() };
+                    if (o_pre) dataMap[o_pre.getID()] = { "ID": o_pre.getID(), "NextSiblingID": -1 }; //nodes[i].preSibling.setNextSibling(null);
                 }
-                dataMap[nodes[i].getID()] = {"ID":nodes[i].getID(),"ParentID":new_parentID};
+                dataMap[nodes[i].getID()] = { "ID": nodes[i].getID(), "ParentID": new_parentID };
                 lastID = nodes[i].getID();
                 lastNext = nodes[i].nextSibling;
             }
-            if(dataMap[lastID] !== undefined){
+            if (dataMap[lastID] !== undefined) {
                 dataMap[lastID].NextSiblingID = o_nextID;
             }
-            if(lastNext){
+            if (lastNext) {
                 let t_index = o_children.indexOf(lastNext);
-                for(let j = t_index;j <o_children.length;j++ ){//剩下的添加为最后一个选中节点的子节点
-                    dataMap[o_children[j].getID()] = {"ID":o_children[j].getID(),"ParentID":lastID};
+                for (let j = t_index; j < o_children.length; j++) {//剩下的添加为最后一个选中节点的子节点
+                    dataMap[o_children[j].getID()] = { "ID": o_children[j].getID(), "ParentID": lastID };
                 }
             }
-            for(let key in dataMap){
-                updateDatas.push({type: 'update', data:dataMap[key]});
+            for (let key in dataMap) {
+                updateDatas.push({ type: 'update', data: dataMap[key] });
             }
             return updateDatas;
         };
         Tree.prototype.m_downLevel = function (nodes) {
-            let pre = nodes[0].preSibling ; //第一个节点的前一节点,即会成为新的父节点
-            let next ;//最后一个节点的后一节点,会成为pre 的下一个节点
-            let last ;//选中的最后一个节点,nextSibling要设置为0
-            for( let n of nodes){
+            let pre = nodes[0].preSibling; //第一个节点的前一节点,即会成为新的父节点
+            let next;//最后一个节点的后一节点,会成为pre 的下一个节点
+            let last;//选中的最后一个节点,nextSibling要设置为0
+            for (let n of nodes) {
                 next = n.nextSibling;
                 last = n;
-                let  children = n.parent?n.parent.children:this.roots;
+                let children = n.parent ? n.parent.children : this.roots;
                 children.splice(n.siblingIndex(), 1);
                 pre.addChild(n);
             }
-            if (!pre.expanded)  pre.setExpanded(true);
+            if (!pre.expanded) pre.setExpanded(true);
             pre.setNextSibling(next);
             last.nextSibling = null;
             tools.sortTreeItems(this);
@@ -810,25 +821,25 @@ var idTree = {
         };
 
         Tree.prototype.getDownLevelDatas = function (nodes) {
-            let dataMap = {},updateDatas=[],nextID,last;//注释同m_downLevel 方法
+            let dataMap = {}, updateDatas = [], nextID, last;//注释同m_downLevel 方法
             let newParent = nodes[0].preSibling;//{"type":"update","data":{"ID":3,"ParentID":-1,"NextSiblingID":5}}
-            let newPre = newParent.children && newParent.children.length > 0 ? newParent.children[newParent.children.length -1]:null;
-            if(newPre){ //如果新的父节点有子节点,则把新的父节点的最后一个子节点的下一节点的值改成第一个选中节点的ID
-                dataMap[newPre.getID()] = {"ID":newPre.getID(),"NextSiblingID":nodes[0].getID()}
+            let newPre = newParent.children && newParent.children.length > 0 ? newParent.children[newParent.children.length - 1] : null;
+            if (newPre) { //如果新的父节点有子节点,则把新的父节点的最后一个子节点的下一节点的值改成第一个选中节点的ID
+                dataMap[newPre.getID()] = { "ID": newPre.getID(), "NextSiblingID": nodes[0].getID() }
             }
-            for(let n of nodes){
+            for (let n of nodes) {
                 nextID = n.getNextSiblingID();
                 last = n;
-                dataMap[n.getID()] = {"ID":n.getID(),"ParentID":newParent.getID()}//修改父ID;
+                dataMap[n.getID()] = { "ID": n.getID(), "ParentID": newParent.getID() }//修改父ID;
             }
-            dataMap[newParent.getID()] = {"ID":newParent.getID(),"NextSiblingID":nextID}//设置新的父节点的下一个节点ID;
-            if(dataMap[last.getID()]!==undefined){//把最后一个节点的下一个节点ID变成-1
+            dataMap[newParent.getID()] = { "ID": newParent.getID(), "NextSiblingID": nextID }//设置新的父节点的下一个节点ID;
+            if (dataMap[last.getID()] !== undefined) {//把最后一个节点的下一个节点ID变成-1
                 dataMap[last.getID()].NextSiblingID = -1
-            }else {
-                dataMap[last.getID()] = {"ID":last.getID(),"NextSiblingID":-1};
+            } else {
+                dataMap[last.getID()] = { "ID": last.getID(), "NextSiblingID": -1 };
             }
-            for(let key in dataMap){
-                updateDatas.push({type: 'update', data:dataMap[key]});
+            for (let key in dataMap) {
+                updateDatas.push({ type: 'update', data: dataMap[key] });
             }
             return updateDatas;
         };
@@ -839,7 +850,7 @@ var idTree = {
                 nodes.forEach(function (node) {
                     var delData = {};
                     delData[node.tree.setting.id] = node.getID();
-                    datas.push({type: 'delete', data: delData});
+                    datas.push({ type: 'delete', data: delData });
                     addUpdateDataForDelete(datas, node.children);
                 })
             };
@@ -851,32 +862,32 @@ var idTree = {
             }
             return data;
         };
-        Tree.prototype.getDeleteDatas = function (deleteMap,deleteNodes){//批量删除
+        Tree.prototype.getDeleteDatas = function (deleteMap, deleteNodes) {//批量删除
             let datas = [];
-            addDeleteDatas(datas,deleteNodes);
-            for(let d of deleteNodes){
-                addPreUpdateData(datas,deleteMap,d.preSibling,d.nextSibling);
-            }
-           function addPreUpdateData(updateDatas,map,preSibling,nextSibling) {
-               if(preSibling && (map[preSibling.getID()] == undefined || map[preSibling.getID()] == null)){
-                   if(nextSibling){
-                      if(map[nextSibling.getID()]){//如果下一个节点也是要删除的,则再往下顺延
-                          addPreUpdateData(updateDatas,map,preSibling,nextSibling.nextSibling);
-                      }else {
-                          updateDatas.push({type: 'update', data: preSibling.tree.getDataTemplate(preSibling.getID(), preSibling.getParentID(),nextSibling.getID())});
-                      }
-                   }else {
-                       updateDatas.push({type: 'update', data: preSibling.tree.getDataTemplate(preSibling.getID(), preSibling.getParentID(),-1)});
-                   }
-               }
-           }
-
-            function addDeleteDatas(dataArray,nodes) {
-                for(let n of nodes){
+            addDeleteDatas(datas, deleteNodes);
+            for (let d of deleteNodes) {
+                addPreUpdateData(datas, deleteMap, d.preSibling, d.nextSibling);
+            }
+            function addPreUpdateData(updateDatas, map, preSibling, nextSibling) {
+                if (preSibling && (map[preSibling.getID()] == undefined || map[preSibling.getID()] == null)) {
+                    if (nextSibling) {
+                        if (map[nextSibling.getID()]) {//如果下一个节点也是要删除的,则再往下顺延
+                            addPreUpdateData(updateDatas, map, preSibling, nextSibling.nextSibling);
+                        } else {
+                            updateDatas.push({ type: 'update', data: preSibling.tree.getDataTemplate(preSibling.getID(), preSibling.getParentID(), nextSibling.getID()) });
+                        }
+                    } else {
+                        updateDatas.push({ type: 'update', data: preSibling.tree.getDataTemplate(preSibling.getID(), preSibling.getParentID(), -1) });
+                    }
+                }
+            }
+
+            function addDeleteDatas(dataArray, nodes) {
+                for (let n of nodes) {
                     let delData = {};
                     delData[n.tree.setting.id] = n.getID();
-                    dataArray.push({type: 'delete', data: delData});
-                    addDeleteDatas(dataArray,n.children);
+                    dataArray.push({ type: 'delete', data: delData });
+                    addDeleteDatas(dataArray, n.children);
                 }
             }
             return datas;
@@ -884,8 +895,8 @@ var idTree = {
 
         Tree.prototype.getExpState = function (nodes) {
             let sessionExpanded = [];
-            function getStat(items){
-                for(let item of items){
+            function getStat(items) {
+                for (let item of items) {
                     sessionExpanded.push(item.expanded ? 1 : 0);
                 }
             }
@@ -897,9 +908,9 @@ var idTree = {
         //节点根据展开收起列表'010101'展开收起
         Tree.prototype.setExpandedByState = function (nodes, expState) {
             let expStateArr = expState.split('');
-            for(let i = 0; i < nodes.length; i++){
+            for (let i = 0; i < nodes.length; i++) {
                 let expanded = expStateArr[i] == 1 ? true : false;
-                if(nodes[i].expanded === expanded){
+                if (nodes[i].expanded === expanded) {
                     continue;
                 }
                 nodes[i].setExpanded(expanded);
@@ -970,7 +981,25 @@ var idTree = {
             this.event[eventName] = eventFun;
         };
 
+        Tree.prototype.resetID = function (items, IDFunc) {
+            const IDMap = {};
+            items.forEach(item => {
+                IDMap[item.ID] = IDFunc();
+            });
+            items.forEach(item => {
+                if (IDMap[item.ID]) {
+                    item.ID = IDMap[item.ID];
+                }
+                if (IDMap[item.ParentID]) {
+                    item.ParentID = IDMap[item.ParentID];
+                }
+                if (IDMap[item.NextSiblingID]) {
+                    item.NextSiblingID = IDMap[item.NextSiblingID];
+                }
+            });
+        };
+
         return new Tree(setting);
     },
-    updateType: {update: 'update', new: 'new', delete: 'delete'}
+    updateType: { update: 'update', new: 'new', delete: 'delete' }
 };

+ 23 - 0
public/web/lock_util.js

@@ -78,6 +78,28 @@ const lockUtil = (() => {
         $url.prop('href', curURL);
     }
 
+    function unLockSpreads(spreads) {
+        spreads.forEach(spread => {
+            spread.unbind(GC.Spread.Sheets.Events.ButtonClicked);
+            const sheetCount = spread.getSheetCount();
+            for (let i = 0; i < sheetCount; i++) {
+                const sheet = spread.getSheet(i);
+                sheet.suspendPaint();
+                sheet.suspendEvent();
+                sheet.options.isProtected = false;
+                const rowCount = sheet.getRowCount();
+                const colCount = sheet.getColumnCount();
+                for (let row = 0; row < rowCount; row++) {
+                    for (let col = 0; col < colCount; col++) {
+                        sheet.getCell(row, col).locked(false);
+                    }
+                }
+                sheet.resumePaint();
+                sheet.resumeEvent();
+            }
+        });
+    }
+
     function displayLock($lock, locked) {
         $lock.data('locked', locked);
         const innerHtml = locked ? '<i class="fa fa-unlock-alt"></i>' : '<i class="fa fa-lock"></i>';
@@ -107,6 +129,7 @@ const lockUtil = (() => {
         getLocked,
         lockTools,
         lockSpreads,
+        unLockSpreads,
         lockURL,
         displayLock,
         handleLockClick,

+ 63 - 25
public/web/tree_sheet/tree_sheet_controller.js

@@ -31,7 +31,7 @@ var TREE_SHEET_CONTROLLER = {
                 var sels = me.sheet.getSelections();
                 newNodes.sort(function (a, b) {
                     let rst = 0;
-                    if(a.serialNo() > b.serialNo()){
+                    if (a.serialNo() > b.serialNo()) {
                         rst = 1;
                     }
                     else {
@@ -39,10 +39,10 @@ var TREE_SHEET_CONTROLLER = {
                     }
                     return rst;
                 });
-                for(let newNode of newNodes){
+                for (let newNode of newNodes) {
                     me.sheet.addRows(newNode.serialNo(), 1);
                 }
-                for(let newNode of newNodes){
+                for (let newNode of newNodes) {
                     me.setTreeSelected(newNode);
                     TREE_SHEET_HELPER.refreshTreeNodeData(me.setting, me.sheet, [newNode], false);
                     me.sheet.setSelection(newNode.serialNo(), sels[0].col, 1, 1);
@@ -51,7 +51,7 @@ var TREE_SHEET_CONTROLLER = {
         };
 
         controller.prototype.insert = function () {
-            var newNode = null, that = this,  sels = this.sheet.getSelections();
+            var newNode = null, that = this, sels = this.sheet.getSelections();
             if (this.tree) {
                 if (this.tree.selected) {
                     newNode = this.tree.insert(this.tree.selected.getParentID(), this.tree.selected.getNextSiblingID());
@@ -70,10 +70,10 @@ var TREE_SHEET_CONTROLLER = {
             }
         };
         controller.prototype.m_insert = function (datas) {
-            let nodes = [], that = this,  sels = this.sheet.getSelections();
-            if(this.tree){
+            let nodes = [], that = this, sels = this.sheet.getSelections();
+            if (this.tree) {
                 if (this.tree.selected) {
-                    nodes = this.tree.m_insert(datas,this.tree.selected.getParentID(), this.tree.selected.getNextSiblingID());
+                    nodes = this.tree.m_insert(datas, this.tree.selected.getParentID(), this.tree.selected.getNextSiblingID());
                 } else {
                     nodes = this.tree.m_insert(datas);
                 }
@@ -88,11 +88,48 @@ var TREE_SHEET_CONTROLLER = {
                     //that.sheet.showRow(newNode.serialNo(), GC.Spread.Sheets.VerticalPosition.center);
                 });
             }
+            return nodes;
+        };
+
+        controller.prototype.insertByDatas = function (datas) {
+            let nodes = [], that = this, sels = this.sheet.getSelections();
+            if (this.tree) {
+                nodes = this.tree.insertByDatas(datas);
+            }
+            let length = nodes.length;
+            if (nodes.length > 0) {
+                TREE_SHEET_HELPER.massOperationSheet(this.sheet, function () {
+                    that.sheet.addRows(nodes[0].serialNo(), length);
+                    TREE_SHEET_HELPER.refreshTreeNodeData(that.setting, that.sheet, nodes, false);
+                    that.setTreeSelected(nodes[0]);
+                    that.sheet.setSelection(nodes[0].serialNo(), sels[0].col, 1, 1);
+                    //that.sheet.showRow(newNode.serialNo(), GC.Spread.Sheets.VerticalPosition.center);
+                });
+            }
+            return nodes;
+        };
+
+        controller.prototype.insertByDatas = function (datas) {
+            let nodes = [], that = this, sels = this.sheet.getSelections();
+            if (this.tree) {
+                nodes = this.tree.insertByDatas(datas);
+            }
+            let length = nodes.length;
+            if (nodes.length > 0) {
+                TREE_SHEET_HELPER.massOperationSheet(this.sheet, function () {
+                    that.sheet.addRows(nodes[length - 1].serialNo(), length);
+                    TREE_SHEET_HELPER.refreshTreeNodeData(that.setting, that.sheet, nodes, false);
+                    that.setTreeSelected(nodes[length - 1]);
+                    that.sheet.setSelection(nodes[length - 1].serialNo(), sels[0].col, 1, 1);
+                    //that.sheet.showRow(newNode.serialNo(), GC.Spread.Sheets.VerticalPosition.center);
+                });
+            }
+            return nodes;
         };
 
 
         controller.prototype.insertByID = function (ID) {
-            var newNode = null, that = this,  sels = this.sheet.getSelections();
+            var newNode = null, that = this, sels = this.sheet.getSelections();
             if (this.tree) {
                 if (this.tree.selected) {
                     newNode = this.tree.insertByID(ID, this.tree.selected.getParentID(), this.tree.selected.getNextSiblingID());
@@ -112,7 +149,7 @@ var TREE_SHEET_CONTROLLER = {
             return newNode;
         };
         controller.prototype.insertByIDS = function (ID, ParentID, NexSiblingID) {
-            var newNode = null, that = this,  sels = this.sheet.getSelections();
+            var newNode = null, that = this, sels = this.sheet.getSelections();
             if (this.tree) {
                 if (this.tree.selected) {
                     newNode = this.tree.insertByID(ID, ParentID, NexSiblingID);
@@ -132,7 +169,7 @@ var TREE_SHEET_CONTROLLER = {
             return newNode;
         };
         controller.prototype.insertToChild = function (ID, ParentID, NextSiblingID) {
-            var newNode = null, that = this,  sels = this.sheet.getSelections();
+            var newNode = null, that = this, sels = this.sheet.getSelections();
             if (this.tree) {
                 if (this.tree.selected) {
                     newNode = this.tree.insertByID(ID, ParentID, NextSiblingID);
@@ -164,16 +201,17 @@ var TREE_SHEET_CONTROLLER = {
         };
         controller.prototype.m_delete = function (nodes) {
             var that = this, sels = this.sheet.getSelections();
-            if(nodes.length > 0){
-                if(this.tree.m_delete(nodes)){
+            if (nodes.length > 0) {
+                if (this.tree.m_delete(nodes)) {
                     TREE_SHEET_HELPER.massOperationSheet(this.sheet, function () {
                         let rowCount = 0;
-                        for(let node of nodes){
-                            rowCount = rowCount+node.posterityCount() + 1;
+                        for (let node of nodes) {
+                            rowCount = rowCount + node.posterityCount() + 1;
                         }
                         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);
+                        const locateRow = that.tree.items.length <= sels[0].row ? that.tree.items.length - 1 : sels[0].row;
+                        that.setTreeSelected(that.tree.items[locateRow]);
+                        that.sheet.setSelection(locateRow, sels[0].col, 1, sels[0].colCount);
                     });
                 }
             }
@@ -184,7 +222,7 @@ var TREE_SHEET_CONTROLLER = {
                 if (this.tree.selected.upLevel()) {
                     TREE_SHEET_HELPER.massOperationSheet(this.sheet, function () {
                         TREE_SHEET_HELPER.refreshNodesVisible([that.tree.selected], that.sheet, true);
-                       // that.sheet.showRow(that.tree.selected.serialNo(), GC.Spread.Sheets.VerticalPosition.center);
+                        // that.sheet.showRow(that.tree.selected.serialNo(), GC.Spread.Sheets.VerticalPosition.center);
                         if (that.event.refreshBaseActn) {
                             that.event.refreshBaseActn(that.tree);
                         }
@@ -235,12 +273,12 @@ var TREE_SHEET_CONTROLLER = {
             var that = this, sels = this.sheet.getSelections();
             if (this.tree.selected) {
                 TREE_SHEET_HELPER.massOperationSheet(this.sheet, function () {
-                    TREE_SHEET_HELPER.refreshChildrenVisiable(that.sheet,that.tree,that.tree.selected,that.tree.selected.serialNo(),true);//为了处理移动前子项是隐藏的情况,先把所有的列设置为显示
-                    TREE_SHEET_HELPER.refreshChildrenVisiable(that.sheet,that.tree,that.tree.selected.preSibling,that.tree.selected.preSibling.serialNo(),true);
+                    TREE_SHEET_HELPER.refreshChildrenVisiable(that.sheet, that.tree, that.tree.selected, that.tree.selected.serialNo(), true);//为了处理移动前子项是隐藏的情况,先把所有的列设置为显示
+                    TREE_SHEET_HELPER.refreshChildrenVisiable(that.sheet, that.tree, that.tree.selected.preSibling, that.tree.selected.preSibling.serialNo(), true);
                     if (that.tree.selected.upMove()) {
                         TREE_SHEET_HELPER.refreshTreeNodeData(that.setting, that.sheet, [that.tree.selected, that.tree.selected.nextSibling], true);
-                        TREE_SHEET_HELPER.refreshChildrenVisiable(that.sheet,that.tree,that.tree.selected,that.tree.selected.serialNo());
-                        TREE_SHEET_HELPER.refreshChildrenVisiable(that.sheet,that.tree,that.tree.selected.nextSibling,that.tree.selected.nextSibling.serialNo());
+                        TREE_SHEET_HELPER.refreshChildrenVisiable(that.sheet, that.tree, that.tree.selected, that.tree.selected.serialNo());
+                        TREE_SHEET_HELPER.refreshChildrenVisiable(that.sheet, that.tree, that.tree.selected.nextSibling, that.tree.selected.nextSibling.serialNo());
                         that.sheet.setSelection(that.tree.selected.serialNo(), sels[0].col, 1, 1);
                         if (that.event.refreshBaseActn) {
                             that.event.refreshBaseActn(that.tree);
@@ -253,12 +291,12 @@ var TREE_SHEET_CONTROLLER = {
             var that = this, sels = this.sheet.getSelections();
             if (this.tree.selected) {
                 TREE_SHEET_HELPER.massOperationSheet(this.sheet, function () {
-                    TREE_SHEET_HELPER.refreshChildrenVisiable(that.sheet,that.tree,that.tree.selected,that.tree.selected.serialNo(),true);//为了处理移动前子项是隐藏的情况,先把所有的列设置为显示
-                    TREE_SHEET_HELPER.refreshChildrenVisiable(that.sheet,that.tree,that.tree.selected.nextSibling,that.tree.selected.nextSibling.serialNo(),true);
+                    TREE_SHEET_HELPER.refreshChildrenVisiable(that.sheet, that.tree, that.tree.selected, that.tree.selected.serialNo(), true);//为了处理移动前子项是隐藏的情况,先把所有的列设置为显示
+                    TREE_SHEET_HELPER.refreshChildrenVisiable(that.sheet, that.tree, that.tree.selected.nextSibling, that.tree.selected.nextSibling.serialNo(), true);
                     if (that.tree.selected.downMove()) {
                         TREE_SHEET_HELPER.refreshTreeNodeData(that.setting, that.sheet, [that.tree.selected, that.tree.selected.preSibling], true);
-                        TREE_SHEET_HELPER.refreshChildrenVisiable(that.sheet,that.tree,that.tree.selected,that.tree.selected.serialNo());
-                        TREE_SHEET_HELPER.refreshChildrenVisiable(that.sheet,that.tree,that.tree.selected.preSibling,that.tree.selected.preSibling.serialNo());
+                        TREE_SHEET_HELPER.refreshChildrenVisiable(that.sheet, that.tree, that.tree.selected, that.tree.selected.serialNo());
+                        TREE_SHEET_HELPER.refreshChildrenVisiable(that.sheet, that.tree, that.tree.selected.preSibling, that.tree.selected.preSibling.serialNo());
                         that.sheet.setSelection(that.tree.selected.serialNo(), sels[0].col, 1, 1);
                         if (that.event.refreshBaseActn) {
                             that.event.refreshBaseActn(that.tree);

+ 9 - 1
web/maintain/bills_lib/html/qingdan.html

@@ -9,6 +9,7 @@
     <link rel="stylesheet" href="/lib/bootstrap/css/bootstrap.min.css">
     <link rel="stylesheet" href="/web/maintain/bills_lib/css/main.css">
     <link rel="stylesheet" href="/lib/font-awesome/font-awesome.min.css">
+    <link rel="stylesheet" href="/lib/jquery-contextmenu/jquery.contextMenu.css">
     <!--spread-->
     <link rel="stylesheet" href="/lib/spreadjs/sheets/css/gc.spread.sheets.sc.css">
     <link rel="stylesheet" href="/lib/codemirror/codemirror.css">
@@ -340,16 +341,21 @@
     <%include ../../../common/html/uploadImg.html %>
     <!-- JS. -->
     <script src = "/lib/spreadjs/sheets/gc.spread.sheets.all.11.1.2.min.js"></script>
+    <script src = "/lib/localforage/localforage.min.js"></script>
+    <script src="/public/common_util.js"></script>
     <script>GC.Spread.Sheets.LicenseKey =  '<%- LicenseKey %>';</script>
     <script src="/public/web/uuid.js"></script>
     <script src="/lib/jquery/jquery.min.js"></script>
     <script src="/lib/tether/tether.min.js"></script>
+    <script src="/lib/jquery-contextmenu/jquery.contextMenu.min.js"></script>
+    <script src="/lib/jquery-contextmenu/jquery.ui.position.js"></script>
     <script src="/lib/bootstrap/bootstrap.min.js"></script>
     <script src="/web/maintain/bills_lib/scripts/global.js"></script>
     <script src="/public/web/PerfectLoad.js"></script>
     <script src="/public/web/common_ajax.js"></script>
     <script src="/public/web/lock_util.js"></script>
     <script src="/public/web/sheet/sheet_common.js"></script>
+    <script type="text/javascript" src="/public/web/sheet/sheet_data_helper.js"></script>
     <script src="/web/maintain/bills_lib/scripts/set_sheets.js"></script>
     <script src="/web/maintain/bills_lib/scripts/bills_lib_ajax.js"></script>
     <!--idTree-->
@@ -581,6 +587,7 @@
         let kindComboList = kindList.map((data) => data.name);
         let kindCol = setting.cols.findIndex((data) => data.data.field === 'kind');
         setCombo(billsSheet, kindCol, kindComboList);
+        onContextmenuOpr(billsSpread, controller);
     }
 
     function switchFcs(controller, billsSheet, billsSpread, jobsSheet, itemsSheet){
@@ -870,8 +877,9 @@
         sheet.bind(GC.Spread.Sheets.Events.ClipboardPasting, function (sender, args) {
             sheetBillsDatas = tools.getsheetDatas(sheet, 'bills', controller);
             let maxCol = args.cellRange.col + args.cellRange.colCount - 1;
+            let maxRow = args.cellRange.row + args.cellRange.rowCount - 1;
             //复制的列数超过正确的列数,不可复制
-            if(maxCol >= billsLibSetting.cols.length){
+            if(maxCol >= billsLibSetting.cols.length || maxRow >= controller.tree.items.length){
                 args.cancel = true;
             }
         });

+ 282 - 47
web/maintain/bills_lib/scripts/db_controller.js

@@ -32,7 +32,8 @@ function getNewBIlls(controller, rowCount, billsLibId) {
     };
     if (i === rowCount - 1) {
       tem.NextSiblingID = NextSiblingID;
-    } else if (i !== 0) {
+    }
+    if (i !== 0) {
       insertBills[i - 1].NextSiblingID = tem.ID;
     }
 
@@ -46,6 +47,273 @@ function getNewBIlls(controller, rowCount, billsLibId) {
   return { insertBills, updatePreData };
 }
 
+// 创建清单
+const createBills = (controller, btn, insertBills, updatePreData, isInsertByDatas) => {
+  billsAjax.createBills(
+    userAccount,
+    billsLibId,
+    updatePreData,
+    insertBills,
+    function () {
+      if (updatePreData) {
+        const node = controller.tree.findNode(updatePreData.ID);
+        if (node) {
+          Object.assign(node.data, updatePreData);
+        }
+      }
+      // 显示的时候,类别和固定ID转换为名称显示
+      const kindMap = {};
+      kindList.forEach(item => {
+        kindMap[item.value] = item.name;
+      });
+      const flagMap = {};
+      BillsFixedFlagList.forEach(item => {
+        flagMap[item.value] = item.name;
+      });
+      insertBills.forEach(bill => {
+        if (bill.kind) {
+          bill.kind = kindMap[bill.kind] || '';
+        }
+        if (bill.fixedFlag) {
+          bill.fixedFlag = flagMap[bill.fixedFlag] || '';
+        }
+      })
+      const treeData = insertBills.map((item) => ({
+        type: "new",
+        data: item,
+      }));
+      const newNodes = isInsertByDatas ? controller.insertByDatas(insertBills) : controller.m_insert(treeData);
+      newNodes.forEach(n => {
+        n.jobs = [];
+        n.items = [];
+        n.designs = [];
+        controller.sheet.setTag(
+          n.serialNo(),
+          0,
+          n.getID()
+        );
+      });
+      sheetBillsDatas = tools.getsheetDatas(
+        controller.sheet,
+        "bills",
+        controller
+      );
+      if (btn) {
+        tools.btnAction(btn);
+        btn.attr("doing", "false");
+      }
+      controller.sheet.getParent().focus(true);
+    }
+  );
+}
+
+// 获取需要复制的节点(与第一个节点同层的所有节点,包括后代)
+const getCopyNodes = (controller) => {
+  const { tree, sheet } = controller;
+  const selection = sheet.getSelections()[0];
+  const nodes = [];
+  const firstNode = tree.items[selection.row];
+  if (!firstNode) {
+    return [];
+  }
+  const firstNodeDepth = firstNode.depth();
+  for (let i = 0; i < selection.rowCount; i++) {
+    const row = selection.row + i;
+    const node = tree.items[row];
+    if (node && node.depth() === firstNodeDepth) {
+      nodes.push(node);
+    }
+  }
+  const rst = [];
+  nodes.forEach(node => {
+    const allNodes = [node, ...node.getPosterity()];
+    allNodes.forEach(n => {
+      if (!rst.includes(n)) {
+        rst.push(n);
+      }
+    })
+  });
+  return rst;
+}
+
+// 复制整块
+const copyBlock = async (controller) => {
+  try {
+    $.bootstrapLoading.start();
+    const nodes = getCopyNodes(controller);
+    const bills = [];
+    nodes.forEach(node => {
+      const bill = {};
+      Object.keys(node.data).forEach(key => {
+        if (typeof node.data[key] !== 'object') {
+          bill[key] = node.data[key];
+          bill.ID = node.getID();
+          bill.ParentID = node.getParentID();
+          bill.NexSiblingID = node.getNextSiblingID();
+        }
+      });
+      bills.push(bill);
+    });
+    await localforage.setItem('billsBlock', bills);
+    console.log(bills);
+  } catch (error) {
+    alert(error.message);
+  }
+  $.bootstrapLoading.end();
+}
+
+// 设置节点的前三层信息
+const setPasteSectionInfo = (tree, bills) => {
+  const IDMap = {};
+  tree.items.forEach(item => {
+    IDMap[item.getID()] = { ID: item.getID(), ParentID: item.getParentID(), NexSiblingID: item.getNextSiblingID() }; // 树操作后可能有缓存问题,data里的ID还没变
+  });
+  bills.forEach(bill => {
+    IDMap[bill.ID] = bill;
+  });
+  const getThreeParentIDs = (bill) => {
+    const parentIDs = [];
+    let cur = bill;
+    for (let i = 0; i < 3; i++) {
+      const parent = IDMap[cur.ParentID];
+      if (parent) {
+        parentIDs.push(parent.ID);
+        cur = parent;
+      } else {
+        break;
+      }
+    }
+    return parentIDs.reverse();
+  };
+  bills.forEach(bill => {
+    const parentIDs = getThreeParentIDs(bill);
+    const sectionInfo = { first: parentIDs[0] || null, second: parentIDs[1] || null, third: parentIDs[2] || null };
+    bill.sectionInfo = sectionInfo;
+  });
+}
+
+// 获取粘贴整块数据
+const getPasteData = async (controller) => {
+  const bills = await localforage.getItem('billsBlock');
+  if (!bills || !bills.length) {
+    throw new Error('请先复制整块!');
+  }
+  const { tree } = controller;
+  const selected = tree.selected;
+  if (!selected) {
+    throw new Error('请选中节点!');
+  }
+  tree.resetID(bills, uuid.v1);
+  // 粘贴到选中节点的最末子项
+  let updatePreData = null;
+  const lastChild = selected.lastChild();
+  const firstBill = bills[0];
+  if (lastChild) {
+    updatePreData = { ID: lastChild.getID(), NextSiblingID: firstBill.ID };
+  }
+  let lastRoot = firstBill;
+  const kindMap = {};
+  kindList.forEach(item => {
+    kindMap[item.name] = item.value;
+  });
+  const flagMap = {};
+  BillsFixedFlagList.forEach(item => {
+    flagMap[item.name] = item.value;
+  });
+  const firstParentID = firstBill.ParentID;
+  bills.forEach(bill => {
+    bill.billsLibId = billsLibId;
+    bill.jobs = [];
+    bill.items = [];
+    bill.designs = [];
+    if (bill.kind) {
+      bill.kind = kindMap[bill.kind] || undefined;
+    }
+    if (bill.fixedFlag) {
+      bill.fixedFlag = flagMap[bill.fixedFlag] || undefined;
+    }
+    if (bill.ParentID === firstParentID) {
+      bill.ParentID = selected.getID();
+      lastRoot = bill;
+    }
+  });
+  if (lastRoot) {
+    lastRoot.NexSiblingID = -1;
+  }
+  setPasteSectionInfo(tree, bills);
+  return { insertBills: bills, updatePreData };
+}
+
+// 粘贴整块
+const pasteBlock = async (controller) => {
+  try {
+    $.bootstrapLoading.end();
+    const { insertBills, updatePreData } = await getPasteData(controller);
+    if (insertBills.length) {
+      createBills(controller, undefined, insertBills, updatePreData, true);
+    }
+    controller.event.refreshBaseActn(controller.tree);
+  } catch (error) {
+    alert(error.message);
+  }
+  $.bootstrapLoading.end();
+}
+
+
+// 右键
+const onContextmenuOpr = (workBook, controller) => {
+  //右键菜单
+  $.contextMenu({
+    selector: "#spreadBills",
+    build: function ($triggerElement, e) {
+      //控制允许右键菜单在哪个位置出现
+      let target = SheetDataHelper.safeRightClickSelection(
+        $triggerElement,
+        e,
+        workBook
+      );
+      let bill = controller.tree.items[target.row] || null;
+      controller.setTreeSelected(bill);
+      if (target.hitTestType === 3) {
+        return {
+          callback: function () { },
+          items: {
+            copyBlock: {
+              name: "复制整块",
+              disabled: function () {
+                const inValidCell =
+                  !commonUtil.isDef(target.row) ||
+                  !commonUtil.isDef(target.col);
+                const inValidData = target.row >= controller.tree.items.length;
+                return locked || inValidCell || inValidData;
+              },
+              icon: "fa-copy",
+              callback: function (key, opt) {
+                copyBlock(controller);
+              },
+            },
+            pasteBlock: {
+              name: "粘贴整块",
+              disabled: function () {
+                const inValidCell =
+                  !commonUtil.isDef(target.row) ||
+                  !commonUtil.isDef(target.col);
+                const inValidData = target.row >= controller.tree.items.length;
+                return locked || inValidCell || inValidData;
+              },
+              icon: "fa-paste",
+              callback: function (key, opt) {
+                pasteBlock(controller);
+              },
+            },
+          },
+        };
+      } else {
+        return false;
+      }
+    },
+  });
+};
 var dbController = {
   controller: null,
   currentEditData: null,
@@ -57,41 +325,7 @@ var dbController = {
       rowCount,
       billsLibId
     );
-    billsAjax.createBills(
-      userAccount,
-      billsLibId,
-      updatePreData,
-      insertBills,
-      function () {
-        if (updatePreData) {
-          const node = controller.tree.findNode(updatePreData.ID);
-          if (node) {
-            Object.assign(node.data, updatePreData);
-          }
-        }
-
-        const treeData = insertBills.map((item) => ({
-          type: "new",
-          data: item,
-        }));
-        controller.m_insert(treeData);
-        controller.tree.selected.jobs = new Array();
-        controller.tree.selected.items = new Array();
-        controller.sheet.setTag(
-          controller.tree.selected.serialNo(),
-          0,
-          controller.tree.selected.getID()
-        );
-        sheetBillsDatas = tools.getsheetDatas(
-          controller.sheet,
-          "bills",
-          controller
-        );
-        tools.btnAction(btn);
-        btn.attr("doing", "false");
-        controller.sheet.getParent().focus(true);
-      }
-    );
+    createBills(controller, btn, insertBills, updatePreData);
   },
 
   upLevel: function (controller, btn) {
@@ -175,8 +409,8 @@ var dbController = {
         );
 
         billsAjax.upLevel(userAccount, billsLibId, updateData, function () {
-          //tools.btnAction(btn);
-          //btn.attr('doing', 'false');
+          tools.btnAction(btn);
+          btn.attr('doing', 'false');
           for (let upLevelNode of selNodes) {
             controller.setTreeSelected(upLevelNode);
             controller.upLevel();
@@ -192,8 +426,8 @@ var dbController = {
             billsAjax.updateSectionInfo(
               tools.getUpdateSectionData(toUpSectionNodes),
               function () {
-                tools.btnAction(btn);
-                btn.attr("doing", "false");
+                /* tools.btnAction(btn);
+                btn.attr("doing", "false"); */
               }
             );
           }
@@ -249,8 +483,8 @@ var dbController = {
         );
 
         billsAjax.downLevel(userAccount, billsLibId, updateData, function () {
-          //tools.btnAction(btn);
-          //btn.attr('doing', 'false');
+          tools.btnAction(btn);
+          btn.attr('doing', 'false');
           for (let downNode of selNodes) {
             controller.setTreeSelected(downNode);
             controller.downLevel();
@@ -266,8 +500,8 @@ var dbController = {
             billsAjax.updateSectionInfo(
               tools.getUpdateSectionData(toUpSectionNodes),
               function () {
-                tools.btnAction(btn);
-                btn.attr("doing", "false");
+                /* tools.btnAction(btn);
+                btn.attr("doing", "false"); */
               }
             );
           }
@@ -277,6 +511,7 @@ var dbController = {
   },
 
   delete: function (controller, btn, totalJobs, totalItems) {
+    debugger;
     tools.btnClose(btn);
     btn.attr("doing", "true");
     var node = controller.tree.selected;
@@ -299,9 +534,9 @@ var dbController = {
       }
       let updateNode = node.preSibling
         ? {
-            ID: node.preSibling.getID(),
-            NextSiblingID: node.getNextSiblingID(),
-          }
+          ID: node.preSibling.getID(),
+          NextSiblingID: node.getNextSiblingID(),
+        }
         : null;
       billsAjax.deleteBills(
         userAccount,

+ 33 - 10
web/maintain/price_info_lib/js/priceArea.js

@@ -1,5 +1,7 @@
 // 地区表
 const AREA_BOOK = (() => {
+  // 地区表格锁
+  let areaLocked = true;
   const cache = areaList;
   const setting = {
     header: [
@@ -9,7 +11,7 @@ const AREA_BOOK = (() => {
   };
   // 初始化表格
   const workBook = initSheet($('#area-spread')[0], setting);
-  lockUtil.lockSpreads([workBook], locked);
+  lockUtil.lockSpreads([workBook], locked || areaLocked);
   workBook.options.allowExtendPasteRange = false;
   workBook.options.allowUserDragDrop = false;
   workBook.options.allowUserDragFill = false;
@@ -55,13 +57,18 @@ const AREA_BOOK = (() => {
       });
     }
   }
-  sheet.bind(GC.Spread.Sheets.Events.ValueChanged, function (e, info) {
-    const changedCells = [{ row: info.row, col: info.col }];
-    handleEdit(changedCells);
-  });
-  sheet.bind(GC.Spread.Sheets.Events.RangeChanged, function (e, info) {
-    handleEdit(info.changedCells);
-  });
+
+  const bindEdit = () => {
+    sheet.bind(GC.Spread.Sheets.Events.ValueChanged, function (e, info) {
+      const changedCells = [{ row: info.row, col: info.col }];
+      handleEdit(changedCells);
+    });
+    sheet.bind(GC.Spread.Sheets.Events.RangeChanged, function (e, info) {
+      handleEdit(info.changedCells);
+    });
+  };
+
+  bindEdit();
 
   const curArea = { ID: null, name: '' };
   // 焦点变更处理
@@ -139,11 +146,27 @@ const AREA_BOOK = (() => {
           }
           return {
             items: {
+              lock: {
+                name: areaLocked ? '解锁' : '锁定',
+                icon: areaLocked ? "fa-unlock" : 'fa-lock',
+                disabled: function () {
+                  return locked;
+                },
+                callback: function (key, opt) {
+                  areaLocked = !areaLocked;
+                  if (locked || areaLocked) {
+                    lockUtil.lockSpreads([workBook], locked || areaLocked);
+                  } else {
+                    lockUtil.unLockSpreads([workBook]);
+                    bindEdit();
+                  }
+                }
+              },
               insert: {
                 name: '新增',
                 icon: "fa-arrow-left",
                 disabled: function () {
-                  return locked;
+                  return locked || areaLocked;
                 },
                 callback: function (key, opt) {
                   insert();
@@ -153,7 +176,7 @@ const AREA_BOOK = (() => {
                 name: '删除',
                 icon: "fa-arrow-left",
                 disabled: function () {
-                  return locked || !cache[target.row];
+                  return locked || areaLocked || !cache[target.row];
                 },
                 callback: function (key, opt) {
                   del();

+ 191 - 26
web/maintain/price_info_lib/js/priceEmpty.js

@@ -84,10 +84,14 @@ const EMPTY_BOOK = (() => {
     }
   }
 
+  // ai填值缓存,用于ai填值报错时,可以从当前报错处开始匹配,而不用重头开始匹配
+  let aiMatchCache = null;
+
   // 清空
   function clear() {
     cache.length = 0;
     workBookObj.sheet.setRowCount(0);
+    aiMatchCache = null;
   }
 
   let curRow = 0;
@@ -334,58 +338,86 @@ const EMPTY_BOOK = (() => {
     return code.substring(0, 4);
   }
 
+
+  const getAIMatchData = (summaryGroupMap, noCodeSummary) => {
+    if (aiMatchCache && aiMatchCache.changedCells.length && aiMatchCache.curIndex > 0) {
+      return aiMatchCache;
+    }
+    const curPercent = 0;
+    const curIndex = 0;
+    const totalRows = workBookObj.sheet.getRowCount();
+    const changedCells = [];
+    const noMatchRows = []; // 没有匹配、ai没有命中的行,后续需要自动生成别名编码(最大的别名编码+1)
+    for (let i = 0; i < totalRows; i++) {
+      const rowData = getRowData(workBookObj.sheet, i, setting.header);
+      // const code = rowData.code || '';
+      const code = getFourCode(rowData.code);
+      const toMatchSummary = code ? summaryGroupMap[code] || [] : noCodeSummary;
+      if (toMatchSummary.length) {
+        changedCells.push({ row: i });
+      } else {
+        noMatchRows.push(i);
+      }
+    }
+    return {
+      curPercent,
+      curIndex,
+      changedCells,
+      noMatchRows,
+    }
+  }
+
   // ai填值
   const aiMatch = async () => {
+    let percent = 0;
+    let curIndex = 0;
+    let noMatchRows = [];
+    let changedCells = [];
     try {
       // 获取信息价总表
       const priceInfoSummary = await ajaxPost('/priceInfoSummary/getData', {}, 1000 * 60 * 5);
       const summaryGroupMap = _.groupBy(priceInfoSummary, item => getFourCode(item.code));
       const noCodeSummary = priceInfoSummary.filter(item => !item.code);
-      const totalRows = workBookObj.sheet.getRowCount();
-      const changedCells = [];
-      const noMatchRows = []; // 没有匹配、ai没有命中的行,后续需要自动生成别名编码(最大的别名编码+1)
-      for (let i = 0; i < totalRows; i++) {
-        const rowData = getRowData(workBookObj.sheet, i, setting.header);
-        // const code = rowData.code || '';
-        const code = getFourCode(rowData.code);
-        const toMatchSummary = code ? summaryGroupMap[code] || [] : noCodeSummary;
-        if (toMatchSummary.length) {
-          changedCells.push({ row: i });
-        } else {
-          noMatchRows.push(i);
-        }
-      }
+
+      const aiMatchData = getAIMatchData(summaryGroupMap, noCodeSummary);
+      percent = aiMatchData.curPercent;
+      curIndex = aiMatchData.curIndex;
+      changedCells = aiMatchData.changedCells;
+      noMatchRows = aiMatchData.noMatchRows;
+
       if (!changedCells.length) {
         return;
       }
       const classCodeCol = setting.header.findIndex(h => h.dataCode === 'classCode');
       const expStringCol = setting.header.findIndex(h => h.dataCode === 'expString');
-      const chunks = _.chunk(changedCells, 20);
-      let percent = 0;
+      // const chunks = _.chunk(changedCells, 20);
+      const chunks = _.chunk(changedCells, 1); // 只能一条一条匹配改成,否则经常ai服务经常挂
       $.bootstrapLoading.progressStart('AI填值', false);
-      $("#progress_modal_body").text('正在进行AI填值,请稍后...');
+      $('#progress_modal_body').text(`正在进行AI填值,请稍后${curIndex + 1}/${chunks.length}...`);
       await setTimeoutSync(500);
+      const matchResCache = {};
 
       // 分块进行ai匹配
       const step = 100 / (chunks.length || 1);
-      for (const chunk of chunks) {
+      for (let i = curIndex; i < chunks.length; i++) {
+        curIndex = i;
+        const chunk = chunks[i];
         const listA = [];
         const listB = [];
         const summaryData = [];
         chunk.forEach(item => {
           const rowData = getRowData(workBookObj.sheet, item.row, setting.header);
           listA.push(`${rowData.name || ''} ${rowData.specs}`);
-          // const code = rowData.code || '';
           const code = getFourCode(rowData.code);
           const toMatchSummary = code ? summaryGroupMap[code] || [] : noCodeSummary;
           summaryData.push(toMatchSummary);
           const summaryKeys = toMatchSummary.map(summary => `${summary.name || ''} ${summary.specs || ''}`);
-          listB.push(summaryKeys)
+          listB.push([...new Set(summaryKeys)]);
         });
         const test = listB.map(item => item.length);
         console.log(test);
-
-        const matchRes = await ajaxPost('/priceInfoSummary/aiMatch', { listA, listB }, 1000 * 60 * 5);
+        const matchRes = matchResCache[listA[0]] ? matchResCache[listA[0]] : await ajaxPost('/priceInfoSummary/aiMatch', { listA, listB }, 1000 * 60 * 5);
+        matchResCache[listA[0]] = matchRes;
         // 填匹配值到表格,不实时保存,因为需要人工核查
         workBookObj.sheet.suspendEvent();
         workBookObj.sheet.suspendPaint();
@@ -426,15 +458,15 @@ const EMPTY_BOOK = (() => {
         workBookObj.sheet.resumeEvent();
         workBookObj.sheet.resumePaint();
         percent += step;
+        $('#progress_modal_body').text(`正在进行AI填值,请稍后${i + 1}/${chunks.length}...`);
         $("#progress_modal_bar").css('width', `${percent}%`);
-        await setTimeoutSync(500);
+        await setTimeoutSync(100);
       }
 
       // 没匹配到的行,自动生成别名编码
       workBookObj.sheet.suspendEvent();
       workBookObj.sheet.suspendPaint();
       let curMaxClassCode = getMaxClassCode(priceInfoSummary);
-      debugger;
       for (const row of noMatchRows) {
         const newClassCode = getNewMaxClassCode(curMaxClassCode);
         workBookObj.sheet.setValue(row, classCodeCol, newClassCode);
@@ -447,16 +479,148 @@ const EMPTY_BOOK = (() => {
       }
       workBookObj.sheet.resumeEvent();
       workBookObj.sheet.resumePaint();
-
-
+      aiMatchCache = null;
+      $("#ai-match").text('AI填值');
     } catch (error) {
       console.log(error);
+      aiMatchCache = {
+        curPercent: percent,
+        curIndex,
+        noMatchRows,
+        changedCells,
+      }
+      $("#ai-match").text('继续AI填值');
       alert(error);
     }
     await setTimeoutSync(500);
     $.bootstrapLoading.progressEnd();
   }
 
+
+  /*   const aiMatch = async () => {
+      try {
+        // 获取信息价总表
+        const priceInfoSummary = await ajaxPost('/priceInfoSummary/getData', {}, 1000 * 60 * 5);
+        const summaryGroupMap = _.groupBy(priceInfoSummary, item => getFourCode(item.code));
+        const noCodeSummary = priceInfoSummary.filter(item => !item.code);
+        const totalRows = workBookObj.sheet.getRowCount();
+        const changedCells = [];
+        const noMatchRows = []; // 没有匹配、ai没有命中的行,后续需要自动生成别名编码(最大的别名编码+1)
+        for (let i = 0; i < totalRows; i++) {
+          const rowData = getRowData(workBookObj.sheet, i, setting.header);
+          // const code = rowData.code || '';
+          const code = getFourCode(rowData.code);
+          const toMatchSummary = code ? summaryGroupMap[code] || [] : noCodeSummary;
+          if (toMatchSummary.length) {
+            changedCells.push({ row: i });
+          } else {
+            noMatchRows.push(i);
+          }
+        }
+        if (!changedCells.length) {
+          return;
+        }
+        const classCodeCol = setting.header.findIndex(h => h.dataCode === 'classCode');
+        const expStringCol = setting.header.findIndex(h => h.dataCode === 'expString');
+        // const chunks = _.chunk(changedCells, 20);
+        const chunks = _.chunk(changedCells, 1); // 只能一条一条匹配改成,否则经常ai服务经常挂
+        let percent = 0;
+        $.bootstrapLoading.progressStart('AI填值', false);
+        $("#progress_modal_body").text('正在进行AI填值,请稍后...');
+        await setTimeoutSync(500);
+        const matchResCache = {};
+  
+        // 分块进行ai匹配
+        const step = 100 / (chunks.length || 1);
+        for (let i = 0; i < chunks.length; i++) {
+          const chunk = chunks[i];
+          const listA = [];
+          const listB = [];
+          const summaryData = [];
+          chunk.forEach(item => {
+            const rowData = getRowData(workBookObj.sheet, item.row, setting.header);
+            listA.push(`${rowData.name || ''} ${rowData.specs}`);
+            const code = getFourCode(rowData.code);
+            const toMatchSummary = code ? summaryGroupMap[code] || [] : noCodeSummary;
+            summaryData.push(toMatchSummary);
+            const summaryKeys = toMatchSummary.map(summary => `${summary.name || ''} ${summary.specs || ''}`);
+            listB.push([...new Set(summaryKeys)]);
+          });
+          const test = listB.map(item => item.length);
+          console.log(test);
+          const matchRes = matchResCache[listA[0]] ? matchResCache[listA[0]] : await ajaxPost('/priceInfoSummary/aiMatch', { listA, listB }, 1000 * 60 * 5);
+          matchResCache[listA[0]] = matchRes;
+          // 填匹配值到表格,不实时保存,因为需要人工核查
+          workBookObj.sheet.suspendEvent();
+          workBookObj.sheet.suspendPaint();
+          matchRes.forEach((item, index) => {
+            const firstMatch = item[0];
+            const chunkItem = chunk[index];
+            const summaryIndex = item[0].index;
+            const summaryItem = summaryData[index][summaryIndex];
+            const curUnit = cache[chunkItem.row]?.unit || '';
+            const summaryItemUnit = summaryItem?.unit || '';
+            // 相似度过低的、单位不一致的,不命中
+            if (firstMatch.similarity < 70 || curUnit !== summaryItemUnit) {
+              noMatchRows.push(chunkItem.row);
+              return;
+            };
+            if (chunkItem && summaryItem) {
+              workBookObj.sheet.setValue(chunkItem.row, classCodeCol, summaryItem.classCode);
+              cache[chunkItem.row].classCode = summaryItem.classCode;
+              const items = getItemsFromTableItem(cache[chunkItem.row]);
+              items.forEach(item => {
+                item.classCode = summaryItem.classCode;
+              });
+              // 如果实际行存在珠海地区的,才填计算式
+              const tableItems = getItemsFromTableItem(cache[chunkItem.row]);
+              const needExpString = tableItems.some(tItem => {
+                const area = AREA_BOOK.cache.find(areaItem => areaItem.ID === tItem.areaID)
+                return area && area.name && /珠海/.test(area.name);
+              });
+              if (needExpString) {
+                workBookObj.sheet.setValue(chunkItem.row, expStringCol, summaryItem.expString);
+                cache[chunkItem.row].expString = summaryItem.expString;
+                items.forEach(item => {
+                  item.expString = summaryItem.expString;
+                });
+              }
+            }
+          });
+          workBookObj.sheet.resumeEvent();
+          workBookObj.sheet.resumePaint();
+          percent += step;
+          $('#progress_modal_body').text(`正在进行AI填值,请稍后${i + 1}/${chunks.length}...`);
+          $("#progress_modal_bar").css('width', `${percent}%`);
+          await setTimeoutSync(100);
+        }
+  
+        // 没匹配到的行,自动生成别名编码
+        workBookObj.sheet.suspendEvent();
+        workBookObj.sheet.suspendPaint();
+        let curMaxClassCode = getMaxClassCode(priceInfoSummary);
+        for (const row of noMatchRows) {
+          const newClassCode = getNewMaxClassCode(curMaxClassCode);
+          workBookObj.sheet.setValue(row, classCodeCol, newClassCode);
+          cache[row].classCode = newClassCode;
+          const items = getItemsFromTableItem(cache[row]);
+          items.forEach(item => {
+            item.classCode = newClassCode;
+          });
+          curMaxClassCode = newClassCode;
+        }
+        workBookObj.sheet.resumeEvent();
+        workBookObj.sheet.resumePaint();
+  
+  
+      } catch (error) {
+        console.log(error);
+        alert(error);
+      }
+      await setTimeoutSync(500);
+      $.bootstrapLoading.progressEnd();
+    } */
+
   // 保存ai填值
   const saveData = async () => {
     try {
@@ -515,6 +679,7 @@ $(document).ready(() => {
 
   $('#empty-area').on('hidden.bs.modal', function () {
     EMPTY_BOOK.clear();
+    $("#ai-match").text('AI填值');
   });
 
   // 保存至总表

+ 7 - 0
web/maintain/report/html/rpt_tpl_dtl_info.html

@@ -104,5 +104,12 @@
             <!--
             -->
         </div>
+        <p>
+        <div class="form-group col-md-6">
+            <label>同类表 <input id="hasAssociationChk" type="checkbox" disabled></label>
+            <div class="ztree-warp">
+                <ul id="tpl_data_info_association" class="ztree"></ul>
+            </div>
+        </div>
     </div>
 </div>

+ 1 - 0
web/maintain/report/html/rpt_tpl_main.html

@@ -110,6 +110,7 @@
     <script src="/lib/bootstrap/bootstrap.min.js"></script>
     <script src="/web/maintain/report/js/global.js"></script>
     <script src="/web/maintain/report/js/rpt_tpl_main.js"></script>
+    <script src="/web/maintain/report/js/rpt_tpl_association.js"></script>
     <script src="/web/maintain/report/js/rpt_tpl_cfg_helper.js"></script>
     <script src="/web/maintain/report/js/rpt_tpl_band.js"></script>
     <script src="/web/maintain/report/js/rpt_tpl_field_map.js"></script>

+ 40 - 0
web/maintain/report/js/cfg_const.js

@@ -601,6 +601,46 @@ let filterKeysSetting = {
     }
 };
 
+// 同类表
+const associationSetting = {
+    view: {
+        expandSpeed: "",
+        selectedMulti: false
+    },
+    check: {
+        enable: true
+    },
+    edit: {
+        enable: true,
+        showRemoveBtn: true,
+        showRenameBtn: false,
+        removeTitle: "移除同类表",
+        drag: {
+            isCopy: false,
+            isMove: false,
+        }
+    },
+    data: {
+        keep: {
+            parent: true,
+            leaf: true
+        },
+        key: {
+            children: "items",
+            name: "name",
+            title: "refId"
+        },
+        simpleData: {
+            enable: true,
+            rootPId: -1
+        }
+    },
+    callback: {
+        onCheck: associationOprObj.onCheck,
+    }
+    //
+};
+
 const engineering = {
     // 建筑工程
     ARCHITECTURE: 1,

+ 110 - 0
web/maintain/report/js/rpt_tpl_association.js

@@ -0,0 +1,110 @@
+const associationOprObj = {
+    treeObj: null,
+    iniAssociateNodes: function(allTopTplNodes) {
+      // 同类表初始化
+      const me = associationOprObj;
+      zTreeHelper.createTreeDirectly(
+        allTopTplNodes,
+        associationSetting,
+        "tpl_data_info_association",
+        associationOprObj
+      );
+      me.treeObj.expandAll(true);
+      const topNodes = me.treeObj.getNodes();
+      const private_setup_checked = function (itemNode) {
+        if (itemNode.nodeType === RT.NodeType.NODE) {
+          me.treeObj.setChkDisabled(itemNode, true);
+        } else if (itemNode.nodeType === RT.NodeType.TEMPLATE) {
+          me.treeObj.checkNode(itemNode, false); // 在初始化时,默认是不选;只有用户选择了具体的某一个表时,才会更新选择
+        }
+        if (itemNode.items && itemNode.items.length > 0) {
+          for (let subItem of itemNode.items) {
+            private_setup_checked(subItem);
+          }
+        }
+      };
+      for (let node of topNodes) {
+        me.treeObj.setChkDisabled(node, true);
+        if (node.items && node.items.length > 0) {
+          for (let item of node.items) {
+            me.treeObj.setChkDisabled(item, true);
+            private_setup_checked(item);
+          }
+        }
+      }
+    },
+    refreshNodes: function (refNode) {
+      const me = this;
+      // 不管三七二十一,先把所有节点的check给清除
+      const setUnCheck = (itemNode) => {
+        me.treeObj.checkNode(itemNode, false);
+        if (itemNode.items && itemNode.items.length > 0) {
+          for (let subItem of itemNode.items) {
+            setUnCheck(subItem);
+          }
+        }
+      };
+      const topNodes = me.treeObj.getNodes();
+      topNodes.forEach(node => {
+        setUnCheck(node);
+      });
+      const checkRefTpl = (itemNodes, associateRefIds) => {
+        for (const itemNode of itemNodes) {
+          if (itemNode.nodeType === RT.NodeType.TEMPLATE) {
+            if (associateRefIds.includes(itemNode.refId)) {
+              me.treeObj.checkNode(itemNode, true);
+            }
+          } else {
+            if (itemNode.items && itemNode.items.length > 0) {
+              checkRefTpl(itemNode.items, associateRefIds);
+            }
+          }
+        }
+      };
+      $("#hasAssociationChk").get(0).checked = false;
+      if (refNode && refNode.nodeType === RT.NodeType.TEMPLATE) {
+        if (refNode.associateRefIds && refNode.associateRefIds.length > 0) {
+          $("#hasAssociationChk").get(0).checked = true;
+          checkRefTpl(topNodes, refNode.associateRefIds);
+        }
+      }
+    },
+    getCheckedRefIds: function() {
+      const me = associationOprObj;
+      const rst = [];
+      const getCheckedId = (itemNodes) => {
+        for (const itemNode of itemNodes) {
+          if (itemNode.nodeType === RT.NodeType.TEMPLATE && itemNode.checked) {
+            rst.push(itemNode.refId);
+          }
+          if (itemNode.items && itemNode.items.length > 0) {
+            getCheckedId(itemNode.items);
+          }
+        }
+      };
+      if (me.treeObj) {
+        const topNodes = me.treeObj.getNodes();
+        getCheckedId(topNodes);
+      }
+      return rst;
+    },
+    onCheck: function(event, treeId, treeNode) {
+      const me = associationOprObj;
+      if (zTreeOprObj.currentNode) {
+        // 不考虑细节交互了,直接从头到尾扫一遍,重新整理associateRefIds信息,最后更新
+        zTreeOprObj.currentNode.associateRefIds = me.getCheckedRefIds();
+        if (zTreeOprObj.currentNode.associateRefIds.length > 0) {
+          $("#hasAssociationChk").get(0).checked = true;
+        } else {
+          $("#hasAssociationChk").get(0).checked = false;
+        }
+        const topPNode = zTreeOprObj.getParentNodeByNodeLevel(zTreeOprObj.currentNode, NODE_LEVEL_COMPILATION_NEW);
+        const newTopNode = zTreeOprObj.buildRootNodeDoc(topPNode);
+        zTreeOprObj.updateTreeRootNode(newTopNode, true, function (rst) {
+          if (!rst) {
+            alert("修改同类表请求失败!");
+          }
+        });
+      }
+    },
+  };

+ 7 - 0
web/maintain/report/js/rpt_tpl_main.js

@@ -214,6 +214,10 @@ let zTreeOprObj = {
                     if (item.hasOwnProperty('flags')) {
                         ir.flags = item.flags;
                     }
+                    // 同类表(associateRefIds)处理
+                    if (item.hasOwnProperty('associateRefIds')) {
+                        ir.associateRefIds = item.associateRefIds;
+                    }
                     ir.items = me.private_build_items(item.items);
                     itemRst.push(ir);
                 }
@@ -710,6 +714,8 @@ let zTreeOprObj = {
                         }
                     }
                 }
+                // 同类表初始化
+                associationOprObj.iniAssociateNodes(allTopTplNodes);
             });
         }, null, null);
     },
@@ -927,6 +933,7 @@ let zTreeOprObj = {
                 //显示报表模板
                 me.chkAndRreshRefTpl(true);
             }
+            associationOprObj.refreshNodes(me.currentNode);
         }
     },
     isFlowTpl: function () {