模态表单与未保存修改
模态框适合短小、集中的表单。页面拥有显隐、字段值和保存请求;模态框保护用户关闭路径并管理焦点。
完整表单示例
示例假定 POST /api/app/product 接收 { name: string }。通过应用正常布局注册主题与确认宿主。
<template>
<AbpButton @click="open">{{ localization.t('AbpUi::New') }}</AbpButton>
<AbpModal v-model:visible="visible" :busy="saving" :dirty="form.dirty" size="lg" centered>
<template #header
><h2>{{ localization.t('BookStore::Product') }}</h2></template
>
<AbpFormField
v-slot="{ id, describedBy, invalid }"
:label="localization.t('AbpIdentity::DisplayName:Name')"
:errors="messages"
required
>
<AbpInput
:id="id"
v-model="form.controls.name.value"
:aria-describedby="describedBy"
:invalid="invalid"
:disabled="saving"
/>
</AbpFormField>
<p v-for="message in form.unmatchedServerErrors" :key="message" role="alert">{{ message }}</p>
<template #footer="{ close }">
<AbpButton variant="secondary" :disabled="saving" @click="close">
{{ localization.t('AbpUi::Cancel') }}
</AbpButton>
<AbpButton :loading="saving" @click="save">{{ localization.t('AbpUi::Save') }}</AbpButton>
</template>
</AbpModal>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue';
import { inject, RestService, useLocalization } from '@lsw-abpvue/core';
import {
AbpButton,
AbpFormField,
AbpInput,
AbpModal,
useAbpForm,
useServerValidation,
useValidationMessages,
Validators,
} from '@lsw-abpvue/theme-shared';
const rest = inject(RestService);
const localization = useLocalization();
const visible = ref(false);
const saving = ref(false);
const form = useAbpForm({ name: { value: '', validators: [Validators.required()] } });
useServerValidation(form);
const validationMessages = useValidationMessages();
const messages = computed(() =>
form.controls.name.touched ? validationMessages(form.controls.name.errors) : [],
);
function open(): void {
form.reset();
visible.value = true;
}
async function save(): Promise<void> {
if (saving.value || !form.validate()) return;
saving.value = true;
try {
await rest.request<{ name: string }, unknown>({
method: 'POST',
url: '/api/app/product',
body: form.value,
});
visible.value = false;
form.reset();
} catch {
// Global handlers display the failure; the dialog retains the entered values.
} finally {
saving.value = false;
}
}
</script>保存为 src/components/ModalFormExample.vue,在应用页面渲染。为标题提供示例使用的 BookStore 本地化 key。
关闭路径
| 路径 | 行为 |
|---|---|
footer 的 close()、右上角关闭、Esc、遮罩 | 执行关闭保护 |
未保存输入或 dirty: true | 询问是否丢弃修改 |
busy: true | 阻止用户关闭请求 |
保存成功,页面设置 visible = false | 直接关闭 |
| 保存失败 | 页面保留模态框与输入 |
| 组件卸载 | 清理未结束的确认与监听 |
取消按钮绑定作用域插槽的 close。取消时直接设置 visible = false 会绕过丢弃确认;直接赋值适用于操作完成或有意的程序关闭。
模态正文中的原生 input/change 会标记修改。dirty 补充自定义控件和没有原生事件的程序修改,两者共同参与判断;已经发生原生输入后,将 dirty 改为 false 不会撤销本次打开期间记录的修改。
busy 保护关闭路径,自定义页脚控件仍应绑定禁用/加载状态。它不会自动禁用任意插槽内容。
为单个模态框关闭确认
<AbpModal
v-model:visible="visible"
:suppress-unsaved-changes-warning="true"
aria-label="Preview"
>
<p>{{ preview }}</p>
</AbpModal>visible 与 preview 是页面变量。该属性仅关闭此实例的未保存警告,不会关闭 busy 保护。
尺寸与生命周期
使用 size 选择 sm、md、lg 或 xl,默认 md;centered 控制垂直居中。标题放入 header 插槽,完整正文放入默认插槽,操作放入 footer。
init 在每次打开时、对话框进入文档前触发。appear 与 disappear 通知可见状态变化,不表示动画结束。需要全局统一的尺寸或关闭策略时,可以写一个应用组件封装常用属性。
导航与焦点
编辑中的模态框可以参与浏览器页面卸载确认,关闭保护不等于全应用的未保存表单路由守卫。需要阻止 SPA 导航的页面应添加自己的离开路由检查。
有 header 时通过它命名对话框,没有 header 时提供 ariaLabel。关闭后焦点返回触发元素。检查 Esc、遮罩、取消、保存失败和多次打开关闭的行为。