@aiao/rxdb-model 0.0.19 → 0.0.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +43 -6
- package/dist/cn.d.ts +19 -0
- package/dist/cn.d.ts.map +1 -0
- package/dist/entity-capabilities.d.ts +25 -0
- package/dist/entity-capabilities.d.ts.map +1 -0
- package/dist/entity-detail/detail-tabs.d.ts +7 -1
- package/dist/entity-detail/detail-tabs.d.ts.map +1 -1
- package/dist/entity-detail/index.d.ts +5 -0
- package/dist/entity-detail/index.d.ts.map +1 -1
- package/dist/entity-detail/interfaces.d.ts +25 -1
- package/dist/entity-detail/interfaces.d.ts.map +1 -1
- package/dist/entity-field.utils.d.ts +54 -2
- package/dist/entity-field.utils.d.ts.map +1 -1
- package/dist/entity-form/form-data.d.ts +21 -0
- package/dist/entity-form/form-data.d.ts.map +1 -1
- package/dist/entity-form/form-fields.d.ts +19 -1
- package/dist/entity-form/form-fields.d.ts.map +1 -1
- package/dist/entity-form/form-validation.d.ts +13 -0
- package/dist/entity-form/form-validation.d.ts.map +1 -1
- package/dist/entity-form/index.d.ts +5 -0
- package/dist/entity-form/index.d.ts.map +1 -1
- package/dist/entity-form/interfaces.d.ts +60 -0
- package/dist/entity-form/interfaces.d.ts.map +1 -1
- package/dist/entity-list/manual-order-list.d.ts +92 -0
- package/dist/entity-list/manual-order-list.d.ts.map +1 -0
- package/dist/entity-list/types.d.ts +25 -0
- package/dist/entity-list/types.d.ts.map +1 -0
- package/dist/entity-table/columns/build-editable-columns.d.ts +24 -2
- package/dist/entity-table/columns/build-editable-columns.d.ts.map +1 -1
- package/dist/entity-table/columns/column-utils.d.ts +75 -9
- package/dist/entity-table/columns/column-utils.d.ts.map +1 -1
- package/dist/entity-table/editors/color-editor.d.ts +23 -0
- package/dist/entity-table/editors/color-editor.d.ts.map +1 -0
- package/dist/entity-table/editors/date-editor.d.ts +5 -0
- package/dist/entity-table/editors/date-editor.d.ts.map +1 -1
- package/dist/entity-table/editors/enum-editor.d.ts +10 -0
- package/dist/entity-table/editors/enum-editor.d.ts.map +1 -1
- package/dist/entity-table/editors/global-overlay-editor.d.ts +26 -3
- package/dist/entity-table/editors/global-overlay-editor.d.ts.map +1 -1
- package/dist/entity-table/editors/icon-list-editor.d.ts +12 -0
- package/dist/entity-table/editors/icon-list-editor.d.ts.map +1 -1
- package/dist/entity-table/editors/json-editor.d.ts +5 -0
- package/dist/entity-table/editors/json-editor.d.ts.map +1 -1
- package/dist/entity-table/editors/key-value-editor.d.ts +11 -1
- package/dist/entity-table/editors/key-value-editor.d.ts.map +1 -1
- package/dist/entity-table/editors/lucide-svg.d.ts +3 -2
- package/dist/entity-table/editors/lucide-svg.d.ts.map +1 -1
- package/dist/entity-table/editors/multiselect-editor.d.ts +24 -6
- package/dist/entity-table/editors/multiselect-editor.d.ts.map +1 -1
- package/dist/entity-table/editors/number-editor.d.ts +32 -2
- package/dist/entity-table/editors/number-editor.d.ts.map +1 -1
- package/dist/entity-table/editors/relation-editor.d.ts +12 -0
- package/dist/entity-table/editors/relation-editor.d.ts.map +1 -1
- package/dist/entity-table/editors/safe-list-editor.d.ts +1 -0
- package/dist/entity-table/editors/safe-list-editor.d.ts.map +1 -1
- package/dist/entity-table/editors/tags-editor.d.ts +11 -0
- package/dist/entity-table/editors/tags-editor.d.ts.map +1 -1
- package/dist/entity-table/editors/text-format-editor.d.ts +32 -0
- package/dist/entity-table/editors/text-format-editor.d.ts.map +1 -0
- package/dist/entity-table/editors/uuid-editor.d.ts +13 -0
- package/dist/entity-table/editors/uuid-editor.d.ts.map +1 -1
- package/dist/entity-table/index.d.ts +13 -3
- package/dist/entity-table/index.d.ts.map +1 -1
- package/dist/entity-table/interfaces.d.ts +38 -0
- package/dist/entity-table/interfaces.d.ts.map +1 -1
- package/dist/entity-table/vtable/table-clipboard.d.ts +30 -1
- package/dist/entity-table/vtable/table-clipboard.d.ts.map +1 -1
- package/dist/entity-table/vtable/table-factory.d.ts +23 -3
- package/dist/entity-table/vtable/table-factory.d.ts.map +1 -1
- package/dist/entity-table/vtable/table-keyboard.d.ts +18 -1
- package/dist/entity-table/vtable/table-keyboard.d.ts.map +1 -1
- package/dist/entity-table/vtable/table-operations.d.ts +42 -1
- package/dist/entity-table/vtable/table-operations.d.ts.map +1 -1
- package/dist/entity-table/vtable/table-row-move.d.ts +88 -0
- package/dist/entity-table/vtable/table-row-move.d.ts.map +1 -0
- package/dist/entity-table/vtable/table-theme.d.ts +47 -2
- package/dist/entity-table/vtable/table-theme.d.ts.map +1 -1
- package/dist/entity-table/vtable/table-tooltip.d.ts +7 -1
- package/dist/entity-table/vtable/table-tooltip.d.ts.map +1 -1
- package/dist/entity-table/vtable/vtable-compat.d.ts +33 -5
- package/dist/entity-table/vtable/vtable-compat.d.ts.map +1 -1
- package/dist/entity-value.utils.d.ts +49 -1
- package/dist/entity-value.utils.d.ts.map +1 -1
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4394 -64471
- package/dist/query-builder/models/operator.interface.d.ts +5 -1
- package/dist/query-builder/models/operator.interface.d.ts.map +1 -1
- package/dist/query-builder/models/query-builder-state.d.ts +31 -2
- package/dist/query-builder/models/query-builder-state.d.ts.map +1 -1
- package/dist/query-builder/models/validation.interface.d.ts +1 -1
- package/dist/query-builder/services/operator-registry.service.d.ts +7 -1
- package/dist/query-builder/services/operator-registry.service.d.ts.map +1 -1
- package/dist/query-builder/services/query-builder.service.d.ts +10 -4
- package/dist/query-builder/services/query-builder.service.d.ts.map +1 -1
- package/dist/query-builder/services/query-converter.service.d.ts +5 -1
- package/dist/query-builder/services/query-converter.service.d.ts.map +1 -1
- package/dist/query-builder/services/schema-parser.service.d.ts +5 -1
- package/dist/query-builder/services/schema-parser.service.d.ts.map +1 -1
- package/dist/query-builder/services/validation.service.d.ts +29 -3
- package/dist/query-builder/services/validation.service.d.ts.map +1 -1
- package/dist/query-builder/utils/date.d.ts +12 -0
- package/dist/query-builder/utils/date.d.ts.map +1 -1
- package/dist/query-builder/utils/field-tree.d.ts.map +1 -1
- package/dist/query-builder/utils/id.d.ts +2 -0
- package/dist/query-builder/utils/id.d.ts.map +1 -1
- package/dist/query-builder/utils/metadata-field-extractor.d.ts +13 -2
- package/dist/query-builder/utils/metadata-field-extractor.d.ts.map +1 -1
- package/dist/query-builder/utils/popover-position.d.ts +11 -0
- package/dist/query-builder/utils/popover-position.d.ts.map +1 -1
- package/dist/query-builder/utils/schema-factory.d.ts +1 -1
- package/dist/query-builder/utils/structural-equal.d.ts +4 -0
- package/dist/query-builder/utils/structural-equal.d.ts.map +1 -1
- package/dist/query-builder/utils/value-input.d.ts +20 -0
- package/dist/query-builder/utils/value-input.d.ts.map +1 -1
- package/dist/structural-equal.d.ts +5 -1
- package/dist/structural-equal.d.ts.map +1 -1
- package/package.json +34 -10
- package/src/cn.ts +21 -0
- package/src/entity-capabilities.ts +37 -0
- package/src/entity-detail/detail-tabs.ts +48 -0
- package/src/entity-detail/index.ts +7 -0
- package/src/entity-detail/interfaces.ts +55 -0
- package/src/entity-field.utils.ts +205 -0
- package/src/entity-form/form-data.ts +83 -0
- package/src/entity-form/form-fields.ts +48 -0
- package/src/entity-form/form-validation.ts +34 -0
- package/src/entity-form/index.ts +9 -0
- package/src/entity-form/interfaces.ts +116 -0
- package/src/entity-list/manual-order-list.ts +159 -0
- package/src/entity-list/types.ts +22 -0
- package/src/entity-table/columns/build-editable-columns.ts +184 -0
- package/src/entity-table/columns/column-utils.ts +657 -0
- package/src/entity-table/editors/color-editor.ts +239 -0
- package/src/entity-table/editors/date-editor.ts +169 -0
- package/src/entity-table/editors/enum-editor.ts +167 -0
- package/src/entity-table/editors/global-overlay-editor.ts +277 -0
- package/src/entity-table/editors/icon-list-editor.ts +130 -0
- package/src/entity-table/editors/json-editor.ts +212 -0
- package/src/entity-table/editors/key-value-editor.ts +595 -0
- package/src/entity-table/editors/lucide-svg.ts +78 -0
- package/src/entity-table/editors/multiselect-editor.ts +171 -0
- package/src/entity-table/editors/number-editor.ts +322 -0
- package/src/entity-table/editors/relation-editor.ts +316 -0
- package/src/entity-table/editors/safe-list-editor.ts +17 -0
- package/src/entity-table/editors/tags-editor.ts +239 -0
- package/src/entity-table/editors/text-format-editor.ts +220 -0
- package/src/entity-table/editors/uuid-editor.ts +198 -0
- package/src/entity-table/index.ts +96 -0
- package/src/entity-table/interfaces.ts +100 -0
- package/src/entity-table/vtable/table-clipboard.ts +419 -0
- package/src/entity-table/vtable/table-factory.ts +117 -0
- package/src/entity-table/vtable/table-keyboard.ts +89 -0
- package/src/entity-table/vtable/table-operations.ts +123 -0
- package/src/entity-table/vtable/table-row-move.ts +122 -0
- package/src/entity-table/vtable/table-theme.ts +181 -0
- package/src/entity-table/vtable/table-tooltip.ts +70 -0
- package/src/entity-table/vtable/vtable-compat.ts +84 -0
- package/src/entity-value.utils.ts +456 -0
- package/src/index.ts +58 -0
- package/src/query-builder/models/config.interface.ts +31 -0
- package/src/query-builder/models/index.ts +8 -0
- package/src/query-builder/models/operator.interface.ts +191 -0
- package/src/query-builder/models/query-builder-state.ts +222 -0
- package/src/query-builder/models/validation.interface.ts +184 -0
- package/src/query-builder/services/index.ts +9 -0
- package/src/query-builder/services/operator-registry.service.ts +243 -0
- package/src/query-builder/services/query-builder.service.ts +568 -0
- package/src/query-builder/services/query-converter.service.ts +153 -0
- package/src/query-builder/services/schema-parser.service.ts +312 -0
- package/src/query-builder/services/validation.service.ts +390 -0
- package/src/query-builder/utils/date.ts +35 -0
- package/src/query-builder/utils/field-tree.ts +400 -0
- package/src/query-builder/utils/id.ts +15 -0
- package/src/query-builder/utils/index.ts +12 -0
- package/src/query-builder/utils/metadata-field-extractor.ts +460 -0
- package/src/query-builder/utils/popover-position.ts +63 -0
- package/src/query-builder/utils/schema-factory.ts +75 -0
- package/src/query-builder/utils/structural-equal.ts +5 -0
- package/src/query-builder/utils/value-input.ts +105 -0
- package/src/structural-equal.ts +49 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 将日期格式化为 YYYY-MM-DD 字符串。
|
|
3
|
+
*
|
|
4
|
+
* @param date 要格式化的日期(Date 对象或可解析的日期字符串)
|
|
5
|
+
* @returns YYYY-MM-DD 格式的日期字符串;输入为空值或无法解析时返回空字符串
|
|
6
|
+
*/
|
|
7
|
+
export function formatDateToYmd(date: Date | string | null | undefined): string {
|
|
8
|
+
if (!date) return '';
|
|
9
|
+
const d = new Date(date);
|
|
10
|
+
if (isNaN(d.getTime())) return '';
|
|
11
|
+
const yyyy = d.getFullYear();
|
|
12
|
+
const mm = String(d.getMonth() + 1).padStart(2, '0');
|
|
13
|
+
const dd = String(d.getDate()).padStart(2, '0');
|
|
14
|
+
return `${yyyy}-${mm}-${dd}`;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* 将 YYYY-MM-DD 字符串解析为本地时区的 Date。
|
|
19
|
+
*
|
|
20
|
+
* @param ymd YYYY-MM-DD 格式的日期字符串
|
|
21
|
+
* @returns 本地时区当天 00:00:00 的 Date 对象;输入为空、段数不为 3 或任一段 parseInt 结果为 NaN 时返回 null
|
|
22
|
+
*/
|
|
23
|
+
export function parseYmdToLocalDate(ymd: string | null | undefined): Date | null {
|
|
24
|
+
if (!ymd) return null;
|
|
25
|
+
const parts = ymd.split('-');
|
|
26
|
+
if (parts.length !== 3) return null;
|
|
27
|
+
const year = parseInt(parts[0], 10);
|
|
28
|
+
const month = parseInt(parts[1], 10);
|
|
29
|
+
const day = parseInt(parts[2], 10);
|
|
30
|
+
if (isNaN(year) || isNaN(month) || isNaN(day)) return null;
|
|
31
|
+
const date = new Date();
|
|
32
|
+
date.setFullYear(year, month - 1, day);
|
|
33
|
+
date.setHours(0, 0, 0, 0);
|
|
34
|
+
return date;
|
|
35
|
+
}
|
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview 字段树工具函数
|
|
3
|
+
*
|
|
4
|
+
* 提供 Angular/React/Vue 共享的字段树构建和查找功能。
|
|
5
|
+
* 用于将扁平的 FieldMetadata[] 转换为 CascadeSelect 组件所需的树形结构。
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { FieldMetadata, PropertyType } from '../models/query-builder-state.js';
|
|
9
|
+
|
|
10
|
+
const EXISTS_OPTION_LABEL = '__exists__';
|
|
11
|
+
const NULL_CHECK_OPTION_LABEL = '__null_check__';
|
|
12
|
+
|
|
13
|
+
/** 计算字符串中 '.' 的数量(避免使用 regex.match 开销) */
|
|
14
|
+
function countDots(s: string): number {
|
|
15
|
+
let count = 0;
|
|
16
|
+
for (let i = 0; i < s.length; i++) {
|
|
17
|
+
if (s.charCodeAt(i) === 46 /* '.' */) count++;
|
|
18
|
+
}
|
|
19
|
+
return count;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 字段树节点(用于 CascadeSelect)
|
|
24
|
+
*
|
|
25
|
+
* @description
|
|
26
|
+
* 将扁平的 FieldMetadata[] 转换为树形结构,
|
|
27
|
+
* 以便 CascadeSelect 组件级联展示嵌套字段。
|
|
28
|
+
*/
|
|
29
|
+
export interface FieldTreeNode {
|
|
30
|
+
/** 当前层的字段名 */
|
|
31
|
+
label: string;
|
|
32
|
+
/** 完整路径(作为选中值) */
|
|
33
|
+
value: string;
|
|
34
|
+
/** 显示名称 */
|
|
35
|
+
displayName: string;
|
|
36
|
+
/** 字段类型 */
|
|
37
|
+
type: PropertyType;
|
|
38
|
+
/** 是否是关系字段 */
|
|
39
|
+
isRelation: boolean;
|
|
40
|
+
/** 子节点 */
|
|
41
|
+
children?: FieldTreeNode[];
|
|
42
|
+
/** 字段描述 */
|
|
43
|
+
description?: string;
|
|
44
|
+
/** 是否必填 */
|
|
45
|
+
required?: boolean;
|
|
46
|
+
/** 枚举值 */
|
|
47
|
+
enum?: unknown[];
|
|
48
|
+
/** 关系目标实体的字段列表(用于 EXISTS 子查询) */
|
|
49
|
+
relationFields?: FieldMetadata[];
|
|
50
|
+
/** 是否是存在性检查选项(用于解决重复 key 问题) */
|
|
51
|
+
isExistsOption?: boolean;
|
|
52
|
+
/** 是否是空值检查选项(keyValue 字段的整体空值查询) */
|
|
53
|
+
isNullCheckOption?: boolean;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* 将扁平字段列表转换为树结构
|
|
58
|
+
*
|
|
59
|
+
* @description
|
|
60
|
+
* 将 `orders.items.productName` 这样的扁平路径转换为嵌套树结构:
|
|
61
|
+
* ```
|
|
62
|
+
* orders
|
|
63
|
+
* └── items
|
|
64
|
+
* └── productName
|
|
65
|
+
* ```
|
|
66
|
+
*
|
|
67
|
+
* @param fields 扁平的字段元数据列表
|
|
68
|
+
* @returns 树形结构的字段节点
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* ```ts
|
|
72
|
+
* const fields = [
|
|
73
|
+
* { name: 'name', displayName: '姓名', type: 'string' },
|
|
74
|
+
* { name: 'orders', displayName: '订单', type: 'relation' },
|
|
75
|
+
* { name: 'orders.amount', displayName: '订单.金额', type: 'number' }
|
|
76
|
+
* ];
|
|
77
|
+
* const tree = buildFieldTree(fields);
|
|
78
|
+
* // tree[0] = { label: 'name', displayName: '姓名', ... }
|
|
79
|
+
* // tree[1] = { label: 'orders', displayName: '订单', children: [...] }
|
|
80
|
+
* ```
|
|
81
|
+
*/
|
|
82
|
+
export function buildFieldTree(fields: FieldMetadata[]): FieldTreeNode[] {
|
|
83
|
+
// 去重:相同 name 的字段只保留第一个
|
|
84
|
+
const seen = new Set<string>();
|
|
85
|
+
const uniqueFields = fields.filter(f => {
|
|
86
|
+
if (seen.has(f.name)) return false;
|
|
87
|
+
seen.add(f.name);
|
|
88
|
+
return true;
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const root: FieldTreeNode[] = [];
|
|
92
|
+
const nodeMap = new Map<string, FieldTreeNode>();
|
|
93
|
+
|
|
94
|
+
// 按深度排序,确保父节点先创建(预先计算深度避免重复 regex 匹配)
|
|
95
|
+
const depthCache = new Map<FieldMetadata, number>();
|
|
96
|
+
for (const field of uniqueFields) {
|
|
97
|
+
depthCache.set(field, countDots(field.name));
|
|
98
|
+
}
|
|
99
|
+
const sorted = [...uniqueFields].sort((a, b) => (depthCache.get(a) ?? 0) - (depthCache.get(b) ?? 0));
|
|
100
|
+
|
|
101
|
+
// 预处理:构建路径到显示名的映射(用于虚拟节点)
|
|
102
|
+
const pathToDisplayName = new Map<string, string>();
|
|
103
|
+
for (const field of uniqueFields) {
|
|
104
|
+
// 从 displayName 中提取各级显示名
|
|
105
|
+
// 例如 "订单.订单项.商品名称" → { "orders": "订单", "orders.items": "订单项" }
|
|
106
|
+
if (field.displayName) {
|
|
107
|
+
const nameParts = field.name.split('.');
|
|
108
|
+
const displayParts = field.displayName.split('.');
|
|
109
|
+
let path = '';
|
|
110
|
+
for (let i = 0; i < nameParts.length - 1; i++) {
|
|
111
|
+
path = path ? `${path}.${nameParts[i]}` : nameParts[i];
|
|
112
|
+
if (displayParts[i] && !pathToDisplayName.has(path)) {
|
|
113
|
+
pathToDisplayName.set(path, displayParts[i]);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
for (const field of sorted) {
|
|
120
|
+
const parts = field.name.split('.');
|
|
121
|
+
const key = parts[parts.length - 1];
|
|
122
|
+
const isRelation = field.type === 'relation' || !!field.isRelation;
|
|
123
|
+
|
|
124
|
+
// 从 displayName 中提取最后一部分作为节点显示名
|
|
125
|
+
const displayParts = (field.displayName ?? key).split('.');
|
|
126
|
+
const nodeDisplayName = displayParts[displayParts.length - 1] || key;
|
|
127
|
+
|
|
128
|
+
const node: FieldTreeNode = {
|
|
129
|
+
label: key,
|
|
130
|
+
value: field.name,
|
|
131
|
+
displayName: nodeDisplayName,
|
|
132
|
+
type: field.type,
|
|
133
|
+
isRelation,
|
|
134
|
+
description: field.description,
|
|
135
|
+
required: field.required,
|
|
136
|
+
enum: field.enum,
|
|
137
|
+
// 关系字段复制 relationFields 用于子查询
|
|
138
|
+
...(field.relationFields && { relationFields: field.relationFields })
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
nodeMap.set(field.name, node);
|
|
142
|
+
|
|
143
|
+
if (parts.length === 1) {
|
|
144
|
+
// 顶层字段
|
|
145
|
+
root.push(node);
|
|
146
|
+
} else {
|
|
147
|
+
// 嵌套字段,找父节点
|
|
148
|
+
const parentPath = parts.slice(0, -1).join('.');
|
|
149
|
+
const parentNode = nodeMap.get(parentPath);
|
|
150
|
+
|
|
151
|
+
if (parentNode) {
|
|
152
|
+
parentNode.children = parentNode.children ?? [];
|
|
153
|
+
parentNode.children.push(node);
|
|
154
|
+
} else {
|
|
155
|
+
// 父节点不存在,创建虚拟父节点链
|
|
156
|
+
let currentPath = '';
|
|
157
|
+
let currentParent = root;
|
|
158
|
+
|
|
159
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
160
|
+
const part = parts[i];
|
|
161
|
+
currentPath = currentPath ? `${currentPath}.${part}` : part;
|
|
162
|
+
|
|
163
|
+
let existingNode = nodeMap.get(currentPath);
|
|
164
|
+
if (!existingNode) {
|
|
165
|
+
// 尝试从预处理映射中获取显示名
|
|
166
|
+
const virtualDisplayName = pathToDisplayName.get(currentPath) ?? part;
|
|
167
|
+
existingNode = {
|
|
168
|
+
label: part,
|
|
169
|
+
value: currentPath,
|
|
170
|
+
displayName: virtualDisplayName,
|
|
171
|
+
type: 'relation',
|
|
172
|
+
isRelation: true,
|
|
173
|
+
children: []
|
|
174
|
+
};
|
|
175
|
+
nodeMap.set(currentPath, existingNode);
|
|
176
|
+
currentParent.push(existingNode);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
existingNode.children = existingNode.children ?? [];
|
|
180
|
+
currentParent = existingNode.children;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
currentParent.push(node);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// 后处理:为有子节点的关系字段添加「存在性检查」(EXISTS) 选项,
|
|
189
|
+
// 为有子节点且可空的 keyValue 字段添加「空值检查」选项。
|
|
190
|
+
// 由于 CascadeSelect 中有子节点的项只能展开、不能直接被选中,
|
|
191
|
+
// 这里为每个需要的父节点追加一个特殊子节点,以便用户可以选择
|
|
192
|
+
// 「该关系是否存在」或「该 keyValue 是否为空」。
|
|
193
|
+
const fieldMap = new Map<string, FieldMetadata>();
|
|
194
|
+
for (const f of uniqueFields) fieldMap.set(f.name, f);
|
|
195
|
+
addSpecialOptionsToNodes(root, fieldMap);
|
|
196
|
+
|
|
197
|
+
return root;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* 为有子节点的关系/keyValue 字段添加特殊选项
|
|
202
|
+
*
|
|
203
|
+
* @description
|
|
204
|
+
* CascadeSelect 组件中,有子节点的项无法直接选择(只能展开)。
|
|
205
|
+
* - 关系字段:添加"存在性检查"选项,让用户可以进行 EXISTS 查询
|
|
206
|
+
* - keyValue 字段(可空时):添加"空值检查"选项,让用户可以查询整体是否为空
|
|
207
|
+
*
|
|
208
|
+
* @param nodes 树节点列表
|
|
209
|
+
* @param fields 原始字段元数据列表
|
|
210
|
+
*/
|
|
211
|
+
function addSpecialOptionsToNodes(nodes: FieldTreeNode[], fieldMap: Map<string, FieldMetadata>): void {
|
|
212
|
+
for (const node of nodes) {
|
|
213
|
+
const children = node.children;
|
|
214
|
+
if (children && children.length > 0) {
|
|
215
|
+
// 关系节点:添加"存在性检查"选项
|
|
216
|
+
if (node.isRelation && !children.some(child => child.label === EXISTS_OPTION_LABEL)) {
|
|
217
|
+
children.unshift({
|
|
218
|
+
label: EXISTS_OPTION_LABEL,
|
|
219
|
+
value: node.value,
|
|
220
|
+
displayName: '(存在性检查)',
|
|
221
|
+
type: 'relation',
|
|
222
|
+
isRelation: true,
|
|
223
|
+
isExistsOption: true,
|
|
224
|
+
description: `检查 ${node.displayName} 是否存在`,
|
|
225
|
+
relationFields: fieldMap.get(node.value)?.relationFields
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// keyValue 节点(可空):添加"空值检查"选项
|
|
230
|
+
if (node.type === 'keyValue' && fieldMap.get(node.value)?.nullable) {
|
|
231
|
+
if (!children.some(child => child.label === NULL_CHECK_OPTION_LABEL)) {
|
|
232
|
+
children.unshift({
|
|
233
|
+
label: NULL_CHECK_OPTION_LABEL,
|
|
234
|
+
value: node.value,
|
|
235
|
+
displayName: '(空值检查)',
|
|
236
|
+
type: 'keyValue',
|
|
237
|
+
isRelation: false,
|
|
238
|
+
isNullCheckOption: true,
|
|
239
|
+
description: `检查 ${node.displayName} 是否为空`
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// 递归处理子节点
|
|
245
|
+
addSpecialOptionsToNodes(children, fieldMap);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* 将字段树展平为选项列表
|
|
252
|
+
*
|
|
253
|
+
* @param nodes 字段树节点
|
|
254
|
+
* @param separator 层级分隔符(默认 ' › ')
|
|
255
|
+
* @param prefix 前缀路径(递归用)
|
|
256
|
+
* @returns 展平的选项列表
|
|
257
|
+
*/
|
|
258
|
+
export function flattenFieldTree(
|
|
259
|
+
nodes: FieldTreeNode[],
|
|
260
|
+
separator = ' › ',
|
|
261
|
+
prefix = ''
|
|
262
|
+
): Array<{ value: string; label: string }> {
|
|
263
|
+
const result: Array<{ value: string; label: string }> = [];
|
|
264
|
+
collectFlattened(nodes, separator, prefix, result);
|
|
265
|
+
return result;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function collectFlattened(
|
|
269
|
+
nodes: FieldTreeNode[],
|
|
270
|
+
separator: string,
|
|
271
|
+
prefix: string,
|
|
272
|
+
out: Array<{ value: string; label: string }>
|
|
273
|
+
): void {
|
|
274
|
+
for (const node of nodes) {
|
|
275
|
+
const display = node.displayName || node.label;
|
|
276
|
+
const label = prefix ? `${prefix}${separator}${display}` : display;
|
|
277
|
+
if (node.children?.length) {
|
|
278
|
+
collectFlattened(node.children, separator, label, out);
|
|
279
|
+
} else if (node.value) {
|
|
280
|
+
out.push({ value: node.value, label });
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* 在树中查找节点
|
|
287
|
+
*
|
|
288
|
+
* @param nodes 树节点列表
|
|
289
|
+
* @param value 要查找的值(完整路径)
|
|
290
|
+
* @returns 找到的节点或 undefined
|
|
291
|
+
*
|
|
292
|
+
* @example
|
|
293
|
+
* ```ts
|
|
294
|
+
* const node = findNodeByValue(tree, 'orders.items.productName');
|
|
295
|
+
* console.log(node?.displayName); // '商品名称'
|
|
296
|
+
* ```
|
|
297
|
+
*/
|
|
298
|
+
export function findNodeByValue(nodes: FieldTreeNode[], value: string): FieldTreeNode | undefined {
|
|
299
|
+
for (const node of nodes) {
|
|
300
|
+
if (node.value === value) {
|
|
301
|
+
return node;
|
|
302
|
+
}
|
|
303
|
+
if (node.children) {
|
|
304
|
+
const found = findNodeByValue(node.children, value);
|
|
305
|
+
if (found) return found;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
return undefined;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* 创建字段名到节点的映射
|
|
313
|
+
*
|
|
314
|
+
* @description
|
|
315
|
+
* 递归遍历树结构,创建一个 Map 用于快速查找节点。
|
|
316
|
+
* 对于需要频繁查找的场景,比直接递归更高效。
|
|
317
|
+
*
|
|
318
|
+
* @param tree 字段树
|
|
319
|
+
* @returns 字段名到节点的映射
|
|
320
|
+
*
|
|
321
|
+
* @example
|
|
322
|
+
* ```ts
|
|
323
|
+
* const nodeMap = createFieldNodeMap(tree);
|
|
324
|
+
* const node = nodeMap.get('orders.amount');
|
|
325
|
+
* ```
|
|
326
|
+
*/
|
|
327
|
+
export function createFieldNodeMap(tree: FieldTreeNode[]): Map<string, FieldTreeNode> {
|
|
328
|
+
const map = new Map<string, FieldTreeNode>();
|
|
329
|
+
|
|
330
|
+
const collectNodes = (nodes: FieldTreeNode[]) => {
|
|
331
|
+
for (const node of nodes) {
|
|
332
|
+
map.set(node.value, node);
|
|
333
|
+
if (node.children) {
|
|
334
|
+
collectNodes(node.children);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
collectNodes(tree);
|
|
340
|
+
return map;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* 获取 CascadeSelect 的 optionGroupChildren 配置
|
|
345
|
+
*
|
|
346
|
+
* @description
|
|
347
|
+
* CascadeSelect 需要知道每一层的子节点属性名。
|
|
348
|
+
* 由于我们统一使用 'children',返回重复的 ['children', 'children', ...]
|
|
349
|
+
* 深度根据最大嵌套层级决定。
|
|
350
|
+
*
|
|
351
|
+
* @param maxDepth 最大深度,默认 10
|
|
352
|
+
* @returns children 属性名数组
|
|
353
|
+
*/
|
|
354
|
+
export function getOptionGroupChildren(maxDepth = 10): string[] {
|
|
355
|
+
return Array(maxDepth).fill('children');
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* 获取完整的 displayName 路径
|
|
360
|
+
*
|
|
361
|
+
* @description
|
|
362
|
+
* 给定字段路径(如 `orders.createdAt`),返回完整的显示名路径(如 `订单.创建时间`)。
|
|
363
|
+
* 通过遍历树结构,将每一级的 displayName 拼接起来。
|
|
364
|
+
*
|
|
365
|
+
* @param tree 字段树
|
|
366
|
+
* @param value 字段路径(如 `orders.createdAt`)
|
|
367
|
+
* @returns 显示名路径(如 `订单.创建时间`)
|
|
368
|
+
*
|
|
369
|
+
* @example
|
|
370
|
+
* ```ts
|
|
371
|
+
* const displayPath = getDisplayNamePath(tree, 'orders.createdAt');
|
|
372
|
+
* console.log(displayPath); // '订单.创建时间'
|
|
373
|
+
* ```
|
|
374
|
+
*/
|
|
375
|
+
export function getDisplayNamePath(tree: FieldTreeNode[], value: string): string {
|
|
376
|
+
const parts = value.split('.');
|
|
377
|
+
const displayParts: string[] = [];
|
|
378
|
+
let currentLevel: FieldTreeNode[] = tree;
|
|
379
|
+
let currentPath = '';
|
|
380
|
+
|
|
381
|
+
for (let i = 0; i < parts.length; i++) {
|
|
382
|
+
currentPath = currentPath ? `${currentPath}.${parts[i]}` : parts[i];
|
|
383
|
+
// 在当前层级使用单次线性查找(通常子节点数量较少)
|
|
384
|
+
let found: FieldTreeNode | undefined;
|
|
385
|
+
for (const n of currentLevel) {
|
|
386
|
+
if (n.value === currentPath) {
|
|
387
|
+
found = n;
|
|
388
|
+
break;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
if (found) {
|
|
392
|
+
displayParts.push(found.displayName || found.label);
|
|
393
|
+
currentLevel = found.children ?? [];
|
|
394
|
+
} else {
|
|
395
|
+
displayParts.push(parts[i]);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
return displayParts.join('.');
|
|
400
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Utils 导出
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export * from './date.js';
|
|
6
|
+
export * from './field-tree.js';
|
|
7
|
+
export * from './id.js';
|
|
8
|
+
export * from './metadata-field-extractor.js';
|
|
9
|
+
export * from './popover-position.js';
|
|
10
|
+
export * from './schema-factory.js';
|
|
11
|
+
export * from './structural-equal.js';
|
|
12
|
+
export * from './value-input.js';
|