@mx-sose-front/mx-sose-graph 1.1.8 → 1.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/edgeWorker-b57ca007.js +2 -0
- package/dist/assets/edgeWorker-b57ca007.js.map +1 -0
- package/dist/index.d.ts +633 -30
- package/dist/index.esm.js +8728 -4734
- package/dist/index.esm.js.map +1 -1
- package/dist/index.umd.js +1 -1
- package/dist/index.umd.js.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/components/Common/Tree.vue +451 -0
- package/src/components/Common/index.ts +2 -0
- package/src/components/DiagramListTooltip/DiagramListTooltip.vue +1 -2
- package/src/components/Edge/Edge.vue +172 -169
- package/src/components/Gantt/Gantt.vue +1544 -0
- package/src/components/GanttContextMenu/GanttContextMenu.vue +304 -0
- package/src/components/InteractionLayer.vue +343 -147
- package/src/components/Matrix/Matrix.vue +828 -0
- package/src/components/Matrix/index.ts +168 -0
- package/src/components/Shape/ConceptualRole.vue +2 -34
- package/src/components/Table/Table.vue +970 -0
- package/src/constants/edgeShapeKeys.ts +8 -5
- package/src/constants/index.ts +259 -45
- package/src/hooks/index.ts +2 -0
- package/src/hooks/useChartRowSelection.ts +456 -0
- package/src/hooks/useResize.ts +2 -2
- package/src/hooks/useVirtualScroll.ts +258 -0
- package/src/index.ts +1 -1
- package/src/render/shape-renderer.ts +62 -2
- package/src/statics/icons/childIcons//345/221/275/344/273/244@3x.png +0 -0
- package/src/statics/icons/childIcons//346/210/230/347/225/245/346/246/202/345/277/265/350/241/250@3x.png +0 -0
- package/src/statics/icons/childIcons//346/216/247/345/210/266@3x.png +0 -0
- package/src/statics/icons/createMenu/down.png +0 -0
- package/src/statics/icons/createMenu/remove.png +0 -0
- package/src/statics/icons/createMenu/up.png +0 -0
- package/src/store/graphStore.ts +217 -44
- package/src/types/index.ts +86 -4
- package/src/utils/batchAutoExpand.ts +9 -10
- package/src/utils/containers.ts +72 -17
- package/src/utils/contextMenuUtils.ts +7 -7
- package/src/utils/dateUtils.ts +160 -0
- package/src/utils/diagram.ts +10 -8
- package/src/utils/drag.ts +6 -5
- package/src/utils/edgeUtils.ts +344 -427
- package/src/utils/edgeWorker.ts +471 -0
- package/src/utils/hittest.ts +37 -38
- package/src/utils/index.ts +3 -0
- package/src/utils/keyboardUtils.ts +5 -5
- package/src/utils/packageOutline.ts +96 -0
- package/src/utils/rafThrottle.ts +162 -0
- package/src/utils/workerManager.ts +335 -0
- package/src/view/graph.vue +47 -33
- /package/src/statics/icons/childIcons//346/210/230/347/225/{245@3x.png" → 245/345/261/202@3x.png"} +0 -0
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
import { computed, ref, watch, type Ref } from 'vue'
|
|
2
|
+
|
|
3
|
+
import { eventBus } from '../store/eventBus'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 行数据的最小结构约束
|
|
7
|
+
*/
|
|
8
|
+
interface ChartRowItem {
|
|
9
|
+
id: string
|
|
10
|
+
modelId: string
|
|
11
|
+
type: string
|
|
12
|
+
[key: string]: any
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* 移除操作的上下文
|
|
17
|
+
* 用于在接口成功后按原位置恢复选中状态
|
|
18
|
+
*/
|
|
19
|
+
interface RemoveContext {
|
|
20
|
+
removedIds: string[]
|
|
21
|
+
selectedIndices: number[]
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 行选择 Hook 配置项
|
|
26
|
+
*/
|
|
27
|
+
interface UseChartRowSelectionOptions<T extends ChartRowItem> {
|
|
28
|
+
/**
|
|
29
|
+
* 当前表格或甘特图的行数据
|
|
30
|
+
*/
|
|
31
|
+
items: Ref<T[]>
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* 行选中状态变化后向外抛出的事件名
|
|
35
|
+
*/
|
|
36
|
+
rowSelectedEvent: string
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 切换选中前触发
|
|
40
|
+
* 组件通常用它来关闭行内编辑器或日期选择器
|
|
41
|
+
*/
|
|
42
|
+
onBeforeSelect?: () => void
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* 点击空白区域、清空选中时触发
|
|
46
|
+
*/
|
|
47
|
+
onClearSelection?: () => void
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* 删除选中行并重置本地选中态后触发
|
|
51
|
+
*/
|
|
52
|
+
onDeleteRows?: () => void
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* 移除成功并完成本地数据对齐后触发
|
|
56
|
+
*/
|
|
57
|
+
onAfterRemoveSuccess?: () => void
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* 表格/甘特图行交互 Hook
|
|
62
|
+
*
|
|
63
|
+
* @description
|
|
64
|
+
* 抽离 Table 与 Gantt 共用的行交互逻辑,统一处理:
|
|
65
|
+
* 1. 单选、Ctrl 多选、Shift 连续选择
|
|
66
|
+
* 2. 右键菜单定位与菜单动作
|
|
67
|
+
* 3. 上移、下移、删除、移除
|
|
68
|
+
* 4. 移除成功后的按原位置回选
|
|
69
|
+
* 5. 工具栏依赖的选中状态通知
|
|
70
|
+
*/
|
|
71
|
+
export function useChartRowSelection<T extends ChartRowItem>({
|
|
72
|
+
items,
|
|
73
|
+
rowSelectedEvent,
|
|
74
|
+
onBeforeSelect,
|
|
75
|
+
onClearSelection,
|
|
76
|
+
onDeleteRows,
|
|
77
|
+
onAfterRemoveSuccess,
|
|
78
|
+
}: UseChartRowSelectionOptions<T>) {
|
|
79
|
+
// 状态变量
|
|
80
|
+
const selectedRowIds = ref<Set<string>>(new Set())
|
|
81
|
+
const lastSelectedId = ref<string | null>(null)
|
|
82
|
+
const showContextMenu = ref(false)
|
|
83
|
+
const contextMenuPosition = ref({ x: 0, y: 0 })
|
|
84
|
+
const contextMenuTargetItem = ref<T | null>(null)
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* 缓存本次“移除”的上下文
|
|
88
|
+
* 后端确认移除成功后,再按原位置恢复选中
|
|
89
|
+
*/
|
|
90
|
+
const pendingRemoveCtx = ref<RemoveContext | null>(null)
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* 处理行点击选中
|
|
94
|
+
* 支持单选、Ctrl 多选、Shift 连续选择
|
|
95
|
+
*/
|
|
96
|
+
function selectRow(id: string, event?: MouseEvent) {
|
|
97
|
+
onBeforeSelect?.()
|
|
98
|
+
|
|
99
|
+
if (event?.ctrlKey || event?.metaKey) {
|
|
100
|
+
const nextSelected = new Set(selectedRowIds.value)
|
|
101
|
+
if (nextSelected.has(id)) {
|
|
102
|
+
nextSelected.delete(id)
|
|
103
|
+
} else {
|
|
104
|
+
nextSelected.add(id)
|
|
105
|
+
}
|
|
106
|
+
selectedRowIds.value = nextSelected
|
|
107
|
+
lastSelectedId.value = id
|
|
108
|
+
return
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (event?.shiftKey && lastSelectedId.value) {
|
|
112
|
+
const currentIndex = items.value.findIndex(item => item.id === id)
|
|
113
|
+
const lastIndex = items.value.findIndex(item => item.id === lastSelectedId.value)
|
|
114
|
+
|
|
115
|
+
if (currentIndex !== -1 && lastIndex !== -1) {
|
|
116
|
+
const start = Math.min(currentIndex, lastIndex)
|
|
117
|
+
const end = Math.max(currentIndex, lastIndex)
|
|
118
|
+
const nextSelected = new Set<string>()
|
|
119
|
+
|
|
120
|
+
for (let i = start; i <= end; i++) {
|
|
121
|
+
nextSelected.add(items.value[i].id)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
selectedRowIds.value = nextSelected
|
|
125
|
+
}
|
|
126
|
+
return
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const isSameRow = selectedRowIds.value.size === 1 && selectedRowIds.value.has(id)
|
|
130
|
+
|
|
131
|
+
selectedRowIds.value = new Set([id])
|
|
132
|
+
lastSelectedId.value = id
|
|
133
|
+
|
|
134
|
+
if (isSameRow) return
|
|
135
|
+
|
|
136
|
+
const selectedItem = items.value.find(item => item.id === id)
|
|
137
|
+
if (selectedItem) {
|
|
138
|
+
eventBus.emit('chart-row-click', selectedItem)
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* 清空选中状态
|
|
144
|
+
*/
|
|
145
|
+
function clearSelection() {
|
|
146
|
+
selectedRowIds.value = new Set()
|
|
147
|
+
lastSelectedId.value = null
|
|
148
|
+
onClearSelection?.()
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* 向外同步当前选中状态
|
|
153
|
+
* 供工具栏判断上移 / 下移按钮是否可用
|
|
154
|
+
*/
|
|
155
|
+
function emitRowSelected() {
|
|
156
|
+
if (selectedRowIds.value.size === 0) {
|
|
157
|
+
eventBus.emit(rowSelectedEvent, null, { isFirst: false, isLast: false })
|
|
158
|
+
return
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const selectedIndices = getSelectedIndices()
|
|
162
|
+
let isContinuous = true
|
|
163
|
+
|
|
164
|
+
for (let i = 1; i < selectedIndices.length; i++) {
|
|
165
|
+
if (selectedIndices[i] - selectedIndices[i - 1] !== 1) {
|
|
166
|
+
isContinuous = false
|
|
167
|
+
break
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const firstSelectedId = [...selectedRowIds.value][0]
|
|
172
|
+
if (!isContinuous) {
|
|
173
|
+
eventBus.emit(rowSelectedEvent, firstSelectedId, {
|
|
174
|
+
isFirst: true,
|
|
175
|
+
isLast: true,
|
|
176
|
+
})
|
|
177
|
+
return
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const canMoveUp = selectedIndices.length > 0 && selectedIndices[0] > 0
|
|
181
|
+
const canMoveDown =
|
|
182
|
+
selectedIndices.length > 0 && selectedIndices[selectedIndices.length - 1] < items.value.length - 1
|
|
183
|
+
|
|
184
|
+
eventBus.emit(rowSelectedEvent, firstSelectedId, {
|
|
185
|
+
isFirst: !canMoveUp,
|
|
186
|
+
isLast: !canMoveDown,
|
|
187
|
+
})
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* 统一抛出行移动事件
|
|
192
|
+
*/
|
|
193
|
+
function moveRows(direction: 'UP' | 'DOWN') {
|
|
194
|
+
if (selectedRowIds.value.size === 0) return
|
|
195
|
+
|
|
196
|
+
const selectedItems = items.value.filter(item => selectedRowIds.value.has(item.id))
|
|
197
|
+
eventBus.emit('gantt-move-rows', {
|
|
198
|
+
selectedItems,
|
|
199
|
+
allItems: items.value,
|
|
200
|
+
direction,
|
|
201
|
+
})
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* 上移选中行
|
|
206
|
+
*/
|
|
207
|
+
function moveUp() {
|
|
208
|
+
moveRows('UP')
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* 下移选中行
|
|
213
|
+
*/
|
|
214
|
+
function moveDown() {
|
|
215
|
+
moveRows('DOWN')
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* 删除选中行
|
|
220
|
+
*/
|
|
221
|
+
function deleteRows() {
|
|
222
|
+
if (selectedRowIds.value.size === 0) return
|
|
223
|
+
|
|
224
|
+
const deletedRows = items.value.filter(item => selectedRowIds.value.has(item.id))
|
|
225
|
+
const deletePayload = deletedRows.map(item => ({
|
|
226
|
+
modelId: item.modelId,
|
|
227
|
+
shapeKey: item.type,
|
|
228
|
+
}))
|
|
229
|
+
|
|
230
|
+
eventBus.emit('chart-delete', deletePayload)
|
|
231
|
+
selectedRowIds.value = new Set()
|
|
232
|
+
lastSelectedId.value = null
|
|
233
|
+
onDeleteRows?.()
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* 打开右键菜单
|
|
238
|
+
*/
|
|
239
|
+
function handleContextMenu(item: T, event: MouseEvent) {
|
|
240
|
+
event.preventDefault()
|
|
241
|
+
event.stopPropagation()
|
|
242
|
+
|
|
243
|
+
const isSameRow = selectedRowIds.value.size === 1 && selectedRowIds.value.has(item.id)
|
|
244
|
+
|
|
245
|
+
selectedRowIds.value = new Set([item.id])
|
|
246
|
+
lastSelectedId.value = item.id
|
|
247
|
+
|
|
248
|
+
if (!isSameRow) {
|
|
249
|
+
eventBus.emit('chart-row-click', item)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
contextMenuTargetItem.value = item
|
|
253
|
+
contextMenuPosition.value = {
|
|
254
|
+
x: event.clientX,
|
|
255
|
+
y: event.clientY,
|
|
256
|
+
}
|
|
257
|
+
showContextMenu.value = true
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* 计算右键菜单目标行的位置
|
|
262
|
+
* 供菜单判断是否允许上移 / 下移
|
|
263
|
+
*/
|
|
264
|
+
const rowPosition = computed(() => {
|
|
265
|
+
if (!contextMenuTargetItem.value) {
|
|
266
|
+
return { isFirst: true, isLast: true }
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const index = items.value.findIndex(item => item.id === contextMenuTargetItem.value?.id)
|
|
270
|
+
if (index === -1) {
|
|
271
|
+
return { isFirst: true, isLast: true }
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return {
|
|
275
|
+
isFirst: index === 0,
|
|
276
|
+
isLast: index === items.value.length - 1,
|
|
277
|
+
}
|
|
278
|
+
})
|
|
279
|
+
|
|
280
|
+
const canMoveUp = computed(() => !rowPosition.value.isFirst)
|
|
281
|
+
const canMoveDown = computed(() => !rowPosition.value.isLast)
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* 打开属性配置
|
|
285
|
+
*/
|
|
286
|
+
function handlePropertyConfig() {
|
|
287
|
+
if (!contextMenuTargetItem.value) return
|
|
288
|
+
eventBus.emit('chart-property-config', contextMenuTargetItem.value)
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* 在树上定位当前行
|
|
293
|
+
*/
|
|
294
|
+
function handleTreeHighlight() {
|
|
295
|
+
if (!contextMenuTargetItem.value) return
|
|
296
|
+
eventBus.emit('chart-tree-highlight', contextMenuTargetItem.value)
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* 通过右键菜单执行上移
|
|
301
|
+
*/
|
|
302
|
+
function handleMoveUpFromMenu() {
|
|
303
|
+
if (!canMoveUp.value || !contextMenuTargetItem.value) return
|
|
304
|
+
moveUp()
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* 通过右键菜单执行下移
|
|
309
|
+
*/
|
|
310
|
+
function handleMoveDownFromMenu() {
|
|
311
|
+
if (!canMoveDown.value || !contextMenuTargetItem.value) return
|
|
312
|
+
moveDown()
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* 通过右键菜单执行删除
|
|
317
|
+
*/
|
|
318
|
+
function handleDeleteFromMenu() {
|
|
319
|
+
if (!contextMenuTargetItem.value) return
|
|
320
|
+
deleteRows()
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* 通过右键菜单执行移除
|
|
325
|
+
*/
|
|
326
|
+
function handleRemoveFromMenu() {
|
|
327
|
+
if (!contextMenuTargetItem.value) return
|
|
328
|
+
eventBus.emit('chart-remove', contextMenuTargetItem.value)
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* 获取当前选中行的索引列表
|
|
333
|
+
*/
|
|
334
|
+
function getSelectedIndices(): number[] {
|
|
335
|
+
return items.value
|
|
336
|
+
.map((item, index) => (selectedRowIds.value.has(item.id) ? index : -1))
|
|
337
|
+
.filter(index => index !== -1)
|
|
338
|
+
.sort((a, b) => a - b)
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* 触发行移除
|
|
343
|
+
* 先缓存原始位置,等待接口成功后再做本地对齐
|
|
344
|
+
*/
|
|
345
|
+
function removeRows() {
|
|
346
|
+
if (selectedRowIds.value.size === 0) return
|
|
347
|
+
|
|
348
|
+
const selectedIndices = getSelectedIndices()
|
|
349
|
+
if (selectedIndices.length === 0) return
|
|
350
|
+
|
|
351
|
+
const removedRows = items.value.filter(item => selectedRowIds.value.has(item.id))
|
|
352
|
+
const removedIds = removedRows.map(row => row.id)
|
|
353
|
+
|
|
354
|
+
pendingRemoveCtx.value = {
|
|
355
|
+
removedIds,
|
|
356
|
+
selectedIndices,
|
|
357
|
+
}
|
|
358
|
+
eventBus.emit('chart-remove-rows', removedIds)
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* 接口成功后执行本地移除与回选
|
|
363
|
+
*/
|
|
364
|
+
function applyRemoveAfterSuccess(removedIds: string[], selectedIndices: number[]) {
|
|
365
|
+
const removedIdSet = new Set(removedIds)
|
|
366
|
+
items.value = items.value.filter(item => !removedIdSet.has(item.id))
|
|
367
|
+
onAfterRemoveSuccess?.()
|
|
368
|
+
|
|
369
|
+
const nextSelectedIds: string[] = []
|
|
370
|
+
selectedIndices.forEach(index => {
|
|
371
|
+
const nextItem = items.value[index]
|
|
372
|
+
if (nextItem) {
|
|
373
|
+
nextSelectedIds.push(nextItem.id)
|
|
374
|
+
}
|
|
375
|
+
})
|
|
376
|
+
|
|
377
|
+
selectedRowIds.value = new Set(nextSelectedIds)
|
|
378
|
+
lastSelectedId.value = nextSelectedIds.length > 0 ? nextSelectedIds[nextSelectedIds.length - 1] : null
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* 响应外部移除成功回执
|
|
383
|
+
*/
|
|
384
|
+
function onRemoveSuccess() {
|
|
385
|
+
const ctx = pendingRemoveCtx.value
|
|
386
|
+
if (!ctx) return
|
|
387
|
+
|
|
388
|
+
applyRemoveAfterSuccess(ctx.removedIds, ctx.selectedIndices)
|
|
389
|
+
pendingRemoveCtx.value = null
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* 选中最后一行
|
|
394
|
+
* 用于新增成功后的默认回选
|
|
395
|
+
*/
|
|
396
|
+
function selectLastRow() {
|
|
397
|
+
const list = items.value
|
|
398
|
+
if (!list.length) {
|
|
399
|
+
selectedRowIds.value = new Set()
|
|
400
|
+
lastSelectedId.value = null
|
|
401
|
+
return
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
const last = list[list.length - 1]
|
|
405
|
+
selectedRowIds.value = new Set([last.id])
|
|
406
|
+
lastSelectedId.value = last.id
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* 同步“单行选中”状态
|
|
411
|
+
* 供属性面板等只关心单选对象的模块使用
|
|
412
|
+
*/
|
|
413
|
+
watch(
|
|
414
|
+
[selectedRowIds, items],
|
|
415
|
+
([newSet]) => {
|
|
416
|
+
if (newSet.size !== 1) {
|
|
417
|
+
eventBus.emit('row-single-selected', null)
|
|
418
|
+
return
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
const selectedId = [...newSet][0]
|
|
422
|
+
const row = items.value.find(item => item.id === selectedId)
|
|
423
|
+
eventBus.emit('row-single-selected', row ?? null)
|
|
424
|
+
},
|
|
425
|
+
{ deep: true }
|
|
426
|
+
)
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* 同步工具栏依赖的选中位置信息
|
|
430
|
+
*/
|
|
431
|
+
watch([selectedRowIds, () => items.value.length], emitRowSelected, { deep: true })
|
|
432
|
+
|
|
433
|
+
return {
|
|
434
|
+
canMoveDown,
|
|
435
|
+
canMoveUp,
|
|
436
|
+
clearSelection,
|
|
437
|
+
contextMenuPosition,
|
|
438
|
+
contextMenuTargetItem,
|
|
439
|
+
deleteRows,
|
|
440
|
+
handleContextMenu,
|
|
441
|
+
handleDeleteFromMenu,
|
|
442
|
+
handleMoveDownFromMenu,
|
|
443
|
+
handleMoveUpFromMenu,
|
|
444
|
+
handlePropertyConfig,
|
|
445
|
+
handleRemoveFromMenu,
|
|
446
|
+
handleTreeHighlight,
|
|
447
|
+
moveDown,
|
|
448
|
+
moveUp,
|
|
449
|
+
onRemoveSuccess,
|
|
450
|
+
removeRows,
|
|
451
|
+
selectLastRow,
|
|
452
|
+
selectRow,
|
|
453
|
+
selectedRowIds,
|
|
454
|
+
showContextMenu,
|
|
455
|
+
}
|
|
456
|
+
}
|
package/src/hooks/useResize.ts
CHANGED
|
@@ -64,7 +64,7 @@ export interface UseResizeReturn {
|
|
|
64
64
|
groupGhost: Ref<Record<string, Rect>>;
|
|
65
65
|
startResize: (e: MouseEvent, dir: "nw" | "ne" | "sw" | "se", target: Shape) => void;
|
|
66
66
|
handleResize: (e: MouseEvent) => void;
|
|
67
|
-
stopResize: () => void;
|
|
67
|
+
stopResize: () => Promise<void> | void;
|
|
68
68
|
cancelResize: () => void;
|
|
69
69
|
getMinDimensions: (shape: Shape, baseMinW?: number, mode?: MinDimensionMode) => MinDimensions;
|
|
70
70
|
}
|
|
@@ -254,7 +254,7 @@ export function useResize(
|
|
|
254
254
|
/**
|
|
255
255
|
* 停止缩放
|
|
256
256
|
*/
|
|
257
|
-
const stopResize = () => {
|
|
257
|
+
const stopResize = async () => {
|
|
258
258
|
if (!isResizing.value || !resizingTarget.value) return;
|
|
259
259
|
|
|
260
260
|
const id = resizingTarget.value.id;
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import { computed, ref, watch, nextTick, type Ref } from 'vue'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 虚拟滚动配置选项
|
|
5
|
+
*/
|
|
6
|
+
export interface VirtualScrollOptions {
|
|
7
|
+
/**
|
|
8
|
+
* 每行的高度(px)
|
|
9
|
+
*/
|
|
10
|
+
itemHeight: number
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 表头高度(px),默认 0
|
|
14
|
+
*/
|
|
15
|
+
headerHeight?: number
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* 缓冲区行数(上下各预渲染多少行),默认 8
|
|
19
|
+
*/
|
|
20
|
+
overscan?: number
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 容器元素的 ref(用于获取滚动位置和视口高度)
|
|
24
|
+
*/
|
|
25
|
+
containerRef: Ref<HTMLElement | null | undefined>
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* 可选的额外容器 ref(用于多容器同步场景,如甘特图左右两侧)
|
|
29
|
+
*/
|
|
30
|
+
extraContainerRefs?: Ref<HTMLElement | null | undefined>[]
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* 可见列数(用于 colspan),默认 1
|
|
34
|
+
*/
|
|
35
|
+
columnCount?: Ref<number> | number
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 虚拟滚动返回值
|
|
40
|
+
*/
|
|
41
|
+
export interface VirtualScrollReturn<T> {
|
|
42
|
+
/**
|
|
43
|
+
* 当前可见的虚拟行数据
|
|
44
|
+
*/
|
|
45
|
+
virtualRows: Ref<Array<{ item: T; index: number }>>
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* 上方占位空间高度(px)
|
|
49
|
+
*/
|
|
50
|
+
virtualTopSpacerHeight: Ref<number>
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* 下方占位空间高度(px)
|
|
54
|
+
*/
|
|
55
|
+
virtualBottomSpacerHeight: Ref<number>
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* 起始索引
|
|
59
|
+
*/
|
|
60
|
+
virtualStartIndex: Ref<number>
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 结束索引
|
|
64
|
+
*/
|
|
65
|
+
virtualEndIndex: Ref<number>
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* 可见行数
|
|
69
|
+
*/
|
|
70
|
+
virtualVisibleRowCount: Ref<number>
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* 当前滚动位置
|
|
74
|
+
*/
|
|
75
|
+
virtualScrollTop: Ref<number>
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* 视口高度
|
|
79
|
+
*/
|
|
80
|
+
virtualViewportHeight: Ref<number>
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* 同步视口状态(手动调用,用于滚动事件或尺寸变化时)
|
|
84
|
+
*/
|
|
85
|
+
syncVirtualViewportState: (preferredEl?: HTMLElement | null) => void
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* 可见列数(用于 colspan)
|
|
89
|
+
*/
|
|
90
|
+
visibleColumnSpan: Ref<number>
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* 虚拟滚动 Hook
|
|
95
|
+
*
|
|
96
|
+
* @description
|
|
97
|
+
* 用于大数据量列表的性能优化,只渲染可见区域的行,大幅减少 DOM 节点数量。
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* ```ts
|
|
101
|
+
* const containerRef = ref<HTMLElement>()
|
|
102
|
+
* const items = ref([...]) // 1000 条数据
|
|
103
|
+
*
|
|
104
|
+
* const {
|
|
105
|
+
* virtualRows,
|
|
106
|
+
* virtualTopSpacerHeight,
|
|
107
|
+
* virtualBottomSpacerHeight,
|
|
108
|
+
* syncVirtualViewportState
|
|
109
|
+
* } = useVirtualScroll(items, {
|
|
110
|
+
* itemHeight: 32,
|
|
111
|
+
* headerHeight: 56,
|
|
112
|
+
* overscan: 8,
|
|
113
|
+
* containerRef
|
|
114
|
+
* })
|
|
115
|
+
*
|
|
116
|
+
* // 在滚动事件中调用
|
|
117
|
+
* onScroll(() => {
|
|
118
|
+
* syncVirtualViewportState()
|
|
119
|
+
* })
|
|
120
|
+
* ```
|
|
121
|
+
*/
|
|
122
|
+
export function useVirtualScroll<T = any>(
|
|
123
|
+
items: Ref<T[]>,
|
|
124
|
+
options: VirtualScrollOptions
|
|
125
|
+
): VirtualScrollReturn<T> {
|
|
126
|
+
const {
|
|
127
|
+
itemHeight,
|
|
128
|
+
headerHeight = 0,
|
|
129
|
+
overscan = 8,
|
|
130
|
+
containerRef,
|
|
131
|
+
extraContainerRefs = [],
|
|
132
|
+
columnCount = 1,
|
|
133
|
+
} = options
|
|
134
|
+
|
|
135
|
+
// 状态变量
|
|
136
|
+
const virtualScrollTop = ref(0)
|
|
137
|
+
const virtualViewportHeight = ref(0)
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* 同步视口状态
|
|
141
|
+
* 从容器元素中读取当前滚动位置和视口高度
|
|
142
|
+
*/
|
|
143
|
+
function syncVirtualViewportState(preferredEl?: HTMLElement | null) {
|
|
144
|
+
const allRefs = [containerRef, ...extraContainerRefs]
|
|
145
|
+
const sourceEl = preferredEl ?? allRefs.find(r => r.value)?.value
|
|
146
|
+
|
|
147
|
+
if (!sourceEl) return
|
|
148
|
+
|
|
149
|
+
// 更新滚动位置
|
|
150
|
+
virtualScrollTop.value = sourceEl.scrollTop
|
|
151
|
+
|
|
152
|
+
// 更新视口高度(取所有容器的最大值)
|
|
153
|
+
virtualViewportHeight.value = Math.max(
|
|
154
|
+
...allRefs.map(r => r.value?.clientHeight ?? 0)
|
|
155
|
+
)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* 计算可见行数
|
|
160
|
+
* 视口内容区高度 ÷ 行高 = 可见行数
|
|
161
|
+
*/
|
|
162
|
+
const virtualVisibleRowCount = computed(() => {
|
|
163
|
+
const bodyViewportHeight = Math.max(
|
|
164
|
+
virtualViewportHeight.value - headerHeight,
|
|
165
|
+
itemHeight
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
return Math.max(1, Math.ceil(bodyViewportHeight / itemHeight))
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* 计算起始索引
|
|
173
|
+
* 当前滚动位置对应的行索引 - 缓冲区行数
|
|
174
|
+
*/
|
|
175
|
+
const virtualStartIndex = computed(() => {
|
|
176
|
+
return Math.max(
|
|
177
|
+
0,
|
|
178
|
+
Math.floor(virtualScrollTop.value / itemHeight) - overscan
|
|
179
|
+
)
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* 计算结束索引
|
|
184
|
+
* 起始索引 + 可见行数 + 上下缓冲区行数
|
|
185
|
+
*/
|
|
186
|
+
const virtualEndIndex = computed(() => {
|
|
187
|
+
return Math.min(
|
|
188
|
+
items.value.length,
|
|
189
|
+
virtualStartIndex.value + virtualVisibleRowCount.value + overscan * 2
|
|
190
|
+
)
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* 获取当前需要渲染的虚拟行数据
|
|
195
|
+
* 从原始数据中切片,并附加原始索引信息
|
|
196
|
+
*/
|
|
197
|
+
const virtualRows = computed(() => {
|
|
198
|
+
return items.value
|
|
199
|
+
.slice(virtualStartIndex.value, virtualEndIndex.value)
|
|
200
|
+
.map((item, offset) => ({
|
|
201
|
+
item,
|
|
202
|
+
index: virtualStartIndex.value + offset,
|
|
203
|
+
}))
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* 上方占位空间高度
|
|
208
|
+
* 起始索引 × 行高 = 已滚过的行的总高度
|
|
209
|
+
*/
|
|
210
|
+
const virtualTopSpacerHeight = computed(() => {
|
|
211
|
+
return virtualStartIndex.value * itemHeight
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* 下方占位空间高度
|
|
216
|
+
* (总行数 - 结束索引) × 行高 = 未滚到的行的总高度
|
|
217
|
+
*/
|
|
218
|
+
const virtualBottomSpacerHeight = computed(() => {
|
|
219
|
+
return Math.max(
|
|
220
|
+
0,
|
|
221
|
+
(items.value.length - virtualEndIndex.value) * itemHeight
|
|
222
|
+
)
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* 可见列数(用于 colspan)
|
|
227
|
+
*/
|
|
228
|
+
const visibleColumnSpan = computed(() => {
|
|
229
|
+
return typeof columnCount === 'number' ? columnCount : columnCount.value
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* 监听数据长度变化
|
|
234
|
+
* 当过滤结果变短时,浏览器会自动修正 scrollTop,需要重新读取
|
|
235
|
+
*/
|
|
236
|
+
watch(
|
|
237
|
+
() => items.value.length,
|
|
238
|
+
() => {
|
|
239
|
+
nextTick(() => {
|
|
240
|
+
syncVirtualViewportState()
|
|
241
|
+
})
|
|
242
|
+
},
|
|
243
|
+
{ immediate: true }
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
return {
|
|
247
|
+
virtualRows,
|
|
248
|
+
virtualTopSpacerHeight,
|
|
249
|
+
virtualBottomSpacerHeight,
|
|
250
|
+
virtualStartIndex,
|
|
251
|
+
virtualEndIndex,
|
|
252
|
+
virtualVisibleRowCount,
|
|
253
|
+
virtualScrollTop,
|
|
254
|
+
virtualViewportHeight,
|
|
255
|
+
syncVirtualViewportState,
|
|
256
|
+
visibleColumnSpan,
|
|
257
|
+
}
|
|
258
|
+
}
|