feat: apply RBAC read/write separation to outbound_create module
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
This commit is contained in:
@ -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
|
||||
|
||||
@ -17,10 +17,14 @@
|
||||
|
||||
<div class="scan-section">
|
||||
|
||||
<div class="camera-placeholder" @click="showCamera = true">
|
||||
<div v-if="userStore.hasPermission('outbound_create:operation')" class="camera-placeholder" @click="showCamera = true">
|
||||
<el-icon :size="40" color="#409EFF"><CameraFilled /></el-icon>
|
||||
<span class="text">点击开启全屏扫码</span>
|
||||
</div>
|
||||
<div v-else class="camera-placeholder" style="background-color: #f5f5f5; cursor: not-allowed;">
|
||||
<el-icon :size="40" color="#909399"><CameraFilled /></el-icon>
|
||||
<span class="text">无扫码权限</span>
|
||||
</div>
|
||||
|
||||
<div class="input-box">
|
||||
<el-input
|
||||
@ -30,12 +34,13 @@
|
||||
clearable
|
||||
ref="barcodeRef"
|
||||
size="large"
|
||||
:disabled="!userStore.hasPermission('outbound_create:operation')"
|
||||
>
|
||||
<template #prefix>
|
||||
<el-icon><Scissor /></el-icon>
|
||||
</template>
|
||||
<template #append>
|
||||
<el-button @click="handleManualInput">添加</el-button>
|
||||
<el-button @click="handleManualInput" :disabled="!userStore.hasPermission('outbound_create:operation')">添加</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
@ -64,13 +69,14 @@
|
||||
:max="parseFloat(row.available_quantity)"
|
||||
size="small"
|
||||
style="width: 100px"
|
||||
:disabled="!userStore.hasPermission('outbound_create:operation')"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="操作" width="60" align="center" fixed="right">
|
||||
<template #default="{$index}">
|
||||
<el-button type="danger" icon="Delete" circle size="small" @click="removeFromCart($index)" />
|
||||
<el-button v-if="userStore.hasPermission('outbound_create:operation')" type="danger" icon="Delete" circle size="small" @click="removeFromCart($index)" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@ -120,7 +126,7 @@
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="电子签名确认" required>
|
||||
<div class="signature-box" @click="openSignatureDialog">
|
||||
<div class="signature-box" @click="openSignatureDialog" v-if="userStore.hasPermission('outbound_create:operation')">
|
||||
<div v-if="signaturePreviewUrl" class="signed-img">
|
||||
<img :src="signaturePreviewUrl" alt="签名" />
|
||||
<span class="re-sign-tip">点击重签</span>
|
||||
@ -130,11 +136,17 @@
|
||||
<span>点击此处进行全屏签名</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="signature-box" style="background-color: #f5f5f5; cursor: not-allowed;">
|
||||
<div class="unsigned-placeholder">
|
||||
<el-icon :size="24"><EditPen /></el-icon>
|
||||
<span>无签名权限</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<div class="bottom-actions">
|
||||
<el-button @click="clearAll" icon="Refresh">清空</el-button>
|
||||
<el-button type="primary" size="large" :loading="loading" @click="submitForm" icon="Select">
|
||||
<el-button v-if="userStore.hasPermission('outbound_create:operation')" @click="clearAll" icon="Refresh">清空</el-button>
|
||||
<el-button v-if="userStore.hasPermission('outbound_create:operation')" type="primary" size="large" :loading="loading" @click="submitForm" icon="Select">
|
||||
确认出库
|
||||
</el-button>
|
||||
</div>
|
||||
@ -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<any[]>([])
|
||||
@ -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; }
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user