vue3

自定义指令-校验ip输入

# 1.自定义指令-校验 ip 输入

export default {
  install(app) {
    app.directive('ip-input', {
      mounted(el, binding) {
        // 获取修饰符和参数配置
        const { modifiers, value: errorMsg } = binding;
        const { strict = false } = modifiers;

        // 解析错误消息
        const errorMessages = errorMsg || {
          invalid: '请输入有效的IP地址',
          incomplete: 'IP地址不完整'
        };

        // 创建错误提示元素
        const errorElement = document.createElement('div');
        errorElement.className = 'ip-error-message';
        errorElement.style.color = 'red';
        errorElement.style.fontSize = '12px';
        errorElement.style.marginTop = '4px';
        el.parentNode.insertBefore(errorElement, el.nextSibling);

        // 验证IP格式的函数
        const validateIP = (value, isBlur = false) => {
          // 过滤非法字符
          let formattedValue = value.replace(/[^0-9.]/g, '');

          // 处理连续的点
          formattedValue = formattedValue.replace(/\.+/g, '.');

          // 限制最多4个段
          let segments = formattedValue.split('.');
          const validSegments = [];

          // 处理每个段
          for (let i = 0; i < Math.min(segments.length, 4); i++) {
            let segment = segments[i];

            if (parseInt(segment) > 255) {
              segment = '255';
            }

            if (i === segments.length - 1) {
              // 已0开头的,但保留单个0
              if (segment.length > 1 && segment.startsWith('0')) {
                segment = segment.replace(/00/g, '0');
              }
            }

            validSegments.push(segment);
          }

          // 重新组合IP段
          formattedValue = validSegments.join('.');

          // 验证完整IP格式(可选,仅在失去焦点或严格模式时)
          if (isBlur && strict && formattedValue) {
            // 确保IP是完整的(4个段)
            if (validSegments.length === 4) {
              const isValid = validSegments.every(seg => {
                return /^(0|([1-9]\d{0,2}))$/.test(seg) && parseInt(seg) <= 255;
              });

              if (isValid) {
                errorElement.textContent = '';
              } else {
                errorElement.textContent = errorMessages.invalid;
              }
            } else {
              errorElement.textContent = errorMessages.incomplete;
            }
          } else {
            errorElement.textContent = '';
          }

          return formattedValue;
        };

        // 存储事件处理函数的引用,以便后续移除
        const handleInput = e => {
          const originalValue = e.target.value;
          const formattedValue = validateIP(originalValue);

          // 如果值被修改,手动更新输入框
          if (originalValue !== formattedValue) {
            e.target.value = formattedValue;

            // 触发input事件,确保v-model同步
            const event = new Event('input', { bubbles: true });
            e.target.dispatchEvent(event);
          }
        };

        const handleBlur = () => {
          // 自动补全不完整的IP段
          let value = el.value;
          const segments = value.split('.').filter(seg => seg !== '');

          // 如果有不完整的段(例如用户输入了"192.168.")
          if (segments.length < 4 && value.endsWith('.')) {
            segments.push('');
          }

          // 补全缺失的段
          /* while (segments.length < 4) {
            segments.push("0");
          } */

          // 重新组合并验证
          value = segments.join('.');
          const validateData = validateIP(value, true);
          el.value = validateData;
        };

        const handleFocus = () => {
          errorElement.textContent = '';
        };

        const handleKeydown = e => {
          // 按Tab或点号时自动跳到下一段
          if (e.key === 'Tab' || e.key === '.') {
            const cursorPos = el.selectionStart;
            const value = el.value;

            // 如果是点号,阻止默认行为,我们自己处理
            if (e.key === '.') {
              e.preventDefault();

              // 在光标位置插入点号
              const newValue =
                value.substring(0, cursorPos) +
                '.' +
                value.substring(cursorPos);
              el.value = validateIP(newValue);

              // 触发input事件
              const event = new Event('input', { bubbles: true });
              el.dispatchEvent(event);

              // 移动光标到点号后面
              let timer = setTimeout(() => {
                el.selectionStart = el.selectionEnd = cursorPos + 1;
                clearTimeout(timer);
                timer = null;
              }, 0);
            }
          }
        };

        // 添加事件监听器
        el.addEventListener('input', handleInput);
        el.addEventListener('blur', handleBlur);
        el.addEventListener('focus', handleFocus);
        el.addEventListener('keydown', handleKeydown);

        // 存储监听器引用,以便卸载时移除
        el._ipInputListeners = {
          handleInput,
          handleBlur,
          handleFocus,
          handleKeydown,
          errorElement
        };
      },

      unmounted(el) {
        // 移除所有事件监听器
        if (el._ipInputListeners) {
          const {
            handleInput,
            handleBlur,
            handleFocus,
            handleKeydown,
            errorElement
          } = el._ipInputListeners;

          el.removeEventListener('input', handleInput);
          el.removeEventListener('blur', handleBlur);
          el.removeEventListener('focus', handleFocus);
          el.removeEventListener('keydown', handleKeydown);

          // 移除错误提示元素
          if (errorElement && errorElement.parentNode) {
            errorElement.parentNode.removeChild(errorElement);
          }

          // 删除存储的引用
          delete el._ipInputListeners;
        }
      }
    });
  }
};

# 2. 使用

 <input
    type="text"
    v-model="ipValue"
    placeholder="请输入IP地址"
    v-ip-input.strict="{
      invalid: 'IP格式不正2确',
      incomplete: '请补全IP地3址',
    }"
  />
上次更新: