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

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

作者简介:码上言



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



专栏内容:个人博客系统



我的文档网站:http://xyhwh-nav.cn/
后端代码gitee地址:https://gitee.com/whxyh/personal_blog

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

项目部署视频

https://www.bilibili.com/video/BV1sg4y1A7Kv/?vd_source=dc7bf298d3c608d281c16239b3f5167b

文章目录

前言

人生若只如初见,何事秋风悲画扇。写到这里,真的要说再见了,这个是本博客教程的最后一篇,真的是最后一篇了!感谢各位小伙伴们的陪伴和支持,教程时间拉的很长,中间也想过放弃,但最终我还是坚持写完了本篇教程,也算是有始有终。本教程是全部免费分享给各位小伙伴和需要学习的同学们。由于个人技术和时间有限,教程写的不是很好,但我初心是想写一个很基础的项目教程,稍微有点基础的都可以学会。在这期间也认识了很多的小伙伴,有些小伙伴用来当做自己的毕设和课设等,我免费提供了技术支持,不收费用的(在有时间的情况下),在这里非常感谢大家对我的认可和支持。


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

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>

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

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)'
                }
            }
            }
        ]

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

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篇文章,代码我已经全部上传到仓库中,在文章的最下面会有地址。一路走来坚持到现在感觉也挺有成就感的,能为大家带来一些技术上的入门和学习,已经感到很好了。感谢大家的一路陪伴,再见!


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

代码地址:


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


目录
相关文章
|
1月前
|
XML Java 数据格式
SpringBoot入门(8) - 开发中还有哪些常用注解
SpringBoot入门(8) - 开发中还有哪些常用注解
52 0
|
1月前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个具有动态路由和菜单功能的前后端分离应用。
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个具有动态路由和菜单功能的前后端分离应用。首先,创建并配置 Spring Boot 项目,实现后端 API;然后,使用 Ant Design Pro Vue 创建前端项目,配置动态路由和菜单。通过具体案例,展示了如何快速搭建高效、易维护的项目框架。
119 62
|
14天前
|
存储 JavaScript 前端开发
基于 SpringBoot 和 Vue 开发校园点餐订餐外卖跑腿Java源码
一个非常实用的校园外卖系统,基于 SpringBoot 和 Vue 的开发。这一系统源于黑马的外卖案例项目 经过站长的进一步改进和优化,提供了更丰富的功能和更高的可用性。 这个项目的架构设计非常有趣。虽然它采用了SpringBoot和Vue的组合,但并不是一个完全分离的项目。 前端视图通过JS的方式引入了Vue和Element UI,既能利用Vue的快速开发优势,
79 13
|
1月前
|
JavaScript 前端开发 Java
springboot解决js前端跨域问题,javascript跨域问题解决
本文介绍了如何在Spring Boot项目中编写Filter过滤器以处理跨域问题,并通过一个示例展示了使用JavaScript进行跨域请求的方法。首先,在Spring Boot应用中添加一个实现了`Filter`接口的类,设置响应头允许所有来源的跨域请求。接着,通过一个简单的HTML页面和jQuery发送AJAX请求到指定URL,验证跨域请求是否成功。文中还提供了请求成功的响应数据样例及请求效果截图。
springboot解决js前端跨域问题,javascript跨域问题解决
|
22天前
|
JavaScript 安全 Java
java版药品不良反应智能监测系统源码,采用SpringBoot、Vue、MySQL技术开发
基于B/S架构,采用Java、SpringBoot、Vue、MySQL等技术自主研发的ADR智能监测系统,适用于三甲医院,支持二次开发。该系统能自动监测全院患者药物不良反应,通过移动端和PC端实时反馈,提升用药安全。系统涵盖规则管理、监测报告、系统管理三大模块,确保精准、高效地处理ADR事件。
|
1月前
|
XML Java 数据格式
SpringBoot入门(8) - 开发中还有哪些常用注解
SpringBoot入门(8) - 开发中还有哪些常用注解
43 2
|
2月前
|
JavaScript 前端开发 API
Vue.js:现代前端开发的强大框架
【10月更文挑战第11天】Vue.js:现代前端开发的强大框架
89 41
|
1月前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个前后端分离的应用框架,实现动态路由和菜单功能
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个前后端分离的应用框架,实现动态路由和菜单功能。首先,确保开发环境已安装必要的工具,然后创建并配置 Spring Boot 项目,包括添加依赖和配置 Spring Security。接着,创建后端 API 和前端项目,配置动态路由和菜单。最后,运行项目并分享实践心得,帮助开发者提高开发效率和应用的可维护性。
102 2
|
1月前
|
JavaScript Java 项目管理
Java毕设学习 基于SpringBoot + Vue 的医院管理系统 持续给大家寻找Java毕设学习项目(附源码)
基于SpringBoot + Vue的医院管理系统,涵盖医院、患者、挂号、药物、检查、病床、排班管理和数据分析等功能。开发工具为IDEA和HBuilder X,环境需配置jdk8、Node.js14、MySQL8。文末提供源码下载链接。
|
1月前
|
JavaScript NoSQL Java
CC-ADMIN后台简介一个基于 Spring Boot 2.1.3 、SpringBootMybatis plus、JWT、Shiro、Redis、Vue quasar 的前后端分离的后台管理系统
CC-ADMIN后台简介一个基于 Spring Boot 2.1.3 、SpringBootMybatis plus、JWT、Shiro、Redis、Vue quasar 的前后端分离的后台管理系统
49 0