/**
 * @author Zhong
 * @date 2019/6/20
 * @version
 */
const INTERFACE_EXPORT_BASE = (() => {
  'use strict';

  const {
    hasValue,
    isHan
  } = 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,
    EXPORT_KIND
  } = window.commonConstants;
  /* const EXPORT_KIND_NAME = {
      1: '招标',
      2: '投标',
      3: '控制价'
  }; */
  // 配置项
  const CONFIG = Object.freeze({
    TYPE,
    WHITE_SPACE,
    ADJUST_TYPE,
    TIMEOUT_TIME,
  });

  // 缓存项 不需要的时候需要清空
  const _cache = {
    // 错误提示列表
    failList: [],
    // 项目数据（不包含详细数据，项目管理数据）
    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.failList = [];
    _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;
    this.attrs = check(this.attrs);
    handleXMLEntity(this.attrs);
    this.children = [];
  }

  /*
   * xml字符实体的处理，这些特殊字符不处理会导致xml文件格式出错：""、<>、&
   * 要先处理&amp
   * */
  const _xmlEntity = {
    '&': '&amp;',
    '\n': '&#xA;',
    '"': '&quot;',
    '\'': '&apos;',
    '<': '&lt;',
    '>': '&gt;'
  };
  // 对每个元素的所有属性值进行特殊字符处理
  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;
    });
  }

  // 获取Date类型默认值
  function getDateTypeDefaultValue(date) {
    const month = String(date.getMonth() + 1);
    const formattedMonth = month.length === 1 ? `0${month}` : month;
    const day = String(date.getDate());
    const formattedDay = day.length === 1 ? `0${day}` : day;
    return `${date.getFullYear()}-${formattedMonth}-${formattedDay}`;
  }

  /*
   * 检查
   * 创建节点时检查节点的数据（原本是用于自检，现在来处理默认值）
   * @param {Array}datas 需要检查的属性数据
   * @return {Array}
   * */
  function check(datas) {
    const rst = [];
    for (const data of datas) {
      // 错误提示
      if (data.fail && data.fail.hint) {
        _cache.failList.push(data.fail);
      }
      const isHasValue = hasValue(data.value);
      // 设置了mustHasValue，意味着必须要有值才输出该属性
      if (data.mustHasValue && !isHasValue) {
        continue;
      }
      rst.push(data);
      // 值统一转换成String，并且处理各类型属性空值时的默认取值
      data.value = !isHasValue ?
        DEFAULT_VALUE[data.type] ?
          DEFAULT_VALUE[data.type] :
          '' :
        String(data.value);
      // 如果有限定最少长度，则当长度不够时，自动凑
      const autoStr = '1';
      if (data.minLen && data.value.length < data.minLen) {
        let diff = data.minLen - data.value.length;
        while (diff--) {
          data.value += autoStr;
        }
      }
      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, ' ');
      }
      // 类型对应得值不正确时，赋类型对应默认值
      if (!data.type) {
        continue;
      }
      const 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])))/;
      if (data.type === TYPE.DATE && !dateReg.test(data.value)) {
        data.value = getDateTypeDefaultValue(new Date());
      } else if (data.type === TYPE.INT && !Number.isInteger(parseFloat(data.value))) {
        data.value = DEFAULT_VALUE[TYPE.INT];
      } else if (data.type === TYPE.DECIMAL && isNaN(parseFloat(data.value))) {
        data.value = DEFAULT_VALUE[TYPE.DECIMAL];
      } else if (data.type === TYPE.NUM2) {
        data.value = DEFAULT_VALUE[TYPE.NUM2];
      } else if (data.type === TYPE.BOOL && !['true', 'false'].includes(String(data.value))) {
        data.value = DEFAULT_VALUE[TYPE.BOOL];
      }

      //自动补0
      if (data.toFix) {
        data.value = scMathUtil.roundToString(data.value,data.toFix)
      }
    }
    return rst;
  }
  // 等待一段时间
  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, exportKind = null) {
    if (exportKind === EXPORT_KIND.BID_INVITATION || !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 getUnitFee(fee,quantity,decimal){
    if(!!+quantity && +quantity !== 0){
      return scMathUtil.roundForObj(fee/parseFloat(quantity),decimal)
    }
    return fee
  }

  // 获取节点的汇总价格
  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';
  }
  // 获取固定类别节点
  function getNodeByFlag(tree, flag) {
    return tree.items.find(node => node.getFlag() === flag);
  }
  /*
   * 根据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 '';
  }


  //获取当前日期，格式YYYY-MM-DD
  function getNowFormatDay(nowDate) {
    var char = "-";
    if (nowDate == null) {
      nowDate = new Date();
    }
    var day = nowDate.getDate();
    var month = nowDate.getMonth() + 1; //注意月份需要+1
    var year = nowDate.getFullYear();
    //补全0，并拼接
    return year + char + completeDate(month) + char + completeDate(day);
  }

  //获取当前时间，格式YYYY-MM-DD HH:mm
  function getNowFormatTime(T) {
    var nowDate = new Date();
    var colon = ":";
    var h = nowDate.getHours();
    var m = nowDate.getMinutes();
    var s = nowDate.getSeconds();
    if (T) {
      return getNowFormatDay(nowDate) + "T" + completeDate(h) + colon + completeDate(m)+colon+completeDate(s);
    }

    //补全0，并拼接
    return getNowFormatDay(nowDate) + " " + completeDate(h) + colon + completeDate(m);
  }

  //补全0
  function completeDate(value) {
    return value < 10 ? "0" + value : value;
  }

  // 获取关联材料
  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}requestForSummaryInfo 项目表级汇总字段（建设项目、单位工程汇总）
   *         {Number}tenderID 单位工程ID
   *         {String}userID 用户ID
   * @return {Object} 返回的数据结构：{children: [{children: []}]} 最外层为建设项目，中间为单项工程，最底层为单位工程
   * */
  async function getProjectByGranularity(granularity, requestForSummaryInfo, tenderID, userID) {
    let projectData = _cache.projectData;
    // 没有数据，需要拉取
    if (!Object.keys(projectData).length) {
      projectData = await ajaxPost('/pm/api/getProjectByGranularity', {
        user_id: userID,
        tenderID,
        granularity,
        requestForSummaryInfo
      });
      _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;
  }

  // 获取普通基数: {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 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++];
        });
      });
    });
  }

  // 将文本的中文提取出来
  function getHan(str) {
    if (!str) {
      return '';
    }
    return str
      .split('')
      .reduce((acc, cur) => {
        if (isHan(cur)) {
          acc.push(cur);
        }
        return acc;
      }, [])
      .join('');
  }

  // 将错误提示数据进行分类处理
  function transformFailList(failList) {
    const grouped = _.groupBy(failList, 'type');
    const rst = [];
    Object
      .entries(grouped)
      .forEach(([type, items]) => {
        if (type) {
          rst.push(`<span style="font-weight: bold">${type}：</span>`);
        }
        items.forEach(({ hint }) => {
          rst.push(hint);
        });
      });
    return rst;
  }

  const UTIL = Object.freeze({
    hasValue,
    setTimeoutSync,
    getFee,
    getUnitFee,
    getAggregateFee,
    getFeeByFlag,
    getNodeByFlag,
    getPlainAttrs,
    getValueByKey,
    getRelGLJ,
    generateHardwareId,
    arrayToObj,
    validDepth,
    sortByNext,
    getTenderDetail,
    getProjectByGranularity,
    getNormalBase,
    getIDBase,
    transformCalcBase,
    transformCalcBaseState,
    getElementFromSrc,
    getAttr,
    setAttr,
    getParsedData,
    setupCode,
    getHan,
    getNowFormatTime,
    transformFailList,
  });

  // 开始标签
  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('');
    }
  }

  /**
   * 提取要导出的数据
   * @param {Function} entryFunc - 提取数据的入口方法
   * @param {Object} requestForSummaryInfo - 项目表级汇总字段（建设项目、单位工程汇总）
   * @param {Number} exportKind - 导出的文件类型：1-招标、2-投标、3-控制价
   * @param {String} areaKey - 地区标识，如：'安徽@马鞍山'
   * @param {Number} tenderID - 单位工程ID
   * @param {String} userID - 用户ID
   * @return {Promise<Array>} - [{data: Object, exportKind: Number, fileName: String}]
   */
  async function extractExportData(entryFunc, requestForSummaryInfo, exportKind, areaKey, tenderID, userID) {
    // 默认导出投标文件
    if (!exportKind || ![1, 2, 3].includes(exportKind)) {
      exportKind = EXPORT_KIND.BID_SUBMISSION;
    }
    // 拉取标段数据:建设项目、单位工程数据(projects表数据)
    const projectData = await getProjectByGranularity(GRANULARITY.PROJECT, requestForSummaryInfo, tenderID, userID);
    if (!projectData) {
      throw '获取项目数据错误';
    }
    // 单位工程按照树结构数据进行排序，这样导出才的单位工程顺序才是对的
    projectData.children = sortByNext(projectData.children);
    // 先获取需要导出的单位工程的详细数据
    const tenderDetailMap = getItem('tenderDetailMap');
    for (const tenderItem of projectData.children) {
      if (!tenderDetailMap[tenderItem.ID]) {
        await setTimeoutSync(() => { }, TIMEOUT_TIME); // 需要请求项目详细数据的时候，间隔一段时间再初始单位工程数据，减少服务器压力
      }
      // 获取单位工程详细数据
      const detail = await getTenderDetail(tenderItem.ID, userID);
      tenderDetailPretreatment(detail);
      console.log(detail);
    }
    // 提取相关项目的详细导出数据
    return await entryFunc(areaKey, exportKind, projectData, tenderDetailMap);
  }

  // 对getData返回的数据进行一些通用预处理，方便各接口直接取值、处理
  function tenderDetailPretreatment(tenderDetail) {
    const bidEvaluationList = tenderDetail.bid_evaluation_list.datas;
    const evaluateList = tenderDetail.evaluate_list.datas;
    const decimalInfo = tenderDetail.projectInfo.property.decimal;
    // 项目人材机汇总排序
    tenderDetail.projectGLJ.datas.gljList = gljUtil.sortProjectGLJ(tenderDetail.projectGLJ.datas.gljList);
    const projectGLJList = tenderDetail.projectGLJ.datas.gljList;
    // 计算人材机总消耗量，否则projectGLJ.datas.gljList里的数据不会有消耗量数据
    gljUtil.calcProjectGLJQuantity(tenderDetail.projectGLJ.datas, tenderDetail.ration_glj.datas, tenderDetail.Ration.datas, tenderDetail.Bills.datas, tenderDetail.property.decimal.glj.quantity, _, scMathUtil);
    const connectKeyMap = {};
    const projectGLJIDMap = {};
    projectGLJList.forEach(glj => {
      connectKeyMap[gljUtil.getIndex(glj, gljKeyArray)] = glj.id; // 为了方便后续导出处理，给组成物数据设置上对应项目人材机ID
      projectGLJIDMap[glj.id] = glj;
      // 项目人材机设置价格信息，否则projectGLJ.datas.gljList里的数据不会有相关价格信息
      // 价格信息存在新的priceInfo字段，以免对一些方法造成影响
      glj.priceInfo = gljUtil.getGLJPrice(glj, tenderDetail.projectGLJ.datas, tenderDetail.property.calcOptions, tenderDetail.labourCoe.datas, decimalInfo, false, _, scMathUtil, {}, tenderDetail.projectGLJ.getTenderPriceCoe(glj, tenderDetail.property));
      // 计算合价：人材料总消耗量*预算价
      glj.priceInfo.totalPrice = scMathUtil.roundForObj(glj.priceInfo.tenderPrice * glj.tenderQuantity, 2);
    });
    const ratiosArr = Object.values(tenderDetail.projectGLJ.datas.mixRatioMap);
    ratiosArr.forEach(ratios => {
      ratios.forEach(ratio => {
        const ratioKey = gljUtil.getIndex(ratio, gljKeyArray);
        const projectGLJID = connectKeyMap[ratioKey];
        if (projectGLJID) {
          ratio.projectGLJID = projectGLJID;
        }
      });
    });
    // 处理定额人材机，将定额人材机挂到定额的rationGLJList字段中，同时将定额人材机进行排序、计算调价消耗量
    tenderDetail.Ration.datas.forEach(ration => {
      ration.rationGLJList = tenderDetail.ration_glj.datas.filter(glj => {
        const pGLJ = projectGLJIDMap[glj.projectGLJID];
        glj.tenderQuantity = gljUtil.getRationGLJTenderQuantity(glj, ration, decimalInfo.glj.quantity, scMathUtil, pGLJ);
        return glj.rationID === ration.ID;
      });
      ration.rationGLJList = gljUtil.sortRationGLJ(ration.rationGLJList);
    });

    // 获取暂估价材料数据，getData原始数据evaluate_list.datas里的数据缺少一些价格数据，需要调用额外接口
    tenderDetail.evaluateMaterialData = configMaterialObj.getEvaluateMaterialDatas(projectGLJList, evaluateList, decimalInfo);
    // 获取评标材料数据
    tenderDetail.bidMaterialData = configMaterialObj.getBidMaterialDatas(projectGLJList, bidEvaluationList, decimalInfo);
  }

  /**
   * 根据各自费用定额的文件结构，导出文件
   * 每个费用定额可能导出的结果文件都不同
   * 比如广东18需要将一个建设项目文件，多个单位工程文件打包成一个zip文件。重庆18就没这种要求
   * @param {Array} extractData - 提取的数据
   * @param {Function} saveAsFunc - 各自费用定额的导出方法，适应不同接口需要不同的最终文件形式
   */
  async function exportFile(extractData, saveAsFunc) {
    // 获取文件数据
    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 - 默认的通用导出文件方法:一个文件数据对应一个xml文件(变更后缀)
   * @return {Void}
   */
  async function defaultSaveAs(fileData) {
    fileData.forEach(fileItem => saveAs(fileItem.blob, fileItem.fileName));
  }

  return {
    CONFIG,
    CACHE,
    UTIL,
    Element,
    extractExportData,
    defaultSaveAs,
    exportFile,
  };
})();