Skip to content

Commit

Permalink
feat(editor): 支持代码块维度查看与组件的绑定关系,并支持从代码块列表解除绑定
Browse files Browse the repository at this point in the history
  • Loading branch information
parisma authored and jia000 committed Sep 22, 2022
1 parent 5de3eed commit bfaa831
Show file tree
Hide file tree
Showing 9 changed files with 278 additions and 48 deletions.
26 changes: 23 additions & 3 deletions packages/editor/src/Editor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@

<script lang="ts">
import { defineComponent, onUnmounted, PropType, provide, reactive, toRaw, watch } from 'vue';
import { isEmpty } from 'lodash-es';
import { isEmpty,union } from 'lodash-es';
import { EventOption } from '@tmagic/core';
import type { FormConfig } from '@tmagic/form';
Expand Down Expand Up @@ -210,6 +210,12 @@ export default defineComponent({
updateDragEl: {
type: Function as PropType<(el: HTMLDivElement, target: HTMLElement) => void>,
},
/** 可挂载代码块的生命周期 */
codeHooks: {
type: Array<string>,
default: () => ['created', 'mounted'],
},
},
emits: ['props-panel-mounted', 'update:modelValue'],
Expand All @@ -230,8 +236,21 @@ export default defineComponent({
const initCodeRelation = (rootValue: MNode) => {
if (isEmpty(rootValue.items)) return;
rootValue.items.forEach((nodeValue: MNode) => {
if (!isEmpty(nodeValue.created)) {
codeBlockService.setCompRelation(nodeValue.id, nodeValue.created);
let curNodeCombineIds:string[] = []
// 合并各钩子绑定的代码块Id
props.codeHooks.forEach((hook) => {
// continue
if (isEmpty(nodeValue[hook])) return true
// 兼容单选绑定场景
if(typeof nodeValue[hook] === 'string' && nodeValue[hook]) {
curNodeCombineIds = union(curNodeCombineIds,[nodeValue[hook]])
}else if(Array.isArray(nodeValue[hook])) {
curNodeCombineIds = union(curNodeCombineIds,nodeValue[hook])
}
});
// 设置组件与代码块的绑定关系
if(!isEmpty(curNodeCombineIds)) {
codeBlockService.setCompRelation(nodeValue.id, curNodeCombineIds);
}
if (!isEmpty(nodeValue.items)) {
initCodeRelation(nodeValue);
Expand Down Expand Up @@ -351,6 +370,7 @@ export default defineComponent({
containerHighlightType: props.containerHighlightType,
}),
);
provide('codeHooks',props.codeHooks)
return services;
},
Expand Down
31 changes: 18 additions & 13 deletions packages/editor/src/fields/CodeSelect.vue
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,14 @@
<script lang="ts" setup>
import { computed, defineEmits, defineProps, inject, ref, watchEffect } from 'vue';
import { ElMessage } from 'element-plus';
import { map } from 'lodash-es';
import { map, union } from 'lodash-es';
import { SelectConfig } from '@tmagic/form';
import type { Services } from '../type';
import { EditorMode } from '../type';
import { CodeEditorMode } from '../type';
const services = inject<Services>('services');
const codeHooks = inject<string[]>('codeHooks');
const emit = defineEmits(['change']);
Expand Down Expand Up @@ -79,7 +80,6 @@ const fieldKey = ref('');
const combineIds = ref<string[]>([]);
watchEffect(async () => {
if (!combineIds.value) return;
const combineNames = await Promise.all(
combineIds.value.map(async (id) => {
const { name = '' } = (await services?.codeBlockService.getCodeContentById(id)) || {};
Expand All @@ -90,20 +90,25 @@ watchEffect(async () => {
});
const changeHandler = async (value: any) => {
await setCombineRelation(value);
await setCombineRelation();
emit('change', value);
};
// 同步绑定关系
const setCombineRelation = async (selectedIds: string[] | string) => {
if (typeof selectedIds === 'string') {
// 兼容select单选
combineIds.value = [selectedIds];
} else {
combineIds.value = selectedIds;
}
const setCombineRelation = async () => {
// 绑定数组先置空
combineIds.value = [];
// 组件id
const { id = '' } = services?.editorService.get('node') || {};
codeHooks?.forEach((hook) => {
// continue
if (!props.model[hook]) return true;
if (typeof props.model[hook] === 'string' && props.model[hook]) {
combineIds.value = union(combineIds.value, [props.model[hook]]);
} else if (Array.isArray(props.model[hook])) {
combineIds.value = union(combineIds.value, props.model[hook]);
}
});
// 记录组件与代码块的绑定关系
await services?.codeBlockService.setCompRelation(id, combineIds.value);
// 记录当前已被绑定的代码块,为查看弹窗的展示内容
Expand All @@ -115,8 +120,8 @@ const viewHandler = async () => {
ElMessage.error('请先绑定代码块');
return;
}
await setCombineRelation(props.model[props.name]);
await services?.codeBlockService.setMode(EditorMode.LIST);
await setCombineRelation();
await services?.codeBlockService.setMode(CodeEditorMode.LIST);
services?.codeBlockService.setCodeEditorContent(true, combineIds.value[0]);
};
</script>
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
>
<layout v-model:left="left" :min-left="45" class="code-editor-layout">
<!-- 左侧列表 -->
<template #left v-if="mode === EditorMode.LIST">
<template #left v-if="mode === CodeEditorMode.LIST">
<el-tree
v-if="!isEmpty(state.codeList)"
ref="tree"
Expand All @@ -35,7 +35,7 @@
<div
v-if="!isEmpty(codeConfig)"
:class="[
mode === EditorMode.LIST
mode === CodeEditorMode.LIST
? 'm-editor-code-block-editor-panel-list-mode'
: 'm-editor-code-block-editor-panel',
]"
Expand Down Expand Up @@ -82,7 +82,7 @@ import { ElMessage } from 'element-plus';
import { forIn, isEmpty } from 'lodash-es';
import type { CodeBlockContent, CodeDslList, ListState, Services } from '../../../type';
import { EditorMode } from '../../../type';
import { CodeEditorMode } from '../../../type';
import MagicCodeEditor from '../../CodeEditor.vue';
import Layout from '../../Layout.vue';
Expand Down Expand Up @@ -132,7 +132,7 @@ const saveCode = async (): Promise<boolean> => {
try {
// 代码内容
const codeContent = codeEditor.value.getEditor().getValue();
const codeContent = codeEditor.value.getEditor()?.getValue();
/* eslint no-eval: "off" */
codeConfig.value.content = eval(codeContent);
} catch (e: any) {
Expand Down
145 changes: 131 additions & 14 deletions packages/editor/src/layouts/sidebar/code-block/CodeBlockList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -25,21 +25,51 @@
:data="state.codeList"
:highlight-current="true"
:filter-node-method="filterNode"
@node-click="toggleCombineRelation"
>
<template #default="{ data }">
<div :id="data.id" class="list-container">
<div class="list-item">
<div class="code-name">{{ data.name }}({{ data.id }})</div>
<!-- 右侧工具栏 -->
<div class="right-tool">
<el-tooltip effect="dark" :content="editable ? '编辑' : '查看'" placement="bottom">
<Icon :icon="editable ? Edit : View" class="edit-icon" @click="editCode(`${data.id}`)"></Icon>
<Icon :icon="editable ? Edit : View" class="edit-icon" @click.stop="editCode(`${data.id}`)"></Icon>
</el-tooltip>
<el-tooltip
effect="dark"
content="查看绑定关系"
placement="bottom"
v-if="state.bindComps[data.id] && state.bindComps[data.id].length > 0"
>
<Icon :icon="Link" class="edit-icon" @click.stop="toggleCombineRelation(data)"></Icon>
</el-tooltip>
<el-tooltip effect="dark" content="删除" placement="bottom" v-if="editable">
<Icon :icon="Close" class="edit-icon" @click="deleteCode(`${data.id}`)"></Icon>
<Icon :icon="Close" class="edit-icon" @click.stop="deleteCode(`${data.id}`)"></Icon>
</el-tooltip>
<slot name="code-block-panel-tool" :id="data.id"></slot>
</div>
</div>
<!-- 展示代码块下绑定的组件 -->
<div
class="code-comp-map-wrapper"
v-if="data.showRelation && state.bindComps[data.id] && state.bindComps[data.id].length > 0"
>
<svg class="arrow-left" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" data-v-029747aa="">
<path
fill="currentColor"
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"
></path>
</svg>
<el-button
v-for="(comp, index) in state.bindComps[data.id]"
:key="index"
class="code-comp"
size="small"
:plain="true"
>{{ comp.name }}<Icon :icon="Close" class="comp-delete-icon" @click.stop="unbind(comp.id, data.id)"></Icon
></el-button>
</div>
</div>
</template>
</el-tree>
Expand All @@ -54,41 +84,104 @@
</template>

<script lang="ts" setup>
import { computed, inject, reactive, ref, watchEffect } from 'vue';
import { Close, Edit, View } from '@element-plus/icons-vue';
import { computed, inject, reactive, ref, watch } from 'vue';
import { Close, Edit, Link, View } from '@element-plus/icons-vue';
import { ElMessage } from 'element-plus';
import { flattenDeep, forIn, isEmpty, values } from 'lodash-es';
import { flattenDeep, forIn, isEmpty, values, xor } from 'lodash-es';
import { Id } from '@tmagic/schema';
import Icon from '../../../components/Icon.vue';
import type { CodeBlockContent, Services } from '../../../type';
import { CodeDslList, EditorMode, ErrorType, ListState } from '../../../type';
import { CodeDeleteErrorType, CodeDslList, CodeEditorMode, ListState } from '../../../type';
import codeBlockEditor from './CodeBlockEditor.vue';
const props = defineProps<{
customError?: (id: string, errorType: ErrorType) => any;
customError?: (id: string, errorType: CodeDeleteErrorType) => any;
}>();
const services = inject<Services>('services');
const codeHooks = inject<string[]>('codeHooks') || [];
// 代码块列表
const state = reactive<ListState>({
codeList: [],
bindComps: {},
});
const editable = computed(() => services?.codeBlockService.getEditStatus());
watchEffect(async () => {
// 根据代码块ID获取其绑定的组件信息
const getBindCompsByCodeId = (codeId: string) => {
const bindCompIds = services?.codeBlockService.getCodeRelationById(codeId) || [];
if (isEmpty(bindCompIds)) {
state.bindComps[codeId] = [];
return;
}
const compsInfo = bindCompIds.map((compId) => ({
id: compId,
name: getCompName(compId),
}));
state.bindComps[codeId] = compsInfo;
};
// 初始化代码块列表
const initList = async () => {
const codeDsl = (await services?.codeBlockService.getCodeDsl()) || null;
if (!codeDsl) return;
state.codeList = [];
forIn(codeDsl, (value: CodeBlockContent, key: string) => {
forIn(codeDsl, (value: CodeBlockContent, codeId: string) => {
getBindCompsByCodeId(codeId);
state.codeList.push({
id: key,
id: codeId,
name: value.name,
content: value.content,
showRelation: false,
});
});
});
};
watch(
() => services?.codeBlockService.getCodeDsl(),
() => {
initList();
},
{
immediate: true,
},
);
// 监听绑定关系修改,更新到代码块列表
watch(
() => services?.codeBlockService.getCompRelation(),
(curRelation, oldRelation) => {
forIn(curRelation, (codeArr, compId) => {
let oldCodeArr: string[] = [];
if (oldRelation) {
oldCodeArr = oldRelation[compId];
}
// 可能一次清空全部绑定关系,对比结果为数组
const diffCodeIds = xor(codeArr, oldCodeArr);
diffCodeIds.forEach((codeId) => getBindCompsByCodeId(codeId));
});
},
);
// 监听组件名称修改,更新到代码块列表
watch(
() => services?.editorService.get('node'),
(curNode) => {
if (!curNode?.id) return;
forIn(state.bindComps, (bindCompInfo) => {
bindCompInfo.forEach((comp) => {
if (comp.id === curNode.id) {
comp.name = curNode.name;
}
});
});
},
);
// 新增代码块
const createCodeBlock = async () => {
Expand All @@ -101,15 +194,15 @@ const createCodeBlock = async () => {
name: '代码块',
content: `() => {\n // place your code here\n}`,
};
await codeBlockService.setMode(EditorMode.EDITOR);
await codeBlockService.setMode(CodeEditorMode.EDITOR);
const id = await codeBlockService.getUniqueId();
await codeBlockService.setCodeDslById(id, codeConfig);
codeBlockService.setCodeEditorContent(true, id);
};
// 编辑代码块
const editCode = async (key: string) => {
await services?.codeBlockService.setMode(EditorMode.EDITOR);
await services?.codeBlockService.setMode(CodeEditorMode.EDITOR);
services?.codeBlockService.setCodeEditorContent(true, key);
};
Expand All @@ -123,7 +216,7 @@ const deleteCode = (key: string) => {
services?.codeBlockService.deleteCodeDslByIds([key]);
} else {
if (typeof props.customError === 'function') {
props.customError(key, codeIds.includes(key) ? ErrorType.BIND : ErrorType.UNDELETEABLE);
props.customError(key, codeIds.includes(key) ? CodeDeleteErrorType.BIND : CodeDeleteErrorType.UNDELETEABLE);
} else {
ElMessage.error('代码块删除失败');
}
Expand All @@ -143,4 +236,28 @@ const filterNode = (value: string, data: CodeDslList): boolean => {
const filterTextChangeHandler = (val: string) => {
tree.value?.filter(val);
};
// 展示/隐藏组件绑定关系
const toggleCombineRelation = (data: CodeDslList) => {
const { id } = data;
const currentCode = state.codeList.find((item) => item.id === id);
if (!currentCode) return;
currentCode.showRelation = !currentCode?.showRelation;
};
// 获取组件名称展示到tag上
const getCompName = (compId: Id): string => {
const node = services?.editorService.getNodeById(compId);
return node?.name || String(compId);
};
// 解除绑定
const unbind = async (compId: Id, codeId: string) => {
const res = await services?.codeBlockService.unbind(compId, codeId, codeHooks);
if (res) {
ElMessage.success('绑定关系解除成功');
} else {
ElMessage.error('绑定关系解除失败');
}
};
</script>
Loading

0 comments on commit bfaa831

Please sign in to comment.