在工作中由于业务的复杂性,需要使用dhtmlx-gantt来实现复杂表格,以下是甘特图的实现以及一些配置描述。(由于官方文档是英文的,所以对英文不好的不太友好),官方文档:Gantt API Gantt Docs
相关配置:
1、一行需要展示多条数据的操作步骤:需要给父数据的那条添加render:split属性,子数据的parent为父数据的Id即可。
2、配置甘特图的开始和结束时间:
gantt.config.start_date,gantt.config.end_date
3、灯箱中日历设置:
gantt.form_blocks['dhx_calendar']
4、表格列设置
gantt.config.columns
5、隐藏某些行的“添加”按钮
gantt.templates.grid_row_class = function (start, end, task) {
if (task.$level > 1) {
return 'nested_task'
}
.nested_task .gantt_add {
display: none !important;
}
6、周末样式突出
gantt.config.work_time = true
gantt.templates.timeline_cell_class = function (task, date) {
if (!gantt.isWorkTime(date)) return 'week_end'
return ''
}
7、节假日样式突出
const holidays = ['2023-03-29']
const format_date = gantt.date.str_to_date('%Y-%m-%d')
for (let i = 0; i < holidays.length; i++) {
const converted_date = format_date(holidays[i])
gantt.setWorkTime({ date: converted_date, hours: false })
}
注意:dhtmlx.css和dhtmlx.js都要引入index.html
页面代码
<template>
<div>
<div ref="ganttYear" id="ganttYearDom" class="gantt-container" style="width: 100%; height: 450px"></div>
</div>
</template>
<script>
import gantt from 'dhtmlx-gantt'
import 'dhtmlx-gantt/codebase/dhtmlxgantt.css'
import moment from 'moment'
export default {
name: 'resourcePlan',
data() {
return {
//甘特图
params: {
deptIds: [],
startDate: '',
endDate: '',
searchValue: '',
conditions: [],
},
htmlUrl: '',
// 甘特图数据格式
dataSource: [],
ganttHeight: innerHeight - 215 + 'px',
ganttLoading: false,
// 下拉框选项
priorityOptions: [
{ key: '1', label: '编制报告', backgroundColor: '#BD3124', textColor: '#FFF' },
{ key: '2', label: '准备进场新项目', backgroundColor: '#BD3124', textColor: '#FFF' },
{ key: '3', label: '请假休整', backgroundColor: '#81B336', textColor: '#FFF' },
{ key: '4', label: '其他', backgroundColor: '#BD3124', textColor: '#FFF' },
],
tasks: {
data: [], //甘特图数据
links: [],
},
events: [],
}
},
created() {
gantt.clearAll()
},
mounted() {
this.init()
},
methods: {
moment,
// 获取资源统计数据
getResourceInfo() {
gantt.config.start_date = this.nowDate.format('YYYY-MM-DD')
gantt.config.end_date = this.newDate.format('YYYY-MM-DD')
},
// 初始化
init() {
console.log('初始化')
//中文
gantt.i18n.setLocale('cn')
//自适应甘特图的尺寸大小, 使得在不出现滚动条的情况下, 显示全部任务
gantt.config.autosize = false
// 只读模式:打开后不可以操作甘特图
gantt.config.readonly = false
gantt.config.date_format = '%Y-%m-%d'
gantt.config.keep_grid_width = false
gantt.config.grid_resize = true
gantt.config.initial_scroll = false
//是否显示左侧树表格
gantt.config.show_grid = true
function byId(list, id) {
for (var i = 0; i < list.length; i++) {
if (list[i].key == id) return id || ''
}
return ''
}
window.byId = byId
let that = this
var duration = function (a, b, c) {
var res = gantt.calculateDuration(a.getDate(false), b.getDate(false))
c.innerHTML = res + '天'
}
var calendar_init = function (id, data, date) {
var obj = new dhtmlXCalendarObject(id)
obj.setDateFormat(data.date_format ? data.date_format : '')
obj.setDate(date ? date : new Date())
obj.hideTime()
if (data.skin) obj.setSkin(data.skin)
return obj
}
gantt.form_blocks['dhx_calendar'] = {
render: function (sns) {
return (
"<div class='dhx_calendar_cont'><input type='text' readonly='true' id='calendar1'/> – " +
"<input type='text' readonly='true' id='calendar2'/><label id='duration'></label></div>"
)
},
set_value: function (node, value, task, data) {
var a = (node._cal_start = calendar_init('calendar1', data, task.start_date))
var b = (node._cal_end = calendar_init('calendar2', data, task.end_date))
var c = node.lastChild
b.setInsensitiveRange(null, new Date(a.getDate(false) - 86400000))
var a_click = a.attachEvent('onClick', function (date) {
b.setInsensitiveRange(null, new Date(date.getTime() - 86400000))
duration(a, b, c)
})
var b_click = b.attachEvent('onClick', function (date) {
duration(a, b, c)
})
var a_time_click = a.attachEvent('onChange', function (d) {
b.setInsensitiveRange(null, new Date(d.getTime() - 86400000))
duration(a, b, c)
})
var b_time_click = b.attachEvent('onChange', function (d) {
that.changeEnd = false
task.end_date = that.setTime(d, true)
duration(a, b, c)
})
var id = gantt.attachEvent('onAfterLightbox', function () {
a.detachEvent(a_click)
a.detachEvent(a_time_click)
a.unload()
b.detachEvent(b_click)
b.detachEvent(b_time_click)
b.unload()
a = b = null
this.detachEvent(id)
})
document.getElementById('calendar1').value = a.getDate(true)
document.getElementById('calendar2').value = that.setTime(b.getDate(true), false)
duration(a, b, c)
},
get_value: function (node, task) {
task.start_date = node._cal_start.getDate(false)
task.end_date = node._cal_end.getDate(false)
return task
},
focus: function (node) {},
}
//表格列设置
gantt.config.columns = [
{
//最左侧新增符号列,甘特图内置可选使用列
name: 'add',
label: '',
width: '40',
},
{
name: 'userName',
label: '姓名',
tree: true,
width: '150',
},
{
name: 'deptName',
label: '部室',
width: '100',
},
{
name: 'site',
label: '现场实施人员',
hide: true,
},
{
name: 'progorderName',
label: '程序',
width: '100',
hide: true,
},
{
name: 'start_date',
label: '开始时间',
hide: true,
},
{
name: 'end_date',
label: '结束时间',
hide: true,
},
{
name: 'flag',
label: '是否调配',
hide: true,
},
{
name: 'reason',
label: '不可调配原因',
template: function (item) {
return byId(gantt.serverList('staff'), item.reason)
},
hide: true,
},
{
name: 'state',
label: '其他说明',
hide: true,
},
]
//自适应
//gantt.config.fit_tasks = true;
//连线
gantt.config.show_links = false
//表头高度
gantt.config.scale_height = 80
//开启提示:鼠标悬浮在gantt行上显示
gantt.plugins({
tooltip: true,
})
// gantt.config.start_date = new Date(2018,1,1);
// gantt.config.end_date = new Date(2020,1,1);
//周末样式突出
gantt.config.work_time = true
gantt.templates.timeline_cell_class = function (task, date) {
if (!gantt.isWorkTime(date)) return 'week_end'
return ''
}
gantt.templates.task_class = function (start, end, task) {
var css = []
if (task.flagStr == 0) {
css.push('blue_task')
} else if (task.flagStr == 1) {
css.push('light_blue_task')
}
if (task.reason) {
css = css.filter((item) => item !== 'blue_task' && item !== 'light_blue_task')
css.push('gantt_resource_task gantt_resource_' + task.reason)
}
return css.join(' ')
}
//时间轴格式化模版
var yearScaleTemplate = function (date) {
return date.getFullYear()
}
var monthScaleTemplate = function (date) {
var year = date.getFullYear()
var month = date.getMonth()
month = month + 1 + '月'
let yearTemplate = `${year}年${month}`
return yearTemplate
}
//时间轴样式
var yearScaleCss = function () {
return 'yearScaleStyle'
}
var monthScaleCss = function () {
return 'monthScaleStyle'
}
//定义表头日期
gantt.config.scales = [
{ unit: 'month', step: 1, format: monthScaleTemplate, css: monthScaleCss },
{ unit: 'day', step: 1, format: '%D <br/> %d' },
//format: function(date){
// return "<strong>Day " + dayNumber(date) + "</strong><br/>" + dateFormat(date);
// }
]
//鼠标移动显示信息
gantt.templates.tooltip_text = function (start, end, task) {
let endTime = that.setTime(gantt.templates.tooltip_date_format(end), false)
if (task.render == 'split') {
return ''
} else if (task.proGid && task.proGid !== null && task.proGid.indexOf(',') >= 0) {
let viewTask = ''
task.taskArr.forEach((item) => {
viewTask += ``
})
return viewTask
} else if (task.reason == null || task.reason == undefined) {
return ``
} else if (task.reason !== null) {
let obj = that.priorityOptions.find((item) => item.key == task.reason)
if (obj !== undefined) {
return '不可调配原因:'
}
}
}
// 弹框命名文字
gantt.locale.labels.section_priority = '不可调配原因'
gantt.locale.labels.section_progorder = '项目名称'
gantt.locale.labels.section_userName = '姓名'
gantt.locale.labels.section_deptName = '部室'
gantt.locale.labels.section_other = '其他说明'
gantt.locale.labels.message_ok = '确定'
gantt.locale.labels.confirm_deleting = '是否删除?'
//配置Gantt内置弹出框元素(title内容)
gantt.templates.lightbox_header = function (start_date, end_date, task) {
// return `<b>${task.userName}属性信息</b>`
return `<b>设置不可调配原因</b>`
}
gantt.serverList('staff', that.priorityOptions)
// 添加弹窗属性
gantt.config.lightbox.sections = [
{ name: 'priority', height: 30, map_to: 'reason', type: 'select', options: gantt.serverList('staff') },
{ name: 'other', height: 50, map_to: 'state', type: 'textarea', focus: true },
{ name: 'time', type: 'dhx_calendar', map_to: 'auto', skin: '', date_format: '%Y-%m-%d' },
]
// //双击事件
gantt.config.details_on_dblclick = true
//关闭所有错误提示信息:gantt有自己的异常消息,如果不关闭可能页面会弹出异常消息
gantt.config.show_errors = false
//禁止拖动设置任务长度
gantt.attachEvent('onBeforeTaskDrag', function (id, mode, e) {
return false
})
//禁止拖动任务
gantt.config.drag_move = false
//禁止拖动任务进度
gantt.config.drag_progress = false
//禁止拖放添加Link
gantt.config.drag_links = false
//任务条显示信息
gantt.templates.task_text = function (syart, end, task) {
console.log(task, 'taskkkk')
// var calendar = gantt.getTaskCalendar(task);
if (task.reason == 3 || task.reason == 1 || task.reason == 2 || task.reason == 4) {
let obj = that.priorityOptions.find((item) => item.key == task.reason)
if (obj !== undefined) {
return obj.label
}
} else {
if (task.progorderName !== undefined) {
return task.progorderName
}
return ''
}
}
gantt.attachEvent('onParse', function () {
var styleId = 'dynamicGanttStyles'
var element = document.getElementById(styleId)
if (!element) {
element = document.createElement('style')
element.id = styleId
document.querySelector('head').appendChild(element)
}
var html = []
var resources = gantt.serverList('staff')
resources.forEach(function (r) {
html.push(
'.gantt_task_line.gantt_resource_' +
r.key +
'{' +
'background-color:' +
r.backgroundColor +
'; ' +
'color:' +
r.textColor +
';' +
'}'
)
html.push(
'.gantt_row.gantt_resource_' +
r.key +
' .gantt_cell:nth-child(1) .gantt_tree_content{' +
'background-color:' +
r.backgroundColor +
'; ' +
'color:' +
r.textColor +
';' +
'}'
)
})
element.innerHTML = html.join('')
})
gantt.attachEvent('onTaskDblClick', function (id, e) {
if (e.target.className.indexOf('task') == -1) {
return false
}
gantt.showLightbox(id)
return true
})
gantt.config.min_column_width = 40
//监听按钮保存删除事件 updateResourceReason
let onBeforeTaskAdd = gantt.attachEvent('onBeforeTaskAdd', function (id, item) {
let endCopy = JSON.parse(JSON.stringify(item.end_date))
let endTime = that.setTime(endCopy, false)
var params = {
userId: item.parent,
startDate: moment(item.start_date).format('YYYY-MM-DD'),
endDate: moment(item.end_date).format('YYYY-MM-DD'),
// endDate: endTime,
reason: item.reason,
state: item.state,
}
updateResourceReason(params).then((res) => {
if (res.code == 0) {
that.$message.success('新增成功')
that.getData()
return true
} else {
that.$message.warning(res.msg)
that.getData()
return false
}
})
})
this.events.push(onBeforeTaskAdd)
let onBeforeTaskUpdate = gantt.attachEvent('onBeforeTaskUpdate', function (id, item) {
console.log('编辑', id, item)
// let endCopy = JSON.parse(JSON.stringify(item.end_date))
// let endTime = that.setTime(endCopy, false)
let endTime, endCopy
if (that.changeEnd) {
endCopy = JSON.parse(JSON.stringify(item.end_date))
endTime = that.setTime(endCopy, false)
} else {
endTime = moment(item.end_date).format('YYYY-MM-DD')
}
var params = {
id: item.reasonId,
userId: item.userId,
startDate: moment(item.start_date).format('YYYY-MM-DD'),
// endDate: moment(item.end_date).format('YYYY-MM-DD'),
endDate: endTime,
reason: item.reason,
state: item.state,
}
console.log(7777, item.end_date)
updateResourceReason(params).then((res) => {
console.log(1112444, res)
if (res.code == 0) {
that.$message.success('更新成功')
that.getData()
return true
} else {
that.$message.warning(res.msg)
that.getData()
return false
}
})
})
this.events.push(onBeforeTaskUpdate)
// delResourceReason
//监听按钮保存删除事件
let onBeforeTaskDelete = gantt.attachEvent('onBeforeTaskDelete', function (id, item) {
const params = {
id: item.reasonId,
userId: item.userId,
}
delResourceReason(params).then((res) => {
console.log(777, res)
if (res.code == 0) {
that.$message.success('删除成功')
var task = gantt.getTask(id)
that.getData()
return true
} else {
that.$message.warning(res.msg)
that.getData()
return false
}
})
})
this.events.push(onBeforeTaskDelete)
gantt.attachEvent('onBeforeLightbox', function (id) {
that.changeEnd = true
var task = gantt.getTask(id)
if (task.flagStr == 0) {
return false
}
return true
}),
// 初始化
setTimeout(() => {
gantt.clearAll()
gantt.init(that.$refs.ganttYear)
gantt.parse(that.tasks)
document.querySelector('.gantt-container').style.height = '500px'
}, 1000)
},
reload() {
gantt.clearAll() // 从甘特图中删除所有任务和其他元素(包括标记)
// gantt.init(this.$refs.ganttYear)
gantt.parse(this.tasks) // 数据解析
gantt.render() // 呈现整个甘特图
},
getData() {
var arrTaskNew = []
this.params.startDate = moment(this.nowDate).format('YYYY-MM-DD')
this.params.endDate = moment(this.newDate).format('YYYY-MM-DD')
getCalendarInfo(this.params).then((res) => {
if (res.code == 0) {
this.dataSource = res.data
this.dataSource.forEach((item, index) => {
item.render = 'split'
item.id = item.userId
item.start_date = ''
item.end_date = ''
item.type = gantt.config.types.task
arrTaskNew.push(item)
if (item.planDtoList !== null) {
item.planDtoList.forEach((item1, index1) => {
item1.parent = item.id
item1.start_date = item1.startDate
item1.end_date = this.setTime(item1.endDate, true)
let str = ''
if (item1.proGid !== null && item1.proGid.indexOf(',') == -1) {
item1.personMap[item1.proGid].forEach((item2, index2) => {
str += item2.names + '<br/>'
})
item1.site = str
// 如果多个时间相同的任务重叠
} else if (item1.proGid !== null && item1.proGid.indexOf(',') >= 0) {
item1.taskArr = []
let proGidArr = item1.proGid.split(',')
let progorderNameArr = item1.progorderName.split(',')
proGidArr.forEach((item3, index3) => {
let objtask = {
proGid: '',
progorderName: '',
names: '',
}
objtask.proGid = item3
objtask.progorderName = progorderNameArr[index3]
item1.personMap[item3].forEach((item4, index4) => {
objtask.names += item4.names + '<br/>'
})
item1.taskArr.push(objtask)
})
}
arrTaskNew.push(item1)
})
}
})
this.tasks.data = arrTaskNew
// this.init()
this.reload()
} else {
this.$message.warning(res.msg)
}
})
},
setTime(date, bool) {
if (date == null || !date) return null
var dateTime
if (typeof date == 'string') {
dateTime = new Date(date)
} else {
dateTime = date
}
if (bool) {
dateTime = dateTime.setDate(dateTime.getDate() + 1)
dateTime = new Date(dateTime)
} else {
dateTime = dateTime.setDate(dateTime.getDate() - 1)
dateTime = new Date(dateTime)
}
if (typeof date == 'string') {
return this.changeDate(dateTime)
} else {
return dateTime
}
},
changeDate(chinaTime) {
if (typeof date == 'string') return chinaTime
let m = chinaTime.getMonth() + 1
let d = chinaTime.getDate()
if (m >= 0 && m <= 9) {
m = '0' + m
}
if (d >= 0 && d <= 9) {
d = '0' + d
}
return chinaTime.getFullYear() + '-' + m + '-' + d
},
queryApi() {},
},
computed: {
...mapState({
multiTab: (state) => state.app.multiTab,
}),
defaultTime() {
return [this.nowDate, this.newDate]
},
},
beforeDestroy() {
this.events.forEach((ele) => {
gantt.detachEvent(ele)
})
},
}
</script>
<style scoped lang="less">
.gantt-container {
height: 500px;
min-height: 400px;
max-height: 800px !important;
overflow-y: auto !important;
}
.radioRow {
margin: 10px;
margin-bottom: 10px;
display: flex;
align-items: center;
flex-wrap: wrap;
}
rsor: pointer;
}
.dhx_calendar_cont input {
width: 96px;
padding: 0;
margin: 3px 10px 10px 10px;
font-size: 11px;
height: 17px;
text-align: center;
border: 1px solid #ccc;
color: #646464;
}
.dhtmlxcalendar_dhx_skyblue,
.dhtmlxcalendar_dhx_web,
.dhtmlxcalendar_dhx_terrace {
z-index: 999999 !important;
}
</style>