数学公式Input-输入框组件实现详解

47 阅读5分钟

数学公式输入框组件实现详解

引言

在开发数学类应用时,我们经常需要让用户输入数学表达式和公式。一个优秀的数学输入框需要具备灵活的位置定位、输入验证、响应式布局等特性。本文将详细介绍我实现的数学输入框组件,它可以应用在数学教学、科学计算等场景中。

组件功能概览

这个数学输入框组件实现了以下核心功能:

  • 精确定位能力(支持绝对定位)
  • 输入模式切换(数字或文本)
  • 双向数据绑定
  • 自定义验证规则
  • 动态样式配置
  • 尺寸控制(小/中/大)
  • 预置主题支持(默认/边框/下划线)
  • 完整API(聚焦、清空、设值等方法)

代码实现详解

模板部分

<template>
  <input
    ref="inputRef"
    type="text"
    v-model="inputValue"
    @input="handleInput"
    @focus="handleFocus"
    @blur="handleBlur"
    :class="['math-input', customClass, { 'is-focused': isFocused }]"
    :style="inputStyle"
    :maxlength="maxLength"
    :pattern="pattern"
    :placeholder="placeholder"
    :disabled="disabled"
    v-show="visible" />
</template>

​关键点说明​​:

  • ref="inputRef":用于获取DOM引用
  • v-model实现双向绑定
  • 根据焦点状态添加is-focused
  • 多种属性绑定实现灵活配置
  • v-show控制显示/隐藏而非v-if,避免布局跳动

组件配置选项

const props = defineProps({
  modelValue: String,       // 双向绑定值
  maxLength: Number,        // 最大长度(默认1)
  inputMode: {              // 输入模式(number/text)
    type: String,
    default: 'number',
    validator: value => ['number', 'text'].includes(value)
  },
  customClass: String,      // 自定义类名
  position: Object,         // 定位配置
  size: Object,             // 尺寸配置
  styleConfig: Object,      // 样式配置
  placeholder: String,      // 占位文本
  disabled: Boolean,        // 禁用状态
  visible: Boolean,         // 可见性
  validator: Function       // 自定义验证函数
});

这个组件通过多种配置属性实现了高度定制化能力。

响应式计算属性

// 根据输入模式生成正则模式
const pattern = computed(() => {
  return props.inputMode === 'number' ? '\d*' : undefined;
});

// 合并所有样式配置
const inputStyle = computed(() => {
  return {
    position: 'absolute',
    outline: 'none',
    caretColor: 'black',
    ...props.position, // 位置配置
    ...props.size,      // 尺寸配置
    ...props.styleConfig // 样式配置
  };
});

这种合并策略允许用户灵活覆盖各种样式属性。

核心输入处理逻辑

function handleInput(event) {
  let value = event.target.value;

  // 数字模式只允许数字
  if (props.inputMode === 'number') {
    value = value.replace(/\D/g, '');
  }

  // 应用长度限制
  if (value.length > props.maxLength) {
    value = value.slice(0, props.maxLength);
  }

  // 自定义验证
  if (props.validator && typeof props.validator === 'function') {
    const validationResult = props.validator(value);
    if (validationResult !== true) {
      event.target.value = inputValue.value;
      return;
    }
  }

  // 更新值并触发事件
  inputValue.value = value;
  event.target.value = value;
  emit('input', value, event);
  emit('change', value, event);
}

该函数实现了:

  1. 输入过滤(数字模式)
  2. 长度限制
  3. 自定义验证
  4. 事件触发

暴露给外部的方法

defineExpose({
  focus: () => inputRef.value?.focus(),     // 手动聚焦
  blur: () => inputRef.value?.blur(),       // 手动失焦
  clear: () => {                            // 清空内容
    inputValue.value = '';
    emit('update:modelValue', '');
  },
  getValue: () => inputValue.value,         // 获取当前值
  setValue: value => {                      // 设置值
    inputValue.value = value;
    emit('update:modelValue', value);
  }
});

这些方法通过defineExpose暴露,使父组件可以灵活控制输入框。

样式系统设计

.math-input {
  position: absolute;
  text-align: center;
  
  // 尺寸预设
  &.small { width: 0.6rem; height: 0.5rem; font-size: 0.4rem; }
  &.medium { width: 0.8rem; height: 0.6rem; font-size: 0.5rem; }
  &.large { width: 1rem; height: 0.8rem; font-size: 0.6rem; }
  
  // 主题系统
  &.theme-default {
    background-color: transparent;
    border: transparent;
  }
  &.theme-bordered {
    background-color: white;
    border: 1px solid #ccc;
    &.is-focused {
      border-color: #007bff;
      box-shadow: 0 0 0 0.05rem rgba(0, 123, 255, 0.25);
    }
  }
  &.theme-underline {
    border-bottom: 1px solid #ccc;
    &.is-focused { border-bottom-color: #007bff; }
  }
}

样式系统特点:

  • 使用rem单位保持比例缩放
  • 三种预设尺寸
  • 三种主题风格
  • 焦点状态特殊样式
  • 简洁的透明背景默认主题

使用示例

基础数学输入

<MathInput 
  v-model="number" 
  input-mode="number" 
  position="{ top: '20px', left: '50px' }"
  size="{ width: '1.5rem', height: '1.2rem' }"
/>

带验证的复杂公式输入

<MathInput 
  v-model="formula"
  :maxLength="10"
  :validator="validateFormula"
  position="{ top: '120px', left: '75px' }"
  custom-class="medium theme-bordered"
/>

<script>
// 自定义验证函数
const validateFormula = (value) => {
  // 只允许字母、数字和基本运算符
  return /^[\w\d+-*/=^()., ]+$/.test(value);
};
</script>

在数学公式中使用

<div class="formula-container">
  公式:y = 
  <MathInput 
    v-model="a" 
    size="{ width: '0.8rem', height: '0.6rem' }"
    custom-class="theme-underline"
  /> x + 
  <MathInput 
    v-model="b"
    size="{ width: '0.8rem', height: '0.6rem' }"
    custom-class="theme-underline"
  />
</div>

关键开发技巧分享

  1. ​灵活定位​

    • 使用position对象配置,支持动态变更位置
    • 通过transform居中定位:transform: translate(-50%, -50%)
  2. ​可扩展样式​

    • 样式分层配置:位置、尺寸、基础样式分离
    • 自定义类名和默认类的合并策略
  3. ​输入验证​

    • 内置数字模式校验
    • 开放validator属性支持自定义验证
    • 避免输入非法字符
  4. ​API设计​

    • 通过defineExpose暴露常用方法
    • 支持编程式聚焦/设值
    • 清空内容时自动触发更新事件
  5. ​响应式设计​

    • 深度监听modelValue变化
    • 使用computed实现高效的样式合并

适用场景

该组件非常适合以下应用场景:

  • 在线数学试卷/答题系统
  • 科学计算器的输入界面
  • 几何画板的标注输入
  • 数学公式编辑器
  • 数据可视化中的参数调整

例子:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>简化版数学输入框</title>
  <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
  <style>
    .math-input {
      width: 60px;
      height: 40px;
      font-size: 20px;
      text-align: center;
      border: 1px solid #ccc;
      border-radius: 4px;
      outline: none;
    }
    .math-input:focus {
      border-color: #4285f4;
      box-shadow: 0 0 3px rgba(66, 133, 244, 0.5);
    }
    .demo {
      margin: 20px;
      padding: 20px;
      border: 1px solid #eee;
    }
  </style>
</head>
<body>
  <div id="app">
    <h1>简化版数学输入框</h1>
    
    <div class="demo">
      <h3>基本用法</h3>
      <math-input v-model="number1" mode="number"></math-input>
      <p>输入的值: {{ number1 }}</p>
    </div>
    
    <div class="demo">
      <h3>在公式中使用</h3>
      <p>
        y = <math-input v-model="a" mode="number" size="small"></math-input>x + 
        <math-input v-model="b" mode="number" size="small"></math-input>
      </p>
      <p>计算结果: y = {{ a }}x + {{ b }}</p>
    </div>
    
    <div class="demo">
      <h3>带验证的输入</h3>
      <math-input 
        v-model="age" 
        mode="number" 
        :validator="validateAge"
        placeholder="输入年龄"
      ></math-input>
      <p>{{ ageMessage }}</p>
    </div>
  </div>

  <script>
    const { createApp, ref } = Vue;
    
    // 简化版数学输入框组件
    const MathInput = {
      template: `
        <input
          class="math-input"
          :class="size"
          type="text"
          v-model="inputValue"
          @input="handleInput"
          :placeholder="placeholder"
        />
      `,
      props: {
        modelValue: String,
        mode: {
          type: String,
          default: 'text',
          validator: v => ['text', 'number'].includes(v)
        },
        size: {
          type: String,
          default: 'medium',
          validator: v => ['small', 'medium', 'large'].includes(v)
        },
        placeholder: String,
        validator: Function
      },
      emits: ['update:modelValue'],
      setup(props, { emit }) {
        const inputValue = ref(props.modelValue || '');
        
        function handleInput(event) {
          let value = event.target.value;
          
          // 数字模式处理
          if (props.mode === 'number') {
            value = value.replace(/\D/g, '');
          }
          
          // 自定义验证
          if (props.validator && !props.validator(value)) {
            event.target.value = inputValue.value;
            return;
          }
          
          inputValue.value = value;
          emit('update:modelValue', value);
        }
        
        return { inputValue, handleInput };
      }
    };
    
    // 主应用
    createApp({
      components: { MathInput },
      setup() {
        const number1 = ref('3');
        const a = ref('2');
        const b = ref('5');
        const age = ref('');
        const ageMessage = ref('');
        
        const validateAge = (value) => {
          if (!value) {
            ageMessage.value = '请输入年龄';
            return true;
          }
          
          const num = parseInt(value);
          if (isNaN(num) || num < 1 || num > 120) {
            ageMessage.value = '请输入1-120之间的有效年龄';
            return false;
          }
          
          ageMessage.value = '年龄有效';
          return true;
        };
        
        return { number1, a, b, age, ageMessage, validateAge };
      }
    }).mount('#app');
  </script>
</body>
</html>

image.png