little-bee-client

little-bee-admin-logo

Why

为了让大家了解“蜂监工”项目,这篇博文详细介绍了“蜂监工”项目的工作原理。

“蜂监工”的目的什么?

“蜂监工”是“蜂乐园”平台后台客户端,用于监管“蜂乐园”平台,包括,查看“蜂乐园”服务器状态,管理“蜂乐园”用户、数据等。

“蜂监工”为什么选择”Ant Design Pro”框架?

“蜂监工”的目的是为了管理“蜂乐园”平台,那么对它至少有两个要求:

“react-admin”和”Ant Design Pro”对比

React社区中,“react-admin”“Ant Design Pro”是比较成熟的后台管理框架。它们都不仅集成了成熟的UI框架,还封装了react-routerreact-redux等通用模块。

UI风格

“react-admin”用的是“Material-UI”UI框架,而”Ant Design Pro”用的是“Ant Design”UI框架。对UI风格在这里不予置评,下面列出了两种UI框架的Button组件,大家可根据个人喜好进行选择:

与服务器交互方式

个人喜好

个人感觉,”react-admin”封装地相对彻底,Data Provider屏蔽了大部分细节,如果通用的Data Provider可以满足用户需求,上手会非常快。而”Ant Design Pro”并不直接封装react-routerreact-redux等模块,而是直接利用其他框架,如:“UmiJS”“DvaJS”等。因此,用户在使用”Ant Design Pro”时,还需对其他框架有所了解。如果您对”Ant Design Pro”有进一步的兴趣,可以参考”蜂博客”什么是”Ant Design Pro”

封装和灵活度是硬币的两面。

如果封装过度,势必影响使用的灵活性。如果封装过于简单,也会暴露过多细节给用户,给二次开发造成沉重的负担。一个好的框架,应该是开放而友好的。轻度用户可以快速上手,重度用户也有办法深度开发。在这点上,个人觉得”Ant Design Pro”较”react-admin”更出色。尤其是”Ant Design Pro”可以利用“DvaJS”,十分方便地进行redux模式开发。如果大家对redux模式感兴趣,可以参考“蜂博客”:什么是”redux”模式

What

“蜂监工”是什么项目?

“蜂监工”是“蜂乐园”项目中的基于Ant Design Pro框架,支持REST风格的后台客户端,是“蜂乐园”的协调者,用于管理“蜂乐园”用户、数据等。之所以叫“蜂监工”,是因为它负责监管整各“蜂乐园”平台。当前“蜂监工”已经完成了框架的搭建,如:可以和服务器进行REST风格的通讯,实现用户Token登录等。但是,具体功能还在开发中,希望以后其可以有更多强大的功能供用户使用。

“蜂监工”是如何工作的?

“蜂监工”是如何实现的?

“蜂监工”是一个基于“Ant Design Pro”框架的后台客户端。下面罗列了部分“蜂监工”涉及的技术。如果您对以项某项内容感兴趣,那么您可以继续往下看。在“How”部分,我们将详细介绍“蜂监工”是如何实现的。

How

“蜂监工”项目结构

.
├── README.md
├── build
|   └── dist                   // 存放Typescript编译输出
├── config                     // 存放用户配置
|   ├── config.ts              // 主配置文件,可定义菜单,路由,布局等
|   ├── defaultSettings.ts     // 默认配置,包括菜单样式,主题颜色等
|   ├── plugin.config.ts       // 项目插件配置
|   └── themePluginConfig.ts   // 可选主题
├── dist                       // 存放项目build输出
├── jest-puppeteer.config.js   // jest配置
├── jest.config.js             // jest配置
├── jsconfig.json              // 对Javascript文件的主配置
├── mock                       // 测试的mock文件
├── package.json               // 项目包管理文件
├── public                     // 静态资源存放目录,如icon,build后会被一起复制到/dist目录
├── src                        // 源文件
|   ├── assets                 // 静态资源存放目录,如logo,build后会被一起复制到/dist/static目录
|   ├── components             // React组件
|   ├── e2e                    // 集成测试用例
|   ├── global.less            // 框架文件,全局样式
|   ├── global.tsx             // 框架文件,和PWA相关
|   ├── layouts                // 通用布局
|   ├── locales                // 国际化资源
|   ├── manifest.json          // 框架文件,和PWA相关
|   ├── models                 // 全局 dva model
|   ├── pages                  // 页面入口,可在/config/config.ts中和路由绑定
|   ├── service-worker.js      // 框架文件
|   ├── services               // 后台接口服务
|   ├── typings.d.ts           // 对某些全局模块进行声明,服务于Typescript
|   └── utils                  // 工具库
├── tests                      // 测试配置脚本
└── tsconfig.json              // 对Typescript文件的主配置

“蜂监工”如何创建一个无交互页面

“蜂监工”渲染一个新页面只需要两个步骤:

下面以“welcome”页面为例子,阐述“如何创建一个无交互页面”。

“welcome”页面效果

为”welcome”页面添加路由

“/config/config.ts”routes[]中添加如下代码:

{
    path: '/welcome',
    name: 'welcome',
    icon: 'smile',
    component: './welcome',
},

具体步骤可参考官网教程

为”welcome”页面添加组件

“/src/pages”目录中添加新文件夹“welcome”,并在其中新建入口文件“index.txs”,代码如下:

// ./welcome/index.txs
export default (): React.ReactNode => (
  <PageHeaderWrapper>
    <Card>
      ...
      <Carousel autoplay>
        <div className={styles.container}>
          <img src="http://q53wkmg88.bkt.clouddn.com/1.png" alt="Loading" />
        </div>
        ...
      </Carousel>
    </Card>
  </PageHeaderWrapper>
);

“index.txs”文件export了一个React-JSX组件。此组件会被框架的Layout组件接收并渲染。每个页面的Layout也是定义在“/config/config.ts”中。”welcome”的Layout组件为BasicLayout,定义如下:

routes: [
{
    path: '/',
    component: '../layouts/BasicLayout', // 此块区域的Layout都为BasicLayout
    authority: ['admin', 'user'],
    routes: [
    {
        path: '/welcome',
        name: 'welcome',
        icon: 'smile',
        component: './welcome',
    },
]

Layout的详细配置,可参考官网教程

“蜂监工”如何创建一个服务器交互页面

在”Ant Design Pro”中,一个完整的UI交互到服务器处理流程如下(官网教程):

下面以“request”页面为例子,阐述“如何创建一个服务器交互页面”。

“request”页面效果

为”request”页面添加路由

“request”页面路由的创建方法和”无交互页面”是一样的,可参考上面部分:为”welcome”页面添加组件

为”request”页面添加”UI”组件

“request”组件目录结构如下:

└──request               // Request组件主目录
    ├── apiList          // Request组件的子组件目录,对应request页面左侧的"URL"列表
    │   ├── index.less   // ApiList子组件样式
    │   └── index.tsx    // ApiList子组件
    ├── command          // Request组件的子组件目录,对应request页面右侧的命令发送和接收列表
    │   ├── index.less   // Command子组件样式
    │   └── index.tsx    // Command子组件
    ├── index.less       // Request组件样式
    └── index.tsx        // Request组件

不同于”welcome”页面的”UI”组件,”request”页面的”UI”组件是可以和用户交互的。如下图所示,用户点击”URL”列表,右侧的输入框中的内容会变化。 request-action

什么是”redux”

“蜂监工”采用”redux”模式实现不同组件的状态共享,以达到上面的效果。”Ant Design Pro”利用”UmiJS”和”DvaJS”可快速实现”redux”模式,可参考”UmiJS”的官方教程:Use umi with dva。”redux”模式相对复杂,其中涉及很多概念,简单来说,就是两点:1. 状态变化单向流动;2. 全局共享状态。详情可参考“蜂博客”:什么是”redux”

“Ant Design Pro”调试”redux”模式非常方便,通过“Redux DevTools”工具,可以查看所有共享状态的变化。 redux-dev-tools

如何实现”redux”

/* ./src/models/request.ts */
// 共享状态的类型
export interface RequestStateType {
    method?: 'POST' | 'GET' | 'PUT' | 'PATCH' | 'DELETE';
    url?: string;
    ...
}

reducers: {
    // 修改共享状态的方法
    changeApiRoute(state, { payload }) {
      return {
        ...state,
        method: payload.method ? payload.method : state?.method,
        url: payload.url ? payload.url : state?.url,
      };
    },
}
<List.Item
    onClick={() => {
        const { dispatch } = props;
        // dispatch 方法会调用request model中的changeApiRoute,按照payload修改共享状态
        dispatch({
            type: 'request/changeApiRoute',
            payload: { method: item.method, url: item.url },
        });
        ...
    }}
>
<Input
    value={request.url}
    ...
/>
...
// 连接Command组件和共享状态,以此通过"request"获取共享状态"request.url"
export default connect(({ request }: ConnectState) => ({
    request,
}))(Command);

至此,”request”页面的”UI”界面就开发完成了,下面为”request”页面添加服务器交互功能。

为”request”页面添加服务器交互

“request”页面服务器交互效果

如下图所示,当点击”Send”按钮时,网页会向服务器发起request请求,服务器处理完后,返回respond给网页,网页将收到的数据显示到results框中。

send-action

如何用”redux”实现服务器交互

与”UI”交互相同的是,服务器交互也是通过改变全局状态,通知相关组件重新渲染数据。而与”UI”交互不同的是,服务器交互是异步操作,因此需要触发effect操作。

/* ./src/models/request.ts */
export interface RequestStateType {
    respond?: string; // 用于存储服务器返回数据
    ...
}

reducers: {
    // 根据收到的数据,更新全局状态"respond"
    saveResponse(state, action) {
        return {
            ...state,
            respond: JSON.stringify(action.payload),
        };
    },
},

effects: {
    *send({ payload: { method, url, body } }, { call, put }) {
        // 调用"apiRequest"函数,向服务器发起请求
        const { data } = yield call(apiRequest, method, url, body);
        // 得到服务器返回的数据后,通过"saveResponse"更新共享状态
        yield put({
            type: 'saveResponse',
            payload: data,
        });
    },
},

<Button
    onClick={() => {
        const { dispatch } = props;
        // dispatch 方法会调用request model中的send,payload包含发送给服务器的数据
        dispatch({
        type: 'request/send',
        payload: { method: request.method, url: request.url, body: request.body },
        });
    }}
    ...
>
<div className={styles.result}>{request.respond}</div>
...
// 连接Command组件和共享状态,以此通过"request"获取共享状态"request.respond"
export default connect(({ request }: ConnectState) => ({
    request,
}))(Command);
/* src/services/apiRoutes.ts */
export async function apiRequest(method: string, url: string, body?: string): Promise<any> {
  let params: any;
  if (body) {
    try {
      params = JSON.parse(body);
    } catch (err) {
      notification.error({
        message: err.name,
        description: err.message,
      });
    }
  }
  // 调用"umi-request"库,发起请求
  return request(url, {
    method,
    data: params,
  });
}