mirror of
https://github.com/Tencent/tmagic-editor.git
synced 2026-01-19 19:28:17 +00:00
273 lines
7.4 KiB
Vue
273 lines
7.4 KiB
Vue
<template>
|
|
<el-scrollbar class="magic-editor-layer-panel">
|
|
<slot name="layer-panel-header"></slot>
|
|
|
|
<el-input
|
|
class="filterInput"
|
|
size="small"
|
|
placeholder="输入关键字进行过滤"
|
|
clearable
|
|
v-model="filterText"
|
|
@change="filterTextChangeHandler"
|
|
></el-input>
|
|
|
|
<el-tree
|
|
v-if="values.length"
|
|
ref="tree"
|
|
node-key="id"
|
|
empty-text="页面空荡荡的"
|
|
draggable
|
|
:default-expanded-keys="expandedKeys"
|
|
:load="loadItems"
|
|
:data="values"
|
|
:expand-on-click-node="false"
|
|
:highlight-current="true"
|
|
:props="{
|
|
children: 'items',
|
|
}"
|
|
:filter-node-method="filterNode"
|
|
:allow-drop="allowDrop"
|
|
@node-click="clickHandler"
|
|
@node-contextmenu="contextmenu"
|
|
@node-drag-end="handleDragEnd"
|
|
@node-collapse="handleCollapse"
|
|
@node-expand="handleExpand"
|
|
>
|
|
<template #default="{ node, data }">
|
|
<div
|
|
:id="data.id"
|
|
class="cus-tree-node"
|
|
@mousedown="toggleClickFlag"
|
|
@mouseup="toggleClickFlag"
|
|
@mouseenter="highlightHandler(data)"
|
|
:class="{ 'cus-tree-node-hover': canHighlight && data.id === highlightNode?.id }"
|
|
>
|
|
<slot name="layer-node-content" :node="node" :data="data">
|
|
<span>
|
|
{{ `${data.name} (${data.id})` }}
|
|
</span>
|
|
</slot>
|
|
</div>
|
|
</template>
|
|
</el-tree>
|
|
|
|
<teleport to="body">
|
|
<layer-menu ref="menu"></layer-menu>
|
|
</teleport>
|
|
</el-scrollbar>
|
|
</template>
|
|
|
|
<script lang="ts">
|
|
import { computed, defineComponent, inject, Ref, ref, watchEffect } from 'vue';
|
|
import type { ElTree } from 'element-plus';
|
|
import { throttle } from 'lodash-es';
|
|
|
|
import type { Id, MNode, MPage } from '@tmagic/schema';
|
|
import { MContainer, NodeType } from '@tmagic/schema';
|
|
import StageCore from '@tmagic/stage';
|
|
|
|
import type { EditorService } from '../../services/editor';
|
|
import type { Services } from '../../type';
|
|
import { Layout } from '../../type';
|
|
|
|
import LayerMenu from './LayerMenu.vue';
|
|
|
|
const throttleTime = 150;
|
|
|
|
const select = async (data: MNode, editorService?: EditorService) => {
|
|
if (!data.id) {
|
|
throw new Error('没有id');
|
|
}
|
|
|
|
await editorService?.select(data);
|
|
editorService?.get<StageCore>('stage')?.select(data.id);
|
|
};
|
|
|
|
const highlight = (data: MNode, editorService?: EditorService) => {
|
|
if (!data?.id) {
|
|
throw new Error('没有id');
|
|
}
|
|
editorService?.highlight(data);
|
|
editorService?.get<StageCore>('stage')?.highlight(data.id);
|
|
};
|
|
|
|
const useDrop = (tree: Ref<InstanceType<typeof ElTree> | undefined>, editorService?: EditorService) => ({
|
|
allowDrop: (draggingNode: any, dropNode: any, type: string): boolean => {
|
|
const { data } = dropNode || {};
|
|
const { data: ingData } = draggingNode;
|
|
|
|
const { type: ingType } = ingData;
|
|
|
|
if (ingType !== NodeType.PAGE && data.type === NodeType.PAGE) return false;
|
|
if (ingType === NodeType.PAGE && data.type !== NodeType.PAGE) return false;
|
|
if (!data || !data.type) return false;
|
|
if (['prev', 'next'].includes(type)) return true;
|
|
if (data.items || data.type === 'container') return true;
|
|
|
|
return false;
|
|
},
|
|
|
|
async handleDragEnd(e: any) {
|
|
if (!tree.value) return;
|
|
const { data: node } = e;
|
|
const parent = editorService?.getParentById(node.id, false) as MContainer;
|
|
const layout = await editorService?.getLayout(parent);
|
|
node.style.position = layout;
|
|
if (layout === Layout.RELATIVE) {
|
|
node.style.top = 0;
|
|
node.style.left = 0;
|
|
}
|
|
const { data } = tree.value;
|
|
const [page] = data as [MPage];
|
|
editorService?.update(page);
|
|
},
|
|
});
|
|
|
|
const useStatus = (tree: Ref<InstanceType<typeof ElTree> | undefined>, editorService?: EditorService) => {
|
|
const node = ref<MNode>();
|
|
const page = computed(() => editorService?.get('page'));
|
|
const expandedKeys = new Map<Id, Id>();
|
|
|
|
const expandNodes = () => {
|
|
expandedKeys.forEach((key) => {
|
|
if (!tree.value) return;
|
|
tree.value.getNode(key)?.expand();
|
|
});
|
|
};
|
|
|
|
watchEffect(() => {
|
|
if (!tree.value) return;
|
|
if (!editorService) return;
|
|
node.value = editorService.get('node');
|
|
|
|
if (!node.value) return;
|
|
|
|
tree.value.setCurrentKey(node.value.id, true);
|
|
|
|
const parent = editorService.get('parent');
|
|
if (!parent?.id) return;
|
|
|
|
const treeNode = tree.value.getNode(parent.id);
|
|
treeNode?.updateChildren();
|
|
|
|
setTimeout(() => {
|
|
tree.value &&
|
|
Object.entries(tree.value.store.nodesMap).forEach(([id, node]) => {
|
|
if (node.expanded && node.data.items) {
|
|
expandedKeys.set(id, id);
|
|
}
|
|
});
|
|
expandNodes();
|
|
});
|
|
});
|
|
|
|
return {
|
|
values: computed(() => (page.value ? [page.value] : [])),
|
|
|
|
loadItems: (node: any, resolve: Function) => {
|
|
if (Array.isArray(node.data)) {
|
|
return resolve(node.data);
|
|
}
|
|
if (Array.isArray(node.data?.items)) {
|
|
return resolve(node.data?.items);
|
|
}
|
|
resolve([]);
|
|
},
|
|
|
|
highlightNode: computed(() => editorService?.get('highlightNode')),
|
|
clickNode: node,
|
|
expandedKeys: computed(() => (node.value ? [node.value.id] : [])),
|
|
|
|
handleCollapse: (data: MNode) => {
|
|
expandedKeys.delete(data.id);
|
|
},
|
|
|
|
handleExpand: (data: MNode) => {
|
|
const parent = editorService?.getParentById(data.id);
|
|
if (!parent?.id) return;
|
|
expandedKeys.set(parent.id, parent.id);
|
|
},
|
|
};
|
|
};
|
|
|
|
const useFilter = (tree: Ref<InstanceType<typeof ElTree> | undefined>) => ({
|
|
filterText: ref(''),
|
|
|
|
filterNode: (value: string, data: MNode): boolean => {
|
|
if (!value) {
|
|
return true;
|
|
}
|
|
let name = '';
|
|
if (data.name) {
|
|
name = data.name;
|
|
} else if (data.items) {
|
|
name = 'container';
|
|
}
|
|
return `${data.id}${name}${data.type}`.indexOf(value) !== -1;
|
|
},
|
|
|
|
filterTextChangeHandler(val: string) {
|
|
tree.value?.filter(val);
|
|
},
|
|
});
|
|
|
|
export default defineComponent({
|
|
name: 'magic-editor-layer-panel',
|
|
|
|
components: { LayerMenu },
|
|
|
|
setup() {
|
|
const services = inject<Services>('services');
|
|
const tree = ref<InstanceType<typeof ElTree>>();
|
|
const menu = ref<InstanceType<typeof LayerMenu>>();
|
|
const clicked = ref(false);
|
|
const editorService = services?.editorService;
|
|
const highlightHandler = throttle((data: MNode) => {
|
|
highlight(data, editorService);
|
|
}, throttleTime);
|
|
|
|
const toggleClickFlag = () => {
|
|
clicked.value = !clicked.value;
|
|
};
|
|
|
|
const statusData = useStatus(tree, editorService);
|
|
const canHighlight = computed(
|
|
() => statusData.highlightNode.value?.id !== statusData.clickNode.value?.id && !clicked.value,
|
|
);
|
|
|
|
editorService?.on('remove', () => {
|
|
setTimeout(() => {
|
|
tree.value?.getNode(editorService.get('node').id)?.updateChildren();
|
|
}, 0);
|
|
});
|
|
|
|
return {
|
|
tree,
|
|
menu,
|
|
...statusData,
|
|
...useDrop(tree, editorService),
|
|
...useFilter(tree),
|
|
|
|
highlightHandler,
|
|
toggleClickFlag,
|
|
canHighlight,
|
|
|
|
clickHandler(data: MNode): void {
|
|
if (services?.uiService.get<boolean>('uiSelectMode')) {
|
|
document.dispatchEvent(new CustomEvent('ui-select', { detail: data }));
|
|
return;
|
|
}
|
|
tree.value?.setCurrentKey(data.id);
|
|
select(data, editorService);
|
|
},
|
|
|
|
async contextmenu(event: MouseEvent, data: MNode) {
|
|
event.preventDefault();
|
|
await select(data, editorService);
|
|
menu.value?.show(event);
|
|
},
|
|
};
|
|
},
|
|
});
|
|
</script>
|