Browse Source

信息价库

vian 5 years ago
parent
commit
d61b01cb6c

+ 13 - 2
modules/price_info_lib/controllers/index.js

@@ -148,8 +148,8 @@ class PriceInfoController extends BaseController {
 
     async getPriceData(req, res) {
         try {
-            const { classID } = JSON.parse(req.body.data);
-            const data = await facade.getPriceData(classID);
+            const { classIDList } = JSON.parse(req.body.data);
+            const data = await facade.getPriceData(classIDList);
             res.json({ error: 0, message: 'getPriceData success', data });
         } catch (err) {
             console.log(err);
@@ -168,6 +168,17 @@ class PriceInfoController extends BaseController {
         }
     }
 
+    async editClassData(req, res) {
+        try {
+            const { updateData } = JSON.parse(req.body.data);
+            await facade.editClassData(updateData);
+            res.json({ error: 0, message: 'editClass success' });
+        } catch (err) {
+            console.log(err);
+            res.json({ error: 1, message: err.toString() });
+        }
+    }
+
 }
 
 module.exports = {

+ 48 - 5
modules/price_info_lib/facade/index.js

@@ -82,6 +82,8 @@ async function insertAreas(insertData) {
 }
 
 async function deleteAreas(deleteData) {
+    await priceInfoClassModel.remove({ areaID: { $in: deleteData } });
+    await priceInfoItemModel.remove({ areaID: { $in: deleteData } });
     await priceInfoAreaModel.remove({ ID: { $in: deleteData } });
 }
 
@@ -89,21 +91,27 @@ async function getClassData(libID, areaID) {
     return await priceInfoClassModel.find({ libID, areaID }, '-_id').lean();
 }
 
-async function getPriceData(classID) {
-    return await priceInfoItemModel.find({ classID }, '-_id').lean();
+async function getPriceData(classIDList) {
+    return await priceInfoItemModel.find({ classID: { $in: classIDList } }, '-_id').lean();
 }
 
+const UpdateType = {
+    UPDATE: 'update',
+    DELETE: 'delete',
+    CREATE: 'create',
+};
+
 async function editPriceData(postData) {
     const bulks = [];
     postData.forEach(data => {
-        if (data.type === 'update') {
+        if (data.type === UpdateType.UPDATE) {
             bulks.push({
                 updateOne: {
                     filter: { ID: data.ID },
                     update: { ...data.data }
                 }
             });
-        } else if (data.type === 'delete') {
+        } else if (data.type === UpdateType.DELETE) {
             bulks.push({
                 deleteOne: {
                     filter: { ID: data.ID }
@@ -122,6 +130,40 @@ async function editPriceData(postData) {
     }
 }
 
+async function editClassData(updateData) {
+    const bulks = [];
+    const deleteIDList = [];
+    updateData.forEach(({ type, filter, update, document }) => {
+        if (type === UpdateType.UPDATE) {
+            bulks.push({
+                updateOne: {
+                    filter,
+                    update
+                }
+            });
+        } else if (type === UpdateType.DELETE) {
+            deleteIDList.push(filter.ID);
+            bulks.push({
+                deleteOne: {
+                    filter
+                }
+            });
+        } else {
+            bulks.push({
+                insertOne: {
+                    document
+                }
+            });
+        }
+    });
+    if (deleteIDList.length) {
+        await priceInfoItemModel.remove({ classID: { $in: deleteIDList } });
+    }
+    if (bulks.length) {
+        await priceInfoClassModel.bulkWrite(bulks);
+    }
+}
+
 module.exports = {
     getLibs,
     createLib,
@@ -134,5 +176,6 @@ module.exports = {
     deleteAreas,
     getClassData,
     getPriceData,
-    editPriceData
+    editPriceData,
+    editClassData
 }

+ 1 - 0
modules/price_info_lib/routes/index.js

@@ -21,6 +21,7 @@ module.exports = function (app) {
     router.post("/getClassData", priceInfoController.auth, priceInfoController.init, priceInfoController.getClassData);
     router.post("/getPriceData", priceInfoController.auth, priceInfoController.init, priceInfoController.getPriceData);
     router.post("/editPriceData", priceInfoController.auth, priceInfoController.init, priceInfoController.editPriceData);
+    router.post("/editClassData", priceInfoController.auth, priceInfoController.init, priceInfoController.editClassData);
 
     app.use("/priceInfo", router);
 };

+ 1 - 1
web/maintain/price_info_lib/css/index.css

@@ -107,6 +107,6 @@ body {
 
 .main .right {
     float: left;
-    width: 60%;
+    width: 59.9%;
     height: 100%;
 }

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

@@ -27,22 +27,22 @@
                 <div class="top" id="area-spread"></div>
                 <div class="bottom">
                     <div class="tab-bar">
-                        <a href="javascript:void(0);" id="tree_Insert" class="btn btn-sm lock-btn-control disabled"
+                        <a href="javascript:void(0);" id="tree-insert" class="btn btn-sm lock-btn-control disabled"
                             data-toggle="tooltip" data-placement="bottom" title="" data-original-title="插入"><i
                                 class="fa fa-plus" aria-hidden="true"></i></a>
-                        <a href="javascript:void(0);" id="tree_remove" class="btn btn-sm lock-btn-control disabled"
+                        <a href="javascript:void(0);" id="tree-remove" class="btn btn-sm lock-btn-control disabled"
                             data-toggle="tooltip" data-placement="bottom" title="" data-original-title="删除"><i
                                 class="fa fa-remove" aria-hidden="true"></i></a>
-                        <a href="javascript:void(0);" id="tree_upLevel" class="btn btn-sm lock-btn-control disabled"
+                        <a href="javascript:void(0);" id="tree-up-level" class="btn btn-sm lock-btn-control disabled"
                             data-toggle="tooltip" data-placement="bottom" title="" data-original-title="升级"><i
                                 class="fa fa-arrow-left" aria-hidden="true"></i></a>
-                        <a href="javascript:void(0);" id="tree_downLevel" class="btn btn-sm lock-btn-control disabled"
+                        <a href="javascript:void(0);" id="tree-down-level" class="btn btn-sm lock-btn-control disabled"
                             data-toggle="tooltip" data-placement="bottom" title="" data-original-title="降级"><i
                                 class="fa fa-arrow-right" aria-hidden="true"></i></a>
-                        <a href="javascript:void(0);" id="tree_downMove" class="btn btn-sm lock-btn-control disabled"
+                        <a href="javascript:void(0);" id="tree-down-move" class="btn btn-sm lock-btn-control disabled"
                             data-toggle="tooltip" data-placement="bottom" title="" data-original-title="下移"><i
                                 class="fa fa-arrow-down" aria-hidden="true"></i></a>
-                        <a href="javascript:void(0);" id="tree_upMove" class="btn btn-sm lock-btn-control disabled"
+                        <a href="javascript:void(0);" id="tree-up-move" class="btn btn-sm lock-btn-control disabled"
                             data-toggle="tooltip" data-placement="bottom" title="" data-original-title="上移"><i
                                 class="fa fa-arrow-up" aria-hidden="true"></i></a>
                     </div>
@@ -64,6 +64,7 @@
     <script src="/lib/spreadjs/sheets/gc.spread.sheets.all.11.1.2.min.js"></script>
     <script>GC.Spread.Sheets.LicenseKey = '<%- LicenseKey %>';</script>
     <script src="/public/web/uuid.js"></script>
+    <script src="/lib/lodash/lodash.js"></script>
     <script src="/public/web/scMathUtil.js"></script>
     <script src="/public/web/treeDataHelper.js"></script>
     <script src="/public/web/common_ajax.js"></script>

+ 356 - 19
web/maintain/price_info_lib/js/index.js

@@ -1,3 +1,4 @@
+
 function setAlign(sheet, headers) {
     const fuc = () => {
         headers.forEach(({ hAlign, vAlign }, index) => {
@@ -32,6 +33,15 @@ function showData(sheet, data, headers, emptyRows) {
 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 = (() => {
@@ -41,6 +51,8 @@ const AREA_BOOK = (() => {
     };
     // 初始化表格
     const workBook = initSheet($('#area-spread')[0], setting);
+    lockUtil.lockSpreads([workBook], locked);
+    workBook.options.allowExtendPasteRange = false;
     const sheet = workBook.getSheet(0);
 
     // 显示数据
@@ -78,15 +90,16 @@ const AREA_BOOK = (() => {
 
     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, function (e, info) {
-        const row = info.newSelections && info.newSelections[0] ? info.newSelections[0].row : 0;
-        handleSelectionChanged(row);
-    });
+    sheet.bind(GC.Spread.Sheets.Events.SelectionChanged, debounceSelectionChanged);
 
     // 新增
     async function insert() {
@@ -111,7 +124,7 @@ const AREA_BOOK = (() => {
             $.bootstrapLoading.end();
         }
     }
-    
+
     // 删除
     async function del() {
         try {
@@ -153,6 +166,9 @@ const AREA_BOOK = (() => {
                             insert: {
                                 name: '新增',
                                 icon: "fa-arrow-left",
+                                disabled: function () {
+                                    return locked;
+                                },
                                 callback: function (key, opt) {
                                     insert();
                                 }
@@ -161,7 +177,7 @@ const AREA_BOOK = (() => {
                                 name: '删除',
                                 icon: "fa-arrow-left",
                                 disabled: function () {
-                                    return !cache[target.row];
+                                    return locked || !cache[target.row];
                                 },
                                 callback: function (key, opt) {
                                     del();
@@ -214,6 +230,7 @@ const CLASS_BOOK = (() => {
     };
     // 初始化表格
     const workBook = initSheet($('#class-spread')[0], setting);
+    workBook.options.allowExtendPasteRange = false;
     const sheet = workBook.getSheet(0);
 
     let tree;
@@ -236,6 +253,8 @@ const CLASS_BOOK = (() => {
             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;
@@ -252,13 +271,14 @@ const CLASS_BOOK = (() => {
         changedCells.forEach(({ row, col }) => {
             updateData.push({
                 row,
-                ID: tree.items[row].data.ID,
-                name: sheet.getValue(row, col)
+                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, name }) => tree.items[row].data.name = name);
+            updateData.forEach(({ row, update: { name } }) => tree.items[row].data.name = name);
         } catch (err) {
             // 恢复各单元格数据
             sheetCommonObj.renderSheetFunc(sheet, () => {
@@ -276,17 +296,330 @@ const CLASS_BOOK = (() => {
         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');
+
+    // 插入
+    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];
+        const classNode = tree.items[row] || null;
+        tree.selected = classNode;
+        refreshTreeButton(classNode);
         curClass.ID = classNode && classNode.data && classNode.data.ID || null;
-        PRICE_BOOK.initData(curClass.ID);
+        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);
     }
-    sheet.bind(GC.Spread.Sheets.Events.SelectionChanged, function (e, info) {
+    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);
 
     return {
         initData,
@@ -310,6 +643,7 @@ const PRICE_BOOK = (() => {
     };
     // 初始化表格
     const workBook = initSheet($('#price-spread')[0], setting);
+    lockUtil.lockSpreads([workBook], locked);
     const sheet = workBook.getSheet(0);
 
     let cache = [];
@@ -319,13 +653,13 @@ const PRICE_BOOK = (() => {
         sheet.setRowCount(0);
     }
     // 初始化数据
-    async function initData(classID) {
-        if (!classID) {
+    async function initData(classIDList) {
+        if (!classIDList || !classIDList.length) {
             return clear();
         }
         $.bootstrapLoading.start();
         try {
-            cache = await ajaxPost('/priceInfo/getPriceData', { classID }, TIME_OUT);
+            cache = await ajaxPost('/priceInfo/getPriceData', { classIDList }, TIME_OUT);
             showData(sheet, cache, setting.header, 5);
         } catch (err) {
             cache = [];
@@ -381,11 +715,11 @@ const PRICE_BOOK = (() => {
                     if (Object.keys(rowData).length) { // 还有数据,更新
                         const diffData = getRowDiffData(rowData, cache[row], setting.header);
                         if (diffData) {
-                            postData.push({ type: 'update', ID: cache[row].ID, data: diffData });
+                            postData.push({ type: UpdateType.UPDATE, ID: cache[row].ID, data: diffData });
                             updateData.push({ row, data: diffData });
                         }
                     } else { // 该行无数据了,删除
-                        postData.push({ type: 'delete', ID: cache[row].ID });
+                        postData.push({ type: UpdateType.DELETE, ID: cache[row].ID });
                         deleteData.push(cache[row]);
                     }
                 } else { // 新增
@@ -395,7 +729,7 @@ const PRICE_BOOK = (() => {
                         rowData.libID = libID;
                         rowData.areaID = AREA_BOOK.curArea.ID;
                         rowData.classID = CLASS_BOOK.curClass.ID;
-                        postData.push({ type: 'create', data: rowData });
+                        postData.push({ type: UpdateType.CREATE, data: rowData });
                         insertData.push(rowData);
                     }
                 }
@@ -445,5 +779,8 @@ const PRICE_BOOK = (() => {
 })();
 
 $(document).ready(() => {
+    $('[data-toggle="tooltip"]').tooltip();
     AREA_BOOK.handleSelectionChanged(0);
+    const $range = $(document.body);
+    lockUtil.lockTools($range, locked);
 });