基于vue2+elmentui的甘特图

313 阅读6分钟

QQ202488-155336-HD[00h00m00s-00h00m10s].gif

<template>
  <el-scrollbar
    class="gantt-box"
    wrap-style="max-height:346px"
    view-class="rel"
  >
    <div class="calendar-header flex-wrap">
      <div
        class="calendar-head-date flex-shark"
        :class="{ 'today-date': item.isToday }"
        :style="{ width: unitWidth + 'px' }"
        v-for="item in showDate"
        :key="item.formatDate"
      >
        <div class="calendar-weekday">{{ item.time.format('dd') }}</div>
        <div class="calendar-date">{{ item.time.format('DD') }}</div>
      </div>
    </div>
    <div class="mission-box" :style="handleMissionBoxStyle()">
      <div
        class="mission-single-line flex-wrap"
        v-for="(group, index) in showMissionList"
        :key="index"
      >
        <div
          v-for="(item, i) in group"
          :key="item.id"
          class="mission-item flex-center-wrap flex-shark flex-justify-between"
          :style="handleMissionStyle(item, i)"
        >
          <div class="text-ess-1">
            {{ item.name }}-{{ item.real_start || item.start }}到{{
              item.real_end || item.end
            }}
          </div>
          <div class="ml16">{{ item.duration }}天</div>
        </div>
      </div>
    </div>
  </el-scrollbar>
</template>

<script>
export default {
  name: 'ganttCalendar',
  components: {},
  mixins: {},
  props: {
    missionList: {
      type: Array,
      default: () => []
    }
  },
  data() {
    return {
      showDate: [],
      showMissionList: [],
      // 单位宽度
      unitWidth: 86,
      typeList: [
        {
          id: 1,
          name: '活动计划',
          background: '#ECF5FF'
        },
        {
          id: 2,
          name: '团队计划',
          background: '#E1F3D8'
        },
        {
          id: 3,
          name: '其它计划',
          background: '#E8ECFF'
        }
      ]
    }
  },
  computed: {},
  filters: {},
  methods: {
    // 初始化
    init() {
      this.setShowDate()
      this.handleShowMission()
    },
    // 设置展示日期范围
    setShowDate(time) {
      time = new dayjs(time || undefined)
      // 开始时间为上周一,结束时间为下周日
      let startTime = time.subtract(13, 'd')
      let endTime = time.add(14, 'd')
      let showDate = []
      while (startTime.isBefore(endTime)) {
        let info = {
          time: startTime,
          date: dayjs(startTime.format('YYYY-MM-DD')),
          formatDate: startTime.format('YYYY-MM-DD'),
          isToday: startTime.isSame(time, 'd')
        }
        showDate.push(info)
        startTime = startTime.add(1, 'd')
      }
      this.showDate = showDate
      console.log('showdate', this.showDate)
    },
    // 设置展示任务列表,每一组的任务不重叠。showMissionList二维数组,每一项是一组不重叠的任务
    handleShowMission() {
      // 把任务按开始时间排序 计算时忽略时分秒,只计算日期
      let missionList = this.missionList.sort((a, b) => {
        return dayjs(a.start).valueOf() - dayjs(b.start).valueOf()
      })
      // 给任务添加一些属性,用来计算展示
      missionList = missionList.map(item => {
        item.startDate = dayjs(dayjs(item.start).format('YYYY-MM-DD'))
        item.endDate = dayjs(dayjs(item.end).format('YYYY-MM-DD 23:59:59'))
        // 计算任务天数
        item.duration = item.endDate.diff(item.startDate, 'd') + 1
        // 任务的开始日期和结束日期是否超出展示日期范围,如果超出,真实开始日期和结束日期另存为real_start和real_end,用来计算任务持续天数,并且更改任务的开始日期和结束日期计算展示天数
        if (item.startDate.isBefore(this.showDate[0].date)) {
          item.real_start = item.start
          item.start = this.showDate[0].formatDate
          item.startDate = this.showDate[0].date
        }
        if (
          item.endDate.isAfter(this.showDate[this.showDate.length - 1].date)
        ) {
          item.real_end = item.end
          item.end = this.showDate[this.showDate.length - 1].formatDate
          item.endDate = this.showDate[this.showDate.length - 1].date
        }
        return item
      })
      // 展示任务列表
      let showMissionList = []
      // 临时数组,用来存放每一组最晚的结束日期
      let temp = []
      missionList.forEach(item => {
        // 如果任务的开始日期和结束日期超出展示日期范围,不展示
        if (
          item.startDate.isAfter(
            this.showDate[this.showDate.length - 1].date
          ) ||
          item.endDate.isBefore(this.showDate[0].date)
        ) {
          return
        }
        // 计算任务展示天数
        item.show_day = item.endDate.diff(item.startDate, 'd') + 1
        // 查找没有重叠任务的任务组index
        let unOverlap = temp.findIndex(t => {
          return item.startDate.isAfter(t)
        })
        if (unOverlap === -1) {
          // 重叠了,计算和日历开始时间的距离
          item.distance = item.startDate.diff(this.showDate[0].date, 'd')
          // 全都重叠,新开一行
          showMissionList.push([item])
          temp.push(item.endDate)
        } else {
          // 没重叠,计算距离上一个任务结束日期的天数
          item.distance = item.startDate.diff(temp[unOverlap], 'd')
          // 没重叠就放在这一行,更新结束日期
          showMissionList[unOverlap].push(item)
          temp[unOverlap] = item.endDate
        }
      })

      // 按照每组任务的第一个开始时间排序
      this.showMissionList = showMissionList
      console.log('showMissionList', this.showMissionList)
    },
    // 计算单个任务style
    handleMissionStyle(item, index) {
      // 计算任务盒子style,背景为每个日期一根竖线并且要在竖线内,所以有一堆计算。建议别动。
      return {
        width: item.show_day * this.unitWidth - 1 + 'px',
        background: this.typeList.find(t => t.id === item.type).background,
        'margin-left':
          item.real_start && !index
            ? 0
            : Math.max(1, item.distance * this.unitWidth + (index ? 1 : 0)) +
              'px'
      }
    },
    // 计算任务盒子style,背景为每个日期一根竖线
    handleMissionBoxStyle() {
      return {
        width: this.showDate.length * this.unitWidth + 'px',
        'background-image': `linear-gradient(to left, #DCDFE6 1px, transparent 1px), linear-gradient(to left, #409EFF 1px, transparent 1px)`,
        'background-size': `${this.unitWidth}px 100%, 50% 100%`
      }
    }
  },
  watch: {},
  created() {
    this.missionList = [
      {
        id: 1,
        name: '任务1',
        start: '2024-08-01 12:00:00',
        end: '2024-08-10 12:00:00',
        type: 1
      },
      {
        id: 3,
        name: '任务3',
        start: '2024-08-12 12:00:00',
        end: '2024-08-20 12:00:00',
        type: 1
      },
      {
        id: 4,
        name: '任务4',
        start: '2024-08-18 12:00:00',
        end: '2024-08-25 12:00:00',
        type: 2
      },
      {
        id: 5,
        name: '任务5',
        start: '2024-07-20 12:00:00',
        end: '2024-07-25 12:00:00',
        type: 2
      },
      {
        id: 6,
        name: '任务6',
        start: '2024-07-25 12:00:00',
        end: '2024-07-30 12:00:00',
        type: 1
      },
      {
        id: 7,
        name: '任务7',
        start: '2024-07-30 12:00:00',
        end: '2024-08-02 12:00:00',
        type: 2
      },
      {
        id: 8,
        name: '任务8',
        start: '2024-08-02 12:00:00',
        end: '2024-08-06 12:00:00',
        type: 1
      },
      {
        id: 9,
        name: '任务9',
        start: '2024-08-06 12:00:00',
        end: '2024-08-11 12:00:00',
        type: 2
      },
      {
        id: 10,
        name: '任务10',
        start: '2024-08-11 12:00:00',
        end: '2024-08-15 12:00:00',
        type: 1
      },
      {
        id: 11,
        name: '任务11',
        start: '2024-08-15 12:00:00',
        end: '2024-08-19 12:00:00',
        type: 3
      },
      {
        id: 12,
        name: '任务12',
        start: '2024-08-19 12:00:00',
        end: '2024-08-23 12:00:00',
        type: 3
      },
      {
        id: 13,
        name: '任务13',
        start: '2024-08-23 12:00:00',
        end: '2024-08-27 12:00:00',
        type: 2
      },
      {
        id: 14,
        name: '任务14',
        start: '2024-08-27 12:00:00',
        end: '2024-08-31 12:00:00',
        type: 1
      },
      {
        id: 15,
        name: '任务15',
        start: '2024-08-31 12:00:00',
        end: '2024-09-04 12:00:00',
        type: 2
      },
      {
        id: 16,
        name: '任务16',
        start: '2024-09-04 12:00:00',
        end: '2024-09-08 12:00:00',
        type: 1
      },
      {
        id: 17,
        name: '任务17',
        start: '2024-08-01 12:00:00',
        end: '2024-08-02 12:00:00',
        type: 1
      },
      {
        id: 18,
        name: '任务18',
        start: '2024-08-01 12:00:00',
        end: '2024-08-02 12:00:00',
        type: 2
      },
      {
        id: 19,
        name: '任务19',
        start: '2024-08-01 12:00:00',
        end: '2024-08-02 12:00:00',
        type: 1
      },
      {
        id: 20,
        name: '任务20',
        start: '2024-08-01 12:00:00',
        end: '2024-08-02 12:00:00',
        type: 2
      },
      {
        id: 21,
        name: '任务21',
        start: '2024-08-01 12:00:00',
        end: '2024-08-02 12:00:00',
        type: 1
      },
      {
        id: 22,
        name: '任务22',
        start: '2024-08-01 12:00:00',
        end: '2024-08-02 12:00:00',
        type: 3
      },
      {
        id: 23,
        name: '任务23',
        start: '2024-08-01 12:00:00',
        end: '2024-08-02 12:00:00',
        type: 1
      },
      {
        id: 24,
        name: '任务24',
        start: '2024-08-01 12:00:00',
        end: '2024-08-02 12:00:00',
        type: 2
      },
      {
        id: 25,
        name: '任务25',
        start: '2024-08-01 12:00:00',
        end: '2024-08-02 12:00:00',
        type: 1
      },
      {
        id: 26,
        name: '任务26',
        start: '2024-08-01 12:00:00',
        end: '2024-08-02 12:00:00',
        type: 2
      }
    ]
    this.init()
  },
  mounted() {}
}
</script>

<style lang="scss" scoped>
.gantt-box {
  border-radius: 4px;
  border: 1px solid #dcdfe6;
}
.calendar-header {
  position: sticky;
  top: 0;
  width: fit-content;
  text-align: center;
  background: #fff;
  border-bottom: 1px solid #dcdfe6;
  .calendar-head-date {
    padding: 4px 0;
  }
  .calendar-weekday {
    font-weight: 400;
    font-size: 12px;
    color: #909399;
    line-height: 17px;
  }
  .calendar-date {
    font-weight: 600;
    font-size: 14px;
    color: #606266;
    line-height: 20px;
  }
  .today-date {
    position: relative;
    background: #ecf5ff;
  }
  .today-date::after {
    content: '';
    position: absolute;
    left: 50%;
    transform: translate(-62%, 90%);
    display: block;
    height: 5px;
    width: 5px;
    border: 1px solid #409eff;
    background: #fff;
    border-radius: 50%;
  }
}
.mission-box {
  padding: 12px 0;
  .mission-single-line {
    // 也许以后用得上,比如被展示日期截断的任务不要那一侧的圆角
    .border-radius-left {
      border-radius: 4px 0 0 4px;
    }
    .border-radius-right {
      border-radius: 0 4px 4px 0;
    }
    .mission-item {
      padding: 0 8px;
      height: 20px;
      line-height: 20px;
      border-radius: 4px;
      white-space: nowrap;
      font-weight: 400;
      font-size: 12px;
      color: #606266;
    }
  }
  .mission-single-line + .mission-single-line {
    margin-top: 8px;
  }
}
</style>