前端报错提示优化

This commit is contained in:
文睿 张 2026-09-20 17:17:28 +08:00
parent d463fbf71a
commit 8a60e2cb00
2 changed files with 44 additions and 27 deletions

View File

@ -1,5 +1,5 @@
import { flushPromises, mount } from '@vue/test-utils'
import ElementPlus, { ElMessage } from 'element-plus'
import ElementPlus, { ElMessage, ElMessageBox } from 'element-plus'
import { beforeEach, describe, expect, test, vi } from 'vitest'
import Materials from './Materials.vue'
import { codeRulesApi, materialCategoriesApi, unitsApi } from '../api/masterData'
@ -84,6 +84,7 @@ async function pickCategories(wrapper, { major = 1, minor = 12 } = {}) {
describe('新增物料时的编码联动', () => {
let warnSpy
let alertSpy
beforeEach(() => {
materialCategoriesApi.list.mockResolvedValue({ data: categories })
@ -92,6 +93,7 @@ describe('新增物料时的编码联动', () => {
materialsStore.createMaterial.mockReset()
materialsStore.updateMaterial.mockReset()
warnSpy = vi.spyOn(ElMessage, 'warning').mockImplementation(() => {})
alertSpy = vi.spyOn(ElMessageBox, 'alert').mockResolvedValue('confirm')
})
test('还没选小类时,编码框提示先选分类', async () => {
@ -119,11 +121,11 @@ describe('新增物料时的编码联动', () => {
expect(codeRulesApi.next).toHaveBeenCalledWith(12)
expect(codeInput(wrapper).element.value).toBe('A010201')
expect(wrapper.find('.material-code-hint').exists()).toBe(false)
expect(alertSpy).not.toHaveBeenCalled()
wrapper.unmount()
})
test('小类没有规则时清空编码并提示手工填写', async () => {
test('小类没有规则时清空编码,并弹出需要点掉的提示框', async () => {
codeRulesApi.next.mockResolvedValue({ data: { available: false, code: '' } })
const wrapper = mountMaterials()
@ -132,7 +134,11 @@ describe('新增物料时的编码联动', () => {
await pickCategories(wrapper)
expect(codeInput(wrapper).element.value).toBe('')
expect(wrapper.find('.material-code-hint').text()).toContain('请手工填写')
expect(alertSpy).toHaveBeenCalledWith(
'该小类未配置编码规则,请手工填写物料编码',
'提示',
expect.objectContaining({ confirmButtonText: '知道了' })
)
wrapper.unmount()
})
@ -193,6 +199,22 @@ describe('新增物料时的编码联动', () => {
wrapper.unmount()
})
test('名称为空时保存被拦下并提示补名称', async () => {
codeRulesApi.next.mockResolvedValue({ data: { available: true, code: 'A010201' } })
const wrapper = mountMaterials()
await flushPromises()
await openAddDialog(wrapper)
await pickCategories(wrapper)
await findButton(wrapper, '保存').trigger('click')
await flushPromises()
expect(materialsStore.createMaterial).not.toHaveBeenCalled()
expect(warnSpy).toHaveBeenCalledWith('请填写物料名称')
wrapper.unmount()
})
test('小类没有规则且没填编码时,保存被拦下并提示补编码', async () => {
codeRulesApi.next.mockResolvedValue({ data: { available: false, code: '' } })

View File

@ -79,9 +79,8 @@
/>
<el-button v-if="isCreating" link type="primary" @click="regenerateMaterialCode">重新生成</el-button>
</div>
<p v-if="isCreating && codeHint" class="material-code-hint">{{ codeHint }}</p>
</el-form-item>
<el-form-item label="名称">
<el-form-item label="名称" required>
<el-input v-model="form.name" />
</el-form-item>
<el-form-item label="规格">
@ -159,7 +158,7 @@ import { useAuthStore } from '../stores/auth'
import BaseTable from '../components/business/BaseTable.vue'
import DeleteConfirm from '../components/business/DeleteConfirm.vue'
import AlertTip from '../components/business/AlertTip.vue'
import { ElMessage } from 'element-plus'
import { ElMessage, ElMessageBox } from 'element-plus'
import { createMaterialForm, toMaterialPayload } from './materialForm'
import { codeRulesApi, materialCategoriesApi, unitsApi } from '../api/masterData'
import { getMajorCategories, getMinorCategories, resolveCategoryPath } from '../utils/materialCategories'
@ -187,7 +186,6 @@ const categories = ref([])
const units = ref([])
// 用户是否手改过编码:没改过时切分类要按新规则重算,改过就以用户的值为准。
const codeEdited = ref(false)
const codeHint = ref('')
const isCreating = computed(() => dialogTitle.value === '新增物料')
// 小类还没选时编码框必然是空的,提示用户先把分类选完:选完才会自动填入编码,
// 或者由"请手工填写"的提示接手。
@ -227,16 +225,14 @@ function syncMajorCategory() {
// 大类换了,原来的小类规则不再适用;同样不动用户手改过的编码
if (!codeEdited.value) {
form.material_code = ''
codeHint.value = ''
}
}
// 按当前小类的编码规则填编码:有规则自动填,没规则就清空并提示手工填写。
// 按当前小类的编码规则填编码:有规则自动填,没规则就清空并弹窗让用户手工填写。
async function generateMaterialCode() {
if (!isCreating.value) return
if (!form.category_id) {
form.material_code = ''
codeHint.value = ''
return
}
try {
@ -244,15 +240,18 @@ async function generateMaterialCode() {
const { available, code } = response.data || {}
if (available) {
form.material_code = code
codeHint.value = ''
} else {
form.material_code = ''
codeHint.value = '该小类未配置编码规则,请手工填写物料编码'
// 用模态框而不是会自动消失的 toast:这条提示要等用户点掉才走
ElMessageBox.alert('该小类未配置编码规则,请手工填写物料编码', '提示', {
confirmButtonText: '知道了',
type: 'warning'
// ESC 或点遮罩关掉时 alert 会 reject,这里不做处理
}).catch(() => {})
}
} catch (e) {
// 预览失败不该挡住填表:留空让用户手工填写,接口层已经弹过错误提示
// 预览失败不该挡住填表:留空让用户手工填写,失败原因由拦截器提示
form.material_code = ''
codeHint.value = '未能获取编码规则,请手工填写物料编码'
}
}
@ -305,7 +304,6 @@ function showAddModal() {
dialogTitle.value = '新增物料'
Object.assign(form, createMaterialForm())
codeEdited.value = false
codeHint.value = ''
imagePreview.value = ''
if (imageInput.value) imageInput.value.value = ''
dialogVisible.value = true
@ -322,7 +320,6 @@ function showEditModal(row) {
Object.assign(form, createMaterialForm(row, categories.value))
// 编辑时编码只读,标记成"已改过",避免切小类时被规则覆盖
codeEdited.value = true
codeHint.value = ''
imagePreview.value = ''
if (imageInput.value) imageInput.value.value = ''
dialogVisible.value = true
@ -338,6 +335,11 @@ async function handleSave() {
ElMessage.warning('请填写物料编码')
return
}
// 后端对名称也标了 binding:"required",前端先拦下来,避免抛回原始校验错误
if (!form.name.trim()) {
ElMessage.warning('请填写物料名称')
return
}
saveLoading.value = true
try {
const formData = toMaterialPayload(form)
@ -444,21 +446,14 @@ function formatDate(dateStr) {
:deep(.el-table__cell.operation-column) {
border-left: 1px solid var(--el-table-border-color) !important;
}
/* 一行里横排「输入框 + 重新生成」;作为表单项内容区的 flex item,flex:1 让它
占满整行 —— 不设的话会收缩到 el-input 的固有宽度(约 177px),比表单里其它
字段窄一截。 */
.material-code-field {
display: flex;
align-items: center;
gap: 8px;
}
/* 不设 flex 时这一行会收缩到 el-input 的固有宽度(约 177px),比表单里其它
字段窄一截;换成 flex:1 后输入框占满整行剩余宽度,与其它字段对齐。 */
.material-code-field {
flex: 1;
min-width: 0;
}
.material-code-hint {
margin: 4px 0 0;
color: #e6a23c;
font-size: 12px;
line-height: 1.5;
}
</style>