|
@@ -0,0 +1,272 @@
|
|
|
|
|
+/*
|
|
|
|
|
+ * @Descripttion: 导入通用代码
|
|
|
|
|
+ * @Author: vian
|
|
|
|
|
+ * @Date: 2020-09-09 10:45:54
|
|
|
|
|
+ */
|
|
|
|
|
+const INTERFACE_EXPORT_BASE = (() => {
|
|
|
|
|
+
|
|
|
|
|
+ // xml字符实体
|
|
|
|
|
+ const XMLEntity = {
|
|
|
|
|
+ ' ': 'escape{space}',
|
|
|
|
|
+ ' ': 'escape{simpleSpace}',
|
|
|
|
|
+ '	': 'escape{tab}',
|
|
|
|
|
+ '	': 'escape{simpleTab}',
|
|
|
|
|
+ '
': 'escape{return}',
|
|
|
|
|
+ '
': 'escape{simpleReturn}',
|
|
|
|
|
+ '�A;': 'escape{newLine}',
|
|
|
|
|
+ '
': 'escape{simpleNewLine}',
|
|
|
|
|
+ '<': 'escape{less}',
|
|
|
|
|
+ '>': 'escape{greater}',
|
|
|
|
|
+ '&': 'escape{and}',
|
|
|
|
|
+ '"': 'escape{quot}',
|
|
|
|
|
+ ''': 'escape{apos}'
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 避免字符实体进行转义。原文本中含有xml字符实体,转换为其他字符。
|
|
|
|
|
+ function escapeXMLEntity(str) {
|
|
|
|
|
+ for (const [key, value] of Object.entries(XMLEntity)) {
|
|
|
|
|
+ str = str.replace(new RegExp(key, 'g'), value);
|
|
|
|
|
+ }
|
|
|
|
|
+ return str;
|
|
|
|
|
+ }
|
|
|
|
|
+ // 将文本还原为字符实体
|
|
|
|
|
+ function restoreXMLEntity(str) {
|
|
|
|
|
+ for (const [key, value] of Object.entries(XMLEntity)) {
|
|
|
|
|
+ str = str.replace(new RegExp(value, 'g'), key);
|
|
|
|
|
+ }
|
|
|
|
|
+ return str;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /*
|
|
|
|
|
+ * 根据字段数组获得所要字段的值 eg: 要获取标段下的单项工程: ['标段', '单项工程'];
|
|
|
|
|
+ * @param {Object}source 源数据
|
|
|
|
|
+ * {Array}fields 字段数组
|
|
|
|
|
+ * @return {String}
|
|
|
|
|
+ * @example getValue(source, ['标段', '_文件类型'])
|
|
|
|
|
+ * */
|
|
|
|
|
+ function getValue(source, fields) {
|
|
|
|
|
+ let cur = source;
|
|
|
|
|
+ for (const field of fields) {
|
|
|
|
|
+ if (!cur[field]) {
|
|
|
|
|
+ return '';
|
|
|
|
|
+ }
|
|
|
|
|
+ cur = cur[field];
|
|
|
|
|
+ }
|
|
|
|
|
+ return cur || '';
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 获取数据类型
|
|
|
|
|
+ function _plainType(v) {
|
|
|
|
|
+ return Object.prototype.toString.call(v).slice(8, -1);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /*
|
|
|
|
|
+ * 获取某字段的值,强制返回数组,防止一些错误。如果期待返回数组,可以用此方法。
|
|
|
|
|
+ * @param {Object}source 数据源
|
|
|
|
|
+ * {Array}fields 取的字段
|
|
|
|
|
+ * @return {Array}
|
|
|
|
|
+ * @example arrayValue(source, ['标段', '单项工程'])
|
|
|
|
|
+ * */
|
|
|
|
|
+ function arrayValue(source, fields) {
|
|
|
|
|
+ let target = getValue(source, fields);
|
|
|
|
|
+ if (_plainType(target) === 'Object') {
|
|
|
|
|
+ target = [target];
|
|
|
|
|
+ } else if (_plainType(target) !== 'Array') {
|
|
|
|
|
+ target = []
|
|
|
|
|
+ }
|
|
|
|
|
+ return target;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 获取费用
|
|
|
|
|
+ function getFee(fees, fields) {
|
|
|
|
|
+ if (!Array.isArray(fees) || !fees.length) {
|
|
|
|
|
+ return '0';
|
|
|
|
|
+ }
|
|
|
|
|
+ const feeData = fees.find(fee => fee.fieldName === fields[0]);
|
|
|
|
|
+ return feeData && feeData[fields[1]] || '0';
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 合并价格
|
|
|
|
|
+ function mergeFees(feesA, feesB) {
|
|
|
|
|
+ if (!feesA) {
|
|
|
|
|
+ return feesB;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!feesB) {
|
|
|
|
|
+ return [];
|
|
|
|
|
+ }
|
|
|
|
|
+ feesB.forEach(feeB => {
|
|
|
|
|
+ const sameKindFee = feesA.find(feeA => feeA.fieldName === feeB.fieldName);
|
|
|
|
|
+ if (sameKindFee) {
|
|
|
|
|
+ Object.assign(sameKindFee, feeB);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ feesA.push(feeB);
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ return feesA;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 将A对象的一部分属性赋值到B对象上
|
|
|
|
|
+ function assignAttr(target, source, attrs, isExcepted = false) {
|
|
|
|
|
+ if (!source || !target) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ const sourceAttrs = attrs
|
|
|
|
|
+ ? isExcepted
|
|
|
|
|
+ ? Object.keys(source).filter(attr => !attrs.includes(attr))
|
|
|
|
|
+ : attrs
|
|
|
|
|
+ : Object.keys(source);
|
|
|
|
|
+ sourceAttrs.forEach(attr => {
|
|
|
|
|
+ // 如果是价格,不能简单地覆盖,要合并两个对象的价格
|
|
|
|
|
+ target[attr] = attr === 'fees' ? mergeFees(target[attr], source[attr]) : source[attr];
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 获取固定ID
|
|
|
|
|
+ function getFlag(data) {
|
|
|
|
|
+ return data.flags && data.flags[0] && data.flags[0].flag || 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 获取布尔型的数据
|
|
|
|
|
+ function getBool(v) {
|
|
|
|
|
+ return v === 'true' ? true : false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 设置成树结构数据
|
|
|
|
|
+ function setTreeData(data, parent, next) {
|
|
|
|
|
+ const defalutID = -1;
|
|
|
|
|
+ data.ID = uuid.v1();
|
|
|
|
|
+ data.ParentID = parent && parent.ID || defalutID;
|
|
|
|
|
+ data.NextSiblingID = next && next.ID || defalutID;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 递归设置树结构数据,并返回设置好的数据,递归items数组
|
|
|
|
|
+ function mergeDataRecur(parent, items) {
|
|
|
|
|
+ const rst = [];
|
|
|
|
|
+ for (let i = 0; i < items.length; i++) {
|
|
|
|
|
+ const cur = items[i];
|
|
|
|
|
+ const next = items[i + 1];
|
|
|
|
|
+ setTreeData(cur, parent, next);
|
|
|
|
|
+ rst.push(cur);
|
|
|
|
|
+ if (cur.items && cur.items.length) {
|
|
|
|
|
+ rst.push(...mergeDataRecur(cur, cur.items));
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return rst;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /*
|
|
|
|
|
+ * 递归获取相关数据,eg:获取组织措施清单下的所有子清单,该子清单们可能由分类及公式措施项各种组合组成。(参考调用处)
|
|
|
|
|
+ * @param {Object}src(数据源) {Array}fields(二维数组,数组里的成员由需要取的字段组成)
|
|
|
|
|
+ * eg: ['组织措施分类'], ['公式计算措施项'],该层数据可能由组织措施分类 或 公式计算措施项组成 (同层不可同时存在)
|
|
|
|
|
+ * {Function} 获得源数据后,需要提取的数据方法
|
|
|
|
|
+ * @return {Array}
|
|
|
|
|
+ * */
|
|
|
|
|
+ function getItemsRecur(src, fields, extractFuc) {
|
|
|
|
|
+ let itemsSrc = [];
|
|
|
|
|
+ let curField = [''];
|
|
|
|
|
+ for (const field of fields) {
|
|
|
|
|
+ itemsSrc = arrayValue(src, field);
|
|
|
|
|
+ if (itemsSrc.length) {
|
|
|
|
|
+ curField = field;
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return itemsSrc.map(itemSrc => {
|
|
|
|
|
+ const obj = extractFuc(itemSrc, curField);
|
|
|
|
|
+ obj.items = getItemsRecur(itemSrc, fields, extractFuc);
|
|
|
|
|
+ return obj;
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 递归获取相关数据,与上方的方法不同的点在:同层可能出现不同节点,上方方法暂时不取消(防止bug)
|
|
|
|
|
+ // fields内字段的顺序即决定了提取数据类型的顺序,如fields = [['gruop'], ['item']],则提取的数据同层中group数据在item数据之前
|
|
|
|
|
+ function extractItemsRecur(src, fields, extractFuc) {
|
|
|
|
|
+ const rst = [];
|
|
|
|
|
+ for (const field of fields) {
|
|
|
|
|
+ const itemsSrc = arrayValue(src, field);
|
|
|
|
|
+ if (itemsSrc.length) {
|
|
|
|
|
+ const items = itemsSrc.map(itemSrc => {
|
|
|
|
|
+ const obj = extractFuc(itemSrc, field);
|
|
|
|
|
+ obj.items = extractItemsRecur(itemSrc, fields, extractFuc);
|
|
|
|
|
+ return obj;
|
|
|
|
|
+ });
|
|
|
|
|
+ rst.push(...items);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return rst;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const UTIL = Object.freeze({
|
|
|
|
|
+ escapeXMLEntity,
|
|
|
|
|
+ restoreXMLEntity,
|
|
|
|
|
+ getValue,
|
|
|
|
|
+ arrayValue,
|
|
|
|
|
+ getFee,
|
|
|
|
|
+ mergeFees,
|
|
|
|
|
+ assignAttr,
|
|
|
|
|
+ setTreeData,
|
|
|
|
|
+ mergeDataRecur,
|
|
|
|
|
+ getFlag,
|
|
|
|
|
+ getBool,
|
|
|
|
|
+ getItemsRecur,
|
|
|
|
|
+ extractItemsRecur,
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ /*
|
|
|
|
|
+ * 读取文件转换为utf-8编码的字符串
|
|
|
|
|
+ * @param {Blob}file
|
|
|
|
|
+ * @return {Promise}
|
|
|
|
|
+ * */
|
|
|
|
|
+ function readAsTextSync(file) {
|
|
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
|
|
+ const fr = new FileReader();
|
|
|
|
|
+ fr.readAsText(file); // 默认utf-8,如果出现乱码,得看导入文件是什么编码
|
|
|
|
|
+ fr.onload = function () {
|
|
|
|
|
+ resolve(this.result);
|
|
|
|
|
+ };
|
|
|
|
|
+ fr.onerror = function () {
|
|
|
|
|
+ reject('读取文件失败,请重试。');
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param {Function} entryFunc - 各导入接口提取导入数据方法
|
|
|
|
|
+ * @param {File} file - 导入的文件
|
|
|
|
|
+ * @param {String} areaKey - 地区标识,如:'安徽@马鞍山'
|
|
|
|
|
+ * @param {String} feeRateStandard - 导入接口地区对应的费率标准名称
|
|
|
|
|
+ * @param {Boolean} escape - 是否需要避免xml中的实体字符转换
|
|
|
|
|
+ * @return {Promise<Object>}
|
|
|
|
|
+ */
|
|
|
|
|
+ async function extractImportData(entryFunc, file, areaKey, feeRateStandard, escape = false) {
|
|
|
|
|
+ const valuationID = compilationData.ration_valuation[0].id;
|
|
|
|
|
+ const templateData = await ajaxPost('/pm/api/getImportTemplateData', { user_id: userID, valuationID, feeRateStandard });
|
|
|
|
|
+ if (!templateData) {
|
|
|
|
|
+ throw '无法获取有效模板数据。';
|
|
|
|
|
+ }
|
|
|
|
|
+ console.log(templateData);
|
|
|
|
|
+ // 将二进制文件转换成字符串
|
|
|
|
|
+ let xmlStr = await readAsTextSync(file);
|
|
|
|
|
+ if (escape) {
|
|
|
|
|
+ // x2js的str to json的实现方式基于DOMParser,DOMParser会自动将一些实体字符进行转换,比如 “< to <”。如果不想进行自动转换,需要进行处理。
|
|
|
|
|
+ xmlStr = escapeXMLEntity(xmlStr);
|
|
|
|
|
+ }
|
|
|
|
|
+ // 将xml格式良好的字符串转换成对象
|
|
|
|
|
+ const x2js = new X2JS();
|
|
|
|
|
+ let xmlObj = x2js.xml_str2json(xmlStr);
|
|
|
|
|
+ xmlObj = JSON.parse(restoreXMLEntity(JSON.stringify(xmlObj)));
|
|
|
|
|
+ console.log(xmlObj);
|
|
|
|
|
+ if (!xmlObj) {
|
|
|
|
|
+ throw '无有效数据。';
|
|
|
|
|
+ }
|
|
|
|
|
+ return await entryFunc(areaKey, xmlObj, templateData);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return {
|
|
|
|
|
+ UTIL,
|
|
|
|
|
+ extractImportData,
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+})();
|