budgetSummarySheet.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429
  1. /* 建设其他费表格相关 */
  2. const budgetSummaryObj = (() => {
  3. const { 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. const events = {
  113. EnterCell(sender, args) {
  114. args.sheet.repaint();
  115. },
  116. ValueChanged(sender, args) {
  117. edit(args.sheet, [{ row: args.row, col: args.col }]);
  118. },
  119. RangeChanged(sender, args) {
  120. edit(args.sheet, args.changedCells);
  121. }
  122. }
  123. const bindEvents = (sheet) => {
  124. Object.entries(events).forEach(([ev, evFunc]) => {
  125. sheet.bind(GC.Spread.Sheets.Events[ev], evFunc)
  126. })
  127. }
  128. /* 只读相关 */
  129. const lockData = (sheet, nodes) => {
  130. TREE_SHEET_HELPER.massOperationSheet(sheet, () => {
  131. // 工程费用区域,只读
  132. const equipmentNode = nodes.find(node => node.getFlag() === fixedFlag.CONSTRUCTION_EQUIPMENT_FEE);
  133. if (equipmentNode) {
  134. sheet.getRange(0, 0, equipmentNode.serialNo() + 1, budgetSummaryTreeSetting.cols.length, GC.Spread.Sheets.SheetArea.viewport).locked(true);
  135. }
  136. });
  137. }
  138. /* 初始化表格 */
  139. const initSpread = () => {
  140. if (!spread) {
  141. // spread = sheetCommonObj.createSpread($('#budget-summary-sheet')[0], 1);
  142. spread = SheetDataHelper.createNewSpread($('#budget-summary-sheet')[0]);
  143. sheetCommonObj.spreadDefaultStyle(spread);
  144. // 设置表头
  145. const sheet = spread.getSheet(0);
  146. bindEvents(sheet);
  147. const headers = sheetCommonObj.getHeadersFromTreeSetting(budgetSummaryTreeSetting);
  148. sheetCommonObj.setHeader(sheet, headers);
  149. initContextMenu();
  150. } else {
  151. spread.refresh();
  152. }
  153. return spread;
  154. }
  155. // 初始化树
  156. const initTree = (data, sheet, setting) => {
  157. tree = idTree.createNew({ id: 'ID', pid: 'ParentID', nid: 'NextSiblingID', rootId: -1, autoUpdate: true });
  158. const controller = TREE_SHEET_CONTROLLER.createNew(tree, sheet, setting, false);
  159. tree.loadDatas(data);
  160. tree.items.forEach(node => {
  161. node.source = node;
  162. node.sourceType = ModuleNames.bills;
  163. });
  164. controller.showTreeData();
  165. sheet.setRowCount(data.length);
  166. setUnitCombo(sheet, data);
  167. lockData(sheet, tree.items);
  168. }
  169. /* 右键菜单 */
  170. // 更新树结构数据
  171. const updateTree = (sheet, updateData) => {
  172. // 更新数据
  173. updateData.forEach(item => {
  174. if (item.type === 'new') {
  175. rawData.push(item.data)
  176. } else if (item.type === 'update') {
  177. const node = tree.findNode(item.data.ID);
  178. if (node) {
  179. Object.assign(node.data, item.data);
  180. }
  181. } else {
  182. const removeIndex = rawData.findIndex(d => d.ID === item.data.ID);
  183. if (removeIndex > -1) {
  184. rawData.splice(removeIndex, 1);
  185. }
  186. }
  187. });
  188. // 重新初始化树
  189. initTree(rawData, sheet, budgetSummaryTreeSetting);
  190. }
  191. // 插入
  192. const insert = async (sheet, selected) => {
  193. try {
  194. $.bootstrapLoading.start();
  195. const updateData = tree.getInsertData(selected.data.ParentID, selected.data.NextSiblingID, uuid.v1());
  196. const newData = updateData.filter(item => item.type === 'new');
  197. newData.forEach(item => {
  198. item.data.type = selected.data.type;
  199. item.data.projectID = projectObj.project.property.rootProjectID;
  200. });
  201. await bulkOperation(updateData);
  202. updateTree(sheet, updateData);
  203. sheet.setActiveCell(sheet.getActiveRowIndex() + selected.posterityCount() + 1, sheet.getActiveColumnIndex())
  204. } catch (err) {
  205. alert(err);
  206. } finally {
  207. $.bootstrapLoading.end();
  208. }
  209. }
  210. // 删除
  211. const remove = async (sheet, selected) => {
  212. try {
  213. $.bootstrapLoading.start();
  214. const updateData = tree.getDeleteData(selected);
  215. await bulkOperation(updateData);
  216. updateTree(sheet, updateData);
  217. } catch (err) {
  218. alert(err);
  219. } finally {
  220. $.bootstrapLoading.end();
  221. }
  222. }
  223. // 升级
  224. const upLevel = async (sheet, selected) => {
  225. try {
  226. $.bootstrapLoading.start();
  227. const updateData = selected.getUpLevelData();
  228. await bulkOperation(updateData);
  229. updateTree(sheet, updateData);
  230. } catch (err) {
  231. alert(err);
  232. } finally {
  233. $.bootstrapLoading.end();
  234. }
  235. }
  236. // 降级
  237. const downLevel = async (sheet, selected) => {
  238. try {
  239. $.bootstrapLoading.start();
  240. const updateData = selected.getDownLevelData();
  241. await bulkOperation(updateData);
  242. updateTree(sheet, updateData);
  243. } catch (err) {
  244. alert(err);
  245. } finally {
  246. $.bootstrapLoading.end();
  247. }
  248. }
  249. // 上移
  250. const upMove = async (sheet, selected) => {
  251. try {
  252. $.bootstrapLoading.start();
  253. const updateData = selected.getUpMoveData();
  254. await bulkOperation(updateData);
  255. updateTree(sheet, updateData);
  256. const prev = selected.preSibling;
  257. const row = sheet.getActiveRowIndex() - prev.posterityCount() - 1;
  258. sheet.setActiveCell(row, sheet.getActiveColumnIndex());
  259. sheet.showRow(row, GC.Spread.Sheets.VerticalPosition.center);
  260. } catch (err) {
  261. alert(err);
  262. } finally {
  263. $.bootstrapLoading.end();
  264. }
  265. }
  266. // 下移
  267. const downMove = async (sheet, selected) => {
  268. try {
  269. $.bootstrapLoading.start();
  270. const updateData = selected.getDownMoveData();
  271. await bulkOperation(updateData);
  272. updateTree(sheet, updateData);
  273. const next = selected.nextSibling;
  274. const row = sheet.getActiveRowIndex() + next.posterityCount() + 1;
  275. sheet.setActiveCell(row, sheet.getActiveColumnIndex())
  276. sheet.showRow(row, GC.Spread.Sheets.VerticalPosition.center);
  277. } catch (err) {
  278. alert(err);
  279. } finally {
  280. $.bootstrapLoading.end();
  281. }
  282. }
  283. // 是否是属于工程费用区域的节点
  284. const isConstructionFeeArea = (node) => {
  285. return node && node.data && node.data.area === BudgetArea.CONSTRUCTION_FEE;
  286. }
  287. // 初始化右键菜单
  288. const initContextMenu = () => {
  289. if (!spread) {
  290. return;
  291. }
  292. let curRow;
  293. let curNode;
  294. const sheet = spread.getSheet(0);
  295. $.contextMenu({
  296. selector: '#budget-summary-sheet',
  297. build: function ($trigger, e) {
  298. const target = SheetDataHelper.safeRightClickSelection($trigger, e, spread);
  299. curRow = target.row;
  300. curNode = tree && tree.items[curRow] || null;
  301. sheet.setActiveCell(target.row, target.col);
  302. return target.hitTestType === GC.Spread.Sheets.SheetArea.viewport || target.hitTestType === GC.Spread.Sheets.SheetArea.rowHeader;
  303. },
  304. items: {
  305. insert: {
  306. name: '插入行',
  307. icon: 'fa-sign-in',
  308. disabled() {
  309. return !curNode || (curNode.data.area === BudgetArea.CONSTRUCTION_FEE && curNode.getFlag() !== fixedFlag.CONSTRUCTION_FEE);
  310. },
  311. callback() {
  312. insert(sheet, curNode);
  313. }
  314. },
  315. remove: {
  316. name: '删除行',
  317. icon: 'fa-remove',
  318. disabled() {
  319. return !curNode || isConstructionFeeArea(curNode);
  320. },
  321. callback() {
  322. remove(sheet, curNode);
  323. }
  324. },
  325. upLevel: {
  326. name: '升级',
  327. icon: 'fa-arrow-left',
  328. disabled() {
  329. return !curNode || !curNode.canUpLevel() || isConstructionFeeArea(curNode);
  330. },
  331. callback() {
  332. upLevel(sheet, curNode);
  333. }
  334. },
  335. downLevel: {
  336. name: '降级',
  337. icon: 'fa-arrow-right',
  338. disabled() {
  339. return !curNode || !curNode.canDownLevel() || isConstructionFeeArea(curNode) || isConstructionFeeArea(curNode.preSibling);
  340. },
  341. callback() {
  342. downLevel(sheet, curNode);
  343. }
  344. },
  345. upMove: {
  346. name: '上移',
  347. icon: 'fa-arrow-up',
  348. disabled() {
  349. return !curNode || !curNode.canUpMove() || isConstructionFeeArea(curNode) || isConstructionFeeArea(curNode.preSibling);
  350. },
  351. callback() {
  352. upMove(sheet, curNode);
  353. }
  354. },
  355. downMove: {
  356. name: '下移',
  357. icon: 'fa-arrow-down',
  358. disabled() {
  359. return !curNode || !curNode.canDownMove() || isConstructionFeeArea(curNode) || isConstructionFeeArea(curNode.nextSibling);
  360. },
  361. callback() {
  362. downMove(sheet, curNode);
  363. }
  364. },
  365. refresh: {
  366. name: '刷新数据',
  367. icon: 'fa-refresh',
  368. callback() {
  369. init(projectObj.project.property.rootProjectID);
  370. }
  371. },
  372. }
  373. });
  374. }
  375. // 初始化
  376. const init = async (constructionID) => {
  377. try {
  378. $.bootstrapLoading.start();
  379. rawData = await ajaxPost('/bills/getBudgetSummary', { constructionID });
  380. rawData.forEach((item) => {
  381. if (item.quantity) {
  382. item.quantity = parseFloat(item.quantity);
  383. }
  384. item.feesIndex = getFeeIndex(item.fees);
  385. item.flagsIndex = {};
  386. if (item.flags) {
  387. item.flags.forEach((flag) => {
  388. item.flagsIndex[flag.fieldName] = flag;
  389. });
  390. }
  391. });
  392. const spread = initSpread();
  393. const sheet = spread.getSheet(0);
  394. initTree(rawData, sheet, budgetSummaryTreeSetting);
  395. } catch (err) {
  396. console.log(err);
  397. alert(err);
  398. } finally {
  399. $.bootstrapLoading.end();
  400. }
  401. }
  402. // 点击tab,重新初始化
  403. $('#tab-budget-summary').click(function () {
  404. if (!$(this).hasClass('active')) {
  405. init(projectObj.project.property.rootProjectID);
  406. }
  407. });
  408. // 对外暴露
  409. return {
  410. getTree: () => tree,
  411. };
  412. })();