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` 插件的方式内置,只需通过简单的配置即可拥有布局,包括导航以及侧边栏。从而做到用户无需关心布局。 为了进一步降低研发成本,我们将布局利用 `fes.js` 插件的方式内置,只需通过简单的配置即可拥有布局,包括导航以及侧边栏。从而做到用户无需关心布局。
- 侧边栏菜单数据根据路由中的配置自动生成。 - 侧边栏菜单数据根据路由中的配置自动生成。
- 布局,提供 `side``top``mixin``left-right``top-left-right` 五种布局。 - 布局,提供 `side``top``mixin``left-right``top-left-right` 五种布局。
- 主题,提供 `light``dark` 两种主题。 - 主题,提供 `light``dark` 两种主题。
- 默认实现对路由的 404、403 处理。 - 默认实现对路由的 404、403 处理。
- 搭配 [@fesjs/plugin-access](./access.html) 插件使用,可以完成对路由的权限控制。 - 搭配 [@fesjs/plugin-access](./access.html) 插件使用,可以完成对路由的权限控制。
- 搭配 [@fesjs/plugin-locale](./locale.html) 插件使用,提供切换语言的能力。 - 搭配 [@fesjs/plugin-locale](./locale.html) 插件使用,提供切换语言的能力。
- 支持自定义头部或者侧边栏区域。 - 支持自定义头部或者侧边栏区域。
- 菜单支持配置 icon。 - 菜单支持配置 icon。
- 菜单标题支持国际化。 - 菜单标题支持国际化。
- 可配置页面是否需要 layout。 - 可配置页面是否需要 layout。
## 启用方式 ## 启用方式
@ -182,58 +182,58 @@ export function layout(layoutConfig, { initialState }) {
### footer ### footer
- **类型**`String` - **类型**`String`
- **默认值**`null` - **默认值**`null`
- **详情**:页面底部的文字。 - **详情**:页面底部的文字。
### theme ### theme
- **类型**`String` - **类型**`String`
- **默认值**`dark` - **默认值**`dark`
- **详情**:主题,可选有 `dark``light` - **详情**:主题,可选有 `dark``light`
### navigation ### navigation
- **类型**`String` - **类型**`String`
- **默认值**`side` - **默认值**`side`
- **详情**:页面布局类型,可选有 `side``top``mixin` - **详情**:页面布局类型,可选有 `side``top``mixin`
### navigationOnError ### navigationOnError
- **类型**`String``Function` - **类型**`String``Function`
- **详情**:指定 `403``404` 时,页面的布局类型。值同 `navigation`。也支持函数返回。 - **详情**:指定 `403``404` 时,页面的布局类型。值同 `navigation`。也支持函数返回。
### isFixedHeader ### isFixedHeader
- **类型**`Boolean` - **类型**`Boolean`
- **默认值**`false` - **默认值**`false`
- **详情**:是否固定头部,不跟随页面滚动。 - **详情**:是否固定头部,不跟随页面滚动。
### isFixedSidebar ### isFixedSidebar
- **类型**`Boolean` - **类型**`Boolean`
- **默认值**`true` - **默认值**`true`
- **详情**:是否固定 sidebar不跟随页面滚动。 - **详情**:是否固定 sidebar不跟随页面滚动。
### title ### title
- **类型**`String` - **类型**`String`
- **默认值**:默认为 [编译时配置 title](../../../reference/config/#title) - **默认值**:默认为 [编译时配置 title](../../../reference/config/#title)
- **详情**:产品名。 - **详情**:产品名。
### logo ### logo
- **类型**`String` - **类型**`String`
- **默认值**:默认提供 `fes.js` 的 Logo - **默认值**:默认提供 `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 ```js
export const layout = { export const layout = {
@ -243,29 +243,29 @@ export const layout = {
### multiTabs ### multiTabs
- **类型**`boolean` - **类型**`boolean`
- **默认值**`false` - **默认值**`false`
- **详情**:是否开启多页。 - **详情**:是否开启多页。
### menus ### 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 ```js
{ {
@ -292,7 +292,7 @@ export const layout = {
} }
``` ```
- **children**:子菜单配置。 - **children**:子菜单配置。
:::tip :::tip
函数类型仅在运行时可用,可以实现动态变更菜单。 函数类型仅在运行时可用,可以实现动态变更菜单。
@ -300,42 +300,42 @@ export const layout = {
### menuProps ### menuProps
- **类型**`Object` - **类型**`Object`
- **默认值**`{}` - **默认值**`{}`
- **详情**:菜单的配置: - **详情**:菜单的配置:
- **defaultExpandAll**:是否默认展开全部菜单。 - **defaultExpandAll**:是否默认展开全部菜单。
- **expandedKeys**:配置默认展开的菜单,需要传子项是菜单路径的数组。 - **expandedKeys**:配置默认展开的菜单,需要传子项是菜单路径的数组。
- **accordion**:是否只保持一个子菜单的展开。 - **accordion**:是否只保持一个子菜单的展开。
### sideWidth ### sideWidth
- **类型**`Number` - **类型**`Number`
- **默认值**`200` - **默认值**`200`
- **详情**sidebar 的宽度 - **详情**sidebar 的宽度
### renderCustom ### renderCustom
- **类型** `()=> VNodes` - **类型** `()=> VNodes`
- **默认值**`null` - **默认值**`null`
- **详情** 自定义区域内容,仅运行时。 - **详情** 自定义区域内容,仅运行时。
### unAccessHandler ### unAccessHandler
- **类型**`({ to, from, next})=> void` - **类型**`({ to, from, next})=> void`
- **默认值**`null` - **默认值**`null`
- **详情**:仅运行时,当进入某个路由时,如果路由对应的页面不属于可见资源列表,则会暂停进入,调用 `unAccessHandler` 函数。 - **详情**:仅运行时,当进入某个路由时,如果路由对应的页面不属于可见资源列表,则会暂停进入,调用 `unAccessHandler` 函数。
- **参数** - **参数**
- routercreateRouter 创建的路由实例 - routercreateRouter 创建的路由实例
- to 准备进入的路由 - to 准备进入的路由
- from离开的路由 - 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) - 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 ### noFoundHandler
- **类型**`({ to, from, next})=> void` - **类型**`({ to, from, next})=> void`
- **默认值**`null` - **默认值**`null`
- **详情**:仅运行时,当进入某个路由时,如果路由对应的页面不存在,则会调用 `noFoundHandler` 函数。 - **详情**:仅运行时,当进入某个路由时,如果路由对应的页面不存在,则会调用 `noFoundHandler` 函数。
- **参数** - **参数**
- routercreateRouter 创建的路由实例 - routercreateRouter 创建的路由实例
- to 准备进入的路由 - to 准备进入的路由
- from离开的路由 - 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) - 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 ## API
### useTabTitle ### useTabTitle建议使用useLayout
类型定义如下: 类型定义如下:
@ -404,6 +404,18 @@ titleRef.value = 'changed';
</script> </script>
``` ```
### useLayout
类型定义如下:
```ts
function useLayout(options: { title?: string }): { title: Ref<string>; reloadTab: () => void; closeTab: () => void };
```
- title: 更新当前页签的标题
- reloadTab重载当前页签
- closeTab关闭当前页签
## 4.x 升级到 5.x ## 4.x 升级到 5.x
1. 个性化 layout 配置改为使用传入 navigation 1. 个性化 layout 配置改为使用传入 navigation

View File

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

View File

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

View File

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

View File

@ -1,19 +1,23 @@
<template> <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> </template>
<config> <config>
{ {
"title": "menuTest-详情" "title": "menuTest-详情"
} }
</config> </config>
<script> <script>
import { useRoute, useTabTitle } from '@fesjs/fes'; import { useLayout, useRoute } from '@fesjs/fes';
export default { export default {
components: {}, components: {},
setup() { setup() {
const route = useRoute(); const route = useRoute();
const title = useTabTitle(`详情-${route.params?.id}`); const { title } = useLayout({ title: `详情-${route.params?.id}` });
setTimeout(() => { setTimeout(() => {
title.value = `详情-${route.params?.id}-changed`; title.value = `详情-${route.params?.id}-changed`;