@yelon/util
Version:
Universal toolset of ng-yunzai.
430 lines (423 loc) • 14.6 kB
JavaScript
import { deepGet } from '@yelon/util/other';
import { CurrencyPipe, formatNumber } from '@angular/common';
import * as i0 from '@angular/core';
import { inject, LOCALE_ID, DEFAULT_CURRENCY_CODE, Injectable } from '@angular/core';
import { YunzaiConfigService } from '@yelon/util/config';
/**
* String formatting
*
* 字符串格式化
* ```
* format('this is ${name}', { name: 'asdf' })
* // output: this is asdf
* format('this is ${user.name}', { user: { name: 'asdf' } }, true)
* // output: this is asdf
* ```
*/
function format(str, obj, needDeepGet = false) {
return (str || '').replace(/\${([^}]+)}/g, (_work, key) => needDeepGet ? deepGet(obj, key.split('.'), '') : (obj || {})[key] || '');
}
/**
* Format mask
*
* 格式化掩码
*
* | 字符 | 描述 |
* | --- | --- |
* | `0` | 任意数字,若该位置字符不符合,则默认为 `0` 填充 |
* | `9` | 任意数字 |
* | `#` | 任意字符 |
* | `U` | 转换大写 |
* | `L` | 转换小写 |
* | `*` | 转换为 `*` 字符 |
*
* ```ts
* formatMask('123', '(###)') => (123)
* formatMask('15900000000', '999****9999') => 159****0000
* ```
*/
function formatMask(value, option) {
if (!value) {
return '';
}
const opt = {
...(typeof option === 'string' ? { mask: option } : option)
};
const tokens = {
'0': { pattern: /\d/, default: '0' },
'9': { pattern: /\d/ },
'#': { pattern: /[a-zA-Z0-9]/ },
U: {
pattern: /[a-zA-Z]/,
transform: char => char.toLocaleUpperCase()
},
L: {
pattern: /[a-zA-Z]/,
transform: char => char.toLocaleLowerCase()
},
'*': {
pattern: /.*/,
transform: () => `*`
},
...opt.tokens
};
const splitValue = value.split('');
return opt.mask
.split('')
.reduce((res, cur) => {
const token = tokens[cur];
if (!token) {
res.push(cur);
return res;
}
const value = splitValue.shift() ?? '';
if (!token.pattern.test(value)) {
if (token.default)
res.push(token.default);
return res;
}
if (typeof token.transform === 'function') {
res.push(token.transform(value));
}
else {
res.push(value);
}
return res;
}, [])
.join('');
}
const REGEX_STR = {
num: `(([-+]?\\d+\\.\\d+)|([-+]?\\d+)|([-+]?\\.\\d+))(?:[eE]([-+]?\\d+))?`,
idCard: `(^\\d{15}$)|(^\\d{17}(?:[0-9]|X)$)`,
mobile: `^(0|\\+?86|17951)?1[0-9]{10}$`,
url: `(((^https?:(?:\\/\\/)?)(?:[-;:&=\\+\\$,\\w]+@)?[A-Za-z0-9.-]+(?::\\d+)?|(?:www.|[-;:&=\\+\\$,\\w]+@)[A-Za-z0-9.-]+)((?:\\/[\\+~%\\/.\\w-_]*)?\\??(?:[-\\+=&;%@.\\w_]*)#?(?:[\\w]*))?)`,
ip: `(?:^(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}$)|(?:^(?:(?:[a-fA-F\\d]{1,4}:){7}(?:[a-fA-F\\d]{1,4}|:)|(?:[a-fA-F\\d]{1,4}:){6}(?:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}|:[a-fA-F\\d]{1,4}|:)|(?:[a-fA-F\\d]{1,4}:){5}(?::(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}|(?::[a-fA-F\\d]{1,4}){1,2}|:)|(?:[a-fA-F\\d]{1,4}:){4}(?:(?::[a-fA-F\\d]{1,4}){0,1}:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}|(?::[a-fA-F\\d]{1,4}){1,3}|:)|(?:[a-fA-F\\d]{1,4}:){3}(?:(?::[a-fA-F\\d]{1,4}){0,2}:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}|(?::[a-fA-F\\d]{1,4}){1,4}|:)|(?:[a-fA-F\\d]{1,4}:){2}(?:(?::[a-fA-F\\d]{1,4}){0,3}:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}|(?::[a-fA-F\\d]{1,4}){1,5}|:)|(?:[a-fA-F\\d]{1,4}:){1}(?:(?::[a-fA-F\\d]{1,4}){0,4}:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}|(?::[a-fA-F\\d]{1,4}){1,6}|:)|(?::(?:(?::[a-fA-F\\d]{1,4}){0,5}:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}|(?::[a-fA-F\\d]{1,4}){1,7}|:)))(?:%[0-9a-zA-Z]{1,})?$)`,
color: `(?:#|0x)(?:[a-f0-9]{3}|[a-f0-9]{6})\\b|(?:rgb|hsl)a?\\([^\\)]*\\)`,
chinese: `[\u4e00-\u9fa5]+`
};
function genRegex(str, flags) {
return new RegExp(`^${str}$`, flags);
}
const REGEX = {
num: genRegex(REGEX_STR.num),
idCard: genRegex(REGEX_STR.idCard, 'i'),
mobile: genRegex(REGEX_STR.mobile),
url: genRegex(REGEX_STR.url),
ip: genRegex(REGEX_STR.ip),
color: genRegex(REGEX_STR.color),
chinese: genRegex(REGEX_STR.chinese)
};
/**
* Wheter is number
*
* 是否为数字
*/
function isNum(value) {
return REGEX.num.test(value.toString());
}
/**
* Wheter is integer
*
* 是否为整数
*/
function isInt(value) {
return isNum(value) && parseInt(value.toString(), 10).toString() === value.toString();
}
/**
* Wheter is decimal
*
* 是否为小数点数值
*/
function isDecimal(value) {
return isNum(value) && !isInt(value);
}
/**
* Wheter is People's Republic of China identity card
*
* 是否为中华人民共和国居民身份证
*/
function isIdCard(value) {
return REGEX.idCard.test(value);
}
/**
* Wheter is china mobile (China)
*
* 是否为手机号(中国)
*/
function isMobile(value) {
return REGEX.mobile.test(value);
}
/**
* Wheter is url address
*
* 是否URL地址
*/
function isUrl(url) {
return REGEX.url.test(url);
}
/**
* Wheter is IPv4 address (Support v4, v6)
*
* 是否IP4地址(支持v4、v6)
*/
function isIp(ip) {
return REGEX.ip.test(ip);
}
/**
* Wheter is color
*
* 是否颜色代码值
*/
function isColor(color) {
return REGEX.color.test(color);
}
/**
* Wheter is chinese
*
* 是否中文
*/
function isChinese(value) {
return REGEX.chinese.test(value);
}
const CurrencyMega_Powers = [
{ unit: 'Q', value: Math.pow(10, 15) },
{ unit: 'T', value: Math.pow(10, 12) },
{ unit: 'B', value: Math.pow(10, 9) },
{ unit: 'M', value: Math.pow(10, 6) },
{ unit: 'K', value: 1000 }
];
class CurrencyService {
locale = inject(LOCALE_ID);
defCurrencyCode = inject(DEFAULT_CURRENCY_CODE, { optional: true }) ?? 'USD';
cogSrv = inject(YunzaiConfigService);
c;
currencyPipe;
constructor() {
this.currencyPipe = new CurrencyPipe(this.locale, this.defCurrencyCode);
this.c = this.cogSrv.merge('utilCurrency', {
startingUnit: 'yuan',
megaUnit: { Q: '京', T: '兆', B: '亿', M: '万', K: '千' },
precision: 2,
ignoreZeroPrecision: true
});
}
/**
* Format a number with commas as thousands separators
*
* 格式化货币,用逗号将数字格式化为千位分隔符
* ```ts
* 10000 => `10,000`
* 10000.567 => `10,000.57`
* ```
*/
format(value, options) {
options = {
startingUnit: this.c.startingUnit,
precision: this.c.precision,
ignoreZeroPrecision: this.c.ignoreZeroPrecision,
ngCurrency: this.c.ngCurrency,
...options
};
let truthValue = Number(value);
if (value == null || isNaN(truthValue)) {
return '';
}
if (options.startingUnit === 'cent') {
truthValue = truthValue / 100;
}
if (options.ngCurrency != null) {
const cur = options.ngCurrency;
return this.currencyPipe.transform(truthValue, cur.currencyCode, cur.display, cur.digitsInfo, cur.locale || this.locale);
}
const res = formatNumber(truthValue, this.locale, `.${options.ignoreZeroPrecision ? 1 : options.precision}-${options.precision}`);
return options.ignoreZeroPrecision ? res.replace(/(?:\.[0]+)$/g, '') : res;
}
/**
* Large number format filter
*
* 大数据格式化
* ```ts
* 1000 => { value: '1', unit: 'K', unitI18n: '千' }
* 12456 => { value: '12.46', unit: 'K', unitI18n: '千' }
* ```
*/
mega(value, options) {
options = { precision: this.c.precision, unitI18n: this.c.megaUnit, startingUnit: this.c.startingUnit, ...options };
let num = Number(value);
const res = { raw: value, value: '', unit: '', unitI18n: '' };
if (isNaN(num) || num === 0) {
res.value = value.toString();
return res;
}
if (options.startingUnit === 'cent') {
num = num / 100;
}
let abs = Math.abs(+num);
const rounder = Math.pow(10, options.precision);
const isNegative = num < 0;
for (const p of CurrencyMega_Powers) {
let reduced = abs / p.value;
reduced = Math.round(reduced * rounder) / rounder;
if (reduced >= 1) {
abs = reduced;
res.unit = p.unit;
break;
}
}
res.value = (isNegative ? '-' : '') + abs;
res.unitI18n = options.unitI18n[res.unit];
return res;
}
/**
* Converted into RMB notation.
*
* 转化成人民币表示法
*/
cny(value, options) {
options = {
inWords: true,
minusSymbol: '负',
startingUnit: this.c.startingUnit,
...options
};
value = Number(value);
if (isNaN(value)) {
return '';
}
if (options.startingUnit === 'cent') {
value = value / 100;
}
value = value.toString();
let integer;
let decimal;
[integer, decimal] = value.split('.');
let symbol = '';
if (integer.startsWith('-')) {
symbol = options.minusSymbol;
integer = integer.substring(1);
}
if (/^-?\d+$/.test(value)) {
decimal = null;
}
integer = (+integer).toString();
const inWords = options.inWords;
const unit = {
num: inWords
? ['', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖', '点']
: ['', '一', '二', '三', '四', '五', '六', '七', '八', '九', '点'],
radice: inWords
? [
'',
'拾',
'佰',
'仟',
'万',
'拾',
'佰',
'仟',
'亿',
'拾',
'佰',
'仟',
'万亿',
'拾',
'佰',
'仟',
'兆',
'拾',
'佰',
'仟'
]
: [
'',
'十',
'百',
'千',
'万',
'十',
'百',
'千',
'亿',
'十',
'百',
'千',
'万亿',
'十',
'百',
'千',
'兆',
'十',
'百',
'千'
],
dec: ['角', '分', '厘', '毫']
};
if (inWords) {
value = (+value).toFixed(5).toString();
}
let integerRes = '';
const integerCount = integer.length;
if (integer === '0' || integerCount === 0) {
integerRes = '零';
}
else {
let cnDesc = '';
for (let i = 0; i < integerCount; i++) {
const n = +integer[i];
const j = integerCount - i - 1;
const isZero = i > 1 && n !== 0 && integer[i - 1] === '0';
const cnZero = isZero ? '零' : '';
const isEmpptyUnit = (n === 0 && j % 4 !== 0) || integer.substring(i - 3, i - 3 + 4) === '0000';
const descMark = cnDesc;
let cnNum = unit.num[n];
cnDesc = isEmpptyUnit ? '' : unit.radice[j];
// 第一位是一十
if (i === 0 && cnNum === '一' && cnDesc === '十')
cnNum = '';
const isChangeEr = n > 1 &&
cnNum === '二' && // 去除首位
['', '十', '百'].indexOf(cnDesc) === -1 && // 不读两\两十\两百
descMark !== '十'; // 不读十两
if (isChangeEr)
cnNum = '两';
integerRes += cnZero + cnNum + cnDesc;
}
}
// 小数部分拼接
let decimalRes = '';
const decimalCount = decimal ? decimal.toString().length : 0;
if (decimal === null) {
decimalRes = inWords ? '整' : '';
}
else if (decimal === '0') {
decimalRes = '零';
}
else {
for (let i = 0; i < decimalCount; i++) {
if (inWords && i > unit.dec.length - 1)
break;
const n = decimal[i];
const cnZero = n === '0' ? '零' : '';
const cnNum = unit.num[+n];
const cnDesc = inWords ? unit.dec[i] : '';
decimalRes += cnZero + cnNum + cnDesc;
}
}
const ret = symbol +
(inWords
? integerRes + (decimalRes === '零' ? '元整' : `元${decimalRes}`)
: integerRes + (decimalRes === '' ? '' : `点${decimalRes}`));
return ret;
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: CurrencyService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: CurrencyService, providedIn: 'root' });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: CurrencyService, decorators: [{
type: Injectable,
args: [{ providedIn: 'root' }]
}], ctorParameters: () => [] });
/**
* Generated bundle index. Do not edit.
*/
export { CurrencyMega_Powers, CurrencyService, REGEX, REGEX_STR, format, formatMask, isChinese, isColor, isDecimal, isIdCard, isInt, isIp, isMobile, isNum, isUrl };
//# sourceMappingURL=format.mjs.map