base.js 26 KB

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