提交 c774a6d3 作者: vben

feat: support mobile layout adaptation

上级 683d1f52
...@@ -3,17 +3,21 @@ ...@@ -3,17 +3,21 @@
### ✨ Features ### ✨ Features
- 移除左侧菜单搜索,新增顶部菜单搜索功能 - 移除左侧菜单搜索,新增顶部菜单搜索功能
- layout 移动端适配。页面未适配
### ⚡ Performance Improvements ### ⚡ Performance Improvements
- 异步引入组件 - 异步引入组件
- 优化整体结构 - 优化整体结构
- 替换菜单默认滚动条为滚动组件
- 菜单性能优化
### 🎫 Chores ### 🎫 Chores
- 返回顶部样式调整,避免遮住其他元素 - 返回顶部样式调整,避免遮住其他元素
- 升级`ant-design-vue``2.0.0-rc.5` - 升级`ant-design-vue``2.0.0-rc.5`
- 刷新按钮布局调整 - 刷新按钮布局调整
- `route.meta` 移除 `externalLink` 属性
### 🐛 Bug Fixes ### 🐛 Bug Fixes
......
...@@ -37,7 +37,7 @@ ...@@ -37,7 +37,7 @@
"sortablejs": "^1.12.0", "sortablejs": "^1.12.0",
"vditor": "^3.7.2", "vditor": "^3.7.2",
"vue": "^3.0.4", "vue": "^3.0.4",
"vue-i18n": "^9.0.0-beta.12", "vue-i18n": "^9.0.0-beta.13",
"vue-router": "^4.0.1", "vue-router": "^4.0.1",
"vue-types": "^3.0.1", "vue-types": "^3.0.1",
"vuex": "^4.0.0-rc.2", "vuex": "^4.0.0-rc.2",
......
<template> <template>
<div :class="prefixCls" v-if="getShowSearch" @click="handleSearch"> <div :class="prefixCls" v-if="getShowSearch" @click.stop="handleSearch">
<Tooltip> <Tooltip>
<template #title> {{ t('component.app.search') }} </template> <template #title> {{ t('component.app.search') }} </template>
<SearchOutlined /> <SearchOutlined />
</Tooltip> </Tooltip>
<transition name="zoom-fade" mode="out-in"> <AppSearchModal @close="handleClose" :visible="showModal" />
<AppSearchModal @close="handleClose" v-if="showModal" />
</transition>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
......
<template> <template>
<div :class="prefixCls" @click.stop> <Teleport to="body">
<ClickOutSide @clickOutside="handleClose"> <transition name="zoom-fade" mode="out-in">
<div :class="`${prefixCls}-content`"> <div :class="getClass" @click.stop v-if="visible">
<a-input <ClickOutSide @clickOutside="handleClose">
:class="`${prefixCls}-input`" <div :class="`${prefixCls}-content`">
:placeholder="t('component.app.search')" <div :class="`${prefixCls}-input__wrapper`">
allow-clear <a-input
@change="handleSearch" :class="`${prefixCls}-input`"
> :placeholder="t('component.app.search')"
<template #prefix> allow-clear
<SearchOutlined /> @change="handleSearch"
</template> >
</a-input> <template #prefix>
<div :class="`${prefixCls}-not-data`" v-show="getIsNotData"> <SearchOutlined />
{{ t('component.app.searchNotData') }} </template>
</div> </a-input>
<ul :class="`${prefixCls}-list`" v-show="!getIsNotData" ref="scrollWrap"> <span :class="`${prefixCls}-cancel`" @click="handleClose">{{
<li t('component.app.cancel')
:ref="setRefs(index)" }}</span>
v-for="(item, index) in searchResult"
:key="item.path"
:data-index="index"
@mouseenter="handleMouseenter"
@click="handleEnter"
:class="[
`${prefixCls}-list__item`,
{
[`${prefixCls}-list__item--active`]: activeIndex === index,
},
]"
>
<div :class="`${prefixCls}-list__item-icon`">
<g-icon :icon="item.icon || 'mdi:form-select'" :size="20" />
</div> </div>
<div :class="`${prefixCls}-list__item-text`">{{ item.name }}</div>
<div :class="`${prefixCls}-list__item-enter`"> <div :class="`${prefixCls}-not-data`" v-show="getIsNotData">
<g-icon icon="ant-design:enter-outlined" :size="20" /> {{ t('component.app.searchNotData') }}
</div> </div>
</li> <ul :class="`${prefixCls}-list`" v-show="!getIsNotData" ref="scrollWrap">
</ul> <li
<AppSearchFooter /> :ref="setRefs(index)"
v-for="(item, index) in searchResult"
:key="item.path"
:data-index="index"
@mouseenter="handleMouseenter"
@click="handleEnter"
:class="[
`${prefixCls}-list__item`,
{
[`${prefixCls}-list__item--active`]: activeIndex === index,
},
]"
>
<div :class="`${prefixCls}-list__item-icon`">
<g-icon :icon="item.icon || 'mdi:form-select'" :size="20" />
</div>
<div :class="`${prefixCls}-list__item-text`">{{ item.name }}</div>
<div :class="`${prefixCls}-list__item-enter`">
<g-icon icon="ant-design:enter-outlined" :size="20" />
</div>
</li>
</ul>
<AppSearchFooter />
</div>
</ClickOutSide>
</div> </div>
</ClickOutSide> </transition>
</div> </Teleport>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, computed, unref, ref } from 'vue'; import { defineComponent, computed, unref, ref } from 'vue';
...@@ -54,15 +64,20 @@ ...@@ -54,15 +64,20 @@
import AppSearchFooter from './AppSearchFooter.vue'; import AppSearchFooter from './AppSearchFooter.vue';
import { useI18n } from '/@/hooks/web/useI18n'; import { useI18n } from '/@/hooks/web/useI18n';
import { ClickOutSide } from '/@/components/ClickOutSide'; import { ClickOutSide } from '/@/components/ClickOutSide';
import { useAppInject } from '/@/hooks/web/useAppInject';
export default defineComponent({ export default defineComponent({
name: 'AppSearchModal', name: 'AppSearchModal',
components: { SearchOutlined, ClickOutSide, AppSearchFooter }, components: { SearchOutlined, ClickOutSide, AppSearchFooter },
emits: ['close'], emits: ['close'],
props: {
visible: Boolean,
},
setup(_, { emit }) { setup(_, { emit }) {
const scrollWrap = ref<ElRef>(null); const scrollWrap = ref<ElRef>(null);
const { prefixCls } = useDesign('app-search-modal'); const { prefixCls } = useDesign('app-search-modal');
const { t } = useI18n(); const { t } = useI18n();
const [refs, setRefs] = useRefs(); const [refs, setRefs] = useRefs();
const { getIsMobile } = useAppInject();
const { const {
handleSearch, handleSearch,
...@@ -77,9 +92,19 @@ ...@@ -77,9 +92,19 @@
return !keyword || unref(searchResult).length === 0; return !keyword || unref(searchResult).length === 0;
}); });
const getClass = computed(() => {
return [
prefixCls,
{
[`${prefixCls}--mobile`]: unref(getIsMobile),
},
];
});
return { return {
t, t,
prefixCls, prefixCls,
getClass,
handleSearch, handleSearch,
searchResult, searchResult,
activeIndex, activeIndex,
...@@ -98,12 +123,12 @@ ...@@ -98,12 +123,12 @@
<style lang="less" scoped> <style lang="less" scoped>
@import (reference) '../../../../design/index.less'; @import (reference) '../../../../design/index.less';
@prefix-cls: ~'@{namespace}-app-search-modal'; @prefix-cls: ~'@{namespace}-app-search-modal';
@footer-prefix-cls: ~'@{namespace}-app-search-footer';
.@{prefix-cls} { .@{prefix-cls} {
position: fixed; position: fixed;
top: 0; top: 0;
left: 0; left: 0;
z-index: 100; z-index: 800;
display: flex; display: flex;
width: 100%; width: 100%;
height: 100%; height: 100%;
...@@ -113,6 +138,43 @@ ...@@ -113,6 +138,43 @@
justify-content: center; justify-content: center;
// backdrop-filter: blur(2px); // backdrop-filter: blur(2px);
&--mobile {
padding: 0;
> div {
width: 100%;
}
.@{prefix-cls}-input {
width: calc(100% - 38px);
}
.@{prefix-cls}-cancel {
display: inline-block;
}
.@{prefix-cls}-content {
width: 100%;
height: 100%;
border-radius: 0;
}
.@{footer-prefix-cls} {
display: none;
}
.@{prefix-cls}-list {
height: calc(100% - 80px);
max-height: unset;
&__item {
&-enter {
opacity: 0 !important;
}
}
}
}
&-content { &-content {
position: relative; position: relative;
width: 532px; width: 532px;
...@@ -124,10 +186,16 @@ ...@@ -124,10 +186,16 @@
flex-direction: column; flex-direction: column;
} }
&-input__wrapper {
display: flex;
padding: 14px 14px 0 14px;
justify-content: space-between;
align-items: center;
}
&-input { &-input {
width: calc(100% - 28px); width: 100%;
height: 56px; height: 56px;
margin: 14px 14px 0 14px;
font-size: 1.5em; font-size: 1.5em;
color: #1c1e21; color: #1c1e21;
...@@ -136,6 +204,12 @@ ...@@ -136,6 +204,12 @@
} }
} }
&-cancel {
display: none;
font-size: 1em;
color: #666;
}
&-not-data { &-not-data {
display: flex; display: flex;
width: 100%; width: 100%;
......
...@@ -151,7 +151,7 @@ ...@@ -151,7 +151,7 @@
if (props.mode !== MenuModeEnum.HORIZONTAL) { if (props.mode !== MenuModeEnum.HORIZONTAL) {
setOpenKeys(path); setOpenKeys(path);
} }
if (unref(getIsTopMenu)) { if (props.isHorizontal && unref(getSplit)) {
const parentPath = await getCurrentParentPath(path); const parentPath = await getCurrentParentPath(path);
menuState.selectedKeys = [parentPath]; menuState.selectedKeys = [parentPath];
} else { } else {
......
...@@ -50,11 +50,7 @@ const getShowTopMenu = computed(() => { ...@@ -50,11 +50,7 @@ const getShowTopMenu = computed(() => {
}); });
const getShowHeaderTrigger = computed(() => { const getShowHeaderTrigger = computed(() => {
if ( if (unref(getMenuType) === MenuTypeEnum.TOP_MENU || !unref(getShowMenu) || unref(getMenuHidden)) {
unref(getMenuType) === MenuTypeEnum.TOP_MENU ||
!unref(getShowMenu) ||
!unref(getMenuHidden)
) {
return false; return false;
} }
...@@ -79,7 +75,11 @@ const getMiniWidthNumber = computed(() => { ...@@ -79,7 +75,11 @@ const getMiniWidthNumber = computed(() => {
}); });
const getCalcContentWidth = computed(() => { const getCalcContentWidth = computed(() => {
const width = unref(getIsTopMenu) || !unref(getShowMenu) ? 0 : unref(getRealWidth); const width =
unref(getIsTopMenu) || !unref(getShowMenu) || (unref(getSplit) && unref(getMenuHidden))
? 0
: unref(getRealWidth);
return `calc(100% - ${unref(width)}px)`; return `calc(100% - ${unref(width)}px)`;
}); });
......
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>
);
};
},
});
@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%;
}
}
import './LayoutMultipleHeader.less'; <template>
<div :style="getPlaceholderDomStyle" v-if="getIsShowPlaceholderDom" />
import { defineComponent, unref, computed, ref, watch, nextTick, CSSProperties } from 'vue'; <div :style="getWrapStyle" :class="getClass">
<LayoutHeader v-if="getShowInsetHeaderRef" />
import LayoutHeader from './index.vue'; <MultipleTabs v-if="getShowTabs" />
import MultipleTabs from '../tabs/index.vue'; </div>
</template>
import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting'; <script lang="ts">
import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'; import { defineComponent, unref, computed, CSSProperties } from 'vue';
import { useFullContent } from '/@/hooks/web/useFullContent';
import { useMultipleTabSetting } from '/@/hooks/setting/useMultipleTabSetting'; import LayoutHeader from './index.vue';
import { useLayoutContext } from '../useLayoutContext'; import MultipleTabs from '../tabs/index.vue';
import { useAppInject } from '/@/hooks/web/useAppInject';
import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting';
export default defineComponent({ import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
name: 'LayoutMultipleHeader', import { useFullContent } from '/@/hooks/web/useFullContent';
setup() { import { useMultipleTabSetting } from '/@/hooks/setting/useMultipleTabSetting';
const placeholderHeightRef = ref(0); import { useAppInject } from '/@/hooks/web/useAppInject';
const fullHeaderHeightRef = ref(0); import { useDesign } from '/@/hooks/web/useDesign';
const headerElRef = ref<ComponentRef>(null);
const tabElRef = ref<ComponentRef>(null); const HEADER_HEIGHT = 48;
const injectValue = useLayoutContext(); const TABS_HEIGHT = 32;
export default defineComponent({
const { getCalcContentWidth, getSplit } = useMenuSetting(); name: 'LayoutMultipleHeader',
const { getIsMobile } = useAppInject(); components: { LayoutHeader, MultipleTabs },
const { setup() {
getFixed, const { prefixCls } = useDesign('layout-multiple-header');
getShowInsetHeaderRef,
getShowFullHeaderRef, const { getCalcContentWidth, getSplit } = useMenuSetting();
getShowHeader, const { getIsMobile } = useAppInject();
getUnFixedAndFull, const {
getHeaderTheme, getFixed,
} = useHeaderSetting(); getShowInsetHeaderRef,
getShowFullHeaderRef,
const { getFullContent } = useFullContent(); getHeaderTheme,
} = useHeaderSetting();
const { getShowMultipleTab } = useMultipleTabSetting();
const { getFullContent } = useFullContent();
const getShowTabs = computed(() => {
return unref(getShowMultipleTab) && !unref(getFullContent); const { getShowMultipleTab } = useMultipleTabSetting();
});
const getShowTabs = computed(() => {
const getPlaceholderDomStyle = computed( return unref(getShowMultipleTab) && !unref(getFullContent);
(): CSSProperties => { });
return {
height: `${unref(placeholderHeightRef)}px`, const getIsShowPlaceholderDom = computed(() => {
}; return unref(getFixed) || unref(getShowFullHeaderRef);
} });
);
const getWrapStyle = computed(
const getIsShowPlaceholderDom = computed(() => { (): CSSProperties => {
return unref(getFixed) || unref(getShowFullHeaderRef); const style: CSSProperties = {};
}); if (unref(getFixed)) {
style.width = unref(getIsMobile) ? '100%' : unref(getCalcContentWidth);
const getWrapStyle = computed( }
(): CSSProperties => { if (unref(getShowFullHeaderRef)) {
const style: CSSProperties = {}; style.top = `${HEADER_HEIGHT}px`;
if (unref(getFixed)) { }
style.width = unref(getIsMobile) ? '100%' : unref(getCalcContentWidth); return style;
}
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; const getIsFixed = computed(() => {
if (headerEl && !unref(getShowFullHeaderRef) && !unref(getSplit)) { return unref(getFixed) || unref(getShowFullHeaderRef);
height += headerEl.offsetHeight; });
}
if (tabEl) { const getPlaceholderDomStyle = computed(
height += tabEl.offsetHeight; (): CSSProperties => {
let height = 0;
if (unref(getShowFullHeaderRef) || !unref(getSplit)) {
height += HEADER_HEIGHT;
} }
if (unref(getShowMultipleTab)) {
if (fullHeaderEl && unref(getShowFullHeaderRef)) { height += TABS_HEIGHT;
const fullHeaderHeight = fullHeaderEl.offsetHeight;
height += fullHeaderHeight;
fullHeaderHeightRef.value = fullHeaderHeight;
} }
return {
placeholderHeightRef.value = height; height: `${height}px`,
}); };
}, }
{
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>
</>
); );
};
}, 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>
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
@header-trigger-prefix-cls: ~'@{namespace}-layout-header-trigger'; @header-trigger-prefix-cls: ~'@{namespace}-layout-header-trigger';
@header-prefix-cls: ~'@{namespace}-layout-header'; @header-prefix-cls: ~'@{namespace}-layout-header';
@locale-prefix-cls: ~'@{namespace}-app-locale-picker'; @locale-prefix-cls: ~'@{namespace}-app-locale-picker';
@breadcrumb-prefix-cls: ~'@{namespace}-layout-breadcrumb';
@logo-prefix-cls: ~'@{namespace}-app-logo';
.@{header-prefix-cls} { .@{header-prefix-cls} {
display: flex; display: flex;
...@@ -14,6 +16,30 @@ ...@@ -14,6 +16,30 @@
align-items: center; align-items: center;
justify-content: space-between; 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 { &--fixed {
position: fixed; position: fixed;
top: 0; top: 0;
...@@ -78,7 +104,7 @@ ...@@ -78,7 +104,7 @@
&-action { &-action {
display: flex; display: flex;
min-width: 200px; min-width: 180px;
padding-right: 12px; padding-right: 12px;
align-items: center; align-items: center;
......
...@@ -3,17 +3,17 @@ ...@@ -3,17 +3,17 @@
<!-- left start --> <!-- left start -->
<div :class="`${prefixCls}-left`"> <div :class="`${prefixCls}-left`">
<!-- logo --> <!-- logo -->
<AppLogo v-if="getShowHeaderLogo" :class="`${prefixCls}-logo`" :theme="getHeaderTheme" /> <AppLogo
v-if="getShowHeaderLogo || getIsMobile"
<LayoutTrigger :class="`${prefixCls}-logo`"
v-if="getShowContent && getShowHeaderTrigger"
:theme="getHeaderTheme" :theme="getHeaderTheme"
:sider="false"
/> />
<LayoutBreadcrumb <LayoutTrigger
v-if="getShowContent && getShowBread && !getIsMobile" v-if="(getShowContent && getShowHeaderTrigger && !getSplit) || getIsMobile"
:theme="getHeaderTheme" :theme="getHeaderTheme"
:sider="false"
/> />
<LayoutBreadcrumb v-if="getShowContent && getShowBread" :theme="getHeaderTheme" />
</div> </div>
<!-- left end --> <!-- left end -->
...@@ -30,15 +30,15 @@ ...@@ -30,15 +30,15 @@
<!-- action --> <!-- action -->
<div :class="`${prefixCls}-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" /> <UserDropDown :theme="getHeaderTheme" />
...@@ -123,7 +123,11 @@ ...@@ -123,7 +123,11 @@
const theme = unref(getHeaderTheme); const theme = unref(getHeaderTheme);
return [ return [
prefixCls, prefixCls,
{ [`${prefixCls}--fixed`]: props.fixed, [`${prefixCls}--${theme}`]: theme }, {
[`${prefixCls}--fixed`]: props.fixed,
[`${prefixCls}--mobile`]: unref(getIsMobile),
[`${prefixCls}--${theme}`]: theme,
},
]; ];
}); });
...@@ -145,6 +149,7 @@ ...@@ -145,6 +149,7 @@
getShowBread, getShowBread,
getShowContent, getShowContent,
getSplitType, getSplitType,
getSplit,
getMenuMode, getMenuMode,
getShowTopMenu, getShowTopMenu,
getShowLocale, getShowLocale,
......
<template> <template>
<Layout :class="prefixCls"> <Layout :class="prefixCls">
<LayoutFeatures /> <LayoutFeatures />
<LayoutHeader fixed ref="headerRef" v-if="getShowFullHeaderRef" /> <LayoutHeader fixed v-if="getShowFullHeaderRef" />
<Layout> <Layout>
<LayoutSideBar v-if="getShowSidebar" /> <LayoutSideBar v-if="getShowSidebar || getIsMobile" />
<Layout :class="`${prefixCls}__main`"> <Layout :class="`${prefixCls}__main`">
<LayoutMultipleHeader /> <LayoutMultipleHeader />
<LayoutContent /> <LayoutContent />
...@@ -14,21 +14,21 @@ ...@@ -14,21 +14,21 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, ref } from 'vue'; import { defineComponent } from 'vue';
import { Layout } from 'ant-design-vue'; import { Layout } from 'ant-design-vue';
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent'; import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
import LayoutHeader from './header/index.vue'; import LayoutHeader from './header/index.vue';
import LayoutContent from './content/index.vue'; import LayoutContent from './content/index.vue';
import LayoutSideBar from './sider'; import LayoutSideBar from './sider/index.vue';
import LayoutMultipleHeader from './header/LayoutMultipleHeader'; import LayoutMultipleHeader from './header/MultipleHeader.vue';
import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting'; import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting';
import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'; import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
import { useDesign } from '/@/hooks/web/useDesign'; import { useDesign } from '/@/hooks/web/useDesign';
import { createLayoutContext } from './useLayoutContext';
import { registerGlobComp } from '/@/components/registerGlobComp'; import { registerGlobComp } from '/@/components/registerGlobComp';
import { useAppInject } from '/@/hooks/web/useAppInject';
export default defineComponent({ export default defineComponent({
name: 'DefaultLayout', name: 'DefaultLayout',
...@@ -47,11 +47,9 @@ ...@@ -47,11 +47,9 @@
// default layout It is loaded after login. So it won’t be packaged to the first screen // default layout It is loaded after login. So it won’t be packaged to the first screen
registerGlobComp(); registerGlobComp();
const headerRef = ref<ComponentRef>(null);
const { prefixCls } = useDesign('default-layout'); const { prefixCls } = useDesign('default-layout');
createLayoutContext({ fullHeader: headerRef }); const { getIsMobile } = useAppInject();
const { getShowFullHeaderRef } = useHeaderSetting(); const { getShowFullHeaderRef } = useHeaderSetting();
...@@ -60,8 +58,8 @@ ...@@ -60,8 +58,8 @@
return { return {
getShowFullHeaderRef, getShowFullHeaderRef,
getShowSidebar, getShowSidebar,
headerRef,
prefixCls, prefixCls,
getIsMobile,
}; };
}, },
}); });
......
@import (reference) '../../../design/index.less'; @import (reference) '../../../design/index.less';
.layout-menu { @prefix-cls: ~'@{namespace}-layout-menu';
&__logo { @logo-prefix-cls: ~'@{namespace}-app-logo';
.@{prefix-cls} {
&-logo {
height: @header-height; height: @header-height;
padding: 10px 4px 10px 10px; padding: 10px 4px 10px 10px;
...@@ -10,4 +13,12 @@ ...@@ -10,4 +13,12 @@
height: @logo-width; height: @logo-width;
} }
} }
&--mobile {
.@{logo-prefix-cls} {
&__title {
opacity: 1;
}
}
}
} }
import './index.less'; 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 { BasicMenu } from '/@/components/Menu';
import { AppLogo } from '/@/components/Application'; import { AppLogo } from '/@/components/Application';
...@@ -17,7 +17,8 @@ import { openWindow } from '/@/utils'; ...@@ -17,7 +17,8 @@ import { openWindow } from '/@/utils';
import { propTypes } from '/@/utils/propTypes'; import { propTypes } from '/@/utils/propTypes';
import { isUrl } from '/@/utils/is'; import { isUrl } from '/@/utils/is';
import { useRootSetting } from '/@/hooks/setting/useRootSetting'; 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({ export default defineComponent({
name: 'LayoutMenu', name: 'LayoutMenu',
...@@ -50,9 +51,15 @@ export default defineComponent({ ...@@ -50,9 +51,15 @@ export default defineComponent({
} = useMenuSetting(); } = useMenuSetting();
const { getShowLogo } = useRootSetting(); const { getShowLogo } = useRootSetting();
const { prefixCls } = useDesign('layout-menu');
const { menusRef } = useSplitMenu(toRef(props, 'splitType')); 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)); const getComputedMenuTheme = computed(() => props.theme || unref(getMenuTheme));
...@@ -69,6 +76,16 @@ export default defineComponent({ ...@@ -69,6 +76,16 @@ export default defineComponent({
}; };
} }
); );
const getLogoClass = computed(() => {
return [
`${prefixCls}-logo`,
unref(getComputedMenuTheme),
{
[`${prefixCls}--mobile`]: unref(getIsMobile),
},
];
});
/** /**
* click menu * click menu
* @param menu * @param menu
...@@ -91,12 +108,12 @@ export default defineComponent({ ...@@ -91,12 +108,12 @@ export default defineComponent({
} }
function renderHeader() { function renderHeader() {
if (!unref(getIsShowLogo)) return null; if (!unref(getIsShowLogo) && !unref(getIsMobile)) return null;
return ( return (
<AppLogo <AppLogo
showTitle={!unref(getCollapsed)} showTitle={!unref(getCollapsed)}
class={[`layout-menu__logo`, unref(getComputedMenuTheme)]} class={unref(getLogoClass)}
theme={unref(getComputedMenuTheme)} theme={unref(getComputedMenuTheme)}
/> />
); );
...@@ -128,7 +145,6 @@ export default defineComponent({ ...@@ -128,7 +145,6 @@ export default defineComponent({
) : ( ) : (
renderMenu() renderMenu()
)} )}
;
</> </>
); );
}; };
......
...@@ -10,11 +10,13 @@ import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'; ...@@ -10,11 +10,13 @@ import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
import { getChildrenMenus, getCurrentParentPath, getMenus, getShallowMenus } from '/@/router/menus'; import { getChildrenMenus, getCurrentParentPath, getMenus, getShallowMenus } from '/@/router/menus';
import { permissionStore } from '/@/store/modules/permission'; import { permissionStore } from '/@/store/modules/permission';
import { useAppInject } from '/@/hooks/web/useAppInject';
export function useSplitMenu(splitType: Ref<MenuSplitTyeEnum>) { export function useSplitMenu(splitType: Ref<MenuSplitTyeEnum>) {
// Menu array // Menu array
const menusRef = ref<Menu[]>([]); const menusRef = ref<Menu[]>([]);
const { currentRoute } = useRouter(); const { currentRoute } = useRouter();
const { getIsMobile } = useAppInject();
const { setMenuSetting, getIsHorizontal, getSplit } = useMenuSetting(); const { setMenuSetting, getIsHorizontal, getSplit } = useMenuSetting();
const [throttleHandleSplitLeftMenu] = useThrottle(handleSplitLeftMenu, 50); const [throttleHandleSplitLeftMenu] = useThrottle(handleSplitLeftMenu, 50);
...@@ -36,7 +38,7 @@ export function useSplitMenu(splitType: Ref<MenuSplitTyeEnum>) { ...@@ -36,7 +38,7 @@ export function useSplitMenu(splitType: Ref<MenuSplitTyeEnum>) {
watch( watch(
[() => unref(currentRoute).path, () => unref(splitType)], [() => unref(currentRoute).path, () => unref(splitType)],
async ([path]: [string, MenuSplitTyeEnum]) => { async ([path]: [string, MenuSplitTyeEnum]) => {
if (unref(splitNotLeft)) return; if (unref(splitNotLeft) || unref(getIsMobile)) return;
const parentPath = await getCurrentParentPath(path); const parentPath = await getCurrentParentPath(path);
parentPath && throttleHandleSplitLeftMenu(parentPath); parentPath && throttleHandleSplitLeftMenu(parentPath);
...@@ -65,24 +67,24 @@ export function useSplitMenu(splitType: Ref<MenuSplitTyeEnum>) { ...@@ -65,24 +67,24 @@ export function useSplitMenu(splitType: Ref<MenuSplitTyeEnum>) {
// Handle left menu split // Handle left menu split
async function handleSplitLeftMenu(parentPath: string) { async function handleSplitLeftMenu(parentPath: string) {
if (unref(getSplitLeft)) return; if (unref(getSplitLeft) || unref(getIsMobile)) return;
// spilt mode left // spilt mode left
const children = await getChildrenMenus(parentPath); const children = await getChildrenMenus(parentPath);
if (!children) { if (!children) {
setMenuSetting({ hidden: false }); setMenuSetting({ hidden: true });
menusRef.value = []; menusRef.value = [];
return; return;
} }
setMenuSetting({ hidden: true }); setMenuSetting({ hidden: false });
menusRef.value = children; menusRef.value = children;
} }
// get menus // get menus
async function genMenus() { async function genMenus() {
// normal mode // normal mode
if (unref(normalType)) { if (unref(normalType) || unref(getIsMobile)) {
menusRef.value = await getMenus(); menusRef.value = await getMenus();
return; return;
} }
......
<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>
...@@ -12,6 +12,7 @@ import { useTrigger, useDragLine, useSiderEvent } from './useLayoutSider'; ...@@ -12,6 +12,7 @@ import { useTrigger, useDragLine, useSiderEvent } from './useLayoutSider';
import { useAppInject } from '/@/hooks/web/useAppInject'; import { useAppInject } from '/@/hooks/web/useAppInject';
import { useDesign } from '/@/hooks/web/useDesign'; import { useDesign } from '/@/hooks/web/useDesign';
import DragBar from './DragBar.vue';
export default defineComponent({ export default defineComponent({
name: 'LayoutSideBar', name: 'LayoutSideBar',
setup() { setup() {
...@@ -31,11 +32,11 @@ export default defineComponent({ ...@@ -31,11 +32,11 @@ export default defineComponent({
const { prefixCls } = useDesign('layout-sideBar'); const { prefixCls } = useDesign('layout-sideBar');
const { getTriggerAttr, getTriggerSlot } = useTrigger();
const { getIsMobile } = useAppInject(); const { getIsMobile } = useAppInject();
const { renderDragLine } = useDragLine(sideRef, dragBarRef); const { getTriggerAttr, getTriggerSlot } = useTrigger(getIsMobile);
useDragLine(sideRef, dragBarRef);
const { getCollapsedWidth, onBreakpointChange, onCollapseChange } = useSiderEvent(); const { getCollapsedWidth, onBreakpointChange, onCollapseChange } = useSiderEvent();
...@@ -48,7 +49,7 @@ export default defineComponent({ ...@@ -48,7 +49,7 @@ export default defineComponent({
}); });
const showClassSideBarRef = computed(() => { const showClassSideBarRef = computed(() => {
return unref(getSplit) ? unref(getMenuHidden) : true; return unref(getSplit) ? !unref(getMenuHidden) : true;
}); });
const getSiderClass = computed(() => { const getSiderClass = computed(() => {
...@@ -57,7 +58,7 @@ export default defineComponent({ ...@@ -57,7 +58,7 @@ export default defineComponent({
{ {
[`${prefixCls}--fixed`]: unref(getMenuFixed), [`${prefixCls}--fixed`]: unref(getMenuFixed),
hidden: !unref(showClassSideBarRef), hidden: !unref(showClassSideBarRef),
[`${prefixCls}--mix`]: unref(getIsMixMode), [`${prefixCls}--mix`]: unref(getIsMixMode) && !unref(getIsMobile),
}, },
]; ];
}); });
...@@ -84,7 +85,7 @@ export default defineComponent({ ...@@ -84,7 +85,7 @@ export default defineComponent({
menuMode={unref(getMode)} menuMode={unref(getMode)}
splitType={unref(getSplitType)} splitType={unref(getSplitType)}
/> />
{renderDragLine()} <DragBar ref={dragBarRef} />
</> </>
); );
} }
...@@ -101,7 +102,7 @@ export default defineComponent({ ...@@ -101,7 +102,7 @@ export default defineComponent({
collapsible collapsible
class={unref(getSiderClass)} class={unref(getSiderClass)}
width={unref(getMenuWidth)} width={unref(getMenuWidth)}
collapsed={unref(getCollapsed)} collapsed={unref(getIsMobile) ? false : unref(getCollapsed)}
collapsedWidth={unref(getCollapsedWidth)} collapsedWidth={unref(getCollapsedWidth)}
theme={unref(getMenuTheme)} theme={unref(getMenuTheme)}
onCollapse={onCollapseChange} onCollapse={onCollapseChange}
......
...@@ -44,27 +44,6 @@ ...@@ -44,27 +44,6 @@
z-index: 10; 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 { & .ant-layout-sider-trigger {
height: 36px; height: 36px;
line-height: 36px; line-height: 36px;
......
<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>
...@@ -42,12 +42,17 @@ export function useSiderEvent() { ...@@ -42,12 +42,17 @@ export function useSiderEvent() {
/** /**
* Handle related operations of menu folding * Handle related operations of menu folding
*/ */
export function useTrigger() { export function useTrigger(getIsMobile: Ref<boolean>) {
const { getTrigger } = useMenuSetting(); const { getTrigger, getSplit } = useMenuSetting();
const showTrigger = computed(() => { const showTrigger = computed(() => {
const trigger = unref(getTrigger); 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(() => { const getTriggerAttr = computed(() => {
...@@ -77,14 +82,7 @@ export function useTrigger() { ...@@ -77,14 +82,7 @@ export function useTrigger() {
* @param dragBarRef * @param dragBarRef
*/ */
export function useDragLine(siderRef: Ref<any>, dragBarRef: Ref<any>) { export function useDragLine(siderRef: Ref<any>, dragBarRef: Ref<any>) {
const { getMiniWidthNumber, getCollapsed, setMenuSetting, getCanDrag } = useMenuSetting(); const { getMiniWidthNumber, getCollapsed, setMenuSetting } = useMenuSetting();
const getDragBarStyle = computed(() => {
if (unref(getCollapsed)) {
return { left: `${unref(getMiniWidthNumber)}px` };
}
return {};
});
onMounted(() => { onMounted(() => {
nextTick(() => { nextTick(() => {
...@@ -93,16 +91,6 @@ export function useDragLine(siderRef: Ref<any>, dragBarRef: Ref<any>) { ...@@ -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) { function handleMouseMove(ele: HTMLElement, wrap: HTMLElement, clientX: number) {
document.onmousemove = function (innerE) { document.onmousemove = function (innerE) {
let iT = (ele as any).left + (innerE.clientX - clientX); let iT = (ele as any).left + (innerE.clientX - clientX);
...@@ -138,21 +126,22 @@ export function useDragLine(siderRef: Ref<any>, dragBarRef: Ref<any>) { ...@@ -138,21 +126,22 @@ export function useDragLine(siderRef: Ref<any>, dragBarRef: Ref<any>) {
} }
function changeWrapWidth() { function changeWrapWidth() {
const ele = unref(dragBarRef) as any; const ele = unref(dragBarRef)?.$el;
if (!ele) {
return;
}
const side = unref(siderRef); const side = unref(siderRef);
const wrap = (side || {}).$el; const wrap = (side || {}).$el;
ele && ele.onmousedown = (e: any) => {
(ele.onmousedown = (e: any) => { wrap.style.transition = 'unset';
wrap.style.transition = 'unset'; const clientX = e?.clientX;
const clientX = e?.clientX; ele.left = ele.offsetLeft;
ele.left = ele.offsetLeft; handleMouseMove(ele, wrap, clientX);
handleMouseMove(ele, wrap, clientX); removeMouseup(ele);
removeMouseup(ele); ele.setCapture?.();
ele.setCapture?.(); return false;
return false; };
});
} }
return { renderDragLine }; return {};
} }
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);
}
export default { export default {
search: 'Search', search: 'Search',
cancel: 'Cancel',
searchNotData: 'No search results yet', searchNotData: 'No search results yet',
toSearch: 'to search', toSearch: 'to search',
toNavigate: 'to navigate', toNavigate: 'to navigate',
......
export default { export default {
search: '搜索', search: '搜索',
cancel: '取消',
searchNotData: '暂无搜索结果', searchNotData: '暂无搜索结果',
toSearch: '确认', toSearch: '确认',
toNavigate: '切换', toNavigate: '切换',
......
...@@ -13,8 +13,9 @@ const key = Symbol(); ...@@ -13,8 +13,9 @@ const key = Symbol();
let lastChangeTab: RouteLocationNormalized; let lastChangeTab: RouteLocationNormalized;
export function setLastChangeTab(lastChangeRoute: RouteLocationNormalized) { export function setLastChangeTab(lastChangeRoute: RouteLocationNormalized) {
mitt.emit(key, getRoute(lastChangeRoute)); const r = getRoute(lastChangeRoute);
lastChangeTab = getRoute(lastChangeRoute); mitt.emit(key, r);
lastChangeTab = r;
} }
export function listenerLastChangeTab( export function listenerLastChangeTab(
......
...@@ -51,6 +51,5 @@ if (isDevMode()) { ...@@ -51,6 +51,5 @@ if (isDevMode()) {
if (isProdMode() && isUseMock()) { if (isProdMode() && isUseMock()) {
setupProdMockServer(); setupProdMockServer();
} }
// Used to share app instances in other modules // Used to share app instances in other modules
setApp(app); setApp(app);
import { AppRouteModule } from '/@/router/types.d'; import { AppRouteModule } from '/@/router/types.d';
import type { MenuModule, Menu, AppRouteRecordRaw } from '/@/router/types'; import type { MenuModule, Menu, AppRouteRecordRaw } from '/@/router/types';
import { findPath, forEach, treeMap, treeToList } from '/@/utils/helper/treeHelper'; import { findPath, forEach, treeMap } from '/@/utils/helper/treeHelper';
import { cloneDeep } from 'lodash-es'; import { cloneDeep } from 'lodash-es';
import { isUrl } from '/@/utils/is'; import { isUrl } from '/@/utils/is';
...@@ -10,10 +10,6 @@ export function getAllParentPath(treeData: any[], path: string) { ...@@ -10,10 +10,6 @@ export function getAllParentPath(treeData: any[], path: string) {
return (menuList || []).map((item) => item.path); return (menuList || []).map((item) => item.path);
} }
export function flatMenus(menus: Menu[]) {
return treeToList(menus);
}
// 拼接父级路径 // 拼接父级路径
function joinParentPath(list: any, node: any) { function joinParentPath(list: any, node: any) {
let allPaths = getAllParentPath(list, node.path); let allPaths = getAllParentPath(list, node.path);
......
...@@ -5,6 +5,10 @@ import { getParentLayout, LAYOUT } from '/@/router/constant'; ...@@ -5,6 +5,10 @@ import { getParentLayout, LAYOUT } from '/@/router/constant';
import dynamicImport from './dynamicImport'; import dynamicImport from './dynamicImport';
import { cloneDeep } from 'lodash-es'; import { cloneDeep } from 'lodash-es';
export type LayoutMapKey = 'LAYOUT';
const LayoutMap = new Map<LayoutMapKey, () => Promise<typeof import('*.vue')>>();
// 动态引入 // 动态引入
function asyncImportRoute(routes: AppRouteRecordRaw[] | undefined) { function asyncImportRoute(routes: AppRouteRecordRaw[] | undefined) {
if (!routes) return; if (!routes) return;
...@@ -20,16 +24,14 @@ function asyncImportRoute(routes: AppRouteRecordRaw[] | undefined) { ...@@ -20,16 +24,14 @@ function asyncImportRoute(routes: AppRouteRecordRaw[] | undefined) {
}); });
} }
function getLayoutComp(comp: string) {
return comp === 'LAYOUT' ? LAYOUT : '';
}
// Turn background objects into routing objects // Turn background objects into routing objects
export function transformObjToRoute<T = AppRouteModule>(routeList: AppRouteModule[]): T[] { export function transformObjToRoute<T = AppRouteModule>(routeList: AppRouteModule[]): T[] {
LayoutMap.set('LAYOUT', LAYOUT);
routeList.forEach((route) => { routeList.forEach((route) => {
if (route.component) { if (route.component) {
if ((route.component as string).toUpperCase() === 'LAYOUT') { if ((route.component as string).toUpperCase() === 'LAYOUT') {
route.component = getLayoutComp(route.component); route.component = LayoutMap.get(route.component);
} else { } else {
route.children = [cloneDeep(route)]; route.children = [cloneDeep(route)];
route.component = LAYOUT; route.component = LAYOUT;
...@@ -46,16 +48,6 @@ export function transformObjToRoute<T = AppRouteModule>(routeList: AppRouteModul ...@@ -46,16 +48,6 @@ export function transformObjToRoute<T = AppRouteModule>(routeList: AppRouteModul
return (routeList as unknown) as T[]; return (routeList as unknown) as T[];
} }
export function getParams(data: any = {}) {
const { params = {} } = data;
let ret = '';
Object.keys(params).forEach((key) => {
const p = params[key];
ret += `/${p}`;
});
return ret;
}
// Return to the new routing structure, not affected by the original example // Return to the new routing structure, not affected by the original example
export function getRoute(route: RouteLocationNormalized): RouteLocationNormalized { export function getRoute(route: RouteLocationNormalized): RouteLocationNormalized {
if (!route) return route; if (!route) return route;
......
import type { Menu, MenuModule } from '/@/router/types'; import type { Menu, MenuModule } from '/@/router/types';
import type { RouteRecordNormalized } from 'vue-router'; import type { RouteRecordNormalized } from 'vue-router';
import { appStore } from '/@/store/modules/app'; import { appStore } from '/@/store/modules/app';
import { permissionStore } from '/@/store/modules/permission'; import { permissionStore } from '/@/store/modules/permission';
import { transformMenuModule, flatMenus, getAllParentPath } from '/@/router/helper/menuHelper'; import { transformMenuModule, getAllParentPath } from '/@/router/helper/menuHelper';
import { filter } from '/@/utils/helper/treeHelper'; import { filter } from '/@/utils/helper/treeHelper';
import router from '/@/router'; import router from '/@/router';
import { PermissionModeEnum } from '/@/enums/appEnum'; import { PermissionModeEnum } from '/@/enums/appEnum';
...@@ -10,6 +11,8 @@ import { pathToRegexp } from 'path-to-regexp'; ...@@ -10,6 +11,8 @@ import { pathToRegexp } from 'path-to-regexp';
import modules from 'globby!/@/router/menus/modules/**/*.@(ts)'; import modules from 'globby!/@/router/menus/modules/**/*.@(ts)';
const reg = /(((https?:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+(?::\d+)?|(?:www.|[-;:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%@.\w_]*)#?(?:[\w]*))?)$/;
const menuModules: MenuModule[] = []; const menuModules: MenuModule[] = [];
Object.keys(modules).forEach((key) => { Object.keys(modules).forEach((key) => {
...@@ -38,18 +41,9 @@ const staticMenus: Menu[] = []; ...@@ -38,18 +41,9 @@ const staticMenus: Menu[] = [];
async function getAsyncMenus() { async function getAsyncMenus() {
// 前端角色控制菜单 直接取菜单文件 // 前端角色控制菜单 直接取菜单文件
if (!isBackMode()) { return !isBackMode() ? staticMenus : permissionStore.getBackMenuListState;
return staticMenus;
}
return permissionStore.getBackMenuListState;
} }
// 获取深层扁平化菜单
export const getFlatMenus = async (): Promise<Menu[]> => {
const menus = await getAsyncMenus();
return flatMenus(menus);
};
// 获取菜单 树级 // 获取菜单 树级
export const getMenus = async (): Promise<Menu[]> => { export const getMenus = async (): Promise<Menu[]> => {
const menus = await getAsyncMenus(); const menus = await getAsyncMenus();
...@@ -61,7 +55,7 @@ export const getMenus = async (): Promise<Menu[]> => { ...@@ -61,7 +55,7 @@ export const getMenus = async (): Promise<Menu[]> => {
export async function getCurrentParentPath(currentPath: string) { export async function getCurrentParentPath(currentPath: string) {
const menus = await getAsyncMenus(); const menus = await getAsyncMenus();
const allParentPath = await getAllParentPath(menus, currentPath); const allParentPath = await getAllParentPath(menus, currentPath);
return allParentPath[0]; return allParentPath?.[0];
} }
// 获取1级菜单,删除children // 获取1级菜单,删除children
...@@ -81,27 +75,24 @@ export async function getChildrenMenus(parentPath: string) { ...@@ -81,27 +75,24 @@ export async function getChildrenMenus(parentPath: string) {
return parent.children; return parent.children;
} }
// 扁平化children
export async function getFlatChildrenMenus(children: Menu[]) {
return flatMenus(children);
}
// 通用过滤方法 // 通用过滤方法
function basicFilter(routes: RouteRecordNormalized[]) { function basicFilter(routes: RouteRecordNormalized[]) {
return (menu: Menu) => { return (menu: Menu) => {
const matchRoute = routes.find((route) => { const matchRoute = routes.find((route) => {
if (route.meta.externalLink) { const match = route.path.match(reg)?.[0];
if (match && match === menu.path) {
return true; return true;
} }
if (route.meta) { if (route.meta?.carryParam) {
if (route.meta.carryParam) { return pathToRegexp(route.path).test(menu.path);
return pathToRegexp(route.path).test(menu.path);
}
if (route.meta.ignoreAuth) return true;
} }
const isSame = route.path === menu.path;
if (!isSame) return false;
if (route.meta?.ignoreAuth) return true;
return route.path === menu.path; return isSame || pathToRegexp(route.path).test(menu.path);
}); });
if (!matchRoute) return false; if (!matchRoute) return false;
......
...@@ -38,7 +38,6 @@ const iframe: AppRouteModule = { ...@@ -38,7 +38,6 @@ const iframe: AppRouteModule = {
name: 'DocExternal', name: 'DocExternal',
component: IFrame, component: IFrame,
meta: { meta: {
externalLink: true,
title: t('routes.demo.iframe.docExternal'), title: t('routes.demo.iframe.docExternal'),
}, },
}, },
......
...@@ -15,9 +15,6 @@ export interface RouteMeta { ...@@ -15,9 +15,6 @@ export interface RouteMeta {
// icon on tab // icon on tab
icon?: string; icon?: string;
// Jump address // Jump address
frameSrc?: string;
// Outer link jump address
externalLink?: boolean;
// current page transition // current page transition
transitionName?: string; transitionName?: string;
......
...@@ -89,7 +89,7 @@ const setting: ProjectConfig = { ...@@ -89,7 +89,7 @@ const setting: ProjectConfig = {
// Whether to show no dom // Whether to show no dom
show: true, show: true,
// Whether to show dom // Whether to show dom
hidden: true, hidden: false,
// Menu width // Menu width
menuWidth: 210, menuWidth: 210,
// Menu mode // Menu mode
......
...@@ -50,7 +50,7 @@ export function isRegExp(val: unknown): val is RegExp { ...@@ -50,7 +50,7 @@ export function isRegExp(val: unknown): val is RegExp {
return is(val, 'RegExp'); return is(val, 'RegExp');
} }
export function isArray(val: unknown): val is Array<any> { export function isArray(val: any): val is Array<any> {
return val && Array.isArray(val); return val && Array.isArray(val);
} }
......
...@@ -221,25 +221,35 @@ ...@@ -221,25 +221,35 @@
font-size: 23em; font-size: 23em;
} }
@media (min-width: @screen-sm-max) and (max-width: @screen-md-max) { @media (min-width: @screen-sm-max) and (max-width: @screen-md-max) {
font-size: 19em; height: 50%;
font-size: 12em;
border-radius: 10px;
.meridiem {
font-size: 20px;
}
} }
@media (min-width: @screen-xs-max) and (max-width: @screen-sm-max) { @media (min-width: @screen-xs-max) and (max-width: @screen-sm-max) {
font-size: 13em; font-size: 13em;
} }
@media (max-width: @screen-xs) { @media (max-width: @screen-xs) {
height: 50%; height: 30%;
font-size: 6em; font-size: 5em;
border-radius: 20px; border-radius: 10px;
.meridiem {
font-size: 14px;
}
} }
} }
&__footer-date { &__footer-date {
position: absolute; position: absolute;
bottom: 20px; bottom: 20px;
left: 50%; width: 100%;
font-family: helvetica; font-family: helvetica;
color: #bababa; color: #bababa;
transform: translate(-50%, 0); text-align: center;
.time { .time {
font-size: 50px; font-size: 50px;
......
...@@ -1051,43 +1051,43 @@ ...@@ -1051,43 +1051,43 @@
resolved "https://registry.npmjs.org/@iconify/json/-/json-1.1.272.tgz#27c7caee9764e0304161261ec08ffc2794944b66" resolved "https://registry.npmjs.org/@iconify/json/-/json-1.1.272.tgz#27c7caee9764e0304161261ec08ffc2794944b66"
integrity sha512-FyiTc7UiXJ5cDfk09lv70sYOSi5uLyK+a0LnF1KgWmofkikL06p98ksNRN7stmHryOYarSy75xgi6MbgAwtltQ== integrity sha512-FyiTc7UiXJ5cDfk09lv70sYOSi5uLyK+a0LnF1KgWmofkikL06p98ksNRN7stmHryOYarSy75xgi6MbgAwtltQ==
"@intlify/core@9.0.0-beta.12": "@intlify/core-base@9.0.0-beta.13":
version "9.0.0-beta.12" version "9.0.0-beta.13"
resolved "https://registry.npmjs.org/@intlify/core/-/core-9.0.0-beta.12.tgz#f7d2d09060b8e00ae37157e00a0daa1c86290802" resolved "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.0.0-beta.13.tgz#fb6bc278209cb7bef44853a42160fedb0560c3f8"
integrity sha512-0wdOS9d0ZEvGkbNIdaxEHQQOfAIuhv1Q8CSpNImThh8ZDD+5Sa38wTerHBO0/Rk0HfHUP/hjPqbxxRqITmSo1g== integrity sha512-ukImWV+QvRmNZtCTLrSW391z46eMuBheCMPZh801nM3v0Dosfu2PtWO5/z8Q9Bsom4Q+PNQ5eBtOQj2yCAhVEA==
dependencies: dependencies:
"@intlify/message-compiler" "9.0.0-beta.12" "@intlify/message-compiler" "9.0.0-beta.13"
"@intlify/message-resolver" "9.0.0-beta.12" "@intlify/message-resolver" "9.0.0-beta.13"
"@intlify/runtime" "9.0.0-beta.12" "@intlify/runtime" "9.0.0-beta.13"
"@intlify/shared" "9.0.0-beta.12" "@intlify/shared" "9.0.0-beta.13"
"@intlify/message-compiler@9.0.0-beta.12": "@intlify/message-compiler@9.0.0-beta.13":
version "9.0.0-beta.12" version "9.0.0-beta.13"
resolved "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.0.0-beta.12.tgz#836a49cfd057ecb2c536680cc01aa16693211891" resolved "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.0.0-beta.13.tgz#3b8ddcb2be3f80b28c6e4f6028c0b3ec4e709849"
integrity sha512-EMzBDBIsFvWV9w0tRAHzn2BD1C7nkJkXYwDWinROmoL6C4jgKUgon+9Uxp7lV0H1E+7hUfhGj6zHdtJrwFhH+g== integrity sha512-1z7716InFM8FdTAz64wqZvFuT4wL7WKF63v+vUEW4s9FLoL0U+xIccor9P5XHAvvG1gPMH/Zxd0deg/ULZ1Mcg==
dependencies: dependencies:
"@intlify/message-resolver" "9.0.0-beta.12" "@intlify/message-resolver" "9.0.0-beta.13"
"@intlify/shared" "9.0.0-beta.12" "@intlify/shared" "9.0.0-beta.13"
source-map "0.6.1" source-map "0.6.1"
"@intlify/message-resolver@9.0.0-beta.12": "@intlify/message-resolver@9.0.0-beta.13":
version "9.0.0-beta.12" version "9.0.0-beta.13"
resolved "https://registry.npmjs.org/@intlify/message-resolver/-/message-resolver-9.0.0-beta.12.tgz#98cf346f5da0fdf3408ba132c24841295a4e02db" resolved "https://registry.npmjs.org/@intlify/message-resolver/-/message-resolver-9.0.0-beta.13.tgz#ae6de0bf0e54093160442d465e719bf03fd0f146"
integrity sha512-i8bmWzhiBH59YED3SXqvdUfwecl7OUPOU/8yvfdhg2rXuZ4e2chCPnLpPafXz6bi88HcRsWF4aRGlpwDVDYadg== integrity sha512-mR1eSpRtB4jh11TpQTUyzjEwqZ6D30mJYREEfSrl5YKfUKwDQrulrOaIO8T5gVQG2m09vfxJHVrgfJ2hR8z/0Q==
"@intlify/runtime@9.0.0-beta.12": "@intlify/runtime@9.0.0-beta.13":
version "9.0.0-beta.12" version "9.0.0-beta.13"
resolved "https://registry.npmjs.org/@intlify/runtime/-/runtime-9.0.0-beta.12.tgz#647a62a326d92690569798ef046d29e8daa25c96" resolved "https://registry.npmjs.org/@intlify/runtime/-/runtime-9.0.0-beta.13.tgz#8deff103ee6982c6d531314e9f965b90768d8a27"
integrity sha512-4ucZHqk/VGhrQEgu9xU5tE/sJTNfqKBhQtaXyEgYHchL9PvLoS1HFwPjABHvWjo3aVcv4d2cGtUPBwH4oLROKA== integrity sha512-hcb3sg75SokuzNDG8IC6PJmwjsS/xdgevd99UNG1zKb7s5qFFb90ApvPDpiH0+R9TMQe11fZqg5dyrVBKqAV4A==
dependencies: dependencies:
"@intlify/message-compiler" "9.0.0-beta.12" "@intlify/message-compiler" "9.0.0-beta.13"
"@intlify/message-resolver" "9.0.0-beta.12" "@intlify/message-resolver" "9.0.0-beta.13"
"@intlify/shared" "9.0.0-beta.12" "@intlify/shared" "9.0.0-beta.13"
"@intlify/shared@9.0.0-beta.12": "@intlify/shared@9.0.0-beta.13":
version "9.0.0-beta.12" version "9.0.0-beta.13"
resolved "https://registry.npmjs.org/@intlify/shared/-/shared-9.0.0-beta.12.tgz#e939575bc4047411b9fc65347779f5b3173c1130" resolved "https://registry.npmjs.org/@intlify/shared/-/shared-9.0.0-beta.13.tgz#2d93d695f19fd699ea8b336066f9d6dfc185f094"
integrity sha512-XtHAzQ2KBcdN0Khc7ZDCo5GnKQK4Vv0GKD1BplCWntpA2d5XqjdDpFuKumvbiOjPvYtuCFnksJU0OgJiCWG+KQ== integrity sha512-/rqC3YEGHs3uu3XSsF1zdBKJb+on34Yn8Z58K3YxJsFxKPHa8mH73EUtN79hTZWh6Js4zEa/WsCgZCM62b8eJA==
"@koa/cors@^3.1.0": "@koa/cors@^3.1.0":
version "3.1.0" version "3.1.0"
...@@ -8256,16 +8256,13 @@ vue-eslint-parser@^7.3.0: ...@@ -8256,16 +8256,13 @@ vue-eslint-parser@^7.3.0:
esquery "^1.0.1" esquery "^1.0.1"
lodash "^4.17.15" lodash "^4.17.15"
vue-i18n@^9.0.0-beta.12: vue-i18n@^9.0.0-beta.13:
version "9.0.0-beta.12" version "9.0.0-beta.13"
resolved "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.0.0-beta.12.tgz#f6e2fc1cc366b8f16aa4754642931e937ebde303" resolved "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.0.0-beta.13.tgz#89cf5dd1566025f441132231d15ed621ef70ba96"
integrity sha512-hDnr+GsIGCIKRtZsdDczkhqyzbpLuPgEkH5bQyMzrKTLelXipLvIVmUCAsSjyR7xMHDCwP6AwVTIZwk6ENXkwg== integrity sha512-ZN6r5ITODu9NYAAbe1IGVUkNeamuleaXTLn5NMn/YZQ+5NSjDjysyVZVLkVOEOIw6bT2tLveyjsWlAZBVtfcPw==
dependencies: dependencies:
"@intlify/core" "9.0.0-beta.12" "@intlify/core-base" "9.0.0-beta.13"
"@intlify/message-compiler" "9.0.0-beta.12" "@intlify/shared" "9.0.0-beta.13"
"@intlify/message-resolver" "9.0.0-beta.12"
"@intlify/runtime" "9.0.0-beta.12"
"@intlify/shared" "9.0.0-beta.12"
"@vue/devtools-api" "^6.0.0-beta.2" "@vue/devtools-api" "^6.0.0-beta.2"
vue-router@^4.0.1: vue-router@^4.0.1:
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论