vue-stack-tabs 0.1.34 → 0.1.36

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,684 +1,684 @@
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 'ulidx'
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
- currentTab!.pages.clear()
326
-
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
- for (let i = tabs.value.length - 1; i >= 0; i--) {
592
- const tab = tabs.value[i] as ITabItem
593
- if (tab.id === id) {
594
- if (tab.active) {
595
- break
596
- } else {
597
- tab.active = true
598
- // pageShown.value = false
599
- updateSession(tab)
600
- if (route) {
601
- const top = tab.pages.peek()
602
- router.push({
603
- path: top!.path,
604
- query: top!.query
605
- })
606
- }
607
- }
608
- } else {
609
- tabs.value[i].active = false
610
- }
611
- }
612
- // resolve(true)
613
- // })
614
- }
615
- /**
616
- * save current tab info into Browser's session
617
- * @param id
618
- */
619
- const updateSession = (tab: ITabItem) => {
620
- // window.sessionStorage.setItem('tabItems', JSON.stringify(currentItems?.values()))
621
- window.sessionStorage.setItem(SESSION_TAB_NAME, JSON.stringify(tab))
622
- }
623
- const reset = () => {
624
- pageShown.value = false
625
- destroy()
626
- nextTick(() => {
627
- unref(tabs).push(...defaultTabs)
628
- emitter.emit(MittType.TAB_ACTIVE, { id: defaultTabs[0].id! })
629
- // pageShown.value = false
630
- nextTick(() => {
631
- pageShown.value = true
632
- })
633
- })
634
- // active(defaultTabs[0].id)
635
- }
636
- const destroy = () => {
637
- unref(tabs).splice(0)
638
- components.clear()
639
- deletableCache.clear()
640
- deletableTab.clear()
641
- unref(caches).splice(0)
642
- }
643
- const setMaxSize = (size: number) => {
644
- max = size
645
- }
646
- const setGlobalScroll = (globalScroll: boolean) => {
647
- scrollbar = globalScroll
648
- }
649
-
650
- return {
651
- tabs,
652
- caches,
653
- pageShown,
654
- initialed,
655
- setMaxSize,
656
- canAddTab,
657
- initial,
658
- size,
659
- active,
660
- destroy,
661
- reset,
662
- addCache,
663
- removeCache,
664
- getTab,
665
- addTab,
666
- removeTab,
667
- removeAllTabs,
668
- removeLeftTabs,
669
- removeRightTabs,
670
- removeOtherTabs,
671
- hasTab,
672
- renewTab,
673
- addPage,
674
- addComponent,
675
- getComponent,
676
- removeComponent,
677
- markDeletableCache,
678
- removeDeletableCache,
679
- refreshTab,
680
- refreshAllTabs,
681
- addPageScroller,
682
- setGlobalScroll
683
- }
684
- }
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 'ulidx'
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
+ for (let i = tabs.value.length - 1; i >= 0; i--) {
592
+ const tab = tabs.value[i] as ITabItem
593
+ if (tab.id === id) {
594
+ if (tab.active) {
595
+ break
596
+ } else {
597
+ tab.active = true
598
+ // pageShown.value = false
599
+ updateSession(tab)
600
+ if (route) {
601
+ const top = tab.pages.peek()
602
+ router.push({
603
+ path: top!.path,
604
+ query: top!.query
605
+ })
606
+ }
607
+ }
608
+ } else {
609
+ tabs.value[i].active = false
610
+ }
611
+ }
612
+ // resolve(true)
613
+ // })
614
+ }
615
+ /**
616
+ * save current tab info into Browser's session
617
+ * @param id
618
+ */
619
+ const updateSession = (tab: ITabItem) => {
620
+ // window.sessionStorage.setItem('tabItems', JSON.stringify(currentItems?.values()))
621
+ window.sessionStorage.setItem(SESSION_TAB_NAME, JSON.stringify(tab))
622
+ }
623
+ const reset = () => {
624
+ pageShown.value = false
625
+ destroy()
626
+ nextTick(() => {
627
+ unref(tabs).push(...defaultTabs)
628
+ emitter.emit(MittType.TAB_ACTIVE, { id: defaultTabs[0].id! })
629
+ // pageShown.value = false
630
+ nextTick(() => {
631
+ pageShown.value = true
632
+ })
633
+ })
634
+ // active(defaultTabs[0].id)
635
+ }
636
+ const destroy = () => {
637
+ unref(tabs).splice(0)
638
+ components.clear()
639
+ deletableCache.clear()
640
+ deletableTab.clear()
641
+ unref(caches).splice(0)
642
+ }
643
+ const setMaxSize = (size: number) => {
644
+ max = size
645
+ }
646
+ const setGlobalScroll = (globalScroll: boolean) => {
647
+ scrollbar = globalScroll
648
+ }
649
+
650
+ return {
651
+ tabs,
652
+ caches,
653
+ pageShown,
654
+ initialed,
655
+ setMaxSize,
656
+ canAddTab,
657
+ initial,
658
+ size,
659
+ active,
660
+ destroy,
661
+ reset,
662
+ addCache,
663
+ removeCache,
664
+ getTab,
665
+ addTab,
666
+ removeTab,
667
+ removeAllTabs,
668
+ removeLeftTabs,
669
+ removeRightTabs,
670
+ removeOtherTabs,
671
+ hasTab,
672
+ renewTab,
673
+ addPage,
674
+ addComponent,
675
+ getComponent,
676
+ removeComponent,
677
+ markDeletableCache,
678
+ removeDeletableCache,
679
+ refreshTab,
680
+ refreshAllTabs,
681
+ addPageScroller,
682
+ setGlobalScroll
683
+ }
684
+ }