ray-template/src/utils/precision.ts
2024-03-23 11:25:28 +08:00

276 lines
6.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
*
* @author Ray <https://github.com/XiaoDaiGua-Ray>
*
* @date 2023-06-07
*
* @workspace ray-template
*
* @remark 今天也是元气满满撸代码的一天
*/
/**
*
* 文档地址: <https://currency.js.org/#subtract>
*
* Options 默认值
* - symbol default: `$`(货币符号)
* - separator default: `,`(数字分隔符, demo: 1234.56 => '1,234.56')
* - decimal default: `.`(十进制分隔符, demo: 1.23 => '1.23')
* - precision default: `2`(精度保留位数)
* - pattern default: `!#`(!: 货币符号代替, #: 货币金额代替)
* - negativePattern default: `!#`(!: 货币符号代替, #: 货币金额代替)
* - format default: `null`(默认格式化方法替代, 看文档)
* - fromCents default: `false`(尊重精度选项)
* - errorOnInvalid default: `false`(传入 null undefined 直接抛出错误)
* - increment default: `null`(四舍五入增量值)
* - useVedic default: `false`(分组格式化值, demo: currency(1234567.89, { useVedic: true }).format() => '12,34,567.89')
*/
import currency from 'currency.js'
import { cloneDeep } from 'lodash-es'
import { isValueType } from '@/utils'
import type { Options } from 'currency.js'
import type { AnyFC } from '@/types'
export type CurrencyArguments = string | number | currency
export type OriginalValueType = 'string' | 'number'
export interface CurrencyOptions extends Options {
type?: OriginalValueType
}
// currency.js 默认配置
const defaultOptions: Partial<CurrencyOptions> = {
precision: 8,
decimal: '.',
}
// currency.js 原型属性集合
const currencyPrototypeKeys = [
's',
'intValue',
'p',
'value',
'toJSON',
'add',
'cents',
'distribute',
'divide',
'dollars',
'format',
'multiply',
'subtract',
'toString',
]
/**
*
* @param valueOptions 待计算参数列表
* @param dividend 初始值
* @param cb 回调方法
*
* @description
* 计算基础方法, 仅限于该处使用。
*/
const basic = (
valueOptions: CurrencyArguments[],
dividend: CurrencyArguments,
cb: AnyFC,
) => {
if (!valueOptions?.length) {
return 0
}
if (valueOptions.length === 1) {
return currency(valueOptions[0], defaultOptions)
}
const result = valueOptions.reduce((pre, curr, idx, arr) => {
pre = cb?.(pre, curr, idx, arr)
return pre
}, dividend)
return result
}
/**
*
* @param value 待判断值
*
* 检查一个对象是否为 currency.js 的对象
* 当该对象含有 s, intValue, p, value... 属性时, 则认为该对象为 currency.js 的对象
*
* @example
* isCurrency(1.23) // false
* isCurrency('1.23') // false
* isCurrency({ s: 1, intValue: 1, p: 1, value: 1 }) // false
* isCurrency(currency(1)) // true
*/
export const isCurrency = (value: unknown) => {
if (typeof value === 'string' || typeof value === 'number') {
return false
}
if (isValueType<object>(value, 'Object')) {
return currencyPrototypeKeys.every((key) => Reflect.has(value, key))
}
return false
}
/**
*
* @description
* 格式化一个数据值, 并且返回其原始值。
*
* 默认以 number 格式返回。
*
* 如果需要格式化为其他格式(如: 货币单位、分组、分隔符等), 请使用 currency format 方法格式。
*
* @example
* format(0.1) // 0.1
* format(0.1, { symbol: '¥' }) // ¥0.1
*/
export const format = (value: CurrencyArguments, options?: CurrencyOptions) => {
const assignOptions = Object.assign({}, defaultOptions, options)
const v = currency(value, assignOptions)
const { type = 'number' } = assignOptions
return type === 'number' ? v.value : v.toString()
}
/**
*
* @description
* 加法。
*
* @example
* format(add(0.1, 0.2)) // 0.3
* format(add(0.2, 0.33)) // 0.53
*/
export const add = (...args: CurrencyArguments[]) => {
if (args.length === 1) {
return currency(args[0], defaultOptions).add(0)
}
return basic(args, 0, (pre, curr) => {
return currency(pre, defaultOptions).add(curr)
})
}
/**
*
* @description
* 减法。
*
* @example
* format(subtract(0.1, 0.12312)) // -0.02
* format(subtract(0.2, 0.33)) // -0.13
*/
export const subtract = (...args: CurrencyArguments[]) => {
if (args.length === 1) {
return currency(args[0], defaultOptions).subtract(0)
}
if (args.length === 2) {
const [one, two] = args
return currency(one, defaultOptions).subtract(two)
}
const cloneDeepArgs = cloneDeep(args)
const dividend = cloneDeepArgs.shift() as CurrencyArguments
if (!cloneDeepArgs.length) {
return dividend
}
return basic(cloneDeepArgs, dividend, (pre, curr) => {
return currency(pre, defaultOptions).subtract(curr)
})
}
/**
*
* @description
* 乘法。
*
* @example
* format(multiply(1, 0.2)) // 0.2
* format(multiply(0.2, 0.33)) // 0.07
*/
export const multiply = (...args: CurrencyArguments[]) => {
if (args.length === 1) {
return currency(args[0], defaultOptions).multiply(1)
}
return basic(args, 1, (pre, curr) => {
return currency(pre, defaultOptions).multiply(curr)
})
}
/**
*
* @description
* 除法。
*
* @example
* format(divide(1, 0.2)) // 5
* format(divide(0.2, 0.33)) // 0.61
*/
export const divide = (...args: CurrencyArguments[]) => {
if (args.length === 1) {
return currency(args[0], defaultOptions).divide(1)
}
if (args.length === 2) {
const [one, two] = args
return currency(one, defaultOptions).divide(two)
}
const cloneDeepArgs = cloneDeep(args)
const dividend = cloneDeepArgs.shift() as CurrencyArguments
return basic(cloneDeepArgs, dividend, (pre, curr) => {
return currency(pre, defaultOptions).divide(curr)
})
}
/**
*
* @description
* 平分(将一个数值平均分配到一个数组中)
* 如果值为 undefined null 会自动转换为 0
*
* @example
* distribute(0, 1) // [0]
* distribute(0, 3) // [0, 0, 0]
*/
export const distribute = (
value: CurrencyArguments,
length: number,
options?: CurrencyOptions,
) => {
if (length <= 1) {
return [value ? value : 0]
} else {
if (!value) {
return new Array(length).fill(0)
}
}
const assignOptions = Object.assign({}, defaultOptions, options)
const result = currency(value, assignOptions)
.distribute(length)
.map((curr) => {
return format(curr, assignOptions)
})
return result
}