小程序图片裁剪插件image-cropper实现个人头像上传裁剪功能

小程序图片裁剪插件image-cropper实现个人头像上传裁剪功能
参考文档:小程序图片裁剪插件 image-cropper
整体效果流程图
在这里插入图片描述

一、第一步引入image-cropper,放在dist文件夹下
在这里插入图片描述
image-cropper.wxml

  <view class='image-cropper' catchtouchmove='_preventTouchMove'>
    <view class='main' bindtouchend="_cutTouchEnd" bindtouchstart="_cutTouchStart" bindtouchmove="_cutTouchMove" bindtap="_click">
      <view class='content'>
        <view class='content_top bg_gray {{_flag_bright?"":"bg_black"}}' style="height:{{cut_top}}px;transition-property:{{_cut_animation?'':'background'}}"></view>
        <view class='content_middle' style="height:{{height}}px;">
          <view class='content_middle_left bg_gray {{_flag_bright?"":"bg_black"}}' style="width:{{cut_left}}px;transition-property:{{_cut_animation?'':'background'}}"></view>
          <view class='content_middle_middle' style="width:{{width}}px;height:{{height}}px;transition-duration: .3s;transition-property:{{_cut_animation?'':'background'}};">
            <view class="border border-top-left"></view>
            <view class="border border-top-right"></view>
            <view class="border border-right-top"></view>
            <view class="border border-right-bottom"></view>
            <view class="border border-bottom-right"></view>
            <view class="border border-bottom-left"></view>
            <view class="border border-left-bottom"></view>
            <view class="border border-left-top"></view>
          </view>
          <view class='content_middle_right bg_gray {{_flag_bright?"":"bg_black"}}' style="transition-property:{{_cut_animation?'':'background'}}"></view>
        </view>
        <view class='content_bottom bg_gray {{_flag_bright?"":"bg_black"}}' style="transition-property:{{_cut_animation?'':'background'}}"></view>
      </view>
      <image bindload="imageLoad" bindtouchstart="_start" bindtouchmove="_move" bindtouchend="_end" style="width:{{img_width ? img_width + 'px' : 'auto'}};height:{{img_height ? img_height + 'px' : 'auto'}};transform:translate3d({{_img_left-img_width/2}}px,{{_img_top-img_height/2}}px,0) scale({{scale}}) rotate({{angle}}deg);transition-duration:{{_cut_animation?.4:0}}s;" class='img' src='{{imgSrc}}'></image>
    </view>
    <canvas canvas-id='image-cropper' disable-scroll="true" style="width:{{_canvas_width * export_scale}}px;height:{{_canvas_height * export_scale}}px;left:{{canvas_left}}px;top:{{canvas_top}}px" class='image-cropper-canvas'></canvas>
  </view>

image-cropper.wxss

.image-cropper{
  background:rgba(14, 13, 13,.8);
  position: fixed;
  top:0;
  left:0;
  width:100vw;
  height:100vh;
  z-index: 1;
}
.main{
  position: absolute;
  width:100vw;
  height:100vh;
  overflow: hidden;
}
.content{
  z-index: 9;
  position: absolute;
  width:100vw;
  height:100vh;
  display: flex;
  flex-direction:column;
  pointer-events:none;
}
.bg_black{
  background: rgba(0, 0, 0, 0.8)!important;
}
.bg_gray{
  background: rgba(0, 0, 0, 0.45);
  transition-duration: .35s;
}
.content>.content_top{
  pointer-events:none;
}
.content>.content_middle{
  display: flex;
  height: 200px;
  width:100%;
}
.content_middle_middle{
  width:200px;
  box-sizing:border-box;
  position: relative;
  transition-duration: .3s;
}
.content_middle_right{
  flex: auto;
}
.content>.content_bottom{
  flex: auto;
}
.image-cropper .img{
  z-index: 2;
  top:0;
  left:0;
  position: absolute;
  border:none;
  width:100%;
  backface-visibility: hidden;
  transform-origin:center;
}
.image-cropper-canvas{
  position: fixed;
  background: white;
  width:150px;
  height:150px;
  z-index: 10;
  top:-200%;
  pointer-events:none;
}
.border{
  background: white;
  pointer-events:auto;
  position:absolute;
}
.border-top-left{
  left:-2.5px;
  top:-2.5px;
  height:2.5px;
  width:33rpx;
}
.border-top-right{
  right:-2.5px;
  top:-2.5px;
  height:2.5px;
  width:33rpx;
}
.border-right-top{
  top:-1px;
  width:2.5px;
  height:30rpx;
  right:-2.5px;
}
.border-right-bottom{
  width:2.5px;
  height:30rpx;
  right:-2.5px;
  bottom:-1px;
}
.border-bottom-left{
  height:2.5px;
  width:33rpx;
  bottom:-2.5px;
  left:-2.5px;
}
.border-bottom-right{
  height:2.5px;
  width:33rpx;
  bottom:-2.5px;
  right:-2.5px;
}
.border-left-top{
  top:-1px;
  width:2.5px;
  height:30rpx;
  left:-2.5px;
}
.border-left-bottom{
  width:2.5px;
  height:30rpx;
  left:-2.5px;
  bottom:-1px;
}

image-cropper.json

{
  "component": true
}

image-cropper.js

Component({
  properties: {
    /**     
     * 图片路径
     */
    'imgSrc': {
      type: String
    },
    /**
     * 裁剪框高度
     */
    'height': {
      type: Number,
      value: 200
    },
    /**
     * 裁剪框宽度
     */
    'width': {
      type: Number,
      value: 200
    },
    /**
     * 裁剪框最小尺寸
     */
    'min_width': {
      type: Number,
      value: 100
    },
    'min_height': {
      type: Number,
      value: 100
    },
    /**
     * 裁剪框最大尺寸
     */
    'max_width': {
      type: Number,
      value: 300
    },
    'max_height': {
      type: Number,
      value: 300
    },
    /**
     * 裁剪框禁止拖动
     */
    'disable_width': {
      type: Boolean,
      value: false
    },
    'disable_height': {
      type: Boolean,
      value: false
    },
    /**
     * 锁定裁剪框比例
     */
    'disable_ratio': {
      type: Boolean,
      value: false
    },
    /**
     * 生成的图片尺寸相对剪裁框的比例
     */
    'export_scale': {
      type: Number,
      value: 3
    },
    /**
     * 生成的图片质量0-1
     */
    'quality': {
      type: Number,
      value: 1
    },
    'cut_top': {
      type: Number,
      value: null
    },
    'cut_left': {
      type: Number,
      value: null
    },
    /**
     * canvas上边距(不设置默认不显示)
     */
    'canvas_top': {
      type: Number,
      value: null
    },
    /**
     * canvas左边距(不设置默认不显示)
     */
    'canvas_left': {
      type: Number,
      value: null
    },
    /**
     * 图片宽度
     */
    'img_width': {
      type: null,
      value: null
    },
    /**
     * 图片高度
     */
    'img_height': {
      type: null,
      value: null
    },
    /**
     * 图片缩放比
     */
    'scale': {
      type: Number,
      value: 1
    },
    /**
     * 图片旋转角度
     */
    'angle': {
      type: Number,
      value: 0
    },
    /**
     * 最小缩放比
     */
    'min_scale': {
      type: Number,
      value: 0.5
    },
    /**
     * 最大缩放比
     */
    'max_scale': {
      type: Number,
      value: 2
    },
    /**
     * 是否禁用旋转
     */
    'disable_rotate': {
      type: Boolean,
      value: false
    },
    /**
     * 是否限制移动范围(剪裁框只能在图片内)
     */
    'limit_move': {
      type: Boolean,
      value: false
    }
  },
  data: {
    el: 'image-cropper', //暂时无用
    info: wx.getSystemInfoSync(),
    MOVE_THROTTLE: null,//触摸移动节流settimeout
    MOVE_THROTTLE_FLAG: true,//节流标识
    INIT_IMGWIDTH: 0, //图片设置尺寸,此值不变(记录最初设定的尺寸)
    INIT_IMGHEIGHT: 0, //图片设置尺寸,此值不变(记录最初设定的尺寸)
    TIME_BG: null,//背景变暗延时函数
    TIME_CUT_CENTER: null,
    _touch_img_relative: [{
      x: 0,
      y: 0
    }], //鼠标和图片中心的相对位置
    _flag_cut_touch: false,//是否是拖动裁剪框
    _hypotenuse_length: 0, //双指触摸时斜边长度
    _flag_img_endtouch: false, //是否结束触摸
    _flag_bright: true, //背景是否亮
    _canvas_overflow: true,//canvas缩略图是否在屏幕外面
    _canvas_width: 200,
    _canvas_height: 200,
    origin_x: 0.5, //图片旋转中心
    origin_y: 0.5, //图片旋转中心
    _cut_animation: false,//是否开启图片和裁剪框过渡
    _img_top: wx.getSystemInfoSync().windowHeight / 2, //图片上边距
    _img_left: wx.getSystemInfoSync().windowWidth / 2, //图片左边距
    watch: {
      //监听截取框宽高变化
      width(value, that) {
        if (value < that.data.min_width) {
          that.setData({
            width: that.data.min_width
          });
        }
        that._computeCutSize();
      },
      height(value, that) {
        if (value < that.data.min_height) {
          that.setData({
            height: that.data.min_height
          });
        }
        that._computeCutSize();
      },
      angle(value, that) {
        //停止居中裁剪框,继续修改图片位置
        that._moveStop();
        if (that.data.limit_move) {
          if (that.data.angle % 90) {
            that.setData({
              angle: Math.round(that.data.angle / 90) * 90
            });
            return;
          }
        }
      },
      _cut_animation(value, that) {
        //开启过渡300毫秒之后自动关闭
        clearTimeout(that.data._cut_animation_time);
        if (value) {
          that.data._cut_animation_time = setTimeout(() => {
            that.setData({
              _cut_animation: false
            });
          }, 300)
        }
      },
      limit_move(value, that) {
        if (value) {
          if (that.data.angle % 90) {
            that.setData({
              angle: Math.round(that.data.angle / 90) * 90
            });
          }
          that._imgMarginDetectionScale();
          !that.data._canvas_overflow && that._draw();
        }
      },
      canvas_top(value, that) {
        that._canvasDetectionPosition();
      },
      canvas_left(value, that) {
        that._canvasDetectionPosition();
      },
      imgSrc(value, that) {
        that.pushImg();
      },
      cut_top(value, that) {
        that._cutDetectionPosition();
        if (that.data.limit_move) {
          !that.data._canvas_overflow && that._draw();
        }
      },
      cut_left(value, that) {
        that._cutDetectionPosition();
        if (that.data.limit_move) {
          !that.data._canvas_overflow && that._draw();
        }
      }
    }
  },
  attached() {
    this.data.info = wx.getSystemInfoSync();
    //启用数据监听
    this._watcher();
    this.data.INIT_IMGWIDTH = this.data.img_width;
    this.data.INIT_IMGHEIGHT = this.data.img_height;
    this.setData({
      _canvas_height: this.data.height,
      _canvas_width: this.data.width,
    });
    this._initCanvas();
    this.data.imgSrc && (this.data.imgSrc = this.data.imgSrc);
    //根据开发者设置的图片目标尺寸计算实际尺寸
    this._initImageSize();
    //设置裁剪框大小>设置图片尺寸>绘制canvas
    this._computeCutSize();
    //检查裁剪框是否在范围内
    this._cutDetectionPosition();
    //检查canvas是否在范围内
    this._canvasDetectionPosition();
    //初始化完成
    this.triggerEvent('load', {
      cropper: this
    });
  },
  methods: {
    /**
     * 上传图片
     */
    upload() {
      let that = this;
      wx.chooseImage({
        count: 1,
        sizeType: ['original', 'compressed'],
        sourceType: ['album', 'camera'],
        success(res) {
          const tempFilePaths = res.tempFilePaths[0];
          that.pushImg(tempFilePaths);
          wx.showLoading({
            title: '加载中...'
          })
        }
      })
    },
    /**
     * 返回图片信息
     */
    getImg(getCallback) {
      this._draw(() => {
        wx.canvasToTempFilePath({
          width: this.data.width * this.data.export_scale,
          height: Math.round(this.data.height * this.data.export_scale),
          destWidth: this.data.width * this.data.export_scale,
          destHeight: Math.round(this.data.height) * this.data.export_scale,
          fileType: 'png',
          quality: this.data.quality,
          canvasId: this.data.el,
          success: (res) => {
            getCallback({
              url: res.tempFilePath,
              width: this.data.width * this.data.export_scale,
              height: this.data.height * this.data.export_scale
            });
          }
        }, this)
      });
    },
    /**
     * 设置图片动画
     * {
     *    x:10,//图片在原有基础上向下移动10px
     *    y:10,//图片在原有基础上向右移动10px
     *    angle:10,//图片在原有基础上旋转10deg
     *    scale:0.5,//图片在原有基础上增加0.5倍
     * }
     */
    setTransform(transform) {
      if (!transform) return;
      if (!this.data.disable_rotate) {
        this.setData({
          angle: transform.angle ? this.data.angle + transform.angle : this.data.angle
        });
      }
      var scale = this.data.scale;
      if (transform.scale) {
        scale = this.data.scale + transform.scale;
        scale = scale <= this.data.min_scale ? this.data.min_scale : scale;
        scale = scale >= this.data.max_scale ? this.data.max_scale : scale;
      }
      this.data.scale = scale;
      let cutX = this.data.cut_left;
      let cutY = this.data.cut_top;
      if (transform.cutX) {
        this.setData({
          cut_left: cutX + transform.cutX
        });
        this.data.watch.cut_left(null, this);
      }
      if (transform.cutY) {
        this.setData({
          cut_top: cutY + transform.cutY
        });
        this.data.watch.cut_top(null, this);
      }
      this.data._img_top = transform.y ? this.data._img_top + transform.y : this.data._img_top;
      this.data._img_left = transform.x ? this.data._img_left + transform.x : this.data._img_left;
      //图像边缘检测,防止截取到空白
      this._imgMarginDetectionScale();
      //停止居中裁剪框,继续修改图片位置
      this._moveDuring();
      this.setData({
        scale: this.data.scale,
        _img_top: this.data._img_top,
        _img_left: this.data._img_left
      });
      !this.data._canvas_overflow && this._draw();
      //可以居中裁剪框了
      this._moveStop();//结束操作
    },
    /**
     * 设置剪裁框位置
     */
    setCutXY(x, y) {
      this.setData({
        cut_top: y,
        cut_left: x
      });
    },
    /**
     * 设置剪裁框尺寸
     */
    setCutSize(w, h) {
      this.setData({
        width: w,
        height: h
      });
      this._computeCutSize();
    },
    /**
     * 设置剪裁框和图片居中
     */
    setCutCenter() {
      let cut_top = (this.data.info.windowHeight - this.data.height) * 0.5;
      let cut_left = (this.data.info.windowWidth - this.data.width) * 0.5;
      //顺序不能变
      this.setData({
        _img_top: this.data._img_top - this.data.cut_top + cut_top,
        cut_top: cut_top, //截取的框上边距
        _img_left: this.data._img_left - this.data.cut_left + cut_left,
        cut_left: cut_left, //截取的框左边距
      });
    },
    _setCutCenter() {
      let cut_top = (this.data.info.windowHeight - this.data.height) * 0.5;
      let cut_left = (this.data.info.windowWidth - this.data.width) * 0.5;
      this.setData({
        cut_top: cut_top, //截取的框上边距
        cut_left: cut_left, //截取的框左边距
      });
    },
    /**
     * 设置剪裁框宽度-即将废弃
     */
    setWidth(width) {
      this.setData({
        width: width
      });
      this._computeCutSize();
    },
    /**
     * 设置剪裁框高度-即将废弃
     */
    setHeight(height) {
      this.setData({
        height: height
      });
      this._computeCutSize();
    },
    /**
     * 是否锁定旋转
     */
    setDisableRotate(value) {
      this.data.disable_rotate = value;
    },
    /**
     * 是否限制移动
     */
    setLimitMove(value) {
      this.setData({
        _cut_animation: true,
        limit_move: !!value
      });
    },
    /**
     * 初始化图片,包括位置、大小、旋转角度
     */
    imgReset() {
      this.setData({
        scale: 1,
        angle: 0,
        _img_top: wx.getSystemInfoSync().windowHeight / 2,
        _img_left: wx.getSystemInfoSync().windowWidth / 2,
      })
    },
    /**
     * 加载(更换)图片
     */
    pushImg(src) {
      if (src) {
        this.setData({
          imgSrc: src
        });
        //发现是手动赋值直接返回,交给watch处理
        return;
      }

      // getImageInfo接口传入 src: '' 会导致内存泄漏

      if (!this.data.imgSrc) return;
      wx.getImageInfo({
        src: this.data.imgSrc,
        success: (res) => {
          this.data.imageObject = res;
          //图片非本地路径需要换成本地路径
          if (this.data.imgSrc.search(/tmp/) == -1) {
            this.setData({
              imgSrc: res.path
            });
          }
          //计算最后图片尺寸
          this._imgComputeSize();
          if (this.data.limit_move) {
            //限制移动,不留空白处理
            this._imgMarginDetectionScale();
          }
          this._draw();
        },
        fail: (err) => {
          this.setData({
            imgSrc: ''
          });
        }
      });
    },
    imageLoad(e) {
      setTimeout(() => {
        this.triggerEvent('imageload', this.data.imageObject);

      }, 1000)
    },
    /**
     * 设置图片放大缩小
     */
    setScale(scale) {
      if (!scale) return;
      this.setData({
        scale: scale
      });
      !this.data._canvas_overflow && this._draw();
    },
    /**
     * 设置图片旋转角度
     */
    setAngle(angle) {
      if (!angle) return;
      this.setData({
        _cut_animation: true,
        angle: angle
      });
      this._imgMarginDetectionScale();
      !this.data._canvas_overflow && this._draw();
    },
    _initCanvas() {
      //初始化canvas
      if (!this.data.ctx) {
        this.data.ctx = wx.createCanvasContext("image-cropper", this);
      }
    },
    /**
     * 根据开发者设置的图片目标尺寸计算实际尺寸
     */
    _initImageSize() {
      //处理宽高特殊单位 %>px
      if (this.data.INIT_IMGWIDTH && typeof this.data.INIT_IMGWIDTH == "string" && this.data.INIT_IMGWIDTH.indexOf("%") != -1) {
        let width = this.data.INIT_IMGWIDTH.replace("%", "");
        this.data.INIT_IMGWIDTH = this.data.img_width = this.data.info.windowWidth / 100 * width;
      }
      if (this.data.INIT_IMGHEIGHT && typeof this.data.INIT_IMGHEIGHT == "string" && this.data.INIT_IMGHEIGHT.indexOf("%") != -1) {
        let height = this.data.img_height.replace("%", "");
        this.data.INIT_IMGHEIGHT = this.data.img_height = this.data.info.windowHeight / 100 * height;
      }
    },
    /**
     * 检测剪裁框位置是否在允许的范围内(屏幕内)
     */
    _cutDetectionPosition() {
      let _cutDetectionPositionTop = () => {
        //检测上边距是否在范围内
        if (this.data.cut_top < 0) {
          this.setData({
            cut_top: 0
          });
        }
        if (this.data.cut_top > this.data.info.windowHeight - this.data.height) {
          this.setData({
            cut_top: this.data.info.windowHeight - this.data.height
          });
        }
      }, _cutDetectionPositionLeft = () => {
        //检测左边距是否在范围内
        if (this.data.cut_left < 0) {
          this.setData({
            cut_left: 0
          });
        }
        if (this.data.cut_left > this.data.info.windowWidth - this.data.width) {
          this.setData({
            cut_left: this.data.info.windowWidth - this.data.width
          });
        }
      };
      //裁剪框坐标处理(如果只写一个参数则另一个默认为0,都不写默认居中)
      if (this.data.cut_top == null && this.data.cut_left == null) {
        this._setCutCenter();
      } else if (this.data.cut_top != null && this.data.cut_left != null) {
        _cutDetectionPositionTop();
        _cutDetectionPositionLeft();
      } else if (this.data.cut_top != null && this.data.cut_left == null) {
        _cutDetectionPositionTop();
        this.setData({
          cut_left: (this.data.info.windowWidth - this.data.width) / 2
        });
      } else if (this.data.cut_top == null && this.data.cut_left != null) {
        _cutDetectionPositionLeft();
        this.setData({
          cut_top: (this.data.info.windowHeight - this.data.height) / 2
        });
      }
    },
    /**
     * 检测canvas位置是否在允许的范围内(屏幕内)如果在屏幕外则不开启实时渲染
     * 如果只写一个参数则另一个默认为0,都不写默认超出屏幕外
     */
    _canvasDetectionPosition() {
      if (this.data.canvas_top == null && this.data.canvas_left == null) {
        this.data._canvas_overflow = false;
        this.setData({
          canvas_top: -5000,
          canvas_left: -5000
        });
      } else if (this.data.canvas_top != null && this.data.canvas_left != null) {
        if (this.data.canvas_top < - this.data.height || this.data.canvas_top > this.data.info.windowHeight) {
          this.data._canvas_overflow = true;
        } else {
          this.data._canvas_overflow = false;
        }
      } else if (this.data.canvas_top != null && this.data.canvas_left == null) {
        this.setData({
          canvas_left: 0
        });
      } else if (this.data.canvas_top == null && this.data.canvas_left != null) {
        this.setData({
          canvas_top: 0
        });
        if (this.data.canvas_left < -this.data.width || this.data.canvas_left > this.data.info.windowWidth) {
          this.data._canvas_overflow = true;
        } else {
          this.data._canvas_overflow = false;
        }
      }
    },
    /**
     * 图片边缘检测-位置
     */
    _imgMarginDetectionPosition(scale) {
      if (!this.data.limit_move) return;
      let left = this.data._img_left;
      let top = this.data._img_top;
      var scale = scale || this.data.scale;
      let img_width = this.data.img_width;
      let img_height = this.data.img_height;
      if (this.data.angle / 90 % 2) {
        img_width = this.data.img_height;
        img_height = this.data.img_width;
      }
      left = this.data.cut_left + img_width * scale / 2 >= left ? left : this.data.cut_left + img_width * scale / 2;
      left = this.data.cut_left + this.data.width - img_width * scale / 2 <= left ? left : this.data.cut_left + this.data.width - img_width * scale / 2;
      top = this.data.cut_top + img_height * scale / 2 >= top ? top : this.data.cut_top + img_height * scale / 2;
      top = this.data.cut_top + this.data.height - img_height * scale / 2 <= top ? top : this.data.cut_top + this.data.height - img_height * scale / 2;
      this.setData({
        _img_left: left,
        _img_top: top,
        scale: scale
      })
    },
    /**
     * 图片边缘检测-缩放
     */
    _imgMarginDetectionScale() {
      if (!this.data.limit_move) return;
      let scale = this.data.scale;
      let img_width = this.data.img_width;
      let img_height = this.data.img_height;
      if (this.data.angle / 90 % 2) {
        img_width = this.data.img_height;
        img_height = this.data.img_width;
      }
      if (img_width * scale < this.data.width) {
        scale = this.data.width / img_width;
      }
      if (img_height * scale < this.data.height) {
        scale = Math.max(scale, this.data.height / img_height);
      }
      this._imgMarginDetectionPosition(scale);
    },
    _setData(obj) {
      let data = {};
      for (var key in obj) {
        if (this.data[key] != obj[key]) {
          data[key] = obj[key];
        }
      }
      this.setData(data);
      return data;
    },
    /**
     * 计算图片尺寸
     */
    _imgComputeSize() {
      let img_width = this.data.img_width,
        img_height = this.data.img_height;
      if (!this.data.INIT_IMGHEIGHT && !this.data.INIT_IMGWIDTH) {
        //默认按图片最小边 = 对应裁剪框尺寸
        img_width = this.data.imageObject.width;
        img_height = this.data.imageObject.height;
        if (img_width / img_height > this.data.width / this.data.height) {
          img_height = this.data.height;
          img_width = this.data.imageObject.width / this.data.imageObject.height * img_height;
        } else {
          img_width = this.data.width;
          img_height = this.data.imageObject.height / this.data.imageObject.width * img_width;
        }
      } else if (this.data.INIT_IMGHEIGHT && !this.data.INIT_IMGWIDTH) {
        img_width = this.data.imageObject.width / this.data.imageObject.height * this.data.INIT_IMGHEIGHT;
      } else if (!this.data.INIT_IMGHEIGHT && this.data.INIT_IMGWIDTH) {
        img_height = this.data.imageObject.height / this.data.imageObject.width * this.data.INIT_IMGWIDTH;
      }
      this.setData({
        img_width: img_width,
        img_height: img_height
      });
    },
    //改变截取框大小
    _computeCutSize() {
      if (this.data.width > this.data.info.windowWidth) {
        this.setData({
          width: this.data.info.windowWidth,
        });
      } else if (this.data.width + this.data.cut_left > this.data.info.windowWidth) {
        this.setData({
          cut_left: this.data.info.windowWidth - this.data.cut_left,
        });
      };
      if (this.data.height > this.data.info.windowHeight) {
        this.setData({
          height: this.data.info.windowHeight,
        });
      } else if (this.data.height + this.data.cut_top > this.data.info.windowHeight) {
        this.setData({
          cut_top: this.data.info.windowHeight - this.data.cut_top,
        });
      }
      !this.data._canvas_overflow && this._draw();
    },
    //开始触摸
    _start(event) {
      this.data._flag_img_endtouch = false;
      if (event.touches.length == 1) {
        //单指拖动
        this.data._touch_img_relative[0] = {
          x: (event.touches[0].clientX - this.data._img_left),
          y: (event.touches[0].clientY - this.data._img_top)
        }
      } else {
        //双指放大
        let width = Math.abs(event.touches[0].clientX - event.touches[1].clientX);
        let height = Math.abs(event.touches[0].clientY - event.touches[1].clientY);
        this.data._touch_img_relative = [{
          x: (event.touches[0].clientX - this.data._img_left),
          y: (event.touches[0].clientY - this.data._img_top)
        }, {
          x: (event.touches[1].clientX - this.data._img_left),
          y: (event.touches[1].clientY - this.data._img_top)
        }];
        this.data._hypotenuse_length = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2));
      }
      !this.data._canvas_overflow && this._draw();
    },
    _move_throttle() {
      //安卓需要节流
      if (this.data.info.platform == 'android') {
        clearTimeout(this.data.MOVE_THROTTLE);
        this.data.MOVE_THROTTLE = setTimeout(() => {
          this.data.MOVE_THROTTLE_FLAG = true;
        }, 1000 / 40)
        return this.data.MOVE_THROTTLE_FLAG;
      } else {
        this.data.MOVE_THROTTLE_FLAG = true;
      }
    },
    _move(event) {
      if (this.data._flag_img_endtouch || !this.data.MOVE_THROTTLE_FLAG) return;
      this.data.MOVE_THROTTLE_FLAG = false;
      this._move_throttle();
      this._moveDuring();
      if (event.touches.length == 1) {
        //单指拖动
        let left = (event.touches[0].clientX - this.data._touch_img_relative[0].x),
          top = (event.touches[0].clientY - this.data._touch_img_relative[0].y);
        //图像边缘检测,防止截取到空白
        this.data._img_left = left;
        this.data._img_top = top;
        this._imgMarginDetectionPosition();
        this.setData({
          _img_left: this.data._img_left,
          _img_top: this.data._img_top
        });
      } else {
        //双指放大
        let width = (Math.abs(event.touches[0].clientX - event.touches[1].clientX)),
          height = (Math.abs(event.touches[0].clientY - event.touches[1].clientY)),
          hypotenuse = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2)),
          scale = this.data.scale * (hypotenuse / this.data._hypotenuse_length),
          current_deg = 0;
        scale = scale <= this.data.min_scale ? this.data.min_scale : scale;
        scale = scale >= this.data.max_scale ? this.data.max_scale : scale;
        //图像边缘检测,防止截取到空白
        this.data.scale = scale;
        this._imgMarginDetectionScale();
        //双指旋转(如果没禁用旋转)
        let _touch_img_relative = [{
          x: (event.touches[0].clientX - this.data._img_left),
          y: (event.touches[0].clientY - this.data._img_top)
        }, {
          x: (event.touches[1].clientX - this.data._img_left),
          y: (event.touches[1].clientY - this.data._img_top)
        }];
        if (!this.data.disable_rotate) {
          let first_atan = 180 / Math.PI * Math.atan2(_touch_img_relative[0].y, _touch_img_relative[0].x);
          let first_atan_old = 180 / Math.PI * Math.atan2(this.data._touch_img_relative[0].y, this.data._touch_img_relative[0].x);
          let second_atan = 180 / Math.PI * Math.atan2(_touch_img_relative[1].y, _touch_img_relative[1].x);
          let second_atan_old = 180 / Math.PI * Math.atan2(this.data._touch_img_relative[1].y, this.data._touch_img_relative[1].x);
          //当前旋转的角度
          let first_deg = first_atan - first_atan_old,
            second_deg = second_atan - second_atan_old;
          if (first_deg != 0) {
            current_deg = first_deg;
          } else if (second_deg != 0) {
            current_deg = second_deg;
          }
        }
        this.data._touch_img_relative = _touch_img_relative;
        this.data._hypotenuse_length = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2));
        //更新视图
        this.setData({
          angle: this.data.angle + current_deg,
          scale: this.data.scale
        });
      }
      !this.data._canvas_overflow && this._draw();
    },
    //结束操作
    _end(event) {
      this.data._flag_img_endtouch = true;
      this._moveStop();
    },
    //点击中间剪裁框处理
    _click(event) {
      if (!this.data.imgSrc) {
        //调起上传
        this.upload();
        return;
      }
      this._draw(() => {
        let x = event.detail ? event.detail.x : event.touches[0].clientX;
        let y = event.detail ? event.detail.y : event.touches[0].clientY;
        if ((x >= this.data.cut_left && x <= (this.data.cut_left + this.data.width)) && (y >= this.data.cut_top && y <= (this.data.cut_top + this.data.height))) {
          //生成图片并回调
          wx.canvasToTempFilePath({
            width: this.data.width * this.data.export_scale,
            height: Math.round(this.data.height * this.data.export_scale),
            destWidth: this.data.width * this.data.export_scale,
            destHeight: Math.round(this.data.height) * this.data.export_scale,
            fileType: 'png',
            quality: this.data.quality,
            canvasId: this.data.el,
            success: (res) => {
              this.triggerEvent('tapcut', {
                url: res.tempFilePath,
                width: this.data.width * this.data.export_scale,
                height: this.data.height * this.data.export_scale
              });
            }
          }, this)
        }
      });
    },
    //渲染
    _draw(callback) {
      if (!this.data.imgSrc) return;
      let draw = () => {
        //图片实际大小
        let img_width = this.data.img_width * this.data.scale * this.data.export_scale;
        let img_height = this.data.img_height * this.data.scale * this.data.export_scale;
        //canvas和图片的相对距离
        var xpos = this.data._img_left - this.data.cut_left;
        var ypos = this.data._img_top - this.data.cut_top;
        //旋转画布
        this.data.ctx.translate(xpos * this.data.export_scale, ypos * this.data.export_scale);
        this.data.ctx.rotate(this.data.angle * Math.PI / 180);
        this.data.ctx.drawImage(this.data.imgSrc, -img_width / 2, -img_height / 2, img_width, img_height);
        this.data.ctx.draw(false, () => {
          callback && callback();
        });
      }
      if (this.data.ctx.width != this.data.width || this.data.ctx.height != this.data.height) {
        //优化拖动裁剪框,所以必须把宽高设置放在离用户触发渲染最近的地方
        this.setData({
          _canvas_height: this.data.height,
          _canvas_width: this.data.width,
        }, () => {
          //延迟40毫秒防止点击过快出现拉伸或裁剪过多
          setTimeout(() => {
            draw();
          }, 40);
        });
      } else {
        draw();
      }
    },
    //裁剪框处理
    _cutTouchMove(e) {
      if (this.data._flag_cut_touch && this.data.MOVE_THROTTLE_FLAG) {
        if (this.data.disable_ratio && (this.data.disable_width || this.data.disable_height)) return;
        //节流
        this.data.MOVE_THROTTLE_FLAG = false;
        this._move_throttle();
        let width = this.data.width,
          height = this.data.height,
          cut_top = this.data.cut_top,
          cut_left = this.data.cut_left,
          size_correct = () => {
            width = width <= this.data.max_width ? width >= this.data.min_width ? width : this.data.min_width : this.data.max_width;
            height = height <= this.data.max_height ? height >= this.data.min_height ? height : this.data.min_height : this.data.max_height;
          },
          size_inspect = () => {
            if ((width > this.data.max_width || width < this.data.min_width || height > this.data.max_height || height < this.data.min_height) && this.data.disable_ratio) {
              size_correct();
              return false;
            } else {
              size_correct();
              return true;
            }
          };
        height = this.data.CUT_START.height + ((this.data.CUT_START.corner > 1 && this.data.CUT_START.corner < 4 ? 1 : -1) * (this.data.CUT_START.y - e.touches[0].clientY));
        switch (this.data.CUT_START.corner) {
          case 1:
            width = this.data.CUT_START.width + this.data.CUT_START.x - e.touches[0].clientX;
            if (this.data.disable_ratio) {
              height = width / (this.data.width / this.data.height)
            }
            if (!size_inspect()) return;
            cut_left = this.data.CUT_START.cut_left - (width - this.data.CUT_START.width);
            break
          case 2:
            width = this.data.CUT_START.width + this.data.CUT_START.x - e.touches[0].clientX;
            if (this.data.disable_ratio) {
              height = width / (this.data.width / this.data.height)
            }
            if (!size_inspect()) return;
            cut_top = this.data.CUT_START.cut_top - (height - this.data.CUT_START.height)
            cut_left = this.data.CUT_START.cut_left - (width - this.data.CUT_START.width)
            break
          case 3:
            width = this.data.CUT_START.width - this.data.CUT_START.x + e.touches[0].clientX;
            if (this.data.disable_ratio) {
              height = width / (this.data.width / this.data.height)
            }
            if (!size_inspect()) return;
            cut_top = this.data.CUT_START.cut_top - (height - this.data.CUT_START.height);
            break
          case 4:
            width = this.data.CUT_START.width - this.data.CUT_START.x + e.touches[0].clientX;
            if (this.data.disable_ratio) {
              height = width / (this.data.width / this.data.height)
            }
            if (!size_inspect()) return;
            break
        }
        if (!this.data.disable_width && !this.data.disable_height) {
          this.setData({
            width: width,
            cut_left: cut_left,
            height: height,
            cut_top: cut_top,
          })
        } else if (!this.data.disable_width) {
          this.setData({
            width: width,
            cut_left: cut_left
          })
        } else if (!this.data.disable_height) {
          this.setData({
            height: height,
            cut_top: cut_top
          })
        }
        this._imgMarginDetectionScale();
      }
    },
    _cutTouchStart(e) {
      let currentX = e.touches[0].clientX;
      let currentY = e.touches[0].clientY;
      let cutbox_top4 = this.data.cut_top + this.data.height - 30;
      let cutbox_bottom4 = this.data.cut_top + this.data.height + 20;
      let cutbox_left4 = this.data.cut_left + this.data.width - 30;
      let cutbox_right4 = this.data.cut_left + this.data.width + 30;

      let cutbox_top3 = this.data.cut_top - 30;
      let cutbox_bottom3 = this.data.cut_top + 30;
      let cutbox_left3 = this.data.cut_left + this.data.width - 30;
      let cutbox_right3 = this.data.cut_left + this.data.width + 30;

      let cutbox_top2 = this.data.cut_top - 30;
      let cutbox_bottom2 = this.data.cut_top + 30;
      let cutbox_left2 = this.data.cut_left - 30;
      let cutbox_right2 = this.data.cut_left + 30;

      let cutbox_top1 = this.data.cut_top + this.data.height - 30;
      let cutbox_bottom1 = this.data.cut_top + this.data.height + 30;
      let cutbox_left1 = this.data.cut_left - 30;
      let cutbox_right1 = this.data.cut_left + 30;
      if (currentX > cutbox_left4 && currentX < cutbox_right4 && currentY > cutbox_top4 && currentY < cutbox_bottom4) {
        this._moveDuring();
        this.data._flag_cut_touch = true;
        this.data._flag_img_endtouch = true;
        this.data.CUT_START = {
          width: this.data.width,
          height: this.data.height,
          x: currentX,
          y: currentY,
          corner: 4
        }
      } else if (currentX > cutbox_left3 && currentX < cutbox_right3 && currentY > cutbox_top3 && currentY < cutbox_bottom3) {
        this._moveDuring();
        this.data._flag_cut_touch = true;
        this.data._flag_img_endtouch = true;
        this.data.CUT_START = {
          width: this.data.width,
          height: this.data.height,
          x: currentX,
          y: currentY,
          cut_top: this.data.cut_top,
          cut_left: this.data.cut_left,
          corner: 3
        }
      } else if (currentX > cutbox_left2 && currentX < cutbox_right2 && currentY > cutbox_top2 && currentY < cutbox_bottom2) {
        this._moveDuring();
        this.data._flag_cut_touch = true;
        this.data._flag_img_endtouch = true;
        this.data.CUT_START = {
          width: this.data.width,
          height: this.data.height,
          cut_top: this.data.cut_top,
          cut_left: this.data.cut_left,
          x: currentX,
          y: currentY,
          corner: 2
        }
      } else if (currentX > cutbox_left1 && currentX < cutbox_right1 && currentY > cutbox_top1 && currentY < cutbox_bottom1) {
        this._moveDuring();
        this.data._flag_cut_touch = true;
        this.data._flag_img_endtouch = true;
        this.data.CUT_START = {
          width: this.data.width,
          height: this.data.height,
          cut_top: this.data.cut_top,
          cut_left: this.data.cut_left,
          x: currentX,
          y: currentY,
          corner: 1
        }
      }
    },
    _cutTouchEnd(e) {
      this._moveStop();
      this.data._flag_cut_touch = false;
    },
    //停止移动时需要做的操作
    _moveStop() {
      //清空之前的自动居中延迟函数并添加最新的
      clearTimeout(this.data.TIME_CUT_CENTER);
      this.data.TIME_CUT_CENTER = setTimeout(() => {
        //动画启动
        if (!this.data._cut_animation) {
          this.setData({
            _cut_animation: true
          });
        }
        this.setCutCenter();
      }, 1000)
      //清空之前的背景变化延迟函数并添加最新的
      clearTimeout(this.data.TIME_BG);
      this.data.TIME_BG = setTimeout(() => {
        if (this.data._flag_bright) {
          this.setData({
            _flag_bright: false
          });
        }
      }, 2000)
    },
    //移动中
    _moveDuring() {
      //清空之前的自动居中延迟函数
      clearTimeout(this.data.TIME_CUT_CENTER);
      //清空之前的背景变化延迟函数
      clearTimeout(this.data.TIME_BG);
      //高亮背景
      if (!this.data._flag_bright) {
        this.setData({
          _flag_bright: true
        });
      }
    },
    //监听器
    _watcher() {
      Object.keys(this.data).forEach(v => {
        this._observe(this.data, v, this.data.watch[v]);
      })
    },
    _observe(obj, key, watchFun) {
      var val = obj[key];
      Object.defineProperty(obj, key, {
        configurable: true,
        enumerable: true,
        set: (value) => {
          val = value;
          watchFun && watchFun(val, this);
        },
        get() {
          if (val && '_img_top|img_left||width|height|min_width|max_width|min_height|max_height|export_scale|cut_top|cut_left|canvas_top|canvas_left|img_width|img_height|scale|angle|min_scale|max_scale'.indexOf(key) != -1) {
            let ret = parseFloat(parseFloat(val).toFixed(3));
            if (typeof val == "string" && val.indexOf("%") != -1) {
              ret += '%';
            }
            return ret;
          }
          return val;
        }
      })
    },
    _preventTouchMove() {
    }
  }
})

个人头像界面
myhead.wxml

<view class='page'>
  <block wx:if="{{showPic==true}}">
    <image src="../../images/icon_more.png" class="btn-more" bindtap="goOperate" mode="aspectFit"></image>
    <image src="{{srcUrl}}" class="pic" mode="aspectFill" bindtap="previewImg"></image>
  </block>
  <block wx:else>
    <view>
      <image-cropper id="image-cropper" limit_move="{{true}}" disable_rotate="{{true}}" width="{{width}}" height="{{height}}" imgSrc="{{tempFilePaths}}" bindload="cropperload" bindimageload="loadimage" bindtapcut="clickcut"></image-cropper>
      <view class="btn-wrap">
        <view class="btn-cancel" bindtap="backUp">取消</view>
        <view class="btn-sure" bindtap="submit">确定</view>
      </view>
    </view>
  </block>
</view>

myhead.wxss

page {
  width: 100%;
  height: 100%;
}

.page {
  width: 100%;
  height: 100%;
  background: #000;
  display: flex;
  align-items: center;
  flex-direction: column;
  justify-content: center;
}

.btn-more {
  width: 56rpx;
  height: 16rpx;
  position: absolute;
  right: 27rpx;
  top: 27rpx;
}

.pic {
  width: 100%;
  height: 750rpx;
  background: #e3f5fb;
  border: 2rpx solid #31be7c;
  box-shadow: 0px 1rpx 4rpx 0px rgba(85, 85, 85, 0.2);
}

.btn-wrap {
  position: absolute;
  bottom: 60rpx;
  left: 30rpx;
  right: 30rpx;
  height: auto;
  overflow: hidden;
  z-index: 10;
}

.btn-wrap .btn-cancel {
  width: 160rpx;
  height: 68rpx;
  background: #fff;
  border: 1rpx solid #31be7c;
  box-shadow: 0px 1rpx 4rpx 0px rgba(85, 85, 85, 0.2);
  border-radius: 34rpx;
  text-align: center;
  font-size: 32rpx;
  font-family: Source Han Sans CN;
  font-weight: 500;
  color: #31be7c;
  float: left;
  line-height: 68rpx;
}

.btn-sure {
  width: 160rpx;
  height: 68rpx;
  background: #31be7c;
  box-shadow: 0px 1rpx 4rpx 0px rgba(85, 85, 85, 0.2);
  border-radius: 34rpx;
  float: right;
  font-size: 34rpx;
  font-family: Source Han Sans CN;
  font-weight: 500;
  color: #fff;
  text-align: center;
  line-height: 68rpx;
}

myhead.json

{
  "navigationBarTitleText": "个人头像",
  "disableScroll": true,
  "usingComponents": {
    "image-cropper": "../../dist/image-cropper/image-cropper"
  }
}

myhead.js

const {
  ygPost,
  ygSwitch
} = require("../../utils/util.js");
Page({

  /**
   * 页面的初始数据
   */
  data: {
    src: "",
    srcUrl: "",
    showPic: true,
    width: 300, //宽度
    height: 300, //高度
    tempFilePaths: "",
  },

  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function (options) {
    if (options.url == "null" || options.url == "") {
      this.setData({
        srcUrl:"/images/icon_dhead.png"
      })
    }else{
      this.setData({
        srcUrl: ygSwitch.getImgUrl(options.url)
      })
    }
  },
  goOperate: function (e) {
    let that = this
    wx.showActionSheet({
      itemList: ['拍照', '从手机相册选择', '保存照片'],
      success: function (res) {
        if (res.tapIndex == 2) {
          wx.getImageInfo({
            src: that.data.srcUrl,
            success: function (res) {
              var path = res.path;
              wx.saveImageToPhotosAlbum({
                filePath: path,
                success(res) {
                  wx.showToast({
                    title: '保存成功',
                    icon: "none"
                  })
                  console.log(res);
                },
                fail(res) {
                  wx.showToast({
                    title: '保存失败',
                    icon: "none"
                  })
                  console.log(res);
                }
              })
            }
          })
        } else {
          let sourceType = []
          if (res.tapIndex == 0) {
            sourceType.push("camera");
          } else {
            sourceType.push("album");
          }
          wx.chooseImage({
            count: 1, // 默认9
            sizeType: ['original', 'compressed'], // 可以指定是原图还是压缩图,默认二者都有
            sourceType: sourceType, // 可以指定来源是相册还是相机,默认二者都有
            success: function (res) {
              const tempFilePaths = res.tempFilePaths[0];
              that.setData({
                tempFilePaths: tempFilePaths,
                showPic: false,
              })
              //获取到image-cropper实例
              that.cropper = that.selectComponent("#image-cropper");
              wx.showLoading({
                title: '加载中'
              })
            }
          })
        }
      },
      fail: function (res) {}
    })
  },
  loadimage(e) {
    console.log("图片加载完成", e.detail);
    wx.hideLoading();
    //重置图片角度、缩放、位置
    this.cropper.imgReset();
  },
  clickcut(e) {
    console.log(e.detail);
    //点击裁剪框阅览图片
    wx.previewImage({
      current: e.detail.srcUrl, // 当前显示图片的http链接
      urls: [e.detail.url] // 需要预览的图片http链接列表
    })
  },

  backUp() {
    wx.navigateBack({
      delta: 1,
    })
  },

  submit() {
    let that = this;
    this.cropper.getImg((obj) => {
      let url = obj.url;
      ygPost.uploadFile([url], function (ret) {
        let pic = ygSwitch.getImgUrl(ret);
        that.setData({
          src: ret,
          srcUrl: pic,
          showPic: true,
        });
        that.uploadHeadPic();
      });
    });
  },
  uploadHeadPic: function () {
    let that = this;
    ygPost.postGetSession("/User/UploadHeadPic", {
      pic: that.data.src,
    }, function (set) {
      if (set.data.isSuccess) {
        wx.showToast({
          title: '修改成功',
          icon: "success",
        })
      } else {
        wx.showModal({
          content: set.data.message,
          title: "提示",
          showCancel: false,
        })
      }
    });
  },
  previewImg: function (e) {
    let that = this;
    wx.previewImage({
      current: 0, //当前图片地址
      urls: [that.data.srcUrl], //所有要预览的图片的地址集合 数组形式
      success: function (res) {},
      fail: function (res) {},
      complete: function (res) {},
    })
  },
  /**
   * 生命周期函数--监听页面初次渲染完成
   */
  onReady: function () {

  },

  /**
   * 生命周期函数--监听页面显示
   */
  onShow: function (options) {},

  /**
   * 生命周期函数--监听页面隐藏
   */
  onHide: function () {

  },

  /**
   * 生命周期函数--监听页面卸载
   */
  onUnload: function () {
    let pages = getCurrentPages();
    let prevPage = pages[pages.length - 2]
    prevPage.getUserInfo();
    prevPage.setData({
      headPic: this.data.srcUrl
    })
  },

  /**
   * 页面相关事件处理函数--监听用户下拉动作
   */
  onPullDownRefresh: function () {

  },

  /**
   * 页面上拉触底事件的处理函数
   */
  onReachBottom: function () {

  },

  /**
   * 用户点击右上角分享
   */
  onShareAppMessage: function () {

  }
})
  • 2
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
要使用vant-uploader和vue-cropper来实现裁剪图片上传,你可以按照以下步骤进行操作: 1. 首先,安装vant和vue-cropper插件。你可以使用npm或yarn来安装它们。 2. 在你的Vue组件中,引入vant-uploader和vue-cropper组件。 3. 在模板中,使用vant-uploader组件来实现图片上传功能。设置上传的action属性为你的上传接口地址,并设置on-success事件来处理上传成功后的逻辑。 4. 在on-success事件中,获取到上传成功后的图片地址,并将其传递给vue-cropper组件。 5. 在vue-cropper组件中,设置裁剪框的样式和裁剪比例等属性。使用v-model指令来绑定裁剪后的图片数据。 6. 在提交按钮的点击事件中,将裁剪后的图片数据上传到服务器。 下面是一个简单的示例代码: ```vue <template> <div> <van-uploader action="/upload" :on-success="handleUploadSuccess" ></van-uploader> <vue-cropper v-if="showCropper" :src="cropperSrc" :output-size="{ width: 200, height: 200 }" :output-type="'jpeg'" :fixed-box="true" :fixed-number="\[1, 1\]" v-model="croppedImage" ></vue-cropper> <button @click="handleSubmit">提交</button> </div> </template> <script> import { VanUploader } from 'vant'; import VueCropper from 'vue-cropper'; export default { components: { VanUploader, VueCropper, }, data() { return { showCropper: false, cropperSrc: '', croppedImage: '', }; }, methods: { handleUploadSuccess(response) { // 获取上传成功后的图片地址 const imageUrl = response.data.imageUrl; // 显示裁剪组件 this.showCropper = true; // 设置裁剪组件的图片地址 this.cropperSrc = imageUrl; }, handleSubmit() { // 提交裁剪后的图片数据到服务器 // this.croppedImage 包含裁剪后的图片数据 }, }, }; </script> ``` 请注意,以上代码只是一个简单的示例,你需要根据你的实际需求进行适当的修改和调整。同时,你还需要在后端实现相应的上传裁剪功能

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值