perf: 优化甘特图移动端交互

This commit is contained in:
kuaifan 2024-05-30 18:27:54 +08:00
parent d348871b0c
commit bc7874a3a0
4 changed files with 118 additions and 35 deletions

View File

@ -1,28 +1,37 @@
<template> <template>
<div class="common-gantt"> <div class="common-gantt">
<div class="gantt-left" :style="{width:menuWidth+'px'}"> <div class="gantt-left" :style="leftStyle">
<div class="gantt-title"> <div class="gantt-title">
<div class="gantt-title-text">{{$L('任务名称')}}</div> <div class="gantt-title-text">{{$L('任务名称')}}</div>
<div class="gantt-title-right"><slot name="titleTool"/></div>
</div> </div>
<ul ref="ganttItem" <ul ref="ganttItem"
class="gantt-item" class="gantt-item"
@scroll="itemScrollListener" @scroll="itemScrollListener"
@mouseenter="mouseType='item'"> @mouseenter="mouseType='item'">
<li v-for="(item, key) in lists" :key="key"> <li v-for="(item, key) in lists" :key="key" @click="clickItem(item, key)">
<div v-if="item.overdue" class="item-overdue" @click="clickItem(item)">{{$L('已超期')}}</div> <div v-if="item.overdue" class="item-overdue">{{$L('已超期')}}</div>
<div class="item-title" :class="{complete:item.complete, overdue:item.overdue}" @click="clickItem(item)">{{item.label}}</div> <div class="item-title" :class="{complete:item.complete, overdue:item.overdue}">{{item.label}}</div>
<Icon class="item-icon" type="ios-locate-outline" @click="scrollPosition(key)"/> <Icon class="item-icon" type="ios-locate-outline" @click.stop="scrollPosition(key)"/>
</li> </li>
</ul> </ul>
</div> </div>
<div ref="ganttRight" class="gantt-right"> <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 class="gantt-chart"> <div class="gantt-chart">
<ul class="gantt-month"> <ul class="gantt-month">
<li v-for="(item, key) in monthNum" :key="key" :style="monthStyle(key)"> <li v-for="(item, key) in monthNum" :key="key" :style="monthStyle(key)">
<div class="month-format">{{monthFormat(key)}}</div> <div class="month-format">{{monthFormat(key)}}</div>
</li> </li>
</ul> </ul>
<ul class="gantt-date" @mousedown="dateMouseDown"> <ul class="gantt-date"
@touchstart="dateTouchstart"
@touchmove="dateTouchmove"
@touchend="dateTouchend"
@mousedown="dateMouseDown">
<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>
@ -77,10 +86,13 @@ export default {
mouseItem: null, mouseItem: null,
mouseBak: {}, mouseBak: {},
dateMove: null dateMove: null,
maximize: false,
} }
}, },
mounted() { mounted() {
this.maximize = this.windowPortrait;
this.dateWidth = this.itemWidth; this.dateWidth = this.itemWidth;
this.$refs.ganttRight.addEventListener('mousewheel', this.handleScroll, false); this.$refs.ganttRight.addEventListener('mousewheel', this.handleScroll, false);
document.addEventListener('mousemove', this.itemMouseMove); document.addEventListener('mousemove', this.itemMouseMove);
@ -97,9 +109,21 @@ export default {
watch: { watch: {
itemWidth(val) { itemWidth(val) {
this.dateWidth = val; this.dateWidth = val;
},
maximize() {
this.$nextTick(() => {
this.handleResize();
})
} }
}, },
computed: { computed: {
leftStyle({menuWidth, maximize}) {
const style = {width: menuWidth + 'px'}
if (maximize) {
style.display = 'none';
}
return style
},
monthNum() { monthNum() {
const {ganttWidth, dateWidth} = this; const {ganttWidth, dateWidth} = this;
return Math.floor(ganttWidth / dateWidth / 30) + 2 return Math.floor(ganttWidth / dateWidth / 30) + 2
@ -271,6 +295,31 @@ export default {
handleResize() { handleResize() {
this.ganttWidth = this.$refs.ganttTimeline.clientWidth; this.ganttWidth = this.$refs.ganttTimeline.clientWidth;
}, },
dateTouchstart(e) {
if (this.windowPortrait) {
this.maximize = true
}
this.mouseItem = null;
this.dateMove = {
clientX: e.touches[0].clientX
};
},
dateTouchmove(e) {
if (this.mouseItem != null) {
return
}
if (this.dateMove != null) {
let moveX = (this.dateMove.clientX - e.touches[0].clientX) * 5;
this.dateMove.clientX = e.touches[0].clientX;
this.mouseWidth+= moveX;
this.mouseScaleWidth+= moveX * (100 / this.dateWidth);
}
},
dateTouchend() {
if (this.dateMove != null) {
this.dateMove = null;
}
},
dateMouseDown(e) { dateMouseDown(e) {
e.preventDefault(); e.preventDefault();
this.mouseItem = null; this.mouseItem = null;
@ -308,7 +357,9 @@ export default {
} }
} }
this.$set(this.mouseItem, this.mouseBak.type, diff); this.$set(this.mouseItem, this.mouseBak.type, diff);
} else if (this.dateMove != null) { return;
}
if (this.dateMove != null) {
e.preventDefault(); e.preventDefault();
let moveX = (this.dateMove.clientX - e.clientX) * 5; let moveX = (this.dateMove.clientX - e.clientX) * 5;
this.dateMove.clientX = e.clientX; this.dateMove.clientX = e.clientX;
@ -344,7 +395,9 @@ export default {
this.clickItem(this.mouseItem); this.clickItem(this.mouseItem);
} }
this.mouseItem = null; this.mouseItem = null;
} else if (this.dateMove != null) { return
}
if (this.dateMove != null) {
this.dateMove = null; this.dateMove = null;
} }
}, },
@ -359,7 +412,11 @@ export default {
this.mouseWidth+= moveWidth; this.mouseWidth+= moveWidth;
this.mouseScaleWidth+= moveWidth * (100 / this.dateWidth); this.mouseScaleWidth+= moveWidth * (100 / this.dateWidth);
}, },
clickItem(item) { clickItem(item, key = undefined) {
if (key !== undefined && this.windowPortrait) {
this.scrollPosition(key)
return
}
this.$emit("on-click", item) this.$emit("on-click", item)
} }
} }

View File

@ -5,24 +5,27 @@
:menuWidth="menuWidth" :menuWidth="menuWidth"
:itemWidth="80" :itemWidth="80"
@on-change="onChange" @on-change="onChange"
@on-click="onClick"/> @on-click="onClick">
<Dropdown class="project-gstc-dropdown-filtr" :style="dropStyle" trigger="click" @on-click="onSwitchColumn"> <template #titleTool>
<Icon class="project-gstc-dropdown-icon" :class="{filtr:filtrProjectId > 0}" type="md-funnel" /> <Dropdown class="project-gstc-dropdown-filtr" trigger="click" @on-click="onSwitchColumn">
<DropdownMenu slot="list"> <Icon class="project-gstc-dropdown-icon" :class="{filtr:filtrProjectId > 0}" type="md-funnel" />
<DropdownItem :name="0" :class="{'dropdown-active':filtrProjectId == 0}">{{ $L('全部') }}</DropdownItem> <DropdownMenu slot="list">
<DropdownItem <DropdownItem :name="0" :class="{'dropdown-active':filtrProjectId == 0}">{{ $L('全部') }}</DropdownItem>
v-for="(item, index) in projectColumn" <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">({{ filtrLength(item.tasks) }})</span> {{ item.name }}
</DropdownItem> <span v-if="item.tasks">({{ filtrLength(item.tasks) }})</span>
</DropdownMenu> </DropdownItem>
</Dropdown> </DropdownMenu>
</Dropdown>
</template>
</GanttView>
<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 max-height="600" :columns="editColumns" :data="editData"></Table>
<div class="project-gstc-edit-btns"> <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="text" @click="editSubmit(false)">{{$L('取消')}}</Button>
<Button :loading="editLoad > 0" size="small" type="primary" @click="editSubmit(true)">{{$L('保存')}}</Button> <Button :loading="editLoad > 0" size="small" type="primary" @click="editSubmit(true)">{{$L('保存')}}</Button>
@ -114,10 +117,6 @@ export default {
return this.windowWidth < 1440 ? 180 : 260; return this.windowWidth < 1440 ? 180 : 260;
}, },
dropStyle() {
return this.windowWidth < 1440 ? {left: '142px'} : {};
},
completedTask() { completedTask() {
return this.projectData.cacheParameter.completedTask; return this.projectData.cacheParameter.completedTask;
} }

View File

@ -33,21 +33,28 @@
} }
.gantt-title { .gantt-title {
display: flex;
align-items: center;
height: 76px; height: 76px;
flex-grow: 0; flex-grow: 0;
flex-shrink: 0; flex-shrink: 0;
background-color: #F9FAFB; background-color: #F9FAFB;
padding-left: 12px; padding-left: 12px;
padding-top: 26px;
overflow: hidden; overflow: hidden;
.gantt-title-text { .gantt-title-text {
line-height: 100px; flex: 1;
max-width: 200px; line-height: 22px;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
font-weight: 600; font-weight: 600;
} }
.gantt-title-right {
flex-shrink: 0;
}
} }
.gantt-item { .gantt-item {
@ -122,6 +129,28 @@
position: relative; position: relative;
overflow: hidden; overflow: hidden;
.gantt-size {
position: absolute;
top: 76px;
left: 0;
z-index: 2;
overflow: hidden;
cursor: pointer;
user-select: none;
padding: 8px 14px;
background: rgba(255, 255, 255, 0.502);
transition: all 250ms;
box-shadow: 0 0 0 rgba(0, 0, 0, 0);
border-bottom-right-radius: 9px;
&:hover {
box-shadow: 0 0 6px rgba(0, 0, 0, 0.15);
background: #fff;
}
> i {
font-size: 20px;
}
}
.gantt-chart { .gantt-chart {
position: absolute; position: absolute;
top: 0; top: 0;

View File

@ -11,9 +11,7 @@
overflow: hidden; overflow: hidden;
.project-gstc-dropdown-filtr { .project-gstc-dropdown-filtr {
position: absolute; padding: 0 16px;
top: 38px;
left: 222px;
.project-gstc-dropdown-icon { .project-gstc-dropdown-icon {
cursor: pointer; cursor: pointer;