exportStdInterfaceBase.js 34 KB

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