在前端工作中,一般对于展示数据量比较多的页面来说,都是需要一个搜索功能来给予用户更优质的体验;这次展示的例子来源于我的工作中的摘录。插件安装就不多说了,各位大佬一个都懂npm install了吧。
下面是使用的版本信息:
name | edition |
---|---|
vue | 2.6.11 |
vue-router | 3.2.0 |
axios | 0.27.2 |
vant | 2.12.48 |
代码思路:
看到需求的时候就需要有清晰的思路去编写代码,不然写出来的就是乱糟糟的东西,还需要不断的修改,养成先思考再编写的习惯,不要一股脑对着键盘一顿输出。
项目需求首先是关键字搜索、搜索记录、热搜榜单,主要就是实现这三个功能。根据需求可以知道具体流程了,进入搜索 => 输入关键字=>产生搜索记录=>记录一次词条搜索记录(热搜榜)=>展示搜索结果(渲染接口数据)。现在思路清晰了,下面搜索前的是效果图:
vue的结构代码:
<!-- 顶部搜索框 -->
<div class="topSearch">
<form action="/">
<van-search
v-model="value"
show-action
placeholder="请输入搜索关键词"
@search="onSearch"
@cancel="onCancel"
/>
</form>
</div>
<!-- 搜索前 -->
<div class="searchBefore" v-if="searchNull == 0">
<!-- 搜索记录 -->
<div class="search-records">
<div style="display: flex;justify-content: space-between;width: 96%;margin: 0 auto;" v-if="this.searchText.length != 0">
<div style="font-size: 14px;color: #999999;text-align: left;display: block;width: 50%;height: 0.2rem;line-height: 0.2rem;">
历史记录
</div>
<div style="font-size: 15px;color: #999999;width: 30%;height: 0.2rem;line-height: 0.2rem;text-align: right;">
<van-icon name="delete-o" @click="deleteSearch" />
</div>
</div>
<!-- 添加记录 -->
<div class="searchText" v-if="this.searchText.length != 0">
<!-- 添加多条时未换行bug -->
<div v-for="(record,index) in searchText" :key="index" @click="searchTextClisk(index,record)">
{{record.keyword}}
</div>
</div>
</div>
<!-- 热搜 -->
<div class="hotSearch">
<div class="hotSearch-title"><van-icon name="fire" /><div>知识热搜</div></div>
<div class="hotSearch-list">
<ul>
<li v-for="(hots,index) in hotList" :key="index" @click="hotcontent(index,hots)">
<span><van-icon name="stop" /></span>
{{hots.keyword}}
</li>
</ul>
</div>
</div>
</div>
<!-- 搜索结果 -->
<div class="article-list" v-if="searchNull == 1">
<!-- 分类栏 -->
<div class="article-top">
<van-tabs animated sticky @click="typeClick">
<van-tab v-for="(titles,index) in searchTitle" :key="index" :title="titles.name">
<van-list v-model="loading" :finished="finished" finished-text="没有更多了" @load="allLoad">
<!-- 搜索出来的文章结果 -->
<div class="article-con-list">
<!-- 普通文章(包括图文、视频、音频) -->
<div class="general-articles" v-for="(article,index) in articleList" :key="index" @click="gotoArticle(article.id)">
<div>
<!-- 文章标题 -->
<div class="articles-big-title">{{article.title}}</div>
<!-- 文章发布单位和时间 -->
<div class="articles-timeAndName">
<div>{{article.organ_name}}</div><div>{{article.createtime,time | handleDate}}</div>
</div>
</div>
<div class="articles-image">
<!-- 文章图片 -->
<img :src="url + article.image" alt="">
</div>
</div>
<van-empty image="search" description="" v-if="total == 0" />
</van-list>
</van-tab>
</van-tabs>
</div>
</div>
页面全部css样式都在下面,可能有些用不上,大佬们自己调整。
css代码(提供参考,实际工作中自己调整):
<style scoped>
.content{width: 100%;height: auto;}
/* 搜索框 */
.topSearch{width: 100%;height: auto;border-bottom: 1px solid #CCCCCC;}
.topSearch >>> .van-search .van-cell{padding-right: 15px;}
.search-records{font-size: 14px;width: 98%;height: auto;margin-top: 0.15rem;}
.searchText{width: 90%;height: auto;margin: 0 auto;margin-top: 0.1rem;display: flex;justify-content: left;flex-wrap: wrap;}
.searchText div{font-size: 15px;text-align: left;height: 0.25rem;line-height: 0.14rem;width: auto;margin-right: 0.1rem;background: #dddddd;padding: 0.06rem 0.1rem;border-radius: 2000px;margin-top: 0.1rem;}
.hotSearch{width:96%;height: auto;margin: 0 auto;margin-top: 0.2rem;}
.hotSearch-title{font-size: 16px;font-weight: bold;text-align: left;display: flex;justify-content: left;}
.hotSearch-title >>> .van-icon-fire{color: #ff5e5e;margin-right: 0.05rem;line-height: 0.2rem;}
.hotSearch-list{font-size: 15px;}
.hotSearch-list ul{margin-top: 0.1rem;}
.hotSearch-list ul li{text-align: left;margin-top: 0.1rem;width: 55%;overflow: hidden;text-overflow: ellipsis;white-space: nowrap;}
.hotSearch-list >>> .van-icon-stop{color: #d66353;margin-right: 0.05rem;}
/* 搜索结果列表 */
.article-list{width: 100%;height: auto;margin: 0 auto;}
.article-top{width: 100%;height: auto;}
.article-top >>> .van-tabs__nav{background: #f7f7f7;width: 100%;}
.article-top >>> .van-tabs__content{font-size: 14px;background: #f7f7f7;text-align: left;width:90%;height: auto;margin: 0 auto;}
.article-con-list{width: 100%;height: auto;}
.general-articles:nth-child(1){margin-top: 0.2rem;}
.general-articles{width: 100%;height: 1.1rem;background: white;margin-top: 0.2rem;border-radius: 4px;display: flex;justify-content: space-between;padding: 0.1rem;}
.general-articles>div:nth-child(1){width: 50%;position: relative;}
.general-articles>div{width: 40%;}
.articles-big-title{font-size: 16px;font-weight: bold;background: white;width: 100%;height:0.25rem;line-height: 0.25rem;overflow: hidden;text-overflow: ellipsis;white-space: nowrap;}
.articles-timeAndName{width: 100%;display: flex;justify-content: space-between;position: absolute;bottom: 0;font-size: 13px;color: #999999;}
.articles-timeAndName div:nth-child(1){width: 60%;overflow: hidden;text-overflow: ellipsis;white-space: nowrap;text-align: left;line-height: 0.15rem;}
.articles-timeAndName div:nth-child(2){width: 38%;overflow: hidden;text-overflow: ellipsis;white-space: nowrap;text-align: right;line-height: 0.15rem;}
.articles-image{width: 100%;height: 100%;border-radius: 3px;}
.articles-image>img{width: 100%;height: 100%;border-radius: 3px;}
</style>
最重要的js核心代码,重要的是理解方法逻辑,理解了就可以举一反三了
js逻辑(代码全部给出了,我获取接口数据的axios都是全局封装好的):
export default{
data(){
return{
value: '', // 输入的搜索内容
searchText:[],// 搜索记录列表
hotList:[],// 热搜记录列表
articleList:[],//搜索结果列表
page: 1, //列表分页加载
loading: false, //加载状态结束
finished: false, //数据全部加载完成
searchNull:0, //是否开始搜索
//数据全部加载完成
searchTitle:[
{name:'全部'},
{name:'图文'},
{name:'视频'},
{name:'音频'},
],
url: domain.imgUrl,// 全局图片地址拼接
time:'', //当前时间
total:'',//数据总条数
}
},
mounted() {
this.getSearch()
},
methods:{
//搜索记录和热搜
getSearch(){
this.$axios.user_search_history().then(res=>{
// 搜索记录列表
this.searchText = res.data.data.user_history
// 热搜记录列表
this.hotList = res.data.data.hot_list
}).catch(err=>{
console.log(err)
})
},
//删除搜索记录
deleteSearch(){
this.$axios.delete_search_history().then(res=>{
//删除搜索记录列表 需要添加弹窗删除警告
this.searchText = []
})
},
//点击历史搜索记录进行搜索
searchTextClisk(index,record){
//点击搜索记录,填入搜索框
this.value = record.keyword
//触发搜索事件
this.onSearch()
},
//点击热搜进行搜索
hotcontent(index,hots){
//点击搜索记录,填入搜索框
this.value = hots.keyword
//触发搜索事件
this.onSearch()
},
//确定搜索时触发
onSearch(val) {
//搜索时加载
this.$toast.loading({
message: '加载中...',
forbidClick: true,
loadingType: 'spinner',
duration:500,
});
//搜索关键字
this.keyword = val
//输入搜索内容为空时不给搜索
if (val != ''){
//切换为搜索状态
this.searchNull = 1
this.$axios.article_list({keyword: this.keyword,file_type:0,page:this.page}).then(res=>{
//搜索结果列表
this.articleList = res.data.data.data
//当前时间
this.time = res.data.time
//数据总条数
this.total = res.data.data.total
}).catch(err=>{
console.log(err)
})
}else {
//搜索输入内容为空时提示
this.$toast('请输入搜索关键字');
}
},
//点击取消按钮时触发
onCancel() {
this.$router.go(-1);
},
//搜索结果列表加载
allLoad(){
this.page++
//加载请求第二页
this.$axios.article_list({keyword: this.keyword,file_type:0,page:this.page}).then(res => {
const rows = res.data.data.data
this.loading = false
//列表内容总数
this.total = res.data.data.total
if (rows == null || rows.length === 0) {
// 加载结束
this.finished = true
return
}
// 将新数据与老数据进行合并
this.articleList = this.articleList.concat(rows)
// 如果列表数据条数>=总条数,不再触发滚动加载
if (this.articleList.length >= this.total) {
this.finished = true
}
}).catch(err => {
console.log(err)
});
},
//点击标签栏对搜索内容进行分类 (全部、图文、视频、音频)
typeClick(index){
//点击分类时先清空数组,防止切换时出现页面bug
this.articleList = []
//切换为搜索状态
this.searchNull = 1
this.$axios.article_list({keyword: this.keyword,file_type:index,}).then(res=>{
//搜索结果列表
this.articleList = res.data.data.data
//当前时间
this.time = res.data.time
//数据总条数
this.total = res.data.data.total
}).catch(err=>{
console.log(err)
})
},
//点击跳转相对应文章
gotoArticle(id){
this.$router.push('/learning/articleDetails?id=' + id)
},
},
filters: {
handleDate(value, serverTime) {
let date = new Date(value * 1000);
let serverTimes = new Date(serverTime * 1000);
let diffValue = serverTimes - date.getTime(); // 时间差
let mValue = diffValue / (1000 * 60); // 分
let hValue = diffValue / (1000 * 60 * 60); // 小时
let dayValue = diffValue / (1000 * 60 * 60 * 24); // 天
let result = '';
if (date.getFullYear() !== new Date().getFullYear()) { // 不同年
let date = new Date(value * 1000); // 10位数时间戳需要 * 1000
let y = date.getFullYear();
let MM = date.getMonth() + 1;
MM = MM < 10 ? ('0' + MM) : MM;
let d = date.getDate();
d = d < 10 ? ('0' + d) : d;
let h = date.getHours();
h = h < 10 ? ('0' + h) : h;
let m = date.getMinutes();
m = m < 10 ? ('0' + m) : m;
let s = date.getSeconds();
s = s < 10 ? ('0' + s) : s;
return y + '-' + MM + '-' + d;
} else { // 同年
if (dayValue > 360) { // 时间差大于一年
let date = new Date(value * 1000); // 10位数时间戳需要 * 1000
let y = date.getFullYear();
let MM = date.getMonth() + 1;
MM = MM < 10 ? ('0' + MM) : MM;
let d = date.getDate();
d = d < 10 ? ('0' + d) : d;
let h = date.getHours();
h = h < 10 ? ('0' + h) : h;
let m = date.getMinutes();
m = m < 10 ? ('0' + m) : m;
let s = date.getSeconds();
s = s < 10 ? ('0' + s) : s;
return '一年前';
}else if (dayValue >= 30 && dayValue <= 360) {
result = parseInt(dayValue / 30) + '月前';
}else if (dayValue >= 7 && dayValue <= 30) {
result = parseInt(dayValue / 7) + '周前';
} else if (dayValue >= 1 && dayValue <= 6) { // 时间差一天之外 一周之内
result = parseInt(dayValue) + '天前';
} else if (hValue >= 1 && hValue <= 24) {
result = parseInt(hValue) + '小时前';
} else if (mValue >= 1 && mValue <= 60) {
result = parseInt(mValue) + '分钟前';
} else if (diffValue > 0 && diffValue < mValue) {
result = '最新';
}
}
return result
},
},
}