@aiao/rxdb-model 0.0.19 → 0.0.26

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.
Files changed (181) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +43 -6
  3. package/dist/cn.d.ts +19 -0
  4. package/dist/cn.d.ts.map +1 -0
  5. package/dist/entity-capabilities.d.ts +31 -0
  6. package/dist/entity-capabilities.d.ts.map +1 -0
  7. package/dist/entity-detail/detail-tabs.d.ts +13 -2
  8. package/dist/entity-detail/detail-tabs.d.ts.map +1 -1
  9. package/dist/entity-detail/index.d.ts +5 -0
  10. package/dist/entity-detail/index.d.ts.map +1 -1
  11. package/dist/entity-detail/interfaces.d.ts +29 -1
  12. package/dist/entity-detail/interfaces.d.ts.map +1 -1
  13. package/dist/entity-field.utils.d.ts +60 -2
  14. package/dist/entity-field.utils.d.ts.map +1 -1
  15. package/dist/entity-form/form-data.d.ts +22 -1
  16. package/dist/entity-form/form-data.d.ts.map +1 -1
  17. package/dist/entity-form/form-fields.d.ts +25 -2
  18. package/dist/entity-form/form-fields.d.ts.map +1 -1
  19. package/dist/entity-form/form-validation.d.ts +20 -2
  20. package/dist/entity-form/form-validation.d.ts.map +1 -1
  21. package/dist/entity-form/index.d.ts +5 -0
  22. package/dist/entity-form/index.d.ts.map +1 -1
  23. package/dist/entity-form/interfaces.d.ts +65 -1
  24. package/dist/entity-form/interfaces.d.ts.map +1 -1
  25. package/dist/entity-list/manual-order-list.d.ts +99 -0
  26. package/dist/entity-list/manual-order-list.d.ts.map +1 -0
  27. package/dist/entity-table/columns/build-editable-columns.d.ts +26 -4
  28. package/dist/entity-table/columns/build-editable-columns.d.ts.map +1 -1
  29. package/dist/entity-table/columns/column-utils.d.ts +78 -12
  30. package/dist/entity-table/columns/column-utils.d.ts.map +1 -1
  31. package/dist/entity-table/editors/color-editor.d.ts +23 -0
  32. package/dist/entity-table/editors/color-editor.d.ts.map +1 -0
  33. package/dist/entity-table/editors/date-editor.d.ts +6 -1
  34. package/dist/entity-table/editors/date-editor.d.ts.map +1 -1
  35. package/dist/entity-table/editors/enum-editor.d.ts +11 -1
  36. package/dist/entity-table/editors/enum-editor.d.ts.map +1 -1
  37. package/dist/entity-table/editors/global-overlay-editor.d.ts +27 -4
  38. package/dist/entity-table/editors/global-overlay-editor.d.ts.map +1 -1
  39. package/dist/entity-table/editors/icon-list-editor.d.ts +13 -1
  40. package/dist/entity-table/editors/icon-list-editor.d.ts.map +1 -1
  41. package/dist/entity-table/editors/json-editor.d.ts +6 -1
  42. package/dist/entity-table/editors/json-editor.d.ts.map +1 -1
  43. package/dist/entity-table/editors/key-value-editor.d.ts +12 -2
  44. package/dist/entity-table/editors/key-value-editor.d.ts.map +1 -1
  45. package/dist/entity-table/editors/lucide-svg.d.ts +3 -2
  46. package/dist/entity-table/editors/lucide-svg.d.ts.map +1 -1
  47. package/dist/entity-table/editors/multiselect-editor.d.ts +24 -6
  48. package/dist/entity-table/editors/multiselect-editor.d.ts.map +1 -1
  49. package/dist/entity-table/editors/number-editor.d.ts +33 -3
  50. package/dist/entity-table/editors/number-editor.d.ts.map +1 -1
  51. package/dist/entity-table/editors/relation-editor.d.ts +14 -2
  52. package/dist/entity-table/editors/relation-editor.d.ts.map +1 -1
  53. package/dist/entity-table/editors/safe-list-editor.d.ts +1 -0
  54. package/dist/entity-table/editors/safe-list-editor.d.ts.map +1 -1
  55. package/dist/entity-table/editors/tags-editor.d.ts +12 -1
  56. package/dist/entity-table/editors/tags-editor.d.ts.map +1 -1
  57. package/dist/entity-table/editors/text-format-editor.d.ts +32 -0
  58. package/dist/entity-table/editors/text-format-editor.d.ts.map +1 -0
  59. package/dist/entity-table/editors/uuid-editor.d.ts +14 -1
  60. package/dist/entity-table/editors/uuid-editor.d.ts.map +1 -1
  61. package/dist/entity-table/index.d.ts +13 -3
  62. package/dist/entity-table/index.d.ts.map +1 -1
  63. package/dist/entity-table/interfaces.d.ts +13 -0
  64. package/dist/entity-table/interfaces.d.ts.map +1 -1
  65. package/dist/entity-table/vtable/table-clipboard.d.ts +31 -2
  66. package/dist/entity-table/vtable/table-clipboard.d.ts.map +1 -1
  67. package/dist/entity-table/vtable/table-factory.d.ts +25 -5
  68. package/dist/entity-table/vtable/table-factory.d.ts.map +1 -1
  69. package/dist/entity-table/vtable/table-keyboard.d.ts +18 -1
  70. package/dist/entity-table/vtable/table-keyboard.d.ts.map +1 -1
  71. package/dist/entity-table/vtable/table-operations.d.ts +44 -3
  72. package/dist/entity-table/vtable/table-operations.d.ts.map +1 -1
  73. package/dist/entity-table/vtable/table-row-move.d.ts +88 -0
  74. package/dist/entity-table/vtable/table-row-move.d.ts.map +1 -0
  75. package/dist/entity-table/vtable/table-theme.d.ts +47 -2
  76. package/dist/entity-table/vtable/table-theme.d.ts.map +1 -1
  77. package/dist/entity-table/vtable/table-tooltip.d.ts +7 -1
  78. package/dist/entity-table/vtable/table-tooltip.d.ts.map +1 -1
  79. package/dist/entity-table/vtable/vtable-compat.d.ts +33 -5
  80. package/dist/entity-table/vtable/vtable-compat.d.ts.map +1 -1
  81. package/dist/entity-value.utils.d.ts +50 -2
  82. package/dist/entity-value.utils.d.ts.map +1 -1
  83. package/dist/index.d.ts +9 -0
  84. package/dist/index.d.ts.map +1 -1
  85. package/dist/index.js +4392 -64471
  86. package/dist/query-builder/models/config.interface.d.ts +2 -2
  87. package/dist/query-builder/models/operator.interface.d.ts +6 -2
  88. package/dist/query-builder/models/operator.interface.d.ts.map +1 -1
  89. package/dist/query-builder/models/query-builder-state.d.ts +31 -2
  90. package/dist/query-builder/models/query-builder-state.d.ts.map +1 -1
  91. package/dist/query-builder/models/validation.interface.d.ts +2 -2
  92. package/dist/query-builder/services/operator-registry.service.d.ts +13 -3
  93. package/dist/query-builder/services/operator-registry.service.d.ts.map +1 -1
  94. package/dist/query-builder/services/query-builder.service.d.ts +16 -6
  95. package/dist/query-builder/services/query-builder.service.d.ts.map +1 -1
  96. package/dist/query-builder/services/query-converter.service.d.ts +10 -2
  97. package/dist/query-builder/services/query-converter.service.d.ts.map +1 -1
  98. package/dist/query-builder/services/schema-parser.service.d.ts +10 -2
  99. package/dist/query-builder/services/schema-parser.service.d.ts.map +1 -1
  100. package/dist/query-builder/services/validation.service.d.ts +34 -4
  101. package/dist/query-builder/services/validation.service.d.ts.map +1 -1
  102. package/dist/query-builder/utils/date.d.ts +12 -0
  103. package/dist/query-builder/utils/date.d.ts.map +1 -1
  104. package/dist/query-builder/utils/field-tree.d.ts +7 -1
  105. package/dist/query-builder/utils/field-tree.d.ts.map +1 -1
  106. package/dist/query-builder/utils/id.d.ts +2 -0
  107. package/dist/query-builder/utils/id.d.ts.map +1 -1
  108. package/dist/query-builder/utils/metadata-field-extractor.d.ts +15 -3
  109. package/dist/query-builder/utils/metadata-field-extractor.d.ts.map +1 -1
  110. package/dist/query-builder/utils/popover-position.d.ts +11 -0
  111. package/dist/query-builder/utils/popover-position.d.ts.map +1 -1
  112. package/dist/query-builder/utils/schema-factory.d.ts +7 -3
  113. package/dist/query-builder/utils/structural-equal.d.ts +4 -0
  114. package/dist/query-builder/utils/structural-equal.d.ts.map +1 -1
  115. package/dist/query-builder/utils/value-input.d.ts +21 -1
  116. package/dist/query-builder/utils/value-input.d.ts.map +1 -1
  117. package/dist/structural-equal.d.ts +5 -1
  118. package/dist/structural-equal.d.ts.map +1 -1
  119. package/package.json +34 -10
  120. package/src/cn.ts +21 -0
  121. package/src/entity-capabilities.ts +37 -0
  122. package/src/entity-detail/detail-tabs.ts +48 -0
  123. package/src/entity-detail/index.ts +7 -0
  124. package/src/entity-detail/interfaces.ts +55 -0
  125. package/src/entity-field.utils.ts +205 -0
  126. package/src/entity-form/form-data.ts +83 -0
  127. package/src/entity-form/form-fields.ts +48 -0
  128. package/src/entity-form/form-validation.ts +34 -0
  129. package/src/entity-form/index.ts +9 -0
  130. package/src/entity-form/interfaces.ts +116 -0
  131. package/src/entity-list/manual-order-list.ts +159 -0
  132. package/src/entity-table/columns/build-editable-columns.ts +184 -0
  133. package/src/entity-table/columns/column-utils.ts +657 -0
  134. package/src/entity-table/editors/color-editor.ts +239 -0
  135. package/src/entity-table/editors/date-editor.ts +169 -0
  136. package/src/entity-table/editors/enum-editor.ts +167 -0
  137. package/src/entity-table/editors/global-overlay-editor.ts +277 -0
  138. package/src/entity-table/editors/icon-list-editor.ts +130 -0
  139. package/src/entity-table/editors/json-editor.ts +212 -0
  140. package/src/entity-table/editors/key-value-editor.ts +595 -0
  141. package/src/entity-table/editors/lucide-svg.ts +78 -0
  142. package/src/entity-table/editors/multiselect-editor.ts +171 -0
  143. package/src/entity-table/editors/number-editor.ts +322 -0
  144. package/src/entity-table/editors/relation-editor.ts +316 -0
  145. package/src/entity-table/editors/safe-list-editor.ts +17 -0
  146. package/src/entity-table/editors/tags-editor.ts +239 -0
  147. package/src/entity-table/editors/text-format-editor.ts +220 -0
  148. package/src/entity-table/editors/uuid-editor.ts +198 -0
  149. package/src/entity-table/index.ts +96 -0
  150. package/src/entity-table/interfaces.ts +72 -0
  151. package/src/entity-table/vtable/table-clipboard.ts +419 -0
  152. package/src/entity-table/vtable/table-factory.ts +117 -0
  153. package/src/entity-table/vtable/table-keyboard.ts +89 -0
  154. package/src/entity-table/vtable/table-operations.ts +123 -0
  155. package/src/entity-table/vtable/table-row-move.ts +122 -0
  156. package/src/entity-table/vtable/table-theme.ts +181 -0
  157. package/src/entity-table/vtable/table-tooltip.ts +70 -0
  158. package/src/entity-table/vtable/vtable-compat.ts +84 -0
  159. package/src/entity-value.utils.ts +456 -0
  160. package/src/index.ts +57 -0
  161. package/src/query-builder/models/config.interface.ts +31 -0
  162. package/src/query-builder/models/index.ts +8 -0
  163. package/src/query-builder/models/operator.interface.ts +191 -0
  164. package/src/query-builder/models/query-builder-state.ts +222 -0
  165. package/src/query-builder/models/validation.interface.ts +184 -0
  166. package/src/query-builder/services/index.ts +9 -0
  167. package/src/query-builder/services/operator-registry.service.ts +243 -0
  168. package/src/query-builder/services/query-builder.service.ts +568 -0
  169. package/src/query-builder/services/query-converter.service.ts +153 -0
  170. package/src/query-builder/services/schema-parser.service.ts +309 -0
  171. package/src/query-builder/services/validation.service.ts +390 -0
  172. package/src/query-builder/utils/date.ts +35 -0
  173. package/src/query-builder/utils/field-tree.ts +400 -0
  174. package/src/query-builder/utils/id.ts +15 -0
  175. package/src/query-builder/utils/index.ts +12 -0
  176. package/src/query-builder/utils/metadata-field-extractor.ts +460 -0
  177. package/src/query-builder/utils/popover-position.ts +59 -0
  178. package/src/query-builder/utils/schema-factory.ts +75 -0
  179. package/src/query-builder/utils/structural-equal.ts +5 -0
  180. package/src/query-builder/utils/value-input.ts +105 -0
  181. package/src/structural-equal.ts +49 -0
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Entity Form 类型定义
3
+ * @module entity-form/interfaces
4
+ */
5
+ import type { FieldFormat, FieldOptions } from '@aiao/rxdb';
6
+ import type { EntityFieldType, KeyValueSchemaEntry } from '../entity-field.utils.js';
7
+
8
+ /**
9
+ * 表单模式
10
+ */
11
+ export type FormMode = 'view' | 'edit' | 'create';
12
+
13
+ /**
14
+ * 表单字段配置
15
+ */
16
+ export interface FormFieldConfig {
17
+ /** 字段名 */
18
+ field: string;
19
+ /** 显示名称 */
20
+ displayName: string;
21
+ /** 字段类型 */
22
+ type: EntityFieldType;
23
+ /** 是否只读 */
24
+ readonly?: boolean;
25
+ /** 是否可为空 */
26
+ nullable?: boolean;
27
+ /** 是否必填 */
28
+ required?: boolean;
29
+ /** 是否唯一 */
30
+ unique?: boolean;
31
+ /** 枚举类型的可选值 */
32
+ enumValues?: readonly string[];
33
+ /** 键值类型字段的 Schema */
34
+ keyValueSchema?: Record<string, KeyValueSchemaEntry>;
35
+ /** 关系字段关联的实体名 */
36
+ relatedEntityName?: string;
37
+ /** 关系字段关联实体的命名空间 */
38
+ relatedNamespace?: string;
39
+ /** 字段语义标注(只影响展示与控件选择,不改变运行时值类型) */
40
+ format?: FieldFormat;
41
+ /** 枚举/多选值的展示元数据(label / color / disabled) */
42
+ options?: FieldOptions;
43
+ /** 是否为加密列 */
44
+ encrypted?: boolean;
45
+ /** 是否隐藏 */
46
+ hidden?: boolean;
47
+ /** 排序权重 */
48
+ order?: number;
49
+ /** 占位提示 */
50
+ placeholder?: string;
51
+ /** 帮助文本 */
52
+ helpText?: string;
53
+ /** 占据的栅格列数(1 或 2) */
54
+ span?: 1 | 2;
55
+ }
56
+
57
+ /**
58
+ * 表单字段值变更事件
59
+ */
60
+ export interface FormFieldChangeEvent {
61
+ /** 字段名 */
62
+ field: string;
63
+ /** 字段类型 */
64
+ type: EntityFieldType;
65
+ /** 变更后的值 */
66
+ value: unknown;
67
+ /** 变更前的值 */
68
+ previousValue: unknown;
69
+ }
70
+
71
+ /**
72
+ * 表单数据(字段名到字段值的映射)
73
+ */
74
+ export interface EntityFormData {
75
+ /** 字段值(字段名 → 值) */
76
+ [field: string]: unknown;
77
+ }
78
+
79
+ /**
80
+ * 表单校验结果
81
+ */
82
+ export interface FormValidationResult {
83
+ /** 是否通过校验 */
84
+ valid: boolean;
85
+ /** 错误列表 */
86
+ errors: FormFieldValidationError[];
87
+ }
88
+
89
+ /**
90
+ * 表单字段校验错误
91
+ */
92
+ export interface FormFieldValidationError {
93
+ /** 出错字段名 */
94
+ field: string;
95
+ /** 错误提示信息 */
96
+ message: string;
97
+ }
98
+
99
+ /**
100
+ * 关联实体数据提供者
101
+ * @remarks 根据实体名(与可选命名空间)返回可选的关联实体列表
102
+ */
103
+ export interface RelatedEntityProvider {
104
+ /** 按实体名(与可选命名空间)取得可关联实体条目 */
105
+ (entityName: string, namespace?: string): RelatedEntityItem[];
106
+ }
107
+
108
+ /**
109
+ * 关联实体条目
110
+ */
111
+ export interface RelatedEntityItem {
112
+ /** 实体 ID */
113
+ id: string;
114
+ /** 显示名称 */
115
+ displayName: string;
116
+ }
@@ -0,0 +1,159 @@
1
+ /**
2
+ * @fileoverview 实体列表的手动排序:拖拽启用谓词、默认排序与落库协调(US-028 阶段 B)
3
+ *
4
+ * 列表只在「看到的行就是一条完整排序域」时允许拖拽:邻居才是真邻居,
5
+ * `Repository.reorder()` 才能按 `{ prevId, nextId }` 落到用户看到的位置。
6
+ * 三端 `EntityList` 共用这一份判定与协调,框架层只负责收集状态和接线。
7
+ */
8
+ import {
9
+ type EntityMetadata,
10
+ type OrderBy,
11
+ PropertyType,
12
+ isManualOrderEntity,
13
+ manualOrderGroupFields,
14
+ normalizeManualOrderBy
15
+ } from '@aiao/rxdb';
16
+ import type { RowMoveEvent } from '../entity-table/vtable/table-row-move.js';
17
+
18
+ /** 列表排序判定需要的实体元数据 */
19
+ export type ManualOrderListMetadata = Pick<EntityMetadata, 'manualOrder' | 'propertyMap'>;
20
+
21
+ /** 列表的固定查询(详情页关系标签传入),结构同查询构建器的规则组 */
22
+ export interface ListFixedQuery {
23
+ /** 规则组合方式 */
24
+ readonly combinator: 'and' | 'or';
25
+ /** 规则;嵌套规则组视为不能钉住排序域 */
26
+ readonly rules: readonly unknown[];
27
+ }
28
+
29
+ /**
30
+ * 判定列表能否拖拽排序时用到的列表状态
31
+ */
32
+ export interface ListReorderState {
33
+ /** 当前实体元数据;未注册实体为 `undefined` */
34
+ readonly metadata: ManualOrderListMetadata | undefined;
35
+ /** 列头排序状态;只有 `normal`(未按列排序)时显示的才是手动顺序 */
36
+ readonly sortOrder: 'asc' | 'desc' | 'normal';
37
+ /** 用户在筛选框里加了条件 */
38
+ readonly hasUserFilter: boolean;
39
+ /** 列表处于关联选择模式 */
40
+ readonly selectMode: boolean;
41
+ /** 外部传入的固定查询 */
42
+ readonly fixedQuery: ListFixedQuery | undefined;
43
+ /** 排序域已全部加载:没有下一页且不在加载中 */
44
+ readonly fullyLoaded: boolean;
45
+ /** 存在只读行(无编辑权限) */
46
+ readonly hasReadonlyRows: boolean;
47
+ /** 存在未落库的草稿行 */
48
+ readonly hasDrafts: boolean;
49
+ /** 存在未完成的单元格编辑写入 */
50
+ readonly hasPendingEdits: boolean;
51
+ /** 上一次拖拽的重排还没落定 */
52
+ readonly reorderPending: boolean;
53
+ }
54
+
55
+ const STRING_KEY_TYPES: ReadonlySet<string> = new Set<string>([PropertyType.string, PropertyType.uuid]);
56
+
57
+ const isPinRule = (rule: unknown): rule is { field: string; operator: string } => {
58
+ if (typeof rule !== 'object' || rule === null || 'rules' in rule) return false;
59
+ const { field, operator } = rule as { field?: unknown; operator?: unknown };
60
+ return typeof field === 'string' && (operator === '=' || operator === 'null');
61
+ };
62
+
63
+ /**
64
+ * 固定查询是否恰好钉住一条完整的排序域
65
+ *
66
+ * @param groupFields - 实体声明的分组字段
67
+ * @param fixedQuery - 列表的固定查询
68
+ * @returns 整表排序要求没有固定条件;分组排序要求只有对全部分组字段各一条等值(或 `null`)条件
69
+ */
70
+ export function pinsSingleOrderDomain(groupFields: readonly string[], fixedQuery: ListFixedQuery | undefined): boolean {
71
+ const rules = fixedQuery?.rules ?? [];
72
+ if (groupFields.length === 0) return rules.length === 0;
73
+ if (rules.length !== groupFields.length) return false;
74
+ if (rules.length > 1 && fixedQuery?.combinator !== 'and') return false;
75
+ if (!rules.every(isPinRule)) return false;
76
+ const pinned = new Set(rules.map(rule => (rule as { field: string }).field));
77
+ return pinned.size === groupFields.length && groupFields.every(field => pinned.has(field));
78
+ }
79
+
80
+ /**
81
+ * 实体列表当前是否允许拖拽排序
82
+ *
83
+ * @param state - 列表状态
84
+ * @returns 全部条件满足时为 `true`:手动排序实体、字符串主键、未按列排序、无用户筛选、非选择模式、
85
+ * 固定查询钉住一条完整排序域、已全部加载、没有只读行 / 草稿 / 未完成编辑 / 未落定的重排
86
+ *
87
+ * @example
88
+ * ```typescript
89
+ * const enabled = canReorderEntityList({ metadata, sortOrder: 'normal', fixedQuery, ...flags });
90
+ * ```
91
+ */
92
+ export function canReorderEntityList(state: ListReorderState): boolean {
93
+ const { metadata } = state;
94
+ if (!metadata || !isManualOrderEntity(metadata)) return false;
95
+ const keyType = metadata.propertyMap.get('id')?.type;
96
+ if (keyType === undefined || !STRING_KEY_TYPES.has(keyType)) return false;
97
+ if (state.sortOrder !== 'normal' || state.hasUserFilter || state.selectMode) return false;
98
+ if (!pinsSingleOrderDomain(manualOrderGroupFields(metadata), state.fixedQuery)) return false;
99
+ if (!state.fullyLoaded || state.hasReadonlyRows || state.hasDrafts) return false;
100
+ return !state.hasPendingEdits && !state.reorderPending;
101
+ }
102
+
103
+ /**
104
+ * 列表未按列排序时的游标排序
105
+ *
106
+ * @param metadata - 实体元数据;未注册实体为 `undefined`
107
+ * @returns 手动排序实体为 `[分组字段… asc, sortOrder asc, id asc]`,其余为 `[id desc]`
108
+ */
109
+ export function defaultListOrderBy(metadata: Pick<EntityMetadata, 'manualOrder'> | undefined): OrderBy[] {
110
+ if (metadata && isManualOrderEntity(metadata))
111
+ return normalizeManualOrderBy<{ orderBy?: OrderBy[] }>(metadata, {}).orderBy ?? [];
112
+ return [{ field: 'id', sort: 'desc' }];
113
+ }
114
+
115
+ /**
116
+ * 行拖放落库时与界面交互的钩子
117
+ */
118
+ export interface RowMoveCommitHooks {
119
+ /** 放下这一刻列表是否允许拖拽排序({@link canReorderEntityList} 的结论) */
120
+ readonly enabled: boolean;
121
+ /** 落库重排,通常是 `repository.reorder(move.id, { prevId, nextId })` */
122
+ reorder(move: RowMoveEvent): Promise<unknown>;
123
+ /** 把表格行恢复成最近一次已提交的顺序 */
124
+ restore(): void;
125
+ /** 重排进行中状态变化 */
126
+ setPending(pending: boolean): void;
127
+ /** 显示(或用 `null` 清除)落库错误 */
128
+ setError(error: unknown): void;
129
+ }
130
+
131
+ /**
132
+ * 协调一次行拖放的落库
133
+ *
134
+ * @param move - 表格读出的拖放结果
135
+ * @param hooks - 界面钩子
136
+ * @returns 落库成功为 `true`;不允许拖拽或落库失败为 `false`,此时表格已恢复原顺序
137
+ *
138
+ * @remarks
139
+ * 不允许时零写入,直接恢复表格(程序触发的拖放事件同样被拒)。成功后不改表格,
140
+ * 由活查询按数据库重查的结果刷新;失败时显式恢复,下一次拖拽照常可用。
141
+ */
142
+ export async function commitRowMove(move: RowMoveEvent, hooks: RowMoveCommitHooks): Promise<boolean> {
143
+ if (!hooks.enabled) {
144
+ hooks.restore();
145
+ return false;
146
+ }
147
+ hooks.setError(null);
148
+ hooks.setPending(true);
149
+ try {
150
+ await hooks.reorder(move);
151
+ return true;
152
+ } catch (error) {
153
+ hooks.setError(error);
154
+ hooks.restore();
155
+ return false;
156
+ } finally {
157
+ hooks.setPending(false);
158
+ }
159
+ }
@@ -0,0 +1,184 @@
1
+ import type {
2
+ EntityMetadata,
3
+ EntityPropertyMetadata,
4
+ EntityRelationMetadata,
5
+ FieldFormat,
6
+ FieldOptions
7
+ } from '@aiao/rxdb';
8
+ import { PropertyType, RelationKind } from '@aiao/rxdb';
9
+ import type { ColumnDefine } from '@visactor/vtable/es/ts-types/index.js';
10
+ import { deriveEntityCapabilities } from '../../entity-capabilities.js';
11
+ import type { RelatedEntityItem } from '../../entity-form/interfaces.js';
12
+ import { actionsColumn, buildPropertyColumn, formatDateValue, type KVSchemaEntry } from './column-utils.js';
13
+
14
+ /** 仅展示(不可编辑)的系统字段 */
15
+ export const DISPLAY_ONLY_FIELDS = new Set(['createdBy', 'updatedBy']);
16
+
17
+ /** 跳过(不显示为独立列)的系统字段 */
18
+ export const SKIP_FIELDS = new Set(['id', 'createdAt', 'updatedAt']);
19
+
20
+ /** 关联实体候选 provider 工厂:根据外键字段和关联实体名返回 provider 函数 */
21
+ export type RelatedItemsProviderFactory = (
22
+ fkField: string,
23
+ relatedEntityName: string
24
+ ) => (() => RelatedEntityItem[]) | undefined;
25
+
26
+ /** 构建可编辑列的选项 */
27
+ export interface BuildEditableColumnsOptions {
28
+ /** 关联实体候选 provider 工厂(按外键字段与关联实体名返回 provider) */
29
+ relatedItemsProviderFactory?: RelatedItemsProviderFactory;
30
+ /** 操作列标题(默认「操作」) */
31
+ actionsTitle?: string;
32
+ /** 删除按钮文案(默认「删除」) */
33
+ deleteLabel?: string;
34
+ /** 查看按钮文案(默认「查看」) */
35
+ viewLabel?: string;
36
+ /** 创建时间列标题(默认「创建时间」) */
37
+ createdAtTitle?: string;
38
+ /** 更新时间列标题(默认「更新时间」) */
39
+ updatedAtTitle?: string;
40
+ }
41
+
42
+ function buildKeyValueSchema(prop: EntityPropertyMetadata): Record<string, KVSchemaEntry> {
43
+ const schema: Record<string, KVSchemaEntry> = {};
44
+ const properties = (
45
+ prop as EntityPropertyMetadata & {
46
+ properties?: ReadonlyArray<{
47
+ name: string;
48
+ displayName?: string;
49
+ type: EntityPropertyMetadata['type'];
50
+ required?: boolean;
51
+ nullable?: boolean;
52
+ }>;
53
+ }
54
+ ).properties;
55
+ if (!properties) return schema;
56
+ for (const p of properties) {
57
+ schema[p.name] = {
58
+ label: p.displayName,
59
+ type: normalizeKVSchemaEntryType(p.type),
60
+ required: p.required,
61
+ nullable: p.nullable
62
+ };
63
+ }
64
+ return schema;
65
+ }
66
+
67
+ /**
68
+ * 将实体属性类型归一化为 keyValue Schema 值类型
69
+ *
70
+ * 仅 string / enum / uuid / number / integer / boolean / date 可直接映射,
71
+ * 其余类型返回 undefined。
72
+ *
73
+ * @param type 实体属性类型(PropertyType 枚举值)
74
+ * @returns 对应的 KVSchemaEntry 值类型;无法映射时返回 undefined
75
+ */
76
+ export function normalizeKVSchemaEntryType(type: EntityPropertyMetadata['type']): KVSchemaEntry['type'] | undefined {
77
+ switch (typeof type === 'string' ? type : String(type)) {
78
+ case PropertyType.string:
79
+ case PropertyType.enum:
80
+ case PropertyType.uuid:
81
+ return 'string';
82
+ case PropertyType.number:
83
+ return 'number';
84
+ case PropertyType.integer:
85
+ return 'integer';
86
+ case PropertyType.boolean:
87
+ return 'boolean';
88
+ case PropertyType.date:
89
+ return 'date';
90
+ default:
91
+ return undefined;
92
+ }
93
+ }
94
+
95
+ /**
96
+ * 根据 EntityMetadata 构建可编辑的 VTable 列定义
97
+ *
98
+ * 按照以下顺序排列列:
99
+ * 1. ID 列
100
+ * 2. 普通属性列(跳过 SKIP_FIELDS 和 DISPLAY_ONLY_FIELDS)
101
+ * 3. 计算属性列
102
+ * 4. 外键关系列
103
+ * 5. createdAt / updatedAt 系统时间列
104
+ * 6. 操作列(查看 + 删除;删除按实体 `permissions.delete` 派生,见 {@link deriveEntityCapabilities})
105
+ *
106
+ * @param metadata 实体元数据
107
+ * @param options 可选构建选项
108
+ * @returns 可编辑的列定义数组
109
+ */
110
+ export function buildEditableColumns(
111
+ metadata: EntityMetadata,
112
+ options: BuildEditableColumnsOptions = {}
113
+ ): ColumnDefine[] {
114
+ const {
115
+ relatedItemsProviderFactory,
116
+ actionsTitle = '操作',
117
+ deleteLabel = '删除',
118
+ viewLabel = '查看',
119
+ createdAtTitle = '创建时间',
120
+ updatedAtTitle = '更新时间'
121
+ } = options;
122
+
123
+ const { canDelete } = deriveEntityCapabilities(metadata);
124
+ const columns: ColumnDefine[] = [{ field: 'id', title: 'ID', width: 260, sort: true }];
125
+
126
+ for (const [key, prop] of metadata.propertyMap) {
127
+ if (DISPLAY_ONLY_FIELDS.has(key) || SKIP_FIELDS.has(key)) continue;
128
+ const raw = prop as Record<string, unknown>;
129
+ columns.push(
130
+ buildPropertyColumn({
131
+ field: key,
132
+ title: prop.displayName ?? key,
133
+ type: prop.type,
134
+ readonly: raw['readonly'] === true,
135
+ enumValues: raw['enum'] as string[] | undefined,
136
+ keyValueSchema: prop.type === PropertyType.keyValue ? buildKeyValueSchema(prop) : undefined,
137
+ format: raw['format'] as FieldFormat | undefined,
138
+ options: raw['options'] as FieldOptions | undefined
139
+ })
140
+ );
141
+ }
142
+
143
+ for (const [key, prop] of metadata.computedPropertyMap) {
144
+ columns.push(
145
+ buildPropertyColumn({
146
+ field: key,
147
+ title: prop.displayName ?? key,
148
+ type: 'computed',
149
+ readonly: true
150
+ })
151
+ );
152
+ }
153
+
154
+ for (const [fkField, relation] of metadata.foreignKeyRelationMap as Map<string, EntityRelationMetadata>) {
155
+ const type = relation.kind === RelationKind.ONE_TO_ONE ? 'oneToOne' : 'manyToOne';
156
+ columns.push(
157
+ buildPropertyColumn({
158
+ field: fkField,
159
+ title: relation.displayName ?? fkField,
160
+ type,
161
+ nullable: (relation as Record<string, unknown>)['nullable'] === true,
162
+ relatedItemsProvider: relatedItemsProviderFactory?.(fkField, relation.mappedEntity)
163
+ })
164
+ );
165
+ }
166
+
167
+ columns.push(
168
+ {
169
+ field: 'createdAt',
170
+ title: createdAtTitle,
171
+ width: 160,
172
+ fieldFormat: (r: Record<string, unknown>) => formatDateValue(r['createdAt'])
173
+ },
174
+ {
175
+ field: 'updatedAt',
176
+ title: updatedAtTitle,
177
+ width: 160,
178
+ fieldFormat: (r: Record<string, unknown>) => formatDateValue(r['updatedAt'])
179
+ },
180
+ actionsColumn(actionsTitle, deleteLabel, () => canDelete, viewLabel)
181
+ );
182
+
183
+ return columns;
184
+ }