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.Clipboard
clipboardText.value = clipboardData.getData('text')
handlePaste(activeIndex.value)
}
const copyListener = (event: any) => {
if (activeIndex.value === -1) { return }
const clipboardData = event.clipboardData || window.Clipboard
clipboardData.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] = newValue
if (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 = -1
if(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.value
const tempArr = clipboardText1.split('.')
let i
for (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) return
if (valid) {
// const res = await dataApi()
}
})
}
</script>
<style lang='scss' scoped>
</style>
3、实现Mac地址输入的修改
- 将组件中所有的 ['', '', '', lastValue.value] 数组添加两个空值,即改成如下数组
['', '', '', '', '', lastValue.value]
- 将组件中的 handleBlur 方法修改成
const handleBlur = (index: any) => {
activeIndex.value = -1
if(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
}