From 3714dd180b21007ddc03c41c0ef4bda55cdc9b17 Mon Sep 17 00:00:00 2001 From: dxc Date: Fri, 27 Feb 2026 13:54:06 +0800 Subject: [PATCH] feat: apply RBAC read/write separation to outbound_create module Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) --- inventory-backend/app/api/v1/outbound.py | 17 ++++++- inventory-web/src/views/outbound/create.vue | 53 +++++++++++++++++---- 2 files changed, 59 insertions(+), 11 deletions(-) diff --git a/inventory-backend/app/api/v1/outbound.py b/inventory-backend/app/api/v1/outbound.py index ae857e0..a7a48ba 100644 --- a/inventory-backend/app/api/v1/outbound.py +++ b/inventory-backend/app/api/v1/outbound.py @@ -1,7 +1,8 @@ from flask import Blueprint, request, jsonify from app.services.outbound_service import OutboundService -from flask_jwt_extended import jwt_required, get_jwt_identity +from flask_jwt_extended import jwt_required, get_jwt_identity, get_jwt from app.utils.decorators import permission_required +from app.services.auth_service import AuthService import traceback outbound_bp = Blueprint('outbound', __name__, url_prefix='/outbound') @@ -46,8 +47,20 @@ def scan_barcode(): # -------------------------------------------------------- @outbound_bp.route('', methods=['POST']) @jwt_required() -@permission_required('outbound_selection:operation') def create_outbound(): + # 权限检查:需要 outbound_create:operation 或 outbound_selection:operation 之一 + claims = get_jwt() + user_role = claims.get('role') + if not user_role: + return jsonify({'code': 403, 'msg': '未授权'}), 403 + + # 超级管理员直接放行 + if user_role != 'super_admin': + perm_dict = AuthService.get_user_permissions(user_role) + perms = perm_dict.get('menus', []) + perm_dict.get('elements', []) + if ('outbound_create:operation' not in perms) and ('outbound_selection:operation' not in perms): + return jsonify({'code': 403, 'msg': '权限不足'}), 403 + data = request.get_json() if not data: return jsonify({'code': 400, 'msg': '无有效数据'}), 400 diff --git a/inventory-web/src/views/outbound/create.vue b/inventory-web/src/views/outbound/create.vue index 928c385..76911ed 100644 --- a/inventory-web/src/views/outbound/create.vue +++ b/inventory-web/src/views/outbound/create.vue @@ -17,10 +17,14 @@
-
+
点击开启全屏扫码
+
+ + 无扫码权限 +
@@ -64,13 +69,14 @@ :max="parseFloat(row.available_quantity)" size="small" style="width: 100px" + :disabled="!userStore.hasPermission('outbound_create:operation')" /> @@ -120,7 +126,7 @@ -
+
签名 点击重签 @@ -130,11 +136,17 @@ 点击此处进行全屏签名
+
+
+ + 无签名权限 +
+
- 清空 - + 清空 + 确认出库
@@ -205,6 +217,8 @@ import { getStockByBarcode, submitOutbound, getOutboundList } from '@/api/outbou import { uploadFile } from '@/api/common/upload' import { useUserStore } from '@/stores/user' +const userStore = useUserStore() + // --- 状态定义 --- const barcodeInput = ref('') const cartItems = ref([]) @@ -212,7 +226,6 @@ const loading = ref(false) const showCamera = ref(false) const barcodeRef = ref() const formRef = ref() -const userStore = useUserStore() // 签名相关 const showSignatureDialog = ref(false) @@ -292,6 +305,10 @@ const onScanSuccess = (code: string) => { } const handleManualInput = async () => { + if (!userStore.hasPermission('outbound_create:operation')) { + ElMessage.warning('无操作权限') + return + } const code = barcodeInput.value.trim() if (!code) return @@ -355,10 +372,18 @@ const handleManualInput = async () => { } const removeFromCart = (index: number) => { + if (!userStore.hasPermission('outbound_create:operation')) { + ElMessage.warning('无操作权限') + return + } cartItems.value.splice(index, 1) } const clearAll = () => { + if (!userStore.hasPermission('outbound_create:operation')) { + ElMessage.warning('无操作权限') + return + } ElMessageBox.confirm('确定清空所有已选商品吗?', '提示', { type: 'warning' }) .then(() => { cartItems.value = [] @@ -372,6 +397,10 @@ const clearAll = () => { // --- 提交逻辑 --- const submitForm = async () => { + if (!userStore.hasPermission('outbound_create:operation')) { + ElMessage.warning('无操作权限') + return + } if (!formRef.value) return if (cartItems.value.length === 0) return ElMessage.warning('请先添加商品') @@ -427,7 +456,13 @@ const submitForm = async () => { } // --- 签名逻辑 (Canvas) --- -const openSignatureDialog = () => { showSignatureDialog.value = true } +const openSignatureDialog = () => { + if (!userStore.hasPermission('outbound_create:operation')) { + ElMessage.warning('无签名权限') + return + } + showSignatureDialog.value = true +} const initCanvas = async () => { await nextTick() @@ -618,4 +653,4 @@ onUnmounted(() => { .sidebar-actions { flex-direction: row; width: 100%; gap: 10px; } .sidebar-actions .el-button { flex: 1; height: 40px; } } - \ No newline at end of file +