功能:新增托盘业务操作页面
This commit is contained in:
@@ -0,0 +1,461 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-form ref="queryRef" :model="queryParams" :inline="true">
|
||||
<el-form-item label="装填单号" prop="fillingNo">
|
||||
<el-input v-model="queryParams.fillingNo" placeholder="请输入装填单号" clearable maxlength="64" @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="托盘编号" prop="palletCode">
|
||||
<el-input v-model="queryParams.palletCode" placeholder="请输入托盘编号" clearable maxlength="64" @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="生产批次" prop="batchNo">
|
||||
<el-input v-model="queryParams.batchNo" placeholder="请输入生产批次" clearable maxlength="64" @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" :disabled="operationPending" @click="handleQuery" v-hasPermi="['business:filling:list']">搜索</el-button>
|
||||
<el-button icon="Refresh" :disabled="operationPending" @click="resetQuery" v-hasPermi="['business:filling:list']">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
v-if="canAddFilling"
|
||||
class="business-action"
|
||||
type="primary"
|
||||
plain
|
||||
icon="Plus"
|
||||
:disabled="operationPending"
|
||||
@click="handleOpenFilling"
|
||||
v-hasPermi="['business:filling:add']"
|
||||
>托盘装填</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-alert v-if="loadError" :title="loadError" type="error" :closable="false" show-icon class="mb8" />
|
||||
<el-table v-loading="loading" empty-text="暂无数据" :data="fillingList">
|
||||
<el-table-column label="装填单号" prop="fillingNo" min-width="170" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="托盘" min-width="120">
|
||||
<template #default="scope">{{ scope.row.palletCode || `#${scope.row.palletId}` }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="仓库ID" prop="warehouseId" width="110" />
|
||||
<el-table-column label="菌种" min-width="160">
|
||||
<template #default="scope">{{ scope.row.strainNameSnapshot || scope.row.strainCodeSnapshot || `#${scope.row.strainId}` }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="生产批次" prop="batchNo" min-width="130" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="包装规格" prop="packageSpec" min-width="120" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="初始包数" prop="initialPackages" width="100" align="right" />
|
||||
<el-table-column label="操作时间" prop="operatedAt" width="180">
|
||||
<template #default="scope">{{ parseTime(scope.row.operatedAt) }}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination
|
||||
v-show="total > 0"
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNum"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
|
||||
<el-dialog
|
||||
v-model="open"
|
||||
title="托盘装填"
|
||||
width="min(680px, 94vw)"
|
||||
append-to-body
|
||||
:close-on-click-modal="!operationPending"
|
||||
:close-on-press-escape="!operationPending"
|
||||
:show-close="!operationPending"
|
||||
:before-close="handleDialogBeforeClose"
|
||||
>
|
||||
<el-alert v-if="optionError" :title="optionError" type="error" :closable="false" show-icon class="mb8" />
|
||||
<el-alert v-if="scanError" :title="scanError" type="error" :closable="false" show-icon class="mb8" />
|
||||
<el-alert v-if="formError" :title="formError" type="error" :closable="false" show-icon class="mb8" />
|
||||
|
||||
<el-form ref="fillingRef" :model="form" :rules="rules" label-width="96px">
|
||||
<el-form-item label="扫描托盘">
|
||||
<pallet-scan-input
|
||||
:disabled="operationPending || optionLoading"
|
||||
:loading="scanLoading"
|
||||
@scan="handleFillingScan"
|
||||
@invalid="handleScanInvalid"
|
||||
@duplicate="handleScanDuplicate"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-descriptions v-if="scannedPallet" :column="2" border class="scan-summary">
|
||||
<el-descriptions-item label="托盘">{{ scannedPallet.palletCode || `#${scannedPallet.palletId}` }}</el-descriptions-item>
|
||||
<el-descriptions-item label="仓库">#{{ scannedPallet.warehouseId }}</el-descriptions-item>
|
||||
<el-descriptions-item label="流转状态">{{ scannedPallet.flowState }}</el-descriptions-item>
|
||||
<el-descriptions-item label="托盘容量">{{ scannedPallet.capacityPackages }} 包</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<el-empty v-else description="请先扫描待装填托盘" :image-size="72" />
|
||||
|
||||
<el-form-item label="菌种" prop="strainId">
|
||||
<el-select
|
||||
v-model="form.strainId"
|
||||
class="business-control"
|
||||
placeholder="请选择菌种"
|
||||
filterable
|
||||
style="width: 100%"
|
||||
:loading="optionLoading"
|
||||
:disabled="operationPending || optionLoading"
|
||||
>
|
||||
<el-option
|
||||
v-for="option in strainOptions"
|
||||
:key="option.id"
|
||||
:label="option.name || `#${option.id}`"
|
||||
:value="option.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="生产批次" prop="batchNo">
|
||||
<el-input
|
||||
v-model="form.batchNo"
|
||||
class="business-control"
|
||||
maxlength="64"
|
||||
show-word-limit
|
||||
placeholder="请输入生产批次"
|
||||
:disabled="operationPending"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="包装规格" prop="packageSpec">
|
||||
<el-input
|
||||
v-model="form.packageSpec"
|
||||
class="business-control"
|
||||
maxlength="64"
|
||||
show-word-limit
|
||||
placeholder="请输入包装规格"
|
||||
:disabled="operationPending"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="初始包数" prop="initialPackages">
|
||||
<el-input-number
|
||||
v-model="form.initialPackages"
|
||||
class="business-control"
|
||||
:min="1"
|
||||
:precision="0"
|
||||
controls-position="right"
|
||||
style="width: 100%"
|
||||
:disabled="operationPending"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button
|
||||
class="business-action"
|
||||
type="primary"
|
||||
:loading="submitLoading"
|
||||
:disabled="operationPending || scanLoading || optionLoading || !scannedPallet"
|
||||
@click="submitFilling"
|
||||
v-hasPermi="['business:filling:add']"
|
||||
>确认装填</el-button>
|
||||
<el-button class="business-action" :disabled="operationPending" @click="cancelFilling">取消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="BusinessFilling">
|
||||
import PalletScanInput from '@/components/PalletScanInput'
|
||||
import { createIdempotencyKey } from '@/utils/palletScan'
|
||||
import { addFilling, getFillingStrainOptions, listFilling, scanFillingPallet } from '@/api/business/filling'
|
||||
|
||||
const { proxy } = getCurrentInstance()
|
||||
const fillingList = ref([])
|
||||
const strainOptions = ref([])
|
||||
const scannedPallet = ref(null)
|
||||
const loading = ref(false)
|
||||
const optionLoading = ref(false)
|
||||
const scanLoading = ref(false)
|
||||
const submitLoading = ref(false)
|
||||
const loadError = ref('')
|
||||
const optionError = ref('')
|
||||
const scanError = ref('')
|
||||
const formError = ref('')
|
||||
const open = ref(false)
|
||||
const total = ref(0)
|
||||
const fillingIdempotencyKey = ref('')
|
||||
const canAddFilling = computed(() => proxy.$auth.hasPermiAnd(['business:filling:query', 'business:filling:add']))
|
||||
const operationPending = computed(() => submitLoading.value)
|
||||
let listGeneration = 0
|
||||
let optionGeneration = 0
|
||||
let scanGeneration = 0
|
||||
let dialogSession = 0
|
||||
|
||||
function validateRequiredText(rule, value, callback) {
|
||||
const labels = { batchNo: '生产批次', packageSpec: '包装规格' }
|
||||
if (typeof value !== 'string' || value.trim() === '') {
|
||||
callback(new Error(`${labels[rule.field]}不能为空`))
|
||||
return
|
||||
}
|
||||
callback()
|
||||
}
|
||||
|
||||
function validatePositiveInteger(rule, value, callback) {
|
||||
if (!Number.isInteger(value) || value <= 0) {
|
||||
callback(new Error('初始包数必须为正整数'))
|
||||
return
|
||||
}
|
||||
callback()
|
||||
}
|
||||
|
||||
const data = reactive({
|
||||
form: { qrToken: '', warehouseId: undefined, strainId: undefined, batchNo: '', packageSpec: '', initialPackages: undefined },
|
||||
queryParams: { pageNum: 1, pageSize: 10, fillingNo: undefined, palletCode: undefined, batchNo: undefined },
|
||||
rules: {
|
||||
strainId: [{ required: true, message: '请选择菌种', trigger: 'change' }],
|
||||
batchNo: [{ required: true, validator: validateRequiredText, trigger: 'blur' }],
|
||||
packageSpec: [{ required: true, validator: validateRequiredText, trigger: 'blur' }],
|
||||
initialPackages: [{ required: true, validator: validatePositiveInteger, trigger: 'blur' }]
|
||||
}
|
||||
})
|
||||
const { form, queryParams, rules } = toRefs(data)
|
||||
|
||||
function isCurrentListRequest(listContext) {
|
||||
return listContext.generation === listGeneration
|
||||
}
|
||||
|
||||
function getList() {
|
||||
const listContext = {
|
||||
generation: ++listGeneration,
|
||||
query: { ...queryParams.value }
|
||||
};
|
||||
loading.value = true
|
||||
loadError.value = ''
|
||||
listFilling(listContext.query).then((response) => {
|
||||
if (!isCurrentListRequest(listContext)) {
|
||||
return;
|
||||
}
|
||||
fillingList.value = response.rows
|
||||
total.value = response.total
|
||||
}).catch(() => {
|
||||
if (!isCurrentListRequest(listContext)) {
|
||||
return;
|
||||
}
|
||||
fillingList.value = []
|
||||
total.value = 0
|
||||
loadError.value = '装填记录加载失败,请稍后重试'
|
||||
}).finally(() => {
|
||||
if (!isCurrentListRequest(listContext)) {
|
||||
return;
|
||||
}
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function invalidateOptionRequest() {
|
||||
optionGeneration += 1
|
||||
optionLoading.value = false
|
||||
}
|
||||
|
||||
function isCurrentOptionRequest(optionContext) {
|
||||
return optionContext.generation === optionGeneration && optionContext.dialogSession === dialogSession
|
||||
}
|
||||
|
||||
function loadStrainOptions() {
|
||||
const optionContext = { generation: ++optionGeneration, dialogSession }
|
||||
optionLoading.value = true
|
||||
optionError.value = ''
|
||||
getFillingStrainOptions().then((response) => {
|
||||
if (!isCurrentOptionRequest(optionContext)) return
|
||||
strainOptions.value = Array.isArray(response.data) ? response.data : []
|
||||
}).catch(() => {
|
||||
if (!isCurrentOptionRequest(optionContext)) return
|
||||
strainOptions.value = []
|
||||
optionError.value = '菌种选项加载失败,请稍后重试'
|
||||
}).finally(() => {
|
||||
if (!isCurrentOptionRequest(optionContext)) return
|
||||
optionLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function invalidateScanRequest() {
|
||||
scanGeneration += 1
|
||||
scanLoading.value = false
|
||||
}
|
||||
|
||||
function isCurrentScanRequest(scanContext) {
|
||||
return scanContext.generation === scanGeneration && scanContext.dialogSession === dialogSession
|
||||
}
|
||||
|
||||
function clearFillingIdempotencyKey() {
|
||||
fillingIdempotencyKey.value = ''
|
||||
}
|
||||
|
||||
function ensureFillingIdempotencyKey() {
|
||||
if (!fillingIdempotencyKey.value) {
|
||||
fillingIdempotencyKey.value = createIdempotencyKey()
|
||||
}
|
||||
return fillingIdempotencyKey.value
|
||||
}
|
||||
|
||||
function resetFillingOperation() {
|
||||
dialogSession += 1
|
||||
invalidateOptionRequest()
|
||||
invalidateScanRequest()
|
||||
clearFillingIdempotencyKey()
|
||||
scannedPallet.value = null
|
||||
form.value = { qrToken: '', warehouseId: undefined, strainId: undefined, batchNo: '', packageSpec: '', initialPackages: undefined }
|
||||
optionError.value = ''
|
||||
scanError.value = ''
|
||||
formError.value = ''
|
||||
proxy.resetForm('fillingRef')
|
||||
}
|
||||
|
||||
function handleOpenFilling() {
|
||||
if (!canAddFilling.value || operationPending.value) return
|
||||
resetFillingOperation()
|
||||
open.value = true
|
||||
loadStrainOptions()
|
||||
}
|
||||
|
||||
function cancelFilling() {
|
||||
if (operationPending.value) return
|
||||
clearFillingIdempotencyKey()
|
||||
open.value = false
|
||||
resetFillingOperation()
|
||||
}
|
||||
|
||||
function handleDialogBeforeClose(done) {
|
||||
if (operationPending.value) return
|
||||
resetFillingOperation()
|
||||
done()
|
||||
}
|
||||
|
||||
function handleQuery() {
|
||||
queryParams.value.pageNum = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
function resetQuery() {
|
||||
proxy.resetForm('queryRef')
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
function handleFillingScan(qrToken) {
|
||||
if (operationPending.value || scanLoading.value) return
|
||||
clearFillingIdempotencyKey()
|
||||
scannedPallet.value = null
|
||||
form.value.qrToken = ''
|
||||
form.value.warehouseId = undefined
|
||||
const scanContext = { generation: ++scanGeneration, dialogSession, qrToken }
|
||||
scanLoading.value = true
|
||||
scanError.value = ''
|
||||
scanFillingPallet(qrToken).then((response) => {
|
||||
if (!isCurrentScanRequest(scanContext)) return
|
||||
const pallet = response.data
|
||||
if (pallet.flowState !== 'EMPTY_IN_WAREHOUSE') {
|
||||
scanError.value = '该托盘当前不可装填'
|
||||
return
|
||||
}
|
||||
scannedPallet.value = {
|
||||
palletId: pallet.palletId,
|
||||
palletCode: pallet.palletCode || `#${pallet.palletId}`,
|
||||
warehouseId: pallet.warehouseId,
|
||||
flowState: pallet.flowState,
|
||||
capacityPackages: pallet.capacityPackages
|
||||
}
|
||||
form.value.qrToken = qrToken
|
||||
form.value.warehouseId = pallet.warehouseId
|
||||
}).catch(() => {
|
||||
if (!isCurrentScanRequest(scanContext)) return
|
||||
scanError.value = '托盘扫码校验失败,请检查后重试'
|
||||
}).finally(() => {
|
||||
if (!isCurrentScanRequest(scanContext)) return
|
||||
scanLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function handleScanInvalid(message) {
|
||||
clearFillingIdempotencyKey()
|
||||
scannedPallet.value = null
|
||||
form.value.qrToken = ''
|
||||
form.value.warehouseId = undefined
|
||||
scanError.value = message
|
||||
}
|
||||
|
||||
function handleScanDuplicate() {
|
||||
proxy.$modal.msgWarning('扫码过快,请勿重复扫描')
|
||||
}
|
||||
|
||||
function submitFilling() {
|
||||
if (operationPending.value || scanLoading.value || optionLoading.value) return
|
||||
submitLoading.value = true
|
||||
proxy.$refs.fillingRef.validate((valid) => {
|
||||
if (!valid) {
|
||||
submitLoading.value = false
|
||||
return
|
||||
}
|
||||
if (!scannedPallet.value || !form.value.qrToken || !form.value.warehouseId) {
|
||||
formError.value = '请先扫描待装填托盘'
|
||||
submitLoading.value = false
|
||||
return
|
||||
}
|
||||
const idempotencyKey = ensureFillingIdempotencyKey()
|
||||
formError.value = ''
|
||||
addFilling({
|
||||
qrToken: form.value.qrToken,
|
||||
warehouseId: form.value.warehouseId,
|
||||
strainId: form.value.strainId,
|
||||
batchNo: form.value.batchNo.trim(),
|
||||
packageSpec: form.value.packageSpec.trim(),
|
||||
initialPackages: form.value.initialPackages,
|
||||
idempotencyKey: idempotencyKey
|
||||
}).then((response) => {
|
||||
clearFillingIdempotencyKey()
|
||||
proxy.$modal.msgSuccess(response.msg || '托盘装填成功')
|
||||
open.value = false
|
||||
resetFillingOperation()
|
||||
getList()
|
||||
}).catch(() => {
|
||||
formError.value = '装填提交失败;修正业务输入会生成新幂等键,直接重试将复用原键'
|
||||
}).finally(() => {
|
||||
submitLoading.value = false
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
watch(
|
||||
() => [form.value.qrToken, form.value.warehouseId, form.value.strainId, form.value.batchNo, form.value.packageSpec, form.value.initialPackages],
|
||||
() => {
|
||||
if (!submitLoading.value) clearFillingIdempotencyKey()
|
||||
}
|
||||
)
|
||||
|
||||
getList();
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.scan-summary {
|
||||
margin: 0 0 18px 96px;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
:deep(.el-dialog) {
|
||||
width: 94vw !important;
|
||||
}
|
||||
|
||||
.scan-summary {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
:deep(.business-control .el-input__wrapper),
|
||||
:deep(.business-action) {
|
||||
min-height: 48px;
|
||||
}
|
||||
|
||||
:deep(.el-form-item__label) {
|
||||
float: none;
|
||||
display: block;
|
||||
width: 100% !important;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
:deep(.el-form-item__content) {
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user