feat: 管理员代工模式 — 前端权限放开 + 后端审计留痕
前端: - WorkspaceArea 新增 currentUserRole prop - isAssignee/canRecall: SUPER_ADMIN/SUPERVISOR 上帝视角直接放行 - 管理员代操作时显示黄色提示条: 正在以管理员身份代[原负责人]操作 - detail.vue: loadCurrentUser 提取并保存 user.role 后端: - _create_task_log 新增 task_assignee_id 参数 - operator_id!=task_assignee_id 时自动拼接 [管理员xx代办操作] - 12处调用点全部传入 task_assignee_id - receive_task 位置赋值确认为 task.assignee_id(非 operator_id)
This commit is contained in:
@ -78,6 +78,9 @@
|
||||
<view class="fc-time">{{ formatTaskTime(lockedTask) }}</view>
|
||||
</view>
|
||||
|
||||
<view v-if="isAdminProxy" class="admin-proxy-notice">
|
||||
<text class="proxy-text">⚠️ 正在以管理员身份代 [{{ formatUserName(lockedTask.assignee_id) || lockedTask.assignee_id }}] 操作</text>
|
||||
</view>
|
||||
<view v-if="isAssignee" class="footer-actions">
|
||||
<button v-if="lockedTask.status === 'WIP'" class="footer-btn footer-transfer"
|
||||
@tap="$emit('action', { task: lockedTask, type: 'transfer' })"><text class="btn-icon">🔄</text><text class="btn-txt">完工转交</text></button>
|
||||
@ -111,6 +114,7 @@ export default {
|
||||
product: { type: Object, default: null },
|
||||
currentUserId: { type: String, default: "" },
|
||||
currentUsername: { type: String, default: "" },
|
||||
currentUserRole: { type: String, default: "" },
|
||||
initialLockTaskId: { type: String, default: "" },
|
||||
},
|
||||
emits: ["action", "viewRecords"],
|
||||
@ -162,9 +166,22 @@ export default {
|
||||
return result;
|
||||
},
|
||||
lockedTask() { return this.focusTasks.find(t => t.id === this.lockedTaskId) || null; },
|
||||
isAssignee() { if (!this.lockedTask) return false; return this.lockedTask.assignee_id == this.currentUserId || this.lockedTask.assignee_id == this.currentUsername; },
|
||||
isAdmin() { return this.currentUserRole === 'SUPER_ADMIN' || this.currentUserRole === 'SUPERVISOR'; },
|
||||
isAssignee() {
|
||||
if (!this.lockedTask) return false;
|
||||
if (this.isAdmin) return true; // 管理员上帝视角
|
||||
return this.lockedTask.assignee_id == this.currentUserId
|
||||
|| this.lockedTask.assignee_id == this.currentUsername;
|
||||
},
|
||||
isAdminProxy() {
|
||||
// 管理员正在代操作非本人任务
|
||||
return this.isAdmin && this.lockedTask
|
||||
&& this.lockedTask.assignee_id != this.currentUserId
|
||||
&& this.lockedTask.assignee_id != this.currentUsername;
|
||||
},
|
||||
canRecall() {
|
||||
if (!this.lockedTask || this.lockedTask.status !== 'PENDING') return false;
|
||||
if (this.isAdmin) return true; // 管理员可撤回任何转交
|
||||
if (this.isAssignee) return false;
|
||||
if (!this.lockedTask.parent_task_id) return false;
|
||||
const parent = this.taskMap[this.lockedTask.parent_task_id];
|
||||
@ -281,6 +298,8 @@ export default {
|
||||
.btn-icon { font-size: 36rpx; margin-bottom: 6rpx; }
|
||||
.btn-txt { font-size: 24rpx; font-weight: 700; }
|
||||
.footer-readonly { justify-content: center; background: #fef2f2; }
|
||||
.admin-proxy-notice { padding: 10rpx 20rpx; background: #fef9e7; border-top: 2rpx solid #fde68a; flex-shrink: 0; }
|
||||
.proxy-text { font-size: 22rpx; color: #b45309; font-weight: 600; }
|
||||
.readonly-hint { font-size: 24rpx; color: #dc2626; font-weight: 600; }
|
||||
.footer-transfer { background: #dcfce7; color: #16a34a; }
|
||||
.footer-record { background: #eff6ff; color: #2563eb; }
|
||||
|
||||
@ -46,6 +46,7 @@
|
||||
<!-- 工作区视图 -->
|
||||
<WorkspaceArea v-if="currentMode === 'workspace'" :product="product"
|
||||
:currentUserId="currentUserId" :currentUsername="currentUsername"
|
||||
:currentUserRole="currentUserRole"
|
||||
:initialLockTaskId="autoLockTaskId"
|
||||
:key="'wa-' + dictVersion"
|
||||
@action="handleTaskAction" @viewRecords="handleViewRecords" />
|
||||
@ -218,7 +219,7 @@ export default {
|
||||
users: [], TASK_NAME_OPTIONS,
|
||||
createFirstVisible: false, isWarehouseTransfer: false, firstForm: { task_name: "", taskNameIdx: 0, assignee_id: "", assigneeLabel: "", assigneeIdx: 0, note: "" }, firstSaving: false,
|
||||
recordPopup: { visible: false, task: null }, recordForm: { recordId: null, remark: "", images: [], pendingCount: 0 }, recordSaving: false, isUploading: false,
|
||||
currentUser: null, currentUserId: "", currentUsername: "", currentMode: "workspace", autoLockTaskId: "",
|
||||
currentUser: null, currentUserId: "", currentUsername: "", currentUserRole: "", currentMode: "workspace", autoLockTaskId: "",
|
||||
processOptions: [], userOptions: [],
|
||||
actionPopup: { visible: false, type: "", task: null }, actionLoading: false, rejectReason: "", receiveRemark: "", receiveTaskName: "",
|
||||
transferForm: { selectedUserId: "", isWarehouse: false, note: "" },
|
||||
@ -302,7 +303,7 @@ export default {
|
||||
async doEditProduct() { this.editSaving = true; try { this.product = await patch(`/products/${this.product.id}`, { order_no: this.editForm.order_no.trim(), external_serial: this.editForm.external_serial.trim() }); uni.showToast({ title: "已保存", icon: "success" }); this.editProductVisible = false; } catch {} finally { this.editSaving = false; } },
|
||||
|
||||
async loadUsers() { try { const res = await get("/users/", { limit: 200, dept: "IRIS" }); this.users = (res || []).filter(u => u.department === "IRIS"); this.userOptions = this.users.map(u => ({ id: u.username || u.id, name: u.full_name || u.username })); setUserNameMap(this.users); this.dictVersion += 1; } catch (e) { console.error('[loadUsers] 获取用户列表失败:', e); } },
|
||||
loadCurrentUser() { try { let user = uni.getStorageSync("user"); if (typeof user === "string" && user) { try { user = JSON.parse(user); } catch (e) { user = null; } } if (user && typeof user === "object") { this.currentUser = user; this.currentUserId = String(user.id || ""); this.currentUsername = user.username || ""; } } catch {} },
|
||||
loadCurrentUser() { try { let user = uni.getStorageSync("user"); if (typeof user === "string" && user) { try { user = JSON.parse(user); } catch (e) { user = null; } } if (user && typeof user === "object") { this.currentUser = user; this.currentUserId = String(user.id || ""); this.currentUsername = user.username || ""; this.currentUserRole = user.role || ""; } } catch {} },
|
||||
|
||||
onTaskNameChange(e) { const idx = e.detail.value; this.firstForm.taskNameIdx = idx; this.firstForm.task_name = TASK_NAME_OPTIONS[idx]; },
|
||||
onAssigneeChange(e) { const idx = e.detail.value; const u = this.users[idx]; if (u) { this.firstForm.assigneeIdx = idx; this.firstForm.assignee_id = u.username; this.firstForm.assigneeLabel = `${u.full_name} (${u.username})`; } },
|
||||
|
||||
Reference in New Issue
Block a user