mirror of
https://github.com/WeBankFinTech/fes.js.git
synced 2025-04-05 19:41:57 +08:00
398 lines
13 KiB
Vue
398 lines
13 KiB
Vue
<template>
|
|
<FLayout class="main-layout">
|
|
<template v-if="currentNavigation === 'side'">
|
|
<FAside
|
|
v-model:collapsed="collapsedRef"
|
|
:fixed="isFixedSidebar"
|
|
:width="`${sideWidth}px`"
|
|
class="layout-aside"
|
|
collapsible
|
|
:inverted="theme === 'dark'"
|
|
>
|
|
<div class="layout-logo">
|
|
<img v-if="logo" :src="logo" class="logo-img">
|
|
<div v-if="title" class="logo-name">
|
|
{{ title }}
|
|
</div>
|
|
</div>
|
|
<LayoutMenu
|
|
class="layout-menu"
|
|
:menus="menus"
|
|
:collapsed="collapsedRef"
|
|
mode="vertical"
|
|
:inverted="theme === 'dark'"
|
|
:expanded-keys="menuProps?.expandedKeys"
|
|
:default-expand-all="menuProps?.defaultExpandAll"
|
|
:accordion="menuProps?.accordion"
|
|
/>
|
|
</FAside>
|
|
<FLayout :fixed="isFixedSidebar" :style="sideStyleRef">
|
|
<FHeader ref="headerRef" class="layout-header" :fixed="currentFixedHeaderRef">
|
|
<div class="layout-header-custom">
|
|
<slot name="renderCustom" :menus="menus" />
|
|
</div>
|
|
<template v-if="locale">
|
|
<slot name="locale" />
|
|
</template>
|
|
</FHeader>
|
|
<FLayout :embedded="!multiTabs" :fixed="currentFixedHeaderRef" :style="headerStyleRef">
|
|
<FMain class="layout-main">
|
|
<MultiTabProvider :multi-tabs="multiTabs" />
|
|
</FMain>
|
|
<FFooter v-if="footer" class="layout-footer">
|
|
{{ footer }}
|
|
</FFooter>
|
|
</FLayout>
|
|
</FLayout>
|
|
</template>
|
|
<template v-else-if="currentNavigation === 'left-right'">
|
|
<FAside
|
|
v-model:collapsed="collapsedRef"
|
|
:fixed="isFixedSidebar"
|
|
:width="`${sideWidth}px`"
|
|
class="layout-aside"
|
|
collapsible
|
|
:inverted="theme === 'dark'"
|
|
>
|
|
<div class="flex-between">
|
|
<div>
|
|
<div class="layout-logo">
|
|
<img v-if="logo" :src="logo" class="logo-img">
|
|
<div v-if="title" class="logo-name">
|
|
{{ title }}
|
|
</div>
|
|
</div>
|
|
<LayoutMenu
|
|
class="layout-menu"
|
|
:menus="menus"
|
|
:collapsed="collapsedRef"
|
|
mode="vertical"
|
|
:inverted="theme === 'dark'"
|
|
:expanded-keys="menuProps?.expandedKeys"
|
|
:default-expand-all="menuProps?.defaultExpandAll"
|
|
:accordion="menuProps?.accordion"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<div class="layout-aside-custom">
|
|
<slot name="renderCustom" :menus="menus" />
|
|
</div>
|
|
<div v-if="locale" class="layout-aside-locale">
|
|
<slot name="locale" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</FAside>
|
|
<FLayout :fixed="isFixedSidebar" :style="sideStyleRef">
|
|
<FLayout :embedded="!multiTabs">
|
|
<FMain class="layout-main">
|
|
<MultiTabProvider :multi-tabs="multiTabs" />
|
|
</FMain>
|
|
<FFooter v-if="footer" class="layout-footer">
|
|
{{ footer }}
|
|
</FFooter>
|
|
</FLayout>
|
|
</FLayout>
|
|
</template>
|
|
<template v-else-if="currentNavigation === 'top'">
|
|
<FHeader ref="headerRef" class="layout-header" :inverted="theme === 'dark'" :fixed="currentFixedHeaderRef">
|
|
<div class="layout-logo">
|
|
<img v-if="logo" :src="logo" class="logo-img">
|
|
<div v-if="title" class="logo-name">
|
|
{{ title }}
|
|
</div>
|
|
</div>
|
|
<LayoutMenu
|
|
class="layout-menu"
|
|
:menus="menus"
|
|
mode="horizontal"
|
|
:inverted="theme === 'dark'"
|
|
:expanded-keys="menuProps?.expandedKeys"
|
|
:default-expand-all="menuProps?.defaultExpandAll"
|
|
:accordion="menuProps?.accordion"
|
|
/>
|
|
<div class="layout-header-custom">
|
|
<slot name="renderCustom" :menus="menus" />
|
|
</div>
|
|
<template v-if="locale">
|
|
<slot name="locale" />
|
|
</template>
|
|
</FHeader>
|
|
<FLayout :embedded="!multiTabs" :fixed="currentFixedHeaderRef" :style="headerStyleRef">
|
|
<FMain class="layout-main">
|
|
<MultiTabProvider :multi-tabs="multiTabs" />
|
|
</FMain>
|
|
<FFooter v-if="footer" class="layout-footer">
|
|
{{ footer }}
|
|
</FFooter>
|
|
</FLayout>
|
|
</template>
|
|
<template v-else-if="currentNavigation === 'mixin'">
|
|
<FHeader ref="headerRef" class="layout-header" :fixed="currentFixedHeaderRef" :inverted="theme === 'dark'">
|
|
<div class="layout-logo">
|
|
<img v-if="logo" :src="logo" class="logo-img">
|
|
<div v-if="title" class="logo-name">
|
|
{{ title }}
|
|
</div>
|
|
</div>
|
|
<div class="layout-header-custom">
|
|
<slot name="renderCustom" :menus="menus" />
|
|
</div>
|
|
<template v-if="locale">
|
|
<slot name="locale" />
|
|
</template>
|
|
</FHeader>
|
|
<FLayout :fixed="currentFixedHeaderRef" :style="headerStyleRef">
|
|
<FAside v-model:collapsed="collapsedRef" :fixed="isFixedSidebar" :width="`${sideWidth}px`" collapsible class="layout-aside">
|
|
<LayoutMenu
|
|
class="layout-menu"
|
|
:menus="menus"
|
|
:collapsed="collapsedRef"
|
|
mode="vertical"
|
|
:expanded-keys="menuProps?.expandedKeys"
|
|
:default-expand-all="menuProps?.defaultExpandAll"
|
|
:accordion="menuProps?.accordion"
|
|
/>
|
|
</FAside>
|
|
<FLayout :embedded="!multiTabs" :fixed="isFixedSidebar" :style="sideStyleRef">
|
|
<FMain class="layout-main">
|
|
<MultiTabProvider :multi-tabs="multiTabs" />
|
|
</FMain>
|
|
<FFooter v-if="footer" class="layout-footer">
|
|
{{ footer }}
|
|
</FFooter>
|
|
</FLayout>
|
|
</FLayout>
|
|
</template>
|
|
<template v-else>
|
|
<FMain class="layout-main">
|
|
<router-view />
|
|
</FMain>
|
|
</template>
|
|
</FLayout>
|
|
</template>
|
|
|
|
<script>
|
|
import { useRoute, useRouter } from '@@/core/coreExports';
|
|
import { FAside, FFooter, FHeader, FLayout, FMain } from '@fesjs/fes-design';
|
|
import { computed, nextTick, ref, watch } from 'vue';
|
|
import defaultLogo from '../assets/logo.png';
|
|
import LayoutMenu from './Menu.vue';
|
|
import MultiTabProvider from './MultiTabProvider.vue';
|
|
|
|
export default {
|
|
components: {
|
|
FLayout,
|
|
FAside,
|
|
FMain,
|
|
FFooter,
|
|
FHeader,
|
|
LayoutMenu,
|
|
MultiTabProvider,
|
|
},
|
|
props: {
|
|
menus: {
|
|
type: Array,
|
|
default() {
|
|
return [];
|
|
},
|
|
},
|
|
locale: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
title: {
|
|
type: String,
|
|
default: '',
|
|
},
|
|
logo: {
|
|
type: String,
|
|
default: defaultLogo,
|
|
},
|
|
theme: {
|
|
type: String,
|
|
default: 'dark', // light、dark
|
|
},
|
|
navigation: {
|
|
type: String,
|
|
default: 'side', // side 左右(上/下)、 top 上/下、 mixin 上/下(左/右)
|
|
},
|
|
navigationOnError: {
|
|
type: [String, Function], // 403, 404 时的 navigation
|
|
},
|
|
isFixedHeader: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
isFixedSidebar: {
|
|
type: Boolean,
|
|
default: true,
|
|
},
|
|
multiTabs: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
sideWidth: {
|
|
type: Number,
|
|
default: 200,
|
|
},
|
|
footer: String,
|
|
menuProps: {
|
|
type: Object,
|
|
},
|
|
},
|
|
setup(props) {
|
|
const headerRef = ref();
|
|
const headerHeightRef = ref(0);
|
|
const collapsedRef = ref(false);
|
|
const route = useRoute();
|
|
const router = useRouter();
|
|
|
|
const currentNavigation = computed(() => {
|
|
if (route.meta.layout && route.meta.layout.navigation !== undefined) {
|
|
return route.meta.layout.navigation;
|
|
}
|
|
if (props.navigationOnError !== undefined && ['/403', '/404'].includes(route.path)) {
|
|
if (typeof props.navigationOnError === 'function') {
|
|
return props.navigationOnError(route);
|
|
}
|
|
return props.navigationOnError;
|
|
}
|
|
return props.navigation;
|
|
});
|
|
|
|
const currentFixedHeaderRef = computed(() => props.isFixedHeader || props.navigation === 'mixin');
|
|
const headerStyleRef = computed(() => (currentFixedHeaderRef.value ? { top: `${headerHeightRef.value}px` } : null));
|
|
const sideStyleRef = computed(() => {
|
|
const left = collapsedRef.value ? '48px' : `${props.sideWidth}px`;
|
|
return props.isFixedSidebar ? { left } : null;
|
|
});
|
|
|
|
watch(
|
|
router.currentRoute,
|
|
() => {
|
|
nextTick(() => {
|
|
if (headerRef.value) {
|
|
headerHeightRef.value = headerRef.value.$el.offsetHeight;
|
|
}
|
|
});
|
|
},
|
|
{
|
|
immediate: true,
|
|
},
|
|
);
|
|
|
|
return {
|
|
headerRef,
|
|
headerHeightRef,
|
|
route,
|
|
collapsedRef,
|
|
currentFixedHeaderRef,
|
|
headerStyleRef,
|
|
sideStyleRef,
|
|
currentNavigation,
|
|
};
|
|
},
|
|
};
|
|
</script>
|
|
|
|
<style lang="less" scoped>
|
|
.main-layout {
|
|
height: 100vh;
|
|
.layout-main {
|
|
z-index: 0;
|
|
}
|
|
.flex-between {
|
|
display: flex;
|
|
flex-flow: column;
|
|
align-items: stretch;
|
|
justify-content: space-between;
|
|
min-height: 100%;
|
|
}
|
|
.layout-header {
|
|
display: flex;
|
|
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
|
|
z-index: 1;
|
|
.layout-menu {
|
|
border-bottom: none;
|
|
}
|
|
.layout-logo {
|
|
display: flex;
|
|
margin: 0 24px;
|
|
justify-content: flex-start;
|
|
align-items: center;
|
|
height: 100%;
|
|
overflow: hidden;
|
|
transition: all 0.3s;
|
|
.logo-img {
|
|
height: 32px;
|
|
width: auto;
|
|
}
|
|
.logo-name {
|
|
overflow: hidden;
|
|
margin: 0 0 0 12px;
|
|
font-weight: 600;
|
|
font-size: 18px;
|
|
line-height: 32px;
|
|
}
|
|
}
|
|
.layout-header-custom {
|
|
flex: 1;
|
|
}
|
|
.layout-menu {
|
|
width: auto;
|
|
}
|
|
}
|
|
.fes-layout-aside {
|
|
z-index: 1;
|
|
box-shadow: 2px 0 8px 0 rgba(29, 35, 41, 5%);
|
|
.layout-logo {
|
|
height: 32px;
|
|
margin: 16px;
|
|
display: flex;
|
|
justify-content: flex-start;
|
|
align-items: center;
|
|
.logo-img {
|
|
height: 36px;
|
|
width: auto;
|
|
}
|
|
.logo-name {
|
|
overflow: hidden;
|
|
margin: 0 0 0 12px;
|
|
font-weight: 600;
|
|
font-size: 18px;
|
|
line-height: 32px;
|
|
}
|
|
}
|
|
.layout-menu {
|
|
margin-top: 24px;
|
|
}
|
|
|
|
.layout-aside-custom {
|
|
padding: 8px 16px;
|
|
}
|
|
|
|
.layout-aside-locale {
|
|
padding: 8px 16px;
|
|
}
|
|
|
|
&.is-collapsed {
|
|
.layout-logo {
|
|
justify-content: center;
|
|
.logo-img {
|
|
width: 40px;
|
|
height: auto;
|
|
}
|
|
.logo-name {
|
|
display: none;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
.layout-footer {
|
|
padding: 16px;
|
|
text-align: center;
|
|
}
|
|
}
|
|
</style>
|