transfer.js 49 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172
  1. 'use strict';
  2. /**
  3. *
  4. *
  5. * @author Zhong
  6. * @date 2019/2/21
  7. * @version
  8. */
  9. const TransferExcel = (function () {
  10. let curTransferType = 'gl';
  11. const transferType = {
  12. // 公路
  13. gl: 'gl',
  14. // 内蒙养护
  15. neimengyh: 'neimengyh',
  16. // 建筑
  17. jz: 'jz'
  18. };
  19. const $transferModal = $('#transfer');
  20. const $file = $('#transfer-file');
  21. const $transfer = $('#transferConfirm');
  22. const $exportSpread = $('#exportSpread');
  23. let exportSpread = null;
  24. let transferData = [];
  25. //--以防需要可配置的情况
  26. let fixedCount = 4; //固定列数(顺序号、项目、单位、代号)
  27. const beginCol = 0;//起始的列
  28. //固定的这几列的列号映射是动态的,因为有一些子表,这些列进行了合并列的处理
  29. let colMapping = {
  30. serialNo: 0,
  31. name: 1,
  32. unit: 2,
  33. code: 3,
  34. consumeAmt: 4, //消耗量开始的列
  35. };
  36. //---
  37. function isUnDef(v) {
  38. return typeof v === 'undefined' || v === null;
  39. }
  40. //取消所有空格
  41. function trimAll(v) {
  42. return typeof v === 'string' ? v.replace(/\s/g, '') : v;
  43. }
  44. //单元格是否有数据
  45. function cellHasData(cell) {
  46. return cell && cell.value;
  47. }
  48. //行是否有数据
  49. //@param {Object}rowData(行数据) {Array}deduct(排除的列)
  50. function rowHasData(rowData, deduct = null) {
  51. for (let col in rowData) {
  52. if (deduct && deduct.includes(col)) {
  53. continue;
  54. }
  55. let cell = rowData[col];
  56. if (cell && cell.value) {
  57. return true;
  58. }
  59. }
  60. return false;
  61. }
  62. //行为列头行 //第一列为数值,且后面其他列有数据(不单纯认为是第二列,因为怕有合并序号列)
  63. function headerRow(rowData) {
  64. return rowData[0] && rowData[0].value && /^\d+$/.test(rowData[0].value) && rowHasData(rowData, ['0']);
  65. }
  66. //去除定额子表数组中的空行(有的表格是在 单位:xx 下面还跟着一些垃圾数据,导致subRation的range范围判定变大了,但是四列后面的数据是空的)
  67. function simplifyRationTable(arr) {
  68. let target = [];
  69. for (let subArr of arr) {
  70. let emptyRow = subArr.every(function (ele) {
  71. return ele === null;
  72. });
  73. if (emptyRow) {
  74. continue;
  75. }
  76. target.push(subArr);
  77. }
  78. return target;
  79. }
  80. //将二维数组转换成每个元素都有值的二维数据(去没值元素,因为前四列有可能合并列)
  81. function simplifyGljTable(arr) {
  82. let target = [];
  83. for (let subArr of arr) {
  84. let subTarget = [];
  85. for (let ele of subArr) {
  86. if (ele !== null) {
  87. subTarget.push(ele);
  88. }
  89. }
  90. target.push(subTarget);
  91. }
  92. return target;
  93. }
  94. //获取表头定额名称数据(最后一行为定额末位编码)
  95. //合并的单元格处理:为了更好的让定额获得对应的定额名称,将合并单元格做填值处理, eg: [a]['合并'] = [a][a]
  96. //@param {Object}dataTable {Object}range(表头的范围,row, col, rowCount, colCount) {Array}spans(spread解析Excel后的合并数组)
  97. //@return {Array}
  98. function getSubRationTable(dataTable, range, spans) {
  99. let subTable = [];
  100. for (let i = 0; i < range.rowCount; i++) {
  101. subTable.push(Array(range.colCount).fill(null));
  102. }
  103. //获取合并的单元格填充数据
  104. let fillArr = [];
  105. for (let i = 0; i < range.rowCount; i++) {
  106. let row = range.row + i;
  107. if (!dataTable[row]) {
  108. continue;
  109. }
  110. for (let j = 0; j < range.colCount; j++) {
  111. let col = range.col + j;
  112. let cell = dataTable[row][col];
  113. //只有有值的单元格,判断合并才有意义,有值且合并列的单元格,每列的值认为都是一样的
  114. if (cellHasData(cell)) {
  115. //是否合并了单元格
  116. let span = spans.find(function (data) {
  117. return data.row === row && data.col === col;
  118. });
  119. //这个fillData给subData填值用
  120. let fillData = {value: trimAll(cell.value), range: {row: i, col: j}};
  121. if (span) {
  122. fillData.range.rowCount = span.rowCount;
  123. fillData.range.colCount = span.colCount;
  124. } else {
  125. fillData.range.rowCount = 1;
  126. fillData.range.colCount = 1;
  127. }
  128. fillArr.push(fillData);
  129. }
  130. }
  131. }
  132. //将数据填充到subData中
  133. for (let fillData of fillArr) {
  134. //合并行不需要向下填值(否则会重复)
  135. let row = fillData.range.row;
  136. //合并列需要向右填值
  137. for (let j = 0; j < fillData.range.colCount; j++) {
  138. let col = fillData.range.col + j;
  139. subTable[row][col] = trimAll(fillData.value);
  140. }
  141. }
  142. return simplifyRationTable(subTable);
  143. }
  144. //获取工料机子表数据(序号至最末消耗量)
  145. //@param {Object}dataTable {Object}range(工料机子表的范围,row, col, rowCount, colCount)
  146. //@return {Array}
  147. function getSubGljTable(dataTable, range) {
  148. let gljTable = [];
  149. for (let i = 0; i < range.rowCount; i++) {
  150. gljTable.push(Array(range.colCount).fill(null));
  151. }
  152. // 消耗量列
  153. let consumeAmtCol = colMapping.consumeAmt;
  154. for (let i = 0; i < range.rowCount; i++) {
  155. let row = range.row + i;
  156. if (!dataTable[row]) {
  157. continue;
  158. }
  159. for (let j = 0; j < range.colCount; j++) {
  160. let col = range.col + j;
  161. let cell = dataTable[row][col];
  162. if (cellHasData(cell)) {
  163. gljTable[i][j] = cell.value;
  164. } else if (col >= consumeAmtCol) {
  165. // 消耗量单位格,没值则认为是0
  166. gljTable[i][j] = 0;
  167. }
  168. }
  169. }
  170. return gljTable;
  171. }
  172. /*
  173. * 从原本的excel中提取数据
  174. * 需要的数据结构:eg: [{code: '1-1-1', name: '伐树', unit: '表列单位', subTable: [{subGlj: [], subRation: []}]}]
  175. * subTable为定额数据对应的子表数据,其中有定额名称子表、工料机子表
  176. * */
  177. function extractDataFromExcel(sheetData) {
  178. // 识别定额行的表达式
  179. //[\-,—,一] 这里是因为pdf转出来的excel(需要转换的excel)会吧"-"显示成各种奇怪的横杆
  180. //第一数值限制在3位以内,防止有: 2019-05-01等日期干扰
  181. // x-x-x xx
  182. let rationCodeReg = /^(\d{1,3}[\-,_,—,一]{1}\d+[\-,_,—,一]{1}\d+)(\w{0,}[\u4e00-\u9fa5]{1,})/;
  183. let dataTable = sheetData.data.dataTable,
  184. spans = sheetData.spans;
  185. //行数据是定额行 eg: 1-1-1 人工挖土方
  186. //@param {Number}row
  187. //@return {Object || Null} eg: {code: '1-1-1', name: '人工挖土方'}
  188. function rationRow(row) {
  189. let cell = dataTable[row][beginCol];
  190. if (!cell || !cell.value || typeof cell.value !== 'string') {
  191. return false;
  192. }
  193. let v = trimAll(cell.value);
  194. let match = rationCodeReg.exec(v);
  195. if (match && match.length === 3 && match[0] && match[1] && match[2]) {
  196. return {code: match[1].replace(/[_,—,一]/g, '-'), name: match[2]};
  197. }
  198. return null;
  199. }
  200. //单位数据行 eg: 单位:表列单位
  201. //@return {String || Null} eg: '表列单位'
  202. function unitRow(row) {
  203. let cell = dataTable[row][beginCol];
  204. if (!cell || !cell.value || typeof cell.value !== 'string') {
  205. return false;
  206. }
  207. let v = trimAll(cell.value);
  208. let reg = /单位[\:, :]([\w+, \u4e00-\u9fa5]{0,})/;
  209. let match = reg.exec(v);
  210. if (match && match.length === 2 && match[0] && match[1]) {
  211. return match[1];
  212. }
  213. return null;
  214. }
  215. //行数据是人材机数据行
  216. //表头后,某行第一列为数值,第二列有值,则该行为紧接着表头的人材机数据行
  217. //@return {Boolean}
  218. function rowIsGlj(rowData) {
  219. let numberCell = rowData[colMapping.serialNo],
  220. valueCell = rowData[colMapping.name];
  221. return numberCell && numberCell.value && /^\d+$/.test(numberCell.value) && valueCell && Boolean(valueCell.value);
  222. }
  223. //连续有数据的列数(每个子表格中工料机的数据列数)
  224. //由于序号、项目、单位、代号可能存在合并列,因此从消耗量列(消耗量列不会合并列,消耗量对应的列号已在getColMapping获取)开始统计
  225. function getDataCount(row) {
  226. let consumeAmtCol = colMapping.consumeAmt;
  227. for (let col = consumeAmtCol; col < sheetData.columnCount; col++) {
  228. let cell = dataTable[row][col];
  229. if (!cell || !cell.value) {
  230. return col;
  231. }
  232. }
  233. return sheetData.columnCount;
  234. }
  235. //获取前固定四列的动态列映射(解决这几列可能有合并列的问题)
  236. function getColMapping(rowData, colCount) {
  237. let needFields = ['serialNo', 'name', 'unit', 'code', 'consumeAmt'];
  238. function getUndefinedField(obj) {
  239. for (let field of needFields) {
  240. if (!obj.hasOwnProperty(field)) {
  241. return field;
  242. }
  243. }
  244. return null;
  245. }
  246. //假定序号列为第一列
  247. let mapping = {serialNo: 0};
  248. for (let i = 1; i < colCount; i++) {
  249. let col = beginCol + i,
  250. cell = rowData[col];
  251. //还没设置的字段(必须要有的是serialNo, name, unit, code, consumeAmt)
  252. if (cell && cell.value) {
  253. let field = getUndefinedField(mapping);
  254. if (field) {
  255. mapping[field] = col;
  256. }
  257. if (typeof mapping.consumeAmt !== 'undefined') {
  258. return mapping;
  259. }
  260. }
  261. }
  262. return null;
  263. }
  264. //获取表格的子表范围(定额名称表头范围、工料机数据范围)
  265. //遇到单位:xx行rowA后,获取紧跟其后的表格,该表格最少要有一行工料机数据行rowB,表头范围则为rowA至rowB
  266. //@param {Number}beginRow
  267. function getTableRange(beginRow) {
  268. let hasTHead = false,
  269. hasTable = false,
  270. hasMapping = false; //是否获取过固定列映射
  271. let range = {
  272. subRation: {},
  273. subGlj: {},
  274. };
  275. for (let row = beginRow; row < sheetData.rowCount; row++) {
  276. if (!dataTable[row]) {
  277. continue;
  278. }
  279. //第一个有数据的行,为表头第一行
  280. if (rowHasData(dataTable[row]) && !hasTHead) {
  281. hasTHead = true;
  282. range.subRation.row = row;
  283. }
  284. //获取当前子表的固定列映射
  285. if (hasTHead && !hasTable && !hasMapping && headerRow(dataTable[row])) {
  286. hasMapping = true;
  287. colMapping = getColMapping(dataTable[row], sheetData.columnCount);
  288. if (!colMapping) {
  289. return null;
  290. }
  291. }
  292. //第一条工料机数据行
  293. if (hasTHead && !hasTable && colMapping && rowIsGlj(dataTable[row])) {
  294. hasTable = true;
  295. range.subGlj.row = row;
  296. range.subGlj.col = 0;
  297. range.subRation.col = colMapping.consumeAmt;
  298. range.subRation.rowCount = range.subGlj.row - range.subRation.row;
  299. range.subGlj.colCount = getDataCount(row);
  300. range.subRation.colCount = range.subGlj.colCount - colMapping.consumeAmt;
  301. }
  302. if (hasTable && !rowIsGlj(dataTable[row])) {
  303. range.subGlj.rowCount = row - range.subGlj.row;
  304. return range;
  305. }
  306. if (hasTable && (row === sheetData.rowCount - 1 || !dataTable[row + 1])) {
  307. range.subGlj.rowCount = row - range.subGlj.row + 1;
  308. return range;
  309. }
  310. }
  311. return null;
  312. }
  313. //分析整个表
  314. let extractData = [];
  315. //定额行后必须要跟着单位行
  316. let hasUnit = false;
  317. for (let row = 0; row < sheetData.rowCount; row++) {
  318. if (!dataTable[row] || !rowHasData(dataTable[row])) {
  319. continue;
  320. }
  321. let rationData = rationRow(row);
  322. if (rationData) {
  323. if (!hasUnit) {
  324. extractData.pop();
  325. }
  326. hasUnit = false;
  327. //转换数据数组的每个元素,为定额基本数据:不完整的code、不完整的name、unit、和子表门构成
  328. //subTable: [{subRation: [], subGlj: []}],subRation为每个子表表头数据(排除掉了顺序号至代号),
  329. //subGlj为每个子表工料机数据
  330. let basicData = {
  331. code: rationData.code,
  332. name: rationData.name,
  333. unit: null,
  334. subTable: []
  335. };
  336. extractData.push(basicData);
  337. }
  338. let unitData = unitRow(row);
  339. if (unitData) {
  340. hasUnit = true;
  341. let thisBasicData = extractData[extractData.length - 1];
  342. if (thisBasicData) {
  343. if (!thisBasicData.unit) {
  344. thisBasicData.unit = unitData;
  345. }
  346. //获取表格数据
  347. let range = getTableRange(row + 1);
  348. if (range) {
  349. let subRationTable = getSubRationTable(dataTable, range.subRation, spans),
  350. subGljTable = getSubGljTable(dataTable, range.subGlj);
  351. thisBasicData.subTable.push({subRation: subRationTable, subGlj: subGljTable});
  352. //跳过其中的行
  353. row = range.subGlj.row + range.subGlj.rowCount - 1;
  354. }
  355. }
  356. }
  357. }
  358. return extractData;
  359. }
  360. /*
  361. * 转换数据,将提取出来的数据转换成另外一种数据结构,便于转化Excel的结构
  362. * 需要的数据结构: eg: [{code: '1-1-1-1', name: '伐树xxx', unit: '表列单位', gljList: [{code,name,unit,comsumeAmt}]}]
  363. * */
  364. function transferDataFromExtract(extractData) {
  365. console.log(extractData);
  366. let existCodes = [];
  367. //从一个提取的数据(1定额数据及其子表数据)中获取一份转换数据
  368. function transfer(source) {
  369. //以完整定额编码为属性,因为1个定额可能会有多张子表,且完整定额编码相同,子工料机不同,方便直接添加后续的工料机
  370. let temp = {},
  371. target = [];
  372. let basicCode = source.code,
  373. basicName = source.name,
  374. basicUnit = source.unit;
  375. if (existCodes.includes(basicCode)) {
  376. return [];
  377. }
  378. existCodes.push(basicCode);
  379. //处理消耗量,可能有(3.5) 和 - 的情况, 处理:(3.5) => 3.5 - => 0
  380. function handleConsumeAmt(consumeAmt) {
  381. if (typeof consumeAmt === 'string') {
  382. consumeAmt = trimAll(consumeAmt);
  383. consumeAmt = consumeAmt.replace(/[\-,_,—,一,\(,\),(,)]/g, '');
  384. if (!consumeAmt) {
  385. return 0;
  386. }
  387. }
  388. return consumeAmt;
  389. }
  390. //从工料机子表中获取工料机数据, index为定额编码对应的下标索引
  391. function getGljList(gljTable, index) {
  392. let gljList = [];
  393. //获取的工料机对应列
  394. let gljColMapping = {
  395. name: 1,
  396. unit: 2,
  397. code: 3,
  398. consumeAmtCol: fixedCount + index
  399. };
  400. for (let rowData of gljTable) {
  401. //工料机数据必须要有名称、单位、编码
  402. if (!rowData[gljColMapping.name] ||
  403. !rowData[gljColMapping.unit] ||
  404. !rowData[gljColMapping.code]) {
  405. continue;
  406. }
  407. let consumeAmt = isUnDef(rowData[gljColMapping.consumeAmtCol]) ? 0 : handleConsumeAmt(rowData[gljColMapping.consumeAmtCol]);
  408. gljList.push({
  409. name: rowData[gljColMapping.name],
  410. unit: rowData[gljColMapping.unit],
  411. code: rowData[gljColMapping.code],
  412. consumeAmt: consumeAmt,
  413. });
  414. }
  415. return gljList;
  416. }
  417. //拼接定额工料机数据
  418. for (let table of source.subTable) {
  419. let rationTable = table.subRation;
  420. if (!rationTable || rationTable.length === 0) {
  421. continue;
  422. }
  423. let lastRationCodes = rationTable.pop(); //定额子表,最后一行是末位定额编码
  424. for (let i = 0; i < lastRationCodes.length; i++) {
  425. let lastCode = lastRationCodes[i];
  426. //拼接定额编码
  427. let compleCode = `${basicCode}-${lastCode}`,
  428. gljList = getGljList(table.subGlj, i);
  429. if (!temp[compleCode]) { //该定额不存在
  430. temp[compleCode] = {
  431. code: compleCode,
  432. unit: basicUnit,
  433. name: basicName,
  434. gljList: gljList
  435. };
  436. } else { //该定额已存在,则追加工料机
  437. temp[compleCode].gljList = temp[compleCode].gljList.concat(gljList);
  438. }
  439. //拼接定额名称
  440. for (let rationNameRow of rationTable) {
  441. if (rationNameRow[i]) {
  442. temp[compleCode].name += ` ${rationNameRow[i]}`;
  443. }
  444. }
  445. }
  446. }
  447. //将temp对象转换为数据
  448. for (let code in temp) {
  449. target.push(temp[code]);
  450. }
  451. return target;
  452. }
  453. let transferData = [];
  454. for (let data of extractData) {
  455. let unitTargetData = transfer(data);
  456. transferData = transferData.concat(unitTargetData);
  457. }
  458. console.log(transferData);
  459. return transferData;
  460. }
  461. // 提取内蒙养护数据
  462. function extractDataFromNMYH(sheetData) {
  463. // 识别定额行的表达式
  464. //固定列数(顺序号、项目、单位、代号、单价)
  465. fixedCount = 5;
  466. // x-x xx
  467. let rationCodeReg = /^(\d{1,3}[\-,_,—,一]{1}\d+)(\w{0,}[\u4e00-\u9fa5]{1,})/;
  468. let dataTable = sheetData.data.dataTable,
  469. spans = sheetData.spans;
  470. //行数据是定额行 eg: 1-1-1 人工挖土方
  471. //@param {Number}row
  472. //@return {Object || Null} eg: {code: '1-1-1', name: '人工挖土方'}
  473. function rationRow(row) {
  474. let cell = dataTable[row][beginCol];
  475. if (!cell || !cell.value || typeof cell.value !== 'string') {
  476. return false;
  477. }
  478. let v = trimAll(cell.value);
  479. let match = rationCodeReg.exec(v);
  480. if (match && match.length === 3 && match[0] && match[1] && match[2]) {
  481. return {code: match[1].replace(/[_,—,一]/g, '-'), name: match[2]};
  482. }
  483. return null;
  484. }
  485. //单位数据行 eg: 单位:表列单位
  486. //@return {String || Null} eg: '表列单位'
  487. function unitRow(row) {
  488. let cell = dataTable[row][beginCol];
  489. if (!cell || !cell.value || typeof cell.value !== 'string') {
  490. return false;
  491. }
  492. let v = trimAll(cell.value);
  493. let reg = /单位[\:, :]([\w+, \u4e00-\u9fa5]{0,})/;
  494. let match = reg.exec(v);
  495. if (match && match.length === 2 && match[0] && match[1]) {
  496. return match[1];
  497. }
  498. return null;
  499. }
  500. //行数据是人材机数据行
  501. //表头后,某行第一列为数值,第二列有值,则该行为紧接着表头的人材机数据行
  502. //@return {Boolean}
  503. function rowIsGlj(rowData) {
  504. let numberCell = rowData[colMapping.serialNo],
  505. valueCell = rowData[colMapping.name];
  506. return numberCell && numberCell.value && /^\d+$/.test(numberCell.value) && valueCell && Boolean(valueCell.value);
  507. }
  508. //获取前固定四列的动态列映射(解决这几列可能有合并列的问题)
  509. function getColMapping(rowData, colCount) {
  510. let needFields = ['serialNo', 'name', 'unit', 'code'];
  511. function getUndefinedField(obj) {
  512. for (let field of needFields) {
  513. if (!obj.hasOwnProperty(field)) {
  514. return field;
  515. }
  516. }
  517. return null;
  518. }
  519. //假定序号列为第一列
  520. let mapping = {serialNo: 0};
  521. for (let i = 1; i < colCount; i++) {
  522. let col = beginCol + i,
  523. cell = rowData[col];
  524. if (cell && cell.value) {
  525. let field = getUndefinedField(mapping);
  526. if (field) {
  527. mapping[field] = col;
  528. }
  529. if (typeof mapping.code !== 'undefined') {
  530. return mapping;
  531. }
  532. }
  533. }
  534. return null;
  535. }
  536. // 获取定额剩余编号的列号信息
  537. function getRestCodeInfo(rowData) {
  538. if (!rowData) {
  539. return;
  540. }
  541. let numReg = /^\d+$/;
  542. let col,
  543. colCount = 0;
  544. Object.values(rowData).forEach((data, index) => {
  545. let v = data.value || '';
  546. if (numReg.test(v)) {
  547. if (!colCount) {
  548. col = index;
  549. }
  550. colCount++;
  551. }
  552. });
  553. return colCount ? {col, colCount} : null;
  554. }
  555. //获取表格的子表范围(定额名称表头范围、工料机数据范围)
  556. //遇到单位:xx行rowA后,获取紧跟其后的表格,该表格最少要有一行工料机数据行rowB,表头范围则为rowA至rowB
  557. //@param {Number}beginRow
  558. function getTableRange(beginRow) {
  559. let hasTHead = false,
  560. hasTable = false,
  561. hasMapping = false; //是否获取过固定列映射
  562. let range = {
  563. subRation: {},
  564. subGlj: {},
  565. };
  566. for (let row = beginRow; row < sheetData.rowCount; row++) {
  567. if (!dataTable[row]) {
  568. continue;
  569. }
  570. //第一个有数据的行,为表头第一行
  571. if (rowHasData(dataTable[row]) && !hasTHead) {
  572. hasTHead = true;
  573. range.subRation.row = row;
  574. }
  575. //获取当前子表的固定列映射
  576. if (hasTHead && !hasTable && !hasMapping && headerRow(dataTable[row])) {
  577. hasMapping = true;
  578. colMapping = getColMapping(dataTable[row], sheetData.columnCount);
  579. if (!colMapping) {
  580. return null;
  581. }
  582. // 获取定额剩余编码所处的col、和colCount
  583. let restCodeInfo = getRestCodeInfo(dataTable[row - 1]);
  584. if (!restCodeInfo) {
  585. return null;
  586. }
  587. colMapping.consumeAmt = restCodeInfo.col;
  588. range.subRation.col = restCodeInfo.col;
  589. range.subRation.colCount = restCodeInfo.colCount;
  590. range.subGlj.col = 0;
  591. range.subGlj.colCount = restCodeInfo.col + restCodeInfo.colCount;
  592. }
  593. //第一条工料机数据行
  594. if (hasTHead && !hasTable && colMapping && rowIsGlj(dataTable[row])) {
  595. hasTable = true;
  596. range.subGlj.row = row;
  597. range.subRation.rowCount = range.subGlj.row - range.subRation.row;
  598. }
  599. if (hasTable && !rowIsGlj(dataTable[row])) {
  600. range.subGlj.rowCount = row - range.subGlj.row;
  601. return range;
  602. }
  603. if (hasTable && (row === sheetData.rowCount - 1 || !dataTable[row + 1])) {
  604. range.subGlj.rowCount = row - range.subGlj.row + 1;
  605. return range;
  606. }
  607. }
  608. return null;
  609. }
  610. //分析整个表
  611. let extractData = [];
  612. //定额行后必须要跟着单位行
  613. let hasUnit = false;
  614. for (let row = 0; row < sheetData.rowCount; row++) {
  615. if (!dataTable[row] || !rowHasData(dataTable[row])) {
  616. continue;
  617. }
  618. let rationData = rationRow(row);
  619. if (rationData) {
  620. if (!hasUnit) {
  621. extractData.pop();
  622. }
  623. hasUnit = false;
  624. //转换数据数组的每个元素,为定额基本数据:不完整的code、不完整的name、unit、和子表门构成
  625. //subTable: [{subRation: [], subGlj: []}],subRation为每个子表表头数据(排除掉了顺序号至代号),
  626. //subGlj为每个子表工料机数据
  627. let basicData = {
  628. code: rationData.code,
  629. name: rationData.name,
  630. unit: null,
  631. subTable: []
  632. };
  633. extractData.push(basicData);
  634. }
  635. let unitData = unitRow(row);
  636. if (unitData) {
  637. hasUnit = true;
  638. let thisBasicData = extractData[extractData.length - 1];
  639. if (thisBasicData) {
  640. if (!thisBasicData.unit) {
  641. thisBasicData.unit = unitData;
  642. }
  643. //获取表格数据
  644. let range = getTableRange(row + 1);
  645. if (range) {
  646. let subRationTable = getSubRationTable(dataTable, range.subRation, spans),
  647. subGljTable = getSubGljTable(dataTable, range.subGlj);
  648. thisBasicData.subTable.push({subRation: subRationTable, subGlj: subGljTable, colMapping: colMapping});
  649. //跳过其中的行
  650. row = range.subGlj.row + range.subGlj.rowCount - 1;
  651. }
  652. }
  653. }
  654. }
  655. return extractData;
  656. }
  657. // 转换内蒙养护数据
  658. function transferDataFromNMYH(extractData) {
  659. console.log(extractData);
  660. let existCodes = [];
  661. //从一个提取的数据(1定额数据及其子表数据)中获取一份转换数据
  662. function transfer(source) {
  663. //以完整定额编码为属性,因为1个定额可能会有多张子表,且完整定额编码相同,子工料机不同,方便直接添加后续的工料机
  664. let temp = {},
  665. target = [];
  666. let basicCode = source.code,
  667. basicName = source.name,
  668. basicUnit = source.unit;
  669. if (existCodes.includes(basicCode)) {
  670. return [];
  671. }
  672. existCodes.push(basicCode);
  673. //处理消耗量,可能有(3.5) 和 - 的情况, 处理:(3.5) => 3.5 - => 0
  674. function handleConsumeAmt(consumeAmt) {
  675. if (typeof consumeAmt === 'string') {
  676. consumeAmt = trimAll(consumeAmt);
  677. consumeAmt = consumeAmt.replace(/[\-,_,—,一,\(,\),(,)]/g, '');
  678. if (!consumeAmt) {
  679. return 0;
  680. }
  681. }
  682. return consumeAmt;
  683. }
  684. //从工料机子表中获取工料机数据, index为定额编码对应的下标索引
  685. function getGljList(gljColMapping, gljTable, index) {
  686. let gljList = [];
  687. let consumeAmtCol = gljColMapping.consumeAmt + index;
  688. for (let rowData of gljTable) {
  689. //工料机数据必须要有名称、单位、编码
  690. if (!rowData[gljColMapping.name] ||
  691. !rowData[gljColMapping.unit] ||
  692. !rowData[gljColMapping.code]) {
  693. continue;
  694. }
  695. let consumeAmt = isUnDef(rowData[consumeAmtCol]) ? 0 : handleConsumeAmt(rowData[consumeAmtCol]);
  696. gljList.push({
  697. name: rowData[gljColMapping.name],
  698. unit: rowData[gljColMapping.unit],
  699. code: rowData[gljColMapping.code],
  700. consumeAmt: consumeAmt,
  701. });
  702. }
  703. return gljList;
  704. }
  705. //拼接定额工料机数据
  706. for (let table of source.subTable) {
  707. let rationTable = table.subRation;
  708. if (!rationTable || rationTable.length === 0) {
  709. continue;
  710. }
  711. let lastRationCodes = rationTable.pop(); //定额子表,最后一行是末位定额编码
  712. for (let i = 0; i < lastRationCodes.length; i++) {
  713. let lastCode = lastRationCodes[i];
  714. //拼接定额编码
  715. let compleCode = `${basicCode}-${lastCode}`,
  716. gljList = getGljList(table.colMapping, table.subGlj, i);
  717. if (!temp[compleCode]) { //该定额不存在
  718. temp[compleCode] = {
  719. code: compleCode,
  720. unit: basicUnit,
  721. name: basicName,
  722. gljList: gljList
  723. };
  724. } else { //该定额已存在,则追加工料机
  725. temp[compleCode].gljList = temp[compleCode].gljList.concat(gljList);
  726. }
  727. //拼接定额名称
  728. for (let rationNameRow of rationTable) {
  729. if (rationNameRow[i]) {
  730. temp[compleCode].name += ` ${rationNameRow[i]}`;
  731. }
  732. }
  733. }
  734. }
  735. //将temp对象转换为数据
  736. for (let code in temp) {
  737. target.push(temp[code]);
  738. }
  739. return target;
  740. }
  741. let transferData = [];
  742. for (let data of extractData) {
  743. let unitTargetData = transfer(data);
  744. transferData = transferData.concat(unitTargetData);
  745. }
  746. console.log(transferData);
  747. return transferData;
  748. }
  749. /*
  750. * 从原本的建筑Excel中提取数据
  751. * */
  752. function extractDataFromJZ(sheetData) {
  753. let dataTable = sheetData.data.dataTable,
  754. spans = sheetData.spans;
  755. //定额编号为定额行
  756. function rationRow(row) {
  757. let cell = dataTable[row][beginCol];
  758. if (!cell || !cell.value || typeof cell.value !== 'string') {
  759. return false;
  760. }
  761. let v = trimAll(cell.value);
  762. return cell.value === '定额编号';
  763. }
  764. function endRationRow(row) {
  765. let cell = dataTable[row][beginCol];
  766. if (!cell || !cell.value || typeof cell.value !== 'string') {
  767. return false;
  768. }
  769. let v = trimAll(cell.value);
  770. return cell.value.includes('基价');
  771. }
  772. //表格中"分类"行为人材机子表起始行
  773. function classRow(row) {
  774. let cell = dataTable[row][beginCol];
  775. if (!cell || !cell.value || typeof cell.value !== 'string') {
  776. return false;
  777. }
  778. let v = trimAll(cell.value);
  779. return cell.value === '分类';
  780. }
  781. //人材机行:编码、名称、单位不为空
  782. function gljRow(rowData, colMapping) {
  783. let code = rowData[colMapping.code] ? rowData[colMapping.code].value : null,
  784. name = rowData[colMapping.name] ? rowData[colMapping.name].value : null,
  785. unit = rowData[colMapping.unit] ? rowData[colMapping.unit].value : null;
  786. return code && name && unit;
  787. }
  788. //获取"分类"行列对应:编码、名称、单位、单价的列
  789. function getColMapping(rowData, colCount) {
  790. function validMapping(obj) {
  791. let needFields = ['code', 'name', 'unit', 'price'];
  792. for (let field of needFields) {
  793. if (!field in obj) {
  794. return false;
  795. }
  796. }
  797. return true;
  798. }
  799. let mapping = {};
  800. for (let i = 0; i < colCount; i++) {
  801. let col = beginCol + i,
  802. cell = rowData[col];
  803. if (cell && cell.value) {
  804. let cellValue = trimAll(cell.value);
  805. if (cellValue === '编码') {
  806. mapping.code = col;
  807. } else if (cellValue === '名称') {
  808. mapping.name = col;
  809. } else if (cellValue === '单位') {
  810. mapping.unit = col;
  811. } else if ( cellValue.includes('单价')) {
  812. mapping.price = col;
  813. return validMapping(mapping) ? mapping : null;
  814. }
  815. }
  816. }
  817. return null;
  818. }
  819. function getTableRange(beginRow) {
  820. let range = {
  821. subRation: {row: beginRow},
  822. subManage: {},//管理费
  823. subGlj: {},
  824. };
  825. //获取定额子表列数,从定额编号后面的有数据的列算起
  826. let beginCol = null, //第一个有效数据列
  827. lastCol = null; //最后一个有效数据列
  828. for (let col = 0; col < sheetData.columnCount; col++) {
  829. let cell = dataTable[beginRow][col];
  830. if (cell && cell.value && trimAll(cell.value) !== '定额编号') {
  831. if (!beginCol) {
  832. beginCol = col;
  833. }
  834. lastCol = col;
  835. }
  836. }
  837. if (!beginCol || !lastCol) {
  838. return null
  839. }
  840. range.subRation.col = beginCol;
  841. range.subRation.colCount = lastCol - beginCol + 1;
  842. let enterRation = false,
  843. enterGlj = false;
  844. for (let row = beginRow; row < sheetData.rowCount; row++) {
  845. if (!dataTable[row] || !rowHasData(dataTable[row])) {
  846. continue;
  847. }
  848. if (endRationRow(row)) {
  849. enterRation = true;
  850. range.subRation.rowCount = row - range.subRation.row;
  851. }
  852. if (enterRation && classRow(row)) {
  853. enterGlj = true;
  854. colMapping = getColMapping(dataTable[row], sheetData.columnCount);
  855. if (!colMapping) {
  856. return null;
  857. }
  858. //管理费(人材机)相关
  859. range.subManage.row = row - 1;
  860. range.subManage.rowCount = 1;
  861. range.subManage.col = beginCol;
  862. range.subManage.colCount = range.subRation.colCount;
  863. //人材机相关
  864. range.subGlj.row = row + 1;
  865. range.subGlj.col = colMapping.code;
  866. range.subGlj.colCount = lastCol - range.subGlj.col + 1;
  867. }
  868. if (enterGlj && !gljRow(dataTable[row], colMapping)) {
  869. range.subGlj.rowCount = row - range.subGlj.row;
  870. return range;
  871. }
  872. if (enterGlj && (row === sheetData.rowCount - 1 || !dataTable[row + 1])) {
  873. range.subGlj.rowCount = row - range.subGlj.row + 1;
  874. return range;
  875. }
  876. }
  877. return null;
  878. }
  879. //管理费table并入工料机table
  880. function mergeManage(gljTable, manageTable) {
  881. for (let rowData of manageTable) {
  882. rowData.unshift(1);
  883. rowData.unshift('元');
  884. rowData.unshift('管理费');
  885. rowData.unshift('GLF');
  886. }
  887. return gljTable.concat(manageTable);
  888. }
  889. //分析整个表
  890. let extractData = [];
  891. for (let row = 0; row < sheetData.rowCount; row++) {
  892. if (!dataTable[row] || !rowHasData(dataTable[row])) {
  893. continue;
  894. }
  895. if (rationRow(row)) {
  896. //获取表格数据
  897. let range = getTableRange(row);
  898. if (range) {
  899. let subRationTable = validateRationTable(getSubRationTable(dataTable, range.subRation, spans)),
  900. subGljTable = getSubGljTable(dataTable, range.subGlj),
  901. subManageTable = getSubGljTable(dataTable, range.subManage);
  902. subGljTable = mergeManage(subGljTable, subManageTable);
  903. //将管理费合并进人材机
  904. extractData.push({subRation: subRationTable, subGlj: subGljTable});
  905. //跳过其中的行
  906. row = range.subGlj.row + range.subGlj.rowCount - 1;
  907. }
  908. }
  909. }
  910. function validateRationTable(source) {
  911. let target = [];
  912. let codeLine = source[0];
  913. for (let subSource of source) {
  914. let subTarget = [];
  915. for (let i = 0; i < subSource.length; i++) {
  916. if (!codeLine[i]) {
  917. continue;
  918. }
  919. subTarget.push(subSource[i]);
  920. }
  921. target.push(subTarget);
  922. }
  923. return target;
  924. }
  925. return extractData;
  926. }
  927. /*
  928. * 转换建筑数据
  929. * */
  930. function transferDataFromJZ(extractData) {
  931. function handleNumber(v) {
  932. if (typeof v === 'string') {
  933. v = trimAll(v);
  934. v = v.replace(/[\-,_,—,一,\(,\),(,),[,]/g, '');
  935. if (!v) {
  936. return 0;
  937. }
  938. }
  939. return Math.abs(parseFloat(v));
  940. }
  941. //从工料机子表中获取工料机数据, index为定额编码对应的下标索引
  942. function getGljList(gljTable, index) {
  943. let gljList = [];
  944. //获取的工料机对应列
  945. let gljColMapping = {
  946. code: 0,
  947. name: 1,
  948. unit: 2,
  949. price: 3,
  950. consumeAmtCol: fixedCount + index
  951. };
  952. for (let rowData of gljTable) {
  953. //工料机数据必须要有名称、单位、编码、单价
  954. if (!rowData[gljColMapping.name] ||
  955. !rowData[gljColMapping.unit] ||
  956. !rowData[gljColMapping.code] ||
  957. !rowData[gljColMapping.price]) {
  958. continue;
  959. }
  960. let consumeAmt = isUnDef(rowData[gljColMapping.consumeAmtCol]) ? 0 : handleNumber(rowData[gljColMapping.consumeAmtCol]),
  961. price = isUnDef(rowData[gljColMapping.price]) ? 0 : handleNumber(rowData[gljColMapping.price]);
  962. gljList.push({
  963. name: rowData[gljColMapping.name],
  964. unit: rowData[gljColMapping.unit],
  965. code: rowData[gljColMapping.code],
  966. price: price,
  967. consumeAmt: consumeAmt,
  968. });
  969. }
  970. return gljList;
  971. }
  972. let temp = {},
  973. target = [];
  974. for (let source of extractData) {
  975. let rationTable = source.subRation,
  976. gljTable = source.subGlj;
  977. if (!rationTable || rationTable.length === 0) {
  978. continue;
  979. }
  980. //拼接定额工料机数据
  981. let rationCodes = rationTable.shift(); //定额编码
  982. for (let i = 0; i < rationCodes.length; i++) {
  983. let code = rationCodes[i].replace(/[_,—,一]/g, '-');
  984. let gljList = getGljList(gljTable, i);
  985. if (!temp[code]) { //该定额不存在
  986. temp[code] = {
  987. code: code,
  988. unit: '',
  989. name: '',
  990. gljList: gljList
  991. };
  992. } else { //该定额已存在,则追加工料机
  993. temp[code].gljList = temp[code].gljList.concat(gljList);
  994. }
  995. //拼接定额名称
  996. for (let rationNameRow of rationTable) {
  997. if (rationNameRow[i]) {
  998. temp[code].name += ` ${rationNameRow[i]}`;
  999. }
  1000. }
  1001. }
  1002. }
  1003. for (let code in temp) {
  1004. target.push(temp[code]);
  1005. }
  1006. return target;
  1007. }
  1008. //导入Excel
  1009. function exportToExcel(transferData, fileName) {
  1010. $.bootstrapLoading.start();
  1011. setTimeout(function () {
  1012. if (exportSpread) {
  1013. exportSpread.destroy();
  1014. }
  1015. exportSpread = new GC.Spread.Sheets.Workbook($exportSpread[0], {sheetCount: 1});
  1016. let sheet = exportSpread.getSheet(0);
  1017. sheet.suspendPaint();
  1018. sheet.suspendEvent();
  1019. //往表格填值
  1020. let curRow = 1,
  1021. fillCol = {
  1022. code: 1,
  1023. name: 2,
  1024. unit: 3,
  1025. consumeAmt: 4,
  1026. price: 5
  1027. };
  1028. function getRowCount() {
  1029. let count = 0;
  1030. for (let data of transferData) {
  1031. count += 1 + data.gljList.length;
  1032. }
  1033. return count;
  1034. }
  1035. sheet.setRowCount(getRowCount()+1);
  1036. //test
  1037. /* let temp = {};
  1038. for (let testData of transferData) {
  1039. if (temp[testData.code]) {
  1040. temp[testData.code]++;
  1041. } else {
  1042. temp[testData.code] = 1;
  1043. }
  1044. }
  1045. let consoleData = [];
  1046. for (let i in temp) {
  1047. if (temp[i] > 1) {
  1048. consoleData.push({code: i, count: temp[i]});
  1049. }
  1050. }
  1051. console.log(consoleData);*/
  1052. //test
  1053. sheet.setValue(0, fillCol.code, '编码');
  1054. sheet.setValue(0, fillCol.name, '名称');
  1055. sheet.setValue(0, fillCol.unit, '单位');
  1056. sheet.setValue(0, fillCol.consumeAmt, '消耗量');
  1057. if (curTransferType === transferType.jz) {
  1058. sheet.setValue(0, fillCol.price, '单价');
  1059. }
  1060. for (let data of transferData) {
  1061. sheet.setValue(curRow, 0, '定额');
  1062. sheet.setValue(curRow, fillCol.code, data.code);
  1063. sheet.setValue(curRow, fillCol.name, data.name);
  1064. sheet.setValue(curRow, fillCol.unit, data.unit);
  1065. curRow++;
  1066. for (let glj of data.gljList) {
  1067. sheet.setValue(curRow, fillCol.code, glj.code);
  1068. sheet.setValue(curRow, fillCol.name, glj.name);
  1069. sheet.setValue(curRow, fillCol.unit, glj.unit);
  1070. sheet.setValue(curRow, fillCol.consumeAmt, glj.consumeAmt);
  1071. if (curTransferType === transferType.jz) {
  1072. sheet.setValue(curRow, fillCol.price, glj.price);
  1073. }
  1074. curRow++;
  1075. }
  1076. }
  1077. sheet.resumeEvent();
  1078. sheet.resumePaint();
  1079. let json = exportSpread.toJSON();
  1080. let excelIo = new GC.Spread.Excel.IO();
  1081. excelIo.save(json, function(blob) {
  1082. saveAs(blob, fileName);
  1083. $.bootstrapLoading.end();
  1084. $transferModal.modal('hide');
  1085. }, function(e) {
  1086. $.bootstrapLoading.end();
  1087. $transferModal.modal('hide');
  1088. console.log(e);
  1089. });
  1090. }, 200);
  1091. }
  1092. function eventListener() {
  1093. $('.transfer-excel').click(function () {
  1094. curTransferType = $(this).data('exceltype');
  1095. console.log(curTransferType);
  1096. });
  1097. $transferModal.on('hidden.bs.modal', function () {
  1098. $file.val('');
  1099. transferData = [];
  1100. });
  1101. //导入excel,提取并转换定额数据
  1102. $file.change(function () {
  1103. $transfer.addClass('disabled');
  1104. let file = $(this)[0];
  1105. $.bootstrapLoading.start();
  1106. $('#loadingPage').css('z-index', '2000');
  1107. setTimeout(function () {
  1108. let excelFile = file.files[0];
  1109. if(excelFile) {
  1110. let xlsReg = /xls$/g;
  1111. if(excelFile.name && xlsReg.test(excelFile.name)){
  1112. alert('请选择xlsx文件');
  1113. $(this).val('');
  1114. $transfer.removeClass('disabled');
  1115. $.bootstrapLoading.end();
  1116. return;
  1117. }
  1118. //前端解析excel数据
  1119. let excelIo = new GC.Spread.Excel.IO();
  1120. let sDate = +new Date();
  1121. excelIo.open(excelFile, function (json) {
  1122. console.log(json);
  1123. if (curTransferType === transferType.gl) {
  1124. let extractData = extractDataFromExcel(json.sheets.Sheet1);
  1125. transferData = transferDataFromExtract(extractData);
  1126. } else if (curTransferType === transferType.neimengyh) {
  1127. let extractData = extractDataFromNMYH(json.sheets.Sheet1);
  1128. transferData = transferDataFromNMYH(extractData);
  1129. } else {
  1130. let extractData = extractDataFromJZ(json.sheets.Sheet1);
  1131. transferData = transferDataFromJZ(extractData);
  1132. console.log(transferData);
  1133. }
  1134. console.log(`解析Excel文件时间:${+new Date() - sDate}`);
  1135. $.bootstrapLoading.end();
  1136. $transfer.removeClass('disabled');
  1137. }, function (e) {
  1138. $.bootstrapLoading.end();
  1139. $transfer.removeClass('disabled');
  1140. alert(e.errorMessage);
  1141. });
  1142. }
  1143. }, 200);
  1144. });
  1145. //确认转换,导出转换的Excel
  1146. $transfer.click(function () {
  1147. if ($(this).hasClass('disabled')) {
  1148. return;
  1149. }
  1150. if (!transferData || transferData.length === 0) {
  1151. //没有转换数据
  1152. alert('没有转换数据');
  1153. return;
  1154. }
  1155. let fileName = '转换数据.xlsx';
  1156. if ($file[0].files && $file[0].files[0] && $file[0].files[0].name) {
  1157. fileName = '转换数据' + $file[0].files[0].name;
  1158. }
  1159. exportToExcel(transferData, fileName);
  1160. });
  1161. }
  1162. return {eventListener}
  1163. })();
  1164. $(document).ready(function () {
  1165. TransferExcel.eventListener();
  1166. });