美文网首页React
Ant Design Pro学习之使用upload组件并用for

Ant Design Pro学习之使用upload组件并用for

作者: 小马将过河 | 来源:发表于2019-03-14 11:20 被阅读610次

    效果

    upload view http network api-debug

    实现思路和代码

    利用upload提供的beforeUpload属性,先将文件放到state里,随后和form表单一起提交。
    先上干货,再解释一些走过的弯弯绕

    接口代码
    接受实体类

    
    import lombok.Data;
    import org.springframework.web.multipart.MultipartFile;
    
    import javax.persistence.Transient;
    import java.util.Date;
    
    /**
     * @author 红创-马海强
     * @date 2019-02-20 14:06
     * @description 战略报告
     */
    @Data
    public class StrategyReportVo {
    
        private String id;
    
        private String title;
        private Date showTime;
        private String periods;
        private String fileUrl;
        private int deleteFlag = 0;
        private Date createTime;
        @Transient
        private int readCount;
        private MultipartFile[] files;
        private MultipartFile file;
        
    }
    

    API接口

     @PostMapping("/reports")
        public RtnResult update(StrategyReportVo vo) {
            StrategyReport report = new StrategyReport();
            BeanUtils.copyProperties(vo, report);
            return RtnResult.success(strategyReportAdminService.update(report));
        }
    

    注意:接口使用form形式提交,因此在vo前面不能使用@RequestBody注解

    前端为了方便先将fetch请求写在form页面里,规范的话应该写在model里。

    import React, {PureComponent} from 'react';
    import {Modal, Form, Input, Spin, DatePicker, Button, Icon, Upload} from 'antd';
    import _ from 'lodash';
    import FileUpload from '../Common/FileUpload';
    import {uploadUrl} from '../../services/api-base';
    import moment from "moment";
    import {prefix} from '../../services/api';
    
    const {Item: FormItem} = Form;
    
    @Form.create()
    export default class StrategyReportForm extends PureComponent {
    
        state = {
            fileData: [],
        }
    
        /** 文件上传属性 **/
        uploadProps = {
            accept: '.pdf',
            action: uploadUrl,
            name: 'files',
            onUpload: (fileList) => {
                this.props.onChangeFile(fileList);
            },
            onSuccess: (response) => {
                const {name, url} = response[0];
                const file = {
                    uid: -1,
                    name: name,
                    status: 'done',
                    url: url
                };
                this.props.form.setFieldsValue({fileUrl: url});
                this.props.onChangeFile([file]);
            },
            onRemove: () => {
                this.props.onChangeFile([]);
            }
        }
    
    
    //这个是监听文件变化的
    fileChange=(params)=>{
        const {file,fileList}=params;
        if(file.status==='uploading'){
            setTimeout(()=>{
                this.setState({
                    percent:fileList.percent    
                })
            },1000)       
        }
    }
    // 拦截文件上传
    beforeUploadHandle=(file)=>{
        this.setState(({fileData})=>({
            fileData:[...fileData,file],
        }))
        return false;
    }
    // 文件列表的删除
    fileRemove=(file)=>{
        this.setState(({fileData})=>{
            const index = fileData.indexOf(file);
            return {
                fileData: fileData.filter((_, i) => i !== index)
            }
        })
    }
    
        render() {
            const {modalVisible, formLoading, confirmLoading, data, onSave, onCancel, form, fileList} = this.props;
            const {getFieldDecorator} = this.props.form;
            const title = data.id ? '编辑报告' : '添加报告';
            const formItemLayout = {
                labelCol: {span: 5},
                wrapperCol: {span: 15},
            };
            const files = this.state.fileData;
            return (
                <Modal
                    title={title}
                    visible={modalVisible}
                    confirmLoading={confirmLoading}
                    onOk={() => {
                        form.validateFields((err, values) => {
                            if (!err) {
                              let formData = new FormData();
                              formData.append("file", files[0]);
                              for(let i = 0 ;i<files.length;i++){
                                //dataParament.files.fileList[i].originFileObj 这个对象是我观察 antd的Upload组件发现的里面的originFileObj 对象就是file对象
                                formData.append('files',files[i])
                               }
                               //file以外的对象拼接
                               for(let item in values.length) {
                                 if(item !== 'files' && values[item]) {
                                    formData.append(item, values[item]);
                                 }
                               }
                              fetch(`${prefix}/questionnaire/admin/strategy/reports`, {
                                method: 'POST',
                                body: formData,
                                headers: {
                                    'Authorization': `Bearer ${sessionStorage.accessToken}`,
                                },
                              }).then((response => {
                                  if (response.code === 0) {
                                      console.log("=====================", 'OK');
                                  } else {
                                      console.log("=====================", 'error');
                                  }
                              }));
                              onSave(data);
                            }
                        });
                    }}
                    onCancel={onCancel}>
                    <Form id="postForm">
                        <Spin spinning={formLoading} tip="加载中...">
                            {
                                getFieldDecorator('id', {initialValue: _.defaultTo(data.id, null)})
                            }
                            <FormItem label="报告标题" {...formItemLayout}>
                                {
                                    getFieldDecorator('title', {
                                        rules: [
                                            {
                                                type: 'string',
                                                required: true,
                                                message: '标题不能为空!',
                                            },
                                        ],
                                        initialValue: _.defaultTo(data.title, ''),
                                    })(<Input/>)
                                }
                            </FormItem>
                            <FormItem label="显示时间" {...formItemLayout}>
                                {
                                    getFieldDecorator('showTime', {
                                        rules: [
                                            {
                                                required: true,
                                                message: '显示时间不能为空',
                                            },
                                        ],
                                        initialValue: data.showTime ? moment(moment(data.showTime).format('YYYY-MM-DD HH:mm')) : moment(),
                                    })( <DatePicker showTime style={{width: 280}} format="YYYY-MM-DD HH:mm"/>)
                                }
                            </FormItem>
                            <FormItem label="指定期数" {...formItemLayout}>
                                {
                                    getFieldDecorator('periods', {
                                        rules: [
                                            {
                                                type: 'string',
                                                required: false,
                                                message: '期数',
                                            },
                                        ],
                                        initialValue: _.defaultTo(data.periods, ''),
                                    })(<Input/>)
                                }
                            </FormItem>
                            {/* <FormItem label="上传附件" {...formItemLayout}>
                                {
                                    getFieldDecorator('fileUrl', {
                                        rules: [
                                            {
                                                type: 'string',
                                                required: true,
                                                message: '请上传PDF文档',
                                            },
                                        ],
                                        initialValue: _.defaultTo(data.fileUrl, '')
                                    })(<FileUpload
                                        uploadProps={this.uploadProps}
                                        fileList={fileList}
                                        data={{'objectKey': 'strategy/report'}}/>)
                                }
                            </FormItem> */}
                            <FormItem labelCol={{span:5}} wrapperCol={{span:15}} label='文件上传'>
                                {getFieldDecorator('files')(
                                    <Upload action='路径' 
                                        multiple uploadList 
                                        beforeUpload={this.beforeUploadHandle} 
                                        onChange={this.fileChange} 
                                        onRemove={this.fileRemove} 
                                        fileList={this.state.fileData}>
                                        <Button><Icon type='upload' />上传文件</Button>
                                    </Upload>
                                )}
                            </FormItem>
                        </Spin>
                    </Form>
                </Modal>
            );
        }
    
        componentWillReceiveProps(nextProps) {
            if (!this.props.modalVisible && nextProps.modalVisible) {
                this.props.form.resetFields();
            }
        }
    }
    
    

    注意点

    • 1、Upload组件默认是选择文件后直接调用action上传文件,返回url。通常文件都会在form表单里跟别的参数一起,这时候form里其实没有文件,而是文件的url地址。
      就像下面这样。
      StrategyReportForm是这个弹出层,而它的上层页面是StrategyReportList,在list中的form是这样的
              <StrategyReportForm
                      modalVisible={strategyReportForm.modalVisible}
                      confirmLoading={strategyReportForm.confirmLoading}
                      options={strategyReportForm.options}
                      data={strategyReportForm.data}
                      fileList={strategyReportForm.fileList}
                      formLoading={strategyReportForm.formLoading}
                      onChangeFile={(fileList)=>{
                        dispatch({type: 'strategyReportForm/fileList', payload: fileList});
                      }}
                      onSave={(data)=>{
                        dispatch({type: 'strategyReportForm/update', payload: {data, callback:(result)=>{
                            dispatch({type: 'strategyReportList/list', payload:{}});
                        }}});
                    }}
                    onCancel={()=>{
                        dispatch({type: 'strategyReportForm/close'});
               }}/>
    

    这段代码里的onSave回调方法的意思就是上传文件,关闭弹框,刷新列表。
    modle里的update方法与其他的没有两样。

        effects: {
            * update({payload:{data, callback}}, {call, put, select}){
                yield put({type: 'confirmLoading', payload: true});
                const response = yield call(api.update, data);
                if (response.code === 0) {
                    message.success("操作成功");
                    yield put({type: 'close'});
                    if(callback) callback(response.data)
                } else {
                    message.error(response.message);
                }
            },
        }
    

    api.upload这个方法在antd pro里是隔离定义再service目录下的,内容很简单:

    export async function update(params) {
      fetch(`${prefix}/questionnaire/admin/strategy/reports`, {
        method: 'POST',
        body: params,
        headers: {
            'Authorization': `Bearer ${sessionStorage.accessToken}`,
        }
      })
    }
    

    需要注意的是这里得直接使用fetch方法,不能使用框架封装的request发起请求,因为request里封装的content-type类型是application/json

    在and design pro2.x的版本里,request方法已经兼容了这个处理


    2.x request

    在antd1.x的版本里,也可以使用reqeust里封装好的postFormWithProgress方法。比如这个用法:

    <FormItem label="安装包地址" labelCol={{ span: 3 }} wrapperCol={{ span: 9 }}>
              {
                getFieldDecorator('downloadAddr', {
                  rules: [
                    {
                      required: true,
                      message: '安装包地址不能为空',
                    },
                  ],
                })(
                  <Input disabled />
                )
              }
            </FormItem>
            <FormItem wrapperCol={{ offset:3, span: 9 }}>
              <Upload beforeUpload={this.uploadFile}>
                <Button>
                  <Icon type="upload" />上传文件
                </Button>
              </Upload>
              <Progress size="small" style={{ display: 'inline' }} percent={~~(this.state.uploadPercent*100)} />
            </FormItem>
    

    js

      uploadFile = (file) => {
        this.setState({ uploadPercent: 0 });
        uploadAppBinary(file, percent => this.setState({ uploadPercent: percent })).then(
          (resp) => {
            const {
              code,
              message: msg,
              data,
            } = resp;
            if (code === 0) {
              const { downloadAddr } = data;
              this.props.form.setFieldsValue({
                downloadAddr,
              });
            } else {
              message.error(`上传文件失败!--${msg}`);
            }
          },
        ).catch(e => message.error(e.message));
        return false;
      }
    

    service

    export async function uploadAppBinary(file, callback) {
      return postFormWithProgress(`${prefix}/questionnaire/admin/app/release/uploadPackage`, {
        file,
      }, callback);
    }
    
    • 2、但是这次不一样,我们文件先不上传,而是与form表单的其他内容一起提交到API里。解决问题是学到的东西不少,简单记录下。

    2.1、form里应不应该设置Content-Type属性,应该设置成什么?request里会有哪些不一样?
    直接参考post使用form-data和x-www-form-urlencoded的本质区别即可,但是结论是不需要自己设定,程序会自己根据类型设定。

    2.2、调用接口时只要没有文件就没问题,但是有文件了就会400。
    原因:多个文件的append不能直接把数组append进去,比如上面如果不用循环获取fileData里的数据,而是直接formData.append(this.state.fileData);这样的数据发送的接口,就会400,原因就是类型不对。
    如果是单个文件,可以直接使用formData.append(files[0]);这样实现。

    2.3、多个文件和单个文件的处理。
    不论是单个文件或是多个文件,都可以使用循环的形式将文件append到formdata中。

    • 3、其他实现方式
      基于2.x以后的版本实现更简单一些。
      把json传到service的api以后,new出formData,append上参数即可。
    export async function batchImport(params){
        const formData = new FormData();
        for (const key in params) {
            formData.append(key, params[key]);
        }
        return request('/customer/batchImport', { 
            method: 'POST',
            body: formData 
        });
    }
    

    不过就是在form里要利用valuePropNamegetValueFromEvent属性把属性值以json的结构传递到modles里。

    <Modal
                destroyOnClose
                title="导入量体人"
                visible={batchImportShow}
                onOk={this.handleOk}
                onCancel={() => handleImportVisible(false)}>
                <FormItem labelCol={{ span: 5 }} wrapperCol={{ span: 15 }} label="测量计划">
                    {form.getFieldDecorator('planId',{
                        rules: [{ required: true, message: '请选择测量计划', }],
                    })(<Select style={ { width: 200 }} id='planSelect'>
                        <Select.Option key={-99} value=''>全部</Select.Option>
                    { planList.map((item) => <Select.Option key={item.planId} value={item.planId}>{item.planName}</Select.Option>) }
                    </Select>)}
                </FormItem>
                <FormItem labelCol={{ span: 5 }} wrapperCol={{ span: 15 }} label="数据文件">
                    {form.getFieldDecorator('customerFile', {
                        rules: [{ required: true, message: '请上传数据文件', }],
                        valuePropName: 'files',
                        getValueFromEvent: e => e.target.files,
                    })(<Input type='file' name='customerFile' style={{height:35}}/>)}
                </FormItem>
              </Modal>
    

    友情参考

    将选中文件保存到页面的state中
    将文件append到新的formdata中使用post方式提交

    相关文章

      网友评论

        本文标题:Ant Design Pro学习之使用upload组件并用for

        本文链接:https://www.haomeiwen.com/subject/ymjfmqtx.html