Skip to content

Commit

Permalink
feat(Tabs): add query and hash when jumping (#546)
Browse files Browse the repository at this point in the history
  • Loading branch information
tolking authored Dec 12, 2024
1 parent 92a93bc commit 7643fe7
Show file tree
Hide file tree
Showing 7 changed files with 102 additions and 60 deletions.
4 changes: 2 additions & 2 deletions demo/Tabs/slots.vue
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
<template>
<pro-tabs>
<template #label="{ icon, title }">
<template #label="{ icon, meta }">
<div class="tabs-label-content">
<el-icon :size="16" class="label-icon">
<component :is="icon" />
</el-icon>
<span>{{ title.toUpperCase() }}</span>
<span>{{ meta.title.toUpperCase() }}</span>
</div>
</template>
</pro-tabs>
Expand Down
19 changes: 12 additions & 7 deletions docs/en-US/components/Tabs.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ Do something before the tab switch with the `before-leave` hook. If `false` is r

Customize the label content of the tab through `label`

::: tip
Since 1.4.0, the `label` slot parameter has been changed to `RouteLocationNormalizedLoadedGeneric`
:::

::: demo
@/demo/Tabs/slots.vue
:::
Expand Down Expand Up @@ -106,13 +110,14 @@ If you enable the `refresh` feature, you need to configure the `refreshPath` att

### Methods

| Name | Description | Type |
| ---------- | ------------------------- | ---------------------- |
| close | close some tab from tabs | (path: string) => void |
| closeOther | close other tab from tabs | () => void |
| Name | Description | Type |
| ---------- | ------------------------- | ------------------------------------------- |
| list | access history list | Ref<RouteLocationNormalizedLoadedGeneric[]> |
| close | close some tab from tabs | (path: string) => void |
| closeOther | close other tab from tabs | () => void |

### Slots

| Name | Description | Type |
| ----- | -------------------------------------- | ----------------------------- |
| label | customize the label content of the tab | { ...route.meta, path, name } |
| Name | Description | Type |
| ----- | -------------------------------------- | ------------------------------------ |
| label | customize the label content of the tab | RouteLocationNormalizedLoadedGeneric |
19 changes: 12 additions & 7 deletions docs/zh-CN/components/Tabs.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ meta:

通过 `label` 自定义标签页的标题内容

::: tip 提示
自 1.4.0 起,`label` 插槽参数更改为 `RouteLocationNormalizedLoadedGeneric` 类型
:::

::: demo
@/demo/Tabs/slots.vue
:::
Expand Down Expand Up @@ -106,13 +110,14 @@ meta:

### 方法

| 方法名 | 说明 | 类型 |
| ---------- | ---------------------------------- | ---------------------- |
| close | 从 tabs 中关闭指定路由的页面 | (path: string) => void |
| closeOther | 从 tabs 中关闭除当前路由的其它路由 | () => void |
| 方法名 | 说明 | 类型 |
| ---------- | ---------------------------------- | ------------------------------------------- |
| list | 浏览记录 | Ref<RouteLocationNormalizedLoadedGeneric[]> |
| close | 从 tabs 中关闭指定路由的页面 | (path: string) => void |
| closeOther | 从 tabs 中关闭除当前路由的其它路由 | () => void |

### 插槽

| 名称 | 说明 | 类型 |
| ----- | -------------- | ----------------------------- |
| label | 自定义标题内容 | { ...route.meta, path, name } |
| 名称 | 说明 | 类型 |
| ----- | -------------- | ------------------------------------ |
| label | 自定义标题内容 | RouteLocationNormalizedLoadedGeneric |
15 changes: 10 additions & 5 deletions src/Tabs/Tabs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { Back, Close, Refresh, Right } from '@element-plus/icons-vue'
import { useTabs, useTabsDropdown, useTabsMenu } from './useTabs'
import { tabsProps } from './props'
import type { VNode } from 'vue'
import type { ITab } from './type'
import type { RouteLocationNormalizedLoadedGeneric } from 'vue-router'

export default defineComponent({
name: 'ProTabs',
Expand Down Expand Up @@ -49,7 +49,10 @@ export default defineComponent({
closeOther,
})

function createDropdown(item: ITab, index: number) {
function createDropdown(
item: RouteLocationNormalizedLoadedGeneric,
index: number,
) {
const menuList: VNode[] = []

if (contextmenu.value?.refresh && props.refreshPath) {
Expand Down Expand Up @@ -122,15 +125,17 @@ export default defineComponent({
)
}

function createItemSlot(item: ITab) {
return slots.label ? slots.label(item) : item.title
function createItemSlot(item: RouteLocationNormalizedLoadedGeneric) {
return slots.label
? slots.label({ ...item.meta, ...item }) // TODO: change to `item` in the next major version
: item.meta.title
}

function createDefault() {
return list.value.map((item, index) => {
return h(
ElTabPane,
{ key: item.path, name: item.path, label: item.title },
{ key: item.path, name: item.path, label: item.meta.title },
{
label: () =>
props.contextmenu
Expand Down
4 changes: 2 additions & 2 deletions src/Tabs/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ describe('Tabs', () => {
test.concurrent('slot', async () => {
const wrapper = await _mount({
template: `<pro-tabs>
<template #label="{ title, path }">
<span class="title">{{ title }}</span>
<template #label="{ meta, path }">
<span class="title">{{ meta.title }}</span>
<span class="path">{{ path }}</span>
</template>
</pro-tabs>`,
Expand Down
17 changes: 4 additions & 13 deletions src/Tabs/type.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
import { tabsProps } from './props'
import type { Ref } from 'vue'
import type {
RouteLocationNormalizedLoaded,
RouteMeta,
RouteRecordName,
} from 'vue-router'
import type { RouteLocationNormalizedLoadedGeneric } from 'vue-router'
import type { Awaitable } from '@vueuse/core'
import type { IDefineProps } from '../types/index'

Expand All @@ -15,22 +11,17 @@ export interface ITabContextmenuProps {
others?: boolean
}

export interface ITab extends RouteMeta {
path: string
name?: RouteRecordName | null
}

export interface ITabsExpose {
list: Ref<ITab[]>
list: Ref<RouteLocationNormalizedLoadedGeneric[]>
close: (path: string) => void
closeOther: () => void
}

interface BeforeAddArg extends ITabsExpose {
oldPath?: string
route: RouteLocationNormalizedLoaded
route: RouteLocationNormalizedLoadedGeneric
}

export type ITabsBeforeAdd = (arg: BeforeAddArg) => Awaitable<false | void>
export type ITabsBeforeAdd = (arg: BeforeAddArg) => Awaitable<boolean | void>

export type ITabsProps = IDefineProps<typeof tabsProps>
84 changes: 60 additions & 24 deletions src/Tabs/useTabs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,23 @@ import { computed, nextTick, Ref, ref, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useLocale } from '../composables/index'
import { isFunction, isObject, throwWarn } from '../utils/index'
import type { RouteLocationNormalizedLoadedGeneric } from 'vue-router'
import type { DropdownInstance } from 'element-plus'
import type { ITabsProps, ITabsExpose, ITab } from './type'
import type { ITabsProps, ITabsExpose } from './type'

interface UseTabs extends ITabsExpose {
active: Ref<string>
to: (path: string) => void
refresh: (item: ITab) => void
closeLeft: (item: ITab, index: number) => void
closeRight: (item: ITab, index: number) => void
closeOthers: (item: ITab, index: number) => void
refresh: (item: RouteLocationNormalizedLoadedGeneric) => void
closeLeft: (item: RouteLocationNormalizedLoadedGeneric, index: number) => void
closeRight: (
item: RouteLocationNormalizedLoadedGeneric,
index: number,
) => void
closeOthers: (
item: RouteLocationNormalizedLoadedGeneric,
index: number,
) => void
}

export function useTabsMenu() {
Expand All @@ -29,54 +36,70 @@ export function useTabs(props: ITabsProps): UseTabs {
const route = useRoute()
const router = useRouter()
const active = ref('')
const list = ref<ITab[]>([])
const list = ref<RouteLocationNormalizedLoadedGeneric[]>([])

watch(
() => route.path,
async (path, oldPath) => {
if (path === props.refreshPath) return
if (oldPath && !props.keepHiddenRoute) {
const item = list.value.find((item) => item.path === oldPath)
item?.hidden && close(oldPath)
item?.meta?.hidden && close(oldPath)
}
let canAdd: boolean | void = true

if (isFunction(props.beforeAdd)) {
try {
const canAdd = await props.beforeAdd({
canAdd = await props.beforeAdd({
route,
oldPath,
list,
close,
closeOther,
})
if (canAdd !== false) {
addTab({ ...route.meta, path, name: route.name })
}
} catch {
throwWarn('[ProTabs] Failed to execute beforeAdd function')
canAdd = false
}
} else {
addTab({ ...route.meta, path, name: route.name })
}

if (canAdd !== false) {
addTab(route)
}
},
{ immediate: true },
)

function addTab(tab: ITab) {
function addTab(tab: RouteLocationNormalizedLoadedGeneric) {
!list.value.find((item) => item.path === tab.path) && list.value.push(tab)
active.value = tab.path
}

function to(path: string) {
if (path !== route.path) {
router.push(path)
const item = list.value.find((item) => item.path === path)

router.push({
path,
query: item?.query,
hash: item?.hash,
})
}
}

function refresh(item: ITab) {
function refresh(item: RouteLocationNormalizedLoadedGeneric) {
if (!props.refreshPath) return
router.push(props.refreshPath).then(() => {
router.push(item.path)
const path = item.path
const index = list.value.findIndex((item) => item.path === path)
list.value.splice(index, 1)

router.replace(props.refreshPath).then(() => {
list.value.splice(index, 0, item)
router.replace({
path,
query: item?.query,
hash: item?.hash,
})
})
}

Expand All @@ -94,21 +117,30 @@ export function useTabs(props: ITabsProps): UseTabs {
}

function closeOther() {
const title = route.meta?.title || ''
list.value = [{ title, path: active.value }]
const item = list.value.find((item) => item.path === route.path)
list.value = item ? [item] : []
}

function closeLeft(item: ITab, index: number) {
function closeLeft(
item: RouteLocationNormalizedLoadedGeneric,
index: number,
) {
list.value.splice(0, index)
to(item.path)
}

function closeRight(item: ITab, index: number) {
function closeRight(
item: RouteLocationNormalizedLoadedGeneric,
index: number,
) {
list.value.splice(index + 1)
to(item.path)
}

function closeOthers(item: ITab, index: number) {
function closeOthers(
item: RouteLocationNormalizedLoadedGeneric,
index: number,
) {
list.value = list.value.filter((_, i) => i === index)
to(item.path)
}
Expand Down Expand Up @@ -192,7 +224,11 @@ export function useTabsDropdown({
})
}

function handleCommand(command: string, item: ITab, index: number) {
function handleCommand(
command: string,
item: RouteLocationNormalizedLoadedGeneric,
index: number,
) {
switch (command) {
case 'refresh':
refresh(item)
Expand Down

0 comments on commit 7643fe7

Please sign in to comment.