mirror of
https://github.com/kuaifan/dootask.git
synced 2025-12-15 13:22:49 +00:00
perf: 优化甘特图
This commit is contained in:
parent
b83ce02849
commit
5adbd6e8f1
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="wook-gantt">
|
<div class="common-gantt">
|
||||||
<div class="gantt-left" :style="{width:menuWidth+'px'}">
|
<div class="gantt-left" :style="{width:menuWidth+'px'}">
|
||||||
<div class="gantt-title">
|
<div class="gantt-title">
|
||||||
<div class="gantt-title-text">{{$L('任务名称')}}</div>
|
<div class="gantt-title-text">{{$L('任务名称')}}</div>
|
||||||
@ -26,7 +26,7 @@
|
|||||||
<li v-for="(item, key) in dateNum" :key="key" :style="dateStyle(key)">
|
<li v-for="(item, key) in dateNum" :key="key" :style="dateStyle(key)">
|
||||||
<div class="date-format">
|
<div class="date-format">
|
||||||
<div class="format-day">{{dateFormat(key, 'day')}}</div>
|
<div class="format-day">{{dateFormat(key, 'day')}}</div>
|
||||||
<div v-if="dateWidth > 46" class="format-wook">{{dateFormat(key, 'wook')}}</div>
|
<div v-if="dateWidth > 46" class="format-week">{{dateFormat(key, 'week')}}</div>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@ -39,7 +39,7 @@
|
|||||||
class="timeline-item"
|
class="timeline-item"
|
||||||
:style="itemStyle(item)"
|
:style="itemStyle(item)"
|
||||||
@mousedown="itemMouseDown($event, item)">
|
@mousedown="itemMouseDown($event, item)">
|
||||||
<div class="timeline-title">{{item.label}}</div>
|
<div class="timeline-title" :title="item.label">{{item.label}}</div>
|
||||||
<div class="timeline-resizer"></div>
|
<div class="timeline-resizer"></div>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
@ -184,7 +184,7 @@ export default {
|
|||||||
let date = new Date(new Date().getTime() + j * 86400000)
|
let date = new Date(new Date().getTime() + j * 86400000)
|
||||||
if (type == 'day') {
|
if (type == 'day') {
|
||||||
return date.getDate();
|
return date.getDate();
|
||||||
} else if (type == 'wook') {
|
} else if (type == 'week') {
|
||||||
return this.$L(`星期${'日一二三四五六'.charAt(date.getDay())}`);
|
return this.$L(`星期${'日一二三四五六'.charAt(date.getDay())}`);
|
||||||
} else {
|
} else {
|
||||||
return date;
|
return date;
|
||||||
@ -291,7 +291,6 @@ export default {
|
|||||||
type: type,
|
type: type,
|
||||||
clientX: e.clientX,
|
clientX: e.clientX,
|
||||||
value: item[type],
|
value: item[type],
|
||||||
time: item.time,
|
|
||||||
};
|
};
|
||||||
this.mouseItem = item;
|
this.mouseItem = item;
|
||||||
this.dateMove = null;
|
this.dateMove = null;
|
||||||
@ -299,18 +298,16 @@ export default {
|
|||||||
itemMouseMove(e) {
|
itemMouseMove(e) {
|
||||||
if (this.mouseItem != null) {
|
if (this.mouseItem != null) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
var diff = e.clientX - this.mouseBak.clientX;
|
const diff = this.mouseBak.value + (e.clientX - this.mouseBak.clientX);
|
||||||
const {start, end} = this.mouseBak.time;
|
if (this.mouseBak.type === 'moveW') {
|
||||||
let date = new Date();
|
const oneWidthTime = 86400000 / this.dateWidth;
|
||||||
let nowTime = new Date(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0).getTime();
|
const {start, end} = this.mouseItem.time;
|
||||||
let diffStartDay = (start - nowTime) / 1000 / 60 / 60 / 24;
|
let moveTime = diff * oneWidthTime;
|
||||||
let diffEndDay = (end - nowTime) / 1000 / 60 / 60 / 24;
|
if (end + moveTime - start <= 0) {
|
||||||
let width = this.dateWidth * (diffEndDay - diffStartDay);
|
return
|
||||||
width += this.mouseBak.value + diff;
|
}
|
||||||
if (width <= 0) {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
this.$set(this.mouseItem, this.mouseBak.type, this.mouseBak.value + diff);
|
this.$set(this.mouseItem, this.mouseBak.type, diff);
|
||||||
} else if (this.dateMove != null) {
|
} else if (this.dateMove != null) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
let moveX = (this.dateMove.clientX - e.clientX) * 5;
|
let moveX = (this.dateMove.clientX - e.clientX) * 5;
|
||||||
@ -340,9 +337,6 @@ export default {
|
|||||||
this.$set(this.mouseItem, 'moveW', 0);
|
this.$set(this.mouseItem, 'moveW', 0);
|
||||||
isM = true;
|
isM = true;
|
||||||
}
|
}
|
||||||
if (this.mouseItem.time && this.mouseItem.time.start > this.mouseItem.time.end) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
//
|
//
|
||||||
if (isM) {
|
if (isM) {
|
||||||
this.$emit("on-change", this.mouseItem)
|
this.$emit("on-change", this.mouseItem)
|
||||||
@ -371,3 +365,265 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.common-gantt {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: self-start;
|
||||||
|
color: #747a81;
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.gantt-left {
|
||||||
|
flex-grow:0;
|
||||||
|
flex-shrink:0;
|
||||||
|
height: 100%;
|
||||||
|
background-color: #ffffff;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
&:after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 1px;
|
||||||
|
background-color: rgba(237, 241, 242, 0.75);
|
||||||
|
}
|
||||||
|
.gantt-title {
|
||||||
|
height: 76px;
|
||||||
|
flex-grow: 0;
|
||||||
|
flex-shrink: 0;
|
||||||
|
background-color: #F9FAFB;
|
||||||
|
padding-left: 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
.gantt-title-text {
|
||||||
|
line-height: 100px;
|
||||||
|
max-width: 200px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.gantt-item {
|
||||||
|
transform: translateZ(0);
|
||||||
|
max-height: 100%;
|
||||||
|
overflow: auto;
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
> li {
|
||||||
|
height: 40px;
|
||||||
|
border-bottom: 1px solid rgba(237, 241, 242, 0.75);
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding-left: 12px;
|
||||||
|
&:hover {
|
||||||
|
.item-icon {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.item-overdue {
|
||||||
|
flex-grow:0;
|
||||||
|
flex-shrink:0;
|
||||||
|
color: #ffffff;
|
||||||
|
margin-right: 4px;
|
||||||
|
background-color: #ff0000;
|
||||||
|
padding: 1px 3px;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 18px;
|
||||||
|
}
|
||||||
|
.item-title {
|
||||||
|
flex: 1;
|
||||||
|
padding-right: 12px;
|
||||||
|
cursor: default;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
&.complete {
|
||||||
|
text-decoration: line-through;
|
||||||
|
}
|
||||||
|
&.overdue {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.item-icon {
|
||||||
|
display: none;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 32px;
|
||||||
|
margin-right: 2px;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #888888;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.gantt-right {
|
||||||
|
flex: 1;
|
||||||
|
height: 100%;
|
||||||
|
background-color: #ffffff;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
.gantt-chart {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
transform: translateZ(0);
|
||||||
|
.gantt-month {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 1;
|
||||||
|
height: 26px;
|
||||||
|
line-height: 20px;
|
||||||
|
font-size: 14px;
|
||||||
|
background-color: #F9FAFB;
|
||||||
|
> li {
|
||||||
|
flex-grow: 0;
|
||||||
|
flex-shrink: 0;
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
&:after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
width: 1px;
|
||||||
|
height: 100%;
|
||||||
|
background-color: rgba(237, 241, 242, 0.75);
|
||||||
|
}
|
||||||
|
.month-format {
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
padding: 6px 6px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.gantt-date {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
position: absolute;
|
||||||
|
top: 26px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 2;
|
||||||
|
cursor: move;
|
||||||
|
&:before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 50px;
|
||||||
|
background-color: #F9FAFB;
|
||||||
|
}
|
||||||
|
> li {
|
||||||
|
flex-grow: 0;
|
||||||
|
flex-shrink: 0;
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
&:after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
width: 1px;
|
||||||
|
height: 100%;
|
||||||
|
background-color: rgba(237, 241, 242, 0.75);
|
||||||
|
}
|
||||||
|
.date-format {
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 44px;
|
||||||
|
.format-day {
|
||||||
|
line-height: 28px;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
.format-week {
|
||||||
|
line-height: 16px;
|
||||||
|
font-weight: 300;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.gantt-timeline {
|
||||||
|
position: absolute;
|
||||||
|
top: 76px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 3;
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
|
> li {
|
||||||
|
cursor: default;
|
||||||
|
height: 40px;
|
||||||
|
border-bottom: 1px solid rgba(237, 241, 242, 0.75);
|
||||||
|
position: relative;
|
||||||
|
.timeline-item {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
touch-action: none;
|
||||||
|
pointer-events: auto;
|
||||||
|
padding: 4px;
|
||||||
|
margin-top: 4px;
|
||||||
|
background: #e74c3c;
|
||||||
|
border-radius: 18px;
|
||||||
|
color: #fff;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
will-change: contents;
|
||||||
|
height: 32px;
|
||||||
|
.timeline-title {
|
||||||
|
touch-action: none;
|
||||||
|
flex-grow: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
margin-left: 4px;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
.timeline-resizer {
|
||||||
|
height: 22px;
|
||||||
|
touch-action: none;
|
||||||
|
width: 8px;
|
||||||
|
background: rgba(255,255,255,0.1);
|
||||||
|
cursor: ew-resize;
|
||||||
|
flex-shrink: 0;
|
||||||
|
will-change: visibility;
|
||||||
|
position: absolute;
|
||||||
|
top: 5px;
|
||||||
|
right: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
18
resources/assets/js/functions/web.js
vendored
18
resources/assets/js/functions/web.js
vendored
@ -386,24 +386,6 @@
|
|||||||
getDialogUnread(dialog) {
|
getDialogUnread(dialog) {
|
||||||
return dialog ? (dialog.unread || dialog.is_mark_unread || 0) : 0
|
return dialog ? (dialog.unread || dialog.is_mark_unread || 0) : 0
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* 克隆对象
|
|
||||||
* @param myObj
|
|
||||||
* @returns {*}
|
|
||||||
*/
|
|
||||||
cloneData(myObj) {
|
|
||||||
if (typeof (myObj) !== 'object') return myObj;
|
|
||||||
if (myObj === null) return myObj;
|
|
||||||
//
|
|
||||||
if (typeof myObj.length === 'number') {
|
|
||||||
let [...myNewObj] = myObj;
|
|
||||||
return myNewObj;
|
|
||||||
} else {
|
|
||||||
let {...myNewObj} = myObj;
|
|
||||||
return myNewObj;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -1,20 +1,26 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="project-gstc-gantt">
|
<div class="project-gstc-gantt">
|
||||||
<GanttView :lists="lists" :menuWidth="windowWidth ? 180 : 260" :itemWidth="80" @on-change="updateTime" @on-click="clickItem"/>
|
<GanttView
|
||||||
<Dropdown class="project-gstc-dropdown-filtr" :style="windowWidth?{left:'142px'}:{}" @on-click="tapProject">
|
:lists="lists"
|
||||||
<Icon class="project-gstc-dropdown-icon" :class="{filtr:filtrProjectId>0}" type="md-funnel" />
|
:menuWidth="menuWidth"
|
||||||
|
:itemWidth="80"
|
||||||
|
@on-change="onChange"
|
||||||
|
@on-click="onClick"/>
|
||||||
|
<Dropdown class="project-gstc-dropdown-filtr" :style="dropStyle" trigger="click" @on-click="onSwitchColumn">
|
||||||
|
<Icon class="project-gstc-dropdown-icon" :class="{filtr:filtrProjectId > 0}" type="md-funnel" />
|
||||||
<DropdownMenu slot="list">
|
<DropdownMenu slot="list">
|
||||||
<DropdownItem :name="0" :class="{'dropdown-active':filtrProjectId==0}">{{$L('全部')}}</DropdownItem>
|
<DropdownItem :name="0" :class="{'dropdown-active':filtrProjectId == 0}">{{ $L('全部') }}</DropdownItem>
|
||||||
<DropdownItem v-for="(item, index) in projectLabel"
|
<DropdownItem
|
||||||
:key="index"
|
v-for="(item, index) in projectColumn"
|
||||||
:name="item.id"
|
:key="index"
|
||||||
:class="{'dropdown-active':filtrProjectId==item.id}">
|
:name="item.id"
|
||||||
{{item.name}}
|
:class="{'dropdown-active':filtrProjectId == item.id}">
|
||||||
<span v-if="item.tasks">({{item.tasks.length}})</span>
|
{{ item.name }}
|
||||||
|
<span v-if="item.tasks">({{ item.tasks.length }})</span>
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
<div class="project-gstc-edit" :class="{info:editShowInfo, visible:editData&&editData.length > 0}">
|
<div class="project-gstc-edit" :class="{info:editShowInfo, visible:editData && editData.length > 0}">
|
||||||
<div class="project-gstc-edit-info">
|
<div class="project-gstc-edit-info">
|
||||||
<Table size="small" max-height="600" :columns="editColumns" :data="editData"></Table>
|
<Table size="small" max-height="600" :columns="editColumns" :data="editData"></Table>
|
||||||
<div class="project-gstc-edit-btns">
|
<div class="project-gstc-edit-btns">
|
||||||
@ -33,85 +39,83 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import GanttView from "./GanttView";
|
|
||||||
import {mapState} from "vuex";
|
import {mapState} from "vuex";
|
||||||
|
import GanttView from "../../../components/GanttView";
|
||||||
|
|
||||||
/**
|
|
||||||
* 甘特图
|
|
||||||
*/
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ProjectGantt',
|
name: 'ProjectGantt',
|
||||||
components: {GanttView },
|
components: {GanttView},
|
||||||
props: {
|
props: {
|
||||||
projectLabel: {
|
projectColumn: {
|
||||||
default: []
|
default: []
|
||||||
},
|
},
|
||||||
lineTaskData: {
|
|
||||||
default: []
|
|
||||||
},
|
|
||||||
levelList: {},
|
|
||||||
},
|
},
|
||||||
|
|
||||||
data () {
|
data() {
|
||||||
return {
|
return {
|
||||||
loadFinish: false,
|
|
||||||
|
|
||||||
lists: [],
|
lists: [],
|
||||||
|
|
||||||
editColumns: [],
|
|
||||||
editData: [],
|
|
||||||
editShowInfo: false,
|
|
||||||
editLoad: 0,
|
|
||||||
|
|
||||||
filtrProjectId: 0,
|
filtrProjectId: 0,
|
||||||
|
|
||||||
|
editColumns: [
|
||||||
|
{
|
||||||
|
title: this.$L('任务名称'),
|
||||||
|
key: 'label',
|
||||||
|
minWidth: 150,
|
||||||
|
ellipsis: true,
|
||||||
|
}, {
|
||||||
|
title: this.$L('原计划时间'),
|
||||||
|
minWidth: 135,
|
||||||
|
align: 'center',
|
||||||
|
render: (h, {row}) => {
|
||||||
|
if (row.notime === true) {
|
||||||
|
return h('span', '-');
|
||||||
|
}
|
||||||
|
return h('div', {
|
||||||
|
style: {},
|
||||||
|
}, [
|
||||||
|
h('div', $A.formatDate('Y-m-d H:i', Math.round(row.baktime.start / 1000))),
|
||||||
|
h('div', $A.formatDate('Y-m-d H:i', Math.round(row.baktime.end / 1000)))
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
title: this.$L('新计划时间'),
|
||||||
|
minWidth: 135,
|
||||||
|
align: 'center',
|
||||||
|
render: (h, {row}) => {
|
||||||
|
return h('div', {
|
||||||
|
style: {},
|
||||||
|
}, [
|
||||||
|
h('div', $A.formatDate('Y-m-d H:i', Math.round(row.newTime.start / 1000))),
|
||||||
|
h('div', $A.formatDate('Y-m-d H:i', Math.round(row.newTime.end / 1000)))
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
editData: [],
|
||||||
|
editLoad: 0,
|
||||||
|
editShowInfo: false,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
this.editColumns = [
|
|
||||||
{
|
|
||||||
title: this.$L('任务名称'),
|
|
||||||
key: 'label',
|
|
||||||
minWidth: 150,
|
|
||||||
ellipsis: true,
|
|
||||||
}, {
|
|
||||||
title: this.$L('原计划时间'),
|
|
||||||
minWidth: 135,
|
|
||||||
align: 'center',
|
|
||||||
render: (h, params) => {
|
|
||||||
if (params.row.notime === true) {
|
|
||||||
return h('span', '-');
|
|
||||||
}
|
|
||||||
return h('div', {
|
|
||||||
style: {},
|
|
||||||
}, [
|
|
||||||
h('div', $A.formatDate('Y-m-d H:i', Math.round(params.row.backTime.start / 1000))),
|
|
||||||
h('div', $A.formatDate('Y-m-d H:i', Math.round(params.row.backTime.end / 1000)))
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
title: this.$L('新计划时间'),
|
|
||||||
minWidth: 135,
|
|
||||||
align: 'center',
|
|
||||||
render: (h, params) => {
|
|
||||||
return h('div', {
|
|
||||||
style: {},
|
|
||||||
}, [
|
|
||||||
h('div', $A.formatDate('Y-m-d H:i', Math.round(params.row.newTime.start / 1000))),
|
|
||||||
h('div', $A.formatDate('Y-m-d H:i', Math.round(params.row.newTime.end / 1000)))
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
];
|
|
||||||
//
|
|
||||||
this.initData();
|
this.initData();
|
||||||
this.loadFinish = true;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(['userId', 'windowWidth', 'taskPriority']),
|
...mapState(['userId', 'windowWidth', 'taskPriority']),
|
||||||
|
|
||||||
|
menuWidth() {
|
||||||
|
return this.windowWidth < 1440 ? 180 : 260;
|
||||||
|
},
|
||||||
|
|
||||||
|
dropStyle() {
|
||||||
|
return this.windowWidth < 1440 ? {left: '142px'} : {};
|
||||||
|
}
|
||||||
},
|
},
|
||||||
watch:{
|
|
||||||
projectLabel: {
|
watch: {
|
||||||
|
projectColumn: {
|
||||||
handler() {
|
handler() {
|
||||||
this.initData();
|
this.initData();
|
||||||
},
|
},
|
||||||
@ -120,13 +124,18 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
verifyLists(item){
|
initData() {
|
||||||
|
this.lists = [];
|
||||||
|
this.projectColumn && this.projectColumn.some(this.checkAdd);
|
||||||
|
},
|
||||||
|
|
||||||
|
checkAdd(item) {
|
||||||
if (this.filtrProjectId > 0) {
|
if (this.filtrProjectId > 0) {
|
||||||
if (item.id != this.filtrProjectId) {
|
if (item.id != this.filtrProjectId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
item.tasks && item.tasks.forEach((taskData) => {
|
item.tasks && item.tasks.some(taskData => {
|
||||||
let notime = !taskData.start_at || !taskData.end_at;
|
let notime = !taskData.start_at || !taskData.end_at;
|
||||||
let times = this.getTimeObj(taskData);
|
let times = this.getTimeObj(taskData);
|
||||||
let start = times.start;
|
let start = times.start;
|
||||||
@ -134,10 +143,10 @@ export default {
|
|||||||
//
|
//
|
||||||
let color = '#058ce4';
|
let color = '#058ce4';
|
||||||
if (taskData.complete_at) {
|
if (taskData.complete_at) {
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
// 等级颜色
|
// 等级颜色
|
||||||
this.levelList.some(level => {
|
this.taskPriority.some(level => {
|
||||||
if (level.priority === taskData.p_level) {
|
if (level.priority === taskData.p_level) {
|
||||||
color = level.color;
|
color = level.color;
|
||||||
return true;
|
return true;
|
||||||
@ -145,11 +154,11 @@ export default {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
//
|
//
|
||||||
let tempTime = { start, end };
|
let tempTime = {start, end};
|
||||||
let findData = this.editData.find((t) => { return t.id == taskData.id });
|
let bakTime = $A.cloneJSON(tempTime)
|
||||||
|
let findData = this.editData.find(({id}) => id == taskData.id);
|
||||||
if (findData) {
|
if (findData) {
|
||||||
findData.backTime = $A.cloneData(tempTime)
|
tempTime = $A.cloneJSON(findData.newTime);
|
||||||
tempTime = $A.cloneData(findData.newTime);
|
|
||||||
}
|
}
|
||||||
//
|
//
|
||||||
this.lists.push({
|
this.lists.push({
|
||||||
@ -159,108 +168,80 @@ export default {
|
|||||||
overdue: taskData.overdue,
|
overdue: taskData.overdue,
|
||||||
time: tempTime,
|
time: tempTime,
|
||||||
notime: notime,
|
notime: notime,
|
||||||
style: { background: color },
|
baktime: bakTime,
|
||||||
|
style: {background: color},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
initData() {
|
|
||||||
this.lists = [];
|
|
||||||
this.projectLabel && this.projectLabel.forEach((item) => {
|
|
||||||
this.verifyLists(item);
|
|
||||||
});
|
|
||||||
if (this.lists&&this.lists.length == 0) {
|
|
||||||
this.lineTaskData && this.lineTaskData.forEach((item) => {
|
|
||||||
this.verifyLists(item);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
//
|
|
||||||
if (this.lists&&this.lists.length == 0 && this.filtrProjectId == 0) {
|
|
||||||
$A.modalWarning({
|
|
||||||
content: '任务列表为空,请先添加任务。',
|
|
||||||
onOk: () => {
|
|
||||||
this.$emit('on-close');
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
updateTime(item) {
|
onChange(item) {
|
||||||
let original = this.getRawTime(item.id);
|
const {time, baktime} = item;
|
||||||
if (Math.abs(original.end - item.time.end) > 1000 || Math.abs(original.start - item.time.start) > 1000) {
|
if (Math.abs(baktime.end - time.end) > 1000 || Math.abs(baktime.start - time.start) > 1000) {
|
||||||
//修改时间(变化超过1秒钟)
|
//修改时间(变化超过1秒钟)
|
||||||
let backTime = $A.cloneData(original);
|
|
||||||
let newTime = $A.cloneData(item.time);
|
|
||||||
let findData = this.editData.find(({id}) => id == item.id);
|
let findData = this.editData.find(({id}) => id == item.id);
|
||||||
if (findData) {
|
if (findData) {
|
||||||
findData.newTime = newTime;
|
findData.newTime = time;
|
||||||
} else {
|
} else {
|
||||||
this.editData.push({
|
this.editData.push({
|
||||||
id: item.id,
|
id: item.id,
|
||||||
label: item.label,
|
label: item.label,
|
||||||
notime: item.notime,
|
notime: item.notime,
|
||||||
backTime,
|
baktime: item.baktime,
|
||||||
newTime,
|
newTime: time,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
clickItem(item) {
|
onClick(item) {
|
||||||
this.$store.dispatch("openTask", item);
|
this.$store.dispatch("openTask", item);
|
||||||
},
|
},
|
||||||
|
|
||||||
editSubmit(save) {
|
editSubmit(save) {
|
||||||
this.editData&&this.editData.forEach((item) => {
|
this.editData && this.editData.forEach(item => {
|
||||||
|
let task = this.lists.find(({id}) => id == item.id)
|
||||||
if (save) {
|
if (save) {
|
||||||
this.editLoad++;
|
this.editLoad++;
|
||||||
let timeStart = $A.formatDate('Y-m-d H:i', Math.round(item.newTime.start / 1000));
|
let timeStart = $A.formatDate('Y-m-d H:i', Math.round(item.newTime.start / 1000));
|
||||||
let timeEnd = $A.formatDate('Y-m-d H:i', Math.round(item.newTime.end / 1000));
|
let timeEnd = $A.formatDate('Y-m-d H:i', Math.round(item.newTime.end / 1000));
|
||||||
let dataJson = {
|
let dataJson = {
|
||||||
task_id: item.id,
|
task_id: item.id,
|
||||||
times:[timeStart,timeEnd],
|
times: [timeStart, timeEnd],
|
||||||
};
|
};
|
||||||
this.$store.dispatch("taskUpdate", dataJson).then(({msg}) => {
|
this.$store.dispatch("taskUpdate", dataJson).then(({msg}) => {
|
||||||
$A.messageSuccess(msg);
|
|
||||||
this.editLoad--;
|
this.editLoad--;
|
||||||
if (typeof successCallback === "function") successCallback();
|
this.editLoad === 0 && $A.messageSuccess(msg);
|
||||||
|
task && this.$set(task, 'baktime', $A.cloneJSON(task.time));
|
||||||
}).catch(({msg}) => {
|
}).catch(({msg}) => {
|
||||||
$A.modalError(msg);
|
this.editLoad--;
|
||||||
|
this.editLoad === 0 && $A.modalError(msg);
|
||||||
|
task && this.$set(task, 'time', $A.cloneJSON(task.baktime));
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
this.lists.some((task) => {
|
task && this.$set(task, 'time', $A.cloneJSON(task.baktime));
|
||||||
if (task.id == item.id) {
|
|
||||||
this.$set(task, 'time', item.backTime);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.editData = [];
|
this.editData = [];
|
||||||
},
|
},
|
||||||
|
|
||||||
getRawTime(taskId) {
|
getRawTime(taskId) {
|
||||||
let times = null;
|
let task = this.lists.find(({id}) => id == taskId)
|
||||||
this.lists.some((taskData) => {
|
return task ? this.getTimeObj(task) : null;
|
||||||
if (taskData.id == taskId) {
|
|
||||||
times = this.getTimeObj(taskData);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return times;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
getTimeObj(taskData) {
|
getTimeObj(taskData) {
|
||||||
let start = $A.Time(taskData.start_at) || $A.Time(taskData.created_at);
|
let start = $A.Time(taskData.start_at) || $A.Time(taskData.created_at);
|
||||||
let end = $A.Time(taskData.end_at) || ($A.Time(taskData.created_at) + 86400);
|
let end = $A.Time(taskData.end_at) || ($A.Time(taskData.created_at) + 86400);
|
||||||
if (end == start) {
|
if (end == start) {
|
||||||
end = Math.round(new Date($A.formatDate('Y-m-d 23:59:59', end)).getTime()/1000);
|
end = Math.round(new Date($A.formatDate('Y-m-d 23:59:59', end)).getTime() / 1000);
|
||||||
}
|
}
|
||||||
end = Math.max(end, start + 60);
|
end = Math.max(end, start + 60);
|
||||||
start*= 1000;
|
start *= 1000;
|
||||||
end*= 1000;
|
end *= 1000;
|
||||||
return {start, end};
|
return {start, end};
|
||||||
},
|
},
|
||||||
|
|
||||||
tapProject(e) {
|
onSwitchColumn(e) {
|
||||||
this.filtrProjectId = $A.runNum(e);
|
this.filtrProjectId = $A.runNum(e);
|
||||||
this.initData();
|
this.initData();
|
||||||
},
|
},
|
||||||
|
|||||||
@ -314,11 +314,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="tabTypeActive === 'gantt'" class="project-gantt">
|
<div v-else-if="tabTypeActive === 'gantt'" class="project-gantt">
|
||||||
<ProjectGantt
|
<!--甘特图-->
|
||||||
:lineData="ganttColumnList[0].tasks"
|
<ProjectGantt :projectColumn="columnList"/>
|
||||||
:projectLabel="ganttColumnList"
|
|
||||||
:lineTaskData="ganttColumnList[0].tasks"
|
|
||||||
:levelList="taskPriority"/>
|
|
||||||
</div>
|
</div>
|
||||||
<!--项目设置-->
|
<!--项目设置-->
|
||||||
<Modal
|
<Modal
|
||||||
@ -556,8 +553,6 @@ export default {
|
|||||||
'userId',
|
'userId',
|
||||||
'cacheDialogs',
|
'cacheDialogs',
|
||||||
|
|
||||||
'taskPriority',
|
|
||||||
|
|
||||||
'projectId',
|
'projectId',
|
||||||
'projectLoad',
|
'projectLoad',
|
||||||
'cacheTasks',
|
'cacheTasks',
|
||||||
@ -685,26 +680,6 @@ export default {
|
|||||||
return list;
|
return list;
|
||||||
},
|
},
|
||||||
|
|
||||||
ganttColumnList() {
|
|
||||||
const {projectId, cacheColumns} = this;
|
|
||||||
return cacheColumns.filter((row) => {
|
|
||||||
row.tasks = row.tasks.filter(task => {
|
|
||||||
return task.column_id == row.id && !task.complete_at;
|
|
||||||
}).sort((a, b) => {
|
|
||||||
if (a.sort != b.sort) {
|
|
||||||
return a.sort - b.sort;
|
|
||||||
}
|
|
||||||
return a.id - b.id;
|
|
||||||
});
|
|
||||||
return row.project_id == projectId && row.tasks.length > 0;
|
|
||||||
}).sort((a, b) => {
|
|
||||||
if (a.sort != b.sort) {
|
|
||||||
return a.sort - b.sort;
|
|
||||||
}
|
|
||||||
return a.id - b.id;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
myList() {
|
myList() {
|
||||||
const {allTask, taskCompleteTemps, sortField, sortType} = this;
|
const {allTask, taskCompleteTemps, sortField, sortType} = this;
|
||||||
let array = allTask.filter(task => this.myFilter(task));
|
let array = allTask.filter(task => this.myFilter(task));
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
@import "file-content";
|
@import "file-content";
|
||||||
@import "project-archived";
|
@import "project-archived";
|
||||||
@import "project-dialog";
|
@import "project-dialog";
|
||||||
|
@import "project-gantt";
|
||||||
@import "project-list";
|
@import "project-list";
|
||||||
@import "project-log";
|
@import "project-log";
|
||||||
@import "project-management";
|
@import "project-management";
|
||||||
@ -14,5 +15,3 @@
|
|||||||
@import "task-menu";
|
@import "task-menu";
|
||||||
@import "task-priority";
|
@import "task-priority";
|
||||||
@import "team-management";
|
@import "team-management";
|
||||||
@import "project-gantt";
|
|
||||||
@import "gantt-view";
|
|
||||||
|
|||||||
@ -1,261 +0,0 @@
|
|||||||
.wook-gantt {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: self-start;
|
|
||||||
color: #747a81;
|
|
||||||
* {
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
.gantt-left {
|
|
||||||
flex-grow:0;
|
|
||||||
flex-shrink:0;
|
|
||||||
height: 100%;
|
|
||||||
background-color: #ffffff;
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
&:after {
|
|
||||||
content: "";
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
width: 1px;
|
|
||||||
background-color: rgba(237, 241, 242, 0.75);
|
|
||||||
}
|
|
||||||
.gantt-title {
|
|
||||||
height: 76px;
|
|
||||||
flex-grow: 0;
|
|
||||||
flex-shrink: 0;
|
|
||||||
background-color: #F9FAFB;
|
|
||||||
padding-left: 12px;
|
|
||||||
overflow: hidden;
|
|
||||||
.gantt-title-text {
|
|
||||||
line-height: 100px;
|
|
||||||
max-width: 200px;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.gantt-item {
|
|
||||||
transform: translateZ(0);
|
|
||||||
max-height: 100%;
|
|
||||||
overflow: auto;
|
|
||||||
-ms-overflow-style: none;
|
|
||||||
&::-webkit-scrollbar {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
> li {
|
|
||||||
height: 40px;
|
|
||||||
border-bottom: 1px solid rgba(237, 241, 242, 0.75);
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
padding-left: 12px;
|
|
||||||
&:hover {
|
|
||||||
.item-icon {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.item-overdue {
|
|
||||||
flex-grow:0;
|
|
||||||
flex-shrink:0;
|
|
||||||
color: #ffffff;
|
|
||||||
margin-right: 4px;
|
|
||||||
background-color: #ff0000;
|
|
||||||
padding: 1px 3px;
|
|
||||||
border-radius: 3px;
|
|
||||||
font-size: 12px;
|
|
||||||
line-height: 18px;
|
|
||||||
}
|
|
||||||
.item-title {
|
|
||||||
flex: 1;
|
|
||||||
padding-right: 12px;
|
|
||||||
cursor: pointer;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
&.complete {
|
|
||||||
text-decoration: line-through;
|
|
||||||
}
|
|
||||||
&.overdue {
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.item-icon {
|
|
||||||
display: none;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 32px;
|
|
||||||
margin-right: 2px;
|
|
||||||
font-size: 16px;
|
|
||||||
color: #888888;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.gantt-right {
|
|
||||||
flex: 1;
|
|
||||||
height: 100%;
|
|
||||||
background-color: #ffffff;
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
.gantt-chart {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
transform: translateZ(0);
|
|
||||||
.gantt-month {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
z-index: 1;
|
|
||||||
height: 26px;
|
|
||||||
line-height: 20px;
|
|
||||||
font-size: 14px;
|
|
||||||
background-color: #F9FAFB;
|
|
||||||
> li {
|
|
||||||
flex-grow: 0;
|
|
||||||
flex-shrink: 0;
|
|
||||||
height: 100%;
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
&:after {
|
|
||||||
content: "";
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
width: 1px;
|
|
||||||
height: 100%;
|
|
||||||
background-color: rgba(237, 241, 242, 0.75);
|
|
||||||
}
|
|
||||||
.month-format {
|
|
||||||
overflow: hidden;
|
|
||||||
white-space: nowrap;
|
|
||||||
padding: 6px 6px 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.gantt-date {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
position: absolute;
|
|
||||||
top: 26px;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
z-index: 2;
|
|
||||||
cursor: move;
|
|
||||||
&:before {
|
|
||||||
content: "";
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
height: 50px;
|
|
||||||
background-color: #F9FAFB;
|
|
||||||
}
|
|
||||||
> li {
|
|
||||||
flex-grow: 0;
|
|
||||||
flex-shrink: 0;
|
|
||||||
height: 100%;
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
&:after {
|
|
||||||
content: "";
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
width: 1px;
|
|
||||||
height: 100%;
|
|
||||||
background-color: rgba(237, 241, 242, 0.75);
|
|
||||||
}
|
|
||||||
.date-format {
|
|
||||||
overflow: hidden;
|
|
||||||
white-space: nowrap;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
height: 44px;
|
|
||||||
.format-day {
|
|
||||||
line-height: 28px;
|
|
||||||
font-size: 18px;
|
|
||||||
}
|
|
||||||
.format-wook {
|
|
||||||
line-height: 16px;
|
|
||||||
font-weight: 300;
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.gantt-timeline {
|
|
||||||
position: absolute;
|
|
||||||
top: 76px;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
z-index: 3;
|
|
||||||
overflow-x: hidden;
|
|
||||||
overflow-y: auto;
|
|
||||||
> li {
|
|
||||||
cursor: default;
|
|
||||||
height: 40px;
|
|
||||||
border-bottom: 1px solid rgba(237, 241, 242, 0.75);
|
|
||||||
position: relative;
|
|
||||||
.timeline-item {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
touch-action: none;
|
|
||||||
pointer-events: auto;
|
|
||||||
padding: 4px;
|
|
||||||
margin-top: 4px;
|
|
||||||
background: #e74c3c;
|
|
||||||
border-radius: 18px;
|
|
||||||
color: #fff;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
will-change: contents;
|
|
||||||
height: 32px;
|
|
||||||
.timeline-title {
|
|
||||||
touch-action: none;
|
|
||||||
flex-grow: 1;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
margin-left: 4px;
|
|
||||||
margin-right: 10px;
|
|
||||||
cursor:pointer;
|
|
||||||
}
|
|
||||||
.timeline-resizer {
|
|
||||||
height: 22px;
|
|
||||||
touch-action: none;
|
|
||||||
width: 8px;
|
|
||||||
background: rgba(255,255,255,0.1);
|
|
||||||
cursor: ew-resize;
|
|
||||||
flex-shrink: 0;
|
|
||||||
will-change: visibility;
|
|
||||||
position: absolute;
|
|
||||||
top: 5px;
|
|
||||||
right: 5px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user