# number-input **Repository Path**: hejsky/number-input ## Basic Information - **Project Name**: number-input - **Description**: 数字金额处理 - **Primary Language**: JavaScript - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2026-06-11 - **Last Updated**: 2026-06-12 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # number-input 一个零依赖、即插即用的金额/数字输入器插件。原生 JavaScript 编写,支持千分位分隔、大写金额转换、位数单位显示、数值范围限制、前后缀包裹、只读模式与多种回调事件。同时提供 5 个静态工具方法,无需创建实例即可独立调用。 建议配合**Decimal.js**:JavaScript中的任意精度计算库 #### 特性一览 - **千分位格式化**:聚焦时空格分隔便于编辑,失焦后按 `thousandSymbol`(默认 `,`)格式化显示 - **大写金额转换**:实时将数字转为中文大写金额(壹贰叁肆 / 拾佰仟万亿…) - **位数单位显示**:根据整数位数自动显示 千 / 万 / 亿 / 兆 / 京 / 垓 / 秭 / 穰 / 沟 / 涧 / 正 / 载 - **小数/整数控制**:`maxDecimal` 限制最多小数位;`forceDecimal` 失焦自动补 0;`maxInteger` 限制整数部分最长位数 - **数值范围校验**:`min` / `max` 失焦自动钳制,越界时通过 `onValidChange` 反馈 - **前后缀包裹**:`prefix` / `suffix` 非空时自动包裹为带边框的容器,点击前后缀自动聚焦输入框 - **负数支持**:`allowNegative` 控制是否允许输入负数 - **只读模式**:`readOnly` 选项 + 实例方法 `readOnly()` / `enable()` / `isReadOnly()`,采用 `readOnly` 而非 `disabled`,值仍可读取和提交 - **完善回调**:`onChange` / `onBlur` / `onFocus` / `onValidChange` 四种事件,均带防重复触发保护 - **静态工具方法**:5 个独立方法无需创建实例即可调用,适用于纯数据处理场景 - **零依赖**:不依赖任何第三方库,单文件 `index.js` 即可使用,支持 CommonJS 与浏览器全局变量 #### 软件架构 ``` number-input/ ├── index.html # 14 种使用场景的可运行 Demo 页面 ├── index.js # 插件主体(createNumberInput 工厂函数 + 静态工具方法) └── README.md # 使用文档 ``` - **入口函数**:`createNumberInput(inputEl, options)` - **静态方法**:`createNumberInput.format()` / `.clean()` / `.getValue()` / `.getNumberUnit()` / `.toChineseCurrency()` - **导出方式**:浏览器 `window.createNumberInput` + Node `module.exports` #### 安装教程 1. 直接克隆/下载本仓库到本地 2. 在 HTML 中引入 `index.js`(无构建步骤,开箱即用) 3. 打开 `index.html` 即可查看 14 种使用场景的 Demo ```html
``` Node / CommonJS 环境: ```js const createNumberInput = require('./index.js'); ``` #### 使用说明 ##### API 总览 ```js // 创建实例(绑定到 input 元素) const inst = createNumberInput(inputEl, options); // 静态工具方法(无需创建实例,独立调用) createNumberInput.format(val, separator); // 千分位格式化 createNumberInput.clean(val); // 清理千分位等非数字字符 createNumberInput.getValue(val, forceDecimal); // 应用强制小数位数 createNumberInput.getNumberUnit(val); // 获取位数单位 createNumberInput.toChineseCurrency(val); // 转换为大写金额 ``` ##### 配置项 `options` | 参数 | 类型 | 默认值 | 说明 | | --------------------- | --------------------- | ------- | --------------------------------------------------------------------------- | | `maxDecimal` | `number` | `0` | 最大小数位数。`0` 表示整数 | | `forceDecimal` | `number` | `0` | 强制保留几位小数。`0` 表示不强制;`>0` 时失焦 / `getValue` / `getFormattedValue` 会补 0 到指定位数 | | `maxInteger` | `number` | `0` | 整数部分最长位数。`0` 表示不限制;`>0` 时输入超过会自动截断 | | `min` | `number \| null` | `null` | 数值最小值。`null` 表示无限制;失焦时超出范围自动钳制 | | `max` | `number \| null` | `null` | 数值最大值。`null` 表示无限制;失焦时超出范围自动钳制 | | `prefix` | `string` | `''` | 前缀文本(如 `"¥"`)。非空时自动包裹输入框为容器 | | `suffix` | `string` | `''` | 后缀文本(如 `"元"`)。非空时自动包裹输入框为容器 | | `allowNegative` | `boolean` | `false` | 是否允许负数 | | `thousandSeparator` | `boolean` | `false` | 是否启用千分位分隔符 | | `thousandSymbol` | `string` | `','` | 千分位分隔符号(失焦后使用) | | `showChineseCurrency` | `boolean` | `false` | 是否显示大写金额 | | `showNumberUnit` | `boolean` | `false` | 是否显示位数单位 | | `chineseOutputEl` | `HTMLElement \| null` | `null` | 大写金额输出容器 | | `unitOutputEl` | `HTMLElement \| null` | `null` | 位数单位输出容器 | | `readOnly` | `boolean` | `false` | 初始是否只读 | | `onChange` | `Function \| null` | `null` | 值变化回调 `(value: string) => void` | | `onBlur` | `Function \| null` | `null` | 失焦回调 `(value: string) => void` | | `onFocus` | `Function \| null` | `null` | 聚焦回调 `(value: string) => void` | | `onValidChange` | `Function \| null` | `null` | 校验状态变化回调 `(isValid: boolean, error: string \| null, value: string) => void` | ##### 实例方法 | 方法 | 说明 | | --------------------- | --------------------------------------- | | `getValue()` | 获取原始数值(不含千分位分隔符,已应用 `forceDecimal`) | | `setValue(val)` | 设置输入值(自动应用 `forceDecimal` + 范围钳制 + 千分位) | | `getFormattedValue()` | 获取格式化后的值(含千分位,已应用 `forceDecimal`) | | `readOnly()` | 设为只读 | | `enable()` | 设为可编辑 | | `isReadOnly()` | 返回当前是否已只读 | | `getWrapperEl()` | 获取 `prefix` / `suffix` 包裹容器(启用时存在) | | `destroy()` | 销毁实例,移除所有事件监听与定时器,拆解包裹容器 | ##### 静态工具方法 以下方法无需创建实例即可独立调用,适用于纯数据处理场景(如后端数据格式化、表单提交前清理等)。 | 静态方法 | 参数 | 返回值 | 说明 | | ----------------------------------------------- | ------------------------------------------------------------------ | -------------------- | --------------------------------------------- | | `createNumberInput.format(val, separator)` | `val`: `string\|number` 要格式化的值`separator`: `string` 千分位符号,默认 `','` | `string` 千分位格式化后的字符串 | 将纯数字格式化为千分位显示。**注意**:传入带千分位的值需先 `.clean()` 清理 | | `createNumberInput.clean(val)` | `val`: `string\|number` 要清理的值 | `string` 纯数字字符串 | 移除千分位分隔符、货币符号等非数字字符,仅保留数字、小数点、负号 | | `createNumberInput.getValue(val, forceDecimal)` | `val`: `string\|number` 纯数字值`forceDecimal`: `number` 强制小数位数,默认 `0` | `string` 处理后的数字字符串 | 将小数部分补 0 或截断到指定位数。**注意**:`val` 应为纯数字(不含千分位) | | `createNumberInput.getNumberUnit(val)` | `val`: `string\|number` 数字值 | `string` 中文位数单位 | 根据整数位数返回单位(千/万/亿/兆…载),支持自动清理千分位 | | `createNumberInput.toChineseCurrency(val)` | `val`: `string\|number` 数字值 | `string` 中文大写金额 | 转换为财务大写金额格式,支持自动清理千分位,负数前缀"负" | **静态方法使用示例**: ```js // 1. 千分位格式化 createNumberInput.format('1234567.89'); // '1,234,567.89' createNumberInput.format('1234567.89', '_'); // '1_234_567.89' createNumberInput.format('-9876543.21'); // '-9,876,543.21' // 2. 清理千分位等非数字字符 createNumberInput.clean('1,234,567.89'); // '1234567.89' createNumberInput.clean('¥1,234.56元'); // '1234.56' createNumberInput.clean('-1,234.56'); // '-1234.56' // 3. 应用强制小数位数(补0/截断) createNumberInput.getValue('1', 2); // '1.00' createNumberInput.getValue('1.5', 2); // '1.50' createNumberInput.getValue('1.234', 2); // '1.23' createNumberInput.getValue('-1', 2); // '-1.00' createNumberInput.getValue('1.5', 0); // '1.5'(不强制) // 4. 获取位数单位 createNumberInput.getNumberUnit('1234'); // '千' createNumberInput.getNumberUnit('10000'); // '万' createNumberInput.getNumberUnit('100000000'); // '亿' createNumberInput.getNumberUnit('1,234,567'); // '万'(自动清理千分位) // 5. 转换为大写金额 createNumberInput.toChineseCurrency('1234.56'); // '壹仟贰佰叁拾肆元伍角陆分' createNumberInput.toChineseCurrency('-100'); // '负壹佰元整' createNumberInput.toChineseCurrency('0'); // '零元整' createNumberInput.toChineseCurrency('1,234,567'); // '壹佰贰拾叁万肆仟伍佰陆拾柒元整' ``` **典型组合使用场景**: ```js // 场景1:从后端获取带千分位的数据 → 清理 → 计算 → 格式化显示 const serverData = '1,234,567.89'; // 步骤1:清理千分位,获取纯数字 const cleanVal = createNumberInput.clean(serverData); // '1234567.89' // 步骤2:强制2位小数 const stdVal = createNumberInput.getValue(cleanVal, 2); // '1234567.89' // 步骤3:数值计算 const result = Number(stdVal) * 1.08; // 1333333.3212 // 步骤4:格式化显示 const display = createNumberInput.format(String(result)); // '1,333,333.32' // 步骤5:显示大写金额 const chinese = createNumberInput.toChineseCurrency(String(result)); // 大写金额 ``` ```js // 场景2:带货币符号和千分位的复杂组合(合同/发票场景) // 模拟从页面获取的显示值,包含货币符号、千分位、单位 const displayValue = '¥1,234,567.89元'; // 步骤1:清理所有非数字字符(货币符号、千分位逗号、单位) const pureNum = createNumberInput.clean(displayValue); // '1234567.89' // 步骤2:强制2位小数(确保金额格式规范) const stdNum = createNumberInput.getValue(pureNum, 2); // '1234567.89' // 步骤3:数值计算(如加收8%税费) const taxAmount = Number(stdNum) * 0.08; // 98765.4312 const totalAmount = Number(stdNum) + taxAmount; // 1333333.3212 // 步骤4:计算结果标准化 const totalStd = createNumberInput.getValue(String(totalAmount), 2); // '1333333.32' // 步骤5:格式化为千分位显示 const totalDisplay = createNumberInput.format(totalStd); // '1,333,333.32' // 步骤6:拼接货币符号和单位 const finalDisplay = `¥${totalDisplay}元`; // '¥1,333,333.32元' // 步骤7:获取位数单位 const unit = createNumberInput.getNumberUnit(totalStd); // '万' // 步骤8:转换大写金额(合同/发票用) const chinese = createNumberInput.toChineseCurrency(totalStd); // '壹佰叁拾叁万叁仟叁佰叁拾叁元叁角贰分' // 最终输出示例: console.log(`金额:${finalDisplay}`); // 金额:¥1,333,333.32元 console.log(`量级:${unit}`); // 量级:万 console.log(`大写:${chinese}`); // 大写:壹佰叁拾叁万叁仟叁佰叁拾叁元叁角贰分 ``` ##### 完整示例 ```js const moneyInput = createNumberInput(document.getElementById('moneyInput'), { maxDecimal: 4, // 最多 4 位小数 forceDecimal: 2, // 失焦自动补 0 到 2 位 maxInteger: 6, // 整数部分最多 6 位 min: 0, // 最小 0 max: 999999, // 最大 999999 prefix: '¥', // 前缀 suffix: '元', // 后缀 allowNegative: true, // 允许负数 thousandSeparator: true, thousandSymbol: ',', showChineseCurrency: true, showNumberUnit: true, chineseOutputEl: document.getElementById('chineseOutput'), unitOutputEl: document.getElementById('unitOutput'), onChange: (val) => console.log('值变化:', val), onBlur: (val) => console.log('失焦:', val), onFocus: (val) => console.log('聚焦:', val), onValidChange: (ok, err, val) => console.log('校验:', ok, err, val) }); // 程序化取值 / 设值 moneyInput.setValue('12345.6'); // 自动补 0 → '12345.60' console.log(moneyInput.getValue()); // '12345.60' console.log(moneyInput.getFormattedValue()); // '12,345.60' // 切换只读 moneyInput.readOnly(); moneyInput.enable(); console.log(moneyInput.isReadOnly()); // false // 销毁 // moneyInput.destroy(); ``` ##### 行为约定 - **聚焦时**:千分位由 `thousandSymbol` 替换为空格,便于编辑 - **失焦时**:空格千分位替换为 `thousandSymbol`,应用 `forceDecimal`,按 `min` / `max` 钳制 - **输入中**:实时清理非法字符、限制小数/整数位数、智能保留光标位置 - **输入法兼容**:使用 `compositionstart` / `compositionend` 兼容中文输入法 - **大写金额**:支持正负数,最大支持到「载」位(10^44) - **位数单位**:1-4 位 → 千以下;5-8 位 → 万级;9-12 位 → 亿级;依次类推至「载」级 - **静态方法**:定义在 `createNumberInput` 外部,不依赖任何闭包变量,可独立调用 #### Demo 场景说明 `index.html` 内置 14 种典型使用场景: 1. 完整功能(千分位 + 大写 + 单位 + 负数) 2. 仅千分位分隔符(不允许负数) 3. 千分位符号为下划线 `_` 4. 仅位数单位(无千分位,不允许负数) 5. 大写 + 单位 + 负数(无千分位) 6. 千分位 + 位数单位 + 负数 7. 强制保留 4 位小数(失焦自动补 0) 8. 不强制小数位数(输入什么显示什么) 9. 整数部分最长 6 位(超过自动截断) 10. `prefix` / `suffix` 前后缀(自动包裹为容器) 11. `min` / `max` 范围限制(失焦自动钳制 + 校验提示) 12. 回调事件演示(`onChange` / `onBlur` / `onValidChange`) 13. 只读状态演示(`readOnly` + `readOnly()` / `enable()` 方法) 14. 静态工具方法演示(`format` / `clean` / `getValue` / `getNumberUnit` / `toChineseCurrency`) #### 参与贡献 1. Fork 本仓库 2. 新建 `Feat_xxx` 分支 3. 提交代码 4. 新建 Pull Request