@tmagic/editor 1.1.6 → 1.2.0-beta.2
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/dist/style.css +177 -0
- package/dist/tmagic-editor.mjs +1456 -524
- package/dist/tmagic-editor.mjs.map +1 -1
- package/dist/tmagic-editor.umd.js +1448 -510
- package/dist/tmagic-editor.umd.js.map +1 -1
- package/package.json +7 -7
- package/src/Editor.vue +18 -1
- package/src/fields/Code.vue +4 -1
- package/src/fields/CodeLink.vue +19 -11
- package/src/fields/CodeSelect.vue +139 -0
- package/src/index.ts +6 -2
- package/src/layouts/AddPageBox.vue +11 -19
- package/src/layouts/CodeEditor.vue +106 -120
- package/src/layouts/Layout.vue +1 -1
- package/src/layouts/PropsPanel.vue +1 -1
- package/src/layouts/sidebar/LayerMenu.vue +86 -91
- package/src/layouts/sidebar/Sidebar.vue +30 -24
- package/src/layouts/sidebar/TabPane.vue +57 -40
- package/src/layouts/sidebar/code-block/CodeBlockEditor.vue +164 -0
- package/src/layouts/sidebar/code-block/CodeBlockList.vue +267 -0
- package/src/services/codeBlock.ts +415 -0
- package/src/services/editor.ts +27 -1
- package/src/theme/code-block.scss +184 -0
- package/src/theme/index.scss +1 -13
- package/src/theme/layout.scss +5 -0
- package/src/theme/theme.scss +15 -0
- package/src/type.ts +87 -1
- package/src/utils/props.ts +9 -3
- package/types/fields/Code.vue.d.ts +8 -0
- package/types/fields/CodeLink.vue.d.ts +4 -0
- package/types/fields/CodeSelect.vue.d.ts +96 -0
- package/types/index.d.ts +3 -0
- package/types/layouts/AddPageBox.vue.d.ts +45 -3
- package/types/layouts/CodeEditor.vue.d.ts +160 -45
- package/types/layouts/sidebar/LayerMenu.vue.d.ts +16 -64
- package/types/layouts/sidebar/LayerPanel.vue.d.ts +8 -196
- package/types/layouts/sidebar/Sidebar.vue.d.ts +114 -17
- package/types/layouts/sidebar/TabPane.vue.d.ts +80 -12
- package/types/layouts/sidebar/code-block/CodeBlockEditor.vue.d.ts +50 -0
- package/types/layouts/sidebar/code-block/CodeBlockList.vue.d.ts +73 -0
- package/types/services/codeBlock.d.ts +155 -0
- package/types/services/editor.d.ts +12 -1
- package/types/services/props.d.ts +12 -0
- package/types/type.d.ts +77 -1
- package/types/utils/props.d.ts +12 -0
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="m-editor-code-block-list">
|
|
3
|
+
<slot name="code-block-panel-header">
|
|
4
|
+
<div class="code-header-wrapper">
|
|
5
|
+
<el-input
|
|
6
|
+
:class="[editable ? 'code-filter-input' : 'code-filter-input-no-btn']"
|
|
7
|
+
size="small"
|
|
8
|
+
placeholder="输入关键字进行过滤"
|
|
9
|
+
clearable
|
|
10
|
+
v-model="filterText"
|
|
11
|
+
@input="filterTextChangeHandler"
|
|
12
|
+
></el-input>
|
|
13
|
+
<el-button class="create-code-button" type="primary" size="small" @click="createCodeBlock" v-if="editable"
|
|
14
|
+
>新增</el-button
|
|
15
|
+
>
|
|
16
|
+
</div>
|
|
17
|
+
</slot>
|
|
18
|
+
|
|
19
|
+
<!-- 代码块列表 -->
|
|
20
|
+
<el-tree
|
|
21
|
+
v-if="!isEmpty(state.codeList)"
|
|
22
|
+
ref="tree"
|
|
23
|
+
node-key="id"
|
|
24
|
+
empty-text="暂无代码块"
|
|
25
|
+
:data="state.codeList"
|
|
26
|
+
:highlight-current="true"
|
|
27
|
+
:filter-node-method="filterNode"
|
|
28
|
+
@node-click="toggleCombineRelation"
|
|
29
|
+
>
|
|
30
|
+
<template #default="{ data }">
|
|
31
|
+
<div :id="data.id" class="list-container">
|
|
32
|
+
<div class="list-item">
|
|
33
|
+
<div class="code-name">{{ data.name }}({{ data.id }})</div>
|
|
34
|
+
<!-- 右侧工具栏 -->
|
|
35
|
+
<div class="right-tool">
|
|
36
|
+
<el-tooltip effect="dark" :content="editable ? '编辑' : '查看'" placement="bottom">
|
|
37
|
+
<Icon :icon="editable ? Edit : View" class="edit-icon" @click.stop="editCode(`${data.id}`)"></Icon>
|
|
38
|
+
</el-tooltip>
|
|
39
|
+
<el-tooltip
|
|
40
|
+
effect="dark"
|
|
41
|
+
content="查看绑定关系"
|
|
42
|
+
placement="bottom"
|
|
43
|
+
v-if="state.bindComps[data.id] && state.bindComps[data.id].length > 0"
|
|
44
|
+
>
|
|
45
|
+
<Icon :icon="Link" class="edit-icon" @click.stop="toggleCombineRelation(data)"></Icon>
|
|
46
|
+
</el-tooltip>
|
|
47
|
+
<el-tooltip effect="dark" content="删除" placement="bottom" v-if="editable">
|
|
48
|
+
<Icon :icon="Close" class="edit-icon" @click.stop="deleteCode(`${data.id}`)"></Icon>
|
|
49
|
+
</el-tooltip>
|
|
50
|
+
<slot name="code-block-panel-tool" :id="data.id" :data="data.codeBlockContent"></slot>
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
<!-- 展示代码块下绑定的组件 -->
|
|
54
|
+
<div
|
|
55
|
+
class="code-comp-map-wrapper"
|
|
56
|
+
v-if="data.showRelation && state.bindComps[data.id] && state.bindComps[data.id].length > 0"
|
|
57
|
+
>
|
|
58
|
+
<svg class="arrow-left" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" data-v-029747aa="">
|
|
59
|
+
<path
|
|
60
|
+
fill="currentColor"
|
|
61
|
+
d="M609.408 149.376 277.76 489.6a32 32 0 0 0 0 44.672l331.648 340.352a29.12 29.12 0 0 0 41.728 0 30.592 30.592 0 0 0 0-42.752L339.264 511.936l311.872-319.872a30.592 30.592 0 0 0 0-42.688 29.12 29.12 0 0 0-41.728 0z"
|
|
62
|
+
></path>
|
|
63
|
+
</svg>
|
|
64
|
+
<!-- todo 功能暂时隐藏 -->
|
|
65
|
+
<!-- <el-button
|
|
66
|
+
v-for="(comp, index) in state.bindComps[data.id]"
|
|
67
|
+
:key="index"
|
|
68
|
+
class="code-comp"
|
|
69
|
+
size="small"
|
|
70
|
+
:plain="true"
|
|
71
|
+
>{{ comp.name }}<Icon :icon="Close" class="comp-delete-icon" @click.stop="unbind(comp.id, data.id)"></Icon
|
|
72
|
+
></el-button> -->
|
|
73
|
+
<el-button
|
|
74
|
+
v-for="(comp, index) in state.bindComps[data.id]"
|
|
75
|
+
:key="index"
|
|
76
|
+
class="code-comp"
|
|
77
|
+
size="small"
|
|
78
|
+
:plain="true"
|
|
79
|
+
@click.stop="selectComp(comp.id)"
|
|
80
|
+
>{{ comp.name }}</el-button
|
|
81
|
+
>
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
</template>
|
|
85
|
+
</el-tree>
|
|
86
|
+
|
|
87
|
+
<!-- 代码块编辑区 -->
|
|
88
|
+
<code-block-editor>
|
|
89
|
+
<template #code-block-edit-panel-header="{ id }">
|
|
90
|
+
<slot name="code-block-edit-panel-header" :id="id"></slot>
|
|
91
|
+
</template>
|
|
92
|
+
</code-block-editor>
|
|
93
|
+
</div>
|
|
94
|
+
</template>
|
|
95
|
+
|
|
96
|
+
<script lang="ts" setup>
|
|
97
|
+
import { computed, inject, reactive, ref, watch } from 'vue';
|
|
98
|
+
import { Close, Edit, Link, View } from '@element-plus/icons-vue';
|
|
99
|
+
import { ElMessage } from 'element-plus';
|
|
100
|
+
import { forIn, isEmpty } from 'lodash-es';
|
|
101
|
+
|
|
102
|
+
import { Id } from '@tmagic/schema';
|
|
103
|
+
import StageCore from '@tmagic/stage';
|
|
104
|
+
|
|
105
|
+
import Icon from '../../../components/Icon.vue';
|
|
106
|
+
import type { CodeBlockContent, CodeRelation, Services } from '../../../type';
|
|
107
|
+
import { CodeDeleteErrorType, CodeDslList, CodeEditorMode, ListRelationState } from '../../../type';
|
|
108
|
+
|
|
109
|
+
import codeBlockEditor from './CodeBlockEditor.vue';
|
|
110
|
+
|
|
111
|
+
const props = defineProps<{
|
|
112
|
+
customError?: (id: string, errorType: CodeDeleteErrorType) => any;
|
|
113
|
+
}>();
|
|
114
|
+
|
|
115
|
+
const services = inject<Services>('services');
|
|
116
|
+
|
|
117
|
+
// 代码块列表
|
|
118
|
+
const state = reactive<ListRelationState>({
|
|
119
|
+
codeList: [],
|
|
120
|
+
bindComps: {},
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
const editable = computed(() => services?.codeBlockService.getEditStatus());
|
|
124
|
+
|
|
125
|
+
// 根据代码块ID获取其绑定的组件信息
|
|
126
|
+
const getBindCompsByCodeId = (codeId: string, codeBlockContent: CodeBlockContent) => {
|
|
127
|
+
if (isEmpty(codeBlockContent) || isEmpty(codeBlockContent.comps)) {
|
|
128
|
+
state.bindComps[codeId] = [];
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const compsField = codeBlockContent.comps as CodeRelation;
|
|
132
|
+
const bindCompIds = Object.keys(compsField);
|
|
133
|
+
const bindCompsFiltered = bindCompIds.filter((compId) => !isEmpty(compsField[compId]));
|
|
134
|
+
const compsInfo = bindCompsFiltered.map((compId) => ({
|
|
135
|
+
id: compId,
|
|
136
|
+
name: getCompName(compId),
|
|
137
|
+
}));
|
|
138
|
+
state.bindComps[codeId] = compsInfo;
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
// 初始化代码块列表
|
|
142
|
+
const initList = async () => {
|
|
143
|
+
const codeDsl = (await services?.codeBlockService.getCodeDsl()) || null;
|
|
144
|
+
if (!codeDsl) return;
|
|
145
|
+
state.codeList = [];
|
|
146
|
+
forIn(codeDsl, (value: CodeBlockContent, codeId: string) => {
|
|
147
|
+
getBindCompsByCodeId(codeId, value);
|
|
148
|
+
state.codeList.push({
|
|
149
|
+
id: codeId,
|
|
150
|
+
name: value.name,
|
|
151
|
+
codeBlockContent: value,
|
|
152
|
+
showRelation: true,
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
watch(
|
|
158
|
+
() => services?.codeBlockService.getCodeDsl(),
|
|
159
|
+
() => {
|
|
160
|
+
initList();
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
immediate: true,
|
|
164
|
+
deep: true,
|
|
165
|
+
},
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
// 监听组件名称修改,更新到代码块列表
|
|
169
|
+
watch(
|
|
170
|
+
() => services?.editorService.get('node'),
|
|
171
|
+
(curNode) => {
|
|
172
|
+
if (!curNode?.id) return;
|
|
173
|
+
forIn(state.bindComps, (bindCompInfo) => {
|
|
174
|
+
bindCompInfo.forEach((comp) => {
|
|
175
|
+
if (comp.id === curNode.id) {
|
|
176
|
+
comp.name = curNode.name;
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
},
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
// 新增代码块
|
|
184
|
+
const createCodeBlock = async () => {
|
|
185
|
+
const { codeBlockService } = services || {};
|
|
186
|
+
if (!codeBlockService) {
|
|
187
|
+
ElMessage.error('新增代码块失败');
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
const codeConfig: CodeBlockContent = {
|
|
191
|
+
name: '代码块',
|
|
192
|
+
content: `() => {\n // place your code here\n}`,
|
|
193
|
+
};
|
|
194
|
+
await codeBlockService.setMode(CodeEditorMode.EDITOR);
|
|
195
|
+
const id = await codeBlockService.getUniqueId();
|
|
196
|
+
await codeBlockService.setCodeDslById(id, codeConfig);
|
|
197
|
+
codeBlockService.setCodeEditorContent(true, id);
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
// 编辑代码块
|
|
201
|
+
const editCode = async (key: string) => {
|
|
202
|
+
await services?.codeBlockService.setMode(CodeEditorMode.EDITOR);
|
|
203
|
+
services?.codeBlockService.setCodeEditorContent(true, key);
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
// 删除代码块
|
|
207
|
+
const deleteCode = (key: string) => {
|
|
208
|
+
const existBinds = !!(state.bindComps[key]?.length > 0);
|
|
209
|
+
const undeleteableList = services?.codeBlockService.getUndeletableList() || [];
|
|
210
|
+
if (!existBinds && !undeleteableList.includes(key)) {
|
|
211
|
+
// 无绑定关系,且不在不可删除列表中
|
|
212
|
+
services?.codeBlockService.deleteCodeDslByIds([key]);
|
|
213
|
+
} else {
|
|
214
|
+
if (typeof props.customError === 'function') {
|
|
215
|
+
props.customError(key, existBinds ? CodeDeleteErrorType.BIND : CodeDeleteErrorType.UNDELETEABLE);
|
|
216
|
+
} else {
|
|
217
|
+
ElMessage.error('代码块删除失败');
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
const filterText = ref('');
|
|
223
|
+
const tree = ref();
|
|
224
|
+
|
|
225
|
+
const filterNode = (value: string, data: CodeDslList): boolean => {
|
|
226
|
+
if (!value) {
|
|
227
|
+
return true;
|
|
228
|
+
}
|
|
229
|
+
return `${data.name}${data.id}`.indexOf(value) !== -1;
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
const filterTextChangeHandler = (val: string) => {
|
|
233
|
+
tree.value?.filter(val);
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
// 展示/隐藏组件绑定关系
|
|
237
|
+
const toggleCombineRelation = (data: CodeDslList) => {
|
|
238
|
+
const { id } = data;
|
|
239
|
+
const currentCode = state.codeList.find((item) => item.id === id);
|
|
240
|
+
if (!currentCode) return;
|
|
241
|
+
currentCode.showRelation = !currentCode?.showRelation;
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
// 获取组件名称展示到tag上
|
|
245
|
+
const getCompName = (compId: Id): string => {
|
|
246
|
+
const node = services?.editorService.getNodeById(compId);
|
|
247
|
+
return node?.name || String(compId);
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
// todo 功能暂时隐藏
|
|
251
|
+
// 解除绑定
|
|
252
|
+
// const unbind = async (compId: Id, codeId: string) => {
|
|
253
|
+
// const res = await services?.codeBlockService.unbind(compId, codeId, codeHooks);
|
|
254
|
+
// if (res) {
|
|
255
|
+
// ElMessage.success('绑定关系解除成功');
|
|
256
|
+
// } else {
|
|
257
|
+
// ElMessage.error('绑定关系解除失败');
|
|
258
|
+
// }
|
|
259
|
+
// };
|
|
260
|
+
|
|
261
|
+
// 选中组件
|
|
262
|
+
const selectComp = (compId: Id) => {
|
|
263
|
+
const stage = services?.editorService.get<StageCore | null>('stage');
|
|
264
|
+
services?.editorService.select(compId);
|
|
265
|
+
stage?.select(compId);
|
|
266
|
+
};
|
|
267
|
+
</script>
|
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Tencent is pleased to support the open source community by making TMagicEditor available.
|
|
3
|
+
*
|
|
4
|
+
* Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved.
|
|
5
|
+
*
|
|
6
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
* you may not use this file except in compliance with the License.
|
|
8
|
+
* You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
* See the License for the specific language governing permissions and
|
|
16
|
+
* limitations under the License.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { reactive } from 'vue';
|
|
20
|
+
import { cloneDeep, forIn, isEmpty, keys, omit, pick } from 'lodash-es';
|
|
21
|
+
|
|
22
|
+
import { Id, MNode } from '@tmagic/schema';
|
|
23
|
+
|
|
24
|
+
import editorService from '../services/editor';
|
|
25
|
+
import type { CodeBlockContent, CodeBlockDSL, CodeState } from '../type';
|
|
26
|
+
import { CodeEditorMode, CodeSelectOp } from '../type';
|
|
27
|
+
import { error, info } from '../utils/logger';
|
|
28
|
+
|
|
29
|
+
import BaseService from './BaseService';
|
|
30
|
+
|
|
31
|
+
class CodeBlock extends BaseService {
|
|
32
|
+
private state = reactive<CodeState>({
|
|
33
|
+
isShowCodeEditor: false,
|
|
34
|
+
codeDsl: null,
|
|
35
|
+
id: '',
|
|
36
|
+
editable: true,
|
|
37
|
+
mode: CodeEditorMode.EDITOR,
|
|
38
|
+
combineIds: [],
|
|
39
|
+
undeletableList: [],
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
constructor() {
|
|
43
|
+
super([
|
|
44
|
+
'setCodeDsl',
|
|
45
|
+
'getCodeDsl',
|
|
46
|
+
'getCodeContentById',
|
|
47
|
+
'getCodeDslByIds',
|
|
48
|
+
'getCurrentDsl',
|
|
49
|
+
'setCodeDslById',
|
|
50
|
+
'setCodeEditorShowStatus',
|
|
51
|
+
'setEditStatus',
|
|
52
|
+
'setMode',
|
|
53
|
+
'setCombineIds',
|
|
54
|
+
'setUndeleteableList',
|
|
55
|
+
'deleteCodeDslByIds',
|
|
56
|
+
]);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* 设置活动的代码块dsl数据源
|
|
61
|
+
* @param {CodeBlockDSL} codeDsl 代码DSL
|
|
62
|
+
* @returns {void}
|
|
63
|
+
*/
|
|
64
|
+
public async setCodeDsl(codeDsl: CodeBlockDSL): Promise<void> {
|
|
65
|
+
this.state.codeDsl = codeDsl;
|
|
66
|
+
await editorService.setCodeDsl(this.state.codeDsl);
|
|
67
|
+
info('[code-block]:code-dsl-change', this.state.codeDsl);
|
|
68
|
+
this.emit('code-dsl-change', this.state.codeDsl);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* 获取活动的代码块dsl数据源(默认从dsl中的codeBlocks字段读取)
|
|
73
|
+
* @param {boolean} forceRefresh 是否强制从活动dsl拉取刷新
|
|
74
|
+
* @returns {CodeBlockDSL | null}
|
|
75
|
+
*/
|
|
76
|
+
public async getCodeDsl(forceRefresh = false): Promise<CodeBlockDSL | null> {
|
|
77
|
+
if (!this.state.codeDsl || forceRefresh) {
|
|
78
|
+
this.state.codeDsl = await editorService.getCodeDsl();
|
|
79
|
+
}
|
|
80
|
+
return this.state.codeDsl;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* 根据代码块id获取代码块内容
|
|
85
|
+
* @param {string} id 代码块id
|
|
86
|
+
* @returns {CodeBlockContent | null}
|
|
87
|
+
*/
|
|
88
|
+
public async getCodeContentById(id: string): Promise<CodeBlockContent | null> {
|
|
89
|
+
if (!id) return null;
|
|
90
|
+
const totalCodeDsl = await this.getCodeDsl();
|
|
91
|
+
if (!totalCodeDsl) return null;
|
|
92
|
+
return totalCodeDsl[id] ?? null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* 设置代码块ID和代码内容到源dsl
|
|
97
|
+
* @param {string} id 代码块id
|
|
98
|
+
* @param {CodeBlockContent} codeConfig 代码块内容配置信息
|
|
99
|
+
* @returns {void}
|
|
100
|
+
*/
|
|
101
|
+
public async setCodeDslById(id: string, codeConfig: CodeBlockContent): Promise<void> {
|
|
102
|
+
let codeDsl = await this.getCodeDsl();
|
|
103
|
+
if (!codeDsl) {
|
|
104
|
+
// dsl中无代码块字段
|
|
105
|
+
codeDsl = {
|
|
106
|
+
[id]: codeConfig,
|
|
107
|
+
};
|
|
108
|
+
} else {
|
|
109
|
+
const existContent = codeDsl[id] || {};
|
|
110
|
+
codeDsl = {
|
|
111
|
+
...codeDsl,
|
|
112
|
+
[id]: {
|
|
113
|
+
...existContent,
|
|
114
|
+
...codeConfig,
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
await this.setCodeDsl(codeDsl);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* 根据代码块id数组获取代码dsl
|
|
123
|
+
* @param {string[]} ids 代码块id数组
|
|
124
|
+
* @returns {CodeBlockDSL}
|
|
125
|
+
*/
|
|
126
|
+
public async getCodeDslByIds(ids: string[]): Promise<CodeBlockDSL> {
|
|
127
|
+
const codeDsl = await this.getCodeDsl();
|
|
128
|
+
return pick(codeDsl, ids) as CodeBlockDSL;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* 设置代码编辑面板展示状态
|
|
133
|
+
* @param {boolean} status 是否展示代码编辑面板
|
|
134
|
+
* @returns {void}
|
|
135
|
+
*/
|
|
136
|
+
public async setCodeEditorShowStatus(status: boolean): Promise<void> {
|
|
137
|
+
this.state.isShowCodeEditor = status;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* 获取代码编辑面板展示状态
|
|
142
|
+
* @returns {boolean} 是否展示代码编辑面板
|
|
143
|
+
*/
|
|
144
|
+
public getCodeEditorShowStatus(): boolean {
|
|
145
|
+
return this.state.isShowCodeEditor;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* 设置代码编辑面板展示状态及展示内容
|
|
150
|
+
* @param {boolean} status 是否展示代码编辑面板
|
|
151
|
+
* @param {string} id 代码块id
|
|
152
|
+
* @returns {void}
|
|
153
|
+
*/
|
|
154
|
+
public setCodeEditorContent(status: boolean, id: string): void {
|
|
155
|
+
if (!id) return;
|
|
156
|
+
this.setId(id);
|
|
157
|
+
this.state.isShowCodeEditor = status;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* 获取当前选中的代码块内容
|
|
162
|
+
* @returns {CodeBlockContent | null}
|
|
163
|
+
*/
|
|
164
|
+
public async getCurrentDsl() {
|
|
165
|
+
return await this.getCodeContentById(this.state.id);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* 获取编辑状态
|
|
170
|
+
* @returns {boolean} 是否可编辑
|
|
171
|
+
*/
|
|
172
|
+
public getEditStatus(): boolean {
|
|
173
|
+
return this.state.editable;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* 设置编辑状态
|
|
178
|
+
* @param {boolean} 是否可编辑
|
|
179
|
+
* @returns {void}
|
|
180
|
+
*/
|
|
181
|
+
public async setEditStatus(status: boolean): Promise<void> {
|
|
182
|
+
this.state.editable = status;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* 设置当前选中的代码块ID
|
|
187
|
+
* @param {string} id 代码块id
|
|
188
|
+
* @returns {void}
|
|
189
|
+
*/
|
|
190
|
+
public setId(id: string) {
|
|
191
|
+
if (!id) return;
|
|
192
|
+
this.state.id = id;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* 获取当前选中的代码块ID
|
|
197
|
+
* @returns {string} id 代码块id
|
|
198
|
+
*/
|
|
199
|
+
public getId(): string {
|
|
200
|
+
return this.state.id;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* 获取当前模式
|
|
205
|
+
* @returns {CodeEditorMode}
|
|
206
|
+
*/
|
|
207
|
+
public getMode(): CodeEditorMode {
|
|
208
|
+
return this.state.mode;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* 设置当前模式
|
|
213
|
+
* @param {CodeEditorMode} mode 模式
|
|
214
|
+
* @returns {void}
|
|
215
|
+
*/
|
|
216
|
+
public async setMode(mode: CodeEditorMode): Promise<void> {
|
|
217
|
+
this.state.mode = mode;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* 设置当前选中组件已关联绑定的代码块id数组
|
|
222
|
+
* @param {string[]} ids 代码块id数组
|
|
223
|
+
* @returns {void}
|
|
224
|
+
*/
|
|
225
|
+
public async setCombineIds(ids: string[]): Promise<void> {
|
|
226
|
+
this.state.combineIds = ids;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* 获取当前选中组件已关联绑定的代码块id数组
|
|
231
|
+
* @returns {string[]}
|
|
232
|
+
*/
|
|
233
|
+
public getCombineIds(): string[] {
|
|
234
|
+
return this.state.combineIds;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* 设置绑定关系
|
|
239
|
+
* @param {Id} 组件id
|
|
240
|
+
* @param {string[]} diffCodeIds 代码块id数组
|
|
241
|
+
* @param {CodeSelectOp} opFlag 操作类型
|
|
242
|
+
* @param {string} hook 代码块挂载hook名称
|
|
243
|
+
* @returns {void}
|
|
244
|
+
*/
|
|
245
|
+
public async setCombineRelation(compId: Id, diffCodeIds: string[], opFlag: CodeSelectOp, hook: string) {
|
|
246
|
+
const codeDsl = cloneDeep(await this.getCodeDsl());
|
|
247
|
+
if (!codeDsl) return;
|
|
248
|
+
if (opFlag === CodeSelectOp.DELETE) {
|
|
249
|
+
try {
|
|
250
|
+
diffCodeIds.forEach((codeId) => {
|
|
251
|
+
const compsContent = codeDsl[codeId].comps;
|
|
252
|
+
const index = compsContent?.[compId].findIndex((item) => item === hook);
|
|
253
|
+
if (typeof index !== 'undefined' && index !== -1) {
|
|
254
|
+
compsContent?.[compId].splice(index, 1);
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
} catch (e) {
|
|
258
|
+
error(e);
|
|
259
|
+
throw new Error('解绑代码块失败');
|
|
260
|
+
}
|
|
261
|
+
} else if (opFlag === CodeSelectOp.ADD) {
|
|
262
|
+
try {
|
|
263
|
+
diffCodeIds.forEach((codeId) => {
|
|
264
|
+
const compsContent = codeDsl[codeId].comps;
|
|
265
|
+
const existHooks = compsContent?.[compId];
|
|
266
|
+
if (isEmpty(existHooks)) {
|
|
267
|
+
// comps属性不存在,或者comps为空:新增
|
|
268
|
+
codeDsl[codeId].comps = {
|
|
269
|
+
...(codeDsl[codeId].comps || {}),
|
|
270
|
+
[compId]: [hook],
|
|
271
|
+
};
|
|
272
|
+
} else {
|
|
273
|
+
// 往已有的关系中添加hook
|
|
274
|
+
existHooks?.push(hook);
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
} catch (e) {
|
|
278
|
+
error(e);
|
|
279
|
+
throw new Error('绑定代码块失败');
|
|
280
|
+
}
|
|
281
|
+
} else if (opFlag === CodeSelectOp.CHANGE) {
|
|
282
|
+
// 单选修改
|
|
283
|
+
forIn(codeDsl, (codeBlockContent, codeId) => {
|
|
284
|
+
if (codeId === diffCodeIds[0]) {
|
|
285
|
+
// 增加
|
|
286
|
+
codeBlockContent.comps = {
|
|
287
|
+
...(codeBlockContent?.comps || {}),
|
|
288
|
+
[compId]: [hook],
|
|
289
|
+
};
|
|
290
|
+
} else if (isEmpty(diffCodeIds) || codeId !== diffCodeIds[0]) {
|
|
291
|
+
// 清空或者移除之前的选项
|
|
292
|
+
const compHooks = codeBlockContent?.comps?.[compId];
|
|
293
|
+
// continue
|
|
294
|
+
if (!compHooks) return true;
|
|
295
|
+
const index = compHooks.findIndex((hookName) => hookName === hook);
|
|
296
|
+
if (index !== -1) {
|
|
297
|
+
compHooks.splice(index, 1);
|
|
298
|
+
// break
|
|
299
|
+
return false;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
this.setCodeDsl(codeDsl);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* 获取不可删除列表
|
|
309
|
+
* @returns {string[]}
|
|
310
|
+
*/
|
|
311
|
+
public getUndeletableList(): string[] {
|
|
312
|
+
return this.state.undeletableList;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* 设置不可删除列表:为业务逻辑预留的不可删除的代码块列表,由业务逻辑维护(如代码块上线后不可删除)
|
|
317
|
+
* @param {string[]} codeIds 代码块id数组
|
|
318
|
+
* @returns {void}
|
|
319
|
+
*/
|
|
320
|
+
public async setUndeleteableList(codeIds: string[]): Promise<void> {
|
|
321
|
+
this.state.undeletableList = codeIds;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* 在dsl数据源中删除指定id的代码块
|
|
326
|
+
* @param {string[]} codeIds 需要删除的代码块id数组
|
|
327
|
+
* @returns {CodeBlockDSL} 删除后的code dsl
|
|
328
|
+
*/
|
|
329
|
+
public async deleteCodeDslByIds(codeIds: string[]): Promise<CodeBlockDSL> {
|
|
330
|
+
const currentDsl = await this.getCodeDsl();
|
|
331
|
+
const newDsl = omit(currentDsl, codeIds);
|
|
332
|
+
await this.setCodeDsl(newDsl);
|
|
333
|
+
return newDsl;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* 生成代码块唯一id
|
|
338
|
+
* @returns {string} 代码块唯一id
|
|
339
|
+
*/
|
|
340
|
+
public async getUniqueId(): Promise<string> {
|
|
341
|
+
const newId = `code_${Math.random().toString(10).substring(2).substring(0, 4)}`;
|
|
342
|
+
// 判断是否重复
|
|
343
|
+
const dsl = await this.getCodeDsl();
|
|
344
|
+
const existedIds = keys(dsl);
|
|
345
|
+
if (!existedIds.includes(newId)) return newId;
|
|
346
|
+
return await this.getUniqueId();
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* 一对一解除绑定关系 功能隐藏,暂时注释
|
|
351
|
+
* @param {string} compId 组件id
|
|
352
|
+
* @param {string} codeId 代码块id
|
|
353
|
+
* @param {string[]} codeHooks 代码块挂载hook名称
|
|
354
|
+
* @returns {boolean} 结果
|
|
355
|
+
*/
|
|
356
|
+
// public async unbind(compId: Id, codeId: string, codeHooks: string[]): Promise<boolean> {
|
|
357
|
+
// const nodeInfo = editorService.getNodeById(compId);
|
|
358
|
+
// if (!nodeInfo) return false;
|
|
359
|
+
// // 更新node节点信息
|
|
360
|
+
// codeHooks.forEach((hook) => {
|
|
361
|
+
// if (!isEmpty(nodeInfo[hook])) {
|
|
362
|
+
// const newHookInfo = nodeInfo[hook].filter((item: string) => item !== codeId);
|
|
363
|
+
// nodeInfo[hook] = newHookInfo;
|
|
364
|
+
// editorService.update(nodeInfo);
|
|
365
|
+
// }
|
|
366
|
+
// });
|
|
367
|
+
// // 更新绑定关系
|
|
368
|
+
// const oldRelation = await this.getCompRelation();
|
|
369
|
+
// const oldCodeIds = oldRelation[compId];
|
|
370
|
+
// // 不能直接splice修改原数组,会导致绑定关系更新错乱
|
|
371
|
+
// const newCodeIds = oldCodeIds.filter((item) => item !== codeId);
|
|
372
|
+
// this.setCompRelation(compId, newCodeIds);
|
|
373
|
+
// return true;
|
|
374
|
+
// }
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* 通过组件id解除绑定关系(删除组件)
|
|
378
|
+
* @param {MNode} compId 组件节点
|
|
379
|
+
* @returns void
|
|
380
|
+
*/
|
|
381
|
+
public async deleteCompsInRelation(node: MNode) {
|
|
382
|
+
const codeDsl = cloneDeep(await this.getCodeDsl());
|
|
383
|
+
if (!codeDsl) return;
|
|
384
|
+
this.recurseNodes(node, codeDsl);
|
|
385
|
+
this.setCodeDsl(codeDsl);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
public destroy() {
|
|
389
|
+
this.state.isShowCodeEditor = false;
|
|
390
|
+
this.state.codeDsl = null;
|
|
391
|
+
this.state.id = '';
|
|
392
|
+
this.state.editable = true;
|
|
393
|
+
this.state.mode = CodeEditorMode.EDITOR;
|
|
394
|
+
this.state.combineIds = [];
|
|
395
|
+
this.state.undeletableList = [];
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// 删除组件时 如果是容器 需要遍历删除其包含节点的绑定信息
|
|
399
|
+
private recurseNodes(node: MNode, codeDsl: CodeBlockDSL) {
|
|
400
|
+
if (!node.id) return;
|
|
401
|
+
forIn(codeDsl, (codeBlockContent) => {
|
|
402
|
+
const compsContent = codeBlockContent.comps || {};
|
|
403
|
+
codeBlockContent.comps = omit(compsContent, node.id);
|
|
404
|
+
});
|
|
405
|
+
if (!isEmpty(node.items)) {
|
|
406
|
+
node.items.forEach((item: MNode) => {
|
|
407
|
+
this.recurseNodes(item, codeDsl);
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
export type CodeBlockService = CodeBlock;
|
|
414
|
+
|
|
415
|
+
export default new CodeBlock();
|