作者简介:码上言

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

专栏内容:零基础学Java、个人博客系统

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

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

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

文章目录

  • 前言
  • 预告
    • 1、项目完善
      • 1.1、文章创建完后在编辑标签没有展示
      • 1.2、文章字数统计实现
      • 1.3、发布文章新建分类修改
      • 1.4、邮件发送配置修改
      • 1.5、登录页改造升级
    • 2、首页功能前端开发
      • 2.1、顶部导航
      • 2.2、ECharts入门
      • 2.3、数据图表
      • 2.4、添加日历
      • 2.5、词云
      • 2.6、公告

前言

这一篇将是我们项目开发的最后一篇文章了,到这里该和大家说再见了,这个项目从开始写到现在刚好一年了,有时间就写写,中间断了好几个月的时间,看到好多人说对他们很有用,我写的也就变成了有意义的事情,希望大家都不忘初心,牢记使命,认真的学习技术和好好地生活。

正如士兵突击中的许三多说的,好好活,就是做有意义的事,做有意义的事情,就是好好活着。

预告

这里给大家预告一下,这个教程已经接近尾声了,看到大家给我的好多反馈,说的学到了很多东西,我感觉到十分的欣慰,感觉自己的付出没有白费,有的小伙伴说比某些机构的课程学的还多,我感觉只要好好地学习,都是可以的,哪怕你只是拿这个项目去最毕设或者其他的之类的,反正技多不压身,这个也涵盖了好多的东西,基本的入门也可以了。

我在想要不要再搞一个大一点的项目,将知识点再扩大一些,比如现在没有权限的操作、我们登录再加上短信验证码、文件的上传和解析、redis的实际运用等操作、自动化部署代码、原型设计、日志记录、小程序学习等新的技术,会更加完善做项目的流程和规范,基本上达到全栈的技术

我再考虑是不是要开通付费的专栏,大家可以根据自身的需求来学习,感觉自己需要学习就来学习,不需要看这个教程可以了就不要订阅。我感觉肯定会比花上万的去培训要实在的多,大家自己衡量。

欢迎大家给我提改进意见或者要加入什么技术,我尽量用项目来整合这些技术加入实际的应用。感谢各位!

1、项目完善

1.1、文章创建完后在编辑标签没有展示

这是上一篇的bug,我在测试文章添加后,然后再点击编辑后,发现标签的值并没有,我查看了接口返回的数据为空,发现是后端添加文章存入缓存的问题,这里修改一下,只要加一行代码即可,大家可以提前想一下。

先分析一下我们调用的方法findById。

 @Overridepublic ArticleVO findById(Integer articleId) {Article article = articleMap.get(articleId);。。。。。。

只看这一句即可,文章的数据来自map中,然而再添加文章的时候,map只存的是页面传来的数据,并没有将标签的数据给map,所以查出来的话肯定没有标签数据。

可以在文章添加的实现类中直接加上一下这行代码,在我们添加文章之后,在调用init重新加载一下缓存里的数据

this.init();

完整代码:

@Overridepublic void insertOrUpdateArticle(ArticleInsertBO bo) {//分类添加Category category = saveCategory(bo);Article article = BeanUtil.copyProperties(bo, Article.class);if (category != null) {article.setCategoryId(category.getCategoryId());}String username = (String) SecurityUtils.getSubject().getPrincipal();User user = userService.getUserByUserName(username);article.setUserId(user.getId());article.setAuthor(user.getUserName());article.setViews(0L);article.setTotalWords(WordCountUtil.wordCount(bo.getContent()));if (bo.getId() != null) {articleMapper.updateArticle(article);} else {articleMapper.createArticle(article);}articleMap.put(article.getId(), article);//添加文章标签saveTags(bo, article.getId());this.init();//添加文章发送邮箱提醒try {String content = "【{0}】您好:\n" +"您已成功发布了标题为: {1} 的文章 \n" +"请注意查收!\n";MailInfo build = MailInfo.builder().receiveMail(user.getEmail()).content(MessageFormat.format(content, user.getUserName(), article.getTitle())).title("文章发布").build();SendMailConfig.sendMail(build);} catch (Exception e) {log.error("邮件发送失败{}", e.getMessage());}}

1.2、文章字数统计实现

我们在文章的数据表中预留了一个文章的字数,一开始的时候我直接赋值的是0,现在我们要把字数统计给加上,所以需要写一个字数统计的工具类。

package com.blog.personalblog.util;/*** @author: SuperMan* @create: 2022-10-14**/
public class WordCountUtil {/*** 统计字数, 空格不统计* @param string* @return*/public static long wordCount(String string) {if (string == null) {return 0;}long letterCount = 0L;long numCount = 0L;long otherCount = 0L;String str = string.trim();char[] chr = str.toCharArray();for(int i = 0; i < chr.length;i++){if(Character.isLetter(chr[i])){letterCount++;} else if(Character.isDigit(chr[i])){numCount ++;} else{otherCount ++;}}return letterCount + numCount + otherCount;}
}

还是在添加的方法中来统计文章字数。将原来的**article.setTotalWords(0L)**改成以下代码:

 article.setTotalWords(WordCountUtil.wordCount(bo.getContent()));

查看页面效果:

1.3、发布文章新建分类修改

我在测试的时候,发布文章没有选择从数据库查出来的分类,而是自己创建的一个分类,点击发布会报错。

首先定位到代码错误的信息。

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

Category category = categoryService.getCategoryByName(bo.getCategoryName());

这一句我们拿前端传过来的分类名去查找,然后没有找到,我们又将前端的值赋给了它,就报错了。

我们拿到的category是一个null,而null对象在堆中会被java的垃圾回收机制回收。所以这里赋值直接报错了,所以我们再重新new一个分类对象即可。

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

1.4、邮件发送配置修改

这里我把邮箱的配置进行了修改,原来配置的邮箱信息是在代码里配置的,维护不太方便,我把它提到了配置文件中了。以后修改邮箱信息直接修改配置文件,就不需要找代码了。打开application.yml

send:mail:host: # 邮件服务器的SMTP地址port: # 邮件服务器的SMTP端口from: # 发件人pass: # 密码

然后修改代码,打开SendMailConfig.java,将配置信息引入进来。

    @Value("${send.mail.host}")private String host;@Value("${send.mail.port}")private Integer port;@Value("${send.mail.from}")private String from;@Value("${send.mail.pass}")private String pass;public void sendMail(MailInfo mailInfo) {try {MailAccount account = new MailAccount();//邮件服务器的SMTP地址account.setHost(host);//邮件服务器的SMTP端口account.setPort(port);//发件人account.setFrom(from);//密码account.setPass(pass);//使用SSL安全连接account.setSslEnable(false);MailUtil.send(account, mailInfo.getReceiveMail(),mailInfo.getTitle(), mailInfo.getContent(), false);log.info("邮件发送成功!");} catch (Exception e) {log.error("邮件发送失败" + JSONUtil.toJsonStr(mailInfo));}}

添加完之后,再去测试下。

1.5、登录页改造升级

我们现在的登录页面非常的原始,不太好看,俗话说人靠衣服马靠鞍,我们也将登录的入口进行改造,后端的逻辑不用动,我们只改前端代码即可。

<style rel="stylesheet/scss" lang="scss">
$bg:#889aa4;
$light_gray:#eaeaea;/* reset element-ui css */
.login-container {.el-input {display: inline-block;height: 47px;width: 85%;input {background: transparent;border: 0px;-webkit-appearance: none;border-radius: 0px;padding: 12px 5px 12px 15px;color: black;height: 47px;&:-webkit-autofill {-webkit-box-shadow: 0 0 0px 1000px $bg inset !important;-webkit-text-fill-color: black !important;}}}.el-form-item {border: 1px solid rgba(255, 255, 255, 0.1);background: rgba(0, 0, 0, 0.1);border-radius: 5px;color: #454545;}
}</style><style rel="stylesheet/scss" lang="scss" scoped>
$bg:#889aa4;
$dark_gray:#889aa4;
$light_gray:#eee;
.login-container {position: fixed;height: 100%;width: 100%;background:url("../../assets/bj.jpg");position:fixed;background-size:100% 100%;// background-image: "../../assets/bg.jpg";.login-form {position: absolute;left: 0;right: 0;width: 520px;max-width: 100%;padding: 35px 35px 15px 35px;margin: 120px auto;}.tips {font-size: 14px;color: #fff;margin-bottom: 10px;span {&:first-of-type {margin-right: 16px;}}}.svg-container {padding: 6px 5px 6px 15px;color: $dark_gray;vertical-align: middle;width: 30px;display: inline-block;}.title {font-size: 28px;font-weight: 400;margin: 0px auto 40px auto;text-align: center;font-weight: bold;}.show-pwd {position: absolute;right: 10px;top: 7px;font-size: 16px;color: $dark_gray;cursor: pointer;user-select: none;}}
</style>

这里只修改了一些样式和添加了一个背景图片。也算是有点样子了。

2、首页功能前端开发

这一块也就是对应的我们的首页,刚一进来就能直观看到的,我们尽量做的美观一点,逼格高一点。

首先我们首页的顶部先放四个导航菜单,用来展示我们的一些重要的数据。

下面的开发我先写前端页面布局完成之后,再去写后端的代码

2.1、顶部导航

这里使用了vue-element-admin的首页的功能。打开我们的前端项目,然后找到/views/dashboard

然后我们引入一个组件,这个导航菜单已经封装成了一个组件。

dashboard文件下新建一个components文件夹,然后创建一个文件PanelGroup.vue

<template><el-row :gutter="40" class="panel-group"><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="peoples" class-name="card-panel-icon" /></div><div class="card-panel-description"><div class="card-panel-text">文章数量</div><count-to :start-val="0" :end-val="102400" :duration="2600" class="card-panel-num" /></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="message" class-name="card-panel-icon" /></div><div class="card-panel-description"><div class="card-panel-text">分类数量</div><count-to :start-val="0" :end-val="81212" :duration="3000" class="card-panel-num" /></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="money" class-name="card-panel-icon" /></div><div class="card-panel-description"><div class="card-panel-text">标签数量</div><count-to :start-val="0" :end-val="9280" :duration="3200" class="card-panel-num" /></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="shopping" class-name="card-panel-icon" /></div><div class="card-panel-description"><div class="card-panel-text">用户数量</div><count-to :start-val="0" :end-val="13600" :duration="3600" class="card-panel-num" /></div></div></el-col></el-row>
</template><script>
import CountTo from 'vue-count-to'export default {components: {CountTo},methods: {handleSetLineChartData(type) {this.$emit('handleSetLineChartData', type)}}
}
</script><style lang="scss" scoped>
.panel-group {margin-top: 18px;.card-panel-col {margin-bottom: 32px;}.card-panel {height: 108px;cursor: pointer;font-size: 12px;position: relative;overflow: hidden;color: #666;background: #fff;box-shadow: 4px 4px 40px rgba(0, 0, 0, .05);border-color: rgba(0, 0, 0, .05);&:hover {.card-panel-icon-wrapper {color: #fff;}.icon-people {background: #40c9c6;}.icon-message {background: #36a3f7;}.icon-money {background: #f4516c;}.icon-shopping {background: #34bfa3}}.icon-people {color: #40c9c6;}.icon-message {color: #36a3f7;}.icon-money {color: #f4516c;}.icon-shopping {color: #34bfa3}.card-panel-icon-wrapper {float: left;margin: 14px 0 0 14px;padding: 16px;transition: all 0.38s ease-out;border-radius: 6px;}.card-panel-icon {float: left;font-size: 48px;}.card-panel-description {float: right;font-weight: bold;margin: 26px;margin-left: 0px;.card-panel-text {line-height: 18px;color: rgba(0, 0, 0, 0.45);font-size: 16px;margin-bottom: 12px;}.card-panel-num {font-size: 20px;}}}
}@media (max-width:550px) {.card-panel-description {display: none;}.card-panel-icon-wrapper {float: none !important;width: 100%;height: 100%;margin: 0 !important;.svg-icon {display: block;margin: 14px auto !important;float: none !important;}}
}
</style>

然后去dashboard目录下的index.vue中引入该组件。

import PanelGroup from './components/PanelGroup'export default {name: 'Dashboard',components: {PanelGroup},computed: {...mapGetters(['name','roles'])}
}

这时控制台会报一个错误

此时我们要执行:npm install --save vue-count-to即可。

执行完之后,我们在引入该组件。

<panel-group ></panel-group>

此时页面上就已经有数据了,我们进行改造一下页面。

可以看到上边还缺少图标和描述之类的,这个是在组件里修改,打开PanelGroup.vue修改。

<template><el-row :gutter="40" class="panel-group"><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><count-to :start-val="0" :end-val="102400" :duration="2600" class="card-panel-num" /></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><count-to :start-val="0" :end-val="81212" :duration="3000" class="card-panel-num" /></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><count-to :start-val="0" :end-val="9280" :duration="3200" class="card-panel-num" /></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><count-to :start-val="0" :end-val="13600" :duration="3600" class="card-panel-num" /></div></div></el-col></el-row>
</template>

图标的话,可以去官网上查找或者从这里查找,然后将图标下载放到自己项目的svg目录下即可。https://panjiachen.github.io/vue-element-admin/#/icon/index

改造完之后是这样的页面

2.2、ECharts入门

这里先介绍一下echarts

官网:https://echarts.apache.org/zh/index.html

什么是echarts?

它是一个基于 JavaScript 的开源可视化图表库,可以用于我们对数据分析的可视化展示,是我们的数据在图表中清晰可见,一般领导比较喜欢看这种分析的图表。

具体的如何使用这里不再一一讲述了,可以查看官方给的文档,有快速上手的教程可以学习。

2.3、数据图表

这里我们先安装一下echarts图表库。使用以下命令

npm install echarts --save

之后我们在创建的components组件文件中新建一个放图表的文件,现在是一个图表对应一个文件,新建一个BarChart.vue文件,这个放我们的柱状图,打开文件,先写一下存放图表的的容器,并设置一下高和宽。

<template><div :class="className" :style="{height:height,width:width}" />
</template>

紧接着要去写一下图表的代码,先引入echarts文件

import * as echarts from 'echarts'
require('echarts/theme/macarons') // echarts theme

然后设置一下容器的高和宽

export default {props: {className: {type: String,default: 'chart'},width: {type: String,default: '100%'},height: {type: String,default: '300px'}},data() {return {chart: null}},mounted() {this.$nextTick(() => {this.initChart()})},beforeDestroy() {if (!this.chart) {return}this.chart.dispose()this.chart = null},
}

然后就可以通过 echarts.init方法初始化一个 echarts 实例并通过 setOption 方法生成一个简单的柱状图

methods: {initChart() {this.chart = echarts.init(this.$el, 'macarons')this.chart.setOption({title: {text: '发文数量'},tooltip: {trigger: 'axis',axisPointer: {type: 'shadow'}},grid: {left: '3%',right: '4%',bottom: '3%',containLabel: true},xAxis: [{type: 'category',data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],axisTick: {alignWithLabel: true}}],yAxis: [{type: 'value'}],series: [{name: 'Direct',type: 'bar',barWidth: '60%',data: [10, 52, 200, 334, 390, 330, 220]}]})}}

这里的代码大家可以去echarts官网的实例中去查找。

然后再去我们的主页将这个组件引进来。

import BarChart from './components/BarChart'components: {PanelGroup,BarChart,
},

页面代码:

 <el-row :gutter="32" class="row-chart"><el-col :xs="24" :sm="24" :lg="8"><div class="chart-wrapper"><bar-chart /></div></el-col></el-row>

然后再设置一下css样式

<style rel="stylesheet/scss" lang="scss" scoped>.dashboard {&-container {padding: 32px;background-color: #f0f2f5;}}.chart-wrapper {background: #fff;padding: 16px 16px 0;margin-bottom: 32px;}.row-chart{margin-top: 30px;}
</style>

然后我们打开页面,查看一下图表有没有渲染出来

此时就渲染出来了,一个我们会了,我们再添加两个图表分别统计分类和访问量。

和之前的那个图表一样,我这里不再一一讲述,只把代码给大家展现出来。

在components文件夹中新建一个PieChart.vue

<template><div :class="className" :style="{height:height,width:width}" />
</template><script>
import * as echarts from 'echarts'
require('echarts/theme/macarons') // echarts themeexport default {props: {className: {type: String,default: 'chart'},width: {type: String,default: '100%'},height: {type: String,default: '300px'}},data() {return {chart: null}},mounted() {this.$nextTick(() => {this.initChart()})},beforeDestroy() {if (!this.chart) {return}this.chart.dispose()this.chart = null},methods: {initChart() {this.chart = echarts.init(this.$el, 'macarons')this.chart.setOption({title: {text: '分类占比',left: 'left'},tooltip: {trigger: 'item'},series: [{name: 'Access From',type: 'pie',radius: '50%',data: [{ value: 1048, name: 'Search Engine' },{ value: 735, name: 'Direct' },{ value: 580, name: 'Email' },{ value: 484, name: 'Union Ads' },{ value: 300, name: 'Video Ads' }],emphasis: {itemStyle: {shadowBlur: 10,shadowOffsetX: 0,shadowColor: 'rgba(0, 0, 0, 0.5)'}}}]})}}
}
</script>

再新建一个LineChart.vue

<template><div :class="className" :style="{height:height,width:width}" />
</template><script>
import * as echarts from 'echarts'
require('echarts/theme/macarons') // echarts themeexport default {props: {className: {type: String,default: 'chart'},width: {type: String,default: '100%'},height: {type: String,default: '300px'}},data() {return {chart: null}},mounted() {this.$nextTick(() => {this.initChart()})},beforeDestroy() {if (!this.chart) {return}this.chart.dispose()this.chart = null},methods: {initChart() {this.chart = echarts.init(this.$el, 'macarons')this.chart.setOption({title: {text: '访问量'},xAxis: {type: 'category',data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']},yAxis: {type: 'value'},series: [{data: [820, 932, 901, 934, 1290, 1330, 1320],type: 'line',smooth: true}]})}}
}
</script>

再去主页将这两个引入。

index.vue完整代码如下

<template><div class="dashboard-container"><panel-group ></panel-group><!-- 数据分析 --><el-row :gutter="32" class="row-chart"><el-col :xs="24" :sm="24" :lg="8"><div class="chart-wrapper"><bar-chart /></div></el-col><el-col :xs="24" :sm="24" :lg="8"><div class="chart-wrapper"><pie-chart /></div></el-col><el-col :xs="24" :sm="24" :lg="8"><div class="chart-wrapper"><line-chart /></div></el-col></el-row></div></template><script>import PanelGroup from './components/PanelGroup'
import BarChart from './components/BarChart'
import PieChart from './components/PieChart'
import LineChart from './components/LineChart'export default {name: 'Dashboard',components: {PanelGroup,BarChart,PieChart,LineChart},data() {return {}},methods: {},}
</script><style rel="stylesheet/scss" lang="scss" scoped>.dashboard {&-container {padding: 32px;background-color: #f0f2f5;}
}
.chart-wrapper {background: #fff;padding: 16px 16px 0;margin-bottom: 32px;
}
.row-chart{margin-top: 30px;
}
</style>

然后我们看一下效果

是不是感觉还挺哇塞的,其实那种看着非常高大上的大屏展示就是这种画出来的,我后边应该会写一篇如何制作大屏的页面的文章,大家可以等待一下。

现在我们的首页是不是有点样子了,越来越完善了,是有点系统的样子了。

接下来我们再美化一下首页,再添加一点小功能。

2.4、添加日历

大家可以参考这个文章添加日历,以下就是我参照实现的,稍微做了修改。

参考文章:vue日历插件vue-calendar

  1. 首先安装一下日历的组件
npm i vue-calendar-component --save

如果安装失败,可以试试以下的命令

cnpm i vue-calendar-component --save
  1. 引入组件

在我们的首页引入一下日历的组件

import Calendar from 'vue-calendar-component';

然后写绘制日历的代码

  <el-col :xs="24" :sm="24" :lg="8"><div class="chart-wrapper"><div class="con"><div class="now-data-myself"><div class="now-data-myself-time">{{ date }}</div><div class="now-data-myself-week">{{ week }}</div></div><Calendarv-on:choseDay="clickDay"v-on:changeMonth="changeDate"v-on:isToday="clickToday"></Calendar></div></div></el-col>
components: {PanelGroup,BarChart,PieChart,LineChart,Calendar
},
data() {return {date: "",week: "",}
},
created() {var now = new Date();this.date = now.getDate();//得到日期var day = now.getDay();//得到周几var arr_week = new Array("星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六");this.week = arr_week[day];
},methods: {clickDay(data) {},changeDate(data) {},clickToday(data) {}
},

CSS样式:

.now-data-myself {width: 40%;position: absolute;border-right: 1px solid rgba(227, 227, 227, 0.6);
}
.con {position: relative;max-width: 400px;margin: auto;
}
.con .wh_content_all {background: transparent !important;
}
.wh_top_changge li {color: #F56C6C !important;font-size: 15px !important;
}
.wh_content_item, .wh_content_item_tag {color: #303133 !important;
}
.wh_content_item .wh_isToday {background: #00d985  !important;color: #fff  !important;
}
.wh_content_item .wh_chose_day {background: #409EFF  !important;color: #ffff  !important;
}
.wh_item_date:hover {background: rgb(217, 236, 255) !important;border-radius: 100px !important;color: rgb(102, 177, 255)  !important;
}
.wh_jiantou1[data-v-2ebcbc83] {border-top: 2px solid #909399;border-left: 2px solid #909399;width: 7px;height: 7px;
}
.wh_jiantou2[data-v-2ebcbc83] {border-top: 2px solid #909399;border-right: 2px solid #909399;width: 7px;height: 7px;
}
.wh_top_tag[data-v-2ebcbc83] {color: #409EFF;border-top: 1px solid rgba(227, 227, 227, 0.6);border-bottom: 1px solid rgba(227, 227, 227, 0.6);
}
.wh_container[data-v-2ebcbc83] {max-width: 400px;
}
.wh_top_changge[data-v-2ebcbc83] {display: flex;width: 50%;margin-left: 43%;
}
.now-data-myself-time {color: #F56C6C;font-size: 28px;margin-left:60px;height: 33px;font-family: "Helvetica Neue";
}
.now-data-myself-week {margin-left:60px;font-size: 10px;color: #909399;
}
.wh_top_changge .wh_content_li[data-v-2ebcbc83] {font-family: Helvetica;
}

这里我遇到了一个坑,修改的日历的样式没有效果,最终看到css样式的地方加了scoped

<style rel="stylesheet/scss" lang="scss" scoped>

我们把这个去掉即可,想知道什么原因的可以去学习一下。

然后看一下我们的页面。

2.5、词云

接下来我们来写一下这个词云,这个主要是美化我们的页面,做一些特效使用,也多学一些常用的小功能。

我这个是在网上找了一个词云的代码,我将它封装成了一个组件,直接在首页引用即可。

在components文件夹下面新建一个WordCloud.vue文件

<template><section class="cloud-bed"><div class="cloud-box"><spanv-for="(item, index) in dataList":key="index"@click="getDataInfo(item)":style="{color:item.color,background:item.bgColor}">{{ item.name }}</span></div></section>
</template><script>export default {name: "word-cloud",data() {return {timer: 10, // 球体转动速率radius: 0, // 词云球体面积大小dtr: Math.PI/180, //鼠标滑过球体转动速度active: false, // 默认加载是否开启转动lasta: 0, // 上下转动lastb: 0.5, // 左右转动distr: true,tspeed: 1, // 鼠标移动上去时球体转动mouseX: 0,mouseY: 0,tagAttrList: [],tagContent: null,cloudContent: null,sinA: '',cosA: '',sinB: '',cosB: '',sinC: '',cosC: '',dataList: [{name: '页面卡顿\白屏',value: '1',bgColor:'rgb(57, 193, 207,0.12)',color:'#39c1cf',},{name: '闪退',value: '8',bgColor:'rgb(66, 105, 245,0.12)',color:'#4269f5',},{name: '登录问题',value: '9',bgColor:'rgb(184, 107, 215,0.12)',color:'#b86bd7',},{name: '功能bug',value: '3',bgColor:'rgb(243, 84, 83,0.12)',color:'#f35453',},{name: '无法收到短信',value: '6',bgColor:'rgb(250, 116, 20,0.12)',color:'#FA7414',},{name: '人脸/指纹认证失败',value: '10',bgColor:'rgb(255, 171, 30,0.12)',color:'#FFAB1E',},{name: '功能建议',value: '2',bgColor:'rgb(136, 104, 217,0.12)',color:'#8868D9',},{name: 'UI/UX',value: '5',bgColor:'rgb(42, 184, 230,0.12)',color:'#2AB8E6',},{name: '导航性',value: '7',bgColor:'rgb(117, 133, 162,0.12)',color:'#7585A2',},]}},mounted () {this.$nextTick(() => {this.radius = document.querySelector('.cloud-box').offsetWidth / 2this.initWordCloud()})},beforeDestroy () {clearInterval(this.timer)},methods:{// 获取点击文本信息getDataInfo (item) {console.log(item, 'item')},initWordCloud () {this.cloudContent = document.querySelector('.cloud-box');this.tagContent = this.cloudContent.getElementsByTagName('span');for (let i = 0; i < this.tagContent.length; i++) {let tagObj = {};tagObj.offsetWidth = this.tagContent[i].offsetWidth;tagObj.offsetHeight = this.tagContent[i].offsetHeight;this.tagAttrList.push(tagObj);}this.sineCosine(0, 0, 0);this.positionAll();this.cloudContent.onmouseover = () => {this.active=true;};this.cloudContent.onmouseout = () => {this.active=false;};this.cloudContent.onmousemove = (ev) => {let oEvent = window.event || ev;this.mouseX = oEvent.clientX - (this.cloudContent.offsetLeft + this.cloudContent.offsetWidth/2);this.mouseY = oEvent.clientY - (this.cloudContent.offsetTop + this.cloudContent.offsetHeight/2);this.mouseX/= 5;this.mouseY/= 5;};setInterval(this.update, this.timer);},positionAll () {let phi = 0;let theta = 0;let max = this.tagAttrList.length;let aTmp = [];let oFragment = document.createDocumentFragment();//随机排序for (let i=0; i < this.tagContent.length; i++) {aTmp.push(this.tagContent[i]);}aTmp.sort(() => {return Math.random() < 0.5 ? 1 : -1;});for (let i = 0; i < aTmp.length; i++) {oFragment.appendChild(aTmp[i]);}this.cloudContent.appendChild(oFragment);for(let i = 1; i < max + 1; i++){if (this.distr) {phi = Math.acos(-1 + (2 * i - 1) / max);theta = Math.sqrt(max * Math.PI) * phi;} else {phi = Math.random() * (Math.PI);theta = Math.random() * (2 * Math.PI);}//坐标变换this.tagAttrList[i-1].cx = this.radius * Math.cos(theta) * Math.sin(phi);this.tagAttrList[i-1].cy = this.radius * Math.sin(theta) * Math.sin(phi);this.tagAttrList[i-1].cz = this.radius * Math.cos(phi);this.tagContent[i-1].style.left = this.tagAttrList[i-1].cx + this.cloudContent.offsetWidth / 2 - this.tagAttrList[i-1].offsetWidth / 2 + 'px';this.tagContent[i-1].style.top = this.tagAttrList[i-1].cy + this.cloudContent.offsetHeight / 2 - this.tagAttrList[i-1].offsetHeight / 2 + 'px';}},update () {let angleBasicA;let angleBasicB;if (this.active) {angleBasicA = (-Math.min(Math.max(-this.mouseY, -200 ), 200) / this.radius) * this.tspeed;angleBasicB = (Math.min(Math.max(-this.mouseX, -200 ), 200) / this.radius) * this.tspeed;} else {angleBasicA = this.lasta * 0.98;angleBasicB = this.lastb * 0.98;}//默认转动是后是否需要停下// lasta=a;// lastb=b;// if(Math.abs(a)<=0.01 && Math.abs(b)<=0.01)// {// return;// }this.sineCosine(angleBasicA, angleBasicB, 0);for(let j = 0; j < this.tagAttrList.length; j++) {let rx1 = this.tagAttrList[j].cx;let ry1 = this.tagAttrList[j].cy * this.cosA + this.tagAttrList[j].cz * (-this.sinA);let rz1 = this.tagAttrList[j].cy * this.sinA + this.tagAttrList[j].cz * this.cosA;let rx2 = rx1 * this.cosB + rz1 * this.sinB;let ry2 = ry1;let rz2 = rx1 * (-this.sinB) + rz1 * this.cosB;let rx3 = rx2 * this.cosC + ry2 * (-this.sinC);let ry3 = rx2 * this.sinC + ry2 * this.cosC;let rz3 = rz2;this.tagAttrList[j].cx = rx3;this.tagAttrList[j].cy = ry3;this.tagAttrList[j].cz = rz3;let per = 350 / (350 + rz3);this.tagAttrList[j].x = rx3 * per - 2;this.tagAttrList[j].y = ry3 * per;this.tagAttrList[j].scale = per;this.tagAttrList[j].alpha = per;this.tagAttrList[j].alpha = (this.tagAttrList[j].alpha - 0.6) * (10/6);}this.doPosition();this.depthSort();},doPosition() {let len = this.cloudContent.offsetWidth/2;let height = this.cloudContent.offsetHeight/2;for (let i=0;i < this.tagAttrList.length;i++) {this.tagContent[i].style.left = this.tagAttrList[i].cx + len - this.tagAttrList[i].offsetWidth/2 + 'px';this.tagContent[i].style.top = this.tagAttrList[i].cy + height - this.tagAttrList[i].offsetHeight/2 + 'px';// this.tagContent[i].style.fontSize = Math.ceil(12 * this.tagAttrList[i].scale/2) + 8 + 'px';this.tagContent[i].style.fontSize = Math.ceil(12 * this.tagAttrList[i].scale/2) +2 + 'px';this.tagContent[i].style.filter = "alpha(opacity="+100 * this.tagAttrList[i].alpha+")";this.tagContent[i].style.opacity = this.tagAttrList[i].alpha;}},depthSort(){let aTmp = [];for (let i = 0; i < this.tagContent.length; i++) {aTmp.push(this.tagContent[i]);}aTmp.sort((item1, item2) => item2.cz - item1.cz);for (let i = 0; i < aTmp.length; i++) {aTmp[i].style.zIndex=i;}},sineCosine (a, b, c) {this.sinA = Math.sin(a * this.dtr);this.cosA = Math.cos(a * this.dtr);this.sinB = Math.sin(b * this.dtr);this.cosB = Math.cos(b * this.dtr);this.sinC = Math.sin(c * this.dtr);this.cosC = Math.cos(c * this.dtr);}}};
</script><style scoped>
.cloud-bed {width: 250px;height: 270px;margin: auto;
}
.cloud-box{position:relative;margin:20px auto 0px;width: 100%;height: 100%;background:  #00000000;}.cloud-box span{position: absolute;padding: 3px 6px;top: 0px;font-weight: bold;text-decoration:none;left:0px;background-image: linear-gradient(to bottom, red, #fff);background-clip: text;color: transparent;width: 50px;height: 50px;border-radius: 50%;text-align: center;display: flex;align-items: center;justify-content: center;/* line-height: 50px;overflow:hidden;white-space: nowrap;text-overflow: ellipsis; */}
</style>

然后在首页中将组件引进来,和之前的图表引入一样。

<el-col :xs="24" :sm="24" :lg="8"><div class="chart-wrapper"><div class="e-title">文章标签统计</div><word-cloud /></div>
</el-col>

引入组件

import WordCloud from './components/WordCloud.vue'components: {PanelGroup,BarChart,PieChart,LineChart,Calendar,WordCloud
},

然后运行一下项目,我们看一下页面

看着是不是还挺高大上的。这个功能模块添加完成了,最后一个我们写一下公告的展示

2.6、公告

在首页添加了公告的信息,方便我们及时的查看,但只展示最近发的前四条公告,其余的还是要去公告列表中去查看,这个就比较简单了,我们直接引用element-ui的组件即可。

我选择了折叠面板来实现,感觉还挺符合这个通知公告的功能实现。

<el-col :xs="24" :sm="24" :lg="8"><div class="chart-wrapper"><div class="e-title">最新公告</div><el-collapse v-model="activeName" accordion><el-collapse-item title="一致性 Consistency" name="1"><div>与现实生活一致:与现实生活的流程、逻辑保持一致,遵循用户习惯的语言和概念;</div><div>在界面中一致:所有的元素和结构需保持一致,比如:设计样式、图标和文本、元素的位置等。</div></el-collapse-item><el-collapse-item title="反馈 Feedback" name="2"><div>控制反馈:通过界面样式和交互动效让用户可以清晰的感知自己的操作;</div><div>页面反馈:操作后,通过页面元素的变化清晰地展现当前状态。</div></el-collapse-item><el-collapse-item title="效率 Efficiency" name="3"><div>简化流程:设计简洁直观的操作流程;</div><div>清晰明确:语言表达清晰且表意明确,让用户快速理解进而作出决策;</div><div>帮助用户识别:界面简单直白,让用户快速识别而非回忆,减少用户记忆负担。</div></el-collapse-item><el-collapse-item title="可控 Controllability" name="4"><div>用户决策:根据场景可给予用户操作建议或安全提示,但不能代替用户进行决策;</div><div>结果可控:用户可以自由的进行操作,包括撤销、回退和终止当前操作等。</div></el-collapse-item></el-collapse>

里面的内容暂时先不修改,我们下一篇处理后端的接口,再统一对接后端接口数据。

好啦,首页的功能基本上都写完了,我让大家看一下完整的页面效果

这个是不是很哇塞,感觉很beautiful。

首页完整代码如下:

<template><div class="dashboard-container"><panel-group ></panel-group><!-- 数据分析 --><el-row :gutter="32" class="row-chart"><el-col :xs="24" :sm="24" :lg="8"><div class="chart-wrapper"><bar-chart /></div></el-col><el-col :xs="24" :sm="24" :lg="8"><div class="chart-wrapper"><pie-chart /></div></el-col><el-col :xs="24" :sm="24" :lg="8"><div class="chart-wrapper"><line-chart /></div></el-col></el-row><!-- 功能 --><el-row :gutter="32" class="row-chart"><el-col :xs="24" :sm="24" :lg="8"><div class="chart-wrapper"><div class="e-title">文章标签统计</div><word-cloud /></div></el-col><el-col :xs="24" :sm="24" :lg="8"><div class="chart-wrapper"><div class="e-title">最新公告</div><el-collapse v-model="activeName" accordion><el-collapse-item title="一致性 Consistency" name="1"><div>与现实生活一致:与现实生活的流程、逻辑保持一致,遵循用户习惯的语言和概念;</div><div>在界面中一致:所有的元素和结构需保持一致,比如:设计样式、图标和文本、元素的位置等。</div></el-collapse-item><el-collapse-item title="反馈 Feedback" name="2"><div>控制反馈:通过界面样式和交互动效让用户可以清晰的感知自己的操作;</div><div>页面反馈:操作后,通过页面元素的变化清晰地展现当前状态。</div></el-collapse-item><el-collapse-item title="效率 Efficiency" name="3"><div>简化流程:设计简洁直观的操作流程;</div><div>清晰明确:语言表达清晰且表意明确,让用户快速理解进而作出决策;</div><div>帮助用户识别:界面简单直白,让用户快速识别而非回忆,减少用户记忆负担。</div></el-collapse-item><el-collapse-item title="可控 Controllability" name="4"><div>用户决策:根据场景可给予用户操作建议或安全提示,但不能代替用户进行决策;</div><div>结果可控:用户可以自由的进行操作,包括撤销、回退和终止当前操作等。</div></el-collapse-item></el-collapse></div></el-col><el-col :xs="24" :sm="24" :lg="8"><div class="chart-wrapper"><div class="con"><div class="now-data-myself"><div class="now-data-myself-time">{{ date }}</div><div class="now-data-myself-week">{{ week }}</div></div><Calendarv-on:choseDay="clickDay"v-on:changeMonth="changeDate"v-on:isToday="clickToday"></Calendar></div></div></el-col></el-row></div></template><script>import PanelGroup from './components/PanelGroup'
import BarChart from './components/BarChart'
import PieChart from './components/PieChart'
import LineChart from './components/LineChart'
import Calendar from 'vue-calendar-component'
import WordCloud from './components/WordCloud.vue'export default {name: 'Dashboard',components: {PanelGroup,BarChart,PieChart,LineChart,Calendar,WordCloud},data() {return {date: "",week: "",activeName: '1'}},created() {var now = new Date();this.date = now.getDate();//得到日期var day = now.getDay();//得到周几var arr_week = new Array("星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六");this.week = arr_week[day];},methods: {clickDay(data) {},changeDate(data) {},clickToday(data) {}},}
</script><style rel="stylesheet/scss" lang="scss">.dashboard {&-container {padding: 32px;background-color: #f0f2f5;}
}
.chart-wrapper {background: #fff;padding: 16px 16px 0;margin-bottom: 32px;
}
.row-chart{margin-top: 30px;
}
.now-data-myself {width: 40%;position: absolute;border-right: 1px solid rgba(227, 227, 227, 0.6);
}
.con {position: relative;max-width: 400px;margin: auto;
}
.con .wh_content_all {background: transparent !important;
}
.wh_top_changge li {color: #F56C6C !important;font-size: 15px !important;
}
.wh_content_item, .wh_content_item_tag {color: #303133 !important;
}
.wh_content_item .wh_isToday {background: #00d985  !important;color: #fff  !important;
}
.wh_content_item .wh_chose_day {background: #409EFF  !important;color: #ffff  !important;
}
.wh_item_date:hover {background: rgb(217, 236, 255) !important;border-radius: 100px !important;color: rgb(102, 177, 255)  !important;
}
.wh_jiantou1[data-v-2ebcbc83] {border-top: 2px solid #909399;border-left: 2px solid #909399;width: 7px;height: 7px;
}
.wh_jiantou2[data-v-2ebcbc83] {border-top: 2px solid #909399;border-right: 2px solid #909399;width: 7px;height: 7px;
}
.wh_top_tag[data-v-2ebcbc83] {color: #409EFF;border-top: 1px solid rgba(227, 227, 227, 0.6);border-bottom: 1px solid rgba(227, 227, 227, 0.6);
}
.wh_container[data-v-2ebcbc83] {max-width: 400px;
}
.wh_top_changge[data-v-2ebcbc83] {display: flex;width: 50%;margin-left: 43%;
}
.now-data-myself-time {color: #F56C6C;font-size: 28px;margin-left:60px;height: 33px;font-family: "Helvetica Neue";
}
.now-data-myself-week {margin-left:60px;font-size: 10px;color: #909399;
}
.wh_top_changge .wh_content_li[data-v-2ebcbc83] {font-family: Helvetica;
}</style>

好啦,下一篇会写后端的接口,完成数据的渲染就结束了,我上次发起的投票关于上线发布的事情,我后边可能不会更新那一篇了,但是应该会放在下一个专栏里,但是有些好学的小伙伴想学习可以去下面的公众号找我,我可以给你提供思路和部署的方法,我这里就不再以文章的形式写出来了,一篇文章我要写好久的,熬好几夜才搞完,希望大家理解。多多给我点点赞,推荐一下。

最后最后,希望大家再评论区给我留点意见和要搞的技术,要不然我光写大家只看我写的,对提升帮助比较小。感谢大家!

上一篇:Spring Boot + vue-element 开发个人博客项目实战教程(二十四、文章管理页面开发(3))

下一篇:Spring Boot + vue-element 开发个人博客项目实战教程(二十六、项目完善及扩展(后端部分))

Spring Boot + vue-element 开发个人博客项目实战教程(二十五、项目完善及扩展(前端部分))相关推荐

  1. Spring boot实训开发个人博客(二)详情页

    Spring boot实训开发个人博客(二)详情页 1.在index页面添加归档: 2.开始写详情页: 1.头部文件: 2.添加文章内容 <h2 class="ui center al ...

  2. Spring Boot实训开发个人博客13 -博客详情

    文章目录 一.博客详情页面 二.修改IndexController 三.在Blog.html页面添加获取数据 四.页面查看 五.Markdown 转换 HTML (一)添加依赖 (二)编写工具类 (三 ...

  3. Spring Boot + vue-element 开发个人博客项目实战教程(一、项目介绍和规划)

    ⭐ 作者简介:码上言 ⭐ 代表教程:Spring Boot + vue-element 开发个人博客项目实战教程 ⭐专栏内容:零基础学Java.个人博客系统 ⭐我的文档网站:http://xyhwh- ...

  4. Spring Boot Vue Element入门实战(完结)

    最近给朋友做一个大学运动会管理系统,用作教学案例,正好自己也在自学VUE,决定用spring boot vue做一个简单的系统.vue这个前端框架很火,他和传统的Jquery 编程思路完全不一样,Jq ...

  5. Spring Boot+Vue/前后端分离/高并发/秒杀实战课程之spring Security快速搭建oauth2 内存版身份认证

    Springboot快速搭建oauth2 内存版身份认证 环境准备 点击[Create New Project]创建一个新的项目 项目环境配置 配置Thymeleaf 搭建oauth2认证,加入两个依 ...

  6. Spring Boot干货系列:(十二)Spring Boot使用单元测试 | 嘟嘟独立博客

    原文地址 2017-12-28 开启阅读模式 Spring Boot干货系列:(十二)Spring Boot使用单元测试 Spring Boot干货系列 Spring Boot 前言 这次来介绍下Sp ...

  7. Vue + Spring Boot 项目实战(二十二):生产环境初步搭建

    重要链接: 「系列文章目录」 「项目源码(GitHub)」 本篇目录 前言 一.虚拟机与 CentOS 安装 二.MySQL 服务部署 1.虚拟机克隆及网络配置 2.MySQL 安装 3.MySQL ...

  8. Spring Boot Vue Element入门实战(四)主页面开发

    本博客属作者原创,未经允许禁止转载,请尊重原创!如有问题请联系QQ509961766 (一)页面布局 页面布局分为3个部分: 顶部导航:系统logo,登录信息,退出按钮等 左侧菜单:显示系统菜单 右侧 ...

  9. Spring Boot Vue Element入门实战(五)封装axios

    本博客属作者原创,未经允许禁止转载,请尊重原创!如有问题请联系QQ509961766 (一)关于Axios Axios 是一个基于 promise 的 HTTP 库,简单的讲就是可以发送get.pos ...

  10. Spring Boot Vue Element入门实战(十)Vue生命周期

    本博客属作者原创,未经允许禁止转载,请尊重原创!如有问题请联系QQ509961766 前面9篇文章基本上完成了vue 静态页面的入门,包括列表展示,路由动态加载菜单,echarts图表的一些使用,后面 ...

最新文章

  1. 云计算设计模式(十)——守门员模式
  2. 关系数据理论中的范式
  3. 【Linux 内核 内存管理】虚拟地址空间布局架构 ③ ( 内存描述符 mm_struct 结构体成员分析 | mmap | mm_rb | task_size | pgd | mm_users )
  4. c语言调用shell命令一 popen使用以及获取命令返回值
  5. 算法篇之-----滑动窗口(尺取法)
  6. Angry Birds Rio 攻略 1-1
  7. java mp4 视频时间戳_MP4文件中音视频时间戳的计算
  8. 【Alpha】开发日志Day8-0719
  9. mysql 行列转换 动态_mysql 行列动态转换的实现(列联表,交叉表)
  10. 生信学习学的是什么?常识!
  11. 弹性布局(Flex)布局介绍
  12. 除了停电之外,今年的CES还有这些“意外”……
  13. python 自动化测试面试题及答案_自动化测试面试题及答案
  14. 【xsong说算法】剑指offer一个月打卡完毕
  15. electron编写我们第一个hello world程序和文件引入
  16. EXCEL中如何分段进行快速填充
  17. spring boot学生课程考试系统的设计与实现毕业设计源码171548
  18. 数据架构选型必读:2021上半年数据库产品技术解析
  19. jsp写的简单购书网站
  20. OMNeT++理论算法仿真详述

热门文章

  1. 树莓派通过TTL3.3转485 Modbus采集水表
  2. 程序员需要常用到的几大工具,省事高效
  3. 有一门课不及格的学生(YZOJ-1039)
  4. 谢雨欣最欣专辑《欣天地》
  5. Android中so文件的生成和调用
  6. 发现一个好看的手机壁纸网站,撸代码的手已经饥渴难耐了
  7. Word转PDF转换器教程
  8. 求二叉树两个结点的最近公共祖先
  9. Android 常用效果(各种进度条,酷炫loading动画,火箭升空,撒花以及趋势图)...
  10. 创建型设计模式——原型模式