美文网首页
uniapp 图片拖拽排序

uniapp 图片拖拽排序

作者: 我是七月 | 来源:发表于2024-07-17 15:43 被阅读0次

参考插件
https://ext.dcloud.net.cn/plugin?id=2393

主页面

<template>
    <view class="contain">
        <view class="content">
            <view class="title-box">
                <text class="title">荣誉证明</text>
                <text class="detail">拖动可调整顺序</text>
            </view>
            <view class="image-box">
                <shmily-drag-image
                    v-model="list"
                    :action="action"
                    :ossSignatureData="ossSignatureData"
                    :borderRadius="20"
                    keyName="url"
                    :number="20"
                    @uploadState="uploadState"
                ></shmily-drag-image>
            </view>
        </view>
        <view class="user-form-submit">
            <u-button @click="submit" :custom-style="customStyle">提交</u-button>
        </view>
    </view>
</template>

<script>
import { fileSign } from '@/api/article';
export default {
    name: 'ku-honor',
    data() {
        return {
            timer: null,
            ossSignatureData: {},
            action: `${this.$conf.baseUrl}file/upload`,
            list: [
                {
                    id: '1',
                    url: 'https://kuxuezhang.oss-cn-chengdu.aliyuncs.com/filetest/uhaYYVGgvPtUIJqKecS4IPZjKOBqvCXy.jpg'
                },
                {
                    id: '3',
                    url: 'https://kuxuezhang.oss-cn-chengdu.aliyuncs.com/filetest/uvPoIsDJkUYMwx2s7lmymm2WbgBXv80R.jpg'
                },
                {
                    id: '3',
                    url: 'https://kuxuezhang.oss-cn-chengdu.aliyuncs.com/filetest/uHtcGPcQfw2Z3iWPJfYbSlxtfM7yM6Hm.jpg'
                },
                {
                    id: '1',
                    url: 'https://kuxuezhang.oss-cn-chengdu.aliyuncs.com/filetest/uhaYYVGgvPtUIJqKecS4IPZjKOBqvCXy.jpg'
                },
                {
                    id: '3',
                    url: 'https://kuxuezhang.oss-cn-chengdu.aliyuncs.com/filetest/uvPoIsDJkUYMwx2s7lmymm2WbgBXv80R.jpg'
                },
                {
                    id: '3',
                    url: 'https://kuxuezhang.oss-cn-chengdu.aliyuncs.com/filetest/uHtcGPcQfw2Z3iWPJfYbSlxtfM7yM6Hm.jpg'
                },
                {
                    id: '1',
                    url: 'https://kuxuezhang.oss-cn-chengdu.aliyuncs.com/filetest/uhaYYVGgvPtUIJqKecS4IPZjKOBqvCXy.jpg'
                },
                {
                    id: '3',
                    url: 'https://kuxuezhang.oss-cn-chengdu.aliyuncs.com/filetest/uvPoIsDJkUYMwx2s7lmymm2WbgBXv80R.jpg'
                },
                {
                    id: '3',
                    url: 'https://kuxuezhang.oss-cn-chengdu.aliyuncs.com/filetest/uHtcGPcQfw2Z3iWPJfYbSlxtfM7yM6Hm.jpg'
                },
                {
                    id: '1',
                    url: 'https://kuxuezhang.oss-cn-chengdu.aliyuncs.com/filetest/uhaYYVGgvPtUIJqKecS4IPZjKOBqvCXy.jpg'
                },
                {
                    id: '3',
                    url: 'https://kuxuezhang.oss-cn-chengdu.aliyuncs.com/filetest/uvPoIsDJkUYMwx2s7lmymm2WbgBXv80R.jpg'
                },
                {
                    id: '3',
                    url: 'https://kuxuezhang.oss-cn-chengdu.aliyuncs.com/filetest/uHtcGPcQfw2Z3iWPJfYbSlxtfM7yM6Hm.jpg'
                },
                {
                    id: '1',
                    url: 'https://kuxuezhang.oss-cn-chengdu.aliyuncs.com/filetest/uhaYYVGgvPtUIJqKecS4IPZjKOBqvCXy.jpg'
                },
                {
                    id: '3',
                    url: 'https://kuxuezhang.oss-cn-chengdu.aliyuncs.com/filetest/uvPoIsDJkUYMwx2s7lmymm2WbgBXv80R.jpg'
                },
                {
                    id: '3',
                    url: 'https://kuxuezhang.oss-cn-chengdu.aliyuncs.com/filetest/uHtcGPcQfw2Z3iWPJfYbSlxtfM7yM6Hm.jpg'
                },
                {
                    id: '1',
                    url: 'https://kuxuezhang.oss-cn-chengdu.aliyuncs.com/filetest/uhaYYVGgvPtUIJqKecS4IPZjKOBqvCXy.jpg'
                },
                {
                    id: '3',
                    url: 'https://kuxuezhang.oss-cn-chengdu.aliyuncs.com/filetest/uvPoIsDJkUYMwx2s7lmymm2WbgBXv80R.jpg'
                },
                {
                    id: '3',
                    url: 'https://kuxuezhang.oss-cn-chengdu.aliyuncs.com/filetest/uHtcGPcQfw2Z3iWPJfYbSlxtfM7yM6Hm.jpg'
                },
                {
                    id: '1',
                    url: 'https://kuxuezhang.oss-cn-chengdu.aliyuncs.com/filetest/uhaYYVGgvPtUIJqKecS4IPZjKOBqvCXy.jpg'
                },
                {
                    id: '3',
                    url: 'https://kuxuezhang.oss-cn-chengdu.aliyuncs.com/filetest/uvPoIsDJkUYMwx2s7lmymm2WbgBXv80R.jpg'
                },
                {
                    id: '3',
                    url: 'https://kuxuezhang.oss-cn-chengdu.aliyuncs.com/filetest/uHtcGPcQfw2Z3iWPJfYbSlxtfM7yM6Hm.jpg'
                }
            ]
        };
    },
    computed: {
        customStyle() {
            return {
                width: '694rpx',
                height: '80rpx',
                background: '#24C68B',
                borderRadius: '80rpx',
                fontSize: '28rpx',
                fontWeight: 500,
                color: '#FFFFFF',
                letterSpacing: '7px',
                outline: 'none',
                border: 'none'
            };
        }
    },
    onLoad(options) {
        this.setOss();
    },
    destroyed() {
        this.clearTimer();
    },
    methods: {
        setOss() {
            this.getOssData();
            this.clearTimer();
            // OSS签名有效期10分钟,前端每间隔8分钟获取一次新的OSS签名
            this.timer = setInterval(() => {
                this.getOssData();
            }, 1000 * 60 * 8);
        },
        clearTimer() {
            clearInterval(this.timer);
            this.timer = null;
        },
        // 获取oss签名数据
        getOssData() {
            fileSign().then((resp) => {
                let data = resp.data;
                this.ossSignatureData = {
                    url: data.host,
                    key: data.pathPrefix,
                    policy: data.policy,
                    OSSAccessKeyId: data.ossAccessKeyId,
                    signature: data.signature
                };
                console.log('this.ossSignatureData=', this.ossSignatureData);
            });
        },
        uploadState(state) {
            console.log('uploadState99==', state);
            if (state) {
                console.log('uploadState99==正在上传');
            } else {
                console.log('uploadState99==上传完成');
            }
        },
        submit() {
            console.log('this.list=', this.list);
        }
    }
};
</script>

<style>
page {
    background-color: #f7f7f7;
    padding-bottom: constant(safe-area-inset-bottom);
    padding-bottom: env(safe-area-inset-bottom);
}
</style>

<style lang="scss" scoped>
.contain {
    padding-bottom: 140rpx;
}
.content {
    margin-top: 30rpx;
    margin-left: 28rpx;
    width: 694rpx;
    background: #ffffff;
    border-radius: 20rpx;
}
.title-box {
    display: flex;
    align-items: flex-end;
    padding-top: 30rpx;
    padding-left: 24rpx;
    padding-bottom: 30rpx;
}
.title {
    font-size: 28rpx;
    color: #333333;
}
.detail {
    margin-left: 7rpx;
    font-size: 26rpx;
    color: #999999;
}
.image-box {
    padding: 0 16rpx;
    padding-bottom: 20rpx;
}
.user-form-submit {
    width: 750rpx;
    position: fixed;
    bottom: 0rpx;
    z-index: 999;
    background-color: #f7f7f7;
    padding-bottom: calc(20rpx + constant(safe-area-inset-bottom));
    padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
    padding-top: 20rpx;
}
</style>

插件原文件

<template>
  <view class="con">
    <template v-if="viewWidth">
      <movable-area class="area" :style="{ height: areaHeight }" @mouseenter="mouseenter" @mouseleave="mouseleave">
        <movable-view v-for="(item, index) in imageList" :key="item.id" class="view" direction="all" :y="item.y"
          :x="item.x" :damping="40" :disabled="item.disable" @change="onChange($event, item)"
          @touchstart="touchstart(item)" @mousedown="touchstart(item)" @touchend="touchend(item)"
          @mouseup="touchend(item)" :style="{
          width: viewWidth + 'px', 
          height: viewWidth + 'px', 
          'z-index': item.zIndex, 
          opacity: item.opacity 
        }">
          <view class="area-con" :style="{
            width: childWidth, 
            height: childWidth, 
            borderRadius: borderRadius + 'rpx',
            transform: 'scale(' + item.scale + ')' 
          }">
            <image class="pre-image" :src="item.src" mode="aspectFill"></image>
            <view class="del-con" @click="delImages(item, index)" @touchstart.stop="delImageMp(item, index)"
              @touchend.stop="nothing()" @mousedown.stop="nothing()" @mouseup.stop="nothing()">
              <view class="del-wrap">
                <image class="del-image"
                  src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACEAAAAhCAYAAABX5MJvAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAhdEVYdENyZWF0aW9uIFRpbWUAMjAyMDowNzoyNSAyMTo1NDoyOU4TkJAAAADcSURBVFhH7ZfRCoMwDEXLvkjwwVf/bH/emmAyN6glTW9WBjsgwm28OeCLpj81Sil7zvlJ90UiONS/yY5VogsO6XrBg3IEQ5a/s8vRSWUAKmLqp2w5jz5BiNQEGMo3GbloDLtFXJ1IkaEuhAiiY6gEIqB4yqACSk9piIBiKQ8VUFpLviKg3C2rESKgWERCBZSWiEfgIfffYvrrsAgoISJ3Apy3zuTxcSxLQkV6ykNEPKVQkZEyiAiiZKgDIaC4upACSlcn5fM/+WuDCAHF1E/Z/N9AhkMZnPNDPI+UDjPIXgAQIGjNAAAAAElFTkSuQmCC">
                </image>
              </view>
            </view>
          </view>
        </movable-view>
        <view class="add" v-if="imageList.length < number"
          :style="{ top: add.y, left: add.x, width: viewWidth + 'px', height: viewWidth + 'px' }" @click="addImages">
          <view class="add-wrap" :style="{ width: childWidth, height: childWidth, borderRadius: borderRadius + 'rpx' }">
            <image style="width: 54rpx;height: 54rpx;"
              src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADYAAAA2CAYAAACMRWrdAAABIUlEQVRoQ+2a2w2DMAxFeQzWrsMUbadAsEw3S1CqVgppKwLX8BEOP4iHTXx8uUgWdVXoVhdaV0VhSmf7vr/H8V3XzY6V3P9iD+nYOI5P7/01LMI596AwoZV0TIBXIUWFXhKLFBWYSFGhhxQN6SFFQ5i4ogITKSr0cEVDekjRECauqMBEigq9U7piOk2yAti27SUe5ljlTfPEQ6KZecTvwl4P3ytvOv06R2HDMNzes7+6aRrvnHvtf50L13Lp50rx88zcvNlS3JpwKQ67XyK04nq2nFbk/LqVjin0TvmBNgQ2S4UUDcliHgpMpKjQwxUN6SFFQ5i4ogITKSr0cEVDekjRECauqMAsVoph+hVPtYr5+03p9tbYQ96xrYtT4ootbAJGVxxVTapVswAAAABJRU5ErkJggg==">
            </image>
          </view>
        </view>
      </movable-area>

    </template>
  </view>
</template>

<script>
  export default {
    emits: ['input', 'update:modelValue'],
    props: {
      // 排序图片
      value: {
        type: Array,
        default: function() {
          return []
        }
      },
      // 排序图片
      modelValue: {
        type: Array,
        default: function() {
          return []
        }
      },
      // 从 list 元素对象中读取的键名
      keyName: {
        type: String,
        default: null
      },
      // 选择图片数量限制
      number: {
        type: Number,
        default: 6
      },
      // 图片父容器宽度(实际显示的图片宽度为 imageWidth / 1.1 ),单位 rpx
      // imageWidth > 0 则 cols 无效
      imageWidth: {
        type: Number,
        default: 0
      },
      // 图片列数
      cols: {
        type: Number,
        default: 3
      },
      // 图片圆角,单位 rpx
      borderRadius: {
        type: Number,
        default: 0
      },
      // 图片周围空白填充,单位 rpx
      padding: {
        type: Number,
        default: 10
      },
      // 拖动图片时放大倍数 [0, ∞)
      scale: {
        type: Number,
        default: 1.1
      },
      // 拖动图片时不透明度
      opacity: {
        type: Number,
        default: 0.7
      },
      // 自定义添加
      addImage: {
        type: Function,
        default: null
      },
      // 删除确认
      delImage: {
        type: Function,
        default: null
      }
    },
    data() {
      return {
        imageList: [],
        width: 0,
        add: {
          x: 0,
          y: 0
        },
        colsValue: 0,
        viewWidth: 0,
        tempItem: null,
        timer: null,
        changeStatus: true,
        preStatus: true,
        first: true,
      }
    },
    computed: {
      areaHeight() {
        let height = ''
        // return '355px'
        if (this.imageList.length < this.number) {
          height = (Math.ceil((this.imageList.length + 1) / this.colsValue) * this.viewWidth).toFixed() + 'px'
        } else {
          height = (Math.ceil(this.imageList.length / this.colsValue) * this.viewWidth).toFixed() + 'px'
        }
        console.log('areaHeight', height)
        return height
      },
      childWidth() {
        return this.viewWidth - this.rpx2px(this.padding) * 2 + 'px'
      },
    },
    watch: {
      value: {
        handler(n) {
          if (!this.first && this.changeStatus) {
            console.log('watch', n)
            let flag = false
            for (let i = 0; i < n.length; i++) {
              if (flag) {
                this.addProperties(this.getSrc(n[i]))
                continue
              }
              if (this.imageList.length === i || this.imageList[i].src !== this.getSrc(n[i])) {
                flag = true
                this.imageList.splice(i)
                this.addProperties(this.getSrc(n[i]))
              }
            }
          }
        },
        deep: true
      },
      modelValue: {
        handler(n) {
          if (!this.first && this.changeStatus) {
            console.log('watch', n)
            let flag = false
            for (let i = 0; i < n.length; i++) {
              if (flag) {
                this.addProperties(this.getSrc(n[i]))
                continue
              }
              if (this.imageList.length === i || this.imageList[i].src !== this.getSrc(n[i])) {
                flag = true
                this.imageList.splice(i)
                this.addProperties(this.getSrc(n[i]))
              }
            }
          }
        },
        deep: true
      },
    },
    created() {
      this.width = uni.getSystemInfoSync().windowWidth
    },
    mounted() {
      const query = uni.createSelectorQuery().in(this)
      query.select('.con').boundingClientRect(data => {
        this.colsValue = this.cols
        this.viewWidth = data.width / this.cols
        if (this.imageWidth > 0) {
          this.viewWidth = this.rpx2px(this.imageWidth)
          this.colsValue = Math.floor(data.width / this.viewWidth)
        }
        let list = this.value
        // #ifdef VUE3
        list = this.modelValue
        // #endif
        for (let item of list) {
          this.addProperties(this.getSrc(item))
        }
        this.first = false

      })
      query.exec()
    },
    methods: {
      getSrc(item) {
        if(this.keyName !== null) {
          return item[this.keyName]
        }
        return item
      },
      onChange(e, item) {
        if (!item) return
        item.oldX = e.detail.x
        item.oldY = e.detail.y
        if (e.detail.source === 'touch') {
          if (item.moveEnd) {
            item.offset = Math.sqrt(Math.pow(item.oldX - item.absX * this.viewWidth, 2) + Math.pow(item.oldY - item
              .absY * this.viewWidth, 2))
          }
          let x = Math.floor((e.detail.x + this.viewWidth / 2) / this.viewWidth)
          if (x >= this.colsValue) return
          let y = Math.floor((e.detail.y + this.viewWidth / 2) / this.viewWidth)
          let index = this.colsValue * y + x
          if (item.index != index && index < this.imageList.length) {
            this.changeStatus = false
            for (let obj of this.imageList) {
              if (item.index > index && obj.index >= index && obj.index < item.index) {
                this.change(obj, 1)
              } else if (item.index < index && obj.index <= index && obj.index > item.index) {
                this.change(obj, -1)
              } else if (obj.id != item.id) {
                obj.offset = 0
                obj.x = obj.oldX
                obj.y = obj.oldY
                setTimeout(() => {
                  this.$nextTick(() => {
                    obj.x = obj.absX * this.viewWidth
                    obj.y = obj.absY * this.viewWidth
                  })
                }, 0)
              }
            }
            item.index = index
            item.absX = x
            item.absY = y
            if (!item.moveEnd) {
              setTimeout(() => {
                this.$nextTick(() => {
                  item.x = item.absX * this.viewWidth
                  item.y = item.absY * this.viewWidth
                })
              }, 0)
            }
            // console.log('bbb', JSON.parse(JSON.stringify(item)));
            this.sortList()
          }
        }
      },
      change(obj, i) {
        obj.index += i
        obj.offset = 0
        obj.x = obj.oldX
        obj.y = obj.oldY
        obj.absX = obj.index % this.colsValue
        obj.absY = Math.floor(obj.index / this.colsValue)
        setTimeout(() => {
          this.$nextTick(() => {
            obj.x = obj.absX * this.viewWidth
            obj.y = obj.absY * this.viewWidth
          })
        }, 0)
      },
      touchstart(item) {
        this.imageList.forEach(v => {
          v.zIndex = v.index + 9
        })
        item.zIndex = 99
        item.moveEnd = true
        this.tempItem = item
        this.timer = setTimeout(() => {
          item.scale = this.scale
          item.opacity = this.opacity
          clearTimeout(this.timer)
          this.timer = null
        }, 200)
      },
      touchend(item) {
        this.previewImage(item)
        item.scale = 1
        item.opacity = 1
        item.x = item.oldX
        item.y = item.oldY
        item.offset = 0
        item.moveEnd = false
        setTimeout(() => {
          this.$nextTick(() => {
            item.x = item.absX * this.viewWidth
            item.y = item.absY * this.viewWidth
            this.tempItem = null
            this.changeStatus = true
          })
          // console.log('ccc', JSON.parse(JSON.stringify(item)));
        }, 0)
        // console.log('ddd', JSON.parse(JSON.stringify(item)));
      },
      previewImage(item) {
        if (this.timer && this.preStatus && this.changeStatus && item.offset < 28.28) {
          clearTimeout(this.timer)
          this.timer = null
          const list = this.value || this.modelValue
          let srcList = list.map(v => this.getSrc(v))
          console.log(list, srcList);
          uni.previewImage({
            urls: srcList,
            current: item.src,
            success: () => {
              this.preStatus = false
              setTimeout(() => {
                this.preStatus = true
              }, 600)
            },
            fail: (e) => {
              console.log(e);
            }
          })
        } else if (this.timer) {
          clearTimeout(this.timer)
          this.timer = null
        }
      },
      mouseenter() {
        //#ifdef H5
        this.imageList.forEach(v => {
          v.disable = false
        })
        //#endif

      },
      mouseleave() {
        //#ifdef H5
        if (this.tempItem) {
          this.imageList.forEach(v => {
            v.disable = true
            v.zIndex = v.index + 9
            v.offset = 0
            v.moveEnd = false
            if (v.id == this.tempItem.id) {
              if (this.timer) {
                clearTimeout(this.timer)
                this.timer = null
              }
              v.scale = 1
              v.opacity = 1
              v.x = v.oldX
              v.y = v.oldY
              this.$nextTick(() => {
                v.x = v.absX * this.viewWidth
                v.y = v.absY * this.viewWidth
                this.tempItem = null
              })
            }
          })
          this.changeStatus = true
        }
        //#endif
      },
      addImages() {
        if (typeof this.addImage === 'function') {
          this.addImage.bind(this.$parent)()
        } else {
          let checkNumber = this.number - this.imageList.length
          uni.chooseImage({
            count: checkNumber,
            sourceType: ['album', 'camera'],
            success: res => {
              let count = checkNumber <= res.tempFilePaths.length ? checkNumber : res.tempFilePaths.length
              for (let i = 0; i < count; i++) {
                this.addProperties(res.tempFilePaths[i])
              }
              this.sortList()
            }
          })
        }
      },
      delImages(item, index) {
        if (typeof this.delImage === 'function') {
          this.delImage.bind(this.$parent)(() => {
            this.delImageHandle(item, index)
          })
        } else {
          this.delImageHandle(item, index)
        }
      },
      delImageHandle(item, index) {
        this.imageList.splice(index, 1)
        for (let obj of this.imageList) {
          if (obj.index > item.index) {
            obj.index -= 1
            obj.x = obj.oldX
            obj.y = obj.oldY
            obj.absX = obj.index % this.colsValue
            obj.absY = Math.floor(obj.index / this.colsValue)
            this.$nextTick(() => {
              obj.x = obj.absX * this.viewWidth
              obj.y = obj.absY * this.viewWidth
            })
          }
        }
        this.add.x = (this.imageList.length % this.colsValue) * this.viewWidth + 'px'
        this.add.y = Math.floor(this.imageList.length / this.colsValue) * this.viewWidth + 'px'
        this.sortList()
      },
      delImageMp(item, index) {
        //#ifdef MP
        this.delImages(item, index)
        //#endif
      },
      sortList() {
        console.log('sortList');
        const result = []
        let source = this.value
        // #ifdef VUE3
        source = this.modelValue
        // #endif
        
        let list = this.imageList.slice()
        list.sort((a, b) => {
          return a.index - b.index
        })
        for (let s of list) {
          let item = source.find(d => this.getSrc(d) == s.src)
          if (item) {
            result.push(item)
          } else {
            if(this.keyName !== null) {
              result.push({
                [this.keyName]: s.src
              })
            } else {
              result.push(s.src)
            }
          }
        }
        
        this.$emit("input", result);
        this.$emit("update:modelValue", result);
      },
      addProperties(item) {
        console.log(item);
        let absX = this.imageList.length % this.colsValue
        let absY = Math.floor(this.imageList.length / this.colsValue)
        let x = absX * this.viewWidth
        let y = absY * this.viewWidth
        this.imageList.push({
          src: item,
          x,
          y,
          oldX: x,
          oldY: y,
          absX,
          absY,
          scale: 1,
          zIndex: 9,
          opacity: 1,
          index: this.imageList.length,
          id: this.guid(16),
          disable: false,
          offset: 0,
          moveEnd: false
        })
        this.add.x = (this.imageList.length % this.colsValue) * this.viewWidth + 'px'
        this.add.y = Math.floor(this.imageList.length / this.colsValue) * this.viewWidth + 'px'
      },
      nothing() {},
      rpx2px(v) {
        return this.width * v / 750
      },
      guid(len = 32) {
        const chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('')
        const uuid = []
        const radix = chars.length
        for (let i = 0; i < len; i++) uuid[i] = chars[0 | Math.random() * radix]
        uuid.shift()
        return `u${uuid.join('')}`
      }
    }
  }
</script>

<style lang="scss" scoped>
  .con {
    // padding: 30rpx;

    .area {
      width: 100%;

      .view {
        display: flex;
        justify-content: center;
        align-items: center;

        .area-con {
          position: relative;
          overflow: hidden;

          .pre-image {
            width: 100%;
            height: 100%;
          }

          .del-con {
            position: absolute;
            top: 0rpx;
            right: 0rpx;
            padding: 0 0 20rpx 20rpx;

            .del-wrap {
              width: 36rpx;
              height: 36rpx;
              background-color: rgba(0, 0, 0, 0.4);
              border-radius: 0 0 0 10rpx;
              display: flex;
              justify-content: center;
              align-items: center;

              .del-image {
                width: 20rpx;
                height: 20rpx;
              }
            }
          }
        }
      }

      .add {
        position: absolute;
        display: flex;
        justify-content: center;
        align-items: center;

        .add-wrap {
          display: flex;
          justify-content: center;
          align-items: center;
          background-color: #eeeeee;
        }
      }
    }
  }
</style>

修改后文件

<template>
    <view class="con">
        <template v-if="viewWidth">
            <movable-area class="area" :style="{ height: areaHeight }" @mouseenter="mouseenter" @mouseleave="mouseleave">
                <movable-view
                    v-for="(item, index) in imageList"
                    :key="item.id"
                    class="view"
                    direction="all"
                    :y="item.y"
                    :x="item.x"
                    :damping="40"
                    :disabled="item.disable || !isLongPress"
                    @change="onChange($event, item)"
                    @touchstart="touchstart(item)"
                    @mousedown="touchstart(item)"
                    @touchend="touchend(item)"
                    @mouseup="touchend(item)"
                    @longpress="longpressHandler(item)"
                    :style="{
                        width: viewWidth + 'px',
                        height: viewWidth + 'px',
                        'z-index': item.zIndex,
                        opacity: item.opacity
                    }"
                >
                    <view
                        class="area-con"
                        :style="{
                            width: childWidth,
                            height: childWidth,
                            borderRadius: borderRadius + 'rpx',
                            transform: 'scale(' + item.scale + ')'
                        }"
                    >
                        <image class="pre-image" :src="item.src" @click.stop="previewImage(item)" mode="aspectFill"></image>
                        <view
                            class="del-con"
                            @click="delImages(item, index)"
                            @touchstart.stop="delImageMp(item, index)"
                            @touchend.stop="nothing()"
                            @mousedown.stop="nothing()"
                            @mouseup.stop="nothing()"
                        >
                            <view class="del-wrap">
                                <image
                                    class="del-image"
                                    src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACEAAAAhCAYAAABX5MJvAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAhdEVYdENyZWF0aW9uIFRpbWUAMjAyMDowNzoyNSAyMTo1NDoyOU4TkJAAAADcSURBVFhH7ZfRCoMwDEXLvkjwwVf/bH/emmAyN6glTW9WBjsgwm28OeCLpj81Sil7zvlJ90UiONS/yY5VogsO6XrBg3IEQ5a/s8vRSWUAKmLqp2w5jz5BiNQEGMo3GbloDLtFXJ1IkaEuhAiiY6gEIqB4yqACSk9piIBiKQ8VUFpLviKg3C2rESKgWERCBZSWiEfgIfffYvrrsAgoISJ3Apy3zuTxcSxLQkV6ykNEPKVQkZEyiAiiZKgDIaC4upACSlcn5fM/+WuDCAHF1E/Z/N9AhkMZnPNDPI+UDjPIXgAQIGjNAAAAAElFTkSuQmCC"
                                ></image>
                            </view>
                        </view>
                    </view>
                </movable-view>
                <view class="add" v-if="imageList.length < number" :style="{ top: add.y, left: add.x, width: viewWidth + 'px', height: viewWidth + 'px' }" @click="addImages">
                    <view class="add-wrap" :style="{ width: childWidth, height: childWidth, borderRadius: borderRadius + 'rpx' }">
                        <!-- <image style="width: 54rpx;height: 54rpx;"
              src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADYAAAA2CAYAAACMRWrdAAABIUlEQVRoQ+2a2w2DMAxFeQzWrsMUbadAsEw3S1CqVgppKwLX8BEOP4iHTXx8uUgWdVXoVhdaV0VhSmf7vr/H8V3XzY6V3P9iD+nYOI5P7/01LMI596AwoZV0TIBXIUWFXhKLFBWYSFGhhxQN6SFFQ5i4ogITKSr0cEVDekjRECauqMBEigq9U7piOk2yAti27SUe5ljlTfPEQ6KZecTvwl4P3ytvOv06R2HDMNzes7+6aRrvnHvtf50L13Lp50rx88zcvNlS3JpwKQ67XyK04nq2nFbk/LqVjin0TvmBNgQ2S4UUDcliHgpMpKjQwxUN6SFFQ5i4ogITKSr0cEVDekjRECauqMAsVoph+hVPtYr5+03p9tbYQ96xrYtT4ootbAJGVxxVTapVswAAAABJRU5ErkJggg==">
            </image> -->
                        <!-- <image src="../../../../static/tabbar/home_normal.png" mode=""></image> -->
                        <u-icon name="plus" color="#999" size="40"></u-icon>
                        <text class="add-title">上传照片</text>
                    </view>
                </view>
            </movable-area>
        </template>
    </view>
</template>

<script>
export default {
    emits: ['input', 'update:modelValue'],
    props: {
        // 排序图片
        value: {
            type: Array,
            default: function () {
                return [];
            }
        },
        // 排序图片
        modelValue: {
            type: Array,
            default: function () {
                return [];
            }
        },
        // 从 list 元素对象中读取的键名
        keyName: {
            type: String,
            default: null
        },
        // 选择图片数量限制
        number: {
            type: Number,
            default: 6
        },
        // 图片父容器宽度(实际显示的图片宽度为 imageWidth / 1.1 ),单位 rpx
        // imageWidth > 0 则 cols 无效
        imageWidth: {
            type: Number,
            default: 0
        },
        // 图片列数
        cols: {
            type: Number,
            default: 3
        },
        // 图片圆角,单位 rpx
        borderRadius: {
            type: Number,
            default: 0
        },
        // 图片周围空白填充,单位 rpx
        padding: {
            type: Number,
            default: 10
        },
        // 拖动图片时放大倍数 [0, ∞)
        scale: {
            type: Number,
            default: 1.1
        },
        // 拖动图片时不透明度
        opacity: {
            type: Number,
            default: 0.7
        },
        // 自定义添加
        addImage: {
            type: Function,
            default: null
        },
        // 删除确认
        delImage: {
            type: Function,
            default: null
        },
        action: {
            type: String,
            default: null
        },
        ossSignatureData: {
            type: Object,
            default: function () {
                return {};
            }
        }
    },
    data() {
        return {
            isLongPress: false,
            imageList: [],
            width: 0,
            add: {
                x: 0,
                y: 0
            },
            colsValue: 0,
            viewWidth: 0,
            tempItem: null,
            timer: null,
            changeStatus: true,
            preStatus: true,
            first: true
        };
    },
    computed: {
        areaHeight() {
            let height = '';
            // return '355px'
            if (this.imageList.length < this.number) {
                height = (Math.ceil((this.imageList.length + 1) / this.colsValue) * this.viewWidth).toFixed() + 'px';
            } else {
                height = (Math.ceil(this.imageList.length / this.colsValue) * this.viewWidth).toFixed() + 'px';
            }
            console.log('areaHeight', height);
            return height;
        },
        childWidth() {
            return this.viewWidth - this.rpx2px(this.padding) * 2 + 'px';
        }
    },
    watch: {
        value: {
            handler(n) {
                if (!this.first && this.changeStatus) {
                    console.log('watch', n);
                    let flag = false;
                    for (let i = 0; i < n.length; i++) {
                        if (flag) {
                            this.addProperties(this.getSrc(n[i]));
                            continue;
                        }
                        if (this.imageList.length === i || this.imageList[i].src !== this.getSrc(n[i])) {
                            flag = true;
                            this.imageList.splice(i);
                            this.addProperties(this.getSrc(n[i]));
                        }
                    }
                }
            },
            deep: true
        },
        modelValue: {
            handler(n) {
                if (!this.first && this.changeStatus) {
                    console.log('watch', n);
                    let flag = false;
                    for (let i = 0; i < n.length; i++) {
                        if (flag) {
                            this.addProperties(this.getSrc(n[i]));
                            continue;
                        }
                        if (this.imageList.length === i || this.imageList[i].src !== this.getSrc(n[i])) {
                            flag = true;
                            this.imageList.splice(i);
                            this.addProperties(this.getSrc(n[i]));
                        }
                    }
                }
            },
            deep: true
        }
    },
    created() {
        this.width = uni.getSystemInfoSync().windowWidth;
    },
    mounted() {
        const query = uni.createSelectorQuery().in(this);
        query.select('.con').boundingClientRect((data) => {
            this.colsValue = this.cols;
            this.viewWidth = data.width / this.cols;
            if (this.imageWidth > 0) {
                this.viewWidth = this.rpx2px(this.imageWidth);
                this.colsValue = Math.floor(data.width / this.viewWidth);
            }
            let list = this.value;
            // #ifdef VUE3
            list = this.modelValue;
            // #endif
            for (let item of list) {
                this.addProperties(this.getSrc(item));
            }
            this.first = false;
        });
        query.exec();
    },
    methods: {
        longpressHandler(item) {
            console.log('longpressHandler');
            this.isLongPress = true;
            this.imageList.forEach((v) => {
                v.zIndex = v.index + 9;
            });
            item.zIndex = 99;
            item.moveEnd = true;
            this.tempItem = item;
            this.timer = setTimeout(() => {
                item.scale = this.scale;
                item.opacity = this.opacity;
                clearTimeout(this.timer);
                this.timer = null;
            }, 200);
        },
        getSrc(item) {
            if (this.keyName !== null) {
                return item[this.keyName];
            }
            return item;
        },
        onChange(e, item) {
            if (!item) return;
            item.oldX = e.detail.x;
            item.oldY = e.detail.y;
            if (e.detail.source === 'touch') {
                if (item.moveEnd) {
                    item.offset = Math.sqrt(Math.pow(item.oldX - item.absX * this.viewWidth, 2) + Math.pow(item.oldY - item.absY * this.viewWidth, 2));
                }
                let x = Math.floor((e.detail.x + this.viewWidth / 2) / this.viewWidth);
                if (x >= this.colsValue) return;
                let y = Math.floor((e.detail.y + this.viewWidth / 2) / this.viewWidth);
                let index = this.colsValue * y + x;
                if (item.index != index && index < this.imageList.length) {
                    this.changeStatus = false;
                    for (let obj of this.imageList) {
                        if (item.index > index && obj.index >= index && obj.index < item.index) {
                            this.change(obj, 1);
                        } else if (item.index < index && obj.index <= index && obj.index > item.index) {
                            this.change(obj, -1);
                        } else if (obj.id != item.id) {
                            obj.offset = 0;
                            obj.x = obj.oldX;
                            obj.y = obj.oldY;
                            setTimeout(() => {
                                this.$nextTick(() => {
                                    obj.x = obj.absX * this.viewWidth;
                                    obj.y = obj.absY * this.viewWidth;
                                });
                            }, 0);
                        }
                    }
                    item.index = index;
                    item.absX = x;
                    item.absY = y;
                    if (!item.moveEnd) {
                        setTimeout(() => {
                            this.$nextTick(() => {
                                item.x = item.absX * this.viewWidth;
                                item.y = item.absY * this.viewWidth;
                            });
                        }, 0);
                    }
                    // console.log('bbb', JSON.parse(JSON.stringify(item)));
                    this.sortList();
                }
            }
        },
        change(obj, i) {
            obj.index += i;
            obj.offset = 0;
            obj.x = obj.oldX;
            obj.y = obj.oldY;
            obj.absX = obj.index % this.colsValue;
            obj.absY = Math.floor(obj.index / this.colsValue);
            setTimeout(() => {
                this.$nextTick(() => {
                    obj.x = obj.absX * this.viewWidth;
                    obj.y = obj.absY * this.viewWidth;
                });
            }, 0);
        },
        touchstart(item) {
            console.log('touchstart==', item);
            // this.imageList.forEach((v) => {
            //  v.zIndex = v.index + 9;
            // });
            // item.zIndex = 99;
            // item.moveEnd = true;
            // this.tempItem = item;
            // this.timer = setTimeout(() => {
            //  item.scale = this.scale;
            //  item.opacity = this.opacity;
            //  clearTimeout(this.timer);
            //  this.timer = null;
            // }, 200);
        },
        touchend(item) {
            console.log('touchend==', item);
            item.scale = 1;
            item.opacity = 1;
            item.x = item.oldX;
            item.y = item.oldY;
            item.offset = 0;
            item.moveEnd = false;
            setTimeout(() => {
                this.$nextTick(() => {
                    item.scale = 1;
                    item.x = item.absX * this.viewWidth;
                    item.y = item.absY * this.viewWidth;
                    this.tempItem = null;
                    this.changeStatus = true;
                });
                // console.log('ccc', JSON.parse(JSON.stringify(item)));
            }, 200);
            // console.log('ddd', JSON.parse(JSON.stringify(item)));
            this.isLongPress = false;
        },
        previewImage(item) {
            console.log('previewImage===');
            // if (this.timer && this.preStatus && this.changeStatus && item.offset < 28.28) {
            if (this.preStatus && this.changeStatus && item.offset < 28.28) {
                clearTimeout(this.timer);
                this.timer = null;
                const list = this.value || this.modelValue;
                let srcList = list.map((v) => this.getSrc(v));
                console.log(list, srcList);
                uni.previewImage({
                    urls: srcList,
                    current: item.src,
                    success: () => {
                        this.preStatus = false;
                        setTimeout(() => {
                            this.preStatus = true;
                        }, 600);
                    },
                    fail: (e) => {
                        console.log(e);
                    }
                });
            } else if (this.timer) {
                clearTimeout(this.timer);
                this.timer = null;
            }
        },
        mouseenter() {
            //#ifdef H5
            this.imageList.forEach((v) => {
                v.disable = false;
            });
            //#endif
        },
        mouseleave() {
            //#ifdef H5
            if (this.tempItem) {
                this.imageList.forEach((v) => {
                    v.disable = true;
                    v.zIndex = v.index + 9;
                    v.offset = 0;
                    v.moveEnd = false;
                    if (v.id == this.tempItem.id) {
                        if (this.timer) {
                            clearTimeout(this.timer);
                            this.timer = null;
                        }
                        v.scale = 1;
                        v.opacity = 1;
                        v.x = v.oldX;
                        v.y = v.oldY;
                        this.$nextTick(() => {
                            v.x = v.absX * this.viewWidth;
                            v.y = v.absY * this.viewWidth;
                            this.tempItem = null;
                        });
                    }
                });
                this.changeStatus = true;
            }
            //#endif
        },
        addImages() {
            if (typeof this.addImage === 'function') {
                this.addImage.bind(this.$parent)();
            } else {
                let checkNumber = this.number - this.imageList.length;
                uni.chooseMedia({
                    count: checkNumber,
                    mediaType: ['image'],
                    sourceType: ['album', 'camera'],
                    success: (res) => {
                        // console.log('chooseMedia==', res);
                        let count = checkNumber <= res.tempFiles.length ? checkNumber : res.tempFiles.length;
                        for (let i = 0; i < count; i++) {
                            this.addProperties(res.tempFiles[i].tempFilePath);
                        }
                        this.sortList();
                        console.log('imageList11==', this.imageList);
                        for (let i = 0; i < this.imageList.length; i++) {
                            let item = this.imageList[i];
                            if (this.isLocalImages(item.src) && item.uploading != 1) {
                                item.uploading = 1;
                                this.uploadFile(item);
                            }
                        }
                        console.log('imageList22==', this.imageList);
                    }
                });
            }
        },
        // 判断是否是本地temp图片
        isLocalImages(src) {
            return src.indexOf('aliyuncs') == -1;
        },
        // 上传图片
        uploadFile(imageFile) {
            let tmFilesPath = imageFile.src;
            let urlCarr = tmFilesPath.split('.');
            let imageType = urlCarr[urlCarr.length - 1];
            let formData = {
                key: this.ossSignatureData.key + this.$u.guid() + '.' + imageType,
                policy: this.ossSignatureData.policy,
                OSSAccessKeyId: this.ossSignatureData.OSSAccessKeyId,
                signature: this.ossSignatureData.signature
            };
            console.log('formData===', formData);
            return new Promise(() => {
                this.$emit('uploadState', true);
                uni.uploadFile({
                    url: this.ossSignatureData.url,
                    filePath: tmFilesPath,
                    name: 'file',
                    formData: formData,
                    header: this.header,
                    // #ifdef MP-ALIPAY
                    fileType: 'image',
                    // #endif
                    success: (resp) => {
                        let url = this.ossSignatureData.url + formData.key;
                        console.log('url999===', url, imageFile.id);

                        for (let i = 0; i < this.imageList.length; i++) {
                            let item = this.imageList[i];
                            if (item.id == imageFile.id) {
                                item.src = url;
                                item.uploading = 2;
                            }
                        }
                        // 是否还存在正在上传的图片
                        let haveLoading = this.imageList.some((item) => {
                            return this.isLocalImages(item.src) && item.uploading != 2;
                        });
                        this.sortList();
                        this.$emit('uploadState', haveLoading);
                        console.log('this.imageList77===', this.imageList);
                    },
                    fail: (error) => {}
                });
            });
        },
        delImages(item, index) {
            uni.showModal({
                title: '提示',
                content: '是否删除当前图片?',
                confirmColor: '#24C68B',
                success: (res) => {
                    if (res.confirm) {
                        if (typeof this.delImage === 'function') {
                            this.delImage.bind(this.$parent)(() => {
                                this.delImageHandle(item, index);
                            });
                        } else {
                            this.delImageHandle(item, index);
                        }
                    }
                }
            });
        },
        delImageHandle(item, index) {
            uni.showLoading({
                title: '删除图片中',
                mask: true
            });
            let isLastImage = true; //是否是最后一张图片
            this.imageList.splice(index, 1);
            for (let obj of this.imageList) {
                if (obj.index > item.index) {
                    isLastImage = false;
                    obj.index -= 1;
                    obj.x = obj.oldX;
                    obj.y = obj.oldY;
                    obj.absX = obj.index % this.colsValue;
                    obj.absY = Math.floor(obj.index / this.colsValue);
                    this.$nextTick(() => {
                        obj.x = obj.absX * this.viewWidth;
                        obj.y = obj.absY * this.viewWidth;
                        const maxItem = this.imageList.reduce((max, obj) => (max.index > obj.index ? max : obj));
                        if (obj.index >= maxItem.index) {
                            this.$nextTick(() => {
                                uni.hideLoading();
                            });
                        }
                    });
                }
            }
            this.add.x = (this.imageList.length % this.colsValue) * this.viewWidth + 'px';
            this.add.y = Math.floor(this.imageList.length / this.colsValue) * this.viewWidth + 'px';
            this.sortList();
            if (isLastImage) {
                this.$nextTick(() => {
                    uni.hideLoading();
                });
            }
        },
        delImageMp(item, index) {
            //#ifdef MP
            this.delImages(item, index);
            //#endif
        },
        sortList() {
            const result = [];
            let source = this.value;
            // #ifdef VUE3
            source = this.modelValue;
            // #endif

            let list = this.imageList.slice();
            list.sort((a, b) => {
                return a.index - b.index;
            });
            for (let s of list) {
                let item = source.find((d) => this.getSrc(d) == s.src);
                if (item) {
                    result.push(item);
                } else {
                    if (this.keyName !== null) {
                        result.push({
                            [this.keyName]: s.src
                        });
                    } else {
                        result.push(s.src);
                    }
                }
            }

            this.$emit('input', result);
            this.$emit('update:modelValue', result);
        },
        addProperties(item) {
            let absX = this.imageList.length % this.colsValue;
            let absY = Math.floor(this.imageList.length / this.colsValue);
            let x = absX * this.viewWidth;
            let y = absY * this.viewWidth;
            this.imageList.push({
                src: item,
                x,
                y,
                oldX: x,
                oldY: y,
                absX,
                absY,
                scale: 1,
                zIndex: 9,
                opacity: 1,
                index: this.imageList.length,
                id: this.guid(16),
                disable: false,
                offset: 0,
                moveEnd: false
            });
            this.add.x = (this.imageList.length % this.colsValue) * this.viewWidth + 'px';
            this.add.y = Math.floor(this.imageList.length / this.colsValue) * this.viewWidth + 'px';
        },
        nothing() {},
        rpx2px(v) {
            return (this.width * v) / 750;
        },
        guid(len = 32) {
            const chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('');
            const uuid = [];
            const radix = chars.length;
            for (let i = 0; i < len; i++) uuid[i] = chars[0 | (Math.random() * radix)];
            uuid.shift();
            return `u${uuid.join('')}`;
        }
    }
};
</script>

<style lang="scss" scoped>
.con {
    // padding: 30rpx;
    .area {
        width: 100%;

        .view {
            display: flex;
            justify-content: center;
            align-items: center;

            .area-con {
                position: relative;
                overflow: hidden;

                .pre-image {
                    width: 100%;
                    height: 100%;
                }

                .del-con {
                    position: absolute;
                    top: 0rpx;
                    right: 0rpx;
                    padding: 0 0 20rpx 20rpx;

                    .del-wrap {
                        width: 40rpx;
                        height: 40rpx;
                        background-color: rgba(0, 0, 0, 0.4);
                        border-radius: 0 0 0 10rpx;
                        display: flex;
                        justify-content: center;
                        align-items: center;
                        border-bottom-left-radius: 20rpx;

                        .del-image {
                            width: 20rpx;
                            height: 20rpx;
                        }
                    }
                }
            }
        }

        .add {
            position: absolute;
            display: flex;
            justify-content: center;
            align-items: center;

            .add-wrap {
                display: flex;
                justify-content: center;
                align-items: center;
                background-color: white;
                flex-direction: column;

                border-style: dashed;
                border-width: 1px;
                border-spacing: 5px;
                border-color: #999;

                .add-title {
                    margin-top: 10rpx;
                    font-size: 24rpx;
                    color: #999999;
                    line-height: 34rpx;
                }
            }
        }
    }
}
</style>

相关文章

网友评论

      本文标题:uniapp 图片拖拽排序

      本文链接:https://www.haomeiwen.com/subject/ujblhjtx.html