vue2设置拖拽选中时间区域

发布于:2024-12-23 ⋅ 阅读:(11) ⋅ 点赞:(0)

代码

<template>
  <div>
    <div class="time-contain">
      <div
        class="time-con-td"
        style="
          display: flex;
          justify-content: space-between;
          border: 1px solid #eaeef1;
          width: 100%;
        "
      >
        <div class="time-con-th-lef">
          <span class="selectsty">已选</span>
          <span class="noselectsty">未选</span>
        </div>
        <div class="time-con-th-rig">
          <el-button type="primary" @click="selectInvert">反选</el-button>
          <el-button @click="clearScheduleTime">清空</el-button>
        </div>
      </div>
    </div>
    <table
      @mousedown="startDrag"
      @mousemove="dragging"
      @mouseup="endDrag"
      @mouseleave="endDrag"
      class="time-contain"
    >
      <thead>
        <tr>
          <th>周/时间</th>
          <th colspan="48">
            <div class="time-con-th">
              <span class="time-con-td">00:00 - 06:00</span>
              <span class="time-con-td"> 06:00 - 12:00</span>
            </div>
            <div
              class="time-con-th"
              v-for="(week, i) in dayparting"
              v-if="i == 0"
              :key="i"
            >
              <span
                class="time-con-td"
                style="width: 37px"
                v-if="j < 24"
                v-for="(day, j) in week.value"
                :key="j"
                >{{ j }}</span
              >
            </div>
          </th>
        </tr>
      </thead>
      <tbody>
        <tr v-for="(row, rowIndex) in dayparting" :key="rowIndex">
          <td>
            <el-checkbox
              v-model="row.checked"
              @change="selectCurrentLine(rowIndex)"
            ></el-checkbox
            >{{ dayNames[rowIndex] }}
          </td>
          <td
            v-for="(cell, cellIndex) in row.value"
            :key="cellIndex"
            :class="{ selected: cell === 1 }"
          >
          </td>
        </tr>
      </tbody>
    </table>
    <div class="time-contain">
      <div class="time-con-info">
        <div class="d1">已选择时间段:</div>
        <div class="time-con-result">
          <div v-for="(week, i) in dayparting" :key="i">
            <span style="display: none">{{ week }}</span>
            <div class="time-con-result-item" v-if="week.checked">
              <span
                v-if="week.value.indexOf(1) > -1"
                class="time-con-result-title"
                >{{ dayNames[i] }}</span
              >
              <div>
                <span
                  v-for="(slot, idx) in mergeTimeSlots(week.value)"
                  :key="idx"
                  class="time-con-result-time"
                >
                  {{ formatTime(slot, week.value.length === 48) }}
                  <span v-if="idx < mergeTimeSlots(week.value).length - 1"
                    >、</span
                  >
                </span>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      dayNames: ["周一", "周二", "周三", "周四", "周五", "周六", "周日"],
      dayparting: {
        //快手24 头条48
        0: {
          value: [
            1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
            1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
            1, 1,
          ],
          checked: true,
        },
        1: {
          value: [
            1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
            1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
            1, 1,
          ],
          checked: true,
        },
        2: {
          value: [
            1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
            1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
            1, 1,
          ],
          checked: true,
        },
        3: {
          value: [
            1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
            1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
            1, 1,
          ],
          checked: true,
        },
        4: {
          value: [
            1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
            1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
            1, 1,
          ],
          checked: true,
        },
        5: {
          value: [
            1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
            1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
            1, 1,
          ],
          checked: true,
        },
        6: {
          value: [
            1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
            1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
            1, 1,
          ],
          checked: true,
        },
      },
      isDragging: false,
      dragStart: null,
      dragEnd: null,
      lastAction: null, // Track the last action for toggle on click
    };
  },
  methods: {
    toggleSelect(rowIndex, cellIndex) {
      const newValue = this.dayparting[rowIndex].value[cellIndex] == 0 ? 1 : 0;
      this.$set(this.dayparting[rowIndex].value, cellIndex, newValue);
      this.$forceUpdate();
      this.lastAction = newValue; // Store the last action for potential drag operations
      this.$forceUpdate();
    },
    startDrag(event) {
      if (event.buttons !== 1) return; // Only left click
      this.isDragging = true;
      const [rowIndex, cellIndex] = this.getCellIndex(event.target);
      this.dragStart = { rowIndex, cellIndex };
      this.dragEnd = { rowIndex, cellIndex };
      this.toggleSelect(rowIndex, cellIndex); // Click to start selection or deselection
      this.$forceUpdate();
    },
    dragging(event) {
      if (!this.isDragging) return;
      const [rowIndex, cellIndex] = this.getCellIndex(event.target);
      if (rowIndex !== null && cellIndex !== null) {
        this.dragEnd = { rowIndex, cellIndex };
        this.selectOrDeselectCells();
      }
      this.$forceUpdate();
    },
    endDrag() {
      this.isDragging = false;
    },
    selectOrDeselectCells() {
      console.log("000");
      if (!this.dragStart || !this.dragEnd) return;
      const startRow = Math.min(this.dragStart.rowIndex, this.dragEnd.rowIndex);
      const endRow = Math.max(this.dragStart.rowIndex, this.dragEnd.rowIndex);
      const startCell = Math.min(
        this.dragStart.cellIndex,
        this.dragEnd.cellIndex
      );
      const endCell = Math.max(
        this.dragStart.cellIndex,
        this.dragEnd.cellIndex
      );

      for (let r = startRow; r <= endRow; r++) {
        for (let c = startCell; c <= endCell; c++) {
          this.$set(this.dayparting[r].value, c, this.lastAction);
        }
      }
      this.$forceUpdate();
    },
    getCellIndex(target) {
      if (!target.closest("td")) return [null, null];
      const tr = target.closest("tr");
      const td = target.closest("td");
      const row = Array.from(tr.parentElement.children).indexOf(tr);
      const cell = Array.from(tr.children).indexOf(td) - 1; // 减去星期列
      this.$forceUpdate();
      return [row, cell >= 0 ? cell : null];
    },
    clearScheduleTime() {
      Object.keys(this.dayparting).forEach((key) => {
        console.log(key);
        this.dayparting[key].value.fill(0);
        console.log(this.dayparting);
        this.dayparting[key].checked = false;
      });
      this.$forceUpdate();
    },
    selectInvert() {
      Object.keys(this.dayparting).forEach((key) => {
        this.dayparting[key].value = this.dayparting[key].value.map((value) =>
          value === 1 ? 0 : 1
        );
        this.dayparting[key].checked = !this.dayparting[key].checked;
      });
      this.$forceUpdate();
    },
    selectCurrentLine(i) {
      if (this.dayparting[i].checked) {
        let oval = this.dayparting[i].value.map(() => 1);
        this.$set(this.dayparting, i, {
          ...this.dayparting[i],
          value: oval,
        });
      } else {
        let oval = this.dayparting[i].value.map(() => 0);
        this.$set(this.dayparting, i, {
          ...this.dayparting[i],
          value: oval,
        });
      }
    },
    mergeTimeSlots(week) {
      let slots = [];
      let start = null;
      for (let i = 0; i < week.length; i++) {
        if (week[i] === 1) {
          if (start === null) {
            start = i;
          }
        } else {
          if (start !== null) {
            // 记录结束时间是前一个时间段的结束
            slots.push({ start, end: i - 1 });
            start = null;
          }
        }
      }
      // 如果结束时仍有开始时间,说明有一个未结束的时间段
      if (start !== null) {
        slots.push({ start, end: week.length - 1 });
      }
      return slots;
    },
    formatTime(slot, is48HourFormat) {
      let startHour = Math.floor(slot.start / (is48HourFormat ? 2 : 1));
      let startMinute = (slot.start % (is48HourFormat ? 2 : 1)) * 30;
      let endHour = Math.floor((slot.end + 1) / (is48HourFormat ? 2 : 1)); // +1 to include the end of the slot
      let endMinute = ((slot.end + 1) % (is48HourFormat ? 2 : 1)) * 30;
      return `${startHour < 10 ? "0" + startHour : startHour}:${
        startMinute === 0 ? "00" : "30"
      } ~ ${endHour < 10 ? "0" + endHour : endHour}:${
        endMinute === 0 ? "00" : "30"
      }`;
    },
  },
};
</script>

<style scoped>
.selected {
  background-color: #1661f0;
}
table {
  border-collapse: collapse;
  width: 100%;
}
thead th {
  padding: 0;
}
th,
td {
  border: 1px solid #ddd;
  padding: 15px 9px;
  text-align: center;
}

th {
  background-color: #f2f2f2;
}
.time-con-th {
  width: 100%;
  height: 50px;
  display: -webkit-box;
  display: -ms-flexbox;
  display: flex;
  -webkit-box-orient: horizontal;
  -webkit-box-direction: normal;
  -ms-flex-direction: row;
  flex-direction: row;
  -ms-flex-wrap: nowrap;
  flex-wrap: nowrap;
}
.time-con-th span {
  display: inline-block;
  flex: 1;
  height: 50px;
  line-height: 50px;
  border-right: 1px solid #eaeef1;
  border-bottom: 1px solid #eaeef1;
  background: #ffffff;
  cursor: pointer;
  text-align: center;
}
.time-con-info {
  padding: 10px 20px;
  box-sizing: border-box;
  width: 900px;
  display: flex;
  border-right: 1px solid #eaeef1;
  border-bottom: 1px solid #eaeef1;
}
.time-con-info .d1 {
  height: 34px;
  line-height: 34px;
  font-size: 14px;
}
.time-con-info span {
  font-size: 14px;
}
.time-contain .time-con-result {
  display: flex;
  flex-wrap: wrap;
  flex: 1;
}
.time-contain .time-con-result-item {
  margin: 5px;
  padding: 0 10px;
  display: flex;
  background: #e6f1fc;
  line-height: 34px;
  color: #1661f0;
  border: 1px solid #1661f0;
  border-radius: 5px;
}
.time-contain .time-con-result-time {
  color: #1661f0;
}
.time-contain .time-con-td {
  display: inline-block;
  width: 15px;
  height: 50px;
  line-height: 50px;
  border-right: 1px solid #eaeef1;
  border-bottom: 1px solid #eaeef1;
  background: #ffffff;
  cursor: pointer;
  text-align: center;
}
.time-contain .time-con-td-text {
  display: inline-block;
  width: 80px;
  height: 50px;
  line-height: 50px;
  border-right: 1px solid #eaeef1;
  border-bottom: 1px solid #eaeef1;
  background: #ffffff;
  text-align: center;
  user-select: none;
}
.time-con-td-active {
  background: #0080f9 !important;
}
.template-mod-normal-info {
  margin: -10px 0 22px 200px;
}
.detele-info {
  position: absolute;
  top: 14px;
  right: 20px;
  cursor: pointer;
  color: #0080f9;
}
.selectsty {
  margin: 0 25px;
}
.selectsty::before {
  content: "";
  width: 20px;
  height: 10px;
  background: #0080f9;
  border-radius: 5px 5px 5px 5px;
  display: inline-block;
  margin: 0 5px;
}
.noselectsty::before {
  content: "";
  width: 20px;
  height: 10px;
  background: #fff;
  border: 1px solid #dcdfe6;
  border-radius: 5px 5px 5px 5px;
  display: inline-block;
  margin: 0 5px;
}
.time-con-th-lef,
.time-con-th-rig {
  margin-right: 20px;
  display: flex;
}
.time-con-th-rig {
  align-items: center;
}
</style>

效果
在这里插入图片描述