Ant Design Pro學(xué)習(xí)之組件化

同事寫了一個(gè)我目前看著比較正規(guī)化的組件式頁(yè)面娱局,在此作為學(xué)習(xí)標(biāo)準(zhǔn)貼一下郎逃,先看個(gè)效果圖:


列表
編輯1
編輯2

這是一個(gè)oauth的client管理的頁(yè)面肩刃,主要代碼如下:
api列表數(shù)據(jù)結(jié)構(gòu)

{
    "code": 0,
    "message": "操作成功",
    "data": {
        "content": [
            {
                "clientId": "usercenter-manage",
                "clientName": "測(cè)試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
    }
}

組件化嘛精绎,文件自然比較多速缨,打個(gè)標(biāo)識(shí)

1、ClientList:頁(yè)面渲染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: '應(yīng)用ID',
      dataIndex: 'clientId',
    },
    {
      title: '應(yīng)用名稱',
      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="確認(rèn)刪除代乃?"
            onConfirm={() => {
              this.handleRemove(record);
            }}
            okText="確認(rèn)"
            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="授權(quán)模式">
            {toTags(getOAuthTypeNames(record.authorizedGrantTypes))}
          </Description>
          <Description term="平臺(tái)編碼">{record.platformCode}</Description>
          <Description term="資源IDS">{record.resourceIds}</Description>
        </DescriptionList>
        <DescriptionList style={{ marginTop: 15 }} size="small">
          <Description term="短信驗(yàn)證碼長(zhǎng)度">{record.smsCodeLength}</Description>
          <Description term="短信驗(yàn)證碼有效期">
            {record.smsCodeLength ? `${record.smsCodeLength}分鐘` : ''}
          </Description>
          <Description term="短信驗(yàn)證碼簽名">{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="應(yīng)用列表">
        <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:添加編輯單條數(shù)據(jù)的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 ? '更新應(yīng)用' : '添加應(yīng)用';
    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="授權(quán)設(shè)置" key="1">
              <FormItem label="應(yīng)用ID" {...formItemLayout}>
                {form.getFieldDecorator('clientId', {
                  rules: [
                    {
                      type: 'string',
                      required: true,
                      message: '應(yīng)用ID不能為空!',
                    },
                  ],
                  initialValue: data.clientId,
                })(<Input disabled={!!data.clientId} />)}
              </FormItem>
              <FormItem label="應(yīng)用名稱" {...formItemLayout}>
                {form.getFieldDecorator('clientName', {
                  rules: [
                    {
                      type: 'string',
                      required: true,
                      message: '應(yīng)用名稱不能為空搁吓!',
                    },
                  ],
                  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="授權(quán)類型" {...formItemLayout}>
                {form.getFieldDecorator('authorizedGrantTypes', {
                  rules: [
                    {
                      type: 'string',
                      required: true,
                      message: '請(qǐng)選擇授權(quán)類型!',
                    },
                  ],
                  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="短信設(shè)置" key="2">
              <FormItem label="驗(yàn)證碼長(zhǎng)度" {...formItemLayout}>
                {form.getFieldDecorator('smsCodeLength', {
                  initialValue: data.smsCodeLength,
                })(<InputNumber min={1} style={{ width: 300 }} />)}
              </FormItem>
              <FormItem label="驗(yàn)證碼有效期(分鐘)" {...formItemLayout}>
                {form.getFieldDecorator('smsCodeExpire', {
                  initialValue: data.smsCodeExpire,
                })(<InputNumber min={1} style={{ width: 300 }} />)}
              </FormItem>
             <FormItem label="驗(yàn)證碼簽名" {...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: '授權(quán)碼模式', value: 'authorization_code' },
  { name: '簡(jiǎn)化模式', 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 = '未設(shè)置';
  }
  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: '小時(shí)', 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}>隨機(jī)生成</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通贞、短信簽名:輸入內(nèi)容與數(shù)據(jù)庫(kù)保存不一致,前端正則加減括號(hào)

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;

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末恼五,一起剝皮案震驚了整個(gè)濱河市昌罩,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌灾馒,老刑警劉巖茎用,帶你破解...
    沈念sama閱讀 217,734評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異睬罗,居然都是意外死亡轨功,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門傅物,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)夯辖,“玉大人琉预,你說(shuō)我怎么就攤上這事董饰。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,133評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵卒暂,是天一觀的道長(zhǎng)啄栓。 經(jīng)常有香客問(wèn)我,道長(zhǎng)也祠,這世上最難降的妖魔是什么昙楚? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,532評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮诈嘿,結(jié)果婚禮上堪旧,老公的妹妹穿的比我還像新娘。我一直安慰自己奖亚,他們只是感情好淳梦,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,585評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著昔字,像睡著了一般爆袍。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上作郭,一...
    開(kāi)封第一講書(shū)人閱讀 51,462評(píng)論 1 302
  • 那天陨囊,我揣著相機(jī)與錄音,去河邊找鬼夹攒。 笑死蜘醋,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的芹助。 我是一名探鬼主播堂湖,決...
    沈念sama閱讀 40,262評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼状土!你這毒婦竟也來(lái)了无蜂?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,153評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤蒙谓,失蹤者是張志新(化名)和其女友劉穎斥季,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體累驮,經(jīng)...
    沈念sama閱讀 45,587評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡酣倾,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,792評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了谤专。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片躁锡。...
    茶點(diǎn)故事閱讀 39,919評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖置侍,靈堂內(nèi)的尸體忽然破棺而出映之,到底是詐尸還是另有隱情拦焚,我是刑警寧澤,帶...
    沈念sama閱讀 35,635評(píng)論 5 345
  • 正文 年R本政府宣布杠输,位于F島的核電站赎败,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏蠢甲。R本人自食惡果不足惜僵刮,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,237評(píng)論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望鹦牛。 院中可真熱鬧搞糕,春花似錦、人聲如沸曼追。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,855評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)拉鹃。三九已至辈赋,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間膏燕,已是汗流浹背钥屈。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,983評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留坝辫,地道東北人篷就。 一個(gè)月前我還...
    沈念sama閱讀 48,048評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像近忙,于是被迫代替她去往敵國(guó)和親竭业。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,864評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容