feat: 添加转移任务到别的项目功能

This commit is contained in:
weifashi 2023-11-15 16:28:19 +08:00
parent cdc7e671ce
commit 2305d30d35
5 changed files with 354 additions and 65 deletions

View File

@ -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. 工作流列表
*

View File

@ -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

View File

@ -478,4 +478,5 @@ Api接口文档
文件总大小已超过1GB,请分批下载
文件总大小已超过1GB请分批下载
保存任务详情至文件失败
保存任务详情至文件失败,请重试
保存任务详情至文件失败,请重试
移动成功

View 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>

View File

@ -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||'') ? '&#xe61d;' : '&#xe61c;'"></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||'') ? '&#xe61d;' : '&#xe61c;'"></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;
}
},