vue3 实现选择输入框 (带数据选择功能的输入框),这里用的vue3+nuxt(组件直接用)
MyInput.vue:
<template>
<div class="main">
<div v-show="inputing || selecting" class="popover" ref="popover" @mousemove="mouseMove" @mouseleave="mouseLeave" :style="'width:' + width + 'px'">
<div v-for="item in visibleList" @click="handleSelect(item)" class="item" :key="item.value">
{{ item.label }}
div>
div>
<input ref="input" class="input" v-model="tempValue" @focus="handleFocus" @input="handleInput(tempValue)"
@blur="handleBlur" @keydown="handleEnter" :style="'width:' + width + 'px'" />
div>
template>
<script setup>
import { ref, onMounted, onUnmounted, nextTick } from 'vue';
// props
const props = defineProps({
value: { // 绑定的值
type: [String, Number],
required: true
},
list: { // 选择的数据
type: Array,
required: true
},
width: { // 输入框宽度
type: [String, Number],
default: 120,
},
enterCreate: { // 是否启用在输入框回车时创建值
type: Boolean,
default: true
}
})
// emit
const emit = defineEmits(['update:value']) // 双向绑定更新父组件中的变量
const map = {}; // 将list转换成map方便查询
// data
let selecting = ref(false); // 选择状态
let inputing = ref(false); // 编辑状态
let visibleList = ref([]); // 展示的数据
let tempValue = ref(''); // 输入框绑定的值
// ref
let input = ref(null);
let popover = ref(null);
// 初始化操作
(() => {
// list转化为map 加快查询速度
props.list.forEach((item) => {
map[item.value] = item.label;
})
// 初始化输入框展示数据
tempValue.value = map[props.value] ? map[props.value] : props.value;
})()
// 挂载页面
onMounted(() => {
addEventListener('scroll', callback, true)
addEventListener('resize', callback, true)
})
// 卸载页面
onUnmounted(() => {
removeEventListener('scroll', callback)
removeEventListener('resize', callback)
})
// 事件监听回调函数
let callback = () => {
requestAnimationFrame(updatePopoverPosition)
}
// 更新popover位置
let updatePopoverPosition = async () => {
if (!popover.value && !input.value) {
return;
}
// 等待dom渲染完
await nextTick()
// 窗口高度
let windowHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight
// 输入框在屏幕中的绝对高度(到屏幕顶部)
let inputHeight = input.value.getBoundingClientRect().top;
// 输入框屏幕中绝对高度 + 选择弹框本身高度 + 输入框本身高度 不超出屏幕就在输入框下方显示
if (inputHeight + popover.value.clientHeight + input.value.clientHeight < windowHeight) {
popover.value.style.top = inputHeight + input.value.clientHeight + 3 + 'px'; // 输入框下面
popover.value.style.left = input.value.getBoundingClientRect().left + 'px';
}
// 超出了屏幕就在输入框上方显示
else {
popover.value.style.top = inputHeight - 3 - popover.value.clientHeight + 'px';
popover.value.style.left = input.value.getBoundingClientRect().left + 'px';
}
}
// 输入数据时,更新过滤数据
let handleInput = (val) => {
inputing.value = true;
if (!val) {
visibleList.value = props.list; // 没有值直接显示全部
} else {
visibleList.value = props.list.filter(item => {
return item.label.toString().includes(val) // || item.value.toString().includes(val.toUpperCase());
})
}
updatePopoverPosition();
};
// 输入框失去焦点
let handleBlur = () => {
inputing.value = false;
};
// 输入框获取焦点
let handleFocus = () => {
handleInput(tempValue.value)
};
// 选择数据,更新输入框显示值及组件绑定的值
let handleSelect = (item) => {
tempValue.value = item.label ? item.label : item.value;
emit('update:value', item.value)
selecting.value = false;
};
// 鼠标进入下拉框
let mouseMove = () => {
selecting.value = true;
};
// 鼠标移出下拉框
let mouseLeave = () => {
selecting.value = false;
}
// 键盘按下Enter
let handleEnter = (e) => {
if (e.keyCode === 13 && props.enterCreate) { // 回车键 且 启用
emit('update:value', tempValue.value)
selecting.value = false;
inputing.value = false;
input.value.blur(); // 失去焦点
}
}
</script>
父组件调用
test.vue
<template>
<div>
测试值:{{ value }}
<MyInput v-model:value="value" :list="list" :enterCreate="true">MyInput>
div>
template>
<script setup>
import { ref, reactive } from 'vue';
const list = reactive([
{ label: 'AA', value: 11 },
{ label: 'BB', value: 22 },
{ label: 'CC', value: 33 },
{ label: 'DD', value: 44 },
{ label: 'EE', value: 55 },
{ label: 'FF', value: 66 },
{ label: 'HH', value: 77 },
{ label: 'II', value: 88 },
{ label: 'JJ', value: 99 },
]);
const map = {};
let value = ref('11')
</script>