mirror of
https://gitee.com/newgateway/vtj.git
synced 2026-06-30 23:45:12 +08:00
重构 Mask
This commit is contained in:
@@ -53,16 +53,29 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "home",
|
||||
"title": "首页工作台",
|
||||
"type": "dialog",
|
||||
"url": "/ui/mask"
|
||||
},
|
||||
{
|
||||
"id": "test",
|
||||
"title": "测试无图标",
|
||||
"title": "测试",
|
||||
"children": [
|
||||
{
|
||||
"id": "test_1",
|
||||
"title": "测试无图标",
|
||||
"title": "测试弹窗打开",
|
||||
"disabled": false,
|
||||
"type": "route",
|
||||
"type": "dialog",
|
||||
"url": "/ui/mask/page?id=test_1"
|
||||
},
|
||||
{
|
||||
"id": "test_2",
|
||||
"title": "测试新开窗口",
|
||||
"disabled": false,
|
||||
"type": "window",
|
||||
"url": "https://www.baidu.com/"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -2,10 +2,9 @@
|
||||
<XMask
|
||||
:menus="menus"
|
||||
:favorites="favorites"
|
||||
manual
|
||||
@select="onMenuSelect"
|
||||
:menuAdapter="menuAdapter"
|
||||
:defaultActiveMenu="defaultActiveMenu"
|
||||
homepage="/ui/mask"
|
||||
home="/ui/mask"
|
||||
:tabs="20"
|
||||
:actions="actions"
|
||||
theme
|
||||
@@ -40,11 +39,14 @@
|
||||
}).then((res) => res.data);
|
||||
};
|
||||
|
||||
const onMenuSelect = (menu: MenuDataItem) => {
|
||||
router.push({
|
||||
path: '/ui/mask/page',
|
||||
query: { id: menu.id }
|
||||
});
|
||||
const menuAdapter = (menu: MenuDataItem) => {
|
||||
return {
|
||||
...menu,
|
||||
url:
|
||||
!menu.type || menu.type === 'route'
|
||||
? `/ui/mask/page?id=${menu.id}`
|
||||
: menu.url
|
||||
};
|
||||
};
|
||||
|
||||
const defaultActiveMenu = (
|
||||
|
||||
@@ -11,25 +11,26 @@
|
||||
import { ref } from 'vue';
|
||||
import { ElInput, ElButton } from 'element-plus';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { useMask } from '@vtj/ui';
|
||||
import { useMask, MaskTab } from '@vtj/ui';
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const inputValue = ref('');
|
||||
const mask = useMask();
|
||||
const onClick = () => {
|
||||
router.push('/ui/mask/subpage');
|
||||
mask.exposed?.addTab({
|
||||
menu: {
|
||||
id: 'aaaaa',
|
||||
title: '测试'
|
||||
},
|
||||
closable: true
|
||||
});
|
||||
};
|
||||
|
||||
defineOptions({
|
||||
tabAdapter() {
|
||||
console.log('component tabAdapter');
|
||||
}
|
||||
name: 'InnerPage'
|
||||
});
|
||||
|
||||
const defineTab = async () => {
|
||||
return {
|
||||
// title: '自定义标签'
|
||||
};
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
defineTab
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -2,10 +2,16 @@
|
||||
<div>Subpage</div>
|
||||
</template>
|
||||
|
||||
<script type="ts" setup>
|
||||
import {useMask} from '@vtj/ui'
|
||||
<script lang="ts" setup>
|
||||
import { useMask } from '@vtj/ui';
|
||||
const defineTab = () => {
|
||||
return {
|
||||
title: '二级页面'
|
||||
};
|
||||
};
|
||||
|
||||
// const mask = useMask();
|
||||
// mask.exposed.openTab('/ui/mask/subpage', {title:'测试跳转'});
|
||||
// console.log('mask', mask)
|
||||
|
||||
defineExpose({
|
||||
defineTab
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -10,13 +10,13 @@ const packagesPath = resolve('../../packages');
|
||||
|
||||
const alias = {
|
||||
// '@vtj/utils': join(packagesPath, 'utils/src/index.ts'),
|
||||
// '@vtj/ui/lib/style.css': join(packagesPath, 'ui/src/style/index.scss'),
|
||||
'@vtj/ui/lib/style.css': join(packagesPath, 'ui/src/style/index.scss'),
|
||||
// '@vtj/engine/lib/style.css': join(
|
||||
// packagesPath,
|
||||
// 'engine/src/style/index.scss'
|
||||
// ),
|
||||
// '@vtj/icons/lib/style.css': join(packagesPath, 'icons/src/style.scss'),
|
||||
// '@vtj/ui': join(packagesPath, 'ui/src'),
|
||||
'@vtj/ui': join(packagesPath, 'ui/src')
|
||||
// '@vtj/icons': join(packagesPath, 'icons/src'),
|
||||
// '@vtj/engine': join(packagesPath, 'engine/src'),
|
||||
// '@vtj/runtime': join(packagesPath, 'runtime/src')
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<Brand
|
||||
:logo="props.logo"
|
||||
:title="props.title"
|
||||
:url="props.homepage"
|
||||
:url="homeTab.url"
|
||||
:collapsed="collapsed"></Brand>
|
||||
</template>
|
||||
<SwitchBar
|
||||
@@ -21,7 +21,7 @@
|
||||
:flatMenus="flatMenus"
|
||||
:menus="menus"
|
||||
:active="active"
|
||||
@select="selectMenu"></Menu>
|
||||
@select="select"></Menu>
|
||||
</Sidebar>
|
||||
<XContainer class="x-mask__main" grow flex direction="column">
|
||||
<XContainer
|
||||
@@ -32,14 +32,14 @@
|
||||
ref="tabRef"
|
||||
:favorites="favorites"
|
||||
:tabs="showTabs"
|
||||
:isActiveTab="isActiveTab"
|
||||
:home="homeTab"
|
||||
:value="active?.id"
|
||||
@click="activeTab"
|
||||
@home="activeHome"
|
||||
:value="currentTabValue"
|
||||
:isActiveTab="isActiveTab"
|
||||
@click="onTabClick"
|
||||
@remove="removeTab"
|
||||
@refresh="onRefresh"
|
||||
@toggleFavorite="toggleFavorite"></Tabs>
|
||||
@toggleFavorite="toggleFavorite"
|
||||
@dialog="onTabDialog"></Tabs>
|
||||
<Toolbar
|
||||
:tabs="dropdownTabs"
|
||||
:actions="props.actions"
|
||||
@@ -80,11 +80,7 @@
|
||||
nextTick,
|
||||
ref
|
||||
} from 'vue';
|
||||
import {
|
||||
useRoute,
|
||||
useRouter,
|
||||
RouteLocationNormalizedLoaded
|
||||
} from 'vue-router';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { XContainer, MenuDataItem, ActionMenuItem, ActionProps } from '../';
|
||||
import Sidebar from './components/Sidebar.vue';
|
||||
import SwitchBar from './components/SwitchBar.vue';
|
||||
@@ -94,7 +90,13 @@
|
||||
import Toolbar from './components/Toolbar.vue';
|
||||
import Avatar from './components/Avatar.vue';
|
||||
import Content from './components/Content.vue';
|
||||
import { maskProps, MASK_INSTANCE_KEY, MaskEmits } from './types';
|
||||
import {
|
||||
maskProps,
|
||||
MASK_INSTANCE_KEY,
|
||||
MaskEmits,
|
||||
MaskTab,
|
||||
MaskDefineTab
|
||||
} from './types';
|
||||
import { useMenus, useTabs, useFavorites } from './use';
|
||||
|
||||
defineOptions({
|
||||
@@ -116,8 +118,8 @@
|
||||
search,
|
||||
select,
|
||||
active,
|
||||
homeMenu
|
||||
} = useMenus(props, emit, router);
|
||||
getMenuByUrl
|
||||
} = useMenus(props, emit);
|
||||
|
||||
const { favorites, toggleFavorite } = useFavorites(props);
|
||||
|
||||
@@ -128,56 +130,47 @@
|
||||
dropdownTabs,
|
||||
isActiveTab,
|
||||
removeTab,
|
||||
activeTab,
|
||||
activeHome,
|
||||
addTab,
|
||||
removeAllTabs,
|
||||
removeOtherTabs,
|
||||
moveToShow
|
||||
} = useTabs(props, emit, router, active, select, homeMenu);
|
||||
moveToShow,
|
||||
currentTabValue,
|
||||
isHomeTab
|
||||
} = useTabs(props, emit);
|
||||
|
||||
const selectMenu = async (menu: MenuDataItem) => {
|
||||
select(menu);
|
||||
const { type = 'route' } = menu;
|
||||
if (type === 'route') {
|
||||
const init = async (menus?: MenuDataItem[]) => {
|
||||
if (!menus || !menus.length) return;
|
||||
const fullPath = route.fullPath;
|
||||
const menu = getMenuByUrl(fullPath);
|
||||
if (isHomeTab(fullPath)) {
|
||||
currentTabValue.value = fullPath;
|
||||
} else {
|
||||
await nextTick();
|
||||
addTab({
|
||||
menu,
|
||||
closable: false
|
||||
});
|
||||
const { url = fullPath, icon, title = '新建标签页' } = menu || {};
|
||||
const view = contentRef.value.getCacheComponent(fullPath);
|
||||
const defineTab = view?.exposed?.defineTab as MaskDefineTab;
|
||||
const tab: MaskTab = Object.assign(
|
||||
{ url, icon, title, menu },
|
||||
defineTab ? await defineTab() : {}
|
||||
);
|
||||
addTab(tab);
|
||||
}
|
||||
if (menu) {
|
||||
await nextTick();
|
||||
active.value = menu;
|
||||
}
|
||||
};
|
||||
|
||||
const defaultActiveMenu =
|
||||
props.defaultActiveMenu ??
|
||||
((to: RouteLocationNormalizedLoaded) => {
|
||||
return flatMenus.value.find((n) => n.url === to.fullPath);
|
||||
});
|
||||
|
||||
watch(
|
||||
flatMenus,
|
||||
() => {
|
||||
if (!flatMenus.value.length) return;
|
||||
const current = defaultActiveMenu(route, flatMenus.value);
|
||||
if (current) {
|
||||
selectMenu(current);
|
||||
} else {
|
||||
active.value = null;
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
watch(flatMenus, init, { immediate: true });
|
||||
watch(
|
||||
() => route.fullPath,
|
||||
() => {
|
||||
console.log('route change', route.fullPath);
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
() => init(flatMenus.value)
|
||||
);
|
||||
|
||||
const onTabClick = (tab: MaskTab) => {
|
||||
router.push(tab.url).catch((e) => e);
|
||||
};
|
||||
|
||||
const onActionClick = (action: ActionProps) => {
|
||||
emit('actionClick', action);
|
||||
};
|
||||
@@ -190,6 +183,10 @@
|
||||
contentRef.value.refresh();
|
||||
};
|
||||
|
||||
const onTabDialog = (tab: MaskTab) => {
|
||||
console.log('open dialog', tab);
|
||||
};
|
||||
|
||||
provide(MASK_INSTANCE_KEY, instance as ComponentInternalInstance);
|
||||
|
||||
defineExpose({
|
||||
@@ -198,7 +195,7 @@
|
||||
flatMenus,
|
||||
favorites,
|
||||
active,
|
||||
selectMenu,
|
||||
select,
|
||||
addTab
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,28 +1,24 @@
|
||||
<template>
|
||||
<XContainer class="x-mask__content" :flex="false" grow padding>
|
||||
<slot></slot>
|
||||
<KeepAlive :key="aliveKey">
|
||||
<Suspense>
|
||||
<RouterView ref="viewRef" :key="viewKey"></RouterView>
|
||||
</Suspense>
|
||||
</KeepAlive>
|
||||
|
||||
<RouterView v-slot="{ Component, route }">
|
||||
<KeepAlive ref="aliveRef">
|
||||
<component v-if="aliveKey" :is="Component" :key="route.fullPath" />
|
||||
</KeepAlive>
|
||||
</RouterView>
|
||||
</XContainer>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { KeepAlive, Suspense, computed, ref } from 'vue';
|
||||
import { KeepAlive } from 'vue';
|
||||
import { RouterView } from 'vue-router';
|
||||
import { XContainer } from '../../';
|
||||
import { RouterView, useRoute } from 'vue-router';
|
||||
const route = useRoute();
|
||||
const viewKey = computed(() => route.fullPath);
|
||||
import { useViewCache } from '../use';
|
||||
|
||||
const aliveKey = ref(Symbol());
|
||||
const viewRef = ref();
|
||||
const refresh = () => {
|
||||
aliveKey.value = Symbol();
|
||||
};
|
||||
const { aliveKey, aliveRef, refresh, getCacheComponent } = useViewCache();
|
||||
|
||||
defineExpose({
|
||||
viewRef,
|
||||
getCacheComponent,
|
||||
refresh
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -12,23 +12,21 @@
|
||||
:model-value="props.value"
|
||||
@tab-remove="onTabRemove"
|
||||
@tab-click="onTabClick">
|
||||
<ElTabPane v-if="props.home" :name="props.home.menu.id">
|
||||
<ElTabPane v-if="props.home" :name="props.home.url">
|
||||
<template #label>
|
||||
<div class="x-mask-tabs__trigger">
|
||||
<component
|
||||
v-if="props.home.menu.icon"
|
||||
:is="(useIcon(props.home.menu.icon) as any)"></component>
|
||||
v-if="props.home.icon"
|
||||
:is="(useIcon(props.home.icon) as any)"></component>
|
||||
|
||||
<span v-if="props.home.menu.title">{{
|
||||
props.home.menu.title
|
||||
}}</span>
|
||||
<span v-if="props.home.title">{{ props.home.title }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</ElTabPane>
|
||||
<ElTabPane
|
||||
v-for="tab in props.tabs"
|
||||
:key="`tab_${tab.menu.id}`"
|
||||
:name="tab.menu.id"
|
||||
:key="tab.id || tab.url"
|
||||
:name="tab.url"
|
||||
lazy
|
||||
closable>
|
||||
<template #label>
|
||||
@@ -40,10 +38,10 @@
|
||||
<template #reference>
|
||||
<div class="x-mask-tabs__trigger">
|
||||
<component
|
||||
v-if="tab.menu.icon"
|
||||
:is="(useIcon(tab.menu.icon) as any)"></component>
|
||||
v-if="tab.icon"
|
||||
:is="(useIcon(tab.icon) as any)"></component>
|
||||
|
||||
<span v-if="tab.menu.title">{{ tab.menu.title }}</span>
|
||||
<span v-if="tab.title">{{ tab.title }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<XActionBar
|
||||
@@ -89,14 +87,14 @@
|
||||
const emit = defineEmits<{
|
||||
click: [tab: MaskTab];
|
||||
remove: [tab: MaskTab];
|
||||
home: [];
|
||||
refresh: [];
|
||||
toggleFavorite: [item: MenuDataItem];
|
||||
dialog: [tab: MaskTab];
|
||||
}>();
|
||||
|
||||
const getActions = (tab: MaskTab) => {
|
||||
const isFav = !!props.favorites.find(
|
||||
(n) => n === tab.menu || n.id === tab.menu.id
|
||||
(n) => n === tab.menu || n.id === tab.menu?.id
|
||||
);
|
||||
return [
|
||||
{
|
||||
@@ -109,31 +107,33 @@
|
||||
icon: isFav ? StarFilled : Star,
|
||||
label: '收藏',
|
||||
name: 'favorite',
|
||||
value: tab.menu
|
||||
value: tab.menu,
|
||||
disabled: !tab.menu
|
||||
},
|
||||
'|',
|
||||
{
|
||||
icon: CopyDocument,
|
||||
label: '弹窗',
|
||||
name: 'dialog'
|
||||
name: 'dialog',
|
||||
value: tab
|
||||
}
|
||||
] as ActionBarItems;
|
||||
};
|
||||
|
||||
const onTabClick = (pane: TabsPaneContext) => {
|
||||
const name = pane.paneName;
|
||||
if (name === props.home.menu.id) {
|
||||
emit('home');
|
||||
if (name === props.home.url) {
|
||||
emit('click', props.home);
|
||||
return;
|
||||
}
|
||||
const tab = props.tabs.find((n) => n.menu.id === name);
|
||||
const tab = props.tabs.find((n) => n.url === name);
|
||||
if (tab) {
|
||||
emit('click', tab);
|
||||
}
|
||||
};
|
||||
|
||||
const onTabRemove = (name: string | number) => {
|
||||
const tab = props.tabs.find((n) => n.menu.id === name);
|
||||
const tab = props.tabs.find((n) => n.url === name);
|
||||
if (tab) {
|
||||
emit('remove', tab);
|
||||
}
|
||||
@@ -148,6 +148,7 @@
|
||||
emit('toggleFavorite', item.value as MenuDataItem);
|
||||
break;
|
||||
case 'dialog':
|
||||
emit('dialog', item.value as MaskTab);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
const menus = props.tabs.map((n, i) => {
|
||||
return {
|
||||
divided: i === 0,
|
||||
label: n.menu.title,
|
||||
label: n.title,
|
||||
command: n
|
||||
};
|
||||
});
|
||||
|
||||
@@ -4,7 +4,8 @@ import {
|
||||
InjectionKey,
|
||||
ShallowReactive,
|
||||
ComputedRef,
|
||||
ComponentInternalInstance
|
||||
ComponentInternalInstance,
|
||||
DefineComponent
|
||||
} from 'vue';
|
||||
import { RouteLocationNormalizedLoaded } from 'vue-router';
|
||||
|
||||
@@ -61,11 +62,12 @@ export const maskProps = {
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 手动处理菜单打开,设置true,需要自行侦听 menu-select 事件实现菜单打开页面
|
||||
* 菜单项数据适配函数,用作转换菜单项数据
|
||||
*/
|
||||
manual: {
|
||||
type: Boolean
|
||||
menuAdapter: {
|
||||
type: Function as PropType<(menu: MenuDataItem) => MenuDataItem>
|
||||
},
|
||||
|
||||
/**
|
||||
* 设置初始化选中菜单函数
|
||||
*/
|
||||
@@ -78,10 +80,10 @@ export const maskProps = {
|
||||
>
|
||||
},
|
||||
/**
|
||||
* 主页路由路径
|
||||
* 主页Tab配置
|
||||
*/
|
||||
homepage: {
|
||||
type: String,
|
||||
home: {
|
||||
type: [String, Object] as PropType<string | MaskTab>,
|
||||
default: '/'
|
||||
},
|
||||
|
||||
@@ -141,7 +143,19 @@ export type MaskSlots = {
|
||||
};
|
||||
|
||||
export interface MaskTab {
|
||||
menu: MenuDataItem;
|
||||
id?: symbol;
|
||||
// 页面路由
|
||||
url: string;
|
||||
// 标题文本
|
||||
title?: string;
|
||||
// 图标
|
||||
icon?: string | Record<string, any> | DefineComponent<any, any, any, any>;
|
||||
// 能否关闭
|
||||
closable?: boolean;
|
||||
// 弹窗模式
|
||||
dialog?: boolean;
|
||||
// 关联菜单项
|
||||
menu?: MenuDataItem;
|
||||
}
|
||||
|
||||
export type MaskDefineTab = () => Partial<MaskTab> | Promise<Partial<MaskTab>>;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './useMenus';
|
||||
export * from './useTabs';
|
||||
export * from './useFavorites';
|
||||
export * from './useViewCache';
|
||||
|
||||
@@ -1,40 +1,38 @@
|
||||
import { shallowRef, computed, ref } from 'vue';
|
||||
import { Router } from 'vue-router';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { MaskProps, MaskEmits } from '../types';
|
||||
import { MenuDataItem, Emits, createDialog } from '../../';
|
||||
import { HomeFilled } from '@element-plus/icons-vue';
|
||||
import { arrayToMap } from '@vtj/utils';
|
||||
|
||||
const HOME_ID = '__vtj__home__';
|
||||
|
||||
export function useMenus(
|
||||
props: MaskProps,
|
||||
emit: Emits<MaskEmits>,
|
||||
router: Router
|
||||
function toFlat(
|
||||
array: MenuDataItem[],
|
||||
menuAdapter?: (menu: MenuDataItem) => MenuDataItem
|
||||
) {
|
||||
const homeMenu: MenuDataItem = {
|
||||
id: HOME_ID,
|
||||
icon: HomeFilled,
|
||||
url: props.homepage
|
||||
};
|
||||
let result: MenuDataItem[] = [];
|
||||
array.forEach((n) => {
|
||||
n = menuAdapter ? menuAdapter(n) : n;
|
||||
if (!n.children) {
|
||||
result.push(n);
|
||||
} else {
|
||||
result = result.concat(toFlat(n.children, menuAdapter));
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
export function useMenus(props: MaskProps, emit: Emits<MaskEmits>) {
|
||||
const router = useRouter();
|
||||
const collapsed = ref(false);
|
||||
const favorite = ref(false);
|
||||
const keyword = ref('');
|
||||
const menus = shallowRef<MenuDataItem[]>([]);
|
||||
const active = ref<MenuDataItem | null>(homeMenu);
|
||||
const toFlat = (array: MenuDataItem[]) => {
|
||||
let result: MenuDataItem[] = [];
|
||||
array.forEach((n) => {
|
||||
if (!n.children) {
|
||||
result.push(n);
|
||||
} else {
|
||||
result = result.concat(toFlat(n.children));
|
||||
}
|
||||
});
|
||||
return result;
|
||||
};
|
||||
const active = ref<MenuDataItem | null>(null);
|
||||
const flatMenus = computed(() => toFlat(menus.value, props.menuAdapter));
|
||||
const menusMap = computed(() => arrayToMap(flatMenus.value, 'id'));
|
||||
|
||||
const flatMenus = computed(() => toFlat(menus.value));
|
||||
const getMenuByUrl = (url: string) => {
|
||||
return flatMenus.value.find((n) => n.url === url);
|
||||
};
|
||||
|
||||
(async () => {
|
||||
menus.value =
|
||||
@@ -48,12 +46,14 @@ export function useMenus(
|
||||
};
|
||||
|
||||
const select = (menu: MenuDataItem) => {
|
||||
const { type = 'route', url, title, icon } = menu;
|
||||
const activeMenu = menusMap.value.get(menu.id);
|
||||
if (!activeMenu) return;
|
||||
const { type = 'route', url, title, icon } = activeMenu;
|
||||
emit('select', menu);
|
||||
if (type === 'route') {
|
||||
active.value = menu;
|
||||
active.value = activeMenu;
|
||||
}
|
||||
if (props.manual || !url) return;
|
||||
if (!url) return;
|
||||
if (type === 'route' && router) {
|
||||
if (
|
||||
url.startsWith('https:') ||
|
||||
@@ -85,9 +85,10 @@ export function useMenus(
|
||||
keyword,
|
||||
menus,
|
||||
flatMenus,
|
||||
menusMap,
|
||||
search,
|
||||
select,
|
||||
active,
|
||||
homeMenu
|
||||
getMenuByUrl
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,31 +1,35 @@
|
||||
import { shallowRef, watch, computed, ref, Ref } from 'vue';
|
||||
import { shallowRef, computed, ref } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { MaskProps, MaskEmits, TAB_ITEM_WIDTH, MaskTab } from '../types';
|
||||
import { MenuDataItem, Emits } from '../../';
|
||||
import { Emits } from '../../';
|
||||
import { useElementSize } from '@vueuse/core';
|
||||
import { ElMessageBox } from 'element-plus';
|
||||
import type { Router, RouteLocationRaw } from 'vue-router';
|
||||
import { HomeFilled } from '@element-plus/icons-vue';
|
||||
|
||||
export function useTabs(
|
||||
props: MaskProps,
|
||||
emit: Emits<MaskEmits>,
|
||||
router: Router,
|
||||
active: Ref<MenuDataItem | null | undefined>,
|
||||
select: (menu: MenuDataItem) => void,
|
||||
homeMenu: MenuDataItem
|
||||
) {
|
||||
export function useTabs(props: MaskProps, emit: Emits<MaskEmits>) {
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const homeTab = computed<MaskTab>(() => {
|
||||
return {
|
||||
menu: homeMenu,
|
||||
closable: false
|
||||
};
|
||||
return typeof props.home === 'string'
|
||||
? {
|
||||
url: props.home,
|
||||
icon: HomeFilled,
|
||||
closable: false
|
||||
}
|
||||
: ({
|
||||
...props.home,
|
||||
closable: false
|
||||
} as MaskTab);
|
||||
});
|
||||
// tabs 组件引用
|
||||
const tabRef = ref();
|
||||
const { width } = useElementSize(tabRef);
|
||||
// tabs数据项
|
||||
const tabs = shallowRef<MaskTab[]>([]);
|
||||
// 当前激活得Tab菜单id
|
||||
const tabsValue = computed(() => active.value?.id || homeMenu.id);
|
||||
|
||||
// 当前激活的Tab name
|
||||
const currentTabValue = ref<string | undefined>();
|
||||
|
||||
// baner上可以展示的tab数量
|
||||
const showCount = computed(() => Math.floor(width.value / TAB_ITEM_WIDTH));
|
||||
// banner 上的tabs项
|
||||
@@ -35,28 +39,32 @@ export function useTabs(
|
||||
|
||||
// 判断是否激活的tab
|
||||
const isActiveTab = (tab: MaskTab) => {
|
||||
return tabsValue.value === tab.menu.id;
|
||||
return route.fullPath === tab.url;
|
||||
};
|
||||
|
||||
// 判断两个tab项是否相等
|
||||
const isEqual = (a: MaskTab, b: MaskTab) => {
|
||||
return a === b || a.menu.id === b.menu.id;
|
||||
return a === b || a.url === b.url;
|
||||
};
|
||||
|
||||
const isExistTab = (url: string) => {
|
||||
return !!tabs.value.find((n) => n.url === url);
|
||||
};
|
||||
|
||||
const isHomeTab = (url: string) => {
|
||||
return homeTab.value.url === url;
|
||||
};
|
||||
|
||||
// 切换tab
|
||||
const activeTab = (tab: MaskTab) => {
|
||||
if (!isActiveTab(tab)) {
|
||||
select(tab.menu);
|
||||
}
|
||||
currentTabValue.value = tab.url;
|
||||
router.push(currentTabValue.value).catch((e) => e);
|
||||
};
|
||||
|
||||
// 切换到首页
|
||||
const activeHome = () => {
|
||||
active.value = homeTab.value.menu;
|
||||
const url = homeTab.value.menu.url;
|
||||
if (url) {
|
||||
router.push(url).catch((e) => e);
|
||||
}
|
||||
currentTabValue.value = homeTab.value.url;
|
||||
router.push(currentTabValue.value).catch((e) => e);
|
||||
};
|
||||
|
||||
// 新增tab
|
||||
@@ -70,6 +78,19 @@ export function useTabs(
|
||||
}
|
||||
};
|
||||
|
||||
const updateTab = (tab: MaskTab) => {
|
||||
const index = tabs.value.findIndex((n) => isEqual(n, tab));
|
||||
|
||||
if (index >= 0) {
|
||||
const match = tabs.value[index];
|
||||
tabs.value = [...tabs.value].splice(
|
||||
index,
|
||||
1,
|
||||
Object.assign(match, tab, { id: Symbol() })
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// 下拉菜单的tab 移动到 banner 并激活
|
||||
const moveToShow = (tab: MaskTab) => {
|
||||
const items = tabs.value.filter((n) => !isEqual(n, tab));
|
||||
@@ -85,7 +106,8 @@ export function useTabs(
|
||||
if (!ret) return;
|
||||
tabs.value = tabs.value.filter((n) => !isEqual(n, tab));
|
||||
// 删除的是激活tab
|
||||
if (active.value?.id === tab.menu.id) {
|
||||
|
||||
if (currentTabValue.value === tab.url) {
|
||||
const first = tabs.value[0];
|
||||
first ? activeTab(first) : activeHome();
|
||||
}
|
||||
@@ -107,25 +129,16 @@ export function useTabs(
|
||||
type: 'warning'
|
||||
}).catch((e) => false);
|
||||
if (!ret) return;
|
||||
tabs.value = tabs.value.filter((n) => n.menu.id === active.value?.id);
|
||||
tabs.value = tabs.value.filter((n) => n.url === currentTabValue.value);
|
||||
};
|
||||
|
||||
// const openTab = (to: RouteLocationRaw, menu: MenuDataItem) => {
|
||||
// const tab: MaskTab = {
|
||||
// menu: { ...menu, id: Date.now() },
|
||||
// closable: true
|
||||
// };
|
||||
// router.push(to);
|
||||
// // addTab(tab);
|
||||
// };
|
||||
|
||||
return {
|
||||
tabRef,
|
||||
homeTab,
|
||||
tabs,
|
||||
showTabs,
|
||||
dropdownTabs,
|
||||
tabsValue,
|
||||
currentTabValue,
|
||||
isActiveTab,
|
||||
addTab,
|
||||
removeTab,
|
||||
@@ -133,7 +146,10 @@ export function useTabs(
|
||||
activeHome,
|
||||
activeTab,
|
||||
removeAllTabs,
|
||||
removeOtherTabs
|
||||
removeOtherTabs,
|
||||
updateTab,
|
||||
isExistTab,
|
||||
isHomeTab
|
||||
// openTab
|
||||
};
|
||||
}
|
||||
|
||||
33
packages/ui/src/components/mask/use/useViewCache.ts
Normal file
33
packages/ui/src/components/mask/use/useViewCache.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { ref, nextTick, ComponentInternalInstance, onMounted } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
export function useViewCache() {
|
||||
const aliveRef = ref();
|
||||
const aliveKey = ref<any>(Symbol());
|
||||
const viewInstance = ref<ComponentInternalInstance | null>(null);
|
||||
const route = useRoute();
|
||||
|
||||
const refresh = async () => {
|
||||
aliveKey.value = undefined;
|
||||
const caches = aliveRef.value._.__v_cache as Map<string, any>;
|
||||
caches.delete(route.fullPath);
|
||||
await nextTick();
|
||||
aliveKey.value = Symbol();
|
||||
};
|
||||
|
||||
const getCacheComponent = (key: string) => {
|
||||
const caches = aliveRef.value._.__v_cache as Map<string, any>;
|
||||
const match = caches.get(key);
|
||||
if (match) {
|
||||
return match.component as ComponentInternalInstance;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
return {
|
||||
aliveKey,
|
||||
aliveRef,
|
||||
refresh,
|
||||
getCacheComponent,
|
||||
viewInstance
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user