提交 cedba37e 编写于 作者: V vben

feat: add tab drag and drop sort

上级 5cabbac7
......@@ -2,13 +2,14 @@
### ✨ Refactor
- 重构整体 layout。更改代码实现方式。代码更精简
- 重构整体 layout。更改代码实现方式。代码更精简,并加入多语言支持
- 配置项重构
- 移除 messageSetting 配置
### ✨ Features
- 缓存可以配置是否加密,默认生产环境开启 Aes 加密
- 新增标签页拖拽排序
### 🎫 Chores
......
......@@ -33,7 +33,7 @@ export function useMenuSetting() {
const getMenuBgColor = computed(() => unref(getMenuSetting).bgColor);
const getHasDrag = computed(() => unref(getMenuSetting).hasDrag);
const getCanDrag = computed(() => unref(getMenuSetting).canDrag);
const getAccordion = computed(() => unref(getMenuSetting).accordion);
......@@ -117,7 +117,7 @@ export function useMenuSetting() {
getTrigger,
getSplit,
getMenuTheme,
getHasDrag,
getCanDrag,
getIsHorizontal,
getShowSearch,
getCollapsedShowTitle,
......
import { useTimeoutFn } from '/@/hooks/core/useTimeout';
import { PageEnum } from '/@/enums/pageEnum';
import { TabItem, tabStore } from '/@/store/modules/tab';
import { appStore } from '/@/store/modules/app';
import router from '/@/router';
import { ref } from 'vue';
import { pathToRegexp } from 'path-to-regexp';
const activeKeyRef = ref<string>('');
type Fn = () => void;
type RouteFn = (tabItem: TabItem) => void;
interface TabFn {
......@@ -28,6 +20,7 @@ let closeOther: RouteFn;
let closeCurrent: RouteFn;
export let isInitUseTab = false;
export function useTabs() {
function initTabFn({
refreshPageFn,
......@@ -38,6 +31,7 @@ export function useTabs() {
closeCurrentFn,
}: TabFn) {
if (isInitUseTab) return;
refreshPageFn && (refreshPage = refreshPageFn);
closeAllFn && (closeAll = closeAllFn);
closeLeftFn && (closeLeft = closeLeftFn);
......@@ -58,29 +52,13 @@ export function useTabs() {
}
function canIUseFn(): boolean {
const { getProjectConfig } = appStore;
const { multiTabsSetting: { show } = {} } = getProjectConfig;
const { multiTabsSetting: { show } = {} } = appStore.getProjectConfig;
if (!show) {
throw new Error('当前未开启多标签页,请在设置中打开!');
}
return !!show;
}
function getTo(path: string): any {
const routes = router.getRoutes();
const fn = (p: string): any => {
const to = routes.find((item) => {
if (item.path === '/:path(.*)*') return;
const regexp = pathToRegexp(item.path);
return regexp.test(p);
});
if (!to) return '';
if (!to.redirect) return to;
if (to.redirect) {
return getTo(to.redirect as string);
}
};
return fn(path);
}
return {
initTabFn,
refreshPage: () => canIUseFn() && refreshPage(tabStore.getCurrentTab),
......@@ -90,26 +68,5 @@ export function useTabs() {
closeOther: () => canIUseFn() && closeOther(tabStore.getCurrentTab),
closeCurrent: () => canIUseFn() && closeCurrent(tabStore.getCurrentTab),
resetCache: () => canIUseFn() && resetCache(),
addTab: (
path: PageEnum | string,
goTo = false,
opt?: { replace?: boolean; query?: any; params?: any }
) => {
const to = getTo(path);
if (!to) return;
useTimeoutFn(() => {
tabStore.addTabByPathAction();
}, 0);
const { replace, query = {}, params = {} } = opt || {};
activeKeyRef.value = path;
const data = {
path,
query,
params,
};
goTo && replace ? router.replace(data) : router.push(data);
},
activeKeyRef,
};
}
......@@ -4,6 +4,7 @@
display: flex;
height: @header-height;
padding: 0 20px 0 0;
margin-left: -1px;
line-height: @header-height;
color: @white;
background: @white;
......
......@@ -9,4 +9,8 @@
> .ant-layout {
min-height: 100%;
}
&__main {
margin-left: 2px;
}
}
......@@ -81,7 +81,7 @@ export default defineComponent({
{() => (
<>
{unref(showSideBarRef) && <LayoutSideBar />}
<Layout>
<Layout class="default-layout__main">
{() => (
<>
<LayoutMultipleHeader />
......
import { defineComponent, unref, computed } from 'vue';
import type { PropType } from 'vue';
import { defineComponent, unref, computed, FunctionalComponent } from 'vue';
import { TabItem, tabStore } from '/@/store/modules/tab';
import { getScaleAction, TabContentProps } from './tab.data';
import { getScaleAction, TabContentProps } from './data';
import { Dropdown } from '/@/components/Dropdown/index';
import { RightOutlined } from '@ant-design/icons-vue';
import { appStore } from '/@/store/modules/app';
import { TabContentEnum } from './tab.data';
import { TabContentEnum } from './data';
import { useTabDropdown } from './useTabDropdown';
import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting';
import { useMultipleTabSetting } from '/@/hooks/setting/useMultipleTabSetting';
const ExtraContent: FunctionalComponent = () => {
return (
<span class={`multiple-tabs-content__extra `}>
<RightOutlined />
</span>
);
};
const TabContent: FunctionalComponent<{ tabItem: TabItem }> = (props) => {
const { tabItem: { meta } = {} } = props;
function handleContextMenu(e: Event) {
if (!props.tabItem) return;
const tableItem = props.tabItem;
e?.preventDefault();
const index = unref(tabStore.getTabsState).findIndex((tab) => tab.path === tableItem.path);
tabStore.commitCurrentContextMenuIndexState(index);
tabStore.commitCurrentContextMenuState(props.tabItem);
}
return (
<div class={`multiple-tabs-content__content `} onContextmenu={handleContextMenu}>
<span class="ml-1">{meta && meta.title}</span>
</div>
);
};
export default defineComponent({
name: 'TabContent',
......@@ -19,82 +49,39 @@ export default defineComponent({
type: Object as PropType<TabItem>,
default: null,
},
type: {
type: Number as PropType<number>,
type: Number as PropType<TabContentEnum>,
default: TabContentEnum.TAB_TYPE,
},
trigger: {
type: Array as PropType<string[]>,
default: () => {
return ['contextmenu'];
},
},
},
setup(props) {
const getProjectConfigRef = computed(() => {
return appStore.getProjectConfig;
});
const { getShowMenu } = useMenuSetting();
const { getShowHeader } = useHeaderSetting();
const { getShowQuick } = useMultipleTabSetting();
const getIsScaleRef = computed(() => {
const {
menuSetting: { show: showMenu },
headerSetting: { show: showHeader },
} = unref(getProjectConfigRef);
return !showMenu && !showHeader;
const getIsScale = computed(() => {
return !unref(getShowMenu) && !unref(getShowHeader);
});
function handleContextMenu(e: Event) {
if (!props.tabItem) return;
const tableItem = props.tabItem;
e.preventDefault();
const index = unref(tabStore.getTabsState).findIndex((tab) => tab.path === tableItem.path);
tabStore.commitCurrentContextMenuIndexState(index);
tabStore.commitCurrentContextMenuState(props.tabItem);
}
/**
* @description: 渲染图标
*/
function renderTabContent() {
const { tabItem: { meta } = {} } = props;
return (
<div class={`multiple-tabs-content__content `} onContextmenu={handleContextMenu}>
<span class="ml-1">{meta && meta.title}</span>
</div>
);
}
function renderExtraContent() {
return (
<span class={`multiple-tabs-content__extra `}>
<RightOutlined />
</span>
);
}
const getIsTab = computed(() => {
return !unref(getShowQuick) ? true : props.type === TabContentEnum.TAB_TYPE;
});
const { getDropMenuList, handleMenuEvent } = useTabDropdown(props as TabContentProps);
return () => {
const { trigger, type } = props;
const {
multiTabsSetting: { showQuick },
} = unref(getProjectConfigRef);
const isTab = !showQuick ? true : type === TabContentEnum.TAB_TYPE;
const scaleAction = getScaleAction(
unref(getIsScaleRef) ? '缩小' : '放大',
unref(getIsScaleRef)
);
const scaleAction = getScaleAction(unref(getIsScale) ? '收起' : '展开', unref(getIsScale));
const dropMenuList = unref(getDropMenuList) || [];
const isTab = unref(getIsTab);
return (
<Dropdown
dropMenuList={!isTab ? [scaleAction, ...dropMenuList] : dropMenuList}
trigger={isTab ? trigger : ['hover']}
trigger={isTab ? ['contextmenu'] : ['click']}
onMenuEvent={handleMenuEvent}
>
{() => (isTab ? renderTabContent() : renderExtraContent())}
{() => (isTab ? <TabContent tabItem={props.tabItem} /> : <ExtraContent />)}
</Dropdown>
);
};
......
......@@ -6,11 +6,13 @@ export enum TabContentEnum {
TAB_TYPE,
EXTRA_TYPE,
}
export interface TabContentProps {
tabItem: TabItem | AppRouteRecordRaw;
type?: TabContentEnum;
trigger?: Array<'click' | 'hover' | 'contextmenu'>;
}
/**
* @description: 右键:下拉菜单文字
*/
......
......@@ -2,11 +2,12 @@
.multiple-tabs {
z-index: 10;
height: @multiple-height+2;
height: @multiple-height + 2;
padding: 0 0 2px 0;
line-height: @multiple-height+2;
margin-left: -1px;
line-height: @multiple-height + 2;
background: @white;
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.08);
box-shadow: 0 1px 2px 0 rgba(29, 35, 41, 0.05);
.ant-tabs-small {
height: @multiple-height;
......@@ -32,19 +33,25 @@
color: @text-color-call-out;
background: @white;
border: 1px solid darken(@border-color-light, 8%);
border-radius: none !important;
transition: none;
&:hover {
.ant-tabs-close-x {
opacity: 1;
}
}
.ant-tabs-close-x {
width: 12px;
width: 8px;
height: 12px;
font-size: 12px;
color: inherit;
opacity: 0;
transition: none;
&:hover {
svg {
width: 0.8em;
width: 0.75em;
}
}
}
......@@ -61,12 +68,26 @@
}
.ant-tabs-tab-active {
position: relative;
padding-left: 26px;
color: @white;
background: fade(@primary-color, 100%);
border: 0;
&::before {
display: none;
position: absolute;
top: calc(50% - 3px);
left: 8px;
width: 6px;
height: 6px;
background: #fff;
border-radius: 50%;
content: '';
transition: none;
}
.ant-tabs-close-x {
opacity: 1;
}
svg {
......@@ -78,6 +99,10 @@
.ant-tabs-nav > div:nth-child(1) {
padding: 0 10px;
.ant-tabs-tab {
margin-right: 3px !important;
}
}
}
......@@ -111,7 +136,10 @@
text-align: center;
cursor: pointer;
border-left: 1px solid #eee;
// box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
&:hover {
color: @text-color-base;
}
span[role='img'] {
transform: rotate(90deg);
......
import './index.less';
import type { TabContentProps } from './tab.data';
import type { TabContentProps } from './data';
import type { TabItem } from '/@/store/modules/tab';
import type { AppRouteRecordRaw } from '/@/router/types';
import { defineComponent, watch, computed, unref } from 'vue';
import { defineComponent, watch, computed, unref, ref, onMounted, nextTick } from 'vue';
import Sortable from 'sortablejs';
import { useRouter } from 'vue-router';
import { Tabs } from 'ant-design-vue';
......@@ -12,24 +14,28 @@ import TabContent from './TabContent';
import { useGo } from '/@/hooks/web/usePage';
import { TabContentEnum } from './tab.data';
import { TabContentEnum } from './data';
import { tabStore } from '/@/store/modules/tab';
import { userStore } from '/@/store/modules/user';
import { closeTab } from './useTabDropdown';
import { useTabs } from '/@/hooks/web/useTabs';
import { initAffixTabs } from './useAffixTabs';
import { initAffixTabs } from './useMultipleTabs';
import { isNullAndUnDef } from '/@/utils/is';
import { useProjectSetting } from '/@/hooks/setting';
export default defineComponent({
name: 'MultipleTabs',
setup() {
initAffixTabs();
const activeKeyRef = ref('');
const affixTextList = initAffixTabs();
const go = useGo();
const { multiTabsSetting } = useProjectSetting();
const { currentRoute } = useRouter();
const { activeKeyRef } = useTabs();
const getTabsState = computed(() => tabStore.getTabsState);
......@@ -41,24 +47,24 @@ export default defineComponent({
if (!lastChangeRoute || !userStore.getTokenState) return;
const { path, fullPath } = lastChangeRoute;
if (activeKeyRef.value !== (fullPath || path)) {
activeKeyRef.value = fullPath || path;
const { path, fullPath } = lastChangeRoute as AppRouteRecordRaw;
const p = fullPath || path;
if (activeKeyRef.value !== p) {
activeKeyRef.value = p;
}
tabStore.commitAddTab((lastChangeRoute as unknown) as AppRouteRecordRaw);
tabStore.commitAddTab(lastChangeRoute);
},
{
immediate: true,
}
);
// tab切换
function handleChange(activeKey: any) {
activeKeyRef.value = activeKey;
go(activeKey, false);
}
// 关闭当前tab
// Close the current tab
function handleEdit(targetKey: string) {
// Added operation to hide, currently only use delete operation
const index = unref(getTabsState).findIndex(
......@@ -71,30 +77,65 @@ export default defineComponent({
const tabContentProps: TabContentProps = {
tabItem: (currentRoute as unknown) as AppRouteRecordRaw,
type: TabContentEnum.EXTRA_TYPE,
trigger: ['click', 'contextmenu'],
};
return (
<span>
<TabContent {...(tabContentProps as any)} />
</span>
);
return <TabContent {...(tabContentProps as any)} />;
}
function renderTabs() {
return unref(getTabsState).map((item: TabItem) => {
const key = item.query ? item.fullPath : item.path;
const closable = !(item && item.meta && item.meta.affix);
const slots = {
tab: () => <TabContent tabItem={item} />,
};
return (
<Tabs.TabPane key={key} closable={closable}>
{{
tab: () => <TabContent tabItem={item} />,
}}
{slots}
</Tabs.TabPane>
);
});
}
function initSortableTabs() {
if (!multiTabsSetting.canDrag) return;
nextTick(() => {
const el = document.querySelectorAll(
'.multiple-tabs .ant-tabs-nav > div'
)?.[0] as HTMLElement;
if (!el) return;
Sortable.create(el, {
animation: 500,
delay: 400,
delayOnTouchOnly: true,
filter: (e: ChangeEvent) => {
const text = e?.target?.innerText;
if (!text) return false;
return affixTextList.includes(text);
},
onEnd: (evt) => {
const { oldIndex, newIndex } = evt;
if (isNullAndUnDef(oldIndex) || isNullAndUnDef(newIndex) || oldIndex === newIndex) {
return;
}
tabStore.commitSortTabs({ oldIndex, newIndex });
},
});
});
}
onMounted(() => {
initSortableTabs();
});
return () => {
const slots = {
default: () => renderTabs(),
tabBarExtraContent: () => renderQuick(),
};
return (
<div class="multiple-tabs">
<Tabs
......@@ -102,15 +143,12 @@ export default defineComponent({
size="small"
animated={false}
hideAdd={true}
tabBarGutter={4}
tabBarGutter={3}
activeKey={unref(activeKeyRef)}
onChange={handleChange}
onEdit={handleEdit}
>
{{
default: () => renderTabs(),
tabBarExtraContent: () => renderQuick(),
}}
{slots}
</Tabs>
</div>
);
......
import { toRaw } from 'vue';
import { toRaw, ref } from 'vue';
import router from '/@/router';
import { AppRouteRecordRaw } from '/@/router/types';
import { TabItem, tabStore } from '/@/store/modules/tab';
export function initAffixTabs() {
const affixList = ref<TabItem[]>([]);
/**
* @description: Filter all fixed routes
*/
......@@ -23,13 +24,16 @@ export function initAffixTabs() {
*/
function addAffixTabs(): void {
const affixTabs = filterAffixTabs((router.getRoutes() as unknown) as AppRouteRecordRaw[]);
affixList.value = affixTabs;
for (const tab of affixTabs) {
tabStore.commitAddTab(tab);
}
}
let isAddAffix = false;
if (!isAddAffix) {
addAffixTabs();
isAddAffix = true;
}
return affixList.value.map((item) => item.meta?.title).filter(Boolean);
}
import type { AppRouteRecordRaw } from '/@/router/types';
import type { TabContentProps } from './tab.data';
import type { TabContentProps } from './data';
import type { Ref } from 'vue';
import type { TabItem } from '/@/store/modules/tab';
import type { DropMenu } from '/@/components/Dropdown';
import { computed, unref } from 'vue';
import { TabContentEnum, MenuEventEnum, getActions } from './tab.data';
import { TabContentEnum, MenuEventEnum, getActions } from './data';
import { tabStore } from '/@/store/modules/tab';
import { appStore } from '/@/store/modules/app';
import { PageEnum } from '/@/enums/pageEnum';
......@@ -15,9 +15,7 @@ import { useTabs, isInitUseTab } from '/@/hooks/web/useTabs';
import { RouteLocationRaw } from 'vue-router';
const { initTabFn } = useTabs();
/**
* @description: 右键下拉
*/
export function useTabDropdown(tabContentProps: TabContentProps) {
const { currentRoute } = router;
const redo = useRedo();
......@@ -30,26 +28,24 @@ export function useTabDropdown(tabContentProps: TabContentProps) {
: ((unref(currentRoute) as any) as AppRouteRecordRaw);
});
// 当前tab列表
const getTabsState = computed(() => {
return tabStore.getTabsState;
});
// Current tab list
const getTabsState = computed(() => tabStore.getTabsState);
/**
* @description: 下拉列表
* @description: drop-down list
*/
const getDropMenuList = computed(() => {
const dropMenuList = getActions();
// 重置为初始状态
// Reset to initial state
for (const item of dropMenuList) {
item.disabled = false;
}
// 没有tab
// No tab
if (!unref(getTabsState) || unref(getTabsState).length <= 0) {
return dropMenuList;
} else if (unref(getTabsState).length === 1) {
// 只有一个tab
// Only one tab
for (const item of dropMenuList) {
if (item.event !== MenuEventEnum.REFRESH_PAGE) {
item.disabled = true;
......@@ -57,22 +53,20 @@ export function useTabDropdown(tabContentProps: TabContentProps) {
}
return dropMenuList;
}
if (!unref(getCurrentTab)) {
return;
}
if (!unref(getCurrentTab)) return;
const { meta, path } = unref(getCurrentTab);
// console.log(unref(getCurrentTab));
// 刷新按钮
// Refresh button
const curItem = tabStore.getCurrentContextMenuState;
const index = tabStore.getCurrentContextMenuIndexState;
const refreshDisabled = curItem ? curItem.path !== path : true;
// 关闭左侧
// Close left
const closeLeftDisabled = index === 0;
// 关闭右侧
// Close right
const closeRightDisabled = index === unref(getTabsState).length - 1;
// 当前为固定tab
// Currently fixed tab
// TODO PERf
dropMenuList[0].disabled = unref(isTabsRef) ? refreshDisabled : false;
if (meta && meta.affix) {
dropMenuList[1].disabled = true;
......@@ -84,7 +78,7 @@ export function useTabDropdown(tabContentProps: TabContentProps) {
});
/**
* @description: 关闭所有页面时,跳转页面
* @description: Jump to page when closing all pages
*/
function gotoPage() {
const len = unref(getTabsState).length;
......@@ -99,14 +93,14 @@ export function useTabDropdown(tabContentProps: TabContentProps) {
toPath = p;
}
}
// 跳到当前页面报错
// Jump to the current page and report an error
path !== toPath && go(toPath as PageEnum, true);
}
function isGotoPage(currentTab?: TabItem) {
const { path } = unref(currentRoute);
const currentPath = (currentTab || unref(getCurrentTab)).path;
// 不是当前tab,关闭左侧/右侧时,需跳转页面
// Not the current tab, when you close the left/right side, you need to jump to the page
if (path !== currentPath) {
go(currentPath as PageEnum, true);
}
......@@ -117,25 +111,31 @@ export function useTabDropdown(tabContentProps: TabContentProps) {
} catch (error) {}
redo();
}
function closeAll() {
tabStore.commitCloseAllTab();
gotoPage();
}
function closeLeft(tabItem?: TabItem) {
tabStore.closeLeftTabAction(tabItem || unref(getCurrentTab));
isGotoPage(tabItem);
}
function closeRight(tabItem?: TabItem) {
tabStore.closeRightTabAction(tabItem || unref(getCurrentTab));
isGotoPage(tabItem);
}
function closeOther(tabItem?: TabItem) {
tabStore.closeOtherTabAction(tabItem || unref(getCurrentTab));
isGotoPage(tabItem);
}
function closeCurrent(tabItem?: TabItem) {
closeTab(unref(tabItem || unref(getCurrentTab)));
}
function scaleScreen() {
const {
headerSetting: { show: showHeader },
......@@ -159,7 +159,7 @@ export function useTabDropdown(tabContentProps: TabContentProps) {
});
}
// 处理右键事件
// Handle right click event
function handleMenuEvent(menu: DropMenu): void {
const { event } = menu;
......@@ -168,76 +168,74 @@ export function useTabDropdown(tabContentProps: TabContentProps) {
scaleScreen();
break;
case MenuEventEnum.REFRESH_PAGE:
// 刷新页面
// refresh page
refreshPage();
break;
// 关闭当前
// Close current
case MenuEventEnum.CLOSE_CURRENT:
closeCurrent();
break;
// 关闭左侧
// Close left
case MenuEventEnum.CLOSE_LEFT:
closeLeft();
break;
// 关闭右侧
// Close right
case MenuEventEnum.CLOSE_RIGHT:
closeRight();
break;
// 关闭其他
// Close other
case MenuEventEnum.CLOSE_OTHER:
closeOther();
break;
// 关闭其他
// Close all
case MenuEventEnum.CLOSE_ALL:
closeAll();
break;
default:
break;
}
}
return { getDropMenuList, handleMenuEvent };
}
export function getObj(tabItem: TabItem) {
const { params, path, query } = tabItem;
return {
params: params || {},
path,
query: query || {},
};
}
export function closeTab(closedTab: TabItem | AppRouteRecordRaw) {
const { currentRoute, replace } = router;
// 当前tab列表
const getTabsState = computed(() => {
return tabStore.getTabsState;
});
// Current tab list
const getTabsState = computed(() => tabStore.getTabsState);
const { path } = unref(currentRoute);
if (path !== closedTab.path) {
// 关闭的不是激活tab
// Closed is not the activation tab
tabStore.commitCloseTab(closedTab);
return;
}
// 关闭的为激活atb
// Closed is activated atb
let toObj: RouteLocationRaw = {};
const index = unref(getTabsState).findIndex((item) => item.path === path);
// 如果当前为最左边tab
// If the current is the leftmost tab
if (index === 0) {
// 只有一个tab,则跳转至首页,否则跳转至右tab
// There is only one tab, then jump to the homepage, otherwise jump to the right tab
if (unref(getTabsState).length === 1) {
toObj = PageEnum.BASE_HOME;
} else {
// 跳转至右边tab
// Jump to the right tab
const page = unref(getTabsState)[index + 1];
const { params, path, query } = page;
toObj = {
params,
path,
query,
};
toObj = getObj(page);
}
} else {
// 跳转至左边tab
// Close the current tab
const page = unref(getTabsState)[index - 1];
const { params, path, query } = page;
toObj = {
params: params || {},
path,
query: query || {},
};
toObj = getObj(page);
}
const route = (unref(currentRoute) as unknown) as AppRouteRecordRaw;
tabStore.commitCloseTab(route);
......
......@@ -203,7 +203,7 @@ export default defineComponent({
getMenuFixed,
getCollapsed,
getShowSearch,
getHasDrag,
getCanDrag,
getTopMenuAlign,
getAccordion,
getMenuWidth,
......@@ -267,7 +267,7 @@ export default defineComponent({
handler: (e) => {
baseHandler(HandlerEnum.MENU_HAS_DRAG, e);
},
def: unref(getHasDrag),
def: unref(getCanDrag),
disabled: !unref(getShowMenuRef),
}),
renderSwitchItem('侧边菜单搜索', {
......
......@@ -30,7 +30,7 @@ export function handler(event: HandlerEnum, value: any): DeepPartial<ProjectConf
};
case HandlerEnum.MENU_HAS_DRAG:
return { menuSetting: { hasDrag: value } };
return { menuSetting: { canDrag: value } };
case HandlerEnum.MENU_ACCORDION:
return { menuSetting: { accordion: value } };
......
@import (reference) '../../../design/index.less';
.layout-sidebar {
overflow: hidden;
// overflow: hidden;
&.fixed {
position: fixed;
......@@ -15,7 +15,7 @@
}
&:not(.ant-layout-sider-dark) {
border-right: 1px solid @border-color-light;
// border-right: 1px solid @border-color-light;
box-shadow: 2px 0 8px 0 rgba(29, 35, 41, 0.05);
}
......
......@@ -82,7 +82,7 @@ export function useTrigger() {
* @param dragBarRef
*/
export function useDragLine(siderRef: Ref<any>, dragBarRef: Ref<any>) {
const { getMiniWidthNumber, getCollapsed, setMenuSetting, getHasDrag } = useMenuSetting();
const { getMiniWidthNumber, getCollapsed, setMenuSetting, getCanDrag } = useMenuSetting();
const getDragBarStyle = computed(() => {
if (unref(getCollapsed)) {
......@@ -101,7 +101,7 @@ export function useDragLine(siderRef: Ref<any>, dragBarRef: Ref<any>) {
function renderDragLine() {
return (
<div
class={[`layout-sidebar__darg-bar`, !unref(getHasDrag) ? 'hide' : '']}
class={[`layout-sidebar__darg-bar`, { hide: !unref(getCanDrag) }]}
style={unref(getDragBarStyle)}
ref={dragBarRef}
/>
......
......@@ -83,7 +83,7 @@ const setting: ProjectConfig = {
collapsedShowTitle: false,
// Whether it can be dragged
// Only limited to the opening of the left menu, the mouse has a drag bar on the right side of the menu
hasDrag: false,
canDrag: false,
// Whether to show no dom
show: true,
// Whether to show dom
......@@ -114,6 +114,8 @@ const setting: ProjectConfig = {
multiTabsSetting: {
// Turn on
show: true,
// Is it possible to drag and drop sorting tabs
canDrag: true,
// Turn on quick actions
showQuick: true,
// Maximum number of tab cache
......
......@@ -175,6 +175,14 @@ class Tab extends VuexModule {
this.keepAliveTabsState = [];
}
@Mutation
commitSortTabs({ oldIndex, newIndex }: { oldIndex: number; newIndex: number }): void {
const currentTab = this.tabsState[oldIndex];
this.tabsState.splice(oldIndex, 1);
this.tabsState.splice(newIndex, 0, currentTab);
}
@Mutation
closeMultipleTab({ pathList, nameList }: { pathList: string[]; nameList: string[] }): void {
this.tabsState = toRaw(this.tabsState).filter((item) => !pathList.includes(item.fullPath));
......
......@@ -8,7 +8,7 @@ export interface MenuSetting {
fixed: boolean;
collapsed: boolean;
collapsedShowTitle: boolean;
hasDrag: boolean;
canDrag: boolean;
showSearch: boolean;
show: boolean;
hidden: boolean;
......@@ -28,7 +28,7 @@ export interface MultiTabsSetting {
show: boolean;
// 开启快速操作
showQuick: boolean;
canDrag: boolean;
// 缓存最大数量
max: number;
}
......
......@@ -24,6 +24,10 @@ export function isNull(val: unknown): val is null {
return val === null;
}
export function isNullAndUnDef(val: unknown): val is null | undefined {
return isUnDef(val) && isNull(val);
}
export function isNumber(val: unknown): val is number {
return is(val, 'Number');
}
......
......@@ -11,28 +11,18 @@
<a-button class="mr-2" @click="closeOther">关闭其他</a-button>
<a-button class="mr-2" @click="closeCurrent">关闭当前</a-button>
<a-button class="mr-2" @click="refreshPage">刷新当前</a-button>
<a-button class="mr-2" @click="openTab">打开图标界面tab</a-button>
</CollapseContainer>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { CollapseContainer } from '/@/components/Container/index';
import { PageEnum } from '/@/enums/pageEnum';
import { useTabs } from '/@/hooks/web/useTabs';
export default defineComponent({
name: 'TabsDemo',
components: { CollapseContainer },
setup() {
const {
closeAll,
closeLeft,
closeRight,
closeOther,
closeCurrent,
refreshPage,
addTab,
} = useTabs();
const { closeAll, closeLeft, closeRight, closeOther, closeCurrent, refreshPage } = useTabs();
return {
closeAll,
......@@ -41,9 +31,6 @@
closeOther,
closeCurrent,
refreshPage,
openTab: () => {
addTab('/feat/icon' as PageEnum, true);
},
};
},
});
......
......@@ -1495,6 +1495,11 @@
"@types/mime" "*"
"@types/node" "*"
"@types/sortablejs@^1.10.6":
version "1.10.6"
resolved "https://registry.npmjs.org/@types/sortablejs/-/sortablejs-1.10.6.tgz#98725ae08f1dfe28b8da0fdf302c417f5ff043c0"
integrity sha512-QRz8Z+uw2Y4Gwrtxw8hD782zzuxxugdcq8X/FkPsXUa1kfslhGzy13+4HugO9FXNo+jlWVcE6DYmmegniIQ30A==
"@types/unist@*", "@types/unist@^2.0.0", "@types/unist@^2.0.2":
version "2.0.3"
resolved "https://registry.npmjs.org/@types/unist/-/unist-2.0.3.tgz#9c088679876f374eb5983f150d4787aa6fb32d7e"
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册