指令 v-number

456 阅读1分钟

v-number 指令主要为了统一解决数字输入的限制问题 1.失去焦点输入框中的值进行千分位处理,绑定的值不进行千分位处理 2.小数位的限制输入 3.最值限制

// 文件 directive-tool.js
/**
 * 千分位处理函数
 * @param {*} num 传入的值
 * @param {*} dot 千分位符合 默认使用逗号
 * @returns
 */
export function formatThousand(num, dot = ',') {
  let str = '';
  if (typeof num === 'number') {
    str = num.toString();
  } else if (typeof num === 'string') {
    if (num === '') return '';
    if (Number.isNaN(Number(num))) {
      console.error(`${num} is NaN`);
      return '';
    }
    str = num;
  } else {
    console.error(`${num} is NaN`);
    return '';
  }
  const strs = str.split('.');
  let pre = '';
  const midx = strs[0][0] !== '-' ? 3 : 4;
  let idx = strs[0].length;
  while (idx > midx) {
    const idx_pre = idx - 3;
    pre = pre === '' ? strs[0].slice(idx_pre, idx) : `${strs[0].slice(idx_pre, idx)}${dot}${pre}`;
    idx = idx_pre;
  }
  pre = pre === '' ? strs[0] : `${strs[0].slice(0, idx)}${dot}${pre}`;
  return strs.length === 1 ? pre : `${pre}.${strs[1]}`;
}

/**
 * 清除千分位处理函数
 * @param {*} str 传入的值
 * @param {*} dot 千分位符号
 * @returns 
 */
export function formatClearThousand(str, dot = ',') {
  if (typeof str !== 'string') {
    console.error(`${str} is not string`);
    return str;
  }
  const strs = str.split(dot);
  return strs.join('');
}
// 文件 demo.vue 
<template>
  <div>
    <el-input v-number="{ max: 20110, min: -100, preserve: 4, thousand: true }" v-model="val" />
  </div>
</template>

<script>
import { formatThousand, formatClearThousand } from './directive-tool';
/**
 * 指令 v-number 用于控制输入框输入数字限制
 * max 最大值限制
 * min 最小值
 * preserve 保留的小数点位数
 * thousand 是否在失去焦点的时候做千分位处理
 */
export default {
  directives: {
    number: {
      bind(el, binding, vnode) {
        const input = el.getElementsByTagName('input')[0];
        let preserve = Number(binding.value.preserve);
        if (Number.isNaN(preserve) && binding.value.preserve !== undefined) {
          console.error(`v-number:{ preserve : ${binding.value.preserve} } is NaN`);
        }
        // 最值限制
        const limitVal = (val) => {
          let nv = val;
          if (binding.value.max !== undefined) {
            // 最大值限制
            const max = Number(binding.value.max);
            if (Number.isNaN(max)) console.error(`v-number:{ max : ${binding.value.max} } is NaN`);
            else nv = nv > max ? max : nv;
          }
          if (binding.value.min !== undefined) {
            // 最小值限制
            const min = Number(binding.value.min);
            if (Number.isNaN(binding.value.min)) console.error(`v-number:{ min : ${binding.value.min} } is NaN`);
            else nv = nv < min ? min : nv;
          }
          return nv;
        };
        preserve = Number.isNaN(preserve) ? 2 : preserve;
        input.oninput = (event) => {
          if (input.value === '') return;
          const vals = [];
          const idx = input.value.indexOf('.');
          if (idx === -1) {
            vals.push(input.value);
          } else {
            const pre = input.value.slice(0, idx);
            if (pre !== '') vals.push(input.value.slice(0, idx));
            vals.push(input.value.slice(idx + 1, input.value.length));
          }
          vals.forEach((val, index) => {
            if (index === 0) {
              if (val[0] === '-') {
                vals[index] = `-${val.slice(1, val.length).replace(/[^\d]/g, '')}`;
              } else {
                vals[index] = val.replace(/[^\d]/g, '');
              }
            } else {
              vals[index] = val.replace(/[^\d]/g, '');
            }
          });
          input.value = limitVal(vals.join('.'));
          vnode.componentInstance.$emit('input', input.value);
        };
        input.onfocus = (event) => {
          input.value = formatClearThousand(input.value, binding.value?.dot || ',');
        };
        input.onblur = (event) => {
          if (input.value === '') return;
          const vals = [];
          const idx = input.value.indexOf('.');
          if (idx === -1) {
            vals.push(input.value);
            vals.push('');
          } else {
            const pre = input.value.slice(0, idx);
            if (pre !== '') vals.push(input.value.slice(0, idx));
            vals.push(input.value.slice(idx + 1, input.value.length));
          }
          const c = [];
          const cs = vals[1];
          for (let i = 0; i < preserve; i += 1) {
            c[i] = i < cs.length ? cs[i] : '0';
          }
          if (preserve === 0) {
            input.value = String(vals[0]);
          } else {
            vals[1] = c.join('');
            input.value = vals.join('.');
          }
          input.value = limitVal(input.value);
          vnode.componentInstance.$emit('input', input.value);
          if (binding.value.thousand) {
            setTimeout(() => {
              input.value = formatThousand(input.value, binding.value?.dot || ',');
            }, 500);
          }
        };
      },
      inserted(el, binding, vnode) {
        const input = el.getElementsByTagName('input')[0];
        if (binding.value.thousand) input.value = formatThousand(input.value, binding.value?.dot || ',');
      },
    },
  },
  data() {
    return { val: '' };
  },
};
</script>