feat(task): 增加解除任务关联功能

支持用户在任务详情中解除误关联的任务,权限与修改任务一致(项目负责人、任务负责人、任务协助人)。

- 新增 ProjectTaskRelation::deleteRelation() 删除双向关联并推送 WebSocket
- 新增 API POST /api/project/task/related/delete 接口
- 前端关联任务列表 hover 显示删除按钮,点击确认后解除关联

Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
kuaifan 2026-03-09 06:38:13 +00:00
parent 4068966700
commit 04708cedb6
5 changed files with 128 additions and 0 deletions

View File

@ -1987,6 +1987,44 @@ class ProjectController extends AbstractController
]);
}
/**
* @api {post} api/project/task/related/delete 删除任务关联
*
* @apiDescription 需要token身份项目、任务负责人
* @apiVersion 1.0.0
* @apiGroup project
* @apiName task__related__delete
*
* @apiParam {Number} task_id 任务ID
* @apiParam {Number} related_task_id 关联任务ID
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function task__related__delete()
{
User::auth();
//
$task_id = intval(Request::input('task_id'));
$related_task_id = intval(Request::input('related_task_id'));
if ($task_id <= 0 || $related_task_id <= 0) {
return Base::retError('参数错误');
}
//
$task = ProjectTask::userTask($task_id);
//
$project = Project::userProject($task->project_id);
ProjectPermission::userTaskPermission($project, ProjectPermission::TASK_UPDATE, $task);
//
$success = ProjectTaskRelation::deleteRelation($task_id, $related_task_id);
if (!$success) {
return Base::retError('关联不存在');
}
//
return Base::retSuccess('操作成功');
}
/**
* @api {get} api/project/task/content 获取任务详细描述
*

View File

@ -143,6 +143,41 @@ class ProjectTaskRelation extends AbstractModel
return true;
}
/**
* 删除双向任务关联
*
* @param int $taskId 任务ID
* @param int $relatedTaskId 关联任务ID
* @return bool 是否删除成功
*/
public static function deleteRelation(int $taskId, int $relatedTaskId): bool
{
// 删除正向关联
$deleted1 = static::whereTaskId($taskId)
->whereRelatedTaskId($relatedTaskId)
->delete();
// 删除反向关联
$deleted2 = static::whereTaskId($relatedTaskId)
->whereRelatedTaskId($taskId)
->delete();
if ($deleted1 || $deleted2) {
// 推送关联更新
$sourceTask = ProjectTask::with('project')->find($taskId);
$targetTask = ProjectTask::with('project')->find($relatedTaskId);
if ($sourceTask?->project) {
$sourceTask->pushMsg('relation', null, null, false);
}
if ($targetTask?->project) {
$targetTask->pushMsg('relation', null, null, false);
}
return true;
}
return false;
}
public static function recordMentionsFromMessage(WebSocketDialogMsg $msg): void
{
if ($msg->type !== 'text') {

View File

@ -376,6 +376,10 @@
class="related-status archived">
{{$L('已归档')}}
</span>
<Icon
type="md-close"
class="related-remove"
@click.native.stop="removeRelatedTask(item)"/>
</li>
</ul>
</FormItem>
@ -1597,6 +1601,26 @@ export default {
this.$store.dispatch('openTask', item.related_task_id);
},
removeRelatedTask(item) {
if (!item || !item.related_task_id) {
return;
}
$A.modalConfirm({
title: '温馨提示',
content: '确定要解除与任务 #' + item.related_task_id + ' 的关联吗?',
onOk: () => {
this.$store.dispatch('deleteTaskRelated', {
taskId: this.taskId,
relatedTaskId: item.related_task_id,
}).then(() => {
this.loadRelatedTasks();
}).catch(({msg}) => {
$A.modalError(msg);
});
},
});
},
onTaskRelationUpdate(taskId) {
if (!taskId || taskId !== this.taskId) {
return;

View File

@ -2635,6 +2635,19 @@ export default {
});
},
deleteTaskRelated({commit, dispatch}, {taskId, relatedTaskId}) {
return new Promise((resolve, reject) => {
dispatch("call", {
url: 'project/task/related/delete',
data: {task_id: taskId, related_task_id: relatedTaskId},
}).then(({msg}) => {
commit('task/related/clear', taskId);
commit('task/related/clear', relatedTaskId);
resolve(msg);
}).catch(reject);
});
},
/**
* 添加子任务
* @param dispatch

View File

@ -674,6 +674,24 @@
}
}
.related-remove {
flex-shrink: 0;
font-size: 14px;
color: #c5c8ce;
cursor: pointer;
opacity: 0;
transition: opacity 0.2s ease, color 0.2s ease;
margin-left: 4px;
&:hover {
color: #ed4014;
}
}
&:hover .related-remove {
opacity: 1;
}
.ivu-tag {
margin-left: 8px;
}