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;
}
}
});
}
};
<input
type="text"
v-model="ipValue"
placeholder="请输入IP地址"
v-ip-input.strict="{
invalid: 'IP格式不正2确',
incomplete: '请补全IP地3址',
}"
/>