vue-stack-tabs 1.4.1 → 1.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/hooks/tabPanel/pageComponentFactory.d.ts +5 -2
- package/dist/hooks/tabPanel/refresh.d.ts +6 -3
- package/dist/hooks/tabPanel/tabStore.d.ts +69 -0
- package/dist/hooks/useTabPanel.d.ts +13 -3
- package/dist/vue-stack-tabs.es.js +1 -1
- package/package.json +1 -1
- package/src/lib/hooks/tabPanel/pageComponentFactory.tsx +13 -19
- package/src/lib/hooks/tabPanel/refresh.ts +20 -16
- package/src/lib/hooks/tabPanel/tabStore.ts +595 -0
- package/src/lib/hooks/useTabActions.ts +2 -2
- package/src/lib/hooks/useTabPanel.tsx +123 -170
- package/src/lib/hooks/useTabRouter.ts +32 -37
|
@@ -25,15 +25,14 @@ import {
|
|
|
25
25
|
omitStackTabsReservedQuery,
|
|
26
26
|
toSafeTabUrl,
|
|
27
27
|
decodeSafeTabUrl,
|
|
28
|
-
cloneLocationQuery
|
|
29
|
-
clonePage
|
|
28
|
+
cloneLocationQuery
|
|
30
29
|
} from '../utils/urlParser'
|
|
31
30
|
import { TabEventType, useTabEmitter } from './useTabEventBus'
|
|
32
31
|
import { runNavigationTransaction } from './tabPanel/navigationTransaction'
|
|
33
32
|
import { useI18n } from 'vue-i18n-lite'
|
|
34
33
|
|
|
35
34
|
import { resolveStackTabsRuntimeContext, type StackTabsRuntimeContext } from './stackTabsContext'
|
|
36
|
-
import {
|
|
35
|
+
import { getTabStore } from './tabPanel/tabStore'
|
|
37
36
|
import { createTabPanelScroll } from './tabPanel/scroll'
|
|
38
37
|
import { createTabPanelSession } from './tabPanel/session'
|
|
39
38
|
import { createTabPanelRefresh } from './tabPanel/refresh'
|
|
@@ -58,27 +57,50 @@ export default (providedRuntimeContext?: StackTabsRuntimeContext) => {
|
|
|
58
57
|
const runtimeContext = providedRuntimeContext ?? resolveStackTabsRuntimeContext()
|
|
59
58
|
const emitter = useTabEmitter(runtimeContext)
|
|
60
59
|
const { t } = useI18n()
|
|
60
|
+
const { tabs, defaultTabs, caches, refreshKey, iframeRefreshKeys, isInitialized } = runtimeContext
|
|
61
|
+
const store = getTabStore(runtimeContext)
|
|
61
62
|
const {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
63
|
+
getTabs,
|
|
64
|
+
getTab,
|
|
65
|
+
getActiveTab,
|
|
66
|
+
findTabByTopPagePath,
|
|
67
|
+
hasTab,
|
|
68
|
+
canAddTab,
|
|
69
|
+
getPages,
|
|
70
|
+
getTopPage,
|
|
71
|
+
getPageSnapshot,
|
|
72
|
+
addTab,
|
|
73
|
+
addTabs,
|
|
74
|
+
activateTab,
|
|
75
|
+
moveTab,
|
|
76
|
+
removeTab: removeTabFromStore,
|
|
77
|
+
removeTabs: removeTabsFromStore,
|
|
78
|
+
appendPage,
|
|
79
|
+
popPages,
|
|
80
|
+
replacePages,
|
|
81
|
+
clearPages,
|
|
82
|
+
updatePage,
|
|
83
|
+
consumeBackParams,
|
|
73
84
|
markCacheForEviction,
|
|
74
|
-
markTabPagesForEviction,
|
|
75
85
|
markTabPagesForEvictionOnly,
|
|
76
86
|
addCache,
|
|
77
87
|
removeCache,
|
|
78
88
|
evictMarkedCaches,
|
|
79
89
|
evictPageCache,
|
|
80
|
-
replacePageCaches
|
|
81
|
-
|
|
90
|
+
replacePageCaches,
|
|
91
|
+
isTabMarkedForEviction,
|
|
92
|
+
isCacheMarkedForEviction,
|
|
93
|
+
unmarkCacheForEviction,
|
|
94
|
+
clearTabEvictionMarks,
|
|
95
|
+
getComponent,
|
|
96
|
+
setComponent,
|
|
97
|
+
deleteComponent,
|
|
98
|
+
snapshotComponents,
|
|
99
|
+
restoreComponents,
|
|
100
|
+
snapshotCaches,
|
|
101
|
+
restoreCaches,
|
|
102
|
+
reset: resetStore
|
|
103
|
+
} = store
|
|
82
104
|
const { restoreScroller, saveScroller, removeScroller, addPageScroller } =
|
|
83
105
|
createTabPanelScroll(runtimeContext)
|
|
84
106
|
const {
|
|
@@ -89,7 +111,7 @@ export default (providedRuntimeContext?: StackTabsRuntimeContext) => {
|
|
|
89
111
|
getSessionKey
|
|
90
112
|
} = createTabPanelSession(runtimeContext)
|
|
91
113
|
|
|
92
|
-
const size = (): number =>
|
|
114
|
+
const size = (): number => getTabs().length
|
|
93
115
|
|
|
94
116
|
/**
|
|
95
117
|
* 初始化:根据 defaultTabs 创建标签与初始页面,并尝试从 sessionStorage 恢复上次激活的标签
|
|
@@ -139,89 +161,44 @@ export default (providedRuntimeContext?: StackTabsRuntimeContext) => {
|
|
|
139
161
|
}
|
|
140
162
|
|
|
141
163
|
defaultTabs.value = [...defaultTabs.value, ...newDefaults]
|
|
142
|
-
|
|
164
|
+
addTabs(newTabs)
|
|
143
165
|
|
|
144
166
|
// SSR 环境无 sessionStorage,直接跳过恢复。
|
|
145
167
|
const storedTabJson =
|
|
146
168
|
typeof window !== 'undefined' ? sessionStorage.getItem(getSessionKey()) : null
|
|
147
169
|
const restoredTab = restoreTabFromSession(storedTabJson)
|
|
148
|
-
if (restoredTab && !
|
|
170
|
+
if (restoredTab && !hasTab(restoredTab.id)) {
|
|
149
171
|
if (restoredTab.url) restoredTab.url = toSafeTabUrl(restoredTab.url)
|
|
150
172
|
if (restoredTab.iframe && restoredTab.url && restoredTab.iframeRefreshMode === undefined) {
|
|
151
173
|
restoredTab.iframeRefreshMode = isCrossOriginUrl(restoredTab.url) ? 'reload' : 'postMessage'
|
|
152
174
|
}
|
|
153
175
|
|
|
154
176
|
if (restoredTab.pages && typeof restoredTab.pages.list === 'function') {
|
|
155
|
-
|
|
156
|
-
|
|
177
|
+
addTab(restoredTab)
|
|
178
|
+
for (const page of getPages(restoredTab.id)) {
|
|
157
179
|
if (page && page.id) addCache(page.id)
|
|
158
|
-
}
|
|
180
|
+
}
|
|
181
|
+
} else {
|
|
182
|
+
addTab(restoredTab)
|
|
159
183
|
}
|
|
160
|
-
|
|
161
|
-
tabs.value = [...tabs.value, restoredTab]
|
|
162
184
|
}
|
|
163
185
|
isInitialized.value = true
|
|
164
186
|
}
|
|
165
187
|
|
|
166
|
-
const hasTab = (id: string) => tabs.value.some((t) => t.id === id)
|
|
167
|
-
const canAddTab = () =>
|
|
168
|
-
runtimeContext.maxTabCount.value <= 0 ||
|
|
169
|
-
(runtimeContext.maxTabCount.value > 0 && runtimeContext.maxTabCount.value > tabs.value.length)
|
|
170
|
-
|
|
171
|
-
const addTab = (tab: ITabItem): void => {
|
|
172
|
-
tabs.value = [...tabs.value, tab]
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
const getTab = (id: string) => tabs.value.find((t) => t.id === id) ?? null
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* 唯一的标签激活状态写入口。即使标签对象保持不变,也替换列表引用,
|
|
179
|
-
* 让 TabHeader 与 iframe 视图在同一个响应式提交中观察到一致状态。
|
|
180
|
-
*/
|
|
181
|
-
const setActiveTab = (id: string): ITabItem | null => {
|
|
182
|
-
let activeTab: ITabItem | null = null
|
|
183
|
-
tabs.value = tabs.value.map((tab) => {
|
|
184
|
-
tab.active = tab.id === id
|
|
185
|
-
if (tab.active) activeTab = tab
|
|
186
|
-
return tab
|
|
187
|
-
})
|
|
188
|
-
return activeTab
|
|
189
|
-
}
|
|
190
|
-
|
|
191
188
|
const { resolvePageComponent } = createPageComponentFactory({
|
|
192
189
|
runtimeContext,
|
|
193
|
-
|
|
190
|
+
consumeBackParams,
|
|
191
|
+
getComponent,
|
|
192
|
+
setComponent,
|
|
194
193
|
addCache,
|
|
195
194
|
evictMarkedCaches,
|
|
195
|
+
clearTabEvictionMarks,
|
|
196
196
|
addPageScroller,
|
|
197
197
|
saveScroller,
|
|
198
198
|
restoreScroller,
|
|
199
199
|
removeScroller
|
|
200
200
|
})
|
|
201
201
|
|
|
202
|
-
/**
|
|
203
|
-
* 将指定标签移动到目标索引。标签对象本身不变,因此其页面栈和缓存关联保持不受影响。
|
|
204
|
-
* @returns 是否实际调整了顺序
|
|
205
|
-
*/
|
|
206
|
-
const moveTab = (id: string, targetIndex: number): boolean => {
|
|
207
|
-
const sourceIndex = tabs.value.findIndex((tab) => tab.id === id)
|
|
208
|
-
if (
|
|
209
|
-
sourceIndex < 0 ||
|
|
210
|
-
targetIndex < 0 ||
|
|
211
|
-
targetIndex >= tabs.value.length ||
|
|
212
|
-
sourceIndex === targetIndex
|
|
213
|
-
) {
|
|
214
|
-
return false
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
const reorderedTabs = tabs.value.slice()
|
|
218
|
-
const [tab] = reorderedTabs.splice(sourceIndex, 1)
|
|
219
|
-
if (!tab) return false
|
|
220
|
-
reorderedTabs.splice(targetIndex, 0, tab)
|
|
221
|
-
tabs.value = reorderedTabs
|
|
222
|
-
return true
|
|
223
|
-
}
|
|
224
|
-
|
|
225
202
|
/** 把已有标签的对外字段提取为 ITabBase(用于让路由归属回该标签) */
|
|
226
203
|
const toTabBase = (tab: ITabItem): ITabBase => ({
|
|
227
204
|
id: tab.id,
|
|
@@ -245,8 +222,8 @@ export default (providedRuntimeContext?: StackTabsRuntimeContext) => {
|
|
|
245
222
|
}
|
|
246
223
|
|
|
247
224
|
const path = normalizePathForCache(route)
|
|
248
|
-
const activeTab =
|
|
249
|
-
const activePage = activeTab
|
|
225
|
+
const activeTab = getActiveTab()
|
|
226
|
+
const activePage = activeTab ? getTopPage(activeTab.id) : null
|
|
250
227
|
if (activeTab && activePage?.path === path) {
|
|
251
228
|
return toTabBase(activeTab)
|
|
252
229
|
}
|
|
@@ -254,7 +231,7 @@ export default (providedRuntimeContext?: StackTabsRuntimeContext) => {
|
|
|
254
231
|
// 当前激活标签与路由不一致:首屏 URL 无 __tab,或 session 恢复了上次激活的其它标签
|
|
255
232
|
//(演示应用在 iframe 中重载后 hash 为空即属此类)。此时接管栈顶 path 与当前路由一致的
|
|
256
233
|
// 标签(默认标签或已有标签),避免同一个页面既留下已有标签、又生成一个「未命名」的兜底标签。
|
|
257
|
-
const pathMatchedTab =
|
|
234
|
+
const pathMatchedTab = findTabByTopPagePath(path)
|
|
258
235
|
if (pathMatchedTab) return toTabBase(pathMatchedTab)
|
|
259
236
|
|
|
260
237
|
return {
|
|
@@ -292,13 +269,14 @@ export default (providedRuntimeContext?: StackTabsRuntimeContext) => {
|
|
|
292
269
|
active: true,
|
|
293
270
|
pages
|
|
294
271
|
} as unknown as ITabItem
|
|
295
|
-
|
|
272
|
+
activateTab('')
|
|
296
273
|
addTab(targetTab)
|
|
297
274
|
emitter.emit(TabEventType.TAB_ACTIVE, { id: tabInfo.id!, isRoute: false })
|
|
298
275
|
} else {
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
276
|
+
activateTab(targetTab.id)
|
|
277
|
+
const topPage = getTopPage(targetTab.id)
|
|
278
|
+
if (!topPage || topPage.id !== page.id) {
|
|
279
|
+
appendPage(targetTab.id, page)
|
|
302
280
|
}
|
|
303
281
|
}
|
|
304
282
|
return targetTab
|
|
@@ -318,7 +296,7 @@ export default (providedRuntimeContext?: StackTabsRuntimeContext) => {
|
|
|
318
296
|
| { empty: true }
|
|
319
297
|
| { recovered: DefineComponent; cacheName: string } => {
|
|
320
298
|
const tabInfo = parseTabInfoFromRoute(route)
|
|
321
|
-
if (
|
|
299
|
+
if (isTabMarkedForEviction(tabInfo.id!)) return { empty: true }
|
|
322
300
|
|
|
323
301
|
const path = normalizePathForCache(route)
|
|
324
302
|
|
|
@@ -326,14 +304,14 @@ export default (providedRuntimeContext?: StackTabsRuntimeContext) => {
|
|
|
326
304
|
let cacheName: string
|
|
327
305
|
let page: ITabPage | null = null
|
|
328
306
|
|
|
329
|
-
if (targetTab &&
|
|
330
|
-
const top = targetTab.
|
|
307
|
+
if (targetTab && getTopPage(targetTab.id)) {
|
|
308
|
+
const top = getTopPage(targetTab.id)!
|
|
331
309
|
if (!top) throw new Error('[vue-stack-tabs] Stack is not empty but peek() returned undefined')
|
|
332
310
|
|
|
333
311
|
if (top.path === path) {
|
|
334
312
|
cacheName = top.id
|
|
335
313
|
} else {
|
|
336
|
-
const pagesList = targetTab.
|
|
314
|
+
const pagesList = getPages(targetTab.id)
|
|
337
315
|
let foundIndex = -1
|
|
338
316
|
for (let i = pagesList.length - 2; i >= 0; i--) {
|
|
339
317
|
const pastPage = pagesList[i]
|
|
@@ -350,13 +328,11 @@ export default (providedRuntimeContext?: StackTabsRuntimeContext) => {
|
|
|
350
328
|
)
|
|
351
329
|
}
|
|
352
330
|
const stepsToPop = pagesList.length - 1 - foundIndex
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
}
|
|
359
|
-
const newTop = targetTab.pages.peek()
|
|
331
|
+
const poppedPages = popPages(targetTab.id, stepsToPop)
|
|
332
|
+
// 仅标记待驱逐,由 onActivated 或下一拍 evictMarkedCaches 统一清理,
|
|
333
|
+
// 避免 watcher 同步执行与守卫拒绝路由之间产生不一致。
|
|
334
|
+
for (const popped of poppedPages) markCacheForEviction(popped.id)
|
|
335
|
+
const newTop = getTopPage(targetTab.id)
|
|
360
336
|
cacheName = newTop ? newTop.id : createPageId()
|
|
361
337
|
|
|
362
338
|
if (!newTop) {
|
|
@@ -387,16 +363,16 @@ export default (providedRuntimeContext?: StackTabsRuntimeContext) => {
|
|
|
387
363
|
}
|
|
388
364
|
}
|
|
389
365
|
|
|
390
|
-
if (
|
|
391
|
-
|
|
392
|
-
const cached =
|
|
366
|
+
if (isCacheMarkedForEviction(cacheName)) {
|
|
367
|
+
unmarkCacheForEviction(cacheName)
|
|
368
|
+
const cached = getComponent(cacheName)
|
|
393
369
|
if (cached) return { recovered: cached, cacheName }
|
|
394
370
|
}
|
|
395
371
|
|
|
396
372
|
if (page) {
|
|
397
373
|
targetTab = findOrCreateTargetTab(tabInfo, route, cacheName, page)
|
|
398
374
|
} else {
|
|
399
|
-
|
|
375
|
+
activateTab(targetTab!.id)
|
|
400
376
|
}
|
|
401
377
|
|
|
402
378
|
// 首屏 URL 往往没有 __tab(用户直接打开默认路由):把解析出的标签编码写回地址,
|
|
@@ -417,8 +393,8 @@ export default (providedRuntimeContext?: StackTabsRuntimeContext) => {
|
|
|
417
393
|
const activeCacheKey = ref<string>('')
|
|
418
394
|
/** 只随当前活动页面刷新变化,避免一个 Tab 刷新导致其他 Tab 的缓存 key 失效。 */
|
|
419
395
|
const activePageRefreshVersion = computed(() => {
|
|
420
|
-
const activeTab =
|
|
421
|
-
return activeTab
|
|
396
|
+
const activeTab = getActiveTab()
|
|
397
|
+
return activeTab ? (getTopPage(activeTab.id)?.refreshVersion ?? 0) : 0
|
|
422
398
|
})
|
|
423
399
|
|
|
424
400
|
let lastRouteKey = ''
|
|
@@ -455,74 +431,45 @@ export default (providedRuntimeContext?: StackTabsRuntimeContext) => {
|
|
|
455
431
|
const currentTab = getTab(tab.id!)
|
|
456
432
|
if (!currentTab) return undefined
|
|
457
433
|
|
|
458
|
-
const pageSnapshot = currentTab.
|
|
459
|
-
const cacheSnapshot =
|
|
460
|
-
const componentSnapshot =
|
|
434
|
+
const pageSnapshot = getPageSnapshot(currentTab.id)
|
|
435
|
+
const cacheSnapshot = snapshotCaches()
|
|
436
|
+
const componentSnapshot = snapshotComponents()
|
|
461
437
|
|
|
462
438
|
markTabPagesForEvictionOnly(currentTab)
|
|
463
439
|
evictMarkedCaches()
|
|
464
|
-
currentTab.
|
|
440
|
+
clearPages(currentTab.id)
|
|
465
441
|
|
|
466
442
|
return () => {
|
|
467
|
-
currentTab.
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
components.clear()
|
|
471
|
-
for (const [id, component] of componentSnapshot) components.set(id, component)
|
|
443
|
+
replacePages(currentTab.id, pageSnapshot)
|
|
444
|
+
restoreCaches(cacheSnapshot)
|
|
445
|
+
restoreComponents(componentSnapshot)
|
|
472
446
|
}
|
|
473
447
|
}
|
|
474
448
|
|
|
475
449
|
const removeTab = (id: string): string => {
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
const i = tabList.findIndex((t) => t?.id === id)
|
|
480
|
-
if (i < 0) return ''
|
|
481
|
-
|
|
482
|
-
const tab = tabList[i]!
|
|
483
|
-
if (!tab.closable) return id
|
|
484
|
-
|
|
485
|
-
markTabPagesForEviction(tab)
|
|
486
|
-
tab.pages.clear()
|
|
487
|
-
if (tabList.length > 1 && tab.active) {
|
|
488
|
-
activeTabId = (i === 0 ? tabList[1] : tabList[i - 1])?.id ?? ''
|
|
489
|
-
}
|
|
490
|
-
tabs.value = [...tabList.slice(0, i), ...tabList.slice(i + 1)]
|
|
450
|
+
const result = removeTabFromStore(id)
|
|
451
|
+
if (result.status === 'not-found') return ''
|
|
452
|
+
if (result.status === 'not-closable') return id
|
|
491
453
|
|
|
492
|
-
if (
|
|
493
|
-
emitter.emit(TabEventType.TAB_ACTIVE, { id:
|
|
494
|
-
}
|
|
495
|
-
if (!currentTab?.active) {
|
|
496
|
-
evictMarkedCaches()
|
|
497
|
-
tabIdsToEvict.clear()
|
|
454
|
+
if (result.nextActiveId) {
|
|
455
|
+
emitter.emit(TabEventType.TAB_ACTIVE, { id: result.nextActiveId })
|
|
498
456
|
}
|
|
499
457
|
|
|
500
|
-
return
|
|
458
|
+
return result.nextActiveId
|
|
501
459
|
}
|
|
502
460
|
|
|
503
461
|
/** 批量关闭满足条件的可关闭标签,并统一标记其页面缓存待驱逐。 */
|
|
504
|
-
const removeTabs = (shouldRemove: (tab: ITabItem, index: number) => boolean) =>
|
|
505
|
-
|
|
506
|
-
const remaining = tabs.value.filter((tab, index) => {
|
|
507
|
-
if (!tab.closable || !shouldRemove(tab, index)) return true
|
|
508
|
-
markTabPagesForEviction(tab)
|
|
509
|
-
tab.pages.clear()
|
|
510
|
-
removed.push(tab)
|
|
511
|
-
return false
|
|
512
|
-
})
|
|
513
|
-
tabs.value = remaining
|
|
514
|
-
return removed
|
|
515
|
-
}
|
|
462
|
+
const removeTabs = (shouldRemove: (tab: ITabItem, index: number) => boolean) =>
|
|
463
|
+
removeTabsFromStore(shouldRemove)
|
|
516
464
|
|
|
517
465
|
const removeAllTabs = () => {
|
|
518
466
|
removeTabs(() => true)
|
|
519
|
-
|
|
520
|
-
if (hasActive) {
|
|
467
|
+
if (getActiveTab()) {
|
|
521
468
|
evictMarkedCaches()
|
|
522
|
-
|
|
469
|
+
clearTabEvictionMarks()
|
|
523
470
|
return
|
|
524
471
|
}
|
|
525
|
-
const last =
|
|
472
|
+
const last = getTabs().at(-1)
|
|
526
473
|
if (last) emitter.emit(TabEventType.TAB_ACTIVE, { id: last.id })
|
|
527
474
|
}
|
|
528
475
|
|
|
@@ -533,42 +480,44 @@ export default (providedRuntimeContext?: StackTabsRuntimeContext) => {
|
|
|
533
480
|
emitter.emit(TabEventType.TAB_ACTIVE, { id })
|
|
534
481
|
} else {
|
|
535
482
|
evictMarkedCaches()
|
|
536
|
-
|
|
483
|
+
clearTabEvictionMarks()
|
|
537
484
|
}
|
|
538
485
|
}
|
|
539
486
|
|
|
540
487
|
const removeLeftTabs = (id: string) => {
|
|
541
|
-
const pivot =
|
|
488
|
+
const pivot = getTabs().findIndex((t) => t?.id === id)
|
|
542
489
|
if (pivot <= 0) return
|
|
543
490
|
|
|
544
491
|
removeTabs((_, index) => index < pivot)
|
|
545
|
-
if (
|
|
492
|
+
if (getActiveTab()) {
|
|
546
493
|
evictMarkedCaches()
|
|
547
|
-
|
|
494
|
+
clearTabEvictionMarks()
|
|
548
495
|
return
|
|
549
496
|
}
|
|
550
|
-
const first =
|
|
497
|
+
const first = getTabs()[0]
|
|
551
498
|
if (first) emitter.emit(TabEventType.TAB_ACTIVE, { id: first.id })
|
|
552
499
|
}
|
|
553
500
|
|
|
554
501
|
const removeRightTabs = (id: string) => {
|
|
555
|
-
const
|
|
556
|
-
|
|
502
|
+
const tabList = getTabs()
|
|
503
|
+
const pivot = tabList.findIndex((t) => t?.id === id)
|
|
504
|
+
if (pivot < 0 || pivot >= tabList.length - 1) return
|
|
557
505
|
|
|
558
506
|
removeTabs((_, index) => index > pivot)
|
|
559
|
-
if (
|
|
507
|
+
if (getActiveTab()) {
|
|
560
508
|
evictMarkedCaches()
|
|
561
|
-
|
|
509
|
+
clearTabEvictionMarks()
|
|
562
510
|
return
|
|
563
511
|
}
|
|
564
|
-
const last =
|
|
512
|
+
const last = getTabs().at(-1)
|
|
565
513
|
if (last) emitter.emit(TabEventType.TAB_ACTIVE, { id: last.id })
|
|
566
514
|
}
|
|
567
515
|
|
|
568
516
|
const { refreshTab, refreshAllTabs } = createTabPanelRefresh({
|
|
569
|
-
|
|
517
|
+
getTabs,
|
|
518
|
+
getTopPage,
|
|
519
|
+
updatePage,
|
|
570
520
|
iframeRefreshKeys,
|
|
571
|
-
getTab,
|
|
572
521
|
createPageId,
|
|
573
522
|
addCache,
|
|
574
523
|
evictPageCache,
|
|
@@ -576,8 +525,6 @@ export default (providedRuntimeContext?: StackTabsRuntimeContext) => {
|
|
|
576
525
|
refreshIframeByPostMessage: (id) => emitter.emit(TabEventType.REFRESH_IFRAME_POSTMESSAGE, id)
|
|
577
526
|
})
|
|
578
527
|
|
|
579
|
-
const getComponent = (id: string) => components.get(id)
|
|
580
|
-
|
|
581
528
|
/**
|
|
582
529
|
* 激活指定标签,更新 active 状态并可选跳转路由
|
|
583
530
|
* @param id - 标签 id
|
|
@@ -589,9 +536,9 @@ export default (providedRuntimeContext?: StackTabsRuntimeContext) => {
|
|
|
589
536
|
if (!target) return Promise.resolve()
|
|
590
537
|
if (target.active) return Promise.resolve()
|
|
591
538
|
|
|
592
|
-
const top =
|
|
539
|
+
const top = getTopPage(id)
|
|
593
540
|
const activateTarget = () => {
|
|
594
|
-
const activeTab =
|
|
541
|
+
const activeTab = activateTab(id)
|
|
595
542
|
if (activeTab) saveActiveTabToSession(activeTab)
|
|
596
543
|
}
|
|
597
544
|
|
|
@@ -616,7 +563,7 @@ export default (providedRuntimeContext?: StackTabsRuntimeContext) => {
|
|
|
616
563
|
|
|
617
564
|
return runNavigationTransaction({
|
|
618
565
|
apply: () => {
|
|
619
|
-
const prevActiveId =
|
|
566
|
+
const prevActiveId = getActiveTab()?.id
|
|
620
567
|
// SSR 环境无 sessionStorage;快照为 null 时 rollback 视为「无变化」。
|
|
621
568
|
const sessionSnapshot =
|
|
622
569
|
typeof window !== 'undefined' ? window.sessionStorage.getItem(getSessionKey()) : null
|
|
@@ -625,7 +572,7 @@ export default (providedRuntimeContext?: StackTabsRuntimeContext) => {
|
|
|
625
572
|
},
|
|
626
573
|
navigate: () => router.push({ path: top.path, query }),
|
|
627
574
|
rollback: (snapshot) => {
|
|
628
|
-
|
|
575
|
+
activateTab(snapshot.prevActiveId ?? '')
|
|
629
576
|
restoreActiveTabSession(snapshot.sessionSnapshot)
|
|
630
577
|
},
|
|
631
578
|
isFailureResult: isNavigationFailure,
|
|
@@ -636,13 +583,9 @@ export default (providedRuntimeContext?: StackTabsRuntimeContext) => {
|
|
|
636
583
|
const reset = () => removeAllTabs()
|
|
637
584
|
|
|
638
585
|
const destroy = () => {
|
|
639
|
-
|
|
586
|
+
resetStore()
|
|
640
587
|
defaultTabs.value = []
|
|
641
|
-
cacheIdsToEvict.clear()
|
|
642
|
-
tabIdsToEvict.clear()
|
|
643
588
|
iframeRefreshKeys.value = {}
|
|
644
|
-
caches.value = []
|
|
645
|
-
components.clear()
|
|
646
589
|
runtimeContext.scrollPositionsByPageId.clear()
|
|
647
590
|
runtimeContext.isInitialized.value = false
|
|
648
591
|
runtimeContext.refreshKey.value = 0
|
|
@@ -669,8 +612,17 @@ export default (providedRuntimeContext?: StackTabsRuntimeContext) => {
|
|
|
669
612
|
addCache,
|
|
670
613
|
removeCache,
|
|
671
614
|
getTab,
|
|
615
|
+
getActiveTab,
|
|
616
|
+
getTopPage,
|
|
617
|
+
getPages,
|
|
618
|
+
getPageSnapshot,
|
|
672
619
|
moveTab,
|
|
673
620
|
addTab,
|
|
621
|
+
appendPage,
|
|
622
|
+
popPages,
|
|
623
|
+
replacePages,
|
|
624
|
+
clearPages,
|
|
625
|
+
updatePage,
|
|
674
626
|
removeTab,
|
|
675
627
|
removeAllTabs,
|
|
676
628
|
removeLeftTabs,
|
|
@@ -679,12 +631,13 @@ export default (providedRuntimeContext?: StackTabsRuntimeContext) => {
|
|
|
679
631
|
hasTab,
|
|
680
632
|
renewTab,
|
|
681
633
|
addPage,
|
|
682
|
-
addComponent:
|
|
634
|
+
addComponent: setComponent,
|
|
683
635
|
getComponent,
|
|
684
|
-
removeComponent:
|
|
636
|
+
removeComponent: deleteComponent,
|
|
685
637
|
markCacheForEviction,
|
|
686
638
|
evictMarkedCaches,
|
|
687
639
|
evictPageCache,
|
|
640
|
+
clearTabEvictionMarks,
|
|
688
641
|
refreshTab,
|
|
689
642
|
refreshAllTabs,
|
|
690
643
|
iframeRefreshKeys,
|