vue 实现项目进度甘特图

 项目需求:

实现以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) {
            //执行自己的逻辑
        }
    }
    
}

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于springboot实现企业级工位管理系统项目【项目源码+论文说明】计算机毕业设计

基于springboot实现企业级工位管理系统演示 摘要 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;管理信息系统的实施在技术上已逐步成熟。本文介绍了企业级工位管理系统的开发全过程。通过分析企业级工位管理系统管理的不足&#xff0c;创建了一个计算机管理企业级工…

【深度学习】YOLOv5,金属表面的缺陷检测,GC10-DET数据集

目录&#xff1a; 文章目录 数据集数据集转换下载yolov5创建 dataset.yaml训练参数开始训练数据分布问询、帮助 数据集 数据集地址&#xff1a; https://github.com/lvxiaoming2019/GC10-DET-Metallic-Surface-Defect-Datasets 数据集下载方式&#xff1a; Download link&…

transformer 最简单学习3, 训练文本数据输入的形式

1、输入数据中&#xff0c;源数据和目标数据的定义 def get_batch(source,i):用于获取每个批数据合理大小的源数据和目标数据参数source 是通过batchfy 得到的划分batch个 ,的所有数据&#xff0c;并且转置列表示i第几个batchbptt 15 #超参数&#xff0c;一次输入多少个ba…

Spring事务回滚核心源码解读

记一次Springboot事务超时不回滚的分析过程 在Springboot中&#xff0c;我用的xml进行事务管理&#xff0c;DataSourceTransactionManager作为事务管理器&#xff0c;配置了事务控制在Service层&#xff1b;在事务管理器中&#xff0c;配置了defaultTimeout事务超时时间为5秒&…

云香印象终端自动化工具(监听农行收款云音箱)

项目成品 支持自动挂单✅完成监控收款云音箱✅完成卷烟盘点✅完成补单✅完成自动入库✅完成监控微信支付✅完成自动提交会员信息✅完成 用到的技术栈&#xff1a;PythonMQTT5.0PHP 云香印象终端自动收款工具 ​​​​​ 当顾客扫了三合一二维码且支付完成时&#xff0c;监控收到…

精酿啤酒:酿造工艺的自动化与智能化发展

随着科技的不断进步&#xff0c;自动化与智能化已成为啤酒酿造工艺的重要发展方向。Fendi Club啤酒紧跟时代潮流&#xff0c;积极推动酿造工艺的自动化与智能化发展&#xff0c;旨在提高生产效率、确保产品品质和满足市场需求。 Fendi Club啤酒引入自动化生产设备。他们采用自动…

Mybatis-plus 字段结果为NULL

问题 Mybatis-plus 字段结果为NULL 详细问题 笔者使用SpringBootMybatis-plus 进行项目开发。进行接口请求测试&#xff0c;在确定SQL语句没有问题的前提下&#xff0c;返回记录部分字段(譬如字段name)为空。 解决方案 修改Mybatis-plus中mapper的xml文件&#xff0c;使re…

CentOS7版本安装mysql

文章目录 1.安装mysql的前置条件2.清理原有的mysql数据库3.查看是否安装mariadb4.安装mysql依赖包5.解压缩mysql包6.安装目录赋权限&#xff08;注&#xff1a;R必须是大写&#xff09;7.创建MySQL组8.将mysql目录的权限授给mysql用户和mysql组9.创建mysql安装初始化配置文件10…

APP广告变现项目

我们提供的服务可以简化为以下几点&#xff1a; 我们将为您开发一款应用程序(APP)&#xff0c;该APP能够连接市场上的主要广告联盟平台。 我们将指导您完成整个“养机”流程&#xff0c;并确保您的单台设备每条广告能产生大约1元的收益。若您拥有10台设备&#xff0c;每日收益…

ElasticSearch搜索引擎中,在Kibana客户端使用命令的时候,千万不要加分号结尾

ElasticSearch搜索引擎中&#xff0c;在Kibana客户端使用命令的时候&#xff0c;千万不要加分号结尾 今天遇到了一个非常难发现的bug 由于经常写java代码&#xff0c;因此常常会在一句代码结尾之处写上一个分号&#xff0c;并且潜意识里觉得这就是非常正常的事情 不会有什么问…

c++的智能指针(5) -- weak_ptr

概述 我们在使用shared_ptr会出现以下的问题&#xff0c;会导致内存泄露。 代码1: 类内指针循环指向 #include <iostream> #include <memory>class B;class A { public:A() {std::cout << "Construct" << std::endl;}~A() {std::cout <…

鸿蒙入门11-DataPanel组件

数据面板组件 用于将多个数据的占比情况使用 占比图 进行展示 参数 参数形式 &#xff1a; DataPanel( options:{ values: number[], max?: number, type?: DataPanelType } ) 参数名 参数类型 是否必填 默认值 参数描述 values number[] 是 - 数据值列表 最大支持…

Android Studio的button点击事件

xml添加onClick调用方法 public class MainActivity extends AppCompatActivity {// 创建系统时间的文本控件TextView systemTimeTextView;Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activit…

Springboot+Vue项目-基于Java+MySQL的海滨体育馆管理系统(附源码+演示视频+LW)

大家好&#xff01;我是程序猿老A&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f49e;当前专栏&#xff1a;Java毕业设计 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; &#x1f380; Python毕业设计 &…

Jmeter工具+ant+jenkins实现持续集成

jmeterantjenkins持续集成 一、下载并配置jmeter 首先下载jmeter工具&#xff0c;并配置好环境变量&#xff1b;参考&#xff1a; jmeter默认保存的是.jtl格式的文件&#xff0c;要设置一下bin/jmeter.properties,文件内容&#xff0c;保存jmeter.save.saveservice.output_f…

【linux】Bad owner or permissions on

在root用户下执行scp操作向另外一个节点拷贝文件时发生了如下错误&#xff1a; Bad owner or permissions on /etc/crypto-policies/back-ends/openssh.config 我们查看他的权限时发现它所链接的文件权限为777 解决方法就是&#xff1a; chmod 600 /etc/crypto-policies/back-e…

关于加强电力系统通信与电网调度自动化建设问题的规定

关于加强电力系统通信与电网调度自动化建设问题的规定 为了保障电力系统安全、经济、优质、可靠运行&#xff0c;必须加强电网调度管理和提高技术装备水平。根据当前电网技术装备状况&#xff0c;结合电力系统通信和电网调度自动化的特点&#xff0c;以及今后规划发展的要求&am…

Python基础知识—运算符和if语句(二)

&#x1f3ac; 秋野酱&#xff1a;《个人主页》 &#x1f525; 个人专栏:《Java专栏》 《Python专栏》 ⛺️心若有所向往,何惧道阻且长 文章目录 1.输入和输出函数1.1输出函数1.2输入函数 2.常见运算符2.1赋值运算符2.2比较运算符2.3逻辑运算符2.4and逻辑与2.5or逻辑或2.6not逻…

Java后台开发的前置说明

1.知识点逻辑 一个部分 都是先挑重点知识点讲解 然后根据这些重点知识点去完成一个项目的开发 然后在到返回来解决这个部分其他细枝末节的知识点 2.软件开发的分工 我们大致可以将软件开发分成四块&#xff1a; 1.前端开发(比如开发电脑中的京东 htmlcssjavascript) 2.移动开…

Springboot3集成Web、RedisTemplate、Test和knife4j

本例将展示&#xff0c;如何在Springboot3中完成&#xff1a; Redis功能的Web接口实现构建Redis功能的单元测试knife4j自动化生成文档 Redis功能 Pom.xml <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter…