Skip to content

Commit 1f02390

Browse files
authored
feat: multi-node right menu (#1248)
1 parent 3b5fa94 commit 1f02390

7 files changed

Lines changed: 387 additions & 41 deletions

File tree

packages/canvas/DesignCanvas/src/DesignCanvas.vue

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
</template>
2323

2424
<script>
25-
import { ref, watch, onUnmounted, onMounted } from 'vue'
25+
import { ref, watch, onUnmounted, onMounted, computed } from 'vue'
2626
import {
2727
useProperties,
2828
useCanvas,
@@ -43,6 +43,7 @@ import {
4343
import { constants } from '@opentiny/tiny-engine-utils'
4444
import * as ast from '@opentiny/tiny-engine-common/js/ast'
4545
import { initCanvas } from '../../init-canvas/init-canvas'
46+
import { useMultiSelect } from '../../container/src/composables/useMultiSelect'
4647
import { getImportMapData } from './importMap'
4748
import meta from '../meta'
4849
@@ -152,6 +153,9 @@ export default {
152153
}
153154
)
154155
156+
const { multiSelectedStates } = useMultiSelect()
157+
const multiStateLength = computed(() => multiSelectedStates.value.length)
158+
155159
const nodeSelected = (node, parent, type, id) => {
156160
const { leftPanelFixed, rightPanelFixed } = getFixedPanelsStatus()
157161
@@ -176,7 +180,9 @@ export default {
176180
177181
// 如果选中的节点是画布,就设置成默认选中最外层schema
178182
useProperties().getProps(schemaItem || pageSchema, parent)
179-
useCanvas().setCurrentSchema(schemaItem || pageSchema)
183+
const multiSchemas = multiSelectedStates.value.map(({ schema }) => schema)
184+
const currentSchema = multiStateLength.value > 1 ? multiSchemas : schemaItem || pageSchema
185+
useCanvas().setCurrentSchema(currentSchema)
180186
footData.value = getNodePath(schemaItem?.id)
181187
toolbars.visiblePopover = false
182188
}

packages/canvas/DesignCanvas/src/api/useCanvas.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,11 @@ const operationTypeMap = {
347347
parentNode.children.splice(index, 1, newNodeData)
348348
}
349349
break
350+
case 'replace':
351+
if (index !== -1) {
352+
parentNode.children.splice(index, 1, newNodeData)
353+
}
354+
break
350355
case 'bottom':
351356
parentNode.children.splice(index + 1, 0, newNodeData)
352357
break

packages/canvas/container/src/CanvasContainer.vue

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,12 @@ export default {
134134
if (element) {
135135
const currentElement = querySelectById(getCurrent().schema?.id)
136136
137+
// 如果是点击右键则打开右键菜单
138+
if (event.button === 2) {
139+
openMenu(event)
140+
return
141+
}
142+
137143
if (!currentElement?.contains(element) || event.button === 0) {
138144
const isCtrlKey = event.ctrlKey || event.metaKey
139145
const loopId = element.getAttribute(NODE_LOOP)
@@ -149,11 +155,6 @@ export default {
149155
150156
dragStart(node, element, { offsetX: clientX - x, offsetY: clientY - y })
151157
}
152-
153-
// 如果是点击右键则打开右键菜单
154-
if (event.button === 2) {
155-
openMenu(event)
156-
}
157158
}
158159
}
159160

packages/canvas/container/src/components/CanvasMenu.vue

Lines changed: 80 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import { ref, reactive, nextTick, computed } from 'vue'
3636
import { canvasState, getConfigure, getController, getCurrent, copyNode, removeNodeById } from '../container'
3737
import { useLayout, useModal, useCanvas, usePage, getMergeMeta } from '@opentiny/tiny-engine-meta-register'
3838
import { iconRight } from '@opentiny/vue-icon'
39+
import { useMultiSelect } from '../composables/useMultiSelect'
3940
4041
const menuState = reactive({
4142
position: null,
@@ -47,6 +48,9 @@ const current = ref(null)
4748
const menuDom = ref(null)
4849
const subMenuStyles = ref(null)
4950
51+
// 子菜单宽度常量
52+
const SUB_MENU_WIDTH = 137
53+
5054
export const closeMenu = () => {
5155
menuState.show = false
5256
current.value = null
@@ -69,11 +73,11 @@ export const openMenu = (event) => {
6973
if (right > canvasRect.right) {
7074
menuState.position.left = `${parseInt(menuState.position.left) - width - 2}px`
7175
}
72-
// sub-menu样式width为100px,少于100宽度的空白区域则放置到左侧
73-
if (right + 100 < canvasRect.right) {
74-
subMenuStyles.value = { right: '-100px' }
76+
// sub-menu样式width为 137 px,少于 137 宽度的空白区域则放置到左侧
77+
if (right + SUB_MENU_WIDTH < canvasRect.right) {
78+
subMenuStyles.value = { right: `-${SUB_MENU_WIDTH}px`, width: `${SUB_MENU_WIDTH}px` }
7579
} else {
76-
subMenuStyles.value = { left: '-100px' }
80+
subMenuStyles.value = { left: `-${SUB_MENU_WIDTH}px`, width: `${SUB_MENU_WIDTH}px` }
7781
}
7882
}
7983
})
@@ -84,6 +88,8 @@ export default {
8488
IconRight: iconRight()
8589
},
8690
setup(props, { emit }) {
91+
const { multiSelectedStates, areSiblingNodes, batchAddParent, groupAddParent } = useMultiSelect()
92+
8793
const menus = ref([
8894
{ name: '修改属性', code: 'config' },
8995
{
@@ -116,10 +122,40 @@ export default {
116122
{ name: '绑定事件', code: 'bindEvent' }
117123
])
118124
125+
// 多选菜单
126+
const multiSelectMenus = ref([
127+
{ name: '删除', code: 'multiDel' },
128+
{ name: '复制', code: 'multiCopy' },
129+
{
130+
name: '添加父级',
131+
items: [
132+
{
133+
name: '容器(批量)',
134+
code: 'batchWrap',
135+
value: 'div'
136+
},
137+
{
138+
name: '容器(公共父级)',
139+
code: 'groupWrap',
140+
value: 'div',
141+
check: () => areSiblingNodes()
142+
},
143+
{
144+
name: '弹出框(公共父级)',
145+
code: 'groupWrap',
146+
value: 'TinyPopover',
147+
check: () => areSiblingNodes()
148+
}
149+
],
150+
code: 'multiAddParent'
151+
}
152+
])
153+
119154
// 通过画布右键快捷新建区块
120155
const { SaveNewBlock } = getMergeMeta('engine.plugins.blockmanage')?.components || {}
121156
if (SaveNewBlock) {
122157
menus.value.push({ name: '新建区块', code: 'createBlock' })
158+
multiSelectMenus.value.push({ name: '新建区块', code: 'createBlock' })
123159
}
124160
125161
menus.value.unshift({
@@ -132,14 +168,26 @@ export default {
132168
}
133169
})
134170
135-
const filteredMenus = computed(() =>
136-
menus.value.filter((item) => {
171+
const isMultiSelect = computed(() => multiSelectedStates.value.length > 1)
172+
173+
const filteredMenus = computed(() => {
174+
// 如果是多选,则展示多选菜单
175+
if (isMultiSelect.value) {
176+
return multiSelectMenus.value.filter((item) => {
177+
if (typeof item.show === 'function') {
178+
return item.show()
179+
}
180+
return true
181+
})
182+
}
183+
184+
return menus.value.filter((item) => {
137185
if (typeof item.show === 'function') {
138186
return item.show()
139187
}
140188
return true
141189
})
142-
)
190+
})
143191
144192
const boxVisibility = ref(false)
145193
@@ -154,6 +202,16 @@ export default {
154202
copy() {
155203
copyNode(getCurrent().schema?.id)
156204
},
205+
multiDel() {
206+
const ids = multiSelectedStates.value.map((state) => state.id)
207+
ids.forEach((id) => removeNodeById(id))
208+
},
209+
multiCopy() {
210+
const ids = multiSelectedStates.value.map((state) => state.id)
211+
ids.forEach((id) => copyNode(id))
212+
213+
useCanvas().canvasApi.value.updateRect?.()
214+
},
157215
config() {
158216
activeSetting(PLUGIN_NAME.Props)
159217
},
@@ -218,6 +276,14 @@ export default {
218276
parent.children.splice(index, 1, wrapSchema)
219277
getController().addHistory()
220278
},
279+
// 处理批量添加父级的操作
280+
batchWrap({ value }) {
281+
batchAddParent(value)
282+
},
283+
// 处理整体添加父级的操作
284+
groupWrap({ value }) {
285+
groupAddParent(value)
286+
},
221287
createBlock() {
222288
if (useCanvas().isSaved()) {
223289
boxVisibility.value = true
@@ -240,8 +306,13 @@ export default {
240306
return true
241307
}
242308
243-
const actions = ['del', 'copy', 'addParent']
244-
return actions.includes(actionItem.code) && !getCurrent().schema?.id
309+
if (isMultiSelect.value) {
310+
const multiSelectActions = ['multiDel', 'multiCopy', 'multiAddParent']
311+
return multiSelectActions.includes(actionItem.code) && multiSelectedStates.value.length === 0
312+
} else {
313+
const actions = ['del', 'copy', 'addParent']
314+
return actions.includes(actionItem.code) && !getCurrent().schema?.id
315+
}
245316
}
246317
247318
const onShowChildrenMenu = (menuItem) => {
@@ -326,7 +397,6 @@ export default {
326397
}
327398
}
328399
&.sub-menu {
329-
width: 100px;
330400
position: absolute;
331401
top: -2px;
332402
}

0 commit comments

Comments
 (0)