'use strict';
/**
 *
 *
 * @author Zhong
 * @date 2018/6/1
 * @version
 */




const billsGuidance = (function () {


    function sortByCode(arr) {
        function recurCompare(a, b, index) {
            if (a[index] && !b[index]) {
                return 1;
            } else if (!a[index] && b[index]) {
                return -1;
            } else if (a[index] && b[index]) {
                let aV = a[index],
                    bV = b[index];
                if (!isNaN(aV) && !isNaN(bV)) {
                    aV = parseFloat(a[index]);
                    bV = parseFloat(b[index]);
                }
                if (aV > bV) {
                    return 1;
                } else if (aV < bV) {
                    return -1;
                } else {
                    return recurCompare(a, b, index + 1);
                }
            }
            return 0;
        }
        arr.sort(function (a, b) {
            if (!_isDef(a.code) || !_isDef(b.code)) {
                return 0;
            }
            let aArr = a.code.split('-'),
                bArr = b.code.split('-');
            return recurCompare(aArr, bArr, 0);
        });
    }
    // 导出实例
    let curCompilationID = '';
    const locked = lockUtil.getLocked();
    let moduleName = 'stdBillsGuidance';
    //上下拖动的拖动条高度
    const verticalResize = 10;
    //自执行函数全局变量定义
    const libID = getQueryString('libID');
    //总工作内容数据
    let stdBillsJobData = [];
    //总项目特征数据
    let stdBillsFeatureData = [];
    const bills = {
        dom: $('#billsSpread'),
        workBook: null,
        cache: [],
        tree: null,
        controller: null,
        treeSetting: {
            treeCol: 0,
            emptyRows: 0,
            headRows: 1,
            headRowHeight: [40],
            defaultRowHeight: 21,
            cols: [{
                width: 200,
                readOnly: true,
                showHint: true,
                head: {
                    titleNames: ["项目编码"],
                    spanCols: [1],
                    spanRows: [1],
                    vAlign: [1],
                    hAlign: [1],
                    font: ["Arial"]
                },
                data: {
                    field: "code",
                    vAlign: 1,
                    hAlign: 0,
                    font: "Arial"
                }
            }, {
                width: 200,
                readOnly: true,
                head: {
                    titleNames: ["项目名称"],
                    spanCols: [1],
                    spanRows: [1],
                    vAlign: [1],
                    hAlign: [1],
                    font: ["Arial"]
                },
                data: {
                    field: "name",
                    vAlign: 1,
                    hAlign: 0,
                    font: "Arial"
                }
            }]
        },
        headers: [
            { name: '项目编码', dataCode: 'code', width: 200, vAlign: 'center', hAlign: 'left', formatter: '@' },
            { name: '项目名称', dataCode: 'name', width: 200, vAlign: 'center', hAlign: 'left', formatter: '@' }
        ],
        events: {
            SelectionChanged: function (sender, info) {
                billsInitSel(info.newSelections[0].row, info.oldSelections[0]);
            }
        }
    };
    const selectedBgColor = '#DFE8F9';
    const searchBgColor = 'lemonChiffon';

    //项目指引复制整块localStorage key
    const itemCopyBlockKey = 'guideItemCopyBlock';
    const updateType = {
        create: 'create',
        update: 'update',
        del: 'delete'
    };
    //项目指引节点状态：展开全部、收起定额
    const itemExpandState = {
        expand: 1,
        contract: 0
    };
    //项目指引当前节点展开收缩状态，默认展开全部
    let curExpandState = 1;
    const guideItem = {
        dom: $('#guideItemSpread'),
        workBook: null,
        tree: null,
        controller: null,
        treeSetting: {
            treeCol: 0,
            emptyRows: 0,
            headRows: 1,
            headRowHeight: [40],
            defaultRowHeight: 21,
            cols: [{
                width: 400,
                readOnly: locked,
                head: {
                    titleNames: ["项目指引"],
                    spanCols: [1],
                    spanRows: [1],
                    vAlign: [1],
                    hAlign: [1],
                    font: ["Arial"]
                },
                data: {
                    field: "name",
                    vAlign: 1,
                    hAlign: 0,
                    font: "Arial",
                    formatter: "@"
                }
            },
            {
                width: 40,
                readOnly: true,
                head: {
                    titleNames: ["输出特征"],
                    spanCols: [1],
                    spanRows: [1],
                    vAlign: [1],
                    hAlign: [1],
                    font: ["Arial"]
                },
                data: {
                    field: "outputItemCharacter",
                    vAlign: 1,
                    hAlign: 1,
                    font: "Arial"
                }
            },
            {
                width: 30,
                readOnly: true,
                head: {
                    titleNames: ["必填"],
                    spanCols: [1],
                    spanRows: [1],
                    vAlign: [1],
                    hAlign: [1],
                    font: ["Arial"]
                },
                data: {
                    field: "required",
                    vAlign: 1,
                    hAlign: 1,
                    font: "Arial"
                }
            },
            {
                width: 30,
                readOnly: true,
                head: {
                    titleNames: ["材料"],
                    spanCols: [1],
                    spanRows: [1],
                    vAlign: [1],
                    hAlign: [1],
                    font: ["Arial"]
                },
                data: {
                    field: "isMaterial",
                    vAlign: 1,
                    hAlign: 1,
                    font: "Arial"
                }
            },
            {
                width: 30,
                readOnly: true,
                head: {
                    titleNames: ["默认"],
                    spanCols: [1],
                    spanRows: [1],
                    vAlign: [1],
                    hAlign: [1],
                    font: ["Arial"]
                },
                data: {
                    field: "isDefaultOption",
                    vAlign: 1,
                    hAlign: 1,
                    font: "Arial"
                }
            },
            {
                width: 40,
                readOnly: locked,
                head: {
                    titleNames: ["单位"],
                    spanCols: [1],
                    spanRows: [1],
                    vAlign: [1],
                    hAlign: [1],
                    font: ["Arial"]
                },
                data: {
                    field: "unit",
                    vAlign: 1,
                    hAlign: 1,
                    font: "Arial",
                    formatter: "@"
                }
            },
            {
                width: 80,
                readOnly: locked,
                head: {
                    titleNames: ["区间"],
                    spanCols: [1],
                    spanRows: [1],
                    vAlign: [1],
                    hAlign: [1],
                    font: ["Arial"]
                },
                data: {
                    field: "interval",
                    vAlign: 1,
                    hAlign: 1,
                    font: "Arial",
                    formatter: "@"
                }
            },
            ]
        },
        headers: [
            { name: '项目指引', dataCode: 'name', width: 400, vAlign: 'center', hAlign: 'left', formatter: '@' },
            { name: '输出特征', dataCode: 'outputItemCharacter', width: 40, vAlign: 'center', hAlign: 'center' },
            { name: '必填', dataCode: 'required', width: 30, vAlign: 'center', hAlign: 'center' },
            { name: '材料', dataCode: 'isMaterial', width: 30, vAlign: 'center', hAlign: 'center' },
            { name: '默认', dataCode: 'isDefaultOption', width: 30, vAlign: 'center', hAlign: 'center' },
            { name: '单位', dataCode: 'unit', width: 40, vAlign: 'center', hAlign: 'center', formatter: '@' },
            { name: '区间', dataCode: 'interval', width: 80, vAlign: 'center', hAlign: 'center', formatter: '@' },
        ],
        events: {
            SelectionChanged: function (sender, info) {
                guideItemInitSel(info.newSelections[0].row)
            },
            EditEnded: function (sender, args) {
                edit(args.sheet, [{ row: args.row, col: args.col }]);
            },
            ButtonClicked: function (sender, args) {
                edit(args.sheet, [{ row: args.row, col: args.col }]);
            },
            RangeChanged: function (sender, args) {
                edit(args.sheet, args.changedCells);
            },
            CellDoubleClick: function (sender, args) {
                locateAtRation(args.row);
            },
            EnterCell: function (sender, args) {
                console.log(args.row);
            }
        }
    };
    //定额章节树
    const section = {
        dom: $('#sectionSpread'),
        workBook: null,
        cache: [],
        tree: null,
        controller: null,
        treeSetting: {
            treeCol: 0,
            emptyRows: 0,
            headRows: 1,
            headRowHeight: [40],
            defaultRowHeight: 21,
            cols: [{
                width: 400,
                readOnly: true,
                head: {
                    titleNames: ["名称"],
                    spanCols: [1],
                    spanRows: [1],
                    vAlign: [1],
                    hAlign: [1],
                    font: ["Arial"]
                },
                data: {
                    field: "name",
                    vAlign: 1,
                    hAlign: 0,
                    font: "Arial"
                }
            }]
        },
        headers: [
            { name: '名称', dataCode: 'name', width: 400, vAlign: 'center', hAlign: 'left', formatter: '@' },
        ],
        events: {
            SelectionChanged: function (sender, info) {
                sectionInitSel(info.newSelections[0].row)
            }
        }
    };

    const ration = {
        dom: $('#rationSpread'),
        workBook: null,
        datas: [],//所有的数据,搜索定额时，从所有数据中筛选
        cache: [],//显示在表格上的数据，添加定额可以有效根据行识别定额
        headers: [
            { name: '选择', dataCode: 'select', width: 50, vAlign: 'center', hAlign: 'center' },
            { name: '编码', dataCode: 'code', width: 110, vAlign: 'center', hAlign: 'left', formatter: '@' },
            { name: '名称', dataCode: 'name', width: 250, vAlign: 'center', hAlign: 'left', formatter: '@' },
            { name: '单位', dataCode: 'unit', width: 100, vAlign: 'center', hAlign: 'left', formatter: '@' }
        ],
        events: {
            ButtonClicked: function (sender, args) {
                if (args.sheet.isEditing()) {
                    args.sheet.endEdit(true);
                }
            },
            CellDoubleClick: function (sender, args) {
                if (ration.headers[args.col].dataCode === 'name') {
                    let insertDatas = getInsertRations([args.row]);
                    if (insertDatas.length > 0) {
                        insert(insertDatas, false);
                    }
                }
            }
        }
    };
    const options = {
        workBook: {
            tabStripVisible: false,
            allowContextMenu: false,
            allowCopyPasteExcelStyle: false,
            allowExtendPasteRange: false,
            allowUserDragDrop: false,
            allowUserDragFill: false,
            scrollbarMaxAlign: true
        },
        sheet: {
            protectionOptions: { allowResizeRows: true, allowResizeColumns: true },
            clipBoardOptions: GC.Spread.Sheets.ClipboardPasteOptions.values
        }
    };

    /* 清单材料表 */
    const materialHeaders = [
        { name: '材料编码', dataCode: 'code', width: 90, vAlign: 'center', hAlign: 'left', formatter: '@' },
        { name: '材料名称', dataCode: 'name', width: 150, vAlign: 'center', hAlign: 'left', formatter: '@' },
        { name: '规格', dataCode: 'specs', width: 130, vAlign: 'center', hAlign: 'left', formatter: '@' }
    ];
    const billMaterial = {
        dom: $('#bill-material-spread'),
        workBook: null,
        cache: [],
        headers: materialHeaders,
        events: {
            EditEnded: function (sender, args) {
                editMaterials(args.sheet, [{ row: args.row, col: args.col }]);
            },
            RangeChanged: function (sender, args) {
                editMaterials(args.sheet, args.changedCells);
            },
        }
    }

    /* 清单辅助材料录入表 */
    const billMaterialHelper = {
        dom: $('#bill-material-helper-spread'),
        workBook: null,
        cache: [],
        headers: materialHeaders,
        events: {
            // 双击添加到清单材料表
            CellDoubleClick: function (sender, args) {
                // 模拟清单材料表编辑（共用同一个接口）
                if (!billMaterialHelper.cache[args.row]) {
                    return;
                }
                const code = billMaterialHelper.cache[args.row].code;
                const row = billMaterial.cache.length;
                const targetSheet = billMaterial.workBook.getSheet(0);
                targetSheet.setValue(row, 0, code);
                const changedCells = [{ row, col: 0 }];
                editMaterials(billMaterial.workBook.getSheet(0), changedCells);
            }
        }
    }

    /* 导出excel */
    let exportExcelWorkBook = null;

    // 显示清单材料数据
    function showBillMaterialData(sheet, headers, datas, emptyRow = 0) {
        let fuc = function () {
            const rowCount = datas.length + emptyRow;
            sheet.setRowCount(rowCount);
            for (let col = 0, cLen = headers.length; col < cLen; col++) {
                for (let row = 0; row < rowCount; row++) {
                    if (datas[row]) {
                        sheet.setValue(row, col, datas[row][headers[col]['dataCode']] || '');
                    } else {
                        sheet.setValue(row, col, '');
                    }
                }
            }
        };
        renderSheetFunc(sheet, fuc);
    }

    // 获取清单材料数据
    async function getBillMaterials() {
        if (!bills.tree.selected) {
            return;
        }
        billMaterial.cache = [];
        try {
            $.bootstrapLoading.start();
            const { billMaterials, allGljList } = await ajaxPost('/billsGuidance/api/getBillMaterials', { libID, billID: bills.tree.selected.data.ID });
            billMaterial.cache = billMaterials;
            console.log(allGljList);
            sortByCode(billMaterial.cache);
            billMaterialHelper.cache = allGljList
            sortByCode(billMaterialHelper.cache);
        } catch (error) {
            $('#alert-info').text(error.message);
            $('#alert').modal('show');
        } finally {
            showBillMaterialData(billMaterial.workBook.getSheet(0), billMaterial.headers, billMaterial.cache, 30);
            billMaterial.workBook.getSheet(0).showRow(0, GC.Spread.Sheets.VerticalPosition.top);
            showBillMaterialData(billMaterialHelper.workBook.getSheet(0), billMaterialHelper.headers, billMaterialHelper.cache);
            billMaterialHelper.workBook.getSheet(0).showRow(0, GC.Spread.Sheets.VerticalPosition.top);
            $.bootstrapLoading.end();
        }
    }

    function getText(sheet, row, col) {
        let text = sheet.getValue(row, col);
        text = text ? text.toString().trim() : '';
        return text;
    }

    // 编辑材料表
    async function editMaterials(sheet, cells) {
        let isChanged = false;
        for (let cell of cells) {
            const itemCode = billMaterial.cache[cell.row] && billMaterial.cache[cell.row].code || '';
            if (itemCode !== getText(sheet, cell.row, 0)) {
                isChanged = true;
            }
        }
        if (!isChanged) {
            return;
        }
        let gljCodes = new Set();
        const count = sheet.getRowCount();
        for (let row = 0; row < count; row++) {
            const code = getText(sheet, row, 0);
            if (code) {
                gljCodes.add(code);
            }
        }
        // 提交数据
        try {
            $.bootstrapLoading.start();
            billMaterial.cache = await ajaxPost('/billsGuidance/api/editBillMaterials', { libID, billID: bills.tree.selected.data.ID, gljCodes: [...gljCodes], compilationID: curCompilationID }, undefined, true);
            sortByCode(billMaterial.cache);
        } catch (error) {
            $('#alert-info').html(error);
            $('#alert').modal('show');
        } finally {
            showBillMaterialData(billMaterial.workBook.getSheet(0), billMaterial.headers, billMaterial.cache, 30);
            $.bootstrapLoading.end();
        }
    }

    //渲染时方法，停止渲染
    //@param {Object}sheet {Function}func @return {void}
    function renderSheetFunc(sheet, func) {
        sheet.suspendEvent();
        sheet.suspendPaint();
        if (func) {
            func();
        }
        sheet.resumeEvent();
        sheet.resumePaint();
    }
    //设置表选项
    //@param {Object}workBook {Object}opts @return {void}
    function setOptions(workBook, opts) {
        for (let opt in opts.workBook) {
            workBook.options[opt] = opts.workBook[opt];
        }
        for (let opt in opts.sheet) {
            workBook.getActiveSheet().options[opt] = opts.sheet[opt];
        }
    }
    //建表头
    //@param {Object}sheet {Array}headers @return {void}
    function buildHeader(sheet, headers) {
        let fuc = function () {
            sheet.setColumnCount(headers.length);
            sheet.setRowHeight(0, 40, GC.Spread.Sheets.SheetArea.colHeader);
            for (let i = 0, len = headers.length; i < len; i++) {
                sheet.setValue(0, i, headers[i].name, GC.Spread.Sheets.SheetArea.colHeader);
                sheet.setColumnWidth(i, headers[i].width, GC.Spread.Sheets.SheetArea.colHeader);
                sheet.getCell(0, i, GC.Spread.Sheets.SheetArea.colHeader).wordWrap(true);
                if (headers[i].formatter) {
                    sheet.setFormatter(-1, i, headers[i].formatter);
                }
                sheet.getRange(-1, i, -1, 1).hAlign(GC.Spread.Sheets.HorizontalAlign[headers[i]['hAlign']]);
                sheet.getRange(-1, i, -1, 1).vAlign(GC.Spread.Sheets.VerticalAlign[headers[i]['vAlign']]);
            }
        };
        renderSheetFunc(sheet, fuc);
    }
    //表监听事件
    //@param {Object}workBook @return {void}
    function bindEvent(workBook, events) {
        if (Object.keys(events).length === 0) {
            return;
        }
        const Events = GC.Spread.Sheets.Events;
        for (let event in events) {
            workBook.bind(Events[event], events[event]);
        }
    }
    //建表
    //@param {Object}module @return {void}
    function buildSheet(module) {
        if (!module.workBook) {
            module.workBook = new GC.Spread.Sheets.Workbook(module.dom[0], { sheetCount: 1 });
            let sheet = module.workBook.getActiveSheet();
            if (module === bills) {
                sheet.name('stdBillsGuidance_bills');
                //默认初始可控制焦点在清单表中
                module.workBook.focus();
                sheet.options.isProtected = true;
            }
            else if (module === ration) {
                sheet.options.isProtected = true;
                sheet.getRange(-1, 0, -1, 1).locked(locked);
                sheet.getRange(-1, 1, -1, -1).locked(true);
            }
            else if (module === guideItem) {
                sheet.name('stdBillsGuidance_guidance');
                sheetCommonObj.bindEscKey(module.workBook, [{ sheet: sheet, editStarting: null, editEnded: module.events.EditEnded }]);
            }
            else if (module === billMaterial) {
                sheet.options.isProtected = true;
                sheet.getRange(-1, 0, -1, 1).locked(locked);
                sheet.getRange(-1, 1, -1, -1).locked(true);
                sheet.getRange(-1, 2, -1, -1).locked(true);
            }
            else if (module === billMaterialHelper) {
                sheet.options.isProtected = true;
            }
            setOptions(module.workBook, options);
            buildHeader(module.workBook.getActiveSheet(), module.headers);
            bindEvent(module.workBook, module.events);
        }
        lockUtil.lockSpreads([module.workBook], locked);
        if (locked) {
            // 锁定表格后双击事件失效了，但是需要双击定位，因此重新绑定双击
            if (module === guideItem) {
                module.workBook.bind('CellDoubleClick', module.events.CellDoubleClick);
            }
        }
    }
    //清空表数据
    //@param {Object}sheet {Array}headers {Number}rowCount @return {void}
    function cleanData(sheet, headers, rowCount) {
        renderSheetFunc(sheet, function () {
            sheet.clear(-1, 0, -1, headers.length, GC.Spread.Sheets.SheetArea.viewport, GC.Spread.Sheets.StorageType.data);
            if (rowCount >= 0) {
                sheet.setRowCount(rowCount);
            }
        });
    }
    //根据清单获取项目指引
    //@param {String}guidanceLibID {Number}billsID {Function}callback @return {void}
    function getItemsByBills(guidanceLibID, billsID, callback) {
        CommonAjax.post('/billsGuidance/api/getItemsByBills', { guidanceLibID: guidanceLibID, billsID: billsID }, function (rstData) {
            if (callback) {
                callback(rstData);
            }
        });
    }

    //清单表焦点控制
    //@param {Number}row @return {void}
    function billsInitSel(row, oldSel, force = false) {
        let guideSheet = guideItem.workBook.getActiveSheet();
        cleanData(guideSheet, guideItem.headers, -1);
        let node = bills.tree.items[row];
        if (!node) {
            $('#editMaterial').addClass('disabled');
            return;
        }
        const billSheet = bills.workBook.getActiveSheet();
        setBgColor(billSheet, row, selectedBgColor);
        if (oldSel && row !== oldSel.row) {
            const orgNode = bills.tree.items[oldSel.row]
            setBgColor(billSheet, oldSel.row, orgNode && orgNode.isSearch ? searchBgColor : 'white');
        }
        bills.tree.selected = node;
        $('#editMaterial').removeClass('disabled');
        //显示备注
        $('.main-side-bottom').find('textarea').val(node.data.comment ? node.data.comment : '');
        if (!node.guidance.tree || force) {
            getItemsByBills(libID, node.data.ID, function (rstData) {
                initTree(node.guidance, guideSheet, guideItem.treeSetting, rstData);
                setNodesExpandState(node.guidance.tree.items, curExpandState);
                setProcessNodes(guideSheet, node.guidance.tree.items);
                renderSheetFunc(guideSheet, function () {
                    TREE_SHEET_HELPER.refreshNodesVisible(node.guidance.tree.roots, guideSheet, true);
                });
                //设置底色
                setNodesColor(guideSheet, node.guidance.tree.items);
                //项目指引初始焦点
                guideItemInitSel(guideSheet.getActiveRowIndex() ? guideSheet.getActiveRowIndex() : 0);
            });
        } else {
            setNodesExpandState(node.guidance.tree.items, curExpandState);
            node.guidance.controller.showTreeData();
            setProcessNodes(guideSheet, node.guidance.tree.items);
            //设置底色
            setNodesColor(guideSheet, node.guidance.tree.items);
            //项目指引初始焦点
            guideItemInitSel(guideSheet.getActiveRowIndex() ? guideSheet.getActiveRowIndex() : 0);
        }
    }

    function showCheckBox(sheet, nodes) {
        // const checkBoxType = locked ? sheetCommonObj.getReadOnlyCheckBox() : sheetCommonObj.getCheckBox();
        const checkBoxType = new GC.Spread.Sheets.CellTypes.CheckBox();
        const baseType = new GC.Spread.Sheets.CellTypes.Base();
        const outputItemCol = guideItem.headers.findIndex(item => item.dataCode === 'outputItemCharacter');
        const requiredCol = guideItem.headers.findIndex(item => item.dataCode === 'required');
        const materialCol = guideItem.headers.findIndex(item => item.dataCode === 'isMaterial');
        const defaultOption = guideItem.headers.findIndex(item => item.dataCode === 'isDefaultOption');
        renderSheetFunc(sheet, function () {
            nodes.forEach(node => {
                const row = node.serialNo();
                if (isOptionNode(node)) {
                    sheet.setCellType(row, defaultOption, checkBoxType);
                    sheet.setValue(row, defaultOption, node.data.isDefaultOption || false);
                } else {
                    sheet.setCellType(row, defaultOption, baseType);
                    sheet.setValue(row, defaultOption, '');
                }
                if (isProcessNode(node)) {
                    sheet.setCellType(row, outputItemCol, checkBoxType);
                    sheet.setCellType(row, requiredCol, checkBoxType);
                    sheet.setCellType(row, materialCol, checkBoxType);
                    sheet.setValue(row, outputItemCol, node.data.outputItemCharacter || false);
                    sheet.setValue(row, requiredCol, node.data.required || false);
                    sheet.setValue(row, materialCol, node.data.isMaterial || false);
                } else {
                    sheet.setCellType(row, outputItemCol, baseType);
                    sheet.setCellType(row, requiredCol, baseType);
                    sheet.setCellType(row, materialCol, baseType);
                    sheet.setValue(row, outputItemCol, '');
                    sheet.setValue(row, requiredCol, '');
                    sheet.setValue(row, materialCol, '');
                }
            })
        });
    }

    function setReadOnly(sheet, nodes) {
        if (locked) {
            return;
        }
        // 单位仅特征/工序行可输入
        // 区间仅仅选项行可输入
        const unitCol = guideItem.headers.findIndex(item => item.dataCode === 'unit');
        const intervalCol = guideItem.headers.findIndex(item => item.dataCode === 'interval');
        renderSheetFunc(sheet, function () {
            nodes.forEach(node => {
                const row = node.serialNo();
                sheet.getCell(row, unitCol).locked(!isProcessNode(node));
                sheet.getCell(row, intervalCol).locked(!isOptionNode(node));
            });
        });

    }

    // 设置选项行相关
    function setProcessNodes(sheet, nodes) {
        showCheckBox(sheet, nodes);
        setReadOnly(sheet, nodes);
    }

    //设置项目节点展开收起状态：展开全部、收起定额
    //@param {Array}nodes(当前清单下的所有项目指引节点) {Number}expandState(展开全部1或收起定额0).
    function setNodesExpandState(nodes, expandState) {
        if (expandState === itemExpandState.contract) {
            //找出所有定额的父节点
            let rations = _.filter(nodes, function (node) {
                return node.data.type === itemType.ration;
            });
            let rationParentIDs = [];
            for (let ration of rations) {
                if (ration.data.ParentID != -1) {
                    rationParentIDs.push(ration.data.ParentID);
                }
            }
            rationParentIDs = Array.from(new Set(rationParentIDs));
            let rationParentNodes = _.filter(nodes, function (node) {
                return rationParentIDs.includes(node.data.ID);
            });
            //收起定额
            for (let node of rationParentNodes) {
                node.setExpanded(false);
            }
        } else {
            for (let node of nodes) {
                node.setExpanded(true);
            }
        }
    }
    // 设置行底色
    function setBgColor(sheet, row, color) {
        const style = new GC.Spread.Sheets.Style();
        style.borderLeft = new GC.Spread.Sheets.LineBorder("#D4D4D4", GC.Spread.Sheets.LineStyle.thin);
        style.borderTop = new GC.Spread.Sheets.LineBorder("#D4D4D4", GC.Spread.Sheets.LineStyle.thin);
        style.borderRight = new GC.Spread.Sheets.LineBorder("#D4D4D4", GC.Spread.Sheets.LineStyle.thin);
        style.borderBottom = new GC.Spread.Sheets.LineBorder("#D4D4D4", GC.Spread.Sheets.LineStyle.thin);
        style.backColor = color;
        sheet.setStyle(row, -1, style);

    }
    //根据奇偶层级设置节点底色，奇数层为蓝色（树节点深度为偶数）
    //@param {Object}sheet {Array}nodes @return {void}
    function setNodesColor(sheet, nodes) {
        renderSheetFunc(sheet, function () {
            for (let node of nodes) {
                const color = isProcessNode(node) ? selectedBgColor : 'White';
                setBgColor(sheet, node.serialNo(), color);
            }
        });
    }
    //选中的节点是否全是同层节点
    //@param {Object}sheet {Array}items @return {Boolean}
    function itemsSameDepth(sheet, items) {
        let sels = sheet.getSelections();
        if (sels.length === 0 || items.length === 0) {
            return false;
        }
        let depths = [];
        for (let i = 0; i < sels[0].rowCount; i++) {
            let row = sels[0].row + i;
            let node = items[row];
            if (node) {
                depths.push(node.depth());
            }
        }
    }
    //节点子项是否全是工作内容
    //@param {Object}node @return {Boolean}
    function allJobChildren(node) {
        for (let c of node.children) {
            if (c.data.type === itemType.ration) {
                return false;
            }
        }
        return true;
    }
    //节点子项是否全是定额
    //@param {Object}node @return {Boolean}
    function allRationChildren(node) {
        for (let c of node.children) {
            if (c.data.type === itemType.job) {
                return false;
            }
        }
        return true;
    }
    //刷新按钮有效性
    //@param {Object}node @return {void}
    function refreshBtn(node) {
        if (locked) {
            return;
        }
        //全部设为无效
        $('.tools-btn').children().addClass('disabled');
        $('#generate-class').removeClass('disabled');
        $('#insertRation').addClass('disabled');
        $('#insertAll').addClass('disabled');
        $('.main-bottom-content').find('textarea').attr('readonly', true);
        //插入
        if (bills.tree.selected && bills.tree.selected.guidance.tree) {
            $('#insert').removeClass('disabled');
            if (node && node.data.type === itemType.ration) {
                $('#insert').addClass('disabled');
            }
        }
        //删除
        if (node) {
            $('#del').removeClass('disabled');
        }
        if (node && node.data.type === itemType.job) {
            //升级
            if (node.parent) {
                $('#upLevel').removeClass('disabled');
                if (node.nextSibling && node.children.length > 0 && !allJobChildren(node)) {
                    $('#upLevel').addClass('disabled');
                }
            }
            //降级
            if (node.preSibling) {
                $('#downLevel').removeClass('disabled');
                if (node.preSibling.children.length > 0 && !allJobChildren(node.preSibling)) {
                    $('#downLevel').addClass('disabled');
                }
            }
        }
        //上移
        if (node && node.preSibling) {
            $('#upMove').removeClass('disabled')
        }
        //下移
        if (node && node.nextSibling) {
            $('#downMove').removeClass('disabled');
        }
        //收起定额、展开全部
        $('#expandContract').removeClass('disabled');
        //插入定额
        if (node && (node.children.length === 0 || allRationChildren(node))) {
            $('#insertRation').removeClass('disabled');
            $('#insertAll').removeClass('disabled');
        }
        //备注,奇数节点可用
        if (isProcessNode(node)) {
            $('.main-bottom-content').find('textarea').attr('readonly', false);
        }
        // 配置材料
        $('#editMaterial').removeClass('disabled');
    }
    //项目指引表焦点控制
    //@param {Number}row @return {void}
    function guideItemInitSel(row) {
        let billsNode = bills.tree.selected;
        let node = null;
        if (billsNode && billsNode.guidance.tree) {
            node = billsNode.guidance.tree.items[row];
            if (node) {
                billsNode.guidance.tree.selected = node;
                //显示备注
                $('.main-bottom-content').find('textarea').val(node.data.comment ? node.data.comment : '');
            }
        }
        refreshBtn(node);
    }
    //初始化当前库名
    //@param {String} @return {void}
    function initLibName(libName) {
        $('#libName')[0].outerHTML = $('#libName')[0].outerHTML.replace("XXX清单指引", libName);
    }
    //初始化各工作表
    //@param {Array}modules @return {void}
    function initWorkBooks(modules) {
        exportExcelWorkBook = new GC.Spread.Sheets.Workbook($('#excel-spread')[0], { sheetCount: 1 });
        for (let module of modules) {
            buildSheet(module);
        }
    }
    function tipDivCheck() {
        setTimeout(function () {
            let tips = $('#autoTip');
            if (ration.tipDiv == 'show') {
                return;
            } else if (ration.tipDiv == 'hide' && tips) {
                tips.hide();
                ration._toolTipElement = null;
            }
        }, 600)
    }
    //获取悬浮提示单元格
    //@param {Object}sheet @return {Object}
    function getTipCellType(sheet) {
        let setting = {};
        let TipCellType = function () { };
        TipCellType.prototype = new GC.Spread.Sheets.CellTypes.Text();
        TipCellType.prototype.getHitInfo = function (x, y, cellStyle, cellRect, context) {
            return {
                x: x,
                y: y,
                row: context.row,
                col: context.col,
                cellStyle: cellStyle,
                cellRect: cellRect,
                sheet: context.sheet,
                sheetArea: context.sheetArea
            };
        };
        TipCellType.prototype.processMouseEnter = function (hitinfo) {
            let text = hitinfo.sheet.getText(hitinfo.row, hitinfo.col);
            let tag = hitinfo.sheet.getTag(hitinfo.row, hitinfo.col);
            if (tag !== undefined && tag) {
                text = tag;
            }
            if (sheet && sheet.getParent().qo) {
                setting.pos = SheetDataHelper.getObjPos(sheet.getParent().qo);
            }
            let delayTimes = 500; //延时时间
            let now_timeStamp = +new Date();
            this.tipTimeStamp = now_timeStamp;
            let me = this;
            setTimeout(function () {
                if (now_timeStamp - me.tipTimeStamp == 0) {//鼠标停下的时候才显示
                    if (setting.pos && text && text !== '') {
                        //固定不显示的div，存储文本获取固定div宽度，toolTipElement由于显示和隐藏，获取宽度不正确
                        if (!me._fixedTipElement) {
                            let div = $('#fixedTip1')[0];
                            if (!div) {
                                div = document.createElement("div");
                                $(div).css("padding", 5)
                                    .attr("id", 'fixedTip');
                                $(div).hide();
                                document.body.insertBefore(div, null);
                            }
                            me._fixedTipElement = div;
                        }
                        $(me._fixedTipElement).width('');
                        $(me._fixedTipElement).html(text);
                        if (!me._toolTipElement) {
                            let div = $('#autoTip1')[0];
                            if (!div) {
                                div = document.createElement("div");
                                $(div).css("position", "absolute")
                                    .css("border", "1px #C0C0C0 solid")
                                    .css("box-shadow", "1px 2px 5px rgba(0,0,0,0.4)")
                                    .css("font", "0.9rem Calibri")
                                    .css("padding", 5)
                                    .css("background", '#303133')
                                    .css("color", '#fff')
                                    .attr("id", 'autoTip1');
                                $(div).hide();
                                document.body.insertBefore(div, null);
                            }
                            me._toolTipElement = div;
                            $(me._toolTipElement).width('');
                            //实时读取位置信息
                            if (hitinfo.sheet && hitinfo.sheet.getParent().qo) {
                                setting.pos = SheetDataHelper.getObjPos(hitinfo.sheet.getParent().qo);
                            }
                            $(me._toolTipElement).html(text);
                            //定额库定额特殊处理
                            if ($(hitinfo.sheet.getParent().qo).attr('id') === 'rationSpread') {
                                let divWidth = $(me._fixedTipElement).width(),
                                    divHeight = $(me._fixedTipElement).height();
                                if (divWidth > 600) {
                                    divWidth = 590;
                                    $(me._toolTipElement).width(divWidth);
                                }
                                let top = setting.pos.y + hitinfo.y - divHeight < 0 ? 0 : setting.pos.y + hitinfo.cellRect.y - divHeight;
                                $(me._toolTipElement).css("top", top).css("left", setting.pos.x - divWidth);
                            }
                            else {
                                $(me._toolTipElement).css("top", setting.pos.y + hitinfo.y + 15).css("left", setting.pos.x + hitinfo.x + 15);
                            }
                            //名称
                            if (hitinfo.col === 2) {
                                let acStyle = hitinfo.sheet.getActualStyle(hitinfo.row, hitinfo.col),
                                    zoom = hitinfo.sheet.zoom();
                                let value = hitinfo.sheet.getValue(hitinfo.row, hitinfo.col);
                                let textLength = me.getAutoFitWidth(value, text, acStyle, zoom, { sheet: hitinfo.sheet, row: hitinfo.row, col: hitinfo.col, sheetArea: GC.Spread.Sheets.SheetArea.viewport });
                                let cellWidth = hitinfo.sheet.getCell(-1, hitinfo.col).width();
                                if (textLength > cellWidth) {
                                    $(me._toolTipElement).css("top", setting.pos.y + hitinfo.y + 15).css("left", setting.pos.x + hitinfo.x + 15);
                                    $(me._toolTipElement).show("fast");
                                    ration.tipDiv = 'show';//做个标记
                                }
                            }
                            else {
                                $(me._toolTipElement).show("fast");
                                ration.tipDiv = 'show';//做个标记
                            }
                        }
                    }
                }
            }, delayTimes);
        };
        TipCellType.prototype.processMouseLeave = function (hininfo) {
            this.tipTimeStamp = +new Date();
            ration.tipDiv = 'hide';
            if (this._toolTipElement) {
                $(this._toolTipElement).hide();
                this._toolTipElement = null;
            }
            tipDivCheck();//延时检查：当tips正在show的时候，就调用了hide方法，会导致tips一直存在，所以设置一个超时处理
        }
        return new TipCellType();
    }
    //输出表数据(定额表)
    //@param {Object}sheet {Array}headers {Array}datas @return {void}
    function showData(sheet, headers, datas) {
        let fuc = function () {
            sheet.setRowCount(datas.length);
            //复选框
            let checkBoxType = new GC.Spread.Sheets.CellTypes.CheckBox();
            let tipCellType = getTipCellType(sheet);
            sheet.setCellType(-1, 0, checkBoxType);
            for (let col = 0, cLen = headers.length; col < cLen; col++) {
                for (let row = 0, rLen = datas.length; row < rLen; row++) {
                    sheet.setValue(row, col, datas[row][headers[col]['dataCode']]);
                    if (col === 1) {
                        sheet.setTag(row, col, datas[row]['hint']);
                    }
                }
            }
            sheet.setCellType(-1, 1, tipCellType);
            sheet.setCellType(-1, 2, tipCellType);
        };
        renderSheetFunc(sheet, fuc);
    }
    //根据定额章节树ID获取定额(从数据缓存中获取，定额数据一开始一次性拉取)
    //@param {Number}sectionId {Array}rations @return {Array}
    function getRationsBySectionId(sectionId, rations) {
        if (!sectionId || !rations) {
            return [];
        }
        return _.filter(rations, { sectionId });
    }
    //定额章节树焦点控制
    //@param {Number}row @return {void}
    function sectionInitSel(row) {
        let rationSheet = ration.workBook.getActiveSheet();
        let sectionNode = section.tree ? section.tree.items[row] : null;
        if (sectionNode && sectionNode.children.length === 0) {
            let sectionRations = getRationsBySectionId(sectionNode.data.ID, ration.datas);
            ration.cache = sectionRations;
            showData(rationSheet, ration.headers, sectionRations);
        }
        else {
            cleanData(rationSheet, ration.headers, 0);
        }
    }
    //初始化定额条目
    //@param {Number}rationLibId @return {void}
    function initRationItems(rationLibId, successCb) {
        $.bootstrapLoading.start();
        //获取定额章节树
        let sectionSheet = section.workBook.getActiveSheet();
        CommonAjax.post('/rationRepository/api/getRationTree', { rationLibId: rationLibId }, function (sectionDatas) {
            //获取所有定额数据
            let reqEntity = { rationLibId: rationLibId, showHint: true, returnFields: '-_id code ID sectionId name unit basePrice rationGljList jobContent annotation' };
            CommonAjax.post('/rationRepository/api/getRationItemsByLib', reqEntity, function (rstData) {
                section.cache = sectionDatas;
                initTree(section, section.workBook.getActiveSheet(), section.treeSetting, sectionDatas);
                //初始焦点在第一行（切换库）
                sectionSheet.setActiveCell(0, 0);
                sortByCode(rstData);
                /* rstData.sort(function (a, b) {
                    let rst = 0;
                    if(a.code > b.code){
                        rst = 1;
                    }
                    else if(a.code < b.code){
                        rst = -1;
                    }
                    return rst;
                }); */
                ration.datas = rstData;
                sectionInitSel(0);
                $.bootstrapLoading.end();
                if (successCb) {
                    successCb();
                }
            }, function () {
                $.bootstrapLoading.end();
            });
        }, function () {
            $.bootstrapLoading.end();
        });
    }

    // 可用的定额库ID
    const rationLibIDs = [];
    //初始化定额库选择
    //@param {String}compilationId @return {void}
    function initRationLibs(compilationId) {
        CommonAjax.post('/rationRepository/api/getRationLibsByCompilation', { compilationId: compilationId }, function (rstData) {
            $('#rationLibSel').empty();
            for (let rationLib of rstData) {
                rationLibIDs.push(+rationLib.ID);
                let opt = `<option value="${rationLib.ID}">${rationLib.dispName}</option>`;
                $('#rationLibSel').append(opt);
            }
            //初始选择
            initRationItems(parseInt($('#rationLibSel').select().val()));
            $('#rationLibSel').change(function () {
                let rationLibId = parseInt($(this).select().val());
                initRationItems(rationLibId);
            })
        });
    }

    // 变更定额库
    function changeRationLib(libID, successCb) {
        $('#rationLibSel').val(libID);
        initRationItems(libID, successCb);
    }

    // 设置清单名称文本色
    function setBillsForeColor(billsNodes) {
        const sheet = bills.workBook.getActiveSheet();
        renderSheetFunc(sheet, function () {
            for (let bills of billsNodes) {
                const row = bills.serialNo();
                sheet.setStyle(row, 1, { foreColor: bills.data.hasGuide ? 'darkgreen' : 'black' });
            }
        });
    }
    //清单设置悬浮提示信息
    //@param {Array}billsNodes(清单节点) {Array}jobs(总的工作内容数据) {Array}items(总的项目特征数据)
    function setBillsHint(billsNodes, jobs, items) {
        let jobsMapping = {},
            itemsMapping = {};
        for (let job of jobs) {
            jobsMapping[job.id] = job;
        }
        for (let item of items) {
            itemsMapping[item.id] = item;
        }
        let tagInfo = [];
        for (let billsNode of billsNodes) {
            let hintArr = [];
            let billsItems = billsNode.data.items;
            if (billsItems.length > 0) {
                //项目特征
                hintArr.push('项目特征:');
            }
            let itemCount = 1,
                jobCount = 1;
            for (let billsItem of billsItems) {
                let itemData = itemsMapping[billsItem.id];
                if (itemData) {
                    //特征值
                    let eigens = [];
                    for (let eigen of itemData.itemValue) {
                        eigens.push(eigen.value);
                    }
                    eigens = eigens.join(';');
                    hintArr.push(`${itemCount}.${itemData.content}${eigens === '' ? '' : ': ' + eigens}`);
                    itemCount++;
                }
            }
            //工作内容
            let billsJobs = billsNode.data.jobs;
            if (billsJobs.length > 0) {
                hintArr.push('工作内容:');
            }
            for (let billsJob of billsJobs) {
                let jobData = jobsMapping[billsJob.id];
                if (jobData) {
                    hintArr.push(`${jobCount}.${jobData.content}`);
                    jobCount++;
                }
            }
            /*if(billsNode.data.ruleText && billsNode.data.ruleText !== ''){
             hintArr.push('工程量计算规则:');
             hintArr.push(billsNode.data.ruleText);
             }
             if(billsNode.data.recharge && billsNode.data.recharge !== ''){
             hintArr.push('补注:');
             hintArr.push(billsNode.data.recharge);
             }*/
            if (hintArr.length > 0) {
                tagInfo.push({ row: billsNode.serialNo(), value: hintArr.join('\n') });
            }
        }
        let sheet = bills.workBook.getActiveSheet();
        renderSheetFunc(sheet, function () {
            for (let tagI of tagInfo) {
                sheet.setTag(tagI.row, 0, tagI.value);
            }
        });
    }
    //初始化清单的工作内容和项目特征
    //@param {Number}billsLibId {Function}callback @return {void}
    function initJobAndCharacter(billsLibId, callback) {
        CommonAjax.post('/stdBillsEditor/getJobContent', { billsLibId: billsLibId }, function (datas) {
            stdBillsJobData = datas;
            CommonAjax.post('/stdBillsEditor/getItemCharacter', { billsLibId: billsLibId }, function (datas) {
                stdBillsFeatureData = datas;
                if (callback) {
                    callback();
                }
            });
        });
    }
    let billsLibId = 0;
    //获取指引库信息及关联的清单
    //@param {Number}libID {Function}callback @return {Object}
    function getLibWithBills(libID, callback) {
        CommonAjax.post('/billsGuidance/api/getLibWithBills', { libID: libID }, function (rstData) {
            billsLibId = rstData.guidanceLib.billsLibId;
            initRationLibs(rstData.guidanceLib.compilationId);
            curCompilationID = rstData.guidanceLib.compilationId;
            bills.cache = rstData.bills;
            initLibName(rstData.guidanceLib.name);
            /*initTree(bills, bills.workBook.getActiveSheet(), bills.treeSetting, bills.cache);
            //每一棵项目指引树挂在清单节点上
            for(let node of bills.tree.items){
                node.guidance = {tree: null, controller: null};
            }
            //默认初始节点
            billsInitSel(0);
            if(callback){
                callback(rstData);
            }*/
            let initDataCallback = function () {
                initTree(bills, bills.workBook.getActiveSheet(), bills.treeSetting, bills.cache);
                //每一棵项目指引树挂在清单节点上
                for (let node of bills.tree.items) {
                    node.guidance = { tree: null, controller: null };
                }
                //默认初始节点
                billsInitSel(0);
                if (callback) {
                    callback(rstData);
                }
            };
            initJobAndCharacter(rstData.guidanceLib.billsLibId, initDataCallback);
        }, function (msg) {
            window.location.href = '/billsGuidance/main';
        });
    }
    //初始化并输出树
    //@param {Object}module {Object}sheet {Object}treeSetting {Array}datas
    function initTree(module, sheet, treeSetting, datas) {
        module.tree = idTree.createNew({ id: 'ID', pid: 'ParentID', nid: 'NextSiblingID', rootId: -1, autoUpdate: true });
        module.controller = TREE_SHEET_CONTROLLER.createNew(module.tree, sheet, treeSetting);
        module.tree.loadDatas(datas);
        if (module === bills) {
            module.tree.roots.forEach(root => {
                root.setExpanded(false);
            })
        }
        module.controller.showTreeData();
        if (module === bills) {
            setBillsHint(bills.tree.items, stdBillsJobData, stdBillsFeatureData);
            setBillsForeColor(bills.tree.items);
        }
    }

    //更新清单备注
    function updateBillsComment(updateData, callback, errCB) {
        CommonAjax.post('/stdBillsEditor/updateBills', updateData, function () {
            if (callback) {
                callback();
            }
        }, function () {
            if (errCB) {
                errCB();
            }
        });
    }
    //更新项目指引
    //@param {Array}updateDatas {Function}callback @return {void}
    function updateGuideItems(updateDatas, callback, errCB) {
        CommonAjax.post('/billsGuidance/api/updateItems', { updateDatas: updateDatas }, function (rstData) {
            if (callback) {
                callback(rstData);
            }
        }, function () {
            if (errCB) {
                errCB();
            }
            $.bootstrapLoading.end();
        });
    }
    //编辑后自动去除换行符回车符
    const deESC = /[\n]/g;
    //项目指引编辑
    //@param {Object}sheet {Array}cells
    function edit(sheet, cells) {
        let updateDatas = [];
        //同步节点数据
        let syncDatas = [];
        for (let cell of cells) {
            const field = guideItem.headers[cell.col].dataCode;
            let node = bills.tree.selected.guidance.tree.items[cell.row];
            if (field === 'name' || field === 'unit' || field === 'interval') {
                let text = sheet.getValue(cell.row, cell.col);
                text = text ? text.toString() : '';
                text = text.replace(deESC, '');
                sheet.setValue(cell.row, cell.col, text);
                if (node.data[field] != text) {
                    syncDatas.push({ node: node, text: text, field, cell });
                    updateDatas.push({ updateType: updateType.update, findData: { ID: node.getID() }, updateData: { [field]: text } });
                }
            } else if (field === 'outputItemCharacter' || field === 'required' || field === 'isMaterial' || field === 'isDefaultOption') {
                const val = !sheet.getValue(cell.row, cell.col);
                sheet.setValue(cell.row, cell.col, val);
                syncDatas.push({ node: node, text: val, field, cell });
                updateDatas.push({ updateType: updateType.update, findData: { ID: node.getID() }, updateData: { [field]: val } });
            }
        }
        if (updateDatas.length > 0) {
            updateGuideItems(updateDatas, function () {
                for (let syncData of syncDatas) {
                    syncData.node.data[syncData.field] = syncData.text;
                }
            }, function () {
                //失败恢复
                renderSheetFunc(sheet, function () {
                    for (let syncData of syncDatas) {
                        sheet.setValue(syncData.node.serialNo(), syncData.cell.col, syncData.node.data[syncData.field] ? syncData.node.data[syncData.field] : '');
                    }
                });
            });
        }
    }
    //项目指引插入，支持一次插入多条数据
    //@param {Array}datas {Boolean}tobeChild(插入成为子项) {Function}callback @return {void}
    function insert(datas, tobeChild, callback = null) {
        $.bootstrapLoading.start();
        let sheet = guideItem.workBook.getActiveSheet();
        let controller = bills.tree.selected.guidance.controller;
        let selected = bills.tree.selected.guidance.tree.selected;
        let updateDatas = [];
        //建立数组下标索引
        let newDataIndex = {};
        for (let i = 0; i < datas.length; i++) {
            let newNodeData = {
                libID: libID, ID: uuid.v1(), ParentID: selected ? selected.getParentID() : -1, NextSiblingID: selected ? selected.getNextSiblingID() : -1,
                billsID: bills.tree.selected.getID(),
                outputItemCharacter: true,
            };
            //定额类型插入当前工作内容焦点行，
            if (selected && ((selected.data.type === itemType.job && datas[i].type === itemType.ration) || tobeChild)) {
                newNodeData.ParentID = selected.getID();
                newNodeData.NextSiblingID = -1;
            }
            Object.assign(newNodeData, datas[i]);
            newDataIndex[i] = newNodeData;
        }
        for (let i = 0; i < datas.length; i++) {
            //第一个节点
            if (i === 0) {
                //非插入成子节点,更新选中节点NestSiblingID
                if (selected && !((selected.data.type === itemType.job && datas[i].type === itemType.ration) || tobeChild)) {
                    updateDatas.push({ updateType: updateType.update, findData: { ID: selected.getID() }, updateData: { NextSiblingID: newDataIndex[i].ID } });
                }
            }
            //非最后一个节点
            if (i !== datas.length - 1) {
                newDataIndex[i].NextSiblingID = newDataIndex[i + 1].ID;
            }
            updateDatas.push({ updateType: updateType.create, updateData: newDataIndex[i] });
        }
        updateGuideItems(updateDatas, function () {
            const outputItemCol = guideItem.headers.findIndex(item => item.dataCode === 'outputItemCharacter');
            for (let updateData of updateDatas) {
                if (updateData.updateType === updateType.create) {
                    let newNode = controller.insertByIDS(updateData.updateData.ID, updateData.updateData.ParentID, updateData.updateData.NextSiblingID);
                    //同步data
                    Object.assign(newNode.data, updateData.updateData);
                    const row = newNode.serialNo();
                    sheet.setValue(row, 0, newNode.data.name);
                    if (newNode.data.outputItemCharacter !== undefined) {
                        sheet.setValue(row, outputItemCol, newNode.data.outputItemCharacter);
                    }
                    setProcessNodes(sheet, [newNode]);
                    refreshBtn(newNode);
                }
            }
            if (!bills.tree.selected.data.hasGuide && bills.tree.selected.guidance.tree.items.length) {
                bills.tree.selected.data.hasGuide = true;
                setBillsForeColor([bills.tree.selected]);
            }
            if (callback) {
                callback();
            }
            setNodesColor(sheet, bills.tree.selected.guidance.tree.items);
            guideItem.workBook.focus(true);
            $.bootstrapLoading.end();
        });
    }

    // 删除定额(嵌套删除自身及后代定额)
    function delRations() {
        const selected = bills.tree.selected.guidance.tree.selected;
        if (!selected) {
            return;
        }
        if (selected.data.type === itemType.ration) {
            del({ row: selected.serialNo(), col: 0, rowCount: 1, colCount: 1 });
            return;
        }
        const rations = selected.getPosterity().filter(item => item.data.type === itemType.ration);
        if (!rations.length) {
            return;
        }
        const updateDatas = rations.map(item => ({ updateType: updateType.del, findData: { ID: item.data.ID } }));
        const rationIDs = rations.map(r => r.data.ID);
        $.bootstrapLoading.start();
        updateGuideItems(updateDatas, function () {
            const guideSheet = guideItem.workBook.getSheet(0);
            const treeNodes = bills.tree.selected.guidance.tree.items.filter(item => !rationIDs.includes(item.data.ID));
            const state = bills.tree.selected.guidance.tree.getExpState(treeNodes);
            const treeData = treeNodes.map(item => item.data);
            initTree(bills.tree.selected.guidance, guideSheet, guideItem.treeSetting, treeData);
            bills.tree.selected.guidance.tree.setExpandedByState(bills.tree.selected.guidance.tree.items, state);
            renderSheetFunc(guideSheet, function () {
                TREE_SHEET_HELPER.refreshNodesVisible(bills.tree.selected.guidance.tree.roots, guideSheet, true);
            });
            //设置底色
            setNodesColor(guideSheet, bills.tree.selected.guidance.tree.items);
            //项目指引初始焦点
            guideItemInitSel(guideSheet.getActiveRowIndex() ? guideSheet.getActiveRowIndex() : 0);
            $.bootstrapLoading.end();
            guideItem.workBook.focus(true)
        });


    }

    //项目指引删除操作
    //@return {void}
    function del(selArea = null) {
        $.bootstrapLoading.start();
        let controller = bills.tree.selected.guidance.controller;
        let selNodes = [];
        let sheet = guideItem.workBook.getSheet(0);
        let sel = selArea ? selArea : sheet.getSelections()[0];
        if (sel) {
            sel.row = sel.row === -1 ? 0 : sel.row;
            for (let i = 0; i < sel.rowCount; i++) {
                if (bills.tree.selected.guidance.tree.items[sel.row + i]) {
                    selNodes.push(bills.tree.selected.guidance.tree.items[sel.row + i]);
                }
            }
        }
        //选中的块节点
        let blockNodes = getBlockNodes(selNodes);
        let updateDatas = [];
        function getDelDatas(nodes) {
            for (let node of nodes) {
                updateDatas.push({ updateType: updateType.del, findData: { ID: node.getID() } });
                if (node.children.length > 0) {
                    getDelDatas(node.children);
                }
            }
        }
        getDelDatas(blockNodes);
        //更新相关的前节点
        for (let node of blockNodes) {
            if (node.preSibling && !blockNodes.includes(node.preSibling)) {
                let next = node;
                while (next.nextSibling && blockNodes.includes(next.nextSibling)) {
                    next = next.nextSibling;
                }
                updateDatas.push({ updateType: updateType.update, findData: { ID: node.preSibling.getID() }, updateData: { NextSiblingID: next.getNextSiblingID() } });
            }
        }
        updateGuideItems(updateDatas, function () {
            console.log(blockNodes);
            controller.m_delete(blockNodes);
            guideItemInitSel(sheet.getActiveRowIndex());
            refreshBtn(bills.tree.selected.guidance.tree.selected);
            setNodesColor(guideItem.workBook.getActiveSheet(), bills.tree.selected.guidance.tree.items);
            setProcessNodes(guideItem.workBook.getActiveSheet(), bills.tree.selected.guidance.tree.items);
            if (bills.tree.selected.data.hasGuide && !bills.tree.selected.guidance.tree.items.length) {
                bills.tree.selected.data.hasGuide = false;
                setBillsForeColor([bills.tree.selected]);
            }
            $.bootstrapLoading.end();
            guideItem.workBook.focus(true)
        });
    }
    //项目指引升级
    //@return {void}
    function upLevel() {
        $.bootstrapLoading.start();
        let controller = bills.tree.selected.guidance.controller;
        let selected = bills.tree.selected.guidance.tree.selected;
        let updateDatas = [];
        //更新父节点
        updateDatas.push({ updateType: updateType.update, findData: { ID: selected.getParentID() }, updateData: { NextSiblingID: selected.getID() } });
        //更新选中节点
        updateDatas.push({
            updateType: updateType.update, findData: { ID: selected.getID() },
            updateData: { ParentID: selected.parent.getParentID(), NextSiblingID: selected.parent.getNextSiblingID(), unit: '', interval: '' }
        });
        if (selected.nextSibling && selected.children.length > 0) {
            //更新选中节点最末子节点
            let lastChild = selected.children[selected.children.length - 1];
            updateDatas.push({ updateType: updateType.update, findData: { ID: lastChild.getID() }, updateData: { NextSiblingID: -1 } });
        }
        //选中节点的所有后兄弟节点成为选中节点的子项
        let selectedNextIDs = [];
        let sNext = selected.nextSibling;
        while (sNext) {
            selectedNextIDs.push(sNext.getID());
            sNext = sNext.nextSibling;
        }
        for (let sID of selectedNextIDs) {
            updateDatas.push({ updateType: updateType.update, findData: { ID: sID }, updateData: { ParentID: selected.getID() } });
        }
        updateGuideItems(updateDatas, function () {
            controller.upLevel();
            const sheet = guideItem.workBook.getActiveSheet();
            refreshBtn(bills.tree.selected.guidance.tree.selected);
            setNodesColor(sheet, bills.tree.selected.guidance.tree.items);
            setProcessNodes(sheet, bills.tree.selected.guidance.tree.items);
            const unitCol = guideItem.headers.findIndex(item => item.dataCode === 'unit');
            const intervalCol = guideItem.headers.findIndex(item => item.dataCode === 'interval');
            sheet.setValue(selected.serialNo(), unitCol, '');
            sheet.setValue(selected.serialNo(), intervalCol, '');
            $.bootstrapLoading.end();
            guideItem.workBook.focus(true)//31574
        });
    }
    //项目指引降级
    //@return {void}
    function downLevel() {
        $.bootstrapLoading.start();
        let controller = bills.tree.selected.guidance.controller;
        let selected = bills.tree.selected.guidance.tree.selected;
        let updateDatas = [];
        //更新前兄弟节点
        updateDatas.push({ updateType: updateType.update, findData: { ID: selected.preSibling.getID() }, updateData: { NextSiblingID: selected.getNextSiblingID() } });
        //更新前兄弟节点最末子节点
        if (selected.preSibling.children.length > 0) {
            let lastChild = selected.preSibling.children[selected.preSibling.children.length - 1];
            updateDatas.push({ updateType: updateType.update, findData: { ID: lastChild.getID() }, updateData: { NextSiblingID: selected.getID() } });
        }
        //更新选中节点
        updateDatas.push({ updateType: updateType.update, findData: { ID: selected.getID() }, updateData: { ParentID: selected.preSibling.getID(), NextSiblingID: -1, unit: '', interval: '' } });
        updateGuideItems(updateDatas, function () {
            controller.downLevel();
            const sheet = guideItem.workBook.getActiveSheet();
            refreshBtn(bills.tree.selected.guidance.tree.selected);
            setNodesColor(sheet, bills.tree.selected.guidance.tree.items);
            setProcessNodes(sheet, bills.tree.selected.guidance.tree.items);
            const unitCol = guideItem.headers.findIndex(item => item.dataCode === 'unit');
            const intervalCol = guideItem.headers.findIndex(item => item.dataCode === 'interval');
            sheet.setValue(selected.serialNo(), unitCol, '');
            sheet.setValue(selected.serialNo(), intervalCol, '');
            $.bootstrapLoading.end();
            guideItem.workBook.focus(true)
        });
    }
    //项目指引上移
    //@return {void}
    function upMove() {
        $.bootstrapLoading.start();
        let controller = bills.tree.selected.guidance.controller;
        let selected = bills.tree.selected.guidance.tree.selected;
        let updateDatas = [];
        //更新前节点
        updateDatas.push({ updateType: updateType.update, findData: { ID: selected.preSibling.getID() }, updateData: { NextSiblingID: selected.getNextSiblingID() } });
        //更新前前节点
        if (selected.preSibling.preSibling) {
            updateDatas.push({ updateType: updateType.update, findData: { ID: selected.preSibling.preSibling.getID() }, updateData: { NextSiblingID: selected.getID() } });
        }
        //更新选中节点
        updateDatas.push({ updateType: updateType.update, findData: { ID: selected.getID() }, updateData: { NextSiblingID: selected.preSibling.getID() } });
        updateGuideItems(updateDatas, function () {
            controller.upMove();
            refreshBtn(bills.tree.selected.guidance.tree.selected);
            setNodesColor(guideItem.workBook.getActiveSheet(), bills.tree.selected.guidance.tree.items);
            setProcessNodes(guideItem.workBook.getActiveSheet(), bills.tree.selected.guidance.tree.items);
            $.bootstrapLoading.end();
            guideItem.workBook.focus(true)
        });
    }
    //项目指引下移
    //@return {void}
    function downMove() {
        $.bootstrapLoading.start();
        let controller = bills.tree.selected.guidance.controller;
        let selected = bills.tree.selected.guidance.tree.selected;
        let updateDatas = [];
        //更新下节点
        updateDatas.push({ updateType: updateType.update, findData: { ID: selected.getNextSiblingID() }, updateData: { NextSiblingID: selected.getID() } });
        //更新前节点
        if (selected.preSibling) {
            updateDatas.push({ updateType: updateType.update, findData: { ID: selected.preSibling.getID() }, updateData: { NextSiblingID: selected.getNextSiblingID() } });
        }
        //更新选中节点
        updateDatas.push({ updateType: updateType.update, findData: { ID: selected.getID() }, updateData: { NextSiblingID: selected.nextSibling.getNextSiblingID() } });
        updateGuideItems(updateDatas, function () {
            controller.downMove();
            refreshBtn(bills.tree.selected.guidance.tree.selected);
            setNodesColor(guideItem.workBook.getActiveSheet(), bills.tree.selected.guidance.tree.items);
            setProcessNodes(guideItem.workBook.getActiveSheet(), bills.tree.selected.guidance.tree.items);
            $.bootstrapLoading.end();
            guideItem.workBook.focus(true)
        });
    }
    //获取定额类型的项目指引名称，通过定额转换
    //@param {Object}ration @return {String}
    function getRationItemName(ration) {
        let arr = [];
        arr.push(ration.code ? ration.code : '');
        arr.push(ration.name ? ration.name : '');
        arr.push(ration.basePrice ? ration.basePrice : '');
        let rst = arr.join(' ');
        rst += `/${ration.unit ? ration.unit : ''}`;
        return rst;
    }
    //获取选中的定额表行
    //@return {Array}
    function getCheckedRationRows(all) {
        let rst = [];
        let sheet = ration.workBook.getActiveSheet();
        for (let i = 0; i < sheet.getRowCount(); i++) {
            // 全选
            if (all) {
                rst.push(i);
                continue;
            }
            let checked = sheet.getValue(i, 0);
            if (checked) {
                rst.push(i);
            }
        }
        return rst;
    }
    //清空选中定额表行
    //@param {Array}rows @return {void}
    function clearCheckedRation(rows) {
        let sheet = ration.workBook.getActiveSheet();
        renderSheetFunc(sheet, function () {
            for (let row of rows) {
                sheet.setValue(row, 0, 0);
            }
        });
    }
    //获取要插入的定额数据
    //@param {Array}rows @return {Array}
    function getInsertRations(rows) {
        let rst = [];
        //当前已存在定额
        let curRationIems = [];
        let selected = bills.tree.selected.guidance.tree.selected;
        if (selected) {
            if (selected.data.type === itemType.job) {
                curRationIems = selected.children;
            }
            else {
                curRationIems = selected.parent ? selected.parent.children : selected.tree.roots;
            }
        }
        for (let row of rows) {
            let selRation = ration.cache[row];
            if (selRation) {
                //添加的定额是否已存在，不重复添加
                let isExist = false;
                for (let curRation of curRationIems) {
                    if (curRation.data.rationID == selRation.ID) {
                        isExist = true;
                        break;
                    }
                }
                if (!isExist) {
                    rst.push({ type: itemType.ration, name: getRationItemName(selRation), rationID: selRation.ID });
                }
            }
        }
        return rst;
    }
    //获取块节点父项不存在于选中节点中的节点
    //@param {Array}nodes(选中的节点) @return {Array}
    function getBlockNodes(nodes) {
        let nodeMapping = {};
        for (let node of nodes) {
            nodeMapping[node.data.ID] = node;
        }
        //块节点，父项不存在于选中节点中的节点
        let blockNodes = [];
        for (let node of nodes) {
            if (!nodeMapping[node.data.ParentID]) {
                blockNodes.push(node);
            }
        }
        return blockNodes;
    }
    //允许复制整块,如果有多个块节点，且块节点的父项不同，则不可复制
    //@param {Array}nodes(块节点) @return {Boolean}
    function canCopyBlock(nodes) {
        if (!nodes || nodes.length === 0) {
            return false;
        }
        let pID = nodes[0].data.ParentID;
        for (let node of nodes) {
            if (node.data.ParentID !== pID) {
                return false;
            }
        }
        return true;
    }
    //允许粘贴整块 有粘贴数据，节点存在，如果粘贴到的节点为定额数据，粘贴数据为全定额数据
    //@param {Object}node(粘贴到的节点)
    function canPasteBlock(node) {
        let pasteDatas = JSON.parse(getLocalCache(itemCopyBlockKey));
        if (!pasteDatas || pasteDatas.length === 0) {
            return false;
        }
        if (!node) {
            return false;
        }
        //若粘贴到定额节点，则数据须全为定额
        if (node.data.type === itemType.ration) {
            for (let data of pasteDatas) {
                if (data.type !== itemType.ration) {
                    return false;
                }
            }
        }
        //若粘贴到非定额节点，则粘贴的顶层数据须全为非定额
        else {
            let topDatas = _.filter(pasteDatas, { ParentID: -1 });
            for (let topData of topDatas) {
                if (topData.type === itemType.ration) {
                    return false;
                }
            }
        }
        return true;
    }
    //复制整块,将块节点下所有节点数据复制一份，并且重新生成ID、ParentID、NextSiblingID，使用localStorage存储
    //@param {Array}nodes(块节点) @return {void}
    function copyBlocks(nodes) {
        nodes = _.cloneDeep(nodes);
        //将块节点的ParentID暂时设置为-1
        for (let topNode of nodes) {
            topNode.data.ParentID = -1;
        }
        let copyDatas = [];
        let copyNodes = [];
        //获取块节点包含的所有节点（包括自己）
        function containNodes(nodes) {
            for (let node of nodes) {
                copyNodes.push(node);
                if (node.children.length > 0) {
                    containNodes(node.children);
                }
            }
        }
        containNodes(nodes);
        for (let node of copyNodes) {
            copyDatas.push(node.data);
        }
        console.log(`copyDatas`);
        console.log(copyDatas);
        setLocalCache(itemCopyBlockKey, JSON.stringify(copyDatas));
    }
    //粘贴整块，整块数据粘贴到相关节点，并成为其后项
    //@param {Object}node(粘贴到的节点) @return {void}
    function pasteBlock(node) {
        let itemObj = bills.tree.selected.guidance;
        let pasteDatas = JSON.parse(getLocalCache(itemCopyBlockKey));
        //整理ID
        let IDMapping = {};
        for (let data of pasteDatas) {
            data.newID = uuid.v1();
            IDMapping[data.ID] = data;
        }
        for (let data of pasteDatas) {
            let nextData = IDMapping[data.NextSiblingID];
            data.NextSiblingID = nextData ? nextData.newID : -1;
            let parentData = IDMapping[data.ParentID];
            data.ParentID = parentData ? parentData.newID : -1;
        }
        for (let data of pasteDatas) {
            data.ID = data.newID;
            delete data.newID;
        }
        let updateDatas = [];
        //将最顶层的块数据的ParentID设置成粘贴到节点的ParentID,并设置新的billsID
        let topDatas = _.filter(pasteDatas, { ParentID: -1 });
        for (let topData of topDatas) {
            topData.ParentID = node.getParentID();
        }
        //更新数据
        //更新插入的最末顶层数据NextSiblingID
        if (node.nextSibling) {
            topDatas[topDatas.length - 1].NextSiblingID = node.getNextSiblingID();
        }
        //新建节点
        for (let data of pasteDatas) {
            data.libID = libID;
            data.billsID = node.data.billsID;
            delete data._id;
            updateDatas.push({ updateType: updateType.create, updateData: data });
        }
        console.log(`pasteDatas`);
        console.log(pasteDatas);
        //更新粘贴到的节点的NextSiblingID
        updateDatas.push({ updateType: updateType.update, findData: { ID: node.data.ID }, updateData: { NextSiblingID: topDatas[0].ID } })
        $.bootstrapLoading.start();
        updateGuideItems(updateDatas, function (rstData) {
            $.bootstrapLoading.end();
            node.data.NextSiblingID = topDatas[0].ID;
            let newNodes = itemObj.tree.insertDatasTo(node.data, pasteDatas);
            cleanData(guideItem.workBook.getActiveSheet(), guideItem.headers, -1);
            itemObj.controller.showTreeData();
            setNodesColor(guideItem.workBook.getActiveSheet(), bills.tree.selected.guidance.tree.items);
            setProcessNodes(guideItem.workBook.getActiveSheet(), bills.tree.selected.guidance.tree.items);
        }, function () {
            $.bootstrapLoading.end();
        });
    }

    // 双击定位定额
    async function locateAtRation(row) {
        const node = bills.tree.selected.guidance.tree.items[row];
        if (!node || !node.data.rationID) {
            return;
        }
        const rationItem = ration.datas.find(item => item.ID === node.data.rationID);
        // 当前库没有找到，需要后端查找
        if (!rationItem) {
            const findedRation = await ajaxPost('/rationRepository/api/getRationByID', { ID: node.data.rationID });
            if (!findedRation || !rationLibIDs.includes(findedRation.rationRepId)) {
                alert('无法定位此定额，此定额已被删除');
                return;
            }
            changeRationLib(findedRation.rationRepId, () => {
                located(findedRation.ID, findedRation.sectionId);
            })
        } else {
            located(rationItem.ID, rationItem.sectionId);
        }
        function located(rationID, sectionID) {
            if (!section.tree) {
                return;
            }
            const sectionNode = section.tree.nodes[section.tree.prefix + sectionID];
            if (!sectionNode) {
                return;
            }
            // 定位到对应章节树
            const sectionSheet = section.workBook.getActiveSheet();
            const sectionRow = sectionNode.serialNo();
            expandSearchNodes(sectionSheet, [sectionNode], section.tree.roots);
            sectionSheet.setSelection(sectionRow, 0, 1, 1);
            sectionSheet.showRow(sectionRow, GC.Spread.Sheets.VerticalPosition.center);
            sectionInitSel(sectionRow);

            // 定位到对应定额
            const locatedRationRow = ration.cache.findIndex(item => item.ID === rationID);
            if (locatedRationRow !== -1) {
                const rationSheet = ration.workBook.getActiveSheet();
                rationSheet.setSelection(locatedRationRow, 1, 1, 1);
                rationSheet.showRow(locatedRationRow, GC.Spread.Sheets.VerticalPosition.center);
            }
        }
    }

    //初始化右键菜单
    //@return {void}
    function initContextMenu() {
        $.contextMenu({
            selector: '#guideItemSpread',
            build: function ($triggerElement, e) {
                //控制允许右键菜单在哪个位置出现
                let sheet = guideItem.workBook.getSheet(0);
                let offset = $("#guideItemSpread").offset(),
                    x = e.pageX - offset.left,
                    y = e.pageY - offset.top;
                let target = sheet.hitTest(x, y);
                if (target.hitTestType === 3 && typeof target.row !== 'undefined' && typeof target.col !== 'undefined') {//在表格内
                    let sel = sheet.getSelections()[0];
                    if (sel && sel.rowCount === 1) {
                        sheet.setActiveCell(target.row, target.col);
                    }
                    sel = sheet.getSelections()[0];
                    let selNodes = [];
                    if (sel) {
                        sel.row = sel.row === -1 ? 0 : sel.row;
                        for (let i = 0; i < sel.rowCount; i++) {
                            if (bills.tree.selected.guidance.tree.items[sel.row + i]) {
                                selNodes.push(bills.tree.selected.guidance.tree.items[sel.row + i]);
                            }
                        }
                    }
                    //块节点
                    let blockNodes = getBlockNodes(selNodes);
                    //右键在多选内则不重设焦点
                    if (!sel || sel.rowCount === 1 || !(target.row >= sel.row && target.row <= sel.row + sel.rowCount - 1)) {
                        sheet.setActiveCell(target.row, target.col);
                    }
                    guideItemInitSel(target.row);
                    return {
                        callback: function () { },
                        items: {

                            "copy": {
                                name: "复制整块",
                                disabled: function () {
                                    return locked || !canCopyBlock(blockNodes);
                                },
                                icon: "fa-copy",
                                callback: function (key, opt) {
                                    copyBlocks(blockNodes);
                                }
                            },
                            "paste": {
                                name: "粘贴整块",
                                disabled: function () {
                                    let pasteNode = bills.tree.selected.guidance.tree.items[target.row];
                                    return locked || !canPasteBlock(pasteNode);
                                },
                                icon: "fa-paste",
                                callback: function (key, opt) {
                                    let pasteNode = bills.tree.selected.guidance.tree.items[target.row];
                                    pasteBlock(pasteNode);
                                }
                            },
                            "delRations": {
                                name: '删除定额',
                                disabled: function () {
                                    let node = bills.tree.selected.guidance.tree.items[target.row];
                                    return locked || !node
                                },
                                icon: "fa-remove",
                                callback: function (key, opt) {
                                    $('#delRationAlert').modal('show');
                                }
                            },
                            "del": {
                                name: '删除',
                                disabled: function () {
                                    let node = bills.tree.selected.guidance.tree.items[target.row];
                                    return locked || !node
                                },
                                icon: "fa-remove",
                                callback: function (key, opt) {
                                    $('#delAlert').modal('show');
                                }
                            },
                            "insertSibling": {
                                name: '插入行',
                                disabled: function () {
                                    let node = bills.tree.selected.guidance.tree.items[target.row];
                                    return locked || !node || node.data.type !== itemType.job;
                                },
                                icon: "fa-arrow-left",
                                callback: function (key, opt) {
                                    insert([{ type: itemType.job, name: '' }], false);
                                }
                            },
                            "insertChild": {
                                name: '插入子项',
                                disabled: function () {
                                    let node = bills.tree.selected.guidance.tree.items[target.row];
                                    return locked || !node || node.data.type !== itemType.job || !allJobChildren(node);
                                },
                                icon: 'fa-arrow-left',
                                callback: function (key, opt) {
                                    insert([{ type: itemType.job, name: '' }], true);
                                }
                            }
                        }
                    };
                }
                else {
                    return false;
                }
            }
        });
    }

    // 替换定额
    function replaceRation(newRation, orgItem) {
        if (newRation.ID === orgItem.data.rationID) {
            return;
        }
        $.bootstrapLoading.start();
        const updateAttrs = { name: getRationItemName(newRation), rationID: newRation.ID };
        const updateData = [{ updateType: updateType.update, findData: { ID: orgItem.getID() }, updateData: updateAttrs }];
        updateGuideItems(updateData, function () {
            Object.assign(orgItem.data, updateAttrs);
            const row = orgItem.serialNo();
            guideItem.workBook.getSheet(0).setValue(row, 0, orgItem.data.name);
            $.bootstrapLoading.end();
        });
    }

    // 初始化清单右键菜单
    function initRationContextMenu() {
        $.contextMenu({
            selector: '#rationSpread',
            build: function ($triggerElement, e) {
                //控制允许右键菜单在哪个位置出现
                let sheet = ration.workBook.getActiveSheet();;
                let offset = $("#rationSpread").offset(),
                    x = e.pageX - offset.left,
                    y = e.pageY - offset.top;
                let target = sheet.hitTest(x, y);
                if (target.hitTestType === 3 && typeof target.row !== 'undefined' && typeof target.col !== 'undefined') {//在表格内
                    let sel = sheet.getSelections()[0];
                    if (sel && sel.rowCount === 1) {
                        sheet.setActiveCell(target.row, target.col);
                    }
                    sel = sheet.getSelections()[0];
                    if (sel) {
                        sel.row = sel.row === -1 ? 0 : sel.row;
                    }
                    //右键在多选内则不重设焦点
                    if (!sel || sel.rowCount === 1 || !(target.row >= sel.row && target.row <= sel.row + sel.rowCount - 1)) {
                        sheet.setActiveCell(target.row, target.col);
                    }
                    return {
                        callback: function () { },
                        items: {
                            "replace": {
                                name: "替换定额",
                                disabled: function () {
                                    return locked || !bills.tree.selected || !bills.tree.selected.guidance || !bills.tree.selected.guidance.tree || !bills.tree.selected.guidance.tree.selected || bills.tree.selected.guidance.tree.selected.data.type !== itemType.ration;
                                },
                                icon: "fa-copy",
                                callback: function (key, opt) {
                                    const row = sheet.getActiveRowIndex();
                                    if (!ration.cache[row]) {
                                        return;
                                    }
                                    replaceRation(ration.cache[row], bills.tree.selected.guidance.tree.selected);
                                    // copyBlocks(blockNodes);
                                }
                            },
                        }
                    };
                }
                else {
                    return false;
                }
            }
        });
    }

    // 初始化定额右键菜单
    function initBillsContextMenu() {
        $.contextMenu({
            selector: '#billsSpread',
            build: function ($triggerElement, e) {
                //控制允许右键菜单在哪个位置出现
                let sheet = bills.workBook.getActiveSheet();;
                let offset = $("#billsSpread").offset(),
                    x = e.pageX - offset.left,
                    y = e.pageY - offset.top;
                let target = sheet.hitTest(x, y);
                if (target.hitTestType === 3 && typeof target.row !== 'undefined' && typeof target.col !== 'undefined') {//在表格内
                    let sel = sheet.getSelections()[0];
                    if (sel && sel.rowCount === 1) {
                        sheet.setActiveCell(target.row, target.col);
                    }
                    sel = sheet.getSelections()[0];
                    if (sel) {
                        sel.row = sel.row === -1 ? 0 : sel.row;
                    }
                    //右键在多选内则不重设焦点
                    if (!sel || sel.rowCount === 1 || !(target.row >= sel.row && target.row <= sel.row + sel.rowCount - 1)) {
                        sheet.setActiveCell(target.row, target.col);
                    }
                    billsInitSel(target.row, { row: bills.tree.selected.serialNo() });
                    return {
                        callback: function () { },
                        items: {
                            "replace": {
                                name: "配置材料",
                                disabled: function () {
                                    return !bills.tree.selected;
                                },
                                icon: "fa-edit",
                                callback: function (key, opt) {
                                    $('#bill-material-modal').modal('show');
                                }
                            },
                        }
                    };
                }
                else {
                    return false;
                }
            }
        });
    }

    //展开至搜索出来点的节点
    //@param {Array}nodes @return {void}
    function expandSearchNodes(sheet, nodes, roots) {
        renderSheetFunc(sheet, function () {
            function expParentNode(node) {
                if (node.parent) {
                    if (!node.parent.expanded) {
                        node.parent.setExpanded(true);
                    }
                    expParentNode(node.parent);
                }
            }
            for (let node of nodes) {
                expParentNode(node);
            }
            TREE_SHEET_HELPER.refreshNodesVisible(roots, sheet, true);
        });
    }

    // 清空搜索高亮
    function clearHighLight(sheet) {
        renderSheetFunc(sheet, () => {
            for (let i = 0; i < sheet.getRowCount(); i++) {
                setBgColor(sheet, i, 'white')
            }
        });
    }

    // 清空搜索状态

    // 关闭搜索清单结果
    function closeSearchBills(sheet) {
        if (!bills.tree) {
            return;
        }
        $('#searchBillsResult').hide();
        bills.tree.items.forEach(node => {
            node.isSearch = false;
        });
        clearHighLight(sheet);
        setBgColor(sheet, sheet.getActiveRowIndex(), selectedBgColor);
    }

    // 搜索清单
    function searchBills() {
        if (!bills.tree || !bills.workBook) {
            return;
        }
        const sheet = bills.workBook.getActiveSheet();
        const str = $('#searchBillText').val().trim();
        // 空搜索字符，关闭搜索
        if (!str) {
            if ($('#searchBillsResult').is(':visible')) {
                closeSearchBills(sheet);
            }
            return;
        }

        // 过滤清单
        const result = bills.tree.items.filter(item => {
            const codeIs = item.data.code ? item.data.code.indexOf(str) !== -1 : false;
            const nameIs = item.data.name ? item.data.name.indexOf(str) !== -1 : false;
            return codeIs || nameIs;
        });
        if (!result.length) {
            closeSearchBills(sheet);
            return;
        }
        // 显示搜索结果
        $("#searchBillsResult").show();
        $('#searchBillsCount').text(result.length);

        //展开搜索出来的节点
        expandSearchNodes(sheet, result, bills.tree.roots);
        // 标黄结果
        clearHighLight(sheet);
        const col = sheet.getActiveColumnIndex();
        renderSheetFunc(sheet, function () {
            bills.controller.setTreeSelected(result[0]);
            bills.tree.items.forEach(node => {
                if (result.includes(node)) {
                    setBgColor(sheet, node.serialNo(), searchBgColor);
                    node.isSearch = true; // 标记为搜索结果，防止被焦点行变更恢复颜色
                } else {
                    node.isSearch = false;
                }
            })
        });
        //搜索初始定位
        const row = sheet.getActiveRowIndex();
        sheet.setSelection(result[0].serialNo(), col, 1, 1);
        billsInitSel(result[0].serialNo(), { row });
        sheet.showRow(result[0].serialNo(), GC.Spread.Sheets.VerticalPosition.center);

        let curIndex = 0;

        // 上一条
        $('#preBill').unbind('click');
        $('#preBill').bind('click', () => {
            const node = result[curIndex - 1];
            if (!node) {
                return;
            }
            curIndex -= 1;
            const col = sheet.getActiveColumnIndex();
            const row = node.serialNo();
            const orgRow = sheet.getActiveRowIndex();
            sheet.setSelection(row, col, 1, 1);
            billsInitSel(row, { row: orgRow });
            sheet.showRow(row, GC.Spread.Sheets.VerticalPosition.center);
        });

        // 下一条
        $('#nextBills').unbind('click');
        $('#nextBills').bind('click', () => {
            const node = result[curIndex + 1];
            if (!node) {
                return;
            }
            curIndex += 1;
            const col = sheet.getActiveColumnIndex();
            const row = node.serialNo();
            const orgRow = sheet.getActiveRowIndex();
            sheet.setSelection(row, col, 1, 1);
            billsInitSel(row, { row: orgRow });
            sheet.showRow(row, GC.Spread.Sheets.VerticalPosition.center);
        });

    }

    //初始化dom时间
    //@return {void}
    function initDomEvents() {

        // 清单材料窗口
        $("#bill-material-modal").on('hidden.bs.modal', function () {
            billMaterial.cache = [];
            showBillMaterialData(billMaterial.workBook.getSheet(0), billMaterial.headers, billMaterial.cache, 30);
        });
        $("#bill-material-modal").on('shown.bs.modal', function () {
            if (billMaterial.workBook && billMaterialHelper.workBook) {
                billMaterialHelper.workBook.refresh();
                billMaterial.workBook.refresh();
                getBillMaterials();
            }
        });

        /* 自动配置材料 */
        $('#autoSetMaterial').click(async function() {
            $.bootstrapLoading.start();
            try {
                await ajaxPost('/billsGuidance/api/autoSetMaterial', { libID}, 1000 * 60 * 10);
                // 刷新显示
                window.location.reload();
            } catch (error) {
                alert(error);
            }
            $.bootstrapLoading.end();
        });

        /* excel */
        // 生成excel
        $('#buildExcel').click(function () {
            if (bills && bills.tree) {
                $('#excel-modal').modal('show');
            }
        });
        $("#excel-modal").on('hidden.bs.modal', function () {
            $('#excel-info').text(`导出排版中：`);
            $('#excel-spread').hide();
            $('#export-excel-confirm').hide();
            $('#excel-dialog').width('500px');
            if (exportExcelWorkBook) {
                exportExcelWorkBook.clearSheets();
                exportExcelWorkBook.addSheet();
            }
        });
        let excelInstance;
        $("#excel-modal").on('shown.bs.modal', async function () {
            exportExcelWorkBook.refresh();
            if (bills.tree) {
                excelInstance = new ExportExcel(exportExcelWorkBook, bills.tree, libID);
                try {
                    // await excelInstance.paintOnSheet();
                    await excelInstance.paintOnWorkBook();
                } catch (error) {
                    console.log(error);
                    alert(error);
                    $("#excel-modal").modal('hide');
                }
            }
        });
        $('#export-excel-confirm').click(() => {
            if (excelInstance) {
                excelInstance.export();
            }
        })

        $('#insert').click(function () {
            insert([{ type: itemType.job, name: '', outputItemCharacter: true }], false);
        });
        $('#delConfirm').click(function () {
            del();
            $('#delAlert').modal('hide');
        });
        $('#delRationConfirm').click(function () {
            delRations();
            $('#delRationAlert').modal('hide');
        });
        $('#del').click(function () {
            $('#delAlert').modal('show');
        });
        $('#upLevel').click(function () {
            upLevel();
        });
        $('#downLevel').click(function () {
            downLevel();
        });
        $('#upMove').click(function () {
            upMove();
        });
        $('#downMove').click(function () {
            downMove();
        });
        // 收起清单至第二层
        $('#expandToSecond').click(function () {
            if (!bills.tree || !bills.workBook) {
                return;
            }
            bills.tree.roots.forEach(root => {
                root.setExpanded(true);
                root.children.forEach(item => {
                    item.setExpanded(false);
                });
            });
            const billSheet = bills.workBook.getActiveSheet();
            renderSheetFunc(billSheet, function () {
                TREE_SHEET_HELPER.refreshNodesVisible(bills.tree.roots, billSheet, true);
            });
        })
        //收起定额、展开全部
        $('#expandContract').click(function () {
            //目前状态时展开全部节点状态，点击则收起定额
            let tree = bills.tree.selected.guidance.tree,
                itemSheet = guideItem.workBook.getActiveSheet();
            if (curExpandState === itemExpandState.expand) {
                curExpandState = itemExpandState.contract;
                $(this).html('<i class="fa fa-plus-square-o" aria-hidden="true"></i> 展开全部');
                setNodesExpandState(tree.items, itemExpandState.contract);
            } else {
                curExpandState = itemExpandState.expand;
                $(this).html('<i class="fa fa-minus-square-o" aria-hidden="true"></i> 收起定额');
                setNodesExpandState(tree.items, itemExpandState.expand);
            }
            renderSheetFunc(itemSheet, function () {
                TREE_SHEET_HELPER.refreshNodesVisible(tree.roots, itemSheet, true);
            });
        });
        // 配置材料
        $('#editMaterial').click(function () {
            if (bills && bills.tree && bills.tree.selected) {
                $('#bill-material-modal').modal('show');
            }
        });
        // 插入选中定额
        $('#insertRation').click(function () {
            let checkedRows = getCheckedRationRows();
            let insertDatas = getInsertRations(checkedRows);
            if (insertDatas.length > 0) {
                insert(insertDatas, false, function () {
                    //清空选择
                    clearCheckedRation(checkedRows);
                });
            }
            else {
                clearCheckedRation(checkedRows);
            }
        });
        // 插入全部定额
        $('#insertAll').click(function () {
            let isAll = true;
            let checkedRows = getCheckedRationRows(isAll);
            let insertDatas = getInsertRations(checkedRows);
            if (insertDatas.length > 0) {
                insert(insertDatas, false);
            }
        });
        // 搜索清单
        $("#searchBillBtn").click(searchBills);
        $('#searchBillText').keyup(function (e) {
            delayKeyup(function () {
                $('#searchBillBtn').click();
            });
        });
        // 关闭搜索清单
        $('#closeSearchBills').click(() => {
            if (!bills.workBook) {
                return;
            }
            const sheet = bills.workBook.getActiveSheet();
            closeSearchBills(sheet);
        })
        //搜索定额
        $('#searchBtn').click(function () {
            let searchStr = $('#searchText').val();
            if (!searchStr || searchStr === '') {
                ration.cache = ration.datas;
            }
            else {
                let reg = new RegExp(searchStr, 'i');
                ration.cache = _.filter(ration.datas, function (data) {
                    return reg.test(data.code) || reg.test(data.name);
                });
            }
            $('.top-content').hide();
            $('#deResize').hide();
            $('#searchCount').text(`搜索结果: ${ration.cache.length}`);
            $('#rationSearchResult').show();
            autoFlashHeight();
            ration.workBook.refresh();
            let rationSheet = ration.workBook.getActiveSheet();
            renderSheetFunc(rationSheet, function () {
                clearCheckedRation(getCheckedRationRows());
                showData(rationSheet, ration.headers, ration.cache);
            })
        });
        //关闭搜索
        $('#rationSearchResult a').click(function () {
            $('.top-content').show();
            $('#deResize').show();
            $('#rationSearchResult').hide();
            autoFlashHeight();
            renderSheetFunc(ration.workBook.getActiveSheet(), function () {
                clearCheckedRation(getCheckedRationRows());
            });
            section.workBook.refresh();
            ration.workBook.refresh();
            $('#searchText').val('');
            //恢复章节树下的定额
            sectionInitSel(section.workBook.getActiveSheet().getActiveRowIndex());
        });
        let keyupTime = 0,
            delayTime = 500;
        function delayKeyup(callback) {
            let nowTime = Date.now();
            keyupTime = nowTime;
            setTimeout(function () {
                if (nowTime - keyupTime == 0) {
                    callback();
                }
            }, delayTime);
        }
        //执行搜索
        $('#searchText').keyup(function (e) {
            delayKeyup(function () {
                $('#searchBtn').click();
            });
        });
        //编辑清单备注
        $('.main-side-bottom').find('textarea').keyup(function () {
            let me = this;
            let node = bills.tree.selected;
            let comment = $(me).val();
            delayKeyup(function () {
                if (node) {
                    let updateData = { lastOperator: userAccount, billsLibId: billsLibId, updateId: node.getID(), field: 'comment', data: comment };
                    updateBillsComment(updateData, function () {
                        node.data.comment = comment;
                    }, function () {
                        $(me).val(node.data.comment || '');
                    })
                }
            });
        });
        //编辑选项备注
        $('.main-bottom-content').find('textarea').keyup(function () {
            let me = this;
            let node = bills.tree.selected.guidance.tree.selected;
            let comment = $(me).val();
            delayKeyup(function () {
                if (node) {
                    let updateDatas = [{ updateType: updateType.update, findData: { ID: node.getID() }, updateData: { comment: comment } }];
                    updateGuideItems(updateDatas, function (rstData) {
                        node.data.comment = comment;
                    }, function () {
                        $(me).val(node.data.comment || '');
                    });
                }
            });
        });
        //定额高度拖动调整
        let heightEleObj = {
            module: moduleName,
            resize: $('#deResize'),
            top: $('#topContent'),
            topSpread: $('#sectionSpread'),
            bottom: $('#bottomContent'),
            bottomSpread: $('#rationSpread')
        },
            heightLimit = {
                min: 150,
                max: `$(window).height()-$('.header').height()-$('.sidebar-tools-bar').height()-150-10`,
                notTopSpread: 0,
                notBottomSpread: 0,
            };
        SlideResize.verticalSlide(heightEleObj, heightLimit, function () {
            if (section.workBook) {
                section.workBook.refresh();
            }
            if (ration.workBook) {
                ration.workBook.refresh();
            }
        });
        /*slideResize(rationLibResizeEles, {min: 147, max: 680}, 'height', function() {
            //autoFlashHeight();
            if(section.workBook){
                section.workBook.refresh();
            }
            if(ration.workBook){
                ration.workBook.refresh();
            }
        });*/
        //左右拖动
        //清单表与项目指引表
        let leftElesObj = {};
        leftElesObj.module = moduleName;
        leftElesObj.resize = $('#slideResizeLeft');
        leftElesObj.parent = $('#dataRow');
        leftElesObj.left = $('#leftContent');
        leftElesObj.right = $('#midContent');
        SlideResize.horizontalSlide(leftElesObj, { min: 200, max: `$('#dataRow').width() - $('#rightContent').width() - 200` }, function () {
            refreshALlWorkBook();
        });
        //人材机表与人材机组成物表
        let rightElesObj = {};
        rightElesObj.module = moduleName;
        rightElesObj.resize = $('#slideResizeRight');
        rightElesObj.parent = $('#dataRow');
        rightElesObj.left = $('#midContent');
        rightElesObj.right = $('#rightContent');
        SlideResize.horizontalSlide(rightElesObj, { min: 200, max: `$('#dataRow').width() - $('#leftContent').width() - 200` }, function () {
            refreshALlWorkBook();
        });
    }
    //刷新全部工作簿
    //@return {void}
    function refreshALlWorkBook() {
        if (bills.workBook) {
            bills.workBook.refresh();
        }
        if (guideItem.workBook) {
            guideItem.workBook.refresh();
        }
        if (section.workBook) {
            section.workBook.refresh();
        }
        if (ration.workBook) {
            ration.workBook.refresh();
        }
        $('.main-side-bottom').find('textarea').height($('.main-side-bottom').height() - 20);
        $('.main-side-bottom').find('textarea').width($('.main-side-bottom').width() - 25);
        $('.main-bottom-content').find('textarea').height($('.main-bottom-content').height() - 20);
        $('.main-bottom-content').find('textarea').width($('.main-bottom-content').width() - 25);
    }
    //读取拖动相关
    //@return {void}
    function initSlideSize() {
        //定额表上下
        let heightEleObj = {
            module: moduleName,
            top: $('#topContent'),
            topSpread: $('#sectionSpread'),
            bottom: $('#bottomContent'),
            bottomSpread: $('#rationSpread')
        };
        if (!$('#rationSearchResult').is(':visible')) {
            SlideResize.loadVerticalHeight(heightEleObj.module, heightEleObj,
                {
                    totalHeight: `$(window).height()-$('.header').height()-$('.sidebar-tools-bar').height()-10`,
                    notTopSpread: 0, notBottomSpread: 0
                }, function () {
                    if (section.workBook) {
                        section.workBook.refresh();
                    }
                    if (ration.workBook) {
                        ration.workBook.refresh();
                    }
                });
        }
        //水平
        SlideResize.loadHorizonWidth(moduleName, [$('#slideResizeLeft'), $('#slideResizeRight')], [$('#leftContent'), $('#midContent'), $('#rightContent')], function () {
            refreshALlWorkBook();
        });
    }
    //初始化视图
    //@param {void} @return {void}
    function initViews() {
        let modules = [bills, guideItem, section, ration, billMaterial, billMaterialHelper];
        initWorkBooks(modules);
        lockUtil.lockTools($(document.body), locked);
        getLibWithBills(libID);
        initDomEvents();
        initContextMenu();
        initRationContextMenu();
        //initBillsContextMenu();
        initSlideSize();
    }

    // 判断蓝色子项和孙子项是否打勾了必填
    function hasRequireData(node) {
        const requiredList = node.getPosterity().filter(subNode =>
            subNode.data.required === true
        );
        if (requiredList.length) { return true }
        return false;
    }

    // 这里判断有无无定额的情况，有的就返回true，没有就返回false
    function isAllRationData(node) {
        let isRequired = true;
        if (node.children && node.children.length) {
            node.children.forEach(subNode => {
                if (!subNode.children || !subNode.children.length) {
                    isRequired = false;
                }
            })
        }
        //这里是兼容第一层直接是定额的白色节点
        if (node.children && node.children.length) {
            node.children.forEach(subNode => {
                if (subNode.data.rationID) {
                    isRequired = true;
                }
            })
        }
        return isRequired;
    }
    // 获取选套定额
    function getOptionalData(node, list = []) {
        if (isProcessNode(node)) {
            node.children.forEach(element => {
                if (element.children && element.children.length) {
                    element.children.forEach(item => {
                        if (item.data.rationID) {
                            list.push(item.data.rationID);
                        } else if (isProcessNode(item)) {
                            getOptionalData(item, list)
                        }
                    })
                }
            });
        } else {
            node.children.forEach(element => {
                if (element.data.rationID) {
                    list.push(element.data.rationID);
                }
            });
        }
        return list;
    }

    // 获取必填项下的ID和name的键值对
    function getClassCodeStrData(nodes,data={classGroups:{},keyGroup:{}}){
         nodes.forEach(node=>{
            if (isProcessNode(node)&&node.data.required) {
                node.children.forEach(subNode=>{
                    data.classGroups[subNode.data.ID]=subNode.data.name;  
                    data.keyGroup[subNode.data.ID]=`${subNode.parent.data.name}:${subNode.data.name}`; 
                })
            }
            getClassCodeStrData(node.children,data);
         })
        return data;
    }

    //获取定额数据
    // requireRationData必套定额对象
    // optionalRationData 选逃定额对象
    // classGroups classCode文字和id键值对
    // classCodeList 各个classCode的pID和ID的关系
    function getItemData(nodes, requireRationData = {}, optionalRationData = {}, classGroups = {}, prefixID = '', prefixSonID = '', IDData = {},keyGroup={}) {
        const processNodes = nodes.filter(node => isProcessNode(node));
        // const classGroups = []; // 同层必填选项的数组（二维数组）
        processNodes.forEach(processNode => {
            // 蓝色节点，必填
            if (processNode.data.required) {
                // 白色节点
                const optionNodes = processNode.children.filter(node => isOptionNode(node));
                optionNodes.forEach(optionNode => {
                    if (!requireRationData[optionNode.data.ID]) requireRationData[optionNode.data.ID] = [];
                    if (!optionalRationData[optionNode.data.ID]) optionalRationData[optionNode.data.ID] = [];
                    //白色节点下没有蓝色节点，就是到底了
                    if (!optionNode.children.some(subOptionNode => isProcessNode(subOptionNode))) {
                        // 是否必套定额
                        if (isAllRationData(optionNode)) {
                            optionNode.children.forEach(subOptionNode => {
                                requireRationData[optionNode.data.ID].push([subOptionNode.data.rationID]);
                            })
                        } else {
                            optionalRationData[optionNode.data.ID] = getOptionalData(optionNode);
                        }
                        const kV = {};
                        kV[optionNode.data.ID] = optionNode.data.name;
                        Object.assign(classGroups, kV);

                        const keyData={};
                        keyData[optionNode.data.ID] = `${optionNode.parent.data.name}:${optionNode.data.name}`;
                        Object.assign(keyGroup,keyData);
                    } else {
                        const kV = {};
                        kV[optionNode.data.ID] = optionNode.data.name;
                        Object.assign(classGroups, kV);

                        const keyData={};
                        keyData[optionNode.data.ID] = `${optionNode.parent.data.name}:${optionNode.data.name}`;
                        Object.assign(keyGroup,keyData);

                        // 后代项是否有必填
                        if (hasRequireData(optionNode)) {
                            //后代项有必填
                            prefixSonID = '';
                            getItemData(optionNode.children, requireRationData, optionalRationData, classGroups, optionNode.data.ID, prefixSonID, IDData,keyGroup);
                        } else {
                            //后代项无必填
                            optionNode.children.forEach(subOptionNode => {
                                // 是否必套定额
                                if (isAllRationData(optionNode)) {
                                    if (!requireRationData[subOptionNode.parent.data.ID]) requireRationData[subOptionNode.parent.data.ID] = [];
                                    requireRationData[subOptionNode.parent.data.ID].push(getOptionalData(subOptionNode));
                                } else {
                                    if (!optionalRationData[subOptionNode.parent.data.ID]) optionalRationData[subOptionNode.parent.data.ID] = [];
                                    optionalRationData[subOptionNode.parent.data.ID].push(...getOptionalData(subOptionNode));
                                }
                            })
                        }
                    }
                })
            } else {
                // 蓝色节点，非必填
                if (hasRequireData(processNode)) {
                    //后代项有必填
                    if (isProcessNode(processNode)) {
                        //蓝色
                        // 是否必套定额
                        if (isAllRationData(processNode)) {
                            processNode.children.forEach((subProcessNode) => {
                                subProcessNode.children.forEach((sSubProcessNode) => {
                                    //这里是特殊处理，因为原来的逻辑是直接把定额绑到必填白色选项中下面的，每个蓝色的一组，但是这样是不对的，需要绑定在必填白色选项下的蓝色节点，所以这里就需要传入蓝色节点的id
                                    const requireChildrenID = sSubProcessNode.parent.data.required ? prefixSonID : sSubProcessNode.data.ID;
                                    IDData[sSubProcessNode.data.ID] = prefixID;
                                    getItemData(
                                        [sSubProcessNode],
                                        requireRationData,
                                        optionalRationData,
                                        classGroups,
                                        prefixID,
                                        requireChildrenID,
                                        IDData,
                                        keyGroup
                                    )
                                })
                            })
                        }else{
                            // 全部选套就不用走循环了，直接按照选套执行
                            let key = processNode.data.ID;
                            if (prefixID) key = prefixID;
                            if (prefixSonID) key = prefixSonID;
                            if (!optionalRationData[key]) optionalRationData[key] = [];
                            optionalRationData[key].push(...getOptionalData(processNode));
                            // 因为这里没有按照走整个流程，所以文字和ID的关系需要获取补充
                           if(hasRequireData(processNode)) {
                                const result =getClassCodeStrData(processNode.children)
                                Object.assign(classGroups,result.classGroups);
                                Object.assign(keyGroup,result.keyGroup);
                            } ;
                            
                        }
                    }
                } else {
                    let key = processNode.data.ID;
                    if (prefixID) key = prefixID;
                    if (prefixSonID) key = prefixSonID;
                    // 是否必套定额
                    if (isAllRationData(processNode)) {
                        if (!requireRationData[key]) requireRationData[key] = [];
                        requireRationData[key].push(getOptionalData(processNode));
                    } else {
                        if (!optionalRationData[key]) optionalRationData[key] = [];
                        optionalRationData[key].push(...getOptionalData(processNode));
                    }
                }
            }
        })
        return { requireRationData, optionalRationData, classGroups, IDData, keyGroup }
    }




    /* 生成特征分类: 前端测试使用，想要前端测试，需要在zhiyin.html中，将id=generate-classa的按钮放开 */
    function getItemCharacterData(nodes, prefix, prefixID) {
        const processNodes = nodes.filter(node => isProcessNode(node));
        const classGroups = []; // 同层必填选项的数组（二维数组）
        processNodes.forEach(processNode => {
            const classItems = [];
            const optionNodes = processNode.children.filter(node => isOptionNode(node));
            optionNodes.forEach(optionNode => {
                // const name = prefix ? `${prefix}@${optionNode.data.name}` : optionNode.data.name;
                if (optionNode.parent && optionNode.parent.data.required && (!optionNode.children
                    || !optionNode.children.length
                    || (optionNode.children[0].data && optionNode.children[0].data.rationID)
                    || !optionNode.children.some(node => isProcessNode(node)))) {

                    // 必套定额
                    classItems.push({ name: optionNode.data.name, ID: optionNode.data.ID });

                } else {
                    // classItems.push(...getItemCharacterData(optionNode.children, optionNode.parent && optionNode.parent.data.required ? optionNode.data.name : ''));
                    const childrenClassItem = getItemCharacterData(
                        optionNode.children,
                        optionNode.parent && optionNode.parent.data.required ? optionNode.data.name : '',
                        optionNode.parent && optionNode.parent.data.required ? optionNode.data.ID : ''
                    );


                    //如果返回的子项为空，但是父项又勾选了必填，则要把本身存入数组
                    if (optionNode.parent
                        && optionNode.parent.data.required
                        && childrenClassItem.length === 0
                    ) {
                        classItems.push({ name: optionNode.data.name, ID: optionNode.data.ID });
                    } else {

                        classItems.push(...childrenClassItem);
                    }
                }
            });
            if (classItems.length) {
                classGroups.push(classItems);
            }
        });


        // 拼接上一文本
        if (classGroups[0] && classGroups[0].length) {
            // classGroups[0] = classGroups[0].map(name => prefix ? `${prefix}@${name}` : name);
            classGroups[0] = classGroups[0].map(item => {
                item.name = prefix ? `${prefix}@${item.name}` : item.name
                item.ID = prefixID ? `${prefixID}@${item.ID}` : item.ID
                return item;
            });
        }
        // 二维数组内容排列组合
        while (classGroups.length > 1) {
            const prevClassItems = classGroups[0];
            const nextClassItems = classGroups[1];
            const mergedClassItems = [];
            for (let i = 0; i < prevClassItems.length; i++) {
                for (let j = 0; j < nextClassItems.length; j++) {
                    // 拼接文本
                    const mergedName = `${prevClassItems[i].name}@${nextClassItems[j].name}`;
                    const mergedID = `${prevClassItems[i].ID}@${nextClassItems[j].ID}`;
                    mergedClassItems.push({ name: mergedName, ID: mergedID });
                }
            }
            classGroups.splice(0, 2, mergedClassItems);
        }
        // 去重（类别别名要唯一）
        const items = classGroups[0] || [];
        const nameMap = {};
        const rst = [];
        items.forEach(item => {
            if (!nameMap[item.name]) {
                rst.push(item);
            }
            nameMap[item.name] = true;
        });
        return rst;
    }

    // 获取选套定额：把所有分类数据的必套定额确定好了先。选套定额就是清单下所有定额除了必套的
    function getOptionalRationIDs(optionalRationData) {
        const optionalRationIDs = [];
        Object.values(optionalRationData).forEach(optionalRation => {
            if (optionalRation.length) optionalRationIDs.push(...optionalRation);
        })
        return [...new Set(optionalRationIDs)];
    }

    // 获取错套定额：清单下所有定额，除了分类对应的必套、选套定额
    function getErrorRationIDs(requiredRationIDList, optionalRationIDs, guideNodes) {
        const finRequireData = [];
        requiredRationIDList.forEach(requiredRationIDs => {
            finRequireData.push(...requiredRationIDs);
        })
        const errorRationIDs = [];
        guideNodes.forEach(node => {
            if (node.data.rationID && !finRequireData.includes(node.data.rationID) && !optionalRationIDs.includes(node.data.rationID)) {
                errorRationIDs.push(node.data.rationID);
            }
        });
        return [...new Set(errorRationIDs)];
    }

    //把classcode和必套选套定额结合在一起
    function combineData(codeData, requireRationData, optionalRationData, classGroups, IDData,keyGroup) {
        // 这里要记录下已经被绑定的选套定额，因为没有被用的定额需要绑定到各个classcode下
        const matchRationList = [];
        //这里需要把绑定在子节点的定额更新到必填的白色选项中（classcode的值）
        const requireCombineData = {};
        const optionCombineData = {};
        Object.keys(IDData).forEach(key => {
            if (!requireCombineData[IDData[key]]) requireCombineData[IDData[key]] = new Set();
            if (!optionCombineData[IDData[key]]) optionCombineData[IDData[key]] = new Set();
            if (requireRationData[key]) {
                requireRationData[key].forEach(subData => {
                    subData.forEach(subItem => {
                        requireCombineData[IDData[key]].add(subItem);
                    })
                })
            }
            if (optionalRationData[key]) {
                optionalRationData[key].forEach(subData => {
                    optionCombineData[IDData[key]].add(subData);
                })
            }

        })
        const finData = codeData.map(classCodeData => {
            const errorRationIDs = [];
            const optionalRationIDs = [];
            const requiredRationIDs = [];
            let name = '';
            let key = '';            
            const classCodeIDs = classCodeData.ID;
            if (/@/.test(classCodeIDs)) {
                classCodeIDs.split('@').forEach((classCodeID) => {
                    if (name) {
                        name = name + '@' + classGroups[classCodeID];
                        key += keyGroup[classCodeID];
                    } else {
                        name = classGroups[classCodeID];
                        key = keyGroup[classCodeID];
                    };
                    // 一组的必套定额，先去重
                    const unitRation = [];
                  
                    // 这里是必填选项下绑定必套定额
                    if (requireRationData[classCodeID] && requireRationData[classCodeID].length) {
                        requireRationData[classCodeID].forEach(subItem => {
                            unitRation.push(...new Set(subItem));
                        })
                        requiredRationIDs.push(unitRation);
                        // 这里也要把用过的必套定额先存起来
                        matchRationList.push(...unitRation);
                    }
                    //这里是必填选项下绑定在蓝色节点下的必套定额
                    if (requireCombineData[classCodeID] && requireCombineData[classCodeID].size) {
                        requiredRationIDs.push([...requireCombineData[classCodeID]]);
                        // 这里也要把用过的必套定额先存起来
                        matchRationList.push(...requireCombineData[classCodeID]);
                    }

                    // 这里是必填选项下绑定选套定额
                    if (optionalRationData[classCodeID] && optionalRationData[classCodeID].length) {
                        optionalRationIDs.push(...optionalRationData[classCodeID]);
                        matchRationList.push(...optionalRationData[classCodeID]);
                    }
                    //这里是必填选项下绑定在蓝色节点下的选套定额，下同
                    if (optionCombineData[classCodeID] && optionCombineData[classCodeID].size) {
                        optionalRationIDs.push(...optionCombineData[classCodeID]);
                        matchRationList.push(...optionCombineData[classCodeID]);
                    }

                })
                return { name,key, requiredRationIDs, optionalRationIDs: [...new Set(optionalRationIDs)], errorRationIDs }
            } else {
                const unitRation = [];
                name = classGroups[classCodeIDs];
                key = keyGroup[classCodeIDs];
                if (requireRationData[classCodeIDs] && requireRationData[classCodeIDs].length){
                    requireRationData[classCodeIDs].forEach(subItem => {
                        unitRation.push(...new Set(subItem));
                    })
                    requiredRationIDs.push(unitRation); 
                    // 这里也要把用过的必套定额先存起来
                    matchRationList.push(...unitRation);
                }
              
                if (requireCombineData[classCodeIDs] && requireCombineData[classCodeIDs].size) {
                    requiredRationIDs.push([...requireCombineData[classCodeIDs]])
                     // 这里也要把用过的必套定额先存起来
                     matchRationList.push(...requireCombineData[classCodeIDs]);
                };

                if (optionalRationData[classCodeIDs] && optionalRationData[classCodeIDs].length) {
                    optionalRationIDs.push(...optionalRationData[classCodeIDs]);
                    matchRationList.push(...optionalRationData[classCodeIDs]);
                }
                if (optionCombineData[classCodeIDs] && optionCombineData[classCodeIDs].size) {
                    optionalRationIDs.push(...optionCombineData[classCodeIDs]);
                    matchRationList.push(...optionalRationData[classCodeIDs]);
                }
                return { name,key, requiredRationIDs, optionalRationIDs: [...new Set(optionalRationIDs)], errorRationIDs }
            }
        })
        const unMatchRation = [];
        Object.values(optionalRationData).forEach(data => {
            data.forEach((rationID) => {
                if (!matchRationList.includes(rationID)) {
                    unMatchRation.push(rationID);
                }
            })
        })
        // 这里把没有使用过的必套定额也丢到了选套里面
        Object.values(requireRationData).forEach(data => {
            data.forEach((rationData) => {
                rationData.forEach(rationID=>{
                    if (!matchRationList.includes(rationID)) {
                        unMatchRation.push(rationID);
                    }
                })
            })
        })
        return { classData: finData, unMatchRation };
    }

    $('#generate-class').click(() => {
        if (bills.tree.selected && bills.tree.selected.guidance.tree) {
            const classCodeData = getItemCharacterData(bills.tree.selected.guidance.tree.roots);
            const { requireRationData, optionalRationData, classGroups, IDData, keyGroup } = getItemData(bills.tree.selected.guidance.tree.roots);
            //    const noMatchOptionRation=[];
            const { classData, unMatchRation } = combineData(classCodeData, requireRationData, optionalRationData, classGroups, IDData,keyGroup);
            //因为所有的选套都是一样的，所以这里就直接赋值了
            const optionalRationIDs = getOptionalRationIDs(optionalRationData);
            classData.forEach(item => {
                // 不在必填项下的选套都要绑定在每个classcode下
                item.optionalRationIDs.push(...unMatchRation);
                item.errorRationIDs = getErrorRationIDs(item.requiredRationIDs, item.optionalRationIDs, bills.tree.selected.guidance.tree.items);
            })
            console.log(optionalRationIDs);
            console.log(classData);
        }
    });
    return { initViews, initSlideSize, bills };
})();

$(document).ready(function () {
    billsGuidance.initViews();
});