基于vue cli3实现甘特图拖拽

因为工作中要用到甘特图,所以我在网上搜索可以用的甘特图,搜索了好多,但是网上搜到大多数都很鸡肋,不能直接使用,最后我在github上搜索到一个相对成熟的甘特图。附上链接https://github.com/w1301625107/Vue-Gantt-chart
刚开始我看不懂这个博主写的代码,于是想自己写,但是尝试了几天,最后还是以失败告终。没办法活是我自己的,总得想办法弄出来啊,所以我硬着头皮改造代码。先附上博主的甘特图,我感觉这是我见过颜值最高的甘特图了。
,
因为需求要求做到可拖拽,以下是我实现拖拽的过程。
在这里插入图片描述
JS部分
首先介绍第一个mousedown,当我按下鼠标,我会记录当前块的索引。draggable设为可拖拽。
在这里插入图片描述
接着介绍drop,也就是移动到该行触发的事件,这里我用val传参(拖动到指定行的数据)和所有数据对比较,获取该行的索引,接着我会判断拖动的块与该行的块是否冲突。

附上源码:

<template>
  <div class="gantt-blocks"
       :style="{height:blockHeight+'px'}">
    <div class="gantt-block gantt-block-top-space"
         :style="{height:topSpace+'px'}">
    </div>
    <div class="gantt-block"
         :style="blockStyle"
         v-for="(data,index) in showDatas"
         @drop="drop($event,data,index)"
         @dragover.prevent
         @click="clickRow(index)"
         :class="[index === rowIndex ? 'rowColor':'']"
         :key="dataKey?data[dataKey]:index">
<!--      整个数据列表-->
      <template v-if="!customGenerateBlocks">
<!---->
        <div class="gantt-block-item block"
             @mousedown="mousedown(data,item,index)"
             draggable="true"
             @contextmenu="showMenu(data,item,index)"
             v-for="(item,index) in concatArray(data)"
             v-show="isInRenderingTimeRange(item.start)||isInRenderingTimeRange(item.end)"
             :key="itemKey?item[itemKey]:index"
             :class="[blockState && (item === data.gtArray[operationIndex]) ? groundFloor : superstratum]"
             :style="{left:getPosition(item)+'px',width:getWidth(item)+'px'}">
          <slot :data="data"
                :item="item">
            <div class="gantt-block-defaultBlock">need slot</div>
          </slot>
        </div>
      </template>

      <template v-else>
        <slot :data="data"
              :getPositonOffset="getPositonOffset"
              :getWidthAbout2Times="getWidthAbout2Times"
              :isInRenderingTimeRange="isInRenderingTimeRange">need slot</slot>
      </template>
    </div>
  </div>
</template>

<script>
import dayjs from "dayjs";
import dr from "../dynamic-render.js";
import { isUndef, warn } from "../../utils/tool.js";

export default {
  name: "Blocks",
  mixins: [dr],
  data () {
    return {
      handleIndex:0,//行角标
      handleItem:{},
      handleData:{},//当前行数据
      dataCorner:0,//当前行角标
      dialogVisible: false,
      content: {},
      blockIndex: -1,//行内航班所在行角标
      operationIndex:null,
      blockState:false,
      groundFloor:'groundFloor',
      superstratum:'superstratum'
    }
  },
  props: {
    dataKey: String,
    itemKey: String,
    arrayKeys: {
      type: Array
    },
    scrollLeft: Number,
    cellWidth: {
      type: Number,
      required: true
    },
    scale: {
      type: Number,
      required: true
    },
    widthOfRenderAera: {
      type: Number,
      required: true
    },
    endTimeOfRenderArea: [Number, null],
    startTimeOfRenderArea: [Number, null],
    getPositonOffset: Function,
    getWidthAbout2Times: Function,
    customGenerateBlocks: Boolean,
    editStart: {
      type:Date
    },
    editEnd: {
      type:Date
    },
    state:{
      type:Boolean
    },
    uninstall:{
      type:Boolean
    },
    rowIndex: {
      type:Number
    }
  },
  computed: {
    renderAarrys() {
      let { arrayKeys } = this;
      if (arrayKeys.length > 0) {
        return arrayKeys;
      }
      return ["gtArray"];
    },
    blockStyle() {
      return {
        backgroundSize: `${this.cellWidth}px ${this.cellHeight}px`,
        height: `${this.cellHeight}px`
      };
    }
  },
    watch:{
      state () {
        if(this.state){
          if((Number(dayjs(this.editStart).valueOf())>=Number(this.showDatas[this.dataCorner].gtArray[this.blockIndex-1].end)) &&
            (Number(dayjs(this.editEnd).valueOf())<=Number(this.showDatas[this.dataCorner].gtArray[this.blockIndex+1].start))) {
            this.showDatas[this.dataCorner].gtArray[this.blockIndex].start = dayjs(this.editStart).valueOf()
            this.showDatas[this.dataCorner].gtArray[this.blockIndex].end = dayjs(this.editEnd).valueOf()
          } else {
            this.$emit('creatDialog', true)
          }
        }
      },
      uninstall () {
        if (this.uninstall) {
          this.showDatas[0].gtArray.push(this.handleItem)
          this.showDatas[this.dataCorner].gtArray.splice(this.handleIndex, 1)
          let arr = []
          arr = this.showDatas[0].gtArray
          this.$emit('changeUninstall', false, arr)
        }
        this.$emit('detailsItem', this.handleItem)
      }
    },
  methods: {
    /**
     * 根据renderAarrys拼接需要渲染的数组
     *
     * @param {*} data
     * @returns {[]} 该data中所有需要渲染的数据
     */
    concatArray(data) {
      return this.renderAarrys.reduce((prev, curr) => {
        if (Array.isArray(data[curr])) {
          return prev.concat(data[curr]);
        } else {
          return prev;
        }
      }, []);
    },
    /**
     * 判定数据是否在渲染的时间范围内
     *
     * @param {{time:string}} item
     * @returns {boolean} 该
     */
    isInRenderingTimeRange(time) {
      if (this.heightOfRenderAera === 0) {
        return false;
      }

      let { startTimeOfRenderArea, endTimeOfRenderArea } = this;
      if (isUndef(startTimeOfRenderArea) || isUndef(endTimeOfRenderArea)) {
        return false;
      }
      let timeToMs = new Date(time).getTime();
      if (startTimeOfRenderArea <= timeToMs || timeToMs >= endTimeOfRenderArea) {
        return true;
      }
      return false;
    },
    /**
     * 计算时间块长度
     *
     * @param {{start:string,end:string}} block
     * @returns {number}
     */
    getWidth(block) {
      if (isUndef(block.start) || isUndef(block.end)) {
        // warn(`错误,该数据项不含start值 与 end 值 ${JSON.stringify(block)},无法计算宽度值。`)
        return 0;
      }

      return this.getWidthAbout2Times(block.start, block.end);
    },
    /**
     * 计算时间块偏移
     *
     * @param {{start:string}} block
     * @returns {number}
     */
    getPosition(block) {
      if (isUndef(block.start)) {
        warn(
          `错误,该数据项不含start 值 ${JSON.stringify(
            block
          )},无法计算偏移值。`
        );
        return 0;
      }
      return this.getPositonOffset(block.start);
    },
    mousedown (data,item, index) {
      this.$emit('showTime', true)
      this.handleItem = item
      this.handleData = data
      this.blockIndex = index
      for(let i = 0;i<this.showDatas.length;i++) {
        if(this.showDatas[i] === data){
          this.dataCorner = i
        }
      }
      this.$emit('nowRow', this.dataCorner)
    },
    drop (event) {
      console.log('event.y', event,Math.ceil((event.y-100)/this.cellHeight-1)-1)
      var endy=Math.ceil((event.y-100)/this.cellHeight-1)-1;
      let data = this.showDatas[endy].gtArray
      //判断是否重叠(不重叠)
      let blockStart = Number(this.handleItem.start)
      let blockEnd = Number(this.handleItem.end)
      let x = 0
      if (Number(endy) === 0) {
        this.$emit('creatState', true)
      } else {
        for(let i=0; i < data.length; i++) {
          if (((blockEnd >= Number(data[i].end)) && (blockStart >= Number(data[i].end))) ||
            ((blockEnd <= Number(data[i].start)) && (blockStart <= Number(data[i].start)))) {
            ++x
          }
        }
        if(x === data.length) {
          data.push(this.handleItem)
          this.showDatas[this.dataCorner].gtArray.splice(this.blockIndex, 1)
          this.$emit('nowRow', endy)
          console.log('this.dataCorner', this.dataCorner, 'this.handleIndex', this.handleIndex)
        } else {
          this.$emit('creatDialog', true)
        }
      }
    },
    newData () {
      console.log('newdata!',this.dataCorner,this.blockState)
      this.operationIndex = this.blockIndex
      this.blockState = true
    },
    clickRow (data) {
      this.$emit('nowRow', data)
    }
  }
};
</script>

<style lang="scss">
    .block{
        cursor: move;
        position: fixed;
    }
    .groundFloor{
        z-index: 0;
        position:absolute;
    }
    .superstratum{
        z-index: 1;
        position:absolute;
    }
    .rowColor{
        background-color: #edf3ef;
    }
    .creatColor{
        background-color: #dfe5e1 !important;
    }
</style>

以上就是我的分享,如果有什么问题,欢迎大家留言。
最后附上我上传的源码,如有需要可自行下载,链接: 甘特图链接.

Logo

前往低代码交流专区

更多推荐