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();