base.js 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. /*
  2. * @Descripttion: 导入通用代码
  3. * @Author: vian
  4. * @Date: 2020-09-09 10:45:54
  5. */
  6. const INTERFACE_EXPORT_BASE = (() => {
  7. // xml字符实体
  8. const XMLEntity = {
  9. ' ': 'escape{space}',
  10. ' ': 'escape{simpleSpace}',
  11. '	': 'escape{tab}',
  12. '	': 'escape{simpleTab}',
  13. '
': 'escape{return}',
  14. '
': 'escape{simpleReturn}',
  15. '&#000A;': 'escape{newLine}',
  16. '
': 'escape{simpleNewLine}',
  17. '<': 'escape{less}',
  18. '>': 'escape{greater}',
  19. '&': 'escape{and}',
  20. '"': 'escape{quot}',
  21. ''': 'escape{apos}'
  22. };
  23. // 避免字符实体进行转义。原文本中含有xml字符实体,转换为其他字符。
  24. function escapeXMLEntity(str) {
  25. for (const [key, value] of Object.entries(XMLEntity)) {
  26. str = str.replace(new RegExp(key, 'g'), value);
  27. }
  28. return str;
  29. }
  30. // 将文本还原为字符实体
  31. function restoreXMLEntity(str) {
  32. for (const [key, value] of Object.entries(XMLEntity)) {
  33. str = str.replace(new RegExp(value, 'g'), key);
  34. }
  35. return str;
  36. }
  37. /*
  38. * 根据字段数组获得所要字段的值 eg: 要获取标段下的单项工程: ['标段', '单项工程'];
  39. * 属性需要加前缀:“_”
  40. * 节点的不需要加前缀
  41. * @param {Object}source 源数据
  42. * {Array}fields 字段数组
  43. * @return {String}
  44. * @example getValue(source, ['标段', '_文件类型'])
  45. * */
  46. function getValue(source, fields) {
  47. let cur = source;
  48. for (const field of fields) {
  49. if (!cur[field]) {
  50. return '';
  51. }
  52. cur = cur[field];
  53. }
  54. return cur || '';
  55. }
  56. // 获取数据类型
  57. function _plainType(v) {
  58. return Object.prototype.toString.call(v).slice(8, -1);
  59. }
  60. /*
  61. * 获取某字段的值,强制返回数组,防止一些错误。如果期待返回数组,可以用此方法。
  62. * @param {Object}source 数据源
  63. * {Array}fields 取的字段
  64. * @return {Array}
  65. * @example arrayValue(source, ['标段', '单项工程'])
  66. * */
  67. function arrayValue(source, fields) {
  68. let target = getValue(source, fields);
  69. if (_plainType(target) === 'Object') {
  70. target = [target];
  71. } else if (_plainType(target) !== 'Array') {
  72. target = []
  73. }
  74. return target;
  75. }
  76. // 获取费用
  77. function getFee(fees, fields) {
  78. if (!Array.isArray(fees) || !fees.length) {
  79. return '0';
  80. }
  81. const feeData = fees.find(fee => fee.fieldName === fields[0]);
  82. return feeData && feeData[fields[1]] || '0';
  83. }
  84. // 合并价格
  85. function mergeFees(feesA, feesB) {
  86. if (!feesA) {
  87. return feesB;
  88. }
  89. if (!feesB) {
  90. return [];
  91. }
  92. feesB.forEach(feeB => {
  93. const sameKindFee = feesA.find(feeA => feeA.fieldName === feeB.fieldName);
  94. if (sameKindFee) {
  95. Object.assign(sameKindFee, feeB);
  96. } else {
  97. feesA.push(feeB);
  98. }
  99. });
  100. return feesA;
  101. }
  102. // 将A对象的一部分属性赋值到B对象上
  103. function assignAttr(target, source, attrs, isExcepted = false) {
  104. if (!source || !target) {
  105. return;
  106. }
  107. const sourceAttrs = attrs
  108. ? isExcepted
  109. ? Object.keys(source).filter(attr => !attrs.includes(attr))
  110. : attrs
  111. : Object.keys(source);
  112. sourceAttrs.forEach(attr => {
  113. // 如果是价格,不能简单地覆盖,要合并两个对象的价格
  114. target[attr] = attr === 'fees' ? mergeFees(target[attr], source[attr]) : source[attr];
  115. });
  116. }
  117. // 获取固定ID
  118. function getFlag(data) {
  119. return data.flags && data.flags[0] && data.flags[0].flag || 0;
  120. }
  121. // 获取布尔型的数据
  122. function getBool(v) {
  123. return v === 'true' ? true : false;
  124. }
  125. // 设置成树结构数据
  126. function setTreeData(data, parent, next) {
  127. const defalutID = -1;
  128. data.ID = uuid.v1();
  129. data.ParentID = parent && parent.ID || defalutID;
  130. data.NextSiblingID = next && next.ID || defalutID;
  131. }
  132. // 递归设置树结构数据,并返回设置好的数据,递归items数组
  133. function mergeDataRecur(parent, items) {
  134. const rst = [];
  135. for (let i = 0; i < items.length; i++) {
  136. const cur = items[i];
  137. const next = items[i + 1];
  138. setTreeData(cur, parent, next);
  139. rst.push(cur);
  140. if (cur.items && cur.items.length) {
  141. rst.push(...mergeDataRecur(cur, cur.items));
  142. }
  143. }
  144. return rst;
  145. }
  146. /*
  147. * 递归获取相关数据,eg:获取组织措施清单下的所有子清单,该子清单们可能由分类及公式措施项各种组合组成。(参考调用处)
  148. * @param {Object}src(数据源) {Array}fields(二维数组,数组里的成员由需要取的字段组成)
  149. * eg: ['组织措施分类'], ['公式计算措施项'],该层数据可能由组织措施分类 或 公式计算措施项组成 (同层不可同时存在)
  150. * {Function} 获得源数据后,需要提取的数据方法
  151. * @return {Array}
  152. * */
  153. function getItemsRecur(src, fields, extractFuc) {
  154. let itemsSrc = [];
  155. let curField = [''];
  156. for (const field of fields) {
  157. itemsSrc = arrayValue(src, field);
  158. if (itemsSrc.length) {
  159. curField = field;
  160. break;
  161. }
  162. }
  163. return itemsSrc.map(itemSrc => {
  164. const obj = extractFuc(itemSrc, curField);
  165. obj.items = getItemsRecur(itemSrc, fields, extractFuc);
  166. return obj;
  167. });
  168. }
  169. // 递归获取相关数据,与上方的方法不同的点在:同层可能出现不同节点,上方方法暂时不取消(防止bug)
  170. // fields内字段的顺序即决定了提取数据类型的顺序,如fields = [['gruop'], ['item']],则提取的数据同层中group数据在item数据之前
  171. function extractItemsRecur(src, fields, extractFuc) {
  172. const rst = [];
  173. for (const field of fields) {
  174. const itemsSrc = arrayValue(src, field);
  175. if (itemsSrc.length) {
  176. const items = itemsSrc.map(itemSrc => {
  177. const obj = extractFuc(itemSrc, field);
  178. obj.items = extractItemsRecur(itemSrc, fields, extractFuc);
  179. return obj;
  180. });
  181. rst.push(...items);
  182. }
  183. }
  184. return rst;
  185. }
  186. const UTIL = Object.freeze({
  187. escapeXMLEntity,
  188. restoreXMLEntity,
  189. getValue,
  190. arrayValue,
  191. getFee,
  192. mergeFees,
  193. assignAttr,
  194. setTreeData,
  195. mergeDataRecur,
  196. getFlag,
  197. getBool,
  198. getItemsRecur,
  199. extractItemsRecur,
  200. });
  201. /*
  202. * 读取文件转换为utf-8编码的字符串
  203. * @param {Blob}file
  204. * @return {Promise}
  205. * */
  206. function readAsTextSync(file) {
  207. return new Promise((resolve, reject) => {
  208. const fr = new FileReader();
  209. fr.readAsText(file); // 默认utf-8,如果出现乱码,得看导入文件是什么编码
  210. fr.onload = function () {
  211. resolve(this.result);
  212. };
  213. fr.onerror = function () {
  214. reject('读取文件失败,请重试。');
  215. }
  216. });
  217. }
  218. /**
  219. *
  220. * @param {Function} entryFunc - 各导入接口提取导入数据方法
  221. * @param {File} file - 导入的文件
  222. * @param {String} areaKey - 地区标识,如:'安徽@马鞍山'
  223. * @param {String} feeRateStandard - 导入接口地区对应的费率标准名称
  224. * @param {Boolean} escape - 是否需要避免xml中的实体字符转换
  225. * @return {Promise<Object>}
  226. */
  227. async function extractImportData(entryFunc, file, areaKey, feeRateStandard, escape = false) {
  228. const valuationID = compilationData.ration_valuation[0].id;
  229. const templateData = await ajaxPost('/pm/api/getImportTemplateData', { user_id: userID, valuationID, feeRateStandard });
  230. if (!templateData) {
  231. throw '无法获取有效模板数据。';
  232. }
  233. console.log(templateData);
  234. // 将二进制文件转换成字符串
  235. let xmlStr = await readAsTextSync(file);
  236. if (escape) {
  237. // x2js的str to json的实现方式基于DOMParser,DOMParser会自动将一些实体字符进行转换,比如 “&lt; to <”。如果不想进行自动转换,需要进行处理。
  238. xmlStr = escapeXMLEntity(xmlStr);
  239. }
  240. // 将xml格式良好的字符串转换成对象
  241. const x2js = new X2JS();
  242. let xmlObj = x2js.xml_str2json(xmlStr);
  243. xmlObj = JSON.parse(restoreXMLEntity(JSON.stringify(xmlObj)));
  244. console.log(xmlObj);
  245. if (!xmlObj) {
  246. throw '无有效数据。';
  247. }
  248. return await entryFunc(areaKey, xmlObj, templateData);
  249. }
  250. return {
  251. UTIL,
  252. extractImportData,
  253. }
  254. })();