mirror of
https://github.com/Tencent/tmagic-editor.git
synced 2025-05-29 03:19:24 +08:00
refactor(editor): 代码编辑、数据源重构
This commit is contained in:
parent
258d2bc2ea
commit
741140fa71
@ -25,6 +25,10 @@
|
|||||||
<slot name="layer-node-content" :data="data"></slot>
|
<slot name="layer-node-content" :data="data"></slot>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<template #layer-node-label="{ data }">
|
||||||
|
<slot name="layer-node-label" :data="data"></slot>
|
||||||
|
</template>
|
||||||
|
|
||||||
<template #layer-node-tool="{ data }">
|
<template #layer-node-tool="{ data }">
|
||||||
<slot name="layer-node-tool" :data="data"></slot>
|
<slot name="layer-node-tool" :data="data"></slot>
|
||||||
</template>
|
</template>
|
||||||
|
@ -6,6 +6,10 @@
|
|||||||
<slot name="tree-node-content" :data="nodeData"> </slot>
|
<slot name="tree-node-content" :data="nodeData"> </slot>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<template #tree-node-label="{ data: nodeData }">
|
||||||
|
<slot name="tree-node-label" :data="nodeData"> </slot>
|
||||||
|
</template>
|
||||||
|
|
||||||
<template #tree-node-tool="{ data: nodeData }">
|
<template #tree-node-tool="{ data: nodeData }">
|
||||||
<slot name="tree-node-tool" :data="nodeData"> </slot>
|
<slot name="tree-node-tool" :data="nodeData"> </slot>
|
||||||
</template>
|
</template>
|
||||||
@ -28,6 +32,7 @@ import TreeNode from './TreeNode.vue';
|
|||||||
|
|
||||||
defineSlots<{
|
defineSlots<{
|
||||||
'tree-node-content'(props: { data: TreeNodeData }): any;
|
'tree-node-content'(props: { data: TreeNodeData }): any;
|
||||||
|
'tree-node-label'(props: { data: TreeNodeData }): any;
|
||||||
'tree-node-tool'(props: { data: TreeNodeData }): any;
|
'tree-node-tool'(props: { data: TreeNodeData }): any;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
@ -25,7 +25,9 @@
|
|||||||
|
|
||||||
<div class="tree-node-content" @click="nodeClickHandler">
|
<div class="tree-node-content" @click="nodeClickHandler">
|
||||||
<slot name="tree-node-content" :data="data">
|
<slot name="tree-node-content" :data="data">
|
||||||
<div class="tree-node-label">{{ `${data.name} (${data.id})` }}</div>
|
<div class="tree-node-label">
|
||||||
|
<slot name="tree-node-label" :data="data">{{ `${data.name} (${data.id})` }}</slot>
|
||||||
|
</div>
|
||||||
<div class="tree-node-tool">
|
<div class="tree-node-tool">
|
||||||
<slot name="tree-node-tool" :data="data"></slot>
|
<slot name="tree-node-tool" :data="data"></slot>
|
||||||
</div>
|
</div>
|
||||||
@ -44,6 +46,9 @@
|
|||||||
<template #tree-node-content="{ data: nodeData }">
|
<template #tree-node-content="{ data: nodeData }">
|
||||||
<slot name="tree-node-content" :data="nodeData"> </slot>
|
<slot name="tree-node-content" :data="nodeData"> </slot>
|
||||||
</template>
|
</template>
|
||||||
|
<template #tree-node-label="{ data: nodeData }">
|
||||||
|
<slot name="tree-node-label" :data="nodeData"> </slot>
|
||||||
|
</template>
|
||||||
<template #tree-node-tool="{ data: nodeData }">
|
<template #tree-node-tool="{ data: nodeData }">
|
||||||
<slot name="tree-node-tool" :data="nodeData"> </slot>
|
<slot name="tree-node-tool" :data="nodeData"> </slot>
|
||||||
</template>
|
</template>
|
||||||
@ -63,6 +68,7 @@ import type { LayerNodeStatus, TreeNodeData } from '@editor/type';
|
|||||||
import { updateStatus } from '@editor/utils/tree';
|
import { updateStatus } from '@editor/utils/tree';
|
||||||
|
|
||||||
defineSlots<{
|
defineSlots<{
|
||||||
|
'tree-node-label'(props: { data: TreeNodeData }): any;
|
||||||
'tree-node-tool'(props: { data: TreeNodeData }): any;
|
'tree-node-tool'(props: { data: TreeNodeData }): any;
|
||||||
'tree-node-content'(props: { data: TreeNodeData }): any;
|
'tree-node-content'(props: { data: TreeNodeData }): any;
|
||||||
}>();
|
}>();
|
||||||
|
@ -1,18 +1,16 @@
|
|||||||
import { computed, type ComputedRef, ref } from 'vue';
|
import { type Ref, ref } from 'vue';
|
||||||
|
|
||||||
import { Id, MNode } from '@tmagic/schema';
|
import type { Id, MNode } from '@tmagic/schema';
|
||||||
|
|
||||||
import { LayerNodeStatus, Services } from '@editor/type';
|
import type { LayerNodeStatus, TreeNodeData } from '@editor/type';
|
||||||
import { traverseNode } from '@editor/utils';
|
import { traverseNode } from '@editor/utils';
|
||||||
import { updateStatus } from '@editor/utils/tree';
|
import { updateStatus } from '@editor/utils/tree';
|
||||||
|
|
||||||
export const useFilter = (
|
export const useFilter = (
|
||||||
services: Services | undefined,
|
nodeData: Ref<TreeNodeData[]>,
|
||||||
nodeStatusMap: ComputedRef<Map<Id, LayerNodeStatus> | undefined>,
|
nodeStatusMap: Ref<Map<Id, LayerNodeStatus> | undefined>,
|
||||||
filterNodeMethod: (value: string, data: MNode) => boolean,
|
filterNodeMethod: (value: string, data: MNode) => boolean,
|
||||||
) => {
|
) => {
|
||||||
const page = computed(() => services?.editorService.get('page'));
|
|
||||||
|
|
||||||
// tree方法:对树节点进行筛选时执行的方法
|
// tree方法:对树节点进行筛选时执行的方法
|
||||||
const filterIsMatch = (value: string | string[], data: MNode): boolean => {
|
const filterIsMatch = (value: string | string[], data: MNode): boolean => {
|
||||||
const string = !Array.isArray(value) ? [value] : value;
|
const string = !Array.isArray(value) ? [value] : value;
|
||||||
@ -25,18 +23,14 @@ export const useFilter = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
const filter = (text: string | string[]) => {
|
const filter = (text: string | string[]) => {
|
||||||
if (!page.value?.items?.length) return;
|
if (!nodeData.value.length) return;
|
||||||
|
|
||||||
page.value.items.forEach((node) => {
|
nodeData.value.forEach((node) => {
|
||||||
traverseNode(node, (node: MNode, parents: MNode[]) => {
|
traverseNode(node, (node: MNode, parents: MNode[]) => {
|
||||||
if (!nodeStatusMap.value) return;
|
if (!nodeStatusMap.value) return;
|
||||||
|
|
||||||
const visible = filterIsMatch(text, node);
|
const visible = filterIsMatch(text, node);
|
||||||
if (visible && parents.length) {
|
if (visible && parents.length) {
|
||||||
console.log(
|
|
||||||
node.id,
|
|
||||||
parents.map((a) => a.id),
|
|
||||||
);
|
|
||||||
parents.forEach((parent) => {
|
parents.forEach((parent) => {
|
||||||
updateStatus(nodeStatusMap.value!, parent.id, {
|
updateStatus(nodeStatusMap.value!, parent.id, {
|
||||||
visible,
|
visible,
|
48
packages/editor/src/hooks/use-node-status.ts
Normal file
48
packages/editor/src/hooks/use-node-status.ts
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import { ComputedRef, ref, watch } from 'vue';
|
||||||
|
|
||||||
|
import type { Id, MNode } from '@tmagic/schema';
|
||||||
|
|
||||||
|
import { LayerNodeStatus, TreeNodeData } from '@editor/type';
|
||||||
|
import { traverseNode } from '@editor/utils';
|
||||||
|
|
||||||
|
const createPageNodeStatus = (nodeData: TreeNodeData[], initalLayerNodeStatus?: Map<Id, LayerNodeStatus>) => {
|
||||||
|
const map = new Map<Id, LayerNodeStatus>();
|
||||||
|
|
||||||
|
nodeData.forEach((node: MNode) =>
|
||||||
|
traverseNode(node, (node) => {
|
||||||
|
map.set(
|
||||||
|
node.id,
|
||||||
|
initalLayerNodeStatus?.get(node.id) || {
|
||||||
|
visible: true,
|
||||||
|
expand: false,
|
||||||
|
selected: false,
|
||||||
|
draggable: false,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
return map;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useNodeStatus = (nodeData: ComputedRef<TreeNodeData[]>) => {
|
||||||
|
/** 所有页面的节点状态 */
|
||||||
|
const nodeStatusMap = ref(new Map<Id, LayerNodeStatus>());
|
||||||
|
|
||||||
|
// 切换页面或者新增页面,重新生成节点状态
|
||||||
|
watch(
|
||||||
|
nodeData,
|
||||||
|
(nodeData) => {
|
||||||
|
// 生成节点状态
|
||||||
|
nodeStatusMap.value = createPageNodeStatus(nodeData, nodeStatusMap.value);
|
||||||
|
},
|
||||||
|
{
|
||||||
|
immediate: true,
|
||||||
|
deep: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
nodeStatusMap,
|
||||||
|
};
|
||||||
|
};
|
@ -74,6 +74,11 @@
|
|||||||
<component v-else-if="config.slots?.layerNodeContent" :is="config.slots.layerNodeContent" :data="nodeData" />
|
<component v-else-if="config.slots?.layerNodeContent" :is="config.slots.layerNodeContent" :data="nodeData" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<template #layer-node-label="{ data: nodeData }" v-if="config.$key === 'layer' || config.slots?.layerNodeLabel">
|
||||||
|
<slot v-if="config.$key === 'layer'" name="layer-node-label" :data="nodeData"></slot>
|
||||||
|
<component v-else-if="config.slots?.layerNodeLabel" :is="config.slots.layerNodeTool" :data="nodeData" />
|
||||||
|
</template>
|
||||||
|
|
||||||
<template #layer-node-tool="{ data: nodeData }" v-if="config.$key === 'layer' || config.slots?.layerNodeTool">
|
<template #layer-node-tool="{ data: nodeData }" v-if="config.$key === 'layer' || config.slots?.layerNodeTool">
|
||||||
<slot v-if="config.$key === 'layer'" name="layer-node-tool" :data="nodeData"></slot>
|
<slot v-if="config.$key === 'layer'" name="layer-node-tool" :data="nodeData"></slot>
|
||||||
<component v-else-if="config.slots?.layerNodeTool" :is="config.slots.layerNodeTool" :data="nodeData" />
|
<component v-else-if="config.slots?.layerNodeTool" :is="config.slots.layerNodeTool" :data="nodeData" />
|
||||||
|
@ -1,57 +1,42 @@
|
|||||||
<template>
|
<template>
|
||||||
<TMagicTree
|
<Tree :data="codeList" :node-status-map="nodeStatusMap" @node-click="clickHandler">
|
||||||
ref="tree"
|
<template #tree-node-label="{ data }">
|
||||||
class="magic-editor-layer-tree"
|
<div
|
||||||
node-key="id"
|
:class="{
|
||||||
empty-text="暂无代码块"
|
code: data.type === 'code',
|
||||||
:default-expanded-keys="expandedKeys"
|
hook: data.type === 'key',
|
||||||
:expand-on-click-node="false"
|
disabled: data.type === 'key' || data.type === 'code',
|
||||||
:data="codeList"
|
}"
|
||||||
:props="{
|
>
|
||||||
children: 'children',
|
{{ data.name }} {{ data.key ? `(${data.key})` : '' }}
|
||||||
label: 'name',
|
|
||||||
value: 'id',
|
|
||||||
}"
|
|
||||||
:highlight-current="true"
|
|
||||||
:filter-node-method="filterNode"
|
|
||||||
@node-click="clickHandler"
|
|
||||||
>
|
|
||||||
<template #default="{ data }">
|
|
||||||
<div :id="data.id" class="list-container">
|
|
||||||
<div class="list-item">
|
|
||||||
<CodeIcon v-if="data.type === 'code'" class="codeIcon"></CodeIcon>
|
|
||||||
<AppManageIcon v-if="data.type === 'node'" class="compIcon"></AppManageIcon>
|
|
||||||
<span class="name" :class="{ code: data.type === 'code', hook: data.type === 'key' }"
|
|
||||||
>{{ data.name }} ({{ data.id }})</span
|
|
||||||
>
|
|
||||||
<!-- 右侧工具栏 -->
|
|
||||||
<div class="right-tool" v-if="data.type === 'code'">
|
|
||||||
<TMagicTooltip effect="dark" :content="editable ? '编辑' : '查看'" placement="bottom">
|
|
||||||
<Icon :icon="editable ? Edit : View" class="edit-icon" @click.stop="editCode(data.id)"></Icon>
|
|
||||||
</TMagicTooltip>
|
|
||||||
<TMagicTooltip v-if="editable" effect="dark" content="删除" placement="bottom">
|
|
||||||
<Icon :icon="Close" class="edit-icon" @click.stop="deleteCode(`${data.id}`)"></Icon>
|
|
||||||
</TMagicTooltip>
|
|
||||||
<slot name="code-block-panel-tool" :id="data.id" :data="data.codeBlockContent"></slot>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</TMagicTree>
|
|
||||||
|
<template #tree-node-tool="{ data }">
|
||||||
|
<TMagicTooltip v-if="data.type === 'code'" effect="dark" :content="editable ? '编辑' : '查看'" placement="bottom">
|
||||||
|
<Icon :icon="editable ? Edit : View" class="edit-icon" @click.stop="editCode(`${data.key}`)"></Icon>
|
||||||
|
</TMagicTooltip>
|
||||||
|
<TMagicTooltip v-if="data.type === 'code' && editable" effect="dark" content="删除" placement="bottom">
|
||||||
|
<Icon :icon="Close" class="edit-icon" @click.stop="deleteCode(`${data.key}`)"></Icon>
|
||||||
|
</TMagicTooltip>
|
||||||
|
<slot name="code-block-panel-tool" :id="data.key" :data="data.codeBlockContent"></slot>
|
||||||
|
</template>
|
||||||
|
</Tree>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, inject, ref } from 'vue';
|
import { computed, inject } from 'vue';
|
||||||
import { Close, Edit, View } from '@element-plus/icons-vue';
|
import { Close, Edit, View } from '@element-plus/icons-vue';
|
||||||
|
|
||||||
import { DepTargetType } from '@tmagic/dep';
|
import { DepTargetType } from '@tmagic/dep';
|
||||||
import { tMagicMessage, tMagicMessageBox, TMagicTooltip, TMagicTree } from '@tmagic/design';
|
import { tMagicMessage, tMagicMessageBox, TMagicTooltip } from '@tmagic/design';
|
||||||
import type { Id } from '@tmagic/schema';
|
import type { Id, MNode } from '@tmagic/schema';
|
||||||
|
|
||||||
import Icon from '@editor/components/Icon.vue';
|
import Icon from '@editor/components/Icon.vue';
|
||||||
import AppManageIcon from '@editor/icons/AppManageIcon.vue';
|
import Tree from '@editor/components/Tree.vue';
|
||||||
import CodeIcon from '@editor/icons/CodeIcon.vue';
|
import { useFilter } from '@editor/hooks/use-filter';
|
||||||
import { type CodeBlockListSlots, CodeDeleteErrorType, type CodeDslItem, type Services } from '@editor/type';
|
import { useNodeStatus } from '@editor/hooks/use-node-status';
|
||||||
|
import { type CodeBlockListSlots, CodeDeleteErrorType, type Services, type TreeNodeData } from '@editor/type';
|
||||||
|
|
||||||
defineSlots<CodeBlockListSlots>();
|
defineSlots<CodeBlockListSlots>();
|
||||||
|
|
||||||
@ -68,38 +53,49 @@ const emit = defineEmits<{
|
|||||||
remove: [id: string];
|
remove: [id: string];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const { codeBlockService, depService, editorService } = inject<Services>('services') || {};
|
const services = inject<Services>('services');
|
||||||
|
const { codeBlockService, depService, editorService } = services || {};
|
||||||
|
|
||||||
// 代码块列表
|
// 代码块列表
|
||||||
const codeList = computed(() =>
|
const codeList = computed<TreeNodeData[]>(() =>
|
||||||
Object.values(depService?.getTargets(DepTargetType.CODE_BLOCK) || {}).map((target) => {
|
Object.values(depService?.getTargets(DepTargetType.CODE_BLOCK) || {}).map((target) => {
|
||||||
// 组件节点
|
// 组件节点
|
||||||
const compNodes = Object.entries(target.deps).map(([id, dep]) => ({
|
const compNodes: TreeNodeData[] = Object.entries(target.deps).map(([id, dep]) => ({
|
||||||
name: dep.name,
|
name: dep.name,
|
||||||
type: 'node',
|
type: 'node',
|
||||||
id,
|
id: `${target.id}_${id}`,
|
||||||
children: dep.keys.map((key) => ({ name: key, id: key, type: 'key' })),
|
key: id,
|
||||||
|
items: dep.keys.map((key) => {
|
||||||
|
const data: TreeNodeData = { name: `${key}`, id: `${target.id}_${id}_${key}`, type: 'key' };
|
||||||
|
return data;
|
||||||
|
}),
|
||||||
}));
|
}));
|
||||||
return {
|
|
||||||
|
const data: TreeNodeData = {
|
||||||
id: target.id,
|
id: target.id,
|
||||||
|
key: target.id,
|
||||||
name: target.name,
|
name: target.name,
|
||||||
type: 'code',
|
type: 'code',
|
||||||
codeBlockContent: codeBlockService?.getCodeContentById(target.id),
|
codeBlockContent: codeBlockService?.getCodeContentById(target.id),
|
||||||
children: compNodes,
|
items: compNodes,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
return data;
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
// 默认展开组件层级的节点
|
|
||||||
const expandedKeys = computed(() => codeList.value.map((item) => item.id));
|
|
||||||
const editable = computed(() => codeBlockService?.getEditStatus());
|
|
||||||
|
|
||||||
const filterNode = (value: string, data: CodeDslItem): boolean => {
|
const filterNode = (value: string, data: MNode): boolean => {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return `${data.name}${data.id}`.toLocaleLowerCase().indexOf(value.toLocaleLowerCase()) !== -1;
|
return `${data.name}${data.id}`.toLocaleLowerCase().includes(value.toLocaleLowerCase());
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const { nodeStatusMap } = useNodeStatus(codeList);
|
||||||
|
const { filterTextChangeHandler } = useFilter(codeList, nodeStatusMap, filterNode);
|
||||||
|
|
||||||
|
const editable = computed(() => codeBlockService?.getEditStatus());
|
||||||
|
|
||||||
// 选中组件
|
// 选中组件
|
||||||
const selectComp = (compId: Id) => {
|
const selectComp = (compId: Id) => {
|
||||||
const stage = editorService?.get('stage');
|
const stage = editorService?.get('stage');
|
||||||
@ -107,11 +103,9 @@ const selectComp = (compId: Id) => {
|
|||||||
stage?.select(compId);
|
stage?.select(compId);
|
||||||
};
|
};
|
||||||
|
|
||||||
const clickHandler = (data: any, node: any) => {
|
const clickHandler = (event: MouseEvent, data: any) => {
|
||||||
if (data.type === 'node') {
|
if (data.type === 'node') {
|
||||||
selectComp(data.id);
|
selectComp(data.key);
|
||||||
} else if (data.type === 'key') {
|
|
||||||
selectComp(node.parent.data.id);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -122,7 +116,7 @@ const editCode = (id: string) => {
|
|||||||
|
|
||||||
const deleteCode = async (id: string) => {
|
const deleteCode = async (id: string) => {
|
||||||
const currentCode = codeList.value.find((codeItem) => codeItem.id === id);
|
const currentCode = codeList.value.find((codeItem) => codeItem.id === id);
|
||||||
const existBinds = Boolean(currentCode?.children.length);
|
const existBinds = Boolean(currentCode?.items?.length);
|
||||||
const undeleteableList = codeBlockService?.getUndeletableList() || [];
|
const undeleteableList = codeBlockService?.getUndeletableList() || [];
|
||||||
if (!existBinds && !undeleteableList.includes(id)) {
|
if (!existBinds && !undeleteableList.includes(id)) {
|
||||||
await tMagicMessageBox.confirm('确定删除该代码块吗?', '提示', {
|
await tMagicMessageBox.confirm('确定删除该代码块吗?', '提示', {
|
||||||
@ -142,11 +136,7 @@ const deleteCode = async (id: string) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const tree = ref<InstanceType<typeof TMagicTree>>();
|
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
filter(val: string) {
|
filter: filterTextChangeHandler,
|
||||||
tree.value?.filter(val);
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<TMagicScrollbar class="m-editor-code-block-list m-editor-dep-list-panel">
|
<TMagicScrollbar class="m-editor-code-block-list m-editor-layer-panel">
|
||||||
<slot name="code-block-panel-header">
|
<slot name="code-block-panel-header">
|
||||||
<div class="search-wrapper">
|
<div class="search-wrapper">
|
||||||
<SearchInput @search="filterTextChangeHandler"></SearchInput>
|
<SearchInput @search="filterTextChangeHandler"></SearchInput>
|
||||||
@ -17,6 +17,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</CodeBlockList>
|
</CodeBlockList>
|
||||||
</TMagicScrollbar>
|
</TMagicScrollbar>
|
||||||
|
|
||||||
<!-- 代码块编辑区 -->
|
<!-- 代码块编辑区 -->
|
||||||
<CodeBlockEditor
|
<CodeBlockEditor
|
||||||
v-if="codeConfig"
|
v-if="codeConfig"
|
||||||
|
@ -1,48 +1,40 @@
|
|||||||
<template>
|
<template>
|
||||||
<TMagicTree
|
<Tree :data="list" :node-status-map="nodeStatusMap" @node-click="clickHandler">
|
||||||
ref="tree"
|
<template #tree-node-label="{ data }">
|
||||||
class="magic-editor-layer-tree"
|
<div
|
||||||
node-key="id"
|
:class="{
|
||||||
empty-text="暂无代码块"
|
ds: data.type === 'ds',
|
||||||
default-expand-all
|
hook: data.type === 'key',
|
||||||
:expand-on-click-node="false"
|
disabled: data.type === 'key' || data.type === 'ds',
|
||||||
:data="list"
|
}"
|
||||||
:highlight-current="true"
|
>
|
||||||
@node-click="clickHandler"
|
{{ data.name }} {{ data.key ? `(${data.key})` : '' }}
|
||||||
>
|
|
||||||
<template #default="{ data }">
|
|
||||||
<div :id="data.id" class="list-container">
|
|
||||||
<div class="list-item">
|
|
||||||
<Icon v-if="data.type === 'code'" class="codeIcon" :icon="Coin"></Icon>
|
|
||||||
<Icon v-if="data.type === 'node'" class="compIcon" :icon="Aim"></Icon>
|
|
||||||
<span class="name" :class="{ code: data.type === 'ds', hook: data.type === 'key' }">{{
|
|
||||||
data.type === 'key' ? data.name : `${data.name}(${data.id})`
|
|
||||||
}}</span>
|
|
||||||
<!-- 右侧工具栏 -->
|
|
||||||
<div class="right-tool" v-if="data.type === 'ds'">
|
|
||||||
<TMagicTooltip effect="dark" :content="editable ? '编辑' : '查看'" placement="bottom">
|
|
||||||
<Icon :icon="editable ? Edit : View" class="edit-icon" @click.stop="editHandler(`${data.id}`)"></Icon>
|
|
||||||
</TMagicTooltip>
|
|
||||||
<TMagicTooltip v-if="editable" effect="dark" content="删除" placement="bottom">
|
|
||||||
<Icon :icon="Close" class="edit-icon" @click.stop="removeHandler(`${data.id}`)"></Icon>
|
|
||||||
</TMagicTooltip>
|
|
||||||
<slot name="data-source-panel-tool" :data="data"></slot>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</TMagicTree>
|
<template #tree-node-tool="{ data }">
|
||||||
|
<TMagicTooltip v-if="data.type === 'ds'" effect="dark" :content="editable ? '编辑' : '查看'" placement="bottom">
|
||||||
|
<Icon :icon="editable ? Edit : View" class="edit-icon" @click.stop="editHandler(`${data.key}`)"></Icon>
|
||||||
|
</TMagicTooltip>
|
||||||
|
<TMagicTooltip v-if="data.type === 'ds' && editable" effect="dark" content="删除" placement="bottom">
|
||||||
|
<Icon :icon="Close" class="edit-icon" @click.stop="removeHandler(`${data.key}`)"></Icon>
|
||||||
|
</TMagicTooltip>
|
||||||
|
<slot name="data-source-panel-tool" :data="data"></slot>
|
||||||
|
</template>
|
||||||
|
</Tree>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, inject, ref } from 'vue';
|
import { computed, inject } from 'vue';
|
||||||
import { Aim, Close, Coin, Edit, View } from '@element-plus/icons-vue';
|
import { Close, Edit, View } from '@element-plus/icons-vue';
|
||||||
|
|
||||||
import { DepTargetType } from '@tmagic/dep';
|
import { DepTargetType } from '@tmagic/dep';
|
||||||
import { tMagicMessageBox, TMagicTooltip, TMagicTree } from '@tmagic/design';
|
import { tMagicMessageBox, TMagicTooltip } from '@tmagic/design';
|
||||||
import { DepData, Id } from '@tmagic/schema';
|
import { DepData, Id, MNode } from '@tmagic/schema';
|
||||||
|
|
||||||
import Icon from '@editor/components/Icon.vue';
|
import Icon from '@editor/components/Icon.vue';
|
||||||
|
import Tree from '@editor/components/Tree.vue';
|
||||||
|
import { useFilter } from '@editor/hooks/use-filter';
|
||||||
|
import { useNodeStatus } from '@editor/hooks/use-node-status';
|
||||||
import type { DataSourceListSlots, Services } from '@editor/type';
|
import type { DataSourceListSlots, Services } from '@editor/type';
|
||||||
|
|
||||||
defineSlots<DataSourceListSlots>();
|
defineSlots<DataSourceListSlots>();
|
||||||
@ -66,32 +58,39 @@ const dsDep = computed(() => depService?.getTargets(DepTargetType.DATA_SOURCE) |
|
|||||||
const dsMethodDep = computed(() => depService?.getTargets(DepTargetType.DATA_SOURCE_METHOD) || {});
|
const dsMethodDep = computed(() => depService?.getTargets(DepTargetType.DATA_SOURCE_METHOD) || {});
|
||||||
const dsCondDep = computed(() => depService?.getTargets(DepTargetType.DATA_SOURCE_COND) || {});
|
const dsCondDep = computed(() => depService?.getTargets(DepTargetType.DATA_SOURCE_COND) || {});
|
||||||
|
|
||||||
const getKeyTreeConfig = (dep: DepData[string], type?: string) =>
|
const getKeyTreeConfig = (dep: DepData[string], type?: string, parentKey?: Id) =>
|
||||||
dep.keys.map((key) => ({ name: key, id: key, type: 'key', isMethod: type === 'method', isCond: type === 'cond' }));
|
dep.keys.map((key) => ({
|
||||||
|
name: key,
|
||||||
|
id: `${parentKey}_${key}`,
|
||||||
|
type: 'key',
|
||||||
|
isMethod: type === 'method',
|
||||||
|
isCond: type === 'cond',
|
||||||
|
}));
|
||||||
|
|
||||||
const getNodeTreeConfig = (id: string, dep: DepData[string], type?: string) => ({
|
const getNodeTreeConfig = (id: string, dep: DepData[string], type?: string, parentKey?: Id) => ({
|
||||||
name: dep.name,
|
name: dep.name,
|
||||||
type: 'node',
|
type: 'node',
|
||||||
id,
|
id: `${parentKey}_${id}`,
|
||||||
children: getKeyTreeConfig(dep, type),
|
key: id,
|
||||||
|
items: getKeyTreeConfig(dep, type, `${parentKey}_${id}`),
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成tree中依赖节点的数据
|
* 生成tree中依赖节点的数据
|
||||||
* @param children 节点
|
* @param items 节点
|
||||||
* @param deps 依赖
|
* @param deps 依赖
|
||||||
* @param type 依赖类型
|
* @param type 依赖类型
|
||||||
*/
|
*/
|
||||||
const mergeChildren = (children: any[], deps: DepData, type?: string) => {
|
const mergeChildren = (dsId: Id, items: any[], deps: DepData, type?: string) => {
|
||||||
Object.entries(deps).forEach(([id, dep]) => {
|
Object.entries(deps).forEach(([id, dep]) => {
|
||||||
// 已经生成过的节点
|
// 已经生成过的节点
|
||||||
const nodeItem = children.find((item) => item.id === id);
|
const nodeItem = items.find((item) => item.key === id);
|
||||||
// 节点存在,则追加依赖的key
|
// 节点存在,则追加依赖的key
|
||||||
if (nodeItem) {
|
if (nodeItem) {
|
||||||
nodeItem.children = nodeItem.children.concat(getKeyTreeConfig(dep, type));
|
nodeItem.items = nodeItem.items.concat(getKeyTreeConfig(dep, type, nodeItem.key));
|
||||||
} else {
|
} else {
|
||||||
// 节点不存在,则生成
|
// 节点不存在,则生成
|
||||||
children.push(getNodeTreeConfig(id, dep, type));
|
items.push(getNodeTreeConfig(id, dep, type, dsId));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -102,21 +101,32 @@ const list = computed(() =>
|
|||||||
const dsMethodDeps = dsMethodDep.value[ds.id]?.deps || {};
|
const dsMethodDeps = dsMethodDep.value[ds.id]?.deps || {};
|
||||||
const dsCondDeps = dsCondDep.value[ds.id]?.deps || {};
|
const dsCondDeps = dsCondDep.value[ds.id]?.deps || {};
|
||||||
|
|
||||||
const children: any[] = [];
|
const items: any[] = [];
|
||||||
// 数据源依赖分为三种类型:key/node、method、cond,是分开存储,这里将其合并展示
|
// 数据源依赖分为三种类型:key/node、method、cond,是分开存储,这里将其合并展示
|
||||||
mergeChildren(children, dsDeps);
|
mergeChildren(ds.id, items, dsDeps);
|
||||||
mergeChildren(children, dsMethodDeps, 'method');
|
mergeChildren(ds.id, items, dsMethodDeps, 'method');
|
||||||
mergeChildren(children, dsCondDeps, 'cond');
|
mergeChildren(ds.id, items, dsCondDeps, 'cond');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: ds.id,
|
id: ds.id,
|
||||||
|
key: ds.id,
|
||||||
name: ds.title,
|
name: ds.title,
|
||||||
type: 'ds',
|
type: 'ds',
|
||||||
children,
|
items,
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const filterNode = (value: string, data: MNode): boolean => {
|
||||||
|
if (!value) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return `${data.name}${data.id}`.toLocaleLowerCase().includes(value.toLocaleLowerCase());
|
||||||
|
};
|
||||||
|
|
||||||
|
const { nodeStatusMap } = useNodeStatus(list);
|
||||||
|
const { filterTextChangeHandler } = useFilter(list, nodeStatusMap, filterNode);
|
||||||
|
|
||||||
const editHandler = (id: string) => {
|
const editHandler = (id: string) => {
|
||||||
emit('edit', id);
|
emit('edit', id);
|
||||||
};
|
};
|
||||||
@ -138,19 +148,13 @@ const selectComp = (compId: Id) => {
|
|||||||
stage?.select(compId);
|
stage?.select(compId);
|
||||||
};
|
};
|
||||||
|
|
||||||
const clickHandler = (data: any, node: any) => {
|
const clickHandler = (event: MouseEvent, data: any) => {
|
||||||
if (data.type === 'node') {
|
if (data.type === 'node') {
|
||||||
selectComp(data.id);
|
selectComp(data.key);
|
||||||
} else if (data.type === 'key') {
|
|
||||||
selectComp(node.parent.data.id);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const tree = ref<InstanceType<typeof TMagicTree>>();
|
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
filter(val: string) {
|
filter: filterTextChangeHandler,
|
||||||
tree.value?.filter(val);
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<TMagicScrollbar class="data-source-list-panel m-editor-dep-list-panel">
|
<TMagicScrollbar class="data-source-list-panel m-editor-layer-panel">
|
||||||
<div class="search-wrapper">
|
<div class="search-wrapper">
|
||||||
<SearchInput @search="filterTextChangeHandler"></SearchInput>
|
<SearchInput @search="filterTextChangeHandler"></SearchInput>
|
||||||
<TMagicPopover v-if="editable" placement="right">
|
<TMagicPopover v-if="editable" placement="right">
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
v-if="page && nodeStatusMap"
|
v-if="page && nodeStatusMap"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
ref="tree"
|
ref="tree"
|
||||||
:data="[page]"
|
:data="nodeData"
|
||||||
:node-status-map="nodeStatusMap"
|
:node-status-map="nodeStatusMap"
|
||||||
@node-dragover="handleDragOver"
|
@node-dragover="handleDragOver"
|
||||||
@node-dragstart="handleDragStart"
|
@node-dragstart="handleDragStart"
|
||||||
@ -18,14 +18,18 @@
|
|||||||
@node-mouseenter="mouseenterHandler"
|
@node-mouseenter="mouseenterHandler"
|
||||||
@node-click="nodeClickHandler"
|
@node-click="nodeClickHandler"
|
||||||
>
|
>
|
||||||
|
<template #tree-node-content="{ data: nodeData }">
|
||||||
|
<slot name="layer-node-content" :data="nodeData"> </slot>
|
||||||
|
</template>
|
||||||
|
|
||||||
<template #tree-node-tool="{ data: nodeData }">
|
<template #tree-node-tool="{ data: nodeData }">
|
||||||
<slot name="layer-node-tool" :data="nodeData">
|
<slot name="layer-node-tool" :data="nodeData">
|
||||||
<LayerNodeTool :data="nodeData"></LayerNodeTool>
|
<LayerNodeTool :data="nodeData"></LayerNodeTool>
|
||||||
</slot>
|
</slot>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #tree-node-content="{ data: nodeData }">
|
<template #tree-node-label="{ data: nodeData }">
|
||||||
<slot name="layer-node-content" :data="nodeData"> </slot>
|
<slot name="layer-node-label" :data="nodeData"></slot>
|
||||||
</template>
|
</template>
|
||||||
</Tree>
|
</Tree>
|
||||||
|
|
||||||
@ -43,13 +47,13 @@ import type { MNode } from '@tmagic/schema';
|
|||||||
|
|
||||||
import SearchInput from '@editor/components/SearchInput.vue';
|
import SearchInput from '@editor/components/SearchInput.vue';
|
||||||
import Tree from '@editor/components/Tree.vue';
|
import Tree from '@editor/components/Tree.vue';
|
||||||
import type { LayerPanelSlots, MenuButton, MenuComponent, Services } from '@editor/type';
|
import { useFilter } from '@editor/hooks/use-filter';
|
||||||
|
import { LayerPanelSlots, MenuButton, MenuComponent, Services, TreeNodeData } from '@editor/type';
|
||||||
|
|
||||||
import LayerMenu from './LayerMenu.vue';
|
import LayerMenu from './LayerMenu.vue';
|
||||||
import LayerNodeTool from './LayerNodeTool.vue';
|
import LayerNodeTool from './LayerNodeTool.vue';
|
||||||
import { useClick } from './use-click';
|
import { useClick } from './use-click';
|
||||||
import { useDrag } from './use-drag';
|
import { useDrag } from './use-drag';
|
||||||
import { useFilter } from './use-filter';
|
|
||||||
import { useKeybinding } from './use-keybinding';
|
import { useKeybinding } from './use-keybinding';
|
||||||
import { useNodeStatus } from './use-node-status';
|
import { useNodeStatus } from './use-node-status';
|
||||||
|
|
||||||
@ -69,6 +73,7 @@ const editorService = services?.editorService;
|
|||||||
const tree = ref<InstanceType<typeof Tree>>();
|
const tree = ref<InstanceType<typeof Tree>>();
|
||||||
|
|
||||||
const page = computed(() => editorService?.get('page'));
|
const page = computed(() => editorService?.get('page'));
|
||||||
|
const nodeData = computed<TreeNodeData[]>(() => (!page.value ? [] : [page.value]));
|
||||||
|
|
||||||
const { nodeStatusMap } = useNodeStatus(services);
|
const { nodeStatusMap } = useNodeStatus(services);
|
||||||
const { isCtrlKeyDown } = useKeybinding(services, tree);
|
const { isCtrlKeyDown } = useKeybinding(services, tree);
|
||||||
@ -84,7 +89,7 @@ const filterNodeMethod = (v: string, data: MNode): boolean => {
|
|||||||
return `${data.id}${name}${data.type}`.includes(v);
|
return `${data.id}${name}${data.type}`.includes(v);
|
||||||
};
|
};
|
||||||
|
|
||||||
const { filterTextChangeHandler } = useFilter(services, nodeStatusMap, filterNodeMethod);
|
const { filterTextChangeHandler } = useFilter(nodeData, nodeStatusMap, filterNodeMethod);
|
||||||
|
|
||||||
const collapseAllHandler = () => {
|
const collapseAllHandler = () => {
|
||||||
if (!page.value || !nodeStatusMap.value) return;
|
if (!page.value || !nodeStatusMap.value) return;
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
<template #body>
|
<template #body>
|
||||||
<Tree
|
<Tree
|
||||||
class="m-editor-node-list-menu magic-editor-layer-tree"
|
class="m-editor-node-list-menu magic-editor-layer-tree"
|
||||||
:data="[page]"
|
:data="nodeData"
|
||||||
:node-status-map="nodeStatusMap"
|
:node-status-map="nodeStatusMap"
|
||||||
@node-click="clickHandler"
|
@node-click="clickHandler"
|
||||||
></Tree>
|
></Tree>
|
||||||
@ -28,7 +28,7 @@ import type { MNode } from '@tmagic/schema';
|
|||||||
|
|
||||||
import FloatingBox from '@editor/components/FloatingBox.vue';
|
import FloatingBox from '@editor/components/FloatingBox.vue';
|
||||||
import Tree from '@editor/components/Tree.vue';
|
import Tree from '@editor/components/Tree.vue';
|
||||||
import { useFilter } from '@editor/layouts/sidebar/layer/use-filter';
|
import { useFilter } from '@editor/hooks/use-filter';
|
||||||
import { useNodeStatus } from '@editor/layouts/sidebar/layer/use-node-status';
|
import { useNodeStatus } from '@editor/layouts/sidebar/layer/use-node-status';
|
||||||
import type { Services, TreeNodeData } from '@editor/type';
|
import type { Services, TreeNodeData } from '@editor/type';
|
||||||
|
|
||||||
@ -42,12 +42,13 @@ const box = ref<InstanceType<typeof FloatingBox>>();
|
|||||||
const stage = computed(() => editorService?.get('stage'));
|
const stage = computed(() => editorService?.get('stage'));
|
||||||
const page = computed(() => editorService?.get('page'));
|
const page = computed(() => editorService?.get('page'));
|
||||||
const nodes = computed(() => editorService?.get('nodes') || []);
|
const nodes = computed(() => editorService?.get('nodes') || []);
|
||||||
|
const nodeData = computed<TreeNodeData[]>(() => (!page.value ? [] : [page.value]));
|
||||||
|
|
||||||
const { nodeStatusMap } = useNodeStatus(services);
|
const { nodeStatusMap } = useNodeStatus(services);
|
||||||
|
|
||||||
const filterNodeMethod = (value: string, data: MNode): boolean => data.id === value;
|
const filterNodeMethod = (value: string, data: MNode): boolean => data.id === value;
|
||||||
|
|
||||||
const { filterTextChangeHandler } = useFilter(services, nodeStatusMap, filterNodeMethod);
|
const { filterTextChangeHandler } = useFilter(nodeData, nodeStatusMap, filterNodeMethod);
|
||||||
|
|
||||||
const unWatch = watch(
|
const unWatch = watch(
|
||||||
stage,
|
stage,
|
||||||
|
@ -1,59 +0,0 @@
|
|||||||
.m-editor-dep-list-panel {
|
|
||||||
.magic-editor-layer-tree {
|
|
||||||
padding-top: 48px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-wrapper {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
width: 100%;
|
|
||||||
z-index: 1;
|
|
||||||
|
|
||||||
.search-input {
|
|
||||||
flex: 1;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tmagic-design-button {
|
|
||||||
margin-right: 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.list-container {
|
|
||||||
width: 100%;
|
|
||||||
overflow: hidden;
|
|
||||||
.list-item {
|
|
||||||
display: flex;
|
|
||||||
width: 100%;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
.right-tool {
|
|
||||||
display: flex;
|
|
||||||
width: fit-content !important;
|
|
||||||
align-items: center;
|
|
||||||
.edit-icon {
|
|
||||||
margin: 0 5px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.name {
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
width: 0 !important;
|
|
||||||
flex: 1;
|
|
||||||
line-height: 22px;
|
|
||||||
|
|
||||||
&.code {
|
|
||||||
color: #000;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.hook {
|
|
||||||
color: #909399;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -4,4 +4,23 @@
|
|||||||
.m-editor-tree {
|
.m-editor-tree {
|
||||||
padding-top: 48px;
|
padding-top: 48px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.search-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
z-index: 1;
|
||||||
|
|
||||||
|
.search-input {
|
||||||
|
flex: 1;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tmagic-design-button {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,6 @@
|
|||||||
@import "./event.scss";
|
@import "./event.scss";
|
||||||
@import "./layout.scss";
|
@import "./layout.scss";
|
||||||
@import "./breadcrumb.scss";
|
@import "./breadcrumb.scss";
|
||||||
@import "./dep-list.scss";
|
|
||||||
@import "./data-source.scss";
|
@import "./data-source.scss";
|
||||||
@import "./data-source-fields.scss";
|
@import "./data-source-fields.scss";
|
||||||
@import "./data-source-methods.scss";
|
@import "./data-source-methods.scss";
|
||||||
|
@ -59,13 +59,24 @@
|
|||||||
width: 100px;
|
width: 100px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
|
||||||
|
.disabled {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hook {
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.tree-node-tool {
|
.tree-node-tool {
|
||||||
margin-right: 15px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
|
.tmagic-design-icon {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -81,12 +81,12 @@ export interface DataSourceListSlots {
|
|||||||
|
|
||||||
export interface LayerNodeSlots {
|
export interface LayerNodeSlots {
|
||||||
'layer-node-content'(props: { data: MNode }): any;
|
'layer-node-content'(props: { data: MNode }): any;
|
||||||
|
'layer-node-tool'(props: { data: MNode }): any;
|
||||||
|
'layer-node-label'(props: { data: MNode }): any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LayerPanelSlots extends LayerNodeSlots {
|
export interface LayerPanelSlots extends LayerNodeSlots {
|
||||||
'layer-panel-header'(props: {}): any;
|
'layer-panel-header'(props: {}): any;
|
||||||
'layer-node-tool'(props: { data: MNode }): any;
|
|
||||||
'layer-node-content'(props: { data: MNode }): any;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PropsPanelSlots {
|
export interface PropsPanelSlots {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user