推荐一个支持 vue2、vue3 甘特图 vxe-gantt 秒级渲染万级数据量,高性能甘特图组件,支持常用的项目管理甘特图功能、自定义日期轴、右键菜单、可编辑、拖拽任务、依赖线、里程碑等等功能非常多。 接下来做个10万行任务以内的渲染量测试,不仅功能强大,渲染性能也是非常强的。
列表虚拟滚动
纵向虚拟滚动,设置 virtual-y-config={ enabled: true, gt: 0 } 和 height | max-height 来开启
<template>
<div>
<vxe-select v-model="rowSize" :options="dataOptions" @change="changeRowSizeEvent"></vxe-select>
<vxe-gantt ref="ganttRef" v-bind="ganttOptions"></vxe-gantt>
</div>
</template>
<script setup>
import { ref, reactive } from 'vue'
import { VxeUI } from 'vxe-pc-ui'
const ganttRef = ref()
const rowSize = ref(500)
const dataOptions = ref([
{ label: '加载 3 行', value: 3 },
{ label: '加载 20 行', value: 20 },
{ label: '加载 100 行', value: 100 },
{ label: '加载 500 行', value: 500 },
{ label: '加载 1000 行', value: 1000 },
{ label: '加载 5000 行', value: 5000 },
{ label: '加载 10000 行', value: 10000 },
{ label: '加载 50000 行', value: 50000 },
{ label: '加载 100000 行', value: 100000 }
])
const ganttOptions = reactive({
border: true,
loading: false,
showOverflow: true,
showHeaderOverflow: true,
showFooterOverflow: true,
height: 600,
rowConfig: {
keyField: 'id' // 行主键
},
taskBarConfig: {
showProgress: true, // 是否显示进度条
showContent: true, // 是否在任务条显示内容
moveable: true, // 是否允许拖拽任务移动日期
resizable: true, // 是否允许拖拽任务调整日期
barStyle: {
round: true, // 圆角
bgColor: '#fca60b', // 任务条的背景颜色
completedBgColor: '#65c16f' // 已完成部分任务条的背景颜色
}
},
taskViewConfig: {
tableStyle: {
width: 480 // 表格宽度
}
},
virtualYConfig: {
gt: 0,
enabled: true
},
columns: [
{ type: 'seq', width: 70 },
{ field: 'title', title: '任务名称' },
{ field: 'start', title: '开始时间', width: 100 },
{ field: 'end', title: '结束时间', width: 100 },
{ field: 'progress', title: '进度(%)', width: 80 }
],
data: []
})
// 模拟后端数据
const loadList = (size = 200) => {
ganttOptions.loading = true
setTimeout(() => {
const dataList = []
for (let i = 0; i < size; i++) {
dataList.push({
id: 10000 + i,
title: `任务${i + 1}`,
start: i % 3 ? '2024-03-03' : i % 2 ? '2024-03-10' : '2024-03-22',
end: i % 3 ? '2024-03-11' : i % 2 ? '2024-03-19' : '2024-04-04',
progress: i % 2 ? 50 : 30
})
}
const $gantt = ganttRef.value
if ($gantt) {
const startTime = Date.now()
$gantt.loadData(dataList).then(() => {
ganttOptions.loading = false
VxeUI.modal.message({
content: `加载时间 ${Date.now() - startTime} 毫秒`,
status: 'success'
})
})
} else {
ganttOptions.loading = false
}
}, 150)
}
const changeRowSizeEvent = () => {
loadList(rowSize.value)
}
loadList(rowSize.value)
</script>
子任务虚拟滚动
<template>
<div>
<vxe-select v-model="rowSize" :options="dataOptions" @change="changeRowSizeEvent"></vxe-select>
<vxe-gantt ref="ganttRef" v-bind="ganttOptions"></vxe-gantt>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { VxeUI } from 'vxe-pc-ui'
const ganttRef = ref()
const rowSize = ref(500)
const dataOptions = ref([
{ label: '加载 3 行', value: 3 },
{ label: '加载 20 行', value: 20 },
{ label: '加载 100 行', value: 100 },
{ label: '加载 500 行', value: 500 },
{ label: '加载 1000 行', value: 1000 },
{ label: '加载 5000 行', value: 5000 },
{ label: '加载 10000 行', value: 10000 },
{ label: '加载 50000 行', value: 50000 },
{ label: '加载 100000 行', value: 100000 }
])
const ganttOptions = reactive({
border: true,
showOverflow: true,
showHeaderOverflow: true,
showFooterOverflow: true,
height: 600,
treeConfig: {
transform: true,
rowField: 'id',
parentField: 'parentId'
},
taskBarConfig: {
showProgress: true, // 是否显示进度条
showContent: true, // 是否在任务条显示内容
moveable: true, // 是否允许拖拽任务移动日期
resizable: true, // 是否允许拖拽任务调整日期
barStyle: {
round: true, // 圆角
bgColor: '#fca60b', // 任务条的背景颜色
completedBgColor: '#65c16f' // 已完成部分任务条的背景颜色
}
},
taskViewConfig: {
tableStyle: {
width: 480 // 表格宽度
}
},
virtualYConfig: {
gt: 0,
enabled: true
},
columns: [
{ type: 'seq', width: 70 },
{ field: 'title', title: '任务名称', treeNode: true },
{ field: 'start', title: '开始时间', width: 100 },
{ field: 'end', title: '结束时间', width: 100 }
],
data: []
})
// 模拟后端数据
const loadTreeData = (nodeSize) => {
ganttOptions.loading = true
setTimeout(() => {
const dataList = []
let idCounter = 1000000
const rootCount = Math.floor(nodeSize / 2)
const roots = []
for (let i = 0; i < rootCount; i++) {
const rootNode = {
id: idCounter++,
parentId: null,
title: `任务${i + 1}`,
start: i % 3 ? '2024-03-03' : i % 2 ? '2024-03-10' : '2024-03-22',
end: i % 3 ? '2024-03-11' : i % 2 ? '2024-03-19' : '2024-04-04',
progress: i % 2 ? 50 : 30
}
roots.push(rootNode)
dataList.push(rootNode)
}
let generatedCount = rootCount
const secondLevelNodes = []
const secondLevelCount = Math.min(Math.floor(Math.random() * (nodeSize - generatedCount)) + 1, nodeSize - generatedCount)
for (let i = 0; i < secondLevelCount; i++) {
const parent = roots[Math.floor(Math.random() * roots.length)]
const node = {
id: idCounter++,
parentId: parent.id,
title: `任务${i + 1}`,
start: i % 3 ? '2024-03-05' : i % 2 ? '2024-03-8' : '2024-03-17',
end: i % 3 ? '2024-03-09' : i % 2 ? '2024-03-18' : '2024-03-27',
progress: i % 2 ? 50 : 30
}
secondLevelNodes.push(node)
dataList.push(node)
generatedCount++
}
if (generatedCount < nodeSize) {
const thirdLevelCount = nodeSize - generatedCount
for (let i = 0; i < thirdLevelCount; i++) {
let parent
if (secondLevelNodes.length > 0) {
parent = secondLevelNodes[Math.floor(Math.random() * secondLevelNodes.length)]
} else {
parent = roots[Math.floor(Math.random() * roots.length)]
}
const node = {
id: idCounter++,
parentId: parent.id,
title: `任务${i + 1}`,
start: i % 3 ? '2024-04-03' : i % 2 ? '2024-04-08' : '2024-04-14',
end: i % 3 ? '2024-04-07' : i % 2 ? '2024-04-15' : '2024-04-22',
progress: i % 2 ? 50 : 30
}
dataList.push(node)
generatedCount++
}
}
const $gantt = ganttRef.value
if ($gantt) {
const startTime = Date.now()
$gantt.loadData(dataList).then(() => {
ganttOptions.loading = false
VxeUI.modal.message({
content: `加载时间 ${Date.now() - startTime} 毫秒`,
status: 'success'
})
})
} else {
ganttOptions.loading = false
}
}, 150)
}
const changeRowSizeEvent = () => {
loadTreeData(rowSize.value)
}
onMounted(() => {
loadTreeData(rowSize.value)
})
</script>