vue3.0 dragula 看板功能实现

实现功能
1、列表标题可更改
2、列表也可以拖动
3、卡片可拖动
4、列表可新增
在这里插入图片描述

遇到的问题:
1、dragula 安装导入一直报错, 直接require进来的,css是直接加在了vue文件的最后面
2、v-for 加载ref,导致新增的列表无法让元素拖动
解决方法:

// template
:ref="(el) => setPRef(el, index + 1)"

//方法
let parentRefs = [] as any[];
function setPRef(el: any, index: any) {      
     parentRefs[index] = el;
 }
//dragula 设置参数
 dragula(parentRefs, {
    moves: function (el: any, container: any, handle: any) {
       return handle.classList.contains("handle2");
    },
 })

3.列表拖动时,列表需要交换位置

	.on('drag', function (el:any,container:any) {
        startElement = container;
      }).on('drop', function (el:any, container:any) {
        if(container.children[0].id == el.id)
          startElement.appendChild(container.children[1]);
        else
          startElement.appendChild(container.children[0]);
      })

全部代码

<template>
  <div class="contianerkb">
    <div class="kanban" id="kanban">
      <div class="parentbox" v-for="(item, index) in planList" :key="item"  :ref="(el) => setPRef(el, index + 1)" :id="index">
        <div class="parent handle2" :id="item.corridorId">
          <div class="title handle2">
            <el-input
              :class="isfocus ? 'focusinput handle2' : 'blurinput handle2'"
              @blur="(e) => changeCorridorname(e)"
              @keydown.enter="(e) => changeCorridorname(e)"
              v-model="item.corridorname"
            />
            <el-popover
              popper-class="listpopper"
              placement="right"
              :width="400"
              trigger="click"
            >
              <template #reference>
                <em><i class="el-icon-more handle2"></i></em>
              </template>
              <ul>
                <li @click="item.isAddKB = true">添加卡片</li>
                <li>添加列表</li>
                <li>移动列表</li>
                <li>删除列表</li>
              </ul>
            </el-popover>
          </div>
          <div :ref="(el) => setRef(el, index + 1)" class="drag handle2 drag--from">
            <div
              class="box handle"
              v-for="(sitem, i) in item.children"
              :key="i"
              @click="(e) => editKanban(e, sitem.id, item.corridorname)"
            >
            <el-popover
              popper-class="listpopper"
              placement="right"
              :width="400"
              trigger="click"
            >
              <template #reference>
                <em><i class="el-icon-more boxDeal"></i></em>
              </template>
              <ul>
                <li @click="item.isAddKB = true">完成任务</li>
                <li>重命名</li>
                <li>移动卡片</li>
                <li>复制</li>
                <li>删除</li>
              </ul>
            </el-popover>
              <div class="content handle">{{ sitem.kbTitle }}</div>
              <div class="tagshow handle">
                <span
                  v-for="item in sitem.tagArr"
                  :key="item.name"
                  :style="{background:`${item.background}`}"
                >
                  {{ item.name }}
                </span>
              </div>
            </div>
            <div class="addKanbanBox">
              <el-button
                @click="item.isAddKB = true"
                v-if="!item.isAddKB"
                class="addKanban"
                ></el-button
              >
              <div v-if="item.isAddKB" class="textareabox">
                <el-input
                  type="textarea"
                  :rows="2"
                  placeholder="请输入卡片名称"
                  v-model="item.addKBTitle"
                />
                <span class="corridorAdd" @click="(e) => addKanban(e, index)"
                  >添加</span
                >
                <span class="corridorCancel" @click="item.isAddKB = false"
                  >取消</span
                >
              </div>
            </div>
          </div>
          <div class="handle2">
            <i class="el-icon-rank handle2"></i>
          </div>
        </div>
      </div>
      <div class="parent">
        <div
          class="title"
          style="color: #e8453a"
          v-if="!isAddCorr"
          @click="isAddCorr = true"
        >
          <em style="font-size: 20px"></em> 添加列表
        </div>
        <div v-if="isAddCorr" style="margin-top: 3px">
          <el-input class="focusinput" v-model="corridorname" />
          <span class="corridorAdd" @click="corridorAdd">添加</span>
          <span class="corridorCancel" @click="isAddCorr = false">取消</span>
        </div>
      </div>
    </div>
  </div>
</template>

<script  lang="ts">
import { ElMessage } from "element-plus";
import { defineComponent, ref, Ref, onMounted, reactive, toRefs } from "vue";

export default defineComponent({
  components: { },
  setup() {
    const state = reactive<any>({
      onhold: "计划中",
      isfocus: false,
      isEditKanban: false,
      editContent: null,
      planList: [
        {
          corridorId:"1",
          corridorname: "需求池",
          children: [
            {
              picture:'a',
              kbTitle: "【指数基金】基础数据查看",
              tagArr: [
                { name: "指数基金", background: "#a710f9" },
                { name: "10%", background: "#fe9f9f" },
              ],
              content: "指数基金-数据查看,点击上一步按钮,报错,前端传参交易日期为Invalid date",
              id: "1",
            },
          ],
          isAddKB: false,
          addKBTitle: "",
        },
        {
          corridorId:"2",
          corridorname: "需求分析和方案分析",
          children: [
            {
              kbTitle: "【风控中心】权益ETF单次自动校验完成通知(飞书邮件)",
              tagArr: [
                { name: "风控中心", background: "#32c845" },
                { name: "50%", background: "#32c8bf" },
              ],
              content: "自动校验-参数管理-修改自动校验参数后,与pcf_base_info基础参数不同步(不一致),且没有标红展示",
              id: "2",
            },
          ],
          isAddKB: false,
          addKBTitle: "",
        },
        {
          corridorId:"3",
          corridorname: "开发",
          children: [
            {
              kbTitle: "【开发】",
              tagArr: [],
              content: "22222222222222222",
              id: "3",
            },
          ],
          isAddKB: false,
          addKBTitle: "",
        },
      ],
      refArr: [] as any[],
      corridorname: "",
      isAddCorr: false,
    });
    const dragula = require("dragula");


    const itemRefs = [] as any[];
    function setRef(el: any, index: any) {
      if (el) {
     	setRef[index] = el;
      }
    }

    let parentRefs = [] as any[];
    function setPRef(el: any, index: any) {  
      if (el) {    
     		parentRefs[index] = el;
      }
    }

    // ---------------------------------------点击弹框编辑内容
    function editKanban(e: any, id: any, kanbanName: any) {
      // console.log(id)
      state.editContent = null;
      for (let i = 0; i < state.planList.length; i++) {
        const item = state.planList[i];
        for (let j = 0; j < item.children.length; j++) {
          const sitem = item.children[j];
          if (sitem.id == id) {
            state.editContent = sitem;
            break;
          }
        }
        if (state.editContent) {
          break;
        }
      }
    }
    // initRef(state.planList)
    onMounted(() => {
      dragFun();
    });
    let  startElement;
    let removeChildBtn;
    // ---------------------------------------设置拖拽方法
    function dragFun() {
      dragula(itemRefs, {
        moves: function (el: any, container: any, handle: any) {
          return handle.classList.contains("handle");
        },
      }).on('drop', function (el:any, container:any) {
        removeChildBtn = container.getElementsByClassName('addKanbanBox')[0];
        container.removeChild(removeChildBtn)        
        container.appendChild(removeChildBtn)
      });

      dragula(parentRefs, {
        moves: function (el: any, container: any, handle: any) {
          return handle.classList.contains("handle2");
        },
      })
      .on('drag', function (el:any,container:any) {
        startElement = container;
      }).on('drop', function (el:any, container:any) {
        if(container.children[0].id == el.id)
          startElement.appendChild(container.children[1]);
        else
          startElement.appendChild(container.children[0]);
      });
    }
    // ---------------------------------------更改列表名称
    function changeCorridorname(e: any) {
      let flag = false;
      const repeatList = state.planList.filter(
        (item: any) => item.corridorname == e.srcElement.value
      );
      if (repeatList.length >= 2) {
        flag = true;
        ElMessage.error("名称不能重复,请知悉");
      }
      if (flag) {
        // 回归到原来的名称
        return;
      }

      console.log("调用接口,改变看板的名称");
    }
    // ---------------------------------------新增卡片(看板)
    function addKanban(e: any, index: any) {
      state.planList[index].children.push({
        kbTitle: state.planList[index].addKBTitle,
        content: "",
        id: "4",
        tagArr: [],
      });
      state.planList[index].isAddKB = false;
      console.log("调用新增卡片接口");
    }


    // --------------------------------------- 新增列表
    function corridorAdd() {
      let flag = false;
      const repeatList = state.planList.filter(
        (item: any) => item.corridorname == state.corridorname
      );
      if (repeatList.length >= 1) {
        ElMessage.error("名称不能重复,请知悉");
        flag = true;
      }
      if (!flag) {
        console.log("调用接口,新增列表");
        // 
        const arr = JSON.parse(JSON.stringify(state.planList));
        arr.push({
          corridorname: state.corridorname,
          children: [],
        });
        setTimeout(() => {
          state.planList.push({
            corridorname: state.corridorname,
            children: [],
          });
        }, 100);
      }
    }

    return {
      ...toRefs(state),
      changeCorridorname,
      addKanban,
      editKanban,
      corridorAdd,
      setRef,
      setPRef,
    };
  },
});
</script>

<style  lang="scss" scoped>
.contianerkb {
  width: 100%;
  height: calc(100% - 60px);
  overflow-x: auto;
}
#kanban {
  /deep/ .el-dialog__headerbtn .el-dialog__close {
    color: #fff !important;
    font-size: 18px !important;
  }
  font-family: "element-icons";
  height: calc(100% - 10px);
  padding: 10px;
  background: #232424;
  width: max-content;
  overflow: auto;
  margin-top: 10px;
    :deep(.upload-demo){
      float:right; display: flex; flex-direction: column-reverse;
      width:calc(100% - 53px);
      position:relative;
      .el-upload{
        text-align: left;
      }
      .el-upload-list__item:hover{
        background:#232424 !important ;
      }
      .el-upload-list__item:first-child{
        margin-top:0px !important;
      }
        .el-upload-list__item-name [class^=el-icon]{
          width: 40px;
          height: 40px;
          margin-bottom:10px;
          font-size:40px;vertical-align: middle;
          border-radius: 6px; line-height:40px;
          border: 1px solid rgba(82, 84, 83, 0.6);
        }
    }
  .parentbox{
    height: calc(100% - 10px);
    width: 300px;
    float: left;
    position: relative;
    &:hover{
      background:#1c1d1d;
    }
    border-radius: 6px;
    padding:10px
  }
  .parent {
    height: calc(100%);
    width: 280px;
    margin-right: 10px;
    float: left;
    position: relative;
    .corridorCancel {
      margin-top: 10px;
      color: #cdcbc4;
      font-size: 14px;
      line-height: 30px;
      float: right;
      cursor: pointer;
      margin-right: 15px;
    }
    .corridorAdd {
      margin-top: 10px;
      background: rgba(232, 69, 58, 0.5);
      width: 60px;
      line-height: 30px;
      text-align: center;
      height: 30px;
      color: #fff;
      cursor: pointer;
      font-size: 14px;
      float: right;
      border-radius: 15px;
    }
    .addKanban {
      border-radius: 20px;
      width: 100%;
      background: #313232;
      border: 0px;
      color: #fff;
      font-size: 20px;
      font-weight: bold;
      margin-bottom: 10px;
    }
    .title {
      color: #f0f0f0;
      text-align: left;
      height: 40px;
      line-height: 40px;
      font-size: 14px;
      /deep/ .el-input {
        width: calc(100% - 20px) !important;
      }
      // background:#575a59;
    }
    .drag {
      width: 100%;
      height: calc(100% - 50px);
      overflow: auto;
    }
    /deep/ .el-input__inner {
      border: 0px !important;
      padding: 0px;
      font-size: 16px;
      color: #f0f0f0;
      background: transparent !important;
    }
    /deep/ .el-input__inner:focus {
      background-color: #313232 !important;
      padding: 0px 10px;
      border-radius: 10px !important;
    }
    /deep/ .el-textarea__inner {
      background-color: #313232 !important;
      padding: 10px;
      border-radius: 10px !important;
      border: 0px;
      font-family: "element-icons";
      color: #f0f0f0;
    }
    .focusinput {
      /deep/ .el-input__inner {
        background-color: #313232 !important;
        padding: 0px 10px;
        border-radius: 10px !important;
      }
    }
    .textareabox {
      &::after {
        content: "";
        display: block;
        clear: both;
        margin-bottom: 10px;
      }
    }
  }
  :deep() {
    background: #232424;
    .el-dialog .el-dialog__header {
      padding: 0px 20px;
    }
    .el-dialog .el-dialog__body {
      padding: 20px;
    }
  }

  ::-webkit-scrollbar {
    width: 8px;
    height: 8px;
  }
  ::-webkit-scrollbar-thumb {
    background: rgba(0, 0, 0, 0.1);
    border-radius: 6px;
    box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.1);
  }
}
.drag--from {
  // background-color: #434544;
  float: left;
  margin-right: 20px;
}

.drag--to {
  // background-color: #434544;
  float: left;
}

.listpopper{
    ul li{
      margin-top:10px;color:#CDCBC4;cursor:pointer;
      &:hover{
        color:#E8453A
      }
    }
}

.box {
  width: 100%;
  padding: 10px;
  background-color: #313232;
  border-radius: 15px;
  box-sizing: border-box;
  margin-bottom: 10px;
  box-shadow: 2px 2px 6px 0px rgba(0, 0, 0, 0.1);
  color: #cdcbc4;
  position:relative;
  .boxDeal{
    display: none;
    position:absolute;
    top:10px; right:10px;
    width:20px; height:20px; border-radius: 50%; text-align: center; line-height: 20px; background: #434544;
  }
  &:hover{
    .boxDeal{
      display: inline-block;
    }
  }
  .tagshow {
    margin-top: 10px;
    &::after {
      content: "";
      display: block;
      clear: both;
    }
    span {
      padding: 0px 15px;
      font-size: 14px;
      border-radius: 12px;
      color: #fff;
      margin-right: 10px;
      margin-bottom: 5px;
      height: 24px;
      float: left;
      line-height: 24px;
      &.fengkong {
        background: #32c845;
      }
      &.zhishu {
        background: #bc1bfa;
      }
      &.ten {
        background: #fa9d1b;
      }
      &.five {
        background: #c83232;
      }
    }
  }
  .sub-titile {
    color: coral;
    font-size: 14px;
  }
  .content {
    font-size: 14px;
    margin-top: 10px;
  }
}
.gu-mirror {
  position: fixed !important;
  margin: 0 !important;
  z-index: 9999 !important;
  opacity: 0.8;
  transform: rotate(5deg);
  -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=80)";
  filter: alpha(opacity=80);
}
.gu-hide {
  display: none !important;
}
.gu-unselectable {
  -webkit-user-select: none !important;
  -moz-user-select: none !important;
  -ms-user-select: none !important;
  user-select: none !important;
}
.gu-transit {
  opacity: 0.2;
  transform: rotate(5deg);
  -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=20)";
  filter: alpha(opacity=20);
}
</style>

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值