用vue实现的一个自定义甘特图画法

用vue开发一个甘特图,实现了添加,删除,托拉拽的效果,数据的更新没有做,码友可以自行补充。效果如图:

在这里插入图片描述

删除

<template>
  <div class="main">
    <div class="block">
      <el-form :inline="true" :model="formInline" class="form-inline">
        <el-form-item label="请选择颜色">
          <el-color-picker v-model="formInline.bg"></el-color-picker>
        </el-form-item>
        <el-form-item label="流程名">
          <el-input v-model="formInline.content"></el-input>
        </el-form-item>
        <el-form-item label="开始时间">
          <el-input v-model="formInline.start"></el-input>
        </el-form-item>
        <el-form-item label="结束时间">
          <el-input v-model="formInline.end"></el-input>
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="addTask">生成流程</el-button>
        </el-form-item>
      </el-form>
    </div>
    <div class="gantt-chart border-box" ref="ganttChart" id="ganttChart">
      <div class="big-range-time">
        <div
          v-for="item in time.bigtime"
          :key="item.time"
          class="big-time-item"
          :style="{ width: 31 * width + 'px' }"
          :title="item.time"
        >
          {{ item.time }}
        </div>
      </div>
      <div class="small-range-time">
        <div
          v-for="(item, index) in time.smalltime"
          :key="index"
          class="small-time-item"
          :title="item"
          :style="{ height: height + 'px', width: width + 'px' }"
        >
          {{ item + 1 }}
        </div>
      </div>
      <div class="task-background" v-if="time.smalltime.length == 0">
        <div>暂无数据</div>
      </div>
      <div class="task-background" v-else>
        <div
          v-for="(item, index) in gantData.taskitem"
          :key="index"
          class="task-block"
          :style="{ height: height + 'px', width: width + 'px' }"
        >
          <div
            class="task-time-block"
            v-for="item in time.smalltime"
            :key="item"
            :style="{ height: height + 'px', width: width + 'px' }"
          ></div>
        </div>
      </div>
      <div class="task-detail" id="task" ref="task"   :style="{ top: height*2 + 'px'}">
        <div
          :draggable="draggable"
          class="drag-item"
          v-for="(item, index) in taskList"
          @dragstart="dragstart($event, index)"
          @dragenter="dragenter($event, index)"
          @dragover="dragover($event, index)"
          @dragend="dragend($event, index)"
          :key="item.id"
          :style="{ height: height + 'px', width: width * 31 + 'px' }"
        >
          <div
            class="task-item"
            :id="item.id"
            :title="item.title"
            :style="{
              width: item.width + 'px',
              top: item.top + 'px',
              left: item.left + 'px',
              background: item.background,
              height: height + 'px',
            }"
          >
            {{ item.content }}
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  data () {
    return {
      height: 40, // 最小时间单位的高度
      width: 40, // 最小时间单位宽度,大于30
      time: {
        bigtime: [{ time: '2022-07' }],
        smalltime: [],
        bigtimetotal: 1,
        smalltimetotal: 0
      },
      formInline: {
        bg: '#409EFF',
        content: '',
        start: '',
        end: ''
      },
      task: {
        top: '',
        bottom: '',
        left: '',
        right: ''
      },
      gantData: {
        tasktotal: 5,
        taskitem: [
          { start: 12, end: 16.5, content: 'div0', bg: 'lightblue' },
          { start: 7, end: 30, content: 'div1', bg: 'lightblue' },
          { start: 30, end: 31, content: 'div2', bg: 'lightblue' },
          { start: 1, end: 5, content: 'div3', bg: 'lightblue' },
          { start: 1, end: 8, content: 'div4', bg: 'lightblue' }
        ]
      },
      taskList: [],
      dragIdx: '', // 当前拖动元素的位置
      objIdx: '', // 目标位置
      draggable: true // 判断是否可以上下拖动
    }
  },

  methods: {
    // 添加流程
    addTask () {
      this.gantData.taskitem.unshift(this.formInline)
      this.getTask()
    },
    dragstart (e, index) {
      e.stopPropagation()
      this.dragIdx = index
    },

    dragenter (e, index) {
      e.preventDefault()
      this.objIdx = index
    },
    dragend (e, index) {
      e.preventDefault()
      if (this.dragIdx !== this.objIdx) {
        const node = this.gantData.taskitem[this.dragIdx]
        this.gantData.taskitem.splice(this.dragIdx, 1)
        this.gantData.taskitem.splice(this.objIdx, 0, node)
        this.dragIndex = this.objIdx
        this.getTask()
      }
    },
    dragover (e, index) {
      e.preventDefault()
    },
    bindEvent () {
      this.$nextTick(() => {
        this.taskList.forEach((e, index) => {
          const dObj = document.getElementById(e.id)
          const _that = this
          if (dObj) {
            dObj.addEventListener('mousemove', (e) => {
              let stepX
              let temppointX
              let tempwidth
              let isMouseDown
              let stretch
              let tempLeft
              // 检测是否在进度条边界
              // 拉伸
              if (Math.abs(e.offsetX - dObj.offsetWidth) < 10) {
                dObj.style.cursor = 'e-resize'
                dObj.onmousedown = (e) => {
                  _that.draggable = false
                  temppointX = e.clientX
                  tempwidth = dObj.offsetWidth
                  stretch = true
                  isMouseDown = false
                  document.addEventListener('mousemove', (e) => {
                    if (stretch && dObj.offsetWidth + dObj.offsetLeft <= 31 * _that.width && dObj.offsetWidth >= 30) {
                      const stepX = e.clientX - temppointX
                      dObj.style.width = tempwidth + stepX + 'px'
                    }
                    if (dObj.offsetWidth + dObj.offsetLeft > 31 * _that.width) {
                      dObj.style.width = 31 * _that.width - dObj.offsetLeft + 'px'
                    }
                  })
                }
                document.addEventListener('mouseup', isOk)
              } else {
                // 拖曳
                dObj.style.cursor = 'move'
                dObj.onmousedown = (e) => {
                  _that.draggable = false
                  temppointX = e.clientX
                  tempLeft = dObj.offsetLeft
                  isMouseDown = true
                  stretch = false
                  dObj.addEventListener('mousemove', (e) => {
                    if (isMouseDown && dObj.offsetWidth + dObj.offsetLeft <= 31 * _that.width && dObj.offsetLeft >= 0) {
                      stepX = e.clientX - temppointX
                      dObj.style.left = tempLeft + stepX + 'px'
                    }
                    if (dObj.offsetWidth + dObj.offsetLeft > 31 * _that.width) {
                      dObj.style.left = 31 * _that.width - dObj.offsetWidth + 'px'
                    }
                    if (dObj.offsetLeft < 0) {
                      dObj.style.left = 0 + 'px'
                    }
                    document.onmouseup = isOk
                  })
                }
              }

              function isOk () {
                document.onmousemove = null
                document.onmousedown = null
                isMouseDown = false
                stretch = false
                dObj.style.cursor = 'move'
                _that.draggable = true
              }
            })

            // 删除
            dObj.oncontextmenu = (e) => {
              e.preventDefault()
              this.$confirm(`此操作将删除${dObj.innerText}, 是否继续?`, '提示', {
                confirmButtonText: '确定',
                cancelButtonText: '取消',
                type: 'warning'
              })
                .then(() => {
                  this.taskList.splice(index, 1)
                  this.taskList.forEach((e) => {
                    if (e.id > dObj.id) {
                      e.top = e.top - this.height
                    }
                  })
                  this.gantData.taskitem.splice(index, 1)
                  this.$message({
                    type: 'success',
                    message: '删除成功!'
                  })
                })
                .catch(() => {})
            }
          }
        })
      })
    },

    getTask () {
      this.taskList = []
      this.gantData.taskitem.forEach((e, index) => {
        const dObj = {}
        dObj.left = (e.start - 1) * this.width
        dObj.top = index * this.height
        dObj.content = e.content
        dObj.title = e.content
        dObj.width = (e.end - e.start + 1) * this.width
        dObj.id = 'task-item-' + index
        dObj.background = e.bg
        this.taskList.push(dObj)
      })
      this.bindEvent()
    },
    init () {
      for (let i = 0; i < 31; i++) {
        this.time.smalltime.push(i)
      }
      this.time.smalltimetotal = this.time.smalltime.length
      const taskDom = document.getElementById('task')
      this.task.top = taskDom.offsetTop
      this.task.bottom = taskDom.offsetTop + this.width * this.gantData.taskitem.length
      this.task.left = taskDom.offsetLeft
      this.task.right = taskDom.offsetLeft + this.time.smalltimetotal * this.width
      this.getTask()
    }
  },
  created () {},
  mounted () {
    this.init()
  }
}
</script>

<style scoped lang="scss">
.main {
  margin: 0 auto;
  width: 1200px;
  .block {
    display: flex;
    height: 100px;
    align-items: center;
  }
  .gantt-chart {
    overflow-x: scroll;
    position: relative;
    border: 2px solid rgba(0, 0, 0, .1);
    border-radius: 8px;
    box-shadow: 5px 5px 10px 5px  rgba(0, 0, 0, .1);
    .task-background {
      .task-block {
        display: flex;
        .task-time-block {
          flex: 0 0 auto;
          box-sizing: border-box;
          border: 1px solid lightgray;
        }
      }
    }
    .big-range-time {
      background: lightcyan;
      height: 40px;
      display: inline-flex;
      .big-time-item {
        user-select: none;
        display: flex;
        justify-content: center;
        align-items: center;
        box-sizing: border-box;
        border: 1px solid lightgray;
      }
    }
    .small-range-time {
      display: inline-flex;
      .small-time-item {
        user-select: none;
        background: lightgreen;
        display: flex;
        justify-content: center;
        align-items: center;
        box-sizing: border-box;
        border: 1px solid lightgray;
      }
    }
    .task-detail {
      position: absolute;
      z-index: 99;
      .drag-item {
        .task-item {
          display: flex;
          justify-content: center;
          align-items: center;
          user-select: none;
          border: 1px solid lightgray;
          box-sizing: border-box;
          min-width: 30px;
          position: absolute;
          border-radius: 15px;
        }
      }
    }
  }
}
</style>

Logo

技术共进,成长同行——讯飞AI开发者社区

更多推荐