案例分享:用 nocobase 的树表做个标准专业的“部门”基础资料,以及树表的使用要点

步骤一,建表,如下图:



要点:
1,表名、字段名都要尽量标准,系统开发不是一个人的事,涉及自己,团队,用户,后期二开…
2,用树表,nocobase 的树表虽然不完美,但足够用。
3,图中的字段几乎是树表基本、必备的字段,其他功能性字段会随着开发逐渐添加。
4,注意字段类型,图中也几乎是标准选择。
5,字段的准确命名会让后续的开发事半功倍(可以望文生义,可以写通用触发器…)

步骤二,增加页面,选择区块,如下图:


要点:
1,列表,是用户最常用的查询界面,列表要有过滤器。
2,在列表界面上完成查看、编辑、新增等常用动作,是经过时间验证的较好的交互方式。
3,上图中,上部是筛选区块,下部是列表区块,列表区块要用“树表格”(更好),如下图:

4,(在同一个页面上的)筛选区块要主动连接列表区块(如下图)。

步骤三,配置新增页面(部门_表单)如下图:


要点:
1,仅需要父记录、编码、名称三个字段(本案),其他字段都应该是“自动更新”的。
2,要高效使用“区块模板”,编辑和新增在绝大多数场景下可以共用“部门_表单”模板。
3,避免“上下级乱伦死循环”,需要配置“父记录”字段的选择范围,含义是“编辑时父记录不能选择本级或下级”,如下图:

步骤四,处理数据库的“触发器函数”和“触发器”。


关键内容:触发器函数(我使用的数据库是 postgresql )
触发器函数1:更新树表NEW行本级长编码长名称级次,如下图:

触发器函数2:更新树表NEW行下游长编码长名称级次,如下图:

触发器函数3:更新父记录的“明细”为是/否(新增数据行本身的“明细”默认为“是”),如下图:

要点:
1,nocobase 的工作流目前局限性太多,实现以上效果只能用数据库触发器。
2,数据库触发器很简单,借助 copilot 等工具就更简单。(我都是借助 copilot )

主体工作如上,以下分楼层解释:
1,树表的常用场景。
2,为什么要有长编码,长名称,级次,明细这些字段。
3,部门表为什么要有长编码、长名称?会计科目表为什么没有长编码、只有长名称?
4,树表的编码,如何保证“树级关系”?
5,nocobase 树表目前的局限性及解决办法。

5 Likes

1,树表的常用场景:
大型数据资料的分类表,如商品及劳务表,实务中必须有“分类”,这个"分类"就得用树表。
部门,
会计科目,

有层级关系、且界限清晰的其他树状结构

2,为什么要有长编码,长名称,级次,明细这些字段。
经验之谈,后期必用到!

3,部门表为什么要有长编码、长名称?会计科目表为什么没有长编码、只有长名称?
一句话概括:都是由场景决定的。
比如:
部门,每个部门都有自己独立的编码,名称;
但部门之间又有上下级关系,所以这种层状是更合适的,如下图:

而会计科目就不同了:
会计科目的编码,是要前台维护数据的时候,强制保持"层级关系"。
所以,会计科目表就不需要长编码字段,只需要长名称字段,如下图:

长名称是必须的字段吗?
也不是(如果前台维护数据的时候,人为保证名称上的层级关系的话)!
绝大多数的树表都需要“长名称”字段。

4,树表的编码,如何保证“树级关系”?
我认知内,就两种选择:
一,编码不需要带层级关系,如部门编码,需要增加“长编码”字段,系统自动维护;
二,编码必须要带层级关系,如会计科目、商品分类等字段,前台新增数据时保持层级关系(要用其他方法来校验后保存)

5,nocobase 树表目前的局限性及解决办法。

一,树表区块,首列自动分级是鸡肋,父记录的自动长名称也是鸡肋。
二,工作流相比触发器的巨大局限性。
三,树级死循环。
四,级联删除太痛快、太危险了,应该用限制模式。
五,树表参与查询时,父记录的查询结果应该包含子记录的查询结果,目前不是。
六,nocobase 应默认处理好树表的层级、明细等字段。

感谢,大佬分享,触发器那里能否直接贴代码出来啊

BEGIN 
-- 更新树表NEW行本级长编码长名称级次
-- 1. 更新树表NEW行本级长名称和级次
EXECUTE format(
    '
        UPDATE %1$I
        SET
            "f_fullName" = COALESCE((SELECT "f_fullName" FROM %1$I WHERE id = $1), '''') || ''/'' || $2,
            "f_level" = COALESCE((SELECT "f_level" FROM %1$I WHERE id = $1), 0) + 1
        WHERE id = $3
        ',
    TG_TABLE_NAME
) USING NEW."parentId",
NEW."f_name",
NEW."id";
-- 2. 更新树表NEW行本级长编码, 基于f_number
IF (
    SELECT COUNT(*)
    FROM information_schema.columns
    WHERE table_schema = 'public'
        AND table_name = TG_TABLE_NAME
        AND column_name IN ('f_fullNumber', 'f_number')
) = 2 THEN EXECUTE format(
    '
            UPDATE %1$I
            SET "f_fullNumber" = COALESCE((SELECT "f_fullNumber" FROM %1$I WHERE id = $1), '''') || ''/'' || $2
            WHERE id = $3
            ',
    TG_TABLE_NAME
) USING NEW."parentId",
NEW."f_number",
NEW."id";
END IF;
-- 3. 更新树表NEW行本级长编码, 基于f_autoNumber
IF (
    SELECT COUNT(*)
    FROM information_schema.columns
    WHERE table_schema = 'public'
        AND table_name = TG_TABLE_NAME
        AND column_name IN ('f_fullNumber', 'f_autoNumber')
) = 2 THEN EXECUTE format(
    '
            UPDATE %1$I
            SET "f_fullNumber" = COALESCE((SELECT "f_fullNumber" FROM %1$I WHERE id = $1), '''') || ''/'' || $2
            WHERE id = $3
            ',
    TG_TABLE_NAME
) USING NEW."parentId",
NEW."f_autoNumber",
NEW."id";
END IF;
RETURN NEW;
END;
BEGIN -- 更新树表NEW行下游长编码长名称级次
-- 1. 更新树表NEW行下游长名称和级次
EXECUTE format(
    '
        UPDATE %1$I
        SET
            "f_fullName" = COALESCE((SELECT "f_fullName" FROM %1$I WHERE id = $1), '''') || ''/'' || "f_name",
            "f_level" = COALESCE((SELECT "f_level" FROM %1$I WHERE id = $1), 0) + 1
        WHERE "parentId" = $1;
        ',
    TG_TABLE_NAME
) USING NEW."id";
-- 2. 更新树表NEW行下游长编码, 基于f_number
IF (
    SELECT COUNT(*)
    FROM information_schema.columns
    WHERE table_schema = 'public'
        AND table_name = TG_TABLE_NAME
        AND column_name IN ('f_fullNumber', 'f_number')
) = 2 THEN EXECUTE format(
    '
            UPDATE %1$I
            SET "f_fullNumber" = COALESCE((SELECT "f_fullNumber" FROM %1$I WHERE id = $1), '''') || ''/'' || "f_number"
            WHERE "parentId" = $1
            ',
    TG_TABLE_NAME
) USING NEW."id";
END IF;
-- 3. 更新树表NEW行下游长编码, 基于f_autoNumber
IF (
    SELECT COUNT(*)
    FROM information_schema.columns
    WHERE table_schema = 'public'
        AND table_name = TG_TABLE_NAME
        AND column_name IN ('f_fullNumber', 'f_autoNumber')
) = 2 THEN EXECUTE format(
    '
            UPDATE %1$I
            SET "f_fullNumber" = COALESCE((SELECT "f_fullNumber" FROM %1$I WHERE id = $1), '''') || ''/'' || "f_autoNumber"
            WHERE "parentId" = $1
            ',
    TG_TABLE_NAME
) USING NEW."id";
END IF;
RETURN NEW;
END;

语句可以更简单。
因为我有的表用字段 f_number, 有的用字段 f_autoNumber ,造成了整个触发器函数需要判断。
这也体现出表设计时基础字段规范的重要性。
提醒:
1,有了触发器函数,还要去设置触发器,要自己去搞懂。
2,触发器和触发器函数,可用 PgAdmin 等工具配置,更快的方式是用查询器执行语句生成。

1 Like

:+1: