refactor: 完善useLayout (#250)

This commit is contained in:
听海 2024-09-18 14:09:24 +08:00 committed by GitHub
parent b7b7642709
commit b61df95535
7 changed files with 143 additions and 115 deletions

View File

@ -8,16 +8,16 @@ import { withBase } from 'vitepress'
为了进一步降低研发成本,我们将布局利用 `fes.js` 插件的方式内置,只需通过简单的配置即可拥有布局,包括导航以及侧边栏。从而做到用户无需关心布局。
- 侧边栏菜单数据根据路由中的配置自动生成。
- 布局,提供 `side``top``mixin``left-right``top-left-right` 五种布局。
- 主题,提供 `light``dark` 两种主题。
- 默认实现对路由的 404、403 处理。
- 搭配 [@fesjs/plugin-access](./access.html) 插件使用,可以完成对路由的权限控制。
- 搭配 [@fesjs/plugin-locale](./locale.html) 插件使用,提供切换语言的能力。
- 支持自定义头部或者侧边栏区域。
- 菜单支持配置 icon。
- 菜单标题支持国际化。
- 可配置页面是否需要 layout。
- 侧边栏菜单数据根据路由中的配置自动生成。
- 布局,提供 `side``top``mixin``left-right``top-left-right` 五种布局。
- 主题,提供 `light``dark` 两种主题。
- 默认实现对路由的 404、403 处理。
- 搭配 [@fesjs/plugin-access](./access.html) 插件使用,可以完成对路由的权限控制。
- 搭配 [@fesjs/plugin-locale](./locale.html) 插件使用,提供切换语言的能力。
- 支持自定义头部或者侧边栏区域。
- 菜单支持配置 icon。
- 菜单标题支持国际化。
- 可配置页面是否需要 layout。
## 启用方式
@ -182,58 +182,58 @@ export function layout(layoutConfig, { initialState }) {
### footer
- **类型**`String`
- **默认值**`null`
- **类型**`String`
- **默认值**`null`
- **详情**:页面底部的文字。
- **详情**:页面底部的文字。
### theme
- **类型**`String`
- **默认值**`dark`
- **类型**`String`
- **默认值**`dark`
- **详情**:主题,可选有 `dark``light`
- **详情**:主题,可选有 `dark``light`
### navigation
- **类型**`String`
- **默认值**`side`
- **类型**`String`
- **默认值**`side`
- **详情**:页面布局类型,可选有 `side``top``mixin`
- **详情**:页面布局类型,可选有 `side``top``mixin`
### navigationOnError
- **类型**`String``Function`
- **类型**`String``Function`
- **详情**:指定 `403``404` 时,页面的布局类型。值同 `navigation`。也支持函数返回。
- **详情**:指定 `403``404` 时,页面的布局类型。值同 `navigation`。也支持函数返回。
### isFixedHeader
- **类型**`Boolean`
- **默认值**`false`
- **类型**`Boolean`
- **默认值**`false`
- **详情**:是否固定头部,不跟随页面滚动。
- **详情**:是否固定头部,不跟随页面滚动。
### isFixedSidebar
- **类型**`Boolean`
- **默认值**`true`
- **类型**`Boolean`
- **默认值**`true`
- **详情**:是否固定 sidebar不跟随页面滚动。
- **详情**:是否固定 sidebar不跟随页面滚动。
### title
- **类型**`String`
- **默认值**:默认为 [编译时配置 title](../../../reference/config/#title)
- **类型**`String`
- **默认值**:默认为 [编译时配置 title](../../../reference/config/#title)
- **详情**:产品名。
- **详情**:产品名。
### logo
- **类型**`String`
- **默认值**:默认提供 `fes.js` 的 Logo
- **类型**`String`
- **默认值**:默认提供 `fes.js` 的 Logo
- **详情**Logo 的链接,例如在 public/logo.png 放了一个 logo可以这么配置([BASE_URL 来自这里](../../../guide/env#process-env))
- **详情**Logo 的链接,例如在 public/logo.png 放了一个 logo可以这么配置([BASE_URL 来自这里](../../../guide/env#process-env))
```js
export const layout = {
@ -243,29 +243,29 @@ export const layout = {
### multiTabs
- **类型**`boolean`
- **默认值**`false`
- **类型**`boolean`
- **默认值**`false`
- **详情**:是否开启多页。
- **详情**:是否开启多页。
### menus
- **类型**`[] | () => Ref<[]> | () => []`
- **默认值**`[]`
- **类型**`[] | () => Ref<[]> | () => []`
- **默认值**`[]`
- **详情**:菜单配置
- **详情**:菜单配置
子项具体配置如下:
- **name**:菜单的名称。通过匹配 `name` 和路由元信息 [meta](../../../guide/route.md#扩展路由元信息) 中的 `name`,把菜单和路由关联起来, 然后使用路由元信息补充菜单配置,比如 `title``path`  等。
- **name**:菜单的名称。通过匹配 `name` 和路由元信息 [meta](../../../guide/route.md#扩展路由元信息) 中的 `name`,把菜单和路由关联起来, 然后使用路由元信息补充菜单配置,比如 `title``path`  等。
- **path**:菜单的路径,可配置第三方地址。
- **path**:菜单的路径,可配置第三方地址。
- **query**:同 vue-router 的 query 参数。
- **query**:同 vue-router 的 query 参数。
- **params**:同 vue-router 的 params 参数。
- **params**:同 vue-router 的 params 参数。
- **match (v4.0.0+**:额外匹配的路径,当前路由命中匹配规则时,此菜单高亮。
- **match (v4.0.0+**:额外匹配的路径,当前路由命中匹配规则时,此菜单高亮。
```
{
@ -274,17 +274,17 @@ export const layout = {
}
```
- **title**:菜单的标题。
- **title**:菜单的标题。
- 如果同时使用[国际化插件](./locale.md),而且`title`的值以`$`开头,则使用`$`后面的内容去匹配语言设置。
- 如果同时使用[国际化插件](./locale.md),而且`title`的值以`$`开头,则使用`$`后面的内容去匹配语言设置。
- title 支持配置函数,对应 Fes Design 中 Menu 组件的`label`插槽。仅在运行时配置中支持。
- title 支持配置函数,对应 Fes Design 中 Menu 组件的`label`插槽。仅在运行时配置中支持。
- **icon**: 菜单的图标,只一级标题展示图标。
- **icon**: 菜单的图标,只一级标题展示图标。
- 图标使用[fes-design icon](https://fes-design-4gvn317r3b6bfe17-1254145788.ap-shanghai.app.tcloudbase.com/zh/components/icon.html),编译时配置使用组件名称,我们会自动引入组件。
- 图标使用[fes-design icon](https://fes-design-4gvn317r3b6bfe17-1254145788.ap-shanghai.app.tcloudbase.com/zh/components/icon.html),编译时配置使用组件名称,我们会自动引入组件。
- 图标使用本地或者远程 svg 图片。
- 图标使用本地或者远程 svg 图片。
```js
{
@ -292,7 +292,7 @@ export const layout = {
}
```
- **children**:子菜单配置。
- **children**:子菜单配置。
:::tip
函数类型仅在运行时可用,可以实现动态变更菜单。
@ -300,42 +300,42 @@ export const layout = {
### menuProps
- **类型**`Object`
- **默认值**`{}`
- **类型**`Object`
- **默认值**`{}`
- **详情**:菜单的配置:
- **详情**:菜单的配置:
- **defaultExpandAll**:是否默认展开全部菜单。
- **defaultExpandAll**:是否默认展开全部菜单。
- **expandedKeys**:配置默认展开的菜单,需要传子项是菜单路径的数组。
- **expandedKeys**:配置默认展开的菜单,需要传子项是菜单路径的数组。
- **accordion**:是否只保持一个子菜单的展开。
- **accordion**:是否只保持一个子菜单的展开。
### sideWidth
- **类型**`Number`
- **默认值**`200`
- **类型**`Number`
- **默认值**`200`
- **详情**sidebar 的宽度
- **详情**sidebar 的宽度
### renderCustom
- **类型** `()=> VNodes`
- **默认值**`null`
- **类型** `()=> VNodes`
- **默认值**`null`
- **详情** 自定义区域内容,仅运行时。
- **详情** 自定义区域内容,仅运行时。
### unAccessHandler
- **类型**`({ to, from, next})=> void`
- **默认值**`null`
- **类型**`({ to, from, next})=> void`
- **默认值**`null`
- **详情**:仅运行时,当进入某个路由时,如果路由对应的页面不属于可见资源列表,则会暂停进入,调用 `unAccessHandler` 函数。
- **参数**
- routercreateRouter 创建的路由实例
- to 准备进入的路由
- from离开的路由
- next [next 函数](https://next.router.vuejs.org/zh/guide/advanced/navigation-guards.html#%E5%8F%AF%E9%80%89%E7%9A%84%E7%AC%AC%E4%B8%89%E4%B8%AA%E5%8F%82%E6%95%B0-next)
- **详情**:仅运行时,当进入某个路由时,如果路由对应的页面不属于可见资源列表,则会暂停进入,调用 `unAccessHandler` 函数。
- **参数**
- routercreateRouter 创建的路由实例
- to 准备进入的路由
- from离开的路由
- next [next 函数](https://next.router.vuejs.org/zh/guide/advanced/navigation-guards.html#%E5%8F%AF%E9%80%89%E7%9A%84%E7%AC%AC%E4%B8%89%E4%B8%AA%E5%8F%82%E6%95%B0-next)
比如:
@ -357,15 +357,15 @@ export const layout = {
### noFoundHandler
- **类型**`({ to, from, next})=> void`
- **默认值**`null`
- **类型**`({ to, from, next})=> void`
- **默认值**`null`
- **详情**:仅运行时,当进入某个路由时,如果路由对应的页面不存在,则会调用 `noFoundHandler` 函数。
- **参数**
- routercreateRouter 创建的路由实例
- to 准备进入的路由
- from离开的路由
- next [next 函数](https://next.router.vuejs.org/zh/guide/advanced/navigation-guards.html#%E5%8F%AF%E9%80%89%E7%9A%84%E7%AC%AC%E4%B8%89%E4%B8%AA%E5%8F%82%E6%95%B0-next)
- **详情**:仅运行时,当进入某个路由时,如果路由对应的页面不存在,则会调用 `noFoundHandler` 函数。
- **参数**
- routercreateRouter 创建的路由实例
- to 准备进入的路由
- from离开的路由
- next [next 函数](https://next.router.vuejs.org/zh/guide/advanced/navigation-guards.html#%E5%8F%AF%E9%80%89%E7%9A%84%E7%AC%AC%E4%B8%89%E4%B8%AA%E5%8F%82%E6%95%B0-next)
比如:
@ -383,7 +383,7 @@ export const layout = {
## API
### useTabTitle
### useTabTitle建议使用useLayout
类型定义如下:
@ -404,6 +404,18 @@ titleRef.value = 'changed';
</script>
```
### useLayout
类型定义如下:
```ts
function useLayout(options: { title?: string }): { title: Ref<string>; reloadTab: () => void; closeTab: () => void };
```
- title: 更新当前页签的标题
- reloadTab重载当前页签
- closeTab关闭当前页签
## 4.x 升级到 5.x
1. 个性化 layout 配置改为使用传入 navigation

View File

@ -1,3 +1,2 @@
export { default as Page } from './views/page.vue';
export { useTabTitle } from './useTitle';
export * from './useLayout';

View File

@ -1,12 +1,31 @@
import { createSharedComposable } from '@vueuse/core';
import { shallowReactive } from 'vue';
import { inject, ref } from 'vue';
function _useLayout() {
const state = shallowReactive({
closeTab: () => {},
});
import { useRoute } from '@@/core/coreExports';
return state;
export const PLUGIN_LAYOUT_TITLE_KEY = Symbol('PLUGIN_LAYOUT_TITLE_KEY');
export const PLUGIN_LAYOUT_KEY = Symbol('PLUGIN_LAYOUT_KEY');
export function useTabTitle(title) {
const titleMap = inject(PLUGIN_LAYOUT_TITLE_KEY);
if (!titleMap) {
console.warn('[plugin-layout]: 未正确获取到titleMap');
return;
}
const route = useRoute();
const titleRef = ref(title);
const path = route.path;
titleMap.set(path, titleRef);
return titleRef;
}
export const useLayout = createSharedComposable(_useLayout);
export function useLayout(options) {
const parent = inject(PLUGIN_LAYOUT_KEY, { reloadTab: () => void 0, closeTab: () => void 0 });
const titleRef = useTabTitle(options?.title);
return {
...parent,
title: titleRef,
};
}

View File

@ -1,18 +0,0 @@
import { reactive, ref } from 'vue';
import { useRoute } from '@@/core/coreExports';
const cache = reactive(new Map());
export const getTitle = path => cache.get(path);
export const deleteTitle = patch => cache.delete(patch);
export function useTabTitle(title) {
const route = useRoute();
const titleRef = ref(title);
const path = route.path;
cache.set(path, titleRef);
return titleRef;
}

View File

@ -27,13 +27,12 @@
</template>
<script>
import { computed, ref, unref } from 'vue';
import { computed, provide, reactive, ref, unref } from 'vue';
import { FDropdown, FTabPane, FTabs } from '@fesjs/fes-design';
import { MoreOutlined, ReloadOutlined } from '@fesjs/fes-design/icon';
import { plugin, useRoute, useRouter } from '@@/core/coreExports';
import { transTitle } from '../helpers/pluginLocale';
import { deleteTitle, getTitle } from '../useTitle';
import { useLayout } from '../useLayout';
import { PLUGIN_LAYOUT_KEY, PLUGIN_LAYOUT_TITLE_KEY } from '../useLayout';
import Page from './page.vue';
let i = 0;
@ -54,7 +53,14 @@ export default {
const pageRef = ref();
const route = useRoute();
const router = useRouter();
const layoutState = useLayout();
const titleCache = reactive(new Map());
provide(PLUGIN_LAYOUT_TITLE_KEY, titleCache);
const getTitle = path => titleCache.get(path);
const deleteTitle = patch => titleCache.delete(patch);
const createPage = (_route) => {
const computedTitle = computed(() => {
@ -146,7 +152,6 @@ export default {
pageRef.value.removeKeepAlive(selectedPage.name);
deleteTitle(selectedPage.path);
};
layoutState.closeTab = handleCloseTab;
const reloadPage = (path) => {
const selectedPage = findPage(path || unref(route.path));
@ -179,6 +184,11 @@ export default {
}
};
provide(PLUGIN_LAYOUT_KEY, {
closeTab: handleCloseTab,
reloadTab: reloadPage,
});
return {
pageRef,
route,

View File

@ -28,6 +28,8 @@ export const Page: Component;
export function useTabTitle(title: string | Ref<string>): void;
export function useLayout(options: { title?: string }): { title: Ref<string>; reloadTab: () => void; closeTab: () => void };
interface LayoutRuntimeConfig {
footer?: string;
theme?: 'dark' | 'light';

View File

@ -1,19 +1,23 @@
<template>
<div class="page">menuTest: {{ route.params }} <input style="border: 1px solid red" /></div>
<div class="page">
menuTest: {{ route.params }} <input style="border: 1px solid red">
</div>
</template>
<config>
{
"title": "menuTest-详情"
}
</config>
<script>
import { useRoute, useTabTitle } from '@fesjs/fes';
import { useLayout, useRoute } from '@fesjs/fes';
export default {
components: {},
setup() {
const route = useRoute();
const title = useTabTitle(`详情-${route.params?.id}`);
const { title } = useLayout({ title: `详情-${route.params?.id}` });
setTimeout(() => {
title.value = `详情-${route.params?.id}-changed`;