Vue3 使用 echarts 甘特图(GanttChart)
Documentation - Apache ECharts
可自定义设置以下属性
- 甘特图数据(ganttData),类型:Gantt[],必传,默认 []
- 容器宽度(width),类型:number | string,默认 ‘100%’
- 容器高度(height),类型:number | string,默认 ‘100%’
- 主题色(themeColor),类型: string,默认 ‘#1677ff’
- 状态映射表(statusMap),类型: Status[],默认 []
type Gantt
- 名称(name),类型:string,必传
- 开始时间(start),类型:string | number | Date,必传
- 结束时间(end),类型:string | number | Date,必传
- 状态值(status),类型:number | string,用于着色
type Status
- 状态值(value),类型:number | string,必传
- 状态名(label),类型:string,必传
- 状态颜色(color),类型:string,必传
效果如下图:echarts@6.0.0
在线预览
安装插件
pnpm add echarts
创建甘特图组件GanttChart.vue
<script setup lang=\"ts\">import { ref, useTemplateRef, onMounted, onBeforeUnmount, watch, computed } from \'vue\'import { useResizeObserver } from \'../utils\'import * as echarts from \'echarts/core\'import { TooltipComponent, GridComponent, LegendComponent } from \'echarts/components\'import { CustomChart } from \'echarts/charts\'import { CanvasRenderer } from \'echarts/renderers\'echarts.use([TooltipComponent, GridComponent, LegendComponent, CustomChart, CanvasRenderer])const chartRef = useTemplateRef(\'chartRef\')const myChart = ref<any>()let option: anyinterface Gantt { name: string // 名称 start: string | number | Date // 开始时间 end: string | number | Date // 结束时间 status: number | string // 状态值,用于着色}interface Status { value: number | string // 状态值 label: string // 状态名 color: string // 状态颜色}interface Props { ganttData?: Gantt[] // 数据 width?: string | number // 容器宽度 height?: string | number // 容器高度 themeColor?: string // 主题色 statusMap?: Status[] // 状态映射表}const props = withDefaults(defineProps<Props>(), { ganttData: () => [], width: \'100%\', height: \'100%\', themeColor: \'#1677FF\', statusMap: () => []})const chartWidth = computed(() => { if (typeof props.width === \'number\') { return `${props.width}px` } return props.width})const chartHeight = computed(() => { if (typeof props.height === \'number\') { return `${props.height}px` } return props.height})watch( () => props.ganttData, () => { myChart.value && myChart.value.setOption(buildOption(), true) }, { deep: true })// 统一转换为时间戳格式function toTimestamp(value: string | number | Date): number { if (typeof value === \'number\') return value if (value instanceof Date) return value.getTime() // 替换空格为 \'T\' 以提升跨浏览器解析稳定性 const normalized = value.replace(\' \', \'T\') const t = Date.parse(normalized) return isNaN(t) ? new Date(value).getTime() : t}// 处理数据:输出 y 轴分类与自定义系列的数据function getSeriesData() { const yAxisData = props.ganttData.map((item) => item.name) const data = props.ganttData.map((item, index) => { return { value: [ index, // 类别索引 toTimestamp(item.start), // 开始时间戳 toTimestamp(item.end), // 结束时间戳 item.status ], name: item.name } }) return { yAxisData, data }}// 根据状态值获取对应的颜色function getStatusColor(value: string | number): string { const statusItem = props.statusMap.find((status) => String(status.value) === String(value)) const color = statusItem ? statusItem.color : \'#5470c6\' // 默认颜色 return color}function renderGanttBar(params: any, api: any) { const categoryIndex = api.value(0) const startCoord = api.coord([api.value(1), categoryIndex]) const endCoord = api.coord([api.value(2), categoryIndex]) const barHeight = Math.max(api.size([0, 1])[1] * 0.6, 2) const rectShape = { x: startCoord[0], y: startCoord[1] - barHeight / 2, width: Math.max(endCoord[0] - startCoord[0], 0), height: barHeight } const shape = echarts.graphic.clipRectByRect(rectShape, { x: params.coordSys.x, y: params.coordSys.y, width: params.coordSys.width, height: params.coordSys.height }) if (shape) { // 为矩形添加圆角 ;(shape as any).r = 2 } // 获取状态值 const statusValue = api.value(3) // 获取对应的颜色 const color = getStatusColor(statusValue) return { // 返回自定义矩形元素 type: \'rect\', shape, style: { fill: color, stroke: color } }}// 构建图表配置项 optionfunction buildOption() { const { yAxisData, data } = getSeriesData() option = { grid: { top: 0, left: 0, right: 0, bottom: 42, containLabel: true }, legend: { orient: \'horizontal\', left: \'center\', bottom: 0, itemGap: 18, textStyle: { fontWeight: 400, fontSize: 14, color: \'#333\', lineHeight: 22 }, data: props.statusMap.map((status: Status) => { return status.label }) }, tooltip: { trigger: \'item\', formatter: (params: any) => { // console.log(\'params\', params) const start = params.value[1] const end = params.value[2] const format = (timestamp: number) => { return echarts.time.format(timestamp, \'{HH}:{mm}:{ss}\', false) } const piecesMap = new Map(props.statusMap.map((status: Status) => [String(status.value), status.label])) const statusLabel = piecesMap.get(String(params.value[3])) || \'\' return `${params.name}
${format(start)} - ${format(end)}${statusLabel ? `
状态:${params.marker} ${statusLabel}` : \'\'}` } }, xAxis: { type: \'time\', position: \'top\', splitLine: { show: true, lineStyle: { type: \'dashed\', color: \'#ccc\' } }, axisLabel: { color: \'#333\', fontWeight: 400, fontSize: 14, lineHeight: 22, align: \'center\', showMinLabel: true, showMaxLabel: true, formatter: (value: number) => { // return echarts.time.format(value, \'{yyyy}-{MM}-{dd} {HH}:{mm}:{ss}\', false) return echarts.time.format(value, \'{HH}:{mm}\', false) } } }, yAxis: { type: \'category\', inverse: true, data: yAxisData, axisLabel: { color: \'rgba(0, 0, 0, 0.88)\', fontWeight: 500, fontSize: 14, lineHeight: 22 } }, series: props.statusMap.map((status) => ({ name: status.label, type: \'custom\', renderItem: renderGanttBar, // 以 Function 形式提供图形渲染的逻辑 encode: { x: [1, 2], y: 0 }, // 定义 data 的哪个维度被编码成什么 itemStyle: { // 图形样式 color: getStatusColor(status.value) }, data: data.filter((item) => String(item.value[3]) === String(status.value)) })) } return option}function initChart() { myChart.value = echarts.init(chartRef.value) myChart.value.setOption(buildOption())}function showLoading (config: any) { myChart.value && myChart.value.showLoading(\'default\', { text: \'\', color: props.themeColor, ...config }) // 显示加载动画效果}function hideLoading () { myChart.value && myChart.value.hideLoading() // 隐藏动画加载效果}// 监听图表容器尺寸变化,重新初始化图表useResizeObserver(chartRef, () => { requestAnimationFrame(() => { myChart.value && myChart.value.resize() })})onMounted(() => { initChart()})onBeforeUnmount(() => { myChart.value && myChart.value.dispose() // 销毁图表实例})defineExpose({ showLoading, hideLoading})</script><template> <div class=\"chart-container\" ref=\"chartRef\" :style=\"`--chart-width: ${chartWidth}; --chart-height: ${chartHeight};`\" ></div></template><style lang=\"less\" scoped>.chart-container { width: var(--chart-width); height: var(--chart-height);}</style>
在要使用的页面引入
<script setup lang=\"ts\">import GanttChart from \'./GanttChart.vue\'import { useTemplateRef, ref, onMounted } from \'vue\'const ganttRef = useTemplateRef(\'ganttRef\')const ganttData = ref<any[]>([])onMounted(() => { getGanttData()})function getGanttData () { // 模拟接口调用 ganttRef.value.showLoading() setTimeout(() => { ganttData.value.push( { name: \'任务一\', start: \'2025-08-12 00:00:00\', end: \'2025-08-12 04:00:00\', status: \'1\' }, { name: \'任务二\', start: \'2025-08-12 04:00:00\', end: \'2025-08-12 05:00:00\', status: \'2\' }, { name: \'任务三\', start: \'2025-08-12 05:00:00\', end: \'2025-08-12 06:00:00\', status: \'3\' }, { name: \'任务四\', start: \'2025-08-12 06:00:00\', end: \'2025-08-12 08:00:00\', status: \'4\' }, { name: \'任务五\', start: \'2025-08-12 08:00:00\', end: \'2025-08-12 13:00:00\', status: \'5\' } ) ganttRef.value.hideLoading() }, 1500)}const statusMap = [ { value: \'1\', label: \'启动\', color: \'#5470c6\' }, { value: \'2\', label: \'运行\', color: \'#91cc75\' }, { value: \'3\', label: \'等待\', color: \'#fac858\' }, { value: \'4\', label: \'成功\', color: \'#ee6666\' }, { value: \'5\', label: \'失败\', color: \'#73c0de\' }, { value: \'6\', label: \'停止\', color: \'#3ba272\' }]</script><template> <div> <h1>GaugeChart 参考文档</h1> <ul class=\"m-list\"> <li> <a class=\"u-file\" href=\"https://echarts.apache.org/handbook/zh/get-started\" target=\"_blank\">使用手册</a> </li> <li> <a class=\"u-file\" href=\"https://echarts.apache.org/handbook/zh/basics/import\" target=\"_blank\">在项目中引入 ECharts</a> </li> <li> <a class=\"u-file\" href=\"https://echarts.apache.org/zh/builder.html\" target=\"_blank\">ECharts 在线定制</a> </li> </ul> <GanttChart ref=\"ganttRef\" :gantt-data=\"ganttData\" :status-map=\"statusMap\" :height=\"500\" /> </div></template>