|
|
@@ -0,0 +1,754 @@
|
|
|
+/**
|
|
|
+ * @author Zhong
|
|
|
+ * @date 2019/6/20
|
|
|
+ * @version
|
|
|
+ */
|
|
|
+const XML_EXPORT_BASE = (() => {
|
|
|
+ 'use strict';
|
|
|
+
|
|
|
+ const { isDef, hasValue } = window.commonUtil;
|
|
|
+
|
|
|
+ // 属性类型
|
|
|
+ const TYPE = {
|
|
|
+ DATE: 1, // 日期类型YYYY-MM-DD
|
|
|
+ DATE_TIME: 2, // 日期类型YYY-MM-DDTHH:mm:ss
|
|
|
+ INT: 3, // 整数类型
|
|
|
+ DECIMAL: 4, // 数值类型,不限制小数位数
|
|
|
+ NUM2: 5, // 数值类型2:最多两位小数
|
|
|
+ BOOL: 6 // 布尔型
|
|
|
+ };
|
|
|
+ // 需要特殊处理的属性类型默认空值(当一个值为undefined、null的时候,默认给赋什么值)
|
|
|
+ const DEFAULT_VALUE = {
|
|
|
+ [TYPE.INT]: '0',
|
|
|
+ [TYPE.DECIMAL]: '0',
|
|
|
+ [TYPE.NUM2]: '0',
|
|
|
+ [TYPE.BOOL]: 'false'
|
|
|
+ };
|
|
|
+ // 空白字符处理
|
|
|
+ const WHITE_SPACE = {
|
|
|
+ COLLAPSE: 1 // 移除所有空白字符(换行、回车、空格以及制表符会被替换为空格,开头和结尾的空格会被移除,而多个连续的空格会被缩减为一个单一的空格)
|
|
|
+ };
|
|
|
+ // 承包人材料调整类型
|
|
|
+ const ADJUST_TYPE = {
|
|
|
+ info: 'priceInfo', // 造价信息差额调整法
|
|
|
+ coe: 'priceCoe' // 价格指数调整法
|
|
|
+ };
|
|
|
+ // 加载数据间隔,减少服务器压力
|
|
|
+ const TIMEOUT_TIME = 400;
|
|
|
+ // 导出粒度
|
|
|
+ const GRANULARITY = {
|
|
|
+ PROJECT: 1, // 导出建设项目
|
|
|
+ ENGINEERING: 2, // 导出单项工程
|
|
|
+ TENDER: 3 // 导出单位工程
|
|
|
+ };
|
|
|
+ // 导出的文件类型选项
|
|
|
+ const EXPORT_KIND = {
|
|
|
+ BID_INVITATION: 1, // 招标
|
|
|
+ BID_SUBMISSION: 2, // 投标
|
|
|
+ CONTROL: 3 // 控制价
|
|
|
+ };
|
|
|
+ const EXPORT_KIND_NAME = {
|
|
|
+ 1: '招标',
|
|
|
+ 2: '投标',
|
|
|
+ 3: '控制价'
|
|
|
+ };
|
|
|
+ // 配置项
|
|
|
+ const CONFIG = Object.freeze({
|
|
|
+ TYPE,
|
|
|
+ WHITE_SPACE,
|
|
|
+ ADJUST_TYPE,
|
|
|
+ TIMEOUT_TIME,
|
|
|
+ GRANULARITY,
|
|
|
+ EXPORT_KIND,
|
|
|
+ EXPORT_KIND_NAME
|
|
|
+ });
|
|
|
+
|
|
|
+ // 缓存项 不需要的时候需要清空
|
|
|
+ const _cache = {
|
|
|
+ // 项目数据(不包含详细数据,项目管理数据)
|
|
|
+ projectData: {},
|
|
|
+ // 当前导出类型,默认投标
|
|
|
+ exportKind: EXPORT_KIND.BID_SUBMISSION,
|
|
|
+ // 记录拉取的单位工程项目详细数据,导出的时候,可能会导出多个文件,只有导出第一个文件的时候需要请求数据
|
|
|
+ tenderDetailMap: {}
|
|
|
+ };
|
|
|
+ // 返回缓存项
|
|
|
+ function getItem(key) {
|
|
|
+ return _cache[key] || null;
|
|
|
+ }
|
|
|
+ // 设置缓存项
|
|
|
+ function setItem(key, value) {
|
|
|
+ // 与原数据是同类型的数据才可设置成功
|
|
|
+ if (_cache[key] &&
|
|
|
+ Object.prototype.toString.call(_cache[key]) ===
|
|
|
+ Object.prototype.toString.call(value)) {
|
|
|
+ _cache[key] = value;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 清空缓存项
|
|
|
+ function clear() {
|
|
|
+ _cache.projectData = {};
|
|
|
+ _cache.exportKind = EXPORT_KIND.BID_SUBMISSION;
|
|
|
+ _cache.tenderDetailMap = {};
|
|
|
+ }
|
|
|
+ const CACHE = Object.freeze({
|
|
|
+ getItem,
|
|
|
+ setItem,
|
|
|
+ clear
|
|
|
+ });
|
|
|
+
|
|
|
+
|
|
|
+ /*
|
|
|
+ * 定义不设置一个Node方法统一进入的原因:模板化比较直观,不分开定义节点的话,调用传参也很麻烦而且不直观。
|
|
|
+ * 一个节点对应一个构造方法,方便调整配置、方便其他版本开发、接手的人看起来更直观
|
|
|
+ * @param {String}name 节点名
|
|
|
+ * {Array}attrs 节点属性数据
|
|
|
+ * @return {void}
|
|
|
+ * */
|
|
|
+ function Element(name, attrs) {
|
|
|
+ this.name = name;
|
|
|
+ this.attrs = attrs;
|
|
|
+ handleXMLEntity(this.attrs);
|
|
|
+ this.children = [];
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * xml字符实体的处理,这些特殊字符不处理会导致xml文件格式出错:""、<>、&
|
|
|
+ * 要先处理&
|
|
|
+ * */
|
|
|
+ const _xmlEntity = {
|
|
|
+ '&': '&',
|
|
|
+ '\n': '
',
|
|
|
+ '"': '"',
|
|
|
+ '\'': ''',
|
|
|
+ '<': '<',
|
|
|
+ '>': '>'
|
|
|
+ };
|
|
|
+ // 对每个元素的所有属性值进行特殊字符处理
|
|
|
+ function handleXMLEntity(attrs) {
|
|
|
+ for (const attr of attrs) {
|
|
|
+ if (!attr.value) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ for (const [key, value] of Object.entries(_xmlEntity)) {
|
|
|
+ attr.value = attr.value.replace(new RegExp(key, 'g'), value);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 获取处理实体字符后的数据
|
|
|
+ function getParsedData(arr) {
|
|
|
+ return arr.map(data => {
|
|
|
+ for (const [key, value] of Object.entries(_xmlEntity)) {
|
|
|
+ data = data.replace(new RegExp(key, 'g'), value);
|
|
|
+ }
|
|
|
+ return data;
|
|
|
+ });
|
|
|
+ }
|
|
|
+ /*
|
|
|
+ * 检查
|
|
|
+ * 创建节点时检查节点的数据(原本是用于自检,现在来处理默认值)
|
|
|
+ * @param {Array}datas 需要检查的属性数据
|
|
|
+ * @return {void}
|
|
|
+ * */
|
|
|
+ function check(datas) {
|
|
|
+ for (const data of datas) {
|
|
|
+ const isHasValue = hasValue(data.value);
|
|
|
+ // 值统一转换成String,并且处理各类型属性空值时的默认取值
|
|
|
+ data.value = !isHasValue
|
|
|
+ ? DEFAULT_VALUE[data.type]
|
|
|
+ ? DEFAULT_VALUE[data.type]
|
|
|
+ : ''
|
|
|
+ : String(data.value);
|
|
|
+ if (data.whiteSpace && data.whiteSpace === WHITE_SPACE.COLLAPSE) { //处理空格相关
|
|
|
+ data.value = data.value.replace(/[\r\n\t]/g, ' ');
|
|
|
+ data.value = data.value.trim();
|
|
|
+ data.value = data.value.replace(/\s{1,}/g, ' ');
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 等待一段时间
|
|
|
+ function setTimeoutSync(handle, time) {
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
+ setTimeout(() => {
|
|
|
+ if (handle && typeof handle === 'function') {
|
|
|
+ handle();
|
|
|
+ }
|
|
|
+ resolve();
|
|
|
+ }, time);
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * 将节点属性数据(attr数组)转换成简单key-value数据
|
|
|
+ * @param {Object}ele 元素节点数据Element实例
|
|
|
+ * @return {Object}
|
|
|
+ * */
|
|
|
+ function getPlainAttrs(ele) {
|
|
|
+ const obj = {};
|
|
|
+ ele.attrs.forEach(attr => obj[attr.name] = attr.value);
|
|
|
+ return obj;
|
|
|
+ }
|
|
|
+ /*
|
|
|
+ * 从fees数组中获取相关费用
|
|
|
+ * @param {Array}fees 费用数组
|
|
|
+ * {String}feeFields 费用字段
|
|
|
+ * @return {Number}
|
|
|
+ * @example getFee(source.fees, 'common.totalFee')
|
|
|
+ * */
|
|
|
+ function getFee(fees, feeFields) {
|
|
|
+ if (!Array.isArray(fees)) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ const fields = feeFields.split('.');
|
|
|
+ const fee = fees.find(data => data.fieldName === fields[0]);
|
|
|
+ if (!fee) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ return fee[fields[1]] || 0;
|
|
|
+ }
|
|
|
+ // 获取节点的汇总价格
|
|
|
+ function getAggregateFee(nodes) {
|
|
|
+ const total = nodes.reduce((acc, node) => {
|
|
|
+ const price = getFee(node.data.fees, 'common.totalFee');
|
|
|
+ return acc += price;
|
|
|
+ }, 0);
|
|
|
+ return scMathUtil.roundTo(total, -2);
|
|
|
+ }
|
|
|
+ // 获取固定类别行的费用
|
|
|
+ function getFeeByFlag(items, flag, feeFields) {
|
|
|
+ const node = items.find(node => node.getFlag() === flag);
|
|
|
+ return node ? getFee(node.data.fees, feeFields) : '0';
|
|
|
+ }
|
|
|
+ /*
|
|
|
+ * 根据key获取对应的基本信息、工程特征数据
|
|
|
+ * @param {Array}data
|
|
|
+ * {String}key
|
|
|
+ * @return {String}
|
|
|
+ * @example getValueByKey(source.basicInformation, 'projectScale')
|
|
|
+ * */
|
|
|
+ function getValueByKey(items, key) {
|
|
|
+ for (const item of items) {
|
|
|
+ if (item.key === key) {
|
|
|
+ return item.value;
|
|
|
+ }
|
|
|
+ if (item.items && item.items.length) {
|
|
|
+ const value = getValueByKey(item.items, key);
|
|
|
+ if (value) {
|
|
|
+ return value;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return '';
|
|
|
+ }
|
|
|
+ // 获取关联材料
|
|
|
+ function getRelGLJ(allGLJs, gljId) {
|
|
|
+ return allGLJs.find(glj => glj.id === gljId);
|
|
|
+ }
|
|
|
+ // 随机生成机器信息码:CPU信息;硬盘序列号;mac地址;
|
|
|
+ // 保存在localStorage中
|
|
|
+ function generateHardwareId() {
|
|
|
+ const hardwareCacheId = window.localStorage.getItem('hardwareId');
|
|
|
+ if (hardwareCacheId) {
|
|
|
+ return hardwareCacheId;
|
|
|
+ }
|
|
|
+ const charList = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D',
|
|
|
+ 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U',
|
|
|
+ 'V', 'W', 'X', 'Y', 'Z'];
|
|
|
+ function generateCpuId() {
|
|
|
+ let id = '';
|
|
|
+ let count = 16;
|
|
|
+ while (count--) {
|
|
|
+ const randomIdx = parseInt(Math.random() * 16);
|
|
|
+ id += charList[randomIdx];
|
|
|
+ }
|
|
|
+ return id;
|
|
|
+ }
|
|
|
+ function generateDiskId() {
|
|
|
+ let id = '';
|
|
|
+ let count = 8;
|
|
|
+ while (count--) {
|
|
|
+ const randomIdx = parseInt(Math.random() * 36);
|
|
|
+ id += charList[randomIdx];
|
|
|
+ }
|
|
|
+ return id;
|
|
|
+ }
|
|
|
+ function generateMacId() {
|
|
|
+ const idList = [];
|
|
|
+ let outerCount = 6;
|
|
|
+ while (outerCount--) {
|
|
|
+ let tempId = '';
|
|
|
+ let innerCount = 2;
|
|
|
+ while (innerCount--) {
|
|
|
+ const randomIdx = parseInt(Math.random() * 16);
|
|
|
+ tempId += charList[randomIdx];
|
|
|
+ }
|
|
|
+ idList.push(tempId);
|
|
|
+ }
|
|
|
+ return idList.join('-');
|
|
|
+ }
|
|
|
+ const cpuId = generateCpuId();
|
|
|
+ const diskId = generateDiskId();
|
|
|
+ const macId = generateMacId();
|
|
|
+ const hardwareId = [cpuId, diskId, macId].join(';');
|
|
|
+ window.localStorage.setItem('hardwareId', hardwareId);
|
|
|
+ return hardwareId;
|
|
|
+ }
|
|
|
+ // 数组打平成对象
|
|
|
+ function arrayToObj(arr) {
|
|
|
+ const rst = {};
|
|
|
+ for (const data of arr) {
|
|
|
+ rst[data.key] = data.value;
|
|
|
+ }
|
|
|
+ return rst;
|
|
|
+ }
|
|
|
+ /*
|
|
|
+ * 检测层数是否有效
|
|
|
+ * @param {Number}maxDepth(最大深度)
|
|
|
+ * {Object}node(需要检测的清单树节点)
|
|
|
+ * @return {Boolean}
|
|
|
+ * */
|
|
|
+ function validDepth(maxDepth, node) {
|
|
|
+ const nodeDepth = node.depth();
|
|
|
+ const allNodes = node.getPosterity();
|
|
|
+ //检测相对深度
|
|
|
+ for (const n of allNodes) {
|
|
|
+ const relativeDepth = n.depth() - nodeDepth;
|
|
|
+ if (relativeDepth > maxDepth) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 根据数据的NextSiblingID进行排序,返回排序后的数组
|
|
|
+ function sortByNext(datas) {
|
|
|
+ const target = [];
|
|
|
+ const temp = {};
|
|
|
+ for (const data of datas) {
|
|
|
+ temp[data.ID] = { me: data, next: null, prev: null };
|
|
|
+ }
|
|
|
+ for (const data of datas) {
|
|
|
+ const next = temp[data.NextSiblingID] || null;
|
|
|
+ temp[data.ID].next = next;
|
|
|
+ if (next) {
|
|
|
+ next.prev = temp[data.ID];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ let first = null;
|
|
|
+ for (const data of datas) {
|
|
|
+ const me = temp[data.ID];
|
|
|
+ if (!me.prev) {
|
|
|
+ first = me;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (!first) {
|
|
|
+ return datas;
|
|
|
+ }
|
|
|
+ while (first) {
|
|
|
+ target.push(first.me);
|
|
|
+ first = first.next;
|
|
|
+ }
|
|
|
+ return target;
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * 根据粒度获取项目(不包含详细数据)数据
|
|
|
+ * @param {Number}granularity 导出粒度
|
|
|
+ * {Object}summaryObj 汇总字段
|
|
|
+ * {Number}tenderID 单位工程ID
|
|
|
+ * {String}userID 用户ID
|
|
|
+ * @return {Object} 返回的数据结构:{children: [{children: []}]} 最外层为建设项目,中间为单项工程,最底层为单位工程
|
|
|
+ * */
|
|
|
+ async function getProjectByGranularity(granularity, summaryObj, tenderID, userID) {
|
|
|
+ let projectData = _cache.projectData;
|
|
|
+ // 没有数据,需要拉取
|
|
|
+ if (!Object.keys(projectData).length) {
|
|
|
+ projectData = await ajaxPost('/pm/api/getProjectByGranularity', { user_id: userID, tenderID, granularity, summaryObj });
|
|
|
+ _cache.projectData = projectData;
|
|
|
+ }
|
|
|
+ return projectData;
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * 通过getData接口获取单位工程详细数据(带缓存功能)
|
|
|
+ * @param {Number}tenderID 单位工程ID
|
|
|
+ * {String}userID 用户ID
|
|
|
+ * @return {Object} 跟projectObj.project的数据结构一致
|
|
|
+ * */
|
|
|
+ async function getTenderDetail(tenderID, userID) {
|
|
|
+ // 获取单位工程详细数据
|
|
|
+ let tenderDetail = _cache.tenderDetailMap[tenderID];
|
|
|
+ if (!tenderDetail) {
|
|
|
+ tenderDetail = PROJECT.createNew(tenderID, userID);
|
|
|
+ await tenderDetail.loadDataSync();
|
|
|
+ // 标记序号
|
|
|
+ const count = Object.keys(_cache.tenderDetailMap).length;
|
|
|
+ tenderDetail.serialNo = count + 1;
|
|
|
+ _cache.tenderDetailMap[tenderID] = tenderDetail;
|
|
|
+ }
|
|
|
+ return tenderDetail;
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * 提取要导出的数据
|
|
|
+ * @param {Function}entryFunc 提取数据的入口方法
|
|
|
+ * {Number}granularity 导出粒度: 1-建设项目、2-单项工程、3-单位工程
|
|
|
+ * {Number}exportKind 导出的文件类型:1-投标、2-招标、3-控制价
|
|
|
+ * {Number}tenderID 单位工程ID
|
|
|
+ * {String}userID 用户ID
|
|
|
+ * @return {Array} 数据结构为:[{data: Object, exportKind: Number, fileName: String}]
|
|
|
+ * */
|
|
|
+ async function extractExportData(entryFunc, granularity, summaryObj, exportKind, tenderID, userID) {
|
|
|
+ // 默认导出建设项目
|
|
|
+ if (!granularity || ![1, 2, 3].includes(granularity)) {
|
|
|
+ granularity = GRANULARITY.PROJECT;
|
|
|
+ }
|
|
|
+ // 默认导出投标文件
|
|
|
+ if (!exportKind || ![1, 2, 3].includes(exportKind)) {
|
|
|
+ exportKind = EXPORT_KIND.BID_SUBMISSION;
|
|
|
+ }
|
|
|
+ // 拉取标段数据:建设项目、单项工程、单位工程数据
|
|
|
+ const projectData = await getProjectByGranularity(granularity, summaryObj, tenderID, userID);
|
|
|
+ if (!projectData) {
|
|
|
+ throw '获取项目数据错误';
|
|
|
+ }
|
|
|
+ // 单项工程、单位工程按照树结构数据进行排序
|
|
|
+ projectData.children = sortByNext(projectData.children);
|
|
|
+ for (const engData of projectData.children) {
|
|
|
+ engData.children = sortByNext(engData.children);
|
|
|
+ }
|
|
|
+ // 提取相关项目的详细导出数据
|
|
|
+ return await entryFunc(userID, exportKind, projectData);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取普通基数: {xxx}
|
|
|
+ function getNormalBase(str) {
|
|
|
+ const reg = /{.+?}/g;
|
|
|
+ const matchs = str.match(reg);
|
|
|
+ return matchs || [];
|
|
|
+ }
|
|
|
+ // 获取id引用基数: @xxx-xxx-xx
|
|
|
+ function getIDBase(str) {
|
|
|
+ const reg = /@.{36}/g;
|
|
|
+ const matchs = str.match(reg);
|
|
|
+ return matchs || [];
|
|
|
+ }
|
|
|
+
|
|
|
+ // 转换基数表达式
|
|
|
+ // 1.有子项,则取固定清单对应基数
|
|
|
+ // 2.无子项,有基数,a.优先转换为行代号(不可自身) b.不能转换为行代号则找对应字典
|
|
|
+ // 3.基数中有无法转换的,根据导出类型决定
|
|
|
+ function transformCalcBase(exportKind, tenderDetail, node, { CalcBaseMap, FlagCalcBaseMap }) {
|
|
|
+ let expr = node.data.calcBase || '';
|
|
|
+ if (node.children.length) {
|
|
|
+ const flag = node.getFlag();
|
|
|
+ return FlagCalcBaseMap[flag] || '';
|
|
|
+ }
|
|
|
+ if (expr) {
|
|
|
+ let illegal = false;
|
|
|
+ const normalBase = getNormalBase(expr);
|
|
|
+ const idBase = getIDBase(expr);
|
|
|
+ // 普通基数转基数字典
|
|
|
+ normalBase.forEach(base => {
|
|
|
+ let replaceStr = CalcBaseMap[base];
|
|
|
+ // 转换成行代号的优先级比较高,进行清单匹配
|
|
|
+ const flag = FlagCalcBaseMap[base];
|
|
|
+ if (flag) {
|
|
|
+ const flagNode = tenderDetail.mainTree.items.find(mNode => mNode.getFlag() === flag);
|
|
|
+ // 匹配到了 普通基数转换成行引用
|
|
|
+ if (flagNode) {
|
|
|
+ replaceStr = `F${flagNode.serialNo() + 1}`;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 存在无法处理的基数
|
|
|
+ if (!replaceStr) {
|
|
|
+ illegal = true;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ expr = expr.replace(new RegExp(base, 'g'), replaceStr);
|
|
|
+ });
|
|
|
+ // id引用转行代号引用
|
|
|
+ idBase.forEach(base => {
|
|
|
+ const id = base.match(/[^@]+/)[0];
|
|
|
+ const theNode = tenderDetail.mainTree.getNodeByID(id);
|
|
|
+ const rowCode = theNode ? `F${theNode.serialNo() + 1}` : '';
|
|
|
+ if (!rowCode) {
|
|
|
+ illegal = true;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ expr = expr.replace(new RegExp(base, 'g'), rowCode);
|
|
|
+ });
|
|
|
+ // 不合法
|
|
|
+ // 在我们软件中的基数无法找到映射代号的情况下
|
|
|
+ // 导出招标、控制价时,基数为空
|
|
|
+ // 导出投标时,基数=综合合价/费率
|
|
|
+ if (illegal) {
|
|
|
+ if (exportKind === EXPORT_KIND.BID_INVITATION || exportKind === EXPORT_KIND.CONTROL) {
|
|
|
+ return '';
|
|
|
+ } else {
|
|
|
+ const totalFee = getFee(node.data.fees, 'common.totalFee');
|
|
|
+ const feeRate = node.data.feeRate;
|
|
|
+ return +feeRate ? scMathUtil.roundTo(totalFee / (feeRate / 100), -2) : totalFee
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return expr;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 转换基数说明,根据转换后的基数处理
|
|
|
+ // 1.行引用转换为对应行的名称
|
|
|
+ // 2.基数字典转换为中文
|
|
|
+ function transformCalcBaseState(tenderDetail, expr, CalcStateMap) {
|
|
|
+ if (!expr) {
|
|
|
+ return '';
|
|
|
+ }
|
|
|
+ expr = String(expr);
|
|
|
+ // 提取基数
|
|
|
+ const bases = expr.split(/[\+\-\*\/]/g);
|
|
|
+ // 提取操作符
|
|
|
+ const oprs = expr.match(/[\+\-\*\/]/g);
|
|
|
+ // 转换后的基数
|
|
|
+ const newBase = [];
|
|
|
+ let illegal = false;
|
|
|
+ for (const base of bases) {
|
|
|
+ // 行引用转换为名称.
|
|
|
+ if (/F\d+/.test(base)) {
|
|
|
+ const rowCode = base.match(/\d+/)[0];
|
|
|
+ const node = tenderDetail.mainTree.items[rowCode - 1];
|
|
|
+ if (!node || !node.data.name) {
|
|
|
+ illegal = true;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ newBase.push(node && node.data.name ? node.data.name : '');
|
|
|
+ } else if (CalcStateMap[base]) { // 字典转换为中文
|
|
|
+ newBase.push(CalcStateMap[base]);
|
|
|
+ } else if (/^\d+(\.\d+)?$/.test(base)) { // 金额
|
|
|
+ newBase.push(base);
|
|
|
+ } else {
|
|
|
+ illegal = true;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (illegal) {
|
|
|
+ return '';
|
|
|
+ }
|
|
|
+ let newExpr = '';
|
|
|
+ for (let i = 0; i < newBase.length; i++) {
|
|
|
+ newExpr += newBase[i];
|
|
|
+ if (oprs && oprs[i]) {
|
|
|
+ newExpr += oprs[i];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return newExpr;
|
|
|
+ }
|
|
|
+ // 获取工程编号表格相关数据(导出需要弹出工程编号让用户选择)
|
|
|
+ function getCodeSheetData(projectData) {
|
|
|
+ let curCode = '0';
|
|
|
+ let sheetData = [];
|
|
|
+ sheetData.push(getObj(projectData));
|
|
|
+ projectData.children.forEach(eng => {
|
|
|
+ sheetData.push(getObj(eng));
|
|
|
+ eng.children.forEach(tender => {
|
|
|
+ sheetData.push(getObj(tender));
|
|
|
+ });
|
|
|
+ });
|
|
|
+ // 建设项目父ID设置为-1
|
|
|
+ if (sheetData.length) {
|
|
|
+ sheetData[0].ParentID = -1;
|
|
|
+ sheetData[0].code = '';
|
|
|
+ }
|
|
|
+ return sheetData;
|
|
|
+ function getObj(data) {
|
|
|
+ return {
|
|
|
+ collapsed: false,
|
|
|
+ ID: data.ID,
|
|
|
+ ParentID: data.ParentID,
|
|
|
+ NextSiblingID: data.NextSiblingID,
|
|
|
+ name: data.name,
|
|
|
+ code: data.code || String(curCode++)
|
|
|
+ };
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 获取节点的某属性
|
|
|
+ function getAttr(ele, name) {
|
|
|
+ return (ele.attrs.find(attr => attr.name === name) || {}).value;
|
|
|
+ }
|
|
|
+ // 设置节点的某属性
|
|
|
+ function setAttr(ele, name, value) {
|
|
|
+ const attr = ele.attrs.find(attr => attr.name === name);
|
|
|
+ if (attr) {
|
|
|
+ attr.value = value;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 从srcEle节点中获取元素名为eleName的元素
|
|
|
+ function getElementFromSrc(srcEle, eleName) {
|
|
|
+ if (!srcEle || !srcEle.children || !srcEle.children.length) {
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+ return srcEle.children.filter(ele => ele.name === eleName);
|
|
|
+ }
|
|
|
+ /*
|
|
|
+ * 设置完工程编号后,更新原始数据的工程编号
|
|
|
+ * 更新原始数据前需要将编号里的特殊字符进行转换
|
|
|
+ * @param {Array}exportData 提取出来的需要导出的数据
|
|
|
+ * {Array}codes 工程编号表中填写的工程编号
|
|
|
+ * {String}EngineeringName 单项工程元素的名称
|
|
|
+ * {String}tenderName 单位工程元素的名称
|
|
|
+ * {String}codeName 编号属性的名称
|
|
|
+ * @return {void}
|
|
|
+ * */
|
|
|
+ function setupCode(exportData, codes, EngineeringName, tenderName, codeName) {
|
|
|
+ // 转换xml实体字符
|
|
|
+ let parsedCodes = getParsedData(codes);
|
|
|
+ // 给导出数据里的单项工程、单位工程填上用户设置的工程编号
|
|
|
+ exportData.forEach(orgData => {
|
|
|
+ let curIdx = 0;
|
|
|
+ let engs = getElementFromSrc(orgData.data, EngineeringName);
|
|
|
+ engs.forEach(eng => {
|
|
|
+ eng.attrs.find(attr => attr.name === codeName).value = parsedCodes[curIdx++];
|
|
|
+ let tenders = getElementFromSrc(eng, tenderName);
|
|
|
+ tenders.forEach(tender => {
|
|
|
+ tender.attrs.find(attr => attr.name === codeName).value = parsedCodes[curIdx++];
|
|
|
+ });
|
|
|
+ });
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ const UTIL = Object.freeze({
|
|
|
+ isDef,
|
|
|
+ hasValue,
|
|
|
+ setTimeoutSync,
|
|
|
+ getFee,
|
|
|
+ getAggregateFee,
|
|
|
+ getFeeByFlag,
|
|
|
+ getPlainAttrs,
|
|
|
+ getValueByKey,
|
|
|
+ getRelGLJ,
|
|
|
+ generateHardwareId,
|
|
|
+ arrayToObj,
|
|
|
+ validDepth,
|
|
|
+ sortByNext,
|
|
|
+ getTenderDetail,
|
|
|
+ getProjectByGranularity,
|
|
|
+ getNormalBase,
|
|
|
+ getIDBase,
|
|
|
+ transformCalcBase,
|
|
|
+ transformCalcBaseState,
|
|
|
+ getCodeSheetData,
|
|
|
+ getElementFromSrc,
|
|
|
+ getAttr,
|
|
|
+ setAttr,
|
|
|
+ getParsedData,
|
|
|
+ setupCode,
|
|
|
+ });
|
|
|
+
|
|
|
+ // 开始标签
|
|
|
+ function _startTag(ele) {
|
|
|
+ let rst = `<${ele.name}`;
|
|
|
+ for (const attr of ele.attrs) {
|
|
|
+ rst += ` ${attr.name}="${attr.value}"`;
|
|
|
+ }
|
|
|
+ rst += ele.children.length > 0 ? '>' : '/>';
|
|
|
+ return rst;
|
|
|
+ }
|
|
|
+ // 结束标签
|
|
|
+ function _endTag(ele) {
|
|
|
+ return `</${ele.name}>`;
|
|
|
+ }
|
|
|
+ // 拼接成xml字符串
|
|
|
+ function _toXMLStr(eles) {
|
|
|
+ let rst = '';
|
|
|
+ for (const ele of eles) {
|
|
|
+ rst += _startTag(ele);
|
|
|
+ if (ele.children.length > 0) {
|
|
|
+ rst += _toXMLStr(ele.children);
|
|
|
+ rst += _endTag(ele);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return rst;
|
|
|
+ }
|
|
|
+ // 格式化xml字符串
|
|
|
+ function _formatXml(text) {
|
|
|
+ // 去掉多余的空格
|
|
|
+ text = '\n' + text.replace(/>\s*?</g, ">\n<");
|
|
|
+ // 调整格式
|
|
|
+ const reg = /\n(<(([^\?]).+?)(?:\s|\s*?>|\s*?(\/)>)(?:.*?(?:(?:(\/)>)|(?:<(\/)\2>)))?)/mg;
|
|
|
+ const nodeStack = [];
|
|
|
+ const output = text.replace(reg, function ($0, all, name, isBegin, isCloseFull1, isCloseFull2, isFull1, isFull2) {
|
|
|
+ const isClosed = (isCloseFull1 === '/') || (isCloseFull2 === '/') || (isFull1 === '/') || (isFull2 === '/');
|
|
|
+ let prefix = '';
|
|
|
+ if (isBegin === '!') {
|
|
|
+ prefix = getPrefix(nodeStack.length);
|
|
|
+ } else {
|
|
|
+ if (isBegin !== '/') {
|
|
|
+ prefix = getPrefix(nodeStack.length);
|
|
|
+ if (!isClosed) {
|
|
|
+ nodeStack.push(name);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ nodeStack.pop();
|
|
|
+ prefix = getPrefix(nodeStack.length);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return '\n' + prefix + all;
|
|
|
+ });
|
|
|
+ return output.substring(1);
|
|
|
+
|
|
|
+ function getPrefix(prefixIndex) {
|
|
|
+ const span = ' ';
|
|
|
+ const output = [];
|
|
|
+ for (let i = 0; i < prefixIndex; i++) {
|
|
|
+ output.push(span);
|
|
|
+ }
|
|
|
+ return output.join('');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * 根据各自费用定额的文件结构,导出文件
|
|
|
+ * 每个费用定额可能导出的结果文件都不同
|
|
|
+ * 比如广东18需要将一个建设项目文件,多个单位工程文件打包成一个zip文件。重庆18就没这种要求
|
|
|
+ * @param {Array}extractData 提取的数据
|
|
|
+ * {Function}saveAsFunc 各自费用定额的导出方法,适应不同接口需要不同的最终文件形式
|
|
|
+ * @return {Array || void}
|
|
|
+ * */
|
|
|
+ async function exportFile(extractData, saveAsFunc = defaultSaveAs) {
|
|
|
+ // 获取文件数据
|
|
|
+ const fileData = extractData.map(extractObj => {
|
|
|
+ // 转换成xml字符串
|
|
|
+ let xmlStr = _toXMLStr([extractObj.data]);
|
|
|
+ // 加上xml声明
|
|
|
+ xmlStr = `<?xml version="1.0" encoding="utf-8"?>${xmlStr}`;
|
|
|
+ // 格式化
|
|
|
+ xmlStr = _formatXml(xmlStr);
|
|
|
+ const blob = new Blob([xmlStr], { type: 'text/plain;charset=utf-8' });
|
|
|
+ return {
|
|
|
+ blob: blob,
|
|
|
+ exportKind: extractObj.exportKind,
|
|
|
+ fileName: extractObj.fileName
|
|
|
+ };
|
|
|
+ });
|
|
|
+ if (!saveAsFunc) {
|
|
|
+ return fileData;
|
|
|
+ }
|
|
|
+ // 导出
|
|
|
+ await saveAsFunc(fileData);
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * 默认的通用导出文件方法:一个文件数据对应一个xml文件(变更后缀)
|
|
|
+ * @param {Array}fileData 文件数据
|
|
|
+ * @return {void}
|
|
|
+ * */
|
|
|
+ async function defaultSaveAs(fileData) {
|
|
|
+ fileData.forEach(fileItem => saveAs(fileItem.blob, fileItem.fileName));
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+ CONFIG,
|
|
|
+ CACHE,
|
|
|
+ UTIL,
|
|
|
+ Element,
|
|
|
+ extractExportData,
|
|
|
+ exportFile,
|
|
|
+ };
|
|
|
+})();
|