基于Ruoyi和PostgreSQL的统一POI分类后台管理实战
文章以Ruoyi开发框架为前提,重点讲解如何在Ruoyi框架的基础之上开发一款使用于多平台POI分类管理的Web管理工具。
目录
前言
在之前的系列内容,我们已经实现了百度和高德两个平台的POI分类的数据库设计与实现,同时也分别针对性的介绍了如何批量的自动构建百度和高德两个平台的POI分类信息管理。博文地址如下表所示:
博文序号 | 博文地址 |
1 | 基于PostgreSQL的百度或高德等POI多层级分类的数据库设计 |
2 | 基于ApachePOI实现高德POI分类快速导入PostgreSQL数据库实战 |
3 | 基于ApachePOI实现百度POI分类快速导入PostgreSQL数据库实战 |
以上虽然完成了多层级POI分类的数据库设计和多层级实例数据模型及存储的构建,但是尚未开发一套完整的可以Web操作的系统和界面,以方便对相关的分类进行调整和完善。通过对上述的POI分类进行Web管理,不仅直观的展示POI分类管理信息,同时可以对这些分类信息构建物理关联模型。为后续的实体映射提取奠定数据挖掘基础。
本文即在此背景下产生,文章以Ruoyi开发框架为前提,重点讲解如何在Ruoyi框架的基础之上开发一款使用于多平台POI分类管理的Web管理工具。文章详细的介绍了后台的MVC各层级的设计与开发,同时也介绍了前端的设计与实现。通过本文,不仅可以让大家掌握如何对POI数据进行准确分类,同时熟悉使用Web的方式来对数据进行管理和展示。
一、Java后台的设计与实现
本节将重点介绍如何使用Java进行后台业务逻辑的设计与实现。本实例采用的开发模式是经典的MVC模型,即模型层、视图层和控制层,每一层分工协作,共同完成业务需求。本小节也是在此基础上进行展示的,分别从三层来详细介绍如何实现POI的分类管理。
1、模型层实现
模型层是业务系统中负责与数据库进行交互的实体载体。在与数据库的交互过程中以及与前端的业务实现中,模型层实体都是非常重要的对象。对于POI分级分类管理的物理表模型如下:
CREATE TABLE "public"."biz_poi_category" (
"pk_id" int8 NOT NULL,
"category_name" varchar(100) NOT NULL DEFAULT ''::character varying,
"origin_code" varchar(100) NOT NULL DEFAULT ''::character varying,
"parent_id" int8 NOT NULL DEFAULT 0,
"ancestors" varchar(2000) NOT NULL DEFAULT ''::character varying,
"category_english_name" varchar(100) NOT NULL DEFAULT ''::character varying,
"platform" varchar(10) NOT NULL DEFAULT ''::character varying,
"order_num" int4 NOT NULL DEFAULT 0,
"status" int2 NOT NULL DEFAULT 0,
"del_flag" int2 NOT NULL DEFAULT 0,
"create_by" varchar(64) NOT NULL DEFAULT ''::character varying,
"create_time" timestamp(6),
"update_by" varchar(64) NOT NULL DEFAULT ''::character varying,
"update_time" timestamp(6),
CONSTRAINT "pk_biz_poi_category" PRIMARY KEY ("pk_id")
);
CREATE INDEX "idx_biz_poi_category_name" ON "public"."biz_poi_category" USING btree (
"category_name" COLLATE "pg_catalog"."default" "pg_catalog"."text_ops" ASC NULLS LAST
);
COMMENT ON COLUMN "biz_poi_category"."pk_id" IS 'pk_id';
COMMENT ON COLUMN "biz_poi_category"."category_name" IS '分类名称';
COMMENT ON COLUMN "biz_poi_category"."origin_code" IS '原始分类code';
COMMENT ON COLUMN "biz_poi_category"."parent_id" IS '父分类id';
COMMENT ON COLUMN "biz_poi_category"."ancestors" IS '祖级列表';
COMMENT ON COLUMN "biz_poi_category"."category_english_name" IS '分类名称-英文';
COMMENT ON COLUMN "biz_poi_category"."platform" IS '所属平台,bd表示百度,amap 表示高德';
COMMENT ON COLUMN "biz_poi_category"."order_num" IS '排序号';
COMMENT ON COLUMN "biz_poi_category"."status" IS '状态';
COMMENT ON COLUMN "biz_poi_category"."del_flag" IS '栅格标记';
COMMENT ON COLUMN "biz_poi_category"."create_by" IS '创建者';
COMMENT ON COLUMN "biz_poi_category"."create_time" IS '创建时间';
COMMENT ON COLUMN "biz_poi_category"."update_by" IS '更新人';
COMMENT ON COLUMN "biz_poi_category"."update_time" IS '更新时间';
COMMENT ON TABLE "biz_poi_category" IS 'POI分类信息表,完全兼容百度、高德等平台,还可以扩展至其它平台';
与上述物理模型对应的Java实体定义代码如下:
package com.yelang.project.poisubject.poi.domain;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.yelang.framework.web.domain.BaseEntity;
/**
* - POI分类信息表
*
* @author 夜郎king
*/
public class PoiCategory extends BaseEntity {
private static final long serialVersionUID = -541357895123151791L;
/** 分类 ID */
private Long pkId;
/** 父分类ID */
private Long parentId;
/** 祖级列表 */
private String ancestors;
/** 分类名称 */
private String categoryName;
/** 英文分类名称 */
private String categoryEnglishName;
/** 原始分类code */
private String originCode;
/** 所属平台,baidu表示百度,gaode 表示高德 */
private String platform;
/** 显示顺序 */
private Integer orderNum;
/** 部门状态:0正常,1停用 */
private Integer status;
/** 删除标志(0代表存在 2代表删除) */
private Integer delFlag;
/** 父部门名称 */
private String parentName;
/** 排除编号 */
private Long excludeId;
public Long getParentId() {
return parentId;
}
public void setParentId(Long parentId) {
this.parentId = parentId;
}
public String getAncestors() {
return ancestors;
}
public void setAncestors(String ancestors) {
this.ancestors = ancestors;
}
@NotBlank(message = "分类名称不能为空")
@Size(min = 0, max = 100, message = "分类名称长度不能超过100个字符")
public String getCategoryName() {
return categoryName;
}
public void setCategoryName(String categoryName) {
this.categoryName = categoryName;
}
@NotNull(message = "显示顺序不能为空")
public Integer getOrderNum() {
return orderNum;
}
public void setOrderNum(Integer orderNum) {
this.orderNum = orderNum;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public Integer getDelFlag() {
return delFlag;
}
public void setDelFlag(Integer delFlag) {
this.delFlag = delFlag;
}
public String getParentName() {
return parentName;
}
public void setParentName(String parentName) {
this.parentName = parentName;
}
@JsonIgnore
public Long getExcludeId() {
return excludeId;
}
public void setExcludeId(Long excludeId) {
this.excludeId = excludeId;
}
public Long getPkId() {
return pkId;
}
public void setPkId(Long pkId) {
this.pkId = pkId;
}
public String getCategoryEnglishName() {
return categoryEnglishName;
}
public void setCategoryEnglishName(String categoryEnglishName) {
this.categoryEnglishName = categoryEnglishName;
}
public String getOriginCode() {
return originCode;
}
public void setOriginCode(String originCode) {
this.originCode = originCode;
}
public String getPlatform() {
return platform;
}
public void setPlatform(String platform) {
this.platform = platform;
}
public PoiCategory() {
super();
}
public PoiCategory(Long pkId,Long parentId, String ancestors, String categoryName, String categoryEnglishName,
String originCode) {
super();
this.pkId = pkId;
this.parentId = parentId;
this.ancestors = ancestors;
this.categoryName = categoryName;
this.categoryEnglishName = categoryEnglishName;
this.originCode = originCode;
}
}
为了在后续的业务实现中进行一些逻辑操作,增加一些业务属性,比如excludeId,这是为了在树形展示中进行标记的,并不是数据库模型的一个属性,请注意。
2、数据库操作层实现
数据库的操作这里不采用MybatisPlus来实现,其实可以进行优化可改造的。没改造的原因是参考的是Ruoyi本身的部门的多层级设计与实现,而本身Ruoyi的数据库层操作就是采用原生的Mybatis的,这里也就基于Mybatis来进行扩展。MybatisPlus本身对Mybatis只做了扩展,没有做改变,因此可以直接对Mybatis进行相关的实现。受限于本文的内容篇幅,这里仅将部分实例贴出来,如果需要本博客内容的内容参考,可以私信或者参考Ruoyi的官方部门的实现代码,基本是一致的,除了部分字段的不一致外。首先是Mapper层的代码实现:
package com.yelang.project.poisubject.poi.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Param;
import com.yelang.project.poisubject.poi.domain.PoiCategory;
/**
* -POI分类管理 数据层
* @author 夜郎king
*/
public interface PoiCategoryMapper {
/**
* -查询POI分类树
*/
public int selectPoiCategoryCount(PoiCategory category);
/**
* -查询分类是否存在POI
*/
public int checkCategoryExistPoi(Long pkId);
/**
* -查询POI分类管理数据
*/
public List<PoiCategory> selectPoiCategoryList(PoiCategory category);
/**
* -删除POI分类信息
*/
public int deletePoiCategoryById(Long pkId);
/**
* -新增POI分类信息
*/
public int insertPoiCategory(PoiCategory category);
/**
* -修改POI分类信息
*/
public int updatePoiCategory(PoiCategory category);
/**
*- 修改子元素关系
*/
public int updatePoiCategoryChildren(@Param("categories") List<PoiCategory> categories);
/**
* -根据POI分类ID查询信息
*/
public PoiCategory selectPoiCategoryById(Long pkId);
/**
*- 校验分类名称是否唯一
* @return 结果
*/
public PoiCategory checkPoiCategoryNameUnique(@Param("categoryName") String categoryName, @Param("parentId") Long parentId);
/**
*- 修改所在分类正常状态
*/
public void updatePoiCategoryStatusNormal(Long[] pkIds);
/**
*- 根据ID查询所有子分类
*/
public List<PoiCategory> selectChildrenPoiCategoryById(Long pkId);
/**
* -根据ID查询所有子POI分类(正常状态)
*/
public int selectNormalChildrenPoiCategoryById(Long pkId);
/**
* - 批量插入POI目录信息
*/
public void batchInsertPoiCategory(List<PoiCategory> list);
}
与之对应的是XML格式的Mybatis配置文件,在Mybatis配置文件中定了全部的实现。
3、控制层实现
业务层是一个系统中非常重要的一环,我们很多的业务实现都会放在对应的业务层中来进行实现。本文省略的业务层实现并不是不重要,而是因为POI分级管理的业务层与其它的业务层也是非常相似的。控制层是承接了前后端交互的窗口,因此这里将控制层的实现进行了详细的讲解。在控制层的相关方法中,需要实现POI分级管理的新增、修改、删除、列表展示、分级树形信息展示等。这里不一一对源码进行展示,将分类的树形展示控制层方法列出来:
/**
* -加载POI分级列表树(排除下级)
*/
@GetMapping("/treeData/{excludeId}")
@ResponseBody
public List<Ztree> treeDataExcludeChild(@PathVariable(value = "excludeId", required = false) Long excludeId){
PoiCategory category = new PoiCategory();
category.setExcludeId(excludeId);
List<Ztree> ztrees = poiCategoryService.selectPoiCategoryTreeExcludeChild(category);
return ztrees;
}
更多其它的方法见下图 :
以上就是POI分级管理的后台设计与实现,分别介绍了模型层、数据库操作层、控制层的具体实现。 完成了后台的实现后,接下来就需要实现前端的功能实现。
二、前端的设计与实现
本节将重点介绍POI分级管理中的前端设计与实现。这里采用的是Ruoyi的单体架构,前端模板引擎采用的是Thymeleaf实现,主要的技术栈是传统Jquery+Html5的技术。本节将从POI分级列表、新增及修改功能、POI分级树等三个功能来进行详细介绍。
1、POI分级列表实现
在Thymeleaf中,树形分级列表使用的组件是treeTable,基于Ruoyi封装好的组件可以快速开发出层级树形列表,我们只需要定义相关的后台请求资源以及配置好树形表格的信息即可。在Thymeleaf中定义树形列表的配置如下:
<script th:inline="javascript">
var addFlag = [[${@permission.hasPermi('poisubject:poi:add')}]];
var editFlag = [[${@permission.hasPermi('poisubject:poi:edit')}]];
var removeFlag = [[${@permission.hasPermi('poisubject:poi:remove')}]];
var datas = [[${@dict.getType('sys_normal_disable')}]];
var platformDatas = [[${@dict.getType('sys_poi_category_source')}]];
var prefix = ctx + "/poisubject/poi/poicategory";
$(function() {
var options = {
code: "pkId",
parentCode: "parentId",
uniqueId: "pkId",
url: prefix + "/list",
createUrl: prefix + "/add/{id}",
updateUrl: prefix + "/edit/{id}",
removeUrl: prefix + "/remove/{id}",
modalName: "POI分类",
dataUrl: prefix + "/list",
rootIdValue:"0",
expandAll: false,//是否全部展开,设置为否
//,
columns: [{
field: 'selectItem',
radio: true
},
{
field: 'categoryName',
title: '分类名称',
align: "left"
},{
field: 'originCode',
title: '原始编码',
align: "left"
},
{
field: 'platform',
title: '来源平台',
align: "left",
formatter: function(value, item, index) {
return $.table.selectDictLabel(platformDatas, item.platform);
}
},
{
field: 'orderNum',
title: '排序',
align: "left"
},
{
field: 'status',
title: '状态',
align: "left",
formatter: function(value, item, index) {
return $.table.selectDictLabel(datas, item.status);
}
},
{
field: 'createTime',
title: '创建时间',
align: "left"
},
{
title: '操作',
align: 'left',
formatter: function(value, row, index) {
if (row.parentId != 0) {
var actions = [];
actions.push('<a class="btn btn-success btn-xs ' + editFlag + '" href="javascript:void(0)" onclick="$.operate.edit(\'' + row.pkId + '\')"><i class="fa fa-edit"></i>编辑</a> ');
actions.push('<a class="btn btn-info btn-xs ' + addFlag + '" href="javascript:void(0)" onclick="$.operate.add(\'' + row.pkId + '\')"><i class="fa fa-plus"></i>新增</a> ');
actions.push('<a class="btn btn-danger btn-xs ' + removeFlag + '" href="javascript:void(0)" onclick="$.operate.remove(\'' + row.pkId + '\')"><i class="fa fa-trash"></i>删除</a>');
return actions.join('');
} else {
return "";
}
}
}]
};
$.treeTable.init(options);
});
</script>
在这里需要注意的是,在Ruoyi中树形表格展示时,默认会将所有层级都打开,这样会比较影响操作。因此我们可以设置只打开指定的层级即可,设置方法如下:
expandAll: false,//是否全部展开,设置为否
2、新增及修改实现
新增与修改的功能实现比较简单,稍微有一点区别的是,在修改的实现中,首先需要根据POI的分级ID查询对应的分级信息,然后再针对实体进行数据的修改。在Thymeleaf中设置修改属性时,需要绑定原始对象的属性。实现代码如下(以修改为例):
<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org" >
<head>
<th:block th:include="include :: header('修改POI分类')" />
</head>
<body class="white-bg">
<div class="wrapper wrapper-content animated fadeInRight ibox-content">
<form class="form-horizontal m" id="form-poicategory-edit" th:object="${category}">
<input name="pkId" type="hidden" th:field="*{pkId}" />
<input id="treeId" name="parentId" type="hidden" th:field="*{parentId}" />
<div class="form-group">
<label class="col-sm-3 control-label">上级分类:</label>
<div class="col-sm-8">
<div class="input-group">
<input class="form-control" type="text" id="treeName" onclick="selectPoiCategoryTree()" readonly="true" th:field="*{parentName}">
<span class="input-group-addon"><i class="fa fa-search"></i></span>
</div>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label is-required">分类名称:</label>
<div class="col-sm-8">
<input class="form-control" type="text" name="categoryName" th:field="*{categoryName}" id="categoryName" required>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">分类英文名:</label>
<div class="col-sm-8">
<input class="form-control" type="text" name="categoryEnglishName" th:field="*{categoryEnglishName}">
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">原始分类:</label>
<div class="col-sm-8">
<input class="form-control" type="text" name="originCode" th:field="*{originCode}">
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">所属平台:</label>
<div class="col-sm-8">
<div class="radio-box" th:each="platformData : ${@dict.getType('sys_poi_category_source')}">
<input type="radio" th:id="${platformData.dictCode}" name="platform" th:value="${platformData.dictValue}" th:field="*{platform}">
<label th:for="${platformData.dictCode}" th:text="${platformData.dictLabel}"></label>
</div>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label is-required">显示排序:</label>
<div class="col-sm-8">
<input class="form-control" type="text" name="orderNum" th:field="*{orderNum}" required>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">分类状态:</label>
<div class="col-sm-8">
<div class="radio-box" th:each="dict : ${@dict.getType('sys_normal_disable')}">
<input type="radio" th:id="${dict.dictCode}" name="status" th:value="${dict.dictValue}" th:field="*{status}">
<label th:for="${dict.dictCode}" th:text="${dict.dictLabel}"></label>
</div>
</div>
</div>
</form>
</div>
<th:block th:include="include :: footer" />
<script type="text/javascript">
var prefix = ctx + "poisubject/poi/poicategory";
$("#form-poicategory-edit").validate({
onkeyup: false,
rules:{
categoryName:{
remote: {
url: prefix + "/checkPoiCategoryNameUnique",
type: "post",
dataType: "json",
data: {
"pkId": function() {
return $("#pkId").val();
},
"parentId": function() {
return $("input[name='parentId']").val();
},
"categoryName": function() {
return $.common.trim($("#categoryName").val());
}
},
dataFilter: function(data, type) {
return $.validate.unique(data);
}
}
}
},
messages: {
"categoryName": {
remote: "POI分类已经存在"
}
},
focusCleanup: true
});
function submitHandler() {
if ($.validate.form()) {
$.operate.save(prefix + "/edit", $('#form-poicategory-edit').serialize());
}
}
/*分类管理-新增-选择父分类树*/
function selectPoiCategoryTree() {
var pkId = $("#treeId").val();
var excludeId = $("input[name='pkId']").val();
if(pkId > 0) {
var options = {
title: 'POI分类选择',
width: "380",
url: prefix + "/selectPoiCategoryTree/" + $("#treeId").val() + "/" + excludeId,
callBack: doSubmit
};
$.modal.openOptions(options);
} else {
$.modal.alertError("父分类不能选择");
}
}
function doSubmit(index, layero){
var tree = layero.find("iframe")[0].contentWindow.$._tree;
if ($.tree.notAllowLastLevel(tree)) {
var body = $.modal.getChildFrame(index);
$("#treeId").val(body.find('#treeId').val());
$("#treeName").val(body.find('#treeName').val());
$.modal.close(index);
}
}
</script>
</body>
</html>
3、POI分级树实现
与分级列表的展示不一样的时,在新增和编辑的页面中也需要对POI分级信息进行选择,因此需要使用一个支持动态选择的树来进行构建。在Ruoyi中可以基于Ztree来进行快速构建可以支持动态选择的分级树。参考源码如下:
<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org" >
<head>
<th:block th:include="include :: header('POI分类选择')" />
<th:block th:include="include :: ztree-css" />
</head>
<style>
body{height:auto;font-family: "Microsoft YaHei";}
button{font-family: "SimSun","Helvetica Neue",Helvetica,Arial;}
</style>
<body class="hold-transition box box-main">
<input id="treeId" name="treeId" type="hidden" th:value="${category.pkId}"/>
<input id="treeName" name="treeName" type="hidden" th:value="${category.categoryName}"/>
<div class="wrapper"><div class="treeShowHideButton" onclick="$.tree.toggleSearch();">
<label id="btnShow" title="显示搜索" style="display:none;">︾</label>
<label id="btnHide" title="隐藏搜索">︽</label>
</div>
<div class="treeSearchInput" id="search">
<label for="keyword">关键字:</label><input type="text" class="empty" id="keyword" maxlength="50">
<button class="btn" id="btn" onclick="$.tree.searchNode()"> 搜索 </button>
</div>
<div class="treeExpandCollapse">
<a href="#" onclick="$.tree.expand()">展开</a> /
<a href="#" onclick="$.tree.collapse()">折叠</a>
</div>
<div id="tree" class="ztree treeselect"></div>
</div>
<th:block th:include="include :: footer" />
<th:block th:include="include :: ztree-js" />
<script th:inline="javascript">
var prefix = ctx + "poisubject/poi/poicategory"
var pkId = [[${pkId}]];
var excludeId = [[${excludeId}]];
$(function() {
var url = $.common.isEmpty(excludeId) ? prefix + "/treeData": prefix + "/treeData/" + excludeId;
var options = {
url: url,
expandLevel: 2,
onClick : zOnClick
};
$.tree.init(options);
});
function zOnClick(event, treeId, treeNode) {
var treeId = treeNode.id;
var treeName = treeNode.name;
$("#treeId").val(treeId);
$("#treeName").val(treeName);
}
</script>
</body>
</html>
至此基本完成了POI分级管理的前端设计与实现,通过源码的分析和实现,给大家呈现了完整的前端构建过程。 下面来看一下实际的POI分级管理操作界面。
三、Web操作界面成果
在实现了POI分级管理的后端以及前端开发工作之后,接下来就是检验成果的时候了,来看一下实际的Web操作界面。这里选择三个比较常见的场景,POI分类列表、新增及修改、POI分级树展示等三个不同的场景。
1、POI分类列表展示
以上是POI分类列表展示页面,使用树形列表的方式来对数据进行展示,非常直观的实现对数据的展示,展示了POI分类名称,编码、平台来源、排序号、创建日期等。同时支持对当前POI信息进行编辑、新增和删除操作。 点击操作栏中的不同按钮可以打开不同业务窗口,完成对应的业务操作。
2、新增及修改展示
如果想实现POI分类的相关信息的编辑,点击编辑按钮打开信息编辑窗口,在弹出的窗口中设置需要修改的值,完成后点击确定即可完成信息的修改。需要新增POI分类的操作与编辑相类似,这里不再进行赘述。
3、POI分级树展示
在新增和编辑POI分级信息时,最常见的操作就是需要调整POI的分类,这里就需要基于POI的分级树来进行选择。通过一棵可展开,多层级展示的树就可以实现POI的类别重分类设置,分类设置完成后点击确认即可。
四、总结
以上就是本文的主要内容, 文章以Ruoyi开发框架为前提,重点讲解如何在Ruoyi框架的基础之上开发一款使用于多平台POI分类管理的Web管理工具。文章详细的介绍了后台的MVC各层级的设计与开发,同时也介绍了前端的设计与实现。通过本文,不仅可以让大家掌握如何对POI数据进行准确分类,同时熟悉使用Web的方式来对数据进行管理和展示。文章以MVC为架构核心,分别从前后端的构建上对如何实现POI分级管理进行详细的介绍,希望对大家有所帮助。行文仓促,定有不足之处,欢迎各位朋友在评论区批评指正,不胜感激。
更多推荐
所有评论(0)