Ant Design Pro学习之组件化


阿里粑粑开源的管理框架Ant Design Pro使用记录之规范学习,组件化。

简书地址

同事写了一个我目前看着比较正规化的组件式页面,在此作为学习标准贴一下,先看个效果图:
列表

编辑1

编辑2

这是一个oauth的client管理的页面,主要代码如下:
api列表数据结构

{
    "code": 0,
    "message": "操作成功",
    "data": {
        "content": [
            {
                "clientId": "usercenter-manage",
                "clientName": "测试app",
                "resourceIds": "usercenter/manage,smscenter/api",
                "clientSecret": "",
                "scope": "read,write,trust",
                "authorizedGrantTypes": "password,refresh_token",
                "webServerRedirectUri": null,
                "authorities": null,
                "accessTokenValidity": 7200,
                "refreshTokenValidity": null,
                "additionalInformation": null,
                "autoapprove": null,
                "smsCodeLength": 4,
                "smsCodeExpire": 10,
                "smsCodeSign": "【xxxxxx】",
                "platformCode": null,
                "updateTime": "2019-03-25T14:27:26.000+0000",
                "createTime": null
            }
        ],
        "pageable": {
            "sort": {
                "sorted": true,
                "unsorted": false,
                "empty": false
            },
            "offset": 0,
            "pageSize": 10,
            "pageNumber": 0,
            "paged": true,
            "unpaged": false
        },
        "totalPages": 1,
        "totalElements": 9,
        "last": true,
        "size": 10,
        "number": 0,
        "first": true,
        "numberOfElements": 9,
        "sort": {
            "sorted": true,
            "unsorted": false,
            "empty": false
        },
        "empty": false
    }
}

组件化嘛,文件自然比较多,打个标识

1、ClientList:页面渲染js

import React, { PureComponent } from 'react';
import { connect } from 'dva';
import { Popconfirm, Card, Table, Button, Divider, Tag, message } from 'antd';
import PageHeaderWrapper from '@/components/PageHeaderWrapper';
import DescriptionList from '@/components/DescriptionList';
import ClientForm from './ClientForm';
import { humanizeTime, getOAuthTypeNames } from './ClientUtil';
import styles from '../Common/TableList.less';

const { Description } = DescriptionList;

@connect(({ client, loading }) => ({
  client,
  loading: loading.effects['client/fetch'],
  submitting: loading.effects['client/update'],
}))
class ClientList extends PureComponent {
  columns = [
    {
      title: '应用ID',
      dataIndex: 'clientId',
    },
    {
      title: '应用名称',
      dataIndex: 'clientName',
    },
    {
      title: (
        <span>
          Token
          <br />
          有效期
        </span>
      ),
      dataIndex: 'accessTokenValidity',
      render: value => humanizeTime(value),
    &#125;,
    &#123;
      title: (
        <span>
          RefreshToken
          <br />
          有效期
        </span>
      ),
      dataIndex: 'refreshTokenValidity',
      render: value => humanizeTime(value),
    &#125;,
    &#123;
      title: '操作',
      dataIndex: 'action',
      render: (text, record) => (
        <span>
          <a
            onClick=&#123;() => &#123;
              this.handleOpenForm(record);
            &#125;&#125;
          >
            编辑
          </a>
          <Divider type="vertical" />
          <Popconfirm
            title="确认删除?"
            onConfirm=&#123;() => &#123;
              this.handleRemove(record);
            &#125;&#125;
            okText="确认"
            cancelText="取消"
          >
            <a href="#">删除</a>
          </Popconfirm>
        </span>
      ),
    &#125;,
  ];

  componentDidMount() &#123;
    const &#123; dispatch &#125; = this.props;
    dispatch(&#123; type: 'client/fetch' &#125;);
  &#125;

  handleTableChange = pagination => &#123;
    const &#123; current, pageSize &#125; = pagination;
    const &#123; dispatch &#125; = this.props;
    dispatch(&#123; type: 'client/fetch', payload: &#123; page: current - 1, size: pageSize &#125; &#125;);
  &#125;;

  handleTableExpand = record => &#123;
    const toTags = items => items.map((value, index) => <Tag key=&#123;index&#125;>&#123;value&#125;</Tag>);
    return (
      <div>
        <DescriptionList size="small">
          <Description term="授权模式">
            &#123;toTags(getOAuthTypeNames(record.authorizedGrantTypes))&#125;
          </Description>
          <Description term="平台编码">&#123;record.platformCode&#125;</Description>
          <Description term="资源IDS">&#123;record.resourceIds&#125;</Description>
        </DescriptionList>
        <DescriptionList style=&#123;&#123; marginTop: 15 &#125;&#125; size="small">
          <Description term="短信验证码长度">&#123;record.smsCodeLength&#125;</Description>
          <Description term="短信验证码有效期">
            &#123;record.smsCodeLength ? `$&#123;record.smsCodeLength&#125;分钟` : ''&#125;
          </Description>
          <Description term="短信验证码签名">&#123;record.smsCodeSign&#125;</Description>
        </DescriptionList>
      </div>
    );
  &#125;;

  handleOpenForm = formData => &#123;
    const &#123; dispatch &#125; = this.props;
    dispatch(&#123; type: 'client/openForm', payload: formData &#125;);
  &#125;;

  handleCloseForm = () => &#123;
    const &#123; dispatch &#125; = this.props;
    dispatch(&#123; type: 'client/closeForm' &#125;);
  &#125;;

  handleAdd = values => &#123;
    const &#123; dispatch &#125; = this.props;
    dispatch(&#123; type: 'client/update', payload: values &#125;).then(() => &#123;
      message.success('操作成功');
    &#125;);
  &#125;;

  handleRemove = record => &#123;
    const &#123; dispatch &#125; = this.props;
    dispatch(&#123; type: 'client/remove', payload: record.clientId &#125;).then(() => &#123;
      message.success('删除成功');
    &#125;);
  &#125;;

  render() &#123;
    const &#123;
      client: &#123; list, form &#125;,
      loading,
      submitting,
    &#125; = this.props;
    const paginationProps = &#123;
      showSizeChanger: true,
      showQuickJumper: true,
      ...list.pagination,
    &#125;;
    return (
      <PageHeaderWrapper title="应用列表">
        <Card bordered=&#123;false&#125;>
          <div className=&#123;styles.tableList&#125;>
            <div className=&#123;styles.tableListForm&#125; />
            <div className=&#123;styles.tableListOperator&#125;>
              <Button icon="plus" type="primary" onClick=&#123;this.handleOpenForm&#125;>
                新建
              </Button>
            </div>
            <Table
              rowKey="clientId"
              size="middle"
              columns=&#123;this.columns&#125;
              loading=&#123;loading&#125;
              dataSource=&#123;list.data&#125;
              pagination=&#123;paginationProps&#125;
              onChange=&#123;this.handleTableChange&#125;
              expandedRowRender=&#123;this.handleTableExpand&#125;
            />
          </div>
        </Card>
        <ClientForm
          data=&#123;form.data&#125;
          visible=&#123;form.visible&#125;
          submitting=&#123;submitting&#125;
          onClose=&#123;this.handleCloseForm&#125;
          onSave=&#123;this.handleAdd&#125;
        />
      </PageHeaderWrapper>
    );
  &#125;
&#125;

export default ClientList;

2、ClientForm:添加编辑单条数据的Form

import React, &#123; PureComponent &#125; from 'react';
import &#123; Modal, Form, Input, Tabs, InputNumber &#125; from 'antd';
import SecretInput from './SecretInput';
import PeriodInput from './PeriodInput';
import GrantTypeInput from './GrantTypeInput';
import SmsSignInput from './SmsSignInput';

const &#123; Item: FormItem &#125; = Form;
const &#123; TabPane &#125; = Tabs;

@Form.create()
class ClientForm extends PureComponent &#123;
  state = &#123;
    tabKey: '1',
  &#125;;

  reset = () => &#123;
    const &#123; form &#125; = this.props;
    form.resetFields();
    this.setState(&#123; tabKey: '1' &#125;);
  &#125;;

  render() &#123;
    const &#123; data, visible, submitting, onSave, onClose, form &#125; = this.props;
    const &#123; tabKey &#125; = this.state;
    const formItemLayout = &#123;
      labelCol: &#123; span: 5 &#125;,
      wrapperCol: &#123; span: 15 &#125;,
    &#125;;
    const title = data.clientId ? '更新应用' : '添加应用';
    return (
      <Modal
        style=&#123;&#123; top: 10 &#125;&#125;
        width=&#123;800&#125;
        title=&#123;title&#125;
        visible=&#123;visible&#125;
        confirmLoading=&#123;submitting&#125;
        onCancel=&#123;() => &#123;
          this.reset();
          onClose();
        &#125;&#125;
        onOk=&#123;() => &#123;
          form.validateFields((err, values) => &#123;
            if (!err) onSave(values);
            else this.setState(&#123; tabKey: '1' &#125;);
          &#125;);
        &#125;&#125;
      >
        <Form>
          <Tabs
            tabPosition="left"
            activeKey=&#123;tabKey&#125;
            onChange=&#123;activeKey => this.setState(&#123; tabKey: activeKey &#125;)&#125;
          >
            <TabPane tab="授权设置" key="1">
              <FormItem label="应用ID" &#123;...formItemLayout&#125;>
                &#123;form.getFieldDecorator('clientId', &#123;
                  rules: [
                    &#123;
                      type: 'string',
                      required: true,
                      message: '应用ID不能为空!',
                    &#125;,
                  ],
                  initialValue: data.clientId,
                &#125;)(<Input disabled=&#123;!!data.clientId&#125; />)&#125;
              </FormItem>
              <FormItem label="应用名称" &#123;...formItemLayout&#125;>
                &#123;form.getFieldDecorator('clientName', &#123;
                  rules: [
                    &#123;
                      type: 'string',
                      required: true,
                      message: '应用名称不能为空!',
                    &#125;,
                  ],
                  initialValue: data.clientName,
                &#125;)(<Input />)&#125;
              </FormItem>
              <FormItem label="资源IDs" &#123;...formItemLayout&#125;>
                &#123;form.getFieldDecorator('resourceIds', &#123;
                  rules: [
                    &#123;
                      type: 'string',
                      required: true,
                      message: '资源IDs不能为空!',
                    &#125;,
                  ],
                  initialValue: data.resourceIds,
                &#125;)(<Input />)&#125;
              </FormItem>
              <FormItem label="授权类型" &#123;...formItemLayout&#125;>
                &#123;form.getFieldDecorator('authorizedGrantTypes', &#123;
                  rules: [
                    &#123;
                      type: 'string',
                      required: true,
                      message: '请选择授权类型!',
                    &#125;,
                  ],
                  initialValue: data.authorizedGrantTypes,
                &#125;)(<GrantTypeInput />)&#125;
              </FormItem>
              <FormItem label="Token有效期" &#123;...formItemLayout&#125;>
                &#123;form.getFieldDecorator('accessTokenValidity', &#123;
                  initialValue: _.defaultTo(data.accessTokenValidity, ''),
                &#125;)(<PeriodInput />)&#125;
              </FormItem>
              <FormItem label="Refresh有效期" &#123;...formItemLayout&#125;>
                &#123;form.getFieldDecorator('refreshTokenValidity', &#123;
                  initialValue: _.defaultTo(data.refreshTokenValidity, ''),
                &#125;)(<PeriodInput />)&#125;
              </FormItem>
              <FormItem label="秘钥" &#123;...formItemLayout&#125;>
                &#123;form.getFieldDecorator('clientSecret', &#123;
                  initialValue: _.defaultTo(data.clientSecret, ''),
                &#125;)(<SecretInput />)&#125;
              </FormItem>
            </TabPane>
            <TabPane tab="短信设置" key="2">
              <FormItem label="验证码长度" &#123;...formItemLayout&#125;>
                &#123;form.getFieldDecorator('smsCodeLength', &#123;
                  initialValue: data.smsCodeLength,
                &#125;)(<InputNumber min=&#123;1&#125; style=&#123;&#123; width: 300 &#125;&#125; />)&#125;
              </FormItem>
              <FormItem label="验证码有效期(分钟)" &#123;...formItemLayout&#125;>
                &#123;form.getFieldDecorator('smsCodeExpire', &#123;
                  initialValue: data.smsCodeExpire,
                &#125;)(<InputNumber min=&#123;1&#125; style=&#123;&#123; width: 300 &#125;&#125; />)&#125;
              </FormItem>
             <FormItem label="验证码签名" &#123;...formItemLayout&#125;>
                &#123;form.getFieldDecorator('smsCodeSign', &#123;
                  initialValue: data.smsCodeSign,
                &#125;)(<SmsSignInput />)&#125;
              </FormItem>
            </TabPane>
          </Tabs>
        </Form>
      </Modal>
    );
  &#125;
&#125;

export default ClientForm;

3、ClientUtil.js

import _ from 'lodash';
import moment from 'moment';
export const authTypes = [
  &#123; name: '授权码模式', value: 'authorization_code' &#125;,
  &#123; name: '简化模式', value: 'implicit' &#125;,
  &#123; name: '密码模式', value: 'password' &#125;,
  &#123; name: '客户端模式', value: 'client_credentials' &#125;,
  &#123; name: '刷新模式', value: 'refresh_token' &#125;,
];
export function getOAuthTypeNames(str) &#123;
  if (!str) return [];
  const values = str.split(',');
  return values.map(value => _.find(authTypes, t => t.value === value).name);
&#125;
export function getResources(str) &#123;
  if (!str) return [];
  return str.split(',');
&#125;
export function humanizeTime(value) &#123;
  let timeText = '';
  if (value) &#123;
    timeText = moment.duration(value, 'seconds').humanize();
  &#125; else &#123;
    timeText = '未设置';
  &#125;
  return timeText;
&#125;

4、GrantTypeInput组件GrantTypeInput.js

GrantTypeInput

import React, &#123; PureComponent &#125; from 'react';
import &#123; Select &#125; from 'antd';
import &#123; authTypes &#125; from './ClientUtil';

const &#123; Option &#125; = Select;

class GrantTypeInput extends PureComponent &#123;
  state = &#123;
    value: [],
  &#125;;

  componentWillMount() &#123;
    const &#123; props &#125; = this;
    if (props.value) &#123;
      const value = props.value ? props.value.split(',') : [];
      this.setState(&#123; value &#125;);
    &#125;
  &#125;

  componentWillReceiveProps(nextProps) &#123;
    const &#123; props &#125; = this;
    if (props.value !== nextProps.value && !nextProps.value) &#123;
      this.setState(&#123; value: [] &#125;);
    &#125; else &#123;
      const value = nextProps.value ? nextProps.value.split(',') : [];
      this.setState(&#123; value &#125;);
    &#125;
  &#125;

  handleSelectChange = value => &#123;
    const &#123; onChange &#125; = this.props;
    this.setState(&#123; value &#125;);
    if (onChange) onChange(value.join(','));
  &#125;;

  render() &#123;
    const &#123; value &#125; = this.state;
    return (
      <Select
        style=&#123;&#123; width: '100%' &#125;&#125;
        mode="multiple"
        value=&#123;value&#125;
        onChange=&#123;this.handleSelectChange&#125;
      >
        &#123;authTypes.map((type, index) => (
          <Option key=&#123;index&#125; value=&#123;type.value&#125;>
            &#123;type.name&#125;
          </Option>
        ))&#125;
      </Select>
    );
  &#125;
&#125;

export default GrantTypeInput;

5、token有效期输入组件PeriodInput.js

PeriodInput.js

import React, &#123; PureComponent &#125; from 'react';
import moment from 'moment';

import &#123; Input, Select &#125; from 'antd';

const &#123; Option &#125; = Select;

const getTime = (time, fromUnit, toUnit) => moment.duration(Number(time), fromUnit).as(toUnit);

export const timeUnits = [
  &#123; name: '秒', value: 'seconds' &#125;,
  &#123; name: '分钟', value: 'minutes' &#125;,
  &#123; name: '小时', value: 'hours' &#125;,
  &#123; name: '天', value: 'days' &#125;,
];

class PeriodInput extends PureComponent &#123;
  state = &#123;
    value: '',
    unit: 'seconds',
  &#125;;

  componentWillMount() &#123;
    const &#123; props &#125; = this;
    if (props.value) &#123;
      this.setState(&#123;
        unit: 'seconds',
        value: props.value,
      &#125;);
    &#125;
  &#125;

  componentWillReceiveProps(nextProps) &#123;
    const &#123; props &#125; = this;
    if (props.value !== nextProps.value && !nextProps.value) &#123;
      this.setState(&#123;
        unit: 'seconds',
        value: '',
      &#125;);
    &#125; else &#123;
      this.setState(&#123;
        unit: 'seconds',
        value: nextProps.value,
      &#125;);
    &#125;
  &#125;

  handleSelectChange = unitValue => &#123;
    const &#123; unit, value &#125; = this.state;
    const newValue = value ? getTime(value, unit, unitValue) : '';
    this.setState(&#123;
      unit: unitValue,
      value: newValue,
    &#125;);
  &#125;;

  onChangeValue = e => &#123;
    const &#123; value &#125; = e.target;
    const &#123; unit &#125; = this.state;
    const &#123; onChange &#125; = this.props;
    const seconds = getTime(value, unit, 'seconds');
    this.setState(&#123; value &#125;);
    if (onChange) onChange(seconds);
  &#125;;

  render() &#123;
    const &#123; unit, value &#125; = this.state;
    return (
      <Input
        value=&#123;value&#125;
        onChange=&#123;this.onChangeValue&#125;
        addonAfter=&#123;
          <Select style=&#123;&#123; width: 80 &#125;&#125; value=&#123;unit&#125; onChange=&#123;this.handleSelectChange&#125;>
            &#123;timeUnits.map(t => (
              <Option key=&#123;t.value&#125; value=&#123;t.value&#125;>
                &#123;t.name&#125;
              </Option>
            ))&#125;
          </Select>
        &#125;
      />
    );
  &#125;
&#125;

export default PeriodInput;

6、秘钥生成组件SecretInput.js

SecretInput

import React, &#123; PureComponent &#125; from 'react';
import &#123; Row, Col, Slider, Input &#125; from 'antd';
import random from '@/utils/random';

const minValue = 10;
const maxValue = 30;
const defaultState = &#123;
  visible: false,
  value: '',
  length: 10,
&#125;;

class SecretInput extends PureComponent &#123;
  state = &#123; ...defaultState &#125;;

  componentWillMount() &#123;
    const &#123; props &#125; = this;
    if (props.value) &#123;
      this.setState(&#123; value: props.value &#125;);
    &#125;
  &#125;

  componentWillReceiveProps(nextProps) &#123;
    const &#123; props &#125; = this;
    if (props.value !== nextProps.value && !nextProps.value) &#123;
      this.setState(&#123; ...defaultState &#125;);
    &#125; else &#123;
      this.setState(&#123; value: nextProps.value &#125;);
    &#125;
  &#125;

  handleCreatePwd = () => &#123;
    const &#123; length &#125; = this.state;
    this.setState(&#123; visible: true &#125;);
    this.handleChangeLength(length);
  &#125;;

  handleChangeLength = length => &#123;
    const value = random.generate(length);
    this.setState(&#123; length &#125;);
    this.handleChangeValue(value);
  &#125;;

  handleChangeValue = value => &#123;
    const &#123; onChange &#125; = this.props;
    this.setState(&#123; value &#125;);
    if (onChange) onChange(value);
  &#125;;

  render() &#123;
    const &#123; visible, value, length &#125; = this.state;
    return (
      <div>
        <Row gutter=&#123;8&#125;>
          <Col span=&#123;20&#125;>
            <Input value=&#123;value&#125; onChange=&#123;this.handleChangeValue&#125; />
          </Col>
          <Col span=&#123;4&#125;>
            <a onClick=&#123;this.handleCreatePwd&#125;>随机生成</a>
          </Col>
        </Row>
        &#123;visible ? (
          <Row>
            <Col span=&#123;20&#125;>
              <Slider
                min=&#123;minValue&#125;
                max=&#123;maxValue&#125;
                value=&#123;length&#125;
                onChange=&#123;this.handleChangeLength&#125;
              />
            </Col>
          </Row>
        ) : null&#125;
      </div>
    );
  &#125;
&#125;

export default SecretInput;

7、短信签名:输入内容与数据库保存不一致,前端正则加减括号

import React, &#123; PureComponent &#125; from 'react';
import &#123; Input &#125; from 'antd';

const removeBrackets = value => (value ? value.replace(/[【】]/g, '') : '');
const addBrackets = value => (value ? `【$&#123;value&#125;】` : '');

class SmsSignInput extends PureComponent &#123;
  state = &#123;
    value: '',
  &#125;;

  componentWillMount() &#123;
    const &#123; props &#125; = this;
    if (props.value) this.setStateValue(props.value);
  &#125;

  componentWillReceiveProps(nextProps) &#123;
    const &#123; props &#125; = this;
    if (props.value !== nextProps.value && !nextProps.value) &#123;
      this.setStateValue('');
    &#125; else &#123;
      this.setStateValue(nextProps.value);
    &#125;
  &#125;

  setStateValue = value => &#123;
    this.setState(&#123; value: removeBrackets(value) &#125;);
  &#125;;

  handleChangeValue = e => &#123;
    const &#123; value &#125; = e.target;
    const &#123; onChange &#125; = this.props;
    this.setState(&#123; value &#125;);
    if (onChange) onChange(addBrackets(value));
  &#125;;

  render() &#123;
    const &#123; value &#125; = this.state;
    const &#123; props &#125; = this;
    return <Input &#123;...props&#125; value=&#123;value&#125; onChange=&#123;this.handleChangeValue&#125; />;
  &#125;
&#125;

export default SmsSignInput;

评论
  目录