web前端之拖拽API、vue3实现图片上传拖拽排序、拖放、投掷、复制、若依、vuedraggable


vue2+html5+原生dom+原生JavaScript实现跨区域拖放

关键代码

javascript 复制代码
// 放
function drop(ev) {
	let data = ev.dataTransfer.getData("Text"),
		i = ev.path[1].getAttribute("i"),
		text = document.getElementById(data).cloneNode(true).innerText.trim();

	if (i == null) return alert('请放置在文件名上');
	if (app.fileS[i].divs.includes(text)) return alert('不能放重复数据');
	app.fileS[i].divs.push(text);
	for (let is = 0; is < app.fileS.length; is++) {
		if (i == is) {
			app.fileS[is].isShow = true;
		} else {
			app.fileS[is].isShow = false;
		}
	}
}

完整代码

gitee(码云) - mj01分支 - copyDragAndDrop 文件


vue2实现跨区域拖放

关键代码

javascript 复制代码
dragend(item) {
	console.log(item);
	if (this.oldItem != this.newItem) {
		let oldIndex = this.List.indexOf(this.oldItem);
		let newIndex = this.List.indexOf(this.newItem);

		let oldflag = false
		let newflag = false

		if (oldIndex === -1) {
			oldflag = true
			oldIndex = this.list.indexOf(this.oldItem);
		}

		if (newIndex === -1) {
			newflag = true
			newIndex = this.list.indexOf(this.newItem);
		}

		let newList = [...this.List]; // 中间数组,用于交换两个节点
		let newlist = [...this.list]; // 中间数组,用于交换两个节点

		if (!oldflag) {
			newList.splice(oldIndex, 1);
		} else {
			newlist.splice(oldIndex, 1);
		}

		if (!newflag) {
			newList.splice(newIndex, 0, this.oldItem);
		} else {
			newlist.splice(newIndex, 0, this.oldItem);
		}

		// 删除老的节点
		// newList.splice(oldIndex, 1);
		// // 在列表目标位置增加新的节点
		// newList.splice(newIndex, 0, this.oldItem);
		// // 更新this.List,触发transition-group的动画效果
		this.List = [...newList];
		this.list = [...newlist];
	}
}

完整代码

gitee(码云) - mj01分支 - dragAndDrop 文件


vue2+mousedown实现全屏拖动,全屏投掷

html

html 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>鼠标滑动</title>
    <link rel="stylesheet" href="./index.css">
</head>

<body>
	<div id="app">
        <div class="ctn ctn1">
            <div class="sub sub1" v-for="(site, index) in list1">
                <div class="dragCtn fixed" @mousedown="mousedown(site, $event)"
                    @mousemove.prevent='mousemove(site, $event)' @mouseup='mouseup(site, $event)'>
                    {{ site.name }}
                </div>
            </div>
        </div>
        <div class="ctn ctn2">
            <div class="sub sub2" v-for="(site, index) in list2">
                <div class="dragCtn">
                    {{ index }} : {{ site.name }}
                </div>
            </div>
        </div>
    </div>

    <script src="/node_modules/vue/dist/vue.js"></script>
    <script src="./index.js"></script>
</body>

</html>

JavaScript

javascript 复制代码
new Vue({
    el: '#app',
    data: {
        list1: [{ name: '拖动我', index: 0 }],
        list2: [{ name: 'a', index: 0 }, { name: 'b', index: 1 }, { name: 'c', index: 2 }, { name: 'd', index: 3 }],
        vm: '',
        sb_bkx: 0,
        sb_bky: 0,
        is_moving: false
    },
    methods: {
        mousedown: function (site, event) {
            var startx = event.x;
            var starty = event.y;
            this.sb_bkx = startx - event.target.offsetLeft;
            this.sb_bky = starty - event.target.offsetTop;
            this.is_moving = true;
        },
        mousemove: function (site, event) {
            var endx = event.x - this.sb_bkx;
            var endy = event.y - this.sb_bky;
            var _this = this
            if (this.is_moving) {
                event.target.style.left = endx + 'px';
                event.target.style.top = endy + 'px';
            }
        },
        mouseup: function (e) {
            this.is_moving = false;
        }
    }
});

css

css 复制代码
.ctn {
    line-height: 50px;
    cursor: pointer;
    font-size: 20px;
    text-align: center;
    float: left;
}

.sub:hover {
    background: #e6dcdc;
    color: white;
    width: 100px;
}

.ctn1 {
    border: 1px solid green;
    width: 100px;
}

.ctn2 {
    border: 1px solid black;
    width: 100px;
    margin-left: 50px;
}

.fixed {
    width: 100px;
    height: 100px;
    position: fixed;
    background: red;
    left: 10px;
    top: 10px;
    cursor: move;
}

vue3+element-plus+vuedraggable实现图片上传拖拽排序

前言

安装对应的vuedraggable组件
npm install vuedraggable@4.1.0 --save
package.json文件中记录对应的版本号为: "vuedraggable": "4.1.0",这里要注意咯!!!克隆项目的时候这里的4.1.0可能会变为^4.1.0,一定要改为4.1.0;如果不是可以先卸载然后安装正确的版本即可。
如果版本不对会报错,并且不能运行。


本案例基于若依vue3前后端分离项目做二次开发
若依自带二次封装element-plus图片上传组件,但是没有实现拖拽排序功能。
于是又自己封装了一个ImageUploadDraggable图片上传组件,此组件基于若依自带的图片上传组件的基础上进行再次封装。
组件正常引入即可,可以全局引入或局部引入,引入方式跟我们自定的组件一样。


html

html 复制代码
<el-form-item label="图片" class="ws_n">
  <image-upload-draggable v-model="dialogForm.images" :limit="5">
  </image-upload-draggable>
</el-form-item>

JavaScript

javascript 复制代码
let info = reactive({
    dialogForm: {
      // 图片
      images: []
    }
  }),
  {
    dialogForm
  } = toRefs(info);

二次封装上传组件

html 复制代码
<template>
  <div class="component-upload-image">
    <ul class="el-upload-list el-upload-list--picture-card">
      <vue-draggable-next v-model="fileList">
        <li v-for="(item, index) in fileList" :key="item.index" class="el-upload-list__item is-success animated">
          <img :src="item.url" alt="" class="el-upload-list__item-thumbnail" />
          <i class="el-icon-close"></i>
          <span class="el-upload-list__item-actions">
            <!-- 预览 -->
            <span class="el-upload-list__item-preview" @click="handlePictureCardPreviewFileDetail(item)">
              <el-icon>
                <zoom-in></zoom-in>
              </el-icon>
            </span>
            <!-- 删除 -->
            <span class="el-upload-list__item-delete" @click="handleRemoveFileDetail(index)">
              <el-icon>
                <delete></delete>
              </el-icon>
            </span>
          </span>
        </li>
      </vue-draggable-next>
    </ul>
    <el-upload multiple :action="uploadImgUrl" list-type="picture-card" :on-success="handleUploadSuccess"
      :before-upload="handleBeforeUpload" :limit="limit" :on-error="handleUploadError" :on-exceed="handleExceed"
      ref="imageUpload" :show-file-list="false" :headers="headers" :class="{ hide: fileList.length >= limit }">
      <el-icon class="avatar-uploader-icon">
        <plus />
      </el-icon>
    </el-upload>
    <!-- 上传提示 -->
    <div class="el-upload__tip" v-if="showTip">
      请上传
      <template v-if="fileSize">
        大小不超过
        <b style="color: #f56c6c">{{ fileSize }}MB</b>
      </template>
      <template v-if="fileType">
        格式为
        <b style="color: #f56c6c">{{ fileType.join("/") }}</b>
      </template>
      的文件
    </div>

    <el-dialog v-model="dialogVisible" title="预览" width="800px" append-to-body>
      <img :src="dialogImageUrl" style="display: block; max-width: 100%; margin: 0 auto" />
    </el-dialog>
  </div>
</template>

<script setup>
import { VueDraggableNext } from "vue-draggable-next";
import { getToken } from "@/utils/auth";

const props = defineProps({
  modelValue: [String, Object, Array],
  // 图片数量限制
  limit: {
    type: Number,
    default: 5,
  },
  // 大小限制(MB)
  fileSize: {
    type: Number,
    default: 5,
  },
  // 文件类型, 例如['png', 'jpg', 'jpeg']
  fileType: {
    type: Array,
    default: () => ["png", "jpg", "jpeg"],
  },
  // 是否显示提示
  isShowTip: {
    type: Boolean,
    default: true,
  },
});

const { proxy } = getCurrentInstance();
const emit = defineEmits();
const number = ref(0);
const uploadList = ref([]);
const dialogImageUrl = ref("");
const dialogVisible = ref(false);
const baseUrl = import.meta.env.VITE_APP_BASE_API;
// 上传的图片服务器地址
const uploadImgUrl = ref(import.meta.env.VITE_APP_BASE_API + "/file/upload");
const headers = ref({
  Authorization: "Bearer " + getToken(),
  appid: import.meta.env.VITE_APP_ID,
});
const fileList = ref([]);
const showTip = computed(
  () => props.isShowTip && (props.fileType || props.fileSize)
);

watch(
  () => props.modelValue,
  (val) => {
    if (val) {
      // 首先将值转为数组
      const list = Array.isArray(val) ? val : props.modelValue.split(",");
      // 然后将数组转为对象数组
      fileList.value = list.map((item) => {
        if (typeof item === "string") {
          item = { name: item, url: item };
        }
        return item;
      });
    } else {
      fileList.value = [];
      return [];
    }
  },
  { deep: true, immediate: true }
);

// 上传前loading加载
function handleBeforeUpload(file) {
  let isImg = false;
  if (props.fileType.length) {
    let fileExtension = "";
    if (file.name.lastIndexOf(".") > -1) {
      fileExtension = file.name.slice(file.name.lastIndexOf(".") + 1);
    }
    isImg = props.fileType.some((type) => {
      if (file.type.indexOf(type) > -1) return true;
      if (fileExtension && fileExtension.indexOf(type) > -1) return true;
      return false;
    });
  } else {
    isImg = file.type.indexOf("image") > -1;
  }
  if (!isImg) {
    proxy.$modal.msgError(
      `文件格式不正确, 请上传${props.fileType.join("/")}图片格式文件!`
    );
    return false;
  }
  if (props.fileSize) {
    const isLt = file.size / 1024 / 1024 < props.fileSize;
    if (!isLt) {
      proxy.$modal.msgError(`上传头像图片大小不能超过 ${props.fileSize} MB!`);
      return false;
    }
  }
  proxy.$modal.loading("正在上传图片,请稍候...");
  number.value++;
}

// 文件个数超出
function handleExceed() {
  proxy.$modal.msgError(`上传文件数量不能超过 ${props.limit} 个!`);
}

// 上传成功回调
function handleUploadSuccess(res, file) {
  if (res.code === 0) {
    uploadList.value.push({ name: res.data.url, url: res.data.url });
    uploadedSuccessfully();
  } else {
    number.value--;
    proxy.$modal.closeLoading();
    proxy.$modal.msgError(res.msg);
    proxy.$refs.imageUpload.handleRemove(file);
    uploadedSuccessfully();
  }
}

function handlePictureCardPreviewFileDetail(file) {
  dialogImageUrl.value = file.url;
  dialogVisible.value = true;
}

// 删除
function handleRemoveFileDetail(index) {
  fileList.value.splice(index, 1);
}

// 上传结束处理
function uploadedSuccessfully() {
  if (number.value > 0 && uploadList.value.length === number.value) {
    fileList.value = fileList.value
      .filter((f) => f.url !== undefined)
      .concat(uploadList.value);
    uploadList.value = [];
    number.value = 0;
    emit("update:modelValue", listToString(fileList.value));
    proxy.$modal.closeLoading();
  }
}
// 上传失败
function handleUploadError() {
  proxy.$modal.msgError("上传图片失败");
  proxy.$modal.closeLoading();
}

// 对象转成指定字符串分隔
function listToString(list, separator) {
  let strs = "";
  separator = separator || ",";
  for (let i in list) {
    if (undefined !== list[i].url && list[i].url.indexOf("blob:") !== 0) {
      strs += list[i].url.replace(baseUrl, "") + separator;
    }
  }
  return strs != "" ? strs.substr(0, strs.length - 1) : "";
}
</script>

<style scoped lang="scss">
// .el-upload--picture-card 控制加号部分
:deep(.hide .el-upload--picture-card) {
  display: none;
}
</style>

vue2+transition-group实现拖动排序

html

html 复制代码
<transition-group id='app' name="drog" tag="ul">
	<div draggable="true" v-for="(item, index) in lists" @dragstart="dragStart($event, index)" @dragover="allowDrop" @drop="drop($event, index)" v-bind:key="item">{{item}}</div>
</transition-group>

JavaScript

javascript 复制代码
new Vue({
    el: '#app',
    data: {
        lists: ['1: apple', '2: banana', '3: orange', '4: melon']
    },
    
	methods: {
        // 取消默认行为
        allowDrop(e){
            e.preventDefault();
        },
        
        // 开始拖动
        dragStart(e, index){
            let tar = e.target;
            e.dataTransfer.setData('Text', index);
            if (tar.tagName.toLowerCase() == 'li') {
                // console.log('drag start')
                // console.log('drag Index: ' + index)
            }
        },
        
        // 放置
        drop(e, index){
            this.allowDrop(e);
            // console.log('drop index: ' + index);
            //使用一个新数组重新排序后赋给原变量
            let arr = this.lists.concat([]),
                dragIndex = e.dataTransfer.getData('Text');
                temp = arr.splice(dragIndex, 1);
            arr.splice(index, 0, temp[0]);
            // console.log('sort');
            this.lists = arr;
        }
    }
});

原生拖拽排序

html

html 复制代码
<ul id="idUl">
    <li class="m_36 ta_c bc_87ceeb fs_68">1</li>
    <li class="m_36 ta_c bc_87ceeb fs_68">2</li>
    <li class="m_36 ta_c bc_87ceeb fs_68">3</li>
    <li class="m_36 ta_c bc_87ceeb fs_68">4</li>
    <li class="m_36 ta_c bc_87ceeb fs_68">5</li>
</ul>

JavaScript

javascript 复制代码
(function () {
    let ulList = document.querySelector('#idUl'),
        liList = document.querySelectorAll('li'),
        currentLi = undefined;

    liList.forEach(item => item.draggable = "true");

    ulList.addEventListener('dragstart', (e) => {
        e.dataTransfer.effectAllowed = 'move';
        currentLi = e.target;

        setTimeout(() => currentLi.classList.add('bc_transparent color_transparent'), 0);
    });
    ulList.addEventListener('dragenter', (e) => {
        e.preventDefault();

        if (e.target === currentLi || e.target === ulList) return false;

        let liArray = Array.from(ulList.childNodes),
            currentIndex = liArray.indexOf(currentLi),
            targetindex = liArray.indexOf(e.target)

        if (currentIndex < targetindex) {
            ulList.insertBefore(currentLi, e.target.nextElementSibling);
        } else {
            ulList.insertBefore(currentLi, e.target);
        }
    });
    ulList.addEventListener('dragover', (e) => e.preventDefault());
    ulList.addEventListener('dragend', (e) => currentLi.classList.remove('bc_transparent color_transparent'));
})();
相关推荐
文火冰糖的硅基工坊24 分钟前
[嵌入式系统-146]:五次工业革命对应的机器人形态的演进、主要功能的演进以及操作系统的演进
前端·网络·人工智能·嵌入式硬件·机器人
2401_8370885036 分钟前
ResponseEntity - Spring框架的“标准回复模板“
java·前端·spring
yaoganjili1 小时前
用 Tinymce 打造智能写作
前端
angelQ1 小时前
Vue 3 中 ref 获取 scrollHeight 属性为 undefined 问题定位
前端·javascript
Dontla1 小时前
(临时解决)Chrome调试避免跳入第三方源码(设置Blackbox Scripts、将目录添加到忽略列表、向忽略列表添加脚本)
前端·chrome
我的div丢了肿么办1 小时前
js函数声明和函数表达式的理解
前端·javascript·vue.js
云中雾丽1 小时前
React.forwardRef 实战代码示例
前端
朝歌青年说1 小时前
一个在多年的技术债项目中写出来的miniHMR热更新工具
前端
武天1 小时前
一个项目有多个后端地址,每个后端地址的请求拦截器和响应拦截器都不一样,该怎么封装
vue.js
Moonbit1 小时前
倒计时 2 天|Meetup 议题已公开,Copilot 月卡等你来拿!
前端·后端