123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274 |
- import React, { useCallback, useEffect, useState } from 'react'
- import Cropper from "react-cropper";
- import "cropperjs/dist/cropper.css";
- import { Button, Col, Form, InputNumber, message, Radio, Row, Spin, Upload } from 'antd';
- import Modal from 'antd/lib/modal/Modal';
- import { RcFile } from 'antd/lib/upload';
- import { InboxOutlined } from '@ant-design/icons'
- const { Dragger } = Upload
- import { compressAccurately } from 'image-conversion';
- import { blobToBase64 } from '@/utils/compress'
- interface Props {
- width?: number,//初始裁剪框宽
- height?: number,//初始裁剪框高
- imgSize?: { width: number, height: number, size: number },
- disabled?: boolean, // 是否禁止传入
- btnName?: string | JSX.Element//按钮名称
- isChangeCropperSize?: boolean, // 是否可以改变裁剪框大小
- isLaunch?: boolean, // 是否是在落地页上传
- isCropper?: boolean, // 是否需要裁剪
- isJudgeSize?: boolean, // 是否判断图片宽高与传入的裁剪框宽高 图片大小需要大于裁剪框大小
- callback?: (imgData: any) => void//裁剪后图片的数据
- }
- /**图片裁剪
- * @param width 初始裁剪框宽
- * @param height 初始高度
- * @param btnName 按钮名称
- * @param disabled 是否禁止传入
- * @param isLaunch 是否是在落地页上传
- * @param isCropper 是否需要裁剪
- * @param callback 裁剪后图片的数据的回调函数
- * @param isJudgeSize 是否判断图片宽高与传入的裁剪框宽高 图片大小需要大于裁剪框大小 需传入width和height
- */
- export const Demo = (props: Props) => {
- let { isJudgeSize = false, isChangeCropperSize = false, isLaunch = false, isCropper = false, disabled = false, imgSize } = props
- const [image, setImage] = useState('');//初始图片
- const [visible, setVisible] = useState<boolean>(false)
- const [cropper, setCropper] = useState<any>();//实例
- const [loading, setLoading] = useState<boolean>(false)
- const [detail, setDetail] = useState<{ width: number, height: number, rotate: number }>({ width: props?.width || 800, height: props?.height || 800, rotate: 0 });//设置数据
- /**关闭 */
- const cancel = useCallback(() => {
- setVisible(false)
- setCropper(undefined)
- setImage('')
- }, [])
- /**获取裁剪数据*/
- const getCropData = () => {
- if (typeof cropper !== "undefined") {
- props?.callback && props?.callback(cropper.getCroppedCanvas().toDataURL('image/jpeg'))
- cancel()
- }
- };
- /**数值变化重置实例 */
- useEffect(() => {
- if (typeof cropper !== "undefined") {
- cropper.setData(detail);
- }
- }, [cropper, detail]);
- useEffect(() => {
- if (visible && cropper) {
- setTimeout(() => {
- cropper.reset()
- if (props?.width) {
- cropper.setData({ width: props?.width, height: props?.height })
- }
- }, 200)
- }
- }, [visible, cropper])
- return <Spin spinning={loading}>
- {
- <Dragger
- disabled={disabled}
- accept="image/*"
- action="#"
- showUploadList={false}
- multiple={true}
- customRequest={() => { }}
- beforeUpload={(file: RcFile, FileList: RcFile[]): any => {
- setLoading(true)
- let img: any = new Image();
- let _URL = window.URL || window.webkitURL;
- img.onload = function (e: any) {
- if (isJudgeSize) {
- if (!props?.width || !props?.height) {
- console.error('请传入"width"与"height"');
- setLoading(false)
- return
- } else if (props?.width < this.width || props?.height < this.height) {
- message.error(`传入的图片大小不符, 图片大小${this.width}*${this.height}, 需要图片大小${props.width}*${props.height}`)
- setLoading(false)
- return
- }
- }
- const reader = new FileReader();
- reader.onload = async () => {
- let size = imgSize?.size || 307200
- if (isCropper) {
- setImage(reader.result as any);
- setLoading(false)
- } else {
- if (imgSize && (imgSize?.width === 800 || imgSize?.width === 640 || imgSize?.width === 960)) {
- if (imgSize?.width !== this.width || imgSize?.height !== this.height) {
- message.error(`请上传${imgSize?.width} * ${imgSize?.height}尺寸图片`)
- setLoading(false)
- return
- }
- } else if (imgSize?.width === 750) {
- if (imgSize?.width !== this.width || this.height > 1536) {
- message.error(`请上传${imgSize?.width} * <1536尺寸图片`)
- setLoading(false)
- return
- }
- }
- if (size === 307200 && file?.size > size) { // 大于300kb进入压缩
- let bole = await compressAccurately(file, 250)
- if (bole?.size > 300000) {
- bole = await compressAccurately(file, 200)
- }
- if (bole?.size > 300000) {
- bole = await compressAccurately(file, 150)
- }
- if (bole?.size > 300000) {
- bole = await compressAccurately(file, 100)
- }
- let newFile = await blobToBase64(bole)
- message.warning({
- content: '选择的图片大于300KB,图片已压缩',
- duration: 3
- })
- props?.callback && props?.callback(newFile);
- setLoading(false)
- return
- } else if (size === 81920 && file?.size > size) {
- let bole = await compressAccurately(file, 80)
- if (bole?.size > 80000) {
- bole = await compressAccurately(file, 60)
- }
- if (bole?.size > 80000) {
- bole = await compressAccurately(file, 40)
- }
- let newFile = await blobToBase64(bole)
- message.warning({
- content: '选择的图片大于80KB,图片已压缩',
- duration: 3
- })
- props?.callback && props?.callback(newFile);
- setLoading(false)
- return
- }
- props?.callback && props?.callback(reader.result);
- setLoading(false)
- }
- };
- reader.readAsDataURL(file);
- if (isCropper) {
- setLoading(false)
- setVisible(true)
- }
- }
- img.src = _URL.createObjectURL(file);
- }}
- >
- {
- props?.btnName ? props?.btnName : <>
- <p className="ant-upload-drag-icon">
- <InboxOutlined />
- </p>
- <p className="ant-upload-text">单击或拖动文件到此区域以上载</p>
- <p className="ant-upload-hint">支持jpg,png...</p>
- </>
- }
- </Dragger>
- }
- {visible && <Modal
- open={visible}
- onCancel={cancel}
- width={1000}
- footer={<div style={{ display: 'flex', justifyContent: 'center' }}>
- <Button onClick={cancel}>取消</Button>
- {/* <Button onClick={() => {
- cropper.reset()
- if (props?.width) {
- cropper.setData({ width: props?.width, height: props?.height })
- }
- }}>重置</Button> */}
- <Button type='primary' onClick={getCropData}>确定</Button>
- </div>}
- >
- <div>
- <div style={{ width: "100%" }}>
- <Row>
- <Col span={16}>
- <Cropper
- style={{ height: 400, width: 600, backgroundImage: `url(${require('../../../public/init.png')})`, border: '1px solid #efefef' }}
- zoomTo={0.6}
- initialAspectRatio={1}
- preview=".img-preview"
- src={image}
- viewMode={0}
- guides={true}
- rotatable={true}
- minCropBoxHeight={10}
- minCropBoxWidth={10}
- background={false}
- responsive={true}
- autoCropArea={1}
- cropBoxResizable={false}
- checkOrientation={false}
- onInitialized={(instance: any) => {
- setCropper(instance);
- }}
- />
- </Col>
- <Col span={8}>
- <Row gutter={[10, 10]}>
- <Col span={24} style={{ height: 275 }}>
- <div className="box" style={{ width: "100%", float: "right", overflow: 'hidden' }}>
- <h1>Preview</h1>
- <div
- className="img-preview"
- style={{ width: "100%", float: "left", height: "200px", overflow: 'hidden' }}
- />
- </div>
- </Col>
- <Col>
- <Form>
- <Form.Item label="裁剪框宽" style={{ marginBottom: 0 }}>
- <Radio.Group disabled={isChangeCropperSize || isLaunch} onChange={(e) => { setDetail({ ...detail, width: e.target.value, height: 800 }) }} value={detail.width}>
- <Radio value={800}>800</Radio>
- <Radio value={750}>750</Radio>
- <Radio value={640}>640</Radio>
- </Radio.Group>
- </Form.Item>
- <Form.Item label="裁剪框高" style={{ marginBottom: 0 }}>
- {detail.width === 800 ? <Radio.Group disabled={isChangeCropperSize || isLaunch} onChange={(e) => { setDetail({ ...detail, height: e.target.value }) }} value={detail.height}>
- <Radio value={800}>800</Radio>
- <Radio value={640}>640</Radio>
- <Radio value={450}>450</Radio>
- </Radio.Group>
- : detail.width === 640 ? <Radio.Group disabled={isChangeCropperSize || isLaunch} onChange={(e) => { setDetail({ ...detail, height: e.target.value }) }} value={detail.height}>
- <Radio value={800}>800</Radio>
- </Radio.Group>
- :
- <InputNumber
- style={{ width: 200 }}
- value={detail.height}
- disabled={isChangeCropperSize ? isChangeCropperSize : isLaunch ? false : false}
- min={1}
- max={1536}
- onChange={(e) => { setDetail({ ...detail, height: e || 0 }) }}
- />
- }
- </Form.Item>
- </Form>
- </Col>
- </Row>
- </Col>
- </Row>
- </div>
- </div>
- </Modal>}
- </Spin>
- };
- export default Demo;
|