feat: 项目任务新增一个甘特图展示选项

This commit is contained in:
韦荣超 2022-03-11 19:04:51 +08:00
parent 3006dc6584
commit 15ecd8d592
8 changed files with 1073 additions and 3 deletions

View File

@ -385,7 +385,25 @@
*/ */
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;
}
},
}); });
/** /**

View File

@ -0,0 +1,359 @@
<template>
<div class="wook-gantt">
<div class="gantt-left" :style="{width:menuWidth+'px'}">
<div class="gantt-title">
<div class="gantt-title-text">{{$L('任务名称')}}</div>
</div>
<ul ref="ganttItem"
class="gantt-item"
@scroll="itemScrollListener"
@mouseenter="mouseType='item'">
<li v-for="(item, key) in lists" :key="key">
<div v-if="item.overdue" class="item-overdue" @click="clickItem(item)">{{$L('已超期')}}</div>
<div class="item-title" :class="{complete:item.complete, overdue:item.overdue}" @click="clickItem(item)">{{item.label}}</div>
<Icon class="item-icon" type="ios-locate-outline" @click="scrollPosition(key)"/>
</li>
</ul>
</div>
<div ref="ganttRight" class="gantt-right">
<div class="gantt-chart">
<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-wook">{{dateFormat(key, 'wook')}}</div>
</div>
</li>
</ul>
<ul ref="ganttTimeline"
class="gantt-timeline"
@scroll="timelineScrollListener"
@mouseenter="mouseType='timeline'">
<li v-for="(item, key) in lists" :key="key">
<div
class="timeline-item"
:style="itemStyle(item)"
@mousedown="itemMouseDown($event, item)">
<div class="timeline-title">{{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
}
},
mounted() {
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;
}
},
computed: {
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;
let date = new Date();
//00:00:00
let nowDay = new Date(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0);
//
let curDay = new Date(nowDay.getTime() + mouseDay * 86400000);
//
let lastDay = new Date(curDay.getFullYear(), curDay.getMonth() + 1, 0, 23, 59, 59);
//
let diffDay = (lastDay - curDay) / 1000 / 60 / 60 / 24;
//
let width = dateWidth * diffDay;
if (index > 0) {
lastDay = new Date(curDay.getFullYear(), curDay.getMonth() + 1 + index, 0);
width = lastDay.getDate() * dateWidth;
}
return {
width: width + 'px',
}
}
},
monthFormat() {
const {mouseWidth, dateWidth} = this;
return function(index) {
let mouseDay = mouseWidth == 0 ? 0 : mouseWidth / dateWidth;
let date = new Date();
//00:00:00
let nowDay = new Date(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0);
//
let curDay = new Date(nowDay.getTime() + mouseDay * 86400000);
//
if (index > 0) {
curDay = new Date(curDay.getFullYear(), curDay.getMonth() + 1 + index, 0);
}
return $A.formatDate("Y-m", curDay)
}
},
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 = new Date(new Date().getTime() + j * 86400000);
if ([0, 6].indexOf(date.getDay()) !== -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 = new Date(new Date().getTime() + j * 86400000)
if (type == 'day') {
return date.getDate();
} else if (type == 'wook') {
return this.$L(`星期${'日一二三四五六'.charAt(date.getDay())}`);
} else {
return date;
}
}
},
itemStyle() {
const {mouseWidth, dateWidth, ganttWidth} = this;
return function(item) {
const {start, end} = item.time;
const {style, moveX, moveW} = item;
let date = new Date();
//00:00:00
let nowTime = new Date(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0).getTime();
//
let diffStartDay = (start - nowTime) / 1000 / 60 / 60 / 24;
let diffEndDay = (end - nowTime) / 1000 / 60 / 60 / 24;
//
let left = dateWidth * diffStartDay + (mouseWidth * -1);
let width = 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;
},
dateMouseDown(e) {
e.preventDefault();
this.mouseItem = null;
this.dateMove = {
clientX: e.clientX
};
},
itemMouseDown(e, item) {
e.preventDefault();
let type = 'moveX';
if (e.target.className == 'timeline-resizer') {
type = 'moveW';
}
if (typeof item[type] !== "number") {
this.$set(item, type, 0);
}
this.mouseBak = {
type: type,
clientX: e.clientX,
value: item[type],
};
this.mouseItem = item;
this.dateMove = null;
},
itemMouseMove(e) {
if (this.mouseItem != null) {
e.preventDefault();
var diff = e.clientX - this.mouseBak.clientX;
this.$set(this.mouseItem, this.mouseBak.type, this.mouseBak.value + diff);
} else if (this.dateMove != null) {
e.preventDefault();
let moveX = (this.dateMove.clientX - e.clientX) * 5;
this.dateMove.clientX = e.clientX;
this.mouseWidth+= moveX;
this.mouseScaleWidth+= moveX * (100 / this.dateWidth);
}
},
itemMouseUp(e) {
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 (e.target.className == 'timeline-title') {
this.clickItem(this.mouseItem);
}
this.mouseItem = null;
} else if (this.dateMove != null) {
this.dateMove = null;
}
},
scrollPosition(pos) {
let date = new Date();
//00:00:00
let nowDay = new Date(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0);
//
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) {
this.$emit("on-click", item)
}
}
}
</script>

View File

@ -0,0 +1,270 @@
<template>
<div class="project-gstc-gantt">
<GanttView :lists="lists" :menuWidth="windowWidth ? 180 : 260" :itemWidth="80" @on-change="updateTime" @on-click="clickItem"/>
<Dropdown class="project-gstc-dropdown-filtr" :style="windowWidth?{left:'142px'}:{}" @on-click="tapProject">
<Icon class="project-gstc-dropdown-icon" :class="{filtr:filtrProjectId>0}" type="md-funnel" />
<DropdownMenu slot="list">
<DropdownItem :name="0" :class="{'dropdown-active':filtrProjectId==0}">{{$L('全部')}}</DropdownItem>
<DropdownItem v-for="(item, index) in projectLabel"
:key="index"
:name="item.id"
:class="{'dropdown-active':filtrProjectId==item.id}">
{{item.name}}
<span v-if="item.tasks">({{item.tasks.length}})</span>
</DropdownItem>
</DropdownMenu>
</Dropdown>
<div class="project-gstc-close" @click="$emit('on-close')"><Icon type="md-close" /></div>
<div class="project-gstc-edit" :class="{info:editShowInfo, visible:editData&&editData.length > 0}">
<div class="project-gstc-edit-info">
<Table size="small" max-height="600" :columns="editColumns" :data="editData"></Table>
<div class="project-gstc-edit-btns">
<Button :loading="editLoad > 0" size="small" type="text" @click="editSubmit(false)">{{$L('取消')}}</Button>
<Button :loading="editLoad > 0" size="small" type="primary" @click="editSubmit(true)">{{$L('保存')}}</Button>
<Icon type="md-arrow-dropright" class="zoom" @click="editShowInfo=false"/>
</div>
</div>
<div class="project-gstc-edit-small">
<div class="project-gstc-edit-text" @click="editShowInfo=true">{{$L('未保存计划时间')}}: <span v-if="editData">{{editData.length}}</span></div>
<Button :loading="editLoad > 0" size="small" type="text" @click="editSubmit(false)">{{$L('取消')}}</Button>
<Button :loading="editLoad > 0" size="small" type="primary" @click="editSubmit(true)">{{$L('保存')}}</Button>
</div>
</div>
</div>
</template>
<script>
import GanttView from "./GanttView";
import {mapState} from "vuex";
/**
* 甘特图
*/
export default {
name: 'ProjectGantt',
components: {GanttView },
props: {
projectLabel: {
default: []
},
lineTaskData: {
default: []
},
levelList: {},
},
data () {
return {
loadFinish: false,
lists: [],
editColumns: [],
editData: [],
editShowInfo: false,
editLoad: 0,
filtrProjectId: 0,
}
},
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.loadFinish = true;
},
computed: {
...mapState(['userId', 'windowWidth', 'taskPriority']),
},
watch:{
projectLabel: {
handler() {
this.initData();
},
deep: true,
}
},
methods: {
verifyLists(item){
if (this.filtrProjectId > 0) {
if (item.id != this.filtrProjectId) {
return;
}
}
item.tasks && item.tasks.forEach((taskData) => {
let notime = !taskData.start_at || !taskData.end_at;
let times = this.getTimeObj(taskData);
let start = times.start;
let end = times.end;
//
let color = '#058ce4';
if (taskData.complete_at) {
color = '#c1c1c1';
} else {
//
this.levelList.some(level => {
if (level.level === taskData.level) {
color = level.color;
return true;
}
});
}
//
let tempTime = { start, end };
let findData = this.editData.find((t) => { return t.id == taskData.id });
if (findData) {
findData.backTime = $A.cloneData(tempTime)
tempTime = $A.cloneData(findData.newTime);
}
//
this.lists.push({
id: taskData.id,
label: taskData.name,
complete: taskData.complete_at,
overdue: taskData.overdue,
time: tempTime,
notime: notime,
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) {
let original = this.getRawTime(item.id);
if (Math.abs(original.end - item.time.end) > 1000 || Math.abs(original.start - item.time.start) > 1000) {
//1)
let backTime = $A.cloneData(original);
let newTime = $A.cloneData(item.time);
let findData = this.editData.find(({id}) => id == item.id);
if (findData) {
findData.newTime = newTime;
} else {
this.editData.push({
id: item.id,
label: item.label,
notime: item.notime,
backTime,
newTime,
})
}
}
},
clickItem(item) {
this.$store.dispatch("openTask", item);
},
editSubmit(save) {
this.editData&&this.editData.forEach((item) => {
if (save) {
this.editLoad++;
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 dataJson = {
task_id: item.id,
times:[timeStart,timeEnd],
};
this.$store.dispatch("taskUpdate", dataJson).then(({msg}) => {
$A.messageSuccess(msg);
this.editLoad--;
if (typeof successCallback === "function") successCallback();
}).catch(({msg}) => {
$A.modalError(msg);
})
} else {
this.lists.some((task) => {
if (task.id == item.id) {
this.$set(task, 'time', item.backTime);
return true;
}
})
}
});
this.editData = [];
},
getRawTime(taskId) {
let times = null;
this.lists.some((taskData) => {
if (taskData.id == taskId) {
times = this.getTimeObj(taskData);
}
});
return times;
},
getTimeObj(taskData) {
let start = $A.Time(taskData.start_at) || $A.Time(taskData.start_at);
let end = $A.Time(taskData.start_at) || ($A.Time(taskData.created_at) + 86400);
if (end == start) {
end = Math.round(new Date($A.formatDate('Y-m-d 23:59:59', end)).getTime()/1000);
}
end = Math.max(end, start + 60);
start*= 1000;
end*= 1000;
return {start, end};
},
tapProject(e) {
this.filtrProjectId = $A.runNum(e);
this.initData();
},
}
}
</script>

View File

@ -85,6 +85,12 @@
<div><i class="taskfont">&#xe60c;</i></div> <div><i class="taskfont">&#xe60c;</i></div>
<div><i class="taskfont">&#xe66a;</i></div> <div><i class="taskfont">&#xe66a;</i></div>
</div> </div>
<div class="project-gantt-item"
:class="{active:projectGanttShow}"
@click="projectGanttShow=!projectGanttShow">
<Icon type="ios-paper-outline" size="26" />
{{ $L('甘特图') }}
</div>
</div> </div>
</div> </div>
</div> </div>
@ -311,7 +317,15 @@
<TaskRow v-if="projectParameter('showCompleted')" :list="completedList" open-key="completed" @on-priority="addTaskOpen" showCompleteAt/> <TaskRow v-if="projectParameter('showCompleted')" :list="completedList" open-key="completed" @on-priority="addTaskOpen" showCompleteAt/>
</div> </div>
</div> </div>
<div class="project-table overlay-y" v-if="projectGanttShow">
<!-- 甘特图 -->
<ProjectGantt v-if="projectGanttShow"
@on-close="projectGanttShow=false"
:lineData="columnList[0].tasks"
:projectLabel="columnList"
:lineTaskData="columnList[0].tasks"
:levelList="taskPriority"></ProjectGantt>
</div>
<!--项目设置--> <!--项目设置-->
<Modal <Modal
v-model="settingShow" v-model="settingShow"
@ -463,6 +477,7 @@ import DrawerOverlay from "../../../components/DrawerOverlay";
import ProjectWorkflow from "./ProjectWorkflow"; import ProjectWorkflow from "./ProjectWorkflow";
import TaskMenu from "./TaskMenu"; import TaskMenu from "./TaskMenu";
import TaskDeleted from "./TaskDeleted"; import TaskDeleted from "./TaskDeleted";
import ProjectGantt from "./ProjectGantt";
export default { export default {
name: "ProjectList", name: "ProjectList",
@ -470,7 +485,7 @@ export default {
TaskMenu, TaskMenu,
ProjectWorkflow, ProjectWorkflow,
DrawerOverlay, DrawerOverlay,
ProjectLog, TaskArchived, TaskRow, Draggable, TaskAddSimple, UserInput, TaskAdd, TaskPriority, TaskDeleted }, ProjectLog, TaskArchived, TaskRow, Draggable, TaskAddSimple, UserInput, TaskAdd, TaskPriority, TaskDeleted, ProjectGantt},
data() { data() {
return { return {
loading: false, loading: false,
@ -517,6 +532,7 @@ export default {
flowInfo: {}, flowInfo: {},
flowList: [], flowList: [],
projectGanttShow: false,
} }
}, },

View File

@ -14,3 +14,5 @@
@import "task-menu"; @import "task-menu";
@import "task-priority"; @import "task-priority";
@import "team-management"; @import "team-management";
@import "project-gantt";
@import "gantt-view";

View File

@ -0,0 +1,259 @@
.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: 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-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;
}
.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;
}
}
}
}
}
}
}

View File

@ -0,0 +1,135 @@
.project-gstc-gantt {
position: absolute;
top: 15px;
left: 15px;
right: 15px;
bottom: 15px;
z-index: 1;
transform: translateZ(0);
background-color: #fdfdfd;
border-radius: 3px;
overflow: hidden;
.project-gstc-dropdown-filtr {
position: absolute;
top: 38px;
left: 222px;
.project-gstc-dropdown-icon {
cursor: pointer;
color: #999;
font-size: 20px;
&.filtr {
color: #058ce4;
}
}
}
.project-gstc-close {
position: absolute;
top: 8px;
left: 12px;
cursor: pointer;
&:hover {
i {
transform: scale(1) rotate(45deg);
}
}
i {
color: #666666;
font-size: 28px;
transform: scale(0.92);
transition: all .2s;
}
}
.project-gstc-edit {
position: absolute;
bottom: 6px;
right: 6px;
background: #ffffff;
border-radius: 4px;
opacity: 0;
transform: translate(120%, 0);
transition: all 0.2s;
&.visible {
opacity: 1;
transform: translate(0, 0);
}
&.info {
.project-gstc-edit-info {
display: block;
}
.project-gstc-edit-small {
display: none;
}
}
.project-gstc-edit-info {
display: none;
border: 1px solid #e4e4e4;
background: #ffffff;
padding: 6px;
border-radius: 4px;
width: 500px;
.project-gstc-edit-btns {
margin: 12px 6px 4px;
display: flex;
align-items: center;
justify-content: flex-end;
.ivu-btn {
margin-right: 8px;
font-size: 13px;
}
.zoom {
font-size: 20px;
color: #444444;
cursor: pointer;
&:hover {
color: #57a3f3;
}
}
}
}
.project-gstc-edit-small {
border: 1px solid #e4e4e4;
background: #ffffff;
padding: 6px 12px;
display: flex;
align-items: center;
.project-gstc-edit-text {
cursor: pointer;
text-decoration: underline;
color: #444444;
margin-right: 8px;
&:hover {
color: #57a3f3;
}
}
.ivu-btn {
margin-left: 4px;
font-size: 13px;
}
}
}
.ivu-dropdown-item {
&.dropdown-active {
color: #058ce4;
}
}
}

View File

@ -240,6 +240,17 @@
} }
} }
} }
.project-gantt-item{
padding-left:5px;
&.active{
color: $primary-color;
}
.force-right{
&.act{
color: #2A53FF;
}
}
}
} }
} }
} }