vue2 使用 Sortable 库进行拖拽操作_vue sortable(1)

本文介绍了如何在Vue2项目中利用Sortable库实现拖拽功能。通过展示图和源码,详细解释了从npm版转化为UMD版的过程,并提供了简单的HTML代码示例,帮助读者快速理解并实现相同的效果。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  • 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-image
                                                            style="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,
最后
--

**[开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】](https://bbs.youkuaiyun.com/topics/618166371)**

> 最后写上我自己一直喜欢的一句名言:`世界上只有一种真正的英雄主义就是在认清生活真相之后仍然热爱它`
> ![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6L3lnVUFXMUlsN2FRMlNlT2VueEF1QTMxNDlEQmRDMlU0bzBpY21pYVloa2szeXVFZmNydGV4MGYxSWljdFJGNU9RYUw5TWJHb0hiTmtWQlBQUGliTng0RWJpY0EvNjQw?x-oss-process=image/format,png)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值