ProFormUploadButton 踩坑记录
最近在用ant design pro 组件做项目,在使用到ProFormUploadButton这个组件的时候,发现很多文档上没有写的东西,接下来把我的踩坑记录分享给大家.
刚开始只有一个上传的需求,我就把官网的示例直接copy了下来,如下:
<ProFormUploadButton label="upload" name="upload" action="upload.do" />
后面跟我说要一个预览的功能,,这样其我就参考ant design 的组件加成这样:
<ProFormUploadButton
name="businessLicense"
label="营业执照"
max={1}
action="upload.do"
fieldProps={{
multiple: false,
name: 'file',
listType: 'picture-card',
accept:"image/png, image/jpeg",
headers: {
'timestamp': (new Date().valueOf()) + ''
},
onChange: (e) => {
handleChange(e)
},
}}
fileList={fileList}
extra="只能上传jpg/png文件,且不大于3MB"
/>
测试后,发现没有前置校验,于是加了前置校验,如下:
// 上传前置校验
const beforeUpload = (file: RcFile) => {
const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png';
if (!isJpgOrPng) {
message.error('只允许上传 JPG/PNG 格式的文件');
}
const isLt3M = file.size / 1024 / 1024 < 3;
if (!isLt3M) {
message.error('仅支持3M以下的文件');
}
return isJpgOrPng && isLt3M
};
测试后,发现就算校验了,会自动跳过,还是会调接口,所以改成这样
// 上传前置校验
const beforeUpload = (file: RcFile) => {
const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png';
if (!isJpgOrPng) {
message.error('只允许上传 JPG/PNG 格式的文件');
}
const isLt3M = file.size / 1024 / 1024 < 3;
if (!isLt3M) {
message.error('仅支持3M以下的文件');
}
// 如果返回了
if ((isJpgOrPng && isLt3M) === false) {
// 在这里记得要清空fileList
// 返回这个参数beforeUpload为false就可以拦截住了
return Upload.LIST_IGNORE
} else {
return true
}
};
注意:除了fileList以外, 其他所有的属性都要放到ProFormUploadButton组件的fieldProps里面,这样才能生效.
注意二: 在onchange方法里如果上传文件状态为error,还是会显示到fileList,这时候要处理一下:
if (info.file.status == 'error') {
message.error('上传失败!请稍后再试')
setFileList([])
}
我以为到这里就结束了,没想到,又加了需求,说是要让图片可以旋转,放大缩小,我网上找了个js库,用的人比较多,鉴于我是react, 所以又找了个用react封装的插件,下面贴一下两个库的git地址.
react版本 tinymins/viewerjs-react: React wrapper for viewerjs. (github.com)
原版 fengyuanchen/viewerjs: JavaScript image viewer. (github.com)
贴一下整页的代码,方便自己以后回看.
import type { UploadProps } from 'antd';
import { Card, Col, Row, message, Button, Upload, Modal } from 'antd';
import type { FC} from 'react';
import { useState, useEffect, useRef } from 'react';
import type {
ProFormInstance} from '@ant-design/pro-form';
import ProForm, {
ProFormText,
ProFormSelect,
ProFormDependency,
ProFormRadio,
ProFormDatePicker,
ProFormUploadButton
} from '@ant-design/pro-form';
import { PageContainer } from '@ant-design/pro-layout';
import styles from './style.less';
import { getCompanyInfoApi, saveCompanyInfoApi } from './service';
import { validatorOnlyCn, validatorPhoneAndFixedPro, validatorCerdit, validatorEnName, businessLicenseDeadLine } from '@/utils/validator';
import type { UploadChangeParam } from 'antd/lib/upload';
import type { RcFile, UploadFile } from 'antd/lib/upload/interface';
import { history } from 'umi';
import moment from 'moment';
// import ImageFunc from '@/components/ImageFunc';
import RViewerJS from 'viewerjs-react'
import 'viewerjs-react/dist/index.css'
interface TableFormDateType {
key: string;
workId?: string;
name?: string;
department?: string;
isNew?: boolean;
editable?: boolean;
}
const getBase64 = (file: RcFile): Promise<string> =>
new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => resolve(reader.result as string);
reader.onerror = error => reject(error);
});
const FormAdvancedForm: FC<Record<string, any>> = () => {
const [id, setId] = useState<string>('');
// 绑定一个 ProFormInstance 实例
const formRef = useRef<
ProFormInstance<{
date: string;
}>
>();
const [imageName, setImageName] = useState<string>();
const [imageUrl, setImageUrl] = useState<string>();
const [fileList, setFileList] = useState<[]>();
const [btnLoading, setbtnLoading] = useState<boolean>(false);
// 为false时不校验格式
const [noVer, setNoVer] = useState<boolean>(true);
const set_image_url = (new_data) => {
setImageUrl(new_data)
}
const handlePreview = async (file: UploadFile) => {
if (!file.url && !file.preview) {
file.preview = await getBase64(file.originFileObj as RcFile);
}
setImageUrl(file.url || (file.preview as string));
setImageName(file.name || file.url!.substring(file.url!.lastIndexOf('/') + 1));
setTimeout(() => { document.getElementById('imgpre').click()}, 0)
};
const getCompanyInfoFunc = () => {
setFileList(null)
getCompanyInfoApi({loginName: sessionStorage.getItem('loginName') || ''}).then((res: any) => {
if (res.code === 200) {
// 表单回显
// 拷贝一份数据
const newData = JSON.parse(JSON.stringify(res.data))
// businessLicense需要的是list格式的数据 如果不删掉会报错
delete newData.businessLicense
// 表单回显
formRef.current?.setFieldsValue(newData)
// 做成fileList的格式传回
const flit = [
{
uid: moment().valueOf(),
name: res.data.businessLicenseName,
status: 'done',
url: res.data.businessLicense
}
]
if (res.data.businessLicense) {
// 回显fileList
formRef.current?.setFieldsValue({
businessLicense: flit
})
setFileList(flit)
set_image_url(res.data.businessLicense)
setImageName(res.data.businessLicenseName)
}
// res.data.id为null表示是新增
if (res.data.id === null) {
setId('')
return;
} else {
setId(res.data.id)
}
// 不为null表示是编辑
return;
}
message.error('查询失败!请稍后再试')
})
}
const onFinish = async (values: Record<string, any>) => {
setbtnLoading(true)
delete values.file
const obj = {
id: id === '' ? undefined : id,
code: sessionStorage.getItem('loginName'),
businessLicense: imageUrl,
businessLicenseName: imageName
}
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
values.name ? delete values.name : ''
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
values.tel ? delete values.tel : ''
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
values.code ? delete values.code : ''
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
values.email ? delete values.email : ''
values.businessLicenseDeadLine = values.businessLicenseDeadLine ? moment(moment(values.businessLicenseDeadLine).format('YYYY-MM-DD') + ' 23:59:59').valueOf() : undefined
try {
await saveCompanyInfoApi({
...values,
...obj
}).then(res => {
setbtnLoading(false)
if (res.code !== 200) {
message.error('提交失败,请稍后再试');
return;
}
message.success('提交成功');
// 在这里更改组件是否刷新的flag
sessionStorage.setItem('destroyInactiveTabPane', 'true')
getCompanyInfoFunc()
history.push('/platform/filingReview/certification');
})
} catch {
message.error('提交出错,请稍后再试');
setbtnLoading(false)
}
};
useEffect(() => {
// 在这里更改组件是否刷新的flag
sessionStorage.setItem('destroyInactiveTabPane', 'true')
getCompanyInfoFunc()
}, []);
const beforeUpload = (file: RcFile) => {
const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png';
if (!isJpgOrPng) {
message.error('只允许上传 JPG/PNG 格式的文件');
}
const isLt3M = file.size / 1024 / 1024 < 3;
if (!isLt3M) {
message.error('仅支持3M以下的文件');
}
if ((isJpgOrPng && isLt3M) === false) {
setFileList(null)
setImageName(undefined)
set_image_url(undefined);
formRef.current?.resetFields(['resetFields'])
return Upload.LIST_IGNORE
} else {
return true
}
};
const handleChange: UploadProps['onChange'] = (info: UploadChangeParam<UploadFile>) => {
if (info.file.status == 'removed') {
setFileList(null)
}
if (info.file.status === 'done') {
// Get this url from response in real world.
// 做成fileList的格式传回
if (info.file.response.code !== 200) {
message.error(info.file.response.msg)
return;
}
let obj ={
uid: moment().valueOf(),
name: info.file.response.data.fileName,
status: 'done',
url: info.file.response.data.filePath
}
setFileList([obj])
setImageName(info.file.response.data.fileName)
set_image_url(info.file.response.data.filePath);
return;
}
if (info.file.status == 'error') {
message.error('上传失败!请稍后再试')
setFileList([])
}
};
// 点击删除的时候直接清空fileList
const onRemove = () => {
setFileList(null)
}
//选择认证失败时 进行的操作
const failFunC = () => {
setNoVer(false)
}
return (
<div>
<ProForm<TableFormDateType>
layout="horizontal"
// 通过formRef进行绑定
formRef={formRef}
onFinish={onFinish}
submitter={{
// 配置按钮文本
searchConfig: {
submitText: '提交',
},
// 配置按钮的属性
resetButtonProps: {
style: {
// 隐藏重置按钮
display: 'none',
},
},
// 完全自定义整个区域
render: (props, doms) => {
return [
<Row key="submit">
<Button type="primary" key="submit" loading={btnLoading} onClick={() => props.form?.submit?.()} id="submitBtn" style={{margin: '0 auto'}}>
提交
</Button>
</Row>
];
},
}}
labelCol={{span: 10}}
>
<PageContainer header={{title:'',breadcrumb: {}}}>
<Card className={styles.certificationInfoCard} bordered={false}>
<Row gutter={24}>
<Col lg={12}>
<ProFormText
label={'姓名'}
name="name"
disabled
placeholder={''}
/>
<ProFormText
label={'手机号'}
name="tel"
disabled
placeholder={''}
/>
<ProFormText
label={'公司中文名称'}
name="cnCompanyName"
rules={noVer? [
{validator: validatorOnlyCn},
{ required: true, message: '请输入公司中文名称' },
{ min: 2, max: 128, message: '长度为2-128个字符'},
] : [{ required: true, message: '请输入公司中文名称' }]}
/>
<ProFormText
label={'公司电话'}
name="companyTel"
rules={noVer ? [
{validator: validatorPhoneAndFixedPro},
{ required: true, message: '请输入公司电话' },
{ min: 2, max: 24, message: '长度为2-24个字符'},
] : [{ required: true, message: '请输入公司电话' },]}
/>
</Col>
<Col lg={12} >
<ProFormText
label={'登陆账户'}
name="code"
disabled
placeholder={''}
/>
<ProFormText
label={'电子邮箱'}
name="email"
disabled
placeholder={''}
/>
<ProFormText
label={'公司英文名称'}
name="enCompanyName"
rules={noVer ? [
{ max: 128, message: '长度为最多128个字符'},
{validator: validatorEnName},
] : []}
/>
<ProFormText
label={'统一社会信用代码'}
name="socialCreditCode"
rules={noVer ? [
{ required: true, message: '请输入统一社会信用代码' },
{validator: validatorCerdit},
] : [{required: true, message: '请输入统一社会信用代码' },]}
/>
</Col>
</Row>
<Row gutter={24}>
<Col span={12}>
{/* 公司类型 */}
<ProFormSelect
label={'公司类型'}
name="companyType"
rules={[{ required: true, message: '请选择公司类型' }]}
options={[
{
label: '货主',
value: '1',
},
{
label: '货代',
value: '2',
},
{
label: '报关行',
value: '3',
},
{
label: '车队',
value: '4',
},
{
label: '打单行',
value: '5',
},
{
label: '拖车司机',
value: '6',
},
{
label: '船公司',
value: '7',
},
]}
placeholder="请选择公司类型"
/>
</Col>
<Col span={12}> <ProFormText
label={'公司地址'}
name="companyAddress"
placeholder={'请输入公司地址'}
rules={noVer ? [
{ required: true, message: '请输入公司地址' },
{ min: 2, max: 50, message: '长度为2-50个汉字'}
] : [{ required: true, message: '请输入公司地址' },]}
/>
</Col>
</Row>
<Row gutter={24}>
{/* 营业执照期限 */}
<Col span={12}>
<ProFormSelect
label="营业执照期限"
options={[
{
value: '长期',
label: '长期',
},
{
value: '非长期',
label: '非长期',
},
]}
name="businessLicenseStatus"
rules={[
{ required: true, message: '请选择' }
]}
/>
</Col>
<Col span={6}>
<ProFormDependency name={['businessLicenseStatus']}>
{({ businessLicenseStatus }) => {
if (businessLicenseStatus === '长期') {
return (
''
);
}
return <ProFormDatePicker
name="businessLicenseDeadLine"
placeholder={'请选择期限'}
rules={noVer ? [
{ required: true, message: '请选择' },
{ validator: businessLicenseDeadLine }
] : [ { required: true, message: '请选择' },]}
/>;
}}
</ProFormDependency>
</Col>
</Row>
<Row gutter={24}>
<Col span={24}>
<ProFormUploadButton
rules={[
{ required: true, message: '请上传营业执照' },
]}
labelCol={{span: 5}}
name="businessLicense"
label="营业执照"
max={1}
fieldProps={{
multiple: false,
name: 'file',
action: "/external/api/file/uploadFile",
listType: 'picture-card',
data:{
fileType: 'company_license',
loginName: sessionStorage.getItem('loginName') || ''
},
accept:"image/png, image/jpeg",
beforeUpload: (e) => {
return beforeUpload(e)
},
onRemove,
headers: {
'timestamp': (new Date().valueOf()) + ''
},
onChange: (e) => {
handleChange(e)
},
onPreview: handlePreview,
}}
fileList={fileList}
// previewFile={() => {}}
extra="只能上传jpg/png文件,且不大于3MB"
/>
{/* 页面不显示这个组件 点击预览的时候模拟点击这个组件 效果是一样的 */}
<RViewerJS>
<img src={imageUrl} style={{ width: '0.5%'}} id="imgpre"/>
</RViewerJS>
</Col>
</Row>
<Row gutter={24}>
<Col span={17}>
<ProFormRadio.Group
labelCol={{span: 7}}
name="certificationStatus"
label="认证状态"
rules={[
{ required: true, message: '请选择认证状态' }
]}
options={[
{
label: '未认证',
value: 0,
},
{
label: '认证中',
value: 1,
},
{
label: '已认证',
value: 2,
},
{
label: '认证失败',
value: 3,
},
]}
/>
</Col>
<Col span={17}>
<ProFormDependency name={['certificationStatus']}>
{({ certificationStatus }) => {
if (certificationStatus !== 3) {
setNoVer(true)
formRef.current?.resetFields(['certificationRemark'])
return (
''
);
} else {
failFunC()
}
return <ProFormText
label="失败原因"
labelCol={{span: 7}}
name="certificationRemark"
placeholder={'请输入失败原因(16字符)'}
rules={[
{ required: true, message: '失败原因为必填项' },
{ min: 0, max: 16, message: '最多输入16个字符'}
]}
/>;
}}
</ProFormDependency>
</Col>
</Row>
</Card>
</PageContainer>
</ProForm>
</div>
);
};
export default FormAdvancedForm;