需求
- 输入键值对
key="value"
- 可搜索
- 回车生成标签
效果图
代码实现
edit-search.vue
<style scoped lang="scss">
.edit-search {
position: relative;
&__container {
position: relative;
display: inline-flex;
flex-wrap: wrap;
align-items: flex-start;
vertical-align: middle;
width: 100%;
height: auto;
padding: 0 20px 0 10px;
outline: none;
background: $white;
user-select: none;
border: $border-base;
border-radius: 4px;
overflow: hidden;
transition: border ease .2s, box-shadow ease .2s;
&.error {
border: 1px solid #f00;
}
.edit-search__tags {
flex-shrink: 0;
font-size: 12px;
line-height: 12px;
margin-right: 4px;
margin-top: 4px;
border-radius: 3px;
border: $border-base;
background: $bg-base;
padding: 4px 6px;
display: inline-block;
&--close {
font-size: 12px;
margin-left: 5px;
color: $text-placeholder;
&:hover {
color: $text-regular;
}
}
}
.edit-search__input {
height: 30px;
width: 0;
min-width: 30px;
flex: 1;
color: $text-regular;
cursor: default;
&::placeholder {
color: $text-placeholder;
}
&::-moz-placeholder {
color: $text-placeholder;
opacity: 1;
}
}
}
.change-label__popper {
position: absolute;
background: $white;
box-shadow: 0 2px 12px 0 #0000000F;
border: 1px solid #DCE2E6;
border-radius: 4px;
font-size: 14px;
margin-top: 5px;
left: 0;
top: 27px;
z-index: 10;
cursor: auto;
li {
color: $text-secondary;
line-height: 32px;
}
}
.slid-enter-active,
.slid-leave-active {
opacity: 1;
transform: scaleY(1);
transition: transform 300ms cubic-bezier(0.23, 1, 0.32, 1), opacity 300ms cubic-bezier(0.23, 1, 0.32, 1);
transform-origin: center top;
}
.slid-enter,
.slid-leave-active {
opacity: 0;
transform: scaleY(0);
}
}
</style>
<template>
<div class="edit-search">
<div :class="['edit-search__container', {'error': errorEdit} ]">
<div ref="tagRef">
<div
v-for="item in selectedTags"
:key="item"
class="edit-search__tags"
>
<span>{{ item }}</span>
<i
class="yi yi-fail-circle-fill edit-search__tags--close"
@click.stop="handleTageRemove(item)"
/>
</div>
</div>
<input
ref="input"
v-model="inputValue"
type="text"
placeholder="请输入"
class="edit-search__input"
@keydown.enter="handleEnter"
@blur="handleBlur"
@focus="focusInput"
>
</div>
<transition
v-if="type === 'value'"
name="slid"
>
<ul
v-if="showPopper"
v-clickoutside="handleClose"
class="change-label__popper"
:style="'left: '+ leftSize +'px'"
>
<yv-scrollbox style="max-height: 300px; padding: 0 8px">
<li
v-for="(item, index) in keyOptions"
:key="index"
@click="selectOption(item)"
>
{{ item }}
</li>
</yv-scrollbox>
</ul>>
</transition>
</div>
</template>
<script>
const clickoutside = {
bind (el, binding, vnode) {
function documentHandler (e) {
if (el.contains(e.target) || e.target.className === 'edit-search__input') return false
if (binding.expression) binding.value(e)
}
el._vueClickOut = documentHandler
document.addEventListener('click', documentHandler)
},
update () {},
unbind (el, binding) {
document.removeEventListener('click', el._vueClickOut)
delete el._vueClickOut
}
}
export default {
directives: { clickoutside },
props: {
value: {
type: Array,
default: () => []
},
type: {
type: String,
default: 'key'
},
options: {
type: Array,
default: () => []
}
},
data () {
return {
inputValue: '',
keyOptions: this.options,
selectedOptions: [],
selectedTags: [],
errorEdit: false,
showPopper: false,
leftSize: 0
}
},
watch: {
options: {
deep: true,
handler: function (n) {
this.keyOptions = n
this.selectedOptions = this.selectedOptions.filter(item => n.indexOf(Object.keys(item)[0]) > -1)
this.$nextTick(() => {
this.leftSize = this.$refs.tagRef.offsetWidth + 5
})
}
},
selectedOptions: {
deep: true,
handler: function (n) {
const selectedKey = []
const selectTag = []
if (this.type === 'key') {
selectedKey.push(...n)
selectTag.push(...n)
} else {
n.forEach(item => {
selectedKey.push(Object.keys(item)[0])
selectTag.push(Object.keys(item)[0] + '=' + Object.values(item)[0])
})
}
this.selectedTags = selectTag
this.keyOptions = this.options.filter(item => selectedKey.indexOf(item) === -1)
this.$emit('input', n)
this.$emit('change', n)
this.$nextTick(() => {
this.leftSize = this.$refs.tagRef.offsetWidth + 5
})
}
},
value: {
deep: true,
handler: function (n) {
this.handlerVal(n)
},
immediate: true
}
},
methods: {
handlerVal (value) {
if (this.type === 'key') {
if (Array.isArray(value)) {
this.selectedOptions.push(...value)
} else {
this.selectedOptions.push(value)
}
} else {
this.selectedOptions = this.value
}
},
handleClose () {
this.showPopper = false
},
handleTageRemove (item) {
if (this.type === 'key') {
this.selectedOptions = this.selectedOptions.filter(em => em !== item)
} else {
this.selectedOptions = this.selectedOptions.filter(em => Object.keys(em)[0] !== item.split('=')[0])
}
this.showPopper = false
},
focusInput () {
this.showPopper = true
},
selectOption (item) {
this.inputValue = item + '='
this.showPopper = false
},
handleEnter () {
if (!this.inputValue) return
if (this.type === 'key') {
if (/^[a-zA-Z0-9\u4e00-\u9fa5_-]+$/.test(this.inputValue) && this.selectedOptions.indexOf(this.inputValue) === -1) {
this.selectedOptions.push(this.inputValue)
this.inputValue = ''
this.errorEdit = false
} else {
this.errorEdit = true
}
} else {
if (/^[a-zA-Z0-9_-]+=[a-zA-Z0-9\u4e00-\u9fa5_-]+$/.test(this.inputValue) && this.selectedOptions.indexOf(this.inputValue) === -1) {
const inputVal = this.inputValue.split('=')
const obj = {}
obj[inputVal[0]] = inputVal[1]
this.selectedOptions.push(obj)
this.inputValue = ''
this.errorEdit = false
this.showPopper = false
this.$refs.input.blur()
} else {
this.errorEdit = true
}
}
},
handleBlur () {
this.errorEdit = false
this.inputValue = ''
}
}
}
</script>
test.vue
<style lang="scss" scoped>
.search-class {
width: 1000px;
}
</style>
<template>
<div class="page">
<h3>键</h3>
<edit-search
ref="edit1"
class="search-class"
type="key"
:value="edit1Val"
/>
<yv-button @click="btnSelect">
查询
</yv-button>
<h3 style="margin-top: 50px">
键值对
</h3>
<edit-search
ref="edit2"
class="search-class"
style="margin-bottom: 50px"
type="value"
:value="edit2Val"
:options="selectOptions"
/>
<yv-button @click="btnSelect">
查询
</yv-button>
<yv-button @click="edit2Val=[]">
刪除
</yv-button>
<yv-button @click="btnAdd">
加值
</yv-button>
<yv-button
size="small"
@click="changeOptions"
>
改變options
</yv-button>
<yv-button
size="small"
@click="changeSelect"
>
改變value
</yv-button>
</div>
</template>
<script>
import EditSearch from '../components/edit-search'
export default {
name: 'Index',
components: {
EditSearch
},
data () {
return {
selectOptions: ['value1', 'value2', 'selectValue'],
edit1Val: 'value1',
// edit1Val: ['value11', 'value22'],
edit2Val: [{ value1: '111' }, { value2: '222' }]
}
},
methods: {
btnSelect () {
console.log(this.$refs.edit1.getValue())
console.log(this.$refs.edit2.getValue())
},
btnAdd () {
const arr = [{ value1: 'eee' }, { value3: 'eesss' }]
this.edit2Val.push(...arr)
},
changeOptions () {
this.selectOptions = ['value1', 'aaaaaaaaaa', 'ddddddddddd', 'bbbbbbbbb', 'eeeeeeeeeee', 'fffffffffff', 'bbbbbbbbrrrr']
},
changeSelect () {
this.edit2Val.forEach(item => {
item.value1 = 333
})
},
changeVal (val) {
this.edit2Val = val
},
}
}
</script>