vue2 使用 Sortable 库进行拖拽操作

article/2025/9/14 4:12:58

一、vue 项目使用

文档地址: https://www.itxst.com/sortablejs/neuinffi.html

1、安装依赖

npm i -S vuedraggable

2、.vue 文件引入组件

import draggable from "vuedraggable";
components: { draggable },

在这里插入图片描述

3、.使用

查看文档中的示例即可:https://debug.itxst.com/js/ivv3eivm

我们使用的 npm 安装,不需要其他东西,只需要下方标注的主要代码部分,其中的css为演示展示用,无实际用处

在这里插入图片描述

二、进阶案例演示代码(UMD版)

1、采用技术

  • vue2
  • element-ui2
  • avue : 基于vue2 + element-ui2 二次封装组件库
  • Sortable : 拖拽
  • vuedraggable : vue二次封装的拖拽,基于Sortable

2、展示图

在这里插入图片描述

3、源码

原为npm 版, 抽取成 UMD 版便于大家学习参考

1、本地新建 .html文件
2、复制下方代码到 .html
3、打开htm 即得到上方 展示效果中 相同效果

<!DOCTYPE html>
<html>
<head><meta charset="utf-8"><title></title>
</head>
<!-- 引入样式文件 -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@smallwei/avue/lib/index.css"/>
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css"/>
<!-- 引入相关JS 文件 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@smallwei/avue/lib/avue.min.js"></script><!-- 图片拖拽排序 -->
<script src="https://cdn.staticfile.org/Sortable/1.10.0-rc2/Sortable.min.js"></script>
<!-- 已经加载过了 -->
<!--<script src="https://www.itxst.com/package/vue/vue.min.js"></script>-->
<!--<script src="https://www.itxst.com/package/sortable/Sortable.min.js"></script>-->
<script src="https://www.itxst.com/package/vuedraggable/vuedraggable.umd.min.js"></script><div id="app">{{ message }}<div class="bt-article-all"><el-row><el-col :span="24"><el-card class="box-card"><div slot="header" class="clearfix"><span>文章信息区</span><el-button style="float: right; padding: 3px 0" type="text">保存</el-button></div><avue-form ref="form" v-model="obj" :option="option"@reset-change="emptytChange"@submit="submit"><template slot-scope="{row}" slot="content"><TinymceEditor v-if="initSuccess" :content.sync="obj.content"/></template></avue-form></el-card></el-col></el-row><el-row><!-- 左侧区 --><el-col :span="12"><el-card class="box-card"><div slot="header" class="clearfix"><span>内容编辑区</span><!-- <el-button style="float: right; padding: 3px 0" type="text">保存</el-button>--></div><div><!--  group="itxst" --><draggable v-model="contentItems" chosen-class="chosen" force-fallback="true" group="itxst" :disabled="disabledDrag" animation="1000" @start="onStart" @end="onEnd"><transition-group><div id="contentBox" class="bt-card-box" v-for="(item,index) in contentItems" :key="index" style="padding-top: 2%"><el-card class="box-card bt-card-box"><div slot="header" class="clearfix singlePerson"><span>{{index+1}} :   </span><span>{{item.lableName}}  </span><el-button style="float: right; padding: 3px 0" type="text" @click="delItemRow(item)">删除</el-button></div><div v-if="item.lable == 'H1' || item.lable == 'H2' || item.lable == 'H3'"><el-input type="input" placeholder="请输入内容" v-model="item.value"></el-input></div><div v-if="item.lable == 'P' ">富文本组件<!-- 富文本组件 当前单页无法加载 --><!--  <TinymceEditor v-if="drag==false" :content.sync="item.value"/>--></div><div v-if="item.lable == 'IMAGE'"><el-image style="width: 80px; height: 80px" :src="item.value" fit="cover"></el-image></div><div v-if="item.lable == 'VIDEO'"><el-input type="input" placeholder="请输入内容" v-model="item.value"></el-input><!--  <el-image style="width: 80px; height: 80px" :src="item.value" fit="cover"></el-image>--></div><div v-if="item.lable == 'ARRAY'"><!-- <avue-form :option="{column: [{label:'数组框',prop:'array', type:'array', value:[0,1]}]}"></avue-form>--><avue-array v-model="item.value" :option="{dataType:'string'}" placeholder="请输入内容"></avue-array></div></el-card></div></transition-group></draggable></div></el-card></el-col><!-- 右侧区 --><el-col :span="12"><!-- 媒体区 --><div class="bt-card-box"><el-card class="box-card"><div slot="header" class="clearfix"><span>媒体资源区</span></div><div><el-row><draggable v-model="imageItems" chosen-class="chosen" force-fallback="true" :options="{group:{name: 'itxst',pull:'clone'}, sort: true}" animation="1000" @start="onStartImages" @end="onEndImages"><transition-group><el-col :span="4" v-for="(item,index) in imageItems" :key="index"><div style="padding: 5%"><el-card class="box-card"><el-imagestyle="width: 80px; height: 80px":src="item.value"fit="cover"></el-image><span style="text-align: center;display:block;">{{ item.alt }}</span></el-card></div></el-col></transition-group></draggable></el-row></div></el-card></div><!-- 内容预览区 --><div class="bt-card-box" style="padding-top: 2%"><el-card class="box-card"><div slot="header" class="clearfix"><span>内容预览区</span></div><div v-for="(item,index) in contentItems" :key="index"><h1 v-if="item.lable == 'H1'">{{item.value}}</h1><h2 v-if="item.lable == 'H2'">{{item.value}}</h2><h3 v-if="item.lable == 'H3'">{{item.value}}</h3><span v-if="item.lable == 'P'" v-html="item.value"></span><span v-if="item.lable == 'IMAGE'"><img style="width: 50%" :src="item.value" alt="item.alt"></span><div v-if="item.lable == 'VIDEO'"><video width="50%" controls :autoplay="false"><source :src="item.value" type="video/mp4"></video></div><div v-if="item.lable == 'ARRAY'"><li v-for="(item,index) in item.value">{{index+1}}{{item}}</li></div></div></el-card></div></el-col></el-row>{{contentItems}}<div>{{drag?'拖拽中':'拖拽停止'}}</div></div>
</div><body>
<script>//import draggable from "vuedraggable";var vm = new Vue({// 绑定 id="app" 的元素el: "#app",// components: {//     draggable// },// 定义数据data: {message: "这是一个拖拽示例demo",obj: {},initSuccess: false,defaultData: {name: null,alias: null,author: "测试",categoryIds: null,coverUrl: "http://xijia-sz.oss-cn-shenzhen.aliyuncs.com/oss/file/file/gc/08769453-1(6).jpeg",lables: null,content: null,state: 1,describe: "-",auth: 1,sort: 0,seoTitle: null,seoKeyword: null,seoDescription: null,},categoryTree: [],disabledDrag: false, //默认开启拖拽drag: false,dragImages: false,contentItems: [{lable: 'H1', lableName: "一级标题", value: ''},{lable: 'H2', lableName: "二级标题", value: ''},{lable: 'H3', lableName: "三级标题", value: ''},{lable: 'P', lableName: "段落", value: ''},{lable: 'IMAGE', lableName: "图片", value: 'https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg'},{lable: 'VIDEO', lableName: "视频", value: 'http://127.0.0.1:10006/upload/video/swagger-ui.html/20221109-0315-11、恭喜你发现宝藏!!!.mp4'},{lable: 'ARRAY', lableName: "有序列表", value: [0, 1]},],// "lable": "H1", "name": "一级标题", "sort": 1, "value": "-"imageItems: [//fits: ['fill', 'contain', 'cover', 'none', 'fill'],//fits: ['fill', 'contain', 'cover', 'none', 'scale-down'],{lable: "IMAGE", lableName: "图片", alt: "a1", value: "https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg"},{lable: "IMAGE", lableName: "图片", alt: "a2", value: "https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg"},{lable: "IMAGE", lableName: "图片", alt: "a3", value: "https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg"},{lable: "IMAGE", lableName: "图片", alt: "a4", value: "http://xijia-sz.oss-cn-shenzhen.aliyuncs.com/oss/file/file/gc/08769453-1(6).jpeg"},{lable: "IMAGE", lableName: "图片", alt: "a5", value: "http://xijia-sz.oss-cn-shenzhen.aliyuncs.com/oss/file/file/gc/08769453-1(6).jpeg"},{lable: "IMAGE", lableName: "图片", alt: "a6", value: "http://xijia-sz.oss-cn-shenzhen.aliyuncs.com/oss/file/file/gc/08769453-1(6).jpeg"},],},props: {closeDialog: [],uri: {},},computed: {option() {return {submitBtn: false,emptyBtn: false,submitText: '提交',emptyText: "关闭",group: [{// icon: 'el-icon-info',label: '展开/收缩文章信息',collapse: false,prop: 'group1',column: [{label: '文章名',prop: 'name',maxlength: 64,showWordLimit: true,span: 10,rules: [{required: true,message: "请输入 文章名",trigger: "blur"}]},{label: '别名',prop: 'alias',maxlength: 64,showWordLimit: true,span: 10,rules: [{required: true,message: "请输入 别名",trigger: "blur"}]},{label: '作者',prop: 'author',maxlength: 32,showWordLimit: true,span: 10,rules: [{required: true,message: "请输入 作者",trigger: "blur"}]},{label: '分类',prop: 'categoryIds',span: 10,type: "cascader",dataType: 'string',filterable: true,dicData: this.categoryTree,   // 自行替换字典数据props: {value: "id",label: "name",children: "categorys"},rules: [{required: true,message: "请选择 分类ids ",trigger: "blur"}]},{label: '封面图',prop: 'coverUrl',span: 10,rules: [{required: true,message: "请上传 文章封面图url ",trigger: "blur"}],dataType: 'string',accept: 'image/png, image/jpeg, image/jpg, image/gif',type: 'upload',listType: 'picture-img',action: '128.0.0.1/update/image/cover/',   // 上传地址 + 文件保存上传地址(详见接口描叙)multiple: true,          // 文件多选drag: true,              // 拖拽排序limit: 1,                // 上传数量 1 个//fileSize: 500,         // 上传大小 500 kb内tip: '只能上传 jpg/png/gif 格式的图片',loadText: '上传中...',propsHttp: {res: 'data'},uploadBefore: (file, done) => {// 文件上传前处理done(file)},uploadAfter: (res, done) => {this.$message.success('上传成功');done()},uploadError(error, column) {// 上传失败this.$message.error(error);},uploadExceed(limit, files, fileList, column) {// 文件数量验证this.$message.warning(`当前限制文件数量为 $1, 当前共 ${files.length + fileList.length} `);},},// {//     label: '标签',//     prop: 'lables',//     type: 'array',//     dataType: 'string',//     limit: 10,//     span: 10,//     rules: [{//         required: false,//         message: "请添加 标签集",//         trigger: "blur"//     }]// },{label: '文章描述',prop: 'describe',type: 'textarea',maxlength: 256,showWordLimit: true,span: 10,rules: [{required: true,message: "请输入 文章描述",trigger: "blur"}]},// {//     label: '文章内容 ',//     prop: 'content',//     maxlength: 0,//     showWordLimit: true,//     span: 10,//     rules: [{//         required: true,//         message: "请输入 文章内容 ",//         trigger: "blur"//     }]// },// {//     label: '状态 ',//     prop: 'state',//     type: 'radio',//     //dicData: this.dict.get('ARTICLE_STATE'),//     span: 10,//     rules: [{//         required: true,//         message: "请选择 状态 ",//         trigger: "blur"//     }]// },// {//     label: '访问权限 ',//     prop: 'auth',//     type: 'radio',//     //dicData: this.dict.get('ARTICLE_AUTH'),//     span: 10,//     rules: [{//         required: true,//         message: "请选择 访问权限 ",//         trigger: "blur"//     }]// },// {//     label: '排序',//     prop: 'sort',//     maxlength: 11,//     showWordLimit: true,//     span: 10,//     rules: [{//         required: true,//         message: "请输入 排序",//         trigger: "blur"//     }]// },{label: 'seo: Title',prop: 'seoTitle',maxlength: 128,showWordLimit: true,span: 20,labelWidth: 130,rules: [{required: false,message: "请输入 seo优化字段 Title",trigger: "blur"}]},{label: 'seo: Keyword',prop: 'seoKeyword',maxlength: 256,showWordLimit: true,span: 20,labelWidth: 130,rules: [{required: false,message: "请输入 seo优化字段 Keyword",trigger: "blur"}]},{label: 'seo: Description',prop: 'seoDescription',maxlength: 256,showWordLimit: true,span: 20,labelWidth: 130,rules: [{required: false,message: "请输入 seo优化字段 Description",trigger: "blur"}]}]}],}}},created() {this.obj = this.defaultData;//this.findCategorTree();this.initSuccess = true;},// 实例被挂载后调用mounted: function () {console.log("mounted=实例已被挂载")// 输入内容时禁止拖拽var inputs = document.getElementsByClassName('el-input__inner');for (let item of inputs) {item.addEventListener('blur', event => {console.log("inputting!!111");this.disabledDrag = false;});item.addEventListener('focus', event => {console.log("inputting!!222");this.disabledDrag = true;});}},methods: {emptytChange() {this.closeDialog(false);},submit(form, done) {this.crud.post(this.uri.info, this.obj).then((res) => {console.debug(res);if (res.data.code == 200) {this.closeDialog(true);}done(form);}).catch((err) => {console.error(err);done(form);})},/*** 查询分类数据*/async findCategorTree() {let res = await this.crud.get(this.uri.findCategoryTree);this.categoryTree = res.data.data;},/*** 删除行数据* @param element*/delItemRow(item) {if (this.contentItems.length <= 1) {this.$message.error('最后一条数据不能删除');return}this.contentItems.splice(this.contentItems.indexOf(item), 1)},// 内容区拖动onStart() {this.drag = true;},onEnd() {// this.resetImageSort();this.drag = false;},// 图片区拖动onStartImages() {this.dragImages = true;},onEndImages() {this.dragImages = false;},}});// app.use(AVUE);</script><style scoped>/* 主宽度 */.bt-article-all {width: 96%;padding-left: 2%;}/* 卡片上间距 */.bt-card-box {padding-left: 1%;}/* 卡片默认样式 */.text {font-size: 14px;}.clearfix:before,.clearfix:after {display: table;content: "";}.clearfix:after {clear: both}.box-card {width: 100%;/*padding: 1%;*/padding-top: 1%;}
</style></body>
</html>
  • 个人开源项目(通用后台管理系统)–> https://gitee.com/wslxm/xijia-plus , 喜欢的可以看看

  • 本文到此结束,如果觉得有用,动动小手点赞或关注一下呗,将不定时持续更新更多的内容…,感谢大家的观看!


http://chatgpt.dhexx.cn/article/UrUA3Vbm.shtml

相关文章

空指针、悬空指针、野指针

文章目录 前言一、指针&#xff1f;二、指针的应用场景三、 空指针四、 悬空指针五、 野指针正确用法 总结 前言 相信很多小伙伴对指针的使用都有一定的了解了。但更多的人可能对指针又爱又恨。这次我们谈点重要的&#xff0c;进一步加深对指针的理解 一、指针&#xff1f; 指…

【C语言】指针(野指针)

目录 1&#xff1a;什么是野指针&#xff1f; 2&#xff1a;如何规避野指针 1.1&#xff1a;指针变量的初始化 2.2&#xff1a;指针越界访问 3.3&#xff1a;指针指向的空间如果我们还回去的话&#xff0c;就把指针指针置为NULL 4.4&#xff1a;指针使用之前检查有效性…

C语言的野指针

1.野指针 指针变量中的值是非法的内存地址&#xff0c;进而形成野指针野指针不是NULL指针&#xff0c;是指向不可用内存地址的指针NULL指针并无危害&#xff0c;很好判断&#xff0c;也很好调试C语言中无法判断一个指针所保存的地址是否合法&#xff0c;合法的地址是通过变量或…

初识C语言---野指针

野指针概念&#xff1a; 野指针就是指针指向的位置是不可知的&#xff08;随机的、不正确的、没有明确限制的&#xff09;。 一、野指针成因 1、指针未初始化就使用 #include<stdio.h> int main() {int* p; *p 10; return 0; }此段代码中&#…

野指针(概念,产生原因,危害,避免方法)

思维导图: 1.野指针与垂悬指针的区别: 野指针:访问一个已销毁或者访问受限的内存区域的指针,野指针不能判断是否为NULL来避免 垂悬指针:指针正常初始化,曾指向一个对象,该对象被销毁了,但是指针未制空,那么就成了悬空指针。 2.概念 指针指向了一块随机的空间,不受…

野指针概念、定义、及如何规避野指针

野指针 野指针的概念&#xff1a; 野指针就是指针指向的位置不可知的。&#xff08;随机的、不正确的、没有明确限制的&#xff09; 野指针的三种情况 1、指针未定义 #include <stdio.h> int main() {int* p; //局部变量指针未初始化&#xff0c;默认就是随机值*p10;r…

使用 OKTA 作为 SAML IdP 为 FortiClient配置 SAML SSO 登录

描述 随着用于 FortiGate 和 FortiClient 6.4 的 FortiOS 6.4 的发布,现在可以创建SSL SSO 单点登录解决方案,该解决方案可以集成第三方 SAML SSO 身份提供商 (IdP) 并利用其 MFA 功能。 前置条件 FortiGate 运行 FortiOS 6.4.0 或更高版本FortiClient 6.4.0 或更高版本OK…

SAML 流程讲解

SAML&#xff08;Security Assert Mark Language&#xff09;常用来实现SSO。 本文主要梳理一下SAML的代码逻辑 术语讲解&#xff1a; IDP: Identity provider 在单点登陆中是指统一身份认证平台。 SP&#xff1a;Service Provider 在单点登陆中是指需要被认证的服务方。 A…

SAML单点登录-spring-security-saml 整合使用

本文链接&#xff1a;http://t.csdn.cn/BIGKc SAML单点登录-spring-security-saml客户端SP 使用spring-security-saml搭建SAML协议的客户端&#xff0c;该依赖是spring框架的官方库&#xff0c;配置方便、文档详细。提供了包括单点登录、单点登出、获取sq元数据文件等接口&…

盘点认证协议 : 普及篇之SAML

首先分享之前的所有文章 , 欢迎点赞收藏转发三连下次一定 >>>> &#x1f61c;&#x1f61c;&#x1f61c; 文章合集 : &#x1f381; https://juejin.cn/post/6941642435189538824 Github : &#x1f449; https://github.com/black-ant CASE 备份 : &#x1f449…

SAML2.0 笔记(二)

文章目录 零、示例代码参考一、前言二、共通内容1.1、引入依赖1.2、初始化SAML部分1.2.1、检查JCE环境1.2.2、初始化服务 1.3、拦截器部分1.3.1、构建AuthnRequest1.3.2、AuthRequest解析1.3.3、SP模式选择1.3.4、IDP模式选择 1.4、涉及的工具类1.4.1、OpenSAMLUtils工具类1.4.…

SAML单点登录-spring-security-saml客户端SP

SAML单点登录-spring-security-saml客户端SP 使用spring-security-saml搭建SAML协议的客户端&#xff0c;该依赖是spring框架的官方库&#xff0c;配置方便、文档详细。提供了包括单点登录、单点登出、获取sq元数据文件等接口&#xff0c;无需自己实现&#xff0c;参考&#x…

SAML入门

SAML (Security Assertion Markup Language)入门 提到SAML (Security Assertion Markup Language), 很多人都会联想到单点登录SSO。那么Saml到底是什么&#xff0c;它跟sso到底有什么联系&#xff1f;这里给大家分享一下我在读完了saml差不多全部规范之后的一些心得。希望给sa…

SAML

SAML SAML&#xff08;Security Assertion Markup Language&#xff09;是一个基于XML的开源标准数据格式&#xff0c;它在当事方之间交换身份验证和授权数据&#xff0c;尤其是在身份提供者和服务提供者之间交换。SAML2.0可以实现基于网络跨域的单点登录&#xff08;SSO&…

基于SAML的单点登录介绍

一、背景知识&#xff1a; SAML即安全断言标记语言&#xff0c;英文全称是Security Assertion Markup Language。它是一个基于XML的标准&#xff0c;用于在不同的安全域(security domain)之间交换认证和授权数据。在SAML标准定义了身份提供者(identity provider)和服务提供者(s…

走进SAML——基础篇

SAML的全称是Security Assertion Markup Language。提到SAML&#xff0c;我们主要想到的是其在各种单点登录场景中大行其道。单点登录我们通常叫做SSO&#xff0c;那么SAML到底是如何实现SSO的呢&#xff1f;在这个系列的文章中&#xff0c;我将为大家阐释清楚。不过&#xff0…

深入浅出SAML协议

SAML概述 SAML&#xff08;Security Assertion Markup Language 安全断言标记语言&#xff09;是一个基于XML的开源标准数据格式&#xff0c;为在安全域间交换身份认证和授权数据&#xff0c;尤其是在IDP&#xff08;Identity Provider身份提供方&#xff09;和SP&#xff08;…

SAML2.0 笔记(一)

文章目录 一、前言二、初识概念1、SP & IDP 的概念2、认识元数据2.1 IDP MetaData2.1.1 SingleLogoutService2.1.2 SingleSignOnService 2.2 SP MetaData2.2.1 SingleLogoutService2.2.2 AssertionConsumerService 2.3 通用节点2.3.1 EntityId2.3.2 KeyDescriptor2.3.3 Nam…

【学习笔记】白盒及黑盒测试方法简介

目录 测试用例什么是测试用例测试用例的要素 白盒测试白盒测试的基本介绍白盒测试用例设计方法一、 逻辑覆盖法1.语句覆盖2. 判定覆盖3.条件覆盖4.判定-条件覆盖5.条件组合覆盖6.路径覆盖 二、基本路径测试法总结 黑盒测试分类功能测试性能测试 测试设计方法1.等价类法2.边界值…

白盒测试方法的简单理解(通俗易懂)

白盒测试主要使用逻辑覆盖测试方法&#xff0c;包括语句覆盖、判定覆盖、条件覆盖、判定-条件覆盖、条件组合覆盖、路径覆盖等。 假设逻辑判断流程图如下图所示&#xff0c;我们简单来说说每种白盒测试方法是如何来进行的。 一、语句覆盖 语句覆盖的定义是&#xff1a;程序中…