index.tsx 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. import React, { useCallback, useEffect, useState } from 'react'
  2. import Cropper from "react-cropper";
  3. import "cropperjs/dist/cropper.css";
  4. import { Button, Col, Form, InputNumber, message, Radio, Row, Spin, Upload } from 'antd';
  5. import Modal from 'antd/lib/modal/Modal';
  6. import { RcFile } from 'antd/lib/upload';
  7. import { InboxOutlined } from '@ant-design/icons'
  8. const { Dragger } = Upload
  9. import { compressAccurately } from 'image-conversion';
  10. import { blobToBase64 } from '@/utils/compress'
  11. interface Props {
  12. width?: number,//初始裁剪框宽
  13. height?: number,//初始裁剪框高
  14. imgSize?: { width: number, height: number, size: number },
  15. disabled?: boolean, // 是否禁止传入
  16. btnName?: string | JSX.Element//按钮名称
  17. isChangeCropperSize?: boolean, // 是否可以改变裁剪框大小
  18. isLaunch?: boolean, // 是否是在落地页上传
  19. isCropper?: boolean, // 是否需要裁剪
  20. isJudgeSize?: boolean, // 是否判断图片宽高与传入的裁剪框宽高 图片大小需要大于裁剪框大小
  21. callback?: (imgData: any) => void//裁剪后图片的数据
  22. }
  23. /**图片裁剪
  24. * @param width 初始裁剪框宽
  25. * @param height 初始高度
  26. * @param btnName 按钮名称
  27. * @param disabled 是否禁止传入
  28. * @param isLaunch 是否是在落地页上传
  29. * @param isCropper 是否需要裁剪
  30. * @param callback 裁剪后图片的数据的回调函数
  31. * @param isJudgeSize 是否判断图片宽高与传入的裁剪框宽高 图片大小需要大于裁剪框大小 需传入width和height
  32. */
  33. export const Demo = (props: Props) => {
  34. let { isJudgeSize = false, isChangeCropperSize = false, isLaunch = false, isCropper = false, disabled = false, imgSize } = props
  35. const [image, setImage] = useState('');//初始图片
  36. const [visible, setVisible] = useState<boolean>(false)
  37. const [cropper, setCropper] = useState<any>();//实例
  38. const [loading, setLoading] = useState<boolean>(false)
  39. const [detail, setDetail] = useState<{ width: number, height: number, rotate: number }>({ width: props?.width || 800, height: props?.height || 800, rotate: 0 });//设置数据
  40. /**关闭 */
  41. const cancel = useCallback(() => {
  42. setVisible(false)
  43. setCropper(undefined)
  44. setImage('')
  45. }, [])
  46. /**获取裁剪数据*/
  47. const getCropData = () => {
  48. if (typeof cropper !== "undefined") {
  49. props?.callback && props?.callback(cropper.getCroppedCanvas().toDataURL('image/jpeg'))
  50. cancel()
  51. }
  52. };
  53. /**数值变化重置实例 */
  54. useEffect(() => {
  55. if (typeof cropper !== "undefined") {
  56. cropper.setData(detail);
  57. }
  58. }, [cropper, detail]);
  59. useEffect(() => {
  60. if (visible && cropper) {
  61. setTimeout(() => {
  62. cropper.reset()
  63. if (props?.width) {
  64. cropper.setData({ width: props?.width, height: props?.height })
  65. }
  66. }, 200)
  67. }
  68. }, [visible, cropper])
  69. return <Spin spinning={loading}>
  70. {
  71. <Dragger
  72. disabled={disabled}
  73. accept="image/*"
  74. action="#"
  75. showUploadList={false}
  76. multiple={true}
  77. customRequest={() => { }}
  78. beforeUpload={(file: RcFile, FileList: RcFile[]): any => {
  79. setLoading(true)
  80. let img: any = new Image();
  81. let _URL = window.URL || window.webkitURL;
  82. img.onload = function (e: any) {
  83. if (isJudgeSize) {
  84. if (!props?.width || !props?.height) {
  85. console.error('请传入"width"与"height"');
  86. setLoading(false)
  87. return
  88. } else if (props?.width < this.width || props?.height < this.height) {
  89. message.error(`传入的图片大小不符, 图片大小${this.width}*${this.height}, 需要图片大小${props.width}*${props.height}`)
  90. setLoading(false)
  91. return
  92. }
  93. }
  94. const reader = new FileReader();
  95. reader.onload = async () => {
  96. let size = imgSize?.size || 307200
  97. if (isCropper) {
  98. setImage(reader.result as any);
  99. setLoading(false)
  100. } else {
  101. if (imgSize && (imgSize?.width === 800 || imgSize?.width === 640 || imgSize?.width === 960)) {
  102. if (imgSize?.width !== this.width || imgSize?.height !== this.height) {
  103. message.error(`请上传${imgSize?.width} * ${imgSize?.height}尺寸图片`)
  104. setLoading(false)
  105. return
  106. }
  107. } else if (imgSize?.width === 750) {
  108. if (imgSize?.width !== this.width || this.height > 1536) {
  109. message.error(`请上传${imgSize?.width} * <1536尺寸图片`)
  110. setLoading(false)
  111. return
  112. }
  113. }
  114. if (size === 307200 && file?.size > size) { // 大于300kb进入压缩
  115. let bole = await compressAccurately(file, 250)
  116. if (bole?.size > 300000) {
  117. bole = await compressAccurately(file, 200)
  118. }
  119. if (bole?.size > 300000) {
  120. bole = await compressAccurately(file, 150)
  121. }
  122. if (bole?.size > 300000) {
  123. bole = await compressAccurately(file, 100)
  124. }
  125. let newFile = await blobToBase64(bole)
  126. message.warning({
  127. content: '选择的图片大于300KB,图片已压缩',
  128. duration: 3
  129. })
  130. props?.callback && props?.callback(newFile);
  131. setLoading(false)
  132. return
  133. } else if (size === 81920 && file?.size > size) {
  134. let bole = await compressAccurately(file, 80)
  135. if (bole?.size > 80000) {
  136. bole = await compressAccurately(file, 60)
  137. }
  138. if (bole?.size > 80000) {
  139. bole = await compressAccurately(file, 40)
  140. }
  141. let newFile = await blobToBase64(bole)
  142. message.warning({
  143. content: '选择的图片大于80KB,图片已压缩',
  144. duration: 3
  145. })
  146. props?.callback && props?.callback(newFile);
  147. setLoading(false)
  148. return
  149. }
  150. props?.callback && props?.callback(reader.result);
  151. setLoading(false)
  152. }
  153. };
  154. reader.readAsDataURL(file);
  155. if (isCropper) {
  156. setLoading(false)
  157. setVisible(true)
  158. }
  159. }
  160. img.src = _URL.createObjectURL(file);
  161. }}
  162. >
  163. {
  164. props?.btnName ? props?.btnName : <>
  165. <p className="ant-upload-drag-icon">
  166. <InboxOutlined />
  167. </p>
  168. <p className="ant-upload-text">单击或拖动文件到此区域以上载</p>
  169. <p className="ant-upload-hint">支持jpg,png...</p>
  170. </>
  171. }
  172. </Dragger>
  173. }
  174. {visible && <Modal
  175. open={visible}
  176. onCancel={cancel}
  177. width={1000}
  178. footer={<div style={{ display: 'flex', justifyContent: 'center' }}>
  179. <Button onClick={cancel}>取消</Button>
  180. {/* <Button onClick={() => {
  181. cropper.reset()
  182. if (props?.width) {
  183. cropper.setData({ width: props?.width, height: props?.height })
  184. }
  185. }}>重置</Button> */}
  186. <Button type='primary' onClick={getCropData}>确定</Button>
  187. </div>}
  188. >
  189. <div>
  190. <div style={{ width: "100%" }}>
  191. <Row>
  192. <Col span={16}>
  193. <Cropper
  194. style={{ height: 400, width: 600, backgroundImage: `url(${require('../../../public/init.png')})`, border: '1px solid #efefef' }}
  195. zoomTo={0.6}
  196. initialAspectRatio={1}
  197. preview=".img-preview"
  198. src={image}
  199. viewMode={0}
  200. guides={true}
  201. rotatable={true}
  202. minCropBoxHeight={10}
  203. minCropBoxWidth={10}
  204. background={false}
  205. responsive={true}
  206. autoCropArea={1}
  207. cropBoxResizable={false}
  208. checkOrientation={false}
  209. onInitialized={(instance: any) => {
  210. setCropper(instance);
  211. }}
  212. />
  213. </Col>
  214. <Col span={8}>
  215. <Row gutter={[10, 10]}>
  216. <Col span={24} style={{ height: 275 }}>
  217. <div className="box" style={{ width: "100%", float: "right", overflow: 'hidden' }}>
  218. <h1>Preview</h1>
  219. <div
  220. className="img-preview"
  221. style={{ width: "100%", float: "left", height: "200px", overflow: 'hidden' }}
  222. />
  223. </div>
  224. </Col>
  225. <Col>
  226. <Form>
  227. <Form.Item label="裁剪框宽" style={{ marginBottom: 0 }}>
  228. <Radio.Group disabled={isChangeCropperSize || isLaunch} onChange={(e) => { setDetail({ ...detail, width: e.target.value, height: 800 }) }} value={detail.width}>
  229. <Radio value={800}>800</Radio>
  230. <Radio value={750}>750</Radio>
  231. <Radio value={640}>640</Radio>
  232. </Radio.Group>
  233. </Form.Item>
  234. <Form.Item label="裁剪框高" style={{ marginBottom: 0 }}>
  235. {detail.width === 800 ? <Radio.Group disabled={isChangeCropperSize || isLaunch} onChange={(e) => { setDetail({ ...detail, height: e.target.value }) }} value={detail.height}>
  236. <Radio value={800}>800</Radio>
  237. <Radio value={640}>640</Radio>
  238. <Radio value={450}>450</Radio>
  239. </Radio.Group>
  240. : detail.width === 640 ? <Radio.Group disabled={isChangeCropperSize || isLaunch} onChange={(e) => { setDetail({ ...detail, height: e.target.value }) }} value={detail.height}>
  241. <Radio value={800}>800</Radio>
  242. </Radio.Group>
  243. :
  244. <InputNumber
  245. style={{ width: 200 }}
  246. value={detail.height}
  247. disabled={isChangeCropperSize ? isChangeCropperSize : isLaunch ? false : false}
  248. min={1}
  249. max={1536}
  250. onChange={(e) => { setDetail({ ...detail, height: e || 0 }) }}
  251. />
  252. }
  253. </Form.Item>
  254. </Form>
  255. </Col>
  256. </Row>
  257. </Col>
  258. </Row>
  259. </div>
  260. </div>
  261. </Modal>}
  262. </Spin>
  263. };
  264. export default Demo;