vue3 实现选择输入框 (带数据选择功能的输入框),这里用的vue3+nuxt(组件直接用)
- 没有数据时显示弹框,可选择数据;
- 如果要用自己输入的数据,输入完毕直接按回车即可(输入时按回车直接绑定输入框中的值给父组件的变量)
MyInput.vue:文章来源:https://www.toymoban.com/news/detail-713186.html
<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>
<style lang="scss" scoped>
.main {
position: relative;
// 输入框样式
.input {
border: 1px solid #c4c4c4;
border-radius: 3px;
padding: 3px 10px;
outline: none;
font-size: 12px;
line-height: 30px;
height: 30px;
box-sizing: border-box;
&:focus {
border-color: rgb(21, 187, 187);
}
}
// popover样式
.popover {
white-space: normal;
margin-top: 10px;
padding: 10px 0;
border: 1px solid #c4c4c4;
border-radius: 5px;
box-shadow: #a0a0a0 0px 0px 6px;
max-height: 200px;
display: flex;
flex-direction: column;
min-height: 30px;
background: #ffff;
overflow: auto;
z-index: 2023;
position: fixed;
font-size: 12px;
margin: 0 auto;
min-width: 100px;
width: fit-content;
.item {
line-height: 20px;
min-height: 20px;
padding: 3px 10px;
word-break: keep-all;
}
.item:hover {
cursor: pointer;
background: rgba(89, 183, 238, 0.4);
}
}
.popover::-webkit-scrollbar {
width: 5px;
height: 10%
}
.popover::-webkit-scrollbar-track {
background-color: #f1f1f1;
border-radius: 10px;
}
/* 滚动条的滑轨背景颜色 */
.popover::-webkit-scrollbar-thumb {
background-color: #b2b4c0;
border-radius: 10px;
}
}
</style>
父组件调用
test.vue文章来源地址https://www.toymoban.com/news/detail-713186.html
<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>
到了这里,关于vue3 实现选择输入框的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!