'use strict'; /** * * * @author Zhong * @date 2019/6/20 * @version */ const XML_EXPORT_BASE = (() => { // 自检提示的开始记号,区分提示属于哪一部分的类型(eg: 招标、控制价),便于后续做提示的合并去重 const HINT_START = '--start--'; // 属性类型 const TYPE = { DATE: 1, //日期类型YYYY-MM-DD DATE_TIME: 2, //日期类型YYY-MM-DDTHH:mm:ss INT: 3, //整数类型 DECIMAL: 4, //数值类型,不限制小数位数 NUM2: 5, //数值类型2:最多两位小数 BOOL: 6 //布尔型 }; // 需要特殊处理的属性类型默认空值(当一个值为undefined、null的时候,默认给赋什么值) const DEFAULT_VALUE = { [TYPE.INT]: '0', [TYPE.DECIMAL]: '0', [TYPE.NUM2]: '0', [TYPE.BOOL]: 'false' }; // 空白字符处理 const WHITE_SPACE = { COLLAPSE: 1 //移除所有空白字符(换行、回车、空格以及制表符会被替换为空格,开头和结尾的空格会被移除,而多个连续的空格会被缩减为一个单一的空格) }; // 计税方法 const TAX_TYPE = { '1': '一般计税法', '2': '简易计税法', }; // 承包人材料调整类型 const ADJUST_TYPE = { info: 'priceInfo', // 造价信息差额调整法 coe: 'priceCoe' // 价格指数调整法 }; // 加载数据间隔,减少服务器压力 const TIMEOUT_TIME = 400; // 导出粒度 const GRANULARITY = { PROJECT: 1, //导出建设项目 ENGINEERING: 2, //导出单项工程 TENDER: 3 //导出单位工程 }; // 导出的文件类型选项 const EXPORT_KIND = { Tender: 1, //投标 Bid: 2, //招标 Control: 3 //控制价 }; // 配置项 const CONFIG = Object.freeze({ HINT_START, TYPE, WHITE_SPACE, TAX_TYPE, ADJUST_TYPE, TIMEOUT_TIME, GRANULARITY, EXPORT_KIND }); // 缓存项 不需要的时候需要清空 let _cache = { // 失败列表 failList: [], // 项目数据(不包含详细数据,项目管理数据) projectData: {}, // 当前导出类型,默认投标 exportKind: EXPORT_KIND.Tender, // 记录拉取的单位工程项目详细数据,导出的时候,可能会导出多个文件,只有导出第一个文件的时候需要请求数据 tenderDetailMap: {} }; // 返回缓存项 function getItem(key) { return _cache[key] || null; } // 设置缓存项 function setItem(key, value) { // 与原数据是同类型的数据才可设置成功 if (_cache[key] && Object.prototype.toString.call(_cache[key]) === Object.prototype.toString.call(value)) { _cache[key] = value; } } // 清空缓存项 function clear() { _cache.failList = []; _cache.projectData = {}; _cache.exportKind = EXPORT_KIND.Tender; _cache.tenderDetailMap = {}; } const CACHE = Object.freeze({ getItem, setItem, clear }); /* * 定义不设置一个Node方法统一进入的原因:模板化比较直观,不分开定义节点的话,调用传参也很麻烦而且不直观。 * 一个节点对应一个构造方法,方便调整配置、方便其他版本开发、接手的人看起来更直观 * @param {String}name 节点名 * {Array}attrs 节点属性数据 * {Array}failList 失败列表 * @return {void} * */ function Element(name, attrs) { this.name = name; let checkData = check(name, attrs); this.fail = checkData.failHints; this.attrs = checkData.filterAttrs; handleXMLEntity(this.attrs); this.children = []; _cache.failList.push(...this.fail); } /* * xml字符实体的处理,这些特殊字符不处理会导致xml文件格式出错:""、<>、& * 要先处理& * */ let _xmlEntity = { '&': '&', '\n': ' ', '"': '"', '\'': ''', '<': '<', '>': '>' }; // 对每个元素的所有属性值进行特殊字符处理 function handleXMLEntity(attrs) { for (let attr of attrs) { if (!attr.value) { continue; } for (let [key, value] of Object.entries(_xmlEntity)) { attr.value = attr.value.replace(new RegExp(key, 'g'), value); } } } // 获取处理实体字符后的数据 function getParsedData(arr) { return arr.map(data => { for (let [key, value] of Object.entries(_xmlEntity)) { data = data.replace(new RegExp(key, 'g'), value); } return data; }); } /* * 检查 * 创建节点时检查节点的数据 * 1.长度限制minLen,maxLen * 2.值的限制,固定范围:enumeration * @param {String}eleName 节点名称 * {Array}datas 需要检查的属性数据 * @return {Object} failHints没通过的属性提示 filterAttrs过滤后的属性数据(失败提示在属性是必须的时候才提示,如果该属性失败了,但是是非必要属性,那么该属性不显示) * */ function check(eleName, datas) { let rst = { failHints: [], filterAttrs: [] }; let dateReg = /([0-9]{3}[1-9]|[0-9]{2}[1-9][0-9]{1}|[0-9]{1}[1-9][0-9]{2}|[1-9][0-9]{3})-(((0[13578]|1[02])-(0[1-9]|[12][0-9]|3[01]))|((0[469]|11)-(0[1-9]|[12][0-9]|30))|(02-(0[1-9]|[1][0-9]|2[0-8])))/; for (let data of datas) { const isHasValue = hasValue(data.value); // 某属性是非必须的,并且设置了mustHasValue,意味着必须要有值才输出该属性 if (!data.required && data.mustHasValue && !hasValue) { continue; } // 值统一转换成String,并且处理各类型属性空值时的默认取值 data.value = !isHasValue ? DEFAULT_VALUE[data.type] ? DEFAULT_VALUE[data.type] : '' : String(data.value); if (data.whiteSpace && data.whiteSpace === WHITE_SPACE.COLLAPSE) { //处理空格相关 data.value = data.value.replace(/[\r\n\t]/g, ' '); data.value = data.value.trim(); data.value = data.value.replace(/\s{1,}/g, ' '); } let isFail = false; let tempFail = ''; // 提示的名称需要处理,有的接口xml属性为英文,需要显示其展示名称 const name = data.dName || data.name; if (data.minLen && data.value.length < data.minLen) { isFail = true; tempFail = data.failHint ? `${data.failHint}字符数不可小于${data.minLen}个。` : `${eleName}-“${name}”字符数不可小于${data.minLen}个。`; } else if (data.maxLen && data.value.length > data.maxLen) { isFail = true; tempFail = data.failHint ? `${data.failHint}字符数不可大于${data.maxLen}个。` : `${eleName}-“${name}”字符数不可大于${data.maxLen}个。`; } else if (data.enumeration && !data.enumeration.includes(data.value)) { isFail = true; let enumerationHint = data.enumerationHint ? data.enumerationHint.join(';') : data.enumeration.join(';'); tempFail = data.failHint ? `${data.failHint}只能从“${enumerationHint}”中选择。` : `${eleName}-“${name}”只能从“${enumerationHint}”中选择。`; } else if (data.type && data.type === TYPE.DATE && !dateReg.test(data.value)) { isFail = true; tempFail = data.failHint ? `${data.failHint}日期格式必须是YYYY-MM-DD。` : `${eleName}-“${name}”日期格式必须是YYYY-MM-DD。`; } else if (data.type && data.type === TYPE.INT && !Number.isInteger(parseFloat(data.value))) { isFail = true; tempFail = data.failHint ? `${data.failHint}必须为整数。` : `${eleName}-“${name}”必须为整数。`; } else if (data.type && data.type === TYPE.DECIMAL && isNaN(parseFloat(data.value))) { isFail = true; tempFail = data.failHint ? `${data.failHint}必须为数值。` : `${eleName}-“${name}”必须为数值。`; } else if (data.type && data.type === TYPE.NUM2) { let v = parseFloat(data.value); if (isNaN(v)) { isFail = true; tempFail = data.failHint ? `${data.failHint}必须为数值。` : `${eleName}-“${name}”必须为数值。`; } else if (!data.value.length || (data.value.split('.').length > 1 && data.value.split('.')[1].length > 2)) { isFail = true; } } else if (data.type && data.type === TYPE.BOOL && !['true', 'false'].includes(String(data.value))) { isFail = true; tempFail = data.failHint ? `${data.failHint}必须为true或false。` : `${eleName}-“${name}”必须为true或false。`; } if (!isFail || data.required) { rst.filterAttrs.push(data); } if (isFail && data.required && tempFail) { rst.failHints.push(tempFail); } } return rst; } // 提取各导出类型的自检数据 function _extractHintParts(failList) { let rst = [], curPart; for (let hint of failList) { if (hint === HINT_START) { curPart = []; rst.push(curPart); continue; } curPart.push(hint); } return rst; } // 自检提示去重 function deWeightHints(failList) { let rst = []; let hintParts = _extractHintParts(failList); // 建设项目提示文本 let rootHints = [], // 单位工程提示文本映射 tenderMap = {}, reg = /^单位工程/; for (let hintPart of hintParts) { // 单位工程xxx提示 let curTenderHint; // 提取建设项目提示、各自单位工程提示 for (let hint of hintPart) { if (reg.test(hint)) { curTenderHint = hint; if (!tenderMap[curTenderHint]) { tenderMap[curTenderHint] = []; } continue; } if (curTenderHint) { tenderMap[curTenderHint].push(hint); } else { rootHints.push(hint); } } } // 建设项目提示去重,放入结果中 rootHints = [...new Set(rootHints)]; rst.push(...rootHints); // 单位工程提示放入结果中 for (let tenderHint in tenderMap) { rst.push(tenderHint); // 单位工程提示去重 let tenderHints = [...new Set(tenderMap[tenderHint])]; rst.push(...tenderHints); } return rst; } // 等待一段时间 function setTimeoutSync(handle, time) { return new Promise((resolve, reject) => { setTimeout(() => { if (handle && typeof handle === 'function') { handle(); } resolve(); }, time); }); } // v是否定义了,不为undefined和null function isDef(v) { return typeof v !== 'undefined' && v !== null; } function hasValue(v) { // v是否有值,不为undefined、null、'' return typeof v !== 'undefined' && v !== null && v !== ''; } /* * 将节点属性数据(attr数组)转换成简单key-value数据 * @param {Object}ele 元素节点数据Element实例 * @return {Object} * */ function getPlainAttrs(ele) { const obj = {}; ele.attrs.forEach(attr => obj[attr.name] = attr.value); return obj; } /* * 从fees数组中获取相关费用 * @param {Array}fees 费用数组 * {String}feeFields 费用字段 * @return {Number} * @example getFee(source.fees, 'common.totalFee') * */ function getFee(fees, feeFields) { if (!Array.isArray(fees)) { return 0; } let fields = feeFields.split('.'); let fee = fees.find(data => data.fieldName === fields[0]); if (!fee) { return 0; } return fee[fields[1]] || 0; } // 获取节点的汇总价格 function getAggregateFee(nodes) { let total = nodes.reduce((acc, node) => { const price = getFee(node.data.fees, 'common.totalFee'); return acc += price; }, 0); return scMathUtil.roundTo(total, -2); } // 获取固定类别行的费用 function getFeeByFlag(items, flag, feeFields) { const node = items.find(node => node.getFlag() === flag); return node ? getFee(node.data.fees, feeFields) : '0'; } /* * 根据key获取对应的基本信息、工程特征数据 * @param {Array}data * {String}key * @return {String} * @example getValueByKey(source.basicInformation, 'projectScale') * */ function getValueByKey(data, key) { for (let d of data) { if (d.key === key) { return d.value; } if (d.items && d.items.length > 0) { let findData = d.items.find(x => x.key === key); if (findData) { return findData.value; } } } return ''; } // 获取关联材料 function getRelGLJ(allGLJs, gljId) { return allGLJs.find(glj => glj.id === gljId); } // 随机生成机器信息码:CPU信息;硬盘序列号;mac地址; // 保存在localStorage中 function generateHardwareId() { const hardwareCacheId = window.localStorage.getItem('hardwareId'); if (hardwareCacheId) { return hardwareCacheId; } const charList = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']; function generateCpuId() { let id = ''; let count = 16; while (count--) { const randomIdx = parseInt(Math.random() * 16); id += charList[randomIdx]; } return id; } function generateDiskId() { let id = ''; let count = 8; while (count--) { const randomIdx = parseInt(Math.random() * 36); id += charList[randomIdx]; } return id; } function generateMacId() { const idList = []; let outerCount = 6; while (outerCount--) { let tempId = ''; let innerCount = 2; while (innerCount--) { const randomIdx = parseInt(Math.random() * 16); tempId += charList[randomIdx]; } idList.push(tempId); } return idList.join('-'); } const cpuId = generateCpuId(); const diskId = generateDiskId(); const macId = generateMacId(); const hardwareId = [cpuId, diskId, macId].join(';'); window.localStorage.setItem('hardwareId', hardwareId); return hardwareId; } // 数组打平成对象 function arrayToObj(arr) { let rst = {}; for (let data of arr) { rst[data.key] = data.value; } return rst; } /* * 检测层数 * @param {Number}maxDepth(最大深度) * {Object}node(需要检测的清单树节点) * @return {Boolean} * */ function validDepth(maxDepth, node) { let nodeDepth = node.depth(); let allNodes = node.getPosterity(); //检测相对深度 for (let n of allNodes) { let relativeDepth = n.depth() - nodeDepth; if (relativeDepth > maxDepth) { return false; } } return true; } /* * 检测唯一性,有些属性在规定的数据范围内唯一 * @param {Object}constraints(约束池) * {All}data(检测的数据) * {String}hint(提示已存在的内容) * {String}subHint(额外提示,有额外提示时,不用data提示) * @return {void} * */ function checkUnique(constraints, data, hint, subHint) { if (constraints.includes(data)) { let failHint = subHint ? `${hint}“${subHint}”已存在` : `${hint}“${data}”已存在`; _cache.failList.push(failHint); } else if (data) { constraints.push(data); } } //根据数据的NextSiblingID进行排序,返回排序后的数组 function sortByNext(datas) { let target = [], temp = {}; for (let data of datas) { temp[data.ID] = { me: data, next: null, prev: null }; } for (let data of datas) { let next = temp[data.NextSiblingID] || null; temp[data.ID].next = next; if (next) { next.prev = temp[data.ID]; } } let first = null; for (let data of datas) { let me = temp[data.ID]; if (!me.prev) { first = me; } } if (!first) { return datas; } while (first) { target.push(first.me); first = first.next; } return target; } /* * 根据粒度获取项目(不包含详细数据)数据 * @param {Number}granularity 导出粒度 * {Object}summaryObj 汇总字段 * {Number}tenderID 单位工程ID * {String}userID 用户ID * @return {Object} 返回的数据结构:{children: [{children: []}]} 最外层为建设项目,中间为单项工程,最底层为单位工程 * */ async function getProjectByGranularity(granularity, summaryObj, tenderID, userID) { let projectData = _cache.projectData; // 没有数据,需要拉取 if (!Object.keys(projectData).length) { projectData = await ajaxPost('/pm/api/getProjectByGranularity', { user_id: userID, tenderID, granularity, summaryObj}); _cache.projectData = projectData; } return projectData; } /* * 通过getData接口获取单位工程详细数据 * @param {Number}tenderID 单位工程ID * {String}userID 用户ID * @return {Object} 跟projectObj.project的数据结构一致 * */ async function getTenderDetail(tenderID, userID) { // 获取单位工程详细数据 let tenderDetail = _cache.tenderDetailMap[tenderID]; if (!tenderDetail) { tenderDetail = PROJECT.createNew(tenderID, userID); await tenderDetail.loadDataSync(); // 标记序号 const count = Object.keys(_cache.tenderDetailMap).length; tenderDetail.serialNo = count + 1; _cache.tenderDetailMap[tenderID] = tenderDetail; } return tenderDetail; } /* * 提取要导出的数据 * @param {Function}entryFunc 提取数据的入口方法 * {Number}granularity 导出粒度: 1-建设项目、2-单项工程、3-单位工程 * {Number}exportKind 导出的文件类型:1-投标、2-招标、3-控制价 * {Number}tenderID 单位工程ID * {String}userID 用户ID * @return {Array} 数据结构为:[{data: Object, exportKind: Number, fileName: String}] * */ async function extractExportData(entryFunc, granularity, summaryObj, exportKind, tenderID, userID) { // 默认导出建设项目 if (!granularity || ![1, 2, 3].includes(granularity)) { granularity = GRANULARITY.PROJECT; } // 默认导出投标文件 if (!exportKind || ![1, 2, 3].includes(exportKind)) { exportKind = EXPORT_KIND.Tender; } // 拉取标段数据:建设项目、单项工程、单位工程数据 let projectData = await getProjectByGranularity(granularity, summaryObj, tenderID, userID); if (!projectData) { throw '获取项目数据错误'; } // 单项工程、单位工程按照树结构数据进行排序 projectData.children = sortByNext(projectData.children); for (let engData of projectData.children) { engData.children = sortByNext(engData.children); } // 提取相关项目的详细导出数据 return await entryFunc(userID, exportKind, projectData); } //获取普通基数: {xxx} function getNormalBase(str) { let reg = /{.+?}/g, matchs = str.match(reg); return matchs || []; } //获取id引用基数: @xxx-xxx-xx function getIDBase(str) { let reg = /@.{36}/g, matchs = str.match(reg); return matchs || []; } // 转换基数表达式 // 1.有子项,则取固定清单对应基数 // 2.无子项,有基数,a.优先转换为行代号(不可自身) b.不能转换为行代号则找对应字典 // 3.基数中有无法转换的,根据导出类型决定 function transformCalcBase(exportKind, tenderDetail, node, { CalcBaseMap, FlagCalcBaseMap }) { let expr = node.data.calcBase || ''; if (node.children.length) { let flag = node.getFlag(); return FlagCalcBaseMap[flag] || ''; } if (expr) { let illegal = false; let normalBase = getNormalBase(expr), idBase = getIDBase(expr); //普通基数转基数字典 normalBase.forEach(base => { let replaceStr = CalcBaseMap[base]; //转换成行代号的优先级比较高,进行清单匹配 let flag = FlagCalcBaseMap[base]; if (flag) { let flagNode = tenderDetail.mainTree.items.find(mNode => mNode.getFlag() === flag); //匹配到了 普通基数转换成行引用 if (flagNode) { replaceStr = `F${flagNode.serialNo() + 1}`; } } //存在无法处理的基数 if (!replaceStr) { illegal = true; return; } expr = expr.replace(new RegExp(base, 'g'), replaceStr); }); //id引用转行代号引用 idBase.forEach(base => { let id = base.match(/[^@]+/)[0]; let theNode = tenderDetail.mainTree.getNodeByID(id), rowCode = theNode ? `F${theNode.serialNo() + 1}` : ''; if (!rowCode) { illegal = true; return; } expr = expr.replace(new RegExp(base, 'g'), rowCode); }); //不合法 // 在我们软件中的基数无法找到映射代号的情况下 // 导出招标、控制价时,基数为空 // 导出投标时,基数=综合合价/费率 if (illegal) { if (exportKind === EXPORT_KIND.Bid || exportKind === EXPORT_KIND.Control) { return ''; } else { let totalFee = getFee(node.data.fees, 'common.totalFee'), feeRate = node.data.feeRate; return +feeRate ? scMathUtil.roundTo(totalFee / (feeRate / 100), -2) : totalFee } } return expr; } } // 转换基数说明,根据转换后的基数处理 //1.行引用转换为对应行的名称 //2.基数字典转换为中文 function transformCalcBaseState(tenderDetail, expr, CalcStateMap) { if (!expr) { return ''; } expr = String(expr); //提取基数 let bases = expr.split(/[\+\-\*\/]/g); //提取操作符 let oprs = expr.match(/[\+\-\*\/]/g); //转换后的基数 let newBase = []; let illegal = false; for (let base of bases) { //行引用转换为名称. if (/F\d+/.test(base)) { let rowCode = base.match(/\d+/)[0], node = tenderDetail.mainTree.items[rowCode - 1]; if (!node || !node.data.name) { illegal = true; break; } newBase.push(node && node.data.name ? node.data.name : ''); } else if (CalcStateMap[base]) { //字典转换为中文 newBase.push(CalcStateMap[base]); } else if (/^\d+(\.\d+)?$/.test(base)) { //金额 newBase.push(base); } else { illegal = true; break; } } if (illegal) { return ''; } let newExpr = ''; for (let i = 0; i < newBase.length; i++) { newExpr += newBase[i]; if (oprs && oprs[i]) { newExpr += oprs[i]; } } return newExpr; } // 获取工程编号表格相关数据(导出需要弹出工程编号让用户选择) function getCodeSheetData(projectData) { let curCode = '0'; let sheetData = []; sheetData.push(getObj(projectData)); projectData.children.forEach(eng => { sheetData.push(getObj(eng)); eng.children.forEach(tender => { sheetData.push(getObj(tender)); }); }); //建设项目父ID设置为-1 if (sheetData.length) { sheetData[0].ParentID = -1; sheetData[0].code = ''; } return sheetData; function getObj(data) { return { collapsed: false, ID: data.ID, ParentID: data.ParentID, NextSiblingID: data.NextSiblingID, name: data.name, code: data.code || String(curCode++) }; } } // 从srcEle节点中获取元素名为eleName的元素 function getElementFromSrc(srcEle, eleName) { if (!srcEle || !srcEle.children || !srcEle.children.length) { return []; } return srcEle.children.filter(ele => ele.name === eleName); } /* * 设置完工程编号后,更新原始数据的工程编号 * 更新原始数据前需要将编号里的特殊字符进行转换 * @param {Array}exportData 提取出来的需要导出的数据 * {Array}codes 工程编号表中填写的工程编号 * {String}EngineeringName 单项工程元素的名称 * {String}tenderName 单位工程元素的名称 * {String}codeName 编号属性的名称 * @return {void} * */ function setupCode(exportData, codes, EngineeringName, tenderName, codeName) { // 转换xml实体字符 let parsedCodes = getParsedData(codes); // 给导出数据里的单项工程、单位工程填上用户设置的工程编号 exportData.forEach(orgData => { let curIdx = 0; let engs = getElementFromSrc(orgData.data, EngineeringName); engs.forEach(eng => { eng.attrs.find(attr => attr.name === codeName).value = parsedCodes[curIdx++]; let tenders = getElementFromSrc(eng, tenderName); tenders.forEach(tender => { tender.attrs.find(attr => attr.name === codeName).value = parsedCodes[curIdx++]; }); }); }); } /** * 弱自检自检,有错误非强制不可导的自检 * failList里存的是导出规定属性时,不符合标准规定的错误,存在这种错误就不允许导出 * 这里的错误是业务上的提示错误,与标准文件规定无关,存在这种错误是允许导出的 * @return {Array} - 提示信息数组 */ function softCheck() { const tenderDetailMap = _cache.tenderDetailMap; // 检查清单综合单价是否大于最高限价,大于则提示 function checkMaxPrice(tenderDetail) { return tenderDetail.mainTree.items .filter(node => calcTools.unitFeeGTMaxPrice(node, 'common.unitFee')) .map(node => { const code = node.data.code || ''; const name = node.data.name || ''; return `第${node.serialNo()}行“${code + name}”,清单综合单价 > 最高限价`; }); } const infos = []; // 按照获取顺序serialNo(根据树结构)排序 Object.values(tenderDetailMap) .sort((a, b) => a.serialNo - b.serialNo) .forEach(tenderDetail => { const maxPriceInfos = checkMaxPrice(tenderDetail); if (!maxPriceInfos.length) { return; } infos.push(`单位工程“${tenderDetail.projectInfo.name}”下:`); infos.push(...maxPriceInfos); }); return infos; } const UTIL = Object.freeze({ deWeightHints, isDef, hasValue, setTimeoutSync, getFee, getAggregateFee, getFeeByFlag, getPlainAttrs, getValueByKey, getRelGLJ, generateHardwareId, arrayToObj, validDepth, checkUnique, sortByNext, getTenderDetail, getProjectByGranularity, getNormalBase, getIDBase, transformCalcBase, transformCalcBaseState, getCodeSheetData, getElementFromSrc, getParsedData, setupCode, softCheck }); // 开始标签 function _startTag(ele) { let rst = `<${ele.name}`; for (let attr of ele.attrs) { rst += ` ${attr.name}="${attr.value}"`; } rst += ele.children.length > 0 ? '>' : '/>'; return rst; } // 结束标签 function _endTag(ele) { return ``; } // 拼接成xml字符串 function _toXMLStr(eles) { let rst = ''; for (let ele of eles) { rst += _startTag(ele); if (ele.children.length > 0) { rst += _toXMLStr(ele.children); rst += _endTag(ele); } } return rst; } //格式化xml字符串 function _formatXml(text) { //去掉多余的空格 text = '\n' + text.replace(/>\s*?\n<"); //调整格式 let rgx = /\n(<(([^\?]).+?)(?:\s|\s*?>|\s*?(\/)>)(?:.*?(?:(?:(\/)>)|(?:<(\/)\2>)))?)/mg; let nodeStack = []; let output = text.replace(rgx, function ($0, all, name, isBegin, isCloseFull1, isCloseFull2, isFull1, isFull2) { let isClosed = (isCloseFull1 === '/') || (isCloseFull2 === '/') || (isFull1 === '/') || (isFull2 === '/'); let prefix = ''; if (isBegin === '!') { prefix = getPrefix(nodeStack.length); } else { if (isBegin !== '/') { prefix = getPrefix(nodeStack.length); if (!isClosed) { nodeStack.push(name); } } else { nodeStack.pop(); prefix = getPrefix(nodeStack.length); } } let ret = '\n' + prefix + all; return ret; }); let outputText = output.substring(1); return outputText; function getPrefix(prefixIndex) { let span = ' '; let output = []; for (let i = 0; i < prefixIndex; ++i) { output.push(span); } return output.join(''); } } /* * 根据各自费用定额的文件结构,导出文件 * 每个费用定额可能导出的结果文件都不同 * 比如广东18需要将一个建设项目文件,多个单位工程文件打包成一个zip文件。重庆18就没这种要求 * @param {Array}codes 工程编号数据 * {Array}extractData 提取的数据 * {Function}setCodeFunc 各自费用定额的导出前重设用户输入的工程编号方法 * {Function}saveAsFunc 各自费用定额的导出方法 * @return {void} * */ async function exportFile(codes, extractData, setCodeFunc, saveAsFunc) { // 编号重置后将会被导出,需要将编号进行xml字符实体转换 codes = getParsedData(codes); setCodeFunc(codes, extractData); // 获取文件数据 let fileData = extractData.map(extractObj => { // 转换成xml字符串 let xmlStr = _toXMLStr([extractObj.data]); // 加上xml声明 xmlStr = `${xmlStr}`; // 格式化 xmlStr = _formatXml(xmlStr); let blob = new Blob([xmlStr], { type: 'text/plain;charset=utf-8' }); return { blob: blob, exportKind: extractObj.exportKind, fileName: extractObj.fileName }; }); // 导出 await saveAsFunc(fileData); } return { CONFIG, CACHE, UTIL, Element, extractExportData, exportFile }; })();