/** * 块模板库管理。 * Created by CSL on 2018-09-19. */ var blockLibObj = { // libs: [], activeLib: null, mainSpread: null, mainSheet: null, mainTree: null, mainTreeController: null, mainSetting: { "emptyRowHeader": true, "rowHeaderWidth": 15, "emptyRows":0, "headRows":1, "headRowHeight":[30], "defaultRowHeight": 21, "treeCol": 9, "cols":[{ "width":400, "readOnly": true, "head":{ "titleNames":["名称"], "spanCols":[1], "spanRows":[1], "vAlign":[1], "hAlign":[1], "font":["Arial"] }, "data":{ "field":"nodeName", "vAlign":1, "hAlign":0, "font":"Arial" } }] }, mainDatas: [], billSpread: null, billSheet: null, billSetting: { header: [ {headerName: "项目编码", headerWidth: 90, dataCode: "code", dataType: "String", hAlign: "center"}, {headerName: "项目名称", headerWidth: 100, dataCode: "name", dataType: "String"}, {headerName: "单位", headerWidth: 40, dataCode: "unit", dataType: "String", hAlign: "center"}, {headerName: "综合单价", headerWidth: 70, dataCode: "unitFee", dataType: "Number"}, {headerName: "项目特征", headerWidth: 160, dataCode: "itemCharacterText", dataType: "String"} ], view: { lockColumns: [0, 1, 2, 3, 4] } }, rationSpread: null, rationSheet: null, rationSetting: { header: [ {headerName: "编码", headerWidth: 50, dataCode: "code", dataType: "String", hAlign: "center"}, {headerName: "名称", headerWidth: 200, dataCode: "name", dataType: "String"}, {headerName: "单位", headerWidth: 70, dataCode: "unit", dataType: "String", hAlign: "center"}, /* {headerName: "含量", headerWidth: 40, dataCode: "contain", dataType: "Number"}, {headerName: "取费专业", headerWidth: 70, dataCode: "programName", dataType: "String", hAlign: "center"}, */ {headerName: "综合单价", headerWidth: 70, dataCode: "unitFee", dataType: "Number"}, //{headerName: "子目换算状态", headerWidth: 90, dataCode: "adjustState", dataType: "String"} ], view: { lockColumns: [0, 1, 2, 3, 4, 5, 6] } }, cloneType: null, initialShareTip: '', buildSheet: async function () { $.bootstrapLoading.start(); let me = this; let namesAndLib = await ajaxPost('/blockLib/getLibNamesAndFirstLib', {userID: userID, compilationID: projectObj.project.projectInfo.compilation}); function getLibNamesHtml(libsArr) { let result = ''; for (let lib of libsArr) { result += ''; }; return result; }; let html = getLibNamesHtml(namesAndLib.libNames); $("#select_block_lib_names").html(html); this.initialShareTip = namesAndLib.shareList && namesAndLib.shareList.length ? namesAndLib.shareList.reduce((acc, user) => acc += ` ${user.real_name}`, '已分享给') : ''; $('#btn_block_share').attr('data-original-title', this.initialShareTip); await me.loadLib(namesAndLib.firstLib); $.bootstrapLoading.end(); }, loadLib: async function (lib){ let me = this; if (me.mainSpread) { me.mainSpread.destroy(); me.mainSpread = null; }; if (me.billSpread) { me.billSpread.destroy(); me.billSpread = null; }; if (me.rationSpread) { me.rationSpread.destroy(); me.rationSpread = null; }; me.mainDatas = lib.datas; me.activeLib = lib; me.mainSpread = SheetDataHelper.createNewSpread($('#div_block_tree')[0]); me.mainSheet = me.mainSpread.getSheet(0); me.mainSheet.name('blockLibSheet'); sheetCommonObj.spreadDefaultStyle(me.mainSpread); function showBlockTree(datas) { me.mainTree = idTree.createNew({id: 'ID', pid: 'ParentID', nid: 'NextSiblingID', rootId: -1, autoUpdate: false}); me.mainTreeController = TREE_SHEET_CONTROLLER.createNew(me.mainTree, me.mainSheet, me.mainSetting); me.mainTree.loadDatas(datas); me.mainTreeController.showTreeData(); me.mainSheet.getRange(-1, 0, -1, 1).cellType(me.getTreeCell(me.mainTree)); me.mainTree.selected = me.mainTree.items[0]; me.mainTreeController.bind(TREE_SHEET_CONTROLLER.eventName.treeSelectedChanged, function (node) { blockLibObj.loadDetailDatas(node); }); }; showBlockTree(me.mainDatas); /* me.billSpread = sheetCommonObj.buildSheet($('#div_block_bill')[0], me.billSetting, 1); me.billSheet = me.billSpread.getSheet(0); sheetCommonObj.spreadDefaultStyle(me.billSpread); me.billSheet.setColumnWidth(0, 20, GC.Spread.Sheets.SheetArea.rowHeader); */ me.rationSpread = sheetCommonObj.buildSheet($('#div_block_ration')[0], me.rationSetting, 1); me.rationSheet = me.rationSpread.getSheet(0); sheetCommonObj.spreadDefaultStyle(me.rationSpread); me.rationSheet.setColumnWidth(0, 20, GC.Spread.Sheets.SheetArea.rowHeader); me.loadTreeContextMenu(); me.mainSpread.bind(GC.Spread.Sheets.Events.EnterCell, me.onEnterCell); me.mainSpread.bind(GC.Spread.Sheets.Events.CellDoubleClick, this.onCellDoubleClick); }, loadDetailDatas: function (node){ let me = this; if (!node) return; if (node.data.type == 2){ let bill = node.data; let rations = bill.children; //sheetCommonObj.showData(me.billSheet, me.billSetting, [bill]); let rCount = (rations.length > 0) ? rations.length : 1; me.rationSheet.setRowCount(rCount, GC.Spread.Sheets.SheetArea.viewport); sheetCommonObj.showData(me.rationSheet, me.rationSetting, rations); } else{ //sheetCommonObj.cleanSheet(me.billSheet, me.billSetting, 1); sheetCommonObj.cleanSheet(me.rationSheet, me.rationSetting, 1); } }, getTreeCell: function (tree) { let me = this; let indent = 20, levelIndent = -5, halfBoxLength = 5, halfExpandLength = 3, imgWidth = 14, imgHeight = 14; let TreeCell = function () {}; TreeCell.prototype = new GC.Spread.Sheets.CellTypes.Text(); TreeCell.prototype.paint = function (ctx, value, x, y, w, h, style, options) { if (style.backColor) { ctx.save(); ctx.fillStyle = style.backColor; ctx.fillRect(x, y, w, h); ctx.restore(); } else { ctx.clearRect(x, y, w, h); }; let drawLine = function (canvas, x1, y1, x2, y2, color) { ctx.save(); ctx.translate(0.5, 0.5); ctx.beginPath(); ctx.moveTo(x1, y1); ctx.lineTo(x2, y2); ctx.strokeStyle = color; ctx.stroke(); ctx.restore(); }; let drawExpandBox = function (ctx, x, y, w, h, centerX, centerY, expanded) { let rect = {}, h1, h2, offset = 1; rect.top = centerY - halfBoxLength; rect.bottom = centerY + halfBoxLength; rect.left = centerX - halfBoxLength; rect.right = centerX + halfBoxLength; if (rect.left < x + w) { rect.right = Math.min(rect.right, x + w); ctx.save(); ctx.translate(0.5, 0.5); ctx.strokeStyle = 'black'; ctx.beginPath(); ctx.moveTo(rect.left, rect.top); ctx.lineTo(rect.left, rect.bottom); ctx.lineTo(rect.right, rect.bottom); ctx.lineTo(rect.right, rect.top); ctx.lineTo(rect.left, rect.top); ctx.stroke(); ctx.fillStyle = 'white'; ctx.fill(); ctx.restore(); // Draw Horizontal Line h1 = centerX - halfExpandLength; h2 = Math.min(centerX + halfExpandLength, x + w); if (h2 > h1) { drawLine(ctx, h1, centerY, h2, centerY, 'black'); } // Draw Vertical Line if (!expanded && (centerX < x + w)) { drawLine(ctx, centerX, centerY - halfExpandLength, centerX, centerY + halfExpandLength, 'black'); } } }; let node = tree.items[options.row]; if (!node) return; let showTreeLine = true; let centerX = Math.floor(x) + node.depth() * indent + node.depth() * levelIndent + indent / 2; let x1 = centerX + indent / 2; let centerY = Math.floor((y + (y + h)) / 2); let y1; const lineColor = '#ababab'; // Draw Horizontal Line、Image、sibling Vertical Line if (centerX < x + w) { // Draw Horizontal Line drawLine(ctx, centerX, centerY, Math.min(x1, x + w), centerY, lineColor); // Draw Image let imgId; if (node.data.type === 0) imgId = 'blockLib_pic' else if (node.data.type === 1) imgId = 'folder_pic' else if (node.data.type === 2) { imgId = 'block_pic'; }; let img = document.getElementById(imgId); ctx.drawImage(img, centerX+indent/2+3, centerY - 7, imgWidth, imgHeight); // Draw Vertical Line y1 = node.isLast() ? centerY : y + h; if (node.isFirst() && !node.parent/*.parent*/) { drawLine(ctx, centerX, centerY, centerX, y1, lineColor); } else { drawLine(ctx, centerX, y, centerX, y1, lineColor); } } // Draw Expand Box if (node.children.length > 0) { drawExpandBox(ctx, x, y, w, h, centerX, centerY, node.expanded); } // Draw Parent Line var curNode = node.parent, parentCenterX = centerX - indent - levelIndent; while (curNode) { if (!curNode.isLast()) { if (parentCenterX < x + w) { drawLine(ctx, parentCenterX, y, parentCenterX, y + h, lineColor); } } curNode = curNode.parent; parentCenterX -= (indent + levelIndent); } // Draw Text x = x + (node.depth() + 1) * indent + node.depth() * levelIndent + imgWidth + 3; w = w - (node.depth() + 1) * indent - node.depth() * levelIndent - imgWidth - 3; GC.Spread.Sheets.CellTypes.Text.prototype.paint.apply(this, arguments); }; TreeCell.prototype.getHitInfo = function (x, y, cellStyle, cellRect, context) { let info = {x: x, y: y, row: context.row, col: context.col, cellStyle: cellStyle, cellRect: cellRect, sheetArea: context.sheetArea}; let node = tree.items[info.row]; let offset = -1; let centerX = info.cellRect.x + offset + node.depth() * indent + node.depth() * levelIndent + indent / 2; let text = context.sheet.getText(info.row, info.col); let value = context.sheet.getValue(info.row, info.col); let acStyle = context.sheet.getActualStyle(info.row, info.col), zoom = context.sheet.zoom(); let textLength = this.getAutoFitWidth(value, text, acStyle, zoom, {sheet: context.sheet, row: info.row, col: info.col, sheetArea: GC.Spread.Sheets.SheetArea.viewport}); if(info.x > centerX + halfBoxLength && info.x < centerX + halfBoxLength + imgWidth + indent/2+3 + textLength){ info.isReservedLocation = true; } return info; }; TreeCell.prototype.processMouseDown = function (hitinfo) { let offset = -1; let node = tree.items[hitinfo.row]; let centerX = hitinfo.cellRect.x + offset + node.depth() * indent + node.depth() * levelIndent + indent / 2; let centerY = (hitinfo.cellRect.y + offset + (hitinfo.cellRect.y + offset + hitinfo.cellRect.height)) / 2; let text = hitinfo.sheet.getText(hitinfo.row, hitinfo.col); let value = hitinfo.sheet.getValue(hitinfo.row, hitinfo.col); let acStyle = hitinfo.sheet.getActualStyle(hitinfo.row, hitinfo.col), zoom = hitinfo.sheet.zoom(); let textLength = this.getAutoFitWidth(value, text, acStyle, zoom, {sheet: hitinfo.sheet, row: hitinfo.row, col: hitinfo.col, sheetArea: GC.Spread.Sheets.SheetArea.viewport}); //(图标+名字)区域 function withingClickArea(){ return hitinfo.x > centerX + halfBoxLength && hitinfo.x < centerX + halfBoxLength + imgWidth + indent/2+3 + textLength; } //方框外1像素内都有效 if (hitinfo.x >= centerX - halfBoxLength - 2 && hitinfo.x <= centerX + halfBoxLength + 2 && hitinfo.y >= centerY - halfBoxLength - 2 && hitinfo.y <= centerY + halfBoxLength + 2) { node.setExpanded(!node.expanded); TREE_SHEET_HELPER.massOperationSheet(hitinfo.sheet, function () { let iCount = node.posterityCount(), i, child; for (i = 0; i < iCount; i++) { child = tree.items[hitinfo.row + i + 1]; hitinfo.sheet.setRowVisible(hitinfo.row + i + 1, child.visible, hitinfo.sheetArea); } hitinfo.sheet.invalidateLayout(); }); hitinfo.sheet.repaint(); } }; TreeCell.prototype.processMouseMove = function (hitInfo) { let sheet = hitInfo.sheet; let div = sheet.getParent().getHost(); let canvasId = div.id + "vp_vp"; /* let canvas = $(`#${canvasId}`)[0]; //改变鼠标图案 if (sheet && hitInfo.isReservedLocation) { canvas.style.cursor='pointer'; return true; }else{ canvas.style.cursor='default'; }*/ return false; }; TreeCell.prototype.processMouseEnter = function (hitinfo) { let text = hitinfo.sheet.getText(hitinfo.row, hitinfo.col); let value = hitinfo.sheet.getValue(hitinfo.row, hitinfo.col); let tag = hitinfo.sheet.getTag(hitinfo.row, hitinfo.col); let acStyle = hitinfo.sheet.getActualStyle(hitinfo.row, hitinfo.col), zoom = hitinfo.sheet.zoom(); let node = me.mainTree.items[hitinfo.row]; let nodeIndent = node ? (node.depth() + 1) * indent + node.depth() * levelIndent + imgWidth + 3 : 0; let textLength = this.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 - nodeIndent){ TREE_SHEET_HELPER.showTipsDiv(text,{pos: {}},hitinfo); } }; TreeCell.prototype.processMouseLeave = function (hitinfo) { let me = this; TREE_SHEET_HELPER.tipDiv = 'hide'; if (TREE_SHEET_HELPER._toolTipElement) { $(TREE_SHEET_HELPER._toolTipElement).hide(); TREE_SHEET_HELPER._toolTipElement = null; }; TREE_SHEET_HELPER.tipDivCheck();//延时检查:当tips正在show的时候,就调用了hide方法,会导致tips一直存在,所以设置一个超时处理 }; return new TreeCell(); }, newNode: async function (nodeType, nodeName, categoryID, source){ // 1 分类(只用前两个参数) 2 块文件 if (nodeName == '') return; let tree = blockLibObj.mainTree; let ID = uuid.v1(); let pID = (nodeType == 2) ? categoryID : -1; let nID = -1; // 先生成临时结点数据用于提交入库,成功后才生成树结点,并在UI上刷新显示。 let temp = {}; temp.data = { ID: ID, ParentID: pID, NextSiblingID: nID, libID: blockLibObj.activeLib.libID, type: nodeType, nodeName: nodeName }; if (nodeType == 2) blockLibObj.assignData(temp, source); try { let obj = { libID: blockLibObj.activeLib.libID, nodeID: temp.data.ID, create: temp.data }; await ajaxPost('/blockLib/saveBlock', obj); let newN = tree.insertByID(ID, pID, nID); newN.data = temp.data; tree.selected = newN; let sheet = blockLibObj.mainSheet; sheet.suspendPaint(); sheet.suspendEvent(); let idx = tree.items.indexOf(newN); sheet.addRows(idx, 1); sheet.getRange(idx, 0, 1, 1).locked(true); sheet.setValue(idx, 0, newN.data.nodeName); sheet.setSelection(idx, 0, 1, 1); sheet.resumeEvent(); sheet.resumePaint(); } catch (err) { console.log(err.message); return; }; }, assignData: function (block, source){ block.data.compilationID = source.compilationID; block.data.copyTime = source.copyTime; block.data.firstNodeType = source.firstNodeType; block.data.isFBFX = source.isFBFX; let bill = source.datas[0]; block.data.code = bill.code; block.data.name = bill.name; block.data.unit = bill.unit; block.data.itemCharacterText = bill.itemCharacterText; block.data.unitFee = (bill.feesIndex && bill.feesIndex.common) ? bill.feesIndex.common.unitFee : 0; block.data.children = bill.children; for (let r of bill.children){ r.unitFee = (r.feesIndex && r.feesIndex.common) ? r.feesIndex.common.unitFee : 0; if (r.programID) r.programName = projectObj.project.calcProgram.compiledTemplateMaps[r.programID]; // delete r.ID; // 这个不能删! delete r.billsItemID; delete r.fees; delete r.feesIndex; }; }, reName: async function (node, newName){ if (newName == '') return; let obj = { libID: blockLibObj.activeLib.libID, nodeID: node.data.ID, update: {nodeName: newName} }; await ajaxPost('/blockLib/saveBlock', obj); node.data.nodeName = newName; let idx = blockLibObj.mainTree.items.indexOf(node); blockLibObj.mainSheet.setValue(idx, 0, newName); }, moveBlock: function (parentID) { // this.mainTreeController.moveTo(parentID); }, delete: async function () { let node = blockLibObj.mainTree.selected; let obj = { libID: blockLibObj.activeLib.libID, nodeID: node.data.ID, delete: {nodeType: node.data.type} }; await ajaxPost('/blockLib/saveBlock', obj); this.mainTreeController.delete(); }, getCategories: function () { let nodes = [], node = blockLibObj.mainTree.items[0]; nodes.push(node); while (node.nextSibling != null){ node = node.nextSibling; nodes.push(node); }; return nodes; }, curIsBlock: function () { return this.mainTree.selected.data.type == 2; }, curIsCategory: function () { return this.mainTree.selected.data.type == 1; }, getSameNameNode: function(name){ let rst = null; let nodes = blockLibObj.mainTree.items; for (let i = 0; i < nodes.length; i++) { let node = nodes[i]; if (node.data.nodeName == name){ rst = node; break; } } return rst; }, refreshSpread: function (){ if (this.mainSpread) this.mainSpread.refresh(); if (this.billSpread) this.billSpread.refresh(); if (this.rationSpread) this.rationSpread.refresh(); }, loadTreeContextMenu: function (){ let me = this; $.contextMenu({ selector: '#div_block_tree', build: function ($trigger, e) { SheetDataHelper.safeRightClickSelection($trigger, e, me.mainSpread); me.onEnterCell(); }, items: { "oneToOneClone": { name: '一对一克隆', icon: "fa-stop", disabled: function () { let ok = me.curIsBlock() && calcTools.isLeafBill(projectObj.project.mainTree.selected); return !ok; }, visible: function(key, opt){ return true; }, callback: function (key, opt) { blockLibObj.cloneType = 1; $("#div_cloneOptions").modal({show: true}); } }, "oneToMoreClone": { name: '一对多克隆', icon: "fa-th-list", disabled: function () { let ok = me.curIsBlock() && calcTools.isParentBill(projectObj.project.mainTree.selected); return !ok; }, visible: function(key, opt){ return true; }, callback: function (key, opt) { blockLibObj.cloneType = 2; $("#div_cloneOptions").modal({show: true}); } }, "moreToMoreClone": { name: '多对多克隆', icon: "fa-th", disabled: function () { let ok = me.curIsCategory() && calcTools.isParentBill(projectObj.project.mainTree.selected); return !ok; }, visible: function(key, opt){ return true; }, callback: function (key, opt) { blockLibObj.cloneType = 3; $("#div_cloneOptions").modal({show: true}); } }, "delete": { name: '删除', icon: "delete", disabled: function () { return $("#select_block_lib_names option:selected").prop("index") !== 0; }, visible: function(key, opt){ return true; }, callback: function (key, opt) { let name = hintBox.fontRed(me.mainTree.selected.data.nodeName); hintBox.infoBox('操作确认', `确定要删除"${name}"吗?`, 2, function () { me.delete(); }); } }, "moveBlock": { name: '移动模板', icon: "cut", disabled: function () { return true; }, visible: function(key, opt){ // return me.curIsBlock(); return false; }, callback: function (key, opt) { } } } }); }, onEnterCell: function (sender, args) { let me = blockLibObj; me.mainTree.selected = me.mainTree.items[me.mainSheet.getActiveRowIndex()]; }, onCellDoubleClick: function (sender, args) { if (blockLibObj.curIsCategory()) { hintBox.infoBox('系统提示', `双击是“一对一克隆”,不支持分类操作,请在块文件上双击!`, 1); return; } let projectNode = projectObj.project.mainTree.selected; if (!calcTools.isLeafBill(projectNode)) { hintBox.infoBox('系统提示', `双击是“一对一克隆”,请在造价书界面选择最底层的分项/补项/清单进行操作!`, 1); return; } blockLibObj.cloneType = 1; $("#div_cloneOptions").modal({show: true}); }, oneToOneClone: function (projectNode, block, options) { return new Promise(function (resolve, reject) { let canClone = true; if (options.checkCode) canClone = canClone && (projectNode.data.code.substr(0, 9) == block.data.code.substr(0, 9)); if (options.checkName) canClone = canClone && (projectNode.data.name == block.data.name); if (options.checkUnit) canClone = canClone && (projectNode.data.unit == block.data.unit); if (!canClone) return resolve([[], []]); if (options.overwriteRations) projectObj.project.Bills.deleteChildren(projectNode); /* 这里封装成伟城的块文件格式,可直接使用伟城的“粘贴块”接口。 但这里结构要作出调整:忽略叶子清单层,直接从定额开始(跟粘贴块有区别),始终强制在叶子清单下插入定额。 该操作前提:当前块文件的全部数据已从后台取到前台。 */ let vBlock_WC = { compilationID: block.data.compilationID, copyTime: block.data.copyTime, firstNodeType: 1, // 强制改成1 (因为是从清单下的定额开始。清单自身的还是保留,暂不使用使用)。 isFBFX: block.data.isFBFX, zeroQuantity: options.zeroQuantity, datas: block.data.children // rations }; vBlock_WC = JSON.parse(JSON.stringify(vBlock_WC)); BlockController.confirmPaste(vBlock_WC, projectNode, 'sub',function(r, c){ resolve([r, c]) }); }) }, checkShow: async function () { // 这里需要处理异步:模板库装载完再弹出位置选择窗。 if (!$("#kmbk").is(":visible")){ // 如果还没显示 if (!blockLibObj.mainSpread){ await blockLibObj.buildSheet(); }; $('#blockLibTab').click(); // 强制显示 }; $("#div_createBlocks").modal({show: true}); } }; $(document).ready(function(){ // 这里不需要处理异步:因为不需要弹出位置选择窗。 $('#blockLibTab').on('click', function (){ if ($("#kmbk").is(":visible")){ // 显示状态下 if (!blockLibObj.mainSpread){ blockLibObj.buildSheet(); }; } }); $('#btn_block_newFolder').on('click', function (){ $('#input_block_newFolder').val(''); }); $('#btn_block_newFolder_add').on('click', function (){ let name = $('#input_block_newFolder').val(); blockLibObj.newNode(1, name); }); $('#btn_block_reName').on('click', function (){ let select = blockLibObj.mainTree.selected; $('#input_block_reName').val(select.data.nodeName); }); $('#btn_block_reName_OK').on('click', function (){ let select = blockLibObj.mainTree.selected; let oldName = select.data.nodeName; let newName = $('#input_block_reName').val(); if (oldName != newName) blockLibObj.reName(select, newName); }); $("#select_block_lib_names").change(function() { const index = $("#select_block_lib_names option:selected").prop("index"); if (index !== 0) { // 只读 $('#btn_block_newFolder').hide(); $('#btn_block_reName').hide(); $('#btn_block_share').prop('disabled', true); } else { $('#btn_block_newFolder').show(); $('#btn_block_reName').show(); $('#btn_block_share').prop('disabled', false); } async function getLib(){ let libID = $("#select_block_lib_names").val(); let lib = await ajaxPost('/blockLib/getLib', {libID: libID}); blockLibObj.loadLib(lib); if (userID === lib.userID) { $('#btn_block_share').attr('data-original-title', blockLibObj.initialShareTip); } }; $.bootstrapLoading.start(); getLib(); $.bootstrapLoading.end(); }); $('#btn_block_share').click(function () { SHARE_TO.initModal(SHARE_TO.Mode.BLOCK_LIB); }) });