效果图
博客教程:使用 Vue.js 构建一个响应式搜索页面
目录
项目介绍
在这个项目中,我们将构建一个带有搜索记录、搜索发现、热搜榜的响应式搜索页面。用户可以搜索内容,查看历史记录,点击"换一批"按钮来刷新搜索发现内容,并查看当前热门趋势。
所需工具和技术
- HTML/CSS:用于页面布局和样式
- Vue.js:用于实现数据绑定和页面交互
- JavaScript:用于数据处理和逻辑控制
页面布局与样式
我们首先创建 HTML 结构,并使用 CSS 设置页面的基本样式。这个页面主要分为三个部分:
- 搜索栏:允许用户输入搜索关键词并提交。
- 历史搜索和搜索发现:展示用户历史搜索记录,并提供搜索建议。
- 趋势播报(热搜榜):展示当前热门的搜索词条及其趋势值。
在 head
部分引入 Vue.js,并设置基本的 CSS 样式,保持页面简洁、现代的视觉效果。页面的 body
中使用 div
元素构建布局,并通过 Vue 实现交互功能。
Vue.js 数据绑定与事件处理
在 HTML 结构准备好后,我们用 Vue.js 来实现数据的动态绑定和事件处理。以下是各部分功能的实现步骤。
1. 搜索记录的实现
<!-- 搜索框 -->
<div class="search-bar">
<input type="text" v-model="searchQuery" placeholder="搜索内容">
<button @click="search">搜索</button>
</div>
v-model
:绑定输入框的值searchQuery
。@click="search"
:点击按钮时调用search
方法,将输入的内容添加到搜索历史中。
2. 历史搜索与搜索发现的实现
<!-- 历史搜索 -->
<div>
<div class="section-title">历史搜索</div>
<div class="tags-container">
<div v-for="item in searchHistory" :key="item" class="tag">{
{ item }}</div>
</div>
</div>
<!-- 搜索发现 -->
<div>
<div class="section-title">搜索发现</div>
<div class="tags-container">
<div v-for="item in searchDiscovery" :key="item" class="tag">{
{ item }}</div>
</div>
<div class="refresh-button" @click="refreshDiscovery">换一批 ↻</div>
</div>
v-for
循环:通过v-for
遍历searchHistory
和searchDiscovery
数组,动态显示标签内容。refreshDiscovery
方法:当用户点击 “换一批” 按钮时,更新searchDiscovery
数据,展示新的搜索发现内容。
3. 热搜榜的实现
<!-- 热搜榜 -->
<div class="hot-list">
<div class="section-title">趋势播报</div>
<div v-for="(item, index) in hotTrends" :key="index" class="hot-item">
<div class="hot-rank">{
{ index + 1 }}</div>
<div class="hot-details">
<div class="hot-title">{
{ item.title }}</div>
<div class="hot-subtitle">{
{ item.subtitle }}</div>
</div>
<div class="hot-trend">趋势值{
{ item.trendValue }}</div>
</div>
</div>
hotTrends
数组:包含每个热搜项的title
、subtitle
和trendValue
。v-for
循环:动态显示热搜榜的内容。
功能实现
实现搜索记录
search() {
if (this.searchQuery && !this.searchHistory.includes(this.searchQuery)) {
this.searchHistory.push(this.searchQuery);
}
this.searchQuery = '';
}
- 将输入内容添加到
searchHistory
中,确保记录唯一。 - 清空输入框,为下一次搜索准备。
实现搜索发现的换一批功能
refreshDiscovery() {
this.discoveryIndex = (this.discoveryIndex + 1) % this.discoveryOptions.length;
this.searchDiscovery = this.discoveryOptions[this.discoveryIndex];
}
discoveryIndex
:跟踪当前搜索发现的批次,每次点击时更新discoveryIndex
,并刷新searchDiscovery
内容。- 通过
%
运算符使批次循环展示。
实现热搜榜展示
data: {
hotTrends: [
{
title: '暗黑纤体瓶', subtitle: '喜茶新品纤体瓶,万圣节限定款', trendValue: '98.9万' },
// 添加更多热搜数据
]
}
- 热搜榜通过
hotTrends
数组来存储数据,并用v-for
循环展示。
总结与扩展思路
在这个项目中,我们利用 Vue.js 实现了一个搜索页面,包含搜索记录、搜索发现、趋势播报等功能。此页面结构简洁,逻辑清晰,非常适合 Vue.js 初学者练习。
扩展建议
- 本地存储:将
searchHistory
存储到浏览器的localStorage
中,防止页面刷新后丢失记录。 - API 集成:将热搜榜数据从 API 获取,展示实时趋势。
- 交互优化:增加动画效果,如标签的淡入淡出,提高用户体验。
示例代码
完整代码如下所示:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>搜索页 - Vue.js 实现</title>
<script src="https://cdn.staticfile.net/vue/2.7.0/vue.min.js"></script>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 20px;
background-color: #f4f4f4;
display: flex;
justify-content: center;
}
#app {
width: 100%;
max-width: 420px;
background-color: #ffffff;
border-radius: 10px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
padding: 20px;
box-sizing: border-box;
}
.search-bar {
display: flex;
align-items: center;
border: 1px solid #ddd;
border-radius: 20px;
padding: 8px 10px;
margin-bottom: 20px;
}
.search-bar input {
flex: 1;
border: none;
outline: none;
font-size: 16px;
}
.search-bar button {
background: none;
border: none;
color: #007bff;
cursor: pointer;
font-size: 16px;
}
.section-title {
font-size: 16px;
font-weight: bold;
margin: 10px 0;
color: #333;
}
.tags-container {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-bottom: 10px;
}
.tag {
background-color: #f2f2f2;
padding: 8px 12px;
border-radius: 20px;
font-size: 14px;
color: #666;
cursor: pointer;
height: 30px;
line-height: 30px;
}
.refresh-button {
font-size: 14px;
color: #007bff;
cursor: pointer;
margin-top: 5px;
}
.hot-list {
margin-top: 20px;
}
.hot-item {
display: flex;
align-items: center;
padding: 10px 0;
border-bottom: 1px solid #eaeaea;
}
.hot-item:last-child {
border-bottom: none;
}
.hot-rank {
font-size: 18px;
font-weight: bold;
color: #ff6f61;
margin-right: 10px;
width: 30px;
}
.hot-details {
flex: 1;
}
.hot-title {
font-size: 16px;
color: #333;
}
.hot-subtitle {
font-size: 14px;
color: #999;
}
.hot-trend {
font-size: 14px;
color: #666;
}
</style>
</head>
<body>
<div id="app">
<!-- 搜索框 -->
<div class="search-bar">
<input type="text" v-model="searchQuery" placeholder="搜索内容">
<button @click="search">搜索</button>
</div>
<!-- 历史搜索 -->
<div>
<div class="section-title">历史搜索</div>
<div class="tags-container">
<div v-for="item in searchHistory" :key="item" class="tag">{
{ item }}</div>
</div>
</div>
<!-- 搜索发现 -->
<div>
<div class="section-title">搜索发现</div>
<div class="tags-container">
<div v-for="item in searchDiscovery" :key="item" class="tag">{
{ item }}</div>
</div>
<div class="refresh-button" @click="refreshDiscovery">换一批 ↻</div>
</div>
<!-- 热搜榜 -->
<div class="hot-list">
<div class="section-title">趋势播报</div>
<div v-for="(item, index) in hotTrends" :key="index" class="hot-item">
<div class="hot-rank">{
{ index + 1 }}</div>
<div class="hot-details">
<div class="hot-title">{
{ item.title }}</div>
<div class="hot-subtitle">{
{ item.subtitle }}</div>
</div>
<div class="hot-trend">趋势值{
{ item.trendValue }}</div>
</div>
</div>
</div>
<script>
new Vue({
el: '#app',
data: {
searchQuery: '',
searchHistory: ['烧烤', '饮料', '烤洋芋', '烤红薯', '奶茶', '米皮'],
searchDiscovery: ['抢麦当劳惊喜周边', '优可云端超市', '洋芋饭', '刀削面', '番茄鸡蛋'],
hotTrends: [
{
title: '暗黑纤体瓶', subtitle: '喜茶新品纤体瓶,万圣节限定款', trendValue: '98.9万' },
{
title: '太妃棒香拿铁', subtitle: '瑞幸×Pingu,新品周边', trendValue: '97.9万' },
{
title: '鱼豆腐蔬菜米线', subtitle: '米线店火热售卖', trendValue: '91.3万' },
{
title: '香辣水煮鱼', subtitle: '美食广场火爆', trendValue: '93.5万' },
],
discoveryOptions: [
['火锅底料', '果冻粉', '牛排', '羊肉卷', '麻辣香锅'],
['椰子汁', '芋圆', '杨枝甘露', '米皮', '螺蛳粉'],
['汉堡', '冰淇淋', '奶茶', '咖啡豆', '茶叶蛋']
],
discoveryIndex: 0
},
methods: {
search() {
if (this.searchQuery && !this.searchHistory.includes(this.searchQuery)) {
this.searchHistory.push(this.searchQuery);
}
this.searchQuery = '';
},
refreshDiscovery() {
this.discoveryIndex = (this.discoveryIndex + 1) % this.discoveryOptions.length;
this.searchDiscovery = this.discoveryOptions[this.discoveryIndex];
}
}
});
</script>
</body>
</html>