exportStdInterfaceBase.js 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735
  1. 'use strict';
  2. /**
  3. *
  4. *
  5. * @author Zhong
  6. * @date 2019/6/20
  7. * @version
  8. */
  9. const XML_EXPORT_BASE = (() => {
  10. // 属性类型
  11. const TYPE = {
  12. DATE: 1, //日期类型YYYY-MM-DD
  13. DATE_TIME: 2, //日期类型YYY-MM-DDTHH:mm:ss
  14. INT: 3, //整数类型
  15. DECIMAL: 4, //数值类型,不限制小数位数
  16. NUM2: 5, //数值类型2:最多两位小数
  17. BOOL: 6 //布尔型
  18. };
  19. // 需要特殊处理的属性类型默认空值(当一个值为undefined、null的时候,默认给赋什么值)
  20. const DEFAULT_VALUE = {
  21. [TYPE.INT]: '0',
  22. [TYPE.DECIMAL]: '0',
  23. [TYPE.NUM2]: '0',
  24. [TYPE.BOOL]: 'false'
  25. };
  26. // 空白字符处理
  27. const WHITE_SPACE = {
  28. COLLAPSE: 1 //移除所有空白字符(换行、回车、空格以及制表符会被替换为空格,开头和结尾的空格会被移除,而多个连续的空格会被缩减为一个单一的空格)
  29. };
  30. // 计税方法
  31. const TAX_TYPE = {
  32. '1': '一般计税法',
  33. '2': '简易计税法',
  34. };
  35. // 承包人材料调整类型
  36. const ADJUST_TYPE = {
  37. info: 'priceInfo', // 造价信息差额调整法
  38. coe: 'priceCoe' // 价格指数调整法
  39. };
  40. // 加载数据间隔,减少服务器压力
  41. const TIMEOUT_TIME = 500;
  42. // 导出粒度
  43. const GRANULARITY = {
  44. PROJECT: 1, //导出建设项目
  45. ENGINEERING: 2, //导出单项工程
  46. TENDER: 3 //导出单位工程
  47. };
  48. // 导出的文件类型选项
  49. const EXPORT_KIND = {
  50. Tender: 1, //投标
  51. Bid: 2, //招标
  52. Control: 3 //控制价
  53. };
  54. // 配置项
  55. const CONFIG = Object.freeze({
  56. TYPE,
  57. WHITE_SPACE,
  58. TAX_TYPE,
  59. ADJUST_TYPE,
  60. TIMEOUT_TIME,
  61. GRANULARITY,
  62. EXPORT_KIND
  63. });
  64. // 缓存项 不需要的时候需要清空
  65. let _cache = {
  66. // 失败列表
  67. failList: [],
  68. // 项目数据(不包含详细数据,项目管理数据)
  69. projectData: {},
  70. // 当前导出类型,默认投标
  71. exportKind: EXPORT_KIND.Tender,
  72. // 记录拉取的单位工程项目详细数据,导出的时候,可能会导出多个文件,只有导出第一个文件的时候需要请求数据
  73. tenderDetailMap: {}
  74. };
  75. // 返回缓存项
  76. function getItem(key) {
  77. return _cache[key] || null;
  78. }
  79. // 设置缓存项
  80. function setItem(key, value) {
  81. // 与原数据是同类型的数据才可设置成功
  82. if (_cache[key] &&
  83. Object.prototype.toString.call(_cache[key]) ===
  84. Object.prototype.toString.call(value)) {
  85. _cache[key] = value;
  86. }
  87. }
  88. // 清空缓存项
  89. function clear() {
  90. _cache.failList = [];
  91. _cache.projectData = {};
  92. _cache.exportKind = EXPORT_KIND.Tender;
  93. _cache.tenderDetailMap = {};
  94. }
  95. const CACHE = Object.freeze({
  96. getItem,
  97. setItem,
  98. clear
  99. });
  100. /*
  101. * 定义不设置一个Node方法统一进入的原因:模板化比较直观,不分开定义节点的话,调用传参也很麻烦而且不直观。
  102. * 一个节点对应一个构造方法,方便调整配置、方便其他版本开发、接手的人看起来更直观
  103. * @param {String}name 节点名
  104. * {Array}attrs 节点属性数据
  105. * {Array}failList 失败列表
  106. * @return {void}
  107. * */
  108. function Element(name, attrs) {
  109. this.name = name;
  110. let checkData = check(name, attrs);
  111. this.fail = checkData.failHints;
  112. this.attrs = checkData.filterAttrs;
  113. handleXMLEntity(this.attrs);
  114. this.children = [];
  115. _cache.failList.push(...this.fail);
  116. }
  117. /*
  118. * xml字符实体的处理,这些特殊字符不处理会导致xml文件格式出错:""、<>、&
  119. * 要先处理&amp
  120. * */
  121. let _xmlEntity = {
  122. '&': '&amp;',
  123. '\n': '&#xA;',
  124. '"': '&quot;',
  125. '\'': '&apos;',
  126. '<': '&lt;',
  127. '>': '&gt;'
  128. };
  129. // 对每个元素的所有属性值进行特殊字符处理
  130. function handleXMLEntity(attrs) {
  131. for (let attr of attrs) {
  132. if (!attr.value) {
  133. continue;
  134. }
  135. for (let [key, value] of Object.entries(_xmlEntity)) {
  136. attr.value = attr.value.replace(new RegExp(key, 'g'), value);
  137. }
  138. }
  139. }
  140. // 获取处理实体字符后的数据
  141. function getParsedData(arr) {
  142. return arr.map(data => {
  143. for (let [key, value] of Object.entries(_xmlEntity)) {
  144. data = data.replace(new RegExp(key, 'g'), value);
  145. }
  146. return data;
  147. });
  148. }
  149. /*
  150. * 检查
  151. * 创建节点时检查节点的数据
  152. * 1.长度限制minLen,maxLen
  153. * 2.值的限制,固定范围:enumeration
  154. * @param {String}eleName 节点名称
  155. * {Array}datas 需要检查的属性数据
  156. * @return {Object} failHints没通过的属性提示 filterAttrs过滤后的属性数据(失败提示在属性是必须的时候才提示,如果该属性失败了,但是是非必要属性,那么该属性不显示)
  157. * */
  158. function check(eleName, datas) {
  159. let rst = {failHints: [], filterAttrs: []};
  160. 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])))/;
  161. for (let data of datas) {
  162. // 值统一转换成String
  163. data.value = !isDef(data.value)
  164. ? DEFAULT_VALUE[data.type]
  165. ? DEFAULT_VALUE[data.type]
  166. : ''
  167. : String(data.value);
  168. if (data.whiteSpace && data.whiteSpace === WHITE_SPACE.COLLAPSE) { //处理空格相关
  169. data.value = data.value.replace(/[\r,\n,\t]/g, ' ');
  170. data.value = data.value.trim();
  171. data.value = data.value.replace(/\s{1,}/g, ' ');
  172. }
  173. /*if (!data.value && !data.minLen && !data.enumeration) { //值为空,且没有限制最小字符数,且没有限制值,则不需判断
  174. rst.filterAttrs.push(data);
  175. continue;
  176. }*/
  177. let isFail = false,
  178. tempFail = '';
  179. if (data.minLen && data.value.length < data.minLen){
  180. isFail = true;
  181. tempFail = data.failHint
  182. ? `${data.failHint}字符数不可小于${data.minLen}个。`
  183. :`${eleName}-“${data.name}”字符数不可小于${data.minLen}个。`;
  184. } else if (data.maxLen && data.value.length > data.maxLen) {
  185. isFail = true;
  186. tempFail = data.failHint
  187. ? `${data.failHint}字符数不可大于${data.maxLen}个。`
  188. : `${eleName}-“${data.name}”字符数不可大于${data.maxLen}个。`;
  189. } else if (data.enumeration && !data.enumeration.includes(data.value)) {
  190. isFail = true;
  191. let enumerationHint = data.enumerationHint
  192. ? data.enumerationHint.join(';')
  193. : data.enumeration.join(';');
  194. tempFail = data.failHint
  195. ? `${data.failHint}只能从“${enumerationHint}”中选择。`
  196. : `${eleName}-“${data.name}”只能从“${enumerationHint}”中选择。`;
  197. } else if (data.type && data.type === TYPE.DATE && !dateReg.test(data.value)) {
  198. isFail = true;
  199. tempFail = data.failHint
  200. ? `${data.failHint}日期格式必须是YYYY-MM-DD。`
  201. : `${eleName}-“${data.name}”日期格式必须是YYYY-MM-DD。`;
  202. } else if (data.type && data.type === TYPE.INT && !Number.isInteger(parseFloat(data.value))) {
  203. isFail = true;
  204. tempFail = data.failHint
  205. ? `${data.failHint}必须为整数。`
  206. : `${eleName}-“${data.name}”必须为整数。`;
  207. } else if (data.type && data.type === TYPE.DECIMAL && isNaN(parseFloat(data.value))) {
  208. isFail = true;
  209. tempFail = data.failHint
  210. ? `${data.failHint}必须为数值。`
  211. : `${eleName}-“${data.name}”必须为数值。`;
  212. } else if (data.type && data.type === TYPE.NUM2) {
  213. let v = parseFloat(data.value);
  214. if (isNaN(v)) {
  215. isFail = true;
  216. tempFail = data.failHint
  217. ? `${data.failHint}必须为数值。`
  218. : `${eleName}-“${data.name}”必须为数值。`;
  219. } else if (!data.value.length || (data.value.split('.').length > 1 && data.value.split('.')[1].length > 2)){
  220. isFail = true;
  221. }
  222. } else if (data.type && data.type === TYPE.BOOL && !['true', 'false'].includes(String(data.value))) {
  223. isFail = true;
  224. tempFail = data.failHint
  225. ? `${data.failHint}必须为true或false。`
  226. : `${eleName}-“${data.name}”必须为true或false。`;
  227. }
  228. if (!isFail || data.required) {
  229. rst.filterAttrs.push(data);
  230. }
  231. if (isFail && data.required && tempFail) {
  232. rst.failHints.push(tempFail);
  233. }
  234. }
  235. return rst;
  236. }
  237. // 等待一段时间
  238. function setTimeoutSync(handle, time) {
  239. return new Promise((resolve, reject) => {
  240. setTimeout(() => {
  241. if (handle && typeof handle === 'function') {
  242. handle();
  243. }
  244. resolve();
  245. }, time);
  246. });
  247. }
  248. // v是否定义了,不为undefined和null
  249. function isDef(v) {
  250. return typeof v !== 'undefined' && v !== null;
  251. }
  252. // v是否有值,不为undefined、null、''
  253. function hasValue(v) {
  254. return typeof v !== 'undefined' && v !== null && v !== '';
  255. }
  256. /*
  257. * 从fees数组中获取相关费用
  258. * @param {Array}fees 费用数组
  259. * {String}feeFields 费用字段
  260. * @return {Number}
  261. * @example getFee(source.fees, 'common.totalFee')
  262. * */
  263. function getFee(fees, feeFields) {
  264. if (!Array.isArray(fees)) {
  265. return 0;
  266. }
  267. let fields = feeFields.split('.');
  268. let fee = fees.find(data => data.fieldName === fields[0]);
  269. if (!fee) {
  270. return 0;
  271. }
  272. return fee[fields[1]] || 0;
  273. }
  274. /*
  275. * 根据key获取对应的基本信息、工程特征数据
  276. * @param {Array}data
  277. * {String}key
  278. * @return {String}
  279. * @example getValueByKey(source.basicInformation, 'projectScale')
  280. * */
  281. function getValueByKey(data, key) {
  282. for (let d of data) {
  283. if (d.key === key) {
  284. return d.value;
  285. }
  286. if (d.items && d.items.length > 0) {
  287. let findData = d.items.find(x => x.key === key);
  288. if (findData) {
  289. return findData.value;
  290. }
  291. }
  292. }
  293. return '';
  294. }
  295. // 数组打平成对象
  296. function arrayToObj(arr) {
  297. let rst = {};
  298. for (let data of arr) {
  299. rst[data.key] = data.value;
  300. }
  301. return rst;
  302. }
  303. /*
  304. * 检测层数
  305. * @param {Number}maxDepth(最大深度)
  306. * {Object}node(需要检测的清单树节点)
  307. * @return {Boolean}
  308. * */
  309. function validDepth(maxDepth, node) {
  310. let nodeDepth = node.depth();
  311. let allNodes = node.getPosterity();
  312. //检测相对深度
  313. for (let n of allNodes) {
  314. let relativeDepth = n.depth() - nodeDepth;
  315. if (relativeDepth > maxDepth) {
  316. return false;
  317. }
  318. }
  319. return true;
  320. }
  321. /*
  322. * 检测唯一性,有些属性在规定的数据范围内唯一
  323. * @param {Object}constraints(约束池)
  324. * {All}data(检测的数据)
  325. * {String}hint(提示已存在的内容)
  326. * {String}subHint(额外提示,有额外提示时,不用data提示)
  327. * @return {void}
  328. * */
  329. function checkUnique(constraints, data, hint, subHint) {
  330. if (constraints.includes(data)) {
  331. let failHint = subHint
  332. ? `${hint}“${subHint}”已存在`
  333. : `${hint}“${data}”已存在`;
  334. _cache.failList.push(failHint);
  335. } else if (data) {
  336. constraints.push(data);
  337. }
  338. }
  339. //根据数据的NextSiblingID进行排序,返回排序后的数组
  340. function sortByNext(datas) {
  341. let target = [],
  342. temp = {};
  343. for (let data of datas) {
  344. temp[data.ID] = {me: data, next: null, prev: null};
  345. }
  346. for (let data of datas) {
  347. let next = temp[data.NextSiblingID] || null;
  348. temp[data.ID].next = next;
  349. if (next) {
  350. next.prev = temp[data.ID];
  351. }
  352. }
  353. let first = null;
  354. for (let data of datas) {
  355. let me = temp[data.ID];
  356. if (!me.prev) {
  357. first = me;
  358. }
  359. }
  360. if (!first) {
  361. return datas;
  362. }
  363. while (first) {
  364. target.push(first.me);
  365. first = first.next;
  366. }
  367. return target;
  368. }
  369. /*
  370. * 根据粒度获取项目(不包含详细数据)数据
  371. * @param {Number}granularity 导出粒度
  372. * {Number}tenderID 单位工程ID
  373. * {String}userID 用户ID
  374. * @return {Object} 返回的数据结构:{children: [{children: []}]} 最外层为建设项目,中间为单项工程,最底层为单位工程
  375. * */
  376. async function getProjectByGranularity(granularity, tenderID, userID) {
  377. let projectData = _cache.projectData;
  378. // 没有数据,需要拉取
  379. if (!Object.keys(projectData).length) {
  380. projectData = await ajaxPost('/pm/api/getProjectByGranularity', {user_id: userID, tenderID: tenderID, granularity: granularity});
  381. _cache.projectData = projectData;
  382. }
  383. return projectData;
  384. }
  385. /*
  386. * 通过getData接口获取单位工程详细数据
  387. * @param {Number}tenderID 单位工程ID
  388. * {String}userID 用户ID
  389. * @return {Object} 跟projectObj.project的数据结构一致
  390. * */
  391. async function getTenderDetail(tenderID, userID) {
  392. // 获取单位工程详细数据
  393. let tenderDetail = _cache.tenderDetailMap[tenderID];
  394. if (!tenderDetail) {
  395. tenderDetail = PROJECT.createNew(tenderID, userID);
  396. await tenderDetail.loadDataSync();
  397. _cache.tenderDetailMap[tenderID] = tenderDetail;
  398. }
  399. return tenderDetail;
  400. }
  401. /*
  402. * 提取要导出的数据
  403. * @param {Function}entryFunc 提取数据的入口方法
  404. * {Number}granularity 导出粒度: 1-建设项目、2-单项工程、3-单位工程
  405. * {Number}exportKind 导出的文件类型:1-投标、2-招标、3-控制价
  406. * {Number}tenderID 单位工程ID
  407. * {String}userID 用户ID
  408. * @return {Array} 数据结构为:[{data: Object, exportKind: Number, fileName: String}]
  409. * */
  410. async function extractExportData(entryFunc, granularity, exportKind, tenderID, userID) {
  411. // 默认导出建设项目
  412. if (!granularity || ![1, 2, 3].includes(granularity)) {
  413. granularity = GRANULARITY.PROJECT;
  414. }
  415. // 默认导出投标文件
  416. if (!exportKind || ![1, 2, 3].includes(exportKind)) {
  417. exportKind = EXPORT_KIND.Tender;
  418. }
  419. // 拉取标段数据:建设项目、单项工程、单位工程数据
  420. let projectData = await getProjectByGranularity(granularity, tenderID, userID);
  421. if (!projectData) {
  422. throw '获取项目数据错误';
  423. }
  424. // 单项工程、单位工程按照树结构数据进行排序
  425. projectData.children = sortByNext(projectData.children);
  426. for (let engData of projectData.children) {
  427. engData.children = sortByNext(engData.children);
  428. }
  429. // 提取相关项目的详细导出数据
  430. return await entryFunc(userID, exportKind, projectData);
  431. }
  432. //转换基数表达式
  433. //1.有子项,则取固定清单对应基数
  434. //2.无子项,有基数,a.优先转换为行代号(不可自身) b.不能转换为行代号则找对应字典
  435. //3.基数中有无法转换的,设为金额
  436. function transformCalcBase(tenderDetail, node, {CalcBaseMap, FlagCalcBaseMap}) {
  437. let expr = node.data.calcBase || '';
  438. if (node.children.length) {
  439. let flag = node.getFlag();
  440. return FlagCalcBaseMap[flag] || '';
  441. }
  442. if (expr) {
  443. let illegal = false;
  444. let normalBase = _getNormalBase(expr),
  445. idBase = _getIDBase(expr);
  446. //普通基数转基数字典
  447. normalBase.forEach(base => {
  448. let replaceStr = CalcBaseMap[base];
  449. //转换成行代号的优先级比较高,进行清单匹配
  450. let flag = FlagCalcBaseMap[base];
  451. if (flag) {
  452. let flagNode = tenderDetail.mainTree.items.find(mNode => mNode.getFlag() === flag);
  453. //匹配到了 普通基数转换成行引用
  454. if (flagNode) {
  455. replaceStr = `F${flagNode.serialNo() + 1}`;
  456. }
  457. }
  458. //存在无法处理的基数
  459. if (!replaceStr) {
  460. illegal = true;
  461. return;
  462. }
  463. expr = expr.replace(new RegExp(base, 'g'), replaceStr);
  464. });
  465. //id引用转行代号引用
  466. idBase.forEach(base => {
  467. let id = base.match(/[^@]+/)[0];
  468. let theNode = tenderDetail.mainTree.getNodeByID(id),
  469. rowCode = theNode ? `F${theNode.serialNo() + 1}` : '';
  470. if (!rowCode) {
  471. illegal = true;
  472. return;
  473. }
  474. expr = expr.replace(new RegExp(base, 'g'), rowCode);
  475. });
  476. //不合法 返回金额
  477. if (illegal) {
  478. return getFee(node.data.fees, 'common.totalFee');
  479. }
  480. return expr;
  481. }
  482. //获取普通基数: {xxx}
  483. function _getNormalBase(str) {
  484. let reg = /{.+?}/g,
  485. matchs = str.match(reg);
  486. return matchs || [];
  487. }
  488. //获取id引用基数: @xxx-xxx-xx
  489. function _getIDBase(str) {
  490. let reg = /@.{36}/g,
  491. matchs = str.match(reg);
  492. return matchs || [];
  493. }
  494. }
  495. // 转换基数说明,根据转换后的基数处理
  496. //1.行引用转换为对应行的名称
  497. //2.基数字典转换为中文
  498. function transformCalcBaseState(tenderDetail, expr, CalcStateMap) {
  499. if (!expr) {
  500. return '';
  501. }
  502. expr = String(expr);
  503. //提取基数
  504. let bases = expr.split(/[\+\-\*\/]/g);
  505. //提取操作符
  506. let oprs = expr.match(/[\+\-\*\/]/g);
  507. //转换后的基数
  508. let newBase = [];
  509. let illegal = false;
  510. for (let base of bases) {
  511. //行引用转换为名称.
  512. if (/F\d+/.test(base)) {
  513. let rowCode = base.match(/\d+/)[0],
  514. node = tenderDetail.mainTree.items[rowCode - 1];
  515. if (!node || !node.data.name) {
  516. illegal = true;
  517. break;
  518. }
  519. newBase.push(node && node.data.name ? node.data.name : '');
  520. } else if (CalcStateMap[base]){ //字典转换为中文
  521. newBase.push(CalcStateMap[base]);
  522. } else if (/^\d+(\.\d+)?$/.test(base)) { //金额
  523. newBase.push(base);
  524. } else {
  525. illegal = true;
  526. break;
  527. }
  528. }
  529. if (illegal) {
  530. return '';
  531. }
  532. let newExpr = '';
  533. for (let i = 0; i < newBase.length; i++) {
  534. newExpr += newBase[i];
  535. if (oprs && oprs[i]) {
  536. newExpr += oprs[i];
  537. }
  538. }
  539. return newExpr;
  540. }
  541. // 获取工程编号表格相关数据(导出需要弹出工程编号让用户选择)
  542. function getCodeSheetData(projectData) {
  543. let curCode = '0';
  544. let sheetData = [];
  545. sheetData.push(getObj(projectData));
  546. projectData.children.forEach(eng => {
  547. sheetData.push(getObj(eng));
  548. eng.children.forEach(tender => {
  549. sheetData.push(getObj(tender));
  550. });
  551. });
  552. //建设项目父ID设置为-1
  553. if (sheetData.length) {
  554. sheetData[0].ParentID = -1;
  555. sheetData[0].code = '';
  556. }
  557. return sheetData;
  558. function getObj(data) {
  559. return {
  560. collapsed: false,
  561. ID: data.ID,
  562. ParentID: data.ParentID,
  563. NextSiblingID: data.NextSiblingID,
  564. name: data.name,
  565. code: data.code || String(curCode++)
  566. };
  567. }
  568. }
  569. // 从srcEle节点中获取元素名为eleName的元素
  570. function getElementFromSrc(srcEle, eleName) {
  571. if (!srcEle || !srcEle.children || !srcEle.children.length) {
  572. return [];
  573. }
  574. return srcEle.children.filter(ele => ele.name === eleName);
  575. }
  576. /*
  577. * 设置完工程编号后,更新原始数据的工程编号
  578. * 更新原始数据前需要将编号里的特殊字符进行转换
  579. * @param {Array}exportData 提取出来的需要导出的数据
  580. * {Array}codes 工程编号表中填写的工程编号
  581. * {String}EngineeringName 单项工程元素的名称
  582. * {String}tenderName 单位工程元素的名称
  583. * {String}codeName 编号属性的名称
  584. * @return {void}
  585. * */
  586. function setupCode(exportData, codes, EngineeringName, tenderName, codeName) {
  587. // 转换xml实体字符
  588. let parsedCodes = getParsedData(codes);
  589. // 给导出数据里的单项工程、单位工程填上用户设置的工程编号
  590. exportData.forEach(orgData => {
  591. let curIdx = 0;
  592. let engs = getElementFromSrc(orgData.data, EngineeringName);
  593. engs.forEach(eng => {
  594. eng.attrs.find(attr => attr.name === codeName).value = parsedCodes[curIdx++];
  595. let tenders = getElementFromSrc(eng, tenderName);
  596. tenders.forEach(tender => {
  597. tender.attrs.find(attr => attr.name === codeName).value = parsedCodes[curIdx++];
  598. });
  599. });
  600. });
  601. }
  602. const UTIL = Object.freeze({
  603. isDef,
  604. hasValue,
  605. setTimeoutSync,
  606. getFee,
  607. getValueByKey,
  608. arrayToObj,
  609. validDepth,
  610. checkUnique,
  611. sortByNext,
  612. getTenderDetail,
  613. getProjectByGranularity,
  614. transformCalcBase,
  615. transformCalcBaseState,
  616. getCodeSheetData,
  617. getElementFromSrc,
  618. getParsedData,
  619. setupCode
  620. });
  621. // 开始标签
  622. function _startTag(ele) {
  623. let rst = `<${ele.name}`;
  624. for (let attr of ele.attrs) {
  625. rst += ` ${attr.name}="${attr.value}"`;
  626. }
  627. rst += ele.children.length > 0 ? '>' : '/>';
  628. return rst;
  629. }
  630. // 结束标签
  631. function _endTag(ele) {
  632. return `</${ele.name}>`;
  633. }
  634. // 拼接成xml字符串
  635. function _toXMLStr(eles) {
  636. let rst = '';
  637. for (let ele of eles) {
  638. rst += _startTag(ele);
  639. if (ele.children.length > 0) {
  640. rst += _toXMLStr(ele.children);
  641. rst += _endTag(ele);
  642. }
  643. }
  644. return rst;
  645. }
  646. //格式化xml字符串
  647. function _formatXml(text) {
  648. //去掉多余的空格
  649. text = '\n' + text.replace(/>\s*?</g, ">\n<");
  650. //调整格式
  651. let rgx = /\n(<(([^\?]).+?)(?:\s|\s*?>|\s*?(\/)>)(?:.*?(?:(?:(\/)>)|(?:<(\/)\2>)))?)/mg;
  652. let nodeStack = [];
  653. let output = text.replace(rgx, function($0, all, name, isBegin, isCloseFull1, isCloseFull2, isFull1, isFull2){
  654. let isClosed = (isCloseFull1 === '/') || (isCloseFull2 === '/' ) || (isFull1 === '/') || (isFull2 === '/');
  655. let prefix = '';
  656. if (isBegin === '!') {
  657. prefix = getPrefix(nodeStack.length);
  658. } else {
  659. if (isBegin !== '/') {
  660. prefix = getPrefix(nodeStack.length);
  661. if (!isClosed) {
  662. nodeStack.push(name);
  663. }
  664. } else {
  665. nodeStack.pop();
  666. prefix = getPrefix(nodeStack.length);
  667. }
  668. }
  669. let ret = '\n' + prefix + all;
  670. return ret;
  671. });
  672. let outputText = output.substring(1);
  673. return outputText;
  674. function getPrefix(prefixIndex) {
  675. let span = ' ';
  676. let output = [];
  677. for (let i = 0 ; i < prefixIndex; ++i) {
  678. output.push(span);
  679. }
  680. return output.join('');
  681. }
  682. }
  683. /*
  684. * 根据各自费用定额的文件结构,导出文件
  685. * 每个费用定额可能导出的结果文件都不同
  686. * 比如广东18需要将一个建设项目文件,多个单位工程文件打包成一个zip文件。重庆18就没这种要求
  687. * @param {Array}codes 工程编号数据
  688. * {Array}extractData 提取的数据
  689. * {Function}setCodeFunc 各自费用定额的导出前重设用户输入的工程编号方法
  690. * {Function}saveAsFunc 各自费用定额的导出方法
  691. * @return {void}
  692. * */
  693. async function exportFile(codes, extractData, setCodeFunc, saveAsFunc) {
  694. // 编号重置后将会被导出,需要将编号进行xml字符实体转换
  695. codes = getParsedData(codes);
  696. setCodeFunc(codes, extractData);
  697. // 获取文件数据
  698. let fileData = extractData.map(extractObj => {
  699. // 转换成xml字符串
  700. let xmlStr = _toXMLStr([extractObj.data]);
  701. // 加上xml声明
  702. xmlStr = `<?xml version="1.0" encoding="utf-8"?>${xmlStr}`;
  703. // 格式化
  704. xmlStr = _formatXml(xmlStr);
  705. let blob = new Blob([xmlStr], {type: 'text/plain;charset=utf-8'});
  706. return {
  707. blob: blob,
  708. exportKind: extractObj.exportKind,
  709. fileName: extractObj.fileName
  710. };
  711. });
  712. // 导出
  713. await saveAsFunc(fileData);
  714. }
  715. return {
  716. CONFIG,
  717. CACHE,
  718. UTIL,
  719. Element,
  720. extractExportData,
  721. exportFile
  722. };
  723. })();