推薦一個支持 vue2、vue3 甘特圖 vxe-gantt 秒級渲染萬級數據量,高性能甘特圖組件,支持常用的項目管理甘特圖功能、自定義日期軸、右鍵菜單、可編輯、拖拽任務、依賴線、里程碑等等功能非常多。 接下來做個10萬行任務以內的渲染量測試,不僅功能強大,渲染性能也是非常強的。

https://gantt.vxeui.com/

列表虛擬滾動

縱向虛擬滾動,設置 virtual-y-config={ enabled: true, gt: 0 } 和 height | max-height 來開啓

extend_gantt_chart_gantt_scroll_vertical

<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>

子任務虛擬滾動

extend_gantt_chart_gantt_scroll_tree

<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>

https://gitee.com/x-extends/vxe-gantt