Merge branch 'release/test' of http://8.163.30.128:10082/html/admin-web into dev
This commit is contained in:
@@ -161,8 +161,8 @@ const destinationWarehouseValue = computed(() => form.destinationWarehouseCode |
|
||||
const queryFields = computed<QueryField[]>(() => [
|
||||
{ key: 'customerId', type: 'select', label: '客户', width: 220, placeholder: '全部客户', options: customers.value.filter((customer) => customer.status === 'ACTIVE').map((customer) => ({ label: customer.tenantName, value: String(customer.id) })), props: { filterable: true, loading: customersLoading.value } },
|
||||
{ key: 'keyword', type: 'input', label: '关键词', width: 220, placeholder: '柜次号、集装箱号或提单号', props: { 'aria-label': '关键词', maxlength: 128 } },
|
||||
{ key: 'containerType', type: 'select', label: '柜型', width: 140, options: CONTAINER_SPECIFICATIONS.map((item) => ({ label: item.type, value: item.type })), props: { 'aria-label': '柜型' } },
|
||||
{ key: 'status', type: 'select', label: '排柜状态', width: 150, options: [{ label: '待装柜', value: 'WAIT_LOADING' }, { label: '预装柜中', value: 'EDITING' }, { label: '拣货中', value: 'LOADING' }, { label: '装柜完成', value: 'COMPLETED' }], props: { 'aria-label': '排柜状态' } },
|
||||
{ key: 'containerType', type: 'select', label: '柜型', width: 140, placeholder: '请选择箱型', options: CONTAINER_SPECIFICATIONS.map((item) => ({ label: item.type, value: item.type })), props: { 'aria-label': '柜型' } },
|
||||
{ key: 'status', type: 'select', label: '排柜状态', width: 150, placeholder: '请选择货柜状态', options: [{ label: '待装柜', value: 'WAIT_LOADING' }, { label: '预装柜中', value: 'EDITING' }, { label: '拣货中', value: 'LOADING' }, { label: '装柜完成', value: 'COMPLETED' }], props: { 'aria-label': '排柜状态' } },
|
||||
{ key: 'sailingRange', type: 'daterange', label: '预计开船', width: 260, props: { 'aria-label': '预计开船日期范围' } },
|
||||
])
|
||||
const columns: DataTableColumn[] = [
|
||||
|
||||
@@ -294,6 +294,16 @@ function validateDraft() {
|
||||
form.items.forEach((item, index) => {
|
||||
const itemErrors: string[] = []
|
||||
if (!Number.isInteger(item.packageCount) || item.packageCount < 1 || item.packageCount > 1_000_000) itemErrors.push('箱数必须是 1 至 1,000,000 的整数')
|
||||
const dimensions = [
|
||||
['长', item.lengthCm],
|
||||
['宽', item.widthCm],
|
||||
['高', item.heightCm],
|
||||
] as const
|
||||
for (const [label, value] of dimensions) {
|
||||
if (!Number.isFinite(value) || value <= 0) itemErrors.push(`${label}必须是大于 0 的有效数值`)
|
||||
else if (value > 999_999_999.999) itemErrors.push(`${label}超出系统允许上限 999,999,999.999 cm`)
|
||||
else if (Math.abs(value * 1000 - Math.round(value * 1000)) > 1e-6) itemErrors.push(`${label}最多保留 3 位小数`)
|
||||
}
|
||||
if (itemErrors.length) errors[item.clientKey] = itemErrors.map((message) => `第 ${index + 1} 条:${message}`)
|
||||
})
|
||||
if (general.length) errors.general = general
|
||||
@@ -310,7 +320,17 @@ function validateDraft() {
|
||||
}
|
||||
|
||||
function setOperationError(error: unknown, fallback: string) {
|
||||
const details = getApiErrorDetails(error, fallback)
|
||||
const source = getApiErrorDetails(error, fallback)
|
||||
const numericOverflow = /data truncation|out of range|numeric value|decimal|数值.*(过大|超出)|尺寸.*(过大|超限)/i.test(source.message)
|
||||
const largestItem = numericOverflow
|
||||
? [...form.items].sort((left, right) => Math.max(right.lengthCm, right.widthCm, right.heightCm) - Math.max(left.lengthCm, left.widthCm, left.heightCm))[0]
|
||||
: undefined
|
||||
const details = numericOverflow ? {
|
||||
...source,
|
||||
message: largestItem
|
||||
? `箱规尺寸或箱数超出系统允许范围,请检查箱号 ${largestItem.boxCode || largestItem.itemNo}(${formatNumber(largestItem.lengthCm)} × ${formatNumber(largestItem.widthCm)} × ${formatNumber(largestItem.heightCm)} cm)后重试`
|
||||
: '箱规尺寸或箱数超出系统允许范围,请检查长、宽、高和箱数后重试',
|
||||
} : source
|
||||
const conflict = details.message.includes('版本已变化')
|
||||
|| details.message.includes('并发修改')
|
||||
conflictDetected.value = conflict
|
||||
@@ -361,6 +381,7 @@ async function saveDraft() {
|
||||
|
||||
async function submitForReview() {
|
||||
if (!maySubmit.value || !form.measurementId) return
|
||||
if (!validateDraft()) return
|
||||
if (isDirty.value) {
|
||||
ElMessage.warning('请先保存当前修改,再提交审核')
|
||||
return
|
||||
|
||||
@@ -4,6 +4,7 @@ import { ElMessage, ElMessageBox, type FormInstance, type FormRules } from 'elem
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { ArrowLeft, Plus, Save, Trash2 } from '@lucide/vue'
|
||||
import { useAccessPreviewStore } from '@/core/access/accessPreview'
|
||||
import { useWorkspaceTabsStore } from '@/core/workspace-tabs/workspaceTabs'
|
||||
import { getApiErrorDetails } from '@/shared/api/http'
|
||||
import LoadErrorState from '@/shared/components/LoadErrorState.vue'
|
||||
import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue'
|
||||
@@ -52,6 +53,7 @@ const containerKindOptions = [
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const access = useAccessPreviewStore()
|
||||
const workspace = useWorkspaceTabsStore()
|
||||
const id = computed(() => String(route.params.bookingId || ''))
|
||||
const isNew = computed(() => route.name === 'ocean-booking-create' || id.value === 'new')
|
||||
const detail = ref<OceanBookingDetail | null>(null)
|
||||
@@ -220,8 +222,12 @@ async function save() {
|
||||
const saved = isNew.value
|
||||
? await createOceanBooking({ expectedVersion: 0, data: payload })
|
||||
: await updateOceanBookingDraft(id.value, { expectedVersion: detail.value?.version || 0, data: payload })
|
||||
ElMessage.success('订舱草稿已保存')
|
||||
if (isNew.value) await router.replace(`/ocean-bookings/${saved.id}`)
|
||||
ElMessage.success(isNew.value ? `订舱创建成功:${saved.bookingNo || saved.id}` : '订舱草稿已保存')
|
||||
if (isNew.value) {
|
||||
workspace.removeByIds(['ocean-booking-create'])
|
||||
await router.replace('/ocean-bookings')
|
||||
return
|
||||
}
|
||||
applyDetail(saved)
|
||||
} catch (cause) { actionError.value = getApiErrorDetails(cause) }
|
||||
finally { saving.value = false }
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, reactive, ref, watch } from 'vue'
|
||||
import { computed, onActivated, onMounted, reactive, ref, watch } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { Eye, Pencil, Plus } from '@lucide/vue'
|
||||
import { useAccessPreviewStore } from '@/core/access/accessPreview'
|
||||
@@ -58,7 +58,8 @@ async function load() { loading.value = true; error.value = null; try { const ke
|
||||
function open(id: string) { void router.push(`/ocean-bookings/${id}`) }
|
||||
function createBooking() { void router.push('/ocean-bookings/new') }
|
||||
function search() { if (page.value === 1) void load(); else page.value = 1 }
|
||||
watch([page, pageSize], () => void load()); onMounted(() => { void load(); void loadCustomers() })
|
||||
watch([page, pageSize], () => void load()); onMounted(() => void loadCustomers())
|
||||
onActivated(() => void load())
|
||||
</script>
|
||||
<template>
|
||||
<AdminPage as="div" class="ocean-bookings-page">
|
||||
|
||||
@@ -184,8 +184,6 @@ async function loadWarehouses() {
|
||||
|
||||
async function loadPallets() {
|
||||
const sequence = ++palletSequence
|
||||
rows.value = []
|
||||
total.value = 0
|
||||
loadError.value = null
|
||||
loading.value = true
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user