base.js 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642
  1. /*
  2. * @Descripttion: 导入通用代码
  3. * @Author: vian
  4. * @Date: 2020-09-09 10:45:54
  5. */
  6. const XML_RN_STR = "@-@";
  7. const INTERFACE_EXPORT_BASE = (() => {
  8. // xml字符实体
  9. const XMLEntity = {
  10. " ": "escape{space}",
  11. " ": "escape{simpleSpace}",
  12. "	": "escape{tab}",
  13. "	": "escape{simpleTab}",
  14. "
": "escape{return}",
  15. "
": "escape{simpleReturn}",
  16. "&#000A;": "escape{newLine}",
  17. "
": "escape{simpleNewLine}",
  18. "<": "escape{less}",
  19. ">": "escape{greater}",
  20. "&": "escape{and}",
  21. """: "escape{quot}",
  22. "'": "escape{apos}",
  23. };
  24. // 避免字符实体进行转义。原文本中含有xml字符实体,转换为其他字符。
  25. function escapeXMLEntity(str) {
  26. for (const [key, value] of Object.entries(XMLEntity)) {
  27. str = str.replace(new RegExp(key, "g"), value);
  28. }
  29. return str;
  30. }
  31. // 将文本还原为字符实体
  32. function restoreXMLEntity(str) {
  33. for (const [key, value] of Object.entries(XMLEntity)) {
  34. str = str.replace(new RegExp(value, "g"), key);
  35. }
  36. return str;
  37. }
  38. /*
  39. * 根据字段数组获得所要字段的值 eg: 要获取标段下的单项工程: ['标段', '单项工程'];
  40. * 属性需要加前缀:“_”
  41. * 节点的不需要加前缀
  42. * @param {Object}source 源数据
  43. * {Array}fields 字段数组
  44. * @return {String}
  45. * @example getValue(source, ['标段', '_文件类型'])
  46. * */
  47. function getValue(source, fields) {
  48. let cur = source;
  49. for (const field of fields) {
  50. if (!cur[field]) {
  51. return "";
  52. }
  53. cur = cur[field];
  54. }
  55. if (typeof cur === "string") {
  56. cur = cur.replace(new RegExp(XML_RN_STR, "g"), "\r\n");
  57. }
  58. return cur || "";
  59. }
  60. // 获取布尔型的数据
  61. function getBool(source, fields) {
  62. return getValue(source, fields) === "true" ? true : false;
  63. }
  64. // 获取数据类型
  65. function _plainType(v) {
  66. return Object.prototype.toString.call(v).slice(8, -1);
  67. }
  68. /*
  69. * 获取某字段的值,强制返回数组,防止一些错误。如果期待返回数组,可以用此方法。
  70. * @param {Object}source 数据源
  71. * {Array}fields 取的字段
  72. * @return {Array}
  73. * @example arrayValue(source, ['标段', '单项工程'])
  74. * */
  75. function arrayValue(source, fields) {
  76. let target = getValue(source, fields);
  77. if (_plainType(target) === "Object") {
  78. target = [target];
  79. } else if (_plainType(target) !== "Array") {
  80. target = [];
  81. }
  82. return target;
  83. }
  84. // 获取费用
  85. function getFee(fees, fields) {
  86. if (!Array.isArray(fees) || !fees.length) {
  87. return "0";
  88. }
  89. const feeData = fees.find((fee) => fee.fieldName === fields[0]);
  90. return (feeData && feeData[fields[1]]) || "0";
  91. }
  92. // 合并价格
  93. function mergeFees(feesA, feesB) {
  94. if (!feesA) {
  95. return feesB;
  96. }
  97. if (!feesB) {
  98. return [];
  99. }
  100. feesB.forEach((feeB) => {
  101. const sameKindFee = feesA.find((feeA) => feeA.fieldName === feeB.fieldName);
  102. if (sameKindFee) {
  103. Object.assign(sameKindFee, feeB);
  104. } else {
  105. feesA.push(feeB);
  106. }
  107. });
  108. return feesA;
  109. }
  110. // 将A对象的属性赋值到B对象上
  111. function assignAttr(target, source, attrs) {
  112. if (!source || !target) {
  113. return;
  114. }
  115. const sourceAttrs = attrs || Object.keys(source);
  116. for (const attr of sourceAttrs) {
  117. // 如果值是undefined,则不进行赋值覆盖处理
  118. if (attr === "children" || source[attr] === undefined) {
  119. continue;
  120. }
  121. target[attr] =
  122. attr === "fees"
  123. ? mergeFees(target[attr], source[attr]) // 如果是价格,不能简单地覆盖,要合并两个对象的价格
  124. : source[attr];
  125. }
  126. }
  127. // 获取固定ID
  128. function getFlag(data) {
  129. return (data.flags && data.flags[0] && data.flags[0].flag) || 0;
  130. }
  131. // 设置成树结构数据
  132. function setTreeData(data, parent, next) {
  133. const defalutID = -1;
  134. data.ID = uuid.v1();
  135. data.ParentID = (parent && parent.ID) || defalutID;
  136. data.NextSiblingID = (next && next.ID) || defalutID;
  137. }
  138. // 递归设置树结构数据,并返回设置好的数据,递归items数组
  139. function mergeDataRecur(parent, items) {
  140. const rst = [];
  141. for (let i = 0; i < items.length; i++) {
  142. const cur = items[i];
  143. const next = items[i + 1];
  144. setTreeData(cur, parent, next);
  145. rst.push(cur);
  146. if (cur.items && cur.items.length) {
  147. rst.push(...mergeDataRecur(cur, cur.items));
  148. }
  149. }
  150. return rst;
  151. }
  152. // 递归获取相关数据,(同层可以出现不同节点)
  153. // fields内字段的顺序即决定了提取数据类型的顺序,如fields = [['gruop'], ['item']],则提取的数据同层中group数据在item数据之前
  154. function extractItemsRecur(src, fields, extractFuc) {
  155. const rst = [];
  156. for (const field of fields) {
  157. const itemsSrc = arrayValue(src, field);
  158. if (itemsSrc.length) {
  159. /* const items = itemsSrc.map(itemSrc => {
  160. const obj = extractFuc(itemSrc, field[0]);
  161. obj.children = extractItemsRecur(itemSrc, fields, extractFuc);
  162. return obj;
  163. }); */
  164. const items = [];
  165. itemsSrc.forEach((itemSrc) => {
  166. const obj = extractFuc(itemSrc, field[0]);
  167. if (obj) {
  168. if (!obj.children || obj.children.length === 0) {
  169. obj.children = extractItemsRecur(itemSrc, fields, extractFuc);
  170. }
  171. items.push(obj);
  172. }
  173. });
  174. rst.push(...items);
  175. }
  176. }
  177. return rst;
  178. }
  179. const UTIL = Object.freeze({
  180. escapeXMLEntity,
  181. restoreXMLEntity,
  182. getValue,
  183. getBool,
  184. arrayValue,
  185. getFee,
  186. mergeFees,
  187. assignAttr,
  188. setTreeData,
  189. mergeDataRecur,
  190. getFlag,
  191. extractItemsRecur,
  192. });
  193. /**
  194. * 合并基本信息或工程特征
  195. * @param {Array} source - 提取的数据
  196. * @param {Array} target - 模板数据
  197. * @return {Array}
  198. */
  199. function mergeInfo(source, target) {
  200. source.forEach((item) => mergeChild(item, target));
  201. return target;
  202. function mergeChild(item, target) {
  203. for (const child of target) {
  204. if (child.key === item.key) {
  205. child.value = item.value;
  206. return true;
  207. }
  208. if (child.items && child.items.length) {
  209. const rst = mergeChild(item, child.items);
  210. if (rst) {
  211. return true;
  212. }
  213. }
  214. }
  215. return false;
  216. }
  217. }
  218. const { fixedFlag, BillType } = window.commonConstants;
  219. // 标题类别 - flag 映射
  220. const titleTypeToFlag = {
  221. 1: fixedFlag.ONE_SEVEN_BILLS,
  222. 2: fixedFlag.PROVISIONAL_TOTAL,
  223. 3: fixedFlag.BILLS_TOTAL_WT_PROV,
  224. 4: fixedFlag.DAYWORK_LABOR,
  225. 5: fixedFlag.PROVISIONAL,
  226. 6: fixedFlag.TOTAL_COST,
  227. };
  228. function chkIfDayworkBill(billName) {
  229. let rst = false;
  230. if (typeof billName === 'string') {
  231. rst = /计日工/.test(billName) && billName.indexOf('计日工') === 0;
  232. }
  233. return rst;
  234. }
  235. /**
  236. * 将提取出来的清单合并进清单模板
  237. * @param {Array} source - 从xml提取出来的清单
  238. * @param {Array} target - 清单模板数据
  239. * @param {Object} parent - 匹配到模板清单的父清单
  240. * @param {Boolean} onlyImportMatchBills - 是否只导入文件中的清单,未匹配的模板清单不生成
  241. * @return {void}
  242. */
  243. function mergeBills(source, target, parent, matchedFlags) {
  244. source.forEach((bills, index) => {
  245. // 为了大项费用排序
  246. if (!parent) {
  247. bills.seq = index;
  248. }
  249. const simpleName = bills.name ? bills.name.replace(/\s/g, "") : "";
  250. const titleType = bills.titleType || "0";
  251. let matched;
  252. if (!parent) {
  253. if ((titleTypeToFlag[titleType] === fixedFlag.ONE_SEVEN_BILLS || /100章.*章|100章.*700章|100章.*900章/.test(simpleName)) && simpleName.indexOf('第100章') === 0) {
  254. matched = target.find((bills) => getFlag(bills) === fixedFlag.ONE_SEVEN_BILLS);
  255. } else if (titleTypeToFlag[titleType] === fixedFlag.PROVISIONAL_TOTAL || /包含在清单合计中的材料、工程设备、专业工程暂估/.test(simpleName)) {
  256. matched = target.find((bills) => getFlag(bills) === fixedFlag.PROVISIONAL_TOTAL);
  257. } else if (titleTypeToFlag[titleType] === fixedFlag.BILLS_TOTAL_WT_PROV || /清单合计减去材料、工程设备、专业工程暂估价/.test(simpleName)) {
  258. matched = target.find((bills) => getFlag(bills) === fixedFlag.BILLS_TOTAL_WT_PROV);
  259. } else if (titleTypeToFlag[titleType] === fixedFlag.DAYWORK_LABOR || chkIfDayworkBill(simpleName)) {
  260. matched = target.find((bills) => getFlag(bills) === fixedFlag.DAYWORK_LABOR);
  261. } else if (titleTypeToFlag[titleType] === fixedFlag.PROVISIONAL || /暂列金额[((]不含/.test(simpleName)) {
  262. matched = target.find((bills) => getFlag(bills) === fixedFlag.PROVISIONAL);
  263. } else if (titleTypeToFlag[titleType] === fixedFlag.TOTAL_COST || /报价/.test(simpleName)) {
  264. matched = target.find((bills) => getFlag(bills) === fixedFlag.TOTAL_COST);
  265. }
  266. } else {
  267. const parentSimpleName = parent.name ? parent.name.replace(/\s/g, "") : "";
  268. if (/100章.*章|100章.*700章|100章.*900章/.test(parentSimpleName) && /100章总则/.test(simpleName)) {
  269. matched = target.find((bills) => getFlag(bills) === fixedFlag.ONE_HUNDRED_BILLS);
  270. } else if (chkIfDayworkBill(parentSimpleName) && /劳务/.test(simpleName)) {
  271. matched = target.find((bills) => getFlag(bills) === fixedFlag.LABOUR_SERVICE);
  272. } else if (chkIfDayworkBill(parentSimpleName) && /材料/.test(simpleName)) {
  273. matched = target.find((bills) => getFlag(bills) === fixedFlag.MATERIAL);
  274. } else if (chkIfDayworkBill(parentSimpleName) && /机械/.test(simpleName)) {
  275. matched = target.find((bills) => getFlag(bills) === fixedFlag.CONSTRUCTION_MACHINE);
  276. }
  277. }
  278. if (matched) {
  279. matchedFlags.push(getFlag(matched));
  280. assignAttr(matched, bills);
  281. if (bills.children && bills.children.length) {
  282. mergeBills(bills.children, matched.children, matched, matchedFlags);
  283. }
  284. } else {
  285. target.push(bills);
  286. }
  287. });
  288. }
  289. /**
  290. * 处理清单
  291. * @param {Array} tenderBills - 从xml提取出来的清单
  292. * @param {Array} billsTarget - 拷贝一份的模板清单,用于合并提取清单
  293. * @param {Number} tenderID - 单位工程ID
  294. * @param {Boolean} onlyImportMatchBills - 是否只导入文件中的清单,未匹配的模板清单不生成
  295. * @return {Array}
  296. */
  297. function handleBills(tenderBills, billsTarget, tenderID, onlyImportMatchBills) {
  298. const rst = [];
  299. // 将提取的清单数据合并进清单模板数据
  300. const matchedFlags = [];
  301. mergeBills(tenderBills, billsTarget, null, matchedFlags);
  302. // 如果只导入文件中有的清单,除100-700/900章清单、报价清单外,其他未匹配到的固定清单模板,均不导入
  303. function removeNotMatchFlagBills(bills) {
  304. const rst = [];
  305. bills.forEach((item) => {
  306. const flag = getFlag(item);
  307. if (!flag || [fixedFlag.ONE_SEVEN_BILLS, fixedFlag.TOTAL_COST].includes(flag) || matchedFlags.includes(flag)) {
  308. rst.push(item);
  309. }
  310. if (flag && ![fixedFlag.ONE_SEVEN_BILLS, fixedFlag.TOTAL_COST].includes(flag) && item.children && item.children.length) {
  311. item.children = removeNotMatchFlagBills(item.children);
  312. }
  313. });
  314. return rst;
  315. }
  316. if (onlyImportMatchBills) {
  317. billsTarget = removeNotMatchFlagBills(billsTarget);
  318. }
  319. // 大项费用按照清单标题排序
  320. billsTarget.sort((a, b) => a.seq - b.seq);
  321. // 给清单设置数据
  322. const rowCodeData = []; // 行号数据,用于转换行引用
  323. const toBeTransformBills = []; // 待转换的清单
  324. function setBills(bills, parentID) {
  325. bills.forEach((child, index) => {
  326. rst.push(child);
  327. child.projectID = tenderID;
  328. // 如果本身清单就有ID,那不用处理,可以减少处理清单模板的一些ID引用问题
  329. if (!child.ID) {
  330. child.ID = uuid.v1();
  331. }
  332. if (child.quantity) {
  333. child.quantityEXP = child.quantity;
  334. }
  335. child.ParentID = parentID;
  336. child.type = parentID === -1 ? BillType.DXFY : BillType.BILL;
  337. child.NextSiblingID = -1;
  338. const preChild = bills[index - 1];
  339. if (preChild) {
  340. preChild.NextSiblingID = child.ID;
  341. }
  342. if (child.rowCode) {
  343. const regStr = /{[^{}]+}/.test(child.rowCode) ? child.rowCode : `\\b${child.rowCode}\\b`;
  344. rowCodeData.push({ reg: new RegExp(regStr, "g"), ID: child.ID, rowCode: child.rowCode });
  345. }
  346. if (child.tempCalcBase) {
  347. toBeTransformBills.push(child);
  348. }
  349. if (child.children && child.children.length) {
  350. setBills(child.children, child.ID);
  351. }
  352. });
  353. }
  354. setBills(billsTarget, -1);
  355. // 转换计算基数
  356. toBeTransformBills.forEach((bills) => {
  357. rowCodeData.forEach(({ reg, ID, rowCode }) => {
  358. const rowCodeReg = new RegExp(`{${rowCode}}`);
  359. // 替换行号
  360. if (rowCodeReg.test(bills.tempCalcBase)) {
  361. bills.tempCalcBase = bills.tempCalcBase.replace(rowCodeReg, `@${ID}`);
  362. } else {
  363. bills.tempCalcBase = bills.tempCalcBase.replace(reg, `@${ID}`);
  364. }
  365. // 替换基数,防止其他公司的软件导出没有{}
  366. bills.tempCalcBase = bills.tempCalcBase.replace(/.?专项暂定合计.?/g, "{专项暂定合计}");
  367. bills.tempCalcBase = bills.tempCalcBase.replace(/.?各章清单合计.?/g, "{各章清单合计}");
  368. });
  369. /* 检查基数有效性,无效则使用模板的基数 */
  370. // 消除ID引用对基数分割的影响
  371. const IDs = bills.tempCalcBase.match(/@[\da-zA-Z-]{36}/g) || [];
  372. const bases = [...IDs];
  373. const str = bills.tempCalcBase.replace(/@[\da-zA-Z-]{36}/g, "");
  374. const otherBases = str.split(/[+-/*]/).filter((item) => !!item);
  375. bases.push(...otherBases);
  376. // 判定基数有效性
  377. const isValid = bases.every((base) => {
  378. if (base === "{专项暂定合计}" || base === "{各章清单合计}") {
  379. return true;
  380. }
  381. if (/^(\d+(\.\d+)?)%?$/.test(base)) {
  382. // 数值+%(可有可无)
  383. return true;
  384. }
  385. if (/[\da-zA-Z-]{36}/.test(base)) {
  386. // ID引用
  387. return true;
  388. }
  389. return false;
  390. });
  391. if (isValid) {
  392. bills.calcBase = bills.tempCalcBase;
  393. }
  394. });
  395. rst.forEach((bills) => delete bills.children);
  396. return rst;
  397. }
  398. function getTemplateBillsTarget(templateBills) {
  399. const templateTarget = _.cloneDeep(templateBills);
  400. const ungroupedData = [];
  401. function getBillsFromChildren(billsData) {
  402. for (const bills of billsData) {
  403. ungroupedData.push(bills);
  404. if (bills.children && bills.children.length) {
  405. getBillsFromChildren(bills.children);
  406. }
  407. }
  408. }
  409. getBillsFromChildren(templateTarget);
  410. BILLS_UTIL.resetTreeData(ungroupedData, uuid.v1, true);
  411. return templateTarget;
  412. }
  413. // 处理单位工程数据
  414. function handleTenderData(tenders, templateData, rationValuationData, engineeringLib, areaKey, onlyImportMatchBills) {
  415. tenders.forEach((tender, index) => {
  416. tender.compilation = compilationData._id;
  417. tender.userID = userID;
  418. tender.ID = templateData.projectBeginID + index + 1;
  419. tender.ParentID = templateData.projectBeginID;
  420. tender.NextSiblingID = index === tenders.length - 1 ? -1 : templateData.projectBeginID + index + 2;
  421. tender.projType = projectType.tender;
  422. const featureTarget = _.cloneDeep(templateData.feature); // 必须拷贝出一份新数据,否则会被下一个单位工程覆盖
  423. if (!engineeringLib) {
  424. throw "不存在可用工程专业。";
  425. }
  426. const taxData = engineeringLib.lib.tax_group[0];
  427. const featureSource = [
  428. ...(tender.feature || []),
  429. { key: "valuationType", value: "工程量清单" }, // 导入的时候以下项不一定有数据,但是需要自动生成
  430. { key: "feeStandard", value: engineeringLib.lib.feeName },
  431. ];
  432. const needEngineering = featureTarget && featureTarget.find((item) => item.key === "engineering");
  433. if (needEngineering) {
  434. featureSource.push({ key: "engineering", value: engineeringLib.lib.name });
  435. }
  436. tender.property = {
  437. areaKey,
  438. rootProjectID: tender.ParentID,
  439. region: "全省",
  440. engineering_id: engineeringLib.engineering_id,
  441. engineeringName: engineeringLib.lib.name,
  442. feeStandardName: engineeringLib.lib.feeName,
  443. engineering: engineeringLib.engineering,
  444. isInstall: engineeringLib.lib.isInstall,
  445. projectEngineering: engineeringLib.lib.projectEngineering,
  446. valuation: rationValuationData.id,
  447. valuationName: rationValuationData.name,
  448. valuationType: commonConstants.ValuationType.BOQ, // 必为工程量清单
  449. boqType: commonConstants.BOQType.BID_SUBMISSION, // 导入后必为投标
  450. taxType: taxData.taxType,
  451. projectFeature: mergeInfo(featureSource, featureTarget),
  452. featureLibID: (engineeringLib.lib.feature_lib[0] && engineeringLib.lib.feature_lib[0].id) || "",
  453. calcProgram: { name: taxData.program_lib.name, id: taxData.program_lib.id },
  454. colLibID: taxData.col_lib.id,
  455. templateLibID: taxData.template_lib.id,
  456. unitPriceFile: { name: tender.name, id: templateData.unitPriceFileBeginID + index }, // 新建单价文件
  457. feeFile: { name: tender.name, id: `newFeeRate@@${taxData.fee_lib.id}` }, // 新建费率文件
  458. };
  459. delete tender.feature;
  460. const tenderDataBills = getTemplateBillsTarget(templateData.bills);
  461. tender.bills = handleBills(tender.bills, tenderDataBills, tender.ID, onlyImportMatchBills); // 必须要拷贝一份,否则多单位工程情况下,前单位工程的清单数据会被后单位工程的覆盖
  462. // 给暂估材料和评标材料设置项目数据
  463. const setGLJRefFunc = (glj) => {
  464. glj.ID = uuid.v1();
  465. glj.projectID = tender.ID;
  466. };
  467. if (tender.evaluationList && tender.evaluationList.length) {
  468. tender.evaluationList.forEach(setGLJRefFunc);
  469. }
  470. if (tender.bidEvaluationList && tender.bidEvaluationList.length) {
  471. tender.bidEvaluationList.forEach(setGLJRefFunc);
  472. }
  473. });
  474. }
  475. /**
  476. * 将接口中提取出来数据转换成可入库的有效数据
  477. * 因为无法保证这一套逻辑能不能兼容以后的所有接口,因此提取数据与标准数据模板的合并放在前端进行。
  478. * 当统一逻辑无法满足某一接口时,接口可以根据标准模板数据自行进行相关处理。
  479. * @param {Object} importData - 各接口从xml提取出来的数据
  480. * @param {String} areaKey - 接口地区
  481. * @param {Boolean} onlyImportMatchBills - 是否只导入文件中的清单,未匹配的模板清单不生成
  482. * @return {Promise<Object>}
  483. */
  484. async function handleImportData(importData, areaKey, onlyImportMatchBills) {
  485. const valuationID = compilationData.ration_valuation[0].id;
  486. if (!Array.isArray(importData.tenders) && !importData.tenders.length) {
  487. throw "导入的文件中不存在有效的标段数据。";
  488. }
  489. const projectCount = 1 + importData.tenders.length;
  490. // const feeName = compilationData.name === '安徽养护(2018)' ? '安徽养护' : '公路工程';
  491. // 一些接口需要根据导入文件,匹配工程专业库
  492. const rationValuationData = rationValuation && JSON.parse(rationValuation)[0]; // 只有工程量清单才能导入接口
  493. if (!rationValuationData) {
  494. throw "无法获取工程量清单计价数据";
  495. }
  496. const engineeringList = (rationValuationData.engineering_list || []).filter((item) => item.lib.visible);
  497. let engineeringLib = engineeringList[0];
  498. if (importData.engineeringName && importData.feeName) {
  499. const matchLibs = engineeringList.filter((item) => item.lib && item.lib.name === importData.engineeringName);
  500. engineeringLib = matchLibs.find((item) => item.lib.feeName === importData.feeName) || matchLibs[0] || engineeringList[0];
  501. }
  502. const engineeringID = engineeringLib.engineering_id || null;
  503. const templateData = await ajaxPost("/pm/api/getImportTemplateData", { user_id: userID, valuationID, engineeringID, projectCount });
  504. if (!templateData) {
  505. throw "无法获取有效模板数据。";
  506. }
  507. console.log(templateData);
  508. // 处理建设项目数据
  509. // 确定建设项目的名称(不允许重复)
  510. /* const sameDepthProjs = getProjs(projTreeObj.tree.selected);
  511. const matchedProject = sameDepthProjs.find(node => node.data.name === importData.name);
  512. if (matchedProject) {
  513. alert('test');
  514. importData.name += `(${moment(Date.now()).format('YYYY-MM-DD HH:mm:ss')})`;
  515. } */
  516. importData.compilation = compilationData._id;
  517. importData.userID = userID;
  518. importData.ID = templateData.projectBeginID;
  519. const { parentProjectID, preProjectID, nextProjectID } = projTreeObj.getRelProjectID(projTreeObj.tree.selected);
  520. importData.ParentID = parentProjectID;
  521. importData.preID = preProjectID;
  522. importData.NextSiblingID = nextProjectID;
  523. importData.projType = projectType.project;
  524. importData.property = {
  525. valuationType: commonConstants.ValuationType.BOQ, // 必为工程量清单
  526. boqType: commonConstants.BOQType.BID_SUBMISSION, // 导入后必为投标
  527. basicInformation: mergeInfo(importData.info, templateData.basicInfo), // 将提取的基本信息数据与标准基本信息数据进行合并(目前只赋值,没有匹配到的不追加)
  528. };
  529. const adhocInfo = importData.info.find(item => item.key === 'adhocInfoIsTotalChapterFirst');
  530. let bkAdHocInfo = null;
  531. if (adhocInfo) {
  532. bkAdHocInfo = { key: adhocInfo.key, value: adhocInfo.value };
  533. }
  534. delete importData.info;
  535. // 处理单位工程数据
  536. handleTenderData(importData.tenders, templateData, rationValuationData, engineeringLib, areaKey, onlyImportMatchBills);
  537. if (bkAdHocInfo) {
  538. importData.tenders.forEach(tender => {
  539. tender.property[bkAdHocInfo.key] = bkAdHocInfo.value;
  540. });
  541. }
  542. console.log(importData);
  543. }
  544. /*
  545. * 读取文件转换为utf-8编码的字符串
  546. * @param {Blob} file
  547. * @return {Promise}
  548. * */
  549. function readAsTextSync(file) {
  550. return new Promise((resolve, reject) => {
  551. const fr = new FileReader();
  552. fr.readAsText(file); // 默认utf-8,如果出现乱码,得看导入文件是什么编码
  553. fr.onload = function () {
  554. resolve(this.result);
  555. };
  556. fr.onerror = function () {
  557. reject("读取文件失败,请重试。");
  558. };
  559. });
  560. }
  561. /**
  562. *
  563. * @param {Function} entryFunc - 各导入接口提取导入数据方法
  564. * @param {File} file - 导入的文件
  565. * @param {String} areaKey - 地区标识,如:'安徽@马鞍山'
  566. * @param {Boolean} escape - 是否需要避免xml中的实体字符转换
  567. * @param {Boolean} onlyImportMatchBills - 是否只导入文件中的清单,未匹配的模板清单不生成
  568. * @return {Promise<Object>}
  569. */
  570. async function extractImportData(entryFunc, file, areaKey, escape = false, onlyImportMatchBills = false, handleAfterImport = null) {
  571. // 将二进制文件转换成字符串
  572. let xmlStr = await readAsTextSync(file);
  573. if (escape) {
  574. // x2js的str to json的实现方式基于DOMParser,DOMParser会自动将一些实体字符进行转换,比如 “&lt; to <”。如果不想进行自动转换,需要进行处理。
  575. xmlStr = escapeXMLEntity(xmlStr);
  576. }
  577. let reg1 = new RegExp(">\r\n", "g");
  578. xmlStr = xmlStr.replace(reg1, ">");
  579. let reg2 = new RegExp("\r\n", "g");
  580. xmlStr = xmlStr.replace(reg2, XML_RN_STR);
  581. // reg = /(?<=子目名称).+(?=单位)/g;
  582. // let matchResult = xmlStr.match(reg);
  583. // if (matchResult.length > 0) {
  584. // console.log(matchResult[0]);
  585. // }
  586. // xmlStr = xmlStr.replace(new RegExp('\r\n', 'g'), XML_RN_STR);
  587. // 将xml格式良好的字符串转换成对象
  588. const x2js = new X2JS();
  589. let xmlObj = x2js.xml_str2json(xmlStr);
  590. xmlObj = JSON.parse(restoreXMLEntity(JSON.stringify(xmlObj)));
  591. console.log(xmlObj);
  592. if (!xmlObj) {
  593. throw "无有效数据。";
  594. }
  595. const importData = await entryFunc(areaKey, xmlObj);
  596. console.log(areaKey);
  597. await handleImportData(importData, areaKey, onlyImportMatchBills);
  598. if (handleAfterImport) {
  599. handleAfterImport(importData);
  600. }
  601. return importData;
  602. }
  603. return {
  604. UTIL,
  605. extractImportData,
  606. };
  607. })();