vue-stack-tabs 1.0.1 → 1.3.0
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/README.en.md +526 -0
- package/README.md +337 -520
- package/README.ru.md +526 -0
- package/dist/iframe-bridge.d.ts +8 -0
- package/dist/iframe-bridge.html +77 -0
- package/dist/iframe-bridge.mjs +20 -0
- package/dist/iframe-child.html +88 -0
- package/dist/nuxt/favicon.svg +10 -0
- package/dist/nuxt/iframe-bridge.html +77 -0
- package/dist/nuxt/iframe-child.html +88 -0
- package/dist/nuxt/module.d.ts +6 -0
- package/dist/nuxt/module.mjs +28 -0
- package/dist/nuxt/plugin.mjs +9 -0
- package/dist/nuxt/runtime/plugin.d.ts +2 -0
- package/dist/nuxt/runtime/plugin.mjs +9 -0
- package/dist/src/lib/StackTabs.vue.d.ts +30 -16
- package/dist/src/lib/components/ContextMenu/ContextMenuItem.vue.d.ts +7 -1
- package/dist/src/lib/components/ContextMenu/index.vue.d.ts +5 -4
- package/dist/src/lib/components/PageLoading.vue.d.ts +2 -1
- package/dist/src/lib/components/StackKeepAlive/StackCacheRenderer.vue.d.ts +25 -0
- package/dist/src/lib/components/StackKeepAlive/StackKeepAlive.vue.d.ts +12 -0
- package/dist/src/lib/components/TabHeader/TabHeaderButton.vue.d.ts +11 -12
- package/dist/src/lib/components/TabHeader/TabHeaderItem.vue.d.ts +5 -4
- package/dist/src/lib/components/TabHeader/TabHeaderScroll.vue.d.ts +8 -22
- package/dist/src/lib/components/TabHeader/index.vue.d.ts +11 -164
- package/dist/src/lib/hooks/stackTabsContext.d.ts +49 -0
- package/dist/src/lib/hooks/tabPanel/evict.d.ts +21 -23
- package/dist/src/lib/hooks/tabPanel/navigationTransaction.d.ts +35 -0
- package/dist/src/lib/hooks/tabPanel/refresh.d.ts +17 -0
- package/dist/src/lib/hooks/tabPanel/scroll.d.ts +8 -8
- package/dist/src/lib/hooks/tabPanel/session.d.ts +9 -8
- package/dist/src/lib/hooks/tabPanel/state.d.ts +20 -73
- package/dist/src/lib/hooks/useIframeManager.d.ts +35 -0
- package/dist/src/lib/hooks/useTabActions.d.ts +5 -46
- package/dist/src/lib/hooks/useTabEventBus.d.ts +22 -9
- package/dist/src/lib/hooks/useTabPanel.d.ts +17 -74
- package/dist/src/lib/hooks/useTabRouter.d.ts +5 -5
- package/dist/src/lib/i18n/index.d.ts +6 -4
- package/dist/src/lib/i18n/lang/en.d.ts +5 -0
- package/dist/src/lib/i18n/lang/zh-CN.d.ts +5 -0
- package/dist/src/lib/i18n/stackTabsLocale.d.ts +2 -0
- package/dist/src/lib/iframe-bridge.d.ts +8 -0
- package/dist/src/lib/iframe.vue.d.ts +2 -1
- package/dist/src/lib/index.d.ts +4 -5
- package/dist/src/lib/model/TabModel.d.ts +14 -33
- package/dist/src/lib/nuxt/module.d.ts +6 -0
- package/dist/src/lib/nuxt/runtime/plugin.client.d.ts +2 -0
- package/dist/src/lib/nuxt/runtime/plugin.d.ts +2 -0
- package/dist/src/lib/utils/iframeBridge.d.ts +11 -3
- package/dist/src/lib/utils/stackTabsA11y.d.ts +8 -0
- package/dist/src/lib/utils/stackTabsMessage.d.ts +7 -0
- package/dist/src/lib/utils/tabInfoEncoder.d.ts +1 -16
- package/dist/src/lib/utils/typeGuards.d.ts +2 -0
- package/dist/src/lib/utils/urlParser.d.ts +24 -3
- package/dist/src/lib/versionLogger.d.ts +1 -0
- package/dist/style.css +2 -1
- package/dist/utils/iframeBridge.d.ts +39 -0
- package/dist/vue-stack-tabs.css +2 -1
- package/dist/vue-stack-tabs.es.js +1 -2411
- package/package.json +68 -47
- package/src/lib/StackTabs.vue +216 -192
- package/src/lib/assets/style/stackTab.scss +84 -12
- package/src/lib/components/ContextMenu/ContextMenuItem.vue +23 -11
- package/src/lib/components/ContextMenu/index.vue +94 -10
- package/src/lib/components/PageLoading.vue +9 -7
- package/src/lib/components/StackKeepAlive/StackCacheRenderer.vue +70 -0
- package/src/lib/components/StackKeepAlive/StackKeepAlive.vue +57 -0
- package/src/lib/components/TabHeader/TabHeaderButton.vue +19 -8
- package/src/lib/components/TabHeader/TabHeaderItem.vue +45 -16
- package/src/lib/components/TabHeader/TabHeaderScroll.vue +47 -25
- package/src/lib/components/TabHeader/index.vue +109 -12
- package/src/lib/hooks/stackTabsContext.ts +135 -0
- package/src/lib/hooks/tabPanel/evict.ts +123 -41
- package/src/lib/hooks/tabPanel/navigationTransaction.ts +61 -0
- package/src/lib/hooks/tabPanel/refresh.ts +109 -0
- package/src/lib/hooks/tabPanel/scroll.ts +57 -36
- package/src/lib/hooks/tabPanel/session.ts +84 -22
- package/src/lib/hooks/tabPanel/state.ts +41 -44
- package/src/lib/hooks/useIframeManager.ts +253 -0
- package/src/lib/hooks/useTabActions.ts +62 -30
- package/src/lib/hooks/useTabEventBus.ts +29 -19
- package/src/lib/hooks/useTabLoading.ts +3 -2
- package/src/lib/hooks/useTabPanel.tsx +415 -213
- package/src/lib/hooks/useTabRouter.ts +172 -43
- package/src/lib/i18n/index.ts +53 -45
- package/src/lib/i18n/lang/en.ts +6 -1
- package/src/lib/i18n/lang/zh-CN.ts +6 -1
- package/src/lib/i18n/stackTabsLocale.ts +8 -0
- package/src/lib/iframe-bridge.ts +12 -0
- package/src/lib/index.ts +14 -19
- package/src/lib/model/TabModel.ts +34 -86
- package/src/lib/nuxt/module.d.ts +4 -0
- package/src/lib/nuxt/module.ts +8 -3
- package/src/lib/nuxt/runtime/plugin.client.ts +2 -3
- package/src/lib/nuxt/runtime/plugin.ts +11 -0
- package/src/lib/utils/iframeBridge.ts +29 -5
- package/src/lib/utils/scrollUtils.ts +9 -4
- package/src/lib/utils/stackTabsA11y.ts +12 -0
- package/src/lib/utils/stackTabsMessage.ts +99 -0
- package/src/lib/utils/tabInfoEncoder.ts +69 -42
- package/src/lib/utils/typeGuards.ts +3 -0
- package/src/lib/utils/urlParser.ts +144 -14
- package/src/lib/versionLogger.ts +7 -19
- package/dist/src/lib/hooks/tabPanel/evict.spec.d.ts +0 -1
- package/dist/src/lib/hooks/useTabPanel.spec.d.ts +0 -1
- package/dist/src/lib/model/TabModel.spec.d.ts +0 -1
- package/dist/src/lib/utils/iframeBridge.spec.d.ts +0 -1
- package/dist/src/lib/utils/tabInfoEncoder.spec.d.ts +0 -1
- package/dist/src/lib/utils/urlParser.spec.d.ts +0 -1
- package/dist/vue-stack-tabs.umd.js +0 -1
- package/src/lib/hooks/tabPanel/evict.spec.ts +0 -85
- package/src/lib/hooks/useTabPanel.spec.ts +0 -34
- package/src/lib/model/TabModel.spec.ts +0 -40
- package/src/lib/utils/iframeBridge.spec.ts +0 -37
- package/src/lib/utils/tabInfoEncoder.spec.ts +0 -42
- package/src/lib/utils/urlParser.spec.ts +0 -45
|
@@ -12,51 +12,42 @@
|
|
|
12
12
|
* 使用范围:内部 hook,由 StackTabs、useTabActions、useTabRouter、ContextMenu 调用
|
|
13
13
|
*/
|
|
14
14
|
import type { ITabBase, ITabData, ITabItem, ITabPage } from '../model/TabModel'
|
|
15
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
defineComponent,
|
|
17
|
+
onActivated,
|
|
18
|
+
onDeactivated,
|
|
19
|
+
onMounted,
|
|
20
|
+
onUnmounted,
|
|
21
|
+
computed,
|
|
22
|
+
ref,
|
|
23
|
+
shallowRef,
|
|
24
|
+
cloneVNode
|
|
25
|
+
} from 'vue'
|
|
16
26
|
import type { DefineComponent, VNode } from 'vue'
|
|
17
|
-
import { useRouter } from 'vue-router'
|
|
27
|
+
import { isNavigationFailure, useRouter } from 'vue-router'
|
|
18
28
|
import type { RouteLocationNormalizedLoaded } from 'vue-router'
|
|
19
|
-
import { ulid } from 'ulid'
|
|
20
29
|
import { encodeTabInfo, createPageId, decodeTabInfo } from '../utils/tabInfoEncoder'
|
|
21
30
|
import { defu } from 'defu'
|
|
22
31
|
import { Stack } from '../model/TabModel'
|
|
23
|
-
import {
|
|
32
|
+
import {
|
|
33
|
+
parseUrl,
|
|
34
|
+
isCrossOriginUrl,
|
|
35
|
+
omitStackTabsReservedQuery,
|
|
36
|
+
toSafeTabUrl,
|
|
37
|
+
decodeSafeTabUrl,
|
|
38
|
+
cloneLocationQuery,
|
|
39
|
+
clonePage
|
|
40
|
+
} from '../utils/urlParser'
|
|
24
41
|
import PageLoading from '../components/PageLoading.vue'
|
|
25
42
|
import { TabEventType, useTabEmitter } from './useTabEventBus'
|
|
43
|
+
import { runNavigationTransaction } from './tabPanel/navigationTransaction'
|
|
26
44
|
import { useI18n } from 'vue-i18n-lite'
|
|
27
45
|
|
|
28
|
-
import {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
tabIdsToEvict,
|
|
34
|
-
refreshKey,
|
|
35
|
-
excludedCacheIdsForRefresh,
|
|
36
|
-
iframeRefreshKeys,
|
|
37
|
-
isInitialized,
|
|
38
|
-
maxTabCount,
|
|
39
|
-
useGlobalScroll,
|
|
40
|
-
cacheIdsToEvict,
|
|
41
|
-
setMaxTabCount,
|
|
42
|
-
setUseGlobalScroll,
|
|
43
|
-
setSessionPrefix
|
|
44
|
-
} from './tabPanel/state'
|
|
45
|
-
import {
|
|
46
|
-
markCacheForEviction,
|
|
47
|
-
markTabPagesForEviction,
|
|
48
|
-
markTabPagesForEvictionOnly,
|
|
49
|
-
addCache,
|
|
50
|
-
removeCache,
|
|
51
|
-
evictMarkedCaches
|
|
52
|
-
} from './tabPanel/evict'
|
|
53
|
-
import { restoreScroller, saveScroller, removeScroller, addPageScroller } from './tabPanel/scroll'
|
|
54
|
-
import {
|
|
55
|
-
saveActiveTabToSession,
|
|
56
|
-
clearSession,
|
|
57
|
-
restoreTabFromSession,
|
|
58
|
-
getSessionKey
|
|
59
|
-
} from './tabPanel/session'
|
|
46
|
+
import { resolveStackTabsRuntimeContext } from './stackTabsContext'
|
|
47
|
+
import { createTabPanelEviction } from './tabPanel/evict'
|
|
48
|
+
import { createTabPanelScroll } from './tabPanel/scroll'
|
|
49
|
+
import { createTabPanelSession } from './tabPanel/session'
|
|
50
|
+
import { createTabPanelRefresh } from './tabPanel/refresh'
|
|
60
51
|
|
|
61
52
|
/* eslint-disable vue/one-component-per-file */
|
|
62
53
|
/** 占位组件:当路由组件为空或标签已被标记删除时返回 */
|
|
@@ -67,6 +58,11 @@ const EmptyPlaceholderComponent = defineComponent({
|
|
|
67
58
|
}
|
|
68
59
|
})
|
|
69
60
|
|
|
61
|
+
const cloneRouteQuery = (route: RouteLocationNormalizedLoaded, tabInfo: ITabBase) => ({
|
|
62
|
+
...cloneLocationQuery(route.query),
|
|
63
|
+
__tab: route.query.__tab ?? encodeTabInfo(tabInfo)
|
|
64
|
+
})
|
|
65
|
+
|
|
70
66
|
/** 规范化路径(nginx 等可能加尾部斜杠) */
|
|
71
67
|
export const normalizePathForCache = (route: RouteLocationNormalizedLoaded): string => {
|
|
72
68
|
const lastMatch = route.matched[route.matched.length - 1]
|
|
@@ -79,6 +75,37 @@ export default () => {
|
|
|
79
75
|
const router = useRouter()
|
|
80
76
|
const emitter = useTabEmitter()
|
|
81
77
|
const { t } = useI18n()
|
|
78
|
+
const runtimeContext = resolveStackTabsRuntimeContext()
|
|
79
|
+
const {
|
|
80
|
+
tabs,
|
|
81
|
+
defaultTabs,
|
|
82
|
+
caches,
|
|
83
|
+
components,
|
|
84
|
+
tabIdsToEvict,
|
|
85
|
+
refreshKey,
|
|
86
|
+
iframeRefreshKeys,
|
|
87
|
+
isInitialized,
|
|
88
|
+
cacheIdsToEvict
|
|
89
|
+
} = runtimeContext
|
|
90
|
+
const {
|
|
91
|
+
markCacheForEviction,
|
|
92
|
+
markTabPagesForEviction,
|
|
93
|
+
markTabPagesForEvictionOnly,
|
|
94
|
+
addCache,
|
|
95
|
+
removeCache,
|
|
96
|
+
evictMarkedCaches,
|
|
97
|
+
evictPageCache,
|
|
98
|
+
replacePageCaches
|
|
99
|
+
} = createTabPanelEviction(runtimeContext)
|
|
100
|
+
const { restoreScroller, saveScroller, removeScroller, addPageScroller } =
|
|
101
|
+
createTabPanelScroll(runtimeContext)
|
|
102
|
+
const {
|
|
103
|
+
saveActiveTabToSession,
|
|
104
|
+
clearSession,
|
|
105
|
+
restoreTabFromSession,
|
|
106
|
+
restoreActiveTabSession,
|
|
107
|
+
getSessionKey
|
|
108
|
+
} = createTabPanelSession(runtimeContext)
|
|
82
109
|
|
|
83
110
|
const size = (): number => tabs.value.length
|
|
84
111
|
|
|
@@ -87,80 +114,114 @@ export default () => {
|
|
|
87
114
|
* @param staticTabs - 初始标签配置
|
|
88
115
|
*/
|
|
89
116
|
const initialize = (staticTabs: ITabData[]) => {
|
|
117
|
+
const newDefaults: ITabItem[] = []
|
|
118
|
+
const newTabs: ITabItem[] = []
|
|
119
|
+
|
|
90
120
|
for (const item of staticTabs) {
|
|
91
|
-
const fullItem = defu(item, {
|
|
92
|
-
|
|
93
|
-
fullItem.id = tabId
|
|
94
|
-
const uri = parseUrl(fullItem.path)
|
|
95
|
-
const config = defu(fullItem, {
|
|
96
|
-
closable: true,
|
|
121
|
+
const fullItem = defu(item, {
|
|
122
|
+
id: crypto.randomUUID(),
|
|
97
123
|
refreshable: true,
|
|
124
|
+
closable: true,
|
|
98
125
|
iframe: false
|
|
99
126
|
})
|
|
100
|
-
const
|
|
127
|
+
const tabId = fullItem.id ?? crypto.randomUUID()
|
|
128
|
+
fullItem.id = tabId
|
|
129
|
+
const uri = parseUrl(fullItem.path)
|
|
130
|
+
const cacheName = createPageId()
|
|
101
131
|
const page: ITabPage = {
|
|
102
132
|
id: cacheName,
|
|
103
133
|
tabId,
|
|
104
134
|
path: uri.path,
|
|
105
|
-
query: defu(uri.query, { __tab: encodeTabInfo(fullItem) })
|
|
135
|
+
query: defu(omitStackTabsReservedQuery(uri.query), { __tab: encodeTabInfo(fullItem) })
|
|
106
136
|
}
|
|
107
137
|
const pages = new Stack<ITabPage>()
|
|
108
138
|
pages.push(page)
|
|
109
139
|
const tab: ITabItem = {
|
|
110
140
|
id: tabId,
|
|
111
|
-
title:
|
|
112
|
-
closable:
|
|
113
|
-
refreshable:
|
|
114
|
-
iframe:
|
|
115
|
-
iframeRefreshMode:
|
|
141
|
+
title: fullItem.title ?? '',
|
|
142
|
+
closable: fullItem.closable!,
|
|
143
|
+
refreshable: fullItem.refreshable!,
|
|
144
|
+
iframe: fullItem.iframe!,
|
|
145
|
+
iframeRefreshMode:
|
|
146
|
+
(fullItem as { iframeRefreshMode?: string }).iframeRefreshMode === 'reload'
|
|
147
|
+
? 'reload'
|
|
148
|
+
: 'postMessage',
|
|
116
149
|
active: false,
|
|
117
150
|
pages
|
|
118
151
|
}
|
|
119
152
|
|
|
120
153
|
addCache(cacheName)
|
|
121
|
-
|
|
122
|
-
|
|
154
|
+
newDefaults.push(tab)
|
|
155
|
+
newTabs.push({ ...tab })
|
|
123
156
|
}
|
|
124
157
|
|
|
158
|
+
defaultTabs.value = [...defaultTabs.value, ...newDefaults]
|
|
159
|
+
tabs.value = [...tabs.value, ...newTabs]
|
|
160
|
+
|
|
125
161
|
const storedTabJson = sessionStorage.getItem(getSessionKey())
|
|
126
162
|
const restoredTab = restoreTabFromSession(storedTabJson)
|
|
127
|
-
if (restoredTab && !tabs.value.some((t) => t.id === restoredTab
|
|
163
|
+
if (restoredTab && !tabs.value.some((t) => t.id === restoredTab.id)) {
|
|
164
|
+
if (restoredTab.url) restoredTab.url = toSafeTabUrl(restoredTab.url)
|
|
128
165
|
if (restoredTab.iframe && restoredTab.url && restoredTab.iframeRefreshMode === undefined) {
|
|
129
|
-
restoredTab.iframeRefreshMode = isCrossOriginUrl(restoredTab.url)
|
|
130
|
-
? 'reload'
|
|
131
|
-
: 'postMessage'
|
|
166
|
+
restoredTab.iframeRefreshMode = isCrossOriginUrl(restoredTab.url) ? 'reload' : 'postMessage'
|
|
132
167
|
}
|
|
133
|
-
|
|
168
|
+
|
|
169
|
+
if (restoredTab.pages && typeof restoredTab.pages.list === 'function') {
|
|
170
|
+
const pages = restoredTab.pages.list()
|
|
171
|
+
pages.forEach((page) => {
|
|
172
|
+
if (page && page.id) addCache(page.id)
|
|
173
|
+
})
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
tabs.value = [...tabs.value, restoredTab]
|
|
134
177
|
}
|
|
135
178
|
isInitialized.value = true
|
|
136
179
|
}
|
|
137
180
|
|
|
138
181
|
const hasTab = (id: string) => tabs.value.some((t) => t.id === id)
|
|
139
182
|
const canAddTab = () =>
|
|
140
|
-
maxTabCount <= 0 ||
|
|
183
|
+
runtimeContext.maxTabCount.value <= 0 ||
|
|
184
|
+
(runtimeContext.maxTabCount.value > 0 && runtimeContext.maxTabCount.value > tabs.value.length)
|
|
141
185
|
|
|
142
|
-
const addTab = (tab: ITabItem) => {
|
|
143
|
-
tabs.value.
|
|
144
|
-
return Promise.resolve(true)
|
|
186
|
+
const addTab = (tab: ITabItem): void => {
|
|
187
|
+
tabs.value = [...tabs.value, tab]
|
|
145
188
|
}
|
|
146
189
|
|
|
147
190
|
const getTab = (id: string) => tabs.value.find((t) => t.id === id) ?? null
|
|
148
191
|
|
|
149
|
-
/** 从路由解析 tabInfo
|
|
192
|
+
/** 从路由解析 tabInfo,若无则创建默认信息,调用方负责把编码信息写入内部 page.query */
|
|
150
193
|
const parseTabInfoFromRoute = (route: RouteLocationNormalizedLoaded): ITabBase => {
|
|
151
|
-
if (
|
|
152
|
-
|
|
153
|
-
|
|
194
|
+
if (typeof route.query.__tab === 'string') {
|
|
195
|
+
const tabInfo = decodeTabInfo(route.query.__tab)
|
|
196
|
+
if (tabInfo.id) return tabInfo
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const path = normalizePathForCache(route)
|
|
200
|
+
const activeTab = tabs.value.find((tab) => tab.active)
|
|
201
|
+
const activePage = activeTab?.pages.peek()
|
|
202
|
+
if (activeTab && activePage?.path === path) {
|
|
203
|
+
return {
|
|
204
|
+
id: activeTab.id,
|
|
205
|
+
title: activeTab.title,
|
|
206
|
+
closable: activeTab.closable,
|
|
207
|
+
refreshable: activeTab.refreshable,
|
|
208
|
+
iframe: activeTab.iframe,
|
|
209
|
+
iframeRefreshMode: activeTab.iframeRefreshMode
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return {
|
|
214
|
+
id: crypto.randomUUID(),
|
|
154
215
|
title: t('VueStackTab.undefined'),
|
|
155
216
|
closable: true,
|
|
156
217
|
refreshable: true,
|
|
157
218
|
iframe: false
|
|
158
219
|
}
|
|
159
|
-
route.query.__tab = encodeTabInfo(tabInfo)
|
|
160
|
-
return tabInfo
|
|
161
220
|
}
|
|
162
221
|
|
|
163
|
-
/**
|
|
222
|
+
/**
|
|
223
|
+
* 查找或创建目标标签,并同步更新 active 状态与 pages 栈。
|
|
224
|
+
*/
|
|
164
225
|
const findOrCreateTargetTab = (
|
|
165
226
|
tabInfo: ITabBase,
|
|
166
227
|
route: RouteLocationNormalizedLoaded,
|
|
@@ -184,13 +245,12 @@ export default () => {
|
|
|
184
245
|
refreshable: tabInfo.refreshable!,
|
|
185
246
|
iframe: tabInfo.iframe!,
|
|
186
247
|
iframeRefreshMode: tabInfo.iframeRefreshMode ?? 'postMessage',
|
|
187
|
-
url:
|
|
248
|
+
url: decodeSafeTabUrl(src),
|
|
188
249
|
active: true,
|
|
189
250
|
pages
|
|
190
|
-
}
|
|
191
|
-
addTab(targetTab)
|
|
192
|
-
|
|
193
|
-
})
|
|
251
|
+
} as unknown as ITabItem
|
|
252
|
+
addTab(targetTab)
|
|
253
|
+
emitter.emit(TabEventType.TAB_ACTIVE, { id: tabInfo.id!, isRoute: false })
|
|
194
254
|
} else {
|
|
195
255
|
targetTab.active = true
|
|
196
256
|
if (targetTab.pages.isEmpty() || targetTab.pages.peek()!.id !== page.id) {
|
|
@@ -200,9 +260,12 @@ export default () => {
|
|
|
200
260
|
return targetTab
|
|
201
261
|
}
|
|
202
262
|
|
|
263
|
+
const createCacheName = () => {
|
|
264
|
+
return createPageId()
|
|
265
|
+
}
|
|
266
|
+
|
|
203
267
|
/**
|
|
204
|
-
*
|
|
205
|
-
* @returns 缓存名、标签信息、目标标签,或 empty/recovered
|
|
268
|
+
* 根据当前路由更新/创建标签与页面状态(核心路由适配器)。
|
|
206
269
|
*/
|
|
207
270
|
const updatePageState = (
|
|
208
271
|
route: RouteLocationNormalizedLoaded
|
|
@@ -214,62 +277,179 @@ export default () => {
|
|
|
214
277
|
if (tabIdsToEvict.has(tabInfo.id!)) return { empty: true }
|
|
215
278
|
|
|
216
279
|
const path = normalizePathForCache(route)
|
|
217
|
-
|
|
280
|
+
|
|
281
|
+
let targetTab: ITabItem | null = tabInfo.id ? (getTab(tabInfo.id) as ITabItem | null) : null
|
|
282
|
+
let cacheName: string
|
|
283
|
+
let page: ITabPage | null = null
|
|
284
|
+
|
|
285
|
+
if (targetTab && !targetTab.pages.isEmpty()) {
|
|
286
|
+
const top = targetTab.pages.peek()
|
|
287
|
+
if (!top) throw new Error('[vue-stack-tabs] Stack is not empty but peek() returned undefined')
|
|
288
|
+
|
|
289
|
+
if (top.path === path) {
|
|
290
|
+
cacheName = top.id
|
|
291
|
+
} else {
|
|
292
|
+
const pagesList = targetTab.pages.readonlyList()
|
|
293
|
+
let foundIndex = -1
|
|
294
|
+
for (let i = pagesList.length - 2; i >= 0; i--) {
|
|
295
|
+
const pastPage = pagesList[i]
|
|
296
|
+
if (pastPage && pastPage.path === path) {
|
|
297
|
+
foundIndex = i
|
|
298
|
+
break
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if (foundIndex !== -1) {
|
|
303
|
+
if (!import.meta.env.PROD) {
|
|
304
|
+
console.warn(
|
|
305
|
+
'[vue-stack-tabs] Detected native browser back navigation, repairing page stack...'
|
|
306
|
+
)
|
|
307
|
+
}
|
|
308
|
+
const stepsToPop = pagesList.length - 1 - foundIndex
|
|
309
|
+
for (let i = 0; i < stepsToPop; i++) {
|
|
310
|
+
const popped = targetTab.pages.pop()
|
|
311
|
+
if (popped) evictPageCache(popped.id)
|
|
312
|
+
}
|
|
313
|
+
const newTop = targetTab.pages.peek()
|
|
314
|
+
cacheName = newTop ? newTop.id : createPageId()
|
|
315
|
+
|
|
316
|
+
if (!newTop) {
|
|
317
|
+
page = {
|
|
318
|
+
id: cacheName,
|
|
319
|
+
tabId: tabInfo.id!,
|
|
320
|
+
path: route.path,
|
|
321
|
+
query: cloneRouteQuery(route, tabInfo)
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
} else {
|
|
325
|
+
cacheName = createPageId()
|
|
326
|
+
page = {
|
|
327
|
+
id: cacheName,
|
|
328
|
+
tabId: tabInfo.id!,
|
|
329
|
+
path: route.path,
|
|
330
|
+
query: cloneRouteQuery(route, tabInfo)
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
} else {
|
|
335
|
+
cacheName = createPageId()
|
|
336
|
+
page = {
|
|
337
|
+
id: cacheName,
|
|
338
|
+
tabId: tabInfo.id!,
|
|
339
|
+
path: route.path,
|
|
340
|
+
query: cloneRouteQuery(route, tabInfo)
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
218
344
|
if (cacheIdsToEvict.has(cacheName)) {
|
|
219
345
|
cacheIdsToEvict.delete(cacheName)
|
|
220
346
|
const cached = components.get(cacheName)
|
|
221
347
|
if (cached) return { recovered: cached, cacheName }
|
|
222
348
|
}
|
|
223
349
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
350
|
+
if (page) {
|
|
351
|
+
targetTab = findOrCreateTargetTab(tabInfo, route, cacheName, page)
|
|
352
|
+
} else {
|
|
353
|
+
for (const t of tabs.value) t.active = false
|
|
354
|
+
targetTab!.active = true
|
|
229
355
|
}
|
|
230
|
-
|
|
231
|
-
saveActiveTabToSession(targetTab)
|
|
232
|
-
return { cacheName, tabInfo, targetTab }
|
|
356
|
+
|
|
357
|
+
saveActiveTabToSession(targetTab!)
|
|
358
|
+
return { cacheName, tabInfo, targetTab: targetTab! }
|
|
233
359
|
}
|
|
234
360
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
361
|
+
/**
|
|
362
|
+
* 获取或创建与 cacheName 对应的 cacheComponent(keep-alive 包装组件)。
|
|
363
|
+
*/
|
|
364
|
+
const resolvePageComponent = (ctx: {
|
|
365
|
+
cacheName: string
|
|
366
|
+
tabInfo: ITabBase
|
|
367
|
+
targetTab: ITabItem
|
|
368
|
+
}): DefineComponent => {
|
|
239
369
|
const { cacheName, tabInfo } = ctx
|
|
370
|
+
|
|
240
371
|
if (components.has(cacheName)) {
|
|
241
372
|
addCache(cacheName)
|
|
242
373
|
return components.get(cacheName)!
|
|
243
374
|
}
|
|
375
|
+
|
|
244
376
|
const cacheComponent = defineComponent({
|
|
245
377
|
name: cacheName,
|
|
246
|
-
|
|
378
|
+
props: {
|
|
379
|
+
vnode: {
|
|
380
|
+
type: Object as () => VNode,
|
|
381
|
+
required: false,
|
|
382
|
+
default: undefined
|
|
383
|
+
}
|
|
384
|
+
},
|
|
247
385
|
emits: ['onLoaded'],
|
|
248
|
-
setup(
|
|
386
|
+
setup(props, context) {
|
|
387
|
+
const localBackParams = ref<Record<string, unknown> | null>(null)
|
|
388
|
+
const lastVnode = shallowRef<VNode | null>(null)
|
|
389
|
+
const lastCloned = shallowRef<VNode | null>(null)
|
|
390
|
+
const lastBackParams = shallowRef<Record<string, unknown> | null>(null)
|
|
391
|
+
|
|
392
|
+
const checkAndConsumeBackParams = () => {
|
|
393
|
+
try {
|
|
394
|
+
const tab = getTab(tabInfo.id!)
|
|
395
|
+
const top = tab?.pages.peek()
|
|
396
|
+
if (top && top.id === cacheName && top._backParams) {
|
|
397
|
+
localBackParams.value = { ...top._backParams }
|
|
398
|
+
delete top._backParams
|
|
399
|
+
} else {
|
|
400
|
+
localBackParams.value = null
|
|
401
|
+
}
|
|
402
|
+
} catch {
|
|
403
|
+
localBackParams.value = null
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
249
407
|
onMounted(() => {
|
|
250
408
|
context.emit('onLoaded')
|
|
251
|
-
addPageScroller(cacheName,
|
|
409
|
+
addPageScroller(cacheName, `#W-${cacheName}`)
|
|
410
|
+
checkAndConsumeBackParams()
|
|
252
411
|
})
|
|
412
|
+
|
|
253
413
|
onDeactivated(() => saveScroller(cacheName))
|
|
414
|
+
|
|
254
415
|
onActivated(() => {
|
|
255
416
|
context.emit('onLoaded')
|
|
256
417
|
restoreScroller(cacheName)
|
|
257
418
|
evictMarkedCaches()
|
|
258
419
|
tabIdsToEvict.clear()
|
|
259
|
-
|
|
260
|
-
component.props = null
|
|
420
|
+
checkAndConsumeBackParams()
|
|
261
421
|
})
|
|
422
|
+
|
|
262
423
|
onUnmounted(() => removeScroller(cacheName))
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
424
|
+
|
|
425
|
+
return () => {
|
|
426
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
427
|
+
const Node = (props as any).vnode as VNode
|
|
428
|
+
const backParams = localBackParams.value
|
|
429
|
+
|
|
430
|
+
if (Node !== lastVnode.value || backParams !== lastBackParams.value) {
|
|
431
|
+
const dynamicBackProps = backParams ? { _back: backParams } : {}
|
|
432
|
+
lastCloned.value = Node
|
|
433
|
+
? cloneVNode(Node, { tId: tabInfo.id, pId: cacheName, ...dynamicBackProps })
|
|
434
|
+
: null
|
|
435
|
+
lastVnode.value = Node
|
|
436
|
+
lastBackParams.value = backParams
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
return (
|
|
440
|
+
<div
|
|
441
|
+
class="cache-page-wrapper"
|
|
442
|
+
id={`W-${cacheName}`}
|
|
443
|
+
style={[
|
|
444
|
+
runtimeContext.useGlobalScroll.value ? 'overflow:auto' : 'overflow:hidden',
|
|
445
|
+
'height: 100%'
|
|
446
|
+
]}
|
|
447
|
+
>
|
|
448
|
+
{lastCloned.value}
|
|
449
|
+
<PageLoading tabId={tabInfo.id!} />
|
|
450
|
+
</div>
|
|
451
|
+
)
|
|
452
|
+
}
|
|
273
453
|
}
|
|
274
454
|
}) as DefineComponent
|
|
275
455
|
components.set(cacheName, cacheComponent)
|
|
@@ -277,32 +457,63 @@ export default () => {
|
|
|
277
457
|
return cacheComponent
|
|
278
458
|
}
|
|
279
459
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
460
|
+
/** 当前正在渲染的页面的缓存 Key(ULID),供 StackTabs.vue 的 :key 绑定使用 */
|
|
461
|
+
const activeCacheKey = ref<string>('')
|
|
462
|
+
/** 只随当前活动页面刷新变化,避免一个 Tab 刷新导致其他 Tab 的缓存 key 失效。 */
|
|
463
|
+
const activePageRefreshVersion = computed(() => {
|
|
464
|
+
const activeTab = tabs.value.find((tab) => tab.active)
|
|
465
|
+
return activeTab?.pages.peek()?.refreshVersion ?? 0
|
|
466
|
+
})
|
|
283
467
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
468
|
+
let lastRouteKey = ''
|
|
469
|
+
let lastAddPageResult: DefineComponent | null = null
|
|
470
|
+
|
|
471
|
+
const addPage = (
|
|
472
|
+
route: RouteLocationNormalizedLoaded,
|
|
473
|
+
component?: VNode | null
|
|
474
|
+
): DefineComponent => {
|
|
289
475
|
if (!component) return EmptyPlaceholderComponent as DefineComponent
|
|
290
476
|
|
|
477
|
+
const routeKey = route.fullPath + (route.query.__tab || '')
|
|
478
|
+
if (routeKey === lastRouteKey && lastAddPageResult) {
|
|
479
|
+
return lastAddPageResult
|
|
480
|
+
}
|
|
481
|
+
|
|
291
482
|
const state = updatePageState(route)
|
|
292
483
|
if ('empty' in state) return EmptyPlaceholderComponent as DefineComponent
|
|
293
484
|
if ('recovered' in state) {
|
|
294
485
|
addCache(state.cacheName)
|
|
486
|
+
activeCacheKey.value = state.cacheName
|
|
487
|
+
lastRouteKey = routeKey
|
|
488
|
+
lastAddPageResult = state.recovered
|
|
295
489
|
return state.recovered
|
|
296
490
|
}
|
|
297
|
-
|
|
491
|
+
activeCacheKey.value = state.cacheName
|
|
492
|
+
const result = resolvePageComponent(state)
|
|
493
|
+
lastRouteKey = routeKey
|
|
494
|
+
lastAddPageResult = result
|
|
495
|
+
return result
|
|
298
496
|
}
|
|
299
497
|
|
|
300
498
|
const renewTab = (tab: ITabData) => {
|
|
301
499
|
const currentTab = getTab(tab.id!)
|
|
302
|
-
if (!currentTab) return
|
|
500
|
+
if (!currentTab) return undefined
|
|
501
|
+
|
|
502
|
+
const pageSnapshot = currentTab.pages.list().map((p) => clonePage(p))
|
|
503
|
+
const cacheSnapshot = [...caches.value]
|
|
504
|
+
const componentSnapshot = new Map(components)
|
|
505
|
+
|
|
303
506
|
markTabPagesForEvictionOnly(currentTab)
|
|
304
507
|
evictMarkedCaches()
|
|
305
508
|
currentTab.pages.clear()
|
|
509
|
+
|
|
510
|
+
return () => {
|
|
511
|
+
currentTab.pages.clear()
|
|
512
|
+
for (const page of pageSnapshot) currentTab.pages.push(clonePage(page))
|
|
513
|
+
caches.value = [...cacheSnapshot]
|
|
514
|
+
components.clear()
|
|
515
|
+
for (const [id, component] of componentSnapshot) components.set(id, component)
|
|
516
|
+
}
|
|
306
517
|
}
|
|
307
518
|
|
|
308
519
|
const removeTab = (id: string): string => {
|
|
@@ -333,16 +544,21 @@ export default () => {
|
|
|
333
544
|
return activeTabId
|
|
334
545
|
}
|
|
335
546
|
|
|
336
|
-
|
|
547
|
+
/** 批量关闭满足条件的可关闭标签,并统一标记其页面缓存待驱逐。 */
|
|
548
|
+
const removeTabs = (shouldRemove: (tab: ITabItem, index: number) => boolean) => {
|
|
337
549
|
const tabList = tabs.value
|
|
338
|
-
for (let
|
|
339
|
-
const tab = tabList[
|
|
340
|
-
if (tab
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
}
|
|
550
|
+
for (let index = tabList.length - 1; index >= 0; index--) {
|
|
551
|
+
const tab = tabList[index]
|
|
552
|
+
if (!tab || !tab.closable || !shouldRemove(tab, index)) continue
|
|
553
|
+
markTabPagesForEviction(tab)
|
|
554
|
+
tab.pages.clear()
|
|
555
|
+
tabList.splice(index, 1)
|
|
345
556
|
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
const removeAllTabs = () => {
|
|
560
|
+
const tabList = tabs.value
|
|
561
|
+
removeTabs(() => true)
|
|
346
562
|
const hasActive = tabList.some((t) => t.active)
|
|
347
563
|
if (hasActive) {
|
|
348
564
|
evictMarkedCaches()
|
|
@@ -355,18 +571,8 @@ export default () => {
|
|
|
355
571
|
|
|
356
572
|
const removeOtherTabs = (id: string) => {
|
|
357
573
|
const tabList = tabs.value
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
const tab = tabList[i]
|
|
361
|
-
if (!tab) continue
|
|
362
|
-
if (tab.id === id) {
|
|
363
|
-
activeTab = tab as ITabItem
|
|
364
|
-
} else if (tab.closable) {
|
|
365
|
-
markTabPagesForEviction(tab)
|
|
366
|
-
tab.pages.clear()
|
|
367
|
-
tabList.splice(i, 1)
|
|
368
|
-
}
|
|
369
|
-
}
|
|
574
|
+
const activeTab = tabList.find((tab) => tab.id === id)
|
|
575
|
+
removeTabs((tab) => tab.id !== id)
|
|
370
576
|
if (activeTab && !activeTab.active) {
|
|
371
577
|
emitter.emit(TabEventType.TAB_ACTIVE, { id })
|
|
372
578
|
} else {
|
|
@@ -380,14 +586,7 @@ export default () => {
|
|
|
380
586
|
const pivot = tabList.findIndex((t) => t?.id === id)
|
|
381
587
|
if (pivot <= 0) return
|
|
382
588
|
|
|
383
|
-
|
|
384
|
-
const tab = tabList[i]
|
|
385
|
-
if (tab?.closable) {
|
|
386
|
-
markTabPagesForEviction(tab)
|
|
387
|
-
tab.pages.clear()
|
|
388
|
-
tabList.splice(i, 1)
|
|
389
|
-
}
|
|
390
|
-
}
|
|
589
|
+
removeTabs((_, index) => index < pivot)
|
|
391
590
|
if (tabList.some((t) => t.active)) {
|
|
392
591
|
evictMarkedCaches()
|
|
393
592
|
tabIdsToEvict.clear()
|
|
@@ -402,14 +601,7 @@ export default () => {
|
|
|
402
601
|
const pivot = tabList.findIndex((t) => t?.id === id)
|
|
403
602
|
if (pivot < 0 || pivot >= tabList.length - 1) return
|
|
404
603
|
|
|
405
|
-
|
|
406
|
-
const tab = tabList[i]
|
|
407
|
-
if (tab?.closable) {
|
|
408
|
-
markTabPagesForEviction(tab)
|
|
409
|
-
tab.pages.clear()
|
|
410
|
-
tabList.splice(i, 1)
|
|
411
|
-
}
|
|
412
|
-
}
|
|
604
|
+
removeTabs((_, index) => index > pivot)
|
|
413
605
|
if (tabList.some((t) => t.active)) {
|
|
414
606
|
evictMarkedCaches()
|
|
415
607
|
tabIdsToEvict.clear()
|
|
@@ -419,54 +611,16 @@ export default () => {
|
|
|
419
611
|
if (last) emitter.emit(TabEventType.TAB_ACTIVE, { id: last.id })
|
|
420
612
|
}
|
|
421
613
|
|
|
422
|
-
const refreshTab = (
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
} else {
|
|
433
|
-
;(emitter as { emit: (t: string, p: string) => void }).emit('REFRESH_IFRAME_POSTMESSAGE', id)
|
|
434
|
-
}
|
|
435
|
-
return
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
const currentPageId = tab.pages.peek()?.id
|
|
439
|
-
if (!currentPageId) return
|
|
440
|
-
|
|
441
|
-
excludedCacheIdsForRefresh.value = [...excludedCacheIdsForRefresh.value, currentPageId]
|
|
442
|
-
if (tab.active) {
|
|
443
|
-
refreshKey.value++
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
const refreshAllTabs = () => {
|
|
448
|
-
const toRefresh: string[] = []
|
|
449
|
-
for (const tab of tabs.value) {
|
|
450
|
-
if (!tab?.refreshable) continue
|
|
451
|
-
if (tab.iframe) {
|
|
452
|
-
if (tab.iframeRefreshMode === 'reload') {
|
|
453
|
-
iframeRefreshKeys.value = {
|
|
454
|
-
...iframeRefreshKeys.value,
|
|
455
|
-
[tab.id]: (iframeRefreshKeys.value[tab.id] ?? 0) + 1
|
|
456
|
-
}
|
|
457
|
-
} else {
|
|
458
|
-
;(emitter as { emit: (t: string, p: string) => void }).emit('REFRESH_IFRAME_POSTMESSAGE', tab.id)
|
|
459
|
-
}
|
|
460
|
-
} else {
|
|
461
|
-
const pageId = tab.pages.peek()?.id
|
|
462
|
-
if (pageId) toRefresh.push(pageId)
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
if (toRefresh.length > 0) {
|
|
466
|
-
excludedCacheIdsForRefresh.value = [...excludedCacheIdsForRefresh.value, ...toRefresh]
|
|
467
|
-
refreshKey.value++
|
|
468
|
-
}
|
|
469
|
-
}
|
|
614
|
+
const { refreshTab, refreshAllTabs } = createTabPanelRefresh({
|
|
615
|
+
tabs,
|
|
616
|
+
iframeRefreshKeys,
|
|
617
|
+
getTab,
|
|
618
|
+
createPageId,
|
|
619
|
+
addCache,
|
|
620
|
+
evictPageCache,
|
|
621
|
+
replacePageCaches,
|
|
622
|
+
refreshIframeByPostMessage: (id) => emitter.emit(TabEventType.REFRESH_IFRAME_POSTMESSAGE, id)
|
|
623
|
+
})
|
|
470
624
|
|
|
471
625
|
const getComponent = (id: string) => components.get(id)
|
|
472
626
|
|
|
@@ -476,32 +630,71 @@ export default () => {
|
|
|
476
630
|
* @param route - 是否执行 router.push
|
|
477
631
|
*/
|
|
478
632
|
const active = (id: string, route = true) => {
|
|
479
|
-
emitter.emit(
|
|
633
|
+
emitter.emit(TabEventType.FORWARD)
|
|
480
634
|
const target = getTab(id)
|
|
481
|
-
if (!target) return
|
|
482
|
-
if (target.active) return
|
|
635
|
+
if (!target) return Promise.resolve()
|
|
636
|
+
if (target.active) return Promise.resolve()
|
|
483
637
|
|
|
484
|
-
|
|
485
|
-
|
|
638
|
+
const top = target.pages.peek()
|
|
639
|
+
const activateTarget = () => {
|
|
640
|
+
for (const tab of tabs.value) {
|
|
641
|
+
tab.active = tab.id === id
|
|
642
|
+
}
|
|
643
|
+
saveActiveTabToSession(target as ITabItem)
|
|
644
|
+
tabs.value = tabs.value.slice()
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
if (!route) {
|
|
648
|
+
activateTarget()
|
|
649
|
+
return Promise.resolve()
|
|
486
650
|
}
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
if (route) {
|
|
491
|
-
const top = target.pages.peek()
|
|
492
|
-
if (top) router.push({ path: top.path, query: top.query })
|
|
651
|
+
if (!top) {
|
|
652
|
+
activateTarget()
|
|
653
|
+
return Promise.resolve()
|
|
493
654
|
}
|
|
655
|
+
|
|
656
|
+
const __tab = encodeTabInfo({
|
|
657
|
+
id: target.id,
|
|
658
|
+
title: target.title,
|
|
659
|
+
closable: target.closable,
|
|
660
|
+
refreshable: target.refreshable,
|
|
661
|
+
iframe: target.iframe
|
|
662
|
+
})
|
|
663
|
+
const query = defu({ __tab }, top.query || {})
|
|
664
|
+
|
|
665
|
+
return runNavigationTransaction({
|
|
666
|
+
apply: () => {
|
|
667
|
+
const prevActiveId = tabs.value.find((tab) => tab.active)?.id
|
|
668
|
+
const sessionSnapshot = window.sessionStorage.getItem(getSessionKey())
|
|
669
|
+
activateTarget()
|
|
670
|
+
return { prevActiveId, sessionSnapshot }
|
|
671
|
+
},
|
|
672
|
+
navigate: () => router.push({ path: top.path, query }),
|
|
673
|
+
rollback: (snapshot) => {
|
|
674
|
+
for (const tab of tabs.value) {
|
|
675
|
+
tab.active = tab.id === snapshot.prevActiveId
|
|
676
|
+
}
|
|
677
|
+
tabs.value = tabs.value.slice()
|
|
678
|
+
restoreActiveTabSession(snapshot.sessionSnapshot)
|
|
679
|
+
},
|
|
680
|
+
isFailureResult: isNavigationFailure,
|
|
681
|
+
rejectFailureResult: true
|
|
682
|
+
})
|
|
494
683
|
}
|
|
495
684
|
|
|
496
685
|
const reset = () => removeAllTabs()
|
|
497
686
|
|
|
498
687
|
const destroy = () => {
|
|
499
|
-
tabs.value
|
|
688
|
+
tabs.value = []
|
|
689
|
+
defaultTabs.value = []
|
|
500
690
|
cacheIdsToEvict.clear()
|
|
501
691
|
tabIdsToEvict.clear()
|
|
502
692
|
iframeRefreshKeys.value = {}
|
|
503
|
-
caches.value
|
|
693
|
+
caches.value = []
|
|
504
694
|
components.clear()
|
|
695
|
+
runtimeContext.scrollPositionsByPageId.clear()
|
|
696
|
+
runtimeContext.isInitialized.value = false
|
|
697
|
+
runtimeContext.refreshKey.value = 0
|
|
505
698
|
clearSession()
|
|
506
699
|
}
|
|
507
700
|
|
|
@@ -509,13 +702,17 @@ export default () => {
|
|
|
509
702
|
tabs,
|
|
510
703
|
caches,
|
|
511
704
|
refreshKey,
|
|
512
|
-
|
|
705
|
+
activeCacheKey,
|
|
706
|
+
activePageRefreshVersion,
|
|
513
707
|
isInitialized,
|
|
514
|
-
setMaxSize:
|
|
708
|
+
setMaxSize: (value: number) => {
|
|
709
|
+
runtimeContext.maxTabCount.value = value
|
|
710
|
+
},
|
|
515
711
|
canAddTab,
|
|
516
712
|
initialize,
|
|
517
713
|
size,
|
|
518
714
|
active,
|
|
715
|
+
getCacheName: createCacheName,
|
|
519
716
|
destroy,
|
|
520
717
|
reset,
|
|
521
718
|
addCache,
|
|
@@ -535,12 +732,17 @@ export default () => {
|
|
|
535
732
|
removeComponent: (id: string) => components.delete(id),
|
|
536
733
|
markCacheForEviction,
|
|
537
734
|
evictMarkedCaches,
|
|
735
|
+
evictPageCache,
|
|
538
736
|
refreshTab,
|
|
539
737
|
refreshAllTabs,
|
|
540
738
|
iframeRefreshKeys,
|
|
541
739
|
addPageScroller,
|
|
542
|
-
setGlobalScroll:
|
|
740
|
+
setGlobalScroll: (value: boolean) => {
|
|
741
|
+
runtimeContext.useGlobalScroll.value = value
|
|
742
|
+
},
|
|
543
743
|
clearSession,
|
|
544
|
-
setSessionPrefix
|
|
744
|
+
setSessionPrefix: (value: string) => {
|
|
745
|
+
runtimeContext.sessionPrefix.value = value
|
|
746
|
+
}
|
|
545
747
|
}
|
|
546
748
|
}
|