首页 星云 工具 资源 星选 资讯 热门工具
:

PDF转图片 完全免费 小红书视频下载 无水印 抖音视频下载 无水印 数字星空

《花100块做个摸鱼小网站! 》第四篇—前端应用搭建和完成第一个热搜组件

编程知识
2024年08月26日 07:30

⭐️基础链接导航⭐️

服务器 → ☁️ 阿里云活动地址

看样例 → 🐟 摸鱼小网站地址

学代码 → 💻 源码库地址

一、前言

在本系列文章的早期章节中,我们已经成功地购买了服务器并配置了MySQL、Redis等核心中间件。紧接着,我们不仅建立了后端服务,还开发了我们的首个爬虫程序。后面我们还把爬取到的数据进行了保存,生成了一整套MVC的后端代码,并且提供了一个接口出来。

这篇文章呢我要开始前端开发部分了。与后端开发相比,前端开发的优势在于其直观性和即时反馈。开发者可以迅速看到自己代码的成果,这种“所见即所得”的体验极大地提升了开发的乐趣和满足感。在接下来的篇章中,我将展示如何将爬取到的热搜数据整合到前端界面中,使之以一种用户友好的方式呈现,大家姑妄看之。

二、前端应用搭建

我的前端技术栈还停留在四年前,那时候我主要使用的是Vue2和ElementUI。并不是说我认为Vue3或React不好,只是我更习惯使用我熟悉的技术。即便如此,这些技术依然能够带来不错的效果。如果你想要尝试不同的技术或组件库,那也是完全可以的。

1. 前端环境搭建

(1)安装node.js,下载相应版本的node.js,下载地址:https://nodejs.org/en/download/ ,下载完双击安装,点击下一步直到安装完成,建议下载版本的是:v16.20.2

(2)安装完成后,附件里选择命令提示符(或者在开始的搜索框里输入cmd回车调出命令面板)输入:node -v回车,出现相应版本证明安装成功, node环境已经安装完成。

由于有些npm有些资源被屏蔽或者是国外资源的原因,经常会导致用npm安装依赖包的时候失败,所有我还需要npm的 国内镜像---cnpm。在命令行中输入:npm install -g cnpm --registry=https://registry.npmmirror.com 回车,大约需要3分钟,如果一直没有反应使用管理员身份运行cmd重试。

(3)安装全局vue-cli脚手架,用于帮助搭建所需的模板框架。输入命令:cnpm install -g @vue/cli回车等待完成。

(4)创建项目,首先我们要选定目录,然后再命令行中把目录转到选定的目录,假如我们打算把项目新建在e盘下的vue文件夹中则输入下面的命令: e:回车,然后cd vue,然后输入命令:vue create summo-sbmy-front-web,回车,然后它就会让你选择vue2还是vue3,选择vue2后点击enter,它就会创建好项目并且下载好依赖。

(5)启动项目,首先切换到summo-sbmy-front-web目录,然后执行npm run serve,项目运行成功后,浏览器会自动打开localhost:8080(如果浏览器没有自动打开 ,可以手动输入)。运行成功后,会看到Welcome to Your Vue.js App页面。

2. 脚手架处理

我的开发工具是VS Code,免费的,下载地址如下:https://code.visualstudio.com/

(1)文件和代码清理

删除components下的HelloWorld.vue文件
删除assets下的logo.png文件

原始App.vue代码如下

<template>
  <div>
    <img alt="Vue logo" src="./assets/logo.png">
    <HelloWorld msg="Welcome to Your Vue.js App"/>
  </div>
</template>

<script>
import HelloWorld from './components/HelloWorld.vue'

export default {
  name: 'App',
  components: {
    HelloWorld
  }
}
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

删除不必要的代码,不然启动会报错

<template>
  <div>
  </div>
</template>

<script>

export default {
  name: 'App',
  components: {
  }
}
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

(2)axios和element-ui依赖引入

执行安装命令

//axios依赖引入
cnpm install axios

//element-ui依赖引入
cnpm install element-ui

下载完上面这两个组件后,去main.js中注册组件,然后才能使用,main.js代码如下:

import Vue from 'vue'
import App from './App.vue'

//引入饿了么UI
import {
  Calendar,
  Row,
  Col,
  Link,
  Button,
  Loading,
  Container,
  Header,
  Footer,
  Main,
  Form,
  Autocomplete,
  Tooltip,
  Card,
  Dialog
}  from 'element-ui';
Vue.use(Calendar)
Vue.use(Row)
Vue.use(Col)
Vue.use(Link)
Vue.use(Button)
Vue.use(Loading)
Vue.use(Container)
Vue.use(Header)
Vue.use(Footer)
Vue.use(Form)
Vue.use(Autocomplete)
Vue.use(Tooltip)
Vue.use(Card)
Vue.use(Main)
Vue.use(Dialog)
import "element-ui/lib/theme-chalk/index.css"

//引入axios
import axios from 'axios';
Vue.prototype.$ajax = axios;

Vue.config.productionTip = false

new Vue({
  render: h => h(App),
}).$mount('#app')

(3)封装apiService.js方便调用接口

在src目录下创建一个文件夹名为config,创建一个apiService.js,代码如下:

// apiService.js
import axios from "axios";

// 创建axios实例并配置基础URL
const apiClient = axios.create({
  baseURL: "http://localhost:80/api",
  headers: {
    "Content-Type": "application/json"
  },
});

export default {
  // 封装Get接口
  get(fetchUrl) {
    return apiClient.get(fetchUrl);
  }
};

三、前端热搜组件

1. 组件介绍

首先,成品的热搜组件样式如下,包括标题(图标+名称)、内容区(排序、标题、热度),点击标题可以跳转到指定的热搜文章。

2. 组件实现

在components目录下创建HotSearchBoard.vue文件,代码如下:

<template>
  <el-card v-loading="loading">
    <template #header>
      <div>
        <img :src="icon" />
        {{ title }}热榜
      </div>
    </template>
    <div>
      <div
        v-for="item in hotSearchData"
        :key="item.hotSearchOrder"
        :class="getRankingClass(item.hotSearchOrder)"
       
      >
        <span>{{ item.hotSearchOrder }}</span>
        <span
         
          @click="openLink(item.hotSearchUrl)"
        >
          {{ item.hotSearchTitle }}
        </span>
        <span>{{ formatHeat(item.hotSearchHeat) }}</span>
      </div>
    </div>
  </el-card>
</template>

<script>
import apiService from "@/config/apiService.js";
export default {
  props: {
    title: String,
    icon: String,
    type: String,
  },
  data() {
    return {
      hotSearchData: [],
      loading:false
    };
  },
  created() {
    this.fetchData(this.type);
  },
  methods: {
    fetchData(type) {
      this.loading = true
      apiService
        .get("/hotSearch/queryByType?type=" + type)
        .then((res) => {
          // 处理响应数据
          this.hotSearchData = res.data.data;
        })
        .catch((error) => {
          // 处理错误情况
          console.error(error);
        }).finally(() => {
          // 加载结束
          this.loading = false; 
        });
    },
    getRankingClass(order) {
      if (order === 1) return "top-ranking-1";
      if (order === 2) return "top-ranking-2";
      if (order === 3) return "top-ranking-3";
      return "";
    },
    formatHeat(heat) {
      // 如果 heat 已经是字符串,并且以 "万" 结尾,那么直接返回
      if (typeof heat === "string" && heat.endsWith("万")) {
        return heat;
      }
      let number = parseFloat(heat); // 确保转换为数值类型进行比较
      if (isNaN(number)) {
        return heat; // 如果无法转换为数值,则原样返回
      }

      // 如果数值小于1000,直接返回该数值
      if (number < 1000) {
        return number.toString();
      }

      // 如果数值在1000到9999之间,转换为k为单位
      if (number >= 1000 && number < 10000) {
        return (number / 1000).toFixed(1) + "k";
      }

      // 如果数值大于等于10000,转换为万为单位
      if (number >= 10000) {
        return (number / 10000).toFixed(1) + "万";
      }
    },
    openLink(url) {
      if (url) {
        // 使用window.open在新标签页中打开链接
        window.open(url, "_blank");
      }
    },
  },
};
</script>

<style scoped>
.custom-card {
  background-color: #ffffff;
  border-radius: 10px;
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
  margin-bottom: 20px;
}
.custom-card:hover {
  box-shadow: 0 6px 8px rgba(0, 0, 0, 0.25);
}

>>> .el-card__header {
  padding: 10px 18px;
}
>>> .el-card__body {
  display: flex;
  padding: 10px 0px 10px 10px;
}
.card-title {
  display: flex;
  align-items: center;
  font-weight: bold;
  font-size: 16px;
}

.card-title-icon {
  fill: currentColor;
  width: 24px;
  height: 24px;
  margin-right: 8px;
}

.cell-group-scrollable {
  max-height: 350px;
  overflow-y: auto;
  padding-right: 16px; /* 恢复内容区域的内边距 */
  flex: 1;
}

.cell-wrapper {
  display: flex;
  align-items: center;
  padding: 8px 8px; /* 减小上下内边距以减少间隔 */
  border-bottom: 1px solid #e8e8e8; /* 为每个项之间添加分割线 */
}

.cell-order {
  width: 20px;
  text-align: left;
  font-size: 16px;
  font-weight: 700;
  margin-right: 8px;
  color: #7a7a7a; /* 统一非特殊序号颜色 */
}

/* 通过在cell-heat类前面添加更多的父级选择器,提高了特异性 */
.cell-heat {
  min-width: 50px;
  text-align: right;
  font-size: 12px;
  color: #7a7a7a;
}
.cell-title {
  font-size: 13px;
  color: #495060;
  line-height: 22px;
  flex-grow: 1;
  overflow: hidden;
  text-align: left; /* 左对齐 */
  text-overflow: ellipsis; /* 超出部分显示省略号 */
}
.top-ranking-1 .cell-order {
  color: #fadb14;
} /* 金色 */
.top-ranking-2 .cell-order {
  color: #a9a9a9;
} /* 银色 */
.top-ranking-3 .cell-order {
  color: #d48806;
} /* 铜色 */
/* 新增的.hover-effect类用于标题的hover状态 */
.cell-title.hover-effect {
  cursor: pointer; /* 鼠标悬停时显示指针形状 */
  transition: color 0.3s ease; /* 平滑地过渡颜色变化 */
}

/* 当鼠标悬停在带有.hover-effect类的元素上时改变颜色 */
.cell-title.hover-effect:hover {
  color: #409eff; /* 或者使用你喜欢的颜色 */
}
</style>

在App.vue中添加热搜组件,由于不止一个热搜,我把它做成了一个数组

<template>
  <div>
    <el-row :gutter="10">
      <el-col :span="6" v-for="(board, index) in hotBoards" :key="index">
        <hot-search-board
          :title="board.title"
          :icon="board.icon"
          :fetch-url="board.fetchUrl"
          :type="board.type"
        />
      </el-col>
    </el-row>
  </div>
</template>

<script>
import HotSearchBoard from "@/components/HotSearchBoard.vue";
export default {
  name: "App",
  components: {
    HotSearchBoard,
  },
  data() {
    return {
      hotBoards: [
        {
          title: "百度",
          icon: require("@/assets/icons/baidu-icon.svg"),
          type: "baidu",
        },
        {
          title: "抖音",
          icon: require("@/assets/icons/douyin-icon.svg"),
          type: "douyin",
        },
      ],
    };
  },
};
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
  background: #f8f9fa; /* 提供一个柔和的背景色 */
  min-height: 100vh; /* 使用视口高度确保填满整个屏幕 */
  padding: 0; /* 保持整体布局紧凑,无额外内边距 */
}
</style>

最终的项目结构和文件如下

代码不难,无非就是使用卡片和列表对热搜进行展示,还有就是我加了一些样式,比如前三名的排序有颜色,字体改了改。

四、小结一下

在本篇文章中,我主要展示前端代码逻辑。至于后端,也做了一些更新,比如新增了queryType接口和跨域请求的处理,但这些内容都是基础的,你下载代码后就能一目了然,不懂的评论区交流,或者加我微信:hb1766435296。之前的准备工作终于开始见到成效,虽然看起来简单,但实际上解决了不少复杂问题。现在,服务器、前端和后端的基础都打好了,接下来我会继续开发,增加更多功能。

关于爬虫部分,我已经成功实现了针对12个不同网站的爬取功能。考虑到爬虫的逻辑相对简单,无需单独撰写文章来详细说明。因此,我计划在每篇文章的附录或额外部分简要介绍各个热搜网站的爬虫逻辑。这样的安排既能保证信息的完整性,又不会让文章显得过于冗长。

番外:知乎热搜爬虫

1. 爬虫方案评估

知乎热搜是这样的, 接口是:https://www.zhihu.com/api/v3/feed/topstory/hot-lists/total

数据还算完备,有标题、热度、封面、排序等,知乎的热搜接口返回的数据格式是JSON,这种比返回HTML更简单。

2. 网页解析代码

这个就可以使用Postman生成调用代码,流程我就不赘述了,直接上代码,ZhihuHotSearchJob:

package com.summo.sbmy.job.zhihu;

import java.io.IOException;
import java.util.List;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;

import com.google.common.collect.Lists;
import com.summo.sbmy.dao.entity.SbmyHotSearchDO;
import com.summo.sbmy.service.SbmyHotSearchService;
import lombok.extern.slf4j.Slf4j;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import static com.summo.sbmy.common.enums.HotSearchEnum.ZHIHU;

/**
 * @author summo
 * @version DouyinHotSearchJob.java, 1.0.0
 * @description 知乎热搜Java爬虫代码
 * @date 2024年08月09
 */
@Component
@Slf4j
public class ZhihuHotSearchJob {

    @Autowired
    private SbmyHotSearchService sbmyHotSearchService;

    /**
     * 定时触发爬虫方法,1个小时执行一次
     */
    @Scheduled(fixedRate = 1000 * 60 * 60)
    public void hotSearch() throws IOException {
        try {
            //查询知乎热搜数据
            OkHttpClient client = new OkHttpClient().newBuilder().build();
            Request request = new Request.Builder().url("https://www.zhihu.com/api/v3/feed/topstory/hot-lists/total")
                .method("GET", null).build();
            Response response = client.newCall(request).execute();
            JSONObject jsonObject = JSONObject.parseObject(response.body().string());
            JSONArray array = jsonObject.getJSONArray("data");
            List<SbmyHotSearchDO> sbmyHotSearchDOList = Lists.newArrayList();
            for (int i = 0, len = array.size(); i < len; i++) {
                //获取知乎热搜信息
                JSONObject object = (JSONObject)array.get(i);
                JSONObject target = object.getJSONObject("target");
                //构建热搜信息榜
                SbmyHotSearchDO sbmyHotSearchDO = SbmyHotSearchDO.builder().hotSearchResource(ZHIHU.getCode()).build();
                //设置知乎三方ID
                sbmyHotSearchDO.setHotSearchId(target.getString("id"));
                //设置文章连接
                sbmyHotSearchDO.setHotSearchUrl("https://www.zhihu.com/question/" + sbmyHotSearchDO.getHotSearchId());
                //设置文章标题
                sbmyHotSearchDO.setHotSearchTitle(target.getString("title"));
                //设置作者名称
                sbmyHotSearchDO.setHotSearchAuthor(target.getJSONObject("author").getString("name"));
                //设置作者头像
                sbmyHotSearchDO.setHotSearchAuthorAvatar(target.getJSONObject("author").getString("avatar_url"));
                //设置文章摘要
                sbmyHotSearchDO.setHotSearchExcerpt(target.getString("excerpt"));
                //设置热搜热度
                sbmyHotSearchDO.setHotSearchHeat(object.getString("detail_text").replace("热度", ""));
                //按顺序排名
                sbmyHotSearchDO.setHotSearchOrder(i + 1);
                sbmyHotSearchDOList.add(sbmyHotSearchDO);
            }
            //数据持久化
            sbmyHotSearchService.saveCache2DB(sbmyHotSearchDOList);
        } catch (IOException e) {
            log.error("获取知乎数据异常", e);
        }
    }

}

知乎的热搜数据是自带唯一ID的,不需要我们手动生成,非常方便。

From:https://www.cnblogs.com/wlovet/p/18376100
本文地址: http://shuzixingkong.net/article/1437
0评论
提交 加载更多评论
其他文章 互联工厂数据交换标准:IPC-CFX
本文我们一起了解下IPC-CFX标准产生的背景 和 用途,它是机器设备之间通信的“统一语言”,是大家都懂的“普通话”而不是“方言”。IPC-CFX使用AMQP v1.0传输协议实现安全的连接,使用JSON进行数据编码,提供了明确的消息结构和数据内容,确保即插即用,它或许是工业4.0应用的基础。
互联工厂数据交换标准:IPC-CFX 互联工厂数据交换标准:IPC-CFX 互联工厂数据交换标准:IPC-CFX
使用 Nuxt 的 showError 显示全屏错误页面
title: 使用 Nuxt 的 showError 显示全屏错误页面 date: 2024/8/26 updated: 2024/8/26 author: cmdragon excerpt: 摘要:本文介绍Nuxt.js中的showError方法用于显示全屏错误页面,包括其参数类型及使用方式,并演
使用 Nuxt 的 showError 显示全屏错误页面 使用 Nuxt 的 showError 显示全屏错误页面
C#/.NET/.NET Core技术前沿周刊 | 第 2 期(2024年8.19-8.25)
前言 C#/.NET/.NET Core技术前沿周刊,你的每周技术指南针!记录、追踪C#/.NET/.NET Core领域、生态的每周最新、最实用、最有价值的技术文章、社区动态、优质项目和学习资源等。让你时刻站在技术前沿,助力技术成长与视野拓宽。 欢迎投稿,推荐或自荐优质文章/项目/学习资源等。每周
C#/.NET/.NET Core技术前沿周刊 | 第 2 期(2024年8.19-8.25) C#/.NET/.NET Core技术前沿周刊 | 第 2 期(2024年8.19-8.25) C#/.NET/.NET Core技术前沿周刊 | 第 2 期(2024年8.19-8.25)
【LLM训练系列】NanoGPT源码详解和中文GPT训练实践
本文是【训练LLM系列】的第一篇,主要重点介绍NanoGPT代码以及中文、英文预训练实践。最新版参见我的知乎:https://zhuanlan.zhihu.com/p/716442447 除跑通原始NanoGPT代码之外,分别使用了《红楼梦》、四大名著和几十本热门网络小说,进行了字符级、自行训练to
【LLM训练系列】NanoGPT源码详解和中文GPT训练实践 【LLM训练系列】NanoGPT源码详解和中文GPT训练实践 【LLM训练系列】NanoGPT源码详解和中文GPT训练实践
异源数据同步 → DataX 为什么要支持 kafka?
开心一刻 昨天发了一条朋友圈:酒吧有什么好去的,上个月在酒吧当服务员兼职,一位大姐看上了我,说一个月给我 10 万,要我陪她去上海,我没同意 朋友评论道:你没同意,为什么在上海? 我回复到:上个月没同意 前情回顾 关于 DataX,官网有很详细的介绍,鄙人不才,也写过几篇文章 异构数据源同步之数据同
异源数据同步 → DataX 为什么要支持 kafka? 异源数据同步 → DataX 为什么要支持 kafka? 异源数据同步 → DataX 为什么要支持 kafka?
甲方扔给两个存在包名与类名均相同的Jar包,要在工程中同时使用怎么办?
你的项目是否曾遇到过有jar包冲突,而这些冲突的jar包又必须同时存在的情况?一般来说,jar 冲突都是因不同的上层依赖项,自身又依赖了相同 jar 包的不同版本所致,解决办法也都是去除其中一个即可。需要同时保留冲突jar包的情况,实属罕见。 在与第三访系统集成通信时,有一种方式是由被集成方提供Ja
甲方扔给两个存在包名与类名均相同的Jar包,要在工程中同时使用怎么办? 甲方扔给两个存在包名与类名均相同的Jar包,要在工程中同时使用怎么办? 甲方扔给两个存在包名与类名均相同的Jar包,要在工程中同时使用怎么办?
为什么说 Swoole 是 PHP 程序员技术水平的分水岭?
大家好,我是码农先森。 谈到这个话题有些朋友心中不免会有疑惑,为什么是 Swoole 而不是其他呢?因为 Swoole 是基于 C/C++ 语言开发的高性能异步通信扩展,覆盖的特性足够的多,有利于 PHP 程序员接触更全面的技术知识点。大多数的朋友踏入到 PHP 的大门都是因其简单的语法及其弱类型的
为什么说 Swoole 是 PHP 程序员技术水平的分水岭? 为什么说 Swoole 是 PHP 程序员技术水平的分水岭? 为什么说 Swoole 是 PHP 程序员技术水平的分水岭?
2024年智能革命:HarmonyOS NEXT与盘古大模型5.0的颠覆性融合
2024年,华为发布了震撼业界的HarmonyOS NEXT和盘古大模型5.0,智能设备市场迎来前所未有的变革。这不仅是技术的革新,更是一次深刻的未来预告。操作系统与AI技术的深度融合,将会如何改变我们日常生活的方方面面?你是否好奇,这些颠覆性功能如何重新定义智能设备的应用场景?开发者们又是如何利用
2024年智能革命:HarmonyOS NEXT与盘古大模型5.0的颠覆性融合