功能:一级需求部门任务支持跨部门分配成员

1. 在系统设置中添加'允许跨部门分配成员'开关
2. 后端添加获取所有需求部门成员的API
3. 前端根据配置决定是查询单个部门还是所有需求部门的成员
4. 下拉框显示成员所属部门名称,支持按部门名称搜索
This commit is contained in:
Your Name 2026-01-21 14:53:46 +08:00
parent db860ee73b
commit 634806e7cf
8 changed files with 188 additions and 53 deletions

Binary file not shown.

View File

@ -46,6 +46,12 @@ public class UserController {
return Result.success(userService.getAllUsers());
}
@Operation(summary = "获取所有需求部门的成员列表")
@GetMapping("/demand-dept-members")
public Result<List<User>> getAllDemandDeptMembers() {
return Result.success(userService.getAllDemandDeptMembers());
}
@Operation(summary = "根据ID获取用户")
@GetMapping("/{id}")
public Result<User> getUserById(@PathVariable Long id) {

View File

@ -471,6 +471,28 @@ public class DepartmentService {
);
}
/**
* 获取所有需求部门的ID列表(包括 demand 和 primary_demand 类型)
* 用于跨部门成员分配功能
*/
public List<Long> getAllDemandDeptIds() {
List<Department> demandDepts = departmentMapper.selectList(
new LambdaQueryWrapper<Department>()
.in(Department::getDeptType, "demand", "primary_demand")
.eq(Department::getStatus, 1)
.eq(Department::getDeleted, 0)
);
List<Long> allDeptIds = new java.util.ArrayList<>();
for (Department dept : demandDepts) {
allDeptIds.add(dept.getId());
// 递归获取所有子部门ID
allDeptIds.addAll(getAllChildDeptIds(dept.getId()));
}
return allDeptIds;
}
private Integer defaultLimitEnabled(Integer limitEnabled) {
return limitEnabled != null ? limitEnabled : 0;
}

View File

@ -223,6 +223,7 @@ public class SystemConfigService {
createConfigIfNotExists("task.grab_timeout", "30", "task", "抢单超时时间(分钟)", "任务发布后多久无人抢单则自动关闭", "number", 1);
createConfigIfNotExists("task.auto_close_days", "7", "task", "自动关闭天数", "未完成的任务多少天后自动关闭", "number", 1);
createConfigIfNotExists("task.primary_dept_can_fill_detail", "false", "task", "一级需求部门可填写任务明细", "开启后,一级需求部门发布任务时可以填写任务明细;关闭后,任务明细将由需求部门填写", "boolean", 1);
createConfigIfNotExists("primary_dept_cross_dept_assign", "false", "task", "允许跨部门分配成员", "开启后,需求部门负责人审核一级需求任务时可以选择所有需求部门的成员来完善任务明细", "boolean", 1);
// 积分相关配置
createConfigIfNotExists("score.low_balance_alert", "100", "score", "积分余额预警阈值", "部门积分低于此值时发出预警", "number", 1);

View File

@ -398,6 +398,45 @@ public class UserService {
);
}
/**
* 获取所有需求部门的成员列表
* 用于一级需求部门任务的跨部门成员分配
*/
public List<User> getAllDemandDeptMembers() {
// 1. 获取所有需求部门的ID(dept_type = 'demand' 或 'primary_demand')
List<Long> demandDeptIds = departmentService.getAllDemandDeptIds();
if (demandDeptIds.isEmpty()) {
log.warn("没有找到需求部门");
return new java.util.ArrayList<>();
}
log.info("查询需求部门成员,部门IDs: {}", demandDeptIds);
// 2. 查询这些部门下的所有成员
List<User> users = userMapper.selectList(
new LambdaQueryWrapper<User>()
.in(User::getDeptId, demandDeptIds)
.eq(User::getStatus, 1) // 只查询启用状态的用户
.eq(User::getDeleted, 0) // 只查询未删除的用户
.orderByAsc(User::getDeptId)
.orderByAsc(User::getRealName)
);
// 3. 填充部门名称
for (User user : users) {
if (user.getDeptId() != null && (user.getDeptName() == null || user.getDeptName().isEmpty())) {
Department dept = departmentMapper.selectById(user.getDeptId());
if (dept != null) {
user.setDeptName(dept.getDeptName());
}
}
}
log.info("查询到 {} 个需求部门成员", users.size());
return users;
}
/**
* 调整用户所属部门
*/

View File

@ -53,6 +53,14 @@ export const getAllUsers = async (): Promise<User[]> => {
return response.data || [];
};
/**
* 获取所有需求部门的成员列表(用于跨部门分配)
*/
export const getAllDemandDeptMembers = async (): Promise<User[]> => {
const response = await request.get('/users/demand-dept-members');
return response.data || [];
};
/**
* 根据ID获取用户
*/

View File

@ -1348,9 +1348,32 @@
</div>
</div>
<a-divider orientation="left">
<Icon name="users" size="sm" style="margin-right: 8px;" />
成员分配范围
</a-divider>
<div class="config-grid" style="grid-template-columns: 1fr;">
<div class="config-item">
<div class="config-item-header">
<Icon name="users" size="md" color="#52c41a" />
<span class="config-item-title">允许跨部门分配成员</span>
</div>
<div class="config-item-desc">
开启后,需求部门负责人审核一级需求任务时,可以选择<strong>所有需求部门</strong>的成员来完善任务明细。<br/>
关闭后,只能选择<strong>当前负责人所属部门</strong>的成员。
</div>
<a-switch
v-model:checked="primaryDeptConfig.crossDeptAssign"
checked-children="开启"
un-checked-children="关闭"
/>
</div>
</div>
<a-alert
message="说明"
description="一级需求部门发起的任务会先流转到需求部门负责人审核,审核通过后由指定的需求部门成员完善任务明细后再发布。"
description="一级需求部门发起的任务会先流转到需求部门负责人审核,审核通过后由指定的需求部门成员完善任务明细后再发布。开启跨部门分配后,可以将任务分配给其他需求部门的成员。"
type="info"
show-icon
style="margin-top: 16px; margin-bottom: 16px;"
@ -4818,7 +4841,8 @@ const savingTaskReleaseConfig = ref(false)
// ==================== 一级需求部门配置 ====================
const primaryDeptConfig = ref({
canFillDetail: false // 一级需求部门是否可以填写任务明细
canFillDetail: false, // 一级需求部门是否可以填写任务明细
crossDeptAssign: false // 是否允许跨部门分配成员完善任务明细
})
const savingPrimaryDeptConfig = ref(false)
@ -4975,8 +4999,10 @@ const loadTaskReleaseConfig = async () => {
// 加载一级需求部门配置
const primaryDeptCanFillDetailConfig = configs.find((c: any) => c.configKey === 'primary_dept_can_fill_detail')
const crossDeptAssignConfig = configs.find((c: any) => c.configKey === 'primary_dept_cross_dept_assign')
primaryDeptConfig.value = {
canFillDetail: primaryDeptCanFillDetailConfig?.configValue === 'true'
canFillDetail: primaryDeptCanFillDetailConfig?.configValue === 'true',
crossDeptAssign: crossDeptAssignConfig?.configValue === 'true'
}
} catch (error) {
console.error('加载任务发布类型配置失败:', error)
@ -5027,7 +5053,8 @@ const handleSavePrimaryDeptConfig = async () => {
savingPrimaryDeptConfig.value = true
try {
await batchUpdateSystemConfigs({
'primary_dept_can_fill_detail': primaryDeptConfig.value.canFillDetail.toString()
'primary_dept_can_fill_detail': primaryDeptConfig.value.canFillDetail.toString(),
'primary_dept_cross_dept_assign': primaryDeptConfig.value.crossDeptAssign.toString()
})
message.success('一级需求部门配置保存成功')
} catch (error: any) {

View File

@ -1948,17 +1948,24 @@
placeholder="请选择部门成员"
style="width: 100%"
show-search
:filter-option="(input, option) => option.label.toLowerCase().includes(input.toLowerCase())"
:filter-option="(input, option) => {
const label = option.label || ''
const deptName = option['data-dept'] || ''
return label.toLowerCase().includes(input.toLowerCase()) || deptName.toLowerCase().includes(input.toLowerCase())
}"
>
<a-select-option
v-for="member in deptMembersList"
:key="member.id"
:value="member.id"
:label="member.realName"
:data-dept="member.deptName"
>
<div style="display: flex; justify-content: space-between; align-items: center;">
<span>{{ member.realName }}</span>
<span style="color: #888; font-size: 12px;">{{ member.username }}</span>
<span style="color: #888; font-size: 12px;">
{{ member.deptName ? `${member.deptName} · ` : '' }}{{ member.username }}
</span>
</div>
</a-select-option>
</a-select>
@ -2370,7 +2377,7 @@ import * as userApi from '@/api/user'
import * as configApi from '@/api/config'
import * as authApi from '@/api/auth'
import * as departmentApi from '@/api/department'
import { getWorkHoursConfig } from '@/api/system'
import { getWorkHoursConfig, getSystemConfig } from '@/api/system'
import type { Task, TaskApprovalDTO, TaskProduct, ExportTaskHallParams } from '@/api/task'
import { exportTaskHall } from '@/api/task'
import type { DesignerWithWorkload } from '@/api/user'
@ -3696,7 +3703,7 @@ const primaryApproveModalVisible = ref(false)
const primaryApproveTaskData = ref<Task | null>(null)
const primaryApproveCompleterId = ref<number | undefined>(undefined)
const primaryApproveComment = ref('')
const deptMembersList = ref<Array<{id: number, realName: string, username: string}>>([])
const deptMembersList = ref<Array<{id: number, realName: string, username: string, deptName?: string}>>([])
const primaryApprovalLoading = ref(false)
// 判断任务是否已预先指定设计师
@ -5437,55 +5444,80 @@ const handleApprovePrimaryTask = async (task: Task) => {
try {
const userInfo = JSON.parse(localStorage.getItem('userInfo') || '{}')
// 优先使用用户管理的需求部门(dept_type = 'demand'),而不是接单部门
let deptId = userInfo.deptId
let selectedDeptName = ''
// 从管理的部门中找需求部门(demand类型)
if (managedDepartments.value.length > 0) {
// 优先找需求部门
const demandDept = managedDepartments.value.find(d => d.deptType === 'demand')
if (demandDept) {
deptId = demandDept.deptId
selectedDeptName = demandDept.deptName
console.log(`使用用户管理的需求部门: ${selectedDeptName} (ID: ${deptId})`)
} else {
// 没有需求部门,使用第一个部门
deptId = managedDepartments.value[0].deptId
selectedDeptName = managedDepartments.value[0].deptName
console.log(`没有找到需求部门,使用第一个管理的部门: ${selectedDeptName} (ID: ${deptId})`)
}
} else if (userInfo.managedDepartments && userInfo.managedDepartments.length > 0) {
// 优先找需求部门
const demandDept = userInfo.managedDepartments.find((d: any) => d.deptType === 'demand')
if (demandDept) {
deptId = demandDept.deptId
selectedDeptName = demandDept.deptName
console.log(`使用userInfo中管理的需求部门: ${selectedDeptName} (ID: ${deptId})`)
} else {
deptId = userInfo.managedDepartments[0].deptId
selectedDeptName = userInfo.managedDepartments[0].deptName
console.log(`没有找到需求部门,使用userInfo中第一个管理的部门: ${selectedDeptName} (ID: ${deptId})`)
}
// 先检查是否开启了跨部门分配功能
let crossDeptAssignEnabled = false
try {
const configResponse = await getSystemConfig('primary_dept_cross_dept_assign')
crossDeptAssignEnabled = configResponse.data?.configValue === 'true'
console.log('跨部门分配功能状态:', crossDeptAssignEnabled)
} catch (e) {
console.log('获取跨部门分配配置失败,使用默认值(关闭)')
}
if (!deptId) {
message.error('无法获取部门信息')
return
if (crossDeptAssignEnabled) {
// 开启跨部门分配:获取所有需求部门的成员
console.log('跨部门分配已开启,获取所有需求部门成员')
const allMembers = await userApi.getAllDemandDeptMembers()
deptMembersList.value = (allMembers || []).map(u => ({
id: u.id!,
realName: u.realName || u.username,
username: u.username,
deptName: u.deptName || ''
}))
console.log(`获取到${deptMembersList.value.length}个需求部门成员(跨部门)`)
} else {
// 未开启跨部门分配:只获取当前负责人部门的成员
// 优先使用用户管理的需求部门(dept_type = 'demand'),而不是接单部门
let deptId = userInfo.deptId
let selectedDeptName = ''
// 从管理的部门中找需求部门(demand类型)
if (managedDepartments.value.length > 0) {
// 优先找需求部门
const demandDept = managedDepartments.value.find(d => d.deptType === 'demand')
if (demandDept) {
deptId = demandDept.deptId
selectedDeptName = demandDept.deptName
console.log(`使用用户管理的需求部门: ${selectedDeptName} (ID: ${deptId})`)
} else {
// 没有需求部门,使用第一个部门
deptId = managedDepartments.value[0].deptId
selectedDeptName = managedDepartments.value[0].deptName
console.log(`没有找到需求部门,使用第一个管理的部门: ${selectedDeptName} (ID: ${deptId})`)
}
} else if (userInfo.managedDepartments && userInfo.managedDepartments.length > 0) {
// 优先找需求部门
const demandDept = userInfo.managedDepartments.find((d: any) => d.deptType === 'demand')
if (demandDept) {
deptId = demandDept.deptId
selectedDeptName = demandDept.deptName
console.log(`使用userInfo中管理的需求部门: ${selectedDeptName} (ID: ${deptId})`)
} else {
deptId = userInfo.managedDepartments[0].deptId
selectedDeptName = userInfo.managedDepartments[0].deptName
console.log(`没有找到需求部门,使用userInfo中第一个管理的部门: ${selectedDeptName} (ID: ${deptId})`)
}
}
if (!deptId) {
message.error('无法获取部门信息')
return
}
console.log(`查询部门 ${deptId} (${selectedDeptName}) 的成员(包含子部门)`)
// 获取部门成员(包含所有子部门)
const response = await userApi.getUserList(1, 1000, undefined, deptId, 1, true)
deptMembersList.value = (response.records || []).map(u => ({
id: u.id!,
realName: u.realName || u.username,
username: u.username,
deptName: u.deptName || ''
}))
console.log(`获取到${deptMembersList.value.length}个部门成员(含子部门)`)
}
console.log(`查询部门 ${deptId} (${selectedDeptName}) 的成员(包含子部门)`)
// 获取部门成员(包含所有子部门)
const response = await userApi.getUserList(1, 1000, undefined, deptId, 1, true)
deptMembersList.value = (response.records || []).map(u => ({
id: u.id!,
realName: u.realName || u.username,
username: u.username
}))
console.log(`获取到${deptMembersList.value.length}个部门成员(含子部门)`)
primaryApproveTaskData.value = task
primaryApproveCompleterId.value = undefined
primaryApproveComment.value = ''