#### **⭐️**一款基于Vue3结合Element Plus开发的实用性较强和支持大数据量操作的甘特图组件。
##### 😇支持：

- 查看项目和生产计划
- 拖动或手动编辑计划任务
- 拖动或手动调整计划进度
- 拖动或手动移除计划任务
- 拖动或手动新增计划任务
- 聚焦到目标任务
- 一键返回顶部
- 可视区域伸展或折叠
- 当前可视区域行数范围标记
- 组件提供内置各种钩子函数以满足日常业务需求
- 数据懒加载，支持大数据量（>1w条）任务展示或操作
- ...
##### 🙂安装
```shell
npm i ry-gantt-chart
```
###### 😉引入
```javascript
import { createApp } from "vue";
import App from "./App.vue";
import "./common/css/common.css";
import ElementPlus from "element-plus";
import "element-plus/dist/index.css";

import ryGanttChart from "ry-gantt-chart";
import 'ry-gantt-chart/dist/style.css'

const app = createApp(App)
  .use(ElementPlus)
  .use(ryGanttChart)
  .mount("#app");

```
#### 😁使用
```javascript
<script setup>
import { ref, onMounted, getCurrentInstance, computed } from "vue";
import mockGanttList from "../../mock/ganttList.js";
import unArrangeTableData from "../../mock/unArrangeTableData.js";
import dayjs from "dayjs";
const ctx = getCurrentInstance();

// 甘特图视图起始日期
const startDate = ref(
  dayjs()
    .subtract(2, "day")
    .format("YYYY-MM-DD") + " 00:00:00"
);

// 甘特图结束日期
const endDate = ref(
  dayjs()
    .add(3, "month")
    .format("YYYY-MM-DD") + " 00:00:00"
);

// 甘特图配置
const config = ref({
  type: "month", // year | month | date
  scaleWidth: 150, // px 下刻度每刻度长度
  divideBy: 2, // 上刻度
  rowHeight: 30, // 每条泳道高度
  viewHeight: 300, // 甘特图初始高度
  viewMaxHeight: 400, // 甘特图最大高度
  showCurrentTimeScaler: true, // 是否显示当前时间轴
  showRowsRange: true, // 是否显示当前可视区域显示行数范围
  showGoToTop: true, // 是否显示滚回视图顶部按钮
});

// 数据集合
const list = ref(mockGanttList(20).list);


// 甘特图左侧表格配置
const ganttTableConfig = ref({
  tableColumn: [
    {
      label: "项目",
      prop: "project",
    },
    {
      label: "生产线",
      prop: "prodLine",
    },
    {
      label: "区域",
      prop: "zone",
      "show-overflow-tooltip": true,
    },
  ],
});

// 下方待排区表格配置
const unArrangeConfig = ref({
  list: unArrangeTableData,
  tableColumn: [
    {
      label: "流水号",
      prop: "pkid",
    },
    {
      label: "工作任务",
      prop: "content",
    },
  ],
  // 待排区列表行按钮配置
  rowOperations: [
    {
      name: "查看",
      type: "success",
      click: (scope) => {
        console.log(scope);
      },
    },
  ],
  // 待排区列表操作列配置
  operationsConfig: {
    width: 100,
  },
});
const dateRange = ref([]);

const unitText = computed(() => {
  const units = {
    year: "年",
    month: "月",
    date: "日",
  };
  return units[config.value.type];
});


// 插入新任务
const insertNewTask = () => {
  const rowIndex = 2;
  ctx.refs.ryGanttChart.insertNewTask(rowIndex, {
    pkid: "8021",
    startTime: dayjs()
      .add("1", "day")
      .format("YYYY-MM-DD HH:mm:ss"),
    endTime: dayjs()
      .add("3", "day")
      .format("YYYY-MM-DD HH:mm:ss"),
    content: "检查燃油滤芯No7.",
    background: "rgb(223,102,2)",
  });
};

// 根据id删除某条任务
const handleDeleteTask = () => {
  ctx.refs.ryGanttChart.removeTaskById("23007");
};

// 点击了某条任务
const handleClickTask = (rowIndex, task) => {
  console.log("rowIndex, task: ", rowIndex, task);
};

// 获取操作过的任务集合
const handleGetModifiedData = () => {
  ctx.refs.ryGanttChart.getModifiedData((v) => console.log("v", v));
};

const toggleResults = (val, list) => {
  const idx = list.findIndex((v) => v === val);
  return idx >= list.length - 1 ? list[0] : list[idx + 1];
};
const handleChangeScaleUnit = () => {
  config.value.type = toggleResults(config.value.type, ["year", "month", "date"]);
};
const handleChangeRowHeight = () => {
  config.value.rowHeight = toggleResults(config.value.rowHeight, [30, 40, 55]);
};
const handleChangeTopScale = () => {
  config.value.divideBy = toggleResults(config.value.divideBy, [1, 2, 3, 4]);
};
const handleChangeDownScale = () => {
  config.value.scaleWidth = toggleResults(config.value.scaleWidth, [100, 150, 300]);
};
const handleChangeQty = () => {
  const qty = toggleResults(list.value.length, [20, 200, 500]);
  list.value = mockGanttList(qty)["list"];
};
const handleDateChange = () => {
  [startDate.value, endDate.value] = dateRange.value;
};
// 根据id聚焦到某条任务
const handleFouce = () => {
  ctx.refs.ryGanttChart.scrollToTaskById("23022");
};
</script>

<template>
  <h1 class="title">RY-GANTT-CHART</h1>
  <div class="ctrl-wrapper">
    <div style="width: 300px;overflow: hidden;padding-right:20px;">
      <el-date-picker
        style="width:95%"
        type="daterange"
        v-model="dateRange"
        range-separator="至"
        start-placeholder="开始时间"
        end-placeholder="结束时间"
        value-format="YYYY-MM-DD HH:mm:ss"
        :clearable="false"
        @change="handleDateChange"
      ></el-date-picker>
    </div>

    <el-button type="success" @click="handleChangeQty">数据量</el-button>
    <el-button type="success" @click="handleChangeTopScale">上刻度宽度</el-button>
    <el-button type="success" @click="handleChangeDownScale">下刻度宽度</el-button>
    <el-button type="success" @click="handleChangeRowHeight">行高</el-button>
    <el-button type="success" @click="handleDeleteTask">删除某条任务</el-button>
    <el-button type="success" @click="handleFouce">聚焦到某一任务</el-button>
    <el-button type="success" @click="insertNewTask">插入新增任务</el-button>
    <el-button type="success" @click="handleGetModifiedData">获取操作项</el-button>
    <el-button type="success" @click="handleChangeScaleUnit">{{ unitText }}</el-button>
  </div>
  <ryGanttChart
    ref="ryGanttChart"
    :config="config"
    :list="list"
    :startDate="startDate"
    :endDate="endDate"
    :ganttTableConfig="ganttTableConfig"
    :unArrangeConfig="unArrangeConfig"
  >
    <!-- 任务条内容插槽 -->
    <template v-slot:taskContent="slotProps">
      <div class="text" @click="handleClickTask(slotProps.rowIndex, slotProps.task)">
        {{ `${slotProps.task.content} (id:${slotProps.task.pkid})` }}
      </div>
    </template>
  </ryGanttChart>
</template>

<style scoped lang="scss">
.title {
  margin: 0;
  text-align: center;
  font-family: "Times New Roman", Times, serif;
}
.text {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ctrl-wrapper {
  display: flex;
  justify-content: flex-end;
  align-items: center;
  padding: 10px 0;

  h1 {
    margin: 0;
    width: 500px;
    font-size: 30px;
    flex: 1;
  }
}
</style>

```
#### 🧐数据结构
```javascript
import { dayjs } from "../common/utils";

export default (qty) => {
  let taskCount = 23000;
  let count = 0;
  return {
    list: Array.from({ length: qty }, (_, index) => {
      count += 3;

      return {
        pkid: index.toString(), // pkid ->当前行父任务主键
        project:
          "ROCKET" +
          dayjs()
            .add(count, "day")
            .format("MMDD"),
        prodLine: "生产线-" + index,
        zone: "月球",
        /* tasks -> 子任务对象属性  */
        tasks: [
          {
            pkid: (taskCount++).toString(), // 子任务主键
            startTime:
              dayjs()
                .subtract(1, "day")
                .format("YYYY-MM-DD") + " 00:00:00", // startTime -> 任务开始日期
            endTime:
              dayjs()
                .add(1, "day")
                .format("YYYY-MM-DD") + " 00:00:00", // endTime ->任务结束日期
            content: "Daily check", // 任务内容
            background: "rgb(182,220,22)", // background ->任务条背景色
            color: "black", // color ->任务条字体颜色
            disabled: false, // disabled ->当前任务是否无法被拖动或编辑
          },
          // ...
       
        ],
      };
    }),
  };
};

```
```javascript
export default [
  {
    pkid: "1055",
    content: "安装车门",
    rangeDays: 3,
  },
  {
    pkid: "1056",
    content: "安装倒后镜",
    rangeDays: 3,
  },
  {
    pkid: "1067",
    content: "全车检查",
    rangeDays: 3,
  },
  {
    pkid: "1058",
    content: "更换ECU",
    rangeDays: 3,
  },
];

```
#### RY-GANTT-TABLE 属性
| **属性名** | **参数 ** | **说明 ** |
| --- | --- | --- |
| list:Array | 数据结构见demo示例 | 甘特图源数据  |
| startDate:String | "YYYY-MM-DD HH:mm:ss" | 刻度尺起始日期 |
| endDate:String | "YYYY-MM-DD HH:mm:ss" | 刻度尺结束日期 |
| disabeld:Boolean | default:false | 甘特图是否只读 |
| showSideExpander | default:false | 是否显示“展开收起”侧栏按钮 |
| showBottomExpander | default:true | 是否显示“展开更多”甘特图视图按钮 |
| showUnArrangeTable | default:true | 是否显示待排区，默认true |
| config：Object |  | 甘特图视图区配置 |
|  | type:String &#124; 'year' ,'month' , 'date' | 刻度尺单位范围 |
|  | scaleWidth:Number | 刻度尺下部每刻度间隔宽度（px） |
|  | divideBy：Number | 刻度尺上部刻度根据每间隔下部刻度多少个进行划分 |
|  | rowHeight:Number | 泳道行高(px) |
|  | viewHeight:Number | 甘特图出初始高度 |
|  | viewMaxHeight:Number | 甘特图最大高度 |
|  | showCurrentTimeScaler:Boolean | 是否显示当前时间节点标线 |
|  | showGoToTop：Boolean | 是否显示返回顶部按钮 |
|  | showRowsRange:Boolean | 是否显示当前可视区域行数范围 |
|  | topScaleFormatter(obj) | obj：Object &#124; 刻度尺上部每刻度内容格式化处理函数 &#124; 需返回字符串 |
|  | downScaleFormatter(obj) | obj：Object &#124; 刻度尺下部每刻度内容格式化处理函数 &#124; 需返回字符串 |
| unArrangeConfig:Object | 
 | 待排区域表格配置 |
|  | tableColumn:Array | 表头配置  |
|  | data:Array | 待排区域表格源数据 |
|  | rowOperations:Array | 行操作配置  |
|  | operationsConfig:Object |  |
| ganttTableConfig:Object |  | 甘特图左侧表格配置 |
|  | tableColumn:Array | 表头配置 |
| beforeMountData：Function | (data）=>data | 挂载甘特图视图层数据前调用，需返回数据对象。 |
| afterMountedData:Function | (data) =>{} | 挂载甘特图视图层数据完成后调用 |
| beforeRemove:Function | (task,next)=>{} | 完成移除任务前调用 &#124; task:Object 当前任务对象 &#124; next:Function 调用next执行后续逻辑 |
| beforeHorizontalMove:Function | (task,next)=>{} | 完成水平移动任务前调用 &#124; task:Object 当前任务对象 &#124; next:Function 调用next执行后续逻辑 |
| beforeVerticalMove:Function | (task,next)=>{} | 完成垂直移动任务前调用 &#124; task:Object 当前任务对象 &#124; next:Function 调用next执行后续逻辑 |
| beforeInsertToGantt:Function | (task,next)=>{} | 完成插入新数据前调用 &#124; task:Object 当前任务对象 &#124; next:Function 调用next执行后续逻辑 |
| beforeDropToUnArrange | (task,next)=>{} | 完成放置到待排区前校验  &#124; task:Object 当前任务对象 &#124; next:Function 调用next执行后续逻辑 |
| afterDropToUnArrange | (task)=>{} | 完成放置到待排区后调用  &#124; task:Object 当前任务对象  |

#### RY-GANTT-CHART 方法
| **名称** | **参数** | **返回值** |
| --- | --- | --- |
| insertNewTask(rowIndex,task) | 插入任务task到某行row | rowIndex : Number &#124; 行索引 task:Object 任务内容 （必要属性参考甘特图数据结构） |
| getModifiedData（res） | (res)=>{} &#124; res：变化过的任务数据集合 | Promise <Array> &#124; 变化过的任务数据集合 |
| getUnArrangeTableData（） |  | Array &#124; 待排区表格数据 |
| scrollToTaskById(id) | id:string | 通过任务主键让视图滚动到该任务所在位置，并且会有高亮闪烁一次的圆点出现 |
| removeTaskById(id) | id:string | 根据任务id，从甘特图移除某条任务 |
| editTask({targetRowIndex,task}) | targetRowIndex:number , task:Object | 手动编辑某条任务 &#124; targetRowIndex ：要把该任务移动到某行的索引 &#124; task ：任务对象，任务的pkid ，起始日期，结束日期字段不能为空。 |

#### RY-GANTT-CHART 事件
| **名称** | **说明** | **回调参数** |
| --- | --- | --- |
| expander-change | 收起展开按钮change事件 | type:String,isExpand:Boolean &#124; type : side、bottom  |
| scaler-click | 刻度尺区域点击事件 |  |

#### RY-GANTT-CHART SLOTS 插槽
| **名称** | **说明** | **类型** |
| --- | --- | --- |
| leftHeader |  | - |
| taskContent | 任务内容 | 作用域参数为 { task } ，该任务对象 |

**Demo：
  1. npm run dev 启动这个项目，查看/examples/pages/demo 或 /examples/pages/demo1 的页面
  2. 或 https://gitee.com/RYANLLL/ry-gantt-chart-demo
