分类功能
声明:该项目是GitHub上的开源项目,本人已购买作者相关课程,仅供个人学习使用。
课程链接:https://www.lanqiao.cn/courses/1367
使用技术
- jqgrid
- bootstrap
- ajax
分类介绍
- 在博客系统中,分类模块的设计是不可缺少的,我们在各大博客网站中都能够看到这个模块设计,在浏览文章的过程中,我们也会挑选出我们感兴趣类别中的文章进行阅读,比如你偏爱前端类别下的内容,那就可以针对性的浏览所有前端类别下的文章,因此对博文进行归类是十分必要的。
表结构设计
- 首先将表结构确定下来,每篇文章都会被归类到一个类别下,一个类别下会有多篇文章,分类实体与文章实体的关系是一对多的关系,因此在表结构设计时,在文章表中设置一个分类关联字段即可,分类表只需要将分类相关的字段定义好,分类实体与文章实体的关系交给文章表来维护即可
blog_category
CREATE TABLE `blog_category` (
`category_id` INT(11) NOT NULL AUTO_INCREMENT COMMENT '分类表主键',
`category_name` VARCHAR(50) NOT NULL COMMENT '分类的名称',
`category_icon` VARCHAR(50) NOT NULL COMMENT '分类的图标',
`category_rank` INT(11) NOT NULL DEFAULT '1' COMMENT '分类的排序值 被使用的越多数值越大',
`is_deleted` TINYINT(4) NOT NULL DEFAULT '0' COMMENT '是否删除 0=否 1=是',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`category_id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
软删除
在删除操作时我们并不是执行 delete 语句,而是将需要删除的这条记录的 is_deleted 字段修改为 1,这样就表示该行记录已经被执行了删除操作,那么其他的 select 查询语句就需要在查询条件中添加 is_deleted = 0 将“被删除”的记录给过滤出去。
BlogCategory
package com.rm.pojo;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.util.Date;
//博客分类实体
@Data
public class BlogCategory {
private Integer categoryId;
private String categoryName;
private String categoryIcon;
private String categoryRank;
private Byte isDeleted;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date createTime;
}
接口介绍
- 为了让页面体验更加友好,就不采用传统的 MVC 跳转模式,一个功能一个页面,这种交互感觉有些浪费,翻页的时候,翻一页跳转一次也比较繁琐,添加或者新增的时候也要进行页面跳转,所以这些功能的实现就采用通过 Ajax 异步与后端交互数据,当使用者点击了页面上的元素,此时触发响应的 js 事件,进而通过 Ajax 的方式向后端请求数据,前端再根据后端返回的数据内容去进行响应的展示逻辑,在前面的个人信息修改中其实用到的就是这种方式。
分类模块在后台管理系统中有 5 个接口,分别是:
- 分类列表分页接口
- 添加分类接口
- 根据 id 获取单条分类记录接口
- 删除分类接口
分类分页
使用jqGrid实现表格的展示及分页功能
使用前需要导入
- jquery.js
- jqgrid.js
- jqgrid国际化.js
- bootstrap.js
前端页面展示效果
前端传递的数据
//传递给后端的数据,page默认1,limit是rowNum设置的值,默认10
prmNames: {
page: "page",
rows: "limit"
},
工具类
PageUtil
根据前端传递的当前页page和每页显示多少条数据limit
- 计算出start(从第几条数据开始查)
- currentPage
- pageSize
import java.util.LinkedHashMap;
import java.util.Map;
//根据前端获取的参数创建一个map集合
//该集合包括开始索引,起始页,每页显示数据数
@Data
public class PageUtil extends LinkedHashMap<String, Object> {
//当前页码
private int currentPage;
//每页条数
private int pageSize;
//根据前端传递的参数构造一个LinkedHashMap存放开始索引,
public PageUtil(Map<String, Object> params) {
this.putAll(params);
//分页参数
this.currentPage = Integer.parseInt(params.get("page").toString());
this.pageSize = Integer.parseInt(params.get("limit").toString());
this.put("start", (currentPage - 1) * pageSize);
this.put("page", currentPage);
this.put("pageSize", pageSize);
}
}
PageResult
package com.rm.util;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
//分页工具类
@Data
public class PageResult implements Serializable {
//属性名不能修改,和js文件中的属性相同
//如果不同,可以使用jsonReader进行映射
//总记录数
private int records;
//每页记录数
private int pageSize;
//总页数
private int total;
//当前页数
private int page;
//列表数据
private List<?> rows;
public PageResult(List<?> list, int total, int pageSize, int currentPage) {
//数据
this.rows = list;
//总数据数
this.records = total;
//每页展示几条数据
this.pageSize = pageSize;
//当前页
this.page = currentPage;
//总页数ceil:向上取整,小数部分直接舍去如:16/5 就是共4页
this.total = (int) Math.ceil((double)records/pageSize);
}
}
mapper接口
//分类排序
List<BlogCategory> findCategoryList(PageUtil pageUtil);
//查询总数
int getTotalCategories();
<!--page是当前页,默认1。limit是每页显示多少条数据-->
<select id="findCategoryList" parameterType="map" resultType="BlogCategory">
select *
from blog_category
where is_deleted=0
order by category_rank desc,create_time desc
<if test="start!=null and pageSize!=null">
limit #{start},#{pageSize}
</if>
</select>
<select id="getTotalCategories" resultType="int">
select count(*) from blog_category
where is_deleted=0
</select>
service实现类
@Override
public List<BlogCategory> findCategoryList(PageUtil pageUtil) {
return categoryMapper.findCategoryList(pageUtil);
}
@Override
public int getTotalCategories() {
return categoryMapper.getTotalCategories();
}
@Override
public PageResult getBlogCategoryPage(PageUtil pageUtil) {
//查询所有的数据
List<BlogCategory> categoryList = categoryMapper.findCategoryList(pageUtil);
//查询总数
int total = categoryMapper.getTotalCategories();
//根据总数,当前页,每页展示的条数创建一个pageResult
PageResult pageResult =
new PageResult(categoryList,total, pageUtil.getPageSize(),pageUtil.getPage());
return pageResult;
}
controller层
/**
* 分类列表
*/
@PostMapping("/categories/list")
@ResponseBody
public PageResult list(@RequestParam Map<String, Object> params) {
//根据前端传递的起始页和每页显示的条数,创建一个map集合
PageUtil pageUtil = new PageUtil(params);
//结果以json数据发送给前端,包括(列表数据,总记录数,总页数,每页多少条数据,当前页)
return categoryService.getBlogCategoryPage(pageUtil);
}
js文件
$(function () {
//标签列表展示
$("#jqGrid").jqGrid({
//从服务器接收数据的请求
url: '/admin/categories/list',
//接收到的数据格式
datatype: "json",
mtype:"POST",
/*
colModel:表格的列属性
label:如果colNames为空则用此值来作为列的显示名称,如果都没有设置则使用name值
*/
colNames:["id","分类名称","分类图标",'添加时间'],
colModel: [
{name: 'categoryId', key: true, hidden: true},
{name: 'categoryName'},
{name:'categoryIcon',formatter:imgFormatter},
{name: 'createTime'}
],
rowNum: 5,
styleUI: 'Bootstrap',
autowidth: true,
multiselect: true,
height:443,
//分页导航条位置
pager: "#jqGridPager",
//传递给后端的数据,page默认1,limit是rowNum设置的值,默认10
prmNames: {
page: "page",
rows: "limit"
},
gridComplete: function () {
//隐藏grid底部滚动条
$("#jqGrid").closest(".ui-jqgrid-bdiv").css({"overflow-x": "hidden"});
}
});
jQuery("select.image-picker").imagepicker({
hide_select: false
});
jQuery("select.image-picker.show-labels").imagepicker({
hide_select: false,
show_label: true
});
$(window).resize(function () {
$("#jqGrid").setGridWidth($(".card-body").width());
});
var container = jQuery("select.image-picker.masonry").next("ul.thumbnails");
container.imagesLoaded(function () {
container.masonry({
itemSelector: "li"
});
});
});
//图标格式化函数
function imgFormatter(cellvalue) {
return "<a href='" + cellvalue + "'>
<img src='" + cellvalue + "' height=\"64\" width=\"64\" alt='icon'/></a>";
}
添加分类
category.html
<button class="btn btn-info" onclick="categoryAdd()">
<i class="fa fa-plus"></i> 新增
</button>
category.js
function categoryAdd() {
reset();
$('.modal-title').html('分类添加');
//显示模态框
$('#categoryModal').modal('show');
}
function reset() {
//标签名默认空
$("#categoryName").val('');
//标签图片默认选择第一个
$("#categoryIcon option:first").prop("selected", 'selected');
}
模态框
- 显示图标需要导入
image-picker.min.js
category.html
<div class="modal-body">
<form id="categoryForm">
<div class="form-group">
<div class="alert alert-danger" id="edit-error-msg" style="display: none;">
错误信息展示栏。
</div>
</div>
<input type="hidden" class="form-control" id="categoryId" name="categoryId">
<div class="form-group">
<label for="categoryName" class="control-label">分类名称:</label>
<input type="text" class="form-control" id="categoryName" name="categoryName"
placeholder="请输入分类名称" required="true">
</div>
<div class="form-group">
<label for="categoryIcon" class="control-label">分类图标:</label>
<select class='form-control select2 image-picker' id="categoryIcon"
name="categoryIcon">
<option data-img-src='00.png' value='00.png'> 默认图标</option>
<option data-img-src='01.png' value='/01.png'> 图标1</option>
。。。
</select>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">取消</button>
<button type="button" class="btn btn-primary" id="saveButton">确认</button>
</div>
点击确认按钮,执行添加操作
category.js
//绑定modal上的保存按钮
$('#saveButton').click(function () {
var categoryName = $("#categoryName").val();
if (!validCN_ENString2_18(categoryName)) {
$('#edit-error-msg').css("display", "block");
$('#edit-error-msg').html("请输入符合规范的分类名称!");
} else {
//表单提交的所有数据序列化
var params = $("#categoryForm").serialize();
var url = '/admin/categories/save';
$.ajax({
type: 'POST',//方法类型
url: url,
data: params,
success: function (result) {
if (result>0) {
//隐藏模态栏
$('#categoryModal').modal('hide');
swal("保存成功", {
icon: "success"
});
reload();
}
},
error: function () {
swal("操作失败", {
icon: "error"
});
}
});
}
});
public.js
/**
* 正则匹配2-18位的中英文字符串
*
* @param str
* @returns {boolean}
*/
function validCN_ENString2_18(str) {
var pattern = /^[a-zA-Z0-9-\u4E00-\u9FA5_,, ]{2,18}$/;
if (pattern.test(str.trim())) {
return (true);
} else {
return (false);
}
}
controller
/**
* 分类添加
*/
@PostMapping("/categories/save")
@ResponseBody
public int save(@RequestParam("categoryName") String categoryName,
@RequestParam("categoryIcon") String categoryIcon) {
int i = categoryService.addCategory(categoryName, categoryIcon);
return i;
}
sql语句
<?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.rm.dao.CategoryMapper">
<insert id="addCategory" parameterType="blogCategory">
insert into rm_blog.blog_category (category_name,category_icon)
values (#{categoryName},#{categoryIcon});
</insert>
</mapper>
删除分类
勾选前面的单选框,并点击删除,可以删除分类
- 可以勾选多个
//jqgrid生成表格的时候需要添加该属性
multiselect: true
前端按钮
<button class="btn btn-danger" onclick="deleteCategory()"><i
class="fa fa-trash-o"></i> 删除
</button>
js
function deleteCategory() {
//获取选择行的id,可以多选
var ids = getSelectedRows();
$.ajax({
type: "POST",
url: "/admin/categories/delete",
contentType: "application/json",
data: JSON.stringify(ids),
success: function (r) {
if (r > 0) {
alert("删除成功");
//重加载页面
$("#jqGrid").trigger("reloadGrid");
} else {
alert("删除失败")
}
}
});
};
dao层
//通过id删除分类(软删除)
int deleteByPrimaryKey(Integer categoryId);
<!--根据id删除分类(软删除)-->
<update id="deleteByPrimaryKey" parameterType="_int">
update rm_blog.blog_category set is_deleted =1 where category_id=#{id} ;
</update>
service层
//通过id删除分类
int deleteByPrimaryKey(Integer categoryId);
@Override
public int deleteByPrimaryKey(Integer categoryId) {
return categoryMapper.deleteByPrimaryKey(categoryId);
}
controller层
接收选择的ids,遍历删除
//分类删除
@PostMapping("/categories/delete")
@ResponseBody
public int delete(@RequestBody Integer[] ids) {
int i=0;
for (Integer id : ids) {
i=categoryService.deleteByPrimaryKey(id);
}
return i;
}