Appearance
Modal 弹窗组件
Modal 组件是一个功能强大的弹窗组件,用于展示重要信息、表单输入或进行用户交互确认。它支持多种尺寸、位置、动画效果和自定义内容,提供组件式和函数式两种调用方式。
基础用法
Modal 组件的基础用法非常简单,通过控制 open 属性来显示或隐藏弹窗。
基础用法
通过控制 open 属性来显示或隐藏弹窗
vue
<template>
<div class="modal-demo">
<div class="demo-header">
<h2>基础用法</h2>
<p>通过控制 open 属性来显示或隐藏弹窗</p>
</div>
<div class="demo-content">
<Button @click="openModal">打开弹窗</Button>
<Modal
v-model:open="isOpen"
title="基础弹窗"
@ok="handleOk"
@cancel="handleCancel"
:mask="false"
>
<div class="modal-body-content">
<p>这是一个基础的弹窗示例</p>
<p>您可以在这里放置任何内容,如表单、信息展示等</p>
</div>
</Modal>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
const isOpen = ref(false);
const openModal = () => {
isOpen.value = true;
};
const handleOk = () => {
console.log('用户点击了确定');
isOpen.value = false;
};
const handleCancel = () => {
console.log('用户点击了取消');
isOpen.value = false;
};
</script>
<style scoped>
.modal-demo {
padding: 20px;
background-color: var(--bg-card);
border-radius: var(--radius-md);
}
.demo-header {
margin-bottom: 24px;
}
.demo-header h2 {
margin: 0 0 8px 0;
font-size: var(--font-size-xl);
font-weight: var(--font-weight-semibold);
color: var(--text-primary);
}
.demo-header p {
margin: 0;
color: var(--text-secondary);
font-size: var(--font-size-sm);
}
.demo-content {
display: flex;
flex-direction: column;
gap: 16px;
}
.modal-body-content {
padding: 8px 0;
}
.modal-body-content p {
margin: 12px 0;
line-height: 1.6;
}
</style>不同尺寸
Modal 组件支持多种预设尺寸,可以根据内容需求选择合适的大小。
不同尺寸
Modal 组件支持多种预设尺寸,可以根据内容需求选择合适的大小
vue
<template>
<div class="modal-demo">
<div class="demo-header">
<h2>不同尺寸</h2>
<p>Modal 组件支持多种预设尺寸,可以根据内容需求选择合适的大小</p>
</div>
<div class="demo-content">
<div class="button-group">
<Button @click="openModal('sm')">小尺寸弹窗</Button>
<Button @click="openModal('md')">中等弹窗</Button>
<Button @click="openModal('lg')">大尺寸弹窗</Button>
<Button @click="openModal('xl')">超大弹窗</Button>
<Button @click="openModal('fullscreen')">全屏弹窗</Button>
</div>
<Modal
v-model:open="isOpen"
:size="currentSize"
title="不同尺寸的弹窗"
@ok="handleOk"
@cancel="handleCancel"
>
<div class="modal-body-content">
<p>当前尺寸: {{ currentSize }}</p>
<p v-if="currentSize === 'sm'">这是一个小尺寸弹窗,适合简单提示信息。</p>
<p v-else-if="currentSize === 'md'">这是一个中等尺寸弹窗,是最常用的默认尺寸。</p>
<p v-else-if="currentSize === 'lg'">这是一个大尺寸弹窗,适合展示较多内容。</p>
<p v-else-if="currentSize === 'xl'">这是一个超大尺寸弹窗,适合复杂表单或大量数据展示。</p>
<p v-else-if="currentSize === 'fullscreen'">这是一个全屏弹窗,适合需要用户全神贯注完成的任务。</p>
<div v-if="currentSize !== 'fullscreen'">
<h3>尺寸说明:</h3>
<ul>
<li>sm - 300px 宽度</li>
<li>md - 500px 宽度(默认)</li>
<li>lg - 700px 宽度</li>
<li>xl - 900px 宽度</li>
<li>fullscreen - 全屏显示</li>
</ul>
</div>
</div>
</Modal>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
const isOpen = ref(false);
const currentSize = ref('md');
const openModal = (size) => {
currentSize.value = size;
isOpen.value = true;
};
const handleOk = () => {
isOpen.value = false;
};
const handleCancel = () => {
isOpen.value = false;
};
</script>
<style scoped>
.modal-demo {
padding: 20px;
background-color: var(--bg-card);
border-radius: var(--radius-md);
}
.demo-header {
margin-bottom: 24px;
}
.demo-header h2 {
margin: 0 0 8px 0;
font-size: var(--font-size-xl);
font-weight: var(--font-weight-semibold);
color: var(--text-primary);
}
.demo-header p {
margin: 0;
color: var(--text-secondary);
font-size: var(--font-size-sm);
}
.demo-content {
display: flex;
flex-direction: column;
gap: 16px;
}
.button-group {
display: flex;
gap: 12px;
flex-wrap: wrap;
}
.modal-body-content {
padding: 8px 0;
}
.modal-body-content p {
margin: 12px 0;
line-height: 1.6;
}
.modal-body-content h3 {
margin: 16px 0 8px 0;
font-size: var(--font-size-lg);
font-weight: var(--font-weight-medium);
}
.modal-body-content ul {
margin: 8px 0 16px 0;
padding-left: 24px;
}
.modal-body-content li {
margin: 4px 0;
line-height: 1.5;
}
</style>不同位置
Modal 组件可以显示在屏幕的不同位置,包括居中、顶部、底部、左侧和右侧。
不同位置
Modal 组件可以显示在屏幕的不同位置,包括居中、顶部、底部、左侧和右侧
vue
<template>
<div class="modal-demo">
<div class="demo-header">
<h2>不同位置</h2>
<p>Modal 组件可以显示在屏幕的不同位置,包括居中、顶部、底部、左侧和右侧</p>
</div>
<div class="demo-content">
<div class="button-group">
<Button @click="openModal('center')">居中显示</Button>
<Button @click="openModal('top')">顶部显示</Button>
<Button @click="openModal('bottom')">底部显示</Button>
<Button @click="openModal('left')">左侧显示</Button>
<Button @click="openModal('right')">右侧显示</Button>
</div>
<Modal
v-model:open="isOpen"
:position="currentPosition"
:size="getSizeByPosition(currentPosition)"
:title="getTitleByPosition(currentPosition)"
:closable="true"
:maskClosable="true"
@ok="handleOk"
@cancel="handleCancel"
>
<div class="modal-body-content">
<p>当前位置: {{ currentPosition }}</p>
<div v-if="currentPosition === 'center'">
<p>居中弹窗是最常用的模式,适合需要用户重点关注的内容。</p>
<p>可以设置为不同的尺寸,默认中等大小。</p>
</div>
<div v-else-if="currentPosition === 'top'">
<p>顶部弹窗从屏幕上方滑入,常用于通知、轻量级确认等场景。</p>
</div>
<div v-else-if="currentPosition === 'bottom'">
<p>底部弹窗从屏幕下方滑入,常用于操作菜单、选项选择等场景。</p>
<p>这种模式在移动端应用中尤为常见。</p>
</div>
<div v-else-if="currentPosition === 'left'">
<p>左侧弹窗从屏幕左侧滑入,常用于展示辅助信息、导航菜单等。</p>
</div>
<div v-else-if="currentPosition === 'right'">
<p>右侧弹窗从屏幕右侧滑入,常用于展示详情信息、设置面板等。</p>
</div>
<div class="responsive-note">
<strong>响应式说明:</strong>
<p>在移动设备上,侧边弹窗(left/right)会自动调整为全屏模式,以确保良好的用户体验。</p>
</div>
</div>
</Modal>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
const isOpen = ref(false);
const currentPosition = ref('center');
const openModal = (position) => {
currentPosition.value = position;
isOpen.value = true;
};
const getSizeByPosition = (position) => {
if (position === 'left' || position === 'right') {
return 'md'; // 侧边弹窗的默认大小
}
return 'md'; // 其他位置使用默认大小
};
const getTitleByPosition = (position) => {
const positionTitles = {
center: '居中弹窗',
top: '顶部弹窗',
bottom: '底部弹窗',
left: '左侧弹窗',
right: '右侧弹窗'
};
return positionTitles[position] || '弹窗示例';
};
const handleOk = () => {
isOpen.value = false;
};
const handleCancel = () => {
isOpen.value = false;
};
</script>
<style scoped>
.modal-demo {
padding: 20px;
background-color: var(--bg-card);
border-radius: var(--radius-md);
}
.demo-header {
margin-bottom: 24px;
}
.demo-header h2 {
margin: 0 0 8px 0;
font-size: var(--font-size-xl);
font-weight: var(--font-weight-semibold);
color: var(--text-primary);
}
.demo-header p {
margin: 0;
color: var(--text-secondary);
font-size: var(--font-size-sm);
}
.demo-content {
display: flex;
flex-direction: column;
gap: 16px;
}
.button-group {
display: flex;
gap: 12px;
flex-wrap: wrap;
}
.modal-body-content {
padding: 8px 0;
}
.modal-body-content p {
margin: 12px 0;
line-height: 1.6;
}
.responsive-note {
margin-top: 20px;
padding: 12px;
background-color: var(--bg-secondary);
border-radius: var(--radius-sm);
font-size: var(--font-size-sm);
}
.responsive-note strong {
display: block;
margin-bottom: 8px;
color: var(--text-primary);
}
.responsive-note p {
margin: 0;
color: var(--text-secondary);
}
</style>自定义内容
Modal 组件支持自定义头部、内容和底部,可以通过插槽来定制弹窗的各个部分。
自定义内容
Modal 组件支持自定义头部、内容和底部,可以通过插槽来定制弹窗的各个部分
vue
<template>
<div class="modal-demo">
<div class="demo-header">
<h2>自定义内容</h2>
<p>Modal 组件支持自定义头部、内容和底部,可以通过插槽来定制弹窗的各个部分</p>
</div>
<div class="demo-content">
<Button @click="openModal">打开自定义弹窗</Button>
<Modal
v-model:open="isOpen"
size="lg"
:closable="true"
:maskClosable="true"
@cancel="handleCancel"
>
<!-- 自定义头部 -->
<template #header>
<div class="custom-header">
<div class="header-icon">📝</div>
<div class="header-content">
<h3>自定义表单</h3>
<p>请填写以下表单信息</p>
</div>
</div>
</template>
<!-- 自定义内容 -->
<div class="custom-content">
<div class="form-group">
<label for="name">姓名</label>
<Input id="name" v-model="formData.name" placeholder="请输入姓名" />
</div>
<div class="form-group">
<label for="email">邮箱</label>
<Input id="email" v-model="formData.email" placeholder="请输入邮箱" type="email" />
</div>
<div class="form-group">
<label for="message">留言</label>
<Textarea id="message" v-model="formData.message" placeholder="请输入留言内容" rows="4" />
</div>
<div class="form-group">
<Checkbox v-model="formData.agree">我同意隐私政策</Checkbox>
</div>
</div>
<!-- 自定义底部 -->
<template #footer>
<div class="custom-footer">
<Button @click="resetForm" type="default">重置</Button>
<div class="action-buttons">
<Button @click="handleCancel" type="default">取消</Button>
<Button @click="handleSubmit" type="primary" :disabled="!formData.agree">提交</Button>
</div>
</div>
</template>
</Modal>
</div>
</div>
</template>
<script setup>
import { ref, reactive } from 'vue';
const isOpen = ref(false);
const formData = reactive({
name: '',
email: '',
message: '',
agree: false
});
const openModal = () => {
isOpen.value = true;
};
const resetForm = () => {
formData.name = '';
formData.email = '';
formData.message = '';
formData.agree = false;
};
const handleSubmit = () => {
if (!formData.agree) return;
console.log('表单提交:', formData);
// 这里可以添加表单提交逻辑
alert('表单提交成功!');
isOpen.value = false;
resetForm();
};
const handleCancel = () => {
isOpen.value = false;
resetForm();
};
</script>
<style scoped>
.modal-demo {
padding: 20px;
background-color: var(--bg-card);
border-radius: var(--radius-md);
}
.demo-header {
margin-bottom: 24px;
}
.demo-header h2 {
margin: 0 0 8px 0;
font-size: var(--font-size-xl);
font-weight: var(--font-weight-semibold);
color: var(--text-primary);
}
.demo-header p {
margin: 0;
color: var(--text-secondary);
font-size: var(--font-size-sm);
}
.demo-content {
display: flex;
flex-direction: column;
gap: 16px;
}
/* 自定义头部样式 */
.custom-header {
display: flex;
align-items: center;
gap: 12px;
padding: 0;
}
.header-icon {
font-size: 24px;
line-height: 1;
}
.header-content h3 {
margin: 0 0 4px 0;
font-size: var(--font-size-lg);
font-weight: var(--font-weight-semibold);
color: var(--text-primary);
}
.header-content p {
margin: 0;
font-size: var(--font-size-sm);
color: var(--text-secondary);
}
/* 自定义内容样式 */
.custom-content {
padding: 8px 0;
}
.form-group {
margin-bottom: 16px;
}
.form-group label {
display: block;
margin-bottom: 6px;
font-weight: var(--font-weight-medium);
color: var(--text-primary);
font-size: var(--font-size-sm);
}
.form-group :deep(.Input) {
width: 100%;
}
.form-group :deep(.Textarea) {
width: 100%;
}
/* 自定义底部样式 */
.custom-footer {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
}
.action-buttons {
display: flex;
gap: 8px;
}
</style>API 调用方式
除了组件方式,Modal 还提供了函数式 API,可以直接通过 JavaScript/TypeScript 调用。
函数式 API 说明
通过 showModal(options) 函数可以方便地在任何地方打开弹窗,无需在模板中预先声明组件。
该函数返回一个包含 close() 方法的对象,用于手动关闭弹窗。
vue
<template>
<div class="modal-demo">
<div class="demo-content">
<div class="button-group">
<Button @click="showBasicModal">基础弹窗</Button>
<Button @click="showCustomizedModal">自定义按钮</Button>
<Button @click="showAsyncModal">异步关闭</Button>
<Button @click="showMultipleModal">多弹窗嵌套</Button>
</div>
<div class="info-box">
<h3>函数式 API 说明</h3>
<p>
通过
<code>showModal(options)</code>
函数可以方便地在任何地方打开弹窗,无需在模板中预先声明组件。
</p>
<p>
该函数返回一个包含 <code>close()</code> 方法的对象,用于手动关闭弹窗。
</p>
</div>
</div>
</div>
</template>
<script setup>
// 基础弹窗示例
const showBasicModal = () => {
showModal({
title: '基础函数式弹窗',
content: '这是一个通过函数式 API 打开的基础弹窗示例。',
size: 'md',
onOk: () => {
console.log('点击了确定按钮');
// 可以在这里执行确认操作
},
onCancel: () => {
console.log('点击了取消按钮');
},
});
};
// 自定义按钮示例
const showCustomizedModal = () => {
const modal = showModal({
title: '自定义按钮',
content: '这个弹窗使用了自定义的底部按钮。',
size: 'sm',
footer: () => {
// 创建自定义底部
const footer = document.createElement('div');
footer.className = 'custom-footer';
footer.style.display = 'flex';
footer.style.justifyContent = 'flex-end';
footer.style.gap = '8px';
footer.style.width = '100px';
// 创建删除按钮
const deleteBtn = document.createElement('button');
deleteBtn.textContent = '删除';
deleteBtn.className = 'Button Button--default';
deleteBtn.style.backgroundColor = 'var(--color-danger)';
deleteBtn.style.color = 'white';
deleteBtn.style.padding = '4px 8px';
deleteBtn.style.borderRadius = '4px';
deleteBtn.onclick = () => {
alert('删除操作执行');
modal.close();
};
// 创建取消按钮
const cancelBtn = document.createElement('button');
cancelBtn.textContent = '取消';
cancelBtn.className = 'Button Button--default';
cancelBtn.onclick = () => {
modal.close();
};
footer.appendChild(cancelBtn);
footer.appendChild(deleteBtn);
return footer;
},
onClose: () => {
console.log('弹窗已关闭');
},
});
};
// 异步关闭示例
const showAsyncModal = () => {
let modal;
modal = showModal({
title: '异步关闭',
content: '点击确定后将模拟异步操作,操作完成后自动关闭弹窗。',
size: 'md',
onOk: () => {
// 禁用确定按钮,防止重复点击
modal.options.confirmLoading = true;
// 模拟异步操作
setTimeout(() => {
console.log('异步操作完成');
modal.close();
}, 2000);
// 返回 false 阻止自动关闭
return false;
},
onCancel: () => {
// 如果有正在进行的异步操作,可以在这里处理
console.log('取消操作');
},
});
};
// 多弹窗嵌套示例
const showMultipleModal = () => {
const outerModal = showModal({
title: '外层弹窗',
content: '这是一个外层弹窗,点击按钮可以打开内层弹窗。',
size: 'lg',
footer: () => {
const footer = document.createElement('div');
footer.className = 'custom-footer';
footer.style.display = 'flex';
footer.style.justifyContent = 'flex-end';
footer.style.gap = '8px';
// 创建打开内层弹窗按钮
const openInnerBtn = document.createElement('button');
openInnerBtn.textContent = '打开内层弹窗';
openInnerBtn.className = 'x-button x-button--primary';
openInnerBtn.onclick = () => {
console.log('点击了打开内层弹窗按钮');
// 打开内层弹窗
const innerModal = showModal({
title: '内层弹窗',
content: '这是一个嵌套在内层的弹窗。',
size: 'md',
onOk: () => {
console.log('内层弹窗确定');
},
onCancel: () => {
console.log('内层弹窗取消');
},
});
console.log('内层弹窗已创建:', innerModal);
};
// 创建关闭按钮
const closeBtn = document.createElement('button');
closeBtn.textContent = '关闭';
closeBtn.className = 'x-button x-button--default';
closeBtn.onclick = () => {
outerModal.close();
};
footer.appendChild(closeBtn);
footer.appendChild(openInnerBtn);
return footer;
},
onClose: () => {
console.log('外层弹窗已关闭');
},
});
};
</script>
<style scoped>
.modal-demo {
padding: 20px;
background-color: var(--bg-card);
border-radius: var(--radius-md);
}
.demo-header {
margin-bottom: 24px;
}
.demo-header h2 {
margin: 0 0 8px 0;
font-size: var(--font-size-xl);
font-weight: var(--font-weight-semibold);
color: var(--text-primary);
}
.demo-header p {
margin: 0;
color: var(--text-secondary);
font-size: var(--font-size-sm);
}
.demo-content {
display: flex;
flex-direction: column;
gap: 20px;
}
.button-group {
display: flex;
gap: 12px;
flex-wrap: wrap;
}
.info-box {
padding: 16px;
background-color: var(--bg-secondary);
border-radius: var(--radius-md);
border-left: 4px solid var(--color-primary);
}
.info-box h3 {
margin: 0 0 12px 0;
font-size: var(--font-size-base);
font-weight: var(--font-weight-semibold);
color: var(--text-primary);
}
.info-box p {
margin: 8px 0;
line-height: 1.6;
color: var(--text-secondary);
font-size: var(--font-size-sm);
}
.info-box code {
background-color: var(--bg-muted);
padding: 2px 6px;
border-radius: var(--radius-sm);
font-family: var(--font-family-mono);
font-size: var(--font-size-xs);
color: var(--text-primary);
}
</style>Modal Props
| 属性名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| id | string | - | 弹窗的唯一标识符 |
| open | boolean | - | 控制弹窗的显示和隐藏 |
| defaultOpen | boolean | false | 弹窗的默认显示状态 |
| title | string | - | 弹窗标题 |
| content | string | HTMLElement | (() => HTMLElement) | - | 弹窗内容(函数式API时使用) |
| size | 'sm' | 'md' | 'lg' | 'xl' | 'fullscreen' | 'md' | 弹窗尺寸 |
| position | 'center' | 'top' | 'bottom' | 'left' | 'right' | 'center' | 弹窗位置 |
| closable | boolean | true | 是否显示关闭按钮 |
| mask | boolean | true | 是否显示遮罩层 |
| maskClosable | boolean | true | 点击遮罩层是否可以关闭弹窗 |
| escClosable | boolean | true | 按 ESC 键是否可以关闭弹窗 |
| footer | boolean | HTMLElement | (() => HTMLElement) | true | 底部按钮配置 |
| width | string | number | - | 自定义宽度 |
| height | string | number | - | 自定义高度 |
| transitionDuration | number | 300 | 弹窗动画时长(毫秒) |
| maskTransitionDuration | number | 200 | 遮罩层动画时长(毫秒) |
事件
| 事件名 | 参数 | 说明 |
|---|---|---|
| open | value: boolean | 弹窗打开时触发 |
| close | value: boolean | 弹窗关闭时触发 |
| ok | - | 点击确定按钮时触发 |
| cancel | - | 点击取消按钮或关闭弹窗时触发 |
插槽
| 插槽名 | 说明 |
|---|---|
| default | 弹窗内容 |
| footer | 底部自定义内容 |
函数式 API
Modal 组件提供了 showModal 函数,可以通过编程方式打开弹窗:
showModal(options)
参数:
options:ModalOptions类型,包含以下属性:title:string- 弹窗标题content:string \| HTMLElement \| (() => HTMLElement)- 弹窗内容size:ModalSize- 弹窗尺寸position:ModalPosition- 弹窗位置closable:boolean- 是否显示关闭按钮maskClosable:boolean- 点击遮罩是否可以关闭escClosable:boolean- 按 ESC 键是否可以关闭footer:boolean \| HTMLElement \| (() => HTMLElement)- 底部配置onOk:() => void- 确定按钮回调onCancel:() => void- 取消按钮回调onClose:() => void- 关闭回调
返回值:
- 包含
close()方法的对象,用于手动关闭弹窗
使用示例
javascript
import { showModal } from '@/components/Modal';
// 基础用法
const modal = showModal({
title: '确认操作',
content: '确定要执行此操作吗?',
size: 'sm',
onOk: () => {
console.log('用户确认操作');
},
onCancel: () => {
console.log('用户取消操作');
}
});
// 手动关闭
// modal.close();响应式设计
Modal 组件在移动设备上会自动调整大小和位置,确保良好的用户体验。对于侧边弹窗(left/right),在小屏幕上会自动调整为全屏模式。
无障碍支持
Modal 组件支持无障碍功能,包括 ARIA 属性和键盘导航。默认情况下,弹窗会自动管理焦点,并支持通过 ESC 键关闭。
暗黑模式
Modal 组件完全支持暗黑模式,会根据当前主题自动调整样式。