@ibiz-template/model-helper 0.7.41-alpha.105 → 0.7.41-alpha.106

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.
@@ -0,0 +1,197 @@
1
+ /* eslint-disable no-case-declarations */
2
+ import { IDETree, IDETreeNodeRS } from '@ibiz/model-core';
3
+
4
+ /**
5
+ * 合并树部件
6
+ *
7
+ * @author tony001
8
+ * @date 2024-09-26 17:09:25
9
+ * @export
10
+ * @param {IDETree} dst
11
+ * @param {IDETree} source
12
+ */
13
+ export function mergeTreeView(dst: IDETree, source: IDETree): void {
14
+ if (!dst || !source) return;
15
+ // 合并树节点
16
+ if (source.detreeNodes && source.detreeNodes.length > 0) {
17
+ if (!dst.detreeNodes) {
18
+ dst.detreeNodes = [];
19
+ }
20
+ source.detreeNodes.forEach(sourceNode => {
21
+ const item = dst.detreeNodes!.find(dstNode => {
22
+ return dstNode.id === sourceNode.id;
23
+ });
24
+ if (!item) {
25
+ dst.detreeNodes?.push(sourceNode);
26
+ }
27
+ });
28
+ }
29
+ // 合并树节点关系
30
+ if (source.detreeNodeRSs && source.detreeNodeRSs.length > 0) {
31
+ if (!dst.detreeNodeRSs) {
32
+ dst.detreeNodeRSs = [];
33
+ }
34
+ source.detreeNodeRSs.forEach(sourceNodeRs => {
35
+ // 若配置了用户标记(dynamic_overlay:before|after|replace|delete|start|end:noderesid),则需根据用户标记进行合并,否则原始数据没有则直接附加末尾
36
+ if (
37
+ (sourceNodeRs as IModel).userTag &&
38
+ (sourceNodeRs as IModel).userTag.startsWith('dynamic_overlay') &&
39
+ (sourceNodeRs as IModel).userTag.split(':').length === 3
40
+ ) {
41
+ mergeSubAppTreeNodeResToDst(dst, sourceNodeRs);
42
+ } else {
43
+ const itemIndex = dst.detreeNodeRSs!.findIndex(dstNodeRs => {
44
+ return (
45
+ dstNodeRs.parentDETreeNodeId === sourceNodeRs.parentDETreeNodeId &&
46
+ dstNodeRs.childDETreeNodeId === sourceNodeRs.childDETreeNodeId
47
+ );
48
+ });
49
+ // 若存在end标记,则需要添加到第一个end标记节点关系前面,保证end标记节点关系始终添加到最后
50
+ const endIndex = dst.detreeNodeRSs!.findIndex(dstNodeRs => {
51
+ if (
52
+ dstNodeRs.parentDETreeNodeId === sourceNodeRs.parentDETreeNodeId
53
+ ) {
54
+ const tags = (sourceNodeRs as IModel).userTag?.split(':');
55
+ if (!tags || tags.length !== 3 || tags[1] !== 'end') return false;
56
+ return true;
57
+ }
58
+ return false;
59
+ });
60
+ if (itemIndex === -1) {
61
+ if (endIndex === -1) {
62
+ dst.detreeNodeRSs?.push(sourceNodeRs);
63
+ } else {
64
+ dst.detreeNodeRSs!.splice(endIndex, 0, sourceNodeRs);
65
+ }
66
+ }
67
+ }
68
+ });
69
+ }
70
+ }
71
+
72
+ /**
73
+ * 合并指定子应用树节点关系到主应用树指定位置
74
+ * @param dst 原始树
75
+ * @param sourceNode 子应用树节点关系
76
+ */
77
+ function mergeSubAppTreeNodeResToDst(
78
+ dst: IDETree,
79
+ sourceNodeRs: IDETreeNodeRS,
80
+ ): void {
81
+ // dynamic_overlay:before|after|replace|delete|start|end:nodeid 定义附加位置
82
+ const [dynamicOverlay, targetPosition, targetTag] = (
83
+ sourceNodeRs as IModel
84
+ ).userTag.split(':');
85
+ if (!dynamicOverlay || !targetPosition || !targetTag) return;
86
+ switch (targetPosition) {
87
+ case 'before':
88
+ // 在目标节点之前,dynamic_overlay:before:childnodeid,这儿最后一节拼接的是子节点标识
89
+ const beforeIndex = dst.detreeNodeRSs!.findIndex(dstNode => {
90
+ return (
91
+ dstNode.childDETreeNodeId === targetTag &&
92
+ dstNode.parentDETreeNodeId === sourceNodeRs.parentDETreeNodeId
93
+ );
94
+ });
95
+ if (beforeIndex !== -1) {
96
+ dst.detreeNodeRSs!.splice(beforeIndex, 0, sourceNodeRs);
97
+ }
98
+ break;
99
+ case 'after':
100
+ // 在目标节点之后,格式如:dynamic_overlay:after:childnodeid,最后一节拼接的是子节点标识
101
+ const afterIndex = dst.detreeNodeRSs!.findIndex(dstNode => {
102
+ return (
103
+ dstNode.childDETreeNodeId === targetTag &&
104
+ dstNode.parentDETreeNodeId === sourceNodeRs.parentDETreeNodeId
105
+ );
106
+ });
107
+ if (afterIndex !== -1) {
108
+ dst.detreeNodeRSs!.splice(afterIndex + 1, 0, sourceNodeRs);
109
+ }
110
+ break;
111
+ case 'replace':
112
+ // 替换目标节点,格式如:dynamic_overlay:replace:childnodeid,最后一节拼接的是子节点标识
113
+ const replaceIndex = dst.detreeNodeRSs!.findIndex(dstNode => {
114
+ return (
115
+ dstNode.childDETreeNodeId === targetTag &&
116
+ dstNode.parentDETreeNodeId === sourceNodeRs.parentDETreeNodeId
117
+ );
118
+ });
119
+ if (replaceIndex !== -1) {
120
+ dst.detreeNodeRSs!.splice(replaceIndex, 1, sourceNodeRs);
121
+ }
122
+ break;
123
+ case 'delete':
124
+ // 删除目标节点,格式如:dynamic_overlay:delete:childnodeid,最后一节拼接的是子节点标识
125
+ const deleteIndex = dst.detreeNodeRSs!.findIndex(dstNode => {
126
+ return (
127
+ dstNode.childDETreeNodeId === targetTag &&
128
+ dstNode.parentDETreeNodeId === sourceNodeRs.parentDETreeNodeId
129
+ );
130
+ });
131
+ if (deleteIndex !== -1) {
132
+ dst.detreeNodeRSs!.splice(deleteIndex, 1);
133
+ }
134
+ break;
135
+ case 'start':
136
+ // 在目标节点内部开始,格式如:dynamic_overlay:start:随机字符,最后一节拼接的是随机字符,读的是当前节点关系的父节点标识
137
+ const startIndex = dst.detreeNodeRSs!.findIndex(dstNode => {
138
+ return dstNode.parentDETreeNodeId === sourceNodeRs.parentDETreeNodeId;
139
+ });
140
+ if (startIndex !== -1) {
141
+ dst.detreeNodeRSs!.splice(startIndex, 0, sourceNodeRs);
142
+ }
143
+ break;
144
+ case 'end':
145
+ // 在目标节点内部结束,格式如:dynamic_overlay:end:随机字符,最后一节拼接的是随机字符,读的是当前节点关系的父节点标识
146
+ const endIndex = getLastIndex(dst.detreeNodeRSs!, dstNode => {
147
+ return dstNode.parentDETreeNodeId === sourceNodeRs.parentDETreeNodeId;
148
+ });
149
+ if (endIndex !== -1) {
150
+ dst.detreeNodeRSs!.splice(endIndex + 1, 0, sourceNodeRs);
151
+ }
152
+ break;
153
+ default:
154
+ // 未识别位置,若源树不存在该关系,则直接附加到最后
155
+ const defaultIndex = dst.detreeNodeRSs!.findIndex(dstNodeRs => {
156
+ return (
157
+ dstNodeRs.parentDETreeNodeId === sourceNodeRs.parentDETreeNodeId &&
158
+ dstNodeRs.childDETreeNodeId === sourceNodeRs.childDETreeNodeId
159
+ );
160
+ });
161
+ // 若存在end标记,则需要添加到第一个end标记节点关系前面,保证end标记节点关系始终添加到最后
162
+ const defaultEndIndex = dst.detreeNodeRSs!.findIndex(dstNodeRs => {
163
+ if (dstNodeRs.parentDETreeNodeId === sourceNodeRs.parentDETreeNodeId) {
164
+ const tags = (sourceNodeRs as IModel).userTag?.split(':');
165
+ if (!tags || tags.length !== 3 || tags[1] !== 'end') return false;
166
+ return true;
167
+ }
168
+ return false;
169
+ });
170
+ if (defaultIndex === -1) {
171
+ if (defaultEndIndex === -1) {
172
+ dst.detreeNodeRSs?.push(sourceNodeRs);
173
+ } else {
174
+ dst.detreeNodeRSs!.splice(defaultEndIndex, 0, sourceNodeRs);
175
+ }
176
+ }
177
+ break;
178
+ }
179
+ }
180
+
181
+ /**
182
+ * 获取指定数组中满足条件的最后一个元素
183
+ * @param arr 指定数组
184
+ * @param predicate 过滤条件
185
+ * @returns 找到则返回指定元素下标,反之返回-1
186
+ */
187
+ function getLastIndex(
188
+ arr: IModel[],
189
+ predicate: (item: IModel) => boolean,
190
+ ): number {
191
+ for (let i = arr.length - 1; i >= 0; i--) {
192
+ if (predicate(arr[i])) {
193
+ return i;
194
+ }
195
+ }
196
+ return -1;
197
+ }
@@ -0,0 +1,30 @@
1
+ import pluralize from 'pluralize';
2
+
3
+ // 补充特殊转换规则
4
+ pluralize.addPluralRule(/(matr|vert|ind)ix|ex$/, '$1ices');
5
+
6
+ /**
7
+ * 英文转复数写法
8
+ *
9
+ * @author chitanda
10
+ * @date 2022-08-25 18:08:41
11
+ * @export
12
+ * @param {string} key
13
+ * @return {*} {string}
14
+ */
15
+ export function plural(key: string): string {
16
+ return pluralize(key);
17
+ }
18
+
19
+ /**
20
+ * 英文转复数写法并转全小写
21
+ *
22
+ * @author chitanda
23
+ * @date 2022-08-25 18:08:23
24
+ * @export
25
+ * @param {string} key
26
+ * @return {*} {string}
27
+ */
28
+ export function pluralLower(key: string): string {
29
+ return plural(key).toLowerCase();
30
+ }
@@ -0,0 +1,361 @@
1
+ import { IAppDERS, IApplication } from '@ibiz/model-core';
2
+ import { plural } from '../plural/plural';
3
+ import { ModelUtil } from '../../model-util';
4
+
5
+ /**
6
+ * 获取服务拼接递归对象
7
+ */
8
+ export type ServicePathDeep = [IAppDERS, ServicePathDeep[]];
9
+
10
+ /**
11
+ * 服务路径项
12
+ */
13
+ export type ServicePathItem = {
14
+ /**
15
+ * 实体代码名称(标准)
16
+ *
17
+ * @author chitanda
18
+ * @date 2022-08-25 18:08:35
19
+ * @type {string}
20
+ */
21
+ codeName: string;
22
+ /**
23
+ * 实体代码名称(小写)
24
+ *
25
+ * @author chitanda
26
+ * @date 2022-08-25 18:08:54
27
+ * @type {string}
28
+ */
29
+ lower: string;
30
+ /**
31
+ * 实体代码名称复数(小写)
32
+ *
33
+ * @author chitanda
34
+ * @date 2022-08-25 18:08:13
35
+ * @type {string}
36
+ */
37
+ plural: string;
38
+ };
39
+
40
+ /**
41
+ * 服务接口项
42
+ */
43
+ export type ServiceApiItem = {
44
+ /**
45
+ * 实体代码名称(标准)
46
+ *
47
+ * @author tony001
48
+ * @date 2024-05-11 15:05:54
49
+ * @type {string}
50
+ */
51
+ codeName: string;
52
+
53
+ /**
54
+ * 实体服务接口代码标识
55
+ *
56
+ * @author tony001
57
+ * @date 2024-05-11 15:05:43
58
+ * @type {string}
59
+ */
60
+ deApiCodeName: string;
61
+
62
+ /**
63
+ * 实体服务接口代码标识2(复数)
64
+ *
65
+ * @author tony001
66
+ * @date 2024-05-11 15:05:43
67
+ * @type {string}
68
+ */
69
+ deApiCodeName2: string;
70
+ };
71
+
72
+ /**
73
+ * 服务路径拼接工具
74
+ *
75
+ * @author chitanda
76
+ * @date 2022-08-22 21:08:52
77
+ * @export
78
+ * @class ServicePathUtil
79
+ */
80
+ export class ServicePathUtil {
81
+ /**
82
+ * 应用实体关系
83
+ *
84
+ * @author chitanda
85
+ * @date 2022-08-22 22:08:18
86
+ * @protected
87
+ * @type {Map<string, IAppDERS[]>} <应用实体 id, 应用实体父关系>
88
+ */
89
+ protected entityRsMap: Map<string, IAppDERS[]> = new Map();
90
+
91
+ /**
92
+ * 实体资源路径
93
+ *
94
+ * @author chitanda
95
+ * @date 2022-08-22 22:08:58
96
+ * @protected
97
+ * @type {Map<string, ServicePathItem[][]>}
98
+ */
99
+ protected entityRsPathMap: Map<string, ServicePathItem[][]> = new Map();
100
+
101
+ /**
102
+ * 实体资源加载状态关系
103
+ *
104
+ * @author tony001
105
+ * @date 2024-05-20 16:05:41
106
+ * @protected
107
+ * @type {Map<string, boolean>}
108
+ */
109
+ protected entityRsLoadMap: Map<string, boolean> = new Map();
110
+
111
+ constructor(
112
+ protected appDataEntities: IModel[],
113
+ protected allDERss: IAppDERS[],
114
+ protected modelUtil: ModelUtil,
115
+ ) {}
116
+
117
+ /**
118
+ * 根据应用主实体过滤从关系集合
119
+ *
120
+ * @author chitanda
121
+ * @date 2023-04-20 17:04:34
122
+ * @protected
123
+ * @param {string} id
124
+ * @return {*} {IAppDERS[]}
125
+ */
126
+ protected filterDERSs(id: string): IAppDERS[] {
127
+ if (this.entityRsMap.has(id)) {
128
+ return this.entityRsMap.get(id)!;
129
+ }
130
+ const items = this.allDERss.filter(item => {
131
+ if ((item as IModel).rSMode === 2 && item.minorAppDataEntityId === id) {
132
+ return item;
133
+ }
134
+ return null;
135
+ });
136
+ if (items.length > 0) {
137
+ this.entityRsMap.set(id, items);
138
+ }
139
+ return items;
140
+ }
141
+
142
+ /**
143
+ * 计算指定应用实体所有资源路径
144
+ *
145
+ * @author chitanda
146
+ * @date 2023-04-22 13:04:27
147
+ * @param {string} id
148
+ * @return {*} {string[]}
149
+ */
150
+ async calcRequestPaths(id: string): Promise<string[]> {
151
+ const paths = await this.calcPaths(id);
152
+ return paths.map(path => {
153
+ return path.map(item => `${item.plural}/\${${item.lower}}`).join('/');
154
+ });
155
+ }
156
+
157
+ /**
158
+ * 计算指定实体所有资源路径
159
+ *
160
+ * @author chitanda
161
+ * @date 2023-04-22 13:04:36
162
+ * @protected
163
+ * @param {string} id
164
+ * @return {*} {ServicePathItem[][]} 返回顺序为 [祖父实体,爷爷实体,父实体,当前实体]
165
+ */
166
+ protected async calcPaths(id: string): Promise<ServicePathItem[][]> {
167
+ const entityRef = this.appDataEntities.find(item => item.id === id);
168
+ if (!entityRef) {
169
+ throw new Error(ibiz.i18n.t('modelHelper.utils.noFoundEntity', { id }));
170
+ }
171
+ const { codeName } = entityRef;
172
+ if (!this.entityRsLoadMap.has(codeName)) {
173
+ this.entityRsLoadMap.set(codeName, false);
174
+ }
175
+ // 需要处理完成才能获取
176
+ if (
177
+ this.entityRsPathMap.has(codeName) &&
178
+ this.entityRsLoadMap.get(codeName)
179
+ ) {
180
+ return this.entityRsPathMap.get(codeName)!;
181
+ }
182
+ const deRss = this.filterDERSs(id);
183
+ if (deRss) {
184
+ // 已经计算过的应用实体标识
185
+ const ids: string[] = [id];
186
+ const arr = this.calcDeepPath(ids, deRss);
187
+ await this.deepFillPath(codeName, [codeName], arr);
188
+ let paths = this.entityRsPathMap.get(codeName);
189
+ if (paths) {
190
+ paths = this.sortPath(paths);
191
+ this.entityRsPathMap.set(codeName, paths);
192
+ this.entityRsLoadMap.set(codeName, true);
193
+ return paths;
194
+ }
195
+ }
196
+ this.entityRsLoadMap.set(codeName, true);
197
+ return [];
198
+ }
199
+
200
+ /**
201
+ * 计算递归资源路径
202
+ *
203
+ * @author chitanda
204
+ * @date 2023-08-23 14:08:39
205
+ * @protected
206
+ * @param {string[]} ids
207
+ * @param {IAppDERS[]} deRss
208
+ * @param {number} [num=0]
209
+ * @return {*} {ServicePathDeep[]}
210
+ */
211
+ protected calcDeepPath(
212
+ ids: string[],
213
+ deRss: IAppDERS[],
214
+ num: number = 0,
215
+ ): ServicePathDeep[] {
216
+ if (num > 10) {
217
+ throw new Error(ibiz.i18n.t('modelHelper.utils.maximumTier'));
218
+ }
219
+ num += 1;
220
+ const arr: ServicePathDeep[] = [];
221
+ deRss.forEach(rs => {
222
+ if (ids.includes(rs.majorAppDataEntityId!)) {
223
+ ibiz.log.warn(
224
+ ibiz.i18n.t('modelHelper.utils.circularRecursive'),
225
+ rs.majorAppDataEntityId,
226
+ ibiz.i18n.t('modelHelper.utils.calculatedEntities'),
227
+ ids,
228
+ );
229
+ return;
230
+ }
231
+ const items = this.filterDERSs(rs.majorAppDataEntityId!);
232
+ arr.push([
233
+ rs,
234
+ this.calcDeepPath([...ids, rs.majorAppDataEntityId!], items, num),
235
+ ]);
236
+ });
237
+ return arr;
238
+ }
239
+
240
+ /**
241
+ * 递归填充计算所有资源路径
242
+ *
243
+ * @author chitanda
244
+ * @date 2022-08-22 22:08:04
245
+ * @protected
246
+ * @param {string} deCodeName
247
+ * @param {string[]} pathNames
248
+ * @param {ServicePathDeep[]} items
249
+ */
250
+ protected async deepFillPath(
251
+ deCodeName: string,
252
+ pathNames: string[],
253
+ items: ServicePathDeep[],
254
+ ): Promise<void> {
255
+ for (let i = 0; i < items.length; i++) {
256
+ const item = items[i];
257
+ const [rs, children] = item;
258
+ if (children.length > 0) {
259
+ await this.deepFillPath(
260
+ deCodeName,
261
+ [...pathNames, rs.majorDECodeName!],
262
+ children,
263
+ );
264
+ }
265
+ if (!this.entityRsPathMap.has(deCodeName)) {
266
+ this.entityRsPathMap.set(deCodeName, []);
267
+ }
268
+ const arr = this.entityRsPathMap.get(deCodeName)!;
269
+
270
+ const serviceApiItems = await this.getServiceApiItems(pathNames);
271
+ const rsServiceApiItems = await this.getServiceApiItems([
272
+ rs.majorDECodeName!,
273
+ ]);
274
+ const tempArr = [
275
+ ...pathNames.map((pathName, index) => {
276
+ return {
277
+ codeName: pathName,
278
+ lower: pathName.toLowerCase(),
279
+ plural: serviceApiItems[index].deApiCodeName2,
280
+ };
281
+ }),
282
+ {
283
+ codeName: rs.majorDECodeName!,
284
+ lower: rs.majorDECodeName!.toLowerCase(),
285
+ plural: rsServiceApiItems[0].deApiCodeName2,
286
+ },
287
+ ].reverse();
288
+ const targetIndex = arr.findIndex(ele => {
289
+ return (
290
+ ele
291
+ .map(val => {
292
+ return val.codeName;
293
+ })
294
+ .join('/') ===
295
+ tempArr
296
+ .map(temp => {
297
+ return temp.codeName;
298
+ })
299
+ .join('/')
300
+ );
301
+ });
302
+ if (targetIndex === -1) {
303
+ arr.push(tempArr);
304
+ }
305
+ }
306
+ }
307
+
308
+ /**
309
+ * 排序资源路径顺序
310
+ *
311
+ * @author chitanda
312
+ * @date 2022-08-22 22:08:44
313
+ * @protected
314
+ * @param {ServicePathItem[][]} paths
315
+ * @return {*} {ServicePathItem[][]}
316
+ */
317
+ protected sortPath(paths: ServicePathItem[][]): ServicePathItem[][] {
318
+ return paths.sort((a, b) => {
319
+ return b.length - a.length;
320
+ });
321
+ }
322
+
323
+ /**
324
+ * 通过codeName数据获取相关接口标识数据
325
+ *
326
+ * @author tony001
327
+ * @date 2024-05-11 17:05:51
328
+ * @protected
329
+ * @param {string[]} codeNames
330
+ * @return {*} {Promise<ServiceApiItem[]>}
331
+ */
332
+ protected async getServiceApiItems(
333
+ codeNames: string[],
334
+ ): Promise<ServiceApiItem[]> {
335
+ const serviceApiItems: ServiceApiItem[] = [];
336
+ for (let i = 0; i < codeNames.length; i++) {
337
+ const appEntity = await this.modelUtil.getAppDataEntityModel(
338
+ codeNames[i],
339
+ false,
340
+ );
341
+ const serviceApiItem: ServiceApiItem = {
342
+ codeName: codeNames[i],
343
+ deApiCodeName: appEntity.dEAPICodeName,
344
+ deApiCodeName2: '',
345
+ };
346
+ if (appEntity.dEAPICodeName) {
347
+ if (!appEntity.deapicodeName2) {
348
+ serviceApiItem.deApiCodeName2 = plural(appEntity.dEAPICodeName);
349
+ }
350
+ const { engineVer } =
351
+ (await this.modelUtil.getAppModel()) as IApplication;
352
+ if (!engineVer || engineVer < 240) {
353
+ serviceApiItem.deApiCodeName2 =
354
+ appEntity.deapicodeName2!.toLowerCase();
355
+ }
356
+ }
357
+ serviceApiItems.push(serviceApiItem);
358
+ }
359
+ return serviceApiItems;
360
+ }
361
+ }