个人博客-3(分类功能实现)

本文介绍了如何在博客系统中使用Ajax和jqGrid实现分类的分页显示、添加、删除和修改功能。通过前端与后端交互,实现了无刷新的用户体验,包括使用jqGrid展示表格,利用PageUtil处理分页参数,通过Ajax调用后台接口进行数据操作,以及使用模态框添加和删除分类。同时,详细展示了相关SQL语句和后端接口实现。
摘要由CSDN通过智能技术生成

分类功能

声明:该项目是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>&nbsp;新增
</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>&nbsp;删除
</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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值