From 27c136c9060d46772cf728292703efd9f53c4b41 Mon Sep 17 00:00:00 2001 From: hongawen <83944980@qq.com> Date: Tue, 14 Jul 2026 11:24:49 +0800 Subject: [PATCH] =?UTF-8?q?fix(projects):=201=E3=80=81=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E7=AE=A1=E4=BF=8A=E6=8F=90=E7=9A=84=E5=B7=A5=E6=97=B6=E5=A1=AB?= =?UTF-8?q?=E6=8A=A5=E5=AE=8C=E6=AF=95=E5=90=8E=EF=BC=8C=E6=8F=90=E7=A4=BA?= =?UTF-8?q?=E7=9A=84404=E6=8A=A5=E9=94=99=EF=BC=9B2=E3=80=81=E8=B0=83?= =?UTF-8?q?=E6=95=B4=E6=89=A7=E8=A1=8C=E5=8D=8F=E5=8A=9E=E4=BA=BA=E5=8F=AF?= =?UTF-8?q?=E4=BB=A5=E7=BC=96=E8=BE=91=E5=8D=8F=E5=8A=9E=E4=BA=BA=E5=88=97?= =?UTF-8?q?=E8=A1=A8=E7=9A=84=E6=93=8D=E4=BD=9C=EF=BC=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../composables/task-permission-rules.ts | 5 ++++- .../composables/use-task-completion-cascade.ts | 18 +++++++++--------- .../composables/use-task-permissions.ts | 16 ++++++++++++++-- .../execution/modules/task-workspace.vue | 1 - 4 files changed, 27 insertions(+), 13 deletions(-) diff --git a/src/views/project/project/execution/composables/task-permission-rules.ts b/src/views/project/project/execution/composables/task-permission-rules.ts index 97c4388..a0f3854 100644 --- a/src/views/project/project/execution/composables/task-permission-rules.ts +++ b/src/views/project/project/execution/composables/task-permission-rules.ts @@ -29,7 +29,10 @@ function isTaskMutationActor(task: Api.Project.ProjectTask, identity: TaskMutati ); } -function isExecutionMutationActor(execution: Api.Project.ProjectExecution, identity: TaskMutationIdentity): boolean { +export function isExecutionMutationActor( + execution: Api.Project.ProjectExecution, + identity: TaskMutationIdentity +): boolean { const { currentUserId, projectManagerUserId } = identity; if (!currentUserId) return false; diff --git a/src/views/project/project/execution/composables/use-task-completion-cascade.ts b/src/views/project/project/execution/composables/use-task-completion-cascade.ts index 1ba7d8a..6b66347 100644 --- a/src/views/project/project/execution/composables/use-task-completion-cascade.ts +++ b/src/views/project/project/execution/composables/use-task-completion-cascade.ts @@ -12,7 +12,6 @@ export interface CascadeTriggerPayload { export interface UseTaskCompletionCascadeOptions { projectId: ComputedRef; - executionId: ComputedRef; /** 由调用方提供:打开 StatusActionDialog 的钩子;composable 不持有 dialog 实例 */ openStatusActionDialog: (task: ProjectTask, action: TaskAction, fromCascade: boolean) => void; /** 从 task 的 availableActions 里找出"完成"动作;找不到返回 null */ @@ -74,7 +73,7 @@ export function useTaskCompletionCascade(options: UseTaskCompletionCascadeOption return; } - const siblings = await loadSiblings(completedTask.parentTaskId); + const siblings = await loadSiblings(completedTask.parentTaskId, completedTask.executionId); if (siblings === null) { window.$message?.warning('父任务级联检查失败'); return; @@ -84,7 +83,7 @@ export function useTaskCompletionCascade(options: UseTaskCompletionCascadeOption return; } - const parent = await fetchTaskDetail(completedTask.parentTaskId); + const parent = await fetchTaskDetail(completedTask.parentTaskId, completedTask.executionId); if (!parent) { window.$message?.warning('父任务级联检查失败'); return; @@ -122,7 +121,8 @@ export function useTaskCompletionCascade(options: UseTaskCompletionCascadeOption } try { - const result = await fetchGetProjectTaskWorklogPage(options.projectId.value, options.executionId.value, task.id, { + // 跨执行视角下 workspace 没有执行上下文,级联链路一律取任务自身的 executionId + const result = await fetchGetProjectTaskWorklogPage(options.projectId.value, task.executionId, task.id, { pageNo: 1, pageSize: -1 }); @@ -153,10 +153,10 @@ export function useTaskCompletionCascade(options: UseTaskCompletionCascadeOption } } - /** 拉父级下所有子任务;接口失败返回 null(与"空列表"区分,由调用方降级处理) */ - async function loadSiblings(parentTaskId: string): Promise { + /** 拉父级下所有子任务;接口失败返回 null(与"空列表"区分,由调用方降级处理)。父子任务同执行,executionId 取被完成任务自身的值 */ + async function loadSiblings(parentTaskId: string, executionId: string): Promise { try { - const result = await fetchGetProjectTaskPage(options.projectId.value, options.executionId.value, { + const result = await fetchGetProjectTaskPage(options.projectId.value, executionId, { pageNo: 1, pageSize: -1, parentTaskId @@ -171,9 +171,9 @@ export function useTaskCompletionCascade(options: UseTaskCompletionCascadeOption } /** 拉父任务详情;失败返回 null */ - async function fetchTaskDetail(taskId: string): Promise { + async function fetchTaskDetail(taskId: string, executionId: string): Promise { try { - const result = await fetchGetProjectTask(options.projectId.value, options.executionId.value, taskId); + const result = await fetchGetProjectTask(options.projectId.value, executionId, taskId); if (result.error || !result.data) { return null; } diff --git a/src/views/project/project/execution/composables/use-task-permissions.ts b/src/views/project/project/execution/composables/use-task-permissions.ts index c383b58..59dc4f9 100644 --- a/src/views/project/project/execution/composables/use-task-permissions.ts +++ b/src/views/project/project/execution/composables/use-task-permissions.ts @@ -6,6 +6,7 @@ import { canDeleteTaskByIdentity, canEditExecutionByIdentity, canEditTaskByIdentity, + isExecutionMutationActor, isMutable } from './task-permission-rules'; @@ -54,13 +55,24 @@ export function useTaskPermissions() { }); } + /** 协办人弹窗内的写操作(设为负责人 / 失效 / 新增协办人)与编辑同一身份口径:执行负责人或项目负责人 */ function canChangeExecutionOwner(execution: Api.Project.ProjectExecution): boolean { - return isMutable(execution) && hasPermission('project:execution:owner'); + return ( + isMutable(execution) && + isExecutionMutationActor(execution, { + currentUserId: currentUserId.value, + projectManagerUserId: currentProjectManagerUserId.value + }) + ); } function canManageExecutionAssignee(execution: Api.Project.ProjectExecution): boolean { return ( - isMutable(execution) && (hasPermission('project:execution:assignee') || currentUserId.value === execution.ownerId) + isMutable(execution) && + isExecutionMutationActor(execution, { + currentUserId: currentUserId.value, + projectManagerUserId: currentProjectManagerUserId.value + }) ); } diff --git a/src/views/project/project/execution/modules/task-workspace.vue b/src/views/project/project/execution/modules/task-workspace.vue index 34f8a69..3e36d5a 100644 --- a/src/views/project/project/execution/modules/task-workspace.vue +++ b/src/views/project/project/execution/modules/task-workspace.vue @@ -539,7 +539,6 @@ async function handleOperateSubmit(payload: Api.Project.SaveProjectTaskParams) { const cascade = useTaskCompletionCascade({ projectId: computed(() => props.projectId), - executionId, openStatusActionDialog: async (task, action, fromCascade) => { currentTask.value = task; currentStatusAction.value = action;