fix: 防止 FAQ 表单重复提交
This commit is contained in:
@@ -3,9 +3,9 @@
|
|||||||
:visible="visible"
|
:visible="visible"
|
||||||
:width="760"
|
:width="760"
|
||||||
:title="faq ? '编辑 FAQ' : '新建 FAQ'"
|
:title="faq ? '编辑 FAQ' : '新建 FAQ'"
|
||||||
:closable="!submitting"
|
:closable="!busy"
|
||||||
:mask-closable="!submitting"
|
:mask-closable="!busy"
|
||||||
:esc-to-close="!submitting"
|
:esc-to-close="!busy"
|
||||||
:on-before-cancel="canClose"
|
:on-before-cancel="canClose"
|
||||||
unmount-on-close
|
unmount-on-close
|
||||||
@cancel="close"
|
@cancel="close"
|
||||||
@@ -55,6 +55,7 @@
|
|||||||
<a-form-item label="子分类" field="sub_category">
|
<a-form-item label="子分类" field="sub_category">
|
||||||
<a-input v-model="form.sub_category" placeholder="请输入子分类或补充分类说明" :max-length="100" allow-clear />
|
<a-input v-model="form.sub_category" placeholder="请输入子分类或补充分类说明" :max-length="100" allow-clear />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<a-alert v-if="originalCategoryUnavailable" type="warning">原分类已停用或不可用,请重新选择</a-alert>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="form-section">
|
<section class="form-section">
|
||||||
@@ -113,15 +114,15 @@
|
|||||||
|
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<a-space>
|
<a-space>
|
||||||
<a-button :disabled="submitting" @click="close">取消</a-button>
|
<a-button :disabled="busy" @click="close">取消</a-button>
|
||||||
<a-button type="primary" :loading="submitting" :disabled="submitting" @click="handleSubmit">保存</a-button>
|
<a-button type="primary" :loading="busy" :disabled="busy" @click="handleSubmit">保存</a-button>
|
||||||
</a-space>
|
</a-space>
|
||||||
</template>
|
</template>
|
||||||
</a-drawer>
|
</a-drawer>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { reactive, ref, watch } from 'vue'
|
import { computed, reactive, ref, watch } from 'vue'
|
||||||
import { Message, type FormInstance } from '@arco-design/web-vue'
|
import { Message, type FormInstance } from '@arco-design/web-vue'
|
||||||
import { faqPriorityOptions, faqProblemTypeOptions, type Faq, type FaqFormData } from '@/api/kb/faq'
|
import { faqPriorityOptions, faqProblemTypeOptions, type Faq, type FaqFormData } from '@/api/kb/faq'
|
||||||
|
|
||||||
@@ -149,10 +150,13 @@ const props = withDefaults(defineProps<Props>(), {
|
|||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(event: 'update:visible', visible: boolean): void
|
(event: 'update:visible', visible: boolean): void
|
||||||
(event: 'submit', values: FaqFormData): void
|
(event: 'submit', values: FaqFormData, done: () => void): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const formRef = ref<FormInstance>()
|
const formRef = ref<FormInstance>()
|
||||||
|
const validating = ref(false)
|
||||||
|
const originalCategoryUnavailable = ref(false)
|
||||||
|
const busy = computed(() => validating.value || props.submitting)
|
||||||
type FaqEditorForm = Omit<FaqFormData, 'category_id'> & { category_id?: number }
|
type FaqEditorForm = Omit<FaqFormData, 'category_id'> & { category_id?: number }
|
||||||
|
|
||||||
const form = reactive<FaqEditorForm>(createEmptyForm())
|
const form = reactive<FaqEditorForm>(createEmptyForm())
|
||||||
@@ -180,6 +184,8 @@ function createEmptyForm(): FaqEditorForm {
|
|||||||
/** 打开抽屉时载入当前 FAQ,关闭后由 unmount-on-close 清理视图。 */
|
/** 打开抽屉时载入当前 FAQ,关闭后由 unmount-on-close 清理视图。 */
|
||||||
function resetForm() {
|
function resetForm() {
|
||||||
const faq = props.faq
|
const faq = props.faq
|
||||||
|
const categoryAvailable = Boolean(faq && props.categories.some((category) => category.id === faq.category_id))
|
||||||
|
originalCategoryUnavailable.value = Boolean(faq && !categoryAvailable)
|
||||||
Object.assign(
|
Object.assign(
|
||||||
form,
|
form,
|
||||||
faq
|
faq
|
||||||
@@ -187,7 +193,7 @@ function resetForm() {
|
|||||||
question: faq.question,
|
question: faq.question,
|
||||||
answer: faq.answer,
|
answer: faq.answer,
|
||||||
priority: faq.priority || 'medium',
|
priority: faq.priority || 'medium',
|
||||||
category_id: faq.category_id,
|
category_id: categoryAvailable ? faq.category_id : undefined,
|
||||||
sub_category: faq.sub_category || '',
|
sub_category: faq.sub_category || '',
|
||||||
problem_type: faq.problem_type || '',
|
problem_type: faq.problem_type || '',
|
||||||
solution: faq.solution || '',
|
solution: faq.solution || '',
|
||||||
@@ -201,6 +207,7 @@ function resetForm() {
|
|||||||
}
|
}
|
||||||
: createEmptyForm()
|
: createEmptyForm()
|
||||||
)
|
)
|
||||||
|
validating.value = false
|
||||||
formRef.value?.clearValidate()
|
formRef.value?.clearValidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,48 +215,84 @@ watch(
|
|||||||
() => props.visible,
|
() => props.visible,
|
||||||
(visible) => {
|
(visible) => {
|
||||||
if (visible) resetForm()
|
if (visible) resetForm()
|
||||||
|
else validating.value = false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.categories,
|
||||||
|
() => {
|
||||||
|
const faq = props.faq
|
||||||
|
if (!props.visible || !faq || !originalCategoryUnavailable.value) return
|
||||||
|
if (props.categories.some((category) => category.id === faq.category_id)) {
|
||||||
|
form.category_id = faq.category_id
|
||||||
|
originalCategoryUnavailable.value = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => form.category_id,
|
||||||
|
(categoryId) => {
|
||||||
|
if (categoryId && props.categories.some((category) => category.id === categoryId)) {
|
||||||
|
originalCategoryUnavailable.value = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
/** 校验通过后交由父页面保存,接口成功前不关闭抽屉。 */
|
/** 校验通过后交由父页面保存,接口成功前不关闭抽屉。 */
|
||||||
async function handleSubmit() {
|
async function handleSubmit() {
|
||||||
if (props.submitting) return
|
if (busy.value) return
|
||||||
const errors = await formRef.value?.validate()
|
validating.value = true
|
||||||
if (errors) return
|
let handedOff = false
|
||||||
|
try {
|
||||||
|
const errors = await formRef.value?.validate()
|
||||||
|
if (errors) return
|
||||||
|
|
||||||
const question = form.question.trim()
|
const question = form.question.trim()
|
||||||
const answer = form.answer.trim()
|
const answer = form.answer.trim()
|
||||||
const categoryId = form.category_id
|
const categoryId = form.category_id
|
||||||
if (!question || !answer || !categoryId) {
|
if (!question || !answer || !categoryId) {
|
||||||
Message.warning('问题、答案和公共分类为必填项')
|
Message.warning('问题、答案和公共分类为必填项')
|
||||||
return
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
handedOff = true
|
||||||
|
emit(
|
||||||
|
'submit',
|
||||||
|
{
|
||||||
|
...form,
|
||||||
|
question,
|
||||||
|
answer,
|
||||||
|
category_id: categoryId,
|
||||||
|
sub_category: form.sub_category.trim(),
|
||||||
|
problem_type: form.problem_type.trim(),
|
||||||
|
solution: form.solution.trim(),
|
||||||
|
process_steps: form.process_steps.trim(),
|
||||||
|
prerequisites: form.prerequisites.trim(),
|
||||||
|
keywords: form.keywords.trim(),
|
||||||
|
tags: form.tags.trim(),
|
||||||
|
detection_point_ids: form.detection_point_ids.trim(),
|
||||||
|
applicable_scope: form.applicable_scope.trim(),
|
||||||
|
remarks: form.remarks.trim(),
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
validating.value = false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} finally {
|
||||||
|
if (!handedOff) validating.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
emit('submit', {
|
|
||||||
...form,
|
|
||||||
question,
|
|
||||||
answer,
|
|
||||||
category_id: categoryId,
|
|
||||||
sub_category: form.sub_category.trim(),
|
|
||||||
problem_type: form.problem_type.trim(),
|
|
||||||
solution: form.solution.trim(),
|
|
||||||
process_steps: form.process_steps.trim(),
|
|
||||||
prerequisites: form.prerequisites.trim(),
|
|
||||||
keywords: form.keywords.trim(),
|
|
||||||
tags: form.tags.trim(),
|
|
||||||
detection_point_ids: form.detection_point_ids.trim(),
|
|
||||||
applicable_scope: form.applicable_scope.trim(),
|
|
||||||
remarks: form.remarks.trim(),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 提交中拒绝关闭,避免中断请求或丢失输入。 */
|
/** 提交中拒绝关闭,避免中断请求或丢失输入。 */
|
||||||
function close() {
|
function close() {
|
||||||
if (!props.submitting) emit('update:visible', false)
|
if (!busy.value) emit('update:visible', false)
|
||||||
}
|
}
|
||||||
|
|
||||||
function canClose() {
|
function canClose() {
|
||||||
return !props.submitting
|
return !busy.value
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleVisibleChange(visible: boolean) {
|
function handleVisibleChange(visible: boolean) {
|
||||||
|
|||||||
@@ -437,15 +437,18 @@ function openEditFromDetail(faq: Faq) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 保存表单;仅接口成功后关闭抽屉。 */
|
/** 保存表单;仅接口成功后关闭抽屉。 */
|
||||||
async function saveFaq(values: FaqFormData) {
|
async function saveFaq(values: FaqFormData, done: () => void) {
|
||||||
const target = editingFaq.value
|
if (formSubmitting.value) {
|
||||||
if ((!target && !canCreate.value) || (target && !canEdit(target))) {
|
done()
|
||||||
Message.error('无操作权限')
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
formSubmitting.value = true
|
formSubmitting.value = true
|
||||||
|
const target = editingFaq.value
|
||||||
try {
|
try {
|
||||||
|
if ((!target && !canCreate.value) || (target && !canEdit(target))) {
|
||||||
|
Message.error('无操作权限')
|
||||||
|
return
|
||||||
|
}
|
||||||
const reply = target ? await updateFaq({ id: target.id, ...values }) : await createFaq(values)
|
const reply = target ? await updateFaq({ id: target.id, ...values }) : await createFaq(values)
|
||||||
if (!showReplyResult(reply, target ? '保存 FAQ 失败' : '创建 FAQ 失败')) return
|
if (!showReplyResult(reply, target ? '保存 FAQ 失败' : '创建 FAQ 失败')) return
|
||||||
Message.success(target ? 'FAQ 已保存' : 'FAQ 已创建')
|
Message.success(target ? 'FAQ 已保存' : 'FAQ 已创建')
|
||||||
@@ -458,6 +461,7 @@ async function saveFaq(values: FaqFormData) {
|
|||||||
showRequestError(error, target ? '保存 FAQ 失败' : '创建 FAQ 失败')
|
showRequestError(error, target ? '保存 FAQ 失败' : '创建 FAQ 失败')
|
||||||
} finally {
|
} finally {
|
||||||
formSubmitting.value = false
|
formSubmitting.value = false
|
||||||
|
done()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user