perf: 任务操作菜单组件化

This commit is contained in:
kuaifan 2022-01-09 00:03:00 +08:00
parent 114b792300
commit eb1f5f2632
12 changed files with 394 additions and 491 deletions

View File

@ -116,8 +116,7 @@
<Icon type="md-trash" />{{$L('删除')}} <Icon type="md-trash" />{{$L('删除')}}
</div> </div>
</EDropdownItem> </EDropdownItem>
<EDropdownItem divided disabled>{{$L('颜色')}}</EDropdownItem> <EDropdownItem v-for="(c, k) in $store.state.columnColorList" :key="k" :divided="k==0" :command="c">
<EDropdownItem v-for="(c, k) in $store.state.columnColorList" :key="k" :command="c">
<div class="item"> <div class="item">
<i class="taskfont" :style="{color:c.color}" v-html="c.color == column.color ? '&#xe61d;' : '&#xe61c;'"></i>{{$L(c.name)}} <i class="taskfont" :style="{color:c.color}" v-html="c.color == column.color ? '&#xe61d;' : '&#xe61c;'"></i>{{$L(c.name)}}
</div> </div>
@ -154,42 +153,7 @@
<div :class="['task-head', item.desc ? 'has-desc' : '']"> <div :class="['task-head', item.desc ? 'has-desc' : '']">
<div class="task-title"><pre>{{item.name}}</pre></div> <div class="task-title"><pre>{{item.name}}</pre></div>
<div class="task-menu" @click.stop=""> <div class="task-menu" @click.stop="">
<div v-if="taskLoad[item.id] === true" class="loading"><Loading /></div> <TaskMenu :task="item" icon="ios-more"/>
<EDropdown
v-else
trigger="click"
size="small"
@command="dropTask(item, $event)">
<Icon type="ios-more" />
<EDropdownMenu slot="dropdown" class="project-list-more-dropdown-menu">
<EDropdownItem v-if="item.complete_at" command="uncomplete">
<div class="item red">
<Icon type="md-checkmark-circle-outline" />{{$L('标记未完成')}}
</div>
</EDropdownItem>
<EDropdownItem v-else command="complete">
<div class="item">
<Icon type="md-radio-button-off" />{{$L('完成')}}
</div>
</EDropdownItem>
<EDropdownItem command="archived">
<div class="item">
<Icon type="ios-filing" />{{$L('归档')}}
</div>
</EDropdownItem>
<EDropdownItem command="remove">
<div class="item">
<Icon type="md-trash" />{{$L('删除')}}
</div>
</EDropdownItem>
<EDropdownItem divided disabled>{{$L('背景色')}}</EDropdownItem>
<EDropdownItem v-for="(c, k) in $store.state.taskColorList" :key="k" :command="c">
<div class="item">
<i class="taskfont" :style="{color:c.color||'#f9f9f9'}" v-html="c.color == item.color ? '&#xe61d;' : '&#xe61c;'"></i>{{$L(c.name)}}
</div>
</EDropdownItem>
</EDropdownMenu>
</EDropdown>
</div> </div>
</div> </div>
<div v-if="item.desc" class="task-desc"><pre v-html="item.desc"></pre></div> <div v-if="item.desc" class="task-desc"><pre v-html="item.desc"></pre></div>
@ -286,7 +250,7 @@
<Col span="3"></Col> <Col span="3"></Col>
<Col span="3"></Col> <Col span="3"></Col>
</Row> </Row>
<TaskRow v-if="projectParameter('showMy')" :list="transforTasks(myList)" open-key="my" @command="dropTask" @on-priority="addTaskOpen" fast-add-task/> <TaskRow v-if="projectParameter('showMy')" :list="transforTasks(myList)" open-key="my" @on-priority="addTaskOpen" fast-add-task/>
</div> </div>
<!--协助的任务--> <!--协助的任务-->
<div v-if="helpList.length" :class="['project-table-body', !projectParameter('showHelp') ? 'project-table-hide' : '']"> <div v-if="helpList.length" :class="['project-table-body', !projectParameter('showHelp') ? 'project-table-hide' : '']">
@ -301,7 +265,7 @@
<Col span="3"></Col> <Col span="3"></Col>
<Col span="3"></Col> <Col span="3"></Col>
</Row> </Row>
<TaskRow v-if="projectParameter('showHelp')" :list="helpList" open-key="help" @command="dropTask" @on-priority="addTaskOpen"/> <TaskRow v-if="projectParameter('showHelp')" :list="helpList" open-key="help" @on-priority="addTaskOpen"/>
</div> </div>
<!--未完成任务--> <!--未完成任务-->
<div v-if="projectData.task_num > 0" :class="['project-table-body', !projectParameter('showUndone') ? 'project-table-hide' : '']"> <div v-if="projectData.task_num > 0" :class="['project-table-body', !projectParameter('showUndone') ? 'project-table-hide' : '']">
@ -316,7 +280,7 @@
<Col span="3"></Col> <Col span="3"></Col>
<Col span="3"></Col> <Col span="3"></Col>
</Row> </Row>
<TaskRow v-if="projectParameter('showUndone')" :list="unList" open-key="undone" @command="dropTask" @on-priority="addTaskOpen"/> <TaskRow v-if="projectParameter('showUndone')" :list="unList" open-key="undone" @on-priority="addTaskOpen"/>
</div> </div>
<!--已完成任务--> <!--已完成任务-->
<div v-if="projectData.task_num > 0" :class="['project-table-body', !projectParameter('showCompleted') ? 'project-table-hide' : '']"> <div v-if="projectData.task_num > 0" :class="['project-table-body', !projectParameter('showCompleted') ? 'project-table-hide' : '']">
@ -331,7 +295,7 @@
<Col span="3"></Col> <Col span="3"></Col>
<Col span="3">{{projectData.task_num > 0 && projectParameter('showCompleted') ? $L('完成时间') : ''}}</Col> <Col span="3">{{projectData.task_num > 0 && projectParameter('showCompleted') ? $L('完成时间') : ''}}</Col>
</Row> </Row>
<TaskRow v-if="projectParameter('showCompleted')" :list="completedList" open-key="completed" @command="dropTask" @on-priority="addTaskOpen" showCompleteAt/> <TaskRow v-if="projectParameter('showCompleted')" :list="completedList" open-key="completed" @on-priority="addTaskOpen" showCompleteAt/>
</div> </div>
</div> </div>
@ -475,10 +439,12 @@ import TaskArchived from "./TaskArchived";
import ProjectLog from "./ProjectLog"; import ProjectLog from "./ProjectLog";
import DrawerOverlay from "../../../components/DrawerOverlay"; import DrawerOverlay from "../../../components/DrawerOverlay";
import ProjectWorkflow from "./ProjectWorkflow"; import ProjectWorkflow from "./ProjectWorkflow";
import TaskMenu from "./TaskMenu";
export default { export default {
name: "ProjectList", name: "ProjectList",
components: { components: {
TaskMenu,
ProjectWorkflow, ProjectWorkflow,
DrawerOverlay, DrawerOverlay,
ProjectLog, TaskArchived, TaskRow, Draggable, TaskAddSimple, UserInput, TaskAdd, TaskPriority}, ProjectLog, TaskArchived, TaskRow, Draggable, TaskAddSimple, UserInput, TaskAdd, TaskPriority},
@ -489,9 +455,6 @@ export default {
columnLoad: {}, columnLoad: {},
columnTopShow: {}, columnTopShow: {},
taskLoad: {},
tempShowTasks: [],
sortField: 'end_at', sortField: 'end_at',
sortType: 'desc', sortType: 'desc',
@ -639,13 +602,13 @@ export default {
}, },
myList() { myList() {
const {projectId, cacheTasks, searchText, tempShowTasks, sortField, sortType} = this; const {projectId, cacheTasks, searchText, sortField, sortType} = this;
const array = cacheTasks.filter((task) => { const array = cacheTasks.filter((task) => {
if (task.project_id != projectId) { if (task.project_id != projectId) {
return false; return false;
} }
if (!this.projectParameter('completedTask')) { if (!this.projectParameter('completedTask')) {
if (task.complete_at && !tempShowTasks.find(({id}) => id == task.id)) { if (task.complete_at) {
return false; return false;
} }
} }
@ -672,13 +635,13 @@ export default {
}, },
helpList() { helpList() {
const {projectId, cacheTasks, searchText, tempShowTasks, userId, sortField, sortType} = this; const {projectId, cacheTasks, searchText, userId, sortField, sortType} = this;
const array = cacheTasks.filter((task) => { const array = cacheTasks.filter((task) => {
if (task.project_id != projectId || task.parent_id > 0) { if (task.project_id != projectId || task.parent_id > 0) {
return false; return false;
} }
if (!this.projectParameter('completedTask')) { if (!this.projectParameter('completedTask')) {
if (task.complete_at && !tempShowTasks.find(({id}) => id == task.id)) { if (task.complete_at) {
return false; return false;
} }
} }
@ -767,9 +730,6 @@ export default {
projectData() { projectData() {
this.sortData = this.getSort(); this.sortData = this.getSort();
}, },
'$route'() {
this.tempShowTasks = [];
},
searchShow(val) { searchShow(val) {
if (val) { if (val) {
this.$nextTick(() => { this.$nextTick(() => {
@ -965,112 +925,6 @@ export default {
}); });
}, },
dropTask(task, command) {
if ($A.isJson(command)) {
if (command.name) {
//
this.updateTask(task, {
color: command.color
})
}
return;
}
if ($A.leftExists(command, 'column::')) {
//
this.updateTask(task, {
column_id: $A.leftDelete(command, 'column::')
})
return;
}
if ($A.leftExists(command, 'priority::')) {
//
let data = this.taskPriority[parseInt($A.leftDelete(command, 'priority::'))];
if (data) {
this.updateTask(task, {
p_level: data.priority,
p_name: data.name,
p_color: data.color,
})
}
return;
}
switch (command) {
case 'complete':
if (task.complete_at) return;
this.updateTask(task, {
complete_at: $A.formatDate("Y-m-d H:i:s")
}).then(() => {
this.tempShowTasks.push(task)
})
break;
case 'uncomplete':
if (!task.complete_at) return;
this.updateTask(task, {
complete_at: false
}).then(() => {
this.tempShowTasks = this.tempShowTasks.filter(({id}) => id != task.id)
})
break;
case 'archived':
case 'remove':
this.archivedOrRemoveTask(task, command);
break;
}
},
updateTask(task, updata) {
return new Promise((resolve, reject) => {
if (this.taskLoad[task.id] === true) {
reject()
return;
}
this.$set(this.taskLoad, task.id, true);
//
Object.keys(updata).forEach(key => this.$set(task, key, updata[key]));
//
this.$store.dispatch("taskUpdate", Object.assign(updata, {
task_id: task.id,
})).then(() => {
this.$set(this.taskLoad, task.id, false);
resolve()
}).catch(({msg}) => {
$A.modalError(msg);
this.$set(this.taskLoad, task.id, false);
this.$store.dispatch("getTaskOne", task.id);
reject()
});
});
},
archivedOrRemoveTask(task, type) {
let typeDispatch = type == 'remove' ? 'removeTask' : 'archivedTask';
let typeName = type == 'remove' ? '删除' : '归档';
let typeTask = task.parent_id > 0 ? '子任务' : '任务';
$A.modalConfirm({
title: typeName + typeTask,
content: '你确定要' + typeName + typeTask + '【' + task.name + '】吗?',
loading: true,
onOk: () => {
if (this.taskLoad[task.id] === true) {
this.$Modal.remove();
return;
}
this.$set(this.taskLoad, task.id, true);
this.$store.dispatch(typeDispatch, task.id).then(({msg}) => {
$A.messageSuccess(msg);
this.$Modal.remove();
this.$set(this.taskLoad, task.id, false);
}).catch(({msg}) => {
$A.modalError(msg, 301);
this.$Modal.remove();
this.$set(this.taskLoad, task.id, false);
});
}
});
},
onSort(field) { onSort(field) {
this.sortField = field; this.sortField = field;
this.sortType = this.sortType == 'desc' ? 'asc' : 'desc'; this.sortType = this.sortType == 'desc' ? 'asc' : 'desc';
@ -1311,7 +1165,6 @@ export default {
toggleCompleted() { toggleCompleted() {
this.$store.dispatch('toggleProjectParameter', 'completedTask'); this.$store.dispatch('toggleProjectParameter', 'completedTask');
this.tempShowTasks = [];
}, },
expiresFormat(date) { expiresFormat(date) {

View File

@ -97,6 +97,13 @@ export default {
title: this.$L('完成时间'), title: this.$L('完成时间'),
key: 'complete_at', key: 'complete_at',
width: 168, width: 168,
render: (h, {row}) => {
return h('div', {
style: {
color: row.complete_at ? '' : '#f00'
}
}, row.complete_at || this.$L('未完成'));
}
}, },
{ {
title: this.$L('归档时间'), title: this.$L('归档时间'),

View File

@ -2,40 +2,7 @@
<!--子任务--> <!--子任务-->
<li v-if="ready && taskDetail.parent_id > 0"> <li v-if="ready && taskDetail.parent_id > 0">
<div class="subtask-icon"> <div class="subtask-icon">
<div v-if="taskDetail.loading === true" class="loading"><Loading /></div> <TaskMenu :task="taskDetail" :load-status="taskDetail.loading === true"/>
<EDropdown
v-else
trigger="click"
placement="bottom"
size="small"
@command="dropTask">
<div>
<Icon v-if="taskDetail.complete_at" class="completed" type="md-checkmark-circle" />
<Icon v-else type="md-radio-button-off" />
</div>
<EDropdownMenu slot="dropdown" class="project-list-more-dropdown-menu">
<EDropdownItem v-if="taskDetail.complete_at" command="uncomplete">
<div class="item red">
<Icon type="md-checkmark-circle-outline" />{{$L('标记未完成')}}
</div>
</EDropdownItem>
<EDropdownItem v-else command="complete">
<div class="item">
<Icon type="md-radio-button-off" />{{$L('完成')}}
</div>
</EDropdownItem>
<EDropdownItem command="times">
<div class="item">
<Icon type="md-time" />{{$L('时间')}}
</div>
</EDropdownItem>
<EDropdownItem command="remove">
<div class="item">
<Icon type="md-trash" />{{$L('删除')}}
</div>
</EDropdownItem>
</EDropdownMenu>
</EDropdown>
</div> </div>
<div class="subtask-name"> <div class="subtask-name">
<Input <Input
@ -58,9 +25,10 @@
@on-clear="timeClear" @on-clear="timeClear"
@on-ok="timeOk" @on-ok="timeOk"
transfer> transfer>
<div @click="openTime" :class="['time', taskDetail.today ? 'today' : '', taskDetail.overdue ? 'overdue' : '']"> <div v-if="taskDetail.end_at && taskDetail.end_at != mainEndAt" @click="openTime" :class="['time', taskDetail.today ? 'today' : '', taskDetail.overdue ? 'overdue' : '']">
{{taskDetail.end_at && taskDetail.end_at != mainEndAt ? expiresFormat(taskDetail.end_at) : ' '}} {{expiresFormat(taskDetail.end_at)}}
</div> </div>
<Icon v-else class="clock" type="ios-clock-outline" @click="openTime" />
</DatePicker> </DatePicker>
<Poptip <Poptip
ref="owner" ref="owner"
@ -93,8 +61,7 @@
<div v-else-if="ready" v-show="taskDetail.id > 0" :class="{'task-detail':true, 'open-dialog': hasOpenDialog, 'completed': taskDetail.complete_at}"> <div v-else-if="ready" v-show="taskDetail.id > 0" :class="{'task-detail':true, 'open-dialog': hasOpenDialog, 'completed': taskDetail.complete_at}">
<div class="task-info"> <div class="task-info">
<div class="head"> <div class="head">
<Icon v-if="taskDetail.complete_at" class="icon completed" type="md-checkmark-circle" @click="updateData('uncomplete')"/> <TaskMenu :task="taskDetail" class="icon" size="medium" :color-show="false" quick-completed/>
<Icon v-else class="icon" type="md-radio-button-off" @click="updateData('complete')"/>
<div class="nav"> <div class="nav">
<p v-if="projectName"><span>{{projectName}}</span></p> <p v-if="projectName"><span>{{projectName}}</span></p>
<p v-if="columnName"><span>{{columnName}}</span></p> <p v-if="columnName"><span>{{columnName}}</span></p>
@ -130,34 +97,9 @@
<ETooltip v-if="$Electron" :content="$L('新窗口打开')"> <ETooltip v-if="$Electron" :content="$L('新窗口打开')">
<i class="taskfont open" @click="openNewWin">&#xe776;</i> <i class="taskfont open" @click="openNewWin">&#xe776;</i>
</ETooltip> </ETooltip>
<EDropdown <div class="menu">
trigger="click" <TaskMenu :task="taskDetail" icon="ios-more" completed-icon="ios-more" size="medium" :color-show="false"/>
placement="bottom" </div>
@command="dropTask">
<Icon class="menu" type="ios-more"/>
<EDropdownMenu slot="dropdown">
<EDropdownItem v-if="taskDetail.complete_at" command="uncomplete">
<div class="item red">
<Icon type="md-checkmark-circle-outline" />{{$L('标记未完成')}}
</div>
</EDropdownItem>
<EDropdownItem v-else command="complete">
<div class="item">
<Icon type="md-radio-button-off" />{{$L('完成')}}
</div>
</EDropdownItem>
<EDropdownItem command="archived">
<div class="item">
<Icon type="ios-filing" />{{$L('归档')}}
</div>
</EDropdownItem>
<EDropdownItem command="remove">
<div class="item">
<Icon type="md-trash" />{{$L('删除')}}
</div>
</EDropdownItem>
</EDropdownMenu>
</EDropdown>
</div> </div>
</div> </div>
<div class="scroller overlay-y"> <div class="scroller overlay-y">
@ -428,10 +370,11 @@ import TaskUpload from "./TaskUpload";
import DialogWrapper from "./DialogWrapper"; import DialogWrapper from "./DialogWrapper";
import ProjectLog from "./ProjectLog"; import ProjectLog from "./ProjectLog";
import {Store} from "le5le-store"; import {Store} from "le5le-store";
import TaskMenu from "./TaskMenu";
export default { export default {
name: "TaskDetail", name: "TaskDetail",
components: {ProjectLog, DialogWrapper, TaskUpload, UserInput, TaskPriority, TEditor}, components: {TaskMenu, ProjectLog, DialogWrapper, TaskUpload, UserInput, TaskPriority, TEditor},
props: { props: {
taskId: { taskId: {
type: Number, type: Number,
@ -794,34 +737,8 @@ export default {
} }
}, },
dropTask(command) {
switch (command) {
case 'complete':
this.updateData('complete')
break;
case 'uncomplete':
this.updateData('uncomplete')
break;
case 'times':
this.openTime()
break;
case 'archived':
case 'remove':
this.archivedOrRemoveTask(command);
break;
}
},
updateData(action, params) { updateData(action, params) {
switch (action) { switch (action) {
case 'complete':
this.$set(this.taskDetail, 'complete_at', $A.formatDate());
action = 'complete_at';
break;
case 'uncomplete':
this.$set(this.taskDetail, 'complete_at', false);
action = 'complete_at';
break;
case 'priority': case 'priority':
this.$set(this.taskDetail, 'p_level', params.priority) this.$set(this.taskDetail, 'p_level', params.priority)
this.$set(this.taskDetail, 'p_name', params.name) this.$set(this.taskDetail, 'p_name', params.name)

View File

@ -0,0 +1,184 @@
<template>
<EDropdown
trigger="click"
:size="size"
placement="bottom"
@command="dropTask">
<slot name="icon">
<div class="task-menu-icon">
<div v-if="loadIng" class="loading"><Loading /></div>
<template v-else>
<Icon v-if="task.complete_at" class="completed" :type="completedIcon" />
<Icon v-else :type="icon" class="uncomplete"/>
</template>
</div>
</slot>
<EDropdownMenu slot="dropdown" class="task-menu-more-dropdown">
<EDropdownItem v-if="task.complete_at" command="uncomplete">
<div class="item red">
<Icon type="md-checkmark-circle-outline" />{{$L('标记未完成')}}
</div>
</EDropdownItem>
<EDropdownItem v-else command="complete">
<div class="item">
<Icon type="md-radio-button-off" />{{$L('完成')}}
</div>
</EDropdownItem>
<EDropdownItem v-if="task.parent_id === 0" command="archived">
<div class="item">
<Icon type="ios-filing" />{{$L('归档')}}
</div>
</EDropdownItem>
<EDropdownItem command="remove">
<div class="item">
<Icon type="md-trash" />{{$L('删除')}}
</div>
</EDropdownItem>
<template v-if="task.parent_id === 0 && colorShow">
<EDropdownItem v-for="(c, k) in $store.state.taskColorList" :key="k" :divided="k==0" :command="c">
<div class="item">
<i class="taskfont" :style="{color:c.color||'#f9f9f9'}" v-html="c.color == task.color ? '&#xe61d;' : '&#xe61c;'"></i>{{$L(c.name)}}
</div>
</EDropdownItem>
</template>
</EDropdownMenu>
</EDropdown>
</template>
<script>
import {mapState} from "vuex";
export default {
name: "TaskMenu",
props: {
task: {
type: Object,
default: () => {
return {};
}
},
loadStatus: {
type: Boolean,
default: false
},
colorShow: {
type: Boolean,
default: true
},
quickCompleted: { //
type: Boolean,
default: false
},
size: {
type: String,
default: 'small'
},
icon: {
type: String,
default: 'md-radio-button-off'
},
completedIcon: {
type: String,
default: 'md-checkmark-circle'
},
},
data() {
return {
}
},
computed: {
...mapState(['taskLoading']),
loadIng() {
if (this.loadStatus) {
return true;
}
const load = this.taskLoading.find(({id}) => id == this.task.id);
return load && load.num > 0
}
},
methods: {
dropTask(command) {
if ($A.isJson(command)) {
if (command.name) {
//
this.updateTask({
color: command.color
})
}
return;
}
switch (command) {
case 'complete':
if (this.task.complete_at) return;
this.updateTask({
complete_at: $A.formatDate("Y-m-d H:i:s")
}).then(() => {
//
})
break;
case 'uncomplete':
if (!this.task.complete_at) return;
this.updateTask({
complete_at: false
}).then(() => {
//
})
break;
case 'archived':
case 'remove':
this.archivedOrRemoveTask(command);
break;
}
},
updateTask(updata) {
return new Promise((resolve, reject) => {
if (this.loadIng) {
reject()
return;
}
//
Object.keys(updata).forEach(key => this.$set(this.task, key, updata[key]));
//
this.$store.dispatch("taskUpdate", Object.assign(updata, {
task_id: this.task.id,
})).then(() => {
resolve()
}).catch(({msg}) => {
$A.modalError(msg);
this.$store.dispatch("getTaskOne", this.task.id);
reject()
});
});
},
archivedOrRemoveTask(type) {
let typeDispatch = type == 'remove' ? 'removeTask' : 'archivedTask';
let typeName = type == 'remove' ? '删除' : '归档';
let typeTask = this.task.parent_id > 0 ? '子任务' : '任务';
$A.modalConfirm({
title: typeName + typeTask,
content: '你确定要' + typeName + typeTask + '【' + this.task.name + '】吗?',
loading: true,
onOk: () => {
if (this.loadIng) {
this.$Modal.remove();
return;
}
this.$store.dispatch(typeDispatch, this.task.id).then(({msg}) => {
$A.messageSuccess(msg);
this.$Modal.remove();
}).catch(({msg}) => {
$A.modalError(msg, 301);
this.$Modal.remove();
});
}
});
},
},
}
</script>

View File

@ -9,47 +9,7 @@
:class="['sub-icon', taskOpen[item.id] ? 'active' : '']" :class="['sub-icon', taskOpen[item.id] ? 'active' : '']"
type="ios-arrow-forward" type="ios-arrow-forward"
@click="getSublist(item)"/> @click="getSublist(item)"/>
<EDropdown <TaskMenu :ref="`taskMenu_${item.id}`" :task="item"/>
trigger="click"
size="small"
placement="bottom"
@command="dropTask(item, $event)">
<div class="drop-icon">
<Icon v-if="item.complete_at" class="completed" type="md-checkmark-circle" />
<Icon v-else type="md-radio-button-off" />
<div v-if="taskLoad[item.id] === true" class="loading"><Loading /></div>
</div>
<EDropdownMenu slot="dropdown" class="project-list-more-dropdown-menu">
<EDropdownItem v-if="item.complete_at" command="uncomplete">
<div class="item red">
<Icon type="md-checkmark-circle-outline" />{{$L('标记未完成')}}
</div>
</EDropdownItem>
<EDropdownItem v-else command="complete">
<div class="item">
<Icon type="md-radio-button-off" />{{$L('完成')}}
</div>
</EDropdownItem>
<EDropdownItem v-if="item.parent_id === 0" command="archived">
<div class="item">
<Icon type="ios-filing" />{{$L('归档')}}
</div>
</EDropdownItem>
<EDropdownItem command="remove">
<div class="item">
<Icon type="md-trash" />{{$L('删除')}}
</div>
</EDropdownItem>
<template v-if="item.parent_id === 0">
<EDropdownItem divided disabled>{{$L('背景色')}}</EDropdownItem>
<EDropdownItem v-for="(c, k) in $store.state.taskColorList" :key="k" :command="c">
<div class="item">
<i class="taskfont" :style="{color:c.color||'#f9f9f9'}" v-html="c.color == item.color ? '&#xe61d;' : '&#xe61c;'"></i>{{$L(c.name)}}
</div>
</EDropdownItem>
</template>
</EDropdownMenu>
</EDropdown>
<div class="item-title" @click="openTask(item)"> <div class="item-title" @click="openTask(item)">
<span v-if="item.sub_top === true">{{$L('子任务')}}</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> <span v-if="item.sub_my && item.sub_my.length > 0">+{{item.sub_my.length}}</span>
@ -145,10 +105,11 @@ import TaskPriority from "./TaskPriority";
import TaskAddSimple from "./TaskAddSimple"; import TaskAddSimple from "./TaskAddSimple";
import {mapState} from "vuex"; import {mapState} from "vuex";
import {Store} from "le5le-store"; import {Store} from "le5le-store";
import TaskMenu from "./TaskMenu";
export default { export default {
name: "TaskRow", name: "TaskRow",
components: {TaskAddSimple, TaskPriority}, components: {TaskMenu, TaskAddSimple, TaskPriority},
props: { props: {
list: { list: {
type: Array, type: Array,
@ -211,8 +172,31 @@ export default {
return column ? column.name : ''; return column ? column.name : '';
}, },
dropTask(task, command) { dropTask(task, command) {
this.$emit("command", task, command) const el = this.$refs[`taskMenu_${task.id}`];
if (!el) {
return;
}
//
if ($A.leftExists(command, 'column::')) {
//
el[0].updateTask({
column_id: $A.leftDelete(command, 'column::')
})
return;
}
if ($A.leftExists(command, 'priority::')) {
//
let data = this.taskPriority[parseInt($A.leftDelete(command, 'priority::'))];
if (data) {
el[0].updateTask({
p_level: data.priority,
p_name: data.name,
p_color: data.color,
})
}
}
}, },
onPriority(data) { onPriority(data) {

View File

@ -40,45 +40,11 @@
v-if="item.p_name" v-if="item.p_name"
class="priority-color" class="priority-color"
:style="{backgroundColor:item.p_color}"></em> :style="{backgroundColor:item.p_color}"></em>
<EDropdown <TaskMenu :task="item">
trigger="click" <div slot="icon" class="drop-icon" @click.stop="">
size="small"
placement="bottom"
@command="dropTask(item, $event)">
<div class="drop-icon" @click.stop="">
<i class="taskfont" v-html="item.complete_at ? '&#xe627;' : '&#xe625;'"></i> <i class="taskfont" v-html="item.complete_at ? '&#xe627;' : '&#xe625;'"></i>
</div> </div>
<EDropdownMenu slot="dropdown" class="project-list-more-dropdown-menu"> </TaskMenu>
<EDropdownItem v-if="item.complete_at" command="uncomplete">
<div class="item red">
<Icon type="md-checkmark-circle-outline" />{{$L('标记未完成')}}
</div>
</EDropdownItem>
<EDropdownItem v-else command="complete">
<div class="item">
<Icon type="md-radio-button-off" />{{$L('完成')}}
</div>
</EDropdownItem>
<EDropdownItem v-if="item.parent_id === 0" command="archived">
<div class="item">
<Icon type="ios-filing" />{{$L('归档')}}
</div>
</EDropdownItem>
<EDropdownItem command="remove">
<div class="item">
<Icon type="md-trash" />{{$L('删除')}}
</div>
</EDropdownItem>
<template v-if="item.parent_id === 0">
<EDropdownItem divided disabled>{{$L('背景色')}}</EDropdownItem>
<EDropdownItem v-for="(c, k) in $store.state.taskColorList" :key="k" :command="c">
<div class="item">
<i class="taskfont" :style="{color:c.color||'#f9f9f9'}" v-html="c.color == item.color ? '&#xe61d;' : '&#xe61c;'"></i>{{$L(c.name)}}
</div>
</EDropdownItem>
</template>
</EDropdownMenu>
</EDropdown>
<div class="item-title"> <div class="item-title">
<span v-if="item.sub_top === true">{{$L('子任务')}}</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> <span v-if="item.sub_my && item.sub_my.length > 0">+{{item.sub_my.length}}</span>
@ -107,9 +73,10 @@
import {mapGetters, mapState} from "vuex"; import {mapGetters, mapState} from "vuex";
import AppDown from "../../components/AppDown"; import AppDown from "../../components/AppDown";
import {Store} from "le5le-store"; import {Store} from "le5le-store";
import TaskMenu from "./components/TaskMenu";
export default { export default {
components: {AppDown}, components: {TaskMenu, AppDown},
data() { data() {
return { return {
nowTime: $A.Time(), nowTime: $A.Time(),
@ -117,10 +84,6 @@ export default {
loadIng: 0, loadIng: 0,
dashboard: 'today', dashboard: 'today',
taskLoad: {},
tempShowTasks: [],
} }
}, },
@ -156,7 +119,7 @@ export default {
}, },
list() { list() {
const {dashboard, tempShowTasks} = this; const {dashboard} = this;
let data = []; let data = [];
switch (dashboard) { switch (dashboard) {
case 'today': case 'today':
@ -166,112 +129,17 @@ export default {
data = this.transforTasks(this.dashboardTask.overdue); data = this.transforTasks(this.dashboardTask.overdue);
break break
} }
if (tempShowTasks.length > 0) {
data.push(...tempShowTasks);
}
return data.sort((a, b) => { return data.sort((a, b) => {
return $A.Date(a.end_at) - $A.Date(b.end_at); return $A.Date(a.end_at) - $A.Date(b.end_at);
}); });
}, },
}, },
watch: {
'$route'() {
this.tempShowTasks = [];
},
dashboard() {
this.tempShowTasks = [];
}
},
methods: { methods: {
dropTask(task, command) {
switch (command) {
case 'complete':
if (task.complete_at) return;
this.updateTask(task, {
complete_at: $A.formatDate("Y-m-d H:i:s")
}).then(() => {
this.tempShowTasks.push(task)
})
break;
case 'uncomplete':
if (!task.complete_at) return;
this.updateTask(task, {
complete_at: false
}).then(() => {
this.tempShowTasks = this.tempShowTasks.filter(({id}) => id != task.id)
})
break;
case 'archived':
case 'remove':
this.archivedOrRemoveTask(task, command);
break;
default:
if (command.name) {
this.updateTask(task, {
color: command.color
})
}
break;
}
},
openTask(task) { openTask(task) {
this.$store.dispatch("openTask", task) this.$store.dispatch("openTask", task)
}, },
updateTask(task, updata) {
return new Promise((resolve, reject) => {
if (this.taskLoad[task.id] === true) {
reject()
return;
}
this.$set(this.taskLoad, task.id, true);
//
Object.keys(updata).forEach(key => this.$set(task, key, updata[key]));
//
this.$store.dispatch("taskUpdate", Object.assign(updata, {
task_id: task.id,
})).then(() => {
this.$set(this.taskLoad, task.id, false);
resolve()
}).catch(({msg}) => {
$A.modalError(msg);
this.$set(this.taskLoad, task.id, false);
this.$store.dispatch("getTaskOne", task.id);
reject();
});
})
},
archivedOrRemoveTask(task, type) {
let typeDispatch = type == 'remove' ? 'removeTask' : 'archivedTask';
let typeName = type == 'remove' ? '删除' : '归档';
let typeTask = task.parent_id > 0 ? '子任务' : '任务';
$A.modalConfirm({
title: typeName + typeTask,
content: '你确定要' + typeName + typeTask + '【' + task.name + '】吗?',
loading: true,
onOk: () => {
if (this.taskLoad[task.id] === true) {
this.$Modal.remove();
return;
}
this.$set(this.taskLoad, task.id, true);
this.$store.dispatch(typeDispatch, task.id).then(({msg}) => {
$A.messageSuccess(msg);
this.$Modal.remove();
this.$set(this.taskLoad, task.id, false);
}).catch(({msg}) => {
$A.modalError(msg, 301);
this.$Modal.remove();
this.$set(this.taskLoad, task.id, false);
});
}
});
},
expiresFormat(date) { expiresFormat(date) {
return $A.countDownFormat(date, this.nowTime) return $A.countDownFormat(date, this.nowTime)
}, },

View File

@ -1135,6 +1135,7 @@ export default {
reject({msg: 'Parameter error'}); reject({msg: 'Parameter error'});
return; return;
} }
dispatch("taskLoadAdd", task_id)
dispatch("call", { dispatch("call", {
url: 'project/task/remove', url: 'project/task/remove',
data: { data: {
@ -1142,10 +1143,12 @@ export default {
}, },
}).then(result => { }).then(result => {
dispatch("forgetTask", task_id) dispatch("forgetTask", task_id)
dispatch("taskLoadSub", task_id)
resolve(result) resolve(result)
}).catch(e => { }).catch(e => {
console.error(e); console.error(e);
dispatch("getTaskOne", task_id); dispatch("getTaskOne", task_id);
dispatch("taskLoadSub", task_id)
reject(e) reject(e)
}); });
}); });
@ -1164,6 +1167,7 @@ export default {
reject({msg: 'Parameter error'}); reject({msg: 'Parameter error'});
return; return;
} }
dispatch("taskLoadAdd", task_id)
dispatch("call", { dispatch("call", {
url: 'project/task/archived', url: 'project/task/archived',
data: { data: {
@ -1171,10 +1175,12 @@ export default {
}, },
}).then(result => { }).then(result => {
dispatch("forgetTask", task_id) dispatch("forgetTask", task_id)
dispatch("taskLoadSub", task_id)
resolve(result) resolve(result)
}).catch(e => { }).catch(e => {
console.error(e); console.error(e);
dispatch("getTaskOne", task_id); dispatch("getTaskOne", task_id)
dispatch("taskLoadSub", task_id)
reject(e) reject(e)
}); });
}); });
@ -1365,28 +1371,67 @@ export default {
* 更新任务 * 更新任务
* @param state * @param state
* @param dispatch * @param dispatch
* @param data * @param data {task_id, ?}
* @returns {Promise<unknown>} * @returns {Promise<unknown>}
*/ */
taskUpdate({state, dispatch}, data) { taskUpdate({state, dispatch}, data) {
return new Promise(function (resolve, reject) { return new Promise(function (resolve, reject) {
const post = $A.cloneJSON($A.date2string(data)); const post = $A.cloneJSON($A.date2string(data));
// //
dispatch("taskLoadAdd", post.task_id)
dispatch("call", { dispatch("call", {
url: 'project/task/update', url: 'project/task/update',
data: post, data: post,
method: 'post', method: 'post',
}).then(result => { }).then(result => {
dispatch("taskLoadSub", post.task_id)
dispatch("saveTask", result.data) dispatch("saveTask", result.data)
resolve(result) resolve(result)
}).catch(e => { }).catch(e => {
console.error(e); console.error(e);
dispatch("taskLoadSub", post.task_id)
dispatch("getTaskOne", post.task_id); dispatch("getTaskOne", post.task_id);
reject(e) reject(e)
}); });
}); });
}, },
/**
* 任务增加等待
* @param state
* @param task_id
*/
taskLoadAdd({state}, task_id) {
setTimeout(() => {
const load = state.taskLoading.find(({id}) => id == task_id)
if (!load) {
state.taskLoading.push({
id: task_id,
num: 1
})
} else {
load.num++;
}
}, 300)
},
/**
* 任务减少等待
* @param state
* @param task_id
*/
taskLoadSub({state}, task_id) {
const load = state.taskLoading.find(({id}) => id == task_id)
if (!load) {
state.taskLoading.push({
id: task_id,
num: -1
})
} else {
load.num--;
}
},
/** /**
* 获取任务优先级预设数据 * 获取任务优先级预设数据
* @param state * @param state

View File

@ -67,6 +67,7 @@ state.taskId = 0;
state.taskContents = []; state.taskContents = [];
state.taskFiles = []; state.taskFiles = [];
state.taskLogs = []; state.taskLogs = [];
state.taskLoading = [];
// 任务优先级 // 任务优先级
state.taskPriority = []; state.taskPriority = [];

View File

@ -10,5 +10,6 @@
@import "task-add-simple"; @import "task-add-simple";
@import "task-archived"; @import "task-archived";
@import "task-detail"; @import "task-detail";
@import "task-menu";
@import "task-priority"; @import "task-priority";
@import "team-management"; @import "team-management";

View File

@ -380,10 +380,11 @@
} }
.ivu-icon { .ivu-icon {
font-size: 22px; font-size: 22px;
color: #777777; &.uncomplete {
cursor: pointer; color: #777777;
&:hover { &:hover {
color: #555555; color: #555555;
}
} }
} }
} }
@ -651,31 +652,12 @@
align-items: flex-start; align-items: flex-start;
padding: 12px 12px 12px 34px; padding: 12px 12px 12px 34px;
line-height: 24px; line-height: 24px;
.drop-icon {
position: relative;
}
.loading { .loading {
position: absolute; height: 24px;
top: 0;
left: 0;
bottom: 0;
width: 16px;
display: flex;
align-items: center;
justify-content: center;
.common-loading {
margin: 0;
width: 14px;
height: 14px;
}
} }
.ivu-icon { .ivu-icon {
cursor: pointer; cursor: pointer;
font-size: 16px; font-size: 16px;
color: #cccccc;
&.completed {
color: $primary-color;
}
&.sub-icon { &.sub-icon {
font-size: 16px; font-size: 16px;
width: 16px; width: 16px;
@ -689,6 +671,9 @@
transform: rotate(90deg); transform: rotate(90deg);
} }
} }
&.uncomplete {
color: #cccccc;
}
} }
.item-title { .item-title {
flex: 1; flex: 1;

View File

@ -26,11 +26,23 @@
background-color: #f4f5f5; background-color: #f4f5f5;
} }
.icon { .icon {
width: 18px; .task-menu-icon {
font-size: 18px; display: flex;
cursor: pointer; align-items: center;
&.completed { .ivu-icon {
color: $primary-color; font-size: 18px;
}
.loading {
width: 18px;
height: 18px;
.common-loading {
width: 16px;
height: 16px;
}
}
.uncomplete {
color: #888888;
}
} }
} }
.nav { .nav {
@ -78,8 +90,12 @@
} }
.menu { .menu {
margin-left: 12px; margin-left: 12px;
font-size: 22px; display: flex;
cursor: pointer; align-items: center;
.ivu-icon {
font-size: 22px;
color: #606266;
}
} }
} }
} }
@ -243,37 +259,22 @@
&:last-child { &:last-child {
margin-bottom: -6px; margin-bottom: -6px;
} }
&:hover {
.subtask-time {
.clock {
transform: translateX(0);
opacity: 0.8;
}
}
}
.subtask-icon { .subtask-icon {
width: 16px; width: 16px;
height: 26px; height: 26px;
line-height: 26px; line-height: 26px;
margin-right: 6px; margin-right: 6px;
display: flex;
align-items: center;
cursor: pointer; cursor: pointer;
.loading {
width: 16px;
height: 16px;
margin: 0;
padding: 2px;
}
.ivu-icon {
font-size: 16px;
color: #cccccc;
&.completed {
color: $primary-color;
}
}
&.sub-icon {
font-size: 16px;
width: 16px;
height: 16px;
margin-left: -20px;
margin-right: 4px;
color: #cfcfcf;
transition: transform 0.2s;
&.active {
transform: rotate(90deg);
}
}
} }
.subtask-name { .subtask-name {
flex: 1; flex: 1;
@ -309,6 +310,13 @@
color: #ff9900; color: #ff9900;
} }
} }
.clock {
margin: 3px 2px;
font-size: 20px;
transition: all 0.2s;
transform: translateX(50%);
opacity: 0;
}
} }
.subtask-avatar { .subtask-avatar {
height: 20px; height: 20px;

View File

@ -0,0 +1,50 @@
.task-menu-icon {
position: relative;
.loading {
width: 16px;
height: 16px;
display: flex;
align-items: center;
justify-content: center;
.common-loading {
margin: 0;
width: 14px;
height: 14px;
}
}
.ivu-icon {
cursor: pointer;
font-size: 16px;
color: #cccccc;
&.completed {
color: $primary-color;
}
}
}
.task-menu-more-dropdown {
> li {
.item {
display: flex;
align-items: center;
> i {
width: 18px;
height: 18px;
line-height: 18px;
font-size: 18px;
margin-right: 8px;
padding: 0;
color: #bbbbbb;
&.ivu-icon {
font-size: 16px;
}
}
}
}
}