FormulaInput 公式输入组件
一个支持简单数学公式计算的输入组件,基于 Vue 3 + TypeScript + Ant Design Vue 开发。
功能特点
- 支持直接输入数字
- 支持输入数学公式(使用等号触发计算)
- 支持下拉选项选择
- 支持自定义小数位数
- 实时显示计算结果
- 失焦时自动格式化结果
- 支持 v-model 双向绑定
- 支持禁用状态
使用示例
<template>
<formula-input
v-model="value"
:options="options"
:decimal-places="2"
placeholder="请输入数字或计算公式"
/>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const value = ref<number>(0);
const options = ref([
{ label: '选项1', value: 100 },
{ label: '选项2', value: 200 },
]);
</script>
属性说明
属性名 | 类型 | 默认值 | 说明 |
---|---|---|---|
modelValue | string | number | ‘’ | 组件的值,支持 v-model 双向绑定 |
placeholder | string | ‘请输入数字或计算公式’ | 输入框占位文本 |
disabled | boolean | false | 是否禁用 |
options | Option[] | [] | 下拉选项列表 |
decimalPlaces | number | 2 | 小数位数,小于 0 表示不保留小数 |
Option 接口
interface Option {
label: string; // 选项显示文本
value: string | number; // 选项值
}
事件
事件名 | 说明 | 回调参数 |
---|---|---|
update:modelValue | 值更新时触发 | (value: number) |
change | 值变化时触发 | (value: number) |
数学公式支持
组件支持以下数学运算符:
- 加法:
+
- 减法:
-
- 乘法:
*
- 除法:
/
- 括号:
()
- 小数点:
.
示例:
1 + 1 = 2
2 * 3 = 6
(1 + 2) * 3 = 9
10 / 2 = 5
注意事项
- 输入公式时需要使用等号(=)触发计算
- 失焦时会自动格式化结果
- 所有返回给父组件的值都是数字类型
- 当输入值无法转换为数字时,会返回 0
- 在 vxe-table 的可编辑单元格中使用时,组件会自动处理编辑态切换时的计算
依赖项
- Vue 3
- TypeScript
- Ant Design Vue
- mathjs(用于数学公式计算)
- es2020
组件代码
<template>
<div class="formula-input-wrapper">
<a-auto-complete
v-model:value="inputValue"
:placeholder="placeholder"
:disabled="disabled"
:options="options"
:filter-option="false"
@search="handleSearch"
@blur="handleBlur"
@focus="handleFocus"
@select="handleSelect"
>
<template #suffix>
<span v-if="result !== null && !isFocused" class="result">= {{ result }}</span>
</template>
</a-auto-complete>
</div>
</template>
<script lang="ts" setup>
import { ref, watch, onMounted, onBeforeUnmount } from 'vue';
import { evaluate } from 'mathjs';
// 定义选项接口
interface Option {
label: string;
value: string | number;
}
// 定义组件属性接口
interface Props {
modelValue?: string | number;
placeholder?: string;
disabled?: boolean;
options?: Option[];
decimalPlaces?: number; // 小数位数配置
}
// 设置组件属性默认值
const props = withDefaults(defineProps<Props>(), {
modelValue: '',
placeholder: '请输入数字或计算公式',
disabled: false,
options: () => [],
decimalPlaces: 2, // 默认保留两位小数
});
const emit = defineEmits(['update:modelValue', 'change']);
// 核心状态变量
const inputValue = ref<string>(''); // 输入框的值
const result = ref<number | null>(null); // 计算结果
const isFocused = ref<boolean>(false); // 输入框是否聚焦
/**
* 格式化数字,根据配置保留小数位数并四舍五入
* @param num 需要格式化的数字
* @returns 格式化后的数字字符串
*/
const formatNumber = (num: number) => {
// 如果 decimalPlaces 小于 0,表示不需要保留小数位数
if (props.decimalPlaces < 0) {
return Math.round(num).toString();
}
// 如果需要保留小数位数,使用配置的位数
return Number(Math.round(Number(num + `e${props.decimalPlaces}`)) + `e-${props.decimalPlaces}`).toFixed(props.decimalPlaces);
};
/**
* 计算公式结果
* @param formula 计算公式
*/
const calculateResult = (formula: string) => {
try {
formula = formula.replace(/\s/g, ''); // 移除所有空格
// 验证公式有效性
if (!formula || !/^[\d+\-*/(). ]+$/.test(formula)) {
result.value = null;
return;
}
// 计算结果
result.value = evaluate(formula);
} catch (error) {
result.value = null;
}
};
/**
* 初始化输入值并处理公式
* @param value 初始值
*/
const initValue = (value: string | number) => {
if (value) {
inputValue.value = String(value);
if (inputValue.value.includes('=')) {
const formula = inputValue.value.split('=')[1].trim();
calculateResult(formula);
}
}
};
// 事件处理函数
const handleSearch = (value: string) => {
// 尝试将输入值转换为数字
const numValue = Number(value);
emit('update:modelValue', isNaN(numValue) ? 0 : numValue);
emit('change', isNaN(numValue) ? 0 : numValue);
};
const handleSelect = (value: string | number) => {
inputValue.value = String(value);
result.value = null;
// 确保返回数字类型
const numValue = Number(value);
emit('update:modelValue', isNaN(numValue) ? 0 : numValue);
emit('change', isNaN(numValue) ? 0 : numValue);
};
const handleBlur = () => {
isFocused.value = false;
if (result.value !== null) {
const formattedResult = formatNumber(result.value);
inputValue.value = formattedResult;
// 使用计算结果(已经是数字类型)
emit('update:modelValue', result.value);
emit('change', result.value);
} else {
const numValue = Number(inputValue.value);
emit('update:modelValue', isNaN(numValue) ? 0 : numValue);
emit('change', isNaN(numValue) ? 0 : numValue);
}
};
const handleFocus = () => {
isFocused.value = true;
};
// 生命周期钩子
onMounted(() => {
initValue(props.modelValue);
});
// 监听外部值变化
watch(
() => props.modelValue,
(newVal) => {
if (newVal !== inputValue.value) {
initValue(newVal);
}
}
);
// 监听输入值变化,自动计算公式
watch(
() => inputValue.value,
(newVal) => {
if (newVal && newVal.includes('=')) {
const formula = newVal.split('=')[1].trim();
calculateResult(formula);
// 如果计算结果存在,立即格式化
if (result.value !== null) {
result.value = Number(formatNumber(result.value));
}
} else {
result.value = null;
}
}
);
// 组件卸载前确保公式计算完成
onBeforeUnmount(() => {
if (inputValue.value && inputValue.value.includes('=')) {
const formula = inputValue.value.split('=')[1].trim();
calculateResult(formula);
if (result.value !== null) {
result.value = Number(formatNumber(result.value));
emit('update:modelValue', result.value);
emit('change', result.value);
} else {
const numValue = Number(inputValue.value);
emit('update:modelValue', isNaN(numValue) ? 0 : numValue);
emit('change', isNaN(numValue) ? 0 : numValue);
}
}
});
</script>
<style lang="less" scoped>
.formula-input-wrapper {
width: 100%;
// 确保输入框和自动完成组件占满容器宽度
:deep(.ant-select),
:deep(.ant-input-affix-wrapper) {
width: 100%;
}
// 计算结果样式
.result {
color: #1890ff;
margin-left: 8px;
}
}
</style>