import { format as formatFns, parseISO } from 'date-fns'; /** * @description 格式化控制器 * @export * @class FormatController */ export class FormatController { /** * @description 唯一实例 * @private * @static * @memberof FormatController */ private static readonly instance = new FormatController(); /** * Creates an instance of FormatController. * @memberof FormatController */ private constructor() { if (FormatController.instance) { return FormatController.instance; } } /** * @description 获取唯一实例 * @static * @return {*} {FormatController} * @memberof FormatController */ public static getInstance(): FormatController { return FormatController.instance; } /** * 格式化 * * @param {*} value 值 * @param {*} formatRule 格式化规则 * @memberof FormatController */ public format(value: any, formatRule: any) { let _value = value; if (value && formatRule) { const returnValue = this.formatData(value, formatRule); _value = this.encodeHtml(returnValue); } return _value; } /** * @description 格式化时间 * @private * @param {*} newValue 新值 * @param {*} oldValue 原始值 * @param {string} dateFormat 日期格式化 * @return {*} * @memberof FormatController */ private formatDate(newValue: any, oldValue: any, dateFormat: string) { if (isNaN(oldValue) && !isNaN(Date.parse(oldValue))) { if (parseISO(oldValue)) { const tempFormat = dateFormat.replace(/Y/g, 'y').replace(/D/g, 'd'); return formatFns(parseISO(oldValue), tempFormat); } else { return oldValue; } } else { return newValue; } } /** * @description 乘法 ,解决js小数相乘精度缺失问题 * @date 2023/02/15 18:02:08 * @private * @param {number} arg1 * @param {number} arg2 * @returns {*} * @memberof FormatController */ private accMul(arg1: number, arg2: number) { var m = 0, s1 = arg1.toString(), s2 = arg2.toString(); try { m += s1.split(".")[1].length } catch (e) { } try { m += s2.split(".")[1].length } catch (e) { } return Number(s1.replace(".", "")) * Number(s2.replace(".", "")) / Math.pow(10, m) } /** * @description 格式化数据 * @private * @param {string} value 值 * @param {string} formatRule 格式化规则 * @return {*} * @memberof FormatController */ private formatData(value: any, formatRule: string) { const oldValue = value; let usedCalc = false; const formatRules = formatRule.split(';'); if ( formatRules.length < 4 && formatRules[0].match(/^\[(>|<|=|>=|<=|<>)\d+\]/) ) { usedCalc = true; } let newFormatRule = this.handleFormatRules(value, formatRules); // 遇到 % 乘以 100 if ( newFormatRule.match(/[^*|\\|_]%/) !== null && newFormatRule.match(/"%"/) === null && newFormatRule.match(/'%'/) === null && value.toString().match(/^-?\d+(\.\d+)?$/) ) { value = this.accMul(value,100); } let codeZhengshuNumCount = 0; let codeXiaoshuNumCount = 0; let allEffectivePlaceholder = true; let isQianfenwei = false; let isFenShuwei = false; // 处理自定义格式中整数部分数字占位符个数 if (newFormatRule.match(/(([#|0|\?](\\\.|[^#|0|\.])*)+)\.?/) !== null) { const temp = newFormatRule .replace(/[*|\\|_]{2}/g, '') .replace(/[*|\\|_]([#|0|\?])/g, '') .match(/(([#|0|\?](\\\.|[^#|0|\.])*)+)\.?/); if (temp) { codeZhengshuNumCount = temp[1].match(/([#|0|\?])/g)?.length || 0; } } // 处理千分位 if (newFormatRule.match(/[^*|\\|_],/) !== null) { isQianfenwei = true; const endQianfenwei = (newFormatRule + ' ').match( /[^*|\\|_](,+)[^#|0|\?]/ ); if (endQianfenwei !== null) { // 是否有末尾千分位 value = value / Math.pow(1000, endQianfenwei[1].length); } } // 处理小数 if (newFormatRule.match(/\.(([#|0|\?]?(\\\.|[^#|0|\.])*)+)/) !== null) { const xiaoshuCode = ( newFormatRule .replace(/[*|\\|_]{2}/g, '') ?.replace(/[*|\\|_]([#|0|\?])/g, '') ?.match(/\.(([#|0|\?]?(\\\.|[^#|0|\.])*)+)/) as any )[1]; const xiaoshuNumSlot = xiaoshuCode?.match(/([#|0|\?])/g); if (xiaoshuCode.match(/^#+%?$/) === null) { allEffectivePlaceholder = false; } if (xiaoshuNumSlot === null) { codeXiaoshuNumCount = 0; } else { codeXiaoshuNumCount = xiaoshuNumSlot.length; } } else { // rule没有小数部分,直接四舍五入 if (typeof value === 'number') { value = Math.round(value); } } // 处理分数 const tempFenshuMatch = newFormatRule .replace(/[*|\\|_]{2}/g, '') .replace(/[*|\\|_]([#|0|\?])/g, '') .match(/(.*[^*|\\|_])\/[^\d|#|0|\?]*?([\d|#|0|\?]+)/); let fenmu: any; if (tempFenshuMatch) { //如果是分数表达式 value = oldValue; const isHasZhengshu: any = tempFenshuMatch[1].match(/([#|0\?]+[^#|0\?]*)/g); fenmu = tempFenshuMatch[2]; if (tempFenshuMatch.length >= 2) { isFenShuwei = true; } if (fenmu === '?') { const temp = this.parseNumber(parseFloat(value), 1); fenmu = temp[1]; } else if (fenmu === '??') { const temp = this.parseNumber(parseFloat(value), 2); fenmu = temp[1]; } else if (fenmu === '???') { const temp = this.parseNumber(parseFloat(value), 3); fenmu = temp[1]; } const runValue = parseInt( ((parseInt((value * fenmu * 2).toString()) + 1) / 2).toString() ); if (isHasZhengshu.length >= 2) { value = [parseInt((runValue / fenmu).toString()), runValue % fenmu]; } if (isHasZhengshu.length >= 2) { codeZhengshuNumCount = isHasZhengshu[0].match(/([#|0\?])[^#|0\?]*/g).length; codeXiaoshuNumCount = isHasZhengshu[1].match(/([#|0\?])[^#|0\?]*/g).length; } else { codeZhengshuNumCount = 0; codeXiaoshuNumCount = isHasZhengshu[0].length; value = [0, runValue]; } } else { // 如果是一个极小的数字,例如9.568181142949328e-7 const e = value.toString().match(/e([+|-])(\d+)$/); value = value.toString().split('.'); if (e) { if (e[1] === '-') { if (e[2] > 0) { value = [ 0, Array(e[2] - 1) .fill(0) .join('') + value[0] + value[1].replace(/e-(\d+)$/, ''), ]; } } } } value[0] = Math.abs(value[0]).toString(); let temp = ''; const returnValue = ['', '', '']; let returnHtml = ''; // 已经跑完的数字位置 let finishedNumCount = 0; let styleColor = ''; while (newFormatRule.length > 0) { temp = newFormatRule[0]; if (temp === '_') { returnHtml += '' + newFormatRule[1] + ''; newFormatRule = newFormatRule.slice(2); } else if (temp === '*') { returnValue[0] = returnHtml; if (newFormatRule[1] !== '=') { returnValue[1] += newFormatRule[1]; } returnHtml = ''; newFormatRule = newFormatRule.slice(2); } else if (temp === '[') { newFormatRule = newFormatRule.slice(1); const type: any = newFormatRule.match(/^([^\]]+)\]/); const styleColorList: any = { 红色: 'red', 黑色: 'black', 黄色: 'yellow', 绿色: 'green', 白色: 'white', 蓝色: 'blue', 青色: 'cyan', 洋红: 'magenta', }; const findStr = newFormatRule.match(/^\[\$(\S)-804\]/); if (Object.keys(styleColorList).includes(type[1])) { styleColor = styleColorList[type[1]]; returnHtml += ''; } else if (findStr) { newFormatRule = newFormatRule.replace(/^\[\$(\S)-804\]/, ''); returnHtml += findStr[1]; } newFormatRule = newFormatRule.slice(type.length + 1); } else if (temp === '#' || temp === '0' || temp === '?') { const match = newFormatRule.match(/^([#|0]+)\.([#|0]+)E\+([#|0]+)/); if (match) { // 科学计数法 let match1Length = match[1].length; const match2Length = match[2].length; const match3Length = match[3].length; let firstWeishu = value[0].length; //整数位数 let chengshu = 0; if (value.length > 1) { //有小数部分 if (value[0] === 0) { //数字是小于1的 value[0] = ''; let ppp = 0; while (ppp++ < 100) { if (value[1][0] !== '0') { value[0] += value[1][0]; } value[1] = value[1].slice(1); chengshu--; if (value[1].length === 0) { //if(value[0]=='0'){ value = [value[0]]; firstWeishu = value[0].length; break; } } //小于1的,表达式整数无论多少位,只进步到一位有效数字,在excel尝试得到,不知道原因 if (match1Length > 1) { for (let i = 0; i < match1Length - 1; i++) { returnHtml += '0'; } } match1Length = 1; } else { value = [value[0] + value[1]]; } } returnHtml += value[0].toString().slice(0, match1Length) + '.'; //整数位 //小数部分 let xiaoshuValue = ''; for ( let i = match2Length + match1Length; i >= match1Length + 1; i-- ) { if ( newFormatRule[i] === '#' && xiaoshuValue === '' && value[0][i - 1] === '0' ) { return; } else if (value[0][i - 1] !== undefined) { xiaoshuValue = value[0][i - 1] + xiaoshuValue; } else if (newFormatRule[i] === '0') { xiaoshuValue = '0' + xiaoshuValue; } } returnHtml += xiaoshuValue + 'E'; returnHtml += firstWeishu > match1Length - chengshu ? '+' : '-'; //指数部分 const valueTemp = Math.abs(firstWeishu - match1Length + chengshu); if (valueTemp.toString().length < match3Length) { for ( let i = 0; i < match3Length - valueTemp.toString().length; i++ ) { returnHtml += '0'; } } returnHtml += valueTemp; newFormatRule = newFormatRule.replace( /^([#|0]+)\.([#|0]+)E\+([#|0]+)/, '' ); } else { if (finishedNumCount >= codeZhengshuNumCount) { //进入小数区间,或者分数区间 if (isFenShuwei === true) { if ( finishedNumCount > codeZhengshuNumCount + codeXiaoshuNumCount - 1 ) { //进入分母区间了 const oldReturnHtml = returnHtml; if ( fenmu.toString()[ finishedNumCount - codeZhengshuNumCount - codeXiaoshuNumCount ] ) { returnHtml += fenmu.toString(); //[finishedNumCount - codeZhengshuNumCount - codeXiaoshuNumCount]; } else if (temp === '0') { returnHtml += '0'; } else if (temp === '?') { returnHtml += ' '; } newFormatRule = newFormatRule.slice( returnHtml.length - oldReturnHtml.length ); } else { if ( value.length === 2 && value[1].length > codeXiaoshuNumCount ) { returnHtml += value[1]; newFormatRule = newFormatRule.slice(codeXiaoshuNumCount); } else { if ( value.length === 2 && finishedNumCount - codeZhengshuNumCount > codeXiaoshuNumCount - value[1].length - 1 ) { returnHtml += value[1][ value[1].length - codeXiaoshuNumCount + finishedNumCount - codeZhengshuNumCount ]; } else if (temp === '0') { returnHtml += '0'; } else if (temp === '?') { returnHtml += ' '; } newFormatRule = newFormatRule.slice(1); } } } else { if ( value.length === 2 && value[1].length > finishedNumCount - codeZhengshuNumCount ) { returnHtml += value[1][finishedNumCount - codeZhengshuNumCount]; } else if (temp === '0') { returnHtml += '0'; } else if (temp === '?') { returnHtml += ' '; } newFormatRule = newFormatRule.slice(1); } } else if ( codeZhengshuNumCount - finishedNumCount <= value[0].length ) { // 整数部分 code 3个 value 2个, finishedNumCount应该1就进入 if (finishedNumCount === 0) { // 整数之前的部分(code位少) // 整数部分 虽然code的位数不够,则都显示 for (let i = 0; i < value[0].length - codeZhengshuNumCount; i++) { returnHtml += value[0][i]; if (isQianfenwei && (value[0].length - i) % 3 === 1) { returnHtml += ','; } } } if (value[0] === '0') { if (temp === '0') { returnHtml += '0'; } else if (temp === '?') { returnHtml += ' '; } } else { returnHtml += value[0][ value[0].length + finishedNumCount - codeZhengshuNumCount ]; } if (!(isFenShuwei && value[0] === '0')) { if ( isQianfenwei && (codeZhengshuNumCount - finishedNumCount) % 3 === 1 && codeZhengshuNumCount - finishedNumCount !== 1 ) { returnHtml += ','; } } newFormatRule = newFormatRule.slice(1); } else { if (temp === '0') { returnHtml += '0'; } else if (temp === '?') { returnHtml += ' '; } if (temp === '0') { if ( isQianfenwei && (codeZhengshuNumCount - finishedNumCount) % 3 === 1 && codeZhengshuNumCount - finishedNumCount !== 1 ) { returnHtml += ','; } } newFormatRule = newFormatRule.slice(1); } finishedNumCount++; } } else if (temp === '@') { returnHtml += oldValue; newFormatRule = newFormatRule.slice(1); } else if (temp === '"') { const findStr: any = newFormatRule.match(/^"([^"]*)"/); returnHtml += findStr[1]; newFormatRule = newFormatRule.replace(/^"([^"]*)"/, ''); } else if (temp === '\\' || temp === '!') { returnHtml += newFormatRule[1]; newFormatRule = newFormatRule.slice(2); } else if (temp === ',') { newFormatRule = newFormatRule.slice(1); } else if (temp === '.') { //真实数字精度大于格式小数精度,则进行四舍五入 if ( finishedNumCount === codeZhengshuNumCount && value.length === 2 && value[1].length > codeXiaoshuNumCount ) { // 前面添加一个1,后面再删除,是为了防止第一位是0,造成的省略 let fixed = Math.round( parseFloat( '1' + value[1].slice(0, codeXiaoshuNumCount) + '.' + value[1].slice(codeXiaoshuNumCount) ) ).toString(); // 变为2有进位 if (fixed.slice(0, 1) === '2') { returnHtml = (Number(value[0]) + 1).toString(); } fixed = fixed.slice(1); value[1] = parseFloat('0.' + fixed) .toFixed(codeXiaoshuNumCount) .split('.')[1] .replace(/0+$/, ''); if (value[1].match(/^0+$/) !== null) { value = [value[0]]; } } if (allEffectivePlaceholder) { // 如果小数点后面都是#,而且遇到了整数,则判断小数点是无效的 // ##.## 遇到【3】,最终显示【 3 】,而不是【 3. 】 if (value[1]) { returnHtml += temp; } } else { returnHtml += temp; } newFormatRule = newFormatRule.slice(1); } else { returnHtml += temp; newFormatRule = newFormatRule.slice(1); } } returnHtml = this.formatDate(returnHtml, oldValue, formatRule); returnValue[2] = returnHtml; // 如果只配置了规则或者颜色,等于默认填充了值 if ((usedCalc || styleColor) && returnHtml === '') { returnValue[2] = oldValue; } if (styleColor !== '') { returnValue[3] = styleColor; } return returnValue; } /** * @description 处理格式化规则 * 根据格式化规则对 正 负 零 区分显示规则处理 * @private * @param {*} value 值 * @param {string} formatRule 格式化规则 * @return {*} {string} * @memberof FormatController */ private handleFormatRules(value: any, formatRules: string[]): string { let matchFunc = [ // 正数 function (value: any) { return ( parseFloat(value).toString() === value.toString() && parseFloat(value) > 0 ); }, // 负数 function (value: any) { return ( parseFloat(value).toString() === value.toString() && parseFloat(value) < 0 ); }, // 0 function (value: any) { return value === 0 || value === '0' || value === '-0'; }, // 文本 function () { return true; }, ]; // 是否匹配 const isMatch = function (value: any, temp1: any, temp2: any) { if (temp1 === '=') { temp1 = '=='; } switch (temp1) { case '>': return parseFloat(value) > parseFloat(temp2); case '<': return parseFloat(value) < parseFloat(temp2); case '>=': return parseFloat(value) >= parseFloat(temp2); case '<=': return parseFloat(value) <= parseFloat(temp2); case '<>': return parseFloat(value) !== parseFloat(temp2); case '==': return parseFloat(value) === parseFloat(temp2); } }; if ( formatRules.length < 4 && formatRules[0].match(/^\[(>|<|=|>=|<=|<>)\d+\]/) ) { // 使用了条件表达式 matchFunc = [ function (value: any) { const temp: any = formatRules[0].match(/^\[(>|<|=|>=|<=|<>)(\d+)\]/); return isMatch(value, temp[1], temp[2]) || true; }, function (value: any) { const temp: any = formatRules[1].match(/^\[(>|<|=|>=|<=|<>)(\d+)\]/); return isMatch(value, temp[1], temp[2]) || true; }, function () { return true; }, ]; } if (formatRules.length === 3) { formatRules[3] = '@'; } else if (formatRules.length === 2) { formatRules[3] = '@'; formatRules[2] = formatRules[0]; } else if (formatRules.length === 1) { if (formatRules[0].includes('@')) { formatRules[3] = formatRules[0]; } else { formatRules[3] = '@'; } formatRules[2] = formatRules[0]; formatRules[1] = '-' + formatRules[0]; } let newFormatRule = ''; for (const i in matchFunc) { if (matchFunc[i](value)) { newFormatRule = formatRules[i]; break; } } return newFormatRule; } /** * @description 解析数字 * @param {number} num 值 * @param {number} placeholder 占位数 * @return {*} * @memberof FormatController */ private parseNumber(num: number, placeholder: number) { const numList = num.toString().split('.'); num = parseFloat('0.' + numList[1]); let nearHalf = [0, 1]; for (let fenmu = 1; fenmu < Math.pow(10, placeholder); fenmu++) { let begin = 1; let end = fenmu - 1; let half = 0; let tag = true; while (tag) { half = begin + parseInt(((end - begin) / 2).toString()); if (half / fenmu === num) { begin = half; end = half; tag = false; } if (half / fenmu > num) { end = half; } else { begin = half; } if (end - begin <= 1) { tag = false; } } if (half / fenmu === num) { nearHalf = [half, fenmu]; tag = false; } if (begin / fenmu === num) { nearHalf = [begin, fenmu]; tag = false; } if (end / fenmu === num) { nearHalf = [end, fenmu]; tag = false; } if ( placeholder === 1 || (placeholder === 2 && fenmu >= 10) || (placeholder === 3 && fenmu >= 100) ) { if ( Math.abs(end / fenmu - num) < Math.abs(nearHalf[0] / nearHalf[1] - num) ) { nearHalf = [end, fenmu]; } } } nearHalf[0] = parseInt(nearHalf[0].toString()) + parseFloat(numList[0]) * nearHalf[1]; return nearHalf; } /** * @description Html * @param {*} [value0, value1, value2, style] * @return {*} * @memberof FormatController */ private encodeHtml([value0, value1, value2, style]: any) { if (value1) { const fillValue = value1.repeat(1000); return ( '' + (value0 ? '' + value0 + '' : '') + ('' + fillValue + '') + (value2 ? '' + value2 + '' : '') + '' ); } else { if (style) { return ( '' + [value0, value1, value2].join('') + '' ); } else { return [value0, value1, value2].join(''); } } } } // 导出服务 export const format: FormatController = FormatController.getInstance();