/** * * @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 }