123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759 |
- <template>
- <div class="elements props" v-show="group === '图层'">
- <div
- class="flex mt-16 mb-16"
- style="justify-content: end; padding-right: 8px"
- >
- <t-tooltip content="置顶" placement="top">
- <div class="icon-box" @click="changeActivLayer('top')">
- <BacktopIcon />
- </div>
- </t-tooltip>
- <t-tooltip content="置底" placement="top">
- <div class="icon-box" @click="changeActivLayer('bottom')">
- <Download1Icon />
- </div>
- </t-tooltip>
- <t-tooltip content="上一层" placement="top">
- <div class="icon-box" @click="changeActivLayer('up')">
- <ArrowUpIcon />
- </div>
- </t-tooltip>
- <t-tooltip content="下一层" placement="top">
- <div class="icon-box" @click="changeActivLayer('down')">
- <ArrowDownIcon />
- </div>
- </t-tooltip>
- <t-tooltip :content="data.expandAll ? '折叠' : '展开'" placement="top">
- <div class="icon-box" @click="changeExpand">
- <MenuFoldIcon v-if="data.expandAll" />
- <MenuUnfoldIcon v-else />
- </div>
- </t-tooltip>
- </div>
- <t-tree
- class="flex-grow"
- ref="tree"
- activeMultiple
- :data="data.tree"
- :actived="data.actived"
- v-model:expanded="data.expanded"
- activable
- :expand-parent="true"
- style="padding: 0 4px 8px 8px"
- :scroll="{
- // rowHeight: 34,
- bufferSize: 20,
- threshold: 40,
- type: 'virtual',
- }"
- >
- <template #label="{ node }: any">
- <div class="flex middle" :class="{ gray: node.data.visible === false }">
- <template
- v-if="node.getChildren() || node.data.value.endsWith('Layer')"
- >
- <folder-open-icon class="mr-8" v-if="node.expanded" />
- <folder-icon class="mr-8" v-else />
- </template>
- <!-- <t-tooltip v-else-if="node.data.tag === 'dom'" content="DOM元素">
- <Code1Icon />
- </t-tooltip> -->
- <t-tooltip v-else :content="node.data.tag === 'dom' ? 'DOM元素' : ''">
- <control-platform-icon class="mr-8" />
- </t-tooltip>
- <t-input
- v-if="node.data.edited"
- v-model="node.data.label"
- :autofocus="true"
- style="width: 100px"
- @blur="onDescription(node)"
- @enter="onDescription(node)"
- />
- <span
- v-else
- style="width: 100px"
- @click="onActive($event, node.value)"
- @dblclick="ondblclick(node)"
- @contextmenu="oncontextmenu($event,node)"
- >
- {{ node.label }}
- </span>
- </div>
- </template>
- <template #operations="{ node }: any">
- <div
- class="flex middle operations"
- :class="{
- gray: node.data.visible === false,
- show: node.data.visible === false || node.data.locked,
- }"
- style="width: 46px; height: 16px"
- >
- <template v-if="!node.data.value.endsWith('Layer')">
- <t-tooltip
- class="mr-8"
- v-if="!node.data.locked"
- content="可编辑"
- placement="top"
- >
- <svg class="l-icon" aria-hidden="true" @click="lock(node, 1)">
- <use xlink:href="#l-unlock"></use>
- </svg>
- </t-tooltip>
- <t-tooltip
- class="mr-8"
- v-else-if="node.data.locked == 1"
- content="禁止编辑"
- placement="top"
- >
- <svg class="l-icon" aria-hidden="true" @click="lock(node, 2)">
- <use xlink:href="#l-lock"></use>
- </svg>
- </t-tooltip>
- <t-tooltip
- class="mr-8"
- v-else-if="node.data.locked == 2"
- content="禁止编辑和移动"
- placement="top"
- >
- <svg class="l-icon" aria-hidden="true" @click="lock(node, 10)">
- <use xlink:href="#l-wufayidong"></use>
- </svg>
- </t-tooltip>
- <t-tooltip
- class="mr-8"
- v-else-if="node.data.locked == 10"
- content="禁止所有事件"
- placement="top"
- >
- <svg class="l-icon" aria-hidden="true" @click="lock(node, 0)">
- <use xlink:href="#l-jinyong"></use>
- </svg>
- </t-tooltip>
- </template>
- <span v-else style="width: 24px"></span>
- <browse-icon
- v-if="node.data.visible !== false"
- @click="visible(node, false)"
- />
- <browse-off-icon v-else @click="visible(node, true)" />
- </div>
- </template>
- </t-tree>
- </div>
- <div class="elements" v-show="group === '分组'">
- <div class="groups-panel" style="padding: 8px 0">
- <div class="flex middle between" style="padding: 0 12px">
- <div class="title">分组</div>
- <t-tooltip content="新建分组" placement="top">
- <div class="icon-box" @click="addGroup">
- <AddIcon />
- </div>
- </t-tooltip>
- </div>
- <div class="groups">
- <div
- v-for="(item, i) in data.groups"
- class="flex middle between hover"
- :class="{ primary: i == data.activedGroup }"
- >
- <span
- class="flex-grow"
- v-if="i != data.editedGroup"
- @click="activeGroup(i)"
- @dblclick="
- data.activedGroup = data.editedGroup = i;
- data.group = item;
- "
- >
- {{ item }}
- </span>
- <t-input
- v-else
- v-model="data.group"
- :autofocus="true"
- @blur="setGroup"
- @enter="setGroup"
- />
- <browse-icon
- v-if="!data.hiddenGroups.includes(item)"
- @click="visibleGroup(item, false)"
- />
- <browse-off-icon v-else @click="visibleGroup(item, true)" />
- <t-popconfirm
- content="确认删除该分组吗?"
- @confirm="delGroup"
- @cancel="data.deleteGroup = undefined"
- >
- <delete-icon
- class="ml-8"
- :class="{ block: i == data.deleteGroup }"
- @click="data.deleteGroup = i"
- />
- </t-popconfirm>
- </div>
- </div>
- </div>
- </div>
- </template>
- <script lang="ts" setup>
- import { onBeforeMount, onMounted, onBeforeUnmount, reactive, ref } from 'vue';
- import { MessagePlugin } from 'tdesign-vue-next';
- import { LockState, Pen, isDomShapes } from '@meta2d/core';
- import { getPenTree, inTreePanel, setChildrenVisible } from '@/services/common';
- import {
- FolderOpenIcon,
- FolderIcon,
- ControlPlatformIcon,
- BrowseIcon,
- BrowseOffIcon,
- DeleteIcon,
- AddIcon,
- Code1Icon,
- BacktopIcon,
- Download1Icon,
- ArrowDownIcon,
- ArrowUpIcon,
- MenuUnfoldIcon,
- MenuFoldIcon,
- } from 'tdesign-icons-vue-next';
- const props = defineProps<{
- group: string;
- }>();
- const tree = ref<any>(null);
- const data = reactive<any>({
- tree: [],
- actived: [],
- groups: [],
- hiddenGroups: [],
- expandAll: false,
- expanded: [],
- });
- onMounted(() => {
- meta2d.on('opened', getTree);
- meta2d.on('add', getTree);
- meta2d.on('undo', getTree);
- meta2d.on('redo', getTree);
- meta2d.on('delete', getTree);
- meta2d.on('combine', getTree);
- meta2d.on('click', getActived);
- meta2d.on('paste', getActived);
- meta2d.on('layer', layerChange);
- meta2d.on('active', getActived);
- if (inTreePanel.timer) {
- clearTimeout(inTreePanel.timer);
- inTreePanel.timer = undefined;
- }
- inTreePanel.value = true;
- getTree();
- getActived();
- const d = meta2d.store.data as any;
- if (!d.groups) {
- d.groups = [];
- }
- data.groups = d.groups;
- getHiddenGroups();
- });
- const getTree = () => {
- data.tree = getPhysicalTree();
- };
- const getPhysicalTree = () => {
- const pens = meta2d.store.data.pens;
- const imageLayer = [];
- const mainLayer = [];
- const imageBottomLayer = [];
- const templateLayer = [];
- const doms = [];
- for (let i = pens.length - 1; i >= 0; i--) {
- if (pens[i].parentId) {
- continue;
- }
- if (
- pens[i].name.endsWith('Dom') ||
- isDomShapes.includes(pens[i].name) ||
- meta2d.store.options.domShapes.includes(pens[i].name)
- ) {
- //dom图元根据层级来决定
- const node = calcElem(pens[i]);
- node.tag = 'dom';
- doms.push(node);
- } else if (pens[i].canvasLayer === 1) {
- //模版层
- templateLayer.push(calcElem(pens[i]));
- } else if (pens[i].name === 'image' || pens[i].image) {
- if (pens[i].canvasLayer === 2) {
- //底层图片绘制层
- imageBottomLayer.push(calcElem(pens[i]));
- } else if (pens[i].canvasLayer === 3) {
- //主画布层
- mainLayer.push(calcElem(pens[i]));
- } else {
- //上层图片绘制层
- imageLayer.push(calcElem(pens[i]));
- }
- } else {
- //主画布层
- mainLayer.push(calcElem(pens[i]));
- }
- }
- const list = [
- {
- label: '上层图片层', //4
- value: 'imageLayer',
- children: imageLayer,
- zIndex: 3.5,
- },
- {
- label: '主画布层', //3
- value: 'mainLayer',
- children: mainLayer,
- zIndex: 2.5,
- },
- {
- label: '底层图片层', //2
- value: 'imageBottomLayer',
- children: imageBottomLayer,
- zIndex: 1.5,
- },
- {
- label: '模版层', //1
- value: 'templateLayer',
- children: templateLayer,
- zIndex: 0.5,
- },
- ];
- list.push(...doms);
- list.sort((a, b) => b.zIndex - a.zIndex);
- return list;
- };
- const getPenLayer = (pen: Pen) => {
- if (
- pen.name.endsWith('Dom') ||
- isDomShapes.includes(pen.name) ||
- meta2d.store.options.domShapes.includes(pen.name)
- ) {
- return 'dom';
- } else if (pen.canvasLayer === 1) {
- //模版层
- return 'templateLayer';
- } else if (pen.name === 'image' || pen.image) {
- if (pen.canvasLayer === 2) {
- //底层图片绘制层
- return 'imageBottomLayer';
- } else if (pen.canvasLayer === 3) {
- //主画布层
- return 'mainLayer';
- } else {
- //上层图片绘制层
- return 'imageLayer';
- }
- } else {
- //主画布层
- return 'mainLayer';
- }
- };
- const layerChange = () => {
- getTree();
- // setTimeout(() => {
- // if (data.actived && data.actived.length) {
- // const element = document.body.querySelector(
- // `[data-value="${data.actived[0]}"]`
- // );
- // const layer = getPenLayer(meta2d.store.active[0]);
- // data.expanded = [layer];
- // if (element) {
- // element.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
- // } else {
- // let index = data.tree.findIndex((item) => item.value === layer);
- // let innerIndex = data.tree[index]?.children.findIndex(
- // (item) => item.value === meta2d.store.active[0].id
- // );
- // setTimeout(() => {
- // tree.value?.scrollToElement({ index: index + innerIndex });
- // }, 500);
- // }
- // }
- // }, 500);
- };
- const changeActivLayer = (key: string) => {
- if (meta2d.store.active && meta2d.store.active.length) {
- meta2d[key]();
- } else {
- MessagePlugin.info('请先选中图元');
- }
- };
- const changeExpand = () => {
- data.expandAll = !data.expandAll;
- if (data.expandAll) {
- data.expanded = data.tree.map((item) => item.value);
- } else {
- data.expanded = [];
- }
- };
- const getHiddenGroups = () => {
- data.groups.forEach((item) => {
- if (
- meta2d.store.data.pens.some(
- (pen) =>
- !pen.parentId && pen.tags?.includes(item) && pen.visible === false
- )
- ) {
- data.hiddenGroups.push(item);
- }
- });
- };
- let getActiveFlag = false; //点击画布导致的active还是结构中选择导致的active
- const getActived = () => {
- if (getActiveFlag) {
- getActiveFlag = false;
- return;
- }
- //TODO加个异步?等展开完成后再滚动
- data.actived = [];
- if (meta2d.store.active && meta2d.store.active.length) {
- for (const pen of meta2d.store.active) {
- data.actived.push(pen.id);
- }
- const element = document.body.querySelector(
- `[data-value="${data.actived[0]}"]`
- );
- const layer = getPenLayer(meta2d.store.active[0]);
- data.expanded = [layer];
- if (element) {
- // console.log("element",element);
- element.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
- } else {
- // setTimeout(() => {
- // const element = document.body.querySelector(
- // `[data-value="${data.actived[0]}"]`
- // );
- // element && element.scrollIntoView({ block: 'center' });
- // }, 500);
- // let pens = meta2d.store.data.pens.filter((pen) => !pen.parentId);
- // let index = pens.findIndex((item) => item.id === meta2d.store.active[0].id);
- // let index = data.tree.findIndex(
- // (item) => item.value === meta2d.store.active[0].id
- // );
- let index = data.tree.findIndex((item) => item.value === layer);
- let innerIndex = data.tree[index]?.children.findIndex(
- (item) => item.value === meta2d.store.active[0].id
- );
- // tree.value?.scrollTo()
- // console.log("index",index,innerIndex,data.tree);
- setTimeout(() => {
- tree.value?.scrollToElement({ index: index + innerIndex });
- }, 500);
- }
- }
- };
- const calcElem = (node: Pen) => {
- if (!node) {
- return;
- }
- const elem: any = {
- label: (node as any).description || node.name,
- value: node.id,
- locked: node.locked,
- visible: node.visible,
- tag: (node as any).tag,
- zIndex: node.calculative.zIndex !== undefined ? node.calculative.zIndex : 5,
- };
- if (!node.children) {
- return elem;
- }
- elem.children = [];
- for (const id of node.children) {
- const child = calcElem(meta2d.store.pens[id]);
- child && elem.children.push(child);
- }
- return elem;
- };
- const onActive = (e,value: any) => {
- if (!value || value.endsWith('Layer')) {
- return;
- }
- if(e.ctrlKey){
- if(data.actived.includes(value)){
- data.actived = data.actived.filter((item) => item !== value);
- }else{
- data.actived.push(value);
- }
- }else{
- data.actived = [value];
- }
- getActiveFlag = true;
- if(data.actived.length > 1){
- let pens = [];
- data.actived.forEach((item) => {
- pens.push(meta2d.store.pens[item]);
- });
- meta2d.active(pens, true);
- }else{
- const pen = meta2d.store.pens[value];
- meta2d.active([pen], true);
- if (!pen.calculative?.inView) {
- meta2d.gotoView(pen);
- meta2d.resize();
- }
- }
- meta2d.render();
- };
- const ondblclick = (node: any) => {
- if (node.data.value.endsWith('Layer')) {
- return;
- } else {
- node.data.edited = true;
- }
- };
- const oncontextmenu = (ev,node: any) => {
- ev.preventDefault();
- ev.stopPropagation();
- let id = node.data.value;
- if(meta2d.store.active[0].id !== id){
- return;
- }
- let e = {
- clientX:ev.clientX ,
- clientY:ev.clientY
- }
- meta2d.emit('contextmenu',{e})
- }
- const lock = (node: any, v: LockState) => {
- node.data.locked = v;
- meta2d.setValue({
- id: node.value,
- locked: v,
- });
- };
- const visible = (node: any, v: boolean) => {
- node.data.visible = v;
- if (node.data.value.endsWith('Layer')) {
- if (node.data.value === 'imageLayer') {
- meta2d.canvas.canvasImage.canvas.style.display = v ? 'block' : 'none';
- } else if (node.data.value === 'mainLayer') {
- meta2d.canvas.canvas.style.display = v ? 'block' : 'none';
- } else if (node.data.value === 'imageBottomLayer') {
- meta2d.canvas.canvasImageBottom.canvas.style.display = v
- ? 'block'
- : 'none';
- } else if (node.data.value === 'templateLayer') {
- meta2d.canvas.canvasTemplate.canvas.style.display = v ? 'block' : 'none';
- }
- return;
- }
- setChildrenVisible(node, v);
- const pen = meta2d.findOne(node.value);
- pen && meta2d.setVisible(pen, v);
- meta2d.render();
- };
- const visibleGroup = (item, v: boolean) => {
- if (v) {
- let index = data.hiddenGroups.indexOf(item);
- if (index !== -1) {
- data.hiddenGroups.splice(index, 1);
- }
- } else {
- data.hiddenGroups.push(item);
- }
- let pens = meta2d.store.data.pens.filter(
- (pen) => !pen.parentId && pen.tags?.includes(item)
- );
- pens.forEach((pen) => {
- meta2d.setValue(
- { id: pen.id, visible: v },
- { render: false, doEvent: false }
- );
- });
- meta2d.render();
- };
- const onDescription = (node: any) => {
- node.data.edited = false;
- node.setData({ label: node.data.label });
- meta2d.setValue({
- id: node.value,
- description: node.data.label,
- });
- };
- const addGroup = () => {
- const i = data.groups.length + 1;
- data.group = '组' + i;
- data.groups.push(data.group);
- data.activedGroup = data.editedGroup = i;
- };
- const activeGroup = (i: number) => {
- data.activedGroup = i;
- const group = data.groups[i];
- const pens: Pen[] = [];
- for (const item of meta2d.store.data.pens) {
- if (item.tags?.includes(group)) {
- pens.push(item);
- }
- }
- meta2d.active(pens, false);
- meta2d.render();
- };
- const setGroup = () => {
- if (data.groups[data.editedGroup] === data.group) {
- data.editedGroup = undefined;
- return;
- }
- if (data.groups.includes(data.group)) {
- MessagePlugin.error('已经存在相同分组!');
- return;
- }
- for (const item of meta2d.store.data.pens) {
- // @ts-ignore
- if (item.group === data.groups[data.editedGroup]) {
- // @ts-ignore
- item.group === data.group;
- }
- }
- data.groups[data.editedGroup] = data.group;
- data.editedGroup = undefined;
- };
- const delGroup = () => {
- for (const item of meta2d.store.data.pens) {
- // @ts-ignore
- if (item.group === data.groups[data.deleteGroup]) {
- // @ts-ignore
- delete item.group;
- }
- }
- data.groups.splice(data.deleteGroup, 1);
- data.deleteGroup = undefined;
- };
- onBeforeUnmount(() => {
- meta2d.off('opened', getTree);
- meta2d.off('add', getTree);
- meta2d.off('undo', getTree);
- meta2d.off('redo', getTree);
- meta2d.off('delete', getTree);
- meta2d.off('combine', getTree);
- meta2d.off('click', getActived);
- meta2d.off('paste', getActived);
- meta2d.off('layer', layerChange);
- meta2d.off('active', getActived);
- inTreePanel.timer = setTimeout(() => {
- inTreePanel.value = false;
- }, 500);
- });
- </script>
- <style lang="postcss" scoped>
- .elements {
- display: flex;
- flex-direction: column;
- height: calc(100vh - 90px);
- background: var(--color-background-active);
- & > * {
- overflow-y: auto;
- width: 100%;
- }
- .t-tree {
- /* .t-tag {
- background-color: #4583ff33;
- position: absolute;
- right: 45px;
- width: 45.6px;
- height: 21.6px;
- font-size: 10px;
- line-height: 14px;
- transform: scale(0.83333);
- transform-origin: 0 0;
- } */
- }
- .groups-panel {
- flex-shrink: 0;
- height: 100%;
- /* height: calc(100% - 55px); */
- border-top: 1px solid var(--color-border-input);
- padding: 8px 12px;
- }
- .groups {
- height: calc(100% - 55px);
- /* height: calc(100% - 24px); */
- overflow-y: auto;
- padding: 8px;
- & > div {
- height: 32px;
- line-height: 32px;
- padding: 0 8px;
- svg {
- display: none;
- &.block {
- display: block;
- }
- }
- &:hover {
- svg {
- display: block;
- }
- background: var(--color-background-input);
- }
- }
- }
- }
- .icon-box {
- width: 24px;
- height: 24px;
- margin: 4px;
- text-align: center;
- line-height: 24px;
- border-radius: 4px;
- &:hover {
- background: var(--td-brand-color-light);
- }
- .t-icon {
- width: 14px;
- height: 14px;
- }
- }
- </style>
|