diff --git a/app/Http/Controllers/Api/ProjectController.php b/app/Http/Controllers/Api/ProjectController.php index 178beca24..59ba74b57 100755 --- a/app/Http/Controllers/Api/ProjectController.php +++ b/app/Http/Controllers/Api/ProjectController.php @@ -311,6 +311,7 @@ class ProjectController extends AbstractController * @apiParam {Number} [archive_days] 自动归档天数 * @apiParam {String} [ai_auto_analyze] AI自动分析(open|close) * @apiParam {String} [task_template_share] 共享模板(open|close) + * @apiParam {String} [department_owner_view] 部门负责人视角可见(open|close) * * @apiSuccess {Number} ret 返回状态码(1正确、0错误) * @apiSuccess {String} msg 返回信息(错误描述) @@ -327,6 +328,7 @@ class ProjectController extends AbstractController $archive_days = intval(Request::input('archive_days')); $ai_auto_analyze = Request::input('ai_auto_analyze'); $task_template_share = Request::input('task_template_share'); + $department_owner_view = Request::input('department_owner_view'); if (mb_strlen($name) < 2) { return Base::retError('项目名称不可以少于2个字'); } elseif (mb_strlen($name) > 32) { @@ -342,7 +344,7 @@ class ProjectController extends AbstractController } // $project = Project::userProject($project_id, true, true); - AbstractModel::transaction(function () use ($archive_days, $archive_method, $ai_auto_analyze, $task_template_share, $desc, $name, $project) { + AbstractModel::transaction(function () use ($archive_days, $archive_method, $ai_auto_analyze, $task_template_share, $department_owner_view, $desc, $name, $project) { if ($project->name != $name) { $project->addLog("修改项目名称", [ 'change' => [$project->name, $name] @@ -380,6 +382,12 @@ class ProjectController extends AbstractController ]); $project->task_template_share = $task_template_share; } + if (in_array($department_owner_view, ['open', 'close']) && $project->department_owner_view != $department_owner_view) { + $project->addLog("修改负责人视角可见", [ + 'change' => [$project->department_owner_view, $department_owner_view] + ]); + $project->department_owner_view = $department_owner_view; + } $project->save(); }); $project->pushMsg('update'); @@ -1406,15 +1414,12 @@ class ProjectController extends AbstractController $query->on('project_sub_task_visibility_users.task_id', '=', 'project_tasks.parent_id'); $query->where('project_sub_task_visibility_users.userid', $userid); }); - $builder->where(function ($query) use ($userid, $departmentView) { + $builder->where(function ($query) use ($userid) { $query->where("project_tasks.visibility", 1); $query->orWhere("project_users.userid", $userid); $query->orWhere("project_task_users.userid", $userid); $query->orWhere("project_task_visibility_users.userid", $userid); $query->orWhere("project_sub_task_visibility_users.userid", $userid); - if ($departmentView['enabled']) { - $query->orWhereIn('project_tasks.project_id', $departmentView['project_ids']); - } }); // 优化子查询汇总 $builder->leftJoinSub(function ($query) { @@ -2048,7 +2053,7 @@ class ProjectController extends AbstractController $projectOwnerids = ProjectUser::whereProjectId($task->project_id) ->whereIn('owner', [ProjectUser::OWNER_PRIMARY, ProjectUser::OWNER_DEPUTY]) ->pluck('userid')->map(fn($v) => (int)$v)->toArray(); // 项目负责人(含项目管理员) - if (!UserDepartment::isDepartmentReadonlyProject($departmentView, intval($task->project_id)) && $task->visibility != 1 && !in_array($user->userid, $projectOwnerids)) { + if ($task->visibility != 1 && !in_array($user->userid, $projectOwnerids)) { $taskUserids = ProjectTaskUser::whereTaskId($task_id)->pluck('userid')->toArray(); //任务负责人、协助人 $subTaskUserids = ProjectTaskUser::whereTaskPid($task_id)->pluck('userid')->toArray(); //子任务负责人、协助人 $visibleUserids = ProjectTaskVisibilityUser::whereTaskId($task_id)->pluck('userid')->toArray(); //可见人 diff --git a/app/Models/Project.php b/app/Models/Project.php index b1c6fb20b..570783eb7 100644 --- a/app/Models/Project.php +++ b/app/Models/Project.php @@ -24,6 +24,7 @@ use Request; * @property int|null $archive_days 自动归档天数 * @property string|null $ai_auto_analyze AI自动分析 * @property string|null $task_template_share 共享模板开关 + * @property string|null $department_owner_view 部门负责人视角可见开关 * @property string|null $user_simple 成员总数|1,2,3 * @property int|null $dialog_id 聊天会话ID * @property \Illuminate\Support\Carbon|null $archived_at 归档时间 diff --git a/app/Models/ProjectTask.php b/app/Models/ProjectTask.php index 001274475..f12c1ef79 100644 --- a/app/Models/ProjectTask.php +++ b/app/Models/ProjectTask.php @@ -2278,7 +2278,8 @@ class ProjectTask extends AbstractModel $builder->withTrashed(); } $task = $builder->first(); - if (!empty($task) && UserDepartment::isDepartmentReadonlyProject($departmentView, intval($task->project_id))) { + // 仅"全员可见"(visibility=1)的任务走负责人只读视角;指定成员可见的任务交由 userTask 按可见性校验 + if (!empty($task) && intval($task->visibility) === 1 && UserDepartment::isDepartmentReadonlyProject($departmentView, intval($task->project_id))) { if ($archived === true && $task->archived_at != null) { throw new ApiException('任务已归档', ['task_id' => $task_id]); } diff --git a/app/Models/UserDepartment.php b/app/Models/UserDepartment.php index 77f708633..6ff9d57ec 100644 --- a/app/Models/UserDepartment.php +++ b/app/Models/UserDepartment.php @@ -567,10 +567,17 @@ class UserDepartment extends AbstractModel if (empty($memberUserids)) { return $empty; } - $projectIds = ProjectUser::whereIn('userid', $memberUserids) - ->pluck('project_id') + // 项目可单独关闭"部门负责人视角可见",关闭后对负责人隐藏(含项目和任务群聊) + $projectIds = ProjectUser::whereIn('project_users.userid', $memberUserids) + ->join('projects', 'projects.id', '=', 'project_users.project_id') + ->whereNull('projects.deleted_at') + ->where(function ($query) { + $query->where('projects.department_owner_view', '<>', 'close') + ->orWhereNull('projects.department_owner_view'); + }) + ->distinct() + ->pluck('projects.id') ->map(fn($v) => intval($v)) - ->unique() ->values() ->toArray(); $ownProjectIds = ProjectUser::whereUserid($user->userid) diff --git a/app/Models/WebSocketDialog.php b/app/Models/WebSocketDialog.php index 1af0f0c22..554fd68e4 100644 --- a/app/Models/WebSocketDialog.php +++ b/app/Models/WebSocketDialog.php @@ -904,15 +904,19 @@ class WebSocketDialog extends AbstractModel case 'project': case 'task': // 项目群、任务群对话校验是否在项目内 + $taskVisibility = 1; // 项目群不涉及任务可见性,按可见处理 if ($dialog->group_type === 'project') { $projectId = intval(Project::whereDialogId($dialog->id)->value('id')); } else { - $projectId = intval(ProjectTask::whereDialogId($dialog->id)->value('project_id')); + $taskRow = ProjectTask::select(['project_id', 'visibility'])->whereDialogId($dialog->id)->first(); + $projectId = intval($taskRow?->project_id); + $taskVisibility = intval($taskRow?->visibility); } if ($projectId > 0 && ProjectUser::whereProjectId($projectId)->whereUserid($userid)->exists()) { return $dialog; } - if ($projectId > 0 && $checkOwner === false) { + // 部门负责人只读视角:项目群放行;任务群仅"全员可见"任务放行,指定成员可见任务不放行 + if ($projectId > 0 && $checkOwner === false && ($dialog->group_type === 'project' || $taskVisibility === 1)) { $departmentView = UserDepartment::ownerViewContext(User::auth(), true); if (UserDepartment::isDepartmentReadonlyProject($departmentView, $projectId)) { return $dialog; diff --git a/database/migrations/2026_05_21_000001_add_department_owner_view_to_projects_table.php b/database/migrations/2026_05_21_000001_add_department_owner_view_to_projects_table.php new file mode 100644 index 000000000..fe2babbaf --- /dev/null +++ b/database/migrations/2026_05_21_000001_add_department_owner_view_to_projects_table.php @@ -0,0 +1,26 @@ +string('department_owner_view', 20)->default('open')->after('task_template_share')->comment('部门负责人视角可见开关'); + } + }); + } + + public function down() + { + Schema::table('projects', function (Blueprint $table) { + if (Schema::hasColumn('projects', 'department_owner_view')) { + $table->dropColumn('department_owner_view'); + } + }); + } +} diff --git a/language/original-api.txt b/language/original-api.txt index f87439055..211a2db20 100644 --- a/language/original-api.txt +++ b/language/original-api.txt @@ -994,3 +994,4 @@ LDAP 用户缺少邮箱属性,请联系管理员配置 该用户不存在 无权操作此模板 修改共享模板 +修改负责人视角可见 diff --git a/language/original-web.txt b/language/original-web.txt index 5c852cc2b..ee89ee421 100644 --- a/language/original-web.txt +++ b/language/original-web.txt @@ -2392,3 +2392,6 @@ AI任务分析 开启后,添加任务时可使用其他项目共享的任务模板。 关闭后,添加任务时仅加载本项目模板,不显示其他项目共享模板。 根据系统设置的自动归档规则执行 +负责人视角 +开启后,部门负责人可只读查看本项目及其全员可见任务。 +关闭后,本项目及其群聊对部门负责人视角隐藏。 diff --git a/resources/assets/js/pages/manage/components/ProjectPanel.vue b/resources/assets/js/pages/manage/components/ProjectPanel.vue index 707a91325..4ca607437 100644 --- a/resources/assets/js/pages/manage/components/ProjectPanel.vue +++ b/resources/assets/js/pages/manage/components/ProjectPanel.vue @@ -452,6 +452,14 @@
{{$L('开启后,添加任务时可使用其他项目共享的任务模板。')}}
{{$L('关闭后,添加任务时仅加载本项目模板,不显示其他项目共享模板。')}}
+ + + {{$L('开启')}} + {{$L('关闭')}} + +
{{$L('开启后,部门负责人可只读查看本项目及其全员可见任务。')}}
+
{{$L('关闭后,本项目及其群聊对部门负责人视角隐藏。')}}
+
@@ -1665,7 +1673,8 @@ export default { archive_method: this.projectData.archive_method, archive_days: this.projectData.archive_days, ai_auto_analyze: this.projectData.ai_auto_analyze || 'open', - task_template_share: this.projectData.task_template_share || 'open' + task_template_share: this.projectData.task_template_share || 'open', + department_owner_view: this.projectData.department_owner_view || 'open' }); this.settingShow = true; this.$nextTick(() => { diff --git a/resources/assets/js/pages/manage/components/TaskDetail.vue b/resources/assets/js/pages/manage/components/TaskDetail.vue index df8a2b0e1..c3b6c3ce8 100755 --- a/resources/assets/js/pages/manage/components/TaskDetail.vue +++ b/resources/assets/js/pages/manage/components/TaskDetail.vue @@ -117,7 +117,7 @@
- {{$L('当前为负责人视角:你可查看任务内容、动态和附件,并参与讨论,但不能编辑任务。')}} + {{$L('当前为负责人 ,并参与讨论,但不能编辑任务。')}} {{$L('该任务尚未被领取,点击这里')}}