feat: support mobile layout adaptation

This commit is contained in:
vben
2020-12-15 14:59:22 +08:00
parent 683d1f52ca
commit c774a6d3a0
35 changed files with 586 additions and 627 deletions

View File

@@ -1,214 +0,0 @@
import './index.less';
import type { FunctionalComponent } from 'vue';
import type { Component } from '/@/components/types';
import { defineComponent, unref, computed } from 'vue';
import { Layout, Tooltip, Badge } from 'ant-design-vue';
import { AppLogo } from '/@/components/Application';
import LayoutMenu from '../menu';
import LockAction from './actions/LockAction';
import LayoutTrigger from '../trigger/index.vue';
import NoticeAction from './notice/NoticeActionItem.vue';
import { LockOutlined, BugOutlined } from '@ant-design/icons-vue';
import { AppSearch } from '/@/components/Application';
import { useModal } from '/@/components/Modal';
import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting';
import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
import { useRootSetting } from '/@/hooks/setting/useRootSetting';
import { useLocaleSetting } from '/@/hooks/setting/useLocaleSetting';
import { useRouter } from 'vue-router';
import { errorStore } from '/@/store/modules/error';
import { PageEnum } from '/@/enums/pageEnum';
import { MenuModeEnum, MenuSplitTyeEnum } from '/@/enums/menuEnum';
import { AppLocalePicker } from '/@/components/Application';
import { useI18n } from '/@/hooks/web/useI18n';
import { propTypes } from '/@/utils/propTypes';
import { UserDropDown, LayoutBreadcrumb, FullScreen } from './components';
import { useAppInject } from '/@/hooks/web/useAppInject';
import { useDesign } from '../../../hooks/web/useDesign';
interface TooltipItemProps {
title: string;
}
const TooltipItem: FunctionalComponent<TooltipItemProps> = (props, { slots }) => {
return (
<Tooltip>
{{
title: () => props.title,
default: () => slots.default?.(),
}}
</Tooltip>
);
};
export default defineComponent({
name: 'LayoutHeader',
props: {
fixed: propTypes.bool,
},
setup(props) {
const { t } = useI18n();
const { prefixCls } = useDesign('layout-header');
const { getShowTopMenu, getShowHeaderTrigger, getSplit } = useMenuSetting();
const { getShowLocale } = useLocaleSetting();
const { getUseErrorHandle } = useRootSetting();
const {
getHeaderTheme,
getUseLockPage,
getShowFullScreen,
getShowNotice,
getShowContent,
getShowBread,
getShowHeaderLogo,
} = useHeaderSetting();
const { push } = useRouter();
const [register, { openModal }] = useModal();
const { getIsMobile } = useAppInject();
const headerClass = computed(() => {
const theme = unref(getHeaderTheme);
return theme ? `${prefixCls}__header--${theme}` : '';
});
const getSplitType = computed(() => {
return unref(getSplit) ? MenuSplitTyeEnum.TOP : MenuSplitTyeEnum.NONE;
});
const getMenuMode = computed(() => {
return unref(getSplit) ? MenuModeEnum.HORIZONTAL : null;
});
function handleToErrorList() {
push(PageEnum.ERROR_LOG_PAGE).then(() => {
errorStore.commitErrorListCountState(0);
});
}
function handleLockPage() {
openModal(true);
}
function renderHeaderLeft() {
return (
<>
{unref(getShowContent) && (
<div class={`${prefixCls}__left`}>
{unref(getShowHeaderTrigger) && (
<LayoutTrigger theme={unref(getHeaderTheme)} sider={false} />
)}
{unref(getShowBread) && !unref(getIsMobile) && (
<LayoutBreadcrumb theme={unref(getHeaderTheme)} />
)}
</div>
)}
</>
);
}
function renderHeaderContent() {
return (
<div class={`${prefixCls}__content`}>
{unref(getShowTopMenu) && !unref(getIsMobile) && (
<div class={[`${prefixCls}__menu `]}>
{/* <div class={[`layout-header__menu `]}> */}
<LayoutMenu
isHorizontal={true}
// class={`justify-${unref(getTopMenuAlign)}`}
theme={unref(getHeaderTheme)}
splitType={unref(getSplitType)}
menuMode={unref(getMenuMode)}
/>
</div>
)}
</div>
);
}
function renderActionDefault(Comp: Component | any, event: Fn) {
return (
<div class={`${prefixCls}__action-item`} onClick={event}>
<Comp class={`${prefixCls}__action-icon`} />
</div>
);
}
function renderAction() {
return (
<div class={`${prefixCls}__action`}>
{!unref(getIsMobile) && <AppSearch class={`${prefixCls}__action-item`} />}
{unref(getUseErrorHandle) && !unref(getIsMobile) && (
<TooltipItem title={t('layout.header.tooltipErrorLog')}>
{() => (
<Badge
count={errorStore.getErrorListCountState}
offset={[0, 10]}
dot
overflowCount={99}
>
{() => renderActionDefault(BugOutlined, handleToErrorList)}
</Badge>
)}
</TooltipItem>
)}
{unref(getUseLockPage) && !unref(getIsMobile) && (
<TooltipItem title={t('layout.header.tooltipLock')}>
{() => renderActionDefault(LockOutlined, handleLockPage)}
</TooltipItem>
)}
{unref(getShowNotice) && !unref(getIsMobile) && (
<TooltipItem title={t('layout.header.tooltipNotify')}>
{() => <NoticeAction />}
</TooltipItem>
)}
{unref(getShowFullScreen) && !unref(getIsMobile) && <FullScreen />}
<UserDropDown theme={unref(getHeaderTheme)} />
{unref(getShowLocale) && (
<AppLocalePicker
reload={true}
showText={false}
class={`${prefixCls}__action-item locale`}
/>
)}
</div>
);
}
function renderHeaderDefault() {
return (
<>
{unref(getShowHeaderLogo) && (
<AppLogo class={`${prefixCls}__logo`} theme={unref(getHeaderTheme)} />
)}
{renderHeaderLeft()}
{renderHeaderContent()}
{renderAction()}
<LockAction onRegister={register} />
</>
);
}
return () => {
return (
<Layout.Header class={[prefixCls, unref(headerClass), { fixed: props.fixed }]}>
{() => renderHeaderDefault()}
</Layout.Header>
);
};
},
});

View File

@@ -1,18 +0,0 @@
@import (reference) '../../../design/index.less';
.multiple-tab-header {
margin-left: 1px;
transition: width 0.2s;
flex: 0 0 auto;
&.dark {
margin-left: 0;
}
&.fixed {
position: fixed;
top: 0;
z-index: @multiple-tab-fixed-z-index;
width: 100%;
}
}

View File

@@ -1,125 +0,0 @@
import './LayoutMultipleHeader.less';
import { defineComponent, unref, computed, ref, watch, nextTick, CSSProperties } from 'vue';
import LayoutHeader from './index.vue';
import MultipleTabs from '../tabs/index.vue';
import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting';
import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
import { useFullContent } from '/@/hooks/web/useFullContent';
import { useMultipleTabSetting } from '/@/hooks/setting/useMultipleTabSetting';
import { useLayoutContext } from '../useLayoutContext';
import { useAppInject } from '/@/hooks/web/useAppInject';
export default defineComponent({
name: 'LayoutMultipleHeader',
setup() {
const placeholderHeightRef = ref(0);
const fullHeaderHeightRef = ref(0);
const headerElRef = ref<ComponentRef>(null);
const tabElRef = ref<ComponentRef>(null);
const injectValue = useLayoutContext();
const { getCalcContentWidth, getSplit } = useMenuSetting();
const { getIsMobile } = useAppInject();
const {
getFixed,
getShowInsetHeaderRef,
getShowFullHeaderRef,
getShowHeader,
getUnFixedAndFull,
getHeaderTheme,
} = useHeaderSetting();
const { getFullContent } = useFullContent();
const { getShowMultipleTab } = useMultipleTabSetting();
const getShowTabs = computed(() => {
return unref(getShowMultipleTab) && !unref(getFullContent);
});
const getPlaceholderDomStyle = computed(
(): CSSProperties => {
return {
height: `${unref(placeholderHeightRef)}px`,
};
}
);
const getIsShowPlaceholderDom = computed(() => {
return unref(getFixed) || unref(getShowFullHeaderRef);
});
const getWrapStyle = computed(
(): CSSProperties => {
const style: CSSProperties = {};
if (unref(getFixed)) {
style.width = unref(getIsMobile) ? '100%' : unref(getCalcContentWidth);
}
if (unref(getShowFullHeaderRef)) {
style.top = `${unref(fullHeaderHeightRef)}px`;
}
return style;
}
);
const getIsFixed = computed(() => {
return unref(getFixed) || unref(getShowFullHeaderRef);
});
watch(
() => [
unref(getFixed),
unref(getShowFullHeaderRef),
unref(getShowHeader),
unref(getShowMultipleTab),
],
() => {
if (unref(getUnFixedAndFull)) return;
nextTick(() => {
const headerEl = unref(headerElRef)?.$el;
const tabEl = unref(tabElRef)?.$el;
const fullHeaderEl = unref(injectValue.fullHeader)?.$el;
let height = 0;
if (headerEl && !unref(getShowFullHeaderRef) && !unref(getSplit)) {
height += headerEl.offsetHeight;
}
if (tabEl) {
height += tabEl.offsetHeight;
}
if (fullHeaderEl && unref(getShowFullHeaderRef)) {
const fullHeaderHeight = fullHeaderEl.offsetHeight;
height += fullHeaderHeight;
fullHeaderHeightRef.value = fullHeaderHeight;
}
placeholderHeightRef.value = height;
});
},
{
immediate: true,
}
);
return () => {
return (
<>
{unref(getIsShowPlaceholderDom) && <div style={unref(getPlaceholderDomStyle)} />}
<div
style={unref(getWrapStyle)}
class={['multiple-tab-header', unref(getHeaderTheme), { fixed: unref(getIsFixed) }]}
>
{unref(getShowInsetHeaderRef) && <LayoutHeader ref={headerElRef} />}
{unref(getShowTabs) && <MultipleTabs ref={tabElRef} />}
</div>
</>
);
};
},
});

View File

@@ -0,0 +1,124 @@
<template>
<div :style="getPlaceholderDomStyle" v-if="getIsShowPlaceholderDom" />
<div :style="getWrapStyle" :class="getClass">
<LayoutHeader v-if="getShowInsetHeaderRef" />
<MultipleTabs v-if="getShowTabs" />
</div>
</template>
<script lang="ts">
import { defineComponent, unref, computed, CSSProperties } from 'vue';
import LayoutHeader from './index.vue';
import MultipleTabs from '../tabs/index.vue';
import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting';
import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
import { useFullContent } from '/@/hooks/web/useFullContent';
import { useMultipleTabSetting } from '/@/hooks/setting/useMultipleTabSetting';
import { useAppInject } from '/@/hooks/web/useAppInject';
import { useDesign } from '/@/hooks/web/useDesign';
const HEADER_HEIGHT = 48;
const TABS_HEIGHT = 32;
export default defineComponent({
name: 'LayoutMultipleHeader',
components: { LayoutHeader, MultipleTabs },
setup() {
const { prefixCls } = useDesign('layout-multiple-header');
const { getCalcContentWidth, getSplit } = useMenuSetting();
const { getIsMobile } = useAppInject();
const {
getFixed,
getShowInsetHeaderRef,
getShowFullHeaderRef,
getHeaderTheme,
} = useHeaderSetting();
const { getFullContent } = useFullContent();
const { getShowMultipleTab } = useMultipleTabSetting();
const getShowTabs = computed(() => {
return unref(getShowMultipleTab) && !unref(getFullContent);
});
const getIsShowPlaceholderDom = computed(() => {
return unref(getFixed) || unref(getShowFullHeaderRef);
});
const getWrapStyle = computed(
(): CSSProperties => {
const style: CSSProperties = {};
if (unref(getFixed)) {
style.width = unref(getIsMobile) ? '100%' : unref(getCalcContentWidth);
}
if (unref(getShowFullHeaderRef)) {
style.top = `${HEADER_HEIGHT}px`;
}
return style;
}
);
const getIsFixed = computed(() => {
return unref(getFixed) || unref(getShowFullHeaderRef);
});
const getPlaceholderDomStyle = computed(
(): CSSProperties => {
let height = 0;
if (unref(getShowFullHeaderRef) || !unref(getSplit)) {
height += HEADER_HEIGHT;
}
if (unref(getShowMultipleTab)) {
height += TABS_HEIGHT;
}
return {
height: `${height}px`,
};
}
);
const getClass = computed(() => {
return [
prefixCls,
`${prefixCls}--${unref(getHeaderTheme)}`,
{ [`${prefixCls}--fixed`]: unref(getIsFixed) },
];
});
return {
getClass,
prefixCls,
getPlaceholderDomStyle,
getIsFixed,
getWrapStyle,
getIsShowPlaceholderDom,
getShowTabs,
getShowInsetHeaderRef,
};
},
});
</script>
<style lang="less" scoped>
@import (reference) '../../../design/index.less';
@prefix-cls: ~'@{namespace}-layout-multiple-header';
.@{prefix-cls} {
margin-left: 1px;
transition: width 0.2s;
flex: 0 0 auto;
&--dark {
margin-left: 0;
}
&--fixed {
position: fixed;
top: 0;
z-index: @multiple-tab-fixed-z-index;
width: 100%;
}
}
</style>

View File

@@ -2,6 +2,8 @@
@header-trigger-prefix-cls: ~'@{namespace}-layout-header-trigger';
@header-prefix-cls: ~'@{namespace}-layout-header';
@locale-prefix-cls: ~'@{namespace}-app-locale-picker';
@breadcrumb-prefix-cls: ~'@{namespace}-layout-breadcrumb';
@logo-prefix-cls: ~'@{namespace}-app-logo';
.@{header-prefix-cls} {
display: flex;
@@ -14,6 +16,30 @@
align-items: center;
justify-content: space-between;
&--mobile {
.@{breadcrumb-prefix-cls},
.error-action,
.notify-item,
.fullscreen-item {
display: none;
}
.@{logo-prefix-cls} {
min-width: unset;
padding-right: 0;
&__title {
display: none;
}
}
.@{header-trigger-prefix-cls} {
padding: 0 4px 0 8px !important;
}
.@{header-prefix-cls}-action {
padding-right: 4px;
}
}
&--fixed {
position: fixed;
top: 0;
@@ -78,7 +104,7 @@
&-action {
display: flex;
min-width: 200px;
min-width: 180px;
padding-right: 12px;
align-items: center;

View File

@@ -3,17 +3,17 @@
<!-- left start -->
<div :class="`${prefixCls}-left`">
<!-- logo -->
<AppLogo v-if="getShowHeaderLogo" :class="`${prefixCls}-logo`" :theme="getHeaderTheme" />
<AppLogo
v-if="getShowHeaderLogo || getIsMobile"
:class="`${prefixCls}-logo`"
:theme="getHeaderTheme"
/>
<LayoutTrigger
v-if="getShowContent && getShowHeaderTrigger"
v-if="(getShowContent && getShowHeaderTrigger && !getSplit) || getIsMobile"
:theme="getHeaderTheme"
:sider="false"
/>
<LayoutBreadcrumb
v-if="getShowContent && getShowBread && !getIsMobile"
:theme="getHeaderTheme"
/>
<LayoutBreadcrumb v-if="getShowContent && getShowBread" :theme="getHeaderTheme" />
</div>
<!-- left end -->
@@ -30,15 +30,15 @@
<!-- action -->
<div :class="`${prefixCls}-action`">
<AppSearch v-if="!getIsMobile" :class="`${prefixCls}-action__item`" />
<AppSearch :class="`${prefixCls}-action__item `" />
<ErrorAction v-if="getUseErrorHandle && !getIsMobile" :class="`${prefixCls}-action__item`" />
<ErrorAction v-if="getUseErrorHandle" :class="`${prefixCls}-action__item error-action`" />
<LockItem v-if="getUseLockPage && !getIsMobile" :class="`${prefixCls}-action__item`" />
<LockItem v-if="getUseLockPage" :class="`${prefixCls}-action__item lock-item`" />
<Notify v-if="getShowNotice && !getIsMobile" :class="`${prefixCls}-action__item`" />
<Notify v-if="getShowNotice" :class="`${prefixCls}-action__item notify-item`" />
<FullScreen v-if="getShowFullScreen && !getIsMobile" :class="`${prefixCls}-action__item`" />
<FullScreen v-if="getShowFullScreen" :class="`${prefixCls}-action__item fullscreen-item`" />
<UserDropDown :theme="getHeaderTheme" />
@@ -123,7 +123,11 @@
const theme = unref(getHeaderTheme);
return [
prefixCls,
{ [`${prefixCls}--fixed`]: props.fixed, [`${prefixCls}--${theme}`]: theme },
{
[`${prefixCls}--fixed`]: props.fixed,
[`${prefixCls}--mobile`]: unref(getIsMobile),
[`${prefixCls}--${theme}`]: theme,
},
];
});
@@ -145,6 +149,7 @@
getShowBread,
getShowContent,
getSplitType,
getSplit,
getMenuMode,
getShowTopMenu,
getShowLocale,

View File

@@ -1,9 +1,9 @@
<template>
<Layout :class="prefixCls">
<LayoutFeatures />
<LayoutHeader fixed ref="headerRef" v-if="getShowFullHeaderRef" />
<LayoutHeader fixed v-if="getShowFullHeaderRef" />
<Layout>
<LayoutSideBar v-if="getShowSidebar" />
<LayoutSideBar v-if="getShowSidebar || getIsMobile" />
<Layout :class="`${prefixCls}__main`">
<LayoutMultipleHeader />
<LayoutContent />
@@ -14,21 +14,21 @@
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue';
import { defineComponent } from 'vue';
import { Layout } from 'ant-design-vue';
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
import LayoutHeader from './header/index.vue';
import LayoutContent from './content/index.vue';
import LayoutSideBar from './sider';
import LayoutMultipleHeader from './header/LayoutMultipleHeader';
import LayoutSideBar from './sider/index.vue';
import LayoutMultipleHeader from './header/MultipleHeader.vue';
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 { useAppInject } from '/@/hooks/web/useAppInject';
export default defineComponent({
name: 'DefaultLayout',
@@ -47,11 +47,9 @@
// default layout It is loaded after login. So it wont be packaged to the first screen
registerGlobComp();
const headerRef = ref<ComponentRef>(null);
const { prefixCls } = useDesign('default-layout');
createLayoutContext({ fullHeader: headerRef });
const { getIsMobile } = useAppInject();
const { getShowFullHeaderRef } = useHeaderSetting();
@@ -60,8 +58,8 @@
return {
getShowFullHeaderRef,
getShowSidebar,
headerRef,
prefixCls,
getIsMobile,
};
},
});

View File

@@ -1,7 +1,10 @@
@import (reference) '../../../design/index.less';
.layout-menu {
&__logo {
@prefix-cls: ~'@{namespace}-layout-menu';
@logo-prefix-cls: ~'@{namespace}-app-logo';
.@{prefix-cls} {
&-logo {
height: @header-height;
padding: 10px 4px 10px 10px;
@@ -10,4 +13,12 @@
height: @logo-width;
}
}
&--mobile {
.@{logo-prefix-cls} {
&__title {
opacity: 1;
}
}
}
}

View File

@@ -1,8 +1,8 @@
import './index.less';
import { PropType, toRef } from 'vue';
import type { PropType, CSSProperties } from 'vue';
import { computed, defineComponent, unref } from 'vue';
import { computed, defineComponent, unref, toRef } from 'vue';
import { BasicMenu } from '/@/components/Menu';
import { AppLogo } from '/@/components/Application';
@@ -17,7 +17,8 @@ import { openWindow } from '/@/utils';
import { propTypes } from '/@/utils/propTypes';
import { isUrl } from '/@/utils/is';
import { useRootSetting } from '/@/hooks/setting/useRootSetting';
import { CSSProperties } from 'vue';
import { useAppInject } from '/@/hooks/web/useAppInject';
import { useDesign } from '/@/hooks/web/useDesign';
export default defineComponent({
name: 'LayoutMenu',
@@ -50,9 +51,15 @@ export default defineComponent({
} = useMenuSetting();
const { getShowLogo } = useRootSetting();
const { prefixCls } = useDesign('layout-menu');
const { menusRef } = useSplitMenu(toRef(props, 'splitType'));
const getComputedMenuMode = computed(() => props.menuMode || unref(getMenuMode));
const { getIsMobile } = useAppInject();
const getComputedMenuMode = computed(() =>
unref(getIsMobile) ? MenuModeEnum.INLINE : props.menuMode || unref(getMenuMode)
);
const getComputedMenuTheme = computed(() => props.theme || unref(getMenuTheme));
@@ -69,6 +76,16 @@ export default defineComponent({
};
}
);
const getLogoClass = computed(() => {
return [
`${prefixCls}-logo`,
unref(getComputedMenuTheme),
{
[`${prefixCls}--mobile`]: unref(getIsMobile),
},
];
});
/**
* click menu
* @param menu
@@ -91,12 +108,12 @@ export default defineComponent({
}
function renderHeader() {
if (!unref(getIsShowLogo)) return null;
if (!unref(getIsShowLogo) && !unref(getIsMobile)) return null;
return (
<AppLogo
showTitle={!unref(getCollapsed)}
class={[`layout-menu__logo`, unref(getComputedMenuTheme)]}
class={unref(getLogoClass)}
theme={unref(getComputedMenuTheme)}
/>
);
@@ -128,7 +145,6 @@ export default defineComponent({
) : (
renderMenu()
)}
;
</>
);
};

View File

@@ -10,11 +10,13 @@ import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
import { getChildrenMenus, getCurrentParentPath, getMenus, getShallowMenus } from '/@/router/menus';
import { permissionStore } from '/@/store/modules/permission';
import { useAppInject } from '/@/hooks/web/useAppInject';
export function useSplitMenu(splitType: Ref<MenuSplitTyeEnum>) {
// Menu array
const menusRef = ref<Menu[]>([]);
const { currentRoute } = useRouter();
const { getIsMobile } = useAppInject();
const { setMenuSetting, getIsHorizontal, getSplit } = useMenuSetting();
const [throttleHandleSplitLeftMenu] = useThrottle(handleSplitLeftMenu, 50);
@@ -36,7 +38,7 @@ export function useSplitMenu(splitType: Ref<MenuSplitTyeEnum>) {
watch(
[() => unref(currentRoute).path, () => unref(splitType)],
async ([path]: [string, MenuSplitTyeEnum]) => {
if (unref(splitNotLeft)) return;
if (unref(splitNotLeft) || unref(getIsMobile)) return;
const parentPath = await getCurrentParentPath(path);
parentPath && throttleHandleSplitLeftMenu(parentPath);
@@ -65,24 +67,24 @@ export function useSplitMenu(splitType: Ref<MenuSplitTyeEnum>) {
// Handle left menu split
async function handleSplitLeftMenu(parentPath: string) {
if (unref(getSplitLeft)) return;
if (unref(getSplitLeft) || unref(getIsMobile)) return;
// spilt mode left
const children = await getChildrenMenus(parentPath);
if (!children) {
setMenuSetting({ hidden: false });
setMenuSetting({ hidden: true });
menusRef.value = [];
return;
}
setMenuSetting({ hidden: true });
setMenuSetting({ hidden: false });
menusRef.value = children;
}
// get menus
async function genMenus() {
// normal mode
if (unref(normalType)) {
if (unref(normalType) || unref(getIsMobile)) {
menusRef.value = await getMenus();
return;
}

View File

@@ -0,0 +1,67 @@
<template>
<div :class="getClass" :style="getDragBarStyle" />
</template>
<script lang="ts">
import { defineComponent, computed, unref } from 'vue';
import { useDesign } from '/@/hooks/web/useDesign';
import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
export default defineComponent({
name: 'DargBar',
props: {
mobile: Boolean,
},
setup(props) {
const { getMiniWidthNumber, getCollapsed, getCanDrag } = useMenuSetting();
const { prefixCls } = useDesign('darg-bar');
const getDragBarStyle = computed(() => {
if (unref(getCollapsed)) {
return { left: `${unref(getMiniWidthNumber)}px` };
}
return {};
});
const getClass = computed(() => {
return [
prefixCls,
{
[`${prefixCls}--hide`]: !unref(getCanDrag) || props.mobile,
},
];
});
return {
prefixCls,
getDragBarStyle,
getClass,
};
},
});
</script>
<style lang="less" scoped>
@import (reference) '../../../design/index.less';
@prefix-cls: ~'@{namespace}-darg-bar';
.@{prefix-cls} {
position: absolute;
top: 0;
right: -2px;
z-index: @side-drag-z-index;
width: 2px;
height: 100%;
cursor: col-resize;
border-top: none;
border-bottom: none;
&--hide {
display: none;
}
&:hover {
background: @primary-color;
box-shadow: 0 0 4px 0 rgba(28, 36, 56, 0.15);
}
}
</style>

View File

@@ -12,6 +12,7 @@ import { useTrigger, useDragLine, useSiderEvent } from './useLayoutSider';
import { useAppInject } from '/@/hooks/web/useAppInject';
import { useDesign } from '/@/hooks/web/useDesign';
import DragBar from './DragBar.vue';
export default defineComponent({
name: 'LayoutSideBar',
setup() {
@@ -31,11 +32,11 @@ export default defineComponent({
const { prefixCls } = useDesign('layout-sideBar');
const { getTriggerAttr, getTriggerSlot } = useTrigger();
const { getIsMobile } = useAppInject();
const { renderDragLine } = useDragLine(sideRef, dragBarRef);
const { getTriggerAttr, getTriggerSlot } = useTrigger(getIsMobile);
useDragLine(sideRef, dragBarRef);
const { getCollapsedWidth, onBreakpointChange, onCollapseChange } = useSiderEvent();
@@ -48,7 +49,7 @@ export default defineComponent({
});
const showClassSideBarRef = computed(() => {
return unref(getSplit) ? unref(getMenuHidden) : true;
return unref(getSplit) ? !unref(getMenuHidden) : true;
});
const getSiderClass = computed(() => {
@@ -57,7 +58,7 @@ export default defineComponent({
{
[`${prefixCls}--fixed`]: unref(getMenuFixed),
hidden: !unref(showClassSideBarRef),
[`${prefixCls}--mix`]: unref(getIsMixMode),
[`${prefixCls}--mix`]: unref(getIsMixMode) && !unref(getIsMobile),
},
];
});
@@ -84,7 +85,7 @@ export default defineComponent({
menuMode={unref(getMode)}
splitType={unref(getSplitType)}
/>
{renderDragLine()}
<DragBar ref={dragBarRef} />
</>
);
}
@@ -101,7 +102,7 @@ export default defineComponent({
collapsible
class={unref(getSiderClass)}
width={unref(getMenuWidth)}
collapsed={unref(getCollapsed)}
collapsed={unref(getIsMobile) ? false : unref(getCollapsed)}
collapsedWidth={unref(getCollapsedWidth)}
theme={unref(getMenuTheme)}
onCollapse={onCollapseChange}

View File

@@ -44,27 +44,6 @@
z-index: 10;
}
&__darg-bar {
position: absolute;
top: 0;
right: -2px;
z-index: @side-drag-z-index;
width: 2px;
height: 100%;
cursor: col-resize;
border-top: none;
border-bottom: none;
&.hide {
display: none;
}
&:hover {
background: @primary-color;
box-shadow: 0 0 4px 0 rgba(28, 36, 56, 0.15);
}
}
& .ant-layout-sider-trigger {
height: 36px;
line-height: 36px;

View File

@@ -0,0 +1,54 @@
<template>
<Drawer
v-if="getIsMobile"
placement="left"
:class="prefixCls"
:width="getMenuWidth"
:getContainer="null"
:visible="!getCollapsed"
@close="handleClose"
>
<Sider />
</Drawer>
<Sider v-else />
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import Sider from './LayoutSider';
import { Drawer } from 'ant-design-vue';
import { useAppInject } from '/@/hooks/web/useAppInject';
import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
import { useDesign } from '/@/hooks/web/useDesign';
export default defineComponent({
name: 'SiderWrapper',
components: { Sider, Drawer },
setup() {
const { prefixCls } = useDesign('layout-sider-wrapper');
const { getIsMobile } = useAppInject();
const { setMenuSetting, getCollapsed, getMenuWidth } = useMenuSetting();
function handleClose() {
setMenuSetting({
collapsed: true,
});
}
return { prefixCls, getIsMobile, getCollapsed, handleClose, getMenuWidth };
},
});
</script>
<style lang="less">
@import (reference) '../../../design/index.less';
@prefix-cls: ~'@{namespace}-layout-sider-wrapper';
.@{prefix-cls} {
.ant-drawer-body {
height: 100vh;
padding: 0;
}
.ant-drawer-header-no-title {
display: none;
}
}
</style>

View File

@@ -42,12 +42,17 @@ export function useSiderEvent() {
/**
* Handle related operations of menu folding
*/
export function useTrigger() {
const { getTrigger } = useMenuSetting();
export function useTrigger(getIsMobile: Ref<boolean>) {
const { getTrigger, getSplit } = useMenuSetting();
const showTrigger = computed(() => {
const trigger = unref(getTrigger);
return trigger !== TriggerEnum.NONE && trigger === TriggerEnum.FOOTER;
return (
trigger !== TriggerEnum.NONE &&
!unref(getIsMobile) &&
(trigger === TriggerEnum.FOOTER || unref(getSplit))
);
});
const getTriggerAttr = computed(() => {
@@ -77,14 +82,7 @@ export function useTrigger() {
* @param dragBarRef
*/
export function useDragLine(siderRef: Ref<any>, dragBarRef: Ref<any>) {
const { getMiniWidthNumber, getCollapsed, setMenuSetting, getCanDrag } = useMenuSetting();
const getDragBarStyle = computed(() => {
if (unref(getCollapsed)) {
return { left: `${unref(getMiniWidthNumber)}px` };
}
return {};
});
const { getMiniWidthNumber, getCollapsed, setMenuSetting } = useMenuSetting();
onMounted(() => {
nextTick(() => {
@@ -93,16 +91,6 @@ export function useDragLine(siderRef: Ref<any>, dragBarRef: Ref<any>) {
});
});
function renderDragLine() {
return (
<div
class={[`layout-sidebar__darg-bar`, { hide: !unref(getCanDrag) }]}
style={unref(getDragBarStyle)}
ref={dragBarRef}
/>
);
}
function handleMouseMove(ele: HTMLElement, wrap: HTMLElement, clientX: number) {
document.onmousemove = function (innerE) {
let iT = (ele as any).left + (innerE.clientX - clientX);
@@ -138,21 +126,22 @@ export function useDragLine(siderRef: Ref<any>, dragBarRef: Ref<any>) {
}
function changeWrapWidth() {
const ele = unref(dragBarRef) as any;
const ele = unref(dragBarRef)?.$el;
if (!ele) {
return;
}
const side = unref(siderRef);
const wrap = (side || {}).$el;
ele &&
(ele.onmousedown = (e: any) => {
wrap.style.transition = 'unset';
const clientX = e?.clientX;
ele.left = ele.offsetLeft;
handleMouseMove(ele, wrap, clientX);
removeMouseup(ele);
ele.setCapture?.();
return false;
});
ele.onmousedown = (e: any) => {
wrap.style.transition = 'unset';
const clientX = e?.clientX;
ele.left = ele.offsetLeft;
handleMouseMove(ele, wrap, clientX);
removeMouseup(ele);
ele.setCapture?.();
return false;
};
}
return { renderDragLine };
return {};
}

View File

@@ -1,16 +0,0 @@
import { InjectionKey, Ref } from 'vue';
import { createContext, useContext } from '/@/hooks/core/useContext';
export interface LayoutContextProps {
fullHeader: Ref<ComponentRef>;
}
const key: InjectionKey<LayoutContextProps> = Symbol();
export function createLayoutContext(context: LayoutContextProps) {
return createContext<LayoutContextProps>(context, key);
}
export function useLayoutContext() {
return useContext<LayoutContextProps>(key);
}