diff --git a/backend/app/services/task_service.py b/backend/app/services/task_service.py index 63a612a..813f087 100644 --- a/backend/app/services/task_service.py +++ b/backend/app/services/task_service.py @@ -238,12 +238,16 @@ async def receive_task( """ task = await _get_task_or_404(db, task_id) + # 权限校验:只有负责人本人可接收 + if operator_id and task.assignee_id and operator_id != task.assignee_id: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail=f"您无权操作此任务,当前任务负责人为 {task.assignee_id}", + ) + # 校验:只有 PENDING 状态可接收 if task.status != TASK_STATUS_PENDING: raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail=f"只有待接收(PENDING)状态的任务可接收,当前状态: {task.status}", - ) now = get_beijing_time() task.status = TASK_STATUS_WIP @@ -284,6 +288,13 @@ async def reject_task( """ task = await _get_task_or_404(db, task_id) + # 权限校验:只有负责人本人可驳回 + if operator_id and task.assignee_id and operator_id != task.assignee_id: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail=f"您无权操作此任务,当前任务负责人为 {task.assignee_id}", + ) + # 校验:不能重复驳回已完成/已驳回的任务 if task.status in (TASK_STATUS_COMPLETED, TASK_STATUS_REJECTED): raise HTTPException( diff --git a/track-uniapp/src/pages/scan/detail.vue b/track-uniapp/src/pages/scan/detail.vue index 164e6fb..1eb99e9 100644 --- a/track-uniapp/src/pages/scan/detail.vue +++ b/track-uniapp/src/pages/scan/detail.vue @@ -90,21 +90,21 @@ - - - - - @@ -379,7 +379,7 @@ export default { }); }, activeTask() { - // 查找树中第一个 WIP 或 PENDING 任务作为底部 Footer 的操作目标 + // 查找树中第一个 WIP 或 PENDING 任务(不限归属) const findActive = (tasks) => { if (!tasks) return null; for (const t of tasks) { @@ -393,6 +393,24 @@ export default { }; return this.product ? findActive(this.product.task_tree) : null; }, + myActiveTask() { + // 只返回当前登录人自己的 WIP/PENDING 任务 + const findMine = (tasks) => { + if (!tasks) return null; + for (const t of tasks) { + const isMine = t.status === 'WIP' || t.status === 'PENDING'; + const matchId = t.assignee_id == this.currentUserId; + const matchName = t.assignee_id == this.currentUsername; + if (isMine && (matchId || matchName)) return t; + if (t.child_tasks && t.child_tasks.length) { + const found = findMine(t.child_tasks); + if (found) return found; + } + } + return null; + }; + return this.product ? findMine(this.product.task_tree) : null; + }, }, onLoad(options) { this.loadUsers(); @@ -516,7 +534,8 @@ export default { // 场景B: 立即接收 if (this.firstForm.autoReceive) { try { - await post(`/tasks/${task.id}/receive`); + const opId = this.currentUsername || this.currentUserId; + await post(`/tasks/${task.id}/receive?operator_id=${encodeURIComponent(opId)}`); } catch { /* receive 失败不影响流程 */ } } uni.showToast({ @@ -696,7 +715,8 @@ export default { this.actionLoading = true; try { const remark = this.receiveRemark.trim() || undefined; - await post(`/tasks/${this.actionPopup.task.id}/receive`, { remark }); + const opId = this.currentUsername || this.currentUserId; + await post(`/tasks/${this.actionPopup.task.id}/receive?operator_id=${encodeURIComponent(opId)}`, { remark }); uni.showToast({ title: "已接收", icon: "success" }); this.closeActionPopup(); this.doQuery(this.product.serial_number); @@ -705,7 +725,8 @@ export default { async doReject() { this.actionLoading = true; try { - await post(`/tasks/${this.actionPopup.task.id}/reject`, { reason: this.rejectReason.trim() }); + const opId = this.currentUsername || this.currentUserId; + await post(`/tasks/${this.actionPopup.task.id}/reject?operator_id=${encodeURIComponent(opId)}`, { reason: this.rejectReason.trim() }); uni.showToast({ title: "已驳回,返工任务已创建", icon: "success" }); this.closeActionPopup(); this.doQuery(this.product.serial_number); diff --git a/track-uniapp/src/utils/request.js b/track-uniapp/src/utils/request.js index d3acb94..941886d 100644 --- a/track-uniapp/src/utils/request.js +++ b/track-uniapp/src/utils/request.js @@ -30,6 +30,9 @@ export default function request(options) { uni.showToast({ title: "登录已过期,请重新登录", icon: "none" }); setTimeout(() => uni.reLaunch({ url: "/pages/login/login" }), 1000); reject(res); + } else if (code === 403) { + uni.showToast({ title: res.data?.detail || "无权操作", icon: "none", duration: 3000 }); + reject(res); } else if (code === 400) { uni.showToast({ title: res.data?.detail || "请求参数有误", icon: "none", duration: 2500 }); reject(res);