NocoBase 数据建模实战:用普通表 + 树表 + 多对一关联搭建工单系统
【免费下载链接】nocobaseNocoBase is an open-source AI + no-code platform for building business systems fast. Instead of generating everything from scratch, AI works on top of production-proven infrastructure and a WYSIWYG no-code interface, so you get both speed and reliability.项目地址: https://gitcode.com/GitHub_Trending/no/nocobase
本篇技术指南是 NocoBase 工单系统系列教程的第 2 章,聚焦数据建模:如何在 NocoBase 中创建数据表(Collection)、配置字段(Field)并建立表间关联。你将学会用一张普通表承载工单数据、用一张树表承载层级分类、再用三个多对一(M2O)关系字段把工单、分类和用户串起来——这套"两张表搞定工单系统"的建模思路,可以直接复用到订单、客户、任务等绝大多数业务场景。
数据模型是整个系统的地基:先想清楚要存哪些数据、数据之间有什么关系,后面搭建界面、配置权限、编写工作流才能水到渠成。文中所有操作都基于当前仓库的 NocoBase 开源代码,并会穿插对应的源码实现细节,帮助你不仅"会点",而且"懂原理"。
什么是数据表和字段
如果你用过 Excel,理解 NocoBase 的数据表概念就很容易:
| Excel 概念 | NocoBase 概念 | 说明 |
|---|---|---|
| 工作表 | 数据表(Collection) | 一类数据的容器 |
| 列标题 | 字段(Field) | 描述数据的属性 |
| 每一行 | 记录(Record) | 一条具体的数据 |
比如我们要做的"工单表",就像一张 Excel 表格——每一列是一个字段(标题、状态、优先级……),每一行是一条工单记录。在源码层面,NocoBase 用CollectionOptions来描述一张数据表的核心配置,其中name是表的标识名、title是界面显示名、fields是字段列表,定义位于 packages/core/database/src/collection.ts:
export interface CollectionOptions extends Omit<ModelOptions, 'name' | 'hooks'> { name: string; // 数据表标识名,如 tickets title?: string; // 界面显示名,如 工单 tableName?: string; // 物理表名 fields?: FieldOptions[]; tree?: string; // 树表专用:声明树的实现方式 autoGenId?: boolean; // 是否自动生成自增主键,默认 true ... }不过,NocoBase 比 Excel 强大得多。它支持多种数据表类型,不同类型自带不同的能力。官方文档 数据表 中列出了完整的表结构类型:
| 表类型 | 适合场景 | 举例 |
|---|---|---|
| 普通表 | 大多数业务数据 | 工单、订单、客户 |
| 树表 | 有层级关系的数据 | 分类目录、部门组织架构、商品分类、地区层级 |
| 日历表 | 带时间范围的事件 | 会议室预约、项目排期、排班 |
| 评论表 | 围绕业务记录的讨论 | 任务评论、审批意见、客户反馈 |
| 文件表 | 附件元信息管理 | 合同附件、发票文件、产品图片 |
| 数据库视图 | 连接已有数据库 view | 财务报表视图、聚合视图 |
| SQL 表 | 把 SQL 查询结果结构化为数据表 | 销售汇总、库存预警 |
| 继承表 | 多类对象共享公共字段 | 资产父表派生电脑、车辆、家具 |
今天我们会用到普通表和树表,其他类型以后用到再学。
进入数据源管理:点击左下角「数据源管理」图标(齿轮旁边的数据库图标),你会看到「主数据源」——我们所有的表都建在这里。NocoBase 是一个数据模型驱动的平台,数据源可以是主数据库、外部数据库、REST API 等,详见 数据源概述;工单系统的所有表都建立在主数据源(NocoBase 主数据库,支持 PostgreSQL、MySQL、MariaDB 等)中。
创建核心表:工单
我们直奔主题,先创建系统的核心——工单表。
创建表
- 在数据源管理页面,点击主数据源进入。
- 点击「创建数据表」,选择「普通表」。
- 数据表名称:
tickets,数据表标题:工单。
创建表时,系统会默认勾选一组系统字段,它们会自动记录每条数据的元信息:
| 字段 | 说明 |
|---|---|
| ID | 主键,分布式唯一标识 |
| 创建日期 | 记录的创建时间 |
| 创建人 | 谁创建了这条记录 |
| 最后修改日期 | 最后一次更新时间 |
| 最后修改人 | 最后一次更新的用户 |
这些系统字段保持默认即可,不需要手动管理。如果某些场景不需要,也可以取消勾选。在源码层面,普通表默认开启autoGenId(自动生成id主键),同时由 NocoBase 框架在创建时挂载创建人、更新人等审计字段,这类由平台维护的字段在官方文档中统称为系统字段。
添加基础字段
表创建好了,接下来添加字段。点击工单表的「配置字段(Configure fields)」,你会看到刚才默认的系统字段已经在列表中了。点击右上角的「添加字段(Add field)」按钮,会展开一个下拉字段类型列表——从中选择你要添加的字段类型。
我们先添加工单自身的字段,关联字段稍后再加。
1. 标题(单行文本)
每条工单都需要一个简短的标题来概括问题。点击「添加字段」→ 选择「单行文本」(对应官方文档的文本字段):
- 字段名称:
title,字段标题:标题 - 点击「设置验证规则」,添加一条「必填」规则
字段创建时,NocoBase 会让你同时确认字段标识名(Field name)和界面显示名(Field display name):标识名用于 API、关系字段、权限、工作流等内部引用,创建后通常不再修改,只支持字母、数字和下划线,并且必须以字母开头;显示名则是业务人员在界面上看到的名字。这种"标识名 / 显示名分离"的设计,让数据库结构稳定、界面文案灵活。
2. 描述(Markdown(Vditor))
用来详细描述问题,支持格式排版,方便贴图、贴代码。在「添加字段」→「Media」分类下有三种可选:
| 字段类型 | 特点 |
|---|---|
| Markdown | 基本 Markdown,简单样式 |
| Rich Text | 富文本,简单样式 + 附件上传 |
| Markdown(Vditor) | 功能最丰富,支持所见即所得、即时渲染、源码编辑三种模式 |
我们选Markdown(Vditor):字段名称description,字段标题描述。富文本/长文本类字段适合保存正文、说明文档、处理方案、代码片段等较复杂内容,是工单问题描述的理想载体。
3. 状态(下拉菜单 - 单选)
工单从提交到完成,需要一个状态来跟踪进度。选择「下拉单选」字段类型:
- 字段名称:
status,字段标题:状态 - 添加选项值(每个选项需要填写「选项值」和「选项标签」,颜色可选):
| 选项值 | 选项标签 | 颜色 |
|---|---|---|
| pending | 待处理 | Orange(日暮) |
| in_progress | 处理中 | Blue(拂晓蓝) |
| completed | 已完成 | Green(极光绿) |
先填好选项并保存。然后再次点击该字段的「编辑(Edit)」,这时就能在「默认值」里选择「待处理」了。
首次创建时还没有选项数据,所以默认值选不了——需要保存后再回来设置。
为什么用下拉单选?因为状态是固定的几个值,下拉单选字段可以防止用户随意填写,保证数据规范。下拉单选特别适合状态、等级、类型、来源这类固定范围的业务字段——比如订单状态、工单状态、审批状态、客户等级、优先级。它的默认数据类型是
string,保存选中的选项值;每个选项都可以配置显示名称、选项值和颜色,颜色会在后续界面展示中直接生效。
4. 优先级(下拉菜单 - 单选)
区分工单的紧急程度,方便处理人员按优先级排序。同样是「下拉单选」:
- 字段名称:
priority,字段标题:优先级 - 添加选项值:
| 选项值 | 选项标签 | 颜色 |
|---|---|---|
| low | 低 | |
| medium | 中 | |
| high | 高 | Orange(日暮) |
| urgent | 紧急 | Red(薄暮) |
到这里,工单表有了 4 个基础字段。但是——工单应该有个"分类"吧?比如"网络问题""软件故障"?
如果把分类做成下拉菜单,当然也行。但你很快会发现:分类可能有子分类("硬件问题"下面还有"显示器""键盘""打印机"),下拉菜单就不够用了。
我们需要另一张表来专门管理分类。而且这张表,用 NocoBase 的树表来建最合适。
创建分类树表:让分类有层级
什么是树表
树表是一种特殊的数据表,它自带父子关系——每条记录可以有一个"父节点"。这天然适合有层级结构的数据:
硬件问题 ← 一级分类 ├── 显示器 ← 二级分类 ├── 键盘鼠标 └── 打印机 软件故障 ├── 办公软件 └── 系统问题 网络问题 账号权限如果用普通表,你需要自己手动建一个"父分类"字段来实现这种关系。而树表会自动帮你处理好,还支持树形展示、添加子记录等操作,省心很多。
在源码层面,NocoBase 的树表采用**邻接表(adjacency list)**结构保存父子关系:每条记录都通过一个外键指向自己的父节点。看 packages/core/database/src/listeners/adjacency-list.ts 的实现,当 Collection 配置了tree选项时,所有标记为treeParent/treeChildren的字段会自动把目标指向当前表、并把外键命名为parentId:
export const beforeDefineAdjacencyListCollection = (options: CollectionOptions) => { if (!options.tree) { return; } (options.fields || []).forEach((field) => { if (field.treeParent || field.treeChildren) { if (!field.target) { field.target = options.name; // 父子都指向自身 } if (!field.foreignKey) { field.foreignKey = 'parentId'; // 默认外键 } } }); };而在 packages/core/database/src/collection.ts 中,treeParentField和treeChildrenField两个 getter 会从字段列表里识别出标记为treeParent(多对一,指向父节点)和treeChildren(一对多,指向子节点)的关系字段,供树形查询与树形展示使用。对应的测试用例(packages/core/database/src/tests/eager-loading/eager-loading-tree.test.ts)也验证了这种配置方式:tree: 'adjacency-list'配合treeParent: true、treeChildren: true两个关系字段,即可得到完整的树模型。
创建表
- 回到数据源管理,点击「创建数据表」。
- 这次选择「树表」(不是普通表!)。
- 数据表名称:
categories,数据表标题:工单分类。
注意创建后,表里除了系统字段外,还会自动出现「Parent」和「Children」两个关系字段——这就是树表的特殊能力。通过 Parent 可以访问父节点,通过 Children 可以访问所有子节点,不需要你手动添加。
根据官方文档 树表,树表创建后内置字段通常包括:id(主键)、createdAt/createdBy/updatedAt/updatedBy(系统字段),以及parentId(保存父节点 ID,根节点通常为空)、parent(多对一关系字段,指向父节点)、children(一对多关系字段,表示子节点)。其中parentId就是邻接表结构中的外键列。
需要注意两点:一是树表只能通过主数据库页面创建,外部数据库、REST API 数据源和外部 NocoBase 数据源不支持创建树表;二是树表数据要避免形成循环关系(如 A 的父节点是 B、B 的父节点又是 A),循环会让树形展示和筛选结果异常。
添加字段
点击「配置字段」进入字段列表,可以看到系统字段和自动生成的 Parent、Children 字段。点击右上角「添加字段」:
字段一:分类名称
- 选择「单行文本」
- 字段名称:
name,字段标题:分类名称 - 点击「设置验证规则」,添加「必填」规则
字段二:颜色
- 选择「颜色」
- 字段名称:
color,字段标题:颜色
颜色字段可以让每个分类有自己的标识色,后面在界面上展示时会更直观。
到这里,两张数据表的基础字段就配好了。接下来我们把它们关联起来。
回到工单表:添加关联字段
关系字段初次接触可能有点抽象。如果你觉得不太好理解,可以先跳到 第 3 章:搭建页面,在实际的页面操作中感受一下数据是怎么展示的,再回来补上关联字段。
工单需要关联到分类、提交人和处理人。这类字段叫做关系字段——它不像"标题"那样直接存一段文字,而是存了另一张表里某条记录的 ID,通过这个 ID 找到对应的记录。
用一条具体的工单来看——工单的各个属性中,"分类"和"提交人"存的不是文字,而是一个 ID。系统通过这个 ID,从对应的表里精准找到那条记录。你在界面上看到的是名称("网络问题""张三"),背后就是通过 ID 关联的。多条工单可以指向同一个分类或同一个用户——这种关系叫做多对一(M2O,对应 Sequelize/数据库中的 BelongsTo 关系)。
在源码层面,多对一字段对应 packages/core/database/src/interfaces/many-to-one-interface.ts 中的ManyToOneInterface,底层由 packages/core/database/src/fields/belongs-to-field.ts 的BelongsToField实现。有几个值得了解的细节:
- 外键自动生成规则:如果配置时不显式填写外键,
BelongsToField会自动按${字段名}_${目标键}的驼峰规则生成,例如category_id(见 belongs-to-field.ts)。 - 目标键默认主键:
targetKey未指定时默认取目标表的主键(primaryKeyAttribute),也就是id。 - 类型匹配校验:绑定关联时会检查外键与目标键的数据类型是否一致,不一致会直接抛出错误(见 belongs-to-field.ts),从底层保证关联不会产生脏数据。
添加关系字段
回到工单表的「配置字段」→「添加字段」,选择「多对一」。创建时你会看到这些配置项:
| 配置项 | 说明 | 怎么填 |
|---|---|---|
| 源数据表 | 当前表(自动填好) | 不用改 |
| 目标数据表 | 要关联到哪张表 | 选择对应的表 |
| 外键 | 存在当前表里的关联列名 | 填一个有意义的名字 |
| 目标数据表标识字段 | 默认id | 保持默认即可 |
| ON DELETE | 目标记录被删除时的处理方式 | 保持默认即可 |
外键默认会自动生成一个随机名(如
f_xxxxx),建议改成有意义的名字,方便日后维护。命名用小写字母加下划线(如category_id),不用大小写混合。
关于配置项,官方文档 多对一 给出了更完整的说明:
- Source collection(源表):当前字段所在表;
- Target collection(目标表):与哪张表关联;
- Foreign key(外键):源表中的字段,用于建立两张表之间的关联;
- Target key(目标键):外键约束引用的字段,必须具备唯一性,通常就是主键
id; - ON DELETE:删除目标(父表)记录时对子表外键引用的处理规则——
CASCADE(级联删除关联子记录)、SET NULL(把子表外键置为 NULL)、RESTRICT(存在关联子记录时拒绝删除父记录,默认选项)、NO ACTION(与 RESTRICT 类似)。
按这个方式依次添加三个字段:
5. 分类 → 工单分类表
- 字段标题:
分类 - 目标数据表:选择「工单分类」(如果列表中没有,直接输入表名会自动创建)
- 外键:
category_id
6. 提交人 → 用户表
记录是谁提交了这条工单。NocoBase 内置了用户表,直接关联即可。
- 字段标题:
提交人 - 目标数据表:选择「用户」
- 外键:
submitter_id
7. 处理人 → 用户表
记录谁在负责处理这条工单。
- 字段标题:
处理人 - 目标数据表:选择「用户」
- 外键:
assignee_id
数据模型全貌
回顾一下我们搭建的完整数据模型:
- tickets(工单):
title、description、status、priority4 个基础字段,外加category_id → categories、submitter_id → users、assignee_id → users3 个多对一关联; - categories(工单分类):
name、color2 个自定义字段,加上系统自动生成的parentId/parent/children树形字段,天然支持多级分类; - users(用户):NocoBase 内置用户表,工单的提交人和处理人都指向它。
用 ER 图符号表示:tickets }o--|| categories(多条工单属于一个分类)、tickets }o--|| users(多条工单由同一用户提交/处理)。}o--||表示多对一关系:左边"多",右边"一"。
值得一提的是,从源码结构看,关系字段并不是在数据表里存一列"对象",而是在源表中存一个外键列(如category_id),NocoBase 通过关系元数据把它与目标表的主键(默认id)绑定起来,并在查询时自动做关联加载。这也是为什么"多条工单指向同一个分类"时只需要在分类表里保存一条记录——这正是数据建模消除冗余的核心价值。
小结
这一章我们完成了数据建模——整个工单系统的骨架:
- 工单表(tickets):4 个基础字段 + 3 个关联字段,用普通表创建
- 工单分类表(categories):2 个自定义字段 + 自动的 Parent/Children 字段,用树表创建,天然支持层级分类
我们学到了几个重要概念:
- 数据表(Collection)= 一类数据的容器,由
name(标识名)、title(显示名)、fields(字段)等选项定义 - 数据表类型= 不同场景选不同类型(普通表、树表、日历表、文件表……),树表基于邻接表结构实现,自动生成
parentId/parent/children - 字段(Field)= 数据的属性,通过「配置字段」→「添加字段」来创建,每个字段同时有标识名与显示名
- 系统字段= ID、创建日期、创建人等,建表时自动勾选
- 关系字段(多对一)= 指向另一张表的记录,通过外键建立表与表之间的关联,外键命名建议用有业务含义的小写下划线形式
你可能注意到,后续的截图中已经有数据了——这些测试数据是我们为了演示效果提前录入的,别着急。在 NocoBase 中,数据的增删改查都是通过前端页面完成的。第 3 章我们会搭建表格来展示数据,第 4 章会搭建表单来录入数据,一步步揭晓。
相关资源
- 数据源概述 — NocoBase 数据建模核心概念
- 数据表 — 全部数据表类型详解
- 数据表字段 — 所有字段类型详解
- 下拉单选 — 固定选项字段的配置说明
- 树表 — 树表的邻接表实现与使用约束
- 多对一关联 — 关联关系配置说明
- 第 3 章:搭建页面 — 下一章,让数据真正展示出来
【免费下载链接】nocobaseNocoBase is an open-source AI + no-code platform for building business systems fast. Instead of generating everything from scratch, AI works on top of production-proven infrastructure and a WYSIWYG no-code interface, so you get both speed and reliability.项目地址: https://gitcode.com/GitHub_Trending/no/nocobase
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考