Просмотр исходного кода

Merge branch '1.0.0_online' of http://192.168.1.41:3000/SmartCost/ConstructionCost into 1.0.0_online

TonyKang 6 лет назад
Родитель
Сommit
edb487fc46

+ 3 - 2
modules/pm/controllers/pm_controller.js

@@ -495,10 +495,11 @@ module.exports = {
     },
 
     recGC: function(request, response){
-        let userID = request.session.sessionUser.id;
+        let userID = request.session.sessionUser.id,
+            compilationId = request.session.sessionCompilation._id;
         let data = JSON.parse(request.body.data);
         let nodes = data.nodes;
-        ProjectsData.recGC(userID, nodes, function (err, msg, data) {
+        ProjectsData.recGC(userID, compilationId, nodes, function (err, msg, data) {
             callback(request, response, err, msg, data);
         });
    },

+ 21 - 7
modules/pm/facade/pm_facade.js

@@ -666,7 +666,9 @@ function getBuildingArea(projFeature){
 async function getSummaryInfoByTender(tenderID, summaryType) {
     const notDeleted = [{deleteInfo: null}, {'deleteInfo.deleted': false}];
     let tender = await projectModel.findOne({ID: tenderID, $or: notDeleted});
-    let parentName;
+    let parent,
+        parentName,
+        compilationIllustration;
     let summaryList = [];
     if(!tender){
         return null;
@@ -680,8 +682,8 @@ async function getSummaryInfoByTender(tenderID, summaryType) {
         return null;
     }
     let summaryInfo = await getSummaryInfo([project.ID]);
-    if(summaryType === projectType.engineering){
-        parentName = engineering.name ? engineering.name : '';
+    if(summaryType === projectType.engineering){ // 汇总到单项工程级别
+        parent = engineering;
         let tenders = await projectModel.find({ParentID: engineering.ID, $or: notDeleted});
         for(let t of tenders){
             if(summaryInfo[t.ID]){
@@ -689,9 +691,8 @@ async function getSummaryInfoByTender(tenderID, summaryType) {
                 summaryList.push(summaryInfo[t.ID]);
             }
         }
-    }
-    else {
-        parentName = project.name ? project.name : '';
+    } else { // 汇总到建设项目级别
+        parent = project;
         let engs = await projectModel.find({ParentID: project.ID, $or: notDeleted});
         for(let e of engs){
             if(summaryInfo[e.ID]){
@@ -700,7 +701,20 @@ async function getSummaryInfoByTender(tenderID, summaryType) {
             }
         }
     }
-    return {parent: {name: parentName}, subList: summaryList};
+    // 项目名称
+    parentName = parent && parent.name
+                    ? parent.name
+                    : '';
+    // 编制说明
+    compilationIllustration = parent && parent.property && parent.property.compilationIllustration
+                                ? parent.property.compilationIllustration
+                                : '';
+    return {
+        parent: {
+            name: parentName,
+            compilationIllustration: compilationIllustration},
+        subList: summaryList
+    };
 }
 
 //获取单位工程的各标段费用信息(不进行汇总)

+ 4 - 4
modules/pm/models/project_model.js

@@ -497,8 +497,8 @@ ProjectsDAO.prototype.getGCFiles = async function (fileType, userID) {
     return rst;
 };
 
-ProjectsDAO.prototype.getFirstNodeID = async function (userID, pid) {
-    let nodes = await Projects.find({userID: userID, ParentID: pid, deleteInfo: null});
+ProjectsDAO.prototype.getFirstNodeID = async function (userID, compilationId, pid) {
+    let nodes = await Projects.find({userID: userID, compilation: compilationId, ParentID: pid, deleteInfo: null});
     if (nodes.length === 0) {
         return -1;
     }
@@ -529,7 +529,7 @@ ProjectsDAO.prototype.getFirstNodeID = async function (userID, pid) {
     }
 };
 
-ProjectsDAO.prototype.recGC = async function (userID, datas, callback) {
+ProjectsDAO.prototype.recGC = async function (userID, compilationId, datas, callback) {
     let functions = [];
     let updateDatas = [];
     for (let i = 0, len = datas.length; i < len; i++) {
@@ -552,7 +552,7 @@ ProjectsDAO.prototype.recGC = async function (userID, datas, callback) {
                         datas[i].updateData.ParentID = -1;
                     }
                 }
-                let firstNodeID = await this.getFirstNodeID(userID, projPid);
+                let firstNodeID = await this.getFirstNodeID(userID, compilationId, projPid);
                 datas[i].updateData.NextSiblingID = firstNodeID;
             }
             updateDatas.push(datas[i])

+ 40 - 0
public/web/id_tree.js

@@ -758,6 +758,46 @@ var idTree = {
             let node = this.nodes[this.prefix+ID];
             return node;
         };
+        //检查树结构数据有没问题
+        Tree.prototype.check = function (roots) {
+            return isValid(roots);
+            function isValid(nodes) {
+                for (let node of nodes) {
+                    if (node.data.ParentID !== -1 &&
+                        (!node.parent || node.parent.data.ID !== node.data.ParentID)) {
+                        console.log(`${node.serialNo() + 1}:${node.data.name} parent对应错误`);
+                        return false;
+                    }
+                    if (node.data.ParentID === -1 && node.parent) {
+                        console.log(`${node.serialNo() + 1}:${node.data.name} 不应有parent`);
+                        return false;
+                    }
+                    if (node.data.NextSiblingID !== -1 &&
+                        (!node.nextSibling || node.nextSibling.data.ID !== node.data.NextSiblingID)) {
+                        console.log(`${node.serialNo() + 1}:${node.data.name} next对应错误`);
+                        return false;
+                    }
+                    if (node.data.NextSiblingID === -1 && node.nextSibling) {
+                        console.log(`${node.serialNo() + 1}:${node.data.name} 不应有next`);
+                        return false;
+                    }
+                    let sameDepthNodes = node.parent ? node.parent.children : roots,
+                        nodeIdx = sameDepthNodes.indexOf(node),
+                        nextIdx = sameDepthNodes.indexOf(node.nextSibling);
+                    if (nodeIdx !== -1 && nextIdx !== -1 && nodeIdx > nextIdx) {
+                        console.log(`${node.serialNo() + 1}:${node.data.name} node索引大于next索引`);
+                        return false;
+                    }
+                    if (node.children.length) {
+                        let v = isValid(node.children);
+                        if (!v) {
+                            return false;
+                        }
+                    }
+                }
+                return true;
+            }
+        };
         return new Tree(setting);
     },
     updateType: {update: 'update', new: 'new', delete: 'delete'}

+ 14 - 0
web/building_saas/css/custom.css

@@ -326,4 +326,18 @@ input.text-right{
 .export-check{
     overflow: auto;
     height: 400px;
+}
+.shake-input {
+    animation: shake-input 0.2s 4;
+}
+@keyframes shake-input {
+    0% {
+        transform: translateX(0);
+    }
+    30% {
+        transform: translateX(-2%);
+    }
+    100% {
+        transform: translateX(4%)
+    }
 }

+ 12 - 1
web/building_saas/js/global.js

@@ -154,4 +154,15 @@ function getFeeIndex(fees) {
 function replaceAll(FindText, RepText,str) {
     let regExp = new RegExp(FindText, "g");
     return str.replace(regExp, RepText);
-};
+};
+
+function setTimeoutSync(handle, time) {
+    return new Promise(function(resolve, reject) {
+        setTimeout(function () {
+            if (handle && typeof handle === 'function') {
+                handle();
+            }
+            resolve();
+        }, time);
+    });
+}

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

@@ -97,13 +97,15 @@
                             <a id="uploadGld" class="dropdown-item" href="#import" data-toggle="modal" data-target="#import">导入广联达算量Excel清单</a>
                         </div>
                     </span>
+                      <% if (compilationName === '重庆定额(2018)') { %>
                       <span id="exportSpan" class="btn btn-light btn-sm" data-toggle="tooltip" data-original-title="数据接口" data-placement="bottom">
                     <a class="dropdown-toggle" href="#" data-toggle="dropdown"><i class="fa  fa-code-fork"></i></a>
                     <div class="dropdown-menu" id="exportMenu">
-                        <a class="dropdown-item" href="#export" data-toggle="modal" data-target="#export">重庆市电子招投标数据接口</a>
+                        <a class="dropdown-item" href="#export" data-toggle="modal" data-target="#export">导出重庆市电子招投标数据文件</a>
                         <a class="dropdown-item" href="javascript:void(0)" id="SEIMenu"  >导出重庆市指标成果文件</a>
                     </div>
                 </span>
+                      <% } %>
                     <a href="javascript:void(0)" class="btn btn-light btn-sm" id="insertRation" data-toggle="tooltip" data-placement="bottom" data-original-title="插入定额"><i class="fa fa-sign-in" aria-hidden="true"></i></a>
                     <!--2018-11-15 隐藏删除按钮   <a href="javascript:void(0)" class="btn btn-light btn-sm" id="delete" data-toggle="tooltip" data-placement="bottom" data-original-title="删除"><i class="fa fa-remove" aria-hidden="true"></i></a>-->
                     <a href="javascript:void(0)" class="btn btn-light btn-sm" id="upLevel" data-toggle="tooltip" data-placement="bottom" data-original-title="升级"><i class="fa fa-arrow-left" aria-hidden="true"></i></a>

+ 8 - 3
web/building_saas/main/js/models/calc_program.js

@@ -953,9 +953,14 @@ let calcTools = {
      },
     getFeeRateByNode(node){
         let decimal = getDecimal("feeRate");
-        if(node.data.feeRateID) return scMathUtil.roundForObj(projectObj.project.FeeRate.getFeeRateByID(node.data.feeRateID).rate,decimal);
-        if (node.data.feeRate || node.data.feeRate == 0) return scMathUtil.roundForObj(node.data.feeRate,decimal);
-        return 100
+        if(node.data.feeRateID) {
+            let r = projectObj.project.FeeRate.getFeeRateByID(node.data.feeRateID);
+            if (r) return scMathUtil.roundForObj(r.rate, decimal);
+        };
+        if (node.data.feeRate || node.data.feeRate == 0)
+            return scMathUtil.roundForObj(node.data.feeRate,decimal);
+
+        return 100;
     }
 };
 

+ 6 - 1
web/building_saas/main/js/models/exportSEIInterface.js

@@ -213,6 +213,7 @@ async function exportSEI(projectID) {
     async function prepareTenderDatas(tenders,project) {
         for(let t of tenders){
             await setTenderData(t,project);
+            await setTimeoutSync(null,500);
         }
 
 
@@ -437,8 +438,9 @@ async function exportSEI(projectID) {
 
         function getFeatrue(node,parentMap){
             if(parentMap[node.ID]){//如果有子节点,那么它就是一个节点
+                let name = node.exportName?node.exportName:node.name;
                 let tem = {
-                    name:node.name.replace("*",""),
+                    name:name.replace("*",""),
                     attrs:[],
                     children:[]
                 };
@@ -448,6 +450,9 @@ async function exportSEI(projectID) {
                 }
                 return tem;
             }else {//如果没有子节点,那么它就是父节点的一个属性
+                if(node.isDetail == true){//如果是明细节点,则造一个明细节点
+                    return {name:"明细",attrs:[{name:"名称",value:node.value}],children:[]};
+                }
                 return {name:node.name.replace("*",""),value:node.value};
             }
         }

+ 15 - 16
web/building_saas/main/js/models/exportStandardInterface.js

@@ -353,7 +353,6 @@ const XMLStandard = (function () {
         //招标信息定义
         function BiddingInfo(source) {
             //控制总价: 如果文件类型是“控制价”,则导出建设项目的工程造价;如果是“招标”、“投标”,则取0
-            console.log(exportKind);
             let attrs = [
                 {name: '招标代理机构', value: getValueByKey(source.basicInformation, 'agency')},
                 {name: '造价工程师', value: getValueByKey(source.basicInformation, 'tenderCostEngineer'), required: true},
@@ -397,19 +396,19 @@ const XMLStandard = (function () {
         //费用构成定义
         function FeeFrom(summaryInfo) {
             let attrs = [
-                {name: '工程费合计', value: summaryInfo.engineeringCost, required: true, type: TYPE.NUM2},
-                {name: '分部分项清单合计', value: summaryInfo.subEngineering, required: true, type: TYPE.NUM2},
-                {name: '措施项目清单合计', value: summaryInfo.measure, required: true, type: TYPE.NUM2},
-                {name: '安全文明施工专项费', value: summaryInfo.safetyConstruction, required: true, type: TYPE.NUM2},
-                {name: '其他项目清单合计', value: summaryInfo.other, required: true, type: TYPE.NUM2},
-                {name: '暂列金额合计', value: summaryInfo.provisional, type: TYPE.NUM2},
-                {name: '材料暂估价合计', value: summaryInfo.materialProvisional, type: TYPE.NUM2},
-                {name: '专业工程暂估价合计', value: summaryInfo.engineeringEstimate, type: TYPE.NUM2},
-                {name: '计日工合计', value: summaryInfo.daywork, type: TYPE.NUM2},
-                {name: '总承包服务费合计', value: summaryInfo.turnKeyContract, type: TYPE.NUM2},
-                {name: '签证索赔合计', value: summaryInfo.claimVisa, type: TYPE.NUM2},
-                {name: '规费', value: summaryInfo.charge, required: true, type: TYPE.NUM2},
-                {name: '税金', value: summaryInfo.tax, required: true, type: TYPE.NUM2},
+                {name: '工程费合计', value: exportKind === ExportKind.Tender ? summaryInfo.engineeringCost : '0', required: true, type: TYPE.NUM2},
+                {name: '分部分项清单合计', value: exportKind === ExportKind.Tender ? summaryInfo.subEngineering : '0', required: true, type: TYPE.NUM2},
+                {name: '措施项目清单合计', value: exportKind === ExportKind.Tender ? summaryInfo.measure : '0', required: true, type: TYPE.NUM2},
+                {name: '安全文明施工专项费', value: exportKind === ExportKind.Tender ? summaryInfo.safetyConstruction : '0', required: true, type: TYPE.NUM2},
+                {name: '其他项目清单合计', value: exportKind === ExportKind.Tender ? summaryInfo.other : '0', required: true, type: TYPE.NUM2},
+                {name: '暂列金额合计', value: exportKind === ExportKind.Tender ? summaryInfo.provisional : '0', type: TYPE.NUM2},
+                {name: '材料暂估价合计', value: exportKind === ExportKind.Tender ? summaryInfo.materialProvisional : '0', type: TYPE.NUM2},
+                {name: '专业工程暂估价合计', value: exportKind === ExportKind.Tender ? summaryInfo.engineeringEstimate : '0', type: TYPE.NUM2},
+                {name: '计日工合计', value: exportKind === ExportKind.Tender ? summaryInfo.daywork : '0', type: TYPE.NUM2},
+                {name: '总承包服务费合计', value: exportKind === ExportKind.Tender ? summaryInfo.turnKeyContract : '0', type: TYPE.NUM2},
+                {name: '签证索赔合计', value: exportKind === ExportKind.Tender ? summaryInfo.claimVisa : '0', type: TYPE.NUM2},
+                {name: '规费', value: exportKind === ExportKind.Tender ? summaryInfo.charge : '0', required: true, type: TYPE.NUM2},
+                {name: '税金', value: exportKind === ExportKind.Tender ? summaryInfo.tax : '0', required: true, type: TYPE.NUM2},
             ];
             element.call(this, '费用构成', attrs);
         }
@@ -1407,8 +1406,8 @@ const XMLStandard = (function () {
             //单项工程编号要唯一
             checkUnique(curProjectEle.constraints.engCode, source.code, '单项工程编号');
             //费用构成
-            /*let feeForm = new FeeFrom(summaryInfo[engData.ID]);
-            engineering.children.push(feeForm);*/
+            let feeForm = new FeeFrom(summaryInfo[engData.ID]);
+            engineering.children.push(feeForm);
             //分批次获取单位工程
             for (let tenderData of engData.children) {
                 curPMData.tender = tenderData;

+ 3 - 2
web/building_saas/main/js/models/importStandardInterface.js

@@ -1064,10 +1064,11 @@ const ImportXML = (() => {
                     zzcsSubs.push(item);
                 }
             }
+            addFixedBlock(fixedFlag.CONSTRUCTION_ORGANIZATION, zzcsSubs, tenderData.csxm.zzcs.fees);
+            //更新原本的最后一个节点
             if (zzcsSubs.length && lastItem) {
-                zzcsSubs[0].NextSiblingID = lastItem.ID;
+                lastItem.NextSiblingID = zzcsSubs[0].ID;
             }
-            addFixedBlock(fixedFlag.CONSTRUCTION_ORGANIZATION, zzcsSubs, tenderData.csxm.zzcs.fees);
             //技术措施
             addFixedBlock(fixedFlag.CONSTRUCTION_TECH, tenderData.csxm.jscs.items, tenderData.csxm.jscs.fees);
             //暂列金额

+ 2 - 0
web/building_saas/pm/html/project-management.html

@@ -6,6 +6,7 @@
     <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
     <meta http-equiv="x-ua-compatible" content="ie=edge">
     <title>项目管理-纵横建筑计价</title>
+    <link rel="stylesheet" href="/lib/jquery-ui/jquery-ui.css" type="text/css">
     <link rel="stylesheet" href="/lib/bootstrap/css/bootstrap.min.css">
     <link rel="stylesheet" href="/web/building_saas/css/main.css">
     <link rel="stylesheet" href="/web/building_saas/css/custom.css">
@@ -910,6 +911,7 @@
 <script src = "/lib/spreadjs/sheets/gc.spread.sheets.all.11.1.2.min.js"></script>
 <script>GC.Spread.Sheets.LicenseKey = '<%- LicenseKey %>';</script>
 <script src="/lib/x2js/xml2json.min.js"></script>
+<script type="text/javascript" src="/lib/jquery-ui/jquery-ui-datepickerCN.js"></script>
 <script type="text/javascript">
     let billValuation = JSON.parse('<%- billValuation %>');
     let rationValuation = JSON.parse('<%- rationValuation %>');

+ 21 - 12
web/building_saas/pm/js/pm_newMain.js

@@ -216,7 +216,6 @@ const projTreeObj = {
             this.bindEvent(newWorkBook);
             this.loadContextMenu();
             this.loadStartMenu();
-            this.loadBtn();
         }
         return newWorkBook;
     },
@@ -356,8 +355,11 @@ const projTreeObj = {
             }
         },
         importInterface: {
-            name: '导入标接口',
+            name: '导入招投标接口文件',
             icon: 'fa-cloud-upload',
+            visible: function () {
+               return compilationData && compilationData.name === '重庆定额(2018)';
+            },
             callback: function () {
                 $('#importInterface').modal('show');
             }
@@ -1238,7 +1240,6 @@ const projTreeObj = {
         $("#mr_title").text(title);
         this.initHtmlByFromRadio();
         let result = from == "unitPriceFile"?await this.setUnitFileChangeFileData():await this.setFeeRateChangeFileData();
-        console.log(result);
         this.changeInfo = result;
         this.loadFileOptions(this.changeInfo,from);
 
@@ -1445,8 +1446,6 @@ const projTreeObj = {
         //发送文件改变消息
         data.oldFileIDs = _.uniq(oldFileIDs);
         socket.emit('multiFileChangeNotify', data);
-
-        console.log(result);
     },
     checkFileName:async function (fileID,name,type) {
         let node = this.tree.selected;
@@ -1579,6 +1578,8 @@ const projTreeObj = {
 
 };
 $(document).ready(function() {
+    //绑定项目管理的升降级、上下移按钮事件
+    projTreeObj.loadBtn();
     $('#sideTab').find('li').click(function () {
        //消除tooltip
         $('[data-toggle="tooltip"]').tooltip('hide');
@@ -1685,7 +1686,6 @@ $(document).ready(function() {
                 infoData = await ajaxPost('/pm/api/getBasicInfo', {user_id: userID});
                 //第一次默认是投标
                 needfulInfoData = getNeedfulBasicInfo(infoData, fileKind);
-                console.log(infoData);
             } catch (err) {
                 $('#add-project-dialog').modal('hide');
             }
@@ -1811,7 +1811,6 @@ $(document).ready(function() {
         curValuation = $(this).val();
         curValuationName = $(this).text();
         let engineeringList = getEngineeringList();
-        console.log(engineeringList);
         let engineeringHtml = getEngineeringHtml(engineeringList);
         $("#tender-engineering").html(engineeringHtml);
         $('#valuation-info').hide();
@@ -2158,6 +2157,7 @@ $(document).ready(function() {
     }
     //根据基本信息、工程特征数据获取对应节点
     function getFormRow(data) {
+        let dateReg = /([0-9]{3}[1-9]|[0-9]{2}[1-9][0-9]{1}|[0-9]{1}[1-9][0-9]{2}|[1-9][0-9]{3})-(((0[13578]|1[02])-(0[1-9]|[12][0-9]|3[01]))|((0[469]|11)-(0[1-9]|[12][0-9]|30))|(02-(0[1-9]|[1][0-9]|2[0-8])))/;
         let cellType = data.cellType ? data.cellType : 'text';
         let $label = $(`<label for="staticEmail" class="col-3 col-form-label col-form-label-sm">${data.dispName}</label>`),
             $cell;
@@ -2167,6 +2167,15 @@ $(document).ready(function() {
             for (let opt of opts) {
                 $cell.append($(`<option ${data.value === opt ? 'selected' : ''} value="${opt}">${opt}</option>`));
             }
+        } else if (cellType === 'date') {   //不用原生的日期控件
+            $cell = $(`<input type="pm-${cellType}" name="${data.key}"
+                       value="${data.value}" class="form-control form-control-sm" placeholder="请输入">`);
+            $cell.datepicker({dateFormat: 'yy-mm-dd'});
+            $cell.change(function () {
+                if (!dateReg.test($($cell).val())) {
+                    $($cell).val('');
+                }
+            });
         } else {
             $cell = $(`<input type="${cellType}" ${cellType === 'number' ? 'min="0"' : ''} name="${data.key}"
                        value="${data.value}" class="form-control form-control-sm" placeholder="请输入">`);
@@ -2179,12 +2188,15 @@ $(document).ready(function() {
         return $row;
     }
 
-    //验证必填信息
+    //验证必填信息.
     function validRequiredData($form) {
         let eles = $form.find('input, select');
         for (let ele of eles) {
+            $(ele).removeClass('shake-input');
             let v = $(ele).val().trim();
             if (!v || v === '请选择') {
+                $(ele).focus();
+                $(ele).addClass('shake-input');
                 return false;
             }
         }
@@ -2448,7 +2460,6 @@ $(document).ready(function() {
         $("#copy-to-dialog").modal('hide');
         $.bootstrapLoading.start();
         CommonAjax.post('/pm/api/copyProjects',{projectMap:projectMap,user_id: userID},function (result) {
-            console.log(result);
             let newNode = projTreeObj.insert(result['copy'].document,parent,next);
             let refreshNodes = projTreeObj.calEngineeringCost(newNode);
             projTreeObj.refreshNodeData(refreshNodes);
@@ -2472,7 +2483,6 @@ $(document).ready(function() {
     $('#tender-feeStandard').change(function () {
        changeFeeStandard();
     });
-
     //导入相关
     importView.eventListen();
 });
@@ -4464,7 +4474,6 @@ function setShareToModal(selected){
         userIDs.push(user.userID);
     }
     CommonAjax.post('/user/getUsers', {userIDs: userIDs}, function (rstData) {
-        console.log(rstData);
         for(let userInfo of rstData){
             for(let user of selected.data.shareInfo){
                 if(user.userID === userInfo._id){
@@ -4612,4 +4621,4 @@ function refreshProjSummary(project, summaryInfo) {
         }
     }
     projTreeObj.refreshNodeData(refreshNodes);
-}
+}

+ 42 - 3
web/building_saas/pm/js/pm_tree.js

@@ -272,7 +272,10 @@ const pmTree = {
                     }
                     that.maxNodeId(node.id());
                     if(parent.childIndex(node) === -1){
-                        if(pre && parent.childIndex(pre) !== -1){
+                        if (!pre) {
+                            parent.children.unshift(node);
+                        }
+                        else if(pre && parent.childIndex(pre) !== -1){
                             parent.children.splice(parent.childIndex(pre) + 1, 0, node);
                         }
                         else if(next && parent.childIndex(next) !== -1){
@@ -371,8 +374,44 @@ const pmTree = {
 
             };
 
-            Tree.prototype.refreshData = function () {
-
+            Tree.prototype.check = function (_root) {
+                return isValid(_root.children);
+                function isValid(nodes) {
+                    for (let node of nodes) {
+                        if (node.data.ParentID !== -1 &&
+                            (!node.parent || node.parent.data.ID !== node.data.ParentID)) {
+                            console.log(`${node.serialNo() + 1}:${node.data.name} parent对应错误`);
+                            return false;
+                        }
+                        if (node.data.ParentID === -1 && node.parent && node.parent !== _root) {
+                            console.log(`${node.serialNo() + 1}:${node.data.name} 不应有parent`);
+                            return false;
+                        }
+                        if (node.data.NextSiblingID !== -1 &&
+                            (!node.nextSibling || node.nextSibling.data.ID !== node.data.NextSiblingID)) {
+                            console.log(`${node.serialNo() + 1}:${node.data.name} next对应错误`);
+                            return false;
+                        }
+                        if (node.data.NextSiblingID === -1 && node.nextSibling) {
+                            console.log(`${node.serialNo() + 1}:${node.data.name} 不应有next`);
+                            return false;
+                        }
+                        let parent = node.parent,
+                            nodeIdx = parent.children.indexOf(node),
+                            nextIdx = parent.children.indexOf(node.nextSibling);
+                        if (nodeIdx !== -1 && nextIdx !== -1 && nodeIdx > nextIdx) {
+                            console.log(`${node.serialNo() + 1}:${node.data.name} node索引大于next索引`);
+                            return false;
+                        }
+                        if (node.children.length) {
+                            let v = isValid(node.children);
+                            if (!v) {
+                                return false;
+                            }
+                        }
+                    }
+                    return true;
+                }
             };
 
             return Tree;