diff --git a/frontend/src/views/Materials.test.js b/frontend/src/views/Materials.test.js
index f1ea8586..8c590fb2 100644
--- a/frontend/src/views/Materials.test.js
+++ b/frontend/src/views/Materials.test.js
@@ -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: '' } })
diff --git a/frontend/src/views/Materials.vue b/frontend/src/views/Materials.vue
index eef1b20c..0b203f8b 100644
--- a/frontend/src/views/Materials.vue
+++ b/frontend/src/views/Materials.vue
@@ -79,9 +79,8 @@
/>
重新生成
-
{{ codeHint }}
-
+
@@ -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;
-}