用vue实现的一个自定义甘特图画法
用vue开发一个甘特图,实现了添加,删除,托拉拽的效果,数据的更新没有做,码友可以自行补充。效果如图:
<template>
<div class="main">
<div class="block">
<el-form :inline="true" :model="formInline" class="form-inline">
<el-form-item label="请选择颜色">
<el-color-picker v-model="formInline.bg"></el-color-picker>
</el-form-item>
<el-form-item label="流程名">
<el-input v-model="formInline.content"></el-input>
</el-form-item>
<el-form-item label="开始时间">
<el-input v-model="formInline.start"></el-input>
</el-form-item>
<el-form-item label="结束时间">
<el-input v-model="formInline.end"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="addTask">生成流程</el-button>
</el-form-item>
</el-form>
</div>
<div class="gantt-chart border-box" ref="ganttChart" id="ganttChart">
<div class="big-range-time">
<div
v-for="item in time.bigtime"
:key="item.time"
class="big-time-item"
:style="{ width: 31 * width + 'px' }"
:title="item.time"
>
{{ item.time }}
</div>
</div>
<div class="small-range-time">
<div
v-for="(item, index) in time.smalltime"
:key="index"
class="small-time-item"
:title="item"
:style="{ height: height + 'px', width: width + 'px' }"
>
{{ item + 1 }}
</div>
</div>
<div class="task-background" v-if="time.smalltime.length == 0">
<div>暂无数据</div>
</div>
<div class="task-background" v-else>
<div
v-for="(item, index) in gantData.taskitem"
:key="index"
class="task-block"
:style="{ height: height + 'px', width: width + 'px' }"
>
<div
class="task-time-block"
v-for="item in time.smalltime"
:key="item"
:style="{ height: height + 'px', width: width + 'px' }"
></div>
</div>
</div>
<div class="task-detail" id="task" ref="task" :style="{ top: height*2 + 'px'}">
<div
:draggable="draggable"
class="drag-item"
v-for="(item, index) in taskList"
@dragstart="dragstart($event, index)"
@dragenter="dragenter($event, index)"
@dragover="dragover($event, index)"
@dragend="dragend($event, index)"
:key="item.id"
:style="{ height: height + 'px', width: width * 31 + 'px' }"
>
<div
class="task-item"
:id="item.id"
:title="item.title"
:style="{
width: item.width + 'px',
top: item.top + 'px',
left: item.left + 'px',
background: item.background,
height: height + 'px',
}"
>
{{ item.content }}
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
data () {
return {
height: 40, // 最小时间单位的高度
width: 40, // 最小时间单位宽度,大于30
time: {
bigtime: [{ time: '2022-07' }],
smalltime: [],
bigtimetotal: 1,
smalltimetotal: 0
},
formInline: {
bg: '#409EFF',
content: '',
start: '',
end: ''
},
task: {
top: '',
bottom: '',
left: '',
right: ''
},
gantData: {
tasktotal: 5,
taskitem: [
{ start: 12, end: 16.5, content: 'div0', bg: 'lightblue' },
{ start: 7, end: 30, content: 'div1', bg: 'lightblue' },
{ start: 30, end: 31, content: 'div2', bg: 'lightblue' },
{ start: 1, end: 5, content: 'div3', bg: 'lightblue' },
{ start: 1, end: 8, content: 'div4', bg: 'lightblue' }
]
},
taskList: [],
dragIdx: '', // 当前拖动元素的位置
objIdx: '', // 目标位置
draggable: true // 判断是否可以上下拖动
}
},
methods: {
// 添加流程
addTask () {
this.gantData.taskitem.unshift(this.formInline)
this.getTask()
},
dragstart (e, index) {
e.stopPropagation()
this.dragIdx = index
},
dragenter (e, index) {
e.preventDefault()
this.objIdx = index
},
dragend (e, index) {
e.preventDefault()
if (this.dragIdx !== this.objIdx) {
const node = this.gantData.taskitem[this.dragIdx]
this.gantData.taskitem.splice(this.dragIdx, 1)
this.gantData.taskitem.splice(this.objIdx, 0, node)
this.dragIndex = this.objIdx
this.getTask()
}
},
dragover (e, index) {
e.preventDefault()
},
bindEvent () {
this.$nextTick(() => {
this.taskList.forEach((e, index) => {
const dObj = document.getElementById(e.id)
const _that = this
if (dObj) {
dObj.addEventListener('mousemove', (e) => {
let stepX
let temppointX
let tempwidth
let isMouseDown
let stretch
let tempLeft
// 检测是否在进度条边界
// 拉伸
if (Math.abs(e.offsetX - dObj.offsetWidth) < 10) {
dObj.style.cursor = 'e-resize'
dObj.onmousedown = (e) => {
_that.draggable = false
temppointX = e.clientX
tempwidth = dObj.offsetWidth
stretch = true
isMouseDown = false
document.addEventListener('mousemove', (e) => {
if (stretch && dObj.offsetWidth + dObj.offsetLeft <= 31 * _that.width && dObj.offsetWidth >= 30) {
const stepX = e.clientX - temppointX
dObj.style.width = tempwidth + stepX + 'px'
}
if (dObj.offsetWidth + dObj.offsetLeft > 31 * _that.width) {
dObj.style.width = 31 * _that.width - dObj.offsetLeft + 'px'
}
})
}
document.addEventListener('mouseup', isOk)
} else {
// 拖曳
dObj.style.cursor = 'move'
dObj.onmousedown = (e) => {
_that.draggable = false
temppointX = e.clientX
tempLeft = dObj.offsetLeft
isMouseDown = true
stretch = false
dObj.addEventListener('mousemove', (e) => {
if (isMouseDown && dObj.offsetWidth + dObj.offsetLeft <= 31 * _that.width && dObj.offsetLeft >= 0) {
stepX = e.clientX - temppointX
dObj.style.left = tempLeft + stepX + 'px'
}
if (dObj.offsetWidth + dObj.offsetLeft > 31 * _that.width) {
dObj.style.left = 31 * _that.width - dObj.offsetWidth + 'px'
}
if (dObj.offsetLeft < 0) {
dObj.style.left = 0 + 'px'
}
document.onmouseup = isOk
})
}
}
function isOk () {
document.onmousemove = null
document.onmousedown = null
isMouseDown = false
stretch = false
dObj.style.cursor = 'move'
_that.draggable = true
}
})
// 删除
dObj.oncontextmenu = (e) => {
e.preventDefault()
this.$confirm(`此操作将删除${dObj.innerText}, 是否继续?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(() => {
this.taskList.splice(index, 1)
this.taskList.forEach((e) => {
if (e.id > dObj.id) {
e.top = e.top - this.height
}
})
this.gantData.taskitem.splice(index, 1)
this.$message({
type: 'success',
message: '删除成功!'
})
})
.catch(() => {})
}
}
})
})
},
getTask () {
this.taskList = []
this.gantData.taskitem.forEach((e, index) => {
const dObj = {}
dObj.left = (e.start - 1) * this.width
dObj.top = index * this.height
dObj.content = e.content
dObj.title = e.content
dObj.width = (e.end - e.start + 1) * this.width
dObj.id = 'task-item-' + index
dObj.background = e.bg
this.taskList.push(dObj)
})
this.bindEvent()
},
init () {
for (let i = 0; i < 31; i++) {
this.time.smalltime.push(i)
}
this.time.smalltimetotal = this.time.smalltime.length
const taskDom = document.getElementById('task')
this.task.top = taskDom.offsetTop
this.task.bottom = taskDom.offsetTop + this.width * this.gantData.taskitem.length
this.task.left = taskDom.offsetLeft
this.task.right = taskDom.offsetLeft + this.time.smalltimetotal * this.width
this.getTask()
}
},
created () {},
mounted () {
this.init()
}
}
</script>
<style scoped lang="scss">
.main {
margin: 0 auto;
width: 1200px;
.block {
display: flex;
height: 100px;
align-items: center;
}
.gantt-chart {
overflow-x: scroll;
position: relative;
border: 2px solid rgba(0, 0, 0, .1);
border-radius: 8px;
box-shadow: 5px 5px 10px 5px rgba(0, 0, 0, .1);
.task-background {
.task-block {
display: flex;
.task-time-block {
flex: 0 0 auto;
box-sizing: border-box;
border: 1px solid lightgray;
}
}
}
.big-range-time {
background: lightcyan;
height: 40px;
display: inline-flex;
.big-time-item {
user-select: none;
display: flex;
justify-content: center;
align-items: center;
box-sizing: border-box;
border: 1px solid lightgray;
}
}
.small-range-time {
display: inline-flex;
.small-time-item {
user-select: none;
background: lightgreen;
display: flex;
justify-content: center;
align-items: center;
box-sizing: border-box;
border: 1px solid lightgray;
}
}
.task-detail {
position: absolute;
z-index: 99;
.drag-item {
.task-item {
display: flex;
justify-content: center;
align-items: center;
user-select: none;
border: 1px solid lightgray;
box-sizing: border-box;
min-width: 30px;
position: absolute;
border-radius: 15px;
}
}
}
}
}
</style>