vue-stack-tabs 0.1.28 → 0.1.30

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