代替vue-currency-input原生货币符号,
vue-currency-input,使用缺点
- 小数点输入光标会跳动
- 货币符号无法设置输入框右侧
- 输入内容无法展示小数点
- 兼容 element-plus的el-input功能差
使用环境UI组件库element-plus,直接上代码,
CurrencyInput.vue
vue
<template>
<div class="currency-input">
<label v-if="label" :for="id" class="label">{{ label }}</label>
<div class="input-wrapper">
<el-input :id="id" v-model="inputValue" :placeholder="placeholder" :disabled="disabled" @focus="onFocus"
@blur="onBlur">
<template #[slotName]>
<span class="currency-symbol">{{ currencySymbol }}</span>
</template>
</el-input>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, watch, onMounted, } from 'vue';
// 计算属性返回插槽名
const slotName = computed(() => props.appendMode);
interface Props {
modelValue?: number | null;
label?: string;
placeholder?: string;
disabled?: boolean;
id?: string;
currency?: string;
locale?: string;
decimalPlaces?: number;
min?: number;
max?: number;
precision?: number; // 小数点后保留位数
appendMode?: 'prefix' | 'suffix'; // 追加模式,前缀或后缀
}
const props = withDefaults(defineProps<Props>(), {
modelValue: null,
label: '',
placeholder: '',
disabled: false,
id: 'currency-input',
currency: 'CNY',
locale: 'zh-CN',
decimalPlaces: 2,
min: -Infinity,
max: Infinity,
precision: 2,
appendMode: 'suffix', // 追加模式,默认为后缀
});
const emit = defineEmits(['update:modelValue', 'focus', 'blur']);
// 输入框显示值
const inputValue = ref('');
// 是否聚焦
const isFocused = ref(false);
// 获取货币符号
const currencySymbol = computed(() => {
try {
const formatter = new Intl.NumberFormat(props.locale, {
style: 'currency',
currency: props.currency,
minimumFractionDigits: 0,
maximumFractionDigits: 0
});
const parts = formatter.formatToParts(1);
const symbolPart = parts.find(part => part.type === 'currency');
return symbolPart ? symbolPart.value : props.currency;
} catch {
return props.currency;
}
});
// 数字转为千分位格式
const toFormatted = (num: number | null): string => {
if (num === null || isNaN(num)) return '';
return new Intl.NumberFormat(props.locale, {
minimumFractionDigits: props.decimalPlaces,
maximumFractionDigits: props.decimalPlaces
}).format(num);
};
// 字符串解析为数字
const toNumber = (str: string): number | null => {
if (!str) return null;
const clean = str.replace(/[^\d.-]/g, '');
if (!clean || clean === '-' || clean === '.') return null;
const num = parseFloat(clean);
if (isNaN(num)) return null;
if (num < props.min) return props.min;
if (num > props.max) return props.max;
return num;
};
// 监听外部 modelValue 变化
watch(() => props.modelValue, (val) => {
if (!isFocused.value) {
inputValue.value = toFormatted(val);
}
});
// 聚焦事件
const onFocus = (event: FocusEvent) => {
isFocused.value = true;
// 聚焦时显示纯数字,方便编辑
const num = toNumber(inputValue.value);
inputValue.value = num !== null ? num.toString() : '';
emit('focus', event);
};
// 失焦事件
const onBlur = (event: FocusEvent) => {
isFocused.value = false;
const num = toNumber(inputValue.value);
inputValue.value = toFormatted(num);
emit('update:modelValue', num);
emit('blur', event);
};
// 初始化
onMounted(() => {
if (props.modelValue !== null && props.modelValue !== undefined) {
inputValue.value = toFormatted(props.modelValue);
}
});
</script>
<style scoped>
.currency-input {
display: flex;
flex-direction: column;
margin-bottom: 12px;
}
.label {
margin-bottom: 4px;
font-weight: 500;
color: #333;
}
.input-wrapper {
position: relative;
}
.currency-symbol {
color: #999;
font-size: 14px;
}
</style>
index.vue
js
<template>
<div>
<CurrencyInput v-model="amount" label="金额" placeholder="请输入金额" currency="HKD" locale="zh-CN" :precision="4" append-mode="prefix"
:min="0" :max="9999999" />
<p>当前值:{{ amount }}</p>
</div>
</template>
<script setup>
defineOptions({
name: 'CurrencyInputIndex'
});
import { ref } from 'vue';
import CurrencyInput from './CurrencyInput.vue';
import CurrencyInput1 from './CurrencyInput1.vue';
const amount = ref(null);
</script>
输入框左侧

输入框右侧
