vue3 

使用vue-cropper。与 elelment-ui 的el-upload 组件组合

我们实现的效果

我们经过裁剪 可以获取到这个 图片的base64 因为目前后端存储的都是 base64 图片 

这样其实这个功能反而方便了

我们封装了一个裁剪的图片 我现在来贴上代码

子组件

<template>
  <div class="cover-upload-container">
    <!-- 上传按钮 -->
    <el-upload
      v-model:file-list="fileList"
      class="upload-demo"
      action=""
      :auto-upload="false"
      :show-file-list="false"
      :on-change="handleFileChange"
      accept="image/*"
    >
      <el-button type="default" round>选择封面</el-button>
      <template #tip>
        <div class="el-upload__tip">封面尺寸为600*800像素</div>
      </template>
    </el-upload>

    <!-- 裁剪对话框 -->
    <el-dialog 
      v-model="showCropDialog" 
      title="封面裁剪" 
      width="900px"
      :before-close="handleCancelCrop"
    >
      <div class="crop-container">
        <div class="crop-area">
          <vue-cropper
            ref="cropperRef"
            :src="tempImageUrl"
            :aspect-ratio="3/4"
            :view-mode="1"
            :guides="true"
            :background="false"
            :auto-crop-area="0.8"
            :movable="true"
            :zoomable="true"
            :rotatable="true"
            preview=".preview"
            class="cropper-instance"
          />
        </div>
        
        <div class="preview-area">
          <div class="preview-title">预览效果</div>
          <div class="preview"></div>
          <div class="preview-tip">600×800像素</div>
        </div>
      </div>

      <div class="crop-actions">
        <el-button @click="rotate(-90)" size="small">
          <el-icon><RefreshLeft /></el-icon>
          向左旋转
        </el-button>
        <el-button @click="rotate(90)" size="small">
          <el-icon><RefreshRight /></el-icon>
          向右旋转
        </el-button>
        <el-button @click="reset" size="small">重置</el-button>
        <el-button @click="zoom(0.1)" size="small">放大</el-button>
        <el-button @click="zoom(-0.1)" size="small">缩小</el-button>
      </div>

      <div class="crop-tips">
        <h4>封面裁剪规范</h4>
        <p>1. 图片质量:请提供高分辨率、清晰无水印的官方封面图片。</p>
        <p>2. 内容完整:请通过鼠标拖动或缩放,确保书名、作者、出版社等所有关键信息完整地置于裁剪框内,且无明显拉伸变形</p>
      </div>

      <template #footer>
        <el-button @click="handleCancelCrop">取消</el-button>
        <el-button type="primary" @click="handleConfirmCrop">确定裁剪</el-button>
      </template>
    </el-dialog>

    <!-- 裁剪结果预览 -->
    <!-- <div v-if="croppedImageUrl" class="result-preview">
      <div class="preview-header">
        <span>封面预览</span>
        <el-button type="danger" text @click="handleRemoveCover" size="small">删除</el-button>
      </div>
      <img :src="croppedImageUrl" class="cover-image" alt="封面预览">
    </div> -->
  </div>
</template>

<script setup>
import { ref, nextTick } from 'vue';
import { ElMessage } from 'element-plus';
import { RefreshLeft, RefreshRight } from '@element-plus/icons-vue';
import VueCropper from 'vue-cropperjs';
import 'cropperjs/dist/cropper.css';

// 响应式数据
const fileList = ref([]);
const showCropDialog = ref(false);
const tempImageUrl = ref('');
const croppedImageUrl = ref('');
const cropperRef = ref(null);

const emit =defineEmits(["cover-cropped"])

// 处理文件选择
const handleFileChange = (file) => {
  // 验证文件类型
  if (!file.raw.type.startsWith('image/')) {
    ElMessage.error('请选择图片文件');
    return;
  }

  // 验证文件大小(5MB)
  if (file.raw.size > 5 * 1024 * 1024) {
    ElMessage.error('图片大小不能超过5MB');
    return;
  }

  // 创建文件读取器
  const reader = new FileReader();
  reader.onload = (e) => {
    tempImageUrl.value = e.target.result;
    showCropDialog.value = true;
    
    // 确保DOM更新后初始化
    nextTick(() => {
      // 可以在这里添加一些初始化后的操作
    });
  };
  reader.readAsDataURL(file.raw);
};

// 旋转图片
const rotate = (degree) => {
  if (cropperRef.value) {
    cropperRef.value.rotate(degree);
  }
};

// 缩放图片
const zoom = (ratio) => {
  if (cropperRef.value) {
    cropperRef.value.relativeZoom(ratio);
  }
};

// 重置裁剪
const reset = () => {
  if (cropperRef.value) {
    cropperRef.value.reset();
  }
};

// 取消裁剪
const handleCancelCrop = () => {
  showCropDialog.value = false;
  tempImageUrl.value = '';
  fileList.value = []; // 清空文件列表
};

// 确认裁剪
const handleConfirmCrop = () => {
  if (!cropperRef.value) {
    ElMessage.error('裁剪器未初始化');
    return;
  }

  // 获取裁剪后的 base64
  const base64 = cropperRef.value.getCroppedCanvas().toDataURL('image/jpeg', 0.9);
  
  // 也可以获取 blob 用于上传
  cropperRef.value.getCroppedCanvas().toBlob((blob) => {
    // 这里可以处理 blob 上传到服务器
    console.log('裁剪后的blob:', blob);
    
    // 显示裁剪结果
    croppedImageUrl.value = base64;
    showCropDialog.value = false;
    tempImageUrl.value = '';
    fileList.value = []; // 清空文件列表
    
    ElMessage.success('封面裁剪完成');
    
    // 可以在这里触发上传或传递给父组件
    emit('cover-cropped', { base64, blob });
  }, 'image/jpeg', 0.9);
};

// 删除封面
const handleRemoveCover = () => {
  croppedImageUrl.value = '';
  ElMessage.info('已删除封面');
};

// 暴露方法给父组件(可选)
defineExpose({
  getCoverData: () => {
    return croppedImageUrl.value;
  },
  clearCover: () => {
    croppedImageUrl.value = '';
  }
});
</script>

<style scoped>
.cover-upload-container {
  width: 100%;
}

.upload-demo {
  margin-bottom: 20px;
}

.el-upload__tip {
  margin-top: 8px;
  font-size: 12px;
  color: #909399;
}

/* 裁剪容器 */
.crop-container {
  display: flex;
  gap: 30px;
  margin-bottom: 20px;
  align-items: flex-start;
}

.crop-area {
  flex: 1;
  min-height: 400px;
  max-height: 500px;
  display: flex;
  justify-content: center;
  align-items: center;
  background: #f8f9fa;
  border-radius: 6px;
  padding: 10px;
}

/* 修复 vue-cropper 样式 */
.cropper-instance {
  width: 100% !important;
  height: 400px !important;
  max-width: 100%;
}

:deep(.cropper-container) {
  direction: ltr !important;
  font-size: 0;
  line-height: 0;
  position: relative;
  touch-action: none;
  user-select: none;
}

:deep(.cropper-wrap-box) {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  overflow: hidden;
}

:deep(.cropper-canvas) {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
}

.preview-area {
  width: 180px;
  text-align: center;
  flex-shrink: 0;
}

.preview-title {
  font-size: 14px;
  color: #606266;
  margin-bottom: 12px;
  font-weight: 500;
}

.preview {
  width: 135px;
  height: 180px;
  border: 1px solid #e4e7ed;
  border-radius: 6px;
  overflow: hidden;
  margin: 0 auto;
  background: #f5f7fa;
}

.preview-tip {
  font-size: 12px;
  color: #909399;
  margin-top: 8px;
}

/* 裁剪操作区 */
.crop-actions {
  display: flex;
  gap: 12px;
  justify-content: center;
  margin-bottom: 20px;
  flex-wrap: wrap;
  padding: 15px;
  background: #f8f9fa;
  border-radius: 6px;
}

/* 提示信息 */
.crop-tips {
  padding: 16px;
  background: #f0f9ff;
  border: 1px solid #e1f5fe;
  border-radius: 6px;
  font-size: 13px;
  color: #606266;
  line-height: 1.6;
}

.crop-tips h4 {
  margin: 0 0 10px 0;
  color: #1890ff;
  font-size: 14px;
  font-weight: 600;
}

.crop-tips p {
  margin: 4px 0;
}

/* 结果预览 */
.result-preview {
  margin-top: 20px;
  border: 1px solid #e4e7ed;
  border-radius: 8px;
  padding: 16px;
  background: #fafafa;
}

.preview-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 12px;
  font-size: 14px;
  color: #303133;
  font-weight: 500;
}

.cover-image {
  width: 150px;
  height: 200px;
  object-fit: cover;
  border-radius: 6px;
  border: 2px solid #fff;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}

/* 响应式设计 */
@media (max-width: 768px) {
  .crop-container {
    flex-direction: column;
    gap: 20px;
  }
  
  .crop-area {
    min-height: 300px;
  }
  
  .cropper-instance {
    height: 300px !important;
  }
  
  .preview-area {
    width: 100%;
  }
  
  .preview {
    width: 120px;
    height: 160px;
  }
  
  .crop-actions {
    flex-direction: column;
    align-items: center;
  }
  
  .crop-actions .el-button {
    width: 160px;
  }
}
</style>

父组件使用

							<cover-upload @cover-cropped="handleCoverCropped" />

const handleCoverCropped=(e)=>{
	console.log(e,"base64");
	defaultCover.value=e.base64
	

}

这样的话就能够实现

不过不要忘了安装 vue-cropper

这个小伙伴们应该都会npm 吧 我就不多说了 我目前写这个文章的目的是为了记录自己做过的功能 以及为了方便大家开发遇到问题 帮助大家快速完成需求 因为现在不是前端的时代 学习技术固然重要 但是在这个环境下 前端已经抄底的情况下 

我希望大家不要纠结 代码学习怎么样 主要完成任务 拿到属于自己的报酬 

能捞就捞 

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐