perf: 优化项目面板

This commit is contained in:
kuaifan 2024-12-15 22:27:05 +08:00
parent a1ce6e6928
commit 783c21ad18
2 changed files with 211 additions and 142 deletions

View File

@ -81,7 +81,7 @@
</div>
<div class="project-switch">
<div v-if="completedCount > 0" class="project-checkbox">
<Checkbox :value="projectData.cacheParameter.completedTask" @on-change="toggleCompleted">{{$L('显示已完成')}}</Checkbox>
<Checkbox :value="projectData.cacheParameter.completedTask" @on-change="toggleParameter('completedTask')">{{$L('显示已完成')}}</Checkbox>
</div>
<div class="project-select">
<Cascader ref="flow" :data="flowData" @on-change="flowChange" transfer-class-name="project-panel-flow-cascader" transfer>
@ -249,7 +249,12 @@
</li>
</Draggable>
</div>
<Scrollbar v-else-if="tabTypeActive === 'table'" class="project-table" enable-x>
<Scrollbar
v-else-if="tabTypeActive === 'table'"
ref="projectTableScroll"
class="project-table"
enable-x
@on-scroll="handleTaskScroll">
<div class="project-table-head">
<Row class="task-row">
<Col span="12">
@ -294,7 +299,13 @@
<Col span="3"></Col>
<Col span="3"></Col>
</Row>
<TaskRow v-if="projectData.cacheParameter.showMy" :list="transforTasks(myList)" open-key="my" @on-priority="addTaskOpen" fast-add-task/>
<TaskRow
v-if="projectData.cacheParameter.showMy"
:list="transforTasks(myList)"
:task-visibilitys="taskRowVisibilitys"
open-key="my"
@on-priority="addTaskOpen"
fast-add-task/>
</div>
<!--协助的任务-->
<div v-if="helpList.length" :class="['project-table-body', !projectData.cacheParameter.showHelp ? 'project-table-hide' : '']">
@ -309,7 +320,12 @@
<Col span="3"></Col>
<Col span="3"></Col>
</Row>
<TaskRow v-if="projectData.cacheParameter.showHelp" :list="helpList" open-key="help" @on-priority="addTaskOpen"/>
<TaskRow
v-if="projectData.cacheParameter.showHelp"
:list="helpList"
:task-visibilitys="taskRowVisibilitys"
open-key="help"
@on-priority="addTaskOpen"/>
</div>
<!--未完成任务-->
<div v-if="projectData.task_num > 0" :class="['project-table-body', !projectData.cacheParameter.showUndone ? 'project-table-hide' : '']">
@ -324,7 +340,12 @@
<Col span="3"></Col>
<Col span="3"></Col>
</Row>
<TaskRow v-if="projectData.cacheParameter.showUndone" :list="unList" open-key="undone" @on-priority="addTaskOpen"/>
<TaskRow
v-if="projectData.cacheParameter.showUndone"
:list="unList"
:task-visibilitys="taskRowVisibilitys"
open-key="undone"
@on-priority="addTaskOpen"/>
</div>
<!--已完成任务-->
<div v-if="projectData.task_num > 0" :class="['project-table-body', !projectData.cacheParameter.showCompleted ? 'project-table-hide' : '']">
@ -341,7 +362,13 @@
<div class="ellipsis">{{projectData.task_num > 0 && projectData.cacheParameter.showCompleted ? $L('完成时间') : ''}}</div>
</Col>
</Row>
<TaskRow v-if="projectData.cacheParameter.showCompleted" :list="completedList" open-key="completed" @on-priority="addTaskOpen" showCompleteAt/>
<TaskRow
v-if="projectData.cacheParameter.showCompleted"
:list="completedList"
:task-visibilitys="taskRowVisibilitys"
open-key="completed"
@on-priority="addTaskOpen"
showCompleteAt/>
</div>
</Scrollbar>
<div v-else-if="tabTypeActive === 'gantt'" class="project-gantt">
@ -624,8 +651,9 @@ export default {
flowInfo: {},
flowList: [],
columnVisibility: {},
taskVisibility: {},
columnVisibilitys: {},
taskVisibilitys: {},
taskRowVisibilitys: {},
}
},
@ -971,6 +999,21 @@ export default {
},
watch: {
projectId: {
handler(id) {
if (id > 0) {
this.getFlowData();
this.handleColumnDebounce();
}
},
immediate: true,
},
'allTask.length'() {
this.handleColumnDebounce();
},
windowWidth() {
this.handleColumnDebounce();
},
projectData() {
this.sortData = this.getSort();
},
@ -984,24 +1027,6 @@ export default {
this.loading = false;
}
},
projectId: {
handler(val) {
if (val > 0) {
this.getFlowData();
this.handleColumnDebounce();
}
},
immediate: true,
},
'allTask.length'() {
this.handleColumnDebounce();
},
'projectData.cacheParameter.completedTask'() {
this.handleColumnDebounce();
},
windowWidth() {
this.handleColumnDebounce();
}
},
methods: {
@ -1507,8 +1532,9 @@ export default {
});
},
flowChange(value, data) {
flowChange(_, data) {
this.flowInfo = data.pop() || {};
this.handleColumnDebounce();
},
inviteCopy() {
@ -1525,10 +1551,6 @@ export default {
});
},
toggleCompleted() {
this.toggleParameter('completedTask');
},
workflowBeforeClose() {
return new Promise(resolve => {
if (!this.$refs.workflow) {
@ -1629,15 +1651,16 @@ export default {
},
toggleParameter(data) {
if (data === 'completedTask') {
this.$store.dispatch("forgetTaskCompleteTemp", true);
} else if (data === 'chat') {
if (data === 'chat') {
if (this.windowPortrait) {
this.$store.dispatch('openDialog', this.projectData.dialog_id)
return;
}
} else if (data === 'completedTask') {
this.$store.dispatch("forgetTaskCompleteTemp", true);
}
this.$store.dispatch('toggleProjectParameter', data);
this.handleColumnDebounce();
},
onBack() {
@ -1654,7 +1677,7 @@ export default {
},
taskItemVisible({id, column_id}) {
return this.columnVisibility[column_id] && this.taskVisibility[id]?.visible
return this.columnVisibilitys[column_id] && this.taskVisibilitys[id]?.visible
},
taskItemStyle({id, column_id, color}) {
@ -1663,7 +1686,7 @@ export default {
style.backgroundColor = color;
}
if (!this.taskItemVisible({id, column_id})) {
style.height = (this.taskVisibility[id]?.height || 146) + 'px';
style.height = (this.taskVisibilitys[id]?.height || 146) + 'px';
}
return style;
},
@ -1671,9 +1694,16 @@ export default {
handleColumnDebounce() {
if (!this.columnDebounceInvoke) {
this.columnDebounceInvoke = debounce(_ => {
if (this.tabTypeActive === 'column') {
this.$nextTick(this.handleColumnScroll)
}
this.$nextTick(_ => {
switch (this.tabTypeActive) {
case 'column':
this.handleColumnScroll()
break;
case 'table':
this.handleTaskScroll({target: this.$refs.projectTableScroll?.$el})
break;
}
})
}, 10);
}
this.columnDebounceInvoke();
@ -1690,8 +1720,8 @@ export default {
if (!columnContainer) {
return
}
const columnId = parseInt(columnContainer.getAttribute('data-id'))
if (!columnId) {
const dataId = columnContainer.getAttribute('data-id')
if (!dataId) {
return
}
const mainContainer = this.$refs.projectColumn;
@ -1711,11 +1741,9 @@ export default {
columnRect.bottom > mainRect.top
)
if (visible) {
this.handleTaskScroll({
target: columnContainer.querySelector('.task-scrollbar')
})
this.handleTaskScroll({target: columnContainer.querySelector('.task-scrollbar')})
}
this.$set(this.columnVisibility, columnId, visible)
this.$set(this.columnVisibilitys, dataId, visible)
},
async handleTaskScroll({target}) {
@ -1726,14 +1754,25 @@ export default {
if (!targetItem.length) {
return
}
let visibleType = null;
switch (this.tabTypeActive) {
case 'column':
visibleType = 'taskVisibilitys'
break;
case 'table':
visibleType = 'taskRowVisibilitys'
break;
default:
return
}
const targetRect = target.getBoundingClientRect();
for (const item of targetItem) {
const taskId = parseInt(item.getAttribute('data-id'));
if (!taskId) {
const dataId = item.getAttribute('data-id');
if (!dataId) {
continue;
}
const taskRect = item.getBoundingClientRect();
const originalVisible = this.taskVisibility[taskId]?.visible || false;
const originalVisible = this[visibleType][dataId]?.visible || false;
const currentVisible = (
taskRect.top >= targetRect.top - taskRect.height &&
taskRect.bottom <= targetRect.bottom + taskRect.height
@ -1741,8 +1780,8 @@ export default {
if (currentVisible === originalVisible) {
continue;
}
const firstVisible = this.taskVisibility[taskId] === undefined && currentVisible;
this.$set(this.taskVisibility, taskId, {
const firstVisible = this[visibleType][dataId] === undefined && currentVisible;
this.$set(this[visibleType], dataId, {
visible: currentVisible,
height: taskRect.height
});
@ -1750,7 +1789,7 @@ export default {
await this.$nextTick();
}
}
}
},
}
}
</script>

View File

@ -1,98 +1,107 @@
<template>
<div class="task-rows">
<div v-for="(item, key) in list" :key="key" :ref="`task_${item.id}`">
<Row class="task-row" :style="item.color ? {backgroundColor: item.color, borderBottomColor: item.color} : {}">
<em v-if="item.p_name" class="priority-color" :style="{backgroundColor:item.p_color}"></em>
<Col span="12" :class="['row-name', item.complete_at ? 'complete' : '']">
<Icon
v-if="(item.sub_num > 0 && item.sub_top !== true) || (item.parent_id === 0 && fastAddTask)"
:class="['sub-icon', taskOpen[item.id] ? 'active' : '']"
type="ios-arrow-forward"
@click="getSublist(item)"/>
<TaskMenu :ref="`taskMenu_${item.id}`" :task="item"/>
<div class="item-title" @click="openTask(item)">
<!--工作流状态-->
<span v-if="item.flow_item_name" :class="item.flow_item_status" @click.stop="openMenu($event, item)">{{item.flow_item_name}}</span>
<!--是否子任务-->
<span v-if="item.sub_top === true">{{$L('子任务')}}</span>
<!--有多少个子任务-->
<span v-if="item.sub_my && item.sub_my.length > 0">+{{item.sub_my.length}}</span>
<!--任务描述-->
{{item.name}}
</div>
<div class="item-icons" @click="openTask(item)">
<div v-if="item.desc" class="item-icon">
<i class="taskfont">&#xe71a;</i>
<div
v-for="(item, key) in list"
:key="key"
:ref="`task_${item.id}`"
:data-id="`${openKey}_${item.id}`"
class="task-item">
<Row
class="task-row"
:style="taskItemStyle(item)">
<template v-if="taskItemVisible(`${openKey}_${item.id}`)">
<em v-if="item.p_name" class="priority-color" :style="{backgroundColor:item.p_color}"></em>
<Col span="12" :class="['row-name', item.complete_at ? 'complete' : '']">
<Icon
v-if="(item.sub_num > 0 && item.sub_top !== true) || (item.parent_id === 0 && fastAddTask)"
:class="['sub-icon', taskOpen[item.id] ? 'active' : '']"
type="ios-arrow-forward"
@click="getSublist(item)"/>
<TaskMenu :ref="`taskMenu_${item.id}`" :task="item"/>
<div class="item-title" @click="openTask(item)">
<!--工作流状态-->
<span v-if="item.flow_item_name" :class="item.flow_item_status" @click.stop="openMenu($event, item)">{{item.flow_item_name}}</span>
<!--是否子任务-->
<span v-if="item.sub_top === true">{{$L('子任务')}}</span>
<!--有多少个子任务-->
<span v-if="item.sub_my && item.sub_my.length > 0">+{{item.sub_my.length}}</span>
<!--任务描述-->
{{item.name}}
</div>
<div v-if="item.file_num > 0" class="item-icon">
<i class="taskfont">&#xe71c;</i>
<em>{{item.file_num}}</em>
<div class="item-icons" @click="openTask(item)">
<div v-if="item.desc" class="item-icon">
<i class="taskfont">&#xe71a;</i>
</div>
<div v-if="item.file_num > 0" class="item-icon">
<i class="taskfont">&#xe71c;</i>
<em>{{item.file_num}}</em>
</div>
<div v-if="item.msg_num > 0" class="item-icon">
<i class="taskfont">&#xe71e;</i>
<em>{{item.msg_num}}</em>
</div>
<div v-if="item.sub_num > 0" class="item-icon" @click.stop="getSublist(item)">
<i class="taskfont">&#xe71f;</i>
<em>{{item.sub_complete}}/{{item.sub_num}}</em>
</div>
</div>
<div v-if="item.msg_num > 0" class="item-icon">
<i class="taskfont">&#xe71e;</i>
<em>{{item.msg_num}}</em>
</div>
<div v-if="item.sub_num > 0" class="item-icon" @click.stop="getSublist(item)">
<i class="taskfont">&#xe71f;</i>
<em>{{item.sub_complete}}/{{item.sub_num}}</em>
</div>
</div>
</Col>
<Col span="3" class="row-column">
<EDropdown
trigger="click"
size="small"
placement="bottom"
:disabled="item.sub_top === true"
@command="dropTask(item, $event)">
<div class="task-column">{{columnName(item.column_id)}}</div>
<EDropdownMenu slot="dropdown">
<EDropdownItem v-for="column in columnList(item.project_id)" :key="column.id" :command="'column::' + column.id">
{{column.name}}
</EDropdownItem>
</EDropdownMenu>
</EDropdown>
</Col>
<Col span="3" class="row-priority">
<EDropdown
trigger="click"
size="small"
placement="bottom"
:disabled="item.sub_top === true"
@command="dropTask(item, $event)">
<TaskPriority :backgroundColor="item.p_color">{{item.p_name || $L('未设置')}}</TaskPriority>
<EDropdownMenu slot="dropdown">
<EDropdownItem v-for="(item, key) in taskPriority" :key="key" :command="'priority::' + key">
<i
class="taskfont"
:style="{color:item.color}"
v-html="item.p_name == item.name ? '&#xe61d;' : '&#xe61c;'"></i>
{{item.name}}
</EDropdownItem>
</EDropdownMenu>
</EDropdown>
</Col>
<Col span="3" class="row-user">
<ul @click="openTask(item)">
<li v-for="(user, keyu) in ownerUser(item.task_user)" :key="keyu" v-if="keyu < 3">
<UserAvatar :userid="user.userid" size="32" :borderWitdh="2" :borderColor="item.color" :showName="ownerUser(item.task_user).length === 1"/>
</li>
<li v-if="ownerUser(item.task_user).length === 0" class="no-owner">
<Button type="primary" size="small" @click.stop="openTask(item, true)">{{$L('领取任务')}}</Button>
</li>
</ul>
</Col>
<Col span="3" class="row-time">
<ETooltip
v-if="!item.complete_at && item.end_at"
:class="['task-time', item.today ? 'today' : '', item.overdue ? 'overdue' : '']"
:disabled="$isEEUiApp || windowTouch"
:open-delay="600"
:content="item.end_at">
<div @click="openTask(item)">{{expiresFormat(item.end_at)}}</div>
</ETooltip>
<div v-else-if="showCompleteAt && item.complete_at" :title="item.complete_at">{{completeAtFormat(item.complete_at)}}</div>
</Col>
</Col>
<Col span="3" class="row-column">
<EDropdown
trigger="click"
size="small"
placement="bottom"
:disabled="item.sub_top === true"
@command="dropTask(item, $event)">
<div class="task-column">{{columnName(item.column_id)}}</div>
<EDropdownMenu slot="dropdown">
<EDropdownItem v-for="column in columnList(item.project_id)" :key="column.id" :command="'column::' + column.id">
{{column.name}}
</EDropdownItem>
</EDropdownMenu>
</EDropdown>
</Col>
<Col span="3" class="row-priority">
<EDropdown
trigger="click"
size="small"
placement="bottom"
:disabled="item.sub_top === true"
@command="dropTask(item, $event)">
<TaskPriority :backgroundColor="item.p_color">{{item.p_name || $L('未设置')}}</TaskPriority>
<EDropdownMenu slot="dropdown">
<EDropdownItem v-for="(item, key) in taskPriority" :key="key" :command="'priority::' + key">
<i
class="taskfont"
:style="{color:item.color}"
v-html="item.p_name == item.name ? '&#xe61d;' : '&#xe61c;'"></i>
{{item.name}}
</EDropdownItem>
</EDropdownMenu>
</EDropdown>
</Col>
<Col span="3" class="row-user">
<ul @click="openTask(item)">
<li v-for="(user, keyu) in ownerUser(item.task_user)" :key="keyu" v-if="keyu < 3">
<UserAvatar :userid="user.userid" size="32" :borderWitdh="2" :borderColor="item.color" :showName="ownerUser(item.task_user).length === 1"/>
</li>
<li v-if="ownerUser(item.task_user).length === 0" class="no-owner">
<Button type="primary" size="small" @click.stop="openTask(item, true)">{{$L('领取任务')}}</Button>
</li>
</ul>
</Col>
<Col span="3" class="row-time">
<ETooltip
v-if="!item.complete_at && item.end_at"
:class="['task-time', item.today ? 'today' : '', item.overdue ? 'overdue' : '']"
:disabled="$isEEUiApp || windowTouch"
:open-delay="600"
:content="item.end_at">
<div @click="openTask(item)">{{expiresFormat(item.end_at)}}</div>
</ETooltip>
<div v-else-if="showCompleteAt && item.complete_at" :title="item.complete_at">{{completeAtFormat(item.complete_at)}}</div>
</Col>
</template>
</Row>
<TaskRow
v-if="taskOpen[item.id]===true"
@ -139,6 +148,10 @@ export default {
type: Boolean,
default: false
},
taskVisibilitys: {
type: Object,
default: () => ({})
}
},
data() {
return {
@ -279,6 +292,23 @@ export default {
} else {
return time.format('YYYY-MM-DD')
}
},
taskItemVisible(key) {
return this.parentId > 0 || this.taskVisibilitys[key]?.visible
},
taskItemStyle({id, color}) {
const style = {}
if (color) {
style.backgroundColor = color;
style.borderBottomColor = color;
}
const key = `${this.openKey}_${id}`;
if (!this.taskItemVisible(key)) {
style.height = (this.taskVisibilitys[key]?.height || 49) + 'px';
}
return style;
}
}
}