uniapp-vue3来实现一个金额千分位展示效果
前言:
uniapp-vue3来实现一个金额千分位展示效果
实现效果:
实现目标:
1、封装组件,组件内部要实现,
- input输入金额后,聚焦离开后,金额以千分位效果展示,
- 聚焦后展示大写金额的弹框
- 随时写的内容,可以用v-model传输给父级
- 金额要有最大值,还要只能输入数字,小数后只能有2位
2、实现方法:
1)input框输入金额,并添加聚焦,离开事件
页面上添加内容:
<inputv-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.valueif(!val) return ''let value = val.toString().replace(/[^\d.]/g, '')if (value.length > 1 && value.startsWith('0')) {value = value.substring(1)}const min = 0const 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 = numValuedisplayValue.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) || 0displayValue.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"><inputv-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.valueif(!val) return ''let value = val.toString().replace(/[^\d.]/g, '')if (value.length > 1 && value.startsWith('0')) {value = value.substring(1)}const min = 0const 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 = numValuedisplayValue.value = valuebigNumCont.value = amountToChinese(rawValue.value)emit('update:modelValue', rawValue.value)}// 处理失去焦点const handleBlur = () => {showBigNum.value = falsedisplayValue.value = formatDisplay(rawValue.value)bigNumCont.value = amountToChinese(rawValue.value)}// 处理获取焦点const handleFocus = () => {showBigNum.value = truedisplayValue.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) || 0displayValue.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>