vue2 implements card drag-and-drop course schedule

Table of contents

1. Effect display

2. Code analysis

2.1. Form writing and course cards

2.2. Initialization data and rendering

2.3. Drag the card to the table and insert it.

2.4. Deletion by custom instructions

3. Js implements drag-and-drop sorting of lists

3.1. Effect display

3.2. Code analysis 

1. Effect display

2. Code analysis

Main page code:

<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>

Card component code:

<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. Form writing and course cards

  Static pages mainly use a two-column layout. The card component is on the left and the table is on the right.

2.2. Initialization data and rendering

  td is a cell, and now there is a 7X7 table in it. If we want to render the card of the specified course, we can use the value in the cellData variable, which is similar to a two-dimensional array. We can find the coordinates and render them.

2.3. Drag the card to the table and insert it.

  Drag effect: draggable="true" means that the box can be dragged , including many events, such as dragstart, dragend, dragover, dragenter, etc.

  Insertion implementation: It seems like dragging the card on the left in, but in fact, the data is changed when entering the table (drag ends), that is, adding an attribute and attribute value to the cellData object to achieve the insertion effect. Copy directly in vue2, the view will not change, you can update the view through $set , this.$set(this.cellData, prop, key);

2.4. Deletion by custom instructions

  In this example,  v-CoursePanel ="{ cellData, n, timeSlot: i }" in the view is used, combined with the CoursePanel instruction in directives to delete cards in the table. Deletion here refers to deleting any card in the table, but not the original card on the left. Therefore, the component combines custom instructions to perform deletion operations.

  Now there is a bug in its deletion. Click x to delete, and you will only see the deleted card effect when you need to drag (add) it again, which needs to be improved. . .

3. Js implements drag-and-drop sorting of lists

3.1. Effect display

3.2. Code analysis 

<!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>

  When the page is initialized, the render() and bindEvent() functions are executed. The former renders a Li list with content and appends it ( x.appendChild() ) to the main box.

  Drag and drop to insert ideas:

In the handleDragOver function: When it is about to reach half the position of the object to be inserted ( e.clientY <= item.offsetTop + item.offsetHeight / 2 ), it inserts itself ( x.insertBefore() ) before an object.

Guess you like

Origin blog.csdn.net/qq_44930306/article/details/131418299