Antd-Pro 如何定义一个菜单并且mock数据到页面


1、Antd-Pro介绍

Ant Design Pro 是一个企业级中后台前端/设计解决方案,我们秉承 Ant Design 的设计价值观,致力于在设计规范和基础组件的基础上,继续向上构建,提炼出典型模板/业务组件/配套设计资源,进一步提升企业级中后台产品设计研发过程中的『用户』和『设计者』的体验。随着『设计者』的不断反馈,我们将持续迭代,逐步沉淀和总结出更多设计模式和相应的代码实现,阐述中后台产品模板/组件/业务场景的最佳实践,也十分期待你的参与和共建。

2、前期准备

Antd-pro的技术栈基于 ES2015+Reactdvag2 和 antd,提前了解和学习这些知识会非常有帮助。官方的脚手架是基于dva-cli搭建的,所以了解dva是必须,否则很多代码是看不懂的,没有概念的自行百度去脑补一下。

3、环境搭建

从官方提供的git地址下载代码就可以了,用vscode打开代码。
  • 配置好registry
  • npm i 安装相关依赖
  • npm run build 编译成功

Antd-Pro 如何定义一个菜单并且mock数据到页面

npm start启动网站

Antd-Pro 如何定义一个菜单并且mock数据到页面

4、添加一个菜单

  • =》 pims-web/src/common/menu.js。  这个文件默认定义了菜单结构的json,我们加一个Demo1-节点类型的菜单。

    menu.js
    {
       name: 'Demo1',
       icon: 'user',
       path: 'demo1',
       authority: 'admin',
       children: [
         {
           name: '节点类型',
           path: 'demo11',
         }
       ],
     },
  • =》pims-web/src/common/router.js。  router.js是负责整个工程的路由事件。[“factoryTypes”],这个参数是我们新建的store,用来接收action的请求,这个是dva的特性,此处可以将我们的component与model绑定起来,实现state的更新和传递。
router.js
'/demo1/demo11': {
     component: dynamicWrapper(app, ["factoryTypes"], () => import('../routes/Demo1/demo11')),
   },

5、准备mock数据及配置

  • =》pims-web/mock/api.js。 这个文件里面定义了很多常量,用来初始化一些通用的数据。对于整个系统来说,请求接口是复杂并且繁多的,为了处理大量模拟请求的场景,我们也可以在mock文件夹下单独新建一个mock文件,根据业务需求来mock数据。这里已工厂模型类型为例:

    api.js
    //罗凯——工厂模型类型
    export const getFactoryTypes =  [{
      key: '0',
      name: '工厂',
    }, {
      key: '1',
      name: '车间',
    },
    {
      key: '2',
      name: '工段',
    }];
     
     
    export default {
      getNotice,
      getActivities,
      getFakeList,
      getFactoryTypes,
    };


    我们定义了一个叫 getFactoryTypes的常量,初始化了三条数据。然后在export中将我们定义的常量输出。

  • =》pims-web/.roadhogrc.mock.js。 添加代理请求,此处使用了roadhog的请求代理功能来处理代理请求,支持基于require动态分析的实时刷新,支持ES6语法。

    'GET /api/factoryTypes': getFactoryTypes,

    当客户端(浏览器)发送请求,如:GET /api/factoryTypes,那么本地启动的 roadhog server 会跟此配置文件匹配请求路径以及方法,如果匹配到了,就会将请求通过配置处理,就可以像样例一样,你可以直接返回数据,也可以通过函数处理以及重定向到另一个服务器。

    尝试请求接口,浏览器正常返回数据,说明配置已经成功了。


  • =》pims-web/src/services/api.js.。调用request模块将我们的接口封装成一个异步接口,并export。
export async function queryFactoryTypes() {
  return request('/api/factoryTypes');
}

6、如何请求数据

简单的话实际上直接通过ajax调用我们上面封装的api地址就可以直接拿到数据了,但是antd-pro引入了dva(redux)的概念,让我们能够更方便的进行状态管理。

redux解决了什么样的问题呢:

    •     用户的使用方式复杂
    • 不同身份的用户有不同的使用方式(比如普通用户和管理员)
    • 多个用户之间可以协作
    • 与服务器大量交互,或者使用了WebSocket
    • View要从多个来源获取数据
    • 某个组件的状态,需要共享
    • 某个状态需要在任何地方都可以拿到
    • 一个组件需要改变全局状态
    • 一个组件需要改变另一个组件的状态

redux可参考阮一峰的【Redux的基本用法】:http://www.ruanyifeng.com/blog/2016/09/redux_tutorial_part_one_basic_usages.html

  • =》pims-web/src/services/model/factoryTypes.js.。 建立我们自己的model,来接收action,更新state并传递给props。
    import { queryFactoryTypes } from '../services/api';
     
     
    export default {
      namespace: 'factoryTypes',
      state: {
        list: [],
      },
     
      effects: {
        *fetchList(_, { call, put }) {
          const response = yield call(queryFactoryTypes); //调用函数,成功后触发 `saveList` action,保存数据到 state
          yield put({
            type: 'saveList',
            payload: Array.isArray(response) ? response : [],
          });
        },
      },
     
      reducers: {
        saveList(state, action) {
          //保存数据到state
          return {
            ...state,
            list: action.payload,
          };
        },
      },
    };

前面我们新建了一个菜单,当然也要新建对应的页面文件和样式文件。=》pims-web/src/routes/Demo1.Antd-Pro 如何定义一个菜单并且mock数据到页面
.

Demo11.js
import React, {PureComponent} from 'react';
import ReactDom from 'react-dom';
import {Card, Layout, Table, Input, Icon, Button, Popconfirm } from 'antd';
import { connect } from 'dva';
var styles = require(`./style.css`);
class EditableCell extends React.Component {
    state = {
      value: this.props.value,
      editable: false,
    }
    handleChange = (e) => {
      const value = e.target.value;
      this.setState({ value });
    }
    check = () => {
      this.setState({ editable: false });
      if (this.props.onChange) {
        this.props.onChange(this.state.value);
      }
    }
    edit = () => {
      this.setState({ editable: true });
    }
    render() {
      const { value, editable } = this.state;
      return (
        <div className={styles["editable-cell"]}>
          {
            editable ?
              <div className={styles["editable-cell-input-wrapper"]}>
                <Input
                  value={value}
                  onChange={this.handleChange}
                  onPressEnter={this.check}
                />
                <Icon
                  type="check"
                  className={styles["editable-cell-icon-check"]}
                  onClick={this.check}
                />
              </div>
              :
              <div className={styles["editable-cell-text-wrapper"]}>
                {value || ' '}
                <Icon
                  type="edit"
                  className={styles["editable-cell-icon"]}
                  onClick={this.edit}
                />
              </div>
          }
        </div>
      );
    }
  }
  //connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])
  @connect(({factoryTypes, loading }) => (
    {
     factoryTypes,
     factoryTypesLoading: loading.effects['factoryTypes/fetchList'],
   }
 ))
  class EditableTable extends React.Component {
    componentDidMount() {
       const { dispatch } = this.props;
       dispatch({
          type: 'factoryTypes/fetchList',
        });
      }
    constructor(props) {
      super(props);
      this.columns = [{
        title: '类型编号',
        dataIndex: 'key',
        width: '15%',
      }, {
        title: '类型名称',
        dataIndex: 'name',
        width: '15%',
        render: (text, record) => (
            <EditableCell
              value={text}
              onChange={this.onCellChange(record.name, 'name')}
            />
          ),
      }, {
        title: '操作',
        dataIndex: 'operation',
        render: (text, record) => {
          return (
            this.state.dataSource.length > 1 ?
            (
              <Popconfirm title="Sure to delete?" onConfirm={() => this.onDelete(record.key)}>
                <a href="javascript:;">删除</a>
              </Popconfirm>
            ) : null
          );
        },
      }];
 
 
      this.state = {
        dataSource:"",
        count: 0,
      };
    }
    onCellChange = (key, dataIndex) => {
      return (value) => {
        const dataSource = [...this.state.dataSource];
        const target = dataSource.find(item => item.key === key);
        if (target) {
          target[dataIndex] = value;
          this.setState({ dataSource });
        }
      };
    }
    onDelete = (key) => {
      const dataSource = [...this.state.dataSource];
      this.setState({ dataSource: dataSource.filter(item => item.key !== key) });
    }
    handleAdd = () => {
      const { count, dataSource } = this.state;
      const newData = {
        key: count,
        name: `Edward King ${count}`,
      };
      this.setState({
        dataSource: [...dataSource, newData],
        count: count + 1,
      });
    }
   componentWillReceiveProps=()=>{
        const { factoryTypes: { list } } = this.props;
        this.setState({dataSource: list,count:3});
   }
    render() {
      const { dataSource } = this.state;
      const columns = this.columns;
      return (
        <Card bordered={false}>
             <Button className={styles["editable-add-btn"]} onClick={this.handleAdd}>添加</Button>
          <Table bordered dataSource={dataSource} columns={columns} size="small"/>
        </Card>
      );
    }
  }
export default() => (
   <EditableTable />
);
  • react-redux 的文档中,对 @connect 的描述是一段很难理解的英文,我一直没有搞清楚这个东西的用法。大致的作用就是连接React组件与 Redux store,也就是我们前面定义的model。
  • 在组建render完成之后调用 componentDidMount 函数,我们通过 dispatch 来触发action,从而来更新state并且传递给组件的props。
  • 最后在组件接收到的props参数后调用 componentWillReceiveProps 获取更新后的state,根据新的state来初始化组件内部的state,完成页面的数据绑定。


style.css
/* @import '~antd/lib/style/themes/default.less';
@import '../../utils/utils.less'; */
 
 
.editable-cell {
  position: relative;
}
 
.editable-cell-input-wrapper,
.editable-cell-text-wrapper {
  padding-right: 24px;
}
 
.editable-cell-text-wrapper {
  padding: 5px 24px 5px 5px;
}
 
.editable-cell-icon,
.editable-cell-icon-check {
  position: absolute;
  right: 0;
  width: 20px;
  cursor: pointer;
}
 
.editable-cell-icon {
  line-height: 18px;
  display: none;
}
 
.editable-cell-icon-check {
  line-height: 28px;
}
 
.editable-cell:hover .editable-cell-icon {
  display: inline-block;
}
 
.editable-cell-icon:hover,
.editable-cell-icon-check:hover {
  color: #108ee9;
}
 
.editable-add-btn {
  margin-bottom: 8px;
}

7、最终的效果

Antd-Pro 如何定义一个菜单并且mock数据到页面