超详细的vue3+ts+dhtmlx甘特图免费版组件使用方法,支持事件内自定义和样式修改

官方文档:如何从 dhtmlx甘特甘特图文档开始

这个组件比较古老,对vue的兼容性很不友好。以下是一些开发心得:

1.安装dhtmlx 甘特图

npm install @dhtmlx/trial-vue-gantt

2.创建Gantt.vue 组件


<template>
	<div class="xh-radio-switch">
		<el-select v-model="test" @change="radioSwitchChange">
			<el-option v-for="item in options" :key="item.val" :label="item.name" :value="item.val"></el-option>
		</el-select>
	</div>
	<div style="width: 100%;" ref="ganttRef"></div>
</template>
<script setup  lang="ts">
import { onMounted, ref, nextTick } from 'vue'
import { gantt } from 'dhtmlx-gantt'
import { ElMessage, ElMessageBox, dayjs } from 'element-plus';
const emits = defineEmits<{
	(e: 'openDetail', id: number): void
	(e: 'openShow', id: number): void
	(e: 'handle', success: true): void
}>()
const test = ref('')
const options = ref(
	[
		{
			name: '日',
			val: 'day'
		},
		{
			name: '周',
			val: 'week'
		},
		{
			name: '月',
			val: 'month'
		},
		{
			name: '季度',
			val: 'quarter'
		},
	]
)
const props = defineProps<{
	// 任务对象
	tasks?: any
	// 显示列设置
	columns?: Array<any>
	// 显示单位
	scaleUnit?: {
		type: String,
		default: 'day' // “minute”, “hour”, “day”, “week”, “quarter”, “month”, “year”
	},
	// 时间显示格式
	dateScale?: {
		type: String,
		default: '%Y-%m-%d'
	}
}>()
// 挂载ref
const ganttRef = ref()
onMounted(() => {
	// 清空之前的配置
	gantt.clearAll()
	// 默认配置
	gantt.config.xml_date = '%Y-%m-%d'
	gantt.i18n.setLocale('cn') // 设置中文
	gantt.config.readonly = false // 设置为可编辑
	gantt.config.autosize = true//自适应尺寸
	gantt.config.autofit = true// 表格列宽自适应
	gantt.config.autoscroll = true// 把任务或者连线拖拽到浏览器屏幕外时,自动触发滚动效果
	gantt.config.drag_progress = false//取消任务进度条进度拖动
	//时间栏配置
	gantt.config.scales = [
		{ unit: 'month', step: 1, format: '%Y年%m月' },
		{ unit: 'day', step: 1, format: '%m/%d' },
	]
	gantt.config.scale_height = 60
	//更改树状的图标
	gantt.templates.grid_open = (item: any) => {
		props.tasks.data.forEach((item: any) => {
			changeItemStyle(item)
		})
		return "<div data-icon='" + (item.$open ? "close" : "open") + "' class='gantt_tree_icon gantt_" +
			(item.$open ? "close" : "open") + "'></div>"
	}
	//更改父项图标
	gantt.templates.grid_folder = (item: any) => {
		return ""
	}
	//更改子项图标
	gantt.templates.grid_file = (item: any) => {
		return ""
	}
	//自定义配置左侧表格列
	gantt.config.columns = [
		{ name: "text", label: "事务名称", align: "left", tree: true, width: 260 },
		{
			name: "status", label: " ", width: 80, align: "center", template: function (task: any) {
				switch (task.status) {
					case 1:
						return (
							`<div data-show class="progress progress-success">已完成</div>
							<div data-show class="avatar"><img src="${task.avatar}" /></div>`
						);
						break;
					case 2:
						return (
							`<div data-show class="progress progress-todo">待 办</div>
							<div data-show class="avatar"><img src="${task.avatar}" /></div>`
						);
						break;
					case 3:
						return (
							`<div data-show class="progress progress-doing">进行中</div>
							<div data-show class="avatar"><img src="${task.avatar}" /></div>`
						);
						break;
						break;
					default:
						return (
							`<div data-show class="progress progress-todo">待办</div>
							<div data-show class="avatar"><img src="${task.avatar}" /></div>`
						);
						break;
				}
			}
		},
		{
			name: "add_item", label: "", width: 44, template: () => {
				return `<div ref="addRef" id="button" class="add-item" data-action="add"></div>`
			}
		}
	]
	//自定义右侧任务栏颜色
	gantt.templates.task_class = (start: string, end: string, task: any) => {
		if (task.parent) return 'task-child'
		return 'task-' + task.id
	}
	//获取任务时
	gantt.attachEvent("onTaskLoading", (task: any) => {
		changeItemStyle(task)
		return true
	})
	//打开分支时候
	gantt.attachEvent("onTaskOpened", function () {
		props.tasks.data.forEach((item: any) => {
			changeItemStyle(item)
		})
	});
	//关闭分支时
	gantt.attachEvent("onTaskClosed", function () {
		props.tasks.data.forEach((item: any) => {
			changeItemStyle(item)
		})
	});
	//拖动任务时
	gantt.attachEvent("onTaskDrag", function (id: number, mode: any, task: any, original: any) {
		changeItemStyle(task)
	});
	//甘特图 任务栏更新后 可获取当前任务对象
	gantt.attachEvent("onAfterTaskUpdate", async function (id: number, item: any) {
		changeItemStyle(item)
		const params_start = {
			issues_id: id,
			name: 'start_date',
			values: dayjs(item.start_date).format('YYYY-MM-DD')
		}
		const params_end = {
			issues_id: id,
			name: 'end_date',
			values: dayjs(item.end_date).format('YYYY-MM-DD')
		}
		const res1 = await xxxxx(params_start)
		if (res1.code !== 0) return ElMessage.error(res1.message)
		const res2 = await xxxxx(params_end)
		if (res2.code !== 0) return ElMessage.error(res2.message)
	});
	//甘特图 点击事件
	gantt.attachEvent("onTaskClick", function (id: number, e: any) {
		//点击显示更多按钮
		const buttonAdd = e.target.closest("[data-action]")
		if (buttonAdd) {
			emits('openDetail', id)
		}
		//点击状态按钮
		const buttonShow = e.target.closest("[data-show]")
		if (buttonShow) {
			emits('openShow', id)
		}
		//点击展开/收起按钮
		const buttonIcon = e.target.closest("[data-icon]")
		if (buttonIcon) {
			const active = buttonIcon.getAttribute("data-icon")
			switch (active) {
				case 'open':
					gantt.open(id)
					break;
				case 'close':
					gantt.close(id)
					break;
				default:
					break;
			}
		}
	})
	//取消默认双击事件
	gantt.attachEvent("onTaskDblClick", function (id: number, e: any) {
		return false
	});
	// 处理连线完成事件
	let linkAddHandler = null
	linkAddHandler = async function (id: number, item: any) {
		props.tasks.data.forEach((item: any) => {
			changeItemStyle(item)
		})
		const params = {
			issues_id: Number(item.source),
			relate_type: 1,
			relate_issues_id: Number(item.target)
		}
		const res = await xxxxxxxx(params)
		if (res.code !== 0) return ElMessage.error(res.message)
		ElMessage.success(res.data)
		emits('handle', true)
	}
	gantt.attachEvent("onAfterLinkAdd", linkAddHandler)
	// 在需要取消监听时执行
	gantt.detachEvent("onAfterLinkAdd", linkAddHandler)
	// 处理连接线双击事件
	let linkDblClickHandler = null
	linkDblClickHandler = function (id: number, e: any) {
		ElMessageBox.confirm(
			'此操作不可恢复',
			'删除此关联事务',
			{
				confirmButtonText: '删除',
				cancelButtonText: '取消',
				type: 'warning',
			}
		).then(async () => {
			const res = await xxxxxx(id)
			if (res.code !== 0) return ElMessage.error(res.message)
			ElMessage.success(res.data)
			gantt.deleteLink(id)
			emits('handle', true)
		})
	}
	gantt.attachEvent('onLinkDblClick', linkDblClickHandler);
	// 初始化甘特图
	gantt.init(ganttRef.value)
	// 渲染数据
	gantt.parse(props.tasks)
})
//----------------自定义函数
const backgroundColorFun = (number1: number, number2: number) => {
	return `linear-gradient(to right, #68d390 ${0}%, #68d390 ${number1}%,#0085ff ${number1}%,#0085ff ${number2}%, #c4c4c4 ${number2}%, #c4c4c4 ${100}%)`
}
const changeItemStyle = (task: any) => {
	if (!task.parent) {
		const total = task.done + task.backlog + task.under_way + task.lay_aside
		if (total) {
			const percentDone = (task.done / total) * 100
			const percentUnderWay = (task.under_way / total) * 100
			nextTick(() => {
				const taskClass = document.querySelector(`.task-${task.id}`) as HTMLElement
				if (taskClass) {
					taskClass.style.background = `${backgroundColorFun(percentDone, percentDone + percentUnderWay)}`
					taskClass.style.borderRadius = '50px'
				}
			})
		} else {
			nextTick(() => {
				const taskClass = document.querySelector(`.task-${task.id}`) as HTMLElement
				if (taskClass) {
					taskClass.style.background = `${backgroundColorFun(0, 0)}`
					taskClass.style.borderRadius = '50px'
				}
			})
		}
	}
}
//日 周 月 季度 切换
const switchToDay = () => {
	gantt.config.scale_unit = "day"
	gantt.config.step = 1
	gantt.render()
}
const switchToWeek = () => {
	gantt.config.scale_unit = "week"
	gantt.config.step = 1
	gantt.render()
}
const switchToMonth = () => {
	gantt.config.scale_unit = "month"
	gantt.config.step = 1
	gantt.render()
}
const switchToQuarter = () => {
	gantt.config.scale_unit = "month"
	gantt.config.step = 3
	gantt.render()
}
const radioSwitchChange = (e: string) => {
	switch (e) {
		case 'day':
			switchToDay()
			break;
		case 'week':
			switchToWeek()
			break;
		case 'month':
			switchToMonth()
			break;
		case 'quarter':
			switchToQuarter()
			break;
		default:
			break;
	}
}

</script>
<style lang="less">
.xh-radio-switch {
	display: flex;
	justify-content: flex-end;
}

.add-item {
	cursor: pointer;
	width: 20px;
	height: 20px;
	background-size: cover;
	background-image: url(xxxxxx)
}

/* 进度盒 */
.progress {
	display: flex;
	justify-content: center;
	align-items: center;
	width: 43px;
	height: 20px;
	border-radius: 5px;
	font-size: 12px;
	padding: 3px;

	&-success {
		background: rgba(63, 200, 114, 0.15);
		color: #67d390;
	}

	&-todo {
		background: #dfe1e6;
		color: #84526e;
	}

	&-doing {
		background: rgba(0, 133, 255, 0.15);
		color: #0085FF;
	}
}

/**用户头像 */
.avatar {
	display: flex;

	img {
		width: 24px;
		height: 24px;
		border-radius: 50%;
		margin-left: 10px;
	}

}

/** 左侧树状 新增按钮 */
.gantt_add,
.gantt_grid_head_add {
	background-image: url(xxxxxxxx) !important;
	opacity: .9 !important;
}

.gantt_tree_content {
	display: flex;
	justify-content: center;
	align-items: center;
}

/* 定义任务栏颜色 */
.gantt_task_line {
	border-color: rgba(0, 0, 0, 0.25);
}

.gantt_task_line .gantt_task_progress {
	background-color: rgba(0, 0, 0, 0.25);
}

/* task */
.gantt_task_line {
	.task-5 {
		border-radius: 50px;
		// background: v-bind(backgroundColor);
	}
}

.gantt_task_line.task {
	border-radius: 50px;
	// background: v-bind(backgroundColor);
}

.gantt_task_line.task-child {
	background-color: #ffe176;
	border-radius: 50px;
}

.gantt_task_line .gantt_task_progress {
	background-color: #68d390;
}

.gantt_task_line.task .gantt_task_content {
	color: #fff;
}
</style>
<style>
@import "/node_modules/dhtmlx-gantt/codebase/dhtmlxgantt.css"
</style>

 3.在vue中使用

<template>
<Gantt v-if="ganttTasks.data.length" :tasks="ganttTasks" :columns="ganttColumns" style="height: 500px;" @handle="getGanttList()"></Gantt>
</template>

<script setup lang="ts">
import Gantt from './components/Gantt.vue'
//-----------------------------------生命周期
onMounted(() => {
	Promise.all([getGanttList()])
})
//-----------------------------------甘特图
const ganttData = ref<xxxx[]>([]) //后端数据
const linkData = ref<any[]>([])//相关事务数据
const ganttKeywords = ref<xxxx>({
	synergy_type_id: 1 //事务类型
})
const ganttTasks = ref<any>({
	//数据
	data: [],
	//链接
	links: []
})
const ganttColumns = ref([
	{ align: 'left', name: 'text', label: ' ', tree: true },
])
const getGanttList = async () => {
	const res = await xxxxxxxxxx(ganttKeywords.value)
	if (res.code !== 0) return ElMessage.error(res.message)
	ganttData.value = res.data
	ganttTasks.value.data = ganttData.value.map((item: xxxx) => {
//这里的返回值 就是gantt回调函数中的task形参
		return {
			id: item.id, parent: item.parent_id, text: 'XH-' + item.id + ' ' + item.name, start_date: item.start_date, end_date: item.end_date, progress: item.completion_progress, status: item.status, avatar: item.principal.avatar || defaultImage, level: item.level, done: item.done, backlog: item.backlog, under_way: item.under_way, lay_aside: item.lay_aside
		}
	})
	linkData.value = ganttData.value.filter(item => item.issues_relation && item.issues_relation.length)
	if (linkData.value.length) {
		ganttTasks.value.links = linkData.value.map(item => item.issues_relation).flat(Infinity).map((item: xxxx) => {
			return {
				id: item.id, source: item.synergy_issues_id, target: item.relate_issues_id, type: '0'
			}
		})
	}
}
</script>

  • 5
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值