uniapp-vue3来实现一个金额千分位展示效果

发布于:2025-08-02 ⋅ 阅读:(15) ⋅ 点赞:(0)

前言:

        uniapp-vue3来实现一个金额千分位展示效果

实现效果:

实现目标:

1、封装组件,组件内部要实现,

  • input输入金额后,聚焦离开后,金额以千分位效果展示,
  • 聚焦后展示大写金额的弹框
  • 随时写的内容,可以用v-model传输给父级
  • 金额要有最大值,还要只能输入数字,小数后只能有2位

2、实现方法:

1)input框输入金额,并添加聚焦,离开事件

页面上添加内容:

<input
	v-model="displayValue"
	@input="handleInput"
	@blur="handleBlur"
	@focus="handleFocus"
	placeholder="请输入金额"
/>

js中增加配置

const displayValue = ref('')
const rawValue = ref(0)


const handleInput = (e) => {
	let val = e.detail.value
}

// 处理失去焦点
const handleBlur = () => {
}

// 处理获取焦点
const handleFocus = () => {
}

2)输入的内容,处理非数字内容,还有只能有一个小数点,小数点后添加2位限制,最大值限制

const displayValue = ref('') //千分位处理后的字段
const rawValue = ref(0)      //拿到的实际内容数据
const maxFloatNum = ref(2)  //限制小数点后几位
const maxNum = ref(100000000000) //设置最大值
const handleInput = (e) => {
		let val = e.detail.value
		if(!val) return ''
	  let value = val.toString().replace(/[^\d.]/g, '')
		if (value.length > 1 && value.startsWith('0')) {
		  value = value.substring(1)
		}
		const min = 0
	  const max = maxNum.value

	  // 确保只有一个点
	  const parts = value.split('.')
	  if (parts.length > 2) {
	    value = parts[0] + '.' + parts.slice(1).join('')
	  }
	  // 限制小数点后两位
	  if (parts.length === maxFloatNum.value) {
	    value = parts[0] + '.' + parts[1].slice(0, maxFloatNum.value)
	  }

	  // 转换为数字
	  const numValue = parseFloat(value || 0)

	  // 检查范围
	  if (numValue > max) {
	    value = max.toString()
	  } else if (numValue < min) {
	    value = min.toString()
	  }

	  rawValue.value = numValue
      displayValue.value = value
	}

3)聚焦和离开时候将数据转换位千分位,通过正则

// 格式化显示值(添加千分位)
	const formatDisplay = (value) => {
	  const num = parseFloat(value || 0)
	  return num.toFixed(maxFloatNum.value).replace(/\B(?=(\d{3})+(?!\d))/g, ',')
	}
// 处理失去焦点
const handleBlur = () => {
	displayValue.value = formatDisplay(rawValue.value)
}

// 处理获取焦点
const handleFocus = () => {
	displayValue.value = rawValue.value.toString()
}

4)来写一个大小写转换的功能,并展示到界面input上面,配合样式

js中封装小写转大写方法
//封装的小写转大写方法,适合各种场景
  const amountToChinese = (num)=> {
    const cnNums = ['零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖']
    const cnIntRadice = ['', '拾', '佰', '仟']
    const cnIntUnits = ['', '万', '亿', '兆']
    const cnDecUnits = ['角', '分', '毫', '厘']
    const cnInteger = '整'
    const cnIntLast = '元'
    if(num > maxNum.value){
      return '-'
    }
    // 处理负数
    let sign = '';
    if (num < 0) {
      sign = '负';
      num = Math.abs(num);
    }

    // 分离整数和小数部分
    let numStr = num.toString();
    let integerStr = '';
    let decimalStr = '';
    if (numStr.indexOf('.') !== -1) {
      const parts = numStr.split('.');
      integerStr = parts[0];
      decimalStr = parts[1].substring(0, 4); // 最多支持4位小数
    } else {
      integerStr = numStr;
    }

    // 处理整数部分
    let chineseInteger = '';
    if (parseInt(integerStr, 10) > 0) {
      let zeroCount = 0;
      const intLen = integerStr.length;
      for (let i = 0; i < intLen; i++) {
        const n = integerStr.charAt(i);
        const p = intLen - i - 1;
        const q = p / 4;
        const m = p % 4;
        if (n === '0') {
          zeroCount++;
        } else {
          if (zeroCount > 0) {
            chineseInteger += cnNums[0];
          }
          zeroCount = 0;
          chineseInteger += cnNums[parseInt(n)] + cnIntRadice[m];
        }
        if (m === 0 && zeroCount < 4) {
          chineseInteger += cnIntUnits[q];
        }
      }
      chineseInteger += cnIntLast;
    }

    // 处理小数部分
    let chineseDecimal = '';
    if (decimalStr) {
      for (let i = 0; i < decimalStr.length; i++) {
        const n = decimalStr.charAt(i);
        if (n !== '0') {
          chineseDecimal += cnNums[parseInt(n)] + cnDecUnits[i];
        }
      }
    }

    // 组合结果
    let result = sign + chineseInteger + chineseDecimal;
    if (!chineseInteger && !chineseDecimal) {
      result = cnNums[0] + cnIntLast + cnInteger;
    } else if (!chineseDecimal) {
      result += cnInteger;
    }

    return result;
  }
样式中通过定位,来实现input上面内容的展示
<style lang="scss" scoped>
  .moneyInputBox{
    position: relative;
    input{
      width: 100%;
      padding: 0 10rpx !important;
      box-sizing: border-box !important;
      border: 1rpx solid #dadbde;
      height: 62rpx !important;
      line-height: 42rpx!important;
      border-radius: 4px;
    }
    .bigNumBox{
      position: absolute;
      bottom: 76rpx;
      width:auto;
      background: rgba(0,0,0,.5);
      padding:10rpx;
      color:#fff;
      border-radius: 20rpx;
      border:1rpx solid #dadbde;
    }
  }
</style>

5)将我们当前的组件,用watch监听+emit发送的方法,实现数据的双向绑定,可以在父级用v-model来绑定内容

js具体配置:
<script setup>
	import { ref, watch, defineProps, defineEmits } from 'vue'
  const props = defineProps({
    modelValue: {
      type: [Number, String],
      default: 0
    }
  })
  const emit = defineEmits(['update:modelValue'])

   // 监听外部传入的modelValue变化
  watch(() => props.modelValue, (newVal) => {
    if (newVal !== rawValue.value) {
      rawValue.value = parseFloat(newVal) || 0
      displayValue.value = formatDisplay(rawValue.value)
      bigNumCont.value = amountToChinese(rawValue.value)
    }
  }, { immediate: true })  
    

</script>
父级调用:
<moneyInput v-model="moneyNum"></moneyInput>

<script setup>

    const moneyNum = ref(100)

封装代码源码  moneyInput.vue

<template>
	<view class="moneyInputBox">
		<input
			v-model="displayValue"
			@input="handleInput"
			@blur="handleBlur"
			@focus="handleFocus"
			placeholder="请输入金额"
		/>
    <view v-if="showBigNum" class="bigNumBox">{{bigNumCont}}</view>
	</view>
</template>

<script setup>
	import { ref, watch, defineProps, defineEmits } from 'vue'
  const props = defineProps({
    modelValue: {
      type: [Number, String],
      default: 0
    }
  })
  const emit = defineEmits(['update:modelValue'])


	const displayValue = ref('')
	const rawValue = ref(0)
	const maxFloatNum = ref(2)
	const maxNum = ref(100000000000)
	const showBigNum = ref(false)
	const bigNumCont = ref('')
	// 格式化显示值(添加千分位)
	const formatDisplay = (value) => {
	  const num = parseFloat(value || 0)
	  return num.toFixed(maxFloatNum.value).replace(/\B(?=(\d{3})+(?!\d))/g, ',')
	}
	// 处理输入变化
	const handleInput = (e) => {
		let val = e.detail.value
		if(!val) return ''
	  let value = val.toString().replace(/[^\d.]/g, '')
		if (value.length > 1 && value.startsWith('0')) {
		  value = value.substring(1)
		}
		const min = 0
	  const max = maxNum.value

	  // 确保只有一个点
	  const parts = value.split('.')
	  if (parts.length > 2) {
	    value = parts[0] + '.' + parts.slice(1).join('')
	  }
	  // 限制小数点后两位
	  if (parts.length === maxFloatNum.value) {
	    value = parts[0] + '.' + parts[1].slice(0, maxFloatNum.value)
	  }

	  // 转换为数字
	  const numValue = parseFloat(value || 0)

	  // 检查范围
	  if (numValue > max) {
	    value = max.toString()
	  } else if (numValue < min) {
	    value = min.toString()
	  }

	  rawValue.value = numValue
    displayValue.value = value
    bigNumCont.value = amountToChinese(rawValue.value)
    emit('update:modelValue', rawValue.value)
	}

	// 处理失去焦点
	const handleBlur = () => {
    showBigNum.value = false
	  displayValue.value = formatDisplay(rawValue.value)
    bigNumCont.value = amountToChinese(rawValue.value)
	}

	// 处理获取焦点
	const handleFocus = () => {
    showBigNum.value = true
	  displayValue.value = rawValue.value.toString()
    bigNumCont.value = amountToChinese(rawValue.value)
	}
  const amountToChinese = (num)=> {
    const cnNums = ['零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖']
    const cnIntRadice = ['', '拾', '佰', '仟']
    const cnIntUnits = ['', '万', '亿', '兆']
    const cnDecUnits = ['角', '分', '毫', '厘']
    const cnInteger = '整'
    const cnIntLast = '元'
    if(num > maxNum.value){
      return '-'
    }
    // 处理负数
    let sign = '';
    if (num < 0) {
      sign = '负';
      num = Math.abs(num);
    }

    // 分离整数和小数部分
    let numStr = num.toString();
    let integerStr = '';
    let decimalStr = '';
    if (numStr.indexOf('.') !== -1) {
      const parts = numStr.split('.');
      integerStr = parts[0];
      decimalStr = parts[1].substring(0, 4); // 最多支持4位小数
    } else {
      integerStr = numStr;
    }

    // 处理整数部分
    let chineseInteger = '';
    if (parseInt(integerStr, 10) > 0) {
      let zeroCount = 0;
      const intLen = integerStr.length;
      for (let i = 0; i < intLen; i++) {
        const n = integerStr.charAt(i);
        const p = intLen - i - 1;
        const q = p / 4;
        const m = p % 4;
        if (n === '0') {
          zeroCount++;
        } else {
          if (zeroCount > 0) {
            chineseInteger += cnNums[0];
          }
          zeroCount = 0;
          chineseInteger += cnNums[parseInt(n)] + cnIntRadice[m];
        }
        if (m === 0 && zeroCount < 4) {
          chineseInteger += cnIntUnits[q];
        }
      }
      chineseInteger += cnIntLast;
    }

    // 处理小数部分
    let chineseDecimal = '';
    if (decimalStr) {
      for (let i = 0; i < decimalStr.length; i++) {
        const n = decimalStr.charAt(i);
        if (n !== '0') {
          chineseDecimal += cnNums[parseInt(n)] + cnDecUnits[i];
        }
      }
    }

    // 组合结果
    let result = sign + chineseInteger + chineseDecimal;
    if (!chineseInteger && !chineseDecimal) {
      result = cnNums[0] + cnIntLast + cnInteger;
    } else if (!chineseDecimal) {
      result += cnInteger;
    }

    return result;
  }

  // 监听外部传入的modelValue变化
  watch(() => props.modelValue, (newVal) => {
    if (newVal !== rawValue.value) {
      rawValue.value = parseFloat(newVal) || 0
      displayValue.value = formatDisplay(rawValue.value)
      bigNumCont.value = amountToChinese(rawValue.value)
    }
  }, { immediate: true })
</script>

<style lang="scss" scoped>
  .moneyInputBox{
    position: relative;
    input{
      width: 100%;
      padding: 0 10rpx !important;
      box-sizing: border-box !important;
      border: 1rpx solid #dadbde;
      height: 62rpx !important;
      line-height: 42rpx!important;
      border-radius: 4px;
    }
    .bigNumBox{
      position: absolute;
      bottom: 76rpx;
      width:auto;
      background: rgba(0,0,0,.5);
      padding:10rpx;
      color:#fff;
      border-radius: 20rpx;
      border:1rpx solid #dadbde;
    }
  }
</style>