|
|
|
@ -53,8 +53,8 @@ |
|
|
|
|
</div> |
|
|
|
|
</template> |
|
|
|
|
|
|
|
|
|
<script lang="tsx"> |
|
|
|
|
import { defineComponent, reactive, watchEffect, toRefs } from 'vue'; |
|
|
|
|
<script lang="tsx" setup> |
|
|
|
|
import { ref, watchEffect } from 'vue'; |
|
|
|
|
import { cloneDeep } from 'lodash-es'; |
|
|
|
|
import DraggableTransitionGroup from './draggable-transition-group.vue'; |
|
|
|
|
import CompRender from './comp-render'; |
|
|
|
@ -67,196 +67,176 @@ |
|
|
|
|
import { useModal } from '@/visual-editor/hooks/useModal'; |
|
|
|
|
import { generateNanoid } from '@/visual-editor/utils'; |
|
|
|
|
|
|
|
|
|
export default defineComponent({ |
|
|
|
|
defineOptions({ |
|
|
|
|
name: 'SimulatorEditor', |
|
|
|
|
components: { |
|
|
|
|
DraggableTransitionGroup, |
|
|
|
|
CompRender, |
|
|
|
|
SlotItem, |
|
|
|
|
}, |
|
|
|
|
emits: ['on-selected'], |
|
|
|
|
setup() { |
|
|
|
|
const { currentPage, setCurrentBlock } = useVisualData(); |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
const { globalProperties } = useGlobalProperties(); |
|
|
|
|
const { currentPage, setCurrentBlock } = useVisualData(); |
|
|
|
|
|
|
|
|
|
const state = reactive({ |
|
|
|
|
drag: false, |
|
|
|
|
}); |
|
|
|
|
const { globalProperties } = useGlobalProperties(); |
|
|
|
|
|
|
|
|
|
const drag = ref(false); |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* @description 操作当前页面样式表 |
|
|
|
|
*/ |
|
|
|
|
watchEffect(() => { |
|
|
|
|
const { bgImage, bgColor } = currentPage.value.config; |
|
|
|
|
const bodyStyleStr = ` |
|
|
|
|
/** |
|
|
|
|
* @description 操作当前页面样式表 |
|
|
|
|
*/ |
|
|
|
|
watchEffect(() => { |
|
|
|
|
const { bgImage, bgColor } = currentPage.value.config; |
|
|
|
|
const bodyStyleStr = ` |
|
|
|
|
.simulator-editor-content { |
|
|
|
|
background-color: ${bgColor}; |
|
|
|
|
background-image: url(${bgImage}); |
|
|
|
|
}`; |
|
|
|
|
const styleSheets = document.styleSheets[0]; |
|
|
|
|
const firstCssRule = document.styleSheets[0].cssRules[0]; |
|
|
|
|
const isExistContent = firstCssRule.cssText.includes('.simulator-editor-content'); |
|
|
|
|
if (isExistContent) { |
|
|
|
|
styleSheets.deleteRule(0); |
|
|
|
|
} |
|
|
|
|
styleSheets.insertRule(bodyStyleStr); |
|
|
|
|
}); |
|
|
|
|
const styleSheets = document.styleSheets[0]; |
|
|
|
|
const firstCssRule = document.styleSheets[0].cssRules[0]; |
|
|
|
|
const isExistContent = firstCssRule.cssText.includes('.simulator-editor-content'); |
|
|
|
|
if (isExistContent) { |
|
|
|
|
styleSheets.deleteRule(0); |
|
|
|
|
} |
|
|
|
|
styleSheets.insertRule(bodyStyleStr); |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
//递归实现 |
|
|
|
|
//@leafId 为你要查找的id, |
|
|
|
|
//@nodes 为原始Json数据 |
|
|
|
|
//@path 供递归使用,不要赋值 |
|
|
|
|
const findPathByLeafId = ( |
|
|
|
|
leafId, |
|
|
|
|
nodes: VisualEditorBlockData[] = [], |
|
|
|
|
path: VisualEditorBlockData[] = [], |
|
|
|
|
) => { |
|
|
|
|
for (let i = 0; i < nodes.length; i++) { |
|
|
|
|
const tmpPath = path.concat(); |
|
|
|
|
tmpPath.push(nodes[i]); |
|
|
|
|
if (leafId == nodes[i]._vid) { |
|
|
|
|
return tmpPath; |
|
|
|
|
} |
|
|
|
|
const slots = nodes[i].props?.slots || {}; |
|
|
|
|
const keys = Object.keys(slots); |
|
|
|
|
for (let j = 0; j < keys.length; j++) { |
|
|
|
|
const children = slots[keys[j]]?.children; |
|
|
|
|
if (children) { |
|
|
|
|
const findResult = findPathByLeafId(leafId, children, tmpPath); |
|
|
|
|
if (findResult) { |
|
|
|
|
return findResult; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
//递归实现 |
|
|
|
|
//@leafId 为你要查找的id, |
|
|
|
|
//@nodes 为原始Json数据 |
|
|
|
|
//@path 供递归使用,不要赋值 |
|
|
|
|
const findPathByLeafId = ( |
|
|
|
|
leafId, |
|
|
|
|
nodes: VisualEditorBlockData[] = [], |
|
|
|
|
path: VisualEditorBlockData[] = [], |
|
|
|
|
) => { |
|
|
|
|
for (let i = 0; i < nodes.length; i++) { |
|
|
|
|
const tmpPath = path.concat(); |
|
|
|
|
tmpPath.push(nodes[i]); |
|
|
|
|
if (leafId == nodes[i]._vid) { |
|
|
|
|
return tmpPath; |
|
|
|
|
} |
|
|
|
|
const slots = nodes[i].props?.slots || {}; |
|
|
|
|
const keys = Object.keys(slots); |
|
|
|
|
for (let j = 0; j < keys.length; j++) { |
|
|
|
|
const children = slots[keys[j]]?.children; |
|
|
|
|
if (children) { |
|
|
|
|
const findResult = findPathByLeafId(leafId, children, tmpPath); |
|
|
|
|
if (findResult) { |
|
|
|
|
return findResult; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
// 给当前点击的组件设置聚焦 |
|
|
|
|
const handleSlotsFocus = (block: VisualEditorBlockData, _vid: string) => { |
|
|
|
|
const slots = block.props?.slots || {}; |
|
|
|
|
if (Object.keys(slots).length > 0) { |
|
|
|
|
Object.keys(slots).forEach((key) => { |
|
|
|
|
slots[key]?.children?.forEach((item) => { |
|
|
|
|
item.focusWithChild = false; |
|
|
|
|
item.focus = item._vid == _vid; |
|
|
|
|
if (item.focus) { |
|
|
|
|
const arr = findPathByLeafId(_vid, currentPage.value.blocks); |
|
|
|
|
arr.forEach((n) => (n.focusWithChild = true)); |
|
|
|
|
} |
|
|
|
|
if (Object.keys(item.props?.slots || {}).length) { |
|
|
|
|
handleSlotsFocus(item, _vid); |
|
|
|
|
} |
|
|
|
|
}); |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
// 选择要操作的组件 |
|
|
|
|
const selectComp = (element: VisualEditorBlockData) => { |
|
|
|
|
setCurrentBlock(element); |
|
|
|
|
currentPage.value.blocks.forEach((block) => { |
|
|
|
|
block.focus = element._vid == block._vid; |
|
|
|
|
block.focusWithChild = false; |
|
|
|
|
handleSlotsFocus(block, element._vid); |
|
|
|
|
element.focusWithChild = false; |
|
|
|
|
// 给当前点击的组件设置聚焦 |
|
|
|
|
const handleSlotsFocus = (block: VisualEditorBlockData, _vid: string) => { |
|
|
|
|
const slots = block.props?.slots || {}; |
|
|
|
|
if (Object.keys(slots).length > 0) { |
|
|
|
|
Object.keys(slots).forEach((key) => { |
|
|
|
|
slots[key]?.children?.forEach((item) => { |
|
|
|
|
item.focusWithChild = false; |
|
|
|
|
item.focus = item._vid == _vid; |
|
|
|
|
if (item.focus) { |
|
|
|
|
const arr = findPathByLeafId(_vid, currentPage.value.blocks); |
|
|
|
|
arr.forEach((n) => (n.focusWithChild = true)); |
|
|
|
|
} |
|
|
|
|
if (Object.keys(item.props?.slots || {}).length) { |
|
|
|
|
handleSlotsFocus(item, _vid); |
|
|
|
|
} |
|
|
|
|
}); |
|
|
|
|
}; |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* 删除组件 |
|
|
|
|
*/ |
|
|
|
|
const deleteComp = ( |
|
|
|
|
block: VisualEditorBlockData, |
|
|
|
|
parentBlocks = currentPage.value.blocks, |
|
|
|
|
) => { |
|
|
|
|
console.log(block, 'block'); |
|
|
|
|
const index = parentBlocks.findIndex((item) => item._vid == block._vid); |
|
|
|
|
if (index != -1) { |
|
|
|
|
delete globalProperties.$$refs[parentBlocks[index]._vid]; |
|
|
|
|
const delTarget = parentBlocks.splice(index, 1)[0]; |
|
|
|
|
if (delTarget.focus) { |
|
|
|
|
setCurrentBlock({} as VisualEditorBlockData); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
// 选择要操作的组件 |
|
|
|
|
const selectComp = (element: VisualEditorBlockData) => { |
|
|
|
|
setCurrentBlock(element); |
|
|
|
|
currentPage.value.blocks.forEach((block) => { |
|
|
|
|
block.focus = element._vid == block._vid; |
|
|
|
|
block.focusWithChild = false; |
|
|
|
|
handleSlotsFocus(block, element._vid); |
|
|
|
|
element.focusWithChild = false; |
|
|
|
|
}); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
const onContextmenuBlock = ( |
|
|
|
|
e: MouseEvent, |
|
|
|
|
block: VisualEditorBlockData, |
|
|
|
|
parentBlocks = currentPage.value.blocks, |
|
|
|
|
) => { |
|
|
|
|
$$dropdown({ |
|
|
|
|
reference: e, |
|
|
|
|
content: () => ( |
|
|
|
|
<> |
|
|
|
|
<DropdownOption |
|
|
|
|
label="复制节点" |
|
|
|
|
icon="el-icon-document-copy" |
|
|
|
|
{...{ |
|
|
|
|
onClick: () => { |
|
|
|
|
const index = parentBlocks.findIndex((item) => item._vid == block._vid); |
|
|
|
|
if (index != -1) { |
|
|
|
|
const setBlockVid = (block: VisualEditorBlockData) => { |
|
|
|
|
block._vid = `vid_${generateNanoid()}`; |
|
|
|
|
block.focus = false; |
|
|
|
|
const slots = block?.props?.slots || {}; |
|
|
|
|
const slotKeys = Object.keys(slots); |
|
|
|
|
if (slotKeys.length) { |
|
|
|
|
slotKeys.forEach((slotKey) => { |
|
|
|
|
slots[slotKey]?.children?.forEach((child) => setBlockVid(child)); |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
const blockCopy = cloneDeep(parentBlocks[index]); |
|
|
|
|
setBlockVid(blockCopy); |
|
|
|
|
parentBlocks.splice(index + 1, 0, blockCopy); |
|
|
|
|
/** |
|
|
|
|
* 删除组件 |
|
|
|
|
*/ |
|
|
|
|
const deleteComp = (block: VisualEditorBlockData, parentBlocks = currentPage.value.blocks) => { |
|
|
|
|
console.log(block, 'block'); |
|
|
|
|
const index = parentBlocks.findIndex((item) => item._vid == block._vid); |
|
|
|
|
if (index != -1) { |
|
|
|
|
delete globalProperties.$$refs[parentBlocks[index]._vid]; |
|
|
|
|
const delTarget = parentBlocks.splice(index, 1)[0]; |
|
|
|
|
if (delTarget.focus) { |
|
|
|
|
setCurrentBlock({} as VisualEditorBlockData); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
const onContextmenuBlock = ( |
|
|
|
|
e: MouseEvent, |
|
|
|
|
block: VisualEditorBlockData, |
|
|
|
|
parentBlocks = currentPage.value.blocks, |
|
|
|
|
) => { |
|
|
|
|
$$dropdown({ |
|
|
|
|
reference: e, |
|
|
|
|
content: () => ( |
|
|
|
|
<> |
|
|
|
|
<DropdownOption |
|
|
|
|
label="复制节点" |
|
|
|
|
icon="el-icon-document-copy" |
|
|
|
|
{...{ |
|
|
|
|
onClick: () => { |
|
|
|
|
const index = parentBlocks.findIndex((item) => item._vid == block._vid); |
|
|
|
|
if (index != -1) { |
|
|
|
|
const setBlockVid = (block: VisualEditorBlockData) => { |
|
|
|
|
block._vid = `vid_${generateNanoid()}`; |
|
|
|
|
block.focus = false; |
|
|
|
|
const slots = block?.props?.slots || {}; |
|
|
|
|
const slotKeys = Object.keys(slots); |
|
|
|
|
if (slotKeys.length) { |
|
|
|
|
slotKeys.forEach((slotKey) => { |
|
|
|
|
slots[slotKey]?.children?.forEach((child) => setBlockVid(child)); |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
const blockCopy = cloneDeep(parentBlocks[index]); |
|
|
|
|
setBlockVid(blockCopy); |
|
|
|
|
parentBlocks.splice(index + 1, 0, blockCopy); |
|
|
|
|
} |
|
|
|
|
}, |
|
|
|
|
}} |
|
|
|
|
/> |
|
|
|
|
<DropdownOption |
|
|
|
|
label="查看节点" |
|
|
|
|
icon="el-icon-view" |
|
|
|
|
{...{ |
|
|
|
|
onClick: () => |
|
|
|
|
useModal({ |
|
|
|
|
title: '节点信息', |
|
|
|
|
footer: null, |
|
|
|
|
props: { |
|
|
|
|
width: 600, |
|
|
|
|
}, |
|
|
|
|
}} |
|
|
|
|
/> |
|
|
|
|
<DropdownOption |
|
|
|
|
label="查看节点" |
|
|
|
|
icon="el-icon-view" |
|
|
|
|
{...{ |
|
|
|
|
onClick: () => |
|
|
|
|
useModal({ |
|
|
|
|
title: '节点信息', |
|
|
|
|
footer: null, |
|
|
|
|
props: { |
|
|
|
|
width: 600, |
|
|
|
|
}, |
|
|
|
|
content: () => ( |
|
|
|
|
<MonacoEditor |
|
|
|
|
code={JSON.stringify(block)} |
|
|
|
|
layout={{ width: 530, height: 600 }} |
|
|
|
|
vid={block._vid} |
|
|
|
|
/> |
|
|
|
|
), |
|
|
|
|
}), |
|
|
|
|
}} |
|
|
|
|
/> |
|
|
|
|
<DropdownOption |
|
|
|
|
label="删除节点" |
|
|
|
|
icon="el-icon-delete" |
|
|
|
|
{...{ |
|
|
|
|
onClick: () => deleteComp(block, parentBlocks), |
|
|
|
|
}} |
|
|
|
|
/> |
|
|
|
|
</> |
|
|
|
|
), |
|
|
|
|
}); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
return { |
|
|
|
|
...toRefs(state), |
|
|
|
|
currentPage, |
|
|
|
|
deleteComp, |
|
|
|
|
selectComp, |
|
|
|
|
onContextmenuBlock, |
|
|
|
|
}; |
|
|
|
|
}, |
|
|
|
|
}); |
|
|
|
|
content: () => ( |
|
|
|
|
<MonacoEditor |
|
|
|
|
code={JSON.stringify(block)} |
|
|
|
|
layout={{ width: 530, height: 600 }} |
|
|
|
|
vid={block._vid} |
|
|
|
|
/> |
|
|
|
|
), |
|
|
|
|
}), |
|
|
|
|
}} |
|
|
|
|
/> |
|
|
|
|
<DropdownOption |
|
|
|
|
label="删除节点" |
|
|
|
|
icon="el-icon-delete" |
|
|
|
|
{...{ |
|
|
|
|
onClick: () => deleteComp(block, parentBlocks), |
|
|
|
|
}} |
|
|
|
|
/> |
|
|
|
|
</> |
|
|
|
|
), |
|
|
|
|
}); |
|
|
|
|
}; |
|
|
|
|
</script> |
|
|
|
|
<style lang="scss" scoped> |
|
|
|
|
@import './func.scss'; |
|
|
|
|