查看: 265|回复: 3

Vue 金额输入框组件 vxe-number-input 浮动标签与中文

[复制链接]
发表于 1 小时前 | 显示全部楼层 |阅读模式
在 Vue 项目中做金额录入时,vxe-number-input 的 type="amount" 可以直接作为数值型输入框使用。除了基本的数值约束,它还提供了浮动标签(float-content)和工具提示(show-tooltip)两类展示能力:前者适合在输入框上方标注“万”“元”这类计量单位,后者适合在输入过程中实时反馈当前数值对应的中文大写金额。下面按“基础标签配置—工具提示—自定义提示内容—金额转中文实现”的顺序整理具体写法。

一、用 float-content 在输入框上方显示浮动标签

给组件加上 float-content 属性,并填入需要提示的文字,该文字就会以浮动标签形式出现在输入框上方。下面的例子把标签设为“万”,用于提示当前输入值的计量单位:
  1. <template>
  2.   <div>
  3.     <vxe-number-input
  4.       v-model="val1"
  5.       placeholder="显示浮动标签"
  6.       type="amount"
  7.       float-content="万"
  8.       clearable
  9.     ></vxe-number-input>
  10.   </div>
  11. </template>
  12. <script setup>
  13. import { ref } from 'vue'
  14. const val1 = ref(null)
  15. </script>
复制代码

这里的 float-content 只负责展示,不参与数值计算,输入框的绑定值仍然是 val1。因此金额单位换算、精度校验等逻辑仍需要在业务层自行处理,浮动标签只是把“当前输入的是什么单位”这一信息显式呈现出来。

二、用 show-tooltip 打开实时工具提示

如果希望输入时能看到当前数值的提示信息,可以加上 show-tooltip。此时工具提示会跟随输入值实时更新:
  1. <template>
  2.   <div>
  3.     <vxe-number-input
  4.       v-model="val1"
  5.       type="amount"
  6.       placeholder="实时显示工具提示"
  7.       show-tooltip
  8.     ></vxe-number-input>
  9.   </div>
  10. </template>
  11. <script setup>
  12. import { ref } from 'vue'
  13. const val1 = ref(null)
  14. </script>
复制代码

默认提示内容通常只包含数值本身,真正的定制空间在 tooltip-config 上。

三、用 tooltip-config.contentMethod 自定义提示内容

tooltip-config 支持 contentMethod,用来接管工具提示的文本生成。它接收一个对象参数 { value, label }:value 是当前输入值,label 是输入框的标签或占位符。返回值就是最终展示的提示文本。下面的例子把中文大写金额拼进提示中:
  1. <template>
  2.   <div>
  3.     <vxe-number-input
  4.       v-model="val1"
  5.       type="amount"
  6.       placeholder="显示中文金额"
  7.       :tooltip-config="tooltipConfig"
  8.       show-tooltip
  9.       clearable
  10.     ></vxe-number-input>
  11.   </div>
  12. </template>
  13. <script setup>
  14. import { ref, reactive } from 'vue'
  15. import { amountToChinese } from './util'
  16. const val1 = ref(null)
  17. const tooltipConfig = reactive({
  18.   contentMethod({ value, label }) {
  19.     return value ? `${label}\n${amountToChinese(value)}` : ''
  20.   }
  21. })
  22. </script>
复制代码

示例中提示文本由两行组成:第一行是标签或占位符,第二行是转换后的中文金额;当 value 为空时返回空字符串,工具提示不展示内容。需要注意 contentMethod 是纯展示逻辑,不要在里面对 v-model 绑定的值做二次修改,否则容易和输入框自身的数值处理产生冲突。

四、金额转中文大写金额的函数实现

amountToChinese 的目标是把 123.45 这类数值转换为“壹佰贰拾叁元肆角伍分”。整体流程可以拆成四步:数值归一化、整数与小数拆分、整数部分按四位分组转换、拼接元角分并处理“整”和负号。原文实现如下:
  1. import XEUtils from 'xe-utils'
  2. /**
  3. * 将数字金额转换为中文大写
  4. */
  5. export function amountToChinese(num) {
  6.   let amount = XEUtils.toNumber(num)
  7.   if (amount === 0) {
  8.     return '零元整'
  9.   }
  10.   // 处理负数
  11.   const isNegative = amount < 0
  12.   amount = Math.abs(amount)
  13.   // 分离整数和小数部分
  14.   const amountStr = amount.toFixed(2)
  15.   const [integerPart, decimalPart] = amountStr.split('.')
  16.   // 中文大写数字
  17.   const digitsMap = {
  18.     0: '零', 1: '壹', 2: '贰', 3: '叁', 4: '肆',
  19.     5: '伍', 6: '陆', 7: '柒', 8: '捌', 9: '玖'
  20.   }
  21.   // 单位(每4位一组)
  22.   const unitGroups = ['', '万', '亿', '万亿', '亿亿']
  23.   // 每组的单位(千、百、十)
  24.   const units = ['', '拾', '佰', '仟']
  25.   // 转换整数部分
  26.   function convertIntegerPart(numStr) {
  27.     if (numStr === '0') return ''
  28.     let result = ''
  29.     // 按4位一组分组
  30.     const groups = []
  31.     let temp = numStr
  32.     while (temp.length > 4) {
  33.       groups.unshift(temp.slice(-4))
  34.       temp = temp.slice(0, -4)
  35.     }
  36.     if (temp) {
  37.       groups.unshift(temp)
  38.     }
  39.     groups.forEach((group, groupIndex) => {
  40.       if (group === '0000') {
  41.         // 整组为零,跳过但记录需要补零
  42.         result += '零'
  43.         return
  44.       }
  45.       let groupResult = ''
  46.       let needZero = false // 是否需要在前面补零
  47.       for (let i = 0; i < group.length; i++) {
  48.         const digit = group[i]
  49.         const position = group.length - 1 - i // 当前位数
  50.         const unitIndex = position % 4
  51.         if (digit !== '0') {
  52.           // 如果上一个字符是0或者需要补零,且当前不是第一位
  53.           if (needZero || (i > 0 && group[i - 1] === '0')) {
  54.             // 避免多个零之间重复加"零"
  55.             if (!groupResult.endsWith('零')) {
  56.               groupResult += '零'
  57.             }
  58.             needZero = false
  59.           }
  60.           groupResult += digitsMap[digit]
  61.           if (units[unitIndex]) {
  62.             groupResult += units[unitIndex]
  63.           }
  64.         } else {
  65.           needZero = true
  66.         }
  67.       }
  68.       // 添加组单位
  69.       if (groupResult && groupIndex < groups.length - 1) {
  70.         groupResult += unitGroups[groups.length - 1 - groupIndex]
  71.       }
  72.       // 合并结果,处理相邻组的零
  73.       if (groupResult) {
  74.         if (result.endsWith('零')) {
  75.           result = result.slice(0, -1) // 去掉多余的零
  76.         }
  77.         result += groupResult
  78.       }
  79.     })
  80.     return result
  81.   }
  82.   // 转换小数部分
  83.   function convertDecimalPart(numStr) {
  84.     if (!numStr || numStr === '00') return ''
  85.     let result = ''
  86.     const [jiao, fen] = numStr.split('').map((d) => digitsMap[d])
  87.     if (jiao !== '零') {
  88.       result += jiao + '角'
  89.     } else if (fen !== '零') {
  90.       result += '零'
  91.     }
  92.     if (fen !== '零') {
  93.       result += fen + '分'
  94.     }
  95.     return result
  96.   }
  97.   // 组装结果
  98.   let result = ''
  99.   // 整数部分
  100.   const integerResult = convertIntegerPart(integerPart)
  101.   if (integerResult) {
  102.     result += integerResult + '元'
  103.   } else if (decimalPart !== '00') {
  104.     // 整数部分为零且有小数
  105.     result = '零'
  106.   }
  107.   // 小数部分
  108.   const decimalResult = convertDecimalPart(decimalPart)
  109.   result += decimalResult
  110.   // 如果只有整数没有小数,添加"整"
  111.   if (decimalPart === '00') {
  112.     result += '整'
  113.   }
  114.   // 处理负数
  115.   if (isNegative) {
  116.     result = '负' + result
  117.   }
  118.   return result
  119. }
复制代码

实现中有几个关键点需要注意:

1. 入口先用 XEUtils.toNumber 统一类型,避免字符串或空值直接参与运算;随后用 Math.abs 处理负数并记录符号。
2. 金额统一通过 toFixed(2) 保留两位小数,再按小数点拆分整数部分和小数部分,这样角、分的取值都来自固定两位字符串。
3. 整数部分按四位一组切分,组单位数组依次对应空、万、亿、万亿、亿亿;组内单位数组依次对应个、拾、佰、仟。
4. 组内遇到连续的 0 时用 needZero 标记,并借助 endsWith('零') 去重,避免出现“零零”这类连续零。
5. 小数部分中,角为零而分不为零时先补一个“零”,例如 0.05 会得到“零元零伍分”这种表达所需的前置零。
6. 整数部分为空但存在小数时先把结果置为“零”;若小数部分正好是 00,则在末尾补“整”。

五、适用场景与落地建议

这套组合适合财务、报销、订单金额、合同金额等需要明确单位和中文大写的录入场景:float-content 负责静态单位提示,show-tooltip 配合 contentMethod 负责动态金额反馈,amountToChinese 则作为独立的格式化工具函数复用于表格、详情页或打印模板。

实际使用时还应注意:金额精度最终取决于 toFixed(2) 的取整行为,涉及分位舍入规则时应按业务要求单独处理;unitGroups 只覆盖到“亿亿”,超大金额需要扩展单位数组;分组补零逻辑中的分支较多,建议用 1001、10001、100000001、0.05、-123.45 等边界值做用例验证,确认输出符合财务口径后再接入表单。
回复

使用道具 举报

发表于 1 小时前 | 显示全部楼层

Re: Vue 金额输入框组件 vxe-number-input 浮动标签与中文

楼主的整理思路很清楚,float-content 和 tooltip-config.contentMethod 的职责边界也说明得很实用。实际项目里我会再注意几点:amountToChinese 里 toFixed(2) 会做四舍五入,如果业务要求截断小数需要提前处理;负数金额的“负”前缀位置要明确;整数部分按四位分组时,零的处理以及“万/亿”衔接比较容易出错,最好补几个用例验证,比如 1001、10000、100000001、0.05、-123.45。另外 contentMethod 里只读取 value、不修改 v-model 这点很关键。首帖最后的函数好像截断了,期待补全完整实现和测试用例。
回复 支持 反对

使用道具 举报

发表于 1 小时前 | 显示全部楼层

Re: Vue 金额输入框组件 vxe-number-input 浮动标签与中文

这篇整理得挺清楚,float-content 用来标“万”“元”这类单位、show-tooltip 配合 tooltip-config.contentMethod 展示中文大写金额,这个组合思路很实用;contentMethod 只做展示、不要在里面改绑定值的提醒也很关键。金额转中文部分思路也顺,不过正文到“转换整数部分”这里像是被截断了,后面分组转换和元角分拼接没贴全,方便的话可以补完整。另外实际用的时候可能还要留意 toFixed 的小数进位、负号、“零元整”、中间连续零以及超大金额单位这些边界情况,期待后续。
回复 支持 反对

使用道具 举报

发表于 1 小时前 | 显示全部楼层

Re: Vue 金额输入框组件 vxe-number-input 浮动标签与中文

楼主的整理挺清晰的,尤其是把 float-content 和 show-tooltip 的定位区分开,很多人一开始容易把浮动标签当成数值单位换算来用,实际上它只是纯展示,换算和精度校验还是得在业务层兜住,这点提醒得很到位。 另外两个点我觉得也很关键:一是 contentMethod 里只做文本生成、不去改 v-model 的值,否则确实容易和组件自身的数值处理打架;二是当 value 为空时返回空字符串,避免出现一个空白的提示气泡。 关于金额转中文,实战里最容易翻车的往往是零的处理:比如 1001 应该是“壹仟零壹元整”,100010 这类跨分组的情况还要判断上一组末尾是不是零,否则容易出现“壹拾万零壹仟”这种多零少零的毛病;还有小数部分为零时补“整”、角为零而分不为零时要不要保留“零角”等等,具体看业务规范。 不过帖子到第四部分“转换整数部分”那里看起来被截断了,后面的分组转换逻辑没贴全,方便的话可以把剩下的实现补上来,想看看你处理连续零和跨组补零的具体写法。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-10-31 07:52 , Processed in 0.027922 second(s), 28 queries , Gzip On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部