This commit is contained in:
2026-08-18 09:09:34 +08:00
parent dcbca0bf85
commit 8046f9b9ee
27 changed files with 230 additions and 83 deletions
+3 -5
View File
@@ -6,6 +6,7 @@ import { firstLegBusinessTypeLabel, firstLegTaxModeLabel } from '@/shared/utils/
import { useAccessPreviewStore } from '@/core/access/accessPreview'
import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue'
import AppDialog from '@/shared/components/AppDialog.vue'
import OperationErrorToast from '@/shared/components/OperationErrorToast.vue'
import {
ApiResponseError,
getApiErrorDetails,
@@ -365,6 +366,8 @@ defineExpose({ reload: loadFee })
</details>
</div>
<OperationErrorToast :details="adjustmentError" />
<AppDialog
v-model="adjustOpen"
title="调整入库费用"
@@ -379,11 +382,6 @@ defineExpose({ reload: loadFee })
<template #title>费用已被其他操作员更新,系统已刷新最新快照</template>
<p>当前输入仍保留,请先核对最新版本后再提交。</p>
</el-alert>
<el-alert v-if="adjustmentError" class="adjustment-alert" type="error" :closable="false" show-icon role="alert">
<template #title>费用调整失败</template>
<p>{{ adjustmentError.message }}</p>
<ApiErrorMeta :details="adjustmentError" />
</el-alert>
<el-form ref="adjustFormRef" :model="adjustmentForm" :rules="adjustmentRules" label-position="top" class="adjustment-form" :disabled="adjusting">
<div class="adjustment-amounts">
<el-form-item label="调整后计费量" prop="chargeableQuantity" required>
@@ -1,4 +1,4 @@
import ElementPlus, { ElMessageBox } from 'element-plus'
import ElementPlus, { ElMessage, ElMessageBox } from 'element-plus'
import { createPinia, setActivePinia } from 'pinia'
import { enableAutoUnmount, flushPromises, mount } from '@vue/test-utils'
import { afterAll, afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
@@ -275,6 +275,7 @@ describe('InboundMeasurementPanel', () => {
})
it('shows a normal A0409 business reason instead of relabeling it as a version conflict', async () => {
const messageSpy = vi.spyOn(ElMessage, 'error')
vi.mocked(updateConfirmedPackageCount).mockRejectedValueOnce(
new ApiResponseError('该托盘已被占用,请重新选择', 'A0409', 409),
)
@@ -285,11 +286,31 @@ describe('InboundMeasurementPanel', () => {
await save?.trigger('click')
await flushPromises()
expect(wrapper.text()).toContain('该托盘已被占用,请重新选择')
expect(wrapper.text()).toContain('操作未完成')
expect(messageSpy).toHaveBeenCalledWith(expect.objectContaining({
message: '该托盘已被占用,请重新选择',
showClose: true,
duration: 4500,
}))
expect(wrapper.text()).not.toContain('操作未完成')
expect(wrapper.text()).not.toContain('数据版本已变化,当前修改尚未丢失')
})
it('reports draft validation as a dismissible light message without a fixed page alert', async () => {
const messageSpy = vi.spyOn(ElMessage, 'warning')
vi.mocked(getInboundMeasurement).mockResolvedValueOnce(measurement({ items: [] }))
const wrapper = await mountPanel()
const save = wrapper.findAll('button').find((button) => button.text().includes('保存测量数据'))
await save?.trigger('click')
await flushPromises()
expect(messageSpy).toHaveBeenCalledWith(expect.objectContaining({
message: '请至少保留一条实际扫描箱测量记录',
showClose: true,
duration: 4500,
}))
expect(wrapper.text()).not.toContain('请修正测量草稿中的问题')
})
it('requires the dirty draft to be saved before submitting for review', async () => {
const wrapper = await mountPanel()
const countInput = wrapper.find('.measurement-count-editor input')
@@ -200,8 +200,7 @@ async function submitManualEntry() {
await loadMeasurement()
emit('updated')
} catch (error) {
const details = setOperationError(error, '手动箱规录入失败,请检查后重试')
ElMessage.error(details.message)
setOperationError(error, '手动箱规录入失败,请检查后重试')
} finally {
manualEntrySubmitting.value = false
}
@@ -311,14 +310,26 @@ function validateDraft() {
})
if (general.length) errors.general = general
validationErrors.value = errors
const messages = Object.values(errors).flat()
if (messages.length) {
ElMessage.warning({
message: messages.length === 1 ? messages[0] : `${messages[0]}(另有 ${messages.length - 1} 项)`,
showClose: true,
duration: 4500,
})
}
return Object.keys(errors).length === 0
}
function setOperationError(error: unknown, fallback: string) {
const details = getApiErrorDetails(error, fallback)
operationError.value = details
conflictDetected.value = details.message.includes('版本已变化')
const conflict = details.message.includes('版本已变化')
|| details.message.includes('并发修改')
conflictDetected.value = conflict
operationError.value = conflict ? details : null
if (!conflict) {
ElMessage.error({ message: details.message, showClose: true, duration: 4500 })
}
return details
}
@@ -365,8 +376,7 @@ async function saveDraft() {
emit('saved')
emit('changed')
} catch (error) {
const details = setOperationError(error, '测量草稿保存失败,请检查后重试')
if (!conflictDetected.value) ElMessage.error(details.message)
setOperationError(error, '测量草稿保存失败,请检查后重试')
} finally {
saving.value = false
}
@@ -400,8 +410,7 @@ async function submitForReview() {
emit('submitted')
emit('changed')
} catch (error) {
const details = setOperationError(error, '测量数据提交失败,请检查图片和称重结果后重试')
if (!conflictDetected.value) ElMessage.error(details.message)
setOperationError(error, '测量数据提交失败,请检查图片和称重结果后重试')
} finally {
submitting.value = false
}
@@ -505,18 +514,9 @@ defineExpose({ saveDraft, submitForReview, updateActualGrossWeight, mayMaintain,
<ApiErrorMeta v-if="operationError" :details="operationError" />
<el-button size="small" @click="reloadLatest">加载最新版本</el-button>
</el-alert>
<el-alert v-else-if="operationError" type="error" :closable="false" show-icon class="measurement-alert">
<template #title>操作未完成</template>
<p>{{ operationError.message }}</p>
<ApiErrorMeta :details="operationError" />
</el-alert>
<el-alert v-if="Object.keys(validationErrors).length" type="error" :closable="false" show-icon class="measurement-alert">
<template #title>请修正测量草稿中的问题</template>
<ul>
<li v-for="message in Object.values(validationErrors).flat()" :key="message">{{ message }}</li>
</ul>
</el-alert>
<p v-if="Object.keys(validationErrors).length" class="sr-only" role="alert">
{{ Object.values(validationErrors).flat().join(';') }}
</p>
<form class="measurement-form" @submit.prevent="saveDraft">
<fieldset :disabled="!mayMaintain || saving">