在基于react的web后台开发中,常常会使用到antd组件,可以使用现有所提供的,也可以自己再次封装使用。基于平时开发中遇到较多关于antd组件问题的情况,记录一下平时常用的代码实现。
(以下组件大都是用在表单中的,也有个别不是~)
单选框
<FormItem {...formItemLayout} label="是否启用" required>
{getFieldDecorator('enabled',{
initialValue: data.enabled,
rules: [{ required: true, message: '请选择是否启用' }],
})(
<RadioGroup>
<Radio value={true}>启用</Radio>
<Radio value={false}>禁用</Radio>
</RadioGroup>
)}
</FormItem>
下拉选择,数据源根据接口获取
<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>
开发的切换
<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>
此情况组件用例是在一个新增/编辑的弹出框中使用,新增时不带数据,可选择;编辑时带出默认数据,也可选择;层级是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();
}
// ...
},
完成~~~
此情况是用于获取一个中国地区各省市级地址信息的获取及展示/显示;
在平时开发项目中,一般会封装好一个组件,然后多个地方使用,提高开发效率。
开发思路:一开始获得第一层级的地址a,将a的id传至后端请求a的第二层级b,b的id又请求第三层级c…以此类推,将请求到的数据b每一项加上isLeaf属性(末级为true,非末级为false),然后将这个新的b赋值给a的children,就可以显示第二层级b的数据了,依此类推,第三第四级也是…
以下是有关子组件及父组件的逻辑及实现:(主要看注释吧~)
<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);
}
})
}
(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属性,替换上一级中被选择的那一项,就可以显示下一级的数据了~~~
某些列表,后端不做分页,需要前端分页时,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中,点击上一页/下一页好像没有效果???
扩展:编辑单元格?
暂时先把最近常见的写上,后续还会扩展~~