antd常用组件的问题及实现

在基于react的web后台开发中,常常会使用到antd组件,可以使用现有所提供的,也可以自己再次封装使用。基于平时开发中遇到较多关于antd组件问题的情况,记录一下平时常用的代码实现。

(以下组件大都是用在表单中的,也有个别不是~)

1.Radio

单选框


<FormItem {...formItemLayout} label="是否启用" required>
  {getFieldDecorator('enabled',{
    initialValue: data.enabled,
    rules: [{ required: true, message: '请选择是否启用' }],
  })(
    <RadioGroup>
      <Radio value={true}>启用</Radio>
      <Radio value={false}>禁用</Radio>
    </RadioGroup>
  )}
</FormItem>

2.Select

下拉选择,数据源根据接口获取

<FormItem {...formItemLayout} label='xxx'>
   {getFieldDecorator('feedHabit', {
     initialValue: (detail && detail.feedHabit) || undefined,
     rules: [
       { required: false, message: 'xxx' }
     ]
     })(
       <Select 
         showSearch
         allowClear
         placeholder='xxx'
         filterOption={(inputValue, option) => option.props.children.indexOf(inputValue) > -1}
         notFoundContent={
           <Empty
             image={Empty.PRESENTED_IMAGE_SIMPLE}
             style={{ margin: 8 }}
           >
             <Button
               type='primary'
               onClick={() => dispatch({ type: `${namespace}/getFeedHabitList` })}
             >刷新
             </Button>
           </Empty>
         }
       >
         { 
           feedHabitList && feedHabitList.map((item, index) => (
             <Select.Option key={item.code} value={item.code}>{item.name}</Select.Option>
           ))
         }
       </Select>
     )}
 </FormItem>

3.Switch

开发的切换

<Form.Item {...formItemLayout} label='xxx'>
  {getFieldDecorator('enabledAutoFeed', {
    initialValue: (detail && detail.enabledAutoFeed) || undefined,
    valuePropName: 'checked', // 记得加上这个属性
    rules: [
      {required: false, message: 'xxx'}
    ]
  })(
    <Switch
      checkedChildren='是'
      unCheckedChildren='否'
      onChange={(val, event) => {
        event.preventDefault();
        event.stopPropagation();
        this.setState({ enabledAutoFeed: val })		// 将当前改变的值存至state中,提交时直接用state中存储的值就好了
      }}
    /> 
  )}
</Form.Item>

4.Cascader

情况一:只有两个层级数

此情况组件用例是在一个新增/编辑的弹出框中使用,新增时不带数据,可选择;编辑时带出默认数据,也可选择;层级是2级。
开发思路
(1)新增时,不带默认数据出来,但是已经请求到第一层级的数据,点击第一层级中的某一项item时,加载请求第二层级的数据给到item的children中,这样就可以显示第二层级的数据了;
(2)编辑时,带出默认数据,此时defaultMaterialIds应该是有两个id值的,如:[parentId, sonId],但是在点编辑弹出弹框而还没有点击级联组件时,只显示了第一层级的数据,因为此时页面起始已经有第一层级的数据源,而不知道第二层级的数据源,只单纯的有第二层级项的id(也就是sonId),是不知道它的label(或name)的,思路是:在点击编辑的时候,拿到它的第一层级id(也就是parentId),请求第二层级的数据就好了(其实就是点击编辑时再次调用loadData方法~~~)

附上实现逻辑代码:
1.弹框中(editModal.jsx):
render中:

<Form.Item {...formItemLayout} label='物料名称'>
    {getFieldDecorator(`materialId`, {
      // defaultMaterialIds 是默认的数据(一个数组形式),如:[parentId, sonId]
      initialValue: defaultMaterialIds || []
      })(
        <Cascader
          fieldNames={fieldNames}
          options={materialLabel}
          loadData={this.loadData}
          onChange={this.onChange}
          placeholder='请选择物料名称'
          changeOnSelect
        />
      )}
  </Form.Item>

相应方法:

// 级联-选择后的回调
  onChange = (value, selectedOptions) => {
    const { dispatch, namespace } = this.props;
    const name = [];
    const id = [];
    const code = [];
    // console.log(value, selectedOptions);

    selectedOptions.map((v) => {
      name.push(v.name);
      id.push(v.id);
      code.push(v.code)
    });

    this.setState({
      currentId: value
    })
  };


  // 动态加载选项
  loadData = (selectedOptions ) => {
    const { dispatch, namespace, materialLabel, form: { setFieldsValue }, } = this.props;
    const targetOption = selectedOptions[selectedOptions.length - 1];
    targetOption.loading = true;
    // 异步加载数据,targetOption 是选中的那一项数据
    dispatch({
      type:`${namespace}/getMaterialList`,
      payload:{ targetOption },
      callback:(res)=>{
        targetOption.loading = false;
      }
    })

  }

2.view.jsx中(editModal在view中被引用):

// 点击编辑时,传第一级id,请求第二级数据显示
  loadData = (parentId) => {
    const { dispatch, pigfarmConfListIndexMod: { materialLabel } }= this.props;
    // materialLabel为第一层级数据源
    const targetOption = materialLabel.find(item => item.id === parentId)

    // 异步加载数据
    dispatch({
      type:`${namespace}/getMaterialList`,
      payload:{ targetOption },
    })
  }
  
// 点击编辑时调用的方法
showModal = ({data = null, type = 'add' }) => {
    const { dispatch } = this.props;
    if(type === 'add') {
      dispatch({ 
        type: `${namespace}/updateStore`, 
        payload: { showModal: true, detail: data, modalType: type,  } 
      });
    }else {
      // 编辑时拿到parentId请求第二层级数据
      this.loadData(data.parentId);
      dispatch({ 
        type: `${namespace}/updateStore`, 
        payload: { showModal: true, detail: data, defaultMaterialIds: [data.parentId, data.materialId], modalType: type,  } 
      });
    }
  }

3.mod.js中:

// 获取物料列表---这是第二层级
    *getMaterialList({ payload, callback }, { put, call, select }) {
      // ...
      // targetOption 是点击的第一层级的项
      let targetOption = payload.targetOption;
      const children = [];
      // 第一层级数据源materialLabel是一个对象数组,数组中的每一项都是一个对象,带有id,name等属性
      const _materialLabel = [...materialLabel]
      // payload.targetOption.id是点击的第一层级的项,用来获取第二层级的数据源
      const result = yield call(urlMaterialList, {id: payload.targetOption.id});
      // idx是在第一层级中点击的项的位置
      const idx = _materialLabel.findIndex(item=>item.id===targetOption.id)
      // 深拷贝第一层级中选中的项,后面会加上一个children属性,而children就是第二层级的数据源
      // 把这个第二层级数据源 替换掉 第一层级中选中的那个项
      // 然后赋值给整个第一层级的数据源,因为第一层级选中的那个项已经加上了children属性,可以显示第二层级的数据了
      
      const materialItem = cloneDeep(targetOption);
      // ...
        if(result.data.length > 0) {
          result.data.map(item => {
            // 因为第2级就是末级了,所以isLeaf为true
            let obj = { ...item, isLeaf: true };
            children.push(obj);
          })
          // 当前项加上children ,loading 属性
          materialItem.children = children;
          materialItem.loading = false;
          // 然后替换第一层级中选中的那一项
          _materialLabel.splice(idx,1,materialItem)
          yield put({ 
            type: 'updateStore', 
            payload: {
              materialList: result.data,
              materialLabel:_materialLabel
            }
          });
          callback && callback();
        }
      // ...
    },

完成~~~

情况二:多个层级(以下列子为4级:地址的省-市-区-镇)

此情况是用于获取一个中国地区各省市级地址信息的获取及展示/显示;
在平时开发项目中,一般会封装好一个组件,然后多个地方使用,提高开发效率。

开发思路:一开始获得第一层级的地址a,将a的id传至后端请求a的第二层级b,b的id又请求第三层级c…以此类推,将请求到的数据b每一项加上isLeaf属性(末级为true,非末级为false),然后将这个新的b赋值给a的children,就可以显示第二层级b的数据了,依此类推,第三第四级也是…

以下是有关子组件及父组件的逻辑及实现:(主要看注释吧~)

1.子组件中(封装export名为CascaderAddress ):

<Cascader
	// 在实际编辑器中,应该不能这样注释的,这里是为理解外加的注释
   {...options} 	// 可选项配置,如placeHolder,changeOnSelect等,当然也可直接分开写	
  value={defaultAddress}	// 指定的或默认的数据
  options={addressOptions}	// 级联地址的数据源
  loadData={this.loadAddressData}	// 动态加载数据(如点击第一层,加载第二层...)
  onChange={this.onChangeAddress}	// 点击/选择完成后的回调
  fieldNames={fieldNames}	// 自定义数据源options的label,value,children
/>

// 地址选择
  onChangeAddress = (value, selectedOptions) => {
    const { changeCallback } = this.props;
    const dataString = [];
    const ids = [];

    selectedOptions.map((v) => {
      dataString.push(v.areaName);
      ids.push(v.id);
    });
	// 选择完成后的回调,将选择地址的id,name,以及其他信息存起来备用
    changeCallback({data: selectedOptions, ids, dataString});
  }

  // 获取地址
  loadAddressData = selectedOptions => {
    const { loadData, addressOptions } = this.props;
    const targetOption = selectedOptions[selectedOptions.length - 1];
    targetOption.loading = true;
	// 将当前选择项的id请求后端拿到下一层级数据(加上isLeaf属性),赋给当前选择项的children,就可以显示出下一层级的数据了
    this.getAddressOptions(targetOption.id, (list) => {
      targetOption.loading = false;
      targetOption.children = list;

      loadData(addressOptions);  
    })
  };

// 请求地址数据
  getAddressOptions = (parentId, callback) => {
    const { max } = this.state;
    const { request, url } = this.props;
    const params = {
      'id': parentId || '0',	// '0'表示请求第一层级,看后端需要传什么
    }

    request({
      // 拿点击的那一项的id请求下一层级的数据(也就是children)
      url: `${url}/${params.id}`,
      method: 'get',
      data: params,
    })
      .then((res) => {
        if(res.code === '000000') {
          let list = []
          if(res.data && res.data.length) {
          // 判断是否为末级,为每一项加上isLeaf属性
            list = res.data.map(item => ({ ...item, isLeaf: item.areaLevel > max }));
          }
		// 回调显示当前list数据
          callback(list);
        } else {
          message.error(res.msg);
        }
      })
  }


2.父组件中引用:

(1)view.jsx

// 注意farmAddress为当前级联控件的唯一标识
<Form.Item {...formItemLayout} label='地址'>
   {getFieldDecorator('farmAddress', {
     initialValue: defaultAddressIds : undefined,
     rules: [
       { required: true, message: '请输入地址' },
     ],
   })(
     <CascaderAddress {...cascaderProps}/>
   )}
 </Form.Item>

// 级联数据值名称配置
  const cascaderOptionLabel = {
    label: 'areaName',	// 显示的是label,自定义为areaName
    value: 'id', // 传值的是value,自定义为id
    children: 'children'
  }

  // 级联地址选择配置项目
  const cascaderProps = {
    request,
    url: `xxx`,
    addressOptions,	// 数据源
    defaultAddress: defaultAddressIds,	// 默认数据,
    fieldNames: cascaderOptionLabel,	// 自定义配置项
    loadData: (data) => {	// 动态加载数据
      dispatch({ type: `${namespace}/updateStore`, payload: { addressOptions: [...data] } })
    },
    // 选择之后的回调,farmAddress为表单控件中存储的数据,可直接/处理之后提交~
    changeCallback: ({data, dataString, ids}) => {
      setFieldsValue({ farmAddress: ids }); // 设置form表单数据
      dispatch({ type: `${namespace}/updateStore`, payload: { defaultAddressIds: ids } }); // 存储至mod备用
    },
    options: {
      placeholder: '请选择地址',
      changeOnSelect: true,
    }
  }

}

(2)mod.js

// 获取地址列表
    *getAddressOptions({ payload = { id: '' }, callback }, { put, call, select }) {
		...
      // ...假设调用接口请求到数据存至res.data
        let addressOptions = []
        if(res.data && res.data.length) {
          addressOptions = res.data.map(item => ({ id: item.id, value: item.id, areaName: item.areaName, isLeaf: false }))
        }

        yield put({
          type: 'updateStore',
          payload: {
            addressOptions,
          }
        });
    },


 // 根据ID获取该地址信息包含各级数据
    * getAreaInfo({ payload, callback }, { call, put, select }) {
     ...
     // ...
        let addressOptions = [];
        if(result.data && result.data.length) {
          addressOptions = result.data[0].list;

          addressOptions.map( v => { // 省
            v.isLeaf = false;
            if(v.id === result.data[0].selected.id) {
              v.children = result.data[1].list;

              v.children.map( v1 => { // 市
                v1.isLeaf = false;
                if(v1.id === result.data[1].selected.id) {
                  v1.children = result.data[2].list;

                  v1.children.map( v2 => { // 区
                    v2.isLeaf = false;
                    if(v2.id === result.data[2].selected.id) {
                      v2.children = result.data[3].list;
                    }
                  })
                }
              })
            }
          })
        }

        yield put({
          type: 'updateStore',
          payload: {
            addressOptions,
            detailArrdess: result.data,
            // 后端返回的默认地址id,组成一个数组展示
            defaultAddressIds: [detail.addressProvince, detail.addressCity, detail.addressZone, detail.addressTown]
          }
        });

        callback && callback();
     // ...
    },
  }

其实两个情况很类似的,都是将取到的下一级的数据,每一项都加上isLeaf属性,然后将这个新的数据加上children,loading属性,替换上一级中被选择的那一项,就可以显示下一级的数据了~~~

5.Table

某些列表,后端不做分页,需要前端分页时,table的一般配置:

<Table  
	bordered	// 给整个table加上边框
	dataSource={tableList}  
	columns={tableColumns}  
	rowkey={record => record.id} 
	pagination={{ 
		showQuickJumper: true, 
		showSizeChanger: true, 
		showTotal: total => `共${total}条`, 
		pageSizeOptions:['10','20','50','100'],  
		// pageSize: 5, // 固定5条一页,有pageSize就不用pageSizeOptions了
	}}
 />

另外,需要/验证一下:若Table放入一个列Col中,点击上一页/下一页好像没有效果???

扩展:编辑单元格?

暂时先把最近常见的写上,后续还会扩展~~

你可能感兴趣的:(antd常用组件的问题及实现)