基于Vue2的前端热搜数据展示系统开发实践
一、技术选型与项目初始化
在完成服务器环境部署、后端服务搭建以及爬虫系统开发之后,本文将聚焦于前端展示层的构建。前端开发的核心优势在于其可视化程度高、调试反馈及时,开发者能够直观地观察代码运行效果,这种即时反馈机制显著提升了开发体验。本篇将详细讲解如何将爬取到的热搜数据以友好的界面形式呈现给用户。
考虑到团队技术积累和学习成本,本次前端项目采用以下技术栈:Vue2作为核心框架,配合ElementUI组件库实现界面搭建。虽然Vue3和React在某些方面具有优势,但在中小型项目的快速迭代场景中,Vue2的成熟生态和丰富的第三方资源仍具有显著竞争力。
1.1 Node.js环境配置
前端开发的基础是Node.js运行时环境。首先访问Node.js官方网站下载安装包,建议选择LTS版本以保证稳定性。完成安装后,通过命令行验证安装结果:
node -v
如果正确显示版本号,说明Node.js环境已配置成功。鉴于国内网络环境对npm官方仓库访问存在限制,建议配置国内镜像源以提升依赖安装效率:
npm install -g cnpm --registry=https://registry.npmmirror.com
1.2 Vue项目脚手架搭建
使用Vue CLI工具可以快速生成项目模板,大幅减少基础配置工作。通过全局安装脚手架并创建项目:
cnpm install -g @vue/cli
vue create hot-search-frontend
在项目创建过程中,选择Vue2版本作为基础模板。创建完成后,进入项目目录并启动开发服务器:
cd hot-search-frontend
npm run serve
项目启动后,浏览器将自动访问http://localhost:8080,展示Vue.js的欢迎页面。
二、基础架构与依赖配置
2.1 项目结构优化
初始化项目包含一些演示文件,需要进行清理以保持代码结构简洁。主要删除以下内容:
- src/components/HelloWorld.vue组件
- src/assets/logo.png图片文件
清理后的App.vue文件结构如下:
<template>
<div id="app"></div>
</template>
<script>
export default {
name: 'App'
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
text-align: center;
margin-top: 60px;
}
</style>
2.2 核心依赖引入
项目需要引入axios用于HTTP请求处理,element-ui提供丰富的UI组件。执行以下命令完成安装:
cnpm install axios element-ui
在main.js中完成组件注册和全局配置:
import Vue from 'vue'
import App from './App.vue'
import {
Card,
Row,
Col,
Loading,
Container,
Header,
Main,
Footer
} from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
Vue.use(Card)
Vue.use(Row)
Vue.use(Col)
Vue.use(Loading)
Vue.use(Container)
Vue.use(Header)
Vue.use(Main)
Vue.use(Footer)
import axios from 'axios'
Vue.prototype.$http = axios
Vue.config.productionTip = false
new Vue({
render: h => h(App)
}).$mount('#app')
2.3 API服务层封装
为了统一管理接口请求并提高代码复用性,创建独立的API服务模块。在src/config目录下新建apiService.js文件:
import axios from 'axios'
const requestClient = axios.create({
baseURL: 'http://localhost:80/api',
timeout: 10000,
headers: {
'Content-Type': 'application/json'
}
})
export const hotSearchApi = {
getByType(type) {
return requestClient.get(`/hotSearch/list?type=${type}`)
}
}
三、热搜展示组件开发
3.1 组件需求分析
热搜展示组件需要实现以下功能:展示平台名称和图标、以列表形式呈现热搜条目、显示排名序号和热度数值、支持点击跳转到原始链接、前三名排名采用特殊颜色标识。
3.2 组件代码实现
在src/components目录下创建HotBoard.vue组件:
<template>
<el-card class="platform-card" v-loading="isLoading">
<template #header>
<div class="card-header">
<img :src="platformIcon" class="platform-icon" />
{{ platformName }}热搜榜
</div>
</template>
<div class="hot-list-scroll">
<div
v-for="item in hotList"
:key="item.hotSearchOrder"
:class="getRankingStyle(item.hotSearchOrder)"
class="hot-item"
>
<span class="item-order">{{ item.hotSearchOrder }}</span>
<span
class="item-title clickable"
@click="openTargetUrl(item.hotSearchUrl)"
>
{{ item.hotSearchTitle }}
</span>
<span class="item-heat">{{ formatHeatValue(item.hotSearchHeat) }}</span>
</div>
</div>
</el-card>
</template>
<script>
import { hotSearchApi } from '@/config/apiService'
export default {
name: 'HotBoard',
props: {
platformName: String,
platformIcon: String,
platformType: String
},
data() {
return {
hotList: [],
isLoading: false
}
},
mounted() {
this.loadHotData(this.platformType)
},
methods: {
async loadHotData(type) {
this.isLoading = true
try {
const response = await hotSearchApi.getByType(type)
this.hotList = response.data.data
} catch (error) {
console.error('数据加载失败:', error)
} finally {
this.isLoading = false
}
},
getRankingStyle(order) {
if (order === 1) return 'rank-gold'
if (order === 2) return 'rank-silver'
if (order === 3) return 'rank-bronze'
return ''
},
formatHeatValue(heat) {
if (typeof heat === 'string' && heat.includes('万')) {
return heat
}
const numValue = parseFloat(heat)
if (isNaN(numValue)) return heat
if (numValue < 1000) return numValue.toString()
if (numValue < 10000) return (numValue / 1000).toFixed(1) + 'k'
return (numValue / 10000).toFixed(1) + '万'
},
openTargetUrl(url) {
if (url) {
window.open(url, '_blank')
}
}
}
}
</script>
<style scoped>
.platform-card {
border-radius: 10px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
margin-bottom: 20px;
}
.platform-card:hover {
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
}
.card-header {
display: flex;
align-items: center;
font-weight: 600;
font-size: 15px;
}
.platform-icon {
width: 22px;
height: 22px;
margin-right: 8px;
}
.hot-list-scroll {
max-height: 320px;
overflow-y: auto;
padding-right: 12px;
}
.hot-item {
display: flex;
align-items: center;
padding: 10px 8px;
border-bottom: 1px solid #eee;
}
.item-order {
width: 22px;
font-size: 15px;
font-weight: 700;
color: #888;
margin-right: 8px;
}
.item-title {
flex: 1;
font-size: 13px;
color: #444;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
text-align: left;
}
.item-heat {
min-width: 45px;
text-align: right;
font-size: 12px;
color: #888;
}
.clickable {
cursor: pointer;
transition: color 0.2s;
}
.clickable:hover {
color: #409eff;
}
.rank-gold .item-order { color: #fadb14; }
.rank-silver .item-order { color: #b0b0b0; }
.rank-bronze .item-order { color: #d48806; }
</style>
3.3 主页面组件集成
在App.vue中引入热搜组件,并通过数据驱动的方式实现多平台热搜的同时展示:
<template>
<div id="app">
<el-row :gutter="12">
<el-col :span="6" v-for="(config, idx) in boardConfigs" :key="idx">
<hot-board
:platform-name="config.name"
:platform-icon="config.icon"
:platform-type="config.type"
/>
</el-col>
</el-row>
</div>
</template>
<script>
import HotBoard from '@/components/HotBoard.vue'
export default {
name: 'App',
components: { HotBoard },
data() {
return {
boardConfigs: [
{
name: '百度',
icon: require('@/assets/icons/baidu.svg'),
type: 'baidu'
},
{
name: '抖音',
icon: require('@/assets/icons/douyin.svg'),
type: 'douyin'
}
]
}
}
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
background: #f5f7fa;
min-height: 100vh;
padding: 20px;
}
</style>
四、知乎热搜数据爬取实现
4.1 接口分析与数据获取
知乎热搜数据通过RESTful接口提供,接口地址为https://www.zhihu.com/api/v3/feed/topstory/hot-lists/total,返回JSON格式的完整数据结构,包含标题、热度值、作者信息等丰富字段。相比HTML页面解析,JSON数据的处理更加便捷高效。
4.2 数据采集服务实现
创建定时任务类实现数据的周期性采集:
package com.example.spider.zhihu
import com.alibaba.fastjson.JSONArray
import com.alibaba.fastjson.JSONObject
import com.example.dao.HotSearchRepository
import com.example.entity.HotSearchEntity
import okhttp3.OkHttpClient
import okhttp3.Request
import org.springframework.scheduling.annotation.Scheduled
import org.springframework.stereotype.Component
import java.io.IOException
@Component
class ZhihuHotCollector {
@Autowired
private lateinit var hotSearchRepository: HotSearchRepository
@Scheduled(fixedRate = 3600000)
fun collectHotData() {
val client = OkHttpClient.Builder().build()
val request = Request.Builder()
.url("https://www.zhihu.com/api/v3/feed/topstory/hot-lists/total")
.get()
.build()
try {
val response = client.newCall(request).execute()
val jsonResponse = JSONObject.parseObject(response.body()?.string())
val dataArray = jsonResponse.getJSONArray("data")
val entityList = mutableListOf<HotSearchEntity>()
for (i in 0 until dataArray.size) {
val item = dataArray.getJSONObject(i)
val target = item.getJSONObject("target")
val entity = HotSearchEntity().apply {
this.resourceCode = "ZHIHU"
this.topicId = target.getString("id")
this.topicUrl = "https://www.zhihu.com/question/$topicId"
this.topicTitle = target.getString("title")
this.authorName = target.getJSONObject("author")?.getString("name")
this.authorAvatar = target.getJSONObject("author")?.getString("avatar_url")
this.topicSummary = target.getString("excerpt")
this.heatValue = item.getString("detail_text").replace("热度", "")
this.rankOrder = i + 1
}
entityList.add(entity)
}
hotSearchRepository.saveAll(entityList)
} catch (e: IOException) {
System.err.println("知乎数据采集异常: ${e.message}")
}
}
}
知乎热搜数据自带唯一标识符,采集时直接使用该标识即可,无需额外生成,有效避免了数据重复问题。