Appearance
Dialog 对话框
用于承载需要用户聚焦处理的确认、编辑或详情内容。
基础用法
vue
<template>
<x-button type="primary" @click="visible = true">打开对话框</x-button>
<x-dialog v-model="visible" title="标题" width="520px">
内容
<template #footer="{ cancel, ok, loadingObj }">
<x-space>
<x-button :loading="loadingObj.cancel" @click="cancel">取消</x-button>
<x-button type="primary" :loading="loadingObj.ok" @click="ok">确认</x-button>
</x-space>
</template>
</x-dialog>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const visible = ref(false);
</script>异步关闭
vue
<template>
<x-button type="primary" @click="visible = true">异步确认</x-button>
<x-dialog v-model="visible" title="提交确认" :on-before-cancel="beforeClose">
确认按钮会等待异步任务完成;取消按钮会被拦截并保持打开。
<template #footer="{ cancel, ok, loadingObj }">
<x-space>
<x-button :loading="loadingObj.cancel" @click="cancel">取消</x-button>
<x-button type="primary" :loading="loadingObj.ok" @click="ok">提交</x-button>
</x-space>
</template>
</x-dialog>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const visible = ref(false);
const beforeClose = async (action: 'cancel' | 'ok') => {
if (action === 'cancel') return false;
await new Promise((resolve) => window.setTimeout(resolve, 1200));
return true;
};
</script>显示方式
vue
<template>
<div class="demo-inline-group">
<x-button @click="centerVisible = true">居中</x-button>
<x-button @click="topVisible = true">顶部距离</x-button>
<x-button @click="fullscreenVisible = true">全屏</x-button>
</div>
<x-dialog v-model="centerVisible" center title="居中对话框" />
<x-dialog v-model="topVisible" title="自定义顶部距离" top="80px" />
<x-dialog v-model="fullscreenVisible" title="全屏对话框" fullscreen />
</template>
<script setup lang="ts">
import { ref } from 'vue';
const centerVisible = ref(false);
const topVisible = ref(false);
const fullscreenVisible = ref(false);
</script>保留内容状态
vue
<template>
<x-button @click="visible = true">打开并保留内容</x-button>
<x-dialog v-model="visible" title="关闭后保留内容" :destroy-on-closed="false">
<x-input v-model="text" placeholder="关闭后再次打开,内容仍会保留" />
</x-dialog>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const visible = ref(false);
const text = ref('');
</script>命令式调用
vue
<template>
<div class="demo-inline-group">
<x-button type="primary" @click="openCommandDialog">命令式打开</x-button>
<x-button @click="openNoFooterDialog">无底部按钮</x-button>
<x-button @click="openManualDialog">手动更新关闭</x-button>
<x-button @click="Dialog.destroyAll()">关闭全部</x-button>
</div>
</template>
<script setup lang="ts">
import { Dialog } from 'x-next';
const openCommandDialog = () => {
Dialog({
title: '提交确认',
content: '确认提交当前表单吗?',
onOk: () => console.log('ok'),
});
};
const openNoFooterDialog = () => {
Dialog({
title: '说明',
content: () => 'footer=false 时不会显示底部按钮',
footer: false,
});
};
const openManualDialog = () => {
const instance = Dialog({
title: '处理中',
content: '3 秒后关闭',
});
window.setTimeout(() => instance.update({ content: '即将关闭' }), 1500);
window.setTimeout(() => instance.close(), 3000);
};
</script>局部容器
renderTo 接受选择器或 HTMLElement。宿主应在打开前已经挂载,找不到目标时会回退到 body;为局部弹层提供明确的宽高。局部宿主使用定位上下文,静态定位的宿主会在打开期间临时设置为 position: relative,弹层根使用绝对定位。关闭动画结束或组件卸载后恢复原始内联样式;同一宿主上的多个 Dialog 会保持锁定,直到最后一个关闭。
局部遮罩只覆盖宿主区域,overflow: hidden 会裁剪超出区域的弹层内容。全屏及页面级模态交互建议使用默认 body 宿主;局部容器的尺寸、祖先裁剪和层叠上下文仍由业务布局控制。disabled 仅关闭 Teleport,适合内联渲染,不代表禁用 Dialog 交互,也不取消 renderTo 对应的滚动锁。
vue
<template>
<x-button @click="openLocalDialog">挂载到局部容器</x-button>
<div id="dialog-local-container" class="dialog-local-container"></div>
</template>
<script setup lang="ts">
import { Dialog } from 'x-next';
const openLocalDialog = () => {
Dialog({
title: '局部容器',
content: 'renderTo 找不到目标时会回退到 body',
renderTo: '#dialog-local-container',
});
};
</script>切换宿主与嵌套对话框
打开后可修改 renderTo,滚动锁会从旧宿主转移到新宿主。下面的两个 Dialog 使用同一个目标,关闭其中一层仍保留另一层的锁;关闭后可滚动宿主查看恢复效果。
宿主 A:关闭后可滚动
宿主 B:关闭后可滚动
vue
<template>
<button @click="visible = true">打开局部对话框</button>
<div class="dialog-host-row">
<div id="dialog-host-a" class="dialog-host"><div class="host-content">宿主 A</div></div>
<div id="dialog-host-b" class="dialog-host"><div class="host-content">宿主 B</div></div>
</div>
<DialogComponent v-model="visible" :render-to="target" title="局部编辑" width="280px" top="24px" :mask-to-close="false">
<p>当前宿主:{{ target === '#dialog-host-a' ? 'A' : 'B' }}</p>
<button @click="target = target === '#dialog-host-a' ? '#dialog-host-b' : '#dialog-host-a'">切换宿主</button>
<button @click="nestedVisible = true">打开第二层</button>
</DialogComponent>
<DialogComponent v-model="nestedVisible" :render-to="target" title="第二层" width="260px" top="48px">
关闭这一层后,下层对话框仍保持宿主滚动锁。
</DialogComponent>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { DialogComponent } from 'x-next';
import 'x-next/style/base.css';
import 'x-next/style/dialog.css';
const visible = ref(false);
const nestedVisible = ref(false);
const target = ref('#dialog-host-a');
</script>
<style scoped>
.dialog-host-row { display: flex; flex-wrap: wrap; gap: 12px; width: 100%; }
.dialog-host { position: relative; flex: 1 1 300px; height: 320px; overflow: auto; }
.host-content { min-height: 640px; }
</style>按需导入
包根导出的 DialogComponent 是声明式 Vue 组件,用于模板、v-model 和插槽;Dialog 是命令式函数,用于 Dialog(options)、返回的 close/update 和 Dialog.destroyAll()。全局安装组件库后仍可在模板中使用 <x-dialog>。
ts
import { DialogComponent, Dialog } from 'x-next';vue
<template>
<button @click="visible = true">声明式打开</button>
<DialogComponent v-model="visible" title="创建知识库" width="480px">
<label for="knowledge-name">名称</label>
<input id="knowledge-name" v-model="name" />
<template #footer="{ cancel, ok }">
<button @click="cancel">取消</button>
<button @click="ok">确认</button>
</template>
</DialogComponent>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { DialogComponent } from 'x-next';
import 'x-next/style/base.css';
import 'x-next/style/dialog.css';
const visible = ref(false);
const name = ref('');
</script>样式按需引入(base.css 为共享基础层,多个组件只需引入一次):
ts
import 'x-next/style/base.css';
import 'x-next/style/dialog.css';Component Props
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
modelValue | 是否显示对话框 | boolean | false |
renderTo | 挂载容器,找不到时回退到 body | string | HTMLElement | 'body' |
disabled | 是否禁用 Teleport | boolean | false |
top | 距离顶部位置,center=false 时生效 | string | number | '3vh' |
center | 是否居中显示 | boolean | false |
width | 宽度 | string | number | 0 |
height | 高度 | string | number | 0 |
minWidth | 最小宽度 | string | number | 0 |
minHeight | 最小高度 | string | number | 0 |
title | 标题 | string | '' |
mask | 是否显示遮罩 | boolean | true |
maskToClose | 点击遮罩是否关闭 | boolean | true |
escToClose | 按下 Esc 是否关闭 | boolean | true |
fullscreen | 是否全屏 | boolean | false |
destroyOnClosed | 关闭后是否销毁内容 | boolean | true |
onBeforeCancel | 关闭前拦截,返回 false 阻止关闭 | (action?: 'cancel' | 'ok') => boolean | Promise<boolean> | - |
popupClass | 弹层自定义类名 | string | - |
showClose | 是否显示关闭按钮 | boolean | true |
showHeaderBottomLine | 是否显示标题底部分割线 | boolean | false |
hideFooter | 是否隐藏底部区域 | boolean | false |
Component Events
| 事件名 | 说明 | 回调参数 |
|---|---|---|
update:modelValue | 显隐状态变化时触发 | value: boolean |
open | 打开时触发 | - |
opened | 打开动画结束后触发 | - |
close | 请求关闭时触发 | action: 'cancel' | 'ok', event?: Event |
closed | 关闭动画结束后触发 | - |
Component Slots
| 插槽名 | 说明 | 参数 |
|---|---|---|
title | 自定义标题 | - |
default | 对话框主体内容 | - |
footer | 自定义底部操作区 | { cancel, ok, loadingObj, loading-obj } |
Dialog Methods
| 方法 | 说明 | 参数 | 返回值 |
|---|---|---|---|
Dialog(options) | 打开命令式对话框 | DialogOptions | DialogReturn |
Dialog.destroyAll() | 关闭全部命令式对话框 | - | void |
Dialog Options
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
title | 标题 | DialogContent | - |
content | 正文内容 | DialogContent | - |
footer | 自定义底部;false 时隐藏底部 | boolean | VNode | (scoped) => DialogRenderResult | 默认按钮 |
okText | 确认按钮文案 | string | '确认' |
cancelText | 取消按钮文案 | string | '取消' |
mask | 是否显示遮罩 | boolean | true |
maskToClose | 点击遮罩是否关闭 | boolean | true |
beforeOnOk | 确认前拦截 | () => boolean | Promise<boolean> | - |
beforeOnCancel | 取消前拦截 | () => boolean | Promise<boolean> | - |
onOk | 点击确认回调 | (event: Event) => void | - |
onCancel | 点击取消回调 | (event: Event) => void | - |
width | 宽度 | string | number | 368 |
height | 高度 | string | number | 'auto' |
top | 距离顶部位置 | string | number | 0 |
center | 是否居中 | boolean | true |
renderTo | 挂载容器,找不到时回退到 body | string | HTMLElement | 'body' |
hideCancel | 是否隐藏取消按钮 | boolean | false |
hideOk | 是否隐藏确认按钮 | boolean | false |
popupClass | 弹层自定义类名 | string | - |
返回值
| 方法 | 说明 | 参数 |
|---|---|---|
close() | 关闭当前对话框 | - |
update(options) | 更新当前对话框配置 | Partial<DialogOptions> |
无障碍说明
Dialog 打开后会聚焦到首个可通过 Tab 到达的元素,Tab / Shift+Tab 在对话框内部循环;tabindex 为负数的元素仍可通过业务代码聚焦,但不会作为顺序循环的边界。隐藏、inert 和禁用控件会被跳过,透明或零宽的输入仍可接受键盘焦点。没有可用的顺序焦点时聚焦对话框容器。
关闭或条件卸载会把焦点恢复到打开前仍在文档中的元素;若焦点已移到其他交互区域或后来打开的 Dialog,则保留当前焦点。对话框容器带有 role="dialog" 和标题关联,右上角关闭控件是可聚焦按钮。无标题时使用 aria-label 或 aria-labelledby 提供名称,可通过 aria-describedby 关联说明。