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

refactor: 信息价库代码分离

vian 1 год назад
Родитель
Сommit
913bf09607

+ 6 - 0
web/maintain/price_info_lib/html/edit.html

@@ -20,6 +20,7 @@
             <div class="navbar-text"><a href="/priceInfo/main">信息价库</a><i
                     class="fa fa-angle-right fa-fw"></i><%= libName  %>
                <button id="calc-price-index">计算指数</button>     
+               <button id="match-summary">匹配总表</button> 
             </div>
 
         </nav>
@@ -108,6 +109,11 @@
         const compilationID = '<%- compilationID %>';
         const curLibPeriod = '<%- period %>';
     </script>
+    <script src="/web/maintain/price_info_lib/js/common.js"></script>
+    <script src="/web/maintain/price_info_lib/js/priceArea.js"></script>
+    <script src="/web/maintain/price_info_lib/js/priceClass.js"></script>
+    <script src="/web/maintain/price_info_lib/js/priceKeyword.js"></script>
+    <script src="/web/maintain/price_info_lib/js/priceItem.js"></script>
     <script src="/web/maintain/price_info_lib/js/index.js"></script>
 </body>
 

+ 56 - 0
web/maintain/price_info_lib/js/common.js

@@ -0,0 +1,56 @@
+
+function setAlign(sheet, headers) {
+  const fuc = () => {
+    headers.forEach(({ hAlign, vAlign }, index) => {
+      sheetCommonObj.setAreaAlign(sheet.getRange(-1, index, -1, 1), hAlign, vAlign)
+    });
+  };
+  sheetCommonObj.renderSheetFunc(sheet, fuc);
+}
+
+function setFormatter(sheet, headers) {
+  const fuc = () => {
+    headers.forEach(({ formatter }, index) => {
+      if (formatter) {
+        sheet.setFormatter(-1, index, formatter);
+      }
+    });
+  };
+  sheetCommonObj.renderSheetFunc(sheet, fuc);
+}
+
+function initSheet(dom, setting) {
+  const workBook = sheetCommonObj.buildSheet(dom, setting);
+  const sheet = workBook.getSheet(0);
+  setAlign(sheet, setting.header);
+  setFormatter(sheet, setting.header);
+  return workBook;
+}
+
+function showData(sheet, data, headers, emptyRows) {
+  const fuc = () => {
+    sheet.setRowCount(data.length);
+    data.forEach((item, row) => {
+      headers.forEach(({ dataCode }, col) => {
+        sheet.setValue(row, col, item[dataCode] || '');
+      });
+    });
+    if (emptyRows) {
+      sheet.addRows(data.length, emptyRows);
+    }
+  };
+  sheetCommonObj.renderSheetFunc(sheet, fuc);
+}
+
+const TIME_OUT = 10000;
+const libID = window.location.search.match(/libID=([^&]+)/)[1];
+
+const UpdateType = {
+  UPDATE: 'update',
+  DELETE: 'delete',
+  CREATE: 'create',
+};
+
+const DEBOUNCE_TIME = 200;
+
+const locked = lockUtil.getLocked();

+ 0 - 890
web/maintain/price_info_lib/js/index.js

@@ -1,897 +1,7 @@
-
-function setAlign(sheet, headers) {
-    const fuc = () => {
-        headers.forEach(({ hAlign, vAlign }, index) => {
-            sheetCommonObj.setAreaAlign(sheet.getRange(-1, index, -1, 1), hAlign, vAlign)
-        });
-    };
-    sheetCommonObj.renderSheetFunc(sheet, fuc);
-}
-
-function setFormatter(sheet, headers) {
-    const fuc = () => {
-        headers.forEach(({ formatter }, index) => {
-            if (formatter) {
-                sheet.setFormatter(-1, index, formatter);
-            }
-        });
-    };
-    sheetCommonObj.renderSheetFunc(sheet, fuc);
-}
-
-function initSheet(dom, setting) {
-    const workBook = sheetCommonObj.buildSheet(dom, setting);
-    const sheet = workBook.getSheet(0);
-    setAlign(sheet, setting.header);
-    setFormatter(sheet, setting.header);
-    return workBook;
-}
-
-function showData(sheet, data, headers, emptyRows) {
-    const fuc = () => {
-        sheet.setRowCount(data.length);
-        data.forEach((item, row) => {
-            headers.forEach(({ dataCode }, col) => {
-                sheet.setValue(row, col, item[dataCode] || '');
-            });
-        });
-        if (emptyRows) {
-            sheet.addRows(data.length, emptyRows);
-        }
-    };
-    sheetCommonObj.renderSheetFunc(sheet, fuc);
-}
-
-const TIME_OUT = 10000;
-const libID = window.location.search.match(/libID=([^&]+)/)[1];
-
-const UpdateType = {
-    UPDATE: 'update',
-    DELETE: 'delete',
-    CREATE: 'create',
-};
-
-const DEBOUNCE_TIME = 200;
-
-const locked = lockUtil.getLocked();
-
-// 地区表
-const AREA_BOOK = (() => {
-    const cache = areaList;
-    const setting = {
-        header: [
-            { headerName: '序号', headerWidth: 60, dataCode: 'serialNo', dataType: 'Number', hAlign: 'center', vAlign: 'center' },
-            { headerName: '地区', headerWidth: $('#area-spread').width() - 80, dataCode: 'name', dataType: 'String', hAlign: 'center', vAlign: 'center' },
-        ]
-    };
-    // 初始化表格
-    const workBook = initSheet($('#area-spread')[0], setting);
-    lockUtil.lockSpreads([workBook], locked);
-    workBook.options.allowExtendPasteRange = false;
-    workBook.options.allowUserDragDrop = true;
-    workBook.options.allowUserDragFill = true;
-    const sheet = workBook.getSheet(0);
-
-    // 排序显示
-    cache.sort((a, b) => a.serialNo - b.serialNo);
-
-    // 显示数据
-    showData(sheet, cache, setting.header);
-
-    // 编辑处理
-    async function handleEdit(changedCells) {
-        debugger;
-        const updateData = [];
-        let reSort = false;
-        changedCells.forEach(({ row, col }) => {
-            const field = setting.header[col].dataCode;
-            let value = sheet.getValue(row, col);
-            if (field === 'serialNo') {
-                reSort = true;
-                value = +value;
-            }
-            updateData.push({
-                row,
-                field,
-                value,
-                ID: cache[row].ID,
-            });
-        });
-        try {
-            await ajaxPost('/priceInfo/editArea', { updateData }, TIME_OUT);
-            updateData.forEach(({ row, field, value }) => cache[row][field] = value);
-            if (reSort) {
-                cache.sort((a, b) => a.serialNo - b.serialNo);
-                showData(sheet, cache, setting.header);
-            }
-        } catch (err) {
-            // 恢复各单元格数据
-            sheetCommonObj.renderSheetFunc(sheet, () => {
-                changedCells.forEach(({ row, col, field }) => {
-                    sheet.setValue(row, col, cache[row][field]);
-                });
-            });
-        }
-    }
-    sheet.bind(GC.Spread.Sheets.Events.ValueChanged, function (e, info) {
-        const changedCells = [{ row: info.row, col: info.col }];
-        handleEdit(changedCells);
-    });
-    sheet.bind(GC.Spread.Sheets.Events.RangeChanged, function (e, info) {
-        handleEdit(info.changedCells);
-    });
-
-    const curArea = { ID: null };
-    // 焦点变更处理
-    const debounceSelectionChanged = _.debounce(function (e, info) {
-        const row = info.newSelections && info.newSelections[0] ? info.newSelections[0].row : 0;
-        handleSelectionChanged(row);
-    }, DEBOUNCE_TIME, { leading: true }); // leading = true : 先触发再延迟
-    function handleSelectionChanged(row) {
-        const areaItem = cache[row];
-        curArea.ID = areaItem && areaItem.ID || null;
-        CLASS_BOOK.initData(libID, curArea.ID);
-    }
-    sheet.bind(GC.Spread.Sheets.Events.SelectionChanged, debounceSelectionChanged);
-
-    // 新增
-    async function insert() {
-        const data = {
-            compilationID,
-            ID: uuid.v1(),
-            name: '',
-        };
-        try {
-            $.bootstrapLoading.start();
-            await ajaxPost('/priceInfo/insertArea', { insertData: [data] });
-            // 新增的数据总是添加在最后
-            sheet.addRows(cache.length, 1);
-            cache.push(data);
-            const lastRow = cache.length - 1;
-            sheet.setSelection(lastRow, 0, 1, 1);
-            sheet.showRow(lastRow, GC.Spread.Sheets.VerticalPosition.top);
-            handleSelectionChanged(lastRow);
-        } catch (err) {
-            alert(err);
-        } finally {
-            $.bootstrapLoading.end();
-        }
-    }
-
-    // 删除
-    async function del() {
-        try {
-            $.bootstrapLoading.start();
-            await ajaxPost('/priceInfo/deleteArea', { deleteData: [curArea.ID] });
-            const index = cache.findIndex(item => item.ID === curArea.ID);
-            sheet.deleteRows(index, 1);
-            cache.splice(index, 1);
-            const row = sheet.getActiveRowIndex();
-            handleSelectionChanged(row);
-        } catch (err) {
-            alert(err);
-        } finally {
-            $.bootstrapLoading.end();
-        }
-    }
-
-    // 右键功能
-    function buildContextMenu() {
-        $.contextMenu({
-            selector: '#area-spread',
-            build: function ($triggerElement, e) {
-                // 控制允许右键菜单在哪个位置出现
-                const offset = $('#area-spread').offset();
-                const x = e.pageX - offset.left;
-                const y = e.pageY - offset.top;
-                const target = sheet.hitTest(x, y);
-                if (target.hitTestType === 3) { // 在表格内
-                    const sel = sheet.getSelections()[0];
-                    if (sel && sel.rowCount === 1 && typeof target.row !== 'undefined') {
-                        const orgRow = sheet.getActiveRowIndex();
-                        if (orgRow !== target.row) {
-                            sheet.setActiveCell(target.row, target.col);
-                            handleSelectionChanged(target.row);
-                        }
-                    }
-                    return {
-                        items: {
-                            insert: {
-                                name: '新增',
-                                icon: "fa-arrow-left",
-                                disabled: function () {
-                                    return locked;
-                                },
-                                callback: function (key, opt) {
-                                    insert();
-                                }
-                            },
-                            del: {
-                                name: '删除',
-                                icon: "fa-arrow-left",
-                                disabled: function () {
-                                    return locked || !cache[target.row];
-                                },
-                                callback: function (key, opt) {
-                                    del();
-                                }
-                            },
-                        }
-                    };
-                }
-                else {
-                    return false;
-                }
-            }
-        });
-    }
-    buildContextMenu();
-
-    return {
-        handleSelectionChanged,
-        curArea,
-    }
-
-})();
-
-// 分类表
-const CLASS_BOOK = (() => {
-    const setting = {
-        header: [{ headerName: '分类', headerWidth: $('#area-spread').width(), dataCode: 'name', dataType: 'String', hAlign: 'left', vAlign: 'center' }],
-        controller: {
-            cols: [
-                {
-                    data: {
-                        field: 'name',
-                        vAlign: 1,
-                        hAlign: 0,
-                        font: 'Arial'
-                    },
-                }
-            ],
-            headRows: 1,
-            headRowHeight: [30],
-            emptyRows: 0,
-            treeCol: 0
-        },
-        tree: {
-            id: 'ID',
-            pid: 'ParentID',
-            nid: 'NextSiblingID',
-            rootId: -1
-        }
-    };
-    // 初始化表格
-    const workBook = initSheet($('#class-spread')[0], setting);
-    workBook.options.allowExtendPasteRange = false;
-    workBook.options.allowUserDragDrop = true;
-    workBook.options.allowUserDragFill = true;
-    const sheet = workBook.getSheet(0);
-
-    let tree;
-    let controller;
-    // 初始化数据
-    async function initData(libID, areaID) {
-        if (!areaID) {
-            tree = null;
-            controller = null;
-            sheet.setRowCount(0);
-            PRICE_BOOK.clear();
-            return;
-        }
-        $.bootstrapLoading.start();
-        try {
-            const data = await ajaxPost('/priceInfo/getClassData', { libID, areaID }, TIME_OUT);
-            tree = idTree.createNew(setting.tree);
-            tree.loadDatas(data);
-            tree.selected = tree.items.length > 0 ? tree.items[0] : null;
-            controller = TREE_SHEET_CONTROLLER.createNew(tree, sheet, setting.controller, false);
-            controller.showTreeData();
-            handleSelectionChanged(0);
-            sheet.setSelection(0, 0, 1, 1);
-            lockUtil.lockSpreads([workBook], locked);
-        } catch (err) {
-            tree = null;
-            controller = null;
-            sheet.setRowCount(0);
-            alert(err);
-        } finally {
-            $.bootstrapLoading.end();
-        }
-    }
-
-    // 编辑处理
-    async function handleEdit(changedCells) {
-        const updateData = [];
-        changedCells.forEach(({ row, col }) => {
-            updateData.push({
-                row,
-                type: UpdateType.UPDATE,
-                filter: { ID: tree.items[row].data.ID },
-                update: { name: sheet.getValue(row, col) }
-            });
-        });
-        try {
-            await ajaxPost('/priceInfo/editClassData', { updateData }, TIME_OUT);
-            updateData.forEach(({ row, update: { name } }) => tree.items[row].data.name = name);
-        } catch (err) {
-            // 恢复各单元格数据
-            sheetCommonObj.renderSheetFunc(sheet, () => {
-                changedCells.forEach(({ row }) => {
-                    sheet.setValue(tree.items[row].data.name);
-                });
-            });
-        }
-    }
-    sheet.bind(GC.Spread.Sheets.Events.ValueChanged, function (e, info) {
-        const changedCells = [{ row: info.row, col: info.col }];
-        handleEdit(changedCells);
-    });
-    sheet.bind(GC.Spread.Sheets.Events.RangeChanged, function (e, info) {
-        handleEdit(info.changedCells);
-    });
-
-    // 树操作相关
-    const $insert = $('#tree-insert');
-    const $remove = $('#tree-remove');
-    const $upLevel = $('#tree-up-level');
-    const $downLevel = $('#tree-down-level');
-    const $downMove = $('#tree-down-move');
-    const $upMove = $('#tree-up-move');
-    const $calcPriceIndex = $('#calc-price-index');
-
-    // 插入
-    let canInsert = true;
-    async function insert() {
-        try {
-            if (!canInsert) {
-                return false;
-            }
-            canInsert = false;
-            $.bootstrapLoading.start();
-            const updateData = [];
-            const selected = tree.selected;
-            const newItem = {
-                libID,
-                areaID: AREA_BOOK.curArea.ID,
-                ID: uuid.v1(),
-                name: '',
-                ParentID: '-1',
-                NextSiblingID: '-1'
-            };
-            if (selected) {
-                newItem.ParentID = selected.data.ParentID;
-                updateData.push({
-                    type: UpdateType.UPDATE,
-                    filter: { ID: selected.data.ID },
-                    update: { NextSiblingID: newItem.ID }
-                });
-                if (selected.nextSibling) {
-                    newItem.NextSiblingID = selected.nextSibling.data.ID;
-                }
-            }
-            updateData.push({
-                type: UpdateType.CREATE,
-                document: newItem
-            });
-            await ajaxPost('/priceInfo/editClassData', { updateData });
-            controller.insertByID(newItem.ID);
-            handleSelectionChanged(sheet.getActiveRowIndex());
-        } catch (err) {
-            alert(err);
-        } finally {
-            canInsert = true;
-            $.bootstrapLoading.end();
-        }
-    }
-
-    $insert.click(_.debounce(insert, DEBOUNCE_TIME, { leading: true }));
-
-    // 删除
-    let canRemove = true;
-    async function remove() {
-        try {
-            if (!canRemove) {
-                return false;
-            }
-            canRemove = false;
-            $.bootstrapLoading.start();
-            const updateData = [];
-            const selected = tree.selected;
-            const children = selected.getPosterity();
-            [selected, ...children].forEach(node => updateData.push({
-                type: UpdateType.DELETE,
-                filter: { ID: node.data.ID }
-            }));
-            if (selected.preSibling) {
-                updateData.push({
-                    type: UpdateType.UPDATE,
-                    filter: { ID: selected.preSibling.data.ID },
-                    update: { NextSiblingID: selected.data.NextSiblingID }
-                });
-            }
-            await ajaxPost('/priceInfo/editClassData', { updateData });
-            controller.delete();
-            handleSelectionChanged(sheet.getActiveRowIndex());
-        } catch (err) {
-            alert(err);
-        } finally {
-            canRemove = true;
-            $.bootstrapLoading.end();
-        }
-    }
-
-    $remove.click(_.debounce(remove, DEBOUNCE_TIME, { leading: true }));
-
-    // 升级
-    let canUpLevel = true;
-    async function upLevel() {
-        try {
-            if (!canUpLevel) {
-                return false;
-            }
-            canUpLevel = false;
-            $.bootstrapLoading.start();
-            const updateData = [];
-            const selected = tree.selected;
-            if (selected.preSibling) {
-                updateData.push({
-                    type: UpdateType.UPDATE,
-                    filter: { ID: selected.preSibling.data.ID },
-                    update: { NextSiblingID: -1 }
-                });
-            }
-            if (selected.parent) {
-                updateData.push({
-                    type: UpdateType.UPDATE,
-                    filter: { ID: selected.parent.data.ID },
-                    update: { NextSiblingID: selected.data.ID }
-                });
-            }
-            updateData.push({
-                type: UpdateType.UPDATE,
-                filter: { ID: selected.data.ID },
-                update: { ParentID: selected.parent.data.ParentID, NextSiblingID: selected.parent.data.NextSiblingID }
-            });
-            let curNode = selected.nextSibling;
-            while (curNode) {
-                updateData.push({
-                    type: UpdateType.UPDATE,
-                    filter: { ID: curNode.data.ID },
-                    update: { ParentID: selected.data.ID }
-                });
-                curNode = curNode.nextSibling;
-            }
-            await ajaxPost('/priceInfo/editClassData', { updateData });
-            controller.upLevel();
-            refreshTreeButton(tree.selected);
-        } catch (err) {
-            alert(err);
-        } finally {
-            canUpLevel = true;
-            $.bootstrapLoading.end();
-        }
-    }
-
-    $upLevel.click(_.debounce(upLevel, DEBOUNCE_TIME, { leading: true }));
-
-    // 降级
-    let canDownLevel = true;
-    async function downLevel() {
-        try {
-            if (!canDownLevel) {
-                return false;
-            }
-            canDownLevel = false;
-            $.bootstrapLoading.start();
-            const updateData = [];
-            const selected = tree.selected;
-            if (selected.preSibling) {
-                updateData.push({
-                    type: UpdateType.UPDATE,
-                    filter: { ID: selected.preSibling.data.ID },
-                    update: { NextSiblingID: selected.data.NextSiblingID }
-                });
-                const preSiblingLastChild = selected.preSibling.children[selected.preSibling.children.length - 1];
-                if (preSiblingLastChild) {
-                    updateData.push({
-                        type: UpdateType.UPDATE,
-                        filter: { ID: preSiblingLastChild.data.ID },
-                        update: { NextSiblingID: selected.data.ID }
-                    });
-                }
-                updateData.push({
-                    type: UpdateType.UPDATE,
-                    filter: { ID: selected.data.ID },
-                    update: { ParentID: selected.preSibling.data.ID, NextSiblingID: -1 }
-                });
-            }
-            await ajaxPost('/priceInfo/editClassData', { updateData });
-            controller.downLevel();
-            refreshTreeButton(tree.selected);
-        } catch (err) {
-            alert(err);
-        } finally {
-            canDownLevel = true;
-            $.bootstrapLoading.end();
-        }
-    }
-
-    $downLevel.click(_.debounce(downLevel, DEBOUNCE_TIME, { leading: true }));
-
-    // 下移
-    let canDownMove = true;
-    async function downMove() {
-        try {
-            if (!canDownMove) {
-                return false;
-            }
-            canDownMove = false;
-            $.bootstrapLoading.start();
-            const updateData = [];
-            const selected = tree.selected;
-            if (selected.preSibling) {
-                updateData.push({
-                    type: UpdateType.UPDATE,
-                    filter: { ID: selected.preSibling.data.ID },
-                    update: { NextSiblingID: selected.data.NextSiblingID }
-                });
-            }
-            updateData.push({
-                type: UpdateType.UPDATE,
-                filter: { ID: selected.data.ID },
-                update: { NextSiblingID: selected.nextSibling.data.NextSiblingID }
-            });
-            updateData.push({
-                type: UpdateType.UPDATE,
-                filter: { ID: selected.nextSibling.data.ID },
-                update: { NextSiblingID: selected.data.ID }
-            });
-            await ajaxPost('/priceInfo/editClassData', { updateData });
-            controller.downMove();
-            refreshTreeButton(tree.selected);
-        } catch (err) {
-            alert(err);
-        } finally {
-            canDownMove = true;
-            $.bootstrapLoading.end();
-        }
-    }
-
-    $downMove.click(_.debounce(downMove, DEBOUNCE_TIME, { leading: true }));
-
-    // 上移
-    let canUpMove = true;
-    async function upMove() {
-        try {
-            if (!canUpMove) {
-                return false;
-            }
-            canUpMove = false;
-            $.bootstrapLoading.start();
-            const updateData = [];
-            const selected = tree.selected;
-            if (selected.preSibling) {
-                updateData.push({
-                    type: UpdateType.UPDATE,
-                    filter: { ID: selected.preSibling.data.ID },
-                    update: { NextSiblingID: selected.data.NextSiblingID }
-                });
-            }
-            const prePreSibling = selected.preSibling.preSibling;
-            if (prePreSibling) {
-                updateData.push({
-                    type: UpdateType.UPDATE,
-                    filter: { ID: prePreSibling.data.ID },
-                    update: { NextSiblingID: selected.data.ID }
-                });
-            }
-            updateData.push({
-                type: UpdateType.UPDATE,
-                filter: { ID: selected.data.ID },
-                update: { NextSiblingID: selected.preSibling.data.ID }
-            });
-            await ajaxPost('/priceInfo/editClassData', { updateData });
-            controller.upMove();
-            refreshTreeButton(tree.selected);
-        } catch (err) {
-            alert(err);
-        } finally {
-            canUpMove = true;
-            $.bootstrapLoading.end();
-        }
-    }
-
-    $upMove.click(_.debounce(upMove, DEBOUNCE_TIME, { leading: true }));
-
-
-    // 刷新树操作按钮有效性
-    function refreshTreeButton(selected) {
-        if (locked) {
-            return;
-        }
-        $insert.removeClass('disabled');
-        $remove.removeClass('disabled');
-        $upLevel.removeClass('disabled');
-        $downLevel.removeClass('disabled');
-        $downMove.removeClass('disabled');
-        $upMove.removeClass('disabled');
-        if (!selected) {
-            $remove.addClass('disabled');
-            $upLevel.addClass('disabled');
-            $downLevel.addClass('disabled');
-            $downMove.addClass('disabled');
-            $upMove.addClass('disabled');
-        } else {
-            if (!selected.preSibling) {
-                $downLevel.addClass('disabled');
-                $upMove.addClass('disabled');
-            }
-            if (!selected.nextSibling) {
-                $downMove.addClass('disabled');
-            }
-            if (!selected.parent) {
-                $upLevel.addClass('disabled');
-            }
-        }
-    }
-
-    // 焦点变更处理
-    const curClass = { ID: null };
-    function handleSelectionChanged(row) {
-        const classNode = tree.items[row] || null;
-        tree.selected = classNode;
-        refreshTreeButton(classNode);
-        curClass.ID = classNode && classNode.data && classNode.data.ID || null;
-        const classIDList = []
-        if (classNode) {
-            classIDList.push(classNode.data.ID);
-            const children = classNode.getPosterity();
-            children.forEach(child => classIDList.push(child.data.ID));
-        }
-        PRICE_BOOK.initData(classIDList);
-    }
-    const debounceSelectionChanged = _.debounce(function (e, info) {
-        const row = info.newSelections && info.newSelections[0] ? info.newSelections[0].row : 0;
-        handleSelectionChanged(row);
-    }, DEBOUNCE_TIME, { leading: true });
-    sheet.bind(GC.Spread.Sheets.Events.SelectionChanged, debounceSelectionChanged);
-
-
-
-    $calcPriceIndex.click(_.debounce(async () => {
-        $.bootstrapLoading.start();
-        try {
-            const data = await ajaxPost('/priceInfo/calcPriceIndex', { libID, period: curLibPeriod, compilationID }, TIME_OUT);
-            //alert(data);
-
-            if (data) {
-                const htmlStr = data.replace(/\n/gm, '<br>'); //replaceAll('\n','<br>',data);
-                $("#result-info-body").html(htmlStr);
-                $("#result-info").modal('show');
-            } else {
-                alert('计算完成!')
-            }
-
-
-        } catch (error) {
-            console.log(error);
-        }
-        $.bootstrapLoading.end();
-
-    }, DEBOUNCE_TIME, { leading: true }));
-
-
-    return {
-        initData,
-        handleSelectionChanged,
-        curClass,
-    }
-
-})();
-
-// 关键字表
-const KEYWORD_BOOK = (() => {
-    const setting = {
-        header: [
-            { headerName: '关键字', headerWidth: 200, dataCode: 'keyword', dataType: 'String', hAlign: 'left', vAlign: 'center' },
-            { headerName: '单位', headerWidth: 70, dataCode: 'unit', dataType: 'String', hAlign: 'center', vAlign: 'center' },
-            { headerName: '关键字效果', headerWidth: 100, dataCode: 'coe', dataType: 'String', hAlign: 'center', vAlign: 'center' },
-            { headerName: '组别', headerWidth: 50, dataCode: 'group', dataType: 'String', hAlign: 'center', vAlign: 'center' },
-            { headerName: '选项号', headerWidth: 70, dataCode: 'optionCode', dataType: 'String', hAlign: 'center', vAlign: 'center' },
-        ],
-    };
-    // 初始化表格
-    const workBook = initSheet($('#keyword-spread')[0], setting);
-    workBook.options.allowUserDragDrop = false;
-    workBook.options.allowUserDragFill = false;
-    lockUtil.lockSpreads([workBook], true);
-    const sheet = workBook.getSheet(0);
-
-    // 显示关键字数据
-    const showKeywordData = (keywordList) => {
-        showData(sheet, keywordList, setting.header);
-    }
-
-    return {
-        showKeywordData
-    }
-})();
-
-// 价格信息表
-const PRICE_BOOK = (() => {
-    const setting = {
-        header: [
-            { headerName: '编码', headerWidth: 100, dataCode: 'code', dataType: 'String', hAlign: 'left', vAlign: 'center', formatter: "@" },
-            { headerName: '别名编码', headerWidth: 70, dataCode: 'classCode', dataType: 'String', hAlign: 'left', vAlign: 'center', formatter: "@" },
-            { headerName: '名称', headerWidth: 200, dataCode: 'name', dataType: 'String', hAlign: 'left', vAlign: 'center' },
-            { headerName: '规格型号', headerWidth: 120, dataCode: 'specs', dataType: 'String', hAlign: 'left', vAlign: 'center' },
-            { headerName: '单位', headerWidth: 80, dataCode: 'unit', dataType: 'String', hAlign: 'center', vAlign: 'center' },
-            { headerName: '不含税价', headerWidth: 80, dataCode: 'noTaxPrice', dataType: 'String', hAlign: 'right', vAlign: 'center' },
-            { headerName: '含税价', headerWidth: 80, dataCode: 'taxPrice', dataType: 'String', hAlign: 'right', vAlign: 'center' },
-            { headerName: '月份备注', headerWidth: 140, dataCode: 'dateRemark', dataType: 'String', hAlign: 'left', vAlign: 'center' },
-            { headerName: '计算式', headerWidth: 100, dataCode: 'expString', dataType: 'String', hAlign: 'left', vAlign: 'center' },
-        ],
-    };
-    // 初始化表格
-    const workBook = initSheet($('#price-spread')[0], setting);
-    workBook.options.allowUserDragDrop = true;
-    workBook.options.allowUserDragFill = true;
-    lockUtil.lockSpreads([workBook], locked);
-    const sheet = workBook.getSheet(0);
-
-    let cache = [];
-    // 清空
-    function clear() {
-        cache = [];
-        sheet.setRowCount(0);
-    }
-    // 初始化数据
-    async function initData(classIDList) {
-        if (!classIDList || !classIDList.length) {
-            return clear();
-        }
-        $.bootstrapLoading.start();
-        try {
-            cache = await ajaxPost('/priceInfo/getPriceData', { classIDList }, TIME_OUT);
-            cache = _.sortBy(cache, 'classCode');
-            showData(sheet, cache, setting.header, 5);
-            const row = sheet.getActiveRowIndex();
-            const keywordList = cache[row] && cache[row].keywordList || [];
-            KEYWORD_BOOK.showKeywordData(keywordList);
-        } catch (err) {
-            cache = [];
-            sheet.setRowCount(0);
-            alert(err);
-        } finally {
-            $.bootstrapLoading.end();
-        }
-    }
-
-    // 获取当前表中行数据
-    function getRowData(sheet, row, headers) {
-        const item = {};
-        headers.forEach(({ dataCode }, index) => {
-            const value = sheet.getValue(row, index) || '';
-            if (value) {
-                item[dataCode] = value;
-            }
-        });
-        return item;
-    }
-
-    // 获取表数据和缓存数据的不同数据
-    function getRowDiffData(curRowData, cacheRowData, headers) {
-        let item = null;
-        headers.forEach(({ dataCode }) => {
-            const curValue = curRowData[dataCode];
-            const cacheValue = cacheRowData[dataCode];
-            if (!cacheValue && !curValue) {
-                return;
-            }
-            if (cacheValue !== curValue) {
-                if (!item) {
-                    item = {};
-                }
-                item[dataCode] = curValue || '';
-            }
-        });
-        return item;
-    }
-
-    // 编辑处理
-    async function handleEdit(changedCells) {
-        const postData = []; // 请求用
-        // 更新缓存用
-        const updateData = [];
-        const deleteData = [];
-        const insertData = [];
-        try {
-            changedCells.forEach(({ row }) => {
-                if (cache[row]) {
-                    const rowData = getRowData(sheet, row, setting.header);
-                    if (Object.keys(rowData).length) { // 还有数据,更新
-                        const diffData = getRowDiffData(rowData, cache[row], setting.header);
-                        if (diffData) {
-                            postData.push({ type: UpdateType.UPDATE, ID: cache[row].ID, data: diffData });
-                            updateData.push({ row, data: diffData });
-                        }
-                    } else { // 该行无数据了,删除
-                        postData.push({ type: UpdateType.DELETE, ID: cache[row].ID });
-                        deleteData.push(cache[row]);
-                    }
-                } else { // 新增
-                    const rowData = getRowData(sheet, row, setting.header);
-                    if (Object.keys(rowData).length) {
-                        rowData.ID = uuid.v1();
-                        rowData.libID = libID;
-                        rowData.compilationID = compilationID;
-                        rowData.areaID = AREA_BOOK.curArea.ID;
-                        rowData.classID = CLASS_BOOK.curClass.ID;
-                        rowData.period = curLibPeriod;
-                        postData.push({ type: UpdateType.CREATE, data: rowData });
-                        insertData.push(rowData);
-                    }
-                }
-            });
-            if (postData.length) {
-                await ajaxPost('/priceInfo/editPriceData', { postData }, TIME_OUT);
-                // 更新缓存,先更新然后删除,最后再新增,防止先新增后缓存数据的下标与更新、删除数据的下标对应不上
-                updateData.forEach(item => {
-                    Object.assign(cache[item.row], item.data);
-                });
-                deleteData.forEach(item => {
-                    const index = cache.indexOf(item);
-                    if (index >= 0) {
-                        cache.splice(index, 1);
-                    }
-                });
-                insertData.forEach(item => cache.push(item));
-                if (deleteData.length || insertData.length) {
-                    showData(sheet, cache, setting.header, 5);
-                }
-            }
-        } catch (err) {
-            // 恢复各单元格数据
-            showData(sheet, cache, setting.header, 5);
-        }
-    }
-    sheet.bind(GC.Spread.Sheets.Events.ValueChanged, function (e, info) {
-        const changedCells = [{ row: info.row }];
-        handleEdit(changedCells);
-    });
-    sheet.bind(GC.Spread.Sheets.Events.SelectionChanged, function (e, info) {
-        const row = info.newSelections && info.newSelections[0] ? info.newSelections[0].row : 0;
-        // 显示关键字数据
-        const keywordList = cache[row] && cache[row].keywordList || [];
-        KEYWORD_BOOK.showKeywordData(keywordList);
-    });
-    sheet.bind(GC.Spread.Sheets.Events.RangeChanged, function (e, info) {
-        const changedRows = [];
-        let preRow;
-        info.changedCells.forEach(({ row }) => {
-            if (row !== preRow) {
-                changedRows.push({ row });
-            }
-            preRow = row;
-        });
-        handleEdit(changedRows);
-    });
-
-    return {
-        clear,
-        initData,
-    }
-})();
-
 $(document).ready(() => {
     console.log('进入信息价');
     $('[data-toggle="tooltip"]').tooltip();
     AREA_BOOK.handleSelectionChanged(0);
     const $range = $(document.body);
     lockUtil.lockTools($range, locked);
-
-
-
-
 });

+ 178 - 0
web/maintain/price_info_lib/js/priceArea.js

@@ -0,0 +1,178 @@
+// 地区表
+const AREA_BOOK = (() => {
+  const cache = areaList;
+  const setting = {
+    header: [
+      { headerName: '序号', headerWidth: 60, dataCode: 'serialNo', dataType: 'Number', hAlign: 'center', vAlign: 'center' },
+      { headerName: '地区', headerWidth: $('#area-spread').width() - 80, dataCode: 'name', dataType: 'String', hAlign: 'center', vAlign: 'center' },
+    ]
+  };
+  // 初始化表格
+  const workBook = initSheet($('#area-spread')[0], setting);
+  lockUtil.lockSpreads([workBook], locked);
+  workBook.options.allowExtendPasteRange = false;
+  workBook.options.allowUserDragDrop = true;
+  workBook.options.allowUserDragFill = true;
+  const sheet = workBook.getSheet(0);
+
+  // 排序显示
+  cache.sort((a, b) => a.serialNo - b.serialNo);
+
+  // 显示数据
+  showData(sheet, cache, setting.header);
+
+  // 编辑处理
+  async function handleEdit(changedCells) {
+    debugger;
+    const updateData = [];
+    let reSort = false;
+    changedCells.forEach(({ row, col }) => {
+      const field = setting.header[col].dataCode;
+      let value = sheet.getValue(row, col);
+      if (field === 'serialNo') {
+        reSort = true;
+        value = +value;
+      }
+      updateData.push({
+        row,
+        field,
+        value,
+        ID: cache[row].ID,
+      });
+    });
+    try {
+      await ajaxPost('/priceInfo/editArea', { updateData }, TIME_OUT);
+      updateData.forEach(({ row, field, value }) => cache[row][field] = value);
+      if (reSort) {
+        cache.sort((a, b) => a.serialNo - b.serialNo);
+        showData(sheet, cache, setting.header);
+      }
+    } catch (err) {
+      // 恢复各单元格数据
+      sheetCommonObj.renderSheetFunc(sheet, () => {
+        changedCells.forEach(({ row, col, field }) => {
+          sheet.setValue(row, col, cache[row][field]);
+        });
+      });
+    }
+  }
+  sheet.bind(GC.Spread.Sheets.Events.ValueChanged, function (e, info) {
+    const changedCells = [{ row: info.row, col: info.col }];
+    handleEdit(changedCells);
+  });
+  sheet.bind(GC.Spread.Sheets.Events.RangeChanged, function (e, info) {
+    handleEdit(info.changedCells);
+  });
+
+  const curArea = { ID: null };
+  // 焦点变更处理
+  const debounceSelectionChanged = _.debounce(function (e, info) {
+    const row = info.newSelections && info.newSelections[0] ? info.newSelections[0].row : 0;
+    handleSelectionChanged(row);
+  }, DEBOUNCE_TIME, { leading: true }); // leading = true : 先触发再延迟
+  function handleSelectionChanged(row) {
+    const areaItem = cache[row];
+    curArea.ID = areaItem && areaItem.ID || null;
+    CLASS_BOOK.initData(libID, curArea.ID);
+  }
+  sheet.bind(GC.Spread.Sheets.Events.SelectionChanged, debounceSelectionChanged);
+
+  // 新增
+  async function insert() {
+    const data = {
+      compilationID,
+      ID: uuid.v1(),
+      name: '',
+    };
+    try {
+      $.bootstrapLoading.start();
+      await ajaxPost('/priceInfo/insertArea', { insertData: [data] });
+      // 新增的数据总是添加在最后
+      sheet.addRows(cache.length, 1);
+      cache.push(data);
+      const lastRow = cache.length - 1;
+      sheet.setSelection(lastRow, 0, 1, 1);
+      sheet.showRow(lastRow, GC.Spread.Sheets.VerticalPosition.top);
+      handleSelectionChanged(lastRow);
+    } catch (err) {
+      alert(err);
+    } finally {
+      $.bootstrapLoading.end();
+    }
+  }
+
+  // 删除
+  async function del() {
+    try {
+      $.bootstrapLoading.start();
+      await ajaxPost('/priceInfo/deleteArea', { deleteData: [curArea.ID] });
+      const index = cache.findIndex(item => item.ID === curArea.ID);
+      sheet.deleteRows(index, 1);
+      cache.splice(index, 1);
+      const row = sheet.getActiveRowIndex();
+      handleSelectionChanged(row);
+    } catch (err) {
+      alert(err);
+    } finally {
+      $.bootstrapLoading.end();
+    }
+  }
+
+  // 右键功能
+  function buildContextMenu() {
+    $.contextMenu({
+      selector: '#area-spread',
+      build: function ($triggerElement, e) {
+        // 控制允许右键菜单在哪个位置出现
+        const offset = $('#area-spread').offset();
+        const x = e.pageX - offset.left;
+        const y = e.pageY - offset.top;
+        const target = sheet.hitTest(x, y);
+        if (target.hitTestType === 3) { // 在表格内
+          const sel = sheet.getSelections()[0];
+          if (sel && sel.rowCount === 1 && typeof target.row !== 'undefined') {
+            const orgRow = sheet.getActiveRowIndex();
+            if (orgRow !== target.row) {
+              sheet.setActiveCell(target.row, target.col);
+              handleSelectionChanged(target.row);
+            }
+          }
+          return {
+            items: {
+              insert: {
+                name: '新增',
+                icon: "fa-arrow-left",
+                disabled: function () {
+                  return locked;
+                },
+                callback: function (key, opt) {
+                  insert();
+                }
+              },
+              del: {
+                name: '删除',
+                icon: "fa-arrow-left",
+                disabled: function () {
+                  return locked || !cache[target.row];
+                },
+                callback: function (key, opt) {
+                  del();
+                }
+              },
+            }
+          };
+        }
+        else {
+          return false;
+        }
+      }
+    });
+  }
+  buildContextMenu();
+
+  return {
+    handleSelectionChanged,
+    curArea,
+  }
+
+})();

+ 455 - 0
web/maintain/price_info_lib/js/priceClass.js

@@ -0,0 +1,455 @@
+// 分类表
+const CLASS_BOOK = (() => {
+  const setting = {
+    header: [{ headerName: '分类', headerWidth: $('#area-spread').width(), dataCode: 'name', dataType: 'String', hAlign: 'left', vAlign: 'center' }],
+    controller: {
+      cols: [
+        {
+          data: {
+            field: 'name',
+            vAlign: 1,
+            hAlign: 0,
+            font: 'Arial'
+          },
+        }
+      ],
+      headRows: 1,
+      headRowHeight: [30],
+      emptyRows: 0,
+      treeCol: 0
+    },
+    tree: {
+      id: 'ID',
+      pid: 'ParentID',
+      nid: 'NextSiblingID',
+      rootId: -1
+    }
+  };
+  // 初始化表格
+  const workBook = initSheet($('#class-spread')[0], setting);
+  workBook.options.allowExtendPasteRange = false;
+  workBook.options.allowUserDragDrop = true;
+  workBook.options.allowUserDragFill = true;
+  const sheet = workBook.getSheet(0);
+
+  let tree;
+  let controller;
+  // 初始化数据
+  async function initData(libID, areaID) {
+    if (!areaID) {
+      tree = null;
+      controller = null;
+      sheet.setRowCount(0);
+      PRICE_BOOK.clear();
+      return;
+    }
+    $.bootstrapLoading.start();
+    try {
+      const data = await ajaxPost('/priceInfo/getClassData', { libID, areaID }, TIME_OUT);
+      tree = idTree.createNew(setting.tree);
+      tree.loadDatas(data);
+      tree.selected = tree.items.length > 0 ? tree.items[0] : null;
+      controller = TREE_SHEET_CONTROLLER.createNew(tree, sheet, setting.controller, false);
+      controller.showTreeData();
+      handleSelectionChanged(0);
+      sheet.setSelection(0, 0, 1, 1);
+      lockUtil.lockSpreads([workBook], locked);
+    } catch (err) {
+      tree = null;
+      controller = null;
+      sheet.setRowCount(0);
+      alert(err);
+    } finally {
+      $.bootstrapLoading.end();
+    }
+  }
+
+  // 编辑处理
+  async function handleEdit(changedCells) {
+    const updateData = [];
+    changedCells.forEach(({ row, col }) => {
+      updateData.push({
+        row,
+        type: UpdateType.UPDATE,
+        filter: { ID: tree.items[row].data.ID },
+        update: { name: sheet.getValue(row, col) }
+      });
+    });
+    try {
+      await ajaxPost('/priceInfo/editClassData', { updateData }, TIME_OUT);
+      updateData.forEach(({ row, update: { name } }) => tree.items[row].data.name = name);
+    } catch (err) {
+      // 恢复各单元格数据
+      sheetCommonObj.renderSheetFunc(sheet, () => {
+        changedCells.forEach(({ row }) => {
+          sheet.setValue(tree.items[row].data.name);
+        });
+      });
+    }
+  }
+  sheet.bind(GC.Spread.Sheets.Events.ValueChanged, function (e, info) {
+    const changedCells = [{ row: info.row, col: info.col }];
+    handleEdit(changedCells);
+  });
+  sheet.bind(GC.Spread.Sheets.Events.RangeChanged, function (e, info) {
+    handleEdit(info.changedCells);
+  });
+
+  // 树操作相关
+  const $insert = $('#tree-insert');
+  const $remove = $('#tree-remove');
+  const $upLevel = $('#tree-up-level');
+  const $downLevel = $('#tree-down-level');
+  const $downMove = $('#tree-down-move');
+  const $upMove = $('#tree-up-move');
+  const $calcPriceIndex = $('#calc-price-index');
+
+  // 插入
+  let canInsert = true;
+  async function insert() {
+    try {
+      if (!canInsert) {
+        return false;
+      }
+      canInsert = false;
+      $.bootstrapLoading.start();
+      const updateData = [];
+      const selected = tree.selected;
+      const newItem = {
+        libID,
+        areaID: AREA_BOOK.curArea.ID,
+        ID: uuid.v1(),
+        name: '',
+        ParentID: '-1',
+        NextSiblingID: '-1'
+      };
+      if (selected) {
+        newItem.ParentID = selected.data.ParentID;
+        updateData.push({
+          type: UpdateType.UPDATE,
+          filter: { ID: selected.data.ID },
+          update: { NextSiblingID: newItem.ID }
+        });
+        if (selected.nextSibling) {
+          newItem.NextSiblingID = selected.nextSibling.data.ID;
+        }
+      }
+      updateData.push({
+        type: UpdateType.CREATE,
+        document: newItem
+      });
+      await ajaxPost('/priceInfo/editClassData', { updateData });
+      controller.insertByID(newItem.ID);
+      handleSelectionChanged(sheet.getActiveRowIndex());
+    } catch (err) {
+      alert(err);
+    } finally {
+      canInsert = true;
+      $.bootstrapLoading.end();
+    }
+  }
+
+  $insert.click(_.debounce(insert, DEBOUNCE_TIME, { leading: true }));
+
+  // 删除
+  let canRemove = true;
+  async function remove() {
+    try {
+      if (!canRemove) {
+        return false;
+      }
+      canRemove = false;
+      $.bootstrapLoading.start();
+      const updateData = [];
+      const selected = tree.selected;
+      const children = selected.getPosterity();
+      [selected, ...children].forEach(node => updateData.push({
+        type: UpdateType.DELETE,
+        filter: { ID: node.data.ID }
+      }));
+      if (selected.preSibling) {
+        updateData.push({
+          type: UpdateType.UPDATE,
+          filter: { ID: selected.preSibling.data.ID },
+          update: { NextSiblingID: selected.data.NextSiblingID }
+        });
+      }
+      await ajaxPost('/priceInfo/editClassData', { updateData });
+      controller.delete();
+      handleSelectionChanged(sheet.getActiveRowIndex());
+    } catch (err) {
+      alert(err);
+    } finally {
+      canRemove = true;
+      $.bootstrapLoading.end();
+    }
+  }
+
+  $remove.click(_.debounce(remove, DEBOUNCE_TIME, { leading: true }));
+
+  // 升级
+  let canUpLevel = true;
+  async function upLevel() {
+    try {
+      if (!canUpLevel) {
+        return false;
+      }
+      canUpLevel = false;
+      $.bootstrapLoading.start();
+      const updateData = [];
+      const selected = tree.selected;
+      if (selected.preSibling) {
+        updateData.push({
+          type: UpdateType.UPDATE,
+          filter: { ID: selected.preSibling.data.ID },
+          update: { NextSiblingID: -1 }
+        });
+      }
+      if (selected.parent) {
+        updateData.push({
+          type: UpdateType.UPDATE,
+          filter: { ID: selected.parent.data.ID },
+          update: { NextSiblingID: selected.data.ID }
+        });
+      }
+      updateData.push({
+        type: UpdateType.UPDATE,
+        filter: { ID: selected.data.ID },
+        update: { ParentID: selected.parent.data.ParentID, NextSiblingID: selected.parent.data.NextSiblingID }
+      });
+      let curNode = selected.nextSibling;
+      while (curNode) {
+        updateData.push({
+          type: UpdateType.UPDATE,
+          filter: { ID: curNode.data.ID },
+          update: { ParentID: selected.data.ID }
+        });
+        curNode = curNode.nextSibling;
+      }
+      await ajaxPost('/priceInfo/editClassData', { updateData });
+      controller.upLevel();
+      refreshTreeButton(tree.selected);
+    } catch (err) {
+      alert(err);
+    } finally {
+      canUpLevel = true;
+      $.bootstrapLoading.end();
+    }
+  }
+
+  $upLevel.click(_.debounce(upLevel, DEBOUNCE_TIME, { leading: true }));
+
+  // 降级
+  let canDownLevel = true;
+  async function downLevel() {
+    try {
+      if (!canDownLevel) {
+        return false;
+      }
+      canDownLevel = false;
+      $.bootstrapLoading.start();
+      const updateData = [];
+      const selected = tree.selected;
+      if (selected.preSibling) {
+        updateData.push({
+          type: UpdateType.UPDATE,
+          filter: { ID: selected.preSibling.data.ID },
+          update: { NextSiblingID: selected.data.NextSiblingID }
+        });
+        const preSiblingLastChild = selected.preSibling.children[selected.preSibling.children.length - 1];
+        if (preSiblingLastChild) {
+          updateData.push({
+            type: UpdateType.UPDATE,
+            filter: { ID: preSiblingLastChild.data.ID },
+            update: { NextSiblingID: selected.data.ID }
+          });
+        }
+        updateData.push({
+          type: UpdateType.UPDATE,
+          filter: { ID: selected.data.ID },
+          update: { ParentID: selected.preSibling.data.ID, NextSiblingID: -1 }
+        });
+      }
+      await ajaxPost('/priceInfo/editClassData', { updateData });
+      controller.downLevel();
+      refreshTreeButton(tree.selected);
+    } catch (err) {
+      alert(err);
+    } finally {
+      canDownLevel = true;
+      $.bootstrapLoading.end();
+    }
+  }
+
+  $downLevel.click(_.debounce(downLevel, DEBOUNCE_TIME, { leading: true }));
+
+  // 下移
+  let canDownMove = true;
+  async function downMove() {
+    try {
+      if (!canDownMove) {
+        return false;
+      }
+      canDownMove = false;
+      $.bootstrapLoading.start();
+      const updateData = [];
+      const selected = tree.selected;
+      if (selected.preSibling) {
+        updateData.push({
+          type: UpdateType.UPDATE,
+          filter: { ID: selected.preSibling.data.ID },
+          update: { NextSiblingID: selected.data.NextSiblingID }
+        });
+      }
+      updateData.push({
+        type: UpdateType.UPDATE,
+        filter: { ID: selected.data.ID },
+        update: { NextSiblingID: selected.nextSibling.data.NextSiblingID }
+      });
+      updateData.push({
+        type: UpdateType.UPDATE,
+        filter: { ID: selected.nextSibling.data.ID },
+        update: { NextSiblingID: selected.data.ID }
+      });
+      await ajaxPost('/priceInfo/editClassData', { updateData });
+      controller.downMove();
+      refreshTreeButton(tree.selected);
+    } catch (err) {
+      alert(err);
+    } finally {
+      canDownMove = true;
+      $.bootstrapLoading.end();
+    }
+  }
+
+  $downMove.click(_.debounce(downMove, DEBOUNCE_TIME, { leading: true }));
+
+  // 上移
+  let canUpMove = true;
+  async function upMove() {
+    try {
+      if (!canUpMove) {
+        return false;
+      }
+      canUpMove = false;
+      $.bootstrapLoading.start();
+      const updateData = [];
+      const selected = tree.selected;
+      if (selected.preSibling) {
+        updateData.push({
+          type: UpdateType.UPDATE,
+          filter: { ID: selected.preSibling.data.ID },
+          update: { NextSiblingID: selected.data.NextSiblingID }
+        });
+      }
+      const prePreSibling = selected.preSibling.preSibling;
+      if (prePreSibling) {
+        updateData.push({
+          type: UpdateType.UPDATE,
+          filter: { ID: prePreSibling.data.ID },
+          update: { NextSiblingID: selected.data.ID }
+        });
+      }
+      updateData.push({
+        type: UpdateType.UPDATE,
+        filter: { ID: selected.data.ID },
+        update: { NextSiblingID: selected.preSibling.data.ID }
+      });
+      await ajaxPost('/priceInfo/editClassData', { updateData });
+      controller.upMove();
+      refreshTreeButton(tree.selected);
+    } catch (err) {
+      alert(err);
+    } finally {
+      canUpMove = true;
+      $.bootstrapLoading.end();
+    }
+  }
+
+  $upMove.click(_.debounce(upMove, DEBOUNCE_TIME, { leading: true }));
+
+
+  // 刷新树操作按钮有效性
+  function refreshTreeButton(selected) {
+    if (locked) {
+      return;
+    }
+    $insert.removeClass('disabled');
+    $remove.removeClass('disabled');
+    $upLevel.removeClass('disabled');
+    $downLevel.removeClass('disabled');
+    $downMove.removeClass('disabled');
+    $upMove.removeClass('disabled');
+    if (!selected) {
+      $remove.addClass('disabled');
+      $upLevel.addClass('disabled');
+      $downLevel.addClass('disabled');
+      $downMove.addClass('disabled');
+      $upMove.addClass('disabled');
+    } else {
+      if (!selected.preSibling) {
+        $downLevel.addClass('disabled');
+        $upMove.addClass('disabled');
+      }
+      if (!selected.nextSibling) {
+        $downMove.addClass('disabled');
+      }
+      if (!selected.parent) {
+        $upLevel.addClass('disabled');
+      }
+    }
+  }
+
+  // 焦点变更处理
+  const curClass = { ID: null };
+  function handleSelectionChanged(row) {
+    const classNode = tree.items[row] || null;
+    tree.selected = classNode;
+    refreshTreeButton(classNode);
+    curClass.ID = classNode && classNode.data && classNode.data.ID || null;
+    const classIDList = []
+    if (classNode) {
+      classIDList.push(classNode.data.ID);
+      const children = classNode.getPosterity();
+      children.forEach(child => classIDList.push(child.data.ID));
+    }
+    PRICE_BOOK.initData(classIDList);
+  }
+  const debounceSelectionChanged = _.debounce(function (e, info) {
+    const row = info.newSelections && info.newSelections[0] ? info.newSelections[0].row : 0;
+    handleSelectionChanged(row);
+  }, DEBOUNCE_TIME, { leading: true });
+  sheet.bind(GC.Spread.Sheets.Events.SelectionChanged, debounceSelectionChanged);
+
+
+
+  $calcPriceIndex.click(_.debounce(async () => {
+    $.bootstrapLoading.start();
+    try {
+      const data = await ajaxPost('/priceInfo/calcPriceIndex', { libID, period: curLibPeriod, compilationID }, TIME_OUT);
+      //alert(data);
+
+      if (data) {
+        const htmlStr = data.replace(/\n/gm, '<br>'); //replaceAll('\n','<br>',data);
+        $("#result-info-body").html(htmlStr);
+        $("#result-info").modal('show');
+      } else {
+        alert('计算完成!')
+      }
+
+
+    } catch (error) {
+      console.log(error);
+    }
+    $.bootstrapLoading.end();
+
+  }, DEBOUNCE_TIME, { leading: true }));
+
+
+  return {
+    initData,
+    handleSelectionChanged,
+    curClass,
+  }
+
+})();

+ 165 - 0
web/maintain/price_info_lib/js/priceItem.js

@@ -0,0 +1,165 @@
+// 价格信息表
+const PRICE_BOOK = (() => {
+  const setting = {
+    header: [
+      { headerName: '编码', headerWidth: 100, dataCode: 'code', dataType: 'String', hAlign: 'left', vAlign: 'center', formatter: "@" },
+      { headerName: '别名编码', headerWidth: 70, dataCode: 'classCode', dataType: 'String', hAlign: 'left', vAlign: 'center', formatter: "@" },
+      { headerName: '名称', headerWidth: 200, dataCode: 'name', dataType: 'String', hAlign: 'left', vAlign: 'center' },
+      { headerName: '规格型号', headerWidth: 120, dataCode: 'specs', dataType: 'String', hAlign: 'left', vAlign: 'center' },
+      { headerName: '单位', headerWidth: 80, dataCode: 'unit', dataType: 'String', hAlign: 'center', vAlign: 'center' },
+      { headerName: '不含税价', headerWidth: 80, dataCode: 'noTaxPrice', dataType: 'String', hAlign: 'right', vAlign: 'center' },
+      { headerName: '含税价', headerWidth: 80, dataCode: 'taxPrice', dataType: 'String', hAlign: 'right', vAlign: 'center' },
+      { headerName: '月份备注', headerWidth: 140, dataCode: 'dateRemark', dataType: 'String', hAlign: 'left', vAlign: 'center' },
+      { headerName: '计算式', headerWidth: 100, dataCode: 'expString', dataType: 'String', hAlign: 'left', vAlign: 'center' },
+    ],
+  };
+  // 初始化表格
+  const workBook = initSheet($('#price-spread')[0], setting);
+  workBook.options.allowUserDragDrop = true;
+  workBook.options.allowUserDragFill = true;
+  lockUtil.lockSpreads([workBook], locked);
+  const sheet = workBook.getSheet(0);
+
+  let cache = [];
+  // 清空
+  function clear() {
+    cache = [];
+    sheet.setRowCount(0);
+  }
+  // 初始化数据
+  async function initData(classIDList) {
+    if (!classIDList || !classIDList.length) {
+      return clear();
+    }
+    $.bootstrapLoading.start();
+    try {
+      cache = await ajaxPost('/priceInfo/getPriceData', { classIDList }, TIME_OUT);
+      cache = _.sortBy(cache, 'classCode');
+      showData(sheet, cache, setting.header, 5);
+      const row = sheet.getActiveRowIndex();
+      const keywordList = cache[row] && cache[row].keywordList || [];
+      KEYWORD_BOOK.showKeywordData(keywordList);
+    } catch (err) {
+      cache = [];
+      sheet.setRowCount(0);
+      alert(err);
+    } finally {
+      $.bootstrapLoading.end();
+    }
+  }
+
+  // 获取当前表中行数据
+  function getRowData(sheet, row, headers) {
+    const item = {};
+    headers.forEach(({ dataCode }, index) => {
+      const value = sheet.getValue(row, index) || '';
+      if (value) {
+        item[dataCode] = value;
+      }
+    });
+    return item;
+  }
+
+  // 获取表数据和缓存数据的不同数据
+  function getRowDiffData(curRowData, cacheRowData, headers) {
+    let item = null;
+    headers.forEach(({ dataCode }) => {
+      const curValue = curRowData[dataCode];
+      const cacheValue = cacheRowData[dataCode];
+      if (!cacheValue && !curValue) {
+        return;
+      }
+      if (cacheValue !== curValue) {
+        if (!item) {
+          item = {};
+        }
+        item[dataCode] = curValue || '';
+      }
+    });
+    return item;
+  }
+
+  // 编辑处理
+  async function handleEdit(changedCells) {
+    const postData = []; // 请求用
+    // 更新缓存用
+    const updateData = [];
+    const deleteData = [];
+    const insertData = [];
+    try {
+      changedCells.forEach(({ row }) => {
+        if (cache[row]) {
+          const rowData = getRowData(sheet, row, setting.header);
+          if (Object.keys(rowData).length) { // 还有数据,更新
+            const diffData = getRowDiffData(rowData, cache[row], setting.header);
+            if (diffData) {
+              postData.push({ type: UpdateType.UPDATE, ID: cache[row].ID, data: diffData });
+              updateData.push({ row, data: diffData });
+            }
+          } else { // 该行无数据了,删除
+            postData.push({ type: UpdateType.DELETE, ID: cache[row].ID });
+            deleteData.push(cache[row]);
+          }
+        } else { // 新增
+          const rowData = getRowData(sheet, row, setting.header);
+          if (Object.keys(rowData).length) {
+            rowData.ID = uuid.v1();
+            rowData.libID = libID;
+            rowData.compilationID = compilationID;
+            rowData.areaID = AREA_BOOK.curArea.ID;
+            rowData.classID = CLASS_BOOK.curClass.ID;
+            rowData.period = curLibPeriod;
+            postData.push({ type: UpdateType.CREATE, data: rowData });
+            insertData.push(rowData);
+          }
+        }
+      });
+      if (postData.length) {
+        await ajaxPost('/priceInfo/editPriceData', { postData }, TIME_OUT);
+        // 更新缓存,先更新然后删除,最后再新增,防止先新增后缓存数据的下标与更新、删除数据的下标对应不上
+        updateData.forEach(item => {
+          Object.assign(cache[item.row], item.data);
+        });
+        deleteData.forEach(item => {
+          const index = cache.indexOf(item);
+          if (index >= 0) {
+            cache.splice(index, 1);
+          }
+        });
+        insertData.forEach(item => cache.push(item));
+        if (deleteData.length || insertData.length) {
+          showData(sheet, cache, setting.header, 5);
+        }
+      }
+    } catch (err) {
+      // 恢复各单元格数据
+      showData(sheet, cache, setting.header, 5);
+    }
+  }
+  sheet.bind(GC.Spread.Sheets.Events.ValueChanged, function (e, info) {
+    const changedCells = [{ row: info.row }];
+    handleEdit(changedCells);
+  });
+  sheet.bind(GC.Spread.Sheets.Events.SelectionChanged, function (e, info) {
+    const row = info.newSelections && info.newSelections[0] ? info.newSelections[0].row : 0;
+    // 显示关键字数据
+    const keywordList = cache[row] && cache[row].keywordList || [];
+    KEYWORD_BOOK.showKeywordData(keywordList);
+  });
+  sheet.bind(GC.Spread.Sheets.Events.RangeChanged, function (e, info) {
+    const changedRows = [];
+    let preRow;
+    info.changedCells.forEach(({ row }) => {
+      if (row !== preRow) {
+        changedRows.push({ row });
+      }
+      preRow = row;
+    });
+    handleEdit(changedRows);
+  });
+
+  return {
+    clear,
+    initData,
+  }
+})();

+ 27 - 0
web/maintain/price_info_lib/js/priceKeyword.js

@@ -0,0 +1,27 @@
+// 关键字表
+const KEYWORD_BOOK = (() => {
+  const setting = {
+    header: [
+      { headerName: '关键字', headerWidth: 200, dataCode: 'keyword', dataType: 'String', hAlign: 'left', vAlign: 'center' },
+      { headerName: '单位', headerWidth: 70, dataCode: 'unit', dataType: 'String', hAlign: 'center', vAlign: 'center' },
+      { headerName: '关键字效果', headerWidth: 100, dataCode: 'coe', dataType: 'String', hAlign: 'center', vAlign: 'center' },
+      { headerName: '组别', headerWidth: 50, dataCode: 'group', dataType: 'String', hAlign: 'center', vAlign: 'center' },
+      { headerName: '选项号', headerWidth: 70, dataCode: 'optionCode', dataType: 'String', hAlign: 'center', vAlign: 'center' },
+    ],
+  };
+  // 初始化表格
+  const workBook = initSheet($('#keyword-spread')[0], setting);
+  workBook.options.allowUserDragDrop = false;
+  workBook.options.allowUserDragFill = false;
+  lockUtil.lockSpreads([workBook], true);
+  const sheet = workBook.getSheet(0);
+
+  // 显示关键字数据
+  const showKeywordData = (keywordList) => {
+    showData(sheet, keywordList, setting.header);
+  }
+
+  return {
+    showKeywordData
+  }
+})();