Browse Source

Merge branch 'master' of http://192.168.1.41:3000/maixinrong/Calculation

TonyKang 5 years ago
parent
commit
2aab13f7a1

+ 287 - 76
app/base/base_tree_service.js

@@ -95,6 +95,20 @@ class TreeService extends Service {
         }));
     }
 
+    async getDataByKidAndCount(mid, kid, count) {
+        if ((mid <= 0) || (kid <= 0)) return [];
+        const select = await this.getDataByKid(mid, kid);
+        if (!select) throw '数据错误';
+
+        if (count > 1) {
+            const selects = await this.getNextsData(mid, select[this.setting.pid], select[this.setting.order] - 1);
+            if (selects.length < count) throw '数据错误';
+            return selects.slice(0, count);
+        } else {
+            return [select];
+        }
+    }
+
     /**
      * 获取节点数据
      * @param id
@@ -167,6 +181,7 @@ class TreeService extends Service {
             value: order,
             operate: '>',
         });
+        this.sqlBuilder.orderBy = [['order', 'ASC']];
 
         const [sql, sqlParam] = this.sqlBuilder.build(this.tableName);
         const data = await this.db.query(sql, sqlParam);
@@ -350,6 +365,17 @@ class TreeService extends Service {
     }
 
     /**
+     * 删除相关数据 用于继承
+     * @param mid
+     * @param deleteData
+     * @returns {Promise<void>}
+     * @private
+     */
+    async _deleteRelaData(mid, deleteData) {
+
+    }
+
+    /**
      * 删除节点
      * @param {Number} tenderId - 标段id
      * @param {Object} deleteData - 删除节点数据
@@ -403,9 +429,8 @@ class TreeService extends Service {
             }
             // 选中节点--全部后节点 order--
             await this._updateChildrenOrder(mid, select[this.setting.pid], select[this.setting.order] + 1, -1);
-            //await this._updateSelectNextsOrder(select, -1);
             // 删除部位明细
-            //await this.ctx.service.pos.deletePosData(this.transaction, tenderId, this._.map(deleteData, 'id'));
+            await this._deleteRelaData(mid, deleteData);
             await this.transaction.commit();
             this.transaction = null;
         } catch (err) {
@@ -424,6 +449,57 @@ class TreeService extends Service {
         return { delete: deleteData, update: updateData };
     }
 
+    async deleteNodes(mid, kid, count) {
+        if ((mid <= 0) || (kid <= 0) || (count <= 0)) return [];
+        const selects = await this.getDataByKidAndCount(mid, kid, count);
+        const first = selects[0];
+        const parent = await this.getDataByKid(mid, first[this.setting.pid]);
+        const childCount = parent ? this.count(this.getCondition({mid: mid, pid: parent[this.setting.id]})) : -1;
+        let deleteData = [];
+        for (const s of selects) {
+            deleteData = deleteData.concat(await this.getDataByFullPath(mid, s[this.setting.fullPath] + '%'));
+        }
+
+        this.transaction = await this.db.beginTransaction();
+        try {
+            // 删除
+            for (const s of selects) {
+                const operate = await this._deleteNodeData(mid, s);
+            }
+            // 选中节点--父节点 只有一个子节点时,应升级is_leaf
+            if (parent && childCount === count) {
+                const updateParent = {id: parent.id };
+                updateParent[this.setting.isLeaf] = true;
+                await this.transaction.update(this.tableName, updateParent);
+            }
+            // 选中节点--全部后节点 order--
+            await this._updateChildrenOrder(mid, first[this.setting.pid], first[this.setting.order] + count, -count);
+            await this.transaction.commit();
+            this.transaction = null;
+
+            const updateData = await this.getNextsData(mid, first[this.setting.pid], first[this.setting.order] - 1);
+            if (parent && childCount === count) {
+                const updateData1 = await this.getDataByKid(mid, parent[this.setting.id]);
+                updateData.push(updateData1);
+            }
+            return { delete: deleteData, update: updateData };
+        } catch (err) {
+            if (this.transaction) {
+                await this.transaction.rollback();
+                this.transaction = null;
+            }
+            throw err;
+        }
+    }
+
+    async delete(mid, kid, count) {
+        if (count && count > 1) {
+            return await this.deleteNodes(mid, kid, count);
+        } else {
+            return await this.deleteNode(mid, kid);
+        }
+    }
+
     /**
      * 上移节点
      *
@@ -431,21 +507,24 @@ class TreeService extends Service {
      * @param {Number} kid - 选中节点id
      * @return {Array} - 发生改变的数据
      */
-    async upMoveNode(mid, kid) {
-        if (!mid || !kid) return null;
-        const select = await this.getDataByKid(mid, kid);
-        if (!select) {
-            throw '上移节点数据错误';
-        }
-        const pre = await this.getDataByParentAndOrder(mid, select[this.setting.pid], select[this.setting.order] - 1);
-        if (!pre) {
-            throw '节点不可上移';
-        }
-
+    async upMoveNode(mid, kid, count) {
+        if (!count) count = 1;
+        if (!mid || (mid <= 0) || !kid || (kid <= 0)) return null;
+        const selects = await this.getDataByKidAndCount(mid, kid, count);
+        if (selects.length !== count) throw '上移节点数据错误';
+        const first = selects[0];
+        const pre = await this.getDataByParentAndOrder(mid, first[this.setting.pid], first[this.setting.order] - 1);
+        if (!pre) throw '节点不可上移';
+
+        const order = [];
         this.transaction = await this.db.beginTransaction();
         try {
-            const sData = await this.transaction.update(this.tableName, { id: select.id, order: select[this.setting.order] - 1 });
-            const pData = await this.transaction.update(this.tableName, { id: pre.id, order: pre[this.setting.order] + 1 });
+            for (const s of selects) {
+                const sData = await this.transaction.update(this.tableName, { id: s.id, order: s[this.setting.order] - 1 });
+                order.push(s[this.setting.order] - 1);
+            }
+            const pData = await this.transaction.update(this.tableName, { id: pre.id, order: pre[this.setting.order] + count });
+            order.push(pre[this.setting.order] + count);
             await this.transaction.commit();
             this.transaction = null;
         } catch (err) {
@@ -454,7 +533,7 @@ class TreeService extends Service {
             throw err;
         }
 
-        const resultData = await this.getDataByParentAndOrder(mid, select[this.setting.pid], [select[this.setting.order], pre[this.setting.order]]);
+        const resultData = await this.getDataByParentAndOrder(mid, first[this.setting.pid], order);
         return { update: resultData };
     }
 
@@ -465,21 +544,28 @@ class TreeService extends Service {
      * @param {Number} kid - 选中节点id
      * @return {Array} - 发生改变的数据
      */
-    async downMoveNode(mid, kid) {
-        if (!mid || !kid) return null;
-        const select = await this.getDataByKid(mid, kid);
-        if (!select) {
+    async downMoveNode(mid, kid, count) {
+        if (!count) count = 1;
+        if (!mid || (mid <= 0) || !kid || (kid <= 0)) return null;
+        const selects = await this.getDataByKidAndCount(mid, kid, count);
+        if (selects.length !== count) {
             throw '下移节点数据错误';
         }
-        const next = await this.getDataByParentAndOrder(mid, select[this.setting.pid], select[this.setting.order] + 1);
+        const last = selects[count - 1];
+        const next = await this.getDataByParentAndOrder(mid, last[this.setting.pid], last[this.setting.order] + 1);
         if (!next) {
             throw '节点不可下移';
         }
 
+        const order = [];
         this.transaction = await this.db.beginTransaction();
         try {
-            const sData = await this.transaction.update(this.tableName, { id: select.id, order: select[this.setting.order] + 1 });
-            const pData = await this.transaction.update(this.tableName, { id: next.id, order: next[this.setting.order] - 1 });
+            for (const s of selects) {
+                const sData = await this.transaction.update(this.tableName, { id: s.id, order: s[this.setting.order] + 1 });
+                order.push(s[this.setting.order] + 1);
+            }
+            const nData = await this.transaction.update(this.tableName, { id: next.id, order: next[this.setting.order] - count });
+            order.push(next[this.setting.order] - count);
             await this.transaction.commit();
             this.transaction = null;
         } catch (err) {
@@ -488,7 +574,7 @@ class TreeService extends Service {
             throw err;
         }
 
-        const resultData = await this.getDataByParentAndOrder(mid, select[this.setting.pid], [select[this.setting.order], next[this.setting.order]]);
+        const resultData = await this.getDataByParentAndOrder(mid, last[this.setting.pid], order);
         return { update: resultData };
     }
 
@@ -591,48 +677,52 @@ class TreeService extends Service {
      * @param {Number} selectId - 选中节点id
      * @return {Array} - 发生改变的数据
      */
-    async upLevelNode(mid, kid) {
-        if ((mid <= 0) || (kid <= 0)) return [];
+    async upLevelNode(mid, kid, count) {
+        if (!count) count = 1;
+        const selects = await this.getDataByKidAndCount(mid, kid, count);
 
-        const select = await this.getDataByKid(mid, kid);
-        if (!select) throw '升级节点数据错误';
-        const parent = await this.getDataByKid(mid, select[this.setting.pid]);
+        if (selects.length !== count) throw '升级节点数据错误';
+        const first = selects[0], last = selects[count - 1];
+        const parent = await this.getDataByKid(mid, first[this.setting.pid]);
         if (!parent) throw '升级节点数据错误';
 
+        const newPath = [];
         this.transaction = await this.db.beginTransaction();
-        const newFullPath = select[this.setting.fullPath].replace(select[this.setting.pid] + '-', '');
         try {
             // 选中节点--父节点 选中节点为firstChild时,修改is_leaf
-            if (select[this.setting.order] === 1) {
+            if (first[this.setting.order] === 1) {
                 await this.transaction.update(this.tableName, {
                     id: parent.id,
                     is_leaf: true,
                 });
             }
             // 选中节点--父节点--全部后兄弟节点 order+1
-            await this._updateChildrenOrder(mid, parent[this.setting.pid], parent[this.setting.order] + 1);
-            //await this._updateSelectNextsOrder(parent);
-            // 选中节点 修改pid, order, full_path, level, is_leaf, 清空计算项
-            const updateData = { id: select.id };
-            updateData[this.setting.pid] = parent[this.setting.pid];
-            updateData[this.setting.order] = parent[this.setting.order] + 1;
-            updateData[this.setting.level] = select[this.setting.level] - 1;
-            updateData[this.setting.fullPath] = newFullPath;
-
-            const nexts = await this.getNextsData(mid, parent[this.setting.kid], select[this.setting.order]);
-            if (nexts.length > 0) {
-                updateData.is_leaf = true;
-                // updateData.unit_price = null;
-                // updateData.quantity = null;
-                // updateData.total_price = null;
-                // updateData.deal_qty = null;
-                // updateData.deal_tp = null;
+            await this._updateChildrenOrder(mid, parent[this.setting.pid], parent[this.setting.order] + 1, count);
+            for (const [i, s] of selects.entries()) {
+                // 选中节点 修改pid, order, full_path, level, is_leaf, 清空计算项
+                const updateData = { id: s.id };
+                updateData[this.setting.pid] = parent[this.setting.pid];
+                updateData[this.setting.order] = parent[this.setting.order] + i + 1;
+                updateData[this.setting.level] = s[this.setting.level] - 1;
+                updateData[this.setting.fullPath] = s[this.setting.fullPath].replace(s[this.setting.pid] + '-', '');
+                newPath.push(updateData[this.setting.fullPath]);
+                if (s.is_leaf && s.id === last.id) {
+                    const nexts = await this.getNextsData(mid, parent[this.setting.kid], last[this.setting.order]);
+                    if (nexts.length > 0) {
+                        updateData.is_leaf = false;
+                        // updateData.unit_price = null;
+                        // updateData.quantity = null;
+                        // updateData.total_price = null;
+                        // updateData.deal_qty = null;
+                        // updateData.deal_tp = null;
+                    }
+                }
+                await this.transaction.update(this.tableName, updateData);
+                // 选中节点--全部子节点(含孙) level-1, full_path变更
+                await this._syncUplevelChildren(s);
             }
-            await this.transaction.update(this.tableName, updateData);
-            // 选中节点--全部子节点(含孙) level-1, full_path变更
-            await this._syncUplevelChildren(select);
             // 选中节点--全部后兄弟节点 收编为子节点 修改pid, order, full_path
-            await this._syncUpLevelNexts(select);
+            await this._syncUpLevelNexts(last);
             await this.transaction.commit();
             this.transaction = null;
         } catch (err) {
@@ -642,9 +732,12 @@ class TreeService extends Service {
         }
 
         // 查询修改的数据
-        const resultData1 = await this.getDataByFullPath(mid, newFullPath + '%');
-        const resultData2 = await this.getNextsData(mid, parent[this.setting.pid], parent[this.setting.order] - 1);
-        return { update: resultData1.concat(resultData2) };
+        let updateData = await this.getNextsData(mid, parent[this.setting.pid], parent[this.setting.order] - 1);
+        for (const path of newPath) {
+            const children = await this.getDataByFullPath(mid, path + '-%');
+            updateData = updateData.concat(children);
+        }
+        return { update: updateData };
     }
 
     /**
@@ -685,30 +778,40 @@ class TreeService extends Service {
      * @param {Number} selectId - 选中节点id
      * @return {Array} - 发生改变的数据
      */
-    async downLevelNode(mid, kid) {
-        if ((mid <= 0) || (kid <= 0)) return [];
-        const select = await this.getDataByKid(mid, kid);
-        if (!select) throw '降级节点数据错误';
-        const pre = await this.getDataByParentAndOrder(mid, select[this.setting.pid], select[this.setting.order] - 1);
+    async downLevelNode(mid, kid, count) {
+        console.log(mid);
+        console.log(kid);
+        console.log(count);
+        if (!count) count = 1;
+        const selects = await this.getDataByKidAndCount(mid, kid, count);
+        if (!selects) throw '降级节点数据错误';
+        const first = selects[0];
+        const pre = await this.getDataByParentAndOrder(mid, first[this.setting.pid], first[this.setting.order] - 1);
         if (!pre) throw '节点不可降级';
         const preLastChild = await this.getLastChildData(mid, pre[this.setting.kid]);
+        console.log(selects);
+        console.log(pre);
 
+        const newPath = [];
         this.transaction = await this.db.beginTransaction();
-        const orgLastPath = select[this.setting.level] === 1 ? select[this.setting.kid] : '-' + select[this.setting.kid];
-        const newLastPath = select[this.setting.level] === 1 ? pre[this.setting.kid] + '-' + select[this.setting.kid] : '-' + pre[this.setting.kid] + '-' + select[this.setting.kid];
-        const newFullPath = select.full_path.replace(orgLastPath, newLastPath);
         try {
             // 选中节点--全部后节点 order--
-            await this._updateChildrenOrder(mid, select[this.setting.pid], select[this.setting.order] + 1, -1);
-            // 选中节点 修改pid, level, order, full_path
-            const updateData = { id: select.id };
-            updateData[this.setting.pid] = pre[this.setting.kid];
-            updateData[this.setting.order] = preLastChild ? preLastChild[this.setting.order] + 1 : 1;
-            updateData[this.setting.level] = select[this.setting.level] + 1;
-            updateData[this.setting.fullPath] = newFullPath;
-            await this.transaction.update(this.tableName, updateData);
-            // 选中节点--全部子节点(含孙) level++, full_path
-            await this._syncDownlevelChildren(select, pre);
+            await this._updateChildrenOrder(mid, first[this.setting.pid], first[this.setting.order] + 1, -count);
+
+            for (const [i, s] of selects.entries()) {
+                // 选中节点 修改pid, level, order, full_path
+                const updateData = { id: s.id };
+                updateData[this.setting.pid] = pre[this.setting.kid];
+                updateData[this.setting.order] = preLastChild ? preLastChild[this.setting.order] + i + 1 : i + 1;
+                updateData[this.setting.level] = s[this.setting.level] + 1;
+                const orgLastPath = s[this.setting.level] === 1 ? s[this.setting.kid] : '-' + s[this.setting.kid];
+                const newLastPath = s[this.setting.level] === 1 ? pre[this.setting.kid] + '-' + s[this.setting.kid] : '-' + pre[this.setting.kid] + '-' + s[this.setting.kid];
+                updateData[this.setting.fullPath] = s[this.setting.fullPath].replace(orgLastPath, newLastPath);
+                newPath.push(updateData[this.setting.fullPath]);
+                await this.transaction.update(this.tableName, updateData);
+                // 选中节点--全部子节点(含孙) level++, full_path
+                await this._syncDownlevelChildren(s, pre);
+            }
             // 选中节点--前兄弟节点 is_leaf应为false, 清空计算相关字段
             const updateData2 = { id: pre.id };
             updateData2[this.setting.isLeaf] = false;
@@ -727,11 +830,13 @@ class TreeService extends Service {
         }
 
         // 查询修改的数据
+        let updateData = await this.getNextsData(mid, pre[this.setting.pid], pre[this.setting.order] - 1);
         // 选中节点及子节点
-        const resultData1 = await this.getDataByFullPath(mid, newFullPath + '%');
+        for (const p of newPath) {
+            updateData = updateData.concat(await this.getDataByFullPath(mid, p + '%'));
+        }
         // 选中节点--原前兄弟节点&全部后兄弟节点
-        const resultData2 = await this.getNextsData(mid, pre[this.setting.pid], pre[this.setting.order] - 1);
-        return { update: resultData1.concat(resultData2) };
+        return { update: updateData };
     }
 
     /**
@@ -781,6 +886,112 @@ class TreeService extends Service {
             return await this.getDataById(datas.id);
         }
     }
+
+    async pasteBlockRelaData(relaData) {
+        if (!this.transaction) throw '更新数据错误';
+        // for (const id of relaData) {
+        //     await this.ctx.service.pos.copyBillsPosData(id.org, id.new, this.transaction);
+        // }
+    }
+
+    async getPasteBlockResult(select, copyNodes) {
+        const createData = await this.getDataByIds(newIds);
+        const updateData = await this.getNextsData(selectData[this.setting.mid], selectData[this.setting.pid], selectData[this.setting.order] + copyNodes.length);
+        //const posData = await this.ctx.service.pos.getPosData({ lid: newIds });
+        return {
+            ledger: { create: createData, update: updateData },
+            //pos: posData,
+        };
+    }
+
+    /**
+     * 复制粘贴整块
+     * @param {Number} tenderId - 标段Id
+     * @param {Number} selectId - 选中几点Id
+     * @param {Array} block - 复制节点Id
+     * @return {Object} - 提价后的数据(其中新增粘贴数据,只返回第一层)
+     */
+    async pasteBlock(mid, kid, block) {
+        if ((mid <= 0) || (kid <= 0)) return [];
+
+        const selectData = await this.getDataByKid(mid, kid);
+        if (!selectData) throw '数据错误';
+
+        const copyNodes = await this.getDataByNodeIds(mid, block);
+        if (!copyNodes || copyNodes.length <= 0)  throw '复制数据错误';
+        for (const node of copyNodes) {
+            if (node[this.setting.pid] !== copyNodes[0][this.setting.pid]) throw '复制数据错误:仅可操作同层节点';
+        }
+
+        const newParentPath = selectData[this.setting.fullPath].replace(selectData[this.setting.kid], '');
+        const orgParentPath = copyNodes[0][this.setting.fullPath].replace(copyNodes[0][this.setting.kid], '');
+        const newIds = [];
+        this.transaction = await this.db.beginTransaction();
+        try {
+            // 选中节点的所有后兄弟节点,order+粘贴节点个数
+            await this._updateSelectNextsOrder(selectData, copyNodes.length);
+            for (let iNode = 0; iNode < copyNodes.length; iNode++) {
+                const node = copyNodes[iNode];
+                let datas = await this.getDataByFullPath(mid, node[this.setting.fullPath] + '%');
+                datas = this._.sortBy(datas, 'level');
+
+                const maxId = await this._getMaxLid(mid);
+                this._cacheMaxLid(mid, maxId + datas.length);
+
+                const billsId = [];
+                // 计算粘贴数据中需更新部分
+                for (let index = 0; index < datas.length; index++) {
+                    const data = datas[index];
+                    const newId = maxId + index + 1;
+                    const idChange = {
+                        org: data.id,
+                    };
+                    if (this.setting.uuid) data.id = this.uuid.v4();
+                    idChange.new = data.id;
+                    data[this.setting.mid] = mid;
+                    if (!data[this.setting.isLeaf]) {
+                        for (const children of datas) {
+                            children[this.setting.fullPath] = children[this.setting.fullPath].replace('-' + data[this.setting.kid], '-' + newId);
+                            if (children[this.setting.pid] === data[this.setting.kid]) {
+                                children[this.setting.pid] = newId;
+                            }
+                        }
+                    } else {
+                        data[this.setting.fullPath] = data[this.setting.fullPath].replace('-' + data[this.setting.kid], '-' + newId);
+                    }
+                    data[this.setting.kid] = newId;
+                    data[this.setting.fullPath] = data[this.setting.fullPath].replace(orgParentPath, newParentPath);
+                    if (data[this.setting.pid] === node[this.setting.pid]) {
+                        data[this.setting.pid] = selectData[this.setting.pid];
+                        data[this.setting.order] = selectData[this.setting.order] + iNode + 1;
+                    }
+                    data[this.setting.level] = data[this.setting.level] + selectData[this.setting.level] - copyNodes[0][this.setting.level];
+                    idChange.isLeaf = data[this.setting.isLeaf];
+                    billsId.push(idChange);
+                    newIds.push(data.id);
+                }
+                const newData = await this.transaction.insert(this.tableName, datas);
+                await this.pasteBlockRelaData(billsId);
+            }
+            // 数据库创建新增节点数据
+            await this.transaction.commit();
+            this.transaction = null;
+        } catch (err) {
+            await this.transaction.rollback();
+            this.transaction = null;
+            throw err;
+        }
+
+        // 查询应返回的结果
+        const createData = await this.getDataByIds(newIds);
+        const updateData = await this.getNextsData(selectData[this.setting.mid], selectData[this.setting.pid], selectData[this.setting.order] + copyNodes.length);
+        //const posData = await this.ctx.service.pos.getPosData({ lid: newIds });
+        return {
+            ledger: { create: createData, update: updateData },
+            //pos: posData,
+        };
+    }
+
 }
 
 module.exports = TreeService;

+ 5 - 1
app/controller/change_controller.js

@@ -286,7 +286,7 @@ module.exports = app => {
                     // 获取标准清单列表
                     const dealBillList = await ctx.service.dealBills.getAllDataByCondition({ where: { tender_id: tenderid } });
                     // 获取清单列表和台账清单列表
-                    const ledger = await ctx.service.ledger.getDataByTenderId(tenderid, -1);
+                    const ledger = await ctx.service.ledger.getData(tenderid);
                     const pos = await ctx.service.pos.getPosData({ tid: tenderid });
                     renderData.ledger = ledger;
                     renderData.pos = pos;
@@ -436,6 +436,10 @@ module.exports = app => {
                     }
                 }
                 renderData.auditList = auditList;
+
+                // 获取是否已存在调用变更令
+                const stageChangeNum = await ctx.service.stageChange.count({ cid: change.cid });
+                renderData.stageChangeNum = stageChangeNum;
                 await this.layout('change/info.ejs', renderData, 'change/info_modal.ejs');
             } catch (err) {
                 this.log(err);

+ 4 - 4
app/controller/ledger_audit_controller.js

@@ -89,7 +89,7 @@ module.exports = app => {
                     }
                 }
 
-                const ledgerData = await ctx.service.ledger.getDataByTenderId(ctx.tender.id, -1);
+                const ledgerData = await ctx.service.ledger.getData(ctx.tender.id);
                 renderData.curAuditor = curAuditor;
                 renderData.auditConst = auditConst;
                 renderData.auditors = auditors;
@@ -201,11 +201,11 @@ module.exports = app => {
 
                 await ctx.service.ledgerAudit.start(ctx.tender.id, ctx.tender.data.ledger_times);
 
-                ctx.redirect('/tender/' + ctx.tender.id + '/ledger/explode');
+                ctx.redirect('/tender/' + ctx.tender.id + '/ledger');
             } catch (err) {
                 this.log(err);
                 ctx.session.postError = err.toString();
-                ctx.redirect('/tender/' + ctx.tender.id + '/ledger/explode');
+                ctx.redirect('/tender/' + ctx.tender.id + '/ledger');
             }
         }
 
@@ -232,7 +232,7 @@ module.exports = app => {
 
                 await ctx.service.ledgerAudit.check(tender.id, checkType, ctx.request.body.opinion, tender.data.ledger_times);
 
-                ctx.redirect('/tender/' + ctx.tender.id + '/ledger/explode');
+                ctx.redirect('/tender/' + ctx.tender.id + '/ledger');
             } catch (err) {
                 this.log(err);
                 ctx.session.postError = err.toString();

+ 119 - 260
app/controller/ledger_controller.js

@@ -115,7 +115,7 @@ module.exports = app => {
                 const content = auditors.length > 0 ? await ctx.service.ledgerAuditContent.getAllDataByCondition({
                     where: { tender_id: tender.id, times, audit_id: auditors[0].audit_id },
                 }) : null;
-                const ledgerData = await ctx.service.ledger.getDataByTenderId(tender.id, -1);
+                const ledgerData = await ctx.service.ledger.getData(tender.id);
                 const user = await ctx.service.projectAccount.getAccountInfoById(ctx.tender.data.user_id);
                 const auditHistory = [];
                 if (ctx.tender.data.ledger_times > 1) {
@@ -187,237 +187,100 @@ module.exports = app => {
 
             ctx.body = responseData;
         }
-
         /**
-         * 树结构基本操作(增、删、上下移、升降级)(Ajax)
+         * 树结构基本操作(增、删、上下移、升降级)
          * @param {Object} ctx - egg全局变量
          * @return {Promise<void>}
          */
-        async baseOperation(ctx) {
-            const responseData = {
-                err: 0,
-                msg: '',
-                data: [],
-            };
-            try {
-                if (!ctx.tender.data || ctx.tender.data.user_id !== ctx.session.sessionUser.accountId || this._ledgerReadOnly()) {
-                    throw '标段数据错误';
-                }
-                const data = JSON.parse(ctx.request.body.data);
-
-                switch (data.postType) {
-                    case 'add':
-                        responseData.data = await ctx.service.ledger.addNode(ctx.tender.id, data.id);
-                        break;
-                    case 'delete':
-                        responseData.data = await ctx.service.ledger.deleteNode(ctx.tender.id, data.id);
-                        break;
-                    case 'up-move':
-                        responseData.data = await ctx.service.ledger.upMoveNode(ctx.tender.id, data.id);
-                        break;
-                    case 'down-move':
-                        responseData.data = await ctx.service.ledger.downMoveNode(ctx.tender.id, data.id);
-                        break;
-                    case 'up-level':
-                        responseData.data = await ctx.service.ledger.upLevelNode(ctx.tender.id, data.id);
-                        break;
-                    case 'down-level':
-                        responseData.data = await ctx.service.ledger.downLevelNode(ctx.tender.id, data.id);
-                        break;
-                    default:
-                        throw '未知操作';
-                }
-            } catch (err) {
-                responseData.err = 1;
-                responseData.msg = err;
-                this.log(err);
+        async _base(ctx, type, data) {
+            if (isNaN(data.id) || data.id <= 0) throw '数据错误';
+            if (type !== 'add') {
+                if (isNaN(data.count) || data.count <= 0) data.count = 1;
             }
-
-            ctx.body = responseData;
-        }
-
-        /**
-         * 提交更新数据 (Ajax)
-         * @param ctx
-         * @return {Promise<void>}
-         */
-        async updateInfo(ctx) {
-            const responseData = {
-                err: 0,
-                msg: '',
-                data: [],
-            };
-            try {
-                if (!ctx.tender.data || ctx.tender.data.user_id !== ctx.session.sessionUser.accountId || this._ledgerReadOnly()) {
-                    throw '标段数据错误';
-                }
-                const data = JSON.parse(ctx.request.body.data);
-                if (data instanceof Array) {
-                    responseData.data = await ctx.service.ledger.updateInfos(ctx.tender.id, data);
-                } else {
-                    responseData.data = await ctx.service.ledger.updateInfo(ctx.tender.id, data);
-                }
-            } catch (err) {
-                responseData.err = 1;
-                responseData.msg = err;
+            switch (type) {
+                case 'add':
+                    return await ctx.service.ledger.addNode(ctx.tender.id, data.id);
+                case 'delete':
+                    return await ctx.service.ledger.delete(ctx.tender.id, data.id, data.count);
+                case 'up-move':
+                    return await ctx.service.ledger.upMoveNode(ctx.tender.id, data.id, data.count);
+                case 'down-move':
+                    return await ctx.service.ledger.downMoveNode(ctx.tender.id, data.id, data.count);
+                case 'up-level':
+                    return await ctx.service.ledger.upLevelNode(ctx.tender.id, data.id, data.count);
+                case 'down-level':
+                    return await ctx.service.ledger.downLevelNode(ctx.tender.id, data.id, data.count);
             }
-
-            ctx.body = responseData;
         }
-
-        async update(ctx) {
-            const responseData = {
-                err: 0,
-                msg: '',
-                data: [],
-            };
-            try {
-                const tenderId = parseInt(ctx.params.id);
-                if (!tenderId) {
-                    throw '当前未打开标段';
-                }
-                const tenderData = await ctx.service.tender.getDataById(tenderId);
-                if (!tenderData || tenderData.user_id !== ctx.session.sessionUser.accountId || this._ledgerReadOnly()) {
-                    throw '标段数据错误';
-                }
-                const data = JSON.parse(ctx.request.body.data);
-                responseData.data = await ctx.service.ledger.updateCalc(ctx.tender.id, data);
-            } catch (err) {
-                this.log(err);
-                responseData.err = 1;
-                responseData.msg = err;
-            }
-
-            ctx.body = responseData;
-        }
-
         /**
-         * 复制粘贴整块 (Ajax)
+         * 复制粘贴整块
          *
          * @param ctx
          * @return {Promise<void>}
          */
-        async pasteBlock(ctx) {
-            const responseData = {
-                err: 0,
-                msg: '',
-                data: [],
-            };
-            try {
-                if (!ctx.tender.data || ctx.tender.data.user_id !== ctx.session.sessionUser.accountId || this._ledgerReadOnly()) {
-                    throw '标段数据错误';
-                }
-
-                const data = JSON.parse(ctx.request.body.data);
-                if ((isNaN(data.id) || data.id <= 0) || (!data.tid && data.tid <= 0) || (!data.block || data.block.length <= 0)) {
-                    throw '参数错误';
-                }
-
-                responseData.data = await ctx.service.ledger.pasteBlock(data.tid, data.id, data.block);
-            } catch (err) {
-                responseData.err = 1;
-                responseData.msg = err;
-            }
-
-            ctx.body = responseData;
+        async _pasteBlock(ctx, data) {
+            if ((isNaN(data.id) || data.id <= 0) ||
+                (!data.tid && data.tid <= 0) ||
+                (!data.block || data.block.length <= 0)) throw '参数错误';
+            return await ctx.service.ledger.pasteBlock(data.tid, data.id, data.block);
         }
-
         /**
-         * 从标准项目表添加数据 (Ajax)
+         * 从标准项目表添加数据
          * @param ctx
          * @return {Promise<void>}
          */
-        async addFromStandardLib(ctx) {
-            const responseData = {
-                err: 0,
-                msg: '',
-                data: [],
-            };
-            try {
-                if (!ctx.tender.data || ctx.tender.data.user_id !== ctx.session.sessionUser.accountId || this._ledgerReadOnly()) {
-                    throw '标段数据错误';
-                }
-                const data = JSON.parse(ctx.request.body.data);
-                if ((isNaN(data.id) || data.id <= 0) || !data.stdType || !data.stdNode) {
-                    throw '参数错误';
-                }
-                // todo 校验项目是否使用该库的权限
-
-                let stdLib, addType;
-                switch (data.stdType) {
-                    case 'xmj':
-                        stdLib = ctx.service.stdXmj;
-                        addType = stdDataAddType.withParent;
-                        break;
-                    case 'gcl':
-                        stdLib = ctx.service.stdGcl;
-                        const selectNode = await ctx.service.ledger.getDataByNodeId(ctx.tender.id, data.id);
-                        if (selectNode.b_code) {
-                            addType = stdDataAddType.next;
-                        } else {
-                            addType = stdDataAddType.child;
-                        }
-                        break;
-                    default:
-                        throw '未知标准库';
-                }
-                const stdData = await stdLib.getDataByDataId(data.stdLibId, data.stdNode);
-                switch (addType) {
-                    case stdDataAddType.child:
-                        responseData.data = await ctx.service.ledger.addStdNodeAsChild(ctx.tender.id, data.id, stdData);
-                        break;
-                    case stdDataAddType.next:
-                        responseData.data = await ctx.service.ledger.addStdNode(ctx.tender.id, data.id, stdData);
-                        break;
-                    case stdDataAddType.withParent:
-                        responseData.data = await ctx.service.ledger.addStdNodeWithParent(ctx.tender.id, stdData, stdLib);
-                        break;
-                    default:
-                        throw '未知添加方式';
-                }
-            } catch (err) {
-                responseData.err = 1;
-                responseData.msg = err;
+        async _addStd(ctx, data) {
+            if ((isNaN(data.id) || data.id <= 0) || !data.stdType || !data.stdNode) throw '参数错误';
+            // todo 校验项目是否使用该库的权限
+
+            let stdLib, addType;
+            switch (data.stdType) {
+                case 'xmj':
+                    stdLib = ctx.service.stdXmj;
+                    addType = stdDataAddType.withParent;
+                    break;
+                case 'gcl':
+                    stdLib = ctx.service.stdGcl;
+                    const selectNode = await ctx.service.ledger.getDataByNodeId(ctx.tender.id, data.id);
+                    if (selectNode.b_code) {
+                        addType = stdDataAddType.next;
+                    } else {
+                        addType = stdDataAddType.child;
+                    }
+                    break;
+                default:
+                    throw '未知标准库';
+            }
+            const stdData = await stdLib.getDataByDataId(data.stdLibId, data.stdNode);
+            switch (addType) {
+                case stdDataAddType.child:
+                    return await ctx.service.ledger.addStdNodeAsChild(ctx.tender.id, data.id, stdData);
+                    break;
+                case stdDataAddType.next:
+                    return await ctx.service.ledger.addStdNode(ctx.tender.id, data.id, stdData);
+                case stdDataAddType.withParent:
+                    return await ctx.service.ledger.addStdNodeWithParent(ctx.tender.id, stdData, stdLib);
+                default:
+                    throw '未知添加方式';
             }
-
-            ctx.body = responseData;
         }
-
         /**
          * 从签约清单添加节点
          * @param ctx
          * @returns {Promise<void>}
          */
-        async addFromDealBills(ctx) {
-            try {
-                if (ctx.tender.ledgerReadOnly) {
-                    throw '标段数据错误';
-                }
-                const data = JSON.parse(ctx.request.body.data);
-                if ((isNaN(data.id) || data.id <= 0) || !data.type || !data.dealBills) {
-                    throw '参数错误';
-                }
-                let result;
-                if (data.type === 'child') {
-                    result = await ctx.service.ledger.addChild(ctx.tender.id, data.id, data.dealBills);
-                } else if (data.type === 'next') {
-                    result = await ctx.service.ledger.addNode(ctx.tender.id, data.id, data.dealBills);
-                } else {
-                    throw '参数错误';
-                }
-                ctx.body = {err: 0, msg: '', data: result};
-            } catch (err) {
-                this.log(err);
-                if (err.stack) {
-                    ctx.body = {err: 1, msg: '未知错误', data: null};
-                } else {
-                    ctx.body = {err: 1, msg: err, data: null};
-                }
+        async _addDeal(ctx, data) {
+            if (!data.type || !data.dealBills) throw '数据错误';
+            if (data.type === 'child') {
+                return await ctx.service.ledger.addChild(ctx.tender.id, data.id, data.dealBills);
+            } else if (data.type === 'next') {
+                return await ctx.service.ledger.addNode(ctx.tender.id, data.id, data.dealBills);
+            } else {
+                throw '数据错误';
             }
         }
-
         /**
-         * 批量插入数据 (Ajax)
+         * 批量插入数据
          *
          * data = {id, batchData, batchType}
          * data.batchType = 'batchInsertChild'/'batchInsertNext'
@@ -427,73 +290,69 @@ module.exports = app => {
          * @param ctx
          * @return {Promise<void>}
          */
-        async batchInsert(ctx) {
-            const responseData = {
-                err: 0,
-                msg: '',
-                data: [],
-            };
-            try {
-                if (!ctx.tender.data || ctx.tender.data.user_id !== ctx.session.sessionUser.accountId || this._ledgerReadOnly()) {
-                    throw '标段数据错误';
-                }
-                const data = JSON.parse(ctx.request.body.data);
-                if ((isNaN(data.id) || data.id <= 0) || !data.batchType) {
+        async _batchInsert(ctx, data) {
+            if ((isNaN(data.id) || data.id <= 0) || !data.batchType) throw '参数错误';
+
+            switch (data.batchType) {
+                case 'child':
+                    return await ctx.service.ledger.batchInsertChild(ctx.tender.id, data.id, data.batchData);
+                case 'next':
+                    return await ctx.service.ledger.batchInsertNext(ctx.tender.id, data.id, data.batchData);
+                default:
                     throw '参数错误';
-                }
-
-                switch (data.batchType) {
-                    case 'child':
-                        responseData.data = await ctx.service.ledger.batchInsertChild(ctx.tender.id, data.id, data.batchData);
-                        break;
-                    case 'next':
-                        responseData.data = await ctx.service.ledger.batchInsertNext(ctx.tender.id, data.id, data.batchData);
-                        break;
-                    default:
-                        throw '参数错误';
-                }
-            } catch (err) {
-                this.log(err);
-                responseData.err = 1;
-                responseData.msg = err;
             }
-
-            ctx.body = responseData;
         }
-
         /**
-         * 查询
-         *
+         * 更新清单相关 (Ajax)
          * @param ctx
-         * @return {Promise<void>}
+         * @returns {Promise<void>}
          */
-        async search(ctx) {
-            const responseData = {
-                err: 0,
-                msg: '',
-                data: [],
-            };
+        async update(ctx) {
             try {
-                const tenderId = ctx.params.id;
-                if (!tenderId) {
-                    throw '当前未打开标段';
-                }
+                if (!ctx.tender.data) throw '标段数据错误';
+                if (ctx.tender.data.user_id !== ctx.session.sessionUser.accountId || this._ledgerReadOnly()) throw '您无权进行该操作';
                 const data = JSON.parse(ctx.request.body.data);
-                if (!data.keyword || data.keyword === '') {
-                    throw '参数错误';
+                if (!data.postType || !data.postData) throw '数据错误';
+
+                const responseData = {
+                    err: 0,
+                    msg: '',
+                    data: [],
+                };
+                switch (data.postType) {
+                    case 'add':
+                    case 'delete':
+                    case 'up-move':
+                    case 'down-move':
+                    case 'up-level':
+                    case 'down-level':
+                        responseData.data = await this._base(ctx, data.postType, data.postData);
+                        break;
+                    case 'update':
+                        responseData.data = await ctx.service.ledger.updateCalc(ctx.tender.id, data.postData);
+                        break;
+                    case 'paste-block':
+                        responseData.data = await this._pasteBlock(ctx, data.postData);
+                        break;
+                    case 'add-std':
+                        responseData.data = await this._addStd(ctx, data.postData);
+                        break;
+                    case 'add-deal':
+                        responseData.data = await this._addDeal(ctx, data.postData);
+                        break;
+                    case 'batch-insert':
+                        responseData.data = await this._batchInsert(ctx, data.postData);
+                        break;
+                    default:
+                        throw '未知操作';
                 }
-                responseData.data = await ctx.service.ledger.search(tenderId, {
-                    value: ctx.app.mysql.escape('%' + data.keyword + '%'),
-                    operate: 'Like',
-                    fields: ['code', 'b_code', 'name'],
-                });
+
+                ctx.body = responseData;
             } catch (err) {
                 this.log(err);
-                responseData.err = 1;
-                responseData.msg = err;
+                ctx.body = this.ajaxErrorBody(err);
             }
 
-            ctx.body = responseData;
         }
 
         /**
@@ -624,7 +483,7 @@ module.exports = app => {
                 const data = JSON.parse(LzString.decompressFromUTF16(compressData));
                 const responseData = { err: 0, msg: '', data: {}, };
                 await ctx.service.ledger.importExcel(data);
-                responseData.data.bills = await ctx.service.ledger.getDataByTenderId(ctx.tender.id, -1);
+                responseData.data.bills = await ctx.service.ledger.getData(ctx.tender.id);
                 responseData.data.pos = await ctx.service.pos.getPosData({tid: this.ctx.tender.id});
                 ctx.body = responseData;
             } catch (err) {

+ 1 - 1
app/controller/measure_controller.js

@@ -208,7 +208,7 @@ module.exports = app => {
                 };
                 if (data.main) {
                     result.main = {};
-                    result.main.ledger = await ctx.service.ledger.getDataByTenderId(ctx.tender.id, -1);
+                    result.main.ledger = await ctx.service.ledger.getData(ctx.tender.id);
                     result.main.pos = await ctx.service.pos.getPosData({tid: ctx.tender.id});
                 }
                 if (data.stages) {

+ 17 - 3
app/controller/profile_controller.js

@@ -129,6 +129,12 @@ module.exports = app => {
                 const rule = { mobile: { type: 'mobile', allowEmpty: false } };
                 ctx.helper.validate(rule);
 
+                // 查找是否有重复的认证手机
+                const accountData = await ctx.service.projectAccount.getDataByCondition({ project_id: ctx.session.sessionProject.id, auth_mobile: mobile });
+                if (accountData !== null) {
+                    throw '此手机号码已被使用,请重新输入!';
+                }
+
                 const result = await ctx.service.projectAccount.setSMSCode(sessionUser.accountId, mobile);
                 if (!result) {
                     throw '获取验证码失败';
@@ -148,22 +154,30 @@ module.exports = app => {
          * @return {void}
          */
         async bindMobile(ctx) {
+            const response = {
+                err: 0,
+                msg: '',
+            };
             try {
                 const rule = ctx.service.projectAccount.rule('bindMobile');
                 ctx.helper.validate(rule);
 
                 const sessionUser = ctx.session.sessionUser;
+
                 const result = await ctx.service.projectAccount.bindMobile(sessionUser.accountId, ctx.request.body, ctx.session.sessionProject.id);
 
                 if (!result) {
                     throw '绑定手机失败!';
                 }
-                this.setMessage('绑定成功', this.messageType.SUCCESS);
+                // this.setMessage('绑定成功', this.messageType.SUCCESS);
+                response.msg = '绑定成功';
+                response.url = ctx.request.header.referer;
             } catch (error) {
                 console.log(error);
-                this.setMessage(error.toString(), this.messageType.ERROR);
+                response.err = 1;
+                response.msg = error.toString();
             }
-            ctx.redirect(ctx.request.header.referer);
+            ctx.body = response;
         }
 
         /**

+ 1 - 1
app/controller/report_controller.js

@@ -128,7 +128,7 @@ async function getReportData(ctx, params, filters) {
                 runnableKey.push('tender_info');
                 break;
             case 'ledger' :
-                runnableRst.push(ctx.service.ledger.getDataByTenderId(params.tender_id, 0));
+                runnableRst.push(ctx.service.ledger.getData(params.tender_id, 0));
                 runnableKey.push('ledger');
                 break;
             case 'stage_bills':

+ 1 - 1
app/controller/revise_controller.js

@@ -196,7 +196,7 @@ module.exports = app => {
         }
 
         async _getDefaultReviseInfoData(ctx, revise) {
-            const reviseBills = await ctx.service.reviseBills.getData(ctx.tender.id, revise.id);
+            const reviseBills = await ctx.service.reviseBills.getData(ctx.tender.id);
             const revisePos = await ctx.service.revisePos.getData(ctx.tender.id, revise.id);
             const [ledgerSpread, posSpread] = this._getSpreadSetting(revise);
             return {

+ 6 - 11
app/controller/stage_controller.js

@@ -141,7 +141,7 @@ module.exports = app => {
                 const renderData = await this._getDefaultRenderData(ctx);
                 [renderData.ledgerSpread, renderData.posSpread] = this._getSpreadSetting();
                 renderData.changeConst = changeConst;
-                renderData.ledgerData = await ctx.service.ledger.getDataByTenderId(ctx.tender.id, -1);
+                renderData.ledgerData = await ctx.service.ledger.getData(ctx.tender.id);
                 const dgnData = await ctx.service.stageBillsDgn.getDgnData(ctx.tender.id);
                 for (const d of dgnData) {
                     const l = ctx.app._.find(renderData.ledgerData, {id: d.id});
@@ -380,11 +380,11 @@ module.exports = app => {
                 const data = JSON.parse(ctx.request.body.data);
                 // 加载台账数据
                 if (data.loadType === 'ledger') {
-                    const ledgerData = await ctx.service.ledger.getDataByTenderId(ctx.tender.id, -1);
+                    const ledgerData = await ctx.service.ledger.getData(ctx.tender.id);
                     ctx.body = {err: 0, msg: '', data: ledgerData };
                 } else if (data.loadType === 'all') {
                     const result = {};
-                    result.ledger = await ctx.service.ledger.getDataByTenderId(ctx.tender.id, -1);
+                    result.ledger = await ctx.service.ledger.getData(ctx.tender.id);
                     result.pos = await ctx.service.pos.getPosData({tid: ctx.tender.id});
                     if (ctx.stage.readOnly) {
                         const curStage = await ctx.service.stageBills.getAuditorStageData(ctx.tender.id, ctx.stage.id, ctx.stage.curTimes, ctx.stage.curOrder);
@@ -754,7 +754,7 @@ module.exports = app => {
             try {
                 const data = {};
                 data.tenderInfo = ctx.tender.info;
-                data.ledger = await ctx.service.ledger.getDataByTenderId(ctx.tender.id, -1);
+                data.ledger = await ctx.service.ledger.getData(ctx.tender.id);
                 data.usedChangesId = await ctx.service.stageChange.getStageUsedChangeId(ctx.stage.id);
                 data.changes = await ctx.service.change.getChangeAndUsedInfo(ctx.tender.id);
                 if (data.changes.length > 0) {
@@ -981,7 +981,7 @@ module.exports = app => {
                 await this._getStageAuditViewData(ctx);
                 const renderData = await this._getDefaultRenderData(ctx);
                 [renderData.gclSpread, renderData.leafXmjSpread] = this._getGatherSpreadSetting();
-                renderData.ledger = await ctx.service.ledger.getDataByTenderId(ctx.tender.id, -1);
+                renderData.ledger = await ctx.service.ledger.getData(ctx.tender.id);
                 renderData.curLedgerData = await ctx.service.stageBills.getAuditorStageData(ctx.tender.id, ctx.stage.id, ctx.stage.times, 0);
 
                 renderData.pos = await ctx.service.pos.getPosData({tid: ctx.tender.id});
@@ -1028,11 +1028,6 @@ module.exports = app => {
                 await this._getStageAuditViewData(ctx);
                 const renderData = await this._getDefaultRenderData(ctx);
                 [renderData.ledgerSpread, renderData.posSpread] = getCompareSpreadSetting();
-                // renderData.ledger = await ctx.service.ledger.getDataByTenderId(ctx.tender.id, -1);
-                // renderData.orgStageLedger = await ctx.service.stageBills.getAuditorStageData(ctx.tender.id, ctx.stage.id, ctx.stage.times, 0);
-                // renderData.pos = await ctx.service.pos.getPosData({tid: ctx.tender.id});
-                // renderData.orgStagePos = await ctx.service.stagePos.getAuditorStageData(ctx.tender.id,
-                //     ctx.stage.id, ctx.stage.times, ctx.stage.curAuditor ? ctx.stage.curAuditor.order : 0);
                 renderData.jsFiles = this.app.jsFiles.common.concat(this.app.jsFiles.stage.compare);
                 await this.layout('stage/compare.ejs', renderData, 'stage/compare_modal.ejs');
             } catch (err) {
@@ -1050,7 +1045,7 @@ module.exports = app => {
                 };
                 if (data.main) {
                     result.main = {};
-                    result.main.ledger = await ctx.service.ledger.getDataByTenderId(ctx.tender.id, -1);
+                    result.main.ledger = await ctx.service.ledger.getData(ctx.tender.id);
                     result.main.pos = await ctx.service.pos.getPosData({tid: ctx.tender.id});
                 }
                 for (const order of data.roles) {

+ 1 - 1
app/middleware/session_auth.js

@@ -24,7 +24,7 @@ module.exports = options => {
             }
             // 校验session
             const sessionToken = crypto.createHmac('sha1', sessionUser.loginTime + '')
-                .update(sessionUser.account).digest().toString('base64');
+                .update(sessionUser.account).digest('hex').toString('base64');
             if (sessionToken !== sessionUser.sessionToken) {
                 throw 'session数据错误';
             }

File diff suppressed because it is too large
+ 3 - 3
app/public/css/bootstrap/bootstrap.min.css


+ 195 - 143
app/public/js/ledger.js

@@ -81,6 +81,26 @@ $(document).ready(function() {
                 return sheet.zh_tree.nodes[sel.row];
             }
         },
+        getDefaultSelectInfo: function (sheet) {
+            const tree = sheet.zh_tree;
+            if (!tree) return;
+            const sel = sheet.getSelections()[0];
+            const node = sheet.zh_tree.nodes[sel.row];
+            if (!node) return;
+            let count = 1;
+            if (sel.rowCount > 1) {
+                for (let r = 1; r < sel.rowCount; r++) {
+                    const rNode = sheet.zh_tree.nodes[sel.row + r];
+                    if (rNode.level > node.level) continue;
+                    if ((rNode.level < node.level) || (rNode.level === node.level && rNode.pid !== node.pid)) {
+                        toast('请选择同一清单下的节点,进行该操作');
+                        return;
+                    }
+                    count += 1;
+                }
+            }
+            return [tree, node, count];
+        },
         /**
          * 刷新顶部按钮是否可用
          * @param sheet
@@ -98,23 +118,35 @@ $(document).ready(function() {
             const sel = selection ? selection[0] : sheet.getSelections()[0];
             const row = sel ? sel.row : -1;
             const tree = sheet.zh_tree;
-            if (!tree) { return; }
-            const node = sheet.zh_tree.nodes[row];
-            const preNode = tree.getPreSiblingNode(node);
+            if (!tree) return;
+            const first = sheet.zh_tree.nodes[row];
+            let last = first, sameParent = true;
+            if (sel.rowCount > 1) {
+                for (let r = 1; r < sel.rowCount; r++) {
+                    const rNode = tree.nodes[sel.row + r];
+                    if (rNode.level > first.level) continue;
+                    if ((rNode.level < first.level) || (rNode.level === first.level && rNode.pid !== first.pid)) {
+                        sameParent = false;
+                        break;
+                    }
+                    last = rNode;
+                }
+            }
+            const preNode = tree.getPreSiblingNode(first);
             const valid = !sheet.zh_setting.readOnly;
 
-            setObjEnable($('#insert'), valid && node && node.level > 1);
-            setObjEnable($('#delete'), valid && node && node.level > 1);
-            setObjEnable($('#up-move'), valid && node && node.level > 1 && preNode);
-            setObjEnable($('#down-move'), valid && node && node.level > 1 && !tree.isLastSibling(node));
+            setObjEnable($('#insert'), valid && first && first.level > 1);
+            setObjEnable($('#delete'), valid && first && sameParent && first.level > 1);
+            setObjEnable($('#up-move'), valid && first && sameParent && first.level > 1 && preNode);
+            setObjEnable($('#down-move'), valid && first && sameParent && first.level > 1 && !tree.isLastSibling(last));
             if (checkTzMeasureType()) {
-                const posRange = node ? pos.getLedgerPos(node.id) : [];
-                setObjEnable($('#up-level'), valid && node && tree.getParent(node) && node.level > 2 && (!posRange || posRange.length === 0));
+                const posRange = last ? pos.getLedgerPos(last.id) : [];
+                setObjEnable($('#up-level'), valid && first && sameParent && tree.getParent(first) && first.level > 2 && (!posRange || posRange.length === 0));
                 const preNodePosRange = preNode ? pos.getLedgerPos(preNode.id) : [];
-                setObjEnable($('#down-level'), valid && node && node.level > 1 && preNode && (!preNodePosRange || preNodePosRange.length === 0));
+                setObjEnable($('#down-level'), valid && first && sameParent && first.level > 1 && preNode && (!preNodePosRange || preNodePosRange.length === 0));
             } else {
-                setObjEnable($('#up-level'), valid && node && node.level > 2 && tree.getParent(node));
-                setObjEnable($('#down-level'), valid && node && node.level > 1 && preNode);
+                setObjEnable($('#up-level'), valid && first && sameParent && first.level > 2 && tree.getParent(first));
+                setObjEnable($('#down-level'), valid && first && sameParent && first.level > 1 && preNode);
             }
             setObjEnable($('#cut'), valid);
             setObjEnable($('#paste'), valid);
@@ -124,8 +156,11 @@ $(document).ready(function() {
                 const tree = sheet.zh_tree;
                 // 处理删除
                 if (data.delete) {
+                    data.delete.sort(function (x, y) {
+                        return y.deleteIndex - x.deleteIndex;
+                    });
                     for (const d of data.delete) {
-                        sheet.deleteRows(tree.nodes.indexOf(d), 1);
+                        sheet.deleteRows(d.deleteIndex, 1);
                     }
                 }
                 // 处理新增
@@ -169,9 +204,8 @@ $(document).ready(function() {
          * 新增节点
          * @param spread
          */
-        addNode: function (spread) {
+        addNode: function (sheet) {
             const self = this;
-            const sheet = spread.getActiveSheet();
             const row = sheet.getSelections()[0].row;
 
             const tree = sheet.zh_tree;
@@ -180,128 +214,132 @@ $(document).ready(function() {
             const node = sheet.zh_tree.nodes[row];
             if (!node) { return; }
 
-            SpreadJsObj.massOperationSheet(sheet, function () {
-                tree.baseOperation('/tender/' + getTenderId() + '/ledger/base-operation', node, 'add', function (result) {
-                    self.refreshTree(sheet, result);
-                    self.refreshOperationValid(sheet);
-                });
+            postData(window.location.pathname + '/update', {
+                postType: 'add',
+                postData: {
+                    id: tree.getNodeKey(node),
+                }
+            }, function (result) {
+                const refreshNode = tree.loadPostData(result);
+                self.refreshTree(sheet, refreshNode);
+                self.refreshOperationValid(sheet);
             });
         },
         /**
          * 删除选中节点
          * @param spread
          */
-        deleteNode: function (spread) {
+        deleteNode: function (sheet) {
             const self = this;
-            const sheet = spread.getActiveSheet();
-            const row = sheet.getSelections()[0].row;
-
-            const tree = sheet.zh_tree;
-            if (!tree) { return; }
-
-            const node = sheet.zh_tree.nodes[row];
-            if (!node) { return; }
-
-            const count = ledgerTree.getPosterity(node).length;
-            tree.baseOperation('/tender/' + getTenderId() + '/ledger/base-operation', node, 'delete', function (result) {
-                //treeOperationObj.refreshTree(sheet, result);
-                sheet.deleteRows(row, count + 1);
-                for (const data of result.update) {
-                    SpreadJsObj.reLoadRowData(sheet, tree.nodes.indexOf(data), tree.getPosterity(data).length + 1);
-                }
-                self.refreshOperationValid(sheet);
-                for (const data of result.delete) {
-                    pos.removeDatasByMasterId(data.id);
-                }
-                posOperationObj.loadCurPosData();
-            });
+            const [tree, node, count] = this.getDefaultSelectInfo(sheet);
+            if (tree && node && count) {
+                const data = {
+                    postType: 'delete',
+                    postData: {
+                        id: tree.getNodeKey(node),
+                        count: count
+                    }
+                };
+                postData(window.location.pathname + '/update', data, function (result) {
+                    const refreshNode = tree.loadPostData(result);
+                    for (const d of refreshNode.delete) pos.removeDatasByMasterId(d.id);
+                    self.refreshTree(sheet, refreshNode);
+                    self.refreshOperationValid(sheet);
+                    posOperationObj.loadCurPosData();
+                });
+            }
         },
         /**
          * 上移选中节点
          * @param spread
          */
-        upMove: function (spread) {
+        upMove: function (sheet) {
             const self = this;
-            const sheet = spread.getActiveSheet();
-            const sel = sheet.getSelections()[0];
-            const row = sel.row;
-
-            const tree = sheet.zh_tree;
-            if (!tree) { return; }
-
-            const node = tree.nodes[row];
-            if (!node) { return; }
-
-            tree.baseOperation('/tender/' + getTenderId() + '/ledger/base-operation', node, 'up-move', function (result) {
-                self.refreshTree(sheet, result);
-                sheet.setSelection(tree.nodes.indexOf(node), sel.col, sel.rowCount, sel.colCount);
-                self.refreshOperationValid(sheet);
-            });
-
+            const [tree, node, count] = this.getDefaultSelectInfo(sheet);
+            if (tree && node && count) {
+                const data = {
+                    postType: 'up-move',
+                    postData: {
+                        id: tree.getNodeKey(node),
+                        count: count
+                    }
+                };
+                postData(window.location.pathname + '/update', data, function (result) {
+                    const refreshNode = tree.loadPostData(result);
+                    self.refreshTree(sheet, refreshNode);
+                    const sel = sheet.getSelections()[0];
+                    sheet.setSelection(tree.nodes.indexOf(node), sel.col, sel.rowCount, sel.colCount);
+                    self.refreshOperationValid(sheet);
+                });
+            }
         },
         /**
          * 下移选中节点
          * @param spread
          */
-        downMove: function (spread) {
+        downMove: function (sheet) {
             const self = this;
-            const sheet = spread.getActiveSheet();
-            const sel = sheet.getSelections()[0];
-            const row = sel.row;
-
-            const tree = sheet.zh_tree;
-            if (!tree) { return; }
-
-            const node = tree.nodes[row];
-            if (!node) { return; }
-
-            tree.baseOperation('/tender/' + getTenderId() + '/ledger/base-operation', node, 'down-move', function (result) {
-                self.refreshTree(sheet, result);
-                sheet.setSelection(tree.nodes.indexOf(node), sel.col, sel.rowCount, sel.colCount);
-                self.refreshOperationValid(sheet);
-            });
-
+            const [tree, node, count] = this.getDefaultSelectInfo(sheet);
+            if (tree && node && count) {
+                const data = {
+                    postType: 'down-move',
+                    postData: {
+                        id: tree.getNodeKey(node),
+                        count: count
+                    }
+                };
+                postData(window.location.pathname + '/update', data, function (result) {
+                    const refreshNode = tree.loadPostData(result);
+                    self.refreshTree(sheet, refreshNode);
+                    const sel = sheet.getSelections()[0];
+                    sheet.setSelection(tree.nodes.indexOf(node), sel.col, sel.rowCount, sel.colCount);
+                    self.refreshOperationValid(sheet);
+                });
+            }
         },
         /**
          * 升级选中节点
          * @param spread
          */
-        upLevel: function (spread) {
+        upLevel: function (sheet) {
             const self = this;
-            const sheet = spread.getActiveSheet();
-            const row = sheet.getSelections()[0].row;
-
-            const tree = sheet.zh_tree;
-            if (!tree) { return; }
-
-            const node = tree.nodes[row];
-            if (!node) { return; }
-
-            tree.baseOperation('/tender/' + getTenderId() + '/ledger/base-operation', node, 'up-level', function (result) {
-                self.refreshTree(sheet, result);
-                self.refreshOperationValid(sheet);
-            });
-
+            const [tree, node, count] = this.getDefaultSelectInfo(sheet);
+            if (tree && node && count) {
+                const data = {
+                    postType: 'up-level',
+                    postData: {
+                        id: tree.getNodeKey(node),
+                        count: count
+                    }
+                };
+                postData(window.location.pathname + '/update', data, function (result) {
+                    const refreshNode = tree.loadPostData(result);
+                    self.refreshTree(sheet, refreshNode);
+                    self.refreshOperationValid(sheet);
+                });
+            }
         },
         /**
          * 降级选中节点
          * @param spread
          */
-        downLevel: function (spread) {
+        downLevel: function (sheet) {
             const self = this;
-            const sheet = spread.getActiveSheet();
-            const row = sheet.getSelections()[0].row;
-
-            const tree = sheet.zh_tree;
-            if (!tree) { return; }
-
-            const node = tree.nodes[row];
-            if (!node) { return; }
-
-            tree.baseOperation('/tender/' + getTenderId() + '/ledger/base-operation', node, 'down-level', function (result) {
-                self.refreshTree(sheet, result);
-                self.refreshOperationValid(sheet);
-            });
+            const [tree, node, count] = this.getDefaultSelectInfo(sheet);
+            if (tree && node && count) {
+                const data = {
+                    postType: 'down-level',
+                    postData: {
+                        id: tree.getNodeKey(node),
+                        count: count
+                    }
+                };
+                postData(window.location.pathname + '/update', data, function (result) {
+                    const refreshNode = tree.loadPostData(result);
+                    self.refreshTree(sheet, refreshNode);
+                    self.refreshOperationValid(sheet);
+                });
+            }
         },
         /**
          * 编辑单元格响应事件
@@ -353,8 +391,9 @@ $(document).ready(function() {
                     data[col.field] = null;
                 }
                 // 更新至服务器
-                info.sheet.zh_tree.update('/tender/' + getTenderId() + '/ledger/update', data, function (result) {
-                    treeOperationObj.refreshTree(info.sheet, result);
+                postData(window.location.pathname + '/update', {postType: 'update', postData: data}, function (result) {
+                    const refreshNode = ledgerTree.loadPostData(result);
+                    treeOperationObj.refreshTree(info.sheet, refreshNode);
                 });
             }
         },
@@ -412,11 +451,12 @@ $(document).ready(function() {
                     }
                 }
                 if (datas.length > 0) {
-                    info.sheet.zh_tree.update('/tender/' + getTenderId() + '/ledger/update', datas, function (result) {
-                        if (result.update) {
-                            result.update = result.update.concat(filterNodes);
+                    postData(window.location.pathname + '/update', {postType: 'update', postData: data}, function (result) {
+                        const refreshNode = tree.loadPostData(result);
+                        if (refreshNode.update) {
+                            refreshNode.update = refreshNode.update.concat(filterNodes);
                         }
-                        treeOperationObj.refreshTree(info.sheet, result);
+                        treeOperationObj.refreshTree(info.sheet, refreshNode);
                     }, function () {
                         SpreadJsObj.reLoadRowData(info.sheet, info.cellRange.row, info.cellRange.rowCount);
                     });
@@ -475,8 +515,9 @@ $(document).ready(function() {
                     }
                 }
                 if (datas.length > 0) {
-                    sheet.zh_tree.update('/tender/' + getTenderId() + '/ledger/update', datas, function (result) {
-                        treeOperationObj.refreshTree(sheet, result);
+                    postData(window.location.pathname + '/update', {postType: 'update', postData: datas}, function (result) {
+                        const refreshNode = tree.loadPostData(result);
+                        treeOperationObj.refreshTree(sheet, refreshNode);
                     });
                 }
             }
@@ -497,10 +538,13 @@ $(document).ready(function() {
             const node = tree.nodes[row];
             if (!node) { return; }
 
-            postData('/tender/' + getTenderId() + '/ledger/paste-block', {
-                id: node[tree.setting.id],
-                tid: copyInfo.tid,
-                block: copyInfo.block,
+            postData(window.location.pathname + '/update', {
+                postType: 'paste-block',
+                postData: {
+                    id: tree.getNodeKey(node),
+                    tid: copyInfo.tid,
+                    block: copyInfo.block,
+                }
             }, function (data) {
                 pos.updateDatas(data.pos);
                 const result = tree.loadPostData(data.ledger);
@@ -552,9 +596,10 @@ $(document).ready(function() {
             }
 
             if (datas.length > 0) {
-                sheet.zh_tree.update('/tender/' + getTenderId() + '/ledger/update', datas, function (result) {
+                postData(window.location.pathname + '/update', {postType: 'update', postData: datas}, function (result) {
+                    const refreshNode = tree.loadPostData(result);
                     callback();
-                    treeOperationObj.refreshTree(sheet, result);
+                    treeOperationObj.refreshTree(sheet, refreshNode);
                 });
             }
         },
@@ -581,22 +626,22 @@ $(document).ready(function() {
 
         // 绑定 删除等 顶部按钮
         $('#insert').click(() => {
-            treeOperationObj.addNode(ledgerSpread);
+            treeOperationObj.addNode(ledgerSpread.getActiveSheet());
         });
         $('#delete').click(function () {
-            treeOperationObj.deleteNode(ledgerSpread);
+            treeOperationObj.deleteNode(ledgerSpread.getActiveSheet());
         });
         $('#up-move').click(function () {
-            treeOperationObj.upMove(ledgerSpread);
+            treeOperationObj.upMove(ledgerSpread.getActiveSheet());
         });
         $('#down-move').click(function () {
-            treeOperationObj.downMove(ledgerSpread);
+            treeOperationObj.downMove(ledgerSpread.getActiveSheet());
         });
         $('#up-level').click(function () {
-            treeOperationObj.upLevel(ledgerSpread);
+            treeOperationObj.upLevel(ledgerSpread.getActiveSheet());
         });
         $('#down-level').click(function () {
-            treeOperationObj.downLevel(ledgerSpread);
+            treeOperationObj.downLevel(ledgerSpread.getActiveSheet());
         });
 
         let batchInsertObj;
@@ -612,7 +657,7 @@ $(document).ready(function() {
                     name: '新增',
                     icon: 'fa-sign-in',
                     callback: function (key, opt) {
-                        treeOperationObj.addNode(ledgerSpread);
+                        treeOperationObj.addNode(ledgerSpread.getActiveSheet());
                     },
                     visible: function(key, opt){
                         const sheet = ledgerSpread.getActiveSheet();
@@ -626,7 +671,7 @@ $(document).ready(function() {
                     name: '删除',
                     icon: 'fa-remove',
                     callback: function (key, opt) {
-                        treeOperationObj.deleteNode(ledgerSpread);
+                        treeOperationObj.deleteNode(ledgerSpread.getActiveSheet());
                     },
                     visible: function (key, opt) {
                         const sheet = ledgerSpread.getActiveSheet();
@@ -1208,13 +1253,18 @@ $(document).ready(function() {
                     }
                 }
 
-                mainTree.postData('/tender/' + getTenderId() + '/ledger/add-by-std', mainNode, {
-                    tender_id: mainNode.tender_id,
-                    stdType: stdType,
-                    stdLibId: stdNode.list_id,
-                    stdNode: stdTree.getNodeKey(stdNode)
+                postData(window.location.pathname + '/update', {
+                    postType: 'add-std',
+                    postData: {
+                        id: ledgerTree.getNodeKey(mainNode),
+                        tender_id: mainNode.tender_id,
+                        stdType: stdType,
+                        stdLibId: stdNode.list_id,
+                        stdNode: stdTree.getNodeKey(stdNode)
+                    }
                 }, function (result) {
-                    treeOperationObj.refreshTree(mainSheet, result);
+                    const refreshNode = mainTree.loadPostData(result);
+                    treeOperationObj.refreshTree(mainSheet, refreshNode);
                     treeOperationObj.refreshOperationValid(mainSheet);
                 });
             });
@@ -1265,12 +1315,15 @@ $(document).ready(function() {
                     return;
                 }
 
-                postData('/tender/' + getTenderId() + '/ledger/add-by-deal', {
-                    id: mainNode.ledger_id,
-                    type: mainNode.code ? 'child' : 'next',
-                    dealBills: {
-                        b_code: dealBills.code, name: dealBills.name, unit: dealBills.unit,
-                        unit_price: dealBills.unit_price,
+                postData(window.location.pathname + '/update', {
+                    postType: 'add-deal',
+                    postData: {
+                        id: mainNode.ledger_id,
+                        type: mainNode.code ? 'child' : 'next',
+                        dealBills: {
+                            b_code: dealBills.code, name: dealBills.name, unit: dealBills.unit,
+                            unit_price: dealBills.unit_price,
+                        }
                     },
                 }, function (result) {
                     const refreshData = mainTree.loadPostData(result);
@@ -1298,7 +1351,6 @@ $(document).ready(function() {
             const self = this;
             postData(this.url+'/get-data', {}, function (data) {
                 self.data = data;
-                //self.calculateData();
                 SpreadJsObj.loadSheetData(self.spread.getActiveSheet(), 'data', data);
             });
         }
@@ -1446,9 +1498,9 @@ $(document).ready(function() {
                 if (select) {
                     const insertData = {};
                     insertData.batchType = (select.code && select.code !== '') ? 'child' : 'next';
-                    insertData.id = select[ledgerTree.setting.id];
+                    insertData.id = ledgerTree.getNodeKey(select);
                     insertData.batchData = self.getBatchData();
-                    postData('/tender/' + getTenderId() + '/ledger/batch-insert', insertData, function (data) {
+                    postData(window.location.pathname + '/update', {postType: 'batch-insert', postData: insertData}, function (data) {
                         pos.updateDatas(data.pos);
                         const result = ledgerTree.loadPostData(data.ledger);
                         treeOperationObj.refreshTree(sheet, result);

+ 207 - 269
app/public/js/path_tree.js

@@ -9,6 +9,8 @@
 
 'use strict';
 
+const itemsPre = 'id_';
+
 class PosData {
     /**
      * 构造函数
@@ -16,7 +18,7 @@ class PosData {
      */
     constructor (setting) {
         // 无索引
-        this.datas = null;
+        this.datas = [];
         // 以key为索引
         this.items = {};
         // 以分类id为索引的有序
@@ -245,8 +247,6 @@ class MasterPosData extends PosData {
     }
 }
 
-const itemsPre = 'id_';
-
 const createNewPathTree = function (type, setting) {
     class BaseTree {
         /**
@@ -285,6 +285,13 @@ const createNewPathTree = function (type, setting) {
                 }
             };
             this.nodes = [];
+            if (!isResort) {
+                this.children = this.getChildren();
+            } else {
+                this.children.sort(function (a, b) {
+                    return a.order - b.order;
+                })
+            }
             addSortNodes(this.children);
         }
         /**
@@ -324,6 +331,7 @@ const createNewPathTree = function (type, setting) {
                 return a.order - b.order;
             });
             this.sortTreeNode(true);
+            if (this.setting.autoExpand >= 0) this.expandByLevel(this.setting.autoExpand);
         }
 
         getItemsByIndex(index) {
@@ -444,7 +452,6 @@ const createNewPathTree = function (type, setting) {
         getPosterity (node) {
             if (node.full_path !== '') {
                 const reg = new RegExp('^' + node.full_path + '-');
-                console.log(reg);
                 return this.datas.filter(function (x) {
                     return reg.test(x.full_path);
                 });
@@ -459,7 +466,7 @@ const createNewPathTree = function (type, setting) {
          */
         isLastSibling (node) {
             const siblings = this.getChildren(this.getParent(node));
-            return node.order === siblings[siblings.length - 1].order;
+            return (siblings && siblings.length > 0) ?  node.order === siblings[siblings.length - 1].order : false;
         };
         /**
          * 刷新子节点是否可见
@@ -534,14 +541,6 @@ const createNewPathTree = function (type, setting) {
          * @param {Number} level - 展开层数
          */
         expandByLevel(level) {
-            // function recursiveExpand(nodes, parent) {
-            //     for (const node of nodes) {
-            //         node.expanded = node.level < level;
-            //         node.visible = parent ? (parent.expanded && parent.visible) : true;
-            //         recursiveExpand(node.children, node);
-            //     }
-            // }
-            // recursiveExpand(this.children);
             this.expandByCustom(function (n) {
                 return n.level < level;
             });
@@ -563,143 +562,6 @@ const createNewPathTree = function (type, setting) {
             }
             return reload;
         }
-    }
-
-    class MeasureTree extends BaseTree {
-        addData (datas) {
-            const loadedData = [];
-            for (const data of datas) {
-                let node = this.getItems(data[this.setting.id]);
-                if (node) {
-                    for (const prop in node) {
-                        if (data[prop] !== undefined) {
-                            node[prop] = data[prop];
-                        }
-                    }
-                    loadedData.push(node);
-                } else {
-                    const keyName = itemsPre + data[this.setting.id];
-                    const node = JSON.parse(JSON.stringify(data));
-                    this.items[keyName] = node;
-                    this.datas.push(node);
-                    node.expanded = false;
-                    node.visible = true;
-                    loadedData.push(node);
-                }
-            }
-            this.sortTreeNode();
-            for (const node of loadedData) {
-                const children = node.children;
-                if (!node.expanded && children.length > 0) {
-                    node.expanded = true;
-                    this._refreshChildrenVisible(node);
-                }
-            }
-            return loadedData;
-        }
-        removeData (datas) {
-            datas.sort(function (a, b) {
-                return b.level - a.level;
-            });
-            const removeArrayData = function (array, data) {
-                const index = array.indexOf(data);
-                array.splice(index, 1);
-            };
-            for (const data of datas) {
-                const node = this.getItems(data[this.setting.id]);
-                if (node && this.getChildren(node).length === 0) {
-                    delete this.items[itemsPre + node[this.setting.id]];
-                    if (node[this.setting.pid] !== this.setting.rootId) {
-                        const parent = this.items[itemsPre + node[this.setting.pid]];
-                        removeArrayData(parent.children, node);
-                    }
-                    removeArrayData(this.datas, node);
-                    removeArrayData(this.nodes, node);
-                }
-            }
-        };
-        loadLeafData (data) {
-            const datas = data instanceof Array ? data : [data];
-            for (const d of datas) {
-                let node = this.getItems(d[this.setting.id]);
-                if (node && node.is_leaf) {
-                    for (const prop in node) {
-                        if (data[prop] !== undefined) {
-                            node[prop] = d[prop];
-                        }
-                    }
-                }
-            }
-        };
-    }
-
-    class FxTree extends BaseTree {
-
-        /**
-         * 检查节点是否是最底层项目节
-         * @param node
-         * @returns {boolean}
-         */
-        isLeafXmj(node) {
-            if (!node.code) {
-                return false;
-            }
-            for (const child of node.children) {
-                if (!child.b_code || child.b_code === '') {
-                    return false;
-                }
-            }
-            return true;
-        }
-
-        /**
-         * 查询最底层项目节(本身或父项)
-         * @param {Object} node - 查询节点
-         * @returns {Object}
-         */
-        getLeafXmjParent(node) {
-            let parent = node;
-            while (parent) {
-                if (this.isLeafXmj(parent)) {
-                    return parent;
-                } else {
-                    parent = this.getParent(parent);
-                }
-            }
-            return null;
-        }
-
-        /**
-         * 展开至最底层项目节
-         */
-        expandToLeafXmj() {
-            const self = this;
-            this.expandByCustom(function (node) {
-                if (node.b_code && node.b_code !== '') {
-                    return false;
-                } else {
-                    return !self.isLeafXmj(node);
-                }
-            })
-        }
-
-        /**
-         * 展开至计算项
-         */
-        expandByCalcFields() {
-            const self = this;
-            this.expandByCustom(function (node) {
-                for (const field of self.setting.calcFields) {
-                    if (node[field]) {
-                        return true;
-                    }
-                }
-                return false;
-            })
-        }
-    }
-
-    class LedgerTree extends FxTree {
 
         /**
          * 加载数据(动态),只加载不同部分
@@ -730,8 +592,12 @@ const createNewPathTree = function (type, setting) {
             }
             loadedData = _.uniq(loadedData);
             for (const node of loadedData) {
-                node.children = this.getChildren(node);
-                node.expanded = node.children.length === 0 ? true : node.children[0].visible;
+                if (node) {
+                    node.children = this.getChildren(node);
+                    node.expanded = node.children.length === 0 ? true : node.children[0].visible;
+                } else {
+                    this.children = this.getChildren(null);
+                }
             }
             this.sortTreeNode(true);
             return loadedData;
@@ -772,11 +638,17 @@ const createNewPathTree = function (type, setting) {
                     const parent = this.getItems(node[this.setting.pid]);
                     if (parent && resortData.indexOf(parent) === -1) {
                         resortData.push(parent);
+                    } else {
+                        resortData.push(this.setting.rootId);
                     }
                 }
             }
             for (const node of resortData) {
-                node.children = this.getChildren(node);
+                if (node && node !== this.setting.rootId) {
+                    node.children = this.getChildren(node);
+                } else {
+                    this.children = this.getChildren(null);
+                }
             }
             this.sortTreeNode(true);
             for (const node of loadedData) {
@@ -802,17 +674,22 @@ const createNewPathTree = function (type, setting) {
                 const node = this.getItems(data[this.setting.id]);
                 if (node) {
                     freeDatas.push(node);
+                    node.deleteIndex = this.nodes.indexOf(node);
                     delete this.items[itemsPre + node[this.setting.id]];
                     if (node[this.setting.pid] !== this.setting.rootId) {
                         const parent = this.getItems(node[this.setting.pid]);
                         if (parent) {
                             removeArrayData(parent.children, node);
                         }
+                    } else {
+                        removeArrayData(this.children, node);
                     }
                     removeArrayData(this.datas, node);
-                    removeArrayData(this.nodes, node);
                 }
             }
+            for(const node of freeDatas) {
+                removeArrayData(this.nodes, node);
+            }
             return freeDatas;
         };
         /**
@@ -871,6 +748,176 @@ const createNewPathTree = function (type, setting) {
         };
 
         /**
+         * 因为提交其他数据,引起的树结构数据更新,调用该方法
+         *
+         * @param data - 更新的数据 {update, create, delete}
+         * @returns {{}}
+         */
+        loadPostData(data) {
+            const result = {};
+            if (data.delete) {
+                result.delete = this._freeData(data.delete);
+            }
+            if (data.create) {
+                result.create = this._loadData(data.create);
+            }
+            if (data.update) {
+                result.update = this._updateData(data.update);
+            }
+            return result;
+        }
+        /**
+         * 加载子节点
+         * @param {Object} node
+         * @param {function} callback
+         */
+        loadChildren (node, callback) {
+            if (this.setting.url !== '') {
+                const self = this;
+                postData(this.setting.url, {postType: 'load-child', id: this.getNodeKeyData(node)}, function (data) {
+                    self._loadData(data);
+                    callback();
+                });
+            }
+        };
+    }
+
+    class MeasureTree extends BaseTree {
+        addData (datas) {
+            const loadedData = [];
+            for (const data of datas) {
+                let node = this.getItems(data[this.setting.id]);
+                if (node) {
+                    for (const prop in node) {
+                        if (data[prop] !== undefined) {
+                            node[prop] = data[prop];
+                        }
+                    }
+                    loadedData.push(node);
+                } else {
+                    const keyName = itemsPre + data[this.setting.id];
+                    const node = JSON.parse(JSON.stringify(data));
+                    this.items[keyName] = node;
+                    this.datas.push(node);
+                    node.expanded = false;
+                    node.visible = true;
+                    loadedData.push(node);
+                }
+            }
+            this.sortTreeNode();
+            for (const node of loadedData) {
+                const children = node.children;
+                if (!node.expanded && children.length > 0) {
+                    node.expanded = true;
+                    this._refreshChildrenVisible(node);
+                }
+            }
+            return loadedData;
+        }
+        removeData (datas) {
+            datas.sort(function (a, b) {
+                return b.level - a.level;
+            });
+            const removeArrayData = function (array, data) {
+                const index = array.indexOf(data);
+                array.splice(index, 1);
+            };
+            for (const data of datas) {
+                const node = this.getItems(data[this.setting.id]);
+                if (node && this.getChildren(node).length === 0) {
+                    delete this.items[itemsPre + node[this.setting.id]];
+                    if (node[this.setting.pid] !== this.setting.rootId) {
+                        const parent = this.items[itemsPre + node[this.setting.pid]];
+                        removeArrayData(parent.children, node);
+                    }
+                    removeArrayData(this.datas, node);
+                    removeArrayData(this.nodes, node);
+                }
+            }
+        };
+        loadLeafData (data) {
+            const datas = data instanceof Array ? data : [data];
+            for (const d of datas) {
+                let node = this.getItems(d[this.setting.id]);
+                if (node && node.is_leaf) {
+                    for (const prop in node) {
+                        if (data[prop] !== undefined) {
+                            node[prop] = d[prop];
+                        }
+                    }
+                }
+            }
+        };
+    }
+
+    class FxTree extends BaseTree {
+
+        /**
+         * 检查节点是否是最底层项目节
+         * @param node
+         * @returns {boolean}
+         */
+        isLeafXmj(node) {
+            if (!node.code) {
+                return false;
+            }
+            for (const child of node.children) {
+                if (!child.b_code || child.b_code === '') {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        /**
+         * 查询最底层项目节(本身或父项)
+         * @param {Object} node - 查询节点
+         * @returns {Object}
+         */
+        getLeafXmjParent(node) {
+            let parent = node;
+            while (parent) {
+                if (this.isLeafXmj(parent)) {
+                    return parent;
+                } else {
+                    parent = this.getParent(parent);
+                }
+            }
+            return null;
+        }
+
+        /**
+         * 展开至最底层项目节
+         */
+        expandToLeafXmj() {
+            const self = this;
+            this.expandByCustom(function (node) {
+                if (node.b_code && node.b_code !== '') {
+                    return false;
+                } else {
+                    return !self.isLeafXmj(node);
+                }
+            })
+        }
+
+        /**
+         * 展开至计算项
+         */
+        expandByCalcFields() {
+            const self = this;
+            this.expandByCustom(function (node) {
+                for (const field of self.setting.calcFields) {
+                    if (node[field]) {
+                        return true;
+                    }
+                }
+                return false;
+            })
+        }
+    }
+
+    class LedgerTree extends FxTree {
+        /**
          *
          * @param parent
          * @param node
@@ -919,17 +966,17 @@ const createNewPathTree = function (type, setting) {
          */
         loadPostData(data) {
             const result = {}, reCalcNodes = [];
-            if (data.update) {
-                result.update = this._updateData(data.update);
-                this._getReCalcNodes(reCalcNodes, result.update);
+            if (data.delete) {
+                result.delete = this._freeData(data.delete);
+                this._getReCalcNodes(reCalcNodes, result.delete);
             }
             if (data.create) {
                 result.create = this._loadData(data.create);
                 this._getReCalcNodes(reCalcNodes, result.create);
             }
-            if (data.delete) {
-                result.delete = this._freeData(data.delete);
-                this._getReCalcNodes(reCalcNodes, result.delete);
+            if (data.update) {
+                result.update = this._updateData(data.update);
+                this._getReCalcNodes(reCalcNodes, result.update);
             }
             reCalcNodes.sort((a, b) => {
                 return b.level - a.level;
@@ -940,115 +987,6 @@ const createNewPathTree = function (type, setting) {
             result.update = result.update ? result.update.concat(reCalcNodes) : reCalcNodes;
             return result;
         }
-
-        /**
-         * 以下方法需等待响应, 通过callback刷新界面
-         */
-        /**
-         * 加载子节点
-         * @param {Object} node
-         * @param {function} callback
-         */
-        loadChildren (node, callback) {
-            const self = this;
-            const url = this.setting.preUrl ? this.setting.preUrl + '/get-children' : 'get-children';
-            postData(url, this.getNodeKeyData(node), function (data) {
-                self._loadData(data);
-                callback();
-            });
-        };
-        /**
-         * 树结构基本操作
-         * @param {String} url - 请求地址
-         * @param {Object} node - 操作节点
-         * @param {String} type - 操作类型
-         * @param {function} callback - 界面刷新
-         */
-        baseOperation (url, node, type, callback) {
-            const self = this;
-            const data = {
-                id: node[this.setting.id],
-                postType: type
-            };
-            postData(url, data, function (datas) {
-                const refreshData = self.loadPostData(datas);
-                callback(refreshData);
-            });
-        };
-        /**
-         * 节点数据编辑
-         * @param {String} url - 请求地址
-         * @param {Array|Object} updateData - 需更新的数据
-         * @param {function} callback - 界面刷新
-         */
-        update (url, updateData, callback) {
-            const self = this;
-            postData(url, updateData, function (datas) {
-                const refreshData = self.loadPostData(datas);
-                callback(refreshData);
-            }, function () {
-                if (updateData instanceof Array) {
-                    const result = [];
-                    for (const data of updateData) {
-                        result.push(self.getItems(data[self.setting.id]));
-                    }
-                    callback(result)
-                } else {
-                    callback([self.getItems(updateData[self.setting.id])]);
-                }
-            });
-        };
-        /**
-         * 复制粘贴整块(目前仅可粘贴为后项)
-         * @param {String} url - 请求地址
-         * @param {Object} node - 操作节点
-         * @param {Array} block - 被复制整块的节点列表
-         * @param {function} callback - 界面刷新
-         */
-        pasteBlock (url, node, block, callback) {
-            const self = this;
-            const data = {
-                id: node[self.setting.id],
-                block: block
-            };
-            postData(url, data, function (datas) {
-                const refreshData = self.loadPostData(datas);
-                callback(refreshData);
-            });
-        };
-        /**
-         * 提交数据
-         * @param {String} url - 请求地址
-         * @param {Object} node - 当前选中节点
-         * @param {Object} data - 提交的数据
-         * @param {function} callback - 界面刷新
-         */
-        postData (url, node, data, callback) {
-            const self = this;
-            if (node) {
-                data.id = node[self.setting.id];
-            }
-            postData(url, data, function (datas) {
-                const refreshData = self.loadPostData(datas);
-                callback(refreshData);
-                // const result = {};
-                // if (datas.update) {
-                //     result.update = self._updateData(datas.update);
-                // }
-                // if (datas.create) {
-                //     result.create = self._loadData(datas.create);
-                // }
-                // if (datas.delete) {
-                //     result.delete = self._freeData(datas.delete);
-                // }
-                // if (datas.expand) {
-                //     const [create, update] = self._loadExpandData(datas.expand);
-                //     result.create = result.create ? result.create.concat(create) : create;
-                //     result.expand = update;
-                // }
-                // callback(result);
-            });
-        };
     }
 
     class StageTree extends FxTree {

+ 16 - 0
app/public/js/profile.js

@@ -78,6 +78,9 @@ $(document).ready(function() {
         $("#bind-btn").click(function() {
             const code = $("input[name='code']").val();
             const mobile = $("input[name='auth_mobile']").val();
+            if ($(this).hasClass('disabled')) {
+                return false;
+            }
             if (!(/^1[3456789]\d{9}$/.test(mobile))) {
                 toast('请填写正确的手机号码', 'error');
                 return false;
@@ -87,6 +90,19 @@ $(document).ready(function() {
                 toast('请填写正确的验证码', 'error');
                 return false;
             }
+            $.ajax({
+                url: '/profile/bind?_csrf=' + csrf,
+                type: 'post',
+                data: { auth_mobile: mobile, code: code },
+                dataTye: 'json',
+                success: function(response) {
+                    if (response.err === 0) {
+                        window.location.href = response.data.url;
+                    } else {
+                        toast(response.msg, 'error');
+                    }
+                }
+            });
         });
         // 修改手机
         $('#change-mobile').click(function () {

+ 48 - 13
app/public/js/revise.js

@@ -66,12 +66,32 @@ $(document).ready(() => {
     pos.loadDatas(posData);
 
     const billsTreeSpreadObj = {
+        getDefaultSelectInfo: function (sheet) {
+            const tree = sheet.zh_tree;
+            if (!tree) return;
+            const sel = sheet.getSelections()[0];
+            const node = sheet.zh_tree.nodes[sel.row];
+            if (!node) return;
+            let count = 1;
+            if (sel.rowCount > 1) {
+                for (let r = 1; r < sel.rowCount; r++) {
+                    const rNode = sheet.zh_tree.nodes[sel.row + r];
+                    if (rNode.level > node.level) continue;
+                    if ((rNode.level < node.level) || (rNode.level === node.level && rNode.pid !== node.pid)) {
+                        toast('请选择同一清单下的节点,进行该操作');
+                        return;
+                    }
+                    count += 1;
+                }
+            }
+            return [tree, node, count];
+        },
         /**
          * 刷新顶部按钮是否可用
          * @param sheet
          * @param selections
          */
-        refreshOperationValid: function (sheet) {
+        refreshOperationValid: function (sheet, selection) {
             const setObjEnable = function (obj, enable) {
                 if (enable) {
                     obj.removeClass('disabled');
@@ -79,23 +99,38 @@ $(document).ready(() => {
                     obj.addClass('disabled');
                 }
             };
+            const sel = selection ? selection[0] : sheet.getSelections()[0];
+            const row = sel ? sel.row : -1;
             const tree = sheet.zh_tree;
-            const node = SpreadJsObj.getSelectObject(sheet);
-            const preNode = tree.getPreSiblingNode(node);
+            if (!tree) return;
+            const first = sheet.zh_tree.nodes[row];
+            let last = first, sameParent = true;
+            if (sel.rowCount > 1) {
+                for (let r = 1; r < sel.rowCount; r++) {
+                    const rNode = tree.nodes[sel.row + r];
+                    if (rNode.level > first.level) continue;
+                    if ((rNode.level < first.level) || (rNode.level === first.level && rNode.pid !== first.pid)) {
+                        sameParent = false;
+                        break;
+                    }
+                    last = rNode;
+                }
+            }
+            const preNode = tree.getPreSiblingNode(first);
             const valid = !sheet.zh_setting.readOnly;
 
-            setObjEnable($('a[type="add"]'), valid);
-            setObjEnable($('a[type="delete"]'), valid && node);
-            setObjEnable($('a[type="up-move"]'), valid && node && preNode);
-            setObjEnable($('a[type="down-move"]'), valid && node && !tree.isLastSibling(node));
-            if (isTz) {
-                const posRange = node ? pos.getLedgerPos(node.id) : [];
-                setObjEnable($('a[type="up-level"]'), valid && node && tree.getParent(node) && node.level > 2 && (!posRange || posRange.length === 0));
+            setObjEnable($('#insert'), valid && first && first.level > 1);
+            setObjEnable($('#delete'), valid && first && sameParent && first.level > 1);
+            setObjEnable($('#up-move'), valid && first && sameParent && first.level > 1 && preNode);
+            setObjEnable($('#down-move'), valid && first && sameParent && first.level > 1 && !tree.isLastSibling(last));
+            if (checkTzMeasureType()) {
+                const posRange = last ? pos.getLedgerPos(last.id) : [];
+                setObjEnable($('#up-level'), valid && first && sameParent && tree.getParent(first) && first.level > 2 && (!posRange || posRange.length === 0));
                 const preNodePosRange = preNode ? pos.getLedgerPos(preNode.id) : [];
-                setObjEnable($('a[type="down-level"]'), valid && node && preNode && (!preNodePosRange || preNodePosRange.length === 0));
+                setObjEnable($('#down-level'), valid && first && sameParent && first.level > 1 && preNode && (!preNodePosRange || preNodePosRange.length === 0));
             } else {
-                setObjEnable($('#up-level'), valid && node && tree.getParent(node));
-                setObjEnable($('#down-level'), valid && node && preNode);
+                setObjEnable($('#up-level'), valid && first && sameParent && first.level > 2 && tree.getParent(first));
+                setObjEnable($('#down-level'), valid && first && sameParent && first.level > 1 && preNode);
             }
             setObjEnable($('#cut'), valid);
             setObjEnable($('#paste'), valid);

+ 56 - 18
app/public/js/spreadjs_rela/spreadjs_zh.js

@@ -112,13 +112,13 @@ const SpreadJsObj = {
             allowResizeColumns: true
         };
 
-        sheet.options.protectionOptions = option;
-        sheet.options.isProtected = true;
-        sheet.options.allowCellOverflow = false;
-
         const defaultStyle = sheet.getDefaultStyle();
         defaultStyle.locked = false;
         sheet.setDefaultStyle(defaultStyle);
+
+        sheet.options.protectionOptions = option;
+        sheet.options.isProtected = true;
+        sheet.options.allowCellOverflow = false;
     },
     /**
      * sheet批量操作优化(sheet操作大批量数据时, 屏蔽数据刷新, 可优化大量时间)
@@ -360,8 +360,10 @@ const SpreadJsObj = {
                 cell.backColor(sheet.zh_setting.getColor(data, col, sheet.getDefaultStyle().backColor));
             }
         });
+
     },
     _defineColCellType: function (sheet, col, colSetting) {
+        sheet.AcitveComboRefresh = false;
         if(colSetting.cellType === 'ellipsis') {
             if (!sheet.extendCellType.ellipsis) {
                 sheet.extendCellType.ellipsis = this.CellType.getEllipsisTextCellType();
@@ -419,19 +421,44 @@ const SpreadJsObj = {
         if (colSetting.cellType === 'unit') {
             if (!sheet.extendCellType.unit) {
                 sheet.extendCellType.unit = this.CellType.getUnitCellType();
-                sheet.bind(spreadNS.Events.LeaveCell, function (e, info) {
-                    const cellType = info.sheet.getCell(info.row, info.col).cellType();
-                    if (cellType === sheet.extendCellType.unit) {
-                        info.sheet.leaveCell = {row: info.row, col: info.col};
-                    } else {
-                        delete info.sheet.leaveCell;
-                    }
-                });
-                sheet.bind(spreadNS.Events.EnterCell, function (e, info) {
-                    if (info.sheet.leaveCell) {
-                        info.sheet.repaint(info.sheet.getCellRect(info.sheet.leaveCell.row, info.sheet.leaveCell.col));
-                    }
-                });
+                if (!sheet.AcitveComboRefresh) {
+                    sheet.bind(spreadNS.Events.LeaveCell, function (e, info) {
+                        const cellType = info.sheet.getCell(info.row, info.col).cellType();
+                        if (cellType === sheet.extendCellType.unit) {
+                            info.sheet.leaveCell = {row: info.row, col: info.col};
+                        } else {
+                            delete info.sheet.leaveCell;
+                        }
+                    });
+                    sheet.bind(spreadNS.Events.EnterCell, function (e, info) {
+                        if (info.sheet.leaveCell) {
+                            info.sheet.repaint(info.sheet.getCellRect(info.sheet.leaveCell.row, info.sheet.leaveCell.col));
+                        }
+                    });
+                    sheet.AcitveComboRefresh = true;
+                }
+            }
+            sheet.getRange(-1, col, -1, 1).cellType(sheet.extendCellType.unit);
+        }
+        if (colSetting.cellType === 'customizeCombo') {
+            if (!sheet.extendCellType.unit) {
+                sheet.extendCellType.unit = this.CellType.getCustomizeComboCellType(colSetting.comboItems);
+                if (!sheet.AcitveComboRefresh) {
+                    sheet.bind(spreadNS.Events.LeaveCell, function (e, info) {
+                        const cellType = info.sheet.getCell(info.row, info.col).cellType();
+                        if (cellType === sheet.extendCellType.unit) {
+                            info.sheet.leaveCell = {row: info.row, col: info.col};
+                        } else {
+                            delete info.sheet.leaveCell;
+                        }
+                    });
+                    sheet.bind(spreadNS.Events.EnterCell, function (e, info) {
+                        if (info.sheet.leaveCell) {
+                            info.sheet.repaint(info.sheet.getCellRect(info.sheet.leaveCell.row, info.sheet.leaveCell.col));
+                        }
+                    });
+                    sheet.AcitveComboRefresh = true;
+                }
             }
             sheet.getRange(-1, col, -1, 1).cellType(sheet.extendCellType.unit);
         }
@@ -491,6 +518,7 @@ const SpreadJsObj = {
                 const data = sortData[i];
                 if (!data) { continue; }
                 this._loadRowData(sheet, data, i);
+                sheet.setRowVisible(i, data.visible);
             }
             this.endMassOperation(sheet);
         } catch (err) {
@@ -515,6 +543,7 @@ const SpreadJsObj = {
                 const data = sortData[row];
                 // 单元格重新写入数据
                 this._loadRowData(sheet, data, row);
+                sheet.setRowVisible(row, data.visible);
             };
             this.endMassOperation(sheet);
         } catch (err) {
@@ -1567,6 +1596,15 @@ const SpreadJsObj = {
                 '桥长米', '公路公里', '株', '组', '座', '元', '工日', '套', '台班', '艘班', '亩',
                 'm/处', 'm/道', 'm/座', 'm2/m', 'm3/m', 'm3/处', '根/米', 'm3/m2']);
             return combo;
-        }
+        },
+        /**
+         * 获取 自定义的CellType
+         * @returns {GC.Spread.Sheets.CellTypes.ComboBox}
+         */
+        getCustomizeComboCellType: function (items) {
+            let combo = this.getActiveComboCellType();
+            combo.itemHeight(10).editorValueType(spreadNS.CellTypes.EditorValueType.value).items(items);
+            return combo;
+        },
     }
 };

+ 2 - 8
app/router.js

@@ -86,16 +86,10 @@ module.exports = app => {
 
 
     // 台账管理相关
-    app.get('/tender/:id/ledger/explode', sessionAuth, tenderCheck, 'ledgerController.explode');
+    app.get('/tender/:id/ledger', sessionAuth, tenderCheck, 'ledgerController.explode');
     app.post('/tender/:id/ledger/get-children', sessionAuth, tenderCheck, 'ledgerController.getChildren');
-    app.post('/tender/:id/ledger/base-operation', sessionAuth, tenderCheck, 'ledgerController.baseOperation');
     app.post('/tender/:id/ledger/update', sessionAuth, tenderCheck, 'ledgerController.update');
-    app.post('/tender/:id/ledger/update-info', sessionAuth, tenderCheck, 'ledgerController.updateInfo');
-    app.post('/tender/:id/ledger/paste-block', sessionAuth, tenderCheck, 'ledgerController.pasteBlock');
-    app.post('/tender/:id/ledger/add-by-std', sessionAuth, tenderCheck, 'ledgerController.addFromStandardLib');
-    app.post('/tender/:id/ledger/add-by-deal', sessionAuth, tenderCheck, 'ledgerController.addFromDealBills');
-    app.post('/tender/:id/ledger/batch-insert', sessionAuth, tenderCheck, 'ledgerController.batchInsert');
-    app.post('/tender/:id/ledger/explode/upload-excel', sessionAuth, tenderCheck, 'ledgerController.uploadExcel');
+    app.post('/tender/:id/ledger/upload-excel', sessionAuth, tenderCheck, 'ledgerController.uploadExcel');
     app.get('/tender/:id/ledger/download/:file', sessionAuth, tenderCheck, 'ledgerController.download');
     app.post('/tender/:id/pos', sessionAuth, tenderCheck, 'ledgerController.pos');
     app.post('/tender/:id/pos/update', sessionAuth, tenderCheck, 'ledgerController.posUpdate');

File diff suppressed because it is too large
+ 21 - 911
app/service/ledger.js


+ 3 - 0
app/service/ledger_revise.js

@@ -78,6 +78,9 @@ module.exports = app => {
 
         async _initReviseBills(transaction, tid) {
             const sql = 'Insert Into ' + this.ctx.service.reviseBills.tableName +
+                '  (id, code, b_code, name, unit, source, remark, ledger_id, ledger_pid, level, order, full_path, is_leaf,' +
+                '     quantity, total_price, unit_price, drawing_code, memo, dgn_qty1, dgn_qty2, deal_qty, deal_tp,' +
+                '     sgfh_qty, sgfh_tp, sjcl_qty, sjcl_tp, qtcl_qty, qtcl_tp, node_type, crid)' +
                 '  Select * From ' + this.ctx.service.ledger.tableName +
                 '  Where `tender_id` = ?';
             const sqlParam = [tid];

+ 0 - 36
app/service/ledger_revise_bills.js

@@ -1,36 +0,0 @@
-'use strict';
-
-/**
- *
- *
- * @author Mai
- * @date
- * @version
- */
-
-module.exports = app => {
-    class LedgerReviseBills extends app.BaseService {
-        /**
-         * 构造函数
-         *
-         * @param {Object} ctx - egg全局变量
-         * @return {void}
-         */
-        constructor(ctx) {
-            super(ctx);
-            this.tableName = 'ledger_revise_bills';
-        }
-
-        /**
-         * 初始化 修订的 清单
-         * @param tid
-         * @param lrid
-         * @returns {Promise<void>}
-         */
-        async initReviseBills(tid, lrid) {
-
-        }
-    }
-
-    return LedgerReviseBills;
-};

+ 0 - 36
app/service/ledger_revise_pos.js

@@ -1,36 +0,0 @@
-'use strict';
-
-/**
- *
- *
- * @author Mai
- * @date
- * @version
- */
-
-module.exports = app => {
-    class LedgerRevisePos extends app.BaseService {
-        /**
-         * 构造函数
-         *
-         * @param {Object} ctx - egg全局变量
-         * @return {void}
-         */
-        constructor(ctx) {
-            super(ctx);
-            this.tableName = 'ledger_revise_pos';
-        }
-
-        /**
-         * 初始化 修订的 部位明细
-         * @param tid
-         * @param lrid
-         * @returns {Promise<void>}
-         */
-        async initRevisePos(tid, lrid) {
-
-        }
-    }
-
-    return LedgerRevisePos;
-};

+ 3 - 3
app/service/project_account.js

@@ -168,7 +168,7 @@ module.exports = app => {
                     const currentTime = new Date().getTime() / 1000;
                     // 加密token
                     const sessionToken = crypto.createHmac('sha1', currentTime + '').update(accountData.account)
-                        .digest().toString('base64');
+                        .digest('hex').toString('base64');
 
                     if (loginType === 2) {
                         const updateData = {
@@ -426,13 +426,13 @@ module.exports = app => {
             const cacheKey = 'smsCode:' + accountId;
             const cacheCode = await this.cache.get(cacheKey);
             if (cacheCode === null || data.code === undefined || cacheCode !== (data.code + data.auth_mobile)) {
-                return false;
+                throw '验证码错误!';
             }
 
             // 查找是否有重复的认证手机
             const accountData = await this.getDataByCondition({ project_id: pid, auth_mobile: data.auth_mobile });
             if (accountData !== null) {
-                throw '已存在对应的手机';
+                throw '此手机号码已被使用,请重新输入!';
             }
 
             const updateData = { id: accountId, auth_mobile: data.auth_mobile };

+ 6 - 1
app/service/revise_bills.js

@@ -26,11 +26,16 @@ module.exports = app => {
                 level: 'level',
                 isLeaf: 'is_leaf',
                 fullPath: 'full_path',
-                keyPre: 'revise_bills_maxLid:'
+                keyPre: 'revise_bills_maxLid:',
+                uuid: true,
             });
             this.tableName = 'revise_bills';
         }
 
+        async _deleteRelaData (mid, deleteData) {
+            await this.ctx.service.revisePos.deletePosData(this.transaction, mid, this._.map(deleteData, 'id'));
+        }
+
         /**
          * 新增节点
          * @param {Number} tid - 台账id

+ 6 - 2
app/service/stage.js

@@ -308,7 +308,9 @@ module.exports = app => {
                         if (pt.attachment !== null && pt.attachment !== '') {
                             const payAttList = JSON.parse(pt.attachment);
                             for (const pat of payAttList) {
-                                await fs.unlinkSync(path.join(this.app.baseDir, pat.filepath));
+                                if (fs.existsSync(path.join(this.app.baseDir, pat.filepath))) {
+                                    await fs.unlinkSync(path.join(this.app.baseDir, pat.filepath));
+                                }
                             }
                         }
                     }
@@ -318,7 +320,9 @@ module.exports = app => {
                 const attList = await this.ctx.service.stageAtt.getAllDataByCondition({ where: { sid: id } });
                 if (attList.length !== 0) {
                     for (const att of attList) {
-                        await fs.unlinkSync(path.join(this.app.baseDir, att.filepath));
+                        if (fs.existsSync(path.join(this.app.baseDir, att.filepath))) {
+                            await fs.unlinkSync(path.join(this.app.baseDir, att.filepath));
+                        }
                     }
                 }
                 await transaction.delete(this.ctx.service.stageAtt.tableName, { sid: id });

+ 1 - 1
app/service/stage_audit.js

@@ -667,7 +667,7 @@ module.exports = app => {
         async getAuditGroupByList(stageId, times) {
             const sql = 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`sid`, la.`aid`, la.`order` ' +
                 'FROM ?? AS la, ?? AS pa ' +
-                'WHERE la.`sid` = ? and la.`times` = ? and la.`aid` = pa.`id` GROUP BY la.`aid`';
+                'WHERE la.`sid` = ? and la.`times` = ? and la.`aid` = pa.`id` GROUP BY la.`aid` ORDER BY la.`order`';
             const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, stageId, times];
             return await this.db.query(sql, sqlParam);
             // const sql = 'SELECT `tid`, `sid`, `aid`, `order` FROM ?? WHERE `sid` = ? and `times` = ? GROUP BY `aid`';

+ 1 - 1
app/service/stage_pos.js

@@ -389,7 +389,7 @@ module.exports = app => {
                     said: this.ctx.session.sessionUser.accountId,
                     times: this.ctx.stage.curTimes,
                     order: this.ctx.stage.curOrder,
-                    contract_qty: orgPos.contract_qty,
+                    contract_qty: orgPos ? orgPos.contract_qty : 0,
                     qc_qty: qty,
                 });
             }

+ 4 - 0
app/view/change/info.ejs

@@ -108,8 +108,12 @@
                 <a href="#sp-list" data-toggle="modal" data-target="#sp-list" class="btn btn-outline-secondary btn-sm btn-block">审批中</a>
             <% } %>
             <% if (auditStatus === 4 && ctx.session.sessionUser.accountId === auditList[auditList.length-1].uid) { %>
+                <% if (stageChangeNum === 0) { %>
                 <!--重新审批-->
                 <a href="javascript: void(0);" data-toggle="modal" data-target="#sp-down-back" class="btn btn-warning btn-sm btn-block">重新审批</a>
+                <% } else { %>
+                <button class="btn btn-outline-secondary btn-sm btn-block" data-toggle="tooltip" data-placement="bottom" title="已被调用">重新审批</button>
+                <% } %>
             <% } %>
         </div>
     </div>

+ 1 - 1
app/view/change/info_modal.ejs

@@ -589,7 +589,7 @@
         </div>
     </div>
 </div>
-<% if (auditStatus === 4 && ctx.session.sessionUser.accountId === auditList[auditList.length-1].uid) { %>
+<% if (auditStatus === 4 && ctx.session.sessionUser.accountId === auditList[auditList.length-1].uid && stageChangeNum === 0) { %>
 <!--重新审批-->
 <div class="modal fade" id="sp-down-back" data-backdrop="static">
     <div class="modal-dialog" role="document">

+ 2 - 2
app/view/profile/sms.ejs

@@ -22,7 +22,7 @@
                     </div>
                     <% } %>
                     <!--绑定手机-->
-                    <form id="mobile-form" method="post" action="/profile/bind" <% if (accountData.auth_mobile !== '') { %>style="display: none" <% } %>>
+                    <form id="mobile-form" <% if (accountData.auth_mobile !== '') { %>style="display: none" <% } %>>
                         <div class="form-group">
                             <label>认证手机(用于 找回密码、接收通知)</label>
                             <div class="input-group mb-3">
@@ -38,7 +38,7 @@
                                 <input type="hidden" name="_csrf" value="<%= ctx.csrf %>">
                             </div>
                         </div>
-                        <button type="submit" class="btn btn-secondary disabled" id="bind-btn">确认绑定</button>
+                        <button type="button" class="btn btn-secondary disabled" id="bind-btn">确认绑定</button>
                     </form>
                     <% if (accountData.auth_mobile !== '') { %>
                     <!--短信通知开关(已有认证手机后显示)-->

+ 4 - 2
app/view/stage/audit_btn.ejs

@@ -1,7 +1,9 @@
 <div class="contarl-box">
     <% if (ctx.stage.status === auditConst.status.uncheck) { %>
-        <% if (ctx.session.sessionUser.accountId === ctx.stage.user_id) {%>
+        <% if (ctx.session.sessionUser.accountId === ctx.stage.user_id && ctx.stage.check_detail === 0) { %>
             <a href="javascript: void(0);" data-toggle="modal" data-target="#sub-sp" class="btn btn-primary btn-sm btn-block">上报审批</a>
+        <% } else if (ctx.session.sessionUser.accountId === ctx.stage.user_id && ctx.stage.check_detail === 1) {%>
+            <a href="javascript: void(0);" data-toggle="modal" data-target="#sub-sp3" class="btn btn-primary btn-sm btn-block">上报审批</a>
         <% } %>
     <% } else if (ctx.stage.status === auditConst.status.checking) { %>
         <% if (ctx.stage.curAuditor && ctx.stage.curAuditor.aid === ctx.session.sessionUser.accountId) { %>
@@ -24,7 +26,7 @@
             <a href="#sp-back" data-toggle="modal" data-target="#sp-back" class="btn btn-warning btn-sm btn-block">审批退回</a>
         <% } %>
     <% } %>
-    <% if (ctx.stage.auditors[ctx.stage.auditors.length-1].aid === ctx.session.sessionUser.accountId && ctx.stage.status === auditConst.status.checked && ctx.stage.order === ctx.stage.highOrder) { %>
+    <% if (ctx.stage.auditors !== undefined && ctx.stage.auditors.length !== 0 && ctx.stage.auditors[ctx.stage.auditors.length-1].aid === ctx.session.sessionUser.accountId && ctx.stage.status === auditConst.status.checked && ctx.stage.order === ctx.stage.highOrder) { %>
         <a href="javascript: void(0);" data-toggle="modal" data-target="#sp-down-back" class="btn btn-warning btn-sm btn-block">重新审批</a>
     <% } %>
 </div>

+ 5 - 5
app/view/stage/audit_modal.ejs

@@ -77,7 +77,7 @@
                 <h5 class="modal-title">上报审批</h5>
             </div>
             <div class="modal-body">
-                <h5>上报前,需要完成中间计量。</h5>
+                <h5>上报前,需要完成本期中间计量。</h5>
             </div>
             <div class="modal-footer">
                 <button type="button" class="btn btn-secondary" data-dismiss="modal">关闭</button>
@@ -284,7 +284,7 @@
                 <h5 class="modal-title">审批通过</h5>
             </div>
             <div class="modal-body">
-                <h5>审批前,需要完成中间计量。</h5>
+                <h5>审批前,需要完成本期中间计量。</h5>
             </div>
             <div class="modal-footer">
                 <button type="button" class="btn btn-secondary" data-dismiss="modal">关闭</button>
@@ -771,7 +771,7 @@
                 <h5 class="modal-title">重新上报</h5>
             </div>
             <div class="modal-body">
-                <h5>上报前,需要完成中间计量。</h5>
+                <h5>上报前,需要完成本期中间计量。</h5>
             </div>
             <div class="modal-footer">
                 <button type="button" class="btn btn-secondary" data-dismiss="modal">关闭</button>
@@ -1099,7 +1099,7 @@
                     <h5 class="modal-title">审批通过</h5>
                 </div>
                 <div class="modal-body">
-                    <h5>审批前,需要完成中间计量。</h5>
+                    <h5>审批前,需要完成本期中间计量。</h5>
                 </div>
                 <div class="modal-footer">
                     <button type="button" class="btn btn-secondary" data-dismiss="modal">关闭</button>
@@ -1112,7 +1112,7 @@
     </div>
     <% } %>
 <% } %>
-<% if (ctx.stage.auditors[ctx.stage.auditors.length-1].aid === ctx.session.sessionUser.accountId && ctx.stage.status === auditConst.status.checked && ctx.stage.order === ctx.stage.highOrder) { %>
+<% if (ctx.stage.auditors !== undefined && ctx.stage.auditors.length !== 0 && ctx.stage.auditors[ctx.stage.auditors.length-1].aid === ctx.session.sessionUser.accountId && ctx.stage.status === auditConst.status.checked && ctx.stage.order === ctx.stage.highOrder) { %>
     <!--上报审批 需要完成中间计量-->
     <div class="modal fade" id="sp-down-back" data-backdrop="static">
         <div class="modal-dialog" role="document">

+ 27 - 14
app/view/tender/tender_sub_menu.ejs

@@ -5,23 +5,36 @@
         </div>
     </div>
     <div class="scrollbar-auto">
-        <% for (const m in tenderMenu) { %>
         <div class="nav-box">
-            <% const menu = tenderMenu[m]; %>
-            <% if (menu.display) { %>
-            <h3><%- menu.icon %><%- menu.name %></h3>
-            <% } %>
-            <% if (menu.children && menu.children.length > 0) { %>
-            <ul class="nav-list list-unstyled <% if (menu.display) { %>sub-list<% } %>">
-                <% for (const mc of menu.children) { %>
-                <li <% if (ctx.url === preUrl + mc.url) { %>class="active"<% } %>>
-                    <a href="<%- (mc.fixedUrl ? mc.url : preUrl + mc.url) %>"><%- mc.icon %><span <%- mc.class%>><%- mc.name %></span></a>
-                </li>
-                <% } %>
+            <ul class="nav-list list-unstyled">
+                <li <% if (ctx.url === '/tender/' + ctx.tender.id) { %>class="active"<% } %>><a href="/tender/<%- ctx.tender.id %>"><i class="fa fa-pie-chart"></i> <span>标段概况</span></a></li>
+            </ul>
+        </div>
+        <div class="nav-box">
+            <h3><i class="fa fa-list-alt"></i> 0号台帐</h3>
+            <ul class="nav-list list-unstyled sub-list">
+                <li <% if (ctx.url === '/tender/' + ctx.tender.id + '/ledger') { %>class="active"<% } %>><a href="/tender/<%- ctx.tender.id %>/ledger"><span>台帐分解</span></a></li>
+                <li <% if (ctx.url === '/tender/' + ctx.tender.id + '/ledger/audit') { %>class="active"<% } %>><a href="/tender/<%- ctx.tender.id %>/ledger/audit"><span>台帐审批</span></a></li>
+                <li <% if (ctx.url === '/tender/' + ctx.tender.id + '/revise') { %>class="active"<% } %>><a href="/tender/<%- ctx.tender.id %>/revise"><span>台帐修订</span></a></li>
+            </ul>
+        </div>
+        <div class="nav-box">
+            <h3><i class="fa fa-calendar-check-o"></i> 计量台帐</h3>
+            <ul class="nav-list list-unstyled sub-list">
+                <li <% if (ctx.url === '/tender/' + ctx.tender.id + '/measure/stage') { %>class="active"<% } %>><a href="/tender/<%- ctx.tender.id %>/measure/stage"><span>期列表</span></a></li>
+                <li <% if (ctx.url === '/tender/' + ctx.tender.id + '/measure/compare') { %>class="active"<% } %>><a href="/tender/<%- ctx.tender.id %>/measure/compare"><span>多期比较</span></a></li>
+            </ul>
+        </div>
+        <div class="nav-box">
+            <ul class="nav-list list-unstyled">
+                <li <% if (ctx.url === '/tender/' + ctx.tender.id + '/change') { %>class="active"<% } %>><a href="/tender/<%- ctx.tender.id %>/change"><i class="fa fa-retweet"></i> <span>工程变更</span></a></li>
+            </ul>
+        </div>
+        <div class="nav-box">
+            <ul class="nav-list list-unstyled">
+                <li <% if (ctx.url === '/tender/' + ctx.tender.id + '/report') { %>class="active"<% } %>><a href="/tender/<%- ctx.tender.id %>/report"><i class="fa fa-file-text-o"></i> <span>报表</span></a></li>
             </ul>
-            <% } %>
         </div>
-        <% } %>
         <div class="side-fold"><a href="javascript: void(0)" data-toggle="tooltip" data-placement="top" data-original-title="折叠侧栏" id="to-mini-menu"><i class="fa fa-sign-out fa-flip-horizontal"></i></a></div>
     </div>
 </div>

+ 1 - 1
app/view/tender/tender_sub_mini_menu.ejs

@@ -12,7 +12,7 @@
         <div class="nav-box">
             <h3><i class="fa fa-list-alt"></i> 0号台帐</h3>
             <ul class="nav-list list-unstyled sub-list">
-                <li <% if (ctx.url === '/tender/' + ctx.tender.id + '/ledger/explode') { %>class="active"<% } %>><a href="/tender/<%- ctx.tender.id %>/ledger/explode"><span>台帐分解</span></a></li>
+                <li <% if (ctx.url === '/tender/' + ctx.tender.id + '/ledger') { %>class="active"<% } %>><a href="/tender/<%- ctx.tender.id %>/ledger"><span>台帐分解</span></a></li>
                 <li <% if (ctx.url === '/tender/' + ctx.tender.id + '/ledger/audit') { %>class="active"<% } %>><a href="/tender/<%- ctx.tender.id %>/ledger/audit"><span>台帐审批</span></a></li>
                 <li <% if (ctx.url === '/tender/' + ctx.tender.id + '/revise') { %>class="active"<% } %>><a href="/tender/<%- ctx.tender.id %>/revise"><span>台帐修订</span></a></li>
             </ul>

+ 4 - 114
test/app/service/ledger.test.js

@@ -112,7 +112,7 @@ describe('test/app/service/ledger.test.js', () => {
     afterEach(function* () {
         const ctx = app.mockContext(mockData);
 
-        const nodes = yield ctx.service.ledger.getDataByTenderId(ctx.tender.id, -1);
+        const nodes = yield ctx.service.ledger.getData(ctx.tender.id);
         const paths = _.uniqBy(nodes, 'full_path');
         assert(nodes.length === paths.length);
     });
@@ -136,14 +136,14 @@ describe('test/app/service/ledger.test.js', () => {
      */
 
     // 测试R类方法
-    it('test getDataByTenderId', function* () {
+    it('test getData', function* () {
         const ctx = app.mockContext(mockData);
 
         // 查询前4层节点
-        const result1 = yield ctx.service.ledger.getDataByTenderId(ctx.tender.id);
+        const result1 = yield ctx.service.ledger.getData(ctx.tender.id);
         assert(result1.length === 12);
         // 查询前3层节点
-        const result2 = yield ctx.service.ledger.getDataByTenderId(ctx.tender.id, 3);
+        const result2 = yield ctx.service.ledger.getData(ctx.tender.id, 3);
         assert(result2.length === 10);
     });
     it('test getDataByNodeId', function* () {
@@ -500,89 +500,6 @@ describe('test/app/service/ledger.test.js', () => {
         └── 1-4
      */
     // 增量计算
-    it('test updateInfo', function* () {
-        const ctx = app.mockContext(mockData);
-
-        // 修改new(id=17)的code 为 1-1-4
-        const node = yield ctx.service.ledger.getDataByNodeId(ctx.tender.id, 17);
-        assert(node);
-
-        const resultData = yield ctx.service.ledger.updateInfo(ctx.tender.id, {
-            id: node.id,
-            tender_id: node.tender_id,
-            ledger_id: node.ledger_id,
-            code: '1-1-4',
-        });
-        assert(resultData.code === '1-1-4');
-    });
-    /* 期望运行结果:
-        1
-        ├── 1-1
-        │   ├── 1-1-1
-        │   │   └── 202-2
-        │   │       ├── 202-2-c
-        │   │       └── 202-2-e
-        │   └── 1-1-4
-        ├── 1-1-2
-        │   └── 1-1-3
-        ├── 1-2
-        │   ├── 1-2-1
-        │   ├── 1-1-1
-        │   │   └── 202-2
-        │   │       ├── 202-2-c
-        │   │       └── 202-2-e
-        │   ├── new
-        │   └── 1-3
-        │       └── 1-3-1
-        └── 1-4
-     */
-    it('test updateInfos', function* () {
-        const ctx = app.mockContext(mockData);
-
-        // 修改1-1-1(id=18)的code 为 1-2-2、修改new(id=22)的code 为 1-2-3
-        const node1 = yield ctx.service.ledger.getDataByNodeId(ctx.tender.id, 18);
-        assert(node1);
-        const node2 = yield ctx.service.ledger.getDataByNodeId(ctx.tender.id, 22);
-        assert(node2);
-
-        const resultData = yield ctx.service.ledger.updateInfos(ctx.tender.id, [{
-            id: node1.id,
-            tender_id: node1.tender_id,
-            ledger_id: node1.ledger_id,
-            code: '1-2-2',
-        }, {
-            id: node2.id,
-            tender_id: node2.tender_id,
-            ledger_id: node2.ledger_id,
-            code: '1-2-3',
-        }]);
-        assert(resultData.length === 2);
-        let node = _.find(resultData, {id: node1.id});
-        assert(node.code === '1-2-2');
-        node = _.find(resultData, {id: node2.id});
-        assert(node.code === '1-2-3');
-    });
-    /* 期望运行结果:
-        1
-        ├── 1-1
-        │   ├── 1-1-1
-        │   │   └── 202-2
-        │   │       ├── 202-2-c
-        │   │       └── 202-2-e
-        │   └── 1-1-4
-        ├── 1-1-2
-        │   └── 1-1-3
-        ├── 1-2
-        │   ├── 1-2-1
-        │   ├── 1-2-2
-        │   │   └── 202-2
-        │   │       ├── 202-2-c
-        │   │       └── 202-2-e
-        │   ├── 1-2-3
-        │   └── 1-3
-        │       └── 1-3-1
-        └── 1-4
-     */
     it('test updateCalc - update 1', function* () {
         const ctx = app.mockContext(mockData);
         // 计算需使用清单精度、小数位数
@@ -1160,33 +1077,6 @@ describe('test/app/service/ledger.test.js', () => {
         //assert(result3 && result3.toFixed(8) == 60.00007146);
     });
 
-    // 测试搜索类方法
-    it('test search', function* () {
-        const ctx = app.mockContext(mockData);
-
-        const result = yield ctx.service.ledger.search(ctx.tender.id, {
-            value: app.mysql.escape('%1-3%'),
-            operate: 'Like',
-            fields: ['code', 'name'],
-        });
-        assert(result.length === 3);
-    });
-    it('test searchRange', function* () {
-        const ctx = app.mockContext(mockData);
-        const result = yield ctx.service.ledger.searchRange(ctx.tender.id, {
-            value: app.mysql.escape('%1-3%'),
-            operate: 'Like',
-            fields: ['code', 'name'],
-        }, [{
-            type: 'And',
-            value: app.mysql.escape('1.13.%'),
-            operate: 'Like',
-            fields: ['full_path'],
-        }]);
-        assert(result.length === 1);
-        assert(result[0].code === '1-1-3');
-    });
-
     // 测试导入excel 在\lib\analysis_excel.test.js中进行单元测试
 
     // 小数位数策略示例:

+ 1 - 1
test/app/service/tender.test.js

@@ -60,7 +60,7 @@ describe('test/app/service/tender.test.js', () => {
         assert(data);
         testTenderId = data.id;
 
-        const ledger = yield ctx.service.ledger.getDataByTenderId(testTenderId, -1);
+        const ledger = yield ctx.service.ledger.getData(testTenderId);
         assert(ledger.length === 53);
 
         const payNodes = yield ctx.service.pay.getAllDataByCondition({where: {tid: testTenderId}});