std_billsGuidance_lib.js 61 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432
  1. 'use strict';
  2. /**
  3. *
  4. *
  5. * @author Zhong
  6. * @date 2018/6/11
  7. * @version
  8. */
  9. //清单指引/精灵获取完清单数据后的回调函数
  10. let doAfterLoadGuidance = null;
  11. const billsGuidance = (function () {
  12. //更新类型
  13. const updateType = {update: 'update', create: 'create'};
  14. //库类型
  15. const libType = {'guidance': 1, 'elf': 2}; //清单指引、清单精灵
  16. const libSel = $('#stdBillsGuidanceLibSelect');
  17. //工作内容
  18. let stdBillsJobData = [];
  19. //项目特征
  20. let stdBillsFeatureData = [];
  21. //正在插入
  22. let isInserting = false;
  23. const bills = {
  24. dom: $('#billsGuidance_bills'),
  25. workBook: null,
  26. cache: [],
  27. tree: null,
  28. controller: null,
  29. treeSetting: {
  30. emptyRowHeader: true,
  31. rowHeaderWidth: 15,
  32. treeCol: 1,
  33. emptyRows: 0,
  34. headRows: 1,
  35. headRowHeight: [40],
  36. defaultRowHeight: 21,
  37. cols: [
  38. {
  39. width: 35,
  40. readOnly: false,
  41. showHint: false,
  42. head: {
  43. titleNames: ["选用"],
  44. spanCols: [1],
  45. spanRows: [1],
  46. vAlign: [1],
  47. hAlign: [1],
  48. font: ["Arial"]
  49. },
  50. data: {
  51. field: "select",
  52. vAlign: 1,
  53. hAlign: 1,
  54. font: "Arial"
  55. }
  56. },
  57. {
  58. width: 105,
  59. readOnly: true,
  60. showHint: true,
  61. head: {
  62. titleNames: ["项目编码"],
  63. spanCols: [1],
  64. spanRows: [1],
  65. vAlign: [1],
  66. hAlign: [1],
  67. font: ["Arial"]
  68. },
  69. data: {
  70. field: "code",
  71. vAlign: 1,
  72. hAlign: 0,
  73. font: "Arial"
  74. }
  75. }, {
  76. width: 190,
  77. readOnly: true,
  78. head: {
  79. titleNames: ["项目名称"],
  80. spanCols: [1],
  81. spanRows: [1],
  82. vAlign: [1],
  83. hAlign: [1],
  84. font: ["Arial"]
  85. },
  86. data: {
  87. field: "name",
  88. vAlign: 1,
  89. hAlign: 0,
  90. font: "Arial"
  91. }
  92. },
  93. {
  94. width: 60,
  95. readOnly: true,
  96. head: {
  97. titleNames: ["计量单位"],
  98. spanCols: [1],
  99. spanRows: [1],
  100. vAlign: [1],
  101. hAlign: [1],
  102. font: ["Arial"]
  103. },
  104. data: {
  105. field: "unit",
  106. vAlign: 1,
  107. hAlign: 1,
  108. font: "Arial"
  109. }
  110. }
  111. ]
  112. },
  113. headers: [
  114. {name: '选用', dataCode: 'select', width: 35, vAlign: 'center', hAlign: 'center', formatter: '@'},
  115. {name: '项目编码', dataCode: 'code', width: 105, vAlign: 'center', hAlign: 'left', formatter: '@'},
  116. {name: '项目名称', dataCode: 'name', width: 190, vAlign: 'center', hAlign: 'left', formatter: '@'},
  117. {name: '单位', dataCode: 'unit', width: 60, vAlign: 'center', hAlign: 'center', formatter: '@'},
  118. ],
  119. rowHeaderWidth:1,
  120. events: {
  121. ButtonClicked: function (sender, args) {
  122. if(args.sheet.isEditing()){
  123. args.sheet.endEdit(true);
  124. }
  125. if (args.col !== 0) {
  126. return;
  127. }
  128. const selectedNode = bills.tree.items[args.row];
  129. const curValue = args.sheet.getValue(args.row, args.col);
  130. let count = selectedNode.posterityCount();
  131. if (count) {
  132. renderSheetFunc(args.sheet, () => {
  133. let row = args.row;
  134. while (count--) {
  135. row++;
  136. args.sheet.setValue(row, args.col, curValue);
  137. }
  138. });
  139. }
  140. },
  141. CellDoubleClick: function (sender, args) {
  142. if (args.col === 0) {
  143. return;
  144. }
  145. if(!bills.tree){
  146. return;
  147. }
  148. let node = bills.tree.items[args.row];
  149. if(!node){
  150. return;
  151. }
  152. //展开收起(非最底层节点且双击的是第一列)
  153. if (args.col === 1 && node.children.length > 0) {
  154. node.setExpanded(!node.expanded);
  155. //设置展开收起状态
  156. sessionStorage.setItem('stdBillsGuidanceExpState', bills.tree.getExpState(bills.tree.items));
  157. renderSheetFunc(args.sheet, function () {
  158. let iCount = node.posterityCount(), i, child;
  159. for (i = 0; i < iCount; i++) {
  160. child = bills.tree.items[args.row + i + 1];
  161. args.sheet.setRowVisible(args.row + i + 1, child.visible, args.sheetArea);
  162. }
  163. args.sheet.invalidateLayout();
  164. });
  165. args.sheet.repaint();
  166. } else if (!projectReadOnly && !isInserting && !projectObj.project.isBillsLocked() && (![1].includes(args.col) || node.children.length === 0)) {
  167. //选中部分的最底层(只是选中部分的最底)
  168. let lowestNodes = [bills.tree.items[args.row]];
  169. insertBills(lowestNodes);
  170. }
  171. }
  172. }
  173. };
  174. // 获取对比树片段数据的方法,此方法可能会被覆盖,方法存在一个对象中,使得外部可以覆盖相关方法
  175. // 在农村公路2020中,主树对比片段与这里的逻辑是不相同的
  176. const overwrite = {
  177. getFragment() {
  178. return { parent: null, mainTreeFragment: projectObj.project.Bills.tree.roots }
  179. }
  180. };
  181. // 反转插入顺序
  182. // 正常顺序插入时,next数据可能还没插入,会造成错误,需要反过来插入
  183. // insertData为正向排好序的数据
  184. function reverseInsertData(insertData) {
  185. const reverseData = [];
  186. const parentMap = {};
  187. const IDMap = {};
  188. insertData.forEach(item => {
  189. IDMap[item.data.ID] = item;
  190. (parentMap[item.data.ParentID] || (parentMap[item.data.ParentID] = [])).push(item);
  191. });
  192. // 找到顶层数据: 数据中没有找不到对应parent的数据
  193. const topLevelData = insertData
  194. .filter(item => !IDMap[item.data.ParentID])
  195. .reverse();
  196. function getReverseData(items) {
  197. items.forEach(item => {
  198. reverseData.push(item);
  199. const children = parentMap[item.data.ID];
  200. if (children && children.length) {
  201. getReverseData(children.reverse());
  202. }
  203. })
  204. }
  205. getReverseData(topLevelData);
  206. return reverseData;
  207. }
  208. //插入清单
  209. async function insertBills(lowestNodes) {
  210. try {
  211. let selTree = getSelTree(lowestNodes);
  212. const { errMsg, parent, mainTreeFragment } = overwrite.getFragment();
  213. if (errMsg) {
  214. alert(errMsg);
  215. return;
  216. }
  217. let compareData = compareTree(parent, mainTreeFragment, selTree.roots);
  218. _chkBillsUnitPrice(compareData.postData); //设置指标基价(默认单价)
  219. let sheet = projectObj.mainSpread.getActiveSheet(),
  220. row = sheet.getActiveColumnIndex(),
  221. col = sheet.getActiveColumnIndex();
  222. if (compareData.postData.length > 0) {
  223. //如果插入的是固定清单,则需要判断该固定清单在造价书中是否已存在,造价书中不可存在相同的固定清单
  224. let fixedDatas = compareData.postData.filter((data) =>
  225. data.updateType === updateType.create && Array.isArray(data.updateData.flags));
  226. if (fixedDatas.length > 0) {
  227. //提示已存在此固定清单并且定位
  228. let firstFixed = fixedDatas[0].updateData;
  229. let existNode = projectObj.project.mainTree.items.find((node) =>
  230. node.data && node.data.flagsIndex && node.data.flagsIndex.fixed && node.data.flagsIndex.fixed.flag === firstFixed.flags[0].flag);
  231. if (existNode) {
  232. alert(`固定清单<strong>“${firstFixed.name}”</strong>已被第${existNode.serialNo() + 1}行清单占用。`);
  233. locateAtSpread(sheet, existNode.serialNo(), col);
  234. return;
  235. }
  236. }
  237. isInserting = true;
  238. await ajaxPost('/bills/insertBills', { postData: compareData.postData });
  239. // 更新前端树
  240. const toReverseData = [];
  241. const cacheData = [];
  242. compareData.postData.forEach(item => {
  243. const cacheItem = {
  244. type: ModuleNames.bills,
  245. action: item.updateType === updateType.create ? 'add' : 'update',
  246. data: item.updateData,
  247. }
  248. if (item.updateType === updateType.create) {
  249. toReverseData.push(cacheItem);
  250. } else {
  251. cacheData.push(cacheItem);
  252. }
  253. });
  254. cacheData.push(...reverseInsertData(toReverseData));
  255. console.log(cacheData);
  256. const nodes = projectObj.project.updateNodesCache(cacheData, false);
  257. projectObj.mainController.refreshTreeNode(nodes);
  258. row = nodes[nodes.length - 1].serialNo();
  259. //有新的节点插入,也有可能定位至旧节点(批量选用的情况下)
  260. if (compareData.locateNode) {
  261. //该清单节点在主树的位置
  262. row = projectObj.project.mainTree.nodes[projectObj.project.mainTree.prefix + compareData.locateNode.data.ID].serialNo();
  263. }
  264. locateAtSpread(sheet, row, col);
  265. } else if (compareData.locateNode) {
  266. //该清单节点在主树的位置
  267. row = projectObj.project.mainTree.nodes[projectObj.project.mainTree.prefix + compareData.locateNode.data.ID].serialNo();
  268. locateAtSpread(sheet, row, col);
  269. }
  270. } catch (err) {
  271. console.log(err);
  272. if (!$('hintBox_form').is(':visible')) {
  273. alert(err);
  274. }
  275. } finally {
  276. isInserting = false;
  277. $.bootstrapLoading.end();
  278. }
  279. }
  280. function locateAtSpread(sheet, row, col) {
  281. sheet.setSelection(row, col, 1, 1);
  282. projectObj.mainController.setTreeSelected(projectObj.mainController.tree.items[row]);//触发树节点选中事件
  283. sheet.showRow(row, GC.Spread.Sheets.VerticalPosition.center);
  284. }
  285. /*
  286. *
  287. * 1.选中的树结构(清单规则选中的节点及其所有父项)与主树对比(主树中节点“编码-名称-单位”与选中树~组合相同视为同一节点),
  288. * 将主树中不含有的选中节点全部插入
  289. * 2.插入位置由1对比得出,主树有与选中树结构相同层次结构的片段,这个相同片段为插入位置
  290. * 3.选中树结构除去相同片段,为需要插入的节点,插入时,遇到同层节点,根据编码的字符编码值确定顺序
  291. * a.code <= b.code,则a节点在b节点前
  292. * */
  293. //获取选中的树(将选中的节点及其所有父项,组合成一棵树,没有重复节点)
  294. //@param {Array}lowestNodes(选中的最底层节点) @return {Array}
  295. function getSelTree(lowestNodes) {
  296. let allNodes = [];
  297. //获取树上所有的节点
  298. for (let node of lowestNodes) {
  299. while (node && !allNodes.includes(node)) {
  300. allNodes.push(node);
  301. node = node.parent;
  302. }
  303. }
  304. // 根据原节点serialNo排序,排序后,后一项节点只可能前节点的后兄弟项,或子项
  305. allNodes.sort(function (a, b) {
  306. let aV = a.serialNo(),
  307. bV = b.serialNo();
  308. if (aV > bV) {
  309. return 1;
  310. } else if (aV < bV) {
  311. return -1;
  312. }
  313. return 0;
  314. });
  315. //生成树数据
  316. let treeData = [];
  317. //旧ID与新ID映射
  318. let IDMapping = {};
  319. const ParentIDMap = {};
  320. for (let i = 0; i < allNodes.length; i++) {
  321. //原节点
  322. let thisN = allNodes[i];
  323. let newNodeData = { code: thisN.data.code, name: thisN.data.name, ID: uuid.v1(), NextSiblingID: -1, ParentID: -1, orgID: thisN.data.ID };
  324. IDMapping[newNodeData.orgID] = newNodeData.ID;
  325. (ParentIDMap[thisN.data.ParentID] || (ParentIDMap[thisN.data.ParentID] = [])).push(newNodeData); // 兄弟节点已经拍好序(serialNo排序)
  326. treeData.push(newNodeData);
  327. }
  328. // 重新设置ParentID、NextSiblingID
  329. for (const orgParentID in ParentIDMap) {
  330. const newNodeDatas = ParentIDMap[orgParentID];
  331. const newParentID = orgParentID == -1 ? -1 : IDMapping[orgParentID];
  332. newNodeDatas.forEach((newNodeData, index) => {
  333. newNodeData.ParentID = newParentID;
  334. const preNewNodeData = newNodeDatas[index - 1];
  335. if (preNewNodeData) {
  336. preNewNodeData.NextSiblingID = newNodeData.ID;
  337. }
  338. });
  339. }
  340. let selTree = idTree.createNew({id: 'ID', pid: 'ParentID', nid: 'NextSiblingID', rootId: -1, autoUpdate: true});
  341. selTree.loadDatas(treeData);
  342. return selTree;
  343. }
  344. /*
  345. * 选中树与清单主树进行对比,(自上而下,从roots开始)获取插入、更新数据
  346. * 找到的清单树中清单子项含有非清单项或输入了计算基数,则中止该清单的对比,不可插入数据
  347. * */
  348. const firstBillsSymbol = Symbol('第一行固定清单');
  349. //获取节点的匹配数据:编码-名称-单位
  350. //@param {Object}node @return {String}
  351. function getMatchContent(node) {
  352. // 标准清单第一个根节点与项目清单第一个根节点写死能对应匹配上
  353. const firstStdNode = bills.tree.roots[0];
  354. const firstProjectNode = projectObj.project.Bills.tree.roots[0];
  355. if (node === firstStdNode || node === firstProjectNode) {
  356. return firstBillsSymbol;
  357. }
  358. return `${node.data.code ? node.data.code : '*'}-${node.data.name ? node.data.name : '*'}-${node.data.unit ? node.data.unit : '*'}`;
  359. }
  360. //**对比清单主树节点片段与选中树节点片段,获取需要更新和插入的数据**
  361. //@param {Object}parent - 主树片段的父节点 {Array}mainTreeFragment {Array}stdTreeFragment
  362. function compareTree(parent, mainTreeFragment, stdTreeFragment) {
  363. //需要插入、更新的数据
  364. let postData = [],
  365. //跟树结构自动定位至的清单节点(最近匹配到的节点)
  366. locateNode = null;
  367. if (!mainTreeFragment || !stdTreeFragment) {
  368. return postData;
  369. }
  370. comparePeer(parent, mainTreeFragment, stdTreeFragment);
  371. return {postData, locateNode};
  372. /*
  373. * 该清单节点是否可以继续往下递归匹配,即该节点是否还可插入子项(在该层匹配到的时候判断)
  374. * 1.该底层节点不能含有非清单子项
  375. * 2.该节点不能含有计算基数
  376. * todo 3.数量单价等相关概念,等有了补上
  377. * @param {Object}billsNode(清单树中的某节点) @return {Boolean}
  378. * */
  379. function canRecursive(billsNode) {
  380. //主树节点含有子节点,清单节点不含子节点,说明该节点含有非清单子项
  381. //这里需要去mainTree查,不能在清单树查,因为清单树的节点children里只会有清单,就算实际上有定额,这个children里也不含该定额
  382. let mainTreeNode = projectObj.project.mainTree.getNodeByID(billsNode.data.ID);
  383. if (!mainTreeNode) {
  384. return false;
  385. }
  386. if (mainTreeNode && mainTreeNode.children.length > 0 && billsNode.children.length === 0) {
  387. return false;
  388. } else if (billsNode.data.calcBase) {
  389. return false;
  390. }
  391. return true;
  392. }
  393. //获取某选中节点要往清单主树同层插入的位置
  394. function insertPos(peerNodes, node) {
  395. //node选中树中没有记录原清单的数据,只记录了原清单ID,需要node的数据,则要找回原清单
  396. let orgNode = bills.tree.nodes[`${bills.tree.prefix}${node.data.orgID}`];
  397. if (!orgNode) {
  398. return -1;
  399. }
  400. let insertCode = orgNode.data.code ? orgNode.data.code : '';
  401. //插入最顶层节点或者无编码,对比选中节点后兄弟节点和同层主树节点的名称,插在的匹配的节点前面
  402. function mathNext(selNode, compareNodes) {
  403. let selNext = selNode.nextSibling;
  404. while (selNext) {
  405. for (let i = 0; i < compareNodes.length; i++) {
  406. if (compareNodes[i].data.name === selNext.data.name) {
  407. return i;
  408. }
  409. }
  410. selNext = selNext.nextSibling;
  411. }
  412. return compareNodes.length;
  413. }
  414. if (orgNode.depth() === 0 || insertCode === '') {
  415. return mathNext(orgNode, peerNodes);
  416. //return peerNodes.length;
  417. }
  418. // 比较编码大小,可能需要对比xx-xx xx-xx-xx ....
  419. function compareCode(codeA, codeB) {
  420. const codeASplits = codeA.split('-');
  421. const codeBSplits = codeB.split('-');
  422. const count = Math.max(codeASplits.length, codeBSplits.length);
  423. for (let i = 0; i < count; i++) {
  424. const perA = codeASplits[i];
  425. const perB = codeBSplits[i];
  426. if (perA && !perB) {
  427. return 1;
  428. } else if (!perA && perB) {
  429. return -1;
  430. } else if (perA && perB) {
  431. const numberCompare = !isNaN(perA) && !isNaN(perB);
  432. const compareRst = numberCompare ? +perA - +perB : perA.localeCompare(perB);
  433. if (compareRst === 0) {
  434. continue;
  435. } else {
  436. return compareRst;
  437. }
  438. }
  439. }
  440. }
  441. for (let i = 0; i < peerNodes.length; i++) {
  442. let thisNode = peerNodes[i];
  443. let thisCode = thisNode.data.code ? thisNode.data.code : '';
  444. //确定同层节点的顺序,编码小于等于在前,大于在后
  445. const compareRst = compareCode(insertCode, thisCode);
  446. if (compareRst <= 0) {
  447. return i;
  448. } else {
  449. continue;
  450. }
  451. }
  452. /* for (let i = 0; i < peerNodes.length; i++) {
  453. let thisNode = peerNodes[i];
  454. let thisCode = thisNode.data.code ? thisNode.data.code : '';
  455. //确定同层节点的顺序,编码小于等于在前,大于在后
  456. if (insertCode <= thisCode) {
  457. return i;
  458. } else if (insertCode > thisCode) {
  459. continue;
  460. }
  461. } */
  462. return peerNodes.length;
  463. }
  464. //获取插入清单数据
  465. function getInsertData(insertObj) {
  466. let stdNode = bills.tree.nodes[`${bills.tree.prefix}${insertObj.orgID}`];
  467. if (!stdNode) {
  468. return null;
  469. }
  470. let stdData = {};
  471. stdData.projectID = projectObj.project.projectInfo.ID;
  472. stdData.isAdd = 1;
  473. stdData.ID = insertObj.ID;
  474. stdData.ParentID = insertObj.ParentID;
  475. stdData.NextSiblingID = insertObj.NextSiblingID;
  476. //顶层节点是大项费用
  477. stdData.type = stdNode.parent ? billType.BILL : billType.DXFY;
  478. stdData.code = stdNode.data.code;
  479. stdData.name = stdNode.data.name;
  480. stdData.unit = stdNode.data.unit;
  481. stdData.ruleText = stdNode.data.ruleText;
  482. stdData.comments = stdNode.data.comments;
  483. stdData.programID = stdNode.data.engineering;
  484. stdData.billsLibId = stdNode.data.billsLibId;
  485. if (stdNode.data.fixedFlag) {
  486. stdData.flags = [{flag : stdNode.data.fixedFlag, fieldName : 'fixed'}];
  487. stdData.flagsIndex = {fixed: {fieldName: 'fixed', flag: stdNode.data.fixedFlag}}; //前端用
  488. }
  489. if (projectObj.project.property.valuationType === commonConstants.ValuationType.BOQ) {
  490. stdData.unitPriceAnalysis = 1;
  491. }
  492. return stdData;
  493. }
  494. //从同层节点中获取更新数据
  495. //@param {Object}parentNode(同层节点挂载的父节点,清单主树中) {Array}peerNodes(同层节点,含有清单树和选中树节点)
  496. // {Array}billsNodes(该层清单树节点) {Array}selNodes(该层选中树节点) @return {Array}
  497. function getUpdateDataFromPeer(parentNode, peerNodes, billsNodes, selNodes) {
  498. let rst = [];
  499. //向下获取插入数据直到最底层
  500. function getDataTillDeepest(node) {
  501. if (node) {
  502. let insertData = getInsertData(node.data);
  503. if (insertData) {
  504. rst.push({updateType: updateType.create, updateData: insertData});
  505. }
  506. for (let child of node.children) {
  507. getDataTillDeepest(child);
  508. }
  509. }
  510. }
  511. for (let i = 0; i < peerNodes.length; i++) {
  512. let thisNode = peerNodes[i],
  513. nextNode = peerNodes[i + 1];
  514. //更新原清单节点NextSiblingID
  515. if (billsNodes.includes(thisNode) && selNodes.includes(nextNode)) {
  516. rst.push({updateType: updateType.update, updateData: {ID: thisNode.data.ID, NextSiblingID: nextNode.data.ID}});
  517. } else if (selNodes.includes(thisNode) && billsNodes.includes(nextNode)) { //变更选中节点的NextSiblingID
  518. thisNode.data.NextSiblingID = nextNode.data.ID;
  519. }
  520. //所有选中的同层节点设为清单树父节点的子项,获取插入数据(插入该同层节点及其所有子节点)
  521. if (selNodes.includes(thisNode)) {
  522. thisNode.data.ParentID = parentNode ? parentNode.data.ID : -1;
  523. getDataTillDeepest(thisNode);
  524. }
  525. }
  526. return rst;
  527. }
  528. //同层节点之间比较,匹配到的则继续往下匹配,匹配不到的节点则按照规定的顺序进行排序,插入更新(更新清单树节点中NextSiblingID改变的节点)
  529. function comparePeer(parentNode, billsNodes, selNodes) {
  530. let peerNodes = [].concat(billsNodes); //同层节点
  531. let matchNode = null; //匹配到的清单主树节点
  532. for (let selNode of selNodes) {
  533. let stdNode = bills.tree.nodes[`${bills.tree.prefix}${selNode.data.orgID}`],
  534. selMatch = getMatchContent(stdNode),
  535. isMatched = false;
  536. for( let billsNode of billsNodes) {
  537. let billsMatch = getMatchContent(billsNode);
  538. if (selMatch === billsMatch) {//只进行一次成功匹配
  539. // 特殊处理:如果匹配成功,但是清单名称不同,将造价书清单的名称设成清单库中的清单名称
  540. const stdName = selNode.data.name || '';
  541. const billsName = billsNode.data.name || '';
  542. if (billsName !== stdName) {
  543. postData.push({updateType: updateType.update, updateData: {ID: billsNode.data.ID, name: stdName}});
  544. }
  545. matchNode = billsNode;
  546. isMatched = true;
  547. if (selNode.children.length === 0) {//成功匹配且为选中的最底节点,则为自动定位节点
  548. locateNode = matchNode;
  549. }
  550. break;
  551. }
  552. }
  553. if (isMatched && canRecursive(matchNode)) {//匹配成功,且该匹配到的节点可插入子项,递归匹配子项
  554. comparePeer(matchNode, matchNode.children, selNode.children);
  555. } else if (!isMatched) { //匹配不成功,其节点与该层清单节点同层,根据编码插入同层数组中
  556. let pos = insertPos(peerNodes, selNode);
  557. if (pos >= 0) {
  558. peerNodes.splice(pos, 0, selNode);
  559. }
  560. }
  561. }
  562. //同层节点比清单树节点多了,说明在该层有选中节点插入清单树中
  563. if (peerNodes.length > billsNodes.length) {
  564. let updateData = getUpdateDataFromPeer(parentNode, peerNodes, billsNodes, selNodes);
  565. postData = postData.concat(updateData);
  566. }
  567. }
  568. }
  569. function _chkBillsUnitPrice(billsNodes) {
  570. if (billsUnitPriceFeature !== null) {
  571. for (const node of billsNodes) {
  572. if (node.updateType === 'create') {
  573. //只有创建的才设置初始化单价
  574. const _chkKeys = function(keys) {
  575. let rst = false;
  576. for (const subKey of keys) {
  577. let isPrjFeatureMatch = false;
  578. for (let prjF of projectObj.project.projectInfo.property.projectFeature) {
  579. if (prjF.key === subKey.key) {
  580. if (prjF.value === subKey.value) {
  581. isPrjFeatureMatch = true;
  582. // isKeyMatch = true;
  583. break;
  584. } else {
  585. //key相同而value不同,则无需再循环,直接退出
  586. break;
  587. }
  588. }
  589. }
  590. if (isPrjFeatureMatch) {
  591. rst = true;
  592. break;
  593. }
  594. }
  595. return rst;
  596. };
  597. let isMatch = true;
  598. // 1. 基本数量
  599. // 先判断此bills是否有配置
  600. let basicValue = 0;
  601. for (const bm of billsUnitPriceFeature.feature.basicMappings) {
  602. isMatch = true;
  603. if (bm.parentBasicKeys.length === billsUnitPriceFeature.feature.basicKeyOptions.length) {
  604. for (let kIdx = 0; kIdx < bm.parentBasicKeys.length; kIdx++) {
  605. if (bm.parentBasicKeys[kIdx] !== 'ALL' && node.updateData[billsUnitPriceFeature.feature.basicKeyOptions[kIdx].key] !== bm.parentBasicKeys[kIdx]) {
  606. isMatch = false;
  607. break;
  608. }
  609. }
  610. } else {
  611. isMatch = false;
  612. break;
  613. }
  614. if (isMatch) {
  615. //再根据相关项目属性指定基数
  616. for (const bms of bm.subs) {
  617. if (_chkKeys(bms.keys)) {
  618. basicValue = bms.basicValue;
  619. break;
  620. }
  621. }
  622. break;
  623. }
  624. }
  625. // 2. 相关系数(允许多个)
  626. let factors = [];
  627. for (const fm of billsUnitPriceFeature.feature.factorMappings) {
  628. isMatch = true;
  629. if (fm.basicFactorKeys.length === billsUnitPriceFeature.feature.basicKeyOptions.length) {
  630. for (let kIdx = 0; kIdx < fm.basicFactorKeys.length; kIdx++) {
  631. if (fm.basicFactorKeys[kIdx] !== 'ALL' && node.updateData[billsUnitPriceFeature.feature.basicKeyOptions[kIdx].key] !== fm.basicFactorKeys[kIdx]) {
  632. isMatch = false;
  633. break;
  634. }
  635. }
  636. } else {
  637. isMatch = false;
  638. break;
  639. }
  640. if (isMatch) {
  641. for (const fms of fm.subs) {
  642. if (_chkKeys(fms.keys)) {
  643. factors.push(fms.basicValue);
  644. }
  645. }
  646. // 允许多个,不break;
  647. }
  648. }
  649. //3. 装配(指标基价 即 默认的清单单价)
  650. let unitFeeVal = basicValue;
  651. for (const factor of factors) {
  652. unitFeeVal = unitFeeVal * parseFloat(factor);
  653. }
  654. // 暂时未设小数位数 scMathUtil.roundTo(unitFeeVal,2);
  655. node.updateData.calcFlag = 2; //当用户输入单价
  656. if (!node.updateData.hasOwnProperty('fees')) {
  657. node.updateData.fees = [];
  658. }
  659. node.updateData.fees.push({fieldName: 'common', unitFee: unitFeeVal, totalFee: 0, tenderUnitFee: unitFeeVal, tenderTotalFee: 0});
  660. }
  661. }
  662. }
  663. }
  664. //项目指引类型
  665. const itemType = {
  666. job: 0,
  667. ration: 1
  668. };
  669. const guideItem = {
  670. dom: $('#billsGuidance_items'),
  671. workBook: null,
  672. tree: null,
  673. controller: null,
  674. treeSetting: {
  675. treeCol: 1,
  676. emptyRows: 0,
  677. headRows: 1,
  678. headRowHeight: [40],
  679. defaultRowHeight: 21,
  680. cols: [
  681. {
  682. width: 35,
  683. readOnly: false,
  684. head: {
  685. titleNames: ["选择"],
  686. spanCols: [1],
  687. spanRows: [1],
  688. vAlign: [1],
  689. hAlign: [1],
  690. font: ["Arial"]
  691. },
  692. data: {
  693. field: "select",
  694. vAlign: 1,
  695. hAlign: 1,
  696. font: "Arial"
  697. }
  698. },
  699. {
  700. width: 420,
  701. readOnly: false,
  702. head: {
  703. titleNames: ["项目指引"],
  704. spanCols: [1],
  705. spanRows: [1],
  706. vAlign: [1],
  707. hAlign: [1],
  708. font: ["Arial"]
  709. },
  710. data: {
  711. field: "name",
  712. vAlign: 1,
  713. hAlign: 0,
  714. font: "Arial"
  715. }
  716. }
  717. ]
  718. },
  719. headers: [
  720. {name: '选择', dataCode: 'select', width: 35, vAlign: 'center', hAlign: 'center', formatter: '@'},
  721. {name: '项目指引', dataCode: 'name', width: 300, vAlign: 'center', hAlign: 'left', formatter: '@'},
  722. ],
  723. rowHeaderWidth:25,
  724. events: {
  725. EditStarting: function (sender, args) {
  726. if(!bills.tree || guideItem.headers[args.col]['dataCode'] === 'name'){
  727. args.cancel = true;
  728. }
  729. },
  730. ButtonClicked: function (sender, args) {
  731. if(args.sheet.isEditing()){
  732. args.sheet.endEdit(true);
  733. }
  734. refreshInsertRation();
  735. },
  736. CellDoubleClick: function (sender, args) {
  737. if(!bills.tree || !bills.tree.selected){
  738. return;
  739. }
  740. let node = bills.tree.selected.guidance.tree.selected;
  741. if(!node){
  742. return;
  743. }
  744. if(node.children.length === 0){
  745. if(guideItem.headers[args.col]['dataCode'] === 'name'){
  746. insertRations(getInsertRationData([args.row]));
  747. }
  748. }
  749. else {
  750. node.setExpanded(!node.expanded);
  751. renderSheetFunc(args.sheet, function () {
  752. let iCount = node.posterityCount(), i, child;
  753. for (i = 0; i < iCount; i++) {
  754. child = bills.tree.selected.guidance.tree.items[args.row + i + 1];
  755. args.sheet.setRowVisible(args.row + i + 1, child.visible, args.sheetArea);
  756. }
  757. args.sheet.invalidateLayout();
  758. });
  759. args.sheet.repaint();
  760. }
  761. }
  762. }
  763. };
  764. const elfItem = {
  765. dom: $('#billsGuidance_items'),
  766. workBook: null,
  767. tree: null,
  768. controller: null,
  769. treeSetting: {
  770. treeCol: 0,
  771. emptyRows: 0,
  772. headRows: 1,
  773. headRowHeight: [40],
  774. defaultRowHeight: 21,
  775. cols: [
  776. {
  777. width: 250,
  778. readOnly: true,
  779. head: {
  780. titleNames: ["施工工序"],
  781. spanCols: [1],
  782. spanRows: [1],
  783. vAlign: [1],
  784. hAlign: [1],
  785. font: ["Arial"]
  786. },
  787. data: {
  788. field: "name",
  789. vAlign: 1,
  790. hAlign: 0,
  791. font: "Arial"
  792. }
  793. },
  794. {
  795. width: 250,
  796. readOnly: false,
  797. head: {
  798. titleNames: ["选项"],
  799. spanCols: [1],
  800. spanRows: [1],
  801. vAlign: [1],
  802. hAlign: [1],
  803. font: ["Arial"]
  804. },
  805. data: {
  806. field: "options",
  807. vAlign: 1,
  808. hAlign: 0,
  809. font: "Arial"
  810. }
  811. }
  812. ]
  813. },
  814. headers: [
  815. {name: '施工工序', dataCode: 'name', width: 250, rateWidth: 0.5, vAlign: 'center', hAlign: 'center', formatter: '@'},
  816. {name: '选项', dataCode: 'options', width: 250, rateWidth: 0.5, vAlign: 'center', hAlign: 'left', formatter: '@'},
  817. ],
  818. rowHeaderWidth:25,
  819. events: {
  820. CellClick: function (sender, args) {
  821. if(elfItem.headers[args.col]['dataCode'] === 'options' && args.sheetArea === 3){
  822. if(!args.sheet.getCell(args.row, args.col).locked() && !args.sheet.isEditing()){
  823. args.sheet.startEdit();
  824. }
  825. }
  826. },
  827. ClipboardPasting: function (sender, info) {
  828. info.cancel = true;
  829. }
  830. }
  831. };
  832. const options = {
  833. workBook: {
  834. tabStripVisible: false,
  835. allowContextMenu: false,
  836. allowCopyPasteExcelStyle : false,
  837. allowExtendPasteRange: false,
  838. allowUserDragDrop : false,
  839. allowUserDragFill: false,
  840. scrollbarMaxAlign : true
  841. },
  842. sheet: {
  843. protectionOptions: {allowResizeRows: true, allowResizeColumns: true},
  844. clipBoardOptions: GC.Spread.Sheets.ClipboardPasteOptions.values
  845. }
  846. };
  847. //渲染时方法,停止渲染
  848. //@param {Object}sheet {Function}func @return {void}
  849. function renderSheetFunc(sheet, func){
  850. sheet.suspendEvent();
  851. sheet.suspendPaint();
  852. if(func){
  853. func();
  854. }
  855. sheet.resumeEvent();
  856. sheet.resumePaint();
  857. }
  858. //设置表选项
  859. //@param {Object}workBook {Object}opts @return {void}
  860. function setOptions (workBook, opts) {
  861. for(let opt in opts.workBook){
  862. workBook.options[opt] = opts.workBook[opt];
  863. }
  864. for(let opt in opts.sheet){
  865. workBook.getActiveSheet().options[opt] = opts.sheet[opt];
  866. }
  867. }
  868. //建表头
  869. //@param {Object}sheet {Array}headers @return {void}
  870. function buildHeader(sheet, headers) {
  871. let fuc = function () {
  872. sheet.setColumnCount(headers.length);
  873. sheet.setRowHeight(0, 30, GC.Spread.Sheets.SheetArea.colHeader);
  874. //sheet.setColumnWidth(0, sheet.getParent() === bills.workBook ? 15 : 25, GC.Spread.Sheets.SheetArea.rowHeader);
  875. if(sheet.getParent() === elfItem.workBook || sheet.getParent() === guideItem.workBook){
  876. sheet.setRowHeight(0, 20, GC.Spread.Sheets.SheetArea.colHeader);
  877. }
  878. for(let i = 0, len = headers.length; i < len; i++){
  879. sheet.setValue(0, i, headers[i].name, GC.Spread.Sheets.SheetArea.colHeader);
  880. sheet.setColumnWidth(i, headers[i].width, GC.Spread.Sheets.SheetArea.colHeader);
  881. if(headers[i].formatter){
  882. sheet.setFormatter(-1, i, headers[i].formatter);
  883. }
  884. sheet.getRange(-1, i, -1, 1).hAlign(GC.Spread.Sheets.HorizontalAlign[headers[i]['hAlign']]);
  885. sheet.getRange(-1, i, -1, 1).vAlign(GC.Spread.Sheets.VerticalAlign[headers[i]['vAlign']]);
  886. }
  887. };
  888. renderSheetFunc(sheet, fuc);
  889. }
  890. //表监听事件
  891. //@param {Object}workBook @return {void}
  892. function bindEvent(workBook, events) {
  893. if(Object.keys(events).length === 0){
  894. return;
  895. }
  896. const Events = GC.Spread.Sheets.Events;
  897. for(let event in events){
  898. workBook.bind(Events[event], events[event]);
  899. }
  900. }
  901. //根据宽度比例设置列宽
  902. //@param {Object}workBook {Number}workBookWidth {Array}headers @return {void}
  903. function setColumnWidthByRate(workBook, workBookWidth, headers) {
  904. if(workBook){
  905. workBookWidth -= 48;
  906. const sheet = workBook.getActiveSheet();
  907. sheet.suspendEvent();
  908. sheet.suspendPaint();
  909. for(let col = 0; col < headers.length; col++){
  910. if(headers[col]['rateWidth'] !== undefined && headers[col]['rateWidth'] !== null && headers[col]['rateWidth'] !== ''){
  911. let width = workBookWidth * headers[col]['rateWidth'];
  912. if(headers[col]['dataCode'] === 'options'){
  913. width = width;
  914. }
  915. sheet.setColumnWidth(col, width, GC.Spread.Sheets.SheetArea.colHeader)
  916. }
  917. else {
  918. if(headers[col]['headerWidth'] !== undefined && headers[col]['headerWidth'] !== null && headers[col]['headerWidth'] !== ''){
  919. sheet.setColumnWidth(col, headers[col]['headerWidth'], GC.Spread.Sheets.SheetArea.colHeader)
  920. }
  921. }
  922. }
  923. sheet.resumeEvent();
  924. sheet.resumePaint();
  925. }
  926. }
  927. //建表
  928. //@param {Object}module @return {void}
  929. function buildSheet(module) {
  930. if(!module.workBook){
  931. module.workBook = new GC.Spread.Sheets.Workbook(module.dom[0], {sheetCount: 1});
  932. sheetCommonObj.spreadDefaultStyle(module.workBook);
  933. let sheet = module.workBook.getActiveSheet();
  934. if(module === bills){
  935. //默认初始可控制焦点在清单表中
  936. sheet.options.rowHeaderVisible = false;
  937. module.workBook.focus();
  938. sheet.options.isProtected = true;
  939. sheet.name('stdBillsGuidance_bills');
  940. //设置悬浮提示
  941. TREE_SHEET_HELPER.initSetting(bills.dom[0], bills.treeSetting);
  942. }
  943. if(module === guideItem){
  944. sheet.options.isProtected = true;
  945. sheet.getRange(-1, 0, -1, 1).locked(false);
  946. sheet.getRange(-1, 1, -1, 1).locked(true);
  947. }
  948. if(module === elfItem){
  949. sheet.options.isProtected = true;
  950. sheet.getRange(-1, 0, -1, 1).locked(true);
  951. sheet.getRange(-1, 1, -1, 1).locked(false);
  952. }
  953. if(module.rowHeaderWidth) {
  954. sheet.setColumnWidth(0, module.rowHeaderWidth, GC.Spread.Sheets.SheetArea.rowHeader);
  955. }
  956. setOptions(module.workBook, options);
  957. buildHeader(module.workBook.getActiveSheet(), module.headers);
  958. if(module === elfItem){
  959. setColumnWidthByRate(elfItem.workBook, $('#zy').width(), elfItem.headers)
  960. }
  961. bindEvent(module.workBook, module.events);
  962. }
  963. }
  964. //清空表数据
  965. //@param {Object}sheet {Array}headers {Number}rowCount @return {void}
  966. function cleanData(sheet, headers, rowCount){
  967. renderSheetFunc(sheet, function () {
  968. sheet.clear(-1, 0, -1, headers.length, GC.Spread.Sheets.SheetArea.viewport, GC.Spread.Sheets.StorageType.data);
  969. if (rowCount > 0) {
  970. sheet.setRowCount(rowCount);
  971. }
  972. });
  973. }
  974. //初始化各工作表
  975. //@param {Array}modules @return {void}
  976. function initWorkBooks(modules){
  977. for(let module of modules){
  978. buildSheet(module);
  979. }
  980. }
  981. //点击清单名称后面的问号,弹出补注窗口并设置当前节点(或xxx父节点)的补注
  982. //@param {Number}row(当前焦点行) @return {void}
  983. function initRechargeModal(row) {
  984. let node = bills.tree.items[row];
  985. while (node && !node.data.recharge){
  986. node = node.parent;
  987. }
  988. let recharge = node && node.data.recharge ? node.data.recharge : '无内容';
  989. node = bills.tree.items[row];
  990. while (node && !node.data.ruleText){
  991. node = node.parent;
  992. }
  993. let ruleText = node && node.data.ruleText ? node.data.ruleText : '无内容';
  994. $('#questionTab1').text('补注');
  995. $('#questionContent1').html(recharge);
  996. $('#questionContent2').html(ruleText);
  997. $('#questionModal').modal('show');
  998. }
  999. //节点链上含有补注或工程量计算规则数据
  1000. //@param {Number}row(行当前行) @return {Boolean}
  1001. function hasRechargeRuleText(row) {
  1002. let node = bills.tree.items[row];
  1003. if (!node) {
  1004. return false;
  1005. }
  1006. while (node) {
  1007. if (node.data.recharge || node.data.ruleText) {
  1008. return true;
  1009. }
  1010. node = node.parent;
  1011. }
  1012. return false;
  1013. }
  1014. //初始化并输出树
  1015. //@param {Object}module {Object}sheet {Object}treeSetting {Array}datas
  1016. function initTree(module, sheet, treeSetting, datas){
  1017. module.tree = idTree.createNew({id: 'ID', pid: 'ParentID', nid: 'NextSiblingID', rootId: -1, autoUpdate: true});
  1018. module.controller = TREE_SHEET_CONTROLLER.createNew(module.tree, sheet, treeSetting, false);
  1019. module.tree.loadDatas(datas);
  1020. if(module === bills){
  1021. initExpandStat();
  1022. }
  1023. module.controller.showTreeData();
  1024. if (module === bills) {
  1025. module.workBook.getSheet(0).options.rowHeaderVisible = true;
  1026. setBillsHint(bills.tree.items, stdBillsJobData, stdBillsFeatureData);
  1027. renderSheetFunc(sheet, function () {
  1028. const checkBoxType = new GC.Spread.Sheets.CellTypes.CheckBox();
  1029. for (let i = 0; i < bills.tree.items.length; i++) {
  1030. sheet.setCellType(i, 0, checkBoxType);
  1031. sheet.setCellType(i, 2, TREE_SHEET_HELPER.getQuestionCellType(initRechargeModal, hasRechargeRuleText));
  1032. }
  1033. });
  1034. }
  1035. }
  1036. //项目指引表焦点控制
  1037. //@param {Number}row @return {void}
  1038. function guideItemInitSel(row){
  1039. let billsNode = bills.tree.selected;
  1040. let node = null;
  1041. if(billsNode && billsNode.guidance.tree){
  1042. node = billsNode.guidance.tree.items[row];
  1043. if(node){
  1044. billsNode.guidance.tree.selected = node;
  1045. }
  1046. }
  1047. }
  1048. //清单精灵表焦点控制
  1049. //@param {Number}row @return {void}
  1050. function elfItemInitSel(row){
  1051. let billsNode = bills.tree.selected;
  1052. let node = null;
  1053. if(billsNode && billsNode.elf.tree){
  1054. node = billsNode.elf.tree.items[row];
  1055. if(node){
  1056. billsNode.elf.tree.selected = node;
  1057. }
  1058. }
  1059. }
  1060. //根据项目指引的类型设置单元格类型,定额类型的项目指引为复选框
  1061. //@param {Array}nodes @return {void}
  1062. function setItemCellType(nodes){
  1063. //设置单元格类型
  1064. const base = new GC.Spread.Sheets.CellTypes.Base();
  1065. const checkBox = new GC.Spread.Sheets.CellTypes.CheckBox();
  1066. const sheet = guideItem.workBook.getActiveSheet();
  1067. renderSheetFunc(sheet, function(){
  1068. for(let node of nodes){
  1069. sheet.setCellType(node.serialNo(), 0, node.data.type === itemType.ration ? checkBox : base);
  1070. }
  1071. });
  1072. }
  1073. //初始化清单的工作内容和项目特征
  1074. //@param {Number}billsLibId {Function}callback @return {void}
  1075. function initJobAndCharacter(billsLibId, callback){
  1076. CommonAjax.post('/stdBillsEditor/getJobContent', {userId: userID, billsLibId: billsLibId}, function (datas) {
  1077. stdBillsJobData = datas;
  1078. CommonAjax.post('/stdBillsEditor/getItemCharacter', {userId: userID, billsLibId: billsLibId}, function (datas) {
  1079. stdBillsFeatureData = datas;
  1080. if(callback){
  1081. callback();
  1082. }
  1083. });
  1084. });
  1085. }
  1086. //初始化清单展开收起状态
  1087. //@return {void}
  1088. function initExpandStat(){
  1089. //读取展开收起状态
  1090. let currentExpState = sessionStorage.getItem('stdBillsGuidanceExpState');
  1091. if(currentExpState){
  1092. bills.tree.setExpandedByState(bills.tree.items, currentExpState);
  1093. }
  1094. //非叶子节点默认收起
  1095. else{
  1096. bills.tree.setRootExpanded(bills.tree.roots, false);
  1097. // 默认展开第一个节点到第二层
  1098. bills.tree.roots[0].setExpanded(true);
  1099. }
  1100. }
  1101. //设置tag以悬浮提示
  1102. function setTagForHint(nodes){
  1103. let sheet = bills.workBook.getActiveSheet();
  1104. renderSheetFunc(sheet, function () {
  1105. for(let node of nodes){
  1106. sheet.setTag(node.serialNo(), 2, node.data.ruleText ? node.data.ruleText : '');
  1107. }
  1108. });
  1109. }
  1110. //根据编码定位至清单精灵库中
  1111. //@param {String}code @return {void}
  1112. function locateAtBills(code) {
  1113. let nineCode = code.substring(0, 9);
  1114. let items = bills.tree.items;
  1115. let locateBills = _.find(items, function(item){
  1116. return item.data.code === nineCode;
  1117. });
  1118. if(locateBills){
  1119. expandSearchNodes([locateBills]);
  1120. sessionStorage.setItem('stdBillsGuidanceExpState', bills.tree.getExpState(bills.tree.items));
  1121. }
  1122. let sheet = bills.workBook.getActiveSheet();
  1123. let locateRow = locateBills ? locateBills.serialNo() : 0;
  1124. sheet.setActiveCell(locateRow, 0);
  1125. sheet.showRow(locateRow, GC.Spread.Sheets.VerticalPosition.center);
  1126. }
  1127. //清单设置悬浮提示信息
  1128. //@param {Array}billsNodes(清单节点) {Array}jobs(总的工作内容数据) {Array}items(总的项目特征数据)
  1129. function setBillsHint(billsNodes, jobs, items) {
  1130. let jobsMapping = {},
  1131. itemsMapping = {};
  1132. for(let job of jobs){
  1133. jobsMapping[job.id] = job;
  1134. }
  1135. for(let item of items){
  1136. itemsMapping[item.id] = item;
  1137. }
  1138. let tagInfo = [];
  1139. for(let billsNode of billsNodes){
  1140. let hintArr = [];
  1141. let billsItems = billsNode.data.items;
  1142. if(billsItems.length > 0){
  1143. //项目特征
  1144. hintArr.push('项目特征:');
  1145. }
  1146. let itemCount = 1,
  1147. jobCount = 1;
  1148. for(let billsItem of billsItems){
  1149. let itemData = itemsMapping[billsItem.id];
  1150. if(itemData){
  1151. //特征值
  1152. let eigens = [];
  1153. for(let eigen of itemData.itemValue){
  1154. eigens.push(eigen.value);
  1155. }
  1156. eigens = eigens.join(';');
  1157. hintArr.push(`${itemCount}.${itemData.content}${eigens === '' ? '' : ': ' + eigens}`);
  1158. itemCount ++;
  1159. }
  1160. }
  1161. //工作内容
  1162. let billsJobs = billsNode.data.jobs;
  1163. if(billsJobs.length > 0){
  1164. hintArr.push('工作内容:');
  1165. }
  1166. for(let billsJob of billsJobs){
  1167. let jobData = jobsMapping[billsJob.id];
  1168. if(jobData){
  1169. hintArr.push(`${jobCount}.${jobData.content}`);
  1170. jobCount ++;
  1171. }
  1172. }
  1173. /*if(billsNode.data.ruleText && billsNode.data.ruleText !== ''){
  1174. hintArr.push('工程量计算规则:');
  1175. hintArr.push(billsNode.data.ruleText);
  1176. }
  1177. if(billsNode.data.recharge && billsNode.data.recharge !== ''){
  1178. hintArr.push('补注:');
  1179. hintArr.push(billsNode.data.recharge);
  1180. }*/
  1181. if(hintArr.length > 0){
  1182. tagInfo.push({row: billsNode.serialNo(), value: hintArr.join('\n')});
  1183. }
  1184. }
  1185. let sheet = bills.workBook.getActiveSheet();
  1186. renderSheetFunc(sheet, function () {
  1187. for(let tagI of tagInfo){
  1188. sheet.setTag(tagI.row, 0, tagI.value);
  1189. }
  1190. });
  1191. }
  1192. //初始选择标准清单
  1193. //@param {Number}libID @return {void}
  1194. function libInitSel(libID){
  1195. //获取清单
  1196. $.bootstrapLoading.start();
  1197. CommonAjax.post('/billsGuidance/api/getLibWithBills', {libID: libID, isGuidanceLib: false}, function(rstData){
  1198. if(guideItem.workBook){
  1199. guideItem.workBook.destroy();
  1200. guideItem.workBook = null;
  1201. }
  1202. if(elfItem.workBook){
  1203. elfItem.workBook.destroy();
  1204. elfItem.workBook = null;
  1205. }
  1206. initViews();
  1207. let callback = function () {
  1208. initTree(bills, bills.workBook.getActiveSheet(), bills.treeSetting, rstData.bills);
  1209. if(doAfterLoadGuidance){
  1210. doAfterLoadGuidance();
  1211. }
  1212. $.bootstrapLoading.end();
  1213. };
  1214. //获取清单库中的工作内容和项目特征
  1215. initJobAndCharacter(libID, callback);
  1216. }, function () {
  1217. $.bootstrapLoading.end();
  1218. });
  1219. }
  1220. //初始化清单指引库
  1221. //@param {Array}libDats @return {void}
  1222. function initLibs(libDatas){
  1223. libSel.empty();
  1224. if(!libDatas){
  1225. return;
  1226. }
  1227. let selectedLib = sessionStorage.getItem('stdBillsGuidance');
  1228. for(let libData of libDatas){
  1229. let opt = $('<option>').val(libData.id).text(libData.name);
  1230. if(selectedLib && libData.id == selectedLib){
  1231. opt.attr('selected', 'selected');
  1232. }
  1233. libSel.append(opt);
  1234. }
  1235. //初始默认选择
  1236. libInitSel(libSel.select().val());
  1237. }
  1238. //初始化视图
  1239. //@param {void} @return {void}
  1240. function initViews(){
  1241. //赋初始高度
  1242. if($('#billsGuidance_bills').height() === 0 || $('#billsGuidance_items').height() === 0){
  1243. let height = $(window).height()-$(".header").height()-$(".toolsbar").height()-$(".tools-bar-height-z").height();
  1244. $('#billsGuidance_bills').height(height / 2);
  1245. $('#billsGuidance_items').height(height / 2);
  1246. }
  1247. let modules = [bills];
  1248. modules.push(elfItem);
  1249. initWorkBooks(modules);
  1250. }
  1251. //展开至搜索出来点的节点
  1252. //@param {Array}nodes @return {void}
  1253. function expandSearchNodes(nodes){
  1254. let that = this;
  1255. let billsSheet = bills.workBook.getActiveSheet();
  1256. renderSheetFunc(billsSheet, function () {
  1257. function expParentNode(node){
  1258. if(node.parent){
  1259. if (!node.parent.expanded) {
  1260. node.parent.setExpanded(true);
  1261. }
  1262. expParentNode(node.parent);
  1263. }
  1264. }
  1265. for(let node of nodes){
  1266. expParentNode(node);
  1267. }
  1268. TREE_SHEET_HELPER.refreshTreeNodeData(bills.treeSetting, billsSheet, bills.tree.roots, true);
  1269. TREE_SHEET_HELPER.refreshNodesVisible(bills.tree.roots, billsSheet, true);
  1270. });
  1271. }
  1272. //各按钮监听事件
  1273. //@return {void}
  1274. function bindBtn(){
  1275. //打开清单指引库
  1276. $('#stdBillsGuidanceTab').click(function () {
  1277. if(libSel.children().length === 0 && !$(this).hasClass('disabled')){
  1278. initLibs(projectObj.project.projectInfo.engineeringInfo.bill_lib);
  1279. }
  1280. });
  1281. //更改清单指引库
  1282. $('#stdBillsGuidanceLibSelect').change(function () {
  1283. //关闭搜索窗口
  1284. $('#billsGuidanceSearchResult').hide();
  1285. billsLibObj.clearHighLight(bills.workBook);
  1286. libInitSel($(this).select().val());
  1287. //记住选项
  1288. sessionStorage.setItem('stdBillsGuidance', $(this).select().val());
  1289. //清除展开收起状态sessionStorage
  1290. sessionStorage.removeItem('stdBillsGuidanceExpState');
  1291. });
  1292. //搜索
  1293. $('#stdBillsGuidanceSearch>div>button').click(function () {
  1294. if(!bills.tree){
  1295. return;
  1296. }
  1297. let billsSheet = bills.workBook.getActiveSheet();
  1298. billsLibObj.clearHighLight(bills.workBook);
  1299. let keyword = $('#stdBillsGuidanceSearch>input').val();
  1300. if (!keyword || keyword === '') {
  1301. $('#billsGuidanceSearchResult').hide();
  1302. return;
  1303. }
  1304. let result = bills.tree.items.filter(function (item) {
  1305. let codeIs = item.data.code ? item.data.code.indexOf(keyword) !== -1 : false;
  1306. let nameIs = item.data.name ? item.data.name.indexOf(keyword) !== -1 : false;
  1307. return codeIs || nameIs;
  1308. });
  1309. result.sort(function (x, y) {
  1310. return x.serialNo() - y.serialNo();
  1311. });
  1312. if (result.length !== 0) {
  1313. //展开搜索出来的节点
  1314. expandSearchNodes(result);
  1315. //设置记住展开
  1316. sessionStorage.setItem('stdBillsGuidanceExpState', bills.tree.getExpState(bills.tree.items));
  1317. let sel = billsSheet.getSelections();
  1318. renderSheetFunc(billsSheet, function () {
  1319. bills.controller.setTreeSelected(result[0]);
  1320. billsSheet.setSelection(result[0].serialNo(), sel[0].col, 1, 1);
  1321. for (let node of result) {
  1322. billsSheet.getRange(node.serialNo(), -1, 1, -1).backColor('lemonChiffon');
  1323. }
  1324. });
  1325. //搜索初始定位
  1326. billsSheet.showRow(result[0].serialNo(), GC.Spread.Sheets.VerticalPosition.center);
  1327. //查找下一条
  1328. $('#nextBillsGuidance').show();
  1329. $('#nextBillsGuidance').unbind('click');
  1330. $('#nextBillsGuidance').bind('click', function () {
  1331. let cur = bills.tree.selected, resultIndex = result.indexOf(cur), sel = billsSheet.getSelections();
  1332. if (resultIndex === result.length - 1) {
  1333. bills.controller.setTreeSelected(result[0]);
  1334. billsSheet.setSelection(result[0].serialNo(), sel[0].col, 1, 1);
  1335. billsSheet.showRow(result[0].serialNo(), GC.Spread.Sheets.VerticalPosition.center);
  1336. } else {
  1337. bills.controller.setTreeSelected(result[resultIndex + 1]);
  1338. billsSheet.setSelection(result[resultIndex + 1].serialNo(), sel[0].col, 1, 1);
  1339. billsSheet.showRow(result[resultIndex + 1].serialNo(), GC.Spread.Sheets.VerticalPosition.center);
  1340. }
  1341. });
  1342. //查找上一条
  1343. $('#preBillsGuidance').show();
  1344. $('#preBillsGuidance').unbind('click');
  1345. $('#preBillsGuidance').bind('click', function () {
  1346. let cur = bills.tree.selected, resultIndex = result.indexOf(cur), sel = billsSheet.getSelections();
  1347. if (resultIndex === 0) {
  1348. bills.controller.setTreeSelected(result[result.length - 1]);
  1349. billsSheet.setSelection(result[result.length - 1].serialNo(), sel[0].col, 1, 1);
  1350. billsSheet.showRow(result[result.length - 1].serialNo(), GC.Spread.Sheets.VerticalPosition.center);
  1351. } else {
  1352. bills.controller.setTreeSelected(result[resultIndex - 1]);
  1353. billsSheet.setSelection(result[resultIndex - 1].serialNo(), sel[0].col, 1, 1);
  1354. billsSheet.showRow(result[resultIndex - 1].serialNo(), GC.Spread.Sheets.VerticalPosition.center);
  1355. }
  1356. });
  1357. } else {
  1358. billsLibObj.clearHighLight(bills.workBook);
  1359. $('#nextBillsGuidance').hide();
  1360. $('#preBillsGuidance').hide();
  1361. }
  1362. $('#billsGuidanceSearchResultCount').text('搜索结果:' + result.length);
  1363. $('#billsGuidanceSearchResult').show();
  1364. autoFlashHeight();
  1365. refreshWorkBook();
  1366. });
  1367. //搜索框回车
  1368. $('#stdBillsGuidanceSearch>input').bind('keypress', function (event) {
  1369. if(event.keyCode === 13){
  1370. $(this).blur();
  1371. $('#stdBillsGuidanceSearch>div>button').click();
  1372. }
  1373. });
  1374. // 关闭搜索结果
  1375. $('#closeSearchBillsGuidance').click(function () {
  1376. $('#billsGuidanceSearchResult').hide();
  1377. billsLibObj.clearHighLight(bills.workBook);
  1378. autoFlashHeight();
  1379. refreshWorkBook();
  1380. });
  1381. // 添加选用
  1382. $('#add-select-bills').click(function() {
  1383. if (isInserting) {
  1384. return;
  1385. }
  1386. const sheet = bills.controller.sheet;
  1387. const lowestNodes = [];
  1388. for (let i = 0; i < bills.tree.items.length; i++) {
  1389. const checked = sheet.getValue(i, 0);
  1390. const node = bills.tree.items[i];
  1391. if (checked && !node.children.length) {
  1392. lowestNodes.push(node);
  1393. }
  1394. }
  1395. if (lowestNodes.length) {
  1396. $.bootstrapLoading.start();
  1397. insertBills(lowestNodes);
  1398. }
  1399. });
  1400. }
  1401. //刷新表
  1402. //@return {void}
  1403. function refreshWorkBook(){
  1404. if(bills.workBook){
  1405. bills.workBook.refresh();
  1406. }
  1407. if(guideItem.workBook){
  1408. guideItem.workBook.refresh();
  1409. }
  1410. if(elfItem.workBook){
  1411. elfItem.workBook.refresh();
  1412. }
  1413. }
  1414. return {initViews, initLibs, bindBtn, refreshWorkBook, setColumnWidthByRate, locateAtBills, bills, elfItem, overwrite};
  1415. })();
  1416. $(document).ready(function(){
  1417. billsGuidance.bindBtn();
  1418. });