budgetSummarySheet.js 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831
  1. /* 建设其他费表格相关 */
  2. const budgetSummaryObj = (() => {
  3. const { isEmptyVal, isDef, isNumber } = window.commonUtil;
  4. const { fixedFlag, BudgetArea, BudgetType } = window.commonConstants;
  5. let budgetSummaryTreeSetting;
  6. let curBudgetType = BudgetType.BUILDING;
  7. // 原始数据
  8. let rawData = [];
  9. // ID与原始数据映射表(主要是恢复用)
  10. const orgMap = {};
  11. // 建设其他费表格对象
  12. let spread = null;
  13. // 建设其他费树
  14. let tree = null;
  15. // 计算相关
  16. const calcSetting = {
  17. costGrowthRate: 0,
  18. growthPeriod: 0,
  19. };
  20. // 单位设置下拉框
  21. const setUnitCombo = (sheet, data) => {
  22. const unitCol = budgetSummaryTreeSetting.cols.findIndex(item => item.data.field === 'unit');
  23. if (unitCol >= 0) {
  24. TREE_SHEET_HELPER.massOperationSheet(sheet, () => {
  25. const comboBox = sheetCommonObj.getDynamicCombo();
  26. comboBox
  27. .itemHeight(10)
  28. .items(['m', 'm2', 'm3', 'km', 't', 'kg', '台班', '工日', '昼夜', '元', '项', '处', '个', '件',
  29. '根', '组', '系统', '台', '套', '株', '丛', '缸', '支', '只', '块', '座', '对', '份', '樘', '攒', '榀'])
  30. .editable(true);
  31. data.forEach((item, index) => {
  32. sheet.getCell(index, unitCol).cellType(comboBox);
  33. })
  34. });
  35. }
  36. }
  37. const getFieldByCol = (col) => {
  38. const item = budgetSummaryTreeSetting.cols[col];
  39. return item && item.data && item.data.field || null;
  40. }
  41. // 单元格值验证器
  42. const validator = {
  43. text() {
  44. return true
  45. },
  46. number(val) {
  47. return !isDef(val) || isNumber(val);
  48. },
  49. };
  50. const getValidator = (col) => {
  51. const item = budgetSummaryTreeSetting.cols[col];
  52. if (!item) {
  53. return 'text';
  54. }
  55. return validator[item.data.type || 'text'];
  56. }
  57. // 单元格文本转换处理
  58. const textFactory = {
  59. calcBase(node) {
  60. if (node.data.calcBase && node.data.calcBase !== "") {
  61. return cbParser.toFExpr(node.data.calcBase);
  62. }
  63. return '';
  64. },
  65. 'feesIndex.common.unitFee': (node) => {
  66. return _.get(node, 'data.feesIndex.common.unitFee', '') || '';
  67. },
  68. 'feesIndex.common.totalFee': (node) => {
  69. return _.get(node, 'data.feesIndex.common.totalFee', '') || '';
  70. },
  71. 'feesIndex.building.totalFee': (node) => {
  72. return _.get(node, 'data.feesIndex.building.totalFee', '') || '';
  73. },
  74. 'feesIndex.installation.totalFee': (node) => {
  75. return _.get(node, 'data.feesIndex.installation.totalFee', '') || '';
  76. },
  77. 'feesIndex.equipment.totalFee': (node) => {
  78. return _.get(node, 'data.feesIndex.equipment.totalFee', '') || '';
  79. },
  80. 'feesIndex.other.totalFee': (node) => {
  81. return _.get(node, 'data.feesIndex.other.totalFee', '') || '';
  82. },
  83. };
  84. /* 表格事件相关 */
  85. // 根据节点数据刷新表格数据
  86. const refreshData = (sheet, changedCells) => {
  87. if (!tree) {
  88. return;
  89. }
  90. TREE_SHEET_HELPER.massOperationSheet(sheet, () => {
  91. changedCells.forEach(({ row, col }) => {
  92. const node = tree.items[row];
  93. const field = getFieldByCol(col);
  94. if (!field || !node) {
  95. return;
  96. }
  97. const textFunc = textFactory[field];
  98. const val = textFunc ? textFunc(node) : node.data[field] || '';
  99. sheet.setValue(row, col, val);
  100. });
  101. });
  102. }
  103. // 刷新整个表格
  104. const refreshAll = (sheet) => {
  105. const changedCells = [];
  106. const colCount = budgetSummaryTreeSetting.cols.length;
  107. for (let row = 0; row < tree.items.length; row++) {
  108. for (let col = 0; col < colCount; col++) {
  109. changedCells.push({ row, col })
  110. }
  111. }
  112. refreshData(sheet, changedCells);
  113. }
  114. // 更新数据
  115. const bulkOperation = async (bulkData) => {
  116. if (bulkData.length) {
  117. await ajaxPost('/bills/bulkOperation', { bulkData });
  118. }
  119. };
  120. // 编辑相关
  121. const edit = async (sheet, changedCells, calcNodes = null) => {
  122. if (!changedCells.length && !calcNodes) {
  123. return;
  124. }
  125. if (changedCells.length) {
  126. // 单元格值验证
  127. const isValid = changedCells.every(({ row, col }) => {
  128. const val = sheet.getValue(row, col);
  129. return getValidator(col)(val);
  130. });
  131. // 验证不通过,恢复
  132. if (!isValid) {
  133. refreshData(sheet, changedCells);
  134. }
  135. }
  136. let needCalc = false;
  137. const nodes = [];
  138. try {
  139. $.bootstrapLoading.start();
  140. const IDMap = {};
  141. const bulkData = [];
  142. changedCells.forEach(({ row, col }) => {
  143. const node = tree.items[row];
  144. if (!node) {
  145. return;
  146. }
  147. if (!nodes.find(n => n.data.ID !== node.data.ID)) {
  148. nodes.push(node);
  149. }
  150. const field = getFieldByCol(col);
  151. const value = sheet.getValue(row, col) || '';
  152. const data = (IDMap[node.data.ID] || (IDMap[node.data.ID] = {}));
  153. if (['feesIndex.common.unitFee'].includes(field)) {
  154. const fees = node.data.fees || [];
  155. const feeItem = fees.find(item => item.fieldName === 'common');
  156. if (feeItem) {
  157. feeItem.unitFee = value;
  158. } else {
  159. fees.push({ fieldName: 'common', totalFee: 0, unitFee: +value });
  160. }
  161. data[field] = fees;
  162. node.data[field] = fees;
  163. node.data.feesIndex = getFeeIndex(node.data.fees);
  164. } else {
  165. data[field] = value;
  166. node.data[field] = value;
  167. }
  168. if (field === 'calcBase') {
  169. node.data.userCalcBase = value;
  170. projectObj.project.calcBase.calculate(node, null, false);
  171. if (!projectObj.project.calcBase.success) {
  172. throw projectObj.project.calcBase.errMsg;
  173. } else if (isEmptyVal(value)) {
  174. // 删除清单基数,单价要清空
  175. calcTools.setFieldValue(node, 'feesIndex.common.unitFee', 0);
  176. }
  177. data.calcBase = node.data.calcBase;
  178. data.calcBaseValue = node.data.calcBaseValue;
  179. data.tenderCalcBaseValue = node.data.tenderCalcBaseValue;
  180. }
  181. if (['quantity', 'feesIndex.common.unitFee', 'calcBase', 'feeRate'].includes(field)) {
  182. needCalc = true;
  183. }
  184. });
  185. calcNodes = calcNodes ? calcNodes : nodes;
  186. // 重算节点
  187. const dataArr = [];
  188. if ((needCalc && nodes.length) || (calcNodes && calcNodes.length)) {
  189. const changedNodes = projectObj.project.calcProgram.calcNodes(calcNodes, false, tree);
  190. for (const node of changedNodes) {
  191. nodes.push(node);
  192. if (node.changed) {
  193. const data = calcTools.cutNodeForSave(node);
  194. dataArr.push(data);
  195. }
  196. }
  197. }
  198. dataArr.forEach(item => {
  199. delete item.projectID;
  200. const data = IDMap[item.ID];
  201. if (data) {
  202. Object.assign(data, item);
  203. } else {
  204. IDMap[item.ID] = item;
  205. }
  206. });
  207. // 保存节点
  208. Object
  209. .entries(IDMap)
  210. .forEach(([ID, data]) => {
  211. const node = tree.findNode(ID);
  212. if (!node) {
  213. return;
  214. }
  215. // 处理其他费用
  216. if (node.isBelongToFlags([fixedFlag.CONSTRUCTION_OTHER_FEE])) {
  217. const fees = data.fees || [];
  218. const commonFeeItem = fees.find(item => item.fieldName === 'common');
  219. const otherFeeItem = fees.find(item => item.fieldName === 'other');
  220. if (otherFeeItem) {
  221. otherFeeItem.totalFee = commonFeeItem && commonFeeItem.totalFee || 0;
  222. } else {
  223. fees.push({ fieldName: 'other', totalFee: commonFeeItem && commonFeeItem.totalFee || 0 });
  224. }
  225. }
  226. const actualID = node.data.area === BudgetArea.CONSTRUCTION_FEE ? node.data.orgProjectID : ID;
  227. const updateType = node.data.area === BudgetArea.CONSTRUCTION_FEE ? 'updateProject' : 'update';
  228. bulkData.push({ type: updateType, data: { ...data, ID: actualID } });
  229. });
  230. await bulkOperation(bulkData);
  231. Object
  232. .entries(IDMap)
  233. .forEach(([ID, data]) => {
  234. const node = tree.findNode(ID);
  235. if (node) {
  236. Object.assign(node.data, data);
  237. node.data.feesIndex = getFeeIndex(node.data.fees);
  238. orgMap[ID] = _.cloneDeep(node.data);
  239. }
  240. });
  241. refreshAll(sheet);
  242. } catch (err) {
  243. console.log(err);
  244. nodes.forEach(node => {
  245. const orgItem = orgMap[node.data.ID];
  246. if (orgItem) {
  247. node.data = _.cloneDeep(orgItem);
  248. // Object.assign(node.data, orgItem);
  249. }
  250. });
  251. refreshData(sheet, changedCells);
  252. alert(err);
  253. } finally {
  254. $.bootstrapLoading.end();
  255. }
  256. }
  257. // 是否是属于工程费用区域的节点
  258. const isConstructionFeeArea = (node) => {
  259. return node && node.data && node.data.area === BudgetArea.CONSTRUCTION_FEE;
  260. }
  261. // 工具栏可操作性
  262. let upLevelDisabled = false;
  263. let downLevelDisabled = false;
  264. let upMoveDisabled = false;
  265. let downMoveDisabled = false;
  266. const refreshToolsBar = (node) => {
  267. upLevelDisabled = !node || !node.canUpLevel() || isConstructionFeeArea(node) || (node.nextSibling && node.data.calcBase);
  268. downLevelDisabled = !node || !node.canDownLevel() || isConstructionFeeArea(node) || isConstructionFeeArea(node.preSibling) || (node.preSibling && node.preSibling.data.calcBase);
  269. upMoveDisabled = !node || !node.canUpMove() || isConstructionFeeArea(node) || isConstructionFeeArea(node.preSibling);
  270. downMoveDisabled = !node || !node.canDownMove() || isConstructionFeeArea(node) || isConstructionFeeArea(node.nextSibling);
  271. if (upLevelDisabled) {
  272. $('#budget-upLevel').addClass('disabled');
  273. } else {
  274. $('#budget-upLevel').removeClass('disabled');
  275. }
  276. if (downLevelDisabled) {
  277. $('#budget-downLevel').addClass('disabled');
  278. } else {
  279. $('#budget-downLevel').removeClass('disabled');
  280. }
  281. if (upMoveDisabled) {
  282. $('#budget-upMove').addClass('disabled');
  283. } else {
  284. $('#budget-upMove').removeClass('disabled');
  285. }
  286. if (downMoveDisabled) {
  287. $('#budget-downMove').addClass('disabled');
  288. } else {
  289. $('#budget-downMove').removeClass('disabled');
  290. }
  291. };
  292. // 表格选中相关
  293. const selectCell = (row, col, setCell = false) => {
  294. const node = tree.items[row];
  295. refreshToolsBar(node);
  296. tree.selected = node;
  297. console.log(node);
  298. const sheet = spread.getSheet(0);
  299. if (setCell) {
  300. sheet.setActiveCell(row, col);
  301. }
  302. //设置选中行底色和恢复前选中行底色
  303. const refreshNodes = [node];
  304. if (!tree.preSelected) {
  305. refreshNodes.push(tree.items[0]);
  306. } else {
  307. refreshNodes.push(tree.preSelected);
  308. }
  309. tree.preSelected = node;
  310. projectObj.setNodesStyle(sheet, refreshNodes, tree);
  311. };
  312. // 事件列表
  313. const events = {
  314. EnterCell(sender, args) {
  315. args.sheet.repaint();
  316. },
  317. SelectionChanged(sender, args) {
  318. const newSel = args.newSelections[0] ? { row: args.newSelections[0].row, col: args.newSelections[0].col } : { row: 0, col: 0 };
  319. selectCell(newSel.row, newSel.col);
  320. },
  321. ValueChanged(sender, args) {
  322. if (isEmptyVal(args.oldValue) && isEmptyVal(args.newValue)) {
  323. return;
  324. }
  325. edit(args.sheet, [{ row: args.row, col: args.col }]);
  326. },
  327. RangeChanged(sender, args) {
  328. edit(args.sheet, args.changedCells);
  329. }
  330. }
  331. const bindEvents = (sheet) => {
  332. Object.entries(events).forEach(([ev, evFunc]) => {
  333. sheet.bind(GC.Spread.Sheets.Events[ev], evFunc);
  334. });
  335. }
  336. /* 只读相关 */
  337. // 单元格锁定判断
  338. const lockFactory = {
  339. code(node) {
  340. return !!(node && node.getFlag());
  341. },
  342. name(node) {
  343. return !!(node && node.getFlag());
  344. },
  345. calcBase(node) {
  346. return !!(node && node.children.length);
  347. },
  348. feeRate(node) {
  349. return !!(node && node.children.length);
  350. }
  351. };
  352. const lockData = (sheet, nodes, isMass = true) => {
  353. const lock = () => {
  354. if (projectReadOnly) {
  355. sheet.getRange(0, 0, nodes.length, budgetSummaryTreeSetting.cols.length, GC.Spread.Sheets.SheetArea.viewport).locked(true);
  356. return 0;
  357. }
  358. // 工程费用区域,只读
  359. const constructionFeeNode = nodes.find(node => node.getFlag() === fixedFlag.CONSTRUCTION_FEE);
  360. if (!constructionFeeNode) {
  361. return 0;
  362. }
  363. const endIndex = constructionFeeNode.posterityCount() + 1;
  364. sheet.getRange(0, 0, endIndex, budgetSummaryTreeSetting.cols.length, GC.Spread.Sheets.SheetArea.viewport).locked(true);
  365. // 除第一行,章编号 节编号可编辑
  366. if (curBudgetType === BudgetType.RAIL) {
  367. const chapterCodeCol = budgetSummaryTreeSetting.cols.findIndex(item => item.data.field === 'chapterCode');
  368. const sectionCodeCol = budgetSummaryTreeSetting.cols.findIndex(item => item.data.field === 'sectionCode');
  369. for (let row = 1; row < endIndex; row++) {
  370. if (chapterCodeCol > -1) {
  371. sheet.getCell(row, chapterCodeCol).locked(false);
  372. }
  373. if (sectionCodeCol > -1) {
  374. sheet.getCell(row, sectionCodeCol).locked(false);
  375. }
  376. }
  377. }
  378. return endIndex;
  379. }
  380. if (isMass) {
  381. TREE_SHEET_HELPER.massOperationSheet(sheet, () => {
  382. return lock();
  383. });
  384. } else {
  385. return lock();
  386. }
  387. }
  388. /* 单元格类型 */
  389. const calcBaseButtonCallback = (hitInfo) => {
  390. calcBaseView.onCalcBaseButtonClick(hitInfo, 'budget');
  391. };
  392. const cellTypeFactory = {
  393. calcBase(node) {
  394. const locked = lockFactory.calcBase(node);
  395. return sheetCommonObj.getCusButtonCellType(calcBaseButtonCallback, locked);
  396. }
  397. }
  398. /* 设置可编辑区域节点的只读性和单元格类型 */
  399. const setCells = (sheet, startRow, nodes) => {
  400. for (let row = startRow; row < nodes.length; row++) {
  401. const node = nodes[row];
  402. budgetSummaryTreeSetting.cols.forEach((item, col) => {
  403. const field = item.data.field;
  404. // 锁定单元格
  405. const lockFunc = lockFactory[field];
  406. if (lockFunc) {
  407. const isLocked = lockFunc(node);
  408. sheet.getCell(row, col).locked(isLocked);
  409. }
  410. // 设置单元格类型
  411. const cellTypeFunc = cellTypeFactory[field];
  412. if (cellTypeFunc) {
  413. const cellType = cellTypeFunc(node);
  414. sheet.getCell(row, col).cellType(cellType);
  415. }
  416. // 单元格文本转换
  417. const textFunc = textFactory[field];
  418. if (textFunc) {
  419. sheet.setValue(row, col, textFunc(node));
  420. }
  421. });
  422. }
  423. }
  424. /* 初始化表格 */
  425. const initSpread = () => {
  426. if (!spread) {
  427. // spread = sheetCommonObj.createSpread($('#budget-summary-sheet')[0], 1);
  428. spread = SheetDataHelper.createNewSpread($('#budget-summary-sheet')[0]);
  429. sheetCommonObj.spreadDefaultStyle(spread);
  430. // 设置表头
  431. const sheet = spread.getSheet(0);
  432. bindEvents(sheet);
  433. const headers = sheetCommonObj.getHeadersFromTreeSetting(budgetSummaryTreeSetting);
  434. sheetCommonObj.setHeader(sheet, headers);
  435. // 右键菜单
  436. initContextMenu();
  437. } else {
  438. spread.refresh();
  439. }
  440. return spread;
  441. }
  442. // 初始化树
  443. const initTree = (data, sheet, setting) => {
  444. tree = idTree.createNew({ id: 'ID', pid: 'ParentID', nid: 'NextSiblingID', rootId: -1, autoUpdate: true });
  445. const controller = TREE_SHEET_CONTROLLER.createNew(tree, sheet, setting, false, true);
  446. tree.loadDatas(data);
  447. tree.items.forEach(node => {
  448. node.source = node;
  449. node.sourceType = ModuleNames.bills;
  450. });
  451. controller.showTreeData();
  452. sheet.setRowCount(data.length);
  453. setUnitCombo(sheet, data);
  454. TREE_SHEET_HELPER.massOperationSheet(sheet, () => {
  455. const startRow = lockData(sheet, tree.items, false);
  456. setCells(sheet, startRow, tree.items);
  457. // 表格格式化
  458. budgetSummaryTreeSetting.cols.forEach((item, index) => {
  459. sheet.setFormatter(-1, index, item.formatter || '@', GC.Spread.Sheets.SheetArea.viewport);
  460. });
  461. });
  462. selectCell(sheet.getActiveRowIndex(), sheet.getActiveColumnIndex());
  463. }
  464. /* 右键菜单 */
  465. // 更新树结构数据
  466. const updateTree = (sheet, updateData) => {
  467. // 更新数据
  468. updateData.forEach(item => {
  469. if (item.type === 'new') {
  470. orgMap[item.data.ID] = _.cloneDeep(item.data);
  471. rawData.push(item.data)
  472. } else if (item.type === 'update') {
  473. if (orgMap[item.data.ID]) {
  474. Object.assign(orgMap[item.data.ID], item.data);
  475. }
  476. const node = tree.findNode(item.data.ID);
  477. if (node) {
  478. Object.assign(node.data, item.data);
  479. }
  480. } else {
  481. delete orgMap[item.data.ID];
  482. const removeIndex = rawData.findIndex(d => d.ID === item.data.ID);
  483. if (removeIndex > -1) {
  484. rawData.splice(removeIndex, 1);
  485. }
  486. }
  487. });
  488. // 重新初始化树
  489. initTree(rawData, sheet, budgetSummaryTreeSetting);
  490. }
  491. let loading = false;
  492. // 插入
  493. const insert = async (sheet, selected) => {
  494. try {
  495. if (loading) {
  496. return;
  497. }
  498. loading = true;
  499. $.bootstrapLoading.start();
  500. const updateData = tree.getInsertData(selected.data.ParentID, selected.data.NextSiblingID, uuid.v1());
  501. const newData = updateData.filter(item => item.type === 'new');
  502. newData.forEach(item => {
  503. item.data.fees = [];
  504. item.data.flags = [];
  505. item.feesIndex = {};
  506. item.flagsIndex = {};
  507. item.data.type = selected.data.type;
  508. item.data.projectID = projectObj.project.property.rootProjectID;
  509. });
  510. await bulkOperation(updateData);
  511. updateTree(sheet, updateData);
  512. selectCell(sheet.getActiveRowIndex() + selected.posterityCount() + 1, sheet.getActiveColumnIndex(), true);
  513. } catch (err) {
  514. alert(err);
  515. } finally {
  516. $.bootstrapLoading.end();
  517. loading = false;
  518. }
  519. }
  520. // 删除
  521. const remove = async (sheet, selected) => {
  522. try {
  523. if (loading) {
  524. return;
  525. }
  526. loading = true;
  527. $.bootstrapLoading.start();
  528. const updateData = tree.getDeleteData(selected);
  529. await bulkOperation(updateData);
  530. updateTree(sheet, updateData);
  531. } catch (err) {
  532. alert(err);
  533. } finally {
  534. $.bootstrapLoading.end();
  535. loading = false;
  536. }
  537. }
  538. // 升级
  539. const upLevel = async (selected) => {
  540. if (!spread || !tree) {
  541. return;
  542. }
  543. const sheet = spread.getSheet(0);
  544. selected = selected ? selected : tree.selected;
  545. try {
  546. if (loading || upLevelDisabled) {
  547. return;
  548. }
  549. loading = true;
  550. $.bootstrapLoading.start();
  551. const updateData = selected.getUpLevelData();
  552. await bulkOperation(updateData);
  553. updateTree(sheet, updateData);
  554. } catch (err) {
  555. alert(err);
  556. } finally {
  557. $.bootstrapLoading.end();
  558. loading = false;
  559. }
  560. }
  561. // 降级
  562. const downLevel = async (selected) => {
  563. if (!spread || !tree) {
  564. return;
  565. }
  566. const sheet = spread.getSheet(0);
  567. selected = selected ? selected : tree.selected;
  568. try {
  569. if (loading || downLevelDisabled) {
  570. return;
  571. }
  572. loading = true;
  573. $.bootstrapLoading.start();
  574. const updateData = selected.getDownLevelData();
  575. await bulkOperation(updateData);
  576. updateTree(sheet, updateData);
  577. } catch (err) {
  578. alert(err);
  579. } finally {
  580. $.bootstrapLoading.end();
  581. loading = false;
  582. }
  583. }
  584. // 上移
  585. const upMove = async (selected) => {
  586. if (!spread || !tree) {
  587. return;
  588. }
  589. const sheet = spread.getSheet(0);
  590. selected = selected ? selected : tree.selected;
  591. try {
  592. if (loading || upMoveDisabled) {
  593. return;
  594. }
  595. loading = true;
  596. $.bootstrapLoading.start();
  597. const updateData = selected.getUpMoveData();
  598. await bulkOperation(updateData);
  599. updateTree(sheet, updateData);
  600. const prev = selected.preSibling;
  601. const row = sheet.getActiveRowIndex() - prev.posterityCount() - 1;
  602. selectCell(row, sheet.getActiveColumnIndex(), true);
  603. sheet.showRow(row, GC.Spread.Sheets.VerticalPosition.center);
  604. } catch (err) {
  605. alert(err);
  606. } finally {
  607. $.bootstrapLoading.end();
  608. loading = false;
  609. }
  610. }
  611. // 下移
  612. const downMove = async (selected) => {
  613. if (!spread || !tree) {
  614. return;
  615. }
  616. const sheet = spread.getSheet(0);
  617. selected = selected ? selected : tree.selected;
  618. try {
  619. if (loading || downMoveDisabled) {
  620. return;
  621. }
  622. loading = true;
  623. $.bootstrapLoading.start();
  624. const updateData = selected.getDownMoveData();
  625. await bulkOperation(updateData);
  626. updateTree(sheet, updateData);
  627. const next = selected.nextSibling;
  628. const row = sheet.getActiveRowIndex() + next.posterityCount() + 1;
  629. selectCell(row, sheet.getActiveColumnIndex(), true);
  630. sheet.showRow(row, GC.Spread.Sheets.VerticalPosition.center);
  631. } catch (err) {
  632. alert(err);
  633. } finally {
  634. $.bootstrapLoading.end();
  635. loading = false;
  636. }
  637. }
  638. // 初始化右键菜单
  639. const initContextMenu = () => {
  640. if (!spread) {
  641. return;
  642. }
  643. let curRow;
  644. let curNode;
  645. const sheet = spread.getSheet(0);
  646. $.contextMenu({
  647. selector: '#budget-summary-sheet',
  648. build: function ($trigger, e) {
  649. const target = SheetDataHelper.safeRightClickSelection($trigger, e, spread);
  650. curRow = target.row;
  651. curNode = tree && tree.items[curRow] || null;
  652. selectCell(target.row, target.col, true);
  653. return target.hitTestType === GC.Spread.Sheets.SheetArea.viewport || target.hitTestType === GC.Spread.Sheets.SheetArea.rowHeader;
  654. },
  655. items: {
  656. insert: {
  657. name: '插入行',
  658. icon: 'fa-sign-in',
  659. disabled() {
  660. return !curNode || (curNode.data.area === BudgetArea.CONSTRUCTION_FEE && curNode.getFlag() !== fixedFlag.CONSTRUCTION_FEE);
  661. },
  662. callback() {
  663. insert(sheet, curNode);
  664. }
  665. },
  666. remove: {
  667. name: '删除行',
  668. icon: 'fa-remove',
  669. disabled() {
  670. return !curNode || isConstructionFeeArea(curNode) || curNode.getFlag();
  671. },
  672. callback() {
  673. remove(sheet, curNode);
  674. }
  675. },
  676. refresh: {
  677. name: '刷新数据',
  678. icon: 'fa-refresh',
  679. callback() {
  680. init(projectObj.project.property.rootProjectID);
  681. }
  682. },
  683. }
  684. });
  685. }
  686. // 初始化
  687. const init = async (constructionID) => {
  688. try {
  689. $.bootstrapLoading.start();
  690. // 得先计算费用汇总(概算汇总计算基于费用汇总算出来的总金额)
  691. await projectObj.project.calcProgram.getGatherFeeData();
  692. const { budgetType, treeData, costGrowthRate, growthPeriod } = await ajaxPost('/bills/initialBudgetSummary', { constructionID });
  693. budgetSummaryTreeSetting = budgetType === BudgetType.BUILDING ? budgetInstallationSetting : budgetRailSetting;
  694. curBudgetType = budgetType;
  695. calcSetting.costGrowthRate = costGrowthRate;
  696. calcSetting.growthPeriod = growthPeriod;
  697. $('#costGrowthRate').val(costGrowthRate);
  698. $('#growthPeriod').val(growthPeriod);
  699. rawData = treeData;
  700. rawData.forEach((item) => {
  701. if (item.quantity) {
  702. item.quantity = parseFloat(item.quantity);
  703. }
  704. item.feesIndex = getFeeIndex(item.fees);
  705. item.flagsIndex = {};
  706. if (item.flags) {
  707. item.flags.forEach((flag) => {
  708. item.flagsIndex[flag.fieldName] = flag;
  709. });
  710. }
  711. });
  712. const spread = initSpread();
  713. const sheet = spread.getSheet(0);
  714. rawData.forEach(item => {
  715. orgMap[item.ID] = _.cloneDeep(item);
  716. });
  717. initTree(rawData, sheet, budgetSummaryTreeSetting);
  718. calcBase.initBudget();
  719. // 造价计算
  720. await edit(sheet, [], tree.roots.slice(1));
  721. } catch (err) {
  722. console.log(err);
  723. alert(err);
  724. } finally {
  725. $.bootstrapLoading.end();
  726. }
  727. }
  728. // 点击tab,重新初始化
  729. $('#tab-budget-summary').click(function () {
  730. if (!$(this).hasClass('active')) {
  731. init(projectObj.project.property.rootProjectID);
  732. }
  733. });
  734. $('#budget-upLevel').click(() => {
  735. upLevel();
  736. });
  737. $('#budget-downLevel').click(() => {
  738. downLevel();
  739. });
  740. $('#budget-upMove').click(() => {
  741. upMove();
  742. });
  743. $('#budget-downMove').click(() => {
  744. downMove();
  745. });
  746. /* 建设项目设置 */
  747. $('#openConstructionSet').click(() => {
  748. $('#constructionSet').modal('show');
  749. });
  750. function isKeyNumber(keyCode) {
  751. // 数字
  752. if (keyCode >= 48 && keyCode <= 57) {
  753. return true;
  754. } else if (keyCode >= 96 && keyCode <= 105) { //小键盘数字
  755. return true;
  756. } else if (keyCode == 8 || keyCode == 46 || keyCode == 37 || keyCode == 39 || keyCode == 108 || keyCode == 110) { // Backspace, del, 左右方向键
  757. return true;
  758. } else if (keyCode >= 112 && keyCode <= 123) { //F1 -F12
  759. return true;
  760. }
  761. return false;
  762. }
  763. //年造价增涨率
  764. $('#costGrowthRate').keydown(function (e) {
  765. return isKeyNumber(e.keyCode);
  766. });
  767. //计费年限
  768. $("#growthPeriod").keydown(function (e) {
  769. return isKeyNumber(e.keyCode);
  770. });
  771. // 确认设置
  772. $('#construction-set-ok').click(async () => {
  773. try {
  774. $.bootstrapLoading.start();
  775. const curCostGrowthRate = $('#costGrowthRate').val();
  776. const curGrowthPeriod = $('#growthPeriod').val();
  777. const bulkData = [
  778. {
  779. type: 'updateProject',
  780. data: {
  781. ID: projectObj.project.property.rootProjectID,
  782. 'property.costGrowthRate': curCostGrowthRate,
  783. 'property.growthPeriod': curGrowthPeriod,
  784. }
  785. }
  786. ];
  787. await bulkOperation(bulkData);
  788. calcSetting.costGrowthRate = curCostGrowthRate;
  789. calcSetting.growthPeriod = curGrowthPeriod;
  790. } catch (err) {
  791. alert(err);
  792. $('#costGrowthRate').val(calcSetting.costGrowthRate);
  793. $('#growthPeriod').val(calcSetting.growthPeriod);
  794. } finally {
  795. $.bootstrapLoading.end();
  796. }
  797. });
  798. // 对外暴露
  799. return {
  800. getTree: () => tree,
  801. getSheet: () => spread.getSheet(0),
  802. calcSetting,
  803. edit,
  804. };
  805. })();