vue3+dhtmlx 甘特图真是案例

使用vue3 + ts +dhtmlx 实现项目任务甘特图展示

支持拖拽,选择人员,优先级,开发状态,进度

效果图


完整代码

安装命令:npm i dhtmlx-gantt

javascript 复制代码
<template>
    <div style="height: 100%; background-color: white">
        <div class="gantt-header">
            <el-button type="primary" @click="addNewTask">
                <el-icon><Plus /></el-icon>新建任务
            </el-button>
            <el-button type="primary" @click="handVal">
                获取数据
            </el-button>
            <!-- <el-button type="success" @click="addSubTaskToSelected">
                <el-icon><Plus /></el-icon>添加子任务
            </el-button> -->
        </div>
        <div ref="ganttRef" style="width: 100%; height: 600px"></div>
    </div>
</template>

<script setup name="gantt-widget">
import { ref, reactive, onMounted, defineEmits } from 'vue'
import { gantt } from 'dhtmlx-gantt'
import 'dhtmlx-gantt/codebase/dhtmlxgantt.css'
import { Plus } from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus'

import { defineProps } from 'vue'
const props = defineProps({
    widgetObj: {
        type: Object,
        required: true,
    },
})

const ganttRef = ref()
const tasks = ref({})

//动态加载数据
const fetchData = () => {
    tasks.value = {
        data: [
            {
                id: 10,
                text: 'RFQ&项目启动',
                type: 'project',
                progress: 0.1,
                open: true,
                person: '张三',
                priority: 1,
            },
            {
                id: 12,
                text: '产品需求 #1.0.1',
                start_date: '02-01-2025',
                duration: 3,
                parent: 10,
                progress: 1,
                person: '张三',
                priority: 2,
            },
            {
                id: 13,
                text: '产品数据 #1.0.2',
                start_date: '05-01-2025',
                duration: 3,
                parent: 10,
                progress: 0.8,
                person: '张三',
            },
            {
                id: 14,
                text: '环境数据 #1.0.3',
                start_date: '08-01-2025',
                duration: 3,
                parent: 10,
                progress: 0,
                person: '张三',
            },
            {
                id: 15,
                text: '项目评估指令 #1.1',
                start_date: '11-01-2025',
                duration: 3,
                parent: 10,
                progress: 0,
                person: '张三',
            },
            {
                id: 16,
                text: '成立项目小组 #1.2.1',
                start_date: '12-01-2025',
                duration: 2,
                parent: 10,
                progress: 0,
                person: '张三',
            },
            {
                id: 17,
                text: '可行性评估 #1.2.2',
                start_date: '13-01-2025',
                duration: 3,
                parent: 10,
                progress: 0,
                person: '张三',
            },
            {
                id: 18,
                text: '输入评审 #1.2.3',
                start_date: '14-01-2025',
                duration: 2,
                parent: 10,
                progress: 0,
                person: '张三',
            },
            {
                id: 19,
                text: '初始产品方案 #1.2.4',
                start_date: '16-01-2025',
                duration: 2,
                parent: 10,
                progress: 0,
                person: '张三',
            },

            {
                id: 20,
                text: '产品设计&开发',
                type: 'project',
                progress: 0,
                person: '张三',
                open: false,
            },

            {
                id: 21,
                text: '设计输入信息管理#3.0.1',
                start_date: '18-01-2025',
                duration: 2,
                parent: 20,
                progress: 0,
                person: '张三',
            },
            {
                id: 22,
                text: '产品设计过往教训展开 #3.0.2',
                start_date: '20-01-2025',
                duration: 2,
                parent: 20,
                progress: 0,
                person: '张三',
            },
            {
                id: 23,
                text: '产品设计进度管理 #3.0.3',
                start_date: '22-01-2025',
                duration: 2,
                parent: 20,
                progress: 0,
                person: '张三',
            },
            {
                id: 24,
                text: '产品设计方案 #3.0.4',
                start_date: '24-01-2025',
                duration: 2,
                parent: 20,
                progress: 0,
                person: '张三',
            },
            {
                id: 25,
                text: '产品特殊特性识别 #3.0.5',
                start_date: '26-01-2025',
                duration: 2,
                parent: 20,
                progress: 0,
                person: '张三',
            },
            {
                id: 26,
                text: '产品设计方案评审 #3.0.6',
                start_date: '28-01-2025',
                duration: 2,
                parent: 20,
                progress: 0,
                person: '张三',
            },

            {
                id: 30,
                text: '过程设计&开发',
                type: 'project',
                progress: 0,
                person: '张三',
                open: false,
            },
            {
                id: 31,
                text: '场地规划 #5.0.1',
                start_date: '28-02-2025',
                duration: 3,
                parent: 30,
                progress: 0,
                person: '张三',
            },
            {
                id: 32,
                text: '场地评审 #5.0.2',
                start_date: '28-02-2025',
                duration: 3,
                parent: 30,
                progress: 0,
                person: '张三',
            },
            {
                id: 33,
                text: '过程检验标准 #5.0.3',
                start_date: '29-02-2025',
                duration: 3,
                parent: 30,
                progress: 0,
                person: '张三',
            },
            {
                id: 40,
                text: '产品&过程验证',
                type: 'project',
                open: false,
                progress: 0,
                person: '张三',
            },
            {
                id: 41,
                text: '量具重复再现性分析 #6.0.1',
                start_date: '29-02-2025',
                duration: 3,
                parent: 40,
                progress: 0,
                person: '张三',
            },
            {
                id: 42,
                text: '检具使用验收 #6.0.2',
                start_date: '01-03-2025',
                duration: 3,
                parent: 40,
                progress: 0,
                person: '张三',
            },
            {
                id: 43,
                text: '工装-厂外预验收 #6.1.1',
                start_date: '02-03-2025',
                duration: 3,
                parent: 40,
                progress: 0,
                person: '张三',
            },
            {
                id: 44,
                text: '设备-厂外预验收 #6.2.1',
                start_date: '03-03-2025',
                duration: 3,
                parent: 40,
                progress: 0,
                person: '张三',
            },
            {
                id: 45,
                text: '模具-厂外预验收 #6.3.1',
                start_date: '04-03-2025',
                duration: 3,
                parent: 40,
                progress: 0,
                person: '张三',
            },

            {
                id: 50,
                text: '过程验证',
                type: 'project',
                open: false,
                progress: 0,
                person: '张三',
            },
            {
                id: 51,
                text: '小批量试生产总结 #7.0.1',
                start_date: '28-04-2025',
                duration: 3,
                parent: 50,
                progress: 0,
                person: '张三',
            },
            {
                id: 52,
                text: '产品尺寸记录 #7.0.2',
                start_date: '29-04-2025',
                duration: 3,
                parent: 50,
                progress: 0,
                person: '张三',
            },
            {
                id: 52,
                text: '过程能力研究 #7.0.3',
                start_date: '30-04-2025',
                duration: 3,
                parent: 50,
                progress: 0,
                person: '张三',
            },
            {
                id: 60,
                text: '反馈,纠正和改进',
                type: 'project',
                open: false,
                progress: 0,
                person: '张三',
            },
            {
                id: 61,
                text: '模工检移交 #8.0.1',
                start_date: '28-05-2025',
                duration: 3,
                parent: 60,
                progress: 0,
                person: '张三',
            },
            {
                id: 62,
                text: '项目移交会议 #8.0.2',
                start_date: '29-05-2025',
                duration: 3,
                parent: 60,
                progress: 0,
                person: '张三',
            },
            {
                id: 63,
                text: '取得量产交付计划 #8.1.1',
                start_date: '30-05-2025',
                duration: 3,
                parent: 60,
                progress: 0,
                person: '张三',
            },
        ],
        links: [
            { id: 10, source: 12, target: 13, type: 1 },
            { id: 11, source: 13, target: 14, type: 1 },
            { id: 12, source: 14, target: 15, type: 1 },
        ]
    }
}

// 添加子任务到选中的任务
const addSubTaskToSelected = () => {
    const selectedTask = gantt.getSelectedId()
    if (selectedTask) {
        addSubTask(selectedTask)
    } else {
        ElMessage.warning('请先选择一个任务')
    }
}

// 添加新任务
const addNewTask = (parentId = null) => {
    const newTask = {
        id: gantt.uid(),
        text: '新任务',
        start_date: new Date(),
        duration: 1,
        progress: 0, // 默认状态为未开始
        person: '张三', // 默认负责人
        parent: parentId,
        priority: 2 // 默认优先级为中
    }
    gantt.addTask(newTask)
}

// 添加子任务
const addSubTask = (parentId) => {
    addNewTask(parentId)
}
const handVal = () => {
    // 获取甘特图所有任务数据
    const data = gantt.serialize()
    console.log('甘特图数据:', data)
    
    // 获取选中的任务
    const selectedTaskId = gantt.getSelectedId()
    if (selectedTaskId) {
        const selectedTask = gantt.getTask(selectedTaskId)
        console.log('选中的任务:', selectedTask)
    }
}

//初始化配置
const initGantt = () => {
    gantt.plugins({ 
        tooltip: true, // 鼠标悬停显示任务信息
        quick_info: true // 快速信息
    })

    // 隐藏右侧日期列表
    // gantt.config.show_chart = false

    // 添加右键菜单
    gantt.config.context_menu = {
        items: {
            add: {
                text: "添加任务",
                icon: "gantt_add",
                onclick: function (id) {
                    addNewTask()
                }
            },
            add_sub: {
                text: "添加子任务",
                icon: "gantt_add",
                onclick: function (id) {
                    addSubTask(id)
                }
            },
            delete: {
                text: "删除",
                icon: "gantt_delete",
                onclick: function (id) {
                    gantt.deleteTask(id)
                }
            }
        }
    }

    // 允许拖放
    gantt.config.drag_project = true
    gantt.config.drag_move = true
    gantt.config.drag_resize = true
    gantt.config.drag_links = true

    // 允许任务排序
    gantt.config.order_branch = true
    gantt.config.order_branch_free = true

    // 显示任务树
    gantt.config.show_grid = true
    gantt.config.show_task_cells = true
    // 隐藏任务之间的连线
    gantt.config.show_links = false

    // 汉化窗口
    gantt.locale.labels = {
        dhx_cal_today_button: '今天',
        day_tab: '日',
        week_tab: '周',
        month_tab: '月',
        new_event: '新建日程',
        icon_save: '保存',
        icon_cancel: '关闭',
        icon_details: '详细',
        icon_edit: '编辑',
        icon_delete: '删除',
        confirm_closing: '请确认是否撤销修改!', //Your changes will be lost, are your sure?
        confirm_deleting: '是否删除计划?',
        section_description: '描述:',
        section_time: '时间范围:',
        section_type: '类型:',
        section_text: '计划名称:',
        section_test: '测试:',
        section_projectClass: '项目类型:',
        taskProjectType_0: '项目任务',
        taskProjectType_1: '普通任务',
        section_head: '负责人:',
        section_priority: '优先级:',
        taskProgress: '任务状态',
        taskProgress_0: '未开始',
        taskProgress_1: '进行中',
        taskProgress_2: '已完成',
        taskProgress_3: '已延期',
        taskProgress_4: '搁置中',
        section_template: 'Details',
        /* grid columns */
        column_text: '计划名称',
        column_start_date: '开始时间',
        column_duration: '持续时间',
        column_add: '',
        column_priority: '难度',
        /* link confirmation */
        link: '关联',
        confirm_link_deleting: '将被删除',
        message_ok: '确定',
        message_cancel: '取消',
        link_start: ' (开始)',
        link_end: ' (结束)',

        type_task: '任务',
        type_project: '项目',
        type_milestone: '里程碑',
        minutes: '分钟',
        hours: '小时',
        days: '天',
        weeks: '周',
        months: '月',
        years: '年',
    }

    // 格式化日期
    gantt.locale.date = {
        month_full: [
            '1月',
            '2月',
            '3月',
            '4月',
            '5月',
            '6月',
            '7月',
            '8月',
            '9月',
            '10月',
            '11月',
            '12月',
        ],
        month_short: [
            '1月',
            '2月',
            '3月',
            '4月',
            '5月',
            '6月',
            '7月',
            '8月',
            '9月',
            '10月',
            '11月',
            '12月',
        ],
        day_full: [
            '星期日',
            '星期一',
            '星期二',
            '星期三',
            '星期四',
            '星期五',
            '星期六',
        ],
        day_short: [
            '星期日',
            '星期一',
            '星期二',
            '星期三',
            '星期四',
            '星期五',
            '星期六',
        ],
    }

    // 当task的长度改变时,自动调整图表坐标轴区间用于适配task的长度
    gantt.config.fit_tasks = true

    // 定义时间格式
    gantt.config.scales = [
        { unit: 'month', step: 1, date: '%F, %Y' },
        { unit: 'day', step: 1, date: '%j, %D' },
    ]
    // gantt.config.scale_height = 80
    // gantt.config.row_height = 60
    // gantt.config.bar_height = 40
    gantt.i18n.setLocale('cn')
    // gantt.config.autosize = true
    // gantt.config.readonly = true
    gantt.config.show_grid = true
    gantt.config.show_task_tooltips = true
    gantt.config.show_progress = true
    
    gantt.config.branches = {
        open: 'open',
        closed: 'closed',
    }

    gantt.templates.tooltip_text = (start, end, task) => `
        <div>
            <div>任务:${task.text}</div>
            <div>开始时间:${formatDate(task.start_date, '{y}-{m}-{d}')}</div>
            <div>结束时间:${formatDate(task.end_date, '{y}-{m}-{d}')}</div>
            <div>进度:${task.progress * 100}%</div>
        </div>`

    gantt.config.columns = [
        {
            name: 'text',
            label: '任务名称',
            width: '250',
            tree: true,
            align: 'left',
        },
        { name: 'start_date', label: '起始时间', width: '100', align: 'center' },
        { name: 'duration', label: '持续时间', width: '80', align: 'center' },
        {
            name: 'progress',
            label: '进度',
            width: '100',
            align: 'center',
            template: function (obj) {
                return obj.progress * 100 + '%'
            },
        },
        { name: 'person', label: '负责人', width: '80', align: 'center' },
        {
            name: 'status',
            label: '状态',
            width: '100',
            align: 'center',
            template: function (obj) {
                const status = getTaskStatus(obj)
                return `<span class="task-status ${status.class}">${status.text}</span>`
            }
        },
        {
            name: 'priority',
            label: '优先级',
            width: '80',
            align: 'center',
            template: function (obj) {
                const priority = obj.priority 
                return `<span class="task-priority priority-${priority}">${getPriorityText(priority)}</span>`   // return priority
            }
        },
        {
            name: 'add',
            label: '操作',
            width: '100',
            align: 'center',
            template: function (obj) {
                return `<div class="task-actions">
                    <el-button type="primary" size="small" onclick="gantt.$vue.addSubTask(${obj.id})">
                        <el-icon><Plus /></el-icon>添加子任务
                    </el-button>
                </div>`
            }
        }
    ]
    gantt.config.lightbox_zindex = 10000

    // 定义可选的人员列表
    gantt.serverList("person", [
        { key: "张三", label: "张三" },
        { key: "李四", label: "李四" },
        { key: "王五", label: "王五" },
        { key: "赵六", label: "赵六" }
    ]);

    // 定义任务状态列表
    gantt.serverList("status", [
        { key: 0, label: "未开始" },
        { key: 0.5, label: "进行中" },
        { key: 1, label: "已完成" }
    ]);

    // 定义优先级列表
    gantt.serverList("priority", [
        { key: 1, label: "高" },
        { key: 2, label: "中" },
        { key: 3, label: "低" }
    ]);

    // 添加弹窗属性
    gantt.config.lightbox.sections = [
        {
            name: 'description',
            height: 70,
            map_to: 'text',
            type: 'textarea',
            focus: true,
        },
        { name: 'type', type: 'typeselect', map_to: 'type' },
        { name: 'time', type: 'duration', map_to: 'auto' },
        {
            name: 'priority',
            height: 30,
            map_to: 'priority',
            type: 'select',
            label: '优先级',
            options: gantt.serverList("priority")
        },
        {
            name: 'person',
            height: 30,
            map_to: 'person',
            type: 'select',
            label: '负责人',
            options: gantt.serverList("person")
        },
        {
            name: 'progress',
            height: 30,
            map_to: 'progress',
            type: 'select',
            label: '状态',
            options: gantt.serverList("status")
        }
    ]

    // 获取优先级文本
    const getPriorityText = (priority) => {
        
        const priorityList = gantt.serverList("priority")
        const found = priorityList.find(item => item.key == priority)
        return found ? found.label : ''
    }

    // 获取任务状态
    const getTaskStatus = (task) => {
        if (task.progress === 1) {
            return { text: '已完成', class: 'status-completed' }
        } else if (task.progress > 0) {
            return { text: '进行中', class: 'status-in-progress' }
        } else {
            return { text: '未开始', class: 'status-not-started' }
        }
    }

    // 将 Vue 实例挂载到 gantt 对象上,以便在模板中访问
    gantt.$vue = {
        addSubTask: addSubTask,
        getTaskStatus: getTaskStatus,
        getPriorityText: getPriorityText
    }

    // 初始化
    gantt.init(ganttRef.value)

    // 清空旧数据
    gantt.clearAll()

    // 数据解析
    gantt.parse(tasks.value)
}

const formatDate = (date, format) => {
    if (!date) return ''
    const d = new Date(date)
    const year = d.getFullYear()
    const month = String(d.getMonth() + 1).padStart(2, '0')
    const day = String(d.getDate()).padStart(2, '0')
    
    return format
        .replace('{y}', year)
        .replace('{m}', month)
        .replace('{d}', day)
}

onMounted(() => {
    fetchData()
    initGantt()
})
</script>

<style lang="scss" scoped>
.gantt_cal_light {
    z-index: 9999 !important;
}

.gantt_cal_cover {
    z-index: 10000 !important;
}

.gantt-header {
    padding: 10px;
    border-bottom: 1px solid #ebeef5;
    margin-bottom: 10px;
}

:deep(.task-actions) {
    .el-button {
        padding: 4px 8px;
        font-size: 12px;
    }
}

:deep(.task-status) {
    padding: 2px 8px;
    border-radius: 4px;
    font-size: 12px;
    
    &.status-completed {
        background-color: #f0f9eb;
        color: #67c23a;
    }
    
    &.status-in-progress {
        background-color: #fdf6ec;
        color: #e6a23c;
    }
    
    &.status-not-started {
        background-color: #f4f4f5;
        color: #909399;
    }
}

:deep(.task-priority) {
    padding: 2px 8px;
    border-radius: 4px;
    font-size: 12px;
    
    &.priority-1 {
        background-color: #fef0f0;
        color: #f56c6c;
    }
    
    &.priority-2 {
        background-color: #fdf6ec;
        color: #e6a23c;
    }
    
    &.priority-3 {
        background-color: #f0f9eb;
        color: #67c23a;
    }
}
</style>
相关推荐
艾小逗1 小时前
vue3中的effectScope有什么作用,如何使用?如何自动清理
前端·javascript·vue.js
小小小小宇3 小时前
手写 zustand
前端
Hamm4 小时前
用装饰器和ElementPlus,我们在NPM发布了这个好用的表格组件包
前端·vue.js·typescript
小小小小宇5 小时前
前端国际化看这一篇就够了
前端
大G哥5 小时前
PHP标签+注释+html混写+变量
android·开发语言·前端·html·php
whoarethenext5 小时前
html初识
前端·html
小小小小宇5 小时前
一个功能相对完善的前端 Emoji
前端
m0_627827525 小时前
vue中 vue.config.js反向代理
前端
Java&Develop5 小时前
onloyoffice历史版本功能实现,版本恢复功能,编辑器功能实现 springboot+vue2
前端·spring boot·编辑器
白泽talk5 小时前
2个小时1w字| React & Golang 全栈微服务实战
前端·后端·微服务