20260818223354

This commit is contained in:
oneao committed 2026-08-18 22:33:54 +08:00
1 parent f01153e9fe
commit e91fd1d8de
2 files changed
+117 -210

No files matched your search

+42 -154
View File
@@ -2,7 +2,7 @@
## 1. 说明
本文档给出 FMS 新系统核心平台表的 SQL Server 建表语句,范围包括元数据中心(模块、实体、字段、关系、页面菜单、视图、表单、多语言)、Schema Compiler、权限中心、技术日志、登录日志与业务审计。
本文档给出 FMS 新系统核心平台表的 SQL Server 建表语句,范围包括元数据中心(模块、实体、字段、字段分组、关系、页面菜单、多语言)、Schema Compiler、权限中心、技术日志、登录日志与业务审计。
设计原则:
@@ -83,7 +83,7 @@ create index ix_s_module_dependency_depends_on
### 2.3 实体
实体是数据和字段的所有者,映射一张真实物理表。页面、列表、表单都只是实体的展示方式。
实体是数据和字段的所有者,同时声明"查询数据源"和"保存数据源"。查询源可以是真实表,也可以是带关联/聚合的 SQL 视图(视图只读,用于列表展示);保存目标必须是真实表。
```sql
create table dbo.s_module_entity (
@@ -92,7 +92,9 @@ create table dbo.s_module_entity (
b_module_key nvarchar(128) not null, -- 所属模块 key
b_name_key nvarchar(256) not null, -- 实体显示名多语言 key
b_description nvarchar(1000) null, -- 实体描述
b_physical_table sysname not null, -- 真实物理表名(如 ocean_booking)
b_view_table sysname not null, -- 查询数据源:真实表或 SQL 视图(如 ocean_booking / v_booking_with_fee)
b_save_table sysname null, -- 保存目标:真实表(查询源为视图时必填,只读实体可为空)
b_key_field sysname null, -- 保存对象主键字段(如 b_id)
b_delete_policy nvarchar(32) not null -- 删除策略(默认 lifecycle)
constraint df_s_module_entity_delete_policy default N'lifecycle',
b_origin nvarchar(32) not null -- 来源:platform / tenant
@@ -109,6 +111,17 @@ create table dbo.s_module_entity (
create index ix_s_module_entity_module_key
on dbo.s_module_entity (b_module_key, b_enabled);
create index ix_s_module_entity_save_table
on dbo.s_module_entity (b_save_table);
```
实体的"查/存"配置方式:
```text
简单实体: b_view_table = b_save_table = 同一张真实表
复杂列表: b_view_table = 带关联/聚合的 SQL 视图(只读), b_save_table = 真实表
纯查询实体: b_view_table = 视图或表, b_save_table = 空(只读,不可保存)
```
删除策略取值(`b_delete_policy`):
@@ -153,6 +166,7 @@ create table dbo.s_module_field (
constraint df_s_module_field_readonly default 0,
b_sort_no int not null -- 列表/表单默认排序
constraint df_s_module_field_sort_no default 0,
b_group_key nvarchar(128) null, -- 所属分组 key(逻辑外键,空为未分组)
b_width int null, -- 列表列宽(px)
b_component_type nvarchar(64) null, -- 固定表单组件类型(input/select/date)
b_auditable tinyint not null -- 是否记录字段级审计
@@ -253,8 +267,6 @@ create table dbo.s_module_menu (
b_menu_type nvarchar(32) not null, -- 菜单类型:directory/page/data_category/data/external
b_route nvarchar(256) null, -- 固定页面或外链路由
b_entity_key nvarchar(192) null, -- data 类型绑定的实体 key
b_view_key nvarchar(192) null, -- 绑定的列表视图 key
b_form_key nvarchar(192) null, -- 绑定的表单 key
b_permission_key nvarchar(192) null, -- 页面访问权限 key
b_icon_key nvarchar(128) null, -- 菜单图标 key
b_sort_no int not null -- 菜单排序号
@@ -284,153 +296,40 @@ create index ix_s_module_menu_entity_key
on dbo.s_module_menu (b_entity_key, b_enabled);
```
约束由元数据校验器执行:`directory` 节点不能填写业务表或外链路由;`page` 节点必须填写 `b_route`,并绑定 `b_view_key`/`b_form_key`;`data` 节点必须填写 `b_entity_key` 和 `b_view_key`(列表)、`b_form_key`(表单);`external` 节点必须填写 `b_route` 和 `b_permission_key`。固定页面组件负责布局,表中只保存实体与视图/表单引用和导航配置。目录仅在至少存在一个可访问子页面时显示。
约束由元数据校验器执行:`directory` 节点不能填写业务表或外链路由;`page` 节点必须填写 `b_route`;`data` 节点必须填写 `b_entity_key`(查询走实体 `b_view_table`,保存走实体 `b_save_table`);`external` 节点必须填写 `b_route` 和 `b_permission_key`。固定页面组件负责布局,表中只保存实体引用和导航配置。目录仅在至少存在一个可访问子页面时显示。
### 2.7 视图(列表)
### 2.7 字段分组
视图定义"一个实体的一种列表展示方式",一个实体可以有多个视图。列表字段配置在 `s_module_view_field`。
字段分组是实体级独立定义,用于表单/编辑页面按分组展示字段。分组定义集中在 `s_module_field_group`,字段通过 `s_module_field.b_group_key` 引用分组,避免在每个字段上重复维护分组标题与排序。
```sql
create table dbo.s_module_view (
create table dbo.s_module_field_group (
b_id bigint identity(1, 1) not null, -- 内部自增主键
b_view_key nvarchar(192) not null, -- 视图稳定业务标识(如 ocean.booking.default-list)
b_entity_key nvarchar(192) not null, -- 所属实体 key
b_name_key nvarchar(256) not null, -- 视图显示名多语言 key
b_queryable tinyint not null -- 是否提供查询条件区
constraint df_s_module_view_queryable default 1,
b_exportable tinyint not null -- 是否允许导出
constraint df_s_module_view_exportable default 1,
b_page_size int null, -- 默认分页大小
b_config_text nvarchar(max) null, -- 视图扩展配置
b_enabled tinyint not null -- 是否启用
constraint df_s_module_view_enabled default 1,
b_group_key nvarchar(128) not null, -- 分组稳定标识(如 basic/transport/finance)
b_name_key nvarchar(256) not null, -- 分组标题多语言 key
b_sort_no int not null -- 分组排序号(0,1,2...)
constraint df_s_module_field_group_sort_no default 0,
b_enabled tinyint not null -- 是否启用(停用则整组隐藏)
constraint df_s_module_field_group_enabled default 1,
b_config_text nvarchar(max) null, -- 分组扩展配置
b_created_at datetime2(3) not null -- 创建时间(UTC)
constraint df_s_module_view_created_at default sysutcdatetime(),
constraint df_s_module_field_group_created_at default sysutcdatetime(),
b_created_by nvarchar(128) not null, -- 创建人
constraint pk_s_module_view primary key (b_id),
constraint ux_s_module_view_key unique (b_view_key)
);
create index ix_s_module_view_entity_key
on dbo.s_module_view (b_entity_key, b_enabled);
create table dbo.s_module_view_field (
b_id bigint identity(1, 1) not null, -- 内部自增主键
b_view_key nvarchar(192) not null, -- 所属视图 key
b_field_key nvarchar(128) not null, -- 引用的字段 key
b_sort_no int not null -- 列排序号
constraint df_s_module_view_field_sort_no default 0,
b_width int null, -- 列宽(px)
b_visible tinyint not null -- 是否显示
constraint df_s_module_view_field_visible default 1,
b_frozen tinyint not null -- 是否固定列
constraint df_s_module_view_field_frozen default 0,
b_align nvarchar(16) null, -- 对齐方式(left/center/right)
b_format_text nvarchar(256) null, -- 显示格式(如日期、千分位)
b_queryable tinyint not null -- 是否作为查询条件
constraint df_s_module_view_field_queryable default 0,
b_query_operator nvarchar(32) null, -- 查询运算符(eq/contains/gte/...)
b_created_at datetime2(3) not null -- 创建时间(UTC)
constraint df_s_module_view_field_created_at default sysutcdatetime(),
b_created_by nvarchar(128) not null, -- 创建人
constraint pk_s_module_view_field primary key (b_id),
constraint ux_s_module_view_field_key unique (
b_view_key,
b_field_key
constraint pk_s_module_field_group primary key (b_id),
constraint ux_s_module_field_group_key unique (
b_entity_key,
b_group_key
)
);
create index ix_s_module_view_field_view_key
on dbo.s_module_view_field (b_view_key, b_sort_no);
create index ix_s_module_field_group_entity_key
on dbo.s_module_field_group (b_entity_key, b_enabled, b_sort_no);
```
### 2.8 表单
表单渲染时按 `b_sort_no` 排序分组,组内字段按 `s_module_field.b_sort_no` 排序;`s_module_field.b_group_key` 为空的字段显示在"未分组"区。新增/调整/停用分组只改 `s_module_field_group`,不需要逐个修改字段。
表单定义"一个实体的一种编辑展示方式",一个实体可以有多个表单(默认编辑表单、详情表单)。子表区域通过 `s_module_form_relation` 引用关系并绑定子表视图/表单。
```sql
create table dbo.s_module_form (
b_id bigint identity(1, 1) not null, -- 内部自增主键
b_form_key nvarchar(192) not null, -- 表单稳定业务标识(如 ocean.booking.default-form)
b_entity_key nvarchar(192) not null, -- 所属实体 key
b_name_key nvarchar(256) not null, -- 表单显示名多语言 key
b_form_type nvarchar(32) not null -- 表单类型(默认 default)
constraint df_s_module_form_form_type default N'default',
b_config_text nvarchar(max) null, -- 表单扩展配置(控件特有属性与布局 JSON)
b_enabled tinyint not null -- 是否启用
constraint df_s_module_form_enabled default 1,
b_created_at datetime2(3) not null -- 创建时间(UTC)
constraint df_s_module_form_created_at default sysutcdatetime(),
b_created_by nvarchar(128) not null, -- 创建人
constraint pk_s_module_form primary key (b_id),
constraint ux_s_module_form_key unique (b_form_key)
);
create index ix_s_module_form_entity_key
on dbo.s_module_form (b_entity_key, b_enabled);
create table dbo.s_module_form_field (
b_id bigint identity(1, 1) not null, -- 内部自增主键
b_form_key nvarchar(192) not null, -- 所属表单 key
b_field_key nvarchar(128) not null, -- 引用的字段 key
b_group_key nvarchar(128) null, -- 所属分组 key(逻辑分组)
b_sort_no int not null -- 表单内排序号
constraint df_s_module_form_field_sort_no default 0,
b_required tinyint not null -- 是否必填(覆盖字段默认)
constraint df_s_module_form_field_required default 0,
b_readonly tinyint not null -- 是否只读(覆盖字段默认)
constraint df_s_module_form_field_readonly default 0,
b_visible tinyint not null -- 是否显示
constraint df_s_module_form_field_visible default 1,
b_span int null, -- 布局占宽(栅格)
b_component_type nvarchar(64) null, -- 控件类型(覆盖字段默认)
b_config_text nvarchar(max) null, -- 控件扩展配置
b_created_at datetime2(3) not null -- 创建时间(UTC)
constraint df_s_module_form_field_created_at default sysutcdatetime(),
b_created_by nvarchar(128) not null, -- 创建人
constraint pk_s_module_form_field primary key (b_id),
constraint ux_s_module_form_field_key unique (
b_form_key,
b_field_key
)
);
create index ix_s_module_form_field_form_key
on dbo.s_module_form_field (b_form_key, b_sort_no);
create table dbo.s_module_form_relation (
b_id bigint identity(1, 1) not null, -- 内部自增主键
b_form_key nvarchar(192) not null, -- 所属表单 key
b_relation_key nvarchar(192) not null, -- 引用的主子关系 key
b_child_view_key nvarchar(192) null, -- 子表列表配置 key
b_child_form_key nvarchar(192) null, -- 子表编辑配置 key(可选)
b_display_mode nvarchar(16) not null -- 子表显示模式(默认 table)
constraint df_s_module_form_relation_display_mode default N'table',
b_editable tinyint not null -- 是否可编辑
constraint df_s_module_form_relation_editable default 1,
b_allow_add tinyint not null -- 是否允许新增行
constraint df_s_module_form_relation_allow_add default 1,
b_allow_delete tinyint not null -- 是否允许删除行
constraint df_s_module_form_relation_allow_delete default 1,
b_allow_sort tinyint not null -- 是否允许排序行
constraint df_s_module_form_relation_allow_sort default 1,
b_sort_no int not null -- 子表区域排序号
constraint df_s_module_form_relation_sort_no default 0,
b_created_at datetime2(3) not null -- 创建时间(UTC)
constraint df_s_module_form_relation_created_at default sysutcdatetime(),
b_created_by nvarchar(128) not null, -- 创建人
constraint pk_s_module_form_relation primary key (b_id),
constraint ux_s_module_form_relation_key unique (
b_form_key,
b_relation_key
)
);
create index ix_s_module_form_relation_form_key
on dbo.s_module_form_relation (b_form_key, b_sort_no);
```
### 2.9 多语言
### 2.8 多语言
多语言是元数据运行时的一部分,统一解析模块、菜单、实体、字段、动作和主数据的显示名称。
@@ -461,7 +360,7 @@ create index ix_s_module_i18n_resource_key
## 3. Schema Compiler
元数据变更通过服务层校验后立即生效。Schema Compiler 根据 `s_module_entity` 的物理表配置和 `s_module_field` 的物理字段配置生成所需的 `CREATE TABLE`、`ALTER TABLE`、索引和约束语句,并由应用服务直接执行;执行成功后提交元数据变更,失败则回滚本次操作并返回错误。
元数据变更通过服务层校验后立即生效。Schema Compiler 根据 `s_module_entity` 的保存数据源(`b_save_table`)配置和 `s_module_field` 的物理字段配置生成所需的 `CREATE TABLE`、`ALTER TABLE`、索引和约束语句,并由应用服务直接执行;执行成功后提交元数据变更,失败则回滚本次操作并返回错误。查询数据源(`b_view_table`)为 SQL 视图时,由运维或售后在数据库中维护视图定义,Schema Compiler 不生成视图 DDL。
执行机制:
@@ -700,24 +599,14 @@ create index ix_s_log_audit_field_audit_event_id
| `s_module_dependency` | `b_depends_on_module_key` | `s_module.b_module_key` |
| `s_module_entity` | `b_module_key` | `s_module.b_module_key` |
| `s_module_field` | `b_entity_key` | `s_module_entity.b_entity_key` |
| `s_module_field` | `b_group_key` | 实体范围内的 `s_module_field_group.b_group_key` |
| `s_module_field_group` | `b_entity_key` | `s_module_entity.b_entity_key` |
| `s_module_relation` | `b_entity_key` | `s_module_entity.b_entity_key` |
| `s_module_relation` | `b_target_entity_key` | `s_module_entity.b_entity_key` |
| `s_module_menu` | `b_module_key` | `s_module.b_module_key` |
| `s_module_menu` | `b_parent_menu_key` | `s_module_menu.b_menu_key` |
| `s_module_menu` | `b_entity_key` | `s_module_entity.b_entity_key` |
| `s_module_menu` | `b_view_key` | `s_module_view.b_view_key` |
| `s_module_menu` | `b_form_key` | `s_module_form.b_form_key` |
| `s_module_menu` | `b_permission_key` | `s_auth_permission.b_permission_key` |
| `s_module_view` | `b_entity_key` | `s_module_entity.b_entity_key` |
| `s_module_view_field` | `b_view_key` | `s_module_view.b_view_key` |
| `s_module_view_field` | `b_field_key` | 实体范围内的 `s_module_field.b_field_key` |
| `s_module_form` | `b_entity_key` | `s_module_entity.b_entity_key` |
| `s_module_form_field` | `b_form_key` | `s_module_form.b_form_key` |
| `s_module_form_field` | `b_field_key` | 实体范围内的 `s_module_field.b_field_key` |
| `s_module_form_relation` | `b_form_key` | `s_module_form.b_form_key` |
| `s_module_form_relation` | `b_relation_key` | `s_module_relation.b_relation_key` |
| `s_module_form_relation` | `b_child_view_key` | `s_module_view.b_view_key` |
| `s_module_form_relation` | `b_child_form_key` | `s_module_form.b_form_key` |
| `s_auth_user_role` | `b_role_key` | `s_auth_role.b_role_key` |
| `s_auth_role_permission` | `b_permission_key` | `s_auth_permission.b_permission_key` |
| `s_log_audit_field` | `b_event_id` | `s_log_audit.b_id` |
@@ -730,7 +619,7 @@ create index ix_s_log_audit_field_audit_event_id
稳定 key 是否存在
-> 目标资源是否启用
-> 跨模块引用是否合法(依赖图中可达)
-> 字段、实体类型和视图/表单配置是否匹配
-> 字段、实体类型和分组配置是否匹配
-> 删除或停用前是否仍被其他资源引用
```
@@ -739,8 +628,7 @@ create index ix_s_log_audit_field_audit_event_id
建议按以下顺序创建和启用这些表:
```text
元数据基础表(模块/依赖/实体/字段/关系)
-> 视图与表单表
元数据基础表(模块/依赖/实体/字段/字段分组/关系)
-> 页面菜单入口表
-> 多语言表
-> 权限表
+75 -56
View File
@@ -196,7 +196,7 @@ const MODULES = [
desc: '海运出口全流程:订舱、海运单、集装箱、提单、船期。典型的"一模块多实体多菜单"形态。',
deps: ['platform', 'master-data'],
entities: [
{ key: 'ocean.booking', table: 'ocean_booking', policy: 'lifecycle', desc: '订舱主单' },
{ key: 'ocean.booking', table: 'ocean_booking', view: 'v_booking_with_fee', policy: 'lifecycle', desc: '订舱主单' },
{ key: 'ocean.container', table: 'ocean_container', policy: 'cascadeOwned', desc: '箱信息(订舱的子表)' },
{ key: 'ocean.shipment', table: 'ocean_shipment', policy: 'lifecycle', desc: '海运单' },
{ key: 'ocean.bill_of_lading', table: 'ocean_bill_of_lading', policy: 'lifecycle', desc: '提单' },
@@ -204,7 +204,7 @@ const MODULES = [
],
menus: [
{ key: 'ocean.booking.cat', name: '订舱管理', type: 'data_category', children: [
{ key: 'ocean.booking.list', name: '订舱列表', type: 'data', entity: 'ocean.booking', view: 'ocean.booking.default-list', form: 'ocean.booking.default-form', route: '/ocean/bookings', permission: 'ocean.booking.view' },
{ key: 'ocean.booking.list', name: '订舱列表', type: 'data', entity: 'ocean.booking', route: '/ocean/bookings', permission: 'ocean.booking.view' },
]},
{ key: 'ocean.shipment.cat', name: '海运单', type: 'data_category', children: [
{ key: 'ocean.shipment.list', name: '海运单列表', type: 'data', entity: 'ocean.shipment', route: '/ocean/shipments', permission: 'ocean.shipment.view' },
@@ -274,16 +274,16 @@ const MODULES = [
const ENTITY_FIELDS = {
'ocean.booking': [
{ key: 'booking_no', label: '订舱号', type: 'varchar(50)', write: 'direct', req: 1, qry: 1, list: 1, edit: 1 },
{ key: 'customer_id', label: '客户', type: 'bigint', write: 'direct', req: 1, qry: 1, list: 1, edit: 1 },
{ key: 'carrier_id', label: '船公司', type: 'bigint', write: 'direct', req: 1, qry: 1, list: 1, edit: 1 },
{ key: 'etd', label: '预计离港日', type: 'date', write: 'direct', req: 0, qry: 1, list: 1, edit: 1 },
{ key: 'load_port', label: '起运港', type: 'bigint', write: 'direct', req: 1, qry: 1, list: 1, edit: 1 },
{ key: 'discharge_port', label: '目的港', type: 'bigint', write: 'direct', req: 1, qry: 1, list: 1, edit: 1 },
{ key: 'freight_payment',label: '运费支付', type: 'varchar(32)', write: 'direct', req: 0, qry: 1, list: 1, edit: 1 },
{ key: 'gross_weight', label: '毛重', type: 'decimal(18,3)', write: 'direct', req: 0, qry: 0, list: 1, edit: 1 },
{ key: 'volume', label: '体积', type: 'decimal(18,3)', write: 'direct', req: 0, qry: 0, list: 1, edit: 1 },
{ key: 'container_count',label: '箱量', type: 'int', write: 'computed', req: 0, qry: 0, list: 1, edit: 0 },
{ key: 'booking_no', label: '订舱号', type: 'varchar(50)', write: 'direct', req: 1, qry: 1, list: 1, edit: 1, group: 'basic' },
{ key: 'customer_id', label: '客户', type: 'bigint', write: 'direct', req: 1, qry: 1, list: 1, edit: 1, group: 'basic' },
{ key: 'carrier_id', label: '船公司', type: 'bigint', write: 'direct', req: 1, qry: 1, list: 1, edit: 1, group: 'basic' },
{ key: 'etd', label: '预计离港日', type: 'date', write: 'direct', req: 0, qry: 1, list: 1, edit: 1, group: 'transport' },
{ key: 'load_port', label: '起运港', type: 'bigint', write: 'direct', req: 1, qry: 1, list: 1, edit: 1, group: 'transport' },
{ key: 'discharge_port', label: '目的港', type: 'bigint', write: 'direct', req: 1, qry: 1, list: 1, edit: 1, group: 'transport' },
{ key: 'freight_payment',label: '运费支付', type: 'varchar(32)', write: 'direct', req: 0, qry: 1, list: 1, edit: 1, group: 'transport' },
{ key: 'gross_weight', label: '毛重', type: 'decimal(18,3)', write: 'direct', req: 0, qry: 0, list: 1, edit: 1, group: 'cargo' },
{ key: 'volume', label: '体积', type: 'decimal(18,3)', write: 'direct', req: 0, qry: 0, list: 1, edit: 1, group: 'cargo' },
{ key: 'container_count',label: '箱量', type: 'int', write: 'computed', req: 0, qry: 0, list: 1, edit: 0, group: 'cargo' },
{ key: 'status', label: '状态', type: 'varchar(32)', write: 'actionOnly', req: 0, qry: 1, list: 1, edit: 0 },
{ key: 'remark', label: '备注', type: 'nvarchar(1000)', write: 'direct', req: 0, qry: 0, list: 0, edit: 1 },
{ key: 'created_by', label: '创建人', type: 'nvarchar(128)', write: 'system', req: 0, qry: 0, list: 0, edit: 0 },
@@ -297,15 +297,27 @@ const ENTITY_FIELDS = {
{ key: 'vgm', label: 'VGM', type: 'decimal(18,3)', write: 'direct', req: 0, qry: 0, list: 1, edit: 1 },
],
'finance.invoice': [
{ key: 'invoice_no', label: '发票号', type: 'varchar(50)', write: 'direct', req: 1, qry: 1, list: 1, edit: 0 },
{ key: 'direction', label: '方向', type: 'varchar(16)', write: 'createOnly', req: 1, qry: 1, list: 1, edit: 0 },
{ key: 'customer_id', label: '往来单位', type: 'bigint', write: 'direct', req: 1, qry: 1, list: 1, edit: 1 },
{ key: 'amount', label: '含税金额', type: 'decimal(18,2)', write: 'computed', req: 0, qry: 0, list: 1, edit: 0 },
{ key: 'currency', label: '币种', type: 'varchar(8)', write: 'direct', req: 1, qry: 1, list: 1, edit: 1 },
{ key: 'invoice_no', label: '发票号', type: 'varchar(50)', write: 'direct', req: 1, qry: 1, list: 1, edit: 0, group: 'basic' },
{ key: 'direction', label: '方向', type: 'varchar(16)', write: 'createOnly', req: 1, qry: 1, list: 1, edit: 0, group: 'basic' },
{ key: 'customer_id', label: '往来单位', type: 'bigint', write: 'direct', req: 1, qry: 1, list: 1, edit: 1, group: 'basic' },
{ key: 'amount', label: '含税金额', type: 'decimal(18,2)', write: 'computed', req: 0, qry: 0, list: 1, edit: 0, group: 'amount' },
{ key: 'currency', label: '币种', type: 'varchar(8)', write: 'direct', req: 1, qry: 1, list: 1, edit: 1, group: 'amount' },
{ key: 'status', label: '状态', type: 'varchar(32)', write: 'actionOnly', req: 0, qry: 1, list: 1, edit: 0 },
],
};
const ENTITY_GROUPS = {
'ocean.booking': [
{ key: 'basic', name: '基本信息', sort: 0 },
{ key: 'transport', name: '运输信息', sort: 1 },
{ key: 'cargo', name: '货物信息', sort: 2 },
],
'finance.invoice': [
{ key: 'basic', name: '基本信息', sort: 0 },
{ key: 'amount', name: '金额信息', sort: 1 },
],
};
const ENTITY_RELATIONS = {
'ocean.booking': [
{ key: 'ocean.booking.containers', type: 'one-to-many', target: 'ocean.container', ownership: 'composition', delete: 'cascade', save: 'with-parent' },
@@ -314,19 +326,6 @@ const ENTITY_RELATIONS = {
],
};
const ENTITY_VIEWS = {
'ocean.booking': [
{ key: 'ocean.booking.default-list', queryable: 1, exportable: 1, pageSize: 50 },
{ key: 'ocean.booking.query-simple', queryable: 1, exportable: 1, pageSize: 20 },
],
};
const ENTITY_FORMS = {
'ocean.booking': [
{ key: 'ocean.booking.default-form', type: 'default', relations: ['ocean.booking.containers'] },
{ key: 'ocean.booking.readonly-form', type: 'readonly', relations: [] },
],
};
/* ============ 状态与工具 ============ */
const state = { selectedModule: null, selectedMenu: null, selectedEntity: null, openModules: new Set(['ocean']) };
@@ -340,7 +339,11 @@ function findMenuByKey(k) {
function menuOwner(k) {
return MODULES.find(m => m.menus.some(mm => mm.key === k || (mm.children && mm.children.some(c => c.key === k))));
}
function entityTable(ek) {
function entityViewTable(ek) {
for (const m of MODULES) { const e = m.entities.find(x => x.key === ek); if (e) return e.view || e.table; }
return '—';
}
function entitySaveTable(ek) {
for (const m of MODULES) { const e = m.entities.find(x => x.key === ek); if (e) return e.table; }
return '—';
}
@@ -473,14 +476,14 @@ function renderModule() {
<div class="section">
<div class="section-title">包含实体(s_module_entity)<span class="hint">点击实体查看字段元数据</span></div>
<table class="grid">
<thead><tr><th>实体 key</th><th>物理表</th><th>删除策略</th><th>描述</th><th>字段数</th></tr></thead>
<thead><tr><th>实体 key</th><th>查询源 view_table</th><th>保存目标 save_table</th><th>删除策略</th><th>字段数</th></tr></thead>
<tbody>
${m.entities.map(e => `
<tr class="row-click" data-entity="${e.key}">
<td class="mono">${e.key}</td>
<td class="mono dim">${e.view || e.table}${e.view ? '(视图)' : ''}</td>
<td class="mono dim">${e.table}</td>
<td class="dim">${e.policy}</td>
<td>${e.desc}</td>
<td>${ENTITY_FIELDS[e.key] ? ENTITY_FIELDS[e.key].length : 0}</td>
</tr>`).join('')}
</tbody>
@@ -517,10 +520,12 @@ function renderMenu() {
const t = MENU_TYPES[menu.type];
const owner = menuOwner(menu.key);
const entity = menu.entity ? {
key: menu.entity, table: entityTable(menu.entity),
key: menu.entity,
viewTable: entityViewTable(menu.entity),
saveTable: entitySaveTable(menu.entity),
fields: ENTITY_FIELDS[menu.entity] ? ENTITY_FIELDS[menu.entity].length : 0,
views: ENTITY_VIEWS[menu.entity] ? ENTITY_VIEWS[menu.entity].length : 0,
forms: ENTITY_FORMS[menu.entity] ? ENTITY_FORMS[menu.entity].length : 0,
groups: ENTITY_GROUPS[menu.entity] ? ENTITY_GROUPS[menu.entity].length : 0,
rels: ENTITY_RELATIONS[menu.entity] ? ENTITY_RELATIONS[menu.entity].length : 0,
} : null;
return `
@@ -542,13 +547,12 @@ function renderMenu() {
${entity ? `
<div class="section">
<div class="section-title">绑定的实体 / 视图 / 表单 <span class="hint">data 类型</span></div>
<div class="section-title">绑定的实体 <span class="hint">查询走 view_table,保存走 save_table</span></div>
<div class="kv-grid">
<div class="kv"><div class="k">实体 entity_key</div><div class="v"><a class="link" data-entity="${entity.key}">${entity.key} ›</a></div></div>
<div class="kv"><div class="k">物理表</div><div class="v">${entity.table}</div></div>
<div class="kv"><div class="k">字段 / 视图 / 表单</div><div class="v">${entity.fields} / ${entity.views} / ${entity.forms}</div></div>
<div class="kv"><div class="k">视图 key</div><div class="v">${menu.view || '—'}</div></div>
<div class="kv"><div class="k">表单 key</div><div class="v">${menu.form || '—'}</div></div>
<div class="kv"><div class="k">查询源 view_table</div><div class="v">${entity.viewTable}${entity.viewTable !== entity.saveTable ? '(视图)' : ''}</div></div>
<div class="kv"><div class="k">保存目标 save_table</div><div class="v">${entity.saveTable}</div></div>
<div class="kv"><div class="k">字段 / 分组 / 关系</div><div class="v">${entity.fields} / ${entity.groups} / ${entity.rels}</div></div>
</div>
</div>` : ''}
@@ -569,26 +573,50 @@ function renderEntity() {
const ek = ent.key;
const fields = ENTITY_FIELDS[ek] || [];
const rels = ENTITY_RELATIONS[ek] || [];
const views = ENTITY_VIEWS[ek] || [];
const forms = ENTITY_FORMS[ek] || [];
const groups = ENTITY_GROUPS[ek] || [];
const viewTable = ent.view || ent.table;
const isView = !!ent.view;
const groupName = gk => (groups.find(g => g.key === gk) || {}).name || '未分组';
return `
<div class="page-head">
<div class="ph-title">${ent.desc} <span class="tag">${ent.policy}</span></div>
<div class="ph-title">${ent.desc} <span class="tag">${ent.policy}</span>${isView ? ' <span class="tag">视图查询</span>' : ''}</div>
<div class="ph-desc">实体是数据和字段的所有者,字段挂实体(s_module_field.b_entity_key)。返回<a class="link" data-back="mod">${mod.name} 模块</a>。</div>
<div class="ph-meta">entity_key: ${ek} · 物理表: ${ent.table} · module: ${mod.key}</div>
<div class="ph-meta">entity_key: ${ek} · 查询源: ${viewTable} · 保存目标: ${ent.table} · module: ${mod.key}</div>
</div>
<div class="section">
<div class="section-title">字段元数据(s_module_field)<span class="hint">写入模式 / 必填 / 查询 / 列表 / 编辑</span></div>
<div class="section-title">查询 / 保存数据源 <span class="hint">s_module_entity</span></div>
<div class="kv-grid">
<div class="kv"><div class="k">查询源 view_table</div><div class="v">${viewTable}${isView ? '(SQL 视图,带关联/聚合)' : ''}</div></div>
<div class="kv"><div class="k">保存目标 save_table</div><div class="v">${ent.table}</div></div>
<div class="kv"><div class="k">主键字段 key_field</div><div class="v">b_id</div></div>
</div>
</div>
<div class="section">
<div class="section-title">字段分组(s_module_field_group)<span class="hint">分组独立定义,字段引用 group_key</span></div>
${groups.length ? `<table class="grid">
<thead><tr><th>分组 key</th><th>标题 name_key</th><th>排序</th></tr></thead>
<tbody>${groups.map(g => `<tr>
<td class="mono">${g.key}</td>
<td>${g.name}</td>
<td>${g.sort}</td>
</tr>`).join('')}</tbody>
</table>` : '<div class="empty">无分组(字段直接平铺)</div>'}
</div>
<div class="section">
<div class="section-title">字段元数据(s_module_field)<span class="hint">写入模式 / 分组 / 必填 / 查询 / 列表 / 编辑</span></div>
<table class="grid">
<thead><tr><th>字段 key</th><th>标签</th><th>数据类型</th><th>写入模式</th><th>必填</th><th>查询</th><th>列表</th><th>编辑</th></tr></thead>
<thead><tr><th>字段 key</th><th>标签</th><th>数据类型</th><th>写入模式</th><th>分组</th><th>必填</th><th>查询</th><th>列表</th><th>编辑</th></tr></thead>
<tbody>
${fields.map(f => `<tr>
<td class="mono">${f.key}</td>
<td>${f.label}</td>
<td class="mono dim">${f.type}</td>
<td><span class="wm-tag">${f.write}</span></td>
<td>${f.group ? groupName(f.group) : '<span class="dim">—</span>'}</td>
<td>${f.req ? '必填' : '<span class="dim">—</span>'}</td>
<td>${f.qry ? '✓' : ''}</td>
<td>${f.list ? '✓' : ''}</td>
@@ -615,15 +643,6 @@ function renderEntity() {
</tr>`).join('')}</tbody>
</table>` : '<div class="empty">无</div>'}
</div>
<div class="section">
<div class="section-title">视图与表单 <span class="hint">一个实体可有多视图/多表单</span></div>
<div class="kv-grid">
<div class="kv"><div class="k">视图 s_module_view</div><div class="v">${views.map(v => v.key).join('、') || '—'}</div></div>
<div class="kv"><div class="k">表单 s_module_form</div><div class="v">${forms.map(v => v.key).join('、') || '—'}</div></div>
</div>
${forms.some(f => f.relations.length) ? `<div style="margin-top:10px;font-size:12.5px;color:var(--text-3)">主子表区域(s_module_form_relation):${forms.find(f => f.relations.length).relations.join('、')} 以 table 模式内嵌于表单</div>` : ''}
</div>
`;
}