mirror of
https://github.com/kuaifan/dootask.git
synced 2025-12-12 19:35:50 +00:00
feat: 添加转移任务到别的项目功能
This commit is contained in:
parent
cdc7e671ce
commit
2305d30d35
@ -2196,6 +2196,47 @@ class ProjectController extends AbstractController
|
||||
return Base::retSuccess('success', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/project/task/move 35. 任务移动
|
||||
*
|
||||
* @apiDescription 需要token身份(限:项目、任务负责人)
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup project
|
||||
* @apiName task__move
|
||||
*
|
||||
* @apiParam {Number} task_id 任务ID
|
||||
* @apiParam {Number} project_id 项目ID
|
||||
* @apiParam {Number} column_id 列ID
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
* @apiSuccess {Object} data 返回数据
|
||||
*/
|
||||
public function task__move()
|
||||
{
|
||||
User::auth();
|
||||
//
|
||||
$task_id = intval(Request::input('task_id'));
|
||||
$project_id = intval(Request::input('project_id'));
|
||||
$column_id = intval(Request::input('column_id'));
|
||||
//
|
||||
$task = ProjectTask::userTask($task_id, true, true, 2);
|
||||
//
|
||||
if( $task->project_id == $project_id && $task->column_id == $column_id){
|
||||
return Base::retSuccess('移动成功', ['id' => $task_id]);
|
||||
}
|
||||
//
|
||||
$project = Project::userProject($project_id);
|
||||
$column = ProjectColumn::whereProjectId($project->id)->whereId($column_id)->first();
|
||||
if (empty($column)) {
|
||||
return Base::retError('列表不存在');
|
||||
}
|
||||
//
|
||||
$task->moveTask($project_id,$column_id);
|
||||
//
|
||||
return Base::retSuccess('移动成功', ['id' => $task_id]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/project/flow/list 38. 工作流列表
|
||||
*
|
||||
|
||||
@ -1665,6 +1665,46 @@ class ProjectTask extends AbstractModel
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 移动任务
|
||||
* @param int $project_id
|
||||
* @param int $column_id
|
||||
* @return bool
|
||||
*/
|
||||
public function moveTask(int $projectId, int $columnId)
|
||||
{
|
||||
AbstractModel::transaction(function () use($projectId, $columnId) {
|
||||
// 任务内容
|
||||
if($this->content){
|
||||
$this->content->project_id = $projectId;
|
||||
$this->content->save();
|
||||
}
|
||||
// 任务文件
|
||||
foreach ($this->taskFile as $taskFile){
|
||||
$taskFile->project_id = $projectId;
|
||||
$taskFile->save();
|
||||
}
|
||||
// 任务标签
|
||||
foreach ($this->taskTag as $taskTag){
|
||||
$taskTag->project_id = $projectId;
|
||||
$taskTag->save();
|
||||
}
|
||||
// 任务用户
|
||||
foreach ($this->taskUser as $taskUser){
|
||||
$taskUser->project_id = $projectId;
|
||||
$taskUser->save();
|
||||
}
|
||||
//
|
||||
$this->project_id = $projectId;
|
||||
$this->column_id = $columnId;
|
||||
$this->save();
|
||||
//
|
||||
$this->addLog("移动{任务}");
|
||||
});
|
||||
$this->pushMsg('update');
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取任务
|
||||
* @param $task_id
|
||||
|
||||
@ -478,4 +478,5 @@ Api接口文档
|
||||
文件总大小已超过1GB,请分批下载
|
||||
文件总大小已超过1GB,请分批下载
|
||||
保存任务详情至文件失败
|
||||
保存任务详情至文件失败,请重试
|
||||
保存任务详情至文件失败,请重试
|
||||
移动成功
|
||||
177
resources/assets/js/pages/manage/components/TaskMove.vue
Normal file
177
resources/assets/js/pages/manage/components/TaskMove.vue
Normal file
@ -0,0 +1,177 @@
|
||||
<template>
|
||||
<div class="task-add">
|
||||
<Cascader
|
||||
v-model="cascader"
|
||||
:data="cascaderData"
|
||||
:clearable="false"
|
||||
:placeholder="$L('请选择项目')"
|
||||
:load-data="cascaderLoadData"
|
||||
@on-input-change="cascaderInputChange"
|
||||
@on-visible-change="cascaderShow=!cascaderShow"
|
||||
filterable/>
|
||||
<div class="ivu-modal-footer">
|
||||
<div class="adaption">
|
||||
<Button type="default" @click="close">{{$L('取消')}}</Button>
|
||||
<Button type="primary" :loading="loadIng > 0" @click="onConfirm">{{$L('确定')}}</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {mapState} from "vuex";
|
||||
|
||||
export default {
|
||||
name: "TaskMove",
|
||||
props: {
|
||||
value: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
task: {
|
||||
type: Object,
|
||||
default: false
|
||||
},
|
||||
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
cascader: [],
|
||||
cascaderShow: false,
|
||||
cascaderData: [],
|
||||
cascaderValue: '',
|
||||
cascaderLoading: 0,
|
||||
cascaderAlready: [],
|
||||
|
||||
loadIng: 0,
|
||||
beforeClose: [],
|
||||
}
|
||||
},
|
||||
|
||||
async mounted() {
|
||||
this.initCascaderData();
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
this.beforeClose.some(func => {
|
||||
typeof func === "function" && func()
|
||||
})
|
||||
this.beforeClose = [];
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapState(['cacheProjects', 'cacheColumns']),
|
||||
},
|
||||
|
||||
watch: {
|
||||
task: {
|
||||
handler: function (val) {
|
||||
this.cascader = [val.project_id, val.column_id];
|
||||
},
|
||||
deep: true,
|
||||
immediate: true
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
/**
|
||||
* 初始化级联数据
|
||||
*/
|
||||
initCascaderData() {
|
||||
const data = $A.cloneJSON(this.cacheProjects).sort((a, b) => {
|
||||
if (a.top_at || b.top_at) {
|
||||
return $A.Date(b.top_at) - $A.Date(a.top_at);
|
||||
}
|
||||
return b.id - a.id;
|
||||
});
|
||||
this.cascaderData = data.map(project => {
|
||||
const children = this.cacheColumns.filter(({project_id}) => project_id == project.id).map(column => {
|
||||
return {
|
||||
value: column.id,
|
||||
label: column.name
|
||||
}
|
||||
});
|
||||
const data = {
|
||||
value: project.id,
|
||||
label: project.name,
|
||||
children,
|
||||
};
|
||||
if (children.length == 0) {
|
||||
data.loading = false;
|
||||
}
|
||||
return data
|
||||
});
|
||||
},
|
||||
|
||||
cascaderLoadData(item, callback) {
|
||||
item.loading = true;
|
||||
this.$store.dispatch("getColumns", item.value).then((data) => {
|
||||
item.children = data.map(column => {
|
||||
return {
|
||||
value: column.id,
|
||||
label: column.name
|
||||
}
|
||||
});
|
||||
item.loading = false;
|
||||
callback();
|
||||
}).catch(() => {
|
||||
item.loading = false;
|
||||
callback();
|
||||
});
|
||||
},
|
||||
|
||||
cascaderInputChange(key) {
|
||||
this.cascaderValue = key || "";
|
||||
//
|
||||
if (this.cascaderAlready[this.cascaderValue] === true) {
|
||||
return;
|
||||
}
|
||||
this.cascaderAlready[this.cascaderValue] = true;
|
||||
//
|
||||
setTimeout(() => {
|
||||
this.cascaderLoading++;
|
||||
}, 1000)
|
||||
this.$store.dispatch("getProjects", {
|
||||
keys: {
|
||||
name: this.cascaderValue,
|
||||
},
|
||||
getcolumn: 'yes'
|
||||
}).then(() => {
|
||||
this.cascaderLoading--;
|
||||
this.initCascaderData();
|
||||
}).catch(() => {
|
||||
this.cascaderLoading--;
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
async onConfirm() {
|
||||
this.loadIng++;
|
||||
this.$store.dispatch("call", {
|
||||
url: "project/task/move",
|
||||
data: {
|
||||
task_id: this.task.id,
|
||||
project_id: this.cascader[0],
|
||||
column_id: this.cascader[1],
|
||||
}
|
||||
}).then(({msg}) => {
|
||||
this.loadIng--;
|
||||
this.$store.dispatch("saveTask", {
|
||||
id: this.task.id,
|
||||
project_id: this.cascader[0],
|
||||
column_id: this.cascader[1],
|
||||
});
|
||||
$A.messageSuccess(msg);
|
||||
this.close()
|
||||
}).catch(({msg}) => {
|
||||
this.loadIng--;
|
||||
$A.modalError(msg);
|
||||
})
|
||||
},
|
||||
|
||||
close() {
|
||||
this.$emit("input", !this.value)
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -1,80 +1,104 @@
|
||||
<template>
|
||||
<EDropdown
|
||||
ref="dropdown"
|
||||
trigger="click"
|
||||
:disabled="disabled"
|
||||
:size="size"
|
||||
:style="styles"
|
||||
class="task-operation-dropdown"
|
||||
placement="bottom"
|
||||
@command="dropTask"
|
||||
@visible-change="visibleChange">
|
||||
<div ref="icon" class="task-operation-icon"></div>
|
||||
<EDropdownMenu ref="dropdownMenu" slot="dropdown" class="task-operation-more-dropdown">
|
||||
<li class="task-operation-more-warp" :class="size">
|
||||
<ul>
|
||||
<EDropdownItem v-if="!flow" class="load-flow" disabled>
|
||||
<div class="load-flow-warp">
|
||||
<Loading/>
|
||||
</div>
|
||||
</EDropdownItem>
|
||||
<template v-else-if="turns.length > 0">
|
||||
<EDropdownItem v-for="item in turns" :key="item.id" :command="`turn::${item.id}`">
|
||||
<div class="item flow">
|
||||
<Icon v-if="item.id == task.flow_item_id && flow.auto_assign !== true" class="check" type="md-checkmark-circle-outline" />
|
||||
<Icon v-else type="md-radio-button-off" />
|
||||
<div class="flow-name" :class="item.status">{{item.name}}</div>
|
||||
<div>
|
||||
<EDropdown
|
||||
ref="dropdown"
|
||||
trigger="click"
|
||||
:disabled="disabled"
|
||||
:size="size"
|
||||
:style="styles"
|
||||
class="task-operation-dropdown"
|
||||
placement="bottom"
|
||||
@command="dropTask"
|
||||
@visible-change="visibleChange">
|
||||
<div ref="icon" class="task-operation-icon"></div>
|
||||
<EDropdownMenu ref="dropdownMenu" slot="dropdown" class="task-operation-more-dropdown">
|
||||
<li class="task-operation-more-warp" :class="size">
|
||||
<ul>
|
||||
<EDropdownItem v-if="!flow" class="load-flow" disabled>
|
||||
<div class="load-flow-warp">
|
||||
<Loading/>
|
||||
</div>
|
||||
</EDropdownItem>
|
||||
</template>
|
||||
<template v-else>
|
||||
<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>
|
||||
</template>
|
||||
|
||||
<template v-if="task.parent_id === 0">
|
||||
<EDropdownItem :divided="turns.length > 0" command="archived">
|
||||
<div class="item">
|
||||
<Icon type="ios-filing" />{{$L(task.archived_at ? '还原归档' : '归档')}}
|
||||
</div>
|
||||
</EDropdownItem>
|
||||
<EDropdownItem command="remove">
|
||||
<div class="item hover-del">
|
||||
<Icon type="md-trash" />{{$L('删除')}}
|
||||
</div>
|
||||
</EDropdownItem>
|
||||
<template v-if="colorShow">
|
||||
<EDropdownItem v-for="(c, k) in taskColorList" :key="'c_' + k" :divided="k==0" :command="c">
|
||||
<div class="item">
|
||||
<i class="taskfont" :style="{color:c.primary||'#ddd'}" v-html="c.color == (task.color||'') ? '' : ''"></i>{{$L(c.name)}}
|
||||
<template v-else-if="turns.length > 0">
|
||||
<EDropdownItem v-for="item in turns" :key="item.id" :command="`turn::${item.id}`">
|
||||
<div class="item flow">
|
||||
<Icon v-if="item.id == task.flow_item_id && flow.auto_assign !== true" class="check" type="md-checkmark-circle-outline" />
|
||||
<Icon v-else type="md-radio-button-off" />
|
||||
<div class="flow-name" :class="item.status">{{item.name}}</div>
|
||||
</div>
|
||||
</EDropdownItem>
|
||||
</template>
|
||||
</template>
|
||||
<EDropdownItem v-else command="remove" :divided="turns.length > 0">
|
||||
<div class="item">
|
||||
<Icon type="md-trash" />{{$L('删除')}}
|
||||
</div>
|
||||
</EDropdownItem>
|
||||
</ul>
|
||||
</li>
|
||||
</EDropdownMenu>
|
||||
</EDropdown>
|
||||
<template v-else>
|
||||
<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>
|
||||
</template>
|
||||
|
||||
<template v-if="task.parent_id === 0">
|
||||
<EDropdownItem :divided="turns.length > 0" command="archived">
|
||||
<div class="item">
|
||||
<Icon type="ios-filing" />{{$L(task.archived_at ? '还原归档' : '归档')}}
|
||||
</div>
|
||||
</EDropdownItem>
|
||||
<EDropdownItem command="move">
|
||||
<div class="item">
|
||||
<Icon type="md-move" />{{$L('移动')}}
|
||||
</div>
|
||||
</EDropdownItem>
|
||||
<EDropdownItem command="remove">
|
||||
<div class="item hover-del">
|
||||
<Icon type="md-trash" />{{$L('删除')}}
|
||||
</div>
|
||||
</EDropdownItem>
|
||||
<template v-if="colorShow">
|
||||
<EDropdownItem v-for="(c, k) in taskColorList" :key="'c_' + k" :divided="k==0" :command="c">
|
||||
<div class="item">
|
||||
<i class="taskfont" :style="{color:c.primary||'#ddd'}" v-html="c.color == (task.color||'') ? '' : ''"></i>{{$L(c.name)}}
|
||||
</div>
|
||||
</EDropdownItem>
|
||||
</template>
|
||||
</template>
|
||||
<EDropdownItem v-else command="remove" :divided="turns.length > 0">
|
||||
<div class="item">
|
||||
<Icon type="md-trash" />{{$L('删除')}}
|
||||
</div>
|
||||
</EDropdownItem>
|
||||
</ul>
|
||||
</li>
|
||||
</EDropdownMenu>
|
||||
</EDropdown>
|
||||
|
||||
<!--移动任务-->
|
||||
<Modal
|
||||
v-model="moveTaskShow"
|
||||
:title="$L('移动任务')"
|
||||
:mask-closable="false"
|
||||
:styles="{
|
||||
width: '90%',
|
||||
maxWidth: '540px'
|
||||
}"
|
||||
footer-hide>
|
||||
<TaskMove ref="addTask" v-model="moveTaskShow" :task="task"/>
|
||||
</Modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {mapGetters, mapState} from "vuex";
|
||||
import TaskMove from "./TaskMove";
|
||||
|
||||
export default {
|
||||
name: "TaskOperation",
|
||||
components: {
|
||||
TaskMove,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
task: {},
|
||||
@ -88,6 +112,8 @@ export default {
|
||||
element: null,
|
||||
target: null,
|
||||
styles: {},
|
||||
|
||||
moveTaskShow: false,
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
@ -251,6 +277,10 @@ export default {
|
||||
case 'remove':
|
||||
this.archivedOrRemoveTask(command);
|
||||
break;
|
||||
|
||||
case 'move':
|
||||
this.moveTaskShow = true;
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user