vue项目实战 vueCropper 仿微信头像任意区域截取图片,上传到腾讯云保存

news2025/1/10 22:48:05

在package.json中添加

"vue-cropperjs": "4",

后在控制台执行:npm install

ImageCropper.vue

<template>
    <div v-if="src">
      <!-- Vue Cropper区域 -->
      <el-row class="cropper-wrapper" v-if="src">
        <el-col :span="24">
          <vue-cropper 
            v-if="src"
            :key="cropperKey"
            ref="cropper" 
            :src="src"
            :style="{ width: width + 'px', height: height + 'px' }"
            @ready="onReady"
            :dragMode="'move'"
            :viewMode="1"
            :cropBoxResizable="false"
          />
        </el-col>
      </el-row>
  
      <el-row class="button-row" v-if="src">
        <el-col :span="24">
          <el-button class="button-soft-orange" @click="cropImage" icon="el-icon-scissors">裁剪图像</el-button>
          <el-button class="button-soft-blue" @click="downloadImage" icon="el-icon-download">下载图像</el-button>
          <el-button class="button-soft-red" @click.prevent="zoom(0.2)" icon="el-icon-zoom-in" />
          <el-button class="button-soft-red" @click.prevent="zoom(-0.2)" icon="el-icon-zoom-out" />
          <el-button class="button-soft-yellow" @click.prevent="move(-10, 0)" icon="el-icon-arrow-left" />
          <el-button class="button-soft-yellow" @click.prevent="move(10, 0)" icon="el-icon-arrow-right" />
          <el-button class="button-soft-yellow" @click.prevent="move(0, -10)" icon="el-icon-arrow-up" />
          <el-button class="button-soft-yellow" @click.prevent="move(0, 10)" icon="el-icon-arrow-down" />
          <el-button class="button-soft-blue" ref="flipX" @click.prevent="flipX" icon="el-icon-sort" />
          <el-button class="button-soft-blue" ref="flipY" @click.prevent="flipY" icon="el-icon-sort" />
          <el-button class="button-soft-green" @click.prevent="rotate(90)" icon="el-icon-refresh-right" />
          <el-button class="button-soft-green" @click.prevent="rotate(-90)" icon="el-icon-refresh-left" />
          <el-button class="button-soft-gray" @click.prevent="reset" icon="el-icon-refresh" />
        </el-col>
      </el-row>
  
      <el-row v-if="croppedImageUrl">
        <el-col :style="{ width: width + 'px' }" >
          <el-card>
            <h3>裁剪后预览:</h3>
            <div class="preview-and-buttons">
              <div class="image-preview-wrapper">
                <img class="cropped-image-preview" :src="croppedImageUrl" alt="Cropped Image" />
              </div>
              <div class="upload-button-col">
                <el-button class="button-soft-green" @click="uploadImageToCos" icon="el-icon-upload">
                  上传到腾讯云
                </el-button>
                <el-button class="button-soft-gray" @click="resetImage">关闭裁剪区和预览区</el-button>
              </div>
            </div>
          </el-card>
        </el-col>
      </el-row>
    </div>
  </template>
  
  <script>
  import VueCropper from "vue-cropperjs"
  import "cropperjs/dist/cropper.css"
  import { Button, Row, Col, Card } from "element-ui"
  import COS from 'cos-js-sdk-v5'
  
  export default {
    components: {
      VueCropper,
      'el-button': Button,
      'el-row': Row,
      'el-col': Col,
      'el-card': Card
    },
    props: {
        width: {
            type: Number,
            default: 600
        },
        height: {
            type: Number,
            default: 600
        },
        centerWidth: {
          type: Number
        }
    },
    data() {
      return {
        src: '', // 初始 src 为空,等待用户选择本地图片
        cropperKey: 0, // 用于强制重新渲染 cropper
        croppedImageUrl: '',
        uploadedImageUrl: '', // 用于存储上传后返回的 URL
        flipXValue: 1,
        flipYValue: 1,
        cos: null // 用于存储腾讯云COS实例
      }
    },
    mounted() {
      this.cos = new COS({
        SecretId: '', // 写自己的腾讯云 SecretId
        SecretKey: '' // 写自己的腾讯云 SecretKey
      });
    },
    methods: {
      onReady() {
        // 重置画布和裁剪框,确保初始图片位置和大小如重置后的效果
        this.reset();
      },
      onImageChange(event) {
        const file = event.target.files[0];
        if (file) {
          this.src = URL.createObjectURL(file);
          this.cropperKey++; // 更新 key 强制重新渲染 vue-cropper
          this.$emit('imageChanged'); // 通知父组件图片已更换
        }
      },
      cropImage() {
        this.$refs.cropper.cropper.getCroppedCanvas().toBlob((blob) => {
          this.croppedImageUrl = URL.createObjectURL(blob);
        });
      },
      downloadImage() {
        this.$refs.cropper.cropper.getCroppedCanvas().toBlob((blob) => {
          const link = document.createElement('a');
          link.href = URL.createObjectURL(blob);
          link.download = 'cropped_image.png';
          link.click();
        });
      },
      zoom(scale) {
        this.$refs.cropper.cropper.zoom(scale);
      },
      move(offsetX, offsetY) {
        this.$refs.cropper.cropper.move(offsetX, offsetY);
      },
      flipX() {
        this.flipXValue = this.flipXValue === 1 ? -1 : 1;
        this.$refs.cropper.cropper.scaleX(this.flipXValue);
      },
      flipY() {
        this.flipYValue = this.flipYValue === 1 ? -1 : 1;
        this.$refs.cropper.cropper.scaleY(this.flipYValue);
      },
      rotate(degrees) {
        this.$refs.cropper.cropper.rotate(degrees);
      },
      reset() {
        // 先重置裁剪框和画布确保其他变换恢复到初始状态
        this.$refs.cropper.cropper.reset();
        
        // 获取图片原始尺寸
        const imageData = this.$refs.cropper.cropper.getImageData();
        
        // 计算新的高度
        const newHeight = (this.centerWidth / imageData.naturalWidth) * imageData.naturalHeight;
  
        // 设置画布大小并居中
        this.$refs.cropper.cropper.setCanvasData({
          width: this.centerWidth,
          height: newHeight,
          left: (this.width - this.centerWidth) / 2,  // 居中画布
          top: (this.height - newHeight) / 2  // 居中画布
        });
  
        // 设置裁剪框的大小和居中位置
        this.$refs.cropper.cropper.setCropBoxData({
          left: (this.width - 300) / 2,  // 居中裁剪框
          top: (this.height - 200) / 2,   // 居中裁剪框
          width: 300,
          height: 200
        });
  
        this.flipXValue = 1;
        this.flipYValue = 1;
      },
      resetImage() {
        this.src = ''; // 重置 src,使用户可以重新选择图片
        this.cropperKey++; // 更新 key 强制重新渲染 vue-cropper
        this.$emit('imageChanged'); // 通知父组件图片已重置
        this.croppedImageUrl = ''
      },
      uploadImageToCos() {
        this.uploadedImageUrl = '';
        this.$emit('uploadStarted'); // 通知父组件上传开始,清空输入框
  
        this.$refs.cropper.cropper.getCroppedCanvas().toBlob((blob) => {
          const timestamp = new Date().getTime(); // 使用时间戳生成唯一文件名
          const fileName = `cropped_image_${timestamp}.png`; // 生成唯一文件名
          const file = new File([blob], fileName, { type: "image/png" });
  
          this.cos.uploadFile({
            Bucket: '', // 写自己的腾讯云 Bucket
            Region: '', // 写自己的腾讯云 Region 
            Key: fileName, // 使用唯一文件名
            Body: file,
            SliceSize: 1024 * 1024, // 分块大小,单位为字节,这里设置为1MB
          }, (err, data) => {
            if (err) {
              this.$notify.error({
                title: '上传失败',
                message: '图片上传到腾讯云失败,请重试。',
                duration: 1000
              });
              this.$emit('uploadFailed', err); // 通知父组件上传失败
            } else {
              this.uploadedImageUrl = data.Location;
              this.$notify.success({
                title: '上传成功',
                message: '图片成功上传到腾讯云。',
                duration: 1000
              });
              this.$emit('uploaded', this.uploadedImageUrl); // 通知父组件上传成功,并发送 URL
            }
          });
        });
      },
      openFileInput() {
        this.$refs.fileInput.click(); // 模拟点击文件输入控件
      }
    }
  }
  </script>
  
  <style scoped>
  .image-select-wrapper {
    margin-bottom: 20px;
  }
  
  .button-soft-green {
    background-color: #a5d6a7 !important;
    border-color: #a5d6a7 !important;
    color: #fff !important;
  }
  
  .button-soft-orange {
    background-color: #ffcc80 !important;
    border-color: #ffcc80 !important;
    color: #fff !important;
  }
  
  .button-soft-yellow {
    background-color: #f3c672b4 !important;
    border-color: #f3c672b4 !important;
    color: #fff !important;
  }
  
  .button-soft-blue {
    background-color: #bbd0f8 !important;
    border-color: #bbd0f8 !important;
    color: #fff !important;
  }
  
  .button-soft-gray {
    background-color: #cfd8dc !important;
    border-color: #cfd8dc !important;
    color: #fff !important;
  }
  
  .button-soft-red {
    background-color: #ff8a80 !important;
    border-color: #ff8a80 !important;
    color: #fff !important;
  }
  
  .cropper-wrapper {
    margin-bottom: 20px;
  }
  
  .button-row {
    margin-bottom: 20px;
  }
  
  .cropped-image-preview {
    width: 100%;
    height: 100%;
    object-fit: cover;
  }
  
  .upload-button-col {
    display: flex;
    gap: 10px; /* 水平按钮间距 */
    align-items: center;
  }
  
  .preview-and-buttons {
    display: flex;
    align-items: center;
    gap: 20px; /* 图片与按钮组之间的间距 */
  }
  
  .image-preview-wrapper {
    width: 300px!important;
    /* height: 200px!important; */
    border: 1px solid #dcdcdc;
    display: flex;
    align-items: center;
    justify-content: center;
  }
  
  .image-preview-wrapper img {
    max-width: 100%;
    /* max-height: 100%; */
    object-fit: cover;
  }
  </style>

 TestView.vue 

<template>
  <div :style="{ width: parentWidth + 'px' }">
    <div class="input-wrapper" :style="{ width: inputWrapperWidth + 'px' }">
      <el-button @click="openFileInput" style="margin-right: 10px;">选择图片</el-button>
      <el-input :value="uploadedImageUrl" placeholder="上传后的URL" readonly/>
      <input type="file" ref="fileInput" @change="onFileChange" accept="image/*" style="display: none" />
    </div>
    <ImageCropper 
      ref="imageCropper"
      @uploaded="handleUploaded" 
      @uploadFailed="handleUploadFailed" 
      @imageChanged="clearUploadedImageUrl"
      @uploadStarted="clearUploadedImageUrl"
      @getLocalImageUrl="handleGetLocalImageUrl"
      :width="cropperWidth"
      :height="cropperHeight"
      :center-width="setCenterWidth"
      style="margin-top: 10px;"
    />
  </div>
</template>

<script>
import ImageCropper from '@/components7/ImageCropper.vue'

export default {
  components: {
    ImageCropper
  },
  data() {
    return {
      uploadedImageUrl: '', // 用于存储上传成功后的 URL
      parentWidth: 1000, // 父组件宽度
      inputWrapperWidth: 1000, // input-wrapper宽度
      cropperWidth: 1000, // ImageCropper宽度
      cropperHeight: 1000, // ImageCropper高度
      setCenterWidth:900,
      localImageUrl: '' // 本地图片
    }
  },
  methods: {
    handleUploaded(url) {
      this.uploadedImageUrl = url;
    },
    handleUploadFailed(err) {
      console.error('上传失败:', err);
    },
    clearUploadedImageUrl() {
      this.uploadedImageUrl = '';
    },
    openFileInput() {
      this.$refs.fileInput.click();
    },
    onFileChange(event) {
      const file = event.target.files[0];
      if (file) {
        this.$refs.imageCropper.onImageChange(event);
      }
    },
    handleGetLocalImageUrl(url) {
      this.localImageUrl = url;
      console.log("this.localImageUrl:",this.localImageUrl)
    }
  }
}
</script>

<style scoped>
.input-wrapper {
  display: flex;
  align-items: center;
}
.input-wrapper .el-input {
  flex: 1;
}
</style>

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1964701.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

stm32_按键消抖_代码与流程分析

stm32按键消抖 一 问题背景二 解决思路三 代码实现 一 问题背景 开关按钮&#xff1a;按下信号为0&#xff0c;正常信号为1 在按下时&#xff0c;表面上看我们只按了一下&#xff0c;但信号的传递并不是单纯的由1直接变为0。这是由于机械触点断开/闭合时会有抖动&#xff0c;…

北京大学:利用好不确定性,8B小模型也能超越GPT-4

大模型有一个显著的特点&#xff0c;那就是不确定性——对于特定输入&#xff0c;相同的LLM在不同解码配置下可能生成显著不同的输出。 比如问一问chatgpt“今天开心吗&#xff1f;”&#xff0c;可以得到两种不同的回答。 常用的解码策略有两种&#xff0c;一个是贪婪解码&am…

好展位,抢先订!2025浙江(玉环)机械展

2025第18届浙江&#xff08;玉环&#xff09;机械工业展览会 时间地点&#xff1a;2025年4月25-28日 玉环会展中心 近年来&#xff0c;随着玉环工业经济的蓬勃发展&#xff0c;汽摩配件、阀门水暖五金产业、铜加工、眼镜配件、金属加工生产等行业&#xff0c;如同贪婪的巨人&…

React Native初次使用遇到的问题

Write By Monkeyfly 以下内容均为原创&#xff0c;如需转载请注明出处。 前提&#xff1a;距离上次写博文已经过去了5年之久&#xff0c;诸多原因导致的&#xff0c;写一篇优质博文确实费时费力&#xff0c;中间有其他更感兴趣的事要做&#xff08;打游戏、旅游、逛街、看电影…

4.2、存储管理-页式存储

页式存储和段氏存储会考 页式存储几乎必考&#xff0c;段氏存储可能会考 页式存储 页式存储是操作系统的一种存储管理方式。 因为我们的程序往往是远远大于内存的&#xff0c;所以程序在执行的时候&#xff0c;是不会一次性把所有内容都装入到内存中&#xff0c;它会把程序分…

实验3-3 比较大小 想知道①和②的区别是什么(都错)

//实验3-3 比较大小 代码① #include<stdio.h> #include<math.h>void swap(int a, int b);// 正确声明 swap 函数int main() {int a, b, c;scanf("%d %d %d",&a,&b,&c);// 比较并交换 a 和 bif (a > b) {swap(a, b);}// 比较并交换 b 和 …

力扣刷题之3111.覆盖所有点的最少矩形数目

题干描述 给你一个二维整数数组 point &#xff0c;其中 points[i] [xi, yi] 表示二维平面内的一个点。同时给你一个整数 w 。你需要用矩形 覆盖所有 点。 每个矩形的左下角在某个点 (x1, 0) 处&#xff0c;且右上角在某个点 (x2, y2) 处&#xff0c;其中 x1 < x2 且 y2 …

关于vue开发中,高德地图有时不显示的问题

场景1&#xff1a;在弹出框中初始化地图 先设置 this.dialogVisible true; 然后再用nextTick初始化地图 this.$nextTick(() > { this.initAmap(); }); 场景2&#xff1a;点击tab页路由回退&#xff0c;导致高德地图不初始化的问题 解决方法&#xff1a; 通过$refs的方…

焦化行业超低排放改造巩固提升方案(朗观视觉)

朗观视觉小编观察发现&#xff1a;随着全球对环境保护意识的日益增强&#xff0c;焦化行业作为高污染、高排放的工业领域&#xff0c;其超低排放改造已成为行业转型升级的必然趋势。为了积极响应国家关于推进生态文明建设、打赢蓝天保卫战的号召&#xff0c;山东省生态环境厅发…

Windows + Ubuntu双系统!小白轻松安装

前言 这几天有小伙伴想着装WindowsUbuntu双系统&#xff0c;但苦于找不到办法&#xff0c;就在某篇文章后台留言&#xff1a; 这不&#xff0c;今天就更新了嘛&#xff01;虽然做不到有求必应&#xff0c;但教程帖还是可以写写的&#xff0c;能帮一个是一个&#xff01; 今天要…

深入探讨RCE漏洞及其防御策略

1. RCE漏洞 1.1. 漏洞原理 远程代码执行&#xff08;RCE&#xff09;漏洞允许攻击者远程注入并执行操作系统命令或代码&#xff0c;从而控制后台系统。 1.2. 漏洞产生条件 调用第三方组件存在代码执行漏洞。用户输入内容作为系统命令参数拼接到命令中。对用户输入的过滤不严…

关于Linux服务器端更新命令apt update没有效果问题总结(校园网认证)

这里写目录标题 项目场景&#xff1a;问题描述错误解决过程个人解决apt update和apt upgrade 项目场景&#xff1a; 在使用Django进行服务器部署的时候&#xff0c;需要使用执行命令 apt update&#xff1a; 具体场景&#xff1a; 问题描述 需要使用apt命令进行升级、下载…

[HITCON 2017]SSRFme 1

目录 代码审计 符号shell_exec() 函数:GET " . escapeshellarg($_GET["url"])&#xff1a;pathinfo($_GET["filename"]basename() 题目解析 代码审计 118.182.186.90 <?phpif (isset($_SERVER[HTTP_X_FORWARDED_FOR])) {$http_x_headers explod…

二叉树,二叉查找树,平衡二叉树

一.绪论: 二.数据结构(二叉树): 1.简介: 1)每一个节点&#xff08;也叫结点&#xff09;都是一个独立的对象-->当中不仅要存数据值&#xff0c;还要存父节点地址值&#xff0c;左子节点地址值&#xff0c;右子 节点地址值 2)没有父节点或者子节点的节点就记为null 2.遍历方…

【Bug记录】模板生成错误:不属于vector的成员

项目场景&#xff1a; 在调用自己写的一个简化版优先级队列时候报语法错误&#xff1a;模板生成错误 问题描述 模板生成错误&#xff0c;如下图 原因分析&#xff1a; 问题的分析&#xff1a;刚开始怀疑写模板写错了&#xff0c;之后看错误列表发现是top函数不属于vector。…

<数据集>棉花识别数据集<目标检测>

数据集格式&#xff1a;VOCYOLO格式 图片数量&#xff1a;13765张 标注数量(xml文件个数)&#xff1a;13765 标注数量(txt文件个数)&#xff1a;13765 标注类别数&#xff1a;4 标注类别名称&#xff1a;[Partially opened, Fully opened boll, Defected boll, Flower] 序…

llama-factory 系列教程 (六),linux shell 脚本自动实现批量大模型的训练、部署与评估

背景 最近在做大模型微调训练的评估&#xff0c;每次都要手动训练大模型&#xff0c;手动评估。 发现这样太浪费时间了&#xff0c;于是就尝试着使用linux shell 脚本&#xff0c;利用 for 循环自动实现大模型的训练、部署与评估。 实验&#xff1a;在不同的文本分类数据集尺…

记录两道关于编码解码的问题

环境&#xff1a;php环境即可&#xff0c;也可使用phpstudy。 参考文章: 深入理解浏览器解析机制和XSS向量编码-CSDN博客(很重要) HTML 字符编码&#xff08;自我复习&#xff09;-CSDN博客 例题1&#xff1a; <?php header("X-XSS-Protection: 0"); $xss …

Shell编程——简介和基础语法(1)

文章目录 Shell简介什么是ShellShell环境第一个Shell脚本Shell脚本的运行方法 Shell基础语法Shell变量Shell传递参数Shell字符串Shell字符串截取Shell数组Shell运算符 Shell简介 什么是Shell Shell是一种程序设计语言。作为命令语言&#xff0c;它交互式解释和执行用户输入的命…