思路:1.先做出一个上传的图片的上传区
  <!-- 上传区 -->
      <label for="fileUp">
        <div class="upBorder">
          <img src="../assets/add.png" alt="" />
          <input
            ref="fileUp"
            type="file"
            id="fileUp"
            accept="image"
            style="display: none"
            @change="upload()"
          />
        </div>
      </label>

  

  upload() {
      let that = this;
      console.log(this.$refs.fileUp.files);
      if (this.$refs.fileUp.files.length != 0) {
        const reader = new FileReader();
        reader.readAsDataURL(this.$refs.fileUp.files[0]);
        reader.onload = function () {
          const img = new Image();
          img.src = reader.result;
          that.fileList.push(reader.result);
          that.$refs.fileUp.value = null; //上传后重置上传input的value,这样才能同时上传相同的图片
          console.log(reader.result);
        };
        this.upLodaOk = true;
      }
    },

  给上传图片的input绑定上ref属性然后通过FileReader构造函数获取上传的文件。

2.完成已上传文件的预览区域

 <!-- 预览区域 -->
      <div
        class="preView"
        v-for="(i, index) in fileList"
        :key="index"
        ref="preList"
      >
        <div class="fileList" v-if="upLodaOk">
          <img
            src="../assets/remove.png"
            alt=""
            class="remove"
            @click="removeProp(index)"
          />
          <img
            :src="fileList[index]"
            alt=""
            class="img"
            @click="cut(index)"
            ref="imgitem"
          />
        </div>
      </div>

  在upload方法中将通过FileReader构造函数获取上传的文件push到fileList数组中然后遍历渲染出已经上传的图片列表,并且给每一个图片绑定ref属性。

3.完成图片删除的功能

<!-- 删除弹窗 -->
    <div
      class="prop"
      :style="{
        height: this.windowHeight + 'px',
        width: this.windowWidth + 'px',
      }"
      v-if="show"
    >
      <div class="text">
        <img
          src="../assets/remove.png"
          alt=""
          class="close"
          @click="removePropClose()"
        />
        <div>要删除这张照片吗</div>
        <div class="action">
          <button class="btn green" @click="removePropClose()">取消</button>
          <button class="btn blue" @click="remove()">确定</button>
        </div>
      </div>
    </div>
   removeProp(index) {
      //v-for循环中的ref是个数组,根据index来取每一个对应的dom元素
      this.removeIndex = index;
      this.show = true;
    },
    removePropClose() {
      this.show = false;
    },
    remove() {
      this.fileList.splice(this.removeIndex, 1);
      this.$refs.fileUp.value = null; //删除后重置上传input的value,这样才能同时上传相同的图片
      console.log(this.$refs.fileUp.value);
      this.show = false;
    },

点击预览图片上的x会触发删除确认弹窗,在removeProp方法中将要删除的图片的Index接收并存储的removeIndex变量中,remove方法中将fileList数组中对应索引的元素去掉并且重置一下上传属性,也可以在每次上传后重置,并且关闭弹窗

4.完成上传时的剪裁功能

   <!-- 裁剪蒙层 -->
    <div
      class="prop center"
      v-if="cutProp"
      :style="{
        height: this.windowHeight + 'px',
        width: this.windowWidth + 'px',
      }"
    >
      <div v-html="pre" ref="preimg" class="imgContent"></div>
      <div class="cutHandler">
        <button class="btn green" @click="cancel()">取消</button>
        <button class="btn blue" @click="qdcut()">剪裁</button>
      </div>
    </div>
   cut(index) {
      this.selIndex = index;
      this.pre = `<img
            src="${this.fileList[index]}"
            alt=""
            class='cutImg'
          />`;
      this.cutProp = true;
      console.log(this.$refs);
      this.$nextTick(function () {
        console.log(this.$refs.preimg.firstChild); //使用nextTick,dom更新完成后才能获取到子节点
        this.myCropper = new Cropper(this.$refs.preimg.firstChild, {
          aspectRatio: 1 / 1,
          dragMode: "move",
          outputType: "png", //防止图片背景变黑
          crop(event) {
            console.log(event.detail.x);
            console.log(event.detail.y);
            console.log(event.detail.width);
            console.log(event.detail.height);
            console.log(event.detail.rotate);
            console.log(event.detail.scaleX);
            console.log(event.detail.scaleY);
          },
        });
      });
    },
    qdcut() {
      let cropBox = this.myCropper.getCropBoxData();
      console.log(this.myCropper.getCropBoxData()); //打印裁剪数据
      let cropCanvas = this.myCropper.getCroppedCanvas({
        width: cropBox.width,
        height: cropBox.height,
      }); //使用画布画出裁剪后的图片
      let imgData = cropCanvas.toDataURL(); //导出裁剪后图片的数据
      console.log(imgData);
      this.fileList.splice(this.selIndex, 1, imgData);
      console.log(this.fileList);
      this.cutProp = false;
    }, //确定裁剪
    cancel() {
      this.cutProp = false;
    }, //取消裁剪

  因为本次封装的是预览时裁剪的功能,所以裁剪的是点击预览列表中的文件触发的,cut方法将选择的图片的index存储selIndex变量中,然后通过v-html指令在剪裁弹窗中加载出对应的图片来进行裁剪,裁剪使用cropper.js来进行的,注意使用时要在this.$nextTick方法的回调中来进行剪裁函数的初始化,这样才能获取到通过v-html指令插入的图片。

  选择合适的裁剪尺寸后点击确认才加调用qdcut方法,通过cropper.js的内置方法getCropBoxData()获取剪裁的数据,通过getCroppedCanvas()传入对应的数据然后导出剪裁后的图片,将fileList中对应的元素替换即可完成

6.下面附上整个代码,可以直接拿去使用:

  1 <template>
  2   <div>
  3     <!-- 裁剪蒙层 -->
  4     <div
  5       class="prop center"
  6       v-if="cutProp"
  7       :style="{
  8         height: this.windowHeight + 'px',
  9         width: this.windowWidth + 'px',
 10       }"
 11     >
 12       <div v-html="pre" ref="preimg" class="imgContent"></div>
 13       <div class="cutHandler">
 14         <button class="btn green" @click="cancel()">取消</button>
 15         <button class="btn blue" @click="qdcut()">剪裁</button>
 16       </div>
 17     </div>
 18     <!-- 删除弹窗 -->
 19     <div
 20       class="prop"
 21       :style="{
 22         height: this.windowHeight + 'px',
 23         width: this.windowWidth + 'px',
 24       }"
 25       v-if="show"
 26     >
 27       <div class="text">
 28         <img
 29           src="../assets/remove.png"
 30           alt=""
 31           class="close"
 32           @click="removePropClose()"
 33         />
 34         <div>要删除这张照片吗</div>
 35         <div class="action">
 36           <button class="btn green" @click="removePropClose()">取消</button>
 37           <button class="btn blue" @click="remove()">确定</button>
 38         </div>
 39       </div>
 40     </div>
 41     <!-- 上传区域 -->
 42     <div class="upContent">
 43       <!-- 预览区域 -->
 44       <div
 45         class="preView"
 46         v-for="(i, index) in fileList"
 47         :key="index"
 48         ref="preList"
 49       >
 50         <div class="fileList" v-if="upLodaOk">
 51           <img
 52             src="../assets/remove.png"
 53             alt=""
 54             class="remove"
 55             @click="removeProp(index)"
 56           />
 57           <img
 58             :src="fileList[index]"
 59             alt=""
 60             class="img"
 61             @click="cut(index)"
 62             ref="imgitem"
 63           />
 64         </div>
 65       </div>
 66       <!-- 上传区 -->
 67       <label for="fileUp">
 68         <div class="upBorder">
 69           <img src="../assets/add.png" alt="" />
 70           <input
 71             ref="fileUp"
 72             type="file"
 73             id="fileUp"
 74             accept="image"
 75             style="display: none"
 76             @change="upload()"
 77           />
 78         </div>
 79       </label>
 80     </div>
 81   </div>
 82 </template>
 83 <script>
 84 import Cropper from "cropperjs";
 85 import "cropperjs/dist/cropper.css";
 86 export default {
 87   name: "upload",
 88   data() {
 89     return {
 90       cutProp: false,
 91       pre: "", //准备剪裁的图片
 92       selIndex: "", //选择照片的索引
 93       removeIndex: "", //准备删除的照片的索引
 94       show: false, //删除弹出层
 95       myCropper: null,
 96       afterImg: "",
 97       ingData: null,
 98       upLodaOk: false, //是否展示预览列表
 99       fileList: [], //已经上传图片的列表
100     };
101   },
102   methods: {
103     upload() {
104       let that = this;
105       console.log(this.$refs.fileUp.files);
106       if (this.$refs.fileUp.files.length != 0) {
107         const reader = new FileReader();
108         reader.readAsDataURL(this.$refs.fileUp.files[0]);
109         reader.onload = function () {
110           const img = new Image();
111           img.src = reader.result;
112           that.fileList.push(reader.result);
113           that.$refs.fileUp.value = null; //上传后重置上传input的value,这样才能同时上传相同的图片
114           console.log(reader.result);
115         };
116         this.upLodaOk = true;
117       }
118     },
119     removeProp(index) {
120       //v-for循环中的ref是个数组,根据index来取每一个对应的dom元素
121       this.removeIndex = index;
122       this.show = true;
123     },
124     removePropClose() {
125       this.show = false;
126     },
127     remove() {
128       this.fileList.splice(this.removeIndex, 1);
129       this.$refs.fileUp.value = null; //删除后重置上传input的value,这样才能同时上传相同的图片
130       console.log(this.$refs.fileUp.value);
131       this.show = false;
132     },
133     cut(index) {
134       this.selIndex = index;
135       this.pre = `<img
136             src="${this.fileList[index]}"
137             alt=""
138             class='cutImg'
139           />`;
140       this.cutProp = true;
141       console.log(this.$refs);
142       this.$nextTick(function () {
143         console.log(this.$refs.preimg.firstChild); //使用nextTick,dom更新完成后才能获取到子节点
144         this.myCropper = new Cropper(this.$refs.preimg.firstChild, {
145           aspectRatio: 1 / 1,
146           dragMode: "move",
147           outputType: "png", //防止图片背景变黑
148           crop(event) {
149             console.log(event.detail.x);
150             console.log(event.detail.y);
151             console.log(event.detail.width);
152             console.log(event.detail.height);
153             console.log(event.detail.rotate);
154             console.log(event.detail.scaleX);
155             console.log(event.detail.scaleY);
156           },
157         });
158       });
159     },
160     qdcut() {
161       let cropBox = this.myCropper.getCropBoxData();
162       console.log(this.myCropper.getCropBoxData()); //打印裁剪数据
163       let cropCanvas = this.myCropper.getCroppedCanvas({
164         width: cropBox.width,
165         height: cropBox.height,
166       }); //使用画布画出裁剪后的图片
167       let imgData = cropCanvas.toDataURL(); //导出裁剪后图片的数据
168       console.log(imgData);
169       this.fileList.splice(this.selIndex, 1, imgData);
170       console.log(this.fileList);
171       this.cutProp = false;
172     }, //确定裁剪
173     cancel() {
174       this.cutProp = false;
175     }, //取消裁剪
176   },
177   mounted() {},
178   computed: {
179     windowWidth() {
180       return document.documentElement.clientWidth;
181     },
182     windowHeight() {
183       return document.documentElement.clientHeight;
184     },
185   }, //监听屏幕的宽度和高度
186 };
187 </script>
188 <style>
189 .upBorder {
190   width: 8rem;
191   height: 8rem;
192   border: 1px silver dashed;
193   display: flex;
194   justify-content: center;
195   align-items: center;
196 }
197 .upContent {
198   display: flex;
199   justify-content: center;
200   align-items: center;
201 }
202 .img {
203   width: 8rem;
204   height: 8rem;
205 }
206
207 .fileList {
208   position: relative;
209   display: flex;
210   flex-direction: column;
211   justify-content: center;
212   align-items: center;
213 }
214 .remove {
215   position: absolute;
216   width: 1rem;
217   height: 1rem;
218   top: 0rem;
219   right: 0rem;
220   cursor: pointer;
221 }
222 .prop {
223   vertical-align: middle;
224   position: fixed;
225   top: 0;
226   left: 0;
227   z-index: 999;
228   background-color: rgba(0, 0, 0, 0.7);
229 }
230 .text {
231   border-radius: 0.2rem;
232   top: 50%;
233   left: 50%;
234   -webkit-transform: translate3d(-50%, -50%, 0);
235   transform: translate3d(-50%, -50%, 0);
236   position: fixed;
237   z-index: 1000;
238   color: black;
239   text-align: center;
240   background-color: #fff;
241   padding: 2rem 4rem;
242   white-space: nowrap;
243 }
244 .close {
245   position: absolute;
246   top: 0.3rem;
247   right: 0.3rem;
248   width: 1rem;
249   height: 1rem;
250 }
251 .action {
252   display: flex;
253   justify-content: space-between;
254   align-items: center;
255   margin-top: 1rem;
256 }
257 .btn {
258   font-size: 0.12rem;
259   color: #fff;
260   padding: 0.2rem 0.8rem;
261 }
262 .blue {
263   background-color: #1989fa;
264   border: 1px solid #1989fa;
265 }
266 .green {
267   background-color: #07c160;
268   border: 1px solid #07c160;
269 }
270 .cropper-point.point-se {
271   width: 5px;
272   height: 5px;
273 }
274 .cropper {
275   position: fixed;
276   top: 0;
277   z-index: 999;
278 }
279
280 /* .cropper-container{
281    top: 50%;
282   left: 50%;
283   -webkit-transform: translate3d(-50%, -50%, 0);
284   transform: translate3d(-50%, -50%, 0);
285 } */
286 .imgContent {
287   width: 16rem;
288   height: 16rem;
289   display: inline-block;
290   /* top: 50%;
291   left: 50%;
292   -webkit-transform: translate3d(-50%, -50%, 0);
293   transform: translate3d(-50%, -50%, 0); */
294 }
295 .cutImg {
296   display: block;
297   max-width: 100%;
298 }
299 .center {
300   display: flex;
301   flex-direction: column;
302   justify-content: center;
303   align-items: center;
304 }
305 .cropper-bg {
306   background: none;
307 }
308 .cutHandler {
309   margin-top: 2rem;
310   width: 16rem;
311   text-align: center;
312   display: flex;
313   justify-content: space-between;
314   align-items: center;
315 }
316 .cropper-modal {
317   background: rgba(0, 0, 0, 0);
318 }
319 </style>

 运行截图:vue封装原生的可预览裁剪上传图片插件H5,PC端都可以使用-LMLPHP

 vue封装原生的可预览裁剪上传图片插件H5,PC端都可以使用-LMLPHP

 H5,PC端都可以使用
 
07-09 12:32