Skip to content

Commit bc010f1

Browse files
committed
feat: support outlinetree multiple select
1 parent 5797628 commit bc010f1

File tree

9 files changed

+71
-108
lines changed

9 files changed

+71
-108
lines changed

designer-demo/engine.config.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,6 @@ export default {
44
material: ['/mock/bundle.json'],
55
scripts: [],
66
styles: [],
7-
dslMode: 'vue'
7+
dslMode: 'vue',
8+
selectMode: 'vue'
89
}

packages/canvas/container/src/CanvasContainer.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,8 @@ export default {
121121
const handleNodeInteractions = async (event) => {
122122
const { clientX, clientY } = event
123123
closeMenu()
124-
await updateSelectedNode(event)
124+
const isMultipleSelect = event.ctrlKey || event.metaKey
125+
await updateSelectedNode(event, '', isMultipleSelect)
125126
// TODO: 需要支持多选状态下的拖拽逻辑
126127
const node = selectState.value[0]?.node
127128
@@ -233,7 +234,6 @@ export default {
233234
234235
insertPosition.value = false
235236
insertContainer.value = false
236-
// handleNodeInteractions(event)
237237
target.value = event.target
238238
})
239239

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

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
v-show="hoverState.rect.height && hoverState.rect.width && !hoverState.isInactiveNode"
44
class="canvas-rect common-hover hover"
55
>
6-
<div class="corner-mark-left" @click="handleSelectHoverNode">
6+
<div class="corner-mark-left" @click="(e) => handleSelectHoverNode(e)">
77
{{ hoverState.componentName }}
88
</div>
99
<div v-show="hoverState.configure?.isContainer" class="corner-mark-bottom-right">拖放元素到容器内</div>
@@ -29,7 +29,7 @@ export default {
2929
}
3030
},
3131
setup(props) {
32-
const handleSelectHoverNode = () => {
32+
const handleSelectHoverNode = (e) => {
3333
const node = props.hoverState.node
3434
const element = props.hoverState.element
3535
@@ -38,11 +38,12 @@ export default {
3838
}
3939
4040
const { selectNodeById, updateSelectedNode } = useSelectNode()
41+
const isMultipleSelect = e.ctrlKey || e.metaKey
4142
4243
if (element) {
43-
updateSelectedNode({ target: element })
44+
updateSelectedNode({ target: element }, '', isMultipleSelect)
4445
} else {
45-
selectNodeById(node.id)
46+
selectNodeById(node.id, '', isMultipleSelect)
4647
}
4748
}
4849
@@ -66,6 +67,7 @@ export default {
6667
left: v-bind("hoverState.rect.left + 'px'");
6768
height: v-bind("hoverState.rect.height + 'px'");
6869
width: v-bind("hoverState.rect.width + 'px'");
70+
transition: all 0.15s ease-in-out;
6971
.corner-mark-left {
7072
height: 14px;
7173
top: -14px;

packages/canvas/container/src/container.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -383,7 +383,6 @@ export const scrollToNode = (element?: Element | null) => {
383383
return nextTick()
384384
}
385385

386-
// TODO:
387386
export const updateRect = () => {
388387
const { clearHover } = useHoverNode()
389388
const { updateSelectedRect } = useSelectNode()
@@ -662,10 +661,10 @@ export const dragMove = (event: DragEvent) => {
662661
* @param {*} type
663662
* @returns
664663
*/
665-
export const selectNode = async (id: string, type?: string) => {
664+
export const selectNode = async (id: string, type?: string, isMultipleSelect = false) => {
666665
const { selectNodeById } = useSelectNode()
667666

668-
selectNodeById(id, type || '')
667+
selectNodeById(id, type || '', isMultipleSelect)
669668
}
670669

671670
/**
@@ -833,10 +832,10 @@ export const canvasApi = {
833832

834833
return clearSelect()
835834
},
836-
selectNodeById: (id: string, type: string) => {
835+
selectNodeById: (id: string, type: string, isMultipleSelect = false) => {
837836
const { selectNodeById } = useSelectNode()
838837

839-
return selectNodeById(id, type)
838+
return selectNodeById(id, type, isMultipleSelect)
840839
},
841840
selectNode,
842841
hoverNode,

packages/canvas/container/src/interactions/common.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,13 +110,14 @@ export const hoverNodeById = (id: string, updateHoverNode: (e: MouseEvent) => vo
110110
}
111111

112112
export const selectNodeById = async (
113-
updateSelectedNode: (e: MouseEvent, type: string) => void,
113+
updateSelectedNode: (e: MouseEvent, type: string, isMultipleSelect: boolean) => void,
114114
id: string,
115-
type: string
115+
type: string,
116+
isMultipleSelect: boolean
116117
) => {
117118
const element = querySelectById(id)
118119

119120
if (element) {
120-
updateSelectedNode({ target: element } as unknown as MouseEvent, type)
121+
updateSelectedNode({ target: element } as unknown as MouseEvent, type, isMultipleSelect)
121122
}
122123
}

packages/canvas/container/src/interactions/default-interactions.ts renamed to packages/canvas/container/src/interactions/html-interactions.ts

Lines changed: 23 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
* 缺陷:
1919
* 1. 如果画布无法挂载 data-uid 属性到 DOM 节点上,那么该节点无法反查到对应的 node 节点,导致 hover 、选中等逻辑无法生效。
2020
*/
21-
import { nextTick, ref } from 'vue'
21+
import { ref } from 'vue'
2222
import { useCanvas } from '@opentiny/tiny-engine-meta-register'
2323
import { NODE_TAG, NODE_UID, NODE_INACTIVE_UID } from '../../../common'
2424
import { getConfigure, scrollToNode, canvasState, getDocument, querySelectById } from '../container'
@@ -27,7 +27,8 @@ import {
2727
clearHover as commonClearHover,
2828
getClosedElementHasUid,
2929
getWindowRect,
30-
hoverNodeById as commonHoverNodeById
30+
hoverNodeById as commonHoverNodeById,
31+
selectNodeById as commonSelectNodeById
3132
} from './common'
3233
import type { HoverOrSelectState } from './common'
3334

@@ -112,7 +113,7 @@ export const useHoverNode = () => {
112113
}
113114
}
114115

115-
const updateSelectedNode = async (e: MouseEvent, type: string) => {
116+
const updateSelectedNode = async (e: MouseEvent, type: string, isMultipleSelect = false) => {
116117
let res = getRectAndNode(e)
117118

118119
if (!res) {
@@ -121,8 +122,6 @@ const updateSelectedNode = async (e: MouseEvent, type: string) => {
121122
return
122123
}
123124

124-
const isMultipleSelect = e.ctrlKey || e.metaKey
125-
126125
// 选中的是非当前编辑页的节点,改为选中顶层节点
127126
if (!res.node && res.isInactiveNode) {
128127
// 多选选中非激活节点,忽略
@@ -171,44 +170,8 @@ const updateSelectedNode = async (e: MouseEvent, type: string) => {
171170
}
172171
}
173172

174-
// TODO: 支持多选
175-
const selectNodeById = async (id: string, type: string) => {
176-
// commonSelectNodeById(updateSelectedNode, id, type)
177-
const element = querySelectById(id)
178-
const { node, parent } = useCanvas().getNodeWithParentById(id) || {}
179-
180-
if (!element || !node) {
181-
clearSelect()
182-
183-
return
184-
}
185-
186-
const rect = element.getBoundingClientRect()
187-
const componentName = node.componentName
188-
const configure = getConfigure(componentName)
189-
190-
canvasState.current = node
191-
canvasState.parent = parent
192-
selectState.value = [
193-
{
194-
rect: {
195-
top: rect.top,
196-
left: rect.left,
197-
width: rect.width,
198-
height: rect.height
199-
},
200-
node,
201-
configure,
202-
element,
203-
componentName,
204-
isInactiveNode: false
205-
}
206-
]
207-
208-
// TODO: 改成事件通知
209-
canvasState.emit('selected', node, parent, type, node?.id)
210-
await nextTick()
211-
await scrollToNode(element)
173+
const selectNodeById = async (id: string, type: string, isMultipleSelect = false) => {
174+
commonSelectNodeById(updateSelectedNode, id, type, isMultipleSelect)
212175
}
213176

214177
const updateSelectedRect = () => {
@@ -218,31 +181,29 @@ const updateSelectedRect = () => {
218181
}
219182

220183
selectState.value = selectState.value.map((stateItem) => {
184+
// 优先需要尝试使用 querySelectById 计算 位置
185+
const element = querySelectById(stateItem.node.id)
186+
187+
if (element) {
188+
const rect = element.getBoundingClientRect()
189+
190+
return {
191+
...stateItem,
192+
rect: {
193+
top: rect.top,
194+
left: rect.left,
195+
width: rect.width,
196+
height: rect.height
197+
}
198+
}
199+
}
200+
221201
const res = getRectAndNode({ target: stateItem.element } as unknown as MouseEvent)
222202

223203
if (res?.node) {
224204
return res
225205
}
226206

227-
// 通过节点没法直接计算到 rect,可能是没法挂载 data-uid 属性,需要尝试使用 querySelectById
228-
if (!res?.node) {
229-
const element = querySelectById(stateItem.node.id)
230-
231-
if (element) {
232-
const rect = element.getBoundingClientRect()
233-
234-
return {
235-
...stateItem,
236-
rect: {
237-
top: rect.top,
238-
left: rect.left,
239-
width: rect.width,
240-
height: rect.height
241-
}
242-
}
243-
}
244-
}
245-
246207
return stateItem
247208
})
248209
}, 0)

packages/canvas/container/src/interactions/index.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,30 +12,32 @@
1212
import { ref } from 'vue'
1313
import { getMergeMeta } from '@opentiny/tiny-engine-meta-register'
1414
import { useHoverNode as useVueHoverNode, useSelectNode as useVueSelectNode } from './vue-interactions'
15-
import { useHoverNode as useDefaultHoverNode, useSelectNode as useDefaultSelectNode } from './default-interactions'
15+
import { useHoverNode as useDefaultHoverNode, useSelectNode as useDefaultSelectNode } from './html-interactions'
1616

1717
const interactionHooksMap = {
1818
vue: {
1919
useHoverNode: useVueHoverNode,
2020
useSelectNode: useVueSelectNode
2121
},
22-
default: {
22+
html: {
2323
useHoverNode: useDefaultHoverNode,
2424
useSelectNode: useDefaultSelectNode
2525
}
2626
}
2727

28+
type IInteractionHooksMap = typeof interactionHooksMap
29+
2830
const getInteractionFn = () => {
29-
const dslMode = getMergeMeta('engine.config')?.dslMode?.toLowerCase?.() as keyof typeof interactionHooksMap
31+
const selectMode = getMergeMeta('engine.config')?.selectMode?.toLowerCase?.() as keyof IInteractionHooksMap
3032

31-
if (interactionHooksMap[dslMode]) {
32-
return interactionHooksMap[dslMode]
33+
if (interactionHooksMap[selectMode]) {
34+
return interactionHooksMap[selectMode]
3335
}
3436

35-
return interactionHooksMap.default
37+
return interactionHooksMap.vue
3638
}
3739

38-
const interactionsFn = ref<typeof interactionHooksMap[keyof typeof interactionHooksMap] | null>(null)
40+
const interactionsFn = ref<IInteractionHooksMap[keyof IInteractionHooksMap] | null>(null)
3941

4042
export const useHoverNode = () => {
4143
if (!interactionsFn.value) {

packages/canvas/container/src/interactions/vue-interactions.ts

Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
import { ref } from 'vue'
2525
import { useCanvas } from '@opentiny/tiny-engine-meta-register'
2626
import { NODE_TAG, NODE_UID } from '../../../common'
27-
import { canvasState, getConfigure, scrollToNode, getDocument } from '../container'
27+
import { canvasState, getConfigure, scrollToNode, getDocument, querySelectById } from '../container'
2828
import {
2929
initialHoverState,
3030
clearHover as commonClearHover,
@@ -56,9 +56,7 @@ export const getClosedVueElement = (element: HTMLElementWithVue | null): VueInst
5656
}
5757

5858
const curHoverState = ref<HoverOrSelectState>(structuredClone(initialHoverState))
59-
6059
const selectState = ref<HoverOrSelectState[]>([])
61-
6260
const clearHover = () => commonClearHover(curHoverState)
6361

6462
const getRectAndNode = (e: { target: HTMLElementWithVue }): HoverOrSelectState => {
@@ -82,15 +80,7 @@ const getRectAndNode = (e: { target: HTMLElementWithVue }): HoverOrSelectState =
8280
if (!uid) {
8381
let closedVueEle: VueInstanceInternal | undefined = instance
8482

85-
// TODO: 同步 develop 主干分支后,确认是否仍然需要 closedVueEle?.props?.schema?.id (当前没有的话选不中表格插槽里面的组件)
86-
while (
87-
closedVueEle &&
88-
!(
89-
closedVueEle.props?.schema?.id ||
90-
closedVueEle.attrs?.[NODE_UID] ||
91-
closedVueEle.attrs?.[NODE_TAG] === 'RouterView'
92-
)
93-
) {
83+
while (closedVueEle && !(closedVueEle.attrs?.[NODE_UID] || closedVueEle.attrs?.[NODE_TAG] === 'RouterView')) {
9484
closedVueEle = closedVueEle.parent
9585
}
9686

@@ -149,16 +139,14 @@ const hoverNodeById = (id: string): void => {
149139
commonHoverNodeById(id, updateHoverNode)
150140
}
151141

152-
const updateSelectedNode = async (e: MouseEvent, type: string): Promise<void> => {
142+
const updateSelectedNode = async (e: MouseEvent, type: string, isMultipleSelect = false): Promise<void> => {
153143
let res = getRectAndNode({ target: e.target as HTMLElementWithVue })
154144

155145
if (!res) {
156146
clearSelect()
157147
return
158148
}
159149

160-
const isMultipleSelect = e.ctrlKey || e.metaKey
161-
162150
if (!res.node && res.isInactiveNode) {
163151
// 多选选中非激活节点,忽略
164152
if (isMultipleSelect) {
@@ -213,8 +201,8 @@ const updateSelectedNode = async (e: MouseEvent, type: string): Promise<void> =>
213201
}
214202
}
215203

216-
const selectNodeById = (id: string, type: string): void => {
217-
commonSelectNodeById(updateSelectedNode, id, type)
204+
const selectNodeById = (id: string, type: string, isMultipleSelect = false): void => {
205+
commonSelectNodeById(updateSelectedNode, id, type, isMultipleSelect)
218206
}
219207

220208
export const useHoverNode = () => {
@@ -233,9 +221,18 @@ const updateSelectedRect = (): void => {
233221
return
234222
}
235223

236-
selectState.value = selectState.value.map((state) =>
237-
getRectAndNode({ target: state.element as HTMLElementWithVue })
238-
)
224+
selectState.value = selectState.value.map((state) => {
225+
// 这里不能直接计算原来的 element 来获取 rect,因为 element 可能已经被移除
226+
// 或者是 text 节点,直接更新文本,并没有更新 element。
227+
// 需要优先使用 querySelectById 来获取
228+
const target = querySelectById(state.node?.id)
229+
230+
if (target) {
231+
return getRectAndNode({ target: target as HTMLElementWithVue })
232+
}
233+
234+
return getRectAndNode({ target: state.element as HTMLElementWithVue })
235+
})
239236
}, 0)
240237
}
241238

0 commit comments

Comments
 (0)