mirror of
https://github.com/WeBankFinTech/fes.js.git
synced 2025-06-30 02:35:08 +08:00
break: 去掉switch配置,改为使用navigation
This commit is contained in:
parent
4d25799d70
commit
1503d8b8af
BIN
docs/.vuepress/public/left-right.png
Normal file
BIN
docs/.vuepress/public/left-right.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 114 KiB |
@ -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
|
||||
<!--  -->
|
||||
@ -40,54 +40,23 @@
|
||||
<!--  -->
|
||||
<img :src="$withBase('mixin.png')" alt="mixin">
|
||||
|
||||
### 布局开关
|
||||
### left-right
|
||||
<!--  -->
|
||||
<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
|
||||
|
@ -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;
|
||||
|
@ -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>;
|
||||
|
@ -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;
|
||||
|
@ -23,5 +23,5 @@ export const beforeRender = {
|
||||
};
|
||||
|
||||
export const layout = {
|
||||
renderHeader: () => <UserCenter />,
|
||||
renderCustom: () => <UserCenter />,
|
||||
};
|
||||
|
@ -26,7 +26,7 @@ export const beforeRender = {
|
||||
|
||||
export const layout = (layoutConfig, { initialState }) => ({
|
||||
...layoutConfig,
|
||||
renderHeader: () => <UserCenter />,
|
||||
renderCustom: () => <UserCenter />,
|
||||
menus: () => {
|
||||
const menusRef = ref(layoutConfig.menus);
|
||||
watch(
|
||||
|
@ -15,6 +15,6 @@ export default {
|
||||
</script>
|
||||
<style lang="less">
|
||||
.user-center {
|
||||
text-align: right;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
|
@ -6,7 +6,9 @@
|
||||
{
|
||||
"name": "pinia",
|
||||
"title": "pinia",
|
||||
"layout": false
|
||||
"layout": {
|
||||
"navigation": null
|
||||
}
|
||||
}
|
||||
</config>
|
||||
<script>
|
||||
|
Loading…
x
Reference in New Issue
Block a user