diff --git a/config/config.js b/config/config.js
index ac13ede4054a113c2dc5117fc15eae54feeb5e11..2cd3e7e7dc19eb2916017c91a6301807338d3636 100644
--- a/config/config.js
+++ b/config/config.js
@@ -113,6 +113,12 @@ export default {
icon: 'smile',
component: './Welcome',
},
+ {
+ path: '/preview',
+ name: 'preview',
+ icon: 'smile',
+ component: './demo/preview',
+ },
{
component: './404',
},
diff --git a/package.json b/package.json
index 7a1f66fa6daf21417fc19f28eb6a0451250b677f..82f1cecf9771a7bc0b9603507f35dca026fd64e2 100644
--- a/package.json
+++ b/package.json
@@ -54,10 +54,11 @@
"moment": "^2.24.0",
"omit.js": "^1.0.2",
"path-to-regexp": "^3.0.0",
+ "ppfish": "^1.7.1",
"prop-types": "^15.7.2",
"qs": "^6.7.0",
+ "rc-animate": "^2.10.0",
"react": "^16.8.6",
- "react-amap": "^1.2.8",
"react-copy-to-clipboard": "^5.0.1",
"react-document-title": "^2.0.3",
"react-dom": "^16.8.6",
diff --git a/src/components/ImageCropUpload/ReactImageCrop.jsx b/src/components/ImageCropUpload/ReactImageCrop.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..cfc8b1a2fccbb9433922cc6b3e49d622e7399ae6
--- /dev/null
+++ b/src/components/ImageCropUpload/ReactImageCrop.jsx
@@ -0,0 +1,746 @@
+import React, { Component } from 'react';
+import { Button, Icon, Slider } from 'antd';
+import PropTypes from 'prop-types';
+import mimes from '@/utils/common';
+import { data2blob, effectRipple } from '@/utils/common';
+import styles from './ReactImageCrop.less';
+
+class ReactImageCrop extends Component {
+ static defaultProps = {
+ // 图片上传格式
+ imgFormat: 'png',
+ // 图片背景 jpg情况下生效
+ imgBgc: '#fff',
+ // 剪裁图片的宽
+ width: 200,
+ // 剪裁图片的高
+ height: 200,
+ // 不显示旋转功能
+ noRotate: true,
+ // 关闭 圆形图像预览
+ noCircle: false,
+ // FIX 这个功能有bug
+ // 关闭 旋转图像功能
+ noSquare: false,
+ };
+
+ static propTypes = {
+ width: PropTypes.number,
+ height: PropTypes.number,
+ imgFormat: PropTypes.string,
+ imgBgc: PropTypes.string,
+ noCircle: PropTypes.bool,
+ noSquare: PropTypes.bool,
+ noRotate: PropTypes.bool,
+ off: PropTypes.func.isRequired,
+ upload: PropTypes.func.isRequired,
+ };
+
+ constructor(props) {
+ super(props);
+ this.canvasRef = React.createRef();
+ this.fileinput = React.createRef();
+
+ let allowImgFormat = ['jpg', 'png'],
+ tempImgFormat =
+ allowImgFormat.indexOf(this.props.imgFormat) === -1
+ ? "jpg"
+ : this.props.imgFormat,
+ nMime = mimes[tempImgFormat];
+
+ this.state = {
+ step: 1, // 步骤
+ file: null,
+ // 浏览器是否支持该控件
+ isSupported: typeof FormData === 'function',
+ // 浏览器是否支持触屏事件
+ isSupportTouch: document.hasOwnProperty('ontouchstart'),
+ // 原图片拖动事件初始值
+ sourceImgMouseDown: {
+ on: false,
+ mime: nMime,
+ mX: 0, // 鼠标按下的坐标
+ mY: 0,
+ x: 0, // scale原图坐标
+ y: 0,
+ },
+ // 原图展示属性
+ scale: {
+ zoomAddOn: false, // 按钮缩放事件开启
+ zoomSubOn: false, // 按钮缩放事件开启
+ range: 1, // 最大100
+
+ x: 0,
+ y: 0,
+ width: 0,
+ height: 0,
+ maxWidth: 0,
+ maxHeight: 0,
+ minWidth: 0, // 最宽
+ minHeight: 0,
+ naturalWidth: 0, // 原宽
+ naturalHeight: 0,
+ },
+ // 原图容器宽高
+ sourceImgContainer: {
+ // sic
+ width: 240,
+ height: 184, // 如果生成图比例与此一致会出现bug,先改成特殊的格式吧,哈哈哈
+ },
+ mime: mimes['jpg'],
+ // 需求图宽高比
+ ratio: this.props.width / this.props.height,
+ // 原图地址、生成图片地址
+ sourceImg: null,
+ sourceImgUrl: '',
+ createImgUrl: '',
+ // 生成图片预览的容器大小
+ previewContainer: {
+ width: 100,
+ height: 100,
+ },
+ // 滑动条数字
+ sliderValue: 0,
+ };
+ }
+
+ // 原图蒙版属性
+ get sourceImgMasking() {
+ const { sourceImgContainer, ratio } = this.state;
+ const { width, height } = this.props;
+ let sic = sourceImgContainer,
+ sicRatio = sic.width / sic.height, // 原图容器宽高比
+ x = 0,
+ y = 0,
+ w = sic.width,
+ h = sic.height,
+ scale = 1;
+ if (ratio < sicRatio) {
+ scale = sic.height / height;
+ w = sic.height * ratio;
+ x = (sic.width - w) / 2;
+ }
+ if (ratio > sicRatio) {
+ scale = sic.width / width;
+ h = sic.width / ratio;
+ y = (sic.height - h) / 2;
+ }
+ return {
+ scale, // 蒙版相对需求宽高的缩放
+ x,
+ y,
+ width: w,
+ height: h,
+ };
+ }
+
+ // 原图样式
+ get sourceImgStyle() {
+ const { scale } = this.state;
+ const sourceImgMasking = this.sourceImgMasking;
+ const top = scale.y + sourceImgMasking.y + 'px';
+ const left = scale.x + sourceImgMasking.x + 'px';
+ return {
+ position: 'absolute',
+ top,
+ left,
+ width: scale.width + 'px',
+ height: scale.height + 'px', // 兼容 Opera
+ };
+ }
+
+ // 原图遮罩样式
+ get sourceImgShadeStyle() {
+ const { sourceImgContainer } = this.state;
+ const sourceImgMasking = this.sourceImgMasking;
+ const sic = sourceImgContainer;
+ const sim = sourceImgMasking;
+ const w = sim.width === sic.width ? sim.width : (sic.width - sim.width) / 2;
+ const h = sim.height === sic.height ? sim.height : (sic.height - sim.height) / 2;
+ return {
+ width: w + 'px',
+ height: h + 'px',
+ };
+ }
+
+ get previewStyle() {
+ const { ratio, previewContainer } = this.state;
+ const pc = previewContainer;
+ let w = pc.width;
+ let h = pc.height;
+ const pcRatio = w / h;
+ if (ratio < pcRatio) {
+ w = pc.height * ratio;
+ }
+ if (ratio > pcRatio) {
+ h = pc.width / ratio;
+ }
+ return {
+ width: w + 'px',
+ height: h + 'px',
+ };
+ }
+
+ changeFile = e => {
+ e.preventDefault();
+ console.log('bb', e.target);
+ let files = e.target.files || e.dataTransfer.files;
+ this.setState({
+ file: files[0],
+ });
+ this.setSourceImg(files[0]);
+ this.setStep(2);
+ };
+
+ // 设置图片源
+ setSourceImg = file => {
+ // this.$emit('src-file-set', file.name, file.type, file.size);
+ const fr = new FileReader();
+ fr.onload = e => {
+ this.setState({ sourceImgUrl: fr.result });
+ this.startCrop();
+ };
+ fr.readAsDataURL(file);
+ };
+
+ // 剪裁前准备工作
+ startCrop = () => {
+ let that = this;
+ const { width, height } = that.props;
+ const { ratio, scale, sourceImgUrl } = that.state;
+ let sim = this.sourceImgMasking,
+ img = new Image();
+ img.src = sourceImgUrl;
+ img.onload = () => {
+ let nWidth = img.naturalWidth,
+ nHeight = img.naturalHeight,
+ nRatio = nWidth / nHeight,
+ w = sim.width,
+ h = sim.height,
+ x = 0,
+ y = 0;
+ // 图片像素不达标
+ if (nWidth < width || nHeight < height) {
+ that.hasError = true;
+ that.errorMsg = `图片最低像素为(宽*高):${width}*${height}`;
+ return false;
+ }
+ if (ratio > nRatio) {
+ h = w / nRatio;
+ y = (sim.height - h) / 2;
+ }
+ if (ratio < nRatio) {
+ w = h * nRatio;
+ x = (sim.width - w) / 2;
+ }
+ this.setState({
+ scale: {
+ ...scale,
+ range: 0,
+ x: x,
+ y: y,
+ width: w,
+ height: h,
+ minWidth: w,
+ minHeight: h,
+ maxWidth: nWidth * sim.scale,
+ maxHeight: nHeight * sim.scale,
+ naturalWidth: nWidth,
+ naturalHeight: nHeight,
+ },
+ sourceImg: img,
+ });
+ this.createImg();
+ };
+ };
+
+ prepareUpload = () => {
+ // const putExtra = {
+ // fname: this.file.name,
+ // }
+ // const config = {
+ // region: qiniu.region.z2
+ // }
+ // var observable = qiniu.upload(this.file, this.file.name, token, putExtra, config)
+ // var subscription = observable.subscribe(observer) // 上传开始
+ const { createImgUrl } = this.state,
+ blob = data2blob(createImgUrl);
+ this.props.upload({
+ createImgUrl,
+ blob,
+ file: this.state.file,
+ });
+ };
+
+ /* 图片选择区域函数绑定 */
+ preventDefault = e => {
+ e.preventDefault();
+ return false;
+ };
+
+ // 鼠标按下图片准备移动
+ imgStartMove = e => {
+ e.preventDefault();
+ // 支持触摸事件,则鼠标事件无效
+ if (this.state.isSupportTouch && !e.targetTouches) {
+ return false;
+ }
+ let et = e.targetTouches ? e.targetTouches[0] : e,
+ { sourceImgMouseDown, scale } = this.state,
+ simd = sourceImgMouseDown;
+ simd.mX = et.screenX;
+ simd.mY = et.screenY;
+ simd.x = scale.x;
+ simd.y = scale.y;
+ simd.on = true;
+ };
+
+ // 鼠标按下状态下移动,图片移动
+ imgMove = e => {
+ e.preventDefault();
+ // 支持触摸事件,则鼠标事件无效
+ if (this.state.isSupportTouch && !e.targetTouches) {
+ return false;
+ }
+ let et = e.targetTouches ? e.targetTouches[0] : e,
+ {
+ sourceImgMouseDown: { on, mX, mY, x, y },
+ scale
+ } = this.state,
+ sim = this.sourceImgMasking,
+ nX = et.screenX,
+ nY = et.screenY,
+ dX = nX - mX,
+ dY = nY - mY,
+ rX = x + dX,
+ rY = y + dY;
+ if (!on) return;
+ if (rX > 0) {
+ rX = 0;
+ }
+ if (rY > 0) {
+ rY = 0;
+ }
+ if (rX < sim.width - scale.width) {
+ rX = sim.width - scale.width;
+ }
+ if (rY < sim.height - scale.height) {
+ rY = sim.height - scale.height;
+ }
+ this.setState({
+ scale: {
+ ...this.state.scale,
+ x: rX,
+ y: rY,
+ },
+ });
+ };
+
+ // 生成需求图片
+ createImg = e => {
+ let that = this;
+ const { mime, sourceImg, scale: { x, y, width, height } } = that.state;
+ const { imgFormat, imgBgc } = that.props;
+ const { scale } = this.sourceImgMasking;
+ let canvas = this.canvasRef.current;
+ const ctx = canvas.getContext('2d');
+ if (e) {
+ // 取消鼠标按下移动状态
+ that.setState({
+ sourceImgMouseDown: {
+ ...that.state.sourceImgMouseDown,
+ on: false,
+ },
+ });
+ }
+ canvas.width = that.props.width;
+ canvas.height = that.props.height;
+ ctx.clearRect(0, 0, that.props.width, that.props.height);
+
+ if (imgFormat === 'png') {
+ ctx.fillStyle = 'rgba(0,0,0,0)';
+ } else {
+ // 如果jpg 为透明区域设置背景,默认白色
+ ctx.fillStyle = imgBgc;
+ }
+ ctx.fillRect(0, 0, that.props.width, that.props.height);
+
+ ctx.drawImage(
+ sourceImg,
+ x / scale,
+ y / scale,
+ width / scale,
+ height / scale,
+ );
+ that.setState({
+ createImgUrl: canvas.toDataURL(mime),
+ });
+ };
+
+ handleClick = e => {
+ if (e.target !== this.fileinput.current) {
+ e.preventDefault();
+ this.fileinput.current.click();
+ }
+ };
+
+ // 点击波纹效果
+ ripple = e => {
+ effectRipple(e);
+ };
+
+ setStep = val => {
+ if (val === 1) {
+ this.fileinput.current.value = null;
+ }
+ this.setState({ step: val });
+ };
+
+ // 按钮按下开始缩小
+ startZoomSub = e => {
+ let that = this,
+ { scale } = that.state;
+ this.setState({
+ scale: {
+ ...scale,
+ zoomSubOn: true
+ }
+ });
+
+ function zoom() {
+ if (scale.zoomSubOn) {
+ let range = scale.range <= 0 ? 0 : --scale.range;
+ that.zoomImg(range);
+ setTimeout(function() {
+ zoom();
+ }, 60);
+ }
+ }
+ zoom();
+ };
+
+ // 缩放原图
+ zoomImg = newRange => {
+ let that = this;
+ const { scale } = this.state;
+ const sourceImgMasking = this.sourceImgMasking;
+ console.log('a', sourceImgMasking);
+ const { maxWidth, maxHeight, minWidth, minHeight, width, height, x, y } = scale;
+ const sim = sourceImgMasking;
+ // 蒙版宽高
+ const sWidth = sim.width;
+ const sHeight = sim.height;
+ // 新宽高
+ const nWidth = minWidth + (maxWidth - minWidth) * newRange / 100;
+ const nHeight = minHeight + (maxHeight - minHeight) * newRange / 100;
+ // 新坐标(根据蒙版中心点缩放)
+ let nX = sWidth / 2 - nWidth / width * (sWidth / 2 - x);
+ let nY = sHeight / 2 - nHeight / height * (sHeight / 2 - y);
+
+ // 判断新坐标是否超过蒙版限制
+ if (nX > 0) {
+ nX = 0;
+ }
+ if (nY > 0) {
+ nY = 0;
+ }
+ if (nX < sWidth - nWidth) {
+ nX = sWidth - nWidth;
+ }
+ if (nY < sHeight - nHeight) {
+ nY = sHeight - nHeight;
+ }
+
+ // 赋值处理
+ this.setState({
+ scale: {
+ ...scale,
+ x: nX,
+ y: nY,
+ width: nWidth,
+ height: nHeight,
+ range: newRange,
+ },
+ });
+ setTimeout(function() {
+ if (scale.range === newRange) {
+ that.createImg();
+ }
+ }, 300);
+ };
+
+ // 按钮松开或移开取消缩小
+ endZoomSub = e => {
+ let { scale } = this.state;
+ this.setState({
+ scale: {
+ ...scale,
+ zoomSubOn: false,
+ },
+ });
+ };
+
+ // 按钮按下开始放大
+ startZoomAdd = e => {
+ let that = this,
+ { scale } = that.state;
+ this.setState({
+ scale: {
+ ...scale,
+ zoomAddOn: true
+ }
+ });
+
+ function zoom() {
+ if (scale.zoomAddOn) {
+ let range = scale.range >= 100 ? 100 : ++scale.range;
+ that.zoomImg(range);
+ setTimeout(function() {
+ zoom();
+ }, 60);
+ }
+ }
+ zoom();
+ };
+
+ // 按钮松开或移开取消放大
+ endZoomAdd = e => {
+ const { scale } = this.state;
+ this.setState({
+ scale: {
+ ...scale,
+ zoomAddOn: false,
+ },
+ });
+ };
+
+ zoomChange = value => {
+ console.log(value);
+ this.setState({
+ sliderValue: value,
+ });
+ this.zoomImg(value);
+ // this.zoomImg(e.target.value);
+ };
+
+ zoomSub = () => {
+ const { sliderValue } = this.state;
+ const value = sliderValue - 10;
+ this.setState({
+ sliderValue: value > 0 ? value : 0,
+ });
+ };
+
+ zoomAdd = () => {
+ const { sliderValue } = this.state;
+ const value = sliderValue + 10;
+ this.setState({
+ sliderValue: value < 100 ? value : 100,
+ });
+ };
+
+
+ // 顺时针旋转图片
+ rotateImg = e => {
+ let {
+ sourceImg,
+ scale: { naturalWidth, naturalHeight }
+ } = this.state,
+ width = naturalHeight,
+ height = naturalWidth,
+ canvas = this.canvasRef.current,
+ ctx = canvas.getContext("2d");
+ canvas.width = width;
+ canvas.height = height;
+ ctx.clearRect(0, 0, width, height);
+
+ ctx.fillStyle = "rgba(0,0,0,0)";
+ ctx.fillRect(0, 0, width, height);
+
+ ctx.translate(width, 0);
+ ctx.rotate(Math.PI * 90 / 180);
+
+ ctx.drawImage(sourceImg, 0, 0, naturalWidth, naturalHeight);
+ let imgUrl = canvas.toDataURL(mimes["png"]);
+
+ this.setState({
+ sourceImgUrl: imgUrl
+ });
+ this.startCrop();
+ };
+
+ showFiles = () => {
+ const { sourceImgUrl, createImgUrl, file, scale, sliderValue } = this.state;
+ const { noRotate, noCircle, noSquare } = this.props;
+
+ if (!file) {
+ return '';
+ }
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/**/}
+
+
+
+ {noRotate && (
+
+
+
+ )}
+
+
+ {!noSquare && (
+
+
+
头像预览
+
+ )}
+ {!noCircle && (
+
+
+
头像预览
+
+ )}
+
+
+ );
+ };
+
+ render() {
+ const { width, height, off } = this.props;
+ const { step, isSupported } = this.state;
+
+ return (
+
+ {/*
*/}
+ {/**/}
+ {/*
*/}
+
+
+
+
+
+
+
+ 点击,或拖动图片至此处
+
+
+
+
+
+ {/*
*/}
+
+ {this.showFiles()}
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+}
+
+export default ReactImageCrop;
diff --git a/src/components/ImageCropUpload/ReactImageCrop.less b/src/components/ImageCropUpload/ReactImageCrop.less
new file mode 100644
index 0000000000000000000000000000000000000000..151b9602e7f2b6bb01ccc2da0f323b269354d838
--- /dev/null
+++ b/src/components/ImageCropUpload/ReactImageCrop.less
@@ -0,0 +1,451 @@
+@keyframes ricu {
+ 0% {
+ opacity: 0;
+ transform: scale(0) translatey(-60px);
+ }
+ 100% {
+ opacity: 1;
+ transform: scale(1) translatey(0);
+ }
+}
+
+.ricu {
+ //position: fixed;
+ //display: block;
+ //box-sizing: border-box;
+ //z-index: 10000;
+ //top: 0;
+ //bottom: 0;
+ //left: 0;
+ //right: 0;
+ //width: 100%;
+ //height: 100%;
+ //background-color: rgba(0, 0, 0, 0.65);
+ //-webkit-tap-highlight-color: transparent;
+
+ //.wrap {
+ //box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.23);
+ //position: fixed;
+ //display: block;
+ //-webkit-box-sizing: border-box;
+ //box-sizing: border-box;
+ //z-index: 10000;
+ //top: 0;
+ //bottom: 0;
+ //left: 0;
+ //right: 0;
+ //margin: auto;
+ //width: 600px;
+ //height: 330px;
+ //padding: 25px;
+ //background-color: #fff;
+ //border-radius: 2px;
+ //animation: ricu 0.12s ease-in;
+ //}
+
+ .close {
+ position: absolute;
+ right: -30px;
+ top: -30px;
+ }
+
+ .icon4 {
+ position: relative;
+ display: block;
+ width: 30px;
+ height: 30px;
+ cursor: pointer;
+ transition: -webkit-transform 0.18s;
+ transform: rotate(0);
+
+ &::after,
+ &::before {
+ box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.23);
+ content: "";
+ position: absolute;
+ top: 12px;
+ left: 4px;
+ width: 20px;
+ height: 3px;
+ transform: rotate(45deg);
+ background-color: #fff;
+ }
+
+ &::after {
+ transform: rotate(-45deg);
+ }
+
+ &:hover {
+ transform: rotate(90deg);
+ }
+ }
+
+ .step1 {
+ .drop_area {
+ position: relative;
+ box-sizing: border-box;
+ padding: 35px;
+ height: 170px;
+ background-color: rgba(0, 0, 0, 0.03);
+ text-align: center;
+ border: 1px dashed rgba(0, 0, 0, 0.08);
+ overflow: hidden;
+
+ &:hover {
+ cursor: pointer;
+ border-color: rgba(0, 0, 0, 0.1);
+ background-color: rgba(0, 0, 0, 0.05);
+ }
+
+ .ricu-icon1 {
+ display: block;
+ margin: 0 auto 6px;
+ width: 42px;
+ height: 42px;
+ overflow: hidden;
+
+ .ricu-icon1-arrow {
+ display: block;
+ margin: 0 auto;
+ width: 0;
+ height: 0;
+ border-bottom: 14.7px solid rgba(0, 0, 0, 0.3);
+ border-left: 14.7px solid transparent;
+ border-right: 14.7px solid transparent;
+ }
+
+ .ricu-icon1-body {
+ display: block;
+ width: 12.6px;
+ height: 14.7px;
+ margin: 0 auto;
+ background-color: rgba(0, 0, 0, 0.3);
+ }
+
+ .ricu-icon1-bottom {
+ box-sizing: border-box;
+ display: block;
+ height: 12.6px;
+ border: 6px solid rgba(0, 0, 0, 0.3);
+ border-top: none;
+ }
+ }
+
+ .ricu-hint {
+ display: block;
+ padding: 15px;
+ font-size: 14px;
+ color: #666;
+ line-height: 30px;
+ }
+
+ .ricu-no-supported-hint {
+ display: block;
+ position: absolute;
+ top: 0;
+ left: 0;
+ padding: 30px;
+ width: 100%;
+ height: 60px;
+ line-height: 30px;
+ background-color: #eee;
+ text-align: center;
+ color: #666;
+ font-size: 14px;
+ }
+ }
+ }
+
+ .step2 {
+ display: flex;
+ justify-content: space-around;
+ margin: 10px auto;
+
+ .left {
+ position: relative;
+ width: 240px;
+ height: 180px;
+ overflow: hidden;
+ }
+
+ .right {
+ display: flex;
+ flex: 1;
+ justify-content: space-around;
+
+ img {
+ border: 1px solid rgba(0, 0, 0, 0.15);
+ }
+
+ .text {
+ margin-top: 10px;
+ color: #bbb;
+ font-size: 14px;
+ text-align: center;
+ }
+ }
+
+ .img_shade {
+ box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.18);
+ position: absolute;
+ background-color: rgba(241, 242, 243, 0.8);
+ }
+
+ .img_shade_1 {
+ top: 0;
+ left: 0;
+ }
+
+ .img_shade_2 {
+ bottom: 0;
+ right: 0;
+ }
+ }
+
+ .operate {
+ position: absolute;
+ right: 20px;
+ bottom: 20px;
+
+ button {
+ border: none;
+ position: relative;
+ float: left;
+ display: block;
+ margin-left: 10px;
+ width: 100px;
+ height: 36px;
+ line-height: 36px;
+ text-align: center;
+ cursor: pointer;
+ font-size: 14px;
+ color: #4a7;
+ border-radius: 2px;
+ overflow: hidden;
+ user-select: none;
+ outline: none;
+
+ &:hover {
+ background-color: rgba(0, 0, 0, 0.03);
+ }
+
+ &:focus, &:active {
+ border: none;
+ }
+ }
+ }
+
+ .range {
+ position: relative;
+ margin: 30px 0 10px 0;
+ width: 240px;
+ height: 18px;
+
+ .icon5, .icon6 {
+ position: absolute;
+ top: 0;
+ width: 18px;
+ height: 18px;
+ border-radius: 100%;
+ background-color: rgba(0, 0, 0, 0.08);
+ }
+
+ .icon5:hover, .icon6:hover {
+ box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12);
+ cursor: pointer;
+ background-color: rgba(0, 0, 0, 0.14);
+ }
+
+ .icon5 {
+ left: 0;
+
+ &::before {
+ position: absolute;
+ content: "";
+ display: block;
+ left: 3px;
+ top: 8px;
+ width: 12px;
+ height: 2px;
+ background-color: #fff;
+ }
+ }
+
+ .icon6 {
+ right: 0;
+
+ &::before {
+ position: absolute;
+ content: "";
+ display: block;
+ left: 3px;
+ top: 8px;
+ width: 12px;
+ height: 2px;
+ background-color: #fff;
+ }
+
+ &::after {
+ position: absolute;
+ content: "";
+ display: block;
+ top: 3px;
+ left: 8px;
+ width: 2px;
+ height: 12px;
+ background-color: #fff;
+ }
+ }
+
+ input[type="range"] {
+ display: block;
+ padding-top: 5px;
+ margin: 0 auto;
+ width: 180px;
+ height: 8px;
+ vertical-align: top;
+ background: transparent;
+ appearance: none;
+ cursor: pointer;
+
+ &:focus {
+ outline: none;
+ }
+
+ &::-webkit-slider-thumb {
+ -webkit-box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.18);
+ box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.18);
+ -webkit-appearance: none;
+ appearance: none;
+ margin-top: -3px;
+ width: 12px;
+ height: 12px;
+ background-color: #61c091;
+ border-radius: 100%;
+ border: none;
+ -webkit-transition: 0.2s;
+ transition: 0.2s;
+ }
+
+ &::-moz-range-thumb {
+ box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.18);
+ -moz-appearance: none;
+ appearance: none;
+ width: 12px;
+ height: 12px;
+ background-color: #61c091;
+ border-radius: 100%;
+ border: none;
+ -webkit-transition: 0.2s;
+ transition: 0.2s;
+ }
+
+ &::-ms-thumb {
+ box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.18);
+ appearance: none;
+ width: 12px;
+ height: 12px;
+ background-color: #61c091;
+ border: none;
+ border-radius: 100%;
+ -webkit-transition: 0.2s;
+ transition: 0.2s;
+ }
+
+ &:active::-moz-range-thumb {
+ box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.23);
+ width: 14px;
+ height: 14px;
+ }
+
+ &:active::-ms-thumb {
+ box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.23);
+ width: 14px;
+ height: 14px;
+ }
+
+ &:active::-webkit-slider-thumb {
+ -webkit-box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.23);
+ box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.23);
+ margin-top: -4px;
+ width: 14px;
+ height: 14px;
+ }
+
+ &::-webkit-slider-runnable-track {
+ -webkit-box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12);
+ box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12);
+ width: 100%;
+ height: 6px;
+ cursor: pointer;
+ border-radius: 2px;
+ border: none;
+ background-color: rgba(68, 170, 119, 0.3);
+ }
+
+ &::-moz-range-track {
+ box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12);
+ width: 100%;
+ height: 6px;
+ cursor: pointer;
+ border-radius: 2px;
+ border: none;
+ background-color: rgba(68, 170, 119, 0.3);
+ }
+
+ &::-ms-track {
+ box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12);
+ width: 100%;
+ cursor: pointer;
+ background: transparent;
+ border-color: transparent;
+ color: transparent;
+ height: 6px;
+ border-radius: 2px;
+ border: none;
+ }
+
+ &::-ms-fill-lower {
+ background-color: rgba(68, 170, 119, 0.3);
+ }
+
+ &::-ms-fill-upper {
+ background-color: rgba(68, 170, 119, 0.15);
+ }
+
+ &:focus::-webkit-slider-runnable-track {
+ background-color: rgba(68, 170, 119, 0.5);
+ }
+
+ &:focus::-moz-range-track {
+ background-color: rgba(68, 170, 119, 0.5);
+ }
+
+ &:focus::-ms-fill-lower {
+ background-color: rgba(68, 170, 119, 0.45);
+ }
+
+ &:focus::-ms-fill-upper {
+ background-color: rgba(68, 170, 119, 0.25);
+ }
+ }
+ }
+
+ .e-ripple {
+ position: absolute;
+ border-radius: 100%;
+ background-color: rgba(0, 0, 0, 0.15);
+ background-clip: padding-box;
+ pointer-events: none;
+ user-select: none;
+ transform: scale(0);
+ opacity: 1;
+ }
+
+ .e-ripple.z-active {
+ opacity: 0;
+ transform: scale(2);
+ transition: opacity 1.2s ease-out, transform 0.6s ease-out,
+ -webkit-transform 0.6s ease-out;
+ }
+}
diff --git a/src/components/ImageCropUpload/index.jsx b/src/components/ImageCropUpload/index.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..f34a4c544372ac801ed09bccb2adbb96c79f5c36
--- /dev/null
+++ b/src/components/ImageCropUpload/index.jsx
@@ -0,0 +1,186 @@
+import React, { Component } from 'react';
+import { Icon, Upload, Modal, message } from 'antd';
+import PropTypes from 'prop-types';
+import ReactImageCrop from './ReactImageCrop';
+import { data2blob } from '@/utils/common';
+
+function getBase64(img, callback) {
+ const reader = new FileReader();
+ reader.addEventListener('load', () => callback(reader.result));
+ reader.readAsDataURL(img);
+}
+
+class ReactImageCropUpload extends Component {
+ static defaultProps = {
+ // 上传接口地址,如果为空,图片不会上传
+ url: '',
+ // 上传方法
+ method: 'POST',
+ // 向服务器上传的文件名
+ field: 'upload',
+ // 上传附带其他数据,格式 {k:v}
+ params: null,
+ // 上传header设置,格式 {k:v}
+ headers: null,
+ // 支持跨域
+ withCredentials: false,
+ // 原名key,类似于id,触发事件会带上(如果一个页面多个图片上传控件,可以做区分
+ ki: 0,
+ };
+
+ static propTypes = {
+ url: PropTypes.string,
+ method: PropTypes.string,
+ field: PropTypes.string,
+ params: PropTypes.object,
+ headers: PropTypes.object,
+ withCredentials: PropTypes.bool,
+ ki: PropTypes.number,
+ // handleCropUploadSuccess: PropTypes.func.isRequired,
+ // handleCropUploadFail: PropTypes.func.isRequired,
+ };
+
+ constructor(props) {
+ super(props);
+ this.cropRef = React.createRef();
+ this.state = {
+ visible: false,
+ }
+ }
+
+ // 上传图片
+ upload = ({ createImgUrl, blob, file }) => {
+ const child = this.cropRef.current;
+ const { url, params, headers, field, withCredentials, method, handleCropUploadSuccess, handleCropUploadFail, ki } = this.props;
+ const { imgFormat } = child.state;
+ const fmData = new FormData();
+ fmData.append(field, data2blob(createImgUrl), field + "." + imgFormat);
+
+ // 添加其他参数
+ if (typeof params === 'object' && params) {
+ Object.keys(params).forEach(k => {
+ fmData.append(k, params[k]);
+ });
+ }
+
+ // 监听进度回调
+ // const uploadProgress = function(event) {
+ // if (event.lengthComputable) {
+ // that.progress = 100 * Math.round(event.loaded) / event.total;
+ // }
+ // };
+ // 上传文件
+ // child.setStep(3);
+ new Promise(function(resolve, reject) {
+ let client = new XMLHttpRequest();
+ client.open(method, url, true);
+ client.withCredentials = withCredentials;
+ client.onreadystatechange = function() {
+ if (this.readyState !== 4) {
+ return;
+ }
+ if (this.status === 200 || this.status === 201) {
+ resolve(JSON.parse(this.responseText));
+ } else {
+ reject(this.status);
+ }
+ };
+ // client.upload.addEventListener("progress", uploadProgress, false); //监听进度
+ // 设置header
+ if (typeof headers === 'object' && headers) {
+ Object.keys(headers).forEach(k => {
+ client.setRequestHeader(k, headers[k]);
+ });
+ }
+ client.send(fmData);
+ }).then(
+ // 上传成功
+ function(resData) {
+ handleCropUploadSuccess(resData, field, ki);
+ },
+ // 上传失败
+ function(sts) {
+ handleCropUploadFail(sts, field, ki);
+ },
+ );
+ };
+
+ handleChange = info => {
+ if (info.file.status === 'uploading') {
+ this.setState({ loading: true });
+ return;
+ }
+ if (info.file.status === 'done') {
+ // Get this url from response in real world.
+ getBase64(info.file.originFileObj, imageUrl =>
+ this.setState({
+ imageUrl,
+ loading: false,
+ }),
+ );
+ }
+ };
+
+ beforeUpload = (file) => {
+ const imageType = ['image/jpeg', 'image/png', 'image/jpg', 'image/gif'];
+ const isImage = imageType.findIndex(o => o === file.type) !== -1;
+ if (!isImage) {
+ message.error('请选择正确的图片类型!');
+ return false;
+ }
+ const isLt2M = file.size / 1024 / 1024 < 2;
+ if (!isLt2M) {
+ message.error('图片大小不能超过2M!');
+ return false;
+ }
+
+ let reader = new FileReader();
+ reader.readAsDataURL(file);
+ let _this = this;
+ reader.onload = (e) => {
+ _this.setState({
+ cropSrc: e.target.result,
+ visible: true,
+ })
+ }
+ return new Promise((resolve, reject) => {
+ let index = setInterval(() => {
+ if (this.blob) { // 监听裁剪是否完成
+ window.clearInterval(index);
+ this.blob.uid = file.uid; // 需要给裁剪后的blob对象设置uid,否则会报错!!!
+ resolve(this.blob); // 执行后续的上传操作
+ }
+ },300);
+ });
+ };
+
+ render() {
+ const { imageUrl, visible } = this.state;
+ const uploadButton = (
+
+ );
+ return (
+
+
+ {imageUrl ? : uploadButton}
+
+
+
+
+
+ );
+ }
+}
+
+export default ReactImageCropUpload;
diff --git a/src/components/ImagesPreview/index.jsx b/src/components/ImagesPreview/index.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..9b21df046b827c0b5279b8165195955abd20b90e
--- /dev/null
+++ b/src/components/ImagesPreview/index.jsx
@@ -0,0 +1,56 @@
+import React, { PureComponent } from 'react';
+import { PicturePreview } from 'ppfish';
+import styles from './index.less';
+
+class ImagesPreview extends PureComponent {
+ constructor(props) {
+ super(props);
+ console.log('组件传值打印', this.props);
+ this.state = {
+ visible: false,
+ activeIndex: 0,
+ };
+ }
+
+ handleOpen = (index) => {
+ this.setState({
+ visible: true,
+ activeIndex: index,
+ });
+ };
+
+ handleClose = () => {
+ this.setState({
+ visible: false,
+ });
+ };
+
+ render() {
+ const { visible, activeIndex } = this.state;
+ return (
+
+
+
+ {
+ this.props.source && this.props.source.map((item, index) =>
+
+
+
{item.name}
+
,
+ )
+ }
+
+
+
+
+ );
+ }
+}
+
+export default ImagesPreview;
diff --git a/src/components/ImagesPreview/index.less b/src/components/ImagesPreview/index.less
new file mode 100644
index 0000000000000000000000000000000000000000..830e429878aeb5f5668143e5ff9675c7cabb2429
--- /dev/null
+++ b/src/components/ImagesPreview/index.less
@@ -0,0 +1,24 @@
+.picpreview {
+ display: -ms-flexbox;
+ display: flex;
+ .pics {
+ display: -ms-flexbox;
+ display: flex;
+ -ms-flex: 1;
+ flex: 1;
+ -ms-flex-wrap: wrap;
+ flex-wrap: wrap;
+ -ms-flex-pack: center;
+ justify-content: start;
+ .item {
+ margin-left: 20px;
+ cursor: pointer;
+ }
+ .item:first-child {
+ margin-left: 0;
+ }
+ .name {
+ text-align: center;
+ }
+ }
+}
diff --git a/src/components/XqUpload/index.jsx b/src/components/XqUpload/index.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..fc4414875ab2aca913013caafe5891341ab5ce4e
--- /dev/null
+++ b/src/components/XqUpload/index.jsx
@@ -0,0 +1,134 @@
+import React, { Component } from 'react';
+import { AutoComplete, Icon, Input, Upload, message } from 'antd';
+import { PicturePreview } from 'ppfish';
+import styles from './index.less';
+
+export default class XqUpload extends Component {
+ static defaultProps = {
+ defaultActiveFirstOption: false,
+ onPressEnter: () => {},
+ onSearch: () => {},
+ onChange: () => {},
+ className: '',
+ placeholder: '',
+ dataSource: [],
+ defaultOpen: false,
+ onVisibleChange: () => {},
+ };
+
+ static getDerivedStateFromProps(props) {
+ if ('open' in props) {
+ return {
+ searchMode: props.open,
+ };
+ }
+ return null;
+ }
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ visible: false,
+ activeIndex: 0,
+ fileData: [
+ {
+ uid: '-1',
+ name: 'image.png',
+ status: 'done',
+ url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
+ src: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
+ },
+ {
+ uid: '-2',
+ name: 'image.png',
+ status: 'done',
+ url: 'https://nos.netease.com/ysf/3df2280d2319678a091138b0bbba82fe',
+ src: 'https://nos.netease.com/ysf/3df2280d2319678a091138b0bbba82fe',
+ },
+ ],
+ };
+ }
+
+ componentWillUnmount() {
+ clearTimeout(this.timeout);
+ }
+
+ handlePreview = async file => {
+ const { fileData } = this.state;
+ this.setState({
+ visible: true,
+ activeIndex: fileData.findIndex(item => item.uid === file.uid),
+ });
+ };
+
+ handleClose = () => {
+ this.setState({
+ visible: false,
+ });
+ };
+
+ render() {
+ // const { className, placeholder, open, ...restProps } = this.props;
+ const { activeIndex, visible, fileData } = this.state;
+ const props = {
+ name: 'file',
+ multiple: true,
+ listType: 'picture-card',
+ fileList: fileData,
+ onPreview: this.handlePreview,
+ action: 'https://www.mocky.io/v2/5cc8019d300000980a055e76',
+ transformFile(file) {
+ return new Promise(resolve => {
+ const reader = new FileReader();
+ reader.readAsDataURL(file);
+ reader.onload = () => {
+ const canvas = document.createElement('canvas');
+ const img = document.createElement('img');
+ img.src = reader.result;
+ img.onload = () => {
+ const ctx = canvas.getContext('2d');
+ ctx.drawImage(img, 0, 0);
+ ctx.fillStyle = 'red';
+ ctx.textBaseline = 'middle';
+ ctx.fillText('Ant Design', 20, 20);
+ canvas.toBlob(resolve);
+ };
+ };
+ });
+ },
+ onChange(info) {
+ console.log('info', info);
+ const { status } = info.file;
+ if (status !== 'uploading') {
+ console.log(info.file, info.fileList);
+ }
+ if (status === 'done') {
+ message.success(`${info.file.name} file uploaded successfully.`);
+ } else if (status === 'error') {
+ message.error(`${info.file.name} file upload failed.`);
+ }
+ },
+ };
+
+ const uploadButton = (
+
+ );
+ return (
+
+
+ { fileData.length >= 8 ? null : uploadButton }
+
+
+
+ );
+ }
+}
diff --git a/src/components/XqUpload/index.less b/src/components/XqUpload/index.less
new file mode 100644
index 0000000000000000000000000000000000000000..8f40cc7f5a742d90bf1f2844f836c172089c1fad
--- /dev/null
+++ b/src/components/XqUpload/index.less
@@ -0,0 +1,32 @@
+@import '~antd/es/style/themes/default.less';
+
+.headerSearch {
+ :global(.anticon-search) {
+ font-size: 16px;
+ cursor: pointer;
+ }
+ .input {
+ width: 0;
+ background: transparent;
+ border-radius: 0;
+ transition: width 0.3s, margin-left 0.3s;
+ :global(.ant-select-selection) {
+ background: transparent;
+ }
+ input {
+ padding-right: 0;
+ padding-left: 0;
+ border: 0;
+ box-shadow: none !important;
+ }
+ &,
+ &:hover,
+ &:focus {
+ border-bottom: 1px solid @border-color-base;
+ }
+ &.show {
+ width: 210px;
+ margin-left: 8px;
+ }
+ }
+}
diff --git a/src/pages/demo/preview/index.jsx b/src/pages/demo/preview/index.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..da333908832ed6ed09c3cb806437b0fa7ae7b493
--- /dev/null
+++ b/src/pages/demo/preview/index.jsx
@@ -0,0 +1,77 @@
+import React, { Component } from 'react';
+import { Card, Button, Upload, Modal } from 'antd';
+import { PageHeaderWrapper } from '@ant-design/pro-layout';
+import { RichEditor } from 'ppfish';
+import ImagesPreview from '@/components/ImagesPreview';
+import XqUpload from '@/components/XqUpload';
+import ImageCropUpload from '@/components/ImageCropUpload';
+import styles from './style.less';
+
+class Preview extends Component {
+ state = {
+ imgData: [
+ {
+ id: 1,
+ name: '图片',
+ src: 'http://s6.sinaimg.cn/mw690/003tzcWugy70fFioNtre5&690',
+ },
+ {
+ id: 2,
+ name: '图片2',
+ src: 'https://v.feituapp.com/attachment/images/6/2019/06/rMFlfimOLEIOzI5vFLT1O9vu5p191V.jpg',
+ },
+ ],
+ visible: false,
+ };
+
+ handleClick() {
+ this.setState({ visible: true });
+ }
+
+ off() {
+ this.setState({ visible: false });
+ }
+
+ handleCropUploadSuccess(resData, field, ki) {
+ console.log('resData, field, ki===>>>>', resData, field, ki);
+ this.off()
+ }
+
+ handleCropUploadFail(sts, field, ki) {
+ console.log('sts, field, ki===>>>>', sts, field, ki);
+ }
+
+ render() {
+ const { imgData, visible } = this.state;
+ return (
+
+
+ {/**/}
+ {/*
*/}
+ {/*
*/}
+ {/**/}
+ {/**/}
+ {/**/}
+ {/*
*/}
+ {/**/}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+}
+
+export default Preview;
diff --git a/src/pages/demo/preview/style.less b/src/pages/demo/preview/style.less
new file mode 100644
index 0000000000000000000000000000000000000000..006bfc24e0ae6cac017c0d63a7e2009cda3973fd
--- /dev/null
+++ b/src/pages/demo/preview/style.less
@@ -0,0 +1,54 @@
+@import '~antd/es/style/themes/default.less';
+
+.avatar {
+ width: 144px;
+ height: 144px;
+ margin-bottom: 12px;
+ overflow: hidden;
+ img {
+ width: 100%;
+ }
+}
+
+.button_view {
+ width: 144px;
+ text-align: center;
+}
+
+.main {
+ width: 368px;
+ margin: 0 auto;
+ @media screen and (max-width: @screen-sm) {
+ width: 95%;
+ }
+
+ .icon {
+ margin-left: 16px;
+ color: rgba(0, 0, 0, 0.2);
+ font-size: 24px;
+ vertical-align: middle;
+ cursor: pointer;
+ transition: color 0.3s;
+
+ &:hover {
+ color: @primary-color;
+ }
+ }
+
+ .other {
+ margin-top: 24px;
+ line-height: 22px;
+ text-align: left;
+
+ .register {
+ float: right;
+ }
+ }
+
+ :global {
+ .antd-pro-login-submit {
+ width: 100%;
+ margin-top: 24px;
+ }
+ }
+}