bfg-common 1.5.484 → 1.5.486
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.
@@ -1,460 +1,460 @@
|
|
1
|
-
<template>
|
2
|
-
<div
|
3
|
-
:class="['window-container', { loading: props.loading }]"
|
4
|
-
:style="{
|
5
|
-
left: leftLocal + 'px',
|
6
|
-
top: topLocal + 'px',
|
7
|
-
width: widthLocal + 'px',
|
8
|
-
height: heightLocal + 'px',
|
9
|
-
}"
|
10
|
-
>
|
11
|
-
<div
|
12
|
-
ref="headerEl"
|
13
|
-
class="window-header"
|
14
|
-
:style="{ height: headerHeight + 'px' }"
|
15
|
-
>
|
16
|
-
<slot name="header">
|
17
|
-
<div class="window-title text-ellipsis">{{ props.title }}</div>
|
18
|
-
</slot>
|
19
|
-
|
20
|
-
<div class="flex-align-center gap-2">
|
21
|
-
<button class="window-btn window-hide" @click="onCollapse">
|
22
|
-
<ui-icon
|
23
|
-
:name="collapseData.isCollapsed ? 'arrow' : 'check-line'"
|
24
|
-
class="hide-icon"
|
25
|
-
width="16"
|
26
|
-
height="16"
|
27
|
-
/>
|
28
|
-
</button>
|
29
|
-
<button class="window-btn window-view-full-screen" @click="toggle">
|
30
|
-
<ui-icon
|
31
|
-
name="view-full-screen"
|
32
|
-
class="view-full-screen-icon"
|
33
|
-
width="14"
|
34
|
-
height="14"
|
35
|
-
/>
|
36
|
-
</button>
|
37
|
-
<button class="window-btn window-close" @click="onHide">
|
38
|
-
<ui-icon name="close" class="close-icon" width="16" height="16" />
|
39
|
-
</button>
|
40
|
-
</div>
|
41
|
-
</div>
|
42
|
-
|
43
|
-
<div ref="windowContent" class="window-content">
|
44
|
-
<div v-show="props.loading" class="absolute-center">
|
45
|
-
<ui-icon name="spinner" />
|
46
|
-
</div>
|
47
|
-
<slot></slot>
|
48
|
-
<div v-show="isShowContentBlocker" class="content-blocker"></div>
|
49
|
-
</div>
|
50
|
-
|
51
|
-
<!-- Ручки для ресайза -->
|
52
|
-
<div
|
53
|
-
class="resize-handle resize-top"
|
54
|
-
@mousedown="startResize($event, 'top')"
|
55
|
-
></div>
|
56
|
-
<div
|
57
|
-
class="resize-handle resize-right"
|
58
|
-
@mousedown="startResize($event, 'right')"
|
59
|
-
></div>
|
60
|
-
<div
|
61
|
-
class="resize-handle resize-bottom"
|
62
|
-
@mousedown="startResize($event, 'bottom')"
|
63
|
-
></div>
|
64
|
-
<div
|
65
|
-
class="resize-handle resize-left"
|
66
|
-
@mousedown="startResize($event, 'left')"
|
67
|
-
></div>
|
68
|
-
<div
|
69
|
-
class="resize-handle resize-top-left"
|
70
|
-
@mousedown="startResize($event, 'top-left')"
|
71
|
-
></div>
|
72
|
-
<div
|
73
|
-
class="resize-handle resize-top-right"
|
74
|
-
@mousedown="startResize($event, 'top-right')"
|
75
|
-
></div>
|
76
|
-
<div
|
77
|
-
class="resize-handle resize-bottom-left"
|
78
|
-
@mousedown="startResize($event, 'bottom-left')"
|
79
|
-
></div>
|
80
|
-
<div
|
81
|
-
class="resize-handle resize-bottom-right"
|
82
|
-
@mousedown="startResize($event, 'bottom-right')"
|
83
|
-
></div>
|
84
|
-
</div>
|
85
|
-
</template>
|
86
|
-
|
87
|
-
<script setup lang="ts">
|
88
|
-
import { useDraggable, useFullscreen } from '@vueuse/core'
|
89
|
-
import type { UI_I_WindowCollapsedData } from '~/components/atoms/window/lib/models/interfaces'
|
90
|
-
import {
|
91
|
-
minWidth,
|
92
|
-
minHeight,
|
93
|
-
headerHeight,
|
94
|
-
windowPadding,
|
95
|
-
} from '~/components/atoms/window/lib/config/config'
|
96
|
-
|
97
|
-
const props = withDefaults(
|
98
|
-
defineProps<{
|
99
|
-
top: number
|
100
|
-
left: number
|
101
|
-
width: number
|
102
|
-
height: number
|
103
|
-
title: string
|
104
|
-
loading?: boolean
|
105
|
-
}>(),
|
106
|
-
{
|
107
|
-
loading: false,
|
108
|
-
}
|
109
|
-
)
|
110
|
-
const emits = defineEmits<{
|
111
|
-
(event: 'hide'): void
|
112
|
-
}>()
|
113
|
-
|
114
|
-
const windowContent = ref<any>(null)
|
115
|
-
const { toggle } = useFullscreen(windowContent)
|
116
|
-
|
117
|
-
const headerEl = ref(null)
|
118
|
-
const isShowContentBlocker = ref<boolean>(false)
|
119
|
-
|
120
|
-
const leftLocal = ref<number>(props.left)
|
121
|
-
const topLocal = ref<number>(props.top - headerHeight)
|
122
|
-
const widthLocal = ref<number>(Math.max(minWidth, props.width))
|
123
|
-
const heightLocal = ref<number>(
|
124
|
-
Math.max(minHeight, props.height + headerHeight)
|
125
|
-
)
|
126
|
-
|
127
|
-
const collapseData = ref<UI_I_WindowCollapsedData>({
|
128
|
-
isCollapsed: false,
|
129
|
-
cashTop: -1,
|
130
|
-
cashLeft: -1,
|
131
|
-
cashWidth: -1,
|
132
|
-
cashHeight: -1,
|
133
|
-
})
|
134
|
-
|
135
|
-
// Используем vueuse для перетаскивания
|
136
|
-
useDraggable(headerEl, {
|
137
|
-
initialValue: { x: leftLocal.value, y: topLocal.value },
|
138
|
-
onMove({ x: newX, y: newY }) {
|
139
|
-
leftLocal.value = newX
|
140
|
-
topLocal.value = newY
|
141
|
-
},
|
142
|
-
onStart() {
|
143
|
-
isShowContentBlocker.value = true
|
144
|
-
if (!collapseData.value.isCollapsed) {
|
145
|
-
collapseData.value.cashTop = topLocal.value
|
146
|
-
collapseData.value.cashLeft = leftLocal.value
|
147
|
-
collapseData.value.cashWidth = widthLocal.value
|
148
|
-
collapseData.value.cashHeight = heightLocal.value
|
149
|
-
}
|
150
|
-
},
|
151
|
-
onEnd() {
|
152
|
-
isShowContentBlocker.value = false
|
153
|
-
fixPosition()
|
154
|
-
},
|
155
|
-
})
|
156
|
-
|
157
|
-
const fixSize = (): void => {
|
158
|
-
const globalWindowWidth = window.innerWidth
|
159
|
-
const globalWindowHeight = window.innerHeight
|
160
|
-
|
161
|
-
widthLocal.value = Math.min(
|
162
|
-
globalWindowWidth - windowPadding * 2,
|
163
|
-
widthLocal.value
|
164
|
-
)
|
165
|
-
heightLocal.value = Math.min(
|
166
|
-
globalWindowHeight - windowPadding * 2,
|
167
|
-
heightLocal.value
|
168
|
-
)
|
169
|
-
}
|
170
|
-
const fixPosition = (): void => {
|
171
|
-
const globalWindowWidth = window.innerWidth
|
172
|
-
const globalWindowHeight = window.innerHeight
|
173
|
-
let newX = leftLocal.value
|
174
|
-
let newY = topLocal.value
|
175
|
-
|
176
|
-
if (leftLocal.value + widthLocal.value > globalWindowWidth - windowPadding) {
|
177
|
-
newX = globalWindowWidth - widthLocal.value - windowPadding
|
178
|
-
} else if (leftLocal.value < windowPadding) {
|
179
|
-
newX = windowPadding
|
180
|
-
}
|
181
|
-
|
182
|
-
if (topLocal.value + heightLocal.value / 2 > globalWindowHeight) {
|
183
|
-
newY = globalWindowHeight - headerHeight
|
184
|
-
collapseData.value.isCollapsed = true
|
185
|
-
} else if (
|
186
|
-
topLocal.value + heightLocal.value >
|
187
|
-
globalWindowHeight - windowPadding
|
188
|
-
) {
|
189
|
-
newY = globalWindowHeight - heightLocal.value - windowPadding
|
190
|
-
collapseData.value.isCollapsed = false
|
191
|
-
} else if (topLocal.value < windowPadding) {
|
192
|
-
newY = windowPadding
|
193
|
-
collapseData.value.isCollapsed = false
|
194
|
-
} else {
|
195
|
-
collapseData.value.isCollapsed = false
|
196
|
-
}
|
197
|
-
|
198
|
-
smoothMove(newX, newY)
|
199
|
-
}
|
200
|
-
const smoothMove = (newX: number, newY: number, duration = 100): void => {
|
201
|
-
const startX = leftLocal.value
|
202
|
-
const startY = topLocal.value
|
203
|
-
|
204
|
-
// Время начала анимации
|
205
|
-
const startTime = performance.now()
|
206
|
-
|
207
|
-
// Функция анимации
|
208
|
-
const animate = (currentTime: number): void => {
|
209
|
-
// Прошедшее время с начала анимации
|
210
|
-
const elapsedTime = currentTime - startTime
|
211
|
-
|
212
|
-
// Прогресс анимации (от 0 до 1)
|
213
|
-
const progress = Math.min(elapsedTime / duration, 1)
|
214
|
-
|
215
|
-
// Вычисляем новые координаты с помощью линейной интерполяции
|
216
|
-
leftLocal.value = startX + (newX - startX) * progress
|
217
|
-
topLocal.value = startY + (newY - startY) * progress
|
218
|
-
|
219
|
-
// Если анимация не завершена, продолжаем
|
220
|
-
if (progress < 1) {
|
221
|
-
requestAnimationFrame(animate)
|
222
|
-
}
|
223
|
-
}
|
224
|
-
|
225
|
-
// Запускаем анимацию
|
226
|
-
requestAnimationFrame(animate)
|
227
|
-
}
|
228
|
-
|
229
|
-
const onCollapse = (): void => {
|
230
|
-
collapseData.value.isCollapsed = !collapseData.value.isCollapsed
|
231
|
-
|
232
|
-
if (collapseData.value.isCollapsed) {
|
233
|
-
collapseData.value.cashTop = topLocal.value
|
234
|
-
collapseData.value.cashLeft = leftLocal.value
|
235
|
-
collapseData.value.cashWidth = widthLocal.value
|
236
|
-
collapseData.value.cashHeight = heightLocal.value
|
237
|
-
|
238
|
-
const globalWindowWidth = window.innerWidth
|
239
|
-
const globalWindowHeight = window.innerHeight
|
240
|
-
|
241
|
-
widthLocal.value = minWidth
|
242
|
-
// heightLocal.value = minHeight
|
243
|
-
leftLocal.value = globalWindowWidth - widthLocal.value - windowPadding
|
244
|
-
topLocal.value = globalWindowHeight - headerHeight
|
245
|
-
fixPosition()
|
246
|
-
} else {
|
247
|
-
leftLocal.value = collapseData.value.cashLeft
|
248
|
-
topLocal.value = collapseData.value.cashTop
|
249
|
-
widthLocal.value = collapseData.value.cashWidth
|
250
|
-
heightLocal.value = collapseData.value.cashHeight
|
251
|
-
fixPosition()
|
252
|
-
}
|
253
|
-
}
|
254
|
-
|
255
|
-
// Функция для ресайза
|
256
|
-
const startResize = (e: any, direction: any): void => {
|
257
|
-
e.preventDefault()
|
258
|
-
|
259
|
-
const startX = e.clientX
|
260
|
-
const startY = e.clientY
|
261
|
-
const startWidth = widthLocal.value
|
262
|
-
const startHeight = heightLocal.value
|
263
|
-
const startLeft = leftLocal.value
|
264
|
-
const startTop = topLocal.value
|
265
|
-
|
266
|
-
const onMouseMove = (e: any): void => {
|
267
|
-
const deltaX = e.clientX - startX
|
268
|
-
const deltaY = e.clientY - startY
|
269
|
-
|
270
|
-
if (direction.includes('right')) {
|
271
|
-
widthLocal.value = Math.max(minWidth, startWidth + deltaX)
|
272
|
-
}
|
273
|
-
if (direction.includes('bottom')) {
|
274
|
-
heightLocal.value = Math.max(minHeight, startHeight + deltaY)
|
275
|
-
}
|
276
|
-
if (direction.includes('left')) {
|
277
|
-
const newWidth = Math.max(minWidth, startWidth - deltaX)
|
278
|
-
if (newWidth !== widthLocal.value) {
|
279
|
-
leftLocal.value = startLeft + deltaX
|
280
|
-
widthLocal.value = newWidth
|
281
|
-
}
|
282
|
-
}
|
283
|
-
if (direction.includes('top')) {
|
284
|
-
const newHeight = Math.max(minHeight, startHeight - deltaY)
|
285
|
-
if (newHeight !== heightLocal.value) {
|
286
|
-
topLocal.value = startTop + deltaY
|
287
|
-
heightLocal.value = newHeight
|
288
|
-
}
|
289
|
-
}
|
290
|
-
fixSize()
|
291
|
-
}
|
292
|
-
|
293
|
-
const onMouseUp = (): void => {
|
294
|
-
document.removeEventListener('mousemove', onMouseMove)
|
295
|
-
document.removeEventListener('mouseup', onMouseUp)
|
296
|
-
isShowContentBlocker.value = false
|
297
|
-
fixPosition()
|
298
|
-
}
|
299
|
-
|
300
|
-
document.addEventListener('mousemove', onMouseMove)
|
301
|
-
document.addEventListener('mouseup', onMouseUp)
|
302
|
-
isShowContentBlocker.value = true
|
303
|
-
}
|
304
|
-
|
305
|
-
let resizeTimer: any = null
|
306
|
-
const globalWindowResize = (): void => {
|
307
|
-
clearTimeout(resizeTimer)
|
308
|
-
|
309
|
-
resizeTimer = setTimeout(() => {
|
310
|
-
fixSize()
|
311
|
-
fixPosition()
|
312
|
-
}, 250)
|
313
|
-
}
|
314
|
-
onMounted(() => {
|
315
|
-
window.addEventListener('resize', globalWindowResize)
|
316
|
-
fixSize()
|
317
|
-
fixPosition()
|
318
|
-
})
|
319
|
-
onUnmounted(() => {
|
320
|
-
window.removeEventListener('resize', globalWindowResize)
|
321
|
-
})
|
322
|
-
|
323
|
-
const onHide = (): void => {
|
324
|
-
emits('hide')
|
325
|
-
}
|
326
|
-
</script>
|
327
|
-
|
328
|
-
<style lang="scss">
|
329
|
-
.window-container {
|
330
|
-
position: absolute;
|
331
|
-
border-radius: 6px;
|
332
|
-
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
|
333
|
-
overflow: hidden;
|
334
|
-
background-color: #fff;
|
335
|
-
display: flex;
|
336
|
-
flex-direction: column;
|
337
|
-
//min-width: 300px;
|
338
|
-
//min-height: 200px;
|
339
|
-
z-index: var(--z-
|
340
|
-
|
341
|
-
&.loading {
|
342
|
-
opacity: 0.5;
|
343
|
-
}
|
344
|
-
|
345
|
-
.window-header {
|
346
|
-
background-color: #314351;
|
347
|
-
color: white;
|
348
|
-
padding: 6px 12px;
|
349
|
-
cursor: move;
|
350
|
-
user-select: none;
|
351
|
-
display: flex;
|
352
|
-
justify-content: space-between;
|
353
|
-
align-items: center;
|
354
|
-
|
355
|
-
.window-title {
|
356
|
-
font-size: 13px;
|
357
|
-
}
|
358
|
-
|
359
|
-
.window-btn {
|
360
|
-
background: none;
|
361
|
-
border: none;
|
362
|
-
color: #e4e4e4;
|
363
|
-
cursor: pointer;
|
364
|
-
width: 20px;
|
365
|
-
height: 20px;
|
366
|
-
display: flex;
|
367
|
-
justify-content: center;
|
368
|
-
align-items: center;
|
369
|
-
|
370
|
-
&:hover {
|
371
|
-
color: #ffffff;
|
372
|
-
}
|
373
|
-
}
|
374
|
-
}
|
375
|
-
|
376
|
-
.window-content {
|
377
|
-
position: relative;
|
378
|
-
flex-grow: 1;
|
379
|
-
|
380
|
-
.content-blocker {
|
381
|
-
position: absolute;
|
382
|
-
top: 0;
|
383
|
-
left: 0;
|
384
|
-
width: 100%;
|
385
|
-
height: 100%;
|
386
|
-
z-index: 1;
|
387
|
-
}
|
388
|
-
}
|
389
|
-
|
390
|
-
.resize-handle {
|
391
|
-
position: absolute;
|
392
|
-
background: transparent;
|
393
|
-
z-index: 10;
|
394
|
-
|
395
|
-
&.resize-top {
|
396
|
-
top: -3px;
|
397
|
-
left: 0;
|
398
|
-
right: 0;
|
399
|
-
height: 6px;
|
400
|
-
cursor: n-resize;
|
401
|
-
}
|
402
|
-
|
403
|
-
&.resize-right {
|
404
|
-
top: 0;
|
405
|
-
right: -3px;
|
406
|
-
bottom: 0;
|
407
|
-
width: 6px;
|
408
|
-
cursor: e-resize;
|
409
|
-
}
|
410
|
-
|
411
|
-
&.resize-bottom {
|
412
|
-
bottom: -3px;
|
413
|
-
left: 0;
|
414
|
-
right: 0;
|
415
|
-
height: 6px;
|
416
|
-
cursor: s-resize;
|
417
|
-
}
|
418
|
-
|
419
|
-
&.resize-left {
|
420
|
-
top: 0;
|
421
|
-
left: -3px;
|
422
|
-
bottom: 0;
|
423
|
-
width: 6px;
|
424
|
-
cursor: w-resize;
|
425
|
-
}
|
426
|
-
|
427
|
-
&.resize-top-left {
|
428
|
-
top: -6px;
|
429
|
-
left: -6px;
|
430
|
-
width: 12px;
|
431
|
-
height: 12px;
|
432
|
-
cursor: nw-resize;
|
433
|
-
}
|
434
|
-
|
435
|
-
&.resize-top-right {
|
436
|
-
top: -6px;
|
437
|
-
right: -6px;
|
438
|
-
width: 12px;
|
439
|
-
height: 12px;
|
440
|
-
cursor: ne-resize;
|
441
|
-
}
|
442
|
-
|
443
|
-
&.resize-bottom-left {
|
444
|
-
bottom: -6px;
|
445
|
-
left: -6px;
|
446
|
-
width: 12px;
|
447
|
-
height: 12px;
|
448
|
-
cursor: sw-resize;
|
449
|
-
}
|
450
|
-
|
451
|
-
&.resize-bottom-right {
|
452
|
-
bottom: -6px;
|
453
|
-
right: -6px;
|
454
|
-
width: 12px;
|
455
|
-
height: 12px;
|
456
|
-
cursor: se-resize;
|
457
|
-
}
|
458
|
-
}
|
459
|
-
}
|
460
|
-
</style>
|
1
|
+
<template>
|
2
|
+
<div
|
3
|
+
:class="['window-container', { loading: props.loading }]"
|
4
|
+
:style="{
|
5
|
+
left: leftLocal + 'px',
|
6
|
+
top: topLocal + 'px',
|
7
|
+
width: widthLocal + 'px',
|
8
|
+
height: heightLocal + 'px',
|
9
|
+
}"
|
10
|
+
>
|
11
|
+
<div
|
12
|
+
ref="headerEl"
|
13
|
+
class="window-header"
|
14
|
+
:style="{ height: headerHeight + 'px' }"
|
15
|
+
>
|
16
|
+
<slot name="header">
|
17
|
+
<div class="window-title text-ellipsis">{{ props.title }}</div>
|
18
|
+
</slot>
|
19
|
+
|
20
|
+
<div class="flex-align-center gap-2">
|
21
|
+
<button class="window-btn window-hide" @click="onCollapse">
|
22
|
+
<ui-icon
|
23
|
+
:name="collapseData.isCollapsed ? 'arrow' : 'check-line'"
|
24
|
+
class="hide-icon"
|
25
|
+
width="16"
|
26
|
+
height="16"
|
27
|
+
/>
|
28
|
+
</button>
|
29
|
+
<button class="window-btn window-view-full-screen" @click="toggle">
|
30
|
+
<ui-icon
|
31
|
+
name="view-full-screen"
|
32
|
+
class="view-full-screen-icon"
|
33
|
+
width="14"
|
34
|
+
height="14"
|
35
|
+
/>
|
36
|
+
</button>
|
37
|
+
<button class="window-btn window-close" @click="onHide">
|
38
|
+
<ui-icon name="close" class="close-icon" width="16" height="16" />
|
39
|
+
</button>
|
40
|
+
</div>
|
41
|
+
</div>
|
42
|
+
|
43
|
+
<div ref="windowContent" class="window-content">
|
44
|
+
<div v-show="props.loading" class="absolute-center">
|
45
|
+
<ui-icon name="spinner" />
|
46
|
+
</div>
|
47
|
+
<slot></slot>
|
48
|
+
<div v-show="isShowContentBlocker" class="content-blocker"></div>
|
49
|
+
</div>
|
50
|
+
|
51
|
+
<!-- Ручки для ресайза -->
|
52
|
+
<div
|
53
|
+
class="resize-handle resize-top"
|
54
|
+
@mousedown="startResize($event, 'top')"
|
55
|
+
></div>
|
56
|
+
<div
|
57
|
+
class="resize-handle resize-right"
|
58
|
+
@mousedown="startResize($event, 'right')"
|
59
|
+
></div>
|
60
|
+
<div
|
61
|
+
class="resize-handle resize-bottom"
|
62
|
+
@mousedown="startResize($event, 'bottom')"
|
63
|
+
></div>
|
64
|
+
<div
|
65
|
+
class="resize-handle resize-left"
|
66
|
+
@mousedown="startResize($event, 'left')"
|
67
|
+
></div>
|
68
|
+
<div
|
69
|
+
class="resize-handle resize-top-left"
|
70
|
+
@mousedown="startResize($event, 'top-left')"
|
71
|
+
></div>
|
72
|
+
<div
|
73
|
+
class="resize-handle resize-top-right"
|
74
|
+
@mousedown="startResize($event, 'top-right')"
|
75
|
+
></div>
|
76
|
+
<div
|
77
|
+
class="resize-handle resize-bottom-left"
|
78
|
+
@mousedown="startResize($event, 'bottom-left')"
|
79
|
+
></div>
|
80
|
+
<div
|
81
|
+
class="resize-handle resize-bottom-right"
|
82
|
+
@mousedown="startResize($event, 'bottom-right')"
|
83
|
+
></div>
|
84
|
+
</div>
|
85
|
+
</template>
|
86
|
+
|
87
|
+
<script setup lang="ts">
|
88
|
+
import { useDraggable, useFullscreen } from '@vueuse/core'
|
89
|
+
import type { UI_I_WindowCollapsedData } from '~/components/atoms/window/lib/models/interfaces'
|
90
|
+
import {
|
91
|
+
minWidth,
|
92
|
+
minHeight,
|
93
|
+
headerHeight,
|
94
|
+
windowPadding,
|
95
|
+
} from '~/components/atoms/window/lib/config/config'
|
96
|
+
|
97
|
+
const props = withDefaults(
|
98
|
+
defineProps<{
|
99
|
+
top: number
|
100
|
+
left: number
|
101
|
+
width: number
|
102
|
+
height: number
|
103
|
+
title: string
|
104
|
+
loading?: boolean
|
105
|
+
}>(),
|
106
|
+
{
|
107
|
+
loading: false,
|
108
|
+
}
|
109
|
+
)
|
110
|
+
const emits = defineEmits<{
|
111
|
+
(event: 'hide'): void
|
112
|
+
}>()
|
113
|
+
|
114
|
+
const windowContent = ref<any>(null)
|
115
|
+
const { toggle } = useFullscreen(windowContent)
|
116
|
+
|
117
|
+
const headerEl = ref(null)
|
118
|
+
const isShowContentBlocker = ref<boolean>(false)
|
119
|
+
|
120
|
+
const leftLocal = ref<number>(props.left)
|
121
|
+
const topLocal = ref<number>(props.top - headerHeight)
|
122
|
+
const widthLocal = ref<number>(Math.max(minWidth, props.width))
|
123
|
+
const heightLocal = ref<number>(
|
124
|
+
Math.max(minHeight, props.height + headerHeight)
|
125
|
+
)
|
126
|
+
|
127
|
+
const collapseData = ref<UI_I_WindowCollapsedData>({
|
128
|
+
isCollapsed: false,
|
129
|
+
cashTop: -1,
|
130
|
+
cashLeft: -1,
|
131
|
+
cashWidth: -1,
|
132
|
+
cashHeight: -1,
|
133
|
+
})
|
134
|
+
|
135
|
+
// Используем vueuse для перетаскивания
|
136
|
+
useDraggable(headerEl, {
|
137
|
+
initialValue: { x: leftLocal.value, y: topLocal.value },
|
138
|
+
onMove({ x: newX, y: newY }) {
|
139
|
+
leftLocal.value = newX
|
140
|
+
topLocal.value = newY
|
141
|
+
},
|
142
|
+
onStart() {
|
143
|
+
isShowContentBlocker.value = true
|
144
|
+
if (!collapseData.value.isCollapsed) {
|
145
|
+
collapseData.value.cashTop = topLocal.value
|
146
|
+
collapseData.value.cashLeft = leftLocal.value
|
147
|
+
collapseData.value.cashWidth = widthLocal.value
|
148
|
+
collapseData.value.cashHeight = heightLocal.value
|
149
|
+
}
|
150
|
+
},
|
151
|
+
onEnd() {
|
152
|
+
isShowContentBlocker.value = false
|
153
|
+
fixPosition()
|
154
|
+
},
|
155
|
+
})
|
156
|
+
|
157
|
+
const fixSize = (): void => {
|
158
|
+
const globalWindowWidth = window.innerWidth
|
159
|
+
const globalWindowHeight = window.innerHeight
|
160
|
+
|
161
|
+
widthLocal.value = Math.min(
|
162
|
+
globalWindowWidth - windowPadding * 2,
|
163
|
+
widthLocal.value
|
164
|
+
)
|
165
|
+
heightLocal.value = Math.min(
|
166
|
+
globalWindowHeight - windowPadding * 2,
|
167
|
+
heightLocal.value
|
168
|
+
)
|
169
|
+
}
|
170
|
+
const fixPosition = (): void => {
|
171
|
+
const globalWindowWidth = window.innerWidth
|
172
|
+
const globalWindowHeight = window.innerHeight
|
173
|
+
let newX = leftLocal.value
|
174
|
+
let newY = topLocal.value
|
175
|
+
|
176
|
+
if (leftLocal.value + widthLocal.value > globalWindowWidth - windowPadding) {
|
177
|
+
newX = globalWindowWidth - widthLocal.value - windowPadding
|
178
|
+
} else if (leftLocal.value < windowPadding) {
|
179
|
+
newX = windowPadding
|
180
|
+
}
|
181
|
+
|
182
|
+
if (topLocal.value + heightLocal.value / 2 > globalWindowHeight) {
|
183
|
+
newY = globalWindowHeight - headerHeight
|
184
|
+
collapseData.value.isCollapsed = true
|
185
|
+
} else if (
|
186
|
+
topLocal.value + heightLocal.value >
|
187
|
+
globalWindowHeight - windowPadding
|
188
|
+
) {
|
189
|
+
newY = globalWindowHeight - heightLocal.value - windowPadding
|
190
|
+
collapseData.value.isCollapsed = false
|
191
|
+
} else if (topLocal.value < windowPadding) {
|
192
|
+
newY = windowPadding
|
193
|
+
collapseData.value.isCollapsed = false
|
194
|
+
} else {
|
195
|
+
collapseData.value.isCollapsed = false
|
196
|
+
}
|
197
|
+
|
198
|
+
smoothMove(newX, newY)
|
199
|
+
}
|
200
|
+
const smoothMove = (newX: number, newY: number, duration = 100): void => {
|
201
|
+
const startX = leftLocal.value
|
202
|
+
const startY = topLocal.value
|
203
|
+
|
204
|
+
// Время начала анимации
|
205
|
+
const startTime = performance.now()
|
206
|
+
|
207
|
+
// Функция анимации
|
208
|
+
const animate = (currentTime: number): void => {
|
209
|
+
// Прошедшее время с начала анимации
|
210
|
+
const elapsedTime = currentTime - startTime
|
211
|
+
|
212
|
+
// Прогресс анимации (от 0 до 1)
|
213
|
+
const progress = Math.min(elapsedTime / duration, 1)
|
214
|
+
|
215
|
+
// Вычисляем новые координаты с помощью линейной интерполяции
|
216
|
+
leftLocal.value = startX + (newX - startX) * progress
|
217
|
+
topLocal.value = startY + (newY - startY) * progress
|
218
|
+
|
219
|
+
// Если анимация не завершена, продолжаем
|
220
|
+
if (progress < 1) {
|
221
|
+
requestAnimationFrame(animate)
|
222
|
+
}
|
223
|
+
}
|
224
|
+
|
225
|
+
// Запускаем анимацию
|
226
|
+
requestAnimationFrame(animate)
|
227
|
+
}
|
228
|
+
|
229
|
+
const onCollapse = (): void => {
|
230
|
+
collapseData.value.isCollapsed = !collapseData.value.isCollapsed
|
231
|
+
|
232
|
+
if (collapseData.value.isCollapsed) {
|
233
|
+
collapseData.value.cashTop = topLocal.value
|
234
|
+
collapseData.value.cashLeft = leftLocal.value
|
235
|
+
collapseData.value.cashWidth = widthLocal.value
|
236
|
+
collapseData.value.cashHeight = heightLocal.value
|
237
|
+
|
238
|
+
const globalWindowWidth = window.innerWidth
|
239
|
+
const globalWindowHeight = window.innerHeight
|
240
|
+
|
241
|
+
widthLocal.value = minWidth
|
242
|
+
// heightLocal.value = minHeight
|
243
|
+
leftLocal.value = globalWindowWidth - widthLocal.value - windowPadding
|
244
|
+
topLocal.value = globalWindowHeight - headerHeight
|
245
|
+
fixPosition()
|
246
|
+
} else {
|
247
|
+
leftLocal.value = collapseData.value.cashLeft
|
248
|
+
topLocal.value = collapseData.value.cashTop
|
249
|
+
widthLocal.value = collapseData.value.cashWidth
|
250
|
+
heightLocal.value = collapseData.value.cashHeight
|
251
|
+
fixPosition()
|
252
|
+
}
|
253
|
+
}
|
254
|
+
|
255
|
+
// Функция для ресайза
|
256
|
+
const startResize = (e: any, direction: any): void => {
|
257
|
+
e.preventDefault()
|
258
|
+
|
259
|
+
const startX = e.clientX
|
260
|
+
const startY = e.clientY
|
261
|
+
const startWidth = widthLocal.value
|
262
|
+
const startHeight = heightLocal.value
|
263
|
+
const startLeft = leftLocal.value
|
264
|
+
const startTop = topLocal.value
|
265
|
+
|
266
|
+
const onMouseMove = (e: any): void => {
|
267
|
+
const deltaX = e.clientX - startX
|
268
|
+
const deltaY = e.clientY - startY
|
269
|
+
|
270
|
+
if (direction.includes('right')) {
|
271
|
+
widthLocal.value = Math.max(minWidth, startWidth + deltaX)
|
272
|
+
}
|
273
|
+
if (direction.includes('bottom')) {
|
274
|
+
heightLocal.value = Math.max(minHeight, startHeight + deltaY)
|
275
|
+
}
|
276
|
+
if (direction.includes('left')) {
|
277
|
+
const newWidth = Math.max(minWidth, startWidth - deltaX)
|
278
|
+
if (newWidth !== widthLocal.value) {
|
279
|
+
leftLocal.value = startLeft + deltaX
|
280
|
+
widthLocal.value = newWidth
|
281
|
+
}
|
282
|
+
}
|
283
|
+
if (direction.includes('top')) {
|
284
|
+
const newHeight = Math.max(minHeight, startHeight - deltaY)
|
285
|
+
if (newHeight !== heightLocal.value) {
|
286
|
+
topLocal.value = startTop + deltaY
|
287
|
+
heightLocal.value = newHeight
|
288
|
+
}
|
289
|
+
}
|
290
|
+
fixSize()
|
291
|
+
}
|
292
|
+
|
293
|
+
const onMouseUp = (): void => {
|
294
|
+
document.removeEventListener('mousemove', onMouseMove)
|
295
|
+
document.removeEventListener('mouseup', onMouseUp)
|
296
|
+
isShowContentBlocker.value = false
|
297
|
+
fixPosition()
|
298
|
+
}
|
299
|
+
|
300
|
+
document.addEventListener('mousemove', onMouseMove)
|
301
|
+
document.addEventListener('mouseup', onMouseUp)
|
302
|
+
isShowContentBlocker.value = true
|
303
|
+
}
|
304
|
+
|
305
|
+
let resizeTimer: any = null
|
306
|
+
const globalWindowResize = (): void => {
|
307
|
+
clearTimeout(resizeTimer)
|
308
|
+
|
309
|
+
resizeTimer = setTimeout(() => {
|
310
|
+
fixSize()
|
311
|
+
fixPosition()
|
312
|
+
}, 250)
|
313
|
+
}
|
314
|
+
onMounted(() => {
|
315
|
+
window.addEventListener('resize', globalWindowResize)
|
316
|
+
fixSize()
|
317
|
+
fixPosition()
|
318
|
+
})
|
319
|
+
onUnmounted(() => {
|
320
|
+
window.removeEventListener('resize', globalWindowResize)
|
321
|
+
})
|
322
|
+
|
323
|
+
const onHide = (): void => {
|
324
|
+
emits('hide')
|
325
|
+
}
|
326
|
+
</script>
|
327
|
+
|
328
|
+
<style lang="scss">
|
329
|
+
.window-container {
|
330
|
+
position: absolute;
|
331
|
+
border-radius: 6px;
|
332
|
+
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
|
333
|
+
overflow: hidden;
|
334
|
+
background-color: #fff;
|
335
|
+
display: flex;
|
336
|
+
flex-direction: column;
|
337
|
+
//min-width: 300px;
|
338
|
+
//min-height: 200px;
|
339
|
+
z-index: var(--z-fixed);
|
340
|
+
|
341
|
+
&.loading {
|
342
|
+
opacity: 0.5;
|
343
|
+
}
|
344
|
+
|
345
|
+
.window-header {
|
346
|
+
background-color: #314351;
|
347
|
+
color: white;
|
348
|
+
padding: 6px 12px;
|
349
|
+
cursor: move;
|
350
|
+
user-select: none;
|
351
|
+
display: flex;
|
352
|
+
justify-content: space-between;
|
353
|
+
align-items: center;
|
354
|
+
|
355
|
+
.window-title {
|
356
|
+
font-size: 13px;
|
357
|
+
}
|
358
|
+
|
359
|
+
.window-btn {
|
360
|
+
background: none;
|
361
|
+
border: none;
|
362
|
+
color: #e4e4e4;
|
363
|
+
cursor: pointer;
|
364
|
+
width: 20px;
|
365
|
+
height: 20px;
|
366
|
+
display: flex;
|
367
|
+
justify-content: center;
|
368
|
+
align-items: center;
|
369
|
+
|
370
|
+
&:hover {
|
371
|
+
color: #ffffff;
|
372
|
+
}
|
373
|
+
}
|
374
|
+
}
|
375
|
+
|
376
|
+
.window-content {
|
377
|
+
position: relative;
|
378
|
+
flex-grow: 1;
|
379
|
+
|
380
|
+
.content-blocker {
|
381
|
+
position: absolute;
|
382
|
+
top: 0;
|
383
|
+
left: 0;
|
384
|
+
width: 100%;
|
385
|
+
height: 100%;
|
386
|
+
z-index: 1;
|
387
|
+
}
|
388
|
+
}
|
389
|
+
|
390
|
+
.resize-handle {
|
391
|
+
position: absolute;
|
392
|
+
background: transparent;
|
393
|
+
z-index: 10;
|
394
|
+
|
395
|
+
&.resize-top {
|
396
|
+
top: -3px;
|
397
|
+
left: 0;
|
398
|
+
right: 0;
|
399
|
+
height: 6px;
|
400
|
+
cursor: n-resize;
|
401
|
+
}
|
402
|
+
|
403
|
+
&.resize-right {
|
404
|
+
top: 0;
|
405
|
+
right: -3px;
|
406
|
+
bottom: 0;
|
407
|
+
width: 6px;
|
408
|
+
cursor: e-resize;
|
409
|
+
}
|
410
|
+
|
411
|
+
&.resize-bottom {
|
412
|
+
bottom: -3px;
|
413
|
+
left: 0;
|
414
|
+
right: 0;
|
415
|
+
height: 6px;
|
416
|
+
cursor: s-resize;
|
417
|
+
}
|
418
|
+
|
419
|
+
&.resize-left {
|
420
|
+
top: 0;
|
421
|
+
left: -3px;
|
422
|
+
bottom: 0;
|
423
|
+
width: 6px;
|
424
|
+
cursor: w-resize;
|
425
|
+
}
|
426
|
+
|
427
|
+
&.resize-top-left {
|
428
|
+
top: -6px;
|
429
|
+
left: -6px;
|
430
|
+
width: 12px;
|
431
|
+
height: 12px;
|
432
|
+
cursor: nw-resize;
|
433
|
+
}
|
434
|
+
|
435
|
+
&.resize-top-right {
|
436
|
+
top: -6px;
|
437
|
+
right: -6px;
|
438
|
+
width: 12px;
|
439
|
+
height: 12px;
|
440
|
+
cursor: ne-resize;
|
441
|
+
}
|
442
|
+
|
443
|
+
&.resize-bottom-left {
|
444
|
+
bottom: -6px;
|
445
|
+
left: -6px;
|
446
|
+
width: 12px;
|
447
|
+
height: 12px;
|
448
|
+
cursor: sw-resize;
|
449
|
+
}
|
450
|
+
|
451
|
+
&.resize-bottom-right {
|
452
|
+
bottom: -6px;
|
453
|
+
right: -6px;
|
454
|
+
width: 12px;
|
455
|
+
height: 12px;
|
456
|
+
cursor: se-resize;
|
457
|
+
}
|
458
|
+
}
|
459
|
+
}
|
460
|
+
</style>
|