二、Ant Design Pro 布局、菜单、新增页面、CSS Modules和services

一、开发

1、区块(block)

  • 区块是研发资产的一种,它是一系列快速搭建页面的代码片段,它可以帮助你快速的在项目中初始化好一个页面,帮助你更快速的开发代码。当前的区块都是页面级别的区块,你可以理解为它是一些项目中经常会用到的典型页面的模板,使用区块其实相当于从已有的项目中复制一些页面的代码到你当前的项目中
    • 以前开发一个页面:创建 JS -> 创建 CSS -> 创建 Model -> 创建 service -> 写页面组件。
    • 现在开发一个页面:下载区块 -> 基于区块初始化好的页面组件修改代码。
  • 使用区块:Ant Design Pro 中,使用 umi ui 进行区块管理
    • 在 Pro 中资产被分为了两种,区块和模板。区块可以类比为一个组件,而模板代表一个页面。区块现在支持所有 antd 中的 demo,可以更加快速的将 demo 导入到项目中去

2、布局

页面整体布局是一个产品最外层的框架结构,往往会包含导航、页脚、侧边栏、通知栏以及内容

  • 在 Ant Design Pro 中,我们抽离了使用过程中的通用布局,都放在 layouts 目录中,分别为
    • BasicLayout:基础页面布局,包含了头部导航,侧边栏和通知栏
    • UserLayout:抽离出用于登录注册页面的通用布局
    • BlankLayout:空白的布局
  • 配置路由来使用 Ant Design Pro 布局:通常布局是和路由系统紧密结合的,所以定义了布局,还需要配置路由。如配置下面的 config/config.ts ,通过如下配置定义每个页面的布局。
    • 路由里面的第一级数据就是我们的布局,后面的子路由,都嵌套在布局里面
// 这下面就有两个布局,一个是BasicLayout,另一个是UserLayout
routers: [
  {
    path: '/',
    component: '../layouts/BasicLayout', // 指定以下页面的布局
    routes: [
      // dashboard
      { path: '/', redirect: '/dashboard/analysis' },
      {
        path: '/dashboard',
        routes: [
          { path: '/dashboard/analysis', name: 'analysis', component: './Dashboard/Analysis' },
          { path: '/dashboard/monitor', name: 'monitor', component: './Dashboard/Monitor' },
          { path: '/dashboard/workplace', name: 'workplace', component: './Dashboard/Workplace' },
        ],
      },
    ],
  },
  {
    path: '/user',
    component: '../layouts/UserLayout',
    routes:[...]
   }
];

3、路由和菜单

路由和菜单是组织起一个应用的关键骨架,pro 中的路由为了方便管理,使用了中心化的方式,config.ts 统一配置和管理

  • 基本结构:脚手架通过结合一些配置文件、基本算法及工具函数,搭建好了路由和菜单的基本框架
    • 路由管理: 通过约定的语法根据在 config.ts 中配置路由。
    • 菜单生成: 根据路由配置来生成菜单。菜单项名称,嵌套路径与路由高度耦合。
    • 面包屑: 组件 PageHeaderWrapper 中内置的面包屑,也可通过 RouteContext 提供的信息自定义生成。

(1)路由

  • 简单说,这些就是根据路由的配置,来生成页面上面的菜单栏,所以在路由里面新增了下面这些属性
    • name: 当前路由在菜单和面包屑中的名称,注意这里是国际化配置的 key,具体展示菜单名可以在 /src/locales/zh-CN.ts 进行配置。即,生成的菜单项的文本
    • icon: 当前路由在菜单下的图标名。即,生成的菜单项的图标,这个icon可以是ant.design中的类名,也可以是一个url
    • hideInMenu: 当前路由在菜单中不展现,默认 false。
    • hideChildrenInMenu: 当前路由的子级在菜单中不展现,默认 false。
    • hideInBreadcrumb: 当前路由在面包屑中不展现,默认 false。
    • authority: 用来配置这个路由的权限,如果配置了将会验证当前用户的权限,并决定是否展示这个菜单栏,详见:权限管理。
{
  path: '/welcome',
  name: 'smile', // 这里在左边侧边栏显示一个,名字为smile的菜单,菜单左边的icon为smile的图标
  icon: 'smile',
  authority: ['admin'], // 这里这个权限,即管理员才能看到这个菜单栏
  component: './Welcome', // 点击菜单之后,进入到Welcome组件
}

(2)菜单

菜单根据 config.ts 生成: 如果你的项目并不需要菜单,你可以在 src/layouts/BasicLayout.tsx 中设置 menuRender={false}

从服务器请求菜单: 你可以在 src/layouts/BasicLayout.tsx 中修改 menuDataRender,并在代码中发起 http 请求,只需服务器返回对应的路由配置就好

面包屑:面包屑由 PageHeaderWrapper 实现,Layout 将 根据 MenuData 生成的 breadcrumb,并通过 PageHeaderWrapper 将其展现。 PageHeaderWrapper 封装至 Ant Design 的 PageHeader,api 完全相同

4、新增页面

这里的『页面』指配置了路由,能够通过链接直接访问的模块,要新建一个页面,通常只需要在脚手架的基础上进行简单的配置

  • (1)在 src / pages 下创建新的 js,less 文件。如果有多个相关页面,您可以创建一个新文件夹来放置相关文件
    • 样式文件默认使用CSS Modules,如果需要,可以导入antd less 变量 在文件的头部。这样可以轻松获取 antd 样式变量并在文件中使用它们
    • 这时候新增的js组件就只需要export出去就好了,而不需要ReactDOM.render
@import '~antd/lib/style/themes/default.less';

(2)将文件加入菜单和路由

(3)新增 model、service: 布局及路由都配置好之后,回到之前新建的 NewPage.js,可以开始写业务页面代码了!如果需要用到 dva 中的数据流,还需要在 src/models src/services 中建立相应的 model 和 service,具体可以参考脚手架内置页面的写法

(4)render()函数里面return出去的,依然所有的标签是必须包含在一个父元素里面的,不能return多个同级的子元素出去

// config/config.ts.    新增路由/Demo;会在菜单栏加上一个Demo菜单
export default {
  plugins,
  routes: [
    {
      path: '/',
      component: '../layouts/BasicLayout',
      authority: ['admin', 'user'],
      routes: [
        {
          path: '/',
          redirect: '/welcome',
        },
        {
          path: '/Demo',
          name: 'Demo',
          icon: 'smile',
          component: './Demo',
        },
      ],
    },
  ],

// 新建文件 src/pages/Demo.tsx
import React from 'react';
import { Button } from 'antd';

class demo extends React.Component {
    render() {
      return (
        <div>
    <Button type="primary">Primary</Button>
  </div>
      );
    }
  }
export default demo;

5、新增业务通用组件

  • 业务通用组件:这个业务通用组件就是被放在src/components路径下面的组件。对于可能被多处引用的功能模块,建议提炼成业务通用组件统一管理。特征如下
    • 只负责一块相对独立,稳定的功能;
    • 没有单独的路由配置
    • 可能是纯静态的,也可能包含自己的 state,但不涉及 dva 的数据流,仅受父组件(通常是一个页面)传递的参数控制

实际上这个就是定义一个组件,然后在其它地方去引入就好了

// 定义组件,写法如下
export default class HeaderDropdown extends PureComponent {
  render() {
    const { overlayClassName, ...props } = this.props;
    return (
      <Dropdown overlayClassName={classNames(styles.container, overlayClassName)} {...props} />
    );
  }
}

// 在其它组件中,使用如下
import ImageWrapper from '@/components/HeaderDropdown';  // 先引入那个组件
export default () => (
  <HeaderDropdown src="https://os.alipayobjects.com/rmsportal/mgesTPFxodmIwpi.png" desc="示意图" />
);

6、动态主题

这个用的是插件umi-plugin-antd-theme

7、CSS Modules

为了避免全局污染和选择器重复,脚手架默认使用 CSS Modules 模块化方案。css modules功能很单纯,只加入了局部作用域和模块依赖,这恰恰是网页组件最急需的功能

  • CSS Modules方案:这种方案,需要在js中import对应的less文件,然后在js 文件中设置 className 时,用引入的less对象的属性取代了原来的类名字符串,属性名跟 less 文件中对应的类名相同
    • 这里这个导入less的方式就避免了全局污染,并且可以在不同文件中,随意命名相同的类名
    • css modules里面引入图片资源:引入方式有两种。可以用相对路径../引入,也可以用别名@,不过需要在@前面加上~前缀
  • 局部作用域:local产生局部作用域的唯一方法,就是使用一个独一无二的class的名字,不会与其他选择器重名。css module中的类名如果被:local包着(默认情况下,不写这个,就默认为被它包着),就是局部作用域。在局部作用域的时候,构建工具会把类名编译为一个独一无二的哈希字符串,所以这时候就不会与其他选择器重名了
  • 全局作用域:global:全局作用域不会被编译成哈希字符串。只需要在类名前面加上这个:global就好了。
// example.ts
import styles from './example.less'; // 这里为引入less文件
<div className={styles.title}>123</div>; // 这里为使用引入的less对象的属性,来定义类名
// example.less
.title {margin-bottom: 16px;}

// 下面为被构建工具编译后,style.title在两个地方都,被编译成了一个哈希字符串
<div class="_3zyde4l1yATCOkgn-DBWEL">123</div> // example.ts
._3zyde4l1yATCOkgn-DBWEL {margin-bottom: 16px;} // example.less

// 下面为全局作用域,:global后面的不会被编译成哈希字符串
.title2 {
  :global {
    .ant-table-tbody > tr > td {white-space: normal;}
  }
}

// 同时有几个类名
<div className={`${styles.commonTable} ${styles.tablewrap}`}>

// 在css modules里面引入图片资源
.go {
    border-image: url('../../../../assets/img/appIcon.jpg') 20%;
    background: url(~@/foo.png); // 使用@别名来引入,不过就是需要添加~了
}
  • css文件类别: 在一个项目中,样式文件根据功能不同,可以划分为不同的类别
    • src/global.less: 全局样式文件,在这里你可以进行一些通用设置
    • src/defaultSetting.ts: 这个是全局的页面主题配置
    • src/utils/utils.less: 这里可以放置一些工具函数供调用,比如清除浮动 .clearfix
    • 模块样式: 针对某个模块/页面生效的文件
    • 通用模块级: 例如 src/layouts/BasicLayout.less,里面包含一些基本布局的样式,被 src/layouts/BasicLayout.ts 引用,项目中使用这种布局的页面就不需要再关心整体布局的设置
    • 页面级: 具体页面相关的样式,例如 src/routes/Dashborad/Monitor.less,里面的内容仅和本页面的内容相关
    • 组件级: 这个也很简单,就是组件相关的样式了,有一些在页面中重复使用的片段或相对独立的功能,你可以提炼成组件,相关的样式也应该提炼出来放在组件中,而不是混淆在页面里
    • 内联样式:以上样式类别都是针对独立的样式文件,有时样式配置特别简单,也没有重复使用,你也可以用内联样式 style={{ ... }} 来设置
// src/defaultSetting.ts页面主题配置
module.exports = {
  navTheme: 'dark', // 菜单的主题
  primaryColor: '#1890FF', // Ant Design 的主色调
  layout: 'sidemenu', // 菜单的布局,值为 sidemenu 菜单显示在左侧,值为 topmenu 菜单显示在顶部
  contentWidth: 'Fluid', // 内容的布局 Fixed 为定宽到1200px ,Fluid 为流式布局。
  fixedHeader: false, // 固定页头
  autoHideHeader: false, // 下滑时自动隐藏页头
  fixSiderbar: false, // 固定菜单
};

8、和服务端进行交互

在 Ant Design Pro 中,一个完整的前端 UI 交互到服务端处理流程是这样的

1、UI 组件交互操作;
2、调用 model 的 effect;
3、调用统一管理的 service 请求函数;
4、使用封装的 request.ts 发送请求;
5、获取服务端返回;
6、然后调用 reducer 改变 state;
7、更新 model。
  • 在 Ant Design Pro 中,统一的请求处理都放在 services 文件夹中,并且一般按照 model 维度进行拆分文件。如下:
    • 而services文件夹中,所有的具体请求,都是用的封装的request()函数,request()函数是在utils/request中,基于 fetch 进行了封装
    • utils/request.js: 这个request.js文件是用来封装请求的,里面用的es6的fetch()函数,而不是用的ajax了。这里面要先导入import fetch from 'dva/fetch'
// services/user.ts文件
import request from '@/utils/request';
export async function query(params) {
  return request('/go/query', { // 这些请求,都是用request进行了封装的
    method: 'POST',
    body: params,
  });
}

// utils/request.js
import fetch from 'dva/fetch';
export default function request(url, option) {
     const defaultOptions = {credentials: 'include',};
     const newOptions = { ...defaultOptions, ...options };
    return (
    Promise.race([ // 用race,来使长时间请求的时候,可以报错
      fetch(newUrl, newOptions), // 这里就是发送这个请求
      new Promise((resolve, reject) => {
        setTimeout(() => {
          const error = new Error(formatMessage({ id: `response.error.4082222` }));
          error.name = 408222;
          reject(error);
        }, newOptions.timeout || 3000022222);
      }),
    ])
      .then(checkStatus)
      // .then(response => cachedSave(response, hashcode))
      .then(response => {})
}

9、Mock 和联调

上一篇:python学习day12 函数Ⅳ (闭包&内置模块)


下一篇:Ant Design of Vue的select组件placeholder属性失效问题