'use strict';
/**
*
*
* @author Zhong
* @date 2018/6/11
* @version
*/
//清单指引/精灵获取完清单数据后的回调函数
let doAfterLoadGuidance = null;
const billsGuidance = (function () {
//更新类型
const updateType = {update: 'update', create: 'create'};
//库类型
const libType = {'guidance': 1, 'elf': 2}; //清单指引、清单精灵
const libSel = $('#stdBillsGuidanceLibSelect');
//工作内容
let stdBillsJobData = [];
//项目特征
let stdBillsFeatureData = [];
//正在插入
let isInserting = false;
const bills = {
dom: $('#billsGuidance_bills'),
workBook: null,
cache: [],
tree: null,
controller: null,
treeSetting: {
emptyRowHeader: true,
rowHeaderWidth: 15,
treeCol: 1,
emptyRows: 0,
headRows: 1,
headRowHeight: [40],
defaultRowHeight: 21,
cols: [
{
width: 35,
readOnly: false,
showHint: false,
head: {
titleNames: ["选用"],
spanCols: [1],
spanRows: [1],
vAlign: [1],
hAlign: [1],
font: ["Arial"]
},
data: {
field: "select",
vAlign: 1,
hAlign: 1,
font: "Arial"
}
},
{
width: 105,
readOnly: true,
showHint: true,
head: {
titleNames: ["项目编码"],
spanCols: [1],
spanRows: [1],
vAlign: [1],
hAlign: [1],
font: ["Arial"]
},
data: {
field: "code",
vAlign: 1,
hAlign: 0,
font: "Arial"
}
}, {
width: 190,
readOnly: true,
head: {
titleNames: ["项目名称"],
spanCols: [1],
spanRows: [1],
vAlign: [1],
hAlign: [1],
font: ["Arial"]
},
data: {
field: "name",
vAlign: 1,
hAlign: 0,
font: "Arial"
}
},
{
width: 60,
readOnly: true,
head: {
titleNames: ["计量单位"],
spanCols: [1],
spanRows: [1],
vAlign: [1],
hAlign: [1],
font: ["Arial"]
},
data: {
field: "unit",
vAlign: 1,
hAlign: 1,
font: "Arial"
}
}
]
},
headers: [
{name: '选用', dataCode: 'select', width: 35, vAlign: 'center', hAlign: 'center', formatter: '@'},
{name: '项目编码', dataCode: 'code', width: 105, vAlign: 'center', hAlign: 'left', formatter: '@'},
{name: '项目名称', dataCode: 'name', width: 190, vAlign: 'center', hAlign: 'left', formatter: '@'},
{name: '单位', dataCode: 'unit', width: 60, vAlign: 'center', hAlign: 'center', formatter: '@'},
],
rowHeaderWidth:1,
events: {
ButtonClicked: function (sender, args) {
if(args.sheet.isEditing()){
args.sheet.endEdit(true);
}
if (args.col !== 0) {
return;
}
const selectedNode = bills.tree.items[args.row];
const curValue = args.sheet.getValue(args.row, args.col);
let count = selectedNode.posterityCount();
if (count) {
renderSheetFunc(args.sheet, () => {
let row = args.row;
while (count--) {
row++;
args.sheet.setValue(row, args.col, curValue);
}
});
}
},
CellDoubleClick: function (sender, args) {
if (args.col === 0) {
return;
}
if(!bills.tree){
return;
}
let node = bills.tree.items[args.row];
if(!node){
return;
}
//展开收起(非最底层节点且双击的是第一列)
if (args.col === 1 && node.children.length > 0) {
node.setExpanded(!node.expanded);
//设置展开收起状态
sessionStorage.setItem('stdBillsGuidanceExpState', bills.tree.getExpState(bills.tree.items));
renderSheetFunc(args.sheet, function () {
let iCount = node.posterityCount(), i, child;
for (i = 0; i < iCount; i++) {
child = bills.tree.items[args.row + i + 1];
args.sheet.setRowVisible(args.row + i + 1, child.visible, args.sheetArea);
}
args.sheet.invalidateLayout();
});
args.sheet.repaint();
} else if (!projectReadOnly && !isInserting && !projectObj.project.isBillsLocked() && (![1].includes(args.col) || node.children.length === 0)) {
//选中部分的最底层(只是选中部分的最底)
let lowestNodes = [bills.tree.items[args.row]];
insertBills(lowestNodes);
}
}
}
};
// 获取对比树片段数据的方法,此方法可能会被覆盖,方法存在一个对象中,使得外部可以覆盖相关方法
// 在农村公路2020中,主树对比片段与这里的逻辑是不相同的
const overwrite = {
getFragment() {
return { parent: null, mainTreeFragment: projectObj.project.Bills.tree.roots }
}
};
// 反转插入顺序
// 正常顺序插入时,next数据可能还没插入,会造成错误,需要反过来插入
// insertData为正向排好序的数据
function reverseInsertData(insertData) {
const reverseData = [];
const parentMap = {};
const IDMap = {};
insertData.forEach(item => {
IDMap[item.data.ID] = item;
(parentMap[item.data.ParentID] || (parentMap[item.data.ParentID] = [])).push(item);
});
// 找到顶层数据: 数据中没有找不到对应parent的数据
const topLevelData = insertData
.filter(item => !IDMap[item.data.ParentID])
.reverse();
function getReverseData(items) {
items.forEach(item => {
reverseData.push(item);
const children = parentMap[item.data.ID];
if (children && children.length) {
getReverseData(children.reverse());
}
})
}
getReverseData(topLevelData);
return reverseData;
}
//插入清单
async function insertBills(lowestNodes) {
try {
let selTree = getSelTree(lowestNodes);
const { errMsg, parent, mainTreeFragment } = overwrite.getFragment();
if (errMsg) {
alert(errMsg);
return;
}
let compareData = compareTree(parent, mainTreeFragment, selTree.roots);
_chkBillsUnitPrice(compareData.postData); //设置指标基价(默认单价)
let sheet = projectObj.mainSpread.getActiveSheet(),
row = sheet.getActiveColumnIndex(),
col = sheet.getActiveColumnIndex();
if (compareData.postData.length > 0) {
//如果插入的是固定清单,则需要判断该固定清单在造价书中是否已存在,造价书中不可存在相同的固定清单
let fixedDatas = compareData.postData.filter((data) =>
data.updateType === updateType.create && Array.isArray(data.updateData.flags));
if (fixedDatas.length > 0) {
//提示已存在此固定清单并且定位
let firstFixed = fixedDatas[0].updateData;
let existNode = projectObj.project.mainTree.items.find((node) =>
node.data && node.data.flagsIndex && node.data.flagsIndex.fixed && node.data.flagsIndex.fixed.flag === firstFixed.flags[0].flag);
if (existNode) {
alert(`固定清单“${firstFixed.name}”已被第${existNode.serialNo() + 1}行清单占用。`);
locateAtSpread(sheet, existNode.serialNo(), col);
return;
}
}
isInserting = true;
await ajaxPost('/bills/insertBills', { postData: compareData.postData });
// 更新前端树
const toReverseData = [];
const cacheData = [];
compareData.postData.forEach(item => {
const cacheItem = {
type: ModuleNames.bills,
action: item.updateType === updateType.create ? 'add' : 'update',
data: item.updateData,
}
if (item.updateType === updateType.create) {
toReverseData.push(cacheItem);
} else {
cacheData.push(cacheItem);
}
});
cacheData.push(...reverseInsertData(toReverseData));
console.log(cacheData);
const nodes = projectObj.project.updateNodesCache(cacheData, false);
projectObj.mainController.refreshTreeNode(nodes);
row = nodes[nodes.length - 1].serialNo();
//有新的节点插入,也有可能定位至旧节点(批量选用的情况下)
if (compareData.locateNode) {
//该清单节点在主树的位置
row = projectObj.project.mainTree.nodes[projectObj.project.mainTree.prefix + compareData.locateNode.data.ID].serialNo();
}
locateAtSpread(sheet, row, col);
} else if (compareData.locateNode) {
//该清单节点在主树的位置
row = projectObj.project.mainTree.nodes[projectObj.project.mainTree.prefix + compareData.locateNode.data.ID].serialNo();
locateAtSpread(sheet, row, col);
}
} catch (err) {
console.log(err);
if (!$('hintBox_form').is(':visible')) {
alert(err);
}
} finally {
isInserting = false;
$.bootstrapLoading.end();
}
}
function locateAtSpread(sheet, row, col) {
sheet.setSelection(row, col, 1, 1);
projectObj.mainController.setTreeSelected(projectObj.mainController.tree.items[row]);//触发树节点选中事件
sheet.showRow(row, GC.Spread.Sheets.VerticalPosition.center);
}
/*
*
* 1.选中的树结构(清单规则选中的节点及其所有父项)与主树对比(主树中节点“编码-名称-单位”与选中树~组合相同视为同一节点),
* 将主树中不含有的选中节点全部插入
* 2.插入位置由1对比得出,主树有与选中树结构相同层次结构的片段,这个相同片段为插入位置
* 3.选中树结构除去相同片段,为需要插入的节点,插入时,遇到同层节点,根据编码的字符编码值确定顺序
* a.code <= b.code,则a节点在b节点前
* */
//获取选中的树(将选中的节点及其所有父项,组合成一棵树,没有重复节点)
//@param {Array}lowestNodes(选中的最底层节点) @return {Array}
function getSelTree(lowestNodes) {
let allNodes = [];
//获取树上所有的节点
for (let node of lowestNodes) {
while (node && !allNodes.includes(node)) {
allNodes.push(node);
node = node.parent;
}
}
// 根据原节点serialNo排序,排序后,后一项节点只可能前节点的后兄弟项,或子项
allNodes.sort(function (a, b) {
let aV = a.serialNo(),
bV = b.serialNo();
if (aV > bV) {
return 1;
} else if (aV < bV) {
return -1;
}
return 0;
});
//生成树数据
let treeData = [];
//旧ID与新ID映射
let IDMapping = {};
const ParentIDMap = {};
for (let i = 0; i < allNodes.length; i++) {
//原节点
let thisN = allNodes[i];
let newNodeData = { code: thisN.data.code, name: thisN.data.name, ID: uuid.v1(), NextSiblingID: -1, ParentID: -1, orgID: thisN.data.ID };
IDMapping[newNodeData.orgID] = newNodeData.ID;
(ParentIDMap[thisN.data.ParentID] || (ParentIDMap[thisN.data.ParentID] = [])).push(newNodeData); // 兄弟节点已经拍好序(serialNo排序)
treeData.push(newNodeData);
}
// 重新设置ParentID、NextSiblingID
for (const orgParentID in ParentIDMap) {
const newNodeDatas = ParentIDMap[orgParentID];
const newParentID = orgParentID == -1 ? -1 : IDMapping[orgParentID];
newNodeDatas.forEach((newNodeData, index) => {
newNodeData.ParentID = newParentID;
const preNewNodeData = newNodeDatas[index - 1];
if (preNewNodeData) {
preNewNodeData.NextSiblingID = newNodeData.ID;
}
});
}
let selTree = idTree.createNew({id: 'ID', pid: 'ParentID', nid: 'NextSiblingID', rootId: -1, autoUpdate: true});
selTree.loadDatas(treeData);
return selTree;
}
/*
* 选中树与清单主树进行对比,(自上而下,从roots开始)获取插入、更新数据
* 找到的清单树中清单子项含有非清单项或输入了计算基数,则中止该清单的对比,不可插入数据
* */
const firstBillsSymbol = Symbol('第一行固定清单');
//获取节点的匹配数据:编码-名称-单位
//@param {Object}node @return {String}
function getMatchContent(node) {
// 标准清单第一个根节点与项目清单第一个根节点写死能对应匹配上
const firstStdNode = bills.tree.roots[0];
const firstProjectNode = projectObj.project.Bills.tree.roots[0];
if (node === firstStdNode || node === firstProjectNode) {
return firstBillsSymbol;
}
return `${node.data.code ? node.data.code : '*'}-${node.data.name ? node.data.name : '*'}-${node.data.unit ? node.data.unit : '*'}`;
}
//**对比清单主树节点片段与选中树节点片段,获取需要更新和插入的数据**
//@param {Object}parent - 主树片段的父节点 {Array}mainTreeFragment {Array}stdTreeFragment
function compareTree(parent, mainTreeFragment, stdTreeFragment) {
//需要插入、更新的数据
let postData = [],
//跟树结构自动定位至的清单节点(最近匹配到的节点)
locateNode = null;
if (!mainTreeFragment || !stdTreeFragment) {
return postData;
}
comparePeer(parent, mainTreeFragment, stdTreeFragment);
return {postData, locateNode};
/*
* 该清单节点是否可以继续往下递归匹配,即该节点是否还可插入子项(在该层匹配到的时候判断)
* 1.该底层节点不能含有非清单子项
* 2.该节点不能含有计算基数
* todo 3.数量单价等相关概念,等有了补上
* @param {Object}billsNode(清单树中的某节点) @return {Boolean}
* */
function canRecursive(billsNode) {
//主树节点含有子节点,清单节点不含子节点,说明该节点含有非清单子项
//这里需要去mainTree查,不能在清单树查,因为清单树的节点children里只会有清单,就算实际上有定额,这个children里也不含该定额
let mainTreeNode = projectObj.project.mainTree.getNodeByID(billsNode.data.ID);
if (!mainTreeNode) {
return false;
}
if (mainTreeNode && mainTreeNode.children.length > 0 && billsNode.children.length === 0) {
return false;
} else if (billsNode.data.calcBase) {
return false;
}
return true;
}
//获取某选中节点要往清单主树同层插入的位置
function insertPos(peerNodes, node) {
//node选中树中没有记录原清单的数据,只记录了原清单ID,需要node的数据,则要找回原清单
let orgNode = bills.tree.nodes[`${bills.tree.prefix}${node.data.orgID}`];
if (!orgNode) {
return -1;
}
let insertCode = orgNode.data.code ? orgNode.data.code : '';
//插入最顶层节点或者无编码,对比选中节点后兄弟节点和同层主树节点的名称,插在的匹配的节点前面
function mathNext(selNode, compareNodes) {
let selNext = selNode.nextSibling;
while (selNext) {
for (let i = 0; i < compareNodes.length; i++) {
if (compareNodes[i].data.name === selNext.data.name) {
return i;
}
}
selNext = selNext.nextSibling;
}
return compareNodes.length;
}
if (orgNode.depth() === 0 || insertCode === '') {
return mathNext(orgNode, peerNodes);
//return peerNodes.length;
}
// 比较编码大小,可能需要对比xx-xx xx-xx-xx ....
function compareCode(codeA, codeB) {
const codeASplits = codeA.split('-');
const codeBSplits = codeB.split('-');
const count = Math.max(codeASplits.length, codeBSplits.length);
for (let i = 0; i < count; i++) {
const perA = codeASplits[i];
const perB = codeBSplits[i];
if (perA && !perB) {
return 1;
} else if (!perA && perB) {
return -1;
} else if (perA && perB) {
const numberCompare = !isNaN(perA) && !isNaN(perB);
const compareRst = numberCompare ? +perA - +perB : perA.localeCompare(perB);
if (compareRst === 0) {
continue;
} else {
return compareRst;
}
}
}
}
for (let i = 0; i < peerNodes.length; i++) {
let thisNode = peerNodes[i];
let thisCode = thisNode.data.code ? thisNode.data.code : '';
//确定同层节点的顺序,编码小于等于在前,大于在后
const compareRst = compareCode(insertCode, thisCode);
if (compareRst <= 0) {
return i;
} else {
continue;
}
}
/* for (let i = 0; i < peerNodes.length; i++) {
let thisNode = peerNodes[i];
let thisCode = thisNode.data.code ? thisNode.data.code : '';
//确定同层节点的顺序,编码小于等于在前,大于在后
if (insertCode <= thisCode) {
return i;
} else if (insertCode > thisCode) {
continue;
}
} */
return peerNodes.length;
}
//获取插入清单数据
function getInsertData(insertObj) {
let stdNode = bills.tree.nodes[`${bills.tree.prefix}${insertObj.orgID}`];
if (!stdNode) {
return null;
}
let stdData = {};
stdData.projectID = projectObj.project.projectInfo.ID;
stdData.isAdd = 1;
stdData.ID = insertObj.ID;
stdData.ParentID = insertObj.ParentID;
stdData.NextSiblingID = insertObj.NextSiblingID;
//顶层节点是大项费用
stdData.type = stdNode.parent ? billType.BILL : billType.DXFY;
stdData.code = stdNode.data.code;
stdData.name = stdNode.data.name;
stdData.unit = stdNode.data.unit;
stdData.ruleText = stdNode.data.ruleText;
stdData.comments = stdNode.data.comments;
stdData.programID = stdNode.data.engineering;
stdData.billsLibId = stdNode.data.billsLibId;
if (stdNode.data.fixedFlag) {
stdData.flags = [{flag : stdNode.data.fixedFlag, fieldName : 'fixed'}];
stdData.flagsIndex = {fixed: {fieldName: 'fixed', flag: stdNode.data.fixedFlag}}; //前端用
}
if (projectObj.project.property.valuationType === commonConstants.ValuationType.BOQ) {
stdData.unitPriceAnalysis = 1;
}
return stdData;
}
//从同层节点中获取更新数据
//@param {Object}parentNode(同层节点挂载的父节点,清单主树中) {Array}peerNodes(同层节点,含有清单树和选中树节点)
// {Array}billsNodes(该层清单树节点) {Array}selNodes(该层选中树节点) @return {Array}
function getUpdateDataFromPeer(parentNode, peerNodes, billsNodes, selNodes) {
let rst = [];
//向下获取插入数据直到最底层
function getDataTillDeepest(node) {
if (node) {
let insertData = getInsertData(node.data);
if (insertData) {
rst.push({updateType: updateType.create, updateData: insertData});
}
for (let child of node.children) {
getDataTillDeepest(child);
}
}
}
for (let i = 0; i < peerNodes.length; i++) {
let thisNode = peerNodes[i],
nextNode = peerNodes[i + 1];
//更新原清单节点NextSiblingID
if (billsNodes.includes(thisNode) && selNodes.includes(nextNode)) {
rst.push({updateType: updateType.update, updateData: {ID: thisNode.data.ID, NextSiblingID: nextNode.data.ID}});
} else if (selNodes.includes(thisNode) && billsNodes.includes(nextNode)) { //变更选中节点的NextSiblingID
thisNode.data.NextSiblingID = nextNode.data.ID;
}
//所有选中的同层节点设为清单树父节点的子项,获取插入数据(插入该同层节点及其所有子节点)
if (selNodes.includes(thisNode)) {
thisNode.data.ParentID = parentNode ? parentNode.data.ID : -1;
getDataTillDeepest(thisNode);
}
}
return rst;
}
//同层节点之间比较,匹配到的则继续往下匹配,匹配不到的节点则按照规定的顺序进行排序,插入更新(更新清单树节点中NextSiblingID改变的节点)
function comparePeer(parentNode, billsNodes, selNodes) {
let peerNodes = [].concat(billsNodes); //同层节点
let matchNode = null; //匹配到的清单主树节点
for (let selNode of selNodes) {
let stdNode = bills.tree.nodes[`${bills.tree.prefix}${selNode.data.orgID}`],
selMatch = getMatchContent(stdNode),
isMatched = false;
for( let billsNode of billsNodes) {
let billsMatch = getMatchContent(billsNode);
if (selMatch === billsMatch) {//只进行一次成功匹配
// 特殊处理:如果匹配成功,但是清单名称不同,将造价书清单的名称设成清单库中的清单名称
const stdName = selNode.data.name || '';
const billsName = billsNode.data.name || '';
if (billsName !== stdName) {
postData.push({updateType: updateType.update, updateData: {ID: billsNode.data.ID, name: stdName}});
}
matchNode = billsNode;
isMatched = true;
if (selNode.children.length === 0) {//成功匹配且为选中的最底节点,则为自动定位节点
locateNode = matchNode;
}
break;
}
}
if (isMatched && canRecursive(matchNode)) {//匹配成功,且该匹配到的节点可插入子项,递归匹配子项
comparePeer(matchNode, matchNode.children, selNode.children);
} else if (!isMatched) { //匹配不成功,其节点与该层清单节点同层,根据编码插入同层数组中
let pos = insertPos(peerNodes, selNode);
if (pos >= 0) {
peerNodes.splice(pos, 0, selNode);
}
}
}
//同层节点比清单树节点多了,说明在该层有选中节点插入清单树中
if (peerNodes.length > billsNodes.length) {
let updateData = getUpdateDataFromPeer(parentNode, peerNodes, billsNodes, selNodes);
postData = postData.concat(updateData);
}
}
}
function _chkBillsUnitPrice(billsNodes) {
if (billsUnitPriceFeature !== null) {
for (const node of billsNodes) {
if (node.updateType === 'create') {
//只有创建的才设置初始化单价
const _chkKeys = function(keys) {
let rst = false;
for (const subKey of keys) {
let isPrjFeatureMatch = false;
for (let prjF of projectObj.project.projectInfo.property.projectFeature) {
if (prjF.key === bmsK.key) {
if (prjF.value === bmsK.value) {
isPrjFeatureMatch = true;
// isKeyMatch = true;
break;
} else {
//key相同而value不同,则无需再循环,直接退出
break;
}
}
}
if (isPrjFeatureMatch) {
rst = true;
break;
}
}
return rst;
};
let isMatch = true;
// 1. 基本数量
// 先判断此bills是否有配置
for (const bm of billsUnitPriceFeature.feature.basicMappings) {
if (bm.parentBasicKeys.length === billsUnitPriceFeature.feature.basicKeyOptions.length) {
for (let kIdx = 0; kIdx < bm.parentBasicKeys.length; kIdx++) {
if (bm.parentBasicKeys[kIdx] !== 'ALL' && node[billsUnitPriceFeature.feature.basicKeyOptions[kIdx]] !== bm.parentBasicKeys[kIdx]) {
isMatch = false;
break;
}
}
} else {
isMatch = false;
break;
}
}
let basicValue = 0;
if (isMatch) {
//再根据相关项目属性指定基数
for (const bms of billsUnitPriceFeature.feature.basicMappings.subs) {
if (_chkKeys(bms.keys)) {
basicValue = bms.basicValue;
break;
}
}
}
// 2. 相关系数(允许多个)
let factors = [];
isMatch = true;
for (const fm of billsUnitPriceFeature.feature.factorMappings) {
if (fm.basicFactorKeys.length === billsUnitPriceFeature.feature.basicKeyOptions.length) {
for (let kIdx = 0; kIdx < fm.basicFactorKeys.length; kIdx++) {
if (fm.basicFactorKeys[kIdx] !== 'ALL' && node[billsUnitPriceFeature.feature.basicKeyOptions[kIdx]] !== fm.basicFactorKeys[kIdx]) {
isMatch = false;
break;
}
}
} else {
isMatch = false;
break;
}
}
if (isMatch) {
for (const fms of billsUnitPriceFeature.feature.factorMappings.subs) {
if (_chkKeys(fms.keys)) {
factors.push(fms.basicValue); // 允许多个
}
}
}
//3. 装配(指标基价 即 默认的清单单价)
let unitFeeVal = basicValue;
for (const factor of factors) {
unitFeeVal = unitFeeVal * parseFloat(factor);
}
// 暂时未设小数位数 scMathUtil.roundTo(unitFeeVal,2);
node.calcFlag = 2; //当用户输入单价
if (!node.hasOwnProperty('fees')) {
node.fees = [];
}
node.fees.push({fieldName: 'common', unitFee: unitFeeVal, totalFee: 0, tenderUnitFee: unitFeeVal, tenderTotalFee: 0});
}
}
}
}
//项目指引类型
const itemType = {
job: 0,
ration: 1
};
const guideItem = {
dom: $('#billsGuidance_items'),
workBook: null,
tree: null,
controller: null,
treeSetting: {
treeCol: 1,
emptyRows: 0,
headRows: 1,
headRowHeight: [40],
defaultRowHeight: 21,
cols: [
{
width: 35,
readOnly: false,
head: {
titleNames: ["选择"],
spanCols: [1],
spanRows: [1],
vAlign: [1],
hAlign: [1],
font: ["Arial"]
},
data: {
field: "select",
vAlign: 1,
hAlign: 1,
font: "Arial"
}
},
{
width: 420,
readOnly: false,
head: {
titleNames: ["项目指引"],
spanCols: [1],
spanRows: [1],
vAlign: [1],
hAlign: [1],
font: ["Arial"]
},
data: {
field: "name",
vAlign: 1,
hAlign: 0,
font: "Arial"
}
}
]
},
headers: [
{name: '选择', dataCode: 'select', width: 35, vAlign: 'center', hAlign: 'center', formatter: '@'},
{name: '项目指引', dataCode: 'name', width: 300, vAlign: 'center', hAlign: 'left', formatter: '@'},
],
rowHeaderWidth:25,
events: {
EditStarting: function (sender, args) {
if(!bills.tree || guideItem.headers[args.col]['dataCode'] === 'name'){
args.cancel = true;
}
},
ButtonClicked: function (sender, args) {
if(args.sheet.isEditing()){
args.sheet.endEdit(true);
}
refreshInsertRation();
},
CellDoubleClick: function (sender, args) {
if(!bills.tree || !bills.tree.selected){
return;
}
let node = bills.tree.selected.guidance.tree.selected;
if(!node){
return;
}
if(node.children.length === 0){
if(guideItem.headers[args.col]['dataCode'] === 'name'){
insertRations(getInsertRationData([args.row]));
}
}
else {
node.setExpanded(!node.expanded);
renderSheetFunc(args.sheet, function () {
let iCount = node.posterityCount(), i, child;
for (i = 0; i < iCount; i++) {
child = bills.tree.selected.guidance.tree.items[args.row + i + 1];
args.sheet.setRowVisible(args.row + i + 1, child.visible, args.sheetArea);
}
args.sheet.invalidateLayout();
});
args.sheet.repaint();
}
}
}
};
const elfItem = {
dom: $('#billsGuidance_items'),
workBook: null,
tree: null,
controller: null,
treeSetting: {
treeCol: 0,
emptyRows: 0,
headRows: 1,
headRowHeight: [40],
defaultRowHeight: 21,
cols: [
{
width: 250,
readOnly: true,
head: {
titleNames: ["施工工序"],
spanCols: [1],
spanRows: [1],
vAlign: [1],
hAlign: [1],
font: ["Arial"]
},
data: {
field: "name",
vAlign: 1,
hAlign: 0,
font: "Arial"
}
},
{
width: 250,
readOnly: false,
head: {
titleNames: ["选项"],
spanCols: [1],
spanRows: [1],
vAlign: [1],
hAlign: [1],
font: ["Arial"]
},
data: {
field: "options",
vAlign: 1,
hAlign: 0,
font: "Arial"
}
}
]
},
headers: [
{name: '施工工序', dataCode: 'name', width: 250, rateWidth: 0.5, vAlign: 'center', hAlign: 'center', formatter: '@'},
{name: '选项', dataCode: 'options', width: 250, rateWidth: 0.5, vAlign: 'center', hAlign: 'left', formatter: '@'},
],
rowHeaderWidth:25,
events: {
CellClick: function (sender, args) {
if(elfItem.headers[args.col]['dataCode'] === 'options' && args.sheetArea === 3){
if(!args.sheet.getCell(args.row, args.col).locked() && !args.sheet.isEditing()){
args.sheet.startEdit();
}
}
},
ClipboardPasting: function (sender, info) {
info.cancel = true;
}
}
};
const options = {
workBook: {
tabStripVisible: false,
allowContextMenu: false,
allowCopyPasteExcelStyle : false,
allowExtendPasteRange: false,
allowUserDragDrop : false,
allowUserDragFill: false,
scrollbarMaxAlign : true
},
sheet: {
protectionOptions: {allowResizeRows: true, allowResizeColumns: true},
clipBoardOptions: GC.Spread.Sheets.ClipboardPasteOptions.values
}
};
//渲染时方法,停止渲染
//@param {Object}sheet {Function}func @return {void}
function renderSheetFunc(sheet, func){
sheet.suspendEvent();
sheet.suspendPaint();
if(func){
func();
}
sheet.resumeEvent();
sheet.resumePaint();
}
//设置表选项
//@param {Object}workBook {Object}opts @return {void}
function setOptions (workBook, opts) {
for(let opt in opts.workBook){
workBook.options[opt] = opts.workBook[opt];
}
for(let opt in opts.sheet){
workBook.getActiveSheet().options[opt] = opts.sheet[opt];
}
}
//建表头
//@param {Object}sheet {Array}headers @return {void}
function buildHeader(sheet, headers) {
let fuc = function () {
sheet.setColumnCount(headers.length);
sheet.setRowHeight(0, 30, GC.Spread.Sheets.SheetArea.colHeader);
//sheet.setColumnWidth(0, sheet.getParent() === bills.workBook ? 15 : 25, GC.Spread.Sheets.SheetArea.rowHeader);
if(sheet.getParent() === elfItem.workBook || sheet.getParent() === guideItem.workBook){
sheet.setRowHeight(0, 20, GC.Spread.Sheets.SheetArea.colHeader);
}
for(let i = 0, len = headers.length; i < len; i++){
sheet.setValue(0, i, headers[i].name, GC.Spread.Sheets.SheetArea.colHeader);
sheet.setColumnWidth(i, headers[i].width, GC.Spread.Sheets.SheetArea.colHeader);
if(headers[i].formatter){
sheet.setFormatter(-1, i, headers[i].formatter);
}
sheet.getRange(-1, i, -1, 1).hAlign(GC.Spread.Sheets.HorizontalAlign[headers[i]['hAlign']]);
sheet.getRange(-1, i, -1, 1).vAlign(GC.Spread.Sheets.VerticalAlign[headers[i]['vAlign']]);
}
};
renderSheetFunc(sheet, fuc);
}
//表监听事件
//@param {Object}workBook @return {void}
function bindEvent(workBook, events) {
if(Object.keys(events).length === 0){
return;
}
const Events = GC.Spread.Sheets.Events;
for(let event in events){
workBook.bind(Events[event], events[event]);
}
}
//根据宽度比例设置列宽
//@param {Object}workBook {Number}workBookWidth {Array}headers @return {void}
function setColumnWidthByRate(workBook, workBookWidth, headers) {
if(workBook){
workBookWidth -= 48;
const sheet = workBook.getActiveSheet();
sheet.suspendEvent();
sheet.suspendPaint();
for(let col = 0; col < headers.length; col++){
if(headers[col]['rateWidth'] !== undefined && headers[col]['rateWidth'] !== null && headers[col]['rateWidth'] !== ''){
let width = workBookWidth * headers[col]['rateWidth'];
if(headers[col]['dataCode'] === 'options'){
width = width;
}
sheet.setColumnWidth(col, width, GC.Spread.Sheets.SheetArea.colHeader)
}
else {
if(headers[col]['headerWidth'] !== undefined && headers[col]['headerWidth'] !== null && headers[col]['headerWidth'] !== ''){
sheet.setColumnWidth(col, headers[col]['headerWidth'], GC.Spread.Sheets.SheetArea.colHeader)
}
}
}
sheet.resumeEvent();
sheet.resumePaint();
}
}
//建表
//@param {Object}module @return {void}
function buildSheet(module) {
if(!module.workBook){
module.workBook = new GC.Spread.Sheets.Workbook(module.dom[0], {sheetCount: 1});
sheetCommonObj.spreadDefaultStyle(module.workBook);
let sheet = module.workBook.getActiveSheet();
if(module === bills){
//默认初始可控制焦点在清单表中
sheet.options.rowHeaderVisible = false;
module.workBook.focus();
sheet.options.isProtected = true;
sheet.name('stdBillsGuidance_bills');
//设置悬浮提示
TREE_SHEET_HELPER.initSetting(bills.dom[0], bills.treeSetting);
}
if(module === guideItem){
sheet.options.isProtected = true;
sheet.getRange(-1, 0, -1, 1).locked(false);
sheet.getRange(-1, 1, -1, 1).locked(true);
}
if(module === elfItem){
sheet.options.isProtected = true;
sheet.getRange(-1, 0, -1, 1).locked(true);
sheet.getRange(-1, 1, -1, 1).locked(false);
}
if(module.rowHeaderWidth) {
sheet.setColumnWidth(0, module.rowHeaderWidth, GC.Spread.Sheets.SheetArea.rowHeader);
}
setOptions(module.workBook, options);
buildHeader(module.workBook.getActiveSheet(), module.headers);
if(module === elfItem){
setColumnWidthByRate(elfItem.workBook, $('#zy').width(), elfItem.headers)
}
bindEvent(module.workBook, module.events);
}
}
//清空表数据
//@param {Object}sheet {Array}headers {Number}rowCount @return {void}
function cleanData(sheet, headers, rowCount){
renderSheetFunc(sheet, function () {
sheet.clear(-1, 0, -1, headers.length, GC.Spread.Sheets.SheetArea.viewport, GC.Spread.Sheets.StorageType.data);
if (rowCount > 0) {
sheet.setRowCount(rowCount);
}
});
}
//初始化各工作表
//@param {Array}modules @return {void}
function initWorkBooks(modules){
for(let module of modules){
buildSheet(module);
}
}
//点击清单名称后面的问号,弹出补注窗口并设置当前节点(或xxx父节点)的补注
//@param {Number}row(当前焦点行) @return {void}
function initRechargeModal(row) {
let node = bills.tree.items[row];
while (node && !node.data.recharge){
node = node.parent;
}
let recharge = node && node.data.recharge ? node.data.recharge : '无内容';
node = bills.tree.items[row];
while (node && !node.data.ruleText){
node = node.parent;
}
let ruleText = node && node.data.ruleText ? node.data.ruleText : '无内容';
$('#questionTab1').text('补注');
$('#questionContent1').html(recharge);
$('#questionContent2').html(ruleText);
$('#questionModal').modal('show');
}
//节点链上含有补注或工程量计算规则数据
//@param {Number}row(行当前行) @return {Boolean}
function hasRechargeRuleText(row) {
let node = bills.tree.items[row];
if (!node) {
return false;
}
while (node) {
if (node.data.recharge || node.data.ruleText) {
return true;
}
node = node.parent;
}
return false;
}
//初始化并输出树
//@param {Object}module {Object}sheet {Object}treeSetting {Array}datas
function initTree(module, sheet, treeSetting, datas){
module.tree = idTree.createNew({id: 'ID', pid: 'ParentID', nid: 'NextSiblingID', rootId: -1, autoUpdate: true});
module.controller = TREE_SHEET_CONTROLLER.createNew(module.tree, sheet, treeSetting, false);
module.tree.loadDatas(datas);
if(module === bills){
initExpandStat();
}
module.controller.showTreeData();
if (module === bills) {
module.workBook.getSheet(0).options.rowHeaderVisible = true;
setBillsHint(bills.tree.items, stdBillsJobData, stdBillsFeatureData);
renderSheetFunc(sheet, function () {
const checkBoxType = new GC.Spread.Sheets.CellTypes.CheckBox();
for (let i = 0; i < bills.tree.items.length; i++) {
sheet.setCellType(i, 0, checkBoxType);
sheet.setCellType(i, 2, TREE_SHEET_HELPER.getQuestionCellType(initRechargeModal, hasRechargeRuleText));
}
});
}
}
//项目指引表焦点控制
//@param {Number}row @return {void}
function guideItemInitSel(row){
let billsNode = bills.tree.selected;
let node = null;
if(billsNode && billsNode.guidance.tree){
node = billsNode.guidance.tree.items[row];
if(node){
billsNode.guidance.tree.selected = node;
}
}
}
//清单精灵表焦点控制
//@param {Number}row @return {void}
function elfItemInitSel(row){
let billsNode = bills.tree.selected;
let node = null;
if(billsNode && billsNode.elf.tree){
node = billsNode.elf.tree.items[row];
if(node){
billsNode.elf.tree.selected = node;
}
}
}
//根据项目指引的类型设置单元格类型,定额类型的项目指引为复选框
//@param {Array}nodes @return {void}
function setItemCellType(nodes){
//设置单元格类型
const base = new GC.Spread.Sheets.CellTypes.Base();
const checkBox = new GC.Spread.Sheets.CellTypes.CheckBox();
const sheet = guideItem.workBook.getActiveSheet();
renderSheetFunc(sheet, function(){
for(let node of nodes){
sheet.setCellType(node.serialNo(), 0, node.data.type === itemType.ration ? checkBox : base);
}
});
}
//初始化清单的工作内容和项目特征
//@param {Number}billsLibId {Function}callback @return {void}
function initJobAndCharacter(billsLibId, callback){
CommonAjax.post('/stdBillsEditor/getJobContent', {userId: userID, billsLibId: billsLibId}, function (datas) {
stdBillsJobData = datas;
CommonAjax.post('/stdBillsEditor/getItemCharacter', {userId: userID, billsLibId: billsLibId}, function (datas) {
stdBillsFeatureData = datas;
if(callback){
callback();
}
});
});
}
//初始化清单展开收起状态
//@return {void}
function initExpandStat(){
//读取展开收起状态
let currentExpState = sessionStorage.getItem('stdBillsGuidanceExpState');
if(currentExpState){
bills.tree.setExpandedByState(bills.tree.items, currentExpState);
}
//非叶子节点默认收起
else{
bills.tree.setRootExpanded(bills.tree.roots, false);
// 默认展开第一个节点到第二层
bills.tree.roots[0].setExpanded(true);
}
}
//设置tag以悬浮提示
function setTagForHint(nodes){
let sheet = bills.workBook.getActiveSheet();
renderSheetFunc(sheet, function () {
for(let node of nodes){
sheet.setTag(node.serialNo(), 2, node.data.ruleText ? node.data.ruleText : '');
}
});
}
//根据编码定位至清单精灵库中
//@param {String}code @return {void}
function locateAtBills(code) {
let nineCode = code.substring(0, 9);
let items = bills.tree.items;
let locateBills = _.find(items, function(item){
return item.data.code === nineCode;
});
if(locateBills){
expandSearchNodes([locateBills]);
sessionStorage.setItem('stdBillsGuidanceExpState', bills.tree.getExpState(bills.tree.items));
}
let sheet = bills.workBook.getActiveSheet();
let locateRow = locateBills ? locateBills.serialNo() : 0;
sheet.setActiveCell(locateRow, 0);
sheet.showRow(locateRow, GC.Spread.Sheets.VerticalPosition.center);
}
//清单设置悬浮提示信息
//@param {Array}billsNodes(清单节点) {Array}jobs(总的工作内容数据) {Array}items(总的项目特征数据)
function setBillsHint(billsNodes, jobs, items) {
let jobsMapping = {},
itemsMapping = {};
for(let job of jobs){
jobsMapping[job.id] = job;
}
for(let item of items){
itemsMapping[item.id] = item;
}
let tagInfo = [];
for(let billsNode of billsNodes){
let hintArr = [];
let billsItems = billsNode.data.items;
if(billsItems.length > 0){
//项目特征
hintArr.push('项目特征:');
}
let itemCount = 1,
jobCount = 1;
for(let billsItem of billsItems){
let itemData = itemsMapping[billsItem.id];
if(itemData){
//特征值
let eigens = [];
for(let eigen of itemData.itemValue){
eigens.push(eigen.value);
}
eigens = eigens.join(';');
hintArr.push(`${itemCount}.${itemData.content}${eigens === '' ? '' : ': ' + eigens}`);
itemCount ++;
}
}
//工作内容
let billsJobs = billsNode.data.jobs;
if(billsJobs.length > 0){
hintArr.push('工作内容:');
}
for(let billsJob of billsJobs){
let jobData = jobsMapping[billsJob.id];
if(jobData){
hintArr.push(`${jobCount}.${jobData.content}`);
jobCount ++;
}
}
/*if(billsNode.data.ruleText && billsNode.data.ruleText !== ''){
hintArr.push('工程量计算规则:');
hintArr.push(billsNode.data.ruleText);
}
if(billsNode.data.recharge && billsNode.data.recharge !== ''){
hintArr.push('补注:');
hintArr.push(billsNode.data.recharge);
}*/
if(hintArr.length > 0){
tagInfo.push({row: billsNode.serialNo(), value: hintArr.join('\n')});
}
}
let sheet = bills.workBook.getActiveSheet();
renderSheetFunc(sheet, function () {
for(let tagI of tagInfo){
sheet.setTag(tagI.row, 0, tagI.value);
}
});
}
//初始选择标准清单
//@param {Number}libID @return {void}
function libInitSel(libID){
//获取清单
$.bootstrapLoading.start();
CommonAjax.post('/billsGuidance/api/getLibWithBills', {libID: libID, isGuidanceLib: false}, function(rstData){
if(guideItem.workBook){
guideItem.workBook.destroy();
guideItem.workBook = null;
}
if(elfItem.workBook){
elfItem.workBook.destroy();
elfItem.workBook = null;
}
initViews();
let callback = function () {
initTree(bills, bills.workBook.getActiveSheet(), bills.treeSetting, rstData.bills);
if(doAfterLoadGuidance){
doAfterLoadGuidance();
}
$.bootstrapLoading.end();
};
//获取清单库中的工作内容和项目特征
initJobAndCharacter(libID, callback);
}, function () {
$.bootstrapLoading.end();
});
}
//初始化清单指引库
//@param {Array}libDats @return {void}
function initLibs(libDatas){
libSel.empty();
if(!libDatas){
return;
}
let selectedLib = sessionStorage.getItem('stdBillsGuidance');
for(let libData of libDatas){
let opt = $('