LayUI+SSM实现一个简单的后台管理系统

该后台管理系统是用于管理视频网站数据的,目前分5个菜单项,这篇博客主要讲述【影片管理】的具体功能和实现

后台代码结构和【影片管理】的界面如下图


 该界面分为上下2部分,【搜索条件】和【影片列表】,2部分所用到的字段都在【Video】实体类中,具体代码如下

package me.xiaomaju.entity;

import lombok.Data;
@Data public class Video { //表sys_video字段 private String id_video; private String name_video; private String describe; private String image; private int id_state; private String id_area; private String dt_show; //关联sys_type查询,拼接 private String type; //关联sys_state查询 private String name_state; //关联sys_area查询 private String area; //查询条件 page和limit要和LayUI的数据表格参数一致 private Integer page;//当前页码 private Integer limit;//每页显示数量 private String dt_begin; private String dt_end; private String id_type; }

 视频表【sys_video】、状态表【sys_state】、地区表【sys_area】、类型表【sys_type】的表结构如下图

 

 

 

 


 LayUI官网于2021年10月左右关闭,查看LayUI相关文档可以到 https://lln.kim/layui/index.html

【影片管理】明显用到LayUI的表单模块和数据表格模块,前端页面代码如下

<div style="width: 80% ">
    <!-- 搜索表单 -->
    <div>
        <fieldset class="layui-elem-field layui-field-title" style="margin-top: 10px;">
            <legend>搜索条件</legend>
        </fieldset>
        <form class="layui-form layui-form-pane" method="post" style="text-align: center">

            <div class="layui-inline">
                <label class="layui-form-label">名称</label>
                <div class="layui-input-inline">
                    <input type="text" name="name_video" required placeholder="请输入名称" class="layui-input">
                </div>
            </div>

            <div class="layui-inline">
                <label class="layui-form-label">类型</label>
                <div class="layui-input-inline">
                    <select name="id_type" id="select_type" class="layui-input">
                        <option value="">请选择类型</option>
                    </select>
                </div>
            </div>

            <div class="layui-inline">
                <label class="layui-form-label">上映时间</label>
                <div class="layui-input-inline">
                    <input type="text" name="dt_begin" id="dt_begin" required placeholder=""
                           class="layui-input">
                </div>
            </div>

            <div class="layui-inline">
                <label class="layui-form-label"></label>
                <div class="layui-input-inline">
                    <input type="text" name="dt_end" id="dt_end" required placeholder=""
                           class="layui-input">
                </div>
            </div>

            <div class="layui-inline">
                <button type="submit" class="layui-btn" lay-submit lay-filter="doSearch">搜索</button>
            </div>
        </form>
    </div>
    <!-- 搜索表单end -->

    <!-- 数据表格 -->
    <div>
        <fieldset class="layui-elem-field site-demo-button">
            <legend>影片列表</legend>
            <div>
                <!-- 表格 -->
                <table class="layui-hide" id="dataTable" lay-filter="dataTable"></table>
            </div>
        </fieldset>

        <!-- 头部工具栏 -->
        <script type="text/html" id="topToolbar">
            <div class="layui-btn-container">
                <button class="layui-btn layui-btn-sm" lay-event="add"><i class="layui-icon layui-icon-add-circle"></i>添加
                </button>
                <button class="layui-btn layui-btn-sm layui-btn-danger" lay-event="batchDelete"><i
                        class="layui-icon layui-icon-delete"></i>批量删除
                </button>
            </div>
        </script>
        <!-- 行工具栏 -->
        <script type="text/html" id="rowToolbar">
            <button class="layui-btn layui-btn-xs" lay-event="edit">编辑</button>
            <button class="layui-btn layui-btn-danger layui-btn-xs" lay-event="delete">删除</button>
        </script>

    </div>
    <!-- 数据表格end -->
</div>

 

 开始写js代码,后面所有的js代码写在layui.use的 { } 里面。

<script>
    layui.use(['form', 'table', 'jquery', 'layer', 'laydate', 'upload'], function () {
        var form = layui.form;
        var table = layui.table;
        var $ = layui.jquery;
        var layer = layui.layer;
        var laydate = layui.laydate;
        var upload = layui.upload;

        //渲染表格组件
        var tableIns = table.render({
            elem: "#dataTable",//绑定表格元素,推荐使用ID选择器
            url: "${pageContext.request.contextPath}/video/video-list",//异步请求地址,加入分页后,默认使用page(当前页码)和limit(每页显示数量)作为参数名称
            page: true,//开启分页
            toolbar: "#topToolbar",
            cols: [[
                {type: "checkbox", fixed: "left", width: 80, align: "center"}
                , {field: 'id_video', title: 'ID', align: "center", width: 170}
                , {field: 'name_video', title: '名称', align: "center", width: 150}
                , {field: 'describe', title: '描述', align: "left", width: 255}
                , {field: 'image', title: '封面', align: "center", width: 150}
                , {field: 'name_state', title: '状态', align: "center", width: 60}
                , {field: 'type', title: '类型', align: "center", width: 140}
                , {field: 'area', title: '地区', align: "center", width: 90}
                , {field: 'dt_show', title: '上映时间', align: "center", width: 120}
                , {title: "操作", toolbar: "#rowToolbar", align: "center", width: 150}
            ]]
        });
})
</script>

 通过url获取后台数据渲染数据表格,数据表格的数据要求4个属性,先新建该实体,代码如下

package me.xiaomaju.entity;

import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
public class LayUIDataGridView {

    private Integer code=0;
    private String msg="";
    private Long count;
    private Object data;

    /**
     * 封装数据表格
     * @param count
     * @param data
     */
    public LayUIDataGridView(Long count, Object data) {
        this.count = count;
        this.data = data;
    }
    
}

接下来通过 url: "${pageContext.request.contextPath}/video/video-list"  写后台代码

controller层代码如下

@Controller
@RequestMapping("/video")
public class VideoController {

    private static final Logger logger = org.apache.log4j.Logger.getLogger(VideoController.class);

    @Autowired
    private VideoService videoService;

    @RequestMapping(value = "/video-list")
    @ResponseBody
    public LayUIDataGridView findAll(Video video) {
        //设置分页信息(当前页码,每页条数)
        PageHelper.startPage(video.getPage(), video.getLimit());
        //查询数据
        List<Video> videoList = videoService.findByAttributes(video);
        //创建分页对象
        PageInfo<Video> pageInfo = new PageInfo<Video>(videoList);
        //返回数据
        return new LayUIDataGridView(pageInfo.getTotal(), pageInfo.getList());
    }

}
pagehelper是一个分页插件,在mybatis.xml配置一下即可,配置如下
<plugins>
    <plugin interceptor="com.github.pagehelper.PageInterceptor">
<!--        分页合理化参数,默认false,设置为true是,pageNum<=0查第一页,pageNum>pages查最后一页-->
        <property name="reasonable" value="true"/>
    </plugin>
</plugins>

service层代码如下

@Service
public class VideoServiceImpl implements VideoService {

    private static final Logger logger = org.apache.log4j.Logger.getLogger(VideoServiceImpl.class);

    @Autowired
    private IVideoMapper videoMapper;

    @Override
    public List<Video> findByAttributes(Video video) {
      return videoMapper.findByAttributes(video);
}

}

mapper层代码如下

package me.xiaomaju.mapper;

import me.xiaomaju.entity.Area;
import me.xiaomaju.entity.Type;
import me.xiaomaju.entity.Video;
import org.apache.ibatis.annotations.*;

import java.util.List;

public interface IVideoMapper {

    @Select("<script> " +
            "select v.id_video, v.name_video,v.describe,v.image,v.id_state,v.id_area, v.dt_show, s.name_state,a.area from sys_video v " +
            "left join sys_state s on v.id_state=s.id_state " +
            "left join sys_area a on v.id_area=a.id_area " +
            " </script> ")
    public List<Video> findByAttributes(Video video);

}

这样查出了【sys_video】表的所有字段,关联状态和地区表查出具体中文状态和地区。视频与类型则是多对多关系,这种多对多关系的数据用一张中间表来存储。

中间表【sys_video_type】只有2列,id_video和id_type,id_type又是类型表【sys_type】的主键。

所以可以在service层用id_video为入参,查询类型的中文,并将多个类型用逗号拼接在一起,代码如下

    @Override
    public List<Video> findByAttributes(Video video) {
        List<Video> videoList = videoMapper.findByAttributes(video);
        if (videoList.size() < 1)
            return null;
        //封装类型 type
        for (Video _video : videoList) {
            List<String> typeList = videoMapper.findVideoType(_video.getId_video());
            String type = StringUtil.getListStrs(typeList);
            _video.setType(type);
        }
      return videoList;
    } 

  mapper层查询类型代码如下

    @Select("select t.name_type from sys_type t left join sys_video_type vt ON t.id_type= vt.id_type where vt.id_video=#{id} ")
    public List<String> findVideoType(String id);

通过上述表关系在那几张表造一些数据,页面数据表格就有对应数据显示了。

 


 接下来实现上方【搜索条件】的查询功能,效果如下图

其中2个时间项用到了LayUI的日期与时间选择模块,需要进行组件渲染。搜索表单中【类型】下拉框的数据是动态的,后面介绍新增与编辑功能时会将对应js代码写出来。

        //渲染日期组件
        laydate.render({
            elem: "#dt_begin",
        });
        laydate.render({
            elem: "#dt_end"
        });
        //监听搜索按钮提交事件
        form.on("submit(doSearch)", function (data) {
            tableIns.reload({
                where: data.field,//查询条件
                page: {
                    curr: 1
                }
            });
            //禁止页面刷新
            return false;
        });

需要注意的是,搜索和一开始的数据表格渲染走的是相同的后台代码,即"${pageContext.request.contextPath}/video/video-list"。

搜索表单中的字段都在Video实体中,所以直接在mapper层的代码中新增过滤条件,将原方法修改后的代码如下

    @Select("<script> " +
            "select distinct v.id_video, v.name_video,v.describe,v.image,v.id_state,v.id_area, v.dt_show, s.name_state,a.area from sys_video v " +
            "left join sys_state s on v.id_state=s.id_state " +
            "left join sys_area a on v.id_area=a.id_area " +
            "left join sys_video_type vt on v.id_video=vt.id_video " +
            " <where> " +
            " <if test=\"name_video != null and name_video !='' \"> v.name_video=#{name_video} </if> " +
            " <if test=\" id_type != null and id_type !='' \"> and vt.id_type=#{id_type} </if> " +
            " <if test=\"dt_begin != null and dt_begin!='' \"> <![CDATA[ and v.dt_show >= #{dt_begin} ]]> </if> " +
            " <if test=\"dt_end != null and dt_end!='' \"> <![CDATA[ and v.dt_show <= #{dt_end} ]]> </if> " +
            " <if test=\"id_state != 0\"> AND v.id_state=#{id_state}</if> " +
            " </where> " +
            " </script> ")
    public List<Video> findByAttributes(Video video);

 


 接下来实现新增和编辑的功能,新增和编辑用的是同一个表单,效果如下图

 

 首先写这个表单的前台代码

<!-- 弹出层 -->
<div id="addOrUpdateCardView" style="display: none;margin: 10px">
    <form id="dataForm" method="post" class="layui-form  layui-form-pane" lay-filter="dataForm">
        <!-- 隐藏域,保存当前ID -->
        <input type="hidden" name="id_video">

        <div class="layui-form-item">
            <label class="layui-form-label">名称</label>
            <div class="layui-input-block">
                <input type="text" name="name_video" required lay-verify="required|name_video" placeholder="请输入"
                       autocomplete="off" class="layui-input">
            </div>
        </div>

        <div class="layui-form-item layui-form-text">
            <label class="layui-form-label">描述</label>
            <div class="layui-input-block">
                <textarea name="describe" lay-verify="required|describe" class="layui-textarea"></textarea>
            </div>
        </div>

        <div class="layui-form-item">
            <label class="layui-form-label">封面</label>
            <div class="layui-input-block">
                <button type="button" class="layui-btn" id="btn-img">上传图片</button>
                <img class="layui-upload-img" width="50" height="50">
                <!-- 隐藏域,保存后台传回来的图片保存路径 -->
                <input type="hidden" id="imgPath" name="image" value="">
            </div>
        </div>

        <div class="layui-form-item">
            <label class="layui-form-label">地区</label>
            <div class="layui-input-block">
                <select name="id_area" class="layui-input">
                    <option value="">请选择地区</option>
                </select>
            </div>
        </div>

        <div class="layui-form-item">
            <label class="layui-form-label">类型</label>
            <div class="layui-input-block" id="chkBox_type">

            </div>
        </div>

        <div class="layui-form-item">
            <label class="layui-form-label">上映时间</label>
            <div class="layui-input-block">
                <input type="text" name="dt_show" id="dt_show" lay-verify="required|dt_show" placeholder="请输入"
                       class="layui-input">
            </div>
        </div>

        <div class="layui-form-item">
            <label class="layui-form-label">状态</label>
            <div class="layui-input-block" id="sexRadio">
                <input type="radio" name="id_state" value="1" title="禁用">
                <input type="radio" name="id_state" value="2" title="正常" checked>
                <input type="radio" name="id_state" value="3" title="热播">
            </div>
        </div>

        <div class="layui-form-item">
            <div class="layui-input-block">
                <button class="layui-btn" lay-submit lay-filter="doSubmit" id="formSubmit">立即提交</button>
                <button type="reset" class="layui-btn layui-btn-primary">重置</button>
            </div>
        </div>

    </form>
</div>
<!-- 弹出层end-->

新增和编辑对应的js代码,【上映日期】需要进行日期组件渲染

        laydate.render({
            elem: "#dt_show",
            type: "date"//默认
        });
        //监听表格头部工具栏事件
        table.on("toolbar(dataTable)", function (obj) {
            switch (obj.event) {
                //添加
                case 'add':
                    openAddWindow();
                    break;
            }
        });

        //监听表格行工具栏事件
        table.on("tool(dataTable)", function (obj) {
            switch (obj.event) {
                //编辑
                case 'edit':
                    openUpdateWindow(obj.data);
                    break;
            }
        });
        var url;//提交地址
        var mainIndex;//窗口索引

        //打开编辑窗口
        function openUpdateWindow(data) {

            //清空表单数据
            $("#dataForm")[0].reset();

            //将该行的类型给表单勾选上
            var types = data.type.split(",");
            var typeCheckbox = $("input:checkbox[name='id_types']");
            for (var j = 0; j < types.length; j++) {
                for (var i = 0; i < typeCheckbox.length; i++) {
                    if (typeCheckbox[i].title == types[j]) {
                        typeCheckbox[i].checked = true;
                    }
                }
                form.render();  //更新渲染
            }

            mainIndex = layer.open({
                type: 1,//弹出层类型
                title: "影片信息修改",
                area: ['800px', '600px'],
                content: $("#addOrUpdateCardView"),//引用的窗口代码
                success: function () {
                    //表单数据回显
                    // console.log(data);
                    form.val("dataForm", data);
                    //修改请求
                    url = "${pageContext.request.contextPath}/video/update";
                }
            });
        }

        //打开添加窗口
        function openAddWindow() {
            mainIndex = layer.open({
                type: 1,//弹出层类型
                title: "添加影片",
                area: ['800px', '600px'],
                content: $("#addOrUpdateCardView"),//引用的窗口代码
                success: function () {
                    //清空表单数据
                    $("#dataForm")[0].reset();
                    url = "${pageContext.request.contextPath}/video/add";
                }
            });
        }
        //监听弹出层的表单提交事件
        form.on("submit(doSubmit)", function (data) {

            if ($("input:checkbox[name='id_types']:checked").length > 3 || $("input:checkbox[name='id_types']:checked").length < 1) {
                layer.msg('类型为必填项,且最多只能选3项!', {icon: 5});
                return false;
            }

            //获取checkbox[name='id_types']的值,获取所有选中的复选框,并将其值放入数组中
            var arr = new Array();
            $("input:checkbox[name='id_types']:checked").each(function (i) {
                arr[i] = $(this).val();
            });
            //  替换 data.field.id_types的数据为拼接后的字符串
            data.field.id_types = arr.join(",");

            $.post(url, data.field, function (result) {
                if (result.success) {
                    layer.alert(result.message, {icon: 1});
                    //关闭窗口
                    layer.close(mainIndex);
                    //刷新数据表格
                    tableIns.reload();
                } else {
                    layer.alert(result.message, {icon: 2});
                }
            }, "json");
            return false;
        });

编辑功能的controller层代码如下

    @RequestMapping(value = "/update", produces = "text/html;charset=UTF-8")
    @ResponseBody
    public String update(Video video, String[] id_types, HttpSession session) {
        Map<String, Object> map = new HashMap<String, Object>();
        int result = videoService.update(video);

        // 先删除类型再新增
        int types1 = videoService.deleteVideoType(video.getId_video());
        int types = videoService.addVideoType(video.getId_video(), id_types);

        if (result > 0 && types > 0) {
            map.put("success", true);
            map.put("message", "修改成功");

            Admin admin = (Admin) session.getAttribute("loginAdmin");
            logger.info("修改影片的人员为:" + admin.getName_admin() + ";影片ID为:" + video.getId_video());
        } else {
            map.put("success", false);
            map.put("message", "修改失败");
        }
        return JSON.toJSONString(map);
    }

新增功能的controller层代码与编辑类似,新增的是直接新增类型,即往【sys_video_video】插入数据;而编辑需要先删除类型再新增类型,因为【id_types】的长度(表单中类型复选框勾选的数量)不是固定的。

而直接在新增功能的controller层代码中【video.getId_video()】是为空的,需要在mapper层加一个注解,详情见我下一篇博客。

 

搜索表单中的下拉框【类型】,新增功能和编辑功能的弹出层中的下拉框【地区】和复选框【类型】的数据是动态的,需要向后台发送Ajax请求,js代码如下

        //发送Ajax请求查询类型
        $.get("${pageContext.request.contextPath}/video/query-type", function (result) {
            var duoxuan = "";
            var xiala = "";
            //循环遍历集合
            for (let i = 0; i < result.length; i++) {
                duoxuan += "<input type=\"checkbox\" name=\"id_types\" title='" + result[i].name_type + "' value='" + result[i].id_type + "' lay-skin=\"primary\" lay-verify=\"fuxuan\"> "
                xiala += "<option value='" + result[i].id_type + "'>" + result[i].name_type + "</option>"
            }
            //将网页代码追加到下拉列表和多选框中
            $("[id='chkBox_type']").append(duoxuan);
            $("[id='select_type']").append(xiala);
            //更新渲染select下拉框
            form.render("select");
        }, "json");

        //发送Ajax请求查询地区
        $.get("${pageContext.request.contextPath}/video/query-area", function (result) {
            var html = "";
            //循环遍历集合
            for (let i = 0; i < result.length; i++) {
                html += "<option value='" + result[i].id_area + "'>" + result[i].area + "</option>"
            }
            //将网页代码追加到下拉列表中
            $("[name='id_area']").append(html);
            //更新渲染select下拉框
            form.render("select");
        }, "json");

后台controller调用service,service 调用mapper查询地区和类型,返回对应实体的List集合即可。

如果你的前端页面和我一样是用jsp写的,不出意料的话,你的复选框显示可能有问题,在<html>标签上面加上 <!DOCTYPE html> 即可。

 

下面实现弹出层表单中的图片上传功能,js代码如下

        //图片上传
        var uploadInst = upload.render({
            elem: "#btn-img"     /*根据绑定id,打开本地图片*/
            , url: "${pageContext.request.contextPath}/video/upload-img"  /*上传后台接受接口*/
            , done: function (res) {
                if (res.success) {
                    $('#imgPath').attr('value', res.message); 
                } else {
                    layer.alert(res.message, {icon: 2});
                }
            }
        });

后台controller层代码如下

    @RequestMapping(value = "/upload-img", produces = "text/html;charset=UTF-8")
    @ResponseBody
    public String uploadImg(@RequestParam(value = "file", required = false) MultipartFile file, HttpServletRequest request) {
        Map<String, Object> map = new HashMap<String, Object>();
        String originalFilename = file.getOriginalFilename(); //  获取文件上传名
        String path = request.getServletContext().getRealPath("/") +"static\\img\\"+ originalFilename;
        logger.info("上传路径:"+path );
        try {
            file.transferTo(new File(path));
            map.put("success", true);
            map.put("message", path);
        } catch (IOException e) {
            map.put("success", false);
            map.put("message", "图片上传失败");
        }
        return JSON.toJSONString(map);
    }

如果上传成功,将图片路径传到前台,放到弹出层表单中【封面】后的隐藏域中。

 


接下来实现行删除和批量删除,效果如下图

 

新增和批量删除在表格头部工具栏事件监听到,而行删除和编辑在表格行工具栏事件监听到,js代码如下

        //监听表格头部工具栏事件
        table.on("toolbar(dataTable)", function (obj) {
            switch (obj.event) {
                //添加
                case 'add':
                    openAddWindow();
                    break;
                //批量删除
                case 'batchDelete':
                    batchDelete();
                    break;
            }
        });

        //监听表格行工具栏事件
        table.on("tool(dataTable)", function (obj) {
            switch (obj.event) {
                //编辑
                case 'edit':
                    openUpdateWindow(obj.data);
                    break;
                //删除
                case 'delete':
                    deleteById(obj.data);
                    break;
            }
        });
        //单个删除
        function deleteById(data) {
            //提示用户确认是否删除
            layer.confirm('真的要删除吗?', {icon: 3, title: '提示'}, function (index) {
                //发送ajax请求
                $.post("${pageContext.request.contextPath}/video/deleteById", {"id_video": data.id_video}, function (result) {
                    if (result.success) {
                        layer.alert(result.message, {icon: 1});
                        //刷新数据表格
                        tableIns.reload();
                    } else {
                        layer.alert(result.message, {icon: 2});
                    }
                }, "json");
                //关闭提示框
                layer.close(index);
            });
        }

        //批量删除
        function batchDelete() {
            //获取表格对象
            var checkStatus = table.checkStatus('dataTable'); 
            //判断是否有选中行
            if (checkStatus.data.length > 0) {
                //定义数组,保存选中行的ID
                var idArr = [];
                //循环遍历获取选中行(目的是获取选中的每一行的ID值)
                for (let i = 0; i < checkStatus.data.length; i++) {
                    //将选中的ID值添加到数组的末尾
                    idArr.push(checkStatus.data[i].id_video);
                }
                //将数组转成字符串
                var ids = idArr.join(",");
                //提示用户是否删除
                layer.confirm('真的要删除吗?', {icon: 3, title: "提示"}, function (index) {
                    //发送ajax请求
                    $.post("${pageContext.request.contextPath}/video/batch-delete", {"ids": ids}, function (result) {
                        if (result.success) {
                            layer.alert(result.message, {icon: 1});
                            //刷新数据表格
                            tableIns.reload();
                        } else {
                            layer.alert(result.message, {icon: 2});
                        }
                    }, "json");

                    //关闭提示框
                    layer.close(index);
                });
            } else {
                layer.msg("请选择要删除的数据");
            }
        }

 单个删除和批量删除的controller层代码如下

   @RequestMapping(value = "/deleteById", produces = "text/html;charset=UTF-8")
    @ResponseBody
    public String deleteById(String id_video) {
        Map<String, Object> map = new HashMap<String, Object>();
        int result = videoService.deleteById(id_video);
        if (result > 0) {
            map.put("success", true);
            map.put("message", "删除成功");
        } else {
            map.put("success", false);
            map.put("message", "删除失败");
        }
        return JSON.toJSONString(map);
    }

    @RequestMapping(value = "/batch-delete", produces = "text/html;charset=UTF-8")
    @ResponseBody
    public String batchDelte(String ids) {
        Map<String, Object> map = new HashMap<String, Object>();
        int result = videoService.batchDelte(ids);
        if (result > 0) {
            map.put("success", true);
            map.put("message", "删除成功");
        } else {
            map.put("success", false);
            map.put("message", "删除失败");
        }
        return JSON.toJSONString(map);
    }

 批量删除需要在service层中将ids按逗号分隔成数组再删除。

 


 完毕

 

posted @ 2022-02-10 14:30  请叫我小马驹  阅读(718)  评论(1编辑  收藏  举报