vue-stack-tabs 1.4.2 → 2.2.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 +1 -0
- package/README.md +1 -0
- package/README.ru.md +1 -0
- package/dist/StackTabs.vue.d.ts +3 -0
- package/dist/components/ContextMenu/ContextMenuItem.vue.d.ts +1 -1
- package/dist/hooks/tabPanel/refresh.d.ts +4 -2
- package/dist/hooks/tabPanel/tabStore.d.ts +12 -1
- package/dist/hooks/useTabActions.d.ts +1 -0
- package/dist/hooks/useTabPanel.d.ts +14 -2
- package/dist/model/TabModel.d.ts +5 -0
- package/dist/style.css +1 -1
- package/dist/utils/tabInfoEncoder.d.ts +10 -0
- package/dist/utils/urlParser.d.ts +14 -3
- package/dist/vue-stack-tabs.es.js +1 -1
- package/package.json +4 -7
- package/src/lib/StackTabs.vue +24 -2
- package/src/lib/assets/image/close_all.svg +1 -9
- package/src/lib/assets/image/close_lefts.svg +1 -1
- package/src/lib/assets/image/close_other.svg +1 -1
- package/src/lib/assets/image/close_rights.svg +1 -1
- package/src/lib/assets/image/reload.svg +1 -3
- package/src/lib/assets/image/reload_all.svg +1 -6
- package/src/lib/assets/style/common/mixins.scss +58 -19
- package/src/lib/assets/style/common/var.scss +100 -60
- package/src/lib/assets/style/icon.scss +31 -98
- package/src/lib/assets/style/index.scss +2 -2
- package/src/lib/assets/style/loading.scss +45 -0
- package/src/lib/assets/style/{stackTab.scss → stack-tab.scss} +105 -177
- package/src/lib/assets/style/transition.scss +12 -17
- package/src/lib/components/ContextMenu/ContextMenuItem.vue +5 -5
- package/src/lib/components/ContextMenu/index.vue +22 -19
- package/src/lib/components/PageLoading.vue +5 -5
- package/src/lib/components/StackKeepAlive/StackKeepAlive.vue +5 -3
- package/src/lib/components/TabHeader/TabHeaderItem.vue +13 -7
- package/src/lib/components/TabHeader/TabHeaderScroll.vue +8 -6
- package/src/lib/components/TabHeader/index.vue +16 -2
- package/src/lib/hooks/tabPanel/pageComponentFactory.tsx +83 -7
- package/src/lib/hooks/tabPanel/refresh.ts +27 -9
- package/src/lib/hooks/tabPanel/tabStore.ts +86 -16
- package/src/lib/hooks/useContextMenu.ts +1 -1
- package/src/lib/hooks/useIframeManager.ts +10 -3
- package/src/lib/hooks/useTabActions.ts +8 -4
- package/src/lib/hooks/useTabPanel.tsx +204 -27
- package/src/lib/hooks/useTabRouter.ts +18 -11
- package/src/lib/model/TabModel.ts +5 -0
- package/src/lib/utils/tabInfoEncoder.ts +101 -17
- package/src/lib/utils/urlParser.ts +35 -11
- package/dist/vue-stack-tabs.css +0 -2
- package/src/lib/assets/style/overlayer.scss +0 -46
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* 注意:本模块不依赖 vue-router,也不触发业务事件;路由事务与事件由 useTabPanel /
|
|
13
13
|
* useTabRouter 等上层编排。
|
|
14
14
|
*/
|
|
15
|
-
import { reactive, ref } from 'vue'
|
|
15
|
+
import { markRaw, reactive, ref } from 'vue'
|
|
16
16
|
import type { DefineComponent } from 'vue'
|
|
17
17
|
import type { ITabItem, ITabPage } from '../../model/TabModel'
|
|
18
18
|
import { Stack } from '../../model/TabModel'
|
|
@@ -27,6 +27,12 @@ export interface RemoveTabResult {
|
|
|
27
27
|
nextActiveId: string
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
export interface PageUpdate {
|
|
31
|
+
tabId: string
|
|
32
|
+
pageId: string
|
|
33
|
+
patch: PagePatch
|
|
34
|
+
}
|
|
35
|
+
|
|
30
36
|
/** updatePage 允许修改的字段;页面身份与刷新版本必须通过 store 写入 */
|
|
31
37
|
export interface PagePatch {
|
|
32
38
|
id?: string
|
|
@@ -46,6 +52,8 @@ export interface TabStoreApi extends TabPanelEvictionApi {
|
|
|
46
52
|
canAddTab: () => boolean
|
|
47
53
|
getPages: (tabId: string) => readonly ITabPage[]
|
|
48
54
|
getTopPage: (tabId: string) => ITabPage | null
|
|
55
|
+
/** 读取任意页面的刷新版本;渲染层据此只重建对应页面 */
|
|
56
|
+
getPageRefreshVersion: (pageId: string) => number
|
|
49
57
|
/** 返回深拷贝的页面栈快照,可直接用于 rollback */
|
|
50
58
|
getPageSnapshot: (tabId: string) => ITabPage[]
|
|
51
59
|
|
|
@@ -54,6 +62,8 @@ export interface TabStoreApi extends TabPanelEvictionApi {
|
|
|
54
62
|
addTabs: (tabs: readonly ITabItem[]) => void
|
|
55
63
|
/** 唯一 active 写入口 */
|
|
56
64
|
activateTab: (id: string) => ITabItem | null
|
|
65
|
+
/** 动态更新标签标题;返回 false 表示标签不存在 */
|
|
66
|
+
updateTabTitle: (id: string, title: string) => boolean
|
|
57
67
|
moveTab: (id: string, targetIndex: number) => boolean
|
|
58
68
|
removeTab: (id: string) => RemoveTabResult
|
|
59
69
|
removeTabs: (shouldRemove: (tab: ITabItem, index: number) => boolean) => ITabItem[]
|
|
@@ -65,8 +75,10 @@ export interface TabStoreApi extends TabPanelEvictionApi {
|
|
|
65
75
|
/** 用给定页面替换整个栈(内部深拷贝页面) */
|
|
66
76
|
replacePages: (tabId: string, pages: readonly ITabPage[]) => void
|
|
67
77
|
clearPages: (tabId: string) => void
|
|
68
|
-
/** 修改页面字段(id / refreshVersion / _backParams
|
|
78
|
+
/** 修改页面字段(id / refreshVersion / _backParams);仅 id 变化时重建 tabs 数组 */
|
|
69
79
|
updatePage: (tabId: string, pageId: string, patch: PagePatch) => boolean
|
|
80
|
+
/** 批量修改页面字段,最后最多重建一次 tabs 数组(供 refreshAllTabs 使用) */
|
|
81
|
+
updatePages: (updates: readonly PageUpdate[]) => void
|
|
70
82
|
/** 取出并清除栈顶页面的 _backParams;pageId 不是当前栈顶时返回 null */
|
|
71
83
|
consumeBackParams: (tabId: string, pageId: string) => Record<string, unknown> | null
|
|
72
84
|
|
|
@@ -103,11 +115,15 @@ interface TabRecord {
|
|
|
103
115
|
iframe: boolean
|
|
104
116
|
iframeRefreshMode?: 'postMessage' | 'reload'
|
|
105
117
|
url?: string
|
|
118
|
+
/** 固定在标签栏最前方,不可拖动,其他标签也不能排到它前面 */
|
|
119
|
+
pinned?: boolean
|
|
106
120
|
}
|
|
107
121
|
|
|
108
122
|
interface PageStackAccess {
|
|
109
123
|
getPages: (tabId: string) => readonly ITabPage[]
|
|
110
124
|
getTopPage: (tabId: string) => ITabPage | null
|
|
125
|
+
/** 读取任意页面的刷新版本;渲染层据此只重建对应页面 */
|
|
126
|
+
getPageRefreshVersion: (pageId: string) => number
|
|
111
127
|
appendPage: (tabId: string, page: ITabPage) => void
|
|
112
128
|
popPages: (tabId: string, steps: number) => ITabPage[]
|
|
113
129
|
clearPages: (tabId: string) => void
|
|
@@ -211,6 +227,7 @@ const createTabStore = (context: StackTabsRuntimeContext): TabStoreApi => {
|
|
|
211
227
|
// ---- 查询 ----
|
|
212
228
|
const getTabs = (): readonly ITabItem[] => tabs.value
|
|
213
229
|
|
|
230
|
+
// 返回 tabs.value 中的对象(深度代理后的视图),保持调用方的对象身份比较语义。
|
|
214
231
|
const getTab = (id: string): ITabItem | null => tabs.value.find((tab) => tab.id === id) ?? null
|
|
215
232
|
|
|
216
233
|
const getActiveTab = (): ITabItem | null => tabs.value.find((tab) => tab.active) ?? null
|
|
@@ -234,9 +251,13 @@ const createTabStore = (context: StackTabsRuntimeContext): TabStoreApi => {
|
|
|
234
251
|
return pagesById.get(ids[ids.length - 1] as string) ?? null
|
|
235
252
|
}
|
|
236
253
|
|
|
254
|
+
const getPageRefreshVersion = (pageId: string): number =>
|
|
255
|
+
pagesById.get(pageId)?.refreshVersion ?? 0
|
|
256
|
+
|
|
237
257
|
const findTabByTopPagePath = (path: string): ITabItem | null => {
|
|
258
|
+
// 直接遍历 tabViews,避免原先「getTopPage + getTab(find)」的 O(n²) 组合。
|
|
238
259
|
for (const id of tabOrder.value) {
|
|
239
|
-
if (getTopPage(id)?.path === path) return
|
|
260
|
+
if (getTopPage(id)?.path === path) return tabViews.get(id) ?? null
|
|
240
261
|
}
|
|
241
262
|
return null
|
|
242
263
|
}
|
|
@@ -258,7 +279,8 @@ const createTabStore = (context: StackTabsRuntimeContext): TabStoreApi => {
|
|
|
258
279
|
refreshable: tab.refreshable,
|
|
259
280
|
iframe: tab.iframe,
|
|
260
281
|
iframeRefreshMode: tab.iframeRefreshMode,
|
|
261
|
-
url: tab.url
|
|
282
|
+
url: tab.url,
|
|
283
|
+
pinned: tab.pinned
|
|
262
284
|
})
|
|
263
285
|
|
|
264
286
|
const registerTab = (tab: ITabItem): void => {
|
|
@@ -293,22 +315,37 @@ const createTabStore = (context: StackTabsRuntimeContext): TabStoreApi => {
|
|
|
293
315
|
return getTab(id)
|
|
294
316
|
}
|
|
295
317
|
|
|
318
|
+
const updateTabTitle = (id: string, title: string): boolean => {
|
|
319
|
+
const record = tabsById.get(id)
|
|
320
|
+
if (!record) return false
|
|
321
|
+
if (record.title === title) return true
|
|
322
|
+
record.title = title
|
|
323
|
+
syncTabsView()
|
|
324
|
+
return true
|
|
325
|
+
}
|
|
326
|
+
|
|
296
327
|
const moveTab = (id: string, targetIndex: number): boolean => {
|
|
297
328
|
const order = tabOrder.value
|
|
298
329
|
const sourceIndex = order.indexOf(id)
|
|
299
|
-
if (
|
|
300
|
-
sourceIndex < 0 ||
|
|
301
|
-
targetIndex < 0 ||
|
|
302
|
-
targetIndex >= order.length ||
|
|
303
|
-
sourceIndex === targetIndex
|
|
304
|
-
) {
|
|
330
|
+
if (sourceIndex < 0 || targetIndex < 0 || sourceIndex === targetIndex) {
|
|
305
331
|
return false
|
|
306
332
|
}
|
|
307
333
|
|
|
334
|
+
// pinned 标签(home)固定在最前方:自身不可移动,其他标签也不得插到它前面。
|
|
335
|
+
const sourceRecord = tabsById.get(id)
|
|
336
|
+
if (!sourceRecord || sourceRecord.pinned) return false
|
|
337
|
+
|
|
338
|
+
let firstUnpinnedIndex = 0
|
|
339
|
+
for (let index = 0; index < order.length; index++) {
|
|
340
|
+
if (tabsById.get(order[index] as string)?.pinned) firstUnpinnedIndex = index + 1
|
|
341
|
+
}
|
|
342
|
+
const safeTargetIndex = Math.max(targetIndex, firstUnpinnedIndex)
|
|
343
|
+
if (safeTargetIndex >= order.length || safeTargetIndex === sourceIndex) return false
|
|
344
|
+
|
|
308
345
|
const reordered = [...order]
|
|
309
346
|
const [tabId] = reordered.splice(sourceIndex, 1)
|
|
310
347
|
if (!tabId) return false
|
|
311
|
-
reordered.splice(
|
|
348
|
+
reordered.splice(safeTargetIndex, 0, tabId)
|
|
312
349
|
tabOrder.value = reordered
|
|
313
350
|
syncTabsView()
|
|
314
351
|
return true
|
|
@@ -425,12 +462,17 @@ const createTabStore = (context: StackTabsRuntimeContext): TabStoreApi => {
|
|
|
425
462
|
syncTabsView()
|
|
426
463
|
}
|
|
427
464
|
|
|
428
|
-
const
|
|
465
|
+
const applyPagePatch = (
|
|
466
|
+
tabId: string,
|
|
467
|
+
pageId: string,
|
|
468
|
+
patch: PagePatch
|
|
469
|
+
): 'missing' | 'conflict' | 'same-id' | 'new-id' => {
|
|
429
470
|
const ids = pageIdsByTabId.get(tabId)
|
|
430
|
-
if (!ids || !ids.includes(pageId)) return
|
|
471
|
+
if (!ids || !ids.includes(pageId)) return 'missing'
|
|
431
472
|
|
|
432
473
|
const page = pagesById.get(pageId)
|
|
433
|
-
if (!page) return
|
|
474
|
+
if (!page) return 'missing'
|
|
475
|
+
if (patch.id && patch.id !== pageId && pagesById.has(patch.id)) return 'conflict'
|
|
434
476
|
|
|
435
477
|
Object.assign(page, patch)
|
|
436
478
|
if (patch.id && patch.id !== pageId) {
|
|
@@ -440,11 +482,32 @@ const createTabStore = (context: StackTabsRuntimeContext): TabStoreApi => {
|
|
|
440
482
|
tabId,
|
|
441
483
|
ids.map((id) => (id === pageId ? (patch.id as string) : id))
|
|
442
484
|
)
|
|
485
|
+
return 'new-id'
|
|
443
486
|
}
|
|
444
|
-
|
|
487
|
+
return 'same-id'
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* 刷新版本、_backParams 都是 page 上的响应式字段,读取方会自行更新;
|
|
492
|
+
* 只有页面 id 变化才需要重建 tabs 视图,避免刷新一个标签就 diff 整个标签栏。
|
|
493
|
+
*/
|
|
494
|
+
const updatePage = (tabId: string, pageId: string, patch: PagePatch): boolean => {
|
|
495
|
+
const result = applyPagePatch(tabId, pageId, patch)
|
|
496
|
+
if (result === 'missing' || result === 'conflict') return false
|
|
497
|
+
if (result === 'new-id') syncTabsView()
|
|
445
498
|
return true
|
|
446
499
|
}
|
|
447
500
|
|
|
501
|
+
const updatePages = (updates: readonly PageUpdate[]): void => {
|
|
502
|
+
let needsSync = false
|
|
503
|
+
for (const update of updates) {
|
|
504
|
+
if (applyPagePatch(update.tabId, update.pageId, update.patch) === 'new-id') {
|
|
505
|
+
needsSync = true
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
if (needsSync) syncTabsView()
|
|
509
|
+
}
|
|
510
|
+
|
|
448
511
|
const consumeBackParams = (tabId: string, pageId: string): Record<string, unknown> | null => {
|
|
449
512
|
const top = getTopPage(tabId)
|
|
450
513
|
if (!top || top.id !== pageId || !top._backParams) return null
|
|
@@ -486,6 +549,7 @@ const createTabStore = (context: StackTabsRuntimeContext): TabStoreApi => {
|
|
|
486
549
|
const pageAccess: PageStackAccess = {
|
|
487
550
|
getPages,
|
|
488
551
|
getTopPage,
|
|
552
|
+
getPageRefreshVersion,
|
|
489
553
|
appendPage,
|
|
490
554
|
popPages,
|
|
491
555
|
clearPages
|
|
@@ -506,7 +570,9 @@ const createTabStore = (context: StackTabsRuntimeContext): TabStoreApi => {
|
|
|
506
570
|
iframeRefreshMode: record.iframeRefreshMode,
|
|
507
571
|
url: record.url,
|
|
508
572
|
active: activeTabId.value === id,
|
|
509
|
-
pages
|
|
573
|
+
// pages 的读写都转发给 store,本身不需要被 ref 深度代理。
|
|
574
|
+
pages: markRaw(new PageStackView(id, pageAccess)),
|
|
575
|
+
pinned: record.pinned
|
|
510
576
|
}
|
|
511
577
|
tabViews.set(id, view)
|
|
512
578
|
} else {
|
|
@@ -515,6 +581,7 @@ const createTabStore = (context: StackTabsRuntimeContext): TabStoreApi => {
|
|
|
515
581
|
view.refreshable = record.refreshable
|
|
516
582
|
view.iframe = record.iframe
|
|
517
583
|
view.active = activeTabId.value === id
|
|
584
|
+
view.pinned = record.pinned
|
|
518
585
|
if (record.iframeRefreshMode === undefined) {
|
|
519
586
|
delete view.iframeRefreshMode
|
|
520
587
|
} else {
|
|
@@ -548,10 +615,12 @@ const createTabStore = (context: StackTabsRuntimeContext): TabStoreApi => {
|
|
|
548
615
|
canAddTab,
|
|
549
616
|
getPages,
|
|
550
617
|
getTopPage,
|
|
618
|
+
getPageRefreshVersion,
|
|
551
619
|
getPageSnapshot,
|
|
552
620
|
addTab,
|
|
553
621
|
addTabs,
|
|
554
622
|
activateTab,
|
|
623
|
+
updateTabTitle,
|
|
555
624
|
moveTab,
|
|
556
625
|
removeTab,
|
|
557
626
|
removeTabs,
|
|
@@ -560,6 +629,7 @@ const createTabStore = (context: StackTabsRuntimeContext): TabStoreApi => {
|
|
|
560
629
|
replacePages,
|
|
561
630
|
clearPages,
|
|
562
631
|
updatePage,
|
|
632
|
+
updatePages,
|
|
563
633
|
consumeBackParams,
|
|
564
634
|
isTabMarkedForEviction: (id) => tabIdsToEvict.has(id),
|
|
565
635
|
isCacheMarkedForEviction: (id) => cacheIdsToEvict.has(id),
|
|
@@ -41,7 +41,7 @@ export default () => {
|
|
|
41
41
|
/** 点击菜单外部时关闭菜单。 */
|
|
42
42
|
const handleClickOutside = (ev: MouseEvent) => {
|
|
43
43
|
const target = ev.target as Element
|
|
44
|
-
if (target.closest?.('.stack-
|
|
44
|
+
if (target.closest?.('.stack-tab__context-menu')) return
|
|
45
45
|
// 同一手势的 click 仍在守卫窗口内,跳过关闭判断。
|
|
46
46
|
if (Date.now() - lastContextMenuAt < BUBBLE_GUARD_MS) return
|
|
47
47
|
hideContextMenu()
|
|
@@ -137,10 +137,17 @@ export function useIframeManager(options: UseIframeManagerOptions) {
|
|
|
137
137
|
}
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
-
/**
|
|
140
|
+
/**
|
|
141
|
+
* 同步 iframe 激活和移除状态。
|
|
142
|
+
* 只监听 id:active 签名,普通标签的标题/页面栈变化不会触发遍历。
|
|
143
|
+
*/
|
|
144
|
+
const iframeActivationSignature = computed(() =>
|
|
145
|
+
iframeTabs.value.map((frame) => `${frame.id}:${frame.active ? 1 : 0}`).join('|')
|
|
146
|
+
)
|
|
141
147
|
watch(
|
|
142
|
-
|
|
143
|
-
(
|
|
148
|
+
iframeActivationSignature,
|
|
149
|
+
() => {
|
|
150
|
+
const frames = iframeTabs.value
|
|
144
151
|
for (const f of frames) {
|
|
145
152
|
if (!f.active) continue
|
|
146
153
|
iframeEverActivated[f.id] = true
|
|
@@ -2,7 +2,7 @@ import { isNavigationFailure, useRouter } from 'vue-router'
|
|
|
2
2
|
import type { LocationQueryRaw } from 'vue-router'
|
|
3
3
|
import { defu } from 'defu'
|
|
4
4
|
import useTabPanel from './useTabPanel'
|
|
5
|
-
import { encodeTabInfo } from '../utils/tabInfoEncoder'
|
|
5
|
+
import { createId, encodeTabInfo } from '../utils/tabInfoEncoder'
|
|
6
6
|
import {
|
|
7
7
|
parseUrl,
|
|
8
8
|
isCrossOriginUrl,
|
|
@@ -31,6 +31,7 @@ export default function useTabActions(providedRuntimeContext?: StackTabsRuntimeC
|
|
|
31
31
|
const runtimeContext = providedRuntimeContext ?? resolveStackTabsRuntimeContext()
|
|
32
32
|
const {
|
|
33
33
|
active,
|
|
34
|
+
updateTabTitle,
|
|
34
35
|
hasTab,
|
|
35
36
|
refreshKey,
|
|
36
37
|
reset,
|
|
@@ -61,7 +62,7 @@ export default function useTabActions(providedRuntimeContext?: StackTabsRuntimeC
|
|
|
61
62
|
defaults.iframeRefreshMode = (tab.iframeRefreshMode ?? 'postMessage') as IframeRefreshMode
|
|
62
63
|
}
|
|
63
64
|
const info = defu(tab, defaults)
|
|
64
|
-
if (!info.id) info.id =
|
|
65
|
+
if (!info.id) info.id = createId()
|
|
65
66
|
return info
|
|
66
67
|
}
|
|
67
68
|
|
|
@@ -111,10 +112,12 @@ export default function useTabActions(providedRuntimeContext?: StackTabsRuntimeC
|
|
|
111
112
|
const tabQuery = omitStackTabsReservedQuery(tab.query)
|
|
112
113
|
let query = defu(tabQuery, { __tab }) as LocationQueryRaw
|
|
113
114
|
let path: string
|
|
115
|
+
let hash: string | undefined
|
|
114
116
|
|
|
115
117
|
if (!tab.iframe) {
|
|
116
118
|
const url = parseUrl(tab.path)
|
|
117
119
|
path = url.path
|
|
120
|
+
hash = url.hash || undefined
|
|
118
121
|
query = defu(omitStackTabsReservedQuery(url.query), query, { __tab }) as LocationQueryRaw
|
|
119
122
|
query = {
|
|
120
123
|
...query,
|
|
@@ -132,7 +135,7 @@ export default function useTabActions(providedRuntimeContext?: StackTabsRuntimeC
|
|
|
132
135
|
if (!isAllowedTabUrl(tab.path)) return Promise.reject(new Error('Invalid tab URL'))
|
|
133
136
|
return runNavigationTransaction({
|
|
134
137
|
apply: () => undefined,
|
|
135
|
-
navigate: () => router.push({ path, query }),
|
|
138
|
+
navigate: () => router.push({ path, query, hash }),
|
|
136
139
|
rollback: () => undefined,
|
|
137
140
|
isFailureResult: isNavigationFailure,
|
|
138
141
|
rejectFailureResult: true
|
|
@@ -155,7 +158,7 @@ export default function useTabActions(providedRuntimeContext?: StackTabsRuntimeC
|
|
|
155
158
|
if (typeof document === 'undefined') return undefined
|
|
156
159
|
const pageId = target.pageId ?? (target.tabId ? getTopPage(target.tabId)?.id : undefined)
|
|
157
160
|
if (pageId) return document.getElementById(`W-${pageId}`) ?? undefined
|
|
158
|
-
return document.querySelector<HTMLElement>('.
|
|
161
|
+
return document.querySelector<HTMLElement>('.stack-tab__cache-page-wrapper') ?? undefined
|
|
159
162
|
}
|
|
160
163
|
|
|
161
164
|
/** iframe 标签在新窗口打开(无法嵌入时降级) */
|
|
@@ -181,6 +184,7 @@ export default function useTabActions(providedRuntimeContext?: StackTabsRuntimeC
|
|
|
181
184
|
// 激活与重置
|
|
182
185
|
activeTab: active,
|
|
183
186
|
moveTab,
|
|
187
|
+
updateTabTitle,
|
|
184
188
|
reset,
|
|
185
189
|
|
|
186
190
|
// 数据与配置
|