Vue 实现图片列表上传,拖拽排序,图片编辑(裁剪,马赛克,翻转等功能)上传用了element-ui组件可不用

Vue 实现图片列表上传,拖拽排序,图片编辑(裁剪,马赛克,翻转等功能)上传用了element-ui组件可不用

1、相关插件

1、 vue-cropper 裁剪,旋转,放大等功能
2、 vuedraggable 拖拽排序功能
3、element-ui,大家可以用 也可不用 我项目中用了,但是和图片编辑功能无关(但是和图片批量上传有关系,不过也可以自己封装图片上传功能)

2、组件如何使用

1、slot 插槽 自己定义上传图片按钮样式
2、v-model 返回图片列表

// imgList 返回的格式是一个数组,下面这种格式,当然这里是我拍脑袋返回的格式,大家要是觉得不符合自己的效果,可以自行在组件代码里修改,或者评论留言 我帮你改
[
'https://img.life.cntaiping.com/cms/test/61/160670418872041.jpg',
'https://img.life.cntaiping.com/cms/test/61/160670418872041.jpg'
'https://img.life.cntaiping.com/cms/test/61/160670418872041.jpg'
]

3、saveList 保存图片的触发方法
4、大家复制了组件的代码之后,需要全局搜索 需要修改的地方,因为 一些图片上传地址需要修改成自己的,马赛克图片换成自己的 或者网络图片
5、还有一点想说明一下,大家直接,复制这两段代码,安装好上面的依赖,就直接可以出来下面图中的效果

<template>
    <img-upload v-model="imgList" @saveList="saveList">
      <span style="color:blue">上传图片</span>
    </img-upload>
</template>

import imgUpload from './components/imgUpload'

export default {
  components: {
    imgUpload
  },

3、效果展示

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

4、imgUpload封装的组件代码

<template>
  <div>
    <el-dialog
      class="update-dialog"
      width="700px"
      :visible.sync="outerVisible"
      :close-on-click-modal="false"
    >
      <p slot="title" class="titl">
        车源照片 <span>按住拖动可调整顺序,点击后可涂抹、旋转图片</span>
      </p>
      <p class="desc">首图巴拉巴拉 <span>拍摄示例</span></p>
      <p class="slid"><span>{{ value.length }}</span>/15</p>
      <draggable
        v-loading="loadingShow"
        :list="value"
        tag="ul"
        class="img-list"
        v-bind="dragOptions"
        draggable=".item"
      >
        <li v-for="(item,index) in value" :key="item" class="item">
          <img :src="item" alt="" @click="editImg(index)">
          <i class="el-icon-circle-close" @click="deletImgList(index)" />
        </li>
        <li v-show="value.length < 15" class="img-li">
          <el-upload
            class="upload-class"
            :action="uploadUrl"
            :headers="headers"
            :before-upload="uploadBefore"
            :on-success="onSucessUpload"
            multiple
            :on-exceed="onExceedupload"
            :limit="limitNumb"
            :show-file-list="false"
          >
            <i class="el-icon-plus" /> 从电脑上传图片
          </el-upload>
        </li>
      </draggable>
      <el-dialog
        width="700px"
        :visible.sync="innerVisible"
        append-to-body
        :before-close="beforeClose"
        center
      >
        <p slot="title" class="titl">编辑照片</p>
        <div v-loading="loadingCrop" class="edit-img">
          <vueCropper
            ref="cropper"
            :img="editImgUrl"
            :info="false"
            :output-type="'png'"
            :info-true="true"
            :can-move="true"
            :can-move-box="true"
            :fixed="true"
            :fixed-number="[3, 2]"
            :auto-crop-width="600"
            :auto-crop-height="400"
            :mode="'cover'"
            :center-box="true"
            :enlarge="5"
            :full="true"
            :max-img-size="200000"
            @imgLoad="imgLoadCrop"
          />
          <div v-if="toolIndex === 4" class="tumo-img">
            <canvas class="canvas" />
          </div>
          <div v-if="recommendDialog" class="recommend-title">
            您刚才的编辑还未保存,返回将放弃所有编辑
          </div>
        </div>
        <template v-if="!recommendDialog">
          <div class="too-btns">
            <div class="left" @click="toolClick(1)">左旋90</div>
            <div class="right" @click="toolClick(2)">右旋90</div>
            <div class="cut" :class="{'active':toolIndex === 3}" @click="toolClick(3)">{{ toolIndex === 3 ? '取消裁剪':'裁剪' }}</div>
            <div class="tumo" :class="{'active':toolIndex === 4}" @click="toolClick(4)">{{ toolIndex === 4 ? '取消涂抹':'涂抹' }}</div>
          </div>
          <div v-show="toolIndex === 4" class="block-slider">
            <p>涂抹直径</p>
            <el-slider v-model="widthTm" :min="2" :max="50" :step="2" @input="changeWidthTm" />
          </div>
        </template>
        <div slot="footer" class="dialog-footer">
          <el-button v-if="recommendDialog" @click="saveEdit">不保存</el-button>
          <el-button :loading="saveLoading" type="primary" @click="saveEdit(1)">保存修改</el-button>
        </div>
      </el-dialog>
      <div slot="footer" class="dialog-footer">
        <el-button type="primary" @click="saveList">保存车源图片</el-button>
        <el-button @click="outerVisible = false">取 消</el-button>
      </div>
    </el-dialog>
    <div @click="outerVisible = true">
      <slot>
        <!-- slot 数据 -->
      </slot>
    </div>
  </div>
</template>

<script>
import { VueCropper } from 'vue-cropper'
import draggable from 'vuedraggable'
import axios from 'axios'
export default {
  name: 'UploadImg',
  components: {
    VueCropper,
    draggable
  },
  props: {
    value: {
      type: Array,
      default: () => { [] }
    }
  },
  data() {
    return {
      outerVisible: false, // 最外层dialog
      innerVisible: false, // 裁剪弹窗
      limitNumb: 15, // 图片上传数量限制
      loadingShow: false, // 上传图片列表加载
      loadingCrop: false, // 图片编辑时候加载
      // 需要修改的地方 改成自己的 uploadUrl headers
      uploadUrl: '/admin/basic/upload/ArticleCover',
      headers: {
        uid: '123', // this.$sessionObj.uid
        userToken: 'acadcdfefefeafeppoogr123fd', // this.$sessionObj.userToken
        platform: 1, // this.$sessionObj.platform
        deviceInfo: 'acadcdfefefeafeppoogr123fd' // this.$sessionObj.deviceInfo
      },

      editIndex: '', // 被选中的图片的角标
      editImgUrl: '', // 选中图片地址

      toolIndex: 0, // 操作按钮 1 做旋转 2 右旋转 3 裁剪 4 马赛克

      tumoIngUrl: '', // 涂抹时候的图片
      widthTm: 30, // 涂抹半径

      recommendDialog: false, // 没有保存退出的提示框
      isCut: false, // 是否裁剪
      isTumo: false, // 是否涂抹
      isRotate: 0, // 是否旋转
      saveLoading: false // 是否保存
    }
  },
  computed: {
    //   判断是否修改过
    isEdit() {
      return this.isCut || this.isTumo || parseInt(this.isRotate % 4) !== 0
    },
    dragOptions() {
      return {
        animation: 200, // 动画时间
        disabled: false, // false可拖拽,true不可拖拽
        group: 'description',
        ghostClass: 'ghost'
      }
    }
  },
  methods: {
    //  格式化第二个dialog
    refrehDialog() {
      this.toolIndex = 0
      this.tumoIngUrl = ''
      this.recommendDialog = false
      this.isCut = false
      this.isTumo = false
      this.isRotate = 0
      this.saveLoading = false
      this.editImgUrl = ''
    },
    beforeClose() {
      //   判断是否修改了
      if (this.isEdit) {
        this.recommendDialog = true
        return false
      } else {
        this.innerVisible = false
        this.refrehDialog()
      }
    },
    saveEdit(val) {
      if (val === 1) {
        this.saveLoading = true
        //   保存图片 上传图片
        if (this.toolIndex === 4) {
          this.editImgUrl = this.tumoIngUrl
          setTimeout(() => {
            this.uploadImg()
          }, 1000)
        } else {
          this.uploadImg()
        }
      } else {
        //   直接关闭
        this.innerVisible = false
        this.refrehDialog()
      }
    },
    // 上传图片
    uploadImg() {
      // 需要修改的地方 改成自己的 res.data.response[0] 这个获取图片地址格式自己定
      const formData = new FormData()
      this.$refs.cropper.getCropBlob(data => {
        formData.append('file', data, new Date().getTime() + 'edit_.png')
        axios.post(this.uploadUrl, formData).then((res) => {
          const imgList = this.value.slice()
          imgList[this.editIndex] = res.data.response[0]
          this.$emit('input', imgList)
          this.saveLoading = false
          this.innerVisible = false
          this.refrehDialog()
        }).catch(() => { this.saveLoading = false })
      })
    },
    onSucessUpload(response, file, fileList) {
      this.loadingShow = false
      const imgList = this.value.slice()
      // 需要修改的地方 改成自己的 response.response[0] 这个获取图片地址格式
      imgList.push(response.response[0])
      this.$emit('input', imgList)
      this.limitNumb = 15 - this.value.length
    },
    onExceedupload(files) {
      this.$message.warning(`做多上传15张图片`)
    },
    uploadBefore(file) {
      this.loadingShow = true
      // 图片格式是否正确
      let bool = null
      // 获取图片的后缀名
      const dotIndex = file.name.lastIndexOf('.')
      const suffixOfImage = file.name.slice(dotIndex + 1).toLowerCase()
      switch (suffixOfImage) {
        case 'jpeg':
        case 'png':
        case 'jpg':
          bool = true
          break
        default:
          bool = false
      }
      if (bool) {
        const isLt5Mb = file.size / 1024 / 1024 < 5
        if (!isLt5Mb) {
          this.loadingShow = false
          this.$message.warning('上传图片大小不能超过 5MB!')
          return false
        }
      } else {
        this.$message.warning(
          '您上传的图片格式不正确!(仅支持jpeg,png,jpg)'
        )
        this.loadingShow = false
        return false
      }
    },
    // 删除图片
    deletImgList(index) {
      const imgList = this.value.slice()
      imgList.splice(index, 1)
      this.$emit('input', imgList)
    },
    // 编辑图片
    editImg(index) {
      this.editIndex = index
      this.editImgUrl = this.value[index]
      this.innerVisible = true
      this.loadingCrop = true
    },
    // 开启裁剪
    imgLoadCrop() {
      this.loadingCrop = false
    },
    // 点击工具栏
    toolClick(val) {
      if (val === 1 || val === 2) {
        this.roatatImg(val)
      } else if (val === 3) {
        // 裁剪
        if (this.toolIndex === 3) {
          this.isCut = false
          this.$refs.cropper.clearCrop()
          setTimeout(() => {
            this.toolIndex = 0
          }, 0)
        } else if (this.toolIndex === 4) {
          this.isTumo = true
          this.editImgUrl = this.tumoIngUrl
          setTimeout(() => {
            this.$refs.cropper.goAutoCrop()
          }, 0)
        } else {
          this.$refs.cropper.goAutoCrop()
        }
      } else {
        if (this.toolIndex === 4) {
          setTimeout(() => {
            this.toolIndex = 0
          }, 0)
        } else {
          this.$refs.cropper.getCropData((data) => {
            this.tumoIngUrl = data
            this.$refs.cropper.clearCrop()
            setTimeout(() => {
              this.tuMoImg()
            }, 0)
          })
        }
      }
      this.toolIndex = val
    },
    // 左右旋转
    roatatImg(val) {
      if (val === 1) this.isRotate++
      else this.isRotate--
      if (this.toolIndex === 3) {
        this.isCut = true
        this.$refs.cropper.getCropData((data) => {
          this.editImgUrl = data
          this.$refs.cropper.clearCrop()
          setTimeout(() => {
            if (val === 1) this.$refs.cropper.rotateLeft()
            else this.$refs.cropper.rotateRight()
          }, 0)
        })
      } else if (this.toolIndex === 4) {
        this.isTumo = true
        this.editImgUrl = this.tumoIngUrl
        setTimeout(() => {
          if (val === 1) this.$refs.cropper.rotateLeft()
          else this.$refs.cropper.rotateRight()
        }, 0)
      } else {
        if (val === 1) this.$refs.cropper.rotateLeft()
        else this.$refs.cropper.rotateRight()
      }
    },
    changeWidthTm(val) {
      document.querySelector('.block-slider .el-slider__button').style.width = val + 'px'
      document.querySelector('.block-slider .el-slider__button').style.height = val + 'px'
    },
    // 涂抹逻辑
    tuMoImg() {
      const cs = document.querySelector('.canvas')
      const roate = 3
      cs.width = roate * 650
      cs.height = cs.width / 3 * 2

      const ctx = cs.getContext('2d')
      const upImg = new Image()
      const backImg = new Image()
      let isRip = false // 是否开撕
      upImg.src = this.tumoIngUrl
      //  需要修改的地方 换成马赛克图片
      backImg.src = require('../../assets/images/bg2.png')
      upImg.onload = () => {
        // 判断绘制路径
        let dw = 0
        let dh = 0
        let dx = 0
        let dy = 0
        if ((upImg.width / upImg.height) >= (3 / 2)) {
          // 需要满足宽度
          const r = upImg.width / cs.width
          dw = cs.width
          dh = upImg.height / r
          dy = (cs.height - dh) / 2
        } else {
          // 需要满足高度
          const r = upImg.height / cs.height
          dh = cs.height
          console.log(upImg.height, cs.height, r)
          dw = upImg.width / r
          dx = (cs.width - dw) / 2
        }
        console.log(upImg.width, upImg.height, dx, dy, dw, dh)
        ctx.drawImage(upImg, 0, 0, upImg.width, upImg.height, dx, dy, dw, dh) // 画上面的图片
      }
      cs.onmousedown = function() {
        isRip = true
      }
      cs.onmouseup = function() {
        isRip = false
      }
      let timer = null
      cs.onmousemove = (e) => {
        if (isRip) {
          const x = e.offsetX
          const y = e.offsetY
          const w = this.widthTm
          ctx.drawImage(backImg, 0, 0, w, w, (x - w / 2) * roate, (y - w / 2) * roate, w, w)
        }
        if (timer) clearTimeout(timer)
        timer = setTimeout(() => {
          this.tumoIngUrl = cs.toDataURL()
        }, 200)
      }
    },
    // 保存
    saveList() {
      this.outerVisible = false
      this.$emit('saveList', this.value)
    }
  }
}
</script>
<style>
.update-dialog .el-dialog__body {
  padding-top: 10px;
}
.update-dialog .el-icon-circle-close {
  color: red;
  position: absolute;
  font-size: 30px;
  right: -10px;
  top: -10px;
}
.cropper-modal {
  background: rgba(255, 0, 0, .75) !important;
}
.block-slider .el-slider__bar {
    background: rgba(0, 0, 0, .5);
}
.block-slider .el-slider__button-wrapper {
    position: relative;
}
.block-slider .el-slider__button {
    border: none;
    background: red;
    height: 30px;
    width: 30px;
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%,-50%);
}
</style>
<style scoped>
.titl {
  font-size: 20px;
  font-weight: 700;
}
.titl span {
  font-size: 16px;
  font-weight: 500;
  margin-left: 20px;
  color: red;
}
.desc {
    padding-bottom: 15px;
}
.desc span,.slid span {
  color: #409eff;
}
.img-list {
    display: flex;
    flex-wrap: wrap;
    max-height: 350px;
    overflow-y: scroll;
}
.img-list .item:nth-child(1)::after {
    content: '首图';
    position: absolute;
    width: 40px;
    height: 20px;
    text-align: center;
    line-height: 20px;
    top: 0;
    left: 0;
    color: #ffffff;
    font-size: 14px;
    background: coral;
}
.img-list li,.img-li{
    width: 200px;
    height: 133px;
    margin: 15px 10px;
    position: relative;
}

.img-li {
    float: left;
}
.img-list li img{
    width: 100%;
    height: 100%;
    object-fit: contain;
    background: #000;
}
.upload-class {
  color: #409eff;
  text-align: center;
  height: 100%;
  line-height: 133px;
  border: 1px dashed #409eff;
  box-sizing: border-box;
}
.edit-img {
    width: 650px;
    height: 433px;
    position: relative;
}
.tumo-img {
    position: absolute;
    width: 650px;
    height: 433px;
    top: 0;
    left: 0;
}
.edit-img img{
    width: 100%;
    height: 100%;
    object-fit: contain;
}
.too-btns {
    display: flex;
    justify-content: space-around;
    padding-top: 20px;
}
.too-btns  div{
    min-width: 110px;
    height: 40px;
    line-height: 40px;
    text-align: center;
    padding: 0 10px;
    color: #fff;
    background: rgba(0, 0, 0, .6);
}
.too-btns .active{
    background: #000;
    color: yellow;
}
.too-btns div:hover{
    background: #000;
    color: yellow;
    cursor:pointer
}
.block-slider {
    width: 45%;
    margin: 0 auto;
    margin-top: 30px;
    text-align: center;
}
.block-slider p{
    margin-bottom: 10px;
}
.canvas {
    width: 100%;
    height: 100%;
    background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMzTjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC');
}
.recommend-title {
    text-align: center;
    line-height: 433px;
    position: absolute;
    width: 650px;
    height: 453px;
    background: #fff;
    top: -10px;
    left: 0;
}
</style>

  • 4
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
实现拖拽排序组件,可以使用 element-ui 中提供的 el-draggable 组件。在 el-draggable 组件上设置 v-model 绑定一个数组,然后在数组中存储每个组件的参数和属性。在 el-draggable 组件的 slot 中遍历数组,将每个组件渲染出来。同时,可以使用 el-form 组件实现参数输入和断言的功能。 以下是一个简单的实现示例: ```html <template> <div> <el-draggable v-model="componentList"> <template v-for="(component, index) in componentList"> <div :key="index"> <el-form> <el-form-item label="参数1"> <el-input v-model="component.param1"></el-input> </el-form-item> <el-form-item label="参数2"> <el-input v-model="component.param2"></el-input> </el-form-item> </el-form> <el-button @click="assert(component)">断言</el-button> </div> </template> </el-draggable> </div> </template> <script> import { ElDraggable, ElForm, ElFormItem, ElInput, ElButton } from 'element-ui'; export default { components: { ElDraggable, ElForm, ElFormItem, ElInput, ElButton, }, data() { return { componentList: [ { name: '组件1', param1: '', param2: '' }, { name: '组件2', param1: '', param2: '' }, { name: '组件3', param1: '', param2: '' }, ], }; }, methods: { assert(component) { // 实现断言的逻辑 }, }, }; </script> ``` 在这个示例中,我们使用了 el-draggable、el-form、el-form-item、el-input 和 el-button 等 element-ui 组件实现拖拽排序、参数输入和断言的功能。同时,我们在 componentList 数组中存储了每个组件的参数和属性,并在 el-draggable 组件的 slot 中遍历数组,将每个组件渲染出来。在 el-form 组件中,我们使用 el-input 组件实现参数输入的功能。在 el-button 组件中,我们可以绑定一个 assert 方法来实现断言的逻辑。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值