1、组件完成代码
<template><div class="ip-input"><div v-for="(item, index) in ipArr" :key="index" class="ip-input__item-wrap"><input ref="ipInput" v-model="ipArr[index]" type="text" class="ip-input__item" :disabled="(index==ipArr.length-1 && props.lastDisabled) || disabled" :class="{'ip-input__item--active': index === activeIndex}" @input="handleInput(index)" @focus="handleFocus(index)" @blur="handleBlur(index)"@keydown.left.exact="handleFocus(index - 1)" @keydown.right.exact="handleFocus(index + 1)"@keydown.backspace.exact="handleBackspace(index)"><span v-if="index !== ipArr.length - 1" class="ip-input__dot">.</span></div><div class="ip-tip" v-show="showTip">{{props.tipText}}</div></div>
</template>
<script setup lang="ts">const ipInput = ref()
const props = defineProps({// 默认值value: {type: String,default: ''},// 是否禁用输入框disabled: {type: Boolean,default: false},// 是否禁用最后一位,并默认赋值0 (公司业务要求,可忽略)lastDisabled: {type: Boolean,default: false},// 是否启用输入校验isValidate: {type: Boolean,default: false},// 校验提示信息tipText: {type: String,default: '请输入IP'}
})
const lastValue = ref(props.lastDisabled?'0':'')
const ipArr = ref(['', '', '', lastValue.value])
const oldIpInput = ref(['', '', '', lastValue.value])
const activeIndex = ref(-1)
const clipboardText = ref('')const emit = defineEmits(['change', 'input'])const pasteListener = (event: any) => {if (activeIndex.value === -1) { return }const clipboardData = event.clipboardData || window.ClipboardclipboardText.value = clipboardData.getData('text')handlePaste(activeIndex.value)
}
const copyListener = (event: any) => {if (activeIndex.value === -1) { return }const clipboardData = event.clipboardData || window.ClipboardclipboardData.setData('text', ipArr.value.join('.'))event.preventDefault()
}
window.addEventListener('paste', pasteListener)
window.addEventListener('copy', copyListener)onBeforeUnmount(() => {window.removeEventListener('paste', pasteListener)window.removeEventListener('copy', copyListener)
})
const isNumberValid = (value: any) => {return /^\d*$/.test(value) && value <= 255
}
const handleInput = (index: any) => {const newValue: any = ipArr.value[index]// 如果输入的是非数字,或者输入不在0-255之间,则阻止输入if (!isNumberValid(newValue)) {ipArr.value[index] = oldIpInput.value[index]return false}emit('input', ipArr.value.join('.'))oldIpInput.value[index] = newValueif (newValue.length === 3 || (newValue.length === 2 && newValue > 25)) {if (index === ipArr.value.length - 1) { return true }// 将焦点移动到下一个输入框handleFocus(index + 1)}return true
}
const handleFocus = (index: any) => {if (index < 0 || index > ipArr.value.length - 1) { return }if (activeIndex.value !== index) {ipInput.value[index].focus()}activeIndex.value = index
}
const showTip = ref(false)
const handleBlur = (index: any) => {activeIndex.value = -1if(props.isValidate && (ipArr.value[0]==='' || ipArr.value[1]==='' || ipArr.value[2]==='' || ipArr.value[3]==='')) {showTip.value = true} else {showTip.value = false}
}
const handlePaste = (startIndex: any) => {const clipboardText1 = clipboardText.valueconst tempArr = clipboardText1.split('.')let ifor (i = startIndex; i < startIndex + tempArr.length && i < ipArr.value.length; i++) {ipArr.value[i] = tempArr[i]if (!handleInput(i)) { break }}handleFocus(i)
}
const handleBackspace = (index: any) => {if (!ipArr.value[index]) {handleFocus(index - 1)}
}watch(() => props.value,(newVal: any, oldVal: any) => {if (newVal !== oldVal) {emit('change', newVal, oldVal)ipArr.value = ['', '', '', lastValue.value]const tempArr = newVal.split('.')for (let i = 0; i < tempArr.length; i++) {if (!isNumberValid(tempArr[i])) {break}ipArr.value[i] = tempArr[i]}}},{ deep: true, immediate: true }
)defineExpose({ handleBlur, showTip })
</script>
<style lang="scss" scoped>
.ip-input {position: relative;display: inline-flex;align-items: center;justify-content: center;flex-direction: row;.ip-tip {position: absolute;top: 100%;left: 0;color: #f56c6c;font-size: 12px;padding-top: 2px;line-height: 1;animation: topshow 0.1s ease-in-out;@keyframes topshow {from {top: 80%;}to {top: 100%;}}}
}.ip-input__item-wrap {display: flex;align-items: center;justify-content: center;
}.ip-input__item {height: 30px;line-height: 30px;width: 50px;color: #606266;border: none;box-shadow: 0 0 0 1px #dcdfe6 inset;border-radius: 4px;text-align: center;font-size: 13px;outline: none;
}.ip-input__item--active {border-color: #409eff;
}.ip-input__dot {margin: 0 3px;font-size: 20px;color: #606266;
}
input:disabled {background-color: #ddd;
}
</style>
2、组件使用
<template>
<div><el-form ref="ruleFormRef" :model="formData" :rules="rules" label-width="180px"><el-form-item label="IP地址" prop="ip"><IpInput ref="refIpInput" :value="formData.ip" @input="ipChange" :isValidate="true" /></el-form-item><el-form-item label=""><el-button type="primary" @click="submitForm">保 存</el-button></el-form-item></el-form>
</div>
</template>
<script setup lang='ts'>
import IpInput from '@/components/IpInput/index.vue'
const formData = ref<any>({ip: ''
})const rules = reactive({ip: [{ required: true, message: '', trigger: 'blur' }]
})// ip输入框
const refIpInput = ref<any>(null)
const ipChange = (val: string) => {formData.value.ip = val
}// 保存
const ruleFormRef = ref<any>()
const submitForm = () => {ruleFormRef.value!.validate(async (valid: boolean) => {// 校验IP输入refIpInput.value.handleBlur()if(refIpInput.value.showTip) returnif (valid) {// const res = await dataApi()}})
}
</script>
<style lang='scss' scoped></style>
3、实现Mac地址输入的修改
- 将组件中所有的 ['', '', '', lastValue.value] 数组添加两个空值,即改成如下数组
['', '', '', '', '', lastValue.value]
- 将组件中的 handleBlur 方法修改成
const handleBlur = (index: any) => {activeIndex.value = -1if(props.isValidate && (ipArr.value[0]==='' || ipArr.value[1]==='' || ipArr.value[2]==='' || ipArr.value[3]==='' || ipArr.value[4]==='' || ipArr.value[5]==='')) {showTip.value = true} else {showTip.value = false}
}
- 将组件中的 isNumberValid 方法修改成
const isNumberValid = (value: any) => {return /^[0-9A-Fa-f]*$/.test(value) && value.length <= 2
}