refactor(manage): 收口部门负责人ID规范化逻辑并简化后端对话可见性校验

- 后端:任务群/项目群统一按项目级共享判断,不再区分任务可见性
- 前端:新增 department/owner/ids/save mutation 及 normalizeIntArray 工具函数
- 前端:departmentOwnerReadonlyUrls 从 action 局部变量提升至 state
- 前端:修复 TaskDetail 提示文本多余空格

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
kuaifan 2026-05-21 06:06:39 +00:00
parent 8cd4669b90
commit ef7293704b
9 changed files with 59 additions and 43 deletions

View File

@ -904,19 +904,16 @@ class WebSocketDialog extends AbstractModel
case 'project':
case 'task':
// 项目群、任务群对话校验是否在项目内
$taskVisibility = 1; // 项目群不涉及任务可见性,按可见处理
if ($dialog->group_type === 'project') {
$projectId = intval(Project::whereDialogId($dialog->id)->value('id'));
} else {
$taskRow = ProjectTask::select(['project_id', 'visibility'])->whereDialogId($dialog->id)->first();
$projectId = intval($taskRow?->project_id);
$taskVisibility = intval($taskRow?->visibility);
$projectId = intval(ProjectTask::whereDialogId($dialog->id)->value('project_id'));
}
if ($projectId > 0 && ProjectUser::whereProjectId($projectId)->whereUserid($userid)->exists()) {
return $dialog;
}
// 部门负责人只读视角:项目群放行;任务群仅"全员可见"任务放行,指定成员可见任务不放行
if ($projectId > 0 && $checkOwner === false && ($dialog->group_type === 'project' || $taskVisibility === 1)) {
// 部门负责人只读视角:项目/任务群按项目级共享放行(任务数据另按可见性校验,与普通成员一致)
if ($projectId > 0 && $checkOwner === false) {
$departmentView = UserDepartment::ownerViewContext(User::auth(), true);
if (UserDepartment::isDepartmentReadonlyProject($departmentView, $projectId)) {
return $dialog;

View File

@ -26,6 +26,20 @@ const timezone = require("dayjs/plugin/timezone");
return typeof (obj) == "object" && Object.prototype.toString.call(obj).toLowerCase() == '[object array]' && typeof obj.length == "number";
},
/**
* 规范化为整型数组
* @param data
* @returns {number[]}
*/
normalizeIntArray(data) {
if (!this.isArray(data)) {
return [];
}
return [...new Set(data
.map(id => parseInt(id))
.filter(id => id > 0))]
},
/**
* 是否数组对象
* @param obj

View File

@ -678,7 +678,7 @@ export default {
},
cacheDepartmentOwnerIds() {
return (this.$store.state.cacheDepartmentOwnerIds || []).map(id => parseInt(id));
return this.$store.state.cacheDepartmentOwnerIds || [];
},
/**

View File

@ -63,7 +63,7 @@ export default {
immediate: true,
handler(show) {
if (show) {
this.draftIds = (this.cacheDepartmentOwnerIds || []).map(id => parseInt(id));
this.draftIds = (this.cacheDepartmentOwnerIds || []).slice();
} else {
this.applyLoading = false;
}

View File

@ -169,7 +169,7 @@ export default {
},
ownerDepartmentIds() {
return (this.cacheDepartmentOwnerIds || []).map(id => parseInt(id));
return this.cacheDepartmentOwnerIds || [];
},
projectBaseLists() {

View File

@ -113,7 +113,7 @@
</div>
<Scrollbar ref="scroller" class="scroller" :touch-content-blur="false">
<Alert v-if="taskDetail.department_readonly" class="task-readonly-alert" type="info" show-icon>
{{$L('当前为负责人 ,并参与讨论,但不能编辑任务。')}}
{{$L('当前为负责人,并参与讨论,但不能编辑任务。')}}
</Alert>
<Alert v-if="!isDepartmentReadonly && taskDetail.task_user !== undefined && getOwner.length === 0" class="receive-box" type="warning">
<span class="receive-text">{{$L('该任务尚未被领取,点击这里')}}</span>

View File

@ -232,25 +232,9 @@ export default {
], true)) {
params.encrypt = true
}
const departmentOwnerReadonlyUrls = [
'project/lists',
'project/one',
'project/column/lists',
'project/task/lists',
'project/task/one',
'project/task/content',
'project/task/content_history',
'project/task/files',
'project/task/fileinfo',
'project/task/subdata',
'project/task/related',
'project/flow/list',
'project/log/lists',
'project/tag/list',
]
if (params.departmentOwner !== false
&& state.systemConfig.department_owner_project_view === 'open'
&& departmentOwnerReadonlyUrls.includes(params.url)
&& state.departmentOwnerReadonlyUrls.includes(params.url)
&& (state.cacheDepartmentOwnerIds || []).length > 0) {
if (!$A.isJson(params.data)) params.data = {}
if (params.data.department_owner_ids === undefined) {
@ -1193,7 +1177,7 @@ export default {
* @param dispatch
* @returns {Promise<unknown>}
*/
handleReadCache({state}) {
handleReadCache({state, commit}) {
return new Promise(async resolve => {
// 定义需要获取的数据映射
const dataMap = {
@ -1238,7 +1222,11 @@ export default {
// 更新 state
[...dataMap.string, ...dataMap.array, ...dataMap.json].forEach((key, index) => {
state[key] = data[index];
if (key === 'cacheDepartmentOwnerIds') {
commit('department/owner/ids/save', data[index]);
} else {
state[key] = data[index];
}
});
// 特殊处理 cacheDialogs
@ -1597,11 +1585,10 @@ export default {
* @param dispatch
* @returns {Promise<void>}
*/
async getProjectsForDepartmentOwnerView({state, dispatch}) {
async getProjectsForDepartmentOwnerView({state, dispatch, commit}) {
await dispatch("systemSetting").catch(() => {});
if (state.systemConfig.department_owner_project_view !== 'open') {
state.cacheDepartmentOwnerIds = [];
await $A.IDBSet("cacheDepartmentOwnerIds", []).catch(() => {});
commit('department/owner/ids/save', []);
dispatch("getProjectByQueue");
return;
}
@ -1611,8 +1598,7 @@ export default {
__replace: true,
department_owner_ids: restoredDepartmentOwnerIds.join(',')
});
state.cacheDepartmentOwnerIds = restoredDepartmentOwnerIds;
await $A.IDBSet("cacheDepartmentOwnerIds", restoredDepartmentOwnerIds).catch(() => {});
commit('department/owner/ids/save', restoredDepartmentOwnerIds);
return;
}
dispatch("getProjectByQueue");
@ -1624,13 +1610,12 @@ export default {
* @param dispatch
* @returns {Promise<void>}
*/
async restoreDepartmentOwnerView({state, dispatch}) {
async restoreDepartmentOwnerView({state, dispatch, commit}) {
if (state.departmentOwnerViewRestored) {
return [];
}
if (state.systemConfig.department_owner_project_view !== 'open') {
state.cacheDepartmentOwnerIds = [];
await $A.IDBSet("cacheDepartmentOwnerIds", []).catch(() => {});
commit('department/owner/ids/save', []);
return [];
}
state.departmentOwnerViewRestored = true;
@ -1642,8 +1627,7 @@ export default {
if (restored.length > 0) {
state.departmentOwnerProjectsRefreshing = true;
}
state.cacheDepartmentOwnerIds = restored;
await $A.IDBSet("cacheDepartmentOwnerIds", restored).catch(() => {});
commit('department/owner/ids/save', restored);
return restored;
},
@ -1654,20 +1638,19 @@ export default {
* @param ids
* @returns {Promise<void>}
*/
async setDepartmentOwnerIds({state, dispatch}, ids) {
async setDepartmentOwnerIds({state, dispatch, commit}, ids) {
if (state.systemConfig.department_owner_project_view !== 'open') {
ids = [];
}
const normalized = await dispatch("normalizeDepartmentOwnerIds", ids);
const oldValue = (state.cacheDepartmentOwnerIds || []).map(id => parseInt(id)).sort().join(',');
const oldValue = (state.cacheDepartmentOwnerIds || []).slice().sort().join(',');
const newValue = normalized.slice().sort().join(',');
if (oldValue === newValue) {
return;
}
state.departmentOwnerProjectsRefreshing = true;
await dispatch("refreshDepartmentOwnerProjects", normalized);
state.cacheDepartmentOwnerIds = normalized;
await $A.IDBSet("cacheDepartmentOwnerIds", normalized).catch(() => {});
commit('department/owner/ids/save', normalized);
},
/**

View File

@ -248,6 +248,12 @@ export default {
$A.IDBSave("cacheProjectParameter", state.cacheProjectParameter);
},
// 部门负责人视角
'department/owner/ids/save': function(state, data) {
state.cacheDepartmentOwnerIds = $A.normalizeIntArray(data)
$A.IDBSet("cacheDepartmentOwnerIds", state.cacheDepartmentOwnerIds).catch(() => {});
},
// 文件管理
'file/push': function(state, data) {
state.fileLists.push(data)

View File

@ -106,6 +106,22 @@ export default {
cacheDepartmentOwnerIds: [],
departmentOwnerViewRestored: false,
departmentOwnerProjectsRefreshing: false,
departmentOwnerReadonlyUrls: [
'project/lists',
'project/one',
'project/column/lists',
'project/task/lists',
'project/task/one',
'project/task/content',
'project/task/content_history',
'project/task/files',
'project/task/fileinfo',
'project/task/subdata',
'project/task/related',
'project/flow/list',
'project/log/lists',
'project/tag/list',
],
// Emoji
cacheEmojis: [],