35 changed files with 593 additions and 401 deletions
@ -1,7 +1,7 @@ |
|||
import { withInstall } from '../util'; |
|||
|
|||
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent'; |
|||
export const Dropdown = createAsyncComponent(() => import('./src/Dropdown')); |
|||
import Dropdown from './src/Dropdown'; |
|||
|
|||
withInstall(Dropdown); |
|||
export * from './src/types'; |
|||
export { Dropdown }; |
|||
|
|||
@ -1,42 +0,0 @@ |
|||
import type { FunctionalComponent } from 'vue'; |
|||
|
|||
import { defineComponent, unref } from 'vue'; |
|||
|
|||
import { |
|||
DoubleRightOutlined, |
|||
DoubleLeftOutlined, |
|||
MenuUnfoldOutlined, |
|||
MenuFoldOutlined, |
|||
} from '@ant-design/icons-vue'; |
|||
|
|||
import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'; |
|||
import { propTypes } from '/@/utils/propTypes'; |
|||
|
|||
const SiderTrigger: FunctionalComponent = () => { |
|||
const { getCollapsed } = useMenuSetting(); |
|||
return unref(getCollapsed) ? <DoubleRightOutlined /> : <DoubleLeftOutlined />; |
|||
}; |
|||
|
|||
const HeaderTrigger: FunctionalComponent<{ |
|||
theme?: string; |
|||
}> = (props) => { |
|||
const { toggleCollapsed, getCollapsed } = useMenuSetting(); |
|||
return ( |
|||
<span class={['layout-trigger', props.theme]} onClick={toggleCollapsed}> |
|||
{unref(getCollapsed) ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />} |
|||
</span> |
|||
); |
|||
}; |
|||
|
|||
export default defineComponent({ |
|||
name: 'LayoutTrigger', |
|||
props: { |
|||
sider: propTypes.bool.def(true), |
|||
theme: propTypes.oneOf(['light', 'dark']), |
|||
}, |
|||
setup(props) { |
|||
return () => { |
|||
return props.sider ? <SiderTrigger /> : <HeaderTrigger theme={props.theme} />; |
|||
}; |
|||
}, |
|||
}); |
|||
@ -0,0 +1,29 @@ |
|||
<template> |
|||
<LayoutLockPage /> |
|||
<BackTop v-if="getUseOpenBackTop" :target="getTarget" /> |
|||
<SettingDrawer v-if="getShowSettingButton" /> |
|||
</template> |
|||
<script lang="ts"> |
|||
import { defineComponent } from 'vue'; |
|||
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent'; |
|||
import { BackTop } from 'ant-design-vue'; |
|||
import { useRootSetting } from '/@/hooks/setting/useRootSetting'; |
|||
|
|||
export default defineComponent({ |
|||
name: 'LayoutFeatures', |
|||
components: { |
|||
BackTop, |
|||
LayoutLockPage: createAsyncComponent(() => import('/@/views/sys/lock/index.vue')), |
|||
SettingDrawer: createAsyncComponent(() => import('/@/layouts/default/setting/index.vue')), |
|||
}, |
|||
setup() { |
|||
const { getUseOpenBackTop, getShowSettingButton } = useRootSetting(); |
|||
|
|||
return { |
|||
getTarget: () => document.body, |
|||
getUseOpenBackTop, |
|||
getShowSettingButton, |
|||
}; |
|||
}, |
|||
}); |
|||
</script> |
|||
@ -1,28 +0,0 @@ |
|||
@normal-color: rgba(0, 0, 0, 0.45); |
|||
|
|||
@hover-color: rgba(0, 0, 0, 0.85); |
|||
|
|||
.layout-footer { |
|||
color: @normal-color; |
|||
text-align: center; |
|||
|
|||
&__links { |
|||
margin-bottom: 8px; |
|||
|
|||
a { |
|||
color: @normal-color; |
|||
|
|||
&:hover { |
|||
color: @hover-color; |
|||
} |
|||
} |
|||
|
|||
.github { |
|||
margin: 0 30px; |
|||
|
|||
&:hover { |
|||
color: @hover-color; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,34 +0,0 @@ |
|||
import './index.less'; |
|||
|
|||
import { defineComponent } from 'vue'; |
|||
import { Layout } from 'ant-design-vue'; |
|||
|
|||
import { GithubFilled } from '@ant-design/icons-vue'; |
|||
|
|||
import { DOC_URL, GITHUB_URL, SITE_URL } from '/@/settings/siteSetting'; |
|||
import { openWindow } from '/@/utils'; |
|||
|
|||
import { useI18n } from '/@/hooks/web/useI18n'; |
|||
|
|||
export default defineComponent({ |
|||
name: 'LayoutContent', |
|||
setup() { |
|||
const { t } = useI18n(); |
|||
return () => { |
|||
return ( |
|||
<Layout.Footer class="layout-footer"> |
|||
{() => ( |
|||
<> |
|||
<div class="layout-footer__links"> |
|||
<a onClick={() => openWindow(SITE_URL)}>{t('layout.footer.onlinePreview')}</a> |
|||
<GithubFilled onClick={() => openWindow(GITHUB_URL)} class="github" /> |
|||
<a onClick={() => openWindow(DOC_URL)}>{t('layout.footer.onlineDocument')}</a> |
|||
</div> |
|||
<div>Copyright ©2020 Vben Admin</div> |
|||
</> |
|||
)} |
|||
</Layout.Footer> |
|||
); |
|||
}; |
|||
}, |
|||
}); |
|||
@ -0,0 +1,74 @@ |
|||
<template> |
|||
<Footer :class="prefixCls" v-if="getShowLayoutFooter"> |
|||
<div :class="`${prefixCls}__links`"> |
|||
<a @click="openWindow(SITE_URL)">{{ t('layout.footer.onlinePreview') }}</a> |
|||
<GithubFilled @click="openWindow(GITHUB_URL)" :class="`${prefixCls}__github`" /> |
|||
<a @click="openWindow(DOC_URL)">{{ t('layout.footer.onlineDocument') }}</a> |
|||
</div> |
|||
<div>Copyright ©2020 Vben Admin</div> |
|||
</Footer> |
|||
</template> |
|||
|
|||
<script lang="ts"> |
|||
import { computed, defineComponent, unref } from 'vue'; |
|||
import { Layout } from 'ant-design-vue'; |
|||
|
|||
import { GithubFilled } from '@ant-design/icons-vue'; |
|||
|
|||
import { DOC_URL, GITHUB_URL, SITE_URL } from '/@/settings/siteSetting'; |
|||
import { openWindow } from '/@/utils'; |
|||
|
|||
import { useI18n } from '/@/hooks/web/useI18n'; |
|||
import { useRootSetting } from '/@/hooks/setting/useRootSetting'; |
|||
import { useRouter } from 'vue-router'; |
|||
import { useDesign } from '/@/hooks/web/useDesign'; |
|||
|
|||
export default defineComponent({ |
|||
name: 'LayoutFooter', |
|||
components: { Footer: Layout.Footer, GithubFilled }, |
|||
setup() { |
|||
const { t } = useI18n(); |
|||
const { getShowFooter } = useRootSetting(); |
|||
const { currentRoute } = useRouter(); |
|||
const { prefixCls } = useDesign('layout-footer'); |
|||
|
|||
const getShowLayoutFooter = computed(() => { |
|||
return unref(getShowFooter) && !unref(currentRoute).meta?.hiddenFooter; |
|||
}); |
|||
return { getShowLayoutFooter, prefixCls, t, DOC_URL, GITHUB_URL, SITE_URL, openWindow }; |
|||
}, |
|||
}); |
|||
</script> |
|||
<style lang="less" scoped> |
|||
@import (reference) '../../../design/index.less'; |
|||
@prefix-cls: ~'@{namespace}-layout-footer'; |
|||
|
|||
@normal-color: rgba(0, 0, 0, 0.45); |
|||
|
|||
@hover-color: rgba(0, 0, 0, 0.85); |
|||
|
|||
.@{prefix-cls} { |
|||
color: @normal-color; |
|||
text-align: center; |
|||
|
|||
&__links { |
|||
margin-bottom: 8px; |
|||
|
|||
a { |
|||
color: @normal-color; |
|||
|
|||
&:hover { |
|||
color: @hover-color; |
|||
} |
|||
} |
|||
} |
|||
|
|||
&__github { |
|||
margin: 0 30px; |
|||
|
|||
&:hover { |
|||
color: @hover-color; |
|||
} |
|||
} |
|||
} |
|||
</style> |
|||
@ -0,0 +1,95 @@ |
|||
<template> |
|||
<Layout :class="prefixCls"> |
|||
<LayoutFeatures /> |
|||
<LayoutHeader fixed ref="headerRef" v-if="getShowFullHeaderRef" /> |
|||
<Layout> |
|||
<LayoutSideBar v-if="getShowSidebar" /> |
|||
<Layout :class="`${prefixCls}__main`"> |
|||
<LayoutMultipleHeader /> |
|||
<LayoutContent /> |
|||
<LayoutFooter /> |
|||
</Layout> |
|||
</Layout> |
|||
</Layout> |
|||
</template> |
|||
|
|||
<script lang="ts"> |
|||
import { defineComponent, ref } from 'vue'; |
|||
import { Layout } from 'ant-design-vue'; |
|||
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent'; |
|||
|
|||
import LayoutHeader from './header/LayoutHeader'; |
|||
import LayoutContent from './content/index.vue'; |
|||
import LayoutSideBar from './sider'; |
|||
import LayoutMultipleHeader from './header/LayoutMultipleHeader'; |
|||
|
|||
import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting'; |
|||
import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'; |
|||
import { useDesign } from '/@/hooks/web/useDesign'; |
|||
import { createLayoutContext } from './useLayoutContext'; |
|||
|
|||
import { registerGlobComp } from '/@/components/registerGlobComp'; |
|||
import { createBreakpointListen } from '/@/hooks/event/useBreakpoint'; |
|||
import { isMobile } from '/@/utils/is'; |
|||
|
|||
export default defineComponent({ |
|||
name: 'DefaultLayout', |
|||
components: { |
|||
LayoutFeatures: createAsyncComponent(() => import('/@/layouts/default/feature/index.vue')), |
|||
LayoutFooter: createAsyncComponent(() => import('/@/layouts/default/footer/index.vue')), |
|||
LayoutHeader, |
|||
LayoutContent, |
|||
LayoutSideBar, |
|||
LayoutMultipleHeader, |
|||
Layout, |
|||
}, |
|||
setup() { |
|||
const headerRef = ref<ComponentRef>(null); |
|||
const isMobileRef = ref(false); |
|||
|
|||
const { prefixCls } = useDesign('default-layout'); |
|||
|
|||
createLayoutContext({ fullHeader: headerRef, isMobile: isMobileRef }); |
|||
|
|||
createBreakpointListen(() => { |
|||
isMobileRef.value = isMobile(); |
|||
}); |
|||
|
|||
// ! Only register global components here |
|||
// ! Can reduce the size of the first screen code |
|||
// default layout It is loaded after login. So it won’t be packaged to the first screen |
|||
registerGlobComp(); |
|||
|
|||
const { getShowFullHeaderRef } = useHeaderSetting(); |
|||
|
|||
const { getShowSidebar } = useMenuSetting(); |
|||
|
|||
return { |
|||
getShowFullHeaderRef, |
|||
getShowSidebar, |
|||
headerRef, |
|||
prefixCls, |
|||
}; |
|||
}, |
|||
}); |
|||
</script> |
|||
<style lang="less"> |
|||
@import (reference) '../../design/index.less'; |
|||
@prefix-cls: ~'@{namespace}-default-layout'; |
|||
|
|||
.@{prefix-cls} { |
|||
display: flex; |
|||
width: 100%; |
|||
min-height: 100%; |
|||
background: @content-bg; |
|||
flex-direction: column; |
|||
|
|||
> .ant-layout { |
|||
min-height: 100%; |
|||
} |
|||
|
|||
&__main { |
|||
margin-left: 1px; |
|||
} |
|||
} |
|||
</style> |
|||
@ -1,78 +0,0 @@ |
|||
import type { PropType } from 'vue'; |
|||
import { Dropdown } from '/@/components/Dropdown/index'; |
|||
|
|||
import { defineComponent, unref, FunctionalComponent } from 'vue'; |
|||
|
|||
import { TabContentProps } from './types'; |
|||
|
|||
import { RightOutlined } from '@ant-design/icons-vue'; |
|||
|
|||
import { TabContentEnum } from './types'; |
|||
|
|||
import { useTabDropdown } from './useTabDropdown'; |
|||
import { useI18n } from '/@/hooks/web/useI18n'; |
|||
|
|||
import { RouteLocationNormalized } from 'vue-router'; |
|||
|
|||
const { t: titleT } = useI18n(); |
|||
|
|||
const ExtraContent: FunctionalComponent = () => { |
|||
return ( |
|||
<span class={`multiple-tabs-content__extra `}> |
|||
<RightOutlined /> |
|||
</span> |
|||
); |
|||
}; |
|||
|
|||
const TabContent: FunctionalComponent<{ tabItem: RouteLocationNormalized; handler: Fn }> = ( |
|||
props |
|||
) => { |
|||
const { tabItem: { meta } = {} } = props; |
|||
|
|||
return ( |
|||
<div class={`multiple-tabs-content__content `} onContextmenu={props.handler(props.tabItem)}> |
|||
<span class="ml-1">{meta && titleT(meta.title)}</span> |
|||
</div> |
|||
); |
|||
}; |
|||
|
|||
export default defineComponent({ |
|||
name: 'TabContent', |
|||
props: { |
|||
tabItem: { |
|||
type: Object as PropType<RouteLocationNormalized>, |
|||
default: null, |
|||
}, |
|||
|
|||
type: { |
|||
type: Number as PropType<TabContentEnum>, |
|||
default: TabContentEnum.TAB_TYPE, |
|||
}, |
|||
}, |
|||
setup(props) { |
|||
const { |
|||
getDropMenuList, |
|||
handleMenuEvent, |
|||
handleContextMenu, |
|||
getTrigger, |
|||
isTabs, |
|||
} = useTabDropdown(props as TabContentProps); |
|||
|
|||
return () => { |
|||
return ( |
|||
<Dropdown |
|||
dropMenuList={unref(getDropMenuList)} |
|||
trigger={unref(getTrigger)} |
|||
onMenuEvent={handleMenuEvent} |
|||
> |
|||
{() => { |
|||
if (!unref(isTabs)) { |
|||
return <ExtraContent />; |
|||
} |
|||
return <TabContent handler={handleContextMenu} tabItem={props.tabItem} />; |
|||
}} |
|||
</Dropdown> |
|||
); |
|||
}; |
|||
}, |
|||
}); |
|||
@ -1,114 +0,0 @@ |
|||
import './index.less'; |
|||
|
|||
import type { TabContentProps } from './types'; |
|||
|
|||
import { defineComponent, watch, computed, unref, ref } from 'vue'; |
|||
import { useRouter } from 'vue-router'; |
|||
|
|||
import { Tabs } from 'ant-design-vue'; |
|||
import TabContent from './TabContent'; |
|||
|
|||
import { useGo } from '/@/hooks/web/usePage'; |
|||
|
|||
import { TabContentEnum } from './types'; |
|||
|
|||
import { tabStore } from '/@/store/modules/tab'; |
|||
import { userStore } from '/@/store/modules/user'; |
|||
|
|||
import { initAffixTabs, useTabsDrag } from './useMultipleTabs'; |
|||
import { REDIRECT_NAME } from '/@/router/constant'; |
|||
|
|||
export default defineComponent({ |
|||
name: 'MultipleTabs', |
|||
setup() { |
|||
const activeKeyRef = ref(''); |
|||
|
|||
const affixTextList = initAffixTabs(); |
|||
|
|||
useTabsDrag(affixTextList); |
|||
|
|||
const go = useGo(); |
|||
|
|||
const { currentRoute } = useRouter(); |
|||
|
|||
const getTabsState = computed(() => tabStore.getTabsState); |
|||
|
|||
watch( |
|||
() => tabStore.getLastChangeRouteState?.path, |
|||
() => { |
|||
if (tabStore.getLastChangeRouteState?.name === REDIRECT_NAME) { |
|||
return; |
|||
} |
|||
const lastChangeRoute = unref(tabStore.getLastChangeRouteState); |
|||
if (!lastChangeRoute || !userStore.getTokenState) return; |
|||
const { path, fullPath } = lastChangeRoute; |
|||
const p = fullPath || path; |
|||
if (activeKeyRef.value !== p) { |
|||
activeKeyRef.value = p; |
|||
} |
|||
tabStore.addTabAction(lastChangeRoute); |
|||
}, |
|||
{ |
|||
immediate: true, |
|||
} |
|||
); |
|||
|
|||
function handleChange(activeKey: any) { |
|||
activeKeyRef.value = activeKey; |
|||
go(activeKey, false); |
|||
} |
|||
|
|||
// Close the current tab
|
|||
function handleEdit(targetKey: string) { |
|||
// Added operation to hide, currently only use delete operation
|
|||
tabStore.closeTabByKeyAction(targetKey); |
|||
} |
|||
|
|||
function renderQuick() { |
|||
const tabContentProps: TabContentProps = { |
|||
tabItem: currentRoute.value, |
|||
type: TabContentEnum.EXTRA_TYPE, |
|||
}; |
|||
return <TabContent {...tabContentProps} />; |
|||
} |
|||
|
|||
function renderTabs() { |
|||
return unref(getTabsState).map((item) => { |
|||
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}> |
|||
{slots} |
|||
</Tabs.TabPane> |
|||
); |
|||
}); |
|||
} |
|||
|
|||
return () => { |
|||
const slots = { |
|||
default: () => renderTabs(), |
|||
tabBarExtraContent: () => renderQuick(), |
|||
}; |
|||
return ( |
|||
<div class="multiple-tabs"> |
|||
<Tabs |
|||
type="editable-card" |
|||
size="small" |
|||
animated={false} |
|||
hideAdd={true} |
|||
tabBarGutter={3} |
|||
activeKey={unref(activeKeyRef)} |
|||
onChange={handleChange} |
|||
onEdit={handleEdit} |
|||
> |
|||
{slots} |
|||
</Tabs> |
|||
</div> |
|||
); |
|||
}; |
|||
}, |
|||
}); |
|||
@ -0,0 +1,21 @@ |
|||
<template> |
|||
<TabContent :type="TabContentEnum.EXTRA_TYPE" :tabItem="$route" /> |
|||
</template> |
|||
<script lang="ts"> |
|||
import { defineComponent } from 'vue'; |
|||
|
|||
import { TabContentEnum } from '../types'; |
|||
|
|||
import TabContent from './TabContent.vue'; |
|||
export default defineComponent({ |
|||
name: 'QuickButton', |
|||
components: { |
|||
TabContent, |
|||
}, |
|||
setup() { |
|||
return { |
|||
TabContentEnum, |
|||
}; |
|||
}, |
|||
}); |
|||
</script> |
|||
@ -0,0 +1,72 @@ |
|||
<template> |
|||
<Dropdown :dropMenuList="getDropMenuList" :trigger="getTrigger" @menuEvent="handleMenuEvent"> |
|||
<div :class="`${prefixCls}__info`" @contextmenu="handleContext" v-if="isTabs"> |
|||
<span class="ml-1">{{ getTitle }}</span> |
|||
</div> |
|||
|
|||
<span :class="`${prefixCls}__extra`" v-else> |
|||
<RightOutlined /> |
|||
</span> |
|||
</Dropdown> |
|||
</template> |
|||
<script lang="ts"> |
|||
import type { PropType } from 'vue'; |
|||
|
|||
import { defineComponent, computed } from 'vue'; |
|||
import { Dropdown } from '/@/components/Dropdown/index'; |
|||
|
|||
import { TabContentProps, TabContentEnum } from '../types'; |
|||
|
|||
import { RightOutlined } from '@ant-design/icons-vue'; |
|||
|
|||
import { useDesign } from '/@/hooks/web/useDesign'; |
|||
import { useTabDropdown } from '../useTabDropdown'; |
|||
import { useI18n } from '/@/hooks/web/useI18n'; |
|||
|
|||
import { RouteLocationNormalized } from 'vue-router'; |
|||
export default defineComponent({ |
|||
name: 'TabContent', |
|||
components: { Dropdown, RightOutlined }, |
|||
props: { |
|||
tabItem: { |
|||
type: Object as PropType<RouteLocationNormalized>, |
|||
default: null, |
|||
}, |
|||
|
|||
type: { |
|||
type: Number as PropType<TabContentEnum>, |
|||
default: TabContentEnum.TAB_TYPE, |
|||
}, |
|||
}, |
|||
setup(props) { |
|||
const { prefixCls } = useDesign('multiple-tabs-content'); |
|||
const { t } = useI18n(); |
|||
|
|||
const getTitle = computed(() => { |
|||
const { tabItem: { meta } = {} } = props; |
|||
return meta && t(meta.title); |
|||
}); |
|||
|
|||
const { |
|||
getDropMenuList, |
|||
handleMenuEvent, |
|||
handleContextMenu, |
|||
getTrigger, |
|||
isTabs, |
|||
} = useTabDropdown(props as TabContentProps); |
|||
|
|||
function handleContext(e: ChangeEvent) { |
|||
props.tabItem && handleContextMenu(props.tabItem)(e); |
|||
} |
|||
return { |
|||
prefixCls, |
|||
getDropMenuList, |
|||
handleMenuEvent, |
|||
handleContext, |
|||
getTrigger, |
|||
isTabs, |
|||
getTitle, |
|||
}; |
|||
}, |
|||
}); |
|||
</script> |
|||
@ -0,0 +1,123 @@ |
|||
<template> |
|||
<div :class="getWrapClass"> |
|||
<Tabs |
|||
type="editable-card" |
|||
size="small" |
|||
:animated="false" |
|||
:hideAdd="true" |
|||
:tabBarGutter="3" |
|||
:activeKey="activeKeyRef" |
|||
@change="handleChange" |
|||
@edit="handleEdit" |
|||
> |
|||
<template v-for="item in getTabsState" :key="item.query ? item.fullPath : item.path"> |
|||
<TabPane :closable="!(item && item.meta && item.meta.affix)"> |
|||
<template #tab> |
|||
<TabContent :tabItem="item" /> |
|||
</template> |
|||
</TabPane> |
|||
</template> |
|||
<template #tabBarExtraContent> |
|||
<QuickButton /> |
|||
</template> |
|||
</Tabs> |
|||
</div> |
|||
</template> |
|||
<script lang="ts"> |
|||
import { defineComponent, watch, computed, unref, ref } from 'vue'; |
|||
|
|||
import { Tabs } from 'ant-design-vue'; |
|||
import TabContent from './components/TabContent.vue'; |
|||
|
|||
import { useGo } from '/@/hooks/web/usePage'; |
|||
|
|||
import { tabStore } from '/@/store/modules/tab'; |
|||
import { userStore } from '/@/store/modules/user'; |
|||
|
|||
import { initAffixTabs, useTabsDrag } from './useMultipleTabs'; |
|||
import { REDIRECT_NAME } from '/@/router/constant'; |
|||
import { useDesign } from '/@/hooks/web/useDesign'; |
|||
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent'; |
|||
|
|||
export default defineComponent({ |
|||
name: 'MultipleTabs', |
|||
components: { |
|||
QuickButton: createAsyncComponent(() => import('./components/QuickButton.vue')), |
|||
Tabs, |
|||
TabPane: Tabs.TabPane, |
|||
TabContent, |
|||
}, |
|||
setup() { |
|||
const affixTextList = initAffixTabs(); |
|||
|
|||
const activeKeyRef = ref(''); |
|||
|
|||
useTabsDrag(affixTextList); |
|||
const { prefixCls } = useDesign('multiple-tabs'); |
|||
const go = useGo(); |
|||
|
|||
const getTabsState = computed(() => tabStore.getTabsState); |
|||
|
|||
const unClose = computed(() => { |
|||
return getTabsState.value.length === 1; |
|||
}); |
|||
|
|||
const getWrapClass = computed(() => { |
|||
return [ |
|||
prefixCls, |
|||
{ |
|||
[`${prefixCls}--hide-close`]: unClose, |
|||
}, |
|||
]; |
|||
}); |
|||
|
|||
watch( |
|||
() => tabStore.getLastChangeRouteState?.path, |
|||
() => { |
|||
if (tabStore.getLastChangeRouteState?.name === REDIRECT_NAME) { |
|||
return; |
|||
} |
|||
const lastChangeRoute = unref(tabStore.getLastChangeRouteState); |
|||
if (!lastChangeRoute || !userStore.getTokenState) return; |
|||
|
|||
const { path, fullPath } = lastChangeRoute; |
|||
const p = fullPath || path; |
|||
|
|||
if (activeKeyRef.value !== p) { |
|||
activeKeyRef.value = p; |
|||
} |
|||
|
|||
tabStore.addTabAction(lastChangeRoute); |
|||
}, |
|||
{ |
|||
immediate: true, |
|||
} |
|||
); |
|||
|
|||
function handleChange(activeKey: any) { |
|||
activeKeyRef.value = activeKey; |
|||
go(activeKey, false); |
|||
} |
|||
|
|||
// Close the current tab |
|||
function handleEdit(targetKey: string) { |
|||
// Added operation to hide, currently only use delete operation |
|||
if (unref(unClose)) return; |
|||
|
|||
tabStore.closeTabByKeyAction(targetKey); |
|||
} |
|||
return { |
|||
prefixCls, |
|||
unClose, |
|||
getWrapClass, |
|||
handleEdit, |
|||
handleChange, |
|||
activeKeyRef, |
|||
getTabsState, |
|||
}; |
|||
}, |
|||
}); |
|||
</script> |
|||
<style lang="less"> |
|||
@import './index.less'; |
|||
</style> |
|||
@ -0,0 +1,25 @@ |
|||
<template> |
|||
<span :class="[prefixCls, theme]" @click="toggleCollapsed"> |
|||
<MenuUnfoldOutlined v-if="getCollapsed" /> <MenuFoldOutlined v-else /> |
|||
</span> |
|||
</template> |
|||
<script lang="ts"> |
|||
import { defineComponent } from 'vue'; |
|||
import { MenuUnfoldOutlined, MenuFoldOutlined } from '@ant-design/icons-vue'; |
|||
import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'; |
|||
import { useDesign } from '/@/hooks/web/useDesign'; |
|||
import { propTypes } from '/@/utils/propTypes'; |
|||
|
|||
export default defineComponent({ |
|||
name: 'SiderTrigger', |
|||
components: { MenuUnfoldOutlined, MenuFoldOutlined }, |
|||
props: { |
|||
theme: propTypes.oneOf(['light', 'dark']), |
|||
}, |
|||
setup() { |
|||
const { getCollapsed, toggleCollapsed } = useMenuSetting(); |
|||
const { prefixCls } = useDesign('layout-header-trigger'); |
|||
return { getCollapsed, toggleCollapsed, prefixCls }; |
|||
}, |
|||
}); |
|||
</script> |
|||
@ -0,0 +1,18 @@ |
|||
<template> |
|||
<DoubleRightOutlined v-if="getCollapsed" /> |
|||
<DoubleLeftOutlined v-else /> |
|||
</template> |
|||
<script lang="ts"> |
|||
import { defineComponent } from 'vue'; |
|||
import { DoubleRightOutlined, DoubleLeftOutlined } from '@ant-design/icons-vue'; |
|||
import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'; |
|||
|
|||
export default defineComponent({ |
|||
name: 'SiderTrigger', |
|||
components: { DoubleRightOutlined, DoubleLeftOutlined }, |
|||
setup() { |
|||
const { getCollapsed } = useMenuSetting(); |
|||
return { getCollapsed }; |
|||
}, |
|||
}); |
|||
</script> |
|||
@ -0,0 +1,21 @@ |
|||
<template> |
|||
<SiderTrigger v-if="sider" /> |
|||
<HeaderTrigger v-else :theme="theme" /> |
|||
</template> |
|||
<script lang="ts"> |
|||
import { defineComponent } from 'vue'; |
|||
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent'; |
|||
import { propTypes } from '/@/utils/propTypes'; |
|||
|
|||
export default defineComponent({ |
|||
name: 'LayoutTrigger', |
|||
components: { |
|||
SiderTrigger: createAsyncComponent(() => import('./SiderTrigger.vue')), |
|||
HeaderTrigger: createAsyncComponent(() => import('./HeaderTrigger.vue'), { loading: true }), |
|||
}, |
|||
props: { |
|||
sider: propTypes.bool.def(true), |
|||
theme: propTypes.oneOf(['light', 'dark']), |
|||
}, |
|||
}); |
|||
</script> |
|||
Loading…
Reference in new issue