1.前言
学习了SSM框架后练手,写这个博客大概花了一个多星期。博客基本功能都有实现,后台代码写地比较简单。前端页面仿造Hexo博主的博客页面(传送门:https://jerryc.me/),由于本人前端技术有限,只能写一个大概的页面。
2.技术总结
前端:bootstrap+layui
(bootstrap主要用于实现响应式,layui写后台管理系统页面)
后台:SpringMVC+Spring+Mybatis
(Maven搭建环境)
数据库:Mysql
3.主要功能
添加文章、管理文章、显示/隐藏文章、添加标签分类、管理标签分类、评论文章、评论管理、分享文章、友链的管理、个人资料更新
4.数据库设计
文章表
分类表
标签表
标签-文章映射表
评论表
友链表
用户表
5.项目结构
6.部分页面功能
(1)博客首页
首页文章列表分页功能用的是layui的分页模块
HTML代码:
JS代码:
<script>
window.onload = function() {
loadData(); //请求文章数据
getPage(); //分页操作
}
var page = 1; //设置首页页码
var limit = 5; //设置一页显示的条数
var count; //总条数
function loadData() {
$.ajax({
type : "post",
url : "PostController/findByPageDesc",//对应controller的URL
async : false,
dataType : 'json',
data : {
"curr" : page,
"nums" : limit,
},
success : function(msg) {
count = msg.count; //设置总条数
var html = [];
var post = msg.data;
for (var i = 0; i < post.length; i++) {
html.push('<div class=" articleboder">'
+ '<div id="'
+ post[i].id
+'" class="col-sm-6 col-xs-12 articleimg" οnclick="javascript:article(this)">'
+ '<img src="./upload/'+post[i].img+'"/></div>'
+ '<div class="col-sm-6 col-xs-12 articleintro">'
+ '<div class="hidden-xs articlehidden"></div>'
+ '<div class="visible-xs articlevisible"></div>'
+ '<div id="'
+ post[i].id
+ '" class="title" οnclick="javascript:article(this)">'
+ post[i].title
+ '</div>'
+ '<div class="articledate">'
+ ' <span><i class="fa fa-calendar" aria-hidden="true"></i><a>'
+ post[i].timeString
+ '</a></span>'
+ ' <span><a>|</a></span>'
+ ' <span><i class="fa fa-inbox article-meta__icon" aria-hidden="true"></i><a>'
+ post[i].typeString
+ '</a></span>'
+ '</div>'
+ '<div class="intro">'
+ post[i].summary
+ '</div>'
+ '</div>'
+ '</div>');
}
$(".content").empty().append(html);
}
});
}
function getPage() {
layui.use('laypage', function() {
var laypage = layui.laypage;
//执行一个laypage实例
laypage.render({
elem : 'demo2', //注意,这里的 demo2 是 ID,不用加 # 号
count : count, //数据总数,从服务端得到
limit : limit, //每页条数设置
theme : '#1E9FFF',
jump : function(obj, first) {
//obj包含了当前分页的所有参数,比如:
console.log(obj.curr); //得到当前页,以便向服务端请求对应页的数据。
console.log(obj.limit); //得到每页显示的条数
page = obj.curr; //改变当前页码
limit = obj.limit;
//首次不执行
if (!first) {
loadData(); //加载数据
}
}
});
});
}
</script>
后台代码:
@RequestMapping("/findByPageDesc")
public @ResponseBody String findByPageDesc(Integer curr, Integer nums) throws JsonProcessingException{
int pagenum=(curr - 1)*nums;
List<Post> posts = postService.findByPageDesc(pagenum, nums);
JSONObject object = new JSONObject();
object.put("code", 0);
object.put("msg", "");
object.put("count", postService.count());
object.put("data", posts);
System.out.println(object.toJSONString());
return object.toJSONString();
}
(2)随机颜色大小标签
HTML代码:
JS代码:
<script>
//分页
window.onload = function() {
loadData(); //请求数据
label();
}
function loadData() {
$.ajax({
type : "post",
url : "TagController/selectAll",//对应controller的URL
async : false,
dataType : 'json',
success : function(msg) {
var html = [];
var tag = msg.data;
for (var i = 0; i < tag.length; i++) {
html.push('<a href="label_detail.jsp?tagid=' + tag[i].id
+ '">' + tag[i].tag + '</a>');
}
$(".content").empty().append(html);
}
});
}
function label() {
$(document).ready(function() {
var obj = $("#wrap a");//获取a标签中的数据
function rand(num) {
//parseInt();将字符串转为整数
//Math.random();生成随机数
return parseInt(Math.random() * num + 1);
}
function randomcolor() {
var str = Math.ceil(Math.random() * 16777215).toString(16);
if (str.length < 6) {
str = "0" + str;
}
return str;
}
for (len = obj.length, i = len; i--;) {
obj[i].style.left = rand(600) + "px";//标签左右间距
obj[i].style.top = rand(400) + "px";//标签上下间距
obj[i].className = "color" + rand(5);
obj[i].style.zIndex = rand(5);//设置元素的堆叠顺序
obj[i].style.fontSize = rand(5) + 18 + "px";//随机字体大小这里是18-23
obj[i].style.color = "#" + randomcolor();//字体颜色
obj[i].style.padding = rand(15) + "px";
}
});
}
</script>
后台返回标签的Json字符串
(3)写文章
文章内容使用的是百度的ueditor,百度ueditor下载JSP版本,把解压后的文件放进项目目录里,查看官方文档进行配置。
(4)文章管理页面
文章管理页面用的是layui的table模块,有编辑、删除和显示(隐藏)三个功能。
表格数据渲染:
HTML代码:
<table id="test" lay-filter="test"></table>
JS代码:
<script type="text/html" id="toolbarDemo">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-sm" lay-event="getCheckData">获取选中行数据</button>
<button class="layui-btn layui-btn-sm" lay-event="getCheckLength">获取选中数目</button>
<button class="layui-btn layui-btn-sm" lay-event="isAll">验证是否全选</button>
</div>
</script>
<script type="text/html" id="barDemo">
<a class="layui-btn layui-btn-sm layui-bg-blue" lay-event="edit">编辑</a>
<a class="layui-btn layui-btn-sm layui-btn-danger" lay-event="del">删除</a>
</script>
<script id="switchTpl" type="text/html">
<input type="checkbox" name="display" value = {{d.display}} lay-skin="switch" lay-text="显示|隐藏" lay-filter="display" {{ d.display == '1' ? 'checked' : '' }}>
</script>
<script src="${pageContext.request.contextPath}/resources/layui/layui.all.js"></script>
<script>
//JavaScript代码区域
layui.use('element', function(){
var element = layui.element;
});
//显示或隐藏文章
layui.use('form',function(){
var form = layui.form
form.on('switch(display)', function(obj){
//根据业务判断是开启还是关闭
var state = obj.elem.checked?0:1;
//取数据(根据索引table.cache里面的行数据)
var index = obj.othis.parents('tr').attr("data-index");
var id = tableData[index].id;
$.ajax({
url: '../PostController/display?id='+id+'&display='+state,
type: "post",
dataType:"json",
success: function(suc) {
if(suc.code === 1) {
if(suc.msg === 0){
layer.msg("文章已隐藏", {
icon: 6
});
}else if(suc.msg === 1){
layer.msg("文章已显示", {
icon: 6
});
}
} else {
layer.msg("设置失败,请稍后再试!", {
icon: 5
});
}
}
});
});
});
var tableData;
layui.use('table', function(){
var table = layui.table;
table.render({
elem: '#test'
,url:'../PostController/findByPage'
,method:'post'
,limits : [5,10,15,20]
,limit : 10
,request: {
pageName: 'curr' ,//页码的参数名称,默认:page
limitName: 'nums' //每页数据量的参数名,默认:limit
}
,toolbar: '#toolbarDemo' //开启头部工具栏,并为其绑定左侧模板
,defaultToolbar: ['filter', 'exports', 'print', { //自定义头部工具栏右侧图标。如无需自定义,去除该参数即可
title: '提示'
,layEvent: 'LAYTABLE_TIPS'
,icon: 'layui-icon-tips'
}]
,title: '用户数据表'
,cols: [[
{type: 'checkbox', fixed: 'left'}
,{field:'id', title:'ID', width:70, unresize: true, sort: true}
,{field:'title', title:'标题',width:328}
,{field:'typeString', title:'分类',width:158}
,{field:'clickhit', title:'点击数',width:125, sort: true}
,{field:'replyhit', title:'评论数',width:125, sort: true}
,{field:'timeString', title:'发表时间',width:188, sort: true}
,{field:'display', title:'显示状态',width:120,templet:"#switchTpl"}
,{fixed: '', title:'操作', fixed: 'right', width:132, toolbar: '#barDemo'}
]]
,page: true
,id:"tableIns"
,done:function(){
tableData = table.cache.tableIns;
}
});
//头工具栏事件
table.on('toolbar(test)', function(obj){
var checkStatus = table.checkStatus(obj.config.id);
switch(obj.event){
case 'getCheckData':
var data = checkStatus.data;
layer.alert(JSON.stringify(data));
break;
case 'getCheckLength':
var data = checkStatus.data;
layer.msg('选中了:'+ data.length + ' 个');
break;
case 'isAll':
layer.msg(checkStatus.isAll ? '全选': '未全选');
break;
//自定义头工具栏右侧图标 - 提示
case 'LAYTABLE_TIPS':
layer.alert('这是工具栏右侧自定义的一个图标按钮');
break;
};
});
//监听行工具事件
table.on('tool(test)', function(obj){
var data = obj.data
,layEvent = obj.event; //获得 lay-event 对应的值
console.log(obj)
switch(layEvent){
case 'del':
var delIndex = layer.confirm('真的删除"' + data.title + '"吗?', function(delIndex) {
$.ajax({
url: '../PostController/delete?id='+data.id,
type: "post",
dataType:"json",
success: function(suc) {
if(suc.code === 1) {
obj.del(); //删除对应行(tr)的DOM结构,并更新缓存
layer.close(delIndex);
console.log(delIndex);
layer.msg("删除成功", {
icon: 1
});
} else {
layer.msg("删除失败", {
icon: 5
});
}
}
});
layer.close(delIndex);
});
break;
case 'edit':
/* $.ajax({
url: '../PostController/editPost?id='+data.id,
type: "get"
}); */
window.location.href="../PostController/editPost?id="+data.id;
break;
}
});
});
</script>
后台查询、删除、显示(隐藏)文章的Controller省略
(5)图片上传功能和回显
“我的友链”和“基本资料”页面都有图片上传,用的是layui的文件上传功能。图书上传成功后,前端页面会回显图片。
HTML代码:
JS代码:
<script src="${pageContext.request.contextPath}/resources/layui/layui.all.js"></script>
<script>
//JavaScript代码区域
layui.use('element', function(){
var element = layui.element;
});
window.onload= function () {
loadImg();
}
layui.use('upload', function(){
var upload = layui.upload;
//普通图片上传
upload.render({
elem: '#test1'
,url: '../UserController/uploadImg'
,accept: 'images'
,acceptMime: 'image/*'
,size: '1024*5'
,before: function(obj){
//预读本地文件示例,不支持ie8
obj.preview(function(index, file, result){
$('#demo1').attr('src', result); //图片链接(base64)
});
}
,done: function(res, input){
layer.msg('头像上传成功',{icon:6});
console.log(res); //如:{"code":0 ,"msg":"","url":"http://cdn.abc.com/123.jpg"'}
document.getElementById("myimg").innerHTML=res.name;
}
});
});
function loadImg(){
$.ajax({
type:"post",
url:"../UserController/photo",//对应controller的URL
dataType: 'json',
success:function(msg){
document.getElementById('demo1').src=msg.name;
}
});
}
</script>
(6)页面加载动画
加载动画并不是真的会等待页面数据加载完成后才隐藏,只是等待1000毫秒后隐藏。
HTML代码:
JS代码:
<script>
window.onload = function() {
setTimeout(function(){
siteLoading.classList.remove('active')
},1000);
}
</script>
CSS样式:
.wrapper {
height: 200px;
width: 200px;
border: 1px solid #fff;
/* 将圆形动画定位到正中 */
position: relative;
}
.wrapper::before,
.wrapper::after{
content: '';
height: 10px;
width: 10px;
background-color: black;
border-radius: 100%;
/* 将圆形动画定位到正中 */
position: absolute;
left: 0;
top: 0;
bottom: 0;
right: 0;
margin: auto;
animation: dada 2s linear infinite;
}
.wrapper::after {
animation-delay: 1s;
}
@keyframes dada {
0% {
height: 0px;
width: 0px;
opacity: 1;
}
100% {
height: 100px;
width: 100px;
opacity: 0;
}
}
.loading {
display: none;
background-color: #fff;
position: fixed;
top: 0;
left: 0;
height: 100%;
width: 100%;
z-index: 999;
justify-content: center;
align-items: center;
}
.loading.active {
display: flex;
}