importBills.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454
  1. 'use strict';
  2. /**
  3. *
  4. *
  5. * @author Zhong
  6. * @date 2018/8/2
  7. * @version
  8. */
  9. /*
  10. * 清单导入模块,前端导入excel,进行数据提取,用lz-string进行压缩上传处理
  11. * */
  12. const importBills = (function(){
  13. //单元格数据是否存在
  14. function _isDef(data){
  15. return typeof data !== 'undefined' && data !== null && data !== '';
  16. }
  17. //去除转义字符
  18. function _deESC(data) {
  19. return _isDef(data) ? data.toString().replace(/[\r,\n,\s,\t]/g, '') : data;
  20. }
  21. function _deNR(data) {
  22. return _isDef(data) ? data.toString().replace(/\r\r/g, '\r') : data;
  23. }
  24. //列名对应中文字符
  25. const colText = {
  26. serialNo: ['序号'],
  27. code: ['编码', '项目编码'],
  28. name: ['名称', '项目名称'],
  29. itemCharacterText: ['特征', '项目特征'],
  30. unit: ['单位', '计量单位'],
  31. quantity: ['工程量', '项目工程量'],
  32. money: ['金额'],
  33. quantityDetail: ['工程量明细'],
  34. feeDetail: ['费用明细'],
  35. summation: ['合计', '本页小计'],
  36. };
  37. //导入位置对应清单固定标记
  38. const positionFlag = {
  39. fbfx: fixedFlag.SUB_ENGINERRING,
  40. jscsxm: fixedFlag.CONSTRUCTION_TECH,
  41. zzcsxm: fixedFlag.CONSTRUCTION_ORGANIZATION,
  42. };
  43. //上传类型
  44. const uploadType = {
  45. lj: 'lj',
  46. gld: 'gld',
  47. };
  48. //设置导入表内容(选择导入位置)
  49. //@param {Object}workBook
  50. function setImportSheetsInfo(sheets){
  51. let sheetNames = [];
  52. let indexMapping = {};
  53. for(let sheetName in sheets){
  54. indexMapping[sheets[sheetName]['index']] = sheetName;
  55. }
  56. let sheetsCount = Object.keys(sheets).length;
  57. for(let i = 0; i < sheetsCount; i++){
  58. sheetNames.push(indexMapping[i]);
  59. }
  60. let sheetArea = $('#uploadSheets'),
  61. sheetHeader = $('#uploadSheetsHead');
  62. $('#uploadSheets').height('');
  63. sheetArea.empty();
  64. for(let sheetName of sheetNames){
  65. let sheetDiv = $(`<div style="margin-left: 5px;margin-top: 5px;" title="${sheetName}" class="input-group form-check"><label class="form-check-label" style="width:270px; overflow: hidden; white-space: nowrap; text-overflow: ellipsis">
  66. <input class="form-check-input" type="checkbox">${sheetName}</label></div>`);
  67. sheetDiv.find('input[type="checkbox"]').click(function () {
  68. if($('#uploadAlert').is(':visible')){
  69. $('#uploadAlert').hide();
  70. }
  71. });
  72. let sel = $(`<select style="margin-left: 5px; border-radius: .20rem;"><option value="fbfx">分部分项工程</option><option value="zzcsxm">施工组织措施项目</option><option value="jscsxm">施工技术措施项目</option></select>`);
  73. if(sheetName.includes('分部分项工程项目清单计价表')){
  74. sheetDiv.find('input[type="checkbox"]').prop('checked', true);
  75. sel.find('option:eq(0)').prop('selected', true);
  76. }
  77. else if(sheetName.includes('施工组织措施项目清单计价表')){
  78. sheetDiv.find('input[type="checkbox"]').prop('checked', true);
  79. sel.find('option:eq(1)').prop('selected', true);
  80. }
  81. else if(sheetName.includes('施工技术措施项目清单计价表')){
  82. sheetDiv.find('input[type="checkbox"]').prop('checked', true);
  83. sel.find('option:eq(2)').prop('selected', true);
  84. }
  85. sheetDiv.append(sel);
  86. sheetArea.append(sheetDiv);
  87. }
  88. if(sheetNames.length > 0){
  89. sheetArea.show();
  90. sheetHeader.show();
  91. }
  92. if($('#uploadSheets').height() > 250){
  93. sheetArea.css('overflow', 'auto');
  94. sheetArea.height(250);
  95. }
  96. else {
  97. sheetArea.css('overflow', 'hidden');
  98. }
  99. }
  100. //获得选择导入的表信息(表索引及导入位置)
  101. //@return {Object}
  102. function getImportSheetsInfo(){
  103. let rst = [];
  104. let sheetArea = $('#uploadSheets');
  105. let checkedInputs = sheetArea.find('input:checked');
  106. for(let checked of checkedInputs){
  107. rst.push({index: $(checked).parent().parent().index(), position: $(checked).parent().next().select().val()})
  108. }
  109. return rst;
  110. }
  111. function getSheetByIndex(sheets, index){
  112. for(let sheetName in sheets){
  113. let sheet = sheets[sheetName];
  114. if(sheet.index === index){
  115. return sheet;
  116. }
  117. }
  118. return null;
  119. }
  120. //提取excel表头列名与列下标映射
  121. //@
  122. function getColMapping(dataTable){
  123. //获取表头
  124. function getHeadRow(dataTable){
  125. for(let rowIdx in dataTable ){
  126. for(let colIdx in dataTable[rowIdx]){
  127. let cellData = dataTable[rowIdx][colIdx]['value'];
  128. if(cellData && _deESC(cellData) === colText.serialNo[0]){
  129. return dataTable[rowIdx];
  130. }
  131. }
  132. }
  133. return {};
  134. }
  135. //获取需要的表头列与列号对应关系
  136. let colMapping = {};
  137. let headRow = getHeadRow(dataTable);
  138. for(let colIdx in headRow){
  139. let cellData = headRow[colIdx]['value'];
  140. if(!_isDef(cellData)){
  141. continue;
  142. }
  143. //序号
  144. if(colMapping.serialNo === undefined && _deESC(cellData) === colText.serialNo[0]){
  145. colMapping.serialNo = colIdx;
  146. }
  147. //编码
  148. else if(colMapping.code === undefined && (_deESC(cellData) === colText.code[0] || _deESC(cellData) === colText.code[1])){
  149. colMapping.code = colIdx;
  150. }
  151. //名称
  152. else if(colMapping.name === undefined && (_deESC(cellData) === colText.name[0] || _deESC(cellData) === colText.name[1])){
  153. colMapping.name = colIdx;
  154. }
  155. //项目特征
  156. else if(colMapping.itemCharacterText === undefined && (_deESC(cellData) === colText.itemCharacterText[0] || _deESC(cellData) === colText.itemCharacterText[1])){
  157. colMapping.itemCharacterText = colIdx;
  158. }
  159. //单位
  160. else if(colMapping.unit === undefined && (_deESC(cellData) === colText.unit[0] || _deESC(cellData) === colText.unit[1])){
  161. colMapping.unit = colIdx;
  162. }
  163. //工程量
  164. else if(colMapping.quantity === undefined && (_deESC(cellData) === colText.quantity[0] || _deESC(cellData) === colText.quantity[1])){
  165. colMapping.quantity = colIdx;
  166. }
  167. //金额
  168. else if(colMapping.money === undefined && _deESC(cellData).includes(colText.money[0])){
  169. colMapping.money = colIdx;
  170. }
  171. //工程量明细
  172. else if(colMapping.quantityDetail === undefined && _deESC(cellData) === colText.quantityDetail[0]){
  173. colMapping.quantityDetail = colIdx;
  174. }
  175. //费用明细
  176. else if(colMapping.feeDetail === undefined && _deESC(cellData) === colText.feeDetail[0]){
  177. colMapping.feeDetail = colIdx;
  178. }
  179. }
  180. return colMapping;
  181. }
  182. //是否是有效的表头列格式,只要含有各表需要的列就行,不严格控制多少列
  183. function isValidSheet(colMapping, fileType){
  184. function hasField(field, all){
  185. for(let i of all){
  186. if(field === i){
  187. return true;
  188. }
  189. }
  190. return false;
  191. }
  192. let needFields;
  193. if(fileType === uploadType.lj){
  194. //09表:序号、项目编码、项目名称、项目特征、计量单位、工程量、金额
  195. needFields = ['serialNo', 'code', 'name', 'money'];
  196. }
  197. else {
  198. //广联达表:序号、项目编码、项目名称、项目特征、计量单位、工程量、工程量明细、费用明细
  199. needFields = ['serialNo', 'code', 'name', 'itemCharacterText', 'unit', 'quantity', 'quantityDetail', 'feeDetail'];
  200. }
  201. let hasFieldCount = 0;
  202. for(let attr in colMapping){
  203. if(hasField(attr, needFields)){
  204. hasFieldCount++;
  205. }
  206. }
  207. return hasFieldCount === needFields.length;
  208. }
  209. //获取要无效和有效导入表
  210. //@param {Array}importSheetInfo 勾选要导入的表
  211. function getImportSheets(sheets, sheetsInfo, fileType){
  212. let rst = {invalidSheets: [], validSheets: {fbfx: [], jscsxm: [], zzcsxm: []}};
  213. for(let sheetInfo of sheetsInfo){
  214. let sheet = getSheetByIndex(sheets, sheetInfo.index);
  215. if(!sheet){
  216. continue;
  217. }
  218. //没有数据
  219. if(!sheet.data.dataTable){
  220. rst.invalidSheets.push(sheet.name);
  221. continue;
  222. }
  223. //获取表的列设置确定导入的格式是否合法(09、广联达)
  224. let colMapping = getColMapping(sheet.data.dataTable);
  225. if(!isValidSheet(colMapping, fileType)){
  226. rst.invalidSheets.push(sheet.name);
  227. continue;
  228. }
  229. //合法的表
  230. sheet.colMapping = colMapping;
  231. //将合法的表按导入位置分类当做一个表来处理
  232. if(rst.validSheets[sheetInfo.position] !== undefined){
  233. rst.validSheets[sheetInfo.position].push(sheet)
  234. }
  235. }
  236. return rst;
  237. }
  238. //行存在数据
  239. function rowExistData(rowData){
  240. for(let colIdx in rowData){
  241. let colData = rowData[colIdx]['value'];
  242. if(_isDef(colData)){
  243. return true;
  244. }
  245. }
  246. return false;
  247. }
  248. //提取excel表数据中的有效数据(去表头表尾,提取其中的excel数据)(根据fixedBill获取栏头占行数)
  249. function getValidImportData(colMapping, sheetData){
  250. let withingD = false;
  251. let validData = [];
  252. function isHead(rData){
  253. return rData[colMapping.serialNo] && _deESC(rData[colMapping.serialNo]['value']) === colText.serialNo[0];
  254. }
  255. function isTail(rowData){
  256. for(let colIdx in rowData){
  257. let colData = rowData[colIdx]['value'];
  258. if(colData){
  259. let trimColData= _deESC(colData);
  260. if(trimColData === colText.summation[0] || trimColData === colText.summation[1]){
  261. return true;
  262. }
  263. }
  264. }
  265. return false;
  266. }
  267. for(let rowIdx in sheetData){
  268. let rowData = sheetData[rowIdx];
  269. if(isHead(rowData)){
  270. withingD = true;
  271. continue;
  272. }
  273. else if(isTail(rowData)){
  274. withingD = false;
  275. }
  276. if(withingD && rowExistData(rowData)){
  277. validData.push(rowData);
  278. }
  279. }
  280. return validData;
  281. }
  282. //excel数据转换成清单数据
  283. function parseToBillData(validData, colMapping, flag, projectID){
  284. let rst = [];
  285. let billIdx = {};
  286. let preRootID = -1,
  287. preLeafID = -1,
  288. preID = -1;
  289. //父节点:1.无序号 2有编码
  290. function isRoot(rData){
  291. //序号和编码去除转义字符(有的表格单元格看起来是没数据,实际含有\r,\n等数据)
  292. let serialNo = rData[colMapping.serialNo] ? _deESC(rData[colMapping.serialNo]['value']) : '';
  293. let code = rData[colMapping.code] ? _deESC(rData[colMapping.code]['value']) : '';
  294. return !_isDef(serialNo) && _isDef(code);
  295. }
  296. //子节点:有序号
  297. function isLeaf(rData){
  298. let serialNo = rData[colMapping.serialNo] ? _deESC(rData[colMapping.serialNo]['value']) : '';
  299. return _isDef(serialNo);
  300. }
  301. //续数据:1. 前数据有效 2.无序号 3.无编码 4.有名称或特征
  302. function isExtend(preData, rData){
  303. let serialNo = rData[colMapping.serialNo] ? _deESC(rData[colMapping.serialNo]['value']) : '';
  304. let code = rData[colMapping.code] ? _deESC(rData[colMapping.code]['value']) : '';
  305. let name = rData[colMapping.name] ? _deESC(rData[colMapping.name]['value']) : '';
  306. let itemCharacterText = rData[colMapping.itemCharacterText] ? _deESC(rData[colMapping.itemCharacterText]['value']) : '';
  307. return _isDef(preData) && (isRoot(preData) || isLeaf(preData)) && !_isDef(serialNo) && !_isDef(code) && (_isDef(name) || _isDef(itemCharacterText));
  308. }
  309. function getBillType(rData, flag){
  310. if(flag === fixedFlag.CONSTRUCTION_TECH || flag === fixedFlag.CONSTRUCTION_ORGANIZATION){
  311. return billType.BILL;
  312. }
  313. else if(flag === fixedFlag.SUB_ENGINERRING){
  314. return isLeaf(rData) ? billType.FX : billType.FB;
  315. }
  316. return null;
  317. }
  318. let preData = null;
  319. for(let r = 0; r < validData.length; r++){
  320. let rData = validData[r];
  321. if(flag == fixedFlag.CONSTRUCTION_TECH && rData[colMapping.name] && rData[colMapping.name]['value'] === '施工技术措施项目'
  322. || flag == fixedFlag.CONSTRUCTION_ORGANIZATION && rData[colMapping.name] && rData[colMapping.name]['value'] === '施工组织措施项目'){
  323. continue;
  324. }
  325. //过滤无效数据
  326. if(!isRoot(rData) && !isLeaf(rData) && !isExtend(preData, rData)){
  327. continue;
  328. }
  329. if(isExtend(preData, rData)){
  330. let preBill = billIdx[preID];
  331. if(preBill){
  332. //合并续数据
  333. preBill.code += rData[colMapping.code] && rData[colMapping.code]['value'] && _isDef(_deESC(rData[colMapping.code]['value'])) ? rData[colMapping.code]['value'] : '';
  334. preBill.name += rData[colMapping.name] && rData[colMapping.name]['value'] && _isDef(_deESC(rData[colMapping.name]['value'])) ? rData[colMapping.name]['value'] : '';
  335. preBill.itemCharacterText += rData[colMapping.itemCharacterText] && rData[colMapping.itemCharacterText]['value'] && _isDef(_deESC(rData[colMapping.itemCharacterText]['value']))
  336. ? '\n' + _deNR(rData[colMapping.itemCharacterText]['value']) : '';
  337. preBill.unit += rData[colMapping.unit] && rData[colMapping.unit]['value'] && _isDef(_deESC(rData[colMapping.unit]['value'])) ? rData[colMapping.unit]['value'] : '';
  338. preBill.quantity += rData[colMapping.quantity] && rData[colMapping.quantity]['value'] && _isDef(_deESC(rData[colMapping.quantity]['value'])) ? rData[colMapping.quantity]['value'] : '';
  339. }
  340. }
  341. else {
  342. let newID = uuid.v1();
  343. let pID = -1;
  344. let preBill = null;
  345. let preRoot = null,
  346. preLeaf = null;
  347. let nodeType = 'root';//后端以此标记来设置ParentID
  348. let preSerialBill = billIdx[preID];
  349. if(isRoot(rData)){
  350. //pID = 'fixedBillID';
  351. preBill = billIdx[preRootID];
  352. preRoot = billIdx[preRootID];
  353. }
  354. else if(isLeaf(rData)){
  355. nodeType = 'leaf';
  356. //pID = preRootID !== -1 ? preRootID : fixedBill.ID;
  357. preBill = billIdx[preLeafID];
  358. preLeaf = billIdx[preLeafID];
  359. }
  360. //set bill data
  361. billIdx[newID] = {
  362. nodeType: nodeType,
  363. ID: newID, ParentID: pID, NextSiblingID: -1,
  364. code: rData[colMapping.code] && rData[colMapping.code]['value'] ? _deESC(rData[colMapping.code]['value']) : '',
  365. name: rData[colMapping.name] && rData[colMapping.name]['value'] ? _deESC(rData[colMapping.name]['value']) : '',
  366. itemCharacterText: rData[colMapping.itemCharacterText] && rData[colMapping.itemCharacterText]['value'] ? _deNR(rData[colMapping.itemCharacterText]['value']) : '',
  367. itemCharacter: [],
  368. jobContentText: '',
  369. jobContent: [],
  370. programID: null,
  371. unit: rData[colMapping.unit] && rData[colMapping.unit]['value'] ? _deESC(rData[colMapping.unit]['value']) : '',
  372. quantity: rData[colMapping.quantity] && rData[colMapping.quantity]['value'] ? _deESC(rData[colMapping.quantity]['value']) : '',
  373. quantityEXP: rData[colMapping.quantity] && rData[colMapping.quantity]['value'] ? _deESC(rData[colMapping.quantity]['value']) : '',
  374. //安全文明
  375. flags: flag === fixedFlag.CONSTRUCTION_ORGANIZATION && (rData[colMapping.name] && (rData[colMapping.name]['value'] === '安全文明施工专项费用' || rData[colMapping.name]['value'] === '安全文明施工费')) ?
  376. [{fieldName: 'fixed', flag: fixedFlag.SAFETY_CONSTRUCTION}] : [],
  377. fees: [],
  378. projectID: projectID,
  379. type: getBillType(rData, flag)};
  380. //update preBill NextSibling
  381. if(nodeType === 'root' && preRoot){
  382. preRoot.NextSiblingID = newID;
  383. }
  384. else if(nodeType === 'leaf' && preLeaf && preSerialBill && preSerialBill.nodeType === preLeaf.nodeType){
  385. preLeaf.NextSiblingID = newID;
  386. }
  387. /* if(preBill){
  388. preBill.NextSiblingID = newID;
  389. }*/
  390. //set new preID
  391. preID = newID;
  392. preRootID = isRoot(rData) ? newID : preRootID;
  393. preLeafID = isLeaf(rData) ? newID : preLeafID;
  394. }
  395. preData = rData;
  396. }
  397. for(let i in billIdx){
  398. rst.push(billIdx[i]);
  399. }
  400. return rst;
  401. }
  402. function getImportData(validSheets, projectID){
  403. let rst = {fbfx: [], jscsxm: [], zzcsxm: []};
  404. let validSheetsDatas = [];
  405. for(let uploadPosition in validSheets){
  406. let validExcelData = [];
  407. for(let uSheet of validSheets[uploadPosition]){
  408. validExcelData = validExcelData.concat(getValidImportData(uSheet.colMapping, uSheet.data.dataTable));
  409. }
  410. if(validSheets[uploadPosition].length > 0){
  411. validSheetsDatas.push({position: uploadPosition, colMapping: validSheets[uploadPosition][0].colMapping, validExcelData: validExcelData});
  412. }
  413. }
  414. for(let validSheetData of validSheetsDatas){
  415. if(validSheetData.validExcelData.length > 0){
  416. rst[validSheetData.position] = parseToBillData(validSheetData.validExcelData, validSheetData.colMapping, positionFlag[validSheetData.position], projectID);
  417. }
  418. }
  419. return rst;
  420. }
  421. function excelHasValidBills(importBillsData){
  422. for(let i in importBillsData){
  423. if(importBillsData[i].length > 0){
  424. return true;
  425. }
  426. }
  427. return false;
  428. }
  429. return {setImportSheetsInfo, getImportSheetsInfo, getImportSheets, getImportData, excelHasValidBills}
  430. })();