@cicctencent/low-code-simulate-base 0.0.0-stage → 1.1.9-beta.5
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/README.md +1 -3
- package/__tests__/plugin.spec.ts +40 -0
- package/__tests__/utils.transform.test.ts +37 -0
- package/api-extractor.json +36 -0
- package/build/md.js +25 -0
- package/dist/VCComponent.d.ts +5 -0
- package/dist/VCComponent.js +91 -0
- package/dist/domain/BaseProjectDomain.d.ts +349 -0
- package/dist/domain/BaseProjectDomain.js +1305 -0
- package/dist/domain/ComponentManager.d.ts +78 -0
- package/dist/domain/ComponentManager.js +293 -0
- package/dist/domain/DataHistoryDomain.d.ts +53 -0
- package/dist/domain/DataHistoryDomain.js +110 -0
- package/dist/domain/PreviewDomain.d.ts +208 -0
- package/dist/domain/PreviewDomain.js +492 -0
- package/dist/domain/ProjectDomain.d.ts +209 -0
- package/dist/domain/ProjectDomain.js +138 -0
- package/dist/event/generateEventHandler.d.ts +21 -0
- package/dist/event/generateEventHandler.js +218 -0
- package/dist/event/generateEventSchema.d.ts +123 -0
- package/dist/event/generateEventSchema.js +342 -0
- package/dist/event/register.d.ts +56 -0
- package/dist/event/register.js +207 -0
- package/dist/index.d.ts +1330 -0
- package/dist/index.js +28 -0
- package/dist/plugin/onH5PageValidate.d.ts +7 -0
- package/dist/plugin/onH5PageValidate.js +178 -0
- package/dist/plugin/onPlaceholder.d.ts +6 -0
- package/dist/plugin/onPlaceholder.js +68 -0
- package/dist/plugin/subscribeInsertUploadList.d.ts +6 -0
- package/dist/plugin/subscribeInsertUploadList.js +49 -0
- package/dist/plugin/subscribeSetTitle.d.ts +6 -0
- package/dist/plugin/subscribeSetTitle.js +16 -0
- package/dist/plugin/subscribeTransformComponentMeta.d.ts +14 -0
- package/dist/plugin/subscribeTransformComponentMeta.js +31 -0
- package/dist/plugin/subscribeValidateField.d.ts +7 -0
- package/dist/plugin/subscribeValidateField.js +57 -0
- package/dist/plugin/subscribeValidateUpload.d.ts +6 -0
- package/dist/plugin/subscribeValidateUpload.js +92 -0
- package/dist/utils/base64.d.ts +14 -0
- package/dist/utils/base64.js +14 -0
- package/dist/utils/buildInVariable.d.ts +61 -0
- package/dist/utils/buildInVariable.js +216 -0
- package/dist/utils/dom.d.ts +14 -0
- package/dist/utils/dom.js +19 -0
- package/dist/utils/index.d.ts +55 -0
- package/dist/utils/index.js +201 -0
- package/dist/utils/transform.d.ts +39 -0
- package/dist/utils/transform.js +191 -0
- package/dist/utils/validate.d.ts +7 -0
- package/dist/utils/validate.js +18 -0
- package/docs/.api/low-code-simulate-base.api.json +8450 -0
- package/jest.config.ts +20 -0
- package/package.json +44 -4
- package/src/VCComponent.ts +118 -0
- package/src/domain/BaseProjectDomain.ts +1341 -0
- package/src/domain/ComponentManager.ts +241 -0
- package/src/domain/DataHistoryDomain.ts +102 -0
- package/src/domain/PreviewDomain.ts +424 -0
- package/src/domain/ProjectDomain.ts +244 -0
- package/src/event/generateEventHandler.ts +162 -0
- package/src/event/generateEventSchema.ts +371 -0
- package/src/event/register.ts +181 -0
- package/src/index.ts +61 -0
- package/src/plugin/onH5PageValidate.ts +179 -0
- package/src/plugin/onPlaceholder.ts +71 -0
- package/src/plugin/subscribeInsertUploadList.ts +51 -0
- package/src/plugin/subscribeSetTitle.ts +18 -0
- package/src/plugin/subscribeTransformComponentMeta.ts +38 -0
- package/src/plugin/subscribeValidateField.ts +65 -0
- package/src/plugin/subscribeValidateUpload.ts +116 -0
- package/src/utils/base64.ts +17 -0
- package/src/utils/buildInVariable.ts +157 -0
- package/src/utils/dom.ts +30 -0
- package/src/utils/index.ts +194 -0
- package/src/utils/transform.ts +171 -0
- package/src/utils/validate.ts +19 -0
- package/temp/low-code-simulate-base.api.md +597 -0
- package/test/plugin.test.ts +4 -0
- package/tsconfig.build.json +9 -0
- package/tsconfig.json +9 -0
- package/tsconfig.test.json +14 -0
|
@@ -0,0 +1,1341 @@
|
|
|
1
|
+
import { getImageUrls, getSchemaConfigByValue } from '../utils';
|
|
2
|
+
import { getObjectPath } from '../utils/transform'
|
|
3
|
+
import type {
|
|
4
|
+
ImageUrlResponse,
|
|
5
|
+
NavTreeData,
|
|
6
|
+
SchemaData,
|
|
7
|
+
Component,
|
|
8
|
+
ComponentTreeData,
|
|
9
|
+
ComponentItem,
|
|
10
|
+
MenuData,
|
|
11
|
+
Project,
|
|
12
|
+
GroupTree,
|
|
13
|
+
ComponentTree,
|
|
14
|
+
LowCodeKey,
|
|
15
|
+
ProjectData,
|
|
16
|
+
FunctionData,
|
|
17
|
+
FunctionConfig,
|
|
18
|
+
EditorConfig,
|
|
19
|
+
} from '@jt/low-code-core';
|
|
20
|
+
|
|
21
|
+
import { ComponentType } from '@jt/low-code-core';
|
|
22
|
+
import { navTreeKey, componentTreeKey } from '../domain/ProjectDomain';
|
|
23
|
+
import {
|
|
24
|
+
cloneDeep,
|
|
25
|
+
get,
|
|
26
|
+
isEqual,
|
|
27
|
+
set,
|
|
28
|
+
toPlainObject,
|
|
29
|
+
merge,
|
|
30
|
+
isEmpty,
|
|
31
|
+
log,
|
|
32
|
+
} from '@jt/ark-util';
|
|
33
|
+
import { decodeContent } from 'jm-jsruntime';
|
|
34
|
+
|
|
35
|
+
import { getSchemaObjectAttribute } from '../utils';
|
|
36
|
+
import { tipIcon } from '../utils/base64';
|
|
37
|
+
import { transform } from '../utils/transform';
|
|
38
|
+
import ComponentManager from './ComponentManager';
|
|
39
|
+
const logTag = '[low-code-preview BaseProjectDomain]';
|
|
40
|
+
const logInfo = log(logTag);
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @public
|
|
44
|
+
*/
|
|
45
|
+
export interface ProjectDomainState {
|
|
46
|
+
basePath?: string;
|
|
47
|
+
project: Project;
|
|
48
|
+
pageConfig: Record<string, any>; // 页面配置表单schema
|
|
49
|
+
componentConfigs: Component[]; // 组件配置表单schema
|
|
50
|
+
groupConfig: GroupTree[]; // 分组配置
|
|
51
|
+
functionConfig: FunctionConfig; //header配置
|
|
52
|
+
}
|
|
53
|
+
import ProjectDomain from '../domain/ProjectDomain';
|
|
54
|
+
const statusOptions = {
|
|
55
|
+
0: '编辑中',
|
|
56
|
+
1: '已发布',
|
|
57
|
+
2: '已下线',
|
|
58
|
+
3: '审核中',
|
|
59
|
+
4: '发布失败',
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* @public
|
|
63
|
+
*/
|
|
64
|
+
export default class BaseProjectDomain extends ProjectDomain {
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* @public
|
|
68
|
+
* 获取配置
|
|
69
|
+
*/
|
|
70
|
+
get config(): EditorConfig {
|
|
71
|
+
const { navTreeData, componentTreeData, schemaData, functionData } = this;
|
|
72
|
+
return {
|
|
73
|
+
navTreeData: {
|
|
74
|
+
key: navTreeKey,
|
|
75
|
+
value: navTreeData,
|
|
76
|
+
},
|
|
77
|
+
componentTreeData: {
|
|
78
|
+
key: componentTreeKey,
|
|
79
|
+
value: componentTreeData,
|
|
80
|
+
},
|
|
81
|
+
functionData,
|
|
82
|
+
schemaData,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* @public
|
|
87
|
+
*/
|
|
88
|
+
staticPath = 'components.0.list';
|
|
89
|
+
/**
|
|
90
|
+
* @public
|
|
91
|
+
*/
|
|
92
|
+
currentPageIndex = 0; // 当前编辑页面索引
|
|
93
|
+
/**
|
|
94
|
+
* @public
|
|
95
|
+
*/
|
|
96
|
+
currentComponentPath = ''; // 当前编辑组件索引路径
|
|
97
|
+
// 这里的schema需要读取配置文件
|
|
98
|
+
/**
|
|
99
|
+
* @public
|
|
100
|
+
*/
|
|
101
|
+
cManager: ComponentManager;
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* @public
|
|
105
|
+
*/
|
|
106
|
+
get basePath() {
|
|
107
|
+
return `${this.currentPageIndex}.${this.staticPath}`;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* @public
|
|
111
|
+
*/
|
|
112
|
+
constructor(state: ProjectDomainState) {
|
|
113
|
+
super();
|
|
114
|
+
this.state = state;
|
|
115
|
+
// this.basePath = state.basePath || this.basePath;
|
|
116
|
+
this.cManager = ComponentManager.getInstance(state.componentConfigs);
|
|
117
|
+
this.setProject(state.project);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
// generateComponentPath(id: number): string {
|
|
122
|
+
// const index = this.components.findIndex(item=>item.componentId === id);
|
|
123
|
+
// const paths = index !== -1 ? `0.components.0.list.${index}` : ''
|
|
124
|
+
// return paths;
|
|
125
|
+
// }
|
|
126
|
+
/**
|
|
127
|
+
* @public
|
|
128
|
+
*/
|
|
129
|
+
private Id2ComponentPathMap = {};
|
|
130
|
+
/**
|
|
131
|
+
* 根据id在pages中找到组件 生成componentPath
|
|
132
|
+
* @param id - 组件ID
|
|
133
|
+
* @returns 组件路径
|
|
134
|
+
* @example
|
|
135
|
+
* const pages = [{components:[{list:[{componentId:1}]}]}];
|
|
136
|
+
* const componentId = 1;
|
|
137
|
+
* generateComponentPath(componentId)
|
|
138
|
+
* // => 0.components.0.list.0
|
|
139
|
+
*/
|
|
140
|
+
/**
|
|
141
|
+
* @public
|
|
142
|
+
*/
|
|
143
|
+
generateComponentPath(id: number, pageIndex?: number): string {
|
|
144
|
+
let componentPath = this.Id2ComponentPathMap[id];
|
|
145
|
+
if (componentPath) {
|
|
146
|
+
// 由于组件位置可能变化,所以这里要确认一下
|
|
147
|
+
const component = this.pathToData(componentPath);
|
|
148
|
+
if (component && component.componentId === id) {
|
|
149
|
+
return componentPath;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const paths = pageIndex !== undefined ? [pageIndex, this.staticPath] : [this.basePath];
|
|
154
|
+
const findComponent = (list: ComponentItem[]) => {
|
|
155
|
+
for (const i in list) {
|
|
156
|
+
const item = list[i];
|
|
157
|
+
if (item.componentId === id) {
|
|
158
|
+
paths.push(i);
|
|
159
|
+
return true;
|
|
160
|
+
} else if (item.children && item.children.length) {
|
|
161
|
+
paths.push(i, 'children');
|
|
162
|
+
if (findComponent(item.children as ComponentItem[])) {
|
|
163
|
+
return true;
|
|
164
|
+
}
|
|
165
|
+
// 未找到 删除最后两个
|
|
166
|
+
paths.splice(-2, 2);
|
|
167
|
+
}
|
|
168
|
+
// else if (item.slot) {
|
|
169
|
+
// const keys = Object.keys(item.slot);
|
|
170
|
+
// if(keys.length){
|
|
171
|
+
// for(let key of keys){
|
|
172
|
+
// if (item.slot[key]?.componentId === id) {
|
|
173
|
+
// paths.push(i, 'slot', key);
|
|
174
|
+
// return true;
|
|
175
|
+
// }
|
|
176
|
+
// }
|
|
177
|
+
|
|
178
|
+
// }
|
|
179
|
+
// }
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
if (findComponent(this.components)) {
|
|
184
|
+
componentPath = paths.join('.');
|
|
185
|
+
this.Id2ComponentPathMap[id] = componentPath;
|
|
186
|
+
return componentPath;
|
|
187
|
+
}
|
|
188
|
+
return '';
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* @public
|
|
193
|
+
* 获取父组件数据
|
|
194
|
+
* @param childPath - 子组件路径
|
|
195
|
+
* @returns 父组件数据
|
|
196
|
+
*/
|
|
197
|
+
getParent(childPath?: string): number | null {
|
|
198
|
+
childPath = childPath || this.currentComponentPath;
|
|
199
|
+
if (childPath) {
|
|
200
|
+
const { parentComponentPath } = this.getIndexByKey(childPath);
|
|
201
|
+
if (parentComponentPath) {
|
|
202
|
+
return this.pathToData(parentComponentPath);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* @public
|
|
210
|
+
* 更新数据
|
|
211
|
+
* @param oldProject - 旧Project
|
|
212
|
+
* @returns true 新增记录 false 更新记录
|
|
213
|
+
*/
|
|
214
|
+
needUpdateHistory(oldProject): boolean {
|
|
215
|
+
const { projectData } = oldProject;
|
|
216
|
+
let currentComponentPath = oldProject.currentComponentPath;
|
|
217
|
+
if (!projectData || !currentComponentPath) {
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (!currentComponentPath.includes('.')) {
|
|
222
|
+
// 如果,当前导航在页面上
|
|
223
|
+
// 新增页面兼容处理,页面配置formData初始为空,新增页面会设置默认值
|
|
224
|
+
const pageMeta = projectData?.pages[0]?.pageMeta;
|
|
225
|
+
if (isEmpty(pageMeta)) {
|
|
226
|
+
Object.assign(pageMeta, this.pageMeta);
|
|
227
|
+
return true;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
if (this?.currentComponent?.immobile) {
|
|
231
|
+
const { parentComponentPath } = this.getIndexByKey(this.currentComponentPath);
|
|
232
|
+
currentComponentPath = parentComponentPath;
|
|
233
|
+
}
|
|
234
|
+
const oldComponentData = this.pathToData(
|
|
235
|
+
currentComponentPath,
|
|
236
|
+
projectData.pages
|
|
237
|
+
);
|
|
238
|
+
if (!(oldComponentData)) {
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const newComponentData = this.pathToData(currentComponentPath);
|
|
243
|
+
// 点击新增组件先click新增历史记录,后form表单更新set新增历史记录
|
|
244
|
+
// 如果新组件的formData路径比旧的多于1个(1个可能是修改了值)则表示点击新增组件还没有到表单更新默认值
|
|
245
|
+
// 同时currentComponent有值,表示是表单更新设置值
|
|
246
|
+
if (isEqual(oldComponentData, newComponentData) || getObjectPath(oldComponentData.formData).length < getObjectPath(newComponentData?.formData).length - 1) {
|
|
247
|
+
Object.assign(oldComponentData, newComponentData);
|
|
248
|
+
return true;
|
|
249
|
+
}
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* @public
|
|
255
|
+
* 根据组件路径获取组件位置、组件所在列表路径、组件父组件路径以及组件数据
|
|
256
|
+
* @param path - 组件路径
|
|
257
|
+
* @returns
|
|
258
|
+
* @example
|
|
259
|
+
*/
|
|
260
|
+
getIndexByKey(path: string): {
|
|
261
|
+
index: number;
|
|
262
|
+
pageIndex: number;
|
|
263
|
+
componentListPath: string;
|
|
264
|
+
parentComponentPath: string;
|
|
265
|
+
component: ComponentItem;
|
|
266
|
+
} {
|
|
267
|
+
const paths = path ? path.split('.') : [];
|
|
268
|
+
const hasContainer = path.includes('children');
|
|
269
|
+
return {
|
|
270
|
+
index: path ? +paths.slice(-1) : -1,
|
|
271
|
+
pageIndex: path ? +paths.slice(0, 1) : -1,
|
|
272
|
+
componentListPath: hasContainer
|
|
273
|
+
? paths.slice(0, -1).join('.')
|
|
274
|
+
: this.basePath,
|
|
275
|
+
parentComponentPath: hasContainer ? paths.slice(0, -2).join('.') : null,
|
|
276
|
+
component: this.pathToData(path),
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* @public
|
|
282
|
+
* 判断是否符合插入容器组件的条件
|
|
283
|
+
* @param child - 子容器组件配置
|
|
284
|
+
* @param container - 容器组件数据
|
|
285
|
+
* @returns
|
|
286
|
+
* @example
|
|
287
|
+
*/
|
|
288
|
+
canInsertContainer(child: Component, container: ComponentItem) {
|
|
289
|
+
const insertConfig =
|
|
290
|
+
container.insertConfig || this.cManager.get(container.name).insertConfig;
|
|
291
|
+
let errorMessage = '';
|
|
292
|
+
const context = { componentName: child.title };
|
|
293
|
+
if (!insertConfig?.allowList) {
|
|
294
|
+
return true;
|
|
295
|
+
}
|
|
296
|
+
// 判断该组件是否在允许拖入容器的组件范围
|
|
297
|
+
if (!insertConfig?.allowList?.includes(child.name)) {
|
|
298
|
+
errorMessage = decodeContent(insertConfig?.allowErrorMessage, {
|
|
299
|
+
params: context,
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
// 判断该组件是否在单独占据容器的组件范围
|
|
303
|
+
if (insertConfig?.aloneList.includes(child.name)) {
|
|
304
|
+
// 判断容器内是否存在组件
|
|
305
|
+
if (container?.children?.length) {
|
|
306
|
+
errorMessage = decodeContent(insertConfig.aloneErrorMessage, {
|
|
307
|
+
params: context,
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
} else {
|
|
311
|
+
// 判断容器内是否存在单独占据容器的组件
|
|
312
|
+
const aloneComponent = (container.children as ComponentItem[]).find(c =>
|
|
313
|
+
insertConfig?.aloneList.includes(c.name)
|
|
314
|
+
);
|
|
315
|
+
if (aloneComponent) {
|
|
316
|
+
errorMessage = decodeContent(insertConfig?.aloneErrorMessage, {
|
|
317
|
+
params: { componentName: aloneComponent.title },
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
if (errorMessage) {
|
|
322
|
+
this.publishEditor(
|
|
323
|
+
'action',
|
|
324
|
+
toPlainObject({
|
|
325
|
+
key: 'error',
|
|
326
|
+
value: errorMessage,
|
|
327
|
+
})
|
|
328
|
+
);
|
|
329
|
+
return false;
|
|
330
|
+
}
|
|
331
|
+
return true;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* @public
|
|
336
|
+
* @param name - 组件名
|
|
337
|
+
* @param data - 数据
|
|
338
|
+
*/
|
|
339
|
+
action(name: string, data: any): void {
|
|
340
|
+
try {
|
|
341
|
+
console.log('action:', name, data);
|
|
342
|
+
if ('componentListSplice' === name) {
|
|
343
|
+
this.componentListSplice(data.insertIndex,
|
|
344
|
+
data.deleteNum,
|
|
345
|
+
// 不需要this.cManager.copy, 管理端复制的时候已经设置
|
|
346
|
+
data.component,
|
|
347
|
+
data.componentListPath,
|
|
348
|
+
);
|
|
349
|
+
// 因为maxId不一样了,需要重新init
|
|
350
|
+
this.cManager.initComponens(this.components);
|
|
351
|
+
}
|
|
352
|
+
} catch (error) {
|
|
353
|
+
console.log('action error:', error);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* @public
|
|
358
|
+
*/
|
|
359
|
+
get title() {
|
|
360
|
+
const { categoryName, projectName } = this.state.project || {};
|
|
361
|
+
return (categoryName ? `${categoryName}/` : '') + projectName || '';
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* @public
|
|
365
|
+
*/
|
|
366
|
+
get isNoLogin() {
|
|
367
|
+
return this.state.project?.isNoLogin ?? false;
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* @public
|
|
371
|
+
*/
|
|
372
|
+
get pages(): ProjectData {
|
|
373
|
+
return this.state.project?.pages ?? [];
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* @public
|
|
377
|
+
*/
|
|
378
|
+
get components(): ComponentItem[] {
|
|
379
|
+
return this.pages?.[this.currentPageIndex]?.components?.[0]?.list ?? [];
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* @public
|
|
384
|
+
* 含包含config的数据
|
|
385
|
+
*/
|
|
386
|
+
get componentsWithConfig() {
|
|
387
|
+
const joinConfig = (c: ComponentItem) => {
|
|
388
|
+
const comp = cloneDeep({
|
|
389
|
+
...this.cManager.get(c.name),
|
|
390
|
+
...c,
|
|
391
|
+
});
|
|
392
|
+
if (comp.children?.length) {
|
|
393
|
+
comp.children = comp.children.map(joinConfig);
|
|
394
|
+
}
|
|
395
|
+
return comp;
|
|
396
|
+
}
|
|
397
|
+
return this.components.map(joinConfig);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* @public
|
|
402
|
+
*/
|
|
403
|
+
get pageMeta() {
|
|
404
|
+
return this.state.project?.pages?.[this.currentPageIndex]?.pageMeta ?? {};
|
|
405
|
+
}
|
|
406
|
+
//ComponentItem
|
|
407
|
+
/**
|
|
408
|
+
* @public
|
|
409
|
+
*/
|
|
410
|
+
get currentComponent(): any {
|
|
411
|
+
return this.currentComponentPath
|
|
412
|
+
? this.pathToData(this.currentComponentPath)
|
|
413
|
+
: null;
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* @public
|
|
417
|
+
* 获取导航树数据
|
|
418
|
+
*/
|
|
419
|
+
get navTreeData(): NavTreeData[] {
|
|
420
|
+
let navTreeData: NavTreeData[] = [];
|
|
421
|
+
// 根据path确定激活的组件
|
|
422
|
+
const activatedComponentId = this.currentComponent?.componentId;
|
|
423
|
+
const buildOptions = (list: ComponentItem[]): NavTreeData[] => {
|
|
424
|
+
const treeData: NavTreeData[] = [];
|
|
425
|
+
list.forEach((c, index) => {
|
|
426
|
+
const name = c.name;
|
|
427
|
+
const schema = this.cManager.get(name);
|
|
428
|
+
const path = this.generateComponentPath(c.componentId);
|
|
429
|
+
let title = c.title || schema.title;
|
|
430
|
+
if (schema.immobile) {
|
|
431
|
+
title = schema.title + (index + 1);
|
|
432
|
+
}
|
|
433
|
+
const tree: NavTreeData = {
|
|
434
|
+
title,
|
|
435
|
+
// path是对应组件数据的路径 可以用dot-object包映射到对应的组件数据
|
|
436
|
+
key: {
|
|
437
|
+
module: 'navTree',
|
|
438
|
+
action: 'activate',
|
|
439
|
+
component: name,
|
|
440
|
+
path,
|
|
441
|
+
},
|
|
442
|
+
type: c.type,
|
|
443
|
+
icon: c.icon || schema.icon,
|
|
444
|
+
immobile: schema.immobile,
|
|
445
|
+
// 当前激活的组件activity为true
|
|
446
|
+
activity: c.componentId === activatedComponentId ? true : false,
|
|
447
|
+
|
|
448
|
+
componentId: c.componentId, // 导航树需要显示组件ID
|
|
449
|
+
};
|
|
450
|
+
if (!schema.immobile) {
|
|
451
|
+
tree.rightIcon = {
|
|
452
|
+
icon: 'Delete',
|
|
453
|
+
key: {
|
|
454
|
+
module: 'menu',
|
|
455
|
+
action: 'componentDeleteWithConfirm',
|
|
456
|
+
component: name,
|
|
457
|
+
path,
|
|
458
|
+
},
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// 如果组件数据有children
|
|
463
|
+
if (c.children && c.children.length) {
|
|
464
|
+
tree.children = buildOptions(c.children as ComponentItem[]);
|
|
465
|
+
}
|
|
466
|
+
treeData.push(tree);
|
|
467
|
+
});
|
|
468
|
+
return treeData;
|
|
469
|
+
};
|
|
470
|
+
// 没有添加组件时 默认返回空树 有组件时生成组件树
|
|
471
|
+
if (this.state.project.pages) {
|
|
472
|
+
this.state.project.pages.forEach((page, idx) => {
|
|
473
|
+
const path = idx + '';
|
|
474
|
+
const tree: NavTreeData = {
|
|
475
|
+
title: page.title || '页面内容',
|
|
476
|
+
key: { module: 'navTree', action: 'activate', path },
|
|
477
|
+
icon: '', // TODO:
|
|
478
|
+
activity:
|
|
479
|
+
this.currentComponentPath === path && this.currentPageIndex === idx
|
|
480
|
+
? true
|
|
481
|
+
: false,
|
|
482
|
+
};
|
|
483
|
+
tree.children = buildOptions(page.components[0].list);
|
|
484
|
+
navTreeData.push(tree);
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
return navTreeData;
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* @public
|
|
491
|
+
* 获取当前配置表单数据
|
|
492
|
+
*/
|
|
493
|
+
get schemaData(): SchemaData {
|
|
494
|
+
if (!this.state.project.pages) {
|
|
495
|
+
// 未初始化
|
|
496
|
+
return {};
|
|
497
|
+
}
|
|
498
|
+
// 若为免登录 过滤事件和中间件
|
|
499
|
+
const filterEvents = (eventConfig: any) => {
|
|
500
|
+
Object.keys(eventConfig ?? {}).forEach(key => {
|
|
501
|
+
// 过滤掉isNoLogin为false的情况 如果事件和中间件没增加免登录配置 则isNoLogin默认为false!!
|
|
502
|
+
try {
|
|
503
|
+
if (key === 'handler' && eventConfig[key]) {
|
|
504
|
+
eventConfig[key].properties.name.enum = eventConfig[
|
|
505
|
+
key
|
|
506
|
+
].properties.name.enum.filter(item => item.isNoLogin);
|
|
507
|
+
} else if (key === 'middlewares' && eventConfig[key]) {
|
|
508
|
+
eventConfig[key].items.properties.name.enum = eventConfig[
|
|
509
|
+
key
|
|
510
|
+
].items.properties.name.enum.filter(it => it.isNoLogin);
|
|
511
|
+
}
|
|
512
|
+
} catch (error) {
|
|
513
|
+
console.log('schemaData', error);
|
|
514
|
+
}
|
|
515
|
+
});
|
|
516
|
+
};
|
|
517
|
+
|
|
518
|
+
// 没有选中组件或选中组件不存在 默认返回页面表单数据
|
|
519
|
+
if (!this.pathToData(this.currentComponentPath)?.componentId) {
|
|
520
|
+
const schemaConfig = cloneDeep(this.state.pageConfig);
|
|
521
|
+
if (this.isNoLogin) {
|
|
522
|
+
const eventConfig = getSchemaObjectAttribute(schemaConfig, 'event');
|
|
523
|
+
if (eventConfig) filterEvents(eventConfig?.properties);
|
|
524
|
+
}
|
|
525
|
+
return {
|
|
526
|
+
key: {
|
|
527
|
+
module: 'schema',
|
|
528
|
+
action: 'setPathDataWithValidate',
|
|
529
|
+
path: this.currentPageIndex + '.pageMeta',
|
|
530
|
+
},
|
|
531
|
+
title: '页面信息',
|
|
532
|
+
schema: schemaConfig,
|
|
533
|
+
data: cloneDeep(this.pageMeta),
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// 选中组件 返回对应组件表单数据 -> 根据path找到组件
|
|
538
|
+
let component = this.currentComponent;
|
|
539
|
+
//为自创建组件,则用父组件的配置项
|
|
540
|
+
if (component.immobile) {
|
|
541
|
+
const parentComponent = this.getParent();
|
|
542
|
+
if (parentComponent) {
|
|
543
|
+
component = parentComponent;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
const componentSchema = this.cManager.get(component?.name);
|
|
547
|
+
if (!componentSchema) return {};
|
|
548
|
+
|
|
549
|
+
let schemaConfig: any = cloneDeep(componentSchema.config);
|
|
550
|
+
if (this.isNoLogin) {
|
|
551
|
+
const eventConfig = getSchemaObjectAttribute(schemaConfig, 'event');
|
|
552
|
+
if (eventConfig) {
|
|
553
|
+
filterEvents(eventConfig?.properties?.list?.items?.properties);
|
|
554
|
+
}
|
|
555
|
+
// 热区
|
|
556
|
+
const hotAreaConfig = getSchemaObjectAttribute(schemaConfig, 'hotArea');
|
|
557
|
+
if (hotAreaConfig) {
|
|
558
|
+
filterEvents(
|
|
559
|
+
hotAreaConfig?.['ui:options']?.event?.properties?.list?.items
|
|
560
|
+
?.properties
|
|
561
|
+
);
|
|
562
|
+
}
|
|
563
|
+
// 如果当前非免登录组件被隐藏了 右侧区域的配置为空
|
|
564
|
+
// if (!component.isNoLogin) return {};
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
// 替换配置中的占位词
|
|
568
|
+
schemaConfig = transform(schemaConfig, this.getFlagWordMap());
|
|
569
|
+
|
|
570
|
+
return {
|
|
571
|
+
key: {
|
|
572
|
+
module: 'schema',
|
|
573
|
+
action: 'setPathDataWithValidate',
|
|
574
|
+
component: component.name,
|
|
575
|
+
path: this.generateComponentPath(component.componentId) + '.formData',
|
|
576
|
+
},
|
|
577
|
+
title: '基础配置',
|
|
578
|
+
schema: schemaConfig,
|
|
579
|
+
data: cloneDeep(component?.formData),
|
|
580
|
+
componentId: component?.componentId,
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
/**
|
|
584
|
+
* @public
|
|
585
|
+
* 获取组件树数据
|
|
586
|
+
*/
|
|
587
|
+
get componentTreeData(): ComponentTreeData {
|
|
588
|
+
const componentTree: ComponentTreeData = [];
|
|
589
|
+
|
|
590
|
+
const buildTree = (groupList: GroupTree[], trees: ComponentTree[]) => {
|
|
591
|
+
groupList.forEach((v, i) => {
|
|
592
|
+
trees.push({
|
|
593
|
+
title: v.name,
|
|
594
|
+
list: [],
|
|
595
|
+
});
|
|
596
|
+
if (v.children) {
|
|
597
|
+
buildTree(v.children, trees[i].list as ComponentTree[]);
|
|
598
|
+
} else {
|
|
599
|
+
trees[i].list = [
|
|
600
|
+
...this.state.componentConfigs
|
|
601
|
+
.filter(
|
|
602
|
+
c =>
|
|
603
|
+
c.group === v.name &&
|
|
604
|
+
(!this.isNoLogin || (this.isNoLogin && c.isNoLogin)) &&
|
|
605
|
+
!c.invalid
|
|
606
|
+
)
|
|
607
|
+
.map(c => ({
|
|
608
|
+
title: c.title,
|
|
609
|
+
icon: c.icon as string,
|
|
610
|
+
name: c.name,
|
|
611
|
+
type: c.type,
|
|
612
|
+
isNoLogin: c.isNoLogin,
|
|
613
|
+
guideConfig: c?.guideConfig || {},
|
|
614
|
+
key: {
|
|
615
|
+
module: 'componentTree',
|
|
616
|
+
action: 'componentInsert',
|
|
617
|
+
component: c.name,
|
|
618
|
+
},
|
|
619
|
+
})),
|
|
620
|
+
];
|
|
621
|
+
}
|
|
622
|
+
});
|
|
623
|
+
};
|
|
624
|
+
buildTree(this.state.groupConfig, componentTree);
|
|
625
|
+
return componentTree;
|
|
626
|
+
}
|
|
627
|
+
/**
|
|
628
|
+
* @public
|
|
629
|
+
* 获取右键菜单数据 没有考虑有容器组件的情况
|
|
630
|
+
*/
|
|
631
|
+
get menuData(): MenuData {
|
|
632
|
+
if (!this.currentComponentPath) {
|
|
633
|
+
return [];
|
|
634
|
+
}
|
|
635
|
+
const component = this.currentComponent;
|
|
636
|
+
//为自创建组件,不展示右键菜单
|
|
637
|
+
if (component.immobile) {
|
|
638
|
+
return [];
|
|
639
|
+
}
|
|
640
|
+
const key = (action: string) => ({
|
|
641
|
+
module: 'menu',
|
|
642
|
+
action,
|
|
643
|
+
component: component?.name,
|
|
644
|
+
path: this.currentComponentPath,
|
|
645
|
+
});
|
|
646
|
+
const menus: MenuData = [
|
|
647
|
+
{
|
|
648
|
+
key: key('setPathData'),
|
|
649
|
+
title: '重命名',
|
|
650
|
+
color: '#666666',
|
|
651
|
+
},
|
|
652
|
+
{
|
|
653
|
+
key: key('componentCopy'),
|
|
654
|
+
title: '复制组件',
|
|
655
|
+
color: '#666666',
|
|
656
|
+
},
|
|
657
|
+
|
|
658
|
+
{
|
|
659
|
+
key: key('componentDeleteWithConfirm'),
|
|
660
|
+
title: '删除组件',
|
|
661
|
+
color: '#c16e6c',
|
|
662
|
+
},
|
|
663
|
+
];
|
|
664
|
+
const { index, componentListPath } = this.getIndexByKey(
|
|
665
|
+
this.currentComponentPath
|
|
666
|
+
);
|
|
667
|
+
const length = get(this.pages, componentListPath).length;
|
|
668
|
+
if (length > 1) {
|
|
669
|
+
if (index != 0) {
|
|
670
|
+
menus.push({
|
|
671
|
+
key: key('componentMoveUp'),
|
|
672
|
+
title: '组件上移',
|
|
673
|
+
color: '#666666',
|
|
674
|
+
});
|
|
675
|
+
}
|
|
676
|
+
if (index != length - 1) {
|
|
677
|
+
menus.push({
|
|
678
|
+
key: key('componentMoveDown'),
|
|
679
|
+
title: '组件下移',
|
|
680
|
+
color: '#666666',
|
|
681
|
+
});
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
// 单图片组件添加一个上传图片的icon菜单放在第一位
|
|
685
|
+
if (component.name === 'ComponentImage') {
|
|
686
|
+
menus.unshift({
|
|
687
|
+
key: key('componentImageOpen'),
|
|
688
|
+
title: '上传图片',
|
|
689
|
+
color: '#666666',
|
|
690
|
+
});
|
|
691
|
+
}
|
|
692
|
+
return menus;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
/**
|
|
696
|
+
* @public
|
|
697
|
+
* 生成占位词对应的真实数据
|
|
698
|
+
* @param index - 校验时需要传入的组件位置
|
|
699
|
+
* @returns
|
|
700
|
+
*/
|
|
701
|
+
// 所有组件的选择器选项(不包括当前组件)
|
|
702
|
+
getComponentSelectors(index?: number): { label: string; value: string }[] {
|
|
703
|
+
return this.components
|
|
704
|
+
.filter(
|
|
705
|
+
(cc, i) =>
|
|
706
|
+
(index !== undefined
|
|
707
|
+
? index !== i
|
|
708
|
+
: cc.componentId !== this.currentComponent?.componentId) ||
|
|
709
|
+
!this.cManager.get(cc.name)?.unlinkable
|
|
710
|
+
)
|
|
711
|
+
.map(c => ({ label: c.title, value: `${c.name}-${c.componentId}` }));
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
/**
|
|
715
|
+
* @public
|
|
716
|
+
* 当前组件之后的选择器选项(不包括当前组件)
|
|
717
|
+
* @param index - 下标
|
|
718
|
+
* @returns
|
|
719
|
+
*/
|
|
720
|
+
getAfterComponentSelectors(
|
|
721
|
+
index?: number
|
|
722
|
+
): { label: string; value: string }[] {
|
|
723
|
+
const currentIndex =
|
|
724
|
+
index !== undefined
|
|
725
|
+
? index
|
|
726
|
+
: this.components.findIndex(
|
|
727
|
+
c => c.componentId === this.currentComponent?.componentId
|
|
728
|
+
);
|
|
729
|
+
// 若未找到当前组件或者当前组件是最后一个组件 则返回空数组
|
|
730
|
+
if (currentIndex === -1 || currentIndex === this.components.length - 1)
|
|
731
|
+
return [];
|
|
732
|
+
const availableComponents = this.components.slice(currentIndex + 1);
|
|
733
|
+
return availableComponents
|
|
734
|
+
.filter(cc => !this.cManager.get(cc.name)?.unlinkable)
|
|
735
|
+
.map(c => ({ label: c.title, value: `${c.name}-${c.componentId}` }));
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
/**
|
|
739
|
+
* @public
|
|
740
|
+
* 占位词映射表
|
|
741
|
+
* @param index - 下标
|
|
742
|
+
* @returns
|
|
743
|
+
*/
|
|
744
|
+
getFlagWordMap(index?: number) {
|
|
745
|
+
return {
|
|
746
|
+
$componentSelectors: this.getComponentSelectors(index),
|
|
747
|
+
$afterComponentSelectors: this.getAfterComponentSelectors(index),
|
|
748
|
+
$componentId: this.currentComponent?.componentId || this.components[index]?.componentId,
|
|
749
|
+
$componentName: this.currentComponent?.name || this.components[index]?.name,
|
|
750
|
+
$components: this.components
|
|
751
|
+
};
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
/**
|
|
755
|
+
* @public
|
|
756
|
+
* 激活导航树
|
|
757
|
+
* @param key - 组件LowCodeKey
|
|
758
|
+
*/
|
|
759
|
+
activate(key: LowCodeKey): void {
|
|
760
|
+
// 设置索引
|
|
761
|
+
this.currentComponentPath = key.path as string;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
/**
|
|
765
|
+
* @public
|
|
766
|
+
* 生成编辑器删除操作二次确认弹出框所需的数据
|
|
767
|
+
* @param key - 组件LowCodeKey
|
|
768
|
+
* @returns
|
|
769
|
+
*/
|
|
770
|
+
componentDeleteWithConfirm(key: LowCodeKey) {
|
|
771
|
+
if (!key.path) {
|
|
772
|
+
return;
|
|
773
|
+
}
|
|
774
|
+
const component = this.pathToData(key.path);
|
|
775
|
+
const value = {
|
|
776
|
+
type: 'warning',
|
|
777
|
+
title: '提示',
|
|
778
|
+
schema: {
|
|
779
|
+
type: 'object',
|
|
780
|
+
properties: {
|
|
781
|
+
text: {
|
|
782
|
+
type: 'string',
|
|
783
|
+
'x-decorator': 'FormItem',
|
|
784
|
+
'x-component': 'PreviewText.Input',
|
|
785
|
+
default: `是否删除组件: ${component.title}`,
|
|
786
|
+
},
|
|
787
|
+
},
|
|
788
|
+
},
|
|
789
|
+
confirmButton: {
|
|
790
|
+
text: '确认',
|
|
791
|
+
key: {
|
|
792
|
+
module: 'menu',
|
|
793
|
+
component: component.name,
|
|
794
|
+
action: 'componentDelete',
|
|
795
|
+
path: key.path,
|
|
796
|
+
},
|
|
797
|
+
},
|
|
798
|
+
cancelButton: {
|
|
799
|
+
text: '取消',
|
|
800
|
+
},
|
|
801
|
+
};
|
|
802
|
+
this.publishEditor('action', toPlainObject({ key: 'confirm', value }));
|
|
803
|
+
}
|
|
804
|
+
/**
|
|
805
|
+
* @public
|
|
806
|
+
* 组件列表元素处理
|
|
807
|
+
* @param start - 组件插入的位置
|
|
808
|
+
* @param deleteNum - 删除的组件个数
|
|
809
|
+
* @param item - 组件数据
|
|
810
|
+
* @param componentListPath - 组件所在列表的路径
|
|
811
|
+
* @returns
|
|
812
|
+
* @example
|
|
813
|
+
*/
|
|
814
|
+
componentListSplice(
|
|
815
|
+
start: number,
|
|
816
|
+
deleteNum: number,
|
|
817
|
+
item?: ComponentItem | Component,
|
|
818
|
+
componentListPath?: string
|
|
819
|
+
) {
|
|
820
|
+
const components = componentListPath
|
|
821
|
+
? get(this.pages, componentListPath)
|
|
822
|
+
: this.components;
|
|
823
|
+
// 插入操作
|
|
824
|
+
if (deleteNum === 0 && componentListPath && /\.children$/.test(componentListPath)) {
|
|
825
|
+
// 容器组件
|
|
826
|
+
if (!this.canInsertContainer(item, this.pathToData(componentListPath.replace(/\.children$/, '')))) return;
|
|
827
|
+
}
|
|
828
|
+
let deleteList = [];
|
|
829
|
+
if (item) {
|
|
830
|
+
deleteList = components.splice(
|
|
831
|
+
start,
|
|
832
|
+
deleteNum,
|
|
833
|
+
this.cManager.buildItemData(item)
|
|
834
|
+
);
|
|
835
|
+
this.currentComponentPath = componentListPath
|
|
836
|
+
? `${componentListPath}.${start}`
|
|
837
|
+
: `${this.basePath}.${start}`;
|
|
838
|
+
} else {
|
|
839
|
+
deleteList = components.splice(start, deleteNum);
|
|
840
|
+
}
|
|
841
|
+
return deleteList;
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
/**
|
|
845
|
+
* @public
|
|
846
|
+
* 移动组件到某个位置
|
|
847
|
+
* @param originPath - 原路径
|
|
848
|
+
* @param newPath - 目标路径
|
|
849
|
+
* @example
|
|
850
|
+
* moveTo('0.components.0.list.0', '0.components.0.list.1')
|
|
851
|
+
**/
|
|
852
|
+
moveTo(originPath, newPath) {
|
|
853
|
+
console.log('moveTo', originPath, newPath);
|
|
854
|
+
const originPathMap = this.getIndexByKey(originPath);
|
|
855
|
+
let newPathhMap = this.getIndexByKey(newPath);
|
|
856
|
+
if (newPathhMap.component && newPathhMap.component?.immobile) {
|
|
857
|
+
// newPath = '0.components.0.list.0.children.0' 的场景
|
|
858
|
+
// 避免插在tab容器中间
|
|
859
|
+
if (newPathhMap.component.type === ComponentType.Container) {
|
|
860
|
+
newPathhMap = this.getIndexByKey(newPath + '.children.0');
|
|
861
|
+
} else {
|
|
862
|
+
newPathhMap = this.getIndexByKey(newPath.replace('\.children.\d+$', ''));
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
const targetParentPath = newPathhMap.parentComponentPath;
|
|
866
|
+
const component = originPathMap.component;
|
|
867
|
+
let container;
|
|
868
|
+
console.log('newPath', newPath, targetParentPath)
|
|
869
|
+
if (targetParentPath) {
|
|
870
|
+
container = this.getIndexByKey(targetParentPath).component;
|
|
871
|
+
if (container && !this.canInsertContainer(component, container)) {
|
|
872
|
+
this.publishEditor('action', toPlainObject({ key: 'forceUpdate' }));
|
|
873
|
+
return;
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
// 删除被移动的组件(必须是先删除再插入,不然可能导致混乱。)
|
|
878
|
+
this.componentListSplice(
|
|
879
|
+
originPathMap.index,
|
|
880
|
+
1,
|
|
881
|
+
undefined,
|
|
882
|
+
originPathMap.componentListPath
|
|
883
|
+
);
|
|
884
|
+
let afterMoveComponentListPath = newPathhMap.componentListPath;
|
|
885
|
+
// 删除后可能导致路径发生变化,需重新计算目标路径
|
|
886
|
+
// 重新获取组件路径
|
|
887
|
+
if (container) {
|
|
888
|
+
// 插入到容器中
|
|
889
|
+
const afterMoveComponentPath = this.generateComponentPath(
|
|
890
|
+
container.componentId
|
|
891
|
+
);
|
|
892
|
+
afterMoveComponentListPath = `${afterMoveComponentPath}.children`;
|
|
893
|
+
}
|
|
894
|
+
// 在新组件列表中插入被移动的组件
|
|
895
|
+
this.componentListSplice(
|
|
896
|
+
newPathhMap.index,
|
|
897
|
+
0,
|
|
898
|
+
component,
|
|
899
|
+
afterMoveComponentListPath
|
|
900
|
+
);
|
|
901
|
+
console.log(this.components);
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
/**
|
|
905
|
+
* @public
|
|
906
|
+
* 删除组件
|
|
907
|
+
* @param key - 组件LowCodeKey
|
|
908
|
+
* @returns
|
|
909
|
+
*/
|
|
910
|
+
componentDelete(key: LowCodeKey): void {
|
|
911
|
+
if (!key?.path) {
|
|
912
|
+
return;
|
|
913
|
+
}
|
|
914
|
+
const { index, componentListPath, component } = this.getIndexByKey(
|
|
915
|
+
key.path
|
|
916
|
+
);
|
|
917
|
+
if (index === -1) return;
|
|
918
|
+
// 清除当前索引
|
|
919
|
+
this.currentComponentPath = '';
|
|
920
|
+
this.componentListSplice(index, 1, undefined, componentListPath);
|
|
921
|
+
// 若计数为0则重新计数 只针对有单独配置的对象
|
|
922
|
+
const recount = (component: ComponentItem) => {
|
|
923
|
+
this.cManager.delete(component.name);
|
|
924
|
+
((component.children as ComponentItem[]) || []).forEach(c => recount(c));
|
|
925
|
+
};
|
|
926
|
+
recount(component);
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
/**
|
|
930
|
+
* @public
|
|
931
|
+
* 复制组件
|
|
932
|
+
* @param key - 组件LowCodeKey
|
|
933
|
+
* @returns
|
|
934
|
+
*/
|
|
935
|
+
componentCopy(key: LowCodeKey): void {
|
|
936
|
+
if (!key?.path) {
|
|
937
|
+
return;
|
|
938
|
+
}
|
|
939
|
+
const { index, componentListPath, component } = this.getIndexByKey(
|
|
940
|
+
key.path
|
|
941
|
+
);
|
|
942
|
+
if (index === -1) return;
|
|
943
|
+
const componentConfig = this.cManager.get(component.name)
|
|
944
|
+
// 是否走自定义拷贝
|
|
945
|
+
if (componentConfig?.customCopy) {
|
|
946
|
+
this.publishEditor('message', {
|
|
947
|
+
type: 'copy',
|
|
948
|
+
data: {
|
|
949
|
+
component,
|
|
950
|
+
newId: this.cManager.getImxId() + 1,
|
|
951
|
+
componentListPath,
|
|
952
|
+
insertIndex: index + 1,
|
|
953
|
+
deleteCount: 0,
|
|
954
|
+
},
|
|
955
|
+
});
|
|
956
|
+
} else {
|
|
957
|
+
this.componentListSplice(
|
|
958
|
+
index + 1,
|
|
959
|
+
0,
|
|
960
|
+
this.cManager.copy(component),
|
|
961
|
+
componentListPath
|
|
962
|
+
);
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
/**
|
|
967
|
+
* @public
|
|
968
|
+
* 拖动组件
|
|
969
|
+
* @param key - 组件LowCodeKey
|
|
970
|
+
* @param value - 数据
|
|
971
|
+
*/
|
|
972
|
+
componentDrag(
|
|
973
|
+
key: LowCodeKey,
|
|
974
|
+
value: { x: number; y: number; name: string }
|
|
975
|
+
) {
|
|
976
|
+
const elements = document.elementsFromPoint(value.x, value.y);
|
|
977
|
+
// 找到坐标落在里面的最近的那个组件
|
|
978
|
+
const element = elements.find(
|
|
979
|
+
// @ts-ignore
|
|
980
|
+
(el: HTMLDivElement) => el.dataset.id && el.dataset?.component
|
|
981
|
+
);
|
|
982
|
+
const component = this.cManager.get(value.name);
|
|
983
|
+
let start = 0;
|
|
984
|
+
let componentListPath: string = '';
|
|
985
|
+
if (element) {
|
|
986
|
+
const targetComponentId = +((element as any)?.dataset?.id || 0);
|
|
987
|
+
const targetComponentPath = this.generateComponentPath(targetComponentId);
|
|
988
|
+
const targetComponent = this.pathToData(targetComponentPath);
|
|
989
|
+
// 容器组件
|
|
990
|
+
if (targetComponent.type === ComponentType.Container) {
|
|
991
|
+
componentListPath = `${targetComponentPath}.children`;
|
|
992
|
+
}
|
|
993
|
+
// 非容器组件
|
|
994
|
+
else {
|
|
995
|
+
const rect = element.getBoundingClientRect();
|
|
996
|
+
const { componentListPath: path, index } =
|
|
997
|
+
this.getIndexByKey(targetComponentPath);
|
|
998
|
+
componentListPath = path;
|
|
999
|
+
// 判断坐标在该组件的上半部分还是下半部分
|
|
1000
|
+
start = value.y < rect.top + rect.height / 2 ? index : index + 1;
|
|
1001
|
+
}
|
|
1002
|
+
} else {
|
|
1003
|
+
// 页面
|
|
1004
|
+
start = this.components.length;
|
|
1005
|
+
}
|
|
1006
|
+
this.componentListSplice(start, 0, component, componentListPath);
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
/**
|
|
1010
|
+
* @public
|
|
1011
|
+
* 点击时插入组件 直接插到模拟器最后
|
|
1012
|
+
* @param key - 组件LowCodeKey
|
|
1013
|
+
* @returns
|
|
1014
|
+
*/
|
|
1015
|
+
componentInsert(key: LowCodeKey): void {
|
|
1016
|
+
if (!key.component) {
|
|
1017
|
+
return;
|
|
1018
|
+
}
|
|
1019
|
+
let length = this.components.length;
|
|
1020
|
+
let listPath = undefined;
|
|
1021
|
+
|
|
1022
|
+
const component = this.cManager.get(key.component);
|
|
1023
|
+
|
|
1024
|
+
if (this.currentComponent) {
|
|
1025
|
+
const currentComponentConfig = this.cManager.get(this.currentComponent.name);
|
|
1026
|
+
if (currentComponentConfig.immobile) {
|
|
1027
|
+
if (currentComponentConfig.type === ComponentType.Container) {
|
|
1028
|
+
listPath = this.currentComponentPath + '.children';
|
|
1029
|
+
// 选择了容器,则插在容器内部
|
|
1030
|
+
length = this.pathToData(listPath)?.length;
|
|
1031
|
+
}
|
|
1032
|
+
} else {
|
|
1033
|
+
listPath = this.currentComponentPath.replace(/\.\d+$/, '');
|
|
1034
|
+
// 插在当前选择的组件后面
|
|
1035
|
+
length = (parseInt(this.currentComponentPath.substring(listPath.length + 1)) || 0) + 1;
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
this.componentListSplice(length, 0, component, listPath);
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
/**
|
|
1042
|
+
* @public
|
|
1043
|
+
* 组件上移
|
|
1044
|
+
* @param key - 组件LowCodeKey
|
|
1045
|
+
* @returns
|
|
1046
|
+
*/
|
|
1047
|
+
componentMoveUp(key: LowCodeKey): void {
|
|
1048
|
+
if (!key.path) {
|
|
1049
|
+
return;
|
|
1050
|
+
}
|
|
1051
|
+
const { index, componentListPath } = this.getIndexByKey(key.path);
|
|
1052
|
+
this.moveTo(key.path, componentListPath + '.' + Math.max(index - 1, 0));
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
/**
|
|
1056
|
+
* @public
|
|
1057
|
+
* 组件下移
|
|
1058
|
+
* @param key - 组件LowCodeKey
|
|
1059
|
+
* @returns
|
|
1060
|
+
*/
|
|
1061
|
+
componentMoveDown(key: LowCodeKey): void {
|
|
1062
|
+
if (!key.path) {
|
|
1063
|
+
return;
|
|
1064
|
+
}
|
|
1065
|
+
const { index, componentListPath } = this.getIndexByKey(key.path);
|
|
1066
|
+
const afterMoveIndex = Math.min(
|
|
1067
|
+
index + 1,
|
|
1068
|
+
get(this.pages, componentListPath).length - 1
|
|
1069
|
+
);
|
|
1070
|
+
this.moveTo(key.path, componentListPath + '.' + afterMoveIndex);
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
/**
|
|
1074
|
+
* @public
|
|
1075
|
+
* 打开图片上传
|
|
1076
|
+
* @param key - 组件LowCodeKey
|
|
1077
|
+
* @returns
|
|
1078
|
+
*/
|
|
1079
|
+
componentImageOpen(key: LowCodeKey): void {
|
|
1080
|
+
if (!key.path) {
|
|
1081
|
+
return;
|
|
1082
|
+
}
|
|
1083
|
+
const { index, componentListPath, component } = this.getIndexByKey(key.path);
|
|
1084
|
+
console.log('打开图片组件:', component);
|
|
1085
|
+
if (index === -1) return;
|
|
1086
|
+
// 打开云组件进行上传图片
|
|
1087
|
+
this.publishEditor('message', {
|
|
1088
|
+
type: 'openUpload',
|
|
1089
|
+
data: {
|
|
1090
|
+
field: {}
|
|
1091
|
+
},
|
|
1092
|
+
});
|
|
1093
|
+
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
/**
|
|
1097
|
+
* @public
|
|
1098
|
+
* 修改表单更新项目数据
|
|
1099
|
+
* @param path - 路径
|
|
1100
|
+
* @param value - 值
|
|
1101
|
+
*/
|
|
1102
|
+
setPathDataWithValidate(path: string, value: any): void {
|
|
1103
|
+
const data = this.setPathData(path, value);
|
|
1104
|
+
console.log('setPathDataWithValidate', data);
|
|
1105
|
+
// TODO 挪走
|
|
1106
|
+
// 先更新模拟器
|
|
1107
|
+
this.publishPreview('updateProject', this);
|
|
1108
|
+
// 校验图片
|
|
1109
|
+
const imgUrls: ImageUrlResponse[] = [];
|
|
1110
|
+
getImageUrls({
|
|
1111
|
+
// TODO 这里没考虑到页面配置的情况
|
|
1112
|
+
data: this.currentComponent?.formData?.props,
|
|
1113
|
+
path: [],
|
|
1114
|
+
result: imgUrls,
|
|
1115
|
+
});
|
|
1116
|
+
imgUrls.forEach(imgItem => {
|
|
1117
|
+
// 通过某个值获取对应属性的配置
|
|
1118
|
+
const config = getSchemaConfigByValue(
|
|
1119
|
+
this.cManager.get(this.currentComponent.name)?.config as Record<
|
|
1120
|
+
string,
|
|
1121
|
+
any
|
|
1122
|
+
>,
|
|
1123
|
+
this.currentComponent?.formData,
|
|
1124
|
+
imgItem?.url
|
|
1125
|
+
);
|
|
1126
|
+
if (config && config['x-validator']?.upload?.expectWidth) {
|
|
1127
|
+
//不存在expectWidth 不需要模拟器校验
|
|
1128
|
+
return;
|
|
1129
|
+
}
|
|
1130
|
+
this.publishPreview('validateImage', imgItem?.url, result => {
|
|
1131
|
+
const res = this.validateImage(result, imgItem?.path, imgItem?.url);
|
|
1132
|
+
if (res) {
|
|
1133
|
+
this.publishEditor(
|
|
1134
|
+
'action',
|
|
1135
|
+
toPlainObject({ key: 'doubleImage', value: res })
|
|
1136
|
+
);
|
|
1137
|
+
this.publishPreview('updateProject', this);
|
|
1138
|
+
this.publishEditor('update', true);
|
|
1139
|
+
}
|
|
1140
|
+
});
|
|
1141
|
+
});
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
/**
|
|
1145
|
+
* @public
|
|
1146
|
+
* 设置project数据 构造函数里和init时调用
|
|
1147
|
+
* @param project - 项目数据
|
|
1148
|
+
*/
|
|
1149
|
+
setProject(project) {
|
|
1150
|
+
this.state.project = merge(
|
|
1151
|
+
{
|
|
1152
|
+
categoryName: '',
|
|
1153
|
+
projectName: '无题',
|
|
1154
|
+
status: 0,
|
|
1155
|
+
isNoLogin: false,
|
|
1156
|
+
meta: { width: 414, height: 736 },
|
|
1157
|
+
pages: [
|
|
1158
|
+
{
|
|
1159
|
+
components: [
|
|
1160
|
+
{
|
|
1161
|
+
list: [],
|
|
1162
|
+
strategy: '',
|
|
1163
|
+
},
|
|
1164
|
+
],
|
|
1165
|
+
pageMeta: {},
|
|
1166
|
+
},
|
|
1167
|
+
],
|
|
1168
|
+
},
|
|
1169
|
+
project
|
|
1170
|
+
);
|
|
1171
|
+
this.cManager.initComponens(this.components);
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
/**
|
|
1175
|
+
* @public
|
|
1176
|
+
* 替换project数据 undo,redo
|
|
1177
|
+
* @param project - project数据
|
|
1178
|
+
*/
|
|
1179
|
+
replaceProject(project) {
|
|
1180
|
+
const { currentComponentPath, projectData } = project;
|
|
1181
|
+
const { categoryName, projectName, isNoLogin, status } = this.state.project;
|
|
1182
|
+
this.state.project = {
|
|
1183
|
+
...projectData,
|
|
1184
|
+
projectName: projectName,
|
|
1185
|
+
categoryName: categoryName,
|
|
1186
|
+
isNoLogin: isNoLogin,
|
|
1187
|
+
status: status,
|
|
1188
|
+
};
|
|
1189
|
+
this.activate({
|
|
1190
|
+
path: currentComponentPath,
|
|
1191
|
+
});
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
/**
|
|
1195
|
+
* @public
|
|
1196
|
+
* @param result - 模拟器校验二倍图的结果
|
|
1197
|
+
* @param path - 图片地址的路径
|
|
1198
|
+
* @returns
|
|
1199
|
+
*/
|
|
1200
|
+
validateImage(result: boolean, path: string, imgUrl: string) {
|
|
1201
|
+
if (!result) {
|
|
1202
|
+
// 清空图片 // todo 撤销恢复原来的图片
|
|
1203
|
+
set(this.currentComponent?.formData?.props, path, '');
|
|
1204
|
+
// 返回错误结果
|
|
1205
|
+
return this.validateImageFail(imgUrl);
|
|
1206
|
+
}
|
|
1207
|
+
// 校验通过返回null
|
|
1208
|
+
return null;
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
/**
|
|
1212
|
+
* @public
|
|
1213
|
+
* todo: 挪走
|
|
1214
|
+
* 二倍图校验失败需要返回的弹窗配置
|
|
1215
|
+
* @returns
|
|
1216
|
+
*/
|
|
1217
|
+
validateImageFail(imgUrl: string) {
|
|
1218
|
+
try {
|
|
1219
|
+
if (!imgUrl) {
|
|
1220
|
+
return {};
|
|
1221
|
+
}
|
|
1222
|
+
// 通过某个值获取对应属性的配置
|
|
1223
|
+
const config = getSchemaConfigByValue(
|
|
1224
|
+
this.cManager.get(this.currentComponent.name)?.config as any,
|
|
1225
|
+
this.currentComponent?.formData,
|
|
1226
|
+
imgUrl
|
|
1227
|
+
);
|
|
1228
|
+
|
|
1229
|
+
const { upload } = config?.['x-validator'] ?? { upload: {} };
|
|
1230
|
+
|
|
1231
|
+
return {
|
|
1232
|
+
text: upload?.doubleImageTip || '图片清晰度过低,请使用上传2倍图',
|
|
1233
|
+
icon: tipIcon,
|
|
1234
|
+
guide: upload?.guide,
|
|
1235
|
+
};
|
|
1236
|
+
} catch (e) {
|
|
1237
|
+
console.log('validateImageFail catch', e);
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
/**
|
|
1242
|
+
* @public
|
|
1243
|
+
* 获取功能数据
|
|
1244
|
+
*/
|
|
1245
|
+
get functionData(): FunctionData {
|
|
1246
|
+
if (!this.state.functionConfig) {
|
|
1247
|
+
// @ts-ignore
|
|
1248
|
+
return null;
|
|
1249
|
+
}
|
|
1250
|
+
// key规则: module:function, action是功能按钮的key 如undo或redo
|
|
1251
|
+
const data = {} as FunctionData;
|
|
1252
|
+
const { status, meta } = this.state.project;
|
|
1253
|
+
const createFunctionData = (key, value) => ({
|
|
1254
|
+
key: {
|
|
1255
|
+
module: 'function',
|
|
1256
|
+
action: key,
|
|
1257
|
+
},
|
|
1258
|
+
value,
|
|
1259
|
+
clickable: undefined,
|
|
1260
|
+
});
|
|
1261
|
+
Object.entries(this.state.functionConfig).forEach(([key, value]) => {
|
|
1262
|
+
// 如果布尔值为false 表示关闭这个功能 所以不添加这个字段
|
|
1263
|
+
if (typeof value === 'boolean') {
|
|
1264
|
+
if (value) {
|
|
1265
|
+
data[key] = createFunctionData(key, value);
|
|
1266
|
+
if (key === 'undo') {
|
|
1267
|
+
// 配套
|
|
1268
|
+
data['redo'] = createFunctionData('redo', value);
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
} else {
|
|
1272
|
+
let val: any = value;
|
|
1273
|
+
if (key === 'title') {
|
|
1274
|
+
val = this.title;
|
|
1275
|
+
} else if (key === 'status') {
|
|
1276
|
+
val = statusOptions[status ?? value];
|
|
1277
|
+
} else if (key === 'simulatorSizeList') {
|
|
1278
|
+
val = {
|
|
1279
|
+
...val,
|
|
1280
|
+
data: { ...val.data, ...meta },
|
|
1281
|
+
};
|
|
1282
|
+
}
|
|
1283
|
+
data[key] = createFunctionData(key, val);
|
|
1284
|
+
}
|
|
1285
|
+
});
|
|
1286
|
+
return data;
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
/**
|
|
1290
|
+
* @public
|
|
1291
|
+
* @param componentId - 组件id
|
|
1292
|
+
* @param newValue - 新的值
|
|
1293
|
+
* @param oldValue - 旧的值
|
|
1294
|
+
*/
|
|
1295
|
+
syncChildren(componentId: number, newValue?: any[], oldValue?: any[]) {
|
|
1296
|
+
const componentPath = this.generateComponentPath(componentId);
|
|
1297
|
+
const component = this.pathToData(componentPath);
|
|
1298
|
+
console.log('syncChildren', componentId, component);
|
|
1299
|
+
if (component?.name) {
|
|
1300
|
+
const config = this.cManager.get(component.name)
|
|
1301
|
+
const { syncChildrenProp, syncChildrenName } = config?.insertConfig || {};
|
|
1302
|
+
if (syncChildrenProp && syncChildrenName) {
|
|
1303
|
+
// 如果syncChildren.tabs比syncChildren.children长,则往children中插入差额的syncChildrenName组件,并把componentId更新到syncChildren.tabs中没有componentId属性的元素上;如果syncChildren.tabs比syncChildren.children短,则删除syncChildren.children中componentId不在syncChildren.tabs中的组件再同步一次。
|
|
1304
|
+
// 最后,如果currentComponentPath路径为不存在的路径,则把currentComponentPath路径更新为最后一个syncChildren.children的componentId对应的路径
|
|
1305
|
+
const childPath = componentPath + '.children';
|
|
1306
|
+
const children = component.children = component.children || [];
|
|
1307
|
+
const childSetting = get(component.formData, syncChildrenProp) || [];
|
|
1308
|
+
console.log('syncChildren2', children, childSetting)
|
|
1309
|
+
const childLen = children.length;
|
|
1310
|
+
const childrenComponent = this.cManager.get(syncChildrenName);
|
|
1311
|
+
if (childLen < childSetting.length) {
|
|
1312
|
+
// 添加的情况
|
|
1313
|
+
for (let i = childLen; i < childSetting?.length; i++) {
|
|
1314
|
+
this.componentListSplice(i, 0, childrenComponent, childPath);
|
|
1315
|
+
}
|
|
1316
|
+
console.log('syncChildren3', childLen, childSetting?.length, this.pathToData(childPath));
|
|
1317
|
+
// 对于初始化的情况
|
|
1318
|
+
if (childLen === 0) {
|
|
1319
|
+
this.activate({ path: childPath + '.0' });
|
|
1320
|
+
}
|
|
1321
|
+
} else if (childLen > childSetting.length) {
|
|
1322
|
+
// 删除的情况
|
|
1323
|
+
if (newValue && oldValue) {
|
|
1324
|
+
const newChildren = [];
|
|
1325
|
+
const hasIn = {};
|
|
1326
|
+
newValue.forEach((item, index) => {
|
|
1327
|
+
const oldIndex = oldValue.findIndex((o, oldIdx) => isEqual(item, o) && !hasIn[oldIdx]);
|
|
1328
|
+
hasIn[oldIndex] = true;
|
|
1329
|
+
newChildren[index] = children[oldIndex];
|
|
1330
|
+
});
|
|
1331
|
+
component.children = newChildren;
|
|
1332
|
+
} else {
|
|
1333
|
+
component.children = component.children.slice(0, childSetting.length)
|
|
1334
|
+
}
|
|
1335
|
+
this.activate({ path: componentPath });
|
|
1336
|
+
}
|
|
1337
|
+
this.emit('updateHistory');
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
}
|