前言:
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>