前端提示优化
This commit is contained in:
parent
f0722eed00
commit
85d9683754
90
frontend/src/components/business/DeleteConfirm.test.js
Normal file
90
frontend/src/components/business/DeleteConfirm.test.js
Normal file
|
|
@ -0,0 +1,90 @@
|
||||||
|
import { flushPromises, mount } from '@vue/test-utils'
|
||||||
|
import ElementPlus from 'element-plus'
|
||||||
|
import { describe, expect, test, vi } from 'vitest'
|
||||||
|
import DeleteConfirm from './DeleteConfirm.vue'
|
||||||
|
|
||||||
|
const CONFIRM_TEXT = '确认作废'
|
||||||
|
|
||||||
|
function mountConfirm(onConfirm) {
|
||||||
|
return mount(DeleteConfirm, {
|
||||||
|
props: { modelValue: true, confirmText: CONFIRM_TEXT, onConfirm },
|
||||||
|
global: {
|
||||||
|
plugins: [ElementPlus],
|
||||||
|
stubs: { teleport: true }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function confirmButton(wrapper) {
|
||||||
|
return wrapper.findAll('button').find(button => button.text().includes(CONFIRM_TEXT))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 弹窗关闭体现为子组件把 modelValue 置回 false
|
||||||
|
function closedByEmit(wrapper) {
|
||||||
|
return (wrapper.emitted('update:modelValue') || []).some(args => args[0] === false)
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('DeleteConfirm 的关闭时机', () => {
|
||||||
|
test('父操作成功后关闭弹窗', async () => {
|
||||||
|
const onConfirm = vi.fn().mockResolvedValue()
|
||||||
|
const wrapper = mountConfirm(onConfirm)
|
||||||
|
await flushPromises()
|
||||||
|
|
||||||
|
await confirmButton(wrapper).trigger('click')
|
||||||
|
await flushPromises()
|
||||||
|
|
||||||
|
expect(onConfirm).toHaveBeenCalledTimes(1)
|
||||||
|
expect(closedByEmit(wrapper)).toBe(true)
|
||||||
|
wrapper.unmount()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('父操作失败时弹窗保持打开,让用户能直接重试', async () => {
|
||||||
|
const onConfirm = vi.fn().mockRejectedValue(new Error('接口失败'))
|
||||||
|
const wrapper = mountConfirm(onConfirm)
|
||||||
|
await flushPromises()
|
||||||
|
|
||||||
|
await confirmButton(wrapper).trigger('click')
|
||||||
|
await flushPromises()
|
||||||
|
|
||||||
|
expect(onConfirm).toHaveBeenCalledTimes(1)
|
||||||
|
expect(closedByEmit(wrapper)).toBe(false)
|
||||||
|
wrapper.unmount()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('失败后原地点一次即可重试,不必重走确认流程', async () => {
|
||||||
|
const onConfirm = vi.fn()
|
||||||
|
.mockRejectedValueOnce(new Error('接口失败'))
|
||||||
|
.mockResolvedValueOnce()
|
||||||
|
const wrapper = mountConfirm(onConfirm)
|
||||||
|
await flushPromises()
|
||||||
|
|
||||||
|
await confirmButton(wrapper).trigger('click')
|
||||||
|
await flushPromises()
|
||||||
|
expect(closedByEmit(wrapper)).toBe(false)
|
||||||
|
|
||||||
|
// 弹窗还开着,直接再点一次
|
||||||
|
await confirmButton(wrapper).trigger('click')
|
||||||
|
await flushPromises()
|
||||||
|
|
||||||
|
expect(onConfirm).toHaveBeenCalledTimes(2)
|
||||||
|
expect(closedByEmit(wrapper)).toBe(true)
|
||||||
|
wrapper.unmount()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('操作进行中按钮转 loading,避免重复提交', async () => {
|
||||||
|
let resolveRequest
|
||||||
|
const onConfirm = vi.fn(() => new Promise((resolve) => { resolveRequest = resolve }))
|
||||||
|
const wrapper = mountConfirm(onConfirm)
|
||||||
|
await flushPromises()
|
||||||
|
|
||||||
|
await confirmButton(wrapper).trigger('click')
|
||||||
|
await flushPromises()
|
||||||
|
|
||||||
|
expect(confirmButton(wrapper).classes()).toContain('is-loading')
|
||||||
|
|
||||||
|
resolveRequest()
|
||||||
|
await flushPromises()
|
||||||
|
expect(confirmButton(wrapper).classes()).not.toContain('is-loading')
|
||||||
|
wrapper.unmount()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
@ -24,10 +24,17 @@ const props = defineProps({
|
||||||
content: {
|
content: {
|
||||||
type: String,
|
type: String,
|
||||||
default: '确定要删除此数据吗?此操作不可恢复。'
|
default: '确定要删除此数据吗?此操作不可恢复。'
|
||||||
|
},
|
||||||
|
// 确认后执行的实际操作,由父组件传入并返回 Promise。
|
||||||
|
// 这里刻意不用 emit('confirm'):emit 是同步的,拿不到父组件异步操作的结果,
|
||||||
|
// 会退化成"请求刚发出去弹窗就关了",失败时用户得把确认流程重走一遍。
|
||||||
|
onConfirm: {
|
||||||
|
type: Function,
|
||||||
|
required: true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const emit = defineEmits(['update:modelValue', 'confirm'])
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
|
||||||
const visible = ref(props.modelValue)
|
const visible = ref(props.modelValue)
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
|
|
@ -43,8 +50,11 @@ watch(visible, (val) => {
|
||||||
async function handleConfirm() {
|
async function handleConfirm() {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
emit('confirm')
|
await props.onConfirm()
|
||||||
|
// 只有父组件的操作成功返回才关闭弹窗:失败时留着,用户可以直接再点一次
|
||||||
visible.value = false
|
visible.value = false
|
||||||
|
} catch (e) {
|
||||||
|
// 失败原因已由 axios 拦截器统一提示,这里只负责别把弹窗关掉
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -125,7 +125,7 @@
|
||||||
</div>
|
</div>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
<DeleteConfirm v-model="deleteDialogVisible" @confirm="confirmDelete" />
|
<DeleteConfirm v-model="deleteDialogVisible" :on-confirm="confirmDelete" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -144,7 +144,7 @@
|
||||||
title="确认作废该物料?"
|
title="确认作废该物料?"
|
||||||
content="作废后该物料不能再用于新业务;已有业务记录仍会保留。"
|
content="作废后该物料不能再用于新业务;已有业务记录仍会保留。"
|
||||||
confirm-text="确认作废"
|
confirm-text="确认作废"
|
||||||
@confirm="confirmDelete"
|
:on-confirm="confirmDelete"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<AlertTip :message="alertMessage" :type="alertType" />
|
<AlertTip :message="alertMessage" :type="alertType" />
|
||||||
|
|
@ -394,13 +394,11 @@ function handleDelete(id) {
|
||||||
deleteDialogVisible.value = true
|
deleteDialogVisible.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 这里不能吞异常:DeleteConfirm 靠它判断该不该关弹窗,吞掉会让失败时误关。
|
||||||
|
// 错误提示本来由 axios 拦截器统一负责,不吞也不会漏给用户看。
|
||||||
async function confirmDelete() {
|
async function confirmDelete() {
|
||||||
try {
|
|
||||||
await materialsStore.obsoleteMaterial(deleteId.value)
|
await materialsStore.obsoleteMaterial(deleteId.value)
|
||||||
ElMessage.success('物料已作废')
|
ElMessage.success('物料已作废')
|
||||||
} catch (e) {
|
|
||||||
// 错误已由 API 层处理
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleRestore(id) {
|
async function handleRestore(id) {
|
||||||
|
|
|
||||||
|
|
@ -79,7 +79,7 @@
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
<DeleteConfirm v-model="deleteDialogVisible" @confirm="confirmDelete" />
|
<DeleteConfirm v-model="deleteDialogVisible" :on-confirm="confirmDelete" />
|
||||||
|
|
||||||
<el-dialog v-model="detailVisible" title="出库单详情" width="600px">
|
<el-dialog v-model="detailVisible" title="出库单详情" width="600px">
|
||||||
<div v-if="currentRow">
|
<div v-if="currentRow">
|
||||||
|
|
|
||||||
|
|
@ -80,7 +80,7 @@
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
<DeleteConfirm v-model="deleteDialogVisible" @confirm="confirmDelete" />
|
<DeleteConfirm v-model="deleteDialogVisible" :on-confirm="confirmDelete" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user