feat: 完成WMS系统权限体系与业务功能迭代

本提交完成了WMS系统的多维度优化升级:
1. 新增RBAC权限系统,支持角色/菜单/按钮三级权限管控
2. 重构库存盘点、物料管理、区域维护等模块的查询筛选与展示逻辑
3. 优化入库/出库/质检等业务流程,完善数据冗余与业务闭环
4. 移除旧装箱表,将装箱逻辑合并到出库主表
5. 新增前端权限判断工具、导出工具与端到端测试用例
6. 补充完善各类注释与数据库字段说明
This commit is contained in:
SunYF
2026-09-03 14:20:37 +08:00
parent 665ff257dc
commit 572fa975bc
101 changed files with 14531 additions and 4692 deletions
@@ -1,5 +1,5 @@
<script setup>
import { nextTick, onMounted, reactive, ref } from 'vue'
import { computed, nextTick, onMounted, reactive, ref } from 'vue'
import { ElMessage } from 'element-plus'
import request from '../utils/request'
import { getRealName } from '../utils/auth'
@@ -9,81 +9,111 @@ import { helpInspection } from '../help'
const targetType = ref('BATCH')
const status = ref('合格')
const inspector = ref(getRealName())
const submitting = ref(false)
/* ---------- 单个录入(扫码) ---------- */
const scanRef = ref()
const targetId = ref('')
const submitting1 = ref(false)
// 检验量化字段
const inspectQty = ref(0)
const passQty = ref(0)
const checkDesc = ref('')
const failReason = ref('')
onMounted(() => nextTick(() => scanRef.value?.focus()))
// 待检编号列表(合并单/批量,统一一次提交)
const targetList = ref([])
const scanInput = ref('')
const pickerValue = ref('')
async function submitSingle() {
const id = targetId.value.trim()
if (!id) {
ElMessage.warning('请扫入或输入批次号或 SN 序列号')
return
}
if (!inspector.value.trim()) {
ElMessage.warning('请填写检验员')
return
}
submitting1.value = true
// 输入框标题/占位随目标类型变化(而非仅一行小字提示)
const inputLabel = computed(() => (targetType.value === 'BATCH' ? '批次号' : 'SN 序列号'))
const inputPlaceholder = computed(() =>
targetType.value === 'BATCH'
? '扫入或粘贴批次号,可用 逗号/分号/空格/回车 分隔,一次可录多条'
: '扫入或粘贴 SN 序列号,可用 逗号/分号/空格/回车 分隔,一次可录多条'
)
// 库存拾取器:直接选已存在的批次号/SN,避免手输(垃圾设计)
const pickerOptions = ref([])
async function loadPicker() {
try {
await request.post('/inspection/create', {
targetType: targetType.value,
targetId: id,
status: status.value,
inspector: inspector.value.trim()
const mm = targetType.value === 'BATCH' ? 1 : 2
const data = await request.get('/stock/query', {
params: { manageMode: mm, page: 1, pageSize: 500 }
})
ElMessage.success(`检验已记录:${targetType.value} ${id}${status.value}`)
targetId.value = ''
nextTick(() => scanRef.value?.focus())
} finally {
submitting1.value = false
pickerOptions.value = data?.list || []
} catch (e) {
pickerOptions.value = []
}
}
onMounted(loadPicker)
/* ---------- 批量翻转 ---------- */
const batchText = ref('')
const submitting2 = ref(false)
function parseTargets() {
return [...new Set(String(batchText.value).split(/\s+/).map(s => s.trim()).filter(Boolean))]
function pickerLabel(opt) {
const id = targetType.value === 'BATCH' ? opt.batchNo : opt.snCode
return `${opt.materialCode || ''}${id || ''}|质量:${opt.qualityStatus || '未检'}`
}
async function submitBatch() {
const ids = parseTargets()
if (!ids.length) {
ElMessage.warning('请录入待检编号(每行一个)')
function addFromInput() {
const arr = String(scanInput.value)
.split(/[,.。;;、\s]+/)
.map((s) => s.trim())
.filter(Boolean)
for (const v of arr) {
if (!targetList.value.includes(v)) targetList.value.push(v)
}
scanInput.value = ''
}
function addFromPicker() {
if (!pickerValue.value) return
if (!targetList.value.includes(pickerValue.value)) targetList.value.push(pickerValue.value)
pickerValue.value = ''
}
function removeAt(i) {
targetList.value.splice(i, 1)
}
function clearAll() {
targetList.value = []
}
async function submitInspection() {
if (!targetList.value.length) {
ElMessage.warning('请先录入或选择待检编号')
return
}
if (!inspector.value.trim()) {
ElMessage.warning('请填写检验员')
return
}
submitting2.value = true
submitting.value = true
try {
const data = await request.post('/inspection/batch-flip', {
targetType: targetType.value,
targetIds: ids,
targetIds: [...targetList.value],
status: status.value,
inspectQty: Number(inspectQty.value) || 0,
passQty: Number(passQty.value) || 0,
checkDesc: checkDesc.value.trim(),
failReason: failReason.value.trim(),
inspector: inspector.value.trim()
})
ElMessage.success(`批量检验完成:共处理 ${data?.flipped ?? ids.length}`)
batchText.value = ''
const n = data?.flipped ?? targetList.value.length
ElMessage.success(`检验已记录:共 ${n} 条 → ${status.value}`)
clearAll()
inspectQty.value = 0
passQty.value = 0
checkDesc.value = ''
failReason.value = ''
} finally {
submitting2.value = false
submitting.value = false
}
}
</script>
<template>
<el-card shadow="never" style="max-width:760px;margin:0 auto">
<el-form label-width="110px">
<el-card shadow="never" style="max-width:860px;margin:0 auto">
<el-form label-width="96px">
<!-- 目标类型直接改变输入框标题与占位直观体现当前录入对象 -->
<el-form-item label="目标类型">
<el-radio-group v-model="targetType">
<el-radio-button value="BATCH">批次 BATCH</el-radio-button>
<el-radio-button value="SN">序列号 SN</el-radio-button>
<el-radio-group v-model="targetType" @change="loadPicker">
<el-radio-button value="BATCH">批次 (结构件)</el-radio-button>
<el-radio-button value="SN">序列号 SN (精密件)</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item label="检验结论">
@@ -96,26 +126,61 @@ async function submitBatch() {
<el-input v-model="inspector" placeholder="检验员姓名" clearable style="width:260px" />
</el-form-item>
<el-divider content-position="left">单个录入扫码</el-divider>
<el-form-item label="扫码输入">
<el-input ref="scanRef" v-model="targetId" autofocus clearable
placeholder="扫入批次号或 SN,回车提交"
style="width:420px"
@keyup.enter="submitSingle" />
<span class="tip">当前目标{{ targetType === 'BATCH' ? '批次号' : 'SN 序列号' }}</span>
<el-divider content-position="left">检验量化可空</el-divider>
<el-form-item label="检验数量">
<el-input-number v-model="inspectQty" :min="0" :max="999999" placeholder="抽检件数" style="width:160px" />
</el-form-item>
<el-form-item>
<el-button type="primary" :loading="submitting1" @click="submitSingle">提交检验</el-button>
<el-form-item label="合格数量">
<el-input-number v-model="passQty" :min="0" :max="999999" placeholder="合格件数(≤检验数量)" style="width:160px" />
</el-form-item>
<el-form-item label="检测说明">
<el-input v-model="checkDesc" placeholder="检验项目/方法/标准(可空)" clearable style="width:420px" />
</el-form-item>
<el-form-item v-if="status === '不合格'" label="不合格原因">
<el-input v-model="failReason" type="textarea" :rows="2" placeholder="不合格原因(可空)" style="width:420px" />
</el-form-item>
<el-divider content-position="left">批量翻转</el-divider>
<el-form-item label="批量编号">
<el-input v-model="batchText" type="textarea" :rows="6"
placeholder="每行一个编号,支持扫码枪连续录入" />
<el-divider content-position="left">待检编号可扫/选多条统一提交</el-divider>
<!-- 从库存直接拾取避免手输批次号 -->
<el-form-item :label="`从库存选${inputLabel}`">
<el-select
v-model="pickerValue"
filterable
clearable
:placeholder="`选择已存在的${inputLabel}(来自库存)`"
style="width:100%"
@change="addFromPicker"
>
<el-option v-for="opt in pickerOptions" :key="targetType === 'BATCH' ? opt.batchNo : opt.snCode"
:value="targetType === 'BATCH' ? opt.batchNo : opt.snCode"
:label="pickerLabel(opt)" />
</el-select>
</el-form-item>
<!-- 批量/扫码录入单条也走这里无需单独的"单个录入" -->
<el-form-item :label="`录入${inputLabel}`">
<div style="width:100%">
<el-input v-model="scanInput" type="textarea" :rows="4"
:placeholder="inputPlaceholder" />
<el-button type="primary" plain style="margin-top:6px" :disabled="!scanInput.trim()"
@click="addFromInput">录入到列表</el-button>
<span style="margin-left:8px;color:#909399;font-size:12px">支持扫码枪/粘贴多编号用逗号分号空格或回车分隔</span>
</div>
</el-form-item>
<!-- 已选清单 -->
<el-form-item label="已选清单">
<div style="width:100%">
<el-tag v-for="(v, i) in targetList" :key="v + i" closable type="info"
style="margin:0 8px 8px 0" @close="removeAt(i)">{{ v }}</el-tag>
<span v-if="!targetList.length" style="color:#909399;font-size:12px">尚未选择任何待检编号</span>
</div>
</el-form-item>
<el-form-item>
<el-button type="primary" :loading="submitting2" @click="submitBatch">批量提交</el-button>
<el-button @click="batchText = ''">清空</el-button>
<el-button type="primary" :loading="submitting" @click="submitInspection">提交检验</el-button>
<el-button :disabled="!targetList.length" @click="clearAll">清空</el-button>
</el-form-item>
</el-form>
</el-card>
@@ -124,4 +189,4 @@ async function submitBatch() {
<style scoped>
.tip { margin-left: 10px; color: #909399; font-size: 12px; }
</style>
</style>