vue-stack-tabs 0.1.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.
Files changed (61) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +3 -0
  3. package/dist/favicon.svg +10 -0
  4. package/dist/style.css +1 -0
  5. package/dist/vue-stack-tabs.es.js +6386 -0
  6. package/dist/vue-stack-tabs.umd.js +46 -0
  7. package/package.json +92 -0
  8. package/src/lib/StackTabs.vue +136 -0
  9. package/src/lib/assets/image/close.svg +6 -0
  10. package/src/lib/assets/image/close_all.svg +9 -0
  11. package/src/lib/assets/image/close_fill.svg +1 -0
  12. package/src/lib/assets/image/close_lefts.svg +1 -0
  13. package/src/lib/assets/image/close_other.svg +1 -0
  14. package/src/lib/assets/image/close_rights.svg +1 -0
  15. package/src/lib/assets/image/disable.svg +11 -0
  16. package/src/lib/assets/image/fullscreen.svg +1 -0
  17. package/src/lib/assets/image/left_arrow.svg +1 -0
  18. package/src/lib/assets/image/loading.svg +15 -0
  19. package/src/lib/assets/image/mask.svg +7 -0
  20. package/src/lib/assets/image/move.svg +6 -0
  21. package/src/lib/assets/image/reload.svg +3 -0
  22. package/src/lib/assets/image/reload_all.svg +6 -0
  23. package/src/lib/assets/image/reload_lefts.svg +15 -0
  24. package/src/lib/assets/image/reload_rights.svg +15 -0
  25. package/src/lib/assets/image/restore.svg +1 -0
  26. package/src/lib/assets/image/right_arrow.svg +1 -0
  27. package/src/lib/assets/image/scroll_pointer.svg +13 -0
  28. package/src/lib/assets/style/common/mixins.scss +45 -0
  29. package/src/lib/assets/style/common/var.scss +65 -0
  30. package/src/lib/assets/style/icon.scss +93 -0
  31. package/src/lib/assets/style/index.scss +3 -0
  32. package/src/lib/assets/style/stackTab.scss +395 -0
  33. package/src/lib/assets/style/transition.scss +40 -0
  34. package/src/lib/banner.ts +51 -0
  35. package/src/lib/components/ContextMenu/ContextMenuItem.vue +15 -0
  36. package/src/lib/components/ContextMenu/index.vue +128 -0
  37. package/src/lib/components/TabHeader/TabHeaderButton.vue +35 -0
  38. package/src/lib/components/TabHeader/TabHeaderItem.vue +63 -0
  39. package/src/lib/components/TabHeader/TabHeaderScroll.vue +240 -0
  40. package/src/lib/components/TabHeader/index.vue +134 -0
  41. package/src/lib/hooks/useContextMenu.ts +50 -0
  42. package/src/lib/hooks/useStackTab.ts +65 -0
  43. package/src/lib/hooks/useTabLoading.ts +48 -0
  44. package/src/lib/hooks/useTabRouter.ts +95 -0
  45. package/src/lib/hooks/useTabpanel.tsx +489 -0
  46. package/src/lib/i18n/index.ts +72 -0
  47. package/src/lib/i18n/lang/en.ts +15 -0
  48. package/src/lib/i18n/lang/zh-CN.ts +15 -0
  49. package/src/lib/iframe.vue +7 -0
  50. package/src/lib/index.ts +25 -0
  51. package/src/lib/model/TabModel.ts +182 -0
  52. package/src/lib/utils/TabIdHelper.ts +76 -0
  53. package/src/lib/utils/TabScrollHelper.ts +104 -0
  54. package/src/lib/utils/UriHelper.ts +22 -0
  55. package/types/banner.d.ts +2 -0
  56. package/types/hooks/useStackTab.d.ts +8 -0
  57. package/types/hooks/useTabLoading.d.ts +5 -0
  58. package/types/hooks/useTabRouter.d.ts +5 -0
  59. package/types/iframe.vue.d.ts +2 -0
  60. package/types/index.d.ts +10 -0
  61. package/types/model/TabModel.d.ts +114 -0
@@ -0,0 +1,63 @@
1
+ <template>
2
+ <li
3
+ ref="curtab"
4
+ class="stack-tab__item"
5
+ :class="{
6
+ 'is-active': item.active,
7
+ 'is-icon': item.closable
8
+ }"
9
+ @click="activeTab"
10
+ >
11
+ <!-- <div v-if="!!icon" class="stack-tab__item-icon" :class="icon"></div>-->
12
+ <div class="stack-tab__item-title">
13
+ <div class="stack-tab__item-title-content" :title="title">
14
+ {{ title }}
15
+ </div>
16
+ </div>
17
+ <div
18
+ v-if="item.closable"
19
+ class="stack-tab__icon-close-fill stack-tab__item-button"
20
+ :title="t('VueStackTab.close')"
21
+ @click.stop="closeTab"
22
+ />
23
+ </li>
24
+ </template>
25
+
26
+ <script setup lang="ts" name="TabHeaderItem">
27
+ import { computed, ref, watch } from 'vue'
28
+ import type { ITabItem } from '@/lib/model/TabModel'
29
+ import localeI18n from '@/lib/i18n'
30
+ // 消息
31
+ const emit = defineEmits(['close', 'active'])
32
+
33
+ const { t } = localeI18n().getI18n()
34
+ const curtab = ref<HTMLElement>()
35
+ const props = defineProps<{
36
+ item: ITabItem
37
+ }>()
38
+ /**
39
+ * 如果Active为true,就看看
40
+ */
41
+ watch(
42
+ () => props.item.active,
43
+ (val) => {
44
+ if (val) {
45
+ activeTab()
46
+ }
47
+ }
48
+ )
49
+ const title = computed<string>(() => {
50
+ return props.item.title || t('undefined')
51
+ })
52
+
53
+ const closeTab = () => {
54
+ emit('close', props.item)
55
+ }
56
+ const activeTab = () => {
57
+ // if (!props.active) {
58
+ emit('active', props.item, curtab)
59
+ // }
60
+ }
61
+ </script>
62
+
63
+ <style lang="scss" scoped></style>
@@ -0,0 +1,240 @@
1
+ <template>
2
+ <tab-header-button
3
+ v-if="isButtonVisibled"
4
+ icon-class="stack-tab__icon-left-arrow"
5
+ :disabled="isDisabledLeftButton"
6
+ @click="!isDisabledLeftButton && onButton(-1 * space)"
7
+ />
8
+ <div ref="headerScroll" class="stack-tab__scroll" @wheel.prevent="onWheel">
9
+ <div
10
+ ref="container"
11
+ class="stack-tab__scroll-container"
12
+ :class="{
13
+ 'stack-tab__shadow-inset-left': !isDisabledLeftButton && isDisabledRightButton,
14
+ 'stack-tab__shadow-inset-right': !isDisabledRightButton && isDisabledLeftButton,
15
+ 'stack-tab__shadow-inset-all': !isDisabledLeftButton && !isDisabledRightButton
16
+ }"
17
+ @scroll.passive="update"
18
+ >
19
+ <slot />
20
+ </div>
21
+ <div
22
+ v-show="hasScroller"
23
+ ref="bar"
24
+ class="stack-tab__scrollbar"
25
+ :class="{ 'is-dragging': scrollDragging }"
26
+ >
27
+ <div
28
+ ref="thumb"
29
+ class="stack-tab__scrollbar-thumb"
30
+ :class="{ 'is-dragging': scrollDragging }"
31
+ :style="{
32
+ width: `${thumbWidth}px`,
33
+ transform: `translateX(${thumbLeft}px)`
34
+ }"
35
+ @mousedown.prevent="onDragStart"
36
+ />
37
+ </div>
38
+ </div>
39
+ <tab-header-button
40
+ v-if="isButtonVisibled"
41
+ icon-class="stack-tab__icon-right-arrow"
42
+ :disabled="isDisabledRightButton"
43
+ @click="!isDisabledRightButton && onButton(space!)"
44
+ />
45
+ </template>
46
+
47
+ <script lang="ts" setup>
48
+ import { computed, ref, reactive, onMounted, watch, onUnmounted } from 'vue'
49
+ import { ResizeObserver } from '@juggle/resize-observer'
50
+ import type { ScrollData, DragData } from '@/lib/model/TabModel'
51
+ import { _scrollTo, _scrollIntoView } from '@/lib/utils/TabScrollHelper'
52
+ import TabHeaderButton from '@/lib/components/TabHeader/TabHeaderButton.vue'
53
+
54
+ // ref
55
+ const container = ref<HTMLElement | null>(null)
56
+ const bar = ref<HTMLElement | null>(null)
57
+ const thumb = ref<HTMLElement | null>(null)
58
+ const scrollDragging = ref<boolean>(false)
59
+ const isDisabledLeftButton = ref<boolean>(false)
60
+ const isDisabledRightButton = ref<boolean>(false)
61
+ const isButtonVisibled = ref<boolean>(true)
62
+ const headerScroll = ref<HTMLElement>()
63
+ const props = withDefaults(
64
+ defineProps<{
65
+ // 每次移动距离
66
+ space?: number
67
+ isScrollWheel?: boolean
68
+ isScrollButton?: boolean
69
+ }>(),
70
+ {
71
+ space: 300,
72
+ isScrollWheel: true,
73
+ isScrollButton: true
74
+ }
75
+ )
76
+ /**
77
+ * 滚动条数据
78
+ */
79
+ const scrollData = reactive<ScrollData>({
80
+ clientWidth: 0,
81
+ scrollWidth: 0,
82
+ scrollLeft: 0
83
+ })
84
+ const dragData = reactive<DragData>({
85
+ thumbLeft: 0,
86
+ startScrollLeft: 0,
87
+ startThumbLeft: 0,
88
+ startPageX: 0
89
+ })
90
+
91
+ // 监控滚动条数据用于启用/禁用按钮
92
+ watch(scrollData, (newValue) => {
93
+ setScrollButton(newValue)
94
+ })
95
+
96
+ const setScrollButton = (scrollData: ScrollData) => {
97
+ isDisabledLeftButton.value = scrollData.scrollLeft <= 10
98
+ isDisabledRightButton.value =
99
+ scrollData.clientWidth + scrollData.scrollLeft - scrollData.scrollWidth >= -10
100
+ isButtonVisibled.value = scrollData.scrollWidth !== scrollData.clientWidth
101
+ }
102
+
103
+ // 计算属性
104
+ // 是否拥有滚动条
105
+ const hasScroller = computed<boolean>(() => {
106
+ return scrollData.scrollWidth > scrollData.clientWidth
107
+ })
108
+
109
+ // 滑块宽度
110
+ const thumbWidth = computed<number>(() => {
111
+ if (!hasScroller.value) return 0
112
+
113
+ const { clientWidth, scrollWidth } = scrollData
114
+ return (clientWidth / scrollWidth) * clientWidth
115
+ })
116
+
117
+ // 滑块 left
118
+ const thumbLeft = computed(() => {
119
+ if (!hasScroller.value) return 0
120
+
121
+ const { clientWidth, scrollWidth, scrollLeft } = scrollData
122
+
123
+ return (clientWidth - thumbWidth.value) * (scrollLeft / (scrollWidth - clientWidth))
124
+ })
125
+
126
+ // 方法
127
+ // 更新滚动数据
128
+ const update = () => {
129
+ // 判断 container是否为空,主空直接返回
130
+ if (container.value === null || container.value === undefined) {
131
+ return
132
+ }
133
+ const { clientWidth, scrollWidth, scrollLeft } = container.value
134
+
135
+ Object.assign(scrollData, { clientWidth, scrollWidth, scrollLeft })
136
+ }
137
+ const resizeUpdate = new ResizeObserver(() => {
138
+ update()
139
+ const cur = container.value?.querySelector('.stack-tab__item.is-active')
140
+ if (cur) {
141
+ if (!isInView(cur as HTMLElement)) {
142
+ scrollIntoView(cur as HTMLElement)
143
+ }
144
+ }
145
+ })
146
+ const tabsUpdate = new ResizeObserver(() => {
147
+ if (!hasScroller.value) {
148
+ const cur = container.value?.querySelector('.stack-tab__item.is-active')
149
+ if (cur) {
150
+ if (!isInView(cur as HTMLElement)) {
151
+ scrollIntoView(cur as HTMLElement)
152
+ }
153
+ }
154
+ }
155
+ update()
156
+ })
157
+ // 滚动到指定位置
158
+ const scrollTo = (left: number, smooth = true) => {
159
+ _scrollTo({ wrap: container.value, left, smooth })
160
+ }
161
+
162
+ const onButton = (space: number) => {
163
+ scrollTo(container.value!.scrollLeft + space)
164
+ }
165
+ // 页签鼠标滚动
166
+ const onWheel = (e: WheelEvent) => {
167
+ const isWheelUp = e.deltaY < 0
168
+ scrollTo(container.value!.scrollLeft + (isWheelUp ? -props.space : props.space))
169
+ }
170
+ // 拖拽
171
+ const onDragStart = (e: MouseEvent) => {
172
+ Object.assign(dragData, {
173
+ startPageX: e.pageX,
174
+ startScrollLeft: container.value?.scrollLeft,
175
+ startThumbLeft: thumbLeft.value,
176
+ thumbLeft: thumbLeft.value
177
+ })
178
+ scrollDragging.value = true
179
+ document.addEventListener('mousemove', onDragMove)
180
+ document.addEventListener('mouseup', onDragEnd)
181
+ }
182
+
183
+ // 拖拽移动
184
+ const onDragMove = (e: MouseEvent) => {
185
+ const { clientWidth, scrollWidth } = scrollData
186
+ let thumbLeft = dragData.startThumbLeft + e.pageX - dragData.startPageX
187
+ const maxThumbLeft = clientWidth - thumbWidth.value
188
+
189
+ if (thumbLeft < 0) {
190
+ thumbLeft = 0
191
+ } else if (thumbLeft > maxThumbLeft) {
192
+ thumbLeft = maxThumbLeft
193
+ }
194
+ // 更新滑块定位
195
+ dragData.thumbLeft = thumbLeft
196
+ // 滚动
197
+ scrollTo((thumbLeft * (scrollWidth - clientWidth)) / (clientWidth - thumbWidth.value), false)
198
+ e.preventDefault()
199
+ }
200
+ // 拖拽结束
201
+ const onDragEnd = (e: MouseEvent) => {
202
+ scrollDragging.value = false
203
+ document.removeEventListener('mousemove', onDragMove)
204
+ document.removeEventListener('mouseup', onDragEnd)
205
+
206
+ e.preventDefault()
207
+ }
208
+ onMounted(() => {
209
+ resizeUpdate.observe(headerScroll.value as Element)
210
+ const tabNav = container.value?.querySelector('.stack-tab__nav')
211
+ tabsUpdate.observe(tabNav!)
212
+ })
213
+ onUnmounted(() => {
214
+ resizeUpdate.disconnect()
215
+ tabsUpdate.disconnect()
216
+ })
217
+
218
+ // 滚动到元素
219
+ const scrollIntoView = (el: HTMLElement) => {
220
+ _scrollIntoView({ el, wrap: container, block: 'end', inline: 'center' })
221
+ }
222
+
223
+ // 判断子节点是否完全在可视区域
224
+ const isInView = (el: HTMLElement) => {
225
+ if (!hasScroller.value) {
226
+ return true
227
+ }
228
+ const offsetLeft = el.offsetLeft
229
+ const scrollLeft = container.value!.scrollLeft
230
+
231
+ return !(
232
+ offsetLeft < scrollLeft ||
233
+ offsetLeft + el.clientWidth > scrollLeft + container.value!.clientWidth
234
+ )
235
+ }
236
+
237
+ defineExpose({ scrollIntoView, isInView })
238
+ </script>
239
+
240
+ <style scoped></style>
@@ -0,0 +1,134 @@
1
+ <template>
2
+ <div class="stack-tab__header">
3
+ <slot name="leftButton" />
4
+ <tab-header-scroll
5
+ ref="scroll"
6
+ :space="space"
7
+ :is-scroll-wheel="isScrollWheel"
8
+ :is-scroll-button="isScrollButton"
9
+ >
10
+ <template #default>
11
+ <transition-group
12
+ key="tabItemTrans"
13
+ tag="ul"
14
+ class="stack-tab__nav"
15
+ v-bind="tabTrans"
16
+ appear
17
+ >
18
+ <tab-header-item
19
+ v-for="(item, index) in tabs"
20
+ :key="item.id"
21
+ :contextmenu="contextmenu"
22
+ :item="item"
23
+ @contextmenu.prevent="
24
+ (e: MouseEvent) => showContextMenu(e, item as ITabItem, index, tabs.length)
25
+ "
26
+ @click.middle.prevent="closeTab(item as ITabItem)"
27
+ @close="closeTab"
28
+ @active="activeTab"
29
+ />
30
+ </transition-group>
31
+ </template>
32
+ </tab-header-scroll>
33
+ <slot name="rightButton" />
34
+ <tab-header-button
35
+ :icon-class="maximum ? 'stack-tab__icon-restore' : 'stack-tab__icon-fullscreen'"
36
+ :title="t('VueStackTab.maximum')"
37
+ @click="maximum = !maximum"
38
+ />
39
+ <transition name="stack-tab-zoom" appear>
40
+ <context-menu
41
+ v-if="contextMenuShown"
42
+ key="tabMenuTrans"
43
+ :left="contextMenuData.left"
44
+ :top="contextMenuData.top"
45
+ :tab-item="contextMenuData.item as ITabItem"
46
+ :max="contextMenuData.max"
47
+ :index="contextMenuData.index"
48
+ />
49
+ </transition>
50
+ </div>
51
+ </template>
52
+
53
+ <script lang="ts" setup>
54
+ import { inject, computed, ref } from 'vue'
55
+ import type { TransitionProps, Ref } from 'vue'
56
+ import { TabScrollMode } from '@/lib/model/TabModel'
57
+ import type { ITabItem } from '@/lib/model/TabModel'
58
+ import ContextMenu from '@/lib/components/ContextMenu/index.vue'
59
+ import useContextMenu from '@/lib/hooks/useContextMenu'
60
+ import localeI18n from '@/lib/i18n'
61
+ import useTabpanel from '@/lib/hooks/useTabpanel'
62
+ import TabHeaderItem from '@/lib/components/TabHeader/TabHeaderItem.vue'
63
+ import TabHeaderScroll from '@/lib/components/TabHeader/TabHeaderScroll.vue'
64
+ import TabHeaderButton from '@/lib/components/TabHeader/TabHeaderButton.vue'
65
+ // @ts-ignore
66
+ const { t } = localeI18n().getI18n()
67
+ const maximum = inject<boolean>('maximum')
68
+ const emit = defineEmits(['active'])
69
+ const scroll = ref()
70
+ const { shown: contextMenuShown, contextMenuData, showContextMenu } = useContextMenu()
71
+
72
+ const props = withDefaults(
73
+ defineProps<{
74
+ space: number
75
+ // tab的滚动方式
76
+ tabScrollMode?: TabScrollMode
77
+ // tab动画
78
+ tabTransition?: string | TransitionProps
79
+ contextmenu?: boolean | object
80
+ max?: number
81
+ }>(),
82
+ {
83
+ tabTransition: () => {
84
+ return {
85
+ name: 'stack-tab-zoom'
86
+ }
87
+ },
88
+ tabScrollMode: TabScrollMode.BOTH,
89
+ contextmenu: true,
90
+ max: 20
91
+ }
92
+ )
93
+ const { removeTab, active, tabs } = useTabpanel()
94
+
95
+ // 转换tab动画
96
+ const tabTrans = computed<TransitionProps>(() => {
97
+ if (typeof props.tabTransition === 'string') {
98
+ return {
99
+ name: props.tabTransition
100
+ } as TransitionProps
101
+ }
102
+ return props.tabTransition as TransitionProps
103
+ })
104
+ // 判断是否有滚动按钮
105
+ const isScrollButton = computed<boolean>(() => {
106
+ return props.tabScrollMode === TabScrollMode.BOTH || props.tabScrollMode === TabScrollMode.BUTTON
107
+ })
108
+ // 判断是否可以使用滚轮
109
+ const isScrollWheel =
110
+ props.tabScrollMode === TabScrollMode.BOTH || props.tabScrollMode === TabScrollMode.WHEEL
111
+
112
+ // 关闭前,先判断是不最先中状态
113
+ const closeTab = (item: ITabItem) => {
114
+ // emit('close', item)
115
+ if (item.closable) {
116
+ const activeId = removeTab(item.id)
117
+ emit('active', activeId)
118
+ }
119
+ contextMenuShown.value = false
120
+ }
121
+
122
+ const activeTab = (item: ITabItem, curActiveTab: Ref<HTMLElement>) => {
123
+ if (scroll.value) {
124
+ if (!scroll.value!.isInView(curActiveTab.value)) {
125
+ scroll.value!.scrollIntoView(curActiveTab.value)
126
+ }
127
+
128
+ active(item.id)
129
+ emit('active', item.id)
130
+ }
131
+ }
132
+ </script>
133
+
134
+ <style scoped></style>
@@ -0,0 +1,50 @@
1
+ import { onUnmounted, onMounted, ref, reactive, nextTick } from 'vue'
2
+ import type { ITabItem } from '@/lib/model/TabModel'
3
+ export default () => {
4
+ const contextMenuData = reactive({
5
+ item: {},
6
+ index: -1,
7
+ left: 0,
8
+ top: 0,
9
+ max: 0
10
+ })
11
+ const shown = ref<boolean>(false)
12
+
13
+ onMounted(() => {
14
+ document.addEventListener('click', clickOutSide)
15
+ })
16
+ onUnmounted(() => {
17
+ document.removeEventListener('click', clickOutSide)
18
+ })
19
+
20
+ const showContextMenu = async (e: MouseEvent, item: ITabItem, index: number, max: number) => {
21
+ // 关闭已打开的菜单
22
+ shown.value = false
23
+
24
+ await nextTick(() => {
25
+ const { clientY: top, clientX: left } = e
26
+ shown.value = true
27
+ Object.assign(contextMenuData, { item, index, top, left, max })
28
+
29
+ console.log('showContextMenu in callback')
30
+ })
31
+
32
+ // .then(() => {
33
+
34
+ // })
35
+ }
36
+
37
+ const clickOutSide = (ev: MouseEvent) => {
38
+ const classList = (ev.target as Element).classList
39
+ if (classList.contains('stack-tab__contextmenu')) {
40
+ return
41
+ }
42
+ shown.value = false
43
+ }
44
+
45
+ return {
46
+ shown,
47
+ contextMenuData,
48
+ showContextMenu
49
+ }
50
+ }
@@ -0,0 +1,65 @@
1
+ import { useRouter } from 'vue-router'
2
+ import useTabpanel from '@/lib/hooks/useTabpanel'
3
+ import { throttle } from 'lodash-es'
4
+ import type { ITabData } from '@/lib/model/TabModel'
5
+ import { defu } from 'defu'
6
+ import { encodeTabInfo } from '@/lib/utils/TabIdHelper'
7
+ import { uriDecode } from '@/lib/utils/UriHelper'
8
+ import { ulid } from 'ulidx'
9
+ let iframePath: string
10
+ export default () => {
11
+ const router = useRouter()
12
+ const { active, hasTab, pageShown, reset } = useTabpanel()
13
+
14
+ /**
15
+ * 打开新的TAB页面
16
+ * @param tab
17
+ * @param to
18
+ */
19
+ const openNewTab = throttle(async (tab: ITabData) => {
20
+ if (!pageShown.value) {
21
+ return
22
+ }
23
+ const tabInfo = defu(tab, { refreshable: true, closable: true, iframe: false })
24
+
25
+ /**
26
+ * In the function of openNewTab, if the tab already 'ACTIVE' then do nothing.
27
+ */
28
+ if (hasTab(tabInfo.id!)) {
29
+ active(tabInfo.id!)
30
+ return
31
+ }
32
+ if (!tabInfo.id) {
33
+ tabInfo.id = ulid()
34
+ }
35
+ const __tab = await encodeTabInfo(tabInfo)
36
+ let query = defu(
37
+ {
38
+ __tab
39
+ },
40
+ tab.query
41
+ ) as Record<string, string>
42
+ let path
43
+ if (!tab.iframe) {
44
+ const url = uriDecode(tab.path)
45
+ path = url.path
46
+ query = defu(url.query, query)
47
+ } else {
48
+ const __src = encodeURIComponent(tab.path as string)
49
+ path = iframePath
50
+ query['__src'] = __src
51
+ }
52
+
53
+ router.push({
54
+ path,
55
+ query
56
+ })
57
+ return tabInfo.id
58
+ }, 500)
59
+
60
+ const setIFramePath = (path: string) => {
61
+ iframePath = path
62
+ }
63
+
64
+ return { openNewTab, setIFramePath, active, reset }
65
+ }
@@ -0,0 +1,48 @@
1
+ import { useLoading } from 'vue-loading-overlay'
2
+ import type { ActiveLoader } from 'vue-loading-overlay'
3
+ import { type ComponentInternalInstance, getCurrentInstance, onUnmounted } from 'vue'
4
+
5
+ export default () => {
6
+ const $loading = useLoading()
7
+ const { attrs } = getCurrentInstance() as ComponentInternalInstance
8
+ let loadingInstance: ActiveLoader | null = null
9
+ onUnmounted(() => {
10
+ closeTabLoading()
11
+ })
12
+ const openTabLoading = () => {
13
+ const container = document.querySelector(`#${attrs.tId}`)
14
+ // const wrappers = document.getElementsByClassName('cache-page-wrapper')
15
+ // for(const wrapper of wrappers){
16
+ // const childs = wrapper.children
17
+ // for(const child of childs){
18
+ // if(child.getAttribute("tId")===attrs.tId){
19
+ // container=wrapper
20
+ // break;
21
+ // }
22
+ // }
23
+ // if(container!==null){
24
+ // break;
25
+ // }
26
+ // }
27
+ if (loadingInstance === null) {
28
+ loadingInstance = $loading.show({
29
+ canCancel: false,
30
+ isFullPage: false,
31
+ height: 85,
32
+ width: 85,
33
+ loader: 'spinner',
34
+ color: '#007bff',
35
+
36
+ container: container!
37
+ })
38
+ }
39
+ }
40
+
41
+ const closeTabLoading = () => {
42
+ if (loadingInstance !== null) {
43
+ loadingInstance.hide()
44
+ loadingInstance = null
45
+ }
46
+ }
47
+ return { openTabLoading, closeTabLoading }
48
+ }
@@ -0,0 +1,95 @@
1
+ import { defu } from 'defu'
2
+
3
+ import { getCurrentInstance } from 'vue'
4
+ import type { ComponentInternalInstance } from 'vue'
5
+ import { useRoute, useRouter } from 'vue-router'
6
+
7
+ import type { TabRouteLocationRaw } from '@/lib/model/TabModel'
8
+ import useTabpanel from '@/lib/hooks/useTabpanel'
9
+ import { uriDecode } from '@/lib/utils/UriHelper'
10
+ export default () => {
11
+ const { attrs } = getCurrentInstance() as ComponentInternalInstance
12
+ const { pageShown, getTab, markDeletableCache, removeComponent, getComponent } = useTabpanel()
13
+ const route = useRoute()
14
+ const router = useRouter()
15
+ let currentId = ''
16
+ // const currentPageId = ''
17
+ let currentTab = ''
18
+
19
+ currentId = attrs.tId as string
20
+ // currentPageId = attrs.pId as string
21
+ currentTab = route.query.__tab as string
22
+
23
+ const forward = (to: TabRouteLocationRaw) => {
24
+ pageShown.value = false
25
+ const query = defu(
26
+ {
27
+ __tab: currentTab
28
+ },
29
+ to.query
30
+ )
31
+ router.push({
32
+ path: to.path,
33
+ query
34
+ })
35
+ }
36
+ /**
37
+ * 返回
38
+ * @param to url or step
39
+ * @param query 返回时要加截的数据
40
+ */
41
+ const backward = (to: number | string, query?: Object) => {
42
+ pageShown.value = false
43
+
44
+ const tab = getTab(currentId)
45
+ if (!tab) {
46
+ return
47
+ }
48
+ const tabStack = tab.pages
49
+ let absTo = 0
50
+ if (typeof to === 'number') {
51
+ absTo = Math.abs(to)
52
+ } else {
53
+ let index = 1
54
+ let hasPath = false
55
+ const pageItems = tabStack.list()
56
+ const url = uriDecode(to)
57
+ query = defu(url.query, query)
58
+ for (let i = tabStack.size() - 2; i >= 0; i--) {
59
+ if (pageItems[i].path === url.path) {
60
+ hasPath = true
61
+ break
62
+ }
63
+ index += 1
64
+ }
65
+ if (hasPath) {
66
+ absTo = index
67
+ }
68
+ }
69
+
70
+ if (absTo === 0 || tabStack!.size() <= absTo) {
71
+ return
72
+ }
73
+ // const removeCaches = new Set()
74
+ for (let i = 0; i < absTo; i++) {
75
+ const popRoute = tabStack.pop()
76
+ removeComponent(popRoute!.id)
77
+ markDeletableCache(popRoute!.id)
78
+ }
79
+ const pageRoute = tabStack?.peek()
80
+ const component = getComponent(pageRoute!.id)
81
+ if (component !== null && component !== undefined) {
82
+ const sourceComponent = component.components.DynamicComponent
83
+ sourceComponent.props = defu(query, sourceComponent.props)
84
+ }
85
+ router.push({
86
+ path: pageRoute!.path,
87
+ query: pageRoute!.query
88
+ })
89
+ }
90
+
91
+ return {
92
+ forward,
93
+ backward
94
+ }
95
+ }