基于uni-app的通用搜索组件(历史记录,app语音输入,搜索推荐)解析 zy-search
一个通用的搜索组件,包含搜索历史记录,语音输入,搜索推荐功能
插件地址:https://ext.dcloud.net.cn/plugin?id=512
插件内容:
<template name="zy-search">
<view>
<view class="search">
<!-- 运行在app端的代码 -->
<!-- #ifdef APP-PLUS -->
<image src="../../static/zy-search/voice.svg" mode="aspectFit" @click="startRecognize()" class="voice-icon"></image>
<!-- #endif -->
<template v-if="isFocus">
<input maxlength="20" focus type="text" value="" confirm-type="search" @confirm="searchStart()" placeholder="请输入关键词搜索" v-model.trim="searchText"/>
</template>
<template v-else>
<input maxlength="20" type="text" value="" confirm-type="search" @confirm="searchStart()" placeholder="请输入关键词搜索" v-model.trim="searchText"/>
</template>
<image src="../../static/zy-search/search.svg" mode="aspectFit" @click="searchStart()" class="search-icon"></image>
</view>
<view :class="'s-' + theme" v-if="hList.length > 0">
<view class="header">
历史记录
<image src="../../static/zy-search/delete.svg" mode="aspectFit" @click="delhistory"></image>
</view>
<view class="list">
<view v-for="(item,index) in hList" :key="index" @click="keywordsClick(item)">{{item}}</view>
</view>
</view>
<view :class="'wanted-' + theme" v-if="showWant">
<view class="header">猜你想搜的</view>
<view class="list">
<view v-for="(item,index) in hotList" :key="index" @click="keywordsClick(item)">{{item}}</view>
</view>
</view>
</view>
</template>
<script>
export default{
name:"zy-search",
props:{
isFocus:{ //是否自动获取焦点
type: Boolean,
default: false
},
theme:{ //选择块级显示还是圆形显示
type: String,
default: 'block'
},
showWant:{ //是否展示推荐菜单
type: Boolean,
default: false
},
hotList: { //推荐列表数据
type: Array,
default () {
return []
}
},
speechEngine: { //语音引擎=>讯飞:iFly,百度:'baidu'
type: String,
default: 'baidu'
}
},
data() {
return {
searchText:'', //搜索关键词
hList:uni.getStorageSync('search_cache') //历史记录
};
},
methods: {
//触发搜索 并加入历史记录
searchStart: function() {
let _this = this;
// 判断输入值为空的情况
if (_this.searchText == '') {
uni.showToast({
title: '请输入关键字',
icon: 'none',
duration: 1000
});
}else{
// 通知父组件进行搜索
_this.$emit('getSearchText', _this.searchText);
// 从本地缓存中异步获取指定 key 对应的内容。
uni.getStorage({
key:'search_cache',
success(res){
let list = res.data;
if(list.length > 5){
// 如果数组的长度大于5
// 循环该数组
for(let item of list){
// 如果数组中有一项和当前输入框中输入的值相等的话,终止操作流程
if(item == _this.searchText){
return;
}
}
// 删除数组最后一项
list.pop();
// 在数组最前面增加一项
list.unshift(_this.searchText);
}else{
// 如果数组的长度小于5
// 循环该数组
for(let item of list){
// 如果数组中有一项和当前输入框中输入的值相等的话,终止操作流程
if(item == _this.searchText){
return;
}
}
// 在数组最前面增加一项
list.unshift(_this.searchText);
}
// 将当前新数组赋值给列表上显示
_this.hList = list;
// 向本地缓存中异步存储指定 key 对应的内容。
uni.setStorage({
key: 'search_cache',
data: _this.hList
});
},
fail() {
// 如果 从本地缓存中异步获取指定 key 对应的内容 失败
// 清空列表上显示的数组
_this.hList = [];
// 将当前输入的值给列表上显示的数组
_this.hList.push(_this.searchText);
// 向本地缓存中异步存储指定 key 对应的内容。
uni.setStorage({
key: 'search_cache',
data: _this.hList
});
// 通知父组件进行搜索 (这里的搜索多余了)
_this.$emit('getSearchText', _this.searchText);
}
})
}
},
// 关键词点击的事件 关键词搜索与历史搜索
keywordsClick (item) {
// 将点击的关键词内容赋值给搜索的变量
this.searchText = item;
//触发搜索事件
this.searchStart();
},
//清空历史记录
delhistory () {
this.hList = [];
uni.setStorage({
key: 'search_cache',
data: []
});
},
//语音输入
startRecognize: function() {
let _this = this;
let options = {};
// /语音引擎=>讯飞:iFly,百度:'baidu' 父级传入的 默认baidu
options.engine = _this.speechEngine;
// 是否需要标点符号
options.punctuation = false;
// 语音识别超时时间
options.timeout = 10 * 1000;
// 启动语音识别
// 启动语音识别时调用,当语音识别成功后通过successCallback回调返回识别出文本内容,调用语音识别失败则通过errorCallback回调返回。
// plus.speech.startRecognize( options, successCB, errorCB );
// options: ( SpeechRecognizeOption ) 必选 语音识别参数,用于控制语音引擎的各种技术参数
// successCB: ( RecognitionSuccessCallback ) 可选 语音识别成功回调
// 当语音识别引擎识别数据成功时的回调函数,并返回识别出的文本内容。
// errorCB: ( RecognitionErrorCallback ) 可选 语音识别失败时的回调函数
// 当语音识别引擎识别数据失败时的回调函数,并返回失败的错误信息。
plus.speech.startRecognize(options, function(s) {
// 将语言识别到的内容拼接上输入框中的内容赋值给输入框
_this.searchText = _this.searchText + s;
});
}
}
}
</script>
<style lang="less" scoped>
.search{
640upx;
margin: 30upx auto 0;
position: relative;
input{
background-color: #F7F7F7;
padding: 10upx 74upx;
font-size: 28upx;
border-radius: 50upx;
}
.voice-icon{
36upx;
height: 36upx;
padding: 16upx 20upx 16upx 0;
position: absolute;
left: 16upx;
top: 4upx;
z-index: 10;
}
.search-icon{
36upx;
height: 36upx;
padding: 16upx 20upx 16upx 0;
position: absolute;
right: 0;
top: -2upx;
z-index: 10;
}
}
.s-block{
margin-top: 30upx;
.header{
font-size: 32upx;
padding: 30upx;
position: relative;
image{
36upx;
height: 36upx;
padding: 10upx;
position: absolute;
right: 40upx;
top: 24upx;
}
}
.list{
display: flex;
flex-wrap: wrap;
view{
50%;
color: #8A8A8A;
font-size: 28upx;
box-sizing: border-box;
text-align: center;
padding: 20upx 0;
border-top: 2upx solid #FFF;
border-left: 2upx solid #FFF;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
background-color: #F7F7F7;
}
}
}
.s-circle{
margin-top: 30upx;
.header{
font-size: 32upx;
padding: 30upx;
border-bottom: 2upx solid #F9F9F9;
position: relative;
image{
36upx;
height: 36upx;
padding: 10upx;
position: absolute;
right: 40upx;
top: 24upx;
}
}
.list{
display: flex;
flex-wrap: wrap;
padding: 0 30upx 20upx;
view{
padding: 8upx 30upx;
margin: 20upx 30upx 0 0;
font-size: 28upx;
color: #8A8A8A;
background-color: #F7F7F7;
box-sizing: border-box;
text-align: center;
border-radius: 20upx;
}
}
}
.wanted-block{
margin-top: 30upx;
.header{
font-size: 32upx;
padding: 30upx;
}
.list{
display: flex;
flex-wrap: wrap;
view{
50%;
color: #8A8A8A;
font-size: 28upx;
box-sizing: border-box;
text-align: center;
padding: 20upx 0;
border-top: 2upx solid #FFF;
border-left: 2upx solid #FFF;
background-color: #F7F7F7;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
}
}
.wanted-circle{
margin-top: 30upx;
.header{
font-size: 32upx;
padding: 30upx;
}
.list{
display: flex;
flex-wrap: wrap;
padding: 0 30upx 20upx;
view{
padding: 8upx 30upx;
margin: 20upx 30upx 0 0;
font-size: 28upx;
color: #8A8A8A;
background-color: #F7F7F7;
box-sizing: border-box;
text-align: center;
border-radius: 20upx;
}
}
}
</style>
父组件使用:
<template>
<view>
<zy-search :is-focus="true" :theme="themeClass" :show-want="true" :hot-list="hotList" @getSearchText="getSearchText"></zy-search>
</view>
</template>
<script>
import zySearch from './zy-search/zy-search.vue';
export default {
components: {
zySearch
},
data() {
return {
themeClass: 'circle',
hotList: [] //初始化推荐列表
};
},
onShow() {
this.getHotSearch();
},
methods: {
getHotSearch() {
this.http('', {}).then(res => {
if (res.success) {
this.hotList = []
res.data.hottest_list.map((item, index) => {
this.hotList.push(item.content);
});
} else {
}
});
},
getSearchText(e) {
uni.navigateTo({
url: '/pagesCourse/index?keyWords=' + e
});
}
}
};
</script>