赞
踩
项目需求:
实现以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) {
- //执行自己的逻辑
- }
- }
-
- }
以上就是实现甘特图的全部过程,欢迎大佬们指教。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。