一个简单数学公式输入组件FormulaInput

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>

属性说明

属性名类型默认值说明
modelValuestring | number‘’组件的值,支持 v-model 双向绑定
placeholderstring‘请输入数字或计算公式’输入框占位文本
disabledbooleanfalse是否禁用
optionsOption[][]下拉选项列表
decimalPlacesnumber2小数位数,小于 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

注意事项

  1. 输入公式时需要使用等号(=)触发计算
  2. 失焦时会自动格式化结果
  3. 所有返回给父组件的值都是数字类型
  4. 当输入值无法转换为数字时,会返回 0
  5. 在 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> 
使用vue3 +step + ts = lang写一个页面,所有代码都在同一个文件;页面有一个叫编辑公式按钮;点击后,弹出一个页面,这个页面分左右两块A、B块;A块在左,B块在右;A块是二级树,带搜索功能;B块分上下三块B1/B2/B3块,B1占B块的60%,B2块占B块的30%,B3块占B块的10%;B1是多功能输入框,输入框支持上下滚动;输入来源分三块,第一块是左侧树,第二块是入由下方多功能按钮区带入,第三块是键盘输入,键盘输入只能数字和英文字符,自动带入的除外;B2是多功能按钮区,分别是+、-、*、/、if、else if、;、return、>、<、=、{、}、(、)等点击按钮,按钮之间间隔5px,点击B2的按钮,自动带入到B1输入框内;其中点击换行时,B1输入框自动换行,且添加分割线,分割线上下间距1;B1输入框删除时,A块、B2 带入的只能整体删除,不能单个字符删除;B3块有三个按钮靠右排列,分别是公式校验、确认、取消;A块树点击后,自动带入树的值到B1输入框内,展示时显示树的名称,返回的是树的id;B1用户输入只能数字和英文字符,自动带入的除外;B1输入框中的内容起始位正输入框的左上角,距边框5px;B1展示内容样式为随机颜色按钮,点击后,展示左插入、右插入、替换、删除操作,其中删除以右上角角标形式展示;点击B3块确认按钮时,以B1的内容以字符串且保留换行符的形式返回,回显时,需要根据所提供的字符串识别分块;
06-22
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值