'use strict'; /** * * * @author Zhong * @date 2018/8/2 * @version */ /* * 清单导入模块,前端导入excel,进行数据提取,用lz-string进行压缩上传处理 * */ const importBills = (function () { //单元格数据是否存在 function _isDef(data) { return typeof data !== 'undefined' && data !== null && data !== ''; } //去除转义字符 function _deESC(data) { return _isDef(data) ? data.toString().replace(/[\r\n\s\t]/g, '') : data; } function _deNR(data) { return _isDef(data) ? data.toString().replace(/[\r\n]/g, '') : data; } //find 返回最后匹配 function findLast(datas, func) { let filter = datas.filter(func); if (filter.length > 0) { return filter[filter.length - 1]; } return null; } const fileType = { gcl: 0, //工程量清单 qdsl: 1, //清单示例 gclex: 2, // 单机版导出的工程量清单预算表,需要转换成清单示例表来处理 }; //获取列字段对应 function getColMapping(type) { if (type === 0) { //工程量清单 return { code: 0, name: 1, unit: 2, quantity: 4 }; } else { //清单示例表 return { code: 0, name: 1, unit: 2, quantity: 3 }; } } function isGCLHead(dataRow, nextDataRow) { const cell = dataRow[0]; const nextCell = nextDataRow && nextDataRow[0]; return cell && cell.value === '工程量清单' && (!nextCell || !/建设项目名称/.test(nextCell.value)); // 兼容招清单01-1表 } function isGCLExtendHead(dataRow, nextDataRow) { const cell = dataRow[0]; const nextCell = nextDataRow && nextDataRow[0]; if ((cell && cell.value === '工程量清单预算表') || (nextCell && /建设项目名称/.test(nextCell.value))) { // 兼容招清单01-1表 return true; } } //分析文件,1、工程量清单 2、清单示例表 function getFileType(sheetData) { let dataTable = sheetData.data.dataTable, rowCount = sheetData.rowCount; for (let row = 0; row < rowCount; row++) { if (isGCLHead(dataTable[row], dataTable[row + 1])) { return fileType.gcl; } if (isGCLExtendHead(dataTable[row], dataTable[row + 1])) { return fileType.gclex; } } return fileType.qdsl; } //提取工程量清单数据 //层级由depth确定,表格里最顶层depth为0(表头里一清单),表格内容里数据的depth为空格数+1 function extractGCLDatas(sheetData, colMapping) { //let colMapping = {code: 0, name: 1, unit: 2, quantity: 4}; let dataTable = sheetData.data.dataTable, rowCount = sheetData.rowCount; let rst = []; for (let row = 0; row < rowCount; row++) { //表格中顶层节点 if (isGCLHead(dataTable[row], dataTable[row + 1])) { let rootRow = dataTable[row + 2]; let name = rootRow[0].value ? _deNR(rootRow[0].value) : ''; let existsRoot = findLast(rst, x => x.name === name && x.depth === 0); if (!existsRoot) { let root = { ID: uuid.v1(), NextSiblingID: -1, ParentID: -1, name: name, depth: 0, parent: null, unitPriceAnalysis: 1 }; let preData = findLast(rst, x => x.depth === root.depth); if (preData) { preData.NextSiblingID = root.ID; } rst.push(root); } row += 3; continue; } let code = dataTable[row][colMapping.code] ? dataTable[row][colMapping.code].value : null, name = dataTable[row][colMapping.name] ? _deNR(dataTable[row][colMapping.name].value) : null, unit = dataTable[row][colMapping.unit] ? dataTable[row][colMapping.unit].value : null, quantity = dataTable[row][colMapping.quantity] ? dataTable[row][colMapping.quantity].value : null; if (!code && !name || /合计/.test(code)) { //过滤掉同时没有编号和名称的、过滤合计行 continue; } // “子目号”、“单位”、“数量”都为空,“子目名称”不为空时,应将此行清单名称合并到上一行 let lastData = rst[rst.length - 1]; if (!code && !unit && !quantity && name) { lastData.name += name; continue; } //表格内的数据 code = String(code); let depth = getDepth(code); let data = { ID: uuid.v1(), NextSiblingID: -1, ParentID: -1, code: code, name: name, unit: unit, quantity: quantity, depth: depth, unitPriceAnalysis: 1, }; //获取data的父节点链,成为兄弟节点,只能在父链里找前兄弟(不能跨父链) let parents = getParents(lastData); let preData = findLast(parents, x => x.depth === depth); if (preData) { preData.NextSiblingID = data.ID; data.ParentID = preData.ParentID; data.parent = preData.parent; } else { data.ParentID = lastData.ID; data.parent = lastData; } rst.push(data); } console.log(rst); return rst; function getDepth(code) { if (!code) { return 1; } let match = code.match(/\s/g); return match ? match.length + 1 : 1; } } function getParents(data) { let rst = []; let parent = data.parent; while (parent) { rst.push(parent); parent = parent.parent; } rst.push(data); return rst; } //获取编号前缀: 101-1 => 101 101-1-1 => 101-1 function getPrefix(v) { if (!v) { return null; } let reg = /(.*)-/; let match = reg.exec(v); return match ? match[1] : null; } // 示例列映射 const slColMap = { code: 0, name: 1, unit: 2, quantity: 3 }; function isValidGCLExRow(rowData) { if (rowData[0] && /编制[::]/.test(rowData[0].value)) { return false; } if (rowData[1] && /合计/.test(rowData[1].value)) { return false; } if ((!rowData[slColMap.code] || !rowData[slColMap.code].value) && (!rowData[slColMap.name] || !rowData[slColMap.name].value) && (!rowData[slColMap.unit] || !rowData[slColMap.unit].value) && (!rowData[slColMap.quantity] || !rowData[slColMap.quantity].value)) { return false; } return true; } // 将“工程量清单预算表”去掉表头表尾,并转换成清单示例表。 // 工程量清单预算表的格式可参考需求:BUG #3037 function transformGCLExToSL(sheetData) { const rst = { data: { dataTable: [] }, rowCount: 0, }; const dataTable = sheetData.data.dataTable; const rowCount = sheetData.rowCount; let preRootName; for (let row = 0; row < rowCount; row++) { const rowData = dataTable[row]; if (isGCLExtendHead(rowData, dataTable[row + 1])) { const rootRowdata = dataTable[row + 3]; const name = rootRowdata[0].value; if (name) { const rootName = name.replace('工程量清单', '清单'); if (rootName !== preRootName) { preRootName = rootName; rst.data.dataTable.push({ [slColMap.name]: { value: rootName } }); } } row += 4; continue; } if (isValidGCLExRow(rowData)) { const cellData = { [slColMap.code]: { value: rowData[slColMap.code] && rowData[slColMap.code].value || null }, [slColMap.name]: { value: rowData[slColMap.name] && rowData[slColMap.name].value || null }, [slColMap.unit]: { value: rowData[slColMap.unit] && rowData[slColMap.unit].value || null }, [slColMap.quantity]: { value: rowData[slColMap.quantity] && rowData[slColMap.quantity].value || null }, }; rst.data.dataTable.push(cellData); } } rst.rowCount = rst.data.dataTable.length; return rst; } //提取清单示例数据 function extractSLDatas(sheetData) { let dataTable = sheetData.data.dataTable, rowCount = sheetData.rowCount; let rst = []; let curRoot = null; for (let row = 0; row < rowCount; row++) { let code = dataTable[row][slColMap.code] && dataTable[row][slColMap.code].value ? String(dataTable[row][slColMap.code].value).trim() : null, name = dataTable[row][slColMap.name] ? _deNR(dataTable[row][slColMap.name].value) : null, unit = dataTable[row][slColMap.unit] ? dataTable[row][slColMap.unit].value : null, quantity = dataTable[row][slColMap.quantity] ? dataTable[row][slColMap.quantity].value : null; if (!code) { //没有编号的数据,名称必须为:清单 第xx章,认为新的表根节点 const reg = /清单\s+第[^章]+章/; //if (name && /清单 第\d+章/.test(name)) { if (name && reg.test(name)) { curRoot = { code: null, name: name, ID: uuid.v1(), ParentID: -1, NextSiblingID: -1, parent: null, unitPriceAnalysis: 1 }; rst.push(curRoot); } else { curRoot = null; } } else if (!curRoot) { //根节点为无效根节点,其下子数据全部过滤掉 continue; } else { //有code且有有效表根节点 let prefix = getPrefix(code); let data = { code: code, name: name, unit: unit, quantity: quantity, ID: uuid.v1(), NextSiblingID: -1, unitPriceAnalysis: 1 }; let lastData = rst[rst.length - 1]; let parents = getParents(lastData); //某数据编号为此数据的前缀,则某数据为此数据的父节点 let parentData = findLast(parents, x => prefix === x.code); if (!parentData && prefix === '') { // -x的数据,在父链上找不到编号与prefix相同的数据时,父链上-x的数据,则这两数据为兄弟节点,没有则上一行数据为其父节点 let samePrefixData = findLast(parents, x => getPrefix(x.code) === prefix); parentData = samePrefixData ? samePrefixData.parent : lastData; } else if (!parentData && prefix !== '') { //不是-x的数据,在父链上找不到编号与prefix相同的数据时,表根节点为其父节点 parentData = curRoot; } data.ParentID = parentData.ID; data.parent = parentData; let preData = findLast(parents, x => x.ParentID === data.ParentID); if (preData) { preData.NextSiblingID = data.ID; } rst.push(data); } } console.log(rst); return rst; } function extactDatas(sheets) { let rst = []; let curSheetType = null; for (let sheetName in sheets) { let sheetData = sheets[sheetName]; let sheetType = getFileType(sheetData); if (curSheetType !== null && sheetType !== curSheetType) { throw 'excel文件中存在不同格式的表格。'; } curSheetType = sheetType; let colMapping = getColMapping(sheetType); let datas = []; if (sheetType === fileType.gcl) { datas = extractGCLDatas(sheetData, colMapping); } else if (sheetType === fileType.qdsl) { datas = extractSLDatas(sheetData, colMapping); } else { const slSheetData = transformGCLExToSL(sheetData); datas = extractSLDatas(slSheetData, colMapping); } rst = rst.concat(datas); } //编号去除空格 清除多余数据 设置数据 for (let data of rst) { if (data.code && typeof data.code === 'string') { data.code = data.code.replace(/\s/g, ''); } if (data.unit === '㎡') { data.unit = 'm2'; } else if (data.unit === 'm³') { data.unit = 'm3'; } data.projectID = projectObj.project.ID(); data.type = billType.BILL; delete data.parent; delete data.depth; } //将表根节点的ParentID设置成第100章至700章清单的ID let fixedBill = projectObj.project.Bills.tree.roots.find(node => node.data && node.data.flagsIndex && node.data.flagsIndex.fixed && node.data.flagsIndex.fixed.flag === fixedFlag.ONE_SEVEN_BILLS); let rootDatas = rst.filter(data => data.ParentID === -1); for (let root of rootDatas) { root.ParentID = fixedBill.data.ID; } //清单 第100章 总则清单需要加上固定ID let oneHundredBills = rootDatas.find(data => data.name && /第100章/.test(data.name)); if (oneHundredBills) { oneHundredBills.flags = [{ flag: fixedFlag.ONE_HUNDRED_BILLS, fieldName: 'fixed' }]; } return rst; } return { extactDatas } })();