vue-stack-tabs 0.1.40 → 0.1.41

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,690 +1,694 @@
1
- import type { ITabBase, ITabData, ITabItem, ITabPage } from '../model/TabModel'
2
- import {
3
- defineComponent,
4
- nextTick,
5
- onActivated,
6
- onDeactivated,
7
- onMounted,
8
- onUnmounted,
9
- ref,
10
- unref
11
- } from 'vue'
12
- import type { DefineComponent, VNode } from 'vue'
13
- import { useRouter } from 'vue-router'
14
- import type { RouteLocationNormalizedLoaded } from 'vue-router'
15
- import { ulid } from 'ulid'
16
- import { encodeTabInfo, createPageId, decodeTabInfo } from '../utils/TabIdHelper'
17
- import { defu } from 'defu'
18
- import { Stack } from '../model/TabModel'
19
- import { uriDecode } from '../utils/UriHelper'
20
- import PageLoading from '../components/PageLoading.vue'
21
- import { MittType, useEmitter } from './useTabMitt'
22
- import { useI18n } from 'vue-i18n-lite'
23
- const tabs = ref<ITabItem[]>([])
24
- const defaultTabs: ITabItem[] = []
25
- // cache
26
- const caches = ref<string[]>([])
27
- // Dynamic components
28
- const components = new Map<string, any>()
29
- const deletableCache = new Set<String>()
30
- const deletableTab = new Set<String>()
31
- const pageShown = ref<boolean>(true)
32
- const SESSION_TAB_NAME = 'stacktab-active-tab'
33
- const pageScroller = new Map<string, Map<string, any>>()
34
- let initialed = false
35
- let max = 0
36
- let scrollbar = false
37
- export default () => {
38
- const router = useRouter()
39
- const emitter = useEmitter()
40
- const { t } = useI18n()
41
- const size = (): number => {
42
- return tabs.value.length
43
- }
44
-
45
- /**
46
- * Init the Tab list
47
- */
48
- const initial = (staticTabs: ITabData[]) => {
49
- // add default tabs
50
- // defaultTabs.splice(0)
51
- // caches.value.splice(0)
52
- // deletableCache.clear()
53
- // deletableTab.clear()
54
- // components.clear()
55
- for (const item of staticTabs) {
56
- // const id = ulid()
57
- const fullItem = defu(item, { id: ulid(), refreshable: true, closable: true, iframe: false })
58
- const __tab = encodeTabInfo(fullItem)
59
- const uri = uriDecode(fullItem.path)
60
- const config = defu(fullItem, {
61
- closable: true,
62
- refreshable: true,
63
- iframe: false
64
- })
65
- const cacheName = createPageId(fullItem.id, uri.path, uri.query)
66
- const page: ITabPage = {
67
- id: cacheName,
68
- tabId: fullItem.id,
69
- path: uri.path,
70
- query: defu(uri.query, { __tab })
71
- }
72
- const pages = new Stack<ITabPage>()
73
- pages.push(page)
74
- const tab: ITabItem = {
75
- id: fullItem.id,
76
- title: config.title,
77
- closable: config.closable,
78
- refreshable: config.refreshable,
79
- iframe: config.iframe,
80
- active: false,
81
- pages
82
- }
83
-
84
- caches.value.push(cacheName)
85
-
86
- defaultTabs.push(tab)
87
- tabs.value.push({ ...tab })
88
- }
89
-
90
- // add temp tab from session
91
- const tempTab = window.sessionStorage.getItem(SESSION_TAB_NAME)
92
- if (tempTab !== null && tempTab !== undefined) {
93
- // const tempItems = JSON.parse(window.sessionStorage.getItem('tabItems')!)
94
- const temp = JSON.parse(tempTab, (k, v) => {
95
- if (k === 'pages') {
96
- return new Stack<ITabPage>(v)
97
- } else {
98
- return v
99
- }
100
- })
101
- // const temp = defu({ pages: new Stack<ITabPage>() }, tempItems) as ITabItem
102
- let hasTab = false
103
- for (const tab of tabs.value) {
104
- if (tab.id === temp.id) {
105
- hasTab = true
106
- break
107
- }
108
- }
109
- if (!hasTab) {
110
- tabs.value.push(temp)
111
- }
112
- }
113
- initialed = true
114
- }
115
- const hasTab = (id: string) => {
116
- for (const tab of tabs.value) {
117
- if (tab.id === id) {
118
- return true
119
- }
120
- }
121
- return false
122
- }
123
- const canAddTab = () => {
124
- return max <= 0 || (max > 0 && max > tabs.value.length)
125
- }
126
- const addTab = (tab: ITabItem) => {
127
- return new Promise((resolve) => {
128
- tabs.value.push(tab)
129
- resolve(true)
130
- })
131
- }
132
- const getTab = (id: string) => {
133
- for (const tab of tabs.value) {
134
- if (tab.id === id) {
135
- return tab
136
- }
137
- }
138
- return null
139
- }
140
- const addPage = (route: RouteLocationNormalizedLoaded, component: VNode): DefineComponent => {
141
- let cacheComponent: DefineComponent
142
- let tabInfo: ITabBase
143
- if (route.query.__tab) {
144
- tabInfo = decodeTabInfo(route.query.__tab as string)
145
- } else {
146
- tabInfo = {
147
- id: ulid(),
148
- title: t('VueStackTab.undefined'),
149
- closable: true,
150
- refreshable: true,
151
- iframe: false
152
- }
153
- route.query.__tab = encodeTabInfo(tabInfo)
154
- }
155
-
156
- if (deletableTab.has(tabInfo.id!) || !component) {
157
- return
158
- }
159
-
160
- /**
161
- * if using nginx as web server, it will add '/' at the end of url.
162
- *
163
- */
164
- const matchPath = route.matched[route.matched.length - 1].path
165
- const tmpPath = route.path.endsWith('/')
166
- ? route.path.substring(0, route.path.length - 1)
167
- : route.path
168
-
169
- const cacheName = createPageId(
170
- tabInfo.id!,
171
- matchPath === tmpPath ? tmpPath : route.path,
172
- route.query.valueOf()
173
- )
174
- const src = route.query.__src
175
- // 增加tab
176
- let activeTab: ITabItem | null = null
177
- for (const tab of unref(tabs)) {
178
- tab.active = false
179
- if (tab.id === tabInfo.id) {
180
- activeTab = tab as ITabItem
181
- }
182
- }
183
- const page: ITabPage = {
184
- id: cacheName,
185
- tabId: tabInfo.id!,
186
- path: route.path,
187
- query: route.query as Record<string, string>
188
- }
189
-
190
- if (activeTab === null) {
191
- const pages = new Stack<ITabPage>()
192
- pages.push(page)
193
- activeTab = {
194
- id: tabInfo.id!,
195
- title: tabInfo.title,
196
- closable: tabInfo.closable!,
197
- refreshable: tabInfo.refreshable!,
198
- iframe: tabInfo.iframe!,
199
- url: decodeURIComponent(src as string),
200
- active: true,
201
- pages
202
- }
203
- addTab(activeTab).then(() => {
204
- emitter.emit(MittType.TAB_ACTIVE, { id: tabInfo.id!, isRoute: false })
205
- })
206
- } else {
207
- activeTab.active = true
208
- if (activeTab.pages.isEmpty() || activeTab.pages.peek()!.id !== page.id) {
209
- activeTab.pages.push(page)
210
- }
211
- }
212
- updateSession(activeTab)
213
- // const cacheSet = new Set(caches.value)
214
- if (components.has(cacheName)) {
215
- cacheComponent = components.get(cacheName)!
216
- } else {
217
- // 增加控件
218
- cacheComponent = defineComponent({
219
- name: cacheName!,
220
- components: {
221
- DynamicComponent: component
222
- },
223
- emits: {
224
- onLoaded: null
225
- },
226
- setup(props, context) {
227
- onMounted(() => {
228
- context.emit('onLoaded')
229
- addPageScroller(cacheName, '.cache-page-wrapper')
230
- })
231
- onDeactivated(() => {
232
- saveScroller(cacheName)
233
- })
234
- onActivated(() => {
235
- context.emit('onLoaded')
236
- restoreScroller(cacheName)
237
- console.log(pageScroller.get(cacheName))
238
- removeDeletableCache()
239
- deletableTab.clear()
240
- component.props = null
241
- })
242
- onUnmounted(() => {
243
- console.log('on unMounted', cacheName)
244
- removeScroller(cacheName)
245
- // setTimeout(() => {
246
- // 刷新时重置路由
247
- // pageShown.value = true
248
- // }, 500)
249
- })
250
- return () => (
251
- <div
252
- class="cache-page-wrapper"
253
- id={'W-' + tabInfo.id}
254
- style={[scrollbar ? 'overflow:auto' : 'overflow:hidden']}
255
- >
256
- <dynamic-component tId={tabInfo.id} pId={cacheName} />
257
- <PageLoading tId={tabInfo.id!} />
258
- </div>
259
- )
260
- }
261
- })
262
- components.set(cacheName, cacheComponent)
263
-
264
- //
265
- }
266
-
267
- // 加入缓存
268
- // const cacheSet = new Set(caches.value)
269
- // if (routerAlive.value) {
270
- // if (!cacheSet.has(cacheName)) {
271
- // caches.value.push(cacheName)
272
- // }
273
- // }
274
- addCache(cacheName)
275
- return cacheComponent
276
- }
277
- const restoreScroller = (pageId: string) => {
278
- const scroller = pageScroller.get(pageId)
279
- if (scroller) {
280
- for (const key of scroller.keys()) {
281
- if (document.querySelector(key)) {
282
- const result = scroller.get(key)!
283
- document.querySelector(key)!.scrollTop = result.top
284
- document.querySelector(key)!.scrollLeft = result.left
285
- }
286
- }
287
- }
288
- }
289
- const saveScroller = (pageId: string) => {
290
- const scroller = pageScroller.get(pageId)
291
- if (scroller) {
292
- for (const key of scroller.keys()) {
293
- const result = {
294
- top: document.querySelector(key)?.scrollTop ?? 0,
295
- left: document.querySelector(key)?.scrollLeft ?? 0
296
- }
297
- scroller.set(key, result)
298
- }
299
- }
300
- }
301
-
302
- const removeScroller = (pageId: string) => {
303
- const scroller = pageScroller.get(pageId)!
304
- if (scroller) {
305
- scroller.clear()
306
- pageScroller.delete(pageId)
307
- }
308
- }
309
- const addPageScroller = (pageId: string, ...scrollerIds: string[]) => {
310
- if (!pageScroller.has(pageId)) {
311
- pageScroller.set(pageId, new Map<string, number>())
312
- }
313
- const scroller = pageScroller.get(pageId)!
314
- for (const sId of scrollerIds) {
315
- scroller.set(sId, { top: 0, left: 0 })
316
- }
317
- }
318
- // use new url for renwTab like refresh tab
319
- const renewTab = (tab: ITabData) => {
320
- const currentTab = getTab(tab.id!)
321
- for (const item of currentTab!.pages.list()) {
322
- removeComponent(item.id)
323
- markDeletableCache(item.id)
324
- }
325
- removeDeletableCache()
326
- currentTab!.pages.clear()
327
- }
328
- /**
329
- * if id is null , then remove all tab that deleted is true
330
- * @param id
331
- */
332
- const removeTab = (id: string): string => {
333
- let activeTabId = ''
334
- const currentTab = getTab(id)
335
- for (let i = tabs.value.length - 1; i >= 0; i--) {
336
- if (id === unref(tabs)[i].id) {
337
- //if it's not closable then return
338
- if (!unref(tabs)[i].closable) {
339
- return id
340
- }
341
- for (const item of unref(tabs)[i].pages.list()) {
342
- removeComponent(item.id)
343
- markDeletableCache(item.id)
344
- deletableTab.add(unref(tabs)[i].id)
345
- }
346
- unref(tabs)[i].pages.clear()
347
- if (tabs.value.length > 1 && unref(tabs)[i].active) {
348
- if (i === 0) {
349
- activeTabId = unref(tabs)[i + 1].id
350
- } else {
351
- activeTabId = unref(tabs)[i - 1].id
352
- }
353
- }
354
- unref(tabs).splice(i, 1)
355
-
356
- break
357
- }
358
- }
359
-
360
- if (activeTabId !== '') {
361
- emitter.emit(MittType.TAB_ACTIVE, { id: activeTabId })
362
- // active(activeTabId)
363
- }
364
- // if remove inactive tab,then we need remove the cache manually.
365
-
366
- if (!currentTab?.active) {
367
- removeDeletableCache()
368
- deletableTab.clear()
369
- }
370
-
371
- return activeTabId
372
- }
373
-
374
- const removeAllTabs = () => {
375
- const uTabs = unref(tabs)
376
- for (let i = uTabs.length - 1; i >= 0; i--) {
377
- if (uTabs[i].closable) {
378
- for (const item of uTabs[i].pages.list()) {
379
- removeComponent(item.id)
380
- markDeletableCache(item.id)
381
- deletableTab.add(uTabs[i].id)
382
- }
383
- unref(tabs)[i].pages.clear()
384
-
385
- unref(tabs).splice(i, 1)
386
- }
387
- }
388
- for (const stay of uTabs) {
389
- if (stay.active) {
390
- removeDeletableCache()
391
- deletableTab.clear()
392
- return
393
- }
394
- }
395
- // 如果都是非Active状态,就把最后一个active
396
- emitter.emit(MittType.TAB_ACTIVE, { id: uTabs[uTabs.length - 1].id })
397
- // active(uTabs[uTabs.length - 1].id)
398
- }
399
- const removeOtherTabs = (id: string) => {
400
- const uTabs = unref(tabs)
401
- let activeTab
402
- for (let i = uTabs.length - 1; i >= 0; i--) {
403
- if (uTabs[i].id === id) {
404
- activeTab = uTabs[i]
405
- } else if (uTabs[i].closable && uTabs[i].id !== id) {
406
- for (const item of uTabs[i].pages.list()) {
407
- removeComponent(item.id)
408
- markDeletableCache(item.id)
409
- deletableTab.add(uTabs[i].id)
410
- }
411
- unref(tabs)[i].pages.clear()
412
- unref(tabs).splice(i, 1)
413
- }
414
- }
415
- if (!activeTab!.active) {
416
- emitter.emit(MittType.TAB_ACTIVE, { id })
417
- // active(id)
418
- } else {
419
- removeDeletableCache()
420
- deletableTab.clear()
421
- }
422
- }
423
- /**
424
- * 关闭左边
425
- * @param id
426
- */
427
- const removeLeftTabs = (id: string) => {
428
- let startIndex = -1
429
- const uTabs = unref(tabs)
430
- for (let i = 0; i < uTabs.length; i++) {
431
- if (uTabs[i].id === id) {
432
- startIndex = i - 1
433
- break
434
- }
435
- }
436
- for (let i = startIndex; i >= 0; i--) {
437
- if (uTabs[i].closable) {
438
- for (const item of uTabs[i].pages.list()) {
439
- removeComponent(item.id)
440
- markDeletableCache(item.id)
441
- deletableTab.add(uTabs[i].id)
442
- }
443
- unref(tabs)[i].pages.clear()
444
- unref(tabs).splice(i, 1)
445
- }
446
- }
447
- for (const stay of uTabs) {
448
- if (stay.active) {
449
- removeDeletableCache()
450
- deletableTab.clear()
451
- return
452
- }
453
- }
454
- // 如果都是非Active状态,就把最后一个active
455
- emitter.emit(MittType.TAB_ACTIVE, { id: uTabs[0].id })
456
- // active(uTabs[0].id)
457
- }
458
-
459
- /**
460
- * 关闭右边
461
- * @param id
462
- */
463
- const removeRightTabs = (id: string) => {
464
- const uTabs = unref(tabs)
465
- let startIndex = uTabs.length
466
- for (let i = 0; i < uTabs.length; i++) {
467
- if (uTabs[i].id === id) {
468
- startIndex = i + 1
469
- break
470
- }
471
- }
472
- for (let i = uTabs.length - 1; i >= startIndex; i--) {
473
- if (uTabs[i].closable) {
474
- for (const item of uTabs[i].pages.list()) {
475
- removeComponent(item.id)
476
- markDeletableCache(item.id)
477
- deletableTab.add(uTabs[i].id)
478
- }
479
- unref(tabs)[i].pages.clear()
480
- unref(tabs).splice(i, 1)
481
- }
482
- }
483
- for (const stay of uTabs) {
484
- if (stay.active) {
485
- removeDeletableCache()
486
- deletableTab.clear()
487
- return
488
- }
489
- }
490
- emitter.emit(MittType.TAB_ACTIVE, { id: uTabs[uTabs.length - 1].id })
491
- // active(uTabs[uTabs.length - 1].id)
492
- }
493
-
494
- /**
495
- * 刷新
496
- */
497
- const refreshTab = (id: string) => {
498
- // const uTabs = unref(tabs)
499
- for (const uTabs of unref(tabs)) {
500
- if (uTabs.id === id) {
501
- const currentPageId = uTabs.pages.peek()?.id
502
- for (let i = caches.value.length - 1; i >= 0; i--) {
503
- const cacheId = caches.value[i]
504
- if (cacheId === currentPageId) {
505
- caches.value.splice(i, 1)
506
- // 如果要刷新的是显示的TAB
507
- if (uTabs.active) {
508
- pageShown.value = false
509
- nextTick(() => {
510
- pageShown.value = true
511
- })
512
- }
513
- break
514
- }
515
- }
516
- break
517
- }
518
- }
519
- }
520
- /**
521
- * 全部刷新
522
- */
523
- const refreshAllTabs = () => {
524
- const refreshPage = new Set<string>()
525
- tabs.value.forEach((value) => {
526
- refreshPage.add(value.pages.peek()!.id)
527
- })
528
- for (let i = caches.value.length - 1; i >= 0; i--) {
529
- const cacheId = caches.value[i]
530
- if (refreshPage.has(cacheId)) {
531
- caches.value.splice(i, 1)
532
- }
533
- }
534
- nextTick(() => {
535
- pageShown.value = false
536
- nextTick(() => {
537
- setTimeout(() => {
538
- pageShown.value = true
539
- }, 300)
540
- })
541
- })
542
- }
543
-
544
- const addComponent = (id: string, comp: DefineComponent) => {
545
- components.set(id, comp)
546
- }
547
- const getComponent = (id: string) => {
548
- return components.get(id)
549
- }
550
- const removeComponent = (id: string) => {
551
- components.delete(id)
552
- }
553
-
554
- /**
555
- * Delete the cache after new Page loaded.
556
- * @param cacheName
557
- */
558
- const markDeletableCache = (cacheName: string) => {
559
- deletableCache.add(cacheName)
560
- }
561
- const removeDeletableCache = () => {
562
- for (let i = caches.value.length - 1; i >= 0; i--) {
563
- if (deletableCache.has(caches.value[i])) {
564
- unref(caches).splice(i, 1)
565
- }
566
- }
567
- deletableCache.clear()
568
- }
569
- const removeCache = (cacheName: string) => {
570
- for (let i = caches.value.length - 1; i >= 0; i--) {
571
- if (cacheName === caches.value[i]) {
572
- unref(caches).splice(i, 1)
573
- }
574
- }
575
- }
576
- const addCache = (cacheName: string) => {
577
- const cacheSet = new Set(caches.value)
578
- if (!cacheSet.has(cacheName)) {
579
- caches.value.push(cacheName)
580
- }
581
- }
582
-
583
- /**
584
- * active the tab,
585
- * @param id
586
- * @param route
587
- * @return true if already actived
588
- */
589
- const active = (id: string, route = true) => {
590
- // return new Promise((resolve)=>{
591
- emitter.emit('FORWARD')
592
- for (let i = tabs.value.length - 1; i >= 0; i--) {
593
- const tab = tabs.value[i] as ITabItem
594
- if (tab.id === id) {
595
- if (tab.active) {
596
- break
597
- } else {
598
- tab.active = true
599
- // pageShown.value = false
600
- updateSession(tab)
601
- if (route) {
602
- const top = tab.pages.peek()
603
- router.push({
604
- path: top!.path,
605
- query: top!.query
606
- })
607
- }
608
- }
609
- } else {
610
- tabs.value[i].active = false
611
- }
612
- }
613
- // resolve(true)
614
- // })
615
- }
616
- /**
617
- * save current tab info into Browser's session
618
- * @param id
619
- */
620
- const updateSession = (tab: ITabItem) => {
621
- // window.sessionStorage.setItem('tabItems', JSON.stringify(currentItems?.values()))
622
- window.sessionStorage.setItem(SESSION_TAB_NAME, JSON.stringify(tab))
623
- }
624
- const clearSession = () => {
625
- // window.sessionStorage.setItem('tabItems', JSON.stringify(currentItems?.values()))
626
- window.sessionStorage.removeItem(SESSION_TAB_NAME)
627
- }
628
- const reset = () => {
629
- pageShown.value = false
630
- destroy()
631
- nextTick(() => {
632
- unref(tabs).push(...defaultTabs)
633
- emitter.emit(MittType.TAB_ACTIVE, { id: defaultTabs[0].id! })
634
- // pageShown.value = false
635
- nextTick(() => {
636
- pageShown.value = true
637
- })
638
- })
639
- // active(defaultTabs[0].id)
640
- }
641
- const destroy = () => {
642
- unref(tabs).splice(0)
643
- components.clear()
644
- deletableCache.clear()
645
- deletableTab.clear()
646
- unref(caches).splice(0)
647
- }
648
- const setMaxSize = (size: number) => {
649
- max = size
650
- }
651
- const setGlobalScroll = (globalScroll: boolean) => {
652
- scrollbar = globalScroll
653
- }
654
-
655
- return {
656
- tabs,
657
- caches,
658
- pageShown,
659
- initialed,
660
- setMaxSize,
661
- canAddTab,
662
- initial,
663
- size,
664
- active,
665
- destroy,
666
- reset,
667
- addCache,
668
- removeCache,
669
- getTab,
670
- addTab,
671
- removeTab,
672
- removeAllTabs,
673
- removeLeftTabs,
674
- removeRightTabs,
675
- removeOtherTabs,
676
- hasTab,
677
- renewTab,
678
- addPage,
679
- addComponent,
680
- getComponent,
681
- removeComponent,
682
- markDeletableCache,
683
- removeDeletableCache,
684
- refreshTab,
685
- refreshAllTabs,
686
- addPageScroller,
687
- setGlobalScroll,
688
- clearSession
689
- }
690
- }
1
+ import type { ITabBase, ITabData, ITabItem, ITabPage } from '../model/TabModel'
2
+ import {
3
+ defineComponent,
4
+ nextTick,
5
+ onActivated,
6
+ onDeactivated,
7
+ onMounted,
8
+ onUnmounted,
9
+ ref,
10
+ unref
11
+ } from 'vue'
12
+ import type { DefineComponent, VNode } from 'vue'
13
+ import { useRouter } from 'vue-router'
14
+ import type { RouteLocationNormalizedLoaded } from 'vue-router'
15
+ import { ulid } from 'ulid'
16
+ import { encodeTabInfo, createPageId, decodeTabInfo } from '../utils/TabIdHelper'
17
+ import { defu } from 'defu'
18
+ import { Stack } from '../model/TabModel'
19
+ import { uriDecode } from '../utils/UriHelper'
20
+ import PageLoading from '../components/PageLoading.vue'
21
+ import { MittType, useEmitter } from './useTabMitt'
22
+ import { useI18n } from 'vue-i18n-lite'
23
+ const tabs = ref<ITabItem[]>([])
24
+ const defaultTabs: ITabItem[] = []
25
+ // cache
26
+ const caches = ref<string[]>([])
27
+ // Dynamic components
28
+ const components = new Map<string, any>()
29
+ const deletableCache = new Set<String>()
30
+ const deletableTab = new Set<String>()
31
+ const pageShown = ref<boolean>(true)
32
+ const SESSION_TAB_NAME = 'stacktab-active-tab'
33
+ const pageScroller = new Map<string, Map<string, any>>()
34
+ let initialed = false
35
+ let max = 0
36
+ let scrollbar = false
37
+ export default () => {
38
+ const router = useRouter()
39
+ const emitter = useEmitter()
40
+ const { t } = useI18n()
41
+ const size = (): number => {
42
+ return tabs.value.length
43
+ }
44
+
45
+ /**
46
+ * Init the Tab list
47
+ */
48
+ const initial = (staticTabs: ITabData[]) => {
49
+ // add default tabs
50
+ // defaultTabs.splice(0)
51
+ // caches.value.splice(0)
52
+ // deletableCache.clear()
53
+ // deletableTab.clear()
54
+ // components.clear()
55
+ for (const item of staticTabs) {
56
+ // const id = ulid()
57
+ const fullItem = defu(item, { id: ulid(), refreshable: true, closable: true, iframe: false })
58
+ const __tab = encodeTabInfo(fullItem)
59
+ const uri = uriDecode(fullItem.path)
60
+ const config = defu(fullItem, {
61
+ closable: true,
62
+ refreshable: true,
63
+ iframe: false
64
+ })
65
+ const cacheName = createPageId(fullItem.id, uri.path, uri.query)
66
+ const page: ITabPage = {
67
+ id: cacheName,
68
+ tabId: fullItem.id,
69
+ path: uri.path,
70
+ query: defu(uri.query, { __tab })
71
+ }
72
+ const pages = new Stack<ITabPage>()
73
+ pages.push(page)
74
+ const tab: ITabItem = {
75
+ id: fullItem.id,
76
+ title: config.title,
77
+ closable: config.closable,
78
+ refreshable: config.refreshable,
79
+ iframe: config.iframe,
80
+ active: false,
81
+ pages
82
+ }
83
+
84
+ caches.value.push(cacheName)
85
+
86
+ defaultTabs.push(tab)
87
+ tabs.value.push({ ...tab })
88
+ }
89
+
90
+ // add temp tab from session
91
+ const tempTab = window.sessionStorage.getItem(SESSION_TAB_NAME)
92
+ if (tempTab !== null && tempTab !== undefined) {
93
+ // const tempItems = JSON.parse(window.sessionStorage.getItem('tabItems')!)
94
+ const temp = JSON.parse(tempTab, (k, v) => {
95
+ if (k === 'pages') {
96
+ return new Stack<ITabPage>(v)
97
+ } else {
98
+ return v
99
+ }
100
+ })
101
+ // const temp = defu({ pages: new Stack<ITabPage>() }, tempItems) as ITabItem
102
+ let hasTab = false
103
+ for (const tab of tabs.value) {
104
+ if (tab.id === temp.id) {
105
+ hasTab = true
106
+ break
107
+ }
108
+ }
109
+ if (!hasTab) {
110
+ tabs.value.push(temp)
111
+ }
112
+ }
113
+ initialed = true
114
+ }
115
+ const hasTab = (id: string) => {
116
+ for (const tab of tabs.value) {
117
+ if (tab.id === id) {
118
+ return true
119
+ }
120
+ }
121
+ return false
122
+ }
123
+ const canAddTab = () => {
124
+ return max <= 0 || (max > 0 && max > tabs.value.length)
125
+ }
126
+ const addTab = (tab: ITabItem) => {
127
+ return new Promise((resolve) => {
128
+ tabs.value.push(tab)
129
+ resolve(true)
130
+ })
131
+ }
132
+ const getTab = (id: string) => {
133
+ for (const tab of tabs.value) {
134
+ if (tab.id === id) {
135
+ return tab
136
+ }
137
+ }
138
+ return null
139
+ }
140
+ const addPage = (route: RouteLocationNormalizedLoaded, component: VNode): DefineComponent => {
141
+ let cacheComponent: DefineComponent
142
+ let tabInfo: ITabBase
143
+ if (route.query.__tab) {
144
+ tabInfo = decodeTabInfo(route.query.__tab as string)
145
+ } else {
146
+ tabInfo = {
147
+ id: ulid(),
148
+ title: t('VueStackTab.undefined'),
149
+ closable: true,
150
+ refreshable: true,
151
+ iframe: false
152
+ }
153
+ route.query.__tab = encodeTabInfo(tabInfo)
154
+ }
155
+
156
+ if (deletableTab.has(tabInfo.id!) || !component) {
157
+ return
158
+ }
159
+
160
+ /**
161
+ * if using nginx as web server, it will add '/' at the end of url.
162
+ *
163
+ */
164
+ const matchPath = route.matched[route.matched.length - 1].path
165
+ const tmpPath = route.path.endsWith('/')
166
+ ? route.path.substring(0, route.path.length - 1)
167
+ : route.path
168
+
169
+ const cacheName = createPageId(
170
+ tabInfo.id!,
171
+ matchPath === tmpPath ? tmpPath : route.path,
172
+ route.query.valueOf()
173
+ )
174
+ if(deletableCache.has(cacheName)) {
175
+ return components.get(cacheName)!
176
+ }
177
+ const src = route.query.__src
178
+ // 增加tab
179
+ let activeTab: ITabItem | null = null
180
+ for (const tab of unref(tabs)) {
181
+ tab.active = false
182
+ if (tab.id === tabInfo.id) {
183
+ activeTab = tab as ITabItem
184
+ }
185
+ }
186
+ const page: ITabPage = {
187
+ id: cacheName,
188
+ tabId: tabInfo.id!,
189
+ path: route.path,
190
+ query: route.query as Record<string, string>
191
+ }
192
+
193
+ if (activeTab === null) {
194
+ const pages = new Stack<ITabPage>()
195
+ pages.push(page)
196
+ activeTab = {
197
+ id: tabInfo.id!,
198
+ title: tabInfo.title,
199
+ closable: tabInfo.closable!,
200
+ refreshable: tabInfo.refreshable!,
201
+ iframe: tabInfo.iframe!,
202
+ url: decodeURIComponent(src as string),
203
+ active: true,
204
+ pages
205
+ }
206
+ addTab(activeTab).then(() => {
207
+ emitter.emit(MittType.TAB_ACTIVE, { id: tabInfo.id!, isRoute: false })
208
+ })
209
+ } else {
210
+ activeTab.active = true
211
+ if (activeTab.pages.isEmpty() || activeTab.pages.peek()!.id !== page.id) {
212
+ activeTab.pages.push(page)
213
+ }
214
+ }
215
+ updateSession(activeTab)
216
+ // const cacheSet = new Set(caches.value)
217
+ if (components.has(cacheName)) {
218
+ cacheComponent = components.get(cacheName)!
219
+ } else {
220
+ // 增加控件
221
+ cacheComponent = defineComponent({
222
+ name: cacheName!,
223
+ components: {
224
+ DynamicComponent: component
225
+ },
226
+ emits: {
227
+ onLoaded: null
228
+ },
229
+ setup(props, context) {
230
+ onMounted(() => {
231
+ context.emit('onLoaded')
232
+ addPageScroller(cacheName, '.cache-page-wrapper')
233
+ })
234
+ onDeactivated(() => {
235
+ saveScroller(cacheName)
236
+ })
237
+ onActivated(() => {
238
+ context.emit('onLoaded')
239
+ restoreScroller(cacheName)
240
+ console.log(pageScroller.get(cacheName))
241
+ removeDeletableCache()
242
+ deletableTab.clear()
243
+ component.props = null
244
+ })
245
+ onUnmounted(() => {
246
+ console.log('on unMounted', cacheName)
247
+ removeScroller(cacheName)
248
+ // setTimeout(() => {
249
+ // 刷新时重置路由
250
+ // pageShown.value = true
251
+ // }, 500)
252
+ })
253
+ return () => (
254
+ <div
255
+ class="cache-page-wrapper"
256
+ id={'W-' + tabInfo.id}
257
+ style={[scrollbar ? 'overflow:auto' : 'overflow:hidden']}
258
+ >
259
+ <dynamic-component tId={tabInfo.id} pId={cacheName} />
260
+ <PageLoading tId={tabInfo.id!} />
261
+ </div>
262
+ )
263
+ }
264
+ })
265
+ components.set(cacheName, cacheComponent)
266
+
267
+ //
268
+ }
269
+
270
+ // 加入缓存
271
+ // const cacheSet = new Set(caches.value)
272
+ // if (routerAlive.value) {
273
+ // if (!cacheSet.has(cacheName)) {
274
+ // caches.value.push(cacheName)
275
+ // }
276
+ // }
277
+ addCache(cacheName)
278
+ return cacheComponent
279
+ }
280
+ const restoreScroller = (pageId: string) => {
281
+ const scroller = pageScroller.get(pageId)
282
+ if (scroller) {
283
+ for (const key of scroller.keys()) {
284
+ if (document.querySelector(key)) {
285
+ const result = scroller.get(key)!
286
+ document.querySelector(key)!.scrollTop = result.top
287
+ document.querySelector(key)!.scrollLeft = result.left
288
+ }
289
+ }
290
+ }
291
+ }
292
+ const saveScroller = (pageId: string) => {
293
+ const scroller = pageScroller.get(pageId)
294
+ if (scroller) {
295
+ for (const key of scroller.keys()) {
296
+ const result = {
297
+ top: document.querySelector(key)?.scrollTop ?? 0,
298
+ left: document.querySelector(key)?.scrollLeft ?? 0
299
+ }
300
+ scroller.set(key, result)
301
+ }
302
+ }
303
+ }
304
+
305
+ const removeScroller = (pageId: string) => {
306
+ const scroller = pageScroller.get(pageId)!
307
+ if (scroller) {
308
+ scroller.clear()
309
+ pageScroller.delete(pageId)
310
+ }
311
+ }
312
+ const addPageScroller = (pageId: string, ...scrollerIds: string[]) => {
313
+ if (!pageScroller.has(pageId)) {
314
+ pageScroller.set(pageId, new Map<string, number>())
315
+ }
316
+ const scroller = pageScroller.get(pageId)!
317
+ for (const sId of scrollerIds) {
318
+ scroller.set(sId, { top: 0, left: 0 })
319
+ }
320
+ }
321
+ // use new url for renwTab like refresh tab
322
+ const renewTab = (tab: ITabData) => {
323
+ const currentTab = getTab(tab.id!)
324
+ for (const item of currentTab!.pages.list()) {
325
+ removeComponent(item.id)
326
+ markDeletableCache(item.id)
327
+ }
328
+ removeDeletableCache()
329
+ currentTab!.pages.clear()
330
+ }
331
+ /**
332
+ * if id is null , then remove all tab that deleted is true
333
+ * @param id
334
+ */
335
+ const removeTab = (id: string): string => {
336
+ let activeTabId = ''
337
+ const currentTab = getTab(id)
338
+ for (let i = tabs.value.length - 1; i >= 0; i--) {
339
+ if (id === unref(tabs)[i].id) {
340
+ //if it's not closable then return
341
+ if (!unref(tabs)[i].closable) {
342
+ return id
343
+ }
344
+ for (const item of unref(tabs)[i].pages.list()) {
345
+ removeComponent(item.id)
346
+ markDeletableCache(item.id)
347
+ deletableTab.add(unref(tabs)[i].id)
348
+ }
349
+ unref(tabs)[i].pages.clear()
350
+ if (tabs.value.length > 1 && unref(tabs)[i].active) {
351
+ if (i === 0) {
352
+ activeTabId = unref(tabs)[i + 1].id
353
+ } else {
354
+ activeTabId = unref(tabs)[i - 1].id
355
+ }
356
+ }
357
+ unref(tabs).splice(i, 1)
358
+
359
+ break
360
+ }
361
+ }
362
+
363
+ if (activeTabId !== '') {
364
+ emitter.emit(MittType.TAB_ACTIVE, { id: activeTabId })
365
+ // active(activeTabId)
366
+ }
367
+ // if remove inactive tab,then we need remove the cache manually.
368
+
369
+ if (!currentTab?.active) {
370
+ removeDeletableCache()
371
+ deletableTab.clear()
372
+ }
373
+
374
+ return activeTabId
375
+ }
376
+
377
+ const removeAllTabs = () => {
378
+ const uTabs = unref(tabs)
379
+ for (let i = uTabs.length - 1; i >= 0; i--) {
380
+ if (uTabs[i].closable) {
381
+ for (const item of uTabs[i].pages.list()) {
382
+ removeComponent(item.id)
383
+ markDeletableCache(item.id)
384
+ deletableTab.add(uTabs[i].id)
385
+ }
386
+ unref(tabs)[i].pages.clear()
387
+
388
+ unref(tabs).splice(i, 1)
389
+ }
390
+ }
391
+ for (const stay of uTabs) {
392
+ if (stay.active) {
393
+ removeDeletableCache()
394
+ deletableTab.clear()
395
+ return
396
+ }
397
+ }
398
+ // 如果都是非Active状态,就把最后一个active
399
+ emitter.emit(MittType.TAB_ACTIVE, { id: uTabs[uTabs.length - 1].id })
400
+ // active(uTabs[uTabs.length - 1].id)
401
+ }
402
+ const removeOtherTabs = (id: string) => {
403
+ const uTabs = unref(tabs)
404
+ let activeTab
405
+ for (let i = uTabs.length - 1; i >= 0; i--) {
406
+ if (uTabs[i].id === id) {
407
+ activeTab = uTabs[i]
408
+ } else if (uTabs[i].closable && uTabs[i].id !== id) {
409
+ for (const item of uTabs[i].pages.list()) {
410
+ removeComponent(item.id)
411
+ markDeletableCache(item.id)
412
+ deletableTab.add(uTabs[i].id)
413
+ }
414
+ unref(tabs)[i].pages.clear()
415
+ unref(tabs).splice(i, 1)
416
+ }
417
+ }
418
+ if (!activeTab!.active) {
419
+ emitter.emit(MittType.TAB_ACTIVE, { id })
420
+ // active(id)
421
+ } else {
422
+ removeDeletableCache()
423
+ deletableTab.clear()
424
+ }
425
+ }
426
+ /**
427
+ * 关闭左边
428
+ * @param id
429
+ */
430
+ const removeLeftTabs = (id: string) => {
431
+ let startIndex = -1
432
+ const uTabs = unref(tabs)
433
+ for (let i = 0; i < uTabs.length; i++) {
434
+ if (uTabs[i].id === id) {
435
+ startIndex = i - 1
436
+ break
437
+ }
438
+ }
439
+ for (let i = startIndex; i >= 0; i--) {
440
+ if (uTabs[i].closable) {
441
+ for (const item of uTabs[i].pages.list()) {
442
+ removeComponent(item.id)
443
+ markDeletableCache(item.id)
444
+ deletableTab.add(uTabs[i].id)
445
+ }
446
+ unref(tabs)[i].pages.clear()
447
+ unref(tabs).splice(i, 1)
448
+ }
449
+ }
450
+ for (const stay of uTabs) {
451
+ if (stay.active) {
452
+ removeDeletableCache()
453
+ deletableTab.clear()
454
+ return
455
+ }
456
+ }
457
+ // 如果都是非Active状态,就把最后一个active
458
+ emitter.emit(MittType.TAB_ACTIVE, { id: uTabs[0].id })
459
+ // active(uTabs[0].id)
460
+ }
461
+
462
+ /**
463
+ * 关闭右边
464
+ * @param id
465
+ */
466
+ const removeRightTabs = (id: string) => {
467
+ const uTabs = unref(tabs)
468
+ let startIndex = uTabs.length
469
+ for (let i = 0; i < uTabs.length; i++) {
470
+ if (uTabs[i].id === id) {
471
+ startIndex = i + 1
472
+ break
473
+ }
474
+ }
475
+ for (let i = uTabs.length - 1; i >= startIndex; i--) {
476
+ if (uTabs[i].closable) {
477
+ for (const item of uTabs[i].pages.list()) {
478
+ removeComponent(item.id)
479
+ markDeletableCache(item.id)
480
+ deletableTab.add(uTabs[i].id)
481
+ }
482
+ unref(tabs)[i].pages.clear()
483
+ unref(tabs).splice(i, 1)
484
+ }
485
+ }
486
+ for (const stay of uTabs) {
487
+ if (stay.active) {
488
+ removeDeletableCache()
489
+ deletableTab.clear()
490
+ return
491
+ }
492
+ }
493
+ emitter.emit(MittType.TAB_ACTIVE, { id: uTabs[uTabs.length - 1].id })
494
+ // active(uTabs[uTabs.length - 1].id)
495
+ }
496
+
497
+ /**
498
+ * 刷新
499
+ */
500
+ const refreshTab = (id: string) => {
501
+ // const uTabs = unref(tabs)
502
+ for (const uTabs of unref(tabs)) {
503
+ if (uTabs.id === id) {
504
+ const currentPageId = uTabs.pages.peek()?.id
505
+ for (let i = caches.value.length - 1; i >= 0; i--) {
506
+ const cacheId = caches.value[i]
507
+ if (cacheId === currentPageId) {
508
+ caches.value.splice(i, 1)
509
+ // 如果要刷新的是显示的TAB
510
+ if (uTabs.active) {
511
+ pageShown.value = false
512
+ nextTick(() => {
513
+ pageShown.value = true
514
+ })
515
+ }
516
+ break
517
+ }
518
+ }
519
+ break
520
+ }
521
+ }
522
+ }
523
+ /**
524
+ * 全部刷新
525
+ */
526
+ const refreshAllTabs = () => {
527
+ const refreshPage = new Set<string>()
528
+ tabs.value.forEach((value) => {
529
+ refreshPage.add(value.pages.peek()!.id)
530
+ })
531
+ for (let i = caches.value.length - 1; i >= 0; i--) {
532
+ const cacheId = caches.value[i]
533
+ if (refreshPage.has(cacheId)) {
534
+ caches.value.splice(i, 1)
535
+ }
536
+ }
537
+ nextTick(() => {
538
+ pageShown.value = false
539
+ nextTick(() => {
540
+ setTimeout(() => {
541
+ pageShown.value = true
542
+ }, 300)
543
+ })
544
+ })
545
+ }
546
+
547
+ const addComponent = (id: string, comp: DefineComponent) => {
548
+ components.set(id, comp)
549
+ }
550
+ const getComponent = (id: string) => {
551
+ return components.get(id)
552
+ }
553
+ const removeComponent = (id: string) => {
554
+ components.delete(id)
555
+ }
556
+
557
+ /**
558
+ * Delete the cache after new Page loaded.
559
+ * @param cacheName
560
+ */
561
+ const markDeletableCache = (cacheName: string) => {
562
+ deletableCache.add(cacheName)
563
+ }
564
+ const removeDeletableCache = () => {
565
+ for (let i = caches.value.length - 1; i >= 0; i--) {
566
+ if (deletableCache.has(caches.value[i])) {
567
+ unref(caches).splice(i, 1)
568
+ removeComponent(caches.value[i])
569
+ }
570
+ }
571
+ deletableCache.clear()
572
+ }
573
+ const removeCache = (cacheName: string) => {
574
+ for (let i = caches.value.length - 1; i >= 0; i--) {
575
+ if (cacheName === caches.value[i]) {
576
+ unref(caches).splice(i, 1)
577
+ }
578
+ }
579
+ }
580
+ const addCache = (cacheName: string) => {
581
+ const cacheSet = new Set(caches.value)
582
+ if (!cacheSet.has(cacheName)) {
583
+ caches.value.push(cacheName)
584
+ }
585
+ }
586
+
587
+ /**
588
+ * active the tab,
589
+ * @param id
590
+ * @param route
591
+ * @return true if already actived
592
+ */
593
+ const active = (id: string, route = true) => {
594
+ // return new Promise((resolve)=>{
595
+ emitter.emit('FORWARD')
596
+ for (let i = tabs.value.length - 1; i >= 0; i--) {
597
+ const tab = tabs.value[i] as ITabItem
598
+ if (tab.id === id) {
599
+ if (tab.active) {
600
+ break
601
+ } else {
602
+ tab.active = true
603
+ // pageShown.value = false
604
+ updateSession(tab)
605
+ if (route) {
606
+ const top = tab.pages.peek()
607
+ router.push({
608
+ path: top!.path,
609
+ query: top!.query
610
+ })
611
+ }
612
+ }
613
+ } else {
614
+ tabs.value[i].active = false
615
+ }
616
+ }
617
+ // resolve(true)
618
+ // })
619
+ }
620
+ /**
621
+ * save current tab info into Browser's session
622
+ * @param id
623
+ */
624
+ const updateSession = (tab: ITabItem) => {
625
+ // window.sessionStorage.setItem('tabItems', JSON.stringify(currentItems?.values()))
626
+ window.sessionStorage.setItem(SESSION_TAB_NAME, JSON.stringify(tab))
627
+ }
628
+ const clearSession = () => {
629
+ // window.sessionStorage.setItem('tabItems', JSON.stringify(currentItems?.values()))
630
+ window.sessionStorage.removeItem(SESSION_TAB_NAME)
631
+ }
632
+ const reset = () => {
633
+ pageShown.value = false
634
+ destroy()
635
+ nextTick(() => {
636
+ unref(tabs).push(...defaultTabs)
637
+ emitter.emit(MittType.TAB_ACTIVE, { id: defaultTabs[0].id! })
638
+ // pageShown.value = false
639
+ nextTick(() => {
640
+ pageShown.value = true
641
+ })
642
+ })
643
+ // active(defaultTabs[0].id)
644
+ }
645
+ const destroy = () => {
646
+ unref(tabs).splice(0)
647
+ components.clear()
648
+ deletableCache.clear()
649
+ deletableTab.clear()
650
+ unref(caches).splice(0)
651
+ }
652
+ const setMaxSize = (size: number) => {
653
+ max = size
654
+ }
655
+ const setGlobalScroll = (globalScroll: boolean) => {
656
+ scrollbar = globalScroll
657
+ }
658
+
659
+ return {
660
+ tabs,
661
+ caches,
662
+ pageShown,
663
+ initialed,
664
+ setMaxSize,
665
+ canAddTab,
666
+ initial,
667
+ size,
668
+ active,
669
+ destroy,
670
+ reset,
671
+ addCache,
672
+ removeCache,
673
+ getTab,
674
+ addTab,
675
+ removeTab,
676
+ removeAllTabs,
677
+ removeLeftTabs,
678
+ removeRightTabs,
679
+ removeOtherTabs,
680
+ hasTab,
681
+ renewTab,
682
+ addPage,
683
+ addComponent,
684
+ getComponent,
685
+ removeComponent,
686
+ markDeletableCache,
687
+ removeDeletableCache,
688
+ refreshTab,
689
+ refreshAllTabs,
690
+ addPageScroller,
691
+ setGlobalScroll,
692
+ clearSession
693
+ }
694
+ }