vue 实现项目进度甘特图

news2024/7/6 18:53:34

 项目需求:

实现以1天、7天、30天为周期(周期根据筛选条件选择),展示每个项目不同里程碑任务进度。

项目在Vue-Gantt-chart: 使用Vue做数据控制的Gantt图表基础上进行了改造。

有需要的小伙伴也可以直接引入插件,自己修改。

 我是直接把甘特图封装成了组件,结构如下图:

 

 首先,安装插件

npm install v-gantt-chart

引入插件(我是全局引入的)

import vGanttChart from 'v-gantt-chart';

Vue.use(vGanttChart);

 代码如下:

index.js

<template>
  <div class="container">
    <v-gantt-chart
      :startTime="times[0]"
      :endTime="times[1]"
      :cellWidth="cellWidth"
      :cellHeight="cellHeight"
      :timeLines="timeLines"
      :titleHeight="titleHeight"
      :scale="Number(1440 * scale)"
      :titleWidth="titleWidth"
      showCurrentTime
      :hideHeader="hideHeader"
      :dataKey="dataKey"
      :arrayKeys="arrayKeys"
      :scrollToTime="scrollToTime"
      :scrollToPostion="positionA"
      @scrollLeft="scrollLeftA"
      customGenerateBlocks
      :datas="ganttData"
    >
      <template
        v-slot:block="{
          data,
          getPositonOffset,
          getWidthAbout2Times,
          isInRenderingTimeRange,
          startTimeOfRenderArea,
          endTimeOfRenderArea,
          isAcrossRenderingTimeRange
        }"
      >
        <div
          class="gantt-block-item"
          v-for="(item, index) in data.gtArray"
          v-if="
            isInRenderingTimeRange(item.start) ||
            isInRenderingTimeRange(item.end) ||
            isAcrossRenderingTimeRange(item.start, item.end)
          "
          :key="item.id"
          :style="{
            left: getPositonOffset(item.start) + 'px',
            width: getWidthAbout2Times(item.start, item.end) + 'px',
            height: judgeTime(data.gtArray) ? '50%' : '100%',
            top: !judgeTime(data.gtArray)
              ? ''
              : index % 2 !== 1
              ? '0px'
              : '22px'
          }"
        >
          <Test
            :data="data"
            :updateTimeLines="updateTimeLines"
            :cellHeight="cellHeight"
            :currentTime="currentTime"
            :item="item"
            @nodeEvent="nodeEvent"
          ></Test>
        </div>
      </template>
      <template v-slot:left="{ data }">
        <TestLeft :data="data" @panelDb="panelDb"></TestLeft>
      </template>
      <!-- <template v-slot:timeline="{ day , getTimeScales }">
          <TestTimeline :day="day" :getTimeScales="getTimeScales"></TestTimeline>
        </template> -->
      <template v-slot:title>
        <div class="title">名称</div>
      </template>
    </v-gantt-chart>
  </div>
</template>

<script>
import moment from 'moment';
import Test from './components/test.vue';
import TestLeft from './components/test-left.vue';
import TestTimeline from './components/test-timeline.vue';
import TestMarkline from './components/test-markline.vue';

import dayjs from 'dayjs';

export default {
  name: '',
  components: { Test, TestLeft, TestTimeline, TestMarkline },
  props: {
    ganttData: {
      type: Array,
      default: () => []
    },
    scaleData: {
      type: Number,
      default: 10080
    },
    scrollToTime: {
      type: String,
      default: moment().subtract(4, 'days').format('YYYY-MM-DD')
    }
  },

  data() {
    return {
      timeLines: [],
      currentTime: dayjs(),
      cellWidth: 100,
      cellHeight: 50,
      titleHeight: 50,
      titleWidth: 250,
      // scale: 1440 * 30,
      startDate: moment().startOf('year'),
      endDate: moment().endOf('year'),
      times: [
        moment().subtract(1, 'year').format('YYYY-MM-DD hh:mm:ss'),
        moment().add(6, 'months').format('YYYY-MM-DD hh:mm:ss')
      ],
      rowNum: 100,
      colNum: 10,
      datasB: [],
      dataKey: 'projectId',
      // scrollToTime: moment().subtract(14, 'days').format('YYYY-MM-DD'),
      // scrollToTime: moment().subtract(4, 'days').format('YYYY-MM-DD'),
      scrollToPostion: { x: 10000, y: 10000 },
      hideHeader: false,
      hideSecondGantt: false,
      arrayKeys: ['gtArray'],
      scrollToY: 0,
      positionB: {},
      positionA: {}
    };
  },
  watch: {
    scrollToY(val) {
      this.positionA = { x: val };
    },
    ganttData(newVal, oldVal) {
      console.log('newVal===', newVal);
      console.log('oldVal===', oldVal);
    }
  },
  computed: {
    scale() {
      console.log(this.scaleData);
      return this.scaleData / 1440;
    }
  },
  methods: {
    judgeTime(arr) {
      let startTimeArr = [];
      let endTimeArr = [];
      arr.map(function (item) {
        startTimeArr.push(
          item.startDate ? new Date(item.startDate).getTime() : ''
        );
        endTimeArr.push(
          item.delayDate
            ? new Date(item.delayDate).getTime()
            : item.endDate
            ? new Date(item.endDate).getTime()
            : ''
        );
      });
      let allStartTime = startTimeArr.sort(); // 排序
      let allEndTime = endTimeArr.sort();
      let result = 0; // 判断时间是否有重复区间
      for (let k = 0; k < allStartTime.length; k++) {
        if (k > 0) {
          if (allStartTime[k] <= allEndTime[k - 1]) {
            result += 1;
          }
        }
      }
      return result > 0;
    },
    nodeEvent(item) {
      this.$emit('nodeEventClick', item);
    },
    panelDb(item) {
      this.$emit('panelDbClick', item);
    },
    updateTimeLines(timeA, timeB) {
      this.timeLines = [
        {
          time: timeA,
          text: '自定义'
        },
        {
          time: timeB,
          text: '测试',
          color: '#747e80'
        }
      ];
    },
    scrollLeftA(val) {
      this.positionB = { x: val };
    }
  }
};
</script>

<style lang="scss" scoped>
.container {
  height: 82vh;
  background-color: #f5faff;
}
.title {
  width: 100%;
  height: 100%;
  color: #96aaca;
  background: #f5faff;
}
:deep(.gantt-leftbar-wrapper) {
  border-right: 1px solid #c6d8ee !important;
}
</style>

test-left.vue

<template>
  <div class="name">
    <div class="carId" @dblclick="onDblclick" >{{ data.projectName }}</div>
  </div>
</template>

<script>
export default {
  name: "TestLeft",
  props: {
    data: Object,
  },
  methods: {
    onDblclick() {
      // this.updateTimeLines(this.item.start, this.item.end);
      this.$emit('panelDb', this.data);
    }
  }
};
</script>

<style scoped>
.name {
  color: #000000;
  display: flex;
  box-sizing: border-box;
  overflow: hidden;
  height: 100%;
  width: 100%;
  padding: 10px 0;
  align-items: center;
  text-align: center;
  background: #f5faff;
  box-shadow: 2px 0px 4px 0px rgba(0, 0, 0, 0.1);
}

.carId {
  flex: 1;
}

.type {
  padding: 0 5px 0 0;
  font-size: 1.2rem;
}
</style>

test-markline.vue

<template>
  <div
    class="markline"
    :style="{  left: getPosition() + 'px' }"
  >
    <div class="markline-label">
      {{timeConfig.text}}{{ dayjs(timeConfig.time).format("HH:mm:ss") }}
    </div>
  </div>
</template>

<script>
import dayjs from "dayjs"
export default {
  name: "TestMarkLine",
	props:['getPosition','timeConfig'],
	data(){
		return {
			dayjs
		}
	}
}
</script>

<style lang="scss" scoped>

.markline {
    position: absolute;
    z-index: 100;
    width: 2px;
    height: 100vh;
    background: #747e80;

    &-label {
      padding: 3px;
      width: 6rem;
      margin-left: -3rem;
      margin-top: 5rem;
      color: #fff;
      background: #747e80;
      text-align: center;
      border-radius: 5px;
      font-size: 0.7rem;
    }
}
</style>

test-timeline.vue

<template>
 <div class="test">
  <span v-for="i in getTimeScales(day)"> {{i.format('HH:mm')}}</span>
 </div> 
</template>

<script>
export default {
  name: "TestLeft",
  props: {
    day: Object,
    getTimeScales:Function,
  }
};
</script>

<style lang="scss" scoped>
.test{
  display: flex;

  span{
    flex:1
  }
}
</style>

test.vue

<template>
  <el-popover placement="bottom" trigger="hover">
    <div
      slot="reference"
      class="plan"
      :style="{
        'background-color': statusColor,
        'margin-top': 0.1 * cellHeight + 'px'
      }"
      @click="onClick"
    >
      <div class="middle">{{ item.summary }}</div>
    </div>

    <div class="detail">{{ item.summary }}</div>
  </el-popover>
</template>

<script>
import dayjs from 'dayjs';
export default {
  name: 'Test',
  props: {
    data: Object,
    item: Object,
    currentTime: dayjs,
    updateTimeLines: Function,
    cellHeight: Number,
    startTimeOfRenderArea: Number
  },
  data() {
    return {
      dayjs: dayjs,
      stateObj: {
        DelayStart: '#F56C6C',
        Normal: '#C2F1E2',
        NoStart: '#D9E3ED',
        Delay: '#F56C6C',
        Stop: '#D9E3ED',
        DelayRisk: '#FFD4C7',
        NoControl: '#F56C6C',
        Close: '#D9E3ED'
      }
    };
  },
  computed: {
    statusColor() {
      console.log('data=======', this.data);
      let { item } = this;

      return this.stateObj[item.state] || '#D9E3ED';
    },
    startToString() {
      return dayjs(this.item.start).format('HH:mm');
    },
    endToString() {
      return dayjs(this.item.end).format('HH:mm');
    }
  },
  methods: {
    onClick() {
      // this.updateTimeLines(this.item.start, this.item.end);
      this.$emit('nodeEvent', this.item);
    }
  }
};
</script>

<style lang="scss" scoped>
.middle {
  flex: 1;
  text-align: center;
  padding-left: 5px;
  text-overflow: ellipsis;  /* ellipsis:显示省略符号来代表被修剪的文本  string:使用给定的字符串来代表被修剪的文本*/ 
  white-space: nowrap;   /* nowrap:规定段落中的文本不进行换行   */ 
  overflow: hidden; /*超出部分隐藏*/
}
.runTime {
  display: flex;
  flex-direction: column;
}
.plan {
  display: flex;
  align-items: center;
  box-sizing: border-box;
  height: 80%;
  border: 1px solid #f0f0f0;
  border-radius: 5px;
  color: #333333;
  padding-left: 5px;
  font-size: 0.8rem;
  // opacity: 0.8;
}

.detail {
  .header {
    text-align: center;
    font-size: 1rem;
  }
}

.detail ul {
  list-style: none;
  padding: 0px;
  li {
    span {
      display: inline-block;
      width: 80px;
      color: #777;
      font-size: 0.8rem;
    }
    span:first-child {
      text-align: right;
    }

    span:last-child {
    }
  }
}
</style>

页面中使用

<div>
    <ganttChart
      :ganttData="ganttArr"
      :scaleData="scaleData"
      :scrollToTime="scrollToTime"
      @nodeEventClick="nodeEventClick"
      @panelDbClick="panelDbClick"
     ></ganttChart>
</div>
import moment from 'moment';
import ganttChart from './components/ganttChart/index.vue';

export default {
    components: { ganttChart },
    data(){
        return{
            ganttArr: [],
            scaleData: 10080,
            scrollToTime: moment().subtract(4, 'days').format('YYYY-MM-DD'),
        }
    },
    methods: {
        // 点击甘特图node节点
        nodeEventClick(item) {
            // 执行自己的逻辑
        },
        // 双击甘特图左侧标题
        panelDbClick(item) {
            //执行自己的逻辑
        }
    }
    
}

以上就是实现甘特图的全部过程,欢迎大佬们指教。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1626631.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++中的继承与多态

一、继承&#xff1a; 1.什么是继承&#xff1f; 继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段&#xff0c;它允许程序员在保持原有类特性的基础上进行扩展&#xff0c;增加功能&#xff0c;这样产生新的类&#xff0c;称派生类。继承呈现了面向对象…

行为学学习记忆实验和抗焦虑实验两款硬件

安徽耀坤XWX-BM八臂迷宫实验&#xff08;Eight-arm Maze Test&#xff0c;RMT&#xff09;由八个完全相同的臂组成&#xff0c;这些臂从一个中央平台放射出来&#xff0c;所以又被称为放射迷宫。其基本方式是&#xff1a;训练动物受食物的驱使对迷宫的各臂进行探究&#xff0c;…

《第二行代码》第二版学习笔记(6)——内容提供器

文章目录 一 运行时权限2.权限分类3 运行时申请权限 二、内容提供器1、 ContentResolver的基本用法2、现有的内容提供器3、创建自己的内容提供器2.1 创建内容提供器的步骤2.2 跨程序数据共享 内容提供器&#xff08;Content Provider&#xff09;主要用于在不同的应用程序之间实…

用VMware虚拟机安装Centos7

用VMware虚拟机安装Centos7&#xff0c;无需废话&#xff0c;下面直接给步骤。 目录 1.下载VMware2.下载CentOS 73.安装VMware4.安装虚拟机 1.下载VMware VMware官方下载地址 里面可以选择你喜欢的一个版本&#xff0c;然后选择Windows64位的来下载。 2.下载CentOS 7 Cent…

ssm082基于java斗车交易系统设计与实现+vue

斗车交易系统 摘 要 21世纪的今天&#xff0c;随着社会的不断发展与进步&#xff0c;人们对于信息科学化的认识&#xff0c;已由低层次向高层次发展&#xff0c;由原来的感性认识向理性认识提高&#xff0c;管理工作的重要性已逐渐被人们所认识&#xff0c;科学化的管理&…

冯唐成事心法笔记 —— 知世

系列文章目录 冯唐成事心法笔记 —— 知己 冯唐成事心法笔记 —— 知人 冯唐成事心法笔记 —— 知世 冯唐成事心法笔记 —— 知智慧 文章目录 系列文章目录PART 3 知世 成事者的自我修养怎样做一个讨人喜欢的人第一&#xff0c;诚心第二&#xff0c;虚心 如何正确看待别人的评…

[Android]修改应用包名、名称、版本号、Icon、启动页以及环境判断和打包

1.修改包名 在Android Studio中更改项目的包名涉及几个步骤&#xff1a; 打开项目结构: 在Android Studio中&#xff0c;确保您处于Android视图模式&#xff08;在左侧面板顶部有一个下拉菜单可以选择&#xff09;。 重命名包名: 在项目视图中&#xff0c;找到您的包名&…

挑战一周完成Vue3实战项目硅谷甄选Day1:项目初始化、项目配置、项目集成

一、项目初始化 node v16.4.0以上&#xff08;查看node版本 : node -v&#xff09; pnpm 8.0.0&#xff08;npm i -g pnpm8.0.0&#xff09; 在想创建的位置新建文件夹自己命名 在此文件夹下cmd:pnpm create vite 选择如下配置 Project name&#xff08;项目名称&#xff0…

【网络编程】TCP流套接字编程 | Socket类 | ServerSocket类 | 文件资源泄露 | TCP回显服务器 | 网络编程

文章目录 TCP流套接字编程1.ServerSocket类2.Socket类3.文件资源泄露4.**TCP回显服务器** TCP流套接字编程 ​ ServerSocket类和Socket类这两个类都是用来表示socket文件&#xff08;抽象了网卡这样的硬件设备&#xff09;。 TCP是面向字节流的&#xff0c;传输的基本单位是b…

这样狠心的女人,不配当妈!

男人小时候经常受父亲虐待&#xff0c;初中毕业就到深圳打拼&#xff0c;基本与父母再无联系。 因为心有创伤&#xff0c;他没有考虑过结婚的事情&#xff0c;也不希望自己的娃成为受苦的一代。 然而&#xff0c;机缘巧合&#xff0c;他偶然之间认识了自己的爱人。 在妻子小的时…

BGP选路实验(锐捷)---Origin选路

实验拓扑图 基本配置如图所示 要求&#xff1a;R5上利用loopback口建立多个分段ip&#xff0c;利用bgp选路原则让双网段数据通过R6转发&#xff0c;单网段数据通过R7转发&#xff0c;通过修改Origin的属性类型为intcomplete&#xff08;利用三种不同的Origin属性的优先顺序&am…

基于MLP算法实现交通流量预测(Pytorch版)

在海量的城市数据中&#xff0c;交通流量数据无疑是揭示城市运行脉络、洞察出行规律的关键要素之一。实时且精准的交通流量预测不仅能为交通规划者提供科学决策依据&#xff0c;助力提升道路使用效率、缓解交通拥堵&#xff0c;还能为公众出行提供参考&#xff0c;实现个性化导…

【网络安全】安全事件管理处置 — 安全事件处置思路指导

专栏文章索引&#xff1a;网络安全 有问题可私聊&#xff1a;QQ&#xff1a;3375119339 目录 一、处理DDOS事件 1.准备工作 2.预防工作 3.检测与分析 4.限制、消除 5.证据收集 二、处理恶意代码事件 1.准备 2.预防 3.检测与分析 4.限制 5.证据收集 6.消除与恢复 …

JS 删除数组元素( 5种方法 )

No.内容链接1Openlayers 【入门教程】 - 【源代码示例300】 2Leaflet 【入门教程】 - 【源代码图文示例 150】 3Cesium 【入门教程】 - 【源代码图文示例200】 4MapboxGL【入门教程】 - 【源代码图文示例150】 5前端就业宝典 【面试题详细答案 1000】 文章目录 一、五种…

PS学习笔记-抠图相关

选好颜色后&#xff0c;altdelete更换画布颜色、填充前景色 按住shift键自由缩放图片&#xff0c;调好后双击鼠标即可完成&#xff0c;或者点击工具栏的 对勾 在某图层下 CTRLT 变换图片&#xff0c;调好后双击鼠标即可完成&#xff0c;或者点击工具栏的 对勾 CTRLJ复制图…

Linux用户与权限

切换账户 su su [-] [用户名]- 可选&#xff0c;表示在切换用户后加载环境变量&#xff0c;一般都使用 用户名 可选&#xff0c;省略表示切换到root切换用户后&#xff0c;可以使用exit命令退回上一用户&#xff0c;或用快捷键ctrld 为普通命令授权 sudo sudo命令&#xff1a;…

第5章 全局大喇叭——详解广播机制

第5章 全局大喇叭——详解广播机制 如果你了解网络通信原理应该会知道&#xff0c;在一个IP网络范围中&#xff0c;最大的IP地址是被保留作为广播地址来使用的。 比如某个网络的IP范围是192.168.0.XXX&#xff0c;子网掩码是255.255.255.0&#xff0c;那么这个网络的广播地址…

工厂数字化三部曲/业务、数据和IT融合

工厂数字化三部曲: 业务、数据和IT融合 在当今数字化转型的潮流中&#xff0c;企业面临着将业务、数据和IT融合的挑战和机遇。数字化转型不仅是技术上的升级&#xff0c;更是对企业运营模式和管理体系的全面优化和重构。通过业务“数字化”阶段的细致分析和整合&#xff0c;以及…

算法06链表

算法06链表 一、链表概述1.1概述1.2链表的组成部分&#xff1a;1.3链表的优缺点&#xff1a; 二、链表典例力扣707.设计链表难点分析&#xff1a;&#xff08;1&#xff09;MyLinkedList成员变量的确定&#xff1a;&#xff08;2&#xff09;初始化自定义链表&#xff1a;&…

记一次JSON.toJSONString()转换时非属性方法空指针异常排查及toJSONString保留null值属性

记一次JSON.toJSONString()转换时非属性方法空指针异常排查及toJSONString保留null值属性 异常详情 有一个类&#xff0c;里面有两个属性和一个类似工具的getRealName()方法如下&#xff1a; getRealName()方法就是获取这个人的真实名字&#xff0c;如果获取不到就以name返回…