布局

afc163chenshuai2144ddcat1115RichardStarkyizaoviztorbran-niem4jingnikoguWhatAKitty
上次修改时间:2021-05-07 16:23:76

布局是一个中后台应用必备的,一个布局 + ProTable + Form 即可获得一个 CRUD 页面。

Pro 中内置了 plugin-layout 来减少样板代码。简单的使用中我们只需要在 config.ts 中配置 layout 属性就可以实现通用的页面布局。

UI 配置

布局样式

layout 插件 与pro-layout 配置的配置形同。

推荐先使用 Pro 站点 的右侧抽屉来帮助你完成布局相关的整体风格、主题色、导航模式、内容区域宽度、固定 Header、固定侧边菜单、色弱模式等配置选择。然后将拷贝的配置粘贴与 layout 配置中。

Pro 站点

// config.js
import { defineConfig } from 'umi';

export const config = defineConfig({
  layout: {
    name: 'Ant Design Pro',
    logo: 'https://preview.pro.ant.design/static/logo.f0355d39.svg',
    // copy from pro site
    navTheme: 'dark',
    primaryColor: '#1890ff',
    layout: 'sidemenu',
    contentWidth: 'Fluid',
    fixedHeader: false,
    fixSiderbar: false,
    title: 'Ant Design Pro',
    pwa: false,
    iconfontUrl: '',
  },
});

菜单展示

我们可以在 route 中进行 menu 相关配置,来决定当前路由是否会被渲染在菜单中。详细配置说明

  • 当不需要展示在菜单中展示时,可以在路由上配置 hideInMenu 或者删除 menu 相关的配置;
  • 当不需要展示 children 时,可以在路由上配置 hideChildrenInMenu;
  • 当不需要展示自己,只展示 children,可以在路由上配置 flatMenu;
  • 如果没有配置 menu,没有配置 name 的话,则该路由不会在侧边栏中出现。
// config/routes.ts
export default [
  {
    path: '/overview',
    component: 'Overview/index',
    menu: {
      name: 'overview',
      icon: 'testicon',
      flatMenu: false,
      hideInMenu: false,
      hideChildrenInMenu: false,
    },
  },
];

菜单国际化

通过 layout 配置的 locale 配置开启国际化。

开启后路由里配置的菜单名会被当作菜单名国际化的 key,插件会去 locales 文件中查找 menu.[key]对应的文案,默认值为改 key。

// locale/zh-CN.js
export default {
  'menu.overview': '总览',
};

导航右上角

右上角

用户名以及国际化可以通过配置拥有默认的 UI。国际化会通过检测 locale 目录下的文件来展示可供切换的语言种类。

用户名、头像信息可以通过配置全局初始化信息来提供数据。

// src/app.ts
export function getInitialState() {
  return {
    name: 'Serati Ma',
    avatar: 'https://gw.alipayobjects.com/zos/antfincdn/XAosXuNZyF/BiazfanxmamNRoxxVxka.png',
  };
}

退出登陆的逻辑也可以通过配置来自定义。

// src/app.ts
export const layout = {
  logout: () => {
    alert('退出登录成功');
  },
};

如果以上满足不了需求,可以通过以下接口实现右上角 UI 完全的自定义。

// src/app.tsx
import React from 'react';

export const layout = {
  rightRender: (initialState, setInitialState) => {
    // xxx
    return 'xxx';
  },
};

footer

插件并没有提供默认的 footer UI。可以通过以下配置来完成自定义。想和 Pro 官网使用相同的样式可以参考:https://procomponents.ant.design/components/layout#footer

// src/app.tsx
import React from 'react';

export const layout = {
  footerRender: () => {
    // xxx
    return <xxx />;
  },
};

路由配置

权限路由

当需要对某些路由做权限管控,能够搭配内置权限方案来方便的实现。当用户访问没有权限的路由时,layout 会提供默认的无权限页面。

403

详细的配置方案可:点击查看

1.通过全局初始化信息来请求权限相关的初始化信息

// src/app.ts
export async function getInitialState() {
  const data = await fetchXXX();
  return data;
}

2.新增权限定义文件

// src/access.ts
import { InitialState } from 'umi';

export default function accessFactory(initialState: InitialState) {
  return {
    readArticle: initialState.name === 'haha',
  };
}

3.给路由配置权限

// config/route.ts
export default [
  {
    path: '/overview',
    component: 'Overview/index',
    name: 'overview',
    icon: 'testicon',
    access: 'readArticle',
  },
];

404 / 403

内置布局会对不存在的路由、无权访问的路由都会展示默认的 UI。无权访问如上所示。

访问不存在的 UI 时,默认 UI 如下 404

嵌套布局

有时我们的页面可能会有一些全局的通用的处理逻辑或者 UI,会希望在页面加载前完成,通常会希望可以在内置布局内部再包一层 layout 来完成需求。

// config/routes.ts
export default [
  {
    path: '/',
    component: '../layout/index',
    menu: {
      flatMenu: true,
    },
    routes: [
      {
        path: '/',
        redirect: '/overview',
      },
      {
        path: '/overview',
        component: 'Overview/index',
        menu: {
          name: 'overview',
          icon: 'testicon',
        },
      },
    ],
  },
];

// src/layout/index.tsx
const Layout = ({ children }) => children;
export default Layout;

根据路由隐藏左侧菜单、隐藏导航头、footer

有时我们的页面可能存在一些沉浸式的设计,需要针对路由隐藏部分布局。可以通过添加扩展路由配置来实现。详细配置

// config/route.ts
export default [
  {
    path: '/overview',
    component: 'Overview/index',
    name: 'overview',
    icon: 'testicon',
    layout: {
      hideMenu: false,
      hideNav: false,
      hideFooter: false,
    },
  },
];

菜单布局展示方式的修改

有时菜单可能需要于顶部显示,左侧显示,或者顶部显示一级菜单,左侧显示二三级菜单。我们可以修改 defaultSettings 中的 layout 的配置来决定菜单的展示方式。

  • top 菜单于顶部展示
  • side 菜单于左侧展示
  • mix 菜单于顶部和左侧混合展示,需要注意,当 mix 模式时,需要添加splitMenus: true,顶部才可以正确展示一级菜单
// config/defaultSettings.ts
export default {
  layout: 'mix',
  splitMenus: true,
};

同时,当使用 mix 模式后,点击一级菜单,并不会直接定位到第一个子级菜单页面,而是会呈现空白页面,需要于配置中设置一下 redirect 的地址

[
  {
    "path": "/test/list",
    "component": "./test/list"
  },
  {
    "path": "/test/list/testAdd",
    "component": "./test/list/testAdd"
  },
  {
    "redirect": "./test/list"
  }
]

自定义布局

有些时候我们不想使用自带的布局,想做更多的自定义,我们也提供了灵活的自定义方案。

布局本质上是一个特殊的组件,子页面将作为属性传递到布局组件中。 最简单的布局是这样的:

// 必须渲染 children,否则子级路由无法显示
// 在这里您还可以设置全局提供
const layout = ({ children }) => children;
export default layout;

我们在 src/layouts/中创建一个新的 BaseLayout.tsx,复制上面的代码,并在 config/config.ts 添加如下代码:

defineConfig({
  // added configuration
  routes: {
    path: '/',
    component: '.../layouts/BaseLayout',
  },
});

我们可以对 children 进行修改或者包裹,ProLayout 组件就是通过这样的方案来注入菜单等配置。children 是什么与你当前的路径和 layout 在项目中的配置有关系,如果满足不了需求可以试试调整位置。

下面是默认的 ProLayout 的配置,我们可以复制默认代码然后再自定义:

/**
 * Ant Design Pro v4 use `@ant-design/pro-layout` to handle Layout.
 *
 * @see You can view component api by: https://github.com/ant-design/ant-design-pro-layout
 */
import type {
  MenuDataItem,
  BasicLayoutProps as ProLayoutProps,
  Settings,
} from '@ant-design/pro-layout';
import ProLayout, { DefaultFooter } from '@ant-design/pro-layout';
import React from 'react';
import { Link } from 'umi';
import { GithubOutlined } from '@ant-design/icons';
import RightContent from '@/components/GlobalHeader/RightContent';
import logo from '../assets/logo.svg';

export type BasicLayoutProps = {
  breadcrumbNameMap: Record<string, MenuDataItem>;
  route: ProLayoutProps['route'] & {
    authority: string[];
  };
  settings: Settings;
} & ProLayoutProps;

export type BasicLayoutContext = { [K in 'location']: BasicLayoutProps[K] } & {
  breadcrumbNameMap: Record<string, MenuDataItem>;
};

const menuDataRender = (menuList: MenuDataItem[]): MenuDataItem[] =>
  menuList.map((item) => {
    return {
      ...item,
      children: item.children ? menuDataRender(item.children) : undefined,
    };
  });

const defaultFooterDom = (
  <DefaultFooter
    copyright={`${new Date().getFullYear()} 蚂蚁集团体验技术部出品`}
    links={[
      {
        key: 'Ant Design Pro',
        title: 'Ant Design Pro',
        href: 'https://pro.ant.design',
        blankTarget: true,
      },
      {
        key: 'github',
        title: <GithubOutlined />,
        href: 'https://github.com/ant-design/ant-design-pro',
        blankTarget: true,
      },
      {
        key: 'Ant Design',
        title: 'Ant Design',
        href: 'https://ant.design',
        blankTarget: true,
      },
    ]}
  />
);

const BasicLayout: React.FC<BasicLayoutProps> = (props) => {
  const {
    children,
    location = {
      pathname: '/',
    },
  } = props;

  const { formatMessage } = useIntl();

  return (
    <ProLayout
      logo={logo}
      formatMessage={formatMessage}
      {...props}
      onCollapse={handleMenuCollapse}
      onMenuHeaderClick={() => history.push('/')}
      menuItemRender={(menuItemProps, defaultDom) => {
        if (
          menuItemProps.isUrl ||
          !menuItemProps.path ||
          location.pathname === menuItemProps.path
        ) {
          return defaultDom;
        }
        return <Link to={menuItemProps.path}>{defaultDom}</Link>;
      }}
      breadcrumbRender={(routers = []) => [
        {
          path: '/',
          breadcrumbName: formatMessage({ id: 'menu.home' }),
        },
        ...routers,
      ]}
      itemRender={(route, params, routes, paths) => {
        const first = routes.indexOf(route) === 0;
        return first ? (
          <Link to={paths.join('/')}>{route.breadcrumbName}</Link>
        ) : (
          <span>{route.breadcrumbName}</span>
        );
      }}
      footerRender={() => defaultFooterDom}
      menuDataRender={menuDataRender}
      rightContentRender={() => <RightContent />}
    >
      {children}
    </ProLayout>
  );
};

export default BasicLayout;

更多

如果内置的 layout 插件满足不了您的需求,可以通过 issue 告诉我们,我们会尽快处理。

你也可以通过以下配置来关闭默认的功能。

关闭 Layout 插件

将 layout 配置设置成 false。

// config.js
import { defineConfig } from 'umi';

export const config = defineConfig({
  layout: false,
});