importBills.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  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. //find 返回最后匹配
  25. function findLast(datas, func) {
  26. let filter = datas.filter(func);
  27. if (filter.length > 0) {
  28. return filter[filter.length - 1];
  29. }
  30. return null;
  31. }
  32. const fileType = {
  33. gcl: 0, //工程量清单
  34. qdsl: 1 //清单示例
  35. };
  36. //获取列字段对应
  37. function getColMapping(type) {
  38. if (type === 0) { //工程量清单
  39. return { code: 0, name: 1, unit: 2, quantity: 4 };
  40. } else { //清单示例表
  41. return { code: 0, name: 1, unit: 2, quantity: 3 };
  42. }
  43. }
  44. function isGCLHead(dataRow) {
  45. let cell = dataRow[0];
  46. return cell && cell.value === '工程量清单';
  47. }
  48. //分析文件,1、工程量清单 2、清单示例表
  49. function getFileType(sheetData) {
  50. let dataTable = sheetData.data.dataTable,
  51. rowCount = sheetData.rowCount;
  52. for (let row = 0; row < rowCount; row++) {
  53. if (isGCLHead(dataTable[row])) {
  54. return fileType.gcl;
  55. }
  56. }
  57. return fileType.qdsl;
  58. }
  59. //提取工程量清单数据
  60. //层级由depth确定,表格里最顶层depth为0(表头里一清单),表格内容里数据的depth为空格数+1
  61. function extractGCLDatas(sheetData, colMapping) {
  62. //let colMapping = {code: 0, name: 1, unit: 2, quantity: 4};
  63. let dataTable = sheetData.data.dataTable,
  64. rowCount = sheetData.rowCount;
  65. let rst = [];
  66. for (let row = 0; row < rowCount; row++) {
  67. //表格中顶层节点
  68. if (isGCLHead(dataTable[row])) {
  69. let rootRow = dataTable[row + 2];
  70. let name = rootRow[0].value ? _deESC(rootRow[0].value) : '';
  71. let existsRoot = findLast(rst, x => x.name === name && x.depth === 0);
  72. if (!existsRoot) {
  73. let root = {
  74. ID: uuid.v1(),
  75. NextSiblingID: -1,
  76. ParentID: -1,
  77. name: name,
  78. depth: 0,
  79. parent: null,
  80. unitPriceAnalysis: 1
  81. };
  82. let preData = findLast(rst, x => x.depth === root.depth);
  83. if (preData) {
  84. preData.NextSiblingID = root.ID;
  85. }
  86. rst.push(root);
  87. }
  88. row += 3;
  89. continue;
  90. }
  91. let code = dataTable[row][colMapping.code] ? dataTable[row][colMapping.code].value : null,
  92. name = dataTable[row][colMapping.name] ? _deESC(dataTable[row][colMapping.name].value) : null,
  93. unit = dataTable[row][colMapping.unit] ? dataTable[row][colMapping.unit].value : null,
  94. quantity = dataTable[row][colMapping.quantity] ? dataTable[row][colMapping.quantity].value : null;
  95. if (!code && !name || /合计/.test(code)) { //过滤掉同时没有编号和名称的、过滤合计行
  96. continue;
  97. }
  98. // “子目号”、“单位”、“数量”都为空,“子目名称”不为空时,应将此行清单名称合并到上一行
  99. let lastData = rst[rst.length - 1];
  100. if (!code && !unit && !quantity && name) {
  101. lastData.name += name;
  102. continue;
  103. }
  104. //表格内的数据
  105. code = String(code);
  106. let depth = getDepth(code);
  107. let data = {
  108. ID: uuid.v1(),
  109. NextSiblingID: -1,
  110. ParentID: -1,
  111. code: code,
  112. name: name,
  113. unit: unit,
  114. quantity: quantity,
  115. depth: depth,
  116. unitPriceAnalysis: 1,
  117. };
  118. //获取data的父节点链,成为兄弟节点,只能在父链里找前兄弟(不能跨父链)
  119. let parents = getParents(lastData);
  120. let preData = findLast(parents, x => x.depth === depth);
  121. if (preData) {
  122. preData.NextSiblingID = data.ID;
  123. data.ParentID = preData.ParentID;
  124. data.parent = preData.parent;
  125. } else {
  126. data.ParentID = lastData.ID;
  127. data.parent = lastData;
  128. }
  129. rst.push(data);
  130. }
  131. console.log(rst);
  132. return rst;
  133. function getDepth(code) {
  134. if (!code) {
  135. return 1;
  136. }
  137. let match = code.match(/\s/g);
  138. return match ? match.length + 1 : 1;
  139. }
  140. }
  141. function getParents(data) {
  142. let rst = [];
  143. let parent = data.parent;
  144. while (parent) {
  145. rst.push(parent);
  146. parent = parent.parent;
  147. }
  148. rst.push(data);
  149. return rst;
  150. }
  151. //获取编号前缀: 101-1 => 101 101-1-1 => 101-1
  152. function getPrefix(v) {
  153. if (!v) {
  154. return null;
  155. }
  156. let reg = /(.*)-/;
  157. let match = reg.exec(v);
  158. return match ? match[1] : null;
  159. }
  160. //提取清单示例数据
  161. function extractSLDatas(sheetData) {
  162. let colMapping = { code: 0, name: 1, unit: 2, quantity: 3 };
  163. let dataTable = sheetData.data.dataTable,
  164. rowCount = sheetData.rowCount;
  165. let rst = [];
  166. let curRoot = null;
  167. for (let row = 0; row < rowCount; row++) {
  168. let code = dataTable[row][colMapping.code] && dataTable[row][colMapping.code].value ? String(dataTable[row][colMapping.code].value).trim() : null,
  169. name = dataTable[row][colMapping.name] ? _deESC(dataTable[row][colMapping.name].value) : null,
  170. unit = dataTable[row][colMapping.unit] ? dataTable[row][colMapping.unit].value : null,
  171. quantity = dataTable[row][colMapping.quantity] ? dataTable[row][colMapping.quantity].value : null;
  172. if (!code) { //没有编号的数据,名称必须为:清单 第xx章,认为新的表根节点
  173. const reg = /清单\s+第[^章]+章/;
  174. //if (name && /清单 第\d+章/.test(name)) {
  175. if (name && reg.test(name)) {
  176. curRoot = {
  177. code: null,
  178. name: name,
  179. ID: uuid.v1(),
  180. ParentID: -1,
  181. NextSiblingID: -1,
  182. parent: null,
  183. unitPriceAnalysis: 1
  184. };
  185. rst.push(curRoot);
  186. } else {
  187. curRoot = null;
  188. }
  189. } else if (!curRoot) { //根节点为无效根节点,其下子数据全部过滤掉
  190. continue;
  191. } else {
  192. //有code且有有效表根节点
  193. let prefix = getPrefix(code);
  194. let data = {
  195. code: code,
  196. name: name,
  197. unit: unit,
  198. quantity: quantity,
  199. ID: uuid.v1(),
  200. NextSiblingID: -1,
  201. unitPriceAnalysis: 1
  202. };
  203. let lastData = rst[rst.length - 1];
  204. let parents = getParents(lastData);
  205. //某数据编号为此数据的前缀,则某数据为此数据的父节点
  206. let parentData = findLast(parents, x => prefix === x.code);
  207. if (!parentData && prefix === '') { // -x的数据,在父链上找不到编号与prefix相同的数据时,父链上-x的数据,则这两数据为兄弟节点,没有则上一行数据为其父节点
  208. let samePrefixData = findLast(parents, x => getPrefix(x.code) === prefix);
  209. parentData = samePrefixData ? samePrefixData.parent : lastData;
  210. } else if (!parentData && prefix !== '') { //不是-x的数据,在父链上找不到编号与prefix相同的数据时,表根节点为其父节点
  211. parentData = curRoot;
  212. }
  213. data.ParentID = parentData.ID;
  214. data.parent = parentData;
  215. let preData = findLast(parents, x => x.ParentID === data.ParentID);
  216. if (preData) {
  217. preData.NextSiblingID = data.ID;
  218. }
  219. rst.push(data);
  220. }
  221. }
  222. console.log(rst);
  223. return rst;
  224. }
  225. function extactDatas(sheets) {
  226. let rst = [];
  227. let curSheetType = null;
  228. for (let sheetName in sheets) {
  229. let sheetData = sheets[sheetName];
  230. let sheetType = getFileType(sheetData);
  231. if (curSheetType !== null && sheetType !== curSheetType) {
  232. throw 'excel文件中存在不同格式的表格。';
  233. }
  234. curSheetType = sheetType;
  235. let colMapping = getColMapping(sheetType);
  236. let datas = sheetType === fileType.gcl ? extractGCLDatas(sheetData, colMapping) : extractSLDatas(sheetData, colMapping);
  237. rst = rst.concat(datas);
  238. }
  239. //编号去除空格 清除多余数据 设置数据
  240. for (let data of rst) {
  241. if (data.code && typeof data.code === 'string') {
  242. data.code = data.code.replace(/\s/g, '');
  243. }
  244. if (data.unit === '㎡') {
  245. data.unit = 'm2';
  246. } else if (data.unit === 'm³') {
  247. data.unit = 'm3';
  248. }
  249. data.projectID = projectObj.project.ID();
  250. data.type = billType.BILL;
  251. delete data.parent;
  252. delete data.depth;
  253. }
  254. //将表根节点的ParentID设置成第100章至700章清单的ID
  255. let fixedBill = projectObj.project.Bills.tree.roots.find(node => node.data &&
  256. node.data.flagsIndex && node.data.flagsIndex.fixed && node.data.flagsIndex.fixed.flag === fixedFlag.ONE_SEVEN_BILLS);
  257. let rootDatas = rst.filter(data => data.ParentID === -1);
  258. for (let root of rootDatas) {
  259. root.ParentID = fixedBill.data.ID;
  260. }
  261. //清单 第100章 总则清单需要加上固定ID
  262. let oneHundredBills = rootDatas.find(data => data.name && /第100章/.test(data.name));
  263. if (oneHundredBills) {
  264. oneHundredBills.flags = [{ flag: fixedFlag.ONE_HUNDRED_BILLS, fieldName: 'fixed' }];
  265. }
  266. return rst;
  267. }
  268. return { extactDatas }
  269. })();