• 自制甘特图组件


    目录结构

    <template>
      <div class="gantt-content">
        <ganttChart
          :timeWidth="50"
          :startStamp="startStamp"
          :endStamp="endStamp"
          :header="header"
          :body="body"
        />
      </div>
    </template>
    
    <script>
    import ganttChart from "./ganttChart"
    import { orderGantt } from "@api/reportCenter/orderGantt"
    import { searchCalendar } from "@api/basicData/calendar"
    
    export default {
      components:{
        ganttChart,
      },
      data(){
        return {
          startStamp: this.$moment('2020-07-06 08:00:00').valueOf(),
          endStamp: this.$moment('2020-07-11 08:00:00').valueOf(),
          header: {
            height: 100,
            table: [],
            dates: [],
            class: [],
            times: []
          },
          body: {
            height: 40,
            list: []
          },
          year: '',
          week: ''
        }
      },
      methods:{
        // 获取数据
        async getData() {
          const date = await this.searchCalendar()
          if(date.code === 200) {
            const {data: {nowDate = []}} = date
            this.year = nowDate[0].haieryear
            this.haierWeek = nowDate[0].haierweek
          }
          const params = {
            year: this.year,
            haierWeek: this.haierWeek
          }
          const res = await orderGantt(params)
          // 组装甘特图数据
          if(res.code === 200) {
            let table = [
              { label: this.$t('orderGantt.factoryNo'), value: 'factoryNo',  80, style: {} },
              { label: this.$t('orderGantt.productLine'), value: 'productLine',  120 },
              { label: `${this.haierWeek}${this.$t('orderGantt.loadCount')}`, value: 'loadCount',  120 },
            ], 
            dates = [], 
            _class = [], times = [], list = [], cla_day = this.$t('orderGantt.day'), cla_ni = this.$t('orderGantt.night');
            if(res.data.length) {
              res.data[0].dateModels.forEach(item => {
                let theD = this.$moment(item.sdate).format('YYYY/MM/DD')
                dates.push({
                  label: theD, 
                  value: theD, 
                  height: 50
                })
              })
              let dd = res.data[0].dateModels
              this.startStamp = this.$moment(this.$moment(dd[0].sdate).format('YYYY-MM-DD') + " 08:00:00").valueOf()
              this.endStamp = this.$moment(this.$moment(dd[dd.length - 1].sdate).format('YYYY-MM-DD') + " 08:00:00").add(1,'d').valueOf()
            }
            dates.forEach((item, idx) => {
              _class = [
                ..._class,
                { label: cla_day, height: 30 },
                { label: cla_ni, height: 30 },
              ]
              times = [
                ...times,
                {label: '8:00', height: 20 }, {label: '12:00', height: 20 }, 
                {label: '16:00', height: 20 }, {label: '20:00', height: 20 }, 
                {label: '00:00', height: 20 }, {label: '4:00', height: 20 }, 
              ]
            });
            res.data.forEach(item => {
              list.push({
                factoryNo: item.factoryNo,
                productLine: item.productLine,
                loadCount: item.loadCount,
                order: item.sequentialPlanList,
              })
            });
            this.header = {
              ...this.header,
              table,
              dates,
              class: _class,
              times,
            }
            this.body = {
              ...this.body,
              list
            }
          }
        },
        // 获取海尔年、月、周、星期列表
        searchCalendar() {
          return searchCalendar({
            date: this.$moment().format("YYYY-MM-DD"),
            haiermonth: "",
            haiersweek: "",
            haierweek: "",
            haieryear: ""
          })
        },
      },
      created() {
        this.getData()
      }
    }
    </script>
    <style lang="less">
      .gantt-content {
         100%;
        padding: 20px;
        overflow-x: scroll;
      }
    </style>
    <template>
      <div class="gantt-chart" :style="{ widthAll+'px'}">
        <div class="c-content">
          <div class="c-table">
            <div class="c-header">
              <div
                class="th"
                v-for="item in header.table" 
                :key="item.value" 
                :style="{...(item.style || {}),  item.width+'px', height: (item.height || header.height)+'px', lineHeight: (item.height || header.height)+'px'}"
              >
                {{item.label}}
              </div>
            </div>
            <div class="c-body">
              <div
                class="tr"
                v-for="(tr, idx) in body.list"
                :key="idx" 
              >
                <div
                  class="td"
                  v-for="td in header.table"
                  :key="td.value" 
                  :style="{...td.style,  td.width+'px', height: body.height+'px', lineHeight: body.height+'px'}"
                >
                  {{tr[td.value] || ' '}}
                </div>
              </div>
            </div>
          </div>
          <div class="c-gant">
            <div class="c-header">
              <div>
                <div
                  class="th"
                  v-for="(item, idx) in header.dates"
                  :key="idx" 
                  :style="{...(item.style || {}),  dateWidth+'px', height: (item.height || header.height)+'px', lineHeight: (item.height || header.height)+'px'}"
                >
                  {{item.label}}
                </div>
              </div>
              <div>
                <div
                  class="th"
                  v-for="(item, idx) in header.class" 
                  :key="idx" 
                  :style="{...(item.style || {}),  classWidth+'px', height: (item.height || header.height)+'px', lineHeight: (item.height || header.height)+'px'}"
                >
                  {{item.label}}
                </div>
              </div>
              <div>
                <div
                  class="th"
                  v-for="(item, idx) in (theTimes.length ? theTimes : header.times)" 
                  :key="idx" 
                  :style="{...(item.style || {}),  timeWidth+'px', height: (item.height || header.height)+'px', lineHeight: (item.height || header.height)+'px'}"
                >
                  {{item.label}}
                </div>
              </div>
            </div>
            <div class="c-body">
              <div
                class="tr"
                v-for="(tr, idx) in body.list"
                :key="'tr'+idx" 
              >
                <div
                  class="td"
                  v-for="(td, idx2) in (theTimes.length ? theTimes : header.times)"
                  :key="'td'+idx2" 
                  :style="{...td.style,  timeWidth+'px', height: body.height+'px', lineHeight: body.height+'px'}"
                >
                  {{' '}}
                </div>
    
                <div
                  class="td-g"
                  v-for="(tdg, idx3) in ganttArr[idx]"
                  :key="tdg.label + idx3" 
                  :style="{ height: body.height-2+'px', lineHeight: body.height-2+'px', ...tdg.style }"
                  @mouseover="(e) => mouseOver(e, tdg)"
                  @mouseout="(e) => mouseOut(e, tdg)"
                  @contextmenu="(e) => contextmenu(e, tdg)"
                >
                  {{tdg.label}}
                </div>
              </div>
            </div>
          </div>
        </div>
    
        <div id="a-gantt-pop" :style="popStyle">
           <p>{{$t('orderGantt.orderName')}}: {{currentData.label}}</p> 
           <p>{{$t('orderGantt.num')}}: {{currentData.num}}</p> 
           <p>{{$t('orderGantt.startTime')}}: {{currentData.startTime}}</p>
           <p>{{$t('orderGantt.endTime')}}: {{currentData.endTime}}</p>
        </div>
    
        <a-modal 
          :visible="modal.visible" 
          :title="modal.title" 
          width="740px"
          :footer="null"
          @ok="closeModal"
          @cancel="closeModal"
        >
          <p>{{$t('orderGantt.orderName')}}: {{modal.data.label}}</p> 
          <p>{{$t('orderGantt.num')}}: {{modal.data.num}}</p> 
          <p>{{$t('orderGantt.startTime')}}: {{modal.data.startTime}}</p>
          <p>{{$t('orderGantt.endTime')}}: {{modal.data.endTime}}</p>
        </a-modal>
      </div>
    </template>
    
    <script>
      const timeDown = [
        {label: '8:00', height: 20 }, {label: '12:00', height: 20 }, 
        {label: '16:00', height: 20 }, {label: '20:00', height: 20 }, 
        {label: '24:00', height: 20 }, {label: '4:00', height: 20 }
      ]
      const timeUp = [
        {label: '8:00', height: 20 }, {label: '9:00', height: 20 }, {label: '10:00', height: 20 },
        {label: '11:00', height: 20 }, {label: '12:00', height: 20 }, {label: '13:00', height: 20 },
        {label: '14:00', height: 20 }, {label: '15:00', height: 20 }, {label: '16:00', height: 20 },
        {label: '17:00', height: 20 }, {label: '18:00', height: 20 }, {label: '19:00', height: 20 },
        {label: '20:00', height: 20 }, {label: '21:00', height: 20 }, {label: '22:00', height: 20 },
        {label: '23:00', height: 20 }, {label: '00:00', height: 20 }, {label: '1:00', height: 20 },
        {label: '2:00', height: 20 }, {label: '3:00', height: 20 }, {label: '4:00', height: 20 },
        {label: '5:00', height: 20 }, {label: '6:00', height: 20 }, {label: '7:00', height: 20 }
      ]
      function getColor(d) {
        switch (true) {
          case d<100:
            return {
              background: '#F7DEB9',
              borderRadius: '4px',
              border: '1px solid #F7DEB9'
            }
            break;
          case d===100:
            return {
              background: '#CAB9CA',
              borderRadius: '4px',
              border: '1px solid #CAB9CA'
            }
            break;
          case d>100:
            return {
              background: '#735773',
              borderRadius: '4px',
              border: '1px solid #735773'
            }
            break;
          default:
            return {
              background: '#CAB9CA',
              borderRadius: '4px',
              border: '1px solid #CAB9CA'
            }
            break;
        }
      }
    
      export default {
        props:{
          // 开始时间时间戳
          startStamp: {
            type: Number,
            required: true
          },
          // 结束时间时间戳
          endStamp: {
            type: Number,
            required: true
          },
          // 时间单位宽度大小,  实践单位 4小时 、 1小时
          timeWidth: {
            type: Number,
            default: 50
          },
          // 表头
          header: {
            type: Object,
            default: {
              height: 100, // 表头的默认高度
              table: [],   // 表格头
              dates: [],   // 日期
              class: [],   // 班次
              times: []    // 时间
            }
          },
          // 数据
          body: {
            type: Object,
            default: {
              height: 40,   // 表格体的默认高度
              list: []
            }
          },
        },
        data() {
          return {
            widthAll: 1200,
            dateWidth: 300,
            classWidth: 150,
            timeAll: 259200000,  // 默认显示3天, 这是3天的毫秒数
            ganttArr: [],  // 任务数组
            currentData: {},  // 当前操作任务的数据
            popStyle: {  // hover浮层的样式
              display: 'none',
              top: 0,
              left: 0
            },
            modal: {  // 弹窗信息
              visible: false,
              title: '',
              data: {}
            },
            ctrlDown: false,  // true 代表Ctrl键正被按压
            wheelHeight: 0,  // 滚轮滚动的位置
            theTimes: [] // 时间表头,需要内部维护
          };
        },
        computed: {},
        watch: {
          body() {
            this.initGantt()
          },
          timeWidth(l) {
            this.dateWidth = l * 6
            this.classWidth = l * 3
          },
        },
        created() {
          this.initGantt()
        },
        mounted() {
          this.lisenScrol()
        },
        beforeDestroy() {
          this.ctrlDown = false
          this.theTimes = []
          let gantt = document.getElementsByClassName('c-gant')
          if(gantt && gantt[0]) gantt[0].removeEventListener('mousewheel', this.ganttZoom, false)
        },
        methods: {
          // 初始化甘特图
          initGantt() {
            const {list = []} = this.body
            let gArr = [], widthAll = 0;
            this.timeAll = this.endStamp - this.startStamp
            this.header.table.forEach(item => {
              widthAll += item.width
            })
            this.widthAll = widthAll + this.dateWidth * this.header.dates.length
    
            list.forEach(item => {
              let arr = []
              item.order.forEach(o => {
                arr.push({
                  ...o,
                  label: o.orderCode,
                  style: {
                    ...o.style,
                    ...getColor(o.num),
                    ...(this.countWidth(o.startTime, o.endTime))
                  }
                })
              })
              gArr.push(arr)
            })
            this.ganttArr = gArr
          },
          // 计算任务位置
          countWidth(st, et) {
            let w = this.dateWidth * this.header.dates.length, 
              s = this.$moment(st).valueOf(), 
              e = this.$moment(et).valueOf();
            return {
              left: parseInt((s - this.startStamp) * (w / this.timeAll)) + 'px',
               parseInt((e - s) * (w / this.timeAll)) + 'px',
            }
          },
          // 鼠标移入
          mouseOver(e, d) {
            if(this.popStyle && this.popStyle.display === 'none') {
              this.popStyle = {
                display: 'block',
                top: e.clientY + 12+'px',
                left: e.clientX - 100+'px'
              }
              this.currentData = {...d}
            }
          },
          // 鼠标移出
          mouseOut(e, d) {
            this.popStyle = {
              display: 'none',
              top: 0,
              left: 0
            }
            this.currentData = {}
          },
          // 右击
          contextmenu(e, d) {
            e.preventDefault();
            this.modal = {
              visible: true,
              title: d.label,
              data: {...d}
            }
          },
          //  关闭弹窗
          closeModal() {
            this.modal = {
              visible: false,
              title: '',
              data: {}
            }
          },
          // 监听 Ctrl + 滚轮,缩放甘特图
          lisenScrol() {
            let w = this
            document.onkeydown = function(e) {
              if (e.keyCode === 17) w.ctrlDown = true
            },
            document.onkeyup = function(e) {
              if (e.keyCode === 17) w.ctrlDown = false
            },
            document.getElementsByClassName('c-gant')[0].addEventListener('mousewheel', this.ganttZoom, false); 
          },
          ganttZoom(e) {
            e.preventDefault();
              if(this.ctrlDown) {
                let _newTimes = []
                if(e.wheelDeltaY > 0) {  // 放大
                  this.dateWidth = this.timeWidth * 24
                  this.classWidth = this.timeWidth * 12
                  this.header.dates.forEach(item => {
                    _newTimes = [ ..._newTimes, ...timeUp ]
                  });
                } else {  // 缩小
                  this.dateWidth = this.timeWidth * 6
                  this.classWidth = this.timeWidth * 3
                  this.header.dates.forEach(item=> {
                    _newTimes = [ ..._newTimes, ...timeDown ]
                  });
                }
                this.theTimes = _newTimes
                this.initGantt()
              }
          }
        },
      }
    </script>
    
    <style lang="less" scoped>
      .gantt-chart {
        position: relative;
        background-color: white;
        .c-content {
          border: 0.5px solid gray;
          white-space: nowrap;
          .c-table {
            display: inline-block;
            .c-header {
              .th {
                display: inline-block;
                text-align: center;
                border: 0.5px solid gray;
                box-sizing: border-box;
                word-break: break-all;
              }
            }
            .c-body {
              .tr{
                .td {
                  display: inline-block;
                  text-align: center;
                  border: 0.5px solid gray;
                  box-sizing: border-box;
                }
              }
            }
          }
          .c-gant {
            display: inline-block;
            .c-header {
              .th {
                display: inline-block;
                text-align: center;
                border: 0.5px solid gray;
                box-sizing: border-box;
              }
            }
            .c-body {
              margin-top: -1px;
              .tr{
                position: relative;
                .td {
                  display: inline-block;
                  text-align: center;
                  border: 0.5px solid gray;
                  box-sizing: border-box;
                  user-select: none;
                }
                .td-g {
                  position: absolute;
                  text-align: left;
                  box-sizing: border-box;
                  user-select: none;
                  z-index: 99;
                  color: white;
                  font-weight: 600;
                  letter-spacing: 1px;
                  cursor: pointer;
                  top: 1px;
                  white-space: nowrap;
                  text-overflow: ellipsis;
                  overflow: hidden;
                  word-break: break-all;
                }
              }
            }
          }
        }
        #a-gantt-pop{
          display: none;
          position: fixed;
          z-index: 200;
           200px;
          min- 150px;
          min-height: 100px;
          box-shadow: 0 0 12px gray;
          background-color: white;
          padding: 10px;
          border-radius: 5px;
          overflow: hidden;
        }
      }
    </style>
  • 相关阅读:
    可扩容分布式session方案
    Linux 极限压缩
    python调用jenkinsAPI
    Jenkins-slave分布式跨网络发布
    mysql查看指定数据库各表容量大小
    FastAPI--依赖注入之Depends(8)
    FastAPI--跨域处理(7)
    FastAPI--中间件(6)
    FastAPI--错误处理(5)
    FastAPI--响应报文(4)
  • 原文地址:https://www.cnblogs.com/pengfei-nie/p/13750505.html
Copyright © 2020-2023  润新知