mirror of
https://github.com/kuaifan/dootask.git
synced 2025-12-15 13:22:49 +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);
|
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. 工作流列表
|
* @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
|
* @param $task_id
|
||||||
|
|||||||
@ -478,4 +478,5 @@ Api接口文档
|
|||||||
文件总大小已超过1GB,请分批下载
|
文件总大小已超过1GB,请分批下载
|
||||||
文件总大小已超过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>
|
<template>
|
||||||
<EDropdown
|
<div>
|
||||||
ref="dropdown"
|
<EDropdown
|
||||||
trigger="click"
|
ref="dropdown"
|
||||||
:disabled="disabled"
|
trigger="click"
|
||||||
:size="size"
|
:disabled="disabled"
|
||||||
:style="styles"
|
:size="size"
|
||||||
class="task-operation-dropdown"
|
:style="styles"
|
||||||
placement="bottom"
|
class="task-operation-dropdown"
|
||||||
@command="dropTask"
|
placement="bottom"
|
||||||
@visible-change="visibleChange">
|
@command="dropTask"
|
||||||
<div ref="icon" class="task-operation-icon"></div>
|
@visible-change="visibleChange">
|
||||||
<EDropdownMenu ref="dropdownMenu" slot="dropdown" class="task-operation-more-dropdown">
|
<div ref="icon" class="task-operation-icon"></div>
|
||||||
<li class="task-operation-more-warp" :class="size">
|
<EDropdownMenu ref="dropdownMenu" slot="dropdown" class="task-operation-more-dropdown">
|
||||||
<ul>
|
<li class="task-operation-more-warp" :class="size">
|
||||||
<EDropdownItem v-if="!flow" class="load-flow" disabled>
|
<ul>
|
||||||
<div class="load-flow-warp">
|
<EDropdownItem v-if="!flow" class="load-flow" disabled>
|
||||||
<Loading/>
|
<div class="load-flow-warp">
|
||||||
</div>
|
<Loading/>
|
||||||
</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>
|
</div>
|
||||||
</EDropdownItem>
|
</EDropdownItem>
|
||||||
</template>
|
<template v-else-if="turns.length > 0">
|
||||||
<template v-else>
|
<EDropdownItem v-for="item in turns" :key="item.id" :command="`turn::${item.id}`">
|
||||||
<EDropdownItem v-if="task.complete_at" command="uncomplete">
|
<div class="item flow">
|
||||||
<div class="item red">
|
<Icon v-if="item.id == task.flow_item_id && flow.auto_assign !== true" class="check" type="md-checkmark-circle-outline" />
|
||||||
<Icon type="md-checkmark-circle-outline" />{{$L('标记未完成')}}
|
<Icon v-else type="md-radio-button-off" />
|
||||||
</div>
|
<div class="flow-name" :class="item.status">{{item.name}}</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)}}
|
|
||||||
</div>
|
</div>
|
||||||
</EDropdownItem>
|
</EDropdownItem>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
<template v-else>
|
||||||
<EDropdownItem v-else command="remove" :divided="turns.length > 0">
|
<EDropdownItem v-if="task.complete_at" command="uncomplete">
|
||||||
<div class="item">
|
<div class="item red">
|
||||||
<Icon type="md-trash" />{{$L('删除')}}
|
<Icon type="md-checkmark-circle-outline" />{{$L('标记未完成')}}
|
||||||
</div>
|
</div>
|
||||||
</EDropdownItem>
|
</EDropdownItem>
|
||||||
</ul>
|
<EDropdownItem v-else command="complete">
|
||||||
</li>
|
<div class="item">
|
||||||
</EDropdownMenu>
|
<Icon type="md-radio-button-off" />{{$L('完成')}}
|
||||||
</EDropdown>
|
</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>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import {mapGetters, mapState} from "vuex";
|
import {mapGetters, mapState} from "vuex";
|
||||||
|
import TaskMove from "./TaskMove";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "TaskOperation",
|
name: "TaskOperation",
|
||||||
|
components: {
|
||||||
|
TaskMove,
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
task: {},
|
task: {},
|
||||||
@ -88,6 +112,8 @@ export default {
|
|||||||
element: null,
|
element: null,
|
||||||
target: null,
|
target: null,
|
||||||
styles: {},
|
styles: {},
|
||||||
|
|
||||||
|
moveTaskShow: false,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
@ -251,6 +277,10 @@ export default {
|
|||||||
case 'remove':
|
case 'remove':
|
||||||
this.archivedOrRemoveTask(command);
|
this.archivedOrRemoveTask(command);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'move':
|
||||||
|
this.moveTaskShow = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user