优化
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onBeforeUnmount, reactive, ref, watch } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { AlertTriangle, Image, RefreshCw, Ruler, Save, Send } from '@lucide/vue'
|
||||
import { ElMessage, ElMessageBox, type UploadFile } from 'element-plus'
|
||||
import { AlertTriangle, Image, Plus, RefreshCw, Ruler, Save, Send, Upload } from '@lucide/vue'
|
||||
import { useAccessPreviewStore } from '@/core/access/accessPreview'
|
||||
import { useUnsavedWorkRegistration } from '@/core/unsaved-work/useUnsavedWorkRegistration'
|
||||
import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue'
|
||||
@@ -9,7 +9,7 @@ import LoadErrorState from '@/shared/components/LoadErrorState.vue'
|
||||
import StatusBadge from '@/shared/components/StatusBadge.vue'
|
||||
import { getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http'
|
||||
import { inboundProcessStatusLabels, inboundStatusLabel } from '../status'
|
||||
import { downloadInboundMeasurementImage, getInboundMeasurement, submitInboundMeasurement, updateConfirmedPackageCount } from './api'
|
||||
import { createManualCartonScan, downloadInboundMeasurementImage, getInboundMeasurement, submitInboundMeasurement, updateConfirmedPackageCount } from './api'
|
||||
import type {
|
||||
InboundMeasurementDetail,
|
||||
InboundMeasurementItem,
|
||||
@@ -42,10 +42,25 @@ interface MeasurementFormModel {
|
||||
items: EditableMeasurementItem[]
|
||||
}
|
||||
|
||||
interface ManualCartonFormModel {
|
||||
boxCode: string
|
||||
length: number | null
|
||||
width: number | null
|
||||
height: number | null
|
||||
weight: number | null
|
||||
scannedShipmentId: string
|
||||
machineNo: string
|
||||
measuredAt: string
|
||||
}
|
||||
|
||||
const access = useAccessPreviewStore()
|
||||
const loading = ref(false)
|
||||
const saving = ref(false)
|
||||
const submitting = ref(false)
|
||||
const manualEntryOpen = ref(false)
|
||||
const manualEntrySubmitting = ref(false)
|
||||
const manualEntryImage = ref<File | null>(null)
|
||||
const manualEntryErrors = ref<string[]>([])
|
||||
const detail = ref<InboundMeasurementDetail | null>(null)
|
||||
const loadError = ref<ApiErrorDetails | null>(null)
|
||||
const operationError = ref<ApiErrorDetails | null>(null)
|
||||
@@ -69,6 +84,17 @@ const form = reactive<MeasurementFormModel>({
|
||||
items: [],
|
||||
})
|
||||
|
||||
const manualForm = reactive<ManualCartonFormModel>({
|
||||
boxCode: '',
|
||||
length: null,
|
||||
width: null,
|
||||
height: null,
|
||||
weight: null,
|
||||
scannedShipmentId: '',
|
||||
machineNo: 'MANUAL-ADMIN',
|
||||
measuredAt: '',
|
||||
})
|
||||
|
||||
const canView = computed(() => access.can('platform:firstleg:inbound:measure:view'))
|
||||
const canEdit = computed(() => access.can('platform:firstleg:inbound:measure:edit'))
|
||||
const canSubmit = computed(() => access.can('platform:firstleg:inbound:measure:submit'))
|
||||
@@ -77,6 +103,8 @@ const hasContext = computed(() => Boolean(customerId.value && props.inboundId))
|
||||
const countEditableProcess = computed(() => ['WAIT_MEASURE', 'MEASURING', 'MEASURE_PENDING', 'MEASURE_REJECTED'].includes(detail.value?.processStatus || ''))
|
||||
const submitEditableProcess = computed(() => ['WAIT_MEASURE', 'MEASURING', 'MEASURE_REJECTED'].includes(detail.value?.processStatus || ''))
|
||||
const mayMaintain = computed(() => canEdit.value && countEditableProcess.value && !loading.value && !submitting.value)
|
||||
const manualEntryEditableProcess = computed(() => ['WAIT_MEASURE', 'MEASURING', 'MEASURE_REJECTED'].includes(detail.value?.processStatus || ''))
|
||||
const mayCreateManualEntry = computed(() => canEdit.value && manualEntryEditableProcess.value && !loading.value && !saving.value && !submitting.value && !isDirty.value)
|
||||
const confirmedPackageCount = computed(() => form.items.reduce((sum, item) => sum + (Number.isFinite(item.packageCount) ? item.packageCount : 0), 0))
|
||||
const expectedPackageCount = computed(() => detail.value?.originalPackageCount ?? null)
|
||||
const packageCountDifference = computed(() => expectedPackageCount.value == null ? null : confirmedPackageCount.value - expectedPackageCount.value)
|
||||
@@ -108,6 +136,81 @@ function nextItemKey() {
|
||||
return `measurement-item-${itemSequence}`
|
||||
}
|
||||
|
||||
function localDateTimeNow() {
|
||||
const now = new Date()
|
||||
const pad = (value: number) => String(value).padStart(2, '0')
|
||||
return `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}T${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`
|
||||
}
|
||||
|
||||
function resetManualEntry() {
|
||||
manualForm.boxCode = ''
|
||||
manualForm.length = null
|
||||
manualForm.width = null
|
||||
manualForm.height = null
|
||||
manualForm.weight = null
|
||||
manualForm.scannedShipmentId = ''
|
||||
manualForm.machineNo = 'MANUAL-ADMIN'
|
||||
manualForm.measuredAt = localDateTimeNow()
|
||||
manualEntryImage.value = null
|
||||
manualEntryErrors.value = []
|
||||
}
|
||||
|
||||
function openManualEntry() {
|
||||
if (!mayCreateManualEntry.value) {
|
||||
if (isDirty.value) ElMessage.warning('请先保存当前确认箱数,再录入新的箱规')
|
||||
return
|
||||
}
|
||||
resetManualEntry()
|
||||
manualEntryOpen.value = true
|
||||
}
|
||||
|
||||
function selectManualImage(uploadFile: UploadFile) {
|
||||
const file = uploadFile.raw
|
||||
manualEntryImage.value = file || null
|
||||
}
|
||||
|
||||
function validateManualEntry() {
|
||||
const errors: string[] = []
|
||||
if (!manualForm.boxCode.trim()) errors.push('请输入箱号')
|
||||
if (manualForm.boxCode.trim().length > 128) errors.push('箱号不能超过 128 个字符')
|
||||
if (!(manualForm.length != null && manualForm.length > 0)) errors.push('测量长必须大于 0')
|
||||
if (!(manualForm.width != null && manualForm.width > 0)) errors.push('测量宽必须大于 0')
|
||||
if (!(manualForm.height != null && manualForm.height > 0)) errors.push('测量高必须大于 0')
|
||||
if (!(manualForm.weight != null && manualForm.weight > 0)) errors.push('单箱毛重必须大于 0')
|
||||
if (!manualForm.measuredAt) errors.push('请选择测量时间')
|
||||
if (!manualEntryImage.value) errors.push('请上传当前箱测量图片')
|
||||
if (manualEntryImage.value && manualEntryImage.value.size > 5 * 1024 * 1024) errors.push('测量图片不能超过 5 MB')
|
||||
manualEntryErrors.value = errors
|
||||
return errors.length === 0
|
||||
}
|
||||
|
||||
async function submitManualEntry() {
|
||||
if (!mayCreateManualEntry.value || !validateManualEntry() || manualForm.weight == null) return
|
||||
manualEntrySubmitting.value = true
|
||||
operationError.value = null
|
||||
try {
|
||||
await createManualCartonScan(customerId.value, props.inboundId, {
|
||||
boxCode: manualForm.boxCode.trim(),
|
||||
length: manualForm.length!,
|
||||
width: manualForm.width!,
|
||||
height: manualForm.height!,
|
||||
weight: manualForm.weight,
|
||||
scannedShipmentId: manualForm.scannedShipmentId.trim() || null,
|
||||
machineNo: manualForm.machineNo.trim() || 'MANUAL-ADMIN',
|
||||
measuredAt: manualForm.measuredAt,
|
||||
}, manualEntryImage.value!)
|
||||
manualEntryOpen.value = false
|
||||
ElMessage.success('箱规已录入,服务端计算结果已更新')
|
||||
await loadMeasurement()
|
||||
emit('changed')
|
||||
} catch (error) {
|
||||
const details = setOperationError(error, '手动箱规录入失败,请检查后重试')
|
||||
ElMessage.error(details.message)
|
||||
} finally {
|
||||
manualEntrySubmitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function editableItem(item: InboundMeasurementItem): EditableMeasurementItem {
|
||||
return {
|
||||
itemId: String(item.id),
|
||||
@@ -453,6 +556,7 @@ onBeforeUnmount(releaseImagePreview)
|
||||
<section class="measurement-section measurement-items-section">
|
||||
<div class="measurement-section__heading">
|
||||
<div><h3>扫描箱测量数据明细</h3><p>每条记录对应一个实际扫描箱;人工只确认该条记录代表的箱数,设备实测值不可在此修改。</p></div>
|
||||
<el-button v-if="canEdit" type="primary" plain :disabled="!mayCreateManualEntry" @click="openManualEntry"><Plus :size="15" />手动录入箱规</el-button>
|
||||
</div>
|
||||
|
||||
<el-empty v-if="!form.items.length" description="暂无扫描箱测量数据" />
|
||||
@@ -503,6 +607,34 @@ onBeforeUnmount(releaseImagePreview)
|
||||
<el-empty v-else description="暂无可预览图片" />
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog v-model="manualEntryOpen" title="手动录入箱规" width="min(680px, 92vw)" destroy-on-close>
|
||||
<el-alert v-if="manualEntryErrors.length" type="error" :closable="false" show-icon class="manual-entry-alert" title="请修正以下问题">
|
||||
<ul><li v-for="error in manualEntryErrors" :key="error">{{ error }}</li></ul>
|
||||
</el-alert>
|
||||
<el-form label-position="top" class="manual-entry-form" @submit.prevent="submitManualEntry">
|
||||
<div class="measurement-form-grid">
|
||||
<el-form-item label="箱号 *"><el-input v-model="manualForm.boxCode" placeholder="请输入已打印箱唛上的箱号" /></el-form-item>
|
||||
<el-form-item label="测量长(cm) *"><el-input-number v-model="manualForm.length" :min="0.001" :precision="3" controls-position="right" /></el-form-item>
|
||||
<el-form-item label="测量宽(cm) *"><el-input-number v-model="manualForm.width" :min="0.001" :precision="3" controls-position="right" /></el-form-item>
|
||||
<el-form-item label="测量高(cm) *"><el-input-number v-model="manualForm.height" :min="0.001" :precision="3" controls-position="right" /></el-form-item>
|
||||
<el-form-item label="单箱毛重(kg) *"><el-input-number v-model="manualForm.weight" :min="0.001" :precision="3" controls-position="right" /></el-form-item>
|
||||
<el-form-item label="测量时间 *"><el-date-picker v-model="manualForm.measuredAt" type="datetime" value-format="YYYY-MM-DDTHH:mm:ss" format="YYYY-MM-DD HH:mm:ss" /></el-form-item>
|
||||
<el-form-item label="设备/来源编号"><el-input v-model="manualForm.machineNo" maxlength="64" /></el-form-item>
|
||||
<el-form-item label="Shipment ID"><el-input v-model="manualForm.scannedShipmentId" maxlength="64" /></el-form-item>
|
||||
</div>
|
||||
<el-form-item label="当前箱测量图片 *">
|
||||
<el-upload class="manual-entry-upload" :auto-upload="false" :limit="1" accept="image/*" :on-change="selectManualImage" :on-remove="() => { manualEntryImage = null }">
|
||||
<el-button><Upload :size="15" />选择图片</el-button>
|
||||
<template #tip><div class="el-upload__tip">支持图片格式,大小不超过 5 MB</div></template>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button :disabled="manualEntrySubmitting" @click="manualEntryOpen = false">取消</el-button>
|
||||
<el-button type="primary" :loading="manualEntrySubmitting" @click="submitManualEntry">保存箱规</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
</section>
|
||||
</template>
|
||||
@@ -545,6 +677,10 @@ onBeforeUnmount(releaseImagePreview)
|
||||
.measurement-image-field :deep(.el-button) { width: 100%; margin: 0; }
|
||||
.measurement-image-preview { display: grid; min-height: 240px; place-items: center; }
|
||||
.measurement-image-preview img { display: block; width: auto; max-width: 100%; max-height: 68vh; object-fit: contain; }
|
||||
.manual-entry-alert { margin-bottom: 16px; }
|
||||
.manual-entry-alert ul { margin: 6px 0 0; padding-left: 18px; }
|
||||
.manual-entry-form :deep(.el-input-number), .manual-entry-form :deep(.el-date-editor) { width: 100%; }
|
||||
.manual-entry-upload { width: 100%; }
|
||||
.measurement-items-section { background: #fafbfc; }
|
||||
.measurement-item { overflow: hidden; margin-top: 14px; border: 1px solid var(--cc-border); border-radius: 9px; background: #fff; }
|
||||
.measurement-item.has-error { border-color: var(--el-color-danger-light-5); }
|
||||
|
||||
Reference in New Issue
Block a user