文章目录
- I.场景的引入及前提条件
- II.搜索效果的制作
- III.源码
首先放一下效果图:
I.场景的引入及前提条件
搜索的场景有很多很多,这里以搜索好友并添加作为演示的场景。
✅需求: 实现往搜索框中输入好友的姓名,能够实时在页面上反馈出搜索的结果,并选择是否添加为好友。
✅前提条件:后端相关的搜索api,能接收POST/GET请求并返回json格式的数据。
II.搜索效果的制作
本文面向uniapp初学者或急需要做项目的小白,因而步骤较为详细,如果需要只需源码请直接link到第三部分。
1️⃣搭建界面基础
这部分对于掌握H5的小伙伴来说是小菜一碟了,直接开干即可:搭出一个搜索框和一个搜索按钮即可。
这部分的代码参考如下:
<template>
<view>
<view class="header">
添加好友
</view>
<view class="searchInput">
<input type="text" placeholder="请输入对方的昵称" v-model="friendName" @input="inputChanged"/>
</view>
<view class="searchbtn" @click="search">
搜索 <span class="icon-search"></span>
</view>
<view :class = "currentclass">
<view :class="currentIconClass"><image :class = "currentImgClass" :src="imgSrc" mode="widthFix"></image></view>
<view :class="currentNickClass">
<text>{{searchName}}</text>
</view>
<span @click="add" :class="currentFontIconClass"></span>
</view>
</view>
</template>
注意,在这一步,不用太care我的代码里面的绑定事件或类名,只需要简单了解一下布局的思路即可。(后面会讲解这些绑定事件和类名的使用)
2️⃣向后端发送请求获取数据
基础外观搭好之后,下一步是考虑向后端发起请求获取我们需要的数据。
这里可以再拆解成两个小步骤:
🌀第一步,给之前搭建的按钮添加点击事件,该事件即为搜索事件。
🌀第二步,在搜索事件的函数体内完成请求的发送。
第一个小步骤没有要解释的,直接进行绑定即可(@tap或者@click均可),并给事件定义一个名称,例如search。
第二个小步骤是实现search事件:
首先是在script部分定义search函数:
search(){
}
接下来,首先获取到发起搜索请求的这个用户的用户信息,例如用户名:
search(){
this.userName = uni.getStorageSync("username_log"),
}
这一步的userName可定义在data()中。
分析为什么要进行上面的这一步,因为既然我们的目的是搜索好友,那为什么要获取发起搜索的这个用户的信息呢?
这是因为后续我们要通过这个信息来确定该用户搜索的好友是否已经是他的好友了(之前已经被添加了),从而完善我们的加好友功能。
下一步,拿到了发起搜索的这个用户的用户名之后,我们可以向后端发起请求了:
search(){
this.userName = uni.getStorageSync("username_log"),
uni.request({
url:'https://www.xxxx',
method:'GET',
data:{username : this.userName, friendname : this.friendName},
header: {
'content-type': 'application/x-www-form-urlencoded'
},
上面这段请求基于uniapp发送请求的api,其中:
✅url即为后端给你提供的加好友的接口
✅method可选 “GET/POST”,这个也根据后端来调整。
重点解释一下data,data是发给后端用于处理的数据,其中 :前和 :后分别代表了后端定义的数据名称和前端的数据变量名。
在上面的代码中,username代表了后端定义的一个变量名,表示它希望前端给它这个数据;this.userName表示前端定义的一个变量,它存着要给后端的数据。那么毫无疑问,这个this.userName就是之前说的发起搜索的用户的用户名。
第二个参数friendname同样是后端定义的一个变量名,而this.friendName是前端捕获的用户输入在搜索框中的数据,这个数据表示了用户想要搜索的好友的名称。
最后header部分可以直接复制我的无需修改(只要是互传JSON数据)
3️⃣回调函数
(注意上面的代码并不完整,不要直接复制上面的代码,会报错。需要直接拿到完整源码的请在文章第三部分自取)
这部分紧紧承接上面第二步的最后,当完成了对后端的请求,接下来就是写后端返回的信息对应的回调函数:
因为回调函数仍然在search()函数体内,因此我们仍然回到search()函数中。
对于回调函数的书写,考虑到严谨性,可以分成四种情况:
1.用户不存在
这种情况下,在回调之后,我们将会处理成在用户界面生成“该用户不存在”字样。
success: (res) => {
if(this.friendName == "") return;
this.searchName = res.data.username;
if(this.searchName == null){
this.searchName = "此用户不存在!"
this.imgSrc = ""
}
在代码中,与后端协商后决定,当搜索对象不存在时,返回的username值是一个null,因而这里做一个简单的判断即可。
2.搜索的对象是搜索者本身
这种情况下,在回调之后,会将搜索者自己的用户信息渲染出来,但与普通的搜索结果不同的是,此时用户信息栏不显示“加好友”按钮(避免出现自己加自己的奇怪情况)。
else if(this.searchName == uni.getStorageSync("username_log")){
this.currentFontIconClass = "icon-user"
this.currentNickClass = "nickname_searched",
this.currentIconClass = "icon_searched",
this.currentImgClass = "image_searched",
this.currentclass = "friend",
this.imgSrc = "https:xxxx" + this.searchName
}
这部分的代码也很有特点:因为搜索对象是搜索者本身这件事可以用简单的与之前我们存在缓存里的发起搜索请求的用户名进行对比实现,因而这里首先做了这样一个判断。
之后的操作就涉及到了动态类名,所以这块在这里不做详细介绍,在第四步会进行讲解。
最后一句是加载用户的头像,这一块是由于阿帕奇服务器会将用户上传的头像转成一个固定格式的url,因此可以用这行代码拼接出头像的url,从而把头像渲染。这一行是原项目的需要,对于本例的主要功能(搜索效果)来说,可以无视。
3.搜索的对象已经是搜索者的好友
这种情况下,在回调之后,会将这个好友的用户信息渲染出来,但与普通的搜索结果不同的是,此时用户信息栏的“加好友”按钮会变成“已添加”状态(避免出现重复添加的奇怪情况)
else if(res.data.message == "1"){
this.currentFontIconClass = "icon-user-check"
this.currentNickClass = "nickname_searched",
this.currentIconClass = "icon_searched",
this.currentImgClass = "image_searched",
this.currentclass = "friend",
this.imgSrc = "https:xxxx" + this.searchName
}
这部分的代码,首先跟后端协商好,返回的 message 变量的值为1时,代表搜索的对象已经是发起搜索的用户的好友。因而一句简单的判断,即可选中这种情况。
之后仍然是动态类名,仍然不做详细介绍。
4.搜索的对象是正常的结果(除了以上三种情况的结果)
这种情况最为普通,回调后将搜索对象的用户信息渲染即可,用户信息栏会出现正常的“加好友”按钮。
else{
this.currentFontIconClass = "icon-user-plus1"
this.currentNickClass = "nickname_searched",
this.currentIconClass = "icon_searched",
this.currentImgClass = "image_searched",
this.currentclass = "friend",
this.imgSrc = "https:xxxx" + this.searchName
}
以上四种情况的渲染实现方法在下一个步骤:动态类名 中进行介绍。
4️⃣动态类名
首先简单介绍动态类名:动态类名是相对于静态类名而言的。在前端开发中,一个组件可以拥有class,也即类名。当类名以一个 “xx” 的格式被赋值时,它就是一个静态类名。(可以理解为字符串)
当使用 v-bind: variable 时,代表我们使用了一个动态的表达式来表示类名,这个 “variable” 的值就是我们这个组件的类名。其中 v-bind:可以简写为“:”。
接下来举几个在本例中用到的动态类名的场景:
1.关于用户信息组件:
没有搜索任务时,用户信息组件的类名为默认的不显示类名,但当有搜索结果时,类名要变化为相应的结果的类名:
.friend_unSearched{
text-align: center;
width: 100%;
height: 110rpx;
}
.friend{
margin-top: 45rpx;
position: relative;
background-color: #FFFFFF;
width: 100%;
height: 100rpx;
}
2.关于添加好友按钮、用户头像等小组件:
.icon_searched{
height: 100rpx;
width: 100rpx;
}
.icon_unsearched{
width: 0rpx;
height: 0rpx;
}
.image_unsearched{
width: 0rpx;
height: 0rpx;
}
.image_searched{
margin-top: 3rpx;
line-height: 94rpx;
margin-left: 35rpx;
width: 94rpx;
height: 100rpx;
border-radius: 50%;
}
.icon_searched,.nickname_searched{
float: left;
}
.nickname_searched{
margin-left: 55rpx;
line-height: 100rpx;
}
.nickname_unsearched{
line-height: 100rpx;
margin: 0 auto;
text-align: center;
}
.fonticon_unsearched{
height: 0rpx;
width: 0rpx;
}
.icon-user-plus1,.icon-user,.icon-user-check{
line-height: 110rpx;
position: absolute;
right: 35rpx;
font-size: 40rpx;
}
上面的部分是用到了动态类名的css代码片段,可以和最初文章的第一部分展示的html代码进行对比,得到更加深刻的理解。
5️⃣效果优化
最后做一个简单的小的搜索功能优化:
要实现的效果:搜索结果在搜索框最后一个文字被擦除的瞬间完全消失。
首先,在html部分给input组件添加一个@input事件,这个事件会在input输入框的内容发生改变时被触发,同时会把此时的输入框内容作为事件的参数返回。
而后在JavaScript部分实现这个@input事件:
inputChanged(e){
if(e.target.value == ""){
this.searchName = "",
this.imgSrc = "",
this.currentFontIconClass = "fonticon_unsearched"
this.currentclass = "friend_unSearched",
this.currentIconClass = "icon_unsearched",
this.currentImgClass = "image_unsearched",
this.currentNickClass = "nickname_unsearched"
}
}
其中inputChange()是给@input事件起的名字,这个名字可以任意起。需要注意的一点是,这里的 消失 仍然是用到了 动态类名 的方法。
最后的最后补充一下,演示gif图上的用户信息栏最右侧的小人是用字体图标实现的,这部分可能单独做一个博客展示。
III.源码
<template>
<view>
<view class="header">
添加好友
</view>
<view class="searchInput">
<input type="text" placeholder="请输入对方的昵称" v-model="friendName" @input="inputChanged"/>
</view>
<view class="searchbtn" @click="search">
搜索 <span class="icon-search"></span>
</view>
<view :class = "currentclass">
<view :class="currentIconClass"><image :class = "currentImgClass" :src="imgSrc" mode="widthFix"></image></view>
<view :class="currentNickClass">
<text>{{searchName}}</text>
</view>
<span @click="add" :class="currentFontIconClass"></span>
</view>
</view>
</template>
<script>
export default{
data(){
return{
userName:'',
friendName:'',
imgSrc:'',
searchName:'',
color:'#f27498',
currentclass:'friend_unSearched',
currentImgClass:'image_unsearched',
currentIconClass:'icon_unsearched',
currentNickClass:'nickname_unsearched',
currentFontIconClass:"fonticon_unsearched"
}
},
methods:{
search(){
this.userName = uni.getStorageSync("username_log"),
uni.request({
url:'https:xxxx',
method:'GET',
data:{username : this.userName, friendname : this.friendName},
header: {
'content-type': 'application/x-www-form-urlencoded' //表明后端接收的是(表单)字符串类型,例如'id=1231454&sex=男'
},
success: (res) => {
if(this.friendName == "") return;
this.searchName = res.data.username;
if(this.searchName == null){
this.searchName = "此用户不存在!"
this.imgSrc = ""
}
else if(this.searchName == uni.getStorageSync("username_log")){
this.currentFontIconClass = "icon-user"
this.currentNickClass = "nickname_searched",
this.currentIconClass = "icon_searched",
this.currentImgClass = "image_searched",
this.currentclass = "friend",
this.imgSrc = "https:xxxx" + this.searchName
}
else if(res.data.message == "1"){
this.currentFontIconClass = "icon-user-check"
this.currentNickClass = "nickname_searched",
this.currentIconClass = "icon_searched",
this.currentImgClass = "image_searched",
this.currentclass = "friend",
this.imgSrc = "https:xxxx" + this.searchName
}
else{
this.currentFontIconClass = "icon-user-plus1"
this.currentNickClass = "nickname_searched",
this.currentIconClass = "icon_searched",
this.currentImgClass = "image_searched",
this.currentclass = "friend",
this.imgSrc = "https:xxxx" + this.searchName
}
}
})
console.log(this.friendName)
},
inputChanged(e){
if(e.target.value == ""){
this.searchName = "",
this.imgSrc = "",
this.currentFontIconClass = "fonticon_unsearched"
this.currentclass = "friend_unSearched",
this.currentIconClass = "icon_unsearched",
this.currentImgClass = "image_unsearched",
this.currentNickClass = "nickname_unsearched"
}
},
add(){
this.userName = uni.getStorageSync("username_log");
if(this.currentFontIconClass == "icon-user-plus1"){
uni.request({
url:'https:xxxx',
method:'GET',
data:{username : this.userName, friendname: this.friendName},
header: {
'content-type': 'application/x-www-form-urlencoded'
},
success: () => {
uni.showToast({
title: "添加成功"
}),
this.currentFontIconClass = "icon-user-check"
}
})
}
else if(this.currentFontIconClass == "icon-user"){
uni.showToast({
title: "不能添加自己哦!",
icon: "none"
})
}
else if(this.currentFontIconClass == "icon-user-check")
uni.showToast({
title: "已有该好友!",
icon: "none"
})
}
}
}
</script>
<style>
page{
display: flex;
flex-direction: column;
background-color: #ededed;
}
.header{
position: relative;
margin-top: 25rpx;
font-weight: 600;
font-size: 40rpx;
text-align: center;
margin-bottom: 35rpx;
}
.searchInput{
width: 100%;
}
input{
text-align: center;
margin: 0 auto;
border-radius: 10rpx;
border: 2rpx solid #000000;
width: 95%;
height: 70rpx;
}
.searchbtn{
border-radius: 10rpx;
background-color: #f27498;
font-size: 33rpx;
line-height: 65rpx;
text-align: center;
margin: 0 auto;
margin-top: 40rpx;
width: 35%;
height: 65rpx;
color: #FFFFFF;
}
.icon-search{
margin-left: 5rpx;
}
.friend_unSearched{
text-align: center;
width: 100%;
height: 110rpx;
}
.friend{
margin-top: 45rpx;
position: relative;
background-color: #FFFFFF;
width: 100%;
height: 100rpx;
}
.icon_searched{
height: 100rpx;
width: 100rpx;
}
.icon_unsearched{
width: 0rpx;
height: 0rpx;
}
.image_unsearched{
width: 0rpx;
height: 0rpx;
}
.image_searched{
margin-top: 3rpx;
line-height: 94rpx;
margin-left: 35rpx;
width: 94rpx;
height: 100rpx;
border-radius: 50%;
}
.icon_searched,.nickname_searched{
float: left;
}
.nickname_searched{
margin-left: 55rpx;
line-height: 100rpx;
}
.nickname_unsearched{
line-height: 100rpx;
margin: 0 auto;
text-align: center;
}
.fonticon_unsearched{
height: 0rpx;
width: 0rpx;
}
.icon-user-plus1,.icon-user,.icon-user-check{
line-height: 110rpx;
position: absolute;
right: 35rpx;
font-size: 40rpx;
}
</style>