今天来到了文章搜索模块,首先准备好/search/index.vue
<template>
<div class="search-container">
<!-- 搜索栏 -->
<!--
Tips: 在 van-search 外层增加 form 标签,且 action 不为空,即可在 iOS 输入法中显示搜索按钮
-->
<form class="search-form" action="/">
<van-search
v-model="searchText"
show-action
placeholder="请输入搜索关键词"
background="#3296fa"
@search="onSearch"
@cancel="onCancel"
/>
</form>
<!-- 搜索结果 -->
<search-result v-if="isResultShow" :searchText="searchText" />
<!-- /搜索结果 -->
<!-- 联想建议 -->
<search-suggestion
@search="onSearch"
v-else-if="searchText"
:searchText="searchText"
/>
<!-- /联想建议 -->
<!-- 搜索历史记录 -->
<search-history v-else />
<!-- /搜索历史记录 -->
</div>
</template>
<script>
import searchHistory from "@/views/search/components/search-history";
import searchSuggestion from "@/views/search/components/search-suggestion";
import searchResult from "@/views/search/components/search-result";
export default {
name: "SearchPage",
components: {
searchHistory,
searchSuggestion,
searchResult,
},
props: {},
data() {
return {
searchText: "", // 绑定输入框变量
isResultShow: false,
};
},
computed: {},
watch: {},
created() {},
methods: {
onSearch(val) {
this.searchText = val;
this.isResultShow = true;
console.log(val); // 输入的值
},
onCancel() {
this.$router.back();
},
},
};
</script>
<style scoped lang="less">
.search-container {
padding-top: 108px;
.van-search__action {
color: #fff;
}
.search-form {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 1;
}
}
</style>
然后配置好api
/**
* 搜索相关请求模块
*/
import request from "@/utils/request";
export const getSearchSuggestions = (q) => {
return request({
method: "GET",
url: "/v1_0/suggestion",
params: {
q,
},
});
};
/**
* 获取搜索结果
*/
export function getSearchResult(params) {
return request({
method: "GET",
url: "/v1_0/search",
params,
});
}
然后准备好三个组件
![](https://img-blog.csdnimg.cn/f384704d31064de99bb64def78f9e936.png)
search-history.vue
<template>
<div class="search-history">
<van-cell title="搜索历史">
<span>全部删除</span>
<span>完成</span>
<van-icon name="delete" />
</van-cell>
<van-cell title="hello">
<van-icon name="close" />
</van-cell>
<van-cell title="hello">
<van-icon name="close" />
</van-cell>
<van-cell title="hello">
<van-icon name="close" />
</van-cell>
<van-cell title="hello">
<van-icon name="close" />
</van-cell>
</div>
</template>
<script>
export default {
name: "SearchHistory",
components: {},
props: {},
data() {
return {};
},
computed: {},
watch: {},
created() {},
mounted() {},
methods: {},
};
</script>
<style scoped lang="less"></style>
search-result.vue
<template>
<div class="search-result">
<van-list
v-model="loading"
:finished="finished"
finished-text="没有更多了"
@load="onLoad"
>
<van-cell v-for="(obj, index) in list" :key="index" :title="obj.title" />
</van-list>
</div>
</template>
<script>
import { getSearchResultAPI } from "@/api";
export default {
name: "SearchResult",
components: {},
props: {
searchText: {
type: String,
required: true,
},
},
data() {
return {
list: [],
loading: false,
finished: false,
page: 1, // 默认的页数
per_page: 10, // 每一页的数据量
};
},
computed: {},
watch: {},
created() {},
mounted() {},
methods: {
async onLoad() {
// 1. 获取数据
try {
const { data } = await getSearchResultAPI({
page: this.page,
per_page: this.per_page,
q: this.searchText,
});
console.log(data);
const { results } = data.data;
// 2. 将获取到的数据追加到数组,实现数据的更新
this.list = [...this.list, ...results];
// 3. 将loading的状态设置为false
this.loading = false;
// 4. 判断数据是否加载完毕,如果加载完毕,将finished设置为true
if (results.length) {
// 实现翻页 page+1
this.page++;
} else {
// 数据请求完毕
this.finished = true;
}
} catch (error) {
this.loading = false;
this.$toast("获取搜索结果失败");
console.log(error);
}
},
},
};
</script>
<style scoped lang="less"></style>
search-suggestion.vue
<template>
<div class="search-suggestion">
<van-cell
v-for="(text, index) in suggestions"
:key="index"
icon="search"
@click="$emit('search', text)"
>
<div slot="title" v-html="highlight(text)"></div>
</van-cell>
</div>
</template>
<script>
import { getSearchSuggestionsAPI } from "@/api";
// 导入防抖插件
import { debounce } from "lodash";
export default {
name: "SearchSuggestion",
components: {},
props: {
searchText: {
type: String,
required: true,
},
},
data() {
return {
suggestions: [],
};
},
computed: {},
watch: {
searchText: {
// 页面初始化就开始执行
immediate: true,
// debounce(fn,延迟时间)
handler: debounce(function (val) {
console.log(val);
this.loadSuggestions(val);
}, 200),
},
},
created() {},
mounted() {},
methods: {
async loadSuggestions(q) {
try {
const { data } = await getSearchSuggestionsAPI(q);
console.log(data);
this.suggestions = data.data.options;
} catch (error) {
this.$toast("获取搜索联想失败");
console.log(error);
}
},
highlight(text) {
const highlightStr = `<span style="color:red;">${this.searchText}</span>`;
const pattern = new RegExp(this.searchText, "gi");
return text.replace(pattern, highlightStr);
},
},
};
</script>
<style scoped lang="less"></style>
更新了/home/index.vue
<template>
<div class="home-container">
<!-- 导航栏 -->
<van-nav-bar class="page-nav-bar" fixed>
<van-button
class="search-btn"
slot="title"
type="info"
size="small"
round
icon="search"
to="/search"
>搜索</van-button
>
</van-nav-bar>
<!-- /导航栏 -->
<!-- 频道列表 -->
<!--
animated 滑动的动画
border 底边框线
swipeable 开启左右手势滑动
-->
<van-tabs class="channel-tabs" v-model="active" swipeable animated border>
<van-tab
v-for="channel in UserChannels"
:key="channel.id"
:title="channel.name"
>
<div slot="default">
<articleList :channel="channel"></articleList>
</div>
</van-tab>
<!-- 右侧自定义内容 -->
<!-- 占位元素 -->
<div class="placeholder" slot="nav-right"></div>
<!-- 右侧按钮 -->
<template slot="nav-right">
<div class="hamburger-btn" @click="showPopup">
<i class="toutiao toutiao-gengduo"></i>
</div>
</template>
</van-tabs>
<!-- /频道列表 -->
<!-- 弹出层 频道列表 -->
<van-popup
v-model="show"
position="bottom"
:style="{ height: '100%' }"
closeable
close-icon-position="top-left"
>
<ChannelEdit
:UserChannels="UserChannels"
:myActive="active"
@updataActive="changeActive"
></ChannelEdit>
</van-popup>
<!-- 弹出层 频道列表end -->
</div>
</template>
<script>
import { getUserChannelsAPI } from "@/api";
import articleList from "@/views/home/components/article-list";
import ChannelEdit from "./components/channel-edit";
import { mapState } from "vuex";
export default {
name: "HomeIndex",
components: {
articleList,
ChannelEdit,
},
props: {},
data() {
return {
active: 0,
UserChannels: [],
show: false,
// isChannelEditShow: false,
};
},
computed: {
...mapState(["user"]),
},
watch: {},
created() {
this.loadChannels();
},
mounted() {},
methods: {
async loadChannels() {
// 判断用户是否登录
if (this.user) {
// 已登录
try {
const { data } = await getUserChannelsAPI();
this.UserChannels = data.data.channels;
} catch (error) {
console.log("error");
this.$toast("获取频道列表失败");
}
} else {
// 未登录
try {
const { data } = await getUserChannelsAPI();
this.UserChannels = data.data.channels;
} catch (error) {
this.$toast("获取频道列表失败");
}
}
// try {
// // 发请求
// const { data } = await getUserChannelsAPI();
// console.log(data);
// // 成功赋值
// this.UserChannels = data.data.channels;
// } catch (err) {
// // 失败处理
// this.$toast("获取频道数据失败");
// }
},
showPopup() {
this.show = true;
},
changeActive(index) {
this.active = index;
// 关闭弹窗
this.show = false;
},
},
};
</script>
<style scoped lang="less">
.home-container {
// deep的作用是忽略scoped给css标签加上哈希值的影响
padding-top: 174px;
padding-bottom: 100px;
// tabs 标签导航也设置为固定定位
/deep/ .van-tabs__wrap {
position: fixed;
top: 92px;
z-index: 1;
left: 0; // left和right左右各拉一下,为了解决fixed定位造成的菜单无法点击移动的问题
right: 0;
height: 82px;
}
/deep/ .van-nav-bar__title {
max-width: unset;
}
.search-btn {
width: 555px;
height: 64px;
background-color: #5babfb;
border: none;
font-size: 28px;
.van-icon {
font-size: 32px;
}
}
}
/deep/ .channel-tabs {
.van-tab {
border-right: 1px solid #edeff3;
min-width: 200px;
font-size: 30px;
color: #777777;
}
.van-tab--active {
color: #333333;
}
.van-tabs__nav {
padding-bottom: 0;
}
.van-tabs__line {
bottom: 8px;
width: 31px !important;
height: 6px;
background-color: #3296fa;
}
.placeholder {
flex-shrink: 0;
width: 66px;
height: 82px;
}
.hamburger-btn {
position: fixed;
right: 0;
display: flex;
justify-content: center;
align-items: center;
width: 66px;
height: 82px;
background-color: #fff;
background-color: rgba(255, 255, 255, 0.902);
i.toutiao {
font-size: 33px;
}
&:before {
content: "";
position: absolute;
left: 0;
width: 1px;
height: 58px;
background-image: url(~@/assets/gradient-gray-line.png);
background-size: contain;
}
}
}
</style>