budgetSummarySheet.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605
  1. /* 建设其他费表格相关 */
  2. const budgetSummaryObj = (() => {
  3. const { isEmptyVal, isDef, isNumber } = window.commonUtil;
  4. const { fixedFlag, BudgetArea } = window.commonConstants;
  5. // 原始数据
  6. let rawData = [];
  7. // 建设其他费表格对象
  8. let spread = null;
  9. // 建设其他费树
  10. let tree = null;
  11. // 单位设置下拉框
  12. const setUnitCombo = (sheet, data) => {
  13. const unitCol = budgetSummaryTreeSetting.cols.findIndex(item => item.data.field === 'unit');
  14. if (unitCol >= 0) {
  15. TREE_SHEET_HELPER.massOperationSheet(sheet, () => {
  16. const comboBox = sheetCommonObj.getDynamicCombo();
  17. comboBox
  18. .itemHeight(10)
  19. .items(['m', 'm2', 'm3', 'km', 't', 'kg', '台班', '工日', '昼夜', '元', '项', '处', '个', '件',
  20. '根', '组', '系统', '台', '套', '株', '丛', '缸', '支', '只', '块', '座', '对', '份', '樘', '攒', '榀'])
  21. .editable(true);
  22. data.forEach((item, index) => {
  23. sheet.getCell(index, unitCol).cellType(comboBox);
  24. })
  25. });
  26. }
  27. }
  28. const getFieldByCol = (col) => {
  29. const item = budgetSummaryTreeSetting.cols[col];
  30. return item && item.data && item.data.field || null;
  31. }
  32. // 单元格值验证器
  33. const validator = {
  34. text() {
  35. return true
  36. },
  37. number(val) {
  38. return !isDef(val) || isNumber(val);
  39. },
  40. };
  41. const getValidator = (col) => {
  42. const item = budgetSummaryTreeSetting.cols[col];
  43. if (!item) {
  44. return 'text';
  45. }
  46. return validator[item.data.type || 'text'];
  47. }
  48. /* 表格事件相关 */
  49. // 恢复数据
  50. const recover = (sheet, changedCells) => {
  51. if (!tree) {
  52. return;
  53. }
  54. changedCells.forEach(({ row, col }) => {
  55. const node = tree.items[row];
  56. const field = getFieldByCol(col);
  57. if (!field || !node) {
  58. return;
  59. }
  60. const orgVal = node.data[field];
  61. sheet.setValue(row, col, orgVal);
  62. })
  63. }
  64. // 更新数据
  65. const bulkOperation = async (bulkData) => {
  66. await ajaxPost('/bills/bulkOperation', { bulkData });
  67. };
  68. // 编辑相关
  69. const edit = async (sheet, changedCells) => {
  70. if (!changedCells.length) {
  71. return;
  72. }
  73. // 单元格值验证
  74. const isValid = changedCells.every(({ row, col }) => {
  75. const val = sheet.getValue(row, col);
  76. return getValidator(col)(val);
  77. });
  78. // 验证不通过,恢复
  79. if (!isValid) {
  80. recover(sheet, changedCells);
  81. }
  82. console.log(changedCells);
  83. try {
  84. $.bootstrapLoading.start();
  85. const IDMap = {};
  86. const bulkData = [];
  87. changedCells.forEach(({ row, col }) => {
  88. const node = tree.items[row];
  89. if (!node) {
  90. return;
  91. }
  92. const field = getFieldByCol(col);
  93. const data = (IDMap[node.data.ID] || (IDMap[node.data.ID] = {}));
  94. data[field] = sheet.getValue(row, col);
  95. });
  96. Object
  97. .entries(IDMap)
  98. .forEach(([ID, data]) => {
  99. bulkData.push({ type: 'update', data: { ID, ...data } });
  100. });
  101. await bulkOperation(bulkData);
  102. bulkData.forEach(item => {
  103. const node = tree.findNode(item.data.ID);
  104. Object.assign(node.data, item.data);
  105. });
  106. } catch (err) {
  107. alert(err);
  108. } finally {
  109. $.bootstrapLoading.end();
  110. }
  111. }
  112. // 是否是属于工程费用区域的节点
  113. const isConstructionFeeArea = (node) => {
  114. return node && node.data && node.data.area === BudgetArea.CONSTRUCTION_FEE;
  115. }
  116. // 工具栏可操作性
  117. let upLevelDisabled = false;
  118. let downLevelDisabled = false;
  119. let upMoveDisabled = false;
  120. let downMoveDisabled = false;
  121. const refreshToolsBar = (node) => {
  122. upLevelDisabled = !node || !node.canUpLevel() || isConstructionFeeArea(node) || (node.nextSibling && node.data.calcBase);
  123. downLevelDisabled = !node || !node.canDownLevel() || isConstructionFeeArea(node) || isConstructionFeeArea(node.preSibling) || (node.preSibling && node.preSibling.data.calcBase);
  124. upMoveDisabled = !node || !node.canUpMove() || isConstructionFeeArea(node) || isConstructionFeeArea(node.preSibling);
  125. downMoveDisabled = !node || !node.canDownMove() || isConstructionFeeArea(node) || isConstructionFeeArea(node.nextSibling);
  126. if (upLevelDisabled) {
  127. $('#budget-upLevel').addClass('disabled');
  128. } else {
  129. $('#budget-upLevel').removeClass('disabled');
  130. }
  131. if (downLevelDisabled) {
  132. $('#budget-downLevel').addClass('disabled');
  133. } else {
  134. $('#budget-downLevel').removeClass('disabled');
  135. }
  136. if (upMoveDisabled) {
  137. $('#budget-upMove').addClass('disabled');
  138. } else {
  139. $('#budget-upMove').removeClass('disabled');
  140. }
  141. if (downMoveDisabled) {
  142. $('#budget-downMove').addClass('disabled');
  143. } else {
  144. $('#budget-downMove').removeClass('disabled');
  145. }
  146. };
  147. // 表格选中相关
  148. const selectCell = (row, col, setCell = false) => {
  149. const node = tree.items[row];
  150. refreshToolsBar(node);
  151. tree.selected = node;
  152. console.log(node);
  153. const sheet = spread.getSheet(0);
  154. if (setCell) {
  155. sheet.setActiveCell(row, col);
  156. }
  157. //设置选中行底色和恢复前选中行底色
  158. const refreshNodes = [node];
  159. if (!tree.preSelected) {
  160. refreshNodes.push(tree.items[0]);
  161. } else {
  162. refreshNodes.push(tree.preSelected);
  163. }
  164. tree.preSelected = node;
  165. projectObj.setNodesStyle(sheet, refreshNodes, tree);
  166. };
  167. // 事件列表
  168. const events = {
  169. EnterCell(sender, args) {
  170. args.sheet.repaint();
  171. },
  172. SelectionChanged(sender, args) {
  173. const newSel = args.newSelections[0] ? { row: args.newSelections[0].row, col: args.newSelections[0].col } : { row: 0, col: 0 };
  174. selectCell(newSel.row, newSel.col);
  175. },
  176. ValueChanged(sender, args) {
  177. if (isEmptyVal(args.oldValue) && isEmptyVal(args.newValue)) {
  178. return;
  179. }
  180. edit(args.sheet, [{ row: args.row, col: args.col }]);
  181. },
  182. RangeChanged(sender, args) {
  183. edit(args.sheet, args.changedCells);
  184. }
  185. }
  186. const bindEvents = (sheet) => {
  187. Object.entries(events).forEach(([ev, evFunc]) => {
  188. sheet.bind(GC.Spread.Sheets.Events[ev], evFunc);
  189. });
  190. }
  191. /* 只读相关 */
  192. // 单元格锁定判断
  193. const lockFactory = {
  194. code(node) {
  195. return !!(node && node.getFlag());
  196. },
  197. name(node) {
  198. return !!(node && node.getFlag());
  199. },
  200. calcBase(node) {
  201. return !!(node && node.children.length);
  202. },
  203. feeRate(node) {
  204. return !!(node && node.children.length);
  205. }
  206. };
  207. const lockData = (sheet, nodes, isMass = true) => {
  208. const lock = () => {
  209. if (projectReadOnly) {
  210. sheet.getRange(0, 0, nodes.length, budgetSummaryTreeSetting.cols.length, GC.Spread.Sheets.SheetArea.viewport).locked(true);
  211. return;
  212. }
  213. // 工程费用区域,只读
  214. const equipmentNode = nodes.find(node => node.getFlag() === fixedFlag.CONSTRUCTION_EQUIPMENT_FEE);
  215. if (!equipmentNode) {
  216. return;
  217. }
  218. sheet.getRange(0, 0, equipmentNode.serialNo() + 1, budgetSummaryTreeSetting.cols.length, GC.Spread.Sheets.SheetArea.viewport).locked(true);
  219. // 剩下的区域,根据单元格锁定方法,按字段进行锁定判断
  220. const startIndex = equipmentNode.serialNo() + 1;
  221. for (let row = startIndex; row < nodes.length; row ++) {
  222. const node = nodes[row];
  223. budgetSummaryTreeSetting.cols.forEach((item, col) => {
  224. const field = item.data.field;
  225. const lockFunc = lockFactory[field];
  226. if (!lockFunc) {
  227. return;
  228. }
  229. const isLocked = lockFunc(node);
  230. sheet.getCell(row, col).locked(isLocked);
  231. });
  232. }
  233. }
  234. if (isMass) {
  235. TREE_SHEET_HELPER.massOperationSheet(sheet, () => {
  236. lock();
  237. });
  238. } else {
  239. lock();
  240. }
  241. }
  242. /* 初始化表格 */
  243. const initSpread = () => {
  244. if (!spread) {
  245. // spread = sheetCommonObj.createSpread($('#budget-summary-sheet')[0], 1);
  246. spread = SheetDataHelper.createNewSpread($('#budget-summary-sheet')[0]);
  247. sheetCommonObj.spreadDefaultStyle(spread);
  248. // 设置表头
  249. const sheet = spread.getSheet(0);
  250. bindEvents(sheet);
  251. const headers = sheetCommonObj.getHeadersFromTreeSetting(budgetSummaryTreeSetting);
  252. sheetCommonObj.setHeader(sheet, headers);
  253. // 右键菜单
  254. initContextMenu();
  255. } else {
  256. spread.refresh();
  257. }
  258. return spread;
  259. }
  260. // 初始化树
  261. const initTree = (data, sheet, setting) => {
  262. tree = idTree.createNew({ id: 'ID', pid: 'ParentID', nid: 'NextSiblingID', rootId: -1, autoUpdate: true });
  263. const controller = TREE_SHEET_CONTROLLER.createNew(tree, sheet, setting, false, true);
  264. tree.loadDatas(data);
  265. tree.items.forEach(node => {
  266. node.source = node;
  267. node.sourceType = ModuleNames.bills;
  268. });
  269. controller.showTreeData();
  270. sheet.setRowCount(data.length);
  271. setUnitCombo(sheet, data);
  272. TREE_SHEET_HELPER.massOperationSheet(sheet, () => {
  273. lockData(sheet, tree.items, false);
  274. // 表格格式化
  275. budgetSummaryTreeSetting.cols.forEach((item, index) => {
  276. sheet.setFormatter(-1, index, item.formatter || '@', GC.Spread.Sheets.SheetArea.viewport);
  277. });
  278. });
  279. selectCell(sheet.getActiveRowIndex(), sheet.getActiveColumnIndex());
  280. }
  281. /* 右键菜单 */
  282. // 更新树结构数据
  283. const updateTree = (sheet, updateData) => {
  284. // 更新数据
  285. updateData.forEach(item => {
  286. if (item.type === 'new') {
  287. rawData.push(item.data)
  288. } else if (item.type === 'update') {
  289. const node = tree.findNode(item.data.ID);
  290. if (node) {
  291. Object.assign(node.data, item.data);
  292. }
  293. } else {
  294. const removeIndex = rawData.findIndex(d => d.ID === item.data.ID);
  295. if (removeIndex > -1) {
  296. rawData.splice(removeIndex, 1);
  297. }
  298. }
  299. });
  300. // 重新初始化树
  301. initTree(rawData, sheet, budgetSummaryTreeSetting);
  302. }
  303. let loading = false;
  304. // 插入
  305. const insert = async (sheet, selected) => {
  306. try {
  307. if (loading) {
  308. return;
  309. }
  310. loading = true;
  311. $.bootstrapLoading.start();
  312. const updateData = tree.getInsertData(selected.data.ParentID, selected.data.NextSiblingID, uuid.v1());
  313. const newData = updateData.filter(item => item.type === 'new');
  314. newData.forEach(item => {
  315. item.data.fees = [];
  316. item.data.flags = [];
  317. item.feesIndex = {};
  318. item.flagsIndex = {};
  319. item.data.type = selected.data.type;
  320. item.data.projectID = projectObj.project.property.rootProjectID;
  321. });
  322. await bulkOperation(updateData);
  323. updateTree(sheet, updateData);
  324. selectCell(sheet.getActiveRowIndex() + selected.posterityCount() + 1, sheet.getActiveColumnIndex(), true);
  325. } catch (err) {
  326. alert(err);
  327. } finally {
  328. $.bootstrapLoading.end();
  329. loading = false;
  330. }
  331. }
  332. // 删除
  333. const remove = async (sheet, selected) => {
  334. try {
  335. if (loading) {
  336. return;
  337. }
  338. loading = true;
  339. $.bootstrapLoading.start();
  340. const updateData = tree.getDeleteData(selected);
  341. await bulkOperation(updateData);
  342. updateTree(sheet, updateData);
  343. } catch (err) {
  344. alert(err);
  345. } finally {
  346. $.bootstrapLoading.end();
  347. loading = false;
  348. }
  349. }
  350. // 升级
  351. const upLevel = async (selected) => {
  352. if (!spread || !tree) {
  353. return;
  354. }
  355. const sheet = spread.getSheet(0);
  356. selected = selected ? selected : tree.selected;
  357. try {
  358. if (loading || upLevelDisabled) {
  359. return;
  360. }
  361. loading = true;
  362. $.bootstrapLoading.start();
  363. const updateData = selected.getUpLevelData();
  364. await bulkOperation(updateData);
  365. updateTree(sheet, updateData);
  366. } catch (err) {
  367. alert(err);
  368. } finally {
  369. $.bootstrapLoading.end();
  370. loading = false;
  371. }
  372. }
  373. // 降级
  374. const downLevel = async (selected) => {
  375. if (!spread || !tree) {
  376. return;
  377. }
  378. const sheet = spread.getSheet(0);
  379. selected = selected ? selected : tree.selected;
  380. try {
  381. if (loading || downLevelDisabled) {
  382. return;
  383. }
  384. loading = true;
  385. $.bootstrapLoading.start();
  386. const updateData = selected.getDownLevelData();
  387. await bulkOperation(updateData);
  388. updateTree(sheet, updateData);
  389. } catch (err) {
  390. alert(err);
  391. } finally {
  392. $.bootstrapLoading.end();
  393. loading = false;
  394. }
  395. }
  396. // 上移
  397. const upMove = async (selected) => {
  398. if (!spread || !tree) {
  399. return;
  400. }
  401. const sheet = spread.getSheet(0);
  402. selected = selected ? selected : tree.selected;
  403. try {
  404. if (loading || upMoveDisabled) {
  405. return;
  406. }
  407. loading = true;
  408. $.bootstrapLoading.start();
  409. const updateData = selected.getUpMoveData();
  410. await bulkOperation(updateData);
  411. updateTree(sheet, updateData);
  412. const prev = selected.preSibling;
  413. const row = sheet.getActiveRowIndex() - prev.posterityCount() - 1;
  414. selectCell(row, sheet.getActiveColumnIndex(), true);
  415. sheet.showRow(row, GC.Spread.Sheets.VerticalPosition.center);
  416. } catch (err) {
  417. alert(err);
  418. } finally {
  419. $.bootstrapLoading.end();
  420. loading = false;
  421. }
  422. }
  423. // 下移
  424. const downMove = async (selected) => {
  425. if (!spread || !tree) {
  426. return;
  427. }
  428. const sheet = spread.getSheet(0);
  429. selected = selected ? selected : tree.selected;
  430. try {
  431. if (loading || downMoveDisabled) {
  432. return;
  433. }
  434. loading = true;
  435. $.bootstrapLoading.start();
  436. const updateData = selected.getDownMoveData();
  437. await bulkOperation(updateData);
  438. updateTree(sheet, updateData);
  439. const next = selected.nextSibling;
  440. const row = sheet.getActiveRowIndex() + next.posterityCount() + 1;
  441. selectCell(row, sheet.getActiveColumnIndex(), true);
  442. sheet.showRow(row, GC.Spread.Sheets.VerticalPosition.center);
  443. } catch (err) {
  444. alert(err);
  445. } finally {
  446. $.bootstrapLoading.end();
  447. loading = false;
  448. }
  449. }
  450. // 初始化右键菜单
  451. const initContextMenu = () => {
  452. if (!spread) {
  453. return;
  454. }
  455. let curRow;
  456. let curNode;
  457. const sheet = spread.getSheet(0);
  458. $.contextMenu({
  459. selector: '#budget-summary-sheet',
  460. build: function ($trigger, e) {
  461. const target = SheetDataHelper.safeRightClickSelection($trigger, e, spread);
  462. curRow = target.row;
  463. curNode = tree && tree.items[curRow] || null;
  464. selectCell(target.row, target.col, true);
  465. return target.hitTestType === GC.Spread.Sheets.SheetArea.viewport || target.hitTestType === GC.Spread.Sheets.SheetArea.rowHeader;
  466. },
  467. items: {
  468. insert: {
  469. name: '插入行',
  470. icon: 'fa-sign-in',
  471. disabled() {
  472. return !curNode || (curNode.data.area === BudgetArea.CONSTRUCTION_FEE && curNode.getFlag() !== fixedFlag.CONSTRUCTION_FEE);
  473. },
  474. callback() {
  475. insert(sheet, curNode);
  476. }
  477. },
  478. remove: {
  479. name: '删除行',
  480. icon: 'fa-remove',
  481. disabled() {
  482. return !curNode || isConstructionFeeArea(curNode) || curNode.getFlag();
  483. },
  484. callback() {
  485. remove(sheet, curNode);
  486. }
  487. },
  488. /* upLevel: {
  489. name: '升级',
  490. icon: 'fa-arrow-left',
  491. disabled() {
  492. return !curNode || !curNode.canUpLevel() || isConstructionFeeArea(curNode);
  493. },
  494. callback() {
  495. upLevel(sheet, curNode);
  496. }
  497. },
  498. downLevel: {
  499. name: '降级',
  500. icon: 'fa-arrow-right',
  501. disabled() {
  502. return !curNode || !curNode.canDownLevel() || isConstructionFeeArea(curNode) || isConstructionFeeArea(curNode.preSibling);
  503. },
  504. callback() {
  505. downLevel(sheet, curNode);
  506. }
  507. },
  508. upMove: {
  509. name: '上移',
  510. icon: 'fa-arrow-up',
  511. disabled() {
  512. return !curNode || !curNode.canUpMove() || isConstructionFeeArea(curNode) || isConstructionFeeArea(curNode.preSibling);
  513. },
  514. callback() {
  515. upMove(sheet, curNode);
  516. }
  517. },
  518. downMove: {
  519. name: '下移',
  520. icon: 'fa-arrow-down',
  521. disabled() {
  522. return !curNode || !curNode.canDownMove() || isConstructionFeeArea(curNode) || isConstructionFeeArea(curNode.nextSibling);
  523. },
  524. callback() {
  525. downMove(sheet, curNode);
  526. }
  527. }, */
  528. refresh: {
  529. name: '刷新数据',
  530. icon: 'fa-refresh',
  531. callback() {
  532. init(projectObj.project.property.rootProjectID);
  533. }
  534. },
  535. }
  536. });
  537. }
  538. // 初始化
  539. const init = async (constructionID) => {
  540. try {
  541. $.bootstrapLoading.start();
  542. rawData = await ajaxPost('/bills/getBudgetSummary', { constructionID });
  543. rawData.forEach((item) => {
  544. if (item.quantity) {
  545. item.quantity = parseFloat(item.quantity);
  546. }
  547. item.feesIndex = getFeeIndex(item.fees);
  548. item.flagsIndex = {};
  549. if (item.flags) {
  550. item.flags.forEach((flag) => {
  551. item.flagsIndex[flag.fieldName] = flag;
  552. });
  553. }
  554. });
  555. const spread = initSpread();
  556. const sheet = spread.getSheet(0);
  557. initTree(rawData, sheet, budgetSummaryTreeSetting);
  558. } catch (err) {
  559. console.log(err);
  560. alert(err);
  561. } finally {
  562. $.bootstrapLoading.end();
  563. }
  564. }
  565. // 点击tab,重新初始化
  566. $('#tab-budget-summary').click(function () {
  567. if (!$(this).hasClass('active')) {
  568. init(projectObj.project.property.rootProjectID);
  569. }
  570. });
  571. $('#budget-upLevel').click(() => {
  572. upLevel();
  573. });
  574. $('#budget-downLevel').click(() => {
  575. downLevel();
  576. });
  577. $('#budget-upMove').click(() => {
  578. upMove();
  579. });
  580. $('#budget-downMove').click(() => {
  581. downMove();
  582. });
  583. // 对外暴露
  584. return {
  585. getTree: () => tree,
  586. };
  587. })();