vue-stack-tabs 0.1.19 → 0.1.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,137 +1,137 @@
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
- // @ts-nocheck
55
- import { inject, computed, ref, nextTick } from 'vue'
56
- import type { TransitionProps, Ref } from 'vue'
57
- import { TabScrollMode } from '../../model/TabModel'
58
- import type { ITabItem } from '../../model/TabModel'
59
- import ContextMenu from '../ContextMenu/index.vue'
60
- import useContextMenu from '../../hooks/useContextMenu'
61
- import useTabpanel from '../../hooks/useTabpanel'
62
- import TabHeaderItem from './TabHeaderItem.vue'
63
- import TabHeaderScroll from './TabHeaderScroll.vue'
64
- import TabHeaderButton from './TabHeaderButton.vue'
65
- import { useI18n } from 'vue-i18n-lite'
66
- // @ts-ignore
67
- const { t } = useI18n()
68
- const maximum = inject<boolean>('maximum')
69
- const emit = defineEmits(['active'])
70
- const scroll = ref()
71
- const { shown: contextMenuShown, contextMenuData, showContextMenu } = useContextMenu()
72
-
73
- const props = withDefaults(
74
- defineProps<{
75
- space: number
76
- // tab的滚动方式
77
- tabScrollMode?: TabScrollMode
78
- // tab动画
79
- tabTransition?: string | TransitionProps
80
- contextmenu?: boolean | object
81
- max?: number
82
- }>(),
83
- {
84
- tabTransition: () => {
85
- return {
86
- name: 'stack-tab-zoom'
87
- }
88
- },
89
- tabScrollMode: TabScrollMode.BOTH,
90
- contextmenu: true,
91
- max: 20
92
- }
93
- )
94
- const { removeTab, active, tabs } = useTabpanel()
95
-
96
- // 转换tab动画
97
- const tabTrans = computed<TransitionProps>(() => {
98
- if (typeof props.tabTransition === 'string') {
99
- return {
100
- name: props.tabTransition
101
- } as TransitionProps
102
- }
103
- return props.tabTransition as TransitionProps
104
- })
105
- // 判断是否有滚动按钮
106
- const isScrollButton = computed<boolean>(() => {
107
- return props.tabScrollMode === TabScrollMode.BOTH || props.tabScrollMode === TabScrollMode.BUTTON
108
- })
109
- // 判断是否可以使用滚轮
110
- const isScrollWheel =
111
- props.tabScrollMode === TabScrollMode.BOTH || props.tabScrollMode === TabScrollMode.WHEEL
112
-
113
- // 关闭前,先判断是不最先中状态
114
- const closeTab = (item: ITabItem) => {
115
- // emit('close', item)
116
- if (item.closable) {
117
- const activeId = removeTab(item.id)
118
- emit('active', activeId)
119
- }
120
- contextMenuShown.value = false
121
- }
122
-
123
- const activeTab = (item: ITabItem, curActiveTab: Ref<HTMLElement>, isRoute: boolean) => {
124
- if (scroll.value) {
125
- nextTick(() => {
126
- if (!scroll.value!.isInView(curActiveTab.value)) {
127
- scroll.value!.scrollIntoView(curActiveTab.value)
128
- }
129
- })
130
-
131
- active(item.id, isRoute)
132
- emit('active', item.id)
133
- }
134
- }
135
- </script>
136
-
137
- <style scoped></style>
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
+ // @ts-nocheck
55
+ import { inject, computed, ref, nextTick } from 'vue'
56
+ import type { TransitionProps, Ref } from 'vue'
57
+ import { TabScrollMode } from '../../model/TabModel'
58
+ import type { ITabItem } from '../../model/TabModel'
59
+ import ContextMenu from '../ContextMenu/index.vue'
60
+ import useContextMenu from '../../hooks/useContextMenu'
61
+ import useTabpanel from '../../hooks/useTabpanel'
62
+ import TabHeaderItem from './TabHeaderItem.vue'
63
+ import TabHeaderScroll from './TabHeaderScroll.vue'
64
+ import TabHeaderButton from './TabHeaderButton.vue'
65
+ import { useI18n } from 'vue-i18n-lite'
66
+ // @ts-ignore
67
+ const { t } = useI18n()
68
+ const maximum = inject<boolean>('maximum')
69
+ const emit = defineEmits(['active'])
70
+ const scroll = ref()
71
+ const { shown: contextMenuShown, contextMenuData, showContextMenu } = useContextMenu()
72
+
73
+ const props = withDefaults(
74
+ defineProps<{
75
+ space: number
76
+ // tab的滚动方式
77
+ tabScrollMode?: TabScrollMode
78
+ // tab动画
79
+ tabTransition?: string | TransitionProps
80
+ contextmenu?: boolean | object
81
+ max?: number
82
+ }>(),
83
+ {
84
+ tabTransition: () => {
85
+ return {
86
+ name: 'stack-tab-zoom'
87
+ }
88
+ },
89
+ tabScrollMode: TabScrollMode.BOTH,
90
+ contextmenu: true,
91
+ max: 20
92
+ }
93
+ )
94
+ const { removeTab, active, tabs } = useTabpanel()
95
+
96
+ // 转换tab动画
97
+ const tabTrans = computed<TransitionProps>(() => {
98
+ if (typeof props.tabTransition === 'string') {
99
+ return {
100
+ name: props.tabTransition
101
+ } as TransitionProps
102
+ }
103
+ return props.tabTransition as TransitionProps
104
+ })
105
+ // 判断是否有滚动按钮
106
+ const isScrollButton = computed<boolean>(() => {
107
+ return props.tabScrollMode === TabScrollMode.BOTH || props.tabScrollMode === TabScrollMode.BUTTON
108
+ })
109
+ // 判断是否可以使用滚轮
110
+ const isScrollWheel =
111
+ props.tabScrollMode === TabScrollMode.BOTH || props.tabScrollMode === TabScrollMode.WHEEL
112
+
113
+ // 关闭前,先判断是不最先中状态
114
+ const closeTab = (item: ITabItem) => {
115
+ // emit('close', item)
116
+ if (item.closable) {
117
+ const activeId = removeTab(item.id)
118
+ emit('active', activeId)
119
+ }
120
+ contextMenuShown.value = false
121
+ }
122
+
123
+ const activeTab = (item: ITabItem, curActiveTab: Ref<HTMLElement>, isRoute: boolean) => {
124
+ if (scroll.value) {
125
+ nextTick(() => {
126
+ if (!scroll.value!.isInView(curActiveTab.value)) {
127
+ scroll.value!.scrollIntoView(curActiveTab.value)
128
+ }
129
+ })
130
+
131
+ active(item.id, isRoute)
132
+ emit('active', item.id)
133
+ }
134
+ }
135
+ </script>
136
+
137
+ <style scoped></style>
@@ -1,75 +1,102 @@
1
- import { useRouter } from 'vue-router'
2
- import useTabpanel from '../hooks/useTabpanel'
3
- import { throttle } from 'lodash-es'
4
- import type { ITabData } from '../model/TabModel'
5
- import { defu } from 'defu'
6
- import { encodeTabInfo } from '../utils/TabIdHelper'
7
- import { uriDecode } from '../utils/UriHelper'
8
- import { ulid } from 'ulidx'
9
- import { MittType, useEmitter } from './useTabMitt'
10
- let iframePath: string
11
- export default () => {
12
- const router = useRouter()
13
- const { active, hasTab, pageShown, reset, canAddTab } = useTabpanel()
14
- const emitter = useEmitter()
15
- /**
16
- * 打开新的TAB页面
17
- * @param tab
18
- * @param to
19
- */
20
- const openNewTab = throttle((tab: ITabData) => {
21
- return new Promise((resolve, reject) => {
22
- if (!pageShown.value) {
23
- reject()
24
- return
25
- }
26
- const tabInfo = defu(tab, { refreshable: true, closable: true, iframe: false })
27
-
28
- /**
29
- * In the function of openNewTab, if the tab already 'ACTIVE' then do nothing.
30
- */
31
- if (hasTab(tabInfo.id!)) {
32
- emitter.emit(MittType.TAB_ACTIVE, { id: tabInfo.id! })
33
- // active(tabInfo.id!)
34
- resolve(tabInfo.id)
35
- return
36
- }
37
- if (!tabInfo.id) {
38
- tabInfo.id = ulid()
39
- }
40
- if (!canAddTab()) {
41
- reject('Max Size')
42
- return
43
- }
44
- const __tab = encodeTabInfo(tabInfo)
45
- let query = defu(
46
- {
47
- __tab
48
- },
49
- tab.query
50
- ) as Record<string, string>
51
- let path
52
- if (!tab.iframe) {
53
- const url = uriDecode(tab.path)
54
- path = url.path
55
- query = defu(url.query, query)
56
- } else {
57
- const __src = encodeURIComponent(tab.path as string)
58
- path = iframePath
59
- query['__src'] = __src
60
- }
61
-
62
- router.push({
63
- path,
64
- query
65
- })
66
- resolve(tabInfo.id)
67
- })
68
- }, 500)
69
-
70
- const setIFramePath = (path: string) => {
71
- iframePath = path
72
- }
73
-
74
- return { openNewTab, setIFramePath, active, reset }
75
- }
1
+ import { useRouter } from 'vue-router'
2
+ import useTabpanel from '../hooks/useTabpanel'
3
+ import { throttle } from 'lodash-es'
4
+ import type { ITabData, ITabItem, ITabPage } from '../model/TabModel'
5
+ import { defu } from 'defu'
6
+ import { unref } from 'vue'
7
+ import { encodeTabInfo } from '../utils/TabIdHelper'
8
+ import { uriDecode } from '../utils/UriHelper'
9
+ import { ulid } from 'ulidx'
10
+ import { MittType, useEmitter } from './useTabMitt'
11
+ import { Stack } from '../model/TabModel'
12
+ let iframePath: string
13
+ export default () => {
14
+ const router = useRouter()
15
+ const { active, hasTab, pageShown, reset, canAddTab, tabs, addTab, initialed } = useTabpanel()
16
+ const emitter = useEmitter()
17
+ /**
18
+ * 打开新的TAB页面
19
+ * @param tab
20
+ * @param to
21
+ */
22
+ const openNewTab = throttle((tab: ITabData) => {
23
+ return new Promise((resolve, reject) => {
24
+ if (!pageShown.value) {
25
+ reject()
26
+ return
27
+ }
28
+ const tabInfo = defu(tab, { refreshable: true, closable: true, iframe: false })
29
+
30
+ /**
31
+ * In the function of openNewTab, if the tab already 'ACTIVE' then do nothing.
32
+ */
33
+ if (hasTab(tabInfo.id!)) {
34
+ emitter.emit(MittType.TAB_ACTIVE, { id: tabInfo.id! })
35
+ // active(tabInfo.id!)
36
+ resolve(tabInfo.id)
37
+ return
38
+ }
39
+ if (!tabInfo.id) {
40
+ tabInfo.id = ulid()
41
+ }
42
+ if (!canAddTab()) {
43
+ reject('Max Size')
44
+ return
45
+ }
46
+
47
+ if (initialed) {
48
+ // 增加tab
49
+ let activeTab: ITabItem | null = null
50
+ for (const tab of unref(tabs)) {
51
+ tab.active = false
52
+ }
53
+ activeTab = {
54
+ id: tabInfo.id!,
55
+ title: tabInfo.title,
56
+ closable: tabInfo.closable!,
57
+ refreshable: tabInfo.refreshable!,
58
+ iframe: tabInfo.iframe!,
59
+ url: tabInfo.iframe ? tab.path : null,
60
+ active: true,
61
+ pages: new Stack<ITabPage>()
62
+ } as ITabItem
63
+ addTab(activeTab).finally(() => {
64
+ toPage(tab, tabInfo)
65
+ resolve(tabInfo.id)
66
+ })
67
+ } else {
68
+ toPage(tab, tabInfo)
69
+ resolve(tabInfo.id)
70
+ }
71
+ })
72
+ }, 500)
73
+ const toPage = (tab: ITabData, tabInfo: any) => {
74
+ const __tab = encodeTabInfo(tabInfo)
75
+ let query = defu(
76
+ {
77
+ __tab
78
+ },
79
+ tab.query
80
+ ) as Record<string, string>
81
+ let path
82
+ if (!tab.iframe) {
83
+ const url = uriDecode(tab.path)
84
+ path = url.path
85
+ query = defu(url.query, query)
86
+ } else {
87
+ const __src = encodeURIComponent(tab.path as string)
88
+ path = iframePath
89
+ query['__src'] = __src
90
+ }
91
+
92
+ router.push({
93
+ path,
94
+ query
95
+ })
96
+ }
97
+ const setIFramePath = (path: string) => {
98
+ iframePath = path
99
+ }
100
+
101
+ return { openNewTab, setIFramePath, active, reset }
102
+ }
@@ -1,18 +1,18 @@
1
- import { type ComponentInternalInstance, getCurrentInstance, onUnmounted } from 'vue'
2
- import { MittType, useEmitter } from '../hooks/useTabMitt'
3
-
4
- export default () => {
5
- const emitter = useEmitter()
6
- const { attrs } = getCurrentInstance() as ComponentInternalInstance
7
- onUnmounted(() => {
8
- closeTabLoading()
9
- })
10
- const openTabLoading = () => {
11
- emitter.emit(MittType.PAGE_LOADING, { tId: attrs.tId, value: true })
12
- }
13
-
14
- const closeTabLoading = () => {
15
- emitter.emit(MittType.PAGE_LOADING, { tId: attrs.tId, value: false })
16
- }
17
- return { openTabLoading, closeTabLoading }
18
- }
1
+ import { type ComponentInternalInstance, getCurrentInstance, onUnmounted } from 'vue'
2
+ import { MittType, useEmitter } from '../hooks/useTabMitt'
3
+
4
+ export default () => {
5
+ const emitter = useEmitter()
6
+ const { attrs } = getCurrentInstance() as ComponentInternalInstance
7
+ onUnmounted(() => {
8
+ closeTabLoading()
9
+ })
10
+ const openTabLoading = () => {
11
+ emitter.emit(MittType.PAGE_LOADING, { tId: attrs.tId, value: true })
12
+ }
13
+
14
+ const closeTabLoading = () => {
15
+ emitter.emit(MittType.PAGE_LOADING, { tId: attrs.tId, value: false })
16
+ }
17
+ return { openTabLoading, closeTabLoading }
18
+ }
@@ -1,30 +1,30 @@
1
- import type { Emitter, EventType } from 'mitt'
2
- import mitt from 'mitt'
3
- import { type App, inject } from 'vue'
4
- // const emitter = mitt()
5
- // Cutsom type
6
- declare module '@vue/runtime-core' {
7
- export interface ComponentCustomProperties {
8
- $layerEmitter: Emitter<Record<EventType, unknown>>
9
- }
10
- }
11
- const emitter: Emitter<Record<EventType, unknown>> = mitt()
12
-
13
- const plugin = {
14
- install(app: App) {
15
- // const emitter = mitt()
16
- app.provide('tabEmitter', emitter)
17
- // app.config.globalProperties.$emitter = emitter
18
- }
19
- }
20
- // export const setEmitter = (mitter: Emitter<Record<EventType, unknown>>) => {
21
- // emitter = mitter
22
- // }
23
- export const useEmitter = <T extends Record<EventType, unknown>>(): Emitter<T> => {
24
- return inject('tabEmitter')!
25
- }
26
- export enum MittType {
27
- PAGE_LOADING = 'PAGE_LOADING',
28
- TAB_ACTIVE = 'TAB_ACTIVE'
29
- }
30
- export default plugin
1
+ import type { Emitter, EventType } from 'mitt'
2
+ import mitt from 'mitt'
3
+ import { type App, inject } from 'vue'
4
+ // const emitter = mitt()
5
+ // Cutsom type
6
+ declare module '@vue/runtime-core' {
7
+ export interface ComponentCustomProperties {
8
+ $layerEmitter: Emitter<Record<EventType, unknown>>
9
+ }
10
+ }
11
+ const emitter: Emitter<Record<EventType, unknown>> = mitt()
12
+
13
+ const plugin = {
14
+ install(app: App) {
15
+ // const emitter = mitt()
16
+ app.provide('tabEmitter', emitter)
17
+ // app.config.globalProperties.$emitter = emitter
18
+ }
19
+ }
20
+ // export const setEmitter = (mitter: Emitter<Record<EventType, unknown>>) => {
21
+ // emitter = mitter
22
+ // }
23
+ export const useEmitter = <T extends Record<EventType, unknown>>(): Emitter<T> => {
24
+ return inject('tabEmitter')!
25
+ }
26
+ export enum MittType {
27
+ PAGE_LOADING = 'PAGE_LOADING',
28
+ TAB_ACTIVE = 'TAB_ACTIVE'
29
+ }
30
+ export default plugin
@@ -7,17 +7,22 @@ import { type RouteLocationPathRaw, useRoute, useRouter } from 'vue-router'
7
7
  import useTabpanel from '@/lib/hooks/useTabpanel'
8
8
  import { uriDecode } from '@/lib/utils/UriHelper'
9
9
  export default () => {
10
- const { attrs } = getCurrentInstance() as ComponentInternalInstance
11
- const { pageShown, getTab, markDeletableCache, removeComponent, getComponent } = useTabpanel()
10
+ const { attrs, props } = getCurrentInstance() as ComponentInternalInstance
11
+ const { pageShown, getTab, markDeletableCache, removeComponent, getComponent, addPageScroller } =
12
+ useTabpanel()
12
13
  const route = useRoute()
13
14
  const router = useRouter()
14
15
  let currentId = ''
15
16
  // const currentPageId = ''
16
17
  let currentTab = ''
17
-
18
- currentId = attrs.tId as string
18
+ currentId = (attrs.tId || props.tId) as string
19
+ const currentPageId = (attrs.pId || props.pId) as string
19
20
  // currentPageId = attrs.pId as string
20
21
  currentTab = route.query.__tab as string
22
+
23
+ const addScroller = (...scroller: string[]) => {
24
+ addPageScroller(currentPageId, ...scroller)
25
+ }
21
26
  const forward = (to: RouteLocationPathRaw) => {
22
27
  pageShown.value = false
23
28
  const query = defu(
@@ -88,6 +93,7 @@ export default () => {
88
93
 
89
94
  return {
90
95
  forward,
91
- backward
96
+ backward,
97
+ addScroller
92
98
  }
93
99
  }