需求分析

数据字典(Data Dictionary)是一种用户可以访问的记录数据库和应用程序元数据的目录。数据字典在系统也是必备的组件,对此我们想想要怎么做:

  • 基本功能:Key/Value 方式的存储结构,外加其他属性如”说明“,尽可能设计为通用一点
  • 树状结构,也就是说带分类功能的。换句话说,这个数据字典也是一个分类表(数据库层面就是加一个 parentId 字段,当然其他 逻辑较为复杂,而且也不是 pId 这唯一一种树的存储方法)
  • 可以租户/门户隔离,也就是多个门户或者租户。

基本设计

表设计如下。

CREATE TABLE `sys_datadict` (`id` INT(11) NOT NULL AUTO_INCREMENT COMMENT '主键 id,自增',`name` VARCHAR(20) NULL DEFAULT NULL COMMENT '名称、自定义编码、相当于 key。可选的' COLLATE 'utf8mb4_bin',`value` VARCHAR(50) NOT NULL COMMENT '值' COLLATE 'utf8mb4_bin',`content` VARCHAR(200) NULL DEFAULT NULL COMMENT '简介、描述' COLLATE 'utf8mb4_bin',`parentId` INT(11) NULL DEFAULT NULL COMMENT '父 id',`type` INT(11) NULL DEFAULT NULL COMMENT '类型 id',`sortNo` TINYINT(4) NULL DEFAULT NULL COMMENT '顺序、序号',`stat` TINYINT(4) NULL DEFAULT NULL COMMENT '数据字典:状态',`uid` BIGINT(20) NULL DEFAULT NULL COMMENT '唯一 id,通过 uuid 生成不重复 id',`tenantId` INT(11) NOT NULL DEFAULT '0' COMMENT '租户 id。0 = 不设租户',`createByUser` INT(11) NULL DEFAULT NULL COMMENT '创建者 id',`createDate` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',`updateDate` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',PRIMARY KEY (`id`) USING BTREE,INDEX `tenantId` (`tenantId`) USING BTREE
)
COMMENT='数据字典'
COLLATE='utf8mb4_bin';

Java Bean:

import java.util.Date;import com.ajaxjs.framework.IBaseModel;/*** 数据字典 * * @author Frank Cheung<sp42@qq.com>* @date 2021-11-07*/
public class DataDict implements IBaseModel {/*** 主键 id,自增*/private Long id;/*** 名称、自定义编码、相当于 key。可选的*/private String name;/*** 值*/private String value;/*** 简介、描述*/private String desc;/*** 父 id*/private Long parentId;/*** 类型 id*/private Long type;/*** 顺序、序号*/private Integer sortNo;/*** 数据字典:状态*/private Integer stat;/*** 唯一 id,通过 uuid 生成不重复 id*/private Long uid;/*** 租户 id。0 = 不设租户*/private Long tenantId;/*** 创建者 id*/private Long createByUser;/*** 创建时间*/private Date createDate;/*** 修改时间*/private Date updateDate;public Long getId() {return id;}public void setId(Long id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getValue() {return value;}public void setValue(String value) {this.value = value;}public String getDesc() {return desc;}public void setDesc(String desc) {this.desc = desc;}public Long getParentId() {return parentId;}public void setParentId(Long parentId) {this.parentId = parentId;}public Long getType() {return type;}public void setType(Long type) {this.type = type;}public Integer getSortNo() {return sortNo;}public void setSortNo(Integer sortNo) {this.sortNo = sortNo;}public Integer getStat() {return stat;}public void setStat(Integer stat) {this.stat = stat;}public Long getUid() {return uid;}public void setUid(Long uid) {this.uid = uid;}public Long getTenantId() {return tenantId;}public void setTenantId(Long tenantId) {this.tenantId = tenantId;}public Long getCreateByUser() {return createByUser;}public void setCreateByUser(Long createByUser) {this.createByUser = createByUser;}public Date getCreateDate() {return createDate;}public void setCreateDate(Date createDate) {this.createDate = createDate;}public Date getUpdateDate() {return updateDate;}public void setUpdateDate(Date updateDate) {this.updateDate = updateDate;}
}

界面:


UI 设计也是大头问题,我们后面再讲。

实现难点

树状结构存储

如何在关系型数据库里面存储树状结构一直是需要思考的问题,尤其对于数据结构掌握不好的同学来说,更是难点。这个知识点我前期探索过,参考博文《SQL 双亲节点查找所有子节点》。现在回过头来看,结合大家的一些经验,决定这样子做。

  • MySQL 还是老老实实用 parentId 的存储方法
  • MySQL 8.0 虽然支持 CTE 递归,但老版本就无能为力了。对此,比较简单省力的方式是,直接上 MySQL 函数。

MySQL 函数为 getAllChildren,源码如下:

CREATE DEFINER=`root`@`%` FUNCTION `getAllChildren`(`rootId` INT,`tableName` VARCHAR(50)
)
RETURNS varchar(4000) CHARSET latin1BEGINDECLARE P_TEMP VARCHAR(1000);DECLARE C_TEMP VARCHAR(1000);SET P_TEMP = '0';SET C_TEMP = CAST(rootId AS CHAR);WHILE C_TEMP IS NOT NULL DOSET P_TEMP = CONCAT(P_TEMP, ',', C_TEMP);IF tableName = 'sys_datadict' THENSELECT GROUP_CONCAT(id) INTO C_TEMP FROM sys_datadict WHERE FIND_IN_SET(parentId, C_TEMP) > 0; ELSEIF tableName = 'sys_datadict1' THENSELECT GROUP_CONCAT(id) INTO C_TEMP FROM sys_datadict WHERE FIND_IN_SET(parentId, C_TEMP) > 0; ELSE SET C_TEMP = NULL;END IF; -- if结束END WHILE; RETURN P_TEMP;
END

调用例子:

SELECT * FROM sys_datadict WHERE FIND_IN_SET(id, getAllChildren(2, 'sys_datadict')) ORDER BY parentId ASC

关键原理是 MySQL 的 find_in_set 函数,顺带学习一下。当然了——换了别的数据库就不是这样子做,通通要改。

getAllChildren() 第一个参数为 INT,是父亲 id,第二个参数是表名,——为什么有表名呢?因为 MySQL 函数不支持动态传入表名作为参数,于是只能写死,因为当初我想这个函数支持多张表而不是一张表写一个函数的。不过我采取代价相对较低的”写死“,在函数里面写死表名,通过 IF ELSE 判断,便可达到支持多张表的目的。

调用上述 SQL 成功返回如下。

获取节点深度

传入节点 id,返回其深度(depth)。深度即表示节点在树中位于第几层次。

需要一个存储过程和函数来完成。如果你要拿去用,可能要改下 表名、字段名或者出参/入参的类型。

CREATE DEFINER=`root`@`%` PROCEDURE `countLayerHelper`(IN id INT, INOUT counter INT)
BEGINIF EXISTS (SELECT 1 FROM sys_datadict o WHERE o.id = id) THENBEGINDECLARE pid INT DEFAULT 0;DECLARE done BOOLEAN DEFAULT FALSE;DECLARE _cursor CURSOR FOR SELECT o.parentId FROM sys_datadict o WHERE o.id = id;DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;SET counter = counter + 1;OPEN  _cursor;read_loop: LOOPFETCH _cursor INTO pid;IF done THEN LEAVE read_loop;END IF;CALL countLayerHelper(pid, counter);END LOOP;CLOSE _cursor;END;END IF;
END

另外需要一个函数来调用存储过程。

CREATE DEFINER=`root`@`%` FUNCTION `countLayer`(id INT) RETURNS int(11)
BEGINDECLARE counter INT DEFAULT 0;SET max_sp_recursion_depth = 255;CALL countLayerHelper(id, counter);RETURN counter - 1;
END

用户调用:

SELECT countLayer(3) AS depth

参考出处。

树节点数组如何转换为 JSON 树?

数据库返回的是一维数组,如何转换为带层次的 JSON 树呢?首先声明一个树节点的类型。

/*** 树节点*/
type TreeMap = {id: number;/*** 父 id,输入时候必填*/parentId: number;/*** 子节点,生成 Tree 之后出现*/children?: TreeMap[];
}

输入(JSON 起码要有 idparentId 字段):

{"result":[{"stat":null,"updateDate":"2022-03-28 18:12:55","type":null,"content":null,"parentId":9,"sortNo":null,"uid":null,"name":null,"tenantId":0,"id":14,"value":"广州新闻","createByUser":null,"createDate":"2022-03-28 18:12:26"}, {"stat":null,"updateDate":"2022-02-26 21:38:28","type":null,"content":null,"parentId":5,"sortNo":null,"uid":null,"name":null,"tenantId":0,"id":6,"value":"香港新闻","createByUser":null,"createDate":"2022-02-26 21:38:28"}, {"stat":null,"updateDate":"2022-03-28 15:20:43","type":null,"content":null,"parentId":5,"sortNo":null,"uid":null,"name":null,"tenantId":0,"id":7,"value":"澳门新闻","createByUser":null,"createDate":"2022-03-28 15:20:43"}, {"stat":null,"updateDate":"2022-03-28 15:20:55","type":null,"content":null,"parentId":5,"sortNo":null,"uid":null,"name":null,"tenantId":0,"id":8,"value":"台湾新闻","createByUser":null,"createDate":"2022-03-28 15:20:55"}, {"stat":null,"updateDate":"2022-03-28 15:22:06","type":null,"content":null,"parentId":4,"sortNo":null,"uid":null,"name":null,"tenantId":0,"id":12,"value":"美国新闻","createByUser":null,"createDate":"2022-03-28 15:22:06"}, {"stat":null,"updateDate":"2022-03-28 15:22:14","type":null,"content":null,"parentId":4,"sortNo":null,"uid":null,"name":null,"tenantId":0,"id":13,"value":"俄罗斯新闻","createByUser":null,"createDate":"2022-03-28 15:22:14"}, {"stat":null,"updateDate":"2022-03-28 15:21:08","type":null,"content":null,"parentId":3,"sortNo":null,"uid":null,"name":null,"tenantId":0,"id":9,"value":"广东新闻","createByUser":null,"createDate":"2022-03-28 15:21:05"}, {"stat":null,"updateDate":"2022-03-28 15:21:23","type":null,"content":null,"parentId":3,"sortNo":null,"uid":null,"name":null,"tenantId":0,"id":10,"value":"湖南新闻","createByUser":null,"createDate":"2022-03-28 15:21:23"}, {"stat":null,"updateDate":"2021-11-06 23:58:11","type":null,"content":null,"parentId":2,"sortNo":null,"uid":null,"name":null,"tenantId":0,"id":3,"value":"国内新闻","createByUser":null,"createDate":"2021-11-06 23:58:11"}, {"stat":null,"updateDate":"2021-11-06 23:58:25","type":null,"content":null,"parentId":2,"sortNo":null,"uid":null,"name":null,"tenantId":0,"id":4,"value":"国际新闻","createByUser":null,"createDate":"2021-11-06 23:58:25"}, {"stat":null,"updateDate":"2021-11-06 23:58:52","type":null,"content":null,"parentId":2,"sortNo":null,"uid":null,"name":null,"tenantId":0,"id":5,"value":"港澳台新闻","createByUser":null,"createDate":"2021-11-06 23:58:52"}, {"stat":null,"updateDate":"2021-11-06 23:57:25","type":null,"content":null,"parentId":1,"sortNo":null,"uid":null,"name":null,"tenantId":0,"id":2,"value":"新闻","createByUser":null,"createDate":"2021-11-06 23:57:25"}]}

转换为(多了 children,表示下一级的节点集合):

[{"stat": null,"updateDate": "2021-11-06 23:58:11","type": null,"content": null,"parentId": 2,"sortNo": null,"uid": null,"name": null,"tenantId": 0,"id": 3,"value": "国内新闻","createByUser": null,"createDate": "2021-11-06 23:58:11","children": [{"stat": null,"updateDate": "2022-03-28 15:21:08","type": null,"content": null,"parentId": 3,"sortNo": null,"uid": null,"name": null,"tenantId": 0,"id": 9,"value": "广东新闻","createByUser": null,"createDate": "2022-03-28 15:21:05","children": [{"stat": null,"updateDate": "2022-03-28 18:12:55","type": null,"content": null,"parentId": 9,"sortNo": null,"uid": null,"name": null,"tenantId": 0,"id": 14,"value": "广州新闻","createByUser": null,"createDate": "2022-03-28 18:12:26","children": []}]}, {"stat": null,"updateDate": "2022-03-28 15:21:23","type": null,"content": null,"parentId": 3,"sortNo": null,"uid": null,"name": null,"tenantId": 0,"id": 10,"value": "湖南新闻","createByUser": null,"createDate": "2022-03-28 15:21:23","children": []}]
}, {"stat": null,"updateDate": "2021-11-06 23:58:25","type": null,"content": null,"parentId": 2,"sortNo": null,"uid": null,"name": null,"tenantId": 0,"id": 4,"value": "国际新闻","createByUser": null,"createDate": "2021-11-06 23:58:25","children": [{"stat": null,"updateDate": "2022-03-28 15:22:06","type": null,"content": null,"parentId": 4,"sortNo": null,"uid": null,"name": null,"tenantId": 0,"id": 12,"value": "美国新闻","createByUser": null,"createDate": "2022-03-28 15:22:06","children": []}, {"stat": null,"updateDate": "2022-03-28 15:22:14","type": null,"content": null,"parentId": 4,"sortNo": null,"uid": null,"name": null,"tenantId": 0,"id": 13,"value": "俄罗斯新闻","createByUser": null,"createDate": "2022-03-28 15:22:14","children": []}]
}, {"stat": null,"updateDate": "2021-11-06 23:58:52","type": null,"content": null,"parentId": 2,"sortNo": null,"uid": null,"name": null,"tenantId": 0,"id": 5,"value": "港澳台新闻","createByUser": null,"createDate": "2021-11-06 23:58:52","children": [{"stat": null,"updateDate": "2022-02-26 21:38:28","type": null,"content": null,"parentId": 5,"sortNo": null,"uid": null,"name": null,"tenantId": 0,"id": 6,"value": "香港新闻","createByUser": null,"createDate": "2022-02-26 21:38:28","children": []}, {"stat": null,"updateDate": "2022-03-28 15:20:43","type": null,"content": null,"parentId": 5,"sortNo": null,"uid": null,"name": null,"tenantId": 0,"id": 7,"value": "澳门新闻","createByUser": null,"createDate": "2022-03-28 15:20:43","children": []}, {"stat": null,"updateDate": "2022-03-28 15:20:55","type": null,"content": null,"parentId": 5,"sortNo": null,"uid": null,"name": null,"tenantId": 0,"id": 8,"value": "台湾新闻","createByUser": null,"createDate": "2022-03-28 15:20:55","children": []}]
}]

使用递归是一个方法,而且异常简洁、直观:

/*** 数组转成树* * @param list     数组* @param parentId 顶级父 id* @returns */
function tranListToTreeData(list: TreeMap[], parentId: number): TreeMap[] {// 先找到所有的根节点let tranList: TreeMap[] = list.filter((it: TreeMap) => it.parentId === parentId);// 传入 id  list进行递归 在筛选出 他的父级 是一个数组tranList.forEach((itm: TreeMap) => itm.children = tranListToTreeData(list, itm.id));return tranList;
}

另外一个方法是这样的,建立一个 Map<id, TreeMap>,然后相当于建立“链表”连接起来,——这个思路也异常简单,而且比递归的方法少了出栈入栈的消耗,效率更高。

/*** 数组转成树* * @param list 数组* @returns JSON Tree*/
function tranListToTreeData(list: TreeMap[]): TreeMap[] {let treeList: TreeMap[] = [];// 最终要产出的树状数据的数组let map = {};     // 所有项都使用对象存储起来list.forEach((item: TreeMap) => { // 建立一个映射关系:通过 id 快速找到对应的元素if (!item.children)item.children = [];map[item.id] = item;});list.forEach((item: TreeMap) => {// 对于每一个元素来说,先找它的上级//    如果能找到,说明它有上级,则要把它添加到上级的 children 中去//    如果找不到,说明它没有上级,直接添加到 treeListlet parent = map[item.parentId];if (parent)// 如果存在则表示 item 不是最顶层的数据parent.children.push(item);elsetreeList.push(item);// 如果不存在 则是顶层数据});return treeList;
}

AJ 组件库之通用数据字典 DataDict相关推荐

  1. 浅尝 | 从 0 到 1 Vue 组件库封装

    写在前面 对于目前框架为王的时代,前端可能很大一部分时间,都是在开发相关的页面组件,而有句话说得好,没有哪个前端不想拥有一个属于自己的,为自己量身定制的组件库,那么本文就为大家整理一下: 如何从 0 ...

  2. web元件库、axure元件库、通用元件库、常用web组件、常用表单、框架、数据表单、导航栏、边框、图标、列表、日期时间选择器、评分组件、穿梭框、输入框、步骤条、图表组件、数据可视化、后台模板、时间轴

    web元件库.axure元件库.通用元件库.常用web组件.常用表单.框架.数据表单.导航栏.边框.图标.列表.日期时间选择器.评分组件.穿梭框.输入框.步骤条.图表组件.数据可视化.后台模板.时间轴 ...

  3. 移动端通用元件库+app通用元件库+数据展示+操作反馈+通用模板+数据录入+列表页+表单页+详情页+通用版布局+移动端手机模板+业务组件+反馈组件+展示组件+表单组件+导航组件

    移动端通用元件库+app通用元件库+数据展示+操作反馈+通用模板+数据录入+列表页+表单页+详情页+通用版布局+移动端手机模板+业务组件+反馈组件+展示组件+表单组件+导航组件 原型展示及下载地址:h ...

  4. 国产web端开源ui组件-通用前端ui界面组件库

    写个序吧: 随着工作的不断深入,越来越发现很多好的前端开源项目都来自于国外,国产的开源项目很多时候面临叫好不叫座甚至有很多就消失不见了.开源和创新,不仅仅是需要我们的参与同样也需要我们不断的传播,因此 ...

  5. Axure RP大数据BI可视化大屏原型模板及通用组件库

    对于做大数据交互原型设计时,需要做到很多背景很科技背景,数据统计汇总组件.Axure RP大数据可视化大屏原型模板及通用组件库主要结构由大屏模板.登录界面.入口界面.初始框架.图表组件.设计组件等六大 ...

  6. GMU(Global Mobile UI)是百度前端通用组开发的移动端组件库

    GMU(Global Mobile UI)是百度前端通用组开发的移动端组件库,具有代码体积小.简单.易用等特点,组件内部处理了很多移动端的bug,覆盖机型广,能大大减少开发交互型组件的工作量,非常适合 ...

  7. 全端通用快速开发UI组件库UnifyUi大更新,Unify Ui是基于uni-app的全端(vue/nvue)组件库

    本次进行整个框架的ui组件结构,添加详细注释.优化部分组件,新增 暗黑模式 是不是有种剁手的感觉?点击 unifyui 时刻关注动态, unifyui 团队即将公开招募协作者共同完善unifyui! ...

  8. .NET 开源免费图表组件库,Winform,WPF 通用

    大家好, 我是等天黑, 今天给大家介绍一个功能完善, 性能强悍的图表组件库 ScottPlot, 当我第一次在 github 上看到这个库, 我看不懂,但我大受震撼, 这么好的项目当然要分享出来了. ...

  9. 使用vue加svg实现流程图代码_京东风格的移动端Vue组件库NutUI2.0来啦

    移动端 Vue 组件库 NutUI 自发布以来受到了广泛的关注.据不完全统计,目前至少有30多个京东的 web 项目正在使用 NutUI . 经过一段时间紧锣密鼓的开发,近期,京东正式发布了 NutU ...

最新文章

  1. 各大知名企业的Research展示
  2. linux中时间戳与date的互转
  3. 重磅!神策数据游戏行业解决方案全面上线,速来围观
  4. GridView空记录时显示Header和Footer
  5. MySQL 5.6.20-enterprise-commercial的参数文件位置问题
  6. hashchange
  7. 苹果手机还原网络设置会怎样_如果你的苹果手机信号不好!要记得这样设置,让你信号轻松满格...
  8. 初次汇编程序 masm5
  9. 漂亮得不像触控板,全新 Magic Trackpad 2
  10. 戴尔服务器密码忘记,进入PE清除密码
  11. 已知六条边的边长,求四面体体积
  12. GooglTest GoogleMock 实践感想三 死亡测试初步(1)
  13. 大学计算机课英语心得体会,【大学计算机课程总结12篇】_大学计算机课程总结范文大全_2021年大学计算机课程总结_东城教研...
  14. Android studio Android源码开发环境搭建
  15. STM32L4超低功耗特性概述
  16. 教你用安卓手机以及在Linux下连接校园联通无线宽带
  17. 免费的NBA历史得分榜接口
  18. 7. RabbitMQ 高级
  19. java 将.amr音频文件转换为.mp3或.wav文件
  20. 计算机绘图中特征建模的概念,科大机械CADCAM习题集..doc

热门文章

  1. 复旦计算机考研复试要口试吗,复试的正确打开方式|复旦篇
  2. 北邮计算机复试读英语,简单的说一下北邮的复试过程吧
  3. 搜索其他计算机共享的打印机,打印机共享后其他电脑搜索不到_共享打印机查找不到_打印机品牌排行榜...
  4. BOM浏览器对象模型(Browser Object Model)
  5. unity 实现3d模型渐隐(修改材质透明度)
  6. 小米vr一体机html,小米VR一体机体验:一款让你大开眼界的产品
  7. STM32F030F4P6-提高ADC采集精度
  8. 【自制】csdn自定义模块栏目 个性化 【美化个人简介】
  9. 毕业设计-一种基于 MATLAB 的指纹识别方法
  10. geetest php,GitHub - lilwil/geetest: Geetest For ThinkPHP5