上传按钮<input type=“file“ />美化及使用

借鉴文章:上传按钮<input type="file" />智能多效美化

先摆放一张图片来说明在React项目中的使用:

上传按钮<input type=“file“ />美化及使用

其中【上传照片】按钮是 将<input type="file" />与<label>配合,以保持语义化+可访问性为目标,辅以少量Javascript来实现美化的。

下面按照实际开发的项目来逐一讲解(只针对长处方申请业务):

1、Antd Pro2.x中业务代码目录

上传按钮<input type=“file“ />美化及使用

2、在父组件ResidentContract.js中获取 长处方申请 中已上传的数据

/**
 * 4、打开对应的签约操作页面
 * 7-长处方申请
 */
openTabByBtnType = async (item, type) => {
  const {tabPanes, keyList} = this.state;
  const _tabPanes = tabPanes;
  const _keyList = keyList;

  // 7-长处方申请
  if (type === '7') {
    const {dispatch} = this.props;

    //<1>查询已上传的长处方图片
    const {SIGN_ID} = item;
    let recipeCheckedArr = [], fileDetailArray = [], fileTypeFor11 = false;
    await dispatch({
      type: 'residentRecipePhotoModel/selectUploadedRecipePhotoList',
      payload: SIGN_ID,
      callback: (res) => {
        let recipeTypeStr = '';

        if (res.status === 200) {
          const {data} = res;
          for (let i = 0, length = data.length; i < length; i++) {
            if (i == length -1) {
              recipeTypeStr += data[i].fileType;
            } else {
              recipeTypeStr += data[i].fileType + ',';
            }
          }

          // ▲删除签约人员中的重复的长处方类型
          recipeCheckedArr = recipeTypeStr.split(',').filter((element, index, self) => {
            return self.indexOf(element) === index;
          });

          /*查询是否有 '其他' 类型*/
          recipeCheckedArr.map((ele) => {
            if(ele === '11') {
              fileTypeFor11 = true;
            }
          });

          // <1>处理查询结果中fileType对应的attachId, attachId为保存照片内容表的外键
          // <2>过滤出有图片内容的集合
          const fileList = {};
          let n = 0;
	      data.forEach(function (element) {
          const {fileType, attachId, fileContentDetail} = element;
		  if (!!fileContentDetail) { // 图片内容不为空,说明有上传长处方类型图片
		    fileList[fileType] ?
			  fileDetailArray[fileList[fileType] - 1].IdAndUrl.push({attachId, fileContentDetail}) :
				 fileList[fileType] = ++n && fileDetailArray.push({
					fileType: fileType,
					IdAndUrl: [{attachId, fileContentDetail}],
					signId: SIGN_ID
			   });
		     }
          });
        }
      }
    });

    //<1>(从数据字典中)查询慢病长处方类型
    dispatch({
      type: 'residentRecipePhotoModel/getRecipeType',
      payload: '',
      callback: (res) => {
        const {status, data, msg}  = res;
        if (status === 0) {
          _tabPanes.push({title: '长处方申请', id: '7', key: '7', state: '7', closable: false, 
            tabsDetail: data,
            loadedPhotoArray: fileDetailArray,  // 签约人员长处方上传图片的相关信息
            recipeCheckedArr,     // 过滤后的内容,用于匹配哪个checkbox选中
            signPersonDetail: item,
            fileTypeFor11: fileTypeFor11
          });
          _keyList.push('7');

          this.setState({ tabPanes: _tabPanes, activeKey: '7', keyList: _keyList });

        } else {
          Modal.info({title: '提示信息', content: msg, okText: '确定'});
          return;
        }
      }
    });
  }
}

注:针对  【// <2>过滤出有图片内容的集合】的处理代码,可参考本人写过的 如何把数组对象中相同的key值合并,并且把对应的id放到一个数组中

type: 'residentRecipePhotoModel/selectUploadedRecipePhotoList' 获取的数据如下图:

上传按钮<input type=“file“ />美化及使用

type: 'residentRecipePhotoModel/getRecipeType' 获取的数据:

上传按钮<input type=“file“ />美化及使用

3、在ResidentContractMiddle.js组件render()中,长处方申请组件接收参数

上传按钮<input type=“file“ />美化及使用

 

4、ResidentContractMedApply.js

import React, {Component} from 'react';
import {connect} from "dva";
import {Row, Col, Button, Form, Checkbox, Modal, Upload, message} from 'antd';
import PubSub from "pubsub-js";

import {openCaptureVideo} from '@/utils/getSignPatient';

import styles from './ContractMiddleTab.less';

const FormItem = Form.Item;
let globalSignId = 0;

/**
 * @Description: 居民签约-长处方申请
 * @author wanglong
 * @date 2020/7/10 13:59
 */
@connect(({ residentRecipePhotoModel }) => ({residentRecipePhotoModel}))
@Form.create()
export default class ResidentContractMedApply extends Component {
	// 1、初始化状态
	state = {
		previewVisible: false,      // 图片是否预览
		previewImageUrl: '',        // 预览图片的URL(实际为api请求)
		recipeCheckedArray: this.props.recipeCheckedArr,
		fileMap: new Map(),         // 长处方类型与对应的图片 的集合
		loadedPhotoArray: !!this.props.loadedPhotoArray ? this.props.loadedPhotoArray : [], // 数据库已经上传的图片集合
		delFlag: true,              // 控制删除一张图片导致图片集合全部清空的问题
		attachIdArray: [],          // 用来保存保存长处方类型图片子表外键值

		recipePhotoDisabled: false, // 长处方
	}

	//<1>checkbox勾选事件 2019/2/24
	handleRecipeIsCheck = (event) => {
		const {checked, id} = event.target;
		const {recipeCheckedArr, signPersonDetail: {EHR_ID}} = this.props;
		const checkId = id.split('_')[1];

		if (checked) {
			recipeCheckedArr.push(checkId);
			this.setState({recipeCheckedArray: recipeCheckedArr}, () => {
				PubSub.publish( 'changeCheckedDiseases', {ehrId: EHR_ID, fileType: checkId, flag: true});
			});

		} else {
			const photoList = this.state.fileMap.get(checkId * 1);
			if (photoList && photoList.length > 0) {
				Modal.confirm({title: '你确定要清空图片吗?', okText: '确定', cancelText: '取消',
					onOk: () => {
						recipeCheckedArr.splice(recipeCheckedArr.findIndex(v => v === checkId), 1);
						this.setState({ recipeCheckedArray: recipeCheckedArr }, () => {
							PubSub.publish('changeCheckedDiseases', {ehrId: EHR_ID, fileType: checkId, flag: false});
						});
					},
				});

			} else {
				recipeCheckedArr.splice(recipeCheckedArr.findIndex(v => v === checkId), 1);
				this.setState({ recipeCheckedArray: recipeCheckedArr }, () => {
					PubSub.publish('changeCheckedDiseases', {ehrId: EHR_ID, fileType: checkId, flag: false});
				});
			}
		}
	}

	/*<2>自定义文件上传*/
	// 过渡方法:间接打开上传文件窗口
	myBtnUploadClick = (id, signId) => {
		globalSignId = signId;
		const fileInput = document.getElementById(`refFile_${id}`);
		fileInput.click();
	}

	inputFileChange = (e) => {
		const {dispatch} = this.props;
		const {fileMap, attachIdArray} = this.state;

		const fileType = e.target.id.split('_')[1];
		const fileObj = e.target.files[0];
		const fileName = e.target.name;

		//检验文件类型是否正确
		const isJPG = fileObj.type === 'image/jpg';
		const isJPEG = fileObj.type === 'image/jpeg';
		const isPNG = fileObj.type === 'image/png';
		const isPic = isJPG || isJPEG || isPNG;
		if (!isPic) {
			message.error('只能上传jpg/jpeg/png格式的图片!');
			e.target.value = ''; // 还原
			return;
		}

		if(fileObj) {
			const formData = {
				recipeFile: fileObj,
				tableName: "ehr_cli_registry",
				paramId: globalSignId,
				recipeType: fileType
			};
			dispatch({
				type:'residentRecipePhotoModel/uploadRecipePhotoByAntd',
				payload: formData,
				callback: (res) => {
					if (res.status === 200) {
						const { attachId, photoBase64, file_type} = res.data;
						const list = !!fileMap.get(file_type) ? fileMap.get(file_type) : [];
						list.push({
							uid: attachId,
							status: 'done',
							url: attachId,
							thumbUrl: "data:image/jpg;base64," + photoBase64,
							fileType: file_type,
						});
						fileMap.set(file_type, list);

						this.setState({
							fileMap,
							loadedPhotoArray: [],
							delFlag: false,
							attachIdArray: [...attachIdArray, attachId],
						});
					}
				}
			});
		}

		e.target.value = ''; // 上传之后还原
	}
	/*自定义文件上传END*/

	//<3>删除图片
	handlePhotoRemove = (file) => {
		const {dispatch} = this.props;
		dispatch({
			type: 'residentRecipePhotoModel/deleteRecipePhoto',
			payload: file.uid
		});

		let {fileMap} = this.state;
		const index = fileMap.get(file.fileType).indexOf(file);
		const newFileList = fileMap.get(file.fileType).slice();
		newFileList.splice(index, 1);
		fileMap.set(file.fileType, newFileList);

		this.setState({fileMap, loadedPhotoArray: [], delFlag: false});
	}

	//<4>图片预览 2019/2/24
	handlePreview = (file) => {
		const {dispatch} = this.props;
		const {uid} = file;
		dispatch({
			type:'residentRecipePhotoModel/selectPhotoThumbUrl',
			payload: uid,
			callback: (res) => {
				if (!!res) {
					this.setState({previewImageUrl: res, previewVisible: true});
				}
			}
		});
	}

	//<5>取消图片预览 2019/2/24
	handlePreviewCancel = () => {
		this.setState({ previewVisible: false });
	}


	// 渲染组件
	render() {
		const {previewVisible, previewImageUrl, recipeCheckedArray, loadedPhotoArray, delFlag, recipePhotoDisabled} = this.state;

		let {fileMap} = this.state;

		//loadedPhotoArray: 签约人员已有的长处方类型图片; recipeCheckedArr: 过滤去重后的长处方类型
		const {form: { getFieldDecorator }, tabsDetail, signPersonDetail, fileTypeFor11} = this.props;

		/*已经上传的图片回显处理 wanglong */
		if (loadedPhotoArray && loadedPhotoArray.length > 0) {
			loadedPhotoArray.map((element) => (
				tabsDetail.map(tab => {
					const tempList = [];
					if (element.fileType === tab.dictId * 1) {
						element.IdAndUrl.map(obj => {
							tempList.push({
								uid: obj.attachId,
								status: 'done',
								url: `${obj.attachId}`,
								thumbUrl: "data:image/jpg;base64," + obj.fileContentDetail,
								fileType: element.fileType,     // 额外添加的参数
							});
						});

						fileMap.set(element.fileType, tempList);
					}
				})
			));
		}
		// 清空之前的数据
		else if(delFlag) {
			fileMap = new Map();
		}

		return (
			<div>
				<Form.Item style={{ marginTop: -5, marginBottom: -2}}>
					<Button type="primary" style={{float: 'right'}} onClick={() => this.handleSaveRecipePhoto(signPersonDetail)} disabled={recipePhotoDisabled}>
						保存
					</Button>
				</Form.Item>
				<hr style={{ border: '0.5px solid #1890FF', clear:"both" }} />
				{
					tabsDetail.map((recipe, index) => {
						const fileList = !!fileMap.get(recipe.dictId * 1) ? fileMap.get(recipe.dictId * 1) : [];

						if(!fileTypeFor11){
							if (recipe.dictId !== '11' ) { // '其他'项
								return (
									<Row key={index} style={{ marginBottom: 5 }}>
										<Col span={5}>
											<FormItem>
												{getFieldDecorator(`recipe_${recipe.dictId}`, { initialValue: recipe.dictId })(
													<Checkbox checked={recipeCheckedArray.includes(recipe.dictId)} onChange={this.handleRecipeIsCheck}>
														{recipe.dictName}
													</Checkbox>
												)}
											</FormItem>
										</Col>

										{/*针对按钮的处理*/}
										<Col span={19} style={{ display: recipeCheckedArray.includes(recipe.dictId) ? 'block' : 'none' }} className={styles["upload-col"]}>
											<Button type="primary" style={{width: '100px'}} onClick={() => openCaptureVideo(recipe.dictId)} >打开高拍仪</Button>

											<label className={styles["label-upload"]} >
												<input type="button" className={styles["btn-input-upload"]} value="上传照片"
												       onClick={() => this.myBtnUploadClick(recipe.dictId, signPersonDetail.SIGN_ID)}
												/>
												<input type="file" name="recipeFile" className={styles["file-input"]} accept="image/jpg,image/png,image/jpeg,image/bmp"
												       id={`refFile_${recipe.dictId}`} onChange={this.inputFileChange}
												/>
											</label>

											<Upload className="upload-list-inline" listType="picture" onPreview={this.handlePreview} onRemove={this.handlePhotoRemove} fileList={fileList}></Upload>
										</Col>
									</Row>
								)
							}

						} else {
							return (
								<Row key={index} style={{ marginBottom: 5 }}>
									<Col span={5}>
										<FormItem>
											{getFieldDecorator(`recipe_${recipe.dictId}`, { initialValue: recipe.dictId })(
												<Checkbox checked={recipeCheckedArray.includes(recipe.dictId)} onChange={this.handleRecipeIsCheck}>
													{recipe.dictName}
												</Checkbox>
											)}
										</FormItem>
									</Col>

									{/*针对按钮的处理*/}
									<Col span={19} style={{ display: recipeCheckedArray.includes(recipe.dictId) ? 'block' : 'none' }} className={styles["upload-col"]}>
										<Button type="primary" style={{width: '100px'}} onClick={() => openCaptureVideo(recipe.dictId)} >打开高拍仪</Button>

										<label className={styles["label-upload"]} >
											<input type="button" className={styles["btn-input-upload"]} value="上传照片"
											       onClick={() => this.myBtnUploadClick(recipe.dictId, signPersonDetail.SIGN_ID)}
											/>
											<input type="file" name="recipeFile" className={styles["file-input"]} accept="image/jpg,image/png,image/jpeg,image/bmp"
											       id={`refFile_${recipe.dictId}`} onChange={this.inputFileChange}
											/>
										</label>

										<Upload className="upload-list-inline" listType="picture" onPreview={this.handlePreview} onRemove={this.handlePhotoRemove} fileList={fileList}></Upload>
									</Col>
								</Row>
							)
						}
					})
				}
				<Modal visible={previewVisible} footer={null} onCancel={this.handlePreviewCancel} style={{top: 20}}>
					<img alt="example" style={{ width: '100%' }} src={previewImageUrl}/>
				</Modal>
			</div>
		);
	}
}

 

5、ContractMiddleTab.less
/*针对长处方图片上传*/
/*上传外层label样式*/
.label-upload {
  position: relative;
  margin-left: 10px;
  zoom: 1
}

.btn-input-upload {
  padding: 4px 10px;
  width: 100px;
  height: 32px;   //大小
  text-align: center;
  color: #fff;            //文字系列
  border: 1px solid #1890FF;
  background-color: #1890FF;
  border-radius: 4px;     //背景
  cursor: pointer;
  zoom: 1;
}

.btn-input-upload:hover {
  color: #fff;
  background-color: #40a9ff;
  border-color: #40a9ff;
}

.file-input {
  position: absolute;
  left: 0;
  top: -2px;
  width: 100px;
  opacity: 0;
  display: none;
}

.upload-col {
  :global {
    /*上传图片的样式*/
    .upload-list-inline .ant-upload-list-item {
      float: left;
      width: 120px;
      margin-right: 8px;
    }
  }
}

说明:因为业务需求的原因,此处文件上传并没有直接利用<label>中的 for 属性的值(锚点链接的方式) 与 <input type="file" name="file" id="xxx" /> 中 id 属性的值进行关联。而是起了一个包裹美化的作用,让第一个input起到button的作用,进而再去调用上传文件的input。

 

上一篇:2021年,Salesforce开发必须掌握的5个新功能


下一篇:unbuntu18.04安装snort出现 recipe for target 'aclocal.m4' failed