vue 项目 甘特图模块开发

vue前端项目中开发基于甘特图的项目计划模块

参考链接

相比以前jquery的资料,vue的甘特图插件少很多,中文资料更是少的可怜,以下两个链接是在网上搜到相对还不错的甘特图插件

https://www.cnblogs.com/liang715200/p/12029640.html
https://github.com/neuronetio/vue-gantt-elastic

开始使用

  1. 安装以下插件
npm intall gantt-elastic
npm install gantt-elastic-header
npm install dayjs
  1. 复制以下代码到vue文件
<template>
  <q-page class="q-pa-sm">
    <gantt-elastic
      :options="options"
      :tasks="tasks"
      @tasks-changed="tasksUpdate"
      @options-changed="optionsUpdate"
      @dynamic-style-changed="styleUpdate"
    >
      <gantt-header slot="header"></gantt-header>
    </gantt-elastic>
    <div class="q-mt-md" />
    <q-btn @click="addTask" icon="mdi-plus" label="Add task" />
  </q-page>
</template>

<style>
</style>

<script>
import GanttElastic from "gantt-elastic";
import GanttHeader from "gantt-elastic-header";
import dayjs from "dayjs";

// just helper to get current dates
function getDate(hours) {
  const currentDate = new Date();
  const currentYear = currentDate.getFullYear();
  const currentMonth = currentDate.getMonth();
  const currentDay = currentDate.getDate();
  const timeStamp = new Date(
    currentYear,
    currentMonth,
    currentDay,
    0,
    0,
    0
  ).getTime();
  return new Date(timeStamp + hours * 60 * 60 * 1000).getTime();
}
let tasks = [
  {
    id: 1,
    label: "Make some noise",
    user:
      '<a href="https://www.google.com/search?q=John+Doe" target="_blank" style="color:#0077c0;">John Doe</a>',
    start: getDate(-24 * 5),
    duration: 15 * 24 * 60 * 60 * 1000,
    percent: 85,
    type: "project"
    //collapsed: true,
  },
  {
    id: 2,
    label: "With great power comes great responsibility",
    user:
      '<a href="https://www.google.com/search?q=Peter+Parker" target="_blank" style="color:#0077c0;">Peter Parker</a>',
    parentId: 1,
    start: getDate(-24 * 4),
    duration: 4 * 24 * 60 * 60 * 1000,
    percent: 50,
    type: "milestone",
    collapsed: true,
    style: {
      base: {
        fill: "#1EBC61",
        stroke: "#0EAC51"
      }
    }
  },
  {
    id: 3,
    label: "Courage is being scared to death, but saddling up anyway.",
    user:
      '<a href="https://www.google.com/search?q=John+Wayne" target="_blank" style="color:#0077c0;">John Wayne</a>',
    parentId: 2,
    start: getDate(-24 * 3),
    duration: 2 * 24 * 60 * 60 * 1000,
    percent: 100,
    type: "task"
  },
  {
    id: 4,
    label: "Put that toy AWAY!",
    user:
      '<a href="https://www.google.com/search?q=Clark+Kent" target="_blank" style="color:#0077c0;">Clark Kent</a>',
    start: getDate(-24 * 2),
    duration: 2 * 24 * 60 * 60 * 1000,
    percent: 50,
    type: "task",
    dependentOn: [3]
  },
  {
    id: 5,
    label:
      "One billion, gajillion, fafillion... shabadylu...mil...shabady......uh, Yen.",
    user:
      '<a href="https://www.google.com/search?q=Austin+Powers" target="_blank" style="color:#0077c0;">Austin Powers</a>',
    parentId: 4,
    start: getDate(0),
    duration: 2 * 24 * 60 * 60 * 1000,
    percent: 10,
    type: "milestone",
    style: {
      base: {
        fill: "#0287D0",
        stroke: "#0077C0"
      }
    }
  },
  {
    id: 6,
    label: "Butch Mario and the Luigi Kid",
    user:
      '<a href="https://www.google.com/search?q=Mario+Bros" target="_blank" style="color:#0077c0;">Mario Bros</a>',
    parentId: 5,
    start: getDate(24),
    duration: 1 * 24 * 60 * 60 * 1000,
    percent: 50,
    type: "task",
    collapsed: true,
    style: {
      base: {
        fill: "#8E44AD",
        stroke: "#7E349D"
      }
    }
  },
  {
    id: 7,
    label: "Devon, the old man wanted me, it was his dying request",
    user:
      '<a href="https://www.google.com/search?q=Knight+Rider" target="_blank" style="color:#0077c0;">Knight Rider</a>',
    parentId: 2,
    dependentOn: [6],
    start: getDate(24 * 2),
    duration: 4 * 60 * 60 * 1000,
    percent: 20,
    type: "task",
    collapsed: true
  },
  {
    id: 8,
    label: "Hey, Baby! Anybody ever tell you I have beautiful eyes?",
    user:
      '<a href="https://www.google.com/search?q=Johhny+Bravo" target="_blank" style="color:#0077c0;">Johhny Bravo</a>',
    parentId: 7,
    dependentOn: [7],
    start: getDate(24 * 3),
    duration: 1 * 24 * 60 * 60 * 1000,
    percent: 0,
    type: "task"
  },
  {
    id: 9,
    label:
      "This better be important, woman. You are interrupting my very delicate calculations.",
    user:
      '<a href="https://www.google.com/search?q=Dexter\'s+Laboratory" target="_blank" style="color:#0077c0;">Dexter\'s Laboratory</a>',
    parentId: 8,
    dependentOn: [8, 7],
    start: getDate(24 * 4),
    duration: 4 * 60 * 60 * 1000,
    percent: 20,
    type: "task",
    style: {
      base: {
        fill: "#8E44AD",
        stroke: "#7E349D"
      }
    }
  },
  {
    id: 10,
    label: "current task",
    user:
      '<a href="https://www.google.com/search?q=Johnattan+Owens" target="_blank" style="color:#0077c0;">Johnattan Owens</a>',
    start: getDate(24 * 5),
    duration: 24 * 60 * 60 * 1000,
    percent: 0,
    type: "task"
  },
  {
    id: 11,
    label: "test task",
    user:
      '<a href="https://www.google.com/search?q=Johnattan+Owens" target="_blank" style="color:#0077c0;">Johnattan Owens</a>',
    start: getDate(24 * 6),
    duration: 24 * 60 * 60 * 1000,
    percent: 0,
    type: "task"
  },
  {
    id: 12,
    label: "test task",
    user:
      '<a href="https://www.google.com/search?q=Johnattan+Owens" target="_blank" style="color:#0077c0;">Johnattan Owens</a>',
    start: getDate(24 * 7),
    duration: 24 * 60 * 60 * 1000,
    percent: 0,
    type: "task",
    parentId: 11
  },
  {
    id: 13,
    label: "test task",
    user:
      '<a href="https://www.google.com/search?q=Johnattan+Owens" target="_blank" style="color:#0077c0;">Johnattan Owens</a>',
    start: getDate(24 * 8),
    duration: 24 * 60 * 60 * 1000,
    percent: 0,
    type: "task"
  },
  {
    id: 14,
    label: "test task",
    user:
      '<a href="https://www.google.com/search?q=Johnattan+Owens" target="_blank" style="color:#0077c0;">Johnattan Owens</a>',
    start: getDate(24 * 9),
    duration: 24 * 60 * 60 * 1000,
    percent: 0,
    type: "task"
  },
  {
    id: 15,
    label: "test task",
    user:
      '<a href="https://www.google.com/search?q=Johnattan+Owens" target="_blank" style="color:#0077c0;">Johnattan Owens</a>',
    start: getDate(24 * 16),
    duration: 24 * 60 * 60 * 1000,
    percent: 0,
    type: "task"
  }
];
let options = {
  taskMapping: {
    progress: "percent"
  },
  maxRows: 100,
  maxHeight: 500,
  title: {
    label: "Your project title as html (link or whatever...)",
    html: false
  },
  row: {
    height: 24
  },
  calendar: {
    hour: {
      display: true
    }
  },
  chart: {
    progress: {
      bar: false
    },
    expander: {
      display: true
    }
  },
  taskList: {
    expander: {
      straight: false
    },
    columns: [
      {
        id: 1,
        label: "ID",
        value: "id",
        width: 40
      },
      {
        id: 2,
        label: "Description",
        value: "label",
        width: 200,
        expander: true,
        html: true,
        events: {
          click({ data, column }) {
            alert("description clicked!\n" + data.label);
          }
        }
      },
      {
        id: 3,
        label: "Assigned to",
        value: "user",
        width: 130,
        html: true结束
      },
      {
        id: 3,
        label: "Start",
        value: task => dayjs(task.start).format("YYYY-MM-DD"),
        width: 78
      },
      {
        id: 4,
        label: "Type",
        value: "type",
        width: 68
      },
      {
        id: 5,
        label: "%",
        value: "progress",
        width: 35,
        style: {
          "task-list-header-label": {
            "text-align": "center",
            width: "100%"
          },
          "task-list-item-value-container": {
            "text-align": "center",
            width: "100%"
          }
        }
      }
    ]
  },
  locale: {
    name: "en",
    Now: "Now",
    "X-Scale": "Zoom-X",
    "Y-Scale": "Zoom-Y",
    "Task list width": "Task list",
    "Before/After": "Expand",
    "Display task list": "Task list"
  }
};

export default {
  name: "Gantt",
  components: {
    GanttElastic,
    GanttHeader
  },
  data() {
    return {
      tasks,
      options,
      dynamicStyle: {},
      lastId: 16
    };
  },
  methods: {
    addTask() {
      this.tasks.push({
        id: this.lastId++,
        label:
          '<a href="https://images.pexels.com/photos/423364/pexels-photo-423364.jpeg?auto=compress&cs=tinysrgb&h=650&w=940" target="_blank" style="color:#0077c0;">Yeaahh! you have added a task bro!</a>',
        user:
          '<a href="https://images.pexels.com/photos/423364/pexels-photo-423364.jpeg?auto=compress&cs=tinysrgb&h=650&w=940" target="_blank" style="color:#0077c0;">Awesome!</a>',
        start: getDate(24 * 3),
        duration: 1 * 24 * 60 * 60 * 1000,
        percent: 50,
        type: "project"
      });
    },
    tasksUpdate(tasks) {
      this.tasks = tasks;
    },
    optionsUpdate(options) {
      this.options = options;
    },
    styleUpdate(style) {
      this.dynamicStyle = style;
    }
  }
};
</script>

  1. 至此,一个简单的甘特图就能显示出来了,下面放个截图

123

下面重点谈下在集成插件到项目中踩过的坑

  1. 翻译控件中的英文,在option下面添加如下配置,翻译控件中的星期和月份
locale: {
    weekdays:["周日","周一","周二","周三","周四","周五","周六"],
    months:["一月","二月","三月","四月","五月","六月","七月","八月","九月","十月","十一月","十二月"],
  }
  1. 点击事件,获取Id,该控件的点击事件如下,在columns中的events有点击事件的对象,由于在events无法获取vue页面的this对象,因此我定义了that变量,在mount中把that=this
columns: [
      {
        id: 1,
        label: "任务",
        value: "label",
        width: 200,
        expander: true,
        html: true,
        events: {
          click({ data, column }) {
            that.handleEdit(data.id)
          }
        }
      },
      {
        id: 2,
        label: "责任人",
        value: "user",
        width: 78,
        html: true
      },
  mounted() {
    that=this
  },
  1. 该插件有一个bug,当task数据位空时,会造成浏览器进入假死的状态,我的处理方式是当后端返回空数据的时候,判断如果是空的话就用默认数据填充,并且使用v-if判断控件是否显示
<gantt-elastic
      v-if="showGantt"
      :options="options"
      :tasks="tasks"
      @tasks-changed="tasksUpdate"
      @options-changed="optionsUpdate"
      @dynamic-style-changed="styleUpdate"
    >
  1. 最后贴上完整的前端代码
<template>
  <a-card :bordered="false">
      <div class="table-operator">
      <a-button type="primary" icon="plus" @click="hanldleAdd()">新建</a-button>

       <a-button type="primary" icon="redo" @click="showProjcetDrawer()">选择项目</a-button>
       {{ProjectName}}
    </div>
    <div class="table-page-search-wrapper">
      <a-form layout="inline">
        <a-row :gutter="10">
          <a-col :md="4" :sm="24">
            <a-form-item label="查询类别">
              <a-select allowClear v-model="queryParam.condition">
                <a-select-option key="TaskDescripe">任务描述</a-select-option>
                <a-select-option key="ResponseName">责任人</a-select-option>
              </a-select>
            </a-form-item>
          </a-col>
          <a-col :md="4" :sm="24">
            <a-form-item>
              <a-input v-model="queryParam.keyword" placeholder="关键字" />
            </a-form-item>
          </a-col>
          <a-col :md="6" :sm="24">
            <a-button type="primary" @click="()=>(getDataList())">查询</a-button>
            <a-button style="margin-left: 8px" @click="() => (queryParam = {})">重置</a-button>
          </a-col>
          <a-col :md="4" :sm="12">
              <downloadexcel
                class = "btn"
                :data="data"
                :fields = "export_fields"
                :before-generate = "startDownload"
                :before-finish = "finishDownload"
                type    = "xlsx">
                <a-button type="primary" icon="file-excel" >导出</a-button>
              </downloadexcel>
          </a-col>
        </a-row>
      </a-form>
    </div>
    <!-- <div class="table-operator">
      <a-button type="primary" icon="plus" @click="hanldleAdd()">新建</a-button>
      <a-button
        type="primary"
        icon="minus"
        @click="handleDelete(selectedRowKeys)"
        :disabled="!hasSelected()"
        :loading="loading"
      >删除</a-button>
      <a-button type="primary" icon="redo" @click="getDataList()">刷新</a-button>
    </div>

    <div class="table-page-search-wrapper">
      <a-form layout="inline">
        <a-row :gutter="10">
          <a-col :md="4" :sm="24">
            <a-form-item label="查询类别">
              <a-select allowClear v-model="queryParam.condition">
                <a-select-option key="TaskDescripe">任务描述</a-select-option>
                <a-select-option key="ResponseBy">责任人</a-select-option>
                <a-select-option key="RealFinishDate">实际完成时间</a-select-option>
                <a-select-option key="ProjectId">项目Id</a-select-option>
              </a-select>
            </a-form-item>
          </a-col>
          <a-col :md="4" :sm="24">
            <a-form-item>
              <a-input v-model="queryParam.keyword" placeholder="关键字" />
            </a-form-item>
          </a-col>
          <a-col :md="6" :sm="24">
            <a-button type="primary" @click="()=>(pagination.current=1,getDataList())">查询</a-button>
            <a-button style="margin-left: 8px" @click="() => (queryParam = {})">重置</a-button>
          </a-col>
        </a-row>
      </a-form>
    </div> -->

    <gantt-elastic
      v-if="showGantt"
      :options="options"
      :tasks="tasks"
      @tasks-changed="tasksUpdate"
      @options-changed="optionsUpdate"
      @dynamic-style-changed="styleUpdate"
    >
    <!-- <gantt-header :ProjectName="ProjectName" slot="header"></gantt-header> -->
    </gantt-elastic>

    <edit-form ref="editForm" :parentObj="this"></edit-form>

     <a-drawer
        title="选择项目"
        placement="right"
        :closable="false"
        @close="onClose"
        :visible="ProjectDrawerVisible"
      >
        <p @click="SelectProject(item.Id,item.ProjectName)" v-for="item in ProjectData">{{item.ProjectName}}</p>
   </a-drawer>
  </a-card>
</template>

<style>
</style>

<script>
import GanttElastic from "gantt-elastic";
import GanttHeader from "gantt-elastic-header";
import dayjs from "dayjs";
import EditForm from './EditForm'
import moment from 'moment';
import JsonExcel from 'vue-json-excel'

// just helper to get current dates
function getDate(hours) {
  const currentDate = new Date();
  const currentYear = currentDate.getFullYear();
  const currentMonth = currentDate.getMonth();
  const currentDay = currentDate.getDate();
  const timeStamp = new Date(
    currentYear,
    currentMonth,
    currentDay,
    0,
    0,
    0
  ).getTime();
  return new Date(timeStamp + hours * 60 * 60 * 1000).getTime();
}
let that
let tasks = [
{
    id: 1,
    label: "请先选择一个项目",
    user:
      '123',
    start: getDate(-24 * 5),
    end:getDate(-24 * 5),
    duration: 15 * 24 * 60 * 60 * 1000,
    percent: 85,
    type: "project"
  }
];
let basicTask={
    id: 1,
    label: "请先选择一个项目",
    user:
      '456',
    start: getDate(-24 * 5),
    end:getDate(-24 * 5),
    duration: 15 * 24 * 60 * 60 * 1000,
    percent: 85,
    type: "project"
  }
let options = {
  taskMapping: {
    progress: "percent"
  },
  maxRows: 100,
  maxHeight: 500,
  title: {
    label: "avc",
    html: false
  },
  row: {
    height: 24
  },
  calendar: {
    hour: {
      display: false
    }
  },
  chart: {
    progress: {
      bar: false
    },
    expander: {
      display: true
    }
  },
  taskList: {
    expander: {
      straight: false
    },
    columns: [
      {
        id: 1,
        label: "任务",
        value: "label",
        width: 200,
        expander: true,
        html: true,
        events: {
          click({ data, column }) {
            that.handleEdit(data.id)
          }
        }
      },
      {
        id: 2,
        label: "责任人",
        value: "user",
        width: 78,
        html: true
      },
      {
        id: 3,
        label: "开始",
        value: task => dayjs(task.start).format("YYYY-MM-DD"),
        width: 78
      },
      {
        id: 4,
        label: "结束",
        value: task => dayjs(task.end).format("YYYY-MM-DD"),
        width: 78
      },
      {
        id: 5,
        label: "状态",
        value: "Status",
        width: 68
      },
      // {
      //   id: 5,
      //   label: "%",
      //   value: "progress",
      //   width: 35,
      //   style: {
      //     "task-list-header-label": {
      //       "text-align": "center",
      //       width: "100%"
      //     },
      //     "task-list-item-value-container": {
      //       "text-align": "center",
      //       width: "100%"
      //     }
      //   }
      // }
    ]
  },
  locale: {
    weekdays:["周日","周一","周二","周三","周四","周五","周六"],
    months:["一月","二月","三月","四月","五月","六月","七月","八月","九月","十月","十一月","十二月"],
  }
};

let doingStyle={
  base: {
    fill: "#BFEFFF",
    stroke: "#BFEFFF"
  }
}
let warnStyle={
  base: {
    fill: "#FF6A6A",
    stroke: "#FF6A6A"
  }
}
let finishStyle={
  base: {
    fill: "#C1FFC1",
    stroke: "#C1FFC1"
  }
}
let cancelStyle={
  base: {
    fill: "#BABABA",
    stroke: "#BABABA"
  }
}


export default {
  name: "Gantt",
  components: {
    GanttElastic,
    GanttHeader,
    EditForm,
    'downloadexcel': JsonExcel
  },
  mounted() {
    that=this
  },
  data() {
    return {
      basicTask,
      doingStyle,
      finishStyle,
      warnStyle,
      cancelStyle,
      tasks,
      options,
      dynamicStyle: {},
      lastId: 16,
      ProjectDrawerVisible:false,
      ProjectId:"",
      ProjectName:"",
      ProjectData:[],
      TaskData:[],
      sorter: { field: 'StartDate', order: 'asc' },
      data:[],
      queryParam:{},
      that,
      export_fields:
      {
        "项目编号": "ProjectNo",
        "项目名称": "ProjectName",
        "前置任务": "PreTaskName",
        "父任务": "ParentTaskName",
        "任务": "TaskDescripe",
        "状态": "TaskStatus",
        "责任人": "ResponseName",
        "计划开始时间": "StartDate",
        "计划结束时间": "EndDate",
        "实际完成时间": "RealFinishDate",
        "资源": "Resource",
        "配置人员": "ConfigMember"},
        showGantt:false

    };
  },
  methods: {
    moment,
    getDataList() {
      this.selectedRowKeys = []

      this.$http
        .post('/IPMS_Manage/aspice_projectplan/GetDataList', {
          SortField: this.sorter.field || 'Id',
          SortType: this.sorter.order,
          ProjectId:this.ProjectId,
          ...this.queryParam,
        })
        .then(resJson => {
          this.data = resJson.Data
          // console.log(this.data)
          this.TaskData.length=0
          for(var i=0;i<resJson.Data.length;i++)
          {
            var preList=[]
            var temp={
                id:resJson.Data[i].Id,
                label:resJson.Data[i].TaskDescripe,
                user:resJson.Data[i].ResponseName,
                start:moment(resJson.Data[i].StartDate),
                end:moment(resJson.Data[i].EndDate),
                duration: Number(moment(resJson.Data[i].EndDate).diff(moment(resJson.Data[i].StartDate), 'days'))* 24 * 60 * 60 * 1000,
                percent:100,
                type:'project',
                Status:resJson.Data[i].TaskStatus,
                parentId:resJson.Data[i].ParentId
              }
              if(resJson.Data[i].PreTaskId!="")
              {
                  preList.push(resJson.Data[i].PreTaskId)
                  temp.dependentOn=preList
              }
              if(temp.Status=="已完成")
              {
                temp.style=this.finishStyle
              }
              else
              {
                if(temp.Status=="未开始")
                {
                  if(moment()>temp.start)
                  {
                    temp.style=this.warnStyle
                  }
                  else
                  {
                    temp.style=this.doingStyle
                  }
                }
                else if(temp.Status=="进行中")
                {
                    if(moment()>temp.end)
                    {
                      temp.style=this.warnStyle
                    }
                    else
                    {
                      temp.style=this.doingStyle
                    }
                }
                else if(temp.Status=="已取消")
                {
                  temp.style=this.cancelStyle
                }
              }
              this.TaskData.push(temp)
          }
          if(this.TaskData.length>0)
          {
            this.showGantt=true
            this.tasksUpdate(this.TaskData)
          }
          else
          {
            this.showGantt=false
            this.TaskData.push(this.basicTask)
            this.tasksUpdate(this.TaskData)
            this.$message.warning("该项目还没有计划,可以直接新建或者从模板导入!")
          }
        })
    },
    addTask() {
      this.tasks.push({
        id: this.lastId++,
        label:
          '<a href="https://images.pexels.com/photos/423364/pexels-photo-423364.jpeg?auto=compress&cs=tinysrgb&h=650&w=940" target="_blank" style="color:#0077c0;">Yeaahh! you have added a task bro!</a>',
        user:
          '<a href="https://images.pexels.com/photos/423364/pexels-photo-423364.jpeg?auto=compress&cs=tinysrgb&h=650&w=940" target="_blank" style="color:#0077c0;">Awesome!</a>',
        start: getDate(24 * 3),
        duration: 1 * 24 * 60 * 60 * 1000,
        percent: 50,
        type: "project"
      });
    },
    tasksUpdate(tasks) {
      this.tasks = tasks;
    },
    optionsUpdate(options) {
      this.options = options;
    },
    styleUpdate(style) {
      this.dynamicStyle = style;
    },
    hanldleAdd() {
      if(this.ProjectId=="")
      {
        this.$message.warning("请先选择一个项目!")
        return false
      }
      this.$refs.editForm.openForm()
    },
    handleEdit(id) {
      if(this.data.length==0)
      {
        this.$message.warning("该项目还没有任务")
        return false
      }
      this.$refs.editForm.openForm(id)
    },
    showProjcetDrawer()
    {
        this.$http.post('/IPMS_Manage/aspice_project/GetDataList').then(resJson => {
            if (resJson.Success) {
            this.ProjectData = resJson.Data
            this.ProjectDrawerVisible=true
            }
        })
       
    },
    onClose()
    {
        this.ProjectDrawerVisible=false
    },
    SelectProject(id,ProjectName)
    {
        this.ProjectId=id
        this.ProjectName=ProjectName
        // this.queryParam.condition="ProjectId"
        // this.queryParam.keyword=this.ProjectId
        

        this.ProjectDrawerVisible=false
        this.getDataList()
    },
    startDownload(){
        this.loading = true
    },
    finishDownload(){
        this.loading = false
    },
  }
};
</script>

后续项目中遇到的问题将会继续在下面更新,各位大牛如果有好的甘特图插件希望推荐给我,,,

posted @ 2020-03-18 10:00  shenjuncaci  阅读(11575)  评论(15编辑  收藏  举报