| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027 | 'use strict';/** * * * @author Zhong * @date 2019/6/20 * @version */const XML_EXPORT_BASE = (() => {    // 自检提示的开始记号,区分提示属于哪一部分的类型(eg: 招标、控制价),便于后续做提示的合并去重    const HINT_START = '--start--';    // 属性类型    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 TAX_TYPE = {        '1': '一般计税法',        '2': '简易计税法',    };    // 承包人材料调整类型    const ADJUST_TYPE = {        info: 'priceInfo', // 造价信息差额调整法        coe: 'priceCoe' // 价格指数调整法    };    // 加载数据间隔,减少服务器压力    const TIMEOUT_TIME = 400;    // 导出粒度    const GRANULARITY = {        PROJECT: 1,         //导出建设项目        ENGINEERING: 2,     //导出单项工程        TENDER: 3           //导出单位工程    };    // 导出的文件类型选项    const EXPORT_KIND = {        Tender: 1,  //投标        Bid: 2,     //招标        Control: 3  //控制价    };    const EXPORT_KIND_NAME = {        1: '投标',        2: '招标',        3: '控制价'    };    // 配置项    const CONFIG = Object.freeze({        HINT_START,        TYPE,        WHITE_SPACE,        TAX_TYPE,        ADJUST_TYPE,        TIMEOUT_TIME,        GRANULARITY,        EXPORT_KIND,        EXPORT_KIND_NAME    });    // 缓存项 不需要的时候需要清空    let _cache = {        // 失败列表        failList: [],        // 项目数据(不包含详细数据,项目管理数据)        projectData: {},        // 当前导出类型,默认投标        exportKind: EXPORT_KIND.Tender,        // 记录拉取的单位工程项目详细数据,导出的时候,可能会导出多个文件,只有导出第一个文件的时候需要请求数据        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.Tender;        _cache.tenderDetailMap = {};    }    const CACHE = Object.freeze({        getItem,        setItem,        clear    });    /*     * 定义不设置一个Node方法统一进入的原因:模板化比较直观,不分开定义节点的话,调用传参也很麻烦而且不直观。     * 一个节点对应一个构造方法,方便调整配置、方便其他版本开发、接手的人看起来更直观     * @param  {String}name 节点名     *         {Array}attrs 节点属性数据     *         {String}alias 别名,有些节点名称是英文,但是提示的时候需要显示中文的时候用     * @return {void}     * */    function Element(name, attrs, alias) {        this.name = name;        const hintName = alias || name;        const checkData = check(hintName, attrs);        this.fail = checkData.failHints;        this.attrs = checkData.filterAttrs;        handleXMLEntity(this.attrs);        this.children = [];        _cache.failList.push(...this.fail);    }    /*     * xml字符实体的处理,这些特殊字符不处理会导致xml文件格式出错:""、<>、&     * 要先处理&     * */    let _xmlEntity = {        '&': '&',        '\n': '
',        '"': '"',        '\'': ''',        '<': '<',        '>': '>'    };    // 对每个元素的所有属性值进行特殊字符处理    function handleXMLEntity(attrs) {        for (let attr of attrs) {            if (!attr.value) {                continue;            }            for (let [key, value] of Object.entries(_xmlEntity)) {                attr.value = attr.value.replace(new RegExp(key, 'g'), value);            }        }    }    // 获取处理实体字符后的数据    function getParsedData(arr) {        return arr.map(data => {            for (let [key, value] of Object.entries(_xmlEntity)) {                data = data.replace(new RegExp(key, 'g'), value);            }            return data;        });    }    /*     * 检查     * 创建节点时检查节点的数据     * 1.长度限制minLen,maxLen     * 2.值的限制,固定范围:enumeration     * @param  {String}eleName 节点名称     *         {Array}datas 需要检查的属性数据     * @return {Object} failHints没通过的属性提示 filterAttrs过滤后的属性数据(失败提示在属性是必须的时候才提示,如果该属性失败了,但是是非必要属性,那么该属性不显示)     * */    function check(eleName, datas) {        let rst = { failHints: [], filterAttrs: [] };        let 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])))/;        for (let data of datas) {            const isHasValue = hasValue(data.value);            // 某属性是非必须的,并且设置了mustHasValue,意味着必须要有值才输出该属性            if (!data.required && data.mustHasValue && !isHasValue) {                continue;            }            // 值统一转换成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, ' ');            }            let isFail = false;            let tempFail = '';            // 提示的名称需要处理,有的接口xml属性为英文,需要显示其展示名称            const name = data.dName || data.name;            if (data.minLen && data.value.length < data.minLen) {                isFail = true;                tempFail = data.failHint                    ? `${data.failHint}字符数不可小于${data.minLen}个。`                    : `${eleName}-“${name}”字符数不可小于${data.minLen}个。`;            } else if (data.maxLen && data.value.length > data.maxLen) {                isFail = true;                tempFail = data.failHint                    ? `${data.failHint}字符数不可大于${data.maxLen}个。`                    : `${eleName}-“${name}”字符数不可大于${data.maxLen}个。`;            } else if (data.enumeration && !data.enumeration.includes(data.value)) {                isFail = true;                let enumerationHint = data.enumerationHint                    ? data.enumerationHint.join(';')                    : data.enumeration.join(';');                tempFail = data.failHint                    ? `${data.failHint}只能从“${enumerationHint}”中选择。`                    : `${eleName}-“${name}”只能从“${enumerationHint}”中选择。`;            } else if (data.type && data.type === TYPE.DATE && !dateReg.test(data.value)) {                isFail = true;                tempFail = data.failHint                    ? `${data.failHint}日期格式必须是YYYY-MM-DD。`                    : `${eleName}-“${name}”日期格式必须是YYYY-MM-DD。`;            } else if (data.type && data.type === TYPE.INT && !Number.isInteger(parseFloat(data.value))) {                isFail = true;                tempFail = data.failHint                    ? `${data.failHint}必须为整数。`                    : `${eleName}-“${name}”必须为整数。`;            } else if (data.type && data.type === TYPE.DECIMAL && isNaN(parseFloat(data.value))) {                isFail = true;                tempFail = data.failHint                    ? `${data.failHint}必须为数值。`                    : `${eleName}-“${name}”必须为数值。`;            } else if (data.type && data.type === TYPE.NUM2) {                let v = parseFloat(data.value);                if (isNaN(v)) {                    isFail = true;                    tempFail = data.failHint                        ? `${data.failHint}必须为数值。`                        : `${eleName}-“${name}”必须为数值。`;                } else if (!data.value.length || (data.value.split('.').length > 1 && data.value.split('.')[1].length > 2)) {                    isFail = true;                }            } else if (data.type && data.type === TYPE.BOOL && !['true', 'false'].includes(String(data.value))) {                isFail = true;                tempFail = data.failHint                    ? `${data.failHint}必须为true或false。`                    : `${eleName}-“${name}”必须为true或false。`;            }            if (!isFail || data.required) {                rst.filterAttrs.push(data);            }            if (isFail && data.required && tempFail) {                rst.failHints.push(tempFail);            }        }        return rst;    }    // 提取各导出类型的自检数据    function _extractHintParts(failList) {        let rst = [],            curPart;        for (let hint of failList) {            if (hint === HINT_START) {                curPart = [];                rst.push(curPart);                continue;            }            curPart.push(hint);        }        return rst;    }    // 自检提示去重    function deWeightHints(failList) {        let rst = [];        let hintParts = _extractHintParts(failList);        // 建设项目提示文本        let rootHints = [],            // 单位工程提示文本映射            tenderMap = {},            reg = /^<span style="font-weight: bold">单位工程/;        for (let hintPart of hintParts) {            // 单位工程xxx提示            let curTenderHint;            // 提取建设项目提示、各自单位工程提示            for (let hint of hintPart) {                if (reg.test(hint)) {                    curTenderHint = hint;                    if (!tenderMap[curTenderHint]) {                        tenderMap[curTenderHint] = [];                    }                    continue;                }                if (curTenderHint) {                    tenderMap[curTenderHint].push(hint);                } else {                    rootHints.push(hint);                }            }        }        // 建设项目提示去重,放入结果中        rootHints = [...new Set(rootHints)];        rst.push(...rootHints);        // 单位工程提示放入结果中        for (let tenderHint in tenderMap) {            rst.push(tenderHint);            // 单位工程提示去重            let tenderHints = [...new Set(tenderMap[tenderHint])];            rst.push(...tenderHints);        }        return rst;    }    // 等待一段时间    function setTimeoutSync(handle, time) {        return new Promise((resolve, reject) => {            setTimeout(() => {                if (handle && typeof handle === 'function') {                    handle();                }                resolve();            }, time);        });    }    // v是否定义了,不为undefined和null    function isDef(v) {        return typeof v !== 'undefined' && v !== null;    }    function hasValue(v) {        // v是否有值,不为undefined、null、''        return typeof v !== 'undefined' && v !== null && v !== '';    }    /*     * 将节点属性数据(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;        }        let fields = feeFields.split('.');        let fee = fees.find(data => data.fieldName === fields[0]);        if (!fee) {            return 0;        }        return fee[fields[1]] || 0;    }    // 获取节点的汇总价格    function getAggregateFee(nodes) {        let 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) {        let rst = {};        for (let data of arr) {            rst[data.key] = data.value;        }        return rst;    }    /*     * 检测层数     * @param  {Number}maxDepth(最大深度)     *         {Object}node(需要检测的清单树节点)     * @return {Boolean}     * */    function validDepth(maxDepth, node) {        let nodeDepth = node.depth();        let allNodes = node.getPosterity();        //检测相对深度        for (let n of allNodes) {            let relativeDepth = n.depth() - nodeDepth;            if (relativeDepth > maxDepth) {                return false;            }        }        return true;    }    /*     * 检测唯一性,有些属性在规定的数据范围内唯一     * @param  {Object}constraints(约束池)     *         {All}data(检测的数据)     *         {String}hint(提示已存在的内容)     *         {String}subHint(额外提示,有额外提示时,不用data提示)     * @return {void}     * */    function checkUnique(constraints, data, hint, subHint) {        if (constraints.includes(data)) {            let failHint = subHint                ? `${hint}“${subHint}”已存在`                : `${hint}“${data}”已存在`;            _cache.failList.push(failHint);        } else if (data) {            constraints.push(data);        }    }    //根据数据的NextSiblingID进行排序,返回排序后的数组    function sortByNext(datas) {        let target = [],            temp = {};        for (let data of datas) {            temp[data.ID] = { me: data, next: null, prev: null };        }        for (let data of datas) {            let next = temp[data.NextSiblingID] || null;            temp[data.ID].next = next;            if (next) {                next.prev = temp[data.ID];            }        }        let first = null;        for (let data of datas) {            let 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.Tender;        }        // 拉取标段数据:建设项目、单项工程、单位工程数据        let projectData = await getProjectByGranularity(granularity, summaryObj, tenderID, userID);        if (!projectData) {            throw '获取项目数据错误';        }        // 单项工程、单位工程按照树结构数据进行排序        projectData.children = sortByNext(projectData.children);        for (let engData of projectData.children) {            engData.children = sortByNext(engData.children);        }        // 提取相关项目的详细导出数据        return await entryFunc(userID, exportKind, projectData);    }    //获取普通基数: {xxx}    function getNormalBase(str) {        let reg = /{.+?}/g,            matchs = str.match(reg);        return matchs || [];    }    //获取id引用基数: @xxx-xxx-xx    function getIDBase(str) {        let reg = /@.{36}/g,            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) {            let flag = node.getFlag();            return FlagCalcBaseMap[flag] || '';        }        if (expr) {            let illegal = false;            let normalBase = getNormalBase(expr),                idBase = getIDBase(expr);            //普通基数转基数字典            normalBase.forEach(base => {                let replaceStr = CalcBaseMap[base];                //转换成行代号的优先级比较高,进行清单匹配                let flag = FlagCalcBaseMap[base];                if (flag) {                    let 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 => {                let id = base.match(/[^@]+/)[0];                let theNode = tenderDetail.mainTree.getNodeByID(id),                    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 || exportKind === EXPORT_KIND.Control) {                    return '';                } else {                    let totalFee = getFee(node.data.fees, 'common.totalFee'),                        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);        //提取基数        let bases = expr.split(/[\+\-\*\/]/g);        //提取操作符        let oprs = expr.match(/[\+\-\*\/]/g);        //转换后的基数        let newBase = [];        let illegal = false;        for (let base of bases) {            //行引用转换为名称.            if (/F\d+/.test(base)) {                let rowCode = base.match(/\d+/)[0],                    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++];                });            });        });    }    /**     * 项目属性基本信息、工程特征的自检     * 有错误的话,将先直接弹窗提示,不会进行其他的自检     *   */    function propertyCheck() {        const type = {            info: '基本信息',            feature: '工程特征'        };        const failList = ['<span style="font-weight: bold">建设项目下:</span>'];        let haveHandledProjectData = false;        const tenderDetailMap = _cache.tenderDetailMap;        // 按照获取顺序serialNo(根据树结构)排序        Object.values(tenderDetailMap)            .sort((a, b) => a.serialNo - b.serialNo)            .forEach(tenderDetail => {                if (!haveHandledProjectData) {                    haveHandledProjectData = true;                    const basicInformation = tenderDetail.projectInfo.property.basicInformation || [];                    const infoFailList = itemsCheck(basicInformation, type.info);                    if (!infoFailList.length) {                        failList.splice(0, failList.length);                    } else {                        failList.push(...infoFailList);                    }                }                failList.push(`<span style="font-weight: bold">单位工程“${tenderDetail.projectInfo.name}”下:</span>`);                const projectFeature = tenderDetail.projectInfo.property.projectFeature || [];                const featureFailList = itemsCheck(projectFeature, type.feature);                if (!featureFailList.length) {                    failList.splice(-1, 1);                } else {                    failList.push(...featureFailList);                }            });        return failList;        // 条目检查        function itemsCheck(items, type) {            const requiredData = commonUtil.getRequired([], items);            return requiredData                .filter(item => commonUtil.isEmptyVal(item.value))                .map(item => `${type}-“${item.dispName}”不能为空。`);        }    }    /**     * 弱自检自检,有错误非强制不可导的自检     * failList里存的是导出规定属性时,不符合标准规定的错误,存在这种错误就不允许导出     * 这里的错误是业务上的提示错误,与标准文件规定无关,存在这种错误是允许导出的     * @return {Array} - 提示信息数组      */    function softCheck() {        const tenderDetailMap = _cache.tenderDetailMap;        // 检查清单综合单价是否大于最高限价,大于则提示        function checkMaxPrice(tenderDetail) {            return tenderDetail.mainTree.items                .filter(node => calcTools.unitFeeGTMaxPrice(node, 'common.unitFee'))                .map(node => {                    const code = node.data.code || '';                    const name = node.data.name || '';                    return `第${node.serialNo()}行“${code + name}”,清单综合单价 > 最高限价`;                });        }        const infos = [];        // 按照获取顺序serialNo(根据树结构)排序        Object.values(tenderDetailMap)            .sort((a, b) => a.serialNo - b.serialNo)            .forEach(tenderDetail => {                const maxPriceInfos = checkMaxPrice(tenderDetail);                if (!maxPriceInfos.length) {                    return;                }                infos.push(`<span style="font-weight: bold">单位工程“${tenderDetail.projectInfo.name}”下:</span>`);                infos.push(...maxPriceInfos);            });        return infos;    }    const UTIL = Object.freeze({        deWeightHints,        isDef,        hasValue,        setTimeoutSync,        getFee,        getAggregateFee,        getFeeByFlag,        getPlainAttrs,        getValueByKey,        getRelGLJ,        generateHardwareId,        arrayToObj,        validDepth,        checkUnique,        sortByNext,        getTenderDetail,        getProjectByGranularity,        getNormalBase,        getIDBase,        transformCalcBase,        transformCalcBaseState,        getCodeSheetData,        getElementFromSrc,        getAttr,        setAttr,        getParsedData,        setupCode,        propertyCheck,        softCheck,    });    // 开始标签    function _startTag(ele) {        let rst = `<${ele.name}`;        for (let 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 (let 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<");        //调整格式        let rgx = /\n(<(([^\?]).+?)(?:\s|\s*?>|\s*?(\/)>)(?:.*?(?:(?:(\/)>)|(?:<(\/)\2>)))?)/mg;        let nodeStack = [];        let output = text.replace(rgx, function ($0, all, name, isBegin, isCloseFull1, isCloseFull2, isFull1, isFull2) {            let 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);                }            }            let ret = '\n' + prefix + all;            return ret;        });        let outputText = output.substring(1);        return outputText;        function getPrefix(prefixIndex) {            let span = '    ';            let output = [];            for (let i = 0; i < prefixIndex; ++i) {                output.push(span);            }            return output.join('');        }    }    /*     * 根据各自费用定额的文件结构,导出文件     * 每个费用定额可能导出的结果文件都不同     * 比如广东18需要将一个建设项目文件,多个单位工程文件打包成一个zip文件。重庆18就没这种要求     * @param  {Array}codes 工程编号数据     *         {Array}extractData 提取的数据     *         {Function}setCodeFunc 各自费用定额的导出前重设用户输入的工程编号方法     *         {Function}saveAsFunc 各自费用定额的导出方法     * @return {void}     * */    async function exportFile(codes, extractData, setCodeFunc, saveAsFunc) {        // 编号重置后将会被导出,需要将编号进行xml字符实体转换        codes = getParsedData(codes);        setCodeFunc(codes, extractData);        // 获取文件数据        let fileData = extractData.map(extractObj => {            // 转换成xml字符串            let xmlStr = _toXMLStr([extractObj.data]);            // 加上xml声明            xmlStr = `<?xml version="1.0" encoding="utf-8"?>${xmlStr}`;            // 格式化            xmlStr = _formatXml(xmlStr);            let 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数据,中间数据(目前为导出指标压缩包用)    async function getExtractData() {        const fileKind = projectObj.project.projectInfo.property.fileKind;        const projectID = projectObj.project.ID();        const extractData = await extractExportData(XMLStandard.entry, GRANULARITY.PROJECT,            XMLStandard.summaryObj, fileKind, projectID, userID);        // projects表数据        const projectData = _.cloneDeep(_cache.projectData);        // 错误提示        const failList = deWeightHints(_.cloneDeep(_cache.failList));        // 清空缓存        clear();        return {            extractData,            projectData,            failList        }    }    /**     * 提取qtf文件数据(目前为导出指标压缩包用)     * @param {Array} codes - 设置工程编号窗口获取到的编号数据      * @param {Array} extractData - 提取的导出中间数据     * @return {Array}      */    async function getExtractFile(codes, extractData) {        return await exportFile(codes, extractData, XMLStandard.resetContentCode);    }    return {        CONFIG,        CACHE,        UTIL,        Element,        extractExportData,        exportFile,        getExtractData,        getExtractFile,    };})();
 |