break: 去掉switch配置,改为使用navigation

This commit is contained in:
wanchun 2022-06-17 12:44:21 +08:00
parent 4d25799d70
commit 1503d8b8af
9 changed files with 128 additions and 154 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

View File

@ -3,14 +3,14 @@
## 介绍
为了进一步降低研发成本,我们将布局利用 `fes.js` 插件的方式内置,只需通过简单的配置即可拥有布局,包括导航以及侧边栏。从而做到用户无需关心布局。
- 侧边栏菜单数据根据路由中的配置自动生成。
- 布局,提供 `side``top``mixin`种布局。
- 布局,提供 `side``top``mixin``left-right`种布局。
- 主题,提供 `light``dark` 两种主题。
- 默认实现对路由的 404、403 处理。
- 搭配 [@fesjs/plugin-access](./access.html) 插件使用,可以完成对路由的权限控制。
- 搭配 [@fesjs/plugin-locale](./locale.html) 插件使用,提供切换语言的能力。
- 支持自定义头部区域。
- 菜单支持配置icon
- 菜单标题支持国际化
- 支持自定义头部或者侧边栏区域。
- 菜单支持配置icon
- 菜单标题支持国际化
- 可配置页面是否需要 layout。
@ -26,7 +26,7 @@
```
## 布局类型
配置参数是 `navigation`, 布局有三种类型 `side``mixin` `top` 默认是 `side`
配置参数是 `navigation`, 布局有三种类型 `side``mixin` `top``left-right` 默认是 `side`
### side
<!-- ![side](/side.png) -->
@ -40,54 +40,23 @@
<!-- ![mixin](/mixin.png) -->
<img :src="$withBase('mixin.png')" alt="mixin">
### 布局开关
### left-right
<!-- ![mixin](/mixin.png) -->
<img :src="$withBase('left-right.png')" alt="left-right">
布局默认开启,可以通过一些方式更改。
### 页面个性化
开关的可选配置有:
- **sidebar** 左侧区域
- **header** 头部区域
- **logo**logo和标题区域。
#### 全局定义
全局定义可以通过配置`switch`实现,在 `app.js` 中配置:
```js
import UserCenter from '@/components/UserCenter';
export const layout = {
switch: {
header: false
}
};
```
#### 页面定义
可以通过[定义路由元信息](../../../guide/route.html#扩展路由元信息)配置页面的布局开关,添加如下配置:
```vue
<config lang="json">
{
"layout": false
}
</config>
```
如果只是不想展示`sidebar`,则:
可以为页面单独设置布局类型:
```
<config lang="json">
{
"layout": {
"sidebar": false
"navigation": 'top'
}
}
</config>
```
#### 地址参数定义
通过路由的`query`参数定义,比如当访问`http://localhost:8080/#/?layout={%22sidebar%22:%20false,%20%22header%22:%20false}` 时,页面隐藏 `sidebar``header`区域。此种方式优先级最高!
当设置为 `null` 时,页面不使用布局。
## 页面缓存
@ -282,19 +251,6 @@ export const layout = (layoutConfig, { initialState }) => ({
- **accordion**:是否只保持一个子菜单的展开。
### switch
- **类型**`Object`
- **默认值**`{ logo: true, sidebar: true, header: true }`
- **详情**:布局的开关:
- **logo**是否展示logo区域。
- **sidebar**:配置默认展开的菜单,需要传子项是菜单路径的数组。
- **header**:是否只保持一个子菜单的展开。
### sideWidth
- **类型**`Number`
@ -303,12 +259,12 @@ export const layout = (layoutConfig, { initialState }) => ({
- **详情**sidebar的宽度
### renderHeader
### renderCustom
- **类型** `()=> VNodes`
- **默认值**`null`
- **详情** 自定义`top`区域部分位置,仅运行时。
- **详情** 自定义区域内容,仅运行时。
### unAccessHandler

View File

@ -1,8 +1,7 @@
<template>
<f-layout v-if="routeLayout" class="main-layout">
<template v-if="navigation === 'side'">
<f-layout class="main-layout">
<template v-if="currentNavigation === 'side'">
<f-aside
v-if="routeLayout.sidebar"
v-model:collapsed="collapsedRef"
:fixed="isSidebarFixed"
:width="`${sideWidth}px`"
@ -10,7 +9,7 @@
collapsible
:inverted="theme === 'dark'"
>
<div v-if="routeLayout.logo" class="layout-logo">
<div class="layout-logo">
<img :src="logo" class="logo-img" />
<div class="logo-name">{{ title }}</div>
</div>
@ -26,9 +25,9 @@
/>
</f-aside>
<f-layout :fixed="isSidebarFixed" :style="sideStyleRef">
<f-header v-if="routeLayout.header" ref="headerRef" class="layout-header" :fixed="currentFixedHeaderRef">
<f-header ref="headerRef" class="layout-header" :fixed="currentFixedHeaderRef">
<div class="layout-header-custom">
<slot name="customHeader"></slot>
<slot name="renderCustom"></slot>
</div>
<template v-if="locale">
<slot name="locale"></slot>
@ -44,9 +43,56 @@
</f-layout>
</f-layout>
</template>
<template v-if="navigation === 'top'">
<f-header v-if="routeLayout.header" ref="headerRef" class="layout-header" :inverted="theme === 'dark'" :fixed="currentFixedHeaderRef">
<div v-if="routeLayout.logo" class="layout-logo">
<template v-if="currentNavigation === 'left-right'">
<f-aside
v-model:collapsed="collapsedRef"
:fixed="isSidebarFixed"
:width="`${sideWidth}px`"
class="layout-aside"
collapsible
:inverted="theme === 'dark'"
>
<div class="flex-between">
<div>
<div class="layout-logo">
<img :src="logo" class="logo-img" />
<div class="logo-name">{{ title }}</div>
</div>
<Menu
class="layout-menu"
:menus="menus"
:collapsed="collapsedRef"
mode="vertical"
:inverted="theme === 'dark'"
:expandedKeys="menuProps?.expandedKeys"
:defaultExpandAll="menuProps?.defaultExpandAll"
:accordion="menuProps?.accordion"
/>
</div>
<div>
<div class="layout-aside-custom">
<slot name="renderCustom"></slot>
</div>
<div v-if="locale" class="layout-aside-locale">
<slot name="locale"></slot>
</div>
</div>
</div>
</f-aside>
<f-layout :fixed="isSidebarFixed" :style="sideStyleRef">
<f-layout :embedded="!multiTabs">
<f-main class="layout-main">
<MultiTabProvider :multiTabs="multiTabs" />
</f-main>
<f-footer v-if="footer" class="layout-footer">
{{ footer }}
</f-footer>
</f-layout>
</f-layout>
</template>
<template v-else-if="currentNavigation === 'top'">
<f-header ref="headerRef" class="layout-header" :inverted="theme === 'dark'" :fixed="currentFixedHeaderRef">
<div class="layout-logo">
<img :src="logo" class="logo-img" />
<div class="logo-name">{{ title }}</div>
</div>
@ -60,7 +106,7 @@
:accordion="menuProps?.accordion"
/>
<div class="layout-header-custom">
<slot name="customHeader"></slot>
<slot name="renderCustom"></slot>
</div>
<template v-if="locale">
<slot name="locale"></slot>
@ -75,28 +121,21 @@
</f-footer>
</f-layout>
</template>
<template v-if="navigation === 'mixin'">
<f-header v-if="routeLayout.header" ref="headerRef" class="layout-header" :fixed="currentFixedHeaderRef" :inverted="theme === 'dark'">
<div v-if="routeLayout.logo" class="layout-logo">
<template v-else-if="currentNavigation === 'mixin'">
<f-header ref="headerRef" class="layout-header" :fixed="currentFixedHeaderRef" :inverted="theme === 'dark'">
<div class="layout-logo">
<img :src="logo" class="logo-img" />
<div class="logo-name">{{ title }}</div>
</div>
<div class="layout-header-custom">
<slot name="customHeader"></slot>
<slot name="renderCustom"></slot>
</div>
<template v-if="locale">
<slot name="locale"></slot>
</template>
</f-header>
<f-layout :fixed="currentFixedHeaderRef" :style="headerStyleRef">
<f-aside
v-if="routeLayout.sidebar"
v-model:collapsed="collapsedRef"
:fixed="isSidebarFixed"
:width="`${sideWidth}px`"
collapsible
class="layout-aside"
>
<f-aside v-model:collapsed="collapsedRef" :fixed="isSidebarFixed" :width="`${sideWidth}px`" collapsible class="layout-aside">
<Menu
class="layout-menu"
:menus="menus"
@ -117,8 +156,12 @@
</f-layout>
</f-layout>
</template>
<template v-else>
<f-main class="layout-main">
<router-view></router-view>
</f-main>
</template>
</f-layout>
<router-view v-else></router-view>
</template>
<script>
@ -186,72 +229,41 @@ export default {
menuProps: {
type: Object,
},
switch: {
type: Object,
default() {
return {
logo: true,
sidebar: true,
header: true,
};
},
},
},
setup(props) {
const headerRef = ref();
const headerHeightRef = ref(0);
const collapsedRef = ref(false);
const route = useRoute();
const currentNavigation = computed(() => {
if (route.meta.layout && route.meta.layout.navigation !== undefined) {
return route.meta.layout.navigation;
}
return props.navigation;
});
const currentFixedHeaderRef = computed(() => props.isHeaderFixed || 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.isSidebarFixed ? { left } : null;
});
onMounted(() => {
if (headerRef.value) {
headerHeightRef.value = headerRef.value.$el.offsetHeight;
}
});
const collapsedRef = ref(false);
const route = useRoute();
const routeLayout = computed(() => {
let config;
// meta layout true
const metaLayoutConfig = route.meta.layout === undefined ? true : route.meta.layout;
if (typeof metaLayoutConfig === 'boolean') {
config = metaLayoutConfig ? props.switch : false;
} else if (typeof metaLayoutConfig === 'object') {
config = { ...props.switch, ...metaLayoutConfig };
} else {
console.error('[plugin-layout]: meta layout must be object or boolean');
}
// query layout false
const routeQueryLayoutConfig = route.query.layout && JSON.parse(route.query.layout);
if (typeof routeQueryLayoutConfig === 'boolean') {
config = routeQueryLayoutConfig ? props.switch : false;
} else if (typeof routeQueryLayoutConfig === 'object') {
config = { ...config, ...routeQueryLayoutConfig };
} else if (routeQueryLayoutConfig !== undefined) {
console.error('[plugin-layout]: query layout must be object or boolean');
}
return config;
});
const currentFixedHeaderRef = computed(() => props.isHeaderFixed || props.navigation === 'mixin');
const headerStyleRef = computed(() => {
if (!routeLayout.value) return;
if (!routeLayout.value.header) return;
return currentFixedHeaderRef.value ? { top: `${headerHeightRef.value}px` } : null;
});
const sideStyleRef = computed(() => {
if (!routeLayout.value) return;
if (!routeLayout.value.sidebar) return;
const left = collapsedRef.value ? '48px' : `${props.sideWidth}px`;
return props.isSidebarFixed ? { left } : null;
});
return {
headerRef,
headerHeightRef,
route,
routeLayout,
collapsedRef,
currentFixedHeaderRef,
headerStyleRef,
sideStyleRef,
currentNavigation,
};
},
};
@ -262,6 +274,13 @@ export default {
.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);
@ -320,6 +339,15 @@ export default {
.layout-menu {
margin-top: 24px;
}
.layout-aside-custom {
padding: 8px 16px;
}
.layout-aside-locale {
padding: 8px 16px;
}
&.is-collapsed {
.layout-logo {
justify-content: center;

View File

@ -23,12 +23,7 @@ const Layout = defineComponent({
const localeShared = plugin.getShared('locale');
return () => {
const slots = {
customHeader: () => {
if (config.renderHeader) {
return config.renderHeader();
}
return null;
},
renderCustom: config.renderCustom,
locale: () => {
if (localeShared) {
return <localeShared.SelectLang></localeShared.SelectLang>;

View File

@ -5,17 +5,9 @@
</div>
<template #content>
<FScrollbar height="274" class="lang-container">
<div
v-for="item in configs"
:key="item.lang"
:class="[
'lang-option',
item.lang === locale && 'is-selected'
]"
@click="handleSelect(item)"
>
<span>{{item.icon}}</span>
<span>{{item.label}}</span>
<div v-for="item in configs" :key="item.lang" :class="['lang-option', item.lang === locale && 'is-selected']" @click="handleSelect(item)">
<span>{{ item.icon }}</span>
<span>{{ item.label }}</span>
</div>
</FScrollbar>
</template>
@ -28,13 +20,14 @@ import { LanguageOutlined } from '@fesjs/fes-design/icon';
import { useI18n } from 'vue-i18n';
import { computed, ref } from 'vue';
import langUConfigMap from '../langUConfigMap';
// eslint-disable-next-line import/extensions
import { locale as _locale } from '../core';
export default {
components: {
FTooltip,
FScrollbar,
LanguageOutlined
LanguageOutlined,
},
setup() {
const { messages, locale } = useI18n();
@ -57,9 +50,9 @@ export default {
handleSelect,
locale,
configs,
isOpened
isOpened,
};
}
},
};
</script>
<style>
@ -68,10 +61,10 @@ export default {
}
</style>
<style lang="less" scoped>
.lang-icon {
display: flex;
align-items: center;
justify-content: center;
margin: 0 8px;
padding: 0 4px;
cursor: pointer;

View File

@ -23,5 +23,5 @@ export const beforeRender = {
};
export const layout = {
renderHeader: () => <UserCenter />,
renderCustom: () => <UserCenter />,
};

View File

@ -26,7 +26,7 @@ export const beforeRender = {
export const layout = (layoutConfig, { initialState }) => ({
...layoutConfig,
renderHeader: () => <UserCenter />,
renderCustom: () => <UserCenter />,
menus: () => {
const menusRef = ref(layoutConfig.menus);
watch(

View File

@ -15,6 +15,6 @@ export default {
</script>
<style lang="less">
.user-center {
text-align: right;
text-align: center;
}
</style>

View File

@ -6,7 +6,9 @@
{
"name": "pinia",
"title": "pinia",
"layout": false
"layout": {
"navigation": null
}
}
</config>
<script>