vue-stack-tabs 0.1.40 → 0.1.42

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