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),
    },
    {
      title: (
        <span>
          RefreshToken
          <br />
          有效期
        </span>
      ),
      dataIndex: 'refreshTokenValidity',
      render: value => humanizeTime(value),
    },
    {
      title: '操作',
      dataIndex: 'action',
      render: (text, record) => (
        <span>
          <a
            onClick={() => {
              this.handleOpenForm(record);
            }}
          >
            编辑
          </a>
          <Divider type="vertical" />
          <Popconfirm
            title="确认删除?"
            onConfirm={() => {
              this.handleRemove(record);
            }}
            okText="确认"
            cancelText="取消"
          >
            <a href="#">删除</a>
          </Popconfirm>
        </span>
      ),
    },
  ];

  componentDidMount() {
    const { dispatch } = this.props;
    dispatch({ type: 'client/fetch' });
  }

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

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

  handleOpenForm = formData => {
    const { dispatch } = this.props;
    dispatch({ type: 'client/openForm', payload: formData });
  };

  handleCloseForm = () => {
    const { dispatch } = this.props;
    dispatch({ type: 'client/closeForm' });
  };

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

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

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

export default ClientList;

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

import React, { PureComponent } from 'react';
import { Modal, Form, Input, Tabs, InputNumber } from 'antd';
import SecretInput from './SecretInput';
import PeriodInput from './PeriodInput';
import GrantTypeInput from './GrantTypeInput';
import SmsSignInput from './SmsSignInput';

const { Item: FormItem } = Form;
const { TabPane } = Tabs;

@Form.create()
class ClientForm extends PureComponent {
  state = {
    tabKey: '1',
  };

  reset = () => {
    const { form } = this.props;
    form.resetFields();
    this.setState({ tabKey: '1' });
  };

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

export default ClientForm;

3、ClientUtil.js

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

4、GrantTypeInput组件GrantTypeInput.js

GrantTypeInput
import React, { PureComponent } from 'react';
import { Select } from 'antd';
import { authTypes } from './ClientUtil';

const { Option } = Select;

class GrantTypeInput extends PureComponent {
  state = {
    value: [],
  };

  componentWillMount() {
    const { props } = this;
    if (props.value) {
      const value = props.value ? props.value.split(',') : [];
      this.setState({ value });
    }
  }

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

  handleSelectChange = value => {
    const { onChange } = this.props;
    this.setState({ value });
    if (onChange) onChange(value.join(','));
  };

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

export default GrantTypeInput;

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

PeriodInput.js
import React, { PureComponent } from 'react';
import moment from 'moment';

import { Input, Select } from 'antd';

const { Option } = Select;

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

export const timeUnits = [
  { name: '秒', value: 'seconds' },
  { name: '分钟', value: 'minutes' },
  { name: '小时', value: 'hours' },
  { name: '天', value: 'days' },
];

class PeriodInput extends PureComponent {
  state = {
    value: '',
    unit: 'seconds',
  };

  componentWillMount() {
    const { props } = this;
    if (props.value) {
      this.setState({
        unit: 'seconds',
        value: props.value,
      });
    }
  }

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

  handleSelectChange = unitValue => {
    const { unit, value } = this.state;
    const newValue = value ? getTime(value, unit, unitValue) : '';
    this.setState({
      unit: unitValue,
      value: newValue,
    });
  };

  onChangeValue = e => {
    const { value } = e.target;
    const { unit } = this.state;
    const { onChange } = this.props;
    const seconds = getTime(value, unit, 'seconds');
    this.setState({ value });
    if (onChange) onChange(seconds);
  };

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

export default PeriodInput;

6、秘钥生成组件SecretInput.js

SecretInput
import React, { PureComponent } from 'react';
import { Row, Col, Slider, Input } from 'antd';
import random from '@/utils/random';

const minValue = 10;
const maxValue = 30;
const defaultState = {
  visible: false,
  value: '',
  length: 10,
};

class SecretInput extends PureComponent {
  state = { ...defaultState };

  componentWillMount() {
    const { props } = this;
    if (props.value) {
      this.setState({ value: props.value });
    }
  }

  componentWillReceiveProps(nextProps) {
    const { props } = this;
    if (props.value !== nextProps.value && !nextProps.value) {
      this.setState({ ...defaultState });
    } else {
      this.setState({ value: nextProps.value });
    }
  }

  handleCreatePwd = () => {
    const { length } = this.state;
    this.setState({ visible: true });
    this.handleChangeLength(length);
  };

  handleChangeLength = length => {
    const value = random.generate(length);
    this.setState({ length });
    this.handleChangeValue(value);
  };

  handleChangeValue = value => {
    const { onChange } = this.props;
    this.setState({ value });
    if (onChange) onChange(value);
  };

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

export default SecretInput;

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

import React, { PureComponent } from 'react';
import { Input } from 'antd';

const removeBrackets = value => (value ? value.replace(/[【】]/g, '') : '');
const addBrackets = value => (value ? `【${value}】` : '');

class SmsSignInput extends PureComponent {
  state = {
    value: '',
  };

  componentWillMount() {
    const { props } = this;
    if (props.value) this.setStateValue(props.value);
  }

  componentWillReceiveProps(nextProps) {
    const { props } = this;
    if (props.value !== nextProps.value && !nextProps.value) {
      this.setStateValue('');
    } else {
      this.setStateValue(nextProps.value);
    }
  }

  setStateValue = value => {
    this.setState({ value: removeBrackets(value) });
  };

  handleChangeValue = e => {
    const { value } = e.target;
    const { onChange } = this.props;
    this.setState({ value });
    if (onChange) onChange(addBrackets(value));
  };

  render() {
    const { value } = this.state;
    const { props } = this;
    return <Input {...props} value={value} onChange={this.handleChangeValue} />;
  }
}

export default SmsSignInput;

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,928评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,192评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,468评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,186评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,295评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,374评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,403评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,186评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,610评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,906评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,075评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,755评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,393评论 3 320
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,079评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,313评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,934评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,963评论 2 351

推荐阅读更多精彩内容