diff --git a/web-admin/src/utils/rmb-upper.test.ts b/web-admin/src/utils/rmb-upper.test.ts new file mode 100644 index 00000000..2973e1d8 --- /dev/null +++ b/web-admin/src/utils/rmb-upper.test.ts @@ -0,0 +1,38 @@ +// web-admin/src/utils/rmb-upper.test.ts +import { describe, it } from 'node:test' +import assert from 'node:assert/strict' +import { toRmbUpper } from './rmb-upper' + +describe('toRmbUpper', () => { + it('zero', () => { + assert.equal(toRmbUpper(0), '零元整') + }) + it('integer', () => { + assert.equal(toRmbUpper(1), '壹元整') + assert.equal(toRmbUpper(10), '壹拾元整') + assert.equal(toRmbUpper(100), '壹佰元整') + assert.equal(toRmbUpper(1234), '壹仟贰佰叁拾肆元整') + }) + it('jiao fen', () => { + assert.equal(toRmbUpper(1.23), '壹元贰角叁分') + assert.equal(toRmbUpper(0.5), '伍角') + assert.equal(toRmbUpper(0.05), '伍分') + assert.equal(toRmbUpper(1.2), '壹元贰角') + assert.equal(toRmbUpper(1.03), '壹元零叁分') + }) + it('nullish and invalid', () => { + assert.equal(toRmbUpper(null), '') + assert.equal(toRmbUpper(undefined), '') + assert.equal(toRmbUpper(Number.NaN), '') + assert.equal(toRmbUpper(-1), '') + }) + it('too large', () => { + assert.equal(toRmbUpper(1e12), '金额过大') + }) + it('near max has yuan jiao fen', () => { + const s = toRmbUpper(999999999999.99) + assert.ok(s.includes('元')) + assert.ok(s.includes('角') || s.includes('分')) + assert.notEqual(s, '金额过大') + }) +}) diff --git a/web-admin/src/utils/rmb-upper.ts b/web-admin/src/utils/rmb-upper.ts new file mode 100644 index 00000000..2f8ca8ef --- /dev/null +++ b/web-admin/src/utils/rmb-upper.ts @@ -0,0 +1,85 @@ +export function toRmbUpper(value: number | null | undefined): string { + if (value == null || !Number.isFinite(value) || value < 0) { + return '' + } + + if (value > 999999999999.99) { + return '金额过大' + } + + const fen = Math.round(value * 100) + if (fen === 0) { + return '零元整' + } + + const digits = ['零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖'] + const units = ['', '拾', '佰', '仟'] + + function sectionToChinese(n: number): string { + if (n === 0) return '' + let result = '' + let zeroPending = false + for (let i = 0; i < 4; i++) { + const digit = Math.floor(n / 10 ** (3 - i)) % 10 + if (digit === 0) { + if (result !== '') { + zeroPending = true + } + } else { + if (zeroPending) { + result += '零' + zeroPending = false + } + result += digits[digit] + units[3 - i] + } + } + return result + } + + const yuan = Math.floor(fen / 100) + const jiao = Math.floor((fen % 100) / 10) + const fenDigit = fen % 10 + + let result = '' + + if (yuan > 0) { + const yi = Math.floor(yuan / 100000000) + const wan = Math.floor((yuan % 100000000) / 10000) + const rest = yuan % 10000 + + if (yi > 0) { + result += sectionToChinese(yi) + '亿' + } + if (wan > 0) { + if (yi > 0 && wan < 1000) { + result += '零' + } + result += sectionToChinese(wan) + '万' + } else if (yi > 0 && rest > 0) { + result += '零' + } + if (rest > 0) { + if ((yi > 0 || wan > 0) && rest < 1000) { + result += '零' + } + result += sectionToChinese(rest) + } + result += '元' + } + + if (jiao > 0) { + result += digits[jiao] + '角' + } else if (fenDigit > 0 && yuan > 0) { + result += '零' + } + + if (fenDigit > 0) { + result += digits[fenDigit] + '分' + } + + if (jiao === 0 && fenDigit === 0) { + result += '整' + } + + return result +}