fix: 修复用户管理和任务抢单权限控制

1. 修复设置管理员报错
   - 修正BusinessException构造函数调用

2. 完善抢单权限控制
   - 需求部门成员不能抢单
   - 所有部门负责人不能抢单(包括需求和接单部门)
   - 只有指定接单部门的普通成员可以抢单
   - 验证部门是否允许接该需求类型
   - 抢单成功后发送通知给任务发起人

3. 前端抢单按钮状态控制
   - 添加canGrabTask方法判断抢单权限
   - 可接单:绿色按钮
   - 不可接单:灰色按钮并禁用
   - 鼠标悬停显示不能抢单的原因
   - 支持任务列表和详情页面

4. 优化用户体验
   - 点击不可抢单按钮时提示具体原因
   - 按钮文案动态显示状态
This commit is contained in:
Your Name 2026-01-03 11:07:42 +08:00
parent 93781bb202
commit 038659bd41
3 changed files with 99 additions and 16 deletions

View File

@ -670,26 +670,39 @@ public class TaskService {
throw new BusinessException(ResultCode.FORBIDDEN, "该任务不支持抢单");
}
// 验证用户权限:只有接单部门可以接任务(管理员除外)
// 获取用户信息
var user = userService.getUserById(userId);
if (user == null) {
throw new BusinessException(ResultCode.FORBIDDEN, "用户不存在");
}
// 检查是否是管理员(管理员可以同时拥有需求部门和接单部门权限)
boolean isAdmin = isAdminUser(userId);
Long deptId = user.getDeptId();
Department department = deptId != null ? departmentService.getDepartmentById(deptId) : null;
if (deptId == null) {
throw new BusinessException(ResultCode.FORBIDDEN, "您未分配部门,无法接单");
}
// 获取用户所属部门
Department department = departmentService.getDepartmentById(deptId);
if (department == null) {
throw new BusinessException(ResultCode.FORBIDDEN, "未找到用户所属部门,请联系管理员");
}
// 检查1:需求部门不能抢单
if ("demand".equals(department.getDeptType())) {
throw new BusinessException(ResultCode.FORBIDDEN, "需求部门成员不能接单,只能发起需求");
}
// 检查2:接单部门类型验证
if (!"receiving".equals(department.getDeptType())) {
throw new BusinessException(ResultCode.FORBIDDEN, "只有接单部门可以接收任务");
throw new BusinessException(ResultCode.FORBIDDEN, "只有接单部门成员可以接收任务");
}
if (!isAdmin && deptId == null) {
throw new BusinessException(ResultCode.FORBIDDEN, "用户部门信息不完整");
// 检查3:部门负责人不能抢单
if (department.getLeaderId() != null && department.getLeaderId().equals(userId)) {
throw new BusinessException(ResultCode.FORBIDDEN, "部门负责人不能接单,请分配给设计师");
}
// 检查4:验证部门是否允许接该需求类型
if (task.getDemandTypeId() != null) {
validateAllowedDemandTypes(department, task.getDemandTypeId());
}
@ -708,7 +721,22 @@ public class TaskService {
throw new BusinessException(ResultCode.TASK_ALREADY_TAKEN, "任务已被其他设计师抢走");
}
// TODO: 发送通知
log.info("用户 {} 成功抢单任务 {}", userId, taskId);
// 发送通知给任务发起人
if (task.getPublisherId() != null) {
try {
notificationService.sendNotification(
task.getPublisherId(),
"任务已被接单",
String.format("您的任务【%s】已被 %s 接单", task.getTitle(), user.getRealName() != null ? user.getRealName() : user.getUsername()),
"task_grabbed",
taskId
);
} catch (Exception e) {
log.error("发送抢单通知失败", e);
}
}
}
/**

View File

@ -1272,7 +1272,7 @@ public class UserService {
User user = getUserById(userId);
if (user == null) {
throw new BusinessException("用户不存在");
throw new BusinessException(ResultCode.NOT_FOUND, "用户不存在");
}
// 更新管理员标识

View File

@ -320,11 +320,21 @@
查看详情
</button>
<button
class="btn btn-primary"
style="flex: 1"
:class="[
'btn',
canGrabTask(task).canGrab ? 'btn-primary' : 'btn-disabled'
]"
:style="{
flex: 1,
backgroundColor: canGrabTask(task).canGrab ? '#31f08b' : '#666',
cursor: canGrabTask(task).canGrab ? 'pointer' : 'not-allowed',
opacity: canGrabTask(task).canGrab ? 1 : 0.6
}"
:disabled="!canGrabTask(task).canGrab"
:title="canGrabTask(task).canGrab ? '点击抢单' : canGrabTask(task).reason"
@click.stop="handleGrab(task)"
>
抢单
{{ canGrabTask(task).canGrab ? '抢单' : '不可接单' }}
</button>
</div>
</div>
@ -1840,11 +1850,19 @@
<!-- 可接任务:显示抢单 -->
<a-button
v-if="selectedTask.status === 'pending'"
type="primary"
:type="canGrabTask(selectedTask).canGrab ? 'primary' : 'default'"
:style="{
backgroundColor: canGrabTask(selectedTask).canGrab ? '#31f08b' : '#999',
borderColor: canGrabTask(selectedTask).canGrab ? '#31f08b' : '#999',
color: '#fff',
cursor: canGrabTask(selectedTask).canGrab ? 'pointer' : 'not-allowed',
opacity: canGrabTask(selectedTask).canGrab ? 1 : 0.6
}"
size="large"
block
block
:disabled="!canGrabTask(selectedTask).canGrab"
@click="handleGrabFromDetail">
确认抢单
{{ canGrabTask(selectedTask).canGrab ? '确认抢单' : `不可接单(${canGrabTask(selectedTask).reason})` }}
</a-button>
<!-- 进行中任务:显示更新进度和完结 -->
@ -3372,12 +3390,49 @@ const handleTaskClick = (task: TaskOrSubTask) => {
detailDrawerVisible.value = true
}
// 判断用户是否可以抢单
const canGrabTask = (task: TaskOrSubTask) => {
const userInfo = JSON.parse(localStorage.getItem('userInfo') || '{}')
const userDeptId = userInfo.deptId
const userDeptType = userInfo.deptType // 'demand' 或 'receiving'
// 检查1:没有部门不能抢单
if (!userDeptId) {
return { canGrab: false, reason: '您未分配部门,无法接单' }
}
// 检查2:需求部门不能抢单
if (userDeptType === 'demand') {
return { canGrab: false, reason: '需求部门成员不能接单' }
}
// 检查3:部门负责人不能抢单
if (userInfo.isDepartmentLeader === 1 || userInfo.isDepartmentLeader === true) {
return { canGrab: false, reason: '部门负责人不能接单' }
}
// 检查4:验证部门是否允许接该需求类型(如果任务有需求类型限制)
if (task.demandTypeId) {
// 这里需要检查任务的需求类型是否允许当前用户的部门接单
// 由于前端没有完整的需求类型配置,暂时信任后端验证
}
return { canGrab: true, reason: '' }
}
// 抢单(支持普通任务和子任务)
const handleGrab = async (task: TaskOrSubTask) => {
const isSubTask = task.isSubTask
const taskType = isSubTask ? '子任务' : '任务'
const taskNo = isSubTask ? task.subTaskNo : task.taskNo
// 先检查权限
const { canGrab, reason } = canGrabTask(task)
if (!canGrab) {
message.warning(reason)
return
}
Modal.confirm({
title: `确认抢单${taskType}`,
content: `确定要抢单${taskType}"${task.title}"吗?${taskNo ? `(编号:${taskNo})` : ''}`,