当前位置:首页 > 技术 > 正文内容

基于Vue2的前端热搜数据展示系统开发实践

访客 技术 2026年10月2日 5

一、技术选型与项目初始化

在完成服务器环境部署、后端服务搭建以及爬虫系统开发之后,本文将聚焦于前端展示层的构建。前端开发的核心优势在于其可视化程度高、调试反馈及时,开发者能够直观地观察代码运行效果,这种即时反馈机制显著提升了开发体验。本篇将详细讲解如何将爬取到的热搜数据以友好的界面形式呈现给用户。

考虑到团队技术积累和学习成本,本次前端项目采用以下技术栈: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}")
        }
    }
}

知乎热搜数据自带唯一标识符,采集时直接使用该标识即可,无需额外生成,有效避免了数据重复问题。

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。