2024-09-22 23:19:03 +03:00

440 lines
17 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="common-gantt">
<div class="gantt-left" :style="leftStyle">
<div class="gantt-title">
<div class="gantt-title-text">{{$L('任务名称')}}</div>
<div class="gantt-title-right"><slot name="titleTool"/></div>
</div>
<ul ref="ganttItem"
class="gantt-item"
@scroll="itemScrollListener"
@mouseenter="mouseType='item'">
<li v-for="(item, key) in lists" :key="key" @click="clickItem(item, key)">
<div v-if="item.overdue" class="item-overdue">{{$L('已超期')}}</div>
<div class="item-title" :class="{complete:item.complete, overdue:item.overdue}">{{item.label}}</div>
<Icon class="item-icon" type="ios-locate-outline" @click.stop="scrollPosition(key)"/>
</li>
</ul>
</div>
<div ref="ganttRight" class="gantt-right">
<div class="gantt-size" @click="maximize=!maximize">
<i v-if="maximize" class="taskfont">&#xe7d4;</i>
<i v-else class="taskfont">&#xe7d3;</i>
</div>
<div
ref="ganttChart"
class="gantt-chart"
@touchstart="dateTouchstart"
@touchmove="dateTouchmove"
@touchend="dateTouchend">
<ul class="gantt-month">
<li v-for="(item, key) in monthNum" :key="key" :style="monthStyle(key)">
<div class="month-format">{{monthFormat(key)}}</div>
</li>
</ul>
<ul class="gantt-date" @mousedown="dateMouseDown">
<li v-for="(item, key) in dateNum" :key="key" :style="dateStyle(key)">
<div class="date-format">
<div class="format-day">{{dateFormat(key, 'day')}}</div>
<div v-if="dateWidth > 46" class="format-week">{{dateFormat(key, 'week')}}</div>
</div>
</li>
</ul>
<ul ref="ganttTimeline"
class="gantt-timeline"
@scroll="timelineScrollListener"
@mouseenter="mouseType='timeline'">
<li v-for="(item, key) in lists" :key="key" :data-id="item.id">
<div
class="timeline-item"
:style="itemStyle(item)"
@mousedown="itemMouseDown($event, item)">
<div class="timeline-title" :title="item.label">{{item.label}}</div>
<div class="timeline-resizer"></div>
</div>
</li>
</ul>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'GanttView',
props: {
lists: {
type: Array
},
menuWidth: {
type: Number,
default: 300
},
itemWidth: {
type: Number,
default: 100
}
},
data() {
return {
mouseType: '',
mouseWidth: 0,
mouseScaleWidth: 0,
dateWidth: 100,
ganttWidth: 0,
mouseItem: null,
mouseBak: {},
dateMove: null,
maximize: false,
}
},
mounted() {
this.maximize = this.windowPortrait;
this.dateWidth = this.itemWidth;
this.$refs.ganttRight.addEventListener('mousewheel', this.handleScroll, false);
document.addEventListener('mousemove', this.itemMouseMove);
document.addEventListener('mouseup', this.itemMouseUp);
window.addEventListener("resize", this.handleResize, false);
this.handleResize();
},
beforeDestroy() {
this.$refs.ganttRight.removeEventListener('mousewheel', this.handleScroll, false);
document.removeEventListener('mousemove', this.itemMouseMove);
document.removeEventListener('mouseup', this.itemMouseUp);
window.removeEventListener("resize", this.handleResize, false);
},
watch: {
itemWidth(val) {
this.dateWidth = val;
},
maximize() {
this.$nextTick(() => {
this.handleResize();
})
}
},
computed: {
leftStyle({menuWidth, maximize}) {
const style = {width: menuWidth + 'px'}
if (maximize) {
style.display = 'none';
}
return style
},
monthNum() {
const {ganttWidth, dateWidth} = this;
return Math.floor(ganttWidth / dateWidth / 30) + 2
},
monthStyle() {
const {mouseWidth, dateWidth} = this;
return function(index) {
let mouseDay = mouseWidth == 0 ? 0 : mouseWidth / dateWidth;
//今天00:00:00
let nowDay = $A.daytz().startOf('day');
//当前时间
let curDay = nowDay.clone().add(mouseDay * 86400, 'second');
//当月最后一天
let lastDay = curDay.clone().endOf('month');
//相差天数
let diffDay = (lastDay - curDay) / 1000 / 60 / 60 / 24;
//
let width = dateWidth * diffDay;
if (index > 0) {
lastDay = curDay.clone().add(index + 1, 'month').endOf('month');
width = lastDay.date() * dateWidth;
}
return {
width: width + 'px',
}
}
},
monthFormat() {
const {mouseWidth, dateWidth} = this;
return function(index) {
let mouseDay = mouseWidth == 0 ? 0 : mouseWidth / dateWidth;
//开始位置时间今天00:00:00
let nowDay = $A.daytz().startOf('day');
//当前时间
let curDay = nowDay.clone().add(mouseDay, 'day');
//
if (index > 0) {
curDay = curDay.add(index + 1, 'month');
}
return curDay.format('YYYY-MM')
}
},
dateNum() {
const {ganttWidth, dateWidth} = this;
return Math.floor(ganttWidth / dateWidth) + 2
},
dateStyle() {
const {mouseWidth, dateWidth} = this;
return function(index) {
const style = {};
//
let mouseDay = mouseWidth == 0 ? 0 : mouseWidth / dateWidth;
let mouseData = Math.floor(mouseDay) + index;
if (mouseDay == Math.floor(mouseDay)) {
mouseData--;
}
let j = mouseWidth == 0 ? index - 1 : mouseData;
let date = $A.daytz().add(j, 'day');
if ([0, 6].indexOf(date.day()) !== -1) {
style.backgroundColor = '#f9fafb';
}
//
let width = dateWidth;
if (index == 0) {
width = Math.abs((mouseWidth % width - width) % width);
}
style.width = width + 'px';
return style
}
},
dateFormat() {
const {mouseWidth, dateWidth} = this;
return function(index, type) {
let mouseDay = mouseWidth == 0 ? 0 : mouseWidth / dateWidth;
let mouseData = Math.floor(mouseDay) + index;
if (mouseDay == Math.floor(mouseDay)) {
mouseData--;
}
let j = mouseWidth == 0 ? index - 1 : mouseData;
let date = $A.daytz().add(j, 'day');
if (type == 'day') {
return date.date();
} else if (type == 'week') {
return this.$L(`星期${'日一二三四五六'.charAt(date.day())}`);
} else {
return date;
}
}
},
itemStyle() {
const {mouseWidth, dateWidth, ganttWidth} = this;
return function(item) {
const {start, end} = item.time;
const {style, moveX, moveW} = item;
//开始位置时间戳今天00:00:00时间戳
let nowTime = $A.daytz().startOf('day').valueOf();
//距离开始位置多少天
let diffStartDay = (start - nowTime) / 1000 / 60 / 60 / 24;
let diffEndDay = (end - nowTime) / 1000 / 60 / 60 / 24;
//
let left = dateWidth * diffStartDay + (mouseWidth * -1);
let width = Math.max(10, dateWidth * (diffEndDay - diffStartDay));
if (typeof moveX === "number") {
left+= moveX;
}
if (typeof moveW === "number") {
width+= moveW;
}
//
const customStyle = {
left: Math.min(Math.max(left, width * -1.2), ganttWidth * 1.2).toFixed(2) + 'px',
width: width.toFixed(2) + 'px',
};
if (left < 0 && Math.abs(left) < width) {
customStyle.paddingLeft = Math.abs(left).toFixed(2) + 'px'
}
if (left + width > ganttWidth && left < ganttWidth) {
customStyle.paddingRight = Math.abs(left + width - ganttWidth).toFixed(2) + 'px'
}
if (typeof style === "object") {
return Object.assign(customStyle, style);
}
return customStyle
}
},
},
methods: {
itemScrollListener(e) {
if (this.mouseType == 'timeline') {
return;
}
this.$refs.ganttTimeline.scrollTop = e.target.scrollTop;
},
timelineScrollListener(e) {
if (this.mouseType == 'item') {
return;
}
this.$refs.ganttItem.scrollTop = e.target.scrollTop;
},
handleScroll(e) {
e.preventDefault();
if (e.ctrlKey) {
//缩放
this.dateWidth = Math.min(600, Math.max(24, this.dateWidth - Math.floor(e.deltaY)));
this.mouseWidth = this.ganttWidth / 2 * ((this.dateWidth - 100) / 100) + this.dateWidth / 100 * this.mouseScaleWidth;
return;
}
if (e.deltaY != 0) {
const ganttTimeline = this.$refs.ganttTimeline;
let newTop = ganttTimeline.scrollTop + e.deltaY;
if (newTop < 0) {
newTop = 0;
} else if (newTop > ganttTimeline.scrollHeight - ganttTimeline.clientHeight) {
newTop = ganttTimeline.scrollHeight - ganttTimeline.clientHeight;
}
if (ganttTimeline.scrollTop != newTop) {
this.mouseType = 'timeline';
ganttTimeline.scrollTop = newTop;
}
}
if (e.deltaX != 0) {
this.mouseWidth+= e.deltaX;
this.mouseScaleWidth+= e.deltaX * (100 / this.dateWidth);
}
},
handleResize() {
this.ganttWidth = this.$refs.ganttTimeline.clientWidth;
},
dateTouchstart(e) {
if (this.windowPortrait) {
this.maximize = true
}
let parent = e.target.parentNode
let item = null
while (parent) {
if (!parent || parent === this.$refs.ganttChart) {
break
}
if (parent.tagName === 'LI') {
const itemId = parent.getAttribute('data-id')
if (itemId) {
item = this.lists.find(({id}) => itemId == id)
}
}
parent = parent.parentNode
}
if (!item) {
this.onDateMove(e.touches[0].clientX);
return
}
this.onItemMove(item, e.target, e.touches[0].clientX);
},
dateTouchmove(e) {
this.onMoving(e.touches[0].clientX)
},
dateTouchend() {
this.onMoveOver(null);
},
dateMouseDown(e) {
e.preventDefault();
this.onDateMove(e.clientX);
},
itemMouseDown(e, item) {
e.preventDefault();
this.onItemMove(item, e.target, e.clientX);
},
itemMouseMove(e) {
if (this.mouseItem != null || this.dateMove != null) {
e.preventDefault();
this.onMoving(e.clientX);
}
},
itemMouseUp(e) {
this.onMoveOver(e.target);
},
onDateMove(clientX) {
this.mouseItem = null;
this.dateMove = {
clientX
};
},
onItemMove(item, target, clientX) {
let type = 'moveX';
if (target.classList.contains('timeline-resizer')) {
type = 'moveW';
}
if (typeof item[type] !== "number") {
this.$set(item, type, 0);
}
this.mouseBak = {
type: type,
clientX: clientX,
value: item[type],
};
this.mouseItem = item;
this.dateMove = null;
},
onMoving(clientX) {
if (this.mouseItem != null) {
const diff = this.mouseBak.value + (clientX - this.mouseBak.clientX);
if (this.mouseBak.type === 'moveW') {
const oneWidthTime = 86400000 / this.dateWidth;
const {start, end} = this.mouseItem.time;
let moveTime = diff * oneWidthTime;
if (end + moveTime - start <= 0) {
return
}
}
this.$set(this.mouseItem, this.mouseBak.type, diff);
return;
}
if (this.dateMove != null) {
let moveX = (this.dateMove.clientX - clientX) * 5;
this.dateMove.clientX = clientX;
this.mouseWidth+= moveX;
this.mouseScaleWidth+= moveX * (100 / this.dateWidth);
}
},
onMoveOver(target) {
if (this.mouseItem != null) {
const {start, end} = this.mouseItem.time;
let isM = false;
//一个宽度的时间
let oneWidthTime = 86400000 / this.dateWidth;
//修改起止时间
if (typeof this.mouseItem.moveX === "number" && this.mouseItem.moveX != 0) {
let moveTime = this.mouseItem.moveX * oneWidthTime;
this.$set(this.mouseItem.time, 'start', start + moveTime);
this.$set(this.mouseItem.time, 'end', end + moveTime);
this.$set(this.mouseItem, 'moveX', 0);
isM = true;
}
//修改结束时间
if (typeof this.mouseItem.moveW === "number" && this.mouseItem.moveW != 0) {
let moveTime = this.mouseItem.moveW * oneWidthTime;
this.$set(this.mouseItem.time, 'end', end + moveTime);
this.$set(this.mouseItem, 'moveW', 0);
isM = true;
}
//
if (isM) {
this.$emit("on-change", this.mouseItem)
} else if (target && target.className == 'timeline-title') {
this.clickItem(this.mouseItem);
}
this.mouseItem = null;
return
}
if (this.dateMove != null) {
this.dateMove = null;
}
},
scrollPosition(pos) {
//今天00:00:00
let nowDay = $A.daytz().startOf('day').valueOf();
//一个宽度的时间
let oneWidthTime = 86400000 / this.dateWidth;
//
let moveWidth = (this.lists[pos].time.start - nowDay) / oneWidthTime - this.dateWidth - this.mouseWidth;
this.mouseWidth+= moveWidth;
this.mouseScaleWidth+= moveWidth * (100 / this.dateWidth);
},
clickItem(item, key = undefined) {
if (key !== undefined && this.windowPortrait) {
this.scrollPosition(key)
return
}
this.$emit("on-click", item)
}
}
}
</script>