uni-app 单选、多选 搜索框组件
<template>
<view style="width: 100%">
<view
class="search-input-content"
:style="{
width: inputWidth ? inputWidth : '100%',
}"
>
<view
class="c-search-input single-value"
@click="showSearch(innerValue)"
v-if="mode === 'single-value'"
:style="inputStyle"
>
<view class="search-icon">
<u-icon name="search" :size="32" color="#c0c4cc">u-icon>
view>
<span
v-if="innerValue === '' || innerValue == null"
class="placeholder"
>
{{ placeholder }}
span>
<view
class="innerValue"
v-if="!(innerValue === '' || innerValue == null)"
>
{{ innerValue }}
view>
view>
<view
class="c-search-input-m mutil-value"
v-if="mode === 'mutil-value'"
@click="showSearch"
:style="inputStyle"
>
<span v-if="innerValueList.length === 0" class="placeholder">{{
placeholder
}}span>
<block v-for="(item, index) of innerValueList" :key="index">
<u-tag
:text="item"
shape="square"
:closeable="!disabled"
type="warning"
@close="close(index)"
class="mutil-tag"
/>
block>
view>
<view
class="close"
@click="clear"
v-show="
clearable &&
(innerValue || innerValueList.length > 0) &&
!disabled
"
>
<u-icon
name="close-circle-fill"
size="32"
color="#c0c4cc"
>u-icon>
view>
view>
<u-popup v-model="show" mode="top">
<view class="search-popup">
<view class="search-popup-input">
<u-icon
class="u-clear-icon"
:size="32"
name="search"
>u-icon>
<input
class="inner-search-input"
v-model="search[searchParam]"
placeholder="Enter search keywords"
:focus="focusState"
@input="getListByKeyword_debounce"
@blur="focusState = false"
/>
view>
<picker-view
@change="bindChange"
class="picker-view"
:style="{
height: `${pickerViewHeight}rpx`,
}"
:indicator-style="indicatorStyle"
>
<picker-view-column>
<view
class="item"
v-for="(item, index) in list"
:key="index"
>{{ renderLabel(item) }}view
>
picker-view-column>
picker-view>
<view class="search-action">
<span @click="show = false">{{
mode === 'mutil-value' ? 'Back' : 'Cancel'
}}span>
<span
v-if="mode === 'single-value'"
class="action"
@click="ok"
>OKspan
>
<span v-if="mode === 'mutil-value'" class="action">
<span @click="add">Addspan>
span>
view>
<view
class="c-search-input-m mutil-value"
v-if="mode === 'mutil-value'"
><span v-if="innerValueList.length === 0">
{{ placeholder }}
span>
<block v-for="(item, index) of innerValueList" :key="index">
<u-tag
:text="item"
shape="square"
:closeable="!disabled"
type="warning"
@close="close(index)"
class="mutil-tag"
/>
block>
view>
view>
u-popup>
<u-toast ref="uToast" />
view>
template>
<script lang="ts">
import { defineComponent, reactive, PropType } from '@vue/composition-api'
import { SearchCondition } from '@/common/model'
export default defineComponent({
props: {
value: {
default: '',
required: true,
},
initInnerValue: {
type: String,
default: '',
},
label: {
type: String,
default: 'label',
},
labelArr: {
type: Array,
default: () => [],
},
valueName: {
type: String,
default: 'value',
},
mode: {
type: String,
default: 'single-value',
},
placeholder: {
type: String,
default: 'click the input box to enter',
},
api: {
type: Function as PropType<
(searchCondition: SearchCondition) => Promise<any>
>,
default: () => {},
},
url: {
type: String,
},
searchList: {
type: Array,
default: () => [],
},
clearable: {
type: Boolean,
default: true,
},
inputWidth: {
type: String,
},
border: {
type: Boolean,
default: true,
},
radius: {
type: String,
default: '10',
},
height: {
type: String,
default: '70',
},
pickerViewHeight: {
type: String,
default: '250',
},
disabled: {
type: Boolean,
default: false,
},
searchParam: {
type: String,
default: 'keyword',
},
additionalConditions: {
type: Object,
default: () => {},
},
},
created() {
this.DB = this._.debounce(this.getListByKeyword, 300, true)
this.DB()
},
mounted() {
this.$nextTick(() => {
if (this.initInnerValue !== '') {
this.changeInnerValue()
}
})
},
watch: {
initInnerValue() {
this.changeInnerValue()
},
},
computed: {
inputStyle: function(): object {
return {
'border-radius': `${this.radius}rpx`,
'line-height': `${this.height}rpx`,
'min-height': `${this.height}rpx`,
'box-shadow':
this.border && !this.disabled
? `0 0 0 2rpx #e8e8e8`
: `none`,
}
},
},
methods: {
renderLabel(item: any) {
if (this.labelArr.length > 0) {
let str = ''
this.labelArr.forEach((el: any, i: number) => {
str = str + item[el]
if (i < this.labelArr.length - 1) {
str = str + ' | '
}
})
return str
} else {
return item[this.label]
}
},
changeInnerValue() {
this.innerValue = this.initInnerValue
},
bindChange: function(e: any) {
this.searchIndex = e.detail.value[0]
let count = this.search.pageIndex * this.search.pageSize
if (
this.searchIndex === count - 1 &&
this.searchIndex < this.search.total - 1
) {
this.search.pageIndex++
this.DB(true)
}
},
getListByKeyword_debounce() {
this.DB()
},
showSearch(innerValue?: string) {
if (!this.disabled) {
this.show = true
} else {
if (innerValue) {
;(this.$refs.uToast as any).show({
title: innerValue,
icon: false,
})
}
}
},
getListByKeyword(isScroll?: boolean) {
if (this.searchList.length > 0) {
let filter = this.searchList.filter((f) => {
return (
((f as any)[this.label] as String)
.toLowerCase()
.replace(/\s/g, '')
.match(
(this.search as any)[
this.searchParam
].toLowerCase()
) !== null
)
})
this.list.length = 0
this.list.push(...filter)
} else {
if (!isScroll) {
this.search.pageIndex = 1
}
this.search = Object.assign(
this.search,
this.additionalConditions
) as any
if (this.url) {
this.$request
.get(this.url, this.search)
.then((data: any) => {
if (data.Result == 1 && data.Data) {
if (!isScroll) {
this.list.length = 0
}
if (
JSON.parse(
JSON.stringify(data.Data)
) instanceof Array
) {
this.list.push(...data.Data)
this.search.total = data.Data.Total
} else if (
JSON.parse(
JSON.stringify(data.Data.DataList)
) instanceof Array
) {
this.list.push(...data.Data.DataList)
this.search.total = data.Data.Total
}
}
})
} else {
;(this.api as Function)(this.search).then((data: any) => {
if (data.Result == 1 && data.Data) {
if (!isScroll) {
this.list.length = 0
}
if (
JSON.parse(JSON.stringify(data.Data)) instanceof
Array
) {
this.list.push(...data.Data)
this.search.total = data.Data.Total
} else if (
JSON.parse(
JSON.stringify(data.Data.DataList)
) instanceof Array
) {
this.list.push(...data.Data.DataList)
this.search.total = data.Data.Total
}
}
})
}
}
},
ok() {
let list = this.list
if (list.length > 0) {
let item = list[this.searchIndex]
this.actionItem = item
this.innerValue = item[this.label]
;(this.search as any)[this.searchParam] = ''
this.$emit('input', item[this.valueName])
this.$emit('ok', item)
this.show = false
}
},
add() {
let list = this.list
if (list.length > 0) {
let item = list[this.searchIndex]
if (!this.tempValueList.includes(item[this.valueName])) {
this.innerValueList.push(item[this.label])
this.tempValueList.push(item[this.valueName])
this.$emit('input', this.tempValueList)
}
}
this.$nextTick(() => {
this.focusState = true
})
},
close(index: number) {
this.innerValueList.splice(index, 1)
this.tempValueList.splice(index, 1)
this.$emit('input', this.tempValueList)
},
clear() {
if (this.mode === 'single-value') {
this.innerValue = ''
this.$emit('input', '')
}
if (this.mode === 'mutil-value') {
this.innerValueList = []
this.tempValueList = []
this.$emit('input', this.tempValueList)
}
this.$emit('clear')
},
},
setup() {
let data = reactive({
show: false,
focusState: true,
list: [] as any[],
total: 0,
actionItem: {},
search: new SearchCondition(),
innerValue: '',
innerValueList: [] as any[],
searchIndex: 0,
tempValueList: [] as any[],
indicatorStyle: `height: 70rpx`,
focus: false,
DB: (isScroll?: boolean) => {},
})
return data
},
})
script>
<style lang="scss" scoped>
.search-input-content {
width: 100%;
display: flex;
position: relative;
.c-search-input {
display: flex;
width: 0;
flex: 1;
min-height: 70rpx;
border-radius: 8rpx;
line-height: 70rpx;
padding-left: 20rpx;
> span {
color: #ccc;
}
.innerValue {
margin-left: 5rpx;
width: 84%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
.c-search-input-m {
width: 100%;
min-height: 70rpx;
border-radius: 8rpx;
line-height: 70rpx;
padding-left: 10rpx;
}
.mutil-value {
height: auto;
}
.search-icon {
margin-right: 6rpx;
}
.close {
line-height: 28rpx;
height: 28rpx;
position: absolute;
margin-top: -14rpx;
top: 50%;
right: 15rpx;
}
}
.mutil-tag {
margin-right: 10rpx;
}
.search-popup {
padding: 20rpx 35rpx 10rpx 35rpx;
.search-action {
padding-bottom: 10rpx;
display: flex;
justify-content: space-between;
.action {
color: $laxton-type-active;
}
}
.search-popup-input {
display: flex;
justify-content: space-between;
margin-top: 20rpx;
> input {
flex: 1;
height: 70rpx;
border-radius: 30rpx;
padding-left: 15rpx;
margin-left: 30rpx;
line-height: 70rpx;
background-color: #e2e2e2;
}
}
.picker-view {
margin-top: 20rpx;
}
.item {
align-items: center;
justify-content: center;
text-align: center;
line-height: 70rpx;
}
}
style>