@skyservice-developers/vue-dev-kit 1.1.2 → 1.2.0
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/vue2/style.css +1 -1
- package/dist/vue2/vue-dev-kit.cjs +1 -1
- package/dist/vue2/vue-dev-kit.js +679 -585
- package/dist/vue3/style.css +1 -1
- package/dist/vue3/vue-dev-kit.cjs +1 -1
- package/dist/vue3/vue-dev-kit.js +625 -553
- package/package.json +2 -1
- package/src/shared/types/index.d.ts +15 -0
- package/src/shared/utils/index.js +1 -1
- package/src/shared/utils/webviewCheck.js +8 -0
- package/src/vue2/components/Header.vue +280 -100
- package/src/vue3/components/DialogNext.vue +1 -1
- package/src/vue3/components/Header.vue +290 -94
|
@@ -1,49 +1,85 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<header class="sky-header">
|
|
3
|
-
<div class="
|
|
4
|
-
<div class="header-
|
|
5
|
-
<
|
|
2
|
+
<header class="sky-header-container">
|
|
3
|
+
<div class="topmenubox">
|
|
4
|
+
<div class="header-left">
|
|
5
|
+
<button
|
|
6
|
+
v-if="shouldShowBackButton"
|
|
7
|
+
class="btn-back"
|
|
8
|
+
@click="handleBack"
|
|
9
|
+
:title="backButtonTitle"
|
|
10
|
+
>
|
|
11
|
+
<svg
|
|
12
|
+
width="15"
|
|
13
|
+
height="15"
|
|
14
|
+
viewBox="0 0 451.847 451.847"
|
|
15
|
+
style="transform: rotate(90deg)"
|
|
16
|
+
>
|
|
17
|
+
<path
|
|
18
|
+
fill="currentColor"
|
|
19
|
+
d="M225.923,354.706c-8.098,0-16.195-3.092-22.369-9.263L9.27,151.157c-12.359-12.359-12.359-32.397,0-44.751c12.354-12.354,32.388-12.354,44.748,0l171.905,171.915l171.906-171.909c12.359-12.354,32.391-12.354,44.744,0c12.365,12.354,12.365,32.392,0,44.751L248.292,345.449C242.115,351.621,234.018,354.706,225.923,354.706z"
|
|
20
|
+
/>
|
|
21
|
+
</svg>
|
|
22
|
+
</button>
|
|
23
|
+
<div ref="dropdownRef" class="titleAndDesc">
|
|
6
24
|
<button
|
|
7
|
-
|
|
8
|
-
class="
|
|
9
|
-
@click="
|
|
10
|
-
:title="backButtonTitle"
|
|
25
|
+
class="title-dropdown-toggle"
|
|
26
|
+
:class="{ 'title-dropdown-toggle-active': sortedItems.length }"
|
|
27
|
+
@click="toggleDropdown"
|
|
11
28
|
>
|
|
12
|
-
<svg
|
|
13
|
-
width="15"
|
|
14
|
-
height="15"
|
|
15
|
-
viewBox="0 0 451.847 451.847"
|
|
16
|
-
style="transform: rotate(90deg)"
|
|
17
|
-
>
|
|
18
|
-
<path
|
|
19
|
-
fill="currentColor"
|
|
20
|
-
d="M225.923,354.706c-8.098,0-16.195-3.092-22.369-9.263L9.27,151.157c-12.359-12.359-12.359-32.397,0-44.751c12.354-12.354,32.388-12.354,44.748,0l171.905,171.915l171.906-171.909c12.359-12.354,32.391-12.354,44.744,0c12.365,12.354,12.365,32.392,0,44.751L248.292,345.449C242.115,351.621,234.018,354.706,225.923,354.706z"
|
|
21
|
-
/>
|
|
22
|
-
</svg>
|
|
23
|
-
</button>
|
|
24
|
-
<div class="header-title-content">
|
|
25
29
|
<slot name="title">
|
|
26
|
-
<h4 class="
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
+
<h4 class="notPadding" style="margin-bottom: 4px">
|
|
31
|
+
<span class="topmenu-title">{{ title }}</span>
|
|
32
|
+
<svg
|
|
33
|
+
v-if="sortedItems.length"
|
|
34
|
+
class="arrow"
|
|
35
|
+
:class="{ open: isDropdownOpen }"
|
|
36
|
+
width="12"
|
|
37
|
+
height="12"
|
|
38
|
+
viewBox="0 0 451.847 451.847"
|
|
39
|
+
style="flex-shrink: 0"
|
|
40
|
+
>
|
|
41
|
+
<path
|
|
42
|
+
fill="currentColor"
|
|
43
|
+
d="M225.923,354.706c-8.098,0-16.195-3.092-22.369-9.263L9.27,151.157c-12.359-12.359-12.359-32.397,0-44.751c12.354-12.354,32.388-12.354,44.748,0l171.905,171.915l171.906-171.909c12.359-12.354,32.391-12.354,44.744,0c12.365,12.354,12.365,32.392,0,44.751L248.292,345.449C242.115,351.621,234.018,354.706,225.923,354.706z"
|
|
44
|
+
/>
|
|
45
|
+
</svg>
|
|
46
|
+
</h4>
|
|
30
47
|
</slot>
|
|
48
|
+
</button>
|
|
49
|
+
<div v-if="isDropdownOpen && sortedItems.length" class="title-dropdown">
|
|
50
|
+
<div class="title-dropdown-header">{{ dropdownTitle }}</div>
|
|
51
|
+
<div class="title-dropdown-divider"></div>
|
|
52
|
+
<div
|
|
53
|
+
v-for="(item, index) in sortedItems"
|
|
54
|
+
:key="index"
|
|
55
|
+
class="title-dropdown-item"
|
|
56
|
+
@click="selectItem(item)"
|
|
57
|
+
>
|
|
58
|
+
<p class="pageName">{{ capitalize(item.name) }}</p>
|
|
59
|
+
<small class="pageVisit">
|
|
60
|
+
({{ visitLabel }} {{ getTimeAgo(item.lastVisit) }})
|
|
61
|
+
</small>
|
|
62
|
+
</div>
|
|
31
63
|
</div>
|
|
64
|
+
<slot name="subtitle">
|
|
65
|
+
<p v-if="subtitle" class="topmenu-description">{{ subtitle }}</p>
|
|
66
|
+
</slot>
|
|
32
67
|
</div>
|
|
68
|
+
</div>
|
|
33
69
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
</div>
|
|
70
|
+
<div class="topmenubox-button">
|
|
71
|
+
<!-- Порожні блоки ремонтують відображення на windows в додатку, не видаляти! -->
|
|
72
|
+
<div></div>
|
|
73
|
+
<slot></slot>
|
|
74
|
+
<div></div>
|
|
40
75
|
</div>
|
|
41
76
|
</div>
|
|
42
77
|
</header>
|
|
43
78
|
</template>
|
|
44
79
|
|
|
45
80
|
<script setup>
|
|
46
|
-
import { computed } from
|
|
81
|
+
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
|
82
|
+
import { isInIframe } from '../../shared/utils/webviewCheck'
|
|
47
83
|
|
|
48
84
|
const props = defineProps({
|
|
49
85
|
title: {
|
|
@@ -66,64 +102,238 @@ const props = defineProps({
|
|
|
66
102
|
type: Function,
|
|
67
103
|
default: null,
|
|
68
104
|
},
|
|
105
|
+
dropdownItems: {
|
|
106
|
+
type: Array,
|
|
107
|
+
default: () => [],
|
|
108
|
+
},
|
|
109
|
+
dropdownTitle: {
|
|
110
|
+
type: String,
|
|
111
|
+
default: "Останні відвідані розділи",
|
|
112
|
+
},
|
|
113
|
+
visitLabel: {
|
|
114
|
+
type: String,
|
|
115
|
+
default: "Останнє відвідування",
|
|
116
|
+
},
|
|
69
117
|
});
|
|
70
118
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
119
|
+
const emit = defineEmits(['back', 'navigate'])
|
|
120
|
+
|
|
121
|
+
const dropdownRef = ref(null)
|
|
122
|
+
const isDropdownOpen = ref(false)
|
|
123
|
+
|
|
124
|
+
const sortedItems = computed(() => {
|
|
125
|
+
return [...props.dropdownItems].sort((a, b) => b.lastVisit - a.lastVisit)
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
const toggleDropdown = () => {
|
|
129
|
+
if (sortedItems.value.length) {
|
|
130
|
+
isDropdownOpen.value = !isDropdownOpen.value
|
|
77
131
|
}
|
|
78
|
-
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const closeDropdown = () => {
|
|
135
|
+
isDropdownOpen.value = false
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const selectItem = (item) => {
|
|
139
|
+
emit('navigate', item.path)
|
|
140
|
+
closeDropdown()
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const capitalize = (str) => {
|
|
144
|
+
if (!str) return ''
|
|
145
|
+
return str.charAt(0).toUpperCase() + str.slice(1)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const getTimeAgo = (lastVisit) => {
|
|
149
|
+
const now = Date.now()
|
|
150
|
+
const diff = now - lastVisit
|
|
151
|
+
|
|
152
|
+
const seconds = Math.floor(diff / 1000)
|
|
153
|
+
const minutes = Math.floor(seconds / 60)
|
|
154
|
+
const hours = Math.floor(minutes / 60)
|
|
155
|
+
const days = Math.floor(hours / 24)
|
|
156
|
+
|
|
157
|
+
if (days > 0) return `${days}д тому`
|
|
158
|
+
if (hours > 0) return `${hours}год тому`
|
|
159
|
+
if (minutes > 0) return `${minutes}хв тому`
|
|
160
|
+
return `${seconds}с тому`
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const handleClickOutside = (e) => {
|
|
164
|
+
if (dropdownRef.value && !dropdownRef.value.contains(e.target)) {
|
|
165
|
+
closeDropdown()
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
onMounted(() => {
|
|
170
|
+
document.addEventListener('click', handleClickOutside, true)
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
onUnmounted(() => {
|
|
174
|
+
document.removeEventListener('click', handleClickOutside, true)
|
|
175
|
+
})
|
|
79
176
|
|
|
80
177
|
// Показуємо кнопку якщо є backEvent АБО (showBackButton=true І сторінка в iframe)
|
|
81
178
|
const shouldShowBackButton = computed(() => {
|
|
82
|
-
return props.backEvent || (props.showBackButton && isInIframe
|
|
179
|
+
return props.backEvent || (props.showBackButton && isInIframe());
|
|
83
180
|
});
|
|
84
181
|
|
|
85
|
-
// Обробник кнопки "Назад" - викликає backEvent або відправляє повідомлення батьківському вікну
|
|
86
182
|
const handleBack = () => {
|
|
87
|
-
if (props.backEvent)
|
|
88
|
-
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
}
|
|
92
|
-
};
|
|
183
|
+
if (props.backEvent) return props.backEvent()
|
|
184
|
+
|
|
185
|
+
window.parent.postMessage({ type: 'exit' }, '*')
|
|
186
|
+
}
|
|
93
187
|
</script>
|
|
94
188
|
|
|
95
189
|
<style scoped>
|
|
96
|
-
.sky-header {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
190
|
+
.sky-header-container {
|
|
191
|
+
width: 100%;
|
|
192
|
+
min-height: var(--sky-header-min-height, 82px);
|
|
193
|
+
background-color: var(--sky-header-bg, transparent);
|
|
194
|
+
display: flex;
|
|
195
|
+
flex-direction: row;
|
|
196
|
+
padding: var(--sky-header-padding, 10px);
|
|
102
197
|
border-bottom: 1px solid var(--sky-header-border-color, #dee2e6);
|
|
103
|
-
z-index: var(--sky-header-z-index,
|
|
104
|
-
|
|
198
|
+
z-index: var(--sky-header-z-index, 4);
|
|
199
|
+
position: relative;
|
|
105
200
|
}
|
|
106
201
|
|
|
107
|
-
.
|
|
108
|
-
|
|
109
|
-
margin: 0 auto;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
.header-top {
|
|
202
|
+
.topmenubox {
|
|
203
|
+
width: 100%;
|
|
113
204
|
display: flex;
|
|
205
|
+
padding: 4px;
|
|
114
206
|
justify-content: space-between;
|
|
115
207
|
align-items: center;
|
|
208
|
+
background-color: transparent;
|
|
116
209
|
}
|
|
117
210
|
|
|
118
|
-
.header-
|
|
211
|
+
.header-left {
|
|
119
212
|
display: flex;
|
|
120
213
|
align-items: center;
|
|
121
214
|
gap: 12px;
|
|
122
215
|
}
|
|
123
216
|
|
|
124
|
-
.
|
|
217
|
+
.titleAndDesc {
|
|
125
218
|
display: flex;
|
|
126
219
|
flex-direction: column;
|
|
220
|
+
position: relative;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.notPadding {
|
|
224
|
+
margin: 0;
|
|
225
|
+
padding: 0;
|
|
226
|
+
font-size: var(--sky-header-title-size, 18px);
|
|
227
|
+
font-weight: var(--sky-header-title-weight, 500);
|
|
228
|
+
color: var(--sky-header-title-color, #252525);
|
|
229
|
+
line-height: 1.5;
|
|
230
|
+
user-select: none;
|
|
231
|
+
display: flex;
|
|
232
|
+
align-items: center;
|
|
233
|
+
flex-wrap: nowrap;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.topmenu-title {
|
|
237
|
+
white-space: pre-line;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.title-dropdown-toggle {
|
|
241
|
+
display: flex;
|
|
242
|
+
flex-direction: row;
|
|
243
|
+
padding: 0;
|
|
244
|
+
margin: 0;
|
|
245
|
+
background: transparent;
|
|
246
|
+
border: none;
|
|
247
|
+
text-align: left;
|
|
248
|
+
font: inherit;
|
|
249
|
+
color: inherit;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
.title-dropdown-toggle-active {
|
|
253
|
+
cursor: pointer;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
.arrow {
|
|
257
|
+
width: 12px;
|
|
258
|
+
position: relative;
|
|
259
|
+
margin-left: 5px;
|
|
260
|
+
flex-shrink: 0;
|
|
261
|
+
transition: transform 0.25s ease;
|
|
262
|
+
color: var(--sky-header-title-color, #252525);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
.arrow.open {
|
|
266
|
+
transform: rotate(180deg);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
.title-dropdown {
|
|
270
|
+
position: absolute;
|
|
271
|
+
top: 100%;
|
|
272
|
+
left: 0;
|
|
273
|
+
min-width: 240px;
|
|
274
|
+
background: white;
|
|
275
|
+
border-radius: 5px;
|
|
276
|
+
box-shadow: 0 1px 12px rgba(0, 0, 0, 0.1);
|
|
277
|
+
border: none;
|
|
278
|
+
z-index: 10;
|
|
279
|
+
padding: 4px 0;
|
|
280
|
+
margin-top: 4px;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
.title-dropdown-header {
|
|
284
|
+
padding: 4px 24px;
|
|
285
|
+
font-size: 13px;
|
|
286
|
+
color: #6c757d;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
.title-dropdown-divider {
|
|
290
|
+
height: 0;
|
|
291
|
+
margin: 4px 0;
|
|
292
|
+
border-top: 1px solid #e9ecef;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
.title-dropdown-item {
|
|
296
|
+
padding: 4px 24px;
|
|
297
|
+
cursor: pointer;
|
|
298
|
+
transition: background-color 0.1s;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
.title-dropdown-item:hover {
|
|
302
|
+
background-color: #f8f9fa;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
.title-dropdown-item:active {
|
|
306
|
+
background-color: #e9ecef;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
.pageName {
|
|
310
|
+
padding-bottom: 0;
|
|
311
|
+
margin: 0;
|
|
312
|
+
font-weight: 500;
|
|
313
|
+
font-size: 14px;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
.pageVisit {
|
|
317
|
+
color: gray;
|
|
318
|
+
font-weight: 400;
|
|
319
|
+
font-size: 11px;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
.topmenu-description {
|
|
323
|
+
margin: 0;
|
|
324
|
+
margin-bottom: 5px;
|
|
325
|
+
color: var(--sky-header-subtitle-color, #5d5d5d);
|
|
326
|
+
font-size: 13px;
|
|
327
|
+
font-weight: 400;
|
|
328
|
+
line-height: 1.5;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
.topmenubox-button {
|
|
332
|
+
display: flex;
|
|
333
|
+
flex-direction: row;
|
|
334
|
+
align-items: center;
|
|
335
|
+
justify-content: space-between;
|
|
336
|
+
gap: var(--sky-header-actions-gap, 12px);
|
|
127
337
|
}
|
|
128
338
|
|
|
129
339
|
.btn-back {
|
|
@@ -154,41 +364,27 @@ const handleBack = () => {
|
|
|
154
364
|
background-color: var(--sky-header-back-btn-active-bg, #e9ecef);
|
|
155
365
|
}
|
|
156
366
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
.header-subtitle {
|
|
167
|
-
font-size: var(--sky-header-subtitle-size, 14px);
|
|
168
|
-
color: var(--sky-header-subtitle-color, #6c757d);
|
|
169
|
-
font-weight: 400;
|
|
170
|
-
line-height: 1.5;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
.header-actions {
|
|
174
|
-
display: flex;
|
|
175
|
-
gap: var(--sky-header-actions-gap, 12px);
|
|
367
|
+
/* Responsive: <500px — hide description, smaller title */
|
|
368
|
+
@media (max-width: 499px) {
|
|
369
|
+
.topmenu-description {
|
|
370
|
+
display: none;
|
|
371
|
+
}
|
|
372
|
+
.notPadding {
|
|
373
|
+
font-size: 13px;
|
|
374
|
+
}
|
|
176
375
|
}
|
|
177
376
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
.header-top {
|
|
184
|
-
flex-direction: column;
|
|
185
|
-
align-items: flex-start;
|
|
186
|
-
/* gap: 12px; */
|
|
377
|
+
/* Responsive: 500-1099px — smaller description */
|
|
378
|
+
@media (min-width: 500px) and (max-width: 1099px) {
|
|
379
|
+
.topmenu-description {
|
|
380
|
+
font-size: 11px;
|
|
187
381
|
}
|
|
382
|
+
}
|
|
188
383
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
384
|
+
/* iOS safe area */
|
|
385
|
+
@supports (padding-top: env(safe-area-inset-top)) {
|
|
386
|
+
.sky-header-container {
|
|
387
|
+
padding-top: calc(10px + env(safe-area-inset-top));
|
|
192
388
|
}
|
|
193
389
|
}
|
|
194
390
|
</style>
|