vue2实现卡片拖拽式课程表

目录

一、效果展示

二、代码分析

2.1、表格编写与课程卡片

2.2、初始化数据与渲染

2.3、拖拽卡片到表格,进行插入

2.4、自定义指令进行删除

三、Js实现列表拖拽排序

3.1、效果展示

3.2、代码分析 

一、效果展示

二、代码分析

主页面代码:

<template>
  <div class="board">
    <div class="left">
      <CoursePerson
        v-for="({ name }, k) in datas.courses"
        :key="k"
        :courseName="name"
        :courseKey="k"
        @handleDragEnd="handleDragEnd"
      ></CoursePerson>
    </div>
    <div class="right" @dragover="handleDragOver" @dragenter="handleDragEnter">
      <table border="1">
        <tr>
          <th>时间段 / 星期</th>
          <th v-for="(w, i) in datas.weekday" :key="i">{
   
   { w }}</th>
        </tr>
        <tr v-for="(t, i) in datas.time_slot" :key="i">
          <th>{
   
   { t }}</th>
          <td v-for="n in 7" :key="n" :data-weekday="n" :data-time-slot="i">
            <template v-if="cellData[`${n}-${i}`]">
              <CoursePerson
                :courseName="datas.courses[cellData[`${n}-${i}`]].name"
                :courseKey="cellData[`${n}-${i}`]"
                v-CoursePanel="{ cellData, n, timeSlot: i }"
              ></CoursePerson>
            </template>
          </td>
        </tr>
      </table>
    </div>
  </div>
</template>

<script>
import CoursePerson from "@/components/CoursePerson.vue";
export default {
  components: {
    CoursePerson,
  },
  directives: {
    // 定义名为CoursePanel的指令,指向一个配置对象
    CoursePanel: {
      bind(el, bindings) {
        const oRemoveBtn = el.querySelector(".remove-btn");
        const { cellData, n, timeSlot } = bindings.value;
        oRemoveBtn.addEventListener("click", handleRemovePanel, false);
        function handleRemovePanel() {
          delete cellData[`${n}-${timeSlot}`];
        }
      },
    },
  },
  data() {
    return {
      datas: {
        weekday: [
          "星期一",
          "星期二",
          "星期三",
          "星期四",
          "星期五",
          "星期六",
          "星期日",
        ],
        time_slot: [
          "08:00-08:50",
          "09:00-09:50",
          "10:00-10:50",
          "11:00-11:50",
          "14:00-14:50",
          "15:00-15:50",
          "16:00-16:50",
          "17:00-17:50",
        ],
        courses: {
          chinese: {
            name: "语文",
            teachers: ["张三", "李四"],
          },
          math: {
            name: "数学",
            teachers: ["张二", "李五"],
          },
          english: {
            name: "英语",
            teachers: ["张3", "李2"],
          },
        },
      },
      cellData: {
        "1-1": "chinese",
        "2-5": "math",
        "3-3": "english",
      },
      targetCell: null,
    };
  },
  methods: {
    handleDragOver(e) {
      e.preventDefault();
    },
    handleDragEnter(e) {
      e.preventDefault();
      const tar = e.target;
      const tagName = tar.tagName.toLowerCase();
      this.targetCell = tagName != "td" ? null : tar;
    },
    handleDragEnd(e) {
      if (this.targetCell) {
        const weekday = this.targetCell.dataset.weekday;
        const timeSlot = this.targetCell.dataset.timeSlot;
        const prop = `${weekday}-${timeSlot}`;
        if (!this.cellData[prop]) {
          //给表格中没有坐标的课程
          const key = e.dataset.key;
          this.$set(this.cellData, prop, key); //借助$set赋值,更新视图
          // this.cellData[prop] = key; //赋值(vue2赋值视图不更新)
        }
      }
    },
  },
};
</script>

<style lang='less' scoped>
.board {
  position: relative;
  .left {
    position: absolute;
    top: 0;
    left: 0;
    width: 300px;
    height: 100%;
  }
  .right {
    width: 100%;
    height: 800px;
    padding-left: 300px;
    box-sizing: border-box;
    table {
      width: 100%;
      border-collapse: collapse;
      table-layout: fixed;
      td {
        height: 60px;
        text-align: center;
      }
    }
  }
}
</style>

卡片组件代码:

<template>
  <!-- draggable="true"  该盒子可被拖动 -->
  <div
    class="course-panel"
    draggable="true"
    @dragstart="handleDragStart"
    @dragend="handleDragEnd"
    :data-key="courseKey"
  >
    <h1>{
   
   { courseName }}</h1>
    <span class="remove-btn">x</span>
  </div>
</template>

<script>
export default {
  props: ["courseName", "courseKey"],
  methods: {
    handleDragStart(e) {
      const tar = e.target;
      tar.style.opacity = ".6";
    },
    handleDragEnd(e) {
      const tar = e.target;
      tar.style.opacity = "1";
      this.$emit("handleDragEnd", tar);
    },
  },
};
</script>

<style lang='less' scoped>
.course-panel {
  position: relative;
  width: 100px;
  height: 60px;
  background-color: goldenrod;
  margin: 10px auto;
  padding: 10px;
  box-sizing: border-box;
  cursor: move;
  h1 {
    font-size: 20px;
    text-align: center;
  }
  .remove-btn {
    position: absolute;
    top: 0px;
    right: 10px;
  }
}
</style>

2.1、表格编写与课程卡片

  静态页面主要采用两栏布局。左侧为卡片组件,右侧为表格。

2.2、初始化数据与渲染

  td是单元格,现在里面是一个7X7的表格,想要将指定课程的卡片渲染上去,我们可以借助cellData变量里的值,类似二维数组,我们找到坐标,就可以渲染上去。

2.3、拖拽卡片到表格,进行插入

  拖拽效果:draggable="true"  意味着该盒子可被拖动,包括很多事件,比如:dragstart、dragend、dragover、dragenter等。

  插入实现:看似是将左侧的卡片拖进去,实则是在进入表格时(拖拽结束)进行了数据的改动,就是给cellData这个对象里继续增加一个属性和属性值,达到插入效果。vue2里直接复制,视图不会变化,可以通过$set进行视图更新this.$set(this.cellData, prop, key);

2.4、自定义指令进行删除

  本例中通过视图中  v-CoursePanel="{ cellData, n, timeSlot: i }",结合directives里的CoursePanel指令进行表格内卡片的删除。这里的删除指删除表格内的任意卡片,而不能删除左侧的原始卡片。所以组件结合自定义指令去进行删除操作。

  现在它删除有个bug,点击x删除,需要再次拖拽(添加)时,才会看到删除的卡片效果,尚待完善。。。

三、Js实现列表拖拽排序

3.1、效果展示

3.2、代码分析 

<!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>Document</title>
  <style>
    ul {
      padding: 0;
      margin: 0;
      list-style: none;
    }
    p {
      margin: 0;
    }
    .draggable-list-wrapper {
      width: 500px;
      margin: 50px auto;
      box-shadow: 1px 3px 5px #999;
      background-color: white;
      padding: 10px 30px 30px;
    }
    .draggable-item {
      height: 50px;
      line-height: 50px;
      border: 1px solid skyblue;
      padding: 0 15px;
      margin-top: 20px;
      background-color: aquamarine;
    }
    .dragging {
      opacity: 0;
    }
  </style>
</head>
<body>
  <div class="draggable-list-wrapper"></div>
</body>
<script>
  const listData = ['11111', '22222', '33333', '44444'];
  (() => {
    const oWrapper = document.querySelector('.draggable-list-wrapper')
    const init = () => {
      render();
      bindEvent();
    };
    function bindEvent() {
      const oDraggableList = oWrapper.querySelector('.draggable-list')
      const oDraggableItems = oDraggableList.querySelectorAll('.draggable-item')
      oDraggableList.addEventListener('dragover', handleDragOver, false);
      window.addEventListener('dragover', (e) => e.preventDefault(), false);
      oDraggableList.addEventListener('dragenter', (e) => e.preventDefault(), false);
      window.addEventListener('dragenter', (e) => e.preventDefault(), false);
      oDraggableItems.forEach(item => {
        item.addEventListener('dragstart', handleDragStart, false);
        item.addEventListener('dragend', handleDragEnd, false);
      })
    }
    function handleDragStart() {
      const item = this;
      setTimeout(() => item.classList.add('dragging'), 0)
    }
    function handleDragEnd() {
      const item = this;
      item.classList.remove('dragging')
    }
    function handleDragOver(e) {
      e.preventDefault();
      const oDraggableList = this;
      const oDraggableItem = oDraggableList.querySelector('.dragging')
      const oSibItems = oDraggableList.querySelectorAll('.draggable-item:not(.dragging)')
      // 在拖拽时,自身即将到达要插入对象的一半位置时,就将自身插入到某对象之前
      const oSibItem = [...oSibItems].find(item => e.clientY <= item.offsetTop + item.offsetHeight / 2);
      oDraggableList.insertBefore(oDraggableItem, oSibItem)
    }
    function createList() {
      const oDraggableList = document.createElement('ul')
      oDraggableList.className = 'draggable-list';
      listData.forEach(item => {
        const oItem = document.createElement("li");
        oItem.className = 'draggable-item';
        oItem.draggable = true;
        oItem.innerHTML = `<p>${item}</p>`;
        oDraggableList.appendChild(oItem);
        console.log(oItem, '111', oDraggableList);
      })
      console.log(oDraggableList.outerHTML, '222');
      return oDraggableList;
    }
    function render() {
      const oList = createList();
      oWrapper.appendChild(oList);
    }
    init()
  })();
</script>
</html>

  页面初始化时,执行render()和bindEvent()函数,前者是渲染出一个带有内容的Li列表,将其追加(x.appendChild())在主体盒子上.

  拖拽插入思想:

在handleDragOver函数里:自身即将到达要插入对象的一半位置(e.clientY <= item.offsetTop + item.offsetHeight / 2)时,就将自身插入(x.insertBefore())到某对象之前.

猜你喜欢

转载自blog.csdn.net/qq_44930306/article/details/131418299