简介
vue3 原生开源甘特图:地址:https://zunnzunn.github.io/vue-ganttastic/
功能相对简单;做项目进度展示,会议室预定展示还可以;不支持复杂粒度控制;
1、安装
pnpm install @infectoone/vue-ganttastic
2、配置
src/main.js 配置
import { createApp } from "vue"
import App from "./App.vue"
...
import ganttastic from '@infectoone/vue-ganttastic'
...
createApp(App)
.use(ganttastic)
.mount('#app')
官方样例:https://zunnzunn.github.io/vue-ganttastic/examples.html
3、使用
element-pluis +vue-ganttastic 静态使用样例
<template>
<g-gantt-chart
:chart-start="`${applicationDate} 07:00:00`"
:chart-end="`${applicationDate} 23:59:59`"
precision="hour"
date-format="YYYY-MM-DD HH:mm:ss"
bar-start="startTime"
bar-end="endTime"
color-scheme="sky"
:push-on-overlap="false"
:no-overlap="true"
:grid="true"
:row-height="40"
label-column-title="会议室"
label-column-width="300px"
style="width: 87vw;"
>
<template #upper-timeunit="{ date }">
<el-date-picker
v-model="applicationDate"
type="date"
placeholder="选择时间"
value-format="YYYY-MM-DD"
:clearable="false"
style="width: 130px;"
/>
</template>
<template #timeunit="{label, value}">
<el-text style="text-align: left;width: 100%;">{{ label }}</el-text>
</template>
<template #label-column-row="{label}">
<el-tooltip
class="box-item"
effect="dark"
:content="`${label.roomName},可容纳:${label.capacity}人`"
placement="right"
>
<el-text style="width: 270px;" :truncated="true">{{
label.roomName
}},{{ label.capacity }}人
</el-text>
</el-tooltip>
</template>
<g-gantt-row v-for="(item,index) in meetingRoomRequestStatusList" :label="item" :bars="item.rowBarList ">
<template #bar-label="{bar}">
<el-tooltip
class="box-item"
effect="dark"
:content="`${bar.ganttBarConfig?.label}`"
raw-content
placement="top"
>
<el-text style="width: 100%;text-align: center"> {{ bar.ganttBarConfig?.label }}</el-text>
</el-tooltip>
</template>
</g-gantt-row>
</g-gantt-chart>
</template>
<script setup>
import {ref} from "vue"
// 当选选择的时间
const applicationDate = ref('2025-05-12')
// 会议室申请列表:api见:https://zunnzunn.github.io/vue-ganttastic/
const meetingRoomRequestStatusList = ref([{
"id": 1,
"roomName": "1#310会议室",
"capacity": 30,
"rowBarList": [{
"startTime": "2025-05-12 11:00:00",
"endTime": "2025-05-12 12:00:00",
"ganttBarConfig": {
"id": 2,
"label": "XXX",
// 是否有操作条
"hasHandles": false,
// 是否可移动:true 为不可一定
"immobile": true,
"style": {
"background": "#2CABE3",
"borderRadius": "5px",
"color": "black"
}
}
}]
}, {
"id": 2,
"roomName": "1#303-会议室",
"capacity": 8,
"rowBarList": [{
"startTime": "2025-05-12 09:30:00",
"endTime": "2025-05-12 11:00:00",
"ganttBarConfig": {
"id": 6,
"label": "XXXXX",
"hasHandles": false,
"immobile": true,
"style": {
"background": "#2CABE3",
"borderRadius": "5px",
"color": "black"
}
}
}]
}, {
"id": 3,
"roomName": "1#3F录播室",
"rowBarList": [{
"startTime": "2025-05-12 07:00:00",
"endTime": "2025-05-12 08:00:00",
"ganttBarConfig": {
"id": 3,
"label": "XXX",
"hasHandles": false,
"immobile": true,
"style": {
"background": "#2CABE3",
"borderRadius": "5px",
"color": "black"
}
}
}, {
"startTime": "2025-05-12 07:00:00",
"endTime": "2025-05-12 08:00:00",
"ganttBarConfig": {
"id": 7,
"label": "XXX",
"hasHandles": false,
"immobile": true,
"style": {
"background": "#2CABE3",
"borderRadius": "5px",
"color": "black"
}
}
}, {
"startTime": "2025-05-12 10:00:00",
"endTime": "2025-05-12 11:30:00",
"ganttBarConfig": {
"id": 8,
"label": "XXXX",
"hasHandles": false,
"immobile": true,
"style": {
"background": "#2CABE3",
"borderRadius": "5px",
"color": "black"
}
}
}]
}, {
"id": 4,
"roomName": "1#2F实操室",
"capacity": 12,
"rowBarList": [{
"startTime": "2025-05-12 07:00:00",
"endTime": "2025-05-12 08:00:00",
"ganttBarConfig": {
"id": 4,
"label": "XXX",
"hasHandles": false,
"immobile": true,
"style": {
"background": "#2CABE3",
"borderRadius": "5px",
"color": "black"
}
}
}]
}, {
"id": 5,
"roomName": "1@2F教研室",
"rowBarList": []
}, {
"id": 6,
"roomName": "3F连廊会客区",
"rowBarList": [{
"startTime": "2025-05-12 07:00:00",
"endTime": "2025-05-12 08:00:00",
"ganttBarConfig": {
"id": 5,
"label": "XX",
"hasHandles": false,
"immobile": true,
"style": {
"background": "#2CABE3",
"borderRadius": "5px",
"color": "black"
}
}
}]
}, {
"id": 7,
"roomName": "1#5F西侧小会议室",
"rowBarList": []
}, {
"id": 8,
"roomName": "1#5F西侧大会议室",
"rowBarList": []
}, {
"id": 9,
"roomName": "2号楼会议室",
"isSupportVideo": false,
"rowBarList": []
}, {
"id": 10,
"roomName": "2号楼报告厅",
"rowBarList": []
}])
</script>
效果图:
4、常用属性介绍
4.1 g-gantt-chart 主属性介绍
来源:https://zunnzunn.github.io/vue-ganttastic/GGanttChart.html
1)chart-start、chart-start ,图标时间范围
2)precision:最小单位,枚举:hour, day, date, week and month
3)date-format:传入的时间格式:"YYYY-MM-DD HH:mm:ss"
4)bar-start、bar-end 甘特图的开始结束指向的字段
5)color-scheme:配色方案,可每个区域指定颜色,见官方文档
6)push-on-overlap:false,拖动和重叠时条不“相互推动”
7)no-overlap:true,不允许进度条覆盖
8)grid:网格是否显示
9)row-height:行高
10)label-column-title:该属性不为空,则左侧列独立成列
11)label-column-width:指定label-column-title的宽度
12)支持事件:
click-bar:点击进度条
drag-bar:拖拽进度条中
dragend-bar 拖动进度条结束后:可拿到时间范围;
mouseenter-bar:鼠标覆盖事件;还支持单击、双击进度条;不支持点击空白区域;
<g-gantt-chart
:chart-start="`${applicationDate} 07:00:00`"
:chart-start="`${applicationDate} 23:59:59`"
precision="hour"
date-format="YYYY-MM-DD HH:mm:ss"
bar-start="startTime"
bar-end="endTime"
color-scheme="sky"
:push-on-overlap="false"
:no-overlap="true"
:grid="true"
:row-height="40"
label-column-title="会议室"
label-column-width="300px"
@click-bar="handleGanttBarClick"
@drag-bar="handleGanttDrag"
@dragend-bar="handleGanttDragend"
@mouseenter-bar="handleGanttMouseenter">
</g-gantt-chart>
4.2 插槽
常用插槽
1)g-gantt-chart 的 upper-timeunit :时间轴显示日期的部分
2)g-gantt-chart 的 timeunit :时间轴上的刻度部分
3)g-gantt-chart 的 label-column-row :g-gantt-chart 的 label-column-title属性不为空,标题列的插槽;假设 label-column-title 属性为空,则插槽为:label-column-title
4)g-gantt-row 的 bar-label:甘特图进度条的插槽
注:g-gantt-row 的 label 可以把整个对象传递过去,来控制 g-gantt-chart 的 label-column-row 的接受label对象;
<g-gantt-chart>
<template #upper-timeunit="{ date }">
时间轴显示日期的部分{{date}}
</template>
<template #timeunit="{label, value}">
<el-text style="text-align: left;width: 100%;">时间刻度:{{ label }}</el-text>
</template>
<template #label-column-row="{label}">
标题列:
<el-text style="width: 270px;" :truncated="true">{{
label.roomName
}},{{ label.capacity }}人,{{ label.adminList?.map(item => item.userName).join('、') || '' }}
</el-text>
</template>
<g-gantt-row v-for="(item,index) in meetingRoomRequestStatusList" :label="item" :bars="item.rowBarList ">
<template #bar-label="{bar}">
甘特图上的项目进度条
{{bar.ganttBarConfig?.label}}
</template>
</g-gantt-row>
</g-gantt-chart>
5、动态增行
给在 meetingRoomRequestStatusList.value 中 某个会议室的 rowBarList 添加一个进度条属性即可
// 添加会议室单行
const addGanttRow = () => {
editMeetingRoomRequestStatus.value.rowBarList.push({
startTime: formData.value.startTime,
endTime: formData.value.endTime,
ganttBarConfig: {
id: formData.value.id,
label: '当前',
hasHandles: true,
immobile: false,
style: {
background: "#00ff00",
borderRadius: "20px",
color: "black"
}
}
})
}
6 判断点击了哪个会议室的几点
handleChartClick 为实际点击事件,但为了防止拖拽事件和点击事件的冲突
所以: 使用变量 isDragging + 3个事件,来避免误判;
<g-gantt-chart
@click-bar="handleGanttBarClick"
@drag-bar="handleGanttDrag"
@dragend-bar="handleGanttDragend"
>
<g-gantt-row v-for="(item,index) in meetingRoomRequestStatusList" :label="item" :bars="item.rowBarList"
@click="(event) => handleChartClick(event, item, index)">
</g-gantt-row>
</g-gantt-chart>
<script setup>
import dayjs from "dayjs";
// 记录是否在拖拽
const isDragging = ref(false)
// 鼠标点击甘特图进度条
const handleGanttBarClick = ({bar, e, datetime})=>{
isDragging.value = true;
}
// 拖拽甘特图,标记正在拖拽
const handleGanttDrag = ({bar, e}) => {
isDragging.value = true;
}
// 拖拽结束后
const handleGanttDragend = (e) => {
// 拖拽结束后 1s 后允许点击
setTimeout(() => {
isDragging.value = false;
}, 500)
}
// 点击
const handleChartClick = (event, row, rowIndex) => {
setTimeout(() => {
isDragging.value = false;
}, 500)
// 正在拖拽,则排除此次点击
if (isDragging.value) {
return
}
// 图表可点击的宽度(右侧时间轴区域)
const chartStart = new Date(`${applicationDate.value} 07:00:00`).getTime();
const chartEnd = new Date(`${applicationDate.value} 23:59:59`).getTime();
const totalTimeMs = chartEnd - chartStart;
// 计算点击在该行那个位置:offsetX 相对开始,target.offsetWidth:控件宽度
let radio = event.offsetX / event.target.offsetWidth;
if (radio < 0.0 || radio > 1.0) {
return;
}
// 计算点击的时间点
const clickedTime = new Date(chartStart + radio * totalTimeMs)
console.log("点击的时间:", dayjs(clickedTime).format('YYYY-MM-DD HH:mm:ss'));
console.log("点击的行:", rowIndex, row);
}
</script>