一、效果
废话不多说,上效果图:
- 在下方的:
- 在上方的:
二、源码
一般是个输入框,输入关键词,下拉一个搜索列表。
ElementUI
有提供<el-autocomplete>
,但uniapp官网没提供这么细,特简单扩展了一下:
1、/ components / input-search.vue
<template>
<view class="uni-stat__select" :class="'uni-stat__select_'+algin">
<view class="uni-stat-box" :class="{'uni-stat__actived': current}">
<view class="uni-select__input-box" @click="toggleSelector">
<slot ref="slot"></slot>
</view>
<view class="uni-select--mask" v-if="showSelector" @click="toggleSelector" />
<view class="uni-select__selector" v-if="showSelector && current">
<view class="uni-popper__arrow"></view>
<scroll-view scroll-y="true" class="uni-select__selector-scroll">
<view class="uni-select__selector-empty" v-if="loadState==0">
<text class="cbbb">加载中...</text>
</view>
<view class="uni-select__selector-empty" v-else-if="loadState== 1">
<text class="cbbb">请求失败,请稍后重试!</text>
</view>
<view class="uni-select__selector-empty" v-else-if="loadState== 3">
<text class="cbbb">请输入关键词联想~</text>
</view>
<view class="uni-select__selector-empty" v-else-if="loadState== 4">
<text class="cbbb">没有相关数据!</text>
</view>
<view class="uni-select__selector-empty" v-else-if="!list || list.length === 0">
<text>{{emptyTips}}</text>
</view>
<view v-else class="uni-select__selector-item" v-for="(item,index) in list" :key="index" @click="change(item)">
<text :class="{'uni-select__selector__disabled': item.disable}">{{formatItemName(item)}}</text>
</view>
</scroll-view>
</view>
</view>
</view>
</template>
<script>
export default {
name: "input-search",
props: {
type: {
type: [String, Number],
require: true,
},
algin: {
type: String,
default: 'bottom',
},
value: {
type: [String, Number],
default: ''
},
modelValue: {
type: [String, Number],
default: ''
},
emptyTips: {
type: String,
default: '无选项'
},
// 格式化输出 用法 field="_id as value, version as text, uni_platform as label" format="{label} - {text}"
format: {
type: String,
default: ''
},
},
watch: {
value(newVal) {
this.current = newVal;
},
modelValue(newVal) {
this.current = newVal;
},
current() {
this.search();
},
},
data() {
return {
showSelector: false,
current: '',
loadState: -1, //-1初始化0加载中1请求失败2成功3请输入4没有数据
list: [],
searchSyncString: '',
};
},
created() {
this.$nextTick(this.init);
},
methods: {
isDisabled(value) {
let isDisabled = false;
this.mixinDatacomResData.forEach(item => {
if (item.value === value) {
isDisabled = item.disable
}
})
return isDisabled;
},
change(item) {
if (item.disable) return;
this.showSelector = false;
this.current = this.formatItemName(item);
this.confirm(item);
// console.warn(item)
},
toggleSelector() {
if (this.disabled) return;
this.showSelector = !this.showSelector;
if (this.showSelector) this.search();
},
formatItemName(item) {
let {
text,
value,
channel_code
} = item
channel_code = channel_code ? `(${channel_code})` : ''
if (this.format) {
// 格式化输出
let str = "";
str = this.format;
for (let key in item) {
str = str.replace(new RegExp(`{${key}}`, "g"), item[key]);
}
return str;
}
return `${text}`;
},
init() {
this.current = this.value || this.modelValue || '';
if (this.current) this.search();
this.computeButtom();
},
getDefaultSlotElem() {
let temp = this.$scopedSlots.default;
if (temp && typeof(temp) == 'function') {
temp = temp();
temp = temp && temp[0] ? temp[0].elm : null;
}
return temp || null;
},
computeButtom() {
this.soltHeight = (this.getDefaultSlotElem() || {}).clientHeight || 40;
},
search() {
let searchSyncString = this.searchSyncString = Math.random();
setTimeout(() => {
if (searchSyncString != this.searchSyncString) return;
this.searchSync();
}, 500);
},
searchSync() {
console.log('searchSync::::::', this.loadState, this.current);
if (this.loadState == 0 || !this.current) return;
this.loadState = 0;
// if (!this.current) {
// this.loadState = 3;
// return;
// }
if (this.copySearchText == this.current && this.list && this.list.length > 0) {
this.loadState = 2;
return;
}
let params = {};
params.categoryCode = this.type;
params.searchText = this.copySearchText = this.current || '';
this.$request.post('texts/list', params).then((j) => {
this.loadState = 2;
if (!j.code || j.code != 1) {
this.loadState = 1;
return this.$errToast(j, '联想词条');
}
let list = j.data || [];
if (!list || list.length < 1) {
this.loadState = 4;
return;
}
list.forEach((item, index) => {
item.value = item.id;
item.text = item.name || '-';
});
this.list = list;
}).catch((res) => {
this.loadState = 1;
return this.$errToast(j, '联想词条');
});
this.computeButtom();
},
confirm(item) {
this.$emit('confirm', item.text || item.name|| '');
this.loadState = 0;
setTimeout(() => {
this.loadState = 2;
this.computeButtom();
}, 800);
},
},
}
</script>
<style lang="scss" scoped>
$uni-border-3: #e5e5e5;
.uni-stat__select {
position: relative;
}
.uni-stat-box {
width: 100%;
flex: 1;
}
.uni-stat__actived {
width: 100%;
flex: 1;
// outline: 1px solid #2979ff;
}
.uni-select__selector {
/* #ifndef APP-NVUE */
box-sizing: border-box;
/* #endif */
position: absolute;
top: calc(100% + 12px);
left: 0;
width: 100%;
background-color: #FFFFFF;
border: 1px solid #EBEEF5;
border-radius: 6px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
z-index: 3;
padding: 4px 0;
}
.uni-stat__select_top .uni-select__selector {
bottom: calc(100% + 12px);
top: auto;
.uni-popper__arrow {
bottom: -6px;
top: auto;
transform: rotate(180deg);
}
}
.uni-select__selector-scroll {
/* #ifndef APP-NVUE */
max-height: 200px;
box-sizing: border-box;
/* #endif */
}
/* #ifdef H5 */
@media (min-width: 768px) {
.uni-select__selector-scroll {
max-height: 600px;
}
}
/* #endif */
.uni-select__selector-empty,
.uni-select__selector-item {
/* #ifndef APP-NVUE */
display: flex;
cursor: pointer;
/* #endif */
line-height: 1.3;
font-size: 14px;
text-align: left;
/* border-bottom: solid 1px $uni-border-3; */
padding: 2px 10px;
margin: 6px 0;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
}
.uni-select__selector-item:hover {
background-color: #f9f9f9;
}
.uni-select__selector-empty:last-child,
.uni-select__selector-item:last-child {
/* #ifndef APP-NVUE */
border-bottom: none;
/* #endif */
}
.uni-select__selector__disabled {
opacity: 0.4;
cursor: default;
}
/* picker 弹出层通用的指示小三角 */
.uni-popper__arrow,
.uni-popper__arrow::after {
position: absolute;
display: block;
width: 0;
height: 0;
border-color: transparent;
border-style: solid;
border-width: 6px;
}
.uni-popper__arrow {
filter: drop-shadow(0 2px 12px rgba(0, 0, 0, 0.03));
top: -6px;
left: 10%;
margin-right: 3px;
border-top-width: 0;
border-bottom-color: #EBEEF5;
}
.uni-popper__arrow::after {
content: " ";
top: 1px;
margin-left: -6px;
border-top-width: 0;
border-bottom-color: #fff;
}
.uni-select--mask {
position: fixed;
top: 0;
bottom: 0;
right: 0;
left: 0;
z-index: 2;
}
.cbbb {
color: #bbb !important;
}
</style>
2、/ pages / xxx / demo.vue
<template>
<input-search :type="1" :value="handleResult" algin="top" @confirm="confirmInputSearch($event, 'handleResult')">
<input class="uni-input" type="text" placeholder="请填写结果" v-model="handleResult" />
</input-search>
<!-- ...... -->
</template>
import inputSearch from "@/components/input-search.vue";
export default {
components: {
inputSearch,
},
data() {
handleResult: "",
},
methods: {
confirmInputSearch(val, key) {
this.$set(this, key, val);
},
},
//......
},
三、参数说明:
名称 | 类型 | 说明 |
---|---|---|
type | int | 词条类型,如果同个页面有多个联想, 且联想内容不一致时,用此字段与接口对接 |
value | String | 词条内容 |
algin | String | null | top,弹出框的方向,默认bottom |
emptyTips | String | 当词条内容为空时,显示的文本内容(未纳入) |
@confirm | Method | 选中事件,点击了联想内容的一个。返回联想内容text |
四、续
-
功能优势:
- 官方样式,好看啦
- 可扩展
- 支持input、textarea等控件
-
扩展
- 输出格式 format
- 禁用 item内容
- 未完待续…
[The end]