Skip to content

Commit 5ed9a03

Browse files
committed
feat: add parent node menu
1 parent ba609dd commit 5ed9a03

2 files changed

Lines changed: 143 additions & 17 deletions

File tree

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

Lines changed: 86 additions & 5 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,
@@ -84,6 +85,8 @@ export default {
8485
IconRight: iconRight()
8586
},
8687
setup(props, { emit }) {
88+
const { multiSelectedStates, areSiblingNodes, batchAddParent, groupAddParent } = useMultiSelect()
89+
8790
const menus = ref([
8891
{ name: '修改属性', code: 'config' },
8992
{
@@ -116,6 +119,51 @@ export default {
116119
{ name: '绑定事件', code: 'bindEvent' }
117120
])
118121
122+
// 多选菜单
123+
const multiSelectMenus = ref([
124+
{ name: '删除', code: 'multiDel' },
125+
{ name: '复制', code: 'multiCopy' },
126+
{
127+
name: '添加父级',
128+
items: [
129+
{
130+
name: '批量添加-文字提示',
131+
code: 'batchWrap',
132+
value: 'TinyTooltip'
133+
},
134+
{
135+
name: '批量添加-弹出框',
136+
code: 'batchWrap',
137+
value: 'TinyPopover'
138+
},
139+
{
140+
name: '批量添加-容器',
141+
code: 'batchWrap',
142+
value: 'div'
143+
},
144+
{
145+
name: '整体添加-文字提示',
146+
code: 'groupWrap',
147+
value: 'TinyTooltip',
148+
check: () => areSiblingNodes()
149+
},
150+
{
151+
name: '整体添加-弹出框',
152+
code: 'groupWrap',
153+
value: 'TinyPopover',
154+
check: () => areSiblingNodes()
155+
},
156+
{
157+
name: '整体添加-容器',
158+
code: 'groupWrap',
159+
value: 'div',
160+
check: () => areSiblingNodes()
161+
}
162+
],
163+
code: 'multiAddParent'
164+
}
165+
])
166+
119167
// 通过画布右键快捷新建区块
120168
const { SaveNewBlock } = getMergeMeta('engine.plugins.blockmanage')?.components || {}
121169
if (SaveNewBlock) {
@@ -132,14 +180,26 @@ export default {
132180
}
133181
})
134182
135-
const filteredMenus = computed(() =>
136-
menus.value.filter((item) => {
183+
const isMultiSelect = computed(() => multiSelectedStates.value.length > 1)
184+
185+
const filteredMenus = computed(() => {
186+
// 如果是多选,则展示多选菜单
187+
if (isMultiSelect.value) {
188+
return multiSelectMenus.value.filter((item) => {
189+
if (typeof item.show === 'function') {
190+
return item.show()
191+
}
192+
return true
193+
})
194+
}
195+
196+
return menus.value.filter((item) => {
137197
if (typeof item.show === 'function') {
138198
return item.show()
139199
}
140200
return true
141201
})
142-
)
202+
})
143203
144204
const boxVisibility = ref(false)
145205
@@ -152,6 +212,14 @@ export default {
152212
copy() {
153213
copyNode(getCurrent().schema?.id)
154214
},
215+
multiDel() {
216+
const ids = multiSelectedStates.value.map((state) => state.id)
217+
ids.forEach((id) => removeNodeById(id))
218+
},
219+
multiCopy() {
220+
const ids = multiSelectedStates.value.map((state) => state.id)
221+
ids.forEach((id) => copyNode(id))
222+
},
155223
config() {
156224
useLayout().activeSetting('props')
157225
},
@@ -216,6 +284,14 @@ export default {
216284
parent.children.splice(index, 1, wrapSchema)
217285
getController().addHistory()
218286
},
287+
// 处理批量添加父级的操作
288+
batchWrap({ value }) {
289+
batchAddParent(value)
290+
},
291+
// 处理整体添加父级的操作
292+
groupWrap({ value }) {
293+
groupAddParent(value)
294+
},
219295
createBlock() {
220296
if (useCanvas().isSaved()) {
221297
boxVisibility.value = true
@@ -238,8 +314,13 @@ export default {
238314
return true
239315
}
240316
241-
const actions = ['del', 'copy', 'addParent']
242-
return actions.includes(actionItem.code) && !getCurrent().schema?.id
317+
if (isMultiSelect.value) {
318+
const multiSelectActions = ['multiDel', 'multiCopy', 'multiAddParent']
319+
return multiSelectActions.includes(actionItem.code) && multiSelectedStates.value.length === 0
320+
} else {
321+
const actions = ['del', 'copy', 'addParent']
322+
return actions.includes(actionItem.code) && !getCurrent().schema?.id
323+
}
243324
}
244325
245326
const onShowChildrenMenu = (menuItem) => {

packages/canvas/container/src/composables/useMultiSelect.ts

Lines changed: 57 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { ref } from 'vue'
2+
import { useCanvas, useMessage } from '@opentiny/tiny-engine-meta-register'
23
import { getDocument, getRect, querySelectById, getController } from '../container'
34

45
interface Schema {
@@ -81,20 +82,37 @@ export const useMultiSelect = () => {
8182
}
8283

8384
/**
84-
* 判断选中的节点是否都是兄弟节点
85-
* @returns {boolean} 如果所有选中节点都有相同的父节点,返回true;否则返回false
85+
* 判断选中的节点是否都是兄弟节点且是连续的
86+
* @returns {boolean} 如果所有选中节点都有相同的父节点且在父节点的children中是连续的,返回true;否则返回false
8687
*/
8788
const areSiblingNodes = (): boolean => {
88-
if (multiSelectedStates.value.length <= 1) {
89-
return false
90-
}
89+
if (multiSelectedStates.value.length <= 1) return false
90+
91+
// 获取第一个节点的父节点
92+
const firstNode = multiSelectedStates.value[0]
93+
const { parent: firstParent } = useCanvas().getNodeWithParentById(firstNode.id) || {}
94+
if (!firstParent) return false
9195

92-
// 获取所有节点的schema
93-
const schemas = multiSelectedStates.value.map((state) => state.schema)
96+
const parentId = firstParent.id
9497

9598
// 检查所有节点是否有相同的父节点
96-
const parentId = schemas[0]?.parent?.id
97-
return !!parentId && schemas.every((schema) => schema?.parent?.id === parentId)
99+
for (let i = 1; i < multiSelectedStates.value.length; i++) {
100+
const { parent } = useCanvas().getNodeWithParentById(multiSelectedStates.value[i].id) || {}
101+
if (!parent || parent.id !== parentId) {
102+
return false
103+
}
104+
}
105+
106+
// 收集所有节点的索引
107+
const nodeIds = multiSelectedStates.value.map((node) => node.id)
108+
const nodeIndices = nodeIds
109+
.map((id) => firstParent.children.findIndex((child) => child.id === id))
110+
.sort((a, b) => a - b)
111+
112+
// 检查是否是连续的兄弟节点
113+
const isConsecutive = nodeIndices.every((val, i, arr) => i === 0 || val === arr[i - 1] + 1)
114+
115+
return isConsecutive
98116
}
99117

100118
/**
@@ -109,7 +127,7 @@ export const useMultiSelect = () => {
109127
}
110128

111129
const firstState = multiSelectedStates.value[0]
112-
const { parent } = getController().findById(firstState.id) || {}
130+
const { parent } = useCanvas().getNodeWithParentById(firstState.id) || {}
113131

114132
if (!parent) {
115133
return false
@@ -123,6 +141,13 @@ export const useMultiSelect = () => {
123141
.map((id) => parent.children.findIndex((child: Schema) => child.id === id))
124142
.sort((a, b) => a - b)
125143

144+
// 检查索引是否连续
145+
for (let i = 1; i < indices.length; i++) {
146+
if (indices[i] !== indices[i - 1] + 1) {
147+
return false
148+
}
149+
}
150+
126151
// 从父节点中移除这些节点
127152
const selectedNodes: Schema[] = []
128153
indices.reverse().forEach((index) => {
@@ -177,6 +202,11 @@ export const useMultiSelect = () => {
177202
parent.children.splice(indices[0], 0, wrapSchema)
178203

179204
getController().addHistory()
205+
useMessage().publish({ topic: 'schemaChange', data: {} })
206+
setTimeout(() => {
207+
useCanvas().canvasApi.value?.updateRect?.()
208+
}, 0)
209+
180210
return true
181211
}
182212

@@ -254,22 +284,37 @@ export const useMultiSelect = () => {
254284
}
255285

256286
// 对每个选中的节点分别添加父级
287+
let modified = false
288+
257289
multiSelectedStates.value.forEach(({ schema, parent }) => {
258290
if (!schema || !parent) {
259291
return
260292
}
261293

262294
const index = parent.children.findIndex((child) => child.id === schema.id)
295+
if (index === -1) {
296+
return
297+
}
263298

264299
// 创建包装组件的模板
265300
const wrapSchema = createWrapperSchema(componentName, props, schema)
266301

267302
// 替换原节点
268303
parent.children.splice(index, 1, wrapSchema)
304+
modified = true
269305
})
270306

271-
getController().addHistory()
272-
return true
307+
if (modified) {
308+
getController().addHistory()
309+
useMessage().publish({ topic: 'schemaChange', data: {} })
310+
setTimeout(() => {
311+
useCanvas().canvasApi.value?.updateRect?.()
312+
}, 0)
313+
314+
return true
315+
}
316+
317+
return false
273318
}
274319

275320
return {

0 commit comments

Comments
 (0)