Skip to content

feat: multi-node right menu #1248

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Apr 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions packages/canvas/DesignCanvas/src/DesignCanvas.vue
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
</template>

<script>
import { ref, watch, onUnmounted, onMounted } from 'vue'
import { ref, watch, onUnmounted, onMounted, computed } from 'vue'
import {
useProperties,
useCanvas,
Expand All @@ -43,6 +43,7 @@ import {
import { constants } from '@opentiny/tiny-engine-utils'
import * as ast from '@opentiny/tiny-engine-common/js/ast'
import { initCanvas } from '../../init-canvas/init-canvas'
import { useMultiSelect } from '../../container/src/composables/useMultiSelect'
import { getImportMapData } from './importMap'
import meta from '../meta'

Expand Down Expand Up @@ -152,6 +153,9 @@ export default {
}
)

const { multiSelectedStates } = useMultiSelect()
const multiStateLength = computed(() => multiSelectedStates.value.length)

const nodeSelected = (node, parent, type, id) => {
const { leftPanelFixed, rightPanelFixed } = getFixedPanelsStatus()

Expand All @@ -176,7 +180,9 @@ export default {

// 如果选中的节点是画布,就设置成默认选中最外层schema
useProperties().getProps(schemaItem || pageSchema, parent)
useCanvas().setCurrentSchema(schemaItem || pageSchema)
const multiSchemas = multiSelectedStates.value.map(({ schema }) => schema)
const currentSchema = multiStateLength.value > 1 ? multiSchemas : schemaItem || pageSchema
useCanvas().setCurrentSchema(currentSchema)
footData.value = getNodePath(schemaItem?.id)
toolbars.visiblePopover = false
}
Expand Down
5 changes: 5 additions & 0 deletions packages/canvas/DesignCanvas/src/api/useCanvas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,11 @@ const operationTypeMap = {
parentNode.children.splice(index, 1, newNodeData)
}
break
case 'replace':
if (index !== -1) {
parentNode.children.splice(index, 1, newNodeData)
}
break
case 'bottom':
parentNode.children.splice(index + 1, 0, newNodeData)
break
Expand Down
11 changes: 6 additions & 5 deletions packages/canvas/container/src/CanvasContainer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,12 @@ export default {
if (element) {
const currentElement = querySelectById(getCurrent().schema?.id)

// 如果是点击右键则打开右键菜单
if (event.button === 2) {
openMenu(event)
return
}

if (!currentElement?.contains(element) || event.button === 0) {
const isCtrlKey = event.ctrlKey || event.metaKey
const loopId = element.getAttribute(NODE_LOOP)
Expand All @@ -149,11 +155,6 @@ export default {

dragStart(node, element, { offsetX: clientX - x, offsetY: clientY - y })
}

// 如果是点击右键则打开右键菜单
if (event.button === 2) {
openMenu(event)
}
}
}

Expand Down
90 changes: 80 additions & 10 deletions packages/canvas/container/src/components/CanvasMenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import { ref, reactive, nextTick, computed } from 'vue'
import { canvasState, getConfigure, getController, getCurrent, copyNode, removeNodeById } from '../container'
import { useLayout, useModal, useCanvas, usePage, getMergeMeta } from '@opentiny/tiny-engine-meta-register'
import { iconRight } from '@opentiny/vue-icon'
import { useMultiSelect } from '../composables/useMultiSelect'

const menuState = reactive({
position: null,
Expand All @@ -47,6 +48,9 @@ const current = ref(null)
const menuDom = ref(null)
const subMenuStyles = ref(null)

// 子菜单宽度常量
const SUB_MENU_WIDTH = 137

export const closeMenu = () => {
menuState.show = false
current.value = null
Expand All @@ -69,11 +73,11 @@ export const openMenu = (event) => {
if (right > canvasRect.right) {
menuState.position.left = `${parseInt(menuState.position.left) - width - 2}px`
}
// sub-menu样式width为100px,少于100宽度的空白区域则放置到左侧
if (right + 100 < canvasRect.right) {
subMenuStyles.value = { right: '-100px' }
// sub-menu样式width为 137 px,少于 137 宽度的空白区域则放置到左侧
if (right + SUB_MENU_WIDTH < canvasRect.right) {
subMenuStyles.value = { right: `-${SUB_MENU_WIDTH}px`, width: `${SUB_MENU_WIDTH}px` }
} else {
subMenuStyles.value = { left: '-100px' }
subMenuStyles.value = { left: `-${SUB_MENU_WIDTH}px`, width: `${SUB_MENU_WIDTH}px` }
}
}
})
Expand All @@ -84,6 +88,8 @@ export default {
IconRight: iconRight()
},
setup(props, { emit }) {
const { multiSelectedStates, areSiblingNodes, batchAddParent, groupAddParent } = useMultiSelect()

const menus = ref([
{ name: '修改属性', code: 'config' },
{
Expand Down Expand Up @@ -116,10 +122,40 @@ export default {
{ name: '绑定事件', code: 'bindEvent' }
])

// 多选菜单
const multiSelectMenus = ref([
{ name: '删除', code: 'multiDel' },
{ name: '复制', code: 'multiCopy' },
{
name: '添加父级',
items: [
{
name: '容器(批量)',
code: 'batchWrap',
value: 'div'
},
{
name: '容器(公共父级)',
code: 'groupWrap',
value: 'div',
check: () => areSiblingNodes()
},
{
name: '弹出框(公共父级)',
code: 'groupWrap',
value: 'TinyPopover',
check: () => areSiblingNodes()
}
],
code: 'multiAddParent'
}
])

// 通过画布右键快捷新建区块
const { SaveNewBlock } = getMergeMeta('engine.plugins.blockmanage')?.components || {}
if (SaveNewBlock) {
menus.value.push({ name: '新建区块', code: 'createBlock' })
multiSelectMenus.value.push({ name: '新建区块', code: 'createBlock' })
}

menus.value.unshift({
Expand All @@ -132,14 +168,26 @@ export default {
}
})

const filteredMenus = computed(() =>
menus.value.filter((item) => {
const isMultiSelect = computed(() => multiSelectedStates.value.length > 1)

const filteredMenus = computed(() => {
// 如果是多选,则展示多选菜单
if (isMultiSelect.value) {
return multiSelectMenus.value.filter((item) => {
if (typeof item.show === 'function') {
return item.show()
}
return true
})
}

return menus.value.filter((item) => {
if (typeof item.show === 'function') {
return item.show()
}
return true
})
)
})

const boxVisibility = ref(false)

Expand All @@ -154,6 +202,16 @@ export default {
copy() {
copyNode(getCurrent().schema?.id)
},
multiDel() {
const ids = multiSelectedStates.value.map((state) => state.id)
ids.forEach((id) => removeNodeById(id))
},
multiCopy() {
const ids = multiSelectedStates.value.map((state) => state.id)
ids.forEach((id) => copyNode(id))

useCanvas().canvasApi.value.updateRect?.()
},
config() {
activeSetting(PLUGIN_NAME.Props)
},
Expand Down Expand Up @@ -218,6 +276,14 @@ export default {
parent.children.splice(index, 1, wrapSchema)
getController().addHistory()
},
// 处理批量添加父级的操作
batchWrap({ value }) {
batchAddParent(value)
},
// 处理整体添加父级的操作
groupWrap({ value }) {
groupAddParent(value)
},
createBlock() {
if (useCanvas().isSaved()) {
boxVisibility.value = true
Expand All @@ -240,8 +306,13 @@ export default {
return true
}

const actions = ['del', 'copy', 'addParent']
return actions.includes(actionItem.code) && !getCurrent().schema?.id
if (isMultiSelect.value) {
const multiSelectActions = ['multiDel', 'multiCopy', 'multiAddParent']
return multiSelectActions.includes(actionItem.code) && multiSelectedStates.value.length === 0
} else {
const actions = ['del', 'copy', 'addParent']
return actions.includes(actionItem.code) && !getCurrent().schema?.id
}
}

const onShowChildrenMenu = (menuItem) => {
Expand Down Expand Up @@ -326,7 +397,6 @@ export default {
}
}
&.sub-menu {
width: 100px;
position: absolute;
top: -2px;
}
Expand Down
Loading