今天介绍一个博客项目,是基于SpringBoot和Vue的前后端分离项目,前台界面炫酷无比、科技感十足,后台管理端功能丰富。
首页,动态词条
首页下方是博客内容
炫酷的留言板
后台管理界面
前端:vue + vuex + vue-router + axios + vuetify + element + echarts
后端:SpringBoot + nginx + docker + SpringSecurity + Swagger2 + MyBatisPlus + Mysql + Redis + elasticsearch + RabbitMQ + MaxWell + Websocket
其他: 接入QQ,微博第三方登录,接入腾讯云人机验证、websocket
首页代码展示
<template> <div> <!-- banner --> <div class="home-banner" :style="cover"> <div class="banner-container"> <!-- 联系方式 --> <h1 class="blog-title animated zoomIn"> {{ blogInfo.websiteConfig.websiteName }} </h1> <!-- 一言 --> <div class="blog-intro"> {{ obj.output }} <span class="typed-cursor">|</span> </div> <!-- 联系方式 --> <div class="blog-contact"> <a v-if="isShowSocial(qq)" class="mr-5 iconfont iconqq" target="_blank" :href=" http://wpa.qq.com/msgrd?v=3&uin= + blogInfo.websiteConfig.qq + &site=qq&menu=yes " /> <a v-if="isShowSocial(github)" target="_blank" :href="blogInfo.websiteConfig.github" class="mr-5 iconfont icongithub" /> <a v-if="isShowSocial(gitee)" target="_blank" :href="blogInfo.websiteConfig.gitee" class="iconfont icongitee-fill-round" /> </div> </div> <!-- 向下滚动 --> <div class="scroll-down" @click="scrollDown"> <v-icon color="#fff" class="scroll-down-effects"> mdi-chevron-down </v-icon> </div> </div> <!-- 主页文章 --> <v-row class="home-container"> <v-col md="9" cols="12"> <!-- 说说轮播 --> <v-card class="animated zoomIn" v-if="talkList.length > 0"> <Swiper :list="talkList" /> </v-card> <v-card class="animated zoomIn article-card" style="border-radius: 12px 8px 8px 12px" v-for="(item, index) of articleList" :key="item.id"> <!-- 文章封面图 --> <div :class="isRight(index)"> <router-link :to="/articles/ + item.id"> <v-img class="on-hover" width="100%" height="100%" :src="item.articleCover" /> </router-link> </div> <!-- 文章信息 --> <div class="article-wrapper"> <div style="line-height:1.4"> <router-link :to="/articles/ + item.id">{{ item.articleTitle }}</router-link> </div> <div class="article-info"> <!-- 是否置顶 --> <span v-if="item.isTop == 1"> <span style="color:#ff7242"> <i class="iconfont iconzhiding" /> 置顶 </span> <span class="separator">|</span> </span> <!-- 发表时间 --> <v-icon size="14">mdi-calendar-month-outline</v-icon> {{ item.createTime | date }} <span class="separator">|</span> <!-- 文章分类 --> <router-link :to="/categories/ + item.categoryId"> <v-icon size="14">mdi-inbox-full</v-icon> {{ item.categoryName }} </router-link> <span class="separator">|</span> <!-- 文章标签 --> <router-link style="display:inline-block" :to="/tags/ + tag.id" class="mr-1" v-for="tag of item.tagDTOList" :key="tag.id" > <v-icon size="14">mdi-tag-multiple</v-icon>{{ tag.tagName }} </router-link> </div> <!-- 文章内容 --> <div class="article-content"> {{ item.articleContent }} </div> </div> </v-card> <!-- 无限加载 --> <infinite-loading @infinite="infiniteHandler"> <div slot="no-more" /> </infinite-loading> </v-col> <!-- 博主信息 --> <v-col md="3" cols="12" class="d-md-block d-none"> <div class="blog-wrapper"> <v-card class="animated zoomIn blog-card mt-5"> <div class="author-wrapper"> <!-- 博主头像 --> <v-avatar size="110"> <img class="author-avatar" :src="blogInfo.websiteConfig.websiteAvatar" /> </v-avatar> <div style="font-size: 1.375rem;margin-top:0.625rem"> {{ blogInfo.websiteConfig.websiteAuthor }} </div> <div style="font-size: 0.875rem;">{{ blogInfo.websiteConfig.websiteIntro }}</div> </div> <!-- 博客信息 --> <div class="blog-info-wrapper"> <div class="blog-info-data"> <router-link to="/archives"> <div style="font-size: 0.875rem">文章</div> <div style="font-size: 1.25rem"> {{ blogInfo.articleCount }} </div> </router-link> </div> <div class="blog-info-data"> <router-link to="/categories"> <div style="font-size: 0.875rem">分类</div> <div style="font-size: 1.25rem">{{ blogInfo.categoryCount }}</div> </router-link> </div> <div class="blog-info-data"> <router-link to="/tags"> <div style="font-size: 0.875rem">标签</div> <div style="font-size: 1.25rem">{{ blogInfo.tagCount }}</div> </router-link> </div> </div> <!-- 收藏按钮 --> <a class="collection-btn" @click="tip = true"> <v-icon color="#fff" size="18" class="mr-1">mdi-bookmark</v-icon> 加入书签 </a> <!-- 社交信息 --> <div class="card-info-social"> <a v-if="isShowSocial(qq)" class="mr-5 iconfont iconqq" target="_blank" :href=" http://wpa.qq.com/msgrd?v=3&uin= + blogInfo.websiteConfig.qq + &site=qq&menu=yes " /> <a v-if="isShowSocial(github)" target="_blank" :href="blogInfo.websiteConfig.github" class="mr-5 iconfont icongithub" /> <a v-if="isShowSocial(gitee)" target="_blank" :href="blogInfo.websiteConfig.gitee" class="iconfont icongitee-fill-round" /> </div> </v-card> <!-- 网站信息 --> <v-card class="blog-card animated zoomIn mt-5 big"> <div class="web-info-title"> <v-icon size="18">mdi-bell</v-icon> 公告 </div> <div style="font-size:0.875rem"> {{ blogInfo.websiteConfig.websiteNotice }} </div> </v-card> <!-- 网站信息 --> <v-card class="blog-card animated zoomIn mt-5"> <div class="web-info-title"> <v-icon size="18">mdi-chart-line</v-icon>网站资讯</div> <div class="web-info"> <div style="padding:4px 0 0"> 运行时间:<span class="float-right">{{ time }}</span> </div> <div style="padding:4px 0 0"> 总访问量:<span class="float-right"> {{ blogInfo.viewsCount }} </span> </div> </div> </v-card> </div> </v-col> </v-row> <!-- 提示消息 --> <v-snackbar v-model="tip" top color="#49b1f5" :timeout="2000"> 按CTRL+D 键将本页加入书签 </v-snackbar> </div> </template> <script> import Swiper from "../../components/Swiper.vue"; import EasyTyper from "easy-typer-js"; export default { components: { Swiper }, created() {this.init(); this.listHomeTalks(); this.timer = setInterval(this.runTime, 1000); },data: function() { return { tip: false, time: "", obj: { output: "", isEnd: false, speed: 300, singleBack: false, sleep: 0, type: "rollback", backSpeed: 40, sentencePause: true }, articleList: [], talkList: [], current: 1 }; }, methods: { // 初始化 init() { document.title = this.blogInfo.websiteConfig.websiteName; // 一言Api进行打字机循环输出效果 fetch("https://v1.hitokoto.cn?c=i") .then(res => { returnres.json(); }) .then(({ hitokoto }) => { this.initTyped(hitokoto); }); }, listHomeTalks() {this.axios.get("/api/home/talks").then(({ data }) => { this.talkList = data.data; }); }, initTyped(input, fn, hooks) {const obj = this.obj; // eslint-disable-next-line no-unused-vars const typed = new EasyTyper(obj, input, fn, hooks); }, scrollDown() { window.scrollTo({behavior: "smooth", top: document.documentElement.clientHeight }); }, runTime() {var timeold = new Date().getTime() - new Date(this.blogInfo.websiteConfig.websiteCreateTime).getTime();var msPerDay = 24 * 60 * 60 * 1000; var daysold = Math.floor(timeold / msPerDay); var str = ""; var day = new Date(); str += daysold + "天"; str += day.getHours() + "时"; str += day.getMinutes() +"分"; str += day.getSeconds() + "秒"; this.time = str; }, infiniteHandler($state) {let md = require("markdown-it")(); this.axios .get("/api/articles", { params: { current: this.current } }) .then(({ data }) => { if (data.data.length) { // 去除markdown标签data.data.forEach(item =>{ item.articleContent = md .render(item.articleContent) .replace(/<\/?[^>]*>/g, "") .replace(/[|]*\n/, "") .replace(/&npsp;/gi, ""); });this.articleList.push(...data.data); this.current++; $state.loaded(); } else{ $state.complete(); } }); } },computed: { isRight() { return function(index) { if (index % 2 == 0) { return "article-cover left-radius"; } return "article-cover right-radius"; }; }, blogInfo() { return this.$store.state.blogInfo; }, isShowSocial() {return function(social) { return this.blogInfo.websiteConfig.socialUrlList.indexOf(social) !=-1; }; }, cover() { var cover = ""; this.$store.state.blogInfo.pageList.forEach(item => { if (item.pageLabel == "home") { cover = item.pageCover; } });return "background: url(" + cover + ") center center / cover no-repeat"; } } };</script>获取源码请关注后私信“分离博客”