Spring Boot + vue-element 开发个人博客项目实战教程(二十六、前端首页统计完善及完结)

news2024/11/28 7:43:57

作者简介:码上言


代表教程:Spring Boot + vue-element 开发个人博客项目实战教程


专栏内容:个人博客系统


我的文档网站:http://xyhwh-nav.cn/

后端代码gitee地址:https://gitee.com/whxyh/personal_blog

前端代码gitee地址:https://gitee.com/whxyh/personal_vue

文章目录

    • 前言
    • 1、问题修改
    • 2、首页统计
      • 2.1、首页顶部统计
      • 2.2、发文数量图表
      • 2.3、文章分类占比
      • 2.4、在线用户
      • 2.5、获取公告
      • 2.6、词云
    • 完结

前言

人生若只如初见,何事秋风悲画扇。写到这里,真的要说再见了,这个是本博客教程的最后一篇,真的是最后一篇了!感谢各位小伙伴们的陪伴和支持,教程时间拉的很长,中间也想过放弃,但最终我还是坚持写完了本篇教程,也算是有始有终。本教程是全部免费分享给各位小伙伴和需要学习的同学们。

由于个人技术和时间有限,教程写的不是很好,但我初心是想写一个很基础的项目教程,稍微有点基础的都可以学会。在这期间也认识了很多的小伙伴,有些小伙伴用来当做自己的毕设和课设等,我免费提供了技术支持,不收费用的(在有时间的情况下),在这里非常感谢大家对我的认可和支持。

希望大家再未来的日子里,继续追逐自己的梦想,勇敢地面对生活中的挑战。

imagebc47649d26ad5970.png

1、问题修改

在我测试的时候,发现添加文章时,选择添加分类报错,大家可以先把自己的项目跑起来测试一下是否报错。

场景:在添加文章的时候,不要选择已有的分类,要新添加一个分类,然后点击发布文章就会报错了。

定位:定位到保存文章的方法saveCategory

当我们没有从分类表中查询到分类,返回的是一个null,然后接下来又对这个null进行赋值所以报错。我们还需要再新new一个对象来存放新添加的分类即可。具体代码如下:

private Category saveCategory(ArticleInsertBO bo) {
        if (StrUtil.isEmpty(bo.getCategoryName())) {
            return null;
        }
        Category cat = new Category();
        Category category = categoryService.getCategoryByName(bo.getCategoryName());
        if (category == null && !ArticleArtStatusEnum.DRAFT.getStatus().equals(bo.getArtStatus())) {
            cat.setCategoryName(bo.getCategoryName());
            categoryService.saveCategory(cat);
        }
        return cat;
}

然后重启项目,再测试一下没有报错了。

暂时就发现了这一个错误,后边如果有再进行补充。

2、首页统计

现在其他的功能都已经完成,还差首页的数据是在页面写死的,如何能让图表和我们系统联动呢,接下来我们一起来学习一下。

在我们对接图表的时候,我们首先要了解到图表的数据情况,横坐标代表什么,纵坐标代表什么,只有了解图表要展示什么数据我们才能编写接口,要返回什么数据给前端。接下来我会带大家分析一下几种图表的接口开发,这个在以后的工作中都会用到。

首先将项目前后端运行起来,进入页面首页,在最顶部有四个展示数据:文章数量、分类数量、标签数量、用户数量等,现在看到的数据都是假的,我们要将这四个换成我们系统的数据,当发布一篇文章之后,这里的文章数量要**+1**才是我们想要的。

接下来的开发流程是,先来写一个模块的接口,然对接前端,然后再写下一个模块的接口进行对接。

2.1、首页顶部统计

顶部的统计需要4个数据即可,也就是说我们只要返给前端四个字段就可以了。先来创建一个返回类,定义4个字段。

vo的包中新建一个StatisticsTopCountVO.java

package com.blog.personalblog.vo;

import lombok.Data;

/**
 * @author: SuperMan
 * @create: 2023-05-20
 **/

@Data
public class StatisticsTopCountVO {

    /**
     * 文章总数
     */
    private Integer articleCount;
    /**
     * 分类总数
     */
    private Integer categoryCount;
    /**
     * 用户总数
     */
    private Integer userCount;
    /**
     * 标签总数
     */
    private Integer tagCount;

}

接着在service包中新建一个统计的接口StatisticsService.java

package com.blog.personalblog.service;

import com.blog.personalblog.vo.StatisticsTopCountVO;


/**
 * @author: SuperMan
 * @create: 2023-05-20
 **/
public interface StatisticsService {

    /**
     * 首页顶部数据统计
     * @return
     */
    StatisticsTopCountVO getTopCount();

}

然后再来创建一个统计的实现类,这个大家都应该轻车熟路了,我下面就简单的介绍即可。

/**
 * @author: SuperMan
 * @create: 2023-05-20
 **/
@Service
public class StatisticsServiceImpl implements StatisticsService {
    @Resource
    private StatisticsMapper statisticsMapper;
    
    @Override
    public StatisticsTopCountVO getTopCount() {
        StatisticsTopCountVO topCount = statisticsMapper.getTopCount();
        return topCount;
    }
    
}

因为要查询数据库,所以还要创建一个StatisticsMapper.javaStatisticsMapper.xml

@Repository
public interface StatisticsMapper {

    StatisticsTopCountVO getTopCount();
}

xml:

注意:在xml中编写的sql,大家尽量现在自己的sql工具中运行一下看是否有错误,没错误的话在往xml中写,要不然xml中有错误不太好查找问题。

这里用到了多张表查询,一定要确保数据的准确。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.blog.personalblog.mapper.StatisticsMapper">

    <resultMap id="BaseResultMap" type="com.blog.personalblog.vo.StatisticsTopCountVO">
        <result column="article_count" property="articleCount" jdbcType="INTEGER" />
        <result column="category_count" property="categoryCount" jdbcType="INTEGER" />
        <result column="user_count" property="userCount" jdbcType="INTEGER" />
        <result column="tag_count" property="tagCount" jdbcType="INTEGER" />
    </resultMap>

    <select id="getTopCount" resultMap="BaseResultMap">
        SELECT
            (SELECT COUNT(*) FROM person_article) AS article_count,
            (SELECT COUNT(*) FROM person_category) AS category_count,
            (SELECT COUNT(*) FROM person_user) AS user_count,
            (SELECT COUNT(*) FROM person_tag) AS tag_count
    </select>

</mapper>

然后编写一个IndexController.javaapi接口。

@Api(tags = "首页统计")
@RestController
@RequestMapping("/index")
public class IndexController {
    @Resource
    private StatisticsService statisticsService;
    
    /**
     * 顶部统计查询
     * @return
     */
    @ApiOperation(value = "首页顶部统计查询")
    @PostMapping("/getTopCount")
    @OperationLogSys(desc = "首页顶部统计查询", operationType = OperationType.SELECT)
    public JsonResult<Object> getTopCount() {
        StatisticsTopCountVO topCount = statisticsService.getTopCount();
        return JsonResult.success(topCount);
    }
    
}

后端的接口已经完成,再重新运行一下项目。之后打开前端项目,对接接口。

先在/src/api目录下新建一个接口管理的文件:index.js

然后将后端的接口对接,注意没有请求的参数。直接地址和请求方式即可。

import request from '@/utils/request'

export function indexTopCount() {
    return request({
      url: '/index/getTopCount',
      method: 'post'
    })
}

然后在/views/dashboard/components目录下打开PanelGroup.vue。先将接口的方法引进来。

import { indexTopCount } from '@/api/index'

然后添加data()方法和created()方法。

data() {
    return {
      list: [],
      listLoading: true,
    }
  },

  created() {
    this.getTopCount()
  },

在methods方法中,添加一个getTopCount()方法。这里就拿到了后端接口返回的数据,list现在就有数据了,然后去将假数据换成list中的数据即可。

getTopCount() {
      this.listLoading = true
      indexTopCount().then(response => {
        this.list = response.data
        this.listLoading = false
      })
}

将原来的数据删除,然后在el-row标签中添加:data="list",然后用list.的方式就可以获取到数据了。

<template>
  <el-row :gutter="40" class="panel-group" :data="list">
    <el-col :xs="12" :sm="12" :lg="6" class="card-panel-col">
      <div class="card-panel">
        <div class="card-panel-icon-wrapper icon-people">
          <svg-icon icon-class="documentation" class-name="card-panel-icon" />
        </div>
        <div class="card-panel-description">
          <div class="card-panel-text">
            文章数量
          </div>
          <div style="font-size: 20px">
             {{list.articleCount}}
          </div>
         
        </div>
      </div>
    </el-col>
    <el-col :xs="12" :sm="12" :lg="6" class="card-panel-col">
      <div class="card-panel">
        <div class="card-panel-icon-wrapper icon-message">
          <svg-icon icon-class="component" class-name="card-panel-icon"/>
        </div>
        <div class="card-panel-description">
          <div class="card-panel-text">
            分类数量
          </div>
          <div style="font-size: 20px">
             {{list.categoryCount}}
          </div>
         
        </div>
      </div>
    </el-col>
    <el-col :xs="12" :sm="12" :lg="6" class="card-panel-col">
      <div class="card-panel">
        <div class="card-panel-icon-wrapper icon-money">
          <svg-icon icon-class="icon" class-name="card-panel-icon" />
        </div>
        <div class="card-panel-description">
          <div class="card-panel-text">
            标签数量
          </div>
          <div style="font-size: 20px">
             {{list.tagCount}}
          </div>
        </div>
      </div>
    </el-col>
    <el-col :xs="12" :sm="12" :lg="6" class="card-panel-col">
      <div class="card-panel">
        <div class="card-panel-icon-wrapper icon-shopping">
          <svg-icon icon-class="people" class-name="card-panel-icon"/>
        </div>
        <div class="card-panel-description">
          <div class="card-panel-text">
            用户数量
          </div>
          <div style="font-size: 20px">
             {{list.userCount}}
          </div>
        </div>
      </div>
    </el-col>
  </el-row>
</template>

然后我们来看一下页面。此时就有了数据,也确实是我们数据库的真实数据。

image1e4047c5659a731d.png

2.2、发文数量图表

接下来要完成图表数据的接口,其实不难的,我先来教大家方法,然后再编写。先来分析一下这个图表,横坐标是日期,纵坐标是数量,然后去前端看一下图表的假数据是写在哪的,会发现一共写了两个地方,xAxis是横坐标的数据,series是数量,而且都是数组。这时就要想,我们返回的数据是怎么样的格式呢,首先返回的肯定是一个List类型的集合,List中添加的是多个对象格式的数据,对象中有两个字段,一个是日期,另一个是这一天的文章数量。

知道了这些,我们先来写接口,还是在vo中创建一个类:StatisticsBaseCountVO.java

package com.blog.personalblog.vo;

import lombok.Data;

/**
 * @author: SuperMan
 * @create: 2023-05-20
 **/

@Data
public class StatisticsBaseCountVO {


    /**
     * 时间,例如:02-01
     */
    private String date;

    /**
     * 条数
     */
    private Long count;

    public StatisticsBaseCountVO() {

    }

    public StatisticsBaseCountVO(String date, Long count) {
        this.date = date;
        this.count = count;
    }
}

然后写接口,还是在StatisticsService中,包括接下来的所有统计的接口都会写在这个接口里,我下面就不再写了。

	/**
     * 文章近一周统计数据
     * @return
     */
    List<StatisticsBaseCountVO> getArticleCount();

接下来写实现类,这个可能稍微有点复杂,我还是结合代码进行分析。

首先先获取到文章数据,我这里全部查出来了,因为量不大,还是可以的。然后再根据创建时间进行过滤,采用的是Java8的新特性,大家可以去学习学习:https://blog.csdn.net/m0_51014049/article/details/129600237

LocalDate today = LocalDate.now();
//过滤近7天数据
List<Article> articles = articleService.getAll();
List<Article> articlesInLastWeek = articles.stream()
                .filter(article -> article.getCreateTime().toLocalDate().isAfter(today.minusDays(7)))
                .collect(Collectors.toList());

此时拿到了近七天的文章数据,然后根据时间进行分组统计。返回的是一个Map格式的,其中key是时间,value是数量。

Map<LocalDate, Long> map = articlesInLastWeek.stream()
                .collect(Collectors.groupingBy(article -> article.getCreateTime().toLocalDate(), Collectors.counting()));

然后根据map,进行前端返回格式的组装,有的日期一天都没有发文,则默认给它赋值为0,要确保7天的数据都有。用到了putIfAbsent() 方法会先判断指定的键(key)是否存在,不存在则将键/值对插入到 HashMap 中。

DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("MM-dd");
for (int i = 0; i < 7; i++) {
            LocalDate date = today.minusDays(i);
            map.putIfAbsent(date, 0L);
            StatisticsBaseCountVO articleCount = new StatisticsBaseCountVO(date.format(dateFormat), map.get(date));
            list.add(articleCount);
        }

然后在排序一下

list = list.stream().sorted(Comparator.comparing(StatisticsBaseCountVO::getDate)).collect(Collectors.toList());

完整代码:

DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("MM-dd");  
    @Override
    public List<StatisticsBaseCountVO> getArticleCount() {
        List<StatisticsBaseCountVO> list = new ArrayList<>();
        LocalDate today = LocalDate.now();

        //过滤近7天数据
        List<Article> articles = articleService.getAll();
        List<Article> articlesInLastWeek = articles.stream()
                .filter(article -> article.getCreateTime().toLocalDate().isAfter(today.minusDays(7)))
                .collect(Collectors.toList());

        Map<LocalDate, Long> map = articlesInLastWeek.stream()
                .collect(Collectors.groupingBy(article -> article.getCreateTime().toLocalDate(), Collectors.counting()));

        for (int i = 0; i < 7; i++) {
            LocalDate date = today.minusDays(i);
            map.putIfAbsent(date, 0L);
            StatisticsBaseCountVO articleCount = new StatisticsBaseCountVO(date.format(dateFormat), map.get(date));
            list.add(articleCount);
        }
        //排序
        list = list.stream().sorted(Comparator.comparing(StatisticsBaseCountVO::getDate)).collect(Collectors.toList());
        return list;
    }

这个获取全部的的文章的接口要写一下,之前的是分页的,这个不分页,我将代码展示一下就不多介绍了,具体可以看我源码。

List<Article> getAll();    

@Override
public List<Article> getAll() {
    List<Article> all = articleMapper.findAll();
    return all;
}

接着去controller中添加一个接口。

    /**
     * 近一周发文的数量
     * @return
     */
    @ApiOperation(value = "近一周发文的数量")
    @PostMapping("/getWeekNum")
    @OperationLogSys(desc = "近一周发文的数量", operationType = OperationType.SELECT)
    public JsonResult<Object> getWeekNum() {
        List<StatisticsBaseCountVO> list = statisticsService.getArticleCount();
        return JsonResult.success(list);
    }

打开前端代码,还是在index.js中先添加接口。

export function getWeekArticleCount() {
    return request({
      url: '/index/getWeekNum',
      method: 'post'
    })
}

然后打开BarChart.vue文件,引入接口

import { getWeekArticleCount } from '@/api/index'

在data方法中定义一个list数组

data() {
    return {
      chart: null,
      list: [],
    }
  },

接着改造一下mounted()这个声明周期的构造函数。获取后端返回的数据,赋值给list。

 mounted() {
    getWeekArticleCount().then(res => {
        if(res.code === 200) {
          this.list = res.data;
          this.initChart();
        } else {
            this.$message({
              type: 'error',
              message: res.msg
            });
        }
    })
  },

然后找到xAxis数组,将里面的data日期数据换成我们后端返回的日志。

data: this.list.map(item => item.date),

如果你不知道map是什么,为什么这样写就可以,你可以去搜一下list的map()用法,我这里不再说明。

同样下面的series数组中的data数据取list中的count数据。

series: [
          {
            name: '文章数',
            type: 'bar',
            barWidth: '60%',
            data: this.list.map(item => item.count)
          }
        ]

修改完成之后,打开页面查看是否有数据,如果没有的话可以先发布一篇文章试一下。

2.3、文章分类占比

从页面上可以看出,这个是一个饼状图,代表着每个数据的占比,我这里写的是文章标签的占比,这一个标签有多少篇文章,后续可根据自己的需求进行修改,这里教给大家的是开发的流程。从饼状图上可以看出,一共需要两个数据,一个是标签的名称,另一个是对应标签的文章数。那么我们的接口只要将这两个数据返回即可,注意:是多条数据,应该返回一个List集合

还是和之前一样,后端写接口、实现,我这里不再多写,我只将代码展示一下,具体的可以去gitee上下载代码看。

新建StatisticsTagCountVO.java

package com.blog.personalblog.vo;

import lombok.Data;

/**
 * @author: SuperMan
 * @create: 2023-05-20
 **/
@Data
public class StatisticsTagCountVO {

    /**
     * 标签名称
     */
    private String tagName;

    /**
     * 标签总数
     */
    private Integer tagCount;
}

接口

    /**
     * 获取标签数据
     * @return
     */
    List<StatisticsTagCountVO> getTagCount();

实现类

    @Override
    public List<StatisticsTagCountVO> getTagCount() {
        List<StatisticsTagCountVO> tagCount = statisticsMapper.getTagCount();
        return tagCount;
    }

Mapper

List<StatisticsTagCountVO> getTagCount();

xml

<select id="getTagCount" resultMap="BaseResultTagMap">
        SELECT s.tag_name, COUNT(*) AS tag_count FROM person_article_tag a
            left join person_tag s on a.tag_id = s.id
        GROUP BY tag_id
</select>

controller

    /**
     * 获取标签数据
     * @return
     */
    @ApiOperation(value = "获取标签数据")
    @PostMapping("/getTagCount")
    @OperationLogSys(desc = "获取标签数据", operationType = OperationType.SELECT)
    public JsonResult<Object> getTagCount() {
        List<StatisticsTagCountVO> tagCount = statisticsService.getTagCount();
        return JsonResult.success(tagCount);
    }

前端页面,在index.js中添加接口

export function getTagCount() {
  return request({
    url: '/index/getTagCount',
    method: 'post'
  })
}

PieChart.vue中引入

import { getTagCount } from '@/api/index'

然后再data中添加一个list数组

data() {
    return {
      chart: null,
      list: []
    }
  },

获取后端数据,赋值给list。

这里要注意一下,原来的假数据是这种格式:{ value: 1048, name: 'Search Engine' },key和value对应,我们要将拿到的数据先进行改造一下。for循环了一下数据,然后定义了一个数组,然后在for循环中定义了一个Object对象,将对应的数据给Object对象,然后再添加到数组中。

 mounted() {
    getTagCount().then(res => {
        if(res.code === 200) {
          var getData = [];
          for(let i = 0; i < res.data.length; i++) {
            var obj = new Object();
            obj.name = res.data[i].tagName;
            obj.value = res.data[i].tagCount;
            getData[i] = obj;
          }
          this.list = getData;
          this.initChart();
        } else {
            this.$message({
              type: 'error',
              message: res.msg
            });
        }
    })
  },

将假数据替换成list

series: [
            {
            type: 'pie',
            radius: '50%',
            data: this.list,
            emphasis: {
                itemStyle: {
                shadowBlur: 10,
                shadowOffsetX: 0,
                shadowColor: 'rgba(0, 0, 0, 0.5)'
                }
            }
            }
        ]

再去看页面就会有我们接口查出的数据绘制的图表了,如果没有图表生成,先看一下接口有没有数据。或者控制台看一下有没有报错信息等。

image1e76603303cadf59.png

2.4、在线用户

这个图表是一个折线图,横坐标现在修改成每分钟数,纵坐标为在线人数。

开发流程:在线统计我们只做一个简单的统计流程,在我们登录的接口中,当登录完成之后,将数据放到统计的缓存中,然后定时去或者这个缓存的数据。再将分钟划分,拼装成返回给前端的数据。

先在统计中定义两个接口。一个登出,一个登录。

void login(String username, Long date);

void logout(String username, Long date);

然后在实现类中先来定一个全局的缓存,再实现这两个方法。

private Map<String, Long> users = new HashMap<>();

	@Override
    public void login(String username, Long date) {
        users.put(username, date);
    }

    @Override
    public void logout(String username, Long date) {
        users.remove(username, date);
    }

打开登录的接口,在登录完成之后,调用登录的接口。

 @ApiOperation(value = "登录")
    @PostMapping("/login")
    @OperationLogSys(desc = "登录", operationType = OperationType.LOGIN)
    public JsonResult<Object> login(@RequestBody LoginModel loginModel){
        logger.info("{} 在请求登录! ", loginModel.getUsername());
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken(loginModel.getUsername(), loginModel.getPassword(), false);
        try {
            subject.login(token);
            Map<String, Object> ret = new HashedMap();
            ret.put("token", subject.getSession().getId());
            logger.info("{} login success", loginModel.getUsername());
            getLoginInfoLog(loginModel, 0);
            //修改上个登录的时间
            User user = userService.getUserByUserName(loginModel.getUsername());
            userService.updateLoginTime(user.getId());
            //在线人数
            statisticsService.login(user.getUserName(), System.currentTimeMillis());
            。。。。。。

退出的接口:

    @RequestMapping("/logout")
    public JsonResult logout(){
        User user=(User) SecurityUtils.getSubject().getPrincipal();
        Subject subject = SecurityUtils.getSubject();
        if(subject.isAuthenticated()) {
            subject.logout();
        }
        //在线人数
        statisticsService.logout(user.getUserName(), System.currentTimeMillis());
        return JsonResult.success("退出登录");
    }

这样统计的map就维护好了,接下来要统计每分钟的用户数,使用了@Scheduled定时执行该方法。

	/**
     * key:时间,HH:mm
     * value: 人数
     */
    private Map<String, Long> countUser = new HashMap<>();

    SimpleDateFormat sdf = new SimpleDateFormat("HH:mm");

    @Scheduled(cron = "0 */1 * * * ?")
    public void getOnlineUsers() {
        long currentTime = System.currentTimeMillis();
        Long count = 0L;
        for (long loginTime : users.values()) {
            if (currentTime - loginTime <= 60000) {
                count++;
            }
        }
        Date date= new Date(currentTime);
        countUser.putIfAbsent(sdf.format(date), count);
    }

此时的定时还没有效果,要在启动类的方法上加上@EnableScheduling才可以。

在service中写一个在线用户统计的接口。

    /**
     * 获取在线人数
     * @return
     */
    List<StatisticsBaseCountVO> getOnline();

实现类:

实现类和文章统计的的日期划分差不多,只是这里划分为分钟。大家先自己研究一下代码吧,应该可以看懂的。

    @Override
    public List<StatisticsBaseCountVO> getOnline() {
        Map<String, StatisticsBaseCountVO> map = new HashMap<>();

        //分钟划分
        Date date = new Date();
        List<String> res = new ArrayList<>();
        if (date != null) {
            Calendar ca = Calendar.getInstance();
            ca.setTime(date);
            for (int i = 0; i < 60; i++) {
                ca.add(Calendar.MINUTE, -1);
                res.add(sdf.format(ca.getTime()));
            }
        }
        countUser.forEach((key, v) -> {
            StatisticsBaseCountVO baseCount = new StatisticsBaseCountVO();
            baseCount.setDate(key);
            baseCount.setCount(v);
            map.put(key, baseCount);
        });

        res.forEach(m -> {
            map.computeIfAbsent(m, k -> {
                StatisticsBaseCountVO count = new StatisticsBaseCountVO(k, 0L);
                return count;
            });
        });
        List<StatisticsBaseCountVO> sort = CollUtil.sort(map.values(), Comparator.comparing(StatisticsBaseCountVO::getDate));
        return sort;
    }

然后写一个controller接口。

    /**
     * 在线人数
     * @return
     */
    @ApiOperation(value = "在线人数")
    @PostMapping("/getOnline")
    @OperationLogSys(desc = "在线人数", operationType = OperationType.SELECT)
    public JsonResult<Object> getOnline() {
        List<StatisticsBaseCountVO> online = statisticsService.getOnline();
        return JsonResult.success(online);
    }

前端的话和之前的文章的图表差不一样的,不会的可以去看我的项目代码,我这里只贴代码了。

export function getOnlineCount() {
  return request({
    url: '/index/getOnline',
    method: 'post'
  })
}
data() {
    return {
      chart: null,
      list: [],
    }
  },

  mounted() {
      getOnlineCount().then(res => {
        if(res.code === 200) {
          this.list = res.data;
          this.initChart();
        } else {
            this.$message({
              type: 'error',
              message: res.msg
            });
        }
      })
  },

绘制:

xAxis: {
            type: 'category',
            data: this.list.map(item => item.date)
        },
            
series: [
            {
            data: this.list.map(item => item.count),
            type: 'line',
            smooth: true
            }
        ]

2.5、获取公告

这个比较简单了,我们先来写一个controller接口,再来写实现的方法等。

    /**
     * 获取最新前5条公告
     * @return
     */
    @ApiOperation(value = "获取最新前5条公告")
    @PostMapping("/getNoticeList")
    @OperationLogSys(desc = "获取最新前5条公告", operationType = OperationType.SELECT)
    public JsonResult<Object> getNoticeList() {
        List<Notice> list = noticeService.getNoticeTopFive();
        return JsonResult.success(list);
    }

在公告的service中新增一个查询的方法。

    /**
     * 获取前5条公告
     * @return
     */
    List<Notice> getNoticeTopFive();

实现类:

    @Override
    public List<Notice> getNoticeTopFive() {
        List<Notice> noticeList = noticeMapper.getNoticeTopFive();
        return noticeList;
    }

mapper:

List<Notice> getNoticeTopFive();

xml:

   <select id="getNoticeTopFive" resultMap="BaseResultMap">
        SELECT * FROM person_notice ORDER BY create_time DESC LIMIT 5;
    </select>

前端对接也比较简单的。

export function getNoticeList() {
  return request({
    url: '/index/getNoticeList',
    method: 'post'
  })
}

在created的方法中添加查询的方法

import { getNoticeList } from '@/api/index'

created() {

    getNoticeList().then(res => {
        if(res.code === 200) {
          this.list = res.data;
        } else {
            this.$message({
              type: 'error',
              message: res.msg
            });
        }
    })
    .......

将之前的假数据删除掉,然后改成以下的格式。

        <el-collapse v-model="activeName" accordion>
            <el-collapse-item v-for="(item,index) in list" :key="index">
              <template slot="title">
                {{item.noticeTitle}}
                <i class="ssf ssf-colse" @click.stop="close(item,index)"></i>    
              </template>
              <div>{{item.noticeContent}}</div>
            </el-collapse-item>
          </el-collapse>

再来看一下页面就会显示了。

2.6、词云

就剩下最后一个了,这个词云我是用的是标签的数据,在前端的页面中看到,每个属性都会有不同的背景颜色、字体颜色等,所以说我们后还要生成颜色返回给前端。

先来定一个对象:StatisticsWordCloudVO.java

package com.blog.personalblog.vo;

import lombok.Data;

/**
 * @author: SuperMan
 * @create: 2023-05-20
 **/

@Data
public class StatisticsWordCloudVO {

    /**
     * 标签名称
     */
    private String tagName;
    /**
     * 背景颜色
     */
    private String bgColor;
    /**
     * 颜色
     */
    private String color;
    /**
     * 数值
     */
    private String value;

}

接口:

/**
 * 获取词云数据
 * @return
 */
@ApiOperation(value = "获取词云数据")
@PostMapping("/getWordCloud")
@OperationLogSys(desc = "获取词云数据", operationType = OperationType.SELECT)
public JsonResult<Object> getWordCloud() {
    List<StatisticsWordCloudVO> wordCloud = statisticsService.getWordCloud();
    return JsonResult.success(wordCloud);
}

定义service接口:

List<StatisticsWordCloudVO> getWordCloud();

实现类:

@Override
    public List<StatisticsWordCloudVO> getWordCloud() {
        List<StatisticsWordCloudVO> list = new ArrayList<>();
        //获取全部标签
        List<Tag> tags = tagService.getTagsByTagName(new TagBO());

        tags.forEach(t -> {
            int n = ((int) (Math.random() * (100 - 0))) + 0;
            StatisticsWordCloudVO cloud = new StatisticsWordCloudVO();
            Random rng = new Random();
            int red = rng.nextInt(256);
            int green = rng.nextInt(256);
            int blue = rng.nextInt(256);
            String colorString = String.format("rgb(%d, %d, %d, %.2f)", red, green, blue, 0.12f);

            cloud.setBgColor(colorString);
            cloud.setTagName(t.getTagName());
            String hexColor = String.format("#%02X%02X%02X", red, green, blue);
            cloud.setColor(hexColor);
            cloud.setValue(String.valueOf(n));
            list.add(cloud);
        });

        return list;
    }

这里使用了String.format进行拼装成rgb,这个词云以后用的比较少,大家先了解一下即可。

前端还是和之前一样,引入接口地址。

export function getWordCloud() {
  return request({
    url: '/index/getWordCloud',
    method: 'post'
  })
}

获取接口数据:

import { getWordCloud } from '@/api/index'

mounted () {
      getWordCloud().then(res => {
          if(res.code === 200) {
            this.dataList = res.data;
          } else {
              this.$message({
                type: 'error',
                 message: res.msg
              });
          }
      })

页面展示也要改一下

<div class="cloud-box">
      <span
        v-for="(item, index) in dataList"
        :key="index"
        @click="getDataInfo(item)"
        :style="{color:item.color,background:item.bgColor}"
      >
        {{ item.tagName }}
      </span>
</div>

然后看一下页面是不是我们后端的数据展示。

完结

到这里真的要再见了,博客项目的全部教程都已经完结了,一共24篇文章,代码我已经全部上传到仓库中,在文章的最下面会有地址。一路走来坚持到现在感觉也挺有成就感的,能为大家带来一些技术上的入门和学习,已经感到很好了。感谢大家的一路陪伴,再见!

预告:新的项目教程已经在规划,会增加很多的知识点和难度,自动化部署、短信发送、权限管理等操作。欢迎大家来订阅。

imageed7198b6cb1d740d.png

代码地址

Gitee

​ 后端地址:https://gitee.com/whxyh/personal_blog

​ 前端地址:https://gitee.com/whxyh/personal_vue

GitHub

​ 后端地址:https://github.com/dawandou/personal_blog_pro

​ 前端地址:https://github.com/dawandou/personal_blog_vue

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/590447.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

设计模式六大原则的理解

本文参考&#xff1a; 设计模式简介 | 菜鸟教程 (runoob.com) 六大设计原则之依赖倒置原则&#xff08;DIP&#xff09; - 简书 (jianshu.com) 设计模式的六大原则有&#xff1a; 1、开闭原则&#xff08;Open Close Principle&#xff09; 开闭原则的意思是&#xff1a;对扩…

设计模式之~桥接模式

桥接模式&#xff1a; 将抽象部分与它的实现部分分离&#xff0c;使他们都可以独立地变化。这种类型的设计模式属于结构型模式&#xff0c;它通过提供抽象化和实现化之间的桥接结构&#xff0c;来实现二者的解耦。 什么叫抽象与它的实现分离&#xff0c;这并不是说&#xff0c;…

图解系列 图解Spring Boot 最大连接数及最大并发数

文章目录 概序架构图TCP的3次握手4次挥手时序图核心参数AcceptCountMaxConnectionsMinSpareThread/MaxThreadMaxKeepAliveRequestsConnectionTimeoutKeepAliveTimeout 内部线程AcceptorPollerTomcatThreadPoolExecutor 测试参考 每个Spring Boot版本和内置容器不同&#xff0c;…

树状数组学习总结

今天本初中生蒟蒻学习了一下 树状数组 \color{red}{树状数组} 树状数组&#xff0c;总结一下~~~ 树状数组的实现 功能简介 快速求前缀和&#xff08; O ( l o g 2 n ) \color{purple}{O(log_2n)} O(log2​n)&#xff09;修改某一个数&#xff08; O ( l o g 2 n ) \color{gr…

SpringBoot+原生awt,实现花花绿绿的图形验证码

图形验证码是用于验证用户身份的一种方式&#xff0c;通常在网站注册、登录或进行某些敏感操作时会使用。它通过展示一个包含随机字符或数字的图形&#xff0c;要求用户输入相应的字符或数字来证明其为真人而非机器人。图形验证码能有效地防止机器人攻击和恶意注册行为&#xf…

Excel·VBA自动生成日记账的对方科目

如图&#xff1a;根据日记账/序时账的日期、凭证号为一组&#xff0c;按借贷方向生成相反的科目&#xff0c;并写入H列。可能存在一对一、一对多、多对多等情况的账目 目录 数组法遍历、判断、写入测试结果 多对多问题处理测试结果 数组法遍历、判断、写入 适用日期凭证号连续…

HTTPS的加密流程——巨详细!

文章目录 前言HTTPS的工作过程引入对称加密引入非对称加密引入证书完整的加密流程总结 前言 HTTPS 也是一个应用层协议. 是在 HTTP 协议的基础上引入了一个加密层. HTTP 协议内容都是按照文本的方式明文传输的. 这就导致在传输过程中出现一些被篡改的情况. 比如&#xff1a;臭…

民宿预订系统的设计与实现(ASP.NET,SQLServer)

这个民宿预订系统是由第三方的运营公司来运营&#xff0c;他提供了一个民宿和客户都使用的一个信息平台&#xff0c;民宿注册之后把自己的民宿信息发布到网站平台上&#xff0c;然后发布自己的房间信息&#xff0c;打折信息等供客户查看和选择。客户可以在网站平台上查看民宿信…

深度学习:大模型的正则化

l1l2正则和dropout正则化[https://youzipi.blog.csdn.net/article/details/75307522] LN和BN归一化 [深度学习:批归一化Batch Normalization] 主流大模型使用的Normalization主要有三类,分别是Layer Norm,RMS Norm,以及Deep Norm。 Post-Norm和Pre-Norm 根据Normalizat…

网工内推 | 快手、瑞芯微招运维,思科、红帽认证优先

01 快手 招聘岗位&#xff1a;IT系统运维 职责描述&#xff1a; 1、负责IT基础架构运维体系的建设和优化改进&#xff1b; 2、负责IT核心基础服务&#xff08;如DNS、负载均衡、容器&#xff09;的架构设计、平台建设和运维&#xff1b; 3、负责IT内部日志系统、监控系统、报警…

SpringCloud微服务框架(通俗易懂,一秒上手)

&#x1f381;&#x1f381;资源&#xff1a;https://pan.baidu.com/s/1zRmwSvSvoDkWh0-MynwERA&pwd1234 SpringCloud微服务框架 &#xff08;一&#xff09;认识微服务服务架构演变SpringCloud &#xff08;二&#xff09;微服务拆分案例服务拆分服务间调用 &#xff08;三…

ROS:订阅者Subscriber的编程实现(C++)

目录 一、话题模型二、创建功能包三、创建Subscriber代码四、编译代码五、运行 一、话题模型 图中&#xff0c;我们使用ROS Master管理节点。 有两个主要节点&#xff1a; Publisher&#xff0c;名为Turtle Velocity&#xff08;即海龟的速度&#xff09; Subscriber&#xff0…

Rocketmq面试(一) Rocketmq同一个消费组订阅不同的Tag,会有什么问题?

先说结果&#xff1a;会造成数据丢失 再说依据&#xff1a; RocketMQ要求同一个消费者组内的消费者必须订阅关系一致&#xff0c;如果订阅关系不一致会出现消息丢失的问题。 官网入口&#xff1a;订阅关系一致 | RocketMQ 不想看官网的&#xff0c;直接看结论 什么叫订阅关…

复杂SQL实践-MYSQL

MySQL 8.0窗口函数 MySQL从8.0版本开始支持窗口函数。 窗口函数总体上可以分为序号函数, 分布函数, 前后函数, 首尾函数和其他函数。 描述 题目&#xff1a;现在运营想要查看用户在某天刷题后第二天还会再来刷题的平均概率。请你取出相应数据。 示例1 drop table if exist…

对远程http服务的拨测体验

背景&#xff1a; 过程是这样的&#xff0c;需要与合作方数据进行交互&#xff08;肯定是不允许直接连对方数据源的&#xff09;&#xff0c;对方提供了两台server&#xff0c;后端同事在server上面作了proxy搭建了桥接的应用&#xff08;两台server没有公网ip&#xff0c;通过…

Eclipse 教程Ⅹ

本次内容会涉及到Eclipse 重构菜单、Eclipse 添加书签和Eclipse 任务管理&#xff0c;老规矩&#xff0c;直接开始吧&#xff01; Eclipse 重构菜单 使用Eclipse重构 在项目开发中我们经常需要修改类名&#xff0c;但如果其他类依赖该类时&#xff0c;我们就需要花很多时间去…

机器学习模型的生命周期

动动发财的小手&#xff0c;点个赞吧&#xff01; 您的模型如何变化&#xff1f;Source[1] 诞生 当我们构建、训练、拟合或估计我们的模型时&#xff0c;这些数字工具就诞生了。这个阶段几乎从拥有分析目标、数据、计算机、算法以及数据科学家现在已经非常了解的其他一切开始。…

Linux [权限]

Linux 权限 Linux用户分类切换成root方法例子 切换成普通用户方法例子 短暂提权 什么是权限理论知识展示区域 修改权限(1)修改文件属性1. 采用 w/r/x的形式2. 采用八进制的形式 (2)修改身份1. 修改拥有者2. 修改所属组3. 修改拥有者 && 所属组 问题区问题1问题2问题3 L…

实在智能携手各高校打造高端数字化技能教育平台

百年大计&#xff0c;教育为本。2021年在《教育部办公厅关于印发高等职业教育专科英语、信息技术课程标准&#xff09;的通知中把机器人流程自动化列入专科信息技术课程学习计划之中&#xff0c;进一步明确职业教育中数字化人才发展方向。 一、为什么要大力培养数字化人才&…

毕业5年的同学突然告诉我,他已经是年薪30W的自动化测试工程师,我愣住了...

作为一名程序员&#xff0c;都会对自己未来的职业发展而焦虑。一方面是因为IT作为知识密集型的行业&#xff0c;知识体系复杂且知识更新速度非常快&#xff0c;“一日不学就会落后”。 另外一方面&#xff0c;IT又是劳动密集型的行业&#xff0c;不仅业人员多&#xff0c;而且个…