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按逗号分隔成数组再删除。
完毕
浙公网安备 33010602011771号