封装vue-cropper,图片裁剪组件

组件基本使用:

在这里插入图片描述

这里的action同时也可以传相对路径,比如封装了axios,那么组件源码里就不需要引入原生axios,可以替换为封装的axios。传 action="/upload/file"

源代码:

<script setup>
import WuyuCropper from '@/components/wuyu-cropper/index.vue'

function result(data) {
  console.log(data)
}

// 默认头像
const defaultAvatar = 'https://img.yzcdn.cn/vant/cat.jpeg'
</script>

<template>
  <wuyu-cropper @result="result"
                :src="defaultAvatar"
                action="http://localhost:9090/upload/file">
  </wuyu-cropper>
</template>

<style lang="scss" scoped>
</style>

组件属性

名称类型默认值是否必需说明
maxSizeNumber5单位:MB,图片大小限制
fixedNumberArray[1,1]裁剪比例
srcString“”初始化图片回显
actionString#上传接口
headersObject{}请求头

组件事件

名称参数列表说明
result(url)裁剪结束提交后触发该事件,并传递裁剪后的图片url

组件插槽

名称参数列表说明
default自定义按钮,打开裁剪框

组件源码

环境: vue3+element plus+axios+vue-cropper,
可选:vueuse的防抖动函数 useDebounceFn,如果不需要可以去掉

pnpm add vue-cropper@1.1.4 
pnpm add @vueuse/core@11.1.0
<!--可能需要修改 submit函数,根据后端返回值做处理 -->
<script setup>
import {ref} from "vue";
import 'vue-cropper/dist/index.css'
import {VueCropper} from "vue-cropper";
import {useDebounceFn} from "@vueuse/core";
import {ElMessage} from "element-plus";
import axios from "axios";

const emit = defineEmits(['result'])
const prop = defineProps({
  // 单位: MB
  maxSize: {
    type: Number,
    default: 5
  },
  fixedNumber: {
    type: Array,
    default: () => [1, 1]
  },
  src: {
    type: String,
    default: ''
  },
  action: {
    type: String,
    default: '#'
  },
  headers: {
    type: Object,
    default: () => {
    }
  }
})
const cropperRef = ref()
const uploadRef = ref()
const visible = ref(false)
const options = ref({
  img: prop.src, // 图片url
  fixed: true,    // 固定裁剪框大小
  fixedNumber: prop.fixedNumber, // 裁剪框比例
  autoCrop: true,
  filename: getFileName(prop.src)
})
const preview = ref({})
const realTime = useDebounceFn((e) => {
  preview.value = e
}, 500)

// 获取文件名
function getFileName(url) {
  return url.substring(url.lastIndexOf('/') + 1)
}

function open() {
  visible.value = true
  options.value.img = prop.src
  options.value.fixedNumber = prop.fixedNumber
  options.value.filename = getFileName(prop.src)
}

function close() {
  preview.value = {}
  uploadRef.value.clearFiles()
  visible.value = false
}

// 选择文件之前
function beforeUpload(file) {
  if (file.size / 1024 / 1024 > prop.maxSize) {
    ElMessage.error(`文件的大小不能超过:${prop.maxSize}MB`)
    return false
  }
}

// 选择文件之后
function httpRequest({file, filename}) {
  options.value.filename = file.name
  options.value.img = URL.createObjectURL(file)
}

// 提交裁剪文件
function submit() {
  cropperRef.value.getCropBlob(async (data) => {
    const formData = new FormData()
    formData.append('file', data, options.value.filename)
    // 上传文件
    const res = await axios.post(prop.action, formData, {headers: {...prop.headers}})
    // 提交成功,触发result事件,传递url
    emit('result', res.data.data)
    visible.value = false
  })
}

function rotateRight() {
  cropperRef.value.rotateRight()
}

function rotateLeft() {
  cropperRef.value.rotateLeft()
}

function zoomIn() {
  cropperRef.value.changeScale(1)
}

function zoomOut() {
  cropperRef.value.changeScale(-1)
}
</script>

<template>
  <div @click="open">
    <slot>
      <el-button>上传</el-button>
    </slot>
  </div>
  <el-drawer :model-value="visible"
             direction="ttb"
             append-to-body
             size="100%"
             @close="close">
    <template #default>
      <el-row style="height: 80%">
        <el-col :sm="12" class="cropper-box">
          <VueCropper
              ref="cropperRef"
              v-if="visible"
              :img="options.img"
              :fixed="options.fixed"
              :fixed-number="options.fixedNumber"
              :autoCrop="options.autoCrop"
              @realTime="realTime"
          ></VueCropper>
        </el-col>
        <el-col :sm="12" class="preview">
          <div :style="preview.div" class="preview-box">
            <img v-if="preview.url" class="img" :src="preview.url" :style="preview.img"/>
          </div>
        </el-col>
      </el-row>
      <el-row :gutter="10" style="margin-top: 1rem">
        <el-col :sm="1">
          <el-upload action="#"
                     :limit="1"
                     ref="uploadRef"
                     :http-request="httpRequest"
                     :before-upload="beforeUpload"
                     accept=".jpg,.jpeg,.png"
                     :show-file-list="false">
            <template #default>
              <el-button circle type="primary" icon="UploadFilled" title="上传"/>
            </template>
          </el-upload>
        </el-col>
        <el-col :sm="1">
          <el-button circle :disabled="!preview.url" @click="rotateRight" icon="RefreshRight" title="右转"/>
        </el-col>
        <el-col :sm="1">
          <el-button circle :disabled="!preview.url" @click="rotateLeft" icon="RefreshLeft" title="左转"/>
        </el-col>
        <el-col :sm="1">
          <el-button circle :disabled="!preview.url" @click="zoomIn" icon="ZoomIn" title="放大"/>
        </el-col>
        <el-col :sm="3">
          <el-button circle :disabled="!preview.url" @click="zoomOut" icon="ZoomOut" title="缩小"/>
        </el-col>
        <el-col :sm="2">
          <el-button type="success" @click="submit" :disabled="!preview.url">提交</el-button>
        </el-col>
      </el-row>
    </template>
  </el-drawer>
</template>

<style lang="scss" scoped>
.cropper-box {
  height: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
}

.preview {
  height: 100%;
  display: flex;
  justify-content: center;
  align-items: center;

  .preview-box {
    height: 100%;
    overflow: hidden;

    .img {
      height: 100%;
      width: 100%;
    }
  }
}
</style>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值