feat: add RBAC and field masking for borrow/return/records pages
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
This commit is contained in:
@ -13,10 +13,14 @@
|
||||
</template>
|
||||
|
||||
<div class="scan-section">
|
||||
<div class="camera-placeholder" @click="showCamera = true">
|
||||
<div v-if="userStore.hasPermission('op_borrow: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
|
||||
@ -26,12 +30,13 @@
|
||||
clearable
|
||||
ref="barcodeRef"
|
||||
size="large"
|
||||
:disabled="!userStore.hasPermission('op_borrow:operation')"
|
||||
>
|
||||
<template #prefix>
|
||||
<el-icon><Scissor /></el-icon>
|
||||
</template>
|
||||
<template #append>
|
||||
<el-button @click="handleManualInput">添加</el-button>
|
||||
<el-button @click="handleManualInput" :disabled="!userStore.hasPermission('op_borrow:operation')">添加</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
@ -40,16 +45,16 @@
|
||||
<div class="cart-section">
|
||||
<div v-if="cartItems.length > 0">
|
||||
<el-table :data="cartItems" border stripe style="width: 100%">
|
||||
<el-table-column prop="name" label="物品名称" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column prop="sku" label="SKU" width="120" show-overflow-tooltip />
|
||||
<el-table-column v-if="hasColumnPermission('name')" prop="name" label="物品名称" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column v-if="hasColumnPermission('sku')" prop="sku" label="SKU" width="120" show-overflow-tooltip />
|
||||
|
||||
<el-table-column label="可用库存" width="90" align="center">
|
||||
<el-table-column v-if="hasColumnPermission('available_quantity')" label="可用库存" width="90" align="center">
|
||||
<template #default="{row}">
|
||||
<el-tag type="info">{{ parseFloat(row.available_quantity) }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="借用数" width="130" align="center">
|
||||
<el-table-column v-if="hasColumnPermission('out_quantity')" label="借用数" width="130" align="center">
|
||||
<template #default="{row}">
|
||||
<el-input-number
|
||||
v-model="row.out_quantity"
|
||||
@ -57,11 +62,12 @@
|
||||
:max="parseFloat(row.available_quantity)"
|
||||
size="small"
|
||||
style="width: 100px"
|
||||
:disabled="!userStore.hasPermission('op_borrow:operation')"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="操作" width="60" align="center" fixed="right">
|
||||
<el-table-column v-if="userStore.hasPermission('op_borrow:operation')" label="操作" width="60" align="center" fixed="right">
|
||||
<template #default="{$index}">
|
||||
<el-button type="danger" icon="Delete" circle size="small" @click="removeFromCart($index)" />
|
||||
</template>
|
||||
@ -102,7 +108,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('op_borrow:operation')">
|
||||
<div v-if="signaturePreviewUrl" class="signed-img">
|
||||
<img :src="signaturePreviewUrl" alt="签名" />
|
||||
<span class="re-sign-tip">点击重签</span>
|
||||
@ -112,11 +118,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('op_borrow:operation')" @click="clearAll" icon="Refresh">清空</el-button>
|
||||
<el-button v-if="userStore.hasPermission('op_borrow:operation')" type="primary" size="large" :loading="loading" @click="submitForm" icon="Select">
|
||||
确认借出
|
||||
</el-button>
|
||||
</div>
|
||||
@ -187,6 +199,27 @@ import QrScanner from '@/components/QrScanner/index.vue'
|
||||
import { getStockByBarcode } from '@/api/outbound'
|
||||
import request from '@/utils/request'
|
||||
import { uploadFile } from '@/api/common/upload'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
|
||||
const userStore = useUserStore()
|
||||
|
||||
// 列与权限Code的映射关系(数据库中的code)
|
||||
const permissionMap: Record<string, string> = {
|
||||
borrower_name: 'op_borrow:borrower_name',
|
||||
sku: 'op_borrow:sku',
|
||||
available_quantity: 'op_borrow:available_quantity',
|
||||
out_quantity: 'op_borrow:out_quantity',
|
||||
// 其他字段可根据需要添加
|
||||
}
|
||||
|
||||
// 检查列权限
|
||||
const hasColumnPermission = (prop: string) => {
|
||||
if (userStore.role === 'SUPER_ADMIN' || userStore.username === 'IRIS') {
|
||||
return true
|
||||
}
|
||||
const code = permissionMap[prop]
|
||||
return code ? userStore.hasPermission(code) : false
|
||||
}
|
||||
|
||||
// --- 状态定义 ---
|
||||
const barcodeInput = ref('')
|
||||
@ -564,4 +597,4 @@ onUnmounted(() => {
|
||||
.sidebar-actions { flex-direction: row; width: 100%; gap: 10px; }
|
||||
.sidebar-actions .el-button { flex: 1; height: 40px; }
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@ -88,6 +88,32 @@ import request from '@/utils/request'
|
||||
import dayjs from 'dayjs' // 建议使用 dayjs 处理日期,如果没有安装,可以用原生 Date
|
||||
import 'dayjs/locale/zh-cn' // 导入中文包
|
||||
dayjs.locale('zh-cn')
|
||||
import { useUserStore } from '@/stores/user'
|
||||
|
||||
const userStore = useUserStore()
|
||||
|
||||
// 列与权限Code的映射关系(数据库中的code)
|
||||
const permissionMap: Record<string, string> = {
|
||||
borrow_no: 'op_records:borrow_no',
|
||||
borrower_name: 'op_records:borrower_name',
|
||||
sku: 'op_records:sku',
|
||||
borrow_time: 'op_records:borrow_time',
|
||||
return_time: 'op_records:return_time',
|
||||
status: 'op_records:status',
|
||||
expected_return_time: 'op_records:expected_return_time',
|
||||
return_location: 'op_records:return_location',
|
||||
borrow_signature: 'op_records:borrow_signature',
|
||||
return_signature: 'op_records:return_signature',
|
||||
}
|
||||
|
||||
// 检查列权限
|
||||
const hasColumnPermission = (prop: string) => {
|
||||
if (userStore.role === 'SUPER_ADMIN' || userStore.username === 'IRIS') {
|
||||
return true
|
||||
}
|
||||
const code = permissionMap[prop]
|
||||
return code ? userStore.hasPermission(code) : false
|
||||
}
|
||||
|
||||
const list = ref<any[]>([])
|
||||
const total = ref(0)
|
||||
@ -195,4 +221,4 @@ onMounted(fetchData)
|
||||
.text-normal {
|
||||
color: #909399;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@ -13,10 +13,14 @@
|
||||
</template>
|
||||
|
||||
<div class="scan-section">
|
||||
<div class="camera-placeholder" @click="showCamera = true">
|
||||
<div v-if="userStore.hasPermission('op_return: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
|
||||
@ -26,12 +30,13 @@
|
||||
clearable
|
||||
ref="barcodeRef"
|
||||
size="large"
|
||||
:disabled="!userStore.hasPermission('op_return:operation')"
|
||||
>
|
||||
<template #prefix>
|
||||
<el-icon><Scissor /></el-icon>
|
||||
</template>
|
||||
<template #append>
|
||||
<el-button @click="scanItem">识别</el-button>
|
||||
<el-button @click="scanItem" :disabled="!userStore.hasPermission('op_return:operation')">识别</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
@ -40,16 +45,17 @@
|
||||
<div class="cart-section">
|
||||
<div v-if="returnList.length > 0">
|
||||
<el-table :data="returnList" border stripe style="width: 100%">
|
||||
<el-table-column prop="borrower_name" label="借用人" width="90" show-overflow-tooltip />
|
||||
<el-table-column prop="sku" label="SKU" width="120" show-overflow-tooltip />
|
||||
<el-table-column v-if="hasColumnPermission('borrower_name')" prop="borrower_name" label="借用人" width="90" show-overflow-tooltip />
|
||||
<el-table-column v-if="hasColumnPermission('sku')" prop="sku" label="SKU" width="120" show-overflow-tooltip />
|
||||
|
||||
<el-table-column label="归还库位(可改)" min-width="160">
|
||||
<el-table-column v-if="hasColumnPermission('return_location')" label="归还库位(可改)" min-width="160">
|
||||
<template #default="{row}">
|
||||
<el-input
|
||||
v-model="row.return_location"
|
||||
:placeholder="`原: ${row.current_location || '无'}`"
|
||||
clearable
|
||||
size="small"
|
||||
:disabled="!userStore.hasPermission('op_return:operation')"
|
||||
>
|
||||
<template #append v-if="row.return_location !== row.current_location">
|
||||
<span style="color: #E6A23C; font-size: 12px;">变更</span>
|
||||
@ -58,7 +64,7 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="操作" width="60" align="center" fixed="right">
|
||||
<el-table-column v-if="userStore.hasPermission('op_return:operation')" label="操作" width="60" align="center" fixed="right">
|
||||
<template #default="{$index}">
|
||||
<el-button type="danger" icon="Delete" circle size="small" @click="returnList.splice($index, 1)" />
|
||||
</template>
|
||||
@ -77,7 +83,7 @@
|
||||
|
||||
<el-form label-position="top">
|
||||
<el-form-item required>
|
||||
<div class="signature-box" @click="openSignatureDialog">
|
||||
<div class="signature-box" @click="openSignatureDialog" v-if="userStore.hasPermission('op_return:operation')">
|
||||
<div v-if="signaturePreviewUrl" class="signed-img">
|
||||
<img :src="signaturePreviewUrl" alt="签名" />
|
||||
<span class="re-sign-tip">点击重签</span>
|
||||
@ -87,12 +93,18 @@
|
||||
<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>
|
||||
</el-form>
|
||||
|
||||
<div class="bottom-actions">
|
||||
<el-button @click="clearAll" icon="Refresh">清空</el-button>
|
||||
<el-button type="success" size="large" :loading="loading" @click="preSubmitCheck" icon="Select">
|
||||
<el-button v-if="userStore.hasPermission('op_return:operation')" @click="clearAll" icon="Refresh">清空</el-button>
|
||||
<el-button v-if="userStore.hasPermission('op_return:operation')" type="success" size="large" :loading="loading" @click="preSubmitCheck" icon="Select">
|
||||
确认归还
|
||||
</el-button>
|
||||
</div>
|
||||
@ -161,6 +173,25 @@ import { uploadFile } from '@/api/common/upload'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Scissor, EditPen, Delete, CameraFilled, Close, Refresh, Select } from '@element-plus/icons-vue'
|
||||
import QrScanner from '@/components/QrScanner/index.vue'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
|
||||
const userStore = useUserStore()
|
||||
|
||||
// 列与权限Code的映射关系(数据库中的code)
|
||||
const permissionMap: Record<string, string> = {
|
||||
borrower_name: 'op_return:borrower_name',
|
||||
sku: 'op_return:sku',
|
||||
return_location: 'op_return:return_location',
|
||||
}
|
||||
|
||||
// 检查列权限
|
||||
const hasColumnPermission = (prop: string) => {
|
||||
if (userStore.role === 'SUPER_ADMIN' || userStore.username === 'IRIS') {
|
||||
return true
|
||||
}
|
||||
const code = permissionMap[prop]
|
||||
return code ? userStore.hasPermission(code) : false
|
||||
}
|
||||
|
||||
// --- 状态 ---
|
||||
const barcode = ref('')
|
||||
@ -507,4 +538,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